Skip to content

Commit ebe73af

Browse files
Optimize clustering, add Whisper, CI workflow (#126)
* Add Whisper to requirements, update setup.py to read from requirements.txt - Add openai-whisper to requirements.txt for speech decoding - Refactor setup.py to read install_requires from requirements.txt - Update clustering.py to return NaN (not 0.5) when insufficient data - Remove debug print statements from clustering.py - Remove benchmark_cluster.py (temporary file) - Add CLAUDE.md to .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add GitHub Actions workflow for pytest and linting - Run tests on Python 3.9, 3.10, 3.11, 3.12 - Include coverage reporting - Add flake8 linting for syntax errors - Trigger on push to master/main/optimize-quail and PRs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add ReadTheDocs configuration and update doc requirements - Add .readthedocs.yaml for RTD v2 config - Update docs/doc_requirements.txt with modern package versions - Configure Python 3.11 build environment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix linter errors: remove dead code and fix typo - Remove unused compute_stimulus_stick function with undefined variables - Fix typo: experimeter_filter -> experimenter_filter 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add CI and docs badges to README, update speech decoding reference - Add GitHub Actions test status badge - Add ReadTheDocs documentation badge - Update speech decoding mention from Google Cloud to OpenAI Whisper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add ffmpeg and torch to CI for Whisper speech tests - Install ffmpeg via apt-get for audio processing - Install CPU-only torch for Whisper model inference - Install openai-whisper explicitly before package 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix simulate_list bug: use correct group range 1-16 The wordpool groups are numbered 1-16, not 0-15. Using range(16) would select group 0 which doesn't exist, causing sample() to fail. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6cbc63c commit ebe73af

12 files changed

Lines changed: 111 additions & 75 deletions

File tree

.github/workflows/tests.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ master, main, optimize-quail ]
6+
pull_request:
7+
branches: [ master, main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ['3.9', '3.10', '3.11', '3.12']
15+
fail-fast: false
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Set up Python ${{ matrix.python-version }}
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: ${{ matrix.python-version }}
24+
25+
- name: Install system dependencies
26+
run: |
27+
sudo apt-get update
28+
sudo apt-get install -y ffmpeg
29+
30+
- name: Install dependencies
31+
run: |
32+
python -m pip install --upgrade pip
33+
pip install pytest pytest-cov
34+
pip install torch --index-url https://download.pytorch.org/whl/cpu
35+
pip install openai-whisper
36+
pip install -e .
37+
38+
- name: Run tests
39+
run: |
40+
pytest tests/ -v --tb=short
41+
42+
- name: Run tests with coverage
43+
run: |
44+
pytest tests/ --cov=quail --cov-report=xml --cov-report=term-missing
45+
46+
lint:
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- name: Set up Python
52+
uses: actions/setup-python@v5
53+
with:
54+
python-version: '3.11'
55+
56+
- name: Install dependencies
57+
run: |
58+
python -m pip install --upgrade pip
59+
pip install flake8
60+
61+
- name: Lint with flake8
62+
run: |
63+
# Stop the build if there are Python syntax errors or undefined names
64+
flake8 quail --count --select=E9,F63,F7,F82 --show-source --statistics
65+
# Exit-zero treats all errors as warnings
66+
flake8 quail --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ dist/*
1212
venv/
1313
docs/_build
1414
docs/sg_execution_times.rst
15+
CLAUDE.md

.readthedocs.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Read the Docs configuration file for Sphinx projects
2+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3+
4+
version: 2
5+
6+
build:
7+
os: ubuntu-22.04
8+
tools:
9+
python: "3.11"
10+
11+
sphinx:
12+
configuration: docs/conf.py
13+
14+
python:
15+
install:
16+
- requirements: docs/doc_requirements.txt
17+
- method: pip
18+
path: .

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1003184.svg)](https://doi.org/10.5281/zenodo.1003184)
22
[![JOSS](http://joss.theoj.org/papers/3fb5123eb2538e06f6a25ded0a088b73/status.svg)](http://joss.theoj.org/papers/10.21105/joss.00424)
3+
[![Tests](https://github.com/ContextLab/quail/actions/workflows/tests.yml/badge.svg)](https://github.com/ContextLab/quail/actions/workflows/tests.yml)
4+
[![Documentation Status](https://readthedocs.org/projects/cdl-quail/badge/?version=latest)](https://cdl-quail.readthedocs.io/en/latest/?badge=latest)
35

46
![Quail logo](images/Quail_Logo_small.png)
57

@@ -12,7 +14,7 @@ Quail is a Python package that facilitates analyses of behavioral data from memo
1214
- Clustering metrics (e.g. single-number summaries of how often participants transition from recalling a word to another related word, where "related" can be user-defined.)
1315
- Many nice plotting functions
1416
- Convenience functions for loading in data
15-
- Automatically parse speech data (audio files) using wrappers for the Google Cloud Speech to Text API
17+
- Automatically parse speech data (audio files) using OpenAI Whisper
1618

1719
The intended user of this toolbox is a memory researcher who seeks an easy way to analyze and visualize data from free recall psychology experiments.
1820

benchmark_cluster.py

Lines changed: 0 additions & 37 deletions
This file was deleted.

docs/doc_requirements.txt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
sphinx==1.5.5
2-
sphinx_bootstrap_theme==0.4.13
1+
sphinx>=4.0
2+
sphinx_bootstrap_theme
33
sphinx-gallery
44
numpydoc
55
nbsphinx
6-
seaborn>=0.7.1
7-
matplotlib>=1.5.1
8-
scipy>=0.17.1
9-
numpy>=1.10.4
10-
pandas==0.18.1
11-
future
6+
seaborn>=0.12.0
7+
matplotlib>=3.5.0
8+
scipy>=1.10.0
9+
numpy>=2.0.0
10+
pandas>=2.0.0
11+
joblib>=1.3.0
1212
sqlalchemy
13-
dill
1413
requests
1514
pydub
16-
multiprocessing
1715
pathos
1816
jupyter_client
17+
ipykernel

quail/analysis/clustering.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,8 @@ def _get_weight_exact(egg, feature, distdict, permute, n_perms):
9696
rec = list(egg.get_rec_items().values[0])
9797

9898
if len(rec) <= 2:
99-
warnings.warn('Not enough recalls to compute fingerprint, returning default'
100-
'fingerprint.. (everything is .5)')
101-
return 0.5
99+
warnings.warn('Not enough recalls to compute fingerprint, returning NaN')
100+
return np.nan
102101

103102
distmat = get_distmat(egg, feature, distdict)
104103

@@ -174,14 +173,11 @@ def _get_weight_best(egg, feature, distdict, permute, n_perms, distance):
174173

175174
rec = list(egg.get_rec_items().values[0])
176175
if len(rec) <= 2:
177-
warnings.warn('Not enough recalls to compute fingerprint, returning default'
178-
'fingerprint.. (everything is .5)')
176+
warnings.warn('Not enough recalls to compute fingerprint, returning NaN')
179177
return np.nan
180178

181179
distmat = get_distmat(egg, feature, distdict)
182180
matchmat = get_match(egg, feature, distdict)
183-
print(f"DEBUG: matchmat.shape={matchmat.shape}, len(rec)={len(rec)}")
184-
print(f"DEBUG: distmat.shape={distmat.shape}")
185181

186182
ranks = []
187183
for i in range(len(rec)-1):

quail/fingerprint.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -370,13 +370,6 @@ def compute_feature_stick(features, weights, alpha):
370370
return feature_stick
371371

372372
def reorder_list(egg, feature_stick, dist_dict, tau):
373-
374-
def compute_stimulus_stick(s, tau):
375-
'''create a 'stick' of feature weights'''
376-
377-
feature_stick = [[weights[feature]]*round(weights[feature]**alpha)*100 for feature in w]
378-
return [item for sublist in feature_stick for item in sublist]
379-
380373
# parse egg
381374
pres, rec, features, dist_funcs = parse_egg(egg)
382375

quail/load.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ def getFeatures(stimDict):
387387

388388
# add custom filters
389389
if filters:
390-
filter_func = [adaptive_filter, experimeter_filter, experiments_filter] + filters
390+
filter_func = [adaptive_filter, experimenter_filter, experiments_filter] + filters
391391
else:
392392
filter_func = [adaptive_filter, experimenter_filter, experiments_filter]
393393

quail/simulate.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ def simulate_list(nwords=16, nrec=10, ncats=4):
1010
path = os.path.join(os.path.dirname(__file__), 'data/cut_wordpool.csv')
1111
wp = pd.read_csv(path)
1212

13-
# get one list
14-
# logic seems to pick a group random
15-
wp = wp[wp['GROUP']==np.random.choice(list(range(16)), 1)[0]].sample(16)
13+
# get one list - pick a random group (groups are 1-16)
14+
wp = wp[wp['GROUP']==np.random.choice(list(range(1, 17)), 1)[0]].sample(16)
1615

1716
wp['COLOR'] = [[int(np.random.rand() * 255) for i in range(3)] for i in range(16)]
1817

0 commit comments

Comments
 (0)