diff --git a/.gitignore b/.gitignore index 85a021d2..8fc6bd7d 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,4 @@ conda_packages/ docs/html/ docs/source/_build/ docs/source/components.rst +docs/source/devices/_apidoc diff --git a/README.md b/README.md index 5572c725..88e4b949 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ The following devices have been implemented in the _labscript suite_:†` via + +.. code-block:: python + + from labscript_devices import register_classes + +This function informs **labscript** where to find the necessary classes during import. An example for the `NI_DAQmx` device is + +.. code-block:: python + + register_classes( + 'NI_DAQmx', + BLACS_tab='labscript_devices.NI_DAQmx.blacs_tabs.NI_DAQmxTab', + runviewer_parser='labscript_devices.NI_DAQmx.runviewer_parsers.NI_DAQmxParser', + ) + +Contributions to **labscript-devices** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you decide to implement a labscript-device for controlling new hardware, we highly encourage you to consider making a pull-request to the **labscript-devices** repository in order to add your work to the **labscript-suite**. +Increasing the list of supported devices is an important way for the **labscript-suite** to continue to grow, allowing new users to more quickly get up and running with hardware they may already have. \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 3610cd87..5e4d5b26 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -39,6 +39,7 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.autosummary", "sphinx.ext.autosectionlabel", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", @@ -49,6 +50,8 @@ ] autodoc_typehints = 'description' +autoclass_content = 'both' # options: 'both', 'class', 'init' +autodoc_mock_imports = ['PyDAQmx'] # Prefix each autosectionlabel with the name of the document it is in and a colon autosectionlabel_prefix_document = True @@ -169,6 +172,13 @@ else: todo_include_todos = True +# -- Options for PDF output -------------------------------------------------- + +latex_elements = { + # make entire document landscape + 'geometry': '\\usepackage[landscape]{geometry}', +} + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -223,3 +233,64 @@ def setup(app): img_path=img_path ) ) + + # hook to run apidoc before building + app.connect('builder-inited', run_apidoc) + # hooks to test docstring coverage + app.connect('autodoc-process-docstring', doc_coverage) + app.connect('build-finished', doc_report) + + +def run_apidoc(_): + """Runs apidoc with our desired parameters to generate the NI_DAQmx models docs. + """ + from sphinx.ext.apidoc import main + if os.environ.get('READTHEDOCS'): + rel_path = '../..' + else: + rel_path = '..' + daq_models_path = os.path.join(os.path.abspath(rel_path), + 'labscript_devices') + out_path = os.path.join(os.path.dirname(Path(__file__)), + 'devices', '_apidoc', 'models') + templates_path = os.path.join(os.path.dirname(Path(__file__)), + '_templates', 'models') + main(['-TMf', '-s', 'inc', + '-t', templates_path, + '-o', out_path, daq_models_path]) + + +members_to_watch = ['module', 'class', 'function', 'exception', 'method', 'attribute'] +doc_count = 0 +undoc_count = 0 +undoc_objects = [] +undoc_print_objects = False + + +def doc_coverage(app, what, name, obj, options, lines): + global doc_count + global undoc_count + global undoc_objects + + if (what in members_to_watch and len(lines) == 0): + # blank docstring detected + undoc_count += 1 + undoc_objects.append(name) + else: + doc_count += 1 + + +def doc_report(app, exception): + global doc_count + global undoc_count + global undoc_objects + # print out report of documentation coverage + total_docs = undoc_count + doc_count + if total_docs != 0: + print(f'\nAPI Doc coverage of {doc_count/total_docs:.1%}') + if undoc_print_objects or os.environ.get('READTHEDOCS'): + print('\nItems lacking documentation') + print('===========================') + print(*undoc_objects, sep='\n') + else: + print('No docs counted, run \'make clean\' then rebuild to get the count.') \ No newline at end of file diff --git a/docs/source/devices.rst b/docs/source/devices.rst new file mode 100644 index 00000000..6237a18e --- /dev/null +++ b/docs/source/devices.rst @@ -0,0 +1,90 @@ +Devices +========= + +Here is a list of all the currently supported devices. + + +Pseudoclocks +~~~~~~~~~~~~ + +Pseudoclocks provide the timing backbone of the labscript_suite. +These devices produce hardware-timed clocklines that trigger other device outputs and acquisitions. +Many pseudoclock devices also include other types of outputs, including digital voltage and DDS frequency synthesizers. + +.. toctree:: + :maxdepth: 2 + + devices/pulseblaster + devices/pulseblaster_no_dds + devices/opalkellyXEM3001 + devices/pineblaster + devices/prawnblaster + devices/rfblaster + +NI DAQS +~~~~~~~~~~~~ + +The NI_DAQmx device provides a generic interface for National Instruments data acquisition hardware. +This includes digital and analog voltage I/O. These input/outputs can be either static or hardware-timed dynamically changing variables. + +.. toctree:: + :maxdepth: 2 + + devices/ni_daqs + +Cameras +~~~~~~~~~~~~ + +The camera devices provide interfaces for using various scientific cameras to acquire hardware-timed images during an experiment. +They are organized by the programming API the underlies the communication to the device. +The "master" camera class which provides the core functionality and from which the others derive is the IMAQdx class. + +.. toctree:: + :maxdepth: 2 + + devices/IMAQdx + devices/pylon + devices/flycapture2 + devices/spinnaker + devices/andorsolis + + +Frequency Sources +~~~~~~~~~~~~~~~~~ + +These devices cover various frequency sources that provide either hardware-timed frequency, amplitude, or phase updates or static frequency outputs. + +.. toctree:: + :maxdepth: 2 + + devices/novatechDDS9m + devices/phasematrixquicksyn + + +Miscellaneous +~~~~~~~~~~~~~~~ + +These devices cover other types of devices. + +.. toctree:: + :maxdepth: 2 + + devices/alazartechboard + devices/lightcrafterdmd + devices/tekscope + devices/zaberstagecontroller + + +Other +~~~~~~~~~~~~~~ + +These devices provide dummy instruments for prototyping and testing purposes of the rest of the labscript_suite as well as the FunctionRunner device which can run arbitrary code post-shot. + +.. toctree:: + :maxdepth: 2 + + devices/functionrunner + devices/dummypseudoclock + devices/dummyintermediate + devices/testdevice + \ No newline at end of file diff --git a/docs/source/devices/IMAQdx.rst b/docs/source/devices/IMAQdx.rst new file mode 100644 index 00000000..92bcfb47 --- /dev/null +++ b/docs/source/devices/IMAQdx.rst @@ -0,0 +1,47 @@ +IMAQdx Cameras +============== + +Overview +~~~~~~~~ + +The "master" camera device from which all others derive. + +.. autosummary:: + labscript_devices.IMAQdxCamera.labscript_devices + labscript_devices.IMAQdxCamera.blacs_tabs + labscript_devices.IMAQdxCamera.blacs_workers + +Installation +~~~~~~~~~~~~ + + +Usage +~~~~~ + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.IMAQdxCamera + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.IMAQdxCamera.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.IMAQdxCamera.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.IMAQdxCamera.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/alazartechboard.rst b/docs/source/devices/alazartechboard.rst new file mode 100644 index 00000000..a4d715f8 --- /dev/null +++ b/docs/source/devices/alazartechboard.rst @@ -0,0 +1,22 @@ +Alazar Tech Board +================= + +A labscript device class for data acquisition boards made by Alazar Technologies Inc (ATS). + +Installation +~~~~~~~~~~~~ + +This device requires the atsapi.py wrapper. It should be installed into site-packages or +kept in the local directory. + +It also uses the tqdm progress bar, which is not a standard dependency for the labscript-suite. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.AlazarTechBoard + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + :private-members: \ No newline at end of file diff --git a/docs/source/devices/andorsolis.rst b/docs/source/devices/andorsolis.rst new file mode 100644 index 00000000..26093386 --- /dev/null +++ b/docs/source/devices/andorsolis.rst @@ -0,0 +1,45 @@ +Andor Solis Cameras +=================== + +A labscript device for controlling Andor scientific cameras via the Andor SDK3 interface. + +Presently, this device is hard-coded for use with the iXon camera. +Minor modifications can allow use with other Andor cameras, so long as they are compatible with the Andor SDK3 library. + +.. autosummary:: + labscript_devices.AndorSolis.labscript_devices + labscript_devices.AndorSolis.blacs_tabs + labscript_devices.AndorSolis.blacs_workers + +Installation +~~~~~~~~~~~~ + +The Andor SDK is available from Andor as a paid product (typically purchased with the camera). +It must be installed with the SDK directory added to the system path. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.AndorSolis + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.AndorSolis.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.AndorSolis.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.AndorSolis.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/dummyintermediate.rst b/docs/source/devices/dummyintermediate.rst new file mode 100644 index 00000000..7a654dc7 --- /dev/null +++ b/docs/source/devices/dummyintermediate.rst @@ -0,0 +1,10 @@ +Dummy Intermediate Device +========================= + +.. automodule:: labscript_devices.DummyIntermediateDevice + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + :private-members: + diff --git a/docs/source/devices/dummypseudoclock.rst b/docs/source/devices/dummypseudoclock.rst new file mode 100644 index 00000000..5078668b --- /dev/null +++ b/docs/source/devices/dummypseudoclock.rst @@ -0,0 +1,63 @@ +Dummy Pseudoclock +================= + +This device represents a dummy labscript device for purposes of testing BLACS +and labscript. The device is a PseudoclockDevice, and can be the sole device +in a connection table or experiment. + +.. autosummary:: + labscript_devices.DummyPseudoclock.labscript_devices + labscript_devices.DummyPseudoclock.blacs_tabs + labscript_devices.DummyPseudoclock.blacs_workers + labscript_devices.DummyPseudoclock.runviewer_parsers + +Usage +~~~~~ + +.. code-block:: python + + from labscript import * + + from labscript_devices.DummyPseudoclock.labscript_devices import DummyPseudoclock + from labscript_devices.DummyIntermediateDevice import DummyIntermediateDevice + + DummyPseudoclock(name='dummy_clock',BLACS_connection='dummy') + DummyIntermediateDevice(name='dummy_device',BLACS_connection='dummy2', + parent_device=dummy_clock.clockline) + + start() + stop(1) + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.DummyPseudoclock + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.DummyPseudoclock.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.DummyPseudoclock.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.DummyPseudoclock.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.DummyPseudoclock.runviewer_parsers + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/flycapture2.rst b/docs/source/devices/flycapture2.rst new file mode 100644 index 00000000..1f8041d9 --- /dev/null +++ b/docs/source/devices/flycapture2.rst @@ -0,0 +1,125 @@ +FlyCapture2 Cameras +=================== + +This device allows control of FLIR (formerly Point Grey) scientific cameras via the `FlyCapture2 SDK `_ with the now deprecated PyCapture2 wrapper. +In order to use this device, both the SDK and the python wrapper must be installed. +Note that PyCapture2 only supports up to Python 3.6. + +The new FLIR SDK is supported using the :doc:`SpinnakerCamera labscript device `. + +.. autosummary:: + labscript_devices.FlyCapture2Camera.labscript_devices + labscript_devices.FlyCapture2Camera.blacs_tabs + labscript_devices.FlyCapture2Camera.blacs_workers + +Installation +~~~~~~~~~~~~ + +First ensure that the FlyCapture2 SDK is installed. + +The python wrapper is available via FLIR and is only released for Python up to 3.6. +It must be installed separately, pointed to the correct conda environment during install. + + +For GigE cameras, ensure that the network interface card (NIC) on the computer with the BLACS controlling the camera has enabled Jumbo Frames. +That maximum allowed value (typically 9000) is preferable to avoid dropped frames. + +Usage +~~~~~ + +Like the :doc:`IMAQdxCamera ` device, the bulk of camera configuration is performed using a dictionary of kwargs, where the key names and values mirror those provided by the FlyCapture2 SDK interface. +Which parameters can/need to be set depend on the communication interface. +Discovery of what parameters are available can be done in three ways: + +1. Careful reading of the FlyCapture2 SDK docs. +2. Mirroring the FlyCap Viewer parameter names and values. +3. Connecting to the camera with a minimal configuration, viewing the current parameters dictionary, and copying the relevant values to the connection table (preferred). + +The python structure for setting these values differs somewhat from other camera devices in labscript, taking the form of nested dictionaries. +This structure most closely matches the structure of the FlyCapture2 SDK in that each camera property has multiple sub-elements that control the feature. +In this implementation, the standard camera properties are set using keys with ALL CAPS. +The control of the Trigger Mode and Image Mode properties is handled separately, using a slightly different nesting structure than the other properties. + +Below is a generic configuration for a Point Grey Blackfly PGE-23S6M-C device. + +.. code-block:: python + + from labscript import * + + from labscript_devices.FlyCapture2Camera.labscript_devices import FlyCapture2Camera + + FlyCapture2Camera('gigeCamera',parent_device=parent,connection=conn, + serial_number=1234567, # set to the camera serial number + minimum_recovery_time=36e-6, # the minimum exposure time depends on the camera model & configuration + camera_attributs={ + 'GAMMA':{ + 'onOff':False, + 'absControl':True, + 'absValue':1}, + 'AUTO_EXPOSURE':{ + 'onOff':True, + 'absControl':True, + 'autoManualMode':False, + 'absValue':0}, + 'GAIN':{ + 'autoManualMode':False, + 'absControl':True, + 'absValue':0}, + 'SHARPNESS':{ + 'onOff':False, + 'autoManualMode':False, + 'absValue':1024}, + 'FRAME_RATE':{ + 'autoManualMode':False, + 'absControl':True}, + 'SHUTTER':{ + 'autoManualMode':False, + 'absValue':0}, + 'TriggerMode':{ + 'polarity':1, + 'source':0, + 'mode':1, + 'onOff':True}, + 'ImageMode':{ + 'width':1920, + 'height':1200, + 'offsetX':0, + 'offsetY':0, + 'pixelFormat':'MONO16'} + }, + manual_camera_attributes={'TriggerMode':{'onOff':False}}) + + start() + + gigeCamera.expose(t=0.5,'exposure1') + + + stop(1) + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.FlyCapture2Camera + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FlyCapture2Camera.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FlyCapture2Camera.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FlyCapture2Camera.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/functionrunner.rst b/docs/source/devices/functionrunner.rst new file mode 100644 index 00000000..43489d91 --- /dev/null +++ b/docs/source/devices/functionrunner.rst @@ -0,0 +1,44 @@ +Function Runner +=============== + +A labscript device to run custom functions before, after, or during (not yet +implemented) the experiment in software time. + +.. autosummary:: + labscript_devices.FunctionRunner.labscript_devices + labscript_devices.FunctionRunner.blacs_tabs + labscript_devices.FunctionRunner.blacs_workers + labscript_devices.FunctionRunner.utils + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.FunctionRunner + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FunctionRunner.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FunctionRunner.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FunctionRunner.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.FunctionRunner.utils + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/lightcrafterdmd.rst b/docs/source/devices/lightcrafterdmd.rst new file mode 100644 index 00000000..633e4e63 --- /dev/null +++ b/docs/source/devices/lightcrafterdmd.rst @@ -0,0 +1,17 @@ +Light Crafter DMD +================= + +This device allows for control of Light Crafter development module boards. +It is currently hard-coded to work with a DLPC300 with a fixed DLP 0.3 WVGA +resolution. Extenstion to other models involves subclassing and altering +relevant class attributes. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.LightCrafterDMD + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + :private-members: \ No newline at end of file diff --git a/docs/source/devices/ni_daq_models.rst b/docs/source/devices/ni_daq_models.rst new file mode 100644 index 00000000..511e1c15 --- /dev/null +++ b/docs/source/devices/ni_daq_models.rst @@ -0,0 +1,4 @@ +Sub-Classed NI DAQ Models +========================= + +.. include:: _apidoc/models/labscript_devices.NI_DAQmx.models.inc diff --git a/docs/source/devices/ni_daqs.rst b/docs/source/devices/ni_daqs.rst new file mode 100644 index 00000000..d89b1edf --- /dev/null +++ b/docs/source/devices/ni_daqs.rst @@ -0,0 +1,152 @@ +NI DAQs +======= + +Overview +~~~~~~~~ + +This labscript device is a master device that can control a wide range of NI Multi-function data acquistion devices. + +Installation +~~~~~~~~~~~~ + +This labscript device requires an installation of the NI-DAQmx module, available for free from `NI `_. + +The python bindings are provided by the PyDAQmx package, available through pip. + + +Adding a Device +~~~~~~~~~~~~~~~ + +While the `NI_DAQmx` device can be used directly by manually specifying the many necessary parameters, +it is preferable to add the device via an appropriate subclass. +This process is greatly simplified by using the :mod:`get_capabilities.py ` script +followed by the :mod:`generate_subclasses.py ` script. + +To add support for a DAQmx device that is not yet supported, run `get_capabilities.py` on +a computer with the device in question connected (or with a simulated device of the +correct model configured in NI-MAX). This will introspect the capabilities of the device +and add those details to `capabilities.json`. To generate labscript device classes for all +devices whose capabilities are known, run `generate_subclasses.py`. Subclasses of NI_DAQmx +will be made in the `models` subfolder, and they can then be imported into labscript code with: + +.. code-block:: python + + from labscript_devices.NI_DAQmx.labscript_devices import NI_PCIe_6363 + +or similar. The class naming is based on the model name by prepending "NI\_" and +replacing the hyphen with an underscore, i.e. 'PCIe-6363' -> NI_PCIe_6363. + +Generating device classes requires the Python code-formatting library 'black', which can +be installed via pip (Python 3.6+ only). If you don't want to install this library, the +generation code will still work, it just won't be formatted well. + +The current list of pre-subclassed devices is: + +.. toctree:: + :maxdepth: 2 + + ni_daq_models + + +Usage +~~~~~ + +NI Multifunction DAQs generally provide hardware channels for +:class:`StaticAnalogOut `, +:class:`StaticDigitalOut `, +:class:`AnalogOut `, +:class:`DigitalOut `, +and :class:`AnalogIn ` labscript quantities for use in experiments. +Exact numbers of channels, performance, and configuration depend on the model of DAQ used. + +.. code-block:: python + + from labscript import * + + from labscript_devices.DummyPseudoclock.labscript_devices import DummyPseudoclock + from labscript_devices.NI_DAQmx.models.NI_USB_6343 import NI_USB_6343 + + DummyPseudoclock('dummy_clock',BLACS_connection='dummy') + + NI_USB_6343(name='daq',parent_device=dummy_clock.clockline, + MAX_name='ni_usb_6343', + clock_terminal='/ni_usb_6343/PFI0', + acquisition_rate=100e3) + + AnalogIn('daq_ai0',daq,'ai0') + AnalogIn('daq_ai1',daq,'ai1') + + AnalogOut('daq_ao0',daq,'ao0') + AnalogIn('daq_ai1',daq,'ai1') + +NI DAQs are also used within labscript to provide a :class:`WaitMonitor `. +When configured, the `WaitMonitor` allows for arbitrary-length pauses in experiment execution, waiting for some trigger to restart. +The monitor provides a measurement of the duration of the wait for use in interpreting the resulting data from the experiment. + +Configuration uses three digital I/O connections on the DAQ: + +* The parent_connection which sends pulses at the beginning of the experiment, the start of the wait, and the end of the wait. +* The acquisition_connection which must be wired to a counter and measures the time between the pulses of the parent connection. +* The timeout_connection which can send a restart pulse if the wait times out. + +An example configuration of a `WaitMonitor` using a NI DAQ is shown here + +.. code-block:: python + + # A wait monitor for AC-line triggering + # This requires custom hardware + WaitMonitor(name='wait_monitor',parent_device=daq,connection='port0/line0', + acquisition_device=daq, acquisition_connection='ctr0', + timeout_device=daq, timeout_connection='PFI1') + # Necessary to ensure even number of digital out lines in shot + DigitalOut('daq_do1',daq,'port0/line1') + +Note that the counter connection is specified using the logical label `'ctr0'`. On many NI DAQs, the physical connection to this counter is PFI9. +The physical wiring for this configuration would have port0/line0 wired directly to PFI9, with PFI1 being sent to the master pseudoclock retriggering system in case of timeout. +If timeouts are not expected/represent experiment failure, this physical connection can be omitted. + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.NI_DAQmx + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.NI_DAQmx.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.NI_DAQmx.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.NI_DAQmx.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.NI_DAQmx.runviewer_parsers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.NI_DAQmx.daqmx_utils + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.NI_DAQmx.utils + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/novatechDDS9m.rst b/docs/source/devices/novatechDDS9m.rst new file mode 100644 index 00000000..b0124a47 --- /dev/null +++ b/docs/source/devices/novatechDDS9m.rst @@ -0,0 +1,15 @@ +Novatech DDS 9m +=============== + +Labscript device for control of the Novatech DDS9m synthesizer. +With minor modifications, it can also control the Novatech 409B DDS. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.NovaTechDDS9M + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + :private-members: \ No newline at end of file diff --git a/docs/source/devices/opalkellyXEM3001.rst b/docs/source/devices/opalkellyXEM3001.rst new file mode 100644 index 00000000..fdd2b264 --- /dev/null +++ b/docs/source/devices/opalkellyXEM3001.rst @@ -0,0 +1,20 @@ +Cicero Opal-Kelly XEM3001 +========================= + +A pseudoclocking labscript device based on the OpalKelly XEM3001 integration module, which uses a Xilinx Spartan-3 FPGA. + +Installation +~~~~~~~~~~~~ + +Firmware (.bit) files for the FPGA are available `here `_ and should be placed in the labscript_devices folder along with the `CiceroOpalKellyXEM3001.py` file. +The Opal Kelly SDK, which provides the python bindings, is also required. +The python bindings will need to either be added to the PATH or manually copied to the site-packages of the virtual environment that BLACS is running in. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.CiceroOpalKellyXEM3001 + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/phasematrixquicksyn.rst b/docs/source/devices/phasematrixquicksyn.rst new file mode 100644 index 00000000..89f712a5 --- /dev/null +++ b/docs/source/devices/phasematrixquicksyn.rst @@ -0,0 +1,15 @@ +QuickSyn FSW-0010 Synthesizer +============================= + +A labscript device that controlls the NI Quicksyn FSW-0010 Microwave Synthesizer +(formerly PhaseMatrix). + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.PhaseMatrixQuickSyn + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + :private-members: \ No newline at end of file diff --git a/docs/source/devices/pineblaster.rst b/docs/source/devices/pineblaster.rst new file mode 100644 index 00000000..af054413 --- /dev/null +++ b/docs/source/devices/pineblaster.rst @@ -0,0 +1,13 @@ +Pineblaster +=========== + +This labscript device controls the `PineBlaster `_ open-source digital pattern generator based on the Digilent chipKIT Max32 Prototyping platform. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.PineBlaster + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/prawnblaster.rst b/docs/source/devices/prawnblaster.rst new file mode 100644 index 00000000..c491bf85 --- /dev/null +++ b/docs/source/devices/prawnblaster.rst @@ -0,0 +1,114 @@ +PrawnBlaster +============ + +This labscript device controls the `PrawnBlaster `_ open-source digital pattern generator based on the `Raspberry Pi Pico `_ platform. + +Specifications +~~~~~~~~~~~~~~ + +The PrawnBlaster takes advantage of the specs of the Pico to provide the following: + +* Configurable as 1, 2, 3, or 4 truly independent pseudoclocks. + + - Each clock has its own independent instruction set and synchronization between clocks is not required. + - Assuming the default internal clock of 100 MHz, each clock has: + + - Minimum pulse half-period of 50 ns + - Maximum pulse half-period of 42.9 s + - Half-period resolution of 10 ns + +* 30,000 instructions (each with up to 2^32 repetitions) distributed evenly among the configured pseudoclocks; 30,000, 15,000, 10,000, and 7,500 for 1, 2, 3, 4 pseudoclocks respectively. +* Support for external hardware triggers (external trigger common to all pseudoclocks) + + - Up to 100 retriggers (labscript-suite waits) per pseudoclock + - Each wait can support a timeout of up to 42.9 s + - Each wait is internally monitored for its duration (resolution of +/-10 ns) + +* Can be referenced to an external LVCMOS clock +* Internal clock can be set up to 133 MHz (timing specs scale accordingly) + +Installation +~~~~~~~~~~~~ + +In order to turn the standard Pico into a PrawnBlaster, you need to load the custom firmware available in the `Github repo `_ onto the board. +The simplest way to do this is by holding the reset button on the board while plugging the USB into a computer. +This will bring up a mounted folder that you copy-paste the firmware to. Once copied, the board will reset and be ready to go. + +Note that this device communicates using a virtual COM port. +The number is assigned by the controlling computer and will need to be determined in order for BLACS to connect to the PrawnBlaster. + +Usage +~~~~~ + +The default pinout for the PrawnBlaster is as follows: + +* Pseudoclock 0 output: GPIO 9 +* Pseudoclock 1 output: GPIO 11 +* Pseudoclock 2 output: GPIO 13 +* Pseudoclock 3 output: GPIO 15 +* External Triggeer input: GPIO 0 +* External Clock input: GPIO 20 + +Note that signal cable grounds should be connected to the digital grounds of the Pico for proper operation. + +The PrawnBlaster provides up to four independent clocklines. +They can be accessed either by `name.clocklines[int]` +or directly by their auto-generated labscript names `name_clock_line_int`. + +An example connection table that uses the PrawnBlaster: + +.. code-block:: python + + from labscript import * + + from labscript_devices.PrawnBlaster.labscript_devices import PrawnBlaster + from labscript_devices.NI_DAQmx.models.NI_USB_6363 import NI_USB_6363 + + PrawnBlaster(name='prawn', com_port='COM6', num_pseudoclocks=1) + + NI_USB_6363(name='daq', MAX_name='Dev1', + parent_device=prawn.clocklines[0], clock_terminal='/Dev1/PFI0', + acquisition_rate=100e3) + + AnalogOut('ao0', daq, 'ao0') + AnalogOut('ao1', daq, 'ao1') + + if __name__ == '__main__': + + start(0) + + stop(1) + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.PrawnBlaster + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PrawnBlaster.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PrawnBlaster.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PrawnBlaster.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PrawnBlaster.runviewer_parsers + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/pulseblaster.rst b/docs/source/devices/pulseblaster.rst new file mode 100644 index 00000000..0e03ba15 --- /dev/null +++ b/docs/source/devices/pulseblaster.rst @@ -0,0 +1,51 @@ +Pulseblaster +============ + +This labscript device controls the Spincore PulseblaserDDS-II-300-AWG. +The Pulseblaster is a programmable pulse generator that is the typical timing backbone of an experiment (ie it generates the pseudoclock timing pulses that control execution of other devices in the experiment). +This labscript device is the master implementation of the various Pulseblaster devices. +Other Pulseblaster labscript devices subclass this device and make the relevant changes to hard-coded values. +Most importantly, the `core_clock_freq` must be manually set to match that of the Pulseblaster being used in order for the timing of the programmed pulses to be correct (in the `labscript_device` and the `BLACS_worker`). + +This particular version of Pulseblaster has a 75 MHz core clock frequency and also has DDS synthesizer outputs. + +Installation +~~~~~~~~~~~~ + +Use of the Pulseblaster requires driver installation available from the manufacturer `here `_. +The corresponding python wrapper, `spinapi `_ is available via pip. + +.. code-block:: bash + + pip install -U spinapi + +Usage +~~~~~ + +.. code-block:: python + + from labscript import * + + from labscript_devices.PulseBlaster import PulseBlaster + + PulseBlaster(name='pb',board_number=0,programming_scheme='pb_start/BRANCH') + + Clockline(name='pb_clockline_fast', pseudoclock=pb.pseudoclock,connection='flag 0') + Clockline(name='pb_clockline_slow', pseudoclock=pb.pseudoclock,connection='flag 1') + + DigitalOut(name='pb_0',parent_device=pb.direct_outputs,connection='flag 2') + + PulseBlasterDDS(name='pb_dds_0',parent_device=pb.direct_outputs, 'channel 0') + + start() + + stop(1) + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.PulseBlaster + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/pulseblaster_no_dds.rst b/docs/source/devices/pulseblaster_no_dds.rst new file mode 100644 index 00000000..20964037 --- /dev/null +++ b/docs/source/devices/pulseblaster_no_dds.rst @@ -0,0 +1,126 @@ +Pulseblaster (-DDS) +=================== + +Overview +~~~~~~~~ + +This labscript device controls the Spincore Pulseblasers that do not have DDS outputs. +The Pulseblaster is a programmable pulse generator that is the typical timing backbone of an experiment (ie it generates the pseudoclock timing pulses that control execution of other devices in the experiment). +This labscript device inherits from the :doc:`Pulseblaster ` device. +The primary difference is the removal of code handling DDS outputs. + +The labscript-suite currently supports a number of no-dds variants of the Pulseblaster device, each with different numbers of outputs and clock frequencies: + + * `PulseBlaster_No_DDS`: Has 24 digital outputs and a 100 MHz core clock frequency. + * `PulseBlasterUSB`: Identical to the `PulseBlaster_No_DDS` device + * `PulseBlaster_SP2_24_100_32k`: Has slightly lower `clock_limit` and `clock_resolution` than the standard device. Also supports 32k instructions instead of the standard 4k. + * `PulseBlasterESRPro200`: Has a 200 MHz core clock frequency. + * `PulseBlasterESRPro500`: Has a 500 MHz core clock frequency. + +ESR-Pro PulseBlasters +^^^^^^^^^^^^^^^^^^^^^ + +The timing resolution of a normal PulseBlaster is one clock cycle, the minimum interval is typically limited to 5 clock cycles (or nine in the case of the external memory models like the 32k). +The ESR-Pro series of PulseBlasters have the Short Pulse Feature, which allows for pulse lengths of 1-5 clock periods. This is controlled using the top three bits (21-23) according to the following table. + +.. csv-table:: Short Pulse Control + :header: "SpinAPI Define", "Bits 21-23", "Clock Periods", "Pulse Length (ns) at 500 MHz" + :widths: auto + :align: center + + \- , 000, \- , "All outputs low" + "ONE_PERIOD", 001, 1, 2 + "TWO_PERIOD", 010, 2, 4 + "THREE_PERIOD", 011, 3, 6 + "FOUR_PERIOD", 100, 4, 8 + "FIVE_PERIOD", 101, 5, 10 + "ON", 111, \- , "Short Pulse Disabled" + +Currently, the PulseBlaster labscript device does not use this functionality. +However, in order to get any output at all, bits 21-23 must be set high manually. + + +Installation +~~~~~~~~~~~~ + +Use of the Pulseblaster requires driver installation available from the manufacturer `here `_. +The corresponding python wrapper, `spinapi `_ is available via pip. + +.. code-block:: bash + + pip install -U spinapi + +Usage +~~~~~ + +.. code-block:: python + + from labscript import * + + from labscript_devices.PulseBlaster import PulseBlaster + + PulseBlaster(name='pb',board_number=0,programming_scheme='pb_start/BRANCH') + + Clockline(name='pb_clockline_fast', pseudoclock=pb.pseudoclock,connection='flag 0') + Clockline(name='pb_clockline_slow', pseudoclock=pb.pseudoclock,connection='flag 1') + + DigitalOut(name='pb_0',parent_device=pb.direct_outputs,connection='flag 2') + + start() + + stop(1) + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. autosummary:: + labscript_devices.PulseBlaster_No_DDS + labscript_devices.PulseBlasterUSB + labscript_devices.PulseBlaster_SP2_24_100_32k + labscript_devices.PulseBlasterESRPro200 + labscript_devices.PulseBlasterESRPro500 + +PulseBlaster_No_DDS +^^^^^^^^^^^^^^^^^^^ + +.. automodule:: labscript_devices.PulseBlaster_No_DDS + :members: + :undoc-members: + :show-inheritance: + :private-members: + +PulseBlasterUSB +^^^^^^^^^^^^^^^ + +.. automodule:: labscript_devices.PulseBlasterUSB + :members: + :undoc-members: + :show-inheritance: + :private-members: + +PulseBlaster_SP2_24_100_32k +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: labscript_devices.PulseBlaster_SP2_24_100_32k + :members: + :undoc-members: + :show-inheritance: + :private-members: + +PulseBlasterESRPro200 +^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: labscript_devices.PulseBlasterESRPro200 + :members: + :undoc-members: + :show-inheritance: + :private-members: + +PulseBlasterESRPro500 +^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: labscript_devices.PulseBlasterESRPro500 + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/pylon.rst b/docs/source/devices/pylon.rst new file mode 100644 index 00000000..4a157993 --- /dev/null +++ b/docs/source/devices/pylon.rst @@ -0,0 +1,155 @@ +Pylon Cameras +============= + +Overview +~~~~~~~~ + +This device allows control of Basler scientific cameras via the `Pylon API `_ with the `PyPylon python wrapper `_. +In order to use this device, both the Basler Pylon API and the PyPylon wrapper must be installed. + +.. autosummary:: + labscript_devices.PylonCamera.labscript_devices + labscript_devices.PylonCamera.blacs_tabs + labscript_devices.PylonCamera.blacs_workers + +Installation +~~~~~~~~~~~~ + +First ensure that the Basler Pylon SDK is installed. +It is available for free `here `_ (after signing up for a free account with Basler). +It is advisable to use the Pylon Viewer program that comes with the SDK to test communications with the camera. + +The python wrapper is installed via pip: + +.. code-block:: bash + + pip install -U pypylon + +At present, the wrapper is tested and confirmed compatible with Pylon 5 for USB3 and GigE interface cameras. + +For GigE cameras, ensure that the network interface card (NIC) on the computer with the BLACS controlling the camera has enabled Jumbo Frames. +That maximum allowed value (typically 9000) is preferable to avoid dropped frames. + +For USB3 cameras, care should be taken to use a USB3 host that is compatible with the Basler cameras. +Basler maintains a list of compatible host controllers. +The cameras will work on any USB3 port, but non-compatible hosts will not allow for the faster performance. + +Usage +~~~~~ + +Like the :doc:`IMAQdxCamera ` device, the bulk of camera configuration is performed using a dictionary of kwargs, where the key names and values mirror those provided by the Pylon SDK interface. +Which parameters can/need to be set depend on the communication interface. +Discovery of what parameters are available can be done in three ways: + +1. Careful reading of the Pylon SDK docs. +2. Mirroring the Pylon Viewer parameter names and values. +3. Connecting to the camera with a minimal configuration, viewing the current parameters dictionary, and copying the relevant values to the connection table (preferred). + +Below are generic configurations for GigE and USB3 based cameras. + +.. code-block:: python + + from labscript import * + + from labscript_devices.PylonCamera.labscript_devices import PylonCamera + + PylonCamera('gigeCamera',parent_device=parent,connection=conn, + serial_number=1234567, # set to the camera serial number + minimum_recovery_time=20e-3, # the minimum exposure time depends on the camera model & configuration + camera_attributs={ + 'ExposureTimeAbs':1000, #in us + 'ExposureMode':'Timed', + 'ExposureAuto':'Off', + 'GainAuto':'Off', + 'PixelFormat':'Mono12Packed', + 'Gamma':1.0, + 'BlackLevelRaw':0, + 'TriggerSource':'Line 1', + 'TriggerMode':'On' + }, + manual_camera_attributes={ + 'TriggerSource':'Software', + 'TriggerMode':'Off' + }) + + PylonCamera('usb3Camera',parent_device=parent,connection=conn, + serial_number=12345678, + minimum_recovery_time=36e-3, + camera_attributs={ + 'ExposureTime':1000, #in us + 'ExposureMode':'Timed', + 'ExposureAuto':'Off', + 'GainAuto':'Off', + 'PixelFormat':'Mono12Packed', + 'Gamma':1.0, + 'BlackLevel':0, + 'TriggerSource':'Line 1', + 'TriggerMode':'On', + 'ShutterMode':'Global' + }, + manual_camera_attributes={ + 'TriggerSource':'Software', + 'TriggerMode':'Off' + }) + + start() + + gigeCamera.expose(t=0.5,'exposure1') + + usb3Camera.expose(t=0.45,'exposure2') + + stop(1) + +Utilities +~~~~~~~~~ + +The Pylon labscript device includes a script in the `testing` subfolder that can automatically determine the full-frame sensor readout time and maximum possible framerate. +This tool helps in correctly determining the appropriate `minimum_recovery_time` to set for each device. +The minimum recovery time is a function of the model used, the communication bus used, and minor details of the setup (such as host controller firmwares, cable lengths, host computer workload, etc). +As a result, live testing of the device is often needed to accurately determine the actual recovery time needed between shots. + +The script is run from within the testing folder using + +.. code-block:: python + + python ExposureTiming.py [camera_sn] + +with `[camera_sn]` being the serial number of the camera to connect to and test. + +The script reports the minimum recovery time between two shots of 1 ms exposure each, without the use of overlapped exposure mode. +Editing the script to include your typical experiment parameters will help in more accurately determining your minimum recovery time. +Typically, the minimum recovery time should be slightly longer than the reported sensor readout time. + +Note that in overlapped exposure mode, a second exposure is begun before the first exposure has finished reading out and *must* end after the readout of the first exposure frame is complete. +This allows for a series of two exposures with shorter delay between them, at the expense of limitations on the length of the second exposure. +The script will also report the minimum time between the end of one exposure and the beginning of the second (nominally `readout_time - exposure_time`). +Note that this feature is automatically handled at the Pylon API level; this labscript device is not actively aware of it. +As a result, incorrect uses of overlapped mode will not be caught at compile time, but rather during the shot as hardware errors. + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.PylonCamera + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PylonCamera.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PylonCamera.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.PylonCamera.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/rfblaster.rst b/docs/source/devices/rfblaster.rst new file mode 100644 index 00000000..23132177 --- /dev/null +++ b/docs/source/devices/rfblaster.rst @@ -0,0 +1,13 @@ +RFblaster +========= + +Another pseudoclock-cable labscript device. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.RFBlaster + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/spinnaker.rst b/docs/source/devices/spinnaker.rst new file mode 100644 index 00000000..e9452720 --- /dev/null +++ b/docs/source/devices/spinnaker.rst @@ -0,0 +1,111 @@ +Spinnaker Cameras +================= + +This device allows control of FLIR scientific cameras via the `Spinnaker SDK `_ with the PySpin wrapper. +In order to use this device, both the SDK and the python wrapper must be installed. + +.. autosummary:: + labscript_devices.SpinnakerCamera.labscript_devices + labscript_devices.SpinnakerCamera.blacs_tabs + labscript_devices.SpinnakerCamera.blacs_workers + +Installation +~~~~~~~~~~~~ + +First ensure that the Spinnaker SDK is installed. + +The python wrapper is available via FLIR. +It must be installed separately and pointed to the correct conda environment during install. + +For GigE cameras, ensure that the network interface card (NIC) on the computer with the BLACS controlling the camera has enabled Jumbo Frames. +The maximum allowed value (typically 9000) is preferable to avoid dropped frames. + +Usage +~~~~~ + +Like the :doc:`IMAQdxCamera ` device, the bulk of camera configuration is performed using a dictionary of kwargs, where the key names and values mirror those provided by the Spinnaker SDK interface. +Which parameters can/need to be set depend on the communication interface. +Discovery of what parameters are available can be done in three ways: + +1. Careful reading of the Spinnaker SDK docs. +2. Mirroring the SpinView parameter names and values. +3. Connecting to the camera with a minimal configuration, viewing the current parameters dictionary, and copying the relevant values to the connection table (preferred). + +Below is a generic configuration. + +.. code-block:: python + + from labscript import * + + from labscript_devices.SpinnakerCamera.labscript_devices import SpinnakerCamera + + CCT_global_camera_attributes = { + 'AnalogControl::GainAuto': 'Off', + 'AnalogControl::Gain': 0.0, + 'AnalogControl::BlackLevelEnabled': True, + 'AnalogControl::BlackLevel': 0.0, + 'AnalogControl::GammaEnabled': False, + 'AnalogControl::SharpnessEnabled': False, + 'ImageFormatControl::Width': 1008, + 'ImageFormatControl::Height': 800, + 'ImageFormatControl::OffsetX': 200, + 'ImageFormatControl::OffsetY': 224, + 'ImageFormatControl::PixelFormat': 'Mono16', + 'ImageFormatControl::VideoMode': 'Mode0', + 'AcquisitionControl::TriggerMode': 'Off', + 'AcquisitionControl::TriggerSource': 'Line0', + 'AcquisitionControl::TriggerSelector': 'ExposureActive', + 'AcquisitionControl::TriggerActivation': 'FallingEdge', + } + CCT_manual_mode_attributes = { + 'AcquisitionControl::TriggerMode': 'Off', + 'AcquisitionControl::ExposureMode': 'Timed', + } + CCT_buffered_mode_attributes = { + 'AcquisitionControl::TriggerMode': 'On', + 'AcquisitionControl::ExposureMode': 'TriggerWidth', + } + + SpinnakerCamera('gigeCamera',parent_device=parent,connection=conn, + serial_number=1234567, # set to the camera serial number + minimum_recovery_time=36e-6, # the minimum exposure time depends on the camera model & configuration + trigger_edge_type='falling', + camera_attributs={**CCT_global_camera_attributes, + **CCT_buffered_mode_attributes}, + manual_camera_attributes={**CCT_global_camera_attributes, + **CCT_manual_mode_attributes}) + + start() + + gigeCamera.expose(t=0.5,'exposure1',trigger_duration=0.25) + + + stop(1) + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.SpinnakerCamera + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.SpinnakerCamera.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.SpinnakerCamera.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.SpinnakerCamera.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/devices/tekscope.rst b/docs/source/devices/tekscope.rst new file mode 100644 index 00000000..f71cea4f --- /dev/null +++ b/docs/source/devices/tekscope.rst @@ -0,0 +1,49 @@ +Tektronix Oscilloscope +====================== + +A device for controlling Tektronix oscilloscopes using the standard VISA interface. + +.. autosummary:: + labscript_devices.TekScope.labscript_devices + labscript_devices.TekScope.blacs_tabs + labscript_devices.TekScope.blacs_workers + labscript_devices.TekScope.TekScope + +Installation +~~~~~~~~~~~~ + +This wrapper requires PyVISA and a compatible VISA installation. Free versions are +provided by NI and Keysight (NI preferred if already using NI DAQs). + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.TekScope + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.TekScope.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.TekScope.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.TekScope.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.TekScope.TekScope + :members: + :undoc-members: + :show-inheritance: + :private-members: \ No newline at end of file diff --git a/docs/source/devices/testdevice.rst b/docs/source/devices/testdevice.rst new file mode 100644 index 00000000..9a1c1165 --- /dev/null +++ b/docs/source/devices/testdevice.rst @@ -0,0 +1,14 @@ +Test Device +=========== + +A generic test device to aid in testing labscript infrastructure/functionality. + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.test_device + :members: + :undoc-members: + :show-inheritance: + :member-order: bysource + :private-members: \ No newline at end of file diff --git a/docs/source/devices/zaberstagecontroller.rst b/docs/source/devices/zaberstagecontroller.rst new file mode 100644 index 00000000..11e031d4 --- /dev/null +++ b/docs/source/devices/zaberstagecontroller.rst @@ -0,0 +1,44 @@ +Zaber Stage Controller +====================== + +Device for controlling a Zaber translation stage. + +.. autosummary:: + labscript_devices.ZaberStageController.labscript_devices + labscript_devices.ZaberStageController.blacs_tabs + labscript_devices.ZaberStageController.blacs_workers + labscript_devices.ZaberStageController.utils + + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: labscript_devices.ZaberStageController + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.ZaberStageController.labscript_devices + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.ZaberStageController.blacs_tabs + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.ZaberStageController.blacs_workers + :members: + :undoc-members: + :show-inheritance: + :private-members: + +.. automodule:: labscript_devices.ZaberStageController.utils + :members: + :undoc-members: + :show-inheritance: + :private-members: diff --git a/docs/source/ex_conn_tables.rst b/docs/source/ex_conn_tables.rst new file mode 100644 index 00000000..d6004a04 --- /dev/null +++ b/docs/source/ex_conn_tables.rst @@ -0,0 +1,136 @@ +Example Connection Tables +========================= + +An example connection table for the experiment described in [1]_. This connection table makes extensive use of `user_devices`, by name of `naqslab_devices`. + +.. code-block:: python + + from labscript import * + from naqslab_devices.PulseBlasterESRPro300.labscript_device import PulseBlasterESRPro300 + from naqslab_devices.NovaTechDDS.labscript_device import NovaTech409B, NovaTech409B_AC + from labscript_devices.NI_DAQmx.models.NI_USB_6343 import NI_USB_6343 + from naqslab_devices.SignalGenerator.Models import RS_SMA100B, SRS_SG386 + from naqslab_devices import ScopeChannel, StaticFreqAmp + from naqslab_devices.KeysightXSeries.labscript_device import KeysightXScope + #from labscript_devices.PylonCamera.labscript_devices import PylonCamera + from naqslab_devices.KeysightDCSupply.labscript_device import KeysightDCSupply + from naqslab_devices.SR865.labscript_device import SR865 + + PulseBlasterESRPro300(name='pulseblaster_0', board_number=0, programming_scheme='pb_start/BRANCH') + ClockLine(name='pulseblaster_0_clockline_fast', pseudoclock=pulseblaster_0.pseudoclock, connection='flag 0') + ClockLine(name='pulseblaster_0_clockline_slow', pseudoclock=pulseblaster_0.pseudoclock, connection='flag 1') + + NI_USB_6343(name='ni_6343', parent_device=pulseblaster_0_clockline_fast, + clock_terminal='/ni_usb_6343/PFI0', + MAX_name='ni_usb_6343', + acquisition_rate = 243e3, # 500 kS/s max aggregate) + stop_order = -1) #as clocking device, ensure it transitions first + + NovaTech409B(name='novatech_static', com_port="com4", baud_rate = 115200, + phase_mode='aligned',ext_clk=True, clk_freq=100, clk_mult=5) + NovaTech409B_AC(name='novatech', parent_device=pulseblaster_0_clockline_slow, + com_port="com3", update_mode='asynchronous', phase_mode='aligned', + baud_rate = 115200, ext_clk=True, clk_freq=100, clk_mult=5) + + # using NI-MAX alias instead of full VISA name + RS_SMA100B(name='SMA100B', VISA_name='SMA100B') + RS_SMA100B(name='SMA100B2', VISA_name='SMA100B-2') + SRS_SG386(name='SG386', VISA_name='SG386-6181I', output='RF', mod_type='Sweep') + + # call the scope, use NI-MAX alias instead of full name + KeysightXScope(name='Scope',VISA_name='DSOX3024T', + trigger_device=pulseblaster_0.direct_outputs,trigger_connection='flag 3', + num_AI=4,DI=False) + ScopeChannel('Heterodyne',Scope,'Channel 1') + #ScopeChannel('Absorption',Scope,'Channel 2') + #ScopeChannel('Modulation',Scope,'Channel 4') + + # DC Supplies + KeysightDCSupply(name='DCSupply',VISA_name='E3640A', + range='HIGH',volt_limits=(0,20),current_limits=(0,1)) + StaticAnalogOut('DCBias_Gnd',DCSupply,'channel 0') + KeysightDCSupply(name='DCSupply2',VISA_name='E3644A', + range='HIGH',volt_limits=(0,20),current_limits=(0,1)) + StaticAnalogOut('DCBias_Sig',DCSupply2,'channel 0') + + # Lock-In Amplifier + SR865(name='LockIn',VISA_name='SR865') + + # Define Cameras + # note that Basler cameras can overlap frames if + # second exposure does not end before frame transfer of first finishes + + ''' + PylonCamera('CCD_2',parent_device=pulseblaster_0.direct_outputs,connection='flag 6', + serial_number=21646179, + mock=False, + camera_attributes={'ExposureTime':9000, + 'ExposureMode':'Timed', + 'Gain':0.0, + 'ExposureAuto':'Off', + 'GainAuto':'Off', + 'PixelFormat':'Mono12', + 'Gamma':1.0, + 'BlackLevel':0, + 'TriggerSource':'Line1', + 'ShutterMode':'Global', + 'TriggerMode':'On'}, + manual_mode_camera_attributes={'TriggerSource':'Software', + 'TriggerMode':'Off'}) + ''' + # Define the Wait Monitor for the AC-Line Triggering + # note that connections used here cannot be used elsewhere + # 'connection' needs to be physically connected to 'acquisition_connection' + # for M-Series DAQs, ctr0 gate is on PFI9 + WaitMonitor(name='wait_monitor', parent_device=ni_6343, + connection='port0/line0', acquisition_device=ni_6343, + acquisition_connection='ctr0', timeout_device=ni_6343, + timeout_connection='PFI1') + + DigitalOut( 'AC_trigger_arm', pulseblaster_0.direct_outputs, 'flag 2') + + # define the PB digital outputs + DigitalOut( 'probe_AOM_enable', pulseblaster_0.direct_outputs, 'flag 4') + DigitalOut( 'LO_AOM_enable', pulseblaster_0.direct_outputs, 'flag 5') + + # short pulse control channels + DigitalOut( 'bit21', pulseblaster_0.direct_outputs, 'flag 21') + DigitalOut( 'bit22', pulseblaster_0.direct_outputs, 'flag 22') + DigitalOut( 'bit23', pulseblaster_0.direct_outputs, 'flag 23') + + AnalogOut( 'ProbeAmpLock', ni_6343, 'ao0') + AnalogOut( 'LOAmpLock', ni_6343, 'ao1') + AnalogOut( 'blueSweep', ni_6343, 'ao2') + AnalogOut( 'MW_Phase', ni_6343, 'ao3') + + AnalogIn( 'Homodyne', ni_6343, 'ai0') + AnalogIn( 'AI1', ni_6343, 'ai1') + AnalogIn( 'LockInX', ni_6343, 'ai2') + AnalogIn( 'LockInY', ni_6343, 'ai3') + + # this dummy line necessary to balance the digital out for the wait monitor + DigitalOut( 'P0_1', ni_6343, 'port0/line1') + + StaticDDS( 'Probe_EOM', novatech_static, 'channel 0') + StaticDDS( 'Probe_AOM', novatech_static, 'channel 1') + StaticDDS( 'LO_AOM', novatech_static, 'channel 2') + StaticDDS( 'LO', novatech_static, 'channel 3') + + DDS( 'Probe_BN', novatech, 'channel 0') + DDS( 'dds1', novatech, 'channel 1') + StaticDDS( 'SAS_Mod', novatech, 'channel 2') + StaticDDS( 'SAS_LO', novatech, 'channel 3') + + StaticFreqAmp( 'uWaves', SMA100B, 'channel 0', freq_limits=(8e-6,20), amp_limits=(-145,35)) + StaticFreqAmp( 'uWavesLO', SMA100B2, 'channel 0', freq_limits=(8e-6,20), amp_limits=(-145,35)) + StaticFreqAmp( 'blueEOM', SG386, 'channel 0', freq_limits=(1,6.075e3), amp_limits=(-110,16.5)) + + start() + + stop(1) + +References +~~~~~~~~~~ + +.. [1] D. H. Meyer, Z. A. Castillo, K. C. Cox, and P. D. Kunz, J. Phys B, **53** 034001 (2020) + https://iopscience.iop.org/article/10.1088/1361-6455/ab6051 \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index aba2a7a4..c2e62eaa 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,12 +6,19 @@ labscript-devices ================= +This portion of the **labscript-suite** contains the plugin architecture for controlling experimental hardware. +In particular, this code provides the interface between **labscript** high-level instructions and hardware-specific instructions, the communication interface to send those instructions to the hardware, and the **BLACS** instrument control interface. + .. toctree:: :maxdepth: 2 :hidden: :caption: DOCUMENTATION - + introduction + devices + user_devices + ex_conn_tables + adding_devices .. toctree:: :maxdepth: 2 diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 00000000..7bf6d337 --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,25 @@ +Introduction +============ + +The **labscript_devices** module contains the low-level hardware interfacing code that intermediates between the :doc:`labscript ` API (converting **labscript** instructions into hardware instructions) as well as the :doc:`BLACS ` GUI (which communicates directly with the hardware). + +Each "device" is made up of four classes that handle the various tasks. + +* `labscript_device` (derives from :obj:`Device `) + + - Defines the interface between the **labscript** API and generates hardware instructions that can be saved to the shot h5 file. + +* `BLACS_tab` (derives from :obj:`DeviceTab `) + + - Defines the graphical tab that is present in the **BLACS** GUI. This tab provides graphical widgets for controlling hardware outputs and visualizing hardware inputs. + +* `BLACS_worker` (derives from :class:`Worker `) + + - Defines the software control interface to the hardware. The `BLACS_tab` spawns a process that uses this class to send and receive commands with the hardware. + +* `runviewer_parser` + + - Defines a software interface that interprets hardware instructions in a shot h5 file and displays them in the :doc:`runviewer ` GUI. + +The **labscript_suite** provides an extensive :doc:`list of device classes ` for commercially available hardware. +Furthermore, it is simple to add local :doc:`user devices ` to control instruments not already within the labscript-suite. diff --git a/docs/source/pyqt5-modified-objects.inv b/docs/source/pyqt5-modified-objects.inv new file mode 100644 index 00000000..418cb896 Binary files /dev/null and b/docs/source/pyqt5-modified-objects.inv differ diff --git a/docs/source/user_devices.rst b/docs/source/user_devices.rst new file mode 100644 index 00000000..23ca36fc --- /dev/null +++ b/docs/source/user_devices.rst @@ -0,0 +1,35 @@ +User Devices +============ + +Adding custom devices for use in the **labscript-suite** can be done using the `user_devices` mechanism. +This mechanism provides a simple way to add support for a new device without directly interacting with the **labscript-devices** repository. +This is particularly useful when using standard installations of labscript, using code that is proprietary in nature, or code that, while functional, is not mature enough for widespread dissemination. + +This is done by adding the **labscript-device** code into the `userlib/user_devices` folder. Using the custom device in a **labscript** connection table is then done by: + +.. code-block:: python + + from user_devices.MyCustomUserDevice.labscript_devices import MyCustomUserDevice + +This import statement assumes your custom device follows the new device structure organization. + +Note that both the `userlib` path and the `user_devices` folder name can be custom configured in the `labconfig.ini` file. +The `user_devices` folder must be in the `userlib` path. +If a different `user_devices` folder name is used, the import uses that folder name in place of `user_devices` in the above import statement. + +Note that we highly encourage everyone that adds support for new hardware to consider making a pull request to **labscript-devices** so that it may be added to the mainline and more easily used by other groups. + +3rd Party Devices +----------------- + +Below is a list of 3rd party devices developed by users of the **labscript-suite** that can be used via the `user_devices` mechanism described above. +These repositories are not tested or maintained by the **labscript-suite** development team. +As such, there is no guarantee they will work with current or future versions of the **labscript-suite**. +They are also not guaranteed to be free of lab-specific implementation details that may prevent direct use in your apparatus. +They are provided by users to benefit the community in supporting new and/or unusual devices, and can often serve as a good reference when developing your own devices. +Please direct any questions regarding these repositories to their respective owners. + +* `NAQS Lab `__ +* `Vladan Vuletic Group Rb Lab, MIT `__ + +If you would like to add your repository to this list, :doc:`please contact us or make a pull request`. \ No newline at end of file diff --git a/labscript_devices/DummyIntermediateDevice.py b/labscript_devices/DummyIntermediateDevice.py index 768fa015..8ba3f046 100644 --- a/labscript_devices/DummyIntermediateDevice.py +++ b/labscript_devices/DummyIntermediateDevice.py @@ -11,16 +11,43 @@ # # ##################################################################### +""" +Overview +~~~~~~~~ -# This file represents a dummy labscript device for purposes of testing BLACS -# and labscript. The device is a Intermediate Device, and can be attached to -# a pseudoclock in labscript in order to test the pseudoclock behaviour -# without needing a real Intermediate Device. -# -# You can attach an arbitrary number of outputs to this device, however we -# currently only support outputs of type AnalogOut and DigitalOut. I would be -# easy to extend this is anyone needed further functionality. +This file represents a dummy labscript device for purposes of testing BLACS +and labscript. The device is a Intermediate Device, and can be attached to +a pseudoclock in labscript in order to test the pseudoclock behaviour +without needing a real Intermediate Device. +You can attach an arbitrary number of outputs to this device, however we +currently only support outputs of type AnalogOut and DigitalOut. I would be +easy to extend this is anyone needed further functionality. + +Usage +~~~~~ + +.. code-block:: python + + from labscript import * + + from labscript_devices.DummyPseudoclock.labscript_devices import DummyPseudoclock + from labscript_devices.DummyIntermediateDevice import DummyIntermediateDevice + + DummyPseudoclock(name='dummy_clock',BLACS_connection='dummy') + DummyIntermediateDevice(name='dummy_device',BLACS_connection='dummy2', + parent_device=dummy_clock.clockline) + + DigitalOut(name='do1',parent_device=dummy_device,connection='dummy_do1') + DigitalOut(name='do2',parent_device=dummy_device,connection='dummy_do2') + + start() + stop(1) + +Detailed Documentation +~~~~~~~~~~~~~~~~~~~~~~ + +""" from labscript_devices import labscript_device, BLACS_tab, BLACS_worker from labscript import IntermediateDevice, DigitalOut, AnalogOut, config diff --git a/labscript_devices/LightCrafterDMD.py b/labscript_devices/LightCrafterDMD.py index e8e0ca59..3f40ffdb 100644 --- a/labscript_devices/LightCrafterDMD.py +++ b/labscript_devices/LightCrafterDMD.py @@ -61,6 +61,12 @@ class ImageSet(Output): height = HEIGHT # Set default value to be a black image. Here's a raw BMP! default_value = BLANK_BMP + """bytes: A black image. + + Raw bitmap data hidden from docs. + + :meta hide-value: + """ def __init__(self, name, parent_device, connection = 'Mirror'): Output.__init__(self, name, parent_device, connection) diff --git a/labscript_devices/NI_DAQmx/labscript_devices.py b/labscript_devices/NI_DAQmx/labscript_devices.py index 476dc776..dd5107d4 100644 --- a/labscript_devices/NI_DAQmx/labscript_devices.py +++ b/labscript_devices/NI_DAQmx/labscript_devices.py @@ -106,7 +106,52 @@ def __init__( supports_semiperiod_measurement=False, **kwargs ): - """Generic class for NI_DAQmx devices.""" + """Generic class for NI_DAQmx devices. + + Generally over-ridden by device-specific subclasses that contain + the introspected default values. + + Args: + name (str): name to assign to the created labscript device + parent_device (clockline): Parent clockline device that will + clock the outputs of this device + clock_terminal (str): What input on the DAQ is used for the clockline + MAX_name (str): NI-MAX device name + static_AO (int, optional): Number of static analog output channels. + static_DO (int, optional): Number of static digital output channels. + clock_mirror_terminal (str, optional): Channel string of digital output + that mirrors the input clock. Useful for daisy-chaning DAQs on the same + clockline. + acquisiton_rate (float, optional): Default sample rate of inputs. + AI_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog + input voltage range for all analog inputs. + AI_start_delay (float, optional): Time in seconds between start of an + analog input task starting and the first sample. + AO_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog + output voltage range for all analog outputs. + max_AI_multi_chan_rate (float, optional): Max supported analog input + sampling rate when using multiple channels. + max_AI_single_chan_rate (float, optional): Max supported analog input + sampling rate when only using a single channel. + max_AO_sample_rate (float, optional): Max supported analog output + sample rate. + max_DO_sample_rate (float, optional): Max supported digital output + sample rate. + min_sermiperiod_measurement (float, optional): Minimum measurable time + for a semiperiod measurement. + num_AI (int, optional): Number of analog inputs channels. + num_AO (int, optional): Number of analog output channels. + num_CI (int, optional): Number of counter input channels. + ports (dict, optional): Dictionarly of DIO ports, which number of lines + and whether port supports buffered output. + supports_buffered_AO (bool, optional): True if analog outputs support + buffered output + supports_buffered_DO (bool, optional): True if digital outputs support + buffered output + supports_semiperiod_measurement (bool, optional): True if deviec supports + semi-period measurements + + """ # Default static output setting based on whether the device supports buffered # output: @@ -169,8 +214,8 @@ def __init__( self.wait_monitor_minimum_pulse_width = self.min_semiperiod_measurement - # Set allowed children based on capabilities: self.allowed_children = [] + '''Sets the allowed children types based on the capabilites.''' if self.num_AI > 0: self.allowed_children += [AnalogIn] if self.num_AO > 0 and static_AO: @@ -198,7 +243,13 @@ def __init__( IntermediateDevice.__init__(self, name, parent_device, **kwargs) def add_device(self, device): - """Error checking for adding a child device""" + """Error checking for adding a child device. + + Args: + device (labscript device): Child labscript device to + attach to this device. Only types of devices in :obj:`allowed_children` + can be attached. + """ # Verify static/dynamic outputs compatible with configuration: if isinstance(device, StaticAnalogOut) and not self.static_AO: msg = """Cannot add StaticAnalogOut to NI_DAQmx device configured for @@ -293,6 +344,7 @@ def _check_bounds(self, analogs): np.clip(output.raw_output, vmin, vmax, out=output.raw_output) def _check_AI_not_too_fast(self, AI_table): + """Check that analog input acquisition rates do not exceed maximums.""" if AI_table is None: return n = len(set(AI_table['connection'])) @@ -442,6 +494,14 @@ def _check_wait_monitor_timeout_device_config(self): raise RuntimeError(dedent(msg)) def generate_code(self, hdf5_file): + """Generates the hardware code from the script and saves it to the + shot h5 file. + + This is called automatically when a shot is compiled. + + Args: + hdf5_file (str): Path to shot's hdf5 file to save the instructions to. + """ IntermediateDevice.generate_code(self, hdf5_file) analogs = {} digitals = {} diff --git a/labscript_devices/NI_DAQmx/models/NI_PCI_6251.py b/labscript_devices/NI_DAQmx/models/NI_PCI_6251.py index 54f2b139..e7587717 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCI_6251.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCI_6251.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': [-10.0, 10.0], 'AI_start_delay': 2.5e-07, @@ -49,6 +50,7 @@ class NI_PCI_6251(NI_DAQmx): description = 'NI-PCI-6251' def __init__(self, *args, **kwargs): + """Class for NI-PCI-6251""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PCI_6534.py b/labscript_devices/NI_DAQmx/models/NI_PCI_6534.py index 2cc9bd43..c0b7f9f0 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCI_6534.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCI_6534.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -52,6 +53,7 @@ class NI_PCI_6534(NI_DAQmx): description = 'NI-PCI-6534' def __init__(self, *args, **kwargs): + """Class for NI-PCI-6534""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PCI_6713.py b/labscript_devices/NI_DAQmx/models/NI_PCI_6713.py index 4e6341e6..99361845 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCI_6713.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCI_6713.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -45,6 +46,7 @@ class NI_PCI_6713(NI_DAQmx): description = 'NI-PCI-6713' def __init__(self, *args, **kwargs): + """Class for NI-PCI-6713""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PCI_6733.py b/labscript_devices/NI_DAQmx/models/NI_PCI_6733.py index c373fe67..234e3f09 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCI_6733.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCI_6733.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -45,6 +46,7 @@ class NI_PCI_6733(NI_DAQmx): description = 'NI-PCI-6733' def __init__(self, *args, **kwargs): + """Class for NI-PCI-6733""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PCI_DIO_32HS.py b/labscript_devices/NI_DAQmx/models/NI_PCI_DIO_32HS.py index 47b72ac0..6f297f36 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCI_DIO_32HS.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCI_DIO_32HS.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -52,6 +53,7 @@ class NI_PCI_DIO_32HS(NI_DAQmx): description = 'NI-PCI-DIO-32HS' def __init__(self, *args, **kwargs): + """Class for NI-PCI-DIO-32HS""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PCIe_6363.py b/labscript_devices/NI_DAQmx/models/NI_PCIe_6363.py index 7dc4711c..8155ce02 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCIe_6363.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCIe_6363.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': [-10.0, 10.0], 'AI_start_delay': 7e-08, @@ -49,6 +50,7 @@ class NI_PCIe_6363(NI_DAQmx): description = 'NI-PCIe-6363' def __init__(self, *args, **kwargs): + """Class for NI-PCIe-6363""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PCIe_6738.py b/labscript_devices/NI_DAQmx/models/NI_PCIe_6738.py index f0176ea2..fb592d21 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PCIe_6738.py +++ b/labscript_devices/NI_DAQmx/models/NI_PCIe_6738.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -48,6 +49,7 @@ class NI_PCIe_6738(NI_DAQmx): description = 'NI-PCIe-6738' def __init__(self, *args, **kwargs): + """Class for NI-PCIe-6738""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PXI_6733.py b/labscript_devices/NI_DAQmx/models/NI_PXI_6733.py index 1f3181c3..74ff7215 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PXI_6733.py +++ b/labscript_devices/NI_DAQmx/models/NI_PXI_6733.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -45,6 +46,7 @@ class NI_PXI_6733(NI_DAQmx): description = 'NI-PXI-6733' def __init__(self, *args, **kwargs): + """Class for NI-PXI-6733""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PXIe_6361.py b/labscript_devices/NI_DAQmx/models/NI_PXIe_6361.py index 7b103032..7b88180a 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PXIe_6361.py +++ b/labscript_devices/NI_DAQmx/models/NI_PXIe_6361.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': [-10.0, 10.0], 'AI_start_delay': 7e-08, @@ -49,6 +50,7 @@ class NI_PXIe_6361(NI_DAQmx): description = 'NI-PXIe-6361' def __init__(self, *args, **kwargs): + """Class for NI-PXIe-6361""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PXIe_6535.py b/labscript_devices/NI_DAQmx/models/NI_PXIe_6535.py index d6c83d0f..a71a4322 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PXIe_6535.py +++ b/labscript_devices/NI_DAQmx/models/NI_PXIe_6535.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -51,6 +52,7 @@ class NI_PXIe_6535(NI_DAQmx): description = 'NI-PXIe-6535' def __init__(self, *args, **kwargs): + """Class for NI-PXIe-6535""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_PXIe_6738.py b/labscript_devices/NI_DAQmx/models/NI_PXIe_6738.py index 0d6ae352..10b93ec5 100644 --- a/labscript_devices/NI_DAQmx/models/NI_PXIe_6738.py +++ b/labscript_devices/NI_DAQmx/models/NI_PXIe_6738.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': None, 'AI_start_delay': None, @@ -48,6 +49,7 @@ class NI_PXIe_6738(NI_DAQmx): description = 'NI-PXIe-6738' def __init__(self, *args, **kwargs): + """Class for NI-PXIe-6738""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_USB_6008.py b/labscript_devices/NI_DAQmx/models/NI_USB_6008.py index dae0bd4a..4a0b4714 100644 --- a/labscript_devices/NI_DAQmx/models/NI_USB_6008.py +++ b/labscript_devices/NI_DAQmx/models/NI_USB_6008.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': [-10.0, 10.0], 'AI_start_delay': 8.333333333333334e-08, @@ -48,6 +49,7 @@ class NI_USB_6008(NI_DAQmx): description = 'NI-USB-6008' def __init__(self, *args, **kwargs): + """Class for NI-USB-6008""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_USB_6229.py b/labscript_devices/NI_DAQmx/models/NI_USB_6229.py index 6c758e56..b17bf1b5 100644 --- a/labscript_devices/NI_DAQmx/models/NI_USB_6229.py +++ b/labscript_devices/NI_DAQmx/models/NI_USB_6229.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': [-10.0, 10.0], 'AI_start_delay': 2.5e-07, @@ -49,6 +50,7 @@ class NI_USB_6229(NI_DAQmx): description = 'NI-USB-6229' def __init__(self, *args, **kwargs): + """Class for NI-USB-6229""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/NI_USB_6343.py b/labscript_devices/NI_DAQmx/models/NI_USB_6343.py index 3c00aa6b..1d38e471 100644 --- a/labscript_devices/NI_DAQmx/models/NI_USB_6343.py +++ b/labscript_devices/NI_DAQmx/models/NI_USB_6343.py @@ -22,6 +22,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = { 'AI_range': [-10.0, 10.0], 'AI_start_delay': 7e-08, @@ -49,6 +50,7 @@ class NI_USB_6343(NI_DAQmx): description = 'NI-USB-6343' def __init__(self, *args, **kwargs): + """Class for NI-USB-6343""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/_subclass_template.py b/labscript_devices/NI_DAQmx/models/_subclass_template.py index 2edace73..dae85c7a 100644 --- a/labscript_devices/NI_DAQmx/models/_subclass_template.py +++ b/labscript_devices/NI_DAQmx/models/_subclass_template.py @@ -15,6 +15,7 @@ from labscript_devices.NI_DAQmx.labscript_devices import NI_DAQmx +#: CAPABILITIES = ${CAPABILITIES} @@ -22,6 +23,7 @@ class ${CLASS_NAME}(NI_DAQmx): description = '${MODEL_NAME}' def __init__(self, *args, **kwargs): + """Class for ${MODEL_NAME}""" # Any provided kwargs take precedent over capabilities combined_kwargs = CAPABILITIES.copy() combined_kwargs.update(kwargs) diff --git a/labscript_devices/NI_DAQmx/models/generate_subclasses.py b/labscript_devices/NI_DAQmx/models/generate_subclasses.py index c062def9..0a95ab9b 100644 --- a/labscript_devices/NI_DAQmx/models/generate_subclasses.py +++ b/labscript_devices/NI_DAQmx/models/generate_subclasses.py @@ -10,6 +10,16 @@ # file in the root of the project for the full license. # # # ##################################################################### +"""Reads the capabilities file and generates labscript devices +for each known model of DAQ. + +Called from the command line via + +.. code-block:: shell + + python generate_subclasses.py + +""" import os import warnings import json @@ -23,7 +33,12 @@ def reformat_files(filepaths): - """Apply black formatter to a list of source files""" + """Apply `black `_ + formatter to a list of source files. + + Args: + filepaths (list): List of python source files to format. + """ try: import black except ImportError: @@ -42,6 +57,11 @@ def reformat_files(filepaths): def main(): + """Called when the script is run. + + Will attempt to reformat the generated files using + :func:`reformat_files`. + """ capabilities = {} if os.path.exists(CAPABILITIES_FILE): with open(CAPABILITIES_FILE) as f: diff --git a/labscript_devices/NI_DAQmx/models/get_capabilities.py b/labscript_devices/NI_DAQmx/models/get_capabilities.py index 471491b0..acda3512 100644 --- a/labscript_devices/NI_DAQmx/models/get_capabilities.py +++ b/labscript_devices/NI_DAQmx/models/get_capabilities.py @@ -10,6 +10,20 @@ # file in the root of the project for the full license. # # # ##################################################################### +"""This is a script to update `model_capabilities.json` with the capabilities of all +NI-DAQmx devices currently connected to this computer. + +Run this script to add support for a new model of NI-DAQmx device. +Note that this will work with a simulated device configured through NI-MAX as well, +so support can be added without actually having the physical device. + +Called from the command line via + +.. code-block:: shell + + python get_capabilities.py + +""" import numpy as np import os @@ -24,14 +38,15 @@ CAPABILITIES_FILE = os.path.join(THIS_FOLDER, 'capabilities.json') -"""This is a script to update model_capabilities.json with the capabilities of all -NI-DAQmx devices currently connected to this computer. Run this script to add support -for a new model of NI-DAQmx device. Note that this will work with a simulated device -configured through NI-MAX as well, so support can be added without actually having the -physical device""" +def string_prop(func): + """String property wrapper. + Args: + func (function): PyDAQmx library function that returns a string. -def string_prop(func): + Returns: + function: The wrapped function. + """ def wrapped(name=None): BUFSIZE = 4096 result = ctypes.create_string_buffer(BUFSIZE) @@ -45,6 +60,14 @@ def wrapped(name=None): def bool_prop(func): + """Bool property wrapper. + + Args: + func (function): PyDAQmx library function that returns a boolean. + + Returns: + function: The wrapped function. + """ def wrapped(name): result = bool32() func(name, byref(result)) @@ -54,6 +77,14 @@ def wrapped(name): def int32_prop(func): + """Int32 property wrapper. + + Args: + func (function): PyDAQmx library function that returns a int32. + + Returns: + function: The wrapped function. + """ def wrapped(name): result = int32() func(name, byref(result)) @@ -63,6 +94,14 @@ def wrapped(name): def float64_prop(func): + """Float property wrapper. + + Args: + func (function): PyDAQmx library function that returns a float64. + + Returns: + function: The wrapped function. + """ def wrapped(name): result = float64() func(name, byref(result)) @@ -72,6 +111,15 @@ def wrapped(name): def float64_array_prop(func): + """Array of floats property wrapper. + + Args: + func (function): PyDAQmx library function that returns an array of + float64s. + + Returns: + function: The wrapped function. + """ def wrapped(name): import warnings @@ -91,8 +139,15 @@ def wrapped(name): def chans(func): - """string_prop but splitting the return value into separate channels and stripping - the device name from them""" + """string_prop but splitting the return value into separate channels + and stripping the device name from them + + Args: + func (function): PyDAQmx library function that returns channel string. + + Returns: + function: The wrapped function. + """ wrapped1 = string_prop(func) def wrapped2(name): @@ -126,6 +181,16 @@ def wrapped2(name): def port_supports_buffered(device_name, port, clock_terminal=None): + """Empirically determines if the digital port supports buffered output. + + Args: + device_name (str): NI-MAX device name + port (int): Which port to intro-spect + clock_terminal (str, optional): String that specifies the clock terminal. + + Returns: + bool: True if `port` supports buffered output. + """ all_terminals = DAQmxGetDevTerminals(device_name) if clock_terminal is None: clock_terminal = all_terminals[0] @@ -170,6 +235,15 @@ def port_supports_buffered(device_name, port, clock_terminal=None): def AI_start_delay(device_name): + """Empirically determines the analog inputs' start delay. + + Args: + device_name (str): NI-MAX device name + + Returns: + float: Analog input start delay in seconds. `None` if + analog inputs not supported. + """ if 'PFI0' not in DAQmxGetDevTerminals(device_name): return None task = Task() @@ -202,9 +276,19 @@ def AI_start_delay(device_name): def supported_AI_ranges_for_non_differential_input(device_name, AI_ranges): - """Try AI ranges to see which are actually allowed for non-differential input, since + """Empirically determine the analog input voltage ranges for non-differential inputs. + + Tries AI ranges to see which are actually allowed for non-differential input, since the largest range may only be available for differential input, which we don't - attempt to support (though we could with a little effort)""" + attempt to support (though we could with a little effort). + + Args: + device_name (str): NI-MAX device name + AI_ranges (list): list of `[Vmin, Vmax]` pairs to check compatibility. + + Returns: + list: List of lists with the supported voltage ranges. + """ chan = device_name + '/ai0' supported_ranges = [] for Vmin, Vmax in AI_ranges: @@ -227,6 +311,14 @@ def supported_AI_ranges_for_non_differential_input(device_name, AI_ranges): def supports_semiperiod_measurement(device_name): + """Empirically determines if the DAQ supports semiperiod measurement. + + Args: + device_name (str): NI-MAX device name. + + Returns: + bool: True if semi-period measurements are supported by the device. + """ import warnings with warnings.catch_warnings(): @@ -242,7 +334,9 @@ def supports_semiperiod_measurement(device_name): def get_min_semiperiod_measurement(device_name): - """Depending on the timebase used, counter inputs can measure time intervals of + """Determines the minimum semi-period measurement time supported by the device. + + Depending on the timebase used, counter inputs can measure time intervals of various ranges. As a default, we pick a largish range - the one with the fastest timebase still capable of measuring 100 seconds, or the largest time interval if it is less than 100 seconds, and we save the smallest interval measurable with this @@ -258,7 +352,14 @@ def get_min_semiperiod_measurement(device_name): possibility of timing out. For now (in the wait monitor worker class) we pessimistically add one second to the expected longest measurement to account for software delays. These decisions can be revisited if there is a need, do not - hesitate to file an issue on bitbucket regarding this if it affects you.""" + hesitate to file an issue on bitbucket regarding this if it affects you. + + Args: + device_name (str): NI-MAX device name + + Returns: + float: Minimum measurement time. + """ CI_chans = DAQmxGetDevCIPhysicalChans(device_name) CI_chan = device_name + '/' + CI_chans[0] # Make a task with a semiperiod measurement @@ -290,124 +391,124 @@ def get_min_semiperiod_measurement(device_name): return dtmin return dtmin - -capabilities = {} -if os.path.exists(CAPABILITIES_FILE): - with open(CAPABILITIES_FILE) as f: +if __name__ == '__main__': + capabilities = {} + if os.path.exists(CAPABILITIES_FILE): + with open(CAPABILITIES_FILE) as f: + try: + capabilities = json.load(f) + except ValueError: + pass + + + models = [] + for name in DAQmxGetSysDevNames().split(', '): + model = DAQmxGetDevProductType(name) + print("found device:", name, model) + if model not in models: + models.append(model) + capabilities[model] = {} try: - capabilities = json.load(f) - except ValueError: - pass - - -models = [] -for name in DAQmxGetSysDevNames().split(', '): - model = DAQmxGetDevProductType(name) - print("found device:", name, model) - if model not in models: - models.append(model) - capabilities[model] = {} - try: - capabilities[model]["supports_buffered_AO"] = DAQmxGetDevAOSampClkSupported( - name - ) - except PyDAQmx.DAQmxFunctions.AttrNotSupportedError: - capabilities[model]["supports_buffered_AO"] = False - try: - capabilities[model]["max_DO_sample_rate"] = DAQmxGetDevDOMaxRate(name) - capabilities[model]["supports_buffered_DO"] = True - except PyDAQmx.DAQmxFunctions.AttrNotSupportedError: - capabilities[model]["max_DO_sample_rate"] = None - capabilities[model]["supports_buffered_DO"] = False - if capabilities[model]["supports_buffered_AO"]: - capabilities[model]["max_AO_sample_rate"] = DAQmxGetDevAOMaxRate(name) - else: - capabilities[model]["max_AO_sample_rate"] = None - - capabilities[model]["num_AO"] = len(DAQmxGetDevAOPhysicalChans(name)) - capabilities[model]["num_AI"] = len(DAQmxGetDevAIPhysicalChans(name)) - if capabilities[model]["num_AI"] > 0: - single_rate = DAQmxGetDevAIMaxSingleChanRate(name) - multi_rate = DAQmxGetDevAIMaxMultiChanRate(name) - else: - single_rate = None - multi_rate = None - capabilities[model]["max_AI_single_chan_rate"] = single_rate - capabilities[model]["max_AI_multi_chan_rate"] = multi_rate - - capabilities[model]["ports"] = {} - ports = DAQmxGetDevDOPorts(name) - chans = DAQmxGetDevDOLines(name) - for port in ports: - if '_' in port: - # Ignore the alternate port names such as 'port0_32' that allow using two or - # more ports together as a single, larger one: - continue - port_info = {} - capabilities[model]["ports"][port] = port_info - port_chans = [chan for chan in chans if chan.split('/')[0] == port] - port_info['num_lines'] = len(port_chans) - if capabilities[model]["supports_buffered_DO"]: - port_info['supports_buffered'] = port_supports_buffered(name, port) + capabilities[model]["supports_buffered_AO"] = DAQmxGetDevAOSampClkSupported( + name + ) + except PyDAQmx.DAQmxFunctions.AttrNotSupportedError: + capabilities[model]["supports_buffered_AO"] = False + try: + capabilities[model]["max_DO_sample_rate"] = DAQmxGetDevDOMaxRate(name) + capabilities[model]["supports_buffered_DO"] = True + except PyDAQmx.DAQmxFunctions.AttrNotSupportedError: + capabilities[model]["max_DO_sample_rate"] = None + capabilities[model]["supports_buffered_DO"] = False + if capabilities[model]["supports_buffered_AO"]: + capabilities[model]["max_AO_sample_rate"] = DAQmxGetDevAOMaxRate(name) else: - port_info['supports_buffered'] = False + capabilities[model]["max_AO_sample_rate"] = None - capabilities[model]["num_CI"] = len(DAQmxGetDevCIPhysicalChans(name)) - supports_semiperiod = supports_semiperiod_measurement(name) - capabilities[model]["supports_semiperiod_measurement"] = supports_semiperiod - if capabilities[model]["num_CI"] > 0 and supports_semiperiod: - min_semiperiod_measurement = get_min_semiperiod_measurement(name) - else: - min_semiperiod_measurement = None - capabilities[model]["min_semiperiod_measurement"] = min_semiperiod_measurement - - if capabilities[model]['num_AO'] > 0: - AO_ranges = [] - raw_limits = DAQmxGetDevAOVoltageRngs(name) - for i in range(0, len(raw_limits), 2): - Vmin, Vmax = raw_limits[i], raw_limits[i + 1] - AO_ranges.append([Vmin, Vmax]) - # Find range with the largest maximum voltage and use that: - Vmin, Vmax = max(AO_ranges, key=lambda range: range[1]) - # Confirm that no other range has a voltage lower than Vmin, - # since if it does, this violates our assumptions and things might not - # be as simple as having a single range: - assert min(AO_ranges)[0] >= Vmin - capabilities[model]["AO_range"] = [Vmin, Vmax] - else: - capabilities[model]["AO_range"] = None - - if capabilities[model]['num_AI'] > 0: - AI_ranges = [] - raw_limits = DAQmxGetDevAIVoltageRngs(name) - for i in range(0, len(raw_limits), 2): - Vmin, Vmax = raw_limits[i], raw_limits[i + 1] - AI_ranges.append([Vmin, Vmax]) - # Restrict to the ranges allowed for non-differential input: - AI_ranges = supported_AI_ranges_for_non_differential_input(name, AI_ranges) - # Find range with the largest maximum voltage and use that: - Vmin, Vmax = max(AI_ranges, key=lambda range: range[1]) - # Confirm that no other range has a voltage lower than Vmin, - # since if it does, this violates our assumptions and things might not - # be as simple as having a single range: - assert min(AI_ranges)[0] >= Vmin - capabilities[model]["AI_range"] = [Vmin, Vmax] - else: - capabilities[model]["AI_range"] = None + capabilities[model]["num_AO"] = len(DAQmxGetDevAOPhysicalChans(name)) + capabilities[model]["num_AI"] = len(DAQmxGetDevAIPhysicalChans(name)) + if capabilities[model]["num_AI"] > 0: + single_rate = DAQmxGetDevAIMaxSingleChanRate(name) + multi_rate = DAQmxGetDevAIMaxMultiChanRate(name) + else: + single_rate = None + multi_rate = None + capabilities[model]["max_AI_single_chan_rate"] = single_rate + capabilities[model]["max_AI_multi_chan_rate"] = multi_rate + + capabilities[model]["ports"] = {} + ports = DAQmxGetDevDOPorts(name) + chans = DAQmxGetDevDOLines(name) + for port in ports: + if '_' in port: + # Ignore the alternate port names such as 'port0_32' that allow using two or + # more ports together as a single, larger one: + continue + port_info = {} + capabilities[model]["ports"][port] = port_info + port_chans = [chan for chan in chans if chan.split('/')[0] == port] + port_info['num_lines'] = len(port_chans) + if capabilities[model]["supports_buffered_DO"]: + port_info['supports_buffered'] = port_supports_buffered(name, port) + else: + port_info['supports_buffered'] = False + + capabilities[model]["num_CI"] = len(DAQmxGetDevCIPhysicalChans(name)) + supports_semiperiod = supports_semiperiod_measurement(name) + capabilities[model]["supports_semiperiod_measurement"] = supports_semiperiod + if capabilities[model]["num_CI"] > 0 and supports_semiperiod: + min_semiperiod_measurement = get_min_semiperiod_measurement(name) + else: + min_semiperiod_measurement = None + capabilities[model]["min_semiperiod_measurement"] = min_semiperiod_measurement + + if capabilities[model]['num_AO'] > 0: + AO_ranges = [] + raw_limits = DAQmxGetDevAOVoltageRngs(name) + for i in range(0, len(raw_limits), 2): + Vmin, Vmax = raw_limits[i], raw_limits[i + 1] + AO_ranges.append([Vmin, Vmax]) + # Find range with the largest maximum voltage and use that: + Vmin, Vmax = max(AO_ranges, key=lambda range: range[1]) + # Confirm that no other range has a voltage lower than Vmin, + # since if it does, this violates our assumptions and things might not + # be as simple as having a single range: + assert min(AO_ranges)[0] >= Vmin + capabilities[model]["AO_range"] = [Vmin, Vmax] + else: + capabilities[model]["AO_range"] = None + + if capabilities[model]['num_AI'] > 0: + AI_ranges = [] + raw_limits = DAQmxGetDevAIVoltageRngs(name) + for i in range(0, len(raw_limits), 2): + Vmin, Vmax = raw_limits[i], raw_limits[i + 1] + AI_ranges.append([Vmin, Vmax]) + # Restrict to the ranges allowed for non-differential input: + AI_ranges = supported_AI_ranges_for_non_differential_input(name, AI_ranges) + # Find range with the largest maximum voltage and use that: + Vmin, Vmax = max(AI_ranges, key=lambda range: range[1]) + # Confirm that no other range has a voltage lower than Vmin, + # since if it does, this violates our assumptions and things might not + # be as simple as having a single range: + assert min(AI_ranges)[0] >= Vmin + capabilities[model]["AI_range"] = [Vmin, Vmax] + else: + capabilities[model]["AI_range"] = None - if capabilities[model]["num_AI"] > 0: - capabilities[model]["AI_start_delay"] = AI_start_delay(name) - else: - capabilities[model]["AI_start_delay"] = None + if capabilities[model]["num_AI"] > 0: + capabilities[model]["AI_start_delay"] = AI_start_delay(name) + else: + capabilities[model]["AI_start_delay"] = None -with open(CAPABILITIES_FILE, 'w', newline='\n') as f: - data = json.dumps(capabilities, sort_keys=True, indent=4) - f.write(data) + with open(CAPABILITIES_FILE, 'w', newline='\n') as f: + data = json.dumps(capabilities, sort_keys=True, indent=4) + f.write(data) -print("added/updated capabilities for %d models" % len(models)) -print("Total models with known capabilities: %d" % len(capabilities)) -for model in capabilities: - if model not in models: - print(model, 'capabilities not updated') -print("run generate_subclasses.py to make labscript devices for these models") + print("added/updated capabilities for %d models" % len(models)) + print("Total models with known capabilities: %d" % len(capabilities)) + for model in capabilities: + if model not in models: + print(model, 'capabilities not updated') + print("run generate_subclasses.py to make labscript devices for these models") diff --git a/labscript_devices/NovaTechDDS9M.py b/labscript_devices/NovaTechDDS9M.py index 956bab07..42539ccf 100644 --- a/labscript_devices/NovaTechDDS9M.py +++ b/labscript_devices/NovaTechDDS9M.py @@ -620,8 +620,8 @@ def get_traces(self, add_trace, clock=None): if 'TABLE_DATA' in hdf5_file['devices/%s' % self.name]: table_data = hdf5_file['devices/%s/TABLE_DATA' % self.name][:] connection_table_properties = labscript_utils.properties.get(hdf5_file, self.name, 'connection_table_properties') - update_mode = getattr(connection_table_properties, 'update_mode', 'synchronous') - synchronous_first_line_repeat = getattr(connection_table_properties, 'synchronous_first_line_repeat', False) + update_mode = connection_table_properties.get('update_mode', 'synchronous') + synchronous_first_line_repeat = connection_table_properties.get('synchronous_first_line_repeat', False) if update_mode == 'asynchronous' or synchronous_first_line_repeat: table_data = table_data[1:] for i in range(2): diff --git a/labscript_devices/PrawnBlaster/blacs_tabs.py b/labscript_devices/PrawnBlaster/blacs_tabs.py index 25ab73d0..9cc6fd57 100644 --- a/labscript_devices/PrawnBlaster/blacs_tabs.py +++ b/labscript_devices/PrawnBlaster/blacs_tabs.py @@ -24,7 +24,14 @@ class PrawnBlasterTab(DeviceTab): + """BLACS Tab for the PrawnBlaster Device.""" + def initialise_GUI(self): + """Initialises the Tab GUI. + + This method is called automatically by BLACS. + """ + self.connection_table_properties = ( self.settings["connection_table"].find_by_name(self.device_name).properties ) @@ -53,6 +60,16 @@ def initialise_GUI(self): self.statemachine_timeout_add(2000, self.status_monitor) def get_child_from_connection_table(self, parent_device_name, port): + """Finds the attached ClockLines. + + Args: + parent_device_name (str): name of parent_device + port (str): port of parent_device + + Returns: + :class:`~labscript.ClockLine`: PrawnBlaster interal Clocklines + """ + # Pass down channel name search to the pseudoclocks (so we can find the # clocklines) if parent_device_name == self.device_name: @@ -69,6 +86,11 @@ def get_child_from_connection_table(self, parent_device_name, port): return None def initialise_workers(self): + """Initialises the PrawnBlaster Workers. + + This method is called automatically by BLACS. + """ + # Find the COM port to be used com_port = str( self.settings["connection_table"] @@ -97,9 +119,18 @@ def initialise_workers(self): True, ) def status_monitor(self, notify_queue=None): - # When called with a queue, this function writes to the queue - # when the pulseblaster is waiting. This indicates the end of - # an experimental run. + """Gets the status of the PrawnBlaster from the worker. + + When called with a queue, this function writes to the queue + when the PrawnBlaster is waiting. This indicates the end of + an experimental run. + + Args: + notify_queue (:class:`~queue.Queue`): Queue to notify when + the experiment is done. + + """ + status, clock_status, waits_pending = yield ( self.queue_work(self.primary_worker, "check_status") ) @@ -121,6 +152,8 @@ def status_monitor(self, notify_queue=None): @define_state(MODE_BUFFERED, True) def start_run(self, notify_queue): + """When used as the primary Pseudoclock, this starts the run.""" + self.statemachine_timeout_remove(self.status_monitor) yield (self.queue_work(self.primary_worker, "start_run")) self.status_monitor() diff --git a/labscript_devices/PrawnBlaster/blacs_workers.py b/labscript_devices/PrawnBlaster/blacs_workers.py index 6c0bc957..08e5b47d 100644 --- a/labscript_devices/PrawnBlaster/blacs_workers.py +++ b/labscript_devices/PrawnBlaster/blacs_workers.py @@ -19,7 +19,19 @@ class PrawnBlasterWorker(Worker): + """The primary worker for the PrawnBlaster. + + This worker handles configuration and communication + with the hardware. + """ + def init(self): + """Initialises the hardware communication. + + This function is automatically called by BLACS + and configures hardware communication with the device. + """ + # fmt: off global h5py; import labscript_utils.h5_lock, h5py global serial; import serial @@ -58,6 +70,34 @@ def init(self): assert self.prawnblaster.readline().decode() == "ok\r\n" def check_status(self): + """Checks the operational status of the PrawnBlaster. + + This is automatically called by BLACS to update the status + of the PrawnBlaster. It also reads the lengths of any + accumulated waits during a shot. + + Returns: + (int, int, bool): Tuple containing: + + - **run_status** (int): Possible values are: + + * 0 : manual-mode + * 1 : transitioning to buffered execution + * 2 : buffered execution + * 3 : abort requested + * 4 : currently aborting buffered execution + * 5 : last buffered execution aborted + * 6 : transitioning to manual mode + + - **clock_status** (int): Possible values are: + + * 0 : internal clock + * 1 : external clock + + - **waits_pending** (bool): Indicates if all expected waits have + not been read out yet. + """ + if ( self.started and self.wait_table is not None @@ -130,6 +170,15 @@ def check_status(self): return run_status, clock_status, waits_pending def read_status(self): + """Reads the status of the PrawnBlaster. + + Returns: + (int, int): Tuple containing + + - **run-status** (int): Run status code + - **clock-status** (int): Clock status code + """ + self.prawnblaster.write(b"status\r\n") response = self.prawnblaster.readline().decode() match = re.match(r"run-status:(\d) clock-status:(\d)(\r\n)?", response) @@ -145,6 +194,16 @@ def read_status(self): ) def program_manual(self, values): + """Manually sets the state of output pins for the pseudoclocks. + + Args: + values (dict): Dictionary of pseudoclock: value pairs to set. + + Returns: + dict: `values` from arguments on successful programming + reflecting current output state. + """ + for channel, value in values.items(): pin = int(channel.split()[1]) pseudoclock = self.out_pins.index(pin) @@ -158,6 +217,19 @@ def program_manual(self, values): return values def transition_to_buffered(self, device_name, h5file, initial_values, fresh): + """Configures the PrawnBlaster for buffered execution. + + Args: + device_name (str): labscript name of PrawnBlaster + h5file (str): path to shot file to be run + initial_values (dict): Dictionary of output states at start of shot + fresh (bool): When `True`, clear the local :py:attr:`smart_cache`, forcing + a complete reprogramming of the output table. + + Returns: + dict: Dictionary of the expected final output states. + """ + if fresh: self.smart_cache = {} @@ -253,6 +325,9 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh): return final def start_run(self): + """When used as the primary pseudoclock, starts execution + in software time to engage the shot.""" + # Start in software: self.logger.info("sending start") self.prawnblaster.write(b"start\r\n") @@ -263,6 +338,9 @@ def start_run(self): self.started = True def wait_for_trigger(self): + """When used as a secondary pseudoclock, sets the PrawnBlaster + to wait for an initial hardware trigger to begin execution.""" + # Set to wait for trigger: self.logger.info("sending hwstart") self.prawnblaster.write(b"hwstart\r\n") @@ -287,6 +365,13 @@ def wait_for_trigger(self): self.started = True def transition_to_manual(self): + """Transition the PrawnBlaster back to manual mode from buffered execution at + the end of a shot. + + Returns: + bool: `True` if transition to manual is successful. + """ + if self.wait_table is not None: with h5py.File(self.h5_file, "a") as hdf5_file: # Work out how long the waits were, save em, post an event saying so @@ -328,9 +413,16 @@ def transition_to_manual(self): return True def shutdown(self): + """Cleanly shuts down the connection to the PrawnBlaster hardware.""" + self.prawnblaster.close() def abort_buffered(self): + """Aborts a currently running buffered execution. + + Returns: + bool: `True` is abort is successful. + """ if not self.is_master_pseudoclock: # Only need to send abort signal if we have told the PrawnBlaster to wait # for a hardware trigger. Otherwise it's just been programmed with @@ -343,4 +435,9 @@ def abort_buffered(self): return True def abort_transition_to_buffered(self): + """Aborts a transition to buffered. + + Calls :py:meth:`abort_buffered`. + """ + return self.abort_buffered() diff --git a/labscript_devices/PrawnBlaster/labscript_devices.py b/labscript_devices/PrawnBlaster/labscript_devices.py index eb05b79c..31cf9723 100644 --- a/labscript_devices/PrawnBlaster/labscript_devices.py +++ b/labscript_devices/PrawnBlaster/labscript_devices.py @@ -28,21 +28,37 @@ class _PrawnBlasterPseudoclock(Pseudoclock): + """Customized Clockline for use with the PrawnBlaster. + + This Pseudoclock retains information about which hardware clock + it is associated with, and ensures only one clockline per + pseudoclock. + """ def __init__(self, i, *args, **kwargs): + """ + Args: + i (int): Specifies which hardware pseudoclock this device + is associated with. + """ super().__init__(*args, **kwargs) self.i = i def add_device(self, device): + """ + Args: + device (:class:`~labscript.ClockLine`): Clockline to attach to the + pseudoclock. + """ if isinstance(device, ClockLine): # only allow one child if self.child_devices: raise LabscriptError( - f"Each pseudoclock of the PrawnBlaster {self.parent_device.name} only supports 1 clockline, which is automatically created. Please use the clockline located at {self.parent_device.name}.clockline[{self.i}]" + f"Each pseudoclock of the PrawnBlaster {self.parent_device.name} only supports 1 clockline, which is automatically created. Please use the clockline located at {self.parent_device.name}.clocklines[{self.i}]" ) Pseudoclock.add_device(self, device) else: raise LabscriptError( - f"You have connected {device.name} to {self.name} (a Pseudoclock of {self.parent_device.name}), but {self.name} only supports children that are ClockLines. Please connect your device to {self.parent_device.name}.clockline[{self.i}] instead." + f"You have connected {device.name} to {self.name} (a Pseudoclock of {self.parent_device.name}), but {self.name} only supports children that are ClockLines. Please connect your device to {self.parent_device.name}.clocklines[{self.i}] instead." ) @@ -51,6 +67,8 @@ def add_device(self, device): # since everything is handled internally in this device # class _PrawnBlasterDummyPseudoclock(Pseudoclock): + """Dummy Pseudoclock labscript device used internally to allow + :class:`~labscript.WaitMonitor` to work internally to the PrawnBlaster.""" def add_device(self, device): if isinstance(device, _PrawnBlasterDummyClockLine): if self.child_devices: @@ -69,6 +87,8 @@ def generate_code(self, *args, **kwargs): class _PrawnBlasterDummyClockLine(ClockLine): + """Dummy Clockline labscript device used internally to allow + :class:`~labscript.WaitMonitor` to work internally to the PrawnBlaster.""" def add_device(self, device): if isinstance(device, _PrawnBlasterDummyIntermediateDevice): if self.child_devices: @@ -87,6 +107,9 @@ def generate_code(self, *args, **kwargs): class _PrawnBlasterDummyIntermediateDevice(IntermediateDevice): + """Dummy intermediate labscript device used internally to attach + :class:`~labscript.WaitMonitor` objects to the PrawnBlaster.""" + def add_device(self, device): if isinstance(device, WaitMonitor): IntermediateDevice.add_device(self, device) @@ -104,18 +127,26 @@ def generate_code(self, *args, **kwargs): class PrawnBlaster(PseudoclockDevice): description = "PrawnBlaster" clock_limit = 1 / 100e-9 + """Maximum allowable clock rate.""" clock_resolution = 20e-9 - # There appears to be ~50ns buffer on input and then we know there is 80ns between - # trigger detection and first output pulse + """Minimum resolvable time for a clock tick.""" input_response_time = 50e-9 + """Time necessary for hardware to respond to a hardware trigger. + Empirically determined to be a ~50 ns buffer on the input. + """ trigger_delay = input_response_time + 80e-9 - # Overestimate that covers indefinite waits (which labscript does not yet support) + """Processing time delay after trigger is detected. Due to firmware, there is an + 80 ns delay between trigger detection and first output pulse.""" trigger_minimum_duration = 160e-9 - # There are 4 ASM instructions between end of pulse and being ready to detect - # a retrigger + """Minimum required width of hardware trigger. An overestimate that covers + currently unsupported indefinite waits.""" wait_delay = 40e-9 + """Minimum required length of a wait before retrigger can be detected. + Corresponds to 4 instructions.""" allowed_children = [_PrawnBlasterPseudoclock, _PrawnBlasterDummyPseudoclock] max_instructions = 30000 + """Maximum numaber of instructions per pseudoclock. Max is 30,000 for a single + pseudoclock.""" @set_passed_properties( property_names={ @@ -151,6 +182,38 @@ def __init__( external_clock_pin=None, use_wait_monitor=True, ): + """PrawnBlaster Pseudoclock labscript device. + + This labscript device creates Pseudoclocks based on the PrawnBlaster, + a Raspberry Pi Pico with custom firmware. + + Args: + name (str): python variable name to assign to the PrawnBlaster + com_port (str): COM port assigned to the PrawnBlaster by the OS. Takes + the form of `'COMd'`, where `d` is an integer. + num_pseudoclocks (int): Number of pseudoclocks to create. Ranges from 1-4. + trigger_device (:class:`~labscript.IntermediateDevice`, optional): Device + that will send the hardware start trigger when using the PrawnBlaster + as a secondary Pseudoclock. + trigger_connection (str, optional): Which output of the `trigger_device` + is connected to the PrawnBlaster hardware trigger input. + out_pins (list, optional): What outpins to use for the pseudoclock outputs. + Must have length of at least `num_pseudoclocks`. Defaults to `[9,11,13,15]` + in_pins (list, optional): What inpins to use for the pseudoclock hardware + triggering. Must have length of at least `num_pseudoclocks`. + Defaults to `[0,0,0,0]` + clock_frequency (float, optional): Frequency of clock. Standard range + accepts up to 133 MHz. An experimental overclocked firmware is + available that allows higher frequencies. + external_clock_pin (int, optional): If not `None` (the default), + the PrawnBlaster uses an external clock on the provided pin. Valid + options are `20` and `22`. The external frequency must be defined + using `clock_frequency`. + use_wait_monitor (bool, optional): Configure the PrawnBlaster to + perform its own wait monitoring. + + """ + # Check number of pseudoclocks is within range if num_pseudoclocks < 1 or num_pseudoclocks > 4: raise LabscriptError( @@ -249,13 +312,28 @@ def internal_wait_monitor_outputs(self): @property def pseudoclocks(self): + """Returns a list of the automatically generated + :class:`_PrawnBlasterPseudoclock` objects.""" + return copy.copy(self._pseudoclocks) @property def clocklines(self): + """Returns a list of the automatically generated + :class:`~labscript.ClockLine` objects.""" + return copy.copy(self._clocklines) def add_device(self, device): + """Adds child devices. + + This is automatically called by the labscript compiler. + + Args: + device (:class:`_PrawnBlasterPseudoclock` or :class:`_PrawnBlasterDummyPseudoclock`): + Instance to attach to the device. Only the allowed children can be attached. + """ + if len(self.child_devices) < ( self.num_pseudoclocks + self.use_wait_monitor ) and isinstance( @@ -274,6 +352,14 @@ def add_device(self, device): ) def generate_code(self, hdf5_file): + """Generates the hardware instructions for the pseudoclocks. + + This is automatically called by the labscript compiler. + + Args: + hdf5_file (:class:`h5py.File`): h5py file object for shot + """ + PseudoclockDevice.generate_code(self, hdf5_file) group = self.init_device_group(hdf5_file) diff --git a/labscript_devices/PrawnBlaster/runviewer_parsers.py b/labscript_devices/PrawnBlaster/runviewer_parsers.py index 42f9e552..7b62338d 100644 --- a/labscript_devices/PrawnBlaster/runviewer_parsers.py +++ b/labscript_devices/PrawnBlaster/runviewer_parsers.py @@ -19,12 +19,30 @@ class PrawnBlasterParser(object): + """Runviewer parser for the PrawnBlaster Pseudoclocks.""" def __init__(self, path, device): + """ + Args: + path (str): path to h5 shot file + device (str): labscript name of PrawnBlaster device + """ self.path = path self.name = device.name self.device = device def get_traces(self, add_trace, clock=None): + """Reads the shot file and extracts hardware instructions to produce + runviewer traces. + + Args: + add_trace (func): function handle that adds traces to runviewer + clock (tuple, optional): clock times from timing device, if not + the primary pseudoclock + + Returns: + dict: Dictionary of clocklines and triggers derived from instructions + """ + if clock is not None: times, clock_value = clock[0], clock[1] clock_indices = np.where((clock_value[1:] - clock_value[:-1]) == 1)[0] + 1 @@ -53,7 +71,19 @@ def get_traces(self, add_trace, clock=None): # Generate clocklines and triggers clocklines_and_triggers = {} - for pulse_program in pulse_programs: + + for pseudoclock_name, pseudoclock in self.device.child_list.items(): + # Get pseudoclock index + connection_parts = pseudoclock.parent_port.split() + # Skip if not one of the 4 possible pseudoclock outputs (there is one for + # the wait monitor too potentially) + if connection_parts[0] != "pseudoclock": + continue + + # Get the pulse program + index = int(connection_parts[1]) + pulse_program = pulse_programs[index] + time = [] states = [] trigger_index = 0 @@ -84,15 +114,14 @@ def get_traces(self, add_trace, clock=None): states.append(j) t += row["half_period"] * clock_factor - clock = (np.array(time), np.array(states)) + pseudoclock_clock = (np.array(time), np.array(states)) - for pseudoclock_name, pseudoclock in self.device.child_list.items(): - for clock_line_name, clock_line in pseudoclock.child_list.items(): - # Ignore the dummy internal wait monitor clockline - if clock_line.parent_port.startswith("GPIO"): - clocklines_and_triggers[clock_line_name] = clock - add_trace( - clock_line_name, clock, self.name, clock_line.parent_port - ) + for clock_line_name, clock_line in pseudoclock.child_list.items(): + # Ignore the dummy internal wait monitor clockline + if clock_line.parent_port.startswith("GPIO"): + clocklines_and_triggers[clock_line_name] = pseudoclock_clock + add_trace( + clock_line_name, pseudoclock_clock, self.name, clock_line.parent_port + ) return clocklines_and_triggers diff --git a/labscript_devices/SpinnakerCamera/blacs_workers.py b/labscript_devices/SpinnakerCamera/blacs_workers.py index 025bf767..b5cbc9c0 100644 --- a/labscript_devices/SpinnakerCamera/blacs_workers.py +++ b/labscript_devices/SpinnakerCamera/blacs_workers.py @@ -18,7 +18,6 @@ import numpy as np from labscript_utils import dedent from enum import IntEnum -import PySpin from time import sleep, perf_counter from labscript_devices.IMAQdxCamera.blacs_workers import IMAQdxCameraWorker @@ -28,6 +27,9 @@ def __init__(self, serial_number): """Initialize Spinnaker API camera. Serial number should be of string(?) type.""" + global PySpin + import PySpin + self.system = PySpin.System.GetInstance() ver = self.system.GetLibraryVersion() diff --git a/labscript_devices/TekScope/blacs_workers.py b/labscript_devices/TekScope/blacs_workers.py index 52cdc70c..b97afd5f 100644 --- a/labscript_devices/TekScope/blacs_workers.py +++ b/labscript_devices/TekScope/blacs_workers.py @@ -1,17 +1,22 @@ -import time import numpy as np +import labscript_utils.h5_lock +import h5py + from blacs.tab_base_classes import Worker import labscript_utils.properties + class TekScopeWorker(Worker): def init(self): - global h5py; import labscript_utils.h5_lock, h5py global TekScope from .TekScope import TekScope + self.scope = TekScope(self.addr, termination=self.termination) manufacturer, model, sn, revision = self.scope.idn.split(',') assert manufacturer.lower() == 'tektronix' - "Device is made by {:s}, not by Tektronix, and is actually a {:s}".format(manufacturer, model) + "Device is made by {:s}, not by Tektronix, and is actually a {:s}".format( + manufacturer, model + ) print('Connected to {} (SN: {})'.format(model, sn)) def transition_to_buffered(self, device_name, h5file, front_panel_values, refresh): @@ -19,10 +24,10 @@ def transition_to_buffered(self, device_name, h5file, front_panel_values, refres self.device_name = device_name with h5py.File(h5file, 'r') as hdf5_file: print('\n' + h5file) - self.scope_params = scope_params = labscript_utils.properties.get( - hdf5_file, device_name, 'device_properties') + self.scope_params = labscript_utils.properties.get( + hdf5_file, device_name, 'device_properties' + ) self.scope.dev.timeout = 1000 * self.scope_params.get('timeout', 5) - # hdf5_file['devices'][device_name].attrs.create('some name', some_value, dtype='some_type') self.scope.unlock() self.scope.set_acquire_state(True) @@ -40,9 +45,11 @@ def transition_to_manual(self): print('Downloading...') for ch, enabled in channels.items(): if enabled: - wfmp[ch], t, vals[ch] = self.scope.waveform(ch, - int16=self.scope_params.get('int16', False), - preamble_string=self.preamble_string) + wfmp[ch], t, vals[ch] = self.scope.waveform( + ch, + int16=self.scope_params.get('int16', False), + preamble_string=self.preamble_string, + ) wtype.append((ch, 'float')) print(wfmp[ch]['WFID']) @@ -75,4 +82,4 @@ def abort_buffered(self): def abort_transition_to_buffered(self): print('abort_transition_to_buffered: ...') - return self.abort() \ No newline at end of file + return self.abort() diff --git a/setup.cfg b/setup.cfg index 6ee0f61e..c018fdae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,12 +26,15 @@ packages = find: python_requires = >=3.6 install_requires = blacs>=3.0.0 + runmanager>=3.0.0 importlib_metadata labscript>=3.0.0 labscript_utils>=3.0.0 numpy>=1.15.1 pillow + tqdm PyDAQmx + PyVISA PyNIVision pyserial qtutils>=2.2.3 @@ -41,7 +44,7 @@ install_requires = [options.extras_require] docs = PyQt5 - Sphinx==3.0.1 - sphinx-rtd-theme==0.4.3 + Sphinx==3.5.3 + sphinx-rtd-theme==0.5.2 recommonmark==0.6.0 m2r==0.2.1