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 arraytests/test1/positions
: array of the position of all axis at each pointtests/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:
- 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.- A
threading.Event
object may be passed to the constructor’sscan.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.- 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))