micro-manager / pycro-manager

Python control of micro-manager for customized data acquisition
https://pycro-manager.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
165 stars 52 forks source link

Synchronizing Tunable Lens, XY Stage, and Camera in pycromanager's 'Acquire' Functionality #722

Closed kidantad closed 5 months ago

kidantad commented 1 year ago

Hello,

I have a question regarding the use of 'Acquire' in pycromanager, and I would greatly appreciate your insights and guidance. I have previously employed pycromanager to synchronize my XY stage, a separate piezo Z stage, and an sCMOS camera for fast volumetric acquisition. Specifically, I utilize multi-dimensional acquisition to send a TTL signal from my XY stage to the camera, ensuring that they are externally triggered to start imaging simultaneously. This process has been working effectively, with the XY stage initiating movement, followed by the camera capturing images in perfect synchronization. After the imaging is complete, my Z stage moves to the next position, and the process repeats.

Now, I am looking to add another element to this setup: an electro tunable lens, which can dynamically change its focal length when current is applied. I have successfully tested the control of this tunable lens from pycromanager using the lens driver with the Python module linked [here.

With the addition of the tunable lens, my events are as follows:

Event 1: Generate a sinusoidal wave from the tunable lens. Event 2: XY stage moves. Event 3: Camera starts imaging.

I want to maintain the synchronization of events 2 and 3 with external triggering, just as before. However, I will not be using a Z stage in this setup. Instead, I want event 1 to occur in parallel with events 2 and 3. In other words, I want the tunable lens to start changing the focus, the XY stage to move, and the camera to acquire images all at once.

My question is whether it is possible to accomplish this using the 'Acquire' functionality in pycromanager. If so, I would greatly appreciate your guidance on how to set up and configure 'Acquire' to achieve this level of synchronization.

Thank you for your time and assistance in this matter. Your expertise in pycromanager and microscopy-related software would be of great help to my research, and I look forward to your response.

Below is a section from my previous code for the volumetric imaging and the new code from the module opto I want to incorporate

Sets my XY stage (MS 2000) to SCAN mode, defines parameters for the start,end positions, speed and z positions, acquire stage movement and camera simultaneously

` # acquire core from bridge

#mmc = bridge.get_core()
mmc= Core()

# turn on 'transmit repeated commands' for MS2000
No='No'
mmc.set_property('XYStage','OnlySendSerialCommandOnChange',No)

command='SCAN'
mmc.set_property('XYStage','SerialCommand',command)

# check to make sure MS2000 is not busy
ready='B'
while(ready!='N'):
    command = 'STATUS'
    mmc.set_property('XYStage','SerialCommand',command)
    ready = mmc.get_property('XYStage','SerialResponse')
    sleep(.500)

# turn off 'transmit repeated commands' for MS2000
Yes= 'Yes'
mmc.set_property('XYStage','OnlySendSerialCommandOnChange',Yes)

return event

print(ready)

Set and save the overshoot factor and the y position

command='SCAN F=<1>' mmc.set_property('XYStage','SerialCommand',command)

command='SS Z' mmc.set_property('XYStage','SerialCommand',command)

Y_AXIS

y_start = mmc.get_y_position()/1000 #get current y position y_end = 1*y_start

x_pos = mmc.get_x_position() #get current x position

scan_start= (-1*(x_pos/1000)) #Start position in mm scan_end = (scan_start)- 0.04 #End position in mm. 40um relatively scan_speed = 0.02 #in seconds scan_step = 0.0001 #in mm scan_range = np.abs(scan_end-scan_start) scan_positions= np.rint((scan_range/scan_step)+1).astype(int) #number of positions

z_scan_start = 0.0 z_scan_end =0.5 z_scan_step = 0.15 z_sequence = np.arange(z_scan_start, z_scan_end+z_scan_step, z_scan_step) num_z_slices = len(z_sequence)

mmc= Core()

turn on 'transmit repeated commands' for MS2000

mmc.set_property('XYStage','OnlySendSerialCommandOnChange','No')

command = 'SPEED X=.02'

mmc.set_property('XYStage','SerialCommand',command)

mmc.set_property('XYStage','OnlySendSerialCommandOnChange',Yes)

command = 'SCANR X='+str(scan_end)+' Y='+str(scan_start)
mmc.set_property('XYStage','SerialCommand',command)

command= 'SCANV X=' +str(1y_start)+' Y='+str(1y_end) mmc.set_property('XYStage','SerialCommand',command)

mmc.set_property('XYStage','OnlySendSerialCommandOnChange',Yes) print(ready)

%%time count = 0 for z in z_sequence:

count += 1
fileName = save_name+ '_z' + str(count).zfill(8);

with Acquisition(directory=save_dir, name=fileName, post_camera_hook_fn=post_camera_hook_, post_hardware_hook_fn= post_hardware_hook,
                show_display=True) as acq:
    events = multi_d_acquisition_events(
                                    num_time_points=400, time_interval_s=0,
                                    z_start =z, z_end=z-.5*z_scan_step, z_step=z_scan_step,
    )

    acq.acquire(events)`

Section from the module 'opto' I want to add as event 1 that controls the tunable lens by sending a continuous sin waveform.

`from opto import Opto import numpy as np import time

with Opto(port='/dev/cu.usbmodem1411') as o: current_low = o.current_lower() current_high = o.current_upper() current_delta = current_high-current_low for i in np.linspace(0, 2np.pi, 1000): o.current(np.sin(i)current_delta+current_low) time.sleep(0.01)`

henrypinkard commented 1 year ago

There's not built in parallelism or guaranteed synchronization like this in micro-manager API (yet), so the way people typically do something like this is to have an external timing hardware device like an arduino or NI board that synchronizes the timing, a camera that captures images in response to receiving external TLL triggers from that device, and then pycro-manager with acquisition hooks to kick off the process by communicating with the devices and read out/save the images.

See here for more information: https://pycro-manager.readthedocs.io/en/latest/hardware_triggering.html

ShuJiaLab commented 1 year ago

Thank you, Henry, for your insightful response. I initially considered using a function generator for synchronization, but your suggestion of employing an NI board or Arduino appears to be a more compatible, particularly given the scanning mode requirements of my MS2000 stage and the camera setup.

henrypinkard commented 1 year ago

Good Im glad it was helpful. And yes, if you don't need specific analog waveforms I'd say function generators are not worth the hassle