.. _microscopy_quickstart: ==================================================== 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 :mod:`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 :class:`hal.Actuator` or :class:`hal.Sensor` objects. .. _microscopy_scan_tutorial: Creating and using microscope scans =================================== >>> from microscopy.scan import Scan1D, ScanND :func:`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 :func:`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 :class:`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 :attr:`scan.Scan1D.at_every_point`, :attr:`scan.Scan1D.at_start`, :attr:`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 :class:`threading.Event` object may be passed to the constructor's :attr:`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 :class:`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))