Export Individual Orthophotos.py
From Agisoft
#Batch export of orthophotos based on individual cameras or user selected cameras
#creates custom menu item
#compatibility Agisoft PhotoScan Pro 1.1.0
#no arguments required
import os
import time
import random
import PhotoScan
from PySide import QtCore, QtGui
def intersect(p0, pn, l0, l):
d = ((p0 - l0) * pn) / (l * pn)
return d * l + l0
class ExportOrthoDlg(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
self.blend_types = {"Average": PhotoScan.BlendingMode.AverageBlending, "Mosaic": PhotoScan.BlendingMode.MosaicBlending, "Min intensity": PhotoScan.BlendingMode.MinBlending, "Max Intensity": PhotoScan.BlendingMode.MaxBlending}
self.setWindowTitle("Export individual orthophotos")
self.btnQuit = QtGui.QPushButton("Quit")
self.btnQuit.setFixedSize(130,50)
self.btnP1 = QtGui.QPushButton("Export")
self.btnP1.setFixedSize(130,50)
self.pBar = QtGui.QProgressBar()
self.pBar.setTextVisible(False)
self.pBar.setFixedSize(150, 50)
self.resTxt = QtGui.QLabel()
self.resTxt.setText("Export resolution (m/pix):")
self.resTxt.setFixedSize(130, 25)
self.blendTxt = QtGui.QLabel()
self.blendTxt.setText("Blending mode:")
self.blendTxt.setFixedSize(130, 25)
self.blendCmb = QtGui.QComboBox() #texture type values
self.blendCmb.setFixedSize(100, 25)
for type in self.blend_types.keys():
self.blendCmb.addItem(type)
self.resEdt = QtGui.QLineEdit()
self.resEdt.setPlaceholderText("export resolution (m/pix), e.g 0.01")
self.resEdt.setFixedSize(100, 25)
self.selTxt = QtGui.QLabel()
self.selTxt.setText("Export for:")
self.selTxt.setFixedSize(100, 25)
self.radioBtn_all = QtGui.QRadioButton("all cameras")
self.radioBtn_sel = QtGui.QRadioButton("selected cameras")
self.radioBtn_rnd = QtGui.QRadioButton("random 10 cameras")
self.radioBtn_all.setChecked(True)
self.radioBtn_rnd.setChecked(False)
self.radioBtn_sel.setChecked(False)
layout = QtGui.QGridLayout() #creating layout
layout.addWidget(self.resTxt, 0, 1)
layout.addWidget(self.resEdt, 0, 2)
layout.addWidget(self.blendTxt, 1, 1)
layout.addWidget(self.blendCmb, 1, 2)
layout.addWidget(self.selTxt, 0, 0)
layout.addWidget(self.radioBtn_all, 1, 0)
layout.addWidget(self.radioBtn_sel, 2, 0)
layout.addWidget(self.radioBtn_rnd, 3, 0)
layout.addWidget(self.btnP1, 4, 1)
layout.addWidget(self.btnQuit, 4, 2)
layout.addWidget(self.pBar, 3, 0, 5, 1)
self.setLayout(layout)
proc_exp = lambda : self.exp_ortho()
QtCore.QObject.connect(self.btnP1, QtCore.SIGNAL("clicked()"), proc_exp)
QtCore.QObject.connect(self.btnQuit, QtCore.SIGNAL("clicked()"), self, QtCore.SLOT("reject()"))
self.exec()
def surf_height(self, chunk, photo):
points_h = list()
point_cloud = chunk.point_cloud
points = point_cloud.points
npoints = len(points)
num_valid = 0
point_index = 0
for proj in point_cloud.projections[photo]:
track_id = proj.track_id
while point_index < npoints and points[point_index].track_id < track_id:
point_index += 1
if point_index < npoints and points[point_index].track_id == track_id:
if not points[point_index].valid:
continue
v = points[point_index].coord
vt = chunk.transform.matrix.mulp(v)
if chunk.crs:
vt = chunk.crs.project(vt)
points_h.append(vt[2])
num_valid += 1
points_h.sort()
height = points_h[num_valid // 2]
return height
def exp_ortho(self):
doc = PhotoScan.app.document
chunk = doc.chunk
path = doc.path.rsplit("\\", 1)[0]
if not chunk.model:
PhotoScan.app.messageBox("No mesh generated!\n")
return False
try:
resolution = float(self.resEdt.text())
except(ValueError):
PhotoScan.app.messageBox("Incorrect export resolution! Please use point delimiter.\n")
print("Script aborted.")
return False
print("Export started...") #information message
self.btnP1.setDisabled(True)
self.btnQuit.setDisabled(True)
self.pBar.setMinimum(0)
self.pBar.setMaximum(100)
export_list = list()
if self.radioBtn_sel.isChecked():
for photo in chunk.cameras:
if photo.selected:
export_list.append(photo)
elif self.radioBtn_all.isChecked():
export_list = list(chunk.cameras)
elif self.radioBtn_rnd.isChecked():
random_cams = random.sample(range(len(chunk.cameras)), 10) #number of random cameras
for i in range (0, p_num):
export_list.append(chunk.cameras[random_cams[i]])
for photo in chunk.cameras:
photo.enabled = False
blending_mode = self.blend_types[self.blendCmb.currentText()]
processed = 0
t0 = time.time()
for i in range (0, len(chunk.cameras)):
photo = chunk.cameras[i]
photo.enabled = False
PhotoScan.app.update()
for photo in export_list:
if not photo.transform:
continue
x0 = x1 = x2 = x3 = PhotoScan.Vector((0.0,0.0,0.0))
width = photo.sensor.width
height = photo.sensor.height
calibration = photo.sensor.calibration
# vectors corresponding to photo corners
v0 = PhotoScan.Vector(( -calibration.cx / calibration.fx, -calibration.cy / calibration.fy, 1))
v1 = PhotoScan.Vector(( (width - calibration.cx) / calibration.fx, -calibration.cy / calibration.fy, 1))
v2 = PhotoScan.Vector(( -calibration.cx / calibration.fx, (height - calibration.cy) / calibration.fy, 1))
v3 = PhotoScan.Vector(( (width - calibration.cx) / calibration.fx, (height - calibration.cy) / calibration.fy, 1))
vc = photo.center
v0.size = v1.size = v2.size = v3.size = vc.size = 4
v0[3] = v1[3] = v2[3] = v3[3] = 0
vc[3] = 1
M = chunk.transform.matrix * photo.transform
v0_gc = M * v0
v1_gc = M * v1
v2_gc = M * v2
v3_gc = M * v3
vc_gc = chunk.transform.matrix * vc
v0_gc.size = v1_gc.size = v2_gc.size = v3_gc.size = vc_gc.size = 3
# surface normal
cen_p = photo.center
cen_t = chunk.transform.matrix.mulp(cen_p)
if chunk.crs:
cen_t = chunk.crs.project(cen_t)
h = self.surf_height(chunk, photo)
vloc = PhotoScan.Vector((cen_t[0], cen_t[1], h))
vloc_h = PhotoScan.Vector((cen_t[0], cen_t[1], h))
vloc_h[2] += 1
if chunk.crs:
vloc_gc = chunk.crs.unproject(vloc)
vloc_h_gc = chunk.crs.unproject(vloc_h)
surf_n = vloc_h_gc - vloc_gc
else:
vloc_gc = vloc
vloc_h_gc = vloc_h
surf_n = vloc_h - vloc
surf_n.normalize()
v0_gc.normalize()
v1_gc.normalize()
v2_gc.normalize()
v3_gc.normalize()
#intersection with the surface
x0 = intersect(vloc_gc, surf_n, vc_gc, v0_gc)
x1 = intersect(vloc_gc, surf_n, vc_gc, v1_gc)
x2 = intersect(vloc_gc, surf_n, vc_gc, v2_gc)
x3 = intersect(vloc_gc, surf_n, vc_gc, v3_gc)
if chunk.crs:
x0 = chunk.crs.project(x0)
x1 = chunk.crs.project(x1)
x2 = chunk.crs.project(x2)
x3 = chunk.crs.project(x3)
x_0 = min(x0[0], x1[0], x2[0], x3[0])
x_1 = max(x0[0], x1[0], x2[0], x3[0])
y_0 = min(x0[1], x1[1], x2[1], x3[1])
y_1 = max(x0[1], x1[1], x2[1], x3[1])
x_0 -= (x_1 - x_0) / 20.
x_1 += (x_1 - x_0) / 20.
y_0 -= (y_1 - y_0) / 20.
y_1 += (y_1 - y_0) / 20.
reg = (x_0, y_0, x_1, y_1)
photo.enabled = True
PhotoScan.app.update()
p_name = photo.photo.path.rsplit("/", 1)[1].rsplit(".",1)[0]
p_name = "ortho_" + p_name
if chunk.crs:
proj = chunk.crs ##export in chunk coordinate system
else:
proj = PhotoScan.Matrix().diag([1,1,1,1]) #TopXY
d_x = d_y = resolution
#recalculating WGS84 resolution from degrees into meters if required
if chunk.crs:
if not ('PROJCS' in proj.wkt):
crd = photo.reference.location
#longitude
v1 = PhotoScan.Vector((crd[0], crd[1], 0) )
v2 = PhotoScan.Vector((crd[0] + 0.001, crd[1], 0))
vm1 = chunk.crs.unproject(v1)
vm2 = chunk.crs.unproject(v2)
res_x = (vm2 - vm1).norm() * 1000
#latitude
v2 = PhotoScan.Vector( (crd[0], crd[1] + 0.001, 0))
vm2 = chunk.crs.unproject(v2)
res_y = (vm2 - vm1).norm() * 1000
pixel_x = pixel_y = resolution #export resolution (meters/pix)
d_x = pixel_x / res_x
d_y = pixel_y / res_y
if chunk.exportOrthophoto(path + "\\" + p_name + ".tif", format = "tif", blending = blending_mode, color_correction = False, projection = proj, region = reg, dx = d_x, dy = d_y, write_world = True):
processed +=1
photo.enabled = False
self.pBar.setValue(int(processed / len(export_list) * 100))
for i in range (0, len(chunk.cameras)):
photo = chunk.cameras[i]
photo.enabled = True
PhotoScan.app.update()
self.btnP1.setDisabled(False)
self.btnQuit.setDisabled(False)
t1 = time.time()
t1 -= t0
t1 = int(t1)
PhotoScan.app.messageBox("Processing finished.\nProcessed "+ str(processed) +" images to orthophotos.\nProcessing time: "+ str(t1) +" seconds.\nPress OK.") #information message
return 1
def main():
global doc
doc = PhotoScan.app.document
app = QtGui.QApplication.instance()
parent = app.activeWindow()
dlg = ExportOrthoDlg(parent)
PhotoScan.app.addMenuItem("Custom/Export individual orthophotos", main)