Coverage for src/pytribeam/GUI/config_ui/microscope_interface.py: 0%
96 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
1"""Microscope communication interface.
3This module provides an abstraction layer for microscope communication,
4separating hardware interaction from UI code.
5"""
7from typing import Optional, Dict, Tuple
9import pytribeam.factory as factory
10import pytribeam.laser as laser
11import pytribeam.types as tbt
12import pytribeam.utilities as ut
13from pytribeam.GUI.common.errors import MicroscopeConnectionError
14from pytribeam.constants import Conversions
17class MicroscopeInterface:
18 """Interface for microscope communication.
20 This class abstracts microscope operations and provides a clean API
21 for the GUI to interact with hardware. It handles connection management
22 and provides context manager support for automatic cleanup.
24 Attributes:
25 host: Connection host address
26 port: Connection port
27 is_connected: Whether microscope is currently connected
28 """
30 def __init__(self, host: str = "localhost", port: str = ""):
31 """Initialize microscope interface.
33 Args:
34 host: Microscope connection host (default: 'localhost')
35 port: Microscope connection port (default: '')
36 """
37 self.host = host
38 self.port = port
39 self._microscope: Optional[tbt.Microscope] = None
41 @property
42 def is_connected(self) -> bool:
43 """Check if microscope is connected."""
44 return self._microscope is not None
46 @property
47 def microscope(self) -> Optional[tbt.Microscope]:
48 """Get microscope object (read-only access)."""
49 return self._microscope
51 def __enter__(self):
52 """Context manager entry - connect to microscope."""
53 self.connect()
54 return self
56 def __exit__(self, exc_type, exc_val, exc_tb):
57 """Context manager exit - disconnect from microscope."""
58 self.disconnect()
59 return False
61 def connect(self):
62 """Establish connection to microscope.
64 Raises:
65 MicroscopeConnectionError: If connection fails
66 """
67 if self.is_connected:
68 return # Already connected
70 try:
71 self._microscope = tbt.Microscope()
72 ut.connect_microscope(
73 self._microscope,
74 quiet_output=True,
75 connection_host=self.host,
76 connection_port=self.port,
77 )
78 except ConnectionError as e:
79 self._microscope = None
80 raise MicroscopeConnectionError(
81 f"Failed to connect to microscope at {self.host}:{self.port}"
82 ) from e
84 def disconnect(self):
85 """Close microscope connection."""
86 if self._microscope is not None:
87 try:
88 ut.disconnect_microscope(self._microscope)
89 except Exception:
90 pass # Ignore errors during disconnect
91 finally:
92 self._microscope = None
94 def ensure_connected(self):
95 """Ensure microscope is connected, raise if not.
97 Raises:
98 MicroscopeConnectionError: If not connected
99 """
100 if not self.is_connected:
101 raise MicroscopeConnectionError(
102 "Not connected to microscope. Call connect() first."
103 )
105 def get_stage_position(self) -> tbt.StagePositionUser:
106 """Get current stage position.
108 Returns:
109 Current stage position with x, y, z, r, t
111 Raises:
112 MicroscopeConnectionError: If not connected or operation fails
113 """
114 self.ensure_connected()
116 try:
117 return factory.active_stage_position_settings(self._microscope)
118 except Exception as e:
119 raise MicroscopeConnectionError("Failed to get stage position") from e
121 def get_working_distances(self) -> Dict[str, float]:
122 """Get current electron and ion beam working distances.
124 Returns:
125 Dictionary with 'electron_wd_mm' and 'ion_wd_mm' keys
127 Raises:
128 MicroscopeConnectionError: If not connected or operation fails
129 """
130 self.ensure_connected()
132 try:
133 electron_wd = (
134 ut.beam_type(
135 tbt.ElectronBeam(tbt.BeamSettings()), self._microscope
136 ).working_distance.value
137 * Conversions.M_TO_MM
138 )
140 ion_wd = (
141 ut.beam_type(
142 tbt.IonBeam(tbt.BeamSettings()), self._microscope
143 ).working_distance.value
144 * Conversions.M_TO_MM
145 )
147 return {
148 "electron_wd_mm": electron_wd,
149 "ion_wd_mm": ion_wd,
150 }
151 except Exception as e:
152 raise MicroscopeConnectionError("Failed to get working distances") from e
154 def get_stage_info(self) -> Dict[str, any]:
155 """Get comprehensive stage information.
157 Returns:
158 Dictionary with position and working distance info
160 Raises:
161 MicroscopeConnectionError: If not connected or operation fails
162 """
163 self.ensure_connected()
165 try:
166 position = self.get_stage_position()
167 wds = self.get_working_distances()
169 return {
170 "x_mm": position.x_mm,
171 "y_mm": position.y_mm,
172 "z_mm": position.z_mm,
173 "r_deg": position.r_deg,
174 "t_deg": position.t_deg,
175 "electron_wd_mm": wds["electron_wd_mm"],
176 "ion_wd_mm": wds["ion_wd_mm"],
177 }
178 except MicroscopeConnectionError:
179 raise
180 except Exception as e:
181 raise MicroscopeConnectionError("Failed to get stage info") from e
183 def get_imaging_settings(self) -> tbt.ImageSettings:
184 """Get current imaging settings.
186 Returns:
187 Current image settings including beam, detector, scan parameters
189 Raises:
190 MicroscopeConnectionError: If not connected or operation fails
191 """
192 self.ensure_connected()
194 try:
195 return factory.active_image_settings(self._microscope)
196 except Exception as e:
197 raise MicroscopeConnectionError("Failed to get imaging settings") from e
199 def get_laser_state(self) -> Dict:
200 """Get current laser settings.
202 Returns:
203 Dictionary of laser state parameters
205 Raises:
206 MicroscopeConnectionError: If operation fails
207 """
208 try:
209 laser_state = factory.active_laser_state()
210 return laser.laser_state_to_db(laser_state)
211 except Exception as e:
212 raise MicroscopeConnectionError("Failed to get laser state") from e
214 def test_connection(self) -> Tuple[bool, str]:
215 """Test microscope connection.
217 Returns:
218 Tuple of (success, message)
219 """
220 try:
221 self.connect()
222 self.disconnect()
223 return True, "Connection successful"
224 except MicroscopeConnectionError as e:
225 return False, str(e)
226 except Exception as e:
227 return False, f"Unexpected error: {e}"
230def check_device_connections() -> Dict[str, bool]:
231 """Check connections to peripheral devices (EDS, EBSD, laser).
233 Returns:
234 Dictionary mapping device names to connection status
236 Note:
237 This doesn't require a MicroscopeInterface instance as it
238 checks device connections directly.
239 """
240 try:
241 status = laser._device_connections()
242 return status
243 except Exception as e:
244 return {"error": str(e)}
247def format_stage_info(stage_info: Dict[str, float]) -> str:
248 """Format stage information for display.
250 Args:
251 stage_info: Dictionary from get_stage_info()
253 Returns:
254 Formatted multi-line string
255 """
256 return (
257 f"EBeam WD = {stage_info['electron_wd_mm']:.5f} mm\n"
258 f"IBeam WD = {stage_info['ion_wd_mm']:.5f} mm\n"
259 f"\tX = {stage_info['x_mm']:.5f} mm\n"
260 f"\tY = {stage_info['y_mm']:.5f} mm\n"
261 f"\tZ = {stage_info['z_mm']:.5f} mm\n"
262 f"\tR = {stage_info['r_deg']:.5f} °\n"
263 f"\tT = {stage_info['t_deg']:.5f} °"
264 )