4.14. GUI Automation¶
Some feasible automation tasks include:
changing the geometry
changing camera views accurately
displaying specific rays
picking specific rays
debugging
taking automated screenshots
calling functions with settings not available through the GUI
…
4.14.1. The Control Method¶
To do automated tasks, use the TraceGUI.control method
instead of TraceGUI.run.
It requires an automation function as well as an argument tuple.
def automated(GUI):
# do some automation tasks
...
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
After the method is executed, the GUI keeps running normally. This makes it possible to interact with the GUI. The process of closing the application automatically is described in Section 4.14.10
All actions inside the provided automation function run sequentially to avoid race conditions. During this time, the GUI is mostly unresponsive, as the automation function is run in the main thread (as user input would also be).
4.14.2. Applying Properties¶
Available TraceGUI properties are discussed in Section 4.13.3. All of these can be set programmatically.
While the variables are set,
the TraceGUI does not execute the functions or actions that react to these changes instantly.
This is in contrast to the standard TraceGUI.run method,
where a setting is applied subsequently
(for instance, changing the ray color setting updates the color in the scene automatically).
To process all pending events TraceGUI.process must be called.
This is not needed after every property change,
but only if the changes should be visible/executed at this point in time.
def automated(GUI):
# change properties
GUI.minimalistic_view = True
GUI.hide_labels = True
GUI.ray_count = 1000
# GUI properties were set, but the changes need to be processed
GUI.process()
# do some other things
...
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
Note that some functions, like TraceGUI.replot,
also call TraceGUI.process internally.
4.14.3. Replotting¶
While TraceGUI.process reacts to changes in the TraceGUI itself,
it does not handle changes of the raytracer or tracing geometry.
When changing the geometry, the changes are not automatically applied to the scene. The geometry is also not automatically raytraced.
To force the redrawing and retracing of the full scene,
you can call TraceGUI.replot.
It is possible to only update changed objects
with the context manager TraceGUI.smart_replot.
For instance, if a detector is moved,
there is no need for updating the lenses inside the geometry or retracing the scene.
TraceGUI.smart_replot
handles the detection of changes and updating automatically.
Here is an example:
def automated(GUI):
# replot everything
GUI.replot()
# do some actions and at the end replot only changed objects
# and/or retrace the geometry if needed.
with GUI.smart_replot():
some_action_1()
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
4.14.4. Controlling the Camera¶
You can control the camera with the functions TraceGUI.set_camera
and TraceGUI.get_camera.
The former sets the properties, while the latter one returns a dictionary of the current settings.
The following settings are available:
Property |
Description |
|---|---|
|
3D coordinates of center of view in mm |
|
half of vertical visible scene height in mm |
|
camera view direction vector (direction of vector perpendicular to your monitor and in your viewing direction) |
|
absolute camera roll angle in degrees |
You can find example code below:
def automated(sim):
# store initial camera properties
cam_props = sim.get_camera()
# change the center of the view as well as the scaling
sim.set_camera(center=[1, -0.5, 2], height=2.5)
# reset to initial view
sim.set_camera(**cam_props)
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
Applying camera properties at startup is possible using the initial_camera parameter of the TraceGUI class.
This parameter is a dictionary that can include all possible parameters of function TraceGUI.set_camera.
sim = TraceGUI(RT, initial_camera=dict(direction=[0, 1, 0], roll=45))
4.14.5. Taking Screenshots¶
The TraceGUI.screenshot function captures screenshots of the scene.
A path string is required for this function.
The file type is determined automatically from the file name.
Internally, the pyvista.Plotter.screenshot function from pyvista is utilized,
you can therefore use this function’s additional parameters.
def automated(sim):
# default call
sim.screenshot("image.png")
# call with additional parameters
sim.screenshot("image2.png", magnification=2)
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
Note that the magnification parameter leads to a rescaled scene,
where many annotation elements change their relative size.
4.14.6. Selecting Rays¶
By default, a random selection of rays is displayed inside the scene,
where the number is specified by TraceGUI.rays_visible.
A custom selection is set with the function TraceGUI.select_rays.
It takes a mask parameter, which is a one-dimensional boolean numpy.array,
and an optional max_show parameter, that specified the maximum amount of rays to display.
Parameter mask must have the same length as there are rays simulated,
which is set by TraceGUI.ray_count
Note that there is a maximum amount of rays that can be displayed
(specified by the maximum value of TraceGUI.rays_visible,
by default 50000).
If the mask includes more values, a random subset is selected.
Accessing TraceGUI.ray_selection
returns the boolean array for the currently displayed rays.
Typical useful scenarios are debugging or ray analysis. For instance, only rays from a specific source, region or wavelength range can be selected and displayed. See Accessing Ray Properties to learn how to access ray properties. You can find examples for ray selections below.
def automated(GUI):
# display rays with wavelengths between 400 and 450nm
mask = (GUI.raytracer.rays.wl_list >= 400) & (GUI.raytracer.rays.wl_list <= 450)
GUI.select_rays(mask) # no max_show provided, but might be limited by this function
# display 2000 rays that start at x > 0
mask = GUI.raytracer.rays.p_list[:, :, 0] > 0
GUI.select_rays(mask[:, 0], 2000) # slicing with 0 so mask is 1D
# get the mask for actually displayed selection
selection = GUI.ray_selection
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
4.14.7. Picking Manually¶
The function TraceGUI.pick_ray highlights a full ray.
An integer index is required as to select a given ray.
Only currently displayed rays are pickable,
which are defined by TraceGUI.ray_selection,
see Selecting Rays.
So an index=50 means that the 51th True value
of TraceGUI.ray_selection is picked.
Function TraceGUI.pick_ray_section
highlights a ray at a given intersection.
The ray is highlighted, a crosshair is shown at the intersection position
and a ray information text is displayed inside the scene.
Compared to the previous function, an additional integer section parameter is required.
An optional parameter detailed defines if more detailed information should be shown.
This is equivalent to picking a ray section manually with Shift + Click.
Call TraceGUI.reset_picking
to deactivate the ray highlighting, information text, and cross hair.
Here is an example:
def automated(sim):
# pick the ray with index 100
sim.pick_ray(index=100)
# pick ray section 2 of ray 50 with default view
sim.pick_ray_section(index=50, section=2)
# pick ray section with detailed view
sim.pick_ray_section(index=50, section=2, detailed=True)
# reset (=hide) the picking view
sim.reset_picking()
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
4.14.8. Showing Plots¶
Available plotting functions include TraceGUI.source_image,
TraceGUI.source_profile,
TraceGUI.detector_image,
TraceGUI.detector_profile,
TraceGUI.detector_spectrum,
TraceGUI.source_spectrum,
and TraceGUI.move_to_focus.
There are more parameters available through these direct calls than through the GUI. For example, it is possible to save an image to the disk. Additionally, a custom detector/source extent can be specified, a setting not available through the GUI.
def automated(sim):
# change plot settings
sim.image_pixels = 315
sim.image_mode = "Lightness (CIELUV)"
# show a source profile with a user-defined extent
sim.source_profile(extent=[0, 0.1, 0.2, 0.25])
# save a detector image with higher dpi
sim.detector_image(path="detector.png", sargs=(dpi=600))
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
4.14.9. Accessing custom UI elements¶
Custom elements (see Section 4.13.3.5) are accessible through a name, consisting of their type and a chronological number. The number corresponds to the order that the element has been created. Assigning values works analogously to all other parameters. To button action is called with a specific function.
You can find examples below.
sim.custom_value_2 = 4.5
sim.custom_checkbox_1 = False
sim.custom_selection_3 = "Case 2"
sim.custom_button_action_1()
4.14.10. Closing Down¶
Call the function TraceGUI.close to close the GUI down programmatically.
def automated(sim):
# do some things
...
# close everything down
sim.close()
# create the GUI and provide the automation function to TraceGUI.control()
sim = TraceGUI(RT)
sim.control(func=automated, args=(sim,))
This will not only end the GUI, but all other plots and subwindows.