calibrate-suite 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. calibrate_suite-0.1.0.dist-info/METADATA +761 -0
  2. calibrate_suite-0.1.0.dist-info/RECORD +47 -0
  3. calibrate_suite-0.1.0.dist-info/WHEEL +5 -0
  4. calibrate_suite-0.1.0.dist-info/entry_points.txt +3 -0
  5. calibrate_suite-0.1.0.dist-info/licenses/LICENSE +201 -0
  6. calibrate_suite-0.1.0.dist-info/top_level.txt +4 -0
  7. fleet_server/__init__.py +32 -0
  8. fleet_server/app.py +377 -0
  9. fleet_server/config.py +91 -0
  10. fleet_server/templates/error.html +57 -0
  11. fleet_server/templates/index.html +137 -0
  12. fleet_server/templates/viewer.html +490 -0
  13. fleet_server/utils.py +178 -0
  14. gui/__init__.py +2 -0
  15. gui/assets/2d-or-3d-fleet-upload.png +0 -0
  16. gui/assets/2d_3d_overlay_output.jpg +0 -0
  17. gui/assets/3d-or-2d-overlay_page.png +0 -0
  18. gui/assets/3d-or-2d-record-page.png +0 -0
  19. gui/assets/3d_3d_overlay_output.png +0 -0
  20. gui/assets/3d_or_2d_calibrate-page.png +0 -0
  21. gui/assets/GUI_homepage.png +0 -0
  22. gui/assets/hardware_setup.jpeg +0 -0
  23. gui/assets/single_lidar_calibrate_page.png +0 -0
  24. gui/assets/single_lidar_output.png +0 -0
  25. gui/assets/single_lidar_record_page.png +0 -0
  26. gui/assets/virya.jpg +0 -0
  27. gui/main.py +23 -0
  28. gui/widgets/calibrator_widget.py +977 -0
  29. gui/widgets/extractor_widget.py +561 -0
  30. gui/widgets/home_widget.py +117 -0
  31. gui/widgets/recorder_widget.py +127 -0
  32. gui/widgets/single_lidar_widget.py +673 -0
  33. gui/widgets/three_d_calib_widget.py +87 -0
  34. gui/widgets/two_d_calib_widget.py +86 -0
  35. gui/widgets/uploader_widget.py +151 -0
  36. gui/widgets/validator_widget.py +614 -0
  37. gui/windows/main_window.py +56 -0
  38. gui/windows/main_window_ui.py +65 -0
  39. rviz_configs/2D-3D.rviz +183 -0
  40. rviz_configs/3D-3D.rviz +184 -0
  41. rviz_configs/default_calib.rviz +167 -0
  42. utils/__init__.py +13 -0
  43. utils/calibration_common.py +23 -0
  44. utils/cli_calibrate.py +53 -0
  45. utils/cli_fleet_server.py +64 -0
  46. utils/data_extractor_common.py +87 -0
  47. utils/gui_helpers.py +25 -0
@@ -0,0 +1,87 @@
1
+ from PyQt5.QtWidgets import (QWidget, QTabWidget, QVBoxLayout, QHBoxLayout, QLabel,
2
+ QPushButton, QGroupBox, QLineEdit, QComboBox, QTextEdit,
3
+ QRadioButton, QCheckBox, QFileDialog, QMessageBox, QSizePolicy, QScrollArea)
4
+ from PyQt5.QtCore import pyqtSignal, Qt, QProcess
5
+ import os
6
+ import glob
7
+ import json
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add src/ to path for utils import
13
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
14
+ from utils import wrap_with_back
15
+
16
+ # Import existing widgets for other tabs
17
+ try:
18
+ from ..widgets.extractor_widget import ExtractorWidget
19
+ from ..widgets.calibrator_widget import ThreeDCalibratorWidget
20
+ from ..widgets.validator_widget import ThreeDValidatorWidget
21
+ from ..widgets.uploader_widget import UploaderWidget
22
+ except ImportError:
23
+ from gui.widgets.extractor_widget import ExtractorWidget
24
+ from gui.widgets.calibrator_widget import ThreeDCalibratorWidget
25
+ from gui.widgets.validator_widget import ThreeDValidatorWidget
26
+ from gui.widgets.uploader_widget import UploaderWidget
27
+
28
+ class ThreeDThreeDWidget(QWidget):
29
+ go_home = pyqtSignal()
30
+
31
+ def __init__(self):
32
+ super().__init__()
33
+ self.initUI()
34
+
35
+ def initUI(self):
36
+ layout = QVBoxLayout()
37
+
38
+ # Header
39
+ header = QHBoxLayout()
40
+ btn_back = QPushButton("← Home")
41
+ btn_back.clicked.connect(self.go_home.emit)
42
+ btn_back.setFixedWidth(100)
43
+ btn_back.setStyleSheet("""
44
+ QPushButton { background-color: #2E8B57; border-radius: 8px; color: white; }
45
+ QPushButton:hover { background-color: #689F38; }
46
+ """)
47
+ header.addWidget(btn_back)
48
+
49
+ title = QLabel("3D-3D Calibration (LiDAR-Camera)")
50
+ title.setStyleSheet("font-size: 18px; font-weight: bold; color: #2196F3;")
51
+ header.addWidget(title)
52
+ header.addStretch()
53
+ layout.addLayout(header)
54
+
55
+ # Tabs
56
+ self.tabs = QTabWidget()
57
+ self.tabs.setStyleSheet("""
58
+ QTabBar::tab {
59
+ min-width: 150px;
60
+ padding: 8px;
61
+ font-weight: bold;
62
+ }
63
+ QTabBar::tab:selected {
64
+ background-color: #1B5E20;
65
+ color: white;
66
+ }
67
+ """)
68
+ self.init_tabs()
69
+ layout.addWidget(self.tabs)
70
+
71
+ self.setLayout(layout)
72
+
73
+ def init_tabs(self):
74
+ # 1. Record (Multimodal) - Uses ExtractorWidget
75
+ # Note: ExtractorWidget now contains the Multimodal Recording logic
76
+ rec_widget = ExtractorWidget()
77
+ rec_widget.back_clicked.connect(self.go_home.emit)
78
+ self.tabs.addTab(rec_widget, "Record (Multimodal)")
79
+
80
+ # 2. Calibration (3D-3D specific)
81
+ self.tabs.addTab(wrap_with_back(ThreeDCalibratorWidget(), self.go_home), "Calibrate")
82
+
83
+ # 3. Validation (3D-3D specific - Point cloud overlay visualization)
84
+ self.tabs.addTab(wrap_with_back(ThreeDValidatorWidget(), self.go_home), "Validate")
85
+
86
+ # 4. Upload (Shared with 2D-3D)
87
+ self.tabs.addTab(wrap_with_back(UploaderWidget(), self.go_home), "Fleet Upload")
@@ -0,0 +1,86 @@
1
+ from PyQt5.QtWidgets import (QWidget, QTabWidget, QVBoxLayout, QHBoxLayout, QLabel,
2
+ QPushButton, QGroupBox, QLineEdit, QComboBox, QTextEdit,
3
+ QRadioButton, QCheckBox, QFileDialog, QMessageBox, QSizePolicy, QScrollArea)
4
+ from PyQt5.QtCore import pyqtSignal, Qt, QProcess
5
+ import os
6
+ import glob
7
+ import json
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Add src/ to path for utils import
13
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
14
+ from utils import wrap_with_back
15
+
16
+ # Import existing widgets for other tabs
17
+ try:
18
+ from ..widgets.extractor_widget import ExtractorWidget
19
+ from ..widgets.calibrator_widget import CalibratorWidget
20
+ from ..widgets.validator_widget import ValidatorWidget
21
+ from ..widgets.uploader_widget import UploaderWidget
22
+ except ImportError:
23
+ from gui.widgets.extractor_widget import ExtractorWidget
24
+ from gui.widgets.calibrator_widget import CalibratorWidget
25
+ from gui.widgets.validator_widget import ValidatorWidget
26
+ from gui.widgets.uploader_widget import UploaderWidget
27
+
28
+ class TwoDThreeDWidget(QWidget):
29
+ go_home = pyqtSignal()
30
+
31
+ def __init__(self):
32
+ super().__init__()
33
+ self.initUI()
34
+
35
+ def initUI(self):
36
+ layout = QVBoxLayout()
37
+
38
+ # Header
39
+ header = QHBoxLayout()
40
+ btn_back = QPushButton("← Home")
41
+ btn_back.clicked.connect(self.go_home.emit)
42
+ btn_back.setFixedWidth(100)
43
+ btn_back.setStyleSheet("""
44
+ QPushButton { background-color: #2E8B57; border-radius: 8px; color: white; }
45
+ QPushButton:hover { background-color: #689F38; }
46
+ """)
47
+ header.addWidget(btn_back)
48
+
49
+ title = QLabel("2D-3D Calibration (LiDAR-Camera)")
50
+ title.setStyleSheet("font-size: 18px; font-weight: bold; color: #FF9800;")
51
+ header.addWidget(title)
52
+ header.addStretch()
53
+ layout.addLayout(header)
54
+
55
+ # Tabs
56
+ self.tabs = QTabWidget()
57
+ self.tabs.setStyleSheet("""
58
+ QTabBar::tab {
59
+ min-width: 150px;
60
+ padding: 8px;
61
+ font-weight: bold;
62
+ }
63
+ QTabBar::tab:selected {
64
+ background-color: #E65100;
65
+ color: white;
66
+ }
67
+ """)
68
+ self.init_tabs()
69
+ layout.addWidget(self.tabs)
70
+
71
+ self.setLayout(layout)
72
+
73
+ def init_tabs(self):
74
+ # 1. Record (Multimodal) - Uses ExtractorWidget
75
+ rec_widget = ExtractorWidget(enable_depth=False, script_name="2d_3d_data_extractor.py")
76
+ rec_widget.back_clicked.connect(self.go_home.emit)
77
+ self.tabs.addTab(rec_widget, "Record (2D-3D)")
78
+
79
+ # 2. Calibration (moved up after Record)
80
+ self.tabs.addTab(wrap_with_back(CalibratorWidget(), self.go_home), "Calibrate")
81
+
82
+ # 3. Validation with Visualization
83
+ self.tabs.addTab(wrap_with_back(ValidatorWidget(), self.go_home), "Validate")
84
+
85
+ # 4. Upload
86
+ self.tabs.addTab(wrap_with_back(UploaderWidget(), self.go_home), "Fleet Upload")
@@ -0,0 +1,151 @@
1
+ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
2
+ QLineEdit, QPushButton, QMessageBox, QGroupBox, QFileDialog)
3
+ from PyQt5.QtCore import QThread, pyqtSignal
4
+ import os
5
+ import json
6
+ import requests
7
+
8
+ class UploadWorker(QThread):
9
+ finished = pyqtSignal(bool, str)
10
+
11
+ def __init__(self, server_url, file_path, robot_id, score):
12
+ super().__init__()
13
+ self.server_url = server_url
14
+ self.file_path = file_path
15
+ self.robot_id = robot_id
16
+ self.score = score
17
+
18
+ def run(self):
19
+ try:
20
+ url = f"{self.server_url}/api/upload"
21
+
22
+ metadata = {
23
+ 'robot_id': self.robot_id,
24
+ 'score': self.score
25
+ }
26
+
27
+ with open(self.file_path, 'rb') as f:
28
+ files = {'artifacts': f}
29
+ data = {'metadata': json.dumps(metadata)}
30
+
31
+ response = requests.post(url, files=files, data=data)
32
+
33
+ if response.status_code in [200, 201]:
34
+ self.finished.emit(True, "Upload Successful")
35
+ else:
36
+ self.finished.emit(False, f"Server Error: {response.status_code}")
37
+
38
+ except Exception as e:
39
+ self.finished.emit(False, str(e))
40
+
41
+ class UploaderWidget(QWidget):
42
+ def __init__(self):
43
+ super().__init__()
44
+ self.initUI()
45
+
46
+ def initUI(self):
47
+ layout = QVBoxLayout()
48
+
49
+ group = QGroupBox("Upload to Fleet Server")
50
+ form = QVBoxLayout()
51
+
52
+ # Server URL
53
+ url_layout = QHBoxLayout()
54
+ self.url_input = QLineEdit("http://localhost:5000")
55
+ url_layout.addWidget(QLabel("Server URL:"))
56
+ url_layout.addWidget(self.url_input)
57
+ form.addLayout(url_layout)
58
+
59
+ # Robot ID
60
+ rid_layout = QHBoxLayout()
61
+ self.rid_input = QLineEdit("AMR_001")
62
+ rid_layout.addWidget(QLabel("Robot ID:"))
63
+ rid_layout.addWidget(self.rid_input)
64
+ form.addLayout(rid_layout)
65
+
66
+ # File selector with browse button
67
+ file_layout = QHBoxLayout()
68
+ self.file_input = QLineEdit("calib_result.yaml")
69
+ self.btn_browse = QPushButton("Browse...")
70
+ self.btn_browse.clicked.connect(self.browse_file)
71
+ self.btn_browse.setFixedWidth(100)
72
+ file_layout.addWidget(QLabel("Result File:"))
73
+ file_layout.addWidget(self.file_input)
74
+ file_layout.addWidget(self.btn_browse)
75
+ form.addLayout(file_layout)
76
+
77
+ # Upload Button
78
+ self.btn_upload = QPushButton("☁️ Upload Result")
79
+ self.btn_upload.clicked.connect(self.start_upload)
80
+ self.btn_upload.setStyleSheet("""
81
+ QPushButton { background-color: #9C27B0; color: white; padding: 10px; }
82
+ QPushButton:hover { background-color: #BA68C8; }
83
+ """)
84
+ form.addWidget(self.btn_upload)
85
+
86
+ group.setLayout(form)
87
+ layout.addWidget(group)
88
+ self.setLayout(layout)
89
+
90
+ def browse_file(self):
91
+ """Open file dialog to select calibration result file"""
92
+ file_path, _ = QFileDialog.getOpenFileName(
93
+ self,
94
+ "Select Calibration Result File",
95
+ ".", # Start in current directory
96
+ "YAML files (*.yaml *.yml);;All files (*.*)"
97
+ )
98
+ if file_path:
99
+ self.file_input.setText(file_path)
100
+
101
+ def start_upload(self):
102
+ path = self.file_input.text()
103
+ url = self.url_input.text()
104
+ rid = self.rid_input.text()
105
+
106
+ if not os.path.exists(path):
107
+ msg_box = QMessageBox(self)
108
+ msg_box.setIcon(QMessageBox.Warning)
109
+ msg_box.setWindowTitle("Error")
110
+ msg_box.setText("Result file not found")
111
+ msg_box.setStyleSheet("QMessageBox { background-color: #404040; color: white; }")
112
+ msg_box.exec_()
113
+ return
114
+
115
+ # Parse score from yaml if possible, else 0
116
+ score = 0.0
117
+ try:
118
+ # simple parse
119
+ with open(path, 'r') as f:
120
+ for line in f:
121
+ if "avg_point_to_plane_rmse_m" in line:
122
+ score = float(line.split(':')[1].strip())
123
+ break
124
+ except:
125
+ pass
126
+
127
+ self.btn_upload.setEnabled(False)
128
+ self.btn_upload.setText("Uploading...")
129
+
130
+ self.worker = UploadWorker(url, path, rid, score)
131
+ self.worker.finished.connect(self.on_finished)
132
+ self.worker.start()
133
+
134
+ def on_finished(self, success, msg):
135
+ self.btn_upload.setEnabled(True)
136
+ self.btn_upload.setText("☁️ Upload Result")
137
+
138
+ if success:
139
+ msg_box = QMessageBox(self)
140
+ msg_box.setIcon(QMessageBox.Information)
141
+ msg_box.setWindowTitle("Success")
142
+ msg_box.setText(msg)
143
+ msg_box.setStyleSheet("QMessageBox { background-color: #404040; color: white; }")
144
+ msg_box.exec_()
145
+ else:
146
+ msg_box = QMessageBox(self)
147
+ msg_box.setIcon(QMessageBox.Critical)
148
+ msg_box.setWindowTitle("Error")
149
+ msg_box.setText(msg)
150
+ msg_box.setStyleSheet("QMessageBox { background-color: #404040; color: white; }")
151
+ msg_box.exec_()