-
Notifications
You must be signed in to change notification settings - Fork 26
add support for vpixx pixel mode #571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,11 +39,11 @@ | |
| from expyfun import ExperimentController, building_doc | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be nice if this and the other sync utility has VPIXX be a user-set option, defined at the top of the script. (Since our lab and others may not always have this hardware online during an AV test.) And consider defining the Vpixx color bit specification(s) at the top as well, for users to customize the test around their physical Vpixx connections.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the general way of working in expyfun is to set everything that needs to be set, then let the sys config JSON make sure that things (triggering modes, response modes, etc.) get to and from the correct places / devices. As implemented, this code deviates from this pattern by protecting some setting from happening based on a conditional at the top of the file. What I think would be better would be this The "expyfun-standard" way to do this I think would be which means "decide whether or not to display vpixx pixel codes based on system expyfun.json". And end users can set Then at the end of the day the only thing that changes in this specific test is a single line addition of
|
||
| from expyfun.visual import Circle, Rectangle | ||
|
|
||
| print(__doc__) | ||
|
|
||
| SCREEN = 0 | ||
| FULL = False | ||
| WIN_SIZE = (1000, 1000) # or None for full screen if size matches config | ||
| USE_VPIXX = True | ||
| VPIXX_COLOR = [0, 1, 2, 3, 4, 5, 6, 7] # full red | ||
|
|
||
| n_channels = 2 | ||
| click_idx = [0] | ||
|
|
@@ -71,9 +71,12 @@ | |
| circle = Circle(ec, 1, units="deg", fill_color="k", line_color="w") | ||
| # Make a rectangle that is the standard credit card size | ||
| rect = Rectangle(ec, [0, 0, 8.56, 5.398], "cm", None, "#AA3377") | ||
| # set the vpixx trigger pixel | ||
| if USE_VPIXX: | ||
| ec.set_vpixx_color(VPIXX_COLOR) | ||
| while pressed != "8": # enable a clean quit if required | ||
| ec.set_background_color("white") | ||
| t1 = ec.start_stimulus(start_of_trial=False) # skip checks | ||
| t1 = ec.start_stimulus(start_of_trial=False, vpixx=USE_VPIXX) # skip checks | ||
| ec.set_background_color("black") | ||
| t2 = ec.flip() | ||
| diff = round(1000 * (t2 - t1), 2) | ||
|
|
@@ -86,5 +89,3 @@ | |
| ec.refocus() | ||
| pressed = ec.wait_one_press(0.5)[0] if not building_doc else "8" | ||
| ec.stop() | ||
|
|
||
| # ea.plot_screen(screenshot) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -581,6 +581,7 @@ def __init__( | |
| car = sum([np.sin(2 * np.pi * f * t) for f in [800, 1000, 1200]]) | ||
| self._beep = None | ||
| self._beep_data = np.tile(car * np.exp(-t * 10) / 4, (2, 3)) | ||
| self.vpixx_color = () | ||
|
|
||
| # finish initialization | ||
| logger.info("Expyfun: Initialization complete") | ||
|
|
@@ -794,7 +795,31 @@ def set_background_color(self, color="black"): | |
| self._bgcolor = _convert_color(color) | ||
| gl.glClearColor(*[c / 255.0 for c in self._bgcolor]) | ||
|
|
||
| def start_stimulus(self, start_of_trial=True, flip=True, when=None): | ||
| def set_vpixx_color(self, bits=()): | ||
| """Calculate color for vpixx "pixel mode" triggering, from the desired bits. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| bits: array-like of int | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As written, this approach seems to preclude setting all the pixel bits to low. A simple fix would be to allow an empty
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed in cc69b92 |
||
| The bits to be set high (0-indexed). Values between 0 and 23 are valid. | ||
| Note that these are the RGB bits *not the DSUB pins*; pins 1-4 and 14-17 | ||
| control the red value, pins 5-8 and 18-21 control the green value, and pins | ||
| 9-12 and 22-25 control the blue value (13 is ground). See | ||
| https://docs.vpixx.com/vocal/sending-triggers-with-pixel-mode for details. | ||
| """ | ||
| if not len(bits): | ||
| self.vpixx_color = () | ||
| return | ||
| bits = np.array(bits, dtype=int) | ||
| assert bits.min() >= 0 and bits.max() < 24, ( | ||
| "Vpixx color bits must be between 0 and 23" | ||
| ) | ||
| trig_out = np.exp2(bits).sum(dtype=int).item() | ||
| col = (trig_out % 256, (trig_out >> 8) % 256, (trig_out >> 16) % 256) | ||
| # this gets (unavoidably) passed to _convert_color later; must be in range [0,1] | ||
| self.vpixx_color = np.array(col) / 255 | ||
|
|
||
| def start_stimulus(self, start_of_trial=True, flip=True, when=None, vpixx=False): | ||
| """Play audio, (optionally) flip screen, run any "on_flip" functions. | ||
|
|
||
| Parameters | ||
|
|
@@ -812,6 +837,9 @@ def start_stimulus(self, start_of_trial=True, flip=True, when=None): | |
| flip completes (if `flip` is ``True``). As a result, in some | ||
| cases `when` should be set to a value smaller than your true | ||
| intended flip time. | ||
| vpixx : bool | ||
| Whether to display the vpixx trigger pixel on flip. See | ||
| :meth:`ExperimentController.flip` for details. Ignored if ``flip`` is False. | ||
|
|
||
| Returns | ||
| ------- | ||
|
|
@@ -844,7 +872,7 @@ def start_stimulus(self, start_of_trial=True, flip=True, when=None): | |
| self._on_next_flip = ( | ||
| [self._play] + self._ofp_critical_funs + self._on_next_flip | ||
| ) | ||
| stimulus_time = self.flip(when) | ||
| stimulus_time = self.flip(when, vpixx=vpixx) | ||
| else: | ||
| if when is not None: | ||
| self.wait_until(when) | ||
|
|
@@ -1150,7 +1178,7 @@ def _setup_window(self, window_size, exp_name, full_screen, screen): | |
| % (window_size, screen, self.dpi) | ||
| ) | ||
|
|
||
| def flip(self, when=None): | ||
| def flip(self, when=None, vpixx=False): | ||
| """Flip screen, then run any "on-flip" functions. | ||
|
|
||
| Parameters | ||
|
|
@@ -1161,6 +1189,11 @@ def flip(self, when=None): | |
| absolute) wait time before the flip completes. As a result, in | ||
| some cases `when` should be set to a value smaller than your | ||
| true intended flip time. | ||
| vpixx : bool | ||
| Whether to display the vpixx trigger pixel on this flip. Useful only with | ||
| Vpixx projectors set in "pixel mode". Will use the value of | ||
| ``ExperimentController.vpixx_color``, which can be computed from the desired | ||
| output trigger values with :meth:`~ExperimentController.set_vpixx_color`. | ||
|
|
||
| Returns | ||
| ------- | ||
|
|
@@ -1191,6 +1224,21 @@ def flip(self, when=None): | |
| if self.safe_flipping: | ||
| # On NVIDIA Linux these calls cause a 2x delay (33ms instead of 16) | ||
| gl.glFinish() | ||
| if vpixx: | ||
| if not len(self.vpixx_color): | ||
| raise RuntimeError( | ||
| "Vpixx pixel color not set; did you forget to call " | ||
| "ec.set_vpixx_color() ?" | ||
| ) | ||
| rect = Rectangle( | ||
| ec=self, | ||
| pos=(0.5, self.window_size_pix[1] - 0.5, 1, 1), | ||
| units="pix", | ||
| fill_color=self.vpixx_color, | ||
| line_color=None, | ||
| line_width=0.0, | ||
| ) | ||
| rect.draw() | ||
| self._win.flip() | ||
| # this waits until everything is called, including last draw | ||
| self._clear_rect.draw() | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically in expyfun we try to abstract complexity like this (I would have no idea how to switch to blue for example!). Ideally here the API would just allow for
set_vpixx_color("r")orset_vpixx_color("red").If
vpixxsupports full-spectrum(0-255, 0-255, 0-255)colors, then we should just usematplotlibto convert toint8, then convert this to vpixx internal coding.If
vpixxonly supports a subset of colors, I'd prefer:Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The VPixx DataPixx video and Pixel Mode digital output use the RGB color scheme. 8-bits for each of the Red, Blue, Green. I agree with Dan (below) about abstracting the colors for Expyfun scripts, because in practice our lab would ever use only a subset of the digital output pins and we want exact control over those pins because they will be tied to digital inputs of the Megin or other acquisition system.