Skip to content

Commit 3ddcd26

Browse files
Merge pull request #194 from ukaea/abdullah/cleanup-state
Video Annotation: More Refactoring
2 parents bc59710 + 4958f59 commit 3ddcd26

18 files changed

Lines changed: 400 additions & 706 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dependencies = [
2828
"pillow",
2929
"uda",
3030
"sal",
31-
"ruptures @ git+https://github.com/deepcharles/ruptures.git@e88da13b5f625f37321abd07c5d14b91701c6655",
31+
"ruptures>=1.1.10",
3232
"platformdirs",
3333
"mongita>=1.2.0",
3434
"filelock>=3.19.1",

toktagger/api/static/assets/index-B0emAv6V.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

toktagger/api/static/assets/index-Bc_FeFYi.js

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

toktagger/api/static/assets/index-CRmw4bxg.js

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

toktagger/api/static/assets/index-DNaqyC5n.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

toktagger/api/static/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
99
<title>TokTagger</title>
10-
<script type="module" crossorigin src="/assets/index-CRmw4bxg.js"></script>
10+
<script type="module" crossorigin src="/assets/index-Bc_FeFYi.js"></script>
1111
<link rel="modulepreload" crossorigin href="/assets/react-bvV3Sn6d.js">
1212
<link rel="modulepreload" crossorigin href="/assets/plotly-D-EVZd-E.js">
13-
<link rel="stylesheet" crossorigin href="/assets/index-DNaqyC5n.css">
13+
<link rel="stylesheet" crossorigin href="/assets/index-B0emAv6V.css">
1414
</head>
1515
<body>
1616
<div id="root"></div>

toktagger/ui/src/app/components/tools/nav.tsx

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import { useSample } from "@/app/contexts/SampleContext";
2828
import { useSampleHistory } from "@/app/contexts/SampleHistoryContext";
2929
import { getNextSample } from "@/app/core";
3030
import type { SortDescriptor, SortDirection, Key } from "@react-types/shared";
31+
import {
32+
useNavAdapterOptional,
33+
type NavAdapter,
34+
} from "@/app/video/components/video-nav-adapter";
3135

3236
const TOAST_TIMEOUT = 5000;
3337

@@ -82,6 +86,7 @@ type ButtonInfo = {
8286
annotations: Annotation[];
8387
setIsValidated: (validated: boolean) => void;
8488
saveOnNavigate?: boolean;
89+
navAdapter?: NavAdapter | null;
8590
};
8691

8792
type NextButtonInfo = ButtonInfo & {
@@ -103,17 +108,22 @@ function NextButton({
103108
visitedSampleIds,
104109
sortDescriptor,
105110
saveOnNavigate,
111+
navAdapter,
106112
}: NextButtonInfo) {
107113
const navigate = useNavigate();
108114

109115
const moveNextShot = useCallback(async () => {
116+
const annotationsToSave = navAdapter
117+
? navAdapter.getAnnotations()
118+
: annotations;
110119
await saveSampleAnnotations(
111120
project_id,
112121
sample_id,
113-
annotations,
122+
annotationsToSave,
114123
saveOnNavigate,
115124
);
116125
if (saveOnNavigate) {
126+
navAdapter?.afterSave?.();
117127
setIsValidated(true);
118128
}
119129
await navigateToNextSample(
@@ -131,6 +141,7 @@ function NextButton({
131141
setIsValidated,
132142
visitedSampleIds,
133143
sortDescriptor,
144+
navAdapter,
134145
]);
135146

136147
useEffect(() => {
@@ -192,17 +203,22 @@ function PreviousButton({
192203
popVisitedSampleId,
193204
saveOnNavigate,
194205
sortDescriptor,
206+
navAdapter,
195207
}: PreviousButtonInfo) {
196208
const navigate = useNavigate();
197209

198210
const movePreviousShot = useCallback(async () => {
211+
const annotationsToSave = navAdapter
212+
? navAdapter.getAnnotations()
213+
: annotations;
199214
await saveSampleAnnotations(
200215
project_id,
201216
sample_id,
202-
annotations,
217+
annotationsToSave,
203218
saveOnNavigate,
204219
);
205220
if (saveOnNavigate) {
221+
navAdapter?.afterSave?.();
206222
setIsValidated(true);
207223
}
208224

@@ -224,6 +240,7 @@ function PreviousButton({
224240
popVisitedSampleId,
225241
sortDescriptor,
226242
setIsValidated,
243+
navAdapter,
227244
]);
228245

229246
useEffect(() => {
@@ -258,11 +275,21 @@ function SaveButton({
258275
annotations,
259276
setIsValidated,
260277
saveOnNavigate: _saveOnNavigate,
278+
navAdapter,
261279
}: ButtonInfo) {
262280
const handleClick = async () => {
263281
try {
264-
await saveSampleAnnotations(project_id, sample_id, annotations, true);
265-
ToastQueue.positive(`Saved ${annotations.length} annotations!`, {
282+
const annotationsToSave = navAdapter
283+
? navAdapter.getAnnotations()
284+
: annotations;
285+
await saveSampleAnnotations(
286+
project_id,
287+
sample_id,
288+
annotationsToSave,
289+
true,
290+
);
291+
navAdapter?.afterSave?.();
292+
ToastQueue.positive(`Saved ${annotationsToSave.length} annotations!`, {
266293
timeout: TOAST_TIMEOUT,
267294
});
268295
setIsValidated(true);
@@ -286,12 +313,19 @@ function SaveButton({
286313

287314
function ClearButton({
288315
setAnnotations,
316+
navAdapter,
289317
}: {
290318
setAnnotations: (
291319
updater: (annotations: Annotation[]) => Annotation[],
292320
) => void;
321+
navAdapter?: NavAdapter | null;
293322
}) {
294323
const handleClick = () => {
324+
if (navAdapter) {
325+
navAdapter.clear();
326+
return;
327+
}
328+
295329
setAnnotations(() => []);
296330
};
297331

@@ -311,6 +345,8 @@ type SaveInfo = {
311345
annotations: Annotation[];
312346
sortDescriptor: SortDescriptor | null;
313347
saveOnNavigate?: boolean;
348+
setIsValidated: (validated: boolean) => void;
349+
navAdapter?: NavAdapter | null;
314350
};
315351

316352
export function ShotSearch({
@@ -319,6 +355,8 @@ export function ShotSearch({
319355
annotations,
320356
sortDescriptor,
321357
saveOnNavigate,
358+
setIsValidated,
359+
navAdapter,
322360
}: SaveInfo) {
323361
const navigate = useNavigate();
324362
const [errorMessage, setErrorMessage] = useState<string>("");
@@ -332,12 +370,19 @@ export function ShotSearch({
332370
try {
333371
const sample = await getShotSample(project_id, shot_id);
334372
if (sample !== null) {
373+
const annotationsToSave = navAdapter
374+
? navAdapter.getAnnotations()
375+
: annotations;
335376
await saveSampleAnnotations(
336377
project_id,
337378
sample_id,
338-
annotations,
379+
annotationsToSave,
339380
saveOnNavigate,
340381
);
382+
if (saveOnNavigate) {
383+
navAdapter?.afterSave?.();
384+
setIsValidated(true);
385+
}
341386
navigateToSample(project_id, sample._id, navigate, sortDescriptor);
342387
} else {
343388
setErrorMessage("Shot not found!");
@@ -366,6 +411,7 @@ type NavigationBarInfo = {
366411
};
367412
export function NavigationBar({ project_id, sample_id }: NavigationBarInfo) {
368413
const { annotations, setAnnotations, setIsValidated } = useSample();
414+
const navAdapter = useNavAdapterOptional();
369415

370416
const {
371417
visitedSampleIds,
@@ -394,6 +440,7 @@ export function NavigationBar({ project_id, sample_id }: NavigationBarInfo) {
394440
sample_id={sample_id}
395441
annotations={annotations}
396442
setIsValidated={setIsValidated}
443+
navAdapter={navAdapter}
397444
/>
398445
<PreviousButton
399446
project_id={project_id}
@@ -404,6 +451,7 @@ export function NavigationBar({ project_id, sample_id }: NavigationBarInfo) {
404451
popVisitedSampleId={popVisitedSampleId}
405452
saveOnNavigate={SaveOnNavigate}
406453
sortDescriptor={sortDescriptor}
454+
navAdapter={navAdapter}
407455
/>
408456
<NextButton
409457
project_id={project_id}
@@ -413,8 +461,9 @@ export function NavigationBar({ project_id, sample_id }: NavigationBarInfo) {
413461
visitedSampleIds={visitedSampleIds}
414462
saveOnNavigate={SaveOnNavigate}
415463
sortDescriptor={sortDescriptor}
464+
navAdapter={navAdapter}
416465
/>
417-
<ClearButton setAnnotations={setAnnotations} />
466+
<ClearButton setAnnotations={setAnnotations} navAdapter={navAdapter} />
418467
</ButtonGroup>
419468
<TooltipTrigger delay={1000} placement="bottom">
420469
<Checkbox isSelected={SaveOnNavigate} onChange={setSaveOnNavigate}>
@@ -431,6 +480,8 @@ export function NavigationBar({ project_id, sample_id }: NavigationBarInfo) {
431480
annotations={annotations}
432481
sortDescriptor={sortDescriptor}
433482
saveOnNavigate={SaveOnNavigate}
483+
setIsValidated={setIsValidated}
484+
navAdapter={navAdapter}
434485
/>
435486
</Flex>
436487
);

toktagger/ui/src/app/components/tools/toolbar.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import { NavigationBar } from "./nav";
3838
import { useSample } from "@/app/contexts/SampleContext";
3939
import SpectrogramThresholdTool from "../annotators/thresholding";
4040
import { VideoToolbox } from "@/app/video/components/video-toolbox";
41-
import { VideoNavigationBar } from "@/app/video/components/video-navigation-bar";
4241

4342
type AmplitudeSliderInfo = {
4443
data: SpectrogramData;
@@ -321,15 +320,7 @@ export default function ToolBar() {
321320
<Header height="size-300" marginBottom="size-100">
322321
<span style={{ fontSize: "1.2rem" }}>Controls</span>
323322
</Header>
324-
{project.task === TaskType.Video ? (
325-
<VideoNavigationBar
326-
project_id={project_id}
327-
sample_id={sample_id}
328-
onSaved={refreshAnnotations}
329-
/>
330-
) : (
331-
<NavigationBar project_id={project_id} sample_id={sample_id} />
332-
)}
323+
<NavigationBar project_id={project_id} sample_id={sample_id} />
333324
<Accordion allowsMultipleExpanded={true} width="100%">
334325
<Disclosure>
335326
<DisclosureTitle>

toktagger/ui/src/app/contexts/SampleContext.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export function SampleProvider({
137137
const [dataParams, setDataParams] = useState<DataParams>({
138138
name: "identity",
139139
});
140+
const [prevSampleId, setPrevSampleId] = useState(sampleId);
140141

141142
const [plotProps, setPlotProps] = useState<PlotProps>({
142143
colorMap: "Cividis",
@@ -154,9 +155,17 @@ export function SampleProvider({
154155

155156
// Video: remember the last successfully loaded frame so missing frames become navigation bounds.
156157
const lastGoodVideoFrameRef = useRef<number | null>(null);
158+
// Video: track which sample has already had its first-frame bootstrap request.
159+
const bootstrappedVideoSampleIdRef = useRef<string | null>(null);
160+
161+
if (prevSampleId !== sampleId) {
162+
setPrevSampleId(sampleId);
163+
setDataParams({ name: "identity" });
164+
}
157165

158166
useEffect(() => {
159167
setVideoFrameBounds({ min: null, max: null });
168+
lastGoodVideoFrameRef.current = null;
160169
}, [sampleId]);
161170

162171
function extractDetail(payload: unknown): string {
@@ -225,10 +234,12 @@ export function SampleProvider({
225234
let effectiveDataParams: DataParams = dataParams;
226235

227236
if (projectData.task === TaskType.Video) {
237+
const isFirstRequestForSample =
238+
bootstrappedVideoSampleIdRef.current !== sampleId;
228239
effectiveDataParams = {
229240
...dataParams,
230241
name: "image",
231-
frame: dataParams.frame ?? null,
242+
frame: isFirstRequestForSample ? null : (dataParams.frame ?? null),
232243
};
233244
}
234245

@@ -308,6 +319,7 @@ export function SampleProvider({
308319
if (projectData.task === TaskType.Video) {
309320
const frame = (viewData as { frame?: unknown }).frame;
310321
if (typeof frame === "number" && Number.isFinite(frame)) {
322+
bootstrappedVideoSampleIdRef.current = sampleId;
311323
lastGoodVideoFrameRef.current = frame;
312324
setVideoFrameBounds((prev) => ({
313325
...prev,

0 commit comments

Comments
 (0)