diff --git a/src/instamatic/calibrate/calibrate_stage_motion.py b/src/instamatic/calibrate/calibrate_stage_motion.py index 2e8efb2e..8b856f3b 100644 --- a/src/instamatic/calibrate/calibrate_stage_motion.py +++ b/src/instamatic/calibrate/calibrate_stage_motion.py @@ -183,7 +183,7 @@ def plot(self, sst: Optional[Sequence[SpanSpeedTime]] = None) -> None: spans = [s.span for s in sst if s.speed == speed] times = [s.time for s in sst if s.speed == speed] ax.plot(spans, times, fmt, color=color) - label = f'Speed setting {speed:.2f}' + label = f'Speed setting {speed:.2f}' if speed else 'Default speed' handles.append(Line2D([], [], color=color, marker='o', label=label)) ax.set_xlabel(f'Motion span [{self._span_units}]') diff --git a/src/instamatic/calibrate/calibrate_stage_rotation.py b/src/instamatic/calibrate/calibrate_stage_rotation.py index 592cb72f..5a9bf3d6 100644 --- a/src/instamatic/calibrate/calibrate_stage_rotation.py +++ b/src/instamatic/calibrate/calibrate_stage_rotation.py @@ -2,6 +2,7 @@ import argparse import logging +from contextlib import nullcontext from dataclasses import dataclass from textwrap import dedent from time import perf_counter @@ -71,7 +72,8 @@ def calibrate_stage_rotation_live( spans: `Optional[Sequence[float]]` Alpha rotations whose speed will be measured. Default: range(1, 11, 1). speeds: `Optional[Sequence[Union[float, int]]]` - Spead range to measure. Default: range(1, 13, 1) or range(.1, 1.1, .1). + Spead range to measure. Default: range(1, 13, 1) or range(.1, 1.1, .1), + or [None, None, None], 3 runs without setting speed if it's not possible mode: `Literal['auto', 'limited', 'listed']` Determines the way speed settings restrictions are set in calib file. outdir: `str` or None @@ -91,6 +93,10 @@ def calibrate_stage_rotation_live( except AssertionError: # or any other raised if speed can't be set speeds_default = np.linspace(0.01, 0.2, num=20) speed_options = FEI_ROTATION_SPEED_OPTIONS + except (AttributeError, ConnectionError, TypeError): + log('TEM does not support setting with speed, assuming default = 1.') + speeds_default: Sequence[Speed] = [None, None, None] # no translation w/ speed + speed_options = None else: speeds_default = np.arange(1, 13, step=1) speed_options = JEOL_ROTATION_SPEED_OPTIONS @@ -101,16 +107,20 @@ def calibrate_stage_rotation_live( elif mode == 'listed': speed_options = NumericDomain(options=sorted(speeds_)) + try: + starting_stage_speed = ctrl.stage.get_rotation_speed() + except (AttributeError, ConnectionError, TypeError): + starting_stage_speed = None + calib_points: list[SpanSpeedTime] = [] starting_stage_alpha = ctrl.stage.a - starting_stage_speed = ctrl.stage.get_rotation_speed() ctrl.cam.block() try: n_calib_points = len(speeds_) * len(spans_array) log(f'Calibrating a-axis rotation speed based on {n_calib_points} points.') with tqdm(total=n_calib_points) as progress_bar: for speed in speeds_: - with ctrl.stage.rotation_speed(speed=float(speed)): + with ctrl.stage.rotation_speed(float(speed)) if speed else nullcontext(): ctrl.stage.a = 0.0 for target, span in zip(alpha_targets, spans_array): t1 = perf_counter() @@ -120,7 +130,8 @@ def calibrate_stage_rotation_live( progress_bar.update(1) finally: ctrl.stage.set(a=starting_stage_alpha) - ctrl.stage.set_rotation_speed(starting_stage_speed) + if starting_stage_speed is not None: + ctrl.stage.set_rotation_speed(starting_stage_speed) ctrl.cam.unblock() c = CalibStageRotation.from_data(calib_points) @@ -149,7 +160,8 @@ def main_entry() -> None: h = """Comma-delimited list of speed settings to calibrate for. Default: "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0" or - "1 2 3 4 5 6 7 8 9 10 11 12", whichever is accepted by the microscope.""" + "1 2 3 4 5 6 7 8 9 10 11 12", whichever is accepted by the microscope. "-s" + with no values forces 3 rounds using "set" instead of "set_with_speed".""" h = dedent(h.strip()) parser.add_argument('-s', '--speeds', type=float, default=None, nargs='*', help=h) @@ -169,8 +181,11 @@ def main_entry() -> None: parser.add_argument('--plot', action=argparse.BooleanOptionalAction, default=True, help=h) kwargs = vars(parser.parse_args()) - if kwargs['speeds'] is not None and all(v.is_integer() for v in kwargs['speeds']): - kwargs['speeds'] = [int(v) for v in kwargs['speeds']] + if kwargs['speeds'] is not None: + if all(v.is_integer() for v in kwargs['speeds']): + kwargs['speeds'] = [int(v) for v in kwargs['speeds']] + if not kwargs['speeds']: + kwargs['speeds'] = [None, None, None] from instamatic import controller diff --git a/src/instamatic/calibrate/calibrate_stage_translation.py b/src/instamatic/calibrate/calibrate_stage_translation.py index f1af2c53..142e0a4b 100644 --- a/src/instamatic/calibrate/calibrate_stage_translation.py +++ b/src/instamatic/calibrate/calibrate_stage_translation.py @@ -99,7 +99,8 @@ def calibrate_stage_translation_live( spans: `Optional[Sequence[float]]` Translations that will be timed. Default: 10_000 to 100_000 every 10_000 speeds: `Optional[Sequence[Union[float, int]]]` - All speed settings used for calibration. Default: 0.1 to 1.0 every 0.1. + All speed settings used for calibration. Default: 0.1 to 1.0 every 0.1, + or [None, None, None], 3 runs without setting speed if it's not possible axis: `Literal['x', 'y', 'z']` Axis the speed of which is to be calibrated. Default: 'x'. mode: `Literal['auto', 'limited', 'listed']` @@ -118,7 +119,7 @@ def calibrate_stage_translation_live( stage0: StagePositionTuple = ctrl.stage.get() try: ctrl.stage.set_with_speed(*stage0) - except KeyError: + except TypeError: log('TEM does not support setting with speed, assuming default = 1.') speeds_default: Sequence[Speed] = [None, None, None] # no translation w/ speed speed_options = None @@ -180,8 +181,9 @@ def main_entry() -> None: h += 'Default: "10000 20000 30000 40000 50000 60000 70000 80000 90000 100000".' parser.add_argument('-x', '--spans', type=int, default=None, nargs='*', help=h) - h = 'Comma-delimited list of speed settings to calibrate for.' - h += 'Default: "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0".' + h = 'Comma-delimited list of speed settings to calibrate for. ' + h += 'Default: "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0". ' + h += '"-s" with no values forces 3 rounds using "set" instead of "set_with_speed".' parser.add_argument('-s', '--speeds', type=float, default=None, nargs='*', help=h) h = 'Axis whose translation speed should be calibrated, x, y, or z. Default: x' @@ -204,8 +206,11 @@ def main_entry() -> None: parser.add_argument('--plot', action=argparse.BooleanOptionalAction, default=True, help=h) kwargs = vars(parser.parse_args()) - if kwargs['speeds'] is not None and all(v.is_integer() for v in kwargs['speeds']): - kwargs['speeds'] = [int(v) for v in kwargs['speeds']] + if kwargs['speeds'] is not None: + if all(v.is_integer() for v in kwargs['speeds']): + kwargs['speeds'] = [int(v) for v in kwargs['speeds']] + if not kwargs['speeds']: + kwargs['speeds'] = [None, None, None] from instamatic import controller diff --git a/src/instamatic/camera/camera_client.py b/src/instamatic/camera/camera_client.py index a0591d05..08536fbb 100644 --- a/src/instamatic/camera/camera_client.py +++ b/src/instamatic/camera/camera_client.py @@ -127,7 +127,7 @@ def _eval_dct(self, dct): with self._eval_lock: self.s.send(dumper(dct)) - acquiring_image = dct['attr_name'] in {'get_image', 'get_movie', '__gen_next__'} + acquiring_image = dct['attr_name'] in {'get_image', '__gen_next__'} if acquiring_image and not self.use_shared_memory: response = self.s.recv(self._imagebufsize) @@ -139,7 +139,7 @@ def _eval_dct(self, dct): else: raise RuntimeError(f'Received empty response when evaluating {dct=}') - if self.use_shared_memory and acquiring_image: + if self.use_shared_memory and acquiring_image and data: data = self.get_data_from_shared_memory(**data) if status == 200: @@ -196,7 +196,7 @@ def get_data_from_shared_memory(self, name: str, shape: tuple, dtype: str, **kwa print(f'Retrieve data from buffer `{name}`') buffer = self.buffers[name] - data = buffer[:] + data = buffer[:].copy() return data diff --git a/src/instamatic/microscope/interface/simu_microscope.py b/src/instamatic/microscope/interface/simu_microscope.py index 6e40a188..1b0ce557 100644 --- a/src/instamatic/microscope/interface/simu_microscope.py +++ b/src/instamatic/microscope/interface/simu_microscope.py @@ -2,7 +2,8 @@ import random import time -from typing import Optional, Tuple +from contextlib import contextmanager, nullcontext +from typing import Literal, Optional, Tuple from instamatic import config from instamatic._typing import float_deg, int_nm @@ -126,7 +127,7 @@ def __init__(self, name: str = 'simulate'): speed = 20.0 # degree / sec current = random.randint(-40, 40) elif key in ('x', 'y'): - speed = 1_000_000.0 # nm / sec + speed = 100_000.0 # nm / sec current = random.randint(-100000, 100000) elif key == 'z': speed = 100_000.0 # nm / sec @@ -199,6 +200,19 @@ def _StagePositionGetter(self, var: str) -> float: return ret + @contextmanager + def _speed_setting(self, key: Literal['a', 'b', 'x', 'y', 'z'], value: int): + previous_setting = self._stage_dict[key]['speed_setting'] + previous_speed = self._stage_dict[key]['speed'] + speed_base = {'x': 100_000, 'y': 100_000, 'z': 100_000, 'a': 20, 'b': 20} + try: + self._stage_dict[key]['speed_setting'] = value + self._stage_dict[key]['speed'] = speed_base[key] * value / 12 + yield + finally: + self._stage_dict[key]['speed_setting'] = previous_setting + self._stage_dict[key]['speed'] = previous_speed + @property def StagePosition_a(self): return self._StagePositionGetter('a') @@ -438,26 +452,33 @@ def setStagePosition( wait: bool = True, ) -> None: if z is not None: - self.setStageZ(z, wait=wait) + with nullcontext() if speed is None else self._speed_setting('z', speed): + self.setStageZ(z, wait=wait) if a is not None: - self.setStageA(a, wait=wait) + with nullcontext() if speed is None else self._speed_setting('a', speed): + self.setStageA(a, wait=wait) if b is not None: - self.setStageB(b, wait=wait) + with nullcontext() if speed is None else self._speed_setting('b', speed): + self.setStageB(b, wait=wait) if (x is not None) and (y is not None): - self.setStageXY(x=x, y=y, wait=wait) + with nullcontext() if speed is None else self._speed_setting('x', speed): + with nullcontext() if speed is None else self._speed_setting('y', speed): + self.setStageXY(x=x, y=y, wait=wait) else: if x is not None: - self.setStageX(x, wait=wait) + with nullcontext() if speed is None else self._speed_setting('x', speed): + self.setStageX(x, wait=wait) if y is not None: - self.setStageY(y, wait=wait) + with nullcontext() if speed is None else self._speed_setting('y', speed): + self.setStageY(y, wait=wait) def getRotationSpeed(self) -> int: return self._stage_dict['a']['speed_setting'] def setRotationSpeed(self, value: int): self._stage_dict['a']['speed_setting'] = value - self._stage_dict['a']['speed'] = 10.0 * (value / 12) + self._stage_dict['a']['speed'] = 20.0 * (value / 12) def getFunctionMode(self) -> str: """Mag1, mag2, lowmag, samag, diff."""