Skip to content

Commit 7471c15

Browse files
committed
update optimization module with numpy style docstrings
1 parent 413fd17 commit 7471c15

File tree

11 files changed

+1758
-532
lines changed

11 files changed

+1758
-532
lines changed

GEECS-Scanner-GUI/geecs_scanner/optimization/base_evaluator.py

Lines changed: 444 additions & 121 deletions
Large diffs are not rendered by default.

GEECS-Scanner-GUI/geecs_scanner/optimization/base_optimizer.py

Lines changed: 343 additions & 73 deletions
Large diffs are not rendered by default.
Lines changed: 244 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,303 @@
1+
"""
2+
ALine3 electron beam size evaluator for optimization.
3+
4+
This module provides an evaluator for optimizing electron beam size on the ALine3
5+
diagnostic camera (UC_ALineEBeam3). The evaluator computes beam size metrics using
6+
full-width half-maximum (FWHM) measurements in both x and y directions, combining
7+
them into a single objective function suitable for automated optimization.
8+
9+
The evaluator integrates with the GEECS scan analysis framework to process beam
10+
profile images and extract quantitative beam size measurements with proper spatial
11+
calibration.
12+
13+
Classes
14+
-------
15+
ALine3SizeEval
16+
Evaluator for ALine3 electron beam size optimization.
17+
18+
Examples
19+
--------
20+
Basic usage in optimization:
21+
22+
>>> evaluator = ALine3SizeEval(
23+
... device_requirements=device_reqs,
24+
... scan_data_manager=sdm,
25+
... data_logger=logger
26+
... )
27+
>>> result = evaluator.get_value(input_parameters)
28+
>>> beam_size_metric = result['f']
29+
30+
Notes
31+
-----
32+
This evaluator requires the UC_ALineEBeam3 camera device and uses a spatial
33+
calibration of 24.4 μm/pixel for converting pixel measurements to physical units.
34+
The objective function combines x and y FWHM measurements as a sum of squares.
35+
"""
36+
137
from __future__ import annotations
238

3-
import logging
439
from typing import TYPE_CHECKING, Dict, Optional
40+
541
if TYPE_CHECKING:
642
from geecs_scanner.data_acquisition.scan_data_manager import ScanDataManager
743
from geecs_scanner.data_acquisition.data_logger import DataLogger
844

9-
import numpy as np
1045

1146
from geecs_scanner.optimization.base_evaluator import BaseEvaluator
1247

13-
from scan_analysis.base import ScanAnalyzerInfo
14-
from scan_analysis.execute_scan_analysis import analyze_scan, instantiate_scan_analyzer
1548
from scan_analysis.analyzers.common.array2D_scan_analysis import Array2DScanAnalyzer
1649
from image_analysis.utils import read_imaq_image
1750
from image_analysis.offline_analyzers.Undulator.EBeamProfile import EBeamProfileAnalyzer
18-
from geecs_data_utils import ScanTag, ScanData
51+
1952

2053
class ALine3SizeEval(BaseEvaluator):
21-
def __init__(self, device_requirements=None,
22-
scan_data_manager: Optional[ScanDataManager] = None,
23-
data_logger: Optional[DataLogger] = None):
54+
"""
55+
Evaluator for ALine3 electron beam size optimization.
56+
57+
This evaluator computes electron beam size metrics from the UC_ALineEBeam3
58+
diagnostic camera using FWHM measurements. It integrates with the GEECS scan
59+
analysis framework to process beam profile images and extract quantitative
60+
measurements suitable for automated optimization.
61+
62+
The evaluator uses an Array2DScanAnalyzer with an EBeamProfileAnalyzer to
63+
process images and compute beam size statistics. The objective function
64+
combines x and y FWHM measurements with spatial calibration to produce
65+
a single metric for optimization.
2466
25-
required_keys={}
67+
Parameters
68+
----------
69+
device_requirements : dict, optional
70+
Dictionary specifying required devices and variables. If None,
71+
defaults will be used for UC_ALineEBeam3 camera.
72+
scan_data_manager : ScanDataManager, optional
73+
Manager for accessing scan data and file paths.
74+
data_logger : DataLogger, optional
75+
Logger instance for accessing shot data and bin information.
76+
77+
Attributes
78+
----------
79+
dev_name : str
80+
Name of the camera device ('UC_ALineEBeam3').
81+
scan_analyzer : Array2DScanAnalyzer
82+
Analyzer instance for processing beam profile images.
83+
output_key : str
84+
Key name for the optimization objective ('f').
85+
objective_tag : str
86+
Tag for logging objective values.
87+
88+
Methods
89+
-------
90+
evaluate_all_shots(shot_entries)
91+
Evaluate beam size for all shots in a data bin.
92+
objective_fn(x, y)
93+
Static method computing objective function from FWHM values.
94+
95+
Examples
96+
--------
97+
>>> evaluator = ALine3SizeEval(
98+
... scan_data_manager=sdm,
99+
... data_logger=logger
100+
... )
101+
>>> result = evaluator.get_value({"focus_strength": 120.5})
102+
>>> beam_size_metric = result['f']
103+
104+
Notes
105+
-----
106+
- Requires UC_ALineEBeam3 camera device to be available
107+
- Uses spatial calibration of 24.4 μm/pixel
108+
- Objective function is sum of squares: (x_fwhm * cal)² + (y_fwhm * cal)²
109+
- Configured for live analysis during optimization scans
110+
"""
111+
112+
def __init__(
113+
self,
114+
device_requirements=None,
115+
scan_data_manager: Optional[ScanDataManager] = None,
116+
data_logger: Optional[DataLogger] = None,
117+
):
118+
required_keys = {}
26119

27120
super().__init__(
28-
device_requirements = device_requirements,
29-
required_keys = required_keys,
30-
scan_data_manager = scan_data_manager,
31-
data_logger = data_logger
121+
device_requirements=device_requirements,
122+
required_keys=required_keys,
123+
scan_data_manager=scan_data_manager,
124+
data_logger=data_logger,
32125
)
33126

34-
self.dev_name = 'UC_ALineEBeam3'
35-
config_dict = {'camera_name': self.dev_name}
127+
self.dev_name = "UC_ALineEBeam3"
128+
config_dict = {"camera_name": self.dev_name}
36129
self.scan_analyzer = Array2DScanAnalyzer(
37-
device_name=self.dev_name,
38-
image_analyzer=EBeamProfileAnalyzer(**config_dict)
39-
)
130+
device_name=self.dev_name,
131+
image_analyzer=EBeamProfileAnalyzer(**config_dict),
132+
)
40133

41134
# use live_analysis option for the scan_analyzer so that it knows not to try to load
42135
# data from an sFile already written to disk (which doesn't happen until the end of scan)
43136
self.scan_analyzer.live_analysis = True
44137
self.scan_analyzer.use_colon_scan_param = False # default is file-style
45138

46-
self.output_key = 'f' # string name of optimization function defined in config, don't change
47-
self.objective_tag: str = 'PlaceHolder' # string to append to logged objective value
139+
self.output_key = (
140+
"f" # string name of optimization function defined in config, don't change
141+
)
142+
self.objective_tag: str = (
143+
"PlaceHolder" # string to append to logged objective value
144+
)
48145

49-
def evaluate_all_shots(self, shot_entries: list[dict]) -> list[float]:
146+
def evaluate_all_shots(self, shot_entries: list[dict]) -> float:
50147
"""
51-
Evaluate the objective function for a list of shots.
148+
Evaluate beam size objective function for all shots in current bin.
52149
53-
Args:
54-
shot_entries (list[dict]): List of shot_entry dicts as produced by _gather_shot_entries.
150+
Processes all shots in the current data bin to compute an aggregate
151+
beam size metric. Uses the scan analyzer to create an averaged image
152+
from all shots, then analyzes the averaged image to extract FWHM
153+
measurements and compute the objective function.
55154
56-
Returns:
57-
list[float]: List of scalar objective values, one per shot.
58-
"""
155+
Parameters
156+
----------
157+
shot_entries : list of dict
158+
List of shot entry dictionaries as produced by _gather_shot_entries.
159+
Each dictionary contains shot number, scalars, and image paths.
160+
161+
Returns
162+
-------
163+
float
164+
Computed objective value combining x and y FWHM measurements
165+
with spatial calibration.
59166
167+
Notes
168+
-----
169+
This method:
170+
1. Sets auxiliary data for the scan analyzer
171+
2. Runs analysis to create averaged image
172+
3. Loads the averaged image from disk
173+
4. Analyzes the averaged image for beam profile
174+
5. Extracts FWHM measurements
175+
6. Computes objective function
176+
7. Logs results for each shot
177+
178+
The objective function is: (x_fwhm * cal)² + (y_fwhm * cal)²
179+
where cal = 24.4e-3 mm/pixel.
180+
181+
Examples
182+
--------
183+
>>> shot_entries = evaluator._gather_shot_entries(...)
184+
>>> objective_value = evaluator.evaluate_all_shots(shot_entries)
185+
>>> print(f"Beam size metric: {objective_value:.3f}")
186+
"""
60187
# set the 'aux' data manually to isolate the current bin to get analyzed by the ScanAnalyzer
61188
self.scan_analyzer.auxiliary_data = self.current_data_bin
62189
self.scan_analyzer.run_analysis(scan_tag=self.scan_tag)
63190

64191
# grab the path to the saved average image from the scan analyzer and load
65-
avg_image_path = self.scan_analyzer.saved_avg_image_paths[self.bin_number]
192+
avg_image_path = self.scan_analyzer.saved_avg_image_paths[self.bin_number]
66193
avg_image = read_imaq_image(avg_image_path)
67194

68195
# run standalone analyis using the image_analyzer, passing the argument that preprocessing
69196
# has already been done, e.g. ROI, background etc.
70-
result = self.scan_analyzer.image_analyzer.analyze_image(avg_image,
71-
auxiliary_data={"preprocessed":True})
197+
result = self.scan_analyzer.image_analyzer.analyze_image(
198+
avg_image, auxiliary_data={"preprocessed": True}
199+
)
72200

73201
# extract the scalar results returned by the image analyzer
74-
scalar_results = result['analyzer_return_dictionary']
202+
scalar_results = result["analyzer_return_dictionary"]
75203

76204
# define keys to extract values to use for the objective function
77-
x_key = f'{self.dev_name}_x_fwhm'
78-
y_key = f'{self.dev_name}_y_fwhm'
205+
x_key = f"{self.dev_name}_x_fwhm"
206+
y_key = f"{self.dev_name}_y_fwhm"
79207

80-
objective_value = self.objective_fn( x = scalar_results[x_key],
81-
y = scalar_results[y_key])
208+
objective_value = self.objective_fn(
209+
x=scalar_results[x_key], y=scalar_results[y_key]
210+
)
82211

83212
for shot_entry in shot_entries:
84-
self.log_objective_result(shot_num=shot_entry['shot_number'],scalar_value=objective_value)
213+
self.log_objective_result(
214+
shot_num=shot_entry["shot_number"], scalar_value=objective_value
215+
)
85216

86217
return objective_value
87218

88219
@staticmethod
89220
def objective_fn(x, y):
90-
calibration = 24.4e-3 # spatial calibration in um/pixel
91-
return (x*calibration)**2 + (y*calibration)**2
221+
"""
222+
Compute beam size objective function from FWHM measurements.
223+
224+
Combines x and y full-width half-maximum measurements with spatial
225+
calibration to produce a single objective metric for optimization.
226+
The function computes the sum of squares of the calibrated FWHM values.
227+
228+
Parameters
229+
----------
230+
x : float
231+
X-direction FWHM measurement in pixels.
232+
y : float
233+
Y-direction FWHM measurement in pixels.
234+
235+
Returns
236+
-------
237+
float
238+
Objective function value: (x*cal)² + (y*cal)² where
239+
cal = 24.4e-3 mm/pixel is the spatial calibration.
240+
241+
Examples
242+
--------
243+
>>> x_fwhm = 50.2 # pixels
244+
>>> y_fwhm = 45.8 # pixels
245+
>>> objective = ALine3SizeEval.objective_fn(x_fwhm, y_fwhm)
246+
>>> print(f"Beam size metric: {objective:.6f}")
247+
248+
Notes
249+
-----
250+
The spatial calibration of 24.4 μm/pixel converts pixel measurements
251+
to physical units. The sum of squares formulation treats x and y
252+
contributions equally and provides a single metric that decreases
253+
as the beam becomes more focused.
254+
"""
255+
calibration = 24.4e-3 # spatial calibration in um/pixel
256+
return (x * calibration) ** 2 + (y * calibration) ** 2
92257

93258
def _get_value(self, input_data: Dict) -> Dict:
259+
"""
260+
Evaluate beam size objective for current optimization step.
261+
262+
This is the main evaluation method called by the optimization framework.
263+
It gathers shot data from the current bin, processes the beam profile
264+
images, and returns the computed objective function value.
265+
266+
Parameters
267+
----------
268+
input_data : dict
269+
Dictionary of input parameter values for the current optimization
270+
step. Not directly used by this evaluator as it operates on
271+
acquired image data.
94272
95-
shot_entries = self._gather_shot_entries( shot_numbers=self.current_shot_numbers,
96-
scalar_variables=self.required_keys,
97-
non_scalar_variables=['UC_ALineEBeam3'])
273+
Returns
274+
-------
275+
dict
276+
Dictionary containing the objective function result with key 'f'.
277+
278+
Examples
279+
--------
280+
>>> input_params = {"focus_strength": 120.5, "beam_energy": 150.0}
281+
>>> result = evaluator._get_value(input_params)
282+
>>> print(result)
283+
{'f': 0.00234}
284+
285+
Notes
286+
-----
287+
This method:
288+
1. Gathers shot entries for the current data bin
289+
2. Processes all shots to compute beam size metric
290+
3. Returns result in format expected by optimization framework
291+
292+
The 'f' key in the returned dictionary corresponds to the objective
293+
function name defined in the optimization configuration.
294+
"""
295+
shot_entries = self._gather_shot_entries(
296+
shot_numbers=self.current_shot_numbers,
297+
scalar_variables=self.required_keys,
298+
non_scalar_variables=["UC_ALineEBeam3"],
299+
)
98300

99301
result = self.evaluate_all_shots(shot_entries)
100302

101-
return {self.output_key: result}
303+
return {self.output_key: result}

0 commit comments

Comments
 (0)