From 75eaef9d65bbb69dad11cb5c2cc31e558aae1f18 Mon Sep 17 00:00:00 2001 From: cjsha Date: Thu, 30 Oct 2025 15:22:56 -0400 Subject: [PATCH 1/5] Documentation for GPO trigger and stimulator reports --- articles/hardware/hs64/estim.md | 44 ++-- articles/hardware/hs64/gpo-trigger.md | 49 ++++ articles/hardware/hs64/ostim.md | 46 ++-- articles/hardware/hs64/stimulator-data.md | 38 +++ articles/toc.yml | 4 + workflows/hardware/hs64/estim.bonsai | 33 ++- workflows/hardware/hs64/gpo-trigger.bonsai | 52 ++++ workflows/hardware/hs64/hs64.bonsai | 226 +++++++++++++++--- workflows/hardware/hs64/load-hs64.py | 59 ++++- workflows/hardware/hs64/ostim.bonsai | 30 ++- .../hardware/hs64/stimulator-data.bonsai | 41 ++++ ...Headstage64ElectricalStimulatorData.bonsai | 16 ++ ...dstage64ElectricalStimulatorTrigger.bonsai | 38 ++- .../operators/Headstage64GpoTrigger.bonsai | 52 ++++ .../Headstage64OpticalStimulatorData.bonsai | 16 ++ ...Headstage64OpticalStimulatorTrigger.bonsai | 35 ++- 16 files changed, 662 insertions(+), 117 deletions(-) create mode 100644 articles/hardware/hs64/gpo-trigger.md create mode 100644 articles/hardware/hs64/stimulator-data.md create mode 100644 workflows/hardware/hs64/gpo-trigger.bonsai create mode 100644 workflows/hardware/hs64/stimulator-data.bonsai create mode 100644 workflows/operators/Headstage64ElectricalStimulatorData.bonsai create mode 100644 workflows/operators/Headstage64GpoTrigger.bonsai create mode 100644 workflows/operators/Headstage64OpticalStimulatorData.bonsai diff --git a/articles/hardware/hs64/estim.md b/articles/hardware/hs64/estim.md index b9472976..3085aa34 100644 --- a/articles/hardware/hs64/estim.md +++ b/articles/hardware/hs64/estim.md @@ -3,26 +3,42 @@ 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. ::: 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). In the +Headstage 64 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. + 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. The operator only +allows 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 a value. The + operator emits a value determined by +its `Value` property whenever it receives an item in its input sequence. T This +value is used to determine the delay between triggering the stimulus and +delivery of the stimulus. When `Double`'s `Value` property is set to zero, there +is no such delay. -When the operator receives a "True" value in its input -sequence, a stimulus waveform is triggered. The waveform can be modified by editing the +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 diff --git a/articles/hardware/hs64/gpo-trigger.md b/articles/hardware/hs64/gpo-trigger.md new file mode 100644 index 00000000..45ed07bd --- /dev/null +++ b/articles/hardware/hs64/gpo-trigger.md @@ -0,0 +1,49 @@ +--- +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 □ key on the breakout board. The GPO trigger uses a pin on the +headstage to trigger stimulus more instantaneously than other trigger methods +that write to a register on the hardware which takes more time. However, this +trigger operator does not provide stimulus feedback as discussed in + nor is it capable of distinguishing between applying +optical or electrical stimulus. If you want to use the GPO trigger to only +trigger electrical stimulus, the electrical stimulator should be the only +stimulator device armed on the headstage. If you want to use the GPO trigger to +only trigger optical stimulus, the optical stimulator should be the only +stimulator device armed on the headstage. + +::: 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). 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. + + 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 "Square", its +output is "True" when the selected `BreakoutButtonState` bit field contains the +"Square" flag. The operator only +allows 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 a value. + +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 diff --git a/articles/hardware/hs64/ostim.md b/articles/hardware/hs64/ostim.md index 350e612d..d4b5caa1 100644 --- a/articles/hardware/hs64/ostim.md +++ b/articles/hardware/hs64/ostim.md @@ -3,26 +3,42 @@ 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. ::: 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). In the +Headstage 64 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. + 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. The operator only +allows 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 a value. The + operator emits a value determined by +its `Value` property whenever it receives an item in its input sequence. This +value is used to determine the delay between triggering the stimulus and +delivery of the stimulus. When `Double`'s `Value` property is set to zero, there +is no such delay. -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 +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 diff --git a/articles/hardware/hs64/stimulator-data.md b/articles/hardware/hs64/stimulator-data.md new file mode 100644 index 00000000..94c8568d --- /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/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/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..13e7671f --- /dev/null +++ b/workflows/hardware/hs64/gpo-trigger.bonsai @@ -0,0 +1,52 @@ + + + + + + + BreakoutBoard/DigitalIO + + + + Buttons + + + + Square + + + + + + + + + + Source1 + + + + + + + + + + + Headstage64/Headstage64PortController + + + + + + + + + + + + \ No newline at end of file diff --git a/workflows/hardware/hs64/hs64.bonsai b/workflows/hardware/hs64/hs64.bonsai index 10d0b18c..e70b0d45 100644 --- a/workflows/hardware/hs64/hs64.bonsai +++ b/workflows/hardware/hs64/hs64.bonsai @@ -87,7 +87,7 @@ Headstage64/Rhd2164 256 true - Off + Dsp146mHz Low100mHz High10000Hz @@ -104,11 +104,39 @@ Headstage64/Headstage64ElectricalStimulator 259 + false + false + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 Headstage64/Headstage64OpticalStimulator 260 + false + false + false + 5.822 + 0 + 0 + 0.001 + 0.01 + 1 + 0 + 1 + + Headstage64/PersistentHeartbeat + 261 + 10 + PortA @@ -145,7 +173,7 @@ false false FileCount - false + true Timestamp,Value.Clock,Value.StatusCode @@ -170,7 +198,7 @@ - rhd2164-amplifier_.raw + rhd2164-ephys_.raw FileCount false ColumnMajor @@ -203,11 +231,71 @@ - ts4231_.csv + uncalibrated_ts4231_.csv + false + false + FileCount + 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 - false + true Clock,Position @@ -223,7 +311,7 @@ false false FileCount - false + true Clock,EulerAngle,Quaternion,Acceleration,Gravity,Temperature @@ -252,22 +340,27 @@ + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64ElectricalStimulator - true - true - 0 - 100 - 0 - -100 - 200 - 0 - 200 - 400 - 0 - 5 - 1 @@ -278,21 +371,81 @@ + + + + + Source1 + + + + + + + + + + + 0 + + Headstage64/Headstage64OpticalStimulator - true - 0 - 100 - 50 - 50 - 10 - 50 - 20 - 0 - 1 + + + Square + + + + + + + + + + Source1 + + + + + + + + + + + Headstage64/Headstage64PortController + + + + + Headstage64/Headstage64OpticalStimulator + + + + ostim_.csv + false + false + FileCount + true + + + + + Headstage64/Headstage64ElectricalStimulator + + + + estim_.csv + false + false + FileCount + true + + BreakoutBoard/MemoryMonitor @@ -326,9 +479,22 @@ + + + - + + + + + + + + + + + \ No newline at end of file diff --git a/workflows/hardware/hs64/load-hs64.py b/workflows/hardware/hs64/load-hs64.py index 1eeff4df..5b020e8a 100644 --- a/workflows/hardware/hs64/load-hs64.py +++ b/workflows/hardware/hs64/load-hs64.py @@ -1,15 +1,16 @@ # Import necessary packages -import os import numpy as np import matplotlib.pyplot as plt +from matplotlib.lines import Line2D +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('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) # RHD2164 constants ephys_uV_multiplier = 0.195 @@ -21,7 +22,7 @@ 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') @@ -30,23 +31,40 @@ 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'] +rhd2164['time'] = np.fromfile(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)) +ephys = np.reshape(np.fromfile(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)) +aux = np.reshape(np.fromfile(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) +#%% 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'] @@ -58,7 +76,7 @@ # 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'] @@ -76,6 +94,23 @@ plt.ylabel('Voltage (µV)') plt.title('RHD2164 Ephys Data') +# Plot stimulus delivery +ax = plt.gca() + +for stim_clock in estim['clock']: + stim_sec = stim_clock / meta['acq_clk_hz'] + if stim_sec > start_t and stim_sec < start_t + dur: + ax.axvline(x=stim_sec, color='k', alpha=0.5, ls='-') + +for stim_clock in ostim['clock']: + stim_sec = stim_clock / meta['acq_clk_hz'] + if stim_sec > start_t and stim_sec < start_t + dur: + ax.axvline(x=stim_sec, color='k', alpha=0.5, ls='--') + +ax.legend([Line2D([0], [0], color='k', alpha=0.5, ls='-'), + Line2D([0], [0], color='k', alpha=0.5, ls='--')], + ['estim', 'ostim']) + # Plot RHD2164 aux data plt.subplot(712) plt.plot(rhd2164['time'][rhd2164_time_mask], rhd2164['aux_uV'][rhd2164_time_mask]) 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/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 From 000c997a1c0f2992d0230f1ca6ba826010571bb6 Mon Sep 17 00:00:00 2001 From: cjsha Date: Thu, 30 Oct 2025 16:04:36 -0400 Subject: [PATCH 2/5] Trigger GPO key using X key instead of square key - Add note GPO trigger on example workflow page --- articles/hardware/hs64/gpo-trigger.md | 2 +- articles/hardware/hs64/workflow.md | 1 + workflows/hardware/hs64/hs64.bonsai | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/articles/hardware/hs64/gpo-trigger.md b/articles/hardware/hs64/gpo-trigger.md index 45ed07bd..5dc55de9 100644 --- a/articles/hardware/hs64/gpo-trigger.md +++ b/articles/hardware/hs64/gpo-trigger.md @@ -5,7 +5,7 @@ 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 □ key on the breakout board. The GPO trigger uses a pin on the +press of the X key on the breakout board. The GPO trigger uses a pin on the headstage to trigger stimulus more instantaneously than other trigger methods that write to a register on the hardware which takes more time. However, this trigger operator does not provide stimulus feedback as discussed in diff --git a/articles/hardware/hs64/workflow.md b/articles/hardware/hs64/workflow.md index e4b38d06..5974745a 100644 --- a/articles/hardware/hs64/workflow.md +++ b/articles/hardware/hs64/workflow.md @@ -10,6 +10,7 @@ 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 both stimulation types with a lower latency trigger mechanism by pressing the breakout board's X key. - Monitors memory usage data. ::: workflow diff --git a/workflows/hardware/hs64/hs64.bonsai b/workflows/hardware/hs64/hs64.bonsai index e70b0d45..128666ef 100644 --- a/workflows/hardware/hs64/hs64.bonsai +++ b/workflows/hardware/hs64/hs64.bonsai @@ -396,7 +396,7 @@ - Square + X @@ -471,12 +471,11 @@ + - + - - From 5e8047de3bbc9a3d3a6a88b32164592a49a932c8 Mon Sep 17 00:00:00 2001 From: cjsha Date: Thu, 30 Oct 2025 17:21:58 -0400 Subject: [PATCH 3/5] Address bparks feedback --- articles/hardware/breakout/digital-inputs.md | 15 ++++++----- articles/hardware/hs64/estim.md | 10 ++----- articles/hardware/hs64/gpo-trigger.md | 28 ++++++++------------ articles/hardware/hs64/ostim.md | 8 +----- articles/hardware/hs64/workflow.md | 4 ++- includes/breakout-digital-io.md | 8 ++++++ 6 files changed, 33 insertions(+), 40 deletions(-) create mode 100644 includes/breakout-digital-io.md 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 3085aa34..79d1de94 100644 --- a/articles/hardware/hs64/estim.md +++ b/articles/hardware/hs64/estim.md @@ -11,13 +11,7 @@ a train of pulses following a press of the △ key on the breakout board. ![/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 -Headstage 64 example workflow, the `DigitalInput`'s `DeviceName` property is -set to "BreakoutBoard/DigitalInput". This links the `DigitalInput` operator to -the corresponding configuration operator. +[!INCLUDE [](<../../../includes/breakout-digital-io.md>)] is selected from the `DigitalInputDataFrame`. It is an enumerator with values that correspond to bit @@ -33,7 +27,7 @@ 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 a value. The operator emits a value determined by -its `Value` property whenever it receives an item in its input sequence. T This +its `Value` property whenever it receives an item in its input sequence. This value is used to determine the delay between triggering the stimulus and delivery of the stimulus. When `Double`'s `Value` property is set to zero, there is no such delay. diff --git a/articles/hardware/hs64/gpo-trigger.md b/articles/hardware/hs64/gpo-trigger.md index 5dc55de9..d1950819 100644 --- a/articles/hardware/hs64/gpo-trigger.md +++ b/articles/hardware/hs64/gpo-trigger.md @@ -5,28 +5,22 @@ 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 uses a pin on the -headstage to trigger stimulus more instantaneously than other trigger methods -that write to a register on the hardware which takes more time. However, this -trigger operator does not provide stimulus feedback as discussed in - nor is it capable of distinguishing between applying -optical or electrical stimulus. If you want to use the GPO trigger to only -trigger electrical stimulus, the electrical stimulator should be the only -stimulator device armed on the headstage. If you want to use the GPO trigger to -only trigger optical stimulus, the optical stimulator should be the only -stimulator device armed on the headstage. +press of the X key on the breakout board. The GPO trigger toggles a pin on the +headstage to trigger stimulus more instantaneously than writing to a register +which is how other Headstage 64 operators trigger stimulus. + +> [!NOTE] +> If you want to use the GPO trigger to only trigger electrical stimulus, the +> electrical stimulator should be the only stimulator device armed on the +> headstage. If you want to use the GPO trigger to only trigger optical +> stimulus, the optical stimulator should be the only stimulator device armed on +> the headstage. ::: 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). 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. +[!INCLUDE [](<../../../includes/breakout-digital-io.md>)] is selected from the `DigitalInputDataFrame`. It is an enumerator with values that correspond to bit diff --git a/articles/hardware/hs64/ostim.md b/articles/hardware/hs64/ostim.md index d4b5caa1..b38e2c6c 100644 --- a/articles/hardware/hs64/ostim.md +++ b/articles/hardware/hs64/ostim.md @@ -11,13 +11,7 @@ train of pulses following a press of the ◯ key on the breakout board. ![/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 -Headstage 64 example workflow, the `DigitalInput`'s `DeviceName` property is -set to "BreakoutBoard/DigitalInput". This links the `DigitalInput` operator to -the corresponding configuration operator. +[!INCLUDE [](<../../../includes/breakout-digital-io.md>)] is selected from the `DigitalInputDataFrame`. It is an enumerator with values that correspond to bit diff --git a/articles/hardware/hs64/workflow.md b/articles/hardware/hs64/workflow.md index 5974745a..2f9c3e61 100644 --- a/articles/hardware/hs64/workflow.md +++ b/articles/hardware/hs64/workflow.md @@ -10,7 +10,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 both stimulation types with a lower latency trigger mechanism by pressing the breakout board's X key. +- Applies either electrical or optical stimulation (depending on which stimulators + are enabled and armed) using a lower latency trigger mechanism by pressing the breakout + board's X key. - Monitors memory usage data. ::: workflow diff --git a/includes/breakout-digital-io.md b/includes/breakout-digital-io.md new file mode 100644 index 00000000..28c0f1e1 --- /dev/null +++ b/includes/breakout-digital-io.md @@ -0,0 +1,8 @@ +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. \ No newline at end of file From 79d478254800bf252325f46194c4db3aedd867c4 Mon Sep 17 00:00:00 2001 From: cjsha Date: Sun, 2 Nov 2025 19:34:02 -0500 Subject: [PATCH 4/5] bparks feedback, second pass Also: - Add dotnet xrefmap in docfx.json so I can use ``. - Remove include for now, the long-term solution will be to add a template. - Remove `Condition` operator from Headstage64GpoTrigger branch This node's unnecessary bc only True values trigger stimulus anyway. --- articles/hardware/hs64/estim.md | 47 +++++++++----- articles/hardware/hs64/gpo-trigger.md | 43 +++++++------ articles/hardware/hs64/ostim.md | 46 +++++++++----- docfx.json | 3 +- includes/breakout-digital-io.md | 8 --- workflows/hardware/hs64/gpo-trigger.bonsai | 26 ++------ workflows/hardware/hs64/hs64.bonsai | 71 ++++++++-------------- 7 files changed, 118 insertions(+), 126 deletions(-) delete mode 100644 includes/breakout-digital-io.md diff --git a/articles/hardware/hs64/estim.md b/articles/hardware/hs64/estim.md index 79d1de94..a018c046 100644 --- a/articles/hardware/hs64/estim.md +++ b/articles/hardware/hs64/estim.md @@ -7,11 +7,26 @@ 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) ::: -[!INCLUDE [](<../../../includes/breakout-digital-io.md>)] +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. is selected from the `DigitalInputDataFrame`. It is an enumerator with values that correspond to bit @@ -20,19 +35,19 @@ positions of the breakout board's digital port. When this type is connected to a 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. The operator only -allows 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 a value. The - operator emits a value determined by -its `Value` property whenever it receives an item in its input sequence. This -value is used to determine the delay between triggering the stimulus and -delivery of the stimulus. When `Double`'s `Value` property is set to zero, there -is no such delay. +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 +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 index d1950819..f55116cf 100644 --- a/articles/hardware/hs64/gpo-trigger.md +++ b/articles/hardware/hs64/gpo-trigger.md @@ -6,21 +6,32 @@ 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 more instantaneously than writing to a register -which is how other Headstage 64 operators trigger stimulus. +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] -> If you want to use the GPO trigger to only trigger electrical stimulus, the -> electrical stimulator should be the only stimulator device armed on the -> headstage. If you want to use the GPO trigger to only trigger optical -> stimulus, the optical stimulator should be the only stimulator device armed on -> the headstage. +> 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) ::: -[!INCLUDE [](<../../../includes/breakout-digital-io.md>)] +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. is selected from the `DigitalInputDataFrame`. It is an enumerator with values that correspond to bit @@ -29,15 +40,7 @@ positions of the breakout board's digital port. When this type is connected to a property's dropdown menu. Because `HasFlags`'s `Value` is set to "Square", its output is "True" when the selected `BreakoutButtonState` bit field contains the "Square" flag. The operator only -allows 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 a value. - -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 +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 b38e2c6c..ba14838d 100644 --- a/articles/hardware/hs64/ostim.md +++ b/articles/hardware/hs64/ostim.md @@ -7,11 +7,26 @@ 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) ::: -[!INCLUDE [](<../../../includes/breakout-digital-io.md>)] +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. is selected from the `DigitalInputDataFrame`. It is an enumerator with values that correspond to bit @@ -20,19 +35,18 @@ positions of the breakout board's digital port. When this type is connected to a 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. The operator only -allows 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 a value. The - operator emits a value determined by -its `Value` property whenever it receives an item in its input sequence. This -value is used to determine the delay between triggering the stimulus and -delivery of the stimulus. When `Double`'s `Value` property is set to zero, there -is no such delay. +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 +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/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/includes/breakout-digital-io.md b/includes/breakout-digital-io.md deleted file mode 100644 index 28c0f1e1..00000000 --- a/includes/breakout-digital-io.md +++ /dev/null @@ -1,8 +0,0 @@ -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. \ No newline at end of file diff --git a/workflows/hardware/hs64/gpo-trigger.bonsai b/workflows/hardware/hs64/gpo-trigger.bonsai index 13e7671f..7767cc12 100644 --- a/workflows/hardware/hs64/gpo-trigger.bonsai +++ b/workflows/hardware/hs64/gpo-trigger.bonsai @@ -1,43 +1,30 @@  - - BreakoutBoard/DigitalIO + + BreakoutBoard/DigitalIO Buttons - + Square - - - - - Source1 - - - - - - - - - - Headstage64/Headstage64PortController + + Headstage64/Headstage64PortController @@ -46,7 +33,6 @@ - \ No newline at end of file diff --git a/workflows/hardware/hs64/hs64.bonsai b/workflows/hardware/hs64/hs64.bonsai index 128666ef..056a4687 100644 --- a/workflows/hardware/hs64/hs64.bonsai +++ b/workflows/hardware/hs64/hs64.bonsai @@ -104,39 +104,11 @@ Headstage64/Headstage64ElectricalStimulator 259 - false - false - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 Headstage64/Headstage64OpticalStimulator 260 - false - false - false - 5.822 - 0 - 0 - 0.001 - 0.01 - 1 - 0 - 1 - - Headstage64/PersistentHeartbeat - 261 - 10 - PortA @@ -361,6 +333,19 @@ Headstage64/Headstage64ElectricalStimulator + true + false + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 @@ -392,6 +377,16 @@ Headstage64/Headstage64OpticalStimulator + true + 0 + 100 + 100 + 0 + 5 + 50 + 20 + 0 + 1 @@ -402,19 +397,6 @@ - - - - - Source1 - - - - - - - - Headstage64/Headstage64PortController @@ -490,10 +472,9 @@ - - - - + + + \ No newline at end of file From 113cc6c0decd3ad6fc03eddf6874671a8b530b18 Mon Sep 17 00:00:00 2001 From: cjsha Date: Mon, 10 Nov 2025 18:39:07 -0500 Subject: [PATCH 5/5] Address PR feedback Also address jon's oral feedback, including changes to the wf and loading script: - use spikeinterface - plot stim data - offset plotted channels & add scale bar - separate ephys plots from other plots -fix aux channel conversion --- articles/hardware/hs64/estim.md | 43 ++-- articles/hardware/hs64/gpo-trigger.md | 20 +- articles/hardware/hs64/ostim.md | 42 ++-- articles/hardware/hs64/stimulator-data.md | 4 +- articles/hardware/hs64/workflow.md | 10 +- workflows/hardware/hs64/configuration.bonsai | 31 ++- workflows/hardware/hs64/hs64.bonsai | 93 ++++++--- workflows/hardware/hs64/load-hs64.py | 205 +++++++++++-------- workflows/hardware/hs64/port-status.bonsai | 6 +- workflows/hardware/hs64/rhd2164.bonsai | 25 ++- 10 files changed, 293 insertions(+), 186 deletions(-) diff --git a/articles/hardware/hs64/estim.md b/articles/hardware/hs64/estim.md index a018c046..91ee8d8e 100644 --- a/articles/hardware/hs64/estim.md +++ b/articles/hardware/hs64/estim.md @@ -28,26 +28,25 @@ 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. 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. +[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. -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 -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 +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 index f55116cf..48befe4d 100644 --- a/articles/hardware/hs64/gpo-trigger.md +++ b/articles/hardware/hs64/gpo-trigger.md @@ -33,14 +33,12 @@ 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 "Square", its -output is "True" when the selected `BreakoutButtonState` bit field contains the -"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 +[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 ba14838d..3daec794 100644 --- a/articles/hardware/hs64/ostim.md +++ b/articles/hardware/hs64/ostim.md @@ -28,25 +28,25 @@ 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. 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. +[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. -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 -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 +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 index 94c8568d..c7ce3bb7 100644 --- a/articles/hardware/hs64/stimulator-data.md +++ b/articles/hardware/hs64/stimulator-data.md @@ -22,7 +22,7 @@ electrical stimulus is delivered. In the Headstage 64 example workflow, the "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 . +`estim_.csv` using a . The operator generates a sequence of @@ -33,6 +33,6 @@ 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" +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 2f9c3e61..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. @@ -11,7 +19,7 @@ The example workflow below can by copy/pasted into the Bonsai editor using the c - 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 enabled and armed) using a lower latency trigger mechanism by pressing the breakout + are armed) using a lower latency trigger mechanism by pressing the breakout board's X key. - Monitors memory usage data. 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/hs64.bonsai b/workflows/hardware/hs64/hs64.bonsai index 056a4687..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"> @@ -87,7 +88,7 @@ Headstage64/Rhd2164 256 true - Dsp146mHz + Off Low100mHz High10000Hz @@ -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 @@ -187,6 +216,23 @@ ColumnMajor + + + + + + 0 + + + + 30000 + 1920 + + + + + + Headstage64/TS4231V1 @@ -333,19 +379,6 @@ Headstage64/Headstage64ElectricalStimulator - true - false - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 @@ -377,16 +410,6 @@ Headstage64/Headstage64OpticalStimulator - true - 0 - 100 - 100 - 0 - 5 - 50 - 20 - 0 - 1 @@ -450,31 +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 5b020e8a..550abd07 100644 --- a/workflows/hardware/hs64/load-hs64.py +++ b/workflows/hardware/hs64/load-hs64.py @@ -2,21 +2,25 @@ 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 = Path('C:/Users/open-ephys/Documents/data/hs64') # Change to match files' directory +data_directory = Path('./') # 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) +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 @@ -28,20 +32,25 @@ #%% Load RHD2164 data -rhd2164 = {} - -# Load RHD2164 clock data and convert clock cycles to seconds -rhd2164['time'] = np.fromfile(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(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(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 @@ -66,94 +75,108 @@ 'formats': ('u8', '(1,3)f8', '(1,4)f8', '?', '(1,3)f8', '(1,3)f8', 'f8')} 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(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 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') - -# Plot stimulus delivery -ax = plt.gca() - -for stim_clock in estim['clock']: - stim_sec = stim_clock / meta['acq_clk_hz'] - if stim_sec > start_t and stim_sec < start_t + dur: - ax.axvline(x=stim_sec, color='k', alpha=0.5, ls='-') - -for stim_clock in ostim['clock']: - stim_sec = stim_clock / meta['acq_clk_hz'] - if stim_sec > start_t and stim_sec < start_t + dur: - ax.axvline(x=stim_sec, color='k', alpha=0.5, ls='--') +#%% Plot BNO055 and TS4231V1 data -ax.legend([Line2D([0], [0], color='k', alpha=0.5, ls='-'), - Line2D([0], [0], color='k', alpha=0.5, ls='--')], - ['estim', 'ostim']) +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/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