Hi again,
This concept turned out to be really useful for me, to evaluate marker quality in a particularly problematic dataset of mine. Therefore, I made some improvements with the code.
First off, it iterates through every possible combination of image pairs to project it, instead of choosing pairs at random, and then saves the standard deviations of X, Y, Z and Total, respectively. The output csv also notes how many iterations it handled, which should be close to n! of how many projections there are (up to the maximum limit).
import PhotoScan
import numpy as np
import itertools
import random
doc = PhotoScan.app.document
chunk = doc.chunk
max_iterations = 200 # Max allowed iterations for one marker
result = []
for marker in chunk.markers:
num_projections = len(marker.projections)
positions = []
if num_projections > 2 and marker.type == PhotoScan.Marker.Type.Regular: # Marker needs more than two projections to evaluate error, and not be a fiducial
cam_list = [cam for cam in marker.projections.keys() if cam.center] # Every aligned camera with projections
random.shuffle(cam_list) # Needed if the max_iterations is exceeded
count = 0
for a, b in itertools.combinations(cam_list, 2): # Testing pairs of every possible combination
if a.group and b.group and a.group == b.group and a.group.type == PhotoScan.CameraGroup.Type.Station: # Skip if the cameras share station group
continue
if count >= max_iterations: # Break if it reaches the iteration limit
break
count += 1
selected_cameras = [a, b]
# Note pinned pixel coordinates and if pinned or not (green or blue)
px_coords = {camera: (marker.projections[camera].coord, marker.projections[camera].pinned) for camera in cam_list}
# Unpinning the non-selected cameras
for camera in cam_list:
if camera not in selected_cameras:
marker.projections[camera] = None
# Save the estimated position
positions.append(list(chunk.crs.project(chunk.transform.matrix.mulp(marker.position))))
# Revert pinned coordinates
for camera in cam_list:
coord, pinned = px_coords[camera]
marker.projections[camera] = PhotoScan.Marker.Projection(coord)
marker.projections[camera].pinned = pinned
iterations = len(positions) # Amount of tested positions
positions = np.array(positions)
std = np.std(positions, axis=0) # Standard deviation
rms = (np.sqrt(np.mean(std**2))) # RMS of standard deviation
result.append((marker.label,) + tuple(std) + (rms, iterations))
# Write a CSV at desired position
file_name = PhotoScan.app.getSaveFileName("Save output file", filter="*.csv")
if file_name: # If an input was given
with open(file_name, "w") as file:
file.write("Label, X, Y, Z, Total, Iterations\n")
for line in result:
entry = ""
for value in line:
entry += str(value).replace("'", "") + ","
file.write(entry + "\n")
It turned out that some of my markers were really poorly placed, which with this tool is incredibly apparent. So thanks, in a way!
Regards,
Erik
EDIT: The script makes PhotoScan freeze for me sometimes, yet it works perfectly after a restart... Don't know what that's about.