"""Self-check for tests/photogrammetry_fixtures.py
Run: pytest tests/test_photogrammetry_fixtures.py --no-cov
"""
import json
import urllib.request
import cv2
import numpy as np
from core.azure.survey_corners import track_from_srt
from tests.photogrammetry_fixtures import ( # noqa: F401 (fixtures are imported for pytest)
encode_multipart,
fake_nodeodm,
frame_overlap_fraction,
ground,
ground_truth_orthomosaic,
no_gps_tour,
two_tours,
union_bbox_px,
)
def _post(url, fields, files=()):
body, content_type = encode_multipart(fields, list(files))
request = urllib.request.Request(url, data=body, headers={"Content-Type": content_type}, method="POST")
return json.load(urllib.request.urlopen(request))
def _get_json(url):
return json.load(urllib.request.urlopen(url))
def test_tours_have_the_configured_overlap_and_share_area(ground, two_tours):
a, b = two_tours
assert len(a.frames) > 10 and len(b.frames) > 10
# consecutive frames along a flight line overlap ~75 % (the default forward overlap)
assert abs(frame_overlap_fraction(a.frames[0], a.frames[1]) - 0.75) < 0.05
# the two tours overlap each other (shared flight lines), so one mosaic can be built from both
assert any(frame_overlap_fraction(fa, fb) > 0.3 for fa in a.frames for fb in b.frames)
x0, y0, w, h = union_bbox_px([a, b])
assert ground_truth_orthomosaic(ground, [a, b]).shape[:2] == (h, w)
def test_tour_writes_frames_srt_and_manifest_that_survey_corners_can_parse(two_tours, no_gps_tour, tmp_path):
a, _ = two_tours
paths = a.write(tmp_path / "a")
assert len(paths) == len(a.frames) and (tmp_path / "a" / "frame1.jpg").exists()
manifest = json.loads((tmp_path / "a" / "manifest.json").read_text())
assert [m["frame_id"] for m in manifest] == [f.frame_id for f in a.frames]
track = track_from_srt(str(tmp_path / "a" / "tourA.SRT")) # the real parser used by /corners
assert track is not None and len(track) == len(a.frames)
assert np.ptp(track[:, 0]) > 50 # ~112 m east span
no_gps_tour.write(tmp_path / "n")
assert not list((tmp_path / "n").glob("*.SRT")) # no telemetry for the no-GPS tour
assert all(m["lat"] is None for m in json.loads((tmp_path / "n" / "manifest.json").read_text()))
def test_frame_lat_lon_matches_its_pixel_position(ground, two_tours):
f = two_tours[0].frames[0]
gt = ground.geotransform()
cx, cy = f.bbox_px[0] + f.bbox_px[2] / 2, f.bbox_px[1] + f.bbox_px[3] / 2
assert abs(f.lat - (gt["origin_lat"] + cy * gt["lat_per_px"])) < 1e-8
assert abs(f.lon - (gt["origin_lon"] + cx * gt["lon_per_px"])) < 1e-8
def test_fake_nodeodm_task_lifecycle(ground, two_tours, fake_nodeodm):
a, b = two_tours
x0, y0, w, h = union_bbox_px([a, b])
options = json.dumps([{"name": "fast-orthophoto", "value": True}])
task = _post(fake_nodeodm.url + "/task/new", {"name": "t", "options": options},
[("images", fr.filename, b"x") for fr in a.frames[:3]])["uuid"]
assert fake_nodeodm.tasks[task].images == [fr.filename for fr in a.frames[:3]]
codes = [_get_json(f"{fake_nodeodm.url}/task/{task}/info")["status"]["code"] for _ in range(4)]
assert codes == [10, 20, 20, 40] # queued, running, running, completed
png = urllib.request.urlopen(f"{fake_nodeodm.url}/task/{task}/download/orthophoto.tif").read()
mosaic = cv2.imdecode(np.frombuffer(png, np.uint8), cv2.IMREAD_COLOR)
assert mosaic.shape[:2] == (h, w) # the ground-truth orthomosaic
shots = _get_json(f"{fake_nodeodm.url}/task/{task}/download/shots.geojson")
assert len(shots["features"]) == len(a.frames) + len(b.frames)
_post(fake_nodeodm.url + "/task/cancel", {"uuid": task})
assert _get_json(f"{fake_nodeodm.url}/task/{task}/info")["status"]["code"] == 50 # cancelled
No comments:
Post a Comment