@luxonis/visualizer-protobuf 2.63.0 → 2.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{depth-CJRDcPfJ.js → depth-CgtekUia.js} +40 -14
  2. package/dist/{deserialization.worker-CSrvuPJi.js → deserialization.worker-DIb5JYgM.js} +1 -1
  3. package/dist/{index-DVTnwOoU.js → index-1c6__YmG.js} +124 -63
  4. package/dist/{index-BEUK9fjl.js → index-4zBZhITl.js} +2 -2
  5. package/dist/{index-DZpuBiMu.js → index-7qy5F51w.js} +2 -2
  6. package/dist/{index-CAPK46zz.js → index-BKgrv32b.js} +2 -2
  7. package/dist/{index-C4v_oH5A.js → index-BTg1Juch.js} +2 -2
  8. package/dist/{index-ONl6IrAK.js → index-BaC6YOFz.js} +2 -2
  9. package/dist/{index-DT_jjA9M.js → index-Bc_qkAT2.js} +2 -2
  10. package/dist/{index-BVLZBH1f.js → index-BmFISqbo.js} +2 -2
  11. package/dist/{index-BjdZNYkh.js → index-C1xd4eqI.js} +2 -2
  12. package/dist/{index-DgjVQNM1.js → index-Cc9XbP2A.js} +2 -2
  13. package/dist/{index-DW41is0g.js → index-D1VQYW7r.js} +2 -2
  14. package/dist/{index-Cl2wsF35.js → index-D7OE7wIR.js} +2 -2
  15. package/dist/{index-8vtcOv69.js → index-Dn_s4esG.js} +2 -2
  16. package/dist/{index-t2Zi3vlL.js → index-DyDtgl5_.js} +2 -2
  17. package/dist/{index-CaL5FU3W.js → index-IOA4ZGmc.js} +2 -2
  18. package/dist/{index-CyeIwK8g.js → index-O7Hd0ZQa.js} +3 -3
  19. package/dist/{index-Ctxq9ifh.js → index-j9SA4AnC.js} +2 -2
  20. package/dist/{index-DtAhxADj.js → index-rblj1gA1.js} +2 -2
  21. package/dist/{index-WBIAVrf5.js → index-ynJe1rwL.js} +44 -33
  22. package/dist/index.js +2 -2
  23. package/dist/lib/src/components/Panel.js +1 -1
  24. package/dist/lib/src/components/PanelToolbar.d.ts.map +1 -1
  25. package/dist/lib/src/components/PanelToolbar.js +19 -9
  26. package/dist/lib/src/components/PanelToolbar.js.map +1 -1
  27. package/dist/lib/src/messaging/deserialization/video/depth.d.ts.map +1 -1
  28. package/dist/lib/src/messaging/deserialization/video/depth.js +38 -10
  29. package/dist/lib/src/messaging/deserialization/video/depth.js.map +1 -1
  30. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/ImageMode/MessageHandler.d.ts +1 -3
  31. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/ImageMode/MessageHandler.d.ts.map +1 -1
  32. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/ImageMode/MessageHandler.js +108 -53
  33. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/ImageMode/MessageHandler.js.map +1 -1
  34. package/package.json +1 -1
@@ -4666,20 +4666,46 @@ function getDistanceFromDepthDataForOffset(depthData, width, height, xOffset, yO
4666
4666
  return undefined;
4667
4667
  }
4668
4668
 
4669
- // Select the pixel based on the offsets
4670
- const [x, y] = [Math.floor(xOffset * width), Math.floor(yOffset * height)];
4671
-
4672
- // Each "pixel" is represented by 16 bytes where z value is 4 bytes offset by 8 bytes so we want to read bytes <9-12>
4673
- const offset = (y * width + x) * 16 + 8;
4674
-
4675
- // Next 4 bytes represent the z value
4676
- const zBytes = depthData.slice(offset, offset + 4);
4677
-
4678
- // Convert the 4 bytes to a float
4679
- const view = new DataView(zBytes.buffer, zBytes.byteOffset, zBytes.byteLength);
4680
- const distance = view.getFloat32(0, true); // true for little-endian
4681
-
4682
- return distance;
4669
+ // Compute the pixel index corresponding to the offsets, clamped to image bounds
4670
+ const xCenter = Math.min(width - 1, Math.max(0, Math.floor(xOffset * width)));
4671
+ const yCenter = Math.min(height - 1, Math.max(0, Math.floor(yOffset * height)));
4672
+ const pointStride = 16; // bytes per point
4673
+ const zByteOffset = 8; // z is at byte offset 8 within each point
4674
+
4675
+ // Prepare a DataView over the provided depthData for efficient reads
4676
+ const view = new DataView(depthData.buffer, depthData.byteOffset, depthData.byteLength);
4677
+
4678
+ // Average z over a 5x5 window centered at (xCenter, yCenter)
4679
+ const radius = 2;
4680
+ let sum = 0;
4681
+ let count = 0;
4682
+ for (let dy = -radius; dy <= radius; dy++) {
4683
+ const y = yCenter + dy;
4684
+ if (y < 0 || y >= height) {
4685
+ continue;
4686
+ }
4687
+ for (let dx = -radius; dx <= radius; dx++) {
4688
+ const x = xCenter + dx;
4689
+ if (x < 0 || x >= width) {
4690
+ continue;
4691
+ }
4692
+ const idx = y * width + x;
4693
+ const offset = idx * pointStride + zByteOffset;
4694
+ // Ensure we have enough bytes to read a float32
4695
+ if (offset + 4 > depthData.byteLength) {
4696
+ continue;
4697
+ }
4698
+ const z = view.getFloat32(offset, true);
4699
+ if (Number.isFinite(z)) {
4700
+ sum += z;
4701
+ count++;
4702
+ }
4703
+ }
4704
+ }
4705
+ if (count === 0) {
4706
+ return undefined;
4707
+ }
4708
+ return sum / count;
4683
4709
  }
4684
4710
 
4685
4711
  export { toString as $, AppError as A, getAllPanelIds as B, getConfigsForNestedPanelsInsideTab as C, reorderTabWithinTabPanel as D, moveTabBetweenTabPanels as E, isTabPanelConfig as F, DEFAULT_TAB_PANEL_CONFIG as G, addPanelToTab as H, createAddUpdates as I, filterMap as J, uniq as K, Logger$1 as L, useShallowMemo as M, CurrentLayoutContext as N, MessageOrderTracker as O, AppConfigurationContext as P, __rest as Q, __spreadArray as R, getDistanceFromDepthDataForOffset as S, TAB_PANEL_TYPE as T, parseMessage as U, typescript as V, fromMillis as W, parsePixelFormat as X, deserializeDepthFrame as Y, isSymbol as Z, __assign as _, baseEach as a, keys as a0, getSymbols as a1, stubArray as a2, arrayPush as a3, baseGetAllKeys as a4, getAllKeys as a5, arrayMap as a6, baseUniq as a7, useMustNotChange as a8, useCurrentLayoutActions as a9, useCurrentLayoutSelector as aa, usePanelMosaicId as ab, useSelectedPanels as ac, PANEL_TITLE_CONFIG_KEY as ad, noop as ae, useAppConfiguration as af, useValueChangedDebugLog as ag, useJsonTreeTheme as ah, baseFlatten as b, baseIteratee as c, dist as d, estimateObjectSize as e, baseDifference as f, getTag as g, baseKeys as h, baseIsEqual as i, castPath as j, baseGet as k, hasIn as l, shallowequal$1 as m, getPanelIdsInsideTabPanels as n, getPanelTypeFromId as o, removePanelFromTabPanel as p, getPanelIdForType as q, reportError as r, sendNotification as s, toKey as t, useGuaranteedContext as u, getPathFromNode as v, updateTabPanelLayout as w, getSaveConfigsPayloadForAddedPanel as x, replaceAndRemovePanels as y, inlineTabPanelLayouts as z };
@@ -1,5 +1,5 @@
1
1
  import { e as expose } from './comlink-DHMAu6X7.js';
2
- import { U as parseMessage, V as typescript, e as estimateObjectSize, W as fromMillis, d as dist, X as parsePixelFormat, Y as deserializeDepthFrame } from './depth-CJRDcPfJ.js';
2
+ import { U as parseMessage, V as typescript, e as estimateObjectSize, W as fromMillis, d as dist, X as parsePixelFormat, Y as deserializeDepthFrame } from './depth-CgtekUia.js';
3
3
  import { P as PointsAnnotationType, p as protobufsBySchema, T as Type, b as Profile } from './protobuf-B55JS-Px.js';
4
4
  import { i as isCapableOfEncodedStream } from './encoding-DqlhpFR2.js';
5
5
  import 'react';
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useReducer, useRef, useCallback, useLayoutEffect, Component, useContext, useState, useEffect, createContext, forwardRef, createElement, cloneElement, useMemo, Profiler, StrictMode } from 'react';
3
3
  import ReactDOM__default from 'react-dom';
4
- import { Z as isSymbol, $ as toString, a0 as keys, a1 as getSymbols$1, a2 as stubArray, a3 as arrayPush, a4 as baseGetAllKeys, g as getTag, a5 as getAllKeys, k as baseGet, c as baseIteratee, j as castPath, t as toKey, a6 as arrayMap$1, a7 as baseUniq, b as baseFlatten, a8 as useMustNotChange, a9 as useCurrentLayoutActions, aa as useCurrentLayoutSelector, r as reportError, A as AppError, L as Logger, u as useGuaranteedContext, ab as usePanelMosaicId, ac as useSelectedPanels, ad as PANEL_TITLE_CONFIG_KEY, ae as noop$4, o as getPanelTypeFromId, M as useShallowMemo, T as TAB_PANEL_TYPE, J as filterMap, d as dist$2, af as useAppConfiguration, ag as useValueChangedDebugLog, V as typescript, ah as useJsonTreeTheme } from './depth-CJRDcPfJ.js';
4
+ import { Z as isSymbol, $ as toString, a0 as keys, a1 as getSymbols$1, a2 as stubArray, a3 as arrayPush, a4 as baseGetAllKeys, g as getTag, a5 as getAllKeys, k as baseGet, c as baseIteratee, j as castPath, t as toKey, a6 as arrayMap$1, a7 as baseUniq, b as baseFlatten, a8 as useMustNotChange, a9 as useCurrentLayoutActions, aa as useCurrentLayoutSelector, r as reportError, A as AppError, L as Logger, u as useGuaranteedContext, ab as usePanelMosaicId, ac as useSelectedPanels, ad as PANEL_TITLE_CONFIG_KEY, ae as noop$4, o as getPanelTypeFromId, M as useShallowMemo, T as TAB_PANEL_TYPE, J as filterMap, d as dist$2, af as useAppConfiguration, ag as useValueChangedDebugLog, V as typescript, ah as useJsonTreeTheme } from './depth-CgtekUia.js';
5
5
  import { createStore, useStore } from 'zustand';
6
- import { g as generateUtilityClass, c as createAggregator, f as flatRest, b as baseSet, A as AnalyticsContext, P as PropTypes, E as ErrorDisplay, S as Stack$1, m as makeStyles$1, _ as _extends$1, W as WorkspaceContext, u as useAnalytics, a as AppEvent, L as LeftSidebarItemKeys, R as RightSidebarItemKeys, d as useTranslation, e as usePanelCatalog, h as EmptyState, i as isEmpty, j as PanelContext, k as PanelCatalogContext, l as usePanelStateStore, n as useDefaultPanelTitle, o as useWorkspaceStore, p as WorkspaceStoreSelectors, q as difference, r as usePanelContext, s as useMessagePipeline, v as v4, t as useHoverValue, w as useSetHoverValue, x as useClearHoverValue, y as useMessagePipelineGetter, z as usePanelSettingsTreeUpdate, B as PlayerCapabilities, C as assertNever, D as PlayerPresence, F as isEqual, G as isDesktopApp, H as createTheme, I as propTypesExports, J as DEFAULT_CAMERA_STATE$1, K as format$1, M as z, N as serializeError, O as stringify$1, Q as createIntl, T as createIntlCache } from './index-WBIAVrf5.js';
6
+ import { g as generateUtilityClass, c as createAggregator, f as flatRest, b as baseSet, A as AnalyticsContext, P as PropTypes, E as ErrorDisplay, S as Stack$1, m as makeStyles$1, _ as _extends$1, W as WorkspaceContext, u as useAnalytics, a as AppEvent, L as LeftSidebarItemKeys, R as RightSidebarItemKeys, d as useTranslation, e as usePanelCatalog, h as EmptyState, i as isEmpty, j as PanelContext, k as PanelCatalogContext, l as usePanelStateStore, n as useDefaultPanelTitle, o as useWorkspaceStore, p as WorkspaceStoreSelectors, q as difference, r as usePanelContext, s as useMessagePipeline, v as v4, t as useHoverValue, w as useSetHoverValue, x as useClearHoverValue, y as useMessagePipelineGetter, z as usePanelSettingsTreeUpdate, B as PlayerCapabilities, C as assertNever, D as PlayerPresence, F as isEqual, G as isDesktopApp, H as createTheme, I as propTypesExports, J as DEFAULT_CAMERA_STATE$1, K as format$1, M as z, N as serializeError, O as stringify$1, Q as createIntl, T as createIntlCache } from './index-ynJe1rwL.js';
7
7
  import { MosaicDragType, MosaicContext, MosaicWindowContext, getOtherBranch, getNodeAtPath } from 'react-mosaic-component';
8
8
  import { g as getDefaultExportFromCjs, c as commonjsGlobal, d as getAugmentedNamespace } from './protobuf-B55JS-Px.js';
9
9
  import { Link, Button, alpha, IconButton, Card, CardActionArea, CardMedia, CardContent, Typography, Container, Tooltip, Fade, ListItem, ListItemButton, ListItemText, List, TextField, InputAdornment, Popper, Grow, Paper, ClickAwayListener, Menu, MenuItem, Divider, buttonClasses, Backdrop, Chip, useTheme, alertClasses, darken, lighten, inputBaseClasses, autocompleteClasses, inputClasses, Checkbox, dialogActionsClasses, filledInputClasses, inputAdornmentClasses, listSubheaderClasses, selectClasses, tableCellClasses, ThemeProvider as ThemeProvider$1, SvgIcon, tabsClasses as tabsClasses$1, tabClasses, Tabs, Tab, ListItemIcon } from '@mui/material';
@@ -31965,6 +31965,13 @@ function getFrameIdFromImage(image) {
31965
31965
  return image.frame_id;
31966
31966
  }
31967
31967
  }
31968
+ function getTimestampFromImage(image) {
31969
+ if ("header" in image) {
31970
+ return image.header.stamp;
31971
+ } else {
31972
+ return image.timestamp;
31973
+ }
31974
+ }
31968
31975
 
31969
31976
  // This Source Code Form is subject to the terms of the Mozilla Public
31970
31977
  // License, v2.0. If a copy of the MPL was not distributed with this
@@ -32499,6 +32506,15 @@ function getAnnotationAtPath(message, path) {
32499
32506
  // License, v2.0. If a copy of the MPL was not distributed with this
32500
32507
  // file, You can obtain one at http://mozilla.org/MPL/2.0/
32501
32508
 
32509
+ const syncToleranceSec = Number(localStorage.getItem('viewer-syncToleranceSec')) || 0.15; // maximum acceptable time difference for selecting a “closest” annotation.
32510
+ const maxStalenessSec = Number(localStorage.getItem('viewer-maxStalenessSec')) || 0.5; // how long we can hold the last older annotation if no better match is available.
32511
+ const hysteresisSec = Number(localStorage.getItem('viewer-hysteresisSec')) || 0.03; // how much “stickiness” we allow before switching candidates.
32512
+
32513
+ console.log("synchronization parameters:", {
32514
+ syncToleranceSec,
32515
+ maxStalenessSec,
32516
+ hysteresisSec
32517
+ });
32502
32518
  // Have constants for the HUD items so that they don't need to be recreated and GCed every message
32503
32519
  const WAITING_FOR_BOTH_HUD_ITEM = {
32504
32520
  id: WAITING_FOR_BOTH_MESSAGES_HUD_ID,
@@ -32555,10 +32571,10 @@ class MessageHandler {
32555
32571
 
32556
32572
  #onAnnotationReceivedEvent;
32557
32573
  #annotationsFpsRefreshInterval = null;
32574
+ // Store a small buffer of recent annotations per topic with computed seconds timestamps
32558
32575
  annotationsMap = new Map();
32559
- ghostAnnotationsMap = new Map();
32560
- syncToleranceMs = 150;
32561
- ghostTimeoutMs = 1000;
32576
+ // Track last selected annotation per topic to add hysteresis and reduce flicker
32577
+ lastSelectedByTopic = new Map();
32562
32578
  /**
32563
32579
  *
32564
32580
  * @param config - subset of ImageMode settings required for message handling
@@ -32570,6 +32586,17 @@ class MessageHandler {
32570
32586
  annotationsByTopic: new Map()
32571
32587
  };
32572
32588
  this.availableAnnotationTopics = new Set();
32589
+
32590
+ // Set default values for synchronization to local storage if they're not already set
32591
+ if (localStorage.getItem('viewer-syncToleranceSec') === null) {
32592
+ localStorage.setItem('viewer-syncToleranceSec', syncToleranceSec.toString());
32593
+ }
32594
+ if (localStorage.getItem('viewer-maxStalenessSec') === null) {
32595
+ localStorage.setItem('viewer-maxStalenessSec', maxStalenessSec.toString());
32596
+ }
32597
+ if (localStorage.getItem('viewer-hysteresisSec') === null) {
32598
+ localStorage.setItem('viewer-hysteresisSec', hysteresisSec.toString());
32599
+ }
32573
32600
  }
32574
32601
  dispose() {
32575
32602
  if (this.#annotationsFpsRefreshInterval) {
@@ -32598,54 +32625,60 @@ class MessageHandler {
32598
32625
  this.handleImage(messageEvent, normalizeCompressedImage(messageEvent.message));
32599
32626
  };
32600
32627
  handleImage(message, image) {
32601
- const normalized = {
32628
+ this.lastImage = {
32602
32629
  ...message,
32603
32630
  message: image
32604
32631
  };
32605
- this.lastImage = normalized;
32606
32632
  this.#emitState();
32607
32633
  }
32608
32634
  handleCameraInfo = message => {
32609
- const cameraInfo = normalizeCameraInfo(message.message);
32610
- this.#lastReceivedMessages.cameraInfo = cameraInfo;
32635
+ this.#lastReceivedMessages.cameraInfo = normalizeCameraInfo(message.message);
32611
32636
  this.#emitState();
32612
32637
  };
32613
32638
  handleAnnotations = messageEvent => {
32614
32639
  const list = normalizeAnnotations(messageEvent.message, messageEvent.schemaName);
32615
- const timestamp = performance.now();
32640
+ const arrivalSec = performance.now() / 1000;
32616
32641
  if (list.length > 0 && messageEvent.topic) {
32617
32642
  this.#onAnnotationReceivedEvent?.();
32618
- this.annotationsMap.set(messageEvent.topic, {
32619
- annotation: {
32620
- originalMessage: messageEvent,
32621
- annotations: list
32622
- },
32623
- timestamp: timestamp
32624
- });
32625
- this.ghostAnnotationsMap.set(messageEvent.topic, {
32643
+
32644
+ // Prefer timestamps embedded in the annotations themselves
32645
+ const stampTimes = [];
32646
+ for (const a of list) {
32647
+ const sec = a.stamp?.sec;
32648
+ const nsec = a.stamp?.nsec;
32649
+ if (typeof sec === "number" && typeof nsec === "number") {
32650
+ stampTimes.push(sec + nsec / 1e9);
32651
+ }
32652
+ }
32653
+ const timeSec = stampTimes.length > 0 ? Math.max(...stampTimes) : arrivalSec;
32654
+ const entry = {
32626
32655
  annotation: {
32627
32656
  originalMessage: messageEvent,
32628
32657
  annotations: list
32629
32658
  },
32630
- timestamp: timestamp
32631
- });
32659
+ timeSec,
32660
+ arrivalSec
32661
+ };
32662
+ const topic = messageEvent.topic;
32663
+ const buffer = this.annotationsMap.get(topic) ?? [];
32664
+ buffer.push(entry);
32665
+ const maxLen = 60;
32666
+ const retentionSec = 2;
32667
+ const cutoff = timeSec - retentionSec;
32668
+ const trimmed = buffer.filter(e => e.timeSec >= cutoff);
32669
+ while (trimmed.length > maxLen) {
32670
+ trimmed.shift();
32671
+ }
32672
+ this.annotationsMap.set(topic, trimmed);
32632
32673
  }
32674
+
32675
+ // Always emit; render selection logic will pick appropriate annotations for the last image
32676
+ this.#emitState();
32633
32677
  if (this.#config.synchronize === false) {
32634
- this.#emitState();
32635
- return;
32636
- }
32637
- if (list.length === 0 && messageEvent.topic) {
32638
- for (const [topic, annotation] of this.ghostAnnotationsMap.entries()) {
32639
- if (Math.abs(timestamp - annotation.timestamp) <= this.syncToleranceMs) {
32640
- this.annotationsMap.set(topic, {
32641
- annotation: annotation.annotation,
32642
- timestamp
32643
- });
32644
- }
32645
- }
32646
- this.#emitState();
32647
32678
  return;
32648
32679
  }
32680
+
32681
+ // synchronized mode does selection in #getRenderState
32649
32682
  };
32650
32683
  setConfig(newConfig) {
32651
32684
  let changed = false;
@@ -32688,6 +32721,9 @@ class MessageHandler {
32688
32721
  this.#lastReceivedMessages = {
32689
32722
  annotationsByTopic: new Map()
32690
32723
  };
32724
+ this.annotationsMap.clear();
32725
+ this.lastSelectedByTopic.clear();
32726
+ this.lastImage = undefined;
32691
32727
  this.#oldRenderState = undefined;
32692
32728
  this.#emitState();
32693
32729
  }
@@ -32727,40 +32763,65 @@ class MessageHandler {
32727
32763
  image: this.lastImage,
32728
32764
  annotationsByTopic: new Map()
32729
32765
  };
32730
- const imageTimestamp = this.lastImage.receiveTime ? Number(`${this.lastImage.receiveTime.sec}.${this.lastImage.receiveTime.nsec}`) : performance.now();
32731
- const now = performance.now();
32732
- for (const [topic, annotation] of this.annotationsMap.entries()) {
32733
- // FIXME: We can liekly get this value better from the annotation message itself
32734
- const annotationTimestamp = Number.parseFloat(`${annotation.annotation.originalMessage.receiveTime?.sec}.${annotation.annotation.originalMessage.receiveTime?.nsec}`);
32735
-
32736
- // In case the annotation timestamp is not available, we just continue
32737
- if (Number.isNaN(annotationTimestamp)) {
32738
- console.warn("Skipping annotation with no/invalid timestamp", annotation);
32739
- continue;
32766
+ const imageTimestampSec = (() => {
32767
+ const stamp = getTimestampFromImage(this.lastImage.message);
32768
+ const sec = stamp?.sec;
32769
+ const nsec = stamp?.nsec;
32770
+ if (typeof sec === "number" && typeof nsec === "number") {
32771
+ return sec + nsec / 1e9;
32772
+ }
32773
+ return performance.now() / 1000;
32774
+ })();
32775
+ for (const [topic, buffer] of this.annotationsMap.entries()) {
32776
+ if (buffer.length === 0) continue;
32777
+
32778
+ // Prefer newest annotation older than the image within staleness window
32779
+ let preferred;
32780
+ for (let i = buffer.length - 1; i >= 0; i--) {
32781
+ const entry = buffer[i];
32782
+ const age = imageTimestampSec - entry.timeSec;
32783
+ if (age >= 0 && age <= maxStalenessSec) {
32784
+ preferred = entry;
32785
+ break;
32786
+ }
32740
32787
  }
32741
32788
 
32742
- // We only want to show annotations that are older (or same) than the image frame with 100ms tolerance
32743
- const isAnnotationOlderThanImageFrame = annotationTimestamp <= imageTimestamp + 0.1;
32744
-
32745
- //console.log({
32746
- // annotation,
32747
- // imageTimestamp,
32748
- // annotationTimestamp,
32749
- // isAnnotationOlderThanImageFrame,
32750
- //});
32751
-
32752
- const annotationGhost = now - annotation.timestamp < this.ghostTimeoutMs;
32753
-
32754
- // console.log({ now, annotationTimestamp: annotation.timestamp, annotationGhost });
32789
+ // If none older within staleness, fall back to closest within tolerance
32790
+ let fallback;
32791
+ let fallbackDelta = Number.POSITIVE_INFINITY;
32792
+ for (let i = 0; i < buffer.length; i++) {
32793
+ const entry = buffer[i];
32794
+ const delta = Math.abs(entry.timeSec - imageTimestampSec);
32795
+ if (delta < fallbackDelta) {
32796
+ fallback = entry;
32797
+ fallbackDelta = delta;
32798
+ }
32799
+ }
32800
+ let candidate = preferred ?? fallback;
32801
+ if (!candidate) continue;
32802
+ const candidateDelta = Math.abs(candidate.timeSec - imageTimestampSec);
32803
+ const candidateAge = imageTimestampSec - candidate.timeSec;
32804
+ const acceptable = preferred != undefined && candidateAge >= 0 && candidateAge <= maxStalenessSec || preferred == undefined && candidateDelta <= syncToleranceSec;
32805
+ const notFutureFar = candidate.timeSec <= imageTimestampSec + syncToleranceSec;
32806
+ if (!acceptable || !notFutureFar) {
32807
+ continue;
32808
+ }
32755
32809
 
32756
- if ((Math.abs(imageTimestamp - annotation.timestamp) <= this.syncToleranceMs ||
32757
- // within tolerance
32758
- annotationGhost) &&
32759
- // I think this says "or annotation is newer than 1s"
32760
- isAnnotationOlderThanImageFrame // annotation has to be older than image frame within 100ms tolerance
32761
- ) {
32762
- state.annotationsByTopic.set(topic, annotation.annotation);
32810
+ // Hysteresis: if previous selection is very close and still valid, keep it to avoid flicker
32811
+ const prevTime = this.lastSelectedByTopic.get(topic);
32812
+ if (prevTime != undefined) {
32813
+ const prevEntry = buffer.find(e => Math.abs(e.timeSec - prevTime) < 1e-6);
32814
+ if (prevEntry) {
32815
+ const prevDelta = Math.abs(prevEntry.timeSec - imageTimestampSec);
32816
+ const prevAge = imageTimestampSec - prevEntry.timeSec;
32817
+ const prevValid = prevAge >= 0 && prevAge <= maxStalenessSec || prevDelta <= syncToleranceSec;
32818
+ if (prevValid && Math.abs(prevEntry.timeSec - candidate.timeSec) <= hysteresisSec) {
32819
+ candidate = prevEntry;
32820
+ }
32821
+ }
32763
32822
  }
32823
+ state.annotationsByTopic.set(topic, candidate.annotation);
32824
+ this.lastSelectedByTopic.set(topic, candidate.timeSec);
32764
32825
  }
32765
32826
  return state;
32766
32827
  }
@@ -1,5 +1,5 @@
1
- import { a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { a1 as ExternalTokenizer, a9 as ContextTracker, Y as styleTags, Z as tags, a2 as LRParser, a5 as syntaxTree, a3 as ifNotIn, $ as LRLanguage, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport, ac as IterMode, a4 as completeFromList, ad as NodeWeakMap, ab as snippetCompletion } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a1 as ExternalTokenizer, a9 as ContextTracker, Y as styleTags, Z as tags, a2 as LRParser, a5 as syntaxTree, a3 as ifNotIn, $ as LRLanguage, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport, ac as IterMode, a4 as completeFromList, ad as NodeWeakMap, ab as snippetCompletion } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a6 as flatIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a6 as flatIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { af as EditorView, ag as EditorSelection, $ as LRLanguage, Y as styleTags, Z as tags, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a0 as LanguageSupport, a2 as LRParser, a5 as syntaxTree, ah as html, ai as parseMixed, a1 as ExternalTokenizer } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { af as EditorView, ag as EditorSelection, $ as LRLanguage, Y as styleTags, Z as tags, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a0 as LanguageSupport, a2 as LRParser, a5 as syntaxTree, ah as html, ai as parseMixed, a1 as ExternalTokenizer } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { Y as styleTags, Z as tags, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, X as foldNodeProp, a8 as foldInside, ae as defineCSSCompletionSource, a0 as LanguageSupport, a2 as LRParser, a1 as ExternalTokenizer } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { Y as styleTags, Z as tags, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, X as foldNodeProp, a8 as foldInside, ae as defineCSSCompletionSource, a0 as LanguageSupport, a2 as LRParser, a1 as ExternalTokenizer } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { Y as styleTags, Z as tags, $ as LRLanguage, a0 as LanguageSupport, a2 as LRParser, aa as LocalTokenGroup, ah as html, ai as parseMixed, ak as javascriptLanguage } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { Y as styleTags, Z as tags, $ as LRLanguage, a0 as LanguageSupport, a2 as LRParser, aa as LocalTokenGroup, ah as html, ai as parseMixed, ak as javascriptLanguage } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { a9 as ContextTracker, a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport, ai as parseMixed } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a9 as ContextTracker, a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport, ai as parseMixed } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { Y as styleTags, Z as tags, ak as javascriptLanguage, $ as LRLanguage, a0 as LanguageSupport, a2 as LRParser, ah as html, ai as parseMixed, a1 as ExternalTokenizer } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { Y as styleTags, Z as tags, ak as javascriptLanguage, $ as LRLanguage, a0 as LanguageSupport, a2 as LRParser, ah as html, ai as parseMixed, a1 as ExternalTokenizer } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { a9 as ContextTracker, a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, X as foldNodeProp, aj as bracketMatchingHandle, a0 as LanguageSupport, af as EditorView, a5 as syntaxTree, ag as EditorSelection } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a9 as ContextTracker, a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, X as foldNodeProp, aj as bracketMatchingHandle, a0 as LanguageSupport, af as EditorView, a5 as syntaxTree, ag as EditorSelection } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { a1 as ExternalTokenizer, a9 as ContextTracker, Y as styleTags, Z as tags, a2 as LRParser, aa as LocalTokenGroup, ab as snippetCompletion, a5 as syntaxTree, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a6 as flatIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport, a3 as ifNotIn, a4 as completeFromList, ac as IterMode, ad as NodeWeakMap } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a1 as ExternalTokenizer, a9 as ContextTracker, Y as styleTags, Z as tags, a2 as LRParser, aa as LocalTokenGroup, ab as snippetCompletion, a5 as syntaxTree, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a6 as flatIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport, a3 as ifNotIn, a4 as completeFromList, ac as IterMode, ad as NodeWeakMap } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { a1 as ExternalTokenizer, a9 as ContextTracker, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, X as foldNodeProp, a8 as foldInside, U as indentNodeProp, V as continuedIndent, ae as defineCSSCompletionSource, a0 as LanguageSupport } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a1 as ExternalTokenizer, a9 as ContextTracker, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, X as foldNodeProp, a8 as foldInside, U as indentNodeProp, V as continuedIndent, ae as defineCSSCompletionSource, a0 as LanguageSupport } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { U as indentNodeProp, V as continuedIndent, X as foldNodeProp, Y as styleTags, Z as tags, $ as LRLanguage, a0 as LanguageSupport, a1 as ExternalTokenizer, a2 as LRParser, a3 as ifNotIn, a4 as completeFromList, a5 as syntaxTree } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { U as indentNodeProp, V as continuedIndent, X as foldNodeProp, Y as styleTags, Z as tags, $ as LRLanguage, a0 as LanguageSupport, a1 as ExternalTokenizer, a2 as LRParser, a3 as ifNotIn, a4 as completeFromList, a5 as syntaxTree } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { $ as LRLanguage, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, Y as styleTags, Z as tags, a0 as LanguageSupport, a2 as LRParser } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { $ as LRLanguage, U as indentNodeProp, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, Y as styleTags, Z as tags, a0 as LanguageSupport, a2 as LRParser } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,14 +1,14 @@
1
- import { ImagePanel } from './index-DVTnwOoU.js';
1
+ import { ImagePanel } from './index-1c6__YmG.js';
2
2
  import 'react';
3
3
  import 'react-dom';
4
- import './depth-CJRDcPfJ.js';
4
+ import './depth-CgtekUia.js';
5
5
  import './protobuf-B55JS-Px.js';
6
6
  import 'protobufjs/minimal';
7
7
  import 'zustand';
8
8
  import 'react-mosaic-component';
9
9
  import '@mui/material';
10
10
  import './isArrayLikeObject-Bytw9p-q.js';
11
- import './index-WBIAVrf5.js';
11
+ import './index-ynJe1rwL.js';
12
12
  import './comlink-DHMAu6X7.js';
13
13
  import './utils-Hzt3wxhG.js';
14
14
  import './FoxgloveServer-MMAMiqqQ.js';
@@ -1,5 +1,5 @@
1
- import { a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a6 as flatIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a6 as flatIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, a0 as LanguageSupport } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
- import { a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, ah as html, a0 as LanguageSupport, ai as parseMixed } from './index-WBIAVrf5.js';
2
- import './depth-CJRDcPfJ.js';
1
+ import { a1 as ExternalTokenizer, Y as styleTags, Z as tags, a2 as LRParser, $ as LRLanguage, U as indentNodeProp, V as continuedIndent, a7 as delimitedIndent, X as foldNodeProp, a8 as foldInside, ah as html, a0 as LanguageSupport, ai as parseMixed } from './index-ynJe1rwL.js';
2
+ import './depth-CgtekUia.js';
3
3
  import './protobuf-B55JS-Px.js';
4
4
  import 'protobufjs/minimal';
5
5
  import 'react';