Eliminate initial stage positions
Drove the stage right into the bottom of the chamber on startup. Nothing I could do about it.
Inclined to get rid of initial stage positions entirely.
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...
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 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.
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
I think this is a different issue. Fix incoming.
This persists and just became my number one issue.

Still happening.
@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...
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.
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.
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}.
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.
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