Skip to content
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ 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" },
{ name="Steven Zeltmann", email="steven.zeltmann@berkeley.edu" },
]
description = "A 4D-STEM data browser built on py4DSTEM."
readme = "README.md"
Expand Down
1 change: 0 additions & 1 deletion src/py4D_browser/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
46 changes: 7 additions & 39 deletions src/py4D_browser/menu_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -68,6 +62,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][()]
Expand Down Expand Up @@ -101,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)

Expand All @@ -113,44 +112,13 @@ 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)

self.setWindowTitle(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(
Expand Down
79 changes: 63 additions & 16 deletions src/py4D_browser/update_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -21,6 +22,7 @@
CircleGeometry,
AnnulusGeometry,
PointGeometry,
strtobool,
)

from typing import TYPE_CHECKING
Expand Down Expand Up @@ -248,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)
Expand Down Expand Up @@ -309,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()


Expand Down Expand Up @@ -404,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()


Expand Down Expand Up @@ -487,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:
Expand All @@ -509,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:
Expand All @@ -527,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(
Expand Down Expand Up @@ -609,11 +648,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

Expand Down Expand Up @@ -669,11 +708,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

Expand Down Expand Up @@ -880,11 +919,19 @@ 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.itemsize == 4
):
display_text = f"[{x},{y}]: {data.view(np.uint32)[x,y]:#08X}"
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:
Expand Down
24 changes: 21 additions & 3 deletions src/py4D_browser/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,16 +243,34 @@ 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
'val' is anything else.
"""
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,))


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
Loading
Loading