acia.registration#
Pluggable frame-to-frame registration methods for drift correction.
This module provides a small, dependency-free (beyond opencv-python-headless
and scikit-image, both already core dependencies) abstraction for estimating
the rigid drift between a reference frame and a later frame of a time-lapse
sequence: RegistrationMethod. Five concrete implementations are
provided, each exploiting a different signal in the image pair. They are
peers – there is no “winner”, no central registry, and no dispatch logic.
Callers construct the concrete class they want and call estimate()
directly, exactly like CellFilter or
PropertyExtractor.
All methods operate on plain np.ndarray frame pairs (grayscale (H, W)
or multi-channel (H, W, C)) – there is no ImageSequenceSource
plumbing, no calibration, and no pint units here; that is deliberately out of
scope for this module.
- exception acia.registration.RegistrationError[source]#
Bases:
ExceptionRaised when a
RegistrationMethodcannot produce an estimate at all.No method ever silently returns an identity/zero transform it did not actually measure: a silent zero-transform on failure would be indistinguishable from “genuinely no drift” and would corrupt any downstream comparison. Blank input, a non-convergent optimizer, a non-finite result or a model with no support all raise this.
A fit that was measured but scored below its method’s confidence threshold is a different case, and is governed by
on_low_confidence(seeLOW_CONFIDENCE_POLICIES): under the default"keep"the estimate is returned with a warning and itsconfidenceset, so the caller can decide what to trust; under"reject"it raises this too.
- exception acia.registration.LowConfidenceWarning[source]#
Bases:
UserWarningWarned when a fit scoring below its threshold is kept anyway.
Its own category so a caller drowning in them can filter or collect them (
warnings.simplefilter("once", LowConfidenceWarning)) without silencing unrelated warnings – a long time-lapse whose content evolves can produce one per frame for a contiguous tail of frames.
- acia.registration.LOW_CONFIDENCE_POLICIES: tuple[str, ...] = ('keep', 'reject')#
What a method does with a fit that scores below its confidence threshold.
"keep"(default) – return the estimate anyway, with itsconfidenceset, after emitting aLowConfidenceWarningnaming the score and the threshold it missed. Every frame then gets a stored transform, andconfidenceis the signal for which ones to distrust."reject"– raiseRegistrationErrorinstead, so a weak fit never reaches the caller.
- class acia.registration.FrameTransform[source]#
Bases:
objectA rigid (translation + rotation) transform between two frames.
thetafollows the same convention asangle: degrees, counter-clockwise, matching OpenCV’sgetRotationMatrix2D. The rotation is understood to pivot about the frame’s geometric center, followed by a translation of(dx, dy)pixels.- Variables:
dx (float) – Translation along the image x-axis, in pixels.
dy (float) – Translation along the image y-axis, in pixels.
theta (float) – Rotation angle in degrees, counter-clockwise. Defaults to
0.0for translation-only methods; always set explicitly (never omitted) so a translation-only result is unambiguous.confidence (float | None) – The estimating method’s own goodness-of-fit score for this transform, or
Nonewhen the method has no honest scalar to report. A gated method compares it against its own threshold (seeLOW_CONFIDENCE_POLICIES): under the default"keep"a shortfall only warns, so this score is what tells a caller which stored transforms to distrust – nothing downstream drops a frame on its behalf. Scores are not comparable across methods (an ECC correlation coefficient and a RANSAC inlier fraction are different quantities), and for a method comparing against a fixed reference a score is only comparable across frames while the imaged content itself stays static – seeGradientECC.
- to_dict()[source]#
Return a plain JSON-friendly dict representation.
confidenceis emitted only when it is notNone, so a transform from a method that reports no score serializes to exactly the three-key dict it always has.
- classmethod from_dict(data)[source]#
Build a
FrameTransformfrom a plain dict.- Parameters:
data (dict[str, Any]) – A mapping as produced by
to_dict().thetamay be omitted, defaulting to0.0;confidencemay be omitted (ornull), defaulting toNone– so aregistration_transforms.jsonwritten before confidences were recorded loads unchanged.- Returns:
FrameTransform – The reconstructed transform.
- Return type:
- acia.registration.apply_correction(frame, transform)[source]#
Undo an estimated drift by inverting and applying its forward transform.
transformis theFrameTransformestimated as reference->frame; inverting it and warpingframemaps it back onto the reference frame’s coordinate system. Same convention as the rotation-about-center + explicit translation used throughout this module (and mirrored byRotatedCropSequenceSource’s own warp matrix).This is the single place this warp math lives – the verify view, the batch-apply step, and
RegisteredSequenceSourceall call this one implementation, andapply_correction_to_spec()moves crop geometry with the very same matrix, so pixels and geometry cannot disagree.- Parameters:
frame (ndarray) – The frame to correct, grayscale
(H, W)or multi-channel(H, W, C).transform (FrameTransform) – The
(dx, dy, theta)estimated as reference->``frame``.
- Returns:
np.ndarray –
framewarped back onto the reference frame’s coordinatesystem, same shape as
frame.
- Return type:
- acia.registration.apply_correction_to_spec(spec, transform, *, shape)[source]#
Carry a crop spec drawn on one frame onto the drift-corrected view.
A
RotatedCropSpeclives in the pixel coordinate system of the frame it was drawn on.apply_correction()puts corrected frames into the reference frame’s coordinate system, so cropping aRegisteredSequenceSourcewith a spec drawn on some later frame lands the crop wherever that frame had drifted to – off by the full drift accumulated up to it, silently, and worse the later the frame. This maps the spec across, soregistered.crop_rotated(mapped)takes the regionraw.crop_rotated(spec)would have taken from the frame it was drawn on.The rectangle is rigid, so only its pose moves: the center rides the same inverse warp
apply_correction()applies to pixels (via the shared_correction_matrix()), the angle picks uptransform.theta– rotations about a common center compose additively, the same homomorphismcompose()relies on – andsizeis untouched.- Parameters:
spec (RotatedCropSpec) – The crop spec, in the coordinate system of the frame it was drawn on.
transform (FrameTransform | None) – The reference->that-frame transform, or
Noneto returnspecunchanged.Noneis the honest answer for a spec drawn on the reference frame itself and for an unregistered source – both need no mapping.shape (tuple[int, int]) –
(H, W)of the full frame, the same shapeapply_correction()sees – not the crop’s. Keyword-only because this module’s rect geometry is(w, h)while its image shapes are(H, W), and getting them the wrong way round would mis-place the crop without raising.
- Returns:
RotatedCropSpec –
- The equivalent spec in the reference frame’s
coordinate system, ready to use against a source returned by
register().
- Return type:
- class acia.registration.RegistrationMethod[source]#
Bases:
ABCBase class for a pluggable frame-to-frame registration method.
Mirrors the
CellFilter/PropertyExtractorextension style: each concrete method is a standalone class implementingestimate()with the same signature. Instances are used directly – there is no registry and no central dispatch. Adding a new method is just a new subclass.- abstract estimate(reference, frame)[source]#
Estimate the rigid transform that maps
referencetoframe.- Parameters:
- Returns:
FrameTransform –
- The estimated
(dx, dy, theta). A method that gates on confidence may return an estimate scoring below its own threshold, having warned – see
LOW_CONFIDENCE_POLICIES;confidenceis what distinguishes those.
- The estimated
- Raises:
RegistrationError – If no estimate can be produced at all, or if a gated method is configured with
on_low_confidence="reject"and the fit scores below its threshold.- Return type:
- acia.registration.build_sample_frame_indices(size_t, reference_index, n_sample_frames)[source]#
Evenly-spaced comparison-frame indices across
[0, size_t).Shared by the synthetic and real-data sections of the comparison notebook and by the dashboard’s verify step – a bug here is caught once, before real data is ever touched.
Raises
ValueErrorifn_sample_frames < 1: a silently empty/ degenerate sample list would be worse than a loud, immediate failure.Deduplicates (
sorted(set(...))) sincenp.linspace(...).round()can produce repeated indices oncen_sample_framesapproaches or exceedssize_t; if that dedup drops below the requested count, a one-line note is printed so the shortfall isn’t silently misleading.Excludes
reference_indexwhere present: comparing the reference frame against itself is a trivial identity case that wastes a sample and inflates success/drift-magnitude statistics – unless doing so would leave zero frames, in which casereference_indexis kept and a note is printed that it’s a trivial self-comparison.
- Parameters:
- Returns:
list[int] – Sorted, deduplicated sample frame indices.
- Raises:
ValueError – If
n_sample_frames < 1.- Return type:
- acia.registration.run_comparison(methods, reference_frame, get_frame, frame_indices, on_progress=None)[source]#
Run every method in
methodsagainst every frame inframe_indices.get_frame(t)fetches the comparison frame for indext(a realsource.get_frame(t).rawor a synthetic in-memory lookup – the same function drives both the synthetic and real-data sections, and the dashboard’s verify step). A failure in one(method, frame)pair is caught and recorded asNonewithout ever stopping the other methods or frames: broadened fromRegistrationErrortoExceptionso a genuinely unanticipated error can’t silently abort the whole run, and everyresults[name]list ends up with exactly one entry per entry inframe_indices(never ragged).- Parameters:
methods (Mapping[str, RegistrationMethod]) – Mapping of method name ->
RegistrationMethodinstance.reference_frame (ndarray) – The reference frame passed to every
estimatecall.get_frame (Callable[[int], ndarray]) – Callable returning the comparison frame for a given frame index.
frame_indices (list[int]) – Frame indices to compare against
reference_frame.on_progress (Callable[[int, int], None] | None) – Optional callback invoked as
on_progress(i, total)after everyframe_indices[i]has been compared against every method (total = len(frame_indices)) – lets a caller (e.g. the dashboard’s verify step) report per-frame progress without this function knowing anything about UI. Defaults toNoneso existing callers are unaffected. A failure raised by the callback itself is caught and printed the same way a per-method failure is, rather than aborting the rest of the comparison.
- Returns:
dict[str, list] –
- method name -> list of
FrameTransform | None, one entry per
frame_indicesentry, in the same order.
- method name -> list of
- Return type:
dict[str, list[FrameTransform | None]]
- acia.registration.compose(first, second)[source]#
Chain two transforms into the single transform they are equivalent to.
If
firstwas estimated as A->B andsecondas B->C, the result is the A->C transform. Used byReanchoringReferenceto turn a step estimated against a mid-sequence anchor back into an absolute transform against the sequence’s original reference frame.The result is independent of the pivot point: writing a
FrameTransformas the 2x3 matrix[R(theta) | (I - R(theta))c + d](thecv2.getRotationMatrix2D-about-c-then-translate convention this module uses throughout), the matrix product works out totheta = theta_1 + theta_2andd = d_2 + R(theta_2) @ d_1with the pivotccancelling exactly. So callers need not agree on a center, and no frame shape is required here.- Parameters:
first (FrameTransform) – The A->B transform.
second (FrameTransform) – The B->C transform.
- Returns:
FrameTransform –
- The equivalent A->C transform.
confidenceis the minimum of the two inputs’ confidences (a chain is only as trustworthy as its weakest link), or
Noneif either input has none.
- The equivalent A->C transform.
- Return type:
- class acia.registration.PhaseCorrelationHighpass[source]#
Bases:
RegistrationMethodTranslation-only registration via high-pass phase correlation.
Both frames are converted to a Sobel gradient-magnitude representation (see
_grayscale_gradient()) – a cheap high-pass filter that suppresses slow-varying content (e.g. a growing colony’s interior) and emphasizes sharp structure (e.g. device edges) – and then registered withskimage.registration.phase_cross_correlation(), which recovers a subpixel translation via FFT cross-correlation with upsampling.This method is translation-only:
thetais always0.0.Limitation (not a bug – no test required): like any FFT-based phase correlation, the implicit search range is bounded by the frame size, and accuracy degrades as the true shift approaches a large fraction of the frame dimensions (periodic wraparound).
- Variables:
upsample_factor – Subpixel upsampling factor passed to
phase_cross_correlation(higher = finer subpixel precision, at increased compute cost).min_gradient_std – Minimum standard deviation of the gradient-magnitude image required to consider a frame to have detectable signal; below this, the frame is treated as blank/textureless.
- estimate(reference, frame)[source]#
See
RegistrationMethod.estimate().- Parameters:
- Return type:
- class acia.registration.MaskedTemplateCorrelation[source]#
Bases:
RegistrationMethodTranslation-only registration via masked normalized template matching.
A rectangular region of interest (
mask_rect, e.g. drawn around a static microfluidic channel wall or trap chamber) is cropped from the reference frame and matched against a padded search window in the target frame viacv2.matchTemplate(..., TM_CCOEFF_NORMED), with the best match refined to subpixel precision by parabolic interpolation of the correlation surface around its peak.mask_rectis expected to haveangle == 0in typical use (this is a comparison-only method, not a production pipeline), but a non-zero angle is still honored for forward-compatibility, using the same rotate-then-crop math asRotatedCropSequenceSource.RotatedCropSpecis reused here purely as an inert data container – no other coupling toacia.baseis introduced.This method is translation-only:
thetais always0.0.Limitation (not a bug – no test required): the true shift must fall within
search_marginpixels of the template’s nominal location. A shift that exceeds it either pins the peak to the search-window border – always a hardRegistrationError– or lands inside the window on unrelated content, which shows up as a score belowmin_scoreand is therefore governed byon_low_confidence. Under the default"keep"that returns a wrong shift carrying a lowconfidence, so sizesearch_margingenerously rather than relying on the gate to catch it.- Variables:
mask_rect – The rectangle to crop from the reference frame as the template.
search_margin – Extra pixels of search radius (in each spatial direction) added around
mask_rect’s nominal location in the target frame.min_score – Minimum
TM_CCOEFF_NORMEDpeak score for a match to be considered confident.on_low_confidence – What to do with a match scoring below
min_score– seeLOW_CONFIDENCE_POLICIES.
- __init__(mask_rect, search_margin=30, min_score=0.5, *, on_low_confidence='keep')[source]#
- Parameters:
mask_rect (RotatedCropSpec)
search_margin (int)
min_score (float)
on_low_confidence (str)
- estimate(reference, frame)[source]#
See
RegistrationMethod.estimate().- Parameters:
- Return type:
- class acia.registration.HoughLineRigidFit[source]#
Bases:
RegistrationMethodRigid registration via straight-edge (Hough line) matching.
Targets the microfluidic device’s rigid, static geometry (channel walls, trap chamber edges) rather than the growing/dividing cell colony, which should show up as non-line clutter that this method simply ignores.
Pipeline: Canny edge detection, then
cv2.HoughLinesPto find straight segments; near-duplicate detections of the same physical edge (e.g. both sides of a thin stroke’s Canny response) are merged by clustering on (angle, perpendicular offset); thetop_nlongest survivors are kept and refined to subpixel position via an intensity-centroid profile (robust to which side of a thin edge Canny happens to trace). Frame lines are matched to reference lines by nearest angle then nearest perpendicular offset; the median angle shift across matched pairs givestheta, and a small (2 DOF) least-squares solve over each matched pair’s own perpendicular-offset equation gives(dx, dy).Limitation (not a bug – no test required): needs at least two matched lines of sufficiently different orientation (e.g. a horizontal and a vertical device edge) to solve for translation; a scene with only parallel lines cannot constrain the perpendicular-to-those-lines translation component and will raise
RegistrationError. Also,thetarecovery is only meaningful up to the scene’s own rotational symmetry period: for a scene with periodic structure (e.g. a square grid, symmetric under 90-degree rotation), a true rotation near/at that period aliases to a much smaller apparent angle (empirically, a true 80 degree rotation of a square grid was reported as roughly -10 degrees). This is not a bug – the spec’s acceptance criterion only requires correct recovery forthetaup to 5 degrees, well inside any realistic scene’s symmetry period, so it already holds; it is simply not safe to extrapolate this method’sthetaoutput to large rotations.The line-to-line correspondence across frames is the highest-risk part of this method: an ambiguous or absent match honestly raises
RegistrationErrorrather than guessing. Concretely,_match_lines()requires the best-matching reference line to be decisively closer (by perpendicular offset) than the second-best candidate, and resolves any reference line claimed by more than one frame line in favor of the closer match – see_match_lines()for the exact thresholds.- Variables:
top_n – Number of longest (deduplicated) line segments to keep per frame.
canny_thresholds –
(low, high)thresholds forcv2.Canny.hough_threshold – Accumulator threshold for
cv2.HoughLinesP.min_line_length – Minimum segment length for
cv2.HoughLinesP.max_line_gap – Maximum gap to bridge when merging collinear segments in
cv2.HoughLinesP.angle_tolerance – Maximum angle difference (degrees) for a frame line to be considered a candidate match of a reference line.
- __init__(top_n=10, canny_thresholds=(50, 150), hough_threshold=60, min_line_length=60, max_line_gap=15, angle_tolerance=20.0)[source]#
- estimate(reference, frame)[source]#
See
RegistrationMethod.estimate().- Parameters:
- Return type:
- class acia.registration.FeatureRANSACEuclidean[source]#
Bases:
RegistrationMethodRigid registration via ORB features + RANSAC-fit Euclidean transform.
ORB keypoints/descriptors are detected in both frames, matched with a Hamming-distance
cv2.BFMatcherplus Lowe’s ratio test, and the surviving correspondences are fit withcv2.estimateAffinePartial2Dundercv2.RANSAC(robust to outlier matches, e.g. spurious features on the growing/dividing cell colony). The resulting 2x3 similarity matrix is decomposed into(dx, dy, theta).Limitation (not a bug – no test required): needs enough distinctive, repeatable ORB keypoints; a scene dominated by smooth, low-contrast content with few corners (or a very large motion that exceeds ORB’s matching range) will not produce enough inliers.
- Variables:
n_features – Maximum number of ORB features to detect per frame.
ratio_thresh – Lowe’s ratio-test threshold (lower = stricter).
ransac_thresh – RANSAC reprojection-error threshold, in pixels.
min_inliers – Minimum number of RANSAC inlier correspondences for the fit to be considered confident. Also a hard floor earlier in the pipeline: too few candidate matches to run RANSAC at all is a
RegistrationErrorregardless ofon_low_confidence, because no model gets built in that case.on_low_confidence – What to do with a fit whose RANSAC inlier count falls below
min_inliers– seeLOW_CONFIDENCE_POLICIES.
- __init__(n_features=500, ratio_thresh=0.75, ransac_thresh=3.0, min_inliers=3, *, on_low_confidence='keep')[source]#
- estimate(reference, frame)[source]#
See
RegistrationMethod.estimate().- Parameters:
- Return type:
- class acia.registration.GradientECC[source]#
Bases:
RegistrationMethodRigid registration via Enhanced Correlation Coefficient (ECC) maximization.
Both frames are converted to a Sobel gradient-magnitude representation (see
_grayscale_gradient()) – emphasizing sharp device edges over the colony’s smoother interior – andcv2.findTransformECCiteratively refines a Euclidean (rotation + translation) warp maximizing the correlation coefficient between them. The resulting 2x3 matrix is decomposed into(dx, dy, theta).Limitation (not a bug – no test required): ECC is a local optimizer; it needs a reasonable initial overlap and can fail to converge (raising
RegistrationError) for large motions well beyond its capture range. To improve convergence reliability within the intended happy-path envelope (translation up to roughly a dozen pixels, small rotation), estimation runs coarse-to-fine over a small image pyramid (see_build_gray_pyramid()): the coarsest level is seeded with a translation-only phase-correlation estimate (on that level’s gradient images) rather than starting from the identity transform, and each finer level is seeded from the previous level’s converged warp, scaled to that level’s resolution; if the coarse phase-correlation pre-pass itself fails, the coarsest level simply falls back to an identity-seeded warp. This also substantially reduces the number of full-resolution ECC iterations needed on large frames, since the seed arriving at full resolution is already close to the true optimum. One accepted limitation shared withHoughLineRigidFit: on device geometry with several closely-spaced parallel channel walls, downsampling can blur two walls into one feature at a coarse level, risking a wrong-by-one-period coarse seed.Optionally,
early_stop_delta_pxlets estimation stop before reaching full resolution once the coarse-to-fine estimate has stabilized (see_build_gray_pyramid()) – disabled by default (None), since stopping early trades some estimation precision for speed and should be enabled deliberately, not silently.- Important (
min_confidenceon a sequence whose content changes): ECC’s correlation coefficient measures how similar the two gradient images are after alignment. When every frame of a long time-lapse is registered against one fixed reference and the imaged content itself evolves – a colony growing into the field of view, a channel filling with cells – the coefficient decays with elapsed biology, not with misalignment, and a fixed
min_confidencegate eventually flags perfectly good fits in a contiguous tail of late frames. On a synthetic device fixture with growing content and known drift, the coefficient fell from 0.96 to 0.75 over 120 frames while the recovered drift stayed accurate to 0.03 px.exclude_rectsis the direct remedy: exclude the regions whose content changes (the growth channels) so the coefficient is computed only on static geometry. On that same fixture it then stayed above 0.95 for the whole sequence at identical accuracy. Where that is not possible, register against a recent frame instead of a fixed one (seeReanchoringReference). Simply loweringmin_confidence– or relying on the defaulton_low_confidence="keep"to warn and carry on – is a poor substitute: once content changes, the coefficients of correct and incorrect fits overlap, so the score stops discriminating in either direction and a warning stops meaning much.Note also that with
early_stop_delta_pxset, the gated coefficient is the one from whichever pyramid level estimation stopped at, not necessarily the full-resolution one. Coarse levels tend to score slightly higher, so early stopping makes the gate marginally more lenient.
- Variables:
n_iterations – Maximum ECC iterations, applied at every pyramid level.
epsilon – ECC convergence threshold, applied at every pyramid level.
min_gradient_std – Minimum standard deviation of the gradient-magnitude image required to consider a frame to have detectable signal; below this, the frame is treated as blank/textureless.
min_confidence – Minimum
cv2.findTransformECCfinal correlation coefficient for a fit to be considered confident; what happens below it is governed byon_low_confidence. Empirically (randomized translation/rotation within this class’s happy-path envelope, on structured synthetic data), correctly-converged fits cluster tightly around0.98-0.99, while silently-wrong fits (converged to the wrong local optimum) top out around0.87–0.9cleanly separates the two with margin on both sides. That calibration holds only for a static scene; see the “Important” note above before trusting it on a time-lapse whose content evolves.max_pyramid_levels – Hard cap on the number of coarse-to-fine levels (see
_build_gray_pyramid()); the finest level is always the full-resolution frame.min_pyramid_size – Minimum shorter-side length, in pixels, a level must have to be included; smaller frames simply get fewer levels (possibly just one, i.e. today’s single-resolution behavior).
early_stop_delta_px – If set, estimation stops as soon as a level’s full-resolution-equivalent
(dx, dy)changes by less than this many pixels andthetachanges by less than this many degrees, compared to the previous (coarser) level – the same value doing double duty as a pixel threshold and a degree threshold, mirroring this class’s existing0.5px /0.5deg pairing.None(default) always runs to full resolution, matching prior behavior exactly.translation_only – If
True, fitscv2.MOTION_TRANSLATIONinstead ofcv2.MOTION_EUCLIDEAN– no rotation is estimated (the returnedthetais always exactly0.0), which fits fewer parameters and converges more robustly when the true motion is known to be translation-only.False(default) matches prior behavior exactly.exclude_rects – Regions to leave out of the ECC objective entirely (see
_rect_mask()and the “Important” note above), in full-resolution image coordinates. The natural choice is the very rectangles a caller has already marked as its regions of interest: those are where the biology is, hence where the content changes.None(default) registers on the whole frame, matching prior behavior exactly.exclude_shrink_px – Shrink each excluded rectangle inward by this many pixels per side, keeping a band that wide just inside its border in the objective. Those borders usually sit on static device geometry (channel walls) whose sharp edges measurably improve precision, so excluding a rectangle right up to its border throws away the most useful features near it. Ignored when
exclude_rectsisNone.on_low_confidence – What to do with a fit whose final correlation coefficient falls below
min_confidence– seeLOW_CONFIDENCE_POLICIES.
- __init__(n_iterations=200, epsilon=1e-06, min_gradient_std=0.001, min_confidence=0.9, max_pyramid_levels=4, min_pyramid_size=128, early_stop_delta_px=None, translation_only=False, exclude_rects=None, exclude_shrink_px=0.0, *, on_low_confidence='keep')[source]#
- Parameters:
- estimate(reference, frame)[source]#
See
RegistrationMethod.estimate().- Parameters:
- Return type:
- Important (
- class acia.registration.ReanchoringReference[source]#
Bases:
objectEstimate every frame against a reference, re-anchoring when that fails.
A time-lapse is normally registered by estimating each frame against one fixed reference (frame 0). That breaks down when the imaged content itself evolves: a method comparing frame 400 against frame 0 may legitimately fail to find them similar enough to trust, even though the drift between them is perfectly recoverable (see
GradientECC’s note onmin_confidence).Note that the fallback is driven by
RegistrationError, so with the defaulton_low_confidence="keep"a merely unconfident fit no longer triggers it – the method warns and returns its estimate, and this class treats that as the success it is reported to be. Re-anchoring then fires only on outright failures (blank input, non-convergence). Construct the wrapped method withon_low_confidence="reject"to get the older behaviour, where a low score re-anchors instead of being carried forward.This class wraps any
RegistrationMethodwith a reference policy that handles that case without abandoning the fixed reference where it works:"fixed"– always estimate against the original reference. Exactly what a caller gets frommethod.estimate(reference, frame)directly; this class adds nothing but bookkeeping."reanchor"(default) – estimate against the original reference; if that raises, retry once against the most recent successfully estimated frame andcompose()the result back into an absolute reference->frame transform. Purely a fallback: a frame that succeeds against the original reference never takes the second path, so enabling this cannot change an estimate that already worked."chained"– always estimate against the previous frame and compose. Maximally robust to slow content change, but composition error accumulates over the sequence, so prefer"reanchor"unless a sequence changes so fast that the fixed reference is useless from the start.
A frame that fails even after re-anchoring raises, exactly as the bare method would, and leaves the anchor untouched – so an isolated bad frame (a focus blip, a lamp flicker) stays an isolated failure instead of poisoning every frame after it.
- Variables:
reference_frame – Index of the original reference frame; every transform returned by
estimate()is expressed relative to it, whatever anchor was actually used to compute it.anchor_frame – Index of the frame currently being estimated against.
reanchor_events –
(frame, anchor)pairs recorded each time estimation fell back to a mid-sequence anchor – how often the fallback fired, and where.anchors_used –
frame -> anchor indexfor every frame that was estimated against something other thanreference_frame.
- MODES = ('fixed', 'reanchor', 'chained')#
- __init__(method, reference, *, reference_frame=0, mode='reanchor')[source]#
Initialize the policy.
- Parameters:
method (RegistrationMethod) – The registration method to drive.
reference (ndarray) – The original reference frame’s pixel data.
reference_frame (int) – That frame’s index in the sequence.
- Raises:
ValueError – If
modeis not one ofMODES.
- seed(index, image, absolute)[source]#
Restore the last-good state, so a resumed run continues the chain.
Without this, resuming a partially-registered position would treat the first frame of the resumed run as if nothing had been registered yet and re-anchor from the original reference again.
- Parameters:
index (int) – The frame index to treat as the most recent success.
image (ndarray) – That frame’s pixel data.
absolute (FrameTransform) – Its reference->frame transform, as already recorded.
- Return type:
None
- estimate(index, frame)[source]#
Estimate
frame’s transform relative to the original reference.- Parameters:
- Returns:
FrameTransform – The absolute reference->``frame`` transform.
- Raises:
RegistrationError – If the frame cannot be estimated against the reference nor (where applicable) against the fallback anchor.
- Return type: