Skip to content

Commit ed2cddd

Browse files
authored
Reorganize TEMController (#99)
* Reorganize TEMController * Import from module instead of files * Add deprecation decorator * Rename Microscope -> get_microscope, get_tem -> get_microscope_class * Make deprecations visible * Add TEMControl (deprecated) for compatibility * Explicitly import components * Add type hints * Move definitions of deprecated functions * Revert update to new import. Will show warnings * Expand backwards compatibility * Improve traceback message * Increase similarity to old version * Revert * Fix formatting
1 parent d71f4f7 commit ed2cddd

21 files changed

+1279
-1084
lines changed

src/instamatic/TEMController/TEMController.py

Lines changed: 1 addition & 844 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1+
# ruff: noqa: E402
12
from __future__ import annotations
23

4+
import warnings
5+
6+
from instamatic.utils.deprecated import VisibleDeprecationWarning
7+
8+
warnings.warn(
9+
'The `TEMController` module is deprecated since version 2.0.6. Use the `controller`-module instead',
10+
VisibleDeprecationWarning,
11+
stacklevel=2,
12+
)
13+
314
from .microscope import Microscope
415
from .TEMController import get_instance, initialize
Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,20 @@
11
from __future__ import annotations
22

3-
from instamatic import config
4-
from instamatic.TEMController.microscope_base import MicroscopeBase
5-
6-
default_tem_interface = config.microscope.interface
3+
from instamatic.microscope.base import MicroscopeBase
4+
from instamatic.utils.deprecated import deprecated
75

86
__all__ = ['Microscope', 'get_tem']
97

108

9+
@deprecated(since='2.0.6', alternative='instamatic.microscope.get_microscope_class')
1110
def get_tem(interface: str) -> 'type[MicroscopeBase]':
12-
"""Grab tem class with the specific 'interface'."""
13-
simulate = config.settings.simulate
14-
15-
if config.settings.tem_require_admin:
16-
from instamatic import admin
17-
18-
if not admin.is_admin():
19-
raise PermissionError('Access to the TEM interface requires admin rights.')
11+
from instamatic.microscope import get_microscope_class
2012

21-
if simulate or interface == 'simulate':
22-
from .simu_microscope import SimuMicroscope as cls
23-
elif interface == 'jeol':
24-
from .jeol_microscope import JeolMicroscope as cls
25-
elif interface == 'fei':
26-
from .fei_microscope import FEIMicroscope as cls
27-
elif interface == 'fei_simu':
28-
from .fei_simu_microscope import FEISimuMicroscope as cls
29-
else:
30-
raise ValueError(f'No such microscope interface: `{interface}`')
31-
32-
return cls
13+
return get_microscope_class(interface=interface)
3314

3415

16+
@deprecated(since='2.0.6', alternative='instamatic.microscope.get_microscope')
3517
def Microscope(name: str = None, use_server: bool = False) -> MicroscopeBase:
36-
"""Generic class to load microscope interface class.
37-
38-
name: str
39-
Specify which microscope to use, must be one of `jeol`, `fei_simu`, `simulate`
40-
use_server: bool
41-
Connect to microscope server running on the host/port defined in the config file
42-
43-
returns: TEM interface class
44-
"""
45-
if name is None:
46-
interface = default_tem_interface
47-
name = interface
48-
elif name != config.settings.microscope:
49-
config.load_microscope_config(microscope_name=name)
50-
interface = config.microscope.interface
51-
else:
52-
interface = config.microscope.interface
53-
54-
if use_server:
55-
from .microscope_client import MicroscopeClient
56-
57-
tem = MicroscopeClient(interface=interface)
58-
else:
59-
cls = get_tem(interface=interface)
60-
tem = cls(name=name)
61-
62-
return tem
18+
from instamatic.microscope import get_microscope
19+
20+
return get_microscope(name=name, use_server=use_server)
Lines changed: 1 addition & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -1,183 +1,3 @@
11
from __future__ import annotations
22

3-
import atexit
4-
import datetime
5-
import json
6-
import pickle
7-
import socket
8-
import subprocess as sp
9-
import threading
10-
import time
11-
from functools import wraps
12-
13-
from instamatic import config
14-
from instamatic.exceptions import TEMCommunicationError, exception_list
15-
from instamatic.server.serializer import dumper, loader
16-
17-
HOST = config.settings.tem_server_host
18-
PORT = config.settings.tem_server_port
19-
BUFSIZE = 1024
20-
21-
22-
class ServerError(Exception):
23-
pass
24-
25-
26-
def kill_server(p):
27-
# p.kill is not adequate
28-
sp.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])
29-
30-
31-
def start_server_in_subprocess():
32-
cmd = 'instamatic.temserver.exe'
33-
p = sp.Popen(cmd, stdout=sp.DEVNULL)
34-
print(f'Starting TEM server ({HOST}:{PORT} on pid={p.pid})')
35-
atexit.register(kill_server, p)
36-
37-
38-
class MicroscopeClient:
39-
"""Simulates a Microscope object and synchronizes calls over a socket
40-
server.
41-
42-
For documentation, see the actual python interface to the microscope
43-
API.
44-
"""
45-
46-
def __init__(self, *, interface: str):
47-
super().__init__()
48-
49-
self.interface = interface
50-
self.name = interface
51-
self._bufsize = BUFSIZE
52-
53-
try:
54-
self.connect()
55-
except ConnectionRefusedError:
56-
start_server_in_subprocess()
57-
58-
for t in range(30):
59-
try:
60-
self.connect()
61-
except ConnectionRefusedError:
62-
time.sleep(1)
63-
if t > 3:
64-
print('Waiting for server')
65-
if t > 30:
66-
raise TEMCommunicationError(
67-
'Cannot establish server connection (timeout)'
68-
)
69-
else:
70-
break
71-
72-
self._init_dict()
73-
self.check_goniotool()
74-
75-
atexit.register(self.s.close)
76-
77-
def connect(self):
78-
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
79-
self.s.connect((HOST, PORT))
80-
print(f'Connected to TEM server ({HOST}:{PORT})')
81-
82-
def __getattr__(self, func_name):
83-
try:
84-
wrapped = self._dct[func_name]
85-
except KeyError as e:
86-
raise AttributeError(
87-
f'`{self.__class__.__name__}` object has no attribute `{func_name}`'
88-
) from e
89-
90-
@wraps(wrapped)
91-
def wrapper(*args, **kwargs):
92-
dct = {'func_name': func_name, 'args': args, 'kwargs': kwargs}
93-
return self._eval_dct(dct)
94-
95-
return wrapper
96-
97-
def _eval_dct(self, dct):
98-
"""Takes approximately 0.2-0.3 ms per call if HOST=='localhost'."""
99-
self.s.send(dumper(dct))
100-
101-
response = self.s.recv(self._bufsize)
102-
103-
if response:
104-
status, data = loader(response)
105-
106-
if status == 200:
107-
return data
108-
109-
elif status == 500:
110-
error_code, args = data
111-
raise exception_list.get(error_code, TEMCommunicationError)(*args)
112-
113-
else:
114-
raise ConnectionError(f'Unknown status code: {status}')
115-
116-
def _init_dict(self):
117-
from instamatic.TEMController.microscope import get_tem
118-
119-
tem = get_tem(interface=self.interface)
120-
121-
self._dct = {
122-
key: value for key, value in tem.__dict__.items() if not key.startswith('_')
123-
}
124-
125-
def __dir__(self):
126-
return self._dct.keys()
127-
128-
def check_goniotool(self):
129-
"""Check whether goniotool is available and update the config as
130-
necessary."""
131-
if config.settings.use_goniotool:
132-
config.settings.use_goniotool = self.is_goniotool_available()
133-
134-
135-
class TraceVariable:
136-
"""Simple class to trace a variable over time.
137-
138-
Usage:
139-
t = TraceVariable(ctrl.stage.get, verbose=True)
140-
t.start()
141-
t.stage.set(x=0, y=0, wait=False)
142-
...
143-
values = t.stop()
144-
"""
145-
146-
def __init__(
147-
self,
148-
func,
149-
interval: float = 1.0,
150-
name: str = 'variable',
151-
verbose: bool = False,
152-
):
153-
super().__init__()
154-
self.name = name
155-
self.func = func
156-
self.interval = interval
157-
self.verbose = verbose
158-
159-
self._traced = []
160-
161-
def start(self):
162-
print(f'Trace started: {self.name}')
163-
self.update()
164-
165-
def stop(self):
166-
self._timer.cancel()
167-
168-
print(f'Trace canceled: {self.name}')
169-
170-
return self._traced
171-
172-
def update(self):
173-
ret = self.func()
174-
175-
now = datetime.datetime.now().strftime('%H:%M:%S.%f')
176-
177-
if self.verbose:
178-
print(f'{now} | Trace {self.name}: {ret}')
179-
180-
self._traced.append((now, ret))
181-
182-
self._timer = threading.Timer(self.interval, self.update)
183-
self._timer.start()
3+
from instamatic.microscope.client import MicroscopeClient

0 commit comments

Comments
 (0)