Skip to content

Commit 2539d30

Browse files
csu-bot-zividtorbsorb
authored andcommitted
Samples: Fix resource mgtm and improve instructions
Hand-Eye GUI - Update how the step-by-step instructions are displayed to better indicate the completion status of each step. - Provide more feedback while connecting to a camera - Fix resource management by explicitly releasing Zivid resources when the application is closed.
1 parent b30cbb9 commit 2539d30

File tree

10 files changed

+123
-100
lines changed

10 files changed

+123
-100
lines changed

modules/zividsamples/gui/camera_selection.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,28 @@
55
66
"""
77

8-
from typing import List, Optional
8+
from typing import Optional
99

1010
import zivid
1111
from PyQt5.QtCore import QSize, QTimer
1212
from PyQt5.QtWidgets import QDialog, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout
1313
from zividsamples.gui.qt_application import ZividQtApplication
1414

15+
CAMERA_ROLE = 1
16+
1517

1618
class CameraSelectionDialog(QDialog):
1719

18-
def __init__(self, cameras):
20+
def __init__(self, zivid_app: zivid.Application, connect: bool):
1921
super().__init__()
20-
self.selected_camera = None
21-
self.init_ui(cameras)
22+
self.selected_camera: Optional[zivid.Camera] = None
23+
self.zivid_app = zivid_app
24+
self.connect = connect
25+
self.init_ui()
2226

23-
QTimer.singleShot(0, self.adjust_dialog_size)
27+
QTimer.singleShot(0, self.find_cameras)
2428

25-
def init_ui(self, cameras: List[zivid.Camera]):
29+
def init_ui(self):
2630
self.setWindowTitle("Select a Camera")
2731
layout = QVBoxLayout(self)
2832

@@ -31,20 +35,30 @@ def init_ui(self, cameras: List[zivid.Camera]):
3135

3236
self.select_button = QPushButton("Select", self)
3337
self.select_button.clicked.connect(self.on_select)
38+
self.select_button.setEnabled(False)
3439
layout.addWidget(self.select_button)
3540

3641
self.setLayout(layout)
3742

43+
def find_cameras(self):
44+
self.camera_list_widget.addItem("Finding cameras...")
45+
QTimer.singleShot(0, self.update_camera_list)
46+
47+
def update_camera_list(self):
48+
cameras = self.zivid_app.cameras()
49+
self.camera_list_widget.clear()
3850
camera_selected = False
3951
for camera in cameras:
4052
camera_item = QListWidgetItem(
4153
f"{camera.info.model_name} ({camera.info.serial_number} - {camera.state.status})",
4254
self.camera_list_widget,
4355
)
44-
camera_item.setData(1, camera)
56+
camera_item.setData(CAMERA_ROLE, camera)
4557
if camera.state.status == zivid.CameraState.Status.available and not camera_selected:
4658
camera_item.setSelected(True)
4759
camera_selected = True
60+
self.select_button.setEnabled(camera_selected)
61+
QTimer.singleShot(0, self.adjust_dialog_size)
4862

4963
def adjust_dialog_size(self):
5064
max_width = 0
@@ -58,22 +72,31 @@ def adjust_dialog_size(self):
5872
dialog_size = QSize(max_width, self.sizeHint().height())
5973
self.resize(dialog_size.expandedTo(QSize(300, 200)))
6074

75+
def connect_camera(self, camera: Optional[zivid.Camera]):
76+
if camera is not None:
77+
camera.connect()
78+
self.accept()
79+
6180
def on_select(self):
6281
selected_items = self.camera_list_widget.selectedItems()
6382
if selected_items:
64-
self.selected_camera = selected_items[0].data(1)
65-
self.accept()
83+
self.selected_camera = selected_items[0].data(CAMERA_ROLE)
84+
if self.connect and self.selected_camera:
85+
self.camera_list_widget.clear()
86+
self.camera_list_widget.addItem("Connecting...")
87+
QTimer.singleShot(100, lambda: self.connect_camera(self.selected_camera))
88+
else:
89+
self.accept()
6690

6791

68-
def select_camera(cameras: List[zivid.Camera]) -> Optional[zivid.Camera]:
69-
dialog = CameraSelectionDialog(cameras)
92+
def select_camera(zivid_app: zivid.Application, connect: bool) -> Optional[zivid.Camera]:
93+
dialog = CameraSelectionDialog(zivid_app, connect)
7094
if dialog.exec_() == QDialog.Accepted:
7195
return dialog.selected_camera
7296
return None
7397

7498

7599
if __name__ == "__main__": # NOLINT
76-
qtApp = ZividQtApplication()
77-
zividApp = zivid.Application()
78-
select_camera(zividApp.cameras())
79-
qtApp.exec_()
100+
with ZividQtApplication() as qtApp:
101+
selected_camera = select_camera(qtApp.zivid_app, connect=True)
102+
print(f"Selected camera: {selected_camera}")

modules/zividsamples/gui/hand_eye_configuration.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ def select_hand_eye_configuration(
166166

167167

168168
if __name__ == "__main__": # NOLINT
169-
170-
qtApp = ZividQtApplication()
171-
hand_eye_configuration = select_hand_eye_configuration()
172-
print(f"Selected settings: {hand_eye_configuration}")
169+
with ZividQtApplication():
170+
hand_eye_configuration = select_hand_eye_configuration()
171+
print(f"Selected settings: {hand_eye_configuration}")

modules/zividsamples/gui/hand_eye_settings_tester.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import zivid
1313
from nptyping import NDArray, Shape, UInt8
1414
from PyQt5.QtCore import Qt
15-
from PyQt5.QtGui import QColor, QImage, QPainter, QPixmap
15+
from PyQt5.QtGui import QCloseEvent, QColor, QImage, QPainter, QPixmap
1616
from PyQt5.QtWidgets import QAction, QCheckBox, QFileDialog, QGroupBox, QHBoxLayout, QMainWindow, QVBoxLayout, QWidget
1717
from zivid.calibration import DetectionResult, DetectionResultFiducialMarkers, MarkerShape
1818
from zivid.experimental import PixelMapping
@@ -109,13 +109,13 @@ class TestHandEyeCaptureSettings(QMainWindow):
109109
camera: Optional[zivid.Camera] = None
110110
last_frame: zivid.Frame
111111

112-
def __init__(self, parent=None):
112+
def __init__(self, zivid_app: zivid.Application, parent=None):
113113
super().__init__(parent)
114114
self.setObjectName("HandEyeSettingsTester")
115115

116116
self.cv2_handler = CV2Handler()
117-
118-
self.setup_camera()
117+
self.zivid_app = zivid_app
118+
self.camera = select_camera(self.zivid_app, connect=True)
119119
self.setup_settings()
120120
self.create_widgets()
121121
self.setup_layout()
@@ -129,14 +129,6 @@ def __init__(self, parent=None):
129129

130130
self.live2d_widget.start_live_2d()
131131

132-
def setup_camera(self):
133-
self.zividApp = zivid.Application()
134-
cameras = self.zividApp.cameras()
135-
if len(cameras) > 0:
136-
self.camera = select_camera(cameras)
137-
if self.camera is not None:
138-
self.camera.connect()
139-
140132
def setup_settings(self):
141133
if self.camera:
142134
self.settings = select_settings_for_hand_eye(self.camera)
@@ -226,7 +218,9 @@ def on_capture_button_clicked(self):
226218
self.live2d_widget.stop_live_2d()
227219
try:
228220
frame = self.camera.capture_2d_3d(
229-
self.settings.hand_eye if self.capture_with_hand_eye_settings.isChecked() else self.settings.production
221+
self.settings.hand_eye.settings_2d3d
222+
if self.capture_with_hand_eye_settings.isChecked()
223+
else self.settings.production.settings_2d3d
230224
)
231225
self.last_frame = frame
232226
self.visualize_frame_action.setEnabled(True)
@@ -278,11 +272,10 @@ def on_connect_button_clicked(self):
278272
self.camera_buttons.set_connection_status(False)
279273
else:
280274
assert self.settings is not None
281-
self.camera = select_camera(self.zividApp.cameras())
275+
self.camera = select_camera(self.zivid_app, connect=True)
282276
if self.camera is None:
283277
self.camera_buttons.set_connection_status(False)
284278
else:
285-
self.camera.connect()
286279
self.camera_buttons.set_connection_status(self.camera.state.connected)
287280
self.setup_settings()
288281
if self.camera.state.connected:
@@ -361,7 +354,11 @@ def log_detection_result(
361354
log_message += f" (Engine: {self.settings.production.settings_2d3d.engine:>8}, Sampling: {self.settings.production.settings_2d3d.sampling.pixel:>20})"
362355
print(log_message)
363356

357+
def closeEvent(self, event: QCloseEvent) -> None: # pylint: disable=C0103
358+
self.live2d_widget.closeEvent(event)
359+
super().closeEvent(event)
360+
364361

365362
if __name__ == "__main__": # NOLINT
366-
qtApp = ZividQtApplication()
367-
qtApp.run(TestHandEyeCaptureSettings(), "Test Hand Eye Settings")
363+
with ZividQtApplication() as qtApp:
364+
qtApp.run(TestHandEyeCaptureSettings(qtApp.zivid_app), "Test Hand Eye Settings")

modules/zividsamples/gui/marker_widget.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def select_marker_configuration(
270270

271271

272272
if __name__ == "__main__": # NOLINT
273-
qtApp = ZividQtApplication()
274-
widget = MarkersWidget()
275-
widget.show()
276-
qtApp.exec_()
273+
with ZividQtApplication(use_zivid_app=False) as qtApp:
274+
widget = MarkersWidget()
275+
widget.show()
276+
qtApp.exec_()

modules/zividsamples/gui/qt_application.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ class ZividFonts:
188188

189189
class ZividQtApplication(QApplication):
190190

191-
def __init__(self):
191+
def __init__(self, use_zivid_app: bool = True):
192192
if "cv2" in sys.modules:
193193
raise RuntimeError(
194194
"When using a ZividQtApplication you cannot directly load/import cv2. It has conflicting versions of Qt on some platforms. Instead, add functionality to zividsamples.cv2_handler"
@@ -207,6 +207,11 @@ def __init__(self):
207207
)
208208
self.setFont(ZividFonts.normal)
209209

210+
if use_zivid_app:
211+
import zivid # pylint: disable=import-outside-toplevel
212+
213+
self.zivid_app = zivid.Application()
214+
210215
def run(self, win, title: str = "Zivid Qt Application"):
211216
icon_path = get_file_path("LogoZBlue.ico")
212217
self.setWindowIcon(QIcon(icon_path.absolute().as_posix()))
@@ -224,3 +229,10 @@ def run(self, win, title: str = "Zivid Qt Application"):
224229
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("zivid.app.qt_application")
225230

226231
return self.exec_()
232+
233+
def __enter__(self):
234+
return self
235+
236+
def __exit__(self, exception_type, exception_value, traceback):
237+
if hasattr(self, "zivid_app"):
238+
self.zivid_app.release()

modules/zividsamples/gui/rotation_format_configuration.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ def select_rotation_format(
267267

268268

269269
if __name__ == "__main__": # NOLINT
270-
qtApp = ZividQtApplication()
271-
print(
272-
f"Selected format: {select_rotation_format(current_rotation_format=ListOfRobotFormats[0].rotation_information)}"
273-
)
270+
with ZividQtApplication(use_zivid_app=False):
271+
selected_rotation_format = select_rotation_format(
272+
current_rotation_format=ListOfRobotFormats[0].rotation_information
273+
)
274+
print(f"Selected format: {selected_rotation_format}")

modules/zividsamples/gui/set_fixed_objects.py

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -365,36 +365,37 @@ def set_fixed_objects(
365365

366366

367367
if __name__ == "__main__":
368-
qt_app = ZividQtApplication()
369-
370-
# Example usage
371-
result = set_fixed_objects(
372-
FixedCalibrationObjectsData(
373-
hand_eye_configuration=HandEyeConfiguration(eye_in_hand=True, calibration_object=CalibrationObject.Markers),
374-
marker_configuration=MarkerConfiguration(),
375-
marker_positions_eye_in_hand={
376-
0: [100.0, 1000.0, 0.0],
377-
1: [150.0, 1050.0, 0.0],
378-
2: [150.0, 1000.0, 0.0],
379-
3: [100.0, 1050.0, 0.0],
380-
},
381-
marker_positions_eye_to_hand={
382-
0: [100.0, 0.0, 0.0],
383-
1: [150.0, 50.0, 0.0],
384-
2: [150.0, 0.0, 0.0],
385-
3: [100.0, 50.0, 0.0],
386-
},
387-
calibration_board_pose_eye_in_hand=TransformationMatrix(
388-
translation=[-90.0, 1220.0, 0.0],
389-
),
390-
calibration_board_pose_eye_to_hand=TransformationMatrix(
391-
translation=[-90.0, 220.0, 0.0],
392-
),
393-
use_rotation=True,
368+
with ZividQtApplication():
369+
# Example usage
370+
result = set_fixed_objects(
371+
FixedCalibrationObjectsData(
372+
hand_eye_configuration=HandEyeConfiguration(
373+
eye_in_hand=True, calibration_object=CalibrationObject.Markers
374+
),
375+
marker_configuration=MarkerConfiguration(),
376+
marker_positions_eye_in_hand={
377+
0: [100.0, 1000.0, 0.0],
378+
1: [150.0, 1050.0, 0.0],
379+
2: [150.0, 1000.0, 0.0],
380+
3: [100.0, 1050.0, 0.0],
381+
},
382+
marker_positions_eye_to_hand={
383+
0: [100.0, 0.0, 0.0],
384+
1: [150.0, 50.0, 0.0],
385+
2: [150.0, 0.0, 0.0],
386+
3: [100.0, 50.0, 0.0],
387+
},
388+
calibration_board_pose_eye_in_hand=TransformationMatrix(
389+
translation=[-90.0, 1220.0, 0.0],
390+
),
391+
calibration_board_pose_eye_to_hand=TransformationMatrix(
392+
translation=[-90.0, 220.0, 0.0],
393+
),
394+
use_rotation=True,
395+
)
394396
)
395-
)
396-
if result:
397-
print("Fixed objects set successfully:")
398-
print(result.to_fixed_calibration_objects())
399-
else:
400-
print("Fixed objects not set.")
397+
if result:
398+
print("Fixed objects set successfully:")
399+
print(result.to_fixed_calibration_objects())
400+
else:
401+
print("Fixed objects not set.")

modules/zividsamples/gui/tutorial_widget.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,11 @@ def add_steps(self, steps: Dict[str, bool]):
4949
def update_text(self):
5050
self.text_area.clear()
5151
text = f"<h2>{self.title}</h2>"
52-
text += "<p><ol>"
52+
text += "<table cellpadding='5' style='border-collapse: collapse; width: 100%;; margin-top: 10px;'>"
5353
for step, completed in self.steps.items():
54-
if completed:
55-
text += f"<li>&#10003; {step}</li>" # HTML entity for checkmark (✓)
56-
else:
57-
text += f"<li>{step}</li>"
58-
text += "</ol></p>"
54+
checkmark = "&#x2705;" if completed else "&#x2610;" # ✓ for checked, ☐ for unchecked
55+
text += f"<tr><td>{checkmark}</td><td>{step}</td></tr>"
56+
text += "</table>"
5957
text += "<p>" + "</p><p>".join(paragraph for paragraph in self.description) + "</p>"
6058
self.text_area.setText(text)
6159

source/applications/advanced/hand_eye_calibration/hand_eye_gui.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,11 @@ class HandEyeGUI(QMainWindow): # pylint: disable=R0902, R0904
7070
rotation_information: RotationInformation = RotationInformation()
7171
common_instructions: Dict[str, bool] = {}
7272

73-
def __init__(self, parent=None): # noqa: ANN001
73+
def __init__(self, zivid_app: zivid.Application, parent=None): # noqa: ANN001
7474
super().__init__(parent)
7575

76-
self.setup_camera()
76+
self.zivid_app = zivid_app
77+
self.camera = select_camera(self.zivid_app, connect=True)
7778
self.setup_settings()
7879
self.create_widgets()
7980
self.setup_layout()
@@ -86,14 +87,6 @@ def __init__(self, parent=None): # noqa: ANN001
8687

8788
QTimer.singleShot(0, self.update_tab_order)
8889

89-
def setup_camera(self) -> None:
90-
self.zivid_app = zivid.Application()
91-
cameras = self.zivid_app.cameras()
92-
if len(cameras) > 0:
93-
self.camera = select_camera(cameras)
94-
if self.camera is not None:
95-
self.camera.connect()
96-
9790
def setup_settings(self) -> None:
9891
if self.camera:
9992
self.settings = select_settings_for_hand_eye(self.camera)
@@ -606,9 +599,11 @@ def on_connect_button_clicked(self) -> None:
606599
self.live2d_widget.stop_live_2d()
607600
self.live2d_widget.hide()
608601
self.camera.disconnect()
602+
self.setup_instructions()
603+
self.on_instructions_updated()
609604
self.camera_buttons.set_connection_status(False)
610605
else:
611-
self.camera = select_camera(self.zivid_app.cameras())
606+
self.camera = select_camera(self.zivid_app, connect=True)
612607
if self.camera is None:
613608
self.camera_buttons.set_connection_status(False)
614609
else:
@@ -635,9 +630,8 @@ def closeEvent(self, event: QCloseEvent) -> None: # pylint: disable=C0103
635630

636631

637632
def _main() -> None:
638-
qt_app = ZividQtApplication()
639-
640-
sys.exit(qt_app.run(HandEyeGUI(), "Hand-Eye GUI"))
633+
with ZividQtApplication() as qt_app:
634+
sys.exit(qt_app.run(HandEyeGUI(qt_app.zivid_app), "Hand-Eye GUI"))
641635

642636

643637
if __name__ == "__main__": # NOLINT

0 commit comments

Comments
 (0)