Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e939913
fix installation doc for 3.4: use pinned versions
azazellochg Mar 12, 2025
25539e1
EF and source are working
azazellochg Mar 13, 2025
7fcad90
Gun1 is a rare thing; Return nanoAmps
azazellochg Mar 13, 2025
2168a1b
add instrument mode property
azazellochg Mar 13, 2025
16ad292
add euc focus method, find mags on request only
azazellochg Mar 13, 2025
647939b
minor changes
azazellochg Mar 13, 2025
213d946
debug eer acquisition
azazellochg Mar 13, 2025
2157e61
add ef and ld tests, refactor after Krios G4 testing
azazellochg Mar 13, 2025
b18b5c6
add a note about ifaces support
azazellochg Mar 13, 2025
26b7c40
working on Gun1 iface
azazellochg Mar 13, 2025
cbbeb4f
adding some notes
azazellochg Mar 13, 2025
4ed7e0a
fix merge
azazellochg Mar 14, 2025
db7a44a
add calgetter plugin
azazellochg Mar 15, 2025
51ff82d
add docstrings
azazellochg Mar 17, 2025
2a22af8
fix eer error, fix ef test, improve calgetter func, remove unused enu…
azazellochg Mar 17, 2025
528f85f
improving speed test
azazellochg Mar 17, 2025
8b63d77
clear dfd before proceeding, fix save_frames kwarg, remove last frame…
azazellochg Mar 18, 2025
8ce41f0
raise notimpl instead
azazellochg Mar 18, 2025
6d4992b
simplify connected check
azazellochg Mar 18, 2025
e29ba09
fix missing kwargs
azazellochg Mar 18, 2025
4b68fe3
add timings for tecnaiccd, improve speed test, fix safearray orientation
azazellochg Mar 18, 2025
35cbe1f
rename plugin file
azazellochg Mar 18, 2025
02720d2
minor corrections
azazellochg Mar 19, 2025
2ebd855
fix Gun1 and beam tilt calls
azazellochg Mar 20, 2025
6f834ad
log exec special calls
azazellochg Mar 20, 2025
69ea11b
add repr to special objs
azazellochg Mar 20, 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
6 changes: 3 additions & 3 deletions docs/components/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Relative to TEM V1.9 standard scripting adapter:
* Camera
* Configuration
* Gun
* Gun1 (untested)
* Gun1
* Illumination
* InstrumentModeControl
* Projection
Expand All @@ -34,10 +34,10 @@ Relative to TEM V1.2 advanced scripting adapter:

* Acquisitions
* Autoloader
* EnergyFilter (untested)
* EnergyFilter
* Phaseplate
* PiezoStage (untested)
* Source (untested)
* Source
* TemperatureControl
* UserDoorHatch (untested)

Expand Down
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ Latest supported Python version on Windows XP is 3.4. Download pytemscript and i

.. code-block:: python

pip download -d . pytemscript --python-version 34 --only-binary=:all: --platform win32
pip download -d . pytemscript comtypes==1.2.1 mrcfile==1.3.0 numpy==1.15.4 pillow==5.3.0 typing --python-version 34 --only-binary=:all: --platform win32

Copy downloaded \*.whl files to the target PC and install them:

.. code-block:: python

py -m pip install pytemscript --no-index --find-links .
py -m pip install pytemscript typing --no-index --find-links .

Testing
-------
Expand Down
5 changes: 3 additions & 2 deletions docs/remote.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Then you can connect to the server as shown below:

Diagnostic messages are saved to ``socket_client.log`` and ``socket_server.log`` as well as printed to the console. Log files are rotated weekly at midnight.

To shutdown pytemscript-server, press Ctrl+C in the console.

UTAPI client
------------

Expand All @@ -53,8 +55,7 @@ you can search for ``utapi_server.exe`` in the Task Manager. The server is liste
**46699**. Under the hood UTAPI utilizes gRPC (Google Remote Procedure Calls) framework that uses protocol
buffers for communication.

Here we provide a Python client that converts API commands to UTAPI calls.
The client requires extra dependencies to be installed:
Pytemscript converts its API commands to UTAPI calls. The client requires extra dependencies to be installed:

.. code-block:: python

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml.future
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ dependencies = [
]

[project.optional-dependencies]
extra = ["matplotlib", "mypy"]
dev = ["matplotlib", "mypy"]
utapi = ["grpcio", "grpcio-tools", "protobuf"]

[project.urls]
Expand Down
2 changes: 1 addition & 1 deletion pytemscript/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '3.0b1'
__version__ = '3.0b2'
5 changes: 5 additions & 0 deletions pytemscript/clients/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def has_lowdose_iface(self) -> bool:
def has_ccd_iface(self) -> bool:
raise NotImplementedError("Method must be implemented in subclass")

@property
@lru_cache(maxsize=1)
def has_calgetter_iface(self) -> bool:
raise NotImplementedError("Method must be implemented in subclass")

def call(self, method: str, body: RequestBody):
""" Main method used by modules. """
raise NotImplementedError("Method must be implemented in subclass")
Expand Down
14 changes: 13 additions & 1 deletion pytemscript/clients/com_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(self,
self.tem_adv = None
self.tem_lowdose = None
self.tecnai_ccd = None
self.calgetter = None

if platform.system() == "Windows":
logging.getLogger("comtypes").setLevel(logging.INFO)
Expand Down Expand Up @@ -64,6 +65,8 @@ def _initialize(self, useLD: bool, useTecnaiCCD: bool):
if self.tecnai_ccd is None:
self.tecnai_ccd = self._createCOMObject(SCRIPTING_TECNAI_CCD2)

self.calgetter = self._createCOMObject(CALGETTER)

if self.tem is None:
raise RuntimeError("Failed to create COM object.")

Expand All @@ -73,6 +76,7 @@ def _close(self):
self.tem_adv = None
self.tem_lowdose = None
self.tecnai_ccd = None
self.calgetter = None

com_module.CoUninitialize()

Expand Down Expand Up @@ -124,6 +128,11 @@ def has_lowdose_iface(self) -> bool:
def has_ccd_iface(self) -> bool:
return self._scope.tecnai_ccd is not None

@property
@lru_cache(maxsize=1)
def has_calgetter_iface(self) -> bool:
return self._scope.calgetter is not None

def _get(self, attrname):
return rgetattr(self._scope, attrname)

Expand Down Expand Up @@ -163,6 +172,9 @@ def _exec_special(self, attrname, **kwargs):
if obj_cls is None or obj_method is None:
raise AttributeError("obj_class and obj_method must be specified")

logging.debug("=> EXEC_SP: %s.%s, kwargs=%r",obj_cls.__name__,
obj_method, kwargs)

if attrname is None: # plugin case
com_obj = self._scope
else:
Expand All @@ -171,7 +183,7 @@ def _exec_special(self, attrname, **kwargs):
method = getattr(obj_instance, obj_method)

if method is None:
raise AttributeError("Method %s not implemented for %s" % (obj_method, obj_cls))
raise AttributeError("Method %s not implemented for %s" % (obj_method, obj_cls.__name__))

result = method(**kwargs)

Expand Down
8 changes: 8 additions & 0 deletions pytemscript/clients/socket_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def has_ccd_iface(self) -> bool:

return response

@property
@lru_cache(maxsize=1)
def has_calgetter_iface(self) -> bool:
response = self.__send_request({"method": "has_calgetter_iface"})
logging.debug("Received response: %s", response)

return response

def call(self, method: str, body: RequestBody):
""" Main method used by modules. """
payload = {"method": method, "body": body}
Expand Down
62 changes: 40 additions & 22 deletions pytemscript/modules/acquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def show_cameras_cca(self, tem_cameras: Dict) -> Dict:

return tem_cameras

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

:param cameraName: Camera name
Expand All @@ -121,7 +121,7 @@ def acquire(self, cameraName: str) -> Image:
t0 = time.time()
imgs = acq.AcquireImages()
t1 = time.time()
image = convert_image(imgs[0], name=cameraName)
image = convert_image(imgs[0], name=cameraName, **kwargs)
t2 = time.time()
logging.debug("\tAcquisition took %f s" % (t1 - t0))
logging.debug("\tConverting image took %f s" %(t2 - t1))
Expand All @@ -130,7 +130,8 @@ def acquire(self, cameraName: str) -> Image:

def acquire_advanced(self,
cameraName: str,
recording: bool = False) -> Optional[Image]:
recording: bool = False,
**kwargs) -> Optional[Image]:
""" Perform actual acquisition with advanced scripting. """
if recording:
self.com_object.CameraContinuousAcquisition.Start()
Expand All @@ -141,7 +142,7 @@ def acquire_advanced(self,
img = self.com_object.CameraSingleAcquisition.Acquire()
t1 = time.time()
#self.com_object.CameraSingleAcquisition.Wait()
image = convert_image(img, name=cameraName, advanced=True)
image = convert_image(img, name=cameraName, advanced=True, **kwargs)
t2 = time.time()
logging.debug("\tAcquisition took %f s" % (t1 - t0))
logging.debug("\tConverting image took %f s" % (t2 - t1))
Expand Down Expand Up @@ -270,39 +271,52 @@ def set_tem_presets_advanced(self,
else:
raise NotImplementedError("This camera does not support electron counting")

if eer is not None and hasattr(capabilities, 'SupportsEER'):
if capabilities.SupportsEER:
# EER saving is supported in TEM server 7.6 (Titan 3.6 / Talos 2.6)
if hasattr(capabilities, 'SupportsEER'):
eer_is_supported = capabilities.SupportsEER
if eer and eer_is_supported:
settings.EER = eer
if eer and not settings.ElectronCounting:
raise RuntimeError("Electron counting should be enabled when using EER")
if eer and 'group_frames' in kwargs:
raise RuntimeError("No frame grouping allowed when using EER")
else:
elif eer and not eer_is_supported:
raise NotImplementedError("This camera does not support EER")
elif not eer and eer_is_supported:
# EER param is persistent throughout camera COM object lifetime,
# if not using EER we need to set it to False
settings.EER = False

if eer and not settings.ElectronCounting:
raise RuntimeError("Electron counting should be enabled when using EER")
if eer and 'group_frames' in kwargs:
raise RuntimeError("No frame grouping allowed when using EER")

if capabilities.SupportsDoseFractions:
dfd = settings.DoseFractionsDefinition
dfd.Clear()

if kwargs.get('save_frames'):
if not capabilities.SupportsDoseFractions:
raise NotImplementedError("This camera does not support dose fractions")

if 'save_frames' in kwargs:
total = settings.CalculateNumberOfFrames()
now = datetime.now()
settings.SubPathPattern = cameraName + "_" + now.strftime("%d%m%Y_%H%M%S")
output = settings.PathToImageStorage + settings.SubPathPattern

if eer is None:
if eer in [False, None]:
group = kwargs.get('group_frames', 1)
if group < 1:
raise ValueError("Frame group size must be at least 1")
if group > total:
raise ValueError("Frame group size cannot exceed maximum possible "
"number of frames: %d. Change exposure time." % total)

dfd = settings.DoseFractionsDefinition
dfd.Clear()
frame_ranges = [(i, min(i + group, total)) for i in range(0, total-1, group)]
for i in frame_ranges:
logging.debug("Using frame ranges: %s", frame_ranges[:-1])
for i in frame_ranges[:-1]:
dfd.AddRange(i[0], i[1])

logging.info("Movie of %d fractions (%d frames, group=%d) "
"will be saved to: %s.mrc",
len(frame_ranges), total, group, output)
len(frame_ranges)-1, total, group, output)
logging.info("MRC format can only contain images of up to "
"16-bits per pixel, to get true CameraCounts "
"multiply pixels by PixelToValueCameraCounts "
Expand Down Expand Up @@ -431,7 +445,7 @@ def __acquire_with_tecnaiccd(self,
"pass useTecnaiCCD=True to the Microscope() ?")
else:
logging.info("Using TecnaiCCD plugin for Gatan camera")
from ..plugins.tecnai_ccd_plugin import TecnaiCCDPlugin
from ..plugins.tecnai_ccd import TecnaiCCDPlugin

body = RequestBody(attr=None,
obj_cls=TecnaiCCDPlugin,
Expand Down Expand Up @@ -506,7 +520,8 @@ def acquire_tem_image(self,
body = RequestBody(attr="tem.Acquisition",
obj_cls=AcquisitionObj,
obj_method="acquire",
cameraName=cameraName)
cameraName=cameraName,
**kwargs)
image = self.__client.call(method="exec_special", body=body)
logging.info("TEM image acquired on %s", cameraName)

Expand Down Expand Up @@ -537,7 +552,8 @@ def acquire_tem_image(self,
obj_cls=AcquisitionObj,
obj_method="acquire_advanced",
cameraName=cameraName,
recording=kwargs["recording"])
recording=kwargs["recording"],
**kwargs)
self.__client.call(method="exec_special", body=body)
logging.info("TEM image acquired on %s", cameraName)
return None
Expand All @@ -546,7 +562,8 @@ def acquire_tem_image(self,
validator=Image,
obj_cls=AcquisitionObj,
obj_method="acquire_advanced",
cameraName=cameraName)
cameraName=cameraName,
**kwargs)
image = self.__client.call(method="exec_special", body=body)
return image

Expand Down Expand Up @@ -587,7 +604,8 @@ def acquire_stem_image(self,
validator=Image,
obj_cls=AcquisitionObj,
obj_method="acquire",
cameraName=cameraName)
cameraName=cameraName,
**kwargs)
image = self.__client.call(method="exec_special", body=body)
logging.info("STEM image acquired on %s", cameraName)

Expand Down
2 changes: 1 addition & 1 deletion pytemscript/modules/autoloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, client):
self.__id = "tem.AutoLoader"
self.__id_adv = "tem_adv.AutoLoader"
self.__err_msg = "Autoloader is not available"
self.__err_msg_adv = "This function is not available in your advanced scripting interface."
self.__err_msg_adv = "Autoloader advanced interface is not available. Requires TEM server 7.8+"

@property
@lru_cache(maxsize=1)
Expand Down
2 changes: 1 addition & 1 deletion pytemscript/modules/energyfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class EnergyFilter:
def __init__(self, client):
self.__client = client
self.__id = "tem_adv.EnergyFilter"
self.__err_msg = "EnergyFilter interface is not available"
self.__err_msg = "EnergyFilter advanced interface is not available. Requires TEM server 7.8+"

@property
@lru_cache(maxsize=1)
Expand Down
8 changes: 7 additions & 1 deletion pytemscript/modules/extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def __init__(self, x: float, y: float) -> None:
self.__min = None
self.__max = None

def __repr__(self):
def __repr__(self) -> str:
return "Vector(x=%f, y=%f)" % (self.x, self.y)

def __str__(self):
Expand Down Expand Up @@ -150,6 +150,9 @@ def __init__(self,
else:
self.timestamp = datetime.now().strftime("%Y:%m:%d %H:%M:%S")

def __repr__(self) -> str:
return "Image()"

@lru_cache(maxsize=1)
def __create_tiff_tags(self):
"""Create TIFF tags from metadata. """
Expand Down Expand Up @@ -229,6 +232,9 @@ class SpecialObj:
def __init__(self, com_object):
self.com_object = com_object

def __repr__(self):
return "%s()" % self.__class__.__name__


class StageObj(SpecialObj):
""" Wrapper around stage / piezo stage COM object. """
Expand Down
Loading