diff --git a/articles/hardware/breakout/digital-inputs.md b/articles/hardware/breakout/digital-inputs.md index 8d30c0af..815afb20 100644 --- a/articles/hardware/breakout/digital-inputs.md +++ b/articles/hardware/breakout/digital-inputs.md @@ -14,15 +14,16 @@ functionality by responding to button presses and saves digital inputs data. ::: The operator generates a sequence of -[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). In this workflow, digital inputs -are configured to be asynchronous. This means that although the digital inputs -are sampled in hardware at 4 Mhz, data frames are only emitted when the port -status changes (i.e., when a pin, button, or switch is toggled). Digital inputs -can also be +[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). In this +workflow, digital inputs are configured to be asynchronous. This means that +although the digital inputs are sampled in hardware at 4 Mhz, data frames are +only emitted when the port status changes (i.e., when a pin, button, or switch +is toggled) when the `SampleRate` is left blank such as is done in this example +workflow. Digital inputs can also be [configured](xref:OpenEphys.Onix1.ConfigureBreakoutBoard#OpenEphys_Onix1_ConfigureBreakoutBoard_DigitalIO) to be sampled at regular intervals. The digital input ports on the Breakout -Board use 3.3V logic levels but are also 5V tolerant. In the Breakout -Board example workflow, the `DigitalInput`'s `DeviceName` property is set to +Board use 3.3V logic levels but are also 5V tolerant. In the Breakout Board +example workflow, the `DigitalInput`'s `DeviceName` property is set to "BreakoutBoard/DigitalInput". This links the `DigitalInput` operator to the corresponding configuration operator. diff --git a/articles/hardware/hs64/estim.md b/articles/hardware/hs64/estim.md index b9472976..91ee8d8e 100644 --- a/articles/hardware/hs64/estim.md +++ b/articles/hardware/hs64/estim.md @@ -3,26 +3,50 @@ uid: hs64_estim title: Headstage 64 Electrical Stimulation --- -The following excerpt from the Headstage 64 [example workflow](xref:hs64_workflow) demonstrates electrical stimulation by -triggering a train of pulses following a press of the △ key on the breakout board. +The following excerpt from the Headstage 64 [example +workflow](xref:hs64_workflow) demonstrates electrical stimulation by triggering +a train of pulses following a press of the △ key on the breakout board. + +> [!NOTE] +> Only one (electrical or optical) stimulator can armed at a time. If both +> stimulators are armed, the electrical stimulator takes precedence, e.g. +> the electrical stimulator stays armed and the optical stimulator is +> automatically disarmed by the headstage firmware. If you want to interleave +> optical stimulation and electrical stimulation, you must coordinate the +> stimulators to be dynamically armed and disarmed. ::: workflow ![/workflows/hardware/hs64/estim.bonsai workflow](../../../workflows/hardware/hs64/estim.bonsai) ::: The operator generates a sequence of -[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). Although the digital inputs -are sampled at 4 Mhz, these data frames are only emitted when the port status changes (i.e., when a -pin, button, or switch is toggled). In the Breakout Board example workflow, the `DigitalInput`'s -`DeviceName` property is set to "BreakoutBoard/DigitalInput". This links the `DigitalInput` operator -to the corresponding configuration operator. +[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). Although +the digital inputs are sampled at 4 Mhz, these data frames are only emitted when +the port status changes (i.e., when a pin, button, or switch is toggled) when +`DigitalInput`'s `SampleRate` property is left blank such as is done in the +example workflow. The `DigitalInput`'s `DeviceName` property is set to +"BreakoutBoard/DigitalInput". This links the `DigitalInput` operator to the +corresponding configuration operator. - is selected from the `DigitalInputDataFrame`. It is an enumerator with values -that correspond to bit positions of the breakout board's digital port. When this type is connected to a `HasFlags` -operator, the enumerated values appear in the `HasFlags`'s `Value` property's dropdown menu. Because `HasFlags`'s -`Value` is set to "Triangle", its output is "True" when the selected `BreakoutButtonState` bit field contains the -"Triangle" flag. +[Buttons](xref:OpenEphys.Onix1.BreakoutButtonState) is selected from the +`DigitalInputDataFrame` and passed to a `HasFlags` operator, which filters the +sequence based on which button is pressed using the `Value` property's dropdown +menu. In this case, `HasFlags`'s `Value` is set to "Triangle", so its output is +"True" when an item its input sequence contains a "Triangle" flag. The + operator only passes an item in its +input sequence if it's different from the previous item in the input sequence. +The operator only passes an item in its input +sequence if `Condition`'s internal logic is "True". In this case, `Condition` +has no internal logic (which can be inspected by selecting the node and pressing +Ctrl+Enter), so it uses the value of the Boolean in its input +sequence to decide whether or not to pass through an item in its input sequence +to its output sequence. -When the operator receives a "True" value in its input -sequence, a stimulus waveform is triggered. The waveform can be modified by editing the -`Headstage64ElectricalStimulatorTrig` operator's properties. \ No newline at end of file +The operator emits a + determined by `Double`'s `Value` property whenever it +receives an item in its input sequence. Each double in the input sequence +received by +triggers an electrical stimulus waveform. The value of the double determines the +delay in microseconds, executed on the hardware, between triggering the stimulus +and delivery of the stimulus. When `Double`'s `Value` property is set to zero, +there is no delay. \ No newline at end of file diff --git a/articles/hardware/hs64/gpo-trigger.md b/articles/hardware/hs64/gpo-trigger.md new file mode 100644 index 00000000..48befe4d --- /dev/null +++ b/articles/hardware/hs64/gpo-trigger.md @@ -0,0 +1,44 @@ +--- +uid: hs64_gpo-trigger +title: Headstage 64 GPO Trigger +--- + +The following excerpt from the Headstage 64 [example +workflow](xref:hs64_workflow) demonstrates triggering a stimulus following a +press of the X key on the breakout board. The GPO trigger toggles a pin on the +headstage to trigger stimulus which occurs more instantaneously than writing to +a register on the headstage which is how the + and + operators trigger +stimuli. + +> [!NOTE] +> Only one (electrical or optical) stimulator can armed at a time. If both +> stimulators are armed, the electrical stimulator takes precedence, e.g. +> the electrical stimulator stays armed and the optical stimulator is +> automatically disarmed by the headstage firmware. If you want to interleave +> optical stimulation and electrical stimulation, you must coordinate the +> stimulators to be dynamically armed and disarmed. + +::: workflow +![/workflows/hardware/hs64/gpo-trigger.bonsai workflow](../../../workflows/hardware/hs64/gpo-trigger.bonsai) +::: + +The operator generates a sequence of +[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). Although +the digital inputs are sampled at 4 Mhz, these data frames are only emitted when +the port status changes (i.e., when a pin, button, or switch is toggled) when +`DigitalInput`'s `SampleRate` property is left blank such as is done in the +example workflow. The `DigitalInput`'s `DeviceName` property is set to +"BreakoutBoard/DigitalInput". This links the `DigitalInput` operator to the +corresponding configuration operator. + +[Buttons](xref:OpenEphys.Onix1.BreakoutButtonState) is selected from the +`DigitalInputDataFrame` and passed to a `HasFlags` operator, which filters the +sequence based on which button is pressed using the `Value` property's dropdown +menu. In this case, `HasFlags`'s `Value` is set to "Square", so its output is +"True" when an item its input sequence contains a "Square" flag. The + operator only passes an item in its +input sequence if it's different from the previous item in the input sequence. +When the operator receives a "True" +value in its input sequence, a stimulus waveform is triggered. \ No newline at end of file diff --git a/articles/hardware/hs64/ostim.md b/articles/hardware/hs64/ostim.md index 350e612d..3daec794 100644 --- a/articles/hardware/hs64/ostim.md +++ b/articles/hardware/hs64/ostim.md @@ -3,26 +3,50 @@ uid: hs64_ostim title: Headstage 64 Optical Stimulation --- -The following excerpt from the Headstage64 [example workflow](xref:hs64_workflow) demonstrates optical stimulation by -triggering a train of pulses following a press of the ◯ key on the breakout board. +The following excerpt from the Headstage64 [example +workflow](xref:hs64_workflow) demonstrates optical stimulation by triggering a +train of pulses following a press of the ◯ key on the breakout board. + +> [!NOTE] +> Only one (electrical or optical) stimulator can armed at a time. If both +> stimulators are armed, the electrical stimulator takes precedence, e.g. +> the electrical stimulator stays armed and the optical stimulator is +> automatically disarmed by the headstage firmware. If you want to interleave +> optical stimulation and electrical stimulation, you must coordinate the +> stimulators to be dynamically armed and disarmed. ::: workflow ![/workflows/hardware/hs64/ostim.bonsai workflow](../../../workflows/hardware/hs64/ostim.bonsai) ::: The operator generates a sequence of -[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). Although the digital inputs -are sampled at 4 Mhz, these data frames are only emitted when the port status changes (i.e., when a -pin, button, or switch is toggled). In the Breakout Board example workflow, the `DigitalInput`'s -`DeviceName` property is set to "BreakoutBoard/DigitalInput". This links the `DigitalInput` operator -to the corresponding configuration operator. +[DigitalInputDataFrames](xref:OpenEphys.Onix1.DigitalInputDataFrame). Although +the digital inputs are sampled at 4 Mhz, these data frames are only emitted when +the port status changes (i.e., when a pin, button, or switch is toggled) when +`DigitalInput`'s `SampleRate` property is left blank such as is done in the +example workflow. The `DigitalInput`'s `DeviceName` property is set to +"BreakoutBoard/DigitalInput". This links the `DigitalInput` operator to the +corresponding configuration operator. - is selected from the `DigitalInputDataFrame`. It is an enumerator with values -that correspond to bit positions of the breakout board's digital port. When this type is connected to a `HasFlags` -operator, the enumerated values appear in the `HasFlags`'s `Value` property's dropdown menu. Because `HasFlags`'s -`Value` is set to "Circle", its output is "True" when the selected `BreakoutButtonState` bit field contains the -"Circle" flag. +[Buttons](xref:OpenEphys.Onix1.BreakoutButtonState) is selected from the +`DigitalInputDataFrame` and passed to a `HasFlags` operator, which filters the +sequence based on which button is pressed using the `Value` property's dropdown +menu. In this case, `HasFlags`'s `Value` is set to "Circle", so its output is +"True" when an item its input sequence contains a "Circle" flag. The + operator only passes an item in its +input sequence if it's different from the previous item in the input sequence. +The operator only passes an item in its input +sequence if `Condition`'s internal logic is "True". In this case, `Condition` +has no internal logic (which can be inspected by selecting the node and pressing +Ctrl+Enter), so it uses the value of the Boolean in its input +sequence to decide whether or not to pass through an item in its input sequence +to its output sequence. -When the operator receives a "True" value in its input -sequence, a stimulus waveform is triggered. The waveform can be modified by editing the -`Headstage64OpticalStimulatorTrig` operator's properties. \ No newline at end of file +The operator emits a + determined by `Double`'s `Value` property whenever it +receives an item in its input sequence. Each double in the input sequence +received by triggers +an optical stimulus. The value of the double determines the delay in +microseconds, executed on the hardware, between triggering the stimulus and +delivery of the stimulus. When `Double`'s `Value` property is set to zero, there +is no delay. \ No newline at end of file diff --git a/articles/hardware/hs64/stimulator-data.md b/articles/hardware/hs64/stimulator-data.md new file mode 100644 index 00000000..c7ce3bb7 --- /dev/null +++ b/articles/hardware/hs64/stimulator-data.md @@ -0,0 +1,38 @@ +--- +uid: hs64_stimulator-data +title: Headstage 64 Stimulator Data +--- + +The following excerpt from the Headstage 64 [example +workflow](xref:hs64_workflow) demonstrates how to save the waveform parameters +and the hardware timestamp of every stimulus delivered as described in the +, , and articles. + +::: workflow +![/workflows/hardware/hs64/stimulator-data.bonsai workflow](../../../workflows/hardware/hs64/stimulator-data.bonsai) +::: + +The operator +generates a sequence of +[Headstage64ElectricalStimulatorDataFrames](xref:OpenEphys.Onix1.Headstage64ElectricalStimulatorDataFrame) +which contain data about when an electrical stimulus was delivered and the +corresponding electrical stimulation waveform. A frame is emitted when an +electrical stimulus is delivered. In the Headstage 64 example workflow, the +`Headstage64ElectricalStimulatorData`'s `DeviceName` property is set to +"Headstage64/Headstage64ElectricalStimulator". This links the +`Headstage64ElectricalStimulatorData` operator to the corresponding +configuration operator. Frames from this operators are saved to a file named +`estim_.csv` using a . + +The operator generates a +sequence of +[Headstage64OpticalStimulatorDataFrames](xref:OpenEphys.Onix1.Headstage64OpticalStimulatorDataFrame) +which contain data about when an optical stimulus was delivered and the +corresponding optical stimulation waveform. A frame is emitted when an optical +stimulus is delivered. In the Headstage 64 example workflow, the +`Headstage64OpticalStimulatorData`'s `DeviceName` property is set to +"Headstage64/Headstage64OpticalStimulator". This links the +`Headstage64OpticalStimulatorData` operator to the corresponding configuration +operator. Frames from this operators are saved to a file named `ostim_.csv` +using a `CsvWriter`. + diff --git a/articles/hardware/hs64/workflow.md b/articles/hardware/hs64/workflow.md index e4b38d06..17c30e05 100644 --- a/articles/hardware/hs64/workflow.md +++ b/articles/hardware/hs64/workflow.md @@ -3,6 +3,14 @@ uid: hs64_workflow title: Headstage 64 Example Workflow --- +> [!IMPORTANT] +> This workflow requires OpenEphys.Onix1 0.7.0+ and Headstage 64 0.4.0. You can +> update the OpenEphys.Onix1 package using [Bonsai's Package +> Manager](xref:workflow-editor) and you can update the Headstage's firmware +> using the [ONIX Hub +> Updater](https://open-ephys.github.io/onix-docs/docs/Hardware +> Guide/Headstages/updating-firmware.html) + The example workflow below can by copy/pasted into the Bonsai editor using the clipboard icon in the top right. This workflow: - Captures electrophysiology data from passive probes via the RHD2164 amplifier and saves it to disk. - Captures orientation data from the Bno055 IMU and saves it to disk. @@ -10,6 +18,9 @@ The example workflow below can by copy/pasted into the Bonsai editor using the c - Automatically commutates the tether if there is a proper commutator connection. - Applies electrical stimulation triggered by pressing the breakout board's △ key. - Applies optical stimulation triggered by pressing the breakout board's ◯ key. +- Applies either electrical or optical stimulation (depending on which stimulators + are armed) using a lower latency trigger mechanism by pressing the breakout + board's X key. - Monitors memory usage data. ::: workflow diff --git a/articles/toc.yml b/articles/toc.yml index 470cbbcd..f4a5e7fa 100644 --- a/articles/toc.yml +++ b/articles/toc.yml @@ -75,6 +75,10 @@ href: hardware/hs64/estim.md - name: Optical Stimulation href: hardware/hs64/ostim.md + - name: GPO Trigger + href: hardware/hs64/gpo-trigger.md + - name: Stimulator Data + href: hardware/hs64/stimulator-data.md - name: Memory Monitor href: hardware/hs64/memory-monitor.md - href: hardware/hs64/load-data.md diff --git a/docfx.json b/docfx.json index 324e2771..06e8dbbd 100644 --- a/docfx.json +++ b/docfx.json @@ -57,7 +57,8 @@ "xref": [ "https://bonsai-rx.org/docs/xrefmap.yml", "https://horizongir.github.io/opencv.net/xrefmap.yml", - "https://horizongir.github.io/reactive/xrefmap.yml" + "https://horizongir.github.io/reactive/xrefmap.yml", + "https://learn.microsoft.com/en-us/dotnet/.xrefmap.json" ] }, "rules": { diff --git a/workflows/hardware/hs64/configuration.bonsai b/workflows/hardware/hs64/configuration.bonsai index 152abbcd..c88ea431 100644 --- a/workflows/hardware/hs64/configuration.bonsai +++ b/workflows/hardware/hs64/configuration.bonsai @@ -19,7 +19,6 @@ BreakoutBoard/PersistentHeartbeat 0 - true 100 @@ -55,6 +54,8 @@ BreakoutBoard/DigitalIO 7 true + 0 + BreakoutBoard/OutputClock @@ -102,11 +103,39 @@ Headstage64/Headstage64ElectricalStimulator 259 + true + true + 200 + 0 + -200 + 100 + 0 + 100 + 10000 + 0 + 10 + 1 Headstage64/Headstage64OpticalStimulator 260 + true + false + true + 30 + 0 + 100 + 10 + 10 + 10 + 0 + 1 + + Headstage64/PersistentHeartbeat + 261 + 10 + PortA diff --git a/workflows/hardware/hs64/estim.bonsai b/workflows/hardware/hs64/estim.bonsai index a7c65271..e4671da1 100644 --- a/workflows/hardware/hs64/estim.bonsai +++ b/workflows/hardware/hs64/estim.bonsai @@ -22,22 +22,27 @@ + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64ElectricalStimulator - true - true - 0 - 100 - 0 - -100 - 200 - 0 - 200 - 400 - 0 - 5 - 1 @@ -46,6 +51,8 @@ + + \ No newline at end of file diff --git a/workflows/hardware/hs64/gpo-trigger.bonsai b/workflows/hardware/hs64/gpo-trigger.bonsai new file mode 100644 index 00000000..7767cc12 --- /dev/null +++ b/workflows/hardware/hs64/gpo-trigger.bonsai @@ -0,0 +1,38 @@ + + + + + + + BreakoutBoard/DigitalIO + + + + Buttons + + + + Square + + + + + + + + Headstage64/Headstage64PortController + + + + + + + + + + + \ No newline at end of file diff --git a/workflows/hardware/hs64/hs64.bonsai b/workflows/hardware/hs64/hs64.bonsai index 10d0b18c..cdca4074 100644 --- a/workflows/hardware/hs64/hs64.bonsai +++ b/workflows/hardware/hs64/hs64.bonsai @@ -5,6 +5,7 @@ xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:io="clr-namespace:Bonsai.IO;assembly=Bonsai.System" xmlns:dsp="clr-namespace:Bonsai.Dsp;assembly=Bonsai.Dsp" + xmlns:p1="clr-namespace:Bonsai.Ephys.Design;assembly=Bonsai.Ephys.Design" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -104,11 +105,39 @@ Headstage64/Headstage64ElectricalStimulator 259 + true + true + 200 + 0 + -200 + 100 + 0 + 100 + 10000 + 0 + 10 + 1 Headstage64/Headstage64OpticalStimulator 260 + true + false + true + 30 + 0 + 100 + 10 + 10 + 10 + 0 + 1 + + Headstage64/PersistentHeartbeat + 261 + 10 + PortA @@ -134,7 +163,7 @@ - Headstage64/PortController + Headstage64/Headstage64PortController @@ -145,7 +174,7 @@ false false FileCount - false + true Timestamp,Value.Clock,Value.StatusCode @@ -170,7 +199,7 @@ - rhd2164-amplifier_.raw + rhd2164-ephys_.raw FileCount false ColumnMajor @@ -187,6 +216,23 @@ ColumnMajor + + + + + + 0 + + + + 30000 + 1920 + + + + + + Headstage64/TS4231V1 @@ -203,11 +249,71 @@ - ts4231_.csv + uncalibrated_ts4231_.csv false false FileCount - false + true + Clock,Position + + + + + + NaN + NaN + NaN + 1 + NaN + NaN + NaN + 1 + NaN + NaN + NaN + 1 + NaN + NaN + NaN + 1 + + NaN + NaN + NaN + + + + NaN + NaN + NaN + 1 + NaN + NaN + NaN + 1 + NaN + NaN + NaN + 1 + NaN + NaN + NaN + 1 + + NaN + NaN + NaN + + + + + + + calibrated-ts4231_.csv + false + false + FileCount + true Clock,Position @@ -223,7 +329,7 @@ false false FileCount - false + true Clock,EulerAngle,Quaternion,Acceleration,Gravity,Temperature @@ -252,22 +358,27 @@ + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64ElectricalStimulator - true - true - 0 - 100 - 0 - -100 - 200 - 0 - 200 - 400 - 0 - 5 - 1 @@ -278,21 +389,68 @@ + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64OpticalStimulator - true - 0 - 100 - 50 - 50 - 10 - 50 - 20 - 0 - 1 + + + X + + + + + + + + Headstage64/Headstage64PortController + + + + + Headstage64/Headstage64OpticalStimulator + + + + ostim_.csv + false + false + FileCount + true + + + + + Headstage64/Headstage64ElectricalStimulator + + + + estim_.csv + false + false + FileCount + true + + BreakoutBoard/MemoryMonitor @@ -315,20 +473,35 @@ + + - + - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/workflows/hardware/hs64/load-hs64.py b/workflows/hardware/hs64/load-hs64.py index 1eeff4df..550abd07 100644 --- a/workflows/hardware/hs64/load-hs64.py +++ b/workflows/hardware/hs64/load-hs64.py @@ -1,124 +1,182 @@ # Import necessary packages -import os import numpy as np import matplotlib.pyplot as plt +from matplotlib.lines import Line2D +import spikeinterface.extractors as se +from pathlib import Path #%% Set parameters for loading data -suffix = 0 # Change to match filenames' suffix -data_directory = 'C:/Users/open-ephys/Documents/data/hs64' # Change to match files' directory -plot_num_channels = 10 # Number of channels to plot -start_t = 3.0 # Plot start time (seconds) -dur = 2.0 # Plot time duration (seconds) +suffix = 0 # Change to match filenames' suffix +data_directory = Path('./') # Change to match files' directory +plot_num_channels = 10 # Number of channels to plot +start_t = None # Plot start time (seconds). "None" starts the plot at the first sample in the recording +end_t = None # Plot time duration (seconds) "None" ends the plot at the last sample in the recording # RHD2164 constants -ephys_uV_multiplier = 0.195 -aux_uV_multiplier = 37.4 -offset = 32768 -num_channels = 64 +fs_hz = 30e3 +gain_to_uV_ephys = 0.195 +offset_ephys = 0.195 * -32768 +num_channels_ephys = 64 +gain_to_uV_aux = 37.4e-6 +offset_aux = 0 +num_channels_aux = 3 #%% Load acquisition session data dt = {'names': ('time', 'acq_clk_hz', 'block_read_sz', 'block_write_sz'), 'formats': ('datetime64[us]', 'u4', 'u4', 'u4')} -meta = np.genfromtxt(os.path.join(data_directory, f'start-time_{suffix}.csv'), delimiter=',', dtype=dt, skip_header=1) +meta = np.genfromtxt(data_directory / f'start-time_{suffix}.csv', delimiter=',', dtype=dt, skip_header=1) print(f'Recording was started at {meta["time"]} GMT') print(f'Acquisition clock rate was {meta["acq_clk_hz"] / 1e6 } MHz') #%% Load RHD2164 data -rhd2164 = {} - -# Load RHD2164 clock data and convert clock cycles to seconds -rhd2164['time'] = np.fromfile(os.path.join(data_directory, f'rhd2164-clock_{suffix}.raw'), dtype=np.uint64) / meta['acq_clk_hz'] - -# Load and scale RHD2164 ephys data -ephys = np.reshape(np.fromfile(os.path.join(data_directory, f'rhd2164-ephys_{suffix}.raw'), dtype=np.uint16), (-1, num_channels)) -rhd2164['ephys_uV'] = (ephys.astype(np.float32) - offset) * ephys_uV_multiplier - -# Load and scale RHD2164 aux data -aux = np.reshape(np.fromfile(os.path.join(data_directory, f'rhd2164-aux_{suffix}.raw'), dtype=np.uint16), (-1, 3)) -rhd2164['aux_uV'] = (aux.astype(np.float32) - offset) * aux_uV_multiplier - -rhd2164_time_mask = np.bitwise_and(rhd2164['time'] >= start_t, rhd2164['time'] < start_t + dur) +rec_ephys = se.read_binary(data_directory / f'rhd2164-ephys_{suffix}.raw', + sampling_frequency=fs_hz, + dtype=np.uint16, + num_channels=num_channels_ephys, + gain_to_uV=gain_to_uV_ephys, + offset_to_uV=offset_ephys) +rec_ephys.set_times(np.fromfile(data_directory / f'rhd2164-clock_{suffix}.raw', dtype=np.uint64).astype(np.double) / meta['acq_clk_hz'], + with_warning=False) +rec_ephys_slice = rec_ephys.time_slice(start_time=start_t, end_time=end_t) + +rec_aux = se.read_binary(data_directory / f'rhd2164-aux_{suffix}.raw', + sampling_frequency=fs_hz, + dtype=np.uint16, + num_channels=num_channels_aux, + gain_to_uV=gain_to_uV_aux, + offset_to_uV=offset_aux) +rec_ephys.set_times(np.fromfile(data_directory / f'rhd2164-clock_{suffix}.raw', dtype=np.uint64).astype(np.double) / meta['acq_clk_hz'], + with_warning=False) +rec_aux_slice = rec_aux.time_slice(start_time=start_t, end_time=end_t) + +#%% Load stimulator data + +dt = {'names': ('clock', 'hub_clock', 'origin', 'delay', 'rest_current', + 'phase_one_current', 'phase_two_current', 'phase_one_duration', 'inter_phase_interval', 'phase_two_duration', 'inter_pulse_interval', 'pulses_per_burst', 'inter_burst_interval', 'bursts_per_train'), + 'formats': ('u8', 'u8', 'B', 'u4', 'f8', + 'f8', 'f8', 'u4', 'u4', 'u4', + 'u4', 'u4', 'u4', 'u4')} +estim = np.genfromtxt(data_directory / f'estim_{suffix}.csv', delimiter=',', dtype=dt, skip_header=1) + +dt = {'names': ('clock', 'hub_clock', 'origin', 'delay', 'channel_one_current', + 'channel_two_current', 'pulse_duration', 'pulse_period', 'pulses_per_burst', 'inter_burst_interval', + 'bursts_per_train'), + 'formats': ('u8', 'u8', 'B', 'u4', 'f8', + 'f8', 'f8', 'f8', 'u4', 'f8', + 'u4')} +ostim = np.genfromtxt(data_directory / f'ostim_{suffix}.csv', delimiter=',', dtype=dt, skip_header=1) #%% Load BNO055 data dt = {'names': ('clock', 'euler', 'quat', 'is_quat_id', 'accel', 'grav', 'temp'), 'formats': ('u8', '(1,3)f8', '(1,4)f8', '?', '(1,3)f8', '(1,3)f8', 'f8')} -bno055 = np.genfromtxt(os.path.join(data_directory, f'bno055_{suffix}.csv'), delimiter=',', dtype=dt) +bno055 = np.genfromtxt(data_directory / f'bno055_{suffix}.csv', delimiter=',', dtype=dt, skip_header=1) -# Convert clock cycles to seconds bno055_time = bno055['clock'] / meta['acq_clk_hz'] - -bno055_time_mask = np.bitwise_and(bno055_time >= start_t, bno055_time < start_t + dur) +bno055_time_mask = np.bitwise_and(bno055_time >= 0 if start_t is None else start_t, + bno055_time <= bno055_time[-1] if end_t is None else end_t) #%% Load TS4231 data -# Load TS4231 data dt = {'names': ('clock', 'position'), 'formats': ('u8', '(1,3)f8')} -ts4231 = np.genfromtxt(os.path.join(data_directory, f'ts4231_{suffix}.csv'), delimiter=',', dtype=dt) +ts4231 = np.genfromtxt(data_directory / f'calibrated-ts4231_{suffix}.csv', delimiter=',', dtype=dt, skip_header=1) -# Convert clock cycles to seconds ts4231_time = ts4231['clock'] / meta['acq_clk_hz'] +ts4231_time_mask = np.bitwise_and(ts4231_time >= 0 if start_t is None else start_t, + ts4231_time <= ts4231_time[-1] if end_t is None else end_t) + +#%% Plot RHD2164 ephys & stim data + +fig, ax = plt.subplots(1, 1) + +col_indices = np.arange(plot_num_channels)[np.newaxis, :] +offset_uV = rec_ephys_slice.get_traces(return_scaled=True, channel_ids=np.arange(plot_num_channels)) + 1000 * col_indices +ax.plot(rec_ephys_slice.get_times(), offset_uV) +ax.set_xlabel('Time (seconds)') +ax.set_ylabel('Channel Number') +ax.set_yticks(1000 * np.arange(plot_num_channels)) +ax.set_yticklabels(np.arange(plot_num_channels)) +ax.set_title('RHD2164 Ephys Data') + +for stim_clock, stim_origin in zip(estim['clock'], estim['origin']): + stim_sec = stim_clock / meta['acq_clk_hz'] + line_color = 'k' if stim_origin == 'Register' else 'r' + ax.axvline(x=stim_sec, color=line_color, alpha=0.25, ls='-') + +for stim_clock, stim_origin in zip(ostim['clock'], ostim['origin']): + stim_sec = stim_clock / meta['acq_clk_hz'] + line_color = 'k' if stim_origin == 'Register' else 'r' + ax.axvline(x=stim_sec, color=line_color, alpha=0.25, ls='--') + +ax.legend([Line2D([0], [0], color='k', alpha=0.25, ls='-'), + Line2D([0], [0], color='k', alpha=0.25, ls='--'), + Line2D([0], [0], color='r', alpha=0.25, ls='-'), + Line2D([0], [0], color='r', alpha=0.25, ls='--'),], + ['estim (register triggered)', 'ostim (register triggered)', 'estim', 'ostim'], + loc='upper right') + +scale_bar_length = 1000 # µV +scale_bar_x = ax.get_xlim()[0] +scale_bar_y = ax.get_ylim()[0] + +ax.text(scale_bar_x, scale_bar_y, + f' {scale_bar_length} µV', + va='bottom', ha='left', fontsize=10, + bbox=dict(boxstyle='round,pad=0.5', facecolor='white', alpha=0.7, edgecolor='none')) +ax.plot([scale_bar_x, scale_bar_x], + [scale_bar_y, scale_bar_y + scale_bar_length], + 'k-', linewidth=2, zorder=10) + +fig.set_size_inches((8,8)) +fig.tight_layout() -ts4231_time_mask = np.bitwise_and(ts4231_time >= start_t, ts4231_time < start_t + dur) - -#%% Plot time series - -fig = plt.figure(figsize=(12, 10)) +#%% Plot BNO055 and TS4231V1 data -# Plot RHD2164 ephys data -plt.subplot(711) -plt.plot(rhd2164['time'][rhd2164_time_mask], rhd2164['ephys_uV'][:,0:plot_num_channels][rhd2164_time_mask]) -plt.xlabel('Time (seconds)') -plt.ylabel('Voltage (µV)') -plt.title('RHD2164 Ephys Data') +fig, axes = plt.subplots(6, 1, sharex=True) # Plot RHD2164 aux data -plt.subplot(712) -plt.plot(rhd2164['time'][rhd2164_time_mask], rhd2164['aux_uV'][rhd2164_time_mask]) -plt.xlabel('Time (seconds)') -plt.ylabel('Voltage (µV)') -plt.title('RHD2164 Aux Data') +axes[0].plot(rec_aux_slice.get_times(), + rec_aux_slice.get_traces(return_scaled=True, channel_ids=np.arange(num_channels_aux))) +axes[0].set_xlabel('Time (seconds)') +axes[0].set_ylabel('Voltage (V)') +axes[0].set_title('RHD2164 Aux Data') # Plot BNO055 data -plt.subplot(713) -plt.plot(bno055_time[bno055_time_mask], bno055['euler'].squeeze()[bno055_time_mask]) -plt.xlabel('Time (seconds)') -plt.ylabel('degrees') -plt.title('Euler Angles') -plt.legend(['Yaw', 'Pitch', 'Roll']) - -plt.subplot(714) -plt.plot(bno055_time[bno055_time_mask], bno055['quat'].squeeze()[bno055_time_mask]) -plt.xlabel('Time (seconds)') -plt.title('Quaternion') -plt.legend(['X', 'Y', 'Z', 'W']) - -plt.subplot(715) -plt.plot(bno055_time[bno055_time_mask], bno055['accel'].squeeze()[bno055_time_mask]) -plt.xlabel('Time (seconds)') -plt.ylabel('m/s\u00b2') -plt.title('Linear Acceleration') -plt.legend(['X', 'Y', 'Z']) - -plt.subplot(716) -plt.plot(bno055_time[bno055_time_mask], bno055['grav'].squeeze()[bno055_time_mask]) -plt.xlabel('Time (seconds)') -plt.ylabel('m/s\u00b2') -plt.title('Gravity Vector') -plt.legend(['X', 'Y', 'Z']) +axes[1].plot(bno055_time[bno055_time_mask], bno055['euler'].squeeze()[bno055_time_mask]) +axes[1].set_xlabel('Time (seconds)') +axes[1].set_ylabel('degrees') +axes[1].set_title('Euler Angles') +axes[1].legend(['Yaw', 'Pitch', 'Roll'],loc='lower right') + +axes[2].plot(bno055_time[bno055_time_mask], bno055['quat'].squeeze()[bno055_time_mask]) +axes[2].set_xlabel('Time (seconds)') +axes[2].set_title('Quaternions') +axes[2].legend(['X', 'Y', 'Z', 'W'],loc='lower right') + +axes[3].plot(bno055_time[bno055_time_mask], bno055['accel'].squeeze()[bno055_time_mask]) +axes[3].set_xlabel('Time (seconds)') +axes[3].set_ylabel('m/s\u00b2') +axes[3].set_title('Linear Acceleration') +axes[3].legend(['X', 'Y', 'Z'],loc='lower right') + +axes[4].plot(bno055_time[bno055_time_mask], bno055['grav'].squeeze()[bno055_time_mask]) +axes[4].set_xlabel('Time (seconds)') +axes[4].set_ylabel('m/s\u00b2') +axes[4].set_title('Gravity Vector') +axes[4].legend(['X', 'Y', 'Z'],loc='lower right') # Plot TS4231 data -plt.subplot(717) -plt.plot(ts4231_time[ts4231_time_mask], ts4231['position'].squeeze()[ts4231_time_mask]) -plt.xlabel('Time (seconds)') -plt.ylabel('Position (units)') -plt.title('Position Data') -plt.legend(['X', 'Y', 'Z']) +axes[5].plot(ts4231_time[ts4231_time_mask], ts4231['position'].squeeze()[ts4231_time_mask]) +axes[5].set_xlabel('Time (seconds)') +axes[5].set_ylabel('Position (units)') +axes[5].set_title('Position Data') +axes[5].legend(['X', 'Y', 'Z'],loc='lower right') +fig.set_size_inches((8,8)) fig.tight_layout() plt.show() \ No newline at end of file diff --git a/workflows/hardware/hs64/ostim.bonsai b/workflows/hardware/hs64/ostim.bonsai index cf741bfc..578f1e3e 100644 --- a/workflows/hardware/hs64/ostim.bonsai +++ b/workflows/hardware/hs64/ostim.bonsai @@ -22,19 +22,27 @@ + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64OpticalStimulator - true - 0 - 100 - 50 - 50 - 10 - 50 - 20 - 0 - 1 @@ -43,6 +51,8 @@ + + \ No newline at end of file diff --git a/workflows/hardware/hs64/port-status.bonsai b/workflows/hardware/hs64/port-status.bonsai index 166060fd..50bf1454 100644 --- a/workflows/hardware/hs64/port-status.bonsai +++ b/workflows/hardware/hs64/port-status.bonsai @@ -1,5 +1,5 @@  - - Headstage64/PortController + Headstage64/Headstage64PortController @@ -20,7 +20,7 @@ false false FileCount - false + true Timestamp,Value.Clock,Value.StatusCode diff --git a/workflows/hardware/hs64/rhd2164.bonsai b/workflows/hardware/hs64/rhd2164.bonsai index 7ebc767e..3c988282 100644 --- a/workflows/hardware/hs64/rhd2164.bonsai +++ b/workflows/hardware/hs64/rhd2164.bonsai @@ -3,6 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:onix1="clr-namespace:OpenEphys.Onix1;assembly=OpenEphys.Onix1" xmlns:dsp="clr-namespace:Bonsai.Dsp;assembly=Bonsai.Dsp" + xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" + xmlns:p1="clr-namespace:Bonsai.Ephys.Design;assembly=Bonsai.Ephys.Design" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -28,7 +30,7 @@ - rhd2164-amplifier_.raw + rhd2164-ephys_.raw FileCount false ColumnMajor @@ -45,6 +47,23 @@ ColumnMajor + + + + + + 0 + + + + 30000 + 1920 + + + + + + @@ -52,7 +71,11 @@ + + + + \ No newline at end of file diff --git a/workflows/hardware/hs64/stimulator-data.bonsai b/workflows/hardware/hs64/stimulator-data.bonsai new file mode 100644 index 00000000..cda3b3b8 --- /dev/null +++ b/workflows/hardware/hs64/stimulator-data.bonsai @@ -0,0 +1,41 @@ + + + + + + + Headstage64/Headstage64OpticalStimulator + + + + ostim-data_.csv + false + false + FileCount + true + + + + + Headstage64/Headstage64ElectricalStimulator + + + + estim-data_.csv + false + false + FileCount + true + + + + + + + + + \ No newline at end of file diff --git a/workflows/operators/Headstage64ElectricalStimulatorData.bonsai b/workflows/operators/Headstage64ElectricalStimulatorData.bonsai new file mode 100644 index 00000000..4171fc31 --- /dev/null +++ b/workflows/operators/Headstage64ElectricalStimulatorData.bonsai @@ -0,0 +1,16 @@ + + + + + + + Headstage64/Headstage64ElectricalStimulator + + + + + + \ No newline at end of file diff --git a/workflows/operators/Headstage64ElectricalStimulatorTrigger.bonsai b/workflows/operators/Headstage64ElectricalStimulatorTrigger.bonsai index 445adabe..e4671da1 100644 --- a/workflows/operators/Headstage64ElectricalStimulatorTrigger.bonsai +++ b/workflows/operators/Headstage64ElectricalStimulatorTrigger.bonsai @@ -2,6 +2,7 @@ @@ -18,22 +19,30 @@ Triangle + + + + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64ElectricalStimulator - true - false - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 @@ -41,6 +50,9 @@ + + + \ No newline at end of file diff --git a/workflows/operators/Headstage64GpoTrigger.bonsai b/workflows/operators/Headstage64GpoTrigger.bonsai new file mode 100644 index 00000000..13e7671f --- /dev/null +++ b/workflows/operators/Headstage64GpoTrigger.bonsai @@ -0,0 +1,52 @@ + + + + + + + BreakoutBoard/DigitalIO + + + + Buttons + + + + Square + + + + + + + + + + Source1 + + + + + + + + + + + Headstage64/Headstage64PortController + + + + + + + + + + + + \ No newline at end of file diff --git a/workflows/operators/Headstage64OpticalStimulatorData.bonsai b/workflows/operators/Headstage64OpticalStimulatorData.bonsai new file mode 100644 index 00000000..3892d5b4 --- /dev/null +++ b/workflows/operators/Headstage64OpticalStimulatorData.bonsai @@ -0,0 +1,16 @@ + + + + + + + Headstage64/Headstage64OpticalStimulator + + + + + + \ No newline at end of file diff --git a/workflows/operators/Headstage64OpticalStimulatorTrigger.bonsai b/workflows/operators/Headstage64OpticalStimulatorTrigger.bonsai index 71f2fe8b..578f1e3e 100644 --- a/workflows/operators/Headstage64OpticalStimulatorTrigger.bonsai +++ b/workflows/operators/Headstage64OpticalStimulatorTrigger.bonsai @@ -2,6 +2,7 @@ @@ -18,19 +19,30 @@ Circle + + + + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64OpticalStimulator - true - 0 - 100 - 100 - 0 - 5 - 50 - 20 - 0 - 1 @@ -38,6 +50,9 @@ + + + \ No newline at end of file