From 745beee352ab4a483a1a8778da64819bdaebe27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Wed, 20 Aug 2025 14:23:16 +0200 Subject: [PATCH 01/11] Soft-reset FastADT changes to 1 commit for easier maintenance --- .../instamatic/config/camera/test.yaml | 1 + docs/config.md | 3 + docs/gui.md | 39 ++ docs/images/gui_fast_adt.png | Bin 0 -> 115945 bytes src/instamatic/_collections.py | 14 +- src/instamatic/config/camera/simulate.yaml | 1 + src/instamatic/config/camera/simulateDLL.yaml | 1 + src/instamatic/config/settings.yaml | 1 + src/instamatic/experiments/__init__.py | 1 + src/instamatic/experiments/experiment_base.py | 6 +- .../experiments/fast_adt/__init__.py | 0 .../experiments/fast_adt/experiment.py | 477 ++++++++++++++++++ src/instamatic/gui/fast_adt_frame.py | 260 ++++++++++ src/instamatic/gui/modules.py | 3 +- tests/alignments/FastADT_diff.yaml | 1 + tests/alignments/FastADT_image.yaml | 1 + tests/alignments/FastADT_track.yaml | 1 + .../calibration/calib_stage_rotation.yaml | 5 + tests/config/camera/test.yaml | 1 + tests/test_experiments.py | 127 ++--- tests/test_pets_input.py | 17 +- tests/utils.py | 14 + 22 files changed, 904 insertions(+), 70 deletions(-) create mode 100644 docs/images/gui_fast_adt.png create mode 100644 src/instamatic/experiments/fast_adt/__init__.py create mode 100644 src/instamatic/experiments/fast_adt/experiment.py create mode 100644 src/instamatic/gui/fast_adt_frame.py create mode 100644 tests/alignments/FastADT_diff.yaml create mode 100644 tests/alignments/FastADT_image.yaml create mode 100644 tests/alignments/FastADT_track.yaml create mode 100644 tests/config/calibration/calib_stage_rotation.yaml create mode 100644 tests/utils.py diff --git a/docs/app_data/instamatic/config/camera/test.yaml b/docs/app_data/instamatic/config/camera/test.yaml index db1874c3..202fdf33 100644 --- a/docs/app_data/instamatic/config/camera/test.yaml +++ b/docs/app_data/instamatic/config/camera/test.yaml @@ -8,3 +8,4 @@ interface: simulate physical_pixelsize: 0.055 stretch_amplitude: 2.43 stretch_azimuth: 83.37 +dead_time: 0.0 diff --git a/docs/config.md b/docs/config.md index 1d6bb857..14984e40 100644 --- a/docs/config.md +++ b/docs/config.md @@ -206,6 +206,9 @@ This file holds the specifications of the camera. This file is must be located t **interface** : Give the interface of the camera interface to connect to, for example: `timepix`/`emmenu`/`simulate`/`gatan`/`merlin`. Leave blank or set to `None` to load the camera specs, but do not load the camera module (this also turns off the videostream gui). +**dead_time** +: Set the dead time (i.e. the gap between acquisitions) of the detector; if this value (`camera.dead_time`) is not set but required, Instamatic might attempt to use `CalibMovieDelays.dead_time` value calibrated via `instamatic.calibrate_movie_delays` instead. Typically, Instamatic will not run this calibration automatically: the user needs to either set `camera.dead_time` or call `instamatic.calibrate_movie_delays` themselves. + **default_binsize** : Set the default binsize, default: `1`. diff --git a/docs/gui.md b/docs/gui.md index ce8b9485..87cba280 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -154,6 +154,45 @@ Pressing `Start Collection` will collect a series of frames with the given tilt Data are output to `.tiff` and `.mrc`, including input files to read the data in PETS and REDp. +## FastADT data collection + +Module: `fast_adt` + +A generic module used to run cRED experiments where the tracking is done a priori i.e. in a swoop directly preceding the diffraction experiment. + +Before the experiment, decide on diffraction and tracking modes to be used. The diffraction can be collected as a series of static `stills` or a `continuous` movie. In the latter case, the rotation speed (upon calibration via `instamatic.calib_stage_rotation`) will be established automatically. If desired, a `manual` crystal tracking path can be also requested. Tracking is done just before the experiment. A series of images with crystal positions at different angles will be collected and user will be requested to point on points to measure and then confirm by clicking on the image stream. + +![FastADT pane](images/gui_fast_adt.png) + +For optimum performance, the FastADT frame uses three separate TEM setting which need to be preemptively set up by the user. The "Image", "Tracking" and "Diffraction" settings are used when collecting the respective kinds of images. They are saved between microscope sessions. + +**Diffraction mode** +: `stills` or `continuous`, used to switch between rotation and stills/precession-assisted experiment. + +**Diffraction start (deg)** +: The tilt series/swipe starting angle in degrees. + +**Diffraction stop (deg)** +: The tilt series/swipe ending angle in degrees. + +**Diffraction step (deg)** +: The target spacing between angles at which subsequent diffraction images are collected within the tilt series/swipe in degrees. + +**Diffraction exposure (s)** +: The time taken to collect each diffraction image in seconds. In the `continuous` mode it will additionally dictate the rotation speed. + +**Tracking mode** +: Dictates whether `none` or `manual` tracking is to be performed at the start of the experiment. + +**Tracking step (deg)** +: The target spacing between angles at which subsequent tracking images are collected within the tracking series in degrees. + +**Tracking exposure (s)** +: The time taken to collect each tracking image in seconds. + +The function of config settings' `store` / `restore` buttons has been described above. The `start` button starts the (tracking and then) experiment. Further information and instructions concerning the `fast_adt` experiment may appear in the window and in the console. Collected data is automatically output in all available formats to the target directory at the end of experiment. + + ## Machine learning Module: `machine_learning` diff --git a/docs/images/gui_fast_adt.png b/docs/images/gui_fast_adt.png new file mode 100644 index 0000000000000000000000000000000000000000..f341f01127bfe3130542c4caf1da08cc73d1421b GIT binary patch literal 115945 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 literal 0 HcmV?d00001 diff --git a/src/instamatic/_collections.py b/src/instamatic/_collections.py index 78d0e4f5..d2250f08 100644 --- a/src/instamatic/_collections.py +++ b/src/instamatic/_collections.py @@ -1,8 +1,11 @@ from __future__ import annotations +import contextlib +import logging import string +import time from collections import UserDict -from typing import Any, Tuple +from typing import Any, Callable class NoOverwriteDict(UserDict): @@ -14,6 +17,13 @@ def __setitem__(self, key: Any, value: Any) -> None: super().__setitem__(key, value) +class NullLogger(logging.Logger): + def __init__(self, name='null'): + super().__init__(name) + self.addHandler(logging.NullHandler()) + self.propagate = False + + class PartialFormatter(string.Formatter): """`str.format` alternative, allows for partial replacement of {fields}""" @@ -21,7 +31,7 @@ def __init__(self, missing: str = '{{{}}}') -> None: super().__init__() self.missing: str = missing # used instead of missing values - def get_field(self, field_name: str, args, kwargs) -> Tuple[Any, str]: + def get_field(self, field_name: str, args, kwargs) -> tuple[Any, str]: """When field can't be found, return placeholder text instead.""" try: obj, used_key = super().get_field(field_name, args, kwargs) diff --git a/src/instamatic/config/camera/simulate.yaml b/src/instamatic/config/camera/simulate.yaml index 586cbbcf..02e091f8 100644 --- a/src/instamatic/config/camera/simulate.yaml +++ b/src/instamatic/config/camera/simulate.yaml @@ -18,3 +18,4 @@ physical_pixelsize: 0.055 possible_binsizes: [1] stretch_amplitude: 2.43 stretch_azimuth: 83.37 +dead_time: 0.0 diff --git a/src/instamatic/config/camera/simulateDLL.yaml b/src/instamatic/config/camera/simulateDLL.yaml index 93c71f32..d53a2ab0 100644 --- a/src/instamatic/config/camera/simulateDLL.yaml +++ b/src/instamatic/config/camera/simulateDLL.yaml @@ -16,3 +16,4 @@ interface: simulateDLL possible_binsizes: [1, 2, 4] stretch_amplitude: 0 stretch_azimuth: 0 +dead_time: 0.0 diff --git a/src/instamatic/config/settings.yaml b/src/instamatic/config/settings.yaml index b14dea0b..af5e4b05 100644 --- a/src/instamatic/config/settings.yaml +++ b/src/instamatic/config/settings.yaml @@ -59,6 +59,7 @@ modules: - 'cred' - 'cred_tvips' - 'cred_fei' + - 'fast_adt' - 'sed' - 'autocred' - 'red' diff --git a/src/instamatic/experiments/__init__.py b/src/instamatic/experiments/__init__.py index 473aed14..e0e880ed 100644 --- a/src/instamatic/experiments/__init__.py +++ b/src/instamatic/experiments/__init__.py @@ -3,5 +3,6 @@ from .autocred import experiment as autocRED from .cred import experiment as cRED from .cred_tvips import experiment as cRED_tvips +from .fast_adt import experiment as fast_adt from .red import experiment as RED from .serialed import experiment as serialED diff --git a/src/instamatic/experiments/experiment_base.py b/src/instamatic/experiments/experiment_base.py index fb56b267..495fa9d2 100644 --- a/src/instamatic/experiments/experiment_base.py +++ b/src/instamatic/experiments/experiment_base.py @@ -9,7 +9,11 @@ class ExperimentBase(ABC): """Experiment base class.""" @abstractmethod - def start_collection(self): + def __init__(self, *args, **kwargs) -> None: + pass + + @abstractmethod + def start_collection(self, **kwargs): pass def setup(self): diff --git a/src/instamatic/experiments/fast_adt/__init__.py b/src/instamatic/experiments/fast_adt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/instamatic/experiments/fast_adt/experiment.py b/src/instamatic/experiments/fast_adt/experiment.py new file mode 100644 index 00000000..6be38a37 --- /dev/null +++ b/src/instamatic/experiments/fast_adt/experiment.py @@ -0,0 +1,477 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass, field +from pathlib import Path +from queue import Queue +from threading import Thread +from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union + +import numpy as np +import pandas as pd +from matplotlib import pyplot as plt +from typing_extensions import Self + +from instamatic import config +from instamatic._collections import NullLogger +from instamatic._typing import AnyPath +from instamatic.calibrate import CalibBeamShift, CalibMovieDelays, CalibStageRotation +from instamatic.calibrate.filenames import CALIB_BEAMSHIFT +from instamatic.experiments.experiment_base import ExperimentBase +from instamatic.processing.ImgConversionTPX import ImgConversionTPX as ImgConversion + + +def safe_range(*, start: float, stop: float, step: float) -> np.ndarray: + """Find 2+ floats between `start` and `stop` (inclusive) ~`step` apart.""" + step_count = max(round(abs(stop - start) / step) + 1, 2) + return np.linspace(start, stop, step_count, endpoint=True, dtype=float) + + +class FastADTEarlyTermination(RuntimeError): + """Raised if FastADT experiment terminates early due to known reasons.""" + + +class FastADTMissingCalibError(RuntimeError): + """Raised if some calibration is strictly required but missing.""" + + +@dataclass +class Step: + """A dataclass representing a single step in the experimental `Run`.""" + + Index: int + alpha: float + beamshift_x: Optional[float] = None + beamshift_y: Optional[float] = None + delta_x: Optional[float] = None + delta_y: Optional[float] = None + image: Optional[np.ndarray] = None + meta: dict = field(default_factory=dict) + + +class Run: + """Collection of details of a generalized single FastADT run. + + Attributes + ---------- + exposure: float + Time spent collecting each frame, expressed in seconds + continuous: bool + Whether the run involves continuous scan (True) or single frames (False) + table: pd.DataFrame + Describes details of individual steps (to be) measured: + - alpha - average value of the rotation axes for given frame + - delta_x - x beam shift relative from center needed to track the crystal + - delta_y - y beam shift relative from center needed to track the crystal + """ + + def __init__(self, exposure=1.0, continuous=False, **columns: Sequence) -> None: + self.exposure: float = exposure + self.continuous: bool = continuous + self.table: pd.DataFrame = pd.DataFrame.from_dict(columns) + + @property + def scope(self) -> Tuple[float, float]: + """The range of alpha values scanned during the entire run.""" + a = self.table['alpha'] + if not self.continuous: + return a.iloc[0], a.iloc[-1] + return a.iloc[0] - self.osc_angle / 2, a.iloc[-1] + self.osc_angle / 2 + + @property + def steps(self) -> Iterator[Step]: + """Iterate over individual run `Step`s holding rows of `self.table`.""" + return (Step(**t._asdict()) for t in self.table.itertuples()) # noqa + + def interpolate(self, at: np.array, key: str) -> np.ndarray: + """Interpolate values of `table[key]` at some denser grid of points.""" + alpha, values = self.table['alpha'], self.table[key] + if at[0] > at[-1]: # decreasing order is not handled by numpy.interp + return np.interp(at[::-1], alpha[::-1], values[::-1])[::-1] + return np.interp(at, alpha, values) + + @property + def buffer(self) -> List[Tuple[int, np.ndarray, dict]]: + """Standardized list of (number, image, meta) used when saving.""" + return [(i, s.image, s.meta) for i, s in enumerate(self.steps)] + + @property + def has_beam_delta_information(self) -> bool: + return {'delta_x', 'delta_y'}.issubset(self.table.columns) + + @property + def osc_angle(self) -> float: + """Difference of alpha angle between two consecutive frames.""" + a = list(self.table['alpha']) + return (a[-1] - a[0]) / (len(a) - 1) if len(a) > 1 else -1 + + def to_continuous(self) -> Self: + """Construct a new run from N-1 first rows for continuous method.""" + new_alphas = self.table['alpha'].rolling(2).mean().drop(0) + new_cols = self.table.iloc[:-1, :].to_dict(orient='list') + del new_cols['alpha'] + c = self.__class__ + return c(exposure=self.exposure, continuous=True, alpha=new_alphas, **new_cols) + + def calculate_beamshifts(self, ctrl, beamshift) -> None: + """Note CalibBeamShift uses swapped axes: X points down, Y right.""" + beamshift_xy = ctrl.beamshift.get() + pixelcoord_xy = beamshift.beamshift_to_pixelcoord(beamshift_xy) + delta_xys = self.table[['delta_x', 'delta_y']].to_numpy() + crystal_xys = pixelcoord_xy + delta_xys + crystal_yxs = np.fliplr(crystal_xys) + beamshifts = beamshift.pixelcoord_to_beamshift(crystal_yxs) + self.table[['beamshift_x', 'beamshift_y']] = beamshifts + + +class TrackingRun(Run): + """Designed to estimate delta_x/y a priori based on manual used input.""" + + @classmethod + def from_params(cls, params: Dict[str, Any]) -> Self: + alpha_range = safe_range( + start=params['diffraction_start'], + stop=params['diffraction_stop'], + step=params['tracking_step'], + ) + return cls(exposure=params['tracking_time'], alpha=alpha_range) + + +class DiffractionRun(Run): + """The implementation for the actual diffraction experiment itself.""" + + @classmethod + def from_params( + cls, + params: Dict[str, Any], + tracking_run: Optional['TrackingRun'] = None, + ) -> Self: + alpha_range = safe_range( + start=params['diffraction_start'], + stop=params['diffraction_stop'], + step=params['diffraction_step'], + ) + run = cls(exposure=params['diffraction_time'], alpha=alpha_range) + if tracking_run is not None: + run.table['delta_x'] = tracking_run.interpolate(alpha_range, 'delta_x') + run.table['delta_y'] = tracking_run.interpolate(alpha_range, 'delta_y') + return run + + +class Experiment(ExperimentBase): + """Initialize a FastADT-style rotation electron diffraction experiment. + + Parameters + ---------- + ctrl: + Instance of instamatic.controller.TEMController + path: + `str` or `pathlib.Path` object giving the path to save data at + log: + Optional instance of `logging.Logger` + flatfield: + Optional path to flatfield correction image + experiment_frame: + Optional instance of `ExperimentalFastADT` used to display messages + videostream_frame: + Optional instance of `VideoStreamFrame` used to display messages + """ + + name = 'FastADT' + + def __init__( + self, + ctrl, + path: Optional[AnyPath] = None, + log: Optional[logging.Logger] = None, + flatfield: Optional[AnyPath] = None, + experiment_frame: Optional[Any] = None, + videostream_frame: Optional[Any] = None, + ): + super().__init__() + self.ctrl = ctrl + self.path = Path(path) + + self.mrc_path = self.path / 'mrc' + self.tiff_path = self.path / 'tiff' + self.tiff_image_path = self.path / 'tiff_image' + self.mrc_path.mkdir(exist_ok=True, parents=True) + self.tiff_path.mkdir(exist_ok=True, parents=True) + self.tiff_image_path.mkdir(exist_ok=True, parents=True) + + self.log = log or NullLogger() + self.flatfield = flatfield + self.fast_adt_frame = experiment_frame + self.beamshift: Optional[CalibBeamShift] = None + self.camera_length: int = 0 + self.diffraction_mode: str = '' + + if videostream_frame is not None: + d = videostream_frame.click_dispatcher + n = self.name + self.click_listener = c if (c := d.listeners.get(n)) else d.add_listener(n) + self.videostream_processor = videostream_frame.processor + else: # needed only for manual tracking + self.click_listener = None + self.videostream_processor = None + + self.steps_queue: Queue[Union[Step, None]] = Queue() + self.run: Optional[Run] = None + + def restore_fast_adt_diff_for_image(self): + """Restore 'FastADT_diff' config with 'FastADT_track' magnification.""" + self.ctrl.restore('FastADT_track') + tracking_mode = self.ctrl.mode.get() + tracking_magnification = self.ctrl.magnification.value + self.ctrl.restore('FastADT_diff') + self.ctrl.mode.set(tracking_mode) + self.ctrl.magnification.value = tracking_magnification + + def get_beamshift(self) -> CalibBeamShift: + """Must follow `self.restore_fast_adt_diff_for_image()` to see beam.""" + calib_dir = self.path.parent / 'calib' + try: + return CalibBeamShift.from_file(calib_dir / CALIB_BEAMSHIFT) + except OSError: + return CalibBeamShift.live(self.ctrl, outdir=calib_dir) + + def get_dead_time( + self, + exposure: float = 0.0, + header_keys_variable: tuple = (), + header_keys_common: tuple = (), + ) -> float: + """Get time between get_movie frames from any source available or 0.""" + try: + return self.ctrl.cam.dead_time + except AttributeError: + pass + self.msg('`cam.dead_time` not found. Looking for calibrated estimate...') + try: + c = CalibMovieDelays.from_file(exposure, header_keys_variable, header_keys_common) + except RuntimeWarning: + return 0.0 + else: + return c.dead_time + + def get_stage_rotation(self) -> CalibStageRotation: + """Get rotation calibration if present; otherwise warn & terminate.""" + try: + return CalibStageRotation.from_file() + except OSError: + msg = ( + 'Collecting cRED with this script requires calibrated stage rotation. ' + 'Please run `instamatic.calibrate_stage_rotation` first.' + ) + self.msg(msg) + raise FastADTMissingCalibError(msg) + + def msg(self, text: str) -> None: + """Display a message in log.info, consoles & FastADT frame at once.""" + try: + self.fast_adt_frame.message.set(text) + except AttributeError: + pass + print(text) + if text: + self.log.info(text) + + def start_collection(self, **params) -> None: + """Collect FastADT experiment according to provided **params. + + The FastADT experiment can behave quite differently depending on the + input parameters provided. For a full list of parameters, see + `instamatic/gui/fast_adt_frame.py:ExperimentalFastADTVariables`. + The following code is divided into four sections, each responsible + for a different part of the experiment. + + At the beginning, FastADT will blank the beam to minimize sample damage. + The beam will be un-blanked only when strictly needed, e.g. at the start + when collecting a direct-space preview image of the measured crystal. + + In the second part, if any kind of tracking was requested, a tracking + `Run`-object will be created and collected. + + After the image and tracking are collected, FastADT will prepare for + and collect the actual diffraction run. Depending on the experiment mode + requested, it might consist of several stills or a continuous movie. + + Finally, the collected run will be logged and the stage - reset. + """ + self.msg('FastADT experiment started') + with self.ctrl.beam.blanked(): + image_path = self.tiff_image_path / 'image.tiff' + if not image_path.exists(): + self.ctrl.restore('FastADT_image') + with self.ctrl.beam.unblanked(delay=0.2): + self.ctrl.get_image(params['tracking_time'], out=image_path) + + if params['tracking_mode'] == 'manual': + tracking_run = TrackingRun.from_params(params) + self.collect_manual_tracking(tracking_run) + else: + tracking_run = None + + self.run = DiffractionRun.from_params(params, tracking_run) + self.ctrl.restore('FastADT_diff') + self.camera_length = int(self.ctrl.magnification.get()) + self.diffraction_mode = params['diffraction_mode'] + if self.diffraction_mode == 'stills': + self.collect_stills(self.run) + elif self.diffraction_mode == 'continuous': + self.collect_continuous(self.run) + + print(self.run.table) + self.ctrl.restore('FastADT_image') + self.log.info('Collected the following run:') + self.log.info(str(self.run)) + self.ctrl.stage.a = 0.0 + + def collect_manual_tracking(self, run: TrackingRun) -> None: + """Determine the target beam shifts `delta_x` and `delta_y` manually, + based on the beam center found life (to find clicking offset) and + `TrackingRun` to be used for crystal tracking in later experiment.""" + + self.restore_fast_adt_diff_for_image() + self.beamshift = self.get_beamshift() + self.ctrl.stage.a = run.table.loc[len(run.table) // 2, 'alpha'] + with self.ctrl.beam.unblanked(): + self.msg('Collecting tracking. Click on the center of the beam.') + with self.click_listener as cl: + click = cl.get_click() + beam_center_x, beam_center_y = click.x, click.y + + self.ctrl.restore('FastADT_track') + delta_xs, delta_ys = [], [] + Thread(target=self.enqueue_still_steps, args=(run,), daemon=True).start() + while (step := self.steps_queue.get()) is not None: + with self.videostream_processor.temporary(frame=step.image): + m = f'Click on the crystal (image={step.Index}, alpha={step.alpha} deg).' + self.msg(m) + with self.click_listener as cl: + click = cl.get_click() + delta_xs.append(click.x - beam_center_x) + delta_ys.append(click.y - beam_center_y) + self.msg('') + run.table['delta_x'] = delta_xs + run.table['delta_y'] = delta_ys + self.plot_tracking(tracking_run=run) + + def collect_stills(self, run: Run) -> None: + """Collect a series of stills at angles/exposure specified in `run`""" + self.msg('Collecting stills from {} to {} degree'.format(*run.scope)) + images, metas = [], [] + if run.has_beam_delta_information: + run.calculate_beamshifts(self.ctrl, self.beamshift) + + with self.ctrl.beam.unblanked(delay=0.2), self.ctrl.cam.blocked(): + for step in run.steps: + if run.has_beam_delta_information: + self.ctrl.beamshift.set(step.beamshift_x, step.beamshift_y) + self.ctrl.stage.a = step.alpha + image, meta = self.ctrl.get_image(exposure=run.exposure) + images.append(image) + metas.append(meta) + run.table['image'] = images + run.table['meta'] = metas + self.msg('Collected stills from {} to {} degree'.format(*run.scope)) + + def enqueue_still_steps(self, run: Run) -> None: + """Get & put stills to `self.tracking_queue` to eval asynchronously.""" + with self.ctrl.beam.unblanked(delay=0.2), self.ctrl.cam.blocked(): + for step in run.steps: + self.ctrl.stage.a = step.alpha + step.image = self.ctrl.get_image(exposure=run.exposure)[0] + self.steps_queue.put(step) + self.steps_queue.put(None) + + def collect_continuous(self, run) -> None: + """Collect a series of scans at angles/exposure specified in `run`""" + self.msg('Collecting scans from {} to {} degree'.format(*run.scope)) + images, metas = [], [] + if run.has_beam_delta_information: + run.calculate_beamshifts(self.ctrl, self.beamshift) + + # this part correctly finds the closest possible speed settings for expt + detector_dead_time = self.get_dead_time(run.exposure) + time_for_one_frame = run.exposure + detector_dead_time + rot_calib = self.get_stage_rotation() + rot_plan = rot_calib.plan_rotation(time_for_one_frame / run.osc_angle) + run.exposure = abs(rot_plan.pace * run.osc_angle) - detector_dead_time + + self.ctrl.stage.a = float(run.table.loc[0, 'alpha']) + with self.ctrl.stage.rotation_speed(speed=rot_plan.speed): + with self.ctrl.beam.unblanked(delay=0.2): + movie = self.ctrl.get_movie(n_frames=len(run.table) - 1, exposure=run.exposure) + a = float(run.table.iloc[-1].loc['alpha']) + self.ctrl.stage.set_with_speed(a=a, speed=rot_plan.speed, wait=False) + for step, (image, header) in zip(run.steps, movie): + if run.has_beam_delta_information: + self.ctrl.beamshift.set(step.beamshift_x, step.beamshift_y) + images.append(image) + metas.append(header) + self.run = run.to_continuous() + self.run.table['image'] = images + self.run.table['meta'] = metas + self.msg('Collected scans from {} to {} degree'.format(*run.scope)) + + def finalize(self) -> None: + self.msg(f'Saving experiment in: {self.path}') + rotation_axis = config.camera.camera_rotation_vs_stage_xy + pixel_size = config.calibration['diff']['pixelsize'].get(self.camera_length, -1) + physical_pixel_size = config.camera.physical_pixelsize # mm + wavelength = config.microscope.wavelength # angstrom + stretch_azimuth = config.camera.stretch_azimuth + stretch_amplitude = config.camera.stretch_amplitude + + if self.diffraction_mode == 'continuous': + method = 'Continuous-Rotation 3D ED' + else: + method = 'Rotation Electron Diffraction' + + img_conv = ImgConversion( + buffer=self.run.buffer, + osc_angle=self.run.osc_angle, + start_angle=self.run.table['alpha'].iloc[0], + end_angle=self.run.table['alpha'].iloc[-1], + rotation_axis=rotation_axis, + acquisition_time=self.run.exposure, + flatfield=self.flatfield, + pixelsize=pixel_size, + physical_pixelsize=physical_pixel_size, + wavelength=wavelength, + stretch_amplitude=stretch_amplitude, + stretch_azimuth=stretch_azimuth, + method=method, + ) + img_conv.threadpoolwriter(tiff_path=self.tiff_path, mrc_path=self.mrc_path, workers=8) + img_conv.write_ed3d(self.mrc_path) + img_conv.write_pets_inp(self.path) + img_conv.write_beam_centers(self.path) + self.msg('Data collection and conversion done. FastADT experiment finalized.') + + def plot_tracking(self, tracking_run: Run) -> None: + """Plot tracking results in `VideoStreamFrame` and let user reject.""" + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + ax1.set_xlabel('alpha [degrees]') + ax1.set_ylabel('ΔX [pixels]') + ax2.set_ylabel('ΔY [pixels]') + ax1.yaxis.label.set_color('red') + ax2.yaxis.label.set_color('blue') + ax2.spines['left'].set_color('red') + ax2.spines['right'].set_color('blue') + ax1.tick_params(axis='y', colors='red') + ax2.tick_params(axis='y', colors='blue') + ax1.plot('alpha', 'delta_x', data=tracking_run.table, color='red', label='X') + ax2.plot('alpha', 'delta_y', data=tracking_run.table, color='blue', label='Y') + fig.tight_layout() + self.msg('Tracking results: left-click to accept, right-click to reject.') + with self.videostream_processor.temporary(figure=fig): + with self.click_listener as cl: + if cl.get_click().button != 1: + self.msg('Experiment abandoned after tracking.') + raise FastADTEarlyTermination('Experiment abandoned after tracking.') + + def teardown(self) -> None: + self.finalize() diff --git a/src/instamatic/gui/fast_adt_frame.py b/src/instamatic/gui/fast_adt_frame.py new file mode 100644 index 00000000..e004d228 --- /dev/null +++ b/src/instamatic/gui/fast_adt_frame.py @@ -0,0 +1,260 @@ +from __future__ import annotations + +import threading +from queue import Queue +from tkinter import * +from tkinter.ttk import * +from typing import Any, Optional + +from instamatic import controller +from instamatic.utils.spinbox import Spinbox + +from .base_module import BaseModule + +pad0 = {'sticky': 'EW', 'padx': 0, 'pady': 1} +pad10 = {'sticky': 'EW', 'padx': 10, 'pady': 1} +width = {'width': 19} +angle_lim = {'from_': -90, 'to': 90, 'increment': 1, 'width': 20} +angle_delta = {'from_': 0, 'to': 180, 'increment': 0.1, 'width': 20} +duration = {'from_': 0, 'to': 60, 'increment': 0.1} + + +class FastADTConfigProxy: + keys = ( + 'FunctionMode', + 'GunShift', + 'GunTilt', + 'BeamShift', + 'BeamTilt', + 'ImageShift1', + 'ImageShift2', + 'DiffShift', + 'Magnification', + 'DiffFocus', + 'Brightness', + 'SpotSize', + ) + + def __init__(self, name='') -> None: + self.ctrl = controller.get_instance() + self.name = name + + def store(self) -> None: + self.ctrl.store(f'FastADT_{self.name}', keys=self.keys, save_to_file=True) + + def restore(self) -> None: + self.ctrl.restore(f'FastADT_{self.name}') + + +class ExperimentalFastADTVariables: + """A collection of tkinter Variable instances passed to the experiment.""" + + def __init__(self): + self.diffraction_mode = StringVar() + self.diffraction_start = DoubleVar(value=-30) + self.diffraction_stop = DoubleVar(value=30) + self.diffraction_step = DoubleVar(value=0.5) + self.diffraction_time = DoubleVar(value=0.5) + self.tracking_mode = StringVar() + self.tracking_time = DoubleVar(value=0.5) + self.tracking_step = DoubleVar(value=5.0) + + def as_dict(self): + return { + v: getattr(self, v).get() + for v in dir(self) + if isinstance(getattr(self, v), Variable) + } + + +class ExperimentalFastADT(LabelFrame): + """GUI panel to perform selected FastADT-style (c)RED & PED experiments.""" + + def __init__(self, parent): # noqa: parent.__init__ is called + LabelFrame.__init__(self, parent, text='Experiment with a priori tracking options') + self.parent = parent + self.var = ExperimentalFastADTVariables() + self.q: Optional[Queue] = None + self.triggerEvent: Optional[threading.Event] = None + self.busy: bool = False + self.ctrl = controller.get_instance() + + # Top-aligned part of the frame with experiment parameters + f = Frame(self) + + Label(f, text='Diffraction mode:').grid(row=3, column=0, **pad10) + self.diffraction_mode = Combobox(f, textvariable=self.var.diffraction_mode, **width) + self.diffraction_mode['values'] = ['stills', 'continuous'] + self.diffraction_mode['state'] = 'readonly' + self.diffraction_mode.grid(row=3, column=1, **pad10) + self.diffraction_mode.current(0) + + Label(f, text='Diffraction start (deg):').grid(row=4, column=0, **pad10) + var = self.var.diffraction_start + self.diffraction_start = Spinbox(f, textvariable=var, **angle_lim) + self.diffraction_start.grid(row=4, column=1, **pad10) + + Label(f, text='Diffraction stop (deg):').grid(row=5, column=0, **pad10) + var = self.var.diffraction_stop + self.diffraction_stop = Spinbox(f, textvariable=var, **angle_lim) + self.diffraction_stop.grid(row=5, column=1, **pad10) + + Label(f, text='Diffraction step (deg):').grid(row=6, column=0, **pad10) + var = self.var.diffraction_step + self.diffraction_step = Spinbox(f, textvariable=var, **angle_delta) + self.diffraction_step.grid(row=6, column=1, **pad10) + + Label(f, text='Diffraction exposure (s):').grid(row=7, column=0, **pad10) + var = self.var.diffraction_time + self.diffraction_time = Spinbox(f, textvariable=var, **duration) + self.diffraction_time.grid(row=7, column=1, **pad10) + + Label(f, text='Tracking mode:').grid(row=3, column=2, **pad10) + var = self.var.tracking_mode + self.tracking_mode = Combobox(f, textvariable=var, **width) + self.tracking_mode['values'] = ['none', 'manual'] + self.tracking_mode['state'] = 'readonly' + self.tracking_mode.grid(row=3, column=3, **pad10) + self.tracking_mode.bind('<>', self.update_widget_state) + self.tracking_mode.current(0) + + Label(f, text='Tracking step (deg):').grid(row=6, column=2, **pad10) + var = self.var.tracking_step + self.tracking_step = Spinbox(f, textvariable=var, **angle_delta) + self.tracking_step.grid(row=6, column=3, **pad10) + + Label(f, text='Tracking exposure (s):').grid(row=7, column=2, **pad10) + var = self.var.tracking_time + self.tracking_time = Spinbox(f, textvariable=var, **duration) + self.tracking_time.grid(row=7, column=3, **pad10) + + Separator(f, orient=HORIZONTAL).grid(row=8, columnspan=4, sticky=EW, padx=10, pady=10) + + f.pack(side='top', fill='x', pady=10) + + # Store / restore settings buttons + g = Frame(f) + b = Label(g, width=1, text='Config settings:', anchor=NW) + b.grid(row=9, column=0, **pad0) + + image_config = FastADTConfigProxy('image') + track_config = FastADTConfigProxy('track') + diff_config = FastADTConfigProxy('diff') + + text = 'Beam blank/unblank' + beam_blank = Button(g, width=1, text=text, command=self.toggle_beam_blank) + beam_blank.grid(row=10, column=0, **pad0) + + text = 'Image store' + self.image_store = Button(g, width=1, text=text, command=image_config.store) + self.image_store.grid(row=9, column=1, **pad0) + + text = 'Image restore' + self.image_restore = Button(g, width=1, text=text, command=image_config.restore) + self.image_restore.grid(row=10, column=1, **pad0) + + text = 'Tracking store' + self.track_store = Button(g, width=1, text=text, command=track_config.store) + self.track_store.grid(row=9, column=2, **pad0) + + text = 'Tracking restore' + self.track_restore = Button(g, width=1, text=text, command=track_config.restore) + self.track_restore.grid(row=10, column=2, **pad0) + + text = 'Diffraction store' + self.diff_store = Button(g, width=1, text=text, command=diff_config.store) + self.diff_store.grid(row=9, column=3, **pad0) + + text = 'Diffraction restore' + self.diff_restore = Button(g, width=1, text=text, command=diff_config.restore) + self.diff_restore.grid(row=10, column=3, **pad0) + + g.columnconfigure(0, weight=1) + g.columnconfigure(1, weight=1) + g.columnconfigure(2, weight=1) + g.columnconfigure(3, weight=1) + g.grid(row=9, column=0, columnspan=4, sticky=EW, padx=10) + + Separator(f, orient=HORIZONTAL).grid(row=11, columnspan=4, sticky=EW, padx=10, pady=10) + + # Center-aligned sticky message area and bottom start button + f = Frame(self) + + self.message = StringVar(value='Further information will appear here.') + self.message_area = Label(f, textvariable=self.message, anchor=NW) + self.message_area.pack(fill='both', expand=True) + f.pack(side='top', fill='both', expand=True, padx=10) + + self.start_button = Button(self, text='Start', width=1, command=self.start_collection) + self.start_button.pack(side='bottom', fill='x', padx=10, pady=10) + + self.update_widget_state() + + def toggle_beam_blank(self) -> None: + (self.ctrl.beam.unblank if self.ctrl.beam.is_blanked else self.ctrl.beam.blank)() + + def update_widget_state(self, *_, busy: Optional[bool] = None, **__) -> None: + self.busy = busy if busy is not None else self.busy + no_tracking = self.var.tracking_mode.get() == 'none' + widget_state = 'disabled' if self.busy else 'enabled' + tracking_state = 'disabled' if self.busy or no_tracking else 'enabled' + + self.start_button.config(state=widget_state) + self.diffraction_mode.config(state=widget_state) + self.diffraction_start.config(state=widget_state) + self.diffraction_stop.config(state=widget_state) + self.diffraction_step.config(state=widget_state) + self.diffraction_time.config(state=widget_state) + self.tracking_mode.config(state=widget_state) + + self.tracking_step.config(state=tracking_state) + self.tracking_time.config(state=tracking_state) + + def start_collection(self) -> None: + self.q.put(('fast_adt', {'frame': self, **self.var.as_dict()})) + self.triggerEvent.set() + + def set_trigger(self, trigger: threading.Event, q: Queue) -> None: + """A boilerplate method, connects to a GUI thread and command queue.""" + self.triggerEvent: threading.Event = trigger + self.q: Queue = q + + +def fast_adt_interface_command(controller, **params: Any) -> None: + from instamatic.experiments import fast_adt as fast_adt_module + + fast_adt_frame: ExperimentalFastADT = params['frame'] + flat_field = controller.module_io.get_flatfield() + exp_dir = controller.module_io.get_new_experiment_directory() + videostream_frame = controller.app.get_module('stream') + exp_dir.mkdir(exist_ok=True, parents=True) + + controller.fast_adt = fast_adt_module.Experiment( + ctrl=controller.ctrl, + path=exp_dir, + log=controller.log, + flatfield=flat_field, + experiment_frame=fast_adt_frame, + videostream_frame=videostream_frame, + ) + try: + fast_adt_frame.update_widget_state(busy=True) + controller.fast_adt.start_collection(**params) + controller.fast_adt.finalize() + except RuntimeError: + pass # RuntimeError is raised if experiment is terminated early + finally: + del controller.fast_adt + fast_adt_frame.update_widget_state(busy=False) + + +module = BaseModule( + name='fast_adt', display_name='FastADT', tk_frame=ExperimentalFastADT, location='bottom' +) +commands = {'fast_adt': fast_adt_interface_command} + + +if __name__ == '__main__': + root = Tk() + ExperimentalFastADT(root).pack(side='top', fill='both', expand=True) + root.mainloop() diff --git a/src/instamatic/gui/modules.py b/src/instamatic/gui/modules.py index c87aa4f3..da57934e 100644 --- a/src/instamatic/gui/modules.py +++ b/src/instamatic/gui/modules.py @@ -13,6 +13,7 @@ 'cred', 'cred_tvips', 'cred_fei', + 'fast_adt', 'sed', 'autocred', 'red', @@ -47,7 +48,7 @@ try: for job, function in lib.commands.items(): if job in JOBS: - logger.info(f'New job `{job}` already exists in `JOBS` listsing!') + logger.info(f'New job `{job}` already exists in `JOBS` listing!') else: JOBS[job] = function except AttributeError: diff --git a/tests/alignments/FastADT_diff.yaml b/tests/alignments/FastADT_diff.yaml new file mode 100644 index 00000000..302f23ca --- /dev/null +++ b/tests/alignments/FastADT_diff.yaml @@ -0,0 +1 @@ +FunctionMode: mag1 diff --git a/tests/alignments/FastADT_image.yaml b/tests/alignments/FastADT_image.yaml new file mode 100644 index 00000000..302f23ca --- /dev/null +++ b/tests/alignments/FastADT_image.yaml @@ -0,0 +1 @@ +FunctionMode: mag1 diff --git a/tests/alignments/FastADT_track.yaml b/tests/alignments/FastADT_track.yaml new file mode 100644 index 00000000..302f23ca --- /dev/null +++ b/tests/alignments/FastADT_track.yaml @@ -0,0 +1 @@ +FunctionMode: mag1 diff --git a/tests/config/calibration/calib_stage_rotation.yaml b/tests/config/calibration/calib_stage_rotation.yaml new file mode 100644 index 00000000..6c633647 --- /dev/null +++ b/tests/config/calibration/calib_stage_rotation.yaml @@ -0,0 +1,5 @@ +alpha_pace: 1.0 +alpha_windup: 0.0 +delay: 0.0 +speed_options: + options: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] diff --git a/tests/config/camera/test.yaml b/tests/config/camera/test.yaml index db1874c3..202fdf33 100644 --- a/tests/config/camera/test.yaml +++ b/tests/config/camera/test.yaml @@ -8,3 +8,4 @@ interface: simulate physical_pixelsize: 0.055 stretch_amplitude: 2.43 stretch_azimuth: 83.37 +dead_time: 0.0 diff --git a/tests/test_experiments.py b/tests/test_experiments.py index 5c549a24..521dc259 100644 --- a/tests/test_experiments.py +++ b/tests/test_experiments.py @@ -1,13 +1,16 @@ from __future__ import annotations import threading +from dataclasses import dataclass, field from pathlib import Path +from typing import Any from unittest.mock import MagicMock import pytest -from instamatic.experiments import RED, cRED, cRED_tvips +from instamatic.experiments import RED, cRED, cRED_tvips, fast_adt from instamatic.experiments.experiment_base import ExperimentBase +from tests.utils import InstanceAutoTracker def test_autoCRED(ctrl): @@ -27,64 +30,72 @@ def test_serialED(ctrl): pytest.xfail('TODO') -@pytest.mark.parametrize( - ['exp_cls', 'init_kwargs', 'collect_kwargs', 'num_collections'], - [ - ( - cRED.Experiment, - { - 'stop_event': threading.Event(), - 'mode': 'simulate', - }, - {}, - 1, - ), - ( - cRED_tvips.Experiment, - { - 'mode': 'diff', - 'track': None, - 'exposure': 0.1, - }, - { - 'target_angle': -20, - 'manual_control': False, - }, - 1, - ), - ( - RED.Experiment, - { - 'flatfield': None, - }, - { - 'exposure_time': 0.01, - 'tilt_range': 5, - 'stepsize': 1.0, - }, - 2, - ), - ], +@dataclass +class ExperimentTestCase(InstanceAutoTracker): + """Auto-registers experiment test case instances in INSTANCES.""" + + cls: type[ExperimentBase] + init_kwargs: dict[str, Any] = field(default_factory=dict) + collect_kwargs: dict[str, Any] = field(default_factory=dict) + num_collections: int = 1 + + +ExperimentTestCase( + cls=cRED.Experiment, + init_kwargs={'stop_event': threading.Event(), 'mode': 'simulate'}, +) + +ExperimentTestCase( + cls=cRED_tvips.Experiment, + init_kwargs={'mode': 'diff', 'track': None, 'exposure': 0.1}, + collect_kwargs={'target_angle': -20, 'manual_control': False}, +) + +ExperimentTestCase( + cls=RED.Experiment, + init_kwargs={'flatfield': None}, + collect_kwargs={'exposure_time': 0.01, 'tilt_range': 5, 'stepsize': 1.0}, + num_collections=2, ) -def test_experiment( - exp_cls: 'type[ExperimentBase]', - init_kwargs: dict, - collect_kwargs: dict, - num_collections: int, - ctrl, - tmp_path, -): - init_kwargs['ctrl'] = ctrl - - init_kwargs['path'] = tmp_path - - logger = MagicMock() - init_kwargs['log'] = logger - - stop_event = init_kwargs.get('stop_event') + +fast_adt_common_collect_kwargs = { + 'diffraction_step': 0.5, + 'diffraction_time': 0.01, + 'tracking_mode': 'none', + 'tracking_time': 0.01, +} + +ExperimentTestCase( + cls=fast_adt.Experiment, + collect_kwargs={ + 'diffraction_mode': 'stills', + 'diffraction_start': -1, + 'diffraction_stop': 1, + **fast_adt_common_collect_kwargs, + }, +) + +ExperimentTestCase( + cls=fast_adt.Experiment, + collect_kwargs={ + 'diffraction_mode': 'continuous', + 'diffraction_start': 1, + 'diffraction_stop': -1, + **fast_adt_common_collect_kwargs, + }, +) + + +@pytest.mark.parametrize('test_case', ExperimentTestCase.INSTANCES) +def test_experiment(test_case: ExperimentTestCase, ctrl, tmp_path) -> None: + test_case.init_kwargs['ctrl'] = ctrl + test_case.init_kwargs['path'] = tmp_path + test_case.init_kwargs['log'] = MagicMock() + + stop_event = test_case.init_kwargs.get('stop_event') if stop_event is not None: stop_event.set() - with exp_cls(**init_kwargs) as exp: - for _ in range(num_collections): - exp.start_collection(**collect_kwargs) + with test_case.cls(**test_case.init_kwargs) as exp: + for _ in range(test_case.num_collections): + exp.start_collection(**test_case.collect_kwargs) diff --git a/tests/test_pets_input.py b/tests/test_pets_input.py index 30543df4..386241d6 100644 --- a/tests/test_pets_input.py +++ b/tests/test_pets_input.py @@ -7,51 +7,52 @@ import pytest from instamatic.processing.PETS_input_factory import PetsInputFactory, PetsInputWarning +from tests.utils import InstanceAutoTracker -@dataclass -class PetsInputTestInfixCase: +@dataclass(frozen=True) +class PetsInputTestInfixCase(InstanceAutoTracker): prefix: Optional[str] suffix: Optional[str] result: str warnings: tuple[type[UserWarning]] = () -case0 = PetsInputTestInfixCase( # tests if factory works in general +PetsInputTestInfixCase( # tests if factory works in general prefix=None, suffix=None, result='# Title\ndetector asi\nreflectionsize 20', ) -case1 = PetsInputTestInfixCase( # tests if prefix overwrites commands overwrites suffix +PetsInputTestInfixCase( # tests if prefix overwrites commands overwrites suffix prefix='detector default', suffix='reflectionsize 8', result='# Title\ndetector default\nreflectionsize 20', warnings=(PetsInputWarning,), ) -case2 = PetsInputTestInfixCase( # tests if {format} fields are partially substituted +PetsInputTestInfixCase( # tests if {format} fields are partially substituted prefix='detector {detector}\nreflectionsize {reflectionsize}', suffix=None, result='# Title\ndetector {detector}\nreflectionsize 9', warnings=(PetsInputWarning,), ) -case3 = PetsInputTestInfixCase( # tests if partially-duplicate suffix is partially removed +PetsInputTestInfixCase( # tests if partially-duplicate suffix is partially removed prefix=None, suffix='reflectionsize 8\nnoiseparameters 3.5 38', result='# Title\ndetector asi\nreflectionsize 20\nnoiseparameters 3.5 38', warnings=(PetsInputWarning,), ) -case4 = PetsInputTestInfixCase( # tests the consistency of comment and empty line behavior +PetsInputTestInfixCase( # tests the consistency of comment and empty line behavior prefix='# Prefix1\n\n# Prefix3\n\n', # trailing \n cut suffix='', # empty suffix ignored, so \n is not added result='# Title\n# Prefix1\n\n# Prefix3\n\ndetector asi\nreflectionsize 20', ) -@pytest.mark.parametrize('infix_case', [case0, case1, case2, case3, case4]) +@pytest.mark.parametrize('infix_case', PetsInputTestInfixCase.INSTANCES) def test_pets_input(infix_case): pif = PetsInputFactory() diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..040c7b75 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing_extensions import Self + + +class InstanceAutoTracker: + """Track cls instances: useful for @pytest.mark.parametrize dataclasses""" + + def __init_subclass__(cls, **kwargs) -> None: + super().__init_subclass__(**kwargs) + cls.INSTANCES: list[Self] = [] + + def __post_init__(self) -> None: + self.__class__.INSTANCES.append(self) From 4ad616d09b03f00e968a535add2253b57bb5f9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Wed, 20 Aug 2025 15:56:15 +0200 Subject: [PATCH 02/11] Fix `PartialFormatter` needlessly stripping `format_spec` --- src/instamatic/_collections.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/instamatic/_collections.py b/src/instamatic/_collections.py index d2250f08..a2c9852b 100644 --- a/src/instamatic/_collections.py +++ b/src/instamatic/_collections.py @@ -1,11 +1,10 @@ from __future__ import annotations -import contextlib import logging import string -import time from collections import UserDict -from typing import Any, Callable +from dataclasses import dataclass +from typing import Any class NoOverwriteDict(UserDict): @@ -18,6 +17,8 @@ def __setitem__(self, key: Any, value: Any) -> None: class NullLogger(logging.Logger): + """A logger mock that ignores all logging, to be used in headless mode.""" + def __init__(self, name='null'): super().__init__(name) self.addHandler(logging.NullHandler()) @@ -27,6 +28,10 @@ def __init__(self, name='null'): class PartialFormatter(string.Formatter): """`str.format` alternative, allows for partial replacement of {fields}""" + @dataclass(frozen=True) + class Missing: + name: str + def __init__(self, missing: str = '{{{}}}') -> None: super().__init__() self.missing: str = missing # used instead of missing values @@ -34,17 +39,17 @@ def __init__(self, missing: str = '{{{}}}') -> None: def get_field(self, field_name: str, args, kwargs) -> tuple[Any, str]: """When field can't be found, return placeholder text instead.""" try: - obj, used_key = super().get_field(field_name, args, kwargs) - return obj, used_key + return super().get_field(field_name, args, kwargs) except (KeyError, AttributeError, IndexError, TypeError): - return self.missing.format(field_name), field_name + return PartialFormatter.Missing(field_name), field_name def format_field(self, value: Any, format_spec: str) -> str: """If the field was not found, format placeholder as string instead.""" - try: - return super().format_field(value, format_spec) - except (ValueError, TypeError): - return str(value) + if isinstance(value, PartialFormatter.Missing): + if format_spec: + return self.missing.format(f'{value.name}:{format_spec}') + return self.missing.format(f'{value.name}') + return super().format_field(value, format_spec) partial_formatter = PartialFormatter() From 2cf6890ce04177a761481c0e13f023dbd134f691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Wed, 20 Aug 2025 15:57:23 +0200 Subject: [PATCH 03/11] Add tests for all members of `instamatic._collections` --- tests/test_collections.py | 76 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_collections.py diff --git a/tests/test_collections.py b/tests/test_collections.py new file mode 100644 index 00000000..7c41a45c --- /dev/null +++ b/tests/test_collections.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import logging +from contextlib import nullcontext +from dataclasses import dataclass, field +from typing import Any, Optional, Type + +import pytest + +import instamatic._collections as ic +from tests.utils import InstanceAutoTracker + + +def test_no_overwrite_dict() -> None: + """Should work as normal dict unless key exists, in which case raises.""" + nod = ic.NoOverwriteDict({1: 2}) + nod.update({3: 4}) + nod[5] = 6 + del nod[1] + nod[1] = 6 + assert nod == {1: 6, 3: 4, 5: 6} + with pytest.raises(KeyError): + nod[1] = 2 + with pytest.raises(KeyError): + nod.update({3: 4}) + + +def test_null_logger(caplog) -> None: + """NullLogger should void and not propagate messages to root logger.""" + + messages = [] + handler = logging.StreamHandler() + handler.emit = lambda record: messages.append(record.getMessage()) + null_logger = ic.NullLogger() + root_logger = logging.getLogger() + root_logger.addHandler(handler) + + with caplog.at_level(logging.DEBUG): + null_logger.debug('debug message that should be ignored') + null_logger.info('info message that should be ignored') + null_logger.warning('warning message that should be ignored') + null_logger.error('error message that should be ignored') + null_logger.critical('critical message that should be ignored') + + # Nothing should have been captured by pytest's caplog + root_logger.removeHandler(handler) + assert caplog.records == [] + assert caplog.text == '' + assert messages == [] + + +@dataclass +class PartialFormatterTestCase(InstanceAutoTracker): + template: str = '{s} & {f:06.2f}' + args: list[Any] = field(default_factory=list) + kwargs: dict[str, Any] = field(default_factory=dict) + returns: str = '' + raises: Optional[Type[Exception]] = None + + +PartialFormatterTestCase(returns='{s} & {f:06.2f}') +PartialFormatterTestCase(kwargs={'s': 'Text'}, returns='Text & {f:06.2f}') +PartialFormatterTestCase(kwargs={'f': 3.1415}, returns='{s} & 003.14') +PartialFormatterTestCase(kwargs={'x': 'test'}, returns='{s} & {f:06.2f}') +PartialFormatterTestCase(kwargs={'f': 'Text'}, raises=ValueError) +PartialFormatterTestCase(template='{0}{1}', args=[5], returns='5{1}') +PartialFormatterTestCase(template='{0}{1}', args=[5, 6], returns='56') +PartialFormatterTestCase(template='{0}{1}', args=[5, 6, 7], returns='56') + + +@pytest.mark.parametrize('test_case', PartialFormatterTestCase.INSTANCES) +def test_partial_formatter(test_case) -> None: + """Should replace only some {words}, but still fail if format is wrong.""" + c = test_case + with pytest.raises(r) if (r := c.raises) else nullcontext(): + assert ic.partial_formatter.format(c.template, *c.args, **c.kwargs) == c.returns From d1aa93260affb0c1adcb3599be55057228dd895f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Wed, 20 Aug 2025 18:59:42 +0200 Subject: [PATCH 04/11] Improve gatansocket3.py longarray padding to numpy-2 compatible --- src/instamatic/camera/gatansocket3.py | 41 +++++++++++---------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/instamatic/camera/gatansocket3.py b/src/instamatic/camera/gatansocket3.py index 87baf577..4c4d9b89 100644 --- a/src/instamatic/camera/gatansocket3.py +++ b/src/instamatic/camera/gatansocket3.py @@ -86,6 +86,17 @@ sArgsBuffer = np.zeros(ARGS_BUFFER_SIZE, dtype=np.byte) +def string_to_longarray(string: str, *, dtype: type = np.int_) -> np.ndarray: + """Convert the string to a 1D np array of dtype (default np.int_ - C long) + with numpy2-save padding to ensure length is a multiple of dtype.itemsize. + """ + s_bytes = string.encode('utf-8') + dtype_size = np.dtype(type).itemsize + if extra := len(s_bytes) % dtype_size: + s_bytes += b'\0' * (dtype_size - extra) + return np.frombuffer(s_bytes, dtype=dtype) + + class Message: """Information packet to send and receive on the socket. @@ -335,14 +346,7 @@ def SetK2Parameters( funcCode = enum_gs['GS_SetK2Parameters'] self.save_frames = saveFrames - - # filter name - filt_str = filt + '\0' - extra = len(filt_str) % 4 - if extra: - npad = 4 - extra - filt_str = filt_str + npad * '\0' - longarray = np.frombuffer(filt_str.encode(), dtype=np.int_) + longarray = string_to_longarray(filt + '\0', dtype=np.int_) # filter name longs = [ funcCode, @@ -397,12 +401,7 @@ def SetupFileSaving( longs = [enum_gs['GS_SetupFileSaving'], rotationFlip] dbls = [pixelSize] bools = [filePerImage] - names_str = dirname + '\0' + rootname + '\0' - extra = len(names_str) % 4 - if extra: - npad = 4 - extra - names_str = names_str + npad * '\0' - longarray = np.frombuffer(names_str.encode(), dtype=np.int_) + longarray = string_to_longarray(dirname + '\0' + rootname + '\0', dtype=np.int_) message_send = Message( longargs=longs, boolargs=bools, dblargs=dbls, longarray=longarray ) @@ -664,24 +663,18 @@ def ExecuteScript( select_camera=0, recv_longargs_init=(0,), recv_dblargs_init=(0.0,), - recv_longarray_init=[], + recv_longarray_init=None, ): + """Send the command string as a 1D longarray of np.int_ dtype.""" funcCode = enum_gs['GS_ExecuteScript'] - cmd_str = command_line + '\0' - extra = len(cmd_str) % 4 - if extra: - npad = 4 - extra - cmd_str = cmd_str + (npad) * '\0' - # send the command string as 1D longarray - longarray = np.frombuffer(cmd_str.encode(), dtype=np.int_) - # print(longaray) + longarray = string_to_longarray(command_line + '\0', dtype=np.int_) message_send = Message( longargs=(funcCode,), boolargs=(select_camera,), longarray=longarray ) message_recv = Message( longargs=recv_longargs_init, dblargs=recv_dblargs_init, - longarray=recv_longarray_init, + longarray=[] if recv_longarray_init is None else recv_longarray_init, ) self.ExchangeMessages(message_send, message_recv) return message_recv From 49eb28b5d5891bb6177145d3a836c4442a7d7b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Wed, 20 Aug 2025 19:05:00 +0200 Subject: [PATCH 05/11] Improve gatansocket3.py longarray padding to numpy-2 compatible --- src/instamatic/camera/gatansocket3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/instamatic/camera/gatansocket3.py b/src/instamatic/camera/gatansocket3.py index 4c4d9b89..084608fe 100644 --- a/src/instamatic/camera/gatansocket3.py +++ b/src/instamatic/camera/gatansocket3.py @@ -86,12 +86,12 @@ sArgsBuffer = np.zeros(ARGS_BUFFER_SIZE, dtype=np.byte) -def string_to_longarray(string: str, *, dtype: type = np.int_) -> np.ndarray: +def string_to_longarray(string: str, *, dtype: np.dtype = np.int_) -> np.ndarray: """Convert the string to a 1D np array of dtype (default np.int_ - C long) with numpy2-save padding to ensure length is a multiple of dtype.itemsize. """ s_bytes = string.encode('utf-8') - dtype_size = np.dtype(type).itemsize + dtype_size = np.dtype(dtype).itemsize if extra := len(s_bytes) % dtype_size: s_bytes += b'\0' * (dtype_size - extra) return np.frombuffer(s_bytes, dtype=dtype) From 71438f67ff3bc906460cd7701f27f108ac14a31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Wed, 20 Aug 2025 20:20:15 +0200 Subject: [PATCH 06/11] Add simple tests for, clear duplicates in instamatic.tools --- scripts/process_dm.py | 18 ++------------ src/instamatic/tools.py | 15 ++++++------ tests/test_tools.py | 52 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 tests/test_tools.py diff --git a/scripts/process_dm.py b/scripts/process_dm.py index d861f78a..fba2c22d 100644 --- a/scripts/process_dm.py +++ b/scripts/process_dm.py @@ -8,6 +8,7 @@ from skimage.exposure import rescale_intensity from instamatic.processing.ImgConversionDM import ImgConversionDM as ImgConversion +from instamatic.tools import relativistic_wavelength # Script to process cRED data collecting using the DigitalMicrograph script `insteaDMatic` # https://github.com/instamatic-dev/InsteaDMatic @@ -24,21 +25,6 @@ # all `cred_log.txt` files in the subdirectories, and iterate over those. -def relativistic_wavelength(voltage: float = 200): - """Calculate the relativistic wavelength of electrons Voltage in kV Return - wavelength in Angstrom.""" - voltage *= 1000 # -> V - - h = 6.626070150e-34 # planck constant J.s - m = 9.10938356e-31 # electron rest mass kg - e = 1.6021766208e-19 # elementary charge C - c = 299792458 # speed of light m/s - - wl = h / (2 * m * voltage * e * (1 + (e * voltage) / (2 * m * c**2))) ** 0.5 - - return round(wl * 1e10, 6) # m -> Angstrom - - def img_convert(credlog, tiff_path='tiff2', mrc_path='RED', smv_path='SMV'): credlog = Path(credlog) drc = credlog.parent @@ -90,7 +76,7 @@ def img_convert(credlog, tiff_path='tiff2', mrc_path='RED', smv_path='SMV'): if line.startswith('Resolution:'): resolution = line.split()[-1] - wavelength = relativistic_wavelength(high_tension) + wavelength = relativistic_wavelength(high_tension * 1000) # convert from um to mm physical_pixelsize = physical_pixelsize[0] / 1000 diff --git a/src/instamatic/tools.py b/src/instamatic/tools.py index 6711316a..1a0b2a03 100644 --- a/src/instamatic/tools.py +++ b/src/instamatic/tools.py @@ -1,14 +1,11 @@ from __future__ import annotations -import glob -import os import sys from pathlib import Path -from typing import Tuple +from typing import Iterator import numpy as np from scipy import interpolate, ndimage -from skimage import exposure from skimage.measure import regionprops @@ -71,9 +68,13 @@ def to_xds_untrusted_area(kind: str, coords: list) -> str: raise ValueError('Only quadrilaterals are supported for now') -def find_subranges(lst: list) -> Tuple[int, int]: +def find_subranges(lst: list[int]) -> Iterator[tuple[int, int]]: """Takes a range of sequential numbers (possibly with gaps) and splits them - in sequential sub-ranges defined by the minimum and maximum value.""" + in sequential sub-ranges defined by the minimum and maximum value. + + Example: + [1,2,3,7,8,10] --> (1,3), (7,8), (10,10) + """ from itertools import groupby from operator import itemgetter @@ -274,7 +275,7 @@ def get_acquisition_time( def relativistic_wavelength(voltage: float = 200_000) -> float: - """Calculate the relativistic wavelength of electrons from the accelarating + """Calculate the relativistic wavelength of electrons from the accelerating voltage. Input: Voltage in V diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 00000000..a1f66a8c --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from contextlib import nullcontext +from dataclasses import dataclass +from typing import Optional, Type + +import numpy as np +import pytest + +import instamatic.tools as it +from tests.utils import InstanceAutoTracker + + +def test_prepare_grid_coordinates() -> None: + g1 = [[-1, -1], [0, -1], [1, -1], [-1, 0], [0, 0], [1, 0], [-1, 1], [0, 1], [1, 1]] + g2 = it.prepare_grid_coordinates(3, 3, 1) + np.testing.assert_array_equal(np.array(g1), g2) + + +@dataclass +class XdsUntrustedAreaTestCase(InstanceAutoTracker): + kind: str + coords: list + output_len: Optional[int] = None + raises: Optional[Type[Exception]] = None + + +XdsUntrustedAreaTestCase('quadrilateral', [[1, 2]], output_len=28) +XdsUntrustedAreaTestCase('rectangle', [[1, 2], [3, 4]], output_len=28) +XdsUntrustedAreaTestCase('ellipse', [[1, 2], [3, 4]], output_len=26) +XdsUntrustedAreaTestCase('bollocks', [[[1, 2], [3, 4]]], raises=ValueError) + + +@pytest.mark.parametrize('test_case', XdsUntrustedAreaTestCase.INSTANCES) +def test_to_xds_untrusted_area(test_case: XdsUntrustedAreaTestCase) -> None: + """Simple test, just confirm if runs and the output has correct size.""" + with pytest.raises(e) if (e := test_case.raises) else nullcontext(): + output = it.to_xds_untrusted_area(test_case.kind, test_case.coords) + assert len(output) == test_case.output_len + + +def test_find_subranges() -> None: + """Test for sub-ranges from consecutive numbers, pairs, and singletons.""" + input_list = [1, 2, 3, 7, 8, 10] + output_list = list(it.find_subranges(input_list)) + assert output_list == [(1, 3), (7, 8), (10, 10)] + + +def test_relativistic_wavelength() -> None: + assert it.relativistic_wavelength(voltage=120_000) == 0.033492 + assert it.relativistic_wavelength(voltage=200_000) == 0.025079 + assert it.relativistic_wavelength(voltage=300_000) == 0.019687 From 3e7de09202af897db3db814e05a2dd70adbe751d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Thu, 18 Sep 2025 13:52:04 +0200 Subject: [PATCH 07/11] Temporarily require numpy > 2 for GitHub testing --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9485f68b..b66fc99d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "lmfit >= 1.0.0", "matplotlib >= 3.1.2", "mrcfile >= 1.1.2", - "numpy >= 1.17.3, <2", + "numpy >= 2", "pandas >= 1.0.0", "pillow >= 7.0.0", "pywinauto >= 0.6.8; sys_platform == 'windows'", diff --git a/requirements.txt b/requirements.txt index d0d58ae1..5253cf43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ ipython >= 7.11.1 lmfit >= 1.0.0 matplotlib >= 3.1.2 mrcfile >= 1.1.2 -numpy >= 1.17.3, <2 +numpy >= 2 pandas >= 1.0.0 pillow >= 7.0.0 pywinauto >= 0.6.8; sys_platform == 'windows' From cb6e4cd1051ed7b3c346d263d7ce39d9d165ce2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Thu, 18 Sep 2025 14:18:47 +0200 Subject: [PATCH 08/11] Revert "Temporarily require numpy > 2 for GitHub testing" This reverts commit 3e7de09202af897db3db814e05a2dd70adbe751d. --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b66fc99d..9485f68b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "lmfit >= 1.0.0", "matplotlib >= 3.1.2", "mrcfile >= 1.1.2", - "numpy >= 2", + "numpy >= 1.17.3, <2", "pandas >= 1.0.0", "pillow >= 7.0.0", "pywinauto >= 0.6.8; sys_platform == 'windows'", diff --git a/requirements.txt b/requirements.txt index 5253cf43..d0d58ae1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ ipython >= 7.11.1 lmfit >= 1.0.0 matplotlib >= 3.1.2 mrcfile >= 1.1.2 -numpy >= 2 +numpy >= 1.17.3, <2 pandas >= 1.0.0 pillow >= 7.0.0 pywinauto >= 0.6.8; sys_platform == 'windows' From ecbb3c30e8ff809366509650667c6302ddf0c564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Thu, 18 Sep 2025 14:20:24 +0200 Subject: [PATCH 09/11] Lift the numpy < 2 requirement --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9485f68b..e69c913b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "lmfit >= 1.0.0", "matplotlib >= 3.1.2", "mrcfile >= 1.1.2", - "numpy >= 1.17.3, <2", + "numpy >= 1.17.3", "pandas >= 1.0.0", "pillow >= 7.0.0", "pywinauto >= 0.6.8; sys_platform == 'windows'", diff --git a/requirements.txt b/requirements.txt index d0d58ae1..e338809c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ ipython >= 7.11.1 lmfit >= 1.0.0 matplotlib >= 3.1.2 mrcfile >= 1.1.2 -numpy >= 1.17.3, <2 +numpy >= 1.17.3 pandas >= 1.0.0 pillow >= 7.0.0 pywinauto >= 0.6.8; sys_platform == 'windows' From 717a0a5c90854c36a4c605ef823d629f512412ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Thu, 18 Sep 2025 14:27:37 +0200 Subject: [PATCH 10/11] With numpy 2, can we allow Python 3.13 tests? --- .github/workflows/test.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c6428d8..92b1ad8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', ] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', ] steps: - uses: actions/checkout@v3 diff --git a/pyproject.toml b/pyproject.toml index e69c913b..5e2e3a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", From 2e42c8fcdce0d3827fa7aba15abb65b7b4b06700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Tcho=C5=84?= Date: Thu, 18 Sep 2025 15:20:44 +0200 Subject: [PATCH 11/11] LabelFrame -> super, Make # noqa more specific to appease ruff & PyCharm --- src/instamatic/calibrate/calibrate_stage_rotation.py | 2 +- src/instamatic/gui/fast_adt_frame.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/instamatic/calibrate/calibrate_stage_rotation.py b/src/instamatic/calibrate/calibrate_stage_rotation.py index 666b2ccb..75e53d4f 100644 --- a/src/instamatic/calibrate/calibrate_stage_rotation.py +++ b/src/instamatic/calibrate/calibrate_stage_rotation.py @@ -163,7 +163,7 @@ def to_file(self, outdir: Optional[str] = None) -> None: outdir = calibration_drc yaml_path = Path(outdir) / CALIB_STAGE_ROTATION with open(yaml_path, 'w') as yaml_file: - yaml.safe_dump(asdict(self), yaml_file) # noqa: correct type + yaml.safe_dump(asdict(self), yaml_file) # type: ignore[arg-type] log(f'{self} saved to {yaml_path}.') def plot(self, sst: Optional[list[SpanSpeedTime]] = None) -> None: diff --git a/src/instamatic/gui/fast_adt_frame.py b/src/instamatic/gui/fast_adt_frame.py index e004d228..3552d97c 100644 --- a/src/instamatic/gui/fast_adt_frame.py +++ b/src/instamatic/gui/fast_adt_frame.py @@ -70,8 +70,8 @@ def as_dict(self): class ExperimentalFastADT(LabelFrame): """GUI panel to perform selected FastADT-style (c)RED & PED experiments.""" - def __init__(self, parent): # noqa: parent.__init__ is called - LabelFrame.__init__(self, parent, text='Experiment with a priori tracking options') + def __init__(self, parent): + super().__init__(parent, text='Experiment with a priori tracking options') self.parent = parent self.var = ExperimentalFastADTVariables() self.q: Optional[Queue] = None