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: Exception

Raised when a RegistrationMethod cannot 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 (see LOW_CONFIDENCE_POLICIES): under the default "keep" the estimate is returned with a warning and its confidence set, so the caller can decide what to trust; under "reject" it raises this too.

exception acia.registration.LowConfidenceWarning[source]#

Bases: UserWarning

Warned 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 its confidence set, after emitting a LowConfidenceWarning naming the score and the threshold it missed. Every frame then gets a stored transform, and confidence is the signal for which ones to distrust.

  • "reject" – raise RegistrationError instead, so a weak fit never reaches the caller.

class acia.registration.FrameTransform[source]#

Bases: object

A rigid (translation + rotation) transform between two frames.

theta follows the same convention as angle: degrees, counter-clockwise, matching OpenCV’s getRotationMatrix2D. 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.0 for 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 None when the method has no honest scalar to report. A gated method compares it against its own threshold (see LOW_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 – see GradientECC.

dx: float#
dy: float#
theta: float = 0.0#
confidence: float | None = None#
to_dict()[source]#

Return a plain JSON-friendly dict representation.

confidence is emitted only when it is not None, so a transform from a method that reports no score serializes to exactly the three-key dict it always has.

Returns:

dict

{"dx": dx, "dy": dy, "theta": theta}, plus

"confidence" when one is set.

Return type:

dict[str, Any]

classmethod from_dict(data)[source]#

Build a FrameTransform from a plain dict.

Parameters:

data (dict[str, Any]) – A mapping as produced by to_dict(). theta may be omitted, defaulting to 0.0; confidence may be omitted (or null), defaulting to None – so a registration_transforms.json written before confidences were recorded loads unchanged.

Returns:

FrameTransform – The reconstructed transform.

Return type:

FrameTransform

__init__(dx, dy, theta=0.0, confidence=None)#
Parameters:
Return type:

None

acia.registration.apply_correction(frame, transform)[source]#

Undo an estimated drift by inverting and applying its forward transform.

transform is the FrameTransform estimated as reference->frame; inverting it and warping frame maps it back onto the reference frame’s coordinate system. Same convention as the rotation-about-center + explicit translation used throughout this module (and mirrored by RotatedCropSequenceSource’s own warp matrix).

This is the single place this warp math lives – the verify view, the batch-apply step, and RegisteredSequenceSource all call this one implementation, and apply_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

frame warped back onto the reference frame’s coordinate

system, same shape as frame.

Return type:

ndarray

acia.registration.apply_correction_to_spec(spec, transform, *, shape)[source]#

Carry a crop spec drawn on one frame onto the drift-corrected view.

A RotatedCropSpec lives 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 a RegisteredSequenceSource with 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, so registered.crop_rotated(mapped) takes the region raw.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 up transform.theta – rotations about a common center compose additively, the same homomorphism compose() relies on – and size is 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 None to return spec unchanged. None is 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 shape apply_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:

RotatedCropSpec

class acia.registration.RegistrationMethod[source]#

Bases: ABC

Base class for a pluggable frame-to-frame registration method.

Mirrors the CellFilter/PropertyExtractor extension style: each concrete method is a standalone class implementing estimate() 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 reference to frame.

Parameters:
  • reference (ndarray) – The reference frame, grayscale (H, W) or multi-channel (H, W, C).

  • frame (ndarray) – The frame to register against reference, same shape convention as reference.

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; confidence is what distinguishes those.

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:

FrameTransform

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 ValueError if n_sample_frames < 1: a silently empty/ degenerate sample list would be worse than a loud, immediate failure.

  • Deduplicates (sorted(set(...))) since np.linspace(...).round() can produce repeated indices once n_sample_frames approaches or exceeds size_t; if that dedup drops below the requested count, a one-line note is printed so the shortfall isn’t silently misleading.

  • Excludes reference_index where 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 case reference_index is kept and a note is printed that it’s a trivial self-comparison.

Parameters:
  • size_t (int) – Number of frames in the sequence.

  • reference_index (int) – The reference frame index to exclude where possible.

  • n_sample_frames (int) – Requested number of sample indices (>= 1).

Returns:

list[int] – Sorted, deduplicated sample frame indices.

Raises:

ValueError – If n_sample_frames < 1.

Return type:

list[int]

acia.registration.run_comparison(methods, reference_frame, get_frame, frame_indices, on_progress=None)[source]#

Run every method in methods against every frame in frame_indices.

get_frame(t) fetches the comparison frame for index t (a real source.get_frame(t).raw or 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 as None without ever stopping the other methods or frames: broadened from RegistrationError to Exception so a genuinely unanticipated error can’t silently abort the whole run, and every results[name] list ends up with exactly one entry per entry in frame_indices (never ragged).

Parameters:
  • methods (Mapping[str, RegistrationMethod]) – Mapping of method name -> RegistrationMethod instance.

  • reference_frame (ndarray) – The reference frame passed to every estimate call.

  • 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 every frame_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 to None so 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_indices entry, in the same order.

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 first was estimated as A->B and second as B->C, the result is the A->C transform. Used by ReanchoringReference to 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 FrameTransform as the 2x3 matrix [R(theta) | (I - R(theta))c + d] (the cv2.getRotationMatrix2D-about-c-then-translate convention this module uses throughout), the matrix product works out to theta = theta_1 + theta_2 and d = d_2 + R(theta_2) @ d_1 with the pivot c cancelling exactly. So callers need not agree on a center, and no frame shape is required here.

Parameters:
Returns:

FrameTransform

The equivalent A->C transform. confidence is the

minimum of the two inputs’ confidences (a chain is only as trustworthy as its weakest link), or None if either input has none.

Return type:

FrameTransform

class acia.registration.PhaseCorrelationHighpass[source]#

Bases: RegistrationMethod

Translation-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 with skimage.registration.phase_cross_correlation(), which recovers a subpixel translation via FFT cross-correlation with upsampling.

This method is translation-only: theta is always 0.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.

__init__(upsample_factor=20, min_gradient_std=0.001)[source]#
Parameters:
  • upsample_factor (int)

  • min_gradient_std (float)

estimate(reference, frame)[source]#

See RegistrationMethod.estimate().

Parameters:
Return type:

FrameTransform

class acia.registration.MaskedTemplateCorrelation[source]#

Bases: RegistrationMethod

Translation-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 via cv2.matchTemplate(..., TM_CCOEFF_NORMED), with the best match refined to subpixel precision by parabolic interpolation of the correlation surface around its peak.

mask_rect is expected to have angle == 0 in 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 as RotatedCropSequenceSource. RotatedCropSpec is reused here purely as an inert data container – no other coupling to acia.base is introduced.

This method is translation-only: theta is always 0.0.

Limitation (not a bug – no test required): the true shift must fall within search_margin pixels of the template’s nominal location. A shift that exceeds it either pins the peak to the search-window border – always a hard RegistrationError – or lands inside the window on unrelated content, which shows up as a score below min_score and is therefore governed by on_low_confidence. Under the default "keep" that returns a wrong shift carrying a low confidence, so size search_margin generously 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_NORMED peak score for a match to be considered confident.

  • on_low_confidence – What to do with a match scoring below min_score – see LOW_CONFIDENCE_POLICIES.

__init__(mask_rect, search_margin=30, min_score=0.5, *, on_low_confidence='keep')[source]#
Parameters:
estimate(reference, frame)[source]#

See RegistrationMethod.estimate().

Parameters:
Return type:

FrameTransform

class acia.registration.HoughLineRigidFit[source]#

Bases: RegistrationMethod

Rigid 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.HoughLinesP to 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); the top_n longest 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 gives theta, 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, theta recovery 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 for theta up to 5 degrees, well inside any realistic scene’s symmetry period, so it already holds; it is simply not safe to extrapolate this method’s theta output 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 RegistrationError rather 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 for cv2.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]#
Parameters:
estimate(reference, frame)[source]#

See RegistrationMethod.estimate().

Parameters:
Return type:

FrameTransform

class acia.registration.FeatureRANSACEuclidean[source]#

Bases: RegistrationMethod

Rigid registration via ORB features + RANSAC-fit Euclidean transform.

ORB keypoints/descriptors are detected in both frames, matched with a Hamming-distance cv2.BFMatcher plus Lowe’s ratio test, and the surviving correspondences are fit with cv2.estimateAffinePartial2D under cv2.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 RegistrationError regardless of on_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 – see LOW_CONFIDENCE_POLICIES.

__init__(n_features=500, ratio_thresh=0.75, ransac_thresh=3.0, min_inliers=3, *, on_low_confidence='keep')[source]#
Parameters:
  • n_features (int)

  • ratio_thresh (float)

  • ransac_thresh (float)

  • min_inliers (int)

  • on_low_confidence (str)

estimate(reference, frame)[source]#

See RegistrationMethod.estimate().

Parameters:
Return type:

FrameTransform

class acia.registration.GradientECC[source]#

Bases: RegistrationMethod

Rigid 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 – and cv2.findTransformECC iteratively 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 with HoughLineRigidFit: 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_px lets 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_confidence on 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_confidence gate 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_rects is 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 (see ReanchoringReference). Simply lowering min_confidence – or relying on the default on_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_px set, 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.findTransformECC final correlation coefficient for a fit to be considered confident; what happens below it is governed by on_low_confidence. Empirically (randomized translation/rotation within this class’s happy-path envelope, on structured synthetic data), correctly-converged fits cluster tightly around 0.98-0.99, while silently-wrong fits (converged to the wrong local optimum) top out around 0.870.9 cleanly 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 and theta changes 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 existing 0.5 px / 0.5 deg pairing. None (default) always runs to full resolution, matching prior behavior exactly.

  • translation_only – If True, fits cv2.MOTION_TRANSLATION instead of cv2.MOTION_EUCLIDEAN – no rotation is estimated (the returned theta is always exactly 0.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_rects is None.

  • on_low_confidence – What to do with a fit whose final correlation coefficient falls below min_confidence – see LOW_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:

FrameTransform

class acia.registration.ReanchoringReference[source]#

Bases: object

Estimate 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 on min_confidence).

Note that the fallback is driven by RegistrationError, so with the default on_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 with on_low_confidence="reject" to get the older behaviour, where a low score re-anchors instead of being carried forward.

This class wraps any RegistrationMethod with 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 from method.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 and compose() 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_usedframe -> anchor index for every frame that was estimated against something other than reference_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.

  • mode (str) – One of MODES.

Raises:

ValueError – If mode is not one of MODES.

property anchor_frame: int#

Index of the frame the next estimate will be made against.

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:
  • index (int) – frame’s index in the sequence, used for bookkeeping and to decide whether a fallback anchor is even available.

  • frame (ndarray) – The frame’s pixel data.

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:

FrameTransform