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.
- calibrate_suite-0.1.0.dist-info/METADATA +761 -0
- calibrate_suite-0.1.0.dist-info/RECORD +47 -0
- calibrate_suite-0.1.0.dist-info/WHEEL +5 -0
- calibrate_suite-0.1.0.dist-info/entry_points.txt +3 -0
- calibrate_suite-0.1.0.dist-info/licenses/LICENSE +201 -0
- calibrate_suite-0.1.0.dist-info/top_level.txt +4 -0
- fleet_server/__init__.py +32 -0
- fleet_server/app.py +377 -0
- fleet_server/config.py +91 -0
- fleet_server/templates/error.html +57 -0
- fleet_server/templates/index.html +137 -0
- fleet_server/templates/viewer.html +490 -0
- fleet_server/utils.py +178 -0
- gui/__init__.py +2 -0
- gui/assets/2d-or-3d-fleet-upload.png +0 -0
- gui/assets/2d_3d_overlay_output.jpg +0 -0
- gui/assets/3d-or-2d-overlay_page.png +0 -0
- gui/assets/3d-or-2d-record-page.png +0 -0
- gui/assets/3d_3d_overlay_output.png +0 -0
- gui/assets/3d_or_2d_calibrate-page.png +0 -0
- gui/assets/GUI_homepage.png +0 -0
- gui/assets/hardware_setup.jpeg +0 -0
- gui/assets/single_lidar_calibrate_page.png +0 -0
- gui/assets/single_lidar_output.png +0 -0
- gui/assets/single_lidar_record_page.png +0 -0
- gui/assets/virya.jpg +0 -0
- gui/main.py +23 -0
- gui/widgets/calibrator_widget.py +977 -0
- gui/widgets/extractor_widget.py +561 -0
- gui/widgets/home_widget.py +117 -0
- gui/widgets/recorder_widget.py +127 -0
- gui/widgets/single_lidar_widget.py +673 -0
- gui/widgets/three_d_calib_widget.py +87 -0
- gui/widgets/two_d_calib_widget.py +86 -0
- gui/widgets/uploader_widget.py +151 -0
- gui/widgets/validator_widget.py +614 -0
- gui/windows/main_window.py +56 -0
- gui/windows/main_window_ui.py +65 -0
- rviz_configs/2D-3D.rviz +183 -0
- rviz_configs/3D-3D.rviz +184 -0
- rviz_configs/default_calib.rviz +167 -0
- utils/__init__.py +13 -0
- utils/calibration_common.py +23 -0
- utils/cli_calibrate.py +53 -0
- utils/cli_fleet_server.py +64 -0
- utils/data_extractor_common.py +87 -0
- utils/gui_helpers.py +25 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Entry point for calibrate-suite fleet server (REST API).
|
|
4
|
+
|
|
5
|
+
Usage: fleet-server [--host HOST] [--port PORT] [--debug]
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
"""Launch the fleet server REST API."""
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="fleet-server",
|
|
16
|
+
description="Calibrate-Suite Fleet Server: REST API for calibration management",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--host",
|
|
20
|
+
default="127.0.0.1",
|
|
21
|
+
help="Server host address (default: 127.0.0.1)",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--port",
|
|
25
|
+
type=int,
|
|
26
|
+
default=5000,
|
|
27
|
+
help="Server port (default: 5000)",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--debug",
|
|
31
|
+
action="store_true",
|
|
32
|
+
help="Enable debug mode",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--version",
|
|
36
|
+
action="version",
|
|
37
|
+
version="fleet-server (calibrate-suite 0.1.0)",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
|
|
42
|
+
# Import here to avoid Flask dependencies when not needed
|
|
43
|
+
try:
|
|
44
|
+
from fleet_server.app import app
|
|
45
|
+
|
|
46
|
+
print(f"Starting Fleet Server on {args.host}:{args.port}")
|
|
47
|
+
print(f"Dashboard: http://{args.host}:{args.port}")
|
|
48
|
+
print(f"API Docs: http://{args.host}:{args.port}/api/docs")
|
|
49
|
+
print("Press Ctrl+C to stop server\n")
|
|
50
|
+
|
|
51
|
+
app.run(host=args.host, port=args.port, debug=args.debug)
|
|
52
|
+
|
|
53
|
+
except ImportError as e:
|
|
54
|
+
print(
|
|
55
|
+
f"Error: Fleet server dependencies not installed. Please install with:\n"
|
|
56
|
+
f" pip install calibrate-suite\n"
|
|
57
|
+
f"If Flask is missing, try:\n"
|
|
58
|
+
f" pip install flask requests"
|
|
59
|
+
)
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Shared data extractor utilities"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import struct
|
|
5
|
+
import numpy as np
|
|
6
|
+
import open3d as o3d
|
|
7
|
+
|
|
8
|
+
def flush_print(msg):
|
|
9
|
+
"""Print with immediate stdout flush."""
|
|
10
|
+
print(msg)
|
|
11
|
+
sys.stdout.flush()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def save_pc2_to_pcd(msg, filename):
|
|
15
|
+
"""
|
|
16
|
+
Convert ROS PointCloud2 message to PCD file.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
field_names = [f.name for f in msg.fields]
|
|
20
|
+
offsets = {f.name: f.offset for f in msg.fields}
|
|
21
|
+
|
|
22
|
+
if 'x' not in offsets:
|
|
23
|
+
flush_print("[WARN] PointCloud has no X field.")
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
n_points = msg.width * msg.height
|
|
27
|
+
if n_points == 0:
|
|
28
|
+
flush_print(f"[WARN] Empty point cloud (0 points) in {filename}")
|
|
29
|
+
pc = o3d.geometry.PointCloud()
|
|
30
|
+
o3d.io.write_point_cloud(filename, pc)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
step = msg.point_step
|
|
34
|
+
data = msg.data
|
|
35
|
+
|
|
36
|
+
# Extract XYZ points
|
|
37
|
+
off_x = offsets.get('x', 0)
|
|
38
|
+
off_y = offsets.get('y', 4)
|
|
39
|
+
off_z = offsets.get('z', 8)
|
|
40
|
+
|
|
41
|
+
pts = np.zeros((n_points, 3), dtype=np.float32)
|
|
42
|
+
|
|
43
|
+
for i in range(n_points):
|
|
44
|
+
base = i * step
|
|
45
|
+
try:
|
|
46
|
+
pts[i, 0] = struct.unpack_from('f', data, base + off_x)[0]
|
|
47
|
+
pts[i, 1] = struct.unpack_from('f', data, base + off_y)[0]
|
|
48
|
+
pts[i, 2] = struct.unpack_from('f', data, base + off_z)[0]
|
|
49
|
+
except struct.error as e:
|
|
50
|
+
pts[i] = [0, 0, 0]
|
|
51
|
+
|
|
52
|
+
# Filter out NaN and Inf points
|
|
53
|
+
valid_mask = np.isfinite(pts).all(axis=1)
|
|
54
|
+
valid_pts = pts[valid_mask]
|
|
55
|
+
|
|
56
|
+
pc = o3d.geometry.PointCloud()
|
|
57
|
+
if len(valid_pts) > 0:
|
|
58
|
+
pc.points = o3d.utility.Vector3dVector(valid_pts.astype(np.float64))
|
|
59
|
+
|
|
60
|
+
o3d.io.write_point_cloud(filename, pc)
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
flush_print(f"[ERR] Failed to save PCD {filename}: {e}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def depth_to_camera_cloud(depth: np.ndarray, K: np.ndarray):
|
|
67
|
+
"""
|
|
68
|
+
Convert depth image to camera point cloud using intrinsics.
|
|
69
|
+
"""
|
|
70
|
+
h, w = depth.shape
|
|
71
|
+
fx, fy = K[0, 0], K[1, 1]
|
|
72
|
+
cx, cy = K[0, 2], K[1, 2]
|
|
73
|
+
|
|
74
|
+
u, v = np.meshgrid(np.arange(w), np.arange(h))
|
|
75
|
+
z = depth.flatten()
|
|
76
|
+
valid = np.isfinite(z) & (z > 0)
|
|
77
|
+
|
|
78
|
+
if not np.any(valid):
|
|
79
|
+
return o3d.geometry.PointCloud()
|
|
80
|
+
|
|
81
|
+
x = (u.flatten()[valid] - cx) * z[valid] / fx
|
|
82
|
+
y = (v.flatten()[valid] - cy) * z[valid] / fy
|
|
83
|
+
pts = np.vstack((x, y, z[valid])).T
|
|
84
|
+
|
|
85
|
+
pc = o3d.geometry.PointCloud()
|
|
86
|
+
pc.points = o3d.utility.Vector3dVector(pts.astype(np.float64))
|
|
87
|
+
return pc
|
utils/gui_helpers.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Shared GUI helper utilities."""
|
|
2
|
+
|
|
3
|
+
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def wrap_with_back(widget, go_home_signal):
|
|
7
|
+
"""
|
|
8
|
+
Wrap a widget with a Back button at the bottom.
|
|
9
|
+
"""
|
|
10
|
+
wrapper = QWidget()
|
|
11
|
+
layout = QVBoxLayout()
|
|
12
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
13
|
+
layout.addWidget(widget)
|
|
14
|
+
|
|
15
|
+
btn_back = QPushButton("Back")
|
|
16
|
+
btn_back.clicked.connect(go_home_signal.emit)
|
|
17
|
+
# Match style
|
|
18
|
+
btn_back.setStyleSheet("""
|
|
19
|
+
QPushButton { background-color: #2E8B57; border-radius: 8px; padding: 5px; color: white; }
|
|
20
|
+
QPushButton:hover { background-color: #689F38; }
|
|
21
|
+
""")
|
|
22
|
+
|
|
23
|
+
layout.addWidget(btn_back)
|
|
24
|
+
wrapper.setLayout(layout)
|
|
25
|
+
return wrapper
|