Basics of device communication

The devices are represented as Python objects. In most cases, one object controls one device, although sometimes one object can be responsible for multiple interconnected devices (e.g., when daisy-chaining of several devices is used, as in Picomotor stage). All the device control functions are contained within the class. Occasionally, there are auxiliary function present for listing available devices, dealing with data generated by the device, or adjusting global parameters.

Note

Some specific devices functionality might not be completely covered in the current release. If this is the case for your device, you can let the developers know by raising an issue on GitHub, or sending an e-mail to pylablib@gmail.com.

Connection

The device identifier or address needs to be provided upon the device object creation, after which it is automatically connected. Getting the address usually depends on the kind of device:

  • Simple message-style devices, such as AWG, oscilloscopes, sensors and gauges, require an address which depends on the exact connection protocol. For example, serial devices addresses look like "COM1", Visa addresses as "USB0::0x1313::0x8070::000000::INSTR", and network addresses take IP and, possibly, port "192.168.1.3:7230". To get the list of all connected devices, you can run comm_backend.list_backend_resources():

    >> import pylablib as pll
    >> pll.list_backend_resources("serial")  # list serial port resources
    ['COM38', 'COM1', 'COM36', 'COM3']
    >> pll.list_backend_resources("visa")  # note that, by default, visa also includes all the COM ports
    ('USB0::0x1313::0x8070::000000::INSTR',
    'ASRL1::INSTR',
    'ASRL3::INSTR',
    'ASRL10::INSTR',
    'ASRL36::INSTR',
    'ASRL38::INSTR')
    

    Network devices do not easily provide such functionality (and there are, in principle, many unrelated devices connected to the network), so you might need to learn the device IP elsewhere. Usually, it is set on the device front panel or using some kind of configuration tool and a different connection, such as serial or USB.

    In most cases, the connection address is all you need. However, sometimes the connection might require some additional information. The most common situations are ports for the network connection and baud rates for the serial connections. Ports can be supplied either as a part of the string "192.168.1.3:7230", or as a tuple ("192.168.1.3", 7230). The baud rates are, similarly, provided as a tuple: ("COM1", 19200). By default, the devices would use the baud rate which is most common for them, but in some cases (e.g., if the device baud rate can be changed), you might need to provide it explicitly. If it is provided incorrectly, then no communication can be done, and requests will typically return a timeout error:

    >> from pylablib.devices import Ophir
    >> meter = Ophir.VegaPowerMeter("COM3")  # for this power meter 9600 baud are used by default
    >> meter.get_power()  # let us assume that the devices is currently set up with 38400 baud
    ...
    OphirBackendError: backend exception: 'timeout during read'
    >> meter.close()  # need to close the connection before reopening
    >> meter = Ophir.VegaPowerMeter(("COM3",38400))  # explicitly specifying the correct baud rate
    >> meter.get_power()
    1E-6
    
  • More complicated devices using custom DLLs (usually cameras or some translation stages) will have more unique methods of addressing individual devices: serial number, device index, device ID, etc. In most cases such devices come with list_devices or get_devices_number functions, which give the necessary information.

After communication is done, the connection needs to be closed, since in most cases it can only be opened in one program or part of the script at a time. It also implies that usually it’s impossible to connect to the device while its manufacturer software is still running.

The devices have open and close methods, but they can also work in together with Python with statements:

# import Thorlabs device classes
from pylablib.devices import Thorlabs

# connect to FW102 motorized filter wheel
wheel = Thorlabs.FW("COM1")
# set the position
wheel.set_position(1)
# close the connection (until that it's impossible to establish a different connection to this device)
wheel.close()

# a better approach
with Thorlabs.FW("COM1") as wheel: # connection is closed automatically when leaving the with-block
    wheel.set_position(1)

Because the devices are automatically connected on creation, open method is almost never called explicitly. It is generally only used to reconnect to the device after the connection has been previously closed, although in this case creating a new device object would work just as well.

Operation

The devices are controlled by calling their methods; attributes and properties are very rarely used. Effort is made to maintain consistent naming conventions, e.g., most getter-methods will start with get_ and setter methods with set_ or setup_ (depending on the complexity of the method). It is also common for setter methods to return the new value as a result, which is useful in CLI operation and debugging. Devices of the same kind have the same names for similar or identical functions: most stages have move_by, jog and stop methods, and cameras have wait_for_frame and read_multiple_images methods. Whenever it makes sense, these methods will also have the same signatures.

Asynchronous operation and multi-threading

For simplicity of usage and construction, devices interfaces are designed to be synchronous and single-threaded. Asynchronous operation can be achieved by explicit usage of Python multi-threading. Furthermore, the device classes are not designed to be thread safe, i.e., it is not recommended to use the same device simultaneously from two separate threads. However, non-simultaneous calling of device methods from different threads (synchronized, e.g., using locks) or simultaneous usage of several separate devices of the same class is supported.

Error handling

Errors raised by the devices are usually specific to the device and manufacturer, e.g., AttocubeError or TrinamicError. These can be obtained from the module containing the device class, or from the class itself as Error attribute:

>> from pylablib.devices import Attocube
>> atc = Attocube.ANC300("192.168.1.1")
>> atc.disable_axis(1)
>> atc.move_by(1,10)  # move on a disabled axis raises an error for ANC300
...
AttocubeError: Axis in wrong mode
>> try:
..     atc.move_by(1,10)
.. except atc.Error:  # could also write "except Attocube.AttocubeError"
..     print("Can not move")
Can not move

All of the device errors inherit from DeviceError, which in turn is a subclass of RuntimeError. Therefore, one can also use those exception classes instead:

>> import pylablib as pll
>> try:
..     atc.move_by(1,10)
.. except pll.DeviceError:
..     print("Can not move")
Can not move

Getting more information

A lot of information about the devices can be gained just from their method names and descriptions (docstrings). There are several ways of getting these:

  • In many cases your IDE (PyCharm, Spyder, VS Code with installed Python extension) supports code inspection. In this case, the list of methods will usually pop up after you time the device object name and a dot (such as cam.), and the method docstring will show up after you type the method name and parenthesis (such as cam.get_roi(). However, sometimes it might take a while for these pop-ups to show up.
  • You can use console, such as Jupyter QtConsole, Jupyter Notebook, or a similar console built into the IDE. Here the list of methods can be obtained using the autocomplete feature: type name of the class or object with a dot (such as cam.) and then press Tab. The list of all methods should appear. To get the description of a particular class or method, type it with a question mark (such as cam? or cam.get_roi?) and execute the result (Enter or Shift-Enter, depending on the console). A description should appear with the argument names and the description.
  • You can also use the auto-generated documentation within this manual through the search bar: simply type the name of the class or the method (such as AndorSDK3Camera or AndorSDK3Camera.get_roi) and look through the results. However, the formatting of the auto-generated documentation might be a bit overwhelming.

Universal settings access

All devices have get_settings and apply_settings methods which, correspondingly, return Python dictionaries with the most common settings or take these dictionaries and apply the contained settings. These can be used to easily store and re-apply device configuration within a script.

Additionally, there is get_full_info method, which returns as complete information as possible. It is particularly useful to check the device status and see if it is connected and working properly, and to save the devices configuration when acquiring the data. Finally, the settings can also be accessed through .dv attribute, which provides dictionary-like interface:

>>> wheel = Thorlabs.FW("COM1")  # connect to FW102 motorized filter wheel
>>> wheel.get_position()
1
>>> wheel.get_settings()
{'pcount': 6,
'pos': 1,
'sensors_mode': 'off',
'speed_mode': 'high',
'trigger_mode': 'in'}
>>> wheel.dv["pos"]
1
>>> wheel.apply_settings({"pos":2})
>>> wheel.get_position()
2
>>> wheel.dv["pos"] = 3
>>> wheel.get_position()
3
>>> wheel.close()

By default not all information is shown, as it can take long time (up to several seconds) to obtain it, and it takes a lot of space on the screen. To get a full set of parameters, you can call get_full_info(-10) (-10 here specifies the parameter priority threshold, which is 0 by default, and has the lowest value of -10):

>> cam = IMAQdx.IMAQdxCamera()
>> cam.get_full_info()
{   'roi': (0, 1312, 0, 1082),
    'acquisition_in_progress': False,
    'frames_status': TFramesStatus(acquired=0, unread=0, skipped=0, buffer_size=0),
    'cls': 'IMAQdxCamera',
    'conn': 'cam0',
    'detector_size': (1312, 1082),
    'device_info': TDeviceInfo(vendor='Photonfocus AG', model='HD1-D1312-80-G2-12', serial_number='0000000000000000', bus_type='Ethernet')  }
>>
>> cam.get_full_info(-10)
{   'roi': (0, 1312, 0, 1082),
    'acquisition_in_progress': False,
    'frames_status': TFramesStatus(acquired=0, unread=0, skipped=0, buffer_size=0),
    'camera_attributes': Dictionary('AcquisitionAttributes/AdvancedEthernet/BandwidthControl/ActualPeakBandwidth': 1000.0
        ... lots and lots of attributes
    'OffsetX': 0
    'OffsetY': 0
    'PayloadSize': 1419584
    'PixelFormat': Mono8
    'Width': 1312),
    'cls': 'IMAQdxCamera',
    'conn': 'cam0',
    'detector_size': (1312, 1082),
    'device_info': TDeviceInfo(vendor='Photonfocus AG', model='HD1-D1312-80-G2-12', serial_number='0000000000000000', bus_type='Ethernet')  }

Dependencies and external software

Many devices require external software not provided with this package.

The simpler devices using serial connection (either with an external USB-to-Serial adapter, or with a similar built-in chip) only need the corresponding drivers: either standard adapter drivers or the ones supplied by the manufacturer, e.g., via Thorlabs APT software. If the device already shows up as a serial communication port in the OS, no additional software is normally needed. Similarly, devices using Ethernet connection do not need any external software, as long as they are properly connected to the network. Finally, devices using Visa connection require NI VISA Runtime, which is freely available from the National Instruments website. See also PyVISA documentation for details.

Devices which require manufacturer DLLs are harder to set up. For most of them, at the very least, you need to install the manufacturer-provided software for communication. Frequently it already includes the necessary libraries, which means that nothing else is required. However, sometimes you would need to download either an additional SDK package, or DLLs directly from the website. Since these libraries take a lot of space and are often proprietary, they are not distributed with the pylablib.

Note that DLLs can have 32-bit and 64-bit version, and this version should agree with the Python version that you use. Unless you have a really good reason to do otherwise, it is strongly recommended to use 64-bit Python, which means that you would need 64-bit DLLs, which is the standard in most cases these days. To check your Python bitness, you can read the prompt when running the Python console, or run python -c "import platform; print(platform.architecture()[0])" in the command line.

In addition, you need to provide pylablib with the path to the DLLs. In many cases it checks the standard locations such as the default System32 folder (used, e.g., in DCAM or IMAQ cameras), paths contained on the PATH environment variable, or defaults paths for manufacturer software (such as C:/Program Files/Andor SOLIS for Andor cameras). If the software path is different, or if you choose to obtain DLLs elsewhere, you can also explicitly provide path by setting the library parameter:

import pylablib as pll
pll.par["devices/dlls/andor_sdk3"] = "D:/Program Files/Andor SOLIS"
from pylablib.devices import Andor
cam = Andor.AndorSDK3Camera()

All of these requirements are described in detail for the specific devices.

Starting from Python 3.8 the DLL search path is changed to not include the files contained in PATH environment variable and in the script folder. By default, this behavior is still emulated when pylablib searches for the DLLs, since it is required in some cases (e.g., Photon Focus pfcam interface). If needed, it can be turned off (i.e., switched to the new default behavior of Python 3.8+) by setting pll.par["devices/dlls/add_environ_paths"]=False.

Advanced examples

Connecting to a Cryomagnetics LM500 level meter and reading out the level at the first channel:

from pylablib.devices import Cryomagnetics  # import the device library
with Cryomagnetics.LM500("COM1") as lm:
    level = lm.get_level(1)  # read the level

Stepping the M Squared laser wavelength and recording an image from the Andor iXon camera at each step:

with M2.Solstis("192.168.1.2", 34567) as laser, Andor.AndorSDK2Camera() as cam:  # connect to the devices
    # change some camera parameters
    cam.set_exposure(50E-3)
    cam.set_roi(0, 128, 0, 128, hbin=2, vbin=2)
    cam.setup_shutter("open")
    # start camera acquisition
    wavelength = 770E-9  # initial wavelength (in meters)
    images = []
    cam.start_acquisition()
    while wavelength < 780E-9:
        laser.coarse_tune_wavelength(wavelength)  # tune the laser frequency (using coarse tuning)
        time.sleep(0.5)  # wait until the laser stabilizes
        cam.wait_for_frame()  # ensure that there's a frame in the camera queue
        img = cam.read_newest_image()
        images.append(img)
        wavelength += 0.5E-9

Available devices

  • Cameras

    • Andor SDK2 and Andor SDK3: variety of Andor (currently part of Oxford Instruments) cameras. Tested with Andor iXon, Luca, and Zyla.
    • DCAM: Hamamatsu cameras. Tested with Hamamatsu Orca Flash 4.0 and ImagEM.
    • NI IMAQ: National Instruments frame grabbers. Tested with NI PCI-1430 and PCI-1433 frame grabbers together with PhotonFocus MV-D1024E camera.
    • NI IMAQdx: National Instruments universal camera interface. Tested with Ethernet-connected PhotonFocus HD1-D1312 camera.
    • Photon Focus: Photon Focus pfcam interface. Tested with PhotonFocus MV-D1024E camera connected through either NI frame grabbers (PCI-1430 and PCI-1433) or Silicon Software frame grabbers (microEnable IV AD4-CL).
    • PCO SC2: PCO cameras. Tested with pco.edge cameras with CLHS and regular CameraLink interfaces.
    • Silicon Software: Silicon Software frame grabbers. Tested with microEnable IV AD4-CL frame grabbers together with PhotonFocus MV-D1024E camera.
    • Thorlabs Scientific Cameras: Thorlabs sCMOS cameras. Tested with Thorlabs Kiralux camera.
    • Uc480: multiple cameras, including simple Thorlabs and IDS cameras. Tested with IDS SC2592R12M and Thorlabs DCC1545M.
  • Stages

    • Attocube ANC300 and Attocube ANC350: most common Attocube positioner controllers. Tested with Ethernet and USB connection for ANC300, and USB connection for ANC350.
    • Thorlabs APT/Kinesis: basic Thorlabs motorized stages and optomechanics devices. Tested with KDC101 and K10CR1, as well as MFF101 and FW102 (described at a different page)
    • Newport Picomotor: precision piezo-actuated screws based on slip-stick principle. Tested with Newport 8742 Picomotor driver using Ethernet or USB connection.
    • Arcus Performax: fairly common single- and multi-axis motor controllers sold under different brands: Arcus, Nippon Pulse America, or Newmark Systems. Tested with PMX-4EX device with USB connection.
    • Trinamic: universal motor controllers and drivers. Tested with a single-axis TMCM-1110 controller with USB connection.
    • SmarAct: high-performance piezo sliders. So far only simple open-loop SCU controllers are supported. Tested with a standard HCU controller unit.
  • Basic sensors

    • HighFinesse: laser wavelength meters. Tested with WS6 and WS7 USB-controlled devices.
    • Ophir: optical power and energy meters. Tested with Ophir Vega.
    • Lakeshore: temperature sensors. Tested with Lakeshore 218.
    • Pfeiffer: pressure gauges. Tested with TPG261 and DPG202 controllers.
  • Lasers

  • Tektronix oscilloscopes. Tested with TDS2002B, TDS2004B, and DPO2004B.

  • NI DAQs. Tested with NI USB-6008, NI USB-6343, and NI PCIe-6323.

  • Generic AWGs. Tested with Agilent 33500 and 33220A, Rigol DG1022, Tektronix AFG1022, GW Instek AFG2225 and AFG2115, and RS Comp AFG21005.

  • Miscellaneous Thorlabs devices: MFF101/102 motorized flip mirror mount, FW102/212 motorized filter wheel, and MDT693/694 high-voltage source.

  • Miscellaneous OZOptics devices: EPC04 fiber polarization controller, DD100 motorized fiber attenuator, and TF100 motorized fiber filter.

  • Miscellaneous devices