Reference preselection¶
Status: documented (2026-05-24). Synthesises forum-attested mechanism + introspection-confirmed API surface. The Sequential-mode ±24-frame window is empirically verified on Metashape 2.2.3 (2026-07-10).
What it is¶
Reference preselection is an option of Match Photos / Align
Photos that uses the cameras' reference-pane coordinates (or
estimated coordinates from a prior alignment, or sequential
image order) to pick which image pairs the matcher should
consider. Without preselection — or with generic_preselection
alone — the matcher considers every camera against every other,
which scales O(N²) and quickly becomes the dominant cost on
large surveys.
The mechanism, attested verbatim (PhotoScan 1.2 era):
"Reference preselection in the actual version (1.2) uses the coordinate information trying to match the photos (using only certain number of neighbors according to the camera centers coordinates) also the Generic preselection method is applied to those neighbors. [...] In the next version we've made the Generic and Reference option separated adding new preselection mode (reference without generic)." — Alexey Pasumansky, 2016-12-20, PhotoScan 1.2 (permalink)
In other words, reference preselection is a two-stage process in modern Metashape:
- Coordinate-based shortlist. Pick the N nearest neighbours of each camera by reference-pane coordinates.
- Optional generic preselection on the shortlist. If
generic_preselection=True, run the additional feature-based pair selection within the shortlist.
The 1.3-era separation introduced
ReferencePreselectionMode as an enum so that callers could
opt into reference-only preselection (no generic) when the
references are trustworthy enough that feature-based pair
filtering becomes redundant overhead.
Where it lives¶
| Standard | Pro | |
|---|---|---|
| GUI | Workflow → Align Photos → check Reference preselection | (same) |
| Python | Metashape.Chunk.matchPhotos(reference_preselection=True, reference_preselection_mode=…) |
(Pro only) |
| Available since | PhotoScan 1.2 | PhotoScan 1.2 |
| Mode-enum separation | PhotoScan 1.3+ | PhotoScan 1.3+ |
The three preselection modes¶
Metashape.ReferencePreselectionMode enumerates three sources
for the coordinate-based shortlist (introspection-confirmed on
Metashape 2.2.2):
| Mode | What it uses | Typical scenario |
|---|---|---|
ReferencePreselectionSource (default) |
The values in camera.reference.location (and .rotation, if loaded). These are the user-supplied / EXIF-derived reference coordinates. |
Drone surveys with GPS-tagged images; aerial flights where every image has a position. |
ReferencePreselectionEstimated |
The values in camera.transform from a previous alignment. Not the reference-pane values. |
Re-running matching after an initial alignment, where the bundle-adjusted positions are more accurate than the GPS reference. |
ReferencePreselectionSequential |
Image-order pairs: each camera i with the 24 preceding and 24 following frames (a fixed ±24 window — see below). No coordinates required. | Video frames, drone fast-flights where the ordered sequence approximates spatial proximity, time-lapse from a moving rig. |
The default is ReferencePreselectionSource — the user manual's
expected behaviour for a typical aerial workflow.
Python API¶
import Metashape
chunk = Metashape.app.document.chunk
# Default: reference preselection ON, mode = Source.
chunk.matchPhotos(
downscale=1,
generic_preselection=True, # run generic on the shortlist
reference_preselection=True,
reference_preselection_mode=Metashape.ReferencePreselectionSource,
keypoint_limit=40000,
tiepoint_limit=4000,
)
# Reference-only mode (no generic on the shortlist).
chunk.matchPhotos(
reference_preselection=True,
reference_preselection_mode=Metashape.ReferencePreselectionSource,
generic_preselection=False, # disable generic; trust the references
...
)
# Estimated mode — for a second matching pass after initial alignment.
chunk.matchPhotos(
reference_preselection=True,
reference_preselection_mode=Metashape.ReferencePreselectionEstimated,
keep_keypoints=True,
reset_matches=False,
...
)
# Sequential mode — for video / time-ordered captures.
chunk.matchPhotos(
reference_preselection=True,
reference_preselection_mode=Metashape.ReferencePreselectionSequential,
...
)
When to enable / disable¶
Enable reference preselection when¶
- Camera positions are reliable (drone GPS, surveyed positions, laboratory-known geometry).
- The dataset is large enough that O(N²) all-pairs matching would be slow (typically > ~200–500 images).
- You want to constrain the bundle to spatially-plausible matches, reducing the chance of false-match catastrophes (e.g., two images of the same texture in different parts of the scene becoming spuriously matched).
Disable reference preselection when¶
- Camera positions are missing, wildly inaccurate, or unavailable. Reference preselection with bad references prunes correct pairs → forces alignment to fail.
- You're debugging an alignment failure (per the canonical "clean" recipe; see Diagnosing under-aligned chunks) — disabling preselection lets the matcher find pairs generic-preselection identifies as similar even when the references would have excluded them.
- The dataset has a deliberately-symmetric layout where
reference preselection would mistakenly identify
geometrically-symmetric viewpoints as candidate matches
(see Synthetic priors and
ReferencePreselectionSource).
How wide is the "neighbour" search¶
The default neighbour-count for the coordinate-based shortlist
is not exposed as a Python kwarg. The PhotoScan 1.2-era
description above is "a certain number of neighbors" without
specifying the number; subsequent forum discussion (and the
GUI's Reference accuracy setting indirectly) suggests the
search radius scales with the user-supplied
location_accuracy. A camera with location_accuracy = 50 m
will be matched against more candidates than one with
location_accuracy = 1 m.
A follow-up reply raised the open question of how robust the algorithm is to mis-localised priors:
"I had a hunch it might do that after it aligned some images in a previous project where the coordinates were completely wrong in some parts." — James, 2016-12-20, PhotoScan 1.2 (permalink)
The thread doesn't resolve the question. Empirically, reference preselection with bad coordinates can either fail outright (no candidate pairs include the correct match) or succeed serendipitously (correct match happens to be in the shortlist anyway). The diagnostic is to disable reference preselection and re-run; if alignment improves, the references are bad.
Sequential mode: a fixed ±24-frame window¶
The coordinate-based shortlist above applies to Source and
Estimated modes. Sequential mode is different: it ignores
coordinates entirely and pairs each image with a fixed window
of its neighbours in label (filename) order.
A synthetic-pattern experiment on Metashape 2.2.3 pins that window at ±24: each image is matched with the 24 preceding and 24 following frames — up to 48 neighbours per image. The window wraps around the ends of the sequence: the user manual states that "the first with the last images in the sequence will also be compared", so the sequence is treated as a loop and even the first and last frames get the full ±24 window (pairing with frames at the opposite end) rather than a truncated one.
Method. Build image sequences that repeat the first p unique
frames, so two identical frames sit exactly p apart, then run
matchPhotos(generic_preselection=False, reference_preselection=True,
reference_preselection_mode=ReferencePreselectionSequential).
A frame only matches an identical copy when that copy falls
inside the window, i.e. when p ≤ window. Average tie-point
projections per image stayed high (~73) for every period p from
5 through 24 and collapsed to exactly zero at p = 25 and
above — locating the one-directional reach at 24.
Verified: ✓ empirically (synthetic repeating-pattern sequences, generic preselection disabled,
filter_stationary_points=False). The window is not exposed as amatchPhotoskwarg and may differ in other versions — re-measure if the exact value matters for your pipeline.
The verification script is a self-contained binary search: it
generates random-noise patterns, repeats the first p of them, and
detects the match cliff — download measure_sequential_window.py:
Show the verification script (measure_sequential_window.py)
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# Part of the Metashape Expert Manual. MIT-licensed (see README "License").
# © Metashape Expert Manual contributors.
"""
Determine Metashape's sequential reference preselection window size.
This script uses binary search to find the value k such that:
- For pattern sequences of length <= k, sequential matching finds matches
(because identical patterns fall within the matching window)
- For pattern sequences of length > k, no matches are found
(because identical patterns are too far apart)
The script creates random noise images as unique patterns, then builds
sequences of repeating patterns and runs Metashape's matchPhotos with
sequential reference preselection to detect matches.
Usage:
python measure_sequential_window.py
Requirements:
- Metashape Python API
- PIL/Pillow
- numpy
"""
import tempfile
from pathlib import Path
import Metashape
import numpy as np
from PIL import Image
def create_pattern_images(patterns_dir: Path, num_patterns: int, size: tuple[int, int] = (640, 480)) -> None:
"""Create unique random noise images as patterns."""
patterns_dir.mkdir(exist_ok=True)
rng = np.random.default_rng(42)
for i in range(num_patterns):
noise = rng.integers(0, 256, (*size[::-1], 3), dtype=np.uint8)
img = Image.fromarray(noise)
img.save(patterns_dir / f"pat{i:02d}.jpg", quality=95)
def create_image_sequence(images_dir: Path, patterns_dir: Path, k: int, total_images: int = 60) -> int:
"""
Create a sequence of images by repeating k patterns.
Returns the actual number of images created (k * floor(total_images / k)).
"""
images_dir.mkdir(exist_ok=True)
num_images = k * (total_images // k)
for i in range(num_images):
pattern_idx = i % k
src = patterns_dir / f"pat{pattern_idx:02d}.jpg"
dst = images_dir / f"img_{i:03d}.jpg"
if dst.exists():
dst.unlink()
dst.hardlink_to(src)
return num_images
def run_sequential_matching(images_dir: Path) -> float:
"""
Run Metashape sequential matching and return average projections per image.
Returns 0.0 if no matches found, >0 otherwise.
"""
doc = Metashape.Document()
chunk = doc.addChunk()
image_files = sorted(images_dir.glob("img_*.jpg"))
chunk.addPhotos([str(f) for f in image_files])
chunk.matchPhotos(
generic_preselection=False,
reference_preselection=True,
reference_preselection_mode=Metashape.ReferencePreselectionMode.ReferencePreselectionSequential,
filter_stationary_points=False,
)
if not chunk.tie_points or not chunk.cameras:
return 0.0
total = sum(len(chunk.tie_points.projections[cam]) for cam in chunk.cameras)
return total / len(chunk.cameras)
def has_matches(patterns_dir: Path, images_dir: Path, k: int) -> bool:
"""Test if pattern sequence of length k produces matches."""
# Clean up previous images
for f in images_dir.glob("img_*.jpg"):
f.unlink()
create_image_sequence(images_dir, patterns_dir, k)
avg_proj = run_sequential_matching(images_dir)
print(f" k={k}: avg_projections={avg_proj:.2f}")
return avg_proj > 0
def find_sequential_window(patterns_dir: Path, images_dir: Path, low: int = 5, high: int = 50) -> int:
"""
Binary search to find the sequential matching window size.
Returns k where:
- k is the largest value where matches are found
- k+1 is the smallest value where no matches are found
"""
print(f"Binary search for sequential window size in range [{low}, {high}]")
# First verify boundaries
if not has_matches(patterns_dir, images_dir, low):
print(f"ERROR: No matches at k={low}, increase pattern count or lower bound")
return -1
if has_matches(patterns_dir, images_dir, high):
print(f"WARNING: Matches found at k={high}, window may be larger than {high}")
return high
# Binary search: find largest k with matches
while low < high - 1:
mid = (low + high) // 2
if has_matches(patterns_dir, images_dir, mid):
low = mid
else:
high = mid
return low
def main() -> None:
print("Metashape Sequential Reference Preselection Window Size Finder")
print("=" * 60)
with tempfile.TemporaryDirectory() as tmpdir:
tmppath = Path(tmpdir)
patterns_dir = tmppath / "patterns"
images_dir = tmppath / "images"
print("\nCreating 50 unique pattern images...")
create_pattern_images(patterns_dir, num_patterns=50)
print("\nSearching for window size...\n")
window = find_sequential_window(patterns_dir, images_dir, low=5, high=50)
print("\n" + "=" * 60)
if window > 0:
print(f"Sequential reference preselection window size: {window}")
print(f"(Metashape matches image i with images i+1 to i+{window})")
else:
print("Could not determine window size")
if __name__ == "__main__":
main()
Generic preselection on top of reference preselection¶
When both reference_preselection=True and
generic_preselection=True are set, the documented behaviour
is two-stage:
- Reference preselection picks N candidates by coordinate proximity.
- Generic preselection runs feature-based pair selection within those N candidates.
The combined cost is O(N · k) where N is the dataset size
and k is the per-camera shortlist size — much less than the
all-pairs O(N²) cost of generic alone.
When references are reliable, the combined mode is the right default. When references are unreliable, disable reference preselection, not just generic — generic-on-a-bad-shortlist will still miss the correct matches that the bad reference shortlist excluded.
Caveats¶
- Reference preselection does NOT use rotation by default. Attested verbatim: "Camera orientation angles are only taken into account if they are loaded to the Reference pane and when the Ground Altitude value is specified in the pane's preferences dialog." Most aerial workflows leave rotation out of the preselection because the GPS-only references don't carry orientation; a strapdown IMU dataset with full pose references can opt in.
- Sequential mode is for ordered captures. It assumes
consecutive images are spatially close. A randomly-shuffled
image set with
ReferencePreselectionSequentialproduces garbage pair selections. It matches a fixed ±24-frame window (24 preceding + 24 following frames, cyclic), independent of any coordinates orlocation_accuracy— see above. - Estimated mode requires a prior alignment. Calling
matchPhotos(reference_preselection_mode=…Estimated)on a fresh chunk with no prioralignCamerasraises an error: there are no estimated transforms to use. - The coordinate-shortlist neighbour count (
Source/Estimatedmodes) is not user-tunable via Python. The internal logic depends oncamera.reference.location_accuracyand the dataset's density. To force a wider search radius, increase thelocation_accuracyvalue (or set it to a Vector with the desired uncertainty); to force a narrower search, decrease it. (Sequential mode does not use this logic; its window is fixed — see above.) - Reference preselection is the default; disabling it is the unusual choice. Most production aerial workflows will not touch this setting. Disable it only when diagnosing alignment problems (per the "clean" recipe) or when references are demonstrably unreliable.
Related concepts¶
- Generic preselection — the feature-based pair-selection method that runs on the reference-preselection shortlist, or on its own.
- Guided matching — a separate option that adds matches at a later stage of alignment, not at pair-selection time.
- Tie points
Articles in this manual¶
- Diagnosing under-aligned chunks — the canonical "clean" alignment recipe explicitly recommends Reference preselection: disabled as the alignment-debugging baseline.
- Synthetic priors via
ReferencePreselectionSource— usesSourcemode with synthetic (centroid-pointing) priors to bootstrap alignment on repeated-feature scenes. alignChunks(method=PointBased)cannot break geometric symmetry — recommendsreference_preselection=True, reference_preselection_mode=ReferencePreselectionSourcebeforealignChunksto prune the cross-chunk pair pool.
Forum threads worth reading¶
| Date | Version | Author | Thread | One-line takeaway |
|---|---|---|---|---|
| 2016-12-20 | PhotoScan 1.2 | Alexey Pasumansky | Align images - Reference preselection | The canonical mechanism: coordinate-based shortlist + generic on the shortlist. The 1.3 separation introduces "reference without generic" mode. |
| 2016-12-20 | PhotoScan 1.2 | James | Align images - Reference preselection | Open question on robustness with bad coordinates; the thread doesn't resolve it. |