{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Using an external master clock for hardware control of a stage-scanning high NA oblique plane microscope \n", "\n", "Tutorial provided by [qi2lab](https://www.shepherdlaboratory.org).\n", "\n", "This tutorial uses Pycro-Manager to rapidly acquire terabyte-scale volumetric images using external hardware triggering of a stage scan optimized, high numerical aperture (NA) oblique plane microscope (OPM). The microscope that this notebook controls is described in detail in this [preprint](https://www.biorxiv.org/content/10.1101/2020.04.07.030569v2), under the *stage scan OPM* section in the methods. \n", " \n", "This high NA OPM allows for versatile, high-resolution, and large field-of-view single molecule imaging. The main application is quantifying 3D spatial gene expression in millions of cells or large pieces of intact tissue using interative RNA-FISH (see examples [here](https://www.nature.com/articles/s41598-018-22297-7) and [here](https://www.nature.com/articles/s41598-019-43943-8)). Because the fluidics controller for the iterative labeling is also controlled via Python (code not provided here), using Pycro-Manager greatly simplifies controlling these complex experiments.\n", "\n", "The tutorial highlights the use of the `post_camera_hook_fn` and `post_hardware_hook_fn` functionality to allow an external controller to synchronize the microscope acquisition (external master). This is different from the standard hardware sequencing functionality in Pycro-Manager, where the acquisition engine sets up sequencable hardware and the camera serves as the master clock. \n", " \n", "The tutorial also discusses how to structure the events and avoid timeouts to acquire >10 million of events per acquistion." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Microscope hardware" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Briefly, the stage scan high NA OPM is built around a [bespoke tertiary objective](https://andrewgyork.github.io/high_na_single_objective_lightsheet/) designed by Alfred Millet-Sikking and Andrew York at Calico Labs. Stage scanning is performed by an ASI scan optimized XY stage, an ASI FTP Z stage, and an ASI Tiger controller with a programmable logic card. Excitation light is provided by a Coherent OBIS Laser Box. A custom Teensy based DAC synchronizes laser emission and a galvanometer mirror to the scan stage motion to eliminate motion blur. Emitted fluorescence is imaged by a Photometrics Prime BSI. \n", " \n", "The ASI Tiger controller is the master clock in this experiment. The custom Teensy DAC is setup in a closed loop with the Photometrics camera. This controller is detailed in a previous [publication](https://www.nature.com/articles/s41467-017-00514-7) on adaptive light sheet microscopy.\n", "\n", "The code to orthogonally deskew the acquired data and place it into a BigDataViewer HDF5 file that can be read stitched and fused using BigStitcher is found at the qi2lab (www.github.com/qi2lab/OPM/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initial setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pycromanager import Acquisition, Core\n", "import numpy as np\n", "from pathlib import Path\n", "from time import sleep" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Access Micro-Manager core" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define pycromanager specific hook functions for externally controlled hardware acquisition" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Post camera hook function to start external controller\n", "This is run once after the camera is put into active mode in the sequence acquisition. The stage starts moving on this command and outputs a TTL pulse to the camera when it passes the preset initial position. This TTL starts the camera running at the set exposure time using internal timing. The camera acts the master signal for the galvo/laser controller using its own \"exposure out\" signal." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def post_camera_hook_(event, event_queue):\n", "\n", " \"\"\"\n", " Run a set of commands after the camera is started\n", " \n", " :param event: current list of events, each a dictionary, to run in this hardware sequence\n", " :type event: list \n", " :param event_queue: thread-safe event queue\n", " :type event_queue: multiprocessing.Queue\n", "\n", " :return: event_queue\n", " \"\"\"\n", " \n", " # send Tiger command to start constant speed scan\n", " command='1SCAN'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", "\n", " return event" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Post hardware setup function to make sure external controller is ready\n", "This is run once after the acquisition engine sets up the hardware for the non-sequencable hardware, such as the height axis stage and channel." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def post_hardware_hook(event, event_queue):\n", " \n", " \"\"\"\n", " Run a set of commands after the hardware setup calls by acquisition engine are finished\n", " \n", " :param event: current list of events, each a dictionary, to run in this hardware sequence\n", " :type event: list\n", " :param event_queue: thread-safe event queue\n", " :type event_queue: multiprocessing.Queue\n", "\n", " :return: event_queue\n", " \"\"\"\n", " \n", " # turn on 'transmit repeated commands' for Tiger\n", " core.set_property('TigerCommHub','OnlySendSerialCommandOnChange','No')\n", "\n", " # check to make sure Tiger is not busy\n", " ready='B'\n", " while(ready!='N'):\n", " command = 'STATUS'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", " ready = core.get_property('TigerCommHub','SerialResponse')\n", " sleep(.500)\n", "\n", " # turn off 'transmit repeated commands' for Tiger\n", " core.set_property('TigerCommHub','OnlySendSerialCommandOnChange','Yes')\n", "\n", " return event" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Acquistion parameters set by user" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Select laser channels and powers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# lasers to use\n", "# 0 -> inactive\n", "# 1 -> active\n", "\n", "state_405 = 0\n", "state_488 = 0\n", "state_561 = 1\n", "state_635 = 0\n", "state_730 = 0\n", "\n", "# laser powers (0 -> 100%)\n", "\n", "power_405 = 0\n", "power_488 = 0\n", "power_561 = 0\n", "power_635 = 0\n", "power_730 = 0\n", "\n", "# construct arrays for laser informaton\n", "channel_states = [state_405,state_488,state_561,state_635,state_730]\n", "channel_powers = [power_405,power_488,power_561,power_635,power_730]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Camera parameters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# FOV parameters.\n", "# x size (256) is the Rayleigh length of oblique light sheet excitation\n", "# y size (1600) is the high quality lateral extent of the remote image system (~180 microns)\n", "# camera is oriented so that cropping the x size limits the number of readout rows and therefore lowering readout time\n", "ROI = [1024, 0, 256, 1600] #unit: pixels\n", "\n", "# camera exposure\n", "exposure_ms = 5 #unit: ms\n", "\n", "# camera pixel size\n", "pixel_size_um = .115 #unit: um" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stage scan parameters\n", "The user defines these by interactively moving the XY and Z stages around the sample. At the edges of the sample, the user records the positions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# distance between adjacent images.\n", "scan_axis_step_um = 0.2 #unit: um\n", "\n", "# scan axis limits. Use stage positions reported by Micromanager\n", "scan_axis_start_um = 0. #unit: um\n", "scan_axis_end_um = 5000. #unit: um\n", "\n", "# tile axis limits. Use stage positions reported by Micromanager\n", "tile_axis_start_um = 0. #unit: um\n", "tile_axis_end_um = 5000. #unit: um\n", "\n", "# height axis limits. Use stage positions reported by Micromanager\n", "height_axis_start_um = 0.#unit: um\n", "height_axis_end_um = 30. #unit: um" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Path to save acquistion data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "save_directory = Path('/path/to/save')\n", "save_name = 'test'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup hardware for stage scanning sample through oblique digitally scanned light sheet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Calculate stage limits and speeds from user provided scan parameters\n", "Here, the number of events along the scan (x) axis in each acquisition, the overlap between adajcent strips along the tile (y) axis, and the overlap between adajacent strips along the height (z) axis are all calculated." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# scan axis setup\n", "scan_axis_step_mm = scan_axis_step_um / 1000. #unit: mm\n", "scan_axis_start_mm = scan_axis_start_um / 1000. #unit: mm\n", "scan_axis_end_mm = scan_axis_end_um / 1000. #unit: mm\n", "scan_axis_range_um = np.abs(scan_axis_end_um-scan_axis_start_um) # unit: um\n", "scan_axis_range_mm = scan_axis_range_um / 1000 #unit: mm\n", "actual_exposure_s = actual_readout_ms / 1000. #unit: s\n", "scan_axis_speed = np.round(scan_axis_step_mm / actual_exposure_s,2) #unit: mm/s\n", "scan_axis_positions = np.rint(scan_axis_range_mm / scan_axis_step_mm).astype(int) #unit: number of positions\n", "\n", "# tile axis setup\n", "tile_axis_overlap=0.2 #unit: percentage\n", "tile_axis_range_um = np.abs(tile_axis_end_um - tile_axis_start_um) #unit: um\n", "tile_axis_range_mm = tile_axis_range_um / 1000 #unit: mm\n", "tile_axis_ROI = ROI[3]*pixel_size_um #unit: um\n", "tile_axis_step_um = np.round((tile_axis_ROI) * (1-tile_axis_overlap),2) #unit: um\n", "tile_axis_step_mm = tile_axis_step_um / 1000 #unit: mm\n", "tile_axis_positions = np.rint(tile_axis_range_mm / tile_axis_step_mm).astype(int) #unit: number of positions\n", "\n", "# if tile_axis_positions rounded to zero, make sure acquisition visits at least one position\n", "if tile_axis_positions == 0:\n", " tile_axis_positions=1\n", "\n", "# height axis setup\n", "# this is more complicated, because the excitation is an oblique light sheet\n", "# the height of the scan is the length of the ROI in the tilted direction * sin(tilt angle)\n", "height_axis_overlap=0.2 #unit: percentage\n", "height_axis_range_um = np.abs(height_axis_end_um-height_axis_start_um) #unit: um\n", "height_axis_range_mm = height_axis_range_um / 1000 #unit: mm\n", "height_axis_ROI = ROI[2]*pixel_size_um*np.sin(30*(np.pi/180.)) #unit: um\n", "height_axis_step_um = np.round((height_axis_ROI)*(1-height_axis_overlap),2) #unit: um\n", "height_axis_step_mm = height_axis_step_um / 1000 #unit: mm\n", "height_axis_positions = np.rint(height_axis_range_mm / height_axis_step_mm).astype(int) #unit: number of positions\n", "\n", "# if height_axis_positions rounded to zero, make sure acquisition visits at least one position\n", "if height_axis_positions==0:\n", " height_axis_positions=1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup Coherent laser box from user provided laser parameters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()\n", "# turn off lasers\n", "# this relies on a Micro-Manager configuration group that sets all lasers to \"off\" state\n", "core.set_config('Coherent-State','off')\n", "core.wait_for_config('Coherent-State','off')\n", "\n", "# set lasers to user defined power\n", "core.set_property('Coherent-Scientific Remote','Laser 405-100C - PowerSetpoint (%)',channel_powers[0])\n", "core.set_property('Coherent-Scientific Remote','Laser 488-150C - PowerSetpoint (%)',channel_powers[1])\n", "core.set_property('Coherent-Scientific Remote','Laser OBIS LS 561-150 - PowerSetpoint (%)',channel_powers[2])\n", "core.set_property('Coherent-Scientific Remote','Laser 637-140C - PowerSetpoint (%)',channel_powers[3])\n", "core.set_property('Coherent-Scientific Remote','Laser 730-30C - PowerSetpoint (%)',channel_powers[4])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup Photometrics camera for low-noise readout and triggering\n", "The camera input trigger is set to `Trigger first` mode to allow for external control and the output trigger is set to `Rolling Shutter` mode to ensure that laser light is only delivered when the entire chip is exposed. The custom Teensy DAC waits for the signal from the camera to go HIGH and then sweeps a Gaussian pencil beam once across the field-of-view. It then rapidly resets and scans again upon the next trigger. The Teensy additionally blanks the Coherent laser box emission between frames." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()\n", "# set camera into 16bit readout mode\n", "core.set_property('Camera','ReadoutRate','100MHz 16bit')\n", "# give camera time to change modes\n", "sleep(5)\n", "\n", "# set camera into low noise readout mode\n", "core.set_property('Camera','Gain','2-CMS')\n", "# give camera time to change modes\n", "sleep(5)\n", "\n", "# set camera to give an exposure out signal\n", "# this signal is used by the custom DAC to synchronize blanking and a digitally swept light sheet\n", "core.set_property('Camera','ExposureOut','Rolling Shutter')\n", "# give camera time to change modes\n", "sleep(5)\n", "\n", "# change camera timeout.\n", "# this is necessary because the acquisition engine can take a long time to setup with millions of events\n", "# on the first run\n", "core.set_property('Camera','Trigger Timeout (secs)',300)\n", "# give camera time to change modes\n", "sleep(5)\n", "\n", "# set camera to internal trigger\n", "core.set_property('Camera','TriggerMode','Internal Trigger')\n", "# give camera time to change modes\n", "sleep(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup ASI stage control cards and programmable logic card in the Tiger controller\n", "Hardware is setup for a constant-speed scan along the `x` direction, lateral tiling along the `y` direction, and height tiling along the `z` direction. The programmable logic card sends a signal to the camera to start acquiring once the scan (x) axis reaches the desired speed and crosses the user defined start position. \n", " \n", "Documentation for the specific commands to setup the constant speed stage scan on the Tiger controller is at the following links,\n", "- [SCAN](http://asiimaging.com/docs/commands/scan)\n", "- [SCANR](http://asiimaging.com/docs/commands/scanr)\n", "- [SCANV](http://www.asiimaging.com/docs/commands/scanv)\n", "\n", " \n", "Documentation for the programmable logic card is found [here](http://www.asiimaging.com/docs/tiger_programmable_logic_card?s[]=plc).\n", " \n", "The Tiger is polled after each command to make sure that it is ready to receive another command." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()\n", "# Setup the PLC to output external TTL when an internal signal is received from the stage scanning card\n", "plcName = 'PLogic:E:36'\n", "propPosition = 'PointerPosition'\n", "propCellConfig = 'EditCellConfig'\n", "addrOutputBNC3 = 35\n", "addrStageSync = 46 # TTL5 on Tiger backplane = stage sync signal\n", "core.set_property(plcName, propPosition, addrOutputBNC3)\n", "core.set_property(plcName, propCellConfig, addrStageSync)\n", "\n", "# turn on 'transmit repeated commands' for Tiger\n", "core.set_property('TigerCommHub','OnlySendSerialCommandOnChange','No')\n", "\n", "# set tile (y) axis speed to 25% of maximum for all moves\n", "command = 'SPEED Y=.25'\n", "core.set_property('TigerCommHub','SerialCommand',command)\n", "\n", "# check to make sure Tiger is not busy\n", "ready='B'\n", "while(ready!='N'):\n", " command = 'STATUS'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", " ready = core.get_property('TigerCommHub','SerialResponse')\n", " sleep(.500)\n", "\n", "# set scan (x) axis speed to 25% of maximum for non-sequenced moves\n", "command = 'SPEED X=.25'\n", "core.set_property('TigerCommHub','SerialCommand',command)\n", "\n", "# check to make sure Tiger is not busy\n", "ready='B'\n", "while(ready!='N'):\n", " command = 'STATUS'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", " ready = core.get_property('TigerCommHub','SerialResponse')\n", " sleep(.500)\n", "\n", "# turn off 'transmit repeated commands' for Tiger\n", "core.set_property('TigerCommHub','OnlySendSerialCommandOnChange','Yes')\n", "\n", "# turn on 'transmit repeated commands' for Tiger\n", "core.set_property('TigerCommHub','OnlySendSerialCommandOnChange','No')\n", "\n", "# set scan (x) axis speed to correct speed for constant speed movement of scan (x) axis\n", "# expects mm/s\n", "command = 'SPEED X='+str(scan_axis_speed)\n", "core.set_property('TigerCommHub','SerialCommand',command)\n", "\n", "# check to make sure Tiger is not busy\n", "ready='B'\n", "while(ready!='N'):\n", " command = 'STATUS'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", " ready = core.get_property('TigerCommHub','SerialResponse')\n", " sleep(.500)\n", "\n", "# set scan (x) axis to true 1D scan with no backlash\n", "command = '1SCAN X? Y=0 Z=9 F=0'\n", "core.set_property('TigerCommHub','SerialCommand',command)\n", "\n", "# check to make sure Tiger is not busy\n", "ready='B'\n", "while(ready!='N'):\n", " command = 'STATUS'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", " ready = core.get_property('TigerCommHub','SerialResponse')\n", " sleep(.500)\n", "\n", "# set range and return speed (25% of max) for constant speed movement of scan (x) axis\n", "# expects mm\n", "command = '1SCANR X='+str(scan_axis_start_mm)+' Y='+str(scan_axis_end_mm)+' R=25'\n", "core.set_property('TigerCommHub','SerialCommand',command)\n", "\n", "# check to make sure Tiger is not busy\n", "ready='B'\n", "while(ready!='N'):\n", " command = 'STATUS'\n", " core.set_property('TigerCommHub','SerialCommand',command)\n", " ready = core.get_property('TigerCommHub','SerialResponse')\n", " sleep(.500)\n", "\n", "# turn off 'transmit repeated commands' for Tiger\n", "core.set_property('TigerCommHub','OnlySendSerialCommandOnChange','Yes')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup and run the acquisition" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Change core timeout\n", "This is necessary because of the large, slow XY stage moves." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()\n", "# change core timeout for long stage moves\n", "core.set_property('Core','TimeoutMs',20000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Move stage hardware to initial positions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()\n", "# move scan (x) and tile (y) stages to starting positions\n", "core.set_xy_position(scan_axis_start_um,tile_axis_start_um)\n", "core.wait_for_device(xy_stage)\n", "\n", "# move height (z) stage to starting position\n", "core.set_position(height_position_um)\n", "core.wait_for_device(z_stage)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create event structure\n", "The external controller handles all of the events in `x` for a given `yzc` position. To make sure that pycro-manager structures the acquistion this way, the value of the stage positions for `x` are kept constant for all events at a given `yzc` position. This gives the order of the loops to create the event structure as `yzcx`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# empty event dictionary\n", "events = []\n", "\n", "# loop over all tile (y) positions.\n", "for y in range(tile_axis_positions):\n", " \n", " # update tile (y) axis position\n", " tile_position_um = tile_axis_start_um+(tile_axis_step_um*y)\n", " \n", " # loop over all height (z) positions\n", " for z in range(height_axis_positions):\n", " \n", " # update height (z) axis position\n", " height_position_um = height_axis_start_um+(height_axis_step_um*z)\n", " \n", " # loop over all channels (c)\n", " for c in range(len(channel_states)):\n", " \n", " # create events for all scan (x) axis positions. \n", " # The acquistion engine knows that this is a hardware triggered sequence because \n", " # the physical x position does not change when specifying the large number of x events \n", " for x in range(scan_axis_positions):\n", " \n", " # only create events if user sets laser to active\n", " # this relies on a Micromanager group 'Coherent-State' that has individual entries that correspond\n", " # the correct on/off state of each laser. Laser blanking and synchronization are handled by the\n", " # custom Teensy DAC controller.\n", " if channel_states[c]==1:\n", " if (c==0):\n", " evt = { 'axes': {'x': x, 'y':y, 'z':z}, 'x': scan_axis_start_um, 'y': tile_position_um, \n", " 'z': height_position_um, 'channel' : {'group': 'Coherent-State', 'config': '405nm'}}\n", " elif (c==1):\n", " evt = { 'axes': {'x': x, 'y':y, 'z':z}, 'x': scan_axis_start_um, 'y': tile_position_um, \n", " 'z': height_position_um, 'channel' : {'group': 'Coherent-State', 'config': '488nm'}}\n", " elif (c==2):\n", " evt = { 'axes': {'x': x, 'y':y, 'z':z}, 'x': scan_axis_start_um, 'y': tile_position_um, \n", " 'z': height_position_um, 'channel' : {'group': 'Coherent-State', 'config': '561nm'}}\n", " elif (c==3):\n", " evt = { 'axes': {'x': x, 'y':y, 'z':z}, 'x': scan_axis_start_um, 'y': tile_position_um, \n", " 'z': height_position_um, 'channel' : {'group': 'Coherent-State', 'config': '637nm'}}\n", " elif (c==4):\n", " evt = { 'axes': {'x': x, 'y':y, 'z':z}, 'x': scan_axis_start_um, 'y': tile_position_um, \n", " 'z': height_position_um, 'channel' : {'group': 'Coherent-State', 'config': '730nm'}}\n", "\n", " events.append(evt)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run acquisition\n", "\n", "- The camera is set to `Trigger first` mode. In this mode, the camera waits for an external trigger and then runs using the internal timing. \n", "- The acquisition is setup and started. The initial acquisition setup by Pycro-manager and the Java acquisition engine takes a few minutes and requires at significant amount of RAM allocated to ImageJ. 40 GB of RAM seems acceptable. The circular buffer is only allocated 2 GB, because the computer for this experiment has an SSD array capable of writing up to 600 MBps.\n", "- At each `yzc` position, the ASI Tiger controller supplies the external master signal when the the (scan) axis has ramped up to the correct constant speed and crossed `scan_axis_start_um`. The speed is defined by `scan_axis_speed = scan_axis_step_um / camera_exposure_ms`. Acquired images are placed into the `x` axis of the Acquisition without Pycro-Manager interacting with the hardware.\n", "- Once the full acquisition is completed, all lasers are set to `off` and the camera is placed back in `Internal Trigger` mode." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "core = Core()\n", "# set camera to trigger first mode for stage synchronization\n", "# give camera time to change modes\n", "core.set_property('Camera','TriggerMode','Trigger first')\n", "sleep(5)\n", "\n", "# run acquisition\n", "# the acquisition needs to write data at roughly 100-500 MBps depending on frame rate and ROI\n", "# so the display is set to off and no multi-resolution calculations are done\n", "with Acquisition(directory=save_directory, name=save_name, post_hardware_hook_fn=post_hardware_hook,\n", " post_camera_hook_fn=post_camera_hook, show_display=False) as acq:\n", " acq.acquire(events)\n", "\n", "# turn off lasers\n", "core.set_config('Coherent-State','off')\n", "core.wait_for_config('Coherent-State','off')\n", "\n", "# set camera to internal trigger\n", "core.set_property('Camera','TriggerMode','Internal Trigger')\n", "# give camera time to change modes\n", "sleep(5)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.6" } }, "nbformat": 4, "nbformat_minor": 4 }