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
@@ -65,6 +65,9 @@ function copySortedDetectionFrames(detectionFrames) {
65
65
  : undefined,
66
66
  keypoints: detection.keypoints
67
67
  ? {
68
+ boxRelative: detection.keypoints.boxRelative
69
+ ? [...detection.keypoints.boxRelative]
70
+ : undefined,
68
71
  edges: detection.keypoints.edges.map((edge) => [edge[0], edge[1]]),
69
72
  points: detection.keypoints.points.map((point) => ({ ...point })),
70
73
  visibility: detection.keypoints.visibility
@@ -3950,10 +3953,18 @@ function pickDetectionAtPoint(frame, point, options = {}) {
3950
3953
  if (!frame) {
3951
3954
  return null;
3952
3955
  }
3953
- const padding = Math.max(0, options.padding ?? 0);
3954
- const polylinePadding = Math.max(0, options.polylinePadding ?? 6);
3955
- const keypointPadding = Math.max(0, options.keypointPadding ?? 10);
3956
- const edgePadding = Math.max(0, options.edgePadding ?? 8);
3956
+ // Tolerances are screen-space sizes; a zoomed-out viewport maps more media
3957
+ // units per screen pixel, so a fixed media-space padding would shrink the
3958
+ // hit target below the drawn marker.
3959
+ const viewportScale = options.viewportScale !== undefined &&
3960
+ Number.isFinite(options.viewportScale) &&
3961
+ options.viewportScale > 0
3962
+ ? options.viewportScale
3963
+ : 1;
3964
+ const padding = Math.max(0, options.padding ?? 0) / viewportScale;
3965
+ const polylinePadding = Math.max(0, options.polylinePadding ?? 6) / viewportScale;
3966
+ const keypointPadding = Math.max(0, options.keypointPadding ?? 10) / viewportScale;
3967
+ const edgePadding = Math.max(0, options.edgePadding ?? 8) / viewportScale;
3957
3968
  const candidates = [];
3958
3969
  for (let detectionIndex = 0; detectionIndex < frame.detections.length; detectionIndex += 1) {
3959
3970
  const detection = frame.detections[detectionIndex];
@@ -3992,10 +4003,22 @@ function pickDetectionAtPoint(frame, point, options = {}) {
3992
4003
  }
3993
4004
  }
3994
4005
  if (detection.keypoints) {
3995
- const keypointIndex = detection.keypoints.points.findIndex((keypoint, index) => detection.keypoints?.visibility?.[index] !==
3996
- KeypointVisibility.NotLabeled &&
3997
- Math.hypot(point.x - keypoint.x, point.y - keypoint.y) <=
3998
- keypointPadding);
4006
+ // Clustered keypoints share the pick tolerance; take the nearest so the
4007
+ // hover label, the menu, and the handle drag all name the same point.
4008
+ // Later points draw on top, so they win exact ties.
4009
+ let keypointIndex = -1;
4010
+ let keypointDistance = Number.POSITIVE_INFINITY;
4011
+ detection.keypoints.points.forEach((keypoint, index) => {
4012
+ if (detection.keypoints?.visibility?.[index] ===
4013
+ KeypointVisibility.NotLabeled) {
4014
+ return;
4015
+ }
4016
+ const distance = Math.hypot(point.x - keypoint.x, point.y - keypoint.y);
4017
+ if (distance <= keypointPadding && distance <= keypointDistance) {
4018
+ keypointIndex = index;
4019
+ keypointDistance = distance;
4020
+ }
4021
+ });
3999
4022
  if (keypointIndex !== -1) {
4000
4023
  pushCandidate(DetectionPickTarget.Keypoint, Math.PI * keypointPadding * keypointPadding, keypointIndex);
4001
4024
  }
@@ -4368,29 +4391,77 @@ function getAnnotationHandles(detection, viewportScale = 1) {
4368
4391
  if (detection.polyline)
4369
4392
  return getPathHandles(detection.polyline.points, false, radius, hitSize);
4370
4393
  if (detection.keypoints) {
4371
- return detection.keypoints.points.map((point, geometryIndex) => ({
4372
- cursor: "move",
4373
- geometryIndex,
4374
- hitSize,
4375
- id: `kp-${geometryIndex}`,
4376
- kind: AnnotationHandleKind.Keypoint,
4377
- point,
4378
- radius,
4379
- }));
4394
+ const keypointHandles = detection.keypoints.points.flatMap((point, geometryIndex) => detection.keypoints?.visibility?.[geometryIndex] ===
4395
+ KeypointVisibility.NotLabeled
4396
+ ? []
4397
+ : [
4398
+ {
4399
+ cursor: "pointer",
4400
+ geometryIndex,
4401
+ hitSize,
4402
+ id: `kp-${geometryIndex}`,
4403
+ kind: AnnotationHandleKind.Keypoint,
4404
+ point,
4405
+ radius,
4406
+ },
4407
+ ]);
4408
+ if (!detection.rect)
4409
+ return keypointHandles;
4410
+ // The skeleton's box resizes like any box; keypoints come last so a point
4411
+ // sitting on the box edge still wins the hit test.
4412
+ return [
4413
+ ...getBoxHandles(detection.rect, radius, hitSize),
4414
+ ...keypointHandles,
4415
+ ];
4380
4416
  }
4381
4417
  if (detection.rect)
4382
4418
  return getBoxHandles(detection.rect, radius, hitSize);
4383
4419
  return [];
4384
4420
  }
4385
4421
  function pickAnnotationHandle(handles, point) {
4386
- return [...handles]
4387
- .reverse()
4388
- .find((handle) => Math.abs(point.x - handle.point.x) <= handle.hitSize / 2 &&
4389
- Math.abs(point.y - handle.point.y) <= handle.hitSize / 2);
4422
+ let picked;
4423
+ let pickedDistance = Number.POSITIVE_INFINITY;
4424
+ for (const handle of handles) {
4425
+ const dx = Math.abs(point.x - handle.point.x);
4426
+ const dy = Math.abs(point.y - handle.point.y);
4427
+ if (dx > handle.hitSize / 2 || dy > handle.hitSize / 2)
4428
+ continue;
4429
+ // Clustered handles (face keypoints, a tiny box's corners) share hit
4430
+ // areas; the nearest one is what the pointer is on. Later handles draw on
4431
+ // top, so they win exact ties.
4432
+ const distance = Math.hypot(dx, dy);
4433
+ if (distance <= pickedDistance) {
4434
+ picked = handle;
4435
+ pickedDistance = distance;
4436
+ }
4437
+ }
4438
+ return picked;
4390
4439
  }
4391
4440
  function applyAnnotationHandleDrag(detection, handle, point) {
4392
4441
  if (detection.rect && handle.kind === AnnotationHandleKind.Resize) {
4393
- return { ...detection, rect: resizeRect(detection.rect, handle.id, point) };
4442
+ const rect = resizeRect(detection.rect, handle.id, point);
4443
+ const boxRelative = detection.keypoints?.boxRelative;
4444
+ if (!detection.keypoints || !boxRelative?.some(Boolean)) {
4445
+ return { ...detection, rect };
4446
+ }
4447
+ // Box-relative keypoints (unplaced template points) follow the rect;
4448
+ // points the user positioned keep their coordinates.
4449
+ const from = detection.rect;
4450
+ const scaleX = from.width > 0 ? rect.width / from.width : 1;
4451
+ const scaleY = from.height > 0 ? rect.height / from.height : 1;
4452
+ return {
4453
+ ...detection,
4454
+ keypoints: {
4455
+ ...detection.keypoints,
4456
+ points: detection.keypoints.points.map((keypoint, index) => boxRelative[index]
4457
+ ? {
4458
+ x: rect.x + (keypoint.x - from.x) * scaleX,
4459
+ y: rect.y + (keypoint.y - from.y) * scaleY,
4460
+ }
4461
+ : keypoint),
4462
+ },
4463
+ rect,
4464
+ };
4394
4465
  }
4395
4466
  if (handle.kind === AnnotationHandleKind.AddVertex &&
4396
4467
  handle.edgeIndex !== undefined) {
@@ -4411,7 +4482,16 @@ function applyAnnotationHandleDrag(detection, handle, point) {
4411
4482
  if (detection.keypoints) {
4412
4483
  const points = [...detection.keypoints.points];
4413
4484
  points[handle.geometryIndex] = point;
4414
- return { ...detection, keypoints: { ...detection.keypoints, points } };
4485
+ // A positioned point no longer follows the box.
4486
+ const boxRelative = detection.keypoints.boxRelative?.map((relative, index) => relative && index !== handle.geometryIndex);
4487
+ return {
4488
+ ...detection,
4489
+ keypoints: {
4490
+ ...detection.keypoints,
4491
+ points,
4492
+ ...(boxRelative ? { boxRelative } : {}),
4493
+ },
4494
+ };
4415
4495
  }
4416
4496
  return detection;
4417
4497
  }
@@ -4580,7 +4660,7 @@ function createAnnotationEditingEngine(options = {}) {
4580
4660
  case "move": {
4581
4661
  const dx = input.point.x - gesture.start.point.x;
4582
4662
  const dy = input.point.y - gesture.start.point.y;
4583
- const moved = gesture.moved || Math.hypot(dx, dy) >= MOVE_THRESHOLD / scale();
4663
+ const moved = gesture.moved || exceedsMoveThreshold(gesture.start, input);
4584
4664
  gesture = { ...gesture, current: input, moved };
4585
4665
  if (moved) {
4586
4666
  const preview = offsetDetection(gesture.detection, dx, dy);
@@ -4594,10 +4674,17 @@ function createAnnotationEditingEngine(options = {}) {
4594
4674
  }
4595
4675
  break;
4596
4676
  }
4597
- case "resize":
4598
- gesture = { ...gesture, current: input };
4599
- setPreview(applyAnnotationHandleDrag(gesture.detection, gesture.handle, input.point));
4677
+ case "resize": {
4678
+ // A click on a handle is not a drag: a keypoint tapped to open its
4679
+ // menu must not snap to the pointer, so nothing previews or commits
4680
+ // until the pointer has travelled like a move gesture.
4681
+ const moved = gesture.moved || exceedsMoveThreshold(gesture.start, input);
4682
+ gesture = { ...gesture, current: input, moved };
4683
+ if (moved) {
4684
+ setPreview(applyAnnotationHandleDrag(gesture.detection, gesture.handle, input.point));
4685
+ }
4600
4686
  break;
4687
+ }
4601
4688
  case "path":
4602
4689
  setPreview(resolvePathPreview([...gesture.points, input.point]));
4603
4690
  break;
@@ -4659,6 +4746,10 @@ function createAnnotationEditingEngine(options = {}) {
4659
4746
  commit(preview, active.detection);
4660
4747
  return;
4661
4748
  }
4749
+ if (!active.moved) {
4750
+ setState(idleState());
4751
+ return;
4752
+ }
4662
4753
  const preview = applyAnnotationHandleDrag(active.detection, active.handle, input.point);
4663
4754
  commit(preview, active.detection);
4664
4755
  },
@@ -4679,6 +4770,7 @@ function createAnnotationEditingEngine(options = {}) {
4679
4770
  detection,
4680
4771
  handle,
4681
4772
  kind: "resize",
4773
+ moved: false,
4682
4774
  start: input,
4683
4775
  };
4684
4776
  setState({
@@ -4686,7 +4778,7 @@ function createAnnotationEditingEngine(options = {}) {
4686
4778
  activeHandleId: handle.id,
4687
4779
  kind: AnnotationGestureStateKind.Resizing,
4688
4780
  pointerId: input.pointerId ?? null,
4689
- preview: detection,
4781
+ preview: null,
4690
4782
  });
4691
4783
  },
4692
4784
  deleteVertex(detection, vertexIndex) {
@@ -4864,6 +4956,10 @@ function createAnnotationEditingEngine(options = {}) {
4864
4956
  function scale() {
4865
4957
  return Math.max(options.viewportScale?.() ?? 1, Number.EPSILON);
4866
4958
  }
4959
+ function exceedsMoveThreshold(start, input) {
4960
+ return (Math.hypot(input.point.x - start.point.x, input.point.y - start.point.y) >=
4961
+ MOVE_THRESHOLD / scale());
4962
+ }
4867
4963
  function capture(pointerId) {
4868
4964
  if (pointerId !== undefined)
4869
4965
  options.capturePointer?.(pointerId);