Instrumental

Instrumental is a Python-based library for controlling lab hardware like cameras, DAQs, oscilloscopes, spectrometers, and more. It has high-level drivers for instruments from NI, Tektronix, Thorlabs, PCO, Photometrics, Burleigh, and others.

Note

As of version 0.7, Instrumental has dropped support for Python 2 and now requires Python 3.7+.

Instrumental’s goal is to make common tasks simple to perform, while still providing the flexibility to perform complex tasks with relative ease. It also makes it easy to mess around with instruments in the shell. For example, to list the available instruments and open one of them:

>>> from instrumental import instrument, list_instruments
>>> paramsets = list_instruments()
>>> paramsets
[<ParamSet[TSI_Camera] serial='05478' number=0>,
 <ParamSet[K10CR1] serial='55000247'>
 <ParamSet[NIDAQ] model='USB-6221 (BNC)' name='Dev1'>]
>>> daq = instrument(paramsets[2])
>>> daq.ai0.read()
<Quantity(5.04241962841, 'volt')>

If you’re going to be using an instrument repeatedly, save it for later:

>>> daq.save_instrument('myDAQ')

Then you can simply open it by name:

>>> daq = instrument('myDAQ')

You can even access and control instruments on remote machines. Check out Working with Instruments for more detailed info.

Instrumental also bundles in some additional support code, including:

  • Plotting and curve fitting utilities

  • Utilities for acquiring and organizing data

Instrumental makes use of NumPy, SciPy, Matplotlib, and Pint, a Python units library. It optionally uses PyVISA/VISA and other drivers for interfacing with lab equipment.

To download Instrumental or browse its source, see our GitHub page.

You can cite Instrumental through Zenodo (DOI: 10.5281/zenodo.2556398).

Note

Instrumental is currently still under heavy development, so its interfaces are subject to change. Contributions are greatly appreciated, see Writing Drivers and Developer’s Guide for more info.

User Guide

Installation

Brief Install Instructions

Starting with version 0.2.1, you can install Instrumental using pip:

$ pip install instrumental-lib

This will install the latest release version along with the core dependencies if they aren’t already installed. It’s recommended that you use the the Anaconda distribution so you don’t have to compile numpy and scipy (see the detailed install instructions below).

Installing the Development Version from GitHub

Download and extract a zip of Instrumental from the Github page or clone it using git. Now install:

$ cd /path/to/Instrumental
$ python setup.py install

Detailed Install Instructions

Instrumental should install any core dependencies it requires, but if you’re having problems, you may want to read this section over. Note that many per-driver dependencies are not installed automatically, so you can install them as-needed.

Python Sci-Comp Stack

To install the standard scientific computing stack, we recommend using Anaconda. Download the appropriate installer from the download page and run it to install Anaconda. The default installation will include NumPy, SciPy, and Matplotlib as well as lots of other useful stuff.

Pint

Next, install Pint for units support:

$ pip install pint

For more information, or to get a more recent version, check out the Pint install page.

Instrumental

If you’re using git, you can clone the Instrumental repository to get the source code. If you don’t know git or don’t want to set up a local repo yet, you can just download a zip file by clicking the ‘Download ZIP’ button on the right hand side of the Instrumental Github page. Unzip the code wherever you’d like, then open a command prompt to that directory and run:

$ python setup.py install

to install Instrumental to your Python site-packages directory. You’re all set! Now go check out some of the examples in the examples directory contained in the files you downloaded!


Optional Driver Libraries
VISA

To operate devices that communicate using VISA (e.g. Tektronix scopes) you will need:

  1. an implementation of VISA, and

  2. a Python interface layer called PyVISA

More info about PyVISA, including more detailed install-related information can be found here.

Other Drivers

Install directions are located on each driver’s page within the Drivers section. This lists the python packages that are required, as well as any external libraries.

Package Overview

Drivers

The drivers subpackage is the primary focus of Instrumental, and its purpose is to provide relatively high-level ‘drivers’ for interfacing with lab equipment. Currently it supports:

Drivers

Instrumental drivers allow you to control and read data from various hardware devices.

Some devices (e.g. Thorlabs cameras) have drivers that act as wrappers to their drivers’ C bindings, using ctypes or cffi. Others (e.g. Tektronix scopes and AFGs) utilize VISA and PyVISA, its Python wrapper. PyVISA requires a local installation of the VISA library (e.g. NI-VISA) to interface with connected devices.

Cameras

Create Camera objects using instrument().

PCO Cameras

This module is for controlling PCO cameras that use the PCO.camera SDK. Note that not all PCO cameras use this SDK, e.g. older Pixelfly cameras have their own SDK.

Installation

This module requires the PCO SDK and the cffi package.

You should install the PCO SDK provided on PCO’s website. Specifically, this module requires SC2_Cam.dll to be available in your PATH, as well as any interface-specific DLLs. Firewire requires SC2_1394.dll, and each type of Camera Link grabber requires its own DLL, e.g. sc2_cl_me4.dll for a Silicon Software microEnable IV grabber card.

Module Reference
PCO Pixelfly Cameras

This module is for controlling PCO Pixelfly cameras.

Installation

This module requires the Pixelfly SDK and the cffi package.

You should install the Pixelfly SDK provided on PCO’s website. Specifically, this module requires pf_cam.dll to be available in your PATH.

Module Reference

Driver for PCO Pixelfly cameras.

class instrumental.drivers.cameras.pixelfly.Pixelfly(inst=None, **kwds)
cancel_capture()

Cancels a capture sequence, cleaning up and stopping the camera

close()

Clean up memory and close the camera.

get_captured_image(timeout='1s', copy=True, **kwds)

Get the image array(s) from the last capture sequence.

Returns an image numpy array (or tuple of arrays for a multi-exposure sequence). The array has shape (height, width) for grayscale images, and (height, width, 3) for RGB images. Typically the dtype will be uint8, or sometimes uint16 in the case of 16-bit monochromatic cameras.

Parameters
  • timeout (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

grab_image(timeout='1s', copy=True, **kwds)

Perform a capture and return the resulting image array(s).

This is essentially a convenience function that calls start_capture() then get_captured_image(). See get_captured_image() for information about the returned array(s).

Parameters
  • timeouts (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

  • include (You can specify other parameters of the capture as keyword arguments. These) –

  • n_frames (int) – Number of exposures in the sequence

  • vbin (int) – Vertical binning

  • hbin (int) – Horizontal binning

  • exposure_time (Quantity([time])) – Duration of each exposure

  • width (int) – Width of the ROI

  • height (int) – Height of the ROI

  • cx (int) – X-axis center of the ROI

  • cy (int) – Y-axis center of the ROI

  • left (int) – Left edge of the ROI

  • right (int) – Right edge of the ROI

  • top (int) – Top edge of the ROI

  • bot (int) – Bottom edge of the ROI

latest_frame(copy=True)

Get the latest image frame in live mode.

Returns the image array received on the most recent successful call to wait_for_frame().

Parameters

copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

quantum_efficiency(self, wavlen, high_gain=False)

Fractional quantum efficiency of the sensor at a given wavelength

set_mode(shutter='single', trig='software', exposure='10ms', hbin=1, vbin=1, gain='low', depth=12)

Set the mode of the camera.

Parameters
  • shutter (str) – One of ‘single’, ‘double’, or ‘video’.

  • trig (str) – One of ‘software’ or ‘hardware’.

  • exposure (Quantity or str) – Exposure time. Up to 65.6 ms with microsecond resolution.

  • hbin (int) – Horizontal binning factor. Either 1 or 2.

  • vbin (int) – Vertical binning factor. Either 1 or 2.

  • gain (str) – Gain of camera. Either ‘low’ or ‘high’.

  • depth (int) – Bit depth of each pixel. Either 8 or 12.

start_capture(**kwds)

Start a capture sequence and return immediately.

Depending on your camera-specific shutter/trigger settings, this will either start the exposure immediately or ready the camera to start on an explicit (hardware or software) trigger.

It can be useful to invoke start_capture() and get_captured_image() explicitly if you expect the capture sequence to take a long time and you’d like to perform some operations while you wait for the camera:

>>> cam.start_capture()
>>> do_other_useful_stuff()
>>> arr = cam.get_captured_image()

See grab_image() for the set of available kwds.

start_live_video(**kwds)

Start live video mode.

Once live video mode has been started, images will automatically and continuously be acquired. You can check if the next frame is ready by using wait_for_frame(), and access the most recent image’s data with get_captured_image().

See grab_image() for the set of available kwds.

stop_live_video()

Stop live video mode.

wait_for_frame(timeout=None)

Wait until the next frame is ready (in live mode).

Blocks and returns True once the next frame is ready, False if the timeout was reached. Using a timeout of 0 simply polls to see if the next frame is ready.

Parameters

timeout (Quantity([time]), optional) – How long to wait for wait for the image data to be ready. If None (the default), will block forever.

Returns

frame_readyTrue if the next frame is ready, False if the timeout was reached.

Return type

bool

DEFAULT_KWDS = {'bot': None, 'cx': None, 'cy': None, 'exposure_time': <Quantity(10, 'millisecond')>, 'fix_hotpixels': False, 'gain': 'low', 'hbin': 1, 'height': None, 'left': None, 'n_frames': 1, 'right': None, 'shutter': 'single', 'top': None, 'trig': 'software', 'vbin': 1, 'width': None}
property bit_depth
property frame_time
property height

Height of the camera image in pixels

property last_exposure_time
property max_height

Max settable height of the camera image, given current binning/subpixel settings

property max_width

Max settable width of the camera image, given current binning/subpixel settings

property readout_time
property supports_5us_exposure
property supports_double_mode
property supports_hvga
property supports_ir_mode
property supports_qe
property supports_set_exposure
property supports_svga
property supports_vga2
property temperature

The temperature of the CCD.

property width

Width of the camera image in pixels

Thorlabs TSI Cameras

This module is for controlling Thorlabs cameras that use the TSI SDK. Note that Thorlabs DCx cameras use a separate SDK.

Installation

This module requires the TSI SDK and the NiceLib package.

Module Reference
class instrumental.drivers.cameras.tsi.TSI_Camera(inst=None, **kwds)
class TriggerMode(value)

An enumeration.

auto = 'NORMAL'

Auto-trigger as fast as possible once capture has started

hw_bulb = 'PDX'

Trigger a single exposure on an edge of a pulse, and stop the exposure at the end of the pulse

hw_edge = 'TOE'

Trigger a single exposure on an edge, using a software-defined exposure time

software = 'NORMAL'

Alias for auto

cancel_capture()

Cancel a capture sequence, cleaning up and stopping the camera

close()
get_captured_image(timeout='1s', copy=True, wait_for_all=True, **kwds)

Get the image array(s) from the last capture sequence.

Returns an image numpy array (or tuple of arrays for a multi-exposure sequence). The array has shape (height, width) for grayscale images, and (height, width, 3) for RGB images. Typically the dtype will be uint8, or sometimes uint16 in the case of 16-bit monochromatic cameras.

Parameters
  • timeout (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

grab_image(timeout='1s', copy=True, **kwds)

Perform a capture and return the resulting image array(s).

This is essentially a convenience function that calls start_capture() then get_captured_image(). See get_captured_image() for information about the returned array(s).

Parameters
  • timeouts (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

  • include (You can specify other parameters of the capture as keyword arguments. These) –

  • n_frames (int) – Number of exposures in the sequence

  • vbin (int) – Vertical binning

  • hbin (int) – Horizontal binning

  • exposure_time (Quantity([time])) – Duration of each exposure

  • width (int) – Width of the ROI

  • height (int) – Height of the ROI

  • cx (int) – X-axis center of the ROI

  • cy (int) – Y-axis center of the ROI

  • left (int) – Left edge of the ROI

  • right (int) – Right edge of the ROI

  • top (int) – Top edge of the ROI

  • bot (int) – Bottom edge of the ROI

latest_frame(copy=True)

Get the latest image frame in live mode.

Returns the image array received on the most recent successful call to wait_for_frame().

Parameters

copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

start_capture(**kwds)

Start a capture sequence and return immediately.

Depending on your camera-specific shutter/trigger settings, this will either start the exposure immediately or ready the camera to start on an explicit (hardware or software) trigger.

It can be useful to invoke start_capture() and get_captured_image() explicitly if you expect the capture sequence to take a long time and you’d like to perform some operations while you wait for the camera:

>>> cam.start_capture()
>>> do_other_useful_stuff()
>>> arr = cam.get_captured_image()

See grab_image() for the set of available kwds.

start_live_video(**kwds)

Start live video mode.

Once live video mode has been started, images will automatically and continuously be acquired. You can check if the next frame is ready by using wait_for_frame(), and access the most recent image’s data with get_captured_image().

See grab_image() for the set of available kwds.

stop_live_video()

Stop live video mode.

wait_for_frame(timeout=None)

Wait until the next frame is ready (in live mode).

Blocks and returns True once the next frame is ready, False if the timeout was reached. Using a timeout of 0 simply polls to see if the next frame is ready.

Parameters

timeout (Quantity([time]), optional) – How long to wait for wait for the image data to be ready. If None (the default), will block forever.

Returns

frame_readyTrue if the next frame is ready, False if the timeout was reached.

Return type

bool

DEFAULT_KWDS = {'bot': None, 'cx': None, 'cy': None, 'exposure_time': <Quantity(10, 'millisecond')>, 'fix_hotpixels': False, 'gain': 0, 'hbin': 1, 'height': None, 'left': None, 'n_frames': 1, 'right': None, 'rising': True, 'top': None, 'trig': 'auto', 'vbin': 1, 'width': None}
property height

Height of the camera image in pixels

property led_on
property max_height

Max settable height of the camera image, given current binning/subpixel settings

property max_width

Max settable width of the camera image, given current binning/subpixel settings

property model
property name
property serial
property width

Width of the camera image in pixels

class instrumental.drivers.cameras.tsi.TSI_DLL_Camera(ptr)
ClearError()
Close()
FreeImage(image)
GetAcquisitionStatus()
GetCameraName()
GetDataTypeSize(data_type)
GetErrorCode()
GetErrorStr(code)
GetExposeCount()
GetFrameCount()
GetLastErrorStr()
GetParameter(param_id)
GetPendingImage()
Open()
ResetCamera()
ResetExposure()
SetCameraName(name)
SetParameter(param_id, data)
Start()
StartAndWait(timeout_ms)
Status()
Stop()
WaitForImage(timeout_ms=-1)
__init__(ptr)
class instrumental.drivers.cameras.tsi.TSI_DLL_SDK
Close()
GetCamera(camera_number)
GetCameraAddressStr(camera_number, address_select)
GetCameraInterfaceTypeStr(camera_number)
GetCameraName(camera_number)
GetCameraSerialNumStr(camera_number)
GetNumberOfCameras()
Open()
__init__()
destroy()
class instrumental.drivers.cameras.tsi.make_enum(*args)
__init__(*args)
instrumental.drivers.cameras.tsi.from_enum(item)
instrumental.drivers.cameras.tsi.list_instruments()
Thorlabs DCx (UC480) Cameras
Installation
Summary
  1. Install the uc480 API provided by Thorlabs.

  2. Add the DLL to your PATH environment variable.

  3. Run pip install pywin32 nicelib.

  4. Call list_instruments(), which will auto-build the API bindings.

Details
  1. Download and install ThorCam from the Thorlabs website, which comes with the uc480 API libraries. (Since these cameras are rebranded IDS cameras, you may instead install the IDS uEye software)

  2. Make sure the path to the shared library (uc480.dll, uc480_64.dll, ueye_api.dll, or ueye_api_64.dll) is added to your PATH. The library will usually be located in the Thorlabs or IDS folder inside your Program Files folder. On my system they are located within C:\Program Files\Thorlabs\Scientific Imaging\DCx Camera Support\Develop\Lib.

  3. Run pip install pywin32 nicelib on the command line to install the pywin32 and nicelib packages.

  4. Use list_instruments() to see if your camera shows up. This will automatically build the bindings to the DLL. If this doesn’t work (and your camera is plugged in an works with the ThorCam software), try to import the driver module directly: from instrumental.drivers.cameras import uc480. If this fails, the error should give you information about what went wrong. Be sure to check out the FAQs page for more information, and you can use the mailing list or GitHub if you need additional help.


Module Reference

Driver for Thorlabs DCx cameras. May be compatible with iDS cameras that use uEye software. Currently Windows-only, but Linux support should be possible to implement if desired.

class instrumental.drivers.cameras.uc480.UC480_Camera(inst=None, **kwds)

A uc480-supported Camera

__init__()
close(force=False)

Close the camera and release the associated image memory.

Parameters

force (bool) – If True, run the close routine even if the camera is already closed (self.is_open is False).

fill_all_coords(kwds, names)
find_hot_pixels(stddevs=10, **kwds)

Generate the list of hot pixels on the camera sensor.

get(facet_name, use_cache=False)
get_captured_image(timeout='1s', copy=True)

Get the image array(s) from the last capture sequence.

Returns an image numpy array (or tuple of arrays for a multi-exposure sequence). The array has shape (height, width) for grayscale images, and (height, width, 3) for RGB images. Typically the dtype will be uint8, or sometimes uint16 in the case of 16-bit monochromatic cameras.

Parameters
  • timeout (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

get_trigger_delay()

Get the trigger delay in [time] units.

Returns

Trigger delay

Return type

string

get_trigger_level()

Get the current hardware trigger level.

Returns

A value of 0 indicates trigger signal is low (not triggered)

Return type

int

grab_image(timeout='1s', copy=True, **kwds)

Perform a capture and return the resulting image array(s).

This is essentially a convenience function that calls start_capture() then get_captured_image(). See get_captured_image() for information about the returned array(s).

Parameters
  • timeouts (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

  • include (You can specify other parameters of the capture as keyword arguments. These) –

  • n_frames (int) – Number of exposures in the sequence

  • vbin (int) – Vertical binning

  • hbin (int) – Horizontal binning

  • exposure_time (Quantity([time])) – Duration of each exposure

  • width (int) – Width of the ROI

  • height (int) – Height of the ROI

  • cx (int) – X-axis center of the ROI

  • cy (int) – Y-axis center of the ROI

  • left (int) – Left edge of the ROI

  • right (int) – Right edge of the ROI

  • top (int) – Top edge of the ROI

  • bot (int) – Bottom edge of the ROI

latest_frame(copy=True)

Get the latest image frame in live mode.

Returns the image array received on the most recent successful call to wait_for_frame().

Parameters

copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

load_params(filename=None)

Load camera parameters from file or EEPROM.

Parameters

filename (str, optional) – By default, loads the parameters from the camera’s EEPROM. Otherwise, loads it from the specified parameter file. If filename is the empty string ‘’, will open a ‘Load’ dialog to select the file.

observe(name, callback)

Add a callback to observe changes in a facet’s value

The callback should be a callable accepting a ChangeEvent as its only argument. This ChangeEvent is a namedtuple with name, old, and new fields. name is the facet’s name, old is the old value, and new is the new value.

open(num_bufs=1, force=False)

Connect to the camera and set up the image memory.

Parameters

force (bool) – If True, run the open routine even if the camera is already open (self.is_open is True).

save_hot_pixels(path=None)

Save a file listing the hot pixels.

save_instrument(name, force=False)

Save an entry for this instrument in the config file.

Parameters
  • name (str) – The name to give the instrument, e.g. ‘myCam’

  • force (bool, optional) – Force overwrite of the old entry for instrument name. By default, Instrumental will raise an exception if you try to write to a name that’s already taken. If force is True, the old entry will be commented out (with a warning given) and a new entry will be written.

set_auto_exposure(enable=True)

Enable or disable the auto exposure shutter.

set_defaults(**kwds)
set_trigger(mode='software', edge='rising')

Set the camera trigger mode.

Parameters
  • mode (string) – Either ‘off’, ‘software’(default), or ‘hardware’.

  • edge (string) – Hardware trigger is either on the ‘rising’(default) or ‘falling’ edge.

set_trigger_delay(delay)

Set the time to delay a hardware trigger.

Parameters

delay ([time]-dimensioned Quantity) – The delay time after trigger signal is received to trigger the camera. Has microsecond resolution.

start_capture(**kwds)

Start a capture sequence and return immediately.

Depending on your camera-specific shutter/trigger settings, this will either start the exposure immediately or ready the camera to start on an explicit (hardware or software) trigger.

It can be useful to invoke start_capture() and get_captured_image() explicitly if you expect the capture sequence to take a long time and you’d like to perform some operations while you wait for the camera:

>>> cam.start_capture()
>>> do_other_useful_stuff()
>>> arr = cam.get_captured_image()

See grab_image() for the set of available kwds.

start_live_video(framerate=None, **kwds)

Start live video mode.

Once live video mode has been started, images will automatically and continuously be acquired. You can check if the next frame is ready by using wait_for_frame(), and access the most recent image’s data with get_captured_image().

See grab_image() for the set of available kwds.

stop_live_video()

Stop live video mode.

wait_for_frame(timeout=None)

Wait until the next frame is ready (in live mode).

Blocks and returns True once the next frame is ready, False if the timeout was reached. Using a timeout of 0 simply polls to see if the next frame is ready.

Parameters

timeout (Quantity([time]), optional) – How long to wait for wait for the image data to be ready. If None (the default), will block forever.

Returns

frame_readyTrue if the next frame is ready, False if the timeout was reached.

Return type

bool

DEFAULT_KWDS = {'bot': None, 'cx': None, 'cy': None, 'exposure_time': <Quantity(10, 'millisecond')>, 'fix_hotpixels': False, 'gain': 0, 'hbin': 1, 'height': None, 'hsub': 1, 'left': None, 'n_frames': 1, 'right': None, 'top': None, 'vbin': 1, 'vsub': 1, 'width': None}
auto_blacklevel

Whether auto-blacklevel correction is turned on

auto_exposure

Whether auto exposure is enabled

auto_framerate

Whether auto framerate is enabled

auto_gain

Whether auto gain is enabled

auto_sensor_exposure

Whether sensor-based auto exposure is enabled

auto_sensor_framerate

Whether sensor-based auto framerate is enabled

auto_sensor_gain

Whether sensor-based auto gain is enabled

auto_sensor_whitebalance

Whether sensor-based auto whitebalance is enabled

auto_whitebalance

Whether auto whitebalance is enabled

blacklevel_offset

The blacklevel offset value (an int)

property bytes_per_line

Number of bytes used by each line of the image. Read-only

property color_mode

Color mode string. Read-only

exposure
property framerate

Current framerate, in [time]⁻¹ units. Read-only

gain_boost

Whether the analog gain boost is enabled

A change to the gain boost will not affect the very next image captured. It’s unclear whether this is an inadequacy of Instrumental, or an underlying bug/feature of the library.

gamma

The gamma correction value (1.0-10.0)

property height

Height of the camera image in pixels

property id

uEye camera ID number. Read-only

master_gain

The master gain factor; 1.0 is the lowest gain.

property max_height

Max settable height of the camera image, given current binning/subpixel settings

property max_master_gain

Max value that master_gain can take

property max_width

Max settable width of the camera image, given current binning/subpixel settings

property model

Camera model number string. Read-only

pixelclock
pixelclock_default
property serial

Camera serial number string. Read-only

property trigger_mode

Trigger mode string. Read-only

property width

Width of the camera image in pixels


Changelog
Unreleased
  • Added gain_boost, master_gain, gamma, blacklevel, and many auto-x Facets/properties

  • Made sure framerate is set before exposure time

Version 0.4.1
  • Fixed AOI-related error on calling start_live_video()

Version 0.4
  • Converted to use new-style Instrument initialization

  • Added error code to UC480 errors

  • Converted to use new-style Params

Version 0.3
  • Removed deprecated usage of ‘is_SetImageSize’

  • Ported driver to use NiceLib instead of ctypes

  • Added subsampling support

  • Added gain setting

  • Added triggering support

  • Added support for using IDS library

Version 0.2
  • Initial driver release

Photometrics Cameras

This module is for controlling photometrics cameras.

Module Reference
Picam Cameras
Installation

This module requires the Picam SDK and the NiceLib package. Tested to work on Windows and Linux.

On Linux, you must set the GENICAM_ROOT_V2_4 environment variable to the path to genicam (probably /opt/pleora/ebus_sdk/x86_64/lib/genicam) and ensure that Picam’s lockfile directory exists (the Picam SDK installer isn’t good about doing this).

On Windows, the DLLs Picam.dll, Picc.dll, Pida.dll, and Pidi.dll must be copied to a directory on the system path. Note that the DLLs found first on the system path must match the version of the headers installed with the Picam SDK.

Module Reference

In addition to the documented methods, instances of PicamCamera have a params attribute which contains the camera’s Picam parameters. Each parameter implements get_value(), set_value(), can_set(), and get_default() methods that call the underlying Picam SDK functions. For example,

>>> cam.params.ShutterTimingMode.get_value()  #  => gives ShutterTimingMode.AlwaysOpen
>>> cam.params.ShutterTimingMode.set_value(PicamEnums.ShutterTimingMode.AlwaysClosed)
>>> cam.params.ShutterTimingMode.get_value()  #  verify the change
Picam Data Types

These data types are returned by the API and are not meant to be created directly by users. They provide a wrapped interface to Picam’s data types and automatically handle memory cleanup.

Parameter Types

All Picam Parameters accessible through PicamCamera.params are instances of one of these classes.

Low Level Interface

The NicePicamLib class provides a more direct wrapping of the Picam SDK’s C interface—what the NiceLib package calls a “Mid-level” interface. See the NiceLib documentation for more information on how to use this kind of interface.

Generic Camera Interface

Package containing a driver module/class for each supported camera type.

class instrumental.drivers.cameras.Camera(inst=None, **kwds)

A generic camera device.

Camera driver internals can often be quite different; however, Instrumental defines a few basic concepts that all camera drivers should have.

There are two basic modes: finite and continuous.

In finite mode, a camera performs a capture sequence, returning one or more images all at once, when the sequence is finished.

In continuous or live mode, the camera continuously retrieves images until it is manually stopped. This mode can be used e.g. to make a GUI that looks at a live view of the camera. The process looks like this:

>>> cam.start_live_video()
>>> while not_done():
>>>     frame_ready = cam.wait_for_frame()
>>>     if frame_ready:
>>>         arr = cam.latest_frame()
>>>         do_stuff_with(arr)
>>> cam.stop_live_video()
fill_all_coords(kwds, names)
find_hot_pixels(stddevs=10, **kwds)

Generate the list of hot pixels on the camera sensor.

abstract get_captured_image(timeout='1s', copy=True)

Get the image array(s) from the last capture sequence.

Returns an image numpy array (or tuple of arrays for a multi-exposure sequence). The array has shape (height, width) for grayscale images, and (height, width, 3) for RGB images. Typically the dtype will be uint8, or sometimes uint16 in the case of 16-bit monochromatic cameras.

Parameters
  • timeout (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

abstract grab_image(timeouts='1s', copy=True, **kwds)

Perform a capture and return the resulting image array(s).

This is essentially a convenience function that calls start_capture() then get_captured_image(). See get_captured_image() for information about the returned array(s).

Parameters
  • timeouts (Quantity([time]) or None, optional) – Max time to wait for wait for the image data to be ready. If None, will block forever. If timeout is exceeded, a TimeoutError will be raised.

  • copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

  • include (You can specify other parameters of the capture as keyword arguments. These) –

  • n_frames (int) – Number of exposures in the sequence

  • vbin (int) – Vertical binning

  • hbin (int) – Horizontal binning

  • exposure_time (Quantity([time])) – Duration of each exposure

  • width (int) – Width of the ROI

  • height (int) – Height of the ROI

  • cx (int) – X-axis center of the ROI

  • cy (int) – Y-axis center of the ROI

  • left (int) – Left edge of the ROI

  • right (int) – Right edge of the ROI

  • top (int) – Top edge of the ROI

  • bot (int) – Bottom edge of the ROI

abstract latest_frame(copy=True)

Get the latest image frame in live mode.

Returns the image array received on the most recent successful call to wait_for_frame().

Parameters

copy (bool, optional) – Whether to copy the image memory or directly reference the underlying buffer. It is recommended to use True (the default) unless you know what you’re doing.

save_hot_pixels(path=None)

Save a file listing the hot pixels.

set_defaults(**kwds)
abstract start_capture(**kwds)

Start a capture sequence and return immediately.

Depending on your camera-specific shutter/trigger settings, this will either start the exposure immediately or ready the camera to start on an explicit (hardware or software) trigger.

It can be useful to invoke start_capture() and get_captured_image() explicitly if you expect the capture sequence to take a long time and you’d like to perform some operations while you wait for the camera:

>>> cam.start_capture()
>>> do_other_useful_stuff()
>>> arr = cam.get_captured_image()

See grab_image() for the set of available kwds.

abstract start_live_video(**kwds)

Start live video mode.

Once live video mode has been started, images will automatically and continuously be acquired. You can check if the next frame is ready by using wait_for_frame(), and access the most recent image’s data with get_captured_image().

See grab_image() for the set of available kwds.

abstract stop_live_video()

Stop live video mode.

abstract wait_for_frame(timeout=None)

Wait until the next frame is ready (in live mode).

Blocks and returns True once the next frame is ready, False if the timeout was reached. Using a timeout of 0 simply polls to see if the next frame is ready.

Parameters

timeout (Quantity([time]), optional) – How long to wait for wait for the image data to be ready. If None (the default), will block forever.

Returns

frame_readyTrue if the next frame is ready, False if the timeout was reached.

Return type

bool

DEFAULT_KWDS = {'bot': None, 'cx': None, 'cy': None, 'exposure_time': <Quantity(10, 'millisecond')>, 'fix_hotpixels': False, 'gain': 0, 'hbin': 1, 'height': None, 'left': None, 'n_frames': 1, 'right': None, 'top': None, 'vbin': 1, 'width': None}
abstract property height

Height of the camera image in pixels

abstract property max_height

Max settable height of the camera image, given current binning/subpixel settings

abstract property max_width

Max settable width of the camera image, given current binning/subpixel settings

abstract property width

Width of the camera image in pixels

DAQs

Create DAQ objects using instrument().

NI DAQs

This module has been developed using an NI USB-6221 – the code should generally work for all DAQmx boards, but I’m sure there are plenty of compatibility bugs just waiting for you wonderful users to find and fix.

First, make sure you have NI’s DAQmx software installed. Instrumental will then use NiceLib to generate bindings from the header it finds.

The NIDAQ class lets you interact with your board and all its various inputs and outputs in a fairly simple way. Let’s say you’ve hooked up digital I/O P1.0 to analog input AI0, and your analog out AO1 to analog input AI1:

>>> from instrumental.drivers.daq.ni import NIDAQ, list_instruments
>>> list_instruments()
[<NIDAQ 'Dev0'>]
>>> daq = NIDAQ('Dev0')
>>> daq.ai0.read()
<Quantity(0.0154385786803, 'volt)>
>>> daq.port1[0].write(True)
>>> daq.ai0.read()
<Quantity(5.04241962841, 'volt')>
>>> daq.ao1.write('2.1V')
>>> daq.ai1.read()
<Quantity(2.10033320744, 'volt')>

Now let’s try using digital input. Assume P1.1 is attached to P1.2:

>>> daq.port1[1].write(False)
>>> daq.port1[2].read()
False
>>> daq.port1[1].write(True)
>>> daq.port1[2].read()
True

Let’s read and write more than one bit at a time. To write to multiple lines simultaneously, pass an unsigned int to write(). The line with the lowest index corresponds to the lowest bit, and so on. If you read from multiple lines, read() returns an int. Connect P1.0-3 to P1.4-7:

>>> daq.port1[0:3].write(5)  # 0101 = decimal 5
>>> daq.port1[4:7].read()  # Note that the last index IS included
5
>>> daq.port1[7:4].read()  # This flips the ordering of the bits
10                         # 1010 = decimal 10
>>> daq.port1[0].write(False)  # Zero the ones bit individually
>>> daq.port1[4:7].read()  # 0100 = decimal 4
4

You can also read and write arrays of buffered data. Use the same read() and write() methods, just include your timing info (and pass in the data as an array if writing). When writing, you must provide either freq or fsamp, and may provide either duration or reps to specify for how long the waveform is output. For example, there are many ways to output the same sinusoid:

>>> from instrumental import u
>>> from numpy import pi, sin, linspace
>>> data = sin( 2*pi * linspace(0, 1, 100, endpoint=False) )*5*u.V + 5*u.V
>>> daq.ao0.write(data, duration='1s', freq='500Hz')
>>> daq.ao0.write(data, duration='1s', fsamp='50kHz')
>>> daq.ao0.write(data, reps=500, freq='500Hz')
>>> daq.ao0.write(data, reps=500, fsamp='50kHz')

Note the use of endpoint=False in linspace. This ensures we don’t repeat the start/end point (0V) of our sine waveform when outputting more than one period.

All this stuff is great for simple tasks, but sometimes you may want to perform input and output on multiple channels simultaneously. To accomplish this we need to use Tasks.

Note

Tasks in the ni module are similar, but not the same as Tasks in DAQmx (and PyDAQmx). Our Tasks allow you to quickly and easily perform simultaneous input and output with one Task without the hassle of having to create multiple and hook their timing and triggers up.

Here’s an example of how to perform simultaneous input and output:

>>> from instrumental.drivers.daq.ni import NIDAQ, Task
>>> from instrumental import u
>>> from numpy import linspace
>>> daq = NIDAQ('Dev0')
>>> task = Task(daq.ao0, daq.ai0)
>>> task.set_timing(duration='1s', fsamp='10Hz')
>>> write_data = {'ao0': linspace(0, 9, 10) * u.V}
>>> task.run(write_data)
{u'ai0': <Quantity([  1.00000094e+01   1.89578724e-04   9.99485542e-01   2.00007917e+00
   3.00034866e+00   3.99964556e+00   4.99991698e+00   5.99954114e+00
   6.99981625e+00   7.99976941e+00], 'volt')>,
 u't': <Quantity([ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9], 'second')>}

As you can see, we create a dict as input to the run() method. Its keys are the names of the input channels, and its values are the corresponding array Quantities that we want to write. Similarly, the run() returns a dict that contains the input that was read. This dict also contains the time data under key ‘t’. Note that the read and write happen concurrently, so each voltage read has not yet moved to its new setpoint.

There may be applications where data needs to be acquired continuously. This can be achieved by setting up a Task object in ‘continuous’ mode.

Here is an example of how to perform continuous sampling:

from instrumental.drivers.daq.ni import NIDAQ, Task
daq = NIDAQ('Dev0') # Or whatever is shown from instrumental.list_instruments()
task = daq.Task(daq.ai0, daq.ai1)
task.set_timing(fsamp='100 Hz', n_samples=1000, mode='continuous')
task.start()
done = False
while not done:
    data = task.read()
    # do something with the data
    # do something to eventually set done = True
task.stop()

Once set, data will contain a dictionary. Its keys are the names of input channels, and values are the corresponding array Quantities. The dictionary also contains time data under key ‘t’. The length of each of the arrays in this dictionary will be between 0 and n_samples elements. Therefore, you do not need to worry about syncronizing the timing of your read() calls, as each read() call will only return the data returned since the last call to read(), or since the task started. To avoid unexpected behavior, ensure that your code calls task.read() frequently enough so that the daq never completely fills the n_samples-sized buffer.

Module Reference
exception instrumental.drivers.daq.ni.DAQError(code)
__init__(code)
class instrumental.drivers.daq.ni.AnalogIn(daq, chan_name)
__init__(daq, chan_name)
read(duration=None, fsamp=None, n_samples=None, vmin=None, vmax=None, reserve_timeout=None)

Read one or more analog input samples.

By default, reads and returns a single sample. If two of duration, fsamp, and n_samples are given, an array of samples is read and returned.

Parameters
  • duration (Quantity) – How long to read from the analog input, specified as a Quantity. Use with fsamp or n_samples.

  • fsamp (Quantity) – The sample frequency, specified as a Quantity. Use with duration or n_samples.

  • n_samples (int) – The number of samples to read. Use with duration or fsamp.

Returns

data – The data that was read from analog input.

Return type

scalar or array Quantity

read_sample(timeout=None)
start_reading(fsamp=None, vmin=None, vmax=None, overwrite=False, relative_to=RelativeTo.CurrReadPos, offset=0, buf_size=10)
stop_reading()
type = 'AI'
class instrumental.drivers.daq.ni.AnalogOut(daq, chan_name)
__init__(daq, chan_name)
read(duration=None, fsamp=None, n_samples=None)

Read one or more analog output samples.

Not supported by all DAQ models; requires the appropriate internal channel.

By default, reads and returns a single sample. If two of duration, fsamp, and n_samples are given, an array of samples is read and returned.

Parameters
  • duration (Quantity) – How long to read from the analog output, specified as a Quantity. Use with fsamp or n_samples.

  • fsamp (Quantity) – The sample frequency, specified as a Quantity. Use with duration or n_samples.

  • n_samples (int) – The number of samples to read. Use with duration or fsamp.

Returns

data – The data that was read from analog output.

Return type

scalar or array Quantity

write(data, duration=None, reps=None, fsamp=None, freq=None, onboard=True)

Write a value or array to the analog output.

If data is a scalar value, it is written to output and the function returns immediately. If data is an array of values, a buffered write is performed, writing each value in sequence at the rate determined by duration and fsamp or freq. You must specify either fsamp or freq.

When writing an array, this function blocks until the output sequence has completed.

Parameters
  • data (scalar or array Quantity) – The value or values to output, passed in Volt-compatible units.

  • duration (Quantity, optional) – Used when writing arrays of data. This is how long the entirety of the output lasts, specified as a second-compatible Quantity. If duration is longer than a single period of data, the waveform will repeat. Use either this or reps, not both. If neither is given, waveform is output once.

  • reps (int or float, optional) – Used when writing arrays of data. This is how many times the waveform is repeated. Use either this or duration, not both. If neither is given, waveform is output once.

  • fsamp (Quantity, optional) – Used when writing arrays of data. This is the sample frequency, specified as a Hz-compatible Quantity. Use either this or freq, not both.

  • freq (Quantity, optional) – Used when writing arrays of data. This is the frequency of the overall waveform, specified as a Hz-compatible Quantity. Use either this or fsamp, not both.

  • onboard (bool, optional) – Use only onboard memory. Defaults to True. If False, all data will be continually buffered from the PC memory, even if it is only repeating a small number of samples many times.

type = 'AO'
class instrumental.drivers.daq.ni.EdgeSlope(value)

An enumeration.

falling = 'FallingSlope'
rising = 'RisingSlope'
class instrumental.drivers.daq.ni.NIDAQ(inst=None, **kwds)
class Task(*channels)

A high-level task that can synchronize use of multiple channel types.

Note that true DAQmx tasks can only include one type of channel (e.g. AI). To run multiple synchronized reads/writes, we need to make one MiniTask for each type, then use the same sample clock for each.

__init__(*channels)

Create a task that uses the given channels.

Each arg can either be a Channel or a tuple of (Channel, path_str)

abort()

Abort the Task.

This transitions all subtasks to the verified state. See the NI documentation for details on the Task State model.

clear()

Clear the task and release its resources.

This clears all subtasks and releases their resources, aborting them first if necessary.

commit()

Commit the Task.

This transitions all subtasks to the committed state. See the NI documentation for details on the Task State model.

config_digital_edge_trigger(source, edge='rising', n_pretrig_samples=0)

Configure the task to start on a digital edge

You must configure the task’s timing using set_timing() before calling this.

Parameters
  • source (str or Channel) – Terminal of the digital signal to use as the trigger. Note that digital channels may have to be given as a PFI-string, e.g. “PFI3”, rather than in port-line format.

  • edge (EdgeSlope or str) – Trigger slope, either ‘rising’ or ‘falling’

  • n_pretrig_samples (int) – Number of pre-trigger samples to acquire (only works for acquisition). For example, if you’re acquiring 100 samples and n_pretrig_samples is 20, the data will contain 20 samples from right before the trigger, and 80 from right after it.

read(timeout=None)
reserve()

Reserve the Task.

This transitions all subtasks to the reserved state. See the NI documentation for details on the Task State model.

run(write_data=None)

Run a task from start to finish

Writes output data, starts the task, reads input data, stops the task, then returns the input data. Will wait indefinitely for the data to be received. If you need more control, you may instead prefer to use write(), read(), start(), stop(), etc. directly.

set_timing(duration=None, fsamp=None, n_samples=None, mode='finite', edge='rising', clock=None)
start()

Start the Task.

This transitions all subtasks to the running state. See the NI documentation for details on the Task State model.

stop()

Stop the Task and return it to the state it was in before it started.

This transitions all subtasks to the state they in were before they were started, either due to an explicit start() or a call to write() with autostart set to True. See the NI documentation for details on the Task State model.

unreserve()

Unreserve the Task.

This transitions all subtasks to the verified state. See the NI documentation for details on the Task State model.

verify()

Verify the Task.

This transitions all subtasks to the verified state. See the NI documentation for details on the Task State model.

wait_until_done(timeout=None)

Wait until the task is done

write(write_data, autostart=True)

Write data to the output channels.

Useful when you need finer-grained control than run() provides.

property is_done
mx
property product_category
property product_type
property serial
class instrumental.drivers.daq.ni.ProductCategory(value)

An enumeration.

AOSeries = 'AOSeries'
BSeriesDAQ = 'BSeriesDAQ'
CSeriesModule = 'CSeriesModule'
CompactDAQChassis = 'CompactDAQChassis'
DigitalIO = 'DigitalIO'
DynamicSignalAcquisition = 'DynamicSignalAcquisition'
ESeriesDAQ = 'ESeriesDAQ'
MSeriesDAQ = 'MSeriesDAQ'
NIELVIS = 'NIELVIS'
NetworkDAQ = 'NetworkDAQ'
SCCConnectorBlock = 'SCCConnectorBlock'
SCCModule = 'SCCModule'
SCExpress = 'SCExpress'
SCSeriesDAQ = 'SCSeriesDAQ'
SCXIModule = 'SCXIModule'
SSeriesDAQ = 'SSeriesDAQ'
Switches = 'Switches'
TIOSeries = 'TIOSeries'
USBDAQ = 'USBDAQ'
Unknown = 'Unknown'
XSeriesDAQ = 'XSeriesDAQ'
class instrumental.drivers.daq.ni.RelativeTo(value)

An enumeration.

CurrReadPos = 'CurrReadPos'
FirstPretrigSamp = 'FirstPretrigSamp'
FirstSample = 'FirstSample'
MostRecentSamp = 'MostRecentSamp'
RefTrig = 'RefTrig'
class instrumental.drivers.daq.ni.SampleMode(value)

An enumeration.

continuous = 'ContSamps'
finite = 'FiniteSamps'
hwtimed = 'HWTimedSinglePoint'
class instrumental.drivers.daq.ni.TerminalConfig(value)

An enumeration.

NRSE = 'NRSE'
RSE = 'RSE'
default = 'Cfg_Default'
diff = 'Diff'
pseudo_diff = 'PseudoDiff'
class instrumental.drivers.daq.ni.VirtualDigitalChannel(daq, line_pairs)
__init__(daq, line_pairs)
as_input()
as_output()
read(duration=None, fsamp=None, n_samples=None)

Read one or more digital input samples.

  • By default, reads and returns a single sample

  • If only n_samples is given, uses OnDemand (software) timing

  • If two of duration, fsamp, and n_samples are given, uses hardware timing

Parameters
  • duration (Quantity) – How long to read from the digital input, specified as a Quantity. Use with fsamp or n_samples.

  • fsamp (Quantity) – The sample frequency, specified as a Quantity. Use with duration or n_samples.

  • n_samples (int) – The number of samples to read.

Returns

  • data (int or int array) – The data that was read from analog output.

  • For a single-line channel, each sample is a bool. For a multi-line channel, each sample is

  • an int–the lowest bit of the int was read from the first digital line, the second from the

  • second line, and so forth.

write(value)

Write a value to the digital output channel

Parameters

value (int or bool) – An int representing the digital values to write. The lowest bit of the int is written to the first digital line, the second to the second, and so forth. For a single-line DO channel, can be a bool.

write_sequence(data, duration=None, reps=None, fsamp=None, freq=None, onboard=True, clock='')

Write an array of samples to the digital output channel

Outputs a buffered digital waveform, writing each value in sequence at the rate determined by duration and fsamp or freq. You must specify either fsamp or freq.

This function blocks until the output sequence has completed.

Parameters
  • data (array or list of ints or bools) – The sequence of samples to output. For a single-line DO channel, samples can be bools.

  • duration (Quantity, optional) – How long the entirety of the output lasts, specified as a second-compatible Quantity. If duration is longer than a single period of data, the waveform will repeat. Use either this or reps, not both. If neither is given, waveform is output once.

  • reps (int or float, optional) – How many times the waveform is repeated. Use either this or duration, not both. If neither is given, waveform is output once.

  • fsamp (Quantity, optional) – This is the sample frequency, specified as a Hz-compatible Quantity. Use either this or freq, not both.

  • freq (Quantity, optional) – This is the frequency of the overall waveform, specified as a Hz-compatible Quantity. Use either this or fsamp, not both.

  • onboard (bool, optional) – Use only onboard memory. Defaults to True. If False, all data will be continually buffered from the PC memory, even if it is only repeating a small number of samples many times.

property path

Get DAQmx-style name of this channel

type = 'DIO'
Function Generators

Create FunctionGenerator objects using instrument().

Tektronix Function Generators

Driver module for Tektronix function generators. Currently supports:

  • AFG 3000 series

class instrumental.drivers.funcgenerators.tektronix.AFG_3000(inst=None, **kwds)
AM_enabled(channel=1)

Returns whether amplitude modulation is enabled.

Returns

Whether AM is enabled.

Return type

bool

FM_enabled(channel=1)

Returns whether frequency modulation is enabled.

Returns

Whether FM is enabled.

Return type

bool

FSK_enabled(channel=1)

Returns whether frequency-shift keying modulation is enabled.

Returns

Whether FSK is enabled.

Return type

bool

PM_enabled(channel=1)

Returns whether phase modulation is enabled.

Returns

Whether PM is enabled.

Return type

bool

PWM_enabled(channel=1)

Returns whether pulse width modulation is enabled.

Returns

Whether PWM is enabled.

Return type

bool

burst_enabled(channel=1)

Returns whether burst mode is enabled.

Returns

Whether burst mode is enabled.

Return type

bool

disable_AM(channel=1)

Disable amplitude modulation mode.

disable_FM(channel=1)

Disable frequency modulation mode.

disable_FSK(channel=1)

Disable frequency-shift keying mode.

disable_PM(channel=1)

Disable phase modulation mode.

disable_PWM(channel=1)

Disable pulse width modulation mode.

disable_burst(channel=1)

Disable burst mode.

enable_AM(enable=True, channel=1)

Enable amplitude modulation mode.

Parameters

enable (bool, optional) – Whether to enable or disable AM

enable_FM(enable=True, channel=1)

Enable frequency modulation mode.

Parameters

enable (bool, optional) – Whether to enable or disable FM

enable_FSK(enable=True, channel=1)

Enable frequency-shift keying mode.

Parameters

enable (bool, optional) – Whether to enable or disable FSK

enable_PM(enable=True, channel=1)

Enable phase modulation mode.

Parameters

enable (bool, optional) – Whether to enable or disable PM

enable_PWM(enable=True, channel=1)

Enable pulse width modulation mode.

Parameters

enable (bool, optional) – Whether to enable or disable PWM

enable_burst(enable=True, channel=1)

Enable burst mode.

Parameters

enable (bool, optional) – Whether to enable or disable burst mode.

get_dbm(channel=1)

Get the amplitude of the current waveform in dBm.

Note that this returns a float, not a pint.Quantity

Returns

dbm – The current waveform’s dBm amplitude

Return type

float

get_ememory()

Get array of data from edit memory.

Returns

Data retrieved from the AFG’s edit memory.

Return type

numpy.array

get_frequency(channel=1)

Get the frequency to be used in fixed frequency mode.

get_frequency_mode(channel=1)

Get the frequency mode.

Returns

The frequency mode

Return type

‘fixed’ or ‘sweep’

get_vpp(channel=1)

Get the peak-to-peak voltage of the current waveform.

Returns

vpp – The current waveform’s peak-to-peak voltage

Return type

pint.Quantity

get_vrms(channel=1)

Get the RMS voltage of the current waveform.

Returns

vrms – The current waveform’s RMS voltage

Return type

pint.Quantity

set_am_depth(depth, channel=1)

Set depth of amplitude modulation.

Parameters

depth (number) – Depth of modulation in percent. Must be between 0.0% and 120.0%. Has resolution of 0.1%.

set_arb_from_func(func, domain, num_pts=10000, copy_to=None)

Write arbitrary waveform sampled from a function

set_arb_func(data, interp=None, num_pts=10000, copy_to=None)

Write arbitrary waveform data to EditMemory.

Parameters
  • data (array_like) – A 1D array of real values to be used as evenly-spaced points. The values will be normalized to extend from 0 t0 16382. It must have a length in the range [2, 131072]

  • interp (str or int, optional) – Interpolation to use for smoothing out data. None indicates no interpolation. Values include (‘linear’, ‘nearest’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’), or an int to specify the order of spline interpolation. See scipy.interpolate.interp1d for details.

  • num_pts (int) – Number of points to use in interpolation. Default is 10000. Must be greater than or equal to the number of points in data, and at most 131072.

  • copy_to (int (1-4)) – User memory slot into which the function is saved. The data is first transferred into edit memory, then copied to the destination. If None (the default), does not copy into user memory.

set_dbm(dbm, channel=1)

Set the amplitude of the current waveform in dBm.

Note that this returns a float, not a pint.Quantity

Parameters

dbm (float) – The current waveform’s dBm amplitude

set_frequency(freq, change_mode=True, channel=1)

Set the frequency to be used in fixed frequency mode.

Parameters
  • freq (pint.Quantity) – The frequency to be used in fixed frequency mode.

  • change_mode (bool, optional) – If True, will set the frequency mode to fixed.

set_frequency_mode(mode, channel=1)

Set the frequency mode.

In fixed mode, the waveform’s frequency is kept constant. In sweep mode, it is swept according to the sweep settings.

Parameters

mode ({'fixed', 'sweep'}) – Mode to switch to.

set_function(**kwargs)

Set selected function parameters. Useful for setting multiple parameters at once. See individual setters for more details.

When setting the waveform amplitude, you may use up to two of high, low, offset, and vpp/vrms/dbm.

Parameters
  • shape ({'SINusoid', 'SQUare', 'PULSe', 'RAMP', 'PRNoise', 'DC', 'SINC', 'GAUSsian', 'LORentz', 'ERISe', 'EDECay', 'HAVersine', 'USER1', 'USER2', 'USER3', 'USER4', 'EMEMory', 'EFILe'}, optional) – Shape of the waveform. Case-insenitive, abbreviation or full string.

  • phase (pint.Quantity or string or number, optional) – Phase of the waveform in radian-compatible units.

  • vpp (pint.Quantity or string, optional) – Amplitude of the waveform in volt-compatible units.

  • vrms (pint.Quantity or string, optional) – Amplitude of the waveform in volt-compatible units.

  • dbm (pint.Quantity or string, optional) – Amplitude of the waveform in volt-compatible units.

  • offset (pint.Quantity or string, optional) – Offset of the waveform in volt-compatible units.

  • high (pint.Quantity or string, optional) – High level of the waveform in volt-compatible units.

  • low (pint.Quantity or string, optional) – Low level of the waveform in volt-compatible units.

  • channel ({1, 2}, optional) – Output channel to modify. Some models may have only one channel.

set_function_shape(shape, channel=1)

Set shape of output function.

Parameters
  • shape ({'SINusoid', 'SQUare', 'PULSe', 'RAMP', 'PRNoise', 'DC', 'SINC', 'GAUSsian', 'LORentz', 'ERISe', 'EDECay', 'HAVersine', 'USER1', 'USER2', 'USER3', 'USER4', 'EMEMory'}, optional) – Shape of the waveform. Case-insenitive string that contains a valid shape or its abbreviation. The abbreviations are indicated above by capitalization. For example, sin, SINUSOID, and SiN are all valid inputs, while sinus is not.

  • channel ({1, 2}, optional) – Output channel to modify. Some models may have only one channel.

set_high(high, channel=1)

Set the high voltage level of the current waveform.

This changes the high level while keeping the low level fixed.

Parameters

high (pint.Quantity) – The new high level in volt-compatible units

set_low(low, channel=1)

Set the low voltage level of the current waveform.

This changes the low level while keeping the high level fixed.

Parameters

low (pint.Quantity) – The new low level in volt-compatible units

set_offset(offset, channel=1)

Set the voltage offset of the current waveform.

This changes the offset while keeping the amplitude fixed.

Parameters

offset (pint.Quantity) – The new voltage offset in volt-compatible units

set_phase(phase, channel=1)

Set the phase offset of the current waveform.

Parameters

phase (pint.Quantity or number) – The new low level in radian-compatible units. Unitless numbers are treated as radians.

set_sweep(channel=1, **kwargs)

Set selected sweep parameters.

Automatically enables sweep mode.

Parameters
  • start (pint.Quantity) – The start frequency of the sweep in Hz-compatible units

  • stop (pint.Quantity) – The stop frequency of the sweep in Hz-compatible units

  • span (pint.Quantity) – The frequency span of the sweep in Hz-compatible units

  • center (pint.Quantity) – The center frequency of the sweep in Hz-compatible units

  • sweep_time (pint.Quantity) – The sweep time in second-compatible units. Must be between 1 ms and 300 s

  • hold_time (pint.Quantity) – The hold time in second-compatible units

  • return_time (pint.Quantity) – The return time in second-compatible units

  • spacing ({'linear', 'lin', 'logarithmic', 'log'}) – The spacing in time of the sweep frequencies

set_sweep_center(center, channel=1)

Set the sweep frequency center.

This sets the sweep center frequency while keeping the sweep frequency span fixed. The start and stop frequencies will be changed.

Parameters

center (pint.Quantity) – The center frequency of the sweep in Hz-compatible units

set_sweep_hold_time(time, channel=1)

Set the hold time of the sweep.

The hold time is the amount of time that the frequency is held constant after reaching the stop frequency.

Parameters

time (pint.Quantity) – The hold time in second-compatible units

set_sweep_return_time(time, channel=1)

Set the return time of the sweep.

The return time is the amount of time that the frequency spends sweeping from the stop frequency back to the start frequency. This does not include hold time.

Parameters

time (pint.Quantity) – The return time in second-compatible units

set_sweep_spacing(spacing, channel=1)

Set whether a sweep is linear or logarithmic.

Parameters

spacing ({'linear', 'lin', 'logarithmic', 'log'}) – The spacing in time of the sweep frequencies

set_sweep_span(span, channel=1)

Set the sweep frequency span.

This sets the sweep frequency span while keeping the center frequency fixed. The start and stop frequencies will be changed.

Parameters

span (pint.Quantity) – The frequency span of the sweep in Hz-compatible units

set_sweep_start(start, channel=1)

Set the sweep start frequency.

This sets the start frequency while keeping the stop frequency fixed. The span and center frequencies will be changed.

Parameters

start (pint.Quantity) – The start frequency of the sweep in Hz-compatible units

set_sweep_stop(stop, channel=1)

Set the sweep stop frequency.

This sets the stop frequency while keeping the start frequency fixed. The span and center frequencies will be changed.

Parameters

stop (pint.Quantity) – The stop frequency of the sweep in Hz-compatible units

set_sweep_time(time, channel=1)

Set the sweep time.

The sweep time does not include hold time or return time. Sweep time must be between 1 ms and 300 s.

Parameters

time (pint.Quantity) – The sweep time in second-compatible units. Must be between 1 ms and 200 s

set_vpp(vpp, channel=1)

Set the peak-to-peak voltage of the current waveform.

Parameters

vpp (pint.Quantity) – The new peak-to-peak voltage

set_vrms(vrms, channel=1)

Set the amplitude of the current waveform in dBm.

Parameters

vrms (pint.Quantity) – The new RMS voltage

sweep_enabled(channel=1)

Whether the frequency mode is sweep.

Just a convenience method to avoid writing get_frequency_mode() == 'sweep'.

Returns

Whether the frequency mode is sweep

Return type

bool

trigger()

Manually force a trigger event

amplitude
high
low
offset
instrumental.drivers.funcgenerators.tektronix.VoltageFacet(msg, readonly=False, **kwds)
instrumental.drivers.funcgenerators.tektronix.infer_termination(msg_str)
Lasers

Create Laser objects using instrument().

Toptica FemtoFErb 1560 Laser

Driver for Tobtica FemtoFiber Lasers.

The femtofiber drivers, which among other things make the usb connection appear as a serial port, must be installed (available from http://www.toptica.com/products/ultrafast_fiber_lasers/femtofiber_smart/femtosecond_erbium_fiber_laser_1560_nm_femtoferb)

class instrumental.drivers.lasers.femto_ferb.FemtoFiber(inst=None, **kwds)

A femtoFiber laser.

Lasers can only be accessed by their serial port address.

close()

Closes the connection to the laser.

is_control_on()

Returns the status of the hardware input control.

Hardware input control must be on in order for the laser to be controlled by usb connection.

Returns

message – If True, hardware input conrol is on.

Return type

bool

is_on()

Indicates if the laser is on (True) or off (False).

set_control(control)

Sets the status of the hardware input control.

Hardware input control must be on in order for the laser to be controlled by usb connection.

Parameters

control (bool) – If True, hardware input conrol is turned on.

Returns

error – Zero is returned if the hardware input control status was set correctly. Otherwise, the error string returned by the laser is returned.

Return type

int or str

turn_off()

Turns the laser off.

Note that hardware control must be enabled in order for this method to execute properly.

Returns

error – Zero is returned if the laser was correctly turned off. Otherwise, the error string returned by the laser is returned.

Return type

int or str

turn_on()

Turns the laser on.

Note that hardware control must be enabled in order for this method to execute properly.

Returns

error – Zero is returned if the laser was successfuly turned on. Otherwise, the error string returned by the laser is returned.

Return type

int or str

Lock-in Amplifiers

Create Lockin objects using instrument().

SRS Model SR850 Lock-in Amplifier

Driver for SRS model SR850 lock-in amplifier.

Note that the sr850 will not usually work with list_instruments(), because it uses a non-standard termination character, and because one must first send the ‘OUTX’ command to specify which type of output to use.

class instrumental.drivers.lockins.sr850.AlarmMode(value)

An enumeration.

off = 0
on = 1
class instrumental.drivers.lockins.sr850.AuxInput(value)

An enumeration.

four = 4
one = 1
three = 3
two = 2
class instrumental.drivers.lockins.sr850.Ch1OutputSource(value)

An enumeration.

R = 1
X = 0
theta = 2
trace_1 = 3
trace_2 = 4
trace_3 = 5
trace_4 = 6
class instrumental.drivers.lockins.sr850.Ch2OutputSource(value)

An enumeration.

R = 1
Y = 0
theta = 2
trace_1 = 3
trace_2 = 4
trace_3 = 5
trace_4 = 6
class instrumental.drivers.lockins.sr850.CurrentGain(value)

An enumeration.

oneHundredMegaOhm = 1
oneMegaOhm = 0
class instrumental.drivers.lockins.sr850.Divide(value)

An enumeration.

AuxIn1 = 8
AuxIn1_2 = 20
AuxIn2 = 9
AuxIn2_2 = 21
AuxIn3 = 10
AuxIn3_2 = 22
AuxIn4 = 11
AuxIn4_2 = 23
F = 12
F_2 = 24
R = 3
R_2 = 15
R_n = 7
Rn_2 = 19
X = 1
X_2 = 13
X_n = 5
Xn_2 = 17
Y = 2
Y_2 = 14
Y_n = 6
Yn_2 = 18
theta = 4
theta_2 = 16
unity = 0
class instrumental.drivers.lockins.sr850.InputConfiguration(value)

An enumeration.

A = 0
A_B = 1
I = 2
class instrumental.drivers.lockins.sr850.InputCoupling(value)

An enumeration.

AC = 0
DC = 1
class instrumental.drivers.lockins.sr850.InputGround(value)

An enumeration.

floating = 0
ground = 1
class instrumental.drivers.lockins.sr850.LineFilter(value)

An enumeration.

both_filters = 3
line_2x_notch = 2
line_notch = 1
no_filters = 0
class instrumental.drivers.lockins.sr850.LowPassSlope(value)

An enumeration.

eighteen_dB_per_octave = 2
six_dB_per_octave = 0
twelve_dB_per_octave = 1
twentyfour_dB_per_octave = 3
class instrumental.drivers.lockins.sr850.Multiply(value)

An enumeration.

AuxIn1 = 8
AuxIn2 = 9
AuxIn3 = 10
AuxIn4 = 11
F = 12
R = 3
R_n = 7
X = 1
X_n = 5
Y = 2
Y_n = 6
theta = 4
unity = 0
class instrumental.drivers.lockins.sr850.OffsetSelector(value)

An enumeration.

R = 3
X = 1
Y = 2
class instrumental.drivers.lockins.sr850.OutputType(value)

An enumeration.

R = 3
X = 1
Y = 2
theta = 4
class instrumental.drivers.lockins.sr850.Parameter(value)

An enumeration.

units()
AuxIn_1 = 5
AuxIn_2 = 6
AuxIn_3 = 7
AuxIn_4 = 8
R = 3
X = 1
Y = 2
reference_frequency = 9
theta = 4
trace_1 = 10
trace_2 = 11
trace_3 = 12
trace_4 = 13
class instrumental.drivers.lockins.sr850.ReferenceSlope(value)

An enumeration.

sine_zero = 0
ttl_falling = 2
ttl_rising = 1
class instrumental.drivers.lockins.sr850.ReferenceSource(value)

An enumeration.

external = 2
internal = 0
internal_sweep = 1
class instrumental.drivers.lockins.sr850.ReserveMode(value)

An enumeration.

manual = 1
maximum = 0
minimum = 2
class instrumental.drivers.lockins.sr850.SR850

Interfaces with the SRS model SR850 Lock-in Amplifier

class AlarmMode(value)

An enumeration.

off = 0
on = 1
class AuxInput(value)

An enumeration.

four = 4
one = 1
three = 3
two = 2
class Ch1OutputSource(value)

An enumeration.

R = 1
X = 0
theta = 2
trace_1 = 3
trace_2 = 4
trace_3 = 5
trace_4 = 6
class Ch2OutputSource(value)

An enumeration.

R = 1
Y = 0
theta = 2
trace_1 = 3
trace_2 = 4
trace_3 = 5
trace_4 = 6
class CurrentGain(value)

An enumeration.

oneHundredMegaOhm = 1
oneMegaOhm = 0
class Divide(value)

An enumeration.

AuxIn1 = 8
AuxIn1_2 = 20
AuxIn2 = 9
AuxIn2_2 = 21
AuxIn3 = 10
AuxIn3_2 = 22
AuxIn4 = 11
AuxIn4_2 = 23
F = 12
F_2 = 24
R = 3
R_2 = 15
R_n = 7
Rn_2 = 19
X = 1
X_2 = 13
X_n = 5
Xn_2 = 17
Y = 2
Y_2 = 14
Y_n = 6
Yn_2 = 18
theta = 4
theta_2 = 16
unity = 0
class InputConfiguration(value)

An enumeration.

A = 0
A_B = 1
I = 2
class InputCoupling(value)

An enumeration.

AC = 0
DC = 1
class InputGround(value)

An enumeration.

floating = 0
ground = 1
class LineFilter(value)

An enumeration.

both_filters = 3
line_2x_notch = 2
line_notch = 1
no_filters = 0
class LowPassSlope(value)

An enumeration.

eighteen_dB_per_octave = 2
six_dB_per_octave = 0
twelve_dB_per_octave = 1
twentyfour_dB_per_octave = 3
class Multiply(value)

An enumeration.

AuxIn1 = 8
AuxIn2 = 9
AuxIn3 = 10
AuxIn4 = 11
F = 12
R = 3
R_n = 7
X = 1
X_n = 5
Y = 2
Y_n = 6
theta = 4
unity = 0
class OffsetSelector(value)

An enumeration.

R = 3
X = 1
Y = 2
class OutputType(value)

An enumeration.

R = 3
X = 1
Y = 2
theta = 4
class Parameter(value)

An enumeration.

units()
AuxIn_1 = 5
AuxIn_2 = 6
AuxIn_3 = 7
AuxIn_4 = 8
R = 3
X = 1
Y = 2
reference_frequency = 9
theta = 4
trace_1 = 10
trace_2 = 11
trace_3 = 12
trace_4 = 13
class ReferenceSlope(value)

An enumeration.

sine_zero = 0
ttl_falling = 2
ttl_rising = 1
class ReferenceSource(value)

An enumeration.

external = 2
internal = 0
internal_sweep = 1
class ReserveMode(value)

An enumeration.

manual = 1
maximum = 0
minimum = 2
class ScanMode(value)

An enumeration.

loop = 1
single_shot = 0
class ScanSampleRate(value)

An enumeration.

trigger = 14
x125mHz = 1
x128Hz = 11
x16Hz = 8
x1Hz = 4
x250mHz = 2
x256Hz = 12
x2Hz = 5
x32Hz = 9
x4Hz = 6
x500mHz = 3
x512Hz = 13
x62_5mHz = 0
x64Hz = 10
x8Hz = 7
class Sensitivity(value)

An enumeration.

x100mV_nA = 23
x100nV_fA = 5
x100uV_pA = 14
x10mV_nA = 20
x10nV_fA = 2
x10uV_pA = 11
x1V_uA = 26
x1mV_nA = 17
x1uV_pA = 8
x200mV_nA = 24
x200nV_fA = 6
x200uV_pA = 15
x20mV_nA = 21
x20nV_fA = 3
x20uV_pA = 12
x2mV_nA = 18
x2nV_fA = 0
x2uV_pA = 9
x500mV_nA = 25
x500nV_fA = 7
x500uV_pA = 16
x50mV_nA = 22
x50nV_fA = 4
x50uV_pA = 13
x5mV_nA = 19
x5nV_fA = 1
x5uV_pA = 10
class StatusByte(value)

An enumeration.

enabled_bit_in_LIA_status_set = 3
enabled_bit_in_error_status_set = 2
enabled_bit_in_standard_status_set = 5
no_command_execution_in_progress = 1
no_scan_in_progress = 0
output_buffer_non_empty = 4
service_request = 6
class Store(value)

An enumeration.

not_stored = 0
stored = 1
class SweepType(value)

An enumeration.

linear = 0
logarithmic = 1
class SynchronousFilter(value)

An enumeration.

off = 0
on = 1
class TimeConstant(value)

An enumeration.

x100ms = 8
x100s = 14
x100us = 2
x10ks = 18
x10ms = 6
x10s = 12
x10us = 0
x1ks = 16
x1ms = 4
x1s = 10
x300ms = 9
x300s = 15
x300us = 3
x30ks = 19
x30ms = 7
x30s = 13
x30us = 1
x3ks = 17
x3ms = 5
x3s = 11
class TraceNumber(value)

An enumeration.

four = 4
one = 1
three = 3
two = 2
class TriggerStartScanMode(value)

An enumeration.

no = 0
yes = 1
auto_gain()

Performs the auto-gain function.

Note that this function does not return until the process has completed.

auto_offset(offset_selector)

Automatically offsets the selected quadrature to zero

offset_selector should be of type OffsetSelector

auto_phase()

Performs the auto-phase function

auto_reserve()

Performs the auto-reserve function

clear_registers()

Clears all status registers, except for status enable registers.

close()
command_execution_in_progress()

Indicates if a command is currently being executed.

get_alarm_mode()

Returns whether the audible alarm is on or off.

get_aux_in(aux_in)

Returns the voltage of the specified auxiliary input.

aux_in should be of type AuxIn

get_ch1_output_source()

Returns the output source for channel 1.

get_ch2_output_source()

Returns the output source for channel 2.

get_current_gain()

Returns the conversion gain of the input current

get_detection_harmonic()

Returns the detection harmonic

get_frequency_sweep_type()

Returns whether a sweep is linear or logarithmic.

Returns type SweepType

get_input_configuration()

Returns the input configuration.

get_input_coupling()

Returns the input coupling mode

get_input_ground()

Returns the input shield grounding mode.

get_line_filter_status()

Returns the configuratin of the line filters

get_low_pass_slope()

Returns the slope of the low pass filter.

get_output_offsets_and_expands(offset_selector)

Returns the offsets and expands for the selected quadrature.

offset_selector should be of type OffsetSelector

get_reference_frequency()

Returns the frequency of the reference source

get_reference_phase()

Returns the phase shift of the reference source

get_reference_slope()

Returns the mode with wich the reference source is discriminated.

get_reference_source()

Returns the source used for the reference frequency.

Returns type ReferenceSource

get_reserve()

Returns the current value of the dynamic reserve.

get_reserve_mode()

Returns the reserve mode of the instrument.

get_scan_length()

Returns the scan length.

get_scan_mode()

Returns the scan mode.

get_scan_sample_rate()

Sets the sampling rate of a scan.

get_sensitivity()

Returns the sensitivity setting of the instrument

get_sine_amplitude()

Returns the amplitude of the sine output, in Volts.

get_start_frequency()

Returns the freqeuncy that a sweep starts at

get_stop_frequency()

Returns the frequency that a sweep stops at

get_synchronous_filter()

Returns the state of the synchronous filter.

get_time_constant()

Get the current time constant of the instrument.

get_trace(trace_number, points=None, units=None, binary=True)

Returns a vector of the values stored in the indicated trace

If get_trace times out while transferring data, the constants BINARY_TIME_PER_POINT and or ASCII_TIME_PER_POINT may need to be increased

Parameters
  • TraceNumber. (trace_number should be an element of) –

  • position (points is a list of two integers - the first indicates the) –

  • read (of the first value to be) –

  • number (while the second indicates the) –

  • default (of values to be read. By) –

  • read. (all points are) –

  • trace (units - string indicating the proper units of the) –

  • binary (binary - boolean indicating the method of data transfer. Using) –

  • faster. (is usually about 4 times) –

get_trace_definitions(trace)

Returns the definition of the given trace.

Trace should be an enumerator of Trace. The trace definition is of the form m1*m2/d

Returns

  • m1, m2 of type Multiply

  • d of type Divide

  • store of type Store

get_trigger_start_scan_mode()

Returns the mode in which the trigger initiates a scan.

pause_scan()

Pauses a scan or sweep.

Has no effect is no scans are in progress.

read_aux_input(aux_input)

Returns the value of the specified aux input

use type AuxInput

read_output(output_type)

Returns the value of the indicated output

use type OutputType.

read_simultaneously(parameters)

Returns simultaneously the values of the given parameters

the list parameters should have between two and six elements of the type Parameter

read_trace_value(trace_number, units=None)

Returns the current value of indicated trace

trace_number should be of enumeraror class TraceNumber. If specified, units are added to the returned value. units should be a string.

reset_scan()

Resets a scan.

This works whether a scan is in progress, finished, or paused. Note that the data buffer is erased.

scan_in_progress()

Indicates if a scan is in progress.

Note that a paused scan is counted as being in progress

set_alarm_mode(alarm_mode)

Sets the audible alarm on or off.

Use the enumerator AlarmMode.

set_ch1_output_source(ch1_output_source)

Sets the output source for channel 1.

Use the enumerator Ch1OutputSource.

set_ch2_output_source(ch2_output_source)

Sets the output source for channel 2

Use the enumerator Ch2OutputSource.

set_current_gain(current_gain)

Sets the conversion gain of the input current.

Use the enumerator CurrentGain

set_detection_harmonic(harmonic)

Sets the detection harmonic

set_frequency_sweep_type(sweep_type)

Sets whether a sweep is linear or logarithmic.

sweep_type should be of type SweepType

set_input_configuration(input_configuration)

Sets the input configuration.

input_conusing should be of type InputConfiguration

set_input_coupling(input_coupling)

Sets the input coupling mode Use the enumerator InputCoupling

set_input_ground(input_ground)

Sets the input shield grounding mode.

Use the enumerator InputGround

set_line_filter_status(line_filter)

Sets the configuration of the line filters.

Use the enumerator LineFilter

set_low_pass_slope(low_pass_slope)

Sets the slope for the low pass filter

Use the enumerator LowPassSlope

set_output_interface(rs232_interface=True)

Sets the output interface.

Default is serial (rs232_interface=True) Set rs232_interface to False for GPIB

set_output_offsets_and_expands(offset_selector, offset, expand)

Sets the offsets and expands for the selected quadrature.

offset_selector is of type OffsetSelector, and indicates which quadrature to use. Note that offset_selector should be in percent, and expand should be an integer between 1 and 256

set_reference_frequency(frequency)

Sets the frequency of the reference source

set_reference_phase(phase)

Sets the phase shift of the reference source

set_reference_slope(reference_slope)

Sets the mode with wich the reference source is discriminated.

This is only relevant when an external source is used.

reference_slope should be of type ReferenceSlope

set_reference_source(reference_source)

Sets the source used for the reference frequency.

reference_source should be of type ReferenceSource

set_reserve(reserve)

Sets the manual dynamic reserve.

Reserve should be an integer between 0 and 5, inclusive. 0 sets the minimum reserve for the current time constant and sensitivity. Each increment increases the reserve by 10dB.

set_reserve_mode(reserve_mode)

Sets the reserve mode of the instrument

Use the enumerator ReserveMode

set_scan_length(scan_length)

Sets the scan length.

set_scan_mode(scan_mode)

Sets the scan mode. Use the enumerator ScanMode.

set_scan_sample_rate(scan_sample_rate)

Sets the sampling rate of a scan.

Use the enumerator ScanSampleRate.

set_sensitivity(sensitivity)

Sets the sensitivity of the instrument.

Use the enumerator Sensitivity

set_sine_amplitude(amplitude)

Sets the amplitude of the sine output.

Must be between 0.004 and 5V. (Rounds to nearest 0.002 V)

set_start_frequency(frequency)

Sets the frequency a sweep starts at

set_stop_frequency(frequency)

Sets the frequency that a sweep stops at

set_synchronous_filter(synchronous_filter)

Sets the state of the synchronous filter.

Use the enumerator SynchronousFilter. Note that the synchronous filter only operates if the detection frequency is below 200Hz

set_time_constant(time_constant)

Sets the time constant of the instrument

Use the enumerator TimeConstant

set_trace_definitions(trace, m1, m2=Multiply.unity, d=Divide.unity, store=Store.stored)

Sets the definition of the given trace ‘trace’ to be m1*m2/d.

Trace should be an enumerator of Trace, m1 and m2 should be enumerators of Multiply, d should be an enumerator of Divide, and store should be an enumerator of Store.

set_trigger_start_scan_mode(trigger_start_scan_mode)

Sets the mode in which the trigger initiates a scan.

Use the enumerator TriggerStartScanMode.

start_scan()

Starts or resumes a scan/sweep.

Has no effect if a scan is already in progress.

trace_length(trace_number)

Returns the number of points in the specified trace

use the enumerator TraceNumber

trigger()

Initiates a trigger event.

class instrumental.drivers.lockins.sr850.ScanMode(value)

An enumeration.

loop = 1
single_shot = 0
class instrumental.drivers.lockins.sr850.ScanSampleRate(value)

An enumeration.

trigger = 14
x125mHz = 1
x128Hz = 11
x16Hz = 8
x1Hz = 4
x250mHz = 2
x256Hz = 12
x2Hz = 5
x32Hz = 9
x4Hz = 6
x500mHz = 3
x512Hz = 13
x62_5mHz = 0
x64Hz = 10
x8Hz = 7
class instrumental.drivers.lockins.sr850.Sensitivity(value)

An enumeration.

x100mV_nA = 23
x100nV_fA = 5
x100uV_pA = 14
x10mV_nA = 20
x10nV_fA = 2
x10uV_pA = 11
x1V_uA = 26
x1mV_nA = 17
x1uV_pA = 8
x200mV_nA = 24
x200nV_fA = 6
x200uV_pA = 15
x20mV_nA = 21
x20nV_fA = 3
x20uV_pA = 12
x2mV_nA = 18
x2nV_fA = 0
x2uV_pA = 9
x500mV_nA = 25
x500nV_fA = 7
x500uV_pA = 16
x50mV_nA = 22
x50nV_fA = 4
x50uV_pA = 13
x5mV_nA = 19
x5nV_fA = 1
x5uV_pA = 10
class instrumental.drivers.lockins.sr850.StatusByte(value)

An enumeration.

enabled_bit_in_LIA_status_set = 3
enabled_bit_in_error_status_set = 2
enabled_bit_in_standard_status_set = 5
no_command_execution_in_progress = 1
no_scan_in_progress = 0
output_buffer_non_empty = 4
service_request = 6
class instrumental.drivers.lockins.sr850.Store(value)

An enumeration.

not_stored = 0
stored = 1
class instrumental.drivers.lockins.sr850.SweepType(value)

An enumeration.

linear = 0
logarithmic = 1
class instrumental.drivers.lockins.sr850.SynchronousFilter(value)

An enumeration.

off = 0
on = 1
class instrumental.drivers.lockins.sr850.TimeConstant(value)

An enumeration.

x100ms = 8
x100s = 14
x100us = 2
x10ks = 18
x10ms = 6
x10s = 12
x10us = 0
x1ks = 16
x1ms = 4
x1s = 10
x300ms = 9
x300s = 15
x300us = 3
x30ks = 19
x30ms = 7
x30s = 13
x30us = 1
x3ks = 17
x3ms = 5
x3s = 11
class instrumental.drivers.lockins.sr850.TraceNumber(value)

An enumeration.

four = 4
one = 1
three = 3
two = 2
class instrumental.drivers.lockins.sr850.TriggerStartScanMode(value)

An enumeration.

no = 0
yes = 1
Motion Control

Create Motion objects using instrument().

Thorlabs Motorized Filter Flip Mount (MFF10X)

Driver for controlling Thorlabs Flipper Filters using the Kinesis SDK.

One must place Thorlabs.MotionControl.DeviceManager.dll and Thorlabs.MotionControl.FilterFlipper.dll in the path

exception instrumental.drivers.motion.filter_flipper.FilterFlipperError
class instrumental.drivers.motion.filter_flipper.Filter_Flipper(inst=None, **kwds)

Driver for controlling Thorlabs Filter Flippers

The polling period, which is how often the device updates its status, is passed as a pint quantity with units of time and is optional argument, with a default of 200ms

close()
flip()

Flips the position of the filter.

get_position()

Get the position of the flipper.

Returns an instance of Position. Note that this represents the position at the most recent polling event.

get_transit_time()

Returns the transit time.

The transit time is the time to transition from one filter position to the next.

home()

Homes the device

isValidPosition(position)

Indicates if it is possible to move to the given position.

Parameters

position (instance of Position) –

move_and_wait(position, delay='100ms')

Moves to the indicated position and waits until that position is reached.

Parameters
  • position (instance of Position) – should not be ‘Position.moving’

  • delay (pint quantity with units of time) – the period with which the position of the flipper is checked.

move_to(position)

Moves the flipper to the indicated position.

Returns immediatley.

Parameters

position (instance of Position) – should not be ‘Position.moving’

set_transit_time(transit_time='500ms')

Sets the transit time. The transit time is the time to transition from one filter position to the next.

Parameters

transit_time (pint quantity with units of time) –

class instrumental.drivers.motion.filter_flipper.Position(value)

The position of the flipper.

moving = 0
one = 1
two = 2
instrumental.drivers.motion.filter_flipper.list_instruments()
Thorlabs K10CR1 Rotation Stage

Driver for controlling Thorlabs Kinesis devices.

T-Cube DC Servo Motor Controller (TDC001)

Driver for controlling Thorlabs TDC001 T-Cube DC Servo Motor Controllers using the Kinesis SDK.

One must place Thorlabs.MotionControl.DeviceManager.dll and Thorlabs.MotionControl.TCube.DCServo.dll in the path.

exception instrumental.drivers.motion.tdc_001.TDC001Error
class instrumental.drivers.motion.tdc_001.SoftwareApproachPolicy(value)

An enumeration.

AllowAll = 3
DisableFarOutsideRange = 1
DisableOutsideRange = 0
TruncateBeyondLimit = 2
class instrumental.drivers.motion.tdc_001.TDC001(inst=None, **kwds)

Controlling Thorlabs TDC001 T-Cube DC Servo Motor Controllers

The polling period, which is how often the device updates its status, is passed as a pint pint quantity with units of time and is optional argument, with a default of 200ms

class Status(status_bits)

Stores information about the status of the device from the status bits.

__init__(status_bits)
at_position(position, tol=1)

Indicates whether the motor is at the given position.

Parameters
  • position (motor is considered to be at) –

  • tol (int representing the number of encoder counts within which the) –

  • position

close()
get_motor_params()

Gets the stage motor parameters.

Returns

  • (steps_per_rev (int,)

  • gearbox_ratio (int,)

  • pitch (float))

get_position()

Returns the position of the motor.

Note that this represents the position at the most recent polling event.

get_status()

Returns the status registry bits from the device.

home()

Homes the device

move_and_wait(position, delay='100ms', tol=1)

Moves to the indicated position and waits until that position is reached.

Parameters
  • position (pint quantity of units self.real_world_units) –

  • delay (pint quantity with units of time) – the period with which the position of the motor is checked.

  • tol (int) – the tolerance, in encoder units, to which the motor is considered at position

move_to(position)

Moves to the indicated position

Returns immediately.

Parameters

position (pint quantity of units self.real_world_units) –

set_motor_params(steps_per_rev, gearbox_ratio, pitch)

Sets the motor stage parameters.

Parameters
  • steps_per_rev (int) –

  • gearbox_ratio (int) –

  • pitch (float) –

class instrumental.drivers.motion.tdc_001.TravelMode(value)

An enumeration.

Linear = 1
Rotational = 2
instrumental.drivers.motion.tdc_001.initialize_simulations()

Initialize a connection to the Simulation Manager, which must already be running.

Can be called multiple times. For a simulated device to show up in list_instruments, this function must be called after the device’s creation.

This function automatically registers uninitialize_simulations to be called upon program exit.

instrumental.drivers.motion.tdc_001.list_instruments()
instrumental.drivers.motion.tdc_001.uninitialize_simulations()

Release the connection to the Simulation Manager.

This function is automatically registered by initialize_simulations to be called upon program exit.

Attocube ECC100 Motion Controller
Multimeters

Create Multimeter objects using instrument().

HP 34401A Multimeter

Driver module for HP/Agilent 34401A multimeters.

exception instrumental.drivers.multimeters.hp.MultimeterError
class instrumental.drivers.multimeters.hp.HPMultimeter(inst=None, **kwds)
clear()
config_voltage_dc(range=None, resolution=None)

Configure the multimeter to perform a DC voltage measurement.

Parameters
  • range (Quantity, 'min', 'max', or 'def') – Expected value of the input signal

  • resolution (Quantity, 'min', 'max', or 'def') – Resolution of the measurement, in measurement units

fetch()
initiate()
trigger()

Emit the software trigger

To use this function, the device must be in the ‘wait-for-trigger’ state and its trigger_source must be set to ‘bus’.

property npl_cycles
property trigger_source
class instrumental.drivers.multimeters.hp.TriggerSource(value)

An enumeration.

bus = 0
ext = 2
external = 2
imm = 1
immediate = 1
Power Meters

Create PowerMeter objects using instrument().

Newport Power Meters

Driver module for Newport power meters. Supports:

  • 1830-C

For example, suppose a power meter is connected on port COM1. One can then connect and measure the power using the following sequence:

>>> from instrumental import instrument
>>> newport_power_meter = instrument(visa_address='COM1',
                                     classname='Newport_1830_C',
                                     module='powermeters.newport')
>>> newport_power_meter.power
<Quantity(3.003776, 'W')>
class instrumental.drivers.powermeters.newport.Newport_1830_C(inst=None, **kwds)

A Newport 1830-C power meter

attenuator_enabled(**kwds)
close()
disable_attenuator(**kwds)
disable_auto_range()

Disable auto-range

Leaves the signal range at its current position.

disable_hold()

Disable hold mode

disable_zero()

Disable the zero function

enable_attenuator(**kwds)
enable_auto_range()

Enable auto-range

enable_hold(enable=True)

Enable hold mode

enable_zero(enable=True)

Enable the zero function

When enabled, the next power reading is stored as a background value and is subtracted off of all subsequent power readings.

get_filter()

Get the current setting for the averaging filter

Returns

the current averaging filter

Return type

SLOW_FILTER, MEDIUM_FILTER, NO_FILTER

get_power(**kwds)
get_range(**kwds)
get_status_byte(**kwds)
get_units()

Get the units used for displaying power measurements

Returns

units – ‘watts’, ‘db’, ‘dbm’, or ‘rel’

Return type

str

get_valid_power(max_attempts=10, polling_interval=<Quantity(0.1, 'second')>)

Returns a valid power reading

This convience function will try to measure a valid power up to a maximum of max_attempts times, pausing for time polling_interval between each attempt. If a power reading is taken when the power meter is over-range, saturated, or busy, the reading will be invalid. In practice, this function also seems to mitigate the fact that about 1 in 500 power readings mysteriously fails.

Parameters
  • max_attempts (integer) – maximum number of attempts to measure a valid power

  • polling_interval (Quantity) – time to wait between measurement attemps, in units of time

Returns

power – Power in units of watts, regardless of the power meter’s current ‘units’ setting.

Return type

Quantity

get_wavelength(**kwds)
hold_enabled()

Whether hold mode is enabled

Returns

enabled – True if in hold mode, False if in run mode

Return type

bool

is_measurement_valid()

Whether the current measurement is valid

The measurement is considered invalid if the power meter is saturated, over-range or busy.

set_medium_filter()

Set the averaging filter to medium mode

The medium filter uses a 4-measurement running average.

set_no_filter()

Set the averaging filter to fast mode, i.e. no averaging

set_range(**kwds)
set_slow_filter()

Set the averaging filter to slow mode

The slow filter uses a 16-measurement running average.

set_units(units)

Set the units for displaying power measurements

The different unit modes are watts, dB, dBm, and REL. Each displays the power in a different way.

‘watts’ displays absolute power in watts

‘dBm’ displays power in dBm (i.e. dBm = 10 * log(P / 1mW))

‘dB’ displays power in dB relative to the current reference power (i.e. dB = 10 * log(P / Pref). At power-up, the reference power is set to 1mW.

‘REL’ displays power relative to the current reference power (i.e. REL = P / Pref)

The current reference power can be set using store_reference().

Parameters

units ('watts', 'dBm', 'dB', or 'REL') – Case-insensitive str indicating which units mode to enter.

set_wavelength(**kwds)
store_reference()

Store the current power input as a reference

Sets the current power measurement as the reference power for future dB or relative measurements.

zero_enabled()

Whether the zero function is enabled

MEDIUM_FILTER = 2
NO_FILTER = 3
SLOW_FILTER = 1
attenuator

Whether the attenuator is enabled

property local_lockout

Whether local-lockout is enabled

power

Get the current power measurement

Returns

power – Power in units of watts, regardless of the power meter’s current ‘units’ setting.

Return type

Quantity

range

The current input range, [1-8], where 1 is lowest signal.

status_byte
wavelength
instrumental.drivers.powermeters.newport.MyFacet(msg, readonly=False, **kwds)

Like SCPI_Facet, but without a space before the set-value

Thorlabs Power Meters

Driver module for Thorlabs power meters. Supports:

  • PM100D

class instrumental.drivers.powermeters.thorlabs.PM100D(inst=None, **kwds)

A Thorlabs PM100D series power meter

auto_range_enabled(**kwds)
close()
disable_auto_range(**kwds)
enable_auto_range(**kwds)
get_num_averaged(**kwds)
get_power(**kwds)
get_range(**kwds)
get_wavelength(**kwds)
measure(n_samples=100)

Make a multi-sample power measurement

Parameters

n_samples (int) – Number of samples to take

Returns

Measured power, with units and uncertainty, as a pint.Measurement object

Return type

pint.Measurement

set_num_averaged(**kwds)
set_wavelength(**kwds)
auto_range

Whether auto-ranging is enabled

num_averaged

Number of samples to average

power

The measured optical power

range

The current input range’s max power

wavelength

Input signal wavelength

Scopes

Create Scope objects using instrument().

Tektronix Oscilloscopes

Driver module for Tektronix oscilloscopes.

exception instrumental.drivers.scopes.tektronix.ClippingError
class instrumental.drivers.scopes.tektronix.Channel(scope, num)
__init__(scope, num)
offset
position
scale
class instrumental.drivers.scopes.tektronix.ChannelProperty
__init__()
facet(facet)
class instrumental.drivers.scopes.tektronix.Channels(scope, facets)
__init__(scope, facets)
class instrumental.drivers.scopes.tektronix.MSO_DPO_2000(inst=None, **kwds)

A Tektronix MSO/DPO 2000 series oscilloscope.

set_min_window(width)
ch
property datetime
max_waveform_length = 1250000
meas
waveform_length

Record length of the source waveform

class instrumental.drivers.scopes.tektronix.MSO_DPO_3000(inst=None, **kwds)

A Tektronix MSO/DPO 3000 series oscilloscope.

property datetime
class instrumental.drivers.scopes.tektronix.MSO_DPO_4000(inst=None, **kwds)

A Tektronix MSO/DPO 4000 series oscilloscope.

property datetime
class instrumental.drivers.scopes.tektronix.StatScope(inst=None, **kwds)
are_measurement_stats_on()

Returns whether measurement statistics are currently enabled

disable_measurement_stats()

Disables measurement statistics

enable_measurement_stats(enable=True)

Enables measurement statistics.

When enabled, measurement statistics are kept track of, including ‘mean’, ‘stddev’, ‘minimum’, ‘maximum’, and ‘nsamps’.

Parameters

enable (bool) – Whether measurement statistics should be enabled

set_measurement_nsamps(nsamps)

Sets the number of samples used to compute measurements.

Parameters

nsamps (int) – Number of samples used to compute measurements

class instrumental.drivers.scopes.tektronix.TDS_1000(inst=None, **kwds)

A Tektronix TDS 1000 series oscilloscope

class instrumental.drivers.scopes.tektronix.TDS_200(inst=None, **kwds)

A Tektronix TDS 200 series oscilloscope

class instrumental.drivers.scopes.tektronix.TDS_2000(inst=None, **kwds)

A Tektronix TDS 2000 series oscilloscope

class instrumental.drivers.scopes.tektronix.TDS_3000(inst=None, **kwds)

A Tektronix TDS 3000 series oscilloscope.

property datetime
max_waveform_length = 10000
waveform_length

Record length of the source waveform

class instrumental.drivers.scopes.tektronix.TDS_7000(inst=None, **kwds)

A Tektronix TDS 7000 series oscilloscope

max_waveform_length = 500000
waveform_length

Record length of the source waveform

class instrumental.drivers.scopes.tektronix.TekScope(inst=None, **kwds)

A base class for Tektronix scopes. Supports at least TDS 3000 series as well as MSO/DPO 4000 series scopes.

async_get_data(channel=1, width=2)

Retrieve a trace from the scope asynchronously.

Pulls data from channel channel and returns it as a tuple (t,y) of unitful arrays. Python >= 3.3 only.

Use within generators/coroutines like this::

t, y = yield from scope.async_get_data(channel=1)

Parameters
  • channel (int, optional) – Channel number to pull trace from. Defaults to channel 1.

  • width (int, optional) – Number of bytes per sample of data pulled from the scope. 1 or 2.

Returns

t, y – Unitful arrays of data from the scope. t is in seconds, while y is in volts.

Return type

pint.Quantity arrays

get_data(channel=1, width=2, bounds=None)

Retrieve a trace from the scope.

Pulls data from channel channel and returns it as a tuple (t,y) of unitful arrays.

Parameters
  • channel (int, optional) – Channel number to pull trace from. Defaults to channel 1.

  • width (int, optional) – Number of bytes per sample of data pulled from the scope. 1 or 2.

  • bounds (tuple of int, optional) – (start, stop) tuple of first and last sample to read. Index starts at 1.

Returns

t, y – Unitful arrays of data from the scope. t is in seconds, while y is in volts.

Return type

pint.Quantity arrays

get_math_function()
measure(channel, meas_type)

Perform immediate measurement.

read_events()

Get a list of events from the Event Queue

Returns

  • A list of (int, str) pairs containing the code and message for each event in the scope’s

  • event queue.

read_measurement_stats(num)

Read the value and statistics of a measurement.

Parameters

num (int) – Number of the measurement to read from, from 1-4

Returns

stats – Dictionary of measurement statistics. Includes value, mean, stddev, minimum, maximum, and nsamps.

Return type

dict

read_measurement_value(num)

Read the value of a measurement.

Parameters

num (int) – Number of the measurement to read from, from 1-4

Returns

value – Value of the measurement

Return type

pint.Quantity

run_acquire()

Sets the acquire state to ‘run’

set_math_function(expr)

Set the expression used by the MATH channel.

Parameters

expr (str) – a string representing the MATH expression, using channel variables CH1, CH2, etc. eg. ‘CH1/CH2+CH3’

set_measurement_params(num, mtype, channel)

Set the parameters for a measurement.

Parameters
  • num (int) – Measurement number to set, from 1-4.

  • mtype (str) – Type of the measurement, e.g. ‘amplitude’

  • channel (int) – Number of the channel to measure.

stop_acquire()

Sets the acquire state to ‘stop’

property channels
horizontal_delay
horizontal_scale
property interface_type
property math_function
property model
instrumental.drivers.scopes.tektronix.ChannelFacet(msg, convert=None, readonly=False, **kwds)
instrumental.drivers.scopes.tektronix.infer_termination(msg_str)
instrumental.drivers.scopes.tektronix.load_csv(filename)

Load CSV data produced by a Tektronix oscilloscope.

Only developed and tested on CSV data generated by a TDS1001B

instrumental.drivers.scopes.tektronix.strstr(value)

Convert to str and strip outer quotes

Spectrometers

Create Spectrometer objects using instrument().

Bristol Spectrum Analyzers

This module is for controlling Bristol model 721 spectrum analyzers.

Module Reference

Driver for Bristol 721 spectrum analyzers.

class instrumental.drivers.spectrometers.bristol.Bristol_721(inst=None, **kwds)
close()
get_spectrum()

Get a spectrum.

Returns

  • x (array Quantity) – Wavelength of the bins, in nm

  • y (array) – Power in each bin, in arbitrary units

get_wavelength()

Get the vacuum wavelength of the tallest peak in the spectrum

Returns

wavelength – The peak wavelength, in nm

Return type

Quantity

instrumental.drivers.spectrometers.bristol.bin_index(fft_size, dec, fold, lamb)
instrumental.drivers.spectrometers.bristol.ignore_stderr(f)
instrumental.drivers.spectrometers.bristol.list_instruments()
instrumental.drivers.spectrometers.bristol.stderr_redirected(to='/dev/null')
Thorlabs CCS Spectrometers

This module is for controlling Thorlabs CCS spectrometers.

Installation

The Thorlabs OSA software provided on Thorlabs website must be installed. This driver is currently windows only.

Module Reference

Driver Module for Thorlabs CCSXXX series spectrometers. Currently Windows only.

exception instrumental.drivers.spectrometers.thorlabs_ccs.ThorlabsCCSError
class instrumental.drivers.spectrometers.thorlabs_ccs.CCS(inst=None, **kwds)

Thorlabs CCS-series spectrometer.

If this construcor is called, it will connect to the first available spectrometer (if there is at least one). It can also be accessed by calling get_spectrometer using any one of the parameters ‘address’, ‘serial’, or ‘model’. Calling the function instrument(), using any one of the parameters ‘ccs_usb_address’, ‘ccs_serial_number’, or ‘ccs_model’ will also return a CCS instance (if successful).

calibrate_wavelength(calibration_type=Calibration.User, wavelength_array=None, pixel_array=None)

Sets a custom pixel-wavelength calibration.

The wavelength and pixel points are used to interpolate the correlation between pixel and wavelength.

Note that the given values must be strictly increasing as a function of wavelength, and that the lenght of the arrays must be equal and be between 4 and 10 (inclusive). Note that there are also some other requirements, that seem to have something with the calibration data points being somewhat’smooth’ that are not specified in the documentation and may result in the not very descriptive ‘Invalid user wavelength adjustment data’ error.

If calibration_type is Calibration.User, then the last 3 arguments must be given, and are used to set the wavelength calibration.

If calibration_type is Calibration.Factory, then the last three arguments are ignored, and the default factory wavelength calibration is used.

Parameters
  • pixel_data (array of int) – The pixel indices for the interpolation.

  • wavelength_data (array of float) – The wavelengths (in nm) to be used in the interpolation.

  • calibration_type (Calibration) –

close()

Close the spectrometer

cont_scan_in_progress()

Indicates if a continuous scan is in progress

get_amplitude_data(mode=CorrectionType.Store)

Gets the amplitude correction factors.

Parameters

mode (str) – This parameter can be either ‘stored’ or ‘one_time’. If set to ‘one_time’, the correction factors for the current data are returned. If set to ‘stored’, the correction factors stored in the spectrometers non-volatile memory will be returned.

Returns

correction_factors – Array of pixel correction factors, of length NUM_RAW_PIXELS.

Return type

array of float

get_device_info()

Returns and instance of ID_Infor, containing various device information.

get_integration_time()

Returns the integration time.

get_scan_data()

Returns the processed scan data.

Contains the pixel values from the last completed scan.

Returns

data

Return type

numpy array of type float with of length NUM_RAW_PIXELS = 3648,

get_status(status=None)

Returns a list instance containing strings indicating the status of the device. :param status: :type status: int, optional :param An int representing the state of the byte register. If ‘None’: :param (default): :param the method gets the current status directly from the: :param spectrometer.:

is_data_ready()

Indicates if the spectrometer has data ready to transmit.

is_idle()

Supposedly returns ‘True’ if the spectrometer is idle.

The status bit on the spectrometer this driver was tested with did not seem to work properly. This may or may not be a genereal issue.

reset()

Resets the device.

set_amplitude_data(correction_factors, start_index=0, mode=CorrectionType.Store)

Sets the amplitude correction factors.

These factors multiply the pixels intensities to correct for variations between pixels.

On start-up, these are all set to unity. The factors are set by the values in correction_factors, starting with pixel start_index.

Parameters
  • correction_factors (array of float) – Correction factors for the pixels.

  • num_points (int) – The number of pixels to apply the correction factors (typically the length of correction_factors).

  • start_index (int) – The index of the first pixel to which the correction factors will be applied.

  • mode (CorrectionType) – Can be either ‘store’ or ‘one_time’. If set to OneTime, the correction factors are only applied to the current data. If set to Store, the correction factors will be applied to the current data and all future data.

set_background(integration_time=None, num_avg=1)

Collects a background spectrum using the given settings.

Both the integration time and the number of spectra to average over can be specified as paramters. The background spectra itself is returned.

Parameters
  • integration_time (float) – The integration time, in second. If None,the current integration is used.

  • num_avg (int) – The number of spectra to average over. The default is 1 (no averaging).

set_integration_time(integration_time, stop_scan=True)

Sets the integration time.

start_cont_scan_trg()

Arms spectrometer for continuous external triggering.

The spectrometer will wait for a signal from the external trigger before executing a scan, will rearm immediatley after that scan has completed, and so on.

Note that the function returns immediately, and does not wait for the trigger or for the scan to finish.

Note also that this cancels other scans in progress.

start_continuous_scan()
start_scan_trg()

Arms spectrometer to wait for a signal from the external trigger before executing scan.

Note that the function returns immediately, and does not wait for the trigger or for the scan to finish.

Note also that this cancels other scans in progress.

start_single_scan()
stop_and_clear(max_attempts=10)

Stops any scans in progress, and clears any data waiting to transmit. :param max_attempts: Maximum number of attempts to attempt to clear the spectrometer :type max_attempts: int, Default=MAX_ATTEMPTS=10

stop_scan()
take_data(integration_time=None, num_avg=1, use_background=False, max_attempts=10)

Returns scan data.

The data can be averaged over a number of trials ‘num_Avg’ if desired. The stored backgorund spectra can also be subtracted from the data.

Parameters
  • integration_time (Quantity([time]), optional) – The integration time. If not specified, the current integration time is used. Note that in practice, times greater than 50 seconds do not seem to work properly.

  • num_avg (int, Default=1) – The number of spectra to average over.

  • use_background (bool, Default=False) – If true, the spectrometer subtracts the current background spectra (stored in self._background) from the data.

  • max_attempts (int, Default=MAX_ATTEMPTS=10) – Maximum number of attempts to attempt to clear the spectrometer

Returns

  • data (numpy array of float of size (self.num_pixels, 1)) – The amplitude data from the spectrometer, given in arbitrary units.

  • wavelength_data (numpy array of float of size (self.numpixel, 1)) – The wavelength (in nm) corresponding to each pixel.

waiting_for_trig()

Indicates if the spectrometer is waiting for an external trigger signal.

class instrumental.drivers.spectrometers.thorlabs_ccs.Calibration(value)

An enumeration.

Factory = 0
User = 1
class instrumental.drivers.spectrometers.thorlabs_ccs.CorrectionType(value)

An enumeration.

OneTime = 1
Store = 2
class instrumental.drivers.spectrometers.thorlabs_ccs.ID_Info(manufacturer, device_name, serial_number, firmware_version, driver_version)
__init__(manufacturer, device_name, serial_number, firmware_version, driver_version)
class instrumental.drivers.spectrometers.thorlabs_ccs.SpecTypes(value)

An enumeration.

CCS100
CCS125
CCS150
CCS175
CCS200
class instrumental.drivers.spectrometers.thorlabs_ccs.Status(status)
__init__(status)
instrumental.drivers.spectrometers.thorlabs_ccs.list_instruments()

Get a list of all spectrometers currently attached

Wavemeters

Create Wavemeter objects using instrument().

Burleigh Wavemeters

Driver module for Burleigh wavemeters. Supports:

  • WA-1000/1500

class instrumental.drivers.wavemeters.burleigh.WA_1000(inst=None, **kwds)

A Burleigh WA-1000/1500 wavemeter

averaging_enabled()

Whether averaging mode is enabled

disable_averaging()

Disable averaging mode

enable_averaging(enable=True)

Enable averaging mode

get_deviation()

Get the current deviation

Returns

deviation – The wavelength difference between the current input wavelength and the fixed setpoint.

Return type

Quantity

get_num_averaged()

Get the number of samples used in averaging mode

get_pressure()

Get the barometric pressure inside the wavemeter

Returns

pressure – The barometric pressure inside the wavemeter

Return type

Quantity

get_setpoint()

Get the wavelength setpoint

Returns

setpoint – the wavelength setpoint

Return type

Quantity

get_temperature()

Get the temperature inside the wavemeter

Returns

temperature – The temperature inside the wavemeter

Return type

Quantity

get_wavelength()

Get the wavelength

Returns

wavelength – The current input wavelength measurement

Return type

Quantity

is_locked()

Whether the front panel is locked or not

lock(lock=True)

Lock the front panel of the wavemeter, preventing manual input

When locked, the wavemeter can only be controlled remotely by a computer. To unlock, use unlock() or hit the ‘Remote’ button on the wavemeter’s front panel.

set_num_averaged(num)

Set the number of samples used in averaging mode

When averaging mode is enabled, the wavemeter calculates a running average of the last num samples.

Parameters

num (int) – Number of samples to average. Must be between 2 and 50.

set_setpoint(setpoint)

Set the wavelength setpoint

The setpoint is a fixed wavelength used to compute the deviation. It is used for display and to determine the analog output voltage.

Parameters

setpoint (Quantity) – Wavelength of the setpoint, in units of [length]

unlock()

Unlock the front panel of the wavemeter, allowing manual input


Functions
class instrumental.drivers.Instrument(inst=None, **kwds)

Base class for all instruments.

observe(name, callback)

Add a callback to observe changes in a facet’s value

The callback should be a callable accepting a ChangeEvent as its only argument. This ChangeEvent is a namedtuple with name, old, and new fields. name is the facet’s name, old is the old value, and new is the new value.

save_instrument(name, force=False)

Save an entry for this instrument in the config file.

Parameters
  • name (str) – The name to give the instrument, e.g. ‘myCam’

  • force (bool, optional) – Force overwrite of the old entry for instrument name. By default, Instrumental will raise an exception if you try to write to a name that’s already taken. If force is True, the old entry will be commented out (with a warning given) and a new entry will be written.

instrumental.drivers.instrument(inst=None, **kwargs)

Create any Instrumental instrument object from an alias, parameters, or an existing instrument.

reopen_policystr of (‘strict’, ‘reuse’, ‘new’), optional

How to handle the reopening of an existing instrument. ‘strict’ - disallow reopening an instrument with an existing instance which hasn’t been cleaned up yet. ‘reuse’ - if an instrument is being reopened, return the existing instance ‘new’ - always create a new instance of the instrument class. Not recommended unless you know exactly what you’re doing. The instrument objects are not synchronized with one another. By default, follows the ‘reuse’ policy.

>>> inst1 = instrument('MYAFG')
>>> inst2 = instrument(visa_address='TCPIP::192.168.1.34::INSTR')
>>> inst3 = instrument({'visa_address': 'TCPIP:192.168.1.35::INSTR'})
>>> inst4 = instrument(inst1)
instrumental.drivers.list_instruments(server=None, module=None, blacklist=None)

Returns a list of info about available instruments.

May take a few seconds because it must poll hardware devices.

It actually returns a list of specialized dict objects that contain parameters needed to create an instance of the given instrument. You can then get the actual instrument by passing the dict to instrument().

>>> inst_list = get_instruments()
>>> print(inst_list)
[<NIDAQ 'Dev1'>, <TEKTRONIX 'TDS 3032'>, <TEKTRONIX 'AFG3021B'>]
>>> inst = instrument(inst_list[0])
Parameters
  • server (str, optional) – The remote Instrumental server to query. It can be an alias from your instrumental.conf file, or a str of the form (hostname|ip-address)[:port], e.g. ‘192.168.1.10:12345’. Is None by default, meaning search on the local machine.

  • blacklist (list or str, optional) – A str or list of strs indicating driver modules which should not be queried for instruments. Strings should be in the format 'subpackage.module', e.g. 'cameras.pco'. This is useful for very slow-loading drivers whose instruments no longer need to be listed (but may still be in use otherwise). This can be set permanently in your instrumental.conf.

  • module (str, optional) – A str to filter what driver modules are checked. A driver module gets checked only if it contains the substring module in its full name. The full name includes both the driver group and the module, e.g. 'cameras.pco'.

instrumental.drivers.list_visa_instruments()

Returns a list of info about available VISA instruments.

May take a few seconds because it must poll the network.

It actually returns a list of specialized dict objects that contain parameters needed to create an instance of the given instrument. You can then get the actual instrument by passing the dict to instrument().

>>> inst_list = get_visa_instruments()
>>> print(inst_list)
[<TEKTRONIX 'TDS 3032'>, <TEKTRONIX 'AFG3021B'>]
>>> inst = instrument(inst_list[0])
Example
>>> from instrumental import instrument
>>> scope = instrument('my_scope_alias')
>>> x, y = scope.get_data()

It should be pretty easy to write drivers for other VISA-compatible devices by using VisaMixin and Facets. Check out Writing Drivers for more info. Driver submissions are greatly appreciated!


Other Subpackages

There are several other subpackages within Instrumental. These may eventually be moved to their own separate packages.

Plotting

The plotting module provides or aims to provide

  • Unit-aware plotting functions as a drop-in replacement for pyplot

  • Easy slider-plots

Fitting

The fitting module is a good place for curating ‘standard’ fitting tools for common cases like

  • Triple-lorentzian cavity scans

  • Ringdown traces (exponential decay)

It should also provide optional unit-awareness.

Tools

The tools module is used for full-fledged scripts and programs that may make use of all of the other modules above. A good example would be a script that pulls a trace from the scope, auto-fits a ringdown curve, and saves both the raw data and fit parameters to files in a well-organized directory structure.

Working with Instruments

Getting Started

Instrumental tries to make it easy to find and open all the instruments available to your computer. This is primarily accomplished using list_instruments() and instrument():

>>> from instrumental import instrument, list_instruments
>>> paramsets = list_instruments()
>>> paramsets
[<ParamSet[TSI_Camera] serial='05478' number=0>,
 <ParamSet[K10CR1] serial='55000247'>
 <ParamSet[NIDAQ] model='USB-6221 (BNC)' name='Dev1'>]

You can then use the output of list_instruments() to open the instrument you want:

>>> daq = instrument(paramsets[2])
>>> daq
<instrumental.drivers.daq.ni.NIDAQ at 0xb61...>

Or you can enter the parameters directly:

>>> instrument(ni_daq_name='Dev1')
<instrumental.drivers.daq.ni.NIDAQ at 0xb61...>

If you’re going to be using an instrument repeatedly, save it for later:

>>> daq.save_instrument('myDAQ')

Then you can simply open it by name:

>>> daq = instrument('myDAQ')
Using Units

pint units are used heavily by Instrumental, so you should familiarize yourself with them. Many methods only accept unitful quantities, as a way to add clarity and prevent errors. In most cases you can use a string as shorthand and it will be converted automatically:

>>> daq.ao1.write('3.14 V')

If you need to create your own quantities directly, you can use the u and Q_ objects provided by Instrumental:

>>> from instrumental import u, Q_

u is a pint.UnitRegistry, while Q_ is a shorhand for the registry’s Quantity class. There are several ways you can use them:

>>> u.m                       # Access units as attributes
<Unit('meter')>
>>> 3 * u.s
<Quantity(3, 'second')>

>>> u('2.54 inches')          # Parse a string into a quantity using u()
<Quantity(2.54, 'inch')>

>>> Q('852 nm')               # ...or Q_()
<Quantity(852, 'nanometer')>

>>> Q(32.89, 'MHz')           # Specify magnitude and units separately
<Quantity(32.89, 'megahertz')>

pint also supports many physical constants (e.g. )

Note that it can be tricky to create offset units—e.g. by Q_('20 degC')— because pint treats this as a multiplication and will raise an OffsetUnitCalculusError. You can get around this by separating the magnitude and units, e.g. Q_(20, 'degC'). Note that Facets as well as the check_units and unit_mag decorators can properly parse strings like '20 degC'.

Advanced Usage

An Even Quicker Way

Here’s a shortcut for opening an instrument that means you don’t have to assign the instrument list to a variable, or even know how to count—just use part of the instrument’s string:

>>> list_instruments()
[<ParamSet[TSI_Camera] serial='05478' number=0>,
 <ParamSet[K10CR1] serial='55000247'>
 <ParamSet[NIDAQ] model='USB-6221 (BNC)' name='Dev1'>]
>>> instrument('TSI')  # Opens the TSI_Camera
>>> instrument('NIDAQ')  # Opens the NIDAQ

This will work as long as the string you use isn’t saved as an instrument alias. If you use a string that matches multiple instruments, it just picks the first in the list.

Filtering Results

If you’re only interested in a specific driver or category of instrument, you can use the module argument to filter your results. This will also speed up the search for the instruments:

>>> list_instruments(module='cameras')
[<ParamSet[TSI_Camera] serial='05478' number=0>]
>>> list_instruments(module='cameras.tsi')
[<ParamSet[TSI_Camera] serial='05478' number=0>]

list_instruments() checks if module is a substring of each driver module’s name. Only modules whose names match are queried for available instruments.

Remote Instruments

You can even control instruments that are attached to a remote computer:

>>> list_instruments(server='192.168.1.10')

This lists only the instruments located on the remote machine, not any local ones.

The remote PC must be running as an Instrumental server (and its firewall configured to allow inbound connections on this port). To do this, run the script tools/instr_server.py that comes packaged with Instrumental. The client needs to specify the server’s IP address (or hostname), and port number (if differs from the default of 28265). Alternatively, you may save an alias for this server in the [servers] section of you instrumental.conf file (see Saved Instruments for more information about instrumental.conf). Then you can list the remote instruments like this:

>>> list_instruments(server='myServer')

You can then open your instrument using instrument() as usual, but now you’ll get a RemoteInstrument, which you can control just like a regular Instrument.

How Does it All Work?

Listing Instruments

What exactly is list_instruments() doing? Basically it walks through all the driver modules, trying to import them one by one. If import fails (perhaps the DLL isn’t available because the user doesn’t have this instrument), that module is skipped. Each module is responsible for returning a list of its available instruments, e.g. the drivers.daqs.ni module returns a list of all the NI DAQs that are accessible. list_instruments() combines all these instruments into one big list and returns it.

There’s an unfortunate side-effect of this: if a module fails to import due to a bug, the exception is caught and ignored, so you don’t get a helpful traceback. To diagnose issues with a driver module, you can import the module directly:

>>> import instrumental.drivers.daq.ni

or enable logging before calling list_instruments():

>>> from instrumental.log import log_to_screen
>>> log_to_screen()

list_instruments() doesn’t open instruments directly, but instead returns a list of dict-like ParamSet objects that contain info about how to open each instrument. For example, for our DAQ:

>>> dict(paramsets[2])
{'classname': 'NIDAQ',
 'model': 'USB-6221 (BNC)',
 'module': 'daq.ni',
 'name': 'Dev1',
 'serial': 20229473L}

We could also open it with keyword arguments:

>>> instrument(name='Dev1')
<instrumental.drivers.daq.ni.NIDAQ at 0xb69...>

or a dictionary:

>>> instrument({'name': 'Dev1'})
<instrumental.drivers.daq.ni.NIDAQ at 0xb69...>

Behind the scenes, instrument() uses the keywords to figure out what type of instrument you’re talking about, and what class should be instantiated. If you don’t give it much information to use, it may take awhile scanning through the available instruments. You can speed this up by providing the model and/or classname:

>>> instrument(module='daq.ni', classname='NIDAQ', name='Dev1')
<instrumental.drivers.daq.ni.NIDAQ at 0xb69...>

In addition, a convenient shorthand exists for specifying the module (or category of module) when you pass a parameter. For example:

>>> instrument(ni_daq_name='Dev1')
<instrumental.drivers.daq.ni.NIDAQ at 0xb69...>

only looks at instrument types in the daq.ni module that have a name parameter. These special parameter names support the format <module>_<category>_<parameter>, <module>_<parameter>, and <category>_<parameter>. The parameter name is split by underscores, then used to filter which modules are checked. Note that each segment can be abbreviated, so e.g. cam_serial will match all drivers in the cameras category having a serial parameter (this works because ‘cam’ is a substring of ‘cameras’).

Saved Instruments

Opening instruments using list_instruments() is really helpful when you’re messing around in the shell and don’t quite know what info you need yet, or you’re checking what devices are available to you. But if you’ve found your device and want to write a script that reuses it constantly, it’s convenient (and more efficient) to have it saved under an alias, which you can do easily with save_instrument() as we showed above.

When you do this, the instrument’s info gets saved in your instrumental.conf config file. To find where the file is located on your system, run:

>>> from instrumental.conf import user_conf_dir
>>> user_conf_dir
u'C:\\Users\\Lab\\AppData\\Local\\MabuchiLab\\Instrumental'

To save your instrument manually, you can add its parameters to the [instruments] section of instrumental.conf. For our DAQ, that would look like:

# NI-DAQ device
myDAQ = {'module': 'daq.ni', 'classname': 'NIDAQ', 'name': 'Dev1'}

This gives our DAQ the alias myDAQ, which can then be used to open it easily:

>>> instrument('myDAQ')
<instrumental.drivers.daq.ni.NIDAQ at 0xb71...>

The default version of instrumental.conf also provides some commented-out example entries to help make things clear.

Reopen Policy

By default, Instrumental will prevent you from double-opening an instrument:

>>> cam1 = instrument('myCamera')
>>> cam2 = instrument('myCamera')  # results in an InstrumentExistsError

Usually it makes the most sense to simply reuse a previously-opened instrument rather than re-creating it. So, by default an exception is raised in if double-creation is attempted. However, this behavior is configurable via the reopen_policy parameter upon instrument creation.

>>> cam1 = instrument('myCamera')
>>> cam2 = instrument('myCamera', reopen_policy='reuse')
>>> cam1 is cam2
True
>>> cam1 = instrument('myCamera')
>>> cam2 = instrument('myCamera', reopen_policy='new')  # Might cause a driver error
>>> cam1 is cam2
False

The available policies are:

strict

The default policy; raises an InstrumentExistsError if an Instrument object already exists that matches the given paramset.

reuse

Returns the previously-created instrument object if it exists.

new

Simply create the instrument as usual. Not recommended; only use this if you really know what you’re doing, as the instrument driver is unlikely to support two objects controlling a single device.

For these purposes, an instrument “already exists” if the given paramset matches that of any Instrument which hasn’t yet been garbage collected. Thus, an instrument can be re-created even under the strict policy as long as the old instrument has been fully deleted, either manually via del or automatically by it going out of scope.

FAQs

My instrument isn’t showing up in list_instruments(). What now?

If you’re using this particular driver for the first time, make sure you’ve followed the install directions fully. You should also check that the device works with any vendor-provided software (e.g. a camera viewer GUI). If the device still isn’t showing up, you should import the driver module directly to reveal any errors (see Why isn’t list_instruments() producing any errors?).

Why isn’t list_instruments() producing any errors?

list_instruments() is designed to check all Instrumental drivers that are available, importing each driver in turn. If a driver fails to import, this is often because you haven’t installed its requirements (because you’re not using it), so list_instruments() simply ignores the error and moves on.

Where is the instrumental.conf configuration file stored?

The location of instrumental.conf is platform-dependent. To find where the file is located on your system, run:

>>> from instrumental.conf import user_conf_dir
>>> user_conf_dir
u'C:\\Users\\Lab\\AppData\\Local\\MabuchiLab\\Instrumental'

Contributing

Contributions are highly encouraged, and can be as small or big as you like. Don’t worry if you think your code is incomplete or not “pretty” enough—an incomplete driver is more useful than no driver at all, and we can help you improve your code and integrate it within the framework of Instrumental.

Feedback is also much appreciated. Suspected bugs should be submitted as GitHub issues, and you can use the mailing list to get troubleshooting help, provide general feedback, or discuss feature requests and development.

Submitting a New Driver

If you’re considering submitting a driver—thank you! We suggest that you submit it on GitHub via a Pull Request1 that contains only changes that relate to this driver. Sometimes we may ask you to make a few minor changes before the driver is merged in. After merging, we’ll create an issue on GitHub to discuss any additional changes that should be made before the driver is included in a release of Instrumental. This way we get code merged in quickly, while also ensuring high-quality releases.

You should check out Writing Drivers for more info related to driver development. There’s also the Developer’s Guide if you’re the kind of person who enjoys reading about guidelines, coding conventions, and project philosophies.

1

You can ask for help if you’re unfamiliar with git/GitHub and aren’t sure how to make a Pull Request.

Writing Drivers

Examples

VISA Driver Example

In this guide, we’ll run through the process of making a VISA-based driver using the example of a Thorlabs PM100D power meter.

If you’re following along, it may be helpful to look at the PM100D’s commands, listed in the “SCPI Commands” section of its manual [PDF].

First, let’s open the device and play around with it in an ipython shell using pyvisa:

In [1]: import visa

In [2]: rm = visa.ResourceManager()

In [4]: rm.list_resources()
Out[4]:
(u'USB0::0x1313::0x8078::P0009084::INSTR',
 u'USB0::0x0699::0x0362::C101689::INSTR',
 u'ASRL1::INSTR',
 u'ASRL3::INSTR')

Which resource is our power meter? Well, like all well-behaved SCPI instruments, the PM100D supports the *IDN? command, which asks the device to identify itself. Let’s query the IDN of each of these resources:

In [5]: for addr in rm.list_resources():
   ...:     try:
   ...:         print(addr, '-->', rm.open_resource(addr).query('*IDN?').strip())
   ...:     except visa.VisaIOError:
   ...:         pass
   ...:

USB0::0x1313::0x8078::P0009084::INSTR --> Thorlabs,PM100D,P0009084,2.4.0
USB0::0x0699::0x0362::C101689::INSTR --> TEKTRONIX,TDS 1001B,C101689,CF:91.1CT FV:v22.11

We’ve used a try-except block here to catch errors from any devices that don’t support the *IDN? command. We can now see which device is our power meter. Let’s open it and try some of the commands listed in the manual:

In [5]: rsrc = rm.open_resource('USB0::0x1313::0x8078::P0009084::INSTR',
                                read_termination='\n')

In [6]: rsrc.query('measure:power?')
Out[6]: '4.20021615E-06'

In [7]: rsrc.query('power:dc:unit?')
Out[7]: 'W'

In [8]: rsrc.query('sense:corr:wav?')
Out[8]: '8.520000E+02'

Here we’ve set the resource’s read termination to automatically strip off the newline at the end of each message, to make the output clearer. We can see that our power meter is measuring 4.2 microwatts of optical power and its operation wavelength is set to 852 nm. Let’s change the wavelength:

In [9]: rsrc.write('sense:corr:wav 532')
Out[9]: (20, <StatusCode.success: 0>)

In [10]: rsrc.query('sense:corr:wav?')
Out[10]: '5.320000E+02'

Now let’s get start writing our driver:

from instrumental.drivers.powermeters import PowerMeter
from instrumental.drivers import VisaMixin

class PM100D(PowerMeter, VisaMixin):
    """A Thorlabs PM100D series power meter"""
    _INST_PARAMS_ = ['visa_address']

We inherit from PowerMeter, a subclass of Instrument, and use the _INST_PARAMS_ class attribute to declare what parameters our instrument needs. We also inherit from VisaMixin, a mixin class which provides us some useful VISA-related features:

In [1]: from mydriver import *

In [2]: pm = PM100D(visa_address='USB0::0x1313::0x8078::P0009084::INSTR')

In [3]: pm.resource
Out[3]: <'USBInstrument'(u'USB0::0x1313::0x8078::P0009084::INSTR')>

In [4]: pm.query('*IDN?')
Out[4]: 'Thorlabs,PM100D,P0009084,2.4.0\n'

VisaMixin allows us to construct an instance by providing only a visa_address parameter, and provides our class with a resource attribute as well as query and write convenience methods. Notice that the message termination isn’t being stripped. We can enable this by setting read_termination inside the _initialize method, which is called just after the instance is created:

from instrumental.drivers.powermeters import PowerMeter
from instrumental.drivers import VisaMixin

class PM100D(PowerMeter, VisaMixin):
  """A Thorlabs PM100D series power meter"""
  def _initialize(self):
      self.resource.read_termination = '\n'

Now let’s add a method to return the measured optical power:

from instrumental import Q_
[...]
class PM100D(PowerMeter, VisaMixin):
  def power(self):
      """The measured optical power"""
      self.write('power:dc:unit W')
      power_W = float(self.query('measure:power?'))
      return Q_(power_W, 'W')

This will sets the measurement units to watts, queries the power, and returns it as a unitful Quantity. Let’s try it out:

In [3]: pm.power()
Out[3]: <Quantity(1.03476232e-05, 'watt')>

Now let’s add a way to get and set the wavelength, but let’s use the SCPI_Facet convenience function, which allows us to concisely wrap well-behaving SCPI commands:

from instrumental.drivers import VisaMixin, SCPI_Facet
[...]
class PM100D(PowerMeter, VisaMixin):
    [...]
    wavelength = SCPI_Facet('sense:corr:wav', units='nm', type=float,
                            doc="Input signal wavelength")

wavelength here is a Facet, which is like a suped-up python property. We’ve told SCPI_Facet the command to use, and noted that it refers to a float with units of nanometers. Now we let’s see how our new wavelength attribute behaves:

In [4]: pm.wavelength
Out[4]: <Quantity(532.0, 'nanometer')>

In [5]: pm.wavelength = 1064
[...]
DimensionalityError: Cannot convert from 'dimensionless' (dimensionless) to 'nanometer' ([length])

What happened? The Facet ensures that we set the wavelength in units of length, to keep us from making unit conversion errors. We can use either a Quantity or a string that can be parsed by Q_()

In [6]: pm.wavelength = Q_(1064, 'nm')

In [7]: pm.wavelength
Out[7]: <Quantity(1064.0, 'nanometer')>

In [8]: pm.wavelength = Q_(0.633, 'um')

In [9]: pm.wavelength
Out[9]: <Quantity(633.0, 'nanometer')>

In [10]: pm.wavelength = '852 nm'

In [11]: pm.wavelength
Out[11]: <Quantity(852.0, 'nanometer')>

That’s better. Now that we have a basic driver, we need to make sure everything is cleaned up when we close our instrument. For most VISA-based instruments, this isn’t necessary, but the PM100D enters a special REMOTE mode, which locks out the front panel, when you start sending it commands. We use the control_ren() method of pyvisa.resources.USBInstrument to disable remote mode:

[...]
class PM100D(PowerMeter, VisaMixin):
    [...]
    def close(self):
        self.resource.control_ren(False)  # Disable remote mode

The close() method can be called explicitly, and it is automatically called when the instrument is cleaned up or the interpreter exits. This way, the power meter will exit remote mode even if our program exits due to an exception.

@Facet(units='W', cached=False)
def power(self):
    """The measured optical power"""
    self.write('power:dc:unit W')
    return float(self.query('measure:power?'))
from instrumental.drivers.powermeters import PowerMeter
from instrumental.drivers import Facet, SCPI_Facet, VisaMixin, deprecated
class PM100D(PowerMeter, VisaMixin):
    """A Thorlabs PM100D series power meter"""
    range = SCPI_Facet('power:dc:range', units='W', convert=float, readonly=True,
                       doc="The current input range's max power")

    auto_range = SCPI_Facet('power:dc:range:auto', convert=int, value={False:0, True:1},
                            doc="Whether auto-ranging is enabled")

    wavelength = SCPI_Facet('sense:corr:wav', units='nm', type=float,
                            doc="Input signal wavelength")

    num_averaged = SCPI_Facet('sense:average:count', type=int,
                              doc="Number of samples to average")

    def close(self):
        self._rsrc.control_ren(False)  # Disable remote mode
NiceLib Driver Example

In this guide, we’ll run through the process of writing a driver that uses NiceLib to wrap a C library, using the example of an NI DAQ. A NiceLib-based driver consists of three parts: low-level, mid-level, and high-level interfaces.

Low-level

Mimics the C interface directly

Mid-level

Has the same functions as the low-level interface, but with a more convenient interface

High-level

Is nice and pythonic, often doesn’t necessarily mimic the C interface’s structure

Low-Level Interface
The Build File

NiceLib semi-automatically generates a low-level interface for us. To tell it how to do so, we create a build file, which contains a build(). This function invokes build_lib() with arguments that tell it what library (.dll/.so file) we’re wrapping and what header(s) to use.

For our ni library, we name our file _build_ni.py:

# _build_ni.py
from nicelib import build_lib

header_info = r'C:\Program Files (x86)\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include\NIDAQmx.h'
lib_name = 'nicaiu'


def build():
    build_lib(header_info, lib_name, '_nilib', __file__)


if __name__ == '__main__':
    from instrumental.log import log_to_screen
    log_to_screen(fmt='%(message)s')
    build()

You can see we’ve indicated the path to the header NIDAQmx.h and the library nicaiu.dll that are used by the Windows version of NI-DAQmx.

Now let’s manually invoke a build:

$ python _build_ni.py
Module _nilib does not yet exist, building it now. This may take a minute...
Searching for headers...
Found C:\Program Files (x86)\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include\NIDAQmx.h
Parsing and cleaning headers...
Successfully parsed input headers
Compiling cffi module...
Writing macros...
Done building _nilib

Nice, it worked! We now have a freshly-generated _nilib.py module, which is our low-level interface. You can then call load_lib('foo', __package__) to load the LibInfo object, as we’ll see in the mid-level interface section.

Parsing Problems?

We won’t always be this fortunate, since headers sometimes include nonstandard syntax which nicelib’s parsing system can’t handle. In that case, there are two basic approaches:

  1. Manually include only the necessary snippets of the header, cleaning up any unparseable syntax.

  2. Use build_lib's options, including token_hooks and ast_hooks to avoid or programmatically clean up the problem syntax.

Option 1 can be good for quickly moving on and starting to write your mid-level interface. You can include just a few functions that you want to test out, and perhaps later come back and pursue option 2. For example, we could do the following for our DAQmx driver:

from nicelib import build_lib

source = """
#define __CFUNC         __stdcall
typedef signed int int32;
typedef unsigned int uInt32;

int32 __CFUNC DAQmxGetSysDevNames(char *data, uInt32 bufferSize);
int32 __CFUNC DAQmxGetDevProductNum(const char device[], uInt32 *data);
int32 __CFUNC DAQmxGetDevSerialNum(const char device[], uInt32 *data);
"""
lib_name = 'nicaiu'


def build():
    build_lib(None, lib_name, '_nilib', __file__, preamble=source)

We use header_info=None to skip loading any external header files, and pass in our source via the preamble parameter.

Option 2 is more complete, but can sometimes be tricky, as it requires some extra knowledge of C and why some section of code may not be parsing correctly (e.g. because it’s actually C++, which happens commonly in some libs written only for Windows). In the simplest case, you may just need to exclude a problematic header that’s being included, or use one of the pre-written token hooks or ast hooks that nicelib provides. In other cases, you may need to write your own hook to clean up the header. See the nicelib docs for a more detailed account of how to use token/ast hooks and the other paramters of build_lib().

Mid-Level Interface

Once we have a low-level interface that can be loaded via load_lib(), we can start to work on the mid-level bindings. What’s the point of these bindings? Well, they make the functions a lot more hospitable to work with. Take for example int32 DAQmxGetSysDevNames(char *data, uInt32 bufferSize). This function takes a preallocated char buffer and its length, returning its string within the buffer, and an error code as the int32 return value. Using the low-level binding looks like this:

buflen = 1024
data = ffi.new('char[]', buflen)
retval = DAQmxGetSysDevNames(data, buflen)
handle_daq_retval(retval)
result = ffi.string(data)

Seems kinda verbose—and this function only takes two arguments! Write too much code like this and your code’s intent will drown in a sea of bookkeeping. In contrast, using a mid-level binding looks like this:

result = NiceNI.GetSysDevNames()

Better, right? So how do you write these mid-level bindings? Here’s a simple start:

from nicelib import load_lib, NiceLib, Sig

class NiceNI(NiceLib):
    _info_ = load_lib('ni', __package__)
    _prefix_ = 'DAQmx'

    GetErrorString = Sig('in', 'buf', 'len')
    GetSysDevNames = Sig('buf', 'len')
    CreateTask = Sig('in', 'out')

We define a subclass of NiceLib that specifies some general info about the library, as well as some signature (Sig) definitions for the functions we want to wrap. _info_ specifies the lib we’re wrapping, and _prefix_ is a prefix that will be removed from the names of the functions. A Sig specifies the purpose of each of its function’s parameters, e.g. whether it’s an input, an output, or something more special.

For instance, CreateTask was was matched with Sig('in', 'out'), reflecting that int32 DAQmxCreateTask(const char taskName[], TaskHandle *taskHandle) uses taskName as an input, and taskHandle as an output. This tells nicelib that CreateTask takes only one argument and returns one value, and nicelib creates a function accordingly:

In [1]: NiceNI.CreateTask('myTask')
Out[1]: (<cdata 'void *' 0x000000000AD49250>, 0)

But wait, there are two values here, what’s going on? The first part makes sense, that’s our taskHandle (of type TaskHandle, an alias for void*), but what’s the zero from? It’s the actual return value, the error-code of type int32. What if we want to ignore this value, or do something else with it? That’s where RetHandlers come in. We’ll talk more about these later, but nicelib comes with two builtin return handlers, ret_return and ret_ignore. ret_return is used by default, and it tacks the C return value on the the end of the Python return values. ret_ignore simply ignores the return value. There are a few levels at which we can specify the return handler, but to apply it to all functions within the lib we use the _ret_ attribute:

from nicelib import load_lib, NiceLib, Sig, ret_ignore

class NiceNI(NiceLib):
    _info_ = load_lib('ni', __package__)
    _prefix_ = 'DAQmx'
    _ret_ = ret_ignore

    GetErrorString = Sig('in', 'buf', 'len')
    GetSysDevNames = Sig('buf', 'len')
    CreateTask = Sig('in', 'out')

Now let’s try again:

In [1]: NiceNI.CreateTask('myTask')
Out[1]: <cdata 'void *' 0x0000000009169250>

For now let’s ignore the return codes; we’ll handle them properly later. Now that we’ve explained 'in' and 'out', what do 'buf' and 'len' do? Recall that DAQmxGetSysDevNames(char *data, uInt32 bufferSize) takes a char buffer and its length, writing a C-string into the buffer. The pair of 'buf' and 'len' are made for exactly such a situation—nicelib will create a char array, passing it in for the 'buf' parameter, and its length in as the 'len' parameter, then extracting a bytes object using ffi.string() and returning it:

In [2]: NiceNI.GetSysDevNames()
Out[2]: b'Dev1'

You can check out the nicelib docs to find a listing of all the possible Sig string codes and what they do.

TODO:

  • NiceObject classdefs

  • RetHandlers

High-Level Interface

Now let’s get start writing our driver:

from instrumental.drivers.daq import DAQ

class NIDAQ(DAQ):
    _INST_PARAMS_ = ['name', 'serial', 'model']

    def _initialize(self):
        self.name = self._paramset['name']
        self._dev = self.mx.Device(self.name)

We inherit from DAQ, a subclass of Instrument, and use the _INST_PARAMS_ class attribute to declare what parameters our instrument can use to construct itself.


Overview

An Instrumental driver is a high-level Python interface to a hardware device. These can be implemented in a number of ways, but usually fall into one of two categories: message-based drivers and foreign function interface (FFI)-based drivers.

Many lab instruments—whether they use GPIB, RS-232, TCPIP, or USB—communicate using text-based messaging protocols. In this case, we can use PyVISA to interface with the hardware, and focus on providing a high-level pythonic API. See Writing VISA-based Drivers for more details.

Otherwise, the instrument is likely controlled via a library (DLL) which is designed to be used by an application written in C. In this case, we can use NiceLib to greatly simplify the wrapping of the library. See Writing NiceLib-Based Drivers for more details.

Generally a driver module should correspond to a single API/library which is being wrapped. For example, there are two separate drivers for Thorlabs cameras, instrumental.drivers.cameras.uc480 and instrumental.drivers.cameras.tsi, each corresponding to a separate library.

By subclassing Instrument, your class gets a number of features for free:

  • Auto-closing on program exit (just provide a close() method)

  • A context manager which automatically closes the instrument

  • Saving of instruments via save_instrument()

  • Integration with ParamSet

  • Integration with Facet

Writing VISA-based Drivers

To control instruments using message-based protocols, you should use PyVISA, by making your driver class inherit from VisaMixin. You can then use MessageFacet() or SCPI_Facet() to easily implement a lot of common functionality (see Facets for more information). VisaMixin provides a resource property as well as write() and query() methods for your class.

If you’re implementing _instrument() and need to open/access the VISA instrument/resource, you should use instrumental.drivers._get_visa_instrument() to take advantage of caching.

For a walkthough of writing a VISA-based driver, check out the VISA Driver Example.

Writing NiceLib-Based Drivers

If you need to wrap a library or SDK with a C-style interface (most DLLs), you will probably want to use NiceLib, which simplifies the process. You’ll first write some code to generate mid-level bindings for the library, then write your high-level bindings as a separate class which inherits from the appropriate Instrument subclass. See the NiceLib documentation for details on how to use it, and check out other NiceLib-based drivers to see how to integrate with Instrumental.

For a walkthough of writing a NiceLib-based driver, check out the NiceLib Driver Example.

Integrating Your Driver with Instrumental

To make your driver module integrate nicely with Instrumental, there are a few patterns that you should follow.

Special Driver Variables

Note

Some old drivers use special variables that are defined on the module level, just below the imports. This method is now deprecated in favor of class-level variables. See Special Driver Variables (deprecated method) for info on the old variables.

When an instrument is created via list_instruments(), Instrumental must find the proper driver to use. To avoid importing every driver module to check for the instrument, we use the statically generated file driver_info.py. To register your driver in this file, do not edit it directly, but instead define special class attributes within your driver class:

_INST_PARAMS_

A list of strings indicating the parameter names which can be used to construct the instruments that this driver class provides. The ParamSet objects returned by list_instruments() should provide each of these parameters. Usually VISA instruments just set _INST_PARAMS_ = ['visa_address'].

_INST_VISA_INFO_

(Optional, only used for VISA instruments) A tuple (manufac, models), to be checked against the result of an *IDN? query. manufac is the manufacturer string, and models is a list of model strings.

_INST_PRIORITY_

(Optional) An int (nominally 0-9) denoting the driver’s priority. Lower-numbered drivers will be tried first. This is useful because some drivers are either slower, less reliable, or less commonly used than others, and should therefore be tried only after all other options are exhausted.

To re-generate driver_info.py, run python -m instrumental.parse_modules. This will parse all of the driver code and look for classes defining the class attribute _INST_PARAMS_, adding them to its list of known drivers. The generated file contains all driver modules, driver classes, parameters, and required imports. For instanc:

driver_info = OrderedDict([
    ('motion._kinesis.isc', {
        'params': ['serial'],
        'classes': ['K10CR1'],
        'imports': ['cffi', 'nicelib'],
    }),
    ('scopes.tektronix', {
        'params': ['visa_address'],
        'classes': ['MSO_DPO_2000', 'MSO_DPO_3000', 'MSO_DPO_4000', 'TDS_1000', 'TDS_200', 'TDS_2000', 'TDS_3000', 'TDS_7000'],
        'imports': ['pyvisa', 'visa'],
        'visa_info': {
            'MSO_DPO_2000': ('TEKTRONIX', ['MSO2012', 'MSO2014', 'MSO2024', 'DPO2012', 'DPO2014', 'DPO2024']),
        },
    }),
])
Special Driver Variables (deprecated method)

Note that these old variable names lacked the trailing underscore.

_INST_PARAMS

A list of strings indicating the parameter names which can be used to construct the instruments that this driver provides. The ParamSet objects returned by list_instruments() should provide each of these parameters.

_INST_CLASSES

(Not required for VISA-based drivers) A list of strings indicating the names of all Instrument subclasses the driver module provides (typically only one). This allows you to avoid writing a driver-specific _instrument() function in most cases.

_INST_VISA_INFO

(Optional, only used for VISA instruments) A dict mapping instrument class names to a tuple (manufac, models), to be checked against the result of an *IDN? query. manufac is the manufacturer string, and models is a list of model strings.

For instruments that support the *IDN? query, this allows us to directly find the correct driver and class to use.

_INST_PRIORITY

(Optional) An int (nominally 0-9) denoting the driver’s priority. Lower-numbered drivers will be tried first. This is useful because some drivers are either slower, less reliable, or less commonly used than others, and should therefore be tried only after all other options are exhausted.

Special Driver Functions

These functions, if implemented, should be defined at the module level.

list_instruments()

(Optional for VISA-based drivers) This must return a list of ParamSets which correspond to each available device that this driver sees attached. Each ParamSet should contain all of the params listed in this driver’s _INST_PARAMS.

_instrument(paramset)

(Optional) Must find and return the device corresponding to paramset. If this function is defined, instrumental.instrument() will use it to open instruments. Otherwise, the appropriate driver class is instantiated directly.

_check_visa_support(visa_rsrc)

(Optional, only applies to VISA-based drivers) Must return the name of the Instrument subclass to use if visa_rsrc is a device that is supported by this driver, and None if it is not supported. visa_rsrc is a pyvisa.resources.Resource object. This function is only needed for VISA-based drivers where the device does not support the *IDN? query, and instead implements its own message-based protocol.

Writing Your Instrument Subclass

Each driver subpackage (e.g. instrumental.drivers.motion) defines its own subclass of Instrument, which you should use as the base class of your new instrument. For instance, all motion control instruments should inherit from instrumental.drivers.motion.Motion.

Writing _initialize()

Instrument subclasses should implement an _initialize() method to perform any required initialization (instead of __init__). For convenience, the special settings parameter is unpacked (using **) into this initializer. Any optional settings you support should be given default values in the function signature. No other arguments are passed to _initialize().

_paramset and other mixin-related attributes (e.g. resource for subclasses of :class`~instrumental.drivers.VisaMixin`) are already set before _initialize() is called, so you may access them if you need to.

Special Methods

There are also some special methods you may provide, all of which are optional.

close()

Close the instrument. Useful for cleaning up per-instrument resources. This automatically gets called for each instrument upon program exit. The default implementation does nothing.

_fill_out_paramset()

Flesh out the ParamSet that the user provided. Usually you’d only reimplement this to provide a more efficient implementation than the default. The input params can be accessed and modified via self._paramset.

The default implementation first checks which parameters were provided. If the user provided all parameters listed in the module’s _INST_PARAMS, the params are considered complete. Otherwise, the driver’s instrumental.drivers.list_instruments() is called, and the the first matching set of params is used to fill in any missing entries in the input params.

Driver Parameters

A ParamSet is a set of identifying information, like serial number or name, that is used to find and identify an instrument. These ParamSets are used heavily by instrument() and list_instruments(). There are some specially-handled parameters in addition to the ordinary ones, as described below.

You can customize how an Instrument’s paramset is filled out by overriding the _fill_out_paramset() method. The default implementation uses instrumental.drivers.list_instruments() to find a matching paramset, and updates the original paramset with any fields that are missing.

Special params

There are a few parameters that are treated specially. These include:

module

The name of the driver module, relative to the drivers package, e.g. scopes.tektronix.

classname

The name of the class to which these parameters apply.

server

The address of an instrument server which should be used to open the remote instrument.

settings

A dict of extra settings which get passed as arguments to the instrument’s constructor. These settings are separated from the other parameters because they are not considered identifying information, but simply configuration information. More specifically, changing the settings should never change which instrument the given ParamSet will open.

visa_address

The address string of a VISA instrument. If this is given, Instrumental will assume the parameters refer to a VISA instrument, and will try to open it with one of the VISA-based drivers.

Common params

Driver-defined parameters can be named pretty much anything (other than the special names given above). However, they should typically fall into a small set of commonly shared names to make the user’s life easier. Some commonly-used names you should consider using include:

  • serial

  • model

  • number

  • id

  • name

  • port

In general, don’t use vendor-specific names like newport_id (also avoid including underscores, for reasons that will become clear). Convenient vendor-specific parameters are automatically supported by instrument(). Say for example that the driver instrumental.drivers.cameras.tsi supports a :param:`serial` parameter. Then you can use any of the parameters serial, tsi_serial, tsi_cam_serial, and cam_serial to open the camera. The parameter name is split by underscores, then used to filter which modules are checked.

Note that cam_serial (vs cameras_serial) is not a typo. Each section is matched by substring, so you can even use something like tsi_cam_ser.

Useful Utilities

Instrumental provides some commonly-used utilities for helping you to write drivers, including decorators and functions for helping to handle unitful arguments and enums.

instrumental.drivers.util.check_units(*pos, **named)

Decorator to enforce the dimensionality of input args and return values.

Allows strings and anything that can be passed as a single arg to pint.Quantity.

@check_units(value='V')
def set_voltage(value):
    pass  # `value` will be a pint.Quantity with Volt-like units
instrumental.drivers.util.unit_mag(*pos, **named)

Decorator to extract the magnitudes of input args and return values.

Allows strings and anything that can be passed as a single arg to pint.Quantity.

@unit_mag(value='V')
def set_voltage(value):
    pass  # The input must be in Volt-like units and `value` will be a raw number
          # expressing the magnitude in Volts
instrumental.drivers.util.check_enums(**kw_args)

Decorator to type-check input arguments as enums.

Allows strings and anything that can be passed to as_enum.

@check_enums(mode=SampleMode)
def set_mode(mode):
    pass  # `mode` will be of type SampleMode
Noindex

instrumental.drivers.util.as_enum(enum_type, arg)

Check if arg is an instance or key of enum_type, and return that enum

instrumental.drivers.util.visa_timeout_context(resource, timeout)

Context manager for temporarily setting a visa resource’s timeout.

with visa_timeout_context(rsrc, 100):
     ...  # `rsrc` will have a timeout of 100 ms within this block

Driver-Writing Checklist

There are a few things that should be done to make a driver integrate really nicely with Instrumental:

Some other important things to keep in mind:

  • Use Pint Units in your API

  • Ensure Python 3 compatibility

  • Add documentation

    • Add supported device(s) to the list in Package Overview

    • Document methods using numpy-style docstrings

    • Add extra docs to show common usage patterns, if applicable

    • List dependencies following a template (both Python packages and external libraries)

Facets

Note

Facets are new in version 0.4

Introduction

A Facet represents a property of an instrument—for example, the wavelength setting on an optical power meter. Facets exist to ease driver development and help to provide a consistent driver interface with features like unit conversion and bounds-checking. They also make driver code more declarative, and hence easier to read. Take, for example, this snippet from the Thorlabs PM100D driver:

class PM100D(PowerMeter, VisaMixin):
    """A Thorlabs PM100D series power meter"""
    [...]
    wavelength = SCPI_Facet('sense:corr:wav', units='nm', type=float,
                            doc="Input signal wavelength")

This is not much code, yet it already gives us something pretty useful. If we open such a power meter, we can see how its wavelength facet behaves:

>>> pm.wavelength
<Quantity(852.0, 'nanometer')
>>> pm.wavelength = '532 nm'
>>> pm.wavelength
<Quantity(532.0, 'nanometer')
>>> pm.wavelength = 1064
DimensionalityError: Cannot convert from 'dimensionless' to 'nanometer'

As you can see, the facet automatically parses and checks units, and converts any ints to floats. We could also specify the allowed wavelength range if we wanted to.

You’ll notice the code above uses SCPI_Facet, which is a helper function for creating facets that use SCPI messaging standards. When we enter pm.wavelength, it sends the message "sense:corr:wav?" to the device, reads its response, and converts it into the proper type and units. Similarly, when we enter pm.wavelength = '532 nm', it sends the message "sense:corr:wav 532" to the device.

If you’re using a message-based device with slightly different message format, it’s easy to write your own wrapper function that calls MessageFacet. Check out the source of SCPI_Facet to see how this is done. It’s frequently useful to write a helper function like this for a given driver, even if it’s not message-based.

Facets are partially inspired by the Lantz concept of Features (or ‘Feats’).

API

class instrumental.drivers.Facet(fget=None, fset=None, doc=None, cached=False, type=None, units=None, value=None, limits=None, name=None)

Property-like class representing an attribute of an instrument.

Parameters
  • fget (callable, optional) – A function to be used for getting the facet’s value.

  • fset (callable, optional) – A function to be used for setting the facet’s value.

  • doc (str, optional) – Docstring to describe the facet

  • cached (bool, optional) – Whether the facet should use caching. If True, the repeated writes of the same value will only write to the instrument once, while repeated reads of a value will only query the instrument once. Therefore, one should be careful to use caching only when it makes sense. Caching can be disabled on a per-get or per-set basis by using the use_cache parameter to get_value() or set_value().

  • type (callable, optional) – Type of the outward-facing value of the facet. Typically an actual type like int, but can be any callable that converts a value to the proper type.

  • units (pint.Units or corresponding str) – Physical units of the facet’s value. Used for converting both user input (when setting) and the output of fget (when getting).

  • value (dict-like, optional) – A map from ‘external’ values to ‘internal’ facet values. Used internally to convert input values for use with fset and to convert values returned by fget into ‘nice’ values fit force user consumption.

  • limits (sequence, optional) – Limits specified in [stop], [start, stop], or [start, stop, step] format. When given, raises a ValueError if a user tries to set a value that is out of range. step, if given, is used to round an in-range value before passing it to fset.

instrumental.drivers.MessageFacet(get_msg=None, set_msg=None, convert=None, **kwds)

Convenience function for creating message-based Facets.

Creates fget and fset functions that are passed to Facet, based on message templates. This is primarily used for writing your own Facet-creating helper functions for message-based drivers that have a unique message format. For standard SCPI-style messages, you can use SCPI_Facet() directly.

This is for use with VisaMixin, as it assumes the instrument has write and query methods.

Parameters
  • get_msg (str, optional) – Message used to query the facet’s value. If omitted, getting is unsupported.

  • set_msg (str, optional) – Message used to set the facet’s value. This string is filled in with set_msg.format(value), where value is the user-given value being set.

  • convert (function or callable) – Function that converts both the string returned by querying the instrument and the set-value before it is passed to str.format(). Usually something like int or float.

  • **kwds – Any other keywords are passed along to the Facet constructor

instrumental.drivers.SCPI_Facet(msg, convert=None, readonly=False, **kwds)

Facet factory for use in VisaMixin subclasses that use SCPI messages

Parameters
  • msg (str) – Base message used to create SCPI get- and set-messages. For example, if msg='voltage', the get-message is 'voltage?' and the set-message becomes 'voltage {}', where {} gets filled in by the value being set.

  • convert (function or callable) – Function that converts both the string returned by querying the instrument and the set-value before it is passed to str.format(). Usually something like int or float.

  • readonly (bool, optional) – Whether the Facet should be read-only.

  • **kwds – Any other keywords are passed along to the Facet constructor

API Documentation

Driver Utils

class instrumental.drivers.Instrument(inst=None, **kwds)

Base class for all instruments.

_after_init()

Called just after _initialize

_before_init()

Called just before _initialize

classmethod _create(paramset, **other_attrs)

Factory method meant to be used by instrument()

_fill_out_paramset()
_initialize(**settings)
_load_state(state_path=None)

Load instrument state from a pickle file

_save_state(state_path=None)

Save instrument state to a pickle file

close()
get(facet_name, use_cache=False)
observe(name, callback)

Add a callback to observe changes in a facet’s value

The callback should be a callable accepting a ChangeEvent as its only argument. This ChangeEvent is a namedtuple with name, old, and new fields. name is the facet’s name, old is the old value, and new is the new value.

save_instrument(name, force=False)

Save an entry for this instrument in the config file.

Parameters
  • name (str) – The name to give the instrument, e.g. ‘myCam’

  • force (bool, optional) – Force overwrite of the old entry for instrument name. By default, Instrumental will raise an exception if you try to write to a name that’s already taken. If force is True, the old entry will be commented out (with a warning given) and a new entry will be written.

_abc_impl = <_abc_data object>
_all_instances = {}
_instances = <_weakrefset.WeakSet object>
_prop_funcs = {}
_props = []
property _state_path
instrumental.drivers.instrument(inst=None, **kwargs)

Create any Instrumental instrument object from an alias, parameters, or an existing instrument.

reopen_policystr of (‘strict’, ‘reuse’, ‘new’), optional

How to handle the reopening of an existing instrument. ‘strict’ - disallow reopening an instrument with an existing instance which hasn’t been cleaned up yet. ‘reuse’ - if an instrument is being reopened, return the existing instance ‘new’ - always create a new instance of the instrument class. Not recommended unless you know exactly what you’re doing. The instrument objects are not synchronized with one another. By default, follows the ‘reuse’ policy.

>>> inst1 = instrument('MYAFG')
>>> inst2 = instrument(visa_address='TCPIP::192.168.1.34::INSTR')
>>> inst3 = instrument({'visa_address': 'TCPIP:192.168.1.35::INSTR'})
>>> inst4 = instrument(inst1)
instrumental.drivers.list_instruments(server=None, module=None, blacklist=None)

Returns a list of info about available instruments.

May take a few seconds because it must poll hardware devices.

It actually returns a list of specialized dict objects that contain parameters needed to create an instance of the given instrument. You can then get the actual instrument by passing the dict to instrument().

>>> inst_list = get_instruments()
>>> print(inst_list)
[<NIDAQ 'Dev1'>, <TEKTRONIX 'TDS 3032'>, <TEKTRONIX 'AFG3021B'>]
>>> inst = instrument(inst_list[0])
Parameters
  • server (str, optional) – The remote Instrumental server to query. It can be an alias from your instrumental.conf file, or a str of the form (hostname|ip-address)[:port], e.g. ‘192.168.1.10:12345’. Is None by default, meaning search on the local machine.

  • blacklist (list or str, optional) – A str or list of strs indicating driver modules which should not be queried for instruments. Strings should be in the format 'subpackage.module', e.g. 'cameras.pco'. This is useful for very slow-loading drivers whose instruments no longer need to be listed (but may still be in use otherwise). This can be set permanently in your instrumental.conf.

  • module (str, optional) – A str to filter what driver modules are checked. A driver module gets checked only if it contains the substring module in its full name. The full name includes both the driver group and the module, e.g. 'cameras.pco'.

instrumental.drivers.list_visa_instruments()

Returns a list of info about available VISA instruments.

May take a few seconds because it must poll the network.

It actually returns a list of specialized dict objects that contain parameters needed to create an instance of the given instrument. You can then get the actual instrument by passing the dict to instrument().

>>> inst_list = get_visa_instruments()
>>> print(inst_list)
[<TEKTRONIX 'TDS 3032'>, <TEKTRONIX 'AFG3021B'>]
>>> inst = instrument(inst_list[0])
class instrumental.drivers.ParamSet(cls=None, **params)
__init__(cls=None, **params)
create(**settings)
get(key, default=None)
items()
keys()
lazyupdate(other)

Add values from other for keys that are missing

matches(other)

True iff all common keys have matching values

to_ini(name)
update(other)
values()
class instrumental.drivers.VisaMixin(inst=None, **kwds)
_end_transaction()
_flush_message_queue()

Write all queued messages at once

_start_transaction()
query(message, *args, **kwds)

Query the instrument’s VISA resource with message

Flushes the message queue if called within a transaction.

transaction()

Transaction context manager to auto-chain VISA messages

Queues individual messages written with the write() method and sends them all at once, joined by ‘;’. Messages are actually sent (1) when a call to query() is made and (2) upon the end of transaction.

This is especially useful when using higher-level functions that call write(), as it lets you combine multiple logical operations into a single message (if only using writes), which can be faster than sending lots of little messages.

Be cognizant that a visa resource’s write and query methods are not transaction-aware, only VisaMixin’s are. If you need to call one of these methods (e.g. write_raw), make sure you flush the message queue manually with _flush_message_queue().

As an example:

>>> with myinst.transaction():
...     myinst.write('A')
...     myinst.write('B')
...     myinst.query('C?')  # Query forces flush. Writes "A;B" and queries "C?"
...     myinst.write('D')
...     myinst.write('E')  # End of transaction block, writes "D;E"
write(message, *args, **kwds)

Write a string message to the instrument’s VISA resource

Calls format(*args, **kwds) to format the message. This allows for clean inclusion of parameters. For example:

>>> inst.write('source{}:value {}', channel, value)
_abc_impl = <_abc_data object>
property _in_transaction
_instances = <_weakrefset.WeakSet object>
_prop_funcs = {}
_props = []
property resource

VISA resource

instrumental.drivers._get_visa_instrument(params)

Returns the VISA instrument corresponding to ‘visa_address’. Uses caching to avoid multiple network accesses.

Helpful utilities for writing drivers.

instrumental.drivers.util.as_enum(enum_type, arg)

Check if arg is an instance or key of enum_type, and return that enum

instrumental.drivers.util.check_enums(**kw_args)

Decorator to type-check input arguments as enums.

Allows strings and anything that can be passed to as_enum.

@check_enums(mode=SampleMode)
def set_mode(mode):
    pass  # `mode` will be of type SampleMode
instrumental.drivers.util.check_units(*pos, **named)

Decorator to enforce the dimensionality of input args and return values.

Allows strings and anything that can be passed as a single arg to pint.Quantity.

@check_units(value='V')
def set_voltage(value):
    pass  # `value` will be a pint.Quantity with Volt-like units
instrumental.drivers.util.unit_mag(*pos, **named)

Decorator to extract the magnitudes of input args and return values.

Allows strings and anything that can be passed as a single arg to pint.Quantity.

@unit_mag(value='V')
def set_voltage(value):
    pass  # The input must be in Volt-like units and `value` will be a raw number
          # expressing the magnitude in Volts
instrumental.drivers.util.visa_timeout_context(resource, timeout)

Context manager for temporarily setting a visa resource’s timeout.

with visa_timeout_context(rsrc, 100):
     ...  # `rsrc` will have a timeout of 100 ms within this block

Fitting

Module containing utilities related to fitting.

Still very much a work in progress…

instrumental.fitting.curve_fit(f, xdata, ydata, p0=None, sigma=None, **kw)

Wrapper for scipy’s curve_fit that works with pint Quantities.

instrumental.fitting.guided_decay_fit(data_x, data_y)

Guided fit of a ringdown. Takes data_x and data_y as pint Quantities with dimensions of time and voltage, respectively. Plots the data and asks user to manually crop to select the region to fit.

It then does a rough linear fit to find initial parameters and performs a nonlinear fit.

Finally, it plots the data with the curve fit overlayed and returns the full-width at half-max (FWHM) with units.

instrumental.fitting.guided_ringdown_fit(data_x, data_y)

Guided fit of a ringdown. Takes data_x and data_y as pint Quantities with dimensions of time and voltage, respectively. Plots the data and asks user to manually crop to select the region to fit.

It then does a rough linear fit to find initial parameters and performs a nonlinear fit.

Finally, it plots the data with the curve fit overlayed and returns the full-width at half-max (FWHM) with units.

instrumental.fitting.guided_trace_fit(data_x, data_y, EOM_freq)

Guided fit of a cavity scan trace that has sidebands. Takes data_x and data_y as pint Quantities, and the EOM frequency EOM_freq can be anything that the pint.Quantity constructor understands, like an existing pint.Quantity or a string, e.g. '5 MHz'.

It plots the data then asks the user to identify the three maxima by by clicking on them in left-to-right order. It then uses that input to estimate and then do a nonlinear fit of the parameters.

Finally, it plots the data with the curve fit overlayed and returns the parameters in a map.

The parameters are A0, B0, FWHM, nu0, and dnu.

instrumental.fitting.lorentzian(x, A, x0, FWHM)

Lorentzian curve. Takes an array x and returns an array \frac{A}{1 + (\frac{2(x-x0)}{FWHM})^2}

instrumental.fitting.triple_lorentzian(nu, A0, B0, FWHM, nu0, dnu, y0)

Triple lorentzian curve. Takes an array nu and returns an array that is the sum of three lorentzians lorentzian(nu, A0, nu0, FWHM) + lorentzian(nu, B0, nu0-dnu, FWHM) + lorentzian(nu, B0, nu0+dnu, FWHM).

Plotting

Module that provides unit-aware plotting functions that can be used as a drop-in replacement for matplotlib.pyplot.

Also acts as a repository for useful plotting tools, like slider-plots.

instrumental.plotting.param_plot(x, func, params, **kwargs)

Plot a function with user-adjustable parameters.

Parameters
  • x (array_like) – Independent (x-axis) variable.

  • func (function) – Function that takes as its first argument an independent variable and as subsequent arguments takes parameters. It should return an output array the same dimension as x, which is plotted as the y-variable.

  • params (dict) – Dictionary whose keys are strings named exactly as the parameter arguments to func are. [More info on options]

Returns

final_params – A dict whose keys are the same as params and whose values correspond to the values selected by the slider. final_params will continue to change until the figure is closed, at which point it has the final parameter values the user chose. This is useful for hand-fitting curves.

Return type

dict

instrumental.plotting.plot(*args, **kwargs)

Quantity-aware wrapper of pyplot.plot

instrumental.plotting.xlabel(s, *args, **kwargs)

Quantity-aware wrapper of pyplot.xlabel

Automatically adds parenthesized units to the end of s.

instrumental.plotting.ylabel(s, *args, **kwargs)

Quantity-aware wrapper of pyplot.ylabel.

Automatically adds parenthesized units to the end of s.

Tools

class instrumental.tools.DataSession(name, meas_gen, overwrite=False)

A data-taking session.

Useful for organizing, saving, and live-plotting data while (automatically or manually) taking it.

__init__(name, meas_gen, overwrite=False)

Create a DataSession.

Parameters
  • name (str) – The name of the session. Used for naming the saved data file.

  • meas_gen (generator) – A generator that, when iterated through, returns individual measurements as dicts. Each dict key is a string that is the name of what’s being measured, and its matching value is the corresponding quantity. Most often you’ll want to create this generator by writing a generator function.

  • overwrite (bool) – If True, data with the same filename will be overwritten. Defaults to False.

create_plot(vars, **kwargs)

Create a plot of the DataSession.

This plot is live-updated with data points as you take them.

Parameters
  • vars (list of tuples) – vars to plot. Each tuple corresponds to a data series, with x-data, y-data, and optional format string. This is meant to be reminiscent of matplotlib’s plot function. The x and y data can each either be a string (representing the variable in the measurement dict with that name) or a function that takes kwargs with the name of those in the measurement dict and returns its computed value.

  • **kwargs (keyword arguments) – used for formatting the plot. These are passed directly to the plot function. Useful for e.g. setting the linewidth.

save_summary(overwrite=None)
start()

Start collecting data.

This function blocks until all data has been collected.

instrumental.tools.FSRs_from_mode_wavelengths(wavelengths)
instrumental.tools.diff(unitful_array)
instrumental.tools.do_ringdown_set(set_name, base_dir=None)
instrumental.tools.find_FSR()
instrumental.tools.fit_ringdown(scope, channel=1, FSR=None)
instrumental.tools.fit_ringdown_save(subdir='', trace_num=0, base_dir=None)

Read a trace from the scope, save it and fit a ringdown curve.

Parameters
  • subdir (string) – Subdirectory in which to save the data file.

  • trace_num (int) – An index indicating which trace it is.

  • base_dir (string) – The path of the toplevel data directory.

instrumental.tools.fit_scan(EOM_freq, scope, channel=1)
instrumental.tools.fit_scan_save(EOM_freq, subdir='', trace_num=0, base_dir=None)
instrumental.tools.get_photo_fnames()
instrumental.tools.load_data(fname, delimiter='\t')
instrumental.tools.qappend(arr, values, axis=None)

Append values to the end of an array-valued Quantity.

Developer’s Guide

This page is for those of you who enjoy diving into guidelines, coding conventions, and project philosophies. If you’re looking to get started more quickly, instead check out the Contributing and Writing Drivers pages.



Driver Status

Category

Driver

Documentation

instrument()

Python 3 Support

Cameras

PCO

Pixelfly

PVCam

UC480

TSI

DAQ

NI

Yes

Function Generators

Tektronix

Lasers

Femto Ferb

Lock-in Amplifiers

SR850

Motion

Kinesis

Multimeters

HP

Power Meters

Newport

Thorlabs

Oscilloscopes

Tektronix

Spectrometers

Thorlabs

Bristol

Wavemeters

Burleigh

Release Instructions

Here’s some info on what needs to be done before each release:

  • Update the CHANGELOG (add any needed entries and update the version heading)

  • Update the version number in __about__.py

  • Run python -m instrumental.parse_modules from the Instrumental directory to regenerate driver_info.py

  • [Locally build and review the documentation]

  • Verify the PyPI description (generated from README.rst) is valid:

    • python setup.py sdist

    • twine check dist/*

  • Commit and push these changes

  • Wait to verify that the builds, tests, and documentation builds all succeed

  • Tag the commit with the version number and push the tag

    • git tag -m "Release 0.x" 0.x

    • git push --tags

  • Set up the release info on GitHub

    • Go to releases, “Draft a new release”

    • Choose the newly pushed tag

    • Copy in the CHANGELOG section for this release, convert headings to use ###

    • Check “This is a pre-release”

    • Click “Publish release” (DO NOT save as a draft. This will mess up the Github Actions you’ll never publish to TestPyPI)

    • Wait for PyPI GitHub action to run, verify upload succeeded

    • Uncheck “This is a pre-release” and re-publish

    • Verify that the PyPI GitHub action has deployed to the real PyPI


The Instrumental Manifesto

A major goal of Instrumental is to try to unify and simplify a lot of common, useful operations. Essential to that is a consistent and coherent interface.

  • Simple, common tasks should be simple to perform

  • Options should be provided to enable more complex tasks

  • Documentation is essential

  • Use of physical units should be standard and widespread

Simple, common tasks should be simple to perform

Tasks that are conceptually simple or commonly performed should be made easy. This means having sane defaults.

Options should be provided to enable more complex tasks

Along with sane defaults, provide options. Typically, this means providing optional parameters in functions and methods.

Documentation is essential

Providing Documentation can be tiring or boring, but, without it, your carefully crafted interfaces can be opaque to others (including future-you). In particular, all functions and methods should have brief summary sentences, detailed explanations, and descriptions of their parameters and return values.

This also includes providing useful error messages and warnings that the average user can actually understand and do something with.

Use of physical units should be standard and widespread

Units in scientific code can be a big issue. Instrumental incorporates unitful quantities using the very nice Pint package. While units are great, it can seem like extra work to start using them. Instrumental strives to use units everywhere to encourage their widespread use.


Coding Conventions

As with most Python projects, you should be keeping in mind the style suggestions in PEP8. In particular:

  • Use 4 spaces per indent (not tabs!)

  • Classes should be named using CapWords capitalization

  • Functions and methods should be named using lower_case_with_underscores

    • As an exception, python wrapper (e.g. cffi/ctypes) code used as a _thin_ wrapper to an underlying library may stick with its naming convention for functions/methods. (See the docs for Attocube stages for an example of this)

  • Modules and packages should have short, all-lowercase names, e.g. drivers

  • Use a _leading_underscore for non-public functions, methods, variables, etc.

  • Module-level constants are written in ALL_CAPS

Strongly consider using a plugin for your text editor (e.g. vim-flake8) to check your PEP8 compliance.

It is OK to have lines over 80 characters, though they should almost always be 100 characters or less.


Docstrings

Code in Instrumental is primarily documented using python docstrings, following the numpydoc conventions. In general, you should also follow the guidelines of pep 257.

  • No spaces after the opening triple-quote

  • One-line docstrings should be on a single line, e.g. """Does good stuff."""

  • Multi-liners have a summary line, followed by a blank line, followed by the rest of the doc. The closing quote should be on its own line


Python 2/3 Compatibility

Instrumental was originally developed for Python 2.7 and long maintained Python 2/3 cross compatibility. As of release 0.7, we haved dropped that support and now require Python 3.7+. This means all future development should target Python 3.

Python 2 support may be removed from existing code as a part of future development, though this is not currently a priority.


Developing Drivers

If you’re considering writing a driver, thank you! Check out Writing Drivers for details.