Skip to content
Snippets Groups Projects
Commit 563b904f authored by Benedikt Burger's avatar Benedikt Burger
Browse files

Add base function as a base class for function windows.

parent 1bd568d3
No related branches found
No related tags found
No related merge requests found
import time
from typing import Any, Optional, Protocol
from .utils import Position, gui, scale, get_content_complete, set_value
from .main_window import Functions, open_function
class BaseFunction(Protocol):
"""Base class for a function window."""
_function: Functions
# Positions
_run_pos: Position # of the run field
_result_pos: Position # of the results field
_close_pos: Position
_configuration_pos: dict[str, list[Position]] # of the configuration fields
def open(self) -> None:
"""Open the function."""
open_function(self._function)
def run(self) -> None:
"""Click 'Run'."""
gui.click(*scale(*self._run_pos))
def close(self) -> None:
"""Click 'x'."""
self.open()
gui.click(*scale(*self._close_pos))
def configure(
self, data: Optional[dict[str, Any | list[Any] | tuple[Any, ...]]] = None
) -> None:
"""Configure the values and leave the config window open.
If any value is "None", that field will not be changed. This is useful, if you want to
change a single value in a row.
For example `data={'Wavelengths (nm)': [1064.5, None, None]}` will set the first wavelength
to 1064.5 nm while leaving the other wavelengths untouched.
"""
self.open()
if data is None:
return
for key, value in data.items():
positions = self._configuration_pos[key]
if isinstance(value, (list, tuple)):
for i, val in enumerate(value):
if val is not None:
set_value(positions[i], val)
else:
if value is not None:
set_value(positions[0], value)
def get_configuration(self) -> dict[str, list[float | str]]:
"""Read the current configuration."""
self.open()
data = {}
for key, positions in self._configuration_pos.items():
d = []
for pos in positions:
val = get_content_complete(pos)
try:
d.append(float(val))
except ValueError:
d.append(val)
data[key] = d
return data
def read_results(self) -> list[str]:
return get_content_complete(self._result_pos).split("\r\n")
def interpret_results(self, rows: list[str]) -> dict[str, Any]:
"""Interpret the results and return them as a dictionary."""
data = {}
for row in rows:
text, values = row.split("=")
content = []
for element in values.split():
try:
val = float(element)
except ValueError:
val = element
content.append(val)
data[text.strip()] = content
return data
def run_and_read(
self,
waiting_time: float = 1,
max_tries: int = 10,
interval: float = 0.5,
waiting_line_count: int = 3,
) -> dict[str, Any]:
"""Run an analysis and return the result."""
self.run()
time.sleep(waiting_time)
for _ in range(max_tries):
rows = self.read_results()
if len(rows) > waiting_line_count:
break
time.sleep(interval)
# interpret results and save as dictionary:
return self.interpret_results(rows)
def configure_run_read(
self, data: Optional[dict[str, Any]] = None, **kwargs
) -> dict[str, Any]:
"""Configure and run an analysis and return the result."""
self.configure(data)
return self.run_and_read(**kwargs)
def generate_position_dict(
first_position: Position,
configuration_names: list[str],
columns: int = 3,
column_distance: int = 60,
) -> dict[str, list[Position]]:
"""Generate a position dictionary for functions.
This utility makes it easier to generate a position matrix with three fields per entry.
Adjust it afterwards according to the number of entries.
:param first_position: Position (tuple) of the top left configuration field.
:param configuration_names: List of all the names (in order) of the configuration.
"""
# Notes regarding positions for mix: horizontal distance is 60 px, vertical distance 16 pixels
positions = {}
i = 0
for name in configuration_names:
positions[name] = [
(first_position[0] + j * column_distance, first_position[1] + 16 * i)
for j in range(columns)
]
i += 1
return positions
import time
from typing import Any, Optional
from typing import Any, Optional, Protocol
from .utils import Position, gui, scale, set_value, get_content_complete
from .main_window import Functions, open_function
from .utils import Position, gui, scale
from .base_function import BaseFunction
class MixMethods:
class MixMethods(BaseFunction, Protocol):
"""Parent class for mix methods.
Subclass it for specific methods. You should define the positions and the result interpretation.
"""
_function: Functions
# Positions
_accept_pos: Position
_run_pos: Position
_change_inputs_pos: Position
_result_pos: Position # of the results field
_configuration_pos: dict[str, list[Position]] # of the configuration fields
def open(self) -> None:
"""Open the function."""
open_function(self._function)
def accept(self) -> None:
"""Click 'Accept'."""
gui.click(*scale(*self._accept_pos))
def run(self) -> None:
"""Click 'Run'."""
gui.click(*scale(*self._run_pos))
def change_inputs(self) -> None:
"""Click 'Change Inputs'."""
gui.click(*scale(*self._change_inputs_pos))
def configure(self, data: Optional[dict[str, Any]] = None) -> None:
"""Configure the values and leave the config window open.
If any value is "None", that field will not be changed. This is useful, if you want to
change a single value in a row.
For example `data={'Wavelengths (nm)': [1064.5, None, None]}` will set the first wavelength
to 1064.5 nm while leaving the other wavelengths untouched.
"""
self.open()
if data is None:
return
for key, value in data.items():
positions = self._configuration_pos[key]
for i, val in enumerate(value):
if val is not None:
set_value(positions[i], val)
def get_configuration(self) -> dict[str, Any]:
"""Read the current configuration."""
self.open()
data = {}
for key, positions in self._configuration_pos.items():
d = []
for pos in positions:
val = get_content_complete(pos)
try:
d.append(float(val))
except ValueError:
d.append(val)
data[key] = d
return data
def interpret_results(self, rows: list[str]) -> dict[str, Any]:
"""Interpret the results and return them as a dictionary."""
data = {}
for row in rows:
text, values = row.split("=")
data[text.strip()] = [float(i) for i in values.split()]
return data
def read_results(self) -> list[str]:
return get_content_complete(self._result_pos).split("\r\n")
def run_and_read(
self,
waiting_time: float = 1,
max_tries: int = 10,
interval: float = 0.5,
successful_line_count: int = 3,
) -> dict[str, Any]:
"""Run an analysis and return the result."""
self.run()
time.sleep(waiting_time)
for _ in range(max_tries):
rows = self.read_results()
if len(rows) > successful_line_count:
break
time.sleep(interval)
# interpret results and save as dictionary:
return self.interpret_results(rows)
def configure_run_read(
self, data: Optional[dict[str, Any]] = None, **kwargs
) -> dict[str, float | list[float]]:
......@@ -104,26 +28,3 @@ class MixMethods:
self.configure(data)
self.accept()
return self.run_and_read(**kwargs)
def generate_position_dict(
first_configuration_field: Position, configuration_names: list[str]
) -> dict[str, list[Position]]:
"""Generate a position dictionary for mix methods.
This utility makes it easier to generate a position matrix with three fields per entry.
Adjust it afterwards according to the number of entries.
:param first_configuration_field: Position (tuple) of the top left configuration field.
:param configuration_names: List of all the names (in order) of the configuration.
"""
# Notes regarding positions: horizontal distance is 60 px, vertical distance 16 pixels
positions = {}
i = 0
for name in configuration_names:
positions[name] = [
(first_configuration_field[0], first_configuration_field[1] + 16 * i),
(first_configuration_field[0] + 60, first_configuration_field[1] + 16 * i),
(first_configuration_field[0] + 120, first_configuration_field[1] + 16 * i),
]
return positions
from typing import Any
from .base_function import BaseFunction, Functions
class RefractiveIndex(BaseFunction):
_function = Functions.REF_INDEX
_run_pos = (295, 187)
_result_pos = (160, 260)
_close_pos = (365, 41)
_configuration_pos = {
"Crystal": [(170, 90)],
"Temperature": [(290, 90)],
"theta": [(170, 146)],
"phi": [(300, 146)],
"Wavelength": [(170, 190)],
}
def run_and_read(
self,
waiting_time: float = 0.1,
max_tries: int = 10,
interval: float = 0.1,
waiting_line_count: int = 3,
) -> dict[str, Any]:
return super().run_and_read(waiting_time, max_tries, interval, waiting_line_count)
def refractive_indices(
self, Crystal=None, Temperature=None, theta=None, phi=None, Wavelength=None
) -> list[float]:
kwargs = {
"Crystal": Crystal,
"Temperature": Temperature,
"theta": theta,
"phi": phi,
"Wavelength": Wavelength,
}
results = self.configure_run_read(kwargs)
return results["Refractive index (o,e)"]
......@@ -8,6 +8,7 @@ class TwoDMixLP(MixMethods):
_function = Functions.TWOD_MIX_LP
_accept_pos = (506, 520)
_run_pos = (140, 220)
_close_pos = (540, 140)
_change_inputs_pos = (373, 166)
_result_pos = (133, 293)
......@@ -31,7 +32,7 @@ class TwoDMixLP(MixMethods):
"beta blue (cm/W)": [(400, 420), (460, 420), (520, 420)],
"Walkoff angles (mrad)": [(400, 440), (460, 440), (520, 440)],
"Offset in wo dir. (mm)": [(400, 453), (460, 453), (520, 453)],
"Rad. curv. (mm/air´)": [(400, 473), (460, 473), (520, 473)],
"Rad. curv. (mm/air)": [(400, 473), (460, 473), (520, 473)],
"# of integ/grid points": [(400, 486), (460, 486), (520, 486)],
"Crystal/grid sizes (mm)": [(400, 500), (460, 500), (520, 500)],
"Deff (pm/V)": [(400, 520)],
......
......@@ -8,6 +8,7 @@ class TwoDMixSP(MixMethods):
_function = Functions.TWOD_MIX_SP
_accept_pos = (506, 600)
_run_pos = (140, 200)
_close_pos = (540, 140)
_change_inputs_pos = (150, 266)
_result_pos = (133, 293)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment