From 611506a2f14ae5fe181afcc0e8df6981fe2d250f Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Mon, 11 May 2026 15:08:33 -0400 Subject: [PATCH 01/10] empad-specific tooltip --- src/py4D_browser/update_views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/py4D_browser/update_views.py b/src/py4D_browser/update_views.py index 4bbbec2..3a9af20 100644 --- a/src/py4D_browser/update_views.py +++ b/src/py4D_browser/update_views.py @@ -884,7 +884,13 @@ def update_tooltip(self: "DataViewer"): np.uint32, np.float32, ): - display_text = f"[{x},{y}]: {data.view(np.uint32)[x,y]:#08X}" + # display_text = f"[{x},{y}]: {data.view(np.uint32)[x,y]:#010X}" + val = data.view(np.uint32)[x,y] + analog = val & 0x3FFF + digital = (val & 0x3FFFC000) >> 14 + gain = (val & 0x80000000) >> 31 + reserved = (val & 0x40000000) >> 30 + display_text = f"[{x},{y}]: {data.view(np.uint32)[x,y]:#041_b} (R{reserved} G{gain} D{digital} A{analog})" else: display_text = f"[{x},{y}]: {data[x,y]:.5g}" else: From df805b67b6a3969bd218aa8b5515946b1efe271c Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Fri, 5 Jun 2026 14:50:02 -0400 Subject: [PATCH 02/10] concatenated tooltip is a feature flag --- src/py4D_browser/update_views.py | 17 ++++++++++++----- src/py4D_browser/utils.py | 6 +++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/py4D_browser/update_views.py b/src/py4D_browser/update_views.py index 3a9af20..d37b5e4 100644 --- a/src/py4D_browser/update_views.py +++ b/src/py4D_browser/update_views.py @@ -21,6 +21,7 @@ CircleGeometry, AnnulusGeometry, PointGeometry, + strtobool, ) from typing import TYPE_CHECKING @@ -880,12 +881,18 @@ def update_tooltip(self: "DataViewer"): x = int(np.clip(np.floor(pos_in_data.y()), 0, data.shape[0] - 1)) if np.isrealobj(data): - if QtCore.Qt.ControlModifier == modifier_keys and data.dtype in ( - np.uint32, - np.float32, + if ( + QtCore.Qt.ControlModifier == modifier_keys + and strtobool( + self.settings.value("gui/concatenation_tooltip", "0") + ) + and data.dtype + in ( + np.uint32, + np.float32, + ) ): - # display_text = f"[{x},{y}]: {data.view(np.uint32)[x,y]:#010X}" - val = data.view(np.uint32)[x,y] + val = data.view(np.uint32)[x, y] analog = val & 0x3FFF digital = (val & 0x3FFFC000) >> 14 gain = (val & 0x80000000) >> 31 diff --git a/src/py4D_browser/utils.py b/src/py4D_browser/utils.py index 3993310..4925b97 100644 --- a/src/py4D_browser/utils.py +++ b/src/py4D_browser/utils.py @@ -243,7 +243,7 @@ def complex_to_Lab( return rgb -def strtobool(val): +def strtobool(val: str) -> bool: """Convert a string representation of truth to true (1) or false (0). True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if @@ -251,8 +251,8 @@ def strtobool(val): """ val = val.lower() if val in ("y", "yes", "t", "true", "on", "1"): - return 1 + return True elif val in ("n", "no", "f", "false", "off", "0"): - return 0 + return False else: raise ValueError("invalid truth value %r" % (val,)) From 69a63a7118e5f7eb77e52362c203271eff890fd0 Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Fri, 5 Jun 2026 15:01:05 -0400 Subject: [PATCH 03/10] read emdfile calibration by brute force --- src/py4D_browser/menu_actions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/py4D_browser/menu_actions.py b/src/py4D_browser/menu_actions.py index 2745bfa..3334df0 100644 --- a/src/py4D_browser/menu_actions.py +++ b/src/py4D_browser/menu_actions.py @@ -68,6 +68,13 @@ def load_file(self: "DataViewer", filepath, mmap=False, binning=1): if len(parent) > 1 and "emd_group_type" in file[parent].attrs: print("This appears to be an emdfile... reading natively") self.datacube = py4DSTEM.DataCube.from_h5(datacubes[0].file[parent]) + try: + calibration = py4DSTEM.Calibration.from_h5( + datacubes[0].file["/datacube_root/metadatabundle/calibration"] + ) + self.datacube.calibration = calibration + except Exception as e: + self.statusBar().showMessage(str(e)) else: self.datacube = py4DSTEM.DataCube( datacubes[0] if mmap else datacubes[0][()] From b66b9146b1f56c3bbd71e947c0aa831ac8201a94 Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Fri, 5 Jun 2026 15:04:30 -0400 Subject: [PATCH 04/10] better way to check special tooltip for 32-bit dtype --- src/py4D_browser/update_views.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/py4D_browser/update_views.py b/src/py4D_browser/update_views.py index d37b5e4..141fcc2 100644 --- a/src/py4D_browser/update_views.py +++ b/src/py4D_browser/update_views.py @@ -886,11 +886,7 @@ def update_tooltip(self: "DataViewer"): and strtobool( self.settings.value("gui/concatenation_tooltip", "0") ) - and data.dtype - in ( - np.uint32, - np.float32, - ) + and data.itemsize == 4 ): val = data.view(np.uint32)[x, y] analog = val & 0x3FFF From fcd9b6da01c8af47ee1cd820217aa155c0cce5a1 Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Fri, 5 Jun 2026 15:05:48 -0400 Subject: [PATCH 05/10] bump patch version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 365ce82..6b1a5fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "py4D_browser" -version = "1.5.0" +version = "1.5.1" authors = [ { name="Steven Zeltmann", email="steven.zeltmann@lbl.gov" }, ] From 545a0a1dd4795c6352309b8b29050b1ff0271a5c Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Fri, 12 Jun 2026 16:23:34 -0400 Subject: [PATCH 06/10] position detector based on image sizes (so it works when there's an image but no datacube) --- src/py4D_browser/update_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/py4D_browser/update_views.py b/src/py4D_browser/update_views.py index 141fcc2..427191e 100644 --- a/src/py4D_browser/update_views.py +++ b/src/py4D_browser/update_views.py @@ -610,11 +610,11 @@ def update_realspace_detector(self: "DataViewer"): hover_pen = {"color": "c", "width": 6} hover_handle = {"color": "c", "width": 9} - if self.datacube is None: + if self.unscaled_realspace_image is None: x0, y0 = 0, 0 xr, yr = 4, 4 else: - x, y = self.datacube.data.shape[2:] + x, y = self.unscaled_realspace_image.shape[:2] y0, x0 = x // 2, y // 2 xr, yr = (np.minimum(x, y) / 10,) * 2 @@ -670,11 +670,11 @@ def update_diffraction_detector(self: "DataViewer"): hover_pen = {"color": "c", "width": 6} hover_handle = {"color": "c", "width": 9} - if self.datacube is None: + if self.unscaled_diffraction_image is None: x0, y0 = 0, 0 xr, yr = 4, 4 else: - x, y = self.datacube.data.shape[2:] + x, y = self.unscaled_diffraction_image.shape[:2] y0, x0 = x // 2, y // 2 xr, yr = (np.minimum(x, y) / 10,) * 2 From 016bd21bd17a153dfb5ddab607a026157c86e28e Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Tue, 21 Jul 2026 16:22:06 -0400 Subject: [PATCH 07/10] pre-populate calibration windows and fix input bug on selection radius. store voltage in V but display in kV --- .../calibration_plugin/calibration_plugin.py | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py b/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py index a34e7a1..62325ed 100644 --- a/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py +++ b/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py @@ -125,6 +125,7 @@ def __init__(self, datacube, parent, diffraction_selector_size=None): diff_left_layout.addWidget(QLabel("Selection Radius"), 2, 0, Qt.AlignRight) self.diff_selection_box = QLineEdit() + self.diff_selection_box.setValidator(QDoubleValidator()) diff_left_layout.addWidget(self.diff_selection_box, 2, 1) self.diff_selection_box.setEnabled(self.diffraction_selector_size is not None) @@ -163,6 +164,39 @@ def __init__(self, datacube, parent, diffraction_selector_size=None): button_layout.addWidget(done_button) layout.addLayout(button_layout) + ######### POPULATE ######## + + # Real Space + r_pix = self.datacube.calibration.get_R_pixel_size() + r_unit = self.datacube.calibration.get_R_pixel_units() + + self.realspace_pix_box.setText(f"{r_pix:g}") + self.realspace_fov_box.setText(f"{r_pix * self.datacube.R_Ny:g}") + + r_unit_map = {"A": "Å", "nm": "nm"} + self.realspace_unit_box.setCurrentText(r_unit_map.get(r_unit, "Å")) + + # Diffraction + q_pix = self.datacube.calibration.get_Q_pixel_size() + q_unit = self.datacube.calibration.get_Q_pixel_units() + + self.diff_pix_box.setText(f"{q_pix:g}") + self.diff_fov_box.setText(f"{q_pix * self.datacube.Q_Ny:g}") + if self.diffraction_selector_size: + self.diff_selection_box.setText( + f"{q_pix * self.diffraction_selector_size:g}" + ) + + q_unit_map = {"mrad": "mrad", "A^-1": "Å⁻¹", "1/nm": "nm⁻¹"} + self.diff_unit_box.setCurrentText(q_unit_map.get(q_unit, "mrad")) + + # Voltage + try: + voltage = self.datacube.calibration["voltage"] + self.kV_input.setText(f"{voltage / 1e3:g}") + except (KeyError, TypeError): + pass + ######### CALLBACKS ######## self.realspace_pix_box.textEdited.connect(self.realspace_pix_box_changed) self.realspace_fov_box.textEdited.connect(self.realspace_fov_box_changed) @@ -206,16 +240,16 @@ def diffraction_fov_box_changed(self, new_text): def diffraction_selection_box_changed(self, new_text): if self.diffraction_selector_size: - sel_size = float(new_text) + try: + sel_size = float(new_text) + except ValueError: + return pix_size = sel_size / self.diffraction_selector_size - fov = pix_size * self.datacube.Q_Nx + fov = pix_size * self.datacube.Q_Ny self.diff_pix_box.setText(f"{pix_size:g}") self.diff_fov_box.setText(f"{fov:g}") - sel_size = pix_size * self.diffraction_selector_size - self.diff_selection_box.setText(f"{sel_size:g}") - def set_and_close(self): print("Old calibration") @@ -245,7 +279,7 @@ def set_and_close(self): kV_text = self.kV_input.text() if kV_text != "": kV = float(kV_text) - self.datacube.calibration["voltage"] = kV + self.datacube.calibration["voltage"] = kV * 1e3 # note there is no canonical tag for voltage, so we are # going to make our own key for it From f84430adbe5bcc33e3a8e9bc2fe89b7aea02a291 Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Mon, 27 Jul 2026 13:50:01 -0400 Subject: [PATCH 08/10] Add pixel size and unit to the main image setters. This is not ideal, because it is better that the main images be forced to agree always with the underlying datacube, so that the detectors are always in the right place, but that will be fixed later --- src/py4D_browser/main_window.py | 1 - src/py4D_browser/menu_actions.py | 39 ----------------- src/py4D_browser/update_views.py | 42 ++++++++++++++++--- src/py4D_browser/utils.py | 18 ++++++++ .../calibration_plugin/calibration_plugin.py | 18 +++++++- .../tcBF_plugin/tcBF_plugin.py | 14 ++++++- 6 files changed, 84 insertions(+), 48 deletions(-) diff --git a/src/py4D_browser/main_window.py b/src/py4D_browser/main_window.py index adb0764..1415959 100644 --- a/src/py4D_browser/main_window.py +++ b/src/py4D_browser/main_window.py @@ -51,7 +51,6 @@ class DataViewer(QMainWindow): show_keyboard_map, reshape_data, set_datacube, - update_scalebars, copy_vimg_to_clipboard, copy_diff_to_clipboard, copy_result_to_clipboard, diff --git a/src/py4D_browser/menu_actions.py b/src/py4D_browser/menu_actions.py index 3334df0..5d7db7d 100644 --- a/src/py4D_browser/menu_actions.py +++ b/src/py4D_browser/menu_actions.py @@ -39,12 +39,6 @@ def load_data_arina(self: "DataViewer"): self.statusBar().showMessage(f"Arina data was loaded as 3D, please reshape...") self.datacube = dataset - self.diffraction_scale_bar.pixel_size = self.datacube.calibration.get_Q_pixel_size() - self.diffraction_scale_bar.units = self.datacube.calibration.get_Q_pixel_units() - - self.real_space_scale_bar.pixel_size = self.datacube.calibration.get_R_pixel_size() - self.real_space_scale_bar.units = self.datacube.calibration.get_R_pixel_units() - self.update_diffraction_space_view(reset=True) self.update_real_space_view(reset=True) @@ -108,8 +102,6 @@ def load_file(self: "DataViewer", filepath, mmap=False, binning=1): binfactor=binning, ) - self.update_scalebars() - self.update_diffraction_space_view(reset=True) self.update_real_space_view(reset=True) @@ -120,8 +112,6 @@ def load_file(self: "DataViewer", filepath, mmap=False, binning=1): def set_datacube(self: "DataViewer", datacube, window_title): self.datacube = datacube - self.update_scalebars() - self.update_diffraction_space_view(reset=True) self.update_real_space_view(reset=True) @@ -129,35 +119,6 @@ def set_datacube(self: "DataViewer", datacube, window_title): self.signal_datacube_changed.emit() -def update_scalebars(self: "DataViewer"): - - realspace_translation = { - "A": "Å", - } - reciprocal_translation = { - "A^-1": "Å⁻¹", - } - - self.diffraction_scale_bar.pixel_size = self.datacube.calibration.get_Q_pixel_size() - q_units = self.datacube.calibration.get_Q_pixel_units() - self.diffraction_scale_bar.units = ( - reciprocal_translation[q_units] - if q_units in reciprocal_translation.keys() - else q_units - ) - - self.real_space_scale_bar.pixel_size = self.datacube.calibration.get_R_pixel_size() - r_units = self.datacube.calibration.get_R_pixel_units() - self.real_space_scale_bar.units = ( - realspace_translation[r_units] - if r_units in realspace_translation.keys() - else r_units - ) - - self.diffraction_scale_bar.updateBar() - self.real_space_scale_bar.updateBar() - - def reshape_data(self: "DataViewer"): new_shape = ResizeDialog.get_new_size(self.datacube.shape[:2], parent=self) self.datacube.data = self.datacube.data.reshape( diff --git a/src/py4D_browser/update_views.py b/src/py4D_browser/update_views.py index 427191e..95cff40 100644 --- a/src/py4D_browser/update_views.py +++ b/src/py4D_browser/update_views.py @@ -7,6 +7,7 @@ from PyQt5 import QtCore from PyQt5.QtGui import QCursor import os +from py4D_browser.utils import format_unit from py4D_browser.utils import ( @@ -249,7 +250,12 @@ def update_real_space_view(self: "DataViewer", reset=False): # Debug mode for displaying the mask if "MASK_DEBUG" in os.environ: - self.set_diffraction_image(mask.astype(np.float32), reset=reset) + self.set_diffraction_image( + mask.astype(np.float32), + reset=reset, + pixel_size=self.datacube.calibration.get_Q_pixel_size(), + pixel_units=format_unit(self.datacube.calibration.get_Q_pixel_units()), + ) return mask = mask.astype(np.float32) @@ -310,12 +316,25 @@ def update_real_space_view(self: "DataViewer", reset=False): else: raise ValueError("Oopsie") - self.set_virtual_image(vimg, reset=reset) + self.set_virtual_image( + vimg, + reset=reset, + pixel_size=self.datacube.calibration.get_R_pixel_size(), + pixel_units=format_unit(self.datacube.calibration.get_R_pixel_units()), + ) -def set_virtual_image(self: "DataViewer", vimg, reset=False): +def set_virtual_image( + self: "DataViewer", vimg, reset=False, pixel_size=None, pixel_units=None +): self.unscaled_realspace_image = vimg self._render_virtual_image(reset=reset) + if pixel_size is not None: + self.real_space_scale_bar.pixel_size = pixel_size + if pixel_units is not None: + self.real_space_scale_bar.units = pixel_units + if pixel_size is not None or pixel_units is not None: + self.real_space_scale_bar.updateBar() self.signal_virtual_image_data_changed.emit() @@ -405,12 +424,25 @@ def update_diffraction_space_view(self: "DataViewer", reset=False): case _: raise ValueError("Unsupported detector shape...") - self.set_diffraction_image(DP, reset=reset) + self.set_diffraction_image( + DP, + reset=reset, + pixel_size=self.datacube.calibration.get_Q_pixel_size(), + pixel_units=format_unit(self.datacube.calibration.get_Q_pixel_units()), + ) -def set_diffraction_image(self: "DataViewer", DP, reset=False): +def set_diffraction_image( + self: "DataViewer", DP, reset=False, pixel_size=None, pixel_units=None +): self.unscaled_diffraction_image = DP self._render_diffraction_image(reset=reset) + if pixel_size is not None: + self.diffraction_scale_bar.pixel_size = pixel_size + if pixel_units is not None: + self.diffraction_scale_bar.units = pixel_units + if pixel_size is not None or pixel_units is not None: + self.diffraction_scale_bar.updateBar() self.signal_diffraction_data_changed.emit() diff --git a/src/py4D_browser/utils.py b/src/py4D_browser/utils.py index 4925b97..672fc13 100644 --- a/src/py4D_browser/utils.py +++ b/src/py4D_browser/utils.py @@ -256,3 +256,21 @@ def strtobool(val: str) -> bool: return False else: raise ValueError("invalid truth value %r" % (val,)) + + +def format_unit(raw_unit): + """Translate py4DSTEM ASCII unit strings to Unicode for display on scale bars. + + py4DSTEM calibration objects return ASCII unit strings like 'A' and 'A^-1'. + This function translates them to their Unicode equivalents for nicer display. + """ + if raw_unit == "A": + return "Å" + if raw_unit == "A^-1": + return "Å⁻¹" + if raw_unit == "px^-1": + return "px⁻¹" + # generic fallback: replace "^-1" with the Unicode superscript + if raw_unit.endswith("^-1"): + return raw_unit[:-3] + "⁻¹" + return raw_unit diff --git a/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py b/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py index 62325ed..1c0739e 100644 --- a/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py +++ b/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py @@ -283,7 +283,23 @@ def set_and_close(self): # note there is no canonical tag for voltage, so we are # going to make our own key for it - self.parent.update_scalebars() + from py4D_browser.utils import format_unit + + self.parent.real_space_scale_bar.pixel_size = ( + self.datacube.calibration.get_R_pixel_size() + ) + self.parent.real_space_scale_bar.units = format_unit( + self.datacube.calibration.get_R_pixel_units() + ) + self.parent.real_space_scale_bar.updateBar() + + self.parent.diffraction_scale_bar.pixel_size = ( + self.datacube.calibration.get_Q_pixel_size() + ) + self.parent.diffraction_scale_bar.units = format_unit( + self.datacube.calibration.get_Q_pixel_units() + ) + self.parent.diffraction_scale_bar.updateBar() print("New calibration") print(self.datacube.calibration) diff --git a/src/py4d_browser_plugin/tcBF_plugin/tcBF_plugin.py b/src/py4d_browser_plugin/tcBF_plugin/tcBF_plugin.py index 9a1c876..82ceecd 100644 --- a/src/py4d_browser_plugin/tcBF_plugin/tcBF_plugin.py +++ b/src/py4d_browser_plugin/tcBF_plugin/tcBF_plugin.py @@ -84,7 +84,12 @@ def launch_auto(self): plot_convergence=False, ) - parent.set_virtual_image(tcBF.recon_BF, reset=True) + parent.set_virtual_image( + tcBF.recon_BF, + reset=True, + pixel_size=parent.datacube.calibration.get_R_pixel_size(), + pixel_units=parent.datacube.calibration.get_R_pixel_units(), + ) class ManualTCBFDialog(QDialog): @@ -235,4 +240,9 @@ def reconstruct(self): if pad: reconstruction = reconstruction[pad_width:-pad_width, pad_width:-pad_width] - self.parent.set_virtual_image(reconstruction, reset=True) + self.parent.set_virtual_image( + reconstruction, + reset=True, + pixel_size=self.parent.datacube.calibration.get_R_pixel_size(), + pixel_units=self.parent.datacube.calibration.get_R_pixel_units(), + ) From fd268dac6437110d95a148fa39275ed7a93ff38b Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Thu, 30 Jul 2026 10:02:53 -0400 Subject: [PATCH 09/10] add orientation to calibration dialog --- src/py4D_browser/update_views.py | 12 +++++-- .../calibration_plugin/calibration_plugin.py | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/py4D_browser/update_views.py b/src/py4D_browser/update_views.py index 95cff40..e4a990a 100644 --- a/src/py4D_browser/update_views.py +++ b/src/py4D_browser/update_views.py @@ -520,7 +520,9 @@ def update_fft_view(self: "DataViewer", mode: Optional[str] = None): pixel_size=( 1.0 / self.datacube.calibration.get_R_pixel_size() / self.datacube.R_Ny ), - pixel_units=f"{self.datacube.calibration.get_R_pixel_units()}⁻¹", + pixel_units=format_unit( + f"{self.datacube.calibration.get_R_pixel_units()}⁻¹" + ), ) self.fft_widget.getImageItem().setRect(0, 0, fft.shape[1], fft.shape[1]) if mode_switch: @@ -542,7 +544,9 @@ def update_fft_view(self: "DataViewer", mode: Optional[str] = None): pixel_size=( 1.0 / self.datacube.calibration.get_R_pixel_size() / self.datacube.R_Ny ), - pixel_units=f"{self.datacube.calibration.get_R_pixel_units()}⁻¹", + pixel_units=format_unit( + f"{self.datacube.calibration.get_R_pixel_units()}⁻¹" + ), ) self.fft_widget.getImageItem().setRect(0, 0, fft.shape[1], fft.shape[1]) if mode_switch: @@ -560,7 +564,9 @@ def update_fft_view(self: "DataViewer", mode: Optional[str] = None): pixel_size=( 1.0 / self.datacube.calibration.get_Q_pixel_size() / self.datacube.Q_Ny ), - pixel_units=f"{self.datacube.calibration.get_Q_pixel_units()}⁻¹", + pixel_units=format_unit( + f"{self.datacube.calibration.get_Q_pixel_units()}⁻¹" + ), ) else: raise RuntimeError( diff --git a/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py b/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py index 1c0739e..1a9669e 100644 --- a/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py +++ b/src/py4d_browser_plugin/calibration_plugin/calibration_plugin.py @@ -154,6 +154,25 @@ def __init__(self, datacube, parent, diffraction_selector_size=None): self.kV_input = QLineEdit() kV_left_layout.addWidget(self.kV_input, 0, 1) + orientation_box = QGroupBox("Orientation") + layout.addWidget(orientation_box) + orientation_layout = QHBoxLayout() + orientation_box.setLayout(orientation_layout) + orientation_left_layout = QGridLayout() + orientation_layout.addLayout(orientation_left_layout) + orientation_left_layout.addWidget( + QLabel("Scan Rotation [deg]"), 0, 0, Qt.AlignRight + ) + self.rotation_input = QLineEdit() + self.rotation_input.setValidator(QDoubleValidator()) + orientation_left_layout.addWidget(self.rotation_input, 0, 1) + + orientation_left_layout.addWidget( + QLabel("Pattern Transpose"), 1, 0, Qt.AlignRight + ) + self.transpose_checkbox = QCheckBox() + orientation_left_layout.addWidget(self.transpose_checkbox, 1, 1) + button_layout = QHBoxLayout() button_layout.addStretch() cancel_button = QPushButton("Cancel") @@ -197,6 +216,12 @@ def __init__(self, datacube, parent, diffraction_selector_size=None): except (KeyError, TypeError): pass + # Orientation + rotation = self.datacube.calibration.get_QR_rotation_degrees() + if rotation is not None: + self.rotation_input.setText(f"{rotation:g}") + self.transpose_checkbox.setChecked(self.datacube.calibration.get_QR_flip()) + ######### CALLBACKS ######## self.realspace_pix_box.textEdited.connect(self.realspace_pix_box_changed) self.realspace_fov_box.textEdited.connect(self.realspace_fov_box_changed) @@ -205,6 +230,7 @@ def __init__(self, datacube, parent, diffraction_selector_size=None): self.diff_selection_box.textEdited.connect( self.diffraction_selection_box_changed ) + self.rotation_input.textEdited.connect(self.rotation_input_changed) def realspace_pix_box_changed(self, new_text): pix_size = float(new_text) @@ -250,6 +276,9 @@ def diffraction_selection_box_changed(self, new_text): self.diff_pix_box.setText(f"{pix_size:g}") self.diff_fov_box.setText(f"{fov:g}") + def rotation_input_changed(self, new_text): + pass # no dependent fields to update + def set_and_close(self): print("Old calibration") @@ -283,6 +312,13 @@ def set_and_close(self): # note there is no canonical tag for voltage, so we are # going to make our own key for it + rotation_text = self.rotation_input.text() + if rotation_text != "": + rotation = float(rotation_text) + self.datacube.calibration.set_QR_rotation(np.deg2rad(rotation)) + + self.datacube.calibration.set_QR_flip(self.transpose_checkbox.isChecked()) + from py4D_browser.utils import format_unit self.parent.real_space_scale_bar.pixel_size = ( From 8d3ea88126cf5fdac54fd6d1483306718c8a30eb Mon Sep 17 00:00:00 2001 From: Steven Zeltmann Date: Thu, 30 Jul 2026 10:19:11 -0400 Subject: [PATCH 10/10] update email --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6b1a5fb..e8c64fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "py4D_browser" version = "1.5.1" authors = [ - { name="Steven Zeltmann", email="steven.zeltmann@lbl.gov" }, + { name="Steven Zeltmann", email="steven.zeltmann@berkeley.edu" }, ] description = "A 4D-STEM data browser built on py4DSTEM." readme = "README.md"