Skip to content

Commit 44b60d9

Browse files
authored
Merge pull request #169 from aabadie/lightouse_export_calibration_in_header
Add lighthouse calibration tool
2 parents bf93b94 + 7671b1b commit 44b60d9

6 files changed

Lines changed: 104 additions & 71 deletions

File tree

‎dotbot/calibration_exporter.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import logging
2+
import os
3+
import sys
4+
from pathlib import Path
5+
6+
import click
7+
import structlog
8+
9+
from dotbot.lighthouse2 import LighthouseManager, LighthouseManagerState
10+
from dotbot.protocol import PayloadLh2CalibrationHomography
11+
12+
CALIBRATION_HEADER_FILENAME = Path("lh2_calibration.h")
13+
CALIBRATION_HEADER_HEADER = """// Auto-generated file, do not edit!
14+
#ifndef __LH2_CALIBRATION_H
15+
#define __LH2_CALIBRATION_H
16+
17+
#include "localization.h"
18+
19+
#define LH2_CALIBRATION_IS_VALID (1)
20+
21+
static int32_t swrmt_homography[3][3] = {
22+
"""
23+
24+
CALIBRATION_HEADER_FOOTER = """};
25+
26+
#endif // __LH2_CALIBRATION_H
27+
"""
28+
29+
30+
def export_calibration(calibration: PayloadLh2CalibrationHomography) -> str:
31+
"""Export the calibration file to a user-defined location."""
32+
# Store homography matrix as C header to use in SwarmIT bootloader
33+
output = CALIBRATION_HEADER_HEADER
34+
matrix_int = [
35+
int.from_bytes(calibration.homography_matrix[i : i + 4], "little", signed=True)
36+
for i in range(0, 36, 4)
37+
]
38+
matrix = [matrix_int[i : i + 3] for i in range(0, 9, 3)]
39+
for row in matrix:
40+
output += " {" + ", ".join(str(v) for v in row) + "},\n"
41+
output += CALIBRATION_HEADER_FOOTER
42+
return output
43+
44+
45+
@click.command()
46+
@click.argument("output_path", nargs=1)
47+
def main(output_path):
48+
"""Export DotBot calibration data to a file."""
49+
# Disable logging
50+
structlog.configure(
51+
wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL),
52+
)
53+
54+
if not os.path.exists(output_path):
55+
print(f"Error: '{output_path}' doesn't exist.", file=sys.stderr)
56+
sys.exit(1)
57+
58+
lh2_manager = LighthouseManager()
59+
if lh2_manager.state != LighthouseManagerState.Calibrated:
60+
print("Error: LighthouseManager is not calibrated.", file=sys.stderr)
61+
sys.exit(1)
62+
63+
try:
64+
output = export_calibration(lh2_manager.calibration)
65+
header_path = Path(output_path) / CALIBRATION_HEADER_FILENAME
66+
with open(header_path, "w") as header_file:
67+
header_file.write(output)
68+
print(output)
69+
print(f"Calibration data exported to '{header_path}'")
70+
except Exception as e:
71+
print(f"Error exporting calibration data: {e}", file=sys.stderr)
72+
sys.exit(1)
73+
74+
75+
if __name__ == "__main__":
76+
main()

‎dotbot/controller.py‎

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import webbrowser
1515
from binascii import hexlify
1616
from dataclasses import dataclass
17-
from typing import Dict, List, Optional
17+
from typing import Dict, List
1818

1919
import serial
2020
import uvicorn
@@ -72,7 +72,6 @@
7272
PayloadCommandXgoAction,
7373
PayloadGPSPosition,
7474
PayloadGPSWaypoints,
75-
PayloadLh2CalibrationHomography,
7675
PayloadLH2Location,
7776
PayloadLH2Waypoints,
7877
PayloadType,
@@ -493,17 +492,6 @@ async def _dotbots_status_refresh(self):
493492
)
494493
await asyncio.sleep(1)
495494

496-
def _compute_lh2_position(self, frame: Frame) -> Optional[DotBotLH2Position]:
497-
if frame.packet.payload_type not in (
498-
PayloadType.LH2_RAW_DATA,
499-
PayloadType.DOTBOT_DATA,
500-
):
501-
return None
502-
self.lh2_manager.last_raw_data = frame.packet.payload
503-
if self.lh2_manager.state != LighthouseManagerState.Calibrated:
504-
return None
505-
return self.lh2_manager.compute_position(frame.packet.payload)
506-
507495
def handle_received_frame(
508496
self, frame: Frame
509497
): # pylint:disable=too-many-branches,too-many-statements
@@ -569,21 +557,11 @@ def handle_received_frame(
569557
and self.lh2_manager.state == LighthouseManagerState.Calibrated
570558
):
571559
# Send calibration to new dotbot if the localization system is calibrated
572-
# Check if robot has lighthouse calibration
573-
matrix_bytes = bytearray()
574-
for bytes_block in [
575-
int(n * 1e6).to_bytes(4, "little", signed=True)
576-
for n in self.lh2_manager.calibration_data.m.ravel()
577-
]:
578-
matrix_bytes += bytes_block
579-
# Prepare homography matrix and send it to the robot
580-
payload = PayloadLh2CalibrationHomography(
581-
index=0,
582-
homography_matrix=matrix_bytes,
560+
self.logger.info(
561+
"Send calibration data", payload=self.lh2_manager.calibration
583562
)
584-
self.logger.info("Send calibration data", payload=payload)
585563
self.dotbots.update({dotbot.address: dotbot})
586-
self.send_payload(int(source, 16), payload=payload)
564+
self.send_payload(int(source, 16), payload=self.lh2_manager.calibration)
587565

588566
if (
589567
frame.packet.payload_type

‎dotbot/lighthouse2.py‎

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import numpy as np
2323

2424
from dotbot.logger import LOGGER
25-
from dotbot.models import DotBotCalibrationStateModel, DotBotLH2Position
26-
from dotbot.protocol import PayloadLh2RawData
25+
from dotbot.models import DotBotCalibrationStateModel
26+
from dotbot.protocol import PayloadLh2CalibrationHomography, PayloadLh2RawData
2727

2828
if sys.platform == "win32":
2929
LIB_EXT = "dll"
@@ -121,7 +121,7 @@ def __init__(self):
121121
self.reference_points = REFERENCE_POINTS_DEFAULT
122122
Path.mkdir(CALIBRATION_DIR, exist_ok=True)
123123
self.calibration_output_path = CALIBRATION_DIR / "calibration.out"
124-
self.calibration_data = self._load_calibration()
124+
self.calibration: PayloadLh2CalibrationHomography = self._load_calibration()
125125
self.calibration_points = np.zeros(
126126
(2, len(self.reference_points), 2), dtype=np.float64
127127
)
@@ -140,18 +140,14 @@ def state_model(self) -> DotBotCalibrationStateModel:
140140
return DotBotCalibrationStateModel(state="done")
141141
return DotBotCalibrationStateModel(state="unknown")
142142

143-
def _load_calibration(self) -> Optional[CalibrationData]:
143+
def _load_calibration(self) -> Optional[PayloadLh2CalibrationHomography]:
144144
if not os.path.exists(self.calibration_output_path):
145145
self.logger.info("No calibration file found")
146146
return None
147147
with open(self.calibration_output_path, "rb") as calibration_file:
148148
calibration = pickle.load(calibration_file)
149-
# for compatibility with existing calibration data type, cast
150-
# homography matrix to float32
151-
calibration.m = calibration.m.astype(np.float32)
152149
self.logger.info("Lighthouse calibration loaded")
153150
self.state = LighthouseManagerState.Calibrated
154-
155151
return calibration
156152

157153
def add_calibration_point(self, index):
@@ -260,42 +256,23 @@ def compute_calibration(self): # pylint: disable=too-many-locals
260256
ransacReprojThreshold=0.001,
261257
)
262258

263-
self.calibration_data = CalibrationData(zeta, random_rodriguez, n, M)
259+
calibration_data = CalibrationData(zeta, random_rodriguez, n, M)
260+
matrix_bytes = bytearray()
261+
for bytes_block in [
262+
int(n * 1e6).to_bytes(4, "little", signed=True)
263+
for n in calibration_data.m.ravel()
264+
]:
265+
matrix_bytes += bytes_block
266+
267+
# Prepare homography matrix and send it to the robot
268+
self.calibration = PayloadLh2CalibrationHomography(
269+
index=0,
270+
homography_matrix=matrix_bytes,
271+
)
264272

273+
# Store calibration data as pickle for later reload
265274
with open(self.calibration_output_path, "wb") as output_file:
266-
pickle.dump(self.calibration_data, output_file)
275+
pickle.dump(self.calibration, output_file)
267276

268277
self.state = LighthouseManagerState.Calibrated
269-
self.logger.info("Calibration done", data=self.calibration_data)
270-
271-
def compute_position(
272-
self, raw_data: PayloadLh2RawData
273-
) -> Optional[DotBotLH2Position]:
274-
"""Compute the position coordinates from LH2 raw data and available calibration."""
275-
if self.state != LighthouseManagerState.Calibrated:
276-
return None
277-
278-
if any(raw_data.locations[index].bits == 0 for index in range(2)):
279-
return None
280-
281-
counts = lh2_raw_data_to_counts(raw_data)
282-
camera_points = np.asarray(
283-
[
284-
calculate_camera_point(
285-
counts[0], counts[1], raw_data.locations[0].polynomial_index
286-
),
287-
calculate_camera_point(
288-
counts[0], counts[1], raw_data.locations[1].polynomial_index
289-
),
290-
],
291-
dtype=np.float64,
292-
)
293-
294-
pts_cam_new = np.hstack((camera_points, np.ones((len(camera_points), 1))))
295-
reprojected_points = np.matmul(self.calibration_data.m, pts_cam_new[0].T)
296-
297-
return DotBotLH2Position(
298-
x=reprojected_points[0] / reprojected_points[2],
299-
y=1 - reprojected_points[1] / reprojected_points[2],
300-
z=0.0,
301-
)
278+
self.logger.info("Calibration done", data=self.calibration)

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ dotbot-controller = "dotbot.controller_app:main"
7676
dotbot-edge-gateway = "dotbot.edge_gateway_app:main"
7777
dotbot-keyboard = "dotbot.keyboard:main"
7878
dotbot-joystick = "dotbot.joystick:main"
79+
dotbot-calibration-exporter = "dotbot.calibration_exporter:main"
7980

8081
[tool.ruff]
8182
lint.select = ["E", "F"]

‎setup.cfg‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ addopts = -vv -s
88
--cov-report=term-missing
99
--cov-report=xml
1010
testpaths = dotbot
11-
asyncio_default_fixture_loop_scope = "function"
11+
asyncio_default_fixture_loop_scope = function
1212

1313
[tool.black]
1414
line-length = 79

‎tox.ini‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ allowlist_externals=
4949
/bin/bash
5050
/usr/bin/bash
5151
commands=
52-
bash -exc "dotbot-controller --help > /dev/null"
52+
bash -exc "dotbot-controller --help"
53+
bash -exc "dotbot-calibration-exporter --help"
5354

5455
[testenv:web]
5556
allowlist_externals=

0 commit comments

Comments
 (0)