Skip to content

Commit

Permalink
added confirmation box
Browse files Browse the repository at this point in the history
  • Loading branch information
sammosummo committed Aug 9, 2018
1 parent 9959322 commit 956c25c
Show file tree
Hide file tree
Showing 36 changed files with 331 additions and 158 deletions.
92 changes: 92 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
********
Charlie2
********

:Version: 2.0
:Author: Samuel R. Mathias
:Contact: samuel.mathias@yale.edu
:Github: http://github.com/sammosummo/Charlie2/
:License: MIT

Introduction
============

Charlie2 is a free, open-source, cross-platform neurocognitive test battery written in
Python, with a focus on extendability. It will be used to collect data for one of our
lab projects, but may be used freely by others.

What does it do?
================

Like its predecessor, Charlie2 runs neurocognitive tests. There are currently 9 tests in
the battery, taking ~30 minutes to complete per proband. Each test has a docstring with
citations; have a look in the `charlie2/tests` folder to see what is available.

Notable features
================

* Since Charlie2 is written in Python, it is cross-platform. I have had success running
it on various platforms, from tablets running Windows 10 to Raspberry Pis.

* Charlie2 works especially well on touchscreen devices.

* Charlie2 has a GUI which allows the user to store/view proband metadata (e.g., their
age, sex, and miscellaneous notes made before or after testing), run tests
individually or in pre-defined batches, and back up data to a remote storage server
such as Google Drive.

* Modifying or adding new tests to Charlie2 is quick, easy and Pythonic.

* Data are recorded after each trial. This means that you have access to trial-specific
data rather than just the summary data. It also means that the tests are resumable;
that is, the progress of each proband is retained. This prevents a proband from
performing a test twice, and allows them to pick up where they left off, if a test
gets interrupted.

* Summary statistics are automatically computed after a proband completes a test. All of
the data (summary and trial-specific) are stored within various formats, including
human-readable csv files and Python pickles.

What's changed
==============

Charlie2 has an entirely new code base. Below are the most significant changes.

* Charlie2 is written in/for Python 3.6 or greater. It does not work with Python 2, and
probably won't work with earlier versions of Python 3.

* Rather than relying on command-line arguments, Charlie2 is GUI-based.

* Most of the heavy lifting is done by PyQt5, not pygame, which is no longer a
dependency.

* Questionnaires have been completely removed.

What (still) doesn't work
=========================

* Charlie2 is not stand-alone.

* Charlie2 is **not** currently a regular Python package, so isn't installable via
`pip`. This probably won't ever happen.

Installation and usage
======================

Charlie is simply a collection of Python scripts. All functionality is accessed from a
PyQt5 application which is launched via `main.py`. If this is not enough information for
you, I recommend performing the following steps:

1. Download and install Miniconda for your platform from here: https://repo.continuum.io/miniconda/

2. Create a new conda environment called `Charlie2` and `activate` it.

3. Run these commands:
::
conda update conda
conda install pip pyqt pandas
pip install google-api-python-client oauth2client

4. Download Charlie2: https://github.com/sammosummo/Charlie2/archive/master.zip

5. `cd` to the directory you saved Charlie2 and run `python main.py`.
7 changes: 3 additions & 4 deletions charlie2/_scratch/_geometry.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
import cmocean
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm

from scipy import interpolate

cmap = cm.ScalarMappable(cmap=cmocean.cm.phase)
cmap.to_rgba([0., 0.5, 1.])
Expand Down
3 changes: 2 additions & 1 deletion charlie2/_scratch/_intersection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np
import matplotlib.pyplot as plt
import numpy as np


"""
Sukhbinder
Expand Down
1 change: 1 addition & 0 deletions charlie2/_scratch/commandline.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""
import argparse
from argparse import ArgumentParser

from .recipes import str2bool


Expand Down
1 change: 1 addition & 0 deletions charlie2/_scratch/event_tester.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sys import argv, exit

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget

Expand Down
21 changes: 9 additions & 12 deletions charlie2/_scratch/make_vwm.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from charlie2.tools.recipes import get_vwm_stimuli
import wave
from tqdm import tqdm
from os import listdir

if __name__ == '__main__':
from tqdm import tqdm

from charlie2.tools.recipes import get_vwm_stimuli

if __name__ == "__main__":
# sequences = [item for sublist in get_vwm_stimuli("en") for item in sublist]
# s = "/Users/smathias/Documents/Charlie2/charlie2/stimuli/audio/verbalworkingmemory"
# for sequence in sequences:
Expand All @@ -23,20 +25,15 @@
# output.close()
p = "/Users/smathias/Documents/Charlie2/charlie2/stimuli/audio/verbalworkingmemory/"
s = "/Users/smathias/Documents/Charlie2/charlie2/stimuli/audio/common/silence.wav"
s = wave.open(s, 'rb')
for f in [f for f in listdir(p) if '.wav' in f]:
w = wave.open(p + f, 'rb')
s = wave.open(s, "rb")
for f in [f for f in listdir(p) if ".wav" in f]:
w = wave.open(p + f, "rb")
data = []
data.append([s.getparams(), s.readframes(s.getnframes())])
data.append([w.getparams(), w.readframes(w.getnframes())])
w.close()
output = wave.open('tmp.wav', 'wb')
output = wave.open("tmp.wav", "wb")
output.setparams(data[0][0])
for i in range(2):
output.writeframes(data[i][1])
output.close()





3 changes: 2 additions & 1 deletion charlie2/_scratch/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
the user has access to.
"""
from __future__ import print_function

from apiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
from oauth2client import client, file, tools

# Setup the Drive v3 API
SCOPES = [
Expand Down
11 changes: 0 additions & 11 deletions charlie2/_scratch/tmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,14 +798,3 @@
# def play_feedback_sound(self, correct):
# """Play either the correct sound if true or incorrect sound if false."""
# self.feedback_sounds[correct].play()











8 changes: 4 additions & 4 deletions charlie2/_scratch/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import datetime
import hashlib
import mimetypes
import time
import os
import time

import httplib2
from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
from apiclient.http import MediaFileUpload
from oauth2client import client, tools
from oauth2client.file import Storage

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/drive-python-quickstart.json
Expand Down
Binary file added charlie2/stimuli/audio/common/440.wav
Binary file not shown.
Binary file modified charlie2/stimuli/audio/common/correct.wav
Binary file not shown.
Binary file modified charlie2/stimuli/audio/common/incorrect.wav
Binary file not shown.
Binary file added charlie2/stimuli/audio/common/new_block.wav
Binary file not shown.
Binary file added charlie2/stimuli/audio/common/test_over.wav
Binary file not shown.
23 changes: 13 additions & 10 deletions charlie2/tests/digitsymbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,21 @@
814–823.
"""
from sys import gettrace

from charlie2.tools.stats import basic_summary

__version__ = 2.0
__author__ = "Sam Mathias"


from logging import getLogger
from typing import Dict, List, Union
from typing import Dict, List

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent

from charlie2.tools.stats import basic_summary

from ..tools.basetestwidget import BaseTestWidget
from ..tools.recipes import digits, symbols

__version__ = 2.0
__author__ = "Sam Mathias"


logger = getLogger(__name__)


Expand All @@ -71,10 +69,11 @@ def __init__(self, parent=None) -> None:
3. Hide the mouse.
4. Define x-positions of digits and symbols in the key.
5. Load and hide the key.
6. Set a flag so that skipped trials are deleted rather than stored.
"""
super(TestWidget, self).__init__(parent)
if gettrace() is None:
if self.debugging is False:
self.block_deadline = 90 * 1000
else:
self.block_deadline = 4 * 1000
Expand All @@ -84,6 +83,7 @@ def __init__(self, parent=None) -> None:
self.xs = range(-300, 350, 75)
self.digit = None
self.symbol = None
self.delete_skipped = True

def make_trials(self) -> List[Dict[str, int]]:
"""Generates new trials.
Expand Down Expand Up @@ -161,11 +161,14 @@ def keyReleaseEvent_(self, event: QKeyEvent) -> None:
self.symbol.deleteLater()
self.digit.deleteLater()
if t.practice:
self.performing_trial = False
s = self.instructions[7:9][not t.correct]
a = self.display_text(s, (0, -100))
self.play_feeback(t.correct)
self.sleep(1000)
a.hide()
a.deleteLater()
self._performing_trial = True

def summarise(self) -> Dict[str, int]:
"""Summarises the data.
Expand Down
15 changes: 8 additions & 7 deletions charlie2/tests/emotionrecognition.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,19 @@
Neurosci. Methods, 115:137–143.
"""
from logging import getLogger
from typing import Dict, List

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent

from charlie2.tools.basetestwidget import BaseTestWidget
from charlie2.tools.stats import basic_summary

__version__ = 2.0
__author__ = "Sam Mathias"


from logging import getLogger

from PyQt5.QtCore import Qt

from charlie2.tools.basetestwidget import BaseTestWidget

logger = getLogger(__name__)


Expand All @@ -55,7 +52,10 @@ def __init__(self, parent=None) -> None:
"""
super(TestWidget, self).__init__(parent)
self.mouse_visible = False
self.block_deadline = 300 * 1000
if self.debugging:
self.block_deadline = 4 * 1000
else:
self.block_deadline = 240 * 1000

def make_trials(self) -> List[Dict[str, int]]:
"""Generates new trials.
Expand All @@ -66,6 +66,7 @@ def make_trials(self) -> List[Dict[str, int]]:
2. `face` (:obj:`str`)
3. `emotion` (:obj:`str`)
"""

def e(s):
if "N" in s:
return "neutral"
Expand Down
24 changes: 14 additions & 10 deletions charlie2/tests/facialmemory.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,21 @@
performance and ”effort”. Brain Cogn.,33, 388-414.
"""
from logging import getLogger
from sys import gettrace
from typing import Dict, List

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent

from charlie2.tools.basetestwidget import BaseTestWidget

from ..tools.stats import basic_summary

__version__ = 2.0
__author__ = "Sam Mathias"


from logging import getLogger

from PyQt5.QtCore import Qt

from charlie2.tools.basetestwidget import BaseTestWidget

logger = getLogger(__name__)


Expand Down Expand Up @@ -142,13 +140,16 @@ def block(self) -> None:
"""
b = self.current_trial.block_number
if b == 0:
if gettrace() is None:
if self.debugging is False:
self.trial_deadline = int(2.5 * 1000)
else:
self.trial_deadline = 100
else:
self.trial_deadline = None
self.block_deadline = 300 * 1000
if self.debugging:
self.block_deadline = 4 * 1000
else:
self.block_deadline = 240 * 1000
self.display_instructions_with_space_bar(self.instructions[4 + b])

def trial(self) -> None:
Expand Down Expand Up @@ -192,6 +193,9 @@ def summarise(self) -> Dict[str, int]:
dict: Summary statistics.
"""
trials = [t for t in self.procedure.completed_trials if
t["block_type"] == "recognition"]
trials = [
t
for t in self.procedure.completed_trials
if t["block_type"] == "recognition"
]
return basic_summary(trials)
Loading

0 comments on commit 956c25c

Please sign in to comment.