Skip to content

Conversation

mcdoh
Copy link
Contributor

@mcdoh mcdoh commented Oct 15, 2025

What changes are proposed in this pull request?

(Please fill in changes proposed in this fix)

How is this patch tested? If it is not, please explain why.

(Details)

Release Notes

Is this a user-facing change that should be mentioned in the release notes?

  • No. You can skip the rest of this section.
  • Yes. Give a description of this change to be included in the release
    notes for FiftyOne users.

(Details in 1-2 sentences. You can just refer to another PR with a description
if this PR is part of a larger change.)

What areas of FiftyOne does this PR affect?

  • App: FiftyOne application changes
  • Build: Build and test infrastructure changes
  • Core: Core fiftyone Python library changes
  • Documentation: FiftyOne documentation changes
  • Other

Summary by CodeRabbit

  • New Features

    • New "setting" mode for smoother creation/adjustment of bounding boxes and improved overlay-driven interaction flow.
  • Bug Fixes

    • Prevents invalid/negative bounding boxes and division-by-zero during resizing.
    • More reliable overlay coordinate/bounds updates and safer selection handling when labels are missing.
    • Scrim/mask now respects scene boundaries.
  • Chores

    • Minor formatting adjustments with no user-visible behavior changes.

@mcdoh mcdoh requested a review from a team as a code owner October 15, 2025 16:08
Copy link
Contributor

coderabbitai bot commented Oct 15, 2025

Walkthrough

Added overlay-aware interaction support and a SETTING move state for bounding boxes; introduced BaseOverlay.validBounds and guards in Scene2D for relative-bounds updates and coordinate-update signaling. Minor formatting changes in annotate hooks and a guard on overlay selection assignment.

Changes

Cohort / File(s) Summary
Annotate formatting & guard
app/packages/core/src/components/Modal/Sidebar/Annotate/Edit/AnnotationSchema.tsx, app/packages/core/src/components/Modal/Sidebar/Annotate/useLabels.ts, app/packages/core/src/components/Modal/Sidebar/Annotate/useFocus.ts
Reformatted arrow functions (no behavioral change) and added a guard on LIGHTER_EVENTS.OVERLAY_SELECT to avoid assigning undefined to editing.
Overlay base & validation
app/packages/lighter/src/overlay/BaseOverlay.ts
Added public field: string and static validBounds(bounds: Rect): boolean; adjusted imports to use Rect.
Bounding box behavior
app/packages/lighter/src/overlay/BoundingBoxOverlay.ts
Added SETTING move state and settingBounds flag; initialize absoluteBounds with NaN; added hasValidBounds(); guard bounds updates with BaseOverlay.validBounds; include SETTING in move/resize logic and protect aspect-ratio math.
Scene bounds & signaling
app/packages/lighter/src/core/Scene2D.ts
Validate absolute bounds via BaseOverlay.validBounds before computing/applying relative bounds; call overlay.markCoordinateUpdateComplete() immediately after applying relative bounds; removed prior unconditional post-render call.
Interaction overlay support
app/packages/lighter/src/interaction/InteractionManager.ts
InteractionHandler gains optional overlay?: BoundingBoxOverlay; pointer routing prefers overlay when present; drag end swaps overlay back into handlers; canvas/world-point conversion simplified when overlay present; import added for BoundingBoxOverlay.
Renderer scrim clamp
app/packages/lighter/src/renderer/PixiRenderer2D.ts
Replaced direct mask rect with clamped, scene-bounded rectangle calculation for the scrim mask.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User Pointer
  participant IM as InteractionManager
  participant O as BoundingBoxOverlay
  participant S as Scene2D
  note over IM,O #dbeafe: Overlay-aware interactions & SETTING state

  U->>IM: pointerdown / move / up
  alt overlay present
    IM->>O: dispatch pointer events
    O->>O: enter SETTING / DRAGGING / RESIZE states
    O->>O: update absoluteBounds (guard with BaseOverlay.validBounds)
    alt bounds valid
      O->>S: request relative bounds update
      S->>S: compute relative bounds
      S->>O: apply relative bounds
      S->>O: markCoordinateUpdateComplete()
    else bounds invalid
      O-->>IM: skip applying relative bounds
    end
    opt drag end with overlay
      IM->>IM: swap overlay back into handler list
    end
  else
    IM->>IM: fallback to non-overlay handler flow
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit traced a box in air,
With bounds that only truth could bear;
"SETTING now," it twitched its ears,
Guards and swaps calmed all our fears;
Handlers hop and coordinates sing. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Feature/establish bounding box” reads like a branch name and is too generic to convey the primary change, lacking clarity about what feature is being added or modified and failing to summarize the key updates to bounding box overlay state and validation logic. Please update the title to a concise, descriptive sentence that highlights the main change, for example “Add SETTING state and validBounds check to BoundingBoxOverlay” or “Support establishing bounding boxes in overlay interactions.”
Description Check ⚠️ Warning The pull request description is just the unfilled template with no details on the proposed changes, testing strategy, release notes, or affected areas, leaving reviewers without necessary context on what this PR implements or how it is validated. Please complete the template by summarizing the actual changes made, describing how these changes are tested, indicating if this is a user-facing update for release notes, and selecting the affected areas of FiftyOne in the checklist.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/establish-bounding-box

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/packages/lighter/src/overlay/BaseOverlay.ts (1)

36-40: Consider using isFinite for explicit NaN/Infinity checks.

While bounds[prop] >= 0 correctly rejects NaN (since NaN >= 0 is false), using Number.isFinite() would make the intent clearer and also catch Infinity values.

 static validBounds(bounds: Rect): boolean {
   return ["x", "y", "width", "height"].every(
-    (prop) => typeof bounds[prop] === "number" && bounds[prop] >= 0
+    (prop) => Number.isFinite(bounds[prop]) && bounds[prop] >= 0
   );
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4cb3e66 and b1cc2c2.

📒 Files selected for processing (6)
  • app/packages/core/src/components/Modal/Sidebar/Annotate/Edit/AnnotationSchema.tsx (1 hunks)
  • app/packages/core/src/components/Modal/Sidebar/Annotate/useLabels.ts (1 hunks)
  • app/packages/lighter/src/core/Scene2D.ts (2 hunks)
  • app/packages/lighter/src/interaction/InteractionManager.ts (6 hunks)
  • app/packages/lighter/src/overlay/BaseOverlay.ts (2 hunks)
  • app/packages/lighter/src/overlay/BoundingBoxOverlay.ts (14 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/core/src/components/Modal/Sidebar/Annotate/Edit/AnnotationSchema.tsx
  • app/packages/lighter/src/core/Scene2D.ts
  • app/packages/core/src/components/Modal/Sidebar/Annotate/useLabels.ts
  • app/packages/lighter/src/overlay/BaseOverlay.ts
  • app/packages/lighter/src/interaction/InteractionManager.ts
  • app/packages/lighter/src/overlay/BoundingBoxOverlay.ts
🔇 Additional comments (8)
app/packages/core/src/components/Modal/Sidebar/Annotate/Edit/AnnotationSchema.tsx (1)

123-136: LGTM! Formatting update with no functional changes.

The reformatting of the useHandleChanges hook to a more compact arrow function style maintains the same logic, error handling, and type conversions. The change improves code consistency across the codebase.

app/packages/core/src/components/Modal/Sidebar/Annotate/useLabels.ts (1)

117-131: LGTM! Formatting update with no functional changes.

The reformatting of the getFieldType callback to a more compact arrow function style maintains identical logic, error handling, and type assertions. This change aligns with the formatting updates in AnnotationSchema.tsx for code consistency.

app/packages/lighter/src/core/Scene2D.ts (1)

1440-1447: LGTM! Defensive bounds validation added.

The validity check prevents invalid bounds (NaN, negative values) from being propagated through the coordinate system. The placement of markCoordinateUpdateComplete() inside the validity check ensures coordinate updates are only marked complete when bounds are actually valid.

app/packages/lighter/src/interaction/InteractionManager.ts (1)

226-227: LGTM! Overlay-aware interaction flow implemented correctly.

The changes enable overlays to be used during interactive drawing/setting workflows. The pattern is consistent:

  • Prefer interactiveHandler.overlay when present
  • Fall back to interactiveHandler otherwise
  • After pointer up, swap the interactive handler with the overlay to transition from drawing mode to regular interaction mode

This design cleanly separates the interactive creation workflow from normal overlay interactions.

Also applies to: 284-293, 327-346

app/packages/lighter/src/overlay/BoundingBoxOverlay.ts (4)

57-57: LGTM! SETTING state enables initial bounds establishment.

The addition of the SETTING state, settingBounds flag, and NaN initialization work together to support a workflow where bounding box bounds are established during user interaction. Initializing with NaN appropriately marks the bounds as initially invalid until set by the user.

Also applies to: 75-75, 89-89


103-107: Bounds validation added to setters.

Both setAbsoluteBounds and setRelativeBounds now validate bounds before applying them. This defensive approach prevents invalid bounds (NaN, negative values) from being stored, ensuring state consistency throughout the overlay lifecycle.

Also applies to: 111-115


463-465: LGTM! Division-by-zero prevention added.

The fallback to aspectRatio = 1 when moveStartBounds has zero or undefined dimensions prevents division-by-zero errors during resize operations. This is particularly important when transitioning from the SETTING state where bounds may be initially invalid.


295-297: LGTM! SETTING state fully integrated into interaction lifecycle.

The SETTING state is comprehensively handled throughout the interaction flow:

  • Cursor changes to crosshair when bounds are invalid
  • onPointerDown initializes position and sets dimensions to 0
  • onMove and onResize treat SETTING as a resize operation
  • settingBounds flag is properly reset on pointer up
  • hasValidBounds() provides a clean public API for validity checks

The implementation enables a smooth workflow for establishing bounding boxes through user interaction.

Also applies to: 344-344, 379-394, 417-417, 492-509, 565-565, 586-592

*/
export abstract class BaseOverlay implements InteractionHandler {
readonly id: string;
readonly field: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Duplicate field declaration.

Line 18 declares readonly field: string but Line 33 still declares protected field: string. TypeScript will reject this duplicate declaration.

Apply this diff to remove the duplicate:

   protected eventBus?: EventBus;
   protected resourceLoader?: ResourceLoader;
   protected currentStyle?: DrawStyle;
-  protected field: string;
   protected label: RawLookerLabel;

Also applies to: 33-33

🤖 Prompt for AI Agents
In app/packages/lighter/src/overlay/BaseOverlay.ts around lines 18 and 33,
there's a duplicate declaration of `field` (`readonly field: string` at line 18
and `protected field: string` at line 33); remove the duplicate at line 18 so
only the `protected field: string` declaration remains (or alternatively make
both consistent by replacing line 33 with `protected readonly field: string` if
immutability is intended), ensuring a single declaration to satisfy TypeScript.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/packages/core/src/components/Modal/Sidebar/Annotate/useFocus.ts (1)

14-17: Good defensive guard added.

The guard prevents setting editing to an undefined value, which is a solid defensive programming practice.

Consider whether the case where a label is not found represents an error condition. If overlay IDs can legitimately be out of sync with labelMap, the current silent handling is appropriate. However, if this indicates a bug, you might want to add development-mode logging or explicitly clear the editing state to avoid leaving stale values.

Example with logging:

 const label = store.get(labelMap)[event.detail.id];
 if (label) {
   store.set(editing, label);
+} else if (process.env.NODE_ENV === 'development') {
+  console.warn(`Label not found for overlay ID: ${event.detail.id}`);
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1cc2c2 and a046f95.

📒 Files selected for processing (1)
  • app/packages/core/src/components/Modal/Sidebar/Annotate/useFocus.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/core/src/components/Modal/Sidebar/Annotate/useFocus.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a046f95 and 951e979.

📒 Files selected for processing (1)
  • app/packages/lighter/src/renderer/PixiRenderer2D.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/lighter/src/renderer/PixiRenderer2D.ts

Comment on lines +207 to +216
const x = Math.max(bounds.x - halfWidth, 0);
const y = Math.max(bounds.y - halfWidth, 0);
const w =
Math.min(bounds.width + borderWidth, sceneDimensions.width - bounds.x) +
Math.min(bounds.x, 0);
const h =
Math.min(bounds.height + borderWidth, sceneDimensions.height - bounds.y) +
Math.min(bounds.y, 0);

mask.rect(x, y, w, h);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Incorrect width/height calculation for scrim cutout.

The formulas for w and h contain logic errors that produce incorrect dimensions when the bounding box extends beyond scene boundaries.

Issue Analysis:

When bounds.x < 0 (left edge outside scene):

  • Current formula: w = Math.min(110, 210) + (-10) = 100
  • Expected: 95 (the visible portion from x=0 to x=95)

When the bounding box extends past the right edge:

  • Current formula: w = Math.min(110, 50) + 0 = 50
  • Expected: 55 (from x=145 to x=200)

The issue is that the formula attempts to compute width from bounds.x rather than from the clamped x coordinate, and the + Math.min(bounds.x, 0) adjustment doesn't fully compensate.

Apply this diff to fix the calculation:

 const halfWidth = borderWidth / 2;
 const x = Math.max(bounds.x - halfWidth, 0);
 const y = Math.max(bounds.y - halfWidth, 0);
-const w =
-  Math.min(bounds.width + borderWidth, sceneDimensions.width - bounds.x) +
-  Math.min(bounds.x, 0);
-const h =
-  Math.min(bounds.height + borderWidth, sceneDimensions.height - bounds.y) +
-  Math.min(bounds.y, 0);
+const w = Math.min(bounds.x + bounds.width + halfWidth, sceneDimensions.width) - x;
+const h = Math.min(bounds.y + bounds.height + halfWidth, sceneDimensions.height) - y;
 
 mask.rect(x, y, w, h);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const x = Math.max(bounds.x - halfWidth, 0);
const y = Math.max(bounds.y - halfWidth, 0);
const w =
Math.min(bounds.width + borderWidth, sceneDimensions.width - bounds.x) +
Math.min(bounds.x, 0);
const h =
Math.min(bounds.height + borderWidth, sceneDimensions.height - bounds.y) +
Math.min(bounds.y, 0);
mask.rect(x, y, w, h);
const halfWidth = borderWidth / 2;
const x = Math.max(bounds.x - halfWidth, 0);
const y = Math.max(bounds.y - halfWidth, 0);
const w = Math.min(bounds.x + bounds.width + halfWidth, sceneDimensions.width) - x;
const h = Math.min(bounds.y + bounds.height + halfWidth, sceneDimensions.height) - y;
mask.rect(x, y, w, h);
🤖 Prompt for AI Agents
In app/packages/lighter/src/renderer/PixiRenderer2D.ts around lines 207 to 216,
the width/height for the scrim cutout are computed from the original bounds.x/y
causing incorrect sizes when the box is outside scene bounds; fix by computing
clamped left/top (x,y) as you already do, then compute clamped right =
Math.min(bounds.x + bounds.width + borderWidth, sceneDimensions.width) and
bottom = Math.min(bounds.y + bounds.height + borderWidth,
sceneDimensions.height), and set w = Math.max(0, right - x) and h = Math.max(0,
bottom - y) so the cutout dimensions are derived from the clamped coordinates
and never negative.

Copy link
Contributor

@benjaminpkane benjaminpkane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works great!

@benjaminpkane benjaminpkane merged commit 281b4d2 into feat/human-annotation Oct 16, 2025
14 checks passed
@benjaminpkane benjaminpkane deleted the feature/establish-bounding-box branch October 16, 2025 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants