@@ -688,6 +688,37 @@ def _is_particle_area_ok(self, area):
688688 return is_par_area_ok
689689
690690
691+ def _capture_ref_frame (self , par_image = None , pixel_size_um = None ):
692+ '''
693+ Capture (or accept) the grayscale frame used for spot selection.
694+
695+ At the microscope, a fresh frame is acquired via the driver. Offline, the
696+ provided ``par_image`` is used and image dimensions / pixel size are
697+ updated from it. This is the single place that defines how the reference
698+ frame is obtained, shared by automated and callback spot selection.
699+
700+ Parameters
701+ ----------
702+ par_image : ndarray, optional
703+ Grayscale frame to use when not running at the microscope.
704+ pixel_size_um : float, optional
705+ Pixel size in micrometers. Required together with ``par_image`` offline.
706+
707+ Returns
708+ -------
709+ ndarray
710+ The grayscale frame image.
711+ '''
712+ if EM_driver .is_microscope_connected ():
713+ self ._check_EM_controller_initialization ()
714+ return EM_driver .get_image_data (self ._im_width , self ._im_height , 1 )
715+ if par_image is not None and pixel_size_um is not None :
716+ self ._im_height , self ._im_width = par_image .shape
717+ self .EM .pixel_size_um = pixel_size_um
718+ return par_image
719+ raise ValueError ('This function must be run at the microscope, or it needs to be passed both image and its pixel size' )
720+
721+
691722 def _get_particle_mask (self , par_image = None , pixel_size_um = None , results_dir = None , centering = False ):
692723 '''
693724 Returns a binary mask of the particle at the center of the image, if the particle is large enough.
@@ -719,16 +750,9 @@ def _get_particle_mask(self, par_image=None, pixel_size_um=None, results_dir=Non
719750 - The commented `cv2.imshow` lines can be enabled for debugging visualization.
720751 '''
721752 # Get particle mask
722- if EM_driver .is_microscope_connected ():
723- self ._check_EM_controller_initialization ()
724- par_image = EM_driver .get_image_data (self ._im_width , self ._im_height , 1 )
725- elif par_image is not None and pixel_size_um is not None :
726- self ._im_height , self ._im_width = par_image .shape
727- self .EM .pixel_size_um = pixel_size_um
728- if results_dir :
729- self .results_dir = results_dir
730- else :
731- raise ValueError ('This function must be run at the microscope, or it needs to be passed both image and its pixel size' )
753+ if not EM_driver .is_microscope_connected () and results_dir :
754+ self .results_dir = results_dir
755+ par_image = self ._capture_ref_frame (par_image , pixel_size_um )
732756
733757 # Apply the threshold to get a binary image
734758 _ , par_mask = cv2 .threshold (par_image , self .powder_meas_cfg .par_brightness_thresh , 255 , cv2 .THRESH_BINARY )
@@ -984,6 +1008,16 @@ def get_XS_acquisition_spots_coord_list(
9841008 - Exclude points that are close to larger particles along the X-ray emission path to the EDS detector,
9851009 as these may absorb emitted X-rays and degrade spectral signal.
9861010 '''
1011+ # In callback mode, spots are supplied by xsp_spot_selector. Skip the
1012+ # particle mask computation entirely and reuse a single reference frame
1013+ # per particle (see _get_callback_xsp_spots).
1014+ if self .powder_meas_cfg .par_spot_selection_mode == 'callback' :
1015+ return self ._get_callback_xsp_spots (
1016+ n_tot_sp_collected ,
1017+ par_image = par_image ,
1018+ pixel_size_um = pixel_size_um ,
1019+ )
1020+
9871021 # --- 1. Acquire or prepare the particle mask and image ---
9881022 if EM_driver .is_microscope_connected ():
9891023 par_mask_return = self ._get_particle_mask ()
@@ -1006,14 +1040,6 @@ def get_XS_acquisition_spots_coord_list(
10061040 # --- 2. Erode the particle mask to avoid edge effects ---
10071041 margin = max (10 , int (self .powder_meas_cfg .par_mask_margin / self .EM .pixel_size_um ))
10081042 final_mask = self ._erode_particle_mask (par_mask , margin )
1009-
1010- if self .powder_meas_cfg .par_spot_selection_mode == 'callback' :
1011- return self ._get_callback_xsp_spots (
1012- n_tot_sp_collected = n_tot_sp_collected ,
1013- par_image = par_image ,
1014- par_mask = par_mask ,
1015- usable_mask = final_mask ,
1016- )
10171043
10181044 # --- 3. Find bright points in image, which indicate highest regions on particle---
10191045 thresholded_image , min_area_pixels = self ._find_particle_bright_regions (final_mask , par_image )
@@ -1057,21 +1083,27 @@ def get_XS_acquisition_spots_coord_list(
10571083 def _get_callback_xsp_spots (
10581084 self ,
10591085 n_tot_sp_collected : int ,
1060- par_image ,
1061- par_mask ,
1062- usable_mask ,
1086+ par_image = None ,
1087+ pixel_size_um = None ,
10631088 ) -> List [tuple ]:
1064- """Invoke xsp_spot_selector and validate returned pixel coordinates."""
1089+ """Invoke xsp_spot_selector and validate returned pixel coordinates.
1090+
1091+ The reference frame is captured once per particle (when ``self.ref_image``
1092+ is None) and reused for every spot batch on that particle, so the
1093+ drift-correction reference stays fixed across that particle's spectra. No
1094+ particle mask is computed in this mode.
1095+ """
1096+ if self .ref_image is None :
1097+ self .ref_image = self ._capture_ref_frame (par_image , pixel_size_um )
1098+
10651099 frame_label = getattr (self .EM , 'current_frame_label' , '' )
10661100 if frame_label is None :
10671101 frame_label = ''
10681102
10691103 context = XSpSpotSelectionContext (
10701104 particle_id = self .tot_par_cntr ,
10711105 n_tot_sp_collected = n_tot_sp_collected ,
1072- par_image = par_image ,
1073- par_mask = par_mask ,
1074- usable_mask = usable_mask ,
1106+ ref_image = self .ref_image ,
10751107 pixel_size_um = float (self .EM .pixel_size_um ),
10761108 frame_label = str (frame_label ),
10771109 im_width = int (self ._im_width ),
@@ -1085,15 +1117,13 @@ def _get_callback_xsp_spots(
10851117 raise
10861118
10871119 if not raw_spots :
1088- self .ref_image = par_image
10891120 return []
10901121
10911122 selected_points = validate_xsp_spot_pixels (
10921123 raw_spots ,
10931124 im_width = int (self ._im_width ),
10941125 im_height = int (self ._im_height ),
10951126 )
1096- self .ref_image = par_image
10971127 return selected_points
10981128
10991129
0 commit comments