Skip to content

Commit

Permalink
Merge pull request #7 from azazellochg/devel
Browse files Browse the repository at this point in the history
0.9.5
  • Loading branch information
azazellochg authored Oct 25, 2024
2 parents 835fab0 + ee8a4cb commit 4bc221a
Show file tree
Hide file tree
Showing 18 changed files with 344 additions and 142 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish_and_tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
python -m pip install --upgrade pip
pip install setuptools twine
pip install numpy scipy matplotlib mrcfile
- name: Build and publish
- name: Publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
Expand Down
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
0.9.5:
- afis # of checks reduced to 4
- added frame alignment with SEM plugin, enabled for Young fringes and gold diffraction tests
- configs updated
- reset img shifts at the start
- skip auto-stigmate very close to focus, because SEM will increase the defocus
- add wait for drift for eucentricity and tilt axis tests
- add simple GUI as an option
0.9.4:
- add atlas realignment test
- update Delay command
Expand Down
15 changes: 12 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,22 @@ Offline installation
Available scripts
-----------------

- AFIS
- AFIS validation
- Specification (Krios): coma < 750 nm, astigmatism < 10 nm for 5 um shift. Glacios: coma < 1200 nm, astigmatism < 15 nm for 6 um shift
- Description: Measure residual beam tilt and astigmatism at different image shift positions while EPU is open.
- Atlas realignment
- Specification: none
- Description: Compare the shift and rotation between two atlases acquired when reloading the same grid.
- Magnification anisotropy
- Specification: <1%
- Description: Acquire a defocus series and plot astigmatism versus defocus. Calculate anisotropy by estimating deviation from linear behaviour.
- C2 Fresnel fringes
- Specification: on FFI system there should be <5 fringes at 96 kx in nanoprobe close to focus
- Description: Take a picture of a flood beam to see if the fringes from C2 aperture extend all the way to the center (non-FFI systems).
- Eucentricity
- Eucentricity check
- Specification: <2 um in X/Y, <4 um defocus (Krios G2, G3, G3i)
- Description: Estimate X,Y and defocus offset while tilting the stage.
- Gain reference
- Gain reference check
- Specification: none
- Description: Take a picture of a flood beam and check the auto-correlation image.
- Gold diffraction
Expand Down Expand Up @@ -115,3 +116,11 @@ First, have a look at **config.py**: edit *microscopes* dictionary and individua
.. code-block:: python
perfectem
If you prefer clicking buttons over console, you can create a desktop script **PerfectEM.bat** that contains one line:

.. code-block::
perfectem-gui
PS. The simple GUI requires Python built with tkinter support.
56 changes: 33 additions & 23 deletions perfectem/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!python
# -*- coding: utf-8 -*-
# **************************************************************************
# *
# * Authors: Grigory Sharov ([email protected]) [1]
Expand Down Expand Up @@ -30,38 +32,39 @@

from .config import microscopes

__version__ = '0.9.4'
__version__ = '0.9.5'

tests = (
("AFIS", "AFIS validation"),
("Anisotropy", "Magnification anisotropy"),
("C2Fringes", "C2 Fresnel fringes"),
("Eucentricity", "Eucentricity check"),
("GainRef", "Gain reference check"),
("GoldDiffr", "Gold diffraction"),
("InfoLimit", "Information limit (Young fringes)"),
("PointRes", "Point resolution"),
("StageDrift", "Stage drift"),
("ThonRings", "Thon rings"),
("TiltAxis", "Tilt axis offset"),
)
tests = {
"AFIS": "AFIS validation",
"AtlasRealignment": "Atlas realignment",
"Anisotropy": "Magnification anisotropy",
"C2Fringes": "C2 Fresnel fringes",
"Eucentricity": "Eucentricity check",
"GainRef": "Gain reference check",
"GoldDiffr": "Gold diffraction",
"InfoLimit": "Information limit",
"PointRes": "Point resolution",
"StageDrift": "Stage drift",
"ThonRings": "Thon rings",
"TiltAxis": "Tilt axis offset"
}


def show_all_tests(print_docstr: bool = False) -> None:
""" Print list of tests, with an optional docstring for each. """
for num, test in enumerate(tests, start=1):
print(f"\t[{num}] {test[1]}")
for i, item in enumerate(tests.items()):
print(f"\t[{i+1}] {item[1]}")
if print_docstr:
module = importlib.import_module("perfectem.scripts")
func = getattr(module, test[0])
func = getattr(module, item[0])
print(func.__doc__)
print("="*80)


def main(argv: Optional[List] = None) -> None:
try:
import serialem
except ImportError:
except ModuleNotFoundError:
raise ImportError("This program must be run on the computer with SerialEM Python module")

parser = argparse.ArgumentParser(description="This script launches selected TEM performance test")
Expand All @@ -79,16 +82,23 @@ def main(argv: Optional[List] = None) -> None:
raise IndexError("Wrong test number!")

print("\nAvailable microscopes:")
for scope_num, scope in enumerate(microscopes, start=1):
print(f"\t[{scope_num}] {scope[0]}")
for scope_num, scope in enumerate(microscopes.keys(), start=1):
print(f"\t[{scope_num}] {scope}")

scope_num = int(input("\nInput the microscope number: ").strip()) - 1
if scope_num not in range(len(microscopes)):
raise IndexError("Wrong microscope number!")
scope_name = microscopes[scope_num][0]
scope_name = list(microscopes.keys())[scope_num]

func_name = tests[test_num][0]
func_name = list(tests.keys())[test_num]
module = importlib.import_module("perfectem.scripts")
func_object = getattr(module, func_name)
print(func_object.__doc__)
func_object(scope_name=scope_name, **microscopes[scope_num][1][func_name]).run()
func_args = list(microscopes.values())[scope_num][func_name]
func_object(scope_name=scope_name, **func_args).run()


def main_gui() -> None:
from .gui import Application
gui = Application(tests, microscopes)
gui.create_widgets()
94 changes: 50 additions & 44 deletions perfectem/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
class BaseSetup:
""" Initialization and common functions. """

def __init__(self, log_fn: str, scope_name: str, **kwargs: Any) -> None:
def __init__(self, log_fn: str, scope_name: str,
camera_num: Optional[int] = None, **kwargs: Any) -> None:
""" Setup logger, camera settings and common SerialEM params. """

sem.ConnectToSEM(SERIALEM_PORT, SERIALEM_IP)
Expand All @@ -48,7 +49,7 @@ def __init__(self, log_fn: str, scope_name: str, **kwargs: Any) -> None:
self.SCOPE_HAS_C3 = self.func_is_implemented("ReportIlluminatedArea")
self.SCOPE_HAS_AUTOFILL = self.func_is_implemented("AreDewarsFilling")
self.SCOPE_HAS_APER_CTRL = self.func_is_implemented("ReportApertureSize", 1)
self.CAMERA_NUM = 1
self.CAMERA_NUM = camera_num or 1
self.CAMERA_HAS_DIVIDEBY2 = False
self.CAMERA_MODE = 0 # linear
self.DELAY = 3
Expand All @@ -60,7 +61,7 @@ def __init__(self, log_fn: str, scope_name: str, **kwargs: Any) -> None:
f"hasAutofill={self.SCOPE_HAS_AUTOFILL}, "
f"hasApertureControl={self.SCOPE_HAS_APER_CTRL}")

self.select_camera()
self.select_camera(camera_num)

sem.ClearPersistentVars()
sem.SetUserSetting("DriftProtection", 1, 1)
Expand Down Expand Up @@ -101,40 +102,40 @@ def setup_log(self, log_fn: str) -> None:
sem.ErrorsToLog()
sem.SetDirectory(os.getcwd())

def select_camera(self) -> None:
def select_camera(self, camera_num: Optional[int] = None) -> None:
""" Choose camera to use. """

camera_num = 1
camera_names = []
while True:
name = sem.ReportCameraName(camera_num)
if name == "NOCAM":
break
else:
camera_names.append(name)
camera_num += 1

print("Choose camera to use with SerialEM:\n")
for i, c in enumerate(camera_names):
print(f"\t[{i + 1}] {c}")

camera_num = int(input("\nInput the camera number: ").strip())
if camera_num > len(camera_names) or camera_num < 1:
raise IndexError("Wrong camera number!")
else:
sem.SelectCamera(camera_num)
self.CAMERA_NUM = camera_num
cam_name = sem.ReportCameraName(self.CAMERA_NUM)
if "Falcon" in cam_name:
self.CAMERA_MODE = 0
self.CAMERA_HAS_DIVIDEBY2 = False
elif "K2" or "K3" in cam_name:
self.CAMERA_MODE = 1 # always counting
self.CAMERA_HAS_DIVIDEBY2 = True

_, _, mode = sem.ReportMag()
if mode == 1: # EFTEM
sem.SetSlitIn(0) # retract slit
if camera_num is None: # enter from cmd, not GUI
camera_num = 1
camera_names = []
while True:
name = sem.ReportCameraName(camera_num)
if name == "NOCAM":
break
else:
camera_names.append(name)
camera_num += 1

print("Choose camera to use with SerialEM:\n")
for i, c in enumerate(camera_names):
print(f"\t[{i + 1}] {c}")

camera_num = int(input("\nInput the camera number: ").strip())
if camera_num > len(camera_names) or camera_num < 1:
raise IndexError("Wrong camera number!")

sem.SelectCamera(camera_num)
self.CAMERA_NUM = camera_num
cam_name = sem.ReportCameraName(self.CAMERA_NUM)
if "K2" or "K3" in cam_name:
self.CAMERA_MODE = 1 # always counting
self.CAMERA_HAS_DIVIDEBY2 = True
if "Falcon 4" in cam_name:
self.CAMERA_MODE = 1 # always counting
self.CAMERA_HAS_DIVIDEBY2 = False

_, _, mode = sem.ReportMag()
if mode == 1: # EFTEM
sem.SetSlitIn(0) # retract slit

if not sem.ReportColumnOrGunValve():
print("Opening col. valves...")
Expand Down Expand Up @@ -170,6 +171,7 @@ def run(self) -> None:
logging.info(f"Starting script {test_name} {start_time.strftime('%d/%m/%Y %H:%M:%S')}")

try:
sem.SetImageShift(0, 0)
if abs(sem.ReportTiltAngle()) > 0.1:
sem.TiltTo(0)
self._run()
Expand All @@ -183,7 +185,6 @@ def run(self) -> None:

def check_eps(self) -> None:
""" Check max eps after setup beam but before area setup. """
# TODO: verify minimum dose rate for Falcon3/4

logging.info("Checking dose rate...")
old_exp, _ = sem.ReportExposure("F")
Expand All @@ -202,7 +203,7 @@ def check_eps(self) -> None:
spot += 1

if spot != int(sem.ReportSpotSize()) and spot < 12:
logging.info(f"Increasing spot size to {spot} to reduce dose rate below 120 eps")
logging.info(f"Increasing spot size to {spot} to reduce dose rate below 200 eps")
sem.SetSpotSize(spot)

# Restore previous settings
Expand Down Expand Up @@ -258,7 +259,8 @@ def setup_beam(self, mag: int, spot: int, beamsize: float,
def setup_area(self, exp: float, binning: int,
area: str = "F",
preset: str = "F",
mode: Optional[str] = None) -> None:
mode: Optional[int] = None,
frames: Optional[bool] = False) -> None:
""" Setup camera settings for a certain preset. """

logging.info(f"Setting camera: preset={preset}, exp={exp}, binning={binning}, area={area}")
Expand All @@ -271,15 +273,19 @@ def setup_area(self, exp: float, binning: int,
sem.NoMessageBoxOnError()
try:
sem.SetK2ReadMode(preset, camera_mode) # linear=0, counting=1
sem.SetDoseFracParams(preset, 0, 0, 0) # no frames
if frames:
sem.SetFrameTime(preset, 0.000001) # will be fixed by SEM to a min number
sem.SetDoseFracParams(preset, 1, 0, 1, 1, 0) # align frames with SEM plugin
else:
sem.SetDoseFracParams(preset, 0, 0, 0, 0, 0) # no frames
except sem.SEMerror or sem.SEMmoduleError:
pass
sem.NoMessageBoxOnError(0)

logging.info("Setting camera: done!")

@staticmethod
def euc_by_stage(fine: bool = False) -> None:
def euc_by_stage(fine: bool = True) -> None:
""" Check FOV before running eucentricity by stage. """
min_fov = sem.ReportProperty("EucentricityCoarseMinField") # um
pix = sem.ReportCurrentPixelSize("T") # nm, with binning
Expand All @@ -290,9 +296,9 @@ def euc_by_stage(fine: bool = False) -> None:
f"{area} um < EucentricityCoarseMinField={min_fov}, "
"SerialEM will decrease the magnification automatically")
sem.SetAbsoluteFocus(0)
sem.ChangeFocus(-50)
sem.ChangeFocus(-30)
sem.Eucentricity(2 if fine else 1)
sem.ChangeFocus(50)
sem.ChangeFocus(30)

def euc_by_beamtilt(self) -> None:
""" Adapted from https://sphinx-emdocs.readthedocs.io/en/latest/serialEM-note-more-about-z-height.html#z-byv2-function """
Expand Down Expand Up @@ -351,7 +357,7 @@ def autofocus(target: float, precision: float = 0.05,
logging.info(f"Autofocusing to {target} um...")
sem.AutoFocus()

if high_mag:
if high_mag and target < -0.7:
# fix astigmatism again, closer to focus
sem.FixAstigmatismByCTF()

Expand Down
Loading

0 comments on commit 4bc221a

Please sign in to comment.