From 618739a9f950f3b8454f1d75b84d29c7682b3ea5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 22 Dec 2022 23:22:13 -0800 Subject: [PATCH 01/47] Number of context images --- cvat-core/src/frames.ts | 10 +++++----- cvat-core/tests/mocks/dummy-data.mock.js | 8 ++++---- cvat-ui/src/actions/annotation-actions.ts | 6 +++--- cvat-ui/src/actions/boundaries-actions.ts | 4 ++-- .../standard-workspace/context-image/context-image.tsx | 8 ++++---- cvat-ui/src/reducers/annotation-reducer.ts | 10 +++++----- cvat-ui/src/reducers/index.ts | 2 +- cvat/apps/engine/serializers.py | 2 +- cvat/apps/engine/views.py | 4 ++-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 6e0d3961c7c9..d1e010b9a6dd 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -28,7 +28,7 @@ export class FrameData { stopFrame, decodeForward, deleted, - has_related_context: hasRelatedContext, + related_files: relatedFiles, }) { Object.defineProperties( this, @@ -89,15 +89,15 @@ export class FrameData { writable: false, }, /** - * True if some context images are associated with this frame - * @name hasRelatedContext + * Number of context images are associated with this frame + * @name relatedFiles * @type {boolean} * @memberof module:API.cvat.classes.FrameData * @readonly * @instance */ - hasRelatedContext: { - value: hasRelatedContext, + relatedFiles: { + value: relatedFiles, writable: false, }, /** diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js index ef8b15d66213..0bc7642d9533 100644 --- a/cvat-core/tests/mocks/dummy-data.mock.js +++ b/cvat-core/tests/mocks/dummy-data.mock.js @@ -2963,22 +2963,22 @@ const frameMetaDummyData = { width: 2560, height: 1703, name: '1598296101_1033667.jpg', - has_related_context: false + related_files: 0 }, { width: 1600, height: 1200, name: '30fdce7f27b9c7b1d50108d7c16d23ef.jpg', - has_related_context: false + related_files: 0 }, { width: 2880, height: 1800, name: '567362-ily-comedy-drama-1finding-3.jpg', - has_related_context: false + related_files: 0 }, { width: 1920, height: 1080, name: '730443-under-the-sea-wallpapers-1920x1080-windows-10.jpg', - has_related_context: false + related_files: 0 }], deleted_frames: [] }, diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 5f9ff078ff15..62b561a325e3 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -700,7 +700,7 @@ export function changeFrameAsync( number: currentState.annotation.player.frame.number, data: currentState.annotation.player.frame.data, filename: currentState.annotation.player.frame.filename, - hasRelatedContext: currentState.annotation.player.frame.hasRelatedContext, + relatedFiles: currentState.annotation.player.frame.relatedFiles, delay: currentState.annotation.player.frame.delay, changeTime: currentState.annotation.player.frame.changeTime, states: currentState.annotation.annotations.states, @@ -767,7 +767,7 @@ export function changeFrameAsync( number: toFrame, data, filename: data.filename, - hasRelatedContext: data.hasRelatedContext, + relatedFiles: data.relatedFiles, states, minZ, maxZ, @@ -1046,7 +1046,7 @@ export function getJobAsync( states, frameNumber, frameFilename: frameData.filename, - frameHasRelatedContext: frameData.hasRelatedContext, + relatedFiles: frameData.relatedFiles, frameData, colors, filters, diff --git a/cvat-ui/src/actions/boundaries-actions.ts b/cvat-ui/src/actions/boundaries-actions.ts index faa1cac4f8a3..fabea9fe9f8a 100644 --- a/cvat-ui/src/actions/boundaries-actions.ts +++ b/cvat-ui/src/actions/boundaries-actions.ts @@ -24,7 +24,7 @@ export const boundariesActions = { openTime: number; frameNumber: number; frameFilename: string; - frameHasRelatedContext: boolean; + relatedFiles: boolean; colors: string[]; filters: string[]; frameData: any; @@ -58,7 +58,7 @@ export function resetAfterErrorAsync(): ThunkAction { openTime: state.annotation.job.openTime || Date.now(), frameNumber, frameFilename: frameData.filename, - frameHasRelatedContext: frameData.hasRelatedContext, + relatedFiles: frameData.relatedFiles, colors, filters: [], frameData, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx index 229635a88fcc..8d43515ef41c 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx @@ -28,7 +28,7 @@ export function adjustContextImagePosition(sidebarCollapsed: boolean): void { function ContextImage(): JSX.Element | null { const dispatch = useDispatch(); - const { number: frame, hasRelatedContext } = useSelector((state: CombinedState) => state.annotation.player.frame); + const { number: frame, relatedFiles } = useSelector((state: CombinedState) => state.annotation.player.frame); const { data: contextImageData, hidden: contextImageHidden, fetching: contextImageFetching } = useSelector( (state: CombinedState) => state.annotation.player.contextImage, ); @@ -41,13 +41,13 @@ function ContextImage(): JSX.Element | null { }, [frame, contextImageData]); useEffect(() => { - if (hasRelatedContext && !contextImageHidden && !requested) { + if (relatedFiles && !contextImageHidden && !requested) { dispatch(getContextImageAsync()); setRequested(true); } - }, [contextImageHidden, requested, hasRelatedContext]); + }, [contextImageHidden, requested, relatedFiles]); - if (!hasRelatedContext) { + if (!relatedFiles) { return null; } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index e05625074f65..3efaa1f9c3c3 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -65,7 +65,7 @@ const defaultState: AnnotationState = { number: 0, filename: '', data: null, - hasRelatedContext: false, + relatedFiles: 0, fetching: false, delay: 0, changeTime: null, @@ -160,7 +160,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { openTime, frameNumber: number, frameFilename: filename, - frameHasRelatedContext, + relatedFiles, colors, filters, frameData: data, @@ -213,7 +213,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { frame: { ...state.player.frame, filename, - hasRelatedContext: frameHasRelatedContext, + relatedFiles, number, data, }, @@ -277,7 +277,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, filename, - hasRelatedContext, + relatedFiles, states, minZ, maxZ, @@ -293,7 +293,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { frame: { data, filename, - hasRelatedContext, + relatedFiles, number, fetching: false, changeTime, diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 5f688d4f5cae..6b52c0596804 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -678,7 +678,7 @@ export interface AnnotationState { frame: { number: number; filename: string; - hasRelatedContext: boolean; + relatedFiles: number; data: any | null; fetching: boolean; delay: number; diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index fd2448b5ad00..dfc5ca493613 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -866,7 +866,7 @@ class FrameMetaSerializer(serializers.Serializer): width = serializers.IntegerField() height = serializers.IntegerField() name = serializers.CharField(max_length=1024) - has_related_context = serializers.BooleanField() + related_files = serializers.IntegerField() class PluginsSerializer(serializers.Serializer): GIT_INTEGRATION = serializers.BooleanField() diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 0c607e7f3d1a..85e19ba2284d 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -1288,7 +1288,7 @@ def metadata(self, request, pk): 'width': item.width, 'height': item.height, 'name': item.path, - 'has_related_context': hasattr(item, 'related_files') and item.related_files.exists() + 'related_files': item.related_files.count() if hasattr(item, 'related_files') else 0 } for item in media] db_data = db_task.data @@ -1756,7 +1756,7 @@ def metadata(self, request, pk): 'width': item.width, 'height': item.height, 'name': item.path, - 'has_related_context': hasattr(item, 'related_files') and item.related_files.exists() + 'related_files': item.related_files.count() if hasattr(item, 'related_files') else 0 } for item in media] db_data.frames = frame_meta From 44ae26294539b1e61f1d3c2bb39d3918fedc2b05 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sun, 1 Jan 2023 13:17:23 -0800 Subject: [PATCH 02/47] Added grid view --- cvat-core/src/frames.ts | 54 +- cvat-core/src/server-proxy.ts | 4 +- cvat-data/src/ts/cvat-data.ts | 73 ++- cvat-data/src/ts/unzip_imgs.worker.js | 2 +- cvat-ui/package.json | 4 +- cvat-ui/src/actions/annotation-actions.ts | 52 +- .../annotation-page/annotation-page.tsx | 32 +- .../attribute-annotation-sidebar.tsx | 2 - .../attribute-annotation-workspace.tsx | 4 +- .../annotation-page/canvas/canvas-layout.tsx | 253 ++++++++ .../annotation-page/canvas/canvas-wrapper.tsx | 26 +- .../canvas/canvas-wrapper3D.tsx | 565 ++++++++---------- .../canvas/context-image-selector.tsx | 72 +++ .../canvas/context-image-styles.scss | 84 +++ .../annotation-page/canvas/context-image.tsx | 111 ++++ .../canvas/grid-layout-styles.scss | 33 + .../review-workspace/review-workspace.tsx | 4 +- .../context-image/context-image.tsx | 89 --- .../objects-side-bar/objects-side-bar.tsx | 2 - .../standard-workspace/standard-workspace.tsx | 4 +- .../standard-workspace/styles.scss | 49 +- .../controls-side-bar/photo-context.tsx | 38 -- .../standard3D-workspace.tsx | 5 +- .../standard3D-workspace/styles.scss | 58 +- .../components/annotation-page/styles.scss | 8 + .../tag-annotation-sidebar.tsx | 3 - .../tag-annotation-workspace.tsx | 4 +- .../annotation-page/top-bar/left-group.tsx | 15 +- .../annotation-page/top-bar/top-bar.tsx | 3 - .../annotation-page/canvas/canvas-wrapper.tsx | 3 - .../canvas/canvas-wrapper3D.tsx | 4 +- .../annotation-page/top-bar/top-bar.tsx | 6 +- cvat-ui/src/reducers/annotation-reducer.ts | 77 --- cvat-ui/src/reducers/index.ts | 7 - cvat-ui/src/reducers/notifications-reducer.ts | 16 - cvat/apps/engine/views.py | 35 +- yarn.lock | 55 +- 37 files changed, 1010 insertions(+), 846 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx create mode 100644 cvat-ui/src/components/annotation-page/canvas/context-image-selector.tsx create mode 100644 cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss create mode 100644 cvat-ui/src/components/annotation-page/canvas/context-image.tsx create mode 100644 cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss delete mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx delete mode 100644 cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index d1e010b9a6dd..1fd71bc6f473 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -8,6 +8,7 @@ import { isBrowser, isNode } from 'browser-or-node'; import PluginRegistry from './plugins'; import serverProxy from './server-proxy'; import { Exception, ArgumentError, DataError } from './exceptions'; +import { rejects } from 'assert'; // This is the frames storage const frameDataCache = {}; @@ -384,7 +385,9 @@ FrameData.prototype.data.implementation = async function (onServerRequest) { }); }; -function getFrameMeta(jobID, frame) { +function getFrameMeta(jobID, frame): any { + // todo: fix this function because now meta has not only size + // but also number of context images for example const { meta, mode, startFrame } = frameDataCache[jobID]; let size = null; if (mode === 'interpolation') { @@ -413,16 +416,43 @@ class FrameBuffer { this._jobID = jobID; } - isContextImageAvailable(frame) { - return frame in this._contextImage; + addContextImage(frame, data): void { + const promise = new Promise((resolve, reject) => { + data.then((resolvedData) => { + const meta = getFrameMeta(this._jobID, frame); + return cvatData + .decodeZip(resolvedData, 0, meta.related_files, cvatData.DimensionType.DIM_2D); + }).then((decodedData) => { + this._contextImage[frame] = decodedData; + resolve(); + }).catch((error: Error) => { + reject(error); + }); + }); + + this._contextImage[frame] = promise; } - getContextImage(frame) { - return this._contextImage[frame] || null; + isContextImageAvailable(frame): boolean { + return frame in this._contextImage; } - addContextImage(frame, data) { - this._contextImage[frame] = data; + getContextImage(frame): Promise { + return new Promise((resolve) => { + if (frame in this._contextImage) { + if (this._contextImage[frame] instanceof Promise) { + this._contextImage[frame].then(() => { + resolve(this.getContextImage(frame)); + }); + } else { + resolve(Object.keys(this._contextImage[frame]) + .map((key: string): number => +key).sort() + .map((key: number) => this._contextImage[frame][key])); + } + } else { + resolve([]); + } + }); } getFreeBufferSize() { @@ -629,7 +659,7 @@ class FrameBuffer { } } -async function getImageContext(jobID, frame) { +async function getImageContext(jobID, frame, imageId) { return new Promise((resolve, reject) => { serverProxy.frames .getImageContext(jobID, frame) @@ -638,11 +668,7 @@ async function getImageContext(jobID, frame) { // eslint-disable-next-line no-undef resolve(global.Buffer.from(result, 'binary').toString('base64')); } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); + resolve(result); } }) .catch((error) => { @@ -656,7 +682,7 @@ export async function getContextImage(jobID, frame) { return frameDataCache[jobID].frameBuffer.getContextImage(frame); } const response = getImageContext(jobID, frame); - frameDataCache[jobID].frameBuffer.addContextImage(frame, response); + await frameDataCache[jobID].frameBuffer.addContextImage(frame, response); return frameDataCache[jobID].frameBuffer.getContextImage(frame); } diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts index b84898f9783a..3c797ff87992 100644 --- a/cvat-core/src/server-proxy.ts +++ b/cvat-core/src/server-proxy.ts @@ -1369,7 +1369,7 @@ async function getPreview(tid, jid) { return response.data; } -async function getImageContext(jid, frame) { +async function getImageContext(jid, frame, imageId) { const { backendAPI } = config; let response = null; @@ -1381,7 +1381,7 @@ async function getImageContext(jid, frame) { number: frame, }, proxy: config.proxy, - responseType: 'blob', + responseType: 'arraybuffer', }); } catch (errorData) { throw generateError(errorData); diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 1484fff07f4a..ceb6dbfd0b62 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -18,6 +18,67 @@ export const DimensionType = Object.freeze({ DIM_2D: '2d', }); +const createImageBitmap = async function (blob): Promise { + return new Promise((resolve) => { + const img = document.createElement('img'); + img.addEventListener('load', function loadListener() { + resolve(this); + }); + img.src = URL.createObjectURL(blob); + }); +}; + +export function decodeZip(block: any, start: number, end: number, dimension: any): Promise { + return new Promise((resolve, reject) => { + decodeZip.mutex.acquire().then((release) => { + const worker = new ZipDecoder(); + const result: Record = {}; + let decoded = 0; + + worker.onerror = (e: ErrorEvent) => { + release(); + worker.terminate(); + reject(new Error(`Archive can not be decoded. ${e.message}`)); + }; + + worker.onmessage = async (event) => { + const { + isRaw, error, fileName, + } = event.data; + if (error) { + worker.onerror(new ErrorEvent('error', { message: error.toString() })); + } + + let { data } = event.data; + + if (isRaw) { + // polyfill for safary + data = await createImageBitmap(data); + } + + result[fileName.split('.')[0]] = data; + decoded++; + + if (decoded === end) { + release(); + worker.terminate(); + resolve(result); + } + }; + + worker.postMessage({ + block, + start, + end, + dimension, + dimension2D: DimensionType.DIM_2D, + }); + }); + }); +} + +decodeZip.mutex = new Mutex(); + export class FrameProvider { constructor( blockType, @@ -309,18 +370,6 @@ export class FrameProvider { // there is a way to polyfill it with using document.createElement // but document.createElement doesn't work in worker // so, we get raw data and decode it here, no other way - - const createImageBitmap = async function (blob) { - return new Promise((resolve) => { - const img = document.createElement('img'); - img.addEventListener('load', function loadListener() { - resolve(this); - }); - img.src = URL.createObjectURL(blob); - }); - }; - - // eslint-disable-next-line event.data.data = await createImageBitmap(event.data.data); } diff --git a/cvat-data/src/ts/unzip_imgs.worker.js b/cvat-data/src/ts/unzip_imgs.worker.js index 355106b8b239..fd20ede6da77 100644 --- a/cvat-data/src/ts/unzip_imgs.worker.js +++ b/cvat-data/src/ts/unzip_imgs.worker.js @@ -39,6 +39,6 @@ onmessage = (e) => { }); } }); - }); + }).catch((error) => postMessage({ error })); } }; diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 054d4344a8f2..d6222d158422 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -26,8 +26,8 @@ "@types/react": "^16.14.15", "@types/react-color": "^3.0.5", "@types/react-dom": "^16.9.14", + "@types/react-grid-layout": "^1.3.2", "@types/react-redux": "^7.1.18", - "@types/react-resizable": "^3.0.1", "@types/react-router": "^5.1.16", "@types/react-router-dom": "^5.1.9", "@types/react-share": "^3.0.3", @@ -50,9 +50,9 @@ "react-color": "^2.19.3", "react-cookie": "^4.0.3", "react-dom": "^16.14.0", + "react-grid-layout": "^1.3.4", "react-moment": "^1.1.1", "react-redux": "^8.0.2", - "react-resizable": "^3.0.4", "react-router": "^5.1.0", "react-router-dom": "^5.1.0", "react-share": "^4.4.0", diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 62b561a325e3..5a199a8803b3 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -125,7 +125,6 @@ export enum AnnotationActionTypes { SAVE_ANNOTATIONS = 'SAVE_ANNOTATIONS', SAVE_ANNOTATIONS_SUCCESS = 'SAVE_ANNOTATIONS_SUCCESS', SAVE_ANNOTATIONS_FAILED = 'SAVE_ANNOTATIONS_FAILED', - SAVE_UPDATE_ANNOTATIONS_STATUS = 'SAVE_UPDATE_ANNOTATIONS_STATUS', SWITCH_PLAY = 'SWITCH_PLAY', CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', DRAG_CANVAS = 'DRAG_CANVAS', @@ -196,10 +195,6 @@ export enum AnnotationActionTypes { GET_PREDICTIONS = 'GET_PREDICTIONS', GET_PREDICTIONS_FAILED = 'GET_PREDICTIONS_FAILED', GET_PREDICTIONS_SUCCESS = 'GET_PREDICTIONS_SUCCESS', - HIDE_SHOW_CONTEXT_IMAGE = 'HIDE_SHOW_CONTEXT_IMAGE', - GET_CONTEXT_IMAGE = 'GET_CONTEXT_IMAGE', - GET_CONTEXT_IMAGE_SUCCESS = 'GET_CONTEXT_IMAGE_SUCCESS', - GET_CONTEXT_IMAGE_FAILED = 'GET_CONTEXT_IMAGE_FAILED', SWITCH_NAVIGATION_BLOCKED = 'SWITCH_NAVIGATION_BLOCKED', DELETE_FRAME = 'DELETE_FRAME', DELETE_FRAME_SUCCESS = 'DELETE_FRAME_SUCCESS', @@ -1113,19 +1108,8 @@ export function saveAnnotationsAsync(sessionInstance: any, afterSave?: () => voi try { const saveJobEvent = await sessionInstance.logger.log(LogType.saveJob, {}, true); - dispatch({ - type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS, - payload: { status: 'Saving frames' }, - }); await sessionInstance.frames.save(); - await sessionInstance.annotations.save((status: string) => { - dispatch({ - type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS, - payload: { - status, - }, - }); - }); + await sessionInstance.annotations.save(); await saveJobEvent.close(); await sessionInstance.logger.log(LogType.sendTaskInfo, await jobInfoGenerator(sessionInstance)); dispatch(saveLogsAsync()); @@ -1667,40 +1651,6 @@ export function switchPredictor(predictorEnabled: boolean): AnyAction { }, }; } -export function hideShowContextImage(hidden: boolean): AnyAction { - return { - type: AnnotationActionTypes.HIDE_SHOW_CONTEXT_IMAGE, - payload: { - hidden, - }, - }; -} - -export function getContextImageAsync(): ThunkAction { - return async (dispatch: ActionCreator): Promise => { - const state: CombinedState = getStore().getState(); - const { instance: job } = state.annotation.job; - const { number: frameNumber } = state.annotation.player.frame; - - try { - dispatch({ - type: AnnotationActionTypes.GET_CONTEXT_IMAGE, - payload: {}, - }); - - const contextImageData = await job.frames.contextImage(frameNumber); - dispatch({ - type: AnnotationActionTypes.GET_CONTEXT_IMAGE_SUCCESS, - payload: { contextImageData }, - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.GET_CONTEXT_IMAGE_FAILED, - payload: { error }, - }); - } - }; -} export function switchNavigationBlocked(navigationBlocked: boolean): AnyAction { return { diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index 7cb79b706070..ab784ea0106d 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -139,31 +139,13 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { - {workspace === Workspace.STANDARD3D && ( - - - - )} - {workspace === Workspace.STANDARD && ( - - - - )} - {workspace === Workspace.ATTRIBUTE_ANNOTATION && ( - - - - )} - {workspace === Workspace.TAG_ANNOTATION && ( - - - - )} - {workspace === Workspace.REVIEW_WORKSPACE && ( - - - - )} + + {workspace === Workspace.STANDARD3D && } + {workspace === Workspace.STANDARD && } + {workspace === Workspace.ATTRIBUTE_ANNOTATION && } + {workspace === Workspace.TAG_ANNOTATION && } + {workspace === Workspace.REVIEW_WORKSPACE && } + diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index e6c4244b335c..c280b164ae65 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -24,7 +24,6 @@ import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { ThunkDispatch } from 'utils/redux'; import AppearanceBlock from 'components/annotation-page/appearance-block'; import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; -import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import { CombinedState, ObjectType } from 'reducers'; import AttributeEditor from './attribute-editor'; import AttributeSwitcher from './attribute-switcher'; @@ -139,7 +138,6 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. (collapser as HTMLElement).addEventListener('transitionend', listener as any); } - adjustContextImagePosition(!sidebarCollapsed); setSidebarCollapsed(!sidebarCollapsed); }; diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx index 7a50b02d7665..3f9d5d404fe5 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -6,13 +6,13 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; +import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; export default function AttributeAnnotationWorkspace(): JSX.Element { return ( - + ); diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx new file mode 100644 index 000000000000..389eedbc4a8c --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx @@ -0,0 +1,253 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import './grid-layout-styles.scss'; +import 'react-grid-layout/css/styles.css'; + +import React, { useCallback, useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import RGL, { WidthProvider } from 'react-grid-layout'; +import PropTypes from 'prop-types'; +import Layout from 'antd/lib/layout'; +import { DragOutlined } from '@ant-design/icons'; + +import { DimensionType, CombinedState } from 'reducers'; +import CanvasWrapperComponent from 'containers/annotation-page/canvas/canvas-wrapper'; +import CanvasWrapper3DContainer from 'containers/annotation-page/canvas/canvas-wrapper3D'; +import { + PerspectiveViewComponent, + TopViewComponent, + SideViewComponent, + FrontViewComponent, +} from 'components/annotation-page/canvas/canvas-wrapper3D'; +import ContextImage from './context-image'; + +const ReactGridLayout = WidthProvider(RGL); + +enum ViewType { + CANVAS = 'canvas', + CANVAS_3D = 'canvas3D', + CANVAS_3D_TOP = 'canvas3DTop', + CANVAS_3D_SIDE = 'canvas3DSide', + CANVAS_3D_FRONT = 'canvas3DFront', + RELATED_IMAGE = 'relatedImage', +} + +interface ItemLayout { + viewType: ViewType; + offset: number[]; + x: number; + y: number; + w: number; + h: number; + viewIndex?: string; +} + +const defaultLayout: { [index: string]: ItemLayout[] } = {}; +defaultLayout.CANVAS_NO_RELATED = [{ + viewType: ViewType.CANVAS, + offset: [0], + x: 0, + y: 0, + w: 12, + h: 12, +}]; + +defaultLayout.CANVAS_ONE_RELATED = [ + { ...defaultLayout.CANVAS_NO_RELATED[0], w: 9 }, { + viewType: ViewType.RELATED_IMAGE, + offset: [0], + x: 9, + y: 0, + w: 3, + h: 4, + viewIndex: '1', + }, +]; + +defaultLayout.CANVAS_TWO_RELATED = [ + ...defaultLayout.CANVAS_ONE_RELATED, + { ...defaultLayout.CANVAS_ONE_RELATED[1], y: 3, viewIndex: '2' }, +]; + +defaultLayout.CANVAS_THREE_PLUS_RELATED = [ + ...defaultLayout.CANVAS_TWO_RELATED, + { ...defaultLayout.CANVAS_TWO_RELATED[1], y: 6, viewIndex: '3' }, +]; + +defaultLayout.CANVAS_3D_NO_RELATED = [{ + viewType: ViewType.CANVAS_3D, + offset: [0], + x: 0, + y: 0, + w: 12, + h: 9, +}, { + viewType: ViewType.CANVAS_3D_TOP, + offset: [0], + x: 0, + y: 9, + w: 4, + h: 3, +}, { + viewType: ViewType.CANVAS_3D_SIDE, + offset: [0], + x: 4, + y: 9, + w: 4, + h: 3, +}, { + viewType: ViewType.CANVAS_3D_FRONT, + offset: [0], + x: 8, + y: 9, + w: 4, + h: 3, +}]; + +defaultLayout.CANVAS_3D_ONE_RELATED = [ + { ...defaultLayout.CANVAS_3D_NO_RELATED[0], w: 9 }, + { ...defaultLayout.CANVAS_3D_NO_RELATED[1], w: 3 }, + { ...defaultLayout.CANVAS_3D_NO_RELATED[2], x: 3, w: 3 }, + { ...defaultLayout.CANVAS_3D_NO_RELATED[3], x: 6, w: 3 }, + { + viewType: ViewType.RELATED_IMAGE, + offset: [0], + x: 9, + y: 0, + w: 3, + h: 4, + viewIndex: '1', + }, +]; + +defaultLayout.CANVAS_3D_TWO_RELATED = [ + ...defaultLayout.CANVAS_3D_ONE_RELATED, + { ...defaultLayout.CANVAS_3D_ONE_RELATED[4], y: 4, viewIndex: '2' }, +]; + +defaultLayout.CANVAS_3D_THREE_PLUS_RELATED = [ + ...defaultLayout.CANVAS_3D_TWO_RELATED, + { ...defaultLayout.CANVAS_3D_TWO_RELATED[5], y: 8, viewIndex: '3' }, +]; + +const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { + const { viewType: type, offset, viewIndex } = itemLayout; + const key = typeof viewIndex !== 'undefined' ? `${type}_${viewIndex}` : `${type}`; + + let component = null; + switch (type) { + case ViewType.CANVAS: + component = ; + break; + case ViewType.CANVAS_3D: + component = ; + break; + case ViewType.RELATED_IMAGE: + component = ; + break; + case ViewType.CANVAS_3D_FRONT: + component = ; + break; + case ViewType.CANVAS_3D_SIDE: + component = ; + break; + case ViewType.CANVAS_3D_TOP: + component = ; + break; + default: + component =
Undefined view
; + } + + return ( +
+ + { component } +
+ ); +}; + +function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { + const NUM_OF_ROWS = 12; + const MARGIN = 8; + const [rowHeight, setRowHeight] = useState(Math.floor(window.screen.availHeight / NUM_OF_ROWS)); + const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); + const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); + const canvasBackgroundColor = useSelector((state: CombinedState) => state.settings.player.canvasBackgroundColor); + const getLayout = useCallback(() => { + if (type === DimensionType.DIM_2D) { + if (!relatedFiles) return defaultLayout.CANVAS_NO_RELATED; + if (relatedFiles === 1) return defaultLayout.CANVAS_ONE_RELATED; + if (relatedFiles === 2) return defaultLayout.CANVAS_TWO_RELATED; + return defaultLayout.CANVAS_THREE_PLUS_RELATED; + } + + if (!relatedFiles) return defaultLayout.CANVAS_3D_NO_RELATED; + if (relatedFiles === 1) return defaultLayout.CANVAS_3D_ONE_RELATED; + if (relatedFiles === 2) return defaultLayout.CANVAS_3D_TWO_RELATED; + return defaultLayout.CANVAS_3D_THREE_PLUS_RELATED; + }, [type, relatedFiles]); + + const onLayoutChange = useCallback(() => { + if (canvasInstance) { + canvasInstance.fitCanvas(); + canvasInstance.fit(); + } + }, [canvasInstance]); + + useEffect(() => { + const container = window.document.getElementsByClassName('cvat-annotation-layout-content')[0]; + if (container) { + const { height } = container.getBoundingClientRect(); + // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 + setRowHeight(Math.floor((height - MARGIN * (NUM_OF_ROWS + 4)) / NUM_OF_ROWS)); + } + + setTimeout(() => { + if (canvasInstance) { + canvasInstance.fitCanvas(); + canvasInstance.fit(); + } + }); + }, []); + + const children = getLayout().map((value: ItemLayout) => ViewFabric((value))); + const layout = getLayout().map((value: ItemLayout) => ({ + x: value.x, + y: value.y, + w: value.w, + h: value.h, + i: typeof (value.viewIndex) !== 'undefined' ? `${value.viewType}_${value.viewIndex}` : `${value.viewType}`, + })); + + return ( + + ) => ( +
+ )} + draggableHandle='.cvat-grid-item-drag-handler' + > + { children } + + { type === DimensionType.DIM_3D && } + + ); +} + +CanvasLayout.defaultProps = { + type: DimensionType.DIM_2D, +}; + +CanvasLayout.PropType = { + type: PropTypes.oneOf(Object.values(DimensionType)), +}; + +export default React.memo(CanvasLayout); diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx index 58eda99dbfc9..b7b83329e570 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx @@ -4,7 +4,6 @@ // SPDX-License-Identifier: MIT import React from 'react'; -import Layout from 'antd/lib/layout'; import Slider from 'antd/lib/slider'; import Dropdown from 'antd/lib/dropdown'; import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons'; @@ -22,7 +21,6 @@ import CVATTooltip from 'components/common/cvat-tooltip'; import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags'; import ImageSetupsContent from './image-setups-content'; import BrushTools from './brush-tools'; -import ContextImage from '../standard-workspace/context-image/context-image'; const cvat = getCore(); @@ -72,7 +70,6 @@ interface Props { automaticBordering: boolean; intelligentPolygonCrop: boolean; keyMap: KeyMap; - canvasBackgroundColor: string; switchableAutomaticBordering: boolean; showTagsOnFrame: boolean; onSetupCanvas: () => void; @@ -186,7 +183,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { automaticBordering, intelligentPolygonCrop, showProjections, - canvasBackgroundColor, colorBy, onFetchAnnotation, } = this.props; @@ -323,15 +319,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - if (prevProps.canvasBackgroundColor !== canvasBackgroundColor) { - const canvasWrapperElement = window.document - .getElementsByClassName('cvat-canvas-container') - .item(0) as HTMLElement | null; - if (canvasWrapperElement) { - canvasWrapperElement.style.backgroundColor = canvasBackgroundColor; - } - } - this.activateOnCanvas(); } @@ -683,7 +670,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { brightnessLevel, contrastLevel, saturationLevel, - canvasBackgroundColor, } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; @@ -707,12 +693,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { CSSImageFilter: `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`, }); - const canvasWrapperElement = window.document - .getElementsByClassName('cvat-canvas-container') - .item(0) as HTMLElement | null; - if (canvasWrapperElement) { - canvasWrapperElement.style.backgroundColor = canvasBackgroundColor; - } // Events canvasInstance.html().addEventListener( @@ -788,7 +768,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; return ( - + <> {/* This element doesn't have any props @@ -804,7 +784,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { }} /> - }> @@ -832,8 +811,7 @@ export default class CanvasWrapperComponent extends React.PureComponent {
) : null} - ; -
+ ); } } diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx index 783460e600c3..4e2243102538 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx @@ -4,24 +4,22 @@ // SPDX-License-Identifier: MIT import React, { - ReactElement, SyntheticEvent, useEffect, useReducer, useRef, + ReactElement, useEffect, useRef, } from 'react'; -import Layout from 'antd/lib/layout/layout'; import { ArrowDownOutlined, ArrowLeftOutlined, ArrowRightOutlined, ArrowUpOutlined, } from '@ant-design/icons'; -import { ResizableBox } from 'react-resizable'; import { - ColorBy, ContextMenuType, ObjectType, Workspace, + ColorBy, CombinedState, ContextMenuType, ObjectType, Workspace, } from 'reducers'; import { - CameraAction, Canvas3d, ViewType, ViewsDOM, + CameraAction, Canvas3d, ViewsDOM, } from 'cvat-canvas3d-wrapper'; import { Canvas } from 'cvat-canvas-wrapper'; -import ContextImage from 'components/annotation-page/standard-workspace/context-image/context-image'; import CVATTooltip from 'components/common/cvat-tooltip'; import { LogType } from 'cvat-logger'; import { getCore } from 'cvat-core-wrapper'; +import { useSelector } from 'react-redux'; const cvat = getCore(); @@ -54,120 +52,258 @@ interface Props { workspace: Workspace; frame: number; resetZoom: boolean; + perspectiveRenderContainer: string; + topRenderContainer: string; + sideRenderContainer: string; + frontRenderContainer: string; } -interface ViewSize { - fullHeight: number; - fullWidth: number; - vertical: number; - top: number; - side: number; - front: number; -} +export const PerspectiveViewComponent = React.memo( + (): JSX.Element => { + const ref = useRef(null); + const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); + const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + + const screenKeyControl = (code: CameraAction, altKey: boolean, shiftKey: boolean): void => { + canvas.keyControls(new KeyboardEvent('keydown', { code, altKey, shiftKey })); + }; + + const ArrowGroup = (): ReactElement => ( + + + + +
+ + + + + + + + + +
+ ); + + const ControlGroup = (): ReactElement => ( + + + + + + + + + + +
+ + + + + + + + + +
+ ); -function viewSizeReducer( - state: ViewSize, - action: { type: ViewType | 'set' | 'resize'; e?: SyntheticEvent; data?: ViewSize }, -): ViewSize { - const event = (action.e as unknown) as MouseEvent; - const canvas3dContainer = document.getElementById('canvas3d-container'); - if (canvas3dContainer) { - switch (action.type) { - case ViewType.TOP: { - const width = event.clientX - canvas3dContainer.getBoundingClientRect().left; - const topWidth = state.top; - if (topWidth < width) { - const top = state.top + (width - topWidth); - const side = state.side - (width - topWidth); - return { - ...state, - top, - side, - }; + useEffect(() => { + if (ref.current) { + ref.current.appendChild(canvas.html().perspective); + } + }, []); + + return ( +
+ { + frameFetching && ( + + + + ) } - const top = state.top - (topWidth - width); - const side = state.side + (topWidth - width); - return { - ...state, - top, - side, - }; +
+ { ArrowGroup } + { ControlGroup } +
+ ); + }, +); + +export const TopViewComponent = React.memo( + (): JSX.Element => { + const ref = useRef(null); + const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); + const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + + useEffect(() => { + if (ref.current) { + ref.current.appendChild(canvas.html().top); } - case ViewType.SIDE: { - const width = event.clientX - canvas3dContainer.getBoundingClientRect().left; - const topSideWidth = state.top + state.side; - if (topSideWidth < width) { - const side = state.side + (width - topSideWidth); - const front = state.front - (width - topSideWidth); - return { - ...state, - side, - front, - }; + }, []); + + return ( +
+ { + frameFetching && ( + + + + ) } - const side = state.side - (topSideWidth - width); - const front = state.front + (topSideWidth - width); - return { - ...state, - side, - front, - }; +
Top
+
+
+ ); + }, +); + +export const SideViewComponent = React.memo( + (): JSX.Element => { + const ref = useRef(null); + const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); + const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + + useEffect(() => { + if (ref.current) { + ref.current.appendChild(canvas.html().side); } - case ViewType.PERSPECTIVE: - return { - ...state, - vertical: event.clientY - canvas3dContainer.getBoundingClientRect().top, - }; - case 'set': - return action.data as ViewSize; - case 'resize': { - const canvasPerspectiveContainer = document.getElementById('cvat-canvas3d-perspective'); - let midState = { ...state }; - if (canvasPerspectiveContainer) { - if (state.fullHeight !== canvas3dContainer.clientHeight) { - const diff = canvas3dContainer.clientHeight - state.fullHeight; - midState = { - ...midState, - fullHeight: canvas3dContainer.clientHeight, - vertical: state.vertical + diff, - }; - } - if (state.fullWidth !== canvasPerspectiveContainer.clientWidth) { - const oldWidth = state.fullWidth; - const width = canvasPerspectiveContainer.clientWidth; - midState = { - ...midState, - fullWidth: width, - top: (state.top / oldWidth) * width, - side: (state.side / oldWidth) * width, - front: (state.front / oldWidth) * width, - }; - } - return midState; + }, []); + + return ( +
+ { + frameFetching && ( + + + + ) } - return state; +
Side
+
+
+ ); + }, +); + +export const FrontViewComponent = React.memo( + (): JSX.Element => { + const ref = useRef(null); + const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); + const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + + useEffect(() => { + if (ref.current) { + ref.current.appendChild(canvas.html().front); } - default: - throw new Error(); - } - } - return state; -} + }, []); + + return ( +
+ { + frameFetching && ( + + + + ) + } +
Front
+
+
+ ); + }, +); const CanvasWrapperComponent = (props: Props): ReactElement => { const animateId = useRef(0); - const [viewSize, setViewSize] = useReducer(viewSizeReducer, { - fullHeight: 0, - fullWidth: 0, - vertical: 0, - top: 0, - side: 0, - front: 0, - }); - const perspectiveView = useRef(null); - const topView = useRef(null); - const sideView = useRef(null); - const frontView = useRef(null); const { opacity, @@ -188,7 +324,6 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { activeObjectType, onShapeDrawn, onCreateAnnotations, - frameFetching, } = props; const { canvasInstance } = props as { canvasInstance: Canvas3d }; @@ -289,36 +424,6 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { useEffect(() => { const canvasInstanceDOM = canvasInstance.html(); - if ( - perspectiveView && - perspectiveView.current && - topView && - topView.current && - sideView && - sideView.current && - frontView && - frontView.current - ) { - perspectiveView.current.appendChild(canvasInstanceDOM.perspective); - topView.current.appendChild(canvasInstanceDOM.top); - sideView.current.appendChild(canvasInstanceDOM.side); - frontView.current.appendChild(canvasInstanceDOM.front); - const canvas3dContainer = document.getElementById('canvas3d-container'); - if (canvas3dContainer) { - const width = canvas3dContainer.clientWidth / 3; - setViewSize({ - type: 'set', - data: { - fullHeight: canvas3dContainer.clientHeight, - fullWidth: canvas3dContainer.clientWidth, - vertical: canvas3dContainer.clientHeight / 2, - top: width, - side: width, - front: width, - }, - }); - } - } document.addEventListener('keydown', keyControlsKeyDown); document.addEventListener('keyup', keyControlsKeyUp); @@ -367,12 +472,6 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { ); }; - const onResize = (): void => { - setViewSize({ - type: 'resize', - }); - }; - const onCanvasObjectsGroupped = (event: any): void => { const { onGroupAnnotations, onGroupObjects } = props; @@ -394,9 +493,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { canvasInstanceDOM.perspective.addEventListener('canvas.edited', onCanvasEditDone); canvasInstanceDOM.perspective.addEventListener('canvas.contextmenu', onContextMenu); canvasInstanceDOM.perspective.addEventListener('click', onCanvasClick); - canvasInstanceDOM.perspective.addEventListener('canvas.fit', onResize); canvasInstanceDOM.perspective.addEventListener('canvas.groupped', onCanvasObjectsGroupped); - window.addEventListener('resize', onResize); return () => { canvasInstanceDOM.perspective.removeEventListener('canvas.drawn', onCanvasShapeDrawn); @@ -404,183 +501,11 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { canvasInstanceDOM.perspective.removeEventListener('canvas.edited', onCanvasEditDone); canvasInstanceDOM.perspective.removeEventListener('canvas.contextmenu', onContextMenu); canvasInstanceDOM.perspective.removeEventListener('click', onCanvasClick); - canvasInstanceDOM.perspective.removeEventListener('canvas.fit', onResize); canvasInstanceDOM.perspective.removeEventListener('canvas.groupped', onCanvasObjectsGroupped); - window.removeEventListener('resize', onResize); }; }, [frameData, annotations, activeLabelID, contextMenuVisibility]); - const screenKeyControl = (code: CameraAction, altKey: boolean, shiftKey: boolean): void => { - canvasInstance.keyControls(new KeyboardEvent('keydown', { code, altKey, shiftKey })); - }; - - const ArrowGroup = (): ReactElement => ( - - - - -
- - - - - - - - - -
- ); - - const ControlGroup = (): ReactElement => ( - - - - - - - - - - -
- - - - - - - - - -
- ); - - return ( - - - } - onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.PERSPECTIVE, e })} - > - <> - {frameFetching ? ( - - - - ) : null} -
-
- - -
- - -
- } - onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.TOP, e })} - > -
-
TOP
-
-
- - } - onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.SIDE, e })} - > -
-
SIDE
-
-
- -
-
FRONT
-
-
-
- - ); + return <>; }; export default React.memo(CanvasWrapperComponent); diff --git a/cvat-ui/src/components/annotation-page/canvas/context-image-selector.tsx b/cvat-ui/src/components/annotation-page/canvas/context-image-selector.tsx new file mode 100644 index 000000000000..bf2d7f2ac164 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/context-image-selector.tsx @@ -0,0 +1,72 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useRef, useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; + +interface Props { + images: ImageBitmap[]; + offset: number; + onChangeOffset: (offset: number) => void; + onClose: () => void; +} + +function CanvasWithRef({ + image, isActive, onClick, +}: { image: ImageBitmap, isActive: boolean, onClick: () => void }): JSX.Element { + const ref = useRef(null); + + useEffect((): void => { + if (ref.current) { + const context = ref.current.getContext('2d'); + if (context) { + ref.current.width = image.width; + ref.current.height = image.height; + context.drawImage(image, 0, 0); + } + } + }, [image, ref]); + + return ( + + ); +} + +function ContextImageSelector(props: Props): React.ReactPortal { + const { + images, offset, onChangeOffset, onClose, + } = props; + + return ReactDOM.createPortal(( +
+
+ { images.map((image: ImageBitmap, i: number) => ( + { + onChangeOffset(i); + onClose(); + }} + key={i} + /> + ))} +
+
+ ), window.document.body); +} + +ContextImageSelector.PropType = { + images: PropTypes.arrayOf(PropTypes.string), + offset: PropTypes.number, + onChangeOffset: PropTypes.func, + onClose: PropTypes.func, +}; + +export default React.memo(ContextImageSelector); diff --git a/cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss b/cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss new file mode 100644 index 000000000000..6bf364c4d22e --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss @@ -0,0 +1,84 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../../base.scss'; + +.cvat-context-image-wrapper { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + + > .ant-spin { + position: absolute; + top: 50%; + transform: translate(0, -50%); + } + + > .ant-empty { + width: 100%; + height: 100%; + + > .ant-empty-image { + height: 60%; + } + } + + > canvas { + object-fit: contain; + position: relative; + top: 50%; + transform: translateY(-50%); + width: 100%; + height: 100%; + } + + > .cvat-context-image-setup-button { + position: absolute; + top: $grid-unit-size; + right: $grid-unit-size; + font-size: 24px; + } +} + +.cvat-context-image-overlay { + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 1000; + background: rgba(255,255,255,0.25); + position: absolute; + display: flex; + justify-content: space-around; + align-items: center; + + .cvat-context-image-switcher { + width: 80%; + max-height: 80%; + position: relative; + background: white; + padding: $grid-unit-size; + display: block; + justify-content: space-between; + overflow: hidden; + overflow-y: auto; + + .cvat-context-image-switcher-item { + &.cvat-context-image-switcher-item-current { + opacity: 1; + } + + &:hover { + opacity: 0.9; + } + + padding: $grid-unit-size; + opacity: 0.6; + width: 25%; + } + + + } +} diff --git a/cvat-ui/src/components/annotation-page/canvas/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/context-image.tsx new file mode 100644 index 000000000000..97c5c05c86d9 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/context-image.tsx @@ -0,0 +1,111 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import './context-image-styles.scss'; +import React, { useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import notification from 'antd/lib/notification'; +import Spin from 'antd/lib/spin'; +import Empty from 'antd/lib/empty'; +import { SettingOutlined } from '@ant-design/icons'; + +import { CombinedState } from 'reducers'; +import ContextImageSelector from './context-image-selector'; + +interface Props { + offset: number[]; +} + +function ContextImage(props: Props): JSX.Element { + const { offset } = props; + const canvasRef = useRef(null); + const job = useSelector((state: CombinedState) => state.annotation.job.instance); + const { number: frame, relatedFiles } = useSelector((state: CombinedState) => state.annotation.player.frame); + const frameIndex = frame + (offset[0] || 0); + + const [contextImageData, setContextImageData] = useState([]); + const [contextImageOffset, setContextImageOffset] = useState( + Math.min(offset[1] || 0, relatedFiles), + ); + + const [hasError, setHasError] = useState(false); + const [showSelector, setShowSelector] = useState(false); + + useEffect(() => { + let unmounted = false; + const promise = job.frames.contextImage(frameIndex); + promise.then((imageBitmaps: ImageBitmap[]) => { + if (!unmounted) { + setContextImageData(imageBitmaps); + } + }).catch((error: any) => { + if (!unmounted) { + setHasError(true); + notification.error({ + message: `Could not fetch context images. Frame: ${frameIndex}`, + description: error.toString(), + }); + } + }); + + return () => { + setContextImageData([]); + unmounted = true; + }; + }, [frameIndex]); + + useEffect(() => { + if (canvasRef.current) { + const image = contextImageData[contextImageOffset]; + const context = canvasRef.current.getContext('2d'); + if (context) { + canvasRef.current.width = image.width; + canvasRef.current.height = image.height; + context.drawImage(image, 0, 0); + } + } + }, [contextImageData, contextImageOffset, canvasRef]); + + return ( +
+ { hasError && } + { relatedFiles && contextImageData.length === 0 && !hasError && } + { contextImageData.length > 0 && ( + <> + + { relatedFiles > 1 && ( + { + setShowSelector(true); + }} + /> + )} + + )} + { showSelector && ( + { + setContextImageOffset(newContextImageOffset); + }} + onClose={() => { + setShowSelector(false); + }} + /> + )} +
+ ); +} + +ContextImage.PropType = { + offset: PropTypes.arrayOf(PropTypes.number), +}; + +export default React.memo(ContextImage); diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss new file mode 100644 index 000000000000..c613977d784c --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss @@ -0,0 +1,33 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../../base.scss'; + +.cvat-canvas-grid-root { + position: relative; +} + +.cvat-canvas-grid-item { + background-color: rgba(241, 241, 241, 0.25); + border-radius: 2px; + + .cvat-grid-item-drag-handler { + position: absolute; + top: $grid-unit-size; + left: $grid-unit-size; + z-index: 1000; + cursor: move; + font-size: 16px; + background: #d8d8d8; + border-radius: 2px; + } + + .cvat-grid-item-resize-handler { + bottom: 0; + right: 0; + cursor: se-resize; + background: #d8d8d8; + border-radius: $grid-unit-size * 4 0 0 0; + } +} diff --git a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx index cae74fefc0f0..4a75ed5adf21 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx @@ -6,8 +6,8 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; import ControlsSideBarContainer from 'containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar'; +import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; @@ -17,7 +17,7 @@ export default function ReviewWorkspaceComponent(): JSX.Element { return ( - + } /> diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx deleted file mode 100644 index 8d43515ef41c..000000000000 --- a/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useEffect, useState } from 'react'; -import notification from 'antd/lib/notification'; -import { useDispatch, useSelector } from 'react-redux'; -import { QuestionCircleOutlined, ShrinkOutlined } from '@ant-design/icons'; -import Spin from 'antd/lib/spin'; -import Image from 'antd/lib/image'; - -import { CombinedState } from 'reducers'; -import { hideShowContextImage, getContextImageAsync } from 'actions/annotation-actions'; -import CVATTooltip from 'components/common/cvat-tooltip'; - -export function adjustContextImagePosition(sidebarCollapsed: boolean): void { - const element = window.document.getElementsByClassName('cvat-context-image-wrapper')[0] as - | HTMLDivElement - | undefined; - if (element) { - if (sidebarCollapsed) { - element.style.right = '40px'; - } else { - element.style.right = ''; - } - } -} - -function ContextImage(): JSX.Element | null { - const dispatch = useDispatch(); - const { number: frame, relatedFiles } = useSelector((state: CombinedState) => state.annotation.player.frame); - const { data: contextImageData, hidden: contextImageHidden, fetching: contextImageFetching } = useSelector( - (state: CombinedState) => state.annotation.player.contextImage, - ); - const [requested, setRequested] = useState(false); - - useEffect(() => { - if (requested) { - setRequested(false); - } - }, [frame, contextImageData]); - - useEffect(() => { - if (relatedFiles && !contextImageHidden && !requested) { - dispatch(getContextImageAsync()); - setRequested(true); - } - }, [contextImageHidden, requested, relatedFiles]); - - if (!relatedFiles) { - return null; - } - - return ( -
-
- {contextImageFetching ? : null} - {contextImageHidden ? ( - - dispatch(hideShowContextImage(false))} - /> - - ) : ( - <> - dispatch(hideShowContextImage(true))} - /> - { - notification.error({ - message: 'Could not display context image', - description: `Source is ${ - contextImageData === null ? 'empty' : contextImageData.slice(0, 100) - }`, - }); - }} - className='cvat-context-image' - /> - - )} -
- ); -} - -export default React.memo(ContextImage); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index b4239e5fcf43..dfe39ce695ac 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -15,7 +15,6 @@ import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { CombinedState, DimensionType } from 'reducers'; import LabelsList from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; -import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import { collapseSidebar as collapseSidebarAction } from 'actions/annotation-actions'; import AppearanceBlock from 'components/annotation-page/appearance-block'; import IssuesListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/issues-list'; @@ -77,7 +76,6 @@ function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.E (collapser as HTMLElement).addEventListener('transitionend', listener as any); } - adjustContextImagePosition(!sidebarCollapsed); collapseSidebar(); }; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index 096d62fe5c0d..963216cc076e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -7,7 +7,7 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; +import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; @@ -21,7 +21,7 @@ export default function StandardWorkspaceComponent(): JSX.Element { return ( - + } /> diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index ef788cf583cd..5646cd895a11 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -7,55 +7,10 @@ .cvat-standard-workspace.ant-layout { height: 100%; -} - -.cvat-context-image-wrapper { - height: auto; - width: $grid-unit-size * 32; - position: absolute; - top: $grid-unit-size; - right: $grid-unit-size; - z-index: 100; - background: black; - display: flex; - flex-direction: column; - justify-content: space-between; - user-select: none; - > .cvat-context-image-wrapper-header { - height: $grid-unit-size * 4; - width: 100%; - z-index: 101; - background: rgba(0, 0, 0, 0.2); - position: absolute; - top: 0; - left: 0; + > .ant-layout-content { + overflow: auto; } - - > .ant-image { - margin: $grid-unit-size * 0.5; - } - - > span { - position: absolute; - font-size: 18px; - top: 7px; - right: 7px; - z-index: 102; - color: white; - - &:hover { - > svg { - transform: scale(1.2); - } - } - } -} - -.cvat-context-image { - width: 100%; - height: auto; - display: block; } .cvat-objects-sidebar-sider { diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx deleted file mode 100644 index 5ebb696aeb2e..000000000000 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import CameraIcon from '@ant-design/icons/CameraOutlined'; - -import CVATTooltip from 'components/common/cvat-tooltip'; -import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import { Canvas } from 'cvat-canvas-wrapper'; -import { ActiveControl } from 'reducers'; - -interface Props { - canvasInstance: Canvas3d | Canvas; - activeControl: ActiveControl; - hideShowContextImage: (hidden: boolean) => void; - contextImageHide: boolean; -} - -function PhotoContextControl(props: Props): JSX.Element { - const { activeControl, contextImageHide, hideShowContextImage } = props; - - return ( - - { - hideShowContextImage(!contextImageHide); - }} - /> - - ); -} - -export default React.memo(PhotoContextControl); diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index 18f70140b0a2..10b528ebebce 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -6,11 +6,12 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper3D'; +import { DimensionType } from 'reducers'; import ControlsSideBarContainer from 'containers/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; +import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; @@ -19,7 +20,7 @@ export default function StandardWorkspace3DComponent(): JSX.Element { return ( - + } /> diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss index b4ab69aecafe..351f09d38690 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss @@ -77,72 +77,18 @@ } .cvat-canvas3d-fullsize { - position: relative; width: 100%; - height: 100%; -} - -.cvat-canvas3d-view-slider { - position: absolute; - margin-left: auto; - width: $grid-unit-size * 10; - margin-right: auto; - right: 0; - top: 0; - left: 0; - background-color: grey; - height: $grid-unit-size * 0.5; + height: calc(100% - $grid-unit-size * 3); } .cvat-canvas3d-header { - height: $grid-unit-size * 4; + height: $grid-unit-size * 3; width: 100%; background-color: $background-color-2; text-align: center; vertical-align: middle; } -.cvat-resizable { - position: relative; -} - -.cvat-resizable-handle-horizontal { - position: absolute; - margin-left: auto; - width: 100%; - margin-right: auto; - right: 0; - bottom: 0; - left: 0; - background-color: grey; - height: $grid-unit-size * 0.5; - cursor: ns-resize; -} - -.cvat-resizable-handle-vertical-side { - position: absolute; - width: $grid-unit-size * 0.5; - margin-right: auto; - top: $grid-unit-size * 4.5; - right: 0; - bottom: 0; - background-color: grey; - height: 100%; - cursor: ew-resize; -} - -.cvat-resizable-handle-vertical-top { - position: absolute; - width: $grid-unit-size * 0.5; - margin-right: auto; - top: $grid-unit-size * 4.5; - right: 0; - bottom: 0; - background-color: grey; - height: 100%; - cursor: ew-resize; -} - #cvat_canvas_loading_animation { z-index: 1; position: absolute; diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index fcdfe480e772..9aef9f46412b 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -181,6 +181,8 @@ button.cvat-predictor-button { .cvat-player-filename-wrapper { max-width: $grid-unit-size * 30; + max-height: 27px; + line-height: 27px; overflow: hidden; text-overflow: ellipsis; user-select: none; @@ -517,3 +519,9 @@ button.cvat-predictor-button { } } } + +.cvat-saving-job-modal { + span.anticon { + margin-left: $grid-unit-size * 2; + } +} \ No newline at end of file diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index c67258a55541..55aa362964b3 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -27,7 +27,6 @@ import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { getCore, Label, LabelType } from 'cvat-core-wrapper'; import { CombinedState, ObjectType } from 'reducers'; import { filterApplicableForType } from 'utils/filter-applicable-labels'; -import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import LabelSelector from 'components/label-selector/label-selector'; import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; @@ -234,7 +233,6 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen ant-layout-sider-zero-width-trigger ant-layout-sider-zero-width-trigger-left`} onClick={() => { - adjustContextImagePosition(!sidebarCollapsed); setSidebarCollapsed(!sidebarCollapsed); }} > @@ -256,7 +254,6 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen ant-layout-sider-zero-width-trigger ant-layout-sider-zero-width-trigger-left`} onClick={() => { - adjustContextImagePosition(!sidebarCollapsed); setSidebarCollapsed(!sidebarCollapsed); }} > diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx index e4d202d6b446..cd7473e98a22 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx @@ -6,14 +6,14 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; +import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; import TagAnnotationSidebar from './tag-annotation-sidebar/tag-annotation-sidebar'; export default function TagAnnotationWorkspace(): JSX.Element { return ( - + diff --git a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx index e692f20ba951..c35166b67836 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx @@ -4,10 +4,10 @@ import React from 'react'; import { Col } from 'antd/lib/grid'; -import Icon, { StopOutlined, CheckCircleOutlined } from '@ant-design/icons'; +import Icon, { StopOutlined, CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons'; import Modal from 'antd/lib/modal'; import Button from 'antd/lib/button'; -import Timeline from 'antd/lib/timeline'; +import Text from 'antd/lib/typography/Text'; import Dropdown from 'antd/lib/dropdown'; import AnnotationMenuContainer from 'containers/annotation-page/top-bar/annotation-menu'; @@ -19,7 +19,6 @@ import CVATTooltip from 'components/common/cvat-tooltip'; interface Props { saving: boolean; - savingStatuses: string[]; undoAction?: string; redoAction?: string; saveShortcut: string; @@ -39,7 +38,6 @@ interface Props { function LeftGroup(props: Props): JSX.Element { const { saving, - savingStatuses, undoAction, redoAction, saveShortcut, @@ -71,12 +69,9 @@ function LeftGroup(props: Props): JSX.Element { return ( <> - - - {savingStatuses.slice(0, -1).map((status: string, id: number) => ( - {status} - ))} - + + CVAT is working to save annotations, please wait + }> 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 d238c94adaf9..5df047714f4c 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 @@ -17,7 +17,6 @@ import RightGroup from './right-group'; interface Props { playing: boolean; saving: boolean; - savingStatuses: string[]; frameNumber: number; frameFilename: string; frameDeleted: boolean; @@ -75,7 +74,6 @@ interface Props { export default function AnnotationTopBarComponent(props: Props): JSX.Element { const { saving, - savingStatuses, undoAction, redoAction, playing, @@ -135,7 +133,6 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { void; 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 34b321ad890d..876ba09e74c4 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 @@ -56,7 +56,6 @@ interface StateToProps { playing: boolean; saving: boolean; canvasIsReady: boolean; - savingStatuses: string[]; undoAction?: string; redoAction?: string; autoSave: boolean; @@ -106,7 +105,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, }, annotations: { - saving: { uploading: saving, statuses: savingStatuses, forceExit }, + saving: { uploading: saving, forceExit }, history, }, job: { instance: jobInstance }, @@ -135,7 +134,6 @@ function mapStateToProps(state: CombinedState): StateToProps { playing, canvasIsReady, saving, - savingStatuses, frameNumber, frameFilename, jobInstance, @@ -607,7 +605,6 @@ class AnnotationTopBarContainer extends React.PureComponent { const { playing, saving, - savingStatuses, jobInstance, jobInstance: { startFrame, stopFrame }, frameNumber, @@ -751,7 +748,6 @@ class AnnotationTopBarContainer extends React.PureComponent { workspace={workspace} playing={playing} saving={saving} - savingStatuses={savingStatuses} startFrame={startFrame} stopFrame={stopFrame} frameNumber={frameNumber} diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 3efaa1f9c3c3..17193033c1ce 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -73,11 +73,6 @@ const defaultState: AnnotationState = { playing: false, frameAngles: [], navigationBlocked: false, - contextImage: { - fetching: false, - data: null, - hidden: false, - }, }, drawing: { activeShapeType: ShapeType.RECTANGLE, @@ -91,7 +86,6 @@ const defaultState: AnnotationState = { saving: { forceExit: false, uploading: false, - statuses: [], }, collapsed: {}, collapsedAll: true, @@ -299,10 +293,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { changeTime, delay, }, - contextImage: { - ...state.player.contextImage, - ...(state.player.frame.number === number ? {} : { data: null }), - }, }, annotations: { ...state.annotations, @@ -351,7 +341,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { saving: { ...state.annotations.saving, uploading: true, - statuses: [], }, }, }; @@ -385,20 +374,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS: { - const { status } = action.payload; - - return { - ...state, - annotations: { - ...state.annotations, - saving: { - ...state.annotations.saving, - statuses: [...state.annotations.saving.statuses, status], - }, - }, - }; - } case AnnotationActionTypes.SWITCH_PLAY: { const { playing } = action.payload; @@ -1188,58 +1163,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.HIDE_SHOW_CONTEXT_IMAGE: { - const { hidden } = action.payload; - return { - ...state, - player: { - ...state.player, - contextImage: { - ...state.player.contextImage, - hidden, - }, - }, - }; - } - case AnnotationActionTypes.GET_CONTEXT_IMAGE: { - return { - ...state, - player: { - ...state.player, - contextImage: { - ...state.player.contextImage, - fetching: true, - }, - }, - }; - } - case AnnotationActionTypes.GET_CONTEXT_IMAGE_SUCCESS: { - const { contextImageData } = action.payload; - - return { - ...state, - player: { - ...state.player, - contextImage: { - ...state.player.contextImage, - fetching: false, - data: contextImageData, - }, - }, - }; - } - case AnnotationActionTypes.GET_CONTEXT_IMAGE_FAILED: { - return { - ...state, - player: { - ...state.player, - contextImage: { - ...state.player.contextImage, - fetching: false, - }, - }, - }; - } case AnnotationActionTypes.SWITCH_NAVIGATION_BLOCKED: { return { ...state, diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 6b52c0596804..3ee15237ffac 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -466,7 +466,6 @@ export interface NotificationsState { saving: null | ErrorState; jobFetching: null | ErrorState; frameFetching: null | ErrorState; - contextImageFetching: null | ErrorState; changingLabelColor: null | ErrorState; updating: null | ErrorState; creating: null | ErrorState; @@ -687,11 +686,6 @@ export interface AnnotationState { navigationBlocked: boolean; playing: boolean; frameAngles: number[]; - contextImage: { - fetching: boolean; - data: string | null; - hidden: boolean; - }; }; drawing: { activeInteractor?: Model | OpenCVTool; @@ -719,7 +713,6 @@ export interface AnnotationState { saving: { forceExit: boolean; uploading: boolean; - statuses: string[]; }; zLayer: { min: number; diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index c81778b00554..b66c007df5e6 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -85,7 +85,6 @@ const defaultState: NotificationsState = { saving: null, jobFetching: null, frameFetching: null, - contextImageFetching: null, changingLabelColor: null, updating: null, creating: null, @@ -852,21 +851,6 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } - case AnnotationActionTypes.GET_CONTEXT_IMAGE_FAILED: { - return { - ...state, - errors: { - ...state.errors, - annotation: { - ...state.errors.annotation, - contextImageFetching: { - message: 'Could not fetch context image from the server', - reason: action.payload.error, - }, - }, - }, - }; - } case AnnotationActionTypes.SAVE_ANNOTATIONS_FAILED: { return { ...state, diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 85e19ba2284d..5ce6cd10fc18 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -6,6 +6,7 @@ import io import os import os.path as osp +import zipfile import pytz import shutil import traceback @@ -721,20 +722,26 @@ def __call__(self, request, start, stop, db_data): return HttpResponse(buf.getvalue(), content_type=mime) elif self.type == 'context_image': - if not (start <= self.number <= stop): - raise ValidationError('The frame number should be in ' + - f'[{start}, {stop}] range') - - image = Image.objects.get(data_id=db_data.id, frame=self.number) - for i in image.related_files.all(): - path = os.path.realpath(str(i.path)) - image = cv2.imread(path) - success, result = cv2.imencode('.JPEG', image) - if not success: - raise Exception('Failed to encode image to ".jpeg" format') - return HttpResponse(io.BytesIO(result.tobytes()), content_type='image/jpeg') - return Response(data='No context image related to the frame', - status=status.HTTP_404_NOT_FOUND) + if start <= self.number <= stop: + zip_buffer = io.BytesIO() + image = Image.objects.get(data_id=db_data.id, frame=self.number) + with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file: + if not image.related_files.count(): + return Response(data='No context image related to the frame', + status=status.HTTP_404_NOT_FOUND) + + for idx, i in enumerate(image.related_files.all()): + path = os.path.realpath(str(i.path)) + image = cv2.imread(path) + success, result = cv2.imencode('.JPEG', image) + if not success: + raise Exception('Failed to encode image to ".jpeg" format') + zip_file.writestr(f'{str(idx)}.jpg', result.tobytes()) + # response = HttpResponse(wrapper, content_type='application/zip') + # response['Content-Disposition'] = 'attachment; filename=your_zipfile.zip' + return HttpResponse(io.BytesIO(zip_buffer.getvalue()), content_type='application/zip') + raise ValidationError('The frame number should be in ' + + f'[{start}, {stop}] range') else: return Response(data='unknown data type {}.'.format(self.type), status=status.HTTP_400_BAD_REQUEST) diff --git a/yarn.lock b/yarn.lock index f6ecdb92aff5..dd37611535d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1883,7 +1883,7 @@ resolved "https://registry.yarnpkg.com/@types/polylabel/-/polylabel-1.0.5.tgz#9262f269de36f1e9248aeb9dee0ee9d10065e043" integrity sha512-gnaNmo1OJiYNBFAZMZdqLZ3hKx2ee4ksAzqhKWBxuQ61PmhINHMcvIqsGmyCD1WFKCkwRt9NFhMSmKE6AgYY+w== -"@types/prettier@2.4.1", "@types/prettier@^2.0.0", "@types/prettier@^2.1.5": +"@types/prettier@^2.0.0", "@types/prettier@^2.1.5": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== @@ -1916,14 +1916,21 @@ "@types/react" "*" "@types/reactcss" "*" -"@types/react-dom@^16.9.14", "@types/react-dom@^18.0.5": - version "18.0.6" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.6.tgz#36652900024842b74607a17786b6662dd1e103a1" - integrity sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA== +"@types/react-dom@^16.9.14": + version "16.9.17" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.17.tgz#29100cbcc422d7b7dba7de24bb906de56680dd34" + integrity sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g== + dependencies: + "@types/react" "^16" + +"@types/react-grid-layout@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz#9f195666a018a5ae2b773887e3b552cb4378d67f" + integrity sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ== dependencies: "@types/react" "*" -"@types/react-redux@^7.1.18", "@types/react-redux@^7.1.24": +"@types/react-redux@^7.1.18": version "7.1.24" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0" integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ== @@ -1940,7 +1947,7 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^5.1.9", "@types/react-router-dom@^5.3.3": +"@types/react-router-dom@^5.1.9": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== @@ -1964,7 +1971,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.14.15", "@types/react@^17.0.30": +"@types/react@*": version "17.0.48" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.48.tgz#a4532a8b91d7b27b8768b6fc0c3bccb760d15a6c" integrity sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A== @@ -1973,6 +1980,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^16", "@types/react@^16.14.15": + version "16.14.34" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.34.tgz#d129324ffda312044e1c47aab18696e4ed493282" + integrity sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/reactcss@*": version "1.2.6" resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.6.tgz#133c1e7e896f2726370d1d5a26bf06a30a038bcc" @@ -4099,10 +4115,11 @@ custom-error-instance@2.1.1: integrity sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg== "cvat-canvas3d@link:./cvat-canvas3d": - version "0.0.4" + version "0.0.6" dependencies: "@types/three" "^0.125.3" camera-controls "^1.25.3" + cvat-core "link:./cvat-core" three "^0.126.1" "cvat-canvas@link:./cvat-canvas": @@ -4119,7 +4136,7 @@ custom-error-instance@2.1.1: svg.select.js "3.0.1" "cvat-core@link:./cvat-core": - version "7.2.2" + version "7.4.0" dependencies: axios "^0.27.2" browser-or-node "^2.0.0" @@ -7907,6 +7924,11 @@ lodash.flattendeep@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== +lodash.isequal@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -10308,7 +10330,7 @@ react-dom@^16.14.0: prop-types "^15.6.2" scheduler "^0.19.1" -react-draggable@^4.0.3: +react-draggable@^4.0.0, react-draggable@^4.0.3: version "4.4.5" resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c" integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g== @@ -10316,6 +10338,17 @@ react-draggable@^4.0.3: clsx "^1.1.1" prop-types "^15.8.1" +react-grid-layout@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-1.3.4.tgz#4fa819be24a1ba9268aa11b82d63afc4762a32ff" + integrity sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw== + dependencies: + clsx "^1.1.1" + lodash.isequal "^4.0.0" + prop-types "^15.8.1" + react-draggable "^4.0.0" + react-resizable "^3.0.4" + react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" From 445534d5c609d0724d63bcb2cfbf1a0a2c808f26 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sun, 1 Jan 2023 13:19:13 -0800 Subject: [PATCH 03/47] Updated yarn lock --- yarn.lock | 46 +++++++++++++++------------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/yarn.lock b/yarn.lock index dd37611535d1..4b1e6ebb49d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1883,7 +1883,7 @@ resolved "https://registry.yarnpkg.com/@types/polylabel/-/polylabel-1.0.5.tgz#9262f269de36f1e9248aeb9dee0ee9d10065e043" integrity sha512-gnaNmo1OJiYNBFAZMZdqLZ3hKx2ee4ksAzqhKWBxuQ61PmhINHMcvIqsGmyCD1WFKCkwRt9NFhMSmKE6AgYY+w== -"@types/prettier@^2.0.0", "@types/prettier@^2.1.5": +"@types/prettier@2.4.1", "@types/prettier@^2.0.0", "@types/prettier@^2.1.5": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== @@ -1916,12 +1916,12 @@ "@types/react" "*" "@types/reactcss" "*" -"@types/react-dom@^16.9.14": - version "16.9.17" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.17.tgz#29100cbcc422d7b7dba7de24bb906de56680dd34" - integrity sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g== +"@types/react-dom@^16.9.14", "@types/react-dom@^18.0.5": + version "18.0.10" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352" + integrity sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg== dependencies: - "@types/react" "^16" + "@types/react" "*" "@types/react-grid-layout@^1.3.2": version "1.3.2" @@ -1930,24 +1930,17 @@ dependencies: "@types/react" "*" -"@types/react-redux@^7.1.18": - version "7.1.24" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0" - integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ== +"@types/react-redux@^7.1.18", "@types/react-redux@^7.1.24": + version "7.1.25" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" + integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg== dependencies: "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react-resizable@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/react-resizable/-/react-resizable-3.0.2.tgz#3c914be6b02c8d6864b82ffb6461b2e8a771fb75" - integrity sha512-4rHjZDQmSpFqRlNzlcnF5tpOG5fBcMuDlvD+qT3XHAJLKGx/FC3iDQ9li9tHW53ecWwZzHTPCGvz5vNWQN+v/Q== - dependencies: - "@types/react" "*" - -"@types/react-router-dom@^5.1.9": +"@types/react-router-dom@^5.1.9", "@types/react-router-dom@^5.3.3": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== @@ -1971,19 +1964,10 @@ dependencies: "@types/react" "*" -"@types/react@*": - version "17.0.48" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.48.tgz#a4532a8b91d7b27b8768b6fc0c3bccb760d15a6c" - integrity sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@^16", "@types/react@^16.14.15": - version "16.14.34" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.34.tgz#d129324ffda312044e1c47aab18696e4ed493282" - integrity sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA== +"@types/react@*", "@types/react@^16.14.15", "@types/react@^17.0.30": + version "17.0.52" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.52.tgz#10d8b907b5c563ac014a541f289ae8eaa9bf2e9b" + integrity sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" From 3b4c601414da22226ba19805936d732276199b88 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sun, 1 Jan 2023 23:40:58 -0800 Subject: [PATCH 04/47] Some project restructurization --- cvat-canvas3d/src/typescript/canvas3dView.ts | 5 +- .../attribute-annotation-workspace.tsx | 2 +- .../canvas/grid-layout/canvas-layout.conf.tsx | 132 +++++++ .../{ => grid-layout}/canvas-layout.tsx | 121 +------ .../styles.scss} | 2 +- .../canvas2d}/brush-toolbox-styles.scss | 2 +- .../{ => views/canvas2d}/brush-tools.tsx | 0 .../canvas2d}/canvas-context-menu.tsx | 0 .../canvas2d}/canvas-point-context-menu.tsx | 0 .../{ => views/canvas2d}/canvas-wrapper.tsx | 284 +++++++++++++-- .../{ => views/canvas2d}/draggable-hoc.tsx | 0 .../canvas2d}/image-setups-content.tsx | 0 .../{ => views/canvas3d}/canvas-wrapper3D.tsx | 180 ++++++++-- .../context-image}/context-image-selector.tsx | 0 .../context-image}/context-image.tsx | 2 +- .../context-image/styles.scss} | 16 +- .../review-workspace/review-workspace.tsx | 2 +- .../standard-workspace/standard-workspace.tsx | 4 +- .../standard3D-workspace.tsx | 4 +- .../standard3D-workspace/styles.scss | 4 +- .../tag-annotation-workspace.tsx | 2 +- .../canvas/canvas-context-menu.tsx | 2 +- .../annotation-page/canvas/canvas-wrapper.tsx | 336 ------------------ .../canvas/canvas-wrapper3D.tsx | 165 --------- 24 files changed, 571 insertions(+), 694 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx rename cvat-ui/src/components/annotation-page/canvas/{ => grid-layout}/canvas-layout.tsx (65%) rename cvat-ui/src/components/annotation-page/canvas/{grid-layout-styles.scss => grid-layout/styles.scss} (95%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/brush-toolbox-styles.scss (97%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/brush-tools.tsx (100%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/canvas-context-menu.tsx (100%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/canvas-point-context-menu.tsx (100%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/canvas-wrapper.tsx (78%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/draggable-hoc.tsx (100%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas2d}/image-setups-content.tsx (100%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/canvas3d}/canvas-wrapper3D.tsx (79%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/context-image}/context-image-selector.tsx (100%) rename cvat-ui/src/components/annotation-page/canvas/{ => views/context-image}/context-image.tsx (99%) rename cvat-ui/src/components/annotation-page/canvas/{context-image-styles.scss => views/context-image/styles.scss} (86%) delete mode 100644 cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx delete mode 100644 cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx diff --git a/cvat-canvas3d/src/typescript/canvas3dView.ts b/cvat-canvas3d/src/typescript/canvas3dView.ts index 8310bc7bd94a..211522d37ff9 100644 --- a/cvat-canvas3d/src/typescript/canvas3dView.ts +++ b/cvat-canvas3d/src/typescript/canvas3dView.ts @@ -107,6 +107,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { private cube: CuboidModel; private isPerspectiveBeingDragged: boolean; private activatedElementID: number | null; + private isCtrlDown: boolean; private drawnObjects: Record { event.preventDefault(); + this.isCtrlDown = event.ctrlKey; if (this.mode === Mode.DRAG_CANVAS) return; const canvas = this.views.perspective.renderer.domElement; const rect = canvas.getBoundingClientRect(); @@ -1385,7 +1388,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { const { x, y, z } = intersection.point; object.position.set(x, y, z); } - } else if (this.mode === Mode.IDLE && !this.isPerspectiveBeingDragged) { + } else if (this.mode === Mode.IDLE && !this.isPerspectiveBeingDragged && !this.isCtrlDown) { const { renderer } = this.views.perspective.rayCaster; const intersects = renderer.intersectObjects(this.getAllVisibleCuboids(), false); if (intersects.length !== 0) { diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx index 3f9d5d404fe5..02d2f9268035 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -6,7 +6,7 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; +import CanvasLayout from 'components/annotation-page/canvas/grid-layout/canvas-layout'; import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; export default function AttributeAnnotationWorkspace(): JSX.Element { diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx new file mode 100644 index 000000000000..8fab70d80450 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx @@ -0,0 +1,132 @@ +// Copyright (C) 2023 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +export interface ItemLayout { + viewType: ViewType; + offset: number[]; + x: number; + y: number; + w: number; + h: number; + viewIndex?: string; +} + +export enum ViewType { + CANVAS = 'canvas', + CANVAS_3D = 'canvas3D', + CANVAS_3D_TOP = 'canvas3DTop', + CANVAS_3D_SIDE = 'canvas3DSide', + CANVAS_3D_FRONT = 'canvas3DFront', + RELATED_IMAGE = 'relatedImage', +} + +const defaultLayout: { [index: string]: ItemLayout[] } = {}; +defaultLayout.CANVAS_NO_RELATED = [{ + viewType: ViewType.CANVAS, + offset: [0], + x: 0, + y: 0, + w: 12, + h: 12, +}]; + +defaultLayout.CANVAS_ONE_RELATED = [ + { ...defaultLayout.CANVAS_NO_RELATED[0], w: 9 }, { + viewType: ViewType.RELATED_IMAGE, + offset: [0, 0], + x: 9, + y: 0, + w: 3, + h: 4, + viewIndex: '1', + }, +]; + +defaultLayout.CANVAS_TWO_RELATED = [ + ...defaultLayout.CANVAS_ONE_RELATED, + { + ...defaultLayout.CANVAS_ONE_RELATED[1], + viewIndex: '2', + offset: [0, 1], + y: 3, + }, +]; + +defaultLayout.CANVAS_THREE_PLUS_RELATED = [ + ...defaultLayout.CANVAS_TWO_RELATED, + { + ...defaultLayout.CANVAS_TWO_RELATED[1], + viewIndex: '3', + offset: [0, 2], + y: 6, + }, +]; + +defaultLayout.CANVAS_3D_NO_RELATED = [{ + viewType: ViewType.CANVAS_3D, + offset: [0], + x: 0, + y: 0, + w: 12, + h: 9, +}, { + viewType: ViewType.CANVAS_3D_TOP, + offset: [0], + x: 0, + y: 9, + w: 4, + h: 3, +}, { + viewType: ViewType.CANVAS_3D_SIDE, + offset: [0], + x: 4, + y: 9, + w: 4, + h: 3, +}, { + viewType: ViewType.CANVAS_3D_FRONT, + offset: [0], + x: 8, + y: 9, + w: 4, + h: 3, +}]; + +defaultLayout.CANVAS_3D_ONE_RELATED = [ + { ...defaultLayout.CANVAS_3D_NO_RELATED[0], w: 9 }, + { ...defaultLayout.CANVAS_3D_NO_RELATED[1], w: 3 }, + { ...defaultLayout.CANVAS_3D_NO_RELATED[2], x: 3, w: 3 }, + { ...defaultLayout.CANVAS_3D_NO_RELATED[3], x: 6, w: 3 }, + { + viewType: ViewType.RELATED_IMAGE, + offset: [0, 0], + x: 9, + y: 0, + w: 3, + h: 4, + viewIndex: '1', + }, +]; + +defaultLayout.CANVAS_3D_TWO_RELATED = [ + ...defaultLayout.CANVAS_3D_ONE_RELATED, + { + ...defaultLayout.CANVAS_3D_ONE_RELATED[4], + viewIndex: '2', + offset: [0, 1], + y: 4, + }, +]; + +defaultLayout.CANVAS_3D_THREE_PLUS_RELATED = [ + ...defaultLayout.CANVAS_3D_TWO_RELATED, + { + ...defaultLayout.CANVAS_3D_TWO_RELATED[5], + viewIndex: '3', + offset: [0, 2], + y: 8, + }, +]; + +export default defaultLayout; diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx similarity index 65% rename from cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx rename to cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 389eedbc4a8c..2b18dfbbe7bf 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import './grid-layout-styles.scss'; +import './styles.scss'; import 'react-grid-layout/css/styles.css'; import React, { useCallback, useEffect, useState } from 'react'; @@ -13,125 +13,18 @@ import Layout from 'antd/lib/layout'; import { DragOutlined } from '@ant-design/icons'; import { DimensionType, CombinedState } from 'reducers'; -import CanvasWrapperComponent from 'containers/annotation-page/canvas/canvas-wrapper'; -import CanvasWrapper3DContainer from 'containers/annotation-page/canvas/canvas-wrapper3D'; -import { +import CanvasWrapperComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-wrapper'; +import CanvasWrapper3DComponent, { PerspectiveViewComponent, TopViewComponent, SideViewComponent, FrontViewComponent, -} from 'components/annotation-page/canvas/canvas-wrapper3D'; -import ContextImage from './context-image'; +} from 'components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D'; +import ContextImage from 'components/annotation-page/canvas/views/context-image/context-image'; +import defaultLayout, { ItemLayout, ViewType } from './canvas-layout.conf'; const ReactGridLayout = WidthProvider(RGL); -enum ViewType { - CANVAS = 'canvas', - CANVAS_3D = 'canvas3D', - CANVAS_3D_TOP = 'canvas3DTop', - CANVAS_3D_SIDE = 'canvas3DSide', - CANVAS_3D_FRONT = 'canvas3DFront', - RELATED_IMAGE = 'relatedImage', -} - -interface ItemLayout { - viewType: ViewType; - offset: number[]; - x: number; - y: number; - w: number; - h: number; - viewIndex?: string; -} - -const defaultLayout: { [index: string]: ItemLayout[] } = {}; -defaultLayout.CANVAS_NO_RELATED = [{ - viewType: ViewType.CANVAS, - offset: [0], - x: 0, - y: 0, - w: 12, - h: 12, -}]; - -defaultLayout.CANVAS_ONE_RELATED = [ - { ...defaultLayout.CANVAS_NO_RELATED[0], w: 9 }, { - viewType: ViewType.RELATED_IMAGE, - offset: [0], - x: 9, - y: 0, - w: 3, - h: 4, - viewIndex: '1', - }, -]; - -defaultLayout.CANVAS_TWO_RELATED = [ - ...defaultLayout.CANVAS_ONE_RELATED, - { ...defaultLayout.CANVAS_ONE_RELATED[1], y: 3, viewIndex: '2' }, -]; - -defaultLayout.CANVAS_THREE_PLUS_RELATED = [ - ...defaultLayout.CANVAS_TWO_RELATED, - { ...defaultLayout.CANVAS_TWO_RELATED[1], y: 6, viewIndex: '3' }, -]; - -defaultLayout.CANVAS_3D_NO_RELATED = [{ - viewType: ViewType.CANVAS_3D, - offset: [0], - x: 0, - y: 0, - w: 12, - h: 9, -}, { - viewType: ViewType.CANVAS_3D_TOP, - offset: [0], - x: 0, - y: 9, - w: 4, - h: 3, -}, { - viewType: ViewType.CANVAS_3D_SIDE, - offset: [0], - x: 4, - y: 9, - w: 4, - h: 3, -}, { - viewType: ViewType.CANVAS_3D_FRONT, - offset: [0], - x: 8, - y: 9, - w: 4, - h: 3, -}]; - -defaultLayout.CANVAS_3D_ONE_RELATED = [ - { ...defaultLayout.CANVAS_3D_NO_RELATED[0], w: 9 }, - { ...defaultLayout.CANVAS_3D_NO_RELATED[1], w: 3 }, - { ...defaultLayout.CANVAS_3D_NO_RELATED[2], x: 3, w: 3 }, - { ...defaultLayout.CANVAS_3D_NO_RELATED[3], x: 6, w: 3 }, - { - viewType: ViewType.RELATED_IMAGE, - offset: [0], - x: 9, - y: 0, - w: 3, - h: 4, - viewIndex: '1', - }, -]; - -defaultLayout.CANVAS_3D_TWO_RELATED = [ - ...defaultLayout.CANVAS_3D_ONE_RELATED, - { ...defaultLayout.CANVAS_3D_ONE_RELATED[4], y: 4, viewIndex: '2' }, -]; - -defaultLayout.CANVAS_3D_THREE_PLUS_RELATED = [ - ...defaultLayout.CANVAS_3D_TWO_RELATED, - { ...defaultLayout.CANVAS_3D_TWO_RELATED[5], y: 8, viewIndex: '3' }, -]; - const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { const { viewType: type, offset, viewIndex } = itemLayout; const key = typeof viewIndex !== 'undefined' ? `${type}_${viewIndex}` : `${type}`; @@ -237,7 +130,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { > { children } - { type === DimensionType.DIM_3D && } + { type === DimensionType.DIM_3D && } ); } diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss similarity index 95% rename from cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss rename to cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index c613977d784c..351b5e6d89ec 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout-styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -@import '../../../base.scss'; +@import 'base.scss'; .cvat-canvas-grid-root { position: relative; diff --git a/cvat-ui/src/components/annotation-page/canvas/brush-toolbox-styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss similarity index 97% rename from cvat-ui/src/components/annotation-page/canvas/brush-toolbox-styles.scss rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss index 31060b9afd2e..8b3b276202d0 100644 --- a/cvat-ui/src/components/annotation-page/canvas/brush-toolbox-styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -@import '../../../base.scss'; +@import 'base.scss'; .cvat-brush-tools-toolbox { position: absolute; diff --git a/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx similarity index 100% rename from cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-context-menu.tsx similarity index 100% rename from cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-context-menu.tsx diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-point-context-menu.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-point-context-menu.tsx similarity index 100% rename from cvat-ui/src/components/annotation-page/canvas/canvas-point-context-menu.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-point-context-menu.tsx diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx similarity index 78% rename from cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx index b7b83329e570..4aeeb945148a 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx @@ -4,13 +4,14 @@ // SPDX-License-Identifier: MIT import React from 'react'; +import { connect } from 'react-redux'; import Slider from 'antd/lib/slider'; import Dropdown from 'antd/lib/dropdown'; import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { - ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType, + ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType, ActiveControl, CombinedState, } from 'reducers'; import { LogType } from 'cvat-logger'; import { Canvas } from 'cvat-canvas-wrapper'; @@ -19,14 +20,46 @@ import { getCore } from 'cvat-core-wrapper'; import consts from 'consts'; import CVATTooltip from 'components/common/cvat-tooltip'; import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags'; +import { + confirmCanvasReady, + dragCanvas, + zoomCanvas, + resetCanvas, + shapeDrawn, + mergeObjects, + groupObjects, + splitTrack, + editShape, + updateAnnotationsAsync, + createAnnotationsAsync, + mergeAnnotationsAsync, + groupAnnotationsAsync, + splitAnnotationsAsync, + activateObject, + updateCanvasContextMenu, + addZLayer, + switchZLayer, + fetchAnnotationsAsync, + getDataFailed, +} from 'actions/annotation-actions'; +import { + switchGrid, + changeGridColor, + changeGridOpacity, + changeBrightnessLevel, + changeContrastLevel, + changeSaturationLevel, + switchAutomaticBordering, +} from 'actions/settings-actions'; +import { reviewActions } from 'actions/review-actions'; + import ImageSetupsContent from './image-setups-content'; import BrushTools from './brush-tools'; const cvat = getCore(); - const MAX_DISTANCE_TO_OPEN_SHAPE = 50; -interface Props { +interface StateToProps { sidebarCollapsed: boolean; canvasInstance: Canvas | Canvas3d | null; jobInstance: any; @@ -51,9 +84,6 @@ interface Props { gridOpacity: number; activeLabelID: number; activeObjectType: ObjectType; - curZLayer: number; - minZLayer: number; - maxZLayer: number; brightnessLevel: number; contrastLevel: number; saturationLevel: number; @@ -67,26 +97,32 @@ interface Props { textContent: string; showAllInterpolationTracks: boolean; workspace: Workspace; + minZLayer: number; + maxZLayer: number; + curZLayer: number; automaticBordering: boolean; intelligentPolygonCrop: boolean; - keyMap: KeyMap; switchableAutomaticBordering: boolean; + keyMap: KeyMap; showTagsOnFrame: boolean; - onSetupCanvas: () => void; +} + +interface DispatchToProps { + onSetupCanvas(): void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; + onResetCanvas: () => void; + onShapeDrawn: () => void; onMergeObjects: (enabled: boolean) => void; onGroupObjects: (enabled: boolean) => void; onSplitTrack: (enabled: boolean) => void; onEditShape: (enabled: boolean) => void; - onShapeDrawn: () => void; - onResetCanvas: () => void; onUpdateAnnotations(states: any[]): void; onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; - onActivateObject(activatedStateID: number | null, activatedElementID?: number | null): void; + onActivateObject: (activatedStateID: number | null, activatedElementID: number | null) => void; onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; @@ -102,7 +138,212 @@ interface Props { onStartIssue(position: number[]): void; } -export default class CanvasWrapperComponent extends React.PureComponent { +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + canvas: { activeControl, instance: canvasInstance }, + drawing: { activeLabelID, activeObjectType }, + job: { instance: jobInstance }, + player: { + frame: { data: frameData, number: frame, fetching: frameFetching }, + frameAngles, + }, + annotations: { + states: annotations, + activatedStateID, + activatedElementID, + activatedAttributeID, + zLayer: { cur: curZLayer, min: minZLayer, max: maxZLayer }, + }, + sidebarCollapsed, + workspace, + }, + settings: { + player: { + grid, + gridSize, + gridColor, + gridOpacity, + brightnessLevel, + contrastLevel, + saturationLevel, + resetZoom, + smoothImage, + }, + workspace: { + aamZoomMargin, + showObjectsTextAlways, + showAllInterpolationTracks, + showTagsOnFrame, + automaticBordering, + intelligentPolygonCrop, + textFontSize, + controlPointsSize, + textPosition, + textContent, + }, + shapes: { + opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, + }, + }, + shortcuts: { keyMap }, + } = state; + + return { + sidebarCollapsed, + canvasInstance, + jobInstance, + frameData, + frameAngle: frameAngles[frame - jobInstance.startFrame], + frameFetching, + frame, + activatedStateID, + activatedElementID, + activatedAttributeID, + annotations, + opacity: opacity / 100, + colorBy, + selectedOpacity: selectedOpacity / 100, + outlined, + outlineColor, + showBitmap, + showProjections, + grid, + gridSize, + gridColor, + gridOpacity: gridOpacity / 100, + activeLabelID, + activeObjectType, + brightnessLevel: brightnessLevel / 100, + contrastLevel: contrastLevel / 100, + saturationLevel: saturationLevel / 100, + resetZoom, + smoothImage, + aamZoomMargin, + showObjectsTextAlways, + textFontSize, + controlPointsSize, + textPosition, + textContent, + showAllInterpolationTracks, + showTagsOnFrame, + curZLayer, + minZLayer, + maxZLayer, + automaticBordering, + intelligentPolygonCrop, + workspace, + keyMap, + switchableAutomaticBordering: + activeControl === ActiveControl.DRAW_POLYGON || + activeControl === ActiveControl.DRAW_POLYLINE || + activeControl === ActiveControl.DRAW_MASK || + activeControl === ActiveControl.EDIT, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onSetupCanvas(): void { + dispatch(confirmCanvasReady()); + }, + onDragCanvas(enabled: boolean): void { + dispatch(dragCanvas(enabled)); + }, + onZoomCanvas(enabled: boolean): void { + dispatch(zoomCanvas(enabled)); + }, + onResetCanvas(): void { + dispatch(resetCanvas()); + }, + onShapeDrawn(): void { + dispatch(shapeDrawn()); + }, + onMergeObjects(enabled: boolean): void { + dispatch(mergeObjects(enabled)); + }, + onGroupObjects(enabled: boolean): void { + dispatch(groupObjects(enabled)); + }, + onSplitTrack(enabled: boolean): void { + dispatch(splitTrack(enabled)); + }, + onEditShape(enabled: boolean): void { + dispatch(editShape(enabled)); + }, + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); + }, + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(createAnnotationsAsync(sessionInstance, frame, states)); + }, + onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(mergeAnnotationsAsync(sessionInstance, frame, states)); + }, + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(groupAnnotationsAsync(sessionInstance, frame, states)); + }, + onSplitAnnotations(sessionInstance: any, frame: number, state: any): void { + dispatch(splitAnnotationsAsync(sessionInstance, frame, state)); + }, + onActivateObject(activatedStateID: number | null, activatedElementID: number | null = null): void { + if (activatedStateID === null) { + dispatch(updateCanvasContextMenu(false, 0, 0)); + } + + dispatch(activateObject(activatedStateID, activatedElementID, null)); + }, + onUpdateContextMenu( + visible: boolean, + left: number, + top: number, + type: ContextMenuType, + pointID?: number, + ): void { + dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); + }, + onAddZLayer(): void { + dispatch(addZLayer()); + }, + onSwitchZLayer(cur: number): void { + dispatch(switchZLayer(cur)); + }, + onChangeBrightnessLevel(level: number): void { + dispatch(changeBrightnessLevel(level)); + }, + onChangeContrastLevel(level: number): void { + dispatch(changeContrastLevel(level)); + }, + onChangeSaturationLevel(level: number): void { + dispatch(changeSaturationLevel(level)); + }, + onChangeGridOpacity(opacity: number): void { + dispatch(changeGridOpacity(opacity)); + }, + onChangeGridColor(color: GridColor): void { + dispatch(changeGridColor(color)); + }, + onSwitchGrid(enabled: boolean): void { + dispatch(switchGrid(enabled)); + }, + onSwitchAutomaticBordering(enabled: boolean): void { + dispatch(switchAutomaticBordering(enabled)); + }, + onFetchAnnotation(): void { + dispatch(fetchAnnotationsAsync()); + }, + onGetDataFailed(error: any): void { + dispatch(getDataFailed(error)); + }, + onStartIssue(position: number[]): void { + dispatch(reviewActions.startIssue(position)); + }, + }; +} + +type Props = StateToProps & DispatchToProps; + +class CanvasWrapperComponent extends React.PureComponent { public componentDidMount(): void { const { automaticBordering, @@ -308,17 +549,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - if (frameFetching !== prevProps.frameFetching) { - const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation'); - if (loadingAnimation) { - if (frameFetching) { - loadingAnimation.classList.remove('cvat_canvas_hidden'); - } else { - loadingAnimation.classList.add('cvat_canvas_hidden'); - } - } - } - this.activateOnCanvas(); } @@ -457,7 +687,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { if ((e.target as HTMLElement).tagName === 'svg' && e.button !== 2) { if (activatedStateID !== null && workspace !== Workspace.ATTRIBUTE_ANNOTATION) { - onActivateObject(null); + onActivateObject(null, null); } } }; @@ -513,7 +743,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { // and triggers this event // in this case we do not need to update our state if (state.clientID === activatedStateID) { - onActivateObject(null); + onActivateObject(null, null); } }; @@ -544,7 +774,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { private onCanvasEditStart = (): void => { const { onActivateObject, onEditShape } = this.props; - onActivateObject(null); + onActivateObject(null, null); onEditShape(true); }; @@ -815,3 +1045,5 @@ export default class CanvasWrapperComponent extends React.PureComponent { ); } } + +export default connect(mapStateToProps, mapDispatchToProps)(CanvasWrapperComponent); diff --git a/cvat-ui/src/components/annotation-page/canvas/draggable-hoc.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/draggable-hoc.tsx similarity index 100% rename from cvat-ui/src/components/annotation-page/canvas/draggable-hoc.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/draggable-hoc.tsx diff --git a/cvat-ui/src/components/annotation-page/canvas/image-setups-content.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/image-setups-content.tsx similarity index 100% rename from cvat-ui/src/components/annotation-page/canvas/image-setups-content.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas2d/image-setups-content.tsx diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx similarity index 79% rename from cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx index 4e2243102538..8552e6b61fb7 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx @@ -6,58 +6,168 @@ import React, { ReactElement, useEffect, useRef, } from 'react'; +import { connect, useSelector } from 'react-redux'; import { ArrowDownOutlined, ArrowLeftOutlined, ArrowRightOutlined, ArrowUpOutlined, } from '@ant-design/icons'; + +import { + activateObject, + confirmCanvasReady, + createAnnotationsAsync, + dragCanvas, + editShape, + groupAnnotationsAsync, + groupObjects, + resetCanvas, + shapeDrawn, + updateAnnotationsAsync, + updateCanvasContextMenu, +} from 'actions/annotation-actions'; import { ColorBy, CombinedState, ContextMenuType, ObjectType, Workspace, } from 'reducers'; -import { - CameraAction, Canvas3d, ViewsDOM, -} from 'cvat-canvas3d-wrapper'; -import { Canvas } from 'cvat-canvas-wrapper'; +import { CameraAction, Canvas3d, ViewsDOM } from 'cvat-canvas3d-wrapper'; + import CVATTooltip from 'components/common/cvat-tooltip'; import { LogType } from 'cvat-logger'; import { getCore } from 'cvat-core-wrapper'; -import { useSelector } from 'react-redux'; const cvat = getCore(); -interface Props { +interface StateToProps { opacity: number; selectedOpacity: number; outlined: boolean; outlineColor: string; colorBy: ColorBy; frameFetching: boolean; - canvasInstance: Canvas3d | Canvas; + canvasInstance: Canvas3d; jobInstance: any; frameData: any; annotations: any[]; contextMenuVisibility: boolean; - activeLabelID: number; - activeObjectType: ObjectType; + activeLabelID: number | null; activatedStateID: number | null; - onSetupCanvas: () => void; + activeObjectType: ObjectType; + workspace: Workspace; + frame: number; + resetZoom: boolean; +}; + +interface DispatchToProps { + onDragCanvas: (enabled: boolean) => void; + onSetupCanvas(): void; onGroupObjects: (enabled: boolean) => void; onResetCanvas(): void; onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onActivateObject(activatedStateID: number | null): void; onUpdateAnnotations(states: any[]): void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onEditShape: (enabled: boolean) => void; - onDragCanvas: (enabled: boolean) => void; + onActivateObject: (activatedStateID: number | null) => void; onShapeDrawn: () => void; - workspace: Workspace; - frame: number; - resetZoom: boolean; - perspectiveRenderContainer: string; - topRenderContainer: string; - sideRenderContainer: string; - frontRenderContainer: string; + onEditShape: (enabled: boolean) => void; + onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + canvas: { + instance: canvasInstance, + contextMenu: { visible: contextMenuVisibility }, + }, + drawing: { activeLabelID, activeObjectType }, + job: { instance: jobInstance }, + player: { + frame: { data: frameData, number: frame, fetching: frameFetching }, + }, + annotations: { + states: annotations, + activatedStateID, + }, + workspace, + }, + settings: { + player: { + resetZoom, + }, + shapes: { + opacity, colorBy, selectedOpacity, outlined, outlineColor, + }, + }, + } = state; + + return { + canvasInstance: canvasInstance as Canvas3d, + jobInstance, + frameData, + contextMenuVisibility, + annotations, + frameFetching, + frame, + opacity, + colorBy, + selectedOpacity, + outlined, + outlineColor, + activeLabelID, + activatedStateID, + activeObjectType, + resetZoom, + workspace, + }; } +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onDragCanvas(enabled: boolean): void { + dispatch(dragCanvas(enabled)); + }, + onSetupCanvas(): void { + dispatch(confirmCanvasReady()); + }, + onResetCanvas(): void { + dispatch(resetCanvas()); + }, + onGroupObjects(enabled: boolean): void { + dispatch(groupObjects(enabled)); + }, + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(createAnnotationsAsync(sessionInstance, frame, states)); + }, + onShapeDrawn(): void { + dispatch(shapeDrawn()); + }, + onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(groupAnnotationsAsync(sessionInstance, frame, states)); + }, + onActivateObject(activatedStateID: number | null): void { + if (activatedStateID === null) { + dispatch(updateCanvasContextMenu(false, 0, 0)); + } + + dispatch(activateObject(activatedStateID, null, null)); + }, + onEditShape(enabled: boolean): void { + dispatch(editShape(enabled)); + }, + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); + }, + onUpdateContextMenu( + visible: boolean, + left: number, + top: number, + type: ContextMenuType, + pointID?: number, + ): void { + dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); + }, + }; +} + +type Props = StateToProps & DispatchToProps; + export const PerspectiveViewComponent = React.memo( (): JSX.Element => { const ref = useRef(null); @@ -178,11 +288,11 @@ export const PerspectiveViewComponent = React.memo( }, []); return ( -
+
{ frameFetching && ( - - + + ) } @@ -193,8 +303,8 @@ export const PerspectiveViewComponent = React.memo( visibility: frameFetching ? 'hidden' : undefined, }} /> - { ArrowGroup } - { ControlGroup } + +
); }, @@ -216,8 +326,8 @@ export const TopViewComponent = React.memo(
{ frameFetching && ( - - + + ) } @@ -250,8 +360,8 @@ export const SideViewComponent = React.memo(
{ frameFetching && ( - - + + ) } @@ -284,8 +394,8 @@ export const FrontViewComponent = React.memo(
{ frameFetching && ( - - + + ) } @@ -302,7 +412,7 @@ export const FrontViewComponent = React.memo( }, ); -const CanvasWrapperComponent = (props: Props): ReactElement => { +const Canvas3DWrapperComponent = React.memo((props: Props): ReactElement => { const animateId = useRef(0); const { @@ -506,6 +616,6 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { }, [frameData, annotations, activeLabelID, contextMenuVisibility]); return <>; -}; +}); -export default React.memo(CanvasWrapperComponent); +export default connect(mapStateToProps, mapDispatchToProps)(Canvas3DWrapperComponent); diff --git a/cvat-ui/src/components/annotation-page/canvas/context-image-selector.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx similarity index 100% rename from cvat-ui/src/components/annotation-page/canvas/context-image-selector.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx diff --git a/cvat-ui/src/components/annotation-page/canvas/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx similarity index 99% rename from cvat-ui/src/components/annotation-page/canvas/context-image.tsx rename to cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index 97c5c05c86d9..22197022e88d 100644 --- a/cvat-ui/src/components/annotation-page/canvas/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import './context-image-styles.scss'; +import './styles.scss'; import React, { useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; diff --git a/cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss similarity index 86% rename from cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss rename to cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 6bf364c4d22e..72207d88c2cc 100644 --- a/cvat-ui/src/components/annotation-page/canvas/context-image-styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -@import '../../../base.scss'; +@import 'base.scss'; .cvat-context-image-wrapper { width: 100%; @@ -38,7 +38,17 @@ position: absolute; top: $grid-unit-size; right: $grid-unit-size; - font-size: 24px; + font-size: 16px; + opacity: 0; + background: #d8d8d8; + border-radius: 2px; + transition: all 200ms; + } + + &:hover { + > .cvat-context-image-setup-button { + opacity: 1; + } } } @@ -78,7 +88,5 @@ opacity: 0.6; width: 25%; } - - } } diff --git a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx index 4a75ed5adf21..7ddd6abb930f 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx @@ -7,7 +7,7 @@ import React from 'react'; import Layout from 'antd/lib/layout'; import ControlsSideBarContainer from 'containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar'; -import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; +import CanvasLayout from 'components/annotation-page/canvas/grid-layout/canvas-layout'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index 963216cc076e..a3af6bec1216 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -7,12 +7,12 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; +import CanvasLayout from 'components/annotation-page/canvas/grid-layout/canvas-layout'; import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; -import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; +import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-point-context-menu'; import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index 10b528ebebce..ebde847cbcbe 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -11,8 +11,8 @@ import ControlsSideBarContainer from 'containers/annotation-page/standard3D-work import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; -import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; -import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; +import CanvasLayout from 'components/annotation-page/canvas/grid-layout/canvas-layout'; +import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-point-context-menu'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss index 351f09d38690..b2a09702705b 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss @@ -89,14 +89,14 @@ vertical-align: middle; } -#cvat_canvas_loading_animation { +.cvat_canvas_loading_animation { z-index: 1; position: absolute; width: 100%; height: 100%; } -#cvat_canvas_loading_circle { +.cvat_canvas_loading_circle { fill-opacity: 0; stroke: #09c; stroke-width: 3px; diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx index cd7473e98a22..4038e382f01d 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx @@ -6,7 +6,7 @@ import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; -import CanvasLayout from 'components/annotation-page/canvas/canvas-layout'; +import CanvasLayout from 'components/annotation-page/canvas/grid-layout/canvas-layout'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; import TagAnnotationSidebar from './tag-annotation-sidebar/tag-annotation-sidebar'; diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx index 748b75c6f10c..a523ce7d8895 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx @@ -11,7 +11,7 @@ import { CombinedState, ContextMenuType, ShapeType, Workspace, } from 'reducers'; -import CanvasContextMenuComponent from 'components/annotation-page/canvas/canvas-context-menu'; +import CanvasContextMenuComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-context-menu'; import { updateCanvasContextMenu } from 'actions/annotation-actions'; import { reviewActions, finishIssueAsync } from 'actions/review-actions'; import { ThunkDispatch } from 'utils/redux'; diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx deleted file mode 100644 index 189414c86b86..000000000000 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (C) 2020-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import { connect } from 'react-redux'; -import { KeyMap } from 'utils/mousetrap-react'; - -import CanvasWrapperComponent from 'components/annotation-page/canvas/canvas-wrapper'; -import { - confirmCanvasReady, - dragCanvas, - zoomCanvas, - resetCanvas, - shapeDrawn, - mergeObjects, - groupObjects, - splitTrack, - editShape, - updateAnnotationsAsync, - createAnnotationsAsync, - mergeAnnotationsAsync, - groupAnnotationsAsync, - splitAnnotationsAsync, - activateObject, - updateCanvasContextMenu, - addZLayer, - switchZLayer, - fetchAnnotationsAsync, - getDataFailed, -} from 'actions/annotation-actions'; -import { - switchGrid, - changeGridColor, - changeGridOpacity, - changeBrightnessLevel, - changeContrastLevel, - changeSaturationLevel, - switchAutomaticBordering, -} from 'actions/settings-actions'; -import { reviewActions } from 'actions/review-actions'; -import { - ColorBy, - GridColor, - ObjectType, - CombinedState, - ContextMenuType, - Workspace, - ActiveControl, -} from 'reducers'; - -import { Canvas } from 'cvat-canvas-wrapper'; -import { Canvas3d } from 'cvat-canvas3d-wrapper'; - -interface StateToProps { - sidebarCollapsed: boolean; - canvasInstance: Canvas | Canvas3d | null; - jobInstance: any; - activatedStateID: number | null; - activatedElementID: number | null; - activatedAttributeID: number | null; - annotations: any[]; - frameData: any; - frameAngle: number; - frameFetching: boolean; - frame: number; - opacity: number; - colorBy: ColorBy; - selectedOpacity: number; - outlined: boolean; - outlineColor: string; - showBitmap: boolean; - showProjections: boolean; - grid: boolean; - gridSize: number; - gridColor: GridColor; - gridOpacity: number; - activeLabelID: number; - activeObjectType: ObjectType; - brightnessLevel: number; - contrastLevel: number; - saturationLevel: number; - resetZoom: boolean; - smoothImage: boolean; - aamZoomMargin: number; - showObjectsTextAlways: boolean; - textFontSize: number; - controlPointsSize: number; - textPosition: 'auto' | 'center'; - textContent: string; - showAllInterpolationTracks: boolean; - workspace: Workspace; - minZLayer: number; - maxZLayer: number; - curZLayer: number; - automaticBordering: boolean; - intelligentPolygonCrop: boolean; - switchableAutomaticBordering: boolean; - keyMap: KeyMap; - showTagsOnFrame: boolean; -} - -interface DispatchToProps { - onSetupCanvas(): void; - onDragCanvas: (enabled: boolean) => void; - onZoomCanvas: (enabled: boolean) => void; - onResetCanvas: () => void; - onShapeDrawn: () => void; - onMergeObjects: (enabled: boolean) => void; - onGroupObjects: (enabled: boolean) => void; - onSplitTrack: (enabled: boolean) => void; - onEditShape: (enabled: boolean) => void; - onUpdateAnnotations(states: any[]): void; - onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; - onActivateObject: (activatedStateID: number | null, activatedElementID: number | null) => void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; - onAddZLayer(): void; - onSwitchZLayer(cur: number): void; - onChangeBrightnessLevel(level: number): void; - onChangeContrastLevel(level: number): void; - onChangeSaturationLevel(level: number): void; - onChangeGridOpacity(opacity: number): void; - onChangeGridColor(color: GridColor): void; - onSwitchGrid(enabled: boolean): void; - onSwitchAutomaticBordering(enabled: boolean): void; - onFetchAnnotation(): void; - onGetDataFailed(error: any): void; - onStartIssue(position: number[]): void; -} - -function mapStateToProps(state: CombinedState): StateToProps { - const { - annotation: { - canvas: { activeControl, instance: canvasInstance }, - drawing: { activeLabelID, activeObjectType }, - job: { instance: jobInstance }, - player: { - frame: { data: frameData, number: frame, fetching: frameFetching }, - frameAngles, - }, - annotations: { - states: annotations, - activatedStateID, - activatedElementID, - activatedAttributeID, - zLayer: { cur: curZLayer, min: minZLayer, max: maxZLayer }, - }, - sidebarCollapsed, - workspace, - }, - settings: { - player: { - grid, - gridSize, - gridColor, - gridOpacity, - brightnessLevel, - contrastLevel, - saturationLevel, - resetZoom, - smoothImage, - }, - workspace: { - aamZoomMargin, - showObjectsTextAlways, - showAllInterpolationTracks, - showTagsOnFrame, - automaticBordering, - intelligentPolygonCrop, - textFontSize, - controlPointsSize, - textPosition, - textContent, - }, - shapes: { - opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, - }, - }, - shortcuts: { keyMap }, - } = state; - - return { - sidebarCollapsed, - canvasInstance, - jobInstance, - frameData, - frameAngle: frameAngles[frame - jobInstance.startFrame], - frameFetching, - frame, - activatedStateID, - activatedElementID, - activatedAttributeID, - annotations, - opacity: opacity / 100, - colorBy, - selectedOpacity: selectedOpacity / 100, - outlined, - outlineColor, - showBitmap, - showProjections, - grid, - gridSize, - gridColor, - gridOpacity: gridOpacity / 100, - activeLabelID, - activeObjectType, - brightnessLevel: brightnessLevel / 100, - contrastLevel: contrastLevel / 100, - saturationLevel: saturationLevel / 100, - resetZoom, - smoothImage, - aamZoomMargin, - showObjectsTextAlways, - textFontSize, - controlPointsSize, - textPosition, - textContent, - showAllInterpolationTracks, - showTagsOnFrame, - curZLayer, - minZLayer, - maxZLayer, - automaticBordering, - intelligentPolygonCrop, - workspace, - keyMap, - switchableAutomaticBordering: - activeControl === ActiveControl.DRAW_POLYGON || - activeControl === ActiveControl.DRAW_POLYLINE || - activeControl === ActiveControl.DRAW_MASK || - activeControl === ActiveControl.EDIT, - }; -} - -function mapDispatchToProps(dispatch: any): DispatchToProps { - return { - onSetupCanvas(): void { - dispatch(confirmCanvasReady()); - }, - onDragCanvas(enabled: boolean): void { - dispatch(dragCanvas(enabled)); - }, - onZoomCanvas(enabled: boolean): void { - dispatch(zoomCanvas(enabled)); - }, - onResetCanvas(): void { - dispatch(resetCanvas()); - }, - onShapeDrawn(): void { - dispatch(shapeDrawn()); - }, - onMergeObjects(enabled: boolean): void { - dispatch(mergeObjects(enabled)); - }, - onGroupObjects(enabled: boolean): void { - dispatch(groupObjects(enabled)); - }, - onSplitTrack(enabled: boolean): void { - dispatch(splitTrack(enabled)); - }, - onEditShape(enabled: boolean): void { - dispatch(editShape(enabled)); - }, - onUpdateAnnotations(states: any[]): void { - dispatch(updateAnnotationsAsync(states)); - }, - onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(createAnnotationsAsync(sessionInstance, frame, states)); - }, - onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(mergeAnnotationsAsync(sessionInstance, frame, states)); - }, - onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(groupAnnotationsAsync(sessionInstance, frame, states)); - }, - onSplitAnnotations(sessionInstance: any, frame: number, state: any): void { - dispatch(splitAnnotationsAsync(sessionInstance, frame, state)); - }, - onActivateObject(activatedStateID: number | null, activatedElementID: number | null = null): void { - if (activatedStateID === null) { - dispatch(updateCanvasContextMenu(false, 0, 0)); - } - - dispatch(activateObject(activatedStateID, activatedElementID, null)); - }, - onUpdateContextMenu( - visible: boolean, - left: number, - top: number, - type: ContextMenuType, - pointID?: number, - ): void { - dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); - }, - onAddZLayer(): void { - dispatch(addZLayer()); - }, - onSwitchZLayer(cur: number): void { - dispatch(switchZLayer(cur)); - }, - onChangeBrightnessLevel(level: number): void { - dispatch(changeBrightnessLevel(level)); - }, - onChangeContrastLevel(level: number): void { - dispatch(changeContrastLevel(level)); - }, - onChangeSaturationLevel(level: number): void { - dispatch(changeSaturationLevel(level)); - }, - onChangeGridOpacity(opacity: number): void { - dispatch(changeGridOpacity(opacity)); - }, - onChangeGridColor(color: GridColor): void { - dispatch(changeGridColor(color)); - }, - onSwitchGrid(enabled: boolean): void { - dispatch(switchGrid(enabled)); - }, - onSwitchAutomaticBordering(enabled: boolean): void { - dispatch(switchAutomaticBordering(enabled)); - }, - onFetchAnnotation(): void { - dispatch(fetchAnnotationsAsync()); - }, - onGetDataFailed(error: any): void { - dispatch(getDataFailed(error)); - }, - onStartIssue(position: number[]): void { - dispatch(reviewActions.startIssue(position)); - }, - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(CanvasWrapperComponent); diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx deleted file mode 100644 index 108017ed9686..000000000000 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import { connect } from 'react-redux'; - -import CanvasWrapperComponent from 'components/annotation-page/canvas/canvas-wrapper3D'; -import { - activateObject, - confirmCanvasReady, - createAnnotationsAsync, - dragCanvas, - editShape, - groupAnnotationsAsync, - groupObjects, - resetCanvas, - shapeDrawn, - updateAnnotationsAsync, - updateCanvasContextMenu, -} from 'actions/annotation-actions'; - -import { - ColorBy, - CombinedState, - ContextMenuType, - ObjectType, - Workspace, -} from 'reducers'; - -import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import { Canvas } from 'cvat-canvas-wrapper'; - -type StateToProps = { - opacity: number; - selectedOpacity: number; - outlined: boolean; - outlineColor: string; - colorBy: ColorBy; - frameFetching: boolean; - canvasInstance: Canvas3d | Canvas; - jobInstance: any; - frameData: any; - annotations: any[]; - contextMenuVisibility: boolean; - activeLabelID: number; - activatedStateID: number | null; - activeObjectType: ObjectType; - workspace: Workspace; - frame: number; - resetZoom: boolean; -}; - -interface DispatchToProps { - onDragCanvas: (enabled: boolean) => void; - onSetupCanvas(): void; - onGroupObjects: (enabled: boolean) => void; - onResetCanvas(): void; - onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onUpdateAnnotations(states: any[]): void; - onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; - onActivateObject: (activatedStateID: number | null) => void; - onShapeDrawn: () => void; - onEditShape: (enabled: boolean) => void; - onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; -} - -function mapStateToProps(state: CombinedState): StateToProps { - const { - annotation: { - canvas: { - instance: canvasInstance, - contextMenu: { visible: contextMenuVisibility }, - }, - drawing: { activeLabelID, activeObjectType }, - job: { instance: jobInstance }, - player: { - frame: { data: frameData, number: frame, fetching: frameFetching }, - }, - annotations: { - states: annotations, - activatedStateID, - }, - workspace, - }, - settings: { - player: { - resetZoom, - }, - shapes: { - opacity, colorBy, selectedOpacity, outlined, outlineColor, - }, - }, - } = state; - - return { - canvasInstance, - jobInstance, - frameData, - contextMenuVisibility, - annotations, - frameFetching, - frame, - opacity, - colorBy, - selectedOpacity, - outlined, - outlineColor, - activeLabelID, - activatedStateID, - activeObjectType, - resetZoom, - workspace, - }; -} - -function mapDispatchToProps(dispatch: any): DispatchToProps { - return { - onDragCanvas(enabled: boolean): void { - dispatch(dragCanvas(enabled)); - }, - onSetupCanvas(): void { - dispatch(confirmCanvasReady()); - }, - onResetCanvas(): void { - dispatch(resetCanvas()); - }, - onGroupObjects(enabled: boolean): void { - dispatch(groupObjects(enabled)); - }, - onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(createAnnotationsAsync(sessionInstance, frame, states)); - }, - onShapeDrawn(): void { - dispatch(shapeDrawn()); - }, - onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(groupAnnotationsAsync(sessionInstance, frame, states)); - }, - onActivateObject(activatedStateID: number | null): void { - if (activatedStateID === null) { - dispatch(updateCanvasContextMenu(false, 0, 0)); - } - - dispatch(activateObject(activatedStateID, null, null)); - }, - onEditShape(enabled: boolean): void { - dispatch(editShape(enabled)); - }, - onUpdateAnnotations(states: any[]): void { - dispatch(updateAnnotationsAsync(states)); - }, - onUpdateContextMenu( - visible: boolean, - left: number, - top: number, - type: ContextMenuType, - pointID?: number, - ): void { - dispatch(updateCanvasContextMenu(visible, left, top, pointID, type)); - }, - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(CanvasWrapperComponent); From 14be381708da9c63b93b4a6458a5ac815b381080 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Jan 2023 00:18:10 -0800 Subject: [PATCH 05/47] Added context headers --- cvat-canvas/src/scss/canvas.scss | 4 +-- cvat-canvas/src/typescript/canvasView.ts | 4 +-- .../canvas/grid-layout/styles.scss | 4 +-- .../views/canvas3d/canvas-wrapper3D.tsx | 1 + .../views/canvas3d}/styles.scss | 25 ++------------ .../views/context-image/context-image.tsx | 30 ++++++++-------- .../canvas/views/context-image/styles.scss | 34 ++++++++++++------- .../standard3D-workspace.tsx | 1 - 8 files changed, 45 insertions(+), 58 deletions(-) rename cvat-ui/src/components/annotation-page/{standard3D-workspace => canvas/views/canvas3d}/styles.scss (82%) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 556ba4a337de..d9dd2a4fb6b4 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -290,14 +290,14 @@ g.cvat_canvas_shape_occluded { position: relative; } -#cvat_canvas_loading_animation { +.cvat_canvas_loading_animation { z-index: 1; position: absolute; width: 100%; height: 100%; } -#cvat_canvas_loading_circle { +.cvat_canvas_loading_circle { fill-opacity: 0; stroke: #09c; stroke-width: 3px; diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 1eeafbf1ee59..7e52e50b354b 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1130,8 +1130,8 @@ export class CanvasViewImpl implements CanvasView, Listener { }); // Setup loading animation - this.loadingAnimation.setAttribute('id', 'cvat_canvas_loading_animation'); - loadingCircle.setAttribute('id', 'cvat_canvas_loading_circle'); + this.loadingAnimation.classList.add('cvat_canvas_loading_animation'); + loadingCircle.classList.add('cvat_canvas_loading_circle'); loadingCircle.setAttribute('r', '30'); loadingCircle.setAttribute('cx', '50%'); loadingCircle.setAttribute('cy', '50%'); diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index 351b5e6d89ec..3a09d5bf0cde 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -19,7 +19,7 @@ z-index: 1000; cursor: move; font-size: 16px; - background: #d8d8d8; + background: $header-color; border-radius: 2px; } @@ -27,7 +27,7 @@ bottom: 0; right: 0; cursor: se-resize; - background: #d8d8d8; + background: $header-color; border-radius: $grid-unit-size * 4 0 0 0; } } 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 8552e6b61fb7..0c5536a29ddc 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 @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: MIT +import './styles.scss'; import React, { ReactElement, useEffect, useRef, } from 'react'; diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss similarity index 82% rename from cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss rename to cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss index b2a09702705b..ee4de51cb326 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss @@ -1,15 +1,9 @@ -// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @import 'base.scss'; -.cvat-canvas-container-overflow { - overflow: hidden; - width: 100%; - height: 100%; -} - .cvat-canvas3d-perspective { height: 100%; width: 100%; @@ -89,21 +83,8 @@ vertical-align: middle; } -.cvat_canvas_loading_animation { - z-index: 1; - position: absolute; +.cvat-canvas-container-overflow { + overflow: hidden; width: 100%; height: 100%; } - -.cvat_canvas_loading_circle { - fill-opacity: 0; - stroke: #09c; - stroke-width: 3px; - stroke-dasharray: 50; - animation: loadingAnimation 1s linear infinite; -} - -.cvat_canvas_hidden { - display: none; -} diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index 22197022e88d..be5b61d6f8b6 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -9,6 +9,7 @@ import PropTypes from 'prop-types'; import notification from 'antd/lib/notification'; import Spin from 'antd/lib/spin'; import Empty from 'antd/lib/empty'; +import Text from 'antd/lib/typography/Text'; import { SettingOutlined } from '@ant-design/icons'; import { CombinedState } from 'reducers'; @@ -70,24 +71,21 @@ function ContextImage(props: Props): JSX.Element { return (
+
+ Context image + {contextImageOffset} + { relatedFiles > 1 && ( + { + setShowSelector(true); + }} + /> + )} +
{ hasError && } { relatedFiles && contextImageData.length === 0 && !hasError && } - { contextImageData.length > 0 && ( - <> - - { relatedFiles > 1 && ( - { - setShowSelector(true); - }} - /> - )} - - )} + { contextImageData.length > 0 && } { showSelector && ( .cvat-context-image-setup-button { + position: absolute; + top: $grid-unit-size; + right: $grid-unit-size; + font-size: 16px; + opacity: 0; + border-radius: 2px; + transition: all 200ms; + } + } + > canvas { object-fit: contain; position: relative; top: 50%; transform: translateY(-50%); width: 100%; - height: 100%; - } - - > .cvat-context-image-setup-button { - position: absolute; - top: $grid-unit-size; - right: $grid-unit-size; - font-size: 16px; - opacity: 0; - background: #d8d8d8; - border-radius: 2px; - transition: all 200ms; + height: calc(100% - $grid-unit-size * 4); } &:hover { - > .cvat-context-image-setup-button { + > .cvat-context-image-header > .cvat-context-image-setup-button { opacity: 1; } } diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index ebde847cbcbe..27712cb6ae46 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -import './styles.scss'; import React from 'react'; import Layout from 'antd/lib/layout'; From 8e0b1a33138df7424388194f8b8e1ebdab65580b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Jan 2023 22:52:40 -0800 Subject: [PATCH 06/47] Updated license headers --- cvat-canvas/src/scss/canvas.scss | 2 +- cvat-canvas/src/typescript/canvasView.ts | 2 +- cvat-canvas3d/src/typescript/canvas3dView.ts | 2 +- cvat-core/src/frames.ts | 3 +-- cvat-core/src/server-proxy.ts | 2 +- cvat-data/src/ts/cvat-data.ts | 1 + cvat-data/src/ts/unzip_imgs.worker.js | 1 + cvat-ui/src/actions/annotation-actions.ts | 2 +- cvat-ui/src/actions/boundaries-actions.ts | 2 +- cvat-ui/src/components/annotation-page/annotation-page.tsx | 1 + .../attribute-annotation-sidebar.tsx | 2 +- .../attribute-annotation-workspace.tsx | 1 + .../annotation-page/canvas/grid-layout/canvas-layout.tsx | 2 +- .../components/annotation-page/canvas/grid-layout/styles.scss | 2 +- .../canvas/views/canvas2d/brush-toolbox-styles.scss | 2 +- .../annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx | 2 +- .../annotation-page/canvas/views/canvas3d/canvas-wrapper3D.tsx | 2 +- .../annotation-page/canvas/views/canvas3d/styles.scss | 1 + .../canvas/views/context-image/context-image-selector.tsx | 2 +- .../canvas/views/context-image/context-image.tsx | 2 +- .../annotation-page/canvas/views/context-image/styles.scss | 2 +- .../annotation-page/review-workspace/review-workspace.tsx | 1 + .../standard-workspace/objects-side-bar/objects-side-bar.tsx | 1 + .../annotation-page/standard-workspace/standard-workspace.tsx | 2 +- .../components/annotation-page/standard-workspace/styles.scss | 2 +- .../standard3D-workspace/standard3D-workspace.tsx | 1 + cvat-ui/src/components/annotation-page/styles.scss | 3 ++- .../tag-annotation-sidebar/tag-annotation-sidebar.tsx | 2 +- .../tag-annotation-workspace/tag-annotation-workspace.tsx | 1 + cvat-ui/src/components/annotation-page/top-bar/left-group.tsx | 1 + cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx | 1 + .../containers/annotation-page/canvas/canvas-context-menu.tsx | 2 +- cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx | 1 + cvat-ui/src/reducers/annotation-reducer.ts | 2 +- cvat-ui/src/reducers/index.ts | 2 +- cvat-ui/src/reducers/notifications-reducer.ts | 2 +- cvat/apps/engine/serializers.py | 2 +- cvat/apps/engine/views.py | 2 +- 38 files changed, 39 insertions(+), 27 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index d9dd2a4fb6b4..4008aed90812 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 7e52e50b354b..a92865125c33 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1,5 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas3d/src/typescript/canvas3dView.ts b/cvat-canvas3d/src/typescript/canvas3dView.ts index 211522d37ff9..bb2ce1e55ef6 100644 --- a/cvat-canvas3d/src/typescript/canvas3dView.ts +++ b/cvat-canvas3d/src/typescript/canvas3dView.ts @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 1fd71bc6f473..37845acf5b9f 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,6 @@ import { isBrowser, isNode } from 'browser-or-node'; import PluginRegistry from './plugins'; import serverProxy from './server-proxy'; import { Exception, ArgumentError, DataError } from './exceptions'; -import { rejects } from 'assert'; // This is the frames storage const frameDataCache = {}; diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts index 3c797ff87992..1bc830b4e7bd 100644 --- a/cvat-core/src/server-proxy.ts +++ b/cvat-core/src/server-proxy.ts @@ -1,5 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index ceb6dbfd0b62..4fce6293b2b0 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-data/src/ts/unzip_imgs.worker.js b/cvat-data/src/ts/unzip_imgs.worker.js index fd20ede6da77..4c8bcabce190 100644 --- a/cvat-data/src/ts/unzip_imgs.worker.js +++ b/cvat-data/src/ts/unzip_imgs.worker.js @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 5a199a8803b3..4effce3319d2 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/actions/boundaries-actions.ts b/cvat-ui/src/actions/boundaries-actions.ts index fabea9fe9f8a..4ebec2168ac2 100644 --- a/cvat-ui/src/actions/boundaries-actions.ts +++ b/cvat-ui/src/actions/boundaries-actions.ts @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index ab784ea0106d..e8ff662cd9d8 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index c280b164ae65..0ea6568723b2 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx index 02d2f9268035..6cfe835651c8 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 2b18dfbbe7bf..66b7047d4d2e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index 3a09d5bf0cde..762f612d7be0 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss index 8b3b276202d0..488c561390f3 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-toolbox-styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT 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 4aeeb945148a..1d01b541f311 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 @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT 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 0c5536a29ddc..4aa84e54018c 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 @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss index ee4de51cb326..4414ecb73244 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas3d/styles.scss @@ -1,3 +1,4 @@ +// Copyright (C) 2020-2022 Intel Corporation // Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx index bf2d7f2ac164..3a839a37df2c 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index be5b61d6f8b6..fdde14f33688 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 1b047e5b96e0..6d22286443b5 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx index 7ddd6abb930f..bd75dd1409a2 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index dfe39ce695ac..0a5bebadea47 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index a3af6bec1216..3c90aa95d011 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporations +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 5646cd895a11..95f87823ff13 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index 27712cb6ae46..899c4912fdbd 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 9aef9f46412b..1c236bca366a 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -524,4 +525,4 @@ button.cvat-predictor-button { span.anticon { margin-left: $grid-unit-size * 2; } -} \ No newline at end of file +} diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index 55aa362964b3..cfa782039104 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx index 4038e382f01d..ed1b1767826d 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-workspace.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx index c35166b67836..5ddfb5adbbff 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/left-group.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT 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 5df047714f4c..3d66e7ba0abe 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 @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx index a523ce7d8895..4d88eef04e3e 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT 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 876ba09e74c4..1f143ac818b5 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 @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 17193033c1ce..c704d49361ef 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 3ee15237ffac..a3195df83fad 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index b66c007df5e6..9c26adc5d834 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index dfc5ca493613..2bb3003a8f28 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -1,5 +1,5 @@ # Copyright (C) 2019-2022 Intel Corporation -# Copyright (C) 2022 CVAT.ai Corporation +# Copyright (C) 2022-2023 CVAT.ai Corporation # # SPDX-License-Identifier: MIT diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 5ce6cd10fc18..d1d751c16236 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -1,5 +1,5 @@ # Copyright (C) 2018-2022 Intel Corporation -# Copyright (C) 2022 CVAT.ai Corporation +# Copyright (C) 2022-2023 CVAT.ai Corporation # # SPDX-License-Identifier: MIT From 6a12162d6a08af2d9e530120999ce0e862b4a68d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Jan 2023 23:00:23 -0800 Subject: [PATCH 07/47] Fixed stylelint issues --- .../annotation-page/canvas/views/context-image/styles.scss | 4 ++-- cvat-ui/src/components/annotation-page/styles.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 6d22286443b5..2b43f4f94560 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -54,7 +54,7 @@ } &:hover { - > .cvat-context-image-header > .cvat-context-image-setup-button { + > .cvat-context-image-header > .cvat-context-image-setup-button { opacity: 1; } } @@ -66,7 +66,7 @@ top: 0; left: 0; z-index: 1000; - background: rgba(255,255,255,0.25); + background: rgba(255, 255, 255, 0.25); position: absolute; display: flex; justify-content: space-around; diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 1c236bca366a..f776f43bd766 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -182,8 +182,8 @@ button.cvat-predictor-button { .cvat-player-filename-wrapper { max-width: $grid-unit-size * 30; - max-height: 27px; - line-height: 27px; + max-height: $grid-unit-size * 3; + line-height: $grid-unit-size * 3; overflow: hidden; text-overflow: ellipsis; user-select: none; From 270fe3d58236c6a86b5efea36f707d772c4b4040 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 03:15:25 -0800 Subject: [PATCH 08/47] Refactored cvat-data --- cvat-canvas/src/scss/canvas.scss | 15 - cvat-canvas/src/typescript/canvasView.ts | 18 +- cvat-core/src/frames.ts | 2 +- cvat-core/src/server-proxy.ts | 2 +- cvat-data/src/ts/cvat-data.ts | 400 +++++++++--------- cvat-data/src/ts/unzip_imgs.worker.js | 5 +- .../canvas/views/canvas2d/canvas-wrapper.tsx | 18 +- .../views/canvas3d/canvas-wrapper3D.tsx | 61 +-- 8 files changed, 245 insertions(+), 276 deletions(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 4008aed90812..a6723d59b632 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -290,21 +290,6 @@ g.cvat_canvas_shape_occluded { position: relative; } -.cvat_canvas_loading_animation { - z-index: 1; - position: absolute; - width: 100%; - height: 100%; -} - -.cvat_canvas_loading_circle { - fill-opacity: 0; - stroke: #09c; - stroke-width: 3px; - stroke-dasharray: 50; - animation: loadingAnimation 1s linear infinite; -} - #cvat_canvas_text_content { text-rendering: optimizeSpeed; position: absolute; diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index a92865125c33..e9f057f79baf 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -66,7 +66,6 @@ export interface CanvasView { } export class CanvasViewImpl implements CanvasView, Listener { - private loadingAnimation: SVGSVGElement; private text: SVGSVGElement; private adoptedText: SVG.Container; private background: HTMLCanvasElement; @@ -1082,7 +1081,6 @@ export class CanvasViewImpl implements CanvasView, Listener { }; // Create HTML elements - this.loadingAnimation = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.adoptedText = SVG.adopt((this.text as any) as HTMLElement) as SVG.Container; this.background = window.document.createElement('canvas'); @@ -1101,8 +1099,6 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas = window.document.createElement('div'); - const loadingCircle: SVGCircleElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - const gridDefs: SVGDefsElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'defs'); const gridRect: SVGRectElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'rect'); @@ -1129,13 +1125,6 @@ export class CanvasViewImpl implements CanvasView, Listener { patternUnits: 'userSpaceOnUse', }); - // Setup loading animation - this.loadingAnimation.classList.add('cvat_canvas_loading_animation'); - loadingCircle.classList.add('cvat_canvas_loading_circle'); - loadingCircle.setAttribute('r', '30'); - loadingCircle.setAttribute('cx', '50%'); - loadingCircle.setAttribute('cy', '50%'); - // Setup grid this.grid.setAttribute('id', 'cvat_canvas_grid'); this.grid.setAttribute('version', '2'); @@ -1166,14 +1155,12 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.setAttribute('id', 'cvat_canvas_wrapper'); // Unite created HTML elements together - this.loadingAnimation.appendChild(loadingCircle); this.grid.appendChild(gridDefs); this.grid.appendChild(gridRect); gridDefs.appendChild(this.gridPattern); this.gridPattern.appendChild(this.gridPath); - this.canvas.appendChild(this.loadingAnimation); this.canvas.appendChild(this.text); this.canvas.appendChild(this.background); this.canvas.appendChild(this.masksContent); @@ -1412,10 +1399,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } } else if (reason === UpdateReasons.IMAGE_CHANGED) { const { image } = model; - if (!image) { - this.loadingAnimation.classList.remove('cvat_canvas_hidden'); - } else { - this.loadingAnimation.classList.add('cvat_canvas_hidden'); + if (image) { const ctx = this.background.getContext('2d'); this.background.setAttribute('width', `${image.renderWidth}px`); this.background.setAttribute('height', `${image.renderHeight}px`); diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 37845acf5b9f..77d639e02b4b 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -658,7 +658,7 @@ class FrameBuffer { } } -async function getImageContext(jobID, frame, imageId) { +async function getImageContext(jobID, frame) { return new Promise((resolve, reject) => { serverProxy.frames .getImageContext(jobID, frame) diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts index 1bc830b4e7bd..eaa5512ab79d 100644 --- a/cvat-core/src/server-proxy.ts +++ b/cvat-core/src/server-proxy.ts @@ -1369,7 +1369,7 @@ async function getPreview(tid, jid) { return response.data; } -async function getImageContext(jid, frame, imageId) { +async function getImageContext(jid, frame) { const { backendAPI } = config; let response = null; diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 4fce6293b2b0..985f27f1320b 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -4,30 +4,19 @@ // SPDX-License-Identifier: MIT import { Mutex } from 'async-mutex'; -// eslint-disable-next-line max-classes-per-file import { MP4Reader, Bytestream } from './3rdparty/mp4'; -import ZipDecoder from './unzip_imgs.worker'; +import * as ZipDecoder from './unzip_imgs.worker'; import H264Decoder from './3rdparty/Decoder.worker'; -export const BlockType = Object.freeze({ - MP4VIDEO: 'mp4video', - ARCHIVE: 'archive', -}); - -export const DimensionType = Object.freeze({ - DIM_3D: '3d', - DIM_2D: '2d', -}); - -const createImageBitmap = async function (blob): Promise { - return new Promise((resolve) => { - const img = document.createElement('img'); - img.addEventListener('load', function loadListener() { - resolve(this); - }); - img.src = URL.createObjectURL(blob); - }); -}; +export enum BlockType { + MP4VIDEO = 'mp4video', + ARCHIVE = 'archive', +} + +export enum DimensionType { + DIM_3D = '3d', + DIM_2D = '2d', +} export function decodeZip(block: any, start: number, end: number, dimension: any): Promise { return new Promise((resolve, reject) => { @@ -43,20 +32,12 @@ export function decodeZip(block: any, start: number, end: number, dimension: any }; worker.onmessage = async (event) => { - const { - isRaw, error, fileName, - } = event.data; + const { error, fileName } = event.data; if (error) { worker.onerror(new ErrorEvent('error', { message: error.toString() })); } - let { data } = event.data; - - if (isRaw) { - // polyfill for safary - data = await createImageBitmap(data); - } - + const { data } = event.data; result[fileName.split('.')[0]] = data; decoded++; @@ -80,122 +61,171 @@ export function decodeZip(block: any, start: number, end: number, dimension: any decodeZip.mutex = new Mutex(); +interface BlockToDecode { + start: number; + end: number; + block: ArrayBuffer; + resolveCallback: (frame: number) => void; + rejectCallback: (e: ErrorEvent) => void; +} + export class FrameProvider { + private blocksRanges: string[]; + private blockSize: number; + private blockType: BlockType; + + /* + ImageBitmap when decode zip chunks + ImageData when decode video chunks + Blob when 3D dimension + null when not decoded yet + */ + private frames: Record; + private requestedBlockToDecode: null | BlockToDecode; + private blocksAreBeingDecoded: Record; + private promisedFrames: Record void; + reject: () => void; + }>; + private currentDecodingThreads: number; + private currentFrame: number; + private mutex: Mutex; + + private dimension: DimensionType; + private workerThreadsLimit: number; + private cachedEncodedBlocksLimit: number; + private cachedDecodedBlocksLimit: number; + + // used for video chunks to resize after decoding + private renderWidth: number; + private renderHeight: number; + constructor( - blockType, - blockSize, - cachedBlockCount, + blockType: BlockType, + blockSize: number, + cachedBlockCount: number, decodedBlocksCacheSize = 5, maxWorkerThreadCount = 2, - dimension = DimensionType.DIM_2D, + dimension: DimensionType = DimensionType.DIM_2D, ) { - this._frames = {}; - this._cachedBlockCount = Math.max(1, cachedBlockCount); // number of stored blocks - this._decodedBlocksCacheSize = decodedBlocksCacheSize; - this._blocksRanges = []; + this.mutex = new Mutex(); + this.blocksRanges = []; + this.frames = {}; + this.promisedFrames = {}; + this.currentDecodingThreads = 0; + this.currentFrame = -1; + + this.cachedEncodedBlocksLimit = Math.max(1, cachedBlockCount); // number of stored blocks + this.cachedDecodedBlocksLimit = decodedBlocksCacheSize; + this.workerThreadsLimit = maxWorkerThreadCount; + this.dimension = dimension; + + this.renderWidth = 1920; + this.renderHeight = 1080; + this.blockSize = blockSize; + this.blockType = blockType; + + // todo: sort out with logic of blocks this._blocks = {}; - this._running = false; - this._blockSize = blockSize; - this._blockType = blockType; - this._currFrame = -1; - this._requestedBlockDecode = null; - this._width = null; - this._height = null; - this._decodingBlocks = {}; - this._decodeThreadCount = 0; - this._timerId = setTimeout(this._worker.bind(this), 100); - this._mutex = new Mutex(); - this._promisedFrames = {}; - this._maxWorkerThreadCount = maxWorkerThreadCount; - this._dimension = dimension; + this.requestedBlockToDecode = null; + this.blocksAreBeingDecoded = {}; + + setTimeout(this._checkDecodeRequests.bind(this), 100); } - async _worker() { - if (this._requestedBlockDecode !== null && this._decodeThreadCount < this._maxWorkerThreadCount) { - await this.startDecode(); + _checkDecodeRequests(): void { + if (this.requestedBlockToDecode !== null && this.currentDecodingThreads < this.workerThreadsLimit) { + this.startDecode().then(() => { + setTimeout(this._checkDecodeRequests.bind(this), 100); + }); + } else { + setTimeout(this._checkDecodeRequests.bind(this), 100); } - this._timerId = setTimeout(this._worker.bind(this), 100); } - isChunkCached(start, end) { - return `${start}:${end}` in this._blocksRanges; + isChunkCached(start: number, end: number): boolean { + return this.blocksRanges.includes(`${start}:${end}`); } /* This method removes extra data from a cache when memory overflow */ - async _cleanup() { - if (this._blocksRanges.length > this._cachedBlockCount) { - const shifted = this._blocksRanges.shift(); // get the oldest block + async _cleanup(): Promise { + if (this.blocksRanges.length > this.cachedEncodedBlocksLimit) { + const shifted = this.blocksRanges.shift(); // get the oldest block const [start, end] = shifted.split(':').map((el) => +el); - delete this._blocks[start / this._blockSize]; + delete this._blocks[Math.floor(start / this.blockSize)]; for (let i = start; i <= end; i++) { - delete this._frames[i]; + delete this.frames[i]; } } // delete frames whose are not in areas of current frame - const distance = Math.floor(this._decodedBlocksCacheSize / 2); - for (let i = 0; i < this._blocksRanges.length; i++) { - const [start, end] = this._blocksRanges[i].split(':').map((el) => +el); + const distance = Math.floor(this.cachedDecodedBlocksLimit / 2); + for (let i = 0; i < this.blocksRanges.length; i++) { + const [start, end] = this.blocksRanges[i].split(':').map((el) => +el); if ( - end < this._currFrame - distance * this._blockSize || - start > this._currFrame + distance * this._blockSize + end < this.currentFrame - distance * this.blockSize || + start > this.currentFrame + distance * this.blockSize ) { for (let j = start; j <= end; j++) { - delete this._frames[j]; + delete this.frames[j]; } } } } - async requestDecodeBlock(block, start, end, resolveCallback, rejectCallback) { - const release = await this._mutex.acquire(); + async requestDecodeBlock( + block: ArrayBuffer, + start: number, + end: number, + resolveCallback: () => void, + rejectCallback: () => void, + ): Promise { + const release = await this.mutex.acquire(); try { - if (this._requestedBlockDecode !== null) { - if (start === this._requestedBlockDecode.start && end === this._requestedBlockDecode.end) { - this._requestedBlockDecode.resolveCallback = resolveCallback; - this._requestedBlockDecode.rejectCallback = rejectCallback; - } else if (this._requestedBlockDecode.rejectCallback) { - this._requestedBlockDecode.rejectCallback(); + if (this.requestedBlockToDecode !== null) { + if (start === this.requestedBlockToDecode.start && end === this.requestedBlockToDecode.end) { + // only rewrite callbacks if the same block was requested again + this.requestedBlockToDecode.resolveCallback = resolveCallback; + this.requestedBlockToDecode.rejectCallback = rejectCallback; + + // todo: should we reject the previous request here? + } else if (this.requestedBlockToDecode.rejectCallback) { + // if another block requested, the previous request should be rejected + this.requestedBlockToDecode.rejectCallback(); } } - if (!(`${start}:${end}` in this._decodingBlocks)) { - this._requestedBlockDecode = { - block: block || this._blocks[Math.floor(start / this._blockSize)], + + if (!(`${start}:${end}` in this.blocksAreBeingDecoded)) { + this.requestedBlockToDecode = { + block: block || this._blocks[Math.floor(start / this.blockSize)], start, end, resolveCallback, rejectCallback, }; } else { - this._decodingBlocks[`${start}:${end}`].rejectCallback = rejectCallback; - this._decodingBlocks[`${start}:${end}`].resolveCallback = resolveCallback; + this.blocksAreBeingDecoded[`${start}:${end}`].rejectCallback = rejectCallback; + this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback = resolveCallback; } } finally { release(); } } - isRequestExist() { - return this._requestedBlockDecode !== null; + setRenderSize(width: number, height: number): void { + this.renderWidth = width; + this.renderHeight = height; } - setRenderSize(width, height) { - this._width = width; - this._height = height; - } - - /* Method returns frame from collection. Else method returns 0 */ - async frame(frameNumber) { - this._currFrame = frameNumber; + /* Method returns frame from collection. Else method returns null */ + async frame(frameNumber: number): Promise { + this.currentFrame = frameNumber; return new Promise((resolve, reject) => { - if (frameNumber in this._frames) { - if (this._frames[frameNumber] !== null) { - resolve(this._frames[frameNumber]); + if (frameNumber in this.frames) { + if (this.frames[frameNumber] !== null) { + resolve(this.frames[frameNumber]); } else { - this._promisedFrames[frameNumber] = { - resolve, - reject, - }; + this.promisedFrames[frameNumber] = { resolve, reject }; } } else { resolve(null); @@ -203,30 +233,24 @@ export class FrameProvider { }); } - isNextChunkExists(frameNumber) { - const nextChunkNum = Math.floor(frameNumber / this._blockSize) + 1; - if (this._blocks[nextChunkNum] === 'loading') { - return true; - } - + isNextChunkExists(frameNumber: number): boolean { + const nextChunkNum = Math.floor(frameNumber / this.blockSize) + 1; return nextChunkNum in this._blocks; } - /* - Method start asynchronic decode a block of data - - @param block - is a data from a server as is (ts file or archive) - @param start {number} - is the first frame of a block - @param end {number} - is the last frame of a block + 1 - @param callback - callback) - - */ - - setReadyToLoading(chunkNumber) { + setReadyToLoading(chunkNumber: number): void { this._blocks[chunkNumber] = 'loading'; } - static cropImage(imageBuffer, imageWidth, imageHeight, xOffset, yOffset, width, height) { + static cropImage( + imageBuffer: ArrayBuffer, + imageWidth: number, + imageHeight: number, + xOffset: number, + yOffset: number, + width: number, + height: number, + ): ImageData { if (xOffset === 0 && width === imageWidth && yOffset === 0 && height === imageHeight) { return new ImageData(new Uint8ClampedArray(imageBuffer), width, height); } @@ -251,22 +275,26 @@ export class FrameProvider { return new ImageData(rgbaInt8Clamped, width, height); } - async startDecode() { - const release = await this._mutex.acquire(); + async startDecode(): Promise { + const release = await this.mutex.acquire(); try { - const height = this._height; - const width = this._width; - const { start, end, block } = this._requestedBlockDecode; - - this._blocksRanges.push(`${start}:${end}`); - this._decodingBlocks[`${start}:${end}`] = this._requestedBlockDecode; - this._requestedBlockDecode = null; - this._blocks[Math.floor((start + 1) / this._blockSize)] = block; + const height = this.renderHeight; + const width = this.renderWidth; + const { start, end, block } = this.requestedBlockToDecode; + + this.blocksRanges.push(`${start}:${end}`); + this.blocksAreBeingDecoded[`${start}:${end}`] = this.requestedBlockToDecode; + this.requestedBlockToDecode = null; + this._blocks[Math.floor((start + 1) / this.blockSize)] = block; + for (let i = start; i <= end; i++) { - this._frames[i] = null; + this.frames[i] = null; } + this._cleanup(); - if (this._blockType === BlockType.MP4VIDEO) { + this.currentDecodingThreads++; + + if (this.blockType === BlockType.MP4VIDEO) { const worker = new H264Decoder(); let index = start; @@ -276,8 +304,8 @@ export class FrameProvider { return; } - const scaleFactor = Math.ceil(this._height / e.data.height); - this._frames[index] = FrameProvider.cropImage( + const scaleFactor = Math.ceil(height / e.data.height); + this.frames[index] = FrameProvider.cropImage( e.data.buf, e.data.width, e.data.height, @@ -287,37 +315,43 @@ export class FrameProvider { Math.floor(height / scaleFactor), ); - if (this._decodingBlocks[`${start}:${end}`].resolveCallback) { - this._decodingBlocks[`${start}:${end}`].resolveCallback(index); + if (this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback) { + this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback(index); } - if (index in this._promisedFrames) { - this._promisedFrames[index].resolve(this._frames[index]); - delete this._promisedFrames[index]; + if (index in this.promisedFrames) { + const { resolve } = this.promisedFrames[index]; + delete this.promisedFrames[index]; + resolve(this.frames[index]); } + if (index === end) { - this._decodeThreadCount--; - delete this._decodingBlocks[`${start}:${end}`]; worker.terminate(); + this.currentDecodingThreads--; + delete this.blocksAreBeingDecoded[`${start}:${end}`]; } + index++; }; - worker.onerror = (e) => { + worker.onerror = (e: ErrorEvent) => { worker.terminate(); - this._decodeThreadCount--; + this.currentDecodingThreads--; for (let i = index; i <= end; i++) { - if (i in this._promisedFrames) { - this._promisedFrames[i].reject(); - delete this._promisedFrames[i]; + // reject all the following frames + if (i in this.promisedFrames) { + const { reject } = this.promisedFrames[i]; + delete this.promisedFrames[i]; + reject(); } } - if (this._decodingBlocks[`${start}:${end}`].rejectCallback) { - this._decodingBlocks[`${start}:${end}`].rejectCallback(Error(e)); + if (this.blocksAreBeingDecoded[`${start}:${end}`].rejectCallback) { + this.blocksAreBeingDecoded[`${start}:${end}`].rejectCallback(e); } - delete this._decodingBlocks[`${start}:${end}`]; + + delete this.blocksAreBeingDecoded[`${start}:${end}`]; }; worker.postMessage({ @@ -346,80 +380,68 @@ export class FrameProvider { worker.postMessage({ buf: nal, offset: 0, length: nal.length }); }); } - this._decodeThreadCount++; } else { const worker = new ZipDecoder(); let index = start; - worker.onerror = (e) => { - for (let i = start; i <= end; i++) { - if (i in this._promisedFrames) { - this._promisedFrames[i].reject(); - delete this._promisedFrames[i]; - } - } - if (this._decodingBlocks[`${start}:${end}`].rejectCallback) { - this._decodingBlocks[`${start}:${end}`].rejectCallback(Error(e)); - } - this._decodeThreadCount--; - worker.terminate(); - }; - worker.onmessage = async (event) => { - if (this._dimension === DimensionType.DIM_2D && event.data.isRaw) { - // safary doesn't support createImageBitmap - // there is a way to polyfill it with using document.createElement - // but document.createElement doesn't work in worker - // so, we get raw data and decode it here, no other way - event.data.data = await createImageBitmap(event.data.data); - } + this.frames[event.data.index] = event.data.data; - this._frames[event.data.index] = event.data.data; - - if (this._decodingBlocks[`${start}:${end}`].resolveCallback) { - this._decodingBlocks[`${start}:${end}`].resolveCallback(event.data.index); + if (this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback) { + this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback(event.data.index); } - if (event.data.index in this._promisedFrames) { - this._promisedFrames[event.data.index].resolve(this._frames[event.data.index]); - delete this._promisedFrames[event.data.index]; + if (event.data.index in this.promisedFrames) { + const { resolve } = this.promisedFrames[event.data.index]; + delete this.promisedFrames[event.data.index]; + resolve(this.frames[event.data.index]); } if (index === end) { worker.terminate(); - delete this._decodingBlocks[`${start}:${end}`]; - this._decodeThreadCount--; + this.currentDecodingThreads--; + delete this.blocksAreBeingDecoded[`${start}:${end}`]; } index++; }; - const dimension = this._dimension; + + worker.onerror = (e: ErrorEvent) => { + for (let i = start; i <= end; i++) { + if (i in this.promisedFrames) { + const { reject } = this.promisedFrames[i]; + delete this.promisedFrames[i]; + reject(); + } + } + if (this.blocksAreBeingDecoded[`${start}:${end}`].rejectCallback) { + this.blocksAreBeingDecoded[`${start}:${end}`].rejectCallback(e); + } + this.currentDecodingThreads--; + worker.terminate(); + }; + worker.postMessage({ block, start, end, - dimension, + dimension: this.dimension, dimension2D: DimensionType.DIM_2D, }); - this._decodeThreadCount++; } } finally { release(); } } - get decodeThreadCount() { - return this._decodeThreadCount; - } - - get decodedBlocksCacheSize() { - return this._decodedBlocksCacheSize; + get decodedBlocksCacheSize(): number { + return this.cachedDecodedBlocksLimit; } /* Method returns a list of cached ranges Is an array of strings like "start:end" */ - get cachedFrames() { - return [...this._blocksRanges].sort((a, b) => a.split(':')[0] - b.split(':')[0]); + get cachedFrames(): string[] { + return [...this.blocksRanges].sort((a, b) => +a.split(':')[0] - +b.split(':')[0]); } } diff --git a/cvat-data/src/ts/unzip_imgs.worker.js b/cvat-data/src/ts/unzip_imgs.worker.js index 4c8bcabce190..706432e0c6dd 100644 --- a/cvat-data/src/ts/unzip_imgs.worker.js +++ b/cvat-data/src/ts/unzip_imgs.worker.js @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: MIT -const JSZip = require('jszip'); +import JSZip from 'jszip'; onmessage = (e) => { const zip = new JSZip(); @@ -21,7 +21,7 @@ onmessage = (e) => { .async('blob') .then((fileData) => { // eslint-disable-next-line no-restricted-globals - if (dimension === dimension2D && self.createImageBitmap) { + if (dimension === dimension2D) { createImageBitmap(fileData).then((img) => { postMessage({ fileName: relativePath, @@ -34,7 +34,6 @@ onmessage = (e) => { fileName: relativePath, index: fileIndex, data: fileData, - isRaw: true, }); } }); 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 1d01b541f311..acb977a4b298 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 @@ -6,6 +6,7 @@ import React from 'react'; import { connect } from 'react-redux'; import Slider from 'antd/lib/slider'; +import Spin from 'antd/lib/spin'; import Dropdown from 'antd/lib/dropdown'; import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons'; @@ -69,7 +70,7 @@ interface StateToProps { annotations: any[]; frameData: any; frameAngle: number; - frameFetching: boolean; + canvasIsReady: boolean; frame: number; opacity: number; colorBy: ColorBy; @@ -141,11 +142,11 @@ interface DispatchToProps { function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { - canvas: { activeControl, instance: canvasInstance }, + canvas: { activeControl, instance: canvasInstance, ready: canvasIsReady }, drawing: { activeLabelID, activeObjectType }, job: { instance: jobInstance }, player: { - frame: { data: frameData, number: frame, fetching: frameFetching }, + frame: { data: frameData, number: frame }, frameAngles, }, annotations: { @@ -195,7 +196,7 @@ function mapStateToProps(state: CombinedState): StateToProps { jobInstance, frameData, frameAngle: frameAngles[frame - jobInstance.startFrame], - frameFetching, + canvasIsReady, frame, activatedStateID, activatedElementID, @@ -414,7 +415,6 @@ class CanvasWrapperComponent extends React.PureComponent { contrastLevel, saturationLevel, workspace, - frameFetching, showObjectsTextAlways, textFontSize, controlPointsSize, @@ -973,6 +973,7 @@ class CanvasWrapperComponent extends React.PureComponent { switchableAutomaticBordering, automaticBordering, showTagsOnFrame, + canvasIsReady, onSwitchAutomaticBordering, onSwitchZLayer, onAddZLayer, @@ -1005,6 +1006,13 @@ class CanvasWrapperComponent extends React.PureComponent { So, React isn't going to rerender it And it's a reason why cvat-canvas appended in mount function works */} + { + !canvasIsReady && ( +
+ +
+ ) + }
void; @@ -169,11 +170,17 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { type Props = StateToProps & DispatchToProps; +const Spinner = React.memo(() => ( +
+ +
+)); + export const PerspectiveViewComponent = React.memo( (): JSX.Element => { const ref = useRef(null); const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); - const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + const canvasIsReady = useSelector((state: CombinedState) => state.annotation.canvas.ready); const screenKeyControl = (code: CameraAction, altKey: boolean, shiftKey: boolean): void => { canvas.keyControls(new KeyboardEvent('keydown', { code, altKey, shiftKey })); @@ -290,19 +297,10 @@ export const PerspectiveViewComponent = React.memo( return (
- { - frameFetching && ( - - - - ) - } + { !canvasIsReady && }
@@ -315,7 +313,7 @@ export const TopViewComponent = React.memo( (): JSX.Element => { const ref = useRef(null); const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); - const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + const canvasIsReady = useSelector((state: CombinedState) => state.annotation.canvas.ready); useEffect(() => { if (ref.current) { @@ -325,20 +323,11 @@ export const TopViewComponent = React.memo( return (
- { - frameFetching && ( - - - - ) - } + { !canvasIsReady && }
Top
); @@ -349,7 +338,7 @@ export const SideViewComponent = React.memo( (): JSX.Element => { const ref = useRef(null); const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); - const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + const canvasIsReady = useSelector((state: CombinedState) => state.annotation.canvas.ready); useEffect(() => { if (ref.current) { @@ -359,20 +348,11 @@ export const SideViewComponent = React.memo( return (
- { - frameFetching && ( - - - - ) - } + { !canvasIsReady && }
Side
); @@ -383,7 +363,7 @@ export const FrontViewComponent = React.memo( (): JSX.Element => { const ref = useRef(null); const canvas = useSelector((state: CombinedState) => state.annotation.canvas.instance as Canvas3d); - const frameFetching = useSelector((state: CombinedState) => state.annotation.player.frame.fetching); + const canvasIsReady = useSelector((state: CombinedState) => state.annotation.canvas.ready); useEffect(() => { if (ref.current) { @@ -393,20 +373,11 @@ export const FrontViewComponent = React.memo( return (
- { - frameFetching && ( - - - - ) - } + { !canvasIsReady && }
Front
); From 83f71fe0e5397c267ee121c6872db82e8582ce57 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 05:36:12 -0800 Subject: [PATCH 09/47] Fixed positioning issue --- cvat-canvas/src/typescript/canvasModel.ts | 1 - cvat-canvas3d/src/typescript/canvas3dModel.ts | 5 +- cvat-core/src/frames.ts | 137 +++++------------- cvat-core/src/server-proxy.ts | 18 ++- cvat-data/src/ts/cvat-data.ts | 24 +-- 5 files changed, 68 insertions(+), 117 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index b7591f02277b..5f271f88b4d6 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -527,7 +527,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { if (typeof exception !== 'number' || exception === this.data.imageID) { this.notify(UpdateReasons.DATA_FAILED); } - throw exception; }); } diff --git a/cvat-canvas3d/src/typescript/canvas3dModel.ts b/cvat-canvas3d/src/typescript/canvas3dModel.ts index c87feb6bf1ef..48acec10ae4b 100644 --- a/cvat-canvas3d/src/typescript/canvas3dModel.ts +++ b/cvat-canvas3d/src/typescript/canvas3dModel.ts @@ -238,7 +238,10 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { }) .catch((exception: any): void => { this.data.isFrameUpdating = false; - throw exception; + // don't notify when the frame is no longer needed + if (typeof exception !== 'number' || exception === this.data.imageID) { + throw exception; + } }); } diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 77d639e02b4b..8b2fb79a9034 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -3,20 +3,28 @@ // // SPDX-License-Identifier: MIT -import * as cvatData from 'cvat-data'; import { isBrowser, isNode } from 'browser-or-node'; + +import * as cvatData from 'cvat-data'; +import { DimensionType } from 'enums'; import PluginRegistry from './plugins'; -import serverProxy from './server-proxy'; +import serverProxy, { FramesMetaData } from './server-proxy'; import { Exception, ArgumentError, DataError } from './exceptions'; -// This is the frames storage -const frameDataCache = {}; +// frame storage by job id +const frameDataCache: Record = {}; -/** - * Class provides meta information about specific frame and frame itself - * @memberof module:API.cvat.classes - * @hideconstructor - */ export class FrameData { constructor({ width, @@ -33,93 +41,34 @@ export class FrameData { Object.defineProperties( this, Object.freeze({ - /** - * @name filename - * @type {string} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ filename: { value: name, writable: false, }, - /** - * @name width - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ width: { value: width, writable: false, }, - /** - * @name height - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ height: { value: height, writable: false, }, - /** - * @name jid - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ jid: { value: jobID, writable: false, }, - /** - * @name number - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ number: { value: frameNumber, writable: false, }, - /** - * Number of context images are associated with this frame - * @name relatedFiles - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ relatedFiles: { value: relatedFiles, writable: false, }, - /** - * Start frame of the frame in the job - * @name startFrame - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ startFrame: { value: startFrame, writable: false, }, - /** - * Stop frame of the frame in the job - * @name stopFrame - * @type {number} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ stopFrame: { value: stopFrame, writable: false, @@ -128,14 +77,6 @@ export class FrameData { value: decodeForward, writable: false, }, - /** - * True if frame was deleted from the task data - * @name deleted - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ deleted: { value: deleted, writable: false, @@ -144,18 +85,6 @@ export class FrameData { ); } - /** - * Method returns URL encoded image which can be placed in the img tag - * @method data - * @returns {string} - * @memberof module:API.cvat.classes.FrameData - * @instance - * @async - * @param {function} [onServerRequest = () => {}] - * callback which will be called if data absences local - * @throws {module:API.cvat.exception.ServerError} - * @throws {module:API.cvat.exception.PluginError} - */ async data(onServerRequest = () => {}) { const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); return result; @@ -384,9 +313,7 @@ FrameData.prototype.data.implementation = async function (onServerRequest) { }); }; -function getFrameMeta(jobID, frame): any { - // todo: fix this function because now meta has not only size - // but also number of context images for example +function getFrameMeta(jobID, frame): FramesMetaData['frames'][0] { const { meta, mode, startFrame } = frameDataCache[jobID]; let size = null; if (mode === 'interpolation') { @@ -400,6 +327,7 @@ function getFrameMeta(jobID, frame): any { } else { throw new DataError(`Invalid mode is specified ${mode}`); } + return size; } @@ -420,7 +348,7 @@ class FrameBuffer { data.then((resolvedData) => { const meta = getFrameMeta(this._jobID, frame); return cvatData - .decodeZip(resolvedData, 0, meta.related_files, cvatData.DimensionType.DIM_2D); + .decodeZip(resolvedData, 0, meta.related_files, cvatData.DimensionType.DIMENSION_2D); }).then((decodedData) => { this._contextImage[frame] = decodedData; resolve(); @@ -590,7 +518,7 @@ class FrameBuffer { } } - async require(frameNumber, jobID, fillBuffer, frameStep) { + async require(frameNumber: number, jobID: number, fillBuffer: boolean, frameStep: number): FrameData { for (const frame in this._buffer) { if (+frame < frameNumber || +frame >= frameNumber + this._size * frameStep) { delete this._buffer[frame]; @@ -709,16 +637,16 @@ export async function getPreview(taskID = null, jobID = null) { } export async function getFrame( - jobID, - chunkSize, - chunkType, - mode, - frame, - startFrame, - stopFrame, - isPlaying, - step, - dimension, + jobID: number, + chunkSize: number, + chunkType: 'video' | 'imageset', + mode: 'interpolation' | 'annotation', // todo: obsolete, need to remove + frame: number, + startFrame: number, + stopFrame: number, + isPlaying: boolean, + step: number, + dimension: DimensionType, ) { if (!(jobID in frameDataCache)) { const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO : cvatData.BlockType.ARCHIVE; @@ -757,8 +685,9 @@ export async function getFrame( activeChunkRequest: null, nextChunkRequest: null, }; + + // relevant only for video chunks const frameMeta = getFrameMeta(jobID, frame); - // actual only for video chunks frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height); } diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts index eaa5512ab79d..7670a45af4e5 100644 --- a/cvat-core/src/server-proxy.ts +++ b/cvat-core/src/server-proxy.ts @@ -1420,7 +1420,23 @@ async function getData(tid, jid, chunk) { return response; } -async function getMeta(session, jid) { +export interface FramesMetaData { + chunk_size: number; + deleted_frames: number[]; + frame_filter: string; + frames: { + width: number; + height: number; + name: string; + related_files: number; + }[]; + image_quality: number; + size: number; + start_frame: number; + stop_frame: number; +} + +async function getMeta(session, jid): Promise { const { backendAPI } = config; let response = null; diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 985f27f1320b..842ce90d2dd9 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -14,8 +14,8 @@ export enum BlockType { } export enum DimensionType { - DIM_3D = '3d', - DIM_2D = '2d', + DIMENSION_3D = '3d', + DIMENSION_2D = '2d', } export function decodeZip(block: any, start: number, end: number, dimension: any): Promise { @@ -53,7 +53,7 @@ export function decodeZip(block: any, start: number, end: number, dimension: any start, end, dimension, - dimension2D: DimensionType.DIM_2D, + dimension2D: DimensionType.DIMENSION_2D, }); }); }); @@ -106,7 +106,7 @@ export class FrameProvider { cachedBlockCount: number, decodedBlocksCacheSize = 5, maxWorkerThreadCount = 2, - dimension: DimensionType = DimensionType.DIM_2D, + dimension: DimensionType = DimensionType.DIMENSION_2D, ) { this.mutex = new Mutex(); this.blocksRanges = []; @@ -144,7 +144,9 @@ export class FrameProvider { } isChunkCached(start: number, end: number): boolean { - return this.blocksRanges.includes(`${start}:${end}`); + // todo: always returns false because this.blocksRanges is Array, not dictionary + // but if try to correct other errors happens, need to debug.. + return `${start}:${end}` in this.blocksRanges; } /* This method removes extra data from a cache when memory overflow */ @@ -315,8 +317,9 @@ export class FrameProvider { Math.floor(height / scaleFactor), ); - if (this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback) { - this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback(index); + const { resolveCallback } = this.blocksAreBeingDecoded[`${start}:${end}`]; + if (resolveCallback) { + resolveCallback(index); } if (index in this.promisedFrames) { @@ -387,8 +390,9 @@ export class FrameProvider { worker.onmessage = async (event) => { this.frames[event.data.index] = event.data.data; - if (this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback) { - this.blocksAreBeingDecoded[`${start}:${end}`].resolveCallback(event.data.index); + const { resolveCallback } = this.blocksAreBeingDecoded[`${start}:${end}`]; + if (resolveCallback) { + resolveCallback(event.data.index); } if (event.data.index in this.promisedFrames) { @@ -425,7 +429,7 @@ export class FrameProvider { start, end, dimension: this.dimension, - dimension2D: DimensionType.DIM_2D, + dimension2D: DimensionType.DIMENSION_2D, }); } } finally { From 64f202ec898569aa6b19f666f59a847b950d0ecc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 08:58:45 -0800 Subject: [PATCH 10/47] Fixed several test cases --- .../attribute-annotation-sidebar.tsx | 10 +--- .../canvas/grid-layout/canvas-layout.tsx | 14 +++++- .../canvas/grid-layout/styles.scss | 17 +++++-- .../canvas/views/canvas2d/canvas-wrapper.tsx | 30 ------------ .../canvas/views/context-image/styles.scss | 5 +- .../objects-side-bar/objects-side-bar.tsx | 10 +--- .../standard-workspace/styles.scss | 3 +- .../tag-annotation-sidebar.tsx | 9 +--- .../src/utils/opencv-wrapper/tracker-mil.ts | 9 ++-- .../case_101_opencv_basic_actions.js | 4 +- ...ttings_text_size_position_label_content.js | 32 ++++++------- .../case_21_canvas_color_feature.js | 5 +- .../case_30_collapse_sidebar_appearance.js | 47 ++++--------------- ...ute_annotation_mode_zoom_margin_feature.js | 4 +- 14 files changed, 72 insertions(+), 127 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 0ea6568723b2..2c35588de3ff 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -11,8 +11,6 @@ import Text from 'antd/lib/typography/Text'; import { filterApplicableLabels } from 'utils/filter-applicable-labels'; import { Label } from 'cvat-core-wrapper'; -import { Canvas } from 'cvat-canvas-wrapper'; -import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { LogType } from 'cvat-logger'; import { activateObject as activateObjectAction, @@ -38,7 +36,6 @@ interface StateToProps { jobInstance: any; keyMap: KeyMap; normalizedKeyMap: Record; - canvasInstance: Canvas | Canvas3d; canvasIsReady: boolean; curZLayer: number; } @@ -63,7 +60,7 @@ function mapStateToProps(state: CombinedState): StateToProps { zLayer: { cur }, }, job: { instance: jobInstance, labels }, - canvas: { instance: canvasInstance, ready: canvasIsReady }, + canvas: { ready: canvasIsReady }, }, shortcuts: { keyMap, normalizedKeyMap }, } = state; @@ -76,7 +73,6 @@ function mapStateToProps(state: CombinedState): StateToProps { states, keyMap, normalizedKeyMap, - canvasInstance, canvasIsReady, curZLayer: cur, }; @@ -108,7 +104,6 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activateObject, keyMap, normalizedKeyMap, - canvasInstance, canvasIsReady, curZLayer, } = props; @@ -128,8 +123,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. const listener = (event: TransitionEvent): void => { if (event.target && event.propertyName === 'width' && event.target === collapser) { - canvasInstance.fitCanvas(); - canvasInstance.fit(); + window.dispatchEvent(new Event('resize')); (collapser as HTMLElement).removeEventListener('transitionend', listener as any); } }; diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 66b7047d4d2e..8e39e05bffaf 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -64,6 +64,7 @@ const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const NUM_OF_ROWS = 12; const MARGIN = 8; + const PADDING = MARGIN / 2; const [rowHeight, setRowHeight] = useState(Math.floor(window.screen.availHeight / NUM_OF_ROWS)); const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); @@ -94,7 +95,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { if (container) { const { height } = container.getBoundingClientRect(); // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 - setRowHeight(Math.floor((height - MARGIN * (NUM_OF_ROWS + 4)) / NUM_OF_ROWS)); + setRowHeight(Math.floor((height - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS)); } setTimeout(() => { @@ -105,6 +106,14 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }); }, []); + useEffect(() => { + const onResize = onLayoutChange; + window.addEventListener('resize', onResize); + return () => { + window.removeEventListener('resize', onResize); + }; + }, [onLayoutChange]); + const children = getLayout().map((value: ItemLayout) => ViewFabric((value))); const layout = getLayout().map((value: ItemLayout) => ({ x: value.x, @@ -117,7 +126,10 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { return ( { frameData, frameAngle, annotations, - sidebarCollapsed, activatedStateID, curZLayer, resetZoom, @@ -467,19 +463,6 @@ class CanvasWrapperComponent extends React.PureComponent { onFetchAnnotation(); } - if (prevProps.sidebarCollapsed !== sidebarCollapsed) { - const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar'); - if (sidebar) { - sidebar.addEventListener( - 'transitionend', - () => { - canvasInstance.fitCanvas(); - }, - { once: true }, - ); - } - } - if (prevProps.activatedStateID !== null && prevProps.activatedStateID !== activatedStateID) { canvasInstance.activate(null); } @@ -582,8 +565,6 @@ class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().removeEventListener('canvas.splitted', this.onCanvasTrackSplitted); canvasInstance.html().removeEventListener('canvas.error', this.onCanvasErrorOccurrence); - - window.removeEventListener('resize', this.fitCanvas); } private onCanvasErrorOccurrence = (event: any): void => { @@ -675,13 +656,6 @@ class CanvasWrapperComponent extends React.PureComponent { onSplitAnnotations(jobInstance, frame, state); }; - private fitCanvas = (): void => { - const { canvasInstance } = this.props; - if (canvasInstance) { - canvasInstance.fitCanvas(); - } - }; - private onCanvasMouseDown = (e: MouseEvent): void => { const { workspace, activatedStateID, onActivateObject } = this.props; @@ -903,10 +877,6 @@ class CanvasWrapperComponent extends React.PureComponent { } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; - // Size - window.addEventListener('resize', this.fitCanvas); - this.fitCanvas(); - // Grid const gridElement = window.document.getElementById('cvat_canvas_grid'); const gridPattern = window.document.getElementById('cvat_canvas_grid_pattern'); diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 2b43f4f94560..c1fd59253b90 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -28,6 +28,7 @@ .cvat-context-image-header { position: absolute; height: $grid-unit-size * 4; + border-radius: 4px 4px 0 0; width: 100%; text-align: center; z-index: 1; @@ -36,7 +37,7 @@ > .cvat-context-image-setup-button { position: absolute; top: $grid-unit-size; - right: $grid-unit-size; + left: $grid-unit-size * 4; font-size: 16px; opacity: 0; border-radius: 2px; @@ -47,7 +48,7 @@ > canvas { object-fit: contain; position: relative; - top: 50%; + top: calc(50% + $grid-unit-size * 2); transform: translateY(-50%); width: 100%; height: calc(100% - $grid-unit-size * 4); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index 0a5bebadea47..0042bcb67799 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -12,8 +12,6 @@ import Text from 'antd/lib/typography/Text'; import Tabs from 'antd/lib/tabs'; import Layout from 'antd/lib/layout'; -import { Canvas } from 'cvat-canvas-wrapper'; -import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { CombinedState, DimensionType } from 'reducers'; import LabelsList from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; import { collapseSidebar as collapseSidebarAction } from 'actions/annotation-actions'; @@ -26,7 +24,6 @@ interface OwnProps { interface StateToProps { sidebarCollapsed: boolean; - canvasInstance: Canvas | Canvas3d; jobInstance: any; } @@ -38,14 +35,12 @@ function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { sidebarCollapsed, - canvas: { instance: canvasInstance }, job: { instance: jobInstance }, }, } = state; return { sidebarCollapsed, - canvasInstance, jobInstance, }; } @@ -60,15 +55,14 @@ function mapDispatchToProps(dispatch: Dispatch): DispatchToProps { function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { const { - sidebarCollapsed, canvasInstance, collapseSidebar, objectsList, jobInstance, + sidebarCollapsed, collapseSidebar, objectsList, jobInstance, } = props; const collapse = (): void => { const [collapser] = window.document.getElementsByClassName('cvat-objects-sidebar'); const listener = (event: TransitionEvent): void => { if (event.target && event.propertyName === 'width' && event.target === collapser) { - canvasInstance.fitCanvas(); - canvasInstance.fit(); + window.dispatchEvent(new Event('resize')); (collapser as HTMLElement).removeEventListener('transitionend', listener as any); } }; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 95f87823ff13..5980c704f829 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -9,7 +9,8 @@ height: 100%; > .ant-layout-content { - overflow: auto; + overflow-y: hidden; + overflow-x: hidden; } } diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index cfa782039104..994cb2ea127d 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -22,8 +22,6 @@ import { changeFrameAsync, rememberObject, } from 'actions/annotation-actions'; -import { Canvas } from 'cvat-canvas-wrapper'; -import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { getCore, Label, LabelType } from 'cvat-core-wrapper'; import { CombinedState, ObjectType } from 'reducers'; import { filterApplicableForType } from 'utils/filter-applicable-labels'; @@ -38,7 +36,6 @@ interface StateToProps { states: any[]; labels: any[]; jobInstance: any; - canvasInstance: Canvas | Canvas3d; frameNumber: number; keyMap: KeyMap; normalizedKeyMap: Record; @@ -60,7 +57,6 @@ function mapStateToProps(state: CombinedState): StateToProps { }, annotations: { states }, job: { instance: jobInstance, labels }, - canvas: { instance: canvasInstance }, }, shortcuts: { keyMap, normalizedKeyMap }, } = state; @@ -69,7 +65,6 @@ function mapStateToProps(state: CombinedState): StateToProps { jobInstance, labels, states, - canvasInstance: canvasInstance as Canvas | Canvas3d, frameNumber, keyMap, normalizedKeyMap, @@ -101,7 +96,6 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen removeObject, jobInstance, changeFrame, - canvasInstance, frameNumber, onRememberObject, createAnnotations, @@ -142,8 +136,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen (event as TransitionEvent).propertyName === 'width' && ((event.target as any).classList as DOMTokenList).contains('cvat-tag-annotation-sidebar') ) { - canvasInstance.fitCanvas(); - canvasInstance.fit(); + window.dispatchEvent(new Event('resize')); } }; diff --git a/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts b/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts index fa479befc5c7..6f45e0b7c49b 100644 --- a/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts +++ b/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts @@ -27,10 +27,10 @@ export default class TrackerMILImplementation implements TrackerMIL { this.imageData = src; // cut bounding box if its out of image bounds - const x1 = clamp(points[0], 0, src.width); - const y1 = clamp(points[1], 0, src.height); - const x2 = clamp(points[2], 0, src.width); - const y2 = clamp(points[3], 0, src.height); + const x1 = Math.floor(clamp(points[0], 0, src.width)); + const y1 = Math.floor(clamp(points[1], 0, src.height)); + const x2 = Math.floor(clamp(points[2], 0, src.width)); + const y2 = Math.floor(clamp(points[3], 0, src.height)); const [width, height] = [x2 - x1, y2 - y1]; if (width === 0 || height === 0) { @@ -42,6 +42,7 @@ export default class TrackerMILImplementation implements TrackerMIL { matImage = this.cv.matFromImageData(src); const rect = new this.cv.Rect(x1, y1, width, height); this.trackerMIL.init(matImage, rect); + this.trackerMIL.update(matImage); } finally { if (matImage) matImage.delete(); } diff --git a/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js b/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js index cbfd1b09e1c3..2669dc41c6b6 100644 --- a/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js +++ b/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js @@ -35,8 +35,8 @@ context('OpenCV. Intelligent scissors. Histogram Equalization. TrackerMIL.', () labelName, tracker: 'TrackerMIL', pointsMap: [ - { x: 440, y: 45 }, - { x: 650, y: 150 }, + { x: 430, y: 40 }, + { x: 640, y: 145 }, ], }; diff --git a/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js b/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js index 007c60ad57cf..eb567c3446f6 100644 --- a/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js +++ b/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js @@ -67,7 +67,7 @@ context('Settings. Text size/position. Text labels content.', () => { if (expectedPosition === 'outside') { // Text outside the shape of the right. Slightly below the shape upper edge. expect(+shapeLeftPosition + +shapeWidth).lessThan(+textLeftPosition); - expect(+textTopPosition).to.be.within(+shapeTopPosition, +shapeTopPosition + 10); + expect(+textTopPosition).to.be.within(+shapeTopPosition, +shapeTopPosition + 15); } else { // Text inside the shape expect(+shapeLeftPosition + +shapeWidth / 2).greaterThan(+textLeftPosition); @@ -108,6 +108,21 @@ context('Settings. Text size/position. Text labels content.', () => { }); describe(`Testing case "${caseId}"`, () => { + it('Text font size.', () => { + cy.get('.cvat_canvas_text').should('have.attr', 'style', 'font-size: 14px;'); + cy.openSettings(); + + // Change the text size to 16 + cy.get('.cvat-workspace-settings-text-size') + .find('input') + .should('have.attr', 'value', '14') + .clear() + .type('10') + .should('have.attr', 'value', '10'); + cy.closeSettings(); + cy.get('.cvat_canvas_text').should('have.attr', 'style', 'font-size: 10px;'); + }); + it('Text position.', () => { testTextPosition('#cvat_canvas_shape_1', 'outside'); testTextPosition('#cvat_canvas_shape_2', 'outside'); @@ -127,21 +142,6 @@ context('Settings. Text size/position. Text labels content.', () => { testTextPosition('#cvat_canvas_shape_2', 'inside'); }); - it('Text font size.', () => { - cy.get('.cvat_canvas_text').should('have.attr', 'style', 'font-size: 14px;'); - cy.openSettings(); - - // Change the text size to 16 - cy.get('.cvat-workspace-settings-text-size') - .find('input') - .should('have.attr', 'value', '14') - .clear() - .type('16') - .should('have.attr', 'value', '16'); - cy.closeSettings(); - cy.get('.cvat_canvas_text').should('have.attr', 'style', 'font-size: 16px;'); - }); - it('Text labels content.', () => { cy.openSettings(); cy.get('.cvat-workspace-settings-text-content').within(() => { diff --git a/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js b/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js index 6ba5e100d74c..7fe013bdf6bd 100644 --- a/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js +++ b/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js @@ -29,10 +29,11 @@ context('Canvas color feature', () => { .click() .should('have.css', 'background-color') .then((colorPickerBgValue) => { - cy.get('.cvat-canvas-container') + cy.get('.cvat-canvas-grid-root') .should('have.css', 'background-color') .then((canvasBgColor) => { - //For each color change, compare the value with the css value background-color of .cvat-canvas-container + // For each color change compare + // the value with the css value background-color of .cvat-canvas-grid-root expect(colorPickerBgValue).to.be.equal(canvasBgColor); }); }); diff --git a/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js b/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js index 0d0d3f115241..1dc39912c0a0 100644 --- a/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js +++ b/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js @@ -13,7 +13,7 @@ context('Collapse sidebar/appearance. Check issue 3250 (empty sidebar after resi const createRectangleShape2Points = { points: 'By 2 Points', type: 'Shape', - labelName: labelName, + labelName, firstX: 250, firstY: 350, secondX: 350, @@ -21,11 +21,10 @@ context('Collapse sidebar/appearance. Check issue 3250 (empty sidebar after resi }; function checkEqualBackground() { - cy.get('#cvat_canvas_background') - .should('have.css', 'left') - .and((currentValueLeftBackground) => { - currentValueLeftBackground = Number(currentValueLeftBackground.match(/\d+/)); - expect(currentValueLeftBackground).to.be.eq(defaultValueLeftBackground); + cy.get('.cvat-canvas-grid-root') + .then((el) => { + expect(el[0].getBoundingClientRect().left) + .to.be.eq(defaultValueLeftBackground); }); } @@ -34,43 +33,13 @@ context('Collapse sidebar/appearance. Check issue 3250 (empty sidebar after resi cy.createRectangle(createRectangleShape2Points); // get default left value from background - cy.get('#cvat_canvas_background') - .should('have.css', 'left') - .then((currentValueLeftBackground) => { - defaultValueLeftBackground = Number(currentValueLeftBackground.match(/\d+/)); + cy.get('.cvat-canvas-grid-root') + .then((el) => { + defaultValueLeftBackground = el[0].getBoundingClientRect().left; }); }); describe(`Testing case "${caseId}"`, () => { - it('Collapse sidebar. Cheeck issue 3250.', () => { - // hide sidebar - cy.get('.cvat-objects-sidebar-sider').click(); - cy.get('.cvat-objects-sidebar').should('not.be.visible'); - cy.get('#cvat_canvas_background') - .should('have.css', 'left') - .and((currentValueLeftBackground) => { - currentValueLeftBackground = Number(currentValueLeftBackground.match(/\d+/)); - expect(currentValueLeftBackground).to.be.greaterThan(defaultValueLeftBackground); - }); - - // Check issue 3250 - cy.get('#cvat_canvas_content').invoke('attr', 'style').then((canvasContainerStyle) => { - cy.viewport(2999, 2999); // Resize window - cy.get('#cvat_canvas_content').should('have.attr', 'style').and('not.equal', canvasContainerStyle); - cy.viewport(Cypress.config('viewportWidth'), Cypress.config('viewportHeight')); // Return to the original size - cy.get('#cvat_canvas_content').should('have.attr', 'style').and('equal', canvasContainerStyle); - }); - - // unhide sidebar - cy.get('.cvat-objects-sidebar-sider').click(); - cy.get('.cvat-objects-sidebar').should('be.visible'); - checkEqualBackground(); - - // Before the issue fix the sidebar item did not appear accordingly it was not possible to activate the shape through the sidebar item - cy.get(`#cvat-objects-sidebar-state-item-1`).trigger('mouseover'); - cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated'); - }); - it('Collapse appearance', () => { // hide cy.get('.cvat-objects-appearance-collapse-header').click(); diff --git a/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js b/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js index 86afb9501160..3b2de427889d 100644 --- a/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js +++ b/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js @@ -11,7 +11,7 @@ context('Attribute annotation mode (AAM) zoom margin feature', () => { const rectangleShape2Points = { points: 'By 2 Points', type: 'Shape', - labelName: labelName, + labelName, firstX: 100, firstY: 100, secondX: 150, @@ -43,6 +43,8 @@ context('Attribute annotation mode (AAM) zoom margin feature', () => { describe(`Testing case "${caseId}"`, () => { it('Change AAM zoom margin on workspace with rectangle', () => { + cy.get('.cvat-attribute-annotation-sidebar-object-switcher-right').click(); + cy.get('.cvat-attribute-annotation-sidebar-object-switcher-left').click(); cy.get('.cvat-attribute-annotation-sidebar-object-switcher').should('contain', `${labelName} 1 [1/2]`); cy.getScaleValue().then((scaleBeforeChangeZoomMargin) => { changeSettingsZoomMargin(150); From fde56e68745451199a419c621109b7a3df5e42f1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 09:08:04 -0800 Subject: [PATCH 11/47] A couple more fixed tests --- .../actions_tasks3/case_90_context_image.js | 71 +------------------ ...56_canvas3d_functionality_basic_actions.js | 5 +- 2 files changed, 4 insertions(+), 72 deletions(-) diff --git a/tests/cypress/integration/actions_tasks3/case_90_context_image.js b/tests/cypress/integration/actions_tasks3/case_90_context_image.js index a2cf22707d3c..48d8ecf1dc9e 100644 --- a/tests/cypress/integration/actions_tasks3/case_90_context_image.js +++ b/tests/cypress/integration/actions_tasks3/case_90_context_image.js @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -14,34 +15,13 @@ context('Context images for 2D tasks.', () => { const createRectangleShape2Points = { points: 'By 2 Points', type: 'Shape', - labelName: labelName, + labelName, firstX: 250, firstY: 350, secondX: 350, secondY: 450, }; - function previewRotate(directionRotation, expectedDeg) { - if (directionRotation === 'right') { - cy.get('[data-icon="rotate-right"]').click(); - } else { - cy.get('[data-icon="rotate-left"]').click(); - } - cy.get('.ant-image-preview-img').should('have.attr', 'style').and('contain', `rotate(${expectedDeg}deg)`); - } - - function previewScaleWheel(zoom, expectedScaleValue) { - cy.get('.ant-image-preview-img') - .trigger('wheel', { deltaY: zoom }) - .should('have.attr', 'style') - .and('contain', `scale3d(${expectedScaleValue})`); - } - - function previewScaleButton(zoom, expectedScaleValue) { - cy.get(`[data-icon="zoom-${zoom}"]`).click(); - cy.get('.ant-image-preview-img').should('have.attr', 'style').and('contain', `scale3d(${expectedScaleValue})`); - } - before(() => { cy.visit('auth/login'); cy.login(); @@ -56,55 +36,10 @@ context('Context images for 2D tasks.', () => { describe(`Testing case "${caseId}"`, () => { it('Check a context image.', () => { - cy.get('.cvat-context-image').should('exist').and('be.visible'); + cy.get('.cvat-context-image-wrapper').should('exist').and('be.visible'); cy.get('.cvat-player-next-button').click(); cy.get('.cvat-context-image').should('exist').and('be.visible'); // Check a context image on the second frame cy.get('.cvat-player-previous-button').click(); - cy.get('.cvat-context-image-switcher').click(); // Hide a context image - cy.get('.cvat-context-image').should('not.exist'); - cy.get('.cvat-context-image-switcher').click(); // Unhide - cy.get('.cvat-context-image').should('exist').and('be.visible'); - }); - - it('Preview a context image. Rotate.', () => { - let degRight = 0; - let degLeft = 360; - cy.contains('Preview').click(); - cy.get('.ant-image-preview-mask').should('exist'); - for (let numberSpins = 0; numberSpins < 4; numberSpins++) { - degRight += 90; - previewRotate('right', String(degRight)); - } - for (let numberSpins = 0; numberSpins < 4; numberSpins++) { - degLeft -= 90; - previewRotate('left', String(degLeft)); - } - }); - - it('Preview a context image. Scale.', () => { - previewScaleWheel(-1, '2, 2, 1'); - previewScaleWheel(1, '1, 1, 1'); - previewScaleButton('in', '2, 2, 1'); - previewScaleButton('out', '1, 1, 1'); - }); - - it('Preview a context image. Move.', () => { - cy.get('.ant-image-preview-img-wrapper') - .should('have.attr', 'style') - .then((translate3d) => { - cy.get('.ant-image-preview-img').trigger('mousedown', { button: 0 }); - cy.get('.ant-image-preview-moving').should('exist'); - cy.get('.ant-image-preview-wrap').trigger('mousemove', 300, 300); - cy.get('.ant-image-preview-img-wrapper').should('have.attr', 'style').and('not.equal', translate3d); - cy.get('.ant-image-preview-img').trigger('mouseup'); - cy.get('.ant-image-preview-moving').should('not.exist'); - cy.get('.ant-image-preview-img-wrapper').should('have.attr', 'style').and('equal', translate3d); - }); - }); - - it('Preview a context image. Cancel preview.', () => { - cy.get('.ant-image-preview-wrap').type('{Esc}'); - cy.get('.ant-image-preview-wrap').should('have.attr', 'style').and('contain', 'display: none'); }); it('Checking issue "Context image disappears after undo/redo".', () => { diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js index d54f162bf181..c0d86396ddda 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js @@ -63,10 +63,7 @@ context('Canvas 3D functionality. Basic actions.', () => { } function testContextImage() { - cy.get('.cvat-context-image-wrapper img').should('exist').and('be.visible'); - cy.get('.cvat-context-image-switcher').click(); // Context image hide - cy.get('.cvat-context-image-wrapper img').should('not.exist'); - cy.get('.cvat-context-image-switcher').click(); // Context image show + cy.get('.cvat-context-image-wrapper canvas').should('exist').and('be.visible'); } function testControlButtonTooltip(button, expectedTooltipText) { From 0fd047e092297cf5f5cc4e254194248aff6b4427 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 09:12:33 -0800 Subject: [PATCH 12/47] Updated license headers, aborted extra changes --- cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts | 9 ++++----- .../actions_tasks2/case_101_opencv_basic_actions.js | 1 + ...case_111_settings_text_size_position_label_content.js | 1 + .../actions_tasks2/case_21_canvas_color_feature.js | 1 + .../case_30_collapse_sidebar_appearance.js | 1 + ...e_32_attribute_annotation_mode_zoom_margin_feature.js | 1 + 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts b/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts index 6f45e0b7c49b..fa479befc5c7 100644 --- a/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts +++ b/cvat-ui/src/utils/opencv-wrapper/tracker-mil.ts @@ -27,10 +27,10 @@ export default class TrackerMILImplementation implements TrackerMIL { this.imageData = src; // cut bounding box if its out of image bounds - const x1 = Math.floor(clamp(points[0], 0, src.width)); - const y1 = Math.floor(clamp(points[1], 0, src.height)); - const x2 = Math.floor(clamp(points[2], 0, src.width)); - const y2 = Math.floor(clamp(points[3], 0, src.height)); + const x1 = clamp(points[0], 0, src.width); + const y1 = clamp(points[1], 0, src.height); + const x2 = clamp(points[2], 0, src.width); + const y2 = clamp(points[3], 0, src.height); const [width, height] = [x2 - x1, y2 - y1]; if (width === 0 || height === 0) { @@ -42,7 +42,6 @@ export default class TrackerMILImplementation implements TrackerMIL { matImage = this.cv.matFromImageData(src); const rect = new this.cv.Rect(x1, y1, width, height); this.trackerMIL.init(matImage, rect); - this.trackerMIL.update(matImage); } finally { if (matImage) matImage.delete(); } diff --git a/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js b/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js index 2669dc41c6b6..8ecc0fdfe7a6 100644 --- a/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js +++ b/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js b/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js index eb567c3446f6..d2a8a2ca18ea 100644 --- a/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js +++ b/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js b/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js index 7fe013bdf6bd..bfbf055c4722 100644 --- a/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js +++ b/tests/cypress/integration/actions_tasks2/case_21_canvas_color_feature.js @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js b/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js index 1dc39912c0a0..182c7a68a48a 100644 --- a/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js +++ b/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js b/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js index 3b2de427889d..bc77b67ac147 100644 --- a/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js +++ b/tests/cypress/integration/actions_tasks2/case_32_attribute_annotation_mode_zoom_margin_feature.js @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT From 4b3668ce27a9b88d03c0647b210f82b6be1d886b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 09:21:21 -0800 Subject: [PATCH 13/47] Updated versions --- cvat-canvas/package.json | 2 +- cvat-canvas3d/package.json | 2 +- cvat-core/package.json | 2 +- cvat-data/package.json | 2 +- cvat-ui/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index b97d416139f0..b42a186919c6 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.16.1", + "version": "2.16.2", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas3d/package.json b/cvat-canvas3d/package.json index f8c1ea524157..920ef664436b 100644 --- a/cvat-canvas3d/package.json +++ b/cvat-canvas3d/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas3d", - "version": "0.0.6", + "version": "0.0.7", "description": "Part of Computer Vision Annotation Tool which presents its canvas3D library", "main": "src/canvas3d.ts", "scripts": { diff --git a/cvat-core/package.json b/cvat-core/package.json index 4502ffcd28ea..3309cc7193b6 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "7.4.0", + "version": "8.4.0", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "src/api.ts", "scripts": { diff --git a/cvat-data/package.json b/cvat-data/package.json index 2158e87ad650..63c1be63d625 100644 --- a/cvat-data/package.json +++ b/cvat-data/package.json @@ -1,6 +1,6 @@ { "name": "cvat-data", - "version": "1.0.2", + "version": "1.1.0", "description": "", "main": "src/ts/cvat-data.ts", "scripts": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index d6222d158422..c978d757b23a 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.46.0", + "version": "1.47.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { From 1d7d7131bfc8745d9e2149f8ace283c6bea6d00b Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 09:45:17 -0800 Subject: [PATCH 14/47] Updated changelog, fixed several tests --- CHANGELOG.md | 1 + .../case_115_ellipse_shape_track_label.js | 2 +- .../actions_tasks3/case_90_context_image.js | 12 +-- ..._62_canvas3d_functionality_views_resize.js | 84 ------------------- 4 files changed, 3 insertions(+), 96 deletions(-) delete mode 100644 tests/cypress/integration/canvas3d_functionality_2/case_62_canvas3d_functionality_views_resize.js diff --git a/CHANGELOG.md b/CHANGELOG.md index cb76509d893b..28ac0a3bba0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - \[SDK\] Class to represent a project as a PyTorch dataset () +- Grid view and multiple context images supported () ### Changed - The Docker Compose files now use the Compose Specification version diff --git a/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js b/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js index 658dd0be2269..942a43bf83d3 100644 --- a/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js +++ b/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js @@ -73,7 +73,7 @@ context('Actions on ellipse.', () => { it('Ellipse rotation/interpolation.', () => { Cypress.config('scrollBehavior', false); cy.get('.cvat-player-last-button').click(); - cy.shapeRotate('#cvat_canvas_shape_4', '19.5'); + cy.shapeRotate('#cvat_canvas_shape_4', '19.7'); testCompareRotate('cvat_canvas_shape_4', 0); // Rotation with shift cy.shapeRotate('#cvat_canvas_shape_4', '15.0', true); diff --git a/tests/cypress/integration/actions_tasks3/case_90_context_image.js b/tests/cypress/integration/actions_tasks3/case_90_context_image.js index 48d8ecf1dc9e..4167e544bc7f 100644 --- a/tests/cypress/integration/actions_tasks3/case_90_context_image.js +++ b/tests/cypress/integration/actions_tasks3/case_90_context_image.js @@ -38,18 +38,8 @@ context('Context images for 2D tasks.', () => { it('Check a context image.', () => { cy.get('.cvat-context-image-wrapper').should('exist').and('be.visible'); cy.get('.cvat-player-next-button').click(); - cy.get('.cvat-context-image').should('exist').and('be.visible'); // Check a context image on the second frame + cy.get('.cvat-context-image-wrapper').should('exist').and('be.visible'); // Check a context image on the second frame cy.get('.cvat-player-previous-button').click(); }); - - it('Checking issue "Context image disappears after undo/redo".', () => { - cy.createRectangle(createRectangleShape2Points); - cy.contains('.cvat-annotation-header-button', 'Undo').click(); - cy.get('.cvat-context-image').should('have.attr', 'src'); - cy.get('#cvat_canvas_shape_1').should('not.exist'); - cy.contains('.cvat-annotation-header-button', 'Redo').click(); - cy.get('.cvat-context-image').should('have.attr', 'src'); - cy.get('#cvat_canvas_shape_1').should('exist'); - }); }); }); diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_62_canvas3d_functionality_views_resize.js b/tests/cypress/integration/canvas3d_functionality_2/case_62_canvas3d_functionality_views_resize.js deleted file mode 100644 index 6112331ad874..000000000000 --- a/tests/cypress/integration/canvas3d_functionality_2/case_62_canvas3d_functionality_views_resize.js +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -/// - -import { taskName } from '../../support/const_canvas3d'; - -context('Canvas 3D functionality. Resize views.', () => { - const caseId = '62'; - let widthHeightArrBeforeResize = []; - let widthHeightArrAfterResize = []; - - function getViewWidthHeight(element, arrToPush) { - cy.get(element) - .find('canvas') - .invoke('attr', 'width') - .then(($topviewWidth) => { - cy.get(element) - .find('canvas') - .invoke('attr', 'height') - .then(($topviewHeight) => { - arrToPush.push([$topviewWidth, $topviewHeight]); - }); - }); - } - - before(() => { - cy.openTaskJob(taskName); - getViewWidthHeight('.cvat-canvas3d-perspective', widthHeightArrBeforeResize); - getViewWidthHeight('.cvat-canvas3d-topview', widthHeightArrBeforeResize); - getViewWidthHeight('.cvat-canvas3d-sideview', widthHeightArrBeforeResize); - getViewWidthHeight('.cvat-canvas3d-frontview', widthHeightArrBeforeResize); - }); - - describe(`Testing case "${caseId}"`, () => { - it('Resizing perspective.', () => { - cy.get('.cvat-resizable-handle-horizontal').trigger('mousedown', { button: 0, scrollBehavior: false }); - cy.get('.cvat-canvas3d-perspective') - .trigger('mousemove', 600, 300, { scrollBehavior: false }) - .trigger('mouseup'); - getViewWidthHeight('.cvat-canvas3d-perspective', widthHeightArrAfterResize); - }); - - it('Resizing topview.', () => { - cy.get('.cvat-resizable-handle-vertical-top').trigger('mousedown', { button: 0, scrollBehavior: false }); - cy.get('.cvat-canvas3d-topview') - .trigger('mousemove', 200, 200, { scrollBehavior: false }) - .trigger('mouseup'); - getViewWidthHeight('.cvat-canvas3d-topview', widthHeightArrAfterResize); - }); - - it('Resizing sideview.', () => { - cy.get('.cvat-resizable-handle-vertical-side').trigger('mousedown', { button: 0, scrollBehavior: false }); - cy.get('.cvat-canvas3d-frontview') - .trigger('mousemove', 200, 200, { scrollBehavior: false }) - .trigger('mouseup'); - getViewWidthHeight('.cvat-canvas3d-sideview', widthHeightArrAfterResize); - getViewWidthHeight('.cvat-canvas3d-frontview', widthHeightArrAfterResize); - }); - - it('Checking for elements resizing.', () => { - expect(widthHeightArrBeforeResize[0][0]).to.be.equal(widthHeightArrAfterResize[0][0]); // Width of cvat-canvas3d-perspective before and after didn't change - expect(widthHeightArrBeforeResize[0][1]).not.be.equal(widthHeightArrAfterResize[0][1]); // Height of cvat-canvas3d-perspective changed - expect(widthHeightArrAfterResize[1][1]) - .to.be.equal(widthHeightArrAfterResize[2][1]) - .to.be.equal(widthHeightArrAfterResize[3][1]); // Top/side/front has equal height after changes - [ - [widthHeightArrBeforeResize[1][0], widthHeightArrAfterResize[1][0]], - [widthHeightArrBeforeResize[2][0], widthHeightArrAfterResize[2][0]], - [widthHeightArrBeforeResize[3][0], widthHeightArrAfterResize[3][0]], - ].forEach(([widthBefore, widthAfter]) => { - expect(widthBefore).not.be.equal(widthAfter); // Width of top/side/front changed - }); - [ - [widthHeightArrBeforeResize[1][1], widthHeightArrAfterResize[1][1]], - [widthHeightArrBeforeResize[2][1], widthHeightArrAfterResize[2][1]], - [widthHeightArrBeforeResize[3][1], widthHeightArrAfterResize[3][1]], - ].forEach(([heightBefore, heightAfter]) => { - expect(heightBefore).not.be.equal(heightAfter); // Height of top/side/front changed - }); - }); - }); -}); From 1c4fdb4e53978ac9088f711e98c8d116e9890975 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Jan 2023 23:05:58 -0800 Subject: [PATCH 15/47] Fixed eslint issue --- .../integration/actions_tasks3/case_90_context_image.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/cypress/integration/actions_tasks3/case_90_context_image.js b/tests/cypress/integration/actions_tasks3/case_90_context_image.js index 4167e544bc7f..4bac12c98d3d 100644 --- a/tests/cypress/integration/actions_tasks3/case_90_context_image.js +++ b/tests/cypress/integration/actions_tasks3/case_90_context_image.js @@ -12,15 +12,6 @@ context('Context images for 2D tasks.', () => { const attrName = `Attr for ${labelName}`; const textDefaultValue = 'color'; const pathToArchive = `../../${__dirname}/assets/case_90/case_90_context_image.zip`; - const createRectangleShape2Points = { - points: 'By 2 Points', - type: 'Shape', - labelName, - firstX: 250, - firstY: 350, - secondX: 350, - secondY: 450, - }; before(() => { cy.visit('auth/login'); From 36b3fcaa42d0fc07b15591ce03f5888e826cc475 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 4 Jan 2023 02:40:27 -0800 Subject: [PATCH 16/47] Fixed tests --- cvat-core/webpack.config.js | 2 +- cvat-data/src/ts/cvat-data.ts | 2 +- cvat-data/src/ts/unzip_imgs.worker.js | 4 ++-- cvat-data/webpack.config.js | 4 ++-- .../case_97_export_import_task.js | 2 +- ..._canvas3d_functionality_cuboid_grouping.js | 22 +++++++++---------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cvat-core/webpack.config.js b/cvat-core/webpack.config.js index 24098cf87608..cad51dbb44a1 100644 --- a/cvat-core/webpack.config.js +++ b/cvat-core/webpack.config.js @@ -55,7 +55,7 @@ const webConfig = { output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].min.js', - library: 'cvat', + library: 'cvat-core.js', libraryTarget: 'window', }, resolve: { diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 842ce90d2dd9..389742ba3225 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -5,7 +5,7 @@ import { Mutex } from 'async-mutex'; import { MP4Reader, Bytestream } from './3rdparty/mp4'; -import * as ZipDecoder from './unzip_imgs.worker'; +import ZipDecoder from './unzip_imgs.worker'; import H264Decoder from './3rdparty/Decoder.worker'; export enum BlockType { diff --git a/cvat-data/src/ts/unzip_imgs.worker.js b/cvat-data/src/ts/unzip_imgs.worker.js index 706432e0c6dd..a4ed18f78b33 100644 --- a/cvat-data/src/ts/unzip_imgs.worker.js +++ b/cvat-data/src/ts/unzip_imgs.worker.js @@ -3,7 +3,8 @@ // // SPDX-License-Identifier: MIT -import JSZip from 'jszip'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const JSZip = require('jszip'); onmessage = (e) => { const zip = new JSZip(); @@ -20,7 +21,6 @@ onmessage = (e) => { _zip.file(relativePath) .async('blob') .then((fileData) => { - // eslint-disable-next-line no-restricted-globals if (dimension === dimension2D) { createImageBitmap(fileData).then((img) => { postMessage({ diff --git a/cvat-data/webpack.config.js b/cvat-data/webpack.config.js index 71f8663f72e3..c90d0f3d6f54 100644 --- a/cvat-data/webpack.config.js +++ b/cvat-data/webpack.config.js @@ -13,7 +13,7 @@ const cvatData = { target: 'web', mode: 'production', entry: { - 'cvat-data': './src/js/cvat-data.ts', + 'cvat-data': './src/ts/cvat-data.ts', }, output: { path: path.resolve(__dirname, 'dist'), @@ -66,7 +66,7 @@ const cvatData = { }, ], }, - plugins: [new CopyPlugin({ patterns: ['./src/js/3rdparty/avc.wasm'] })], + plugins: [new CopyPlugin({ patterns: ['./src/ts/3rdparty/avc.wasm'] })], }; module.exports = cvatData; diff --git a/tests/cypress/integration/actions_tasks2/case_97_export_import_task.js b/tests/cypress/integration/actions_tasks2/case_97_export_import_task.js index dc5ff8ba4c42..37360f11bb48 100644 --- a/tests/cypress/integration/actions_tasks2/case_97_export_import_task.js +++ b/tests/cypress/integration/actions_tasks2/case_97_export_import_task.js @@ -61,7 +61,7 @@ context('Export, import an annotation task.', { browser: '!firefox' }, () => { .trigger('mousemove') .trigger('mouseover'); cy.get('.svg_select_points_rot').trigger('mousedown', { button: 0 }); - cy.get('.cvat-canvas-container').trigger('mousemove', 340, 150); + cy.get('.cvat-canvas-container').trigger('mousemove', 345, 150); cy.get('.cvat-canvas-container').trigger('mouseup'); cy.get('#cvat_canvas_shape_1').should('have.attr', 'transform'); cy.document().then((doc) => { diff --git a/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js b/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js index 324ab9b66da4..0c405807a3a5 100644 --- a/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js +++ b/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js @@ -14,22 +14,22 @@ context('Canvas 3D functionality. Grouping.', () => { const screenshotsPath = 'cypress/screenshots/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js'; const firstCuboidCreationParams = { labelName, - x: 480, - y: 150, + x: 400, + y: 200, }; const secondCuboidCreationParams = { labelName, - x: 480, - y: 200, + x: 400, + y: 280, }; const thirdCuboidCreationParams = { labelName, - x: 530, - y: 150, + x: 500, + y: 280, }; const fourthCuboidCreationParams = { labelName, - x: 530, + x: 500, y: 200, }; const yellowHex = 'fcbe03'; @@ -64,8 +64,8 @@ context('Canvas 3D functionality. Grouping.', () => { describe(`Testing case "${caseId}"`, () => { it('Grouping two cuboids.', () => { cy.get('.cvat-group-control').click(); - cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 480, 200).click(480, 200); - cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 530, 150).click(530, 150); + cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 400, 280).click(400, 280); + cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 500, 280).click(500, 280); cy.get('.cvat-group-control').click(); cy.changeAppearance('Group'); cy.get('#cvat-objects-sidebar-state-item-1').invoke('attr', 'style').then((bgColorItem1) => { @@ -105,8 +105,8 @@ context('Canvas 3D functionality. Grouping.', () => { it('Reset group.', () => { cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_before_reset_group'); cy.get('.cvat-group-control').click(); - cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 480, 200).click(480, 200); - cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 530, 150).click(530, 150); + cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 400, 280).click(400, 280); + cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 500, 280).click(500, 280); cy.get('body').type('{Shift}g'); cy.get('#cvat-objects-sidebar-state-item-2').invoke('attr', 'style').then((bgColorItem2) => { expect(bgColorItem).to.be.equal(bgColorItem2); From a567d9be5009f2372dbab8fde36c4c8d06e27da0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 4 Jan 2023 04:52:44 -0800 Subject: [PATCH 17/47] Updated license headers --- .../case_83_canvas3d_functionality_cuboid_grouping.js | 2 +- .../case_56_canvas3d_functionality_basic_actions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js b/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js index 0c405807a3a5..0f189ff0214c 100644 --- a/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js +++ b/tests/cypress/integration/canvas3d_functionality/case_83_canvas3d_functionality_cuboid_grouping.js @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js index c0d86396ddda..32c49302fa23 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT From 248d4e36ebe740de5ee3e07887d614fe7816e396 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Jan 2023 00:00:11 -0800 Subject: [PATCH 18/47] Resizing raw height after resizing the browser window --- .../canvas/grid-layout/canvas-layout.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 8e39e05bffaf..db113a61a324 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -83,7 +83,17 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { return defaultLayout.CANVAS_3D_THREE_PLUS_RELATED; }, [type, relatedFiles]); + const onUpdateRawHeight = (): void => { + const container = window.document.getElementsByClassName('cvat-annotation-layout-content')[0]; + if (container) { + const { height } = container.getBoundingClientRect(); + // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 + setRowHeight(Math.floor((height - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS)); + } + }; + const onLayoutChange = useCallback(() => { + onUpdateRawHeight(); if (canvasInstance) { canvasInstance.fitCanvas(); canvasInstance.fit(); @@ -91,13 +101,6 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }, [canvasInstance]); useEffect(() => { - const container = window.document.getElementsByClassName('cvat-annotation-layout-content')[0]; - if (container) { - const { height } = container.getBoundingClientRect(); - // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 - setRowHeight(Math.floor((height - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS)); - } - setTimeout(() => { if (canvasInstance) { canvasInstance.fitCanvas(); @@ -107,7 +110,11 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }, []); useEffect(() => { - const onResize = onLayoutChange; + const onResize = (): void => { + onLayoutChange(); + onUpdateRawHeight(); + }; + window.addEventListener('resize', onResize); return () => { window.removeEventListener('resize', onResize); From d6eb4e51ff5f23ae65d216ee87f83107a45b7ef1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Jan 2023 00:17:36 -0800 Subject: [PATCH 19/47] Fix one corner case --- .../canvas/grid-layout/canvas-layout.conf.tsx | 2 +- .../views/context-image/context-image.tsx | 17 +++++++++++++---- .../canvas/views/context-image/styles.scss | 4 +++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx index 8fab70d80450..119c82c95b3f 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx @@ -58,7 +58,7 @@ defaultLayout.CANVAS_THREE_PLUS_RELATED = [ { ...defaultLayout.CANVAS_TWO_RELATED[1], viewIndex: '3', - offset: [0, 2], + offset: [0, 5], y: 6, }, ]; diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index fdde14f33688..49f2c58c3913 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -27,6 +27,7 @@ function ContextImage(props: Props): JSX.Element { const frameIndex = frame + (offset[0] || 0); const [contextImageData, setContextImageData] = useState([]); + const [fetching, setFetching] = useState(false); const [contextImageOffset, setContextImageOffset] = useState( Math.min(offset[1] || 0, relatedFiles), ); @@ -37,6 +38,7 @@ function ContextImage(props: Props): JSX.Element { useEffect(() => { let unmounted = false; const promise = job.frames.contextImage(frameIndex); + setFetching(true); promise.then((imageBitmaps: ImageBitmap[]) => { if (!unmounted) { setContextImageData(imageBitmaps); @@ -49,6 +51,10 @@ function ContextImage(props: Props): JSX.Element { description: error.toString(), }); } + }).finally(() => { + if (!unmounted) { + setFetching(false); + } }); return () => { @@ -61,7 +67,7 @@ function ContextImage(props: Props): JSX.Element { if (canvasRef.current) { const image = contextImageData[contextImageOffset]; const context = canvasRef.current.getContext('2d'); - if (context) { + if (context && image) { canvasRef.current.width = image.width; canvasRef.current.height = image.height; context.drawImage(image, 0, 0); @@ -83,9 +89,12 @@ function ContextImage(props: Props): JSX.Element { /> )}
- { hasError && } - { relatedFiles && contextImageData.length === 0 && !hasError && } - { contextImageData.length > 0 && } + { (hasError || (!fetching && contextImageOffset >= contextImageData.length)) && } + { fetching && } + { + contextImageOffset < contextImageData.length && + + } { showSelector && ( .ant-empty { width: 100%; - height: 100%; + height: 60%; + margin-top: 20%; + margin-bottom: 20%; > .ant-empty-image { height: 60%; From fd10e12f718a2502dcbd878e4829b0322ea77862 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Jan 2023 00:40:13 -0800 Subject: [PATCH 20/47] Added header & button --- .../canvas/grid-layout/canvas-layout.conf.tsx | 2 +- .../context-image/context-image-selector.tsx | 12 ++++++++-- .../canvas/views/context-image/styles.scss | 22 ++++++++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx index 119c82c95b3f..8fab70d80450 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx @@ -58,7 +58,7 @@ defaultLayout.CANVAS_THREE_PLUS_RELATED = [ { ...defaultLayout.CANVAS_TWO_RELATED[1], viewIndex: '3', - offset: [0, 5], + offset: [0, 2], y: 6, }, ]; diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx index 3a839a37df2c..bd867756e12e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx @@ -5,6 +5,8 @@ import React, { useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; +import Text from 'antd/lib/typography/Text'; +import { CloseOutlined } from '@ant-design/icons'; interface Props { images: ImageBitmap[]; @@ -33,7 +35,7 @@ function CanvasWithRef({ ); } @@ -45,7 +47,13 @@ function ContextImageSelector(props: Props): React.ReactPortal { return ReactDOM.createPortal((
-
+
+
+ + Click the image to display it as a context image + + +
{ images.map((image: ImageBitmap, i: number) => ( Date: Thu, 5 Jan 2023 02:36:17 -0800 Subject: [PATCH 21/47] Tried to fix two tests --- ...as3d_functionality_save_job_remove_annotation.js | 13 ++++++------- .../case_56_canvas3d_functionality_basic_actions.js | 9 ++++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/cypress/integration/canvas3d_functionality/case_88_canvas3d_functionality_save_job_remove_annotation.js b/tests/cypress/integration/canvas3d_functionality/case_88_canvas3d_functionality_save_job_remove_annotation.js index d262052c7580..14a34e91f487 100644 --- a/tests/cypress/integration/canvas3d_functionality/case_88_canvas3d_functionality_save_job_remove_annotation.js +++ b/tests/cypress/integration/canvas3d_functionality/case_88_canvas3d_functionality_save_job_remove_annotation.js @@ -1,7 +1,10 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT +/* eslint-disable cypress/no-unnecessary-waiting */ + /// import { taskName, labelName } from '../../support/const_canvas3d'; @@ -11,8 +14,9 @@ context('Canvas 3D functionality. Save a job. Remove annotations.', () => { const screenshotsPath = 'cypress/screenshots/canvas3d_functionality/case_88_canvas3d_functionality_save_job_remove_annotation.js'; const cuboidCreationParams = { - labelName: labelName, + labelName, }; + const waitTime = 2000; before(() => { cy.openTask(taskName); @@ -24,20 +28,14 @@ context('Canvas 3D functionality. Save a job. Remove annotations.', () => { describe(`Testing case "${caseId}"`, () => { it('Save a job. Reopen the job.', () => { - const waitTime = 1000; - cy.wait(waitTime); cy.saveJob('PATCH', 200, 'saveJob'); - cy.wait(waitTime); cy.goToTaskList(); - cy.wait(waitTime); cy.openTaskJob(taskName); - cy.wait(waitTime); // Waiting for the point cloud to display cy.get('.cvat-objects-sidebar-state-item').then((sidebarStateItem) => { expect(sidebarStateItem.length).to.be.equal(1); }); cy.wait(waitTime); cy.customScreenshot('.cvat-canvas3d-topview', 'canvas3d_topview_after_reopen_job'); - cy.wait(waitTime); cy.compareImagesAndCheckResult( `${screenshotsPath}/canvas3d_topview_before_all.png`, `${screenshotsPath}/canvas3d_topview_after_reopen_job.png`, @@ -49,6 +47,7 @@ context('Canvas 3D functionality. Save a job. Remove annotations.', () => { cy.saveJob('PUT'); cy.contains('Saving changes on the server').should('be.hidden'); cy.get('.cvat-objects-sidebar-state-item').should('not.exist'); + cy.wait(waitTime); cy.customScreenshot('.cvat-canvas3d-topview', 'canvas3d_topview_after_remove_annotations'); cy.compareImagesAndCheckResult( `${screenshotsPath}/canvas3d_topview_after_reopen_job.png`, diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js index 32c49302fa23..191b2f471978 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js @@ -52,9 +52,12 @@ context('Canvas 3D functionality. Basic actions.', () => { function testTopSideFrontChangeOnWheel(element, screenshotNameBefore, screenshotNameAfter) { cy.customScreenshot(element, screenshotNameBefore); - for (let i = 0; i < 3; i++) { - cy.get(element).trigger('wheel', { deltaY: -100 }); - } + cy.get(element).within(() => { + for (let i = 0; i < 3; i++) { + cy.get('.cvat-canvas3d-fullsize').trigger('wheel', { deltaY: -100 }); + } + }); + cy.customScreenshot(element, screenshotNameAfter); cy.compareImagesAndCheckResult( `${screenshotsPath}/${screenshotNameBefore}.png`, From 3b0ac10ac862875670154d9aa4f76e89bccdfa85 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Jan 2023 09:52:05 -0800 Subject: [PATCH 22/47] Reworked screenshot command --- ...56_canvas3d_functionality_basic_actions.js | 4 ++-- tests/cypress/support/commands_canvas3d.js | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js index 191b2f471978..349c0575ed47 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_56_canvas3d_functionality_basic_actions.js @@ -41,7 +41,7 @@ context('Canvas 3D functionality. Basic actions.', () => { function testPerspectiveChangeOnWheel(screenshotNameBefore, screenshotNameAfter) { cy.customScreenshot('.cvat-canvas3d-perspective', screenshotNameBefore); for (let i = 0; i < 3; i++) { - cy.get('.cvat-canvas3d-perspective').trigger('wheel', { deltaY: -50 }); + cy.get('.cvat-canvas3d-perspective canvas').trigger('wheel', { deltaY: -50 }); } cy.customScreenshot('.cvat-canvas3d-perspective', screenshotNameAfter); cy.compareImagesAndCheckResult( @@ -54,7 +54,7 @@ context('Canvas 3D functionality. Basic actions.', () => { cy.customScreenshot(element, screenshotNameBefore); cy.get(element).within(() => { for (let i = 0; i < 3; i++) { - cy.get('.cvat-canvas3d-fullsize').trigger('wheel', { deltaY: -100 }); + cy.get('.cvat-canvas3d-fullsize canvas').trigger('wheel', { deltaY: -100 }); } }); diff --git a/tests/cypress/support/commands_canvas3d.js b/tests/cypress/support/commands_canvas3d.js index 3343429ed1ca..a19873bed37f 100644 --- a/tests/cypress/support/commands_canvas3d.js +++ b/tests/cypress/support/commands_canvas3d.js @@ -29,14 +29,17 @@ Cypress.Commands.add('create3DCuboid', (cuboidCreationParams) => { }); Cypress.Commands.add('customScreenshot', (element, screenshotName) => { - let getEl; - let padding; - if (element.includes('perspective')) { - getEl = cy.get(element); - padding = -130; - } else { - getEl = cy.get(element).find('.cvat-canvas3d-fullsize'); - padding = -40; - } - getEl.screenshot(screenshotName, { padding }); + cy.get(element).then(([$el]) => { + const rect = $el.getBoundingClientRect(); + cy.wrap(rect); + }).then((rect) => { + cy.log(rect); + cy.screenshot(screenshotName, { + overwrite: true, + capture: 'fullPage', + clip: { + x: rect.x, y: rect.y, width: rect.width, height: rect.height, + }, + }); + }); }); From 1adb3ec28934be189dadb673f67e0342cbc98739 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 5 Jan 2023 10:04:00 -0800 Subject: [PATCH 23/47] Removed extra commands --- tests/cypress/support/commands_canvas3d.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/cypress/support/commands_canvas3d.js b/tests/cypress/support/commands_canvas3d.js index a19873bed37f..a99f332f1402 100644 --- a/tests/cypress/support/commands_canvas3d.js +++ b/tests/cypress/support/commands_canvas3d.js @@ -29,10 +29,7 @@ Cypress.Commands.add('create3DCuboid', (cuboidCreationParams) => { }); Cypress.Commands.add('customScreenshot', (element, screenshotName) => { - cy.get(element).then(([$el]) => { - const rect = $el.getBoundingClientRect(); - cy.wrap(rect); - }).then((rect) => { + cy.get(element).then(([$el]) => $el.getBoundingClientRect()).then((rect) => { cy.log(rect); cy.screenshot(screenshotName, { overwrite: true, From e55bf568b6e6a417cdbc903e6bb09818ca64b4c8 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Jan 2023 05:14:27 -0800 Subject: [PATCH 24/47] Added context images names --- cvat-core/src/frames.ts | 4 +- cvat-data/src/ts/cvat-data.ts | 6 ++- .../context-image/context-image-selector.tsx | 43 +++++++++++-------- .../views/context-image/context-image.tsx | 17 ++++---- .../canvas/views/context-image/styles.scss | 37 +++++++++++----- cvat/apps/engine/views.py | 5 ++- 6 files changed, 67 insertions(+), 45 deletions(-) diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 8b2fb79a9034..10a97e61f9e7 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -372,9 +372,7 @@ class FrameBuffer { resolve(this.getContextImage(frame)); }); } else { - resolve(Object.keys(this._contextImage[frame]) - .map((key: string): number => +key).sort() - .map((key: number) => this._contextImage[frame][key])); + resolve({ ...this._contextImage[frame] }); } } else { resolve([]); diff --git a/cvat-data/src/ts/cvat-data.ts b/cvat-data/src/ts/cvat-data.ts index 389742ba3225..b60d0a82384d 100644 --- a/cvat-data/src/ts/cvat-data.ts +++ b/cvat-data/src/ts/cvat-data.ts @@ -18,11 +18,13 @@ export enum DimensionType { DIMENSION_2D = '2d', } -export function decodeZip(block: any, start: number, end: number, dimension: any): Promise { +export function decodeZip( + block: any, start: number, end: number, dimension: any, +): Promise> { return new Promise((resolve, reject) => { decodeZip.mutex.acquire().then((release) => { const worker = new ZipDecoder(); - const result: Record = {}; + const result: Record = {}; let decoded = 0; worker.onerror = (e: ErrorEvent) => { diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx index bd867756e12e..1e9a2a31173a 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx @@ -9,15 +9,15 @@ import Text from 'antd/lib/typography/Text'; import { CloseOutlined } from '@ant-design/icons'; interface Props { - images: ImageBitmap[]; + images: Record; offset: number; onChangeOffset: (offset: number) => void; onClose: () => void; } function CanvasWithRef({ - image, isActive, onClick, -}: { image: ImageBitmap, isActive: boolean, onClick: () => void }): JSX.Element { + image, isActive, onClick, name, +}: { image: ImageBitmap, name: string, isActive: boolean, onClick: () => void }): JSX.Element { const ref = useRef(null); useEffect((): void => { @@ -32,11 +32,13 @@ function CanvasWithRef({ }, [image, ref]); return ( - +
+ {name} + +
); } @@ -54,17 +56,20 @@ function ContextImageSelector(props: Props): React.ReactPortal {
- { images.map((image: ImageBitmap, i: number) => ( - { - onChangeOffset(i); - onClose(); - }} - key={i} - /> - ))} +
+ { Object.entries(images).map(([key, value], i: number) => ( + { + onChangeOffset(i); + onClose(); + }} + key={i} + /> + ))} +
), window.document.body); diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index 49f2c58c3913..11ce9881c6aa 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -26,7 +26,7 @@ function ContextImage(props: Props): JSX.Element { const { number: frame, relatedFiles } = useSelector((state: CombinedState) => state.annotation.player.frame); const frameIndex = frame + (offset[0] || 0); - const [contextImageData, setContextImageData] = useState([]); + const [contextImageData, setContextImageData] = useState>({}); const [fetching, setFetching] = useState(false); const [contextImageOffset, setContextImageOffset] = useState( Math.min(offset[1] || 0, relatedFiles), @@ -39,7 +39,7 @@ function ContextImage(props: Props): JSX.Element { let unmounted = false; const promise = job.frames.contextImage(frameIndex); setFetching(true); - promise.then((imageBitmaps: ImageBitmap[]) => { + promise.then((imageBitmaps: Record) => { if (!unmounted) { setContextImageData(imageBitmaps); } @@ -58,14 +58,16 @@ function ContextImage(props: Props): JSX.Element { }); return () => { - setContextImageData([]); + setContextImageData({}); unmounted = true; }; }, [frameIndex]); useEffect(() => { if (canvasRef.current) { - const image = contextImageData[contextImageOffset]; + const sortedKeys = Object.keys(contextImageData).sort(); + const key = sortedKeys[contextImageOffset]; + const image = contextImageData[key]; const context = canvasRef.current.getContext('2d'); if (context && image) { canvasRef.current.width = image.width; @@ -78,8 +80,7 @@ function ContextImage(props: Props): JSX.Element { return (
- Context image - {contextImageOffset} + {Object.keys(contextImageData).sort()[contextImageOffset]} { relatedFiles > 1 && ( )}
- { (hasError || (!fetching && contextImageOffset >= contextImageData.length)) && } + { (hasError || (!fetching && contextImageOffset >= Object.keys(contextImageData).length)) && } { fetching && } { - contextImageOffset < contextImageData.length && + contextImageOffset < Object.keys(contextImageData).length && } { showSelector && ( diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 7c3b0a4ffdce..44e625dedc99 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -35,6 +35,11 @@ text-align: center; z-index: 1; background: $header-color; + overflow: hidden; + + > span.ant-typography { + line-height: $grid-unit-size * 4; + } > .cvat-context-image-setup-button { position: absolute; @@ -71,9 +76,9 @@ z-index: 1000; background: rgba(255, 255, 255, 0.25); position: absolute; - display: flex; justify-content: space-around; align-items: center; + display: flex; .cvat-context-image-gallery { width: 80%; @@ -86,18 +91,28 @@ overflow: hidden; overflow-y: auto; - .cvat-context-image-gallery-item { - &.cvat-context-image-gallery-item-current { - opacity: 1; - } + .cvat-context-image-gallery-items { + display: block; - &:hover { - opacity: 0.9; - } + .cvat-context-image-gallery-item { + text-align: center; + padding: $grid-unit-size; + opacity: 0.6; + width: 25%; + float: left; - padding: $grid-unit-size; - opacity: 0.6; - width: 25%; + &.cvat-context-image-gallery-item-current { + opacity: 1; + } + + &:hover { + opacity: 0.9; + } + + > canvas { + width: 100%; + } + } } .cvat-context-image-gallery-header { diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 915c3cb81e4c..1aefd54deb5e 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -730,13 +730,14 @@ def __call__(self, request, start, stop, db_data): return Response(data='No context image related to the frame', status=status.HTTP_404_NOT_FOUND) - for idx, i in enumerate(image.related_files.all()): + for i in image.related_files.all(): path = os.path.realpath(str(i.path)) + name = os.path.relpath(str(i.path), db_data.get_upload_dirname()) image = cv2.imread(path) success, result = cv2.imencode('.JPEG', image) if not success: raise Exception('Failed to encode image to ".jpeg" format') - zip_file.writestr(f'{str(idx)}.jpg', result.tobytes()) + zip_file.writestr(f'{name}.jpg', result.tobytes()) # response = HttpResponse(wrapper, content_type='application/zip') # response['Content-Disposition'] = 'attachment; filename=your_zipfile.zip' return HttpResponse(io.BytesIO(zip_buffer.getvalue()), content_type='application/zip') From fc38ebb65beeef52fc8a112e678c194d2203efb3 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Jan 2023 06:45:16 -0800 Subject: [PATCH 25/47] Go to/from fullscreen --- .../canvas/grid-layout/canvas-layout.tsx | 50 ++++++++++++++----- .../canvas/grid-layout/styles.scss | 32 +++++++++++- .../views/context-image/context-image.tsx | 2 +- .../canvas/views/context-image/styles.scss | 11 ++-- 4 files changed, 75 insertions(+), 20 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index db113a61a324..9bf40601d0db 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -10,7 +10,7 @@ import { useSelector } from 'react-redux'; import RGL, { WidthProvider } from 'react-grid-layout'; import PropTypes from 'prop-types'; import Layout from 'antd/lib/layout'; -import { DragOutlined } from '@ant-design/icons'; +import { DragOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; import { DimensionType, CombinedState } from 'reducers'; import CanvasWrapperComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-wrapper'; @@ -26,8 +26,7 @@ import defaultLayout, { ItemLayout, ViewType } from './canvas-layout.conf'; const ReactGridLayout = WidthProvider(RGL); const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { - const { viewType: type, offset, viewIndex } = itemLayout; - const key = typeof viewIndex !== 'undefined' ? `${type}_${viewIndex}` : `${type}`; + const { viewType: type, offset } = itemLayout; let component = null; switch (type) { @@ -53,12 +52,7 @@ const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { component =
Undefined view
; } - return ( -
- - { component } -
- ); + return component; }; function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { @@ -66,6 +60,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const MARGIN = 8; const PADDING = MARGIN / 2; const [rowHeight, setRowHeight] = useState(Math.floor(window.screen.availHeight / NUM_OF_ROWS)); + const [fullscreenKey, setFullscreenKey] = useState(''); const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); const canvasBackgroundColor = useSelector((state: CombinedState) => state.settings.player.canvasBackgroundColor); @@ -121,8 +116,9 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }; }, [onLayoutChange]); - const children = getLayout().map((value: ItemLayout) => ViewFabric((value))); - const layout = getLayout().map((value: ItemLayout) => ({ + const layoutConfig = getLayout(); + const children = layoutConfig.map((value: ItemLayout) => ViewFabric(value)); + const layout = layoutConfig.map((value: ItemLayout) => ({ x: value.x, y: value.y, w: value.w, @@ -147,7 +143,37 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { )} draggableHandle='.cvat-grid-item-drag-handler' > - { children } + { children.map((child: JSX.Element, idx: number): JSX.Element => { + const { viewType, viewIndex } = layoutConfig[idx]; + const key = typeof viewIndex !== 'undefined' ? `${viewType}_${viewIndex}` : `${viewType}`; + return ( +
+ + {fullscreenKey === key ? ( + { + setFullscreenKey(''); + }} + /> + ) : ( + { + setFullscreenKey(key); + }} + /> + )} + + { child } +
+ ); + }) } { type === DimensionType.DIM_3D && } diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index ab8392971b94..11212ea02cd9 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -12,15 +12,43 @@ background-color: rgba(241, 241, 241, 0.7); border-radius: 4px; - .cvat-grid-item-drag-handler { + &.cvat-canvas-grid-fullscreen-item { + width: 100% !important; + height: 100% !important; + transform: none !important; + z-index: 1; + + .cvat-grid-item-resize-handler.react-resizable-handle, + .cvat-grid-item-drag-handler { + visibility: hidden; + } + } + + .cvat-grid-item-fullscreen-handler { + left: $grid-unit-size * 4; + } + + .cvat-grid-item-drag-handler, + .cvat-grid-item-fullscreen-handler { position: absolute; top: $grid-unit-size; - left: $grid-unit-size; + left: $grid-unit-size * 4; z-index: 1000; cursor: move; font-size: 16px; background: $header-color; border-radius: 2px; + opacity: 0.6; + transition: all 200ms; + + &:hover { + opacity: 0.9; + } + + &.cvat-grid-item-fullscreen-handler { + left: $grid-unit-size; + cursor: default; + } } .cvat-grid-item-resize-handler.react-resizable-handle { diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index 11ce9881c6aa..030935306e1e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -80,7 +80,6 @@ function ContextImage(props: Props): JSX.Element { return (
- {Object.keys(contextImageData).sort()[contextImageOffset]} { relatedFiles > 1 && ( )} + {Object.keys(contextImageData).sort()[contextImageOffset]}
{ (hasError || (!fetching && contextImageOffset >= Object.keys(contextImageData).length)) && } { fetching && } diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 44e625dedc99..a60f55f7c728 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -42,13 +42,10 @@ } > .cvat-context-image-setup-button { - position: absolute; - top: $grid-unit-size; - left: $grid-unit-size * 4; font-size: 16px; opacity: 0; - border-radius: 2px; transition: all 200ms; + margin-right: $grid-unit-size; } } @@ -63,7 +60,11 @@ &:hover { > .cvat-context-image-header > .cvat-context-image-setup-button { - opacity: 1; + opacity: 0.6; + + &:hover { + opacity: 0.9; + } } } } From 0a523f91f5e046c99609e789cdbd819e2a4c1e57 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Jan 2023 07:40:07 -0800 Subject: [PATCH 26/47] Adjusted transition events --- .../canvas/grid-layout/canvas-layout.tsx | 26 ++++++++++++++----- .../canvas/grid-layout/styles.scss | 10 ++++++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 9bf40601d0db..df7b0cc695fc 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -105,14 +105,9 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }, []); useEffect(() => { - const onResize = (): void => { - onLayoutChange(); - onUpdateRawHeight(); - }; - - window.addEventListener('resize', onResize); + window.addEventListener('resize', onLayoutChange); return () => { - window.removeEventListener('resize', onResize); + window.removeEventListener('resize', onLayoutChange); }; }, [onLayoutChange]); @@ -126,6 +121,21 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { i: typeof (value.viewIndex) !== 'undefined' ? `${value.viewType}_${value.viewIndex}` : `${value.viewType}`, })); + const getChildByIndex = (idx: number): Element | undefined => { + const [root] = window.document.getElementsByClassName('cvat-canvas-grid-root'); + return Array.from(root.children) + .filter((_child: Element) => _child.classList.contains('cvat-canvas-grid-item'))[idx]; + }; + + const dispatchResizeOnTransitionEnd = (idx: number): void => { + const child = getChildByIndex(idx); + if (child) { + child.addEventListener('transitionend', () => { + window.dispatchEvent(new Event('resize')); + }, { once: true }); + } + }; + return ( { + dispatchResizeOnTransitionEnd(idx); setFullscreenKey(''); }} /> @@ -165,6 +176,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { { + dispatchResizeOnTransitionEnd(idx); setFullscreenKey(key); }} /> diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index 11212ea02cd9..351c1e081d43 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -8,14 +8,22 @@ position: relative; } +.cvat-canvas-grid-fullscreen-item ~ .cvat-canvas-grid-item { + visibility: hidden; +} + .cvat-canvas-grid-item { background-color: rgba(241, 241, 241, 0.7); border-radius: 4px; + &.react-grid-item.cssTransforms { + transition-property: all; + } + &.cvat-canvas-grid-fullscreen-item { width: 100% !important; height: 100% !important; - transform: none !important; + transform: translate(4px, 4px) !important; z-index: 1; .cvat-grid-item-resize-handler.react-resizable-handle, From 5743c7eb5f2d7d8bac2658f29e69eaba91060648 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 6 Jan 2023 09:20:09 -0800 Subject: [PATCH 27/47] Bugfixes and improvements --- .../canvas/grid-layout/canvas-layout.tsx | 72 +++++++++---------- .../canvas/grid-layout/styles.scss | 5 +- .../context-image/context-image-selector.tsx | 6 +- .../canvas/views/context-image/styles.scss | 5 +- cvat/apps/engine/views.py | 18 ++++- 5 files changed, 58 insertions(+), 48 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index df7b0cc695fc..1a32ea6b83b9 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -59,6 +59,17 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const NUM_OF_ROWS = 12; const MARGIN = 8; const PADDING = MARGIN / 2; + const computeRowHeight = (): number => { + const container = window.document.getElementsByClassName('cvat-annotation-header')[0]; + if (container) { + const height = window.innerHeight - container.getBoundingClientRect().bottom; + // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 + return Math.floor((height - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS); + } + + return window.innerHeight; + }; + const [rowHeight, setRowHeight] = useState(Math.floor(window.screen.availHeight / NUM_OF_ROWS)); const [fullscreenKey, setFullscreenKey] = useState(''); const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); @@ -78,17 +89,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { return defaultLayout.CANVAS_3D_THREE_PLUS_RELATED; }, [type, relatedFiles]); - const onUpdateRawHeight = (): void => { - const container = window.document.getElementsByClassName('cvat-annotation-layout-content')[0]; - if (container) { - const { height } = container.getBoundingClientRect(); - // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 - setRowHeight(Math.floor((height - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS)); - } - }; - - const onLayoutChange = useCallback(() => { - onUpdateRawHeight(); + const fitCanvas = useCallback(() => { if (canvasInstance) { canvasInstance.fitCanvas(); canvasInstance.fit(); @@ -96,20 +97,25 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }, [canvasInstance]); useEffect(() => { - setTimeout(() => { - if (canvasInstance) { - canvasInstance.fitCanvas(); - canvasInstance.fit(); + const onResize = (): void => { + setRowHeight(computeRowHeight()); + const [el] = window.document.getElementsByClassName('cvat-canvas-grid-root'); + if (el) { + el.addEventListener('transitionend', () => { + fitCanvas(); + }, { once: true }); } - }); - }, []); + }; - useEffect(() => { - window.addEventListener('resize', onLayoutChange); + window.addEventListener('resize', onResize); return () => { - window.removeEventListener('resize', onLayoutChange); + window.removeEventListener('resize', onResize); }; - }, [onLayoutChange]); + }, [fitCanvas]); + + useEffect(() => { + window.dispatchEvent(new Event('resize')); + }, []); const layoutConfig = getLayout(); const children = layoutConfig.map((value: ItemLayout) => ViewFabric(value)); @@ -121,21 +127,6 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { i: typeof (value.viewIndex) !== 'undefined' ? `${value.viewType}_${value.viewIndex}` : `${value.viewType}`, })); - const getChildByIndex = (idx: number): Element | undefined => { - const [root] = window.document.getElementsByClassName('cvat-canvas-grid-root'); - return Array.from(root.children) - .filter((_child: Element) => _child.classList.contains('cvat-canvas-grid-item'))[idx]; - }; - - const dispatchResizeOnTransitionEnd = (idx: number): void => { - const child = getChildByIndex(idx); - if (child) { - child.addEventListener('transitionend', () => { - window.dispatchEvent(new Event('resize')); - }, { once: true }); - } - }; - return ( ) => (
)} @@ -158,6 +149,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const key = typeof viewIndex !== 'undefined' ? `${viewType}_${viewIndex}` : `${viewType}`; return (
{ - dispatchResizeOnTransitionEnd(idx); + window.dispatchEvent(new Event('resize')); setFullscreenKey(''); }} /> @@ -176,7 +168,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { { - dispatchResizeOnTransitionEnd(idx); + window.dispatchEvent(new Event('resize')); setFullscreenKey(key); }} /> diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index 351c1e081d43..8b5f1a61361f 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -8,10 +8,6 @@ position: relative; } -.cvat-canvas-grid-fullscreen-item ~ .cvat-canvas-grid-item { - visibility: hidden; -} - .cvat-canvas-grid-item { background-color: rgba(241, 241, 241, 0.7); border-radius: 4px; @@ -23,6 +19,7 @@ &.cvat-canvas-grid-fullscreen-item { width: 100% !important; height: 100% !important; + padding-right: $grid-unit-size; transform: translate(4px, 4px) !important; z-index: 1; diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx index 1e9a2a31173a..05779380394e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image-selector.tsx @@ -47,6 +47,8 @@ function ContextImageSelector(props: Props): React.ReactPortal { images, offset, onChangeOffset, onClose, } = props; + const keys = Object.keys(images).sort(); + return ReactDOM.createPortal((
@@ -57,10 +59,10 @@ function ContextImageSelector(props: Props): React.ReactPortal {
- { Object.entries(images).map(([key, value], i: number) => ( + { keys.map((key, i: number) => ( { onChangeOffset(i); diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index a60f55f7c728..52a1060e477e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -39,13 +39,16 @@ > span.ant-typography { line-height: $grid-unit-size * 4; + word-break: break-all; } > .cvat-context-image-setup-button { font-size: 16px; opacity: 0; transition: all 200ms; - margin-right: $grid-unit-size; + position: absolute; + top: $grid-unit-size; + right: $grid-unit-size; } } diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 1aefd54deb5e..1dfa6b57cfb5 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -662,6 +662,21 @@ def _get_rq_response(queue, job_id): return response +def find_common_path(paths): + reference = paths[0].split(os.path.sep) + truncate_from = len(reference) + + if len(paths): + for idx, fragment in enumerate(reference): + thesame = True + for path in paths[1:]: + fragments = path.split(os.path.sep) + thesame = fragment == fragments[idx] if idx < len(fragments) else False + if not thesame: + truncate_from = idx + break + + return os.path.sep.join(reference[0:truncate_from]) class DataChunkGetter: def __init__(self, data_type, data_num, data_quality, task_dim): @@ -730,9 +745,10 @@ def __call__(self, request, start, stop, db_data): return Response(data='No context image related to the frame', status=status.HTTP_404_NOT_FOUND) + common_path = find_common_path(list(map(lambda x: str(x.path), image.related_files.all()))) for i in image.related_files.all(): path = os.path.realpath(str(i.path)) - name = os.path.relpath(str(i.path), db_data.get_upload_dirname()) + name = os.path.relpath(str(i.path), common_path) image = cv2.imread(path) success, result = cv2.imencode('.JPEG', image) if not success: From 72a45b758d2d99c3258e048f2f39d72f9dc2517d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 01:08:46 -0800 Subject: [PATCH 28/47] reset views, add/remove context images runtime --- cvat-ui/src/base.scss | 2 +- .../canvas/grid-layout/canvas-layout.conf.tsx | 67 ++++--- .../canvas/grid-layout/canvas-layout.tsx | 183 ++++++++++++++++-- .../canvas/grid-layout/styles.scss | 49 ++++- .../views/context-image/context-image.tsx | 4 +- .../canvas/views/context-image/styles.scss | 21 +- .../tag-annotation-workspace/styles.scss | 32 +-- .../src/components/layout-grid/styles.scss | 7 +- 8 files changed, 274 insertions(+), 91 deletions(-) diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 7ec4e42e414e..643b0859e2cf 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +7,6 @@ $grid-unit-size: 8px; $header-height: $grid-unit-size * 6; -$layout-sm-grid-size: $grid-unit-size * 0.5; $layout-lg-grid-size: $grid-unit-size * 2; $layout-sm-grid-color: rgba(0, 0, 0, 0.15); $layout-lg-grid-color: rgba(0, 0, 0, 0.15); diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx index 8fab70d80450..c2a6a40c2026 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx @@ -21,8 +21,16 @@ export enum ViewType { RELATED_IMAGE = 'relatedImage', } -const defaultLayout: { [index: string]: ItemLayout[] } = {}; -defaultLayout.CANVAS_NO_RELATED = [{ +const defaultLayout: { + '2D': { + [index: string]: ItemLayout[]; + }; + '3D': { + [index: string]: ItemLayout[]; + }; +} = { '2D': {}, '3D': {} }; + +defaultLayout['2D']['0'] = [{ viewType: ViewType.CANVAS, offset: [0], x: 0, @@ -31,39 +39,38 @@ defaultLayout.CANVAS_NO_RELATED = [{ h: 12, }]; -defaultLayout.CANVAS_ONE_RELATED = [ - { ...defaultLayout.CANVAS_NO_RELATED[0], w: 9 }, { +defaultLayout['2D']['1'] = [ + { ...defaultLayout['2D']['0'][0], w: 9 }, { viewType: ViewType.RELATED_IMAGE, offset: [0, 0], x: 9, y: 0, w: 3, h: 4, - viewIndex: '1', + viewIndex: '0', }, ]; -defaultLayout.CANVAS_TWO_RELATED = [ - ...defaultLayout.CANVAS_ONE_RELATED, - { - ...defaultLayout.CANVAS_ONE_RELATED[1], - viewIndex: '2', +defaultLayout['2D']['2'] = [ + ...defaultLayout['2D']['1'], { + ...defaultLayout['2D']['1'][1], + viewType: ViewType.RELATED_IMAGE, + viewIndex: '1', offset: [0, 1], y: 3, }, ]; -defaultLayout.CANVAS_THREE_PLUS_RELATED = [ - ...defaultLayout.CANVAS_TWO_RELATED, - { - ...defaultLayout.CANVAS_TWO_RELATED[1], - viewIndex: '3', +defaultLayout['2D']['3'] = [ + ...defaultLayout['2D']['2'], { + ...defaultLayout['2D']['2'][2], + viewIndex: '2', offset: [0, 2], y: 6, }, ]; -defaultLayout.CANVAS_3D_NO_RELATED = [{ +defaultLayout['3D']['0'] = [{ viewType: ViewType.CANVAS_3D, offset: [0], x: 0, @@ -93,11 +100,11 @@ defaultLayout.CANVAS_3D_NO_RELATED = [{ h: 3, }]; -defaultLayout.CANVAS_3D_ONE_RELATED = [ - { ...defaultLayout.CANVAS_3D_NO_RELATED[0], w: 9 }, - { ...defaultLayout.CANVAS_3D_NO_RELATED[1], w: 3 }, - { ...defaultLayout.CANVAS_3D_NO_RELATED[2], x: 3, w: 3 }, - { ...defaultLayout.CANVAS_3D_NO_RELATED[3], x: 6, w: 3 }, +defaultLayout['3D']['1'] = [ + { ...defaultLayout['3D']['0'][0], w: 9 }, + { ...defaultLayout['3D']['0'][1], w: 3 }, + { ...defaultLayout['3D']['0'][2], x: 3, w: 3 }, + { ...defaultLayout['3D']['0'][3], x: 6, w: 3 }, { viewType: ViewType.RELATED_IMAGE, offset: [0, 0], @@ -105,25 +112,25 @@ defaultLayout.CANVAS_3D_ONE_RELATED = [ y: 0, w: 3, h: 4, - viewIndex: '1', + viewIndex: '0', }, ]; -defaultLayout.CANVAS_3D_TWO_RELATED = [ - ...defaultLayout.CANVAS_3D_ONE_RELATED, +defaultLayout['3D']['2'] = [ + ...defaultLayout['3D']['1'], { - ...defaultLayout.CANVAS_3D_ONE_RELATED[4], - viewIndex: '2', + ...defaultLayout['3D']['1'][4], + viewIndex: '1', offset: [0, 1], y: 4, }, ]; -defaultLayout.CANVAS_3D_THREE_PLUS_RELATED = [ - ...defaultLayout.CANVAS_3D_TWO_RELATED, +defaultLayout['3D']['3'] = [ + ...defaultLayout['3D']['2'], { - ...defaultLayout.CANVAS_3D_TWO_RELATED[5], - viewIndex: '3', + ...defaultLayout['3D']['2'][5], + viewIndex: '2', offset: [0, 2], y: 8, }, diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 1a32ea6b83b9..cb0d0a92f62e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -10,7 +10,15 @@ import { useSelector } from 'react-redux'; import RGL, { WidthProvider } from 'react-grid-layout'; import PropTypes from 'prop-types'; import Layout from 'antd/lib/layout'; -import { DragOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; +import { + CloseOutlined, + DragOutlined, + FullscreenExitOutlined, + FullscreenOutlined, + PicCenterOutlined, + PlusOutlined, + ReloadOutlined, +} from '@ant-design/icons'; import { DimensionType, CombinedState } from 'reducers'; import CanvasWrapperComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-wrapper'; @@ -21,6 +29,7 @@ import CanvasWrapper3DComponent, { FrontViewComponent, } from 'components/annotation-page/canvas/views/canvas3d/canvas-wrapper3D'; import ContextImage from 'components/annotation-page/canvas/views/context-image/context-image'; +import CVATTooltip from 'components/common/cvat-tooltip'; import defaultLayout, { ItemLayout, ViewType } from './canvas-layout.conf'; const ReactGridLayout = WidthProvider(RGL); @@ -55,10 +64,75 @@ const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { return component; }; +const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number): ItemLayout[] => { + // 9 X 12 for canvas and its elements + // 3 x 12 for related images + + const updatedLayout: ItemLayout[] = []; + + const relatedViews = layoutConfig + .filter((item: ItemLayout) => item.viewType === ViewType.RELATED_IMAGE); + const height = Math.floor(rows / relatedViews.length); + relatedViews.forEach((view: ItemLayout, i: number) => { + updatedLayout.push({ + ...view, + h: height, + w: 3, + x: 9, + y: height * i, + }); + }); + + const widthAvail = relatedViews.length ? 9 : 12; + + if (type === DimensionType.DIM_2D) { + const canvas = layoutConfig + .find((item: ItemLayout) => item.viewType === ViewType.CANVAS) as ItemLayout; + updatedLayout.push({ + ...canvas, + x: 0, + y: 0, + w: widthAvail, + h: 12, + }); + } else { + const canvas = layoutConfig + .find((item: ItemLayout) => item.viewType === ViewType.CANVAS_3D) as ItemLayout; + const top = layoutConfig + .find((item: ItemLayout) => item.viewType === ViewType.CANVAS_3D_TOP) as ItemLayout; + const side = layoutConfig + .find((item: ItemLayout) => item.viewType === ViewType.CANVAS_3D_SIDE) as ItemLayout; + const front = layoutConfig + .find((item: ItemLayout) => item.viewType === ViewType.CANVAS_3D_FRONT) as ItemLayout; + updatedLayout.push({ + ...canvas, + x: 0, + y: 0, + w: widthAvail, + h: 9, + }, { + ...top, x: 0, y: 9, w: widthAvail / 3, h: 3, + }, + { + ...side, x: 3, y: 9, w: widthAvail / 3, h: 3, + }, + { + ...front, x: 6, y: 9, w: widthAvail / 3, h: 3, + }); + } + + return updatedLayout; +}; + function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const NUM_OF_ROWS = 12; const MARGIN = 8; const PADDING = MARGIN / 2; + + const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); + const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); + const canvasBackgroundColor = useSelector((state: CombinedState) => state.settings.player.canvasBackgroundColor); + const computeRowHeight = (): number => { const container = window.document.getElementsByClassName('cvat-annotation-header')[0]; if (container) { @@ -70,24 +144,13 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { return window.innerHeight; }; + const getLayout = useCallback(() => ( + defaultLayout[(type as DimensionType).toUpperCase() as '2D' | '3D'][Math.min(relatedFiles, 3)] + ), [type, relatedFiles]); + + const [layoutConfig, setLayoutConfig] = useState(getLayout()); const [rowHeight, setRowHeight] = useState(Math.floor(window.screen.availHeight / NUM_OF_ROWS)); const [fullscreenKey, setFullscreenKey] = useState(''); - const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); - const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); - const canvasBackgroundColor = useSelector((state: CombinedState) => state.settings.player.canvasBackgroundColor); - const getLayout = useCallback(() => { - if (type === DimensionType.DIM_2D) { - if (!relatedFiles) return defaultLayout.CANVAS_NO_RELATED; - if (relatedFiles === 1) return defaultLayout.CANVAS_ONE_RELATED; - if (relatedFiles === 2) return defaultLayout.CANVAS_TWO_RELATED; - return defaultLayout.CANVAS_THREE_PLUS_RELATED; - } - - if (!relatedFiles) return defaultLayout.CANVAS_3D_NO_RELATED; - if (relatedFiles === 1) return defaultLayout.CANVAS_3D_ONE_RELATED; - if (relatedFiles === 2) return defaultLayout.CANVAS_3D_TWO_RELATED; - return defaultLayout.CANVAS_3D_THREE_PLUS_RELATED; - }, [type, relatedFiles]); const fitCanvas = useCallback(() => { if (canvasInstance) { @@ -117,7 +180,6 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { window.dispatchEvent(new Event('resize')); }, []); - const layoutConfig = getLayout(); const children = layoutConfig.map((value: ItemLayout) => ViewFabric(value)); const layout = layoutConfig.map((value: ItemLayout) => ({ x: value.x, @@ -130,14 +192,25 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { return ( { + const transformedLayout = layoutConfig.map((itemLayout: ItemLayout, i: number): ItemLayout => ({ + ...itemLayout, + x: updatedLayout[i].x, + y: updatedLayout[i].y, + w: updatedLayout[i].w, + h: updatedLayout[i].h, + })); + + setLayoutConfig(transformedLayout); + fitCanvas(); + }} onResize={fitCanvas} resizeHandle={(_: any, ref: React.MutableRefObject) => (
@@ -156,6 +229,24 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { key={key} > + { + if (viewType === ViewType.RELATED_IMAGE) { + setLayoutConfig( + layoutConfig + .filter((item: ItemLayout) => !( + item.viewType === viewType && item.viewIndex === viewIndex + )), + ); + } + }} + /> {fullscreenKey === key ? ( { type === DimensionType.DIM_3D && } +
+ + { + setLayoutConfig(fitLayout(type as DimensionType, layoutConfig, NUM_OF_ROWS)); + window.dispatchEvent(new Event('resize')); + }} + /> + + + { + const MAXIMUM_RELATED = 12; + const existingRelated = layoutConfig + .filter((configItem: ItemLayout) => configItem.viewType === ViewType.RELATED_IMAGE); + + if (existingRelated.length >= MAXIMUM_RELATED) { + return; + } + + if (existingRelated.length === 0) { + setLayoutConfig(defaultLayout[type?.toUpperCase() as '2D' | '3D']['1']); + return; + } + + const viewIndexes = existingRelated + .map((item: ItemLayout) => +(item.viewIndex as string)).sort(); + const max = Math.max(...viewIndexes); + let viewIndex = max + 1; + for (let i = 0; i < max + 1; i++) { + if (!viewIndexes.includes(i)) { + viewIndex = i; + break; + } + } + + const latest = existingRelated[existingRelated.length - 1]; + const copy = { ...latest, offset: [0, viewIndex], viewIndex: `${viewIndex}` }; + setLayoutConfig(fitLayout(type as DimensionType, [...layoutConfig, copy], NUM_OF_ROWS)); + window.dispatchEvent(new Event('resize')); + }} + /> + + + { + setLayoutConfig([...getLayout()]); + window.dispatchEvent(new Event('resize')); + }} + /> + +
); } diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index 8b5f1a61361f..d9ced9977d89 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -8,6 +8,32 @@ position: relative; } +.cvat-grid-layout-common-setups { + position: absolute; + top: 0; + right: 50%; + transform: translate(0, calc($grid-unit-size * 12 - 1px)); + z-index: 1000; + background: $background-color-2; + line-height: $grid-unit-size * 3; + height: calc($grid-unit-size * 3 + 1px); + padding-bottom: $grid-unit-size; + padding-right: $grid-unit-size; + padding-left: $grid-unit-size; + border-radius: 0 0 4px 4px; + border-bottom: 1px solid $border-color-1; + border-right: 1px solid $border-color-1; + border-left: 1px solid $border-color-1; + + > span { + margin-right: $grid-unit-size * 2; + + &:last-child { + margin-right: 0; + } + } +} + .cvat-canvas-grid-item { background-color: rgba(241, 241, 241, 0.7); border-radius: 4px; @@ -29,17 +55,12 @@ } } - .cvat-grid-item-fullscreen-handler { - left: $grid-unit-size * 4; - } - .cvat-grid-item-drag-handler, - .cvat-grid-item-fullscreen-handler { + .cvat-grid-item-fullscreen-handler, + .cvat-grid-item-close-button { position: absolute; top: $grid-unit-size; - left: $grid-unit-size * 4; z-index: 1000; - cursor: move; font-size: 16px; background: $header-color; border-radius: 2px; @@ -50,15 +71,23 @@ opacity: 0.9; } + &.cvat-grid-item-drag-handler { + left: $grid-unit-size * 7; + cursor: move; + } + &.cvat-grid-item-fullscreen-handler { + left: $grid-unit-size * 4; + } + + &.cvat-grid-item-close-button { left: $grid-unit-size; - cursor: default; } } .cvat-grid-item-resize-handler.react-resizable-handle { - bottom: 0; - right: 0; + bottom: -3px; + right: -2px; cursor: se-resize; &::after { diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index 030935306e1e..3c1e8be8a8d2 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -8,7 +8,6 @@ import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import notification from 'antd/lib/notification'; import Spin from 'antd/lib/spin'; -import Empty from 'antd/lib/empty'; import Text from 'antd/lib/typography/Text'; import { SettingOutlined } from '@ant-design/icons'; @@ -90,7 +89,8 @@ function ContextImage(props: Props): JSX.Element { )} {Object.keys(contextImageData).sort()[contextImageOffset]}
- { (hasError || (!fetching && contextImageOffset >= Object.keys(contextImageData).length)) && } + { (hasError || + (!fetching && contextImageOffset >= Object.keys(contextImageData).length)) && No data } { fetching && } { contextImageOffset < Object.keys(contextImageData).length && diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 52a1060e477e..6dd117b883e2 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -16,15 +16,9 @@ transform: translate(0, -50%); } - > .ant-empty { - width: 100%; - height: 60%; - margin-top: 20%; - margin-bottom: 20%; - - > .ant-empty-image { - height: 60%; - } + > .ant-typography { + top: 50%; + position: absolute; } .cvat-context-image-header { @@ -50,6 +44,15 @@ top: $grid-unit-size; right: $grid-unit-size; } + + > .cvat-context-image-close-button { + font-size: 16px; + opacity: 0; + transition: all 200ms; + position: absolute; + top: $grid-unit-size; + right: $grid-unit-size; + } } > canvas { diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss index dc0e1ebd8e47..cde4dcf8614f 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -10,14 +11,14 @@ .cvat-tag-annotation-sidebar:not(.ant-layout-sider-collapsed) { background: $background-color-2; - padding: $grid-unit-size * 0.5; - padding-left: $grid-unit-size * 1.25; + padding: $grid-unit-size; + padding-left: $grid-unit-size; overflow-y: auto; } .cvat-tag-annotation-sidebar-label-select { - padding-top: $grid-unit-size * 1.25; - padding-bottom: $grid-unit-size * 1.8; + padding-top: $grid-unit-size; + padding-bottom: $grid-unit-size * 2; > .ant-col > .ant-select { width: $grid-unit-size * 25; @@ -25,16 +26,16 @@ } .cvat-tag-annotation-sidebar-shortcut-help { - padding-top: $grid-unit-size * 1.8; + padding-top: $grid-unit-size; text-align: center; } .cvat-tag-annotation-sidebar-checkbox-skip-frame { - padding-bottom: $grid-unit-size * 1.8; + padding-bottom: $grid-unit-size; } .cvat-tag-annotation-label-selects { - padding-top: $grid-unit-size * 1.25; + padding-top: $grid-unit-size; .ant-select { width: $grid-unit-size * 29; @@ -42,7 +43,7 @@ } .cvat-tag-annotation-shortcut-key { - margin-left: $grid-unit-size * 1.25; + margin-left: $grid-unit-size; } } @@ -52,14 +53,13 @@ .cvat-frame-tags { .ant-tag { - margin: $grid-unit-size * 0.25; display: inline-flex; justify-content: center; align-items: center; .ant-tag-close-icon { - margin-left: $grid-unit-size * 0.5; - font-size: $grid-unit-size * 1.5; + margin-left: $grid-unit-size; + font-size: 12px; } } } @@ -68,7 +68,7 @@ @extend .cvat-frame-tags; position: absolute; - top: $layout-sm-grid-size; + top: $grid-unit-size * 4; left: $grid-unit-size; z-index: 3; @@ -78,11 +78,11 @@ } .cvat-tag-annotation-sidebar-tag-label { - margin-top: $grid-unit-size * 1.8; + margin-top: $grid-unit-size * 2; } .cvat-add-tag-button { - margin-left: $grid-unit-size * 1.25; - width: $grid-unit-size * 3.5; - height: $grid-unit-size * 3.5; + margin-left: $grid-unit-size; + width: $grid-unit-size * 4; + height: $grid-unit-size * 3; } diff --git a/cvat-ui/src/components/layout-grid/styles.scss b/cvat-ui/src/components/layout-grid/styles.scss index b4b6901dd76f..990aec40fb9d 100644 --- a/cvat-ui/src/components/layout-grid/styles.scss +++ b/cvat-ui/src/components/layout-grid/styles.scss @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -18,13 +19,13 @@ } &.sm { - grid-template-rows: repeat(1000, $layout-sm-grid-size); - grid-template-columns: repeat(1000, $layout-sm-grid-size); + grid-template-rows: repeat(1000, $grid-unit-size); + grid-template-columns: repeat(1000, $grid-unit-size); &::before, &::after { background: linear-gradient(to right, $layout-sm-grid-color 1px, transparent 1px); - background-size: $layout-sm-grid-size; + background-size: $grid-unit-size; } &::after { From ff5f8ae1377bd4bfa95ef6df51d170265bb9f65d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 01:13:43 -0800 Subject: [PATCH 29/47] Fixed two issues --- .../annotation-page/canvas/grid-layout/canvas-layout.tsx | 5 ++++- .../annotation-page/canvas/grid-layout/styles.scss | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index cb0d0a92f62e..355361c9e498 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -231,7 +231,6 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { { const MAXIMUM_RELATED = 12; diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index d9ced9977d89..83b1a923ba28 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -87,7 +87,7 @@ .cvat-grid-item-resize-handler.react-resizable-handle { bottom: -3px; - right: -2px; + right: -3px; cursor: se-resize; &::after { From 8835df0c79ac89160b3b063f3e9544af710b5ecc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 04:00:48 -0800 Subject: [PATCH 30/47] Tried to fix several tests --- .../canvas/grid-layout/canvas-layout.tsx | 23 ++++++++++--------- .../canvas/views/canvas2d/canvas-wrapper.tsx | 1 + tests/cypress/support/commands_canvas3d.js | 3 +-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 355361c9e498..34ef3e359b31 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import RGL, { WidthProvider } from 'react-grid-layout'; import PropTypes from 'prop-types'; +import { isEqual } from 'lodash'; import Layout from 'antd/lib/layout'; import { CloseOutlined, @@ -65,9 +66,6 @@ const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { }; const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number): ItemLayout[] => { - // 9 X 12 for canvas and its elements - // 3 x 12 for related images - const updatedLayout: ItemLayout[] = []; const relatedViews = layoutConfig @@ -135,13 +133,13 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const computeRowHeight = (): number => { const container = window.document.getElementsByClassName('cvat-annotation-header')[0]; + let containerHeight = window.innerHeight; if (container) { - const height = window.innerHeight - container.getBoundingClientRect().bottom; - // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 - return Math.floor((height - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS); + containerHeight = window.innerHeight - container.getBoundingClientRect().bottom; } - return window.innerHeight; + // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 + return Math.floor((containerHeight - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS); }; const getLayout = useCallback(() => ( @@ -149,7 +147,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { ), [type, relatedFiles]); const [layoutConfig, setLayoutConfig] = useState(getLayout()); - const [rowHeight, setRowHeight] = useState(Math.floor(window.screen.availHeight / NUM_OF_ROWS)); + const [rowHeight, setRowHeight] = useState(Math.floor(computeRowHeight())); const [fullscreenKey, setFullscreenKey] = useState(''); const fitCanvas = useCallback(() => { @@ -177,7 +175,8 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { }, [fitCanvas]); useEffect(() => { - window.dispatchEvent(new Event('resize')); + setRowHeight(computeRowHeight()); + // window.dispatchEvent(new Event('resize')); }, []); const children = layoutConfig.map((value: ItemLayout) => ViewFabric(value)); @@ -208,8 +207,10 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { h: updatedLayout[i].h, })); - setLayoutConfig(transformedLayout); - fitCanvas(); + if (!isEqual(layoutConfig, transformedLayout)) { + setLayoutConfig(transformedLayout); + fitCanvas(); + } }} onResize={fitCanvas} resizeHandle={(_: any, ref: React.MutableRefObject) => ( 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 0b0d248851ba..83acf0258da2 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 @@ -899,6 +899,7 @@ class CanvasWrapperComponent extends React.PureComponent { 'canvas.setup', () => { const { activatedStateID, activatedAttributeID } = this.props; + canvasInstance.fitCanvas(); canvasInstance.fit(); canvasInstance.activate(activatedStateID, activatedAttributeID); }, diff --git a/tests/cypress/support/commands_canvas3d.js b/tests/cypress/support/commands_canvas3d.js index a99f332f1402..40233f69ee47 100644 --- a/tests/cypress/support/commands_canvas3d.js +++ b/tests/cypress/support/commands_canvas3d.js @@ -10,7 +10,7 @@ Cypress.Commands.add('compareImagesAndCheckResult', (baseImage, afterImage, noChangesExpected) => { cy.compareImages(baseImage, afterImage).then((diffPercent) => { if (noChangesExpected) { - expect(diffPercent).to.be.lt(0.02); + expect(diffPercent).to.be.lt(0.03); } else { expect(diffPercent).to.be.gt(0); } @@ -30,7 +30,6 @@ Cypress.Commands.add('create3DCuboid', (cuboidCreationParams) => { Cypress.Commands.add('customScreenshot', (element, screenshotName) => { cy.get(element).then(([$el]) => $el.getBoundingClientRect()).then((rect) => { - cy.log(rect); cy.screenshot(screenshotName, { overwrite: true, capture: 'fullPage', From 7a620d7d86f73c62d7dad614c0ccdd70f47434f5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 04:50:49 -0800 Subject: [PATCH 31/47] Fixed wrong test 111 --- ...ttings_text_size_position_label_content.js | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js b/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js index d2a8a2ca18ea..8fe9895b2df0 100644 --- a/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js +++ b/tests/cypress/integration/actions_tasks2/case_111_settings_text_size_position_label_content.js @@ -46,25 +46,24 @@ context('Settings. Text size/position. Text labels content.', () => { let textTopPosition = 0; let getText; - cy.get(shape).then(($shape) => { - shapeLeftPosition = Math.trunc($shape.position().left); - shapeTopPosition = Math.trunc($shape.position().top); - if (shape === '#cvat_canvas_shape_1') { - shapeWidth = $shape.attr('width'); - shapeHeight = $shape.attr('height'); - } else { - const points = $shape.attr('points').split(' '); - shapeWidth = +points[1].split(',')[0] - +points[0].split(',')[0]; - shapeHeight = +points[2].split(',')[1] - +points[0].split(',')[1]; - } + cy.get(shape).then(([shapeObj]) => { + const shapeBBox = shapeObj.getBoundingClientRect(); + shapeLeftPosition = shapeBBox.left; + shapeTopPosition = shapeBBox.top; + shapeWidth = shapeBBox.width; + shapeHeight = shapeBBox.height; + if (shape === '#cvat_canvas_shape_1') { getText = cy.get('.cvat_canvas_text').first(); } else { getText = cy.get('.cvat_canvas_text').last(); } - getText.then(($text) => { - textLeftPosition = Math.trunc($text.position().left); - textTopPosition = Math.trunc($text.position().top); + + getText.then(([textObj]) => { + const textBBox = textObj.getBoundingClientRect(); + textLeftPosition = textBBox.left; + textTopPosition = textBBox.top; + if (expectedPosition === 'outside') { // Text outside the shape of the right. Slightly below the shape upper edge. expect(+shapeLeftPosition + +shapeWidth).lessThan(+textLeftPosition); From 7e641b9c5f2f5ac3805a32d539f30376c96f1340 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 05:06:59 -0800 Subject: [PATCH 32/47] Fixed hardcoded values in tests --- .../actions_objects2/case_108_rotated_bounding_boxes.js | 8 ++++---- .../case_115_ellipse_shape_track_label.js | 2 +- tests/cypress/support/commands_canvas3d.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js b/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js index 7757a85d33fd..d40f0352f3ac 100644 --- a/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js +++ b/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js @@ -66,8 +66,8 @@ context('Rotated bounding boxes.', () => { describe(`Testing case "${caseId}"`, () => { it('Check that bounding boxes can be rotated.', () => { - cy.shapeRotate('#cvat_canvas_shape_1', '15.7'); - cy.shapeRotate('#cvat_canvas_shape_2', '15.7'); + cy.shapeRotate('#cvat_canvas_shape_1', '15.3'); + cy.shapeRotate('#cvat_canvas_shape_2', '15.3'); }); it('Check interpolation, merging/splitting rotated shapes.', () => { @@ -85,7 +85,7 @@ context('Rotated bounding boxes.', () => { } }); - cy.shapeRotate('#cvat_canvas_shape_2', '29.8'); + cy.shapeRotate('#cvat_canvas_shape_2', '29.1'); // Comparison of the values of the shape attribute of the current frame with the previous frame testCompareRotate('cvat_canvas_shape_2', 0); @@ -110,7 +110,7 @@ context('Rotated bounding boxes.', () => { cy.get('#cvat_canvas_shape_4').should('be.visible'); cy.goCheckFrameNumber(9); - cy.shapeRotate('#cvat_canvas_shape_4', '15.7'); + cy.shapeRotate('#cvat_canvas_shape_4', '15.3'); // Comparison of the values of the shape attribute of the current frame with the previous frame testCompareRotate('cvat_canvas_shape_4', 2); diff --git a/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js b/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js index 942a43bf83d3..8e01fe72ce50 100644 --- a/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js +++ b/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js @@ -73,7 +73,7 @@ context('Actions on ellipse.', () => { it('Ellipse rotation/interpolation.', () => { Cypress.config('scrollBehavior', false); cy.get('.cvat-player-last-button').click(); - cy.shapeRotate('#cvat_canvas_shape_4', '19.7'); + cy.shapeRotate('#cvat_canvas_shape_4', '19.0'); testCompareRotate('cvat_canvas_shape_4', 0); // Rotation with shift cy.shapeRotate('#cvat_canvas_shape_4', '15.0', true); diff --git a/tests/cypress/support/commands_canvas3d.js b/tests/cypress/support/commands_canvas3d.js index 40233f69ee47..e3f126341987 100644 --- a/tests/cypress/support/commands_canvas3d.js +++ b/tests/cypress/support/commands_canvas3d.js @@ -10,7 +10,7 @@ Cypress.Commands.add('compareImagesAndCheckResult', (baseImage, afterImage, noChangesExpected) => { cy.compareImages(baseImage, afterImage).then((diffPercent) => { if (noChangesExpected) { - expect(diffPercent).to.be.lt(0.03); + expect(diffPercent).to.be.lt(0.02); } else { expect(diffPercent).to.be.gt(0); } From ab1608960418d83dc49993eeb96d963a83e559a4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 05:15:44 -0800 Subject: [PATCH 33/47] Increased timeout --- ...82_canvas3d_functionality_cuboid_opacity_outlined_borders.js | 2 +- tests/cypress/support/commands_canvas3d.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js b/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js index ffe705dddc07..cc0bdf1d587e 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js @@ -21,7 +21,7 @@ context('Canvas 3D functionality. Opacity. Outlined borders.', () => { before(() => { cy.openTask(taskName); cy.openJob(); - cy.wait(1000); // Waiting for the point cloud to display + cy.wait(2000); // Waiting for the point cloud to display cy.create3DCuboid(cuboidCreationParams); cy.get('.cvat-canvas3d-perspective').trigger('mousemove').click(); // Deactivate the cuboiud cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_deactivate_cuboid'); diff --git a/tests/cypress/support/commands_canvas3d.js b/tests/cypress/support/commands_canvas3d.js index e3f126341987..9be8dcfaef17 100644 --- a/tests/cypress/support/commands_canvas3d.js +++ b/tests/cypress/support/commands_canvas3d.js @@ -1,5 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT From 4cc0f45c4e08cc4a5054a63e63ae5e29206f8292 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 07:22:45 -0800 Subject: [PATCH 34/47] Fix one issue --- ...82_canvas3d_functionality_cuboid_opacity_outlined_borders.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js b/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js index cc0bdf1d587e..3048e7456264 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js @@ -43,11 +43,13 @@ context('Canvas 3D functionality. Opacity. Outlined borders.', () => { `${screenshotsPath}/canvas3d_perspective_opacty_100.png`, `${screenshotsPath}/canvas3d_perspective_opacty_0.png`, ); + cy.get('body').click(); }); it('Change selected opacity to 100. To 0.', () => { cy.get('.cvat-appearance-selected-opacity-slider').click('right'); cy.get('.cvat-appearance-selected-opacity-slider').find('[role="slider"]').should('have.attr', 'aria-valuenow', 100); + cy.get('body').click(); cy.get('.cvat-canvas3d-perspective').trigger('mousemove').trigger('mousemove', 500, 250).wait(1000); // Waiting for the cuboid activation cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_selected_opacty_100'); cy.compareImagesAndCheckResult( From 90890c675b472094deb290e358888c90afbe8a8a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 08:17:39 -0800 Subject: [PATCH 35/47] . --- tests/cypress/support/commands_canvas3d.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cypress/support/commands_canvas3d.js b/tests/cypress/support/commands_canvas3d.js index 9be8dcfaef17..e94734696783 100644 --- a/tests/cypress/support/commands_canvas3d.js +++ b/tests/cypress/support/commands_canvas3d.js @@ -29,7 +29,7 @@ Cypress.Commands.add('create3DCuboid', (cuboidCreationParams) => { }); Cypress.Commands.add('customScreenshot', (element, screenshotName) => { - cy.get(element).then(([$el]) => $el.getBoundingClientRect()).then((rect) => { + cy.get(`${element} canvas`).then(([$el]) => ($el.getBoundingClientRect())).then((rect) => { cy.screenshot(screenshotName, { overwrite: true, capture: 'fullPage', From 015c888914f78ac38e15ef2752a8d76038a4f922 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 08:55:17 -0800 Subject: [PATCH 36/47] Totally reworked 3d test case_82 --- cvat-canvas3d/src/typescript/canvas3dView.ts | 14 +++++ ...onality_cuboid_opacity_outlined_borders.js | 63 +++++++++---------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/cvat-canvas3d/src/typescript/canvas3dView.ts b/cvat-canvas3d/src/typescript/canvas3dView.ts index bb2ce1e55ef6..7d3b40d76e0c 100644 --- a/cvat-canvas3d/src/typescript/canvas3dView.ts +++ b/cvat-canvas3d/src/typescript/canvas3dView.ts @@ -265,6 +265,20 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { const canvasSideView = this.views.side.renderer.domElement; const canvasFrontView = this.views.front.renderer.domElement; + [ + [canvasPerspectiveView, this.views.perspective.scene], + [canvasTopView, this.views.top.scene], + [canvasSideView, this.views.side.scene], + [canvasFrontView, this.views.front.scene], + ].forEach(([view, scene]) => { + Object.defineProperty(view, 'scene', { + value: scene, + enumerable: false, + configurable: false, + writable: false, + }); + }); + canvasPerspectiveView.addEventListener('contextmenu', (e: MouseEvent): void => { if (this.model.data.activeElement.clientID !== null) { this.dispatchEvent( diff --git a/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js b/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js index 3048e7456264..3f14bfe4c2b3 100644 --- a/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js +++ b/tests/cypress/integration/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js @@ -11,7 +11,6 @@ import { taskName, labelName } from '../../support/const_canvas3d'; context('Canvas 3D functionality. Opacity. Outlined borders.', () => { const caseId = '82'; - const screenshotsPath = 'cypress/screenshots/canvas3d_functionality_2/case_82_canvas3d_functionality_cuboid_opacity_outlined_borders.js'; const cuboidCreationParams = { labelName, x: 500, @@ -24,25 +23,26 @@ context('Canvas 3D functionality. Opacity. Outlined borders.', () => { cy.wait(2000); // Waiting for the point cloud to display cy.create3DCuboid(cuboidCreationParams); cy.get('.cvat-canvas3d-perspective').trigger('mousemove').click(); // Deactivate the cuboiud - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_deactivate_cuboid'); }); + const getScene = (el) => el.scene.children[0]; + const getFirstChild = (el) => getScene(el).children[0]; + const getWireframe = (el) => getFirstChild(el).children[0]; + describe(`Testing case "${caseId}"`, () => { it('Change opacity to 100. To 0.', () => { cy.get('.cvat-appearance-opacity-slider').click('right'); cy.get('.cvat-appearance-opacity-slider').find('[role="slider"]').should('have.attr', 'aria-valuenow', 100); - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_opacty_100'); - cy.compareImagesAndCheckResult( - `${screenshotsPath}/canvas3d_perspective_deactivate_cuboid.png`, - `${screenshotsPath}/canvas3d_perspective_opacty_100.png`, - ); + cy.get('.cvat-canvas3d-perspective canvas').then(([el]) => { + expect(getFirstChild(el).material.opacity).to.equal(1); + }); + cy.get('.cvat-appearance-opacity-slider').click('left'); cy.get('.cvat-appearance-opacity-slider').find('[role="slider"]').should('have.attr', 'aria-valuenow', 0); - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_opacty_0'); - cy.compareImagesAndCheckResult( - `${screenshotsPath}/canvas3d_perspective_opacty_100.png`, - `${screenshotsPath}/canvas3d_perspective_opacty_0.png`, - ); + cy.get('.cvat-canvas3d-perspective canvas').then(([el]) => { + expect(getFirstChild(el).material.opacity).to.equal(0); + }); + cy.get('body').click(); }); @@ -51,20 +51,17 @@ context('Canvas 3D functionality. Opacity. Outlined borders.', () => { cy.get('.cvat-appearance-selected-opacity-slider').find('[role="slider"]').should('have.attr', 'aria-valuenow', 100); cy.get('body').click(); cy.get('.cvat-canvas3d-perspective').trigger('mousemove').trigger('mousemove', 500, 250).wait(1000); // Waiting for the cuboid activation - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_selected_opacty_100'); - cy.compareImagesAndCheckResult( - `${screenshotsPath}/canvas3d_perspective_opacty_100.png`, - `${screenshotsPath}/canvas3d_perspective_selected_opacty_100.png`, - true, // No diff between the images - ); + + cy.get('.cvat-canvas3d-perspective canvas').then(([el]) => { + expect(el.scene.children[0].children[0].material.opacity).to.equal(1); + }); + cy.get('.cvat-appearance-selected-opacity-slider').click('left'); cy.get('.cvat-appearance-selected-opacity-slider').find('[role="slider"]').should('have.attr', 'aria-valuenow', 0); - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_selected_opacty_0'); - cy.compareImagesAndCheckResult( - `${screenshotsPath}/canvas3d_perspective_opacty_0.png`, - `${screenshotsPath}/canvas3d_perspective_selected_opacty_0.png`, - true, // No diff between the images - ); + + cy.get('.cvat-canvas3d-perspective canvas').then(([el]) => { + expect(getFirstChild(el).material.opacity).to.equal(0); + }); }); it('Enable/disable outlined borders.', () => { @@ -74,18 +71,14 @@ context('Canvas 3D functionality. Opacity. Outlined borders.', () => { cy.get('div[title="#ff007c"]').click(); cy.contains('Ok').click(); }); - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_enable_outlined_borders'); - cy.compareImagesAndCheckResult( - `${screenshotsPath}/canvas3d_perspective_enable_outlined_borders.png`, - `${screenshotsPath}/canvas3d_perspective_selected_opacty_0.png`, - ); + cy.get('.cvat-canvas3d-perspective canvas').then(([el]) => { + expect({ ...getWireframe(el).material.color }).to.deep.equal({ r: 1, g: 0, b: 0.48627450980392156 }); + }); + cy.get('.cvat-appearance-outlinded-borders-checkbox').find('[type="checkbox"]').uncheck().should('not.be.checked'); - cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_disable_outlined_borders'); - cy.compareImagesAndCheckResult( - `${screenshotsPath}/canvas3d_perspective_disable_outlined_borders.png`, - `${screenshotsPath}/canvas3d_perspective_selected_opacty_0.png`, - true, // No diff between the images - ); + cy.get('.cvat-canvas3d-perspective canvas').then(([el]) => { + expect({ ...getWireframe(el).material.color }).to.deep.equal({ ...getFirstChild(el).material.color }); + }); }); }); }); From 999c626a669262f3567ac5eccc00e35c8b7ae030 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 09:27:07 -0800 Subject: [PATCH 37/47] Advanced fit function to support more context images --- cvat-core/src/frames.ts | 11 ++++++++-- .../canvas/grid-layout/canvas-layout.tsx | 20 ++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 10a97e61f9e7..1dc3ef24d35a 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -9,7 +9,9 @@ import * as cvatData from 'cvat-data'; import { DimensionType } from 'enums'; import PluginRegistry from './plugins'; import serverProxy, { FramesMetaData } from './server-proxy'; -import { Exception, ArgumentError, DataError } from './exceptions'; +import { + Exception, ArgumentError, DataError, ServerError, +} from './exceptions'; // frame storage by job id const frameDataCache: Record { - reject(error); + if (error instanceof ServerError && (error as any).code === 404) { + this._contextImage[frame] = {}; + resolve(); + } else { + reject(error); + } }); }); diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 34ef3e359b31..e0d54c1f2d91 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -70,18 +70,24 @@ const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number const relatedViews = layoutConfig .filter((item: ItemLayout) => item.viewType === ViewType.RELATED_IMAGE); - const height = Math.floor(rows / relatedViews.length); + const cols = relatedViews.length > 6 ? 2 : 1; + const height = Math.floor(rows / (relatedViews.length / cols)); relatedViews.forEach((view: ItemLayout, i: number) => { updatedLayout.push({ ...view, h: height, - w: 3, - x: 9, + w: relatedViews.length > 6 ? 2 : 3, + x: cols === 1 ? 9 : 8 + (i % 2) * 2, y: height * i, }); }); - const widthAvail = relatedViews.length ? 9 : 12; + let widthAvail = 12; + if (relatedViews.length > 6) { + widthAvail = 8; + } else if (relatedViews.length > 0) { + widthAvail = 9; + } if (type === DimensionType.DIM_2D) { const canvas = layoutConfig @@ -109,13 +115,13 @@ const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number w: widthAvail, h: 9, }, { - ...top, x: 0, y: 9, w: widthAvail / 3, h: 3, + ...top, x: 0, y: 9, w: Math.ceil(widthAvail / 3), h: 3, }, { - ...side, x: 3, y: 9, w: widthAvail / 3, h: 3, + ...side, x: 3, y: 9, w: Math.floor(widthAvail / 3), h: 3, }, { - ...front, x: 6, y: 9, w: widthAvail / 3, h: 3, + ...front, x: 6, y: 9, w: Math.floor(widthAvail / 3), h: 3, }); } From f9d7ce3fe4ff92d534873cf5c38d1cb5c67d12da Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 09:59:01 -0800 Subject: [PATCH 38/47] Flickering fixed --- .../canvas/grid-layout/canvas-layout.conf.tsx | 4 +- .../canvas/grid-layout/canvas-layout.tsx | 160 +++++++++--------- 2 files changed, 83 insertions(+), 81 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx index c2a6a40c2026..f5059b22f7b9 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.conf.tsx @@ -57,7 +57,7 @@ defaultLayout['2D']['2'] = [ viewType: ViewType.RELATED_IMAGE, viewIndex: '1', offset: [0, 1], - y: 3, + y: 4, }, ]; @@ -66,7 +66,7 @@ defaultLayout['2D']['3'] = [ ...defaultLayout['2D']['2'][2], viewIndex: '2', offset: [0, 2], - y: 6, + y: 8, }, ]; diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index e0d54c1f2d91..8eb5fc00f862 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -142,10 +142,11 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { let containerHeight = window.innerHeight; if (container) { containerHeight = window.innerHeight - container.getBoundingClientRect().bottom; + // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 + return Math.floor((containerHeight - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS); } - // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 - return Math.floor((containerHeight - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS); + return 0; }; const getLayout = useCallback(() => ( @@ -182,7 +183,6 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { useEffect(() => { setRowHeight(computeRowHeight()); - // window.dispatchEvent(new Event('resize')); }, []); const children = layoutConfig.map((value: ItemLayout) => ViewFabric(value)); @@ -196,86 +196,88 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { return ( - { - const transformedLayout = layoutConfig.map((itemLayout: ItemLayout, i: number): ItemLayout => ({ - ...itemLayout, - x: updatedLayout[i].x, - y: updatedLayout[i].y, - w: updatedLayout[i].w, - h: updatedLayout[i].h, - })); - - if (!isEqual(layoutConfig, transformedLayout)) { - setLayoutConfig(transformedLayout); - fitCanvas(); - } - }} - onResize={fitCanvas} - resizeHandle={(_: any, ref: React.MutableRefObject) => ( -
- )} - draggableHandle='.cvat-grid-item-drag-handler' - > - { children.map((child: JSX.Element, idx: number): JSX.Element => { - const { viewType, viewIndex } = layoutConfig[idx]; - const key = typeof viewIndex !== 'undefined' ? `${viewType}_${viewIndex}` : `${viewType}`; - return ( -
- - { - if (viewType === ViewType.RELATED_IMAGE) { - setLayoutConfig( - layoutConfig - .filter((item: ItemLayout) => !( - item.viewType === viewType && item.viewIndex === viewIndex - )), - ); - } - }} - /> - {fullscreenKey === key ? ( - { - window.dispatchEvent(new Event('resize')); - setFullscreenKey(''); + { !!rowHeight && ( + { + const transformedLayout = layoutConfig.map((itemLayout: ItemLayout, i: number): ItemLayout => ({ + ...itemLayout, + x: updatedLayout[i].x, + y: updatedLayout[i].y, + w: updatedLayout[i].w, + h: updatedLayout[i].h, + })); + + if (!isEqual(layoutConfig, transformedLayout)) { + setLayoutConfig(transformedLayout); + fitCanvas(); + } + }} + onResize={fitCanvas} + resizeHandle={(_: any, ref: React.MutableRefObject) => ( +
+ )} + draggableHandle='.cvat-grid-item-drag-handler' + > + { children.map((child: JSX.Element, idx: number): JSX.Element => { + const { viewType, viewIndex } = layoutConfig[idx]; + const key = typeof viewIndex !== 'undefined' ? `${viewType}_${viewIndex}` : `${viewType}`; + return ( +
+ + - ) : ( - { - window.dispatchEvent(new Event('resize')); - setFullscreenKey(key); + if (viewType === ViewType.RELATED_IMAGE) { + setLayoutConfig( + layoutConfig + .filter((item: ItemLayout) => !( + item.viewType === viewType && item.viewIndex === viewIndex + )), + ); + } }} /> - )} - - { child } -
- ); - }) } - + {fullscreenKey === key ? ( + { + window.dispatchEvent(new Event('resize')); + setFullscreenKey(''); + }} + /> + ) : ( + { + window.dispatchEvent(new Event('resize')); + setFullscreenKey(key); + }} + /> + )} + + { child } +
+ ); + }) } +
+ )} { type === DimensionType.DIM_3D && }
From db2b620610dcb9ebc7f825cfe163e1f138406ef4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 11:17:12 -0800 Subject: [PATCH 39/47] Improved resize --- .../canvas/grid-layout/canvas-layout.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index 8eb5fc00f862..c473fc533bce 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -118,10 +118,14 @@ const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number ...top, x: 0, y: 9, w: Math.ceil(widthAvail / 3), h: 3, }, { - ...side, x: 3, y: 9, w: Math.floor(widthAvail / 3), h: 3, + ...side, x: Math.ceil(widthAvail / 3), y: 9, w: Math.ceil(widthAvail / 3), h: 3, }, { - ...front, x: 6, y: 9, w: Math.floor(widthAvail / 3), h: 3, + ...front, + x: Math.ceil(widthAvail / 3) * 2, + y: 9, + w: Math.floor(widthAvail / 3), + h: 3, }); } @@ -167,6 +171,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { useEffect(() => { const onResize = (): void => { setRowHeight(computeRowHeight()); + fitCanvas(); const [el] = window.document.getElementsByClassName('cvat-canvas-grid-root'); if (el) { el.addEventListener('transitionend', () => { @@ -185,6 +190,10 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { setRowHeight(computeRowHeight()); }, []); + useEffect(() => { + window.dispatchEvent(new Event('resize')); + }, [layoutConfig]); + const children = layoutConfig.map((value: ItemLayout) => ViewFabric(value)); const layout = layoutConfig.map((value: ItemLayout) => ({ x: value.x, @@ -216,10 +225,8 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { if (!isEqual(layoutConfig, transformedLayout)) { setLayoutConfig(transformedLayout); - fitCanvas(); } }} - onResize={fitCanvas} resizeHandle={(_: any, ref: React.MutableRefObject) => (
)} From eabd20aa012a7510485d2023cd03a96497990721 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 11:26:53 -0800 Subject: [PATCH 40/47] Tests --- .../actions_objects2/case_115_ellipse_shape_track_label.js | 2 +- .../actions_tasks2/case_101_opencv_basic_actions.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js b/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js index 8e01fe72ce50..942a43bf83d3 100644 --- a/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js +++ b/tests/cypress/integration/actions_objects2/case_115_ellipse_shape_track_label.js @@ -73,7 +73,7 @@ context('Actions on ellipse.', () => { it('Ellipse rotation/interpolation.', () => { Cypress.config('scrollBehavior', false); cy.get('.cvat-player-last-button').click(); - cy.shapeRotate('#cvat_canvas_shape_4', '19.0'); + cy.shapeRotate('#cvat_canvas_shape_4', '19.7'); testCompareRotate('cvat_canvas_shape_4', 0); // Rotation with shift cy.shapeRotate('#cvat_canvas_shape_4', '15.0', true); diff --git a/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js b/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js index 8ecc0fdfe7a6..7c3bcd1b24eb 100644 --- a/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js +++ b/tests/cypress/integration/actions_tasks2/case_101_opencv_basic_actions.js @@ -212,10 +212,10 @@ context('OpenCV. Intelligent scissors. Histogram Equalization. TrackerMIL.', () // On each frame text is moved by 5px on x and y axis, // so we expect shape to be close to real text positions cy.get('#cvat_canvas_shape_3').invoke('attr', 'x').then((xVal) => { - expect(parseFloat(xVal)).to.be.closeTo(x + (i - 1) * 5, 1.0); + expect(parseFloat(xVal)).to.be.closeTo(x + (i - 1) * 5, 2.0); }); cy.get('#cvat_canvas_shape_3').invoke('attr', 'y').then((yVal) => { - expect(parseFloat(yVal)).to.be.closeTo(y + (i - 1) * 5, 1.0); + expect(parseFloat(yVal)).to.be.closeTo(y + (i - 1) * 5, 2.0); }); cy.get('#cvat-objects-sidebar-state-item-3') .should('contain', 'RECTANGLE TRACK') From 403ca0ddd8928487fc9d9929c03f2d33b6f6a28e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 10 Jan 2023 12:03:17 -0800 Subject: [PATCH 41/47] Aborted test changes --- .../actions_objects2/case_108_rotated_bounding_boxes.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js b/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js index d40f0352f3ac..7757a85d33fd 100644 --- a/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js +++ b/tests/cypress/integration/actions_objects2/case_108_rotated_bounding_boxes.js @@ -66,8 +66,8 @@ context('Rotated bounding boxes.', () => { describe(`Testing case "${caseId}"`, () => { it('Check that bounding boxes can be rotated.', () => { - cy.shapeRotate('#cvat_canvas_shape_1', '15.3'); - cy.shapeRotate('#cvat_canvas_shape_2', '15.3'); + cy.shapeRotate('#cvat_canvas_shape_1', '15.7'); + cy.shapeRotate('#cvat_canvas_shape_2', '15.7'); }); it('Check interpolation, merging/splitting rotated shapes.', () => { @@ -85,7 +85,7 @@ context('Rotated bounding boxes.', () => { } }); - cy.shapeRotate('#cvat_canvas_shape_2', '29.1'); + cy.shapeRotate('#cvat_canvas_shape_2', '29.8'); // Comparison of the values of the shape attribute of the current frame with the previous frame testCompareRotate('cvat_canvas_shape_2', 0); @@ -110,7 +110,7 @@ context('Rotated bounding boxes.', () => { cy.get('#cvat_canvas_shape_4').should('be.visible'); cy.goCheckFrameNumber(9); - cy.shapeRotate('#cvat_canvas_shape_4', '15.3'); + cy.shapeRotate('#cvat_canvas_shape_4', '15.7'); // Comparison of the values of the shape attribute of the current frame with the previous frame testCompareRotate('cvat_canvas_shape_4', 2); From 18d65c81281b93719c5382ad9cc5c56842bd394d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 11 Jan 2023 02:48:07 -0800 Subject: [PATCH 42/47] Adjusted text, moved close button --- .../canvas/grid-layout/styles.scss | 6 +++--- .../canvas/views/context-image/context-image.tsx | 8 +++++++- .../canvas/views/context-image/styles.scss | 15 +++++++++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss index 83b1a923ba28..97e444ba16c8 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/styles.scss @@ -72,16 +72,16 @@ } &.cvat-grid-item-drag-handler { - left: $grid-unit-size * 7; + left: $grid-unit-size * 4; cursor: move; } &.cvat-grid-item-fullscreen-handler { - left: $grid-unit-size * 4; + left: $grid-unit-size; } &.cvat-grid-item-close-button { - left: $grid-unit-size; + right: $grid-unit-size; } } diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index 3c1e8be8a8d2..ae5713f9ad89 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -11,6 +11,7 @@ import Spin from 'antd/lib/spin'; import Text from 'antd/lib/typography/Text'; import { SettingOutlined } from '@ant-design/icons'; +import CVATTooltop from 'components/common/cvat-tooltip'; import { CombinedState } from 'reducers'; import ContextImageSelector from './context-image-selector'; @@ -76,6 +77,7 @@ function ContextImage(props: Props): JSX.Element { } }, [contextImageData, contextImageOffset, canvasRef]); + const contextImageName = Object.keys(contextImageData).sort()[contextImageOffset]; return (
@@ -87,7 +89,11 @@ function ContextImage(props: Props): JSX.Element { }} /> )} - {Object.keys(contextImageData).sort()[contextImageOffset]} +
+ + {contextImageName} + +
{ (hasError || (!fetching && contextImageOffset >= Object.keys(contextImageData).length)) && No data } diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss index 6dd117b883e2..31d1488bd712 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/styles.scss @@ -31,9 +31,16 @@ background: $header-color; overflow: hidden; - > span.ant-typography { - line-height: $grid-unit-size * 4; - word-break: break-all; + > .cvat-context-image-title { + width: calc(100% - $grid-unit-size * 13); + margin-right: $grid-unit-size * 7; + margin-left: $grid-unit-size * 7; + + > span.ant-typography { + font-size: 12px; + line-height: $grid-unit-size * 4; + word-break: break-all; + } } > .cvat-context-image-setup-button { @@ -42,7 +49,7 @@ transition: all 200ms; position: absolute; top: $grid-unit-size; - right: $grid-unit-size; + right: $grid-unit-size * 4; } > .cvat-context-image-close-button { From d36062769b1bba2ea10e2ba9910db82283d84b85 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 11 Jan 2023 03:15:25 -0800 Subject: [PATCH 43/47] Code improvements --- .../canvas/grid-layout/canvas-layout.tsx | 58 +++++++++++-------- .../views/context-image/context-image.tsx | 7 ++- cvat-ui/src/consts.ts | 8 +++ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx index c473fc533bce..17c97d3c9e7c 100644 --- a/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/grid-layout/canvas-layout.tsx @@ -21,6 +21,7 @@ import { ReloadOutlined, } from '@ant-design/icons'; +import consts from 'consts'; import { DimensionType, CombinedState } from 'reducers'; import CanvasWrapperComponent from 'components/annotation-page/canvas/views/canvas2d/canvas-wrapper'; import CanvasWrapper3DComponent, { @@ -65,28 +66,26 @@ const ViewFabric = (itemLayout: ItemLayout): JSX.Element => { return component; }; -const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number): ItemLayout[] => { +const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[]): ItemLayout[] => { const updatedLayout: ItemLayout[] = []; const relatedViews = layoutConfig .filter((item: ItemLayout) => item.viewType === ViewType.RELATED_IMAGE); - const cols = relatedViews.length > 6 ? 2 : 1; - const height = Math.floor(rows / (relatedViews.length / cols)); + const relatedViewsCols = relatedViews.length > 6 ? 2 : 1; + const height = Math.floor(consts.CANVAS_WORKSPACE_ROWS / (relatedViews.length / relatedViewsCols)); relatedViews.forEach((view: ItemLayout, i: number) => { updatedLayout.push({ ...view, h: height, w: relatedViews.length > 6 ? 2 : 3, - x: cols === 1 ? 9 : 8 + (i % 2) * 2, + x: relatedViewsCols === 1 ? 9 : 8 + (i % 2) * 2, y: height * i, }); }); - let widthAvail = 12; - if (relatedViews.length > 6) { - widthAvail = 8; - } else if (relatedViews.length > 0) { - widthAvail = 9; + let widthAvail = consts.CANVAS_WORKSPACE_COLS; + if (updatedLayout.length > 0) { + widthAvail -= updatedLayout[0].w * relatedViewsCols; } if (type === DimensionType.DIM_2D) { @@ -97,7 +96,7 @@ const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number x: 0, y: 0, w: widthAvail, - h: 12, + h: consts.CANVAS_WORKSPACE_ROWS, }); } else { const canvas = layoutConfig @@ -108,24 +107,33 @@ const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number .find((item: ItemLayout) => item.viewType === ViewType.CANVAS_3D_SIDE) as ItemLayout; const front = layoutConfig .find((item: ItemLayout) => item.viewType === ViewType.CANVAS_3D_FRONT) as ItemLayout; + const helpfulCanvasViewHeight = 3; updatedLayout.push({ ...canvas, x: 0, y: 0, w: widthAvail, - h: 9, + h: consts.CANVAS_WORKSPACE_ROWS - helpfulCanvasViewHeight, }, { - ...top, x: 0, y: 9, w: Math.ceil(widthAvail / 3), h: 3, + ...top, + x: 0, + y: consts.CANVAS_WORKSPACE_ROWS, + w: Math.ceil(widthAvail / 3), + h: helpfulCanvasViewHeight, }, { - ...side, x: Math.ceil(widthAvail / 3), y: 9, w: Math.ceil(widthAvail / 3), h: 3, + ...side, + x: Math.ceil(widthAvail / 3), + y: consts.CANVAS_WORKSPACE_ROWS, + w: Math.ceil(widthAvail / 3), + h: helpfulCanvasViewHeight, }, { ...front, x: Math.ceil(widthAvail / 3) * 2, - y: 9, + y: consts.CANVAS_WORKSPACE_ROWS, w: Math.floor(widthAvail / 3), - h: 3, + h: helpfulCanvasViewHeight, }); } @@ -133,10 +141,6 @@ const fitLayout = (type: DimensionType, layoutConfig: ItemLayout[], rows: number }; function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { - const NUM_OF_ROWS = 12; - const MARGIN = 8; - const PADDING = MARGIN / 2; - const relatedFiles = useSelector((state: CombinedState) => state.annotation.player.frame.relatedFiles); const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); const canvasBackgroundColor = useSelector((state: CombinedState) => state.settings.player.canvasBackgroundColor); @@ -147,7 +151,10 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { if (container) { containerHeight = window.innerHeight - container.getBoundingClientRect().bottom; // https://github.com/react-grid-layout/react-grid-layout/issues/628#issuecomment-1228453084 - return Math.floor((containerHeight - MARGIN * (NUM_OF_ROWS)) / NUM_OF_ROWS); + return Math.floor( + (containerHeight - consts.CANVAS_WORKSPACE_MARGIN * (consts.CANVAS_WORKSPACE_ROWS)) / + consts.CANVAS_WORKSPACE_ROWS, + ); } return 0; @@ -207,10 +214,11 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { { !!rowHeight && ( { - setLayoutConfig(fitLayout(type as DimensionType, layoutConfig, NUM_OF_ROWS)); + setLayoutConfig(fitLayout(type as DimensionType, layoutConfig)); window.dispatchEvent(new Event('resize')); }} /> @@ -329,7 +337,7 @@ function CanvasLayout({ type }: { type?: DimensionType }): JSX.Element { const latest = existingRelated[existingRelated.length - 1]; const copy = { ...latest, offset: [0, viewIndex], viewIndex: `${viewIndex}` }; - setLayoutConfig(fitLayout(type as DimensionType, [...layoutConfig, copy], NUM_OF_ROWS)); + setLayoutConfig(fitLayout(type as DimensionType, [...layoutConfig, copy])); window.dispatchEvent(new Event('resize')); }} /> diff --git a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx index ae5713f9ad89..f21e646b931f 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/context-image/context-image.tsx @@ -21,15 +21,18 @@ interface Props { function ContextImage(props: Props): JSX.Element { const { offset } = props; + const defaultFrameOffset = (offset[0] || 0); + const defaultContextImageOffset = (offset[1] || 0); + const canvasRef = useRef(null); const job = useSelector((state: CombinedState) => state.annotation.job.instance); const { number: frame, relatedFiles } = useSelector((state: CombinedState) => state.annotation.player.frame); - const frameIndex = frame + (offset[0] || 0); + const frameIndex = frame + defaultFrameOffset; const [contextImageData, setContextImageData] = useState>({}); const [fetching, setFetching] = useState(false); const [contextImageOffset, setContextImageOffset] = useState( - Math.min(offset[1] || 0, relatedFiles), + Math.min(defaultContextImageOffset, relatedFiles), ); const [hasError, setHasError] = useState(false); diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts index 858c335345a6..a8441de22b25 100644 --- a/cvat-ui/src/consts.ts +++ b/cvat-ui/src/consts.ts @@ -24,6 +24,10 @@ const LATEST_COMMENTS_SHOWN_QUICK_ISSUE = 3; const QUICK_ISSUE_INCORRECT_POSITION_TEXT = 'Wrong position'; const QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT = 'Wrong attribute'; const DEFAULT_PROJECT_SUBSETS = ['Train', 'Test', 'Validation']; +const CANVAS_WORKSPACE_ROWS = 12; +const CANVAS_WORKSPACE_COLS = 12; +const CANVAS_WORKSPACE_MARGIN = 8; +const CANVAS_WORKSPACE_PADDING = CANVAS_WORKSPACE_MARGIN / 2; const OUTSIDE_PIC_URL = 'https://opencv.github.io/cvat/images/image019.jpg'; const DEFAULT_AWS_S3_REGIONS: string[][] = [ ['us-east-1', 'US East (N. Virginia)'], @@ -114,4 +118,8 @@ export default { HEALH_CHECK_RETRIES, HEALTH_CHECK_PERIOD, HEALTH_CHECK_REQUEST_TIMEOUT, + CANVAS_WORKSPACE_ROWS, + CANVAS_WORKSPACE_COLS, + CANVAS_WORKSPACE_MARGIN, + CANVAS_WORKSPACE_PADDING, }; From b6be3f8b57d3ac1b03ddbe6438f119f64c17ca8c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 11 Jan 2023 03:33:26 -0800 Subject: [PATCH 44/47] Returned one test --- .../case_30_collapse_sidebar_appearance.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js b/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js index 182c7a68a48a..850699517903 100644 --- a/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js +++ b/tests/cypress/integration/actions_tasks2/case_30_collapse_sidebar_appearance.js @@ -41,6 +41,22 @@ context('Collapse sidebar/appearance. Check issue 3250 (empty sidebar after resi }); describe(`Testing case "${caseId}"`, () => { + it('Collapse sidebar. Cheeck issue 3250.', () => { + // hide sidebar + cy.get('.cvat-objects-sidebar-sider').click(); + cy.get('.cvat-objects-sidebar').should('not.be.visible'); + + // unhide sidebar + cy.get('.cvat-objects-sidebar-sider').click(); + cy.get('.cvat-objects-sidebar').should('be.visible'); + checkEqualBackground(); + + // Before the issue fix the sidebar item did not appear accordingly + // it was not possible to activate the shape through the sidebar item + cy.get('#cvat-objects-sidebar-state-item-1').trigger('mouseover'); + cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated'); + }); + it('Collapse appearance', () => { // hide cy.get('.cvat-objects-appearance-collapse-header').click(); From ee55e11c70650c1d3f2f67b47c94e56504e0d977 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 11 Jan 2023 06:51:07 -0800 Subject: [PATCH 45/47] Use commonpath --- cvat/apps/engine/views.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index b8e99800406e..24c4b12e1c22 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -670,22 +670,6 @@ def _get_rq_response(queue, job_id): return response -def find_common_path(paths): - reference = paths[0].split(os.path.sep) - truncate_from = len(reference) - - if len(paths): - for idx, fragment in enumerate(reference): - thesame = True - for path in paths[1:]: - fragments = path.split(os.path.sep) - thesame = fragment == fragments[idx] if idx < len(fragments) else False - if not thesame: - truncate_from = idx - break - - return os.path.sep.join(reference[0:truncate_from]) - class DataChunkGetter: def __init__(self, data_type, data_num, data_quality, task_dim): possible_data_type_values = ('chunk', 'frame', 'preview', 'context_image') @@ -753,7 +737,7 @@ def __call__(self, request, start, stop, db_data): return Response(data='No context image related to the frame', status=status.HTTP_404_NOT_FOUND) - common_path = find_common_path(list(map(lambda x: str(x.path), image.related_files.all()))) + common_path = os.path.commonpath(list(map(lambda x: str(x.path), image.related_files.all()))) for i in image.related_files.all(): path = os.path.realpath(str(i.path)) name = os.path.relpath(str(i.path), common_path) From 0ef5d1f88072b6ab7fc95f2ad7a6a842fcc5d282 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Jan 2023 00:12:15 -0800 Subject: [PATCH 46/47] using MediaCache --- cvat/apps/engine/cache.py | 39 ++++++++++++++++++++++++++++++++++++--- cvat/apps/engine/views.py | 28 ++++++---------------------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/cvat/apps/engine/cache.py b/cvat/apps/engine/cache.py index d1b19ef3578b..cc8460c32552 100644 --- a/cvat/apps/engine/cache.py +++ b/cvat/apps/engine/cache.py @@ -3,9 +3,12 @@ # SPDX-License-Identifier: MIT import os +import io +import zipfile from io import BytesIO from datetime import datetime from tempfile import NamedTemporaryFile +import cv2 import pytz from django.core.cache import cache @@ -16,7 +19,7 @@ from cvat.apps.engine.media_extractors import (Mpeg4ChunkWriter, Mpeg4CompressedChunkWriter, ZipChunkWriter, ZipCompressedChunkWriter, ImageDatasetManifestReader, VideoDatasetManifestReader) -from cvat.apps.engine.models import DataChoice, StorageChoice +from cvat.apps.engine.models import DataChoice, StorageChoice, Image from cvat.apps.engine.models import DimensionType from cvat.apps.engine.cloud_provider import get_cloud_storage_instance, Credentials from cvat.apps.engine.utils import md5_hash @@ -34,7 +37,8 @@ def _get_or_set_cache_item(key, create_function): item = cache.get(key) if not item: item = create_function() - cache.set(key, item) + if item[0]: + cache.set(key, item) return item @@ -62,13 +66,20 @@ def get_cloud_preview_with_mime(self, db_storage): return item + def get_frame_context_images(self, db_data, frame_number): + item = self._get_or_set_cache_item( + key=f'context_image_{db_data.id}_{frame_number}', + create_function=lambda: self._prepare_context_image(db_data, frame_number) + ) + + return item + @staticmethod def _get_frame_provider(): from cvat.apps.engine.frame_provider import FrameProvider # TODO: remove circular dependency return FrameProvider def _prepare_chunk_buff(self, db_data, quality, chunk_number): - FrameProvider = self._get_frame_provider() writer_classes = { @@ -183,3 +194,25 @@ def _prepare_cloud_preview(self, db_storage): mime_type = mimetypes.guess_type(preview_path)[0] return buff, mime_type + + def _prepare_context_image(self, db_data, frame_number): + zip_buffer = io.BytesIO() + try: + image = Image.objects.get(data_id=db_data.id, frame=frame_number) + except Image.DoesNotExist: + return None, None + with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file: + if not image.related_files.count(): + return None, None + common_path = os.path.commonpath(list(map(lambda x: str(x.path), image.related_files.all()))) + for i in image.related_files.all(): + path = os.path.realpath(str(i.path)) + name = os.path.relpath(str(i.path), common_path) + image = cv2.imread(path) + success, result = cv2.imencode('.JPEG', image) + if not success: + raise Exception('Failed to encode image to ".jpeg" format') + zip_file.writestr(f'{name}.jpg', result.tobytes()) + buff = zip_buffer.getvalue() + mime_type = 'application/zip' + return buff, mime_type diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index a89e2734161a..f9ae87a6de53 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -6,7 +6,6 @@ import io import os import os.path as osp -import zipfile import pytz import shutil import traceback @@ -14,7 +13,6 @@ from distutils.util import strtobool from tempfile import mkstemp -import cv2 from django.db.models.query import Prefetch from django.shortcuts import get_object_or_404 import django_rq @@ -49,7 +47,7 @@ from cvat.apps.engine.media_extractors import get_mime from cvat.apps.engine.models import ( Job, Task, Project, Issue, Data, - Comment, StorageMethodChoice, StorageChoice, Image, + Comment, StorageMethodChoice, StorageChoice, CloudProviderChoice, Location ) from cvat.apps.engine.models import CloudStorage as CloudStorageModel @@ -697,25 +695,11 @@ def __call__(self, request, start, stop, db_data): elif self.type == 'context_image': if start <= self.number <= stop: - zip_buffer = io.BytesIO() - image = Image.objects.get(data_id=db_data.id, frame=self.number) - with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file: - if not image.related_files.count(): - return Response(data='No context image related to the frame', - status=status.HTTP_404_NOT_FOUND) - - common_path = os.path.commonpath(list(map(lambda x: str(x.path), image.related_files.all()))) - for i in image.related_files.all(): - path = os.path.realpath(str(i.path)) - name = os.path.relpath(str(i.path), common_path) - image = cv2.imread(path) - success, result = cv2.imencode('.JPEG', image) - if not success: - raise Exception('Failed to encode image to ".jpeg" format') - zip_file.writestr(f'{name}.jpg', result.tobytes()) - # response = HttpResponse(wrapper, content_type='application/zip') - # response['Content-Disposition'] = 'attachment; filename=your_zipfile.zip' - return HttpResponse(io.BytesIO(zip_buffer.getvalue()), content_type='application/zip') + cache = MediaCache(self.dimension) + buff, mime = cache.get_frame_context_images(db_data, self.number) + if not buff: + return HttpResponseNotFound() + return HttpResponse(io.BytesIO(buff), content_type=mime) raise ValidationError('The frame number should be in ' + f'[{start}, {stop}] range') else: From f44870ef3d3e54ac953124fcb75a7e9d8f0f9854 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Jan 2023 00:59:45 -0800 Subject: [PATCH 47/47] Fixed zoom behaviour --- cvat-canvas3d/src/typescript/canvas3dView.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cvat-canvas3d/src/typescript/canvas3dView.ts b/cvat-canvas3d/src/typescript/canvas3dView.ts index 7d3b40d76e0c..c1afd80497e7 100644 --- a/cvat-canvas3d/src/typescript/canvas3dView.ts +++ b/cvat-canvas3d/src/typescript/canvas3dView.ts @@ -556,7 +556,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { } private setDefaultZoom(): void { - if (this.model.data.activeElement === null) { + if (this.model.data.activeElement.clientID === null) { Object.keys(this.views).forEach((view: string): void => { const viewType = this.views[view as keyof Views]; if (view !== ViewType.PERSPECTIVE) { @@ -571,7 +571,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { canvasTop.offsetWidth / (bboxtop.max.x - bboxtop.min.x), canvasTop.offsetHeight / (bboxtop.max.y - bboxtop.min.y), ) * 0.4; - this.views.top.camera.zoom = x1 / 100; + this.views.top.camera.zoom = x1 / 50; this.views.top.camera.updateProjectionMatrix(); this.views.top.camera.updateMatrix(); this.updateHelperPointsSize(ViewType.TOP); @@ -582,7 +582,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { canvasFront.offsetWidth / (bboxfront.max.y - bboxfront.min.y), canvasFront.offsetHeight / (bboxfront.max.z - bboxfront.min.z), ) * 0.4; - this.views.front.camera.zoom = x2 / 100; + this.views.front.camera.zoom = x2 / 50; this.views.front.camera.updateProjectionMatrix(); this.views.front.camera.updateMatrix(); this.updateHelperPointsSize(ViewType.FRONT); @@ -593,7 +593,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { canvasSide.offsetWidth / (bboxside.max.x - bboxside.min.x), canvasSide.offsetHeight / (bboxside.max.z - bboxside.min.z), ) * 0.4; - this.views.side.camera.zoom = x3 / 100; + this.views.side.camera.zoom = x3 / 50; this.views.side.camera.updateProjectionMatrix(); this.views.side.camera.updateMatrix(); this.updateHelperPointsSize(ViewType.SIDE); @@ -859,7 +859,8 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.activatedElementID = +clientID; this.rotatePlane(null, null); this.detachCamera(null); - this.setDefaultZoom(); + [ViewType.TOP, ViewType.SIDE, ViewType.FRONT] + .forEach((type) => this.updateHelperPointsSize(type)); } } @@ -1047,6 +1048,9 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { this.deactivateObject(); this.activateObject(); + if (this.activatedElementID) { + this.setDefaultZoom(); + } } else if (reason === UpdateReasons.DRAW) { const data: DrawData = this.controller.drawData; if (Number.isInteger(data.redraw)) {