Hi,
I am integrating PhotoScan to be a part of a larger processing chain that is based around a Matlab processor engine. The engine is already now producing/organizing all PhotoScan input files and using again the output files. Until now, processing in PhotoScan has required a lot of manual clicking including risks for user errors and producing lots of variation in outputs.
My goal was to generate with the other PhotoScan input files also a ready script file so that user would need to do only minimal (if any) clicking in PhotoScan. I have just spent better part of two working days trying to learn Python and how to do all that scripting in PhotoScan. Now I am happy to tell that I managed to get it to work.
The only thing that bugs me was that I was not able to find that good examples for this complete code, but I had to do combine it from multiple small code snippets in this forum and in the Python Reference Manual, and still add a LOT of my own head scratching in. For one thing, I would have appreciated hugely if the official Python reference manual would have contained a complete "example script" appendix with practical example how to do this all. As I found this lack of complete examples annoying/very time consuming, I now wanted to share my script with rest of the community:
# Python script for automated PhotoScan processing
# PhotoScan version 1.0.0
# DEFINE PROCESSING SETTINGS
print("---Defining processing settings...")
# Define: Home directory (later this is referred as ".")
HomeDirectory = "D:\\Temp\\PhotoScanScriptTest"
# Define: Camera calib file
CalibFile = "D:\\Temp\\AgisoftLensCameraCalib.xml"
# Define: PhotoScan Project File
PhotoScanProjectFile = ".\\PhotoScan_Project.psz"
# Define: Source images
AerialImagesPattern = ".\\Aerial_Images_8bit_tif\\*.tif"
# Define: Orientation file
OrientationFile = ".\\CameraOrientations_IMU.txt"
# Define: Cameras output file
CamerasOPKFile = ".\\CameraOrientations_PhotoScan.opk"
# Define: Coordinate system
CoordinateSystemEPSG = "EPSG::32632"
# Define: DSMResolutions (in meters),
# 0 == GSD resolution
DSMResolutions = [1.00, 0]
# Define: OrthoImageResolutions (in meters)
# 0 == GSD resolution
OrthoImageResolutions = [1.00, 0]
# Define: AlignPhotosAccuracy ["high", "medium", "low"]
AlignPhotosAccuracy = "medium"
# Define: BuildGeometryQuality ["lowest", "low", "medium", "high", "ultra"]
BuildGeometryQuality = "medium"
# Define: BuildGeometryFaces [’low’, ‘medium’, ‘high’] or exact number.
BuildGeometryFaces = "medium"
# START ACTUAL SCRIPT (DO NOT EDIT ANYTHING BELOW THIS!)
# INIT ENVIRONMENT
# import stuff
import os
import glob
import PhotoScan
import math
# Set home folder
os.chdir(HomeDirectory)
print("Home directory: " + HomeDirectory )
# get main app objects
doc = PhotoScan.app.document
app = PhotoScan.Application()
# create chunk
chunk = PhotoScan.Chunk()
chunk.label = "New_Chunk"
doc.chunks.add(chunk)
# SET COORDINATE SYSTEM
print("---Settings coordinate system...")
# init coordinate system object
CoordinateSystem = PhotoScan.CoordinateSystem()
if not(CoordinateSystem.init(CoordinateSystemEPSG)):
app.messageBox("Coordinate system EPSG code not recognized!")
# define coordinate system in chunk
chunk.crs = CoordinateSystem
chunk.projection = CoordinateSystem
# FIND ALL PHOTOS IN PATH
AerialImageFiles = glob.glob(AerialImagesPattern)
# LOAD CAMERA CALIBRATION
print("---Loading camera calibration...")
# we need to open first image to define image size
cam = PhotoScan.Camera()
cam.open(AerialImageFiles[0])
# Load sensor calib
user_calib = PhotoScan.Calibration()
if not(user_calib.load(CalibFile)):
app.messageBox("Loading of calibration file failed!")
# build sensor object
sensor = PhotoScan.Sensor()
sensor.label = "MyCamera"
sensor.width = cam.width
sensor.height = cam.height
sensor.fixed = False
sensor.user_calib = user_calib
chunk.sensors.add(sensor)
# LOAD AERIAL IMAGES
print("---Loading images...")
# load each image
for FileName in AerialImageFiles:
print("File: " + FileName)
cam = PhotoScan.Camera()
if not(cam.open(FileName)):
app.messageBox("Loading of image failed: " + FileName)
cam.label = cam.path.rsplit("/",1)[1]
cam.sensor = sensor
chunk.cameras.add(cam)
# LOAD CAMERA ORIENTATIONS
print("---Loading initial photo orientations...")
print("File: " + OrientationFile)
# build ground control
chunk.ground_control.projection = CoordinateSystem
chunk.ground_control.crs = CoordinateSystem
if not(chunk.ground_control.load(OrientationFile,"csv")):
app.messageBox("Loading of orientation file failed!")
chunk.ground_control.apply()
# SAVE PROJECT
print("---Saving project...")
print("File: " + PhotoScanProjectFile)
if not(doc.save(PhotoScanProjectFile)):
app.messageBox("Saving project failed!")
# All init done! Ready to start processing.
# ALIGN PHOTOS
print("---Aligning photos ...")
print("Accuracy: " + AlignPhotosAccuracy)
chunk.matchPhotos(accuracy=AlignPhotosAccuracy, preselection="ground control")
chunk.alignPhotos()
# SAVE PROJECT
print("---Saving project...")
print("File: " + PhotoScanProjectFile)
if not(doc.save(PhotoScanProjectFile)):
app.messageBox("Saving project failed!")
# If ground control markers are used, code should stop here and user should click them in now
# BUILD GEOMETRY
print("---Building Geometry...")
print("Quality: " + BuildGeometryQuality)
print("Faces: " + str(BuildGeometryFaces))
# THESE ROWS WERE ALTERED IN v1.0.0 UPDATE
# chunk.buildDepth(quality=BuildGeometryQuality)
# chunk.buildModel(object="height field", geometry="smooth", faces=BuildGeometryFaces)
if not(chunk.buildDenseCloud(quality=BuildGeometryQuality)):
app.messageBox("Builde Dense Cloud failed!")
if not(chunk.buildModel(surface="height field", source="dense", interpolation="enabled", faces=BuildGeometryFaces)):
app.messageBox("Build mesh model failed!")
# SAVE PROJECT
print("---Saving project...")
print("File: " + PhotoScanProjectFile)
if not(doc.save(PhotoScanProjectFile)):
app.messageBox("Saving project failed!")
# Actual processing done
# EXPORT DATA:
# EXPORT CAMERAS
print("---Exporting camera positions...")
if not(chunk.exportCameras(CamerasOPKFile, "opk", chunk.projection)):
app.messageBox("Exporting Cameras OPK failed!")
# CALCULATE THE BEST SENSIBLE PIXEL SIZES
Resolution = 0;
#if Resolution == 0:
# Get average camera altitude and focal length
N_cameras = len(chunk.cameras)
CamAlt = 0.0
focal_length = 0.0
for i in range(N_cameras):
CamAlt += chunk.ground_control.locations[chunk.cameras[i]].coord[2]
focal_length += (chunk.cameras[i].sensor.calibration.fx + chunk.cameras[0].sensor.calibration.fy) / 2
CamAlt /= N_cameras
focal_length /= N_cameras
# Get average ground altitude
N_points = len(chunk.point_cloud.points)
GroundAlt = 0.0
for i in range(N_points):
GroundAlt += chunk.point_cloud.points[i].coord.z
GroundAlt = GroundAlt / N_points
# calculate Ground sampling distance
GroundSamplingDistance = math.fabs(CamAlt-GroundAlt) / focal_length
# round it to next millimeter
GroundSamplingDistance = math.ceil(GroundSamplingDistance * 1000)/1000
# EXPORT DSM(S)
print("---Exporting Digital Surface Model(s)...")
for Resolution in DSMResolutions:
if Resolution == 0:
Resolution = GroundSamplingDistance
FileName = ".\\DSM_PixelSize=" + str(Resolution) + "m.tif"
print("File: " + FileName)
if not(chunk.exportDem(FileName, format="tif", dx=Resolution, dy=Resolution, projection=chunk.projection)):
app.messageBox("Exporting DSM failed: " + FileName)
# EXPORT ORTHOIMAGE(S)
print("---Exporting Orthoimage(s)...")
for Resolution in OrthoImageResolutions:
if Resolution == 0:
Resolution = GroundSamplingDistance
FileName = ".\\OrthoImage_PixelSize=" + str(Resolution) + "m.tif"
print("File: " + FileName)
if not(chunk.exportOrthophoto(FileName, format="tif", projection=chunk.projection, blending="mosaic", dx=Resolution, dy=Resolution)):
app.messageBox("Exporting orthoimage failed: " + FileName)
# SAVE PROJECT
print("---Saving project...")
print("File: " + PhotoScanProjectFile)
if not(doc.save(PhotoScanProjectFile)):
app.messageBox("Saving project failed!")
# Close photoscan
# app.quit()
Instructions for fellow Python-virgins: copy-paste the script to notepad, edit the files/folders to your own ones, save file as "PythoScript.py" (or anything you want), and run it in PhotoScan using "Tools>Run Script..."
And here is an example of the
CameraOrientations_IMU.txt file:
#CoordinateSystem: WGS84_UTM_Zone_32N(EPSG:32632)
#FileName EastingX NorthingY Altitude
P1440733_8bit.tif 304323.409 5740634.830 79.841
P1440734_8bit.tif 304323.758 5740635.029 79.597
P1440735_8bit.tif 304322.964 5740633.880 78.496
P1440736_8bit.tif 304322.164 5740632.638 79.890
Alexey:
(1) If you want, feel free to add any parts of this script to the Python Reference manual.
(2) Also as requested already in an earlier topic, I would love to be able to run this script from OS command line for example with syntax:
> photoscan.exe -RunScript "D:\Temp\PhotoScanScriptTest\PythonScript.py"...and preferably even with option to disable/hide whole user interface (so that there is no chance that the user disturbs the running of the script, for example by pressing cancel in some dialog):
> photoscan.exe -RunScriptHidden "D:\Temp\PhotoScanScriptTest\PythonScript.py"