Skip to content

Commit f09abe3

Browse files
Optional "pulse" waveform option for galvo (#1226)
* Optional "pulse" waveform option for galvo Pretty basic pulse, delayed by camera delay and lasting sweep time duration. Goal is to have a simple pulse which can run the resonant galvo while acquiring. * Document and correct galvo pulse waveform Expose the new galvo pulse waveform in the configuration selector and user documentation so NI and synthetic galvo users can choose it explicitly. The underlying single_pulse waveform expects delay and pulse_width values as percentages of the sweep, but the galvo pulse branch was passing camera_delay / sweep_time as a fractional ratio. Convert the camera delay to percent before calling single_pulse and derive the remaining pulse width from that percent value. Add a regression test that uses the real SyntheticGalvo adjust path with a 10 ms camera delay over a 100 ms sweep. The test verifies the pulse starts after the expected delay and remains active for the rest of the sweep. Validation: python -m pytest -o addopts='' test/model/devices/galvo/test_galvo_base.py test/model/test_waveforms.py -q; ruff check/format on changed Python files; git diff --check. * Need [0] at the end of single_pulse Last sample in single_pulse must be zero to clear the buffer, or else the galvo never shuts off. I did this before by making the pulse width max 99%, but better to just set the last sample to zero. --------- Co-authored-by: Kevin M. Dean <kevin.dean@utsouthwestern.edu>
1 parent 74f76b4 commit f09abe3

4 files changed

Lines changed: 56 additions & 6 deletions

File tree

‎docs/source/02_user_guide/01_supported_hardware/galvo.rst‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ National Instruments
1515
Multiple types of galvanometers have been used, including Cambridge Technologies/Novanta, Thorlabs, and ScannerMAX. Each of these devices are externally controlled via analog signals delivered from an NI-based data acquisition card.
1616

1717
For NI and synthetic galvos, the software supports the following waveform options:
18-
``sawtooth``, ``sine``, ``halfsaw``, ``quadratic``, and ``centered_cubic``.
18+
``sawtooth``, ``sine``, ``halfsaw``, ``quadratic``, ``centered_cubic``, and
19+
``pulse``.
20+
21+
The ``pulse`` waveform is useful for resonant galvos and other devices that need
22+
a constant analog command during acquisition. It waits for the configured camera
23+
delay, then holds the configured galvo amplitude for the rest of the sweep.
1924

2025
.. collapse:: Configuration File
2126

@@ -38,7 +43,7 @@ For NI and synthetic galvos, the software supports the following waveform option
3843
channel: PXI6259/ao1
3944
min: -1.0
4045
max: 1.0
41-
waveform: square
46+
waveform: pulse
4247
phase: 0
4348
4449
|
@@ -118,7 +123,7 @@ galvo.
118123
channel: PXI6259/ao1
119124
min: -1.0
120125
max: 1.0
121-
waveform: square
126+
waveform: pulse
122127
phase: 0
123128
124129
|

‎src/navigate/config/configuration_database.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@
492492
"Sine": "sine",
493493
"Sawtooth": "sawtooth",
494494
"Square": "square",
495+
"Pulse": "pulse",
495496
}
496497

497498
galvo_hardware_widgets = {

‎src/navigate/model/devices/galvo/base.py‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@
3838
# Third Party Imports
3939

4040
# Local Imports
41-
from navigate.model.waveforms import centered_cubic, quadratic, sawtooth, sine_wave
41+
from navigate.model.waveforms import (
42+
centered_cubic,
43+
quadratic,
44+
sawtooth,
45+
sine_wave,
46+
single_pulse,
47+
)
4248
from navigate.tools.decorators import log_initialization
4349

4450
# # Logger Setup
@@ -173,7 +179,6 @@ def adjust(
173179

174180
# Only proceed if it is enabled in the GUI
175181
if channel["is_selected"] is True:
176-
177182
# Get the Waveform Parameters - Assumes ETL Delay < Camera Delay.
178183
# Should Assert.
179184
exposure_time = exposure_times[channel_key]
@@ -190,7 +195,7 @@ def adjust(
190195
factor_name = None
191196
if galvo_factor == "channel":
192197
factor_name = (
193-
f"Channel {channel_key[channel_key.index('_')+1:]}"
198+
f"Channel {channel_key[channel_key.index('_') + 1 :]}"
194199
)
195200
elif galvo_factor == "laser":
196201
factor_name = channel["laser"]
@@ -247,6 +252,19 @@ def adjust(
247252
offset=galvo_offset,
248253
phase=self.device_config["phase"],
249254
)
255+
elif self.galvo_waveform == "pulse":
256+
pulse_delay = 100 * self.camera_delay / self.sweep_time
257+
pulse_width = 100 - pulse_delay
258+
pulse_wave = single_pulse(
259+
sample_rate=self.sample_rate,
260+
sweep_time=self.sweep_time,
261+
pulse_width=pulse_width,
262+
amplitude=galvo_amplitude,
263+
offset=galvo_offset,
264+
delay=pulse_delay,
265+
)
266+
pulse_wave[-1] = 0
267+
self.waveform_dict[channel_key] = pulse_wave
250268
elif self.galvo_waveform == "halfsaw":
251269
new_wave = sawtooth(
252270
sample_rate=self.sample_rate,

‎test/model/devices/galvo/test_galvo_base.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,32 @@ def test_adjust_halfsaw_uses_expected_extreme(amplitude, source_wave):
229229
assert waveforms["channel_1"][1] == pytest.approx(source_wave[1])
230230

231231

232+
def test_adjust_pulse_waveform_respects_camera_delay():
233+
config = build_base_configuration(
234+
waveform="pulse",
235+
amplitude="1.0",
236+
offset="0.0",
237+
max_voltage=10,
238+
min_voltage=-10,
239+
channel_2_selected=False,
240+
)
241+
config["configuration"]["microscopes"]["TestScope"]["camera"]["delay"] = 10
242+
config["configuration"]["microscopes"]["TestScope"]["daq"]["sample_rate"] = 1000
243+
galvo = build_synthetic_galvo(config)
244+
245+
waveforms = galvo.adjust(
246+
exposure_times={"channel_1": 0.2},
247+
sweep_times={"channel_1": 0.1},
248+
)
249+
250+
waveform = waveforms["channel_1"]
251+
high_indices = np.flatnonzero(waveform > 0.5)
252+
assert waveform.shape == (100,)
253+
assert high_indices[0] == 10
254+
assert high_indices[-1] == 99
255+
assert len(high_indices) == 90
256+
257+
232258
def test_adjust_unknown_waveform_sets_channel_to_none():
233259
config = build_base_configuration(waveform="banana", channel_2_selected=False)
234260
galvo = build_synthetic_galvo(config)

0 commit comments

Comments
 (0)