Skip to content

Commit 0cbabaa

Browse files
benjaminpkanemcdohGavin McDonald
authored
Human Annotation Sidebar (#6410)
* merge * sidebar work * fix bounding box * keep image pixelated (#6409) Co-authored-by: Gavin McDonald <[email protected]> * cleanup events, and schema creation --------- Co-authored-by: Gavin <[email protected]> Co-authored-by: Gavin McDonald <[email protected]>
1 parent e87673c commit 0cbabaa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+999
-914
lines changed

app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx

Lines changed: 6 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
11
/**
22
* Copyright 2017-2025, Voxel51, Inc.
33
*/
4-
import { editing } from "@fiftyone/core/src/components/Modal/Sidebar/Annotate/Edit";
5-
import {
6-
labelAtoms,
7-
labels,
8-
} from "@fiftyone/core/src/components/Modal/Sidebar/Annotate/useLabels";
94
import {
105
ImageOptions,
116
ImageOverlay,
127
overlayFactory,
138
useLighter,
149
useLighterSetupWithPixi,
1510
} from "@fiftyone/lighter";
16-
import { FROM_FO, loadOverlays } from "@fiftyone/looker/src/overlays";
17-
import DetectionOverlay from "@fiftyone/looker/src/overlays/detection";
11+
import type { Sample } from "@fiftyone/state";
1812
import * as fos from "@fiftyone/state";
19-
import { Sample, getSampleSrc } from "@fiftyone/state";
20-
import { getDefaultStore, useSetAtom } from "jotai";
13+
import { getSampleSrc } from "@fiftyone/state";
2114
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
2215
import { useRecoilValue } from "recoil";
2316
import { singletonCanvas } from "./SharedCanvas";
24-
import { convertLegacyToLighterDetection } from "./looker-lighter-bridge";
2517
import { useBridge } from "./useBridge";
2618

2719
export interface LighterSampleRendererProps {
@@ -34,10 +26,10 @@ export interface LighterSampleRendererProps {
3426
/**
3527
* Lighter unit sample renderer with PixiJS renderer.
3628
*/
37-
export const LighterSampleRenderer: React.FC<LighterSampleRendererProps> = ({
29+
export const LighterSampleRenderer = ({
3830
className = "",
3931
sample,
40-
}) => {
32+
}: LighterSampleRendererProps) => {
4133
const containerRef = useRef<HTMLDivElement>(null);
4234

4335
// we have this hack to force a re-render on layout effect, so that containerRef.current is defined
@@ -51,12 +43,6 @@ export const LighterSampleRenderer: React.FC<LighterSampleRendererProps> = ({
5143
// Get access to the lighter instance
5244
const { scene, isReady, addOverlay } = useLighter();
5345

54-
const setEditing = useSetAtom(editing);
55-
56-
const schema = fos.useAssertedRecoilValue(
57-
fos.fieldSchema({ space: fos.State.SPACE.SAMPLE })
58-
);
59-
6046
/**
6147
* This effect is responsible for loading the sample and adding the overlays to the scene.
6248
*/
@@ -82,70 +68,10 @@ export const LighterSampleRenderer: React.FC<LighterSampleRendererProps> = ({
8268
scene.setCanonicalMedia(mediaOverlay);
8369
}
8470

85-
const overlays = loadOverlays(sample.sample, schema, false);
86-
87-
for (const overlay of overlays) {
88-
if (overlay instanceof DetectionOverlay) {
89-
// Convert legacy overlay to lighter overlay with relative coordinates
90-
const lighterOverlay = convertLegacyToLighterDetection(
91-
overlay,
92-
sample.id
93-
);
94-
addOverlay(lighterOverlay, false);
95-
}
96-
}
97-
9871
return () => {
9972
scene.destroy();
10073
};
101-
}, [isReady, addOverlay, sample, scene, schema]);
102-
103-
useEffect(() => {
104-
if (!scene || !sample) return;
105-
106-
const store = getDefaultStore();
107-
108-
// hack: this is how we execute it once
109-
let areOverlaysAdded = false;
110-
111-
const unsub = store.sub(labels, () => {
112-
const labelAtomsList = store.get(labelAtoms);
113-
114-
if (areOverlaysAdded) return;
115-
116-
for (const atom of labelAtomsList) {
117-
const label = store.get(atom);
118-
if (!FROM_FO[label.type]) {
119-
continue;
120-
}
121-
const overlay = FROM_FO[label.type](label.path, label.data)[0];
122-
if (overlay instanceof DetectionOverlay) {
123-
// Convert legacy overlay to lighter overlay with relative coordinates
124-
const lighterOverlay = convertLegacyToLighterDetection(
125-
overlay,
126-
sample.id
127-
);
128-
129-
addOverlay(lighterOverlay, false);
130-
}
131-
areOverlaysAdded = true;
132-
}
133-
});
134-
135-
return () => {
136-
unsub();
137-
areOverlaysAdded = false;
138-
};
139-
}, [scene, sample, addOverlay, labelAtoms]);
140-
141-
/**
142-
* This effect runs cleanup when the component unmounts
143-
*/
144-
useEffect(() => {
145-
return () => {
146-
setEditing(null);
147-
};
148-
}, []);
74+
}, [isReady, addOverlay, sample, scene]);
14975

15076
return (
15177
<div
@@ -174,7 +100,7 @@ const LighterSetupImpl = (props: {
174100
fos.lookerOptions({ modal: true, withFilter: false })
175101
);
176102

177-
const canvas = singletonCanvas.getCanvas(containerRef.current!);
103+
const canvas = singletonCanvas.getCanvas(containerRef.current);
178104

179105
const { scene } = useLighterSetupWithPixi(canvas, options);
180106

app/packages/core/src/components/Modal/Lighter/SharedCanvas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class SingletonCanvas {
5353
* The canvas element itself is preserved for reuse.
5454
*/
5555
detach(): void {
56-
if (this.canvas && this.canvas.parentNode) {
56+
if (this.canvas?.parentNode) {
5757
this.canvas.parentNode.removeChild(this.canvas);
5858
this.isAttached = false;
5959
this.container = null;

app/packages/core/src/components/Modal/Lighter/looker-lighter-bridge.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

app/packages/core/src/components/Modal/Lighter/useBridge.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
* Copyright 2017-2025, Voxel51, Inc.
33
*/
44

5-
import { Scene2D } from "@fiftyone/lighter";
6-
import { colorScheme, colorSeed } from "@fiftyone/state";
5+
import type { Scene2D } from "@fiftyone/lighter";
76
import { useEffect } from "react";
8-
import { useRecoilValue } from "recoil";
9-
import { useOverlayPersistence } from "./useOverlayPersistence";
10-
import { useLighterTooltipEventHandler } from "./useLighterTooltipEventHandler";
7+
import useColorMappingContext from "./useColorMappingContext";
118

129
/**
1310
* Hook that bridges FiftyOne state management system with Lighter.
@@ -17,11 +14,9 @@ import { useLighterTooltipEventHandler } from "./useLighterTooltipEventHandler";
1714
* 2. We trigger certain events into "FiftyOne state" world based on user interactions in Lighter.
1815
*/
1916
export const useBridge = (scene: Scene2D | null) => {
20-
const currentColorScheme = useRecoilValue(colorScheme);
21-
const currentColorSeed = useRecoilValue(colorSeed);
22-
23-
useLighterTooltipEventHandler(scene);
24-
useOverlayPersistence(scene);
17+
// useLighterTooltipEventHandler(scene);
18+
// useOverlayPersistence(scene);
19+
const context = useColorMappingContext();
2520

2621
// Effect to update scene with color scheme changes
2722
useEffect(() => {
@@ -30,14 +25,11 @@ export const useBridge = (scene: Scene2D | null) => {
3025
}
3126

3227
// Update the scene's color mapping context
33-
scene.updateColorMappingContext({
34-
colorScheme: currentColorScheme,
35-
seed: currentColorSeed,
36-
});
28+
scene.updateColorMappingContext(context);
3729

3830
// Mark all overlays as dirty to trigger re-rendering with new colors
39-
scene.getAllOverlays().forEach((overlay) => {
31+
for (const overlay of scene.getAllOverlays()) {
4032
overlay.markDirty();
41-
});
42-
}, [scene, currentColorScheme, currentColorSeed]);
33+
}
34+
}, [scene, context]);
4335
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { colorScheme, colorSeed } from "@fiftyone/state";
2+
import { useMemo } from "react";
3+
import { useRecoilValue } from "recoil";
4+
5+
export default function useColorMappingContext() {
6+
const currentColorScheme = useRecoilValue(colorScheme);
7+
const currentColorSeed = useRecoilValue(colorSeed);
8+
return useMemo(
9+
() => ({
10+
colorScheme: currentColorScheme,
11+
seed: currentColorSeed,
12+
}),
13+
[currentColorScheme, currentColorSeed]
14+
);
15+
}

app/packages/core/src/components/Modal/Lighter/useLighterTooltipEventHandler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
* Copyright 2017-2025, Voxel51, Inc.
33
*/
44

5+
import type { Scene2D } from "@fiftyone/lighter";
6+
import { LIGHTER_EVENTS } from "@fiftyone/lighter";
7+
import type { Hoverable } from "@fiftyone/lighter/src/types";
58
import * as fos from "@fiftyone/state";
69
import { useEffect } from "react";
710
import { useRecoilCallback } from "recoil";
8-
import { LIGHTER_EVENTS, Scene2D } from "@fiftyone/lighter";
9-
import type { Hoverable } from "@fiftyone/lighter/src/types";
1011

1112
/**
1213
* Hook that handles tooltip events for lighter overlays.

app/packages/core/src/components/Modal/Lighter/useOverlayPersistence.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22
* Copyright 2017-2025, Voxel51, Inc.
33
*/
44

5-
import {
6-
addLabel,
7-
removeLabel,
8-
} from "@fiftyone/core/src/components/Modal/Sidebar/Annotate/useLabels";
5+
import type { OverlayEventDetail, Scene2D } from "@fiftyone/lighter";
6+
import { LIGHTER_EVENTS } from "@fiftyone/lighter";
97
import { useOperatorExecutor } from "@fiftyone/operators";
10-
import { DETECTION } from "@fiftyone/utilities";
11-
import { useSetAtom } from "jotai";
128
import { useCallback, useEffect } from "react";
13-
import { LIGHTER_EVENTS, OverlayEventDetail, Scene2D } from "@fiftyone/lighter";
149

1510
/**
1611
* Hook that handles overlay persistence events.
@@ -19,9 +14,6 @@ export const useOverlayPersistence = (scene: Scene2D | null) => {
1914
const addBoundingBox = useOperatorExecutor("add_bounding_box");
2015
const removeBoundingBox = useOperatorExecutor("remove_bounding_box");
2116

22-
const addLabelEffect = useSetAtom(addLabel);
23-
const removeLabelEffect = useSetAtom(removeLabel);
24-
2517
const handlePersistOverlay = useCallback(
2618
(
2719
event: CustomEvent<
@@ -45,14 +37,6 @@ export const useOverlayPersistence = (scene: Scene2D | null) => {
4537
label: overlay.label ?? "",
4638
bounding_box: bbox,
4739
});
48-
49-
addLabelEffect({
50-
id: overlay.id,
51-
data: overlay,
52-
path: overlay.field,
53-
expandedPath: overlay.field + ".detections",
54-
type: DETECTION,
55-
});
5640
} catch (error) {
5741
console.error("Error adding bounding box", error);
5842
}
@@ -64,7 +48,7 @@ export const useOverlayPersistence = (scene: Scene2D | null) => {
6448
);
6549
}
6650
},
67-
[addBoundingBox, addLabelEffect]
51+
[addBoundingBox]
6852
);
6953

7054
const handleRemoveOverlay = useCallback(
@@ -81,13 +65,11 @@ export const useOverlayPersistence = (scene: Scene2D | null) => {
8165
path,
8266
sample_id: sampleId,
8367
});
84-
85-
removeLabelEffect(id);
8668
} catch (error) {
8769
console.error("Error removing bounding box", error);
8870
}
8971
},
90-
[removeBoundingBox, removeLabelEffect]
72+
[removeBoundingBox]
9173
);
9274

9375
useEffect(() => {
@@ -102,5 +84,5 @@ export const useOverlayPersistence = (scene: Scene2D | null) => {
10284
scene.off(LIGHTER_EVENTS.DO_PERSIST_OVERLAY, handlePersistOverlay);
10385
scene.off(LIGHTER_EVENTS.DO_REMOVE_OVERLAY, handleRemoveOverlay);
10486
};
105-
}, [scene]);
87+
}, [handlePersistOverlay, handleRemoveOverlay, scene]);
10688
};

0 commit comments

Comments
 (0)