supervision 0.1.6 → 0.1.7

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 (27) hide show
  1. package/dist/index.js +205 -83
  2. package/dist/index.js.map +1 -1
  3. package/dist/mask-preparation.worker.js.map +1 -1
  4. package/dist/renderers/pixi-annotation-overlay-layer.d.ts +3 -2
  5. package/dist/renderers/pixi-annotation-overlay-layer.d.ts.map +1 -1
  6. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  7. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  8. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  9. package/dist/renderers/pixi-vector-layer.d.ts +2 -1
  10. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  11. package/dist/tracking.worker.js.map +1 -1
  12. package/node_modules/supervision-js-core/dist/index.js +124 -28
  13. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  14. package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts.map +1 -1
  15. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts +1 -1
  16. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts.map +1 -1
  17. package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
  18. package/node_modules/supervision-js-core/dist/types/detections.d.ts +7 -0
  19. package/node_modules/supervision-js-core/dist/types/detections.d.ts.map +1 -1
  20. package/node_modules/supervision-js-core/dist/types/editing.d.ts +6 -0
  21. package/node_modules/supervision-js-core/dist/types/editing.d.ts.map +1 -1
  22. package/node_modules/supervision-js-core/dist/types/interaction-style.d.ts +2 -0
  23. package/node_modules/supervision-js-core/dist/types/interaction-style.d.ts.map +1 -1
  24. package/node_modules/supervision-js-core/dist/types/interaction.d.ts +9 -1
  25. package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
  26. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  27. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { copySortedDetectionFrames, detectionFrameOverlapsRange, projectDetectionFrameForTracking, createSortTracker, createOCSortTracker, createCBIoUTracker, createByteTrackTracker, MediaErrorKind, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createProjectedDetectionFrameSource, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, createIdleDetectionBufferState, MediaInteractionMode, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, BaseFocusStyle, decodeCompressedRleMask, extractMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, createDetectionPickKey, rebaseDetectionPickToFrame, pickDetectionAtPoint, followDetectionPickAcrossFrames, haveSameDetectionPickIdentities, pickAnnotationHandle, getAnnotationHandles, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, KeypointMarkerShape, MarkerSizeSpace, MarkerShape, ShapeInstructionKind, sampleEllipseArc, resolveEllipseSegmentCount, resolveMarkerGeometry, BaseInteractionStyle, DetectionInteractionState, MAX_ID_MASK_PALETTE_ENTRIES, DetectionBufferStatus, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MAX_ID_MASK_STROKE_WIDTH, resolveMaskStyleOpacity, pickDetectionByMaskId, BoxStrokeAlignment, resolveStyleValue, resolveAnnotationStyleState, lightenColor, createViewportController, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation, projectDetectionFrames, DetectionFrameSelectionMode } from 'supervision-js-core';
1
+ import { copySortedDetectionFrames, detectionFrameOverlapsRange, projectDetectionFrameForTracking, createSortTracker, createOCSortTracker, createCBIoUTracker, createByteTrackTracker, MediaErrorKind, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createProjectedDetectionFrameSource, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, createIdleDetectionBufferState, MediaInteractionMode, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, BaseFocusStyle, decodeCompressedRleMask, extractMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, createDetectionPickKey, rebaseDetectionPickToFrame, pickDetectionAtPoint, followDetectionPickAcrossFrames, haveSameDetectionPickIdentities, pickAnnotationHandle, getAnnotationHandles, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, ShapeInstructionKind, sampleEllipseArc, resolveEllipseSegmentCount, KeypointMarkerShape, MarkerSizeSpace, MarkerShape, resolveMarkerGeometry, BaseInteractionStyle, DetectionInteractionState, MAX_ID_MASK_PALETTE_ENTRIES, DetectionBufferStatus, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MAX_ID_MASK_STROKE_WIDTH, resolveMaskStyleOpacity, pickDetectionByMaskId, BoxStrokeAlignment, resolveStyleValue, resolveAnnotationStyleState, lightenColor, createViewportController, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation, projectDetectionFrames, DetectionFrameSelectionMode } from 'supervision-js-core';
2
2
  export { BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaErrorKind, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, RegionRendererComposeMode, RegionRendererRegionKind, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, TrackingGeometry, annotationRendererKinds, annotationRenderers, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createByteTrackTracker, createCBIoUTracker, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createOCSortTracker, createProjectedDetectionFrameSource, createSortTracker, createWritableDetectionFrameSource, detectionPostProcessors, normalizeDetectionClassName, pickDetectionAtPoint, projectDetectionFrame, projectDetectionFrameForTracking, projectDetectionFrames, resolveDetectionClassColorStyle } from 'supervision-js-core';
3
3
 
4
4
  const DEFAULT_DATABASE_NAME = "supervision-js-detection-frames";
@@ -4445,6 +4445,7 @@ function createPixiInteractionLayer(options) {
4445
4445
  includeMasks: options.pickMaskDetectionAtPoint === undefined,
4446
4446
  maskMediaDimensions: options.getMediaDimensions?.(),
4447
4447
  padding: pickPadding,
4448
+ viewportScale: options.getViewportScale?.() ?? 1,
4448
4449
  });
4449
4450
  if (geometryPick && POINT_PRECISE_PICK_TARGETS.has(geometryPick.target)) {
4450
4451
  return geometryPick;
@@ -4526,19 +4527,17 @@ function createPixiInteractionLayer(options) {
4526
4527
  const nextKey = createDetectionPickKey(nextPick);
4527
4528
  if (nextKey === hoveredPickKey) {
4528
4529
  hoveredPick = nextPick;
4529
- if (container) {
4530
- if (!selectedHandleAt(pointerPoint)) {
4531
- container.cursor = nextPick ? "pointer" : resolveIdleCursor();
4532
- }
4530
+ if (container && !selectedHandleAt(pointerPoint)) {
4531
+ container.cursor = nextPick
4532
+ ? hoverCursor(nextPick)
4533
+ : resolveIdleCursor();
4533
4534
  }
4534
4535
  return;
4535
4536
  }
4536
4537
  hoveredPick = nextPick;
4537
4538
  hoveredPickKey = nextKey;
4538
- if (container) {
4539
- if (!selectedHandleAt(pointerPoint)) {
4540
- container.cursor = nextPick ? "pointer" : resolveIdleCursor();
4541
- }
4539
+ if (container && !selectedHandleAt(pointerPoint)) {
4540
+ container.cursor = nextPick ? hoverCursor(nextPick) : resolveIdleCursor();
4542
4541
  }
4543
4542
  options.interaction.onHover?.(nextPick);
4544
4543
  notifyStateChange();
@@ -4609,12 +4608,28 @@ function createPixiInteractionLayer(options) {
4609
4608
  return;
4610
4609
  const editingState = options.editingEngine?.getState();
4611
4610
  if (editingState?.kind === AnnotationGestureStateKind.Moving) {
4612
- container.cursor = "grabbing";
4611
+ container.cursor = "move";
4613
4612
  return;
4614
4613
  }
4614
+ if (editingState?.kind === AnnotationGestureStateKind.Resizing &&
4615
+ selectedPick) {
4616
+ // Keep the grabbed handle's cursor for the whole drag.
4617
+ const active = getAnnotationHandles(selectedPick.detection, options.getViewportScale?.() ?? 1).find((handle) => handle.id === editingState.activeHandleId);
4618
+ if (active) {
4619
+ container.cursor = active.cursor;
4620
+ return;
4621
+ }
4622
+ }
4615
4623
  const handle = selectedHandleAt(point);
4616
4624
  container.cursor =
4617
- handle?.cursor ?? (hoveredPick ? "pointer" : resolveIdleCursor());
4625
+ handle?.cursor ??
4626
+ (hoveredPick ? hoverCursor(hoveredPick) : resolveIdleCursor());
4627
+ }
4628
+ /** Editable geometry drags, so it advertises `move`; a keypoint is a point target. */
4629
+ function hoverCursor(pick) {
4630
+ if (!options.editingEngine || pick.detection.locked || pick.detection.mask)
4631
+ return "pointer";
4632
+ return pick.target === DetectionPickTarget.Keypoint ? "pointer" : "move";
4618
4633
  }
4619
4634
  function resolveIdleCursor() {
4620
4635
  return options.editingEngine?.hasCreationTool() ? "crosshair" : "default";
@@ -5374,35 +5389,37 @@ function createPixiVectorLayer(options) {
5374
5389
  }
5375
5390
  if (polyline)
5376
5391
  drawPixiPath(graphics, polyline.points, false, polyline.stroke, viewportScale);
5377
- if (!keypoints)
5378
- return;
5379
- for (const edge of keypoints.edges) {
5380
- if (edge.shadowStroke)
5381
- drawPixiPath(graphics, [edge.from, edge.to], false, edge.shadowStroke, viewportScale);
5382
- drawPixiPath(graphics, [edge.from, edge.to], false, edge.stroke, viewportScale);
5383
- }
5384
- for (const marker of keypoints.markers) {
5385
- if (marker.shape === KeypointMarkerShape.Cross) {
5386
- drawMarkerInstruction(graphics, {
5387
- center: marker.point,
5388
- kind: ShapeInstructionKind.Marker,
5389
- shape: MarkerShape.Cross,
5390
- size: marker.radius * 2,
5391
- sizeSpace: MarkerSizeSpace.Screen,
5392
- stroke: asOpenStroke(marker.stroke ?? { alpha: 1, color: 0xffffff, width: 2 }),
5393
- }, viewportScale);
5394
- }
5395
- else {
5396
- drawMarkerInstruction(graphics, {
5397
- center: marker.point,
5398
- fill: marker.fill,
5399
- kind: ShapeInstructionKind.Marker,
5400
- shape: MarkerShape.Circle,
5401
- size: marker.radius * 2,
5402
- sizeSpace: MarkerSizeSpace.Screen,
5403
- stroke: marker.stroke,
5404
- }, viewportScale);
5405
- }
5392
+ if (keypoints)
5393
+ drawPixiKeypointInstruction(graphics, keypoints, viewportScale);
5394
+ }
5395
+ }
5396
+ function drawPixiKeypointInstruction(graphics, instruction, viewportScale) {
5397
+ for (const edge of instruction.edges) {
5398
+ if (edge.shadowStroke)
5399
+ drawPixiPath(graphics, [edge.from, edge.to], false, edge.shadowStroke, viewportScale);
5400
+ drawPixiPath(graphics, [edge.from, edge.to], false, edge.stroke, viewportScale);
5401
+ }
5402
+ for (const marker of instruction.markers) {
5403
+ if (marker.shape === KeypointMarkerShape.Cross) {
5404
+ drawMarkerInstruction(graphics, {
5405
+ center: marker.point,
5406
+ kind: ShapeInstructionKind.Marker,
5407
+ shape: MarkerShape.Cross,
5408
+ size: marker.radius * 2,
5409
+ sizeSpace: MarkerSizeSpace.Screen,
5410
+ stroke: asOpenStroke(marker.stroke ?? { alpha: 1, color: 0xffffff, width: 2 }),
5411
+ }, viewportScale);
5412
+ }
5413
+ else {
5414
+ drawMarkerInstruction(graphics, {
5415
+ center: marker.point,
5416
+ fill: marker.fill,
5417
+ kind: ShapeInstructionKind.Marker,
5418
+ shape: MarkerShape.Circle,
5419
+ size: marker.radius * 2,
5420
+ sizeSpace: MarkerSizeSpace.Screen,
5421
+ stroke: marker.stroke,
5422
+ }, viewportScale);
5406
5423
  }
5407
5424
  }
5408
5425
  }
@@ -5562,7 +5579,9 @@ function createPixiInteractionPresentationLayer(options) {
5562
5579
  return;
5563
5580
  }
5564
5581
  syntheticFrame = {
5565
- detections: activePicks.map(({ detection }) => detection),
5582
+ detections: activePicks.map(({ detection }) => detection.id === undefined
5583
+ ? detection
5584
+ : { ...detection, id: undefined }),
5566
5585
  frameIndex: context.frame.frameIndex,
5567
5586
  mediaTime: context.frame.mediaTime,
5568
5587
  };
@@ -5598,6 +5617,13 @@ function createPixiInteractionPresentationLayer(options) {
5598
5617
  for (const pick of picks) {
5599
5618
  const key = createDetectionPickKey(pick.pick);
5600
5619
  if (!key || seenKeys.has(key)) {
5620
+ if (key &&
5621
+ pick.context.state === DetectionInteractionState.Hovered &&
5622
+ pick.pick.target === DetectionPickTarget.Keypoint) {
5623
+ const duplicateIndex = deduped.findIndex((candidate) => createDetectionPickKey(candidate.pick) === key);
5624
+ if (duplicateIndex !== -1)
5625
+ deduped[duplicateIndex] = pick;
5626
+ }
5601
5627
  continue;
5602
5628
  }
5603
5629
  seenKeys.add(key);
@@ -5614,6 +5640,7 @@ function createPixiInteractionPresentationLayer(options) {
5614
5640
  const context = {
5615
5641
  detectionIndex: activePick.detectionIndex,
5616
5642
  frame: activePick.frame,
5643
+ geometryIndex: activePick.geometryIndex,
5617
5644
  mediaTime,
5618
5645
  point: activePick.point,
5619
5646
  state,
@@ -8859,9 +8886,12 @@ function lowerEllipseInstruction(instruction) {
8859
8886
  return { ...instruction, kind: ShapeInstructionKind.Ellipse };
8860
8887
  }
8861
8888
 
8862
- function createPixiAnnotationOverlayLayer(editingEngine, initialStyle) {
8889
+ function createPixiAnnotationOverlayLayer(editingEngine, initialStyle, initialKeypointStyle) {
8863
8890
  let graphics;
8864
8891
  let style = resolveAnnotationOverlayStyle(initialStyle);
8892
+ let keypointStyle = initialKeypointStyle === undefined
8893
+ ? new BaseKeypointStyle()
8894
+ : initialKeypointStyle;
8865
8895
  return {
8866
8896
  attachGraphics(next) {
8867
8897
  graphics = next;
@@ -8870,20 +8900,24 @@ function createPixiAnnotationOverlayLayer(editingEngine, initialStyle) {
8870
8900
  if (next !== undefined)
8871
8901
  style = resolveAnnotationOverlayStyle(next);
8872
8902
  },
8903
+ setKeypointStyle(next) {
8904
+ if (next !== undefined)
8905
+ keypointStyle = next;
8906
+ },
8873
8907
  draw(context) {
8874
8908
  if (!graphics)
8875
8909
  return;
8876
8910
  graphics.clear();
8877
8911
  drawExternalPreview(graphics, context.previewOverlay, context.viewportScale, style);
8878
- drawEditingPreview(graphics, editingEngine, context.viewportScale, style);
8879
- drawSelectionHandles(graphics, context, style);
8912
+ drawEditingPreview(graphics, editingEngine, context.frame, context.viewportScale, style, keypointStyle);
8913
+ drawSelectionHandles(graphics, context, style, editingEngine);
8880
8914
  drawMarquee(graphics, context.marquee, style);
8881
8915
  drawGuides(graphics, editingEngine, context, style);
8882
8916
  drawLoading(graphics, context, style);
8883
8917
  },
8884
8918
  };
8885
8919
  }
8886
- function drawEditingPreview(graphics, engine, viewportScale, style) {
8920
+ function drawEditingPreview(graphics, engine, frame, viewportScale, style, keypointStyle) {
8887
8921
  const state = engine?.getState();
8888
8922
  const detection = state?.preview;
8889
8923
  if (!detection)
@@ -8896,16 +8930,18 @@ function drawEditingPreview(graphics, engine, viewportScale, style) {
8896
8930
  const boxFill = resolveStyleValue(style.editingPreview.boxFill, detection, styleContext);
8897
8931
  const polygonFill = resolveStyleValue(style.editingPreview.polygonFill, detection, styleContext);
8898
8932
  const closeZoneStroke = resolveStyleValue(style.editingPreview.closeZoneStroke, detection, styleContext);
8933
+ // A skeleton's rect is part of its geometry, so it previews alongside the
8934
+ // keypoints; polygon, polyline and mask rects are derived bounds and are not.
8899
8935
  if (detection.rect &&
8900
8936
  !detection.mask &&
8901
8937
  !detection.polygon &&
8902
8938
  !detection.polyline) {
8903
8939
  const { x, y, width, height } = detection.rect;
8904
- if (state.kind === AnnotationGestureStateKind.Creating) {
8905
- graphics
8906
- .roundRect(x - width / 2, y - height / 2, width, height, resolveScreenLength(1, viewportScale))
8907
- .fill(boxFill);
8908
- }
8940
+ // The source detection is hidden while it is edited, so the preview
8941
+ // carries the box fill as well as its outline.
8942
+ graphics
8943
+ .roundRect(x - width / 2, y - height / 2, width, height, resolveScreenLength(1, viewportScale))
8944
+ .fill(boxFill);
8909
8945
  drawPixiPath(graphics, [
8910
8946
  { x: x - width / 2, y: y - height / 2 },
8911
8947
  { x: x + width / 2, y: y - height / 2 },
@@ -8925,6 +8961,22 @@ function drawEditingPreview(graphics, engine, viewportScale, style) {
8925
8961
  }
8926
8962
  if (detection.polyline)
8927
8963
  drawPixiPath(graphics, detection.polyline.points, false, stroke, viewportScale);
8964
+ if (detection.keypoints && keypointStyle) {
8965
+ const previewFrame = {
8966
+ detections: [detection],
8967
+ frameIndex: frame?.frameIndex,
8968
+ mediaTime: frame?.mediaTime ?? 0,
8969
+ };
8970
+ const instruction = keypointStyle.resolve(detection, {
8971
+ detectionIndex: 0,
8972
+ frame: previewFrame,
8973
+ mediaTime: previewFrame.mediaTime,
8974
+ selected: true,
8975
+ viewportScale,
8976
+ });
8977
+ if (instruction)
8978
+ drawPixiKeypointInstruction(graphics, instruction, viewportScale);
8979
+ }
8928
8980
  }
8929
8981
  const CREATION_VERTEX_RADIUS = 6;
8930
8982
  const CREATION_VERTEX_STROKE_WIDTH = 1.5;
@@ -9013,7 +9065,7 @@ function resolveCloseHighlightColor(creationColor, preferredHighlight) {
9013
9065
  ? preferredHighlight
9014
9066
  : 0xffffff;
9015
9067
  }
9016
- function drawSelectionHandles(graphics, context, style) {
9068
+ function drawSelectionHandles(graphics, context, style, engine) {
9017
9069
  const selected = new Set(context.selectedDetectionIds);
9018
9070
  for (const [detectionIndex, detection] of (context.frame?.detections ?? []).entries()) {
9019
9071
  if (detection.id === undefined || !selected.has(detection.id))
@@ -9027,15 +9079,26 @@ function drawSelectionHandles(graphics, context, style) {
9027
9079
  };
9028
9080
  const fill = resolveStyleValue(style.selectionHandle.fill, detection, styleContext) ?? { alpha: 1, color: 0xffffff };
9029
9081
  const stroke = resolveStyleValue(style.selectionHandle.stroke, detection, styleContext) ?? { alpha: 1, color: 0x2563eb, width: 2 };
9030
- for (const handle of getAnnotationHandles(detection, context.viewportScale)) {
9082
+ // Handles ride on the live preview so they follow a move or reshape.
9083
+ const state = engine?.getState();
9084
+ const source = state &&
9085
+ (state.kind === AnnotationGestureStateKind.Moving ||
9086
+ state.kind === AnnotationGestureStateKind.Resizing) &&
9087
+ state.preview &&
9088
+ state.activeDetectionId === detection.id
9089
+ ? state.preview
9090
+ : detection;
9091
+ for (const handle of getAnnotationHandles(source, context.viewportScale)) {
9092
+ const alpha = handle.kind === AnnotationHandleKind.AddVertex
9093
+ ? style.selectionHandle.addVertexAlpha
9094
+ : handle.kind === AnnotationHandleKind.Keypoint
9095
+ ? style.selectionHandle.keypointAlpha
9096
+ : 1;
9097
+ if (alpha <= 0)
9098
+ continue;
9031
9099
  graphics.circle(handle.point.x, handle.point.y, handle.radius);
9032
- graphics.fill({
9033
- ...fill,
9034
- alpha: handle.kind === "addVertex"
9035
- ? style.selectionHandle.addVertexAlpha
9036
- : fill.alpha,
9037
- });
9038
- graphics.stroke(resolvePixiStroke(stroke, context.viewportScale));
9100
+ graphics.fill({ ...fill, alpha: fill.alpha * alpha });
9101
+ graphics.stroke(resolvePixiStroke({ ...stroke, alpha: stroke.alpha * alpha }, context.viewportScale));
9039
9102
  }
9040
9103
  }
9041
9104
  }
@@ -9118,6 +9181,7 @@ const DEFAULT_ANNOTATION_OVERLAY_STYLE = {
9118
9181
  fill: { alpha: 1, color: 0xffffff },
9119
9182
  stroke: { alpha: 1, color: 0x2563eb, width: 2 },
9120
9183
  addVertexAlpha: 0.55,
9184
+ keypointAlpha: 1,
9121
9185
  },
9122
9186
  marquee: { fill: { alpha: 0.2, color: 0x3b82f6 } },
9123
9187
  guide: {
@@ -9310,12 +9374,28 @@ async function createPixiMediaScene(options) {
9310
9374
  let mediaWidth = 0;
9311
9375
  let visibilityVersion = 0;
9312
9376
  let visibilityMaskStyleCache = null;
9313
- const resolveContextState = (detection) => resolveAnnotationStyleState(detection, currentVisibility);
9314
- const resolveLabelContextState = (detection) => ({
9315
- ...resolveContextState(detection),
9316
- hidden: currentVisibility?.labelsHidden === true ||
9317
- resolveContextState(detection).hidden,
9318
- });
9377
+ // While a move, resize, vertex or keypoint gesture edits a detection, the
9378
+ // base layers hide it and the annotation overlay draws the live preview in
9379
+ // its place, so the edit never shows next to its own stale copy. Masks have
9380
+ // no overlay preview and stay drawn.
9381
+ let editingDetectionId = null;
9382
+ const resolveContextState = (detection) => {
9383
+ const state = resolveAnnotationStyleState(detection, currentVisibility);
9384
+ return editingDetectionId !== null &&
9385
+ detection.id === editingDetectionId &&
9386
+ !state.hidden
9387
+ ? { ...state, hidden: true }
9388
+ : state;
9389
+ };
9390
+ // Labels have no overlay preview either: they stay drawn and follow a move
9391
+ // through the fast-translate path, like regions.
9392
+ const resolveLabelContextState = (detection) => {
9393
+ const state = resolveAnnotationStyleState(detection, currentVisibility);
9394
+ return {
9395
+ ...state,
9396
+ hidden: currentVisibility?.labelsHidden === true || state.hidden,
9397
+ };
9398
+ };
9319
9399
  // The vector layer consumes one shape style; renderer kinds that lower to
9320
9400
  // shape instructions compose with the internal hook here.
9321
9401
  function resolveVectorShapeStyle() {
@@ -9388,9 +9468,11 @@ async function createPixiMediaScene(options) {
9388
9468
  },
9389
9469
  onAssetError: options.diagnostics?.onAssetError,
9390
9470
  regionRenderers: options.regionRenderers,
9391
- resolveContextState,
9471
+ // Regions (badges, icons) have no overlay preview: they stay drawn and
9472
+ // follow a move through the fast-translate path instead.
9473
+ resolveContextState: (detection) => resolveAnnotationStyleState(detection, currentVisibility),
9392
9474
  });
9393
- const annotationOverlayLayer = createPixiAnnotationOverlayLayer(options.editingEngine, options.annotationOverlayStyle);
9475
+ const annotationOverlayLayer = createPixiAnnotationOverlayLayer(options.editingEngine, options.annotationOverlayStyle, options.keypointStyle);
9394
9476
  const maskBrushPreview = options.maskBrush
9395
9477
  ? createPixiMaskBrushPreview({
9396
9478
  CanvasSource,
@@ -9528,34 +9610,52 @@ async function createPixiMediaScene(options) {
9528
9610
  Text,
9529
9611
  UniformGroup,
9530
9612
  interactionStyle: options.interactionStyle,
9531
- isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
9613
+ // The detection under an editing gesture is drawn live by the base
9614
+ // layers (move) or the overlay preview (reshape); its hover/selection
9615
+ // redraw would sit at the stale position.
9616
+ isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden &&
9617
+ !isUnderEditingGesture(detection),
9532
9618
  })
9533
9619
  : undefined;
9534
9620
  let fastTranslatedDetectionId = null;
9535
9621
  const unsubscribeFastTranslate = options.editingEngine?.subscribeFastTranslate((id, dx, dy) => {
9536
9622
  fastTranslatedDetectionId = id;
9537
- boxLayer.translateDetection(id, dx, dy);
9538
- vectorLayer.translateDetection(id, dx, dy);
9539
9623
  regionLayer.translateDetection(id, dx, dy);
9540
9624
  labelLayer?.translateDetection(id, dx, dy);
9541
9625
  });
9542
- const unsubscribeEditingState = options.editingEngine?.subscribe((state) => {
9543
- if (state.kind !== AnnotationGestureStateKind.Idle ||
9544
- fastTranslatedDetectionId === null) {
9545
- return;
9546
- }
9547
- const id = fastTranslatedDetectionId;
9548
- fastTranslatedDetectionId = null;
9549
- boxLayer.translateDetection(id, 0, 0);
9550
- vectorLayer.translateDetection(id, 0, 0);
9551
- regionLayer.translateDetection(id, 0, 0);
9552
- labelLayer?.translateDetection(id, 0, 0);
9626
+ const redrawDetectionLayers = (id) => {
9553
9627
  boxLayer.invalidateDetection(id);
9554
9628
  vectorLayer.invalidateDetection(id);
9555
9629
  boxLayer.drawFrame(currentMediaTime, viewportScale);
9556
9630
  vectorLayer.drawFrame(currentMediaTime, viewportScale);
9557
9631
  regionLayer.drawFrame(currentMediaTime, viewportScale);
9558
9632
  labelLayer?.drawFrame(currentMediaTime, viewportScale);
9633
+ };
9634
+ const unsubscribeEditingState = options.editingEngine?.subscribe((state) => {
9635
+ const nextEditingId = (state.kind === AnnotationGestureStateKind.Moving ||
9636
+ state.kind === AnnotationGestureStateKind.Resizing) &&
9637
+ state.activeDetectionId !== null &&
9638
+ state.preview &&
9639
+ !state.preview.mask
9640
+ ? state.activeDetectionId
9641
+ : null;
9642
+ if (nextEditingId !== editingDetectionId) {
9643
+ const previous = editingDetectionId;
9644
+ editingDetectionId = nextEditingId;
9645
+ if (previous !== null)
9646
+ redrawDetectionLayers(previous);
9647
+ if (nextEditingId !== null)
9648
+ redrawDetectionLayers(nextEditingId);
9649
+ drawInteractionPresentationLayer(currentMediaTime);
9650
+ }
9651
+ if (state.kind === AnnotationGestureStateKind.Idle &&
9652
+ fastTranslatedDetectionId !== null) {
9653
+ regionLayer.translateDetection(fastTranslatedDetectionId, 0, 0);
9654
+ labelLayer?.translateDetection(fastTranslatedDetectionId, 0, 0);
9655
+ fastTranslatedDetectionId = null;
9656
+ regionLayer.drawFrame(currentMediaTime, viewportScale);
9657
+ labelLayer?.drawFrame(currentMediaTime, viewportScale);
9658
+ }
9559
9659
  });
9560
9660
  const handleKeyDown = (event) => {
9561
9661
  if (event.key === "Tab" && interactionLayer) {
@@ -9984,6 +10084,7 @@ async function createPixiMediaScene(options) {
9984
10084
  polylineStyle: presentation.polylineStyle,
9985
10085
  keypointStyle: presentation.keypointStyle,
9986
10086
  });
10087
+ annotationOverlayLayer.setKeypointStyle(presentation.keypointStyle);
9987
10088
  regionLayer.setRenderers(presentation.renderers?.filter((renderer) => renderer.kind === "region") ?? []);
9988
10089
  if (presentation.boxCornerStyle !== undefined ||
9989
10090
  presentation.ellipseStyle !== undefined ||
@@ -10269,10 +10370,10 @@ async function createPixiMediaScene(options) {
10269
10370
  const interactionState = interactionLayer?.getState();
10270
10371
  focusLayer.drawFrame({
10271
10372
  frame,
10272
- hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
10373
+ hoveredPick: withEditingPreview(filterVisiblePick(interactionState?.hoveredPick ?? null)),
10273
10374
  idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
10274
10375
  mediaTime,
10275
- selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
10376
+ selectedPick: withEditingPreview(filterVisiblePick(interactionState?.selectedPick ?? null)),
10276
10377
  viewportScale,
10277
10378
  });
10278
10379
  }
@@ -10365,6 +10466,27 @@ async function createPixiMediaScene(options) {
10365
10466
  ? pick
10366
10467
  : null;
10367
10468
  }
10469
+ function editingPreviewFor(detection) {
10470
+ const state = options.editingEngine?.getState();
10471
+ return state &&
10472
+ (state.kind === AnnotationGestureStateKind.Moving ||
10473
+ state.kind === AnnotationGestureStateKind.Resizing) &&
10474
+ state.preview &&
10475
+ detection.id !== undefined &&
10476
+ state.activeDetectionId === detection.id
10477
+ ? state.preview
10478
+ : null;
10479
+ }
10480
+ function isUnderEditingGesture(detection) {
10481
+ return editingPreviewFor(detection) !== null;
10482
+ }
10483
+ /** Point a pick at the live editing preview so focus follows the gesture. */
10484
+ function withEditingPreview(pick) {
10485
+ if (!pick)
10486
+ return null;
10487
+ const preview = editingPreviewFor(pick.detection);
10488
+ return preview ? { ...pick, detection: preview } : pick;
10489
+ }
10368
10490
  }
10369
10491
  function canReuseMaskVisibilityArtifacts(previous, next) {
10370
10492
  return ((previous?.annotationsHidden === true) ===