|
| 1 | +# Creating a Custom Stimulus: Dot Example |
| 2 | + |
| 3 | +This guide explains how to create a custom stimulus in Ethopy by examining the `dot.py` example, which implements a simple dot stimulus for visual experiments. |
| 4 | + |
| 5 | +## Overview of the Dot Stimulus |
| 6 | + |
| 7 | +The Dot stimulus displays a configurable dot (rectangular or oval) on the screen at specified coordinates and sizes. This type of stimulus is commonly used in: |
| 8 | + |
| 9 | +- Visual attention studies |
| 10 | +- Simple detection tasks |
| 11 | +- Eye movement tracking experiments |
| 12 | +- Timing response experiments |
| 13 | + |
| 14 | +## Defining the Stimulus Database Table |
| 15 | + |
| 16 | +Each stimulus requires defining a database table to store its parameters. Here's how the Dot stimulus defines its table: |
| 17 | + |
| 18 | +```python |
| 19 | +@stimulus.schema |
| 20 | +class Dot(Stimulus, dj.Manual): |
| 21 | + definition = """ |
| 22 | + # This class handles the presentation of area mapping Bar stimulus |
| 23 | + -> stimulus.StimCondition |
| 24 | + --- |
| 25 | + bg_level : tinyblob # 0-255 baground color |
| 26 | + dot_level : tinyblob # 0-255 dot color |
| 27 | + dot_x : float # (fraction of monitor width, 0 for center, from -0.5 to 0.5) position of dot on x axis |
| 28 | + dot_y : float # (fraction of monitor width, 0 for center) position of dot on y axis |
| 29 | + dot_xsize : float # fraction of monitor width, width of dots |
| 30 | + dot_ysize : float # fraction of monitor width, height of dots |
| 31 | + dot_shape : enum('rect','oval') # shape of the dot |
| 32 | + dot_time : float # (sec) time of each dot persists |
| 33 | + """ |
| 34 | +``` |
| 35 | + |
| 36 | +This table definition: |
| 37 | +- Uses the `@stimulus.schema` decorator to associate with the database |
| 38 | +- Inherits from both `Stimulus` (base class) and `dj.Manual` (DataJoint table class) |
| 39 | +- Defines a foreign key relationship with `stimulus.StimCondition` (parent table) |
| 40 | +- Specifies parameters for the dot's appearance: |
| 41 | + - Background and dot colors |
| 42 | + - Position (x, y coordinates as fractions of screen width) |
| 43 | + - Size (width and height as fractions of screen width) |
| 44 | + - Shape (rectangular or oval) |
| 45 | + - Duration (how long the dot persists) |
| 46 | + |
| 47 | +## Implementing the Stimulus Class |
| 48 | + |
| 49 | +The Dot class implements several key methods to control the stimulus lifecycle: |
| 50 | + |
| 51 | +```python |
| 52 | +def __init__(self): |
| 53 | + super().__init__() |
| 54 | + self.cond_tables = ['Dot'] |
| 55 | + self.required_fields = ['dot_x', 'dot_y', 'dot_xsize', 'dot_ysize', 'dot_time'] |
| 56 | + self.default_key = {'bg_level': 1, |
| 57 | + 'dot_level': 0, # degrees |
| 58 | + 'dot_shape': 'rect'} |
| 59 | +``` |
| 60 | + |
| 61 | +### 1. `__init__()` method |
| 62 | + |
| 63 | +This initializes the stimulus and defines: |
| 64 | + |
| 65 | +- `self.cond_tables`: List of condition tables this stimulus uses (just 'Dot' in this case) |
| 66 | +- `self.required_fields`: Parameters that must be provided for this stimulus |
| 67 | +- `self.default_key`: Default values for optional parameters |
| 68 | + |
| 69 | +### 2. `prepare()` method |
| 70 | + |
| 71 | +```python |
| 72 | +def prepare(self, curr_cond): |
| 73 | + self.curr_cond = curr_cond |
| 74 | + self.fill_colors.background = self.curr_cond['bg_level'] |
| 75 | + self.Presenter.set_background_color(self.curr_cond['bg_level']) |
| 76 | + width = self.monitor.resolution_x |
| 77 | + height = self.monitor.resolution_y |
| 78 | + x_start = self.curr_cond['dot_x'] * 2 |
| 79 | + y_start = self.curr_cond['dot_y'] * 2 * width/height |
| 80 | + self.rect = (x_start - self.curr_cond['dot_xsize'], |
| 81 | + y_start - self.curr_cond['dot_ysize']*width/height, |
| 82 | + x_start + self.curr_cond['dot_xsize'], |
| 83 | + y_start + self.curr_cond['dot_ysize']*width/height) |
| 84 | +``` |
| 85 | + |
| 86 | +This method: |
| 87 | +1. Stores the current condition parameters |
| 88 | +2. Sets the background color |
| 89 | +3. Calculates the dot's position and size based on: |
| 90 | + - Monitor resolution (to maintain aspect ratio) |
| 91 | + - Condition parameters for position and size |
| 92 | + - Conversion from normalized coordinates to actual screen coordinates |
| 93 | +4. Creates a rectangle tuple (left, top, right, bottom) for drawing |
| 94 | + |
| 95 | +### 3. `start()` method |
| 96 | + |
| 97 | +```python |
| 98 | +def start(self): |
| 99 | + super().start() |
| 100 | + self.Presenter.draw_rect(self.rect, self.curr_cond['dot_level']) |
| 101 | +``` |
| 102 | + |
| 103 | +This method: |
| 104 | +1. Calls the parent class's start method (which initializes timing) |
| 105 | +2. Draws the rectangle using the precalculated coordinates and the specified color |
| 106 | + |
| 107 | +### 4. `present()` method |
| 108 | + |
| 109 | +```python |
| 110 | +def present(self): |
| 111 | + if self.timer.elapsed_time() > self.curr_cond['dot_time']*1000: |
| 112 | + self.in_operation = False |
| 113 | + self.Presenter.fill(self.fill_colors.background) |
| 114 | +``` |
| 115 | + |
| 116 | +This method: |
| 117 | +1. Checks if the dot's display time has elapsed (converting seconds to milliseconds) |
| 118 | +2. If the time has elapsed: |
| 119 | + - Marks the stimulus as no longer in operation |
| 120 | + - Fills the screen with the background color (removing the dot) |
| 121 | + |
| 122 | +### 5. `stop()` and `exit()` methods |
| 123 | + |
| 124 | +```python |
| 125 | +def stop(self): |
| 126 | + self.log_stop() |
| 127 | + self.Presenter.fill(self.fill_colors.background) |
| 128 | + self.in_operation = False |
| 129 | + |
| 130 | +def exit(self): |
| 131 | + self.Presenter.fill(self.fill_colors.background) |
| 132 | + super().exit() |
| 133 | +``` |
| 134 | + |
| 135 | +These methods handle cleanup: |
| 136 | +- `stop()`: Called when the stimulus needs to be stopped during operation |
| 137 | + - Logs the stop event |
| 138 | + - Clears the screen |
| 139 | + - Marks the stimulus as no longer in operation |
| 140 | + |
| 141 | +- `exit()`: Called when the experiment is ending |
| 142 | + - Clears the screen |
| 143 | + - Calls the parent class's exit method for additional cleanup |
| 144 | + |
| 145 | +## Stimulus Lifecycle |
| 146 | + |
| 147 | +The dot stimulus follows this lifecycle: |
| 148 | + |
| 149 | +1. **Initialization**: Set up parameters and default values |
| 150 | +2. **Preparation**: Calculate positioning based on current conditions |
| 151 | +3. **Start**: Draw the dot on the screen |
| 152 | +4. **Present**: Monitor timing and remove the dot when its time expires |
| 153 | +5. **Stop/Exit**: Clean up resources and reset the display |
| 154 | + |
| 155 | +## How to Create Your Own Stimulus |
| 156 | + |
| 157 | +To create your own stimulus: |
| 158 | + |
| 159 | +1. **Define a database table** with appropriate parameters for your stimulus |
| 160 | +2. **Create a stimulus class** that inherits from the base `Stimulus` class |
| 161 | +3. **Specify required fields and defaults** in the `__init__` method |
| 162 | +4. **Implement preparation logic** to set up your stimulus based on conditions |
| 163 | +5. **Create presentation methods** that control how the stimulus appears: |
| 164 | + - `start()`: Initial display |
| 165 | + - `present()`: Continuous updates (if needed) |
| 166 | + - `stop()` and `exit()`: Cleanup |
| 167 | + |
| 168 | +Your stimulus should handle: |
| 169 | +- **Positioning** on the screen (considering aspect ratio) |
| 170 | +- **Timing** of presentation |
| 171 | +- **Transitions** stimulus conditions between states |
| 172 | +- **Cleanup** to ensure the display returns to a neutral state |
| 173 | + |
| 174 | +By following this pattern, you can create diverse visual stimuli for behavioral experiments, from simple dots and shapes to complex moving patterns or interactive elements. |
0 commit comments