gengxin
This commit is contained in:
1 parent
714a2e1db2
commit
f7a782dcef
5 files changed
+584
-73
No files matched your search
@@ -1,11 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from contextlib import redirect_stderr
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import interactive_calibrator as wizard
|
||||
import scan_calibrator as core
|
||||
|
||||
|
||||
@@ -107,5 +112,132 @@ class ApiRecoveryTests(unittest.TestCase):
|
||||
self.assertEqual(move_mock.call_count, 2)
|
||||
|
||||
|
||||
class InteractiveWizardTests(unittest.TestCase):
|
||||
def test_manual_ptz_accepts_annotated_display_format(self):
|
||||
self.assertEqual(
|
||||
wizard.parse_ptz_input(
|
||||
"pan=21720 (217.20°), tilt=1268 (12.68°), "
|
||||
"zoom=4580 (45.80×)"
|
||||
),
|
||||
{"pan": 21720, "tilt": 1268, "zoom": 4580},
|
||||
)
|
||||
|
||||
def test_manual_ptz_rejects_incomplete_input(self):
|
||||
with self.assertRaisesRegex(ValueError, "pan=21720"):
|
||||
wizard.parse_ptz_input("pan=21720, tilt=1268")
|
||||
|
||||
@mock.patch("interactive_calibrator.core.print_ptz")
|
||||
@mock.patch("interactive_calibrator.camera_ptz")
|
||||
@mock.patch("interactive_calibrator.read_zoom_after_move")
|
||||
def test_unreachable_average_uses_nearest_higher_zoom(
|
||||
self, move_mock, camera_mock, _print_mock
|
||||
):
|
||||
origin = {"pan": 23052, "tilt": 1298, "zoom": 4510}
|
||||
camera_mock.return_value = origin
|
||||
|
||||
def quantized(_origin, requested):
|
||||
return {
|
||||
**origin,
|
||||
"zoom": 4550 if requested <= 4565 else 4580,
|
||||
}
|
||||
|
||||
move_mock.side_effect = quantized
|
||||
actual, average = wizard.move_to_scan_zoom({
|
||||
"top-left": {**origin, "zoom": 4550},
|
||||
"bottom-right": {**origin, "zoom": 4580},
|
||||
})
|
||||
|
||||
self.assertEqual(average, 4565)
|
||||
self.assertEqual(actual["zoom"], 4580)
|
||||
self.assertEqual(move_mock.call_args_list[0].args[1], 4565)
|
||||
self.assertEqual(move_mock.call_args_list[-1].args[1], 4580)
|
||||
|
||||
@mock.patch("interactive_calibrator.core.print_ptz")
|
||||
@mock.patch("interactive_calibrator.camera_ptz")
|
||||
@mock.patch("interactive_calibrator.read_zoom_after_move")
|
||||
def test_step_four_always_sends_zoom_even_when_already_equal(
|
||||
self, move_mock, camera_mock, _print_mock
|
||||
):
|
||||
origin = {"pan": 100, "tilt": 200, "zoom": 4580}
|
||||
camera_mock.return_value = origin
|
||||
move_mock.return_value = origin
|
||||
|
||||
actual, _average = wizard.move_to_scan_zoom({
|
||||
"top-left": origin,
|
||||
"bottom-right": origin,
|
||||
})
|
||||
|
||||
self.assertEqual(actual["zoom"], 4580)
|
||||
move_mock.assert_called_once_with(origin, 4580)
|
||||
|
||||
def test_partial_session_is_saved_to_history(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
history = root / "history"
|
||||
state_file = root / "calibration.json"
|
||||
with (
|
||||
mock.patch.object(wizard, "HISTORY_DIR", history),
|
||||
mock.patch.object(wizard, "ACTIVE_HISTORY_FILE", None),
|
||||
mock.patch.object(core, "STATE_FILE", state_file),
|
||||
):
|
||||
state = wizard.start_history_session("hanging", None)
|
||||
state["points"]["top-left"] = {
|
||||
"pan": 21720,
|
||||
"tilt": 1268,
|
||||
"zoom": 4580,
|
||||
}
|
||||
wizard.save_working_state(state)
|
||||
|
||||
records = wizard.load_history_records()
|
||||
|
||||
self.assertEqual(len(records), 1)
|
||||
self.assertTrue(
|
||||
wizard.state_item_exists(records[0][1], "points", "top-left")
|
||||
)
|
||||
self.assertFalse(
|
||||
wizard.state_item_exists(records[0][1], "points", "bottom-right")
|
||||
)
|
||||
|
||||
|
||||
class GenerateZoomTests(unittest.TestCase):
|
||||
def test_generate_uses_camera_reachable_scan_zoom(self):
|
||||
state = {
|
||||
"points": {
|
||||
"top-left": {"pan": 1000, "tilt": 200, "zoom": 4550},
|
||||
"bottom-right": {"pan": 900, "tilt": 100, "zoom": 4580},
|
||||
},
|
||||
"steps": {
|
||||
"pan": {
|
||||
"value": 50,
|
||||
"overlap": 0.04,
|
||||
"target_overlap": 0.04,
|
||||
"overlap_method": "axis_shift_v1_retargeted",
|
||||
"inliers": 50,
|
||||
"visual_direction": "decreasing",
|
||||
},
|
||||
"tilt": {
|
||||
"value": 50,
|
||||
"overlap": 0.04,
|
||||
"target_overlap": 0.04,
|
||||
"overlap_method": "axis_shift_v1_retargeted",
|
||||
"inliers": 50,
|
||||
},
|
||||
},
|
||||
"scan_zoom": 4580,
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
with (
|
||||
mock.patch.object(core, "ROOT", root),
|
||||
mock.patch.object(core, "load_state", return_value=state),
|
||||
):
|
||||
with mock.patch("sys.stdout", new=io.StringIO()):
|
||||
core.command_generate(argparse.Namespace(mount="desktop"))
|
||||
result = next((root / "result").glob("scan_config_*.json"))
|
||||
generated = json.loads(result.read_text(encoding="utf-8"))
|
||||
|
||||
self.assertEqual(generated["scan"]["zoom"], 4580)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user