Skip to content

About

4个Final Cut插件:自动水平、变换1、变换2(类似文档扫描)、Upright(类似lightroom)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

AnyUpright

AnyUpright is a suite of FxPlug effects for single-frame-assisted perspective and upright correction. The effects target fixed-camera or mostly static correction workflows where analysis and manual setup happen on one representative frame, while the resulting transform persists across the whole clip and supports host keyframes through published parameters.

Current State

  • The repository is initialized as an Xcode FxPlug 4 project.
  • The template brightness filter has been replaced with four separate FxPlug filters under the AnyUpright group.
  • The repository tracks four Final Cut Effect templates under fcp-effects/ and their browser previews under preview-design/png/; the Wrapper Application embeds and installs them into the user's Motion Templates directory.
  • There is no package manager, Docker runtime, or CI workflow yet.
  • GitHub Release builds use the reproducible ad-hoc signing and DMG workflow in scripts/build-release.sh; Motion template creation, Wrapper-based installation, and Gatekeeper handling are documented in docs/release-process.md and INSTALL.md.
  • The installed /Applications/AnyUpright.app and its installer UI manage Release only. Debug products remain developer/Agent-managed artifacts under Derived Data and must never replace the installed Release App; canonical and possible stale Debug paths are documented in docs/release-process.md. Release Derived Data is packaging input only: do not register or host-test a temporary Release product, because deleting it does not remove its PlugInKit registration.
  • Geometry tests live in AnyUprightTests/ and can be run as a lightweight Swift executable.
  • The shared geometry layer now includes line candidate filtering, horizon correction estimation, and centered perspective parameter estimation from reference lines.
  • Stretch and Upright expose FxPlug onscreen controls through separate FxOnScreenControl plug-in entries linked to their filter UUIDs with supportedPlugins.
  • Inner Stretch edit-mode dimming is rendered into the filter output itself, so the selected source area follows the video image even when host OSC drawing is unavailable. The interactive outline, handles, hover highlights, hit testing, and drag writeback are owned by the FxPlug OSC layer.
  • tools/render-warp-previews.swift generates CPU-rendered preview PNGs from the same geometry matrices used by the Metal renderer, so matrix semantics can be checked without launching a host app. It does not model Motion's composition of an OSC IOSurface.
  • tools/render-outer-stretch-motion-host.swift loads the production default.metallib, runs the production Outer Stretch preview and OSC texture shaders with production vertex helpers, then applies Motion's observed final OSC-surface Y composition. Use it for Outer Stretch exterior-preview orientation work.

Localization

  • AnyUpright supports English (en) and Simplified Chinese (zh-Hans). English is the development language and the fallback whenever a translation or locale resource is unavailable.
  • Product and effect names, including AnyUpright Horizon, AnyUpright Inner Stretch, AnyUpright Outer Stretch, and AnyUpright Upright, remain English in both locales. Effect descriptions, inspector parameters and popup entries, OSC messages, Defaults windows, host-visible analysis errors, and Wrapper Application menus are localized.
  • Plugin runtime text is owned by AUStringKey and resolved through AUL10n.plugin, which is anchored to the plugin XPC bundle rather than Bundle.main. New user-visible plugin text must add one typed key, an English fallback, and matching entries in both Plugin/en.lproj/Localizable.strings and Plugin/zh-Hans.lproj/Localizable.strings. Dynamic phrases must use complete format strings rather than concatenating translated fragments.
  • Motion and Final Cut Pro choose the locale through their application/system language settings; the plugin has no independent language selector. After changing the host language, fully quit the host and its plugin XPC process before validation. Runtime language switching is not supported.
  • Development logs, model diagnostics, Metal diagnostics, and test fixture text remain English and are not localization surfaces.

Engineering Notes

If you are a person or agent debugging FxPlug coordinate flips, Final Cut/Motion OSC hit testing, viewer-vs-video drift, drawOSC drawable sizing, tiled render sampling shifts, remote-window hosting, or fixed-shape Core ML model routing, start with docs/engineering-notes/.

For plugin-defaults product behavior and persistence, see docs/plugin-defaults-implementation-notes.md. For reusable FxRemoteWindow/ViewBridge behavior, see docs/engineering-notes/fxplug-remote-window-hosting.md.

Effects

AnyUpright Horizon

Automatically detects a horizontal reference line in the current frame and applies a rotation correction across the clip.

Product scope:

  • Horizon is the rotation-only leveling effect. It is for shots where the camera was rolled slightly and a centered affine rotation is the most natural correction.
  • Horizon analysis may use a detected horizon, line slope, vanishing/up direction, or learned camera-calibration cue to estimate roll, but it should write only Rotation for this effect. It should not write perspective, keystone, Inner Stretch, Outer Stretch, pitch, FoV, or homography parameters.
  • Research notes, dataset options, and roll-only validation criteria are recorded in docs/horizon-rotation-research.md.

Current implementation:

  • AnyUpright Horizon is registered as a separate FxPlug filter.
  • Rotation is a manual angle slider.
  • Fill Frame controls whether the rotated image is zoomed enough to avoid black edges.
  • Analyze Horizon starts FxAnalysis near the current parameter time when the host provides one, runs the project-owned Core ML-only GeoCalib roll detector with direct Metal GeoCalib preprocessing, verifier gate, and optimizer glue, and writes only the accepted centered rotation correction back to Rotation.
  • While Horizon analysis is active, the shared stable FxOnScreenControl pass shows a localized model-loading status until its GeoCalib session is ready, then a localized frame-analysis status during prediction and fallback analysis. The status is OSC-only and never enters the final rendered frame. Completion, failure, cancellation, or an invalid analysis range clears the transient overlay.
  • GeoCalib writeback is accepted only when roll_uncertainty <= 3 deg, the absolute correction is within the Horizon +/-45 deg product range, and fewer than two available verifiers disagree with GeoCalib by more than 10 degrees. The current in-plugin verifiers are project Hough-axis and gradient-axis estimates; rejected GeoCalib results produce no writeback.
  • The in-plugin Core ML path uses the shared typed lifecycle component described in docs/engineering-notes/coreml-session-lifecycle.md. GeoCalib owns an independent cache keyed by fixed input shape. Adding the effect asks FxOnScreenControlAPI_v4 for the current input/object size, selects the nearest production shape, and asynchronously prewarms that model when the host exposes dimensions; it no longer hardcodes a 4:3 prewarm. Cached sessions expire independently per shape after 15 seconds without analysis, extend that shape to 30 seconds from the next analysis start, and extend only that shape to 60 seconds when two analyses happen inside its 30-second window.
  • AnyUpright/Plugin/GeoCalibCoreML/ currently contains fixed-shape .mlmodelc graphs because the flexible-shape ML Program paths, while runnable after experimental torch.export conversion patches, measured about 12-13x slower than fixed-shape Core ML for the current 4:3 inputs. This held for both RangeDim and EnumeratedShapes. The fixed production shapes are 4:3 [1, 3, 320, 416], 3:4 [1, 3, 416, 320], 16:9 [1, 3, 320, 544], 9:16 [1, 3, 544, 320], 1:1 [1, 3, 320, 320], 3:2 [1, 3, 320, 480], 2:3 [1, 3, 480, 320], and 2.35:1 [1, 3, 320, 736]. Images whose aspect ratio is not one of these labels use the nearest model ratio, then aspect-fill resize and center crop to that model's static input shape. The compiled models have identical weights/weight.bin blobs, so local plugin resources should dedupe every non-canonical weight with tools/dedupe-geocalib-coreml-weights.sh; this keeps Core ML behavior unchanged while keeping the eight-shape Core ML resource directory around 116MB instead of storing eight full weight copies. For reusable routing diagnostics, see docs/engineering-notes/coreml-fixed-shape-routing.md.
  • Verifiers run only after the primary GeoCalib result passes the uncertainty and +/-45 deg product gate; this keeps rejected frames from paying verifier cost while preserving the two-verifier rejection rule for candidate writebacks.
  • There is no Swift/Metal GeoCalib neural fallback runtime in the plug-in path. If Core ML GeoCalib resources or model execution are unavailable, the existing non-GeoCalib Vision/Hough fallback detectors may still try to produce a rotation; a GeoCalib confidence/verifier rejection does not fall through to those fallback detectors.
  • Horizon analysis writeback history is archived under .agent-work/debug/; stable engineering conventions should live in docs/engineering-notes/.

Workflow:

  1. User applies the effect to a clip.
  2. User analyzes the current frame or selects a candidate line.
  3. Plugin writes the resulting correction into keyframeable parameters.
  4. Playback and export use only the saved transform.

Primary risks:

  • False positives when strong lines are not true horizon references.
  • Low-confidence frames with no reliable horizontal line.
  • Need for manual override and visible candidate feedback.
  • Horizon and Upright save the button's timeline time, convert it to input time with FxTimingAPI_v4, and request a probe of max(0.05 seconds, 2 * sampleDuration) aligned to the input sampling grid. Final Cut Pro may report the analysis callback's inputTimeRange in timeline coordinates, so both range endpoints are converted to input coordinates before clipping. The requested range is clipped at the input head and tail. Inner Stretch is manual-only and does not use FxAnalysis. Final Cut Pro validation on trimmed and retimed clips remains pending.

AnyUpright Stretch Transform

Provides manual four-point perspective transforms through two separate FxPlug filters.

Current implementation:

  • Stretch-specific implementation choices and historical product-shape decisions are recorded in docs/stretch-implementation-notes.md. Reusable coordinate/debugging lessons remain under docs/engineering-notes/.
  • AnyUpright Inner Stretch is registered as a separate FxPlug filter. It fixes the shared Stretch render path to Inner Stretch semantics.
  • AnyUpright Outer Stretch is registered as a separate FxPlug filter. It fixes the shared Stretch render path to output/outer-corner warp semantics.
  • The old inspector Mode popup is now a hidden fixed parameter so each filter keeps stable render state without asking the user to switch modes inside one effect.
  • AnyUpright Inner Stretch is manual-only. It does not register an FxAnalysis protocol, detector button, score threshold, candidate parameters, or candidate-selection OSC state. Retired parameter IDs 216...218 and the former hidden detection-slot ranges remain unassigned for project compatibility.
  • Edit Mode and Ratio are shown only in AnyUpright Inner Stretch. Edit Mode is enabled by default: when enabled, the filter output keeps the image unwarped and dims the area outside the current input selection, while the draggable outline and handles are drawn in the OSC overlay. Disable it to hide the adjuster and apply the selected input selection. Ratio: None keeps the existing full-frame stretch. Fit estimates the selection ratio from the average top/bottom edge length divided by the average left/right edge length and centers that ratio inside the output; pixels outside the fitted target continue through the same quadrilateral projection, so the stretched geometry remains continuous. Fill centers the same ratio around the output and crops the overflow so the corrected selection covers the canvas. Ratio is a discrete non-animatable popup: this keeps Final Cut Pro from rendering one evaluation with the previously published popup value before its Motion-template parameter bridge catches up.
  • Inner Stretch and Outer Stretch both expose Set/Unset Key Frame. When none of the eight corner X/Y channels has a keyframe at the host's current timeline time, one click snapshots their current values and keys all eight channels without changing those values. When any channel is already keyed at that exact time, one click removes the keyframes that exist there. Each operation is grouped into one host undo action when the undo API is available. A successful Set shows the localized keyframes-set message for two seconds in the shared OSC text overlay. A successful Unset shows the localized keyframes-removed message plus the reminder that dragging already-keyframed clips creates a keyframe at the current frame for six seconds. Both filters use the same generation-guarded transient-status controller, so repeated actions replace the pending expiration instead of scheduling competing clears. Each filter reloads its own notification preference on every button action: Inner Stretch reads InnerStretch.plist, while Outer Stretch reads OuterStretch.plist. Saving either Defaults checkbox therefore affects existing and future instances of that filter immediately without changing the other filter. These notices are OSC-only and never enter the rendered image.
  • In AnyUpright Inner Stretch, the default input selection starts at the four input-frame corners. The 100% full-frame selection is covered as the identity/no-offset geometry case for validation. The edit preview dims the outside area to 70% brightness and leaves the selected four-corner selection at original brightness. The OSC overlay connects the four handles with a white outline and draws blue fixed-size circular handles, with yellow hover/drag highlights.
  • In AnyUpright Outer Stretch, each visible output corner exposes only X px and Y px offsets in the inspector.
  • In AnyUpright Inner Stretch, the same four collapsed corner coordinate groups are visible in the inspector, so users can position the input selection through either pixel fields or onscreen handles.
  • Corner positions are persisted exclusively as pixel offsets; percentage corner parameters are not registered or read.
  • Positive X moves right. Positive Y moves up.
  • In AnyUpright Outer Stretch, the four offsets are a destination/output corner selection and map back to the full source frame, matching the direction a user sees in the Motion canvas. The zero-offset OSC corners are converted from the source object's OBJECT frame into host CANVAS coordinates and rendered through the canvas-pixel overlay path, so they align with the visible image corners rather than the surrounding viewer canvas. After coordinate conversion, Inner and Outer share the same active-part resolution, circular handles, and yellow edge/face highlight renderer. When a corner moves beyond the canvas, the normal Warp render also produces a bounded preview texture at up to 1600 pixels on its longest side. The OSC draws only the transformed pixels outside the canvas at 40% brightness, then draws edges and handles above them. Preview cache lookup fails closed unless exactly one render record matches the current time, Outer Stretch parameters, and GPU; it never retains a host input texture or changes the final output. The effect render uses a dedicated output-space quadrilateral coverage mask before projective sampling. The mask applies parameters in the stable correction frame, scales the result into the current render frame, and then crosses the physical-output Y boundary, preventing a strong-perspective homography horizon from exposing a clamped source edge while keeping the mask aligned with the OSC and homography target.
  • In AnyUpright Inner Stretch, the same visible corner offset parameters describe an input selection that maps to the full output frame, matching a document-scanner or Microsoft Lens style correction.
  • The visible Inner Stretch edit UI is split across two layers. The filter output layer keeps an identity preview and dims outside the selected input selection, so users can tell Edit Mode is still active. The Inner Stretch FxOnScreenControl draws the white outline, blue handles, and yellow hover/drag highlights in host canvas space, so handles can remain visible and draggable outside the video frame. For Final Cut raw-canvas events, the OSC outline and hit layer use the same source-preview geometry as the filter output; the unflipped object/canvas stretch is kept for storage/writeback diagnostics. Final Cut host connections disable Motion-style mapped-surface fallback during initial Inner Stretch hover/hit tests so raw canvas points above or outside the video frame cannot fold into an invisible hit layer. Motion-style surface-local event points are mapped back to canvas coordinates for Motion and unknown hosts when needed, but visible Final Cut raw-canvas controls outside the object frame keep raw hit testing. OSC control points are drawn from host canvas-frame points instead of output-image aspect-fit space: X and Y both stay in host canvas pixels, with no frame-center compensation, surface-scale compensation, clamping, Fit renormalization, or renderer-level vertical mirroring. Inner Stretch and Outer Stretch both persist corner movement as pixel offsets; their modes only choose the source-selection or output-corner base geometry. See docs/engineering-notes/y-axis-coordinate-conventions.md, docs/engineering-notes/stretch-osc-hit-testing.md, and docs/engineering-notes/stretch-osc-rendering.md before changing this path.
  • A hidden point-parameter experiment was intentionally backed out: Motion accepted setXValue(_:yValue:) during OSC drags but subsequent reads still returned the default points. Inner Stretch now uses the float-parameter path because Motion was verified to persist those writes.

Two intended filters:

  1. Source stretch to full frame: user drags four points around an object such as a phone screen or sign; editing can display handles without moving the image, and applying maps the selected area to the original frame size.
  2. Frame-corner warp: user drags the four output corners and sees the warped image in realtime; the stretched result is the actual output.

Primary risks:

  • Coordinate consistency across canvas space, source pixels, proxy resolution, and Final Cut project settings.
  • Onscreen control usability.
  • Rectangle detection false positives, especially when the strongest rectangle is not the object the user intended; manual handles remain the correction path.
  • Keyframing corner positions without creating confusing interpolation.

AnyUpright Upright

Provides Lightroom-style upright correction controls.

Current implementation:

  • AnyUpright Upright is registered as a separate FxPlug filter.
  • The visible parameter surface is Direction, Analyze, Mode, Auto Crop, and Edit Mode. The implementation no longer exposes separate chosen-detection, threshold, guide, candidate, vertical-perspective, horizontal-perspective, or rotation controls in the inspector.
  • New AnyUpright Upright instances default to Direction: Vertical and Mode: Auto.
  • Direction chooses the correction family: Vertical, Horizontal, or Full. Hidden vertical perspective, horizontal perspective, and rotation parameter IDs remain registered only so existing Motion and Final Cut documents can still load; Upright rendering no longer reads or writes those values.
  • Mode chooses only how reference lines are selected: Manual uses enabled manual guides, Semi Auto uses user-selected detected candidates, and Auto uses an automatic selector. Analyze is a no-op in manual mode and starts candidate-line analysis in semi/auto modes.
  • Semi Auto and Auto show the same shared centered analysis overlay as Horizon through OSC only. ScaleLSD and the optional GeoCalib camera-prior stage each report model loading until their typed Core ML session is ready, then frame analysis; every cleanup path clears the text. The status never enters the final Warp output.
  • Edit Mode shows the original unwarped frame and displays Upright OSC lines. Turning Edit Mode off applies the stored correction and hides all Upright OSC lines. Auto Crop zooms the rendered correction just enough to keep output-frame corners inside the source frame when possible.
  • Vertical supports zero, one, or two contributing vertical reference lines: zero skips correction, one applies rotation only, and two solve the direct output-to-source matrix so the referenced source-image lines become vertical after correction.
  • Horizontal supports zero, one, or two contributing horizontal reference lines: zero skips correction, one applies rotation only, and two use the symmetric direct output-to-source matrix so the referenced source-image lines become horizontal after correction.
  • Full uses the same unified guided-matrix entry point for zero to four contributing reference lines. Zero skips correction; one applies rotation only; one vertical plus one horizontal uses affine rectification to make the references perpendicular and axis-aligned; two same-orientation references preserve the verified Vertical or Horizontal direct matrix; three references preserve the same-orientation direct perspective matrix and append the minimum axis-preserving shear needed to align the remaining reference; two vertical plus two horizontal references preserve the verified Full vanishing-point matrix.
  • Upright perspective parameters are normalized by their acting axis so guide-line correction keeps the same meaning across aspect ratios: vertical perspective is normalized by image height, and horizontal perspective is normalized by image width.
  • Manual, Semi Auto, and Auto all pass their selected reference lines into the same guided matrix solver and the same matrix application path. Control mode does not participate in transform solving or rendering.
  • Internally this implementation treats upright perspective as a destination/output corner selection and maps it back to the full source frame.
  • The centered keystone math is tested at the homography level: vertical, horizontal, and combined perspective transforms keep the frame center anchored instead of acting like edge-pivot shears.
  • Manual mode displays only the guide lines relevant to the current direction. The first two guides default to vertical references and the last two default to horizontal references, so an unanalyzed clip starts from usable guide positions. In Y-up object space, Guide 3 is the lower horizontal slot and Guide 4 is the upper horizontal slot. Dragging endpoints updates correction immediately. Clicking a guide line toggles whether it contributes to correction; disabled guide endpoints draw gray. Switching from Auto to Manual copies the score-selected automatic lines into the matching manual guide slots. Switching from Semi Auto copies only the selected candidates. A single vertical line is assigned left or right by its midpoint, while a single horizontal line is assigned top or bottom in Y-up object space; unused manual slots are disabled.
  • Vertical/horizontal directions support 0, 1, or 2 contributing lines. Full supports 0, 1, 2, 3, or 4 contributing lines. Zero contributing lines means identity rendering in every control mode.
  • Semi-auto mode displays only analyzed candidate lines that match the current direction. Candidate lines are the only hittable OSC primitives in this mode. Clicking a candidate toggles selection while preventing more than two selected lines per orientation; the shared render path immediately derives the matrix from the new selection.
  • Auto mode analyzes matching candidate lines and displays up to two automatically selected lines per included orientation while Edit Mode is on. Every direction uses the V2 global scene-family selector; Vertical and Horizontal export only the requested family from the selected Full hypothesis. The selected lines enter the same matrix solver used by Manual and Semi Auto.
  • Candidate detection uses the local ScaleLSD Core ML model and Swift decode path. Semi Auto and Auto run the V2 VP/homography candidate ranker described below for every direction.

Implemented controls:

  • Direction dropdown for vertical, horizontal, or full correction.
  • Analyze push button.
  • Mode dropdown for manual, semi-auto, or auto control.
  • Auto Crop checkbox.
  • Edit Mode checkbox.

Automation levels:

  • Auto: detect candidate lines and choose up to two references per included orientation from the highest-ranked V2 Full hypothesis.
  • Semi auto: detect candidate lines matching the current direction and allow the user to choose references by clicking lines in the canvas.
  • Manual: user adjusts the default guide lines directly in the canvas.
  • The semi-auto implementation uses hidden fixed candidate slots rather than dynamic inspector UI rows, and it does not yet draw text labels.

Primary risks:

  • Automatic scoring quality. Every direction uses the frozen V2 pair ranker and risk model; Vertical and Horizontal still depend on the quality of the jointly selected Full hypothesis even though only one family is applied.
  • UX complexity from combining manual axes, drawn lines, detected candidates, and keyframes.
  • Avoiding realtime playback cost from frame analysis.

Architecture Direction

Use one repository and one product suite, but expose four separate Final Cut effects. Shared implementation should live behind small common modules:

  • Geometry: normalized points, lines, homography, affine transforms, vanishing-point helpers, and coordinate conversion.
  • Detection: frame downsampling, edge/line detection, candidate scoring, and analysis result serialization.
  • Rendering: shared Metal pipeline for affine and projective texture warps.
  • UI/controls: image-space edit dimming in the filter render output, reusable Metal onscreen overlay drawing for OSC visuals, canvas-space hit testing, object/canvas conversion through FxOnScreenControlAPI_v4, and parameter writeback where FxPlug APIs permit it.
  • Plugin defaults: Horizon, Inner Stretch, Outer Stretch, and Upright share one AppKit/FxRemoteWindow wrapper, but each opens its own editor and persists a separate strongly typed plist. The files are ~/Library/Application Support/AnyUpright/Defaults/Horizon.plist, InnerStretch.plist, OuterStretch.plist, and Upright.plist. Horizon stores Fill Frame; Inner Stretch stores Ratio and whether keyframe action notifications are suppressed; Outer Stretch stores only its independent keyframe-notification preference; Upright stores Direction, Mode, and Auto Crop. Changing any control immediately writes that editor's typed settings to its plist; there is no Save button. Restore Factory Defaults updates the controls and persists the factory settings in the same action, and is enabled only while the current settings differ from factory values. Saved parameter defaults are read while registering a new filter instance. Inner and Outer Stretch notification preferences are UI-only and each filter reloads only its own plist for every Set/Unset action, so changes apply immediately to existing instances without entering render state or crossing between filters. Other existing-instance behavior remains owned and persisted by FxPlug parameters.
  • FxPlug OSC registration: Inner Stretch, Outer Stretch, and Upright use separate OSC classes linked with supportedPlugins. Apple documentation describes this as the expected shape for onscreen controls, and an installed Pixel Film Studios FxPlug (PFSMaskV2) uses the same supportedPlugins key in its plist. If Motion does not call OSC methods, first suspect stale PlugInKit registration or host instance caching before changing coordinate math. In Motion's Metal OSC path, the drawOSC width/height values can describe the source object, while the drawable tile is represented by destinationImage; host OSC surfaces should map canvas coordinates to the destination texture/tile dimensions instead of treating width and height as the viewport. In Final Cut's zoomed viewer path, Inner Stretch OSC drawing keeps host canvas X and Y direct; the reusable overlay renderer flips Y only when converting those surface pixels into centered Metal vertex coordinates. If vertical pan drift appears, compare the host callback canvas points and X/Y symmetry before adding Y-specific math. Inner Stretch edit-mode dimming is rendered by the filter output so it follows the clip/image, while Inner Stretch OSC owns the interactive outline, handles, hover highlights, hit testing, and drag writeback. Creating /tmp/AnyUprightStretchOSC.debug enables temporary OSC coordinate logging to /tmp/AnyUprightStretchOSC.log; creating /tmp/AnyUprightGeoCalib.debug enables Horizon GeoCalib analysis logging to /tmp/anyupright-geocalib-debug.log. Leave both flags absent during normal use.
  • Stretch object-space conversion: AnyUprightGeometry.stretchObjectPoints, innerStretchObjectPoints, sourceCornerPixelOffset, and cornerPixelOffset own the Motion/FxPlug handle coordinate semantics so corner names, X direction, and Y direction stay testable outside the host app. Both Stretch filters store and expose the four handles as pixel offsets.
  • Upright candidate slots: fixed inspector slot IDs, score gating, object/image coordinate conversion, selection limits, and onscreen hit testing live in AnyUprightUprightCandidates.swift; FxPlug parameter read/write remains in the effect class.
  • Coordinate-system notes: Y-axis semantics differ across image/output pixels, FxPlug object space, host canvas events, Metal overlay drawing, viewer/video rectangles, render tile sampling, guided reference-line solving, and Metal texture-boundary matrices. Start with docs/engineering-notes/stretch-coordinate-layer-contract.md, then read the focused notes under docs/engineering-notes/ before changing Y-axis conversion, hit testing, OSC drawing, parameter writeback, edit-preview sampling, Upright guide geometry, or render matrix boundaries.
  • Upright's Metal warp uses nearest-neighbor sampling only for an identity passthrough with equal source/output dimensions, preventing a no-op effect from softening the image through an unnecessary bilinear resample. Any non-identity transform continues to use linear sampling.

Playback rendering should use precomputed parameters only. Detection should be explicit, cached, or analysis-driven instead of happening on every frame.

The current traditional line detector is a CPU reference implementation based on Sobel edges, gradient-constrained Hough voting, and simple non-maximum suppression. It remains available to reference tests and non-model workflows, but Semi Auto and Auto do not use it as a ScaleLSD fallback. Exact transform parameters are still solved by the shared geometry layer from the selected reference lines.

Research Notes

  • Apple Vision has VNDetectHorizonRequest, whose result is a VNHorizonObservation; this remains the lightweight fallback if the Core ML GeoCalib path cannot run.
  • Roll-only horizon leveling research is tracked in docs/horizon-rotation-research.md. Keep this separate from Inner Stretch, Outer Stretch, and centered perspective correction research: the current Horizon milestone validates only rotation angle accuracy and affine rotation render behavior.
  • Upright auto/semi-auto validation research is tracked in docs/upright-auto-validation-research.md. ScaleLSD is the production model path; the retired M-LSD measurements remain only as historical comparison data.
  • ScaleLSD is the primary production Upright candidate detector for Semi Auto and Auto. AnyUprightScaleLSDDetector.swift uses the shared Apple Silicon Metal resampler to produce its normalized fixed-512 grayscale input, AnyUprightScaleLSDCoreML.swift owns a model-specific facade over an independent .fixed512 lifecycle cache, and AnyUprightScaleLSDPostprocessor.swift reproduces official junction/HAFM/wireframe decoding. Plugin registration asynchronously prewarms the fixed model, and Analyze uses the same configurable 15/30/60-second lifecycle policy as GeoCalib without sharing sessions or expiry state. The postprocessor uses a threshold-bounded uniform spatial grid for nearest-junction lookup; its retained linear implementation is a deterministic test oracle, not the production path. The ignored local FP16 model resource must exist at AnyUpright/Plugin/ScaleLSDCoreML/scalelsd_neural_forward.mlmodelc; Xcode packages it in the XPC service. If ScaleLSD cannot complete, later analysis callbacks may retry and cleanup preserves existing candidates; auto modes do not fall back to CPU Hough.
  • Semi Auto and Auto route the raw fixed-512 lines through AnyUprightUprightV2CandidateGenerator.swift, which reproduces the frozen V2 line deduplication, VP clustering, GeoCalib soft prior, discrete homography generation, crop/Jacobian scoring, and supporter features. AnyUprightUprightV2TreeModel.swift evaluates the bundled LightGBM pair and risk models under Plugin/UprightV2. Auto retains the risk gate and conservatively produces no selected guides when it fails; Semi Auto bypasses that gate so the user can inspect the best-ranked family. An unavailable GeoCalib prior falls back to the V2 no-prior branch; an unavailable V2 model or selector failure falls back to the prior score-only behavior.
  • The selected V2 VP family is reduced with a deterministic quality-and-spatial-coverage selector. Auto receives at most two representative lines per requested orientation; Semi Auto receives at most ten per requested orientation, initially unselected. Full exports both families, while Vertical and Horizontal export only the requested family from the same ranked Full hypothesis. All modes then use the same guided matrix solver as Manual. Switching from Semi Auto to Auto recomputes both displayed selection and runtime correction regardless of Edit Mode, so persisted manual selected flags and Semi Auto correction parameters cannot leak into Auto. The older offline proposal implementation in AnyUprightUprightProposal.swift is not part of production writeback.
  • FxAnalysis only accepts a CMTimeRange; it has no exact-one-frame request. Horizon and Upright use AUFxAnalysisTransaction to request two input sampling intervals while retaining the verified 0.05-second minimum. The shared transaction owns busy checks, saved timeline time, generation-protected request state, first-usable-frame claim/relinquish, and cleanup outcome snapshots. Because Final Cut Pro can pair changing callback times with stale IOSurface pixels, both effects also use the shared render snapshot cache and AUAnalysisFrameSource; Horizon's GeoCalib, verifier, and Hough stages consume the same selected source, while Upright dispatches ScaleLSD through that source abstraction. Each effect still owns detection policy and parameter writeback. A rejected Horizon estimate is completed without a value, while a valid empty Upright detection is a produced result that intentionally clears old slots. Zero callbacks or frames that never complete preparation preserve existing parameters and candidates. See docs/engineering-notes/shared-analysis-infrastructure.md.
  • Upright Semi Auto and Auto OSC candidates are line-only controls: they do not draw endpoint handles and cannot be dragged. Every candidate uses the same orientation-specific color and 3-point line thickness as Manual guides; selected candidates additionally extend a same-color dashed direction guide to the canvas boundaries. Semi Auto still supports clicking the solid candidate line to toggle selection, while Auto remains read-only.
  • Current Horizon implementation uses GeoCalib primary roll estimation gated by roll_uncertainty <= 3 deg plus rejection when two or more lightweight verifiers disagree by more than 10 degrees. The Swift/Core ML migration was verified against the Python fixed-NMF baseline on the 2,000-image LaMAR2k rotation set before project integration. Project-owned pieces now include GeoCalib preprocessing/gate/verifier glue in AnyUprightGeoCalibHorizonDetector.swift, the LM optimizer in AnyUprightGeoCalibOptimizer.swift, the Core ML neural-forward runtime and shape-keyed lifecycle facade in AnyUprightGeoCalibCoreML.swift, the shared lifecycle algorithm in AnyUprightCoreMLSessionLifecycle.swift, and the ignored local Core ML model bundle under AnyUpright/Plugin/GeoCalibCoreML/. Motion and Final Cut Pro 12.2 have both been verified to run Analyze Horizon, accept GeoCalib results, and write only Rotation.
  • Horizon and Upright share AUAppleSiliconImageResampler, which delegates high-quality downsampling to Apple's MPSImageLanczosScale and uses a small project Metal kernel only for RGB/grayscale NCHW packing. Horizon retains nearest-shape aspect-fill center crop and RGB output; Upright retains fixed 512x512 stretch and grayscale output. The shared production path has no Core Image or Swift CPU preprocessing fallback. See docs/engineering-notes/shared-analysis-infrastructure.md.
  • Performance validation should use the Wrapper Application Release build; Debug uses Swift -Onone and Metal debug info. Creating /tmp/AnyUprightGeoCalib.debug enables Horizon GeoCalib host logs in both Debug and Release at /tmp/anyupright-geocalib-debug.log, including click-to-cleanup, RGB render, preprocessing, Core ML cache/load/predict, optimizer gate, verifier, and writeback timings.
  • Creating /tmp/AnyUprightUprightAnalysis.debug enables Upright ScaleLSD stage timing at /tmp/AnyUprightUprightAnalysis.log. The flag is checked only for diagnostics; normal production analysis does not write this log.
  • tools/build-geocalib-runtime-bundle.py is retained only for historical Swift/Metal GeoCalib prototype reproduction; the plug-in no longer ships or calls the runtime bundle.
  • tools/build-geocalib-coreml-fixed-shapes.py builds fixed-shape GeoCalib ML Program graphs for 4:3, 3:4, 16:9, 9:16, 1:1, 3:2, 2:3, and 2.35:1 from the verified algorithm workspace, compiles them with xcrun coremlcompiler, and copies the resulting .mlmodelc directories into the ignored local AnyUpright/Plugin/GeoCalibCoreML/ resource folder.
  • tools/dedupe-geocalib-coreml-weights.sh validates that every non-canonical Core ML weight.bin is byte-identical to neural_forward_320x416.mlmodelc/weights/weight.bin, then replaces it with a relative symlink. The script is safe to rerun and refuses to modify weights that do not hash-match.
  • Flexible-shape Core ML was validated as an experiment, not as the production path. torch.export + RangeDim and torch.export + EnumeratedShapes neural-forward packages both matched the fixed-shape Core ML outputs within 8e-6 max tensor diff on tested 4:3 images, but warm prediction stayed around 353-366 ms for 4:3 inputs instead of 27-29 ms; synthetic 16:9-style shapes stayed around 436-457 ms.
  • Motion 6.2 Release validation of the current 4:3 Horizon.moef test image measured the fixed-shape Core ML path at about 503 ms from Analyze Horizon click/start to parameter writeback after the 15-second model-unload window, and about 279 ms with an in-memory cached Core ML session. The detector body measured 276.7 ms cold and 142.0 ms cached; Core ML itself measured 178.2 ms cold (67.1 ms session load, 110.9 ms predict) and 37.5 ms cached. The old interpretation that Motion did not expose a Metal texture was incomplete: the FxAnalysis frame had an IOSurface but reported registry ID zero, and strict device matching prevented texture creation. The Apple Silicon analysis path now resolves that case through the system default Metal device and has no CI/CPU preprocessing fallback.
  • Apple Vision also has VNDetectRectanglesRequest and VNDetectDocumentSegmentationRequest, both of which can return rectangle corner observations. Inner Stretch intentionally does not use them because its current product workflow is manual-only.
  • Core Image's CIFilter.perspectiveCorrection() is the platform reference for input-selection-to-rectangular-output semantics: four input image corners map to the output image corners. AnyUpright uses its own Metal renderer for FxPlug playback, but the Stretch Inner Stretch mode follows the same conceptual direction.
  • Lightroom's Upright modes include Level, Vertical, Auto, Full, and Guided workflows. Guided Upright lets users draw guides that should become horizontal or vertical, which matches the planned manual reference-line model.
  • OpenCV's HoughLines / HoughLinesP and LineSegmentDetector are the practical reference algorithms for candidate line extraction. The repo should keep the public data model independent from OpenCV so a future implementation can choose Vision, traditional CPU code, Metal kernels, or a small pre-trained model without changing render semantics.
  • FxPlug 4 provides FxAnalysis for explicit frame analysis and FxOnScreenControl for canvas drawing, hit testing, and mouse events. Automatic and semi-automatic modes should analyze a representative frame, write keyframeable parameters, and let the existing Metal warp renderer handle playback.
  • Open-source search did not turn up a usable FxPlug corner-pin onscreen-control implementation. Checked public FxPlug examples and plug-ins include FxKit, Spectra, GyroflowToolbox, pravMotion, FxBrightness, and SpliceKit; they do not provide a working four-corner FxOnScreenControl reference. FxFactory Reverse Corner Pin is the closest product UX reference, but it is closed source. Its public product page describes the target behavior as stretching a perspective area defined by four pins into a rectangle, with keyframeable pins for static shots or camera motion.
  • For Reverse Corner Pin-style behavior, the closest open implementation references found so far are OBS/StreamFX corner-pin or 3D-transform effects. They are useful for render/math semantics but not for FxPlug OSC interaction because they do not exercise Motion/FCP's object-space to canvas-space conversion, hit testing, or parameter writeback APIs.
  • FxPlug angle parameters are handled as radians in the current Motion validation path: getFloatValue returns angle slider values in radians, and setFloatValue writes angle parameter values that Motion displays after radians-to-degrees conversion. This differs from the FxPlug SDK header comment that says angle writes use degrees, so Horizon and Upright keep internal analysis rotation values in radians and write radians back to angle parameters. The validation history is archived under .agent-work/debug/.
  • Upright V2 always ranks a Full Manhattan hypothesis for Semi Auto and Auto. Full exposes both supporting line families, while Vertical and Horizontal expose only the requested family from that same hypothesis. Direction changes therefore affect guide selection and the shared downstream transform mode, not the V2 camera-hypothesis scoring algorithm.

Validation Expectations

For meaningful functionality changes, validate at the lowest level that proves the behavior:

  • Geometry math: deterministic unit tests or sample vectors.
  • Metal warp: visual test frames or known point mapping checks.
  • FxPlug integration: build the wrapper app target and verify the plugin loads in Motion or Final Cut Pro.
  • Final Cut behavior: verify published parameters, keyframes, proxy resolution, and clip trim/retime behavior when possible.

Current command-line checks:

xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightOuterStretchOSCPreviewGeometry.swift AnyUpright/Plugin/AnyUprightLineDetection.swift AnyUpright/Plugin/AnyUprightUprightCandidates.swift AnyUprightTests/AnyUprightGeometryTests.swift -o /tmp/AnyUprightGeometryTests && /tmp/AnyUprightGeometryTests
xcrun swiftc AnyUpright/Plugin/AnyUprightGeoCalibPreprocessGeometry.swift AnyUprightTests/AnyUprightGeoCalibPreprocessGeometryTests.swift -o /tmp/AnyUprightGeoCalibPreprocessGeometryTests && /tmp/AnyUprightGeoCalibPreprocessGeometryTests
xcrun swiftc AnyUpright/Plugin/AnyUprightImageResampler.swift AnyUprightTests/AnyUprightImageResamplerTests.swift -framework Metal -framework MetalPerformanceShaders -o /tmp/AnyUprightImageResamplerTests && /tmp/AnyUprightImageResamplerTests
xcrun swiftc AnyUpright/Plugin/AnyUprightGeoCalibOptimizer.swift AnyUprightTests/AnyUprightGeoCalibOptimizerTests.swift -o /tmp/AnyUprightGeoCalibOptimizerTests && /tmp/AnyUprightGeoCalibOptimizerTests /Users/ibobby/Temp/AnyUprightAlgorithmWorkDirectory/fixtures/geocalib_optimizer_3
xcrun swiftc AnyUpright/Plugin/AnyUprightAnalysisDiagnostics.swift AnyUpright/Plugin/AnyUprightCoreMLSessionLifecycle.swift AnyUprightTests/AnyUprightCoreMLSessionLifecycleTests.swift -o /tmp/AnyUprightCoreMLSessionLifecycleTests && /tmp/AnyUprightCoreMLSessionLifecycleTests
xcrun swiftc AnyUpright/Plugin/AnyUprightLocalization.swift AnyUpright/Plugin/AnyUprightAnalysisDiagnostics.swift AnyUpright/Plugin/AnyUprightAnalysisStatus.swift AnyUprightTests/AnyUprightAnalysisStatusTests.swift -o /tmp/AnyUprightAnalysisStatusTests && /tmp/AnyUprightAnalysisStatusTests
xcrun swiftc AnyUpright/Plugin/AnyUprightLocalization.swift AnyUprightTests/AnyUprightLocalizationTests.swift -o /tmp/AnyUprightLocalizationTests && /tmp/AnyUprightLocalizationTests .
xcrun swiftc AnyUpright/Plugin/AnyUprightStretchKeyframePolicy.swift AnyUprightTests/AnyUprightStretchKeyframePolicyTests.swift -framework CoreMedia -o /tmp/AnyUprightStretchKeyframePolicyTests && /tmp/AnyUprightStretchKeyframePolicyTests
xcrun swiftc AnyUpright/Plugin/AnyUprightFxAnalysisPolicy.swift AnyUprightTests/AnyUprightFxAnalysisPolicyTests.swift -o /tmp/AnyUprightFxAnalysisPolicyTests && /tmp/AnyUprightFxAnalysisPolicyTests
xcrun swiftc AnyUpright/Plugin/AnyUprightEffectKind.swift AnyUprightTests/AnyUprightAnalysisFrameCapturePolicyTests.swift -o /tmp/AnyUprightAnalysisFrameCapturePolicyTests && /tmp/AnyUprightAnalysisFrameCapturePolicyTests
xcrun swiftc AnyUpright/Plugin/AnyUprightAnalysisDiagnostics.swift AnyUpright/Plugin/AnyUprightFxAnalysisPolicy.swift AnyUpright/Plugin/AnyUprightFxAnalysisTransaction.swift AnyUprightTests/AnyUprightFxAnalysisTransactionTests.swift -o /tmp/AnyUprightFxAnalysisTransactionTests && /tmp/AnyUprightFxAnalysisTransactionTests
xcrun swiftc AnyUpright/Plugin/AnyUprightAnalysisDiagnostics.swift AnyUprightTests/AnyUprightAnalysisDiagnosticsTests.swift -o /tmp/AnyUprightAnalysisDiagnosticsTests && /tmp/AnyUprightAnalysisDiagnosticsTests
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightUprightCandidates.swift AnyUpright/Plugin/AnyUprightPluginDefaults.swift AnyUprightTests/AnyUprightPluginDefaultsTests.swift -o /tmp/AnyUprightPluginDefaultsTests && /tmp/AnyUprightPluginDefaultsTests
xcrun swiftc AnyUpright/Plugin/AnyUprightCoreMLMultiArrayIO.swift AnyUprightTests/AnyUprightCoreMLMultiArrayIOTests.swift -o /tmp/AnyUprightCoreMLMultiArrayIOTests && /tmp/AnyUprightCoreMLMultiArrayIOTests
xcrun swiftc AnyUpright/Plugin/AnyUprightAnalysisDiagnostics.swift AnyUpright/Plugin/AnyUprightCoreMLMultiArrayIO.swift AnyUpright/Plugin/AnyUprightCoreMLSessionLifecycle.swift AnyUpright/Plugin/AnyUprightGeoCalibNeuralOutput.swift AnyUpright/Plugin/AnyUprightGeoCalibCoreML.swift AnyUprightTests/AnyUprightGeoCalibCoreMLCacheTests.swift -o /tmp/AnyUprightGeoCalibCoreMLCacheTests && /tmp/AnyUprightGeoCalibCoreMLCacheTests /Users/ibobby/Projects/AnyUpright
xcrun swiftc -O AnyUpright/Plugin/AnyUprightAnalysisDiagnostics.swift AnyUpright/Plugin/AnyUprightCoreMLMultiArrayIO.swift AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightLineDetection.swift AnyUpright/Plugin/AnyUprightCoreMLSessionLifecycle.swift AnyUpright/Plugin/AnyUprightGeoCalibNeuralOutput.swift AnyUpright/Plugin/AnyUprightGeoCalibCoreML.swift AnyUpright/Plugin/AnyUprightGeoCalibOptimizer.swift AnyUpright/Plugin/AnyUprightGeoCalibPreprocessGeometry.swift AnyUpright/Plugin/AnyUprightGeoCalibHorizonDetector.swift tools/evaluate-swift-geocalib-rotation.swift -o /tmp/AnyUprightSwiftGeoCalibFullValidationCompileCheck
xcrun swiftc AnyUpright/Plugin/CommandQueuePool.swift AnyUprightTests/AnyUprightMetalDeviceCacheTests.swift -o /tmp/AnyUprightMetalDeviceCacheTests && /tmp/AnyUprightMetalDeviceCacheTests
xcrun swiftc AnyUpright/Plugin/CommandQueuePool.swift tools/stress-metal-device-cache.swift -o /tmp/AnyUprightStressMetalDeviceCache && /tmp/AnyUprightStressMetalDeviceCache
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightScaleLSDPostprocessor.swift AnyUprightTests/AnyUprightScaleLSDPostprocessorTests.swift -o /tmp/AnyUprightScaleLSDPostprocessorTests && /tmp/AnyUprightScaleLSDPostprocessorTests
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightUprightCandidates.swift AnyUpright/Plugin/AnyUprightScaleLSDPostprocessor.swift AnyUpright/Plugin/AnyUprightScaleLSDPreprocessor.swift AnyUprightTests/AnyUprightScaleLSDProductionTests.swift -o /tmp/AnyUprightScaleLSDProductionTests && /tmp/AnyUprightScaleLSDProductionTests
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightScaleLSDPostprocessor.swift AnyUpright/Plugin/AnyUprightUprightV2TreeModel.swift AnyUpright/Plugin/AnyUprightUprightV2Ranker.swift AnyUprightTests/AnyUprightUprightV2RankerTests.swift -o /tmp/AnyUprightUprightV2RankerTests && /tmp/AnyUprightUprightV2RankerTests AnyUpright/Plugin/UprightV2/pair-ranker.txt AnyUpright/Plugin/UprightV2/risk-model.txt AnyUprightTests/Fixtures/upright-v2-ranker-parity.json
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightScaleLSDPostprocessor.swift AnyUpright/Plugin/AnyUprightUprightV2TreeModel.swift AnyUpright/Plugin/AnyUprightUprightV2Ranker.swift AnyUpright/Plugin/AnyUprightUprightV2Geometry.swift AnyUpright/Plugin/AnyUprightUprightV2CandidateGenerator.swift AnyUprightTests/AnyUprightUprightV2CandidateGeneratorTests.swift -o /tmp/AnyUprightUprightV2CandidateGeneratorTests && /tmp/AnyUprightUprightV2CandidateGeneratorTests AnyUprightTests/Fixtures/upright-v2-generator-parity.json
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightUprightCandidates.swift AnyUpright/Plugin/AnyUprightScaleLSDPostprocessor.swift AnyUpright/Plugin/AnyUprightUprightV2TreeModel.swift AnyUpright/Plugin/AnyUprightUprightV2Ranker.swift AnyUpright/Plugin/AnyUprightUprightV2Geometry.swift AnyUpright/Plugin/AnyUprightUprightV2CandidateGenerator.swift AnyUpright/Plugin/AnyUprightUprightV2GuideSelector.swift AnyUprightTests/AnyUprightUprightV2GuideSelectorTests.swift -o /tmp/AnyUprightUprightV2GuideSelectorTests && /tmp/AnyUprightUprightV2GuideSelectorTests
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightUprightCandidates.swift AnyUpright/Plugin/AnyUprightUprightProposal.swift AnyUprightTests/AnyUprightUprightProposalTests.swift -o /tmp/AnyUprightUprightProposalTests && /tmp/AnyUprightUprightProposalTests
xcrun swiftc tools/validate-fxplug-manifest.swift -o /tmp/AnyUprightValidateManifest && /tmp/AnyUprightValidateManifest .
xcrun swiftc tools/audit-feature-surface.swift -o /tmp/AnyUprightAuditFeatureSurface && /tmp/AnyUprightAuditFeatureSurface .
xcrun swift tools/generate-test-assets.swift .agent-work/test-assets
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift AnyUpright/Plugin/AnyUprightLineDetection.swift tools/analyze-test-assets.swift -o /tmp/AnyUprightAnalyzeAssets && /tmp/AnyUprightAnalyzeAssets .agent-work/test-assets
xcrun swiftc AnyUpright/Plugin/AnyUprightGeometry.swift tools/render-warp-previews.swift -o /tmp/AnyUprightRenderWarpPreviews && /tmp/AnyUprightRenderWarpPreviews .agent-work/test-assets .agent-work/warp-previews
xcrun swiftc tools/validate-warp-previews.swift -o /tmp/AnyUprightValidateWarpPreviews && /tmp/AnyUprightValidateWarpPreviews .agent-work/warp-previews
SDK=$(xcrun --sdk macosx --show-sdk-path) && xcrun swiftc -typecheck AnyUpright/Plugin/*.swift -sdk "$SDK" -F /Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks -F /Library/Developer/Frameworks -I AnyUpright/Plugin -import-objc-header "AnyUpright/Plugin/XPC Service-Bridging-Header.h"
./scripts/build-debug-xpc.sh
xcodebuild -project AnyUpright.xcodeproj -scheme "Wrapper Application" -configuration Release -derivedDataPath /tmp/AnyUprightDerivedDataRelease build

Full 2,000-image GeoCalib validation uses tools/run-swift-geocalib-full-validation.sh with the LaMAR2k dataset in /Users/ibobby/Temp/AnyUprightAlgorithmWorkDirectory/data/lamar2k.

If Xcode reports a missing Metal Toolchain during build, install it with Xcode's suggested xcodebuild -downloadComponent MetalToolchain before repeating the full build.

Debug and Release builds have separate host identities. The normal Debug flow uses ./scripts/build-debug-xpc.sh to build only the canonical AnyUpright XPC Service.pluginkit under /private/tmp/AnyUprightDerivedData; it does not build a Debug .app. Debug uses wrapper ID AnyUpright-Debug, XPC ID AnyUpright-XPC-Service-Debug, its own FxPlug group and seven plugin UUIDs, and filter names ending in (Debug). Release remains AnyUpright.app with the original production IDs and unsuffixed names. Installation and PlugInKit registration of the Debug binary are explicit developer/Agent-managed operations.

Multiple paths registered under the same configuration-specific bundle ID are still ambiguous. In particular, mixing the canonical /private/tmp/AnyUprightDerivedData Debug .pluginkit with Xcode's default Derived Data or copied Debug products can leave Motion using a stale Debug PlugInKit object. If this happens, quit or kill the stale wrapper/XPC process, unregister the exact stale .pluginkit, register the intended binary, and then restart Motion or re-add the effect instance. Query the two identities independently:

pluginkit -m -ADv -i AnyUpright-XPC-Service-Debug
pluginkit -m -ADv -i AnyUpright-XPC-Service

Release host validation has one additional invariant: AnyUpright-XPC-Service must resolve only to /Applications/AnyUpright.app/Contents/PlugIns/AnyUpright XPC Service.pluginkit. The Release build script's /private/tmp/AnyUprightDerivedDataRelease output is never a registration target. A temporary Release path in this query is a stale local-debug registration and must be removed before judging Motion or Final Cut Pro.

Motion templates bind to the selected FxPlug UUID. Use only the unsuffixed Release filters when creating distributable Final Cut Effect templates. A template created from a (Debug) filter intentionally depends on the Debug UUID and is not compatible with a Release-only installation.

Local Test Assets

The repository includes a deterministic Swift generator for host-app validation images:

xcrun swift tools/generate-test-assets.swift .agent-work/test-assets

It writes ignored PNG files under .agent-work/test-assets/:

  • horizon-tilted-8deg.png: a strong 8-degree tilted horizon for Analyze Horizon.
  • stretch-phone-screen.png: a skewed phone-screen grid for both Stretch modes.
  • upright-facade-perspective.png: a perspective facade grid for Upright auto, semi-auto, and guided workflows.

tools/analyze-test-assets.swift compiles with the shared geometry and line-detection files. It verifies that the generated images produce enough candidate lines, that Horizon's fallback detector preserves Hough vote order for the dominant tilted line, and that Upright can solve bounded parameters from the detected candidates. This is algorithm validation only; it does not replace Motion/FCP onscreen-control testing.

tools/render-warp-previews.swift compiles with the shared geometry file and writes preview PNGs under .agent-work/warp-previews/:

  • horizon-fill-preview.png: applies the horizon rotation with fill enabled.
  • stretch-inner-stretch-adjuster-preview.png: keeps the original image still while rendering a CPU reference overlay for Inner Stretch mapping semantics. In the live plug-in, filter output owns the dimming path and OSC owns the interactive outline/handles.
  • stretch-inner-stretch-apply-preview.png: maps the known phone-screen input selection to the full output frame.
  • stretch-output-corners-preview.png: applies output-corner dragging semantics.
  • upright-centered-preview.png: applies centered vertical/horizontal perspective plus rotation.

The preview renderer is CPU-only and exists to prove mapping semantics. Playback in Motion and Final Cut still uses the shared Metal warp.

For Outer Stretch exterior-preview debugging, use the Metal host simulator instead of the CPU preview. It deliberately keeps the plug-in's offscreen preview pass, OSC texture pass, and Motion's final surface composition as separate stages:

xcrun swiftc -parse-as-library \
  -import-objc-header 'AnyUpright/Plugin/XPC Service-Bridging-Header.h' \
  -F /Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks \
  AnyUpright/Plugin/AnyUprightGeometry.swift \
  AnyUpright/Plugin/AnyUprightOuterStretchOSCPreviewGeometry.swift \
  tools/render-outer-stretch-motion-host.swift \
  -o /tmp/AnyUprightOuterStretchMotionHost

Pass the default.metallib from a Debug XPC build and an output PNG path. The fixed pure-blue fixture makes exterior alpha placement visible without source-image detail.

Motion Validation Checklist

After building the wrapper app, Motion should see four independent FxPlug filters under the AnyUpright group. Use a 1920 x 1080 project and import the generated PNGs as still images.

Horizon:

  • Apply AnyUpright Horizon to a photo-like tilted frame first. The GeoCalib path is conservative and may reject synthetic line art; use horizon-tilted-8deg.png mainly for affine render/fill checks or fallback-path debugging.
  • Click Analyze Horizon; when GeoCalib accepts the frame, Rotation should move near the opposite of the visible tilt and the horizon should level out. When GeoCalib rejects the frame, Rotation should remain unchanged.
  • Enable Fill Frame; the render should zoom enough to hide rotation black edges.

Stretch:

  • Apply AnyUpright Inner Stretch to stretch-phone-screen.png.
  • With Edit Mode on, the outside area should be dimmed to 70% brightness while the image itself remains unwarped. The dimming path is filter output and should still appear even if Motion's Publish OSC checkbox is off.
  • Verify the Inner Stretch inspector exposes Ratio: None / Fit / Fill alongside the manual workflow controls; no detector button, score threshold, or candidate-selection toggle should appear.
  • Enable Publish OSC when testing the interactive outline and handles. The four handles should start at the four input-frame corners, and dragging them around the phone-screen area should not warp the image while editing.
  • Verify the four collapsed corner coordinate groups are visible in Inner Stretch and remain synchronized with onscreen handle movement.
  • Turn Edit Mode off. With Ratio: None, the selected screen area should map to the full output frame. With Fit, it should preserve the estimated selection ratio in a centered rectangle and continue the same quadrilateral projection outside it. With Fill, it should preserve that ratio while center-cropping overflow to cover the output. The handles should be hidden in all three cases.
  • Apply AnyUpright Outer Stretch to stretch-phone-screen.png.
  • Drag the four onscreen handles; the image should warp in realtime.
  • Edit Mode should be hidden and the four corner coordinate groups should be visible in Outer Stretch.

Upright:

  • Apply AnyUpright Upright to upright-facade-perspective.png.
  • The inspector should expose only Direction, Analyze, Mode, Auto Crop, and Edit Mode.
  • With Edit Mode on, the image should remain unwarped and Upright OSC should appear inside the actual video frame, not at absolute preview-window coordinates.
  • In Manual mode, switch Direction between Vertical, Horizontal, and Full; only the matching default guide lines should appear. Drag endpoints to update correction, then click a guide line to disable it and verify its endpoints become gray.
  • Turn Edit Mode off; the stored correction should render through the shared Metal warp and all Upright OSC should disappear. Toggle Auto Crop and verify it zooms the rendered correction to avoid exposed source edges when possible.
  • In Semi Auto mode, click Analyze; only analyzed candidate lines matching Direction should be drawn and hittable. Full direction should show up to ten spatially distributed representatives per orientation from the best V2 family even when its risk score exceeds the Auto gate. Clicking candidates should toggle correction selection without allowing more than two selected lines per orientation.
  • In Auto mode, click Analyze; every direction should use the V2 Full-hypothesis selector and display up to two representative lines for each requested orientation.

Open Decisions

  • Whether Motion template files should be tracked in the repository or generated/copied from a documented local template location.
  • Minimum supported Final Cut Pro, Motion, Xcode, and FxPlug SDK versions beyond the current macOS 14.0 / Apple Silicon build boundary.
  • Whether a future commercial distribution should add Developer ID signing and notarization beyond the current ad-hoc GitHub release model.
  • Multi-locale policy beyond the current English and Simplified Chinese resources.
  • Xcode Test Navigator integration for the current lightweight geometry tests.
  • Automated Metal shader validation.

Motion And Final Cut Templates

This repository ships the four effects as FxPlug filters registered by the wrapper app and as four tracked Final Cut Effect templates. The Wrapper Application's Motion Effects Install action embeds the .moef files and preview PNGs, asks the user to authorize their ~/Movies folder, and installs them under ~/Movies/Motion Templates.localized/Effects.localized/AnyUpright/.

For Final Cut templates that need onscreen dragging, the Motion template must include the host Publish OSC setting for the FxPlug filter. In the local .moef XML this appears as the built-in filter parameter id="10005" with name="Publish OSC" and value="1". Publishing only user-facing parameters such as Edit Mode is not enough: Final Cut can still render Inner Stretch's filter-output dimming, but it may not instantiate or dispatch mouse events to the FxOnScreenControl that draws and hits the interactive handles.

The current local development Final Cut templates live under ~/Movies/Motion Templates.localized/Effects.localized/AnyUpright/. Inner Stretch/Inner Stretch.moef publishes Inner Stretch controls. Horizon/Horizon.moef publishes Analyze Horizon (./102), Rotation (./100), Fill Frame (./101), and Defaults (./103), and must also publish the built-in Publish OSC setting for its transient analysis status to appear in Final Cut. Motion Creator Studio 6.3 does not expose the normal publish menu for FxPlug push buttons; button targets must be added to the saved .moef <publishSettings> XML using the current filter object ID. See release-process.md and engineering-notes/motion-fxplug-button-publishing.md. Upright/Upright.moef should publish the five visible controls Direction, Analyze, Mode, Auto Crop, and Edit Mode, plus the built-in Publish OSC setting. After adding or changing a local template, restart Final Cut Pro before judging Effects Browser visibility.

About

4个Final Cut插件:自动水平、变换1、变换2(类似文档扫描)、Upright(类似lightroom)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages