-
Notifications
You must be signed in to change notification settings - Fork 30
Speed setting, mouse stage/beam control, and remote cameras #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
a150689
24a369a
94fd7fb
5f5fac3
0ffc01f
a73ed8f
0b94d82
621d954
8eeda49
190d632
2f1d726
9bfe76c
d672d28
08048e2
dc96cd0
31c28ea
f639dab
560b318
c593bf4
99853eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,18 +2,22 @@ | |||||||||||||||
|
|
||||||||||||||||
| import queue | ||||||||||||||||
| import threading | ||||||||||||||||
| from threading import Event | ||||||||||||||||
| from tkinter import * | ||||||||||||||||
| from tkinter.ttk import * | ||||||||||||||||
| from typing import Dict | ||||||||||||||||
|
|
||||||||||||||||
| import numpy as np | ||||||||||||||||
|
|
||||||||||||||||
| from instamatic import config | ||||||||||||||||
| from instamatic.calibrate import CalibBeamShift | ||||||||||||||||
| from instamatic.calibrate.filenames import CALIB_BEAMSHIFT | ||||||||||||||||
| from instamatic.exceptions import TEMCommunicationError | ||||||||||||||||
| from instamatic.gui.click_dispatcher import ClickEvent, MouseButton | ||||||||||||||||
| from instamatic.utils.spinbox import Spinbox | ||||||||||||||||
|
|
||||||||||||||||
| from .base_module import BaseModule, HasQMixin | ||||||||||||||||
| from .base_module import BaseModule, ModuleFrameMixin | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| class ExperimentalCtrl(LabelFrame, HasQMixin): | ||||||||||||||||
| class ExperimentalCtrl(LabelFrame, ModuleFrameMixin): | ||||||||||||||||
| """This panel holds some frequently used functions to control the electron | ||||||||||||||||
| microscope.""" | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -74,21 +78,31 @@ def __init__(self, parent): | |||||||||||||||
| ) | ||||||||||||||||
| b_wobble.grid(row=4, column=2, sticky='W', columnspan=2) | ||||||||||||||||
|
|
||||||||||||||||
| text = 'Move stage with LMB' | ||||||||||||||||
| self.lmb_stage = Checkbutton(frame, text=text, variable=self.var_lmb_stage) | ||||||||||||||||
| self.lmb_stage.grid(row=1, column=3, columnspan=3, sticky='W') | ||||||||||||||||
| self.var_lmb_stage.trace_add('write', self.toggle_lmb_stage) | ||||||||||||||||
|
|
||||||||||||||||
| text = 'Move beam with RMB' | ||||||||||||||||
| self.rmb_beam = Checkbutton(frame, text=text, variable=self.var_rmb_beam) | ||||||||||||||||
| self.rmb_beam.grid(row=2, column=3, columnspan=3, sticky='W') | ||||||||||||||||
| self.var_rmb_beam.trace_add('write', self.toggle_rmb_beam) | ||||||||||||||||
|
|
||||||||||||||||
| e_stage_x = Spinbox(frame, textvariable=self.var_stage_x, **stage) | ||||||||||||||||
| e_stage_x.grid(row=6, column=1, sticky='EW') | ||||||||||||||||
| e_stage_y = Spinbox(frame, textvariable=self.var_stage_y, **stage) | ||||||||||||||||
| e_stage_y.grid(row=6, column=2, sticky='EW') | ||||||||||||||||
| e_stage_z = Spinbox(frame, textvariable=self.var_stage_z, **stage) | ||||||||||||||||
| e_stage_z.grid(row=6, column=3, sticky='EW') | ||||||||||||||||
|
|
||||||||||||||||
| Label(frame, text='Rotation speed', width=20).grid(row=5, column=0, sticky='W') | ||||||||||||||||
| e_goniotool_tx = Spinbox( | ||||||||||||||||
| frame, width=10, textvariable=self.var_goniotool_tx, from_=1, to=12, increment=1 | ||||||||||||||||
| ) | ||||||||||||||||
| e_goniotool_tx.grid(row=5, column=1, sticky='EW') | ||||||||||||||||
| b_goniotool_set = Button(frame, text='Set', command=self.set_goniotool_tx) | ||||||||||||||||
| b_goniotool_set.grid(row=5, column=2, sticky='EW') | ||||||||||||||||
| if config.settings.use_goniotool: | ||||||||||||||||
| Label(frame, text='Rot. Speed', width=20).grid(row=5, column=0, sticky='W') | ||||||||||||||||
| e_goniotool_tx = Spinbox( | ||||||||||||||||
| frame, width=10, textvariable=self.var_goniotool_tx, from_=1, to=12, increment=1 | ||||||||||||||||
| ) | ||||||||||||||||
| e_goniotool_tx.grid(row=5, column=1, sticky='EW') | ||||||||||||||||
| b_goniotool_set = Button(frame, text='Set', command=self.set_goniotool_tx) | ||||||||||||||||
| b_goniotool_set.grid(row=5, column=2, sticky='W') | ||||||||||||||||
| b_goniotool_default = Button( | ||||||||||||||||
| frame, text='Default', command=self.set_goniotool_tx_default | ||||||||||||||||
| ) | ||||||||||||||||
|
|
@@ -212,6 +226,8 @@ def init_vars(self): | |||||||||||||||
| self.var_diff_defocus_on = BooleanVar(value=False) | ||||||||||||||||
|
|
||||||||||||||||
| self.var_stage_wait = BooleanVar(value=True) | ||||||||||||||||
| self.var_lmb_stage = BooleanVar(value=False) | ||||||||||||||||
| self.var_rmb_beam = BooleanVar(value=False) | ||||||||||||||||
|
|
||||||||||||||||
| def set_mode(self, event=None): | ||||||||||||||||
| self.ctrl.mode.set(self.var_mode.get()) | ||||||||||||||||
|
|
@@ -257,7 +273,12 @@ def set_positive_angle(self): | |||||||||||||||
| def set_goniotool_tx(self, event=None, value=None): | ||||||||||||||||
| if not value: | ||||||||||||||||
| value = self.var_goniotool_tx.get() | ||||||||||||||||
| self.ctrl.stage.set_rotation_speed(value) | ||||||||||||||||
| try: | ||||||||||||||||
| self.ctrl.stage.set_rotation_speed(value) | ||||||||||||||||
| except AttributeError: | ||||||||||||||||
| print('This TEM does not implement `setRotationSpeed` method') | ||||||||||||||||
| except TEMCommunicationError: | ||||||||||||||||
| print('Could not connect to the stage rotation speed controller') | ||||||||||||||||
|
|
||||||||||||||||
| def set_goniotool_tx_default(self, event=None): | ||||||||||||||||
| value = 12 | ||||||||||||||||
|
|
@@ -301,6 +322,54 @@ def toggle_alpha_wobbler(self): | |||||||||||||||
| if self.wobble_stop_event: | ||||||||||||||||
| self.wobble_stop_event.set() | ||||||||||||||||
|
|
||||||||||||||||
| def toggle_lmb_stage(self, _name, _index, _mode): | ||||||||||||||||
| """If self.var_lmb_stage, move stage using Left Mouse Button.""" | ||||||||||||||||
|
|
||||||||||||||||
| d = self.app.get_module('stream').click_dispatcher | ||||||||||||||||
| if self.var_lmb_stage.get(): | ||||||||||||||||
| try: | ||||||||||||||||
| stage_matrix = self.ctrl.get_stagematrix() | ||||||||||||||||
| except KeyError: | ||||||||||||||||
| print('No stage matrix for current mode and magnification found.') | ||||||||||||||||
| print('Run `instamatic.calibrate_stagematrix` to use this feature.') | ||||||||||||||||
| self.var_lmb_stage.set(False) | ||||||||||||||||
| return | ||||||||||||||||
|
|
||||||||||||||||
| def _callback(click: ClickEvent) -> None: | ||||||||||||||||
| if click.button == MouseButton.LEFT: | ||||||||||||||||
| cam_dim_x, cam_dim_y = self.ctrl.cam.get_camera_dimensions() | ||||||||||||||||
| pixel_delta = np.array([click.y - cam_dim_y / 2, click.x - cam_dim_x / 2]) | ||||||||||||||||
| stage_shift = np.dot(pixel_delta, stage_matrix) | ||||||||||||||||
| x, y = self.ctrl.stage.xy | ||||||||||||||||
| self.ctrl.stage.set(x=x + stage_shift[0], y=y + stage_shift[1]) | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this can refactored to make it available as a method on stage, something like: (not entirely sure about the design here)
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I honestly wrote an implementation of a def move(
self,
delta_x: Optional[int_nm] = None,
delta_y: Optional[int_nm] = None,
delta_z: Optional[int_nm] = None,
delta_a: Optional[float_deg] = None,
delta_b: Optional[float_deg] = None,
wait: bool = True,
speed: Optional[float] = None,
) -> None:
"""Move the stage by given values relative to its current position."""
x, y, z, a, b = self.get()
kwargs = {
'x': None if delta_x is None else x + delta_x,
'y': None if delta_y is None else y + delta_y,
'z': None if delta_z is None else z + delta_z,
'a': None if delta_a is None else a + delta_a,
'b': None if delta_b is None else b + delta_b,
'wait': wait
}
if speed is not None:
kwargs['speed'] = speed
self.set(**kwargs)...but then I realized that what would be actually much more intuitive in real conditions would actually be not As for |
||||||||||||||||
|
|
||||||||||||||||
| d.add_listener('lmb_stage', _callback, active=True) | ||||||||||||||||
| else: | ||||||||||||||||
| d.listeners.pop('lmb_stage', None) | ||||||||||||||||
|
Baharis marked this conversation as resolved.
Outdated
|
||||||||||||||||
|
|
||||||||||||||||
| def toggle_rmb_beam(self, _name, _index, _mode) -> None: | ||||||||||||||||
| """If self.var_rmb_beam, move beam using Right Mouse Button.""" | ||||||||||||||||
|
|
||||||||||||||||
| d = self.app.get_module('stream').click_dispatcher | ||||||||||||||||
| if self.var_rmb_beam.get(): | ||||||||||||||||
| path = self.app.get_module('io').get_experiment_directory().parent / 'calib' | ||||||||||||||||
|
Baharis marked this conversation as resolved.
Outdated
|
||||||||||||||||
| try: | ||||||||||||||||
| calib_beamshift = CalibBeamShift.from_file(path / CALIB_BEAMSHIFT) | ||||||||||||||||
| except OSError: | ||||||||||||||||
| print(f'No {CALIB_BEAMSHIFT} file in directory {path} found.') | ||||||||||||||||
| print('Run `instamatic.calibrate_beamshift` there to use this feature.') | ||||||||||||||||
| self.var_rmb_beam.set(False) | ||||||||||||||||
| return | ||||||||||||||||
|
|
||||||||||||||||
| def _callback(click: ClickEvent) -> None: | ||||||||||||||||
| if click.button == MouseButton.RIGHT: | ||||||||||||||||
| bs = calib_beamshift.pixelcoord_to_beamshift((click.y, click.x)) | ||||||||||||||||
| self.ctrl.beamshift.set(*[float(b) for b in bs]) | ||||||||||||||||
|
|
||||||||||||||||
| d.add_listener('rmb_beam', _callback, active=True) | ||||||||||||||||
| else: | ||||||||||||||||
| d.listeners.pop('rmb_beam', None) | ||||||||||||||||
|
Baharis marked this conversation as resolved.
Outdated
|
||||||||||||||||
|
|
||||||||||||||||
| def stage_stop(self): | ||||||||||||||||
| self.q.put(('ctrl', {'task': 'stage.stop'})) | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.