navigate icon indicating copy to clipboard operation
navigate copied to clipboard

Eliminate initial stage positions

Open zacsimile opened this issue 3 years ago • 14 comments

Drove the stage right into the bottom of the chamber on startup. Nothing I could do about it.

zacsimile avatar Dec 17 '22 00:12 zacsimile

Inclined to get rid of initial stage positions entirely.

zacsimile avatar Dec 17 '22 00:12 zacsimile

Go ahead and do it. It seems safer for the stage to stay where it is, than to reset every time you boot the software. Especially because if it does move the sample and focus, then you have to redo all this shit you just did to get it nicely aligned.

This same thing was causing @JinlongL some anxiety too, especially since the user could accidentally change the zero position on the ASI stage and the software would have no clue. We fixed that by disabling the zero function on the hardware, and setting both software and hardware-based stage limits. But that is an annoying sequence of things to do...

I think having the load and unload positions is useful, although we have not yet implemented that.

And on a tangentially related subject, I think we can go through the configuration file and eliminate some of the unused items...

AdvancedImagingUTSW avatar Dec 17 '22 02:12 AdvancedImagingUTSW

Glancing at the stage classes, and there is a lot of stuff we could clean up now too. Many of the old parameters, such as internal positions, and offsets, can probably be removed.

AdvancedImagingUTSW avatar Dec 17 '22 19:12 AdvancedImagingUTSW

@AdvancedImagingUTSW fixed a lot of this, but I think there is still a gremlin lurking in the code. In theory, this issue should have been solved by line 666 of controller.py (following from line 491, line 201) about 3 months ago. Here, the stage positions are updated from the initial configuration.yaml to the current stage position before stage_controller.populate_experiment_values() is set up.

This begs the question: how did the stage manage to move during ASLM startup months after this fact (the day I posted this issue)? I believe the answer lies in PIStage.report_position(). In the event of a GCSError, we will not update the stage position and the stage will move to the positions specified by configuration.yaml on line 496 of controller.py (self.stage_controller.populate_experiment_values()).

We could avoid the problem of this happening on startup by changing line 201 of controller.py to

self.stage_controller.unbind_position_callbacks()
self.populate_experiment_setting()
self.stage_controller.bind_position_callbacks()

In this case, however, the stage will initially report incorrect positions in the event of a GCSError. When the user attempts to move the stage, and the positions update, the stage will still begin moving to the wrong location. However, the user will have the ability to hit the stop button.

This is an edge case for sure.

zacsimile avatar Jan 04 '23 23:01 zacsimile

One more clue when I tried to switch modes while operating with synthetic hardware...

(ASLM) S155475@SW567797 ~ % aslm -sh
Args type: <class 'argparse.Namespace'>
['488nm', '562nm', '642nm']
Exception inside ObjectInSubprocess: Traceback (most recent call last):
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/model/concurrency/concurrency_tools.py", line 519, in _child_loop
    result = getattr(obj, method_name)(*args, **kwargs)
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/model/model.py", line 379, in run_command
    self.change_resolution(
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/model/model.py", line 806, in change_resolution
    self.active_microscope.move_stage_offset(former_microscope)
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/model/microscope.py", line 201, in move_stage_offset
    pos_dict[axes + "_pos"]
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

model thread exception happened! unsupported operand type(s) for +: 'NoneType' and 'int' Traceback (most recent call last):
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/controller/thread_pool.py", line 128, in func
    target(*args, **kwargs)
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/controller/controller.py", line 657, in <lambda>
    "model", lambda: self.model.run_command("update_setting", "resolution")
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/model/concurrency/concurrency_tools.py", line 414, in attr
    return _get_response(self, True)
  File "/Users/S155475/Desktop/GitHub/ASLM/src/aslm/model/concurrency/concurrency_tools.py", line 435, in _get_response
    raise resp
Exception: unsupported operand type(s) for +: 'NoneType' and 'int'

model thread ended because of exception!: not all arguments converted during string formatting

AdvancedImagingUTSW avatar Jan 13 '23 19:01 AdvancedImagingUTSW

I think this is a different issue. Fix incoming.

zacsimile avatar Jan 13 '23 19:01 zacsimile

This persists and just became my number one issue.

IMG_1331

zacsimile avatar Feb 09 '23 18:02 zacsimile

Still happening.

zacsimile avatar Feb 24 '23 16:02 zacsimile

@annie-xd-wang mentioned 3 regions where the software has a chance to move the stage:

Populate stage position in GUI. Set resolution (with stage offsets). Set Zoom Value

As I mentioned last week, we should also look into the auto zeroing and auto referencing that the stage can occasionally do...

AdvancedImagingUTSW avatar Feb 27 '23 18:02 AdvancedImagingUTSW

While trying to get a different stage to work this morning, which only has a single axis, I encountered the error below:

Opening Camera
Number of cameras is 1
Camera Open
Exception inside ObjectInSubprocess: Traceback (most recent call last):
  File "c:\users\dean-lab\desktop\aslm\src\aslm\model\concurrency\concurrency_tools.py", line 521, in _child_loop
    result = getattr(obj, method_name)(*args, **kwargs)
  File "c:\users\dean-lab\desktop\aslm\src\aslm\model\model.py", line 587, in move_stage
    return self.active_microscope.move_stage(pos_dict, wait_until_done)
  File "c:\users\dean-lab\desktop\aslm\src\aslm\model\microscope.py", line 556, in move_stage
    self.stages[axis].move_absolute(
KeyError: 'theta'

model thread exception happened! 'theta' Traceback (most recent call last):
  File "c:\users\dean-lab\desktop\aslm\src\aslm\controller\thread_pool.py", line 175, in func
    target(*args, **kwargs)
  File "c:\users\dean-lab\desktop\aslm\src\aslm\controller\controller.py", line 1116, in move_stage
    self.model.move_stage(pos_dict)
  File "c:\users\dean-lab\desktop\aslm\src\aslm\model\concurrency\concurrency_tools.py", line 412, in attr
    return _get_response(self, True)
  File "c:\users\dean-lab\desktop\aslm\src\aslm\model\concurrency\concurrency_tools.py", line 433, in _get_response
    raise resp
Exception: 'theta'

model thread ended because of exception!: not all arguments converted during string formatting

This appears to be calling to move the stage upon software startup.

view_controller_info.log

view_controller_debug.log

model_debug.log

AdvancedImagingUTSW avatar Apr 01 '23 12:04 AdvancedImagingUTSW

I also notice that when we startup our stages with the biuld_PIStage_connection function, we call pi_tools.startup()...

According to the comments of that function:

  • Define 'stages', stop all, enable servo on all connected axes and reference them with 'refmodes'.
  • Defining stages and homing them is done only if necessary.

I assume this is what causes the stage to go rogue on occasion @zacsimile. I'm also not entirely sure why we are doing this. I think we are sufficiently connected to the stages without calling this function.

AdvancedImagingUTSW avatar Apr 01 '23 13:04 AdvancedImagingUTSW

The first thing @AdvancedImagingUTSW mentioned this morning reminded me that hard-coded axes mappings are not convenient. We saw this was a problem when trying to change the axis @JinlongL's stage uses for "z-stacking". I think we should modify the stage code to map axes in the order they are passed in the configuration.yaml. E.g., if we talk to the PI stage and it says I have axes 1, 2, 3, 4, 5, and the user passes axes: [x, y, z, theta, f] in configuration.yaml, this should automatically create a mapping {"x": 1, "y": 2, "z": 3, "theta": 4, "f": 5}.

zacsimile avatar Apr 01 '23 15:04 zacsimile

I think @AdvancedImagingUTSW is right that the referencing is what moves the stage! Finally an answer for this gremlin.

We could modify the startup to pass None instead of the reference modes when booting up. Then we could check if the stage is referenced and only call the referencing modes if it is not. We could have a popup that says, "Stage is unreferenced. Do you want to reference it? Keep in mind this will move the stage a significant distance. Clear the path." Or something like that.

zacsimile avatar Apr 01 '23 18:04 zacsimile

Here is some code that is tested in the debugging mode of Pycharm. I think if we combine with this the None referencing mode by default, it would be a good start.


def build_PIStage_connection(controller_name, serial_number, stages, reference_modes):
    """Connect to the Physik Instrumente Stage

    Parameters
    ----------
    controller_name : str
        Name of the controller, e.g., "C-863.11"
    serial_number : str
        Serial number of the controller, e.g., "0112345678"
    stages : str
        Stages to connect to, e.g., "M-111.1DG"
    reference_modes : str
        Reference modes for the stages, e.g., "FRF"

    Returns
    -------
    stage_connection : dict
        Dictionary containing the pi_tools and pi_device objects
    """
    pi_stages = stages.split()
    pi_reference_modes = reference_modes.split()
    pi_tools = pitools
    pi_device = GCSDevice(controller_name)
    pi_device.ConnectUSB(serialnum=serial_number)
    pi_tools.startup(
        pi_device, stages=list(pi_stages), refmodes=list(pi_reference_modes)
    )

    # wait until pi_device is ready
    block_flag = True
    while block_flag:
        if pi_device.IsControllerReady():
            block_flag = False
        else:
            time.sleep(0.1)

    # Are we on target? What is our reference mode state? Is the Servo on?
    on_target = pi_device.qFRF()
    servo_on = pi_device.qSVO()
    all_on_target = all(value for value in on_target.values())
    all_servo_on = all(value for value in servo_on.values())
    if all_servo_on and all_on_target:
        pass
    else:
        messagebox.showwarning(title="ASLM",
                               message="Physik Instrumente Stages Unreferenced or Not On Target. \n"
                                       "Proceed With Caution")

    stage_connection = {"pi_tools": pi_tools, "pi_device": pi_device}
    return stage_connection

AdvancedImagingUTSW avatar Sep 22 '23 19:09 AdvancedImagingUTSW