Quick layout.py
From Agisoft
#created by GeoScan Ltd. (http://geoscan.aero) import PhotoScan as ps import math import os, sys import time try: from PySide2.QtGui import * from PySide2.QtCore import * from PySide2.QtWidgets import * except ImportError: from PySide.QtGui import * from PySide.QtCore import * import copy def time_measure(func): def wrapper(*args, **kwargs): t1 = time.time() res = func(*args, **kwargs) t2 = time.time() print("Finished processing in {} sec.".format(t2 - t1)) return res return wrapper def delta_vector_to_chunk(v1, v2): chunk = ps.app.document.chunk v1 = chunk.crs.unproject(v1) v2 = chunk.crs.unproject(v2) v1 = chunk.transform.matrix.inv().mulp(v1) v2 = chunk.transform.matrix.inv().mulp(v2) z = v2 - v1 z.normalize() return z def get_chunk_vectors(lat, lon): z = delta_vector_to_chunk(ps.Vector([lon, lat, 0]), ps.Vector([lon, lat, 1])) y = delta_vector_to_chunk(ps.Vector([lon, lat, 0]), ps.Vector([lon + 0.001, lat, 0])) x = delta_vector_to_chunk(ps.Vector([lon, lat, 0]), ps.Vector([lon, lat+0.001, 0])) return x,y,-z def wgs_to_chunk(chunk, point): return chunk.transform.matrix.inv().mulp(chunk.crs.unproject(point)) def show_message(msg): msgBox = QMessageBox() print(msg) msgBox.setText(msg) msgBox.exec() def check_chunk(chunk): if chunk is None or len(chunk.cameras) == 0: show_message("Empty chunk!") return False if chunk.crs is None: show_message("Initialize chunk coordinate system first") return False return True # returns distance estimation between two cameras in chunk def get_photos_delta(chunk): mid_idx = int(len(chunk.cameras) / 2) if mid_idx == 0: return ps.Vector([0,0,0]) c1 = chunk.cameras[:mid_idx][-1] c2 = chunk.cameras[:mid_idx][-2] print(c1.reference.location) print(c2.reference.location) offset = c1.reference.location - c2.reference.location for i in range(len(offset)): offset[i] = math.fabs(offset[i]) return offset def get_chunk_bounds(chunk): min_latitude = min(c.reference.location[1] for c in chunk.cameras if c.reference.location is not None) max_latitude = max(c.reference.location[1] for c in chunk.cameras if c.reference.location is not None) min_longitude = min(c.reference.location[0] for c in chunk.cameras if c.reference.location is not None) max_longitude = max(c.reference.location[0] for c in chunk.cameras if c.reference.location is not None) offset = get_photos_delta(chunk) offset_factor = 2 delta_latitude = offset_factor * offset.y delta_longitude = offset_factor * offset.x min_longitude -= delta_longitude max_longitude += delta_longitude min_latitude -= delta_latitude max_latitude += delta_latitude return min_latitude, min_longitude, max_latitude, max_longitude # Evaluates rotation matrices for cameras that have location # algorithm is straightforward: we assume copter has zero pitch and roll, # and yaw is evaluated from current copter direction # current direction is evaluated simply subtracting location of # current camera from the next camera location # i and j are unit axis vectors in chunk coordinate system # i || North def estimate_rotation_matrices(chunk, i, j): groups = copy.copy(chunk.camera_groups) groups.append(None) for group in groups: group_cameras = list(filter(lambda c: c.group == group, chunk.cameras)) if len(group_cameras) == 0: continue if len(group_cameras) == 1: if group_cameras[0].reference.rotation is None: group_cameras[0].reference.rotation = ps.Vector([0,0,0]) continue for idx, c in enumerate(group_cameras[0:-1]): next_camera = group_cameras[idx+1] if c.reference.rotation is None: if c.reference.location is None or next_camera.reference.location is None: continue direction = delta_vector_to_chunk(c.reference.location, next_camera.reference.location) cos_yaw = direction * j yaw = math.degrees(math.acos(cos_yaw)) + 90 # TODO not sure about this offset if direction * i > 0: yaw = -yaw c.reference.rotation = ps.Vector([yaw, 0, 0]) group_cameras[-1].reference.rotation = group_cameras[-2].reference.rotation @time_measure def align_cameras(chunk, min_latitude, min_longitude): if chunk.transform.scale is None: chunk.transform.scale = 1 chunk.transform.rotation = ps.Matrix([[1,0,0], [0,1,0], [0,0,1]]) chunk.transform.translation = ps.Vector([0,0,0]) i, j, k = get_chunk_vectors(min_latitude, min_longitude) # i || North estimate_rotation_matrices(chunk, i, j) for c in chunk.cameras: if c.transform is not None: continue location = c.reference.location if location is None: continue chunk_coordinates = wgs_to_chunk(chunk, location) fi = c.reference.rotation.x + 90 fi = math.radians(fi) roll = math.radians(c.reference.rotation.z) pitch = math.radians(c.reference.rotation.y) roll_mat = ps.Matrix([[1, 0, 0], [0, math.cos(roll), -math.sin(roll)], [0, math.sin(roll), math.cos(roll)]]) pitch_mat = ps.Matrix([[math.cos(pitch), 0, math.sin(pitch)], [0, 1, 0], [-math.sin(pitch), 0, math.cos(pitch)]]) yaw_mat = ps.Matrix([[math.cos(fi), -math.sin(fi), 0], [math.sin(fi), math.cos(fi), 0], [0, 0, 1]]) r = roll_mat * pitch_mat * yaw_mat ii = r[0, 0] * i + r[1, 0] * j + r[2, 0] * k jj = r[0, 1] * i + r[1, 1] * j + r[2, 1] * k kk = r[0, 2] * i + r[1, 2] * j + r[2, 2] * k c.transform = ps.Matrix([[ii.x, jj.x, kk.x, chunk_coordinates[0]], [ii.y, jj.y, kk.y, chunk_coordinates[1]], [ii.z, jj.z, kk.z, chunk_coordinates[2]], [0, 0, 0, 1]]) def run_camera_alignment(): doc = ps.app.document chunk = doc.chunk if not check_chunk(chunk): return min_latitude, min_longitude, max_latitude, max_longitude = get_chunk_bounds(chunk) try: align_cameras(chunk, min_latitude, min_longitude) except Exception as e: print(e) ps.app.addMenuItem("Plugins" + "/" + "Apply Vertical Camera Alignment", run_camera_alignment)