From fbda9b2a8351a2957717832d48a6728f2e99ae35 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 9 Aug 2023 17:44:04 +0300 Subject: [PATCH 01/11] Added progress indication --- cvat-ui/src/actions/annotation-actions.ts | 4 ++++ .../components/annotation-page/styles.scss | 22 ++++++++++++++++++- .../top-bar/player-navigation.tsx | 13 +++++++++++ .../annotation-page/top-bar/top-bar.tsx | 3 +++ .../annotation-page/top-bar/top-bar.tsx | 5 +++++ .../settings-modal/workspace-settings.tsx | 3 +++ cvat-ui/src/reducers/annotation-reducer.ts | 3 +++ cvat-ui/src/reducers/index.ts | 1 + 8 files changed, 53 insertions(+), 1 deletion(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 8b7a899b9ec3..e00e1a87156b 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -624,6 +624,7 @@ export function changeFrameAsync( relatedFiles: currentState.annotation.player.frame.relatedFiles, delay: currentState.annotation.player.frame.delay, changeTime: currentState.annotation.player.frame.changeTime, + ranges: currentState.annotation.player.ranges, states: currentState.annotation.annotations.states, minZ: currentState.annotation.annotations.zLayer.min, maxZ: currentState.annotation.annotations.zLayer.max, @@ -645,6 +646,8 @@ export function changeFrameAsync( } const data = await job.frames.get(toFrame, fillBuffer, frameStep); + const ranges = await job.frames.ranges(); + console.log(ranges); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); if (!isAbleToChangeFrame() || statisticsVisible || propagateVisible) { @@ -695,6 +698,7 @@ export function changeFrameAsync( curZ: maxZ, changeTime: currentTime + delay, delay, + ranges, }, }); } catch (error) { diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 7d75f57d8f7e..8e39fd5a64bf 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -126,15 +126,35 @@ } } -.cvat-player-slider { +.cvat-player-slider.ant-slider { width: 350px; margin: 0; + margin-top: $grid-unit-size * -0.5; + > .ant-slider-handle { + z-index: 100; + margin-top: -3.5px; + } + > .ant-slider-track { + background: none; + } > .ant-slider-rail { + height: $grid-unit-size; background-color: $player-slider-color; } } +.cvat-player-slider-progress { + width: 350px; + position: absolute; + top: 0; + pointer-events: none; + + > rect { + fill: #ff4136; + } +} + .cvat-player-filename-wrapper { max-width: $grid-unit-size * 30; max-height: $grid-unit-size * 3; diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index e95d15cf33d0..f39f4df21629 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -21,6 +21,7 @@ interface Props { startFrame: number; stopFrame: number; playing: boolean; + ranges: string; frameNumber: number; frameFilename: string; frameDeleted: boolean; @@ -47,6 +48,7 @@ function PlayerNavigation(props: Props): JSX.Element { deleteFrameShortcut, focusFrameInputShortcut, inputFrameRef, + ranges, onSliderChange, onInputChange, onURLIconClick, @@ -105,6 +107,17 @@ function PlayerNavigation(props: Props): JSX.Element { value={frameNumber || 0} onChange={onSliderChange} /> + + {ranges.split(';').map((range) => { + let [start, end] = range.split(':').map((num) => +num); + start = Math.max(0, start - 1); + const totalSegments = stopFrame - startFrame; + const segmentWidth = 1000 / totalSegments; + const width = Math.max((end - start), 1) * segmentWidth; + const offset = (Math.max((start - startFrame), 0) / totalSegments) * 1000; + return (); + })} + diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index ee800ce68619..7c88063bf217 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -69,6 +69,7 @@ interface Props { onRestoreFrame(): void; switchNavigationBlocked(blocked: boolean): void; jobInstance: any; + ranges: string; } export default function AnnotationTopBarComponent(props: Props): JSX.Element { @@ -77,6 +78,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { undoAction, redoAction, playing, + ranges, frameNumber, frameFilename, frameDeleted, @@ -168,6 +170,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { startFrame={startFrame} stopFrame={stopFrame} playing={playing} + ranges={ranges} frameNumber={frameNumber} frameFilename={frameFilename} frameDeleted={frameDeleted} diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index f53c5f61cdbe..e2ebd27e1fb2 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -65,6 +65,7 @@ interface StateToProps { normalizedKeyMap: Record; canvasInstance: Canvas | Canvas3d; forceExit: boolean; + ranges: string; activeControl: ActiveControl; } @@ -91,6 +92,7 @@ function mapStateToProps(state: CombinedState): StateToProps { annotation: { player: { playing, + ranges, frame: { data: { deleted: frameIsDeleted }, filename: frameFilename, @@ -142,6 +144,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, forceExit, activeControl, + ranges: ranges.join(';'), }; } @@ -638,6 +641,7 @@ class AnnotationTopBarContainer extends React.PureComponent { workspace, canvasIsReady, keyMap, + ranges, normalizedKeyMap, activeControl, searchAnnotations, @@ -766,6 +770,7 @@ class AnnotationTopBarContainer extends React.PureComponent { workspace={workspace} playing={playing} saving={saving} + ranges={ranges} startFrame={startFrame} stopFrame={stopFrame} frameNumber={frameNumber} diff --git a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx index d5be42a14950..1cdcf91614db 100644 --- a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx @@ -7,6 +7,7 @@ import { connect } from 'react-redux'; import { switchAutoSave, + switchImagesCache, changeAutoSaveInterval, changeAAMZoomMargin, switchShowingInterpolatedTracks, @@ -43,6 +44,7 @@ interface StateToProps { interface DispatchToProps { onSwitchAutoSave(enabled: boolean): void; + onSwitchImagesCache(enabled: boolean): void; onChangeAutoSaveInterval(interval: number): void; onChangeAAMZoomMargin(margin: number): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void; @@ -94,6 +96,7 @@ function mapStateToProps(state: CombinedState): StateToProps { const mapDispatchToProps: DispatchToProps = { onSwitchAutoSave: switchAutoSave, + onSwitchImagesCache: switchImagesCache, onChangeAutoSaveInterval: changeAutoSaveInterval, onChangeAAMZoomMargin: changeAAMZoomMargin, onSwitchShowingInterpolatedTracks: switchShowingInterpolatedTracks, diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 71524879f8bd..15fc05c617ff 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -72,6 +72,7 @@ const defaultState: AnnotationState = { delay: 0, changeTime: null, }, + ranges: [], playing: false, frameAngles: [], navigationBlocked: false, @@ -275,12 +276,14 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { maxZ, curZ, delay, + ranges, changeTime, } = action.payload; return { ...state, player: { ...state.player, + ranges, frame: { data, filename, diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index f29b7dff8f94..1af17eb8e280 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -695,6 +695,7 @@ export interface AnnotationState { delay: number; changeTime: number | null; }; + ranges: string[]; navigationBlocked: boolean; playing: boolean; frameAngles: number[]; From 130749aab05c072d7e9e381a00a1698b740b58df Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 20:26:19 +0300 Subject: [PATCH 02/11] Fixed some issues --- cvat-data/src/ts/cvat-data.ts | 10 +++++++++- cvat-ui/src/actions/annotation-actions.ts | 2 ++ .../header/settings-modal/workspace-settings.tsx | 3 --- cvat-ui/src/reducers/annotation-reducer.ts | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 00768a043512..708adbed83d3 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -332,8 +332,16 @@ export class FrameDecoder { } get cachedFrames(): string[] { - const chunks = Object.keys(this.decodedChunks).map((chunkNumber: string) => +chunkNumber).sort((a, b) => a - b); + const chunkIsBeingDecoded = this.chunkIsBeingDecoded ? + Math.floor(this.chunkIsBeingDecoded.start / this.chunkSize) : null; + const chunks = Object.keys(this.decodedChunks) + .map((chunkNumber: string) => +chunkNumber) + .concat(...(chunkIsBeingDecoded !== null ? [chunkIsBeingDecoded] : [])) + .sort((a, b) => a - b); return chunks.map((chunk) => { + if (chunk === chunkIsBeingDecoded) { + return `${this.chunkIsBeingDecoded.start}:${this.chunkIsBeingDecoded.end}`; + } const frames = Object.keys(this.decodedChunks[chunk]).map((frame) => +frame); const min = Math.min(...frames); const max = Math.max(...frames); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index e00e1a87156b..13a2b2e49852 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -966,6 +966,7 @@ export function getJobAsync( }); } + const ranges = await job.frames.ranges(); const states = await job.annotations.get( frameNumber, showAllInterpolationTracks, filters, groundTruthJobId, ); @@ -991,6 +992,7 @@ export function getJobAsync( frameFilename: frameData.filename, relatedFiles: frameData.relatedFiles, frameData, + ranges, colors, filters, minZ, diff --git a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx index 1cdcf91614db..d5be42a14950 100644 --- a/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/workspace-settings.tsx @@ -7,7 +7,6 @@ import { connect } from 'react-redux'; import { switchAutoSave, - switchImagesCache, changeAutoSaveInterval, changeAAMZoomMargin, switchShowingInterpolatedTracks, @@ -44,7 +43,6 @@ interface StateToProps { interface DispatchToProps { onSwitchAutoSave(enabled: boolean): void; - onSwitchImagesCache(enabled: boolean): void; onChangeAutoSaveInterval(interval: number): void; onChangeAAMZoomMargin(margin: number): void; onSwitchShowingInterpolatedTracks(enabled: boolean): void; @@ -96,7 +94,6 @@ function mapStateToProps(state: CombinedState): StateToProps { const mapDispatchToProps: DispatchToProps = { onSwitchAutoSave: switchAutoSave, - onSwitchImagesCache: switchImagesCache, onChangeAutoSaveInterval: changeAutoSaveInterval, onChangeAAMZoomMargin: changeAAMZoomMargin, onSwitchShowingInterpolatedTracks: switchShowingInterpolatedTracks, diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 15fc05c617ff..a5219f559d8b 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -146,6 +146,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { states, openTime, frameNumber: number, + ranges, frameFilename: filename, relatedFiles, colors, @@ -209,6 +210,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, }, + ranges, frameAngles: Array(job.stopFrame - job.startFrame + 1).fill(0), }, drawing: { From 490660570d01f5f1dcc1f611a6db2aa23f6fc86b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 20:39:22 +0300 Subject: [PATCH 03/11] Fixed linter issues --- cvat-ui/src/components/annotation-page/styles.scss | 8 +++++--- .../annotation-page/top-bar/player-navigation.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 8e39fd5a64bf..54dac21de1a0 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT -@import '../../base.scss'; +@import '../../base'; .cvat-annotation-page.ant-layout { height: 100%; @@ -135,9 +135,11 @@ z-index: 100; margin-top: -3.5px; } + > .ant-slider-track { background: none; } + > .ant-slider-rail { height: $grid-unit-size; background-color: $player-slider-color; @@ -241,7 +243,7 @@ .ant-table-thead { > tr > th { - padding: 5px 5px; + padding: $grid-unit-size 0 $grid-unit-size $grid-unit-size * 0.5; } } } @@ -466,7 +468,7 @@ } .group { - background: rgba(216, 233, 250, 0.5); + background: rgba(216, 233, 250, 50%); border: 1px solid #d3e0ec; } } diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index f39f4df21629..7f0d695468b6 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -109,12 +109,12 @@ function PlayerNavigation(props: Props): JSX.Element { /> {ranges.split(';').map((range) => { - let [start, end] = range.split(':').map((num) => +num); - start = Math.max(0, start - 1); + const [start, end] = range.split(':').map((num) => +num); + const adjustedStart = Math.max(0, start - 1); const totalSegments = stopFrame - startFrame; const segmentWidth = 1000 / totalSegments; - const width = Math.max((end - start), 1) * segmentWidth; - const offset = (Math.max((start - startFrame), 0) / totalSegments) * 1000; + const width = Math.max((end - adjustedStart), 1) * segmentWidth; + const offset = (Math.max((adjustedStart - startFrame), 0) / totalSegments) * 1000; return (); })} From 7c1116c991c7c6d98995686138f70b37e038e218 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 21:08:39 +0300 Subject: [PATCH 04/11] Fixed couple more issues --- cvat-ui/src/actions/annotation-actions.ts | 24 ++++++++++++------- .../canvas/views/canvas2d/canvas-wrapper.tsx | 4 ++-- .../views/canvas3d/canvas-wrapper3D.tsx | 4 ++-- .../top-bar/player-navigation.tsx | 24 ++++++++++--------- .../annotation-page/top-bar/top-bar.tsx | 2 +- cvat-ui/src/reducers/annotation-reducer.ts | 10 ++++---- 6 files changed, 40 insertions(+), 28 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 13a2b2e49852..a6ba50a898a7 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -581,10 +581,24 @@ export function switchPlay(playing: boolean): AnyAction { }; } -export function confirmCanvasReady(): AnyAction { +export function confirmCanvasReady(ranges?: string[]): AnyAction { return { type: AnnotationActionTypes.CONFIRM_CANVAS_READY, - payload: {}, + payload: { ranges }, + }; +} + +export function confirmCanvasReadyAsync(): ThunkAction { + return async (dispatch: ActionCreator, getState: () => CombinedState): Promise => { + try { + const state: CombinedState = getState(); + const { instance: job } = state.annotation.job; + const ranges = await job.frames.ranges(); + dispatch(confirmCanvasReady(ranges)); + } catch (error) { + // even if error happens here, do not need to notify the users + dispatch(confirmCanvasReady()); + } }; } @@ -624,7 +638,6 @@ export function changeFrameAsync( relatedFiles: currentState.annotation.player.frame.relatedFiles, delay: currentState.annotation.player.frame.delay, changeTime: currentState.annotation.player.frame.changeTime, - ranges: currentState.annotation.player.ranges, states: currentState.annotation.annotations.states, minZ: currentState.annotation.annotations.zLayer.min, maxZ: currentState.annotation.annotations.zLayer.max, @@ -646,8 +659,6 @@ export function changeFrameAsync( } const data = await job.frames.get(toFrame, fillBuffer, frameStep); - const ranges = await job.frames.ranges(); - console.log(ranges); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); if (!isAbleToChangeFrame() || statisticsVisible || propagateVisible) { @@ -698,7 +709,6 @@ export function changeFrameAsync( curZ: maxZ, changeTime: currentTime + delay, delay, - ranges, }, }); } catch (error) { @@ -966,7 +976,6 @@ export function getJobAsync( }); } - const ranges = await job.frames.ranges(); const states = await job.annotations.get( frameNumber, showAllInterpolationTracks, filters, groundTruthJobId, ); @@ -992,7 +1001,6 @@ export function getJobAsync( frameFilename: frameData.filename, relatedFiles: frameData.relatedFiles, frameData, - ranges, colors, filters, minZ, diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx index 4493ba014f04..df2d3d63381d 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx @@ -24,7 +24,7 @@ import config from 'config'; import CVATTooltip from 'components/common/cvat-tooltip'; import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags'; import { - confirmCanvasReady, + confirmCanvasReadyAsync, dragCanvas, zoomCanvas, resetCanvas, @@ -259,7 +259,7 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { onSetupCanvas(): void { - dispatch(confirmCanvasReady()); + dispatch(confirmCanvasReadyAsync()); }, onDragCanvas(enabled: boolean): void { dispatch(dragCanvas(enabled)); diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx index e21a4a138d99..479ad283ede5 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx @@ -16,7 +16,7 @@ import Spin from 'antd/lib/spin'; import { activateObject, - confirmCanvasReady, + confirmCanvasReadyAsync, createAnnotationsAsync, dragCanvas, editShape, @@ -131,7 +131,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(dragCanvas(enabled)); }, onSetupCanvas(): void { - dispatch(confirmCanvasReady()); + dispatch(confirmCanvasReadyAsync()); }, onResetCanvas(): void { dispatch(resetCanvas()); diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index 7f0d695468b6..35ce6a0f1761 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -107,17 +107,19 @@ function PlayerNavigation(props: Props): JSX.Element { value={frameNumber || 0} onChange={onSliderChange} /> - - {ranges.split(';').map((range) => { - const [start, end] = range.split(':').map((num) => +num); - const adjustedStart = Math.max(0, start - 1); - const totalSegments = stopFrame - startFrame; - const segmentWidth = 1000 / totalSegments; - const width = Math.max((end - adjustedStart), 1) * segmentWidth; - const offset = (Math.max((adjustedStart - startFrame), 0) / totalSegments) * 1000; - return (); - })} - + {!!ranges && ( + + {ranges.split(';').map((range) => { + const [start, end] = range.split(':').map((num) => +num); + const adjustedStart = Math.max(0, start - 1); + const totalSegments = stopFrame - startFrame; + const segmentWidth = 1000 / totalSegments; + const width = Math.max((end - adjustedStart), 1) * segmentWidth; + const offset = (Math.max((adjustedStart - startFrame), 0) / totalSegments) * 1000; + return (); + })} + + )} diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index e2ebd27e1fb2..3500e4e08ef1 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -144,7 +144,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, forceExit, activeControl, - ranges: ranges.join(';'), + ranges: ranges.length ? ranges.join(';') : '', }; } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index a5219f559d8b..3b8c5551bf7e 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -55,6 +55,7 @@ const defaultState: AnnotationState = { job: { openTime: null, labels: [], + groundTruthJobFramesMeta: null, requestedId: null, groundTruthJobId: null, instance: null, @@ -146,7 +147,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { states, openTime, frameNumber: number, - ranges, frameFilename: filename, relatedFiles, colors, @@ -210,7 +210,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, }, - ranges, frameAngles: Array(job.stopFrame - job.startFrame + 1).fill(0), }, drawing: { @@ -278,14 +277,12 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { maxZ, curZ, delay, - ranges, changeTime, } = action.payload; return { ...state, player: { ...state.player, - ranges, frame: { data, filename, @@ -422,8 +419,13 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.CONFIRM_CANVAS_READY: { + const { ranges } = action.payload; return { ...state, + player: { + ...state.player, + ranges: ranges || state.player.ranges, + }, canvas: { ...state.canvas, ready: true, From b403e0234171d8b40db1397de070bbbdbb8f56cc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 21:23:54 +0300 Subject: [PATCH 05/11] Improved progress bar --- cvat-data/src/ts/cvat-data.ts | 15 ++++++++++++--- .../src/components/annotation-page/styles.scss | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 708adbed83d3..10002f91fd28 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -340,12 +340,21 @@ export class FrameDecoder { .sort((a, b) => a - b); return chunks.map((chunk) => { if (chunk === chunkIsBeingDecoded) { - return `${this.chunkIsBeingDecoded.start}:${this.chunkIsBeingDecoded.end}`; + return [this.chunkIsBeingDecoded.start, this.chunkIsBeingDecoded.end]; } const frames = Object.keys(this.decodedChunks[chunk]).map((frame) => +frame); const min = Math.min(...frames); const max = Math.max(...frames); - return `${min}:${max}`; - }); + return [min, max]; + }).reduce>((acc, val) => { + if (acc.length && acc[acc.length - 1][1] + 1 === val[0]) { + const newMax = val[1]; + acc[acc.length - 1][1] = newMax; + } else { + acc.push(val as [number, number]); + } + + return acc; + }, []).map((val) => `${val[0]}:${val[1]}`); } } diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 54dac21de1a0..2236995f870f 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -153,6 +153,7 @@ pointer-events: none; > rect { + transition: width 0.5s; fill: #ff4136; } } From f99cd146bce0222a58dae421074862238c4de4e9 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 21:27:19 +0300 Subject: [PATCH 06/11] rounded progress bar --- .../components/annotation-page/top-bar/player-navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index 35ce6a0f1761..8abe2a315e03 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -116,7 +116,7 @@ function PlayerNavigation(props: Props): JSX.Element { const segmentWidth = 1000 / totalSegments; const width = Math.max((end - adjustedStart), 1) * segmentWidth; const offset = (Math.max((adjustedStart - startFrame), 0) / totalSegments) * 1000; - return (); + return (); })} )} From a27c5b04eaf9359fcb6852e900cd87bc3cfd7987 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 21:32:32 +0300 Subject: [PATCH 07/11] Adjusted style --- cvat-ui/src/components/annotation-page/styles.scss | 3 ++- .../components/annotation-page/top-bar/player-navigation.tsx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 2236995f870f..9d31599fe3d5 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -148,13 +148,14 @@ .cvat-player-slider-progress { width: 350px; + height: $grid-unit-size; position: absolute; top: 0; pointer-events: none; > rect { transition: width 0.5s; - fill: #ff4136; + fill: #1890ff; } } diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx index 8abe2a315e03..931693690be1 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-navigation.tsx @@ -108,7 +108,7 @@ function PlayerNavigation(props: Props): JSX.Element { onChange={onSliderChange} /> {!!ranges && ( - + {ranges.split(';').map((range) => { const [start, end] = range.split(':').map((num) => +num); const adjustedStart = Math.max(0, start - 1); @@ -116,7 +116,7 @@ function PlayerNavigation(props: Props): JSX.Element { const segmentWidth = 1000 / totalSegments; const width = Math.max((end - adjustedStart), 1) * segmentWidth; const offset = (Math.max((adjustedStart - startFrame), 0) / totalSegments) * 1000; - return (); + return (); })} )} From c0bc1bf40f9d16518c3ac16d6bd9efad4a48f295 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 10 Aug 2023 22:20:03 +0300 Subject: [PATCH 08/11] Added missed type --- cvat-core/src/session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts index 4f34ed458ccc..3d81e12ce9a0 100644 --- a/cvat-core/src/session.ts +++ b/cvat-core/src/session.ts @@ -329,6 +329,7 @@ export class Job extends Session { public readonly taskId: number; public readonly dimension: DimensionType; public readonly dataChunkType: ChunkType; + public readonly dataChunkSize: number; public readonly bugTracker: string | null; public readonly mode: TaskMode; public readonly labels: Label[]; From d4e49e78f80915aa3096973933eadf7df8d3b0b0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 11 Aug 2023 00:24:22 +0300 Subject: [PATCH 09/11] updated api --- cvat-core/src/frames.ts | 6 ++-- cvat-core/src/session-implementation.ts | 23 ++++++------- cvat-core/src/session.ts | 12 +++---- cvat-data/src/ts/cvat-data.ts | 39 +++++++---------------- cvat-ui/src/actions/annotation-actions.ts | 19 ++++++++++- 5 files changed, 49 insertions(+), 50 deletions(-) diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 158e35821141..8a77c8993d5b 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -470,6 +470,8 @@ export async function getFrame( chunkSize, decodedBlocksCacheSize, dimension, + startFrame, + stopFrame, ), decodedBlocksCacheSize, activeChunkRequest: null, @@ -582,12 +584,12 @@ export async function findFrame( return lastUndeletedFrame; } -export function getRanges(jobID): Array { +export function getCachedChunks(jobID): number[] { if (!(jobID in frameDataCache)) { return []; } - return frameDataCache[jobID].provider.cachedFrames; + return frameDataCache[jobID].provider.cachedChunks(true); } export function clear(jobID: number): void { diff --git a/cvat-core/src/session-implementation.ts b/cvat-core/src/session-implementation.ts index ea57c1881f1c..5a873ae823c3 100644 --- a/cvat-core/src/session-implementation.ts +++ b/cvat-core/src/session-implementation.ts @@ -13,7 +13,7 @@ import { getFrame, deleteFrame, restoreFrame, - getRanges, + getCachedChunks, clear as clearFrames, findFrame, getContextImage, @@ -163,9 +163,9 @@ export function implementJob(Job) { return result; }; - Job.prototype.frames.ranges.implementation = async function () { - const rangesData = await getRanges(this.id); - return rangesData; + Job.prototype.frames.cachedChunks.implementation = async function () { + const cachedChunks = await getCachedChunks(this.id); + return cachedChunks; }; Job.prototype.frames.preview.implementation = async function (this: JobClass): Promise { @@ -570,21 +570,18 @@ export function implementTask(Task) { isPlaying, step, this.dimension, + (chunkNumber, quality) => job.frames.chunk(chunkNumber, quality), ); return result; }; - Task.prototype.frames.ranges.implementation = async function () { - const rangesData = { - decoded: [], - buffered: [], - }; + Task.prototype.frames.cachedChunks.implementation = async function () { + let chunks = []; for (const job of this.jobs) { - const { decoded, buffered } = await getRanges(job.id); - rangesData.decoded.push(decoded); - rangesData.buffered.push(buffered); + const cachedChunks = await getCachedChunks(job.id); + chunks = chunks.concat(cachedChunks); } - return rangesData; + return Array.from(new Set(chunks)); }; Task.prototype.frames.preview.implementation = async function (this: TaskClass): Promise { diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts index 3d81e12ce9a0..cad8b773ae21 100644 --- a/cvat-core/src/session.ts +++ b/cvat-core/src/session.ts @@ -210,8 +210,8 @@ function buildDuplicatedAPI(prototype) { prototype.frames.save, ); }, - async ranges() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.ranges); + async cachedChunks() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.cachedChunks); return result; }, async preview() { @@ -370,7 +370,7 @@ export class Job extends Session { delete: CallableFunction; restore: CallableFunction; save: CallableFunction; - ranges: CallableFunction; + cachedChunks: CallableFunction; preview: CallableFunction; contextImage: CallableFunction; search: CallableFunction; @@ -574,7 +574,7 @@ export class Job extends Session { delete: Object.getPrototypeOf(this).frames.delete.bind(this), restore: Object.getPrototypeOf(this).frames.restore.bind(this), save: Object.getPrototypeOf(this).frames.save.bind(this), - ranges: Object.getPrototypeOf(this).frames.ranges.bind(this), + cachedChunks: Object.getPrototypeOf(this).frames.cachedChunks.bind(this), preview: Object.getPrototypeOf(this).frames.preview.bind(this), search: Object.getPrototypeOf(this).frames.search.bind(this), contextImage: Object.getPrototypeOf(this).frames.contextImage.bind(this), @@ -685,7 +685,7 @@ export class Task extends Session { delete: CallableFunction; restore: CallableFunction; save: CallableFunction; - ranges: CallableFunction; + cachedChunks: CallableFunction; preview: CallableFunction; contextImage: CallableFunction; search: CallableFunction; @@ -1102,7 +1102,7 @@ export class Task extends Session { delete: Object.getPrototypeOf(this).frames.delete.bind(this), restore: Object.getPrototypeOf(this).frames.restore.bind(this), save: Object.getPrototypeOf(this).frames.save.bind(this), - ranges: Object.getPrototypeOf(this).frames.ranges.bind(this), + cachedChunks: Object.getPrototypeOf(this).frames.cachedChunks.bind(this), preview: Object.getPrototypeOf(this).frames.preview.bind(this), contextImage: Object.getPrototypeOf(this).frames.contextImage.bind(this), search: Object.getPrototypeOf(this).frames.search.bind(this), diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 10002f91fd28..1f454659c702 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -82,6 +82,8 @@ interface BlockToDecode { export class FrameDecoder { private blockType: BlockType; private chunkSize: number; + private startFrame: number; + private stopFrame: number; /* ImageBitmap when decode zip or video chunks Blob when 3D dimension @@ -104,6 +106,8 @@ export class FrameDecoder { chunkSize: number, cachedBlockCount: number, dimension: DimensionType = DimensionType.DIMENSION_2D, + startFrame: number, + stopFrame: number, ) { this.mutex = new Mutex(); this.orderedStack = []; @@ -115,6 +119,8 @@ export class FrameDecoder { this.renderHeight = 1080; this.chunkSize = chunkSize; this.blockType = blockType; + this.startFrame = startFrame; + this.stopFrame = stopFrame; this.decodedChunks = {}; this.requestedChunkToDecode = null; @@ -327,34 +333,11 @@ export class FrameDecoder { } } - get cachedChunks(): number[] { - return Object.keys(this.decodedChunks).map((chunkNumber: string) => +chunkNumber).sort((a, b) => a - b); - } - - get cachedFrames(): string[] { - const chunkIsBeingDecoded = this.chunkIsBeingDecoded ? + public cachedChunks(includeInProgress = false): number[] { + const chunkIsBeingDecoded = includeInProgress && this.chunkIsBeingDecoded ? Math.floor(this.chunkIsBeingDecoded.start / this.chunkSize) : null; - const chunks = Object.keys(this.decodedChunks) - .map((chunkNumber: string) => +chunkNumber) - .concat(...(chunkIsBeingDecoded !== null ? [chunkIsBeingDecoded] : [])) - .sort((a, b) => a - b); - return chunks.map((chunk) => { - if (chunk === chunkIsBeingDecoded) { - return [this.chunkIsBeingDecoded.start, this.chunkIsBeingDecoded.end]; - } - const frames = Object.keys(this.decodedChunks[chunk]).map((frame) => +frame); - const min = Math.min(...frames); - const max = Math.max(...frames); - return [min, max]; - }).reduce>((acc, val) => { - if (acc.length && acc[acc.length - 1][1] + 1 === val[0]) { - const newMax = val[1]; - acc[acc.length - 1][1] = newMax; - } else { - acc.push(val as [number, number]); - } - - return acc; - }, []).map((val) => `${val[0]}:${val[1]}`); + return Object.keys(this.decodedChunks).map((chunkNumber: string) => +chunkNumber).concat( + ...(chunkIsBeingDecoded !== null ? [chunkIsBeingDecoded] : []), + ).sort((a, b) => a - b); } } diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index a6ba50a898a7..2b1217bb67e6 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -593,7 +593,24 @@ export function confirmCanvasReadyAsync(): ThunkAction { try { const state: CombinedState = getState(); const { instance: job } = state.annotation.job; - const ranges = await job.frames.ranges(); + const chunks = await job.frames.cachedChunks() as number[]; + const { startFrame, stopFrame, dataChunkSize } = job; + + const ranges = chunks.map((chunk) => ( + [ + Math.max(startFrame, chunk * dataChunkSize), + Math.min(stopFrame, (chunk + 1) * dataChunkSize - 1), + ] + )).reduce>((acc, val) => { + if (acc.length && acc[acc.length - 1][1] + 1 === val[0]) { + const newMax = val[1]; + acc[acc.length - 1][1] = newMax; + } else { + acc.push(val as [number, number]); + } + return acc; + }, []).map(([start, end]) => `${start}:${end}`); + dispatch(confirmCanvasReady(ranges)); } catch (error) { // even if error happens here, do not need to notify the users From ab6626a3815124d68e748b3291f13b4b96cba377 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 11 Aug 2023 00:27:03 +0300 Subject: [PATCH 10/11] Removed extra arguments --- cvat-core/src/frames.ts | 2 -- cvat-data/src/ts/cvat-data.ts | 6 ------ 2 files changed, 8 deletions(-) diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 8a77c8993d5b..2f5b328f81bf 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -470,8 +470,6 @@ export async function getFrame( chunkSize, decodedBlocksCacheSize, dimension, - startFrame, - stopFrame, ), decodedBlocksCacheSize, activeChunkRequest: null, diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 1f454659c702..8d50fe64083d 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -82,8 +82,6 @@ interface BlockToDecode { export class FrameDecoder { private blockType: BlockType; private chunkSize: number; - private startFrame: number; - private stopFrame: number; /* ImageBitmap when decode zip or video chunks Blob when 3D dimension @@ -106,8 +104,6 @@ export class FrameDecoder { chunkSize: number, cachedBlockCount: number, dimension: DimensionType = DimensionType.DIMENSION_2D, - startFrame: number, - stopFrame: number, ) { this.mutex = new Mutex(); this.orderedStack = []; @@ -119,8 +115,6 @@ export class FrameDecoder { this.renderHeight = 1080; this.chunkSize = chunkSize; this.blockType = blockType; - this.startFrame = startFrame; - this.stopFrame = stopFrame; this.decodedChunks = {}; this.requestedChunkToDecode = null; From 79431a1a4367e924aa8716767785b4550d916550 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 11 Aug 2023 11:11:11 +0300 Subject: [PATCH 11/11] Updated version and changelog --- CHANGELOG.md | 1 + cvat-core/package.json | 2 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/annotation-actions.ts | 4 ++-- cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx | 2 +- cvat-ui/src/reducers/annotation-reducer.ts | 2 +- cvat-ui/src/reducers/index.ts | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f34bfcef5fa8..162f24495b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 user-provided function on the local machine, and a corresponding CLI command (`auto-annotate`) () +- Cached frames indication on the interface () ### Changed diff --git a/cvat-core/package.json b/cvat-core/package.json index 5556d1877650..d9712f79ea66 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "10.0.1", + "version": "11.0.0", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "src/api.ts", "scripts": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 46e6739ff9c5..90ab58642008 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.54.2", + "version": "1.55.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 2b1217bb67e6..24c3cc63c175 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -581,7 +581,7 @@ export function switchPlay(playing: boolean): AnyAction { }; } -export function confirmCanvasReady(ranges?: string[]): AnyAction { +export function confirmCanvasReady(ranges?: string): AnyAction { return { type: AnnotationActionTypes.CONFIRM_CANVAS_READY, payload: { ranges }, @@ -609,7 +609,7 @@ export function confirmCanvasReadyAsync(): ThunkAction { acc.push(val as [number, number]); } return acc; - }, []).map(([start, end]) => `${start}:${end}`); + }, []).map(([start, end]) => `${start}:${end}`).join(';'); dispatch(confirmCanvasReady(ranges)); } catch (error) { diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 3500e4e08ef1..067d85fe1ad0 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -144,7 +144,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, forceExit, activeControl, - ranges: ranges.length ? ranges.join(';') : '', + ranges, }; } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 3b8c5551bf7e..d7d395adb6f1 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -73,7 +73,7 @@ const defaultState: AnnotationState = { delay: 0, changeTime: null, }, - ranges: [], + ranges: '', playing: false, frameAngles: [], navigationBlocked: false, diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 1af17eb8e280..2207d73b807d 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -695,7 +695,7 @@ export interface AnnotationState { delay: number; changeTime: number | null; }; - ranges: string[]; + ranges: string; navigationBlocked: boolean; playing: boolean; frameAngles: number[];