"""This module contains the API of the Allied Vision Goldeye Camera.
It has been modified in order to allow both streaming and recording. It
allows processing of the frames without the need of writting them first.
"""
# C:\Users\alda_ik\Documents\04_PROGRAMMING\02_FINAL_PROJECT\api.py
from vmbpy import * # noqa: F403
from typing import Optional
from datetime import datetime, timedelta
from tkinter import ttk
from matplotlib import dates as mdates
import sys
import os
import threading
import cv2
import math
import pytz
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from . import image_processing as im
from . import constants as const
from . import printer as pri
# All frames will either be recorded in this format, or transformed to it before being displayed
opencv_display_format = PixelFormat.Bgr8 # noqa: F405
[docs]
def feature_changed_handler(feature) -> None:
"""API propietary printing fuction to indicate a changed of value of a feature.
Args:
feature (_type_): Feature to be changed.
"""
msg = "Feature '{}' changed value to '{}'"
print(msg.format(str(feature.get_name()), str(feature.get())), flush=True)
[docs]
def abort(reason: str, return_code: int = 1, usage: bool = False) -> None:
"""API propietary exiting fuction and indicate an error.
Args:
reason (str): Reason to abort operation.
return_code (int, optional): Error code raised. Defaults to 1.
usage (bool, optional): Bool to check if an argument has been parsed. Defaults to False.
"""
print(reason + "\n")
if usage:
pri.print_usage()
sys.exit(return_code)
[docs]
def parse_args() -> Optional[str]:
"""API propietary fuction to parse an argument.
Returns:
Optional[str]: Parsed argument.
"""
args = sys.argv[1:]
argc = len(args)
for arg in args:
if arg in ("/h", "-h"):
pri.print_usage()
sys.exit(0)
if argc > 1:
abort(
reason="Invalid number of arguments. Abort.",
return_code=2,
usage=True,
)
return None if argc == 0 else args[0]
[docs]
def get_camera(camera_id: Optional[str]) -> Camera: # noqa: F405
"""API propietary fuction to obtain the camera.
Args:
camera_id (Optional[str]): Specific camera we want to connect to.
Returns:
Camera: Camera we have connected to.
"""
with VmbSystem.get_instance() as vmb: # noqa: F405
# ip = vmb.GevDeviceForceIPAddress.get()
if camera_id:
try:
return vmb.get_camera_by_id(camera_id)
except VmbCameraError: # noqa: F405
abort("Failed to access Camera '{}'. Abort.".format(camera_id))
else:
cams = vmb.get_all_cameras()
if not cams:
abort("No Cameras accessible. Abort.")
return cams[0]
[docs]
def setup_camera(
cam: Camera, # noqa: F405
gain: tk.StringVar,
exposure: tk.StringVar,
exposure_time_value: int,
iso: tk.StringVar,
) -> None:
"""Configures the camera based on user inputs:
1. Checks camera mode, if the own camera's background is chosen, it needs be enabled.
2. Sets the gain mode, either 0dB or 18dB.
3. Selects the exposure mode, Auto or Manual. If Manual, sets the exposure value.
4. Enables white balancing if camera supports it.
5. Adjusts GeV packet size (just for PoE camera).
Args:
cam (Camera): Camera object from the VMBPY API module.
gain (tk.StringVar): Container of the gain mode (0 [0 dB] or 1 [18 dB]).
exposure (tk.StringVar): Container of the exposure mode (Manual or Auto).
exposure_time_value (int): Specified exposure value for Manual mode.
iso (tk.StringVar): Container of the camera mode: Normal, Hot-pixel substraction, Subtraction or Camera's BC.
"""
with cam:
# print(cam.LUTEnable)
# lut = cam.LUTEnable.get()
# print(cam.LUTEnable.get())
# # lut = True
# lut = True
# print(cam.LUTEnable)
# print(lut)
# cam.LUTEnable.set(lut)
# print(cam.LUTEnable)
# print(cam.LUTEnable)
# cam.LUTEnable.value(True)
# print(cam.LUTEnable)
# Default settings
cam.BCMode.set("Off")
cam.IntegrationMode.set("IntegrateWhileRead")
# cam.IntegrationMode.set("IntegrateThenRead")
print(cam.IntegrationMode)
# Background Correction settings
if iso.get() == "Camera's BC":
cam.BCMode.set("On")
cam.BCIntegrationStart.run()
print(cam.BCMode)
# lut_enable = cam.LUTEnable
# print(lut_enable)
# lut_enable.set(True)
# print(lut_enable)
# Gain settings
if gain.get() == "1 [18 dB]":
try:
cam.SensorGain.set("Gain1")
except (AttributeError, VmbFeatureError): # noqa: F405
pass
else:
try:
cam.SensorGain.set("Gain0")
except (AttributeError, VmbFeatureError): # noqa: F405
pass
print(cam.SensorGain)
# Exposure settings
if exposure.get() == "Manual":
try:
cam.ExposureAuto.set("Off")
exposure_time = cam.ExposureTime
exposure_time.set(exposure_time_value)
except (AttributeError, VmbFeatureError): # noqa: F405
pass
else:
try:
cam.ExposureAuto.set("Continuous")
except (AttributeError, VmbFeatureError): # noqa: F405
pass
# White balancing settings
try:
cam.BalanceWhiteAuto.set("Continuous")
except (AttributeError, VmbFeatureError): # noqa: F405
pass
# GeV packet size settings (only available for GigE Cameras)
try:
stream = cam.get_streams()[0]
stream.GVSPAdjustPacketSize.run()
while not stream.GVSPAdjustPacketSize.is_done():
pass
except (AttributeError, VmbFeatureError): # noqa: F405
pass
[docs]
def upload_lut(
cam: Camera, # noqa: F405
lut_dataset_selector_index: int,
gain: tk.StringVar,
) -> None: # noqa: F405
"""Uploads and enables the LUT:
1. Checks the gain to select the correct LUT path.
2. Opens the LUT file, loads it into the camera and runs it.
3. Prints the directory and selected LUT.
Args:
cam (Camera): Camera object from the VMBPY API module.
lut_dataset_selector_index (int): Index of the selected LUT.
gain (tk.StringVar): Container of the gain mode for choosing the LUT (0 [0 dB] or 1 [18 dB]).
"""
# Set directory
if gain.get() == "1 [18 dB]":
dir = const.LUT_DIR1
else:
dir = const.LUT_DIR0
# Read LUT, upload to camera
with cam:
with open(dir, mode="rb") as file:
fileContent = file.read()
cam.LUTDatasetSelector.set(lut_dataset_selector_index)
cam.LUTValueAll.set(fileContent)
cam.LUTDatasetSave.run()
print(
f"LUT from file {dir} loaded into LUT Nr.{lut_dataset_selector_index}."
)
[docs]
def grab_frame(cam: Camera) -> None: # noqa: F405
"""Captures a frame to be used as a temporal frame:
1. Grabs a frame from the camera.
2. Saves the frame in the specified directory.
Args:
cam (Camera): Camera object from the VMBPY API module.
"""
frame = cam.get_frame()
cv2.imwrite(const.BACKGROUND_FRAME_DIR, frame.as_opencv_image())
[docs]
class Handler:
"""Handles the mayority of the camera operation.
Methods:
__init__: Initializes the Handler class.
update: Updates the live graph of the GUI.
save_plot: Saves the plot stored in the instance.
create_camera_control_slider: Creates an slider, saves it in the instance.
set_exposure: Updates the exposure value.
set_min_max_value: Updates the minimum or maximum spot size value.
__call__: Between each frame, sends the frame for
processing, prepares for the next one, and checks if the program has stopped.
"""
[docs]
def __init__(
self,
cam: Camera, # noqa: F405
exposure_time_value: int,
check: tk.StringVar,
light: tk.StringVar,
gain: tk.StringVar,
iso: tk.StringVar,
payload: tk.StringVar,
elevation_in: tk.StringVar,
fig: plt.figure,
ax: plt.axes,
line: plt.hlines,
xdata: list,
ydata: list,
el_lb,
int_lb,
):
"""Initializes the Handler class with the specified camera and plotting
parameters, and creates a directory for storing the frames:
1. Sets up all necessary instances based on the chosen settings.
2. Creates the directory for saving frames according to the specified settings
(will be saves in the data directory on the root folder of the project,
inside a folder called tracking_images).
Args:
self (Instance): Current instance, provides access to attributes and methods.
cam (Camera): Camera object from the VMBPY API module.
exposure_time_value (int): Specified exposure value for Manual mode.
check (tk.StringVar): Container of the streaming mode.
light (tk.StringVar): Container of the time of day.
gain (tk.StringVar): Container of the gain mode (0 [0 dB] or 1 [18 dB]).
iso (tk.StringVar): Container of the camera mode: Normal, Hot-pixel substraction, Subtraction or Camera's BC.
payload (tk.StringVar): Container of the payload.
elevation_in (tk.StringVar): Container of the elevation mode.
fig (plt.figure): Figure for the GUI plot.
ax (plt.axes): Axes for the GUI plot.
line (plt.hlines): Lines for the GUI plot
xdata (list): Data for the x-axis for the GUI plot
ydata (list): Data for the y-axis for the GUI plot
el_lb (numpy.ndarray): Elevation values from the link_budget
int_lb(numpy.ndarray): Intensity values from the link budget
:no-index:
"""
# Setting up instances
self.shutdown_event = threading.Event()
self.cam = cam
self.exposure_slider = None
self.min_value = 1
self.max_value = const.H_SENSOR_SIZE * const.V_SENSOR_SIZE
self.root = None
# Input instances
self.gain = 1
self.counter = 0
self.background = 0
self.mode = 0
self.cond = 0
# Graph instances
self.fig = fig
self.ax = ax
self.line = line
self.xdata = xdata
self.ydata = ydata
self.el_lb = el_lb
self.int_lb = int_lb
# Graph's update
self.ani = animation.FuncAnimation(
self.fig,
self.update_graph,
interval=100,
blit=False,
cache_frame_data=False,
)
# Directory where the tracking frames get saved: directory of script.
d = r".\data"
self.elev_mod = elevation_in.get()
self.payload = payload.get()
if gain.get() == "1 [18 dB]":
self.gain = 1
else:
self.gain = 0
if iso.get() == "Hot-pixel substraction":
self.background = 3
elif iso.get() == "Subtraction":
self.background = 1
elif iso.get() == "Camera's BC":
self.background = 2
else:
self.background = 0
if light.get() == "Daytime":
self.cond = 0
else:
self.cond = 1
if check.get() == "Record":
# Directory creation
self.mode = 1
now = datetime.now()
if exposure_time_value == 1:
foldername_NP = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_AUTO_GAIN{self.gain}_NP"
)
if self.background == 0:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}"
f"_exp_AUTO_GAIN{self.gain}"
)
elif self.background == 1:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_AUTO_GAIN{self.gain}_IS"
)
elif self.background == 2:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_AUTO_GAIN{self.gain}_CAMERA_BC"
)
else:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_AUTO_GAIN{self.gain}_HP"
)
else:
foldername_NP = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_{exposure_time_value}us_GAIN{self.gain}_NP"
)
if self.background == 0:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_{exposure_time_value}us_GAIN{self.gain}"
)
elif self.background == 1:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_{exposure_time_value}us_GAIN{self.gain}_IS"
)
elif self.background == 2:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_{exposure_time_value}us_GAIN{self.gain}_CAMERA_BC"
)
else:
foldername = (
f"tc_{now.strftime('%Y')}{now.strftime('%m')}{now.strftime('%d')}_"
f"{now.strftime('%H')}{now.strftime('%m')}{now.strftime('%S')}_"
f"exp_{exposure_time_value}us_GAIN{self.gain}_HP"
)
if elevation_in.get() == "Full" and self.payload != "None":
foldername = foldername + "_" + self.payload
foldername_NP = foldername_NP + "_" + self.payload
self.pnp = os.path.join(d, "tracking_images", foldername_NP)
self.p = os.path.join(d, "tracking_images", foldername)
os.makedirs(self.p)
os.makedirs(self.pnp)
[docs]
def update_graph(self, frame: Frame) -> plt.hlines: # noqa: F405
"""Updates the live graph of the GUI with new data stored in the current instance (self):
1. If data is available, plots it on the graph.
2. Adjusts the graph's horizontal and vertical limits, extends the x-axis by 60
seconds, and increase the y-axis limit by 10% of the maximum value.
3. Updates the graph's format as needed.
4. Plots the new data on the graph.
Args:
self (Instance): Current instance, provides access to attributes and methods.
frame (Frame): Frame object from the VMBPY API module.
Returns:
plt.hlines: Updated graph instance.
:no-index:
"""
if not self.xdata: # If no data, just return
return (self.line,)
# Ensure xdata is in UTC
self.xdata = [x.astimezone(pytz.UTC) for x in self.xdata]
self.line.set_data(self.xdata, self.ydata)
ax = self.ax
# Update axis limits
ax.set_xlim(
self.xdata[0],
max(self.xdata[-1], self.xdata[0] + timedelta(seconds=60)),
)
if self.ydata:
ax.set_ylim(0, max(255, max(self.ydata) * 1.1))
# Update the formatter to show appropriate range
locator = mdates.AutoDateLocator()
# formatter = mdates.AutoDateFormatter(locator)
formatter = mdates.DateFormatter("%H:%M:%S", tz=mdates.UTC)
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)
self.fig.canvas.draw()
return (self.line,)
[docs]
def save_plot(self) -> None:
"""Saves the plot stored in the current instance (self):
1. If recording mode is selected, sets the plot size, data, title, and labels.
2. Adjust the format of the time as H:M:S.
3. If a payload is chosen, its name will be added to the plot's title.
4. Saves the plot in the same directory as the recorded frames (stored in the
instance).
Args:
self (Instance): Current instance, provides access to attributes and methods.
:no-index:
"""
if self.mode != 0: # Only save if in recording mode
plt.figure(figsize=(20, 8))
plt.plot(self.xdata, self.ydata)
# Enable the grid
plt.grid(True)
# Format the x-axis ticks
time_format = mdates.DateFormatter("%H:%M:%S")
plt.gca().xaxis.set_major_formatter(time_format)
plt.xlabel("Time [UTC]")
plt.ylabel("Pixel Value [DN]")
plt.gcf().autofmt_xdate() # Rotate and align the tick labels
# Adjust layout manually for a tighter fit, but not as tight as plt.tightlayout()
plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.1)
# plt.tight_layout()
if self.payload != "None":
plt.title(
f"{self.payload} downlink on {datetime.now().strftime('%Y-%m-%d')}"
)
plt.savefig(
f"{self.p}/{datetime.now().strftime('%Y-%m-%d')}_{self.payload}_DL_plot.png"
)
else:
plt.title(f"Downlink on {datetime.now().strftime('%Y-%m-%d')}")
plt.savefig(
f"{self.p}/{datetime.now().strftime('%Y-%m-%d')}_DL_plot.png"
)
plt.close()
[docs]
def create_camera_control_slider(self, root: tk.Tk) -> None:
"""Creates an slider, saves it in the current instance (self) and sets its
initial values:
1. Creates a slider within the current instance using the CameraControlSlider
class.
2. Sets the initial values of the minimum and maximum spot size and the
exposure with set_initial_values().
Args:
self (Instance): Current instance, provides access to attributes and methods.
root (tk.Tk): Main window of the GUI menu.
:no-index:
"""
self.camera_control_slider = CameraControlSlider(root, self)
current_exposure = self.cam.ExposureTime.get()
self.camera_control_slider.set_initial_values(
current_exposure, self.min_value, self.max_value
)
[docs]
def set_exposure(self, exposure_time: int) -> None:
"""Updates the exposure value stored in the current instance (self).
Args:
self (Instance): Current instance, provides access to attributes and methods.
exposure_time (int): Exposure time value.
:no-index:
"""
try: # Tries to update the exposure time
self.cam.ExposureTime.set(exposure_time)
except (AttributeError, VmbFeatureError): # noqa: F405
print("Failed to set exposure time")
[docs]
def set_min_max_value(self, value: int, siz: int) -> None:
"""Updates the minimum or maximum spot size value stored in the current
instance (self).
Args:
self (Instance): Current instance, provides access to attributes and methods.
value (int): Final minimum or maximum spot size value.
siz (int): Integer to differenciate if we want to change the minimum or the maximum spot size value,
1 for the maximum, 0 for minimum.
:no-index:
"""
if siz == 1: # Max value
self.max_value = value
print(f"Max value set to: {value}")
else:
self.min_value = value
print(f"Min value set to: {value}")
[docs]
def __call__(self, cam: Camera, stream: Stream, frame: Frame) -> None: # noqa: F405
"""Between each frame, sends the current frame for processing, prepares for
the next one, and checks if the program has stopped:
1. If program shutdown is activated, exits the fuction.
2. If a frame has been grabbed, increments the counter and starts image
processing.
3. Prepares for the next frame and checks if program is stopped (by
pressing q / Q)
Args:
self (Instance): Current instance, provides access to attributes and methods.
cam (Camera): Camera object from the VMBPY API module.
stream (Stream): Stream object from the VMBPY API module.
frame (Frame): Frame object from the VMBPY API module.
:no-index:
"""
if self.shutdown_event.is_set():
return
if frame.get_status() == FrameStatus.Complete: # noqa: F405
self.counter += 1
# Image Processing fuction
im.frame_processing(self, cam, frame)
cam.queue_frame(frame)
# Check for 'q' key press to stop recording
if cv2.waitKey(1) & 0xFF == ord("q") or cv2.waitKey(1) & 0xFF == ord(
"Q"
):
self.shutdown_event.set()
if self.root:
self.root.quit()
[docs]
class CameraControlSlider(tk.Toplevel):
"""Handles the sliders of the GUI menu.
Methods:
__init__: Initializes the CameraControlSlider class.
setup_exposure_slider: Creates the exposure slider.
setup_min_value_slider: Creates the minimum spot size value slider.
setup_max_value_slider: Creates the maximum spot size value slider
update_exposure: Updates the exposure value.
update_min_value: Updates the minimum spot size value.
update_max_value: Updates the maximum spot size value.
update_from_exposure_entry: Updates the exposure value parsed through the button.
update_from_min_entry: Updates the minimum spot size value parsed through the button.
update_from_max_entry: Updates the maximum spot size value parsed through the button.
set_initial_values: Sets the intial values for all the varibales of the sliders.
Args:
tk (tk.Toplevel): Window where the slider should be created.
"""
[docs]
def __init__(self, master: tk.Tk, camera_control: Handler):
"""Initializes the CameraControlSlider class with the specified
GUI parameters, and creates the sliders:
1. Sets up all necessary instances for the slider elements in the menu.
2. Configures exposure, minimum, and maximum spot size using the appropriate functions.
Args:
self (Instance): Current instance, provides access to attributes and methods.
master (tk.Tk): Tinker GUI window.
camera_control (Handler): Parsed through handler.
"""
# Allows the child class to invoke the constructor of its parent class.
super().__init__(master)
self.camera_control = camera_control
self.title("Camera Control Sliders")
# Dimensions adn position of the control sliders (WidthxHeight+X+Y).
self.geometry("370x285+900+360")
# Creates a frame to hold min and max sliders side by side
self.min_max_frame = ttk.Frame(self)
self.min_max_frame.pack(fill="x", expand=True, pady=10)
# Slider setup
# Exposure slider
self.setup_exposure_slider()
# Min value slider
self.setup_min_value_slider()
# Max value slider
self.setup_max_value_slider()
[docs]
def setup_exposure_slider(self) -> None:
"""Creates the exposure slider:
1. Initializes all slider elements and converts the exposure to a logarithmic scale.
2. Sets up the displayed exposure value and its slider.
3. Configures the button to manually change the exposure value.
Args:
self (Instance): Current instance, provides access to attributes and methods.
"""
# Initial values are converted to a logarithmic scale
self.min_exposure = 10
self.max_exposure = 3000000 # 1 second
self.log_min_exposure = math.log10(self.min_exposure)
self.log_max_exposure = math.log10(self.max_exposure)
self.current_exposure = tk.DoubleVar(value=self.log_min_exposure)
ttk.Label(self, text="Exposure Control").pack(pady=(10, 0))
self.exposure_label = ttk.Label(
self, text=f"Exposure: {self.min_exposure:.2f} µs"
)
self.exposure_label.pack(pady=(0, 5))
# Exposure slider settings
self.exposure_slider = ttk.Scale(
self,
from_=0,
to=1000,
orient="horizontal",
length=600,
command=self.update_exposure, # Calls the fuction on each value change
variable=self.current_exposure,
)
self.exposure_slider.pack(pady=5, padx=20, fill="x")
self.exposure_entry = ttk.Entry(self, width=10)
self.exposure_entry.pack(pady=5)
self.exposure_entry.bind("<Return>", self.update_from_exposure_entry)
self.exposure_set_button = ttk.Button(
self, text="Set Exposure", command=self.update_from_exposure_entry
)
self.exposure_set_button.pack(pady=5)
[docs]
def setup_min_value_slider(self) -> None:
"""Creates the minimum spot size value slider:
1. Initializes all slider elements and converts the minimum spot size value to a logarithmic scale.
2. Sets up the displayed minimum value and its slider.
3. Configures the button to manually change the minimum spot size value.
Args:
self (Instance): Current instance, provides access to attributes and methods.
"""
self.min_value = 1
self.max_value = const.H_SENSOR_SIZE * const.V_SENSOR_SIZE
self.log_min_value = math.log10(self.min_value)
self.log_max_value = math.log10(self.max_value)
self.current_min = tk.DoubleVar(value=self.log_min_value)
min_frame = ttk.Frame(self.min_max_frame)
min_frame.pack(side="left", expand=True, fill="x", padx=10)
ttk.Label(min_frame, text="Minimum Value Control").pack()
self.min_label = ttk.Label(
min_frame, text=f"Min. Beam Spot Size: {self.min_value:.0f}"
)
self.min_label.pack()
self.min_slider = ttk.Scale(
min_frame,
from_=0,
to=1000,
orient="horizontal",
command=self.update_min_value,
variable=self.current_min,
)
self.min_slider.pack(fill="x")
self.min_entry = ttk.Entry(min_frame, width=10)
self.min_entry.pack()
self.min_entry.bind("<Return>", self.update_from_min_entry)
self.min_set_button = ttk.Button(
min_frame, text="Set Min", command=self.update_from_min_entry
)
self.min_set_button.pack()
[docs]
def setup_max_value_slider(self) -> None:
"""Creates the maximum spot size value slider:
1. Initializes all slider elements and converts the maximum spot size value to a logarithmic scale.
2. Sets up the displayed maximum value and its slider.
3. Configures the button to manually change the maximum spot size value.
Args:
self (Instance): Current instance, provides access to attributes and methods.
"""
self.current_max = tk.DoubleVar(value=self.log_max_value)
max_frame = ttk.Frame(self.min_max_frame)
max_frame.pack(side="right", expand=True, fill="x", padx=10)
ttk.Label(max_frame, text="Maximum Value Control").pack()
self.max_label = ttk.Label(
max_frame, text=f"Max Beam Spot Size: {self.max_value:.0f}"
)
self.max_label.pack()
self.max_slider = ttk.Scale(
max_frame,
from_=0,
to=1000,
orient="horizontal",
command=self.update_max_value,
variable=self.current_max,
)
self.max_slider.pack(fill="x")
self.max_entry = ttk.Entry(max_frame, width=10)
self.max_entry.pack()
self.max_entry.bind("<Return>", self.update_from_max_entry)
self.max_set_button = ttk.Button(
max_frame, text="Set Max", command=self.update_from_max_entry
)
self.max_set_button.pack()
[docs]
def update_exposure(self, event=None) -> None:
"""Updates the exposure value parsed through the slider:
1. Gets the exposure from the slider and converts it to a logarithmic scale.
2. Sends the updated exposure value to the camera.
Args:
self (Instance): Current instance, provides access to attributes and methods.
event (_type_, optional): Nothing, just compatibility purposes. Defaults to None.
"""
log_value = (
self.exposure_slider.get()
/ 1000
* (self.log_max_exposure - self.log_min_exposure)
+ self.log_min_exposure
)
exposure = round(10**log_value)
self.exposure_label.config(text=f"Exposure: {exposure:.2f} µs")
self.camera_control.set_exposure(exposure)
self.exposure_entry.delete(0, tk.END)
self.exposure_entry.insert(0, str(exposure))
[docs]
def update_min_value(self, event=None) -> None:
"""Updates the minimum spot size value parsed through the slider:
1. Gets the minimum spot size value from the slider and converts it to a logarithmic scale.
2. If the miniumum sleected spot size value is bigger than the maximum
spot size value, the maximum spot size value is selected as the minimum
spot size value. If a negative value is selected, it will be converted to 0.
Args:
self (Instance): Current instance, provides access to attributes and methods.
event (_type_, optional): Nothing, just compatibility purposes. Defaults to None.
"""
log_value = (
self.min_slider.get()
/ 1000
* (self.log_max_value - self.log_min_value)
+ self.log_min_value
)
min_value = round(10**log_value)
self.min_value = max(0, min(self.max_value - 1, min_value))
self.min_label.config(text=f"Min Value: {self.min_value:.2f}")
# Updates min value
self.camera_control.set_min_max_value(self.min_value, 0)
self.min_entry.delete(0, tk.END)
self.min_entry.insert(0, str(self.min_value))
[docs]
def update_max_value(self, event=None) -> None:
"""Updates the maximum spot size value parsed through the slider:
1. Gets the maximum spot size value from the slider and converts it to a logarithmic scale.
2. The maximum selected spot size value will be the maximum value between
the minimum spot size value and the smaller value between the maximum spot
size value selected from the slider and whole dimensions of the image.
Args:
self (Instance): Current instance, provides access to attributes and methods.
event (_type_, optional): Nothing, just compatibility purposes. Defaults to None.
"""
log_value = (
self.max_slider.get()
/ 1000
* (self.log_max_value - self.log_min_value)
+ self.log_min_value
)
max_value = round(10**log_value)
self.max_value = max(
self.min_value,
min((const.H_SENSOR_SIZE * const.V_SENSOR_SIZE), max_value),
)
self.max_label.config(text=f"Max Value: {self.max_value:.2f}")
self.camera_control.set_min_max_value(self.max_value, 1)
self.max_entry.delete(0, tk.END)
self.max_entry.insert(0, str(self.max_value))
[docs]
def update_from_exposure_entry(self, event=None) -> None:
"""Updates the exposure value parsed through the button:
1. Gets the exposure value and converts it to a logarithmic scale if it
is in the correct range. If not, it raises an error.
2. Updates the exposure value calling the update_exposure() function.
Args:
self (Instance): Current instance, provides access to attributes and methods.
event (_type_, optional): Nothing, just compatibility purposes. Defaults to None.
Raises:
ValueError: Exposure time inputed in the text-box is either bigger than the
maximum exposure time or smaller than the smallest exposure time.
"""
try:
exposure = float(self.exposure_entry.get())
if self.min_exposure <= exposure <= self.max_exposure:
log_value = (
(math.log10(exposure) - self.log_min_exposure)
/ (self.log_max_exposure - self.log_min_exposure)
* 1000
)
self.exposure_slider.set(log_value)
# Updates the exposure value
self.update_exposure(None)
else:
raise ValueError
except ValueError:
self.exposure_entry.delete(0, tk.END)
self.exposure_entry.insert(
0, str(round(10 ** self.current_exposure.get()))
)
[docs]
def update_from_min_entry(self, event=None) -> None:
"""Updates the minimum spot size value parsed through the button:
1. Gets the minimum spot size value and converts it to a logarithmic scale if
it is bigger than zero but smaller than the maximum value.
2. Updates the minimum spot size value calling the update_min_value() function.
Args:
self (Instance): Current instance, provides access to attributes and methods.
event (_type_, optional): Nothing, just compatibility purposes. Defaults to None.
Raises:
ValueError: Minimum spot size value is either smaller than zero or bigger
than the maximum spot size value.
"""
try:
min_value = int(self.min_entry.get())
if 0 <= min_value < self.max_value:
self.min_value = min_value
log_value = (
(math.log10(max(1, min_value)) - self.log_min_value)
/ (self.log_max_value - self.log_min_value)
* 1000
)
self.min_slider.set(log_value)
# Updates the min spot value
self.update_min_value(None)
else:
raise ValueError
except ValueError:
self.min_entry.delete(0, tk.END)
self.min_entry.insert(0, str(self.min_value))
[docs]
def update_from_max_entry(self, event=None) -> None:
"""Updates the maximum spot size value parsed through the button:
1. Gets the maximum spot size value and converts it to a logarithmic scale
if it is bigger or equal to the minimum spot size values and smaller or equal
than the full image.
2. Updates the maximum spot size value calling the update_max_value() function.
Args:
self (Instance): Current instance, provides access to attributes and methods.
event (_type_, optional): Nothing, just compatibility purposes. Defaults to None.
Raises:
ValueError: Maximum spot size value is either bigger than the dimensions of
the whole frame or smaller than the minimum spot size value.
"""
try:
max_value = int(self.max_entry.get())
if max_value >= self.min_value and max_value <= (
const.H_SENSOR_SIZE * const.V_SENSOR_SIZE
):
self.max_value = max_value
log_value = (
(math.log10(max_value) - self.log_min_value)
/ (self.log_max_value - self.log_min_value)
* 1000
)
self.max_slider.set(log_value)
# Updates the max spot value
self.update_max_value(None)
else:
raise ValueError
except ValueError:
self.max_entry.delete(0, tk.END)
self.max_entry.insert(0, str(self.max_value))
[docs]
def set_initial_values(
self, exposure: float, min_value: int, max_value: int
) -> None:
"""Sets the intial values for all the varibales of the sliders.
1. Gets the maximum and minimum spot size and exposure values and converts them to a logarithmic scale.
2. Sets initial values of the sliders.
3. Calls the update fuctions to set an inicial value for the variables.
Args:
self (Instance): Current instance, provides access to attributes and methods.
exposure (float): Initial exposure time.
min_value (int): Initial minimum spot size value.
max_value (int): Initial maximum spot size value.
"""
log_exposure = (
(
math.log10(
max(self.min_exposure, min(self.max_exposure, exposure))
)
- self.log_min_exposure
)
/ (self.log_max_exposure - self.log_min_exposure)
* 1000
)
self.min_value = min_value
self.max_value = max_value
log_min_value = (
(math.log10(max(1, self.min_value)) - self.log_min_value)
/ (self.log_max_value - self.log_min_value)
* 1000
)
log_max_value = (
(math.log10(self.max_value) - self.log_min_value)
/ (self.log_max_value - self.log_min_value)
* 1000
)
# Sets inital values of the sliders
self.exposure_slider.set(log_exposure)
self.min_slider.set(log_min_value)
self.max_slider.set(log_max_value)
# Sets inital values
self.update_exposure(None)
self.update_min_value(None)
self.update_max_value(None)