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.