Available Extensions
Extensions provide additional functionality for Weber. Currently there are two extensions availble:
Weber.@Cedrus
— Macro.@Cedrus()
Creates an extension for Weber allowing experiments to respond to events from Cedrus response-pad hardware. You can use iskeydown
and iskeyup
to check for events. To find the keycodes of the buttons for your response pad, run the following code, and press each of the buttons on the response pad.
run_keycode_helper(extensions=[@Cedrus()])
Do not call @Cedrus inside of a package or in tests. It should only be used in one-off scripts. If WeberCedrus is not currently installed it will be installed by this macro using Pkg.add
which can lead to problems when called in packages or tests.
If you want to include an extension without this behavior you can call @Cedrus_safe which mimics @Cedrus except that it will never call Pkg.add
.
Weber.@DAQmx
— Macro.@DAQmx(port;eeg_sample_rate,[codes])
Create a Weber extension that writes record
events to a digital out line via the DAQmx API. This can be used to send trigger codes during eeg recording.
Arguments
port: should be
nothing
, to disable the extension, or the port name for the digital output line.eeg_sample_rate: should be set to the sampling rate for eeg recording. This calibrates the code length for triggers.
codes: a Dict that maps record event codes (a string) to a number. This should be an Integer less than 256. Any codes not specified here will be automatically set, based on the order in which codes are recieved.
Example
The following experiment sends the code 0x01 to port0 on TestDevice.
port = "/TestDevice/port0/line0:7"
experiment = Experiment(extensions=[
@DAQmx(port;eeg_sample_rate=512,codes=Dict("test" => 0x01))])
setup(experiment) do
addtrial(moment(record,"test"))
end
run(experiment)
Do not call @DAQmx inside of a package or in tests. It should only be used in one-off scripts. If WeberDAQmx is not currently installed it will be installed by this macro using Pkg.add
which can lead to problems when called in packages or tests.
If you want to include an extension without this behavior you can call @DAQmx_safe which mimics @DAQmx except that it will never call Pkg.add
.
Creating Extensions
The following functions are used when extending experiments.
To register your extension within Weber, so users can import your extension with ease, you use can use the @extension
macro.
Weber.@extension
— Macro.@extension [Symbol] begin
[docstring...]
end
Registers a given Weber extension. This creates a macro called @[Symbol]
which imports Weber[Symbol]
and calls Weber[Symbol].InitExtension
, with the given arguments. InitExtension should return either nothing
or an extension object.
The doc string is used to document the usage of the extension, and should normally include a link to the website of a julia package for the extension.
Functions operating over extensions
These functions operate directly on an ExtendedExperiment
.
Base.next
— Method. next(experiment::ExtendedExperiment)
Get the next extended version of this experiment.
DataStructures.top
— Method.top(experiment::Experiment)
Get the the top-most extended verison for this experiment, if any.
Weber.extension
— Method.extension(experiment::ExtendedExperiment)
Get the extension object for this extended expeirment
Extendable Private Functions
Weber.poll_events
— Function. Weber.poll_events(callback,experiment,time)
Call the function callback
, possibility multiple times, passing it an event object each time. The time at which the events are polled is passed, allowing this time to be stored with the event.
This function should never be called directly by user code. A new method of this function can be implemented to extend Weber, allowing it to report new kinds events.
Private Moment Functions
New Weber.SimpleMoment
subtypes can define methods for the following functions to extend the runtime behavior of Weber.
Weber.prepare!
— Function.Weber.prepare!(m,[onset_s])
If there is anything the moment needs to do before it occurs, it is done during prepare!
. Prepare can be used to set up precise timing even when hardware latency is high, if that latency can be predicted, and accounted for. A moment's prepare! method is called just before the first non-zero pause between moments that occurs before this moment: in the simplest case, when this moment has a non-zero value for delta_t
, preapre! will occur delta_t
seconds before this moment. However, if several moments with no pause occur, prepare! will occur before all of those moments as well.
Prepare accepts an optional second argument used to indicate the time, in seconds from the start of the experiemnt when this moment will begin (as a Float64). This argument may be Inf, indicating that it is not possible to predict when the moment will occur at this point, because the timing depends on some stateful information (e.g. a participant's response). It is accetable in this case to throw an error, explaining that this kind of moment must be able to know precisely when it occurs to be prepared.
This method is part of the private interface for moments. It should not be called directly, but implemented as part of an extension. You need only extend the method taking a single arugment unless you intend to use this information during prepartion.
Weber.handle
— Function.handle(exp,queue,moment,to_handle)
Internal method to handle the given moment object in a manner specific to its type.
The function handle
is only called when the appropriate time has been reached for the next moment to be presented (according to delta_t
) or when an event occurs.
The to_handle
object is either a Float64
, indicating the current experiment time, or it is an ExpEvent
indicating the event that just occured. As an example, a timed moment, will run when it recieves any Float64
value, but nothing occurs when passed an event.
The queue is a MomentQueue
object, which has the same interface as the Dequeue
object (from the DataStructures
package) but it is also iterable. Upon calling handle, top(queue) == moment
.
Handle should return a boolean indicating whether the event was "handled" or not. If unhandled, the moment should remain on top of the queue. If returning true, handle should normally remove the top moment from the queue. Exceptions exist (for instance, to allow for loops), but one does not normally need to implement custom moments that have such behavior.
This method is part of the private interface for moments. It should not be called directly, but implemented as part of an extension. It is called during the course of running an experiment.
Weber.moment_trace
— Function.moment_trace(m)
Returns the stacktrace indicating where this moment was defined.
This method is part of the private interface for moments. It should not be called directly, but implemented as part of an extension. You can get a stacktrace inside the function you define that constructs your custom moment using stacktrace()[2:end]
.
Weber.delta_t
— Function.delta_t(m::AbstractMoment)
Returns the time, since the start of the previous moment, at which this moment should begin. The default implementation returns zero.
This method is part of the private interface for moments. It should not be called directly, but implemented as part of an extension.