Skip to content

Commit 2280a7a

Browse files
SoftwareDevice: merge
2 parents 18e4571 + 01f2a3e commit 2280a7a

File tree

12 files changed

+581
-145
lines changed

12 files changed

+581
-145
lines changed

FlyCapture2Camera/blacs_tabs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
from labscript_devices.IMAQdxCamera.blacs_tabs import IMAQdxCameraTab
1515

1616
class FlyCapture2CameraTab(IMAQdxCameraTab):
17+
"""Thin sub-class of obj:`IMAQdxCameraTab`.
18+
19+
This sub-class only defines :obj:`worker_class` to point to the correct
20+
:obj:`FlyCapture2CameraWorker`."""
1721

1822
# override worker class
1923
worker_class = 'labscript_devices.FlyCapture2Camera.blacs_workers.FlyCapture2CameraWorker'

FlyCapture2Camera/blacs_workers.py

Lines changed: 219 additions & 39 deletions
Large diffs are not rendered by default.

FlyCapture2Camera/labscript_devices.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@
1414
from labscript_devices.IMAQdxCamera.labscript_devices import IMAQdxCamera
1515

1616
class FlyCapture2Camera(IMAQdxCamera):
17+
"""Thin sub-class of :obj:`IMAQdxCamera`."""
18+
1719
description = 'FlyCapture2 Camera'
1820

NI_DAQmx/blacs_tabs.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from blacs.device_base_class import DeviceTab
2424
from .utils import split_conn_AO, split_conn_DO
25+
from . import models
2526

2627

2728
class NI_DAQmxTab(DeviceTab):
@@ -155,21 +156,24 @@ def initialise_GUI(self):
155156
self.primary_worker = "main_worker"
156157

157158
if wait_acq_device == self.device_name:
158-
if wait_timeout_device != self.device_name:
159-
msg = """The wait monitor acquisition device must be the same as the
160-
wait timeout device."""
161-
raise RuntimeError(msg)
159+
if wait_timeout_device:
160+
wait_timeout_device = connection_table.find_by_name(wait_timeout_device)
161+
wait_timeout_MAX_name = wait_timeout_device.properties['MAX_name']
162+
else:
163+
wait_timeout_MAX_name = None
162164

163165
if num_CI == 0:
164-
msg = "Device cannot be a wait monitor as it has no counter inputs"
165-
raise RuntimeError(msg)
166+
msg = """Device cannot be the wait monitor acquisiiton device as it has
167+
no counter inputs"""
168+
raise RuntimeError(dedent(msg))
166169

167170
self.create_worker(
168171
"wait_monitor_worker",
169172
'labscript_devices.NI_DAQmx.blacs_workers.NI_DAQmxWaitMonitorWorker',
170173
{
171174
'MAX_name': self.MAX_name,
172175
'wait_acq_connection': wait_acq_connection,
176+
'wait_timeout_MAX_name': wait_timeout_MAX_name,
173177
'wait_timeout_connection': wait_timeout_connection,
174178
'timeout_trigger_type': timeout_trigger_type,
175179
'min_semiperiod_measurement': min_semiperiod_measurement,

NI_DAQmx/blacs_workers.py

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from PyDAQmx.DAQmxCallBack import *
3232

3333
import numpy as np
34+
from numpy.lib.recfunctions import structured_to_unstructured
3435
import labscript_utils.h5_lock
3536
import h5py
3637
from zprocess import Event
@@ -196,32 +197,32 @@ def program_buffered_DO(self, DO_table):
196197
line_final_value = bool((1 << line) & port_final_value)
197198
final_values['%s/line%d' % (port_str, line)] = int(line_final_value)
198199

200+
# Convert DO table to a regular array and ensure it is C continguous:
201+
DO_table = np.ascontiguousarray(
202+
structured_to_unstructured(DO_table, dtype=np.uint32)
203+
)
204+
199205
# Check if DOs are all zero for the whole shot. If they are this triggers a
200206
# bug in NI-DAQmx that throws a cryptic error for buffered output. In this
201207
# case, run it as a non-buffered task.
202-
self.DO_all_zero = all(
203-
DO_table[port].sum() == 0 for port in DO_table.dtype.names
204-
)
208+
self.DO_all_zero = not np.any(DO_table)
205209
if self.DO_all_zero:
206210
DO_table = DO_table[0:1]
207211

208212
if self.static_DO or self.DO_all_zero:
209213
# Static DO. Start the task and write data, no timing configuration.
210214
self.DO_task.StartTask()
211-
# Write data for each port:
212-
for port_str in ports:
213-
# See the comment in self.program_manual as to why we are using uint32
214-
# instead of the native size of the port
215-
data = DO_table[port_str].astype(np.uint32, order='C')
216-
self.DO_task.WriteDigitalU32(
217-
1, # npts
218-
False, # autostart
219-
10.0, # timeout
220-
DAQmx_Val_GroupByChannel,
221-
data,
222-
written,
223-
None,
224-
)
215+
# Write data. See the comment in self.program_manual as to why we are using
216+
# uint32 instead of the native size of each port
217+
self.DO_task.WriteDigitalU32(
218+
1, # npts
219+
False, # autostart
220+
10.0, # timeout
221+
DAQmx_Val_GroupByScanNumber,
222+
DO_table,
223+
written,
224+
None,
225+
)
225226
else:
226227
# We use all but the last sample (which is identical to the second last
227228
# sample) in order to ensure there is one more clock tick than there are
@@ -237,23 +238,18 @@ def program_buffered_DO(self, DO_table):
237238
DAQmx_Val_FiniteSamps,
238239
npts,
239240
)
240-
self.DO_task.CfgOutputBuffer(npts)
241-
242-
# Write data for each port:
243-
for port_str in ports:
244-
# Use all but the last sample as mentioned above. See the comment in
245-
# self.program_manual as to why we are using uint32 instead of the native
246-
# size of the port.
247-
data = DO_table[port_str][:-1].astype(np.uint32, order='C')
248-
self.DO_task.WriteDigitalU32(
249-
npts,
250-
False, # autostart
251-
10.0, # timeout
252-
DAQmx_Val_GroupByChannel,
253-
data,
254-
written,
255-
None,
256-
)
241+
242+
# Write data. See the comment in self.program_manual as to why we are using
243+
# uint32 instead of the native size of each port.
244+
self.DO_task.WriteDigitalU32(
245+
npts,
246+
False, # autostart
247+
10.0, # timeout
248+
DAQmx_Val_GroupByScanNumber,
249+
DO_table[:-1], # All but the last sample as mentioned above
250+
written,
251+
None,
252+
)
257253

258254
# Go!
259255
self.DO_task.StartTask()
@@ -273,15 +269,15 @@ def program_buffered_AO(self, AO_table):
273269
# Collect the final values of the analog outs:
274270
final_values = dict(zip(AO_table.dtype.names, AO_table[-1]))
275271

276-
# Obtain a view that is a regular array:
277-
AO_table = AO_table.view((AO_table.dtype[0], len(AO_table.dtype.names)))
278-
# And convert to 64 bit floats:
279-
AO_table = AO_table.astype(np.float64)
272+
# Convert AO table to a regular array and ensure it is C continguous:
273+
AO_table = np.ascontiguousarray(
274+
structured_to_unstructured(AO_table, dtype=np.float64)
275+
)
280276

281277
# Check if AOs are all zero for the whole shot. If they are this triggers a
282278
# bug in NI-DAQmx that throws a cryptic error for buffered output. In this
283279
# case, run it as a non-buffered task.
284-
self.AO_all_zero = all(AO_table.flatten() == 0)
280+
self.AO_all_zero = not np.any(AO_table)
285281
if self.AO_all_zero:
286282
AO_table = AO_table[0:1]
287283

@@ -763,13 +759,20 @@ def wait_monitor(self):
763759
semiperiods = self.read_edges(2, timeout)
764760
# Did the wait finish of its own accord, or time out?
765761
if semiperiods is None:
766-
# It timed out. Better trigger the clock to resume!
767-
msg = """Wait timed out; retriggering clock with {:.3e} s pulse
768-
({} edge)"""
769-
msg = dedent(msg).format(pulse_width, self.timeout_trigger_type)
770-
self.logger.debug(msg)
771-
self.send_resume_trigger(pulse_width)
772-
# Wait for it to respond to that:
762+
# It timed out. If there is a timeout device, send a trigger to
763+
# resume the clock!
764+
if self.DO_task is not None:
765+
msg = """Wait timed out; retriggering clock with {:.3e} s
766+
pulse ({} edge)"""
767+
msg = msg.format(pulse_width, self.timeout_trigger_type)
768+
self.logger.debug(dedent(msg))
769+
self.send_resume_trigger(pulse_width)
770+
else:
771+
msg = """Specified wait timeout exceeded, but there is no
772+
timeout device with which to resume the experiment.
773+
Continuing to wait."""
774+
self.logger.warning(dedent(msg))
775+
# Keep waiting for the clock to resume:
773776
self.logger.debug('Waiting for pulse indicating end of wait')
774777
semiperiods = self.read_edges(2, timeout=None)
775778
# Alright, now we're at the end of the wait.
@@ -819,7 +822,8 @@ def stop_tasks(self, abort):
819822
if not abort:
820823
# Don't want errors about incomplete task to be raised if we are aborting:
821824
self.CI_task.StopTask()
822-
self.DO_task.StopTask()
825+
if self.DO_task is not None:
826+
self.DO_task.StopTask()
823827
if self.CI_task is not None:
824828
self.CI_task.ClearTask()
825829
self.CI_task = None
@@ -853,15 +857,16 @@ def start_tasks(self):
853857
self.CI_task.StartTask()
854858

855859
# The timeout task:
856-
self.DO_task = Task()
857-
DO_chan = self.MAX_name + '/' + self.wait_timeout_connection
858-
self.DO_task.CreateDOChan(DO_chan, "", DAQmx_Val_ChanForAllLines)
859-
# Ensure timeout trigger is armed:
860-
written = int32()
861-
# Writing autostarts the task:
862-
self.DO_task.WriteDigitalLines(
863-
1, True, 1, DAQmx_Val_GroupByChannel, self.timeout_rearm, written, None
864-
)
860+
if self.wait_timeout_MAX_name is not None:
861+
self.DO_task = Task()
862+
DO_chan = self.wait_timeout_MAX_name + '/' + self.wait_timeout_connection
863+
self.DO_task.CreateDOChan(DO_chan, "", DAQmx_Val_ChanForAllLines)
864+
# Ensure timeout trigger is armed:
865+
written = int32()
866+
# Writing autostarts the task:
867+
self.DO_task.WriteDigitalLines(
868+
1, True, 1, DAQmx_Val_GroupByChannel, self.timeout_rearm, written, None
869+
)
865870

866871
def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
867872
self.logger.debug('transition_to_buffered')

NI_DAQmx/labscript_devices.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
AnalogIn,
3232
bitfield,
3333
config,
34+
compiler,
3435
LabscriptError,
3536
set_passed_properties,
3637
)
@@ -377,6 +378,13 @@ def _make_analog_input_table(self, inputs):
377378
acq['units'],
378379
)
379380
)
381+
if acquisitions and compiler.wait_table and compiler.wait_monitor is None:
382+
msg = """Cannot do analog input on an NI DAQmx device in an experiment that
383+
uses waits without a wait monitor. This is because input data cannot be
384+
'chunked' into requested segments without knowledge of the durations of
385+
the waits. See labscript.WaitMonitor for details."""
386+
raise LabscriptError(dedent(msg))
387+
380388
# The 'a256' dtype below limits the string fields to 256
381389
# characters. Can't imagine this would be an issue, but to not
382390
# specify the string length (using dtype=str) causes the strings
@@ -396,6 +404,51 @@ def _make_analog_input_table(self, inputs):
396404

397405
return acquisition_table
398406

407+
def _check_wait_monitor_timeout_device_config(self):
408+
"""Check that if we are the wait monitor acquisition device and another device
409+
is the wait monitor timeout device, that a) the other device is a DAQmx device
410+
and b) the other device has a start_order lower than us and a stop_order higher
411+
than us."""
412+
if compiler.wait_monitor is None:
413+
return
414+
acquisition_device = compiler.wait_monitor.acquisition_device
415+
timeout_device = compiler.wait_monitor.timeout_device
416+
if acquisition_device is not self or timeout_device is None:
417+
return
418+
if timeout_device is self:
419+
return
420+
if not isinstance(timeout_device, NI_DAQmx):
421+
msg = """If using an NI DAQmx device as a wait monitor acquisition device,
422+
then the wait monitor timeout device must also be an NI DAQmx device,
423+
not {}."""
424+
raise TypeError(dedent(msg).format(type(timeout_device)))
425+
timeout_start = timeout_device.start_order
426+
if timeout_start is None:
427+
timeout_start = 0
428+
timeout_stop = timeout_device.stop_order
429+
if timeout_stop is None:
430+
timeout_stop = 0
431+
self_start = self.start_order
432+
if self_start is None:
433+
self_start = 0
434+
self_stop = self.stop_order
435+
if self_stop is None:
436+
self_stop = 0
437+
if timeout_start >= self_start or timeout_stop <= self_stop:
438+
msg = """If using different DAQmx devices as the wait monitor acquisition
439+
and timeout devices, the timeout device must transition_to_buffered
440+
before the acquisition device, and transition_to_manual after it, in
441+
order to ensure the output port for timeout pulses is not in use (by the
442+
manual mode DO task) when the wait monitor subprocess attempts to use
443+
it. To achieve this, pass the start_order and stop_order keyword
444+
arguments to the devices in your connection table, ensuring that the
445+
timeout device has a lower start_order and a higher stop_order than the
446+
acquisition device. The default start_order and stop_order is zero, so
447+
if you are not otherwise controlling the order that devices are
448+
programmed, you can set start_order=-1, stop_order=1 on the timeout
449+
device only."""
450+
raise RuntimeError(dedent(msg))
451+
399452
def generate_code(self, hdf5_file):
400453
IntermediateDevice.generate_code(self, hdf5_file)
401454
analogs = {}
@@ -430,6 +483,7 @@ def generate_code(self, hdf5_file):
430483
AI_table = self._make_analog_input_table(inputs)
431484

432485
self._check_AI_not_too_fast(AI_table)
486+
self._check_wait_monitor_timeout_device_config()
433487

434488
grp = self.init_device_group(hdf5_file)
435489
if AO_table is not None:

NI_DAQmx/models/NI_PCI_DIO_32HS.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#####################################################################
2+
# #
3+
# /NI_DAQmx/models/_subclass_template.py #
4+
# #
5+
# Copyright 2018, Christopher Billington #
6+
# #
7+
# This file is part of the module labscript_devices, in the #
8+
# labscript suite (see http://labscriptsuite.org), and is #
9+
# licensed under the Simplified BSD License. See the license.txt #
10+
# file in the root of the project for the full license. #
11+
# #
12+
#####################################################################
13+
14+
#####################################################################
15+
# WARNING #
16+
# #
17+
# This file is auto-generated, any modifications may be #
18+
# overwritten. See README.txt in this folder for details #
19+
# #
20+
#####################################################################
21+
22+
23+
from __future__ import division, unicode_literals, print_function, absolute_import
24+
from labscript_utils import PY2
25+
26+
if PY2:
27+
str = unicode
28+
29+
from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx
30+
31+
CAPABILITIES = {
32+
'AI_range': None,
33+
'AI_start_delay': None,
34+
'AO_range': None,
35+
'max_AI_multi_chan_rate': None,
36+
'max_AI_single_chan_rate': None,
37+
'max_AO_sample_rate': None,
38+
'max_DO_sample_rate': 20000000.0,
39+
'min_semiperiod_measurement': None,
40+
'num_AI': 0,
41+
'num_AO': 0,
42+
'num_CI': 0,
43+
'ports': {
44+
'port0': {'num_lines': 8, 'supports_buffered': True},
45+
'port1': {'num_lines': 8, 'supports_buffered': True},
46+
'port2': {'num_lines': 8, 'supports_buffered': True},
47+
'port3': {'num_lines': 8, 'supports_buffered': True},
48+
'port4': {'num_lines': 0, 'supports_buffered': False},
49+
'port5': {'num_lines': 4, 'supports_buffered': False},
50+
},
51+
'supports_buffered_AO': False,
52+
'supports_buffered_DO': True,
53+
'supports_semiperiod_measurement': False,
54+
}
55+
56+
57+
class NI_PCI_DIO_32HS(NI_DAQmx):
58+
description = 'NI-PCI-DIO-32HS'
59+
60+
def __init__(self, *args, **kwargs):
61+
# Any provided kwargs take precedent over capabilities
62+
combined_kwargs = CAPABILITIES.copy()
63+
combined_kwargs.update(kwargs)
64+
NI_DAQmx.__init__(self, *args, **combined_kwargs)

0 commit comments

Comments
 (0)