Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ongoing development #3

Draft
wants to merge 26 commits into
base: devel
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e729c9d
add close connection
azazellochg Feb 8, 2023
05b1083
large refactoring, mainly to separate objects.py and client/sever logic
azazellochg Feb 12, 2023
151bc8c
continue refactoring, basic get/set/has seem to work remotely/locally
azazellochg Apr 5, 2023
74b9784
add feg regs script
azazellochg Jun 26, 2023
3e2e2e9
remove semccd
azazellochg Jan 13, 2025
73e4f02
big refactoring in progress
azazellochg Jan 22, 2025
19b81e9
refactor requirements for rtd
azazellochg Jan 22, 2025
85940c3
first testing. move base com class
azazellochg Jan 22, 2025
03ab4f8
refactor vectors and stage
azazellochg Jan 23, 2025
a433e35
fix imports
azazellochg Jan 23, 2025
58cecb1
adding mags and button events
azazellochg Jan 23, 2025
43f1c40
add missing file
azazellochg Jan 23, 2025
b5e9b7f
playing with buttons, fix vectors and stage, fix call()
azazellochg Jan 23, 2025
91d979d
1) fix has attr, 2) cache stage limits, 3) fix check_prerequisites, 4…
azazellochg Jan 24, 2025
f38ac99
enable stem for the test acquisition
azazellochg Jan 24, 2025
515fe85
add annotations
azazellochg Jan 24, 2025
e83c307
minor fixes
azazellochg Jan 24, 2025
420aed2
cleaner imports
azazellochg Jan 24, 2025
5f025c3
improve enum docs
azazellochg Jan 24, 2025
c5df360
boilerplate for server
azazellochg Jan 26, 2025
ee5a586
first attempt of a socket client
azazellochg Jan 27, 2025
324988c
basic doc
azazellochg Jan 27, 2025
ed6d8a8
better imports
azazellochg Jan 27, 2025
f0125f1
add a remote connection test
azazellochg Jan 27, 2025
f4c0cc0
fix hostname
azazellochg Jan 27, 2025
52c360d
specify data length
azazellochg Jan 27, 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
Prev Previous commit
Next Next commit
first testing. move base com class
  • Loading branch information
azazellochg committed Jan 22, 2025
commit 85940c391a20501eddf930d2f49af6f3a22d578e
71 changes: 0 additions & 71 deletions pytemscript/base_microscope.py

This file was deleted.

82 changes: 77 additions & 5 deletions pytemscript/clients/com_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,64 @@
import logging
import platform
import sys
sys.coinit_flags = 0
import comtypes # COM is initialized automatically for the thread that imports this module for the first time
import comtypes.client
from ..utils.misc import rgetattr, rsetattr
from ..base_microscope import BaseMicroscope
from ..utils.constants import *
from ..utils.enums import TEMScriptingError


class COMBase:
""" Base class that handles COM interface connections. """
def __init__(self, useLD=False, useTecnaiCCD=False):
self.tem = None
self.tem_adv = None
self.tem_lowdose = None
self.tecnai_ccd = None

if platform.system() == "Windows":
logging.getLogger("comtypes").setLevel(logging.INFO)
self._initialize(useLD, useTecnaiCCD)
else:
raise NotImplementedError("Running locally is only supported for Windows platform")

@staticmethod
def _createCOMObject(progId):
""" Connect to a COM interface. """
try:
obj = comtypes.client.CreateObject(progId)
logging.info("Connected to %s" % progId)
return obj
except:
logging.info("Could not connect to %s" % progId)
return None

def _initialize(self, useLD, useTecnaiCCD):
""" Wrapper to create interfaces as requested. """
self.tem_adv = self._createCOMObject(SCRIPTING_ADV)
self.tem = self._createCOMObject(SCRIPTING_STD)

if self.tem is None: # try Tecnai instead
self.tem = self._createCOMObject(SCRIPTING_TECNAI)

if useLD:
self.tem_lowdose = self._createCOMObject(SCRIPTING_LOWDOSE)
if useTecnaiCCD:
self.tecnai_ccd = self._createCOMObject(SCRIPTING_TECNAI_CCD)
if self.tecnai_ccd is None:
self.tecnai_ccd = self._createCOMObject(SCRIPTING_TECNAI_CCD2)
import comtypes.gen.TECNAICCDLib

@staticmethod
def handle_com_error(com_error):
""" Try catching COM error. """
try:
default = TEMScriptingError.E_NOT_OK.value
err = TEMScriptingError(int(getattr(com_error, 'hresult', default))).name
logging.error('COM error: %s' % err)
except ValueError:
logging.error('Exception : %s' % sys.exc_info()[1])


class COMClient:
@@ -11,17 +69,19 @@ class COMClient:
:type useLD: bool
:param useTecnaiCCD: Connect to TecnaiCCD plugin on microscope PC that controls Digital Micrograph (maybe faster than via TIA / std scripting)
:type useTecnaiCCD: bool
:param debug: Print debug messages
:type debug: bool
"""
def __init__(self, useLD=False, useTecnaiCCD=False):
logging.basicConfig(level=logging.INFO,
def __init__(self, useLD=False, useTecnaiCCD=False, debug=False):
logging.basicConfig(level=logging.DEBUG if debug else logging.INFO,
datefmt='%d/%b/%Y %H:%M:%S',
format='[%(asctime)s] %(message)s',
handlers=[
logging.FileHandler("com_client.log", "w", "utf-8"),
logging.StreamHandler()])

# Create all COM interfaces
self._scope = BaseMicroscope(useLD, useTecnaiCCD)
self._scope = COMBase(useLD, useTecnaiCCD)

if useTecnaiCCD:
if self._scope.tecnai_ccd is None:
@@ -51,12 +111,24 @@ def get(self, attrname):
def get_from_cache(self, attrname):
if attrname not in self.cache:
self.cache[attrname] = self.get(attrname)
return self.cache.get(attrname)
return self.cache[attrname]

def clear_cache(self, attrname):
if attrname in self.cache:
del self.cache[attrname]

def has(self, attrname):
""" GET request with cache support. Should be used only for attributes
that do not change over the session. """
if attrname not in self.cache:
try:
_ = self.get(attrname)
self.cache[attrname] = True
except AttributeError:
self.cache[attrname] = False

return self.cache[attrname]

def call(self, attrname, *args, **kwargs):
attrname = attrname.rstrip("()")
return rgetattr(self._scope, attrname, *args, **kwargs)
56 changes: 15 additions & 41 deletions pytemscript/microscope.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from modules import *
from utils.enums import ProductFamily, CondenserLensSystem
from .modules import *
from .utils.enums import ProductFamily, CondenserLensSystem


class Microscope:
""" Base client interface, exposing available methods
and properties.
"""
def __init__(self, communication_type="direct", *args, **kwargs):
self._communication_type = communication_type
if communication_type == "direct":
from clients.com_client import COMClient
def __init__(self, connection="direct", *args, **kwargs):
self._communication_type = connection
if connection == "direct":
from .clients.com_client import COMClient
self.client = COMClient(*args, **kwargs)
elif communication_type == 'grpc':
elif connection == 'grpc':
self.client = GRPCClient(*args, **kwargs)
elif communication_type == 'zmq':
elif connection == 'zmq':
self.client = ZMQClient(*args, **kwargs)
elif communication_type == 'pure_python':
elif connection == 'socket':
self.client = SocketClient(*args, **kwargs)
else:
raise ValueError("Unsupported communication type")
@@ -42,46 +42,20 @@ def __init__(self, communication_type="direct", *args, **kwargs):
if kwargs.get("useLD", False):
self.low_dose = LowDose(client)

def _check_cache(self, key, fetch_func):
if key not in self._cache:
self._cache[key] = fetch_func()
return self._cache[key]

def get(self, attr):
return self.client.get(attr)

def set(self, attr, value, **kwargs):
return self.client.set(attr, value, **kwargs)

def has(self, attr):
""" Get request with cache support. Should be used only for attributes
that do not change over the session. """
if attr not in self._cache:
try:
_ = self.get(attr)
self._cache[attr] = True
except AttributeError:
self._cache[attr] = False

return self._cache.get(attr)

def call(self, attr, *args, **kwargs):
return self.client.call(attr, *args, **kwargs)

@property
def family(self):
""" Returns the microscope product family / platform. """
return self._check_cache("family",
lambda: ProductFamily(self.get("tem.Configuration.ProductFamily")).name)
value = self.client.get_from_cache("tem.Configuration.ProductFamily")
return ProductFamily(value).name

@property
def condenser_system(self):
""" Returns the type of condenser lens system: two or three lenses. """
return self._check_cache("condenser_system",
lambda: CondenserLensSystem(self.get("tem.Configuration.CondenserLensSystem")).name)
value = self.client.get_from_cache("tem.Configuration.CondenserLensSystem")
return CondenserLensSystem(value).name

@property
def user_buttons(self):
""" Returns a dict with assigned hand panels buttons. """
return self._check_cache("user_buttons",
lambda: {b.Name: b.Label for b in self.get("tem.UserButtons")})
values = self.client.get("tem.UserButtons")
return {b.Name: b.Label for b in values}
2 changes: 1 addition & 1 deletion pytemscript/modules/detectors.py
Original file line number Diff line number Diff line change
@@ -79,7 +79,7 @@ def cameras(self):
def stem_detectors(self):
""" Returns a dict with STEM detectors parameters. """
stem_detectors = dict()
detectors = self._client.get_from_cache("tem.Acquisitions.Detectors")
detectors = self._client.get_from_cache("tem.Acquisition.Detectors")
for d in detectors:
info = d.Info
name = info.Name
6 changes: 4 additions & 2 deletions pytemscript/modules/stage.py
Original file line number Diff line number Diff line change
@@ -12,13 +12,14 @@ def __init__(self, client):

def _from_dict(self, **values):
axes = 0
position = self._client.get("tem.Stage.Position")
position = self._client.get_from_cache("tem.Stage.Position")
for key, value in values.items():
if key not in 'xyzab':
raise ValueError("Unexpected axis: %s" % key)
attr_name = key.upper()
setattr(position, attr_name, float(value))
axes |= getattr(StageAxes, attr_name)
self._client.clear_cache("tem.Stage.Position")
return position, axes

def _beta_available(self):
@@ -84,11 +85,12 @@ def holder(self):
@property
def position(self):
""" The current position of the stage (x,y,z in um and a,b in degrees). """
pos = self._client.get("tem.Stage.Position")
pos = self._client.get_from_cache("tem.Stage.Position")
result = {key.lower(): getattr(pos, key) * 1e6 for key in 'XYZ'}

keys = 'AB' if self._beta_available() else 'A'
result.update({key.lower(): math.degrees(getattr(pos, key)) for key in keys})
self._client.clear_cache("tem.Stage.Position")

return result

2 changes: 1 addition & 1 deletion pytemscript/modules/temperature.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ def is_available(self):
if self.__std_available:
return self._client.has("tem.TemperatureControl.TemperatureControlAvailable")
else:
raise NotImplementedError(self._err_msg)
return False

def force_refill(self):
""" Forces LN refill if the level is below 70%, otherwise returns an error.
Loading