diff --git a/examples/base-device/camera_example.py b/examples/base-device/camera_example.py
index 2171457..b3f23bc 100644
--- a/examples/base-device/camera_example.py
+++ b/examples/base-device/camera_example.py
@@ -1,55 +1,64 @@
-from examples.resources.simulated_camera import Camera
-from view.widgets.base_device_widget import BaseDeviceWidget
-from qtpy.QtWidgets import QApplication
import sys
-from qtpy.QtCore import Slot
import threading
from time import sleep
+from voxel.devices.camera.simulated import Camera
+from qtpy.QtCore import Slot
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.base_device_widget import BaseDeviceWidget
+
+
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
attr = getattr(type(device), attr_name, None)
- if isinstance(attr, property): #and attr.fset is not None:
+ if isinstance(attr, property): # and attr.fset is not None:
prop_dict[attr_name] = getattr(device, attr_name)
return prop_dict
-def device_change():
- """Simulate changing device properties"""
+def device_change():
+ """_summary_"""
for i in range(0, 100):
if i == 25:
- print('changing exposure_time_ms')
+ print("changing exposure_time_ms")
base.exposure_time_ms = 2500.0
if i == 50:
- print('changing pixel_type')
- base.pixel_type = 'mono16'
- if i ==75:
- print('changing roi')
+ print("changing pixel_type")
+ base.pixel_type = "mono16"
+ if i == 75:
+ print("changing roi")
# Need to change whole dictionary to trigger update. DOES NOT WORK changing one item
- base.roi = {'width_px': 1016, 'height_px': 2032, 'width_offset_px': 0, 'height_offest_px': 0}
- if i ==99:
- print('changing sensor_height_px')
- base.sensor_height_px = 10640/2
- sleep(.1)
+ base.roi = {"width_px": 1016, "height_px": 2032, "width_offset_px": 0, "height_offest_px": 0}
+ if i == 99:
+ print("changing sensor_height_px")
+ base.sensor_height_px = 10640 / 2
+ sleep(0.1)
@Slot(str)
def widget_property_changed(name):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ """
+ name_lst = name.split(".")
+ print(name, " changed to ", getattr(base, name_lst[0]))
- name_lst = name.split('.')
- print(name, ' changed to ', getattr(base, name_lst[0]))
if __name__ == "__main__":
app = QApplication(sys.argv)
- simulated_camera = Camera('camera')
+ simulated_camera = Camera("camera")
camera_properties = scan_for_properties(simulated_camera)
print(camera_properties)
base = BaseDeviceWidget(Camera, camera_properties)
diff --git a/examples/base-device/instrument_example.py b/examples/base-device/instrument_example.py
index 72a40d5..2e50f33 100644
--- a/examples/base-device/instrument_example.py
+++ b/examples/base-device/instrument_example.py
@@ -1,23 +1,27 @@
-from view.widgets.base_device_widget import BaseDeviceWidget
-from qtpy.QtWidgets import QApplication
+import os
import sys
+from pathlib import Path
+
from qtpy.QtCore import Slot
-from voxel.instruments import Instrument
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.base_device_widget import BaseDeviceWidget
from voxel.acquisition import Acquisition
-from pathlib import Path
-import os
+from voxel.instruments import Instrument
+
+RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources"
+ACQUISITION_YAML = RESOURCES_DIR / "test_acquisition.yaml"
+INSTRUMENT_YAML = RESOURCES_DIR / "simulated_instrument.yaml"
-RESOURCES_DIR = (
- Path(os.path.dirname(os.path.realpath(__file__))) / "resources"
-)
-ACQUISITION_YAML = RESOURCES_DIR / 'test_acquisition.yaml'
-INSTRUMENT_YAML = RESOURCES_DIR / 'simulated_instrument.yaml'
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
attr = getattr(type(device), attr_name, None)
@@ -28,38 +32,44 @@ def scan_for_properties(device):
def set_up_guis(devices, device_type):
+ """_summary_
+
+ :param devices: _description_
+ :type devices: _type_
+ :param device_type: _description_
+ :type device_type: _type_
+ :return: _description_
+ :rtype: _type_
+ """
guis = {}
for name, device in devices.items():
properties = scan_for_properties(device)
# TODO: better way to find out what module
guis[name] = BaseDeviceWidget(type(device), properties)
- guis[name].setWindowTitle(f'{device_type} {name}')
+ guis[name].setWindowTitle(f"{device_type} {name}")
guis[name].ValueChangedInside[str].connect(
- lambda value, dev=device, widget=guis[name],: widget_property_changed(value, dev, widget))
+ lambda value, dev=device, widget=guis[name],: widget_property_changed(value, dev, widget)
+ )
guis[name].show()
return guis
@Slot(str)
def widget_property_changed(name, device, widget):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
-
- name_lst = name.split('.')
- print('widget', name, ' changed to ', getattr(widget, name_lst[0]))
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ :param device: _description_
+ :type device: _type_
+ :param widget: _description_
+ :type widget: _type_
+ """
+ name_lst = name.split(".")
+ print("widget", name, " changed to ", getattr(widget, name_lst[0]))
value = getattr(widget, name_lst[0])
setattr(device, name_lst[0], value)
- # if len(name_lst) == 1: # name refers to attribute
- # setattr(device, name, value)
- # else: # name is a dictionary and key pair split by .
- # dictionary = getattr(device, name_lst[0])
- # # new = {k:getattr(widget, f'{name_lst[0]}.{k}') for k in dictionary.keys()}
- # # print(new)
- # for k in dictionary.keys():
- # print(k, dir(widget))
- # print(k, getattr(widget, f'{name_lst[0]}.{k}_'))
-
- print('Device', name, ' changed to ', getattr(device, name_lst[0]))
+ print("Device", name, " changed to ", getattr(device, name_lst[0]))
if __name__ == "__main__":
@@ -68,20 +78,11 @@ def widget_property_changed(name, device, widget):
# instrument
instrument = Instrument(INSTRUMENT_YAML)
- laser_ui = set_up_guis(instrument.lasers, 'laser')
- combiner_ui = set_up_guis(instrument.combiners, 'combiner')
- camera_ui = set_up_guis(instrument.cameras, 'camera')
+ laser_ui = set_up_guis(instrument.lasers, "laser")
+ combiner_ui = set_up_guis(instrument.combiners, "combiner")
+ camera_ui = set_up_guis(instrument.cameras, "camera")
# acquisition
acquisition = Acquisition(instrument, ACQUISITION_YAML)
- # simulated_camera = Camera('camera')
- # camera_properties = scan_for_properties(simulated_camera)
- # print(camera_properties)
- # base = BaseDeviceWidget(Camera, "examples.resources.simulated_camera", camera_properties)
- # base.ValueChangedInside[str].connect(widget_property_changed)
-
- # t1 = threading.Thread(target=device_change, args=())
- # t1.start()
-
sys.exit(app.exec_())
diff --git a/examples/base-device/laser_example.py b/examples/base-device/laser_example.py
index 3e84218..7dfd176 100644
--- a/examples/base-device/laser_example.py
+++ b/examples/base-device/laser_example.py
@@ -1,55 +1,64 @@
-from examples.resources.simulated_laser import SimulatedLaser
-from view.widgets.base_device_widget import BaseDeviceWidget
-from qtpy.QtWidgets import QApplication
import sys
-from qtpy.QtCore import Slot
import threading
from time import sleep
+from voxel.devices.laser.simulated import SimulatedLaser
+from qtpy.QtCore import Slot
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.base_device_widget import BaseDeviceWidget
+
+
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
attr = getattr(type(device), attr_name, None)
- if isinstance(attr, property): #and attr.fset is not None:
+ if isinstance(attr, property): # and attr.fset is not None:
prop_dict[attr_name] = getattr(device, attr_name)
return prop_dict
-def device_change():
- """Simulate changing device properties"""
+def device_change():
+ """_summary_"""
for i in range(0, 100):
if i == 25:
- print('changing temperature')
+ print("changing temperature")
base.temperature = 25.0
if i == 50:
- print('changing cdrh')
- base.cdrh = 'OFF'
- if i ==75:
- print('changing test_property')
+ print("changing cdrh")
+ base.cdrh = "OFF"
+ if i == 75:
+ print("changing test_property")
# Need to change whole dictionary to trigger update. DOES NOT WORK changing one item
- base.test_property = {"value0":"internal", "value1":"on"}
- if i ==99:
- print('changing power')
+ base.test_property = {"value0": "internal", "value1": "on"}
+ if i == 99:
+ print("changing power")
base.power_setpoint_mw = 67.0
- sleep(.1)
+ sleep(0.1)
@Slot(str)
def widget_property_changed(name):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ """
+ name_lst = name.split(".")
+ print(name, " changed to ", getattr(base, name_lst[0]))
- name_lst = name.split('.')
- print(name, ' changed to ', getattr(base, name_lst[0]))
if __name__ == "__main__":
app = QApplication(sys.argv)
- simulated_laser = SimulatedLaser(port='COM3')
+ simulated_laser = SimulatedLaser(port="COM3")
laser_properties = scan_for_properties(simulated_laser)
base = BaseDeviceWidget(laser_properties, laser_properties)
base.ValueChangedInside[str].connect(widget_property_changed)
diff --git a/examples/base-device/schema_example.py b/examples/base-device/schema_example.py
index 95e4ccb..28a073a 100644
--- a/examples/base-device/schema_example.py
+++ b/examples/base-device/schema_example.py
@@ -1,26 +1,26 @@
-from view.widgets.base_device_widget import BaseDeviceWidget
-from qtpy.QtWidgets import QApplication
import sys
-from qtpy.QtCore import Slot
+
from aind_data_schema.core import acquisition
+from qtpy.QtCore import Slot
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.base_device_widget import BaseDeviceWidget
@Slot(str)
def widget_property_changed(name):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ """
+ name_lst = name.split(".")
+ print(name, " changed to ", getattr(base, name_lst[0]))
- name_lst = name.split('.')
- print(name, ' changed to ', getattr(base, name_lst[0]))
- # if len(name_lst) == 1: # name refers to attribute
- # setattr(writer, name, value)
- # else: # name is a dictionary and key pair split by .
- # getattr(writer, name_lst[0]).__setitem__(name_lst[1], value)
- # print(name, ' changed to ', getattr(writer, name_lst[0]))
if __name__ == "__main__":
app = QApplication(sys.argv)
- acquisition_properties = {k:'' for k in acquisition.Acquisition.model_fields.keys()}
+ acquisition_properties = {k: "" for k in acquisition.Acquisition.model_fields.keys()}
base = BaseDeviceWidget(acquisition.Acquisition.model_fields, acquisition_properties)
base.ValueChangedInside[str].connect(widget_property_changed)
diff --git a/examples/base-device/writer_example.py b/examples/base-device/writer_example.py
index 19030f0..aee6b32 100644
--- a/examples/base-device/writer_example.py
+++ b/examples/base-device/writer_example.py
@@ -1,15 +1,20 @@
-from examples.resources.imaris import Writer
-from view.widgets.base_device_widget import BaseDeviceWidget
-from qtpy.QtWidgets import QApplication
import sys
+
+from voxel.writers.imaris import ImarisWriter
from qtpy.QtCore import Slot
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.base_device_widget import BaseDeviceWidget
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
attr = getattr(type(device), attr_name, None)
@@ -18,30 +23,34 @@ def scan_for_properties(device):
return prop_dict
+
@Slot(str)
def widget_property_changed(name):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
- name_lst = name.split('.')
+ :param name: _description_
+ :type name: _type_
+ """
+ name_lst = name.split(".")
value = getattr(base, name_lst[0])
if len(name_lst) == 1: # name refers to attribute
setattr(writer, name, value)
else: # name is a dictionary and key pair split by .
getattr(writer, name_lst[0]).__setitem__(name_lst[1], value)
- print(name, ' changed to ', getattr(writer, name_lst[0]))
+ print(name, " changed to ", getattr(writer, name_lst[0]))
+
if __name__ == "__main__":
app = QApplication(sys.argv)
- writer = Writer()
- writer.compression = 'lz4shuffle'
- writer.data_type = 'uint16'
- writer.path = r"C:\Users\micah.woodard\Downloads"
+ writer = ImarisWriter()
+ writer.compression = "lz4shuffle"
+ writer.data_type = "uint16"
+ writer.path = "."
writer.color = "#00ff92"
writer_properties = scan_for_properties(writer)
base = BaseDeviceWidget(Writer, writer_properties)
base.ValueChangedInside[str].connect(widget_property_changed)
- base.data_type = 'uint8'
+ base.data_type = "uint8"
sys.exit(app.exec_())
diff --git a/examples/camera_example.py b/examples/camera_example.py
index 02a8125..7c68a4e 100644
--- a/examples/camera_example.py
+++ b/examples/camera_example.py
@@ -1,15 +1,18 @@
-from voxel.devices.camera.simulated import Camera
+from voxel.devices.camera.simulated import SimulatedCamera
from view.widgets.device_widgets.camera_widget import CameraWidget
from qtpy.QtWidgets import QApplication
import sys
from qtpy.QtCore import Slot
-import inspect
+
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
attr = getattr(type(device), attr_name, None)
@@ -21,46 +24,41 @@ def scan_for_properties(device):
@Slot(str)
def widget_property_changed(name, device, widget):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
- name_lst = name.split('.')
- #print('widget', name, ' changed to ', getattr(widget, name_lst[0]))
+ :param name: _description_
+ :type name: _type_
+ :param device: _description_
+ :type device: _type_
+ :param widget: _description_
+ :type widget: _type_
+ """
+ name_lst = name.split(".")
value = getattr(widget, name_lst[0])
setattr(device, name_lst[0], value)
- #print('Device', name, ' changed to ', getattr(device, name_lst[0]))
for k, v in widget.property_widgets.items():
instrument_value = getattr(device, k)
- # print(k, instrument_value)
setattr(widget, k, instrument_value)
+
if __name__ == "__main__":
app = QApplication(sys.argv)
- camera_object = Camera('')
- props = {'exposure_time_ms': 20.0,
- 'pixel_type': 'mono16',
- 'width_px': 1152,
- 'height_px': 1152,
- }
+ camera_object = SimulatedCamera("")
+ props = {
+ "exposure_time_ms": 20.0,
+ "pixel_type": "mono16",
+ "width_px": 1152,
+ "height_px": 1152,
+ "um_px": 1.0
+ }
for k, v in props.items():
setattr(camera_object, k, v)
camera = CameraWidget(camera_object)
- camera.setWindowTitle('Camera')
+ camera.setWindowTitle("Camera")
camera.show()
camera.ValueChangedInside[str].connect(
- lambda value, dev=camera_object, widget=camera,: widget_property_changed(value, dev, widget))
+ lambda value, dev=camera_object, widget=camera, : widget_property_changed(value, dev, widget)
+ )
sys.exit(app.exec_())
- # app = QApplication(sys.argv)
- # simulated_camera = Camera('camera')
- # camera_properties = scan_for_properties(simulated_camera)
- # print(camera_properties)
- # base = BaseDeviceWidget(Camera, "examples.resources.simulated_camera", camera_properties)
- # base.ValueChangedInside[str].connect(widget_property_changed)
- # base.show()
- #
- # t1 = threading.Thread(target=device_change, args=())
- # t1.start()
- #
- # sys.exit(app.exec_())
diff --git a/examples/channel_plan_example.py b/examples/channel_plan_example.py
index 80841d9..ad37377 100644
--- a/examples/channel_plan_example.py
+++ b/examples/channel_plan_example.py
@@ -1,82 +1,62 @@
-from view.widgets.acquisition_widgets.channel_plan_widget import ChannelPlanWidget
-from qtpy.QtWidgets import QApplication
import sys
from unittest.mock import MagicMock
-from pathlib import Path
-import os
-from voxel.devices.lasers.simulated import SimulatedLaser
-from voxel.devices.stage.simulated import Stage
+
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.acquisition_widgets.channel_plan_widget import ChannelPlanWidget
from view.widgets.device_widgets.laser_widget import LaserWidget
from view.widgets.device_widgets.stage_widget import StageWidget
-from threading import Lock
+from voxel.devices.laser.simulated import SimulatedLaser
+from voxel.devices.stage.simulated import SimulatedStage
if __name__ == "__main__":
app = QApplication(sys.argv)
channels = {
- '488': {
- 'filters': ['BP488'],
- 'lasers': ['488nm'],
- 'cameras': ['vnp - 604mx', 'vp-151mx']},
- '639': {
- 'filters': ['LP638'],
- 'lasers': ['639nm'],
- 'cameras': ['vnp - 604mx', 'vp-151mx']}
+ "488": {"filters": ["BP488"], "lasers": ["488nm"], "cameras": ["vnp - 604mx", "vp-151mx"]},
+ "639": {"filters": ["LP638"], "lasers": ["639nm"], "cameras": ["vnp - 604mx", "vp-151mx"]},
}
properties = {
- 'lasers': ['power_setpoint_mw'],
- 'focusing_stages': ['position_mm'],
- 'start_delay_time': {
- 'delegate': 'spin',
- 'type': 'float',
- 'minimum': 0,
- 'initial_value': 15,
+ "lasers": ["power_setpoint_mw"],
+ "focusing_stages": ["position_mm"],
+ "start_delay_time": {
+ "delegate": "spin",
+ "type": "float",
+ "minimum": 0,
+ "initial_value": 15,
+ },
+ "repeats": {
+ "delegate": "spin",
+ "type": "int",
+ "minimum": 0,
},
- 'repeats': {
- 'delegate': 'spin',
- 'type': 'int',
- 'minimum': 0,
+ "example": {
+ "delegate": "combo",
+ "type": "str",
+ "items": ["this", "is", "an", "example"],
+ "initial_value": "example",
},
- 'example': {
- 'delegate': 'combo',
- 'type': 'str',
- 'items': ['this', 'is', 'an', 'example'],
- 'initial_value': 'example'
- }
}
- lasers = {
- '488nm': SimulatedLaser(id='hello', wavelength=488),
- '639nm': SimulatedLaser(id='there', wavelength=639)
- }
+ lasers = {"488nm": SimulatedLaser(id="hello", wavelength=488), "639nm": SimulatedLaser(id="there", wavelength=639)}
- focusing_stages = {
- 'n': Stage(hardware_axis='n', instrument_axis='n')
- }
+ focusing_stages = {"n": SimulatedStage(hardware_axis="n", instrument_axis="n")}
- laser_widgets = {
- '488nm': LaserWidget(lasers['488nm']),
- '639nm': LaserWidget(lasers['639nm'])
- }
+ laser_widgets = {"488nm": LaserWidget(lasers["488nm"]), "639nm": LaserWidget(lasers["639nm"])}
focusing_stage_widgets = {
- 'n': StageWidget(focusing_stages['n']),
+ "n": StageWidget(focusing_stages["n"]),
}
mocked_instrument = MagicMock()
- mocked_instrument.configure_mock(
- lasers=lasers,
- focusing_stages=focusing_stages)
+ mocked_instrument.configure_mock(lasers=lasers, focusing_stages=focusing_stages)
mocked_instrument_view = MagicMock()
- mocked_instrument_view.configure_mock(instrument=mocked_instrument,
- laser_widgets=laser_widgets,
- focusing_stage_widgets=focusing_stage_widgets
- )
+ mocked_instrument_view.configure_mock(
+ instrument=mocked_instrument, laser_widgets=laser_widgets, focusing_stage_widgets=focusing_stage_widgets
+ )
- plan = ChannelPlanWidget(mocked_instrument_view,
- channels,
- properties)
+ plan = ChannelPlanWidget(mocked_instrument_view, channels, properties)
plan.show()
plan.channelAdded.connect(lambda ch: plan.add_channel_rows(ch, [[0, 0]]))
diff --git a/examples/filter_wheel_example.py b/examples/filter_wheel_example.py
index 9fa881c..ac5bf69 100644
--- a/examples/filter_wheel_example.py
+++ b/examples/filter_wheel_example.py
@@ -1,51 +1,69 @@
-from view.widgets.device_widgets.filter_wheel_widget import FilterWheelWidget
-from qtpy.QtWidgets import QApplication
import sys
-from voxel.devices.filterwheel.asi import FilterWheel
-from voxel.devices.filter.asi import Filter
-from time import sleep
import threading
+from time import sleep
+
+from qtpy.QtWidgets import QApplication
from tigerasi.tiger_controller import TigerController
+from view.widgets.device_widgets.filter_wheel_widget import FilterWheelWidget
+from voxel.devices.filter.asi import Filter
+from voxel.devices.filterwheel.asi.fw1000 import FW1000FilterWheel
+
+
def move_filter():
- filter_wheel.filter = 'BP405'
+ """_summary_"""
+ filter_wheel.filter = "BP405"
sleep(3)
- filter_wheel.filter = 'LP638'
+ filter_wheel.filter = "LP638"
sleep(3)
BP405_filter.enable()
+
def widget_property_changed(name, device, widget):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
- print('widget property changed')
- name_lst = name.split('.')
- print('widget', name, ' changed to ', getattr(widget, name_lst[0]))
+ :param name: _description_
+ :type name: _type_
+ :param device: _description_
+ :type device: _type_
+ :param widget: _description_
+ :type widget: _type_
+ """
+ print("widget property changed")
+ name_lst = name.split(".")
+ print("widget", name, " changed to ", getattr(widget, name_lst[0]))
value = getattr(widget, name_lst[0])
setattr(device, name_lst[0], value)
- print('Device', name, ' changed to ', getattr(device, name_lst[0]))
+ print("Device", name, " changed to ", getattr(device, name_lst[0]))
if __name__ == "__main__":
app = QApplication(sys.argv)
- stage = TigerController('COM4')
- filter_wheel = FilterWheel(stage,0, {'BP405': 0,
- 'BP488': 1,
- 'BP561': 2,
- 'LP638': 3,
- 'MB405/488/561/638': 4,
- 'Empty1': 5,
- 'Empty2': 6,})
-
- BP405_filter = Filter(filter_wheel, 'BP405')
- BP561_filter = Filter(filter_wheel, 'BP561')
- LP638_filter = Filter(filter_wheel, 'LP638')
- BP488_filter = Filter(filter_wheel, 'BP488')
+ stage = TigerController("COM4")
+ filter_wheel = FW1000FilterWheel(
+ stage,
+ 0,
+ {
+ "BP405": 0,
+ "BP488": 1,
+ "BP561": 2,
+ "LP638": 3,
+ "MB405/488/561/638": 4,
+ "Empty1": 5,
+ "Empty2": 6,
+ },
+ )
+ BP405_filter = Filter(filter_wheel, "BP405")
+ BP561_filter = Filter(filter_wheel, "BP561")
+ LP638_filter = Filter(filter_wheel, "LP638")
+ BP488_filter = Filter(filter_wheel, "BP488")
widget = FilterWheelWidget(filter_wheel)
- widget.ValueChangedInside[str].connect(lambda value, dev=filter_wheel, gui=widget: widget_property_changed(value, dev, widget))
+ widget.ValueChangedInside[str].connect(
+ lambda value, dev=filter_wheel, gui=widget: widget_property_changed(value, dev, widget)
+ )
widget.show()
t1 = threading.Thread(target=move_filter)
diff --git a/examples/filter_wheel_simulated_example.py b/examples/filter_wheel_simulated_example.py
index 32fe941..86fcb8c 100644
--- a/examples/filter_wheel_simulated_example.py
+++ b/examples/filter_wheel_simulated_example.py
@@ -1,68 +1,79 @@
-from view.widgets.device_widgets.filter_wheel_widget import FilterWheelWidget
-from qtpy.QtWidgets import QApplication
import sys
-from voxel.devices.filterwheel.simulated import FilterWheel
-from voxel.devices.filter.simulated import Filter
from time import sleep
-import threading
+
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.device_widgets.filter_wheel_widget import FilterWheelWidget
+from voxel.devices.filter.simulated import Filter
+from voxel.devices.filterwheel.simulated import SimulatedFilterWheel
def move_filter():
- widget.filter = 'BP561'
+ """_summary_"""
+ widget.filter = "BP561"
sleep(3)
- widget.filter = 'MB405/488/561/638'
+ widget.filter = "MB405/488/561/638"
sleep(3)
- filter_wheel.filter = 'BP405'
+ filter_wheel.filter = "BP405"
sleep(3)
- filter_wheel.filter = 'LP638'
+ filter_wheel.filter = "LP638"
sleep(3)
BP405_filter.enable()
def widget_property_changed(name, device, widget):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
+ """_summary_
- name_lst = name.split('.')
+ :param name: _description_
+ :type name: _type_
+ :param device: _description_
+ :type device: _type_
+ :param widget: _description_
+ :type widget: _type_
+ """
+ name_lst = name.split(".")
value = getattr(widget, name_lst[0])
setattr(device, name_lst[0], value)
- print('Device', name, ' changed to ', getattr(device, name_lst[0]))
+ print("Device", name, " changed to ", getattr(device, name_lst[0]))
if __name__ == "__main__":
app = QApplication(sys.argv)
- filter_wheel = FilterWheel(0, {'BP405': 0,
- 'BP488': 1,
- 'BP561': 2,
- 'LP638': 3,
- 'MB405/488/561/638': 4,
- 'Empty1': 5,
- 'Empty2': 6,
- 'Empty3': 7,
- 'Empty4': 8,
- })
+ filter_wheel = SimulatedFilterWheel(
+ 0,
+ {
+ "BP405": 0,
+ "BP488": 1,
+ "BP561": 2,
+ "LP638": 3,
+ "MB405/488/561/638": 4,
+ "Empty1": 5,
+ "Empty2": 6,
+ "Empty3": 7,
+ "Empty4": 8,
+ },
+ )
- BP405_filter = Filter(filter_wheel, 'BP405')
- BP561_filter = Filter(filter_wheel, 'BP561')
- LP638_filter = Filter(filter_wheel, 'LP638')
- BP488_filter = Filter(filter_wheel, 'BP488')
+ BP405_filter = Filter(filter_wheel, "BP405")
+ BP561_filter = Filter(filter_wheel, "BP561")
+ LP638_filter = Filter(filter_wheel, "LP638")
+ BP488_filter = Filter(filter_wheel, "BP488")
- colors = {'BP405': 'purple',
- 'BP488': 'blue',
- 'BP561': 'yellowgreen',
- 'LP638': 'red',
- 'MB405/488/561/638': 'pink',
- 'Empty1': 'black',
- 'Empty2': 'black',
- 'Empty3': 'black',
- 'Empty4': 'black',
- }
+ colors = {
+ "BP405": "#C875C4",
+ "BP488": "#1F77B4",
+ "BP561": "#2CA02C",
+ "LP638": "#D62768",
+ "MB405/488/561/638": "#17BECF",
+ "Empty1": "#262930",
+ "Empty2": "#262930",
+ "Empty3": "#262930",
+ "Empty4": "#262930",
+ }
widget = FilterWheelWidget(filter_wheel, colors)
widget.ValueChangedInside[str].connect(
- lambda value, dev=filter_wheel, widget=widget,: widget_property_changed(value, dev, widget))
+ lambda value, dev=filter_wheel, widget=widget, : widget_property_changed(value, dev, widget)
+ )
widget.show()
- # t1 = threading.Thread(target=move_filter)
- # t1.start()
-
sys.exit(app.exec_())
diff --git a/examples/laser_example.py b/examples/laser_example.py
index 82b4787..8299b58 100644
--- a/examples/laser_example.py
+++ b/examples/laser_example.py
@@ -1,15 +1,20 @@
-from voxel.devices.laser.simulated import SimulatedLaser
-from view.widgets.device_widgets.laser_widget import LaserWidget
-from qtpy.QtWidgets import QApplication
import sys
+
from qtpy.QtCore import Slot
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.device_widgets.laser_widget import LaserWidget
+from voxel.devices.laser.simulated import SimulatedLaser
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
attr = getattr(type(device), attr_name, None)
@@ -21,9 +26,15 @@ def scan_for_properties(device):
@Slot(str)
def widget_property_changed(name, device, widget):
- """Slot to signal when widget has been changed
- :param name: name of attribute and widget"""
-
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ :param device: _description_
+ :type device: _type_
+ :param widget: _description_
+ :type widget: _type_
+ """
name_lst = name.split('.')
print('widget', name, ' changed to ', getattr(widget, name_lst[0]))
value = getattr(widget, name_lst[0])
@@ -38,22 +49,10 @@ def widget_property_changed(name, device, widget):
if __name__ == "__main__":
app = QApplication(sys.argv)
laser_object = SimulatedLaser(id='', wavelength=488)
- laser = LaserWidget(laser_object, color='blue', advanced_user=False)
+ laser = LaserWidget(laser_object, color='#1F77B4', advanced_user=False)
laser.show()
laser.ValueChangedInside[str].connect(
- lambda value, dev=laser_object, widget=laser,: widget_property_changed(value, dev, widget))
+ lambda value, dev=laser_object, widget=laser, : widget_property_changed(value, dev, widget))
laser.setWindowTitle('Laser')
sys.exit(app.exec_())
- # app = QApplication(sys.argv)
- # simulated_camera = Camera('camera')
- # camera_properties = scan_for_properties(simulated_camera)
- # print(camera_properties)
- # base = BaseDeviceWidget(Camera, "examples.resources.simulated_camera", camera_properties)
- # base.ValueChangedInside[str].connect(widget_property_changed)
- # base.show()
- #
- # t1 = threading.Thread(target=device_change, args=())
- # t1.start()
- #
- # sys.exit(app.exec_())
diff --git a/examples/ni_example.py b/examples/ni_example.py
index ad053b4..c037e8a 100644
--- a/examples/ni_example.py
+++ b/examples/ni_example.py
@@ -1,4 +1,4 @@
-from voxel.devices.daq.ni import DAQ
+from voxel.devices.daq.simulated import SimulatedDAQ
from view.widgets.device_widgets.ni_widget import NIWidget
from qtpy.QtWidgets import QApplication
import sys
@@ -16,16 +16,7 @@
def widget_property_changed(name, device, widget):
"""Slot to signal when widget has been changed
:param name: name of attribute and widget"""
-
- name_lst = name.split('.')
- # print('widget', name, ' changed to ', getattr(widget, name_lst[0]))
- # value = getattr(widget, name_lst[0])
- # setattr(device, name_lst[0], value)
- # print('Device', name, ' changed to ', getattr(device, name_lst[0]))
- # for k, v in widget.property_widgets.items():
- # instrument_value = getattr(device, k)
- # print(k, instrument_value)
- #setattr(widget, k, instrument_value)
+ pass
if __name__ == "__main__":
@@ -38,7 +29,7 @@ def widget_property_changed(name, device, widget):
ao_task = daq_tasks['ao_task']
co_task = daq_tasks['co_task']
- daq_object = DAQ("Dev2")
+ daq_object = SimulatedDAQ("Dev2")
daq_object.tasks = daq_tasks
daq_object.add_task('ao')
daq_object.add_task('co')
@@ -48,7 +39,7 @@ def widget_property_changed(name, device, widget):
gui_config = YAML(typ='safe', pure=True).load(GUI_YAML)
exposed = gui_config['instrument_view']['device_widgets']['PCIe-6738']['init']['exposed_branches']
- daq_widget = NIWidget(daq_object, exposed)
+ daq_widget = NIWidget(daq_object, exposed, advanced_user=False)
daq_widget.show()
daq_widget.ValueChangedInside[str].connect(
lambda value, dev=daq_object, widget=daq_widget,: widget_property_changed(value, dev, widget))
diff --git a/examples/resources/imaris.py b/examples/resources/imaris.py
deleted file mode 100644
index 10d780d..0000000
--- a/examples/resources/imaris.py
+++ /dev/null
@@ -1,342 +0,0 @@
-import numpy as np
-import logging
-import multiprocessing
-import re
-import os
-import sys
-from multiprocessing import Process, Array, Event
-from multiprocessing.shared_memory import SharedMemory
-from ctypes import c_wchar
-from PyImarisWriter import PyImarisWriter as pw
-from pathlib import Path
-from datetime import datetime
-from matplotlib.colors import hex2color
-from time import sleep, perf_counter
-from math import ceil
-
-CHUNK_SIZE = 64
-
-COMPRESSION_TYPES = {
- "none": pw.eCompressionAlgorithmShuffleLZ4,
- "lz4shuffle": pw.eCompressionAlgorithmNone,
-}
-
-DATA_TYPES = {
- "uint8": "uint8",
- "uint16": "uint16",
-}
-
-class ImarisProgressChecker(pw.CallbackClass):
- """Class for tracking progress of an active ImarisWriter disk-writing
- operation."""
-
- def __init__(self):
- self.progress = 0 # a float representing the progress (0 to 1.0).
-
- def RecordProgress(self, progress, total_bytes_written):
- self.progress = progress
-
-class Writer:
-
- def __init__(self):
-
- self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
-
- # Opinioated decision on chunking dimension order
- self.chunk_dim_order = ('z', 'y', 'x')
- # Flow control attributes to synchronize inter-process communication.
- self.done_reading = Event()
- self.done_reading.set() # Set after processing all data in shared mem.
- # Internal flow control attributes to monitor compression progress.
- self.callback_class = ImarisProgressChecker()
-
- @property
- def x_voxel_size(self):
- return self.pixel_x_size_um
-
- @x_voxel_size.setter
- def x_voxel_size(self, x_voxel_size: float):
- self.log.info(f'setting x voxel size to: {x_voxel_size} [um]')
- self.pixel_x_size_um = x_voxel_size
-
- @property
- def y_voxel_size(self):
- return self.pixel_y_size_um
-
- @y_voxel_size.setter
- def y_voxel_size(self, y_voxel_size: float):
- self.log.info(f'setting y voxel size to: {y_voxel_size} [um]')
- self.pixel_y_size_um = y_voxel_size
-
- @property
- def z_voxel_size(self):
- return self.pixel_z_size_um
-
- @z_voxel_size.setter
- def z_voxel_size(self, z_voxel_size: float):
- self.log.info(f'setting z voxel size to: {z_voxel_size} [um]')
- self.pixel_z_size_um = z_voxel_size
-
- @property
- def x_pos_mm(self):
- return self.first_img_centroid_x_um
-
- @x_pos_mm.setter
- def x_pos_mm(self, x_pos_mm: float):
- self.log.info(f'setting x position to: {x_pos_mm} [mm]')
- self.first_img_centroid_x_um = x_pos_mm*1000
-
- @property
- def y_pos_mm(self):
- return self.first_img_centroid_y_um
-
- @y_pos_mm.setter
- def y_pos_mm(self, y_pos_mm: float):
- self.log.info(f'setting y position to: {y_pos_mm} [mm]')
- self.first_img_centroid_y_um = y_pos_mm*1000
-
- @property
- def z_pos_mm(self):
- return self.first_img_centroid_z_um
-
- @z_pos_mm.setter
- def z_pos_mm(self, z_pos_mm: float):
- self.log.info(f'setting z position to: {z_pos_mm} [mm]')
- self.first_img_centroid_z_um = z_pos_mm*1000
-
- @property
- def frame_count(self):
- return self.img_count
-
- @frame_count.setter
- def frame_count(self, frame_count: int):
- self.log.info(f'setting frame count to: {frame_count} [px]')
- self.img_count = frame_count
-
- @property
- def column_count(self):
- return self.cols
-
- @column_count.setter
- def column_count(self, column_count: int):
- self.log.info(f'setting column count to: {column_count} [px]')
- self.cols = column_count
-
- @property
- def row_count(self):
- return self.rows
-
- @row_count.setter
- def row_count(self, row_count: int):
- self.log.info(f'setting row count to: {row_count} [px]')
- self.rows = row_count
-
- @property
- def chunk_count(self):
- return CHUNK_SIZE
-
- @property
- def compression(self):
- return next(key for key, value in COMPRESSION_TYPES.items() if value == self.compression_style)
-
- @compression.setter
- def compression(self, compression: str):
- valid = list(COMPRESSION_TYPES.keys())
- if compression not in valid:
- raise ValueError("compression type must be one of %r." % valid)
- self.log.info(f'setting compression mode to: {compression}')
- self.compression_style = COMPRESSION_TYPES[compression]
-
- @property
- def data_type(self):
- return self.dtype
-
- @data_type.setter
- def data_type(self, data_type: np.unsignedinteger):
- self.log.info(f'setting data type to: {data_type}')
- self.dtype = data_type
-
- @property
- def path(self):
- return self.dest_path
-
- @path.setter
- def path(self, path: Path or str):
- if os.path.isdir(path):
- self.dest_path = Path(path)
- else:
- raise ValueError("%r is not a valid path." % path)
- self.log.info(f'setting path to: {path}')
-
- @property
- def filename(self):
- return self.stack_name
-
- @filename.setter
- def filename(self, filename: str):
- self.stack_name = filename \
- if filename.endswith(".ims") else f"{filename}.ims"
- self.log.info(f'setting filename to: {filename}')
-
- @property
- def channel(self):
- return self.channel_name
-
- @channel.setter
- def channel(self, channel: str):
- self.log.info(f'setting channel name to: {channel}')
- self.channel_name = channel
-
- @property
- def color(self):
- return self.viz_color_hex
-
- @color.setter
- def color(self, color: str):
- if re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', color):
- self.viz_color_hex = color
- else:
- raise ValueError("%r is not a valid hex color code." % color)
- self.log.info(f'setting color to: {color}')
-
- @property
- def shm_name(self):
- """Convenience getter to extract the shared memory address (string)
- from the c array."""
- return str(self._shm_name[:]).split('\x00')[0]
-
- @shm_name.setter
- def shm_name(self, name: str):
- """Convenience setter to set the string value within the c array."""
- for i, c in enumerate(name):
- self._shm_name[i] = c
- self._shm_name[len(name)] = '\x00' # Null terminate the string.
- self.log.info(f'setting shared memory to: {name}')
-
- def prepare(self):
- self.p = Process(target=self._run)
- # Specs for reconstructing the shared memory object.
- self._shm_name = Array(c_wchar, 32) # hidden and exposed via property.
- # This is almost always going to be: (chunk_size, rows, columns).
- chunk_shape_map = {'x': self.cols,
- 'y': self.rows,
- 'z': CHUNK_SIZE}
- self.shm_shape = [chunk_shape_map[x] for x in self.chunk_dim_order]
- self.shm_nbytes = \
- int(np.prod(self.shm_shape, dtype=np.int64)*np.dtype(DATA_TYPES[self.dtype]).itemsize)
- self.log.info(f"{self.stack_name}: intializing writer.")
- self.application_name = 'PyImarisWriter'
- self.application_version = '1.0.0'
- # voxel size metadata to create the converter
- self.image_size = pw.ImageSize(x=self.cols, y=self.rows, z=self.img_count,
- c=1, t=1)
- self.block_size = pw.ImageSize(x=self.cols, y=self.rows, z=CHUNK_SIZE,
- c=1, t=1)
- self.sample_size = pw.ImageSize(x=1, y=1, z=1, c=1, t=1)
- # compute the start/end extremes of the enclosed rectangular solid.
- # (x0, y0, z0) position (in [um]) of the beginning of the first voxel,
- # (xf, yf, zf) position (in [um]) of the end of the last voxel.
- x0 = self.first_img_centroid_x_um - (self.pixel_x_size_um * 0.5 * self.cols)
- y0 = self.first_img_centroid_y_um - (self.pixel_y_size_um * 0.5 * self.rows)
- z0 = self.first_img_centroid_z_um
- xf = self.first_img_centroid_x_um + (self.pixel_x_size_um * 0.5 * self.cols)
- yf = self.first_img_centroid_y_um + (self.pixel_y_size_um * 0.5 * self.rows)
- zf = self.first_img_centroid_z_um + self.img_count * self.pixel_z_size_um
- self.image_extents = pw.ImageExtents(-x0, -y0, -z0, -xf, -yf, -zf)
- # c = channel, t = time. These fields are unused for now.
- # Note: ImarisWriter performs MUCH faster when the dimension sequence
- # is arranged: x, y, z, c, t.
- # It is more efficient to transpose/reshape the data into this
- # shape beforehand instead of defining an arbitrary
- # DimensionSequence and passing the chunk data in as-is.
- self.chunk_dim_order = ('z', 'y', 'x')
- self.dimension_sequence = pw.DimensionSequence('x', 'y', 'z', 'c', 't')
- # lookups for deducing order
- self.dim_map = {'x': 0, 'y': 1, 'z': 2, 'c': 3, 't': 4}
- # name parameters
- self.parameters = pw.Parameters()
- self.parameters.set_channel_name(0, self.channel_name)
- # create options object
- self.opts = pw.Options()
- self.opts.mEnableLogProgress = True
- # set threads to double number of cores
- self.thread_count = 2*multiprocessing.cpu_count()
- self.opts.mNumberOfThreads = self.thread_count
- # set compression type
- if self.compression_style == 'lz4shuffle':
- self.opts.mCompressionAlgorithmType = pw.eCompressionAlgorithmShuffleLZ4
- elif self.compression_style == 'none':
- self.opts.mCompressionAlgorithmType = pw.eCompressionAlgorithmNone
- # color parameters
- self.adjust_color_range = False
- self.color_infos = [pw.ColorInfo()]
- self.color_infos[0].set_base_color(pw.Color(*(*hex2color(self.viz_color_hex), 1.0)))
- # date time parameters
- self.time_infos = [datetime.today()]
-
- def start(self):
- self.log.info(f"{self.stack_name}: starting writer.")
- self.p.start()
-
- def _run(self):
- """Loop to wait for data from a specified location and write it to disk
- as an Imaris file. Close up the file afterwards.
-
- This function executes when called with the start() method.
- """
- # internal logger for process
- logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
- fmt = '%(asctime)s.%(msecs)03d %(levelname)s %(name)s: %(message)s'
- datefmt = '%Y-%m-%d,%H:%M:%S'
- log_formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
- log_handler = logging.StreamHandler(sys.stdout)
- log_handler.setFormatter(log_formatter)
- logger.addHandler(log_handler)
-
- filepath = str((self.dest_path/Path(f"{self.stack_name}")).absolute())
- converter = \
- pw.ImageConverter(DATA_TYPES[self.dtype], self.image_size, self.sample_size,
- self.dimension_sequence, self.block_size, filepath,
- self.opts, self.application_name,
- self.application_version, self.callback_class)
- chunk_count = ceil(self.img_count/CHUNK_SIZE)
- for chunk_num in range(chunk_count):
- block_index = pw.ImageSize(x=0, y=0, z=chunk_num, c=0, t=0)
- # Wait for new data.
- while self.done_reading.is_set():
- print('in imaris writer while self.done_reading.is_set(): ' , self.done_reading.is_set())
- sleep(0.001)
- # Attach a reference to the data from shared memory.
- shm = SharedMemory(self.shm_name, create=False, size=self.shm_nbytes)
- frames = np.ndarray(self.shm_shape, DATA_TYPES[self.dtype], buffer=shm.buf)
- logger.warning(f"{self.stack_name}: writing chunk "
- f"{chunk_num+1}/{chunk_count} of size {frames.shape}.")
- start_time = perf_counter()
- dim_order = [self.dim_map[x] for x in self.chunk_dim_order]
- # Put the frames back into x, y, z, c, t order.
- converter.CopyBlock(frames.transpose(dim_order), block_index)
- frames = None
- logger.warning(f"{self.stack_name}: writing chunk took "
- f"{perf_counter() - start_time:.3f} [s]")
- shm.close()
- self.done_reading.set()
-
- # Wait for file writing to finish.
- if self.callback_class.progress < 1.0:
- logger.warning(f"{self.stack_name}: waiting for data writing to complete for "
- f"{self.stack_name}. "
- f"current progress is {100*self.callback_class.progress:.1f}%.")
- while self.callback_class.progress < 1.0:
- print('in imaris writer while self.callback_class.progress < 1.0: ', self.callback_class.progress)
- sleep(0.5)
- logger.warning(f"{self.stack_name}: waiting for data writing to complete for "
- f"{self.stack_name}. "
- f"current progress is {100*self.callback_class.progress:.1f}%.")
-
- converter.Finish(self.image_extents, self.parameters, self.time_infos,
- self.color_infos, self.adjust_color_range)
- converter.Destroy()
-
- def wait_to_finish(self):
- self.log.info(f"{self.stack_name}: waiting to finish.")
- self.p.join()
\ No newline at end of file
diff --git a/examples/resources/simulated_camera.py b/examples/resources/simulated_camera.py
deleted file mode 100644
index 9a8909b..0000000
--- a/examples/resources/simulated_camera.py
+++ /dev/null
@@ -1,197 +0,0 @@
-import logging
-import numpy
-import time
-from multiprocessing import Process
-from threading import Thread
-
-# constants for VP-151MX camera
-BUFFER_SIZE_FRAMES = 8
-MIN_WIDTH_PX = 64
-MAX_WIDTH_PX = 14192
-DIVISIBLE_WIDTH_PX = 16
-MIN_HEIGHT_PX = 2
-MAX_HEIGHT_PX = 10640
-DIVISIBLE_HEIGHT_PX = 1
-MIN_EXPOSURE_TIME_MS = 0.001
-MAX_EXPOSURE_TIME_MS = 6e4
-
-PIXEL_TYPES = {
- "mono8": "uint8",
- "mono16": "uint16"
-}
-
-LINE_INTERVALS_US = {
- "mono8": 15.00,
- "mono16": 45.44
-}
-
-class Camera:
-
- def __init__(self, id):
-
- self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
- self.id = id
- self.simulated_pixel_type = "mono8"
- self.simulated_line_interval_us = 10
- self.simulated_width_px = 2032
- self.simulated_height_px = 2032
- self.simulated_width_offset_px = 0
- self.simulated_height_offset_px = 0
- self.simulated_exposure_time_ms = 1000
-
- @property
- def exposure_time_ms(self):
- return self.simulated_exposure_time_ms
-
- @exposure_time_ms.setter
- def exposure_time_ms(self, exposure_time_ms: float):
-
- if exposure_time_ms < MIN_EXPOSURE_TIME_MS or \
- exposure_time_ms > MAX_EXPOSURE_TIME_MS:
- self.log.error(f"exposure time must be >{MIN_EXPOSURE_TIME_MS} ms \
- and <{MAX_EXPOSURE_TIME_MS} ms")
- raise ValueError(f"exposure time must be >{MIN_EXPOSURE_TIME_MS} ms \
- and <{MAX_EXPOSURE_TIME_MS} ms")
-
- # Note: round ms to nearest us
- self.simulated_exposure_time_ms = exposure_time_ms
- self.log.info(f"exposure time set to: {exposure_time_ms} ms")
-
- @property
- def roi(self):
- return {'width_px': self.simulated_width_px,
- 'height_px': self.simulated_height_px,
- 'width_offset_px': self.simulated_width_offset_px,
- 'height_offest_px': self.simulated_height_offset_px}
-
- @roi.setter
- def roi(self, roi: dict):
-
- width_px = roi['width_px']
- height_px = roi['height_px']
-
- sensor_height_px = MAX_HEIGHT_PX
- sensor_width_px = MAX_WIDTH_PX
-
- if height_px < MIN_WIDTH_PX or \
- (height_px % DIVISIBLE_HEIGHT_PX) != 0 or \
- height_px > MAX_HEIGHT_PX:
- self.log.error(f"Height must be >{MIN_HEIGHT_PX} px, \
- <{MAX_HEIGHT_PX} px, \
- and a multiple of {DIVISIBLE_HEIGHT_PX} px!")
- raise ValueError(f"Height must be >{MIN_HEIGHT_PX} px, \
- <{MAX_HEIGHT_PX} px, \
- and a multiple of {DIVISIBLE_HEIGHT_PX} px!")
-
- if width_px < MIN_WIDTH_PX or \
- (width_px % DIVISIBLE_WIDTH_PX) != 0 or \
- width_px > MAX_WIDTH_PX:
- self.log.error(f"Width must be >{MIN_WIDTH_PX} px, \
- <{MAX_WIDTH_PX}, \
- and a multiple of {DIVISIBLE_WIDTH_PX} px!")
- raise ValueError(f"Width must be >{MIN_WIDTH_PX} px, \
- <{MAX_WIDTH_PX}, \
- and a multiple of {DIVISIBLE_WIDTH_PX} px!")
-
- # width offset must be a multiple of the divisible width in px
- centered_width_offset_px = round((sensor_width_px/2 - width_px/2)/DIVISIBLE_WIDTH_PX)*DIVISIBLE_WIDTH_PX
- # Height offset must be a multiple of the divisible height in px
- centered_height_offset_px = round((sensor_height_px/2 - height_px/2)/DIVISIBLE_HEIGHT_PX)*DIVISIBLE_HEIGHT_PX
-
- self.simulated_width_px = width_px
- self.simulated_height_px = height_px
- self.simulated_width_offset_px = centered_width_offset_px
- self.simulated_height_offset_px = centered_height_offset_px
- self.log.info(f"roi set to: {width_px} x {height_px} [width x height]")
- self.log.info(f"roi offset set to: {centered_width_offset_px} x {centered_height_offset_px} [width x height]")
-
- @property
- def pixel_type(self):
- pixel_type = self.simulated_pixel_type
- # invert the dictionary and find the abstracted key to output
- #return next(key for key, value in PIXEL_TYPES.items() if value == pixel_type)
- return pixel_type
-
- @pixel_type.setter
- def pixel_type(self, pixel_type_bits: str):
- valid = list(PIXEL_TYPES.keys())
- if pixel_type_bits not in valid:
- raise ValueError("pixel_type_bits must be one of %r." % valid)
- self.simulated_pixel_type = pixel_type_bits
- # self.simulated_pixel_type = PIXEL_TYPES[pixel_type_bits]
- #self.simulated_line_interval_us = pixel_type_bits
- # self.log.info(f"pixel type set_to: {pixel_type_bits}")
-
- @property
- def line_interval_us(self):
- return self.simulated_line_interval_us
-
- @property
- def sensor_width_px(self):
- return MAX_WIDTH_PX
-
- @property
- def sensor_height_px(self):
- return MAX_HEIGHT_PX
-
- def prepare(self):
- self.log.info('simulated camera preparing...')
- self.buffer = list()
-
- def start(self, frame_count: int, live: bool = False):
- self.log.info('simulated camera starting...')
- self.thread = Thread(target=self.generate_frames, args=(frame_count,))
- self.thread.daemon = True
- self.thread.start()
-
- def stop(self):
- self.log.info('simulated camera stopping...')
- self.thread.join()
-
- def grab_frame(self):
- while not self.buffer:
- time.sleep(0.01)
- image = self.buffer.pop(0)
- return image
-
- def get_camera_acquisition_state(self):
- """return a dict with the state of the acquisition buffers"""
- # Detailed description of constants here:
- # https://documentation.euresys.com/Products/Coaxlink/Coaxlink/en-us/Content/IOdoc/egrabber-reference/
- # namespace_gen_t_l.html#a6b498d9a4c08dea2c44566722699706e
- state = {}
- state['frame_index'] = self.frame
- state['in_buffer_size'] = len(self.buffer)
- state['out_buffer_size'] = BUFFER_SIZE_FRAMES - len(self.buffer)
- # number of underrun, i.e. dropped frames
- state['dropped_frames'] = self.dropped_frames
- state['data_rate'] = self.frame_rate*self.simulated_width_px*self.simulated_height_px*numpy.dtype(self.simulated_pixel_type).itemsize/1e6
- state['frame_rate'] = self.frame_rate
- self.log.info(f"id: {self.id}, "
- f"frame: {state['frame_index']}, "
- f"input: {state['in_buffer_size']}, "
- f"output: {state['out_buffer_size']}, "
- f"dropped: {state['dropped_frames']}, "
- f"data rate: {state['data_rate']:.2f} [MB/s], "
- f"frame rate: {state['frame_rate']:.2f} [fps].")
-
- def generate_frames(self, frame_count: int):
- self.frame = 0
- self.dropped_frames = 0
- while self.frame < frame_count:
- start_time = time.time()
- column_count = self.simulated_width_px
- row_count = self.simulated_height_px
- frame_time_s = (row_count*self.simulated_line_interval_us/1000+self.simulated_exposure_time_ms)/1000
- # image = numpy.random.randint(low=128, high=256, size=(row_count, column_count), dtype=self.simulated_pixel_type)
- image = numpy.zeros(shape=(row_count, column_count), dtype=self.simulated_pixel_type)
- while (time.time() - start_time) < frame_time_s:
- time.sleep(0.01)
- if len(self.buffer) < BUFFER_SIZE_FRAMES:
- self.buffer.append(image)
- else:
- self.dropped_frames += 1
- self.log.warning('buffer full, frame dropped.')
- self.frame += 1
- end_time = time.time()
- self.frame_rate = 1/(end_time - start_time)
diff --git a/examples/resources/simulated_laser.py b/examples/resources/simulated_laser.py
deleted file mode 100644
index 5d0cb25..0000000
--- a/examples/resources/simulated_laser.py
+++ /dev/null
@@ -1,144 +0,0 @@
-import logging
-from sympy import symbols, solve
-from serial import Serial
-from enum import Enum
-import sys
-
-# Define StrEnums if they don't yet exist.
-if sys.version_info < (3, 11):
- class StrEnum(str, Enum):
- pass
-else:
- from enum import StrEnum
-
-
-class BoolVal(StrEnum):
- OFF = "0"
- ON = "1"
-
-
-MODULATION_MODES = {
- 'off': {'external_control_mode': 'OFF', 'digital_modulation': 'OFF'},
- 'analog': {'external_control_mode': 'ON', 'digital_modulation': 'OFF'},
- 'digital': {'external_control_mode': 'OFF', 'digital_modulation': 'ON'}
-}
-TEST_PROPERTY = {
- "value0": {
- "internal": None,
- "external": 0,
- },
- "value1": {
- "on": True,
- "off": False,
- }
-}
-
-
-class SimulatedCombiner:
-
- def __init__(self, port):
- """Class for the L6CC oxxius combiner. This combiner can have LBX lasers or LCX"""
-
- self.ser = Serial
- self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
- self._PercentageSplitStatus = 0
-
- @property
- def percentage_split(self):
- """Set percentage split of lasers"""
-
- return self._PercentageSplitStatus
-
- @percentage_split.setter
- def percentage_split(self, value):
- """Get percentage split of lasers"""
- if value > 100 or value < 0:
- self.log.error(f'Impossible to set percentage spilt to {value}')
- return
- self._PercentageSplitStatus = value
-
-
-class SimulatedLaser:
-
- def __init__(self, port: Serial or str, prefix: str = '', coefficients: dict = {}):
- """Communicate with specific LBX laser in L6CC Combiner box.
-
- :param port: comm port for lasers.
- :param prefix: prefix specic to laser.
- """
-
- self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
- self.prefix = prefix
- self.ser = Serial
- self._simulated_power_setpoint_m = 10.0
- self._max_power_mw = 100.0
- self._modulation_mode = 'digital'
- self._temperature = 20.0
- self._cdrh = BoolVal.ON
- self._test_property = {"value0":"external", "value1":"on"}
-
- @property
- def power_setpoint_mw(self):
- """Power of laser in mw"""
- return self._simulated_power_setpoint_m
-
- @power_setpoint_mw.setter
- def power_setpoint_mw(self, value: float):
- self._simulated_power_setpoint_m = value
-
- @property
- def max_power_mw(self):
- """Maximum power of laser in mw"""
- return self._max_power_mw
-
- @property
- def modulation_mode(self):
- """Modulation mode of laser"""
- return self._modulation_mode
-
- @modulation_mode.setter
- def modulation_mode(self, value: str):
- if value not in MODULATION_MODES.keys():
- raise ValueError("mode must be one of %r." % MODULATION_MODES.keys())
- for attribute, state in MODULATION_MODES[value].items():
- setattr(self, attribute, state)
-
- @property
- def temperature(self):
- """Temperature of laser in Celsius"""
- return self._temperature
-
- def status(self):
- return []
-
- @property
- def cdrh(self):
- """Status of five-second safety delay"""
- return self._cdrh
-
- @cdrh.setter
- def cdrh(self, value: BoolVal or str):
- self._cdrh = value
-
- @property
- def test_property(self):
- """Test property used for UI construction"""
- return self._test_property
-
- @test_property.setter
- def test_property(self, value: dict):
-
- value0 = value['value0']
- value1 = value['value1']
-
- if value0 not in TEST_PROPERTY['value0'].keys():
- raise ValueError("mode must be one of %r." % TEST_PROPERTY['value0'].keys())
- if value1 not in TEST_PROPERTY['value1'].keys():
- raise ValueError("mode must be one of %r." % TEST_PROPERTY['value1'].keys())
- self._test_property = value
-
- def enable(self):
- pass
-
- def disable(self):
- pass
diff --git a/instruments/simulated-instrument-z-y-x-coordinate/simulated-view.py b/instruments/simulated-instrument-z-y-x-coordinate/simulated-view.py
index 89ceb78..a0772fc 100644
--- a/instruments/simulated-instrument-z-y-x-coordinate/simulated-view.py
+++ b/instruments/simulated-instrument-z-y-x-coordinate/simulated-view.py
@@ -1,14 +1,13 @@
-from qtpy.QtWidgets import QApplication, QMessageBox, QPushButton, QFileDialog
+import os
import sys
-from view.instrument_view import InstrumentView
+from pathlib import Path
+
+from qtpy.QtWidgets import QApplication
+
from view.acquisition_view import AcquisitionView
-from voxel.instruments.instrument import Instrument
+from view.instrument_view import InstrumentView
from voxel.acquisition.acquisition import Acquisition
-from pathlib import Path
-import os
-import yaml
-import inflection
-from qtpy.QtCore import Qt
+from voxel.instruments.instrument import Instrument
RESOURCES_DIR = (Path(os.path.dirname(os.path.realpath(__file__))))
ACQUISITION_YAML = RESOURCES_DIR / 'test_acquisition.yaml'
diff --git a/instruments/simulated-instrument/simulated-view.py b/instruments/simulated-instrument/simulated-view.py
index e99dbd3..d67a077 100644
--- a/instruments/simulated-instrument/simulated-view.py
+++ b/instruments/simulated-instrument/simulated-view.py
@@ -1,61 +1,78 @@
-from qtpy.QtWidgets import QApplication, QMessageBox, QPushButton, QFileDialog
-import sys
-from view.instrument_view import InstrumentView
-from view.acquisition_view import AcquisitionView
-from exaspim_control.exa_spim_instrument import ExASPIM
-from exaspim_control.exa_spim_acquisition import ExASPIMAcquisition
-from pathlib import Path
import os
-import inflection
-from ruamel.yaml import YAML
-import numpy as np
+import sys
from pathlib import Path, WindowsPath
-RESOURCES_DIR = (Path(os.path.dirname(os.path.realpath(__file__))))
-ACQUISITION_YAML = RESOURCES_DIR / 'acquisition.yaml'
-INSTRUMENT_YAML = RESOURCES_DIR / 'instrument.yaml'
-GUI_YAML = RESOURCES_DIR / 'gui_config.yaml'
+import inflection
+import numpy as np
+from exaspim_control.exa_spim_acquisition import ExASPIMAcquisition
+from exaspim_control.exa_spim_instrument import ExASPIM
+from qtpy.QtWidgets import QApplication, QFileDialog, QMessageBox, QPushButton
+from ruamel.yaml import YAML
+from view.acquisition_view import AcquisitionView
+from view.instrument_view import InstrumentView
-class SimulatedInstrumentView(InstrumentView):
- """View for ExASPIM Instrument"""
+RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__)))
+ACQUISITION_YAML = RESOURCES_DIR / "acquisition.yaml"
+INSTRUMENT_YAML = RESOURCES_DIR / "instrument.yaml"
+GUI_YAML = RESOURCES_DIR / "gui_config.yaml"
- def __init__(self, instrument, config_path: Path, log_level='INFO'):
+class SimulatedInstrumentView(InstrumentView):
+ """_summary_"""
+
+ def __init__(self, instrument, config_path: Path, log_level="INFO"):
+ """_summary_
+
+ :param instrument: _description_
+ :type instrument: _type_
+ :param config_path: _description_
+ :type config_path: Path
+ :param log_level: _description_, defaults to 'INFO'
+ :type log_level: str, optional
+ """
super().__init__(instrument, config_path, log_level)
app.aboutToQuit.connect(self.update_config_on_quit)
self.config_save_to = self.instrument.config_path
def update_config_on_quit(self):
- """Add functionality to close function to save device properties to instrument config"""
-
+ """_summary_"""
return_value = self.update_config_query()
if return_value == QMessageBox.Ok:
self.instrument.update_current_state_config()
self.instrument.save_config(self.config_save_to)
def update_config(self, device_name, device_specs):
- """Update setting in instrument config if already there
- :param device_name: name of device
- :param device_specs: dictionary dictating how device should be set up"""
-
- device_type = inflection.pluralize(device_specs['type'])
- for key in device_specs.get('settings', {}).keys():
+ """_summary_
+
+ :param device_name: _description_
+ :type device_name: _type_
+ :param device_specs: _description_
+ :type device_specs: _type_
+ """
+ device_type = inflection.pluralize(device_specs["type"])
+ for key in device_specs.get("settings", {}).keys():
device_object = getattr(self.instrument, device_type)[device_name]
- device_specs.get('settings')[key] = getattr(device_object, key)
- for subdevice_name, subdevice_specs in device_specs.get('subdevices', {}).items():
+ device_specs.get("settings")[key] = getattr(device_object, key)
+ for subdevice_name, subdevice_specs in device_specs.get("subdevices", {}).items():
self.update_config(subdevice_name, subdevice_specs)
def update_config_query(self):
- """Pop up message asking if configuration would like to be saved"""
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
- msgBox.setText(f"Do you want to update the instrument configuration file at {self.config_save_to} "
- f"to current instrument state?")
+ msgBox.setText(
+ f"Do you want to update the instrument configuration file at {self.config_save_to} "
+ f"to current instrument state?"
+ )
msgBox.setWindowTitle("Updating Configuration")
msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
- save_elsewhere = QPushButton('Change Directory')
+ save_elsewhere = QPushButton("Change Directory")
msgBox.addButton(save_elsewhere, QMessageBox.DestructiveRole)
save_elsewhere.pressed.connect(lambda: self.select_directory(True, msgBox))
@@ -63,13 +80,20 @@ def update_config_query(self):
return msgBox.exec()
def select_directory(self, pressed, msgBox):
- """Select directory"""
+ """_summary_
+ :param pressed: _description_
+ :type pressed: _type_
+ :param msgBox: _description_
+ :type msgBox: _type_
+ """
fname = QFileDialog()
folder = fname.getSaveFileName(directory=str(self.instrument.config_path))
- if folder[0] != '': # user pressed cancel
- msgBox.setText(f"Do you want to update the instrument configuration file at {folder[0]} "
- f"to current instrument state?")
+ if folder[0] != "": # user pressed cancel
+ msgBox.setText(
+ f"Do you want to update the instrument configuration file at {folder[0]} "
+ f"to current instrument state?"
+ )
self.config_save_to = folder[0]
@@ -91,4 +115,4 @@ def select_directory(self, pressed, msgBox):
instrument_view = SimulatedInstrumentView(instrument, GUI_YAML)
acquisition_view = AcquisitionView(acquisition, instrument_view)
- sys.exit(app.exec_())
\ No newline at end of file
+ sys.exit(app.exec_())
diff --git a/instruments/speakeasy-view/speakeasy_view.py b/instruments/speakeasy-view/speakeasy_view.py
index 81aac55..a89e4fe 100644
--- a/instruments/speakeasy-view/speakeasy_view.py
+++ b/instruments/speakeasy-view/speakeasy_view.py
@@ -1,15 +1,13 @@
-from qtpy.QtWidgets import QApplication
+import os
import sys
-from qtpy.QtCore import Slot
-import threading
-from time import sleep
-from view.instrument_view import InstrumentView
-from voxel.instruments.instrument import Instrument
+from pathlib import Path
+
+from qtpy.QtWidgets import QApplication
+
from view.acquisition_view import AcquisitionView
+from view.instrument_view import InstrumentView
from voxel.acquisition.acquisition import Acquisition
-from pathlib import Path
-import os
-from time import sleep
+from voxel.instruments.instrument import Instrument
RESOURCES_DIR = (Path(os.path.dirname(os.path.realpath(__file__))))
@@ -27,9 +25,5 @@
instrument_view = InstrumentView(instrument, GUI_YAML)
- # instrument_view.grab_stage_positions_worker.pause()
- # while not instrument_view.grab_stage_positions_worker.is_paused:
- # sleep(.1)
-
acquisition_view = AcquisitionView(acquisition, instrument_view)
sys.exit(app.exec_())
diff --git a/src/view/__init__.py b/src/view/__init__.py
index d458f74..24157a5 100644
--- a/src/view/__init__.py
+++ b/src/view/__init__.py
@@ -1,3 +1,3 @@
-"""View for Voxel Instruments and Acquisition"""
+"""view for voxel instruments and acquisition"""
__version__ = "0.1.0"
diff --git a/src/view/acquisition_view.py b/src/view/acquisition_view.py
index ad95a6d..025ac39 100644
--- a/src/view/acquisition_view.py
+++ b/src/view/acquisition_view.py
@@ -1,67 +1,73 @@
-import logging
import importlib
-from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties, create_widget, label_maker
-from view.widgets.acquisition_widgets.metadata_widget import MetadataWidget
-from view.widgets.acquisition_widgets.volume_plan_widget import (
- VolumePlanWidget,
- GridFromEdges,
- GridWidthHeight,
- GridRowsColumns,
-)
-from view.widgets.acquisition_widgets.volume_model import VolumeModel
-from view.widgets.acquisition_widgets.channel_plan_widget import ChannelPlanWidget
-from qtpy.QtCore import Slot, Qt
-import inflection
+import logging
+from pathlib import Path
from time import sleep
+from typing import Any, Dict, Iterator, List, Literal, Union
+
+import inflection
+import napari
+import numpy as np
+from napari.qt import get_stylesheet
+from napari.qt.threading import create_worker, thread_worker
+from napari.settings import get_settings
+from qtpy.QtCore import Qt, Slot
+from qtpy.QtGui import QFont
from qtpy.QtWidgets import (
- QGridLayout,
- QWidget,
+ QApplication,
QComboBox,
- QSizePolicy,
- QScrollArea,
QDockWidget,
+ QDoubleSpinBox,
+ QFileDialog,
+ QFrame,
+ QGridLayout,
+ QHBoxLayout,
QLabel,
- QPushButton,
- QSplitter,
QLineEdit,
- QSpinBox,
- QDoubleSpinBox,
+ QMessageBox,
QProgressBar,
+ QPushButton,
+ QScrollArea,
+ QSizePolicy,
QSlider,
- QApplication,
- QHBoxLayout,
- QFrame,
- QFileDialog,
- QMessageBox,
+ QSpinBox,
+ QSplitter,
+ QWidget,
)
-from qtpy.QtGui import QFont
-from napari.qt.threading import thread_worker, create_worker
+
+from view.widgets.acquisition_widgets.channel_plan_widget import ChannelPlanWidget
+from view.widgets.acquisition_widgets.metadata_widget import MetadataWidget
+from view.widgets.acquisition_widgets.volume_model import VolumeModel
+from view.widgets.acquisition_widgets.volume_plan_widget import (
+ GridFromEdges,
+ GridRowsColumns,
+ GridWidthHeight,
+ VolumePlanWidget,
+)
+from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, label_maker, scan_for_properties
from view.widgets.miscellaneous_widgets.q_dock_widget_title_bar import QDockWidgetTitleBar
-from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
-from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
-from pathlib import Path
-from typing import Literal, Union, Iterator
-import numpy as np
-import napari
-from napari.qt import get_stylesheet
-from napari.settings import get_settings
class AcquisitionView(QWidget):
- """ "Class to act as a general acquisition view model to voxel instrument"""
+ """
+ Class to act as a general acquisition view model to voxel instrument.
+ """
def __init__(
self,
acquisition,
instrument_view,
log_level: Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
- ):
- """
- :param acquisition: voxel acquisition object
- :param instrument_view: view object relating to instrument. Needed to lock stage
- :param log_level: level to set logger at
+ ) -> None:
"""
+ Initialize the AcquisitionView.
+ :param acquisition: Voxel acquisition object
+ :type acquisition: object
+ :param instrument_view: View object relating to instrument. Needed to lock stage
+ :type instrument_view: object
+ :param log_level: Level to set logger at
+ :type log_level: Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], optional
+ """
super().__init__()
self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
self.log.setLevel(log_level)
@@ -119,13 +125,13 @@ def __init__(
scroll.setWidget(self.metadata_widget)
scroll.setWindowTitle("Metadata")
scroll.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
- dock = QDockWidget(scroll.windowTitle(), self)
- dock.setWidget(scroll)
- dock.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
- dock.setTitleBarWidget(QDockWidgetTitleBar(dock))
- dock.setWidget(scroll)
- dock.setMinimumHeight(25)
- splitter.addWidget(dock)
+ # dock = QDockWidget(scroll.windowTitle(), self)
+ # dock.setWidget(scroll)
+ # dock.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
+ # dock.setTitleBarWidget(QDockWidgetTitleBar(dock))
+ # dock.setWidget(scroll)
+ # dock.setMinimumHeight(25)
+ splitter.addWidget(scroll)
# create dock widget for operations
for i, operation in enumerate(["writer", "file_transfer", "process", "routine"]):
@@ -136,12 +142,12 @@ def __init__(
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
scroll.setWidget(stack)
scroll.setFixedWidth(self.metadata_widget.size().width())
- dock = QDockWidget(stack.windowTitle())
- dock.setTitleBarWidget(QDockWidgetTitleBar(dock))
- dock.setWidget(scroll)
- dock.setMinimumHeight(25)
- setattr(self, f"{operation}_dock", dock)
- splitter.addWidget(dock)
+ # dock = QDockWidget(stack.windowTitle())
+ # dock.setTitleBarWidget(QDockWidgetTitleBar(dock))
+ # dock.setWidget(scroll)
+ # dock.setMinimumHeight(25)
+ setattr(self, f"{operation}_dock", scroll)
+ splitter.addWidget(scroll)
self.main_layout.addWidget(splitter, 1, 3)
self.setLayout(self.main_layout)
self.setWindowTitle("Acquisition View")
@@ -155,10 +161,11 @@ def __init__(
def create_start_button(self) -> QPushButton:
"""
- Create button to start acquisition
- :return: start button
- """
+ Create the start button.
+ :return: Start button
+ :rtype: QPushButton
+ """
start = QPushButton("Start")
start.clicked.connect(self.start_acquisition)
start.setStyleSheet("background-color: green")
@@ -166,22 +173,21 @@ def create_start_button(self) -> QPushButton:
def create_stop_button(self) -> QPushButton:
"""
- Create button to stop acquisition
- :return: stop button
- """
+ Create the stop button.
+ :return: Stop button
+ :rtype: QPushButton
+ """
stop = QPushButton("Stop")
stop.clicked.connect(self.acquisition.stop_acquisition)
stop.setStyleSheet("background-color: red")
stop.setDisabled(True)
-
return stop
def start_acquisition(self) -> None:
"""
- Start acquisition and disable widgets
+ Start the acquisition process.
"""
-
# add tiles to acquisition config
self.update_tiles()
@@ -223,9 +229,8 @@ def start_acquisition(self) -> None:
def acquisition_ended(self) -> None:
"""
- Re-enable UI's and threads after acquisition has ended
+ Handle the end of the acquisition process.
"""
-
# enable acquisition view
self.start_button.setEnabled(True)
self.metadata_widget.setEnabled(True)
@@ -240,7 +245,6 @@ def acquisition_ended(self) -> None:
daq.tasks = self.config["instrument_view"]["livestream_tasks"][daq_name]["tasks"]
# unanchor grid in volume widget
- # anchor grid in volume widget
for anchor, widget in zip(self.volume_plan.anchor_widgets, self.volume_plan.grid_offset_widgets):
anchor.setChecked(False)
widget.setDisabled(False)
@@ -258,11 +262,13 @@ def acquisition_ended(self) -> None:
def stack_device_widgets(self, device_type: str) -> QWidget:
"""
- Stack like device widgets in layout and hide/unhide with combo box
- :param device_type: type of device being stacked
- :return: widget containing all widgets pertaining to device type stacked ontop of each other
- """
+ Stack device widgets.
+ :param device_type: Type of device
+ :type device_type: str
+ :return: Stacked device widgets
+ :rtype: QWidget
+ """
device_widgets = {
f"{inflection.pluralize(device_type)} {device_name}": create_widget("V", **widgets)
for device_name, widgets in getattr(self, f"{device_type}_widgets").items()
@@ -287,13 +293,15 @@ def stack_device_widgets(self, device_type: str) -> QWidget:
return overlap_widget
@staticmethod
- def hide_devices(text: str, device_widgets: dict) -> None:
- """
- Hide device widget if not selected in combo box
- :param text: selected text of combo box
- :param device_widgets: dictionary of widget groups
+ def hide_devices(text: str, device_widgets: Dict[str, QWidget]) -> None:
"""
+ Hide or show device widgets based on the selected text.
+ :param text: Selected text
+ :type text: str
+ :param device_widgets: Dictionary of device widgets
+ :type device_widgets: dict
+ """
for name, widget in device_widgets.items():
if name != text:
widget.setVisible(False)
@@ -302,25 +310,28 @@ def hide_devices(text: str, device_widgets: dict) -> None:
def create_metadata_widget(self) -> MetadataWidget:
"""
- Create custom widget for metadata in config
- :return: widget for metadata
- """
+ Create the metadata widget.
+ :return: Metadata widget
+ :rtype: MetadataWidget
+ """
metadata_widget = MetadataWidget(self.acquisition.metadata)
metadata_widget.ValueChangedInside[str].connect(
lambda name: setattr(self.acquisition.metadata, name, getattr(metadata_widget, name))
)
for name, widget in metadata_widget.property_widgets.items():
widget.setToolTip("") # reset tooltips
- metadata_widget.setWindowTitle(f"Metadata")
+ metadata_widget.setWindowTitle("Metadata")
return metadata_widget
def create_acquisition_widget(self) -> QSplitter:
"""
- Create widget to visualize acquisition grid
- :return: splitter widget containing the volume model, volume plan, and channel plan widget
- """
+ Create the acquisition widget.
+ :raises KeyError: If the coordinate plane does not match instrument axes in tiling_stages
+ :return: Acquisition widget
+ :rtype: QSplitter
+ """
# find limits of all axes
lim_dict = {}
# add tiling stages
@@ -409,10 +420,11 @@ def create_acquisition_widget(self) -> QSplitter:
def channel_plan_changed(self, channel: str) -> None:
"""
- Handle channel being added to scan
- :param channel: channel added
- """
+ Update the channel plan when a channel is changed.
+ :param channel: The name of the channel that was changed
+ :type channel: str
+ """
tile_order = [[t.row, t.col] for t in self.volume_plan.value()]
if len(tile_order) != 0:
self.channel_plan.add_channel_rows(channel, tile_order)
@@ -420,10 +432,11 @@ def channel_plan_changed(self, channel: str) -> None:
def volume_plan_changed(self, value: Union[GridRowsColumns, GridFromEdges, GridWidthHeight]) -> None:
"""
- Update channel plan and volume model when volume plan is changed
- :param value: new value from volume_plan
- """
+ Update the volume plan when it is changed.
+ :param value: The new value of the volume plan
+ :type value: Union[GridRowsColumns, GridFromEdges, GridWidthHeight]
+ """
tile_volumes = self.volume_plan.scan_ends - self.volume_plan.scan_starts
# update volume model
@@ -444,15 +457,16 @@ def volume_plan_changed(self, value: Union[GridRowsColumns, GridFromEdges, GridW
def update_tiles(self) -> None:
"""
- Update config with the latest tiles
+ Update the tiles in the acquisition configuration.
"""
-
self.acquisition.config["acquisition"]["tiles"] = self.create_tile_list()
- def move_stage(self, fov_position: list[float, float, float]) -> None:
+ def move_stage(self, fov_position: List[float]) -> None:
"""
- Slot for moving stage when fov_position is changed internally by grid_widget
- :param fov_position: new fov position to move to
+ Move the stage to the specified field of view position.
+
+ :param fov_position: The field of view position to move to
+ :type fov_position: list[float]
"""
scalar_coord_plane = [x.strip("-") for x in self.coordinate_plane]
stage_names = {stage.instrument_axis: name for name, stage in self.instrument.tiling_stages.items()}
@@ -464,9 +478,8 @@ def move_stage(self, fov_position: list[float, float, float]) -> None:
def stop_stage(self) -> None:
"""
- Slot for stop stage
+ Stop the stage movement.
"""
-
for name, stage in {
**getattr(self.instrument, "scanning_stages", {}),
**getattr(self.instrument, "tiling_stages", {}),
@@ -475,18 +488,20 @@ def stop_stage(self) -> None:
def setup_fov_position(self) -> None:
"""
- Set up live position thread
+ Set up the field of view position.
"""
-
self.grab_fov_positions_worker = self.grab_fov_positions()
self.grab_fov_positions_worker.yielded.connect(lambda pos: setattr(self.volume_plan, "fov_position", pos))
self.grab_fov_positions_worker.yielded.connect(lambda pos: setattr(self.volume_model, "fov_position", pos))
self.grab_fov_positions_worker.start()
@thread_worker
- def grab_fov_positions(self) -> Iterator[list[float, float, float]]:
+ def grab_fov_positions(self) -> Iterator[List[float]]:
"""
- Grab stage position from all stage objects and yield positions
+ Grab the field of view positions.
+
+ :yield: The field of view positions
+ :rtype: Iterator[list[float]]
"""
scalar_coord_plane = [x.strip("-") for x in self.coordinate_plane]
while True: # best way to do this or have some sort of break?
@@ -501,19 +516,22 @@ def grab_fov_positions(self) -> Iterator[list[float, float, float]]:
try:
pos = stage.position_mm
fov_pos[index] = pos if pos is not None else self.volume_plan.fov_position[index]
- except ValueError as e: # Tigerbox sometime coughs up garbage. Locking issue?
+ except ValueError: # Tigerbox sometime coughs up garbage. Locking issue?
pass
sleep(0.1)
yield fov_pos
def create_operation_widgets(self, device_name: str, operation_name: str, operation_specs: dict) -> None:
"""
- Create widgets based on operation dictionary attributes from instrument or acquisition
- :param operation_name: name of operation
- :param device_name: name of device correlating to operation
- :param operation_specs: dictionary describing set up of operation
- """
+ Create widgets for the specified operation.
+ :param device_name: The name of the device
+ :type device_name: str
+ :param operation_name: The name of the operation
+ :type operation_name: str
+ :param operation_specs: The specifications of the operation
+ :type operation_specs: dict
+ """
operation_type = operation_specs["type"]
operation = getattr(self.acquisition, inflection.pluralize(operation_type))[device_name][operation_name]
@@ -575,11 +593,13 @@ def create_operation_widgets(self, device_name: str, operation_name: str, operat
def update_acquisition_layer(self, image: np.ndarray, camera_name: str) -> None:
"""
- Update viewer with latest frame taken during acquisition
- :param image: numpy array to add to viewer
- :param camera_name: name of camera that image came off
- """
+ Update the acquisition layer with the specified image.
+ :param image: The image to update the layer with
+ :type image: np.ndarray
+ :param camera_name: The name of the camera
+ :type camera_name: str
+ """
if image is not None:
# TODO: How to get current channel
layer_name = f"{camera_name}"
@@ -590,27 +610,33 @@ def update_acquisition_layer(self, image: np.ndarray, camera_name: str) -> None:
layer = self.instrument_view.viewer.add_image(image, name=layer_name)
@thread_worker
- def grab_property_value(self, device: object, property_name: str, widget) -> Iterator:
- """
- Grab value of property and yield
- :param device: device to grab property from
- :param property_name: name of property to get
- :param widget: corresponding device widget
- :return: value of property and widget to update
+ def grab_property_value(self, device: object, property_name: str, widget: Any) -> Iterator:
"""
+ Grab the value of the specified property from the device.
+ :param device: The device to grab the property value from
+ :type device: object
+ :param property_name: The name of the property
+ :type property_name: str
+ :param widget: The widget to update with the property value
+ :type widget: Any
+ :yield: The property value and the widget
+ :rtype: Iterator
+ """
while True: # best way to do this or have some sort of break?
sleep(1)
value = getattr(device, property_name)
yield value, widget
- def update_property_value(self, value, widget) -> None:
- """
- Update stage position in stage widget
- :param widget: widget to update
- :param value: value to update with
+ def update_property_value(self, value: Any, widget: Any) -> None:
"""
+ Update the widget with the specified property value.
+ :param value: The property value
+ :type value: Any
+ :param widget: The widget to update
+ :type widget: Any
+ """
try:
if type(widget) in [QLineEdit, QScrollableLineEdit]:
widget.setText(str(value))
@@ -625,14 +651,17 @@ def update_property_value(self, value, widget) -> None:
pass
@Slot(str)
- def operation_property_changed(self, attr_name: str, operation: object, widget) -> None:
- """
- Slot to signal when operation widget has been changed
- :param widget: widget object relating to operation
- :param operation: operation object
- :param attr_name: name of attribute
+ def operation_property_changed(self, attr_name: str, operation: object, widget: Any) -> None:
"""
+ Handle changes to the operation property.
+ :param attr_name: The name of the attribute that changed
+ :type attr_name: str
+ :param operation: The operation object
+ :type operation: object
+ :param widget: The widget that changed
+ :type widget: Any
+ """
name_lst = attr_name.split(".")
self.log.debug(f"widget {attr_name} changed to {getattr(widget, name_lst[0])}")
value = getattr(widget, name_lst[0])
@@ -653,12 +682,13 @@ def operation_property_changed(self, attr_name: str, operation: object, widget)
self.log.warning(f"{attr_name} can't be mapped into operation properties due to {e}")
pass
- def create_tile_list(self) -> list:
- """
- Return a list of tiles for a scan
- :return: list of tiles
+ def create_tile_list(self) -> List[Dict[str, Any]]:
"""
+ Create a list of tiles for the acquisition.
+ :return: A list of tiles
+ :rtype: list[dict[str, Any]]
+ """
tiles = []
tile_slice = slice(self.volume_plan.start, self.volume_plan.stop)
value = self.volume_plan.value()
@@ -673,14 +703,17 @@ def create_tile_list(self) -> list:
tiles.append(self.write_tile(ch, tile))
return tiles
- def write_tile(self, channel: str, tile) -> dict:
- """
- Write dictionary describing tile parameters
- :param channel: channel the tile is in
- :param tile: tile object
- :return
+ def write_tile(self, channel: str, tile: Any) -> Dict[str, Any]:
"""
+ Write the tile information for the specified channel.
+ :param channel: The name of the channel
+ :type channel: str
+ :param tile: The tile information
+ :type tile: Any
+ :return: A dictionary containing the tile information
+ :rtype: dict[str, Any]
+ """
row, column = tile.row, tile.col
table_row = self.volume_plan.tile_table.findItems(str([row, column]), Qt.MatchExactly)[0].row()
@@ -723,17 +756,19 @@ def write_tile(self, channel: str, tile) -> dict:
def update_config_on_quit(self) -> None:
"""
- Add functionality to close function to save device properties to instrument config
+ Update the acquisition configuration when quitting.
"""
-
return_value = self.update_config_query()
if return_value == QMessageBox.Ok:
self.acquisition.update_current_state_config()
self.acquisition.save_config(self.config_save_to)
- def update_config_query(self) -> None:
+ def update_config_query(self) -> int:
"""
- Pop up message asking if configuration would like to be saved
+ Show a dialog to confirm updating the acquisition configuration.
+
+ :return: The result of the dialog
+ :rtype: int
"""
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
@@ -752,11 +787,13 @@ def update_config_query(self) -> None:
def select_directory(self, pressed: bool, msgBox: QMessageBox) -> None:
"""
- Select directory
- :param pressed: boolean for button press
- :param msgBox:
- """
+ Select a directory to save the configuration file.
+ :param pressed: Whether the button was pressed
+ :type pressed: bool
+ :param msgBox: The message box
+ :type msgBox: QMessageBox
+ """
fname = QFileDialog()
folder = fname.getSaveFileName(directory=str(self.acquisition.config_path))
if folder[0] != "": # user pressed cancel
@@ -768,9 +805,8 @@ def select_directory(self, pressed: bool, msgBox: QMessageBox) -> None:
def close(self) -> None:
"""
- Close operations and end threads
+ Close the acquisition view and all associated resources.
"""
-
for worker in self.property_workers:
worker.quit()
self.grab_fov_positions_worker.quit()
diff --git a/src/view/instrument_view.py b/src/view/instrument_view.py
index 92e6ed9..1c9fda9 100644
--- a/src/view/instrument_view.py
+++ b/src/view/instrument_view.py
@@ -1,54 +1,51 @@
-from ruamel.yaml import YAML
-from qtpy.QtCore import Slot, Signal, Qt
-from qtpy.QtGui import QMouseEvent
-from pathlib import Path
+import datetime
import importlib
-from view.widgets.base_device_widget import (
- BaseDeviceWidget,
- create_widget,
- pathGet,
- scan_for_properties,
- disable_button,
-)
+import inspect
+import logging
+from pathlib import Path
+from time import sleep
+from typing import Any, Iterator, Literal, Tuple, Type, Union
+
+import inflection
+import napari
+import numpy as np
+import tifffile
+from napari.qt.threading import create_worker, thread_worker
+from napari.utils.theme import get_theme
+from qtpy.QtCore import Qt, Signal, Slot
+from qtpy.QtGui import QMouseEvent
from qtpy.QtWidgets import (
- QStyle,
- QRadioButton,
- QWidget,
+ QApplication,
QButtonGroup,
- QSlider,
- QGridLayout,
QComboBox,
- QApplication,
- QVBoxLayout,
- QLabel,
+ QFileDialog,
QFrame,
- QSizePolicy,
- QLineEdit,
- QSpinBox,
- QDoubleSpinBox,
+ QGridLayout,
+ QLabel,
QMessageBox,
QPushButton,
- QFileDialog,
+ QRadioButton,
QScrollArea,
+ QSizePolicy,
+ QStyle,
+ QVBoxLayout,
+ QWidget,
+)
+from ruamel.yaml import YAML
+
+from view.widgets.base_device_widget import (
+ BaseDeviceWidget,
+ create_widget,
+ disable_button,
+ pathGet,
+ scan_for_properties,
)
-import tifffile
-from napari.qt.threading import thread_worker, create_worker
-from napari.utils.theme import get_theme
-import napari
-import datetime
-from time import sleep
-import logging
-import inflection
-import inspect
-from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
-from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
-from view.widgets.miscellaneous_widgets.q_dock_widget_title_bar import QDockWidgetTitleBar
-import numpy as np
-from typing import Literal, Union, Iterator
class InstrumentView(QWidget):
- """ "Class to act as a general instrument view model to voxel instrument"""
+ """
+ Class to act as a general instrument view model to voxel instrument.
+ """
snapshotTaken = Signal((np.ndarray, list))
contrastChanged = Signal((np.ndarray, list))
@@ -58,11 +55,16 @@ def __init__(
instrument,
config_path: Path,
log_level: Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
- ):
+ ) -> None:
"""
- :param instrument: voxel like instrument object
- :param config_path: path to gui config yaml
- :param log_level: level to set logger
+ Initialize the InstrumentView.
+
+ :param instrument: The instrument to be used
+ :type instrument: Instrument
+ :param config_path: The path to the configuration file
+ :type config_path: Path
+ :param log_level: The logging level, defaults to "INFO"
+ :type log_level: Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], optional
"""
super().__init__()
@@ -127,9 +129,8 @@ def __init__(
def setup_daqs(self) -> None:
"""
- Initialize daqs with livestreaming tasks if different from data acquisition tasks
+ Initialize daqs with livestreaming tasks if different from data acquisition tasks.
"""
-
for daq_name, daq in self.instrument.daqs.items():
if daq_name in self.config["instrument_view"].get("livestream_tasks", {}).keys():
daq.tasks = self.config["instrument_view"]["livestream_tasks"][daq_name]["tasks"]
@@ -143,9 +144,8 @@ def setup_daqs(self) -> None:
def setup_stage_widgets(self) -> None:
"""
- Arrange stage position and joystick widget
+ Arrange stage position and joystick widget.
"""
-
stage_widgets = []
for name, widget in {
**self.tiling_stage_widgets,
@@ -176,9 +176,8 @@ def setup_stage_widgets(self) -> None:
def setup_laser_widgets(self) -> None:
"""
- Arrange laser widgets
+ Setup laser widgets.
"""
-
laser_widgets = []
for name, widget in self.laser_widgets.items():
label = QLabel(name)
@@ -194,9 +193,8 @@ def setup_laser_widgets(self) -> None:
def setup_daq_widgets(self) -> None:
"""
- Setup saving to config if widget is from device-widget repo
+ Setup saving to config if widget is from device-widget repo.
"""
-
for daq_name, daq_widget in self.daq_widgets.items():
# if daq_widget is BaseDeviceWidget or inherits from it, update waveforms when gui is changed
if type(daq_widget) == BaseDeviceWidget or BaseDeviceWidget in type(daq_widget).__bases__:
@@ -216,11 +214,13 @@ def setup_daq_widgets(self) -> None:
def stack_device_widgets(self, device_type: str) -> QWidget:
"""
- Stack like device widgets in layout and hide/unhide with combo box
- :param device_type: type of device being stacked
- :return: widget containing all widgets pertaining to device type stacked ontop of each other
- """
+ Stack device widgets.
+ :param device_type: Type of device
+ :type device_type: str
+ :return: Stacked device widgets
+ :rtype: QWidget
+ """
device_widgets = getattr(self, f"{device_type}_widgets")
overlap_layout = QGridLayout()
overlap_layout.addWidget(QWidget(), 1, 0) # spacer widget
@@ -241,11 +241,13 @@ def stack_device_widgets(self, device_type: str) -> QWidget:
def hide_devices(self, text: str, device_type: str) -> None:
"""
- Hide device widget if not selected in combo box
- :param text: selected text of combo box
- :param device_type: type of device related to combo box
- """
+ Hide or show device widgets based on the selected text.
+ :param text: Selected text
+ :type text: str
+ :param device_type: Type of device
+ :type device_type: str
+ """
device_widgets = getattr(self, f"{device_type}_widgets")
for name, widget in device_widgets.items():
if name != text:
@@ -255,10 +257,11 @@ def hide_devices(self, text: str, device_type: str) -> None:
def write_waveforms(self, daq) -> None:
"""
- Write waveforms if livestreaming is on
- :param daq: daq object
- """
+ Write waveforms to the DAQ.
+ :param daq: Data acquisition device
+ :type daq: _type_
+ """
if self.grab_frames_worker.is_running: # if currently livestreaming
if daq.ao_task is not None:
daq.generate_waveforms("ao", self.livestream_channel)
@@ -267,15 +270,18 @@ def write_waveforms(self, daq) -> None:
daq.generate_waveforms("do", self.livestream_channel)
daq.write_do_waveforms(rereserve_buffer=False)
- def update_config_waveforms(self, daq_widget, daq_name: str, attr_name: str) -> None:
- """
- If waveforms are changed in gui, apply changes to livestream_tasks and data_acquisition_tasks if
- applicable
- :param daq_widget: widget pertaining to daq object
- :param daq_name: name of daq
- :param attr_name: waveform attribute to update
+ def update_config_waveforms(self, daq_widget: Type, daq_name: str, attr_name: str) -> None:
"""
+ Update the configuration waveforms.
+ :param daq_widget: DAQ widget
+ :type daq_widget: Type
+ :param daq_name: Name of the DAQ
+ :type daq_name: str
+ :param attr_name: Attribute name
+ :type attr_name: str
+ :raises KeyError: If the attribute path is not valid
+ """
path = attr_name.split(".")
value = getattr(daq_widget, attr_name)
self.log.debug(f"{daq_name} {attr_name} changed to {getattr(daq_widget, path[0])}")
@@ -301,19 +307,17 @@ def update_config_waveforms(self, daq_widget, daq_name: str, attr_name: str) ->
f"be reflected in acquisition"
)
- def setup_filter_wheel_widgets(self):
+ def setup_filter_wheel_widgets(self) -> None:
"""
- Stack filter wheels
+ Stack filter wheels.
"""
-
stacked = self.stack_device_widgets("filter_wheel")
self.viewer.window.add_dock_widget(stacked, area="bottom", name="Filter Wheels")
- def setup_camera_widgets(self):
+ def setup_camera_widgets(self) -> None:
"""
- Setup live view and snapshot button
+ Setup live view and snapshot button.
"""
-
for camera_name, camera_widget in self.camera_widgets.items():
# Add functionality to snapshot button
snapshot_button = getattr(camera_widget, "snapshot_button", QPushButton())
@@ -333,10 +337,11 @@ def setup_camera_widgets(self):
def toggle_live_button(self, camera_name: str) -> None:
"""
- Toggle text and functionality of live button when pressed
- :param camera_name: name of camera to set up
- """
+ Toggle the live button for the camera.
+ :param camera_name: Name of the camera
+ :type camera_name: str
+ """
live_button = getattr(self.camera_widgets[camera_name], "live_button", QPushButton())
live_button.disconnect()
if live_button.text() == "Live":
@@ -353,17 +358,19 @@ def toggle_live_button(self, camera_name: str) -> None:
live_button.pressed.connect(lambda button=live_button: disable_button(button))
live_button.pressed.connect(lambda camera=camera_name: self.toggle_live_button(camera_name))
- def setup_live(self, camera_name: str, frames=float("inf")) -> None:
- """
- Set up for either livestream or snapshot
- :param camera_name: name of camera to set up
- :param frames: how many frames to take
+ def setup_live(self, camera_name: str, frames: float = float("inf")) -> None:
"""
+ Setup live view for the camera.
+ :param camera_name: Name of the camera
+ :type camera_name: str
+ :param frames: Number of frames to capture, defaults to float("inf")
+ :type frames: float, optional
+ """
if self.grab_frames_worker.is_running:
if frames == 1: # create snapshot layer with the latest image
layer = self.viewer.layers[f"{camera_name} {self.livestream_channel}"]
- image = layer.data[0] if layer.multiscale else image.data
+ image = layer.data[0] if layer.multiscale else layer.data
self.update_layer((image, camera_name), snapshot=True)
return
@@ -379,6 +386,7 @@ def setup_live(self, camera_name: str, frames=float("inf")) -> None:
self.instrument.cameras[camera_name].prepare()
self.instrument.cameras[camera_name].start(frames)
+ print(f"Starting live view for {camera_name}")
for laser in self.channels[self.livestream_channel].get("lasers", []):
self.log.info(f"Enabling laser {laser}")
@@ -405,10 +413,11 @@ def setup_live(self, camera_name: str, frames=float("inf")) -> None:
def dismantle_live(self, camera_name: str) -> None:
"""
- Safely shut down live
- :param camera_name: name of camera to shut down live
- """
+ Dismantle the live view for the camera.
+ :param camera_name: Name of the camera
+ :type camera_name: str
+ """
self.instrument.cameras[camera_name].abort()
for daq_name, daq in self.instrument.daqs.items():
daq.stop()
@@ -416,26 +425,32 @@ def dismantle_live(self, camera_name: str) -> None:
self.instrument.lasers[laser_name].disable()
@thread_worker
- def grab_frames(self, camera_name: str, frames=float("inf")) -> Iterator[tuple[np.ndarray, str]]:
- """
- Grab frames from camera
- :param frames: how many frames to take
- :param camera_name: name of camera
+ def grab_frames(self, camera_name: str, frames: float = float("inf")) -> Iterator[Tuple[np.ndarray, str]]:
"""
+ Grab frames from the camera.
+ :param camera_name: Name of the camera
+ :type camera_name: str
+ :param frames: Number of frames to capture, defaults to float("inf")
+ :type frames: float, optional
+ :yield: Tuple containing the image and camera name
+ :rtype: Iterator[Tuple[np.ndarray, str]]
+ """
i = 0
while i < frames: # while loop since frames can == inf
sleep(0.1)
yield self.instrument.cameras[camera_name].grab_frame(), camera_name
i += 1
- def update_layer(self, args, snapshot: bool = False) -> None:
- """
- Update viewer with new camera frame
- :param args: tuple of image and camera name
- :param snapshot: if image taken is a snapshot or not
+ def update_layer(self, args: Tuple[np.ndarray, str], snapshot: bool = False) -> None:
"""
+ Update the layer with the captured image.
+ :param args: Tuple containing the image and camera name
+ :type args: Tuple[np.ndarray, str]
+ :param snapshot: Whether the image is a snapshot, defaults to False
+ :type snapshot: bool, optional
+ """
(image, camera_name) = args
if image is not None:
@@ -454,7 +469,7 @@ def update_layer(self, args, snapshot: bool = False) -> None:
if snapshot: # emit signal if snapshot
image = image if not layer.multiscale else image[-3]
self.snapshotTaken.emit(image, layer.contrast_limits)
- if layer.multiscale == True: # emit most down sampled image if multiscale
+ if layer.multiscale: # emit most down sampled image if multiscale
layer.events.contrast_limits.connect(
lambda event: self.contrastChanged.emit(layer.data[-3], layer.contrast_limits)
)
@@ -468,11 +483,13 @@ def save_image(
layer: Union[napari.layers.image.image.Image, list[napari.layers.image.image.Image]], event: QMouseEvent
) -> None:
"""
- Save image in viewer by right-clicking viewer
- :param layer: layer that was pressed
- :param event: mouse event
- """
+ Save the image to a file.
+ :param layer: Image layer
+ :type layer: Union[napari.layers.image.image.Image, list[napari.layers.image.image.Image]]
+ :param event: Mouse event
+ :type event: QMouseEvent
+ """
if event.button == 2: # Left click
if layer.multiscale:
image = layer.data[0]
@@ -490,9 +507,8 @@ def save_image(
def setup_channel_widget(self) -> None:
"""
- Create widget to select which laser to livestream with
+ Create widget to select which laser to livestream with.
"""
-
widget = QWidget()
widget_layout = QVBoxLayout()
@@ -509,11 +525,13 @@ def setup_channel_widget(self) -> None:
def change_channel(self, checked: bool, channel: str) -> None:
"""
- Update livestream_channel to newly selected channel
- :param channel: name of channel
- :param checked: if button is checked (True) or unchecked(False)
- """
+ Change the livestream channel.
+ :param checked: Whether the channel is checked
+ :type checked: bool
+ :param channel: Name of the channel
+ :type channel: str
+ """
if checked:
if self.grab_frames_worker.is_running: # livestreaming is going
for old_laser_name in self.channels[self.livestream_channel].get("lasers", []):
@@ -533,11 +551,13 @@ def change_channel(self, checked: bool, channel: str) -> None:
def create_device_widgets(self, device_name: str, device_specs: dict) -> None:
"""
- Create widgets based on device dictionary attributes from instrument or acquisition
- :param device_name: name of device
- :param device_specs: dictionary dictating how device should be set up
- """
+ Create widgets for the specified device.
+ :param device_name: Name of the device
+ :type device_name: str
+ :param device_specs: Specifications of the device
+ :type device_specs: dict
+ """
device_type = device_specs["type"]
device = getattr(self.instrument, inflection.pluralize(device_type))[device_name]
@@ -575,15 +595,21 @@ def create_device_widgets(self, device_name: str, device_specs: dict) -> None:
gui.setWindowTitle(f"{device_type} {device_name}")
@thread_worker
- def grab_property_value(self, device: object, property_name: str, device_widget) -> Iterator:
- """
- Grab value of property and yield
- :param device: device to grab property from
- :param property_name: name of property to get
- :param device_widget: widget of entire device that is the parent of property widget
- :return: value of property and widget to update
+ def grab_property_value(
+ self, device: object, property_name: str, device_widget: Type
+ ) -> Iterator[Tuple[Any, Type, str]]:
"""
+ Grab the value of a property from a device.
+ :param device: The device to grab the property value from
+ :type device: object
+ :param property_name: The name of the property to grab
+ :type property_name: str
+ :param device_widget: The widget associated with the device
+ :type device_widget: Type
+ :yield: The property value, device widget, and property name
+ :rtype: Iterator[Tuple[Any, Type, str]]
+ """
while True: # best way to do this or have some sort of break?
sleep(0.5)
try:
@@ -592,28 +618,34 @@ def grab_property_value(self, device: object, property_name: str, device_widget)
value = None
yield value, device_widget, property_name
- def update_property_value(self, value, device_widget, property_name: str) -> None:
- """
- Update stage position in stage widget
- :param device_widget: widget of entire device that is the parent of property widget
- :param value: value to update with
- :param property_name: name of property to set
+ def update_property_value(self, value: Any, device_widget: Type, property_name: str) -> None:
"""
+ Update the widget with the property value.
+ :param value: The property value
+ :type value: Any
+ :param device_widget: The widget associated with the device
+ :type device_widget: Type
+ :param property_name: The name of the property
+ :type property_name: str
+ """
try:
setattr(device_widget, property_name, value) # setting attribute value will update widget
except (RuntimeError, AttributeError): # Pass when window's closed or widget doesn't have position_mm_widget
pass
@Slot(str)
- def device_property_changed(self, attr_name: str, device: object, widget) -> None:
- """
- Slot to signal when device widget has been changed
- :param widget: widget object relating to device
- :param device: device object
- :param attr_name: name of attribute
+ def device_property_changed(self, attr_name: str, device: object, widget: Type) -> None:
"""
+ Handle changes to the device property.
+ :param attr_name: The name of the attribute that changed
+ :type attr_name: str
+ :param device: The device object
+ :type device: object
+ :param widget: The widget that changed
+ :type widget: Type
+ """
name_lst = attr_name.split(".")
self.log.debug(f"widget {attr_name} changed to {getattr(widget, name_lst[0])}")
value = getattr(widget, name_lst[0])
@@ -645,9 +677,8 @@ def device_property_changed(self, attr_name: str, device: object, widget) -> Non
def add_undocked_widgets(self) -> None:
"""
- Add undocked widget so all windows close when closing napari viewer
+ Add undocked widget so all windows close when closing napari viewer.
"""
-
widgets = []
for key, dictionary in self.__dict__.items():
if "_widgets" in key:
@@ -662,10 +693,11 @@ def add_undocked_widgets(self) -> None:
def setDisabled(self, disable: bool) -> None:
"""
- Enable/disable viewer
- :param disable: boolean specifying whether to disable
- """
+ Disable or enable all widgets.
+ :param disable: Whether to disable the widgets
+ :type disable: bool
+ """
widgets = []
for key, dictionary in self.__dict__.items():
if "_widgets" in key:
@@ -678,9 +710,8 @@ def setDisabled(self, disable: bool) -> None:
def update_config_on_quit(self) -> None:
"""
- Add functionality to close function to save device properties to instrument config
+ Add functionality to close function to save device properties to instrument config.
"""
-
return_value = self.update_config_query()
if return_value == QMessageBox.Ok:
self.instrument.update_current_state_config()
@@ -688,8 +719,10 @@ def update_config_on_quit(self) -> None:
def update_config_query(self) -> Literal[0, 1]:
"""
- Pop up message asking if configuration would like to be saved
- :return: user reply to message box
+ Show a dialog to confirm updating the instrument configuration.
+
+ :return: The result of the dialog
+ :rtype: Literal[0, 1]
"""
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
@@ -708,11 +741,13 @@ def update_config_query(self) -> Literal[0, 1]:
def select_directory(self, pressed: bool, msgBox: QMessageBox) -> None:
"""
- Select directory
- :param pressed: boolean for button press
- :param msgBox:
- """
+ Select a directory to save the configuration file.
+ :param pressed: Whether the button was pressed
+ :type pressed: bool
+ :param msgBox: The message box
+ :type msgBox: QMessageBox
+ """
fname = QFileDialog()
folder = fname.getSaveFileName(directory=str(self.instrument.config_path))
if folder[0] != "": # user pressed cancel
@@ -724,9 +759,8 @@ def select_directory(self, pressed: bool, msgBox: QMessageBox) -> None:
def close(self) -> None:
"""
- Close instruments and end threads
+ Close instruments and end threads.
"""
-
for worker in self.property_workers:
worker.quit()
self.grab_frames_worker.quit()
diff --git a/src/view/widgets/acquisition_widgets/channel_plan_widget.py b/src/view/widgets/acquisition_widgets/channel_plan_widget.py
index a35bd55..0fca148 100644
--- a/src/view/widgets/acquisition_widgets/channel_plan_widget.py
+++ b/src/view/widgets/acquisition_widgets/channel_plan_widget.py
@@ -1,38 +1,62 @@
-from qtpy.QtWidgets import QTabWidget, QTabBar, QWidget, QPushButton, \
- QMenu, QToolButton, QAction, QTableWidget, QTableWidgetItem, QComboBox, QSpinBox
-from view.widgets.miscellaneous_widgets.q_item_delegates import QSpinItemDelegate, QTextItemDelegate, QComboItemDelegate
-from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
-from view.widgets.base_device_widget import label_maker
-import numpy as np
-from qtpy.QtCore import Signal, Qt
-from inflection import singularize
+import inspect
from math import isnan
+
+import numpy as np
import pint
-import inspect
+from inflection import singularize
+from qtpy.QtCore import Qt, Signal
+from qtpy.QtGui import QMouseEvent
+from qtpy.QtWidgets import (
+ QAction,
+ QComboBox,
+ QMenu,
+ QPushButton,
+ QSpinBox,
+ QTabBar,
+ QTableWidget,
+ QTableWidgetItem,
+ QTabWidget,
+ QToolButton,
+ QWidget,
+)
+
+from view.instrument_view import InstrumentView
+from view.widgets.base_device_widget import label_maker
+from view.widgets.miscellaneous_widgets.q_item_delegates import QComboItemDelegate, QSpinItemDelegate, QTextItemDelegate
+from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
+
+
class ChannelPlanWidget(QTabWidget):
- """Widget defining parameters per tile per channel """
+ """
+ Widget defining parameters per tile per channel.
+ """
channelAdded = Signal([str])
channelChanged = Signal()
- def __init__(self, instrument_view, channels: dict, properties: dict, unit: str = 'um'):
+ def __init__(self, instrument_view: InstrumentView, channels: dict, properties: dict, unit: str = "um"):
"""
- :param instrument_view: view associated with instrument
- :param channels: dictionary defining channels for instrument
- :param properties: allowed prop for devices
- :param unit: unit of all values
+ Initialize the ChannelPlanWidget.
+
+ :param instrument_view: The instrument view
+ :type instrument_view: InstrumentView
+ :param channels: Dictionary of channels
+ :type channels: dict
+ :param properties: Dictionary of properties
+ :type properties: dict
+ :param unit: Unit of measurement, defaults to 'um'
+ :type unit: str, optional
"""
-
super().__init__()
self.possible_channels = channels
self.channels = []
self.properties = properties
- self.column_data_types = {'step size [um]': float, 'steps': int, 'prefix': str}
+ self.column_data_types = {"step size [um]": float, "steps": int, "prefix": str}
# setup units for step size and step calculation
unit_registry = pint.UnitRegistry()
- self.unit = getattr(unit_registry, unit) # TODO: How to check if unit is in pint?
+ self.unit = getattr(unit_registry, unit) # TODO: How to check if unit is in pint?
self.micron = unit_registry.um
self.steps = {} # dictionary of number of steps for each tile in each channel
@@ -46,17 +70,22 @@ def __init__(self, instrument_view, channels: dict, properties: dict, unit: str
self.setTabBar(self.tab_bar)
self.channel_order = QComboBox()
- self.channel_order.addItems(['per Tile', 'per Volume', ])
+ self.channel_order.addItems(
+ [
+ "per Tile",
+ "per Volume",
+ ]
+ )
self.setCornerWidget(self.channel_order)
self.mode = self.channel_order.currentText()
- self.channel_order.currentTextChanged.connect(lambda value: setattr(self, 'mode', value))
+ self.channel_order.currentTextChanged.connect(lambda value: setattr(self, "mode", value))
# initialize column dictionaries and column delgates
self.initialize_tables(instrument_view)
# add tab with button to add channels
self.add_tool = QToolButton()
- self.add_tool.setText('+')
+ self.add_tool.setText("+")
menu = QMenu()
for channel in self.possible_channels:
action = QAction(str(channel), self)
@@ -64,89 +93,90 @@ def __init__(self, instrument_view, channels: dict, properties: dict, unit: str
menu.addAction(action)
self.add_tool.setMenu(menu)
self.add_tool.setPopupMode(QToolButton.InstantPopup)
- self.insertTab(0, QWidget(), '') # insert dummy qwidget
+ self.insertTab(0, QWidget(), "") # insert dummy qwidget
self.tab_bar.setTabButton(0, QTabBar.RightSide, self.add_tool)
# reorder channels if tabbar moved
- self.tab_bar.tabMoved.connect(lambda:
- setattr(self, 'channels', [self.tabText(ch) for ch in range(self.count() - 1)]))
+ self.tab_bar.tabMoved.connect(
+ lambda: setattr(self, "channels", [self.tabText(ch) for ch in range(self.count() - 1)])
+ )
self._apply_all = True # external flag to dictate behaviour of added tab
- def initialize_tables(self, instrument_view) -> None:
- """
- Initialize table for all channels with proper columns and delegates
- :param instrument_view: view object that contains all widget for devices in an instrument
+ def initialize_tables(self, instrument_view: InstrumentView) -> None:
"""
+ Initialize the tables for each channel.
+ :param instrument_view: The instrument view
+ :type instrument_view: InstrumentView
+ """
# TODO: Checks here if prop or device isn't part of the instrument? Or go in instrument validation?
-
for channel in self.possible_channels:
- setattr(self, f'{channel}_table', QTableWidget())
- table = getattr(self, f'{channel}_table')
+ setattr(self, f"{channel}_table", QTableWidget())
+ table = getattr(self, f"{channel}_table")
table.cellChanged.connect(self.cell_edited)
- columns = ['step size [um]', 'steps', 'prefix']
+ columns = ["step size [um]", "steps", "prefix"]
delegates = [QSpinItemDelegate(), QSpinItemDelegate(minimum=0, step=1), QTextItemDelegate()]
for device_type, properties in self.properties.items():
if device_type in self.possible_channels[channel].keys():
for device_name in self.possible_channels[channel][device_type]:
- device_widget = getattr(instrument_view, f'{singularize(device_type)}_widgets')[device_name]
+ device_widget = getattr(instrument_view, f"{singularize(device_type)}_widgets")[device_name]
device_object = getattr(instrument_view.instrument, device_type)[device_name]
for prop in properties:
# select delegate to use based on type
- column_name = label_maker(f'{device_name}_{prop}')
+ column_name = label_maker(f"{device_name}_{prop}")
descriptor = getattr(type(device_object), prop)
- if not isinstance(descriptor, property) or getattr(descriptor, 'fset', None) is None:
+ if not isinstance(descriptor, property) or getattr(descriptor, "fset", None) is None:
self.column_data_types[column_name] = None
continue
# try and correctly type properties based on setter
- fset = getattr(descriptor, 'fset')
+ fset = getattr(descriptor, "fset")
input_type = list(inspect.signature(fset).parameters.values())[-1].annotation
self.column_data_types[column_name] = input_type if input_type != inspect._empty else None
setattr(self, column_name, {})
columns.append(column_name)
- prop_widget = getattr(device_widget, f'{prop}_widget')
+ prop_widget = getattr(device_widget, f"{prop}_widget")
if type(prop_widget) in [QScrollableLineEdit, QSpinBox]:
- minimum = getattr(descriptor, 'minimum', float('-inf'))
- maximum = getattr(descriptor, 'maximum', float('inf'))
- step = getattr(descriptor, 'step', .1)
+ minimum = getattr(descriptor, "minimum", float("-inf"))
+ maximum = getattr(descriptor, "maximum", float("inf"))
+ step = getattr(descriptor, "step", 0.1)
delegates.append(QSpinItemDelegate(minimum=minimum, maximum=maximum, step=step))
- setattr(self, column_name + '_value_function', prop_widget.value)
- elif type(getattr(device_widget, f'{prop}_widget')) == QComboBox:
- widget = getattr(device_widget, f'{prop}_widget')
+ setattr(self, column_name + "_value_function", prop_widget.value)
+ elif type(getattr(device_widget, f"{prop}_widget")) == QComboBox:
+ widget = getattr(device_widget, f"{prop}_widget")
items = [widget.itemText(i) for i in range(widget.count())]
delegates.append(QComboItemDelegate(items=items))
- setattr(self, column_name + '_value_function', prop_widget.currentText)
+ setattr(self, column_name + "_value_function", prop_widget.currentText)
else: # TODO: How to handle dictionary values
delegates.append(QTextItemDelegate())
- setattr(self, column_name + '_value_function', prop_widget.text)
- elif dict in type(properties).__mro__: # TODO: how to validate the GUI yaml?
+ setattr(self, column_name + "_value_function", prop_widget.text)
+ elif dict in type(properties).__mro__: # TODO: how to validate the GUI yaml?
column_name = label_maker(device_type)
setattr(self, column_name, {})
- setattr(self, column_name + '_initial_value', properties.get('initial_value', None))
+ setattr(self, column_name + "_initial_value", properties.get("initial_value", None))
columns.append(column_name)
- if properties['delegate'] == 'spin':
- minimum = properties.get('minimum', None)
- maximum = properties.get('maximum', None)
- step = properties.get('step', .1 if properties['type'] == 'float' else 1 )
+ if properties["delegate"] == "spin":
+ minimum = properties.get("minimum", None)
+ maximum = properties.get("maximum", None)
+ step = properties.get("step", 0.1 if properties["type"] == "float" else 1)
delegates.append(QSpinItemDelegate(minimum=minimum, maximum=maximum, step=step))
- self.column_data_types[column_name] = float if properties['type'] == 'float' else int
- elif properties['delegate'] == 'combo':
- items = properties['items']
+ self.column_data_types[column_name] = float if properties["type"] == "float" else int
+ elif properties["delegate"] == "combo":
+ items = properties["items"]
delegates.append(QComboItemDelegate(items=items))
- type_mapping = {'int':int, 'float':float, 'str': str}
- self.column_data_types[column_name] = type_mapping[properties['type']]
+ type_mapping = {"int": int, "float": float, "str": str}
+ self.column_data_types[column_name] = type_mapping[properties["type"]]
else:
delegates.append(QTextItemDelegate())
self.column_data_types[column_name] = str
- columns.append('row, column')
+ columns.append("row, column")
for i, delegate in enumerate(delegates):
# table does not take ownership of the delegates, so they are removed from memory as they
# are local variables causing a Segmentation fault. Need to be attributes
- setattr(self, f'{columns[i]}_{channel}_delegate', delegate)
+ setattr(self, f"{columns[i]}_{channel}_delegate", delegate)
table.setItemDelegateForColumn(i, delegate)
table.setColumnCount(len(columns))
table.setHorizontalHeaderLabels(columns)
@@ -157,17 +187,25 @@ def initialize_tables(self, instrument_view) -> None:
@property
def apply_all(self) -> bool:
- """Property for the state of apply all
- :return: boolean indicating if settings for the 0,0 tile are applied to all tiles"""
+ """
+ Get the apply_all property.
+
+ :return: Whether to apply all settings
+ :rtype: bool
+ """
return self._apply_all
@apply_all.setter
def apply_all(self, value: bool) -> None:
- """When apply all is toggled, update existing channels"""
+ """
+ Set the apply_all property.
+ :param value: Whether to apply all settings
+ :type value: bool
+ """
if self._apply_all != value:
for channel in self.channels:
- table = getattr(self, f'{channel}_table')
+ table = getattr(self, f"{channel}_table")
for i in range(1, table.rowCount()): # skip first row
for j in range(table.columnCount() - 1): # skip last column
@@ -180,22 +218,28 @@ def apply_all(self, value: bool) -> None:
@property
def tile_volumes(self) -> np.ndarray:
"""
- Property of tile volumes in 2d numpy array
- :return: 2d numpy array containing the volume of the tile at the i, j location
+ Get the tile volumes.
+
+ :return: The tile volumes
+ :rtype: np.ndarray
"""
return self._tile_volumes
@tile_volumes.setter
def tile_volumes(self, value: np.ndarray) -> None:
- """When tile dims is updated, update size of channel arrays"""
+ """
+ Set the tile volumes.
+ :param value: The tile volumes
+ :type value: np.ndarray
+ """
self._tile_volumes = value
for channel in self.channels:
- table = getattr(self, f'{channel}_table')
+ table = getattr(self, f"{channel}_table")
for i in range(table.columnCount() - 1): # skip row, column
header = table.horizontalHeaderItem(i).text()
- if header == 'step size [um]':
- getattr(self, 'step_size')[channel] = np.resize(getattr(self, 'step_size')[channel], value.shape)
+ if header == "step size [um]":
+ getattr(self, "step_size")[channel] = np.resize(getattr(self, "step_size")[channel], value.shape)
else:
getattr(self, header)[channel] = np.resize(getattr(self, header)[channel], value.shape)
self.step_size[channel] = np.resize(self.step_size[channel], value.shape)
@@ -207,15 +251,15 @@ def tile_volumes(self, value: np.ndarray) -> None:
if tile_index[0] < value.shape[0] and tile_index[1] < value.shape[1]:
self.update_steps(tile_index, row, channel)
-
-
def enable_item(self, item: QTableWidgetItem, enable: bool) -> None:
"""
- Change flags for enabling/disabling items in channel_plan table
- :param item: item to change flag for
- :param enable: boolean value indicating if flags should be configured to enable or disable item
- """
+ Enable or disable a table item.
+ :param item: The table item
+ :type item: QTableWidgetItem
+ :param enable: Whether to enable the item
+ :type enable: bool
+ """
flags = QTableWidgetItem().flags()
if not enable:
flags &= ~Qt.ItemIsEditable
@@ -226,39 +270,41 @@ def enable_item(self, item: QTableWidgetItem, enable: bool) -> None:
item.setFlags(flags)
def add_channel(self, channel: str) -> None:
- """Add channel to acquisition
- :param channel: name of channel to add
"""
+ Add a channel to the widget.
- table = getattr(self, f'{channel}_table')
+ :param channel: The channel to add
+ :type channel: str
+ """
+ table = getattr(self, f"{channel}_table")
- for i in range(3, table.columnCount()-1): # skip steps, step_size, prefix, row/col
+ for i in range(3, table.columnCount() - 1): # skip steps, step_size, prefix, row/col
column_name = table.horizontalHeaderItem(i).text()
- delegate = getattr(self, f'{column_name}_{channel}_delegate', None)
+ delegate = getattr(self, f"{column_name}_{channel}_delegate", None)
if delegate is not None: # Skip if prop did not have setter
- array = getattr(self, f'{column_name}')
+ array = getattr(self, f"{column_name}")
if type(delegate) == QSpinItemDelegate:
array[channel] = np.zeros(self._tile_volumes.shape)
elif type(delegate) == QComboItemDelegate:
- array[channel] = np.empty(self._tile_volumes.shape, dtype='U100')
+ array[channel] = np.empty(self._tile_volumes.shape, dtype="U100")
else:
- array[channel] = np.empty(self._tile_volumes.shape, dtype='U100')
+ array[channel] = np.empty(self._tile_volumes.shape, dtype="U100")
- if getattr(self, column_name + '_initial_value', None) is not None: # get initial value
- array[channel][:, :] = getattr(self, column_name + '_initial_value')
- elif getattr(self, column_name + '_value_function', None) is not None:
+ if getattr(self, column_name + "_initial_value", None) is not None: # get initial value
+ array[channel][:, :] = getattr(self, column_name + "_initial_value")
+ elif getattr(self, column_name + "_value_function", None) is not None:
# call value function to get current set point
- array[channel][:, :] = getattr(self, column_name + '_value_function')()
+ array[channel][:, :] = getattr(self, column_name + "_value_function")()
self.steps[channel] = np.zeros(self._tile_volumes.shape, dtype=int)
self.step_size[channel] = np.zeros(self._tile_volumes.shape, dtype=float)
- self.prefix[channel] = np.zeros(self._tile_volumes.shape, dtype='U100')
+ self.prefix[channel] = np.zeros(self._tile_volumes.shape, dtype="U100")
self.insertTab(0, table, channel)
self.setCurrentIndex(0)
# add button to remove channel
- button = QPushButton('x')
+ button = QPushButton("x")
button.setMaximumWidth(20)
button.setMaximumHeight(20)
button.pressed.connect(lambda: self.remove_channel(channel))
@@ -276,22 +322,25 @@ def add_channel(self, channel: str) -> None:
self.channelAdded.emit(channel)
def add_channel_rows(self, channel: str, order: list) -> None:
- """Add rows to channel table in specific order of tiles
- :param channel: name of channel
- :param order: list of tile order e.g. [[0,0], [0,1]]
"""
+ Add rows to the channel table.
- table = getattr(self, f'{channel}_table')
+ :param channel: The channel to add rows to
+ :type channel: str
+ :param order: The order of the rows
+ :type order: list
+ """
+ table = getattr(self, f"{channel}_table")
table.blockSignals(True)
table.clearContents()
table.setRowCount(0)
arrays = [self.step_size[channel]]
- delegates = [getattr(self, f'step size [um]_{channel}_delegate')]
+ delegates = [getattr(self, f"step size [um]_{channel}_delegate")]
# iterate through columns to find relevant arrays to update
for i in range(1, table.columnCount() - 1): # skip row, column
arrays.append(getattr(self, table.horizontalHeaderItem(i).text())[channel])
- delegates.append(getattr(self, f'{table.horizontalHeaderItem(i).text()}_{channel}_delegate'))
+ delegates.append(getattr(self, f"{table.horizontalHeaderItem(i).text()}_{channel}_delegate"))
for tile in order:
table_row = table.rowCount()
table.insertRow(table_row)
@@ -310,13 +359,15 @@ def add_channel_rows(self, channel: str, order: list) -> None:
table.blockSignals(False)
def remove_channel(self, channel: str) -> None:
- """Remove channel from acquisition
- :param channel: name of channel
"""
+ Remove a channel from the widget.
+ :param channel: The channel to remove
+ :type channel: str
+ """
self.channels.remove(channel)
- table = getattr(self, f'{channel}_table')
+ table = getattr(self, f"{channel}_table")
index = self.indexOf(table)
self.removeTab(index)
@@ -324,8 +375,8 @@ def remove_channel(self, channel: str) -> None:
# remove key from attributes
for i in range(table.columnCount() - 1): # skip row, column
header = table.horizontalHeaderItem(i).text()
- if header == 'step size [um]':
- del getattr(self, 'step_size')[channel]
+ if header == "step size [um]":
+ del getattr(self, "step_size")[channel]
else:
del getattr(self, header)[channel]
@@ -340,23 +391,29 @@ def remove_channel(self, channel: str) -> None:
def cell_edited(self, row: int, column: int, channel: str = None) -> None:
"""
- Update table based on cell edit
- :param row: row of item edited
- :param column: column of item edited
- :param channel: channel name of item edited
+ Handle cell edits in the table.
+
+ :param row: The row of the edited cell
+ :type row: int
+ :param column: The column of the edited cell
+ :type column: int
+ :param channel: The channel of the edited cell, defaults to None
+ :type channel: str, optional
"""
-
channel = self.tabText(self.currentIndex()) if channel is None else channel
- table = getattr(self, f'{channel}_table')
+ table = getattr(self, f"{channel}_table")
table.blockSignals(True) # block signals so updating cells doesn't trigger cell edit again
tile_index = [int(x) for x in table.item(row, table.columnCount() - 1).text() if x.isdigit()]
if column in [0, 1]:
- step_size, steps = self.update_steps(tile_index, row, channel) if column == 0 else \
- self.update_step_size(tile_index, row, channel)
- table.item(row, 0).setData(Qt.EditRole,step_size)
- table.item(row, 1).setData(Qt.EditRole,steps)
+ step_size, steps = (
+ self.update_steps(tile_index, row, channel)
+ if column == 0
+ else self.update_step_size(tile_index, row, channel)
+ )
+ table.item(row, 0).setData(Qt.EditRole, step_size)
+ table.item(row, 1).setData(Qt.EditRole, steps)
# FIXME: I think this is would be considered unexpected behavior
array = getattr(self, table.horizontalHeaderItem(column).text(), self.step_size)[channel]
@@ -365,7 +422,7 @@ def cell_edited(self, row: int, column: int, channel: str = None) -> None:
array[:, :] = value
for i in range(1, table.rowCount()):
item_0 = table.item(0, column)
- table.item(i, column).setData(Qt.EditRole,item_0.data(Qt.EditRole))
+ table.item(i, column).setData(Qt.EditRole, item_0.data(Qt.EditRole))
if column == 0: # update steps as well
table.item(i, column + 1).setData(Qt.EditRole, int(steps))
elif column == 1: # update step_size as well
@@ -375,20 +432,26 @@ def cell_edited(self, row: int, column: int, channel: str = None) -> None:
table.blockSignals(False)
self.channelChanged.emit()
- def update_steps(self, tile_index: list[int], row: int, channel: str) -> list[float, int]:
+ def update_steps(self, tile_index: list[int], row: int, channel: str) -> list[float, int]:
"""
- Update number of steps based on volume
- :param tile_index: integer list specifying row, column value of tile
- :param row: row of item that correspond to tile at position tile_index
- :param channel: name of channel
- :return: step_size in um and number of steps
+ Update the steps for a given tile.
+
+ :param tile_index: The index of the tile
+ :type tile_index: list[int]
+ :param row: The row of the tile
+ :type row: int
+ :param channel: The channel of the tile
+ :type channel: str
+ :return: The updated step size and steps
+ :rtype: list[float, int]
"""
-
- volume_um = (self.tile_volumes[*tile_index]*self.unit).to(self.micron)
+ volume_um = (self.tile_volumes[*tile_index] * self.unit).to(self.micron)
index = tile_index if not self.apply_all else [slice(None), slice(None)]
- steps = volume_um / (float(getattr(self, f'{channel}_table').item(row, 0).data(Qt.EditRole))*self.micron)
- if steps != 0 and not isnan(steps) and steps not in [float('inf'), float('-inf')]:
- step_size = float(round(volume_um / steps, 4)/self.micron) # make dimensionless again for simplicity in code
+ steps = volume_um / (float(getattr(self, f"{channel}_table").item(row, 0).data(Qt.EditRole)) * self.micron)
+ if steps != 0 and not isnan(steps) and steps not in [float("inf"), float("-inf")]:
+ step_size = float(
+ round(volume_um / steps, 4) / self.micron
+ ) # make dimensionless again for simplicity in code
steps = int(round(steps))
else:
steps = 0
@@ -397,21 +460,25 @@ def update_steps(self, tile_index: list[int], row: int, channel: str) -> list[f
return step_size, steps
- def update_step_size(self, tile_index: list[int], row: int, channel: str) -> list[float, int]:
+ def update_step_size(self, tile_index: list[int], row: int, channel: str) -> list[float, int]:
"""
- Update number of steps based on volume
- :param tile_index: integer list specifying row, column value of tile
- :param row: row of item that correspond to tile at position tile_index
- :param channel: name of channel
- :return: step_size in um and number of steps
+ Update the step size for a given tile.
+
+ :param tile_index: The index of the tile
+ :type tile_index: list[int]
+ :param row: The row of the tile
+ :type row: int
+ :param channel: The channel of the tile
+ :type channel: str
+ :return: The updated step size and steps
+ :rtype: list[float, int]
"""
-
- volume_um = (self.tile_volumes[*tile_index]*self.unit).to(self.micron)
+ volume_um = (self.tile_volumes[*tile_index] * self.unit).to(self.micron)
index = tile_index if not self.apply_all else [slice(None), slice(None)]
# make dimensionless again for simplicity in code
- step_size = (volume_um / float(getattr(self, f'{channel}_table').item(row, 1).data(Qt.EditRole)))/self.micron
- if step_size != 0 and not isnan(step_size) and step_size not in [float('inf'), float('-inf')]:
- steps = int(round(volume_um / (step_size*self.micron)))
+ step_size = (volume_um / float(getattr(self, f"{channel}_table").item(row, 1).data(Qt.EditRole))) / self.micron
+ if step_size != 0 and not isnan(step_size) and step_size not in [float("inf"), float("-inf")]:
+ steps = int(round(volume_um / (step_size * self.micron)))
step_size = float(round(step_size, 4))
else:
steps = 0
@@ -419,29 +486,37 @@ def update_step_size(self, tile_index: list[int], row: int, channel: str) -> li
self.step_size[channel][*index] = step_size
return step_size, steps
+
class ChannelPlanTabBar(QTabBar):
- """TabBar that will keep add channel tab at end"""
+ """
+ Tab bar that will keep add channel tab at end.
+ """
def __init__(self):
-
+ """
+ Initialize the ChannelPlanTabBar.
+ """
super(ChannelPlanTabBar, self).__init__()
self.tabMoved.connect(self.tab_index_check)
def tab_index_check(self, prev_index: int, curr_index: int) -> None:
"""
- Keep last tab as last tab
- :param prev_index: previous index of tab
- :param curr_index: index tab was moved to
- """
+ Ensure the add channel tab stays at the end.
+ :param prev_index: The previous index of the tab
+ :type prev_index: int
+ :param curr_index: The current index of the tab
+ :type curr_index: int
+ """
if prev_index == self.count() - 1:
self.moveTab(curr_index, prev_index)
- def mouseMoveEvent(self, ev) -> None:
+ def mouseMoveEvent(self, ev: QMouseEvent) -> None:
"""
- Make last tab immovable
- :param ev: qmouseevent that triggered call
- :return:
+ Handle mouse move events.
+
+ :param ev: The mouse event
+ :type ev: QMouseEvent
"""
index = self.currentIndex()
if index == self.count() - 1: # last tab is immovable
diff --git a/src/view/widgets/acquisition_widgets/metadata_widget.py b/src/view/widgets/acquisition_widgets/metadata_widget.py
index 8c504df..55488f3 100644
--- a/src/view/widgets/acquisition_widgets/metadata_widget.py
+++ b/src/view/widgets/acquisition_widgets/metadata_widget.py
@@ -1,42 +1,68 @@
-from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
-from qtpy.QtWidgets import QWidget
from typing import Callable
+from qtpy.QtWidgets import QWidget
+
+from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
+
+
class MetadataWidget(BaseDeviceWidget):
- """Widget for handling metadata class"""
- def __init__(self, metadata_class, advanced_user: bool = True) -> None:
- """
- :param metadata_class: class to create widget out of
- :param advanced_user: future use argument to determine what should be shown
+ """
+ Widget for handling metadata class.
+ """
+
+ def __init__(self, metadata_class) -> None:
"""
+ Initialize the MetadataWidget.
+ :param metadata_class: The metadata class
+ :type metadata_class: _type_
+ :param advanced_user: Whether the user is advanced, defaults to True
+ :type advanced_user: bool, optional
+ """
properties = scan_for_properties(metadata_class)
self.metadata_class = metadata_class
super().__init__(type(metadata_class), properties)
self.metadata_class = metadata_class
- self.property_widgets.get('acquisition_name_format',
- QWidget()).hide() # hide until BaseClassWidget can handle lists
+ self.property_widgets.get(
+ "acquisition_name_format", QWidget()
+ ).hide() # hide until BaseClassWidget can handle lists
# wrap property setters that are in acquisition_name_format so acquisition name update when changed
- for name in getattr(self, 'acquisition_name_format', []) + \
- ['date_format' if hasattr(self, 'date_format') else None] + \
- ['delimiter' if hasattr(self, 'delimiter') else None]:
+ for name in (
+ getattr(self, "acquisition_name_format", [])
+ + ["date_format" if hasattr(self, "date_format") else None]
+ + ["delimiter" if hasattr(self, "delimiter") else None]
+ ):
if name is not None:
prop = getattr(type(metadata_class), name)
- prop_setter = getattr(prop, 'fset')
- filter_getter = getattr(prop, 'fget')
- setattr(type(metadata_class), name,
- property(filter_getter, self.name_property_change_wrapper(prop_setter)))
+ prop_setter = getattr(prop, "fset")
+ filter_getter = getattr(prop, "fget")
+ setattr(
+ type(metadata_class), name, property(filter_getter, self.name_property_change_wrapper(prop_setter))
+ )
def name_property_change_wrapper(self, func: Callable) -> Callable:
- """Wrapper function that emits a signal when property setters that are in acquisition_name_format is called
- :param func: function to wrap
- :return: wrapped input function
"""
+ Wrap the property setter to update the acquisition name.
+
+ :param func: The property setter function
+ :type func: Callable
+ :return: The wrapped function
+ :rtype: Callable
+ """
+
def wrapper(object, value):
+ """
+ Wrapper function to update the acquisition name.
+
+ :param object: The object
+ :type object: _type_
+ :param value: The value to set
+ :type value: _type_
+ """
func(object, value)
self.acquisition_name = self.metadata_class.acquisition_name
- self.update_property_widget('acquisition_name')
+ self.update_property_widget("acquisition_name")
return wrapper
diff --git a/src/view/widgets/acquisition_widgets/volume_model.py b/src/view/widgets/acquisition_widgets/volume_model.py
index 812df00..a901d5e 100644
--- a/src/view/widgets/acquisition_widgets/volume_model.py
+++ b/src/view/widgets/acquisition_widgets/volume_model.py
@@ -1,32 +1,66 @@
-from pyqtgraph.opengl import GLImageItem
-from qtpy.QtWidgets import QMessageBox, QCheckBox, QGridLayout, QButtonGroup, QLabel, QRadioButton, QPushButton, QWidget
-from qtpy.QtCore import Signal, Qt
-from qtpy.QtGui import QMatrix4x4, QVector3D, QQuaternion
-from math import tan, radians, sqrt
+from math import radians, sqrt, tan
+from typing import List, Optional, Tuple
+
import numpy as np
-from scipy import spatial
from pyqtgraph import makeRGBA
+from pyqtgraph.opengl import GLImageItem
+from qtpy.QtCore import Qt, Signal
+from qtpy.QtGui import QMatrix4x4, QQuaternion, QVector3D, QKeyEvent, QMouseEvent, QWheelEvent
+from qtpy.QtWidgets import QButtonGroup, QCheckBox, QGridLayout, QLabel, QMessageBox, QPushButton, QRadioButton, QWidget
+from scipy import spatial
+
from view.widgets.miscellaneous_widgets.gl_ortho_view_widget import GLOrthoViewWidget
-from view.widgets.miscellaneous_widgets.gl_shaded_box_item import GLShadedBoxItem
from view.widgets.miscellaneous_widgets.gl_path_item import GLPathItem
+from view.widgets.miscellaneous_widgets.gl_shaded_box_item import GLShadedBoxItem
class SignalChangeVar:
+ """
+ Descriptor class to emit a signal when a variable is changed.
+ """
- def __set_name__(self, owner, name):
+ def __set_name__(self, owner: type, name: str) -> None:
+ """
+ Set the name of the variable.
+
+ :param owner: The owner class
+ :type owner: type
+ :param name: The name of the variable
+ :type name: str
+ """
self.name = f"_{name}"
- def __set__(self, instance, value):
+ def __set__(self, instance: object, value: object) -> None:
+ """
+ Set the value of the variable and emit a signal.
+
+ :param instance: The instance of the class
+ :type instance: object
+ :param value: The value to set
+ :type value: object
+ """
setattr(instance, self.name, value) # initially setting attr
instance.valueChanged.emit(self.name[1:])
- def __get__(self, instance, value):
+ def __get__(self, instance: object, owner: type) -> object:
+ """
+ Get the value of the variable.
+
+ :param instance: The instance of the class
+ :type instance: object
+ :param owner: The owner class
+ :type owner: type
+ :return: The value of the variable
+ :rtype: object
+ """
return getattr(instance, self.name)
class VolumeModel(GLOrthoViewWidget):
- """Widget to display configured acquisition grid. Note that the x and y refer to the tiling
- dimensions and z is the scanning dimension"""
+ """
+ Widget to display configured acquisition grid. Note that the x and y refer to the tiling
+ dimensions and z is the scanning dimension.
+ """
fov_dimensions = SignalChangeVar()
fov_position = SignalChangeVar()
@@ -41,10 +75,10 @@ class VolumeModel(GLOrthoViewWidget):
def __init__(
self,
unit: str = "mm",
- limits: list[[float, float], [float, float], [float, float]] = None,
- fov_dimensions: list[float, float, float] = None,
- fov_position: list[float, float, float] = None,
- coordinate_plane: list[str, str, str] = None,
+ limits: Optional[List[Tuple[float, float]]] = None,
+ fov_dimensions: Optional[List[float]] = None,
+ fov_position: Optional[List[float]] = None,
+ coordinate_plane: Optional[List[str]] = None,
fov_color: str = "yellow",
fov_line_width: int = 2,
fov_opacity: float = 0.15,
@@ -61,33 +95,53 @@ def __init__(
limits_line_width: int = 2,
limits_color: str = "white",
limits_opacity: float = 0.1,
- ):
- """
- GLViewWidget to display proposed grid of acquisition
-
- :param unit: unit of the volume model.
- :param coordinate_plane: coordinate plane displayed on widget.
- :param fov_dimensions: dimensions of field of view in coordinate plane
- :param fov_position: position of fov
- :param limits: list of limits ordered in [tile_dim[0], tile_dim[1], scan_dim[0]]
- :param fov_line_width: width of fov outline
- :param fov_line_width: width of fov outline
- :param fov_opacity: opacity of fov face where 1 is fully opaque
- :param path_line_width: width of path line
- :param path_arrow_size: size of arrow at the end of path as a percentage of the field of view
- :param path_arrow_aspect_ratio: aspect ratio of arrow
- :param path_start_color: start color of path
- :param path_end_color: end color of path
- :param active_tile_color: color of tiles when fov is within tile grid
- :param active_tile_opacity: opacity of active tile grid faces where 1 is fully opaque
- :param inactive_tile_color: color of tiles when fov is outside of tile grid
- :param inactive_tile_opacity: opacity of inactive tile grid faces where 1 is fully opaque
- :param tile_line_width: width of tiles
- :param limits_line_width: width of limits box
- :param limits_color: color of limits box
- :param limits_opacity: opacity of limits box
+ ) -> None:
+ """
+ Initialize the VolumeModel.
+
+ :param unit: Unit of measurement, defaults to "mm"
+ :type unit: str, optional
+ :param limits: Limits for the volume, defaults to None
+ :type limits: list[[float, float], [float, float], [float, float]], optional
+ :param fov_dimensions: Dimensions of the field of view, defaults to None
+ :type fov_dimensions: list[float, float, float], optional
+ :param fov_position: Position of the field of view, defaults to None
+ :type fov_position: list[float, float, float], optional
+ :param coordinate_plane: Coordinate plane, defaults to None
+ :type coordinate_plane: list[str, str, str], optional
+ :param fov_color: Color of the field of view, defaults to "yellow"
+ :type fov_color: str, optional
+ :param fov_line_width: Line width of the field of view, defaults to 2
+ :type fov_line_width: int, optional
+ :param fov_opacity: Opacity of the field of view, defaults to 0.15
+ :type fov_opacity: float, optional
+ :param path_line_width: Line width of the path, defaults to 2
+ :type path_line_width: int, optional
+ :param path_arrow_size: Arrow size of the path, defaults to 6.0
+ :type path_arrow_size: float, optional
+ :param path_arrow_aspect_ratio: Arrow aspect ratio of the path, defaults to 4
+ :type path_arrow_aspect_ratio: int, optional
+ :param path_start_color: Start color of the path, defaults to "magenta"
+ :type path_start_color: str, optional
+ :param path_end_color: End color of the path, defaults to "green"
+ :type path_end_color: str, optional
+ :param active_tile_color: Color of the active tile, defaults to "cyan"
+ :type active_tile_color: str, optional
+ :param active_tile_opacity: Opacity of the active tile, defaults to 0.075
+ :type active_tile_opacity: float, optional
+ :param inactive_tile_color: Color of the inactive tile, defaults to "red"
+ :type inactive_tile_color: str, optional
+ :param inactive_tile_opacity: Opacity of the inactive tile, defaults to 0.025
+ :type inactive_tile_opacity: float, optional
+ :param tile_line_width: Line width of the tile, defaults to 2
+ :type tile_line_width: int, optional
+ :param limits_line_width: Line width of the limits, defaults to 2
+ :type limits_line_width: int, optional
+ :param limits_color: Color of the limits, defaults to "white"
+ :type limits_color: str, optional
+ :param limits_opacity: Opacity of the limits, defaults to 0.1
+ :type limits_opacity: float, optional
"""
-
super().__init__(rotationMethod="quaternion")
# initialize attributes
@@ -165,17 +219,6 @@ def __init__(
if limits != [[float("-inf"), float("inf")], [float("-inf"), float("inf")], [float("-inf"), float("inf")]]:
size = [((max(limits[i]) - min(limits[i])) + self.fov_dimensions[i]) for i in range(3)]
- pos = np.array(
- [
- [
- [
- min([x * self.polarity[0] for x in limits[0]]),
- min([y * self.polarity[1] for y in limits[1]]),
- min([z * self.polarity[2] for z in limits[2]]),
- ]
- ]
- ]
- )
stage_limits = GLShadedBoxItem(
width=self.limits_line_width,
pos=np.array(
@@ -233,10 +276,13 @@ def __init__(
self.widgets.setMaximumHeight(70)
self.widgets.show()
- def update_model(self, attribute_name) -> None:
- """Update attributes of grid
- :param attribute_name: name of attribute to update"""
+ def update_model(self, attribute_name: str) -> None:
+ """
+ Update the model based on the changed attribute.
+ :param attribute_name: The name of the changed attribute
+ :type attribute_name: str
+ """
# update color of tiles based on z position
flat_coords = self.grid_coords.reshape([-1, 3]) # flatten array
flat_dims = self.scan_volumes.flatten() # flatten array
@@ -318,19 +364,23 @@ def update_model(self, attribute_name) -> None:
self._update_opts()
- def toggle_view_plane(self, button) -> None:
- """
- Update view plane optics
- :param button: button pressed to change view
+ def toggle_view_plane(self, button: QRadioButton) -> None:
"""
+ Toggle the view plane based on the selected button.
+ :param button: The radio button that was clicked
+ :type button: QRadioButton
+ """
view_plane = tuple(x for x in button.text() if x.isalpha())
self.view_plane = view_plane
- def set_path_pos(self, coord_order: list) -> None:
- """Set the pos of path in correct order
- :param coord_order: ordered list of coords for path"""
+ def set_path_pos(self, coord_order: List[List[float]]) -> None:
+ """
+ Set the path position based on the coordinate order.
+ :param coord_order: The order of coordinates
+ :type coord_order: list
+ """
path = np.array(
[
[
@@ -342,11 +392,15 @@ def set_path_pos(self, coord_order: list) -> None:
)
self.path.setData(pos=path)
- def add_fov_image(self, image: np.ndarray, levels: list[float]) -> None:
- """add image to model assuming image has same fov dimensions and orientation
- :param image: numpy array of image to display in model
- :param levels: levels for passed in image"""
+ def add_fov_image(self, image: np.ndarray, levels: List[float]) -> None:
+ """
+ Add a field of view image.
+ :param image: The image to add
+ :type image: np.ndarray
+ :param levels: The levels for the image
+ :type levels: list[float]
+ """
image_rgba = makeRGBA(image, levels=levels)
image_rgba[0][:, :, 3] = 200
@@ -378,29 +432,34 @@ def add_fov_image(self, image: np.ndarray, levels: list[float]) -> None:
if self.view_plane != (self.coordinate_plane[0], self.coordinate_plane[1]):
gl_image.setVisible(False)
- def adjust_glimage_contrast(self, image: np.ndarray, contrast_levels: list[float]) -> None:
- """
- Adjust image in model contrast levels
- :param image: numpy array of image key in fov_images
- :param contrast_levels: levels for passed in image
+ def adjust_glimage_contrast(self, image: np.ndarray, contrast_levels: List[float]) -> None:
"""
+ Adjust the contrast of a GL image.
+ :param image: The image to adjust
+ :type image: np.ndarray
+ :param contrast_levels: The contrast levels
+ :type contrast_levels: list[float]
+ """
if image.tobytes() in self.fov_images.keys(): # check if image has been deleted
glimage = self.fov_images[image.tobytes()]
- coords = [glimage.transform()[i, 3] / pol for i, pol in zip(range(3), self.polarity)]
self.removeItem(glimage)
self.add_fov_image(image, contrast_levels)
def toggle_fov_image_visibility(self, visible: bool) -> None:
- """Function to hide all fov_images
- :param visible: boolean for if fov_images should be visible"""
+ """
+ Toggle the visibility of the field of view images.
+ :param visible: Whether the images should be visible
+ :type visible: bool
+ """
for image in self.fov_images.values():
image.setVisible(visible)
def _update_opts(self) -> None:
- """Update view of widget. Note that x/y notation refers to horizontal/vertical dimensions of grid view"""
-
+ """
+ Update the options for the view.
+ """
view_plane = self.view_plane
view_pol = [
self.polarity[self.coordinate_plane.index(view_plane[0])],
@@ -496,12 +555,15 @@ def _update_opts(self) -> None:
self.update()
- def move_fov_query(self, new_fov_pos: list[float]) -> [int, bool]:
- """Message box asking if user wants to move fov position
- :param new_fov_pos: position to move the fov to in um
- :return: user reply to pop up and whether to move to the tile nearest the new_fov_pos
+ def move_fov_query(self, new_fov_pos: List[float]) -> Tuple[int, bool]:
"""
+ Query the user to move the field of view.
+ :param new_fov_pos: The new position of the field of view
+ :type new_fov_pos: list[float]
+ :return: The result of the query and whether to move to the nearest tile
+ :rtype: tuple[int, bool]
+ """
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
msgBox.setText(
@@ -518,11 +580,15 @@ def move_fov_query(self, new_fov_pos: list[float]) -> [int, bool]:
return msgBox.exec(), checkbox.isChecked()
- def delete_fov_image_query(self, fov_image_pos: list[float]) -> int:
- """Message box asking if user wants to move fov position
- :param fov_image_pos: coordinates of fov image
- :return: user reply to deleting image"""
+ def delete_fov_image_query(self, fov_image_pos: List[float]) -> int:
+ """
+ Query the user to delete a field of view image.
+ :param fov_image_pos: The position of the field of view image
+ :type fov_image_pos: list[float]
+ :return: The result of the query
+ :rtype: int
+ """
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
msgBox.setText(f"Do you want to delete image at {fov_image_pos} [{self.unit}]?")
@@ -531,11 +597,13 @@ def delete_fov_image_query(self, fov_image_pos: list[float]) -> int:
return msgBox.exec()
- def mousePressEvent(self, event) -> None:
- """Override mouseMoveEvent so user can't change view
- and allow user to move fov easier
- :param event: QMouseEvent of users mouse"""
+ def mousePressEvent(self, event: QMouseEvent) -> None:
+ """
+ Handle mouse press events.
+ :param event: The mouse event
+ :type event: QMouseEvent
+ """
plane = list(self.view_plane) + [ax for ax in self.coordinate_plane if ax not in self.view_plane]
view_pol = [
self.polarity[self.coordinate_plane.index(plane[0])],
@@ -601,18 +669,38 @@ def mousePressEvent(self, event) -> None:
if delete_key is not None:
del self.fov_images[delete_key]
- def mouseMoveEvent(self, event):
- """Override mouseMoveEvent so user can't change view"""
+ def mouseMoveEvent(self, event: QMouseEvent) -> None:
+ """
+ Override mouseMoveEvent so user can't change view.
+
+ :param event: The mouse event
+ :type event: QMouseEvent
+ """
pass
- def wheelEvent(self, event):
- """Override wheelEvent so user can't change view"""
+ def wheelEvent(self, event: QWheelEvent) -> None:
+ """
+ Override wheelEvent so user can't change view.
+
+ :param event: The wheel event
+ :type event: QWheelEvent
+ """
pass
- def keyPressEvent(self, event):
- """Override keyPressEvent so user can't change view"""
+ def keyPressEvent(self, event: QKeyEvent) -> None:
+ """
+ Override keyPressEvent so user can't change view.
+
+ :param event: The key event
+ :type event: QKeyEvent
+ """
pass
- def keyReleaseEvent(self, event):
- """Override keyPressEvent so user can't change view"""
+ def keyReleaseEvent(self, event: QKeyEvent) -> None:
+ """
+ Override keyReleaseEvent so user can't change view.
+
+ :param event: The key event
+ :type event: QKeyEvent
+ """
pass
diff --git a/src/view/widgets/acquisition_widgets/volume_plan_widget.py b/src/view/widgets/acquisition_widgets/volume_plan_widget.py
index f2d861b..d6181cf 100644
--- a/src/view/widgets/acquisition_widgets/volume_plan_widget.py
+++ b/src/view/widgets/acquisition_widgets/volume_plan_widget.py
@@ -1,53 +1,77 @@
-import useq
-from view.widgets.base_device_widget import create_widget
-from view.widgets.miscellaneous_widgets.q_item_delegates import QSpinItemDelegate
-from view.widgets.miscellaneous_widgets.q_start_stop_table_header import QStartStopTableHeader
+from typing import Generator, Literal, Union, Optional, List
+
import numpy as np
+import useq
from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import (
QButtonGroup,
+ QCheckBox,
+ QComboBox,
QDoubleSpinBox,
+ QFrame,
QLabel,
+ QMainWindow,
QRadioButton,
+ QSizePolicy,
QSpinBox,
- QVBoxLayout,
- QWidget,
- QComboBox,
- QMainWindow,
- QFrame,
- QCheckBox,
QTableWidget,
QTableWidgetItem,
- QSizePolicy,
+ QVBoxLayout,
+ QWidget,
)
-from typing import Literal, Union, Generator
+
+from view.widgets.base_device_widget import create_widget
+from view.widgets.miscellaneous_widgets.q_item_delegates import QSpinItemDelegate
+from view.widgets.miscellaneous_widgets.q_start_stop_table_header import QStartStopTableHeader
class GridFromEdges(useq.GridFromEdges):
- """Subclassing useq.GridFromEdges to add row and column attributes and allow reversible order"""
+ """
+ Subclassing useq.GridFromEdges to add row and column attributes and allow reversible order.
+ """
reverse = property() # initialize property
- def __init__(self, reverse=False, *args, **kwargs):
+ def __init__(self, reverse: bool = False, *args, **kwargs) -> None:
+ """
+ Initialize the GridFromEdges.
+
+ :param reverse: Whether to reverse the order, defaults to False
+ :type reverse: bool, optional
+ """
# rewrite property since pydantic doesn't allow to add attr
setattr(type(self), "reverse", property(fget=lambda x: reverse))
super().__init__(*args, **kwargs)
@property
def rows(self) -> int:
- """Property that returns number of rows in configured scan"""
+ """
+ Get the number of rows.
+
+ :return: The number of rows
+ :rtype: int
+ """
dx, _ = self._step_size(self.fov_width, self.fov_height)
return self._nrows(dx)
@property
def columns(self) -> int:
- """Property that returns number of columns in configured scan"""
+ """
+ Get the number of columns.
+
+ :return: The number of columns
+ :rtype: int
+ """
_, dy = self._step_size(self.fov_width, self.fov_height)
return self._ncolumns(dy)
def iter_grid_positions(self, *args, **kwargs) -> Generator:
- """Return generator that contains positions of tiles. If reversed property is True, yield in revere order"""
+ """
+ Iterate over grid positions.
+ :yield: The grid positions
+ :rtype: Generator
+ """
if not self.reverse:
for tile in super().iter_grid_positions(*args, **kwargs):
yield tile
@@ -57,30 +81,52 @@ def iter_grid_positions(self, *args, **kwargs) -> Generator:
class GridWidthHeight(useq.GridWidthHeight):
- """Subclassing useq.GridWidthHeight to add row and column attributes and allow reversible order"""
+ """
+ Subclassing useq.GridWidthHeight to add row and column attributes and allow reversible order.
+ """
reverse = property()
- def __init__(self, reverse=False, *args, **kwargs):
+ def __init__(self, reverse: bool = False, *args, **kwargs) -> None:
+ """
+ Initialize the GridWidthHeight.
+
+ :param reverse: Whether to reverse the order, defaults to False
+ :type reverse: bool, optional
+ """
# rewrite property since pydantic doesn't allow to add attr
setattr(type(self), "reverse", property(fget=lambda x: reverse))
super().__init__(*args, **kwargs)
@property
def rows(self) -> int:
- """Property that returns number of rows in configured scan"""
+ """
+ Get the number of rows.
+
+ :return: The number of rows
+ :rtype: int
+ """
dx, _ = self._step_size(self.fov_width, self.fov_height)
return self._nrows(dx)
@property
def columns(self) -> int:
- """Property that returns number of rows in configured scan"""
+ """
+ Get the number of columns.
+
+ :return: The number of columns
+ :rtype: int
+ """
_, dy = self._step_size(self.fov_width, self.fov_height)
return self._ncolumns(dy)
def iter_grid_positions(self, *args, **kwargs) -> Generator:
- """Return generator that contains positions of tiles. If reversed property is True, yield in revere order"""
+ """
+ Iterate over grid positions.
+ :yield: The grid positions
+ :rtype: Generator
+ """
if not self.reverse:
for tile in super().iter_grid_positions(*args, **kwargs):
yield tile
@@ -90,17 +136,29 @@ def iter_grid_positions(self, *args, **kwargs) -> Generator:
class GridRowsColumns(useq.GridRowsColumns):
- """Subclass useq.GridRowsColumns to allow reversible order"""
+ """
+ Subclass useq.GridRowsColumns to allow reversible order.
+ """
reverse = property()
- def __init__(self, reverse=False, *args, **kwargs):
+ def __init__(self, reverse: bool = False, *args, **kwargs) -> None:
+ """
+ Initialize the GridRowsColumns.
+
+ :param reverse: Whether to reverse the order, defaults to False
+ :type reverse: bool, optional
+ """
setattr(type(self), "reverse", property(fget=lambda x: reverse))
super().__init__(*args, **kwargs)
def iter_grid_positions(self, *args, **kwargs) -> Generator:
- """Return generator that contains positions of tiles. If reversed property is True, yield in revere order"""
+ """
+ Iterate over grid positions.
+ :yield: The grid positions
+ :rtype: Generator
+ """
if not self.reverse:
for tile in super().iter_grid_positions(*args, **kwargs):
yield tile
@@ -110,28 +168,33 @@ def iter_grid_positions(self, *args, **kwargs) -> Generator:
class VolumePlanWidget(QMainWindow):
- """Widget to plan out volume. Grid aspect based on pymmcore GridPlanWidget"""
+ """
+ Widget to plan out volume. Grid aspect based on pymmcore GridPlanWidget.
+ """
valueChanged = Signal(object)
def __init__(
self,
- limits: list[[float, float], [float, float], [float, float]] = None,
- fov_dimensions: list[float, float, float] = None,
- fov_position: list[float, float, float] = None,
- coordinate_plane: list[str, str, str] = None,
+ limits: Optional[List[List[float]]] = None,
+ fov_dimensions: Optional[List[float]] = None,
+ fov_position: Optional[List[float]] = None,
+ coordinate_plane: Optional[List[str]] = None,
unit: str = "um",
- ):
- """
- :param limits: 2D list containing min and max stage limits for each coordinate plane in the order of [
- tiling_dim[0], tiling_dim[1], scanning_dim[0]]
- :param fov_dimensions: dimensions of field of view in
- specified unit in order of [tiling_dim[0], tiling_dim[1], scanning_dim[0]]
- :param fov_position: position of
- field of view in specified unit in order of [tiling_dim[0], tiling_dim[1], scanning_dim[0]]
- :param coordinate_plane: coordinate plane describing the [tiling_dim[0], tiling_dim[1], scanning_dim[0]]. Can
- contain negatives.
- :param unit: common unit of all arguments. Defaults to um
+ ) -> None:
+ """
+ Initialize the VolumePlanWidget.
+
+ :param limits: The limits for the volume, defaults to None
+ :type limits: Optional[List[List[float]]], optional
+ :param fov_dimensions: The dimensions of the field of view, defaults to None
+ :type fov_dimensions: Optional[List[float]], optional
+ :param fov_position: The position of the field of view, defaults to None
+ :type fov_position: Optional[List[float]], optional
+ :param coordinate_plane: The coordinate plane, defaults to None
+ :type coordinate_plane: Optional[List[str]], optional
+ :param unit: The unit of measurement, defaults to "um"
+ :type unit: str, optional
"""
super().__init__()
@@ -361,10 +424,11 @@ def __init__(
def update_tile_table(self, value: Union[GridRowsColumns, GridFromEdges, GridWidthHeight]) -> None:
"""
- Update tile table when value changes
- :param value: newest value containing details of scan
- """
+ Update the tile table with the given value.
+ :param value: The grid value to update the table with
+ :type value: Union[GridRowsColumns, GridFromEdges, GridWidthHeight]
+ """
# check if order changed
table_order = [
[int(x) for x in self.tile_table.item(i, 0).text() if x.isdigit()]
@@ -398,7 +462,9 @@ def update_tile_table(self, value: Union[GridRowsColumns, GridFromEdges, GridWid
# return
def refill_table(self) -> None:
- """Function to clear and populate tile table with current tile configuration"""
+ """
+ Refill the tile table with the current grid values.
+ """
value = self.value()
self.tile_table.clearContents()
self.tile_table.setRowCount(0)
@@ -413,11 +479,13 @@ def refill_table(self) -> None:
def add_tile_to_table(self, row: int, column: int) -> None:
"""
- Add a configured tile into tile_table
- :param row: row of tile
- :param column: column of value
- """
+ Add a tile to the table at the specified row and column.
+ :param row: The row index
+ :type row: int
+ :param column: The column index
+ :type column: int
+ """
self.tile_table.blockSignals(True)
# add new row to table
table_row = self.tile_table.rowCount()
@@ -463,12 +531,15 @@ def add_tile_to_table(self, row: int, column: int) -> None:
def toggle_visibility(self, checked: bool, row: int, column: int) -> None:
"""
- Handle visibility checkbox being toggled
- :param checked: check state of checkbox
- :param row: row of tile
- :param column: column of tile
- """
+ Toggle the visibility of a tile.
+ :param checked: Whether the tile is visible
+ :type checked: bool
+ :param row: The row index
+ :type row: int
+ :param column: The column index
+ :type column: int
+ """
self._tile_visibility[row, column] = checked
if self.apply_all and [row, column] == [0, 0]: # trigger update of all subsequent checkboxes
for r in range(self.tile_table.rowCount()):
@@ -480,10 +551,11 @@ def toggle_visibility(self, checked: bool, row: int, column: int) -> None:
def tile_table_changed(self, item: QTableWidgetItem) -> None:
"""
- Update values if item is changed
- :param item: item that has been changed
- """
+ Handle changes to the tile table.
+ :param item: The table widget item that changed
+ :type item: QTableWidgetItem
+ """
row, column = [int(x) for x in self.tile_table.item(item.row(), 0).text() if x.isdigit()]
col_title = self.table_columns[item.column()]
titles = [f"{self.coordinate_plane[2]} [{self.unit}]", f"{self.coordinate_plane[2]} max [{self.unit}]"]
@@ -505,11 +577,13 @@ def tile_table_changed(self, item: QTableWidgetItem) -> None:
def toggle_grid_position(self, enable: bool, index: Literal[0, 1, 2]) -> None:
"""
- Function connected to the anchor checkboxes. If grid is anchored, allow user to input grid position
- :param enable: State checkbox was toggled to
- :param index: Index of what anchor was checked (0-2)
- """
+ Toggle the grid position.
+ :param enable: Whether to enable the grid position
+ :type enable: bool
+ :param index: The index of the grid position
+ :type index: Literal[0, 1, 2]
+ """
self.grid_offset_widgets[index].setEnabled(enable)
if not enable: # Graph is not anchored
self.grid_offset_widgets[index].setValue(self.fov_position[index])
@@ -520,18 +594,21 @@ def toggle_grid_position(self, enable: bool, index: Literal[0, 1, 2]) -> None:
@property
def apply_all(self) -> bool:
"""
- Return boolean specifying if settings for the 0, 0 tile apply to all tiles
- :return: boolean specifying if settings for the 0, 0 tile apply to all tiles
+ Get whether to apply all settings.
+
+ :return: Whether to apply all settings
+ :rtype: bool
"""
return self._apply_all
@apply_all.setter
def apply_all(self, value: bool) -> None:
"""
- Setting for the 0, 0 tile apply all. If True, will update all tiles
- :param value: boolean to set apply all
- """
+ Set whether to apply all settings.
+ :param value: Whether to apply all settings
+ :type value: bool
+ """
self._apply_all = value
# correctly configure anchor and grid_offset_widget
@@ -553,18 +630,23 @@ def apply_all(self, value: bool) -> None:
self.refill_table() # order, pos, and visibilty doesn't change, so update table to reconfigure editablility
@property
- def fov_position(self) -> list[float, float, float]:
+ def fov_position(self) -> List[float]:
"""
- Current position of the field of view in the specified unit
- :return: list of length 3 specifying current position of fov
+ Get the field of view position.
+
+ :return: The field of view position
+ :rtype: List[float]
"""
return self._fov_position
@fov_position.setter
- def fov_position(self, value: list[float, float, float]) -> None:
+ def fov_position(self, value: List[float]) -> None:
"""
- Set the current position of the field of view in the specified unit
- :param value: list of length 3 specifying new position of fov
+ Set the field of view position.
+
+ :param value: The field of view position
+ :type value: List[float]
+ :raises ValueError: If the value is not a list of length 3
"""
if type(value) is not list and len(value) != 3:
raise ValueError
@@ -579,18 +661,23 @@ def fov_position(self, value: list[float, float, float]) -> None:
self._on_change()
@property
- def fov_dimensions(self) -> list[float, float, float]:
+ def fov_dimensions(self) -> List[float]:
"""
- Returns current field of view dimensions
- :return: list of 3 floats defining the field of view dimensions
+ Get the field of view dimensions.
+
+ :return: The field of view dimensions
+ :rtype: List[float]
"""
return self._fov_dimensions
@fov_dimensions.setter
- def fov_dimensions(self, value: list[float, float, float]) -> None:
+ def fov_dimensions(self, value: List[float]) -> None:
"""
- Setting the fov dimension in the specified unit
- :param value: list of length 3 specifying dimension for field of view
+ Set the field of view dimensions.
+
+ :param value: The field of view dimensions
+ :type value: List[float]
+ :raises ValueError: If the value is not a list of length 2
"""
if type(value) is not list and len(value) != 2:
raise ValueError
@@ -598,15 +685,23 @@ def fov_dimensions(self, value: list[float, float, float]) -> None:
self._on_change()
@property
- def grid_offset(self) -> list[float, float, float]:
- """Returns off set from 0 of tile positions"""
+ def grid_offset(self) -> List[float]:
+ """
+ Get the grid offset.
+
+ :return: The grid offset
+ :rtype: List[float]
+ """
return self._grid_offset
@grid_offset.setter
- def grid_offset(self, value: list[float, float, float]) -> None:
+ def grid_offset(self, value: List[float]) -> None:
"""
- Setting offset from 0 of tile positions in the 3 dimensions of coordinate plane
- :param value: a list of len 3 specifying offset for tile starts
+ Set the grid offset.
+
+ :param value: The grid offset
+ :type value: List[float]
+ :raises ValueError: If the value is not a list of length 3
"""
if type(value) is not list and len(value) != 3:
raise ValueError
@@ -615,12 +710,13 @@ def grid_offset(self, value: list[float, float, float]) -> None:
self._on_change()
@property
- def tile_positions(self) -> [[float, float, float]]:
- """
- Creates 3d list of tile positions based on widget values
- :return: 3D list of tile coordinates
+ def tile_positions(self) -> List[List[float]]:
"""
+ Get the tile positions.
+ :return: The tile positions
+ :rtype: List[List[float]]
+ """
value = self.value()
coords = np.zeros((value.rows, value.columns, 3))
if self._mode != "bounds":
@@ -638,34 +734,38 @@ def tile_positions(self) -> [[float, float, float]]:
@property
def tile_visibility(self) -> np.ndarray:
"""
- 2D matrix of boolean values specifying if tile should be visible
- :return: 2D numpy array containing the start coordinates where the i, j position of start coordinates correlates
- to the i, j position of tile in scan
+ Get the tile visibility.
+
+ :return: The tile visibility
+ :rtype: np.ndarray
"""
return self._tile_visibility
@property
def scan_starts(self) -> np.ndarray:
"""
- 2D matrix of tile start position in scan dimension
- :return: 2D numpy array containing the start coordinates where the i, j position of start coordinates correlates
- to the i, j position of tile in scan
+ Get the scan start positions.
+
+ :return: The scan start positions
+ :rtype: np.ndarray
"""
return self._scan_starts
@property
def scan_ends(self) -> np.ndarray:
"""
- 2D matrix of tile start position in scan dimension
- :return: 2D numpy array containing the end coordinates where the i, j position of end coordinates correlates to
- the i, j position of tile in scan
+ Get the scan end positions.
+
+ :return: The scan end positions
+ :rtype: np.ndarray
"""
return self._scan_ends
def _on_change(self) -> None:
"""
- Function called when things are changed within the widget. Handles formatting start, end, and visibility
- of tiles and emits signal when done.
+ Handle changes to the grid.
+
+ :return: None
"""
if (val := self.value()) is None:
return # pragma: no cover
@@ -679,18 +779,23 @@ def _on_change(self) -> None:
@property
def mode(self) -> Literal["number", "area", "bounds"]:
- """Mode used to calculate tile position
- :return: current mode of widget
+ """
+ Get the grid mode.
+
+ :return: The grid mode
+ :rtype: Literal["number", "area", "bounds"]
"""
return self._mode
@mode.setter
def mode(self, value: Literal["number", "area", "bounds"]) -> None:
"""
- Set mode of widget
- :param value: value to change mode to. Must be 'number', 'area', or 'bounds'
- """
+ Set the grid mode.
+ :param value: The grid mode
+ :type value: Literal["number", "area", "bounds"]
+ :raises ValueError: If the value is not a valid mode
+ """
if value not in ["number", "area", "bounds"]:
raise ValueError
self._mode = value
@@ -711,8 +816,11 @@ def mode(self, value: Literal["number", "area", "bounds"]) -> None:
def value(self) -> Union[GridRowsColumns, GridFromEdges, GridWidthHeight]:
"""
- Value based on widget values
- :return: value containing information about tiles
+ Get the current grid value.
+
+ :raises NotImplementedError: If the mode is not implemented
+ :return: The current grid value
+ :rtype: Union[GridRowsColumns, GridFromEdges, GridWidthHeight]
"""
over = self.overlap.value()
common = {
@@ -747,7 +855,13 @@ def value(self) -> Union[GridRowsColumns, GridFromEdges, GridWidthHeight]:
raise NotImplementedError
-def line():
+def line() -> QFrame:
+ """
+ Create a horizontal line.
+
+ :return: A horizontal line frame
+ :rtype: QFrame
+ """
frame = QFrame()
frame.setFrameShape(QFrame.HLine)
- return frame
+ return frame
\ No newline at end of file
diff --git a/src/view/widgets/base_device_widget.py b/src/view/widgets/base_device_widget.py
index 7f24998..611b357 100644
--- a/src/view/widgets/base_device_widget.py
+++ b/src/view/widgets/base_device_widget.py
@@ -1,41 +1,46 @@
-from qtpy.QtCore import Signal, Slot, QTimer
-from qtpy.QtGui import QIntValidator, QDoubleValidator
+import enum
+import inspect
+import logging
+import re
+import types
+from importlib import import_module
+from inspect import currentframe
+
+import inflection
+from qtpy.QtCore import QTimer, Signal, Slot
+from qtpy.QtGui import QDoubleValidator, QIntValidator
from qtpy.QtWidgets import (
- QWidget,
- QLabel,
QComboBox,
+ QDoubleSpinBox,
QHBoxLayout,
- QVBoxLayout,
- QMainWindow,
+ QLabel,
QLineEdit,
- QSpinBox,
- QDoubleSpinBox,
+ QMainWindow,
QSlider,
+ QSpinBox,
+ QVBoxLayout,
+ QWidget,
)
-from inspect import currentframe
-from importlib import import_module
-import enum
-import types
-import re
-import logging
-import inflection
-from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
-from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
-import inspect
from schema import Schema, SchemaError
+from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
+from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
+
class BaseDeviceWidget(QMainWindow):
+ """_summary_"""
+
ValueChangedOutside = Signal((str,))
ValueChangedInside = Signal((str,))
def __init__(self, device_type: object = None, properties: dict = {}):
- """Base widget for devices like camera, laser, stage, ect. Widget will scan properties of
- device object and create editable inputs for each if not in device_widgets class of device. If no device_widgets
- class is provided, then all properties are exposed
- :param device_type: type of class or dictionary of device object
- :param properties: dictionary contain properties displayed in widget as keys and initial values as values"""
+ """_summary_
+ :param device_type: _description_, defaults to None
+ :type device_type: object, optional
+ :param properties: _description_, defaults to {}
+ :type properties: dict, optional
+ """
self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
super().__init__()
@@ -52,10 +57,15 @@ class is provided, then all properties are exposed
self.ValueChangedOutside[str].connect(self.update_property_widget) # Trigger update when property value changes
def create_property_widgets(self, properties: dict, widget_group):
- """Create input widgets based on properties
- :param properties: dictionary containing properties within a class and mapping to values
- :param widget_group: attribute name for dictionary of widgets"""
-
+ """_summary_
+
+ :param properties: _description_
+ :type properties: dict
+ :param widget_group: _description_
+ :type widget_group: _type_
+ :return: _description_
+ :rtype: _type_
+ """
widgets = {}
for name, value in properties.items():
setattr(self, name, value) # Add device properties as widget properties
@@ -109,11 +119,17 @@ def create_property_widgets(self, properties: dict, widget_group):
return widgets
def create_attribute_widget(self, name, widget_type, values):
- """Create a widget and create corresponding attribute
- :param name: name of property
- :param widget_type: widget type (QLineEdit or QCombobox)
- :param values: input into widget"""
-
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ :param widget_type: _description_
+ :type widget_type: _type_
+ :param values: _description_
+ :type values: _type_
+ :return: _description_
+ :rtype: _type_
+ """
# options = values.keys() if widget_type == 'combo' else values
box = getattr(self, f"create_{widget_type}_box")(name, values)
setattr(self, f"{name}_widget", box) # add attribute for widget input for easy access
@@ -121,10 +137,13 @@ def create_attribute_widget(self, name, widget_type, values):
return box
def check_driver_variables(self, name: str):
- """Check if there is variable in device driver that has name of
- property to inform input widget type and values
- :param name: name of property to search for"""
+ """_summary_
+ :param name: _description_
+ :type name: str
+ :return: _description_
+ :rtype: _type_
+ """
driver_vars = self.device_driver.__dict__
for variable in driver_vars:
search_name = inflection.pluralize(name.replace(".", "_"))
@@ -137,10 +156,15 @@ def check_driver_variables(self, name: str):
return {i.name: i.value for i in enum_class}
def create_text_box(self, name, value):
- """Convenience function to build editable text boxes and add initial value and validator
- :param name: name to emit when text is edited is changed
- :param value: initial value to add to box"""
-
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ :param value: _description_
+ :type value: _type_
+ :return: _description_
+ :rtype: _type_
+ """
# TODO: better way to handle weird types that will crash QT?
value_type = type(value)
textbox = QScrollableLineEdit(str(value))
@@ -157,12 +181,11 @@ def create_text_box(self, name, value):
return textbox
def textbox_edited(self, name):
- """
- Correctly set attribute after textbox has been edited
- :param name: name of property that was edited
- :return:
- """
+ """_summary_
+ :param name: _description_
+ :type name: _type_
+ """
name_lst = name.split(".")
parent_attr = pathGet(self.__dict__, name_lst[0:-1])
value = getattr(self, name + "_widget").text()
@@ -175,10 +198,15 @@ def textbox_edited(self, name):
self.ValueChangedInside.emit(name)
def create_combo_box(self, name, items):
- """Convenience function to build combo boxes and add items
- :param name: name to emit when combobox index is changed
- :param items: items to add to combobox"""
-
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ :param items: _description_
+ :type items: _type_
+ :return: _description_
+ :rtype: _type_
+ """
options = items.keys() if hasattr(items, "keys") else items
box = QComboBox()
box.addItems([str(x) for x in options])
@@ -188,13 +216,13 @@ def create_combo_box(self, name, items):
return box
def combo_box_changed(self, value, name):
- """
- Correctly set attribute after combobox index has been changed
- :param value: new value combobox has been changed to
- :param name: name of property that was edited
- :return:
- """
+ """_summary_
+ :param value: _description_
+ :type value: _type_
+ :param name: _description_
+ :type name: _type_
+ """
name_lst = name.split(".")
parent_attr = pathGet(self.__dict__, name_lst[0:-1])
@@ -210,9 +238,11 @@ def combo_box_changed(self, value, name):
@Slot(str)
def update_property_widget(self, name):
- """Update property widget. Triggers when attribute has been changed outside of widget
- :param name: name of attribute and widget"""
+ """_summary_
+ :param name: _description_
+ :type name: _type_
+ """
value = getattr(self, name, None)
if dict not in type(value).__mro__ and list not in type(value).__mro__: # not a dictionary or list like value
self._set_widget_text(name, value)
@@ -227,10 +257,13 @@ def update_property_widget(self, name):
self.update_property_widget(f"{name}.{i}")
def _set_widget_text(self, name, value):
- """Set widget text based on widget type
- :param name: widget name to set text to
- :param value: value of text"""
+ """_summary_
+ :param name: _description_
+ :type name: _type_
+ :param value: _description_
+ :type value: _type_
+ """
if hasattr(self, f"{name}_widget"):
widget = getattr(self, f"{name}_widget")
widget.blockSignals(True) # block signal indicating change since changing internally
@@ -250,7 +283,13 @@ def _set_widget_text(self, name, value):
self.log.warning(f"{name} doesn't correspond to a widget")
def __setattr__(self, name, value):
- """Overwrite __setattr__ to trigger update if property is changed"""
+ """_summary_
+
+ :param name: _description_
+ :type name: _type_
+ :param value: _description_
+ :type value: _type_
+ """
# check that values adhere to schema of correlating variable
if f"{name}_schema" in self.__dict__.keys():
schema = getattr(self, f"{name}_schema")
@@ -267,10 +306,12 @@ def __setattr__(self, name, value):
# Convenience Functions
def create_dict_schema(dictionary: dict):
- """
- Helper function to create a schema for a dictionary object
- :param dictionary: dictionary to create schema from
- :return: schema of dictionary
+ """_summary_
+
+ :param dictionary: _description_
+ :type dictionary: dict
+ :return: _description_
+ :rtype: _type_
"""
schema = {}
for key, value in dictionary.items():
@@ -285,10 +326,12 @@ def create_dict_schema(dictionary: dict):
def create_list_schema(list_ob: dict):
- """
- Helper function to create a schema for a list object
- :param list_ob: list to create schema from
- :return: schema of list_ob
+ """_summary_
+
+ :param list_ob: _description_
+ :type list_ob: dict
+ :return: _description_
+ :rtype: _type_
"""
schema = []
for value in list_ob:
@@ -302,6 +345,15 @@ def create_list_schema(list_ob: dict):
def check_if_valid(schema, item):
+ """_summary_
+
+ :param schema: _description_
+ :type schema: _type_
+ :param item: _description_
+ :type item: _type_
+ :return: _description_
+ :rtype: _type_
+ """
try:
schema.validate(item)
return True
@@ -310,11 +362,13 @@ def check_if_valid(schema, item):
def create_widget(struct: str, *args, **kwargs):
- """Creates either a horizontal or vertical layout populated with widgets
- :param struct: specifies whether the layout will be horizontal, vertical, or combo
- :param kwargs: all widgets contained in layout
- :return QWidget()"""
+ """_summary_
+ :param struct: _description_
+ :type struct: str
+ :return: _description_
+ :rtype: _type_
+ """
layouts = {"H": QHBoxLayout(), "V": QVBoxLayout()}
widget = QWidget()
if struct == "V" or struct == "H":
@@ -344,10 +398,13 @@ def create_widget(struct: str, *args, **kwargs):
def label_maker(string):
- """Removes underscores from variable names and capitalizes words
- :param string: string to make label out of
- """
+ """_summary_
+ :param string: _description_
+ :type string: _type_
+ :return: _description_
+ :rtype: _type_
+ """
possible_units = ["mm", "um", "px", "mW", "W", "ms", "C", "V", "us"]
label = string.split("_")
label = [words.capitalize() for words in label]
@@ -362,8 +419,15 @@ def label_maker(string):
def pathGet(iterable: dict or list, path: list):
- """Based on list of nested dictionary keys or list indices, return inner dictionary"""
-
+ """_summary_
+
+ :param iterable: _description_
+ :type iterable: dictorlist
+ :param path: _description_
+ :type path: list
+ :return: _description_
+ :rtype: _type_
+ """
for k in path:
k = int(k) if type(iterable) == list else k
iterable = iterable.__getitem__(k)
@@ -371,10 +435,13 @@ def pathGet(iterable: dict or list, path: list):
def scan_for_properties(device):
- """Scan for properties with setters and getters in class and return dictionary
- :param device: object to scan through for properties
- """
+ """_summary_
+ :param device: _description_
+ :type device: _type_
+ :return: _description_
+ :rtype: _type_
+ """
prop_dict = {}
for attr_name in dir(device):
try:
@@ -388,7 +455,12 @@ def scan_for_properties(device):
def disable_button(button, pause=1000):
- """Function to disable button clicks for a period of time to avoid crashing gui"""
+ """_summary_
+ :param button: _description_
+ :type button: _type_
+ :param pause: _description_, defaults to 1000
+ :type pause: int, optional
+ """
button.setEnabled(False)
QTimer.singleShot(pause, lambda: button.setDisabled(False))
diff --git a/src/view/widgets/device_widgets/camera_widget.py b/src/view/widgets/device_widgets/camera_widget.py
index e461a6e..76bdf7a 100644
--- a/src/view/widgets/device_widgets/camera_widget.py
+++ b/src/view/widgets/device_widgets/camera_widget.py
@@ -1,32 +1,33 @@
-from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, scan_for_properties
-from qtpy.QtWidgets import QPushButton, QStyle, QWidget, QHBoxLayout
from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QPushButton, QStyle, QWidget
+
+from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, scan_for_properties
class CameraWidget(BaseDeviceWidget):
+ """_summary_"""
- def __init__(self, camera,
- advanced_user: bool = True):
- """Modify BaseDeviceWidget to be specifically for camera. Main need are adding roi validator,
- live view button, and snapshot button.
- :param camera: camera object
- :param advanced_user: boolean specifying complexity of widget. If True, all property widget of camera will be
- hidden and only the snapshot and live button will be shown.
- """
+ def __init__(self, camera, advanced_user: bool = True):
+ """_summary_
+ :param camera: _description_
+ :type camera: _type_
+ :param advanced_user: _description_, defaults to True
+ :type advanced_user: bool, optional
+ """
self.camera_properties = scan_for_properties(camera)
- del self.camera_properties['latest_frame'] # remove image property
+ del self.camera_properties["latest_frame"] # remove image property
super().__init__(type(camera), self.camera_properties)
- if not advanced_user: # hide widgets
+ if not advanced_user: # hide widgets
for widget in self.property_widgets.values():
widget.setVisible(False)
# create and format livestream button and snapshot button
self.live_button = self.create_live_button()
self.snapshot_button = self.create_snapshot_button()
- picture_buttons = create_widget('H', self.live_button, self.snapshot_button)
+ picture_buttons = create_widget("H", self.live_button, self.snapshot_button)
if advanced_user: # Format widgets better in advaced user mode
@@ -34,87 +35,94 @@ def __init__(self, camera,
direct = Qt.FindDirectChildrenOnly
# reformat binning and pixel type
- pixel_widgets = create_widget('VH',
- *self.property_widgets.get('binning', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('pixel_type', _).findChildren(QWidget,
- options=direct))
+ pixel_widgets = create_widget(
+ "VH",
+ *self.property_widgets.get("binning", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("pixel_type", _).findChildren(QWidget, options=direct),
+ )
# check if properties have setters and if not, disable widgets. Have to do it inside pixel widget
- for i, prop in enumerate(['binning', 'pixel_type']):
+ for i, prop in enumerate(["binning", "pixel_type"]):
attr = getattr(type(camera), prop)
- if getattr(attr, 'fset', None) is None:
+ if getattr(attr, "fset", None) is None:
pixel_widgets.children()[i + 1].setEnabled(False)
# reformat timing widgets
- timing_widgets = create_widget('VH',
- *self.property_widgets.get('exposure_time_ms', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('frame_time_ms', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('line_interval_us', _).findChildren(QWidget,
- options=direct))
+ timing_widgets = create_widget(
+ "VH",
+ *self.property_widgets.get("exposure_time_ms", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("frame_time_ms", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("line_interval_us", _).findChildren(QWidget, options=direct),
+ )
# check if properties have setters and if not, disable widgets
- for i, prop in enumerate(['exposure_time_ms', 'frame_time_ms', 'line_interval_us']):
+ for i, prop in enumerate(["exposure_time_ms", "frame_time_ms", "line_interval_us"]):
attr = getattr(type(camera), prop, False)
- if getattr(attr, 'fset', None) is None:
+ if getattr(attr, "fset", None) is None:
timing_widgets.children()[i + 1].setEnabled(False)
# reformat sensor height and width widget
- sensor_size_widget = create_widget('VH',
- *self.property_widgets.get('sensor_width_px', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('sensor_height_px', _).findChildren(QWidget,
- options=direct))
+ sensor_size_widget = create_widget(
+ "VH",
+ *self.property_widgets.get("sensor_width_px", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("sensor_height_px", _).findChildren(QWidget, options=direct),
+ )
# check if properties have setters and if not, disable widgets
- for i, prop in enumerate(['sensor_width_px', 'sensor_height_px']):
+ for i, prop in enumerate(["sensor_width_px", "sensor_height_px"]):
attr = getattr(type(camera), prop, False)
- if getattr(attr, 'fset', None) is None:
+ if getattr(attr, "fset", None) is None:
sensor_size_widget.children()[i + 1].setEnabled(False)
# reformat roi widget
- self.roi_widget = create_widget('VH',
- *self.property_widgets.get('width_px', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('width_offset_px', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('height_px', _).findChildren(QWidget,
- options=direct),
- *self.property_widgets.get('height_offset_px', _).findChildren(QWidget,
- options=direct))
+ self.roi_widget = create_widget(
+ "VH",
+ *self.property_widgets.get("width_px", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("width_offset_px", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("height_px", _).findChildren(QWidget, options=direct),
+ *self.property_widgets.get("height_offset_px", _).findChildren(QWidget, options=direct),
+ )
self.roi_widget.setContentsMargins(0, 0, 0, 0)
# check if properties have setters and if not, disable widgets
- for i, prop in enumerate(['width_px', 'width_offset_px', 'height_px', 'height_offset_px']):
+ for i, prop in enumerate(["width_px", "width_offset_px", "height_px", "height_offset_px"]):
attr = getattr(type(camera), prop, False)
- if getattr(attr, 'fset', None) is None:
+ if getattr(attr, "fset", None) is None:
self.roi_widget.children()[i + 1].setEnabled(False)
central_widget = self.centralWidget()
central_widget.layout().setSpacing(0) # remove space between central widget and newly formatted widgets
- self.setCentralWidget(create_widget('V',
- picture_buttons,
- pixel_widgets,
- timing_widgets,
- self.roi_widget,
- sensor_size_widget,
- central_widget))
- else: # add snapshot button and liveview
+ self.setCentralWidget(
+ create_widget(
+ "V",
+ picture_buttons,
+ pixel_widgets,
+ timing_widgets,
+ self.roi_widget,
+ sensor_size_widget,
+ central_widget,
+ )
+ )
+ else: # add snapshot button and liveview
central_widget = self.centralWidget()
central_widget.layout().setSpacing(0) # remove space between central widget and newly formatted widgets
- self.setCentralWidget(create_widget('H',self.live_button, self.snapshot_button))
+ self.setCentralWidget(create_widget("H", self.live_button, self.snapshot_button))
def create_live_button(self) -> QPushButton:
- """Add live button"""
+ """_summary_
- button = QPushButton('Live')
+ :return: _description_
+ :rtype: QPushButton
+ """
+ button = QPushButton("Live")
icon = self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay)
button.setIcon(icon)
return button
def create_snapshot_button(self) -> QPushButton:
- """Add snapshot button"""
+ """_summary_
- button = QPushButton('Snapshot')
+ :return: _description_
+ :rtype: QPushButton
+ """
+ button = QPushButton("Snapshot")
# icon = self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay)
# button.setIcon(icon)
return button
diff --git a/src/view/widgets/device_widgets/filter_wheel_widget.py b/src/view/widgets/device_widgets/filter_wheel_widget.py
index e7d913a..b90da09 100644
--- a/src/view/widgets/device_widgets/filter_wheel_widget.py
+++ b/src/view/widgets/device_widgets/filter_wheel_widget.py
@@ -1,33 +1,35 @@
-from pyqtgraph import PlotWidget, TextItem, mkPen, mkBrush, ScatterPlotItem, setConfigOptions
-from qtpy.QtWidgets import QGraphicsEllipseItem, QComboBox
-from qtpy.QtCore import Signal, QTimer, Property, QObject, Slot
-from math import sin, cos, pi, atan, degrees, radians
-from qtpy.QtGui import QFont, QColor
-from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
+from math import atan, cos, degrees, pi, radians, sin
from typing import Callable, Union
+from pyqtgraph import PlotWidget, ScatterPlotItem, TextItem, mkBrush, mkPen, setConfigOptions
+from qtpy.QtCore import Property, QObject, QTimer, Signal, Slot
+from qtpy.QtGui import QColor, QFont
+from qtpy.QtWidgets import QComboBox, QGraphicsEllipseItem
+
+from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
+
setConfigOptions(antialias=True)
class FilterWheelWidget(BaseDeviceWidget):
+ """Widget for controlling a filter wheel device."""
- def __init__(self, filter_wheel,
- colors: dict = None,
- advanced_user: bool = True):
+ def __init__(self, filter_wheel: object, colors: dict = None, advanced_user: bool = True):
"""
- Simple scroll widget for filter wheel
- :param filter_wheel: filter wheel object
- :param colors: colors for filters. Defaults to empty dictionary if not specified
- :param advanced_user: boolean specifying complexity of widget. If False, user won't be able to manually change
- filter wheel but graphics will still work.
+ Initialize the FilterWheelWidget.
+
+ :param filter_wheel: The filter wheel device.
+ :type filter_wheel: object
+ :param colors: Dictionary of colors for the filters, defaults to None.
+ :type colors: dict, optional
+ :param advanced_user: Flag to enable advanced user features, defaults to True.
+ :type advanced_user: bool, optional
"""
-
properties = scan_for_properties(filter_wheel)
-
# wrap filterwheel filter property to emit signal when set
- filter_setter = getattr(type(filter_wheel).filter, 'fset')
- filter_getter = getattr(type(filter_wheel).filter, 'fget')
- setattr(type(filter_wheel), 'filter', property(filter_getter, self.filter_change_wrapper(filter_setter)))
+ filter_setter = getattr(type(filter_wheel).filter, "fset")
+ filter_getter = getattr(type(filter_wheel).filter, "fget")
+ setattr(type(filter_wheel), "filter", property(filter_getter, self.filter_change_wrapper(filter_setter)))
super().__init__(type(filter_wheel), properties)
@@ -38,18 +40,22 @@ def __init__(self, filter_wheel,
# recreate as combo box with filters as options
self.filter_widget = QComboBox()
- self.filter_widget.addItems([f'{v}: {k}' for k, v in self.filters.items()])
- self.filter_widget.currentTextChanged.connect(lambda val: setattr(self, 'filter', val[val.index(' ')+1:]))
- self.filter_widget.currentTextChanged.connect(lambda: self.ValueChangedInside.emit('filter'))
- self.filter_widget.setCurrentText(f'{self.filters[filter_wheel.filter]}: {filter_wheel.filter}')
+ self.filter_widget.addItems([f"{v}: {k}" for k, v in self.filters.items()])
+ self.filter_widget.currentTextChanged.connect(lambda val: setattr(self, "filter", val[val.index(" ") + 1 :]))
+ self.filter_widget.currentTextChanged.connect(lambda: self.ValueChangedInside.emit("filter"))
+ self.filter_widget.setCurrentText(f"{self.filters[filter_wheel.filter]}: {filter_wheel.filter}")
# Add back to property widget
- self.property_widgets['filter'].layout().addWidget(self.filter_widget)
+ self.property_widgets["filter"].layout().addWidget(self.filter_widget)
# Create wheel widget and connect to signals
self.wheel_widget = FilterWheelGraph(self.filters, colors if colors else {})
- self.wheel_widget.ValueChangedInside[str].connect(lambda v: self.filter_widget.setCurrentText(f'{self.filters[v]}: {v}'))
- self.filter_widget.currentTextChanged.connect(lambda val: self.wheel_widget.move_wheel(val[val.index(' ')+1:]))
+ self.wheel_widget.ValueChangedInside[str].connect(
+ lambda v: self.filter_widget.setCurrentText(f"{self.filters[v]}: {v}")
+ )
+ self.filter_widget.currentTextChanged.connect(
+ lambda val: self.wheel_widget.move_wheel(val[val.index(" ") + 1 :])
+ )
self.ValueChangedOutside[str].connect(lambda name: self.wheel_widget.move_wheel(self.filter))
self.centralWidget().layout().addWidget(self.wheel_widget)
@@ -59,54 +65,80 @@ def __init__(self, filter_wheel,
def filter_change_wrapper(self, func: Callable) -> Callable:
"""
- Wrapper function that emits a signal when filterwheel filter setter has been called
- :param func: setter of filter wheel object
+ Wrap the filter change function to emit a signal when the filter is changed.
+
+ :param func: The original filter change function.
+ :type func: Callable
+ :return: The wrapped function.
+ :rtype: Callable
"""
- def wrapper(object, value):
+ def wrapper(object: object, value: str) -> None:
+ """
+ Wrapper function to emit signal on filter change.
+
+ :param object: The filter wheel object.
+ :type object: object
+ :param value: The new filter value.
+ :type value: str
+ """
func(object, value)
self.filter = value
- self.ValueChangedOutside[str].emit('filter')
+ self.ValueChangedOutside[str].emit("filter")
return wrapper
+
class FilterItem(ScatterPlotItem):
- """ScatterPlotItem that will emit signal when pressed"""
+ """
+ ScatterPlotItem that will emit signal when pressed.
+ """
+
pressed = Signal(str)
def __init__(self, filter_name: str, *args, **kwargs):
"""
- :param filter_name: name of filter that will be emitted when pressed
+ Initialize the FilterItem.
+
+ :param filter_name: The name of the filter.
+ :type filter_name: str
"""
self.filter_name = filter_name
super().__init__(*args, **kwargs)
def mousePressEvent(self, ev) -> None:
"""
- Emit signal containing filter_name when item is pressed
- :param ev: QMousePressEvent triggered when item is clicked
+ Emit signal containing filter_name when item is pressed.
+
+ :param ev: QMousePressEvent triggered when item is clicked.
+ :type ev: QMousePressEvent
"""
super().mousePressEvent(ev)
self.pressed.emit(self.filter_name)
+
class FilterWheelGraph(PlotWidget):
+ """Graphical representation of the filter wheel."""
+
ValueChangedInside = Signal((str,))
- def __init__(self, filters: dict, colors: dict, diameter: float = 10.0 , **kwargs):
+ def __init__(self, filters: dict, colors: dict, diameter: float = 10.0, **kwargs):
"""
- Plot widget that creates a visual of filter wheel using points on graph
- :param filters: list possible filters
- :param colors: desired colors of filters where key is filter name and value is string of color. If empty dict,
- default is grey.
- :param diameter: desired diameter of wheel graphic
+ Initialize the FilterWheelGraph.
+
+ :param filters: Dictionary of filters.
+ :type filters: dict
+ :param colors: Dictionary of colors for the filters.
+ :type colors: dict
+ :param diameter: Diameter of the filter wheel, defaults to 10.0.
+ :type diameter: float, optional
"""
-
super().__init__(**kwargs)
self._timelines = []
self.setMouseEnabled(x=False, y=False)
self.showAxes(False, False)
- self.setBackground('#262930')
+ self.setBackground("#262930")
self.filters = filters
self.diameter = diameter
@@ -114,42 +146,53 @@ def __init__(self, filters: dict, colors: dict, diameter: float = 10.0 , **kwarg
# create wheel graphic
wheel = QGraphicsEllipseItem(-self.diameter, -self.diameter, self.diameter * 2, self.diameter * 2)
wheel.setPen(mkPen((0, 0, 0, 100))) # outline of wheel
- wheel.setBrush(mkBrush((128, 128, 128))) # color of wheel
+ wheel.setBrush(mkBrush((65, 75, 70))) # color of wheel
self.addItem(wheel)
self.filter_path = self.diameter - 3
# calculate diameter of filters based on quantity
l = len(self.filters)
- max_diameter = (self.diameter - self.filter_path - .5) * 2
+ max_diameter = (self.diameter - self.filter_path - 0.5) * 2
del_filter = self.filter_path * cos((pi / 2) - (2 * pi / l)) - max_diameter # dist between two filter points
filter_diameter = max_diameter if del_filter > 0 or l == 2 else self.filter_path * cos((pi / 2) - (2 * pi / l))
- angles = [pi / 2+(2 * pi / l * i) for i in range(l)]
+ angles = [pi / 2 + (2 * pi / l * i) for i in range(l)]
self.points = {}
for angle, (filter, i) in zip(angles, self.filters.items()):
- color = QColor(colors.get(filter, 'black')).getRgb()
+ color = colors.get(filter, "black")
+ if type(color) is str:
+ color = QColor(color).getRgb()
+ else:
+ color = QColor().fromRgb(*color).getRgb()
+ color = list(color)
pos = [self.filter_path * cos(angle), self.filter_path * sin(angle)]
# create scatter point filter
point = FilterItem(filter_name=filter, size=filter_diameter, pxMode=False, pos=[pos])
- point.setPen(mkPen((0, 0, 0, 100), width=2)) # outline of filter
+ # update opacity of filter outline color
+ color[-1] = 255
+ point.setBrush(mkBrush(tuple(color))) # color of filter
+ # update opacity of filter outline color
+ color[-1] = 128
+ point.setPen(mkPen(tuple(color), width=3)) # outline of filter
point.setBrush(mkBrush(color)) # color of filter
point.pressed.connect(self.move_wheel)
self.addItem(point)
self.points[filter] = point
# create label
- index = TextItem(text=str(i), anchor=(.5, .5), color='white')
+ index = TextItem(text=str(i), anchor=(0.5, 0.5), color="white")
font = QFont()
- font.setPointSize(round(filter_diameter**2))
+ font.setPointSize(round(filter_diameter**2 - 6))
index.setFont(font)
index.setPos(*pos)
self.addItem(index)
self.points[i] = index
# create active wheel graphic. Add after to display over filters
- active = ScatterPlotItem(size=2, pxMode=False, symbol='t1', pos=[[self.diameter * cos(pi / 2),
- self.diameter * sin(pi / 2)]])
- black = QColor('black').getRgb()
+ active = ScatterPlotItem(
+ size=2, pxMode=False, symbol="t1", pos=[[self.diameter * cos(pi / 2), self.diameter * sin(pi / 2)]]
+ )
+ black = QColor("black").getRgb()
active.setPen(mkPen(black)) # outline
active.setBrush(mkBrush(black)) # color
self.addItem(active)
@@ -158,24 +201,26 @@ def __init__(self, filters: dict, colors: dict, diameter: float = 10.0 , **kwarg
def move_wheel(self, name: str) -> None:
"""
- Create visual of wheel moving to new filer by changing position of filter points
- :param name: name of active filter
+ Move the wheel to the specified filter.
+
+ :param name: The name of the filter to move to.
+ :type name: str
"""
self.ValueChangedInside.emit(name)
point = self.points[name]
- filter_pos = [point.getData()[0][0],point.getData()[1][0]]
+ filter_pos = [point.getData()[0][0], point.getData()[1][0]]
notch_pos = [self.diameter * cos(pi / 2), self.diameter * sin(pi / 2)]
thetas = []
- for x,y in [filter_pos, notch_pos]:
+ for x, y in [filter_pos, notch_pos]:
if y > 0 > x or (y < 0 and x < 0):
- thetas.append(180+degrees(atan(y/x)))
+ thetas.append(180 + degrees(atan(y / x)))
elif y < 0 < x:
- thetas.append(360+degrees(atan(y/x)))
+ thetas.append(360 + degrees(atan(y / x)))
else:
- thetas.append(degrees(atan(y/x)))
+ thetas.append(degrees(atan(y / x)))
filter_theta, notch_theta = thetas
- delta_theta = notch_theta-filter_theta
+ delta_theta = notch_theta - filter_theta
if notch_theta > filter_theta and delta_theta <= 180:
step_size = 1
elif notch_theta > filter_theta and delta_theta > 180:
@@ -192,12 +237,14 @@ def move_wheel(self, name: str) -> None:
# create timelines for all filters and labels
filter_names = list(self.filters.keys())
filter_index = filter_names.index(name)
- filters = [filter_names[(filter_index+i) % len(filter_names)] for i in range(len(filter_names))] # reorder filters starting with filter selected
+ filters = [
+ filter_names[(filter_index + i) % len(filter_names)] for i in range(len(filter_names))
+ ] # reorder filters starting with filter selected
del_theta = 2 * pi / len(filters)
for i, filt in enumerate(filters):
- shift = degrees((del_theta*i))
+ shift = degrees((del_theta * i))
timeline = TimeLine(loopCount=1, interval=10, step_size=step_size)
- timeline.setFrameRange(filter_theta+shift, notch_theta+shift)
+ timeline.setFrameRange(filter_theta + shift, notch_theta + shift)
timeline.frameChanged.connect(lambda i, slot=self.points[filt]: self.move_point(i, slot))
timeline.frameChanged.connect(lambda i, slot=self.points[self.filters[filt]]: self.move_point(i, slot))
self._timelines.append(timeline)
@@ -209,28 +256,39 @@ def move_wheel(self, name: str) -> None:
@Slot(float)
def move_point(self, angle: float, point: Union[FilterItem, TextItem]) -> None:
"""
- Calculate new position of point based on the angle given
- :param angle: angle in degrees
- :param point: point needing to be moved
+ Move a point to a new angle.
+
+ :param angle: The angle to move the point to.
+ :type angle: float
+ :param point: The point to move.
+ :type point: Union[FilterItem, TextItem]
"""
- pos = [self.filter_path * cos(radians(angle)),
- self.filter_path * sin(radians(angle))]
- if type(point) == FilterItem:
+ pos = [self.filter_path * cos(radians(angle)), self.filter_path * sin(radians(angle))]
+ if type(point) is FilterItem:
point.setData(pos=[pos])
- elif type(point) == TextItem:
+ elif type(point) is TextItem:
point.setPos(*pos)
+
class TimeLine(QObject):
- """QObject that steps through values over a period of time and emits values at set interval"""
+ """
+ QObject that steps through values over a period of time and emits values at set interval.
+ """
frameChanged = Signal(float)
- def __init__(self, interval: int = 60, loopCount: int = 1, step_size: float =1, parent=None):
+ def __init__(self, interval: int = 60, loopCount: int = 1, step_size: float = 1, parent: QObject = None):
"""
- :param interval: interval at which to step up and emit value in milliseconds
- :param loopCount: how many times to repeat timeline
- :param step_size: step size to take between emitted values
- :param parent: parent of widget
+ Initialize the TimeLine.
+
+ :param interval: Interval between steps in milliseconds, defaults to 60.
+ :type interval: int, optional
+ :param loopCount: Number of times to loop, defaults to 1.
+ :type loopCount: int, optional
+ :param step_size: Size of each step, defaults to 1.
+ :type step_size: float, optional
+ :param parent: Parent QObject, defaults to None.
+ :type parent: QObject, optional
"""
super(TimeLine, self).__init__(parent)
self._stepSize = step_size
@@ -244,10 +302,11 @@ def __init__(self, interval: int = 60, loopCount: int = 1, step_size: float =1,
def on_timeout(self) -> None:
"""
- Function called by Qtimer that will trigger a step of current step_size and emit new counter value
+ Function called by QTimer that will trigger a step of current step_size and emit new counter value.
"""
- if (self._startFrame <= self._counter <= self._endFrame and self._stepSize > 0) or \
- (self._startFrame >= self._counter >= self._endFrame and self._stepSize < 0):
+ if (self._startFrame <= self._counter <= self._endFrame and self._stepSize > 0) or (
+ self._startFrame >= self._counter >= self._endFrame and self._stepSize < 0
+ ):
self.frameChanged.emit(self._counter)
self._counter += self._stepSize
else:
@@ -259,41 +318,48 @@ def on_timeout(self) -> None:
def setLoopCount(self, loopCount: int) -> None:
"""
- Function set loop count variable
- :param loopCount: integer specifying how many times to repeat timeline
+ Set the number of times to loop.
+
+ :param loopCount: Number of times to loop.
+ :type loopCount: int
"""
self._loopCount = loopCount
def loopCount(self) -> int:
"""
- Current loop count
- :return: Current loop count
+ Get the number of times to loop.
+
+ :return: Number of times to loop.
+ :rtype: int
"""
return self._loopCount
- interval = Property(int, fget=loopCount, fset=setLoopCount)
-
def setInterval(self, interval: int) -> None:
"""
- Function to set interval variable in seconds
- :param interval: integer specifying the length of timeline in milliseconds
+ Set the interval between steps.
+
+ :param interval: Interval between steps in milliseconds.
+ :type interval: int
"""
self._timer.setInterval(interval)
def interval(self) -> int:
"""
- Current interval time in milliseconds
- :return: integer value of current interval time in milliseconds
+ Get the interval between steps.
+
+ :return: Interval between steps in milliseconds.
+ :rtype: int
"""
return self._timer.interval()
- interval = Property(int, fget=interval, fset=setInterval)
-
def setFrameRange(self, startFrame: float, endFrame: float) -> None:
"""
- Setting function for starting and end value that timeline will step through
- :param startFrame: starting value
- :param endFrame: ending value
+ Set the range of frames to step through.
+
+ :param startFrame: The starting frame.
+ :type startFrame: float
+ :param endFrame: The ending frame.
+ :type endFrame: float
"""
self._startFrame = startFrame
self._endFrame = endFrame
@@ -301,12 +367,14 @@ def setFrameRange(self, startFrame: float, endFrame: float) -> None:
@Slot()
def start(self) -> None:
"""
- Function to start QTimer and begin emitting and stepping through value
+ Start the QTimer and begin emitting and stepping through values.
"""
self._counter = self._startFrame
self._loop_counter = 0
self._timer.start()
def stop(self) -> None:
- """Function to stop QTimer and stop stepping through values"""
+ """
+ Stop the QTimer and stop stepping through values.
+ """
self._timer.stop()
diff --git a/src/view/widgets/device_widgets/joystick_widget.py b/src/view/widgets/device_widgets/joystick_widget.py
index 978ef2e..d9e0b21 100644
--- a/src/view/widgets/device_widgets/joystick_widget.py
+++ b/src/view/widgets/device_widgets/joystick_widget.py
@@ -1,16 +1,19 @@
+from qtpy.QtWidgets import QFrame, QLabel, QVBoxLayout
+
from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, label_maker, scan_for_properties
-from qtpy.QtWidgets import QLabel, QFrame, QVBoxLayout
+
class JoystickWidget(BaseDeviceWidget):
+ """_summary_"""
- def __init__(self, joystick,
- advanced_user: bool = True):
- """
- Modify BaseDeviceWidget to be specifically for Joystick.
- :param joystick: joystick object
- :param advanced_user: boolean specifying complexity of widget. If False, returns blank widget
- """
+ def __init__(self, joystick, advanced_user: bool = True):
+ """_summary_
+ :param joystick: _description_
+ :type joystick: _type_
+ :param advanced_user: _description_, defaults to True
+ :type advanced_user: bool, optional
+ """
properties = scan_for_properties(joystick) if advanced_user else {}
super().__init__(type(joystick), properties)
self.advanced_user = advanced_user
@@ -20,47 +23,49 @@ def __init__(self, joystick,
def create_axis_combo_box(self) -> None:
"""
- Transform Instrument Axis text box into combo box and allow selection of only available axes
+ Transform Instrument Axis text box into combo box and allow selection of only available axes.
"""
-
- joystick_widgets = [QLabel('Joystick Mapping'), QLabel()]
+ joystick_widgets = [QLabel("Joystick Mapping"), QLabel()]
for joystick_axis, specs in self.joystick_mapping.items():
unused = list(
- set(self.stage_axes) - set(axis['instrument_axis'] for axis in self.joystick_mapping.values()))
- unused.append(specs['instrument_axis'])
- old_widget = getattr(self, f'joystick_mapping.{joystick_axis}.instrument_axis_widget')
- new_widget = self.create_combo_box(f'joystick_mapping.{joystick_axis}.instrument_axis', unused)
+ set(self.stage_axes) - set(axis["instrument_axis"] for axis in self.joystick_mapping.values())
+ )
+ unused.append(specs["instrument_axis"])
+ old_widget = getattr(self, f"joystick_mapping.{joystick_axis}.instrument_axis_widget")
+ new_widget = self.create_combo_box(f"joystick_mapping.{joystick_axis}.instrument_axis", unused)
old_widget.parentWidget().layout().removeItem(old_widget.parentWidget().layout().itemAt(0))
old_widget.parentWidget().layout().replaceWidget(old_widget, new_widget)
- setattr(self, f'joystick_mapping.{joystick_axis}.instrument_axis_widget', new_widget)
+ setattr(self, f"joystick_mapping.{joystick_axis}.instrument_axis_widget", new_widget)
new_widget.currentTextChanged.connect(self.update_axes_selection)
- widget_dict = {'label': QLabel(label_maker(joystick_axis)),
- **getattr(self, f'joystick_mapping.{joystick_axis}_widgets')}
+ widget_dict = {
+ "label": QLabel(label_maker(joystick_axis)),
+ **getattr(self, f"joystick_mapping.{joystick_axis}_widgets"),
+ }
# add frame
frame = QFrame()
layout = QVBoxLayout()
- layout.addWidget(create_widget('V', **widget_dict))
+ layout.addWidget(create_widget("V", **widget_dict))
frame.setLayout(layout)
- frame.setStyleSheet(f".QFrame {{ border:1px solid grey ; }} ")
+ frame.setStyleSheet(".QFrame {{ border:1px solid grey ; }} ")
if not self.advanced_user:
frame.setEnabled(False)
joystick_widgets.append(frame)
- self.centralWidget().layout().replaceWidget(self.property_widgets['joystick_mapping'],
- create_widget('HV', *joystick_widgets))
+ self.centralWidget().layout().replaceWidget(
+ self.property_widgets["joystick_mapping"], create_widget("HV", *joystick_widgets)
+ )
def update_axes_selection(self) -> None:
"""
- When joystick axis mapped to new stage axis, update available stage axis
+ When joystick axis mapped to new stage axis, update available stage axis.
"""
-
for joystick_axis, specs in self.joystick_mapping.items():
- unused = list(set(self.stage_axes) - set(ax['instrument_axis'] for ax in self.joystick_mapping.values()))
- unused.append(specs['instrument_axis'])
- widget = getattr(self, f'joystick_mapping.{joystick_axis}.instrument_axis_widget')
+ unused = list(set(self.stage_axes) - set(ax["instrument_axis"] for ax in self.joystick_mapping.values()))
+ unused.append(specs["instrument_axis"])
+ widget = getattr(self, f"joystick_mapping.{joystick_axis}.instrument_axis_widget")
# block signals to not trigger currentTextChanged
widget.blockSignals(True)
widget.clear()
widget.addItems(unused)
- widget.setCurrentText(specs['instrument_axis'])
+ widget.setCurrentText(specs["instrument_axis"])
widget.blockSignals(False)
diff --git a/src/view/widgets/device_widgets/laser_widget.py b/src/view/widgets/device_widgets/laser_widget.py
index 4ff7167..d2e0fe3 100644
--- a/src/view/widgets/device_widgets/laser_widget.py
+++ b/src/view/widgets/device_widgets/laser_widget.py
@@ -1,35 +1,41 @@
-from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, scan_for_properties
-from qtpy.QtCore import Qt
import importlib
-from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
-from qtpy.QtGui import QIntValidator, QDoubleValidator
+
+from qtpy.QtCore import Qt
+from qtpy.QtGui import QDoubleValidator, QIntValidator
from qtpy.QtWidgets import QSizePolicy
+
+from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
+from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
+
+
class LaserWidget(BaseDeviceWidget):
+ """_summary_"""
- def __init__(self, laser,
- color: str = 'blue',
- advanced_user: bool = True):
- """
- Modify BaseDeviceWidget to be specifically for laser. Main need is adding slider .
- :param laser: laser object
- :param color: color of laser slider
- :param advanced_user: boolean specifying complexity of widget. If False, only power widget will be visible
- """
+ def __init__(self, laser, color: str = "blue", advanced_user: bool = True):
+ """_summary_
- self.laser_properties = scan_for_properties(laser) if advanced_user else \
- {'power_setpoint_mw': laser.power_setpoint_mw,
- 'power_mw': laser.power_mw}
+ :param laser: _description_
+ :type laser: _type_
+ :param color: _description_, defaults to 'blue'
+ :type color: str, optional
+ :param advanced_user: _description_, defaults to True
+ :type advanced_user: bool, optional
+ """
+ self.laser_properties = (
+ scan_for_properties(laser)
+ if advanced_user
+ else {"power_setpoint_mw": laser.power_setpoint_mw, "power_mw": laser.power_mw}
+ )
self.laser_module = importlib.import_module(laser.__module__)
self.slider_color = color
super().__init__(type(laser), self.laser_properties)
- self.max_power_mw = getattr(type(laser).power_setpoint_mw, 'maximum', 110)
+ self.max_power_mw = getattr(type(laser).power_setpoint_mw, "maximum", 110)
self.add_power_slider()
def add_power_slider(self) -> None:
"""
- Modify power widget to be slider
+ Modify power widget to be slider.
"""
-
setpoint = self.power_setpoint_mw_widget
power = self.power_mw_widget
@@ -46,34 +52,35 @@ def add_power_slider(self) -> None:
setpoint.editingFinished.connect(lambda: slider.setValue(round(float(setpoint.text()))))
setpoint.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
- textbox_power_mw_label = self.property_widgets['power_mw'].layout().itemAt(0).widget()
+ textbox_power_mw_label = self.property_widgets["power_mw"].layout().itemAt(0).widget()
textbox_power_mw_label.setVisible(False) # hide power_mw label
slider = QScrollableFloatSlider(orientation=Qt.Horizontal)
slider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
- slider.setStyleSheet("QSlider::groove:horizontal {border: 1px solid #777;height: 10px;border-radius: 4px;}"
- "QSlider::handle:horizontal {background-color: grey; width: 16px; height: 20px; "
- "line-height: 20px; margin-top: -5px; margin-bottom: -5px; border-radius: 10px; }"
- f"QSlider::sub-page:horizontal {{background: {self.slider_color};border: 1px solid #777;"
- f"height: 10px;border-radius: 4px;}}")
+ slider.setStyleSheet(
+ "QSlider::groove:horizontal {border: 1px solid #777;height: 10px;border-radius: 4px;}"
+ "QSlider::handle:horizontal {background-color: grey; width: 16px; height: 20px; "
+ "line-height: 20px; margin-top: -5px; margin-bottom: -5px; border-radius: 10px; }"
+ f"QSlider::sub-page:horizontal {{background: {self.slider_color};border: 1px solid #777;"
+ f"height: 10px;border-radius: 4px;}}"
+ )
slider.setMinimum(0) # Todo: is it always zero?
slider.setMaximum(int(self.max_power_mw))
slider.setValue(int(self.power_setpoint_mw))
slider.sliderMoved.connect(lambda: setpoint.setText(str(slider.value())))
- slider.sliderReleased.connect(lambda: setattr(self, 'power_setpoint_mw', float(slider.value())))
- slider.sliderReleased.connect(lambda: self.ValueChangedInside.emit('power_setpoint_mw'))
+ slider.sliderReleased.connect(lambda: setattr(self, "power_setpoint_mw", float(slider.value())))
+ slider.sliderReleased.connect(lambda: self.ValueChangedInside.emit("power_setpoint_mw"))
self.power_setpoint_mw_widget_slider = slider
- self.property_widgets['power_setpoint_mw'].layout().addWidget(self.power_mw_widget)
- self.property_widgets['power_setpoint_mw'].layout().addWidget(slider)
-
+ self.property_widgets["power_setpoint_mw"].layout().addWidget(self.power_mw_widget)
+ self.property_widgets["power_setpoint_mw"].layout().addWidget(slider)
def power_slider_fixup(self, value) -> None:
- """
- Fix entered values that are larger than max power
- :param value: value entered that is above maximum of slider
- """
+ """_summary_
+ :param value: _description_
+ :type value: _type_
+ """
self.power_setpoint_mw_widget.setText(str(self.max_power_mw))
- self.power_setpoint_mw_widget.editingFinished.emit()
\ No newline at end of file
+ self.power_setpoint_mw_widget.editingFinished.emit()
diff --git a/src/view/widgets/device_widgets/ni_widget.py b/src/view/widgets/device_widgets/ni_widget.py
index 0d0f265..4be8584 100644
--- a/src/view/widgets/device_widgets/ni_widget.py
+++ b/src/view/widgets/device_widgets/ni_widget.py
@@ -1,42 +1,43 @@
-from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, label_maker, pathGet
-from qtpy.QtWidgets import QTreeWidgetItem, QSizePolicy, QComboBox
-from qtpy.QtCore import Qt
+from random import randint
+from typing import Union
+
+import numpy as np
import qtpy.QtGui as QtGui
-from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
+from qtpy.QtCore import Qt, Slot
+from qtpy.QtWidgets import QComboBox, QSizePolicy, QTreeWidgetItem
+from scipy import signal
+
+from view.widgets.base_device_widget import BaseDeviceWidget, create_widget, label_maker, pathGet
+from view.widgets.device_widgets.waveform_widget import WaveformWidget
from view.widgets.miscellaneous_widgets.q_non_scrollable_tree_widget import QNonScrollableTreeWidget
+from view.widgets.miscellaneous_widgets.q_scrollable_float_slider import QScrollableFloatSlider
from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
-from view.widgets.device_widgets.waveform_widget import WaveformWidget
-import numpy as np
-from scipy import signal
-from qtpy.QtCore import Slot
-from random import randint
-from typing import Union
+
class NIWidget(BaseDeviceWidget):
+ """_summary_"""
- def __init__(self, daq,
- exposed_branches: dict = None,
- advanced_user: bool = True
- ):
- """
- Modify BaseDeviceWidget to be specifically for ni daq.
- :param advanced_user: flag to disable waveform widget
- :param exposed_branches: branches of tasks to be exposed in tree.
- Needs to have keys that map directly into dask
- :param advanced_user: boolean specifying complexity of widget. If False, Waveform widget is not interactive
- """
+ def __init__(self, daq, exposed_branches: dict = None, advanced_user: bool = True):
+ """_summary_
+ :param daq: _description_
+ :type daq: _type_
+ :param exposed_branches: _description_, defaults to None
+ :type exposed_branches: dict, optional
+ :param advanced_user: _description_, defaults to True
+ :type advanced_user: bool, optional
+ """
self.advanced_user = advanced_user
- self.exposed_branches = {'tasks': daq.tasks} if exposed_branches is None else exposed_branches
+ self.exposed_branches = {"tasks": daq.tasks} if exposed_branches is None else exposed_branches
# initialize base widget to create convenient widgets and signals
- super().__init__(daq, {'tasks': daq.tasks})
- del self.property_widgets['tasks'] # delete so view won't confuse and try and update. Hacky?
+ super().__init__(daq, {"tasks": daq.tasks})
+ del self.property_widgets["tasks"] # delete so view won't confuse and try and update. Hacky?
# available channels #TODO: this seems pretty hard coded? A way to avoid this?
- self.ao_physical_chans = [x.replace(f'{daq.id}/', '') for x in daq.ao_physical_chans]
- self.co_physical_chans = [x.replace(f'{daq.id}/', '') for x in daq.co_physical_chans]
- self.do_physical_chans = [x.replace(f'{daq.id}/', '') for x in daq.do_physical_chans]
- self.dio_ports = [x.replace(f'{daq.id}/', '') for x in daq.dio_ports]
+ self.ao_physical_chans = [x.replace(f"{daq.id}/", "") for x in daq.ao_physical_chans]
+ self.co_physical_chans = [x.replace(f"{daq.id}/", "") for x in daq.co_physical_chans]
+ self.do_physical_chans = [x.replace(f"{daq.id}/", "") for x in daq.do_physical_chans]
+ self.dio_ports = [x.replace(f"{daq.id}/", "") for x in daq.dio_ports]
# create waveform widget
if advanced_user:
@@ -46,19 +47,21 @@ def __init__(self, daq,
# create tree widget and format configured widgets into tree
self.tree = QNonScrollableTreeWidget()
for tasks, widgets in self.exposed_branches.items():
- header = QTreeWidgetItem(self.tree,
- [label_maker(tasks.split('.')[-1])]) # take last of list incase key is a map
+ header = QTreeWidgetItem(
+ self.tree, [label_maker(tasks.split(".")[-1])]
+ ) # take last of list incase key is a map
self.create_tree_widget(tasks, header)
- self.tree.setHeaderLabels(['Tasks', 'Values'])
+ self.tree.setHeaderLabels(["Tasks", "Values"])
self.tree.setColumnCount(2)
# Set up waveform widget
if advanced_user:
- graph_parent = QTreeWidgetItem(self.tree, ['Graph'])
+ graph_parent = QTreeWidgetItem(self.tree, ["Graph"])
graph_child = QTreeWidgetItem(graph_parent)
- self.tree.setItemWidget(graph_child, 1, create_widget('H', self.waveform_widget.legend,
- self.waveform_widget))
+ self.tree.setItemWidget(
+ graph_child, 1, create_widget("H", self.waveform_widget.legend, self.waveform_widget)
+ )
graph_parent.addChild(graph_child)
self.setCentralWidget(self.tree)
@@ -67,90 +70,107 @@ def __init__(self, daq,
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
def update_waveform(self, channel_name: str) -> None:
- """Add waveforms to waveform widget
- :param channel_name: name of channel to update"""
+ """_summary_
+ :param channel_name: _description_
+ :type channel_name: str
+ """
if not self.advanced_user:
return
- name_lst = channel_name.split('.')
- task = '.'.join(name_lst[:name_lst.index("ports")])
- port_name = '.'.join(name_lst[:name_lst.index("ports") + 2])
+ name_lst = channel_name.split(".")
+ task = ".".join(name_lst[: name_lst.index("ports")])
+ port_name = ".".join(name_lst[: name_lst.index("ports") + 2])
wl = name_lst[-1]
- waveform = getattr(self, f'{port_name}.waveform')
+ waveform = getattr(self, f"{port_name}.waveform")
kwargs = {
- 'sampling_frequency_hz': getattr(self, f'{task}.timing.sampling_frequency_hz'),
- 'period_time_ms': getattr(self, f'{task}.timing.period_time_ms'),
- 'start_time_ms': getattr(self, f'{port_name}.parameters.start_time_ms.channels.{wl}'),
- 'end_time_ms': getattr(self, f'{port_name}.parameters.end_time_ms.channels.{wl}'),
- 'rest_time_ms': getattr(self, f'{task}.timing.rest_time_ms'),
+ "sampling_frequency_hz": getattr(self, f"{task}.timing.sampling_frequency_hz"),
+ "period_time_ms": getattr(self, f"{task}.timing.period_time_ms"),
+ "start_time_ms": getattr(self, f"{port_name}.parameters.start_time_ms.channels.{wl}"),
+ "end_time_ms": getattr(self, f"{port_name}.parameters.end_time_ms.channels.{wl}"),
+ "rest_time_ms": getattr(self, f"{task}.timing.rest_time_ms"),
}
- scale = kwargs['sampling_frequency_hz']/1000 # account for scaling that occurs in waveform functions
+ scale = kwargs["sampling_frequency_hz"] / 1000 # account for scaling that occurs in waveform functions
- if waveform == 'square wave':
- kwargs['max_volts'] = getattr(self, f'{port_name}.parameters.max_volts.channels.{wl}', 5)
- kwargs['min_volts'] = getattr(self, f'{port_name}.parameters.min_volts.channels.{wl}', 0)
+ if waveform == "square wave":
+ kwargs["max_volts"] = getattr(self, f"{port_name}.parameters.max_volts.channels.{wl}", 5)
+ kwargs["min_volts"] = getattr(self, f"{port_name}.parameters.min_volts.channels.{wl}", 0)
voltages = square_wave(**kwargs)
- start = int(kwargs['start_time_ms'] * scale)
- end = int(kwargs['period_time_ms'] * scale)
- y = [kwargs['min_volts'], kwargs['min_volts'], kwargs['max_volts'],
- kwargs['max_volts'], kwargs['min_volts'], kwargs['min_volts']]
+ start = int(kwargs["start_time_ms"] * scale)
+ end = int(kwargs["period_time_ms"] * scale)
+ y = [
+ kwargs["min_volts"],
+ kwargs["min_volts"],
+ kwargs["max_volts"],
+ kwargs["max_volts"],
+ kwargs["min_volts"],
+ kwargs["min_volts"],
+ ]
x = [0, start - 1, start, end, end + 1, len(voltages)]
else:
- kwargs['amplitude_volts'] = getattr(self, f'{port_name}.parameters.amplitude_volts.channels.{wl}')
- kwargs['offset_volts'] = getattr(self, f'{port_name}.parameters.offset_volts.channels.{wl}')
- kwargs['cutoff_frequency_hz'] = getattr(self,
- f'{port_name}.parameters.cutoff_frequency_hz.channels.{wl}')
- if waveform == 'sawtooth':
+ kwargs["amplitude_volts"] = getattr(self, f"{port_name}.parameters.amplitude_volts.channels.{wl}")
+ kwargs["offset_volts"] = getattr(self, f"{port_name}.parameters.offset_volts.channels.{wl}")
+ kwargs["cutoff_frequency_hz"] = getattr(self, f"{port_name}.parameters.cutoff_frequency_hz.channels.{wl}")
+ if waveform == "sawtooth":
voltages = sawtooth(**kwargs)
- max_point = int(kwargs['end_time_ms'] * scale) if kwargs['end_time_ms'] != kwargs['period_time_ms'] else -1
+ max_point = (
+ int(kwargs["end_time_ms"] * scale) if kwargs["end_time_ms"] != kwargs["period_time_ms"] else -1
+ )
else:
voltages = triangle_wave(**kwargs)
max_point = round(
- (kwargs['start_time_ms'] + ((kwargs['period_time_ms'] - kwargs['start_time_ms']) / 2)) * scale)
+ (kwargs["start_time_ms"] + ((kwargs["period_time_ms"] - kwargs["start_time_ms"]) / 2)) * scale
+ )
- pre_rise_point = int(kwargs['start_time_ms'] * scale)
- post_rise_point = int(kwargs['period_time_ms'] * scale) if kwargs['rest_time_ms'] != 0 else -1
+ pre_rise_point = int(kwargs["start_time_ms"] * scale)
+ post_rise_point = int(kwargs["period_time_ms"] * scale) if kwargs["rest_time_ms"] != 0 else -1
- y = [voltages[0], voltages[pre_rise_point], voltages[max_point], voltages[post_rise_point],
- voltages[-1]]
+ y = [voltages[0], voltages[pre_rise_point], voltages[max_point], voltages[post_rise_point], voltages[-1]]
x = [0, pre_rise_point, max_point, post_rise_point, len(voltages)]
- if 'do_task' not in channel_name:
+ if "do_task" not in channel_name:
# Add min and max for graph
- kwargs['device_max_volts'] = getattr(self, f'{port_name}.device_max_volts')
- kwargs['device_min_volts'] = getattr(self, f'{port_name}.device_min_volts')
+ kwargs["device_max_volts"] = getattr(self, f"{port_name}.device_max_volts")
+ kwargs["device_min_volts"] = getattr(self, f"{port_name}.device_min_volts")
- if item := getattr(self, f'{port_name}.{wl}_plot_item', False):
+ if item := getattr(self, f"{port_name}.{wl}_plot_item", False):
color = item.color
self.waveform_widget.removeDraggableGraphItem(item)
else:
colors = QtGui.QColor.colorNames()
- colors.remove('black')
+ colors.remove("black")
color = colors[randint(0, len(colors) - 1)]
- item = self.waveform_widget.plot(pos=np.column_stack((x, y)),
- waveform=waveform,
- name=name_lst[name_lst.index("ports") + 1] + ' ' + wl,
- color=color,
- parameters={**{k: v['channels'][wl] for k, v in
- getattr(self, f'{port_name}.parameters').items()}, **kwargs})
- item.valueChanged[str, float].connect(lambda var, val: self.waveform_value_changed(
- val, f'{port_name}.parameters.{var}.channels.{wl}'))
- setattr(self, f'{port_name}.{wl}_plot_item', item)
+ item = self.waveform_widget.plot(
+ pos=np.column_stack((x, y)),
+ waveform=waveform,
+ name=name_lst[name_lst.index("ports") + 1] + " " + wl,
+ color=color,
+ parameters={
+ **{k: v["channels"][wl] for k, v in getattr(self, f"{port_name}.parameters").items()},
+ **kwargs,
+ },
+ )
+ item.valueChanged[str, float].connect(
+ lambda var, val: self.waveform_value_changed(val, f"{port_name}.parameters.{var}.channels.{wl}")
+ )
+ setattr(self, f"{port_name}.{wl}_plot_item", item)
@Slot(str, float)
def waveform_value_changed(self, value: float, name: str) -> None:
- """Update textbox if waveform is changed
- :param value: new value to update
- :param name: name of parameter """
-
- name_lst = name.split('.')
- if hasattr(self, f'{name}_slider'): # value is included in exposed branches
- textbox = getattr(self, f'{name}_widget')
- slider = getattr(self, f'{name}_slider')
- value = round(value, 0) if 'time' in name else round(value, 3)
+ """_summary_
+
+ :param value: _description_
+ :type value: float
+ :param name: _description_
+ :type name: str
+ """
+ name_lst = name.split(".")
+ if hasattr(self, f"{name}_slider"): # value is included in exposed branches
+ textbox = getattr(self, f"{name}_widget")
+ slider = getattr(self, f"{name}_slider")
+ value = round(value, 0) if "time" in name else round(value, 3)
textbox.setText(str(value))
slider.setValue(value)
dictionary = pathGet(self.__dict__, name_lst[0:-1])
@@ -158,73 +178,74 @@ def waveform_value_changed(self, value: float, name: str) -> None:
setattr(self, name, value)
self.ValueChangedInside.emit(name)
- def remodel_timing_widgets(self, name: str, widget: Union[QComboBox, QScrollableLineEdit]) \
- -> Union[QComboBox, QScrollableLineEdit]:
- """
- Remodel timing widget to be combo boxes with driver specific variables like possible ports
- :param name: name of attribute
- :param widget: widget object
- :return widget: updated widget corresponding to name attribute
+ def remodel_timing_widgets(
+ self, name: str, widget: Union[QComboBox, QScrollableLineEdit]
+ ) -> Union[QComboBox, QScrollableLineEdit]:
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
"""
- path = name.split('.')
+ path = name.split(".")
if options := self.check_driver_variables(path[-1]):
- widget = self.create_attribute_widget(name, 'combo', options)
+ widget = self.create_attribute_widget(name, "combo", options)
- elif path[-1] in ['trigger_port', 'output_port']:
- widget = self.create_attribute_widget(name, 'combo', self.dio_ports)
+ elif path[-1] in ["trigger_port", "output_port"]:
+ widget = self.create_attribute_widget(name, "combo", self.dio_ports)
return widget
- def remodel_port_widgets(self, name: str, widget: Union[QComboBox, QScrollableLineEdit]) \
- -> Union[QComboBox, QScrollableLineEdit]:
- """Remodel port widgets with possible ports and waveforms
- :param name: name of attribute
- :param widget: widget object
- :return widget: updated widget corresponding to name attribute
- """
+ def remodel_port_widgets(
+ self, name: str, widget: Union[QComboBox, QScrollableLineEdit]
+ ) -> Union[QComboBox, QScrollableLineEdit]:
+ """_summary_
- path = name.split('.')
- task = 'ao' if 'ao_task' in path else 'do'
+ :return: _description_
+ :rtype: _type_
+ """
+ path = name.split(".")
+ task = "ao" if "ao_task" in path else "do"
- if path[-1] == 'port':
- options = getattr(self, f'{task}_physical_chans')
- widget = self.create_attribute_widget(name, 'combo', options)
+ if path[-1] == "port":
+ options = getattr(self, f"{task}_physical_chans")
+ widget = self.create_attribute_widget(name, "combo", options)
- elif path[-1] == 'waveform':
- options = self.check_driver_variables(f'{task}_waveforms')
- widget = self.create_attribute_widget(name, 'combo', options)
+ elif path[-1] == "waveform":
+ options = self.check_driver_variables(f"{task}_waveforms")
+ widget = self.create_attribute_widget(name, "combo", options)
widget.setDisabled(True) # can't change waveform for now. Maybe implemented later on if useful
return widget
def create_sliders(self, name: str) -> None:
- """
- Create slide bars for channel widgets
- :param name: name of attribute
- """
+ """_summary_
- textbox = getattr(self, f'{name}_widget')
+ :param name: _description_
+ :type name: str
+ """
+ textbox = getattr(self, f"{name}_widget")
slider = QScrollableFloatSlider(orientation=Qt.Horizontal)
- path = name.split('.')
- if 'time' in name:
- task = '.'.join(path[:path.index("ports")])
- maximum = getattr(self, f'{task}.timing.period_time_ms')
+ path = name.split(".")
+ if "time" in name:
+ task = ".".join(path[: path.index("ports")])
+ maximum = getattr(self, f"{task}.timing.period_time_ms")
slider.setMaximum(maximum)
textbox.validator().setRange(0.0, maximum, decimals=0)
- elif 'volt' in name:
+ elif "volt" in name:
slider.divisor = 1000
- port = '.'.join(path[:path.index("ports") + 2]) if 'ports' in path else 0
+ port = ".".join(path[: path.index("ports") + 2]) if "ports" in path else 0
# Triangle and sawtooths max amplitude can be less than max volts due to offset so force fixup check
- maximum = getattr(self, f'{port}.device_max_volts', 5)
- minimum = getattr(self, f'{port}.device_min_volts',
- 0) if 'amplitude' not in name else -maximum # allow for negative amplitude
+ maximum = getattr(self, f"{port}.device_max_volts", 5)
+ minimum = (
+ getattr(self, f"{port}.device_min_volts", 0) if "amplitude" not in name else -maximum
+ ) # allow for negative amplitude
slider.setMaximum(maximum)
slider.setMinimum(minimum)
textbox.validator().setRange(minimum, maximum, decimals=4)
- slider.setValue(getattr(self, f'{name}'))
+ slider.setValue(getattr(self, f"{name}"))
- if 'amplitude_volts' in name or 'offset_volts' in name:
+ if "amplitude_volts" in name or "offset_volts" in name:
textbox.editingFinished.connect(lambda: self.check_amplitude(float(textbox.text()), name))
slider.sliderMoved.connect(lambda value: self.check_amplitude(value, name))
else:
@@ -234,107 +255,110 @@ def create_sliders(self, name: str) -> None:
slider.sliderMoved.connect(lambda value: textbox.setText(str(value)))
slider.sliderMoved.connect(lambda value: setattr(self, name, float(value)))
- slider.sliderMoved.connect(lambda value:
- pathGet(self.__dict__, path[0:-1]).__setitem__(path[-1], value))
+ slider.sliderMoved.connect(lambda value: pathGet(self.__dict__, path[0:-1]).__setitem__(path[-1], value))
slider.sliderMoved.connect(lambda: self.ValueChangedInside.emit(name))
slider.sliderMoved.connect(lambda: self.update_waveform(name))
- setattr(self, f'{name}_slider', slider)
+ setattr(self, f"{name}_slider", slider)
def create_tree_widget(self, name: str, parent=None) -> QTreeWidgetItem:
+ """_summary_
+
+ :param name: _description_
+ :type name: str
+ :param parent: _description_, defaults to None
+ :type parent: _type_, optional
+ :return: _description_
+ :rtype: QTreeWidgetItem
"""
- Recursive function to format nested dictionary of ni task items
- :param name: name of attribute
- :param parent: parent to pass into QTreeWidgetItems. Each node will pass itself down as a
- parent so future nodes will appear underneath
- :return items: items to add to QTreeWidget
- """
-
parent = self.tree if parent is None else parent
# TODO: This is haaaaaacky. but might be good for now
- iterable = self.mappedpathGet(self.exposed_branches.copy(), name.split('.'))
+ iterable = self.mappedpathGet(self.exposed_branches.copy(), name.split("."))
items = []
for i, item in enumerate(iterable):
- key = item if hasattr(iterable, 'keys') else str(i) # account for yaml typed
- id = f'{name}.{key}'
- if widget := getattr(self, f'{id}_widget', False):
+ key = item if hasattr(iterable, "keys") else str(i) # account for yaml typed
+ id = f"{name}.{key}"
+ if widget := getattr(self, f"{id}_widget", False):
item = QTreeWidgetItem(parent, [key])
- if 'channel' in name:
+ if "channel" in name:
self.update_waveform(id)
self.create_sliders(id)
- widget = create_widget('H', getattr(self, f'{id}_widget'), getattr(self, f'{id}_slider'))
- elif 'timing' in name:
+ widget = create_widget("H", getattr(self, f"{id}_widget"), getattr(self, f"{id}_slider"))
+ elif "timing" in name:
widget = self.remodel_timing_widgets(id, widget)
- elif key in ['port', 'waveform']:
+ elif key in ["port", "waveform"]:
widget = self.remodel_port_widgets(id, widget)
self.tree.setItemWidget(item, 1, widget)
else:
item = QTreeWidgetItem(parent, [key])
- children = self.create_tree_widget(f'{name}.{key}', item)
+ children = self.create_tree_widget(f"{name}.{key}", item)
item.addChildren(children)
items.append(item)
self.check_to_hide(id, item)
return items
def mappedpathGet(self, dictionary: dict, path: list[str]) -> dict:
-
- """
- Recursive function to map a given path of strings into a dictionary which may contain keys that are parts of
- the path strung together by periods. For example, the path may be ['keys', 'to', 'value', 'I', 'would', 'like']
- and the dictionary could resemble {'keys.to': {'value.I.would':{ 'like': value}}}
-
- :param dictionary: dictionary which may contain keys that are parts of the path strung together by periods
- :param path: list of strings that map a path into dictionary
+ """_summary_
+
+ :param dictionary: _description_
+ :type dictionary: dict
+ :param path: _description_
+ :type path: list[str]
+ :return: _description_
+ :rtype: dict
"""
# TODO: This is haaaaaacky. but might be good for now
try:
dictionary = pathGet(dictionary, path)
except KeyError:
- if '.'.join(path[0:2]) in dictionary.keys():
- dictionary = self.mappedpathGet(dictionary['.'.join(path[0:2])], path[2:])
+ if ".".join(path[0:2]) in dictionary.keys():
+ dictionary = self.mappedpathGet(dictionary[".".join(path[0:2])], path[2:])
else:
- dictionary = self.mappedpathGet(dictionary, ['.'.join(path[0:2]), *path[2:]])
+ dictionary = self.mappedpathGet(dictionary, [".".join(path[0:2]), *path[2:]])
finally:
return dictionary
- def check_to_hide(self, name: str, item: QTreeWidgetItem, dictionary: dict=None) -> None:
- """
- Check if name split by '.' maps into exposed branched. If not, hide associated items
- :param name: name of attribute
- :param item: item associated with attribute
- :param dictionary: dictionary to search in
- :return: None
+ def check_to_hide(self, name: str, item: QTreeWidgetItem, dictionary: dict = None) -> None:
+ """_summary_
+
+ :param name: _description_
+ :type name: str
+ :param item: _description_
+ :type item: QTreeWidgetItem
+ :param dictionary: _description_, defaults to None
+ :type dictionary: dict, optional
"""
# TODO: This is haaaaaacky. but might be good for now
dictionary = self.exposed_branches.copy() if dictionary is None else dictionary
try:
- self.mappedpathGet(dictionary, name.split('.'))
+ self.mappedpathGet(dictionary, name.split("."))
except KeyError:
item.setHidden(True)
def check_amplitude(self, value: float, name: str) -> None:
- """Check if amplitude of triangle or sawtooth is below maximum
- :param value: newly input amplitude value
- :param name: attribute name
- """
+ """_summary_
- textbox = getattr(self, f'{name}_widget')
- slider = getattr(self, f'{name}_slider')
+ :param value: _description_
+ :type value: float
+ :param name: _description_
+ :type name: str
+ """
+ textbox = getattr(self, f"{name}_widget")
+ slider = getattr(self, f"{name}_slider")
maximum = slider.maximum()
- name_lst = name.split('.')
- parameters = '.'.join(name_lst[:name_lst.index("parameters") + 1])
+ name_lst = name.split(".")
+ parameters = ".".join(name_lst[: name_lst.index("parameters") + 1])
wl = name_lst[-1]
# sawtooth or triangle
- offset = value if 'offset_volts' in name else getattr(self, f'{parameters}.offset_volts.channels.{wl}')
- amplitude = value if 'amplitude_volts' in name else getattr(self, f'{parameters}.amplitude_volts.channels.{wl}')
- other_voltage = amplitude if 'offset_volts' in name else offset
+ offset = value if "offset_volts" in name else getattr(self, f"{parameters}.offset_volts.channels.{wl}")
+ amplitude = value if "amplitude_volts" in name else getattr(self, f"{parameters}.amplitude_volts.channels.{wl}")
+ other_voltage = amplitude if "offset_volts" in name else offset
total_amplitude = offset + amplitude
if total_amplitude > maximum:
value = value - (total_amplitude - maximum)
- elif ('amplitude_volts' in name and amplitude > offset) or \
- ('offset_volts' in name and offset < amplitude):
+ elif ("amplitude_volts" in name and amplitude > offset) or ("offset_volts" in name and offset < amplitude):
value = other_voltage
textbox.setText(str(value))
slider.setValue(float(value))
@@ -343,83 +367,96 @@ def check_amplitude(self, value: float, name: str) -> None:
pathGet(self.__dict__, name_lst[0:-1]).__setitem__(name_lst[-1], value)
self.update_waveform(name)
- def textbox_fixup(self, value: float or str, name: str) -> None:
- """
- Fix entered values that are larger than maximum
- :param value: new value entered into textbox
- :param name: name of attribute
+ def textbox_fixup(self, value: float | str, name: str) -> None:
+ """_summary_
+
+ :param value: _description_
+ :type value: floatorstr
+ :param name: _description_
+ :type name: str
"""
- textbox = getattr(self, f'{name}_widget')
- slider = getattr(self, f'{name}_slider')
+ textbox = getattr(self, f"{name}_widget")
+ slider = getattr(self, f"{name}_slider")
maximum = slider.maximum()
textbox.setText(str(maximum))
textbox.editingFinished.emit()
-def sawtooth(sampling_frequency_hz: float,
- period_time_ms: float,
- start_time_ms: float,
- end_time_ms: float,
- rest_time_ms: float,
- amplitude_volts: float,
- offset_volts: float,
- cutoff_frequency_hz: float
- ) -> np.ndarray:
- """
- Function to create a sawtooth wave
- :param sampling_frequency_hz: frequency of waveform. Determines how many samples in a waveform
- :param period_time_ms: duration of wave in ms
- :param start_time_ms: starting time of waveform in ms
- :param end_time_ms: termination time of sawtooth
- :param rest_time_ms: supplemental time after period time has ended in ms
- :param amplitude_volts: amplitude of sawtooth
- :param offset_volts: offset of sawtooth
- :param cutoff_frequency_hz: unused
- :return: numpy array of waveform
+def sawtooth(
+ sampling_frequency_hz: float,
+ period_time_ms: float,
+ start_time_ms: float,
+ end_time_ms: float,
+ rest_time_ms: float,
+ amplitude_volts: float,
+ offset_volts: float,
+ cutoff_frequency_hz: float,
+) -> np.ndarray:
+ """_summary_
+
+ :param sampling_frequency_hz: _description_
+ :type sampling_frequency_hz: float
+ :param period_time_ms: _description_
+ :type period_time_ms: float
+ :param start_time_ms: _description_
+ :type start_time_ms: float
+ :param end_time_ms: _description_
+ :type end_time_ms: float
+ :param rest_time_ms: _description_
+ :type rest_time_ms: float
+ :param amplitude_volts: _description_
+ :type amplitude_volts: float
+ :param offset_volts: _description_
+ :type offset_volts: float
+ :param cutoff_frequency_hz: _description_
+ :type cutoff_frequency_hz: float
+ :return: _description_
+ :rtype: np.ndarray
"""
-
- time_samples_ms = np.linspace(0, 2 * np.pi,
- int(((period_time_ms - start_time_ms) / 1000) * sampling_frequency_hz))
- waveform = offset_volts + amplitude_volts * signal.sawtooth(t=time_samples_ms,
- width=end_time_ms / period_time_ms)
+ time_samples_ms = np.linspace(0, 2 * np.pi, int(((period_time_ms - start_time_ms) / 1000) * sampling_frequency_hz))
+ waveform = offset_volts + amplitude_volts * signal.sawtooth(t=time_samples_ms, width=end_time_ms / period_time_ms)
# add in delay
delay_samples = int((start_time_ms / 1000) * sampling_frequency_hz)
- waveform = np.pad(array=waveform,
- pad_width=(delay_samples, 0),
- mode='constant',
- constant_values=(offset_volts - amplitude_volts)
- )
+ waveform = np.pad(
+ array=waveform, pad_width=(delay_samples, 0), mode="constant", constant_values=(offset_volts - amplitude_volts)
+ )
# add in rest
rest_samples = int((rest_time_ms / 1000) * sampling_frequency_hz)
- waveform = np.pad(array=waveform,
- pad_width=(0, rest_samples),
- mode='constant',
- constant_values=(offset_volts - amplitude_volts)
- )
+ waveform = np.pad(
+ array=waveform, pad_width=(0, rest_samples), mode="constant", constant_values=(offset_volts - amplitude_volts)
+ )
return waveform
-def square_wave(sampling_frequency_hz: float,
- period_time_ms: float,
- start_time_ms: float,
- end_time_ms: float,
- rest_time_ms: float,
- max_volts: float,
- min_volts: float
- ) -> np.ndarray:
- """
- Function to create a sawtooth wave
- :param sampling_frequency_hz: frequency of waveform. Determines how many samples in a waveform
- :param period_time_ms: duration of wave in ms
- :param start_time_ms: starting time of waveform in ms
- :param end_time_ms: termination time of square wave
- :param rest_time_ms: supplemental time after period time has ended in ms
- :param max_volts: maximum volts of square wave
- :param min_volts: minimum volts of square wave
- :return: numpy array of waveform
+def square_wave(
+ sampling_frequency_hz: float,
+ period_time_ms: float,
+ start_time_ms: float,
+ end_time_ms: float,
+ rest_time_ms: float,
+ max_volts: float,
+ min_volts: float,
+) -> np.ndarray:
+ """_summary_
+
+ :param sampling_frequency_hz: _description_
+ :type sampling_frequency_hz: float
+ :param period_time_ms: _description_
+ :type period_time_ms: float
+ :param start_time_ms: _description_
+ :type start_time_ms: float
+ :param end_time_ms: _description_
+ :type end_time_ms: float
+ :param rest_time_ms: _description_
+ :type rest_time_ms: float
+ :param max_volts: _description_
+ :type max_volts: float
+ :param min_volts: _description_
+ :type min_volts: float
+ :return: _description_
+ :rtype: np.ndarray
"""
-
time_samples = int(((period_time_ms + rest_time_ms) / 1000) * sampling_frequency_hz)
start_sample = int((start_time_ms / 1000) * sampling_frequency_hz)
end_sample = int((end_time_ms / 1000) * sampling_frequency_hz)
@@ -430,36 +467,31 @@ def square_wave(sampling_frequency_hz: float,
return waveform
-def triangle_wave(sampling_frequency_hz: float,
- period_time_ms: float,
- start_time_ms: float,
- end_time_ms: float,
- rest_time_ms: float,
- amplitude_volts: float,
- offset_volts: float,
- cutoff_frequency_hz: float
- ) -> np.ndarray:
-
- """Function to create a sawtooth wave
- :param sampling_frequency_hz: frequency of waveform. Determines how many samples in a waveform
- :param period_time_ms: duration of wave in ms
- :param start_time_ms: starting time of waveform in ms
- :param end_time_ms: termination time of triangle_wave
- :param rest_time_ms: supplemental time after period time has ended in ms
- :param amplitude_volts: amplitude of triangle_wave
- :param offset_volts: offset of triangle_wave
- :param cutoff_frequency_hz: unused
- :return: numpy array of waveform"""
-
+def triangle_wave(
+ sampling_frequency_hz: float,
+ period_time_ms: float,
+ start_time_ms: float,
+ end_time_ms: float,
+ rest_time_ms: float,
+ amplitude_volts: float,
+ offset_volts: float,
+ cutoff_frequency_hz: float,
+) -> np.ndarray:
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
# sawtooth with end time in center of waveform
- waveform = sawtooth(sampling_frequency_hz,
- period_time_ms,
- start_time_ms,
- (period_time_ms - start_time_ms) / 2,
- rest_time_ms,
- amplitude_volts,
- offset_volts,
- cutoff_frequency_hz
- )
+ waveform = sawtooth(
+ sampling_frequency_hz,
+ period_time_ms,
+ start_time_ms,
+ (period_time_ms - start_time_ms) / 2,
+ rest_time_ms,
+ amplitude_volts,
+ offset_volts,
+ cutoff_frequency_hz,
+ )
return waveform
diff --git a/src/view/widgets/device_widgets/stage_widget.py b/src/view/widgets/device_widgets/stage_widget.py
index fb7bfda..5e58a38 100644
--- a/src/view/widgets/device_widgets/stage_widget.py
+++ b/src/view/widgets/device_widgets/stage_widget.py
@@ -1,27 +1,31 @@
-from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
-from qtpy.QtWidgets import QLabel
import importlib
+from qtpy.QtWidgets import QLabel
+
+from view.widgets.base_device_widget import BaseDeviceWidget, scan_for_properties
+
+
class StageWidget(BaseDeviceWidget):
+ """_summary_"""
- def __init__(self, stage,
- advanced_user: bool = True):
- """
- Modify BaseDeviceWidget to be specifically for Stage. Main need is advanced user.
- :param stage: stage object
- :param advanced_user: boolean specifying complexity of widget. If False, only position is shown
- """
+ def __init__(self, stage, advanced_user: bool = True):
+ """_summary_
- self.stage_properties = scan_for_properties(stage) if advanced_user else {'position_mm': stage.position_mm}
+ :param stage: _description_
+ :type stage: _type_
+ :param advanced_user: _description_, defaults to True
+ :type advanced_user: bool, optional
+ """
+ self.stage_properties = scan_for_properties(stage) if advanced_user else {"position_mm": stage.position_mm}
self.stage_module = importlib.import_module(stage.__module__)
super().__init__(type(stage), self.stage_properties)
# alter position_mm widget to use instrument_axis as label
- self.property_widgets['position_mm'].setEnabled(False)
- position_label = self.property_widgets['position_mm'].findChild(QLabel)
- unit = getattr(type(stage).position_mm, 'unit', 'mm') # TODO: Change when deliminated property is updated
- position_label.setText(f'{stage.instrument_axis} [{unit}]')
+ self.property_widgets["position_mm"].setEnabled(False)
+ position_label = self.property_widgets["position_mm"].findChild(QLabel)
+ unit = getattr(type(stage).position_mm, "unit", "mm") # TODO: Change when deliminated property is updated
+ position_label.setText(f"{stage.instrument_axis} [{unit}]")
# update property_widgets['position_mm'] text to be white
style = """
@@ -33,4 +37,4 @@ def __init__(self, stage,
color : white;
}
"""
- self.property_widgets['position_mm'].setStyleSheet(style)
+ self.property_widgets["position_mm"].setStyleSheet(style)
diff --git a/src/view/widgets/device_widgets/waveform_widget.py b/src/view/widgets/device_widgets/waveform_widget.py
index 702cdac..91e970c 100644
--- a/src/view/widgets/device_widgets/waveform_widget.py
+++ b/src/view/widgets/device_widgets/waveform_widget.py
@@ -4,41 +4,54 @@
from qtpy.QtWidgets import QWidget, QVBoxLayout
from view.widgets.miscellaneous_widgets.q_clickable_label import QClickableLabel
from typing import Literal, TypedDict
-import inspect
-# TODO: Use this else where to. Consider moving it so we don't have to copy paste?
+
class SignalChangeVar:
- """Class that emits signal containing name when set function is used"""
+ """
+ Class that emits signal containing name when set function is used.
+ """
def __set_name__(self, owner, name) -> None:
- """
- Set name of class. Called in the init of class
- :param owner: instance of class
- :param name: set name of class. Will be variable name
+ """_summary_
+
+ :param owner: _description_
+ :type owner: _type_
+ :param name: _description_
+ :type name: _type_
"""
self.name = f"_{name}"
def __set__(self, instance, value) -> None:
- """
- Setting function of class
- :param instance: instance of class
- :param value: value to set to
- """
+ """_summary_
+ :param instance: _description_
+ :type instance: _type_
+ :param value: _description_
+ :type value: _type_
+ """
setattr(instance, self.name, value) # initially setting attr
instance.valueChanged.emit(self.name[1:], value)
def __get__(self, instance, value):
- """
- Getting function of class
- :param instance: instance of class
- :param value: object calling function
- :return: value of class
+ """_summary_
+
+ :param instance: _description_
+ :type instance: _type_
+ :param value: _description_
+ :type value: _type_
+ :return: _description_
+ :rtype: _type_
"""
return getattr(instance, self.name)
class PointyWaveParameters(TypedDict):
+ """_summary_
+
+ :param TypedDict: _description_
+ :type TypedDict: _type_
+ """
+
start_time_ms: float
end_time_ms: float
amplitude_volts: float
@@ -49,6 +62,12 @@ class PointyWaveParameters(TypedDict):
class SquareWaveParameters(TypedDict):
+ """_summary_
+
+ :param TypedDict: _description_
+ :type TypedDict: _type_
+ """
+
start_time_ms: float
end_time_ms: float
max_volts: float
@@ -58,7 +77,14 @@ class SquareWaveParameters(TypedDict):
class DraggableGraphItem(GraphItem):
- """Graph item representing triangle, sawtooth, and square wave that can be dragged by user to modify wave"""
+ """_summary_
+
+ :param GraphItem: _description_
+ :type GraphItem: _type_
+ :raises Exception: _description_
+ :raises Exception: _description_
+ """
+
# initialize waveform parameters
start_time_ms = SignalChangeVar()
end_time_ms = SignalChangeVar()
@@ -69,68 +95,75 @@ class DraggableGraphItem(GraphItem):
min_volts = SignalChangeVar()
valueChanged = Signal((str, float))
- def __init__(self, pos: np.ndarray,
- waveform: Literal['square wave', 'sawtooth', 'triangle wave'],
- parameters: PointyWaveParameters or SquareWaveParameters, **kwargs):
- """
- :param pos: 2d numpy array of concatenated lists of [[x],[y]] values
- :param waveform: type of waveform positions represent
- :param parameters: dictionary of parameters like amplitude_volts, end_time_ms, ect. and the corresponding values
- :param kwargs: kwargs relating to PlotWidget plot function
+ def __init__(
+ self,
+ pos: np.ndarray,
+ waveform: Literal["square wave", "sawtooth", "triangle wave"],
+ parameters: PointyWaveParameters or SquareWaveParameters,
+ **kwargs,
+ ):
+ """_summary_
+
+ :param pos: _description_
+ :type pos: np.ndarray
+ :param waveform: _description_
+ :type waveform: Literal['square wave', 'sawtooth', 'triangle wave']
+ :param parameters: _description_
+ :type parameters: PointyWaveParametersorSquareWaveParameters
"""
-
self.pos = pos
self.waveform = waveform
self.dragPoint = None
self.dragOffset = None
self.parameters = parameters
- self.name = kwargs.get('name', None)
- self.color = kwargs.get('color', 'black')
+ self.name = kwargs.get("name", None)
+ self.color = kwargs.get("color", "black")
super().__init__(pos=pos, **kwargs)
def setData(self, **kwargs) -> None:
- """
- Set data for waveform graph item
- :param kwargs: data to set
- """
-
- self.pos = kwargs.get('pos', self.pos)
- self.waveform = kwargs.get('waveform', self.waveform)
- self.parameters = kwargs.get('parameters', self.parameters)
+ """_summary_"""
+ self.pos = kwargs.get("pos", self.pos)
+ self.waveform = kwargs.get("waveform", self.waveform)
+ self.parameters = kwargs.get("parameters", self.parameters)
self.define_waves(self.waveform)
npts = self.pos.shape[0]
- kwargs['adj'] = np.column_stack((np.arange(0, npts - 1), np.arange(1, npts)))
- kwargs['data'] = np.empty(npts, dtype=[('index', int)])
- kwargs['data']['index'] = np.arange(npts)
+ kwargs["adj"] = np.column_stack((np.arange(0, npts - 1), np.arange(1, npts)))
+ kwargs["data"] = np.empty(npts, dtype=[("index", int)])
+ kwargs["data"]["index"] = np.arange(npts)
super().setData(**kwargs)
- def define_waves(self, waveform: Literal['square wave', 'sawtooth', 'triangle wave']) -> None:
- """
- Validate and define key indices in waveform
- :param waveform: specification of what waveform graph item represents
- """
+ def define_waves(self, waveform: Literal["square wave", "sawtooth", "triangle wave"]) -> None:
+ """_summary_
- if 'sawtooth' in waveform or 'triangle' in waveform:
+ :param waveform: _description_
+ :type waveform: Literal['square wave', 'sawtooth', 'triangle wave']
+ :raises Exception: _description_
+ :raises Exception: _description_
+ """
+ if "sawtooth" in waveform or "triangle" in waveform:
if self.pos.shape[0] != 5:
- raise Exception(f"Waveform {waveform} must have 5 points in data set. "
- f"Waveform has {self.pos.shape[0]}")
+ raise Exception(
+ f"Waveform {waveform} must have 5 points in data set. " f"Waveform has {self.pos.shape[0]}"
+ )
- elif 'square' in waveform:
+ elif "square" in waveform:
if self.pos.shape[0] != 6:
- raise Exception(f"Waveform {waveform} must have 6 points in data set. "
- f"Waveform has {self.pos.shape[0]}")
+ raise Exception(
+ f"Waveform {waveform} must have 6 points in data set. " f"Waveform has {self.pos.shape[0]}"
+ )
# block signals
self.blockSignals(True)
for k, v in self.parameters.items():
setattr(self, k, v)
self.blockSignals(False)
+
def mouseDragEvent(self, ev) -> None:
- """
- Register if user clicks and drags point of waveform
- :param ev: mouse drag event
- """
+ """_summary_
+ :param ev: _description_
+ :type ev: _type_
+ """
if ev.isStart():
pos = ev.buttonDownPos()
pts = self.scatter.pointsAt(pos)
@@ -150,37 +183,38 @@ def mouseDragEvent(self, ev) -> None:
ev.ignore()
return
ind = self.dragPoint.data()[0]
- if self.waveform == 'square wave':
+ if self.waveform == "square wave":
self.move_square_wave(ind, ev)
- elif self.waveform == 'sawtooth':
+ elif self.waveform == "sawtooth":
self.move_sawtooth(ind, ev)
- elif self.waveform == 'triangle wave':
+ elif self.waveform == "triangle wave":
self.move_triangle_wave(ind, ev)
self.setData(pos=self.pos)
ev.accept()
def move_square_wave(self, ind: int, ev) -> None:
- """
- Move square wave type waveform. Square wave will have 6 indices
- :param ind: index being dragged
- :param ev: mouse event
- """
+ """_summary_
+ :param ind: _description_
+ :type ind: int
+ :param ev: _description_
+ :type ev: _type_
+ """
min_v = self.device_min_volts
max_v = self.device_max_volts
y_pos = ev.pos()[1] + self.dragOffsetY # new y pos is old plus drag offset
- if ind in [1, 4] and min_v <= y_pos <= max_v: # either side of square is moved
+ if ind in [1, 4] and min_v <= y_pos <= max_v: # either side of square is moved
for i in [0, 1, 4, 5]:
self.pos[i][1] = y_pos
- elif ind in [2, 3] and min_v <= y_pos <= max_v: # square is moved
+ elif ind in [2, 3] and min_v <= y_pos <= max_v: # square is moved
for i in [2, 3]:
self.pos[i][1] = y_pos
self.min_volts = self.pos[1][1]
self.max_volts = self.pos[2][1]
- x_pos = ev.pos()[0] + self.dragOffsetX # new x pos is old plus drag offset
+ x_pos = ev.pos()[0] + self.dragOffsetX # new x pos is old plus drag offset
lower_limit_x = self.pos[ind - 1][0] if ind in [1, 3] else self.pos[ind - 2][0]
upper_limit_x = self.pos[ind + 2][0] if ind in [1, 3] else self.pos[ind + 1][0]
if lower_limit_x <= x_pos <= upper_limit_x and ind in [1, 2, 3, 4]:
@@ -192,74 +226,75 @@ def move_square_wave(self, ind: int, ev) -> None:
self.end_time_ms = self.pos[4][0] / 10
def move_sawtooth(self, ind: int, ev) -> None:
- """
- Move sawtooth type waveform. Sawtooth will have 5 indices
- :param ind: index being dragged
- :param ev: mouse event
- """
+ """_summary_
+ :param ind: _description_
+ :type ind: int
+ :param ev: _description_
+ :type ev: _type_
+ """
min_v = self.device_min_volts
max_v = self.device_max_volts
y_pos = ev.pos()[1] + self.dragOffsetY # new y pos is old plus drag offset
- if ind in [1, 3] and min_v <= y_pos <= max_v: # either side of peak is moved
+ if ind in [1, 3] and min_v <= y_pos <= max_v: # either side of peak is moved
self.pos[2][1] = y_pos + (self.pos[2][1] - self.pos[3][1]) # update peak to account for new offset volts
for i in [0, 1, 3, 4]: # update points to include drag value
self.pos[i][1] = ev.pos()[1] + self.dragOffsetY
- self.offset_volts = (self.pos[2][1] + y_pos) / 2 # update offset volts
+ self.offset_volts = (self.pos[2][1] + y_pos) / 2 # update offset volts
- elif ind == 2 and min_v <= y_pos <= max_v and min_v <= 2 * self.offset_volts - y_pos <= max_v: # peak is moved
- self.pos[2][1] = ev.pos()[1] + self.dragOffsetY # update peak with drag value
+ elif ind == 2 and min_v <= y_pos <= max_v and min_v <= 2 * self.offset_volts - y_pos <= max_v: # peak is moved
+ self.pos[2][1] = ev.pos()[1] + self.dragOffsetY # update peak with drag value
self.amplitude_volts = y_pos - self.offset_volts # update amplitude
- for i in [0, 1, 3, 4]: # update points to account for new amplitude
+ for i in [0, 1, 3, 4]: # update points to account for new amplitude
self.pos[i][1] = self.offset_volts - self.amplitude_volts
- x_pos = ev.pos()[0] + self.dragOffsetX # new x pos is old plus drag offset
- if ind in [1] and self.pos[ind - 1][0] <= x_pos <= self.pos[ind + 1][0]: # start time dragged
+ x_pos = ev.pos()[0] + self.dragOffsetX # new x pos is old plus drag offset
+ if ind in [1] and self.pos[ind - 1][0] <= x_pos <= self.pos[ind + 1][0]: # start time dragged
self.pos[ind][0] = x_pos
self.start_time_ms = x_pos / 10
self.pos[2][0] = x_pos + (self.end_time_ms / self.period_time_ms) * (self.pos[3][0] - x_pos)
- elif ind == 2 and self.pos[1][0] <= x_pos <= self.pos[3][0]: # peak is dragged
+ elif ind == 2 and self.pos[1][0] <= x_pos <= self.pos[3][0]: # peak is dragged
self.pos[ind][0] = x_pos
- self.end_time_ms = ((x_pos - self.pos[1][0]) / (self.pos[3][0] - self.pos[1][0])) * \
- self.period_time_ms
+ self.end_time_ms = ((x_pos - self.pos[1][0]) / (self.pos[3][0] - self.pos[1][0])) * self.period_time_ms
def move_triangle_wave(self, ind: int, ev) -> None:
- """
- Move triangle type waveform. Triangle will have 5 indices
- :param ind: index being dragged
- :param ev: mouse event
- """
+ """_summary_
+ :param ind: _description_
+ :type ind: int
+ :param ev: _description_
+ :type ev: _type_
+ """
min_v = self.device_min_volts
max_v = self.device_max_volts
y_pos = ev.pos()[1] + self.dragOffsetY # new y pos is old plus drag offset
- if ind in [1, 3] and min_v <= y_pos <= max_v: # either side of peak is moved
+ if ind in [1, 3] and min_v <= y_pos <= max_v: # either side of peak is moved
for i in [0, 1, 3, 4]: # update points to include drag value
self.pos[i][1] = ev.pos()[1] + self.dragOffsetY
- self.offset_volts = (self.pos[2][1] + y_pos) / 2 # update offset volts
+ self.offset_volts = (self.pos[2][1] + y_pos) / 2 # update offset volts
self.pos[2][1] = y_pos + (self.pos[2][1] - self.pos[3][1]) # update peak to account for new offset volts
- elif ind == 2 and min_v <= y_pos <= max_v and min_v <= 2 * self.offset_volts - y_pos <= max_v: # peak is moved
- self.pos[2][1] = ev.pos()[1] + self.dragOffsetY # update peak with drag value
- self.amplitude_volts = y_pos - self.offset_volts # update amplitude
- for i in [0, 1, 3, 4]: # update points to account for new amplitude
+ elif ind == 2 and min_v <= y_pos <= max_v and min_v <= 2 * self.offset_volts - y_pos <= max_v: # peak is moved
+ self.pos[2][1] = ev.pos()[1] + self.dragOffsetY # update peak with drag value
+ self.amplitude_volts = y_pos - self.offset_volts # update amplitude
+ for i in [0, 1, 3, 4]: # update points to account for new amplitude
self.pos[i][1] = self.offset_volts - self.amplitude_volts
- x_pos = ev.pos()[0] + self.dragOffsetX # new x pos is old plus drag offset
- if ind == 1 and self.pos[0][0] <= x_pos <= self.pos[2][0]: # point before peak
+ x_pos = ev.pos()[0] + self.dragOffsetX # new x pos is old plus drag offset
+ if ind == 1 and self.pos[0][0] <= x_pos <= self.pos[2][0]: # point before peak
self.pos[1][0] = x_pos
- self.start_time_ms = x_pos / 10 # update start time
- self.pos[2][0] = x_pos + (.5 * (self.pos[3][0] - x_pos)) # shift peak
+ self.start_time_ms = x_pos / 10 # update start time
+ self.pos[2][0] = x_pos + (0.5 * (self.pos[3][0] - x_pos)) # shift peak
+
class WaveformWidget(PlotWidget):
+ """_summary_"""
def __init__(self, **kwargs):
- """
- Plot widget to show daq waveforms
- """
+ """_summary_"""
# initialize legend widget
self.legend = QWidget()
self.legend.setLayout(QVBoxLayout())
@@ -267,65 +302,85 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)
- self.setBackground('#262930')
-
- def plot(self, pos: np.ndarray,
- waveform: Literal['square wave', 'sawtooth', 'triangle wave'],
- parameters: PointyWaveParameters or SquareWaveParameters, **kwargs) -> DraggableGraphItem:
+ self.setBackground("#262930")
+
+ def plot(
+ self,
+ pos: np.ndarray,
+ waveform: Literal["square wave", "sawtooth", "triangle wave"],
+ parameters: PointyWaveParameters or SquareWaveParameters,
+ **kwargs,
+ ) -> DraggableGraphItem:
+ """_summary_
+
+ :param pos: _description_
+ :type pos: np.ndarray
+ :param waveform: _description_
+ :type waveform: Literal['square wave', 'sawtooth', 'triangle wave']
+ :param parameters: _description_
+ :type parameters: PointyWaveParametersorSquareWaveParameters
+ :return: _description_
+ :rtype: DraggableGraphItem
"""
- Plot waveforms on graph
- :param pos: 2d numpy array of concatenated lists of [[x],[y]] values
- :param waveform: type of waveform positions represent
- :param parameters: dictionary of parameters like amplitude_volts, end_time_ms, ect. and the corresponding values
- :param kwargs: kwargs relating to PlotWidget plot function
- :return: item plotted in graph
- """
- kwargs['pen'] = mkPen(color=kwargs.get('color', 'grey'), width=3)
+ kwargs["pen"] = mkPen(color=kwargs.get("color", "grey"), width=3)
item = DraggableGraphItem(pos=pos, waveform=waveform, parameters=parameters, **kwargs)
item.setData(pos=pos, waveform=waveform, parameters=parameters, **kwargs)
self.addItem(item)
- if 'name' in kwargs.keys():
+ if "name" in kwargs.keys():
self.add_legend_item(item)
return item
def add_legend_item(self, item: DraggableGraphItem) -> None:
- """
- Add item to legend widget
- :param item: item to add to legend
+ """_summary_
+
+ :param item: _description_
+ :type item: DraggableGraphItem
"""
- self.legend_labels[item.name] = QClickableLabel(f'{item.name}'
- f' '
- f'')
+ self.legend_labels[item.name] = QClickableLabel(
+ f'{item.name}'
+ f' '
+ f""
+ )
self.legend_labels[item.name].clicked.connect(lambda: self.hide_show_line(item))
self.legend.layout().addWidget(self.legend_labels[item.name])
def removeDraggableGraphItem(self, item: DraggableGraphItem) -> None:
- """
- Remove DraggableGraphItem and remove from legend
- :param item: item to remove"""
+ """_summary_
+ :param item: _description_
+ :type item: DraggableGraphItem
+ """
self.removeItem(item)
if item.name is not None:
label = self.legend_labels[item.name]
self.legend.layout().removeWidget(label)
def hide_show_line(self, item) -> None:
- """
- Hide or reveal line if legend is clicked
- :param item: item to hide
+ """_summary_
+
+ :param item: _description_
+ :type item: _type_
"""
if item.isVisible():
item.setVisible(False)
- self.legend_labels[item.name].setText(f'{item.name}'
- f' '
- f'')
+ self.legend_labels[item.name].setText(
+ f'{item.name}'
+ f' '
+ f""
+ )
else:
item.setVisible(True)
- self.legend_labels[item.name].setText(f'{item.name}'
- f' '
- f'')
+ self.legend_labels[item.name].setText(
+ f'{item.name}'
+ f' '
+ f""
+ )
def wheelEvent(self, ev):
- """Overwriting to disable zoom"""
+ """_summary_
+
+ :param ev: _description_
+ :type ev: _type_
+ """
pass
diff --git a/src/view/widgets/miscellaneous_widgets/gl_ortho_view_widget.py b/src/view/widgets/miscellaneous_widgets/gl_ortho_view_widget.py
index 755f9ce..e86d805 100644
--- a/src/view/widgets/miscellaneous_widgets/gl_ortho_view_widget.py
+++ b/src/view/widgets/miscellaneous_widgets/gl_ortho_view_widget.py
@@ -1,33 +1,38 @@
+from typing import Literal
+
import numpy as np
from pyqtgraph.opengl import GLViewWidget
from qtpy.QtGui import QMatrix4x4
-from typing import Literal
+
class GLOrthoViewWidget(GLViewWidget):
"""
- Class inheriting from GLViewWidget that only allows specification of orthogonal or frustum view
+ Class inheriting from GLViewWidget that only allows specification of orthogonal or frustum view.
"""
+
# override projectionMatrix is overrided to enable true ortho projection
- def projectionMatrix(self, region=None, projection: Literal['ortho', 'frustum'] ='ortho') -> QMatrix4x4:
- """
- Function that return projection matrix of space
- :param region: region to create projection matrix for
- :param projection: type of projection. Limited to orthogonal or frustum
- :return:
- """
+ def projectionMatrix(self, region=None, projection: Literal["ortho", "frustum"] = "ortho") -> QMatrix4x4:
+ """_summary_
- assert projection in ['ortho', 'frustum']
+ :param region: _description_, defaults to None
+ :type region: _type_, optional
+ :param projection: _description_, defaults to 'ortho'
+ :type projection: Literal['ortho', 'frustum'], optional
+ :return: _description_
+ :rtype: QMatrix4x4
+ """
+ assert projection in ["ortho", "frustum"]
if region is None:
dpr = self.devicePixelRatio()
region = (0, 0, self.width() * dpr, self.height() * dpr)
x0, y0, w, h = self.getViewport()
- dist = self.opts['distance']
- fov = self.opts['fov']
+ dist = self.opts["distance"]
+ fov = self.opts["fov"]
nearClip = dist * 0.001
- farClip = dist * 1000.
+ farClip = dist * 1000.0
- r = nearClip * np.tan(fov * 0.5 * np.pi / 180.)
+ r = nearClip * np.tan(fov * 0.5 * np.pi / 180.0)
t = r * h / w
# note that x0 and width in these equations must
@@ -38,8 +43,8 @@ def projectionMatrix(self, region=None, projection: Literal['ortho', 'frustum']
top = t * ((region[1] + region[3] - y0) * (2.0 / h) - 1)
tr = QMatrix4x4()
- if projection == 'ortho':
+ if projection == "ortho":
tr.ortho(left, right, bottom, top, nearClip, farClip)
- elif projection == 'frustum':
+ elif projection == "frustum":
tr.frustum(left, right, bottom, top, nearClip, farClip)
- return tr
\ No newline at end of file
+ return tr
diff --git a/src/view/widgets/miscellaneous_widgets/gl_path_item.py b/src/view/widgets/miscellaneous_widgets/gl_path_item.py
index 98bd748..13a8265 100644
--- a/src/view/widgets/miscellaneous_widgets/gl_path_item.py
+++ b/src/view/widgets/miscellaneous_widgets/gl_path_item.py
@@ -1,95 +1,78 @@
-from pyqtgraph.opengl import GLLinePlotItem
-from OpenGL.GL import * # noqa
import numpy as np
+from OpenGL.GL import * # noqa
+from pyqtgraph.opengl import GLLinePlotItem
from qtpy.QtGui import QColor
class GLPathItem(GLLinePlotItem):
- """ Subclass of GLLinePlotItem that creates arrow at end of path"""
+ """Subclass of GLLinePlotItem that creates arrow at end of path."""
def __init__(self, parentItem=None, **kwds):
+ """_summary_
+ :param parentItem: _description_, defaults to None
+ :type parentItem: _type_, optional
+ """
super().__init__(parentItem)
- self.arrow_size_percent = kwds.get('arrow_size', 6.0)
- self.arrow_aspect_ratio = kwds.get('arrow_aspect_ratio', 4)
- self.path_start_color = kwds.get('path_start_color', 'magenta')
- self.path_end_color = kwds.get('path_end_color', 'green')
- self.width = kwds.get('width', 1)
+ self.arrow_size_percent = kwds.get("arrow_size", 6.0)
+ self.arrow_aspect_ratio = kwds.get("arrow_aspect_ratio", 4)
+ self.path_start_color = kwds.get("path_start_color", "magenta")
+ self.path_end_color = kwds.get("path_end_color", "green")
+ self.width = kwds.get("width", 1)
def setData(self, **kwds):
- """Rewrite to draw arrow at end of path"""
-
- kwds['width'] = self.width
+ """_summary_"""
+ kwds["width"] = self.width
- if 'pos' in kwds.keys():
- path = kwds['pos']
+ if "pos" in kwds.keys():
+ path = kwds["pos"]
# draw the end arrow
# determine last line segment direction and draw arrowhead correctly
if len(path) > 1:
vector = path[-1] - path[-2]
if vector[1] > 0:
# calculate arrow size based on vector
- arrow_size = abs(vector[1])*self.arrow_size_percent / 100
- x = np.array([path[-1, 0] - arrow_size,
- path[-1, 0] + arrow_size,
- path[-1, 0],
- path[-1, 0] - arrow_size])
- y = np.array([path[-1, 1],
- path[-1, 1],
- path[-1, 1] + arrow_size * self.arrow_aspect_ratio,
- path[-1, 1]])
- z = np.array([path[-1, 2],
- path[-1, 2],
- path[-1, 2],
- path[-1, 2]])
+ arrow_size = abs(vector[1]) * self.arrow_size_percent / 100
+ x = np.array(
+ [path[-1, 0] - arrow_size, path[-1, 0] + arrow_size, path[-1, 0], path[-1, 0] - arrow_size]
+ )
+ y = np.array(
+ [path[-1, 1], path[-1, 1], path[-1, 1] + arrow_size * self.arrow_aspect_ratio, path[-1, 1]]
+ )
+ z = np.array([path[-1, 2], path[-1, 2], path[-1, 2], path[-1, 2]])
elif vector[1] < 0:
# calculate arrow size based on vector
- arrow_size = abs(vector[1])*self.arrow_size_percent / 100
- x = np.array([path[-1, 0] + arrow_size,
- path[-1, 0] - arrow_size,
- path[-1, 0],
- path[-1, 0] + arrow_size])
- y = np.array([path[-1, 1],
- path[-1, 1],
- path[-1, 1] - arrow_size * self.arrow_aspect_ratio,
- path[-1, 1]])
- z = np.array([path[-1, 2],
- path[-1, 2],
- path[-1, 2],
- path[-1, 2]])
+ arrow_size = abs(vector[1]) * self.arrow_size_percent / 100
+ x = np.array(
+ [path[-1, 0] + arrow_size, path[-1, 0] - arrow_size, path[-1, 0], path[-1, 0] + arrow_size]
+ )
+ y = np.array(
+ [path[-1, 1], path[-1, 1], path[-1, 1] - arrow_size * self.arrow_aspect_ratio, path[-1, 1]]
+ )
+ z = np.array([path[-1, 2], path[-1, 2], path[-1, 2], path[-1, 2]])
elif vector[0] < 0:
# calculate arrow size based on vector
- arrow_size = abs(vector[0])*self.arrow_size_percent / 100
- x = np.array([path[-1, 0],
- path[-1, 0],
- path[-1, 0] - arrow_size * self.arrow_aspect_ratio,
- path[-1, 0]])
- y = np.array([path[-1, 1] + arrow_size,
- path[-1, 1] - arrow_size,
- path[-1, 1],
- path[-1, 1] + arrow_size])
- z = np.array([path[-1, 2],
- path[-1, 2],
- path[-1, 2],
- path[-1, 2]])
+ arrow_size = abs(vector[0]) * self.arrow_size_percent / 100
+ x = np.array(
+ [path[-1, 0], path[-1, 0], path[-1, 0] - arrow_size * self.arrow_aspect_ratio, path[-1, 0]]
+ )
+ y = np.array(
+ [path[-1, 1] + arrow_size, path[-1, 1] - arrow_size, path[-1, 1], path[-1, 1] + arrow_size]
+ )
+ z = np.array([path[-1, 2], path[-1, 2], path[-1, 2], path[-1, 2]])
else:
# calculate arrow size based on vector
- arrow_size = abs(vector[0])*self.arrow_size_percent / 100
- x = np.array([path[-1, 0],
- path[-1, 0],
- path[-1, 0] + arrow_size * self.arrow_aspect_ratio,
- path[-1, 0]])
- y = np.array([path[-1, 1] - arrow_size,
- path[-1, 1] + arrow_size,
- path[-1, 1],
- path[-1, 1] - arrow_size])
- z = np.array([path[-1, 2],
- path[-1, 2],
- path[-1, 2],
- path[-1, 2]])
+ arrow_size = abs(vector[0]) * self.arrow_size_percent / 100
+ x = np.array(
+ [path[-1, 0], path[-1, 0], path[-1, 0] + arrow_size * self.arrow_aspect_ratio, path[-1, 0]]
+ )
+ y = np.array(
+ [path[-1, 1] - arrow_size, path[-1, 1] + arrow_size, path[-1, 1], path[-1, 1] - arrow_size]
+ )
+ z = np.array([path[-1, 2], path[-1, 2], path[-1, 2], path[-1, 2]])
xyz = np.transpose(np.array([x, y, z]))
- kwds['pos'] = np.concatenate((path, xyz), axis=0)
+ kwds["pos"] = np.concatenate((path, xyz), axis=0)
num_tiles = len(path)
path_gradient = np.zeros((num_tiles, 4))
@@ -98,10 +81,10 @@ def setData(self, **kwds):
# fill in (rgb)a first with linear weighted average
start = QColor(self.path_start_color).getRgbF()
end = QColor(self.path_end_color).getRgbF()
- path_gradient[tile, :] = \
- (num_tiles - tile) / num_tiles * np.array(start) + \
- (tile / num_tiles) * np.array(end)
+ path_gradient[tile, :] = (num_tiles - tile) / num_tiles * np.array(start) + (
+ tile / num_tiles
+ ) * np.array(end)
colors = np.repeat([path_gradient[-1, :]], repeats=4, axis=0)
- kwds['color'] = np.concatenate((path_gradient, colors), axis=0)
+ kwds["color"] = np.concatenate((path_gradient, colors), axis=0)
super().setData(**kwds)
diff --git a/src/view/widgets/miscellaneous_widgets/gl_shaded_box_item.py b/src/view/widgets/miscellaneous_widgets/gl_shaded_box_item.py
index bfef88b..3813323 100644
--- a/src/view/widgets/miscellaneous_widgets/gl_shaded_box_item.py
+++ b/src/view/widgets/miscellaneous_widgets/gl_shaded_box_item.py
@@ -1,25 +1,37 @@
-from pyqtgraph.opengl import GLMeshItem
import numpy as np
-from qtpy.QtGui import QColor
from OpenGL.GL import * # noqa
+from pyqtgraph.opengl import GLMeshItem
+from qtpy.QtGui import QColor
class GLShadedBoxItem(GLMeshItem):
- """Subclass of GLMeshItem creates a rectangular mesh item"""
-
- def __init__(self, pos: np.ndarray,
- size: np.ndarray,
- color: str = 'cyan',
- width: float = 1,
- opacity: float = 1,
- *args,
- **kwargs):
- """
- :param pos: position of item
- :param size: size of item
- :param color: color of item
+ """
+ Subclass of GLMeshItem creates a rectangular mesh item.
+ """
+
+ def __init__(
+ self,
+ pos: np.ndarray,
+ size: np.ndarray,
+ color: str = "cyan",
+ width: float = 1,
+ opacity: float = 1,
+ *args,
+ **kwargs,
+ ):
+ """_summary_
+
+ :param pos: _description_
+ :type pos: np.ndarray
+ :param size: _description_
+ :type size: np.ndarray
+ :param color: _description_, defaults to 'cyan'
+ :type color: str, optional
+ :param width: _description_, defaults to 1
+ :type width: float, optional
+ :param opacity: _description_, defaults to 1
+ :type opacity: float, optional
"""
-
self._size = size
self._width = width
self._opacity = opacity
@@ -29,26 +41,44 @@ def __init__(self, pos: np.ndarray,
self._pos = pos
self._vertexes, self._faces = self._create_box(pos, size)
- super().__init__(vertexes=self._vertexes, faces=self._faces, faceColors=colors,
- drawEdges=True, edgeColor=(0, 0, 0, 1), *args, **kwargs)
+ super().__init__(
+ vertexes=self._vertexes,
+ faces=self._faces,
+ faceColors=colors,
+ drawEdges=True,
+ edgeColor=(0, 0, 0, 1),
+ *args,
+ **kwargs,
+ )
def _create_box(self, pos: np.ndarray, size: np.ndarray) -> (np.ndarray, np.ndarray):
+ """_summary_
+
+ :param self: _description_
+ :type self: _type_
+ :param np: _description_
+ :type np: _type_
+ :return: _description_
+ :rtype: _type_
"""
- Convenience method create the vertexes and faces of box to draw
- :param pos: position of upper right corner of box
- :param size: x,y,z size of box
- :return:
- """
-
nCubes = np.prod(pos.shape[:-1])
cubeVerts = np.mgrid[0:2, 0:2, 0:2].reshape(3, 8).transpose().reshape(1, 8, 3)
- cubeFaces = np.array([
- [0, 1, 2], [3, 2, 1],
- [4, 5, 6], [7, 6, 5],
- [0, 1, 4], [5, 4, 1],
- [2, 3, 6], [7, 6, 3],
- [0, 2, 4], [6, 4, 2],
- [1, 3, 5], [7, 5, 3]]).reshape(1, 12, 3)
+ cubeFaces = np.array(
+ [
+ [0, 1, 2],
+ [3, 2, 1],
+ [4, 5, 6],
+ [7, 6, 5],
+ [0, 1, 4],
+ [5, 4, 1],
+ [2, 3, 6],
+ [7, 6, 3],
+ [0, 2, 4],
+ [6, 4, 2],
+ [1, 3, 5],
+ [7, 5, 3],
+ ]
+ ).reshape(1, 12, 3)
size = size.reshape((nCubes, 1, 3))
pos = pos.reshape((nCubes, 1, 3))
vertexes = (cubeVerts * size + pos)[0]
@@ -57,18 +87,30 @@ def _create_box(self, pos: np.ndarray, size: np.ndarray) -> (np.ndarray, np.ndar
return vertexes, faces
def color(self) -> str or list[float, float, float, float]:
- """Color of box and outline"""
+ """_summary_
+
+ :return: _description_
+ :rtype: str or list[float, float, float, float]
+ """
return self._color
def setColor(self, color: str or list[float, float, float, float]) -> None:
+ """_summary_
+
+ :param color: _description_
+ :type color: strorlist[float, float, float, float]
+ """
self._color = color
colors = np.array([self._convert_color(self._color) for i in range(12)])
self.setMeshData(vertexes=self._vertexes, faces=self._faces, faceColors=colors)
def _convert_color(self, color: str) -> list[float, float, float, float]:
- """
- Convenience method used to convert string color
- :param color: name of color to convert to rgbF values
+ """_summary_
+
+ :param color: _description_
+ :type color: str
+ :return: _description_
+ :rtype: list[float, float, float, float]
"""
if isinstance(color, str):
rgbf = list(QColor(color).getRgbF())
@@ -76,27 +118,30 @@ def _convert_color(self, color: str) -> list[float, float, float, float]:
return color
def size(self) -> np.ndarray:
- """Size of box and outline"""
+ """_summary_
+
+ :return: _description_
+ :rtype: np.ndarray
+ """
return self._size
def setSize(self, x: float, y: float, z: float) -> None:
+ """_summary_
+
+ :param x: _description_
+ :type x: float
+ :param y: _description_
+ :type y: float
+ :param z: _description_
+ :type z: float
"""
- Set size of box
- :param x: size in the x dimension
- :param y: size in the y dimension
- :param z: size in the z dimension
- """
-
self._size = np.array([x, y, z])
self._vertexes, self._faces = self._create_box(self._pos, self._size)
colors = np.array([self._convert_color(self._color) for i in range(12)])
- self.setMeshData(vertexes=self._vertexes,
- faces=self._faces,
- faceColors=colors)
+ self.setMeshData(vertexes=self._vertexes, faces=self._faces, faceColors=colors)
def paint(self) -> None:
- """Overwriting to include box outline"""
-
+ """_summary_"""
super().paint()
self.setupGLState()
diff --git a/src/view/widgets/miscellaneous_widgets/q_clickable_label.py b/src/view/widgets/miscellaneous_widgets/q_clickable_label.py
index f98b867..08720ab 100644
--- a/src/view/widgets/miscellaneous_widgets/q_clickable_label.py
+++ b/src/view/widgets/miscellaneous_widgets/q_clickable_label.py
@@ -1,16 +1,20 @@
-from qtpy.QtWidgets import QLabel
from qtpy.QtCore import Signal
from qtpy.QtGui import QMouseEvent
+from qtpy.QtWidgets import QLabel
+
class QClickableLabel(QLabel):
- """QLabel that emits signal when clicked"""
+ """
+ QLabel that emits signal when clicked.
+ """
clicked = Signal()
def mousePressEvent(self, ev: QMouseEvent, **kwargs) -> None:
- """
- Overwriting to emit signal
- :param ev: mouse click event
+ """_summary_
+
+ :param ev: _description_
+ :type ev: QMouseEvent
"""
self.clicked.emit()
super().mousePressEvent(ev, **kwargs)
\ No newline at end of file
diff --git a/src/view/widgets/miscellaneous_widgets/q_dock_widget_title_bar.py b/src/view/widgets/miscellaneous_widgets/q_dock_widget_title_bar.py
index 1aaf41b..741f6f0 100644
--- a/src/view/widgets/miscellaneous_widgets/q_dock_widget_title_bar.py
+++ b/src/view/widgets/miscellaneous_widgets/q_dock_widget_title_bar.py
@@ -1,19 +1,21 @@
-from qtpy.QtWidgets import QFrame, QPushButton, QStyle, QDockWidget, QLabel, QHBoxLayout
+from qtpy.QtCore import Property, QObject, Qt, QTimer, Signal, Slot
from qtpy.QtGui import QMouseEvent
-from qtpy.QtCore import Signal, QTimer, Property, QObject, Slot, Qt
+from qtpy.QtWidgets import QDockWidget, QFrame, QHBoxLayout, QLabel, QPushButton, QStyle
class QDockWidgetTitleBar(QFrame):
- """Widget to act as a QDockWidget title bar. Will allow user to collapse, expand, pop out, and close widget"""
+ """
+ Widget to act as a QDockWidget title bar. Will allow user to collapse, expand, pop out, and close widget.
+ """
resized = Signal()
def __init__(self, dock: QDockWidget, *args, **kwargs):
+ """_summary_
+ :param dock: _description_
+ :type dock: QDockWidget
"""
- :param dock: QDockWidget that widget will be placed in
- """
-
super().__init__(*args, **kwargs)
self._timeline = None
@@ -66,18 +68,21 @@ def __init__(self, dock: QDockWidget, *args, **kwargs):
self.setLayout(layout)
def close(self) -> None:
- """Close widget"""
-
+ """
+ Close widget.
+ """
self.dock.close()
def pop_out(self) -> None:
- """Pop out widget"""
-
+ """
+ Pop out widget.
+ """
self.dock.setFloating(not self.dock.isFloating())
def minimize(self) -> None:
- """Minimize widget"""
-
+ """
+ Minimize widget.
+ """
self.dock.setMinimumHeight(25)
self.current_height = self.dock.widget().height()
self._timeline = TimeLine(loopCount=1, interval=1, step_size=-5)
@@ -86,8 +91,9 @@ def minimize(self) -> None:
self._timeline.start()
def maximize(self) -> None:
- """Minimize widget"""
-
+ """
+ Minimize widget.
+ """
if self.current_height is not None:
self._timeline = TimeLine(loopCount=1, interval=1, step_size=5)
self._timeline.timerEnded.connect(lambda: self.dock.setMinimumHeight(25))
@@ -97,11 +103,11 @@ def maximize(self) -> None:
self._timeline.start()
def set_widget_size(self, i) -> None:
- """
- Change size of widget based on qtimer
- :param i: height to set widgets that will iterate based on qtimer
- """
+ """_summary_
+ :param i: _description_
+ :type i: _type_
+ """
self.dock.widget().resize(self.dock.widget().width(), int(i))
self.dock.resize(self.dock.width(), int(i))
if i > self.dock.minimumHeight():
@@ -110,9 +116,10 @@ def set_widget_size(self, i) -> None:
self.resized.emit()
def mousePressEvent(self, event: QMouseEvent) -> None:
- """
- Overwrite to update initial pos of mouse
- :param event: mouse press event
+ """_summary_
+
+ :param event: _description_
+ :type event: QMouseEvent
"""
if event.button() == Qt.MouseButton.LeftButton:
self.initial_pos = event.position().toPoint()
@@ -120,10 +127,10 @@ def mousePressEvent(self, event: QMouseEvent) -> None:
event.accept()
def mouseMoveEvent(self, event: QMouseEvent) -> None:
- """
- Overwrite to move window when mouse is dragged
- :param event: mouse event
- :return:
+ """_summary_
+
+ :param event: _description_
+ :type event: QMouseEvent
"""
if self.initial_pos is not None:
delta = event.position().toPoint() - self.initial_pos
@@ -134,16 +141,24 @@ def mouseMoveEvent(self, event: QMouseEvent) -> None:
super().mouseMoveEvent(event)
event.accept()
+
class TimeLine(QObject):
+ """_summary_"""
+
frameChanged = Signal(float)
timerEnded = Signal()
def __init__(self, interval=60, loopCount=1, step_size=1, parent=None):
- """
- :param interval: interval at which to step up and emit value in milliseconds
- :param loopCount: how many times to repeat timeline
- :param step_size: step size to take between emitted values
- :param parent: parent of widget
+ """_summary_
+
+ :param interval: _description_, defaults to 60
+ :type interval: int, optional
+ :param loopCount: _description_, defaults to 1
+ :type loopCount: int, optional
+ :param step_size: _description_, defaults to 1
+ :type step_size: int, optional
+ :param parent: _description_, defaults to None
+ :type parent: _type_, optional
"""
super(TimeLine, self).__init__(parent)
self._stepSize = step_size
@@ -157,11 +172,11 @@ def __init__(self, interval=60, loopCount=1, step_size=1, parent=None):
def on_timeout(self) -> None:
"""
- Function called by Qtimer that will trigger a step of current step_size and emit new counter value
+ Function called by Qtimer that will trigger a step of current step_size and emit new counter value.
"""
-
- if (self._startFrame <= self._counter <= self._endFrame and self._stepSize > 0) or \
- (self._startFrame >= self._counter >= self._endFrame and self._stepSize < 0):
+ if (self._startFrame <= self._counter <= self._endFrame and self._stepSize > 0) or (
+ self._startFrame >= self._counter >= self._endFrame and self._stepSize < 0
+ ):
self.frameChanged.emit(self._counter)
self._counter += self._stepSize
else:
@@ -173,58 +188,60 @@ def on_timeout(self) -> None:
self.timerEnded.emit()
def setLoopCount(self, loopCount: int) -> None:
- """
- Function set loop count variable
- :param loopCount: integer specifying how many times to repeat timeline
+ """_summary_
+
+ :param loopCount: _description_
+ :type loopCount: int
"""
self._loopCount = loopCount
def loopCount(self) -> int:
- """
- Current loop count
- :return: Current loop count
+ """_summary_
+
+ :return: _description_
+ :rtype: int
"""
return self._loopCount
interval = Property(int, fget=loopCount, fset=setLoopCount)
def setInterval(self, interval: int) -> None:
- """
- Function to set interval variable in seconds
- :param interval: integer specifying the length of timeline in milliseconds
+ """_summary_
+
+ :param interval: _description_
+ :type interval: int
"""
self._timer.setInterval(interval)
def interval(self) -> int:
- """
- Current interval time in milliseconds
- :return: integer value of current interval time in milliseconds
+ """_summary_
+
+ :return: _description_
+ :rtype: int
"""
return self._timer.interval()
interval = Property(int, fget=interval, fset=setInterval)
def setFrameRange(self, startFrame: float, endFrame: float) -> None:
- """
- Setting function for starting and end value that timeline will step through
- :param startFrame: starting value
- :param endFrame: ending value
+ """_summary_
+
+ :param startFrame: _description_
+ :type startFrame: float
+ :param endFrame: _description_
+ :type endFrame: float
"""
self._startFrame = startFrame
self._endFrame = endFrame
@Slot()
- def start(self)-> None:
- """
- Function to start QTimer and begin emitting and stepping through value
- """
+ def start(self) -> None:
+ """_summary_"""
self._counter = self._startFrame
self._loop_counter = 0
self._timer.start()
- def stop(self)-> None:
- """
- Function to stop QTimer and stop stepping through values
- """
+ def stop(self) -> None:
+ """_summary_"""
self._timer.stop()
- self.timerEnded.emit()
\ No newline at end of file
+ self.timerEnded.emit()
diff --git a/src/view/widgets/miscellaneous_widgets/q_item_delegates.py b/src/view/widgets/miscellaneous_widgets/q_item_delegates.py
index 22ba9f5..d29c0f4 100644
--- a/src/view/widgets/miscellaneous_widgets/q_item_delegates.py
+++ b/src/view/widgets/miscellaneous_widgets/q_item_delegates.py
@@ -1,45 +1,135 @@
-from qtpy.QtWidgets import QStyledItemDelegate, QTextEdit, QSpinBox, QComboBox, QDoubleSpinBox
+from qtpy.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QStyledItemDelegate, QTextEdit
+
class QTextItemDelegate(QStyledItemDelegate):
- """QStyledItemDelegate acting like QTextEdit"""
+ """
+ QStyledItemDelegate acting like QTextEdit.
+ """
def createEditor(self, parent, options, index):
+ """_summary_
+
+ :param parent: _description_
+ :type parent: _type_
+ :param options: _description_
+ :type options: _type_
+ :param index: _description_
+ :type index: _type_
+ :return: _description_
+ :rtype: _type_
+ """
return QTextEdit(parent)
def setEditorData(self, editor, index):
+ """_summary_
+
+ :param editor: _description_
+ :type editor: _type_
+ :param index: _description_
+ :type index: _type_
+ """
editor.setText(str(index.data()))
def setModelData(self, editor, model, index):
+ """_summary_
+
+ :param editor: _description_
+ :type editor: _type_
+ :param model: _description_
+ :type model: _type_
+ :param index: _description_
+ :type index: _type_
+ """
model.setData(index, editor.toPlainText())
class QComboItemDelegate(QStyledItemDelegate):
- """QStyledItemDelegate acting like QComboBox"""
+ """
+ QStyledItemDelegate acting like QComboBox.
+ """
def __init__(self, items: list, parent=None):
+ """_summary_
+
+ :param items: _description_
+ :type items: list
+ :param parent: _description_, defaults to None
+ :type parent: _type_, optional
+ """
super().__init__(parent)
self.items = items
def createEditor(self, parent, options, index):
+ """_summary_
+
+ :param parent: _description_
+ :type parent: _type_
+ :param options: _description_
+ :type options: _type_
+ :param index: _description_
+ :type index: _type_
+ :return: _description_
+ :rtype: _type_
+ """
return QComboBox(parent)
def setEditorData(self, editor, index):
+ """_summary_
+
+ :param editor: _description_
+ :type editor: _type_
+ :param index: _description_
+ :type index: _type_
+ """
editor.addItems(self.items)
def setModelData(self, editor, model, index):
+ """_summary_
+
+ :param editor: _description_
+ :type editor: _type_
+ :param model: _description_
+ :type model: _type_
+ :param index: _description_
+ :type index: _type_
+ """
model.setData(index, editor.currentText())
class QSpinItemDelegate(QStyledItemDelegate):
- """QStyledItemDelegate acting like QSpinBox"""
+ """
+ QStyledItemDelegate acting like QSpinBox.
+ """
def __init__(self, minimum=None, maximum=None, step=None, parent=None):
+ """_summary_
+
+ :param minimum: _description_, defaults to None
+ :type minimum: _type_, optional
+ :param maximum: _description_, defaults to None
+ :type maximum: _type_, optional
+ :param step: _description_, defaults to None
+ :type step: _type_, optional
+ :param parent: _description_, defaults to None
+ :type parent: _type_, optional
+ """
super().__init__(parent)
self.minimum = minimum if minimum is not None else -2147483647
self.maximum = maximum if maximum is not None else 2147483647
- self.step = step if step is not None else .01
+ self.step = step if step is not None else 0.01
def createEditor(self, parent, options, index):
+ """_summary_
+
+ :param parent: _description_
+ :type parent: _type_
+ :param options: _description_
+ :type options: _type_
+ :param index: _description_
+ :type index: _type_
+ :return: _description_
+ :rtype: _type_
+ """
box = QSpinBox(parent) if type(self.step) == int else QDoubleSpinBox(parent)
box.setMinimum(self.minimum)
@@ -50,9 +140,25 @@ def createEditor(self, parent, options, index):
return box
def setEditorData(self, editor, index):
+ """_summary_
+
+ :param editor: _description_
+ :type editor: _type_
+ :param index: _description_
+ :type index: _type_
+ """
value = int(index.data()) if type(self.step) == int else float(index.data())
editor.setValue(value)
def setModelData(self, editor, model, index):
+ """_summary_
+
+ :param editor: _description_
+ :type editor: _type_
+ :param model: _description_
+ :type model: _type_
+ :param index: _description_
+ :type index: _type_
+ """
value = int(editor.value()) if type(self.step) == int else float(editor.value())
model.setData(index, value)
diff --git a/src/view/widgets/miscellaneous_widgets/q_non_scrollable_tree_widget.py b/src/view/widgets/miscellaneous_widgets/q_non_scrollable_tree_widget.py
index 009ddd9..c5f2463 100644
--- a/src/view/widgets/miscellaneous_widgets/q_non_scrollable_tree_widget.py
+++ b/src/view/widgets/miscellaneous_widgets/q_non_scrollable_tree_widget.py
@@ -1,7 +1,13 @@
from qtpy.QtWidgets import QTreeWidget
-class QNonScrollableTreeWidget(QTreeWidget):
- """Disable mouse wheel scroll"""
+class QNonScrollableTreeWidget(QTreeWidget):
+ """_summary_
+ """
def wheelEvent(self, event):
- pass
\ No newline at end of file
+ """_summary_
+
+ :param event: _description_
+ :type event: _type_
+ """
+ pass
diff --git a/src/view/widgets/miscellaneous_widgets/q_scrollable_float_slider.py b/src/view/widgets/miscellaneous_widgets/q_scrollable_float_slider.py
index e254392..025432a 100644
--- a/src/view/widgets/miscellaneous_widgets/q_scrollable_float_slider.py
+++ b/src/view/widgets/miscellaneous_widgets/q_scrollable_float_slider.py
@@ -3,49 +3,119 @@
class QScrollableFloatSlider(QSlider):
- """QSlider that will emit signal if scrolled with mouse wheel and allow float values"""
+ """
+ QSlider that will emit signal if scrolled with mouse wheel and allow float values.
+ """
+
sliderMoved = Signal(float) # redefine slider move to emit float
def __init__(self, decimals=0, *args, **kwargs):
+ """_summary_
+
+ :param decimals: _description_, defaults to 0
+ :type decimals: int, optional
+ """
super().__init__(*args, **kwargs)
- self.divisor = 10 ** decimals
+ self.divisor = 10**decimals
def value(self):
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
return float(super().value()) / self.divisor
def setMinimum(self, value):
+ """_summary_
+
+ :param value: _description_
+ :type value: _type_
+ :return: _description_
+ :rtype: _type_
+ """
return super().setMinimum(int(value * self.divisor))
def setMaximum(self, value):
+ """_summary_
+
+ :param value: _description_
+ :type value: _type_
+ :return: _description_
+ :rtype: _type_
+ """
return super().setMaximum(int(value * self.divisor))
def maximum(self):
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
return super().maximum() / self.divisor
def minimum(self):
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
return super().minimum() / self.divisor
def setSingleStep(self, value):
+ """_summary_
+
+ :param value: _description_
+ :type value: _type_
+ :return: _description_
+ :rtype: _type_
+ """
return super().setSingleStep(value * self.divisor)
def singleStep(self):
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
return float(super().singleStep()) / self.divisor
def setValue(self, value):
+ """_summary_
+
+ :param value: _description_
+ :type value: _type_
+ """
super().setValue(int(value * self.divisor))
def wheelEvent(self, event):
+ """_summary_
+
+ :param event: _description_
+ :type event: _type_
+ """
super().wheelEvent(event)
value = self.value()
self.sliderMoved.emit(value)
self.sliderReleased.emit()
+
def mouseMoveEvent(self, event):
+ """_summary_
+
+ :param event: _description_
+ :type event: _type_
+ """
super().mouseMoveEvent(event)
if event.buttons() == Qt.MouseButton.LeftButton:
value = self.value()
self.sliderMoved.emit(value)
def mousePressEvent(self, event):
+ """_summary_
+
+ :param event: _description_
+ :type event: _type_
+ """
super().mousePressEvent(event)
value = self.value()
self.sliderMoved.emit(value)
diff --git a/src/view/widgets/miscellaneous_widgets/q_scrollable_line_edit.py b/src/view/widgets/miscellaneous_widgets/q_scrollable_line_edit.py
index 5c0e473..1159a32 100644
--- a/src/view/widgets/miscellaneous_widgets/q_scrollable_line_edit.py
+++ b/src/view/widgets/miscellaneous_widgets/q_scrollable_line_edit.py
@@ -1,16 +1,23 @@
+from qtpy.QtGui import QDoubleValidator, QIntValidator
from qtpy.QtWidgets import QLineEdit
-from qtpy.QtGui import QIntValidator, QDoubleValidator
-import typing
+
class QScrollableLineEdit(QLineEdit):
- """Widget inheriting from QLineEdit that allows value to be scrollable"""
+ """
+ Widget inheriting from QLineEdit that allows value to be scrollable.
+ """
def wheelEvent(self, event):
+ """_summary_
+
+ :param event: _description_
+ :type event: _type_
+ """
super().wheelEvent(event)
if self.validator() is not None and type(self.validator()) in [QIntValidator, QDoubleValidator]:
if type(self.validator()) == QDoubleValidator:
- dec = len(self.text()[self.text().index('.') + 1:]) if '.' in self.text() else 0
- change = 10 ** (-dec) if event.angleDelta().y() > 0 else -10 ** (-dec)
+ dec = len(self.text()[self.text().index(".") + 1 :]) if "." in self.text() else 0
+ change = 10 ** (-dec) if event.angleDelta().y() > 0 else -(10 ** (-dec))
new_value = float(f"%.{dec}f" % float(float(self.text()) + change))
else: # QIntValidator
new_value = int(self.text()) + 1 if event.angleDelta().y() > 0 else int(self.text()) - 1
@@ -18,11 +25,18 @@ def wheelEvent(self, event):
self.setText(str(new_value))
self.editingFinished.emit()
-
def value(self):
- """Get float or integer of text"""
+ """_summary_
+
+ :return: _description_
+ :rtype: _type_
+ """
return float(self.text())
def setValue(self, value):
- """Set number as text"""
+ """_summary_
+
+ :param value: _description_
+ :type value: _type_
+ """
self.setText(str(value))
diff --git a/src/view/widgets/miscellaneous_widgets/q_start_stop_table_header.py b/src/view/widgets/miscellaneous_widgets/q_start_stop_table_header.py
index 4341461..a97bb94 100644
--- a/src/view/widgets/miscellaneous_widgets/q_start_stop_table_header.py
+++ b/src/view/widgets/miscellaneous_widgets/q_start_stop_table_header.py
@@ -1,16 +1,23 @@
-from qtpy.QtWidgets import QTableWidgetItem, QHeaderView, QMenu, QAction, QStyle
from qtpy.QtCore import Qt, Signal
from qtpy.QtGui import QMouseEvent
+from qtpy.QtWidgets import QAction, QHeaderView, QMenu, QStyle, QTableWidgetItem
class QStartStopTableHeader(QHeaderView):
- """QTableWidgetItem to be used to select certain tiles to start at"""
+ """
+ QTableWidgetItem to be used to select certain tiles to start at.
+ """
sectionRightClicked = Signal(QMouseEvent)
startChanged = Signal(int)
stopChanged = Signal(int)
def __init__(self, parent):
+ """_summary_
+
+ :param parent: _description_
+ :type parent: _type_
+ """
super().__init__(Qt.Vertical, parent)
self.start = None
@@ -19,8 +26,10 @@ def __init__(self, parent):
self.sectionRightClicked.connect(self.menu_popup)
def mousePressEvent(self, event, **kwargs):
- """Detect click event and set correct setting
- :param **kwargs:
+ """_summary_
+
+ :param event: _description_
+ :type event: _type_
"""
super().mousePressEvent(event, **kwargs)
@@ -28,12 +37,11 @@ def mousePressEvent(self, event, **kwargs):
self.sectionRightClicked.emit(event)
def menu_popup(self, event: QMouseEvent):
- """
- Function to set tile start or stop
- :param event: mousePressEvent of click
- :return:
- """
+ """_summary_
+ :param event: _description_
+ :type event: QMouseEvent
+ """
index = self.logicalIndexAt(event.pos())
start_act = QAction("Set Start", self)
@@ -55,12 +63,11 @@ def menu_popup(self, event: QMouseEvent):
menu.popup(self.mapToGlobal(event.pos()))
def set_start(self, index: int):
- """
- Set start tile
- :param index: index to set to start at
- :return:
- """
+ """_summary_
+ :param index: _description_
+ :type index: int
+ """
if self.start is not None:
self.clear(self.start)
@@ -73,12 +80,11 @@ def set_start(self, index: int):
self.startChanged.emit(index)
def set_stop(self, index: int):
- """
- Set stop tile
- :param index: index to set to stop at
- :return:
- """
+ """_summary_
+ :param index: _description_
+ :type index: int
+ """
if self.stop is not None:
self.clear(self.stop)
@@ -91,18 +97,16 @@ def set_stop(self, index: int):
self.stopChanged.emit(index)
def clear(self, index: int):
- """
- Clear index of start or stop
- :param index:
- :return:
- """
-
+ """_summary_
+ :param index: _description_
+ :type index: int
+ """
if index == self.stop:
self.stop = None
elif index == self.start:
self.start = None
item = QTableWidgetItem()
- item.setText(str(index+1))
+ item.setText(str(index + 1))
self.parent().setVerticalHeaderItem(index, item)
diff --git a/tests/test_acquisition_view.py b/tests/test_acquisition_view.py
index e0a6b05..146a88b 100644
--- a/tests/test_acquisition_view.py
+++ b/tests/test_acquisition_view.py
@@ -1,135 +1,104 @@
-""" testing AcquisitionView """
-
-import unittest
-from view.acquisition_view import AcquisitionView
-from qtpy.QtWidgets import QApplication, QWidget
-from qtpy.QtCore import Qt
import sys
-import numpy as np
+import unittest
from unittest.mock import MagicMock
-from pathlib import Path
-import os
+
+from qtpy.QtCore import Qt
+from qtpy.QtTest import QSignalSpy
+from qtpy.QtWidgets import QApplication
+
+from view.acquisition_view import AcquisitionView
+from view.widgets.device_widgets.laser_widget import LaserWidget
from view.widgets.device_widgets.stage_widget import StageWidget
-from voxel.devices.lasers.simulated import SimulatedLaser
+from voxel.devices.laser.simulated import SimulatedLaser
from voxel.devices.stage.simulated import Stage
-from view.widgets.device_widgets.laser_widget import LaserWidget
-from threading import Lock
-from qtpy.QtTest import QTest, QSignalSpy
app = QApplication(sys.argv)
class AcquisitionViewTests(unittest.TestCase):
- """Tests for AcquisitionView"""
-
- # TODO: A lot more to test for
+ """_summary_
+ """
def test_update_tiles(self):
- """test update_tiles functions"""
-
+ """_summary_"""
channels = {
- '488': {
- 'filters': ['BP488'],
- 'lasers': ['488nm'],
- 'cameras': ['vnp - 604mx', 'vp-151mx']},
- '639': {
- 'filters': ['LP638'],
- 'lasers': ['639nm'],
- 'cameras': ['vnp - 604mx', 'vp-151mx']}
+ "488": {"filters": ["BP488"], "lasers": ["488nm"], "cameras": ["vnp - 604mx", "vp-151mx"]},
+ "639": {"filters": ["LP638"], "lasers": ["639nm"], "cameras": ["vnp - 604mx", "vp-151mx"]},
}
properties = {
- 'lasers': ['power_setpoint_mw'],
- 'focusing_stages': ['position_mm'],
- 'start_delay_time': {
- 'delegate': 'spin',
- 'type': 'float',
- 'minimum': 0,
- 'initial_value': 15,
+ "lasers": ["power_setpoint_mw"],
+ "focusing_stages": ["position_mm"],
+ "start_delay_time": {
+ "delegate": "spin",
+ "type": "float",
+ "minimum": 0,
+ "initial_value": 15,
},
- 'repeats': {
- 'delegate': 'spin',
- 'type': 'int',
- 'minimum': 0,
+ "repeats": {
+ "delegate": "spin",
+ "type": "int",
+ "minimum": 0,
+ },
+ "example": {
+ "delegate": "combo",
+ "type": "str",
+ "items": ["this", "is", "an", "example"],
+ "initial_value": "example",
},
- 'example': {
- 'delegate': 'combo',
- 'type': 'str',
- 'items': ['this', 'is', 'an', 'example'],
- 'initial_value': 'example'
- }
}
lasers = {
- '488nm': SimulatedLaser(id='hello', wavelength=488),
- '639nm': SimulatedLaser(id='there', wavelength=639)
+ "488nm": SimulatedLaser(id="hello", wavelength=488),
+ "639nm": SimulatedLaser(id="there", wavelength=639),
}
tiling_stages = {
- 'x': Stage(hardware_axis='x', instrument_axis='x'),
- 'y': Stage(hardware_axis='y', instrument_axis='y')
+ "x": Stage(hardware_axis="x", instrument_axis="x"),
+ "y": Stage(hardware_axis="y", instrument_axis="y"),
}
- scanning_stages = {
- 'z': Stage(hardware_axis='z', instrument_axis='z')
- }
+ scanning_stages = {"z": Stage(hardware_axis="z", instrument_axis="z")}
- focusing_stages = {
- 'n': Stage(hardware_axis='n', instrument_axis='n')
- }
+ focusing_stages = {"n": Stage(hardware_axis="n", instrument_axis="n")}
focusing_stage_widgets = {
- 'n': StageWidget(focusing_stages['n']),
+ "n": StageWidget(focusing_stages["n"]),
}
- laser_widgets = {
- '488nm': LaserWidget(lasers['488nm']),
- '639nm': LaserWidget(lasers['639nm'])
- }
+ laser_widgets = {"488nm": LaserWidget(lasers["488nm"]), "639nm": LaserWidget(lasers["639nm"])}
gui_config = {
- 'acquisition_view': {
- 'coordinate_plane': ['x', 'y', 'z'],
- 'unit': 'mm',
- 'fov_dimensions': [1, 1, 0],
- 'acquisition_widgets': {
- 'channel_plan': {
- 'init': {
- 'properties': properties
- }
- }
- }
+ "acquisition_view": {
+ "coordinate_plane": ["x", "y", "z"],
+ "unit": "mm",
+ "fov_dimensions": [1, 1, 0],
+ "acquisition_widgets": {"channel_plan": {"init": {"properties": properties}}},
}
}
- instrument_config = {
- 'instrument': {
- 'channels': channels
- }
- }
+ instrument_config = {"instrument": {"channels": channels}}
- acquisition_config = {
- 'acquisition': {
- 'operations': {},
- 'tiles': []
- }
- }
+ acquisition_config = {"acquisition": {"operations": {}, "tiles": []}}
mocked_instrument = MagicMock()
- mocked_instrument.configure_mock(config=instrument_config,
- lasers=lasers,
- tiling_stages=tiling_stages,
- scanning_stages=scanning_stages,
- focusing_stages=focusing_stages)
+ mocked_instrument.configure_mock(
+ config=instrument_config,
+ lasers=lasers,
+ tiling_stages=tiling_stages,
+ scanning_stages=scanning_stages,
+ focusing_stages=focusing_stages,
+ )
mocked_instrument_view = MagicMock()
- mocked_instrument_view.configure_mock(instrument=mocked_instrument,
- laser_widgets=laser_widgets,
- focusing_stage_widgets=focusing_stage_widgets,
- config=gui_config)
+ mocked_instrument_view.configure_mock(
+ instrument=mocked_instrument,
+ laser_widgets=laser_widgets,
+ focusing_stage_widgets=focusing_stage_widgets,
+ config=gui_config,
+ )
mocked_acquisition = MagicMock()
- mocked_acquisition.configure_mock(instrument=mocked_instrument,
- config=acquisition_config)
+ mocked_acquisition.configure_mock(instrument=mocked_instrument, config=acquisition_config)
view = AcquisitionView(mocked_acquisition, mocked_instrument_view)
@@ -142,256 +111,202 @@ def test_update_tiles(self):
self.assertEqual(len(valueChanged_spy), 1) # triggered once
self.assertTrue(valueChanged_spy.isValid())
- view.channel_plan.add_channel('488')
+ view.channel_plan.add_channel("488")
# check channel added is emitted once
self.assertEqual(len(channelsAdded_spy), 1) # triggered once
self.assertTrue(channelsAdded_spy.isValid())
- expected_tiles = [{
- 'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': 0.5,
- 'z': 0.0
- },
- 'tile_number': 0,
- '488nm': {
- 'power_setpoint_mw': 10.0
+ expected_tiles = [
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": 0.5, "z": 0.0},
+ "tile_number": 0,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
},
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''},
{
- 'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': -0.5,
- 'z': 0.0
- },
- 'tile_number': 1,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''}]
-
- actual_tiles = mocked_acquisition.config['acquisition']['tiles']
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": -0.5, "z": 0.0},
+ "tile_number": 1,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
+ },
+ ]
+
+ actual_tiles = mocked_acquisition.config["acquisition"]["tiles"]
self.assertEqual(expected_tiles, actual_tiles)
- table = getattr(view.channel_plan, '488_table')
- table.item(0, 2).setData(Qt.EditRole, 'tile_prefix')
+ table = getattr(view.channel_plan, "488_table")
+ table.item(0, 2).setData(Qt.EditRole, "tile_prefix")
# check channel changed is emitted once
self.assertEqual(len(channelChanged_spy), 1) # triggered once
self.assertTrue(channelChanged_spy.isValid())
- expected_tiles = [{
- 'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': 0.5,
- 'z': 0.0
- },
- 'tile_number': 0,
- '488nm': {
- 'power_setpoint_mw': 10.0
+ expected_tiles = [
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": 0.5, "z": 0.0},
+ "tile_number": 0,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "tile_prefix",
},
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': 'tile_prefix'},
{
- 'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': -0.5,
- 'z': 0.0
- },
- 'tile_number': 1,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': 'tile_prefix'}]
- actual_tiles = mocked_acquisition.config['acquisition']['tiles']
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": -0.5, "z": 0.0},
+ "tile_number": 1,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "tile_prefix",
+ },
+ ]
+ actual_tiles = mocked_acquisition.config["acquisition"]["tiles"]
self.assertEqual(expected_tiles, actual_tiles)
def test_subset_write_tiles(self):
- """test writing a subset of tiles"""
-
+ """_summary_"""
channels = {
- '488': {
- 'filters': ['BP488'],
- 'lasers': ['488nm'],
- 'cameras': ['vnp - 604mx', 'vp-151mx']},
- '639': {
- 'filters': ['LP638'],
- 'lasers': ['639nm'],
- 'cameras': ['vnp - 604mx', 'vp-151mx']}
+ "488": {"filters": ["BP488"], "lasers": ["488nm"], "cameras": ["vnp - 604mx", "vp-151mx"]},
+ "639": {"filters": ["LP638"], "lasers": ["639nm"], "cameras": ["vnp - 604mx", "vp-151mx"]},
}
properties = {
- 'lasers': ['power_setpoint_mw'],
- 'focusing_stages': ['position_mm'],
- 'start_delay_time': {
- 'delegate': 'spin',
- 'type': 'float',
- 'minimum': 0,
- 'initial_value': 15,
+ "lasers": ["power_setpoint_mw"],
+ "focusing_stages": ["position_mm"],
+ "start_delay_time": {
+ "delegate": "spin",
+ "type": "float",
+ "minimum": 0,
+ "initial_value": 15,
},
- 'repeats': {
- 'delegate': 'spin',
- 'type': 'int',
- 'minimum': 0,
+ "repeats": {
+ "delegate": "spin",
+ "type": "int",
+ "minimum": 0,
+ },
+ "example": {
+ "delegate": "combo",
+ "type": "str",
+ "items": ["this", "is", "an", "example"],
+ "initial_value": "example",
},
- 'example': {
- 'delegate': 'combo',
- 'type': 'str',
- 'items': ['this', 'is', 'an', 'example'],
- 'initial_value': 'example'
- }
}
lasers = {
- '488nm': SimulatedLaser(id='hello', wavelength=488),
- '639nm': SimulatedLaser(id='there', wavelength=639)
+ "488nm": SimulatedLaser(id="hello", wavelength=488),
+ "639nm": SimulatedLaser(id="there", wavelength=639),
}
tiling_stages = {
- 'x': Stage(hardware_axis='x', instrument_axis='x'),
- 'y': Stage(hardware_axis='y', instrument_axis='y')
+ "x": Stage(hardware_axis="x", instrument_axis="x"),
+ "y": Stage(hardware_axis="y", instrument_axis="y"),
}
- scanning_stages = {
- 'z': Stage(hardware_axis='z', instrument_axis='z')
- }
+ scanning_stages = {"z": Stage(hardware_axis="z", instrument_axis="z")}
- focusing_stages = {
- 'n': Stage(hardware_axis='n', instrument_axis='n')
- }
+ focusing_stages = {"n": Stage(hardware_axis="n", instrument_axis="n")}
focusing_stage_widgets = {
- 'n': StageWidget(focusing_stages['n']),
+ "n": StageWidget(focusing_stages["n"]),
}
- laser_widgets = {
- '488nm': LaserWidget(lasers['488nm']),
- '639nm': LaserWidget(lasers['639nm'])
- }
+ laser_widgets = {"488nm": LaserWidget(lasers["488nm"]), "639nm": LaserWidget(lasers["639nm"])}
gui_config = {
- 'acquisition_view': {
- 'coordinate_plane': ['x', 'y', 'z'],
- 'unit': 'mm',
- 'fov_dimensions': [1, 1, 0],
- 'acquisition_widgets': {
- 'channel_plan': {
- 'init': {
- 'properties': properties
- }
- }
- }
+ "acquisition_view": {
+ "coordinate_plane": ["x", "y", "z"],
+ "unit": "mm",
+ "fov_dimensions": [1, 1, 0],
+ "acquisition_widgets": {"channel_plan": {"init": {"properties": properties}}},
}
}
- instrument_config = {
- 'instrument': {
- 'channels': channels
- }
- }
+ instrument_config = {"instrument": {"channels": channels}}
- acquisition_config = {
- 'acquisition': {
- 'operations': {},
- 'tiles': []
- }
- }
+ acquisition_config = {"acquisition": {"operations": {}, "tiles": []}}
mocked_instrument = MagicMock()
- mocked_instrument.configure_mock(config=instrument_config,
- lasers=lasers,
- tiling_stages=tiling_stages,
- scanning_stages=scanning_stages,
- focusing_stages=focusing_stages)
+ mocked_instrument.configure_mock(
+ config=instrument_config,
+ lasers=lasers,
+ tiling_stages=tiling_stages,
+ scanning_stages=scanning_stages,
+ focusing_stages=focusing_stages,
+ )
mocked_instrument_view = MagicMock()
- mocked_instrument_view.configure_mock(instrument=mocked_instrument,
- laser_widgets=laser_widgets,
- focusing_stage_widgets=focusing_stage_widgets,
- config=gui_config)
+ mocked_instrument_view.configure_mock(
+ instrument=mocked_instrument,
+ laser_widgets=laser_widgets,
+ focusing_stage_widgets=focusing_stage_widgets,
+ config=gui_config,
+ )
mocked_acquisition = MagicMock()
- mocked_acquisition.configure_mock(instrument=mocked_instrument,
- config=acquisition_config)
+ mocked_acquisition.configure_mock(instrument=mocked_instrument, config=acquisition_config)
view = AcquisitionView(mocked_acquisition, mocked_instrument_view)
view.volume_plan.rows.setValue(4)
- view.channel_plan.add_channel('488')
+ view.channel_plan.add_channel("488")
view.volume_plan.start = 1
-
expected_tiles = [
- {'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': 0.5,
- 'z': 0.0
- },
- 'tile_number': 1,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''},
- {'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': -0.5,
- 'z': 0.0
- },
- 'tile_number': 2,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''},
- {'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': -1.5,
- 'z': 0.0
- },
- 'tile_number': 3,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''}]
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": 0.5, "z": 0.0},
+ "tile_number": 1,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
+ },
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": -0.5, "z": 0.0},
+ "tile_number": 2,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
+ },
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": -1.5, "z": 0.0},
+ "tile_number": 3,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
+ },
+ ]
actual_tiles = view.create_tile_list()
self.assertEqual(expected_tiles, actual_tiles)
@@ -399,46 +314,35 @@ def test_subset_write_tiles(self):
view.volume_plan.stop = 3
expected_tiles = [
- {'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': 0.5,
- 'z': 0.0
- },
- 'tile_number': 1,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''},
- {'channel': '488',
- 'position_mm': {
- 'x': 0.0,
- 'y': -0.5,
- 'z': 0.0
- },
- 'tile_number': 2,
- '488nm': {
- 'power_setpoint_mw': 10.0
- },
- 'start_delay_time': 15.0,
- 'repeats': 0,
- 'example': 'example',
- 'steps': 0,
- 'step_size': 0.0,
- 'prefix': ''},
- ]
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": 0.5, "z": 0.0},
+ "tile_number": 1,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
+ },
+ {
+ "channel": "488",
+ "position_mm": {"x": 0.0, "y": -0.5, "z": 0.0},
+ "tile_number": 2,
+ "488nm": {"power_setpoint_mw": 10.0},
+ "start_delay_time": 15.0,
+ "repeats": 0,
+ "example": "example",
+ "steps": 0,
+ "step_size": 0.0,
+ "prefix": "",
+ },
+ ]
actual_tiles = view.create_tile_list()
self.assertEqual(expected_tiles, actual_tiles)
-
-
-
if __name__ == "__main__":
unittest.main()
sys.exit(app.exec_())
diff --git a/tests/test_base_device_widget.py b/tests/test_base_device_widget.py
index e7f1d5f..89c4598 100644
--- a/tests/test_base_device_widget.py
+++ b/tests/test_base_device_widget.py
@@ -1,58 +1,56 @@
-""" testing BaseDeviceWidget """
-
+import sys
import unittest
-from view.widgets.base_device_widget import BaseDeviceWidget
-from qtpy.QtTest import QTest, QSignalSpy
-from qtpy.QtWidgets import QApplication, QWidget
+
from qtpy.QtCore import Qt
-import sys
+from qtpy.QtGui import QDoubleValidator, QIntValidator
+from qtpy.QtTest import QSignalSpy, QTest
+from qtpy.QtWidgets import QApplication
+
+from view.widgets.base_device_widget import BaseDeviceWidget
from view.widgets.miscellaneous_widgets.q_scrollable_line_edit import QScrollableLineEdit
-from qtpy.QtGui import QIntValidator, QDoubleValidator
app = QApplication(sys.argv)
class BaseDeviceWidgetTests(unittest.TestCase):
- """tests for BaseDeviceWidget"""
+ """_summary_"""
def test_string_properties(self):
- """Test that BaseDeviceWidget can correctly handle properties that are strings"""
-
- properties = {'test_string': 'hello'}
+ """_summary_"""
+ properties = {"test_string": "hello"}
widget = BaseDeviceWidget(properties, properties)
- self.assertTrue(hasattr(widget, 'test_string'))
- self.assertTrue(widget.test_string == 'hello')
- self.assertTrue(hasattr(widget, 'test_string_widget'))
+ self.assertTrue(hasattr(widget, "test_string"))
+ self.assertTrue(widget.test_string == "hello")
+ self.assertTrue(hasattr(widget, "test_string_widget"))
self.assertTrue(type(widget.test_string_widget) == QScrollableLineEdit)
- self.assertTrue(widget.test_string_widget.text() == 'hello')
+ self.assertTrue(widget.test_string_widget.text() == "hello")
# change value externally
outside_signal_spy = QSignalSpy(widget.ValueChangedOutside)
- widget.test_string = 'howdy'
+ widget.test_string = "howdy"
self.assertEqual(len(outside_signal_spy), 1) # triggered once
self.assertTrue(outside_signal_spy.isValid())
- self.assertTrue(widget.test_string == 'howdy')
- self.assertTrue(widget.test_string_widget.text() == 'howdy')
+ self.assertTrue(widget.test_string == "howdy")
+ self.assertTrue(widget.test_string_widget.text() == "howdy")
# change value internally
inside_signal_spy = QSignalSpy(widget.ValueChangedInside)
- widget.test_string_widget.setText('hello')
+ widget.test_string_widget.setText("hello")
QTest.keyPress(widget.test_string_widget, Qt.Key_Enter) # press enter
self.assertEqual(len(inside_signal_spy), 1) # triggered once
self.assertTrue(inside_signal_spy.isValid())
- self.assertTrue(widget.test_string == 'hello')
- self.assertTrue(widget.test_string_widget.text() == 'hello')
+ self.assertTrue(widget.test_string == "hello")
+ self.assertTrue(widget.test_string_widget.text() == "hello")
def test_int_properties(self):
- """Test that BaseDeviceWidget can correctly handle properties that are as ints"""
-
- properties = {'test_int': 1}
+ """_summary_"""
+ properties = {"test_int": 1}
widget = BaseDeviceWidget(properties, properties)
- self.assertTrue(hasattr(widget, 'test_int'))
+ self.assertTrue(hasattr(widget, "test_int"))
self.assertTrue(widget.test_int == 1)
- self.assertTrue(hasattr(widget, 'test_int_widget'))
+ self.assertTrue(hasattr(widget, "test_int_widget"))
self.assertTrue(type(widget.test_int_widget) == QScrollableLineEdit)
self.assertTrue(type(widget.test_int_widget.validator()) == QIntValidator)
self.assertTrue(widget.test_int_widget.value() == 1)
@@ -75,14 +73,13 @@ def test_int_properties(self):
self.assertTrue(widget.test_int_widget.value() == 1)
def test_float_properties(self):
- """Test that BaseDeviceWidget can correctly handle properties that are floats"""
-
- properties = {'test_float': 1.5}
+ """_summary_"""
+ properties = {"test_float": 1.5}
widget = BaseDeviceWidget(properties, properties)
- self.assertTrue(hasattr(widget, 'test_float'))
+ self.assertTrue(hasattr(widget, "test_float"))
self.assertTrue(widget.test_float == 1.5)
- self.assertTrue(hasattr(widget, 'test_float_widget'))
+ self.assertTrue(hasattr(widget, "test_float_widget"))
self.assertTrue(type(widget.test_float_widget) == QScrollableLineEdit)
self.assertTrue(type(widget.test_float_widget.validator()) == QDoubleValidator)
self.assertTrue(widget.test_float_widget.value() == 1.5)
@@ -105,88 +102,88 @@ def test_float_properties(self):
self.assertTrue(widget.test_float_widget.value() == 1.5)
def test_list_properties(self):
- """Test that BaseDeviceWidget can correctly handle properties that are list"""
-
- properties = {'test_list': ['hello', 'world']}
+ """_summary_"""
+ properties = {"test_list": ["hello", "world"]}
widget = BaseDeviceWidget(properties, properties)
- self.assertTrue(hasattr(widget, 'test_list'))
- self.assertTrue(widget.test_list == ['hello', 'world'])
+ self.assertTrue(hasattr(widget, "test_list"))
+ self.assertTrue(widget.test_list == ["hello", "world"])
- self.assertTrue(hasattr(widget, 'test_list.0'))
- self.assertTrue(getattr(widget, 'test_list.0') == 'hello')
- self.assertTrue(hasattr(widget, 'test_list.0_widget'))
- self.assertTrue(type(getattr(widget, 'test_list.0_widget')) == QScrollableLineEdit)
- self.assertTrue(getattr(widget, 'test_list.0_widget').text() == 'hello')
+ self.assertTrue(hasattr(widget, "test_list.0"))
+ self.assertTrue(getattr(widget, "test_list.0") == "hello")
+ self.assertTrue(hasattr(widget, "test_list.0_widget"))
+ self.assertTrue(type(getattr(widget, "test_list.0_widget")) == QScrollableLineEdit)
+ self.assertTrue(getattr(widget, "test_list.0_widget").text() == "hello")
- self.assertTrue(hasattr(widget, 'test_list.1'))
- self.assertTrue(getattr(widget, 'test_list.1') == 'world')
- self.assertTrue(hasattr(widget, 'test_list.1_widget'))
- self.assertTrue(type(getattr(widget, 'test_list.1_widget')) == QScrollableLineEdit)
- self.assertTrue(getattr(widget, 'test_list.1_widget').text() == 'world')
+ self.assertTrue(hasattr(widget, "test_list.1"))
+ self.assertTrue(getattr(widget, "test_list.1") == "world")
+ self.assertTrue(hasattr(widget, "test_list.1_widget"))
+ self.assertTrue(type(getattr(widget, "test_list.1_widget")) == QScrollableLineEdit)
+ self.assertTrue(getattr(widget, "test_list.1_widget").text() == "world")
# change value internally
- getattr(widget, 'test_list.0_widget').setText('howdy')
- QTest.keyPress(getattr(widget, 'test_list.0_widget'), Qt.Key_Enter) # press enter
- self.assertTrue(widget.test_list == ['howdy', 'world'])
- self.assertTrue(getattr(widget, 'test_list.0') == 'howdy')
+ getattr(widget, "test_list.0_widget").setText("howdy")
+ QTest.keyPress(getattr(widget, "test_list.0_widget"), Qt.Key_Enter) # press enter
+ self.assertTrue(widget.test_list == ["howdy", "world"])
+ self.assertTrue(getattr(widget, "test_list.0") == "howdy")
def test_dict_properties(self):
- """Test that BaseDeviceWidget can correctly handle properties that are dictionaries"""
-
- properties = {'test_dict': {'greeting': 'hello', 'directed_to': 'world'}}
+ """_summary_"""
+ properties = {"test_dict": {"greeting": "hello", "directed_to": "world"}}
widget = BaseDeviceWidget(properties, properties)
- self.assertTrue(hasattr(widget, 'test_dict'))
- self.assertTrue(widget.test_dict == {'greeting': 'hello', 'directed_to': 'world'})
+ self.assertTrue(hasattr(widget, "test_dict"))
+ self.assertTrue(widget.test_dict == {"greeting": "hello", "directed_to": "world"})
- self.assertTrue(hasattr(widget, 'test_dict.greeting'))
- self.assertTrue(getattr(widget, 'test_dict.greeting') == 'hello')
- self.assertTrue(hasattr(widget, 'test_dict.greeting_widget'))
- self.assertTrue(type(getattr(widget, 'test_dict.greeting_widget')) == QScrollableLineEdit)
- self.assertTrue(getattr(widget, 'test_dict.greeting_widget').text() == 'hello')
+ self.assertTrue(hasattr(widget, "test_dict.greeting"))
+ self.assertTrue(getattr(widget, "test_dict.greeting") == "hello")
+ self.assertTrue(hasattr(widget, "test_dict.greeting_widget"))
+ self.assertTrue(type(getattr(widget, "test_dict.greeting_widget")) == QScrollableLineEdit)
+ self.assertTrue(getattr(widget, "test_dict.greeting_widget").text() == "hello")
- self.assertTrue(hasattr(widget, 'test_dict.directed_to'))
- self.assertTrue(getattr(widget, 'test_dict.directed_to') == 'world')
- self.assertTrue(hasattr(widget, 'test_dict.directed_to_widget'))
- self.assertTrue(type(getattr(widget, 'test_dict.directed_to_widget')) == QScrollableLineEdit)
- self.assertTrue(getattr(widget, 'test_dict.directed_to_widget').text() == 'world')
+ self.assertTrue(hasattr(widget, "test_dict.directed_to"))
+ self.assertTrue(getattr(widget, "test_dict.directed_to") == "world")
+ self.assertTrue(hasattr(widget, "test_dict.directed_to_widget"))
+ self.assertTrue(type(getattr(widget, "test_dict.directed_to_widget")) == QScrollableLineEdit)
+ self.assertTrue(getattr(widget, "test_dict.directed_to_widget").text() == "world")
# change value internally
- getattr(widget, 'test_dict.greeting_widget').setText('howdy')
- QTest.keyPress(getattr(widget, 'test_dict.greeting_widget'), Qt.Key_Enter) # press enter
- self.assertTrue(widget.test_dict == {'greeting': 'howdy', 'directed_to': 'world'})
- self.assertTrue(getattr(widget, 'test_dict.greeting') == 'howdy')
+ getattr(widget, "test_dict.greeting_widget").setText("howdy")
+ QTest.keyPress(getattr(widget, "test_dict.greeting_widget"), Qt.Key_Enter) # press enter
+ self.assertTrue(widget.test_dict == {"greeting": "howdy", "directed_to": "world"})
+ self.assertTrue(getattr(widget, "test_dict.greeting") == "howdy")
def test_nested_properties(self):
- """Test that BaseDeviceWidget can correctly handle properties that are nested dictionaries"""
-
- properties = {'test_nest_dict': {'greeting_options': {'formal': 'hello', 'cowboy': 'howdy'},
- 'directed_to': 'world'}}
+ """_summary_"""
+ properties = {
+ "test_nest_dict": {"greeting_options": {"formal": "hello", "cowboy": "howdy"}, "directed_to": "world"}
+ }
widget = BaseDeviceWidget(properties, properties)
- self.assertTrue(hasattr(widget, 'test_nest_dict.greeting_options'))
- self.assertTrue(getattr(widget, 'test_nest_dict.greeting_options') == {'formal': 'hello', 'cowboy': 'howdy'})
- self.assertTrue(hasattr(widget, 'test_nest_dict.greeting_options'))
+ self.assertTrue(hasattr(widget, "test_nest_dict.greeting_options"))
+ self.assertTrue(getattr(widget, "test_nest_dict.greeting_options") == {"formal": "hello", "cowboy": "howdy"})
+ self.assertTrue(hasattr(widget, "test_nest_dict.greeting_options"))
- self.assertTrue(hasattr(widget, 'test_nest_dict.greeting_options.formal'))
- self.assertTrue(getattr(widget, 'test_nest_dict.greeting_options.formal') == 'hello')
- self.assertTrue(hasattr(widget, 'test_nest_dict.greeting_options.formal_widget'))
- self.assertTrue(type(getattr(widget, 'test_nest_dict.greeting_options.formal_widget')) == QScrollableLineEdit)
- self.assertTrue(getattr(widget, 'test_nest_dict.greeting_options.formal_widget').text() == 'hello')
+ self.assertTrue(hasattr(widget, "test_nest_dict.greeting_options.formal"))
+ self.assertTrue(getattr(widget, "test_nest_dict.greeting_options.formal") == "hello")
+ self.assertTrue(hasattr(widget, "test_nest_dict.greeting_options.formal_widget"))
+ self.assertTrue(type(getattr(widget, "test_nest_dict.greeting_options.formal_widget")) == QScrollableLineEdit)
+ self.assertTrue(getattr(widget, "test_nest_dict.greeting_options.formal_widget").text() == "hello")
- self.assertTrue(hasattr(widget, 'test_nest_dict.greeting_options.cowboy'))
- self.assertTrue(getattr(widget, 'test_nest_dict.greeting_options.cowboy') == 'howdy')
- self.assertTrue(hasattr(widget, 'test_nest_dict.greeting_options.cowboy_widget'))
- self.assertTrue(type(getattr(widget, 'test_nest_dict.greeting_options.cowboy_widget')) == QScrollableLineEdit)
- self.assertTrue(getattr(widget, 'test_nest_dict.greeting_options.cowboy_widget').text() == 'howdy')
+ self.assertTrue(hasattr(widget, "test_nest_dict.greeting_options.cowboy"))
+ self.assertTrue(getattr(widget, "test_nest_dict.greeting_options.cowboy") == "howdy")
+ self.assertTrue(hasattr(widget, "test_nest_dict.greeting_options.cowboy_widget"))
+ self.assertTrue(type(getattr(widget, "test_nest_dict.greeting_options.cowboy_widget")) == QScrollableLineEdit)
+ self.assertTrue(getattr(widget, "test_nest_dict.greeting_options.cowboy_widget").text() == "howdy")
# change value internally
- getattr(widget, 'test_nest_dict.greeting_options.formal_widget').setText('salutations')
- QTest.keyPress(getattr(widget, 'test_nest_dict.greeting_options.formal_widget'), Qt.Key_Enter) # press enter
- self.assertTrue(widget.test_nest_dict == {'greeting_options': {'formal': 'salutations', 'cowboy': 'howdy'},
- 'directed_to': 'world'})
- self.assertTrue(getattr(widget, 'test_nest_dict.greeting_options.formal') == 'salutations')
+ getattr(widget, "test_nest_dict.greeting_options.formal_widget").setText("salutations")
+ QTest.keyPress(getattr(widget, "test_nest_dict.greeting_options.formal_widget"), Qt.Key_Enter) # press enter
+ self.assertTrue(
+ widget.test_nest_dict
+ == {"greeting_options": {"formal": "salutations", "cowboy": "howdy"}, "directed_to": "world"}
+ )
+ self.assertTrue(getattr(widget, "test_nest_dict.greeting_options.formal") == "salutations")
if __name__ == "__main__":
diff --git a/tests/test_camera_widget.py b/tests/test_camera_widget.py
index 76b4195..e7e335e 100644
--- a/tests/test_camera_widget.py
+++ b/tests/test_camera_widget.py
@@ -1,28 +1,22 @@
-""" testing CameraWidget """
-
-from types import SimpleNamespace
import unittest
from view.widgets.device_widgets.camera_widget import CameraWidget
-from qtpy.QtTest import QTest, QSignalSpy
from qtpy.QtWidgets import QApplication, QWidget
-from qtpy.QtCore import Qt
import sys
import numpy as np
-from unittest.mock import MagicMock, PropertyMock
+from unittest.mock import MagicMock
app = QApplication(sys.argv)
class CameraWidgetTests(unittest.TestCase):
- """Tests for CameraWidget"""
+ """_summary_"""
def test_format(self):
- """Test format of camera widget is correct"""
-
+ """_summary_"""
mock_camera = MagicMock()
mock_camera.configure_mock(
_binning=1,
- _pixel_type='mono8',
+ _pixel_type="mono8",
_exposure_time_ms=20.0,
_sensor_width_px=100,
_sensor_height_px=100,
@@ -31,64 +25,78 @@ def test_format(self):
_height_px=100,
_height_offset_px=0,
roi_widget=QWidget(),
- _latest_frame=np.zeros((100, 100)))
-
- type(mock_camera).binning = property(fget=lambda inst: getattr(inst, '_binning'),
- fset=lambda inst, val: setattr(inst, '_binning', val))
- type(mock_camera).frame_time_ms = property(fget=lambda inst: inst.height_px * inst.line_interval_us / 1000 +
- inst.exposure_time_ms)
+ _latest_frame=np.zeros((100, 100)),
+ )
+
+ type(mock_camera).binning = property(
+ fget=lambda inst: getattr(inst, "_binning"), fset=lambda inst, val: setattr(inst, "_binning", val)
+ )
+ type(mock_camera).frame_time_ms = property(
+ fget=lambda inst: inst.height_px * inst.line_interval_us / 1000 + inst.exposure_time_ms
+ )
type(mock_camera).line_interval_us = property(fget=lambda inst: 20.0)
- type(mock_camera).binning = property(fget=lambda inst: getattr(inst, '_binning'),
- fset=lambda inst, val: setattr(inst, '_binning', val))
- type(mock_camera).pixel_type = property(fget=lambda inst: getattr(inst, '_pixel_type'),
- fset=lambda inst, val: setattr(inst, '_pixel_type', val))
- type(mock_camera).exposure_time_ms = property(fget=lambda inst: getattr(inst, '_exposure_time_ms'),
- fset=lambda inst, v: setattr(inst, '_exposure_time_ms', v))
- type(mock_camera).sensor_width_px = property(fget=lambda inst: getattr(inst, '_sensor_width_px'),
- fset=lambda inst, v: setattr(inst, '_sensor_width_px', v))
- type(mock_camera).sensor_height_px = property(fget=lambda inst: getattr(inst, '_sensor_height_px'),
- fset=lambda inst, v: setattr(inst, '_sensor_height_px',
- v))
- type(mock_camera).width_px = property(fget=lambda inst: getattr(inst, '_width_px'),
- fset=lambda inst, v: setattr(inst, '_width_px', v))
- type(mock_camera).width_offset_px = property(fget=lambda inst: getattr(inst, '_width_offset_px'),
- fset=lambda inst, v: setattr(inst, '_width_offset_px', v))
- type(mock_camera).height_px = property(fget=lambda inst: getattr(inst, '_height_px'),
- fset=lambda inst, v: setattr(inst, '_height_px', v))
- type(mock_camera).height_offset_px = property(fget=lambda inst: getattr(inst, '_height_offset_px'),
- fset=lambda inst, v: setattr(inst, '_height_offset_px', v))
- type(mock_camera).latest_frame = property(fget=lambda inst: getattr(inst, '_latest_frame'))
-
+ type(mock_camera).binning = property(
+ fget=lambda inst: getattr(inst, "_binning"), fset=lambda inst, val: setattr(inst, "_binning", val)
+ )
+ type(mock_camera).pixel_type = property(
+ fget=lambda inst: getattr(inst, "_pixel_type"), fset=lambda inst, val: setattr(inst, "_pixel_type", val)
+ )
+ type(mock_camera).exposure_time_ms = property(
+ fget=lambda inst: getattr(inst, "_exposure_time_ms"),
+ fset=lambda inst, v: setattr(inst, "_exposure_time_ms", v),
+ )
+ type(mock_camera).sensor_width_px = property(
+ fget=lambda inst: getattr(inst, "_sensor_width_px"),
+ fset=lambda inst, v: setattr(inst, "_sensor_width_px", v),
+ )
+ type(mock_camera).sensor_height_px = property(
+ fget=lambda inst: getattr(inst, "_sensor_height_px"),
+ fset=lambda inst, v: setattr(inst, "_sensor_height_px", v),
+ )
+ type(mock_camera).width_px = property(
+ fget=lambda inst: getattr(inst, "_width_px"), fset=lambda inst, v: setattr(inst, "_width_px", v)
+ )
+ type(mock_camera).width_offset_px = property(
+ fget=lambda inst: getattr(inst, "_width_offset_px"),
+ fset=lambda inst, v: setattr(inst, "_width_offset_px", v),
+ )
+ type(mock_camera).height_px = property(
+ fget=lambda inst: getattr(inst, "_height_px"), fset=lambda inst, v: setattr(inst, "_height_px", v)
+ )
+ type(mock_camera).height_offset_px = property(
+ fget=lambda inst: getattr(inst, "_height_offset_px"),
+ fset=lambda inst, v: setattr(inst, "_height_offset_px", v),
+ )
+ type(mock_camera).latest_frame = property(fget=lambda inst: getattr(inst, "_latest_frame"))
widget = CameraWidget(mock_camera)
children = widget.centralWidget().children()
- groups = {'picture_buttons': children[1],
- 'pixel_widgets': children[2],
- 'timing_widgets': children[3],
- 'sensor_size_widgets': children[5],
- }
+ groups = {
+ "picture_buttons": children[1],
+ "pixel_widgets": children[2],
+ "timing_widgets": children[3],
+ "sensor_size_widgets": children[5],
+ }
# check that picture buttons are correctly placed
- self.assertEqual(groups['picture_buttons'].layout().itemAt(0).widget(), widget.live_button)
- self.assertEqual(groups['picture_buttons'].layout().itemAt(1).widget(), widget.snapshot_button)
+ self.assertEqual(groups["picture_buttons"].layout().itemAt(0).widget(), widget.live_button)
+ self.assertEqual(groups["picture_buttons"].layout().itemAt(1).widget(), widget.snapshot_button)
# check that pixel widgets are placed correctly
-
-
- print(groups['pixel_widgets'].children()[1].layout().itemAt(1), widget.property_widgets['binning'])
- self.assertEqual(groups['pixel_widgets'].layout().itemAt(0).widget(), widget.binning_widget)
- self.assertEqual(groups['pixel_widgets'].layout().itemAt(1).widget(), widget.pixel_type_widget)
+ self.assertEqual(groups["pixel_widgets"].layout().itemAt(0).widget(), widget.binning_widget)
+ self.assertEqual(groups["pixel_widgets"].layout().itemAt(1).widget(), widget.pixel_type_widget)
# check that timing widgets are placed correctly
- self.assertEqual(groups['timing_widgets'].layout().itemAt(0).widget(), widget.exposure_time_ms_widget)
- self.assertEqual(groups['timing_widgets'].layout().itemAt(1).widget(), widget.frame_time_ms_widget)
- self.assertEqual(groups['timing_widgets'].layout().itemAt(2).widget(), widget.line_interval_us)
+ self.assertEqual(groups["timing_widgets"].layout().itemAt(0).widget(), widget.exposure_time_ms_widget)
+ self.assertEqual(groups["timing_widgets"].layout().itemAt(1).widget(), widget.frame_time_ms_widget)
+ self.assertEqual(groups["timing_widgets"].layout().itemAt(2).widget(), widget.line_interval_us)
# check that sensor size widgets are placed correctly
- self.assertEqual(groups['sensor_size_widgets'].layout().itemAt(0).widget(), widget.width_px_widget)
- self.assertEqual(groups['sensor_size_widgets'].layout().itemAt(1).widget(), widget.width_offset_px)
- self.assertEqual(groups['sensor_size_widgets'].layout().itemAt(2).widget(), widget.height_px)
- self.assertEqual(groups['sensor_size_widgets'].layout().itemAt(3).widget(), widget.height_offset_px)
+ self.assertEqual(groups["sensor_size_widgets"].layout().itemAt(0).widget(), widget.width_px_widget)
+ self.assertEqual(groups["sensor_size_widgets"].layout().itemAt(1).widget(), widget.width_offset_px)
+ self.assertEqual(groups["sensor_size_widgets"].layout().itemAt(2).widget(), widget.height_px)
+ self.assertEqual(groups["sensor_size_widgets"].layout().itemAt(3).widget(), widget.height_offset_px)
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_volume_plan_widget.py b/tests/test_volume_plan_widget.py
index be72151..157dfbb 100644
--- a/tests/test_volume_plan_widget.py
+++ b/tests/test_volume_plan_widget.py
@@ -1,9 +1,7 @@
-""" testing VolumePlanWidget """
-
import unittest
from view.widgets.acquisition_widgets.volume_plan_widget import VolumePlanWidget
from qtpy.QtTest import QTest, QSignalSpy
-from qtpy.QtWidgets import QApplication, QWidget
+from qtpy.QtWidgets import QApplication
from qtpy.QtCore import Qt
import sys
import numpy as np
@@ -12,13 +10,10 @@
class VolumePlanWidgetTests(unittest.TestCase):
- """Tests for VolumePlanWidget"""
-
- # TODO: Test overlap, relative_to, order, apply_all
+ """_summary_"""
def test_toggle_mode(self):
- """Test functionality of number mode"""
-
+ """_summary_"""
plan = VolumePlanWidget()
plan.show()
@@ -27,7 +22,7 @@ def test_toggle_mode(self):
# check clicking radio button works
QTest.mouseClick(plan.area_button, Qt.LeftButton)
self.assertTrue(plan.area_button.isChecked())
- self.assertEqual(plan.mode, 'area')
+ self.assertEqual(plan.mode, "area")
self.assertEqual(len(valueChanged_spy), 1) # triggered once
self.assertTrue(valueChanged_spy.isValid())
self.assertTrue(plan.area_widget.isEnabled())
@@ -35,7 +30,7 @@ def test_toggle_mode(self):
self.assertFalse(plan.bounds_widget.isEnabled())
QTest.mouseClick(plan.number_button, Qt.LeftButton)
- self.assertEqual(plan.mode, 'number')
+ self.assertEqual(plan.mode, "number")
self.assertEqual(len(valueChanged_spy), 2) # triggered twice
self.assertTrue(valueChanged_spy.isValid())
self.assertFalse(plan.area_widget.isEnabled())
@@ -43,7 +38,7 @@ def test_toggle_mode(self):
self.assertFalse(plan.bounds_widget.isEnabled())
QTest.mouseClick(plan.bounds_button, Qt.LeftButton)
- self.assertEqual(plan.mode, 'bounds')
+ self.assertEqual(plan.mode, "bounds")
self.assertEqual(len(valueChanged_spy), 3) # triggered thrice
self.assertTrue(valueChanged_spy.isValid())
self.assertFalse(plan.area_widget.isEnabled())
@@ -53,11 +48,10 @@ def test_toggle_mode(self):
plan.close()
def test_number_mode(self):
- """Test functionality of number mode"""
-
+ """_summary_"""
plan = VolumePlanWidget()
plan.show()
- plan.mode = 'number'
+ plan.mode = "number"
valueChanged_spy = QSignalSpy(plan.valueChanged)
@@ -101,11 +95,10 @@ def test_number_mode(self):
self.assertTrue(np.array_equal(expected_tile_pos, actual_tile_pos))
def test_area_mode(self):
- """Test functionality of area mode"""
-
+ """_summary_"""
plan = VolumePlanWidget()
plan.show()
- plan.mode = 'area'
+ plan.mode = "area"
valueChanged_spy = QSignalSpy(plan.valueChanged)
@@ -162,11 +155,10 @@ def test_area_mode(self):
self.assertTrue(plan.scan_ends.shape == (2, 2))
def test_bounds_mode(self):
- """Test functionality of bounds mode"""
-
+ """_summary_"""
plan = VolumePlanWidget()
plan.show()
- plan.mode = 'bounds'
+ plan.mode = "bounds"
valueChanged_spy = QSignalSpy(plan.valueChanged)
@@ -208,8 +200,7 @@ def test_bounds_mode(self):
self.assertTrue(np.array_equal(expected_tile_pos, actual_tile_pos))
def test_update_fov_position(self):
- """Test functionality of moving fov mode"""
-
+ """_summary_"""
plan = VolumePlanWidget()
plan.show()
valueChanged_spy = QSignalSpy(plan.valueChanged)
@@ -244,11 +235,8 @@ def test_update_fov_position(self):
self.assertEqual(plan.grid_offset_widgets[2].value(), 3)
self.assertEqual(plan.grid_offset, [1, 2, 3])
-
-
def test_grid_offset_widgets(self):
- """Test functionality of grid_offset_widgets"""
-
+ """_summary_"""
plan = VolumePlanWidget()
plan.show()
valueChanged_spy = QSignalSpy(plan.valueChanged)
@@ -285,7 +273,6 @@ def test_grid_offset_widgets(self):
self.assertTrue(np.array_equal(expected_tiles, actual_tiles))
-
if __name__ == "__main__":
unittest.main()
sys.exit(app.exec_())