1111from PyQt5 .uic import loadUi
1212from PyQt5 .QtCore import Qt
1313
14+ from .coords_converter import CoordsConverter
15+ from .stage_controller import StageController
16+
1417logger = logging .getLogger (__name__ )
15- logger .setLevel (logging .DEBUG )
18+ logger .setLevel (logging .WARNING )
1619
1720package_dir = os .path .dirname (os .path .abspath (__file__ ))
1821debug_dir = os .path .join (os .path .dirname (package_dir ), "debug" )
@@ -26,7 +29,7 @@ class Calculator(QWidget):
2629 reticle adjustments, and issuing movement commands to stages.
2730 """
2831
29- def __init__ (self , model , reticle_selector , stage_controller ):
32+ def __init__ (self , model , reticle_selector ):
3033 """
3134 Initializes the Calculator widget by setting up the UI components and connecting relevant signals.
3235
@@ -37,9 +40,10 @@ def __init__(self, model, reticle_selector, stage_controller):
3740 """
3841 super ().__init__ ()
3942 self .model = model
43+ self .stage_controller = StageController (self .model )
44+ self .coords_converter = CoordsConverter (self .model )
4045 self .reticle_selector = reticle_selector
4146 self .reticle = None
42- self .stage_controller = stage_controller
4347
4448 self .ui = loadUi (os .path .join (ui_dir , "calc.ui" ), self )
4549 self .setWindowTitle ("Calculator" )
@@ -113,17 +117,17 @@ def set_calc_functions(self):
113117 """
114118 for stage_sn , item in self .model .transforms .items ():
115119 transM , scale = item [0 ], item [1 ]
116- if transM is not None : # Set calc function for calibrated stages
120+ if transM is not None and scale is not None : # Set calc function for calibrated stages
117121 push_button = self .findChild (QPushButton , f"convert_{ stage_sn } " )
118122 if not push_button :
119123 logger .warning (f"Error: QPushButton for { stage_sn } not found" )
120124 continue
121125 self ._enable (stage_sn )
122- push_button .clicked .connect (self ._create_convert_function (stage_sn , transM , scale ))
126+ push_button .clicked .connect (self ._create_convert_function (stage_sn ))
123127 else : # Block calc functions for uncalibrated stages
124128 self ._disable (stage_sn )
125129
126- def _create_convert_function (self , stage_sn , transM , scale ):
130+ def _create_convert_function (self , stage_sn ):
127131 """
128132 Creates a lambda function for converting coordinates for a specific stage.
129133
@@ -137,11 +141,9 @@ def _create_convert_function(self, stage_sn, transM, scale):
137141 """
138142 logger .debug ("\n === Creating convert function ===" )
139143 logger .debug (f"Stage SN: { stage_sn } " )
140- logger .debug (f"transM: { transM } " )
141- logger .debug (f"scale: { scale } " )
142- return lambda : self ._convert (stage_sn , transM , scale )
144+ return lambda : self ._convert (stage_sn )
143145
144- def _convert (self , sn , transM , scale ):
146+ def _convert (self , sn ):
145147 """
146148 Performs the conversion between local and global coordinates based on the user's input.
147149 Depending on the entered values, the function converts global to local or local to global coordinates.
@@ -160,15 +162,18 @@ def _convert(self, sn, transM, scale):
160162 localZ = self .findChild (QLineEdit , f"localZ_{ sn } " ).text ()
161163
162164 logger .debug ("- Convert -" )
163- logger .debug (f"Global: { globalX } , { globalY } , { globalZ } " )
164- logger .debug (f"Local: { localX } , { localY } , { localZ } " )
165+ logger .debug (f"User Input (Global): { globalX } , { globalY } , { globalZ } " )
166+ logger .debug (f"User Input (Local): { localX } , { localY } , { localZ } " )
167+ logger .debug (f"User Input (Local): { self .reticle } " )
165168 trans_type , local_pts , global_pts = self ._get_transform_type (globalX , globalY , globalZ , localX , localY , localZ )
166169 if trans_type == "global_to_local" :
167- local_pts_ret = self ._apply_inverse_transformation (global_pts , transM , scale )
168- self ._show_local_pts_result (sn , local_pts_ret )
170+ local_pts_ret = self .coords_converter .global_to_local (sn , global_pts , self .reticle )
171+ if local_pts_ret is not None :
172+ self ._show_local_pts_result (sn , local_pts_ret )
169173 elif trans_type == "local_to_global" :
170- global_pts_ret = self ._apply_transformation (local_pts , transM , scale )
171- self ._show_global_pts_result (sn , global_pts_ret )
174+ global_pts_ret = self .coords_converter .local_to_global (sn , local_pts , self .reticle )
175+ if global_pts_ret is not None :
176+ self ._show_global_pts_result (sn , global_pts_ret )
172177 else :
173178 logger .warning (f"Error: Invalid transforsmation type for { sn } " )
174179 return
@@ -253,117 +258,6 @@ def is_valid_number(s):
253258 else :
254259 return None , None , None
255260
256- def _apply_reticle_adjustments (self , global_pts ):
257- """
258- Applies the selected reticle's adjustments (rotation and offsets) to the given global coordinates.
259-
260- Args:
261- global_pts (ndarray): The global coordinates to adjust.
262-
263- Returns:
264- tuple: The adjusted global coordinates (x, y, z).
265- """
266- reticle_metadata = self .model .get_reticle_metadata (self .reticle )
267- reticle_rot = reticle_metadata .get ("rot" , 0 )
268- reticle_rotmat = reticle_metadata .get ("rotmat" , np .eye (3 )) # Default to identity matrix if not found
269- reticle_offset = np .array ([
270- reticle_metadata .get ("offset_x" , global_pts [0 ]),
271- reticle_metadata .get ("offset_y" , global_pts [1 ]),
272- reticle_metadata .get ("offset_z" , global_pts [2 ])
273- ])
274-
275- if reticle_rot != 0 :
276- # Transpose because points are row vectors
277- global_pts = global_pts @ reticle_rotmat .T
278- global_pts = global_pts + reticle_offset
279-
280- global_x = np .round (global_pts [0 ], 1 )
281- global_y = np .round (global_pts [1 ], 1 )
282- global_z = np .round (global_pts [2 ], 1 )
283- return global_x , global_y , global_z
284-
285- def _apply_transformation (self , local_point_ , transM_LR , scale ):
286- """
287- Applies the transformation to convert local coordinates to global coordinates.
288-
289- Args:
290- local_point (ndarray): The local coordinates to be transformed.
291- transM_LR (ndarray): The transformation matrix.
292- scale (ndarray): The scale factors for the coordinates.
293-
294- Returns:
295- ndarray: The transformed global coordinates.
296- """
297- local_point = local_point_ * scale
298- local_point = np .append (local_point , 1 )
299- global_point = np .dot (transM_LR , local_point )
300- logger .debug (f"local_to_global: { local_point_ } -> { global_point [:3 ]} " )
301- logger .debug (f"R: { transM_LR [:3 , :3 ]} \n T: { transM_LR [:3 , 3 ]} " )
302-
303- # Ensure the reticle is defined and get its metadata
304- if self .reticle and self .reticle != "Global coords" :
305- # Apply the reticle offset and rotation adjustment
306- global_x , global_y , global_z = self ._apply_reticle_adjustments (global_point [:3 ])
307- # Return the adjusted global coordinates
308- return np .array ([global_x , global_y , global_z ])
309-
310- return global_point [:3 ]
311-
312- def _apply_reticle_adjustments_inverse (self , global_point ):
313- """
314- Applies the inverse of reticle adjustments to the global coordinates.
315-
316- Args:
317- global_point (ndarray): The global coordinates to adjust.
318-
319- Returns:
320- ndarray: The adjusted global coordinates.
321- """
322- if self .reticle and self .reticle != "Global coords" :
323- # Convert global_point to numpy array if it's not already
324- global_point = np .array (global_point )
325-
326- # Get the reticle metadata
327- reticle_metadata = self .model .get_reticle_metadata (self .reticle )
328-
329- # Get rotation matrix (default to identity if not found)
330- reticle_rotmat = reticle_metadata .get ("rotmat" , np .eye (3 ))
331-
332- # Get offset values, default to global point coordinates if not found
333- reticle_offset = np .array ([
334- reticle_metadata .get ("offset_x" , 0 ), # Default to 0 if no offset is provided
335- reticle_metadata .get ("offset_y" , 0 ),
336- reticle_metadata .get ("offset_z" , 0 )
337- ])
338-
339- # Subtract the reticle offset
340- global_point = global_point - reticle_offset
341- # Undo the rotation
342- global_point = np .dot (global_point , reticle_rotmat )
343-
344- return global_point
345-
346- def _apply_inverse_transformation (self , global_point , transM_LR , scale ):
347- """
348- Applies the inverse transformation to convert global coordinates to local coordinates.
349-
350- Args:
351- global_point (ndarray): The global coordinates.
352- transM_LR (ndarray): The transformation matrix.
353- scale (ndarray): The scale factors for the coordinates.
354-
355- Returns:
356- ndarray: The transformed local coordinates.
357- """
358- global_point = self ._apply_reticle_adjustments_inverse (global_point )
359-
360- # Transpose the 3x3 rotation part
361- R_T = transM_LR [:3 , :3 ].T
362- local_point = np .dot (R_T , global_point - transM_LR [:3 , 3 ])
363- logger .debug (f"global_to_local { global_point } -> { local_point / scale } " )
364- logger .debug (f"R.T: { R_T } \n T: { transM_LR [:3 , 3 ]} " )
365- return local_point / scale
366-
367261 def _disable (self , sn ):
368262 """
369263 Disables the group box and clears the input fields for the given stage.
@@ -442,7 +336,7 @@ def _connect_move_stage_buttons(self):
442336 for stage_sn in self .model .stages .keys ():
443337 moveXY_button = self .findChild (QPushButton , f"moveStageXY_{ stage_sn } " )
444338 if moveXY_button :
445- moveXY_button .clicked .connect (self ._create_stage_function (stage_sn , "moveXY" ))
339+ moveXY_button .clicked .connect (self ._create_stage_function (stage_sn ))
446340
447341 def _stop_stage (self , move_type ):
448342 """
@@ -455,9 +349,9 @@ def _stop_stage(self, move_type):
455349 command = {
456350 "move_type" : move_type
457351 }
458- self .stage_controller .stop_request (command )
352+ self .stage_controller .request (command )
459353
460- def _create_stage_function (self , stage_sn , move_type ):
354+ def _create_stage_function (self , stage_sn ):
461355 """
462356 Creates a function to move the stage to specified coordinates.
463357
@@ -468,9 +362,9 @@ def _create_stage_function(self, stage_sn, move_type):
468362 Returns:
469363 function: A lambda function to move the stage.
470364 """
471- return lambda : self ._move_stage (stage_sn , move_type )
365+ return lambda : self ._move_stage (stage_sn )
472366
473- def _move_stage (self , stage_sn , move_type ):
367+ def _move_stage (self , stage_sn ):
474368 """
475369 Moves the stage to the coordinates specified in the input fields, with confirmation and safety checks.
476370
@@ -480,6 +374,7 @@ def _move_stage(self, stage_sn, move_type):
480374 """
481375 try :
482376 # Convert the text to float, round it, then cast to int
377+ # Move request is in mm, so divide by 1000
483378 x = float (self .findChild (QLineEdit , f"localX_{ stage_sn } " ).text ()) / 1000
484379 y = float (self .findChild (QLineEdit , f"localY_{ stage_sn } " ).text ()) / 1000
485380 z = 15.0 # Z is inverted in the server.
@@ -493,20 +388,27 @@ def _move_stage(self, stage_sn, move_type):
493388 return
494389
495390 # Use the confirm_move_stage function to ask for confirmation
496- if self ._confirm_move_stage (x , y ):
497- # If the user confirms, proceed with moving the stage
498- print (f"Moving stage { stage_sn } to ({ np .round (x * 1000 )} , { np .round (y * 1000 )} , 0)" )
499- command = {
500- "stage_sn" : stage_sn ,
501- "move_type" : move_type ,
502- "x" : x ,
503- "y" : y ,
504- "z" : z
505- }
506- self .stage_controller .move_request (command )
507- else :
508- # If the user cancels, do nothing
391+ if not self ._confirm_move_stage (x , y ):
509392 print ("Stage move canceled by user." )
393+ return # User canceled the move
394+
395+ # If the user confirms, proceed with moving the stage
396+ command = {
397+ "stage_sn" : stage_sn ,
398+ "move_type" : "stepMode" ,
399+ "stepMode" : 0 # 0 for coarse, 1 for fine
400+ }
401+ self .stage_controller .request (command )
402+
403+ command = {
404+ "stage_sn" : stage_sn ,
405+ "move_type" : "moveXY0" ,
406+ "x" : x ,
407+ "y" : y ,
408+ "z" : z
409+ }
410+ self .stage_controller .request (command )
411+ print (f"Moving stage { stage_sn } to ({ np .round (x * 1000 )} , { np .round (y * 1000 )} , 0)" )
510412
511413 def _is_z_safe_pos (self , stage_sn , x , y , z ):
512414 """
@@ -524,23 +426,25 @@ def _is_z_safe_pos(self, stage_sn, x, y, z):
524426 # Z is inverted in the server
525427 local_pts_z15 = [float (x ) * 1000 , float (y ) * 1000 , float (15.0 - z ) * 1000 ] # Should be top of the stage
526428 local_pts_z0 = [float (x ) * 1000 , float (y ) * 1000 , 15.0 * 1000 ] # Should be bottom
527- for sn , item in self .model .transforms .items ():
429+ for sn , _ in self .model .transforms .items ():
528430 if sn != stage_sn :
529431 continue
530432
531- transM , scale = item [0 ], item [1 ]
532- if transM is not None :
533- try :
534- # Apply transformations to get global points for Z=15 and Z=0
535- global_pts_z15 = self ._apply_transformation (local_pts_z15 , transM , scale )
536- global_pts_z0 = self ._apply_transformation (local_pts_z0 , transM , scale )
537-
538- # Ensure that Z=15 is higher than Z=0 and Z=15 is positive
539- if global_pts_z15 [2 ] > global_pts_z0 [2 ] and global_pts_z15 [2 ] > 0 :
540- return True
541- except Exception as e :
542- logger .error (f"Error applying transformation for stage { stage_sn } : { e } " )
543- return False
433+ try :
434+ # Apply transformations to get global points for Z=15 and Z=0
435+ global_pts_z15 = self .coords_converter .local_to_global (stage_sn , local_pts_z15 )
436+ global_pts_z0 = self .coords_converter .local_to_global (stage_sn , local_pts_z0 )
437+
438+ if global_pts_z15 is None or global_pts_z0 is None :
439+ return False # Transformation failed, return False
440+
441+ # Ensure that Z=15 is higher than Z=0 and Z=15 is positive
442+ if global_pts_z15 [2 ] > global_pts_z0 [2 ] and global_pts_z15 [2 ] > 0 :
443+ return True
444+
445+ except Exception as e :
446+ logger .error (f"Error applying transformation for stage { stage_sn } : { e } " )
447+ return False
544448 return False
545449
546450 def _confirm_move_stage (self , x , y ):
0 commit comments