Quickstart: setting up and using scans in MicroscoPy

Creating HAL-compliant devices

For example, assuming the ESP and Andor drivers are installed on your system:

>>> import newportESP, andor2
>>> esp_controller = newportESP.ESP('/dev/ttyUSB0')
>>> z_driver = esp_controller.axis()
>>> cam_driver = andor2.Andor()

This is very specific to your hardware, and you may be using different hardware or a different driver. The hal API abstracts away all these differences:

>>> from microscopy.device_adapters import actuators, sensors
>>> z = actuators.ESP(z_driver)      # create an Actuator object
>>> cam = sensors.Andor(cam_driver)  # create a Sensor object

z and cam are tranparently interchangeable with any other hal.Actuator or hal.Sensor objects.

Creating and using microscope scans

>>> from microscopy.scan import Scan1D, ScanND

scan.Scan1D() will create a 1D scan. For example, a Z-scan starting at a height of 10mm, going down by 0.6mm in steps of 10um:

>>> zscan = Scan1D((10, -0.6, -0.01), cam, z)

Note

the scan range tuple is of the form (start, **length**, step), not (start, **stop**, step).

The scan can be started immediately:

>>> zscan.run()             # in the main thread     | pause with Ctrl+C
>>> zscan.run(thread=True)  # in a background thread | pause with zscan.pause()

The first invocation blocks the IPython console, where the scan progress will be indicated. Press Ctrl+C to pause the scan. Simply call zscan.run() again to resume the scan. The second invocation will start the scan in a background thread so the console remain usable. The scan can still be paused:

>>> zscan.pause()

and call zscan.run(thread=True) again to resume. At any time one can check on the scan progress:

>>> zscan.ax.print_position()

To create a multidimensional scan, simply define each scan axis separately and combine them using scan.ScanND():

>>> xscan = Scan1D((-1, 2, 0.008), h.cam, h.x)  # X
>>> yscan = Scan1D((-1, 2, 0.008), h.cam, h.y)  # Y
>>> xyzscan = ScanND((zscan, yscan, xscan))     # XYZ 3D scan

Note that the first scan will be the slowest.

Saving scan data and metadata

Data are saved as plain multidimensional arrays in HDF5 format:

>>> filename = '2015_12_14_Example.h5'
>>> xyzscan.save(filename, 'tests/test1', comment='A test XYZ scan')
This create three datasets:
  • tests/test1/spectra: the actual data array
  • tests/test1/positions: array of the position of all axis at each point
  • tests/test1/info: array of point-specific metadata (eg, exposure time)

Scan-wide metadata (scan description, start/end times...) are saved as attributes of tests/test1. See the h5py module for details about the file format.

Advanced topic: Extending scan functionality and interfacing with other Python objects

Several hooks are provided:

  1. Attributes scan.Scan1D.at_every_point, scan.Scan1D.at_start, scan.Scan1D.at_end are list of functions that will be called after every sensor acquisition, at the beginning of a scan, and at the end of a scan, respectively. The functions must have no arguments. They can be passed to the constructor as keyword arguments, or appended to/removed from the attributes at any time with immediate effect.
  2. A threading.Event object may be passed to the constructor’s scan.Scan1D.iteration_event keyword. The Event will be set at after every sensor acquisition to notify any object that may be waiting on the Event.
  3. A function returning a bool may be passed to the optional check argument The function will be called after every point, and the scan will be terminated if True is returned.

Method 1 is more suitable to trigger actions that will take place in the same thread as the scan, while Method 2 allows interaction with objects in a different thread.

For example, let’s assume that control is a callable object that needs to be called between every acquired data point to do some stuff (e.g., track the sample position). The scan would be created as:

>>> scan = Scan1D((10, -0.6, -0.01), cam, ax, at_every_point=[control])

The built-in scan.Stepper makes a stair-like scan (instead of a rectangular one) thus:

>>> xscan = Scan1D((start, length, stop), cam, ax, at_end=[Stepper(step, n)])
>>> yscan = Scan1D(...)
>>> xyscan = ScanND((yscan, xscan))