Skip to content
Draft

3.1 #13

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e767ae8
Merge pull request #4 from azazellochg/devel
azazellochg Feb 19, 2025
0936bb8
Merge pull request #5 from azazellochg/devel
azazellochg Feb 21, 2025
0698c96
Merge pull request #6 from azazellochg/devel
azazellochg Mar 12, 2025
c935c8a
add old plugin code
azazellochg Mar 18, 2025
a855816
first prototype working
azazellochg Mar 19, 2025
d8d590d
add more typehints
azazellochg Mar 19, 2025
9f79bd6
add cameraname method
azazellochg Mar 19, 2025
3b33538
reogranize plugin code
azazellochg Mar 19, 2025
93c6f27
Merge branch 'devel' into socket
azazellochg Mar 20, 2025
7d2caf6
Merge pull request #8 from azazellochg/devel
azazellochg Mar 20, 2025
2ace4e3
Merge remote-tracking branch 'origin/devel' into socket
azazellochg Mar 21, 2025
3b5442b
Merge branch 'devel', remote-tracking branch 'origin' into socket
azazellochg Mar 21, 2025
002d6aa
Merge remote-tracking branch 'origin/devel' into socket
azazellochg Mar 24, 2025
31f24ca
working on stem multichannel acq
azazellochg Mar 24, 2025
0780532
Merge remote-tracking branch 'origin' into socket
azazellochg Mar 26, 2025
4be1a86
expand piezo methods
azazellochg Mar 26, 2025
74978e0
update changelog
azazellochg Mar 26, 2025
1fef283
fix merge
azazellochg Mar 28, 2025
a192ea8
Merge remote-tracking branch 'origin' into socket
azazellochg Mar 31, 2025
090ff4b
merge devel
azazellochg Apr 3, 2025
25f06dd
minor changes
azazellochg Apr 4, 2025
b33fe6c
check avail axes for piezo
azazellochg Apr 4, 2025
69171a6
fixes for multi-detector STEM acq
azazellochg Apr 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .github/workflows/publish_and_tag.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Workflow to send master to pypi and tag the branch
name: master to pypi with comments and tag

# Workflow to send master to pypi
name: master to pypi
# Triggers the workflow on push to the master branch
on:
push:
branches: [ master ]

env:
FOLDER_WITH_VERSION: pytemscript
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
deploy:
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

Version 3.1
^^^^^^^^^^^

* Simultaneous STEM acquisition for multiple detectors
* Use CalGetter to get magnifications and camera lengths
* Add more methods for piezo stage (untested)
* SerialEMCCD plugin for advanced Gatan cameras

Version 3.0
^^^^^^^^^^^

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
author = 'Tore Niermann, Grigory Sharov'

# The full version, including alpha/beta/rc tags
release = '3.0'
release = '3.1'

# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml.future
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ authors = [
description = "TEM Scripting adapter for FEI/TFS microscopes"
readme = {file = "README.rst", content-type = "text/x-rst"}
requires-python = ">=3.8"
keywords = ["TEM python"]
keywords = ["TEM", "python"]
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Science/Research',
Expand Down
171 changes: 124 additions & 47 deletions pytemscript/modules/acquisition.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, Dict
from typing import Optional, Dict, List, Union
import time
import logging
from datetime import datetime
Expand Down Expand Up @@ -109,24 +109,38 @@ def show_cameras_cca(self, tem_cameras: Dict) -> Dict:

return tem_cameras

def acquire(self, cameraName: str, **kwargs) -> Image:
def acquire(self,
cameraName: Union[str, List],
**kwargs) -> Union[Image, List[Image]]:
""" Perform actual acquisition. Camera settings should be set beforehand.

:param str cameraName: Camera name
:returns: Image object
:param cameraName: Camera name(s)
:type cameraName: Union[str, List]
:returns: Image object or a list of Image objects
"""
if isinstance(cameraName, str):
cameraName = [cameraName]

acq = self.com_object
acq.RemoveAllAcqDevices()
acq.AddAcqDeviceByName(cameraName)
for c in cameraName:
acq.AddAcqDeviceByName(c)
t0 = time.time()
imgs = acq.AcquireImages()
t1 = time.time()
image = convert_image(imgs[0], name=cameraName, **kwargs)

result = []
for idx, c in enumerate(cameraName):
image = convert_image(imgs[idx], name=c, **kwargs)
result.append(image)
t2 = time.time()
logging.debug("\tAcquisition took %f s" % (t1 - t0))
logging.debug("\tConverting image took %f s" %(t2 - t1))
logging.debug("\tConverting image(s) took %f s" %(t2 - t1))

return image
if len(result) == 1:
return result[0]
else:
return result

def acquire_advanced(self,
cameraName: str,
Expand Down Expand Up @@ -326,23 +340,34 @@ def set_tem_presets_advanced(self,
total, output)

def set_stem_presets(self,
cameraName: str,
cameraName: Union[str, List],
size: AcqImageSize = AcqImageSize.FULL,
dwell_time: float = 1e-5,
binning: int = 1,
**kwargs) -> None:
if isinstance(cameraName, str):
cameraName = [cameraName]

brightness = kwargs.get('brightness')
if isinstance(brightness, str):
brightness = [brightness]

contrast = kwargs.get('contrast')
if isinstance(contrast, str):
contrast = [contrast]

for idx, cam in enumerate(cameraName):
for obj in self.com_object:
if obj.Info.Name == cam:
self.current_camera = obj
if brightness is not None:
self.current_camera.Info.Brightness = brightness[idx]
if contrast is not None:
self.current_camera.Info.Contrast = contrast[idx]
break

for stem in self.com_object:
if stem.Info.Name == cameraName:
self.current_camera = stem
break
if self.current_camera is None:
raise KeyError("No STEM detector with name %s" % cameraName)

if 'brightness' in kwargs:
self.current_camera.Info.Brightness = kwargs['brightness']
if 'contrast' in kwargs:
self.current_camera.Info.Contrast = kwargs['contrast']
if self.current_camera is None:
raise KeyError("No STEM detector with name %s" % cam)

settings = self.com_object.AcqParams # StemAcqParams
settings.ImageSize = size
Expand Down Expand Up @@ -382,18 +407,24 @@ def __has_film(self) -> bool:
return self.__client.call(method="has", body=body)

@staticmethod
def __find_camera(cameraName: str,
cameras_dict: Dict,
def __find_camera(cameraName: Union[str, List],
all_cameras: Dict,
binning: int) -> Dict:
""" Check camera name and supported binning. """
camera_dict = cameras_dict.get(cameraName)
""" Check camera name(s) and supported binning. """
if isinstance(cameraName, str):
cameraName = [cameraName]

if camera_dict is None:
raise KeyError("No camera with name %s. If using standard scripting the "
"camera must be selected in the microscope user interface" % cameraName)
camera_dict = None
for c in cameraName:
camera_dict = all_cameras.get(c)

if camera_dict is None:
raise KeyError("No camera with name %s. If using standard scripting the "
"camera must be selected in the microscope user interface" % c)

if binning not in camera_dict["binnings"]:
raise ValueError("Unsupported binning value: %d" % binning)
if binning not in camera_dict["binnings"]:
raise ValueError("Unsupported binning value: %d for camera %s" % (
binning, c))

return camera_dict

Expand Down Expand Up @@ -453,7 +484,7 @@ def __acquire_with_tecnaiccd(self,
return image

def acquire_tem_image(self,
cameraName: str,
camera: str,
size: AcqImageSize = AcqImageSize.FULL,
exp_time: float = 1.0,
binning: int = 1,
Expand Down Expand Up @@ -495,10 +526,10 @@ def acquire_tem_image(self,
>>> print(img.width)
4096
"""
camera_dict = self.__find_camera(cameraName, self.cameras, binning)
camera_dict = self.__find_camera(camera, self.cameras, binning)

if kwargs.get("use_tecnaiccd", False):
return self.__acquire_with_tecnaiccd(cameraName, size, exp_time,
return self.__acquire_with_tecnaiccd(camera, size, exp_time,
binning, camera_dict["width"],
**kwargs)

Expand All @@ -511,7 +542,7 @@ def acquire_tem_image(self,
body = RequestBody(attr="tem.Acquisition.Cameras",
obj_cls=AcquisitionObj,
obj_method="set_tem_presets",
cameraName=cameraName,
cameraName=camera,
size=size,
exp_time=exp_time,
binning=binning,
Expand All @@ -522,16 +553,16 @@ def acquire_tem_image(self,
body = RequestBody(attr="tem.Acquisition",
obj_cls=AcquisitionObj,
obj_method="acquire",
cameraName=cameraName,
cameraName=camera,
**kwargs)
image = self.__client.call(method="exec_special", body=body)
logging.info("TEM image acquired on %s", cameraName)
logging.info("TEM image acquired on %s", camera)

if prev_shutter_mode is not None:
body = RequestBody(attr="tem.Acquisition.Cameras",
obj_cls=AcquisitionObj,
obj_method="restore_shutter",
cameraName=cameraName,
cameraName=camera,
prev_shutter_mode=prev_shutter_mode)
self.__client.call(method="exec_special", body=body)

Expand All @@ -541,7 +572,7 @@ def acquire_tem_image(self,
body = RequestBody(attr=self.__id_adv,
obj_cls=AcquisitionObj,
obj_method="set_tem_presets_advanced",
cameraName=cameraName,
cameraName=camera,
size=size,
exp_time=exp_time,
binning=binning,
Expand All @@ -553,31 +584,31 @@ def acquire_tem_image(self,
body = RequestBody(attr=self.__id_adv,
obj_cls=AcquisitionObj,
obj_method="acquire_advanced",
cameraName=cameraName,
cameraName=camera,
recording=kwargs["recording"],
**kwargs)
self.__client.call(method="exec_special", body=body)
logging.info("TEM image acquired on %s", cameraName)
logging.info("TEM image acquired on %s", camera)
return None
else:
body = RequestBody(attr=self.__id_adv,
validator=Image,
obj_cls=AcquisitionObj,
obj_method="acquire_advanced",
cameraName=cameraName,
cameraName=camera,
**kwargs)
image = self.__client.call(method="exec_special", body=body)
return image

def acquire_stem_image(self,
cameraName: str,
detector: str,
size: AcqImageSize = AcqImageSize.FULL,
dwell_time: float = 1e-5,
binning: int = 1,
**kwargs) -> Image:
""" Acquire a STEM image.
""" Acquire a single STEM image.

:param str cameraName: Camera name
:param str detector: Detector name
:param AcqImageSize size: Image size
:param float dwell_time: Dwell time in seconds. The frame time equals the dwell time times the number of pixels plus some overhead (typically 20%, used for the line flyback)
:param int binning: Binning factor. Technically speaking these are "pixel skipping" values, since in STEM we do not combine pixels as a CCD does.
Expand All @@ -586,12 +617,12 @@ def acquire_stem_image(self,
:returns: Image object
:rtype: Image
"""
_ = self.__find_camera(cameraName, self.stem_detectors, binning)
_ = self.__find_camera(detector, self.stem_detectors, binning)

body = RequestBody(attr="tem.Acquisition.Detectors",
obj_cls=AcquisitionObj,
obj_method="set_stem_presets",
cameraName=cameraName,
cameraName=detector,
size=size,
dwell_time=dwell_time,
binning=binning,
Expand All @@ -603,13 +634,59 @@ def acquire_stem_image(self,
validator=Image,
obj_cls=AcquisitionObj,
obj_method="acquire",
cameraName=cameraName,
cameraName=detector,
**kwargs)
image = self.__client.call(method="exec_special", body=body)
logging.info("STEM image acquired on %s", cameraName)
logging.info("STEM image acquired on %s", detector)

return image

def acquire_stem_images(self,
detectors: List[str],
size: AcqImageSize = AcqImageSize.FULL,
dwell_time: float = 1e-5,
binning: int = 1,
**kwargs) -> List[Image]:
""" Simultaneous acquisition of multiple STEM images.

:param List[str] detectors: List of STEM detector names
:param AcqImageSize size: Image size
:param float dwell_time: Dwell time in seconds. The frame time equals the dwell time times the number of pixels plus some overhead (typically 20%, used for the line flyback)
:param int binning: Binning factor. Technically speaking these are "pixel skipping" values, since in STEM we do not combine pixels as a CCD does.
:keyword List[float] brightness: list of Brightness settings for each detector
:keyword List[float] contrast: list of Contrast settings for each detector
:returns: list of Image objects
:rtype: List[Image]
"""
if "brightness" in kwargs and len(kwargs["brightness"]) != len(detectors):
raise ValueError("Number of detectors does not match brightness list")
if "contrast" in kwargs and len(kwargs["contrast"]) != len(detectors):
raise ValueError("Number of detectors does not match contrast list")

_ = self.__find_camera(detectors, self.stem_detectors, binning)

body = RequestBody(attr="tem.Acquisition.Detectors",
obj_cls=AcquisitionObj,
obj_method="set_stem_presets",
cameraName=detectors,
size=size,
dwell_time=dwell_time,
binning=binning,
**kwargs)
self.__client.call(method="exec_special", body=body)

self.__check_prerequisites()
body = RequestBody(attr="tem.Acquisition",
validator=List,
obj_cls=AcquisitionObj,
obj_method="acquire",
cameraName=detectors,
**kwargs)
images = self.__client.call(method="exec_special", body=body)
logging.info("STEM images acquired on %s", detectors)

return images

def acquire_film(self,
film_text: str,
exp_time: float) -> None:
Expand Down
Loading