MCPcopy
hub / github.com/cpaczek/skylight / visionTick

Method visionTick

tracker/src/loop.ts:907–1166  ·  view source on GitHub ↗

* Vision pass (Phase C): track-before-detect. Every candidate blob is * converted to WORLD az/el at frame time and fed to a small track table; * the plane is whichever track MOVES like the ADS-B prediction says the * plane moves (clouds are world-static, noise is incoherent). The locked

()

Source from the content-addressed store, hash-verified

905 * Everything is referenced to FRAME TIME via per-frame arrival timestamps.
906 */
907 private async visionTick(): Promise<void> {
908 const cfg = this.cfg();
909 const tracking =
910 (this.mode === "auto" || this.mode === "manual") && this.current !== null;
911 if (!cfg.vision.enabled || !tracking) {
912 this.lastDetection = null;
913 this.tracks.reset();
914 this.prevLuma = null;
915 this.prevAim = null;
916 this.corrAz = 0;
917 this.corrEl = 0;
918 return;
919 }
920 if (this.visionBusy) return;
921 const frame = this.video.latestFrame();
922 // Captured WITH the frame — by the time the detector finishes, a newer
923 // frame may have arrived and overwritten the stream's timestamp.
924 const frameArrivedAt = this.video.latestFrameAt();
925 const pose = this.driver().getPose();
926 if (!frame || !pose || !this.video.status().running) return;
927
928 const hfov = hfovFromZoomUnits(pose.zoomUnits, cfg.zoom.fovLut);
929 const vfov = hfov * (9 / 16);
930 const aim = worldFromMount(pose, cfg.mount);
931 const predicted = this.lastState?.target.predicted ?? null;
932
933 // Frame-time reference: the camera and the plane both moved while this
934 // frame crossed the RTSP/MJPEG pipeline. Arrival is timestamped exactly;
935 // encodeLagMs covers the residual (exposure -> encode -> RTSP -> decode).
936 const preT = Date.now();
937 const frameT = (frameArrivedAt > 0 ? frameArrivedAt : preT) - cfg.vision.encodeLagMs;
938 const sign = Math.sign(cfg.mount.panGain) || 1;
939 const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
940
941 // Where should the plane be in THIS frame? The locked track's WORLD
942 // position advanced to frame time (world-frame stickiness — it moves
943 // with both the camera and the plane by construction), else ADS-B.
944 let exX = 0.5;
945 let exY = 0.5;
946 const lockedBefore = this.tracks.lockedTrack();
947 const atPre = this.aimAt(frameT);
948 if (lockedBefore && atPre) {
949 const p = lockedBefore.positionAt(frameT);
950 const cosE = Math.max(0.2, Math.cos((atPre.aimEl * Math.PI) / 180));
951 exX = clamp01(0.5 + (norm180(p.azDeg - atPre.aimAz) * cosE * sign) / atPre.hfov);
952 exY = clamp01(0.5 - (p.elDeg - atPre.aimEl) / (atPre.hfov * (9 / 16)));
953 } else if (predicted) {
954 const dAz =
955 norm180(predicted.azDeg - aim.azDeg) *
956 Math.cos((predicted.elDeg * Math.PI) / 180);
957 exX = clamp01(0.5 + dAz / hfov);
958 exY = clamp01(0.5 - (predicted.elDeg - aim.elDeg) / vfov);
959 }
960
961 this.visionBusy = true;
962 try {
963 // --- camera-motion-compensated detection ---
964 // Decode this frame and diff it against the previous one AFTER cancelling

Callers 1

rescheduleTickMethod · 0.95

Calls 15

cfgMethod · 0.95
aimAtMethod · 0.95
hfovFromZoomUnitsFunction · 0.85
worldFromMountFunction · 0.85
clamp01Function · 0.85
norm180Function · 0.85
decodeLumaFunction · 0.85
estimateShiftFunction · 0.85
compensatedResidualFunction · 0.85
maskBelowHorizonFunction · 0.85
findMovingBlobsFunction · 0.85
clampFunction · 0.85

Tested by

no test coverage detected