Skip to content

Commit 904f277

Browse files
committed
update stimunit doc
1 parent f1713cb commit 904f277

File tree

1 file changed

+40
-30
lines changed

1 file changed

+40
-30
lines changed

docs/tutorials/build_stimunit.md

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# StimUnit: Modular Trial Controller
1+
# StimUnit: Modular Stimulus & Response Handler
22

33
## Overview
44

5-
`StimUnit` is a versatile, trial-level controller for PsychoPy experiments. It bundles everything you need for one trial into a single, chainable object:
5+
`StimUnit` is a versatile, stimulus-level controller for PsychoPy experiments. It bundles everything you need for one trial into a single, chainable object:
66

77
- **Stimulus presentation**: Draw multiple visual or audio stimuli together with sub-frame accuracy.
88
- **Response collection**: Detect keyboard events and record reaction times effortlessly.
99
- **Timing control**: Opt for frame-based (refresh-locked) or clock-based timing based on your needs.
1010
- **State management**: Store all trial-related data in a centralized internal dictionary.
1111
- **Event hooks**: Plug in custom callbacks at start, response, timeout, and end stages.
1212

13-
By using `StimUnit`, your experiment code becomes more modular, readable, and maintainable.
13+
By using `StimUnit`, your trial logic (typically defined in `src/run_trial.py`) becomes more modular, readable, and maintainable.
1414

1515
## Key Features
1616

@@ -40,7 +40,8 @@ By using `StimUnit`, your experiment code becomes more modular, readable, and ma
4040
| Full trial control | `.run()` | `unit.run()` |
4141
| Pause & continue | `.wait_and_continue(keys)` | `unit.wait_and_continue(['space'])` |
4242
| Update state | `.set_state(**kwargs)` | `unit.set_state(correct=True)` |
43-
| Retrieve state | `.to_dict()` or access `.state` | `data = unit.to_dict()` |
43+
| Retrieve state | `.get_state()` or access `.state` | `data = unit.get_state(key,default)` |
44+
| Export state | `.to_dict()` | `data = unit.to_dict()` |
4445

4546
## Detailed Usage Guide
4647

@@ -141,7 +142,6 @@ All added stimuli will be drawn or played together when you present the unit.
141142

142143
Note that `add_stim` also support PsychoPy style definitions of the stimulus. For exmaple:
143144
```python
144-
145145
from psychopy.visual import TextStim, Circle
146146
# Example 1
147147
stim_list = {'fix': TextStim(win, text='+', pos=(0,0)),
@@ -166,7 +166,7 @@ make_unit(unit_label=f"pop")\
166166

167167
### 3. Display stimulus with `show()`
168168

169-
The `.show()` method is the core display function in `StimUnit`. It handles precise timing, drawing, optional audio playback, and state logging—all in one call. Use it when you want to present stimuli without requiring responses.
169+
The `show()` method is the core display function in `StimUnit`. It handles precise timing, drawing, optional audio playback, and state logging—all in one call. Use it when you want to present stimuli without requiring responses.
170170

171171

172172
**Key Features of `show()`**
@@ -212,7 +212,6 @@ Use this method when you need standalone stimulus presentation without response
212212
The following examples demonstrate how to use `.show()` for fixed, jittered, and audio-driven durations. Assume `unit` is an initialized `StimUnit` and `stim_bank` contains your stimuli.
213213

214214
```python
215-
216215
# 1. Fixed duration – shows text for exactly 1.0 second
217216
unit.add_stim(stim_bank.get('fixation'))\
218217
.show(duration=1.0)
@@ -276,7 +275,6 @@ final_score = sum(trial.get("feedback_delta", 0) for trial in all_data)
276275
StimUnit('goodbye',win,kb)\
277276
.add_stim(stim_bank.get_and_format('good_bye', total_score=final_score))\
278277
.wait_and_continue(terminate=True)
279-
280278
```
281279

282280

@@ -367,7 +365,7 @@ target.to_dict(trial_data)
367365
- By default, `correct_keys=None`, so any key in `keys` is logged as a response.
368366
- `response_trigger` and `timeout_trigger` send triggers for EEG/behavior marking.
369367

370-
---
368+
371369

372370
#### Scenario 3: Detecting Correct vs. Incorrect Responses
373371
In tasks where only one key is correct (e.g., left vs. right dot detection), specify `correct_keys` to log hits vs. misses.
@@ -510,7 +508,7 @@ unit.add_stim(fix)
510508
```
511509
While `.show()` and `.capture_response()` bundle common patterns, you can achieve the same behavior using lifecycle hooks for greater customization.
512510

513-
#### Replicating `.show()` with Hooks
511+
#### Replicating `show()` with Hooks
514512
```python
515513
unit = StimUnit('show_demo', win, kb)
516514
unit.add_stim(my_stim)
@@ -625,25 +623,25 @@ Use `set_state()` to record key–value data into the unit’s state
625623

626624
**Example**
627625
```python
628-
# --- Feedback ---
629-
if early_response:
630-
delta = settings.delta * -1
631-
hit=False
626+
# --- Feedback ---
627+
if early_response:
628+
delta = settings.delta * -1
629+
hit=False
630+
else:
631+
hit = target.get_state("hit", False)
632+
if condition == "win":
633+
delta = settings.delta if hit else 0
634+
elif condition == "lose":
635+
delta = 0 if hit else settings.delta * -1
632636
else:
633-
hit = target.get_state("hit", False)
634-
if condition == "win":
635-
delta = settings.delta if hit else 0
636-
elif condition == "lose":
637-
delta = 0 if hit else settings.delta * -1
638-
else:
639-
delta = 0
640-
641-
hit_type = "hit" if hit else "miss"
642-
fb_stim = stim_bank.get(f"{condition}_{hit_type}_feedback")
643-
fb = make_unit(unit_label="feedback") \
644-
.add_stim(fb_stim) \
645-
.show(duration=settings.feedback_duration, onset_trigger=settings.triggers.get(f"{condition}_{hit_type}_fb_onset"))
646-
fb.set_state(hit=hit, delta=delta).to_dict(trial_data)
637+
delta = 0
638+
639+
hit_type = "hit" if hit else "miss"
640+
fb_stim = stim_bank.get(f"{condition}_{hit_type}_feedback")
641+
fb = make_unit(unit_label="feedback") \
642+
.add_stim(fb_stim) \
643+
.show(duration=settings.feedback_duration, onset_trigger=settings.triggers.get(f"{condition}_{hit_type}_fb_onset"))
644+
fb.set_state(hit=hit, delta=delta).to_dict(trial_data)
647645
```
648646
In this snippet, we:
649647

@@ -751,9 +749,21 @@ When you call `get_state()`, it first looks for the exact key, then for the pref
751749
```
752750

753751
#### Logging State Internally with `.log_unit()`
754-
- Writes all key–value pairs in `unit.state` to PsychoPy’s log via `logging.data`.
755-
- Automatically called at the end of `unit.run()`, so you normally don't need to invoke it yourself.
756752

753+
`log_unit()` writes every key–value pair in `unit.state` to the log score in `data/*.log`. It uses PsychoPy’s `logging.data()`, which by default appends timestamped entries to the experiment log file or console.
754+
It's automatically invoked within the `StimUnit` class, so you usually don’t need to call it manually.
755+
756+
757+
**What gets logged?** All entries currently in `unit.state`, including:
758+
759+
| State key examples | Description |
760+
|-------------------------------|----------------------------------------------|
761+
| `trial_block`, `trial_trial` | Pre-trial identifiers |
762+
| `onset_time`, `flip_time` | Timestamps from `.show()` or `.run()` |
763+
| `hit`, `response`, `rt` | Response metrics from `capture_response()` |
764+
| `feedback_hit`, `feedback_delta` | Custom values from your `set_state()` calls |
765+
766+
If you need a separate log for debugging at other points, you can call `unit.log_unit()` manually to snapshot current state.
757767

758768

759769

0 commit comments

Comments
 (0)