From 265ed425ec3a64483b7e75ea48902f11fd1df898 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Wed, 11 Sep 2024 12:46:46 +0300 Subject: [PATCH] Quality control page (#8329) --- ...6_093730_klakhov_support_quality_plugin.md | 14 + cvat-core/package.json | 2 +- cvat-core/src/annotations.ts | 83 ++-- cvat-core/src/api-implementation.ts | 16 +- cvat-core/src/api.ts | 12 +- cvat-core/src/frames.ts | 39 +- cvat-core/src/index.ts | 8 +- cvat-core/src/session-implementation.ts | 24 +- cvat-core/src/session.ts | 25 +- cvat-ui/.eslintrc.cjs | 1 + cvat-ui/package.json | 2 +- cvat-ui/src/actions/tasks-actions.ts | 26 +- cvat-ui/src/assets/paid-feature.png | Bin 0 -> 153475 bytes .../components/actions-menu/actions-menu.tsx | 11 +- .../analytics-page/analytics-page.tsx | 45 +- .../shared/quality-settings-modal.tsx | 103 ---- .../src/components/analytics-page/styles.scss | 72 +-- .../analytics-page/task-quality/empty-job.tsx | 39 -- .../task-quality/gt-conflicts.tsx | 112 ----- .../analytics-page/task-quality/issues.tsx | 66 --- .../analytics-page/task-quality/job-list.tsx | 228 --------- .../task-quality/mean-quality.tsx | 100 ---- .../task-quality/task-quality-component.tsx | 271 ----------- .../analytics-page/views/analytics-card.tsx | 9 +- .../annotations-actions-modal.tsx | 3 +- .../manifests-manager.tsx | 2 +- .../components/create-job-page/job-form.tsx | 4 + .../create-task-page/create-task-content.tsx | 102 +++- .../quality-configuration-form.tsx | 176 +++++++ .../components/create-task-page/styles.scss | 7 + .../customizable-components/index.tsx | 12 +- .../paid-feature-placeholder.tsx | 69 +++ .../paid-feature-placeholder/styles.scss | 39 ++ cvat-ui/src/components/cvat-app.tsx | 2 + .../settings-modal/shortcut-settings.tsx | 3 +- .../components/job-item/job-actions-menu.tsx | 8 +- cvat-ui/src/components/job-item/job-item.tsx | 15 +- cvat-ui/src/components/jobs-page/job-card.tsx | 19 +- .../src/components/jobs-page/jobs-content.tsx | 13 +- .../src/components/jobs-page/jobs-page.tsx | 10 +- .../src/components/models-page/top-bar.tsx | 2 +- cvat-ui/src/components/plugins-entrypoint.tsx | 31 +- .../quality-control/quality-control-page.tsx | 448 ++++++++++++++++++ .../quality-control/quality-settings-tab.tsx | 54 +++ .../components/quality-control/styles.scss | 146 ++++++ .../task-quality/allocation-table.tsx | 185 ++++++++ .../task-quality/quality-magement-tab.tsx | 67 +++ .../task-quality/quality-overview-tab.tsx | 26 + .../task-quality/quality-settings-form.tsx | 146 +++--- .../quality-control/task-quality/summary.tsx | 59 +++ .../signing-common/cvat-signing-input.tsx | 4 +- cvat-ui/src/components/task-page/job-list.tsx | 13 +- cvat-ui/src/components/task-page/styles.scss | 9 - .../src/components/task-page/task-page.tsx | 2 +- cvat-ui/src/components/task-page/top-bar.tsx | 4 + .../src/components/tasks-page/task-item.tsx | 4 + cvat-ui/src/config.tsx | 11 + .../containers/actions-menu/actions-menu.tsx | 4 + cvat-ui/src/utils/quality.ts | 87 +--- cvat-ui/webpack.config.js | 4 + .../cypress/e2e/features/ground_truth_jobs.js | 323 +------------ .../support/commands_review_pipeline.js | 19 +- yarn.lock | 2 +- 63 files changed, 1750 insertions(+), 1692 deletions(-) create mode 100644 changelog.d/20240826_093730_klakhov_support_quality_plugin.md create mode 100644 cvat-ui/src/assets/paid-feature.png delete mode 100644 cvat-ui/src/components/analytics-page/shared/quality-settings-modal.tsx delete mode 100644 cvat-ui/src/components/analytics-page/task-quality/empty-job.tsx delete mode 100644 cvat-ui/src/components/analytics-page/task-quality/gt-conflicts.tsx delete mode 100644 cvat-ui/src/components/analytics-page/task-quality/issues.tsx delete mode 100644 cvat-ui/src/components/analytics-page/task-quality/job-list.tsx delete mode 100644 cvat-ui/src/components/analytics-page/task-quality/mean-quality.tsx delete mode 100644 cvat-ui/src/components/analytics-page/task-quality/task-quality-component.tsx create mode 100644 cvat-ui/src/components/create-task-page/quality-configuration-form.tsx create mode 100644 cvat-ui/src/components/customizable-components/paid-feature-placeholder/paid-feature-placeholder.tsx create mode 100644 cvat-ui/src/components/customizable-components/paid-feature-placeholder/styles.scss create mode 100644 cvat-ui/src/components/quality-control/quality-control-page.tsx create mode 100644 cvat-ui/src/components/quality-control/quality-settings-tab.tsx create mode 100644 cvat-ui/src/components/quality-control/styles.scss create mode 100644 cvat-ui/src/components/quality-control/task-quality/allocation-table.tsx create mode 100644 cvat-ui/src/components/quality-control/task-quality/quality-magement-tab.tsx create mode 100644 cvat-ui/src/components/quality-control/task-quality/quality-overview-tab.tsx rename cvat-ui/src/components/{analytics-page => quality-control}/task-quality/quality-settings-form.tsx (79%) create mode 100644 cvat-ui/src/components/quality-control/task-quality/summary.tsx diff --git a/changelog.d/20240826_093730_klakhov_support_quality_plugin.md b/changelog.d/20240826_093730_klakhov_support_quality_plugin.md new file mode 100644 index 000000000000..2b9324c2deb8 --- /dev/null +++ b/changelog.d/20240826_093730_klakhov_support_quality_plugin.md @@ -0,0 +1,14 @@ +### Changed + +- Moved quality control from `analytics` page to `quality control` page + () + +### Removed + +- Quality report no longer available in CVAT community version + () + +### Added + +- Quality management tab on `quality control` allows to enabling/disabling GT frames + () diff --git a/cvat-core/package.json b/cvat-core/package.json index 1e9a4f863def..c93f4e9151a7 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "15.1.3", + "version": "15.2.0", "type": "module", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "src/api.ts", diff --git a/cvat-core/src/annotations.ts b/cvat-core/src/annotations.ts index f9b51139d0ec..706cbc853ed2 100644 --- a/cvat-core/src/annotations.ts +++ b/cvat-core/src/annotations.ts @@ -11,34 +11,42 @@ import AnnotationsHistory from './annotations-history'; import { checkObjectType } from './common'; import Project from './project'; import { Task, Job } from './session'; -import { ScriptingError, ArgumentError } from './exceptions'; +import { ArgumentError } from './exceptions'; import { getDeletedFrames } from './frames'; import { JobType } from './enums'; -type WeakMapItem = { collection: AnnotationsCollection, saver: AnnotationsSaver, history: AnnotationsHistory }; -const jobCache = new WeakMap(); -const taskCache = new WeakMap(); +const jobCollectionCache = new WeakMap(); +const taskCollectionCache = new WeakMap(); -function getCache(sessionType): WeakMap { - if (sessionType === 'task') { - return taskCache; - } +// save history separately as not all history actions are related to annotations (e.g. delete, restore frame are not) +const jobHistoryCache = new WeakMap(); +const taskHistoryCache = new WeakMap(); - if (sessionType === 'job') { - return jobCache; +function getCache(sessionType: 'task' | 'job'): { + collection: typeof jobCollectionCache; + history: typeof jobHistoryCache; +} { + if (sessionType === 'task') { + return { + collection: taskCollectionCache, + history: taskHistoryCache, + }; } - throw new ScriptingError(`Unknown session type was received ${sessionType}`); + return { + collection: jobCollectionCache, + history: jobHistoryCache, + }; } class InstanceNotInitializedError extends Error {} -function getSession(session): WeakMapItem { +export function getCollection(session): AnnotationsCollection { const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); + const { collection } = getCache(sessionType); - if (cache.has(session)) { - return cache.get(session); + if (collection.has(session)) { + return collection.get(session).collection; } throw new InstanceNotInitializedError( @@ -46,23 +54,37 @@ function getSession(session): WeakMapItem { ); } -export function getCollection(session): AnnotationsCollection { - return getSession(session).collection; -} - export function getSaver(session): AnnotationsSaver { - return getSession(session).saver; + const sessionType = session instanceof Task ? 'task' : 'job'; + const { collection } = getCache(sessionType); + + if (collection.has(session)) { + return collection.get(session).saver; + } + + throw new InstanceNotInitializedError( + 'Session has not been initialized yet. Call annotations.get() or annotations.clear({ reload: true }) before', + ); } export function getHistory(session): AnnotationsHistory { - return getSession(session).history; + const sessionType = session instanceof Task ? 'task' : 'job'; + const { history } = getCache(sessionType); + + if (history.has(session)) { + return history.get(session); + } + + const initiatedHistory = new AnnotationsHistory(); + history.set(session, initiatedHistory); + return initiatedHistory; } async function getAnnotationsFromServer(session: Job | Task): Promise { const sessionType = session instanceof Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (!cache.has(session)) { + if (!cache.collection.has(session)) { const serializedAnnotations = await serverProxy.annotations.getAnnotations(sessionType, session.id); // Get meta information about frames @@ -74,7 +96,7 @@ async function getAnnotationsFromServer(session: Job | Task): Promise { } frameMeta.deleted_frames = await getDeletedFrames(sessionType, session.id); - const history = new AnnotationsHistory(); + const history = cache.history.has(session) ? cache.history.get(session) : new AnnotationsHistory(); const collection = new AnnotationsCollection({ labels: session.labels, history, @@ -87,7 +109,8 @@ async function getAnnotationsFromServer(session: Job | Task): Promise { // eslint-disable-next-line no-unsanitized/method collection.import(serializedAnnotations); const saver = new AnnotationsSaver(serializedAnnotations.version, collection, session); - cache.set(session, { collection, saver, history }); + cache.collection.set(session, { collection, saver }); + cache.history.set(session, history); } } @@ -95,8 +118,12 @@ export function clearCache(session): void { const sessionType = session instanceof Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (cache.has(session)) { - cache.delete(session); + if (cache.collection.has(session)) { + cache.collection.delete(session); + } + + if (cache.history.has(session)) { + cache.history.delete(session); } } @@ -125,7 +152,9 @@ export async function clearAnnotations( checkObjectType('reload', reload, 'boolean', null); if (reload) { - cache.delete(session); + cache.collection.delete(session); + // delete history as it may relate to objects from collection we deleted above + cache.history.delete(session); return getAnnotationsFromServer(session); } } diff --git a/cvat-core/src/api-implementation.ts b/cvat-core/src/api-implementation.ts index 8713e7ed2c90..0e9f400ad499 100644 --- a/cvat-core/src/api-implementation.ts +++ b/cvat-core/src/api-implementation.ts @@ -37,7 +37,7 @@ import { import QualityReport from './quality-report'; import QualityConflict, { ConflictSeverity } from './quality-conflict'; import QualitySettings from './quality-settings'; -import { FramesMetaData } from './frames'; +import { getFramesMeta } from './frames'; import AnalyticsReport from './analytics-report'; import { listActions, registerAction, runActions } from './annotations-actions'; import { convertDescriptions, getServerAPISchema } from './server-schema'; @@ -520,9 +520,8 @@ export default function implementAPI(cvat: CVATCore): CVATCore { const settings = await serverProxy.analytics.quality.settings.get(params); const schema = await getServerAPISchema(); const descriptions = convertDescriptions(schema.components.schemas.QualitySettings.properties); - return new QualitySettings({ - ...settings, descriptions, - }); + + return new QualitySettings({ ...settings, descriptions }); }); implementationMixin(cvat.analytics.performance.reports, async (filter: AnalyticsReportFilter) => { checkFilter(filter, { @@ -557,12 +556,9 @@ export default function implementAPI(cvat: CVATCore): CVATCore { const params = fieldsToSnakeCase(body); await serverProxy.analytics.performance.calculate(params, onUpdate); }); - implementationMixin(cvat.frames.getMeta, async (type, id) => { - const result = await serverProxy.frames.getMeta(type, id); - return new FramesMetaData({ - ...result, - deleted_frames: Object.fromEntries(result.deleted_frames.map((_frame) => [_frame, true])), - }); + implementationMixin(cvat.frames.getMeta, async (type: 'job' | 'task', id: number) => { + const result = await getFramesMeta(type, id); + return result; }); return cvat; diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts index ba7191bbc05d..fe3975217ce4 100644 --- a/cvat-core/src/api.ts +++ b/cvat-core/src/api.ts @@ -16,12 +16,16 @@ import Project from './project'; import implementProject from './project-implementation'; import { Attribute, Label } from './labels'; import MLModel from './ml-model'; -import { FrameData } from './frames'; +import { FrameData, FramesMetaData } from './frames'; import CloudStorage from './cloud-storage'; import Organization from './organization'; import Webhook from './webhook'; import AnnotationGuide from './guide'; import BaseSingleFrameAction from './annotations-actions'; +import QualityReport from './quality-report'; +import QualityConflict from './quality-conflict'; +import QualitySettings from './quality-settings'; +import AnalyticsReport from './analytics-report'; import { Request } from './request'; import * as enums from './enums'; @@ -416,6 +420,12 @@ function build(): CVATCore { Webhook, AnnotationGuide, BaseSingleFrameAction, + QualitySettings, + AnalyticsReport, + QualityConflict, + QualityReport, + Request, + FramesMetaData, }, utils: { mask2Rle, diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts index 8aab9f7f0ebe..96295af7d57d 100644 --- a/cvat-core/src/frames.ts +++ b/cvat-core/src/frames.ts @@ -435,19 +435,27 @@ Object.defineProperty(FrameData.prototype.data, 'implementation', { writable: false, }); -async function getJobMeta(jobID: number): Promise { - if (!frameMetaCache[jobID]) { - frameMetaCache[jobID] = serverProxy.frames.getMeta('job', jobID) +export async function getFramesMeta(type: 'job' | 'task', id: number, forceReload = false): Promise { + if (type === 'task') { + // we do not cache task meta currently. So, each new call will results to the server request + const result = await serverProxy.frames.getMeta('task', id); + return new FramesMetaData({ + ...result, + deleted_frames: Object.fromEntries(result.deleted_frames.map((_frame) => [_frame, true])), + }); + } + if (!(id in frameMetaCache) || forceReload) { + frameMetaCache[id] = serverProxy.frames.getMeta('job', id) .then((serverMeta) => new FramesMetaData({ ...serverMeta, deleted_frames: Object.fromEntries(serverMeta.deleted_frames.map((_frame) => [_frame, true])), })) .catch((error) => { - delete frameMetaCache[jobID]; + delete frameMetaCache[id]; throw error; }); } - return frameMetaCache[jobID]; + return frameMetaCache[id]; } async function saveJobMeta(meta: FramesMetaData, jobID: number): Promise { @@ -588,7 +596,7 @@ export async function getFrame( ): Promise { if (!(jobID in frameDataCache)) { const blockType = chunkType === 'video' ? BlockType.MP4VIDEO : BlockType.ARCHIVE; - const meta = await getJobMeta(jobID); + const meta = await getFramesMeta('job', jobID); const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length; const stdDev = Math.sqrt( @@ -655,31 +663,32 @@ export async function getDeletedFrames(instanceType: 'job' | 'task', id): Promis throw new Exception(`getDeletedFrames is not implemented for ${instanceType}`); } -export function deleteFrame(jobID: number, frame: number): void { - const { meta } = frameDataCache[jobID]; +export async function deleteFrame(jobID: number, frame: number): Promise { + const meta = await frameMetaCache[jobID]; meta.deletedFrames[frame] = true; } -export function restoreFrame(jobID: number, frame: number): void { - const { meta } = frameDataCache[jobID]; +export async function restoreFrame(jobID: number, frame: number): Promise { + const meta = await frameMetaCache[jobID]; delete meta.deletedFrames[frame]; } -export async function patchMeta(jobID: number): Promise { - const { meta } = frameDataCache[jobID]; +export async function patchMeta(jobID: number): Promise { + const meta = await frameMetaCache[jobID]; const updatedFields = meta.getUpdated(); if (Object.keys(updatedFields).length) { - const newMeta = await saveJobMeta(meta, jobID); - frameDataCache[jobID].meta = newMeta; + frameMetaCache[jobID] = saveJobMeta(meta, jobID); } + const newMeta = await frameMetaCache[jobID]; + return newMeta; } export async function findFrame( jobID: number, frameFrom: number, frameTo: number, filters: { offset?: number, notDeleted: boolean }, ): Promise { const offset = filters.offset || 1; - const meta = await getJobMeta(jobID); + const meta = await getFramesMeta('job', jobID); const sign = Math.sign(frameTo - frameFrom); const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; diff --git a/cvat-core/src/index.ts b/cvat-core/src/index.ts index 89f5d98f4b9d..4c68e2b23582 100644 --- a/cvat-core/src/index.ts +++ b/cvat-core/src/index.ts @@ -23,7 +23,7 @@ import ObjectState from './object-state'; import MLModel from './ml-model'; import Issue from './issue'; import Comment from './comment'; -import { FrameData } from './frames'; +import { FrameData, FramesMetaData } from './frames'; import CloudStorage from './cloud-storage'; import Organization, { Invitation } from './organization'; import Webhook from './webhook'; @@ -209,6 +209,12 @@ export default interface CVATCore { Webhook: typeof Webhook; AnnotationGuide: typeof AnnotationGuide; BaseSingleFrameAction: typeof BaseSingleFrameAction; + QualityReport: typeof QualityReport; + QualityConflict: typeof QualityConflict; + QualitySettings: typeof QualitySettings; + AnalyticsReport: typeof AnalyticsReport; + Request: typeof Request; + FramesMetaData: typeof FramesMetaData; }; utils: { mask2Rle: typeof mask2Rle; diff --git a/cvat-core/src/session-implementation.ts b/cvat-core/src/session-implementation.ts index 4483c1113936..fa77c934abde 100644 --- a/cvat-core/src/session-implementation.ts +++ b/cvat-core/src/session-implementation.ts @@ -64,7 +64,7 @@ export function implementJob(Job: typeof JobClass): typeof JobClass { Object.defineProperty(Job.prototype.save, 'implementation', { value: async function saveImplementation( this: JobClass, - fields: any, + fields: Parameters[0], ): ReturnType { if (this.id) { const jobData = { @@ -232,7 +232,7 @@ export function implementJob(Job: typeof JobClass): typeof JobClass { value: function saveFramesImplementation( this: JobClass, ): ReturnType { - return patchMeta(this.id); + return patchMeta(this.id).then((meta) => [meta]); }, }); @@ -618,15 +618,19 @@ export function implementTask(Task: typeof TaskClass): typeof TaskClass { Object.defineProperty(Task.prototype.save, 'implementation', { value: async function saveImplementation( this: TaskClass, - options: Parameters[0], + fields: Parameters[0], + options: Parameters[1], ): ReturnType { if (typeof this.id !== 'undefined') { // If the task has been already created, we update it - const taskData = this._updateTrigger.getUpdated(this, { - bugTracker: 'bug_tracker', - projectId: 'project_id', - assignee: 'assignee_id', - }); + const taskData = { + ...fields, + ...this._updateTrigger.getUpdated(this, { + bugTracker: 'bug_tracker', + projectId: 'project_id', + assignee: 'assignee_id', + }), + }; if (taskData.assignee_id) { taskData.assignee_id = taskData.assignee_id.id; @@ -667,6 +671,7 @@ export function implementTask(Task: typeof TaskClass): typeof TaskClass { } const taskSpec: any = { + ...fields, name: this.name, labels: this.labels.map((el) => el.toJSON()), }; @@ -899,8 +904,7 @@ export function implementTask(Task: typeof TaskClass): typeof TaskClass { value: async function saveFramesImplementation( this: TaskClass, ): ReturnType { - return Promise.all(this.jobs.map((job) => patchMeta(job.id))) - .then(() => Promise.resolve()); + return Promise.all(this.jobs.map((job) => patchMeta(job.id))); }, }); diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts index 6209c361912c..1985a72b2683 100644 --- a/cvat-core/src/session.ts +++ b/cvat-core/src/session.ts @@ -22,7 +22,7 @@ import { SerializedLabel, SerializedTask, } from './server-response-types'; import AnnotationGuide from './guide'; -import { FrameData } from './frames'; +import { FrameData, FramesMetaData } from './frames'; import Statistics from './statistics'; import { Request } from './request'; import logger from './logger'; @@ -223,10 +223,11 @@ function buildDuplicatedAPI(prototype) { ); }, async save() { - await PluginRegistry.apiWrapper.call( + const result = await PluginRegistry.apiWrapper.call( this, prototype.frames.save, ); + return result; }, async cachedChunks() { const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.cachedChunks); @@ -377,7 +378,7 @@ export class Session { get: (frame: number, isPlaying?: boolean, step?: number) => Promise; delete: (frame: number) => Promise; restore: (frame: number) => Promise; - save: () => Promise; + save: () => Promise; cachedChunks: () => Promise; preview: () => Promise; contextImage: (frame: number) => Promise>; @@ -663,7 +664,7 @@ export class Job extends Session { return this.#data.target_storage; } - async save(fields: any): Promise { + async save(fields: Record = {}): Promise { const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.save, fields); return result; } @@ -727,9 +728,14 @@ export class Task extends Session { public readonly useZipChunks: boolean; public readonly useCache: boolean; public readonly copyData: boolean; - public readonly cloudStorageID: number; + public readonly cloudStorageId: number; public readonly sortingMethod: string; + public readonly validationMethod: string; + public readonly validationFramesPercent: number; + public readonly validationFramesPerJob: number; + public readonly frameSelectionMethod: string; + constructor(initialData: Readonly & { labels?: SerializedLabel[]; progress?: SerializedTask['jobs']; @@ -774,8 +780,6 @@ export class Task extends Session { cloud_storage_id: undefined, sorting_method: undefined, files: undefined, - - quality_settings: undefined, }; const updateTrigger = new FieldUpdateTrigger(); @@ -1118,8 +1122,11 @@ export class Task extends Session { return result; } - async save(options?: { requestStatusCallback?: (request: Request) => void }): Promise { - const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.save, options); + async save( + fields: Record = {}, + options?: { requestStatusCallback?: (request: Request) => void }, + ): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.save, fields, options); return result; } diff --git a/cvat-ui/.eslintrc.cjs b/cvat-ui/.eslintrc.cjs index 047353d3299e..74f616efcc54 100644 --- a/cvat-ui/.eslintrc.cjs +++ b/cvat-ui/.eslintrc.cjs @@ -29,6 +29,7 @@ module.exports = { 'react/require-default-props': 'off', 'react/no-unused-prop-types': 'off', 'react/no-array-index-key': 'off', + 'react/prop-types': 'off', 'react/jsx-props-no-spreading': 0, 'jsx-quotes': ['error', 'prefer-single'], 'react/static-property-placement': ['warn', 'static public field'], diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 8a0604a8620a..34a288252d25 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.65.2", + "version": "1.66.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 78a9f55b3928..70eb56d4c1e9 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -11,6 +11,7 @@ import { import { filterNull } from 'utils/filter-null'; import { ThunkDispatch, ThunkAction } from 'utils/redux'; +import { ValidationMethod } from 'components/create-task-page/quality-configuration-form'; import { getInferenceStatusAsync } from './models-actions'; import { updateRequestProgress } from './requests-actions'; @@ -213,8 +214,8 @@ ThunkAction { use_zip_chunks: data.advanced.useZipChunks, use_cache: data.advanced.useCache, sorting_method: data.advanced.sortingMethod, - source_storage: new Storage(data.advanced.sourceStorage || { location: StorageLocation.LOCAL }).toJSON(), - target_storage: new Storage(data.advanced.targetStorage || { location: StorageLocation.LOCAL }).toJSON(), + source_storage: new Storage(data.advanced.sourceStorage ?? { location: StorageLocation.LOCAL }).toJSON(), + target_storage: new Storage(data.advanced.targetStorage ?? { location: StorageLocation.LOCAL }).toJSON(), }; if (data.projectId) { @@ -254,13 +255,30 @@ ThunkAction { description.cloud_storage_id = data.cloudStorageId; } + let extras = {}; + + if (data.quality.validationMethod === ValidationMethod.GT) { + extras = { + validation_method: ValidationMethod.GT, + validation_frames_percent: data.quality.validationFramesPercent, + frame_selection_method: data.quality.frameSelectionMethod, + }; + } + + if (data.quality.validationMethod === ValidationMethod.HONEYPOTS) { + extras = { + validation_method: ValidationMethod.HONEYPOTS, + validation_frames_percent: data.quality.validationFramesPercent, + validation_frames_per_job: data.quality.validationFramesPerJob, + }; + } + const taskInstance = new cvat.classes.Task(description); taskInstance.clientFiles = data.files.local; taskInstance.serverFiles = data.files.share.concat(data.files.cloudStorage); taskInstance.remoteFiles = data.files.remote; - try { - const savedTask = await taskInstance.save({ + const savedTask = await taskInstance.save(extras, { requestStatusCallback(request) { let { message } = request; let helperMessage = ''; diff --git a/cvat-ui/src/assets/paid-feature.png b/cvat-ui/src/assets/paid-feature.png new file mode 100644 index 0000000000000000000000000000000000000000..4b9db861aa7331d61d826647d175faa122029c8c GIT binary patch literal 153475 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 z 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

-RQ`jvv6i&wK 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 a|NS3J1bCy7hle=;0000 ), 50]); + menuItems.push([( + + Quality control + + ), 60]); + if (projectID === null) { menuItems.push([( Move to project - ), 60]); + ), 70]); } menuItems.push([( diff --git a/cvat-ui/src/components/analytics-page/analytics-page.tsx b/cvat-ui/src/components/analytics-page/analytics-page.tsx index dfac6803add7..0f0ab289bbc8 100644 --- a/cvat-ui/src/components/analytics-page/analytics-page.tsx +++ b/cvat-ui/src/components/analytics-page/analytics-page.tsx @@ -5,7 +5,6 @@ import './styles.scss'; import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { useLocation, useParams } from 'react-router'; import { Link } from 'react-router-dom'; import { Row, Col } from 'antd/lib/grid'; @@ -14,15 +13,12 @@ import Title from 'antd/lib/typography/Title'; import notification from 'antd/lib/notification'; import moment from 'moment'; import { useIsMounted } from 'utils/hooks'; -import { Project, Task } from 'reducers'; import { - AnalyticsReport, Job, RQStatus, getCore, + AnalyticsReport, Job, Project, RQStatus, Task, getCore, } from 'cvat-core-wrapper'; -import { updateJobAsync } from 'actions/jobs-actions'; import CVATLoadingSpinner from 'components/common/loading-spinner'; import GoBackButton from 'components/common/go-back-button'; import AnalyticsOverview, { DateIntervals } from './analytics-performance'; -import TaskQualityComponent from './task-quality/task-quality-component'; const core = getCore(); @@ -80,7 +76,6 @@ function readInstanceId(type: InstanceType): number { type InstanceType = 'project' | 'task' | 'job'; function AnalyticsPage(): JSX.Element { - const dispatch = useDispatch(); const location = useLocation(); const requestedInstanceType: InstanceType = readInstanceType(location); @@ -224,15 +219,6 @@ function AnalyticsPage(): JSX.Element { }); }, [requestedInstanceType, requestedInstanceID, timePeriod]); - const onJobUpdate = useCallback((job: Job, data: Parameters[0]): void => { - setFetching(true); - dispatch(updateJobAsync(job, data)).finally(() => { - if (isMounted()) { - setFetching(false); - } - }); - }, []); - const onTabKeyChange = useCallback((key: string): void => { setTab(key as AnalyticsTabs); }, []); @@ -242,9 +228,11 @@ function AnalyticsPage(): JSX.Element { let tabs: JSX.Element | null = null; if (instanceType && instance) { backNavigation = ( - - - + + + + + ); let analyticsFor: JSX.Element | null = {`Project #${instance.id}`}; @@ -282,12 +270,7 @@ function AnalyticsPage(): JSX.Element { onCreateReport={onCreateReport} /> ), - }, - ...(instanceType === 'task' ? [{ - key: AnalyticsTabs.QUALITY, - label: 'Quality', - children: , - }] : [])]} + }]} /> ); } @@ -299,11 +282,15 @@ function AnalyticsPage(): JSX.Element { ) : ( - - {backNavigation} - - {title} - {tabs} + + + {backNavigation} + + + {title} + {tabs} + + )} diff --git a/cvat-ui/src/components/analytics-page/shared/quality-settings-modal.tsx b/cvat-ui/src/components/analytics-page/shared/quality-settings-modal.tsx deleted file mode 100644 index 509596df43b1..000000000000 --- a/cvat-ui/src/components/analytics-page/shared/quality-settings-modal.tsx +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2023-2024 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useCallback } from 'react'; -import Text from 'antd/lib/typography/Text'; -import Modal from 'antd/lib/modal'; -import Form from 'antd/lib/form'; -import notification from 'antd/lib/notification'; -import { QualitySettings } from 'cvat-core-wrapper'; -import QualitySettingsForm from '../task-quality/quality-settings-form'; - -interface Props { - fetching: boolean; - qualitySettings: QualitySettings | null; - visible: boolean; - setVisible: (visible: boolean) => void; - setQualitySettings: (settings: QualitySettings) => void; -} - -export default function QualitySettingsModal(props: Props): JSX.Element | null { - const { - fetching, - visible, - qualitySettings: settings, - setVisible, - setQualitySettings, - } = props; - - const [form] = Form.useForm(); - - const onOk = useCallback(async () => { - try { - if (settings) { - const values = await form.validateFields(); - - settings.targetMetric = values.targetMetric; - settings.targetMetricThreshold = values.targetMetricThreshold / 100; - - settings.maxValidationsPerJob = values.maxValidationsPerJob; - - settings.lowOverlapThreshold = values.lowOverlapThreshold / 100; - settings.iouThreshold = values.iouThreshold / 100; - settings.compareAttributes = values.compareAttributes; - - settings.oksSigma = values.oksSigma / 100; - - settings.lineThickness = values.lineThickness / 100; - settings.lineOrientationThreshold = values.lineOrientationThreshold / 100; - settings.orientedLines = values.orientedLines; - - settings.compareGroups = values.compareGroups; - settings.groupMatchThreshold = values.groupMatchThreshold / 100; - - settings.checkCoveredAnnotations = values.checkCoveredAnnotations; - settings.objectVisibilityThreshold = values.objectVisibilityThreshold / 100; - - settings.panopticComparison = values.panopticComparison; - - try { - const responseSettings = await settings.save(); - setQualitySettings(responseSettings); - } catch (error: unknown) { - notification.error({ - message: 'Could not save quality settings', - description: typeof Error === 'object' ? (error as object).toString() : '', - }); - throw error; - } - await settings.save(); - } - setVisible(false); - return settings; - } catch (e) { - return false; - } - }, [settings]); - - const onCancel = useCallback(() => { - setVisible(false); - }, []); - - return ( - Annotation Quality Settings} - open={visible} - onOk={onOk} - onCancel={onCancel} - confirmLoading={fetching} - destroyOnClose - className='cvat-modal-quality-settings' - > - { settings ? ( - - ) : ( - No quality settings - )} - - ); -} diff --git a/cvat-ui/src/components/analytics-page/styles.scss b/cvat-ui/src/components/analytics-page/styles.scss index 23e7886ce078..f9639e2966aa 100644 --- a/cvat-ui/src/components/analytics-page/styles.scss +++ b/cvat-ui/src/components/analytics-page/styles.scss @@ -9,6 +9,7 @@ min-height: $grid-unit-size * 95; padding: $grid-unit-size * 4; padding-bottom: $grid-unit-size; + padding-top: 0; border-radius: $border-radius-base; .ant-tabs { @@ -16,9 +17,7 @@ } } -.cvat-task-quality-page, -.cvat-analytics-overview, -.cvat-project-quality-page { +.cvat-analytics-overview { >.ant-row { margin-top: $grid-unit-size; } @@ -30,19 +29,6 @@ justify-content: space-between; } -.cvat-task-mean-annotation-quality { - .ant-statistic { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } - - .ant-card-body { - padding: $grid-unit-size * 2 $grid-unit-size * 3; - } -} - .cvat-analytics-refresh-button { margin-right: $grid-unit-size; } @@ -69,8 +55,8 @@ .cvat-analytics-settings-tooltip-inner { @extend .cvat-analytics-tooltip-inner; - span:not(:last-child) { - margin-bottom: $grid-unit-size; + div:not(:last-child) { + margin-bottom: $grid-unit-size * 2; } } @@ -93,10 +79,6 @@ } } -.cvat-analytics-time-hint { - font-size: 10px; -} - .cvat-analytics-page { height: 100%; } @@ -105,50 +87,4 @@ overflow-y: auto; width: 100%; height: 100%; - padding-bottom: $grid-unit-size * 2; -} - -.cvat-task-quality-reports-hint { - margin-bottom: $grid-unit-size * 3; -} - -.cvat-job-empty-ground-truth-item { - .ant-card-body { - padding: $grid-unit-size * 3; - } - - .ant-btn { - padding-left: $grid-unit-size * 3; - padding-right: $grid-unit-size * 3; - } -} - -.cvat-quality-settings-switch { - padding: $grid-unit-size $grid-unit-size * 1.25; - border: 1px solid lightgray; - margin-left: $grid-unit-size; - border-radius: $border-radius-base; -} - -.cvat-quality-settings-title { - margin-bottom: $grid-unit-size * 2; - align-items: center; -} - -.cvat-modal-quality-settings { - top: $grid-unit-size * 3; - - .ant-divider { - margin: $grid-unit-size 0; - } - - .ant-form-item-control-input { - min-height: 0; - } -} - -.cvat-job-list-item-conflicts { - display: flex; - justify-content: space-between; - align-items: center; } diff --git a/cvat-ui/src/components/analytics-page/task-quality/empty-job.tsx b/cvat-ui/src/components/analytics-page/task-quality/empty-job.tsx deleted file mode 100644 index ccb3aec48ea5..000000000000 --- a/cvat-ui/src/components/analytics-page/task-quality/empty-job.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import '../styles.scss'; - -import React from 'react'; -import { Link } from 'react-router-dom'; -import { Col, Row } from 'antd/lib/grid'; -import Card from 'antd/lib/card'; -import Button from 'antd/lib/button'; -import Text from 'antd/lib/typography/Text'; - -interface Props { - taskID: number, -} - -function EmptyJobComponent(props: Props): JSX.Element { - const { taskID } = props; - - return ( - - - - - A ground truth job for the task was not created - - - - - - - - ); -} - -export default React.memo(EmptyJobComponent); diff --git a/cvat-ui/src/components/analytics-page/task-quality/gt-conflicts.tsx b/cvat-ui/src/components/analytics-page/task-quality/gt-conflicts.tsx deleted file mode 100644 index baf1f1b55999..000000000000 --- a/cvat-ui/src/components/analytics-page/task-quality/gt-conflicts.tsx +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (C) 2023-2024 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Text from 'antd/lib/typography/Text'; -import { Col, Row } from 'antd/lib/grid'; - -import { QualityReport, QualitySummary } from 'cvat-core-wrapper'; -import { clampValue, percent } from 'utils/quality'; -import AnalyticsCard from '../views/analytics-card'; - -interface Props { - taskReport: QualityReport | null; -} - -interface ConflictTooltipProps { - reportSummary?: QualitySummary; -} - -export function ConflictsTooltip(props: ConflictTooltipProps): JSX.Element { - const { reportSummary } = props; - return ( - - - - Warnings: - - - Low overlap:  - {reportSummary?.conflictsByType.lowOverlap || 0} - - - Mismatching direction:  - {reportSummary?.conflictsByType.mismatchingDirection || 0} - - - Mismatching attributes:  - {reportSummary?.conflictsByType.mismatchingAttributes || 0} - - - Mismatching groups:  - {reportSummary?.conflictsByType.mismatchingGroups || 0} - - - Covered annotation:  - {reportSummary?.conflictsByType.coveredAnnotation || 0} - - - - - Errors: - - - Missing annotations:  - {reportSummary?.conflictsByType.missingAnnotations || 0} - - - Extra annotations:  - {reportSummary?.conflictsByType.extraAnnotations || 0} - - - Mismatching label:  - {reportSummary?.conflictsByType.mismatchingLabel || 0} - - - - ); -} - -function GTConflicts(props: Props): JSX.Element { - const { taskReport } = props; - let conflictsRepresentation: string | number = 'N/A'; - let reportSummary; - if (taskReport) { - reportSummary = taskReport.summary; - conflictsRepresentation = clampValue(reportSummary?.conflictCount); - } - - const bottomElement = ( - <> - - Errors: - {' '} - {clampValue(reportSummary?.errorCount)} - {reportSummary?.errorCount ? - ` (${percent(reportSummary?.errorCount, reportSummary?.conflictCount)})` : ''} - - - {', '} - Warnings: - {' '} - {clampValue(reportSummary?.warningCount)} - { reportSummary?.warningCount ? - ` (${percent(reportSummary?.warningCount, reportSummary?.conflictCount)})` : '' } - - - ); - - return ( - } - size={12} - bottomElement={bottomElement} - /> - ); -} - -export default React.memo(GTConflicts); diff --git a/cvat-ui/src/components/analytics-page/task-quality/issues.tsx b/cvat-ui/src/components/analytics-page/task-quality/issues.tsx deleted file mode 100644 index 63b78109fa64..000000000000 --- a/cvat-ui/src/components/analytics-page/task-quality/issues.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import '../styles.scss'; - -import React, { useEffect, useState } from 'react'; -import Text from 'antd/lib/typography/Text'; -import notification from 'antd/lib/notification'; -import { Task } from 'cvat-core-wrapper'; -import { useIsMounted } from 'utils/hooks'; -import { clampValue, percent } from 'utils/quality'; -import AnalyticsCard from '../views/analytics-card'; - -interface Props { - task: Task; -} - -function Issues(props: Props): JSX.Element { - const { task } = props; - - const [issuesCount, setIssuesCount] = useState(0); - const [resolvedIssues, setResolvedIssues] = useState(0); - const isMounted = useIsMounted(); - - useEffect(() => { - task - .issues() - .then((issues: any[]) => { - if (isMounted()) { - setIssuesCount(issues.length); - setResolvedIssues(issues.reduce((acc, issue) => (issue.resolved ? acc + 1 : acc), 0)); - } - }) - .catch((_error: any) => { - if (isMounted()) { - notification.error({ - description: _error.toString(), - message: "Couldn't fetch issues", - className: 'cvat-notification-notice-get-issues-error', - }); - } - }); - }, []); - - const bottomElement = ( - - Resolved: - {' '} - {clampValue(resolvedIssues)} - {resolvedIssues ? ` (${percent(resolvedIssues, issuesCount)})` : ''} - - ); - - return ( - - ); -} - -export default React.memo(Issues); diff --git a/cvat-ui/src/components/analytics-page/task-quality/job-list.tsx b/cvat-ui/src/components/analytics-page/task-quality/job-list.tsx deleted file mode 100644 index 550ab6764de0..000000000000 --- a/cvat-ui/src/components/analytics-page/task-quality/job-list.tsx +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (C) 2023-2024 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useState } from 'react'; -import { useHistory } from 'react-router'; -import { Row, Col } from 'antd/lib/grid'; -import { DownloadOutlined, QuestionCircleOutlined } from '@ant-design/icons'; -import { Key } from 'antd/lib/table/interface'; -import Table from 'antd/lib/table'; -import Button from 'antd/lib/button'; -import Text from 'antd/lib/typography/Text'; - -import { - Task, Job, JobType, QualityReport, getCore, - TargetMetric, -} from 'cvat-core-wrapper'; -import CVATTooltip from 'components/common/cvat-tooltip'; -import Tag from 'antd/lib/tag'; -import { - collectAssignees, QualityColors, sorter, toRepresentation, -} from 'utils/quality'; -import { ConflictsTooltip } from './gt-conflicts'; - -interface Props { - task: Task; - jobsReports: QualityReport[]; - getQualityColor: (value?: number) => QualityColors; - targetMetric: TargetMetric; -} - -function JobListComponent(props: Props): JSX.Element { - const { - task: taskInstance, - jobsReports: jobsReportsArray, - getQualityColor, - targetMetric, - } = props; - - const jobsReports: Record = jobsReportsArray - .reduce((acc, report) => ({ ...acc, [report.jobID]: report }), {}); - const history = useHistory(); - const { id: taskId, jobs } = taskInstance; - const [renderedJobs] = useState(jobs.filter((job: Job) => job.type === JobType.ANNOTATION)); - - const columns = [ - { - title: 'Job', - dataIndex: 'job', - key: 'job', - sorter: sorter('key'), - render: (id: number): JSX.Element => ( -

- -
- ), - }, - { - title: 'Stage', - dataIndex: 'stage', - key: 'stage', - className: 'cvat-job-item-stage', - render: (jobInstance: any): JSX.Element => { - const { stage } = jobInstance; - - return ( -
- {stage} -
- ); - }, - sorter: sorter('stage.stage'), - filters: [ - { text: 'annotation', value: 'annotation' }, - { text: 'validation', value: 'validation' }, - { text: 'acceptance', value: 'acceptance' }, - ], - onFilter: (value: boolean | Key, record: any) => record.stage.stage === value, - }, - { - title: 'Assignee', - dataIndex: 'assignee', - key: 'assignee', - className: 'cvat-job-item-assignee', - render: (report: QualityReport): JSX.Element => ( - {report?.assignee?.username} - ), - sorter: sorter('assignee.assignee.username'), - filters: collectAssignees(jobsReportsArray), - onFilter: (value: boolean | Key, record: any) => ( - record.assignee.assignee?.username || false - ) === value, - }, - { - title: 'Frame intersection', - dataIndex: 'frame_intersection', - key: 'frame_intersection', - className: 'cvat-job-item-frame-intersection', - sorter: sorter('frame_intersection.summary.frameCount'), - render: (report?: QualityReport): JSX.Element => { - const frames = report?.summary.frameCount; - const frameSharePercent = report?.summary?.frameSharePercent; - return ( - - {toRepresentation(frames, false, 0)} - {frames ? ` (${toRepresentation(frameSharePercent)})` : ''} - - ); - }, - }, - { - title: 'Conflicts', - dataIndex: 'conflicts', - key: 'conflicts', - className: 'cvat-job-item-conflicts', - sorter: sorter('conflicts.summary.conflictCount'), - render: (report: QualityReport): JSX.Element => { - const conflictCount = report?.summary?.conflictCount; - return ( -
- - {conflictCount || 0} - - } - className='cvat-analytics-tooltip' - overlayStyle={{ maxWidth: '500px' }} - > - - -
- ); - }, - }, - { - title: 'Quality', - dataIndex: 'quality', - key: 'quality', - align: 'center' as const, - className: 'cvat-job-item-quality', - sorter: sorter(`quality.summary.${targetMetric}`), - render: (report?: QualityReport): JSX.Element => { - const meanAccuracy = report?.summary?.[targetMetric]; - const accuracyRepresentation = toRepresentation(meanAccuracy); - - return ( - accuracyRepresentation.includes('N/A') ? ( - - N/A - - ) : - {accuracyRepresentation} - ); - }, - }, - { - title: 'Download', - dataIndex: 'download', - key: 'download', - className: 'cvat-job-item-quality-report-download', - align: 'center' as const, - render: (job: Job): JSX.Element => { - const report = jobsReports[job.id]; - const reportID = report?.id; - return ( - reportID ? ( -
- - - ) : - ); - }, - }, - ]; - const data = renderedJobs.reduce((acc: any[], job: any) => { - const report = jobsReports[job.id]; - acc.push({ - key: job.id, - job: job.id, - download: job, - stage: job, - assignee: report, - quality: report, - conflicts: report, - frame_intersection: report, - }); - - return acc; - }, []); - - return ( -
- - - Jobs - - - 'cvat-task-jobs-table-row'} - columns={columns} - dataSource={data} - size='small' - /> - - ); -} - -export default React.memo(JobListComponent); diff --git a/cvat-ui/src/components/analytics-page/task-quality/mean-quality.tsx b/cvat-ui/src/components/analytics-page/task-quality/mean-quality.tsx deleted file mode 100644 index c795cddb1b10..000000000000 --- a/cvat-ui/src/components/analytics-page/task-quality/mean-quality.tsx +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2023-2024 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { DownloadOutlined, SettingOutlined } from '@ant-design/icons'; -import { Col, Row } from 'antd/lib/grid'; -import Text from 'antd/lib/typography/Text'; -import Button from 'antd/lib/button'; - -import { QualityReport, TargetMetric, getCore } from 'cvat-core-wrapper'; -import { toRepresentation } from 'utils/quality'; -import AnalyticsCard from '../views/analytics-card'; - -interface Props { - taskID: number; - taskReport: QualityReport | null; - targetMetric: TargetMetric; - setQualitySettingsVisible: (visible: boolean) => void; -} - -function MeanQuality(props: Props): JSX.Element { - const { - taskID, taskReport, targetMetric, setQualitySettingsVisible, - } = props; - const reportSummary = taskReport?.summary; - - const tooltip = ( -
- - Mean annotation quality consists of: - - - Correct annotations:  - {reportSummary?.validCount || 0} - - - Task annotations:  - {reportSummary?.dsCount || 0} - - - GT annotations:  - {reportSummary?.gtCount || 0} - - - Accuracy:  - {toRepresentation(reportSummary?.accuracy)} - - - Precision:  - {toRepresentation(reportSummary?.precision)} - - - Recall:  - {toRepresentation(reportSummary?.recall)} - -
- ); - - const downloadReportButton = ( -
- -
- { - taskReport?.id ? ( - - ) : null - } - - - setQualitySettingsVisible(true)} - /> - - - - ); - - return ( - - ); -} - -export default React.memo(MeanQuality); diff --git a/cvat-ui/src/components/analytics-page/task-quality/task-quality-component.tsx b/cvat-ui/src/components/analytics-page/task-quality/task-quality-component.tsx deleted file mode 100644 index 3f58a1d666d9..000000000000 --- a/cvat-ui/src/components/analytics-page/task-quality/task-quality-component.tsx +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (C) 2023-2024 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useEffect, useReducer } from 'react'; -import moment from 'moment'; -import { Row } from 'antd/lib/grid'; -import Text from 'antd/lib/typography/Text'; -import notification from 'antd/lib/notification'; -import CVATLoadingSpinner from 'components/common/loading-spinner'; -import JobItem from 'components/job-item/job-item'; -import { - Job, JobType, QualityReport, QualitySettings, TargetMetric, Task, getCore, -} from 'cvat-core-wrapper'; -import { useIsMounted } from 'utils/hooks'; -import { qualityColorGenerator, QualityColors } from 'utils/quality'; -import { ActionUnion, createAction } from 'utils/redux'; -import EmptyGtJob from './empty-job'; -import GtConflicts from './gt-conflicts'; -import Issues from './issues'; -import JobList from './job-list'; -import MeanQuality from './mean-quality'; -import QualitySettingsModal from '../shared/quality-settings-modal'; - -const core = getCore(); - -interface Props { - task: Task; - onJobUpdate: (job: Job, data: Parameters[0]) => void; -} - -interface State { - fetching: boolean; - taskReport: QualityReport | null; - jobsReports: QualityReport[]; - qualitySettings: { - settings: QualitySettings | null; - fetching: boolean; - visible: boolean; - getQualityColor: ((value?: number) => QualityColors) | null; - targetMetric: TargetMetric | null; - }, -} - -enum ReducerActionType { - SET_FETCHING = 'SET_FETCHING', - SET_TASK_REPORT = 'SET_TASK_REPORT', - SET_JOBS_REPORTS = 'SET_JOBS_REPORTS', - SET_QUALITY_SETTINGS = 'SET_QUALITY_SETTINGS', - SET_QUALITY_SETTINGS_VISIBLE = 'SET_QUALITY_SETTINGS_VISIBLE', - SET_QUALITY_SETTINGS_FETCHING = 'SET_QUALITY_SETTINGS_FETCHING', -} - -export const reducerActions = { - setFetching: (fetching: boolean) => ( - createAction(ReducerActionType.SET_FETCHING, { fetching }) - ), - setTaskReport: (qualityReport: QualityReport) => ( - createAction(ReducerActionType.SET_TASK_REPORT, { qualityReport }) - ), - setJobsReports: (qualityReports: QualityReport[]) => ( - createAction(ReducerActionType.SET_JOBS_REPORTS, { qualityReports }) - ), - setQualitySettings: (qualitySettings: QualitySettings) => ( - createAction(ReducerActionType.SET_QUALITY_SETTINGS, { qualitySettings }) - ), - setQualitySettingsVisible: (visible: boolean) => ( - createAction(ReducerActionType.SET_QUALITY_SETTINGS_VISIBLE, { visible }) - ), - setQualitySettingsFetching: (fetching: boolean) => ( - createAction(ReducerActionType.SET_QUALITY_SETTINGS_FETCHING, { fetching }) - ), -}; - -const reducer = (state: State, action: ActionUnion): State => { - if (action.type === ReducerActionType.SET_FETCHING) { - return { - ...state, - fetching: action.payload.fetching, - }; - } - - if (action.type === ReducerActionType.SET_TASK_REPORT) { - return { - ...state, - taskReport: action.payload.qualityReport, - }; - } - - if (action.type === ReducerActionType.SET_JOBS_REPORTS) { - return { - ...state, - jobsReports: action.payload.qualityReports, - }; - } - - if (action.type === ReducerActionType.SET_QUALITY_SETTINGS) { - return { - ...state, - qualitySettings: { - ...state.qualitySettings, - settings: action.payload.qualitySettings, - getQualityColor: qualityColorGenerator(action.payload.qualitySettings.targetMetricThreshold), - targetMetric: action.payload.qualitySettings.targetMetric, - }, - }; - } - - if (action.type === ReducerActionType.SET_QUALITY_SETTINGS_VISIBLE) { - return { - ...state, - qualitySettings: { - ...state.qualitySettings, - visible: action.payload.visible, - }, - }; - } - - if (action.type === ReducerActionType.SET_QUALITY_SETTINGS_FETCHING) { - return { - ...state, - qualitySettings: { - ...state.qualitySettings, - fetching: action.payload.fetching, - }, - }; - } - - return state; -}; - -function TaskQualityComponent(props: Props): JSX.Element { - const { task, onJobUpdate } = props; - const isMounted = useIsMounted(); - - const [state, dispatch] = useReducer(reducer, { - fetching: true, - taskReport: null, - jobsReports: [], - qualitySettings: { - settings: null, - fetching: true, - visible: false, - getQualityColor: null, - targetMetric: null, - }, - }); - - useEffect(() => { - dispatch(reducerActions.setFetching(true)); - dispatch(reducerActions.setQualitySettingsFetching(true)); - - function handleError(error: Error): void { - if (isMounted()) { - notification.error({ - description: error.toString(), - message: 'Could not initialize quality analytics page', - }); - } - } - - core.analytics.quality.reports({ pageSize: 1, target: 'task', taskID: task.id }).then(([report]) => { - let reportRequest = Promise.resolve([]); - if (report) { - reportRequest = core.analytics.quality.reports({ - pageSize: task.jobs.length, - parentID: report.id, - target: 'job', - }); - } - const settingsRequest = core.analytics.quality.settings.get({ taskID: task.id }); - - Promise.all([reportRequest, settingsRequest]).then(([jobReports, settings]) => { - dispatch(reducerActions.setQualitySettings(settings)); - dispatch(reducerActions.setTaskReport(report || null)); - dispatch(reducerActions.setJobsReports(jobReports)); - }).catch(handleError).finally(() => { - dispatch(reducerActions.setQualitySettingsFetching(false)); - dispatch(reducerActions.setFetching(false)); - }); - }).catch(handleError); - }, [task?.id]); - - const { - fetching, taskReport, jobsReports, - qualitySettings: { - settings: qualitySettings, fetching: qualitySettingsFetching, visible: qualitySettingsVisible, - getQualityColor, targetMetric, - }, - } = state; - const gtJob = task.jobs.find((job: Job) => job.type === JobType.GROUND_TRUTH); - const settingsInitialized = qualitySettings && getQualityColor && targetMetric; - - return ( -
- { - fetching ? ( - - ) : ( - <> - { - gtJob && settingsInitialized ? ( - <> - - - { `Created ${taskReport?.id ? moment(taskReport.createdDate).fromNow() : ''}`} - - - - dispatch(reducerActions.setQualitySettingsVisible(visible)) - } - taskID={task.id} - targetMetric={targetMetric} - /> - - - - - - { - (!(gtJob && gtJob.stage === 'acceptance' && gtJob.state === 'completed')) ? ( - - - Quality reports are not computed unless the GT job is in the  - completed state -  and  - acceptance stage. - - - ) : null - } - - - - - - - - ) : ( - - - - ) - } - dispatch(reducerActions.setQualitySettings(settings)) - } - visible={qualitySettingsVisible} - setVisible={ - (visible) => dispatch(reducerActions.setQualitySettingsVisible(visible)) - } - /> - - ) - } -
- ); -} - -export default React.memo(TaskQualityComponent); diff --git a/cvat-ui/src/components/analytics-page/views/analytics-card.tsx b/cvat-ui/src/components/analytics-page/views/analytics-card.tsx index 1bd4538d817e..655759bfaf74 100644 --- a/cvat-ui/src/components/analytics-page/views/analytics-card.tsx +++ b/cvat-ui/src/components/analytics-page/views/analytics-card.tsx @@ -11,7 +11,10 @@ import { QuestionCircleOutlined } from '@ant-design/icons'; interface Props { title: string; - size?: number; + size?: { + cardSize?: number; + leftElementSize?: number; + }; className?: string; value?: string | number; tooltip?: JSX.Element; @@ -26,10 +29,10 @@ function AnalyticsCard(props: Props): JSX.Element { } = props; return ( -
+ - + diff --git a/cvat-ui/src/components/annotation-page/annotations-actions/annotations-actions-modal.tsx b/cvat-ui/src/components/annotation-page/annotations-actions/annotations-actions-modal.tsx index d3069c62dd5f..27898da9fa2a 100644 --- a/cvat-ui/src/components/annotation-page/annotations-actions/annotations-actions-modal.tsx +++ b/cvat-ui/src/components/annotation-page/annotations-actions/annotations-actions-modal.tsx @@ -14,6 +14,8 @@ import Select from 'antd/lib/select'; import notification from 'antd/lib/notification'; import Text from 'antd/lib/typography/Text'; import Modal from 'antd/lib/modal'; +import Alert from 'antd/lib/alert'; +import InputNumber from 'antd/lib/input-number'; import config from 'config'; import { useIsMounted } from 'utils/hooks'; @@ -25,7 +27,6 @@ import { import { Canvas } from 'cvat-canvas-wrapper'; import { fetchAnnotationsAsync, saveAnnotationsAsync } from 'actions/annotation-actions'; import { switchAutoSave } from 'actions/settings-actions'; -import { Alert, InputNumber } from 'antd'; import { clamp } from 'utils/math'; const core = getCore(); diff --git a/cvat-ui/src/components/create-cloud-storage-page/manifests-manager.tsx b/cvat-ui/src/components/create-cloud-storage-page/manifests-manager.tsx index 1dbd43c3e460..f88348287705 100644 --- a/cvat-ui/src/components/create-cloud-storage-page/manifests-manager.tsx +++ b/cvat-ui/src/components/create-cloud-storage-page/manifests-manager.tsx @@ -11,9 +11,9 @@ import Form from 'antd/lib/form'; import { FormListFieldData, FormListOperation } from 'antd/lib/form/FormList'; import Input from 'antd/lib/input'; import Row from 'antd/lib/row'; +import Alert from 'antd/lib/alert'; import Tooltip from 'antd/lib/tooltip'; import config from 'config'; -import { Alert } from 'antd'; interface Props { form: any; diff --git a/cvat-ui/src/components/create-job-page/job-form.tsx b/cvat-ui/src/components/create-job-page/job-form.tsx index 60445c6a0ef8..b94d8a5f3fac 100644 --- a/cvat-ui/src/components/create-job-page/job-form.tsx +++ b/cvat-ui/src/components/create-job-page/job-form.tsx @@ -22,6 +22,7 @@ import { createJobAsync } from 'actions/jobs-actions'; export enum FrameSelectionMethod { RANDOM = 'random_uniform', + RANDOM_PER_JOB = 'random_per_job', } interface JobDataMutual { @@ -144,6 +145,9 @@ function JobForm(props: Props): JSX.Element { Random + + Random per job + diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx index e39716809d73..655d732c3430 100644 --- a/cvat-ui/src/components/create-task-page/create-task-content.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -21,11 +21,13 @@ import FileManagerComponent, { Files } from 'components/file-manager/file-manage import { RemoteFile } from 'components/file-manager/remote-browser'; import { getFileContentType, getContentTypeRemoteFile, getFileNameFromPath } from 'utils/files'; +import { FrameSelectionMethod } from 'components/create-job-page/job-form'; import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form'; import ProjectSearchField from './project-search-field'; import ProjectSubsetField from './project-subset-field'; import MultiTasksProgress from './multi-task-progress'; import AdvancedConfigurationForm, { AdvancedConfiguration, SortingMethod } from './advanced-configuration-form'; +import QualityConfigurationForm, { QualityConfiguration, ValidationMethod } from './quality-configuration-form'; type TabName = 'local' | 'share' | 'remote' | 'cloudStorage'; const core = getCore(); @@ -35,6 +37,7 @@ export interface CreateTaskData { basic: BaseConfiguration; subset: string; advanced: AdvancedConfiguration; + quality: QualityConfiguration; labels: any[]; files: Files; activeFileManagerTab: TabName; @@ -83,6 +86,12 @@ const defaultState: State = { useProjectSourceStorage: true, useProjectTargetStorage: true, }, + quality: { + validationMethod: ValidationMethod.NONE, + validationFramesPercent: 5, + validationFramesPerJob: 1, + frameSelectionMethod: FrameSelectionMethod.RANDOM, + }, labels: [], files: { local: [], @@ -152,6 +161,7 @@ function filterFiles(remoteFiles: RemoteFile[], many: boolean): RemoteFile[] { class CreateTaskContent extends React.PureComponent { private basicConfigurationComponent: RefObject; private advancedConfigurationComponent: RefObject; + private qualityConfigurationComponent: RefObject; private fileManagerComponent: any; public constructor(props: Props & RouteComponentProps) { @@ -159,6 +169,7 @@ class CreateTaskContent extends React.PureComponent(); this.advancedConfigurationComponent = React.createRef(); + this.qualityConfigurationComponent = React.createRef(); } public componentDidMount(): void { @@ -246,6 +257,14 @@ class CreateTaskContent extends React.PureComponent => ( + new Promise((resolve) => { + this.setState({ + quality: { ...values }, + }, resolve); + }) + ); + private handleSubmitAdvancedConfiguration = (values: AdvancedConfiguration): Promise => ( new Promise((resolve) => { this.setState({ @@ -284,6 +303,16 @@ class CreateTaskContent extends React.PureComponent { + this.qualityConfigurationComponent.current?.resetFields(); + this.setState(() => ({ + quality: { + ...defaultState.quality, + validationMethod: value, + }, + })); + }; + private focusToForm = (): void => { this.basicConfigurationComponent.current?.focus(); }; @@ -431,31 +460,38 @@ class CreateTaskContent extends React.PureComponent { + const promises = []; + if (this.advancedConfigurationComponent.current) { - return this.advancedConfigurationComponent.current.submit(); + promises.push(this.advancedConfigurationComponent.current.submit()); } + + if (this.qualityConfigurationComponent.current) { + promises.push(this.qualityConfigurationComponent.current.submit()); + } + + return Promise.all(promises); + }).then(() => { if (projectId) { - return core.projects.get({ id: projectId }) - .then((response: any) => { - const [project] = response; - const { advanced } = this.state; - return this.handleSubmitAdvancedConfiguration({ - ...advanced, - sourceStorage: new Storage( - project.sourceStorage || { location: StorageLocation.LOCAL }, - ), - targetStorage: new Storage( - project.targetStorage || { location: StorageLocation.LOCAL }, - ), - }); - }) - .catch((error: Error): void => { - throw new Error(`Couldn't fetch the project ${projectId} ${error.toString()}`); + return core.projects.get({ id: projectId }).then((response) => { + const [project] = response; + const { advanced } = this.state; + return this.handleSubmitAdvancedConfiguration({ + ...advanced, + sourceStorage: advanced.useProjectSourceStorage ? new Storage( + project.sourceStorage || { location: StorageLocation.LOCAL }, + ) : advanced.sourceStorage, + targetStorage: advanced.useProjectTargetStorage ? new Storage( + project.targetStorage || { location: StorageLocation.LOCAL }, + ) : advanced.targetStorage, }); + }).catch((error: Error): void => { + throw new Error(`Couldn't fetch the project ${projectId} ${error.toString()}`); + }); } + return Promise.resolve(); - }) - .then(resolve) + }).then(resolve) .catch((error: Error | ValidateErrorEntity): void => { notification.error({ message: 'Could not create a task', @@ -564,6 +600,7 @@ class CreateTaskContent extends React.PureComponent + Quality, + children: ( + + ), + }]} + /> + + ); + } + private renderFooterSingleTask(): JSX.Element { const { uploadFileErrorMessage, loading, statusInProgressTask: status } = this.state; @@ -967,6 +1030,7 @@ class CreateTaskContent extends React.PureComponent {many ? this.renderFooterMultiTasks() : this.renderFooterSingleTask() } diff --git a/cvat-ui/src/components/create-task-page/quality-configuration-form.tsx b/cvat-ui/src/components/create-task-page/quality-configuration-form.tsx new file mode 100644 index 000000000000..bc6f8888cfa0 --- /dev/null +++ b/cvat-ui/src/components/create-task-page/quality-configuration-form.tsx @@ -0,0 +1,176 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React, { RefObject } from 'react'; +import Input from 'antd/lib/input'; +import Form, { FormInstance } from 'antd/lib/form'; +import { PercentageOutlined } from '@ant-design/icons'; +import Radio from 'antd/lib/radio'; +import { FrameSelectionMethod } from 'components/create-job-page/job-form'; +import { Col, Row } from 'antd/lib/grid'; +import Select from 'antd/lib/select'; + +export interface QualityConfiguration { + validationMethod: ValidationMethod; + validationFramesPercent: number; + validationFramesPerJob: number; + frameSelectionMethod: FrameSelectionMethod; +} + +interface Props { + onSubmit(values: QualityConfiguration): Promise; + initialValues: QualityConfiguration; + validationMethod: ValidationMethod; + onChangeValidationMethod: (method: ValidationMethod) => void; +} + +export enum ValidationMethod { + NONE = 'none', + GT = 'gt_job', + HONEYPOTS = 'gt_pool', +} + +export default class QualityConfigurationForm extends React.PureComponent { + private formRef: RefObject; + + public constructor(props: Props) { + super(props); + this.formRef = React.createRef(); + } + + public submit(): Promise { + const { onSubmit } = this.props; + if (this.formRef.current) { + return this.formRef.current.validateFields().then((values: QualityConfiguration) => { + onSubmit(values); + }); + } + + return Promise.reject(new Error('Quality form ref is empty')); + } + + public resetFields(): void { + this.formRef.current?.resetFields(['validationFramesPercent', 'validationFramesPerJob', 'frameSelectionMethod']); + } + + private gtParamsBlock(): JSX.Element { + return ( + <> + + + + + + + +value} + rules={[ + { required: true, message: 'The field is required' }, + { + type: 'number', min: 0, max: 100, message: 'Value is not valid', + }, + ]} + > + } + /> + + + + ); + } + + private honeypotsParamsBlock(): JSX.Element { + return ( + + + +value} + rules={[ + { required: true, message: 'The field is required' }, + { + type: 'number', min: 0, max: 100, message: 'Value is not valid', + }, + ]} + > + } /> + + + + +value} + rules={[ + { required: true, message: 'The field is required' }, + { + type: 'number', min: 0, max: 100, message: 'Value is not valid', + }, + ]} + > + } /> + + + + ); + } + + public render(): JSX.Element { + const { initialValues, validationMethod, onChangeValidationMethod } = this.props; + + let paramsBlock: JSX.Element | null = null; + if (validationMethod === ValidationMethod.GT) { + paramsBlock = this.gtParamsBlock(); + } else if (validationMethod === ValidationMethod.HONEYPOTS) { + paramsBlock = this.honeypotsParamsBlock(); + } + + return ( + + + { + onChangeValidationMethod(e.target.value); + }} + > + + None + + + Ground Truth + + + Honeypots + + + + { paramsBlock } + + ); + } +} diff --git a/cvat-ui/src/components/create-task-page/styles.scss b/cvat-ui/src/components/create-task-page/styles.scss index b99de0fbf480..ced94273e436 100644 --- a/cvat-ui/src/components/create-task-page/styles.scss +++ b/cvat-ui/src/components/create-task-page/styles.scss @@ -44,3 +44,10 @@ display: table-cell; } } + + +.cvat-quality-configuration-wrapper { + .ant-collapse-item > .ant-collapse-header { + align-items: center; + } +} diff --git a/cvat-ui/src/components/customizable-components/index.tsx b/cvat-ui/src/components/customizable-components/index.tsx index e036fbfb21ef..e44cfa3cc247 100644 --- a/cvat-ui/src/components/customizable-components/index.tsx +++ b/cvat-ui/src/components/customizable-components/index.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2023 CVAT.ai Corporation +// Copyright (C) 2023-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -9,6 +9,9 @@ import Button from 'antd/lib/button'; import { SaveIcon } from 'icons'; import GlobalHotKeys from 'utils/mousetrap-react'; import CVATTooltip from 'components/common/cvat-tooltip'; +import PaidFeaturePlaceholder from 'components/customizable-components/paid-feature-placeholder/paid-feature-placeholder'; +import AllocationTable from 'components/quality-control/task-quality/allocation-table'; +import config from 'config'; import { CombinedState } from 'reducers'; import { ShortcutScope } from 'utils/enums'; import { registerComponentShortcuts } from 'actions/shortcuts-actions'; @@ -55,6 +58,13 @@ const storage = { ); }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + QUALITY_CONTROL_OVERVIEW: [(_: any) => ( + + )], + + QUALITY_CONTROL_ALLOCATION_TABLE: [AllocationTable], }; export default storage; diff --git a/cvat-ui/src/components/customizable-components/paid-feature-placeholder/paid-feature-placeholder.tsx b/cvat-ui/src/components/customizable-components/paid-feature-placeholder/paid-feature-placeholder.tsx new file mode 100644 index 000000000000..b9994046bfd8 --- /dev/null +++ b/cvat-ui/src/components/customizable-components/paid-feature-placeholder/paid-feature-placeholder.tsx @@ -0,0 +1,69 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Text from 'antd/lib/typography/Text'; +import Card from 'antd/es/card/Card'; +import Button from 'antd/lib/button'; +import { Row, Col } from 'antd/es/grid'; + +import './styles.scss'; +import CVATMarkdown from 'components/common/cvat-markdown'; +import config from 'config'; + +interface Props { + featureDescription: string; +} + +function PaidFeaturePlaceholder(props: Readonly): JSX.Element | null { + const { featureDescription } = props; + + const { PAID_PLACEHOLDER_CONFIG } = config; + const { url } = PAID_PLACEHOLDER_CONFIG; + + return ( +
+ } + > + +
+ + + + You discovered a premium feature + + + + + + + {featureDescription} + + + + + + + + + + + + + + + ); +} + +export default React.memo(PaidFeaturePlaceholder); diff --git a/cvat-ui/src/components/customizable-components/paid-feature-placeholder/styles.scss b/cvat-ui/src/components/customizable-components/paid-feature-placeholder/styles.scss new file mode 100644 index 000000000000..673ba7b499cf --- /dev/null +++ b/cvat-ui/src/components/customizable-components/paid-feature-placeholder/styles.scss @@ -0,0 +1,39 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base'; + +.cvat-paid-feature-placeholder-wrapper { + display: flex; + justify-content: center; + align-items: center; + padding: $grid-unit-size * 2; +} + +.cvat-paid-feature-placeholder { + width: $grid-unit-size * 55; + height: $grid-unit-size * 77; + + .ant-card-body { + height: 80%; + } +} + +.cvat-paid-feature-placeholder-title { + text-align: center; + + span { + font-size: 28px; + font-weight: bold; + } +} + +.cvat-paid-feature-placeholder-description { + text-align: center; +} + +.cvat-paid-feature-placeholder-inner-wrapper { + height: 100%; + justify-content: space-between; +} diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 1282ac21bcaf..46abf3c3b753 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -81,6 +81,7 @@ import EmailVerificationSentPage from './email-confirmation-pages/email-verifica import IncorrectEmailConfirmationPage from './email-confirmation-pages/incorrect-email-confirmation'; import CreateJobPage from './create-job-page/create-job-page'; import AnalyticsPage from './analytics-page/analytics-page'; +import QualityControlPage from './quality-control/quality-control-page'; import InvitationWatcher from './invitation-watcher/invitation-watcher'; interface CVATAppProps { @@ -511,6 +512,7 @@ class CVATApplication extends React.PureComponent + diff --git a/cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx b/cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx index 0736e851844b..f65e8eb5c8a1 100644 --- a/cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/shortcut-settings.tsx @@ -12,13 +12,14 @@ import { Alert, } from 'antd/lib'; import Search from 'antd/lib/input/Search'; +import Empty from 'antd/lib/empty'; +import Modal from 'antd/lib/modal'; import React, { useState, useMemo, useCallback, } from 'react'; import { ShortcutScope } from 'utils/enums'; import { KeyMap } from 'utils/mousetrap-react'; -import { Empty, Modal } from 'antd'; import { shortcutsActions } from 'actions/shortcuts-actions'; import { useDispatch, useSelector } from 'react-redux'; import { CombinedState } from 'reducers'; diff --git a/cvat-ui/src/components/job-item/job-actions-menu.tsx b/cvat-ui/src/components/job-item/job-actions-menu.tsx index 14e209af4eb5..0a3ac6c1900e 100644 --- a/cvat-ui/src/components/job-item/job-actions-menu.tsx +++ b/cvat-ui/src/components/job-item/job-actions-menu.tsx @@ -6,22 +6,22 @@ import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router'; import Modal from 'antd/lib/modal'; -import { exportActions } from 'actions/export-actions'; -import { Job, JobType } from 'cvat-core-wrapper'; +import { exportActions } from 'actions/export-actions'; import { deleteJobAsync } from 'actions/jobs-actions'; import { importActions } from 'actions/import-actions'; +import { Job, JobType } from 'cvat-core-wrapper'; import Menu, { MenuInfo } from 'components/dropdown-menu'; interface Props { job: Job; - onJobUpdate: (job: Job, fields: Parameters[0]) => void; } function JobActionsMenu(props: Props): JSX.Element { const { job } = props; - const history = useHistory(); + const dispatch = useDispatch(); + const history = useHistory(); const onDelete = useCallback(() => { Modal.confirm({ diff --git a/cvat-ui/src/components/job-item/job-item.tsx b/cvat-ui/src/components/job-item/job-item.tsx index 70c7d2bd56f7..3fe5267f9ef2 100644 --- a/cvat-ui/src/components/job-item/job-item.tsx +++ b/cvat-ui/src/components/job-item/job-item.tsx @@ -5,9 +5,7 @@ import './styles.scss'; import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; -import { CombinedState } from 'reducers'; import moment from 'moment'; import { Col, Row } from 'antd/lib/grid'; import Card from 'antd/lib/card'; @@ -27,6 +25,8 @@ import { import { useIsMounted } from 'utils/hooks'; import UserSelector from 'components/task-page/user-selector'; import CVATTooltip from 'components/common/cvat-tooltip'; +import { useSelector } from 'react-redux'; +import { CombinedState } from 'reducers'; import JobActionsMenu from './job-actions-menu'; interface Props { @@ -100,12 +100,15 @@ function ReviewSummaryComponent({ jobInstance }: { jobInstance: any }): JSX.Elem function JobItem(props: Props): JSX.Element { const { job, task, onJobUpdate } = props; - const { stage, id } = job; + + const deletes = useSelector((state: CombinedState) => state.jobs.activities.deletes); + const deleted = job.id in deletes ? deletes[job.id] === true : false; + + const { stage } = job; const created = moment(job.createdDate); const updated = moment(job.updatedDate); const now = moment(moment.now()); - const deletes = useSelector((state: CombinedState) => state.jobs.activities.deletes); - const deleted = id in deletes ? deletes[id] === true : false; + const style = {}; if (deleted) { (style as any).pointerEvents = 'none'; @@ -262,7 +265,7 @@ function JobItem(props: Props): JSX.Element { } + overlay={} > diff --git a/cvat-ui/src/components/jobs-page/job-card.tsx b/cvat-ui/src/components/jobs-page/job-card.tsx index 4f4e98e3769e..a76ed0c38146 100644 --- a/cvat-ui/src/components/jobs-page/job-card.tsx +++ b/cvat-ui/src/components/jobs-page/job-card.tsx @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT import React from 'react'; +import { useSelector } from 'react-redux'; import { useHistory } from 'react-router'; import Card from 'antd/lib/card'; import Descriptions from 'antd/lib/descriptions'; @@ -14,6 +15,7 @@ import { Job } from 'cvat-core-wrapper'; import { useCardHeightHOC } from 'utils/hooks'; import Preview from 'components/common/preview'; import JobActionsMenu from 'components/job-item/job-actions-menu'; +import { CombinedState } from 'reducers'; const useCardHeight = useCardHeightHOC({ containerClassName: 'cvat-jobs-page', @@ -25,11 +27,14 @@ const useCardHeight = useCardHeightHOC({ interface Props { job: Job; - onJobUpdate: (job: Job, fields: Parameters[0]) => void; } function JobCardComponent(props: Props): JSX.Element { - const { job, onJobUpdate } = props; + const { job } = props; + + const deletes = useSelector((state: CombinedState) => state.jobs.activities.deletes); + const deleted = job.id in deletes ? deletes[job.id] === true : false; + const history = useHistory(); const height = useCardHeight(); const onClick = (event: React.MouseEvent): void => { @@ -41,9 +46,15 @@ function JobCardComponent(props: Props): JSX.Element { } }; + const style = {}; + if (deleted) { + (style as any).pointerEvents = 'none'; + (style as any).opacity = 0.5; + } + return ( @@ -74,7 +85,7 @@ function JobCardComponent(props: Props): JSX.Element { } + overlay={()} > diff --git a/cvat-ui/src/components/jobs-page/jobs-content.tsx b/cvat-ui/src/components/jobs-page/jobs-content.tsx index 9f48d27dff55..706fab727025 100644 --- a/cvat-ui/src/components/jobs-page/jobs-content.tsx +++ b/cvat-ui/src/components/jobs-page/jobs-content.tsx @@ -7,19 +7,14 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { Col, Row } from 'antd/lib/grid'; import { CombinedState } from 'reducers'; -import { Job, JobType } from 'cvat-core-wrapper'; +import { Job } from 'cvat-core-wrapper'; import dimensions from 'utils/dimensions'; import JobCard from './job-card'; -interface Props { - onJobUpdate(job: Job, data: Parameters[0]): void; -} - -function JobsContentComponent(props: Props): JSX.Element { - const { onJobUpdate } = props; +function JobsContentComponent(): JSX.Element { const jobs = useSelector((state: CombinedState) => state.jobs.current); - const groupedJobs = jobs.filter((job: Job) => job.type === JobType.ANNOTATION).reduce( + const groupedJobs = jobs.reduce( (acc: Job[][], storage: Job, index: number): Job[][] => { if (index && index % 4) { acc[acc.length - 1].push(storage); @@ -39,7 +34,7 @@ function JobsContentComponent(props: Props): JSX.Element { {jobInstances.map((job: Job) => ( - + ))} diff --git a/cvat-ui/src/components/jobs-page/jobs-page.tsx b/cvat-ui/src/components/jobs-page/jobs-page.tsx index 6c9c911e091d..450ade6fc6ca 100644 --- a/cvat-ui/src/components/jobs-page/jobs-page.tsx +++ b/cvat-ui/src/components/jobs-page/jobs-page.tsx @@ -4,17 +4,16 @@ // SPDX-License-Identifier: MIT import './styles.scss'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router'; import { useDispatch, useSelector } from 'react-redux'; import Spin from 'antd/lib/spin'; import { Col, Row } from 'antd/lib/grid'; import Pagination from 'antd/lib/pagination'; -import { Job } from 'cvat-core-wrapper'; import { updateHistoryFromQuery } from 'components/resource-sorting-filtering'; import { CombinedState, Indexable, JobsQuery } from 'reducers'; -import { getJobsAsync, updateJobAsync } from 'actions/jobs-actions'; +import { getJobsAsync } from 'actions/jobs-actions'; import { anySearch } from 'utils/any-search'; import TopBarComponent from './top-bar'; @@ -28,9 +27,6 @@ function JobsPageComponent(): JSX.Element { const query = useSelector((state: CombinedState) => state.jobs.query); const fetching = useSelector((state: CombinedState) => state.jobs.fetching); const count = useSelector((state: CombinedState) => state.jobs.count); - const onJobUpdate = useCallback((job: Job, data: Parameters[0]) => { - dispatch(updateJobAsync(job, data)); - }, []); const queryParams = new URLSearchParams(history.location.search); const updatedQuery = { ...query }; @@ -58,7 +54,7 @@ function JobsPageComponent(): JSX.Element { const content = count ? ( <> - + ; + REGISTER_ACTION: PluginsActionTypes.ADD_UI_COMPONENT; + REMOVE_ACTION: PluginsActionTypes.REMOVE_UI_COMPONENT; + customizableComponents: typeof CustomizableComponents; + actionCreators: PluginActionCreators; + core: CVATCore; + store: ReturnType; +} + export type ComponentBuilder = ({ dispatch, REGISTER_ACTION, REMOVE_ACTION, + customizableComponents, actionCreators, core, store, -}: { - dispatch: Dispatch, - /** - * @deprecated Please, use actionCreators.addUIComponent instead - */ - REGISTER_ACTION: PluginsActionTypes.ADD_UI_COMPONENT, - /** - * @deprecated Please, use actionCreators.removeUIComponent instead - */ - REMOVE_ACTION: PluginsActionTypes.REMOVE_UI_COMPONENT, - actionCreators: PluginActionCreators, - core: CVATCore, - store: ReturnType -}) => { +}: ComponentBuilderArgs) => { name: string; destructor: CallableFunction; globalStateDidUpdate?: CallableFunction; @@ -66,9 +67,11 @@ function PluginEntrypoint(): null { dispatch, REGISTER_ACTION: PluginsActionTypes.ADD_UI_COMPONENT, REMOVE_ACTION: PluginsActionTypes.REMOVE_UI_COMPONENT, + customizableComponents: CustomizableComponents, actionCreators: { changeFrameAsync, updateCurrentJobAsync, + updateJobAsync, getModelsSuccess: modelsActions.getModelsSuccess, addUICallback: pluginActions.addUICallback, removeUICallback: pluginActions.removeUICallback, diff --git a/cvat-ui/src/components/quality-control/quality-control-page.tsx b/cvat-ui/src/components/quality-control/quality-control-page.tsx new file mode 100644 index 000000000000..64e475321cfa --- /dev/null +++ b/cvat-ui/src/components/quality-control/quality-control-page.tsx @@ -0,0 +1,448 @@ +// Copyright (C) 2023-2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; + +import React, { + useCallback, useEffect, useReducer, useState, +} from 'react'; +import { useParams } from 'react-router'; +import { Link } from 'react-router-dom'; +import { Row, Col } from 'antd/lib/grid'; +import Tabs, { TabsProps } from 'antd/lib/tabs'; +import Title from 'antd/lib/typography/Title'; +import notification from 'antd/lib/notification'; +import { useIsMounted } from 'utils/hooks'; +import { + Job, JobType, QualityReport, QualitySettings, Task, getCore, FramesMetaData, + TargetMetric, +} from 'cvat-core-wrapper'; +import CVATLoadingSpinner from 'components/common/loading-spinner'; +import GoBackButton from 'components/common/go-back-button'; +import { ActionUnion, createAction } from 'utils/redux'; +import QualityOverviewTab from './task-quality/quality-overview-tab'; +import QualityManagementTab from './task-quality/quality-magement-tab'; +import QualitySettingsTab from './quality-settings-tab'; + +const core = getCore(); + +function getTabFromHash(supportedTabs: string[]): string { + const tab = window.location.hash.slice(1); + return supportedTabs.includes(tab) ? tab : supportedTabs[0]; +} + +type InstanceType = 'task'; + +interface State { + fetching: boolean; + reportRefreshingStatus: string | null; + gtJob: { + instance: Job | null, + meta: FramesMetaData | null, + }, + qualitySettings: { + settings: QualitySettings | null; + fetching: boolean; + targetMetric: TargetMetric | null; + }, +} + +enum ReducerActionType { + SET_FETCHING = 'SET_FETCHING', + SET_TASK_REPORT = 'SET_TASK_REPORT', + SET_JOBS_REPORTS = 'SET_JOBS_REPORTS', + SET_QUALITY_SETTINGS = 'SET_QUALITY_SETTINGS', + SET_QUALITY_SETTINGS_FETCHING = 'SET_QUALITY_SETTINGS_FETCHING', + SET_REPORT_REFRESHING_STATUS = 'SET_REPORT_REFRESHING_STATUS', + SET_GT_JOB = 'SET_GT_JOB', + SET_GT_JOB_META = 'SET_GT_JOB_META', +} + +export const reducerActions = { + setFetching: (fetching: boolean) => ( + createAction(ReducerActionType.SET_FETCHING, { fetching }) + ), + setTaskReport: (qualityReport: QualityReport) => ( + createAction(ReducerActionType.SET_TASK_REPORT, { qualityReport }) + ), + setJobsReports: (qualityReports: QualityReport[]) => ( + createAction(ReducerActionType.SET_JOBS_REPORTS, { qualityReports }) + ), + setQualitySettings: (qualitySettings: QualitySettings) => ( + createAction(ReducerActionType.SET_QUALITY_SETTINGS, { qualitySettings }) + ), + setQualitySettingsFetching: (fetching: boolean) => ( + createAction(ReducerActionType.SET_QUALITY_SETTINGS_FETCHING, { fetching }) + ), + setReportRefreshingStatus: (status: string | null) => ( + createAction(ReducerActionType.SET_REPORT_REFRESHING_STATUS, { status }) + ), + setGtJob: (job: Job | null) => ( + createAction(ReducerActionType.SET_GT_JOB, { job }) + ), + setGtJobMeta: (meta: FramesMetaData | null) => ( + createAction(ReducerActionType.SET_GT_JOB_META, { meta }) + ), +}; + +const reducer = (state: State, action: ActionUnion): State => { + if (action.type === ReducerActionType.SET_FETCHING) { + return { + ...state, + fetching: action.payload.fetching, + }; + } + + if (action.type === ReducerActionType.SET_QUALITY_SETTINGS) { + return { + ...state, + qualitySettings: { + ...state.qualitySettings, + settings: action.payload.qualitySettings, + targetMetric: action.payload.qualitySettings.targetMetric, + }, + }; + } + + if (action.type === ReducerActionType.SET_QUALITY_SETTINGS_FETCHING) { + return { + ...state, + qualitySettings: { + ...state.qualitySettings, + fetching: action.payload.fetching, + }, + }; + } + + if (action.type === ReducerActionType.SET_REPORT_REFRESHING_STATUS) { + return { + ...state, + reportRefreshingStatus: action.payload.status, + }; + } + + if (action.type === ReducerActionType.SET_GT_JOB) { + return { + ...state, + gtJob: { + ...state.gtJob, + instance: action.payload.job, + }, + }; + } + + if (action.type === ReducerActionType.SET_GT_JOB_META) { + return { + ...state, + gtJob: { + ...state.gtJob, + meta: action.payload.meta, + }, + }; + } + + return state; +}; + +function QualityControlPage(): JSX.Element { + const [state, dispatch] = useReducer(reducer, { + fetching: true, + reportRefreshingStatus: null, + gtJob: { + instance: null, + meta: null, + }, + qualitySettings: { + settings: null, + fetching: true, + targetMetric: null, + }, + }); + + const requestedInstanceType: InstanceType = 'task'; + const requestedInstanceID = +useParams<{ tid: string }>().tid; + + const [instanceType, setInstanceType] = useState(null); + const [instance, setInstance] = useState(null); + const isMounted = useIsMounted(); + + const supportedTabs = ['overview', 'settings', 'management']; + const [activeTab, setActiveTab] = useState(getTabFromHash(supportedTabs)); + const receiveInstance = async (type: InstanceType, id: number): Promise => { + let receivedInstance: Task | null = null; + let gtJob: Job | null = null; + let gtJobMeta: FramesMetaData | null = null; + + try { + if (type === 'task') { + [receivedInstance] = await core.tasks.get({ id }); + gtJob = receivedInstance.jobs.find((job: Job) => job.type === JobType.GROUND_TRUTH) ?? null; + if (gtJob) { + gtJobMeta = await core.frames.getMeta('job', gtJob.id) as FramesMetaData; + } + } else { + return null; + } + + if (isMounted()) { + dispatch(reducerActions.setGtJob(gtJob)); + dispatch(reducerActions.setGtJobMeta(gtJobMeta)); + setInstance(receivedInstance); + setInstanceType(type); + } + return receivedInstance; + } catch (error: unknown) { + notification.error({ + message: `Could not receive requested ${type}`, + description: `${error instanceof Error ? error.message : ''}`, + }); + return null; + } + }; + + const receiveSettings = useCallback(async (taskInstance: Task) => { + dispatch(reducerActions.setQualitySettingsFetching(true)); + + function handleError(error: Error): void { + if (isMounted()) { + notification.error({ + description: error.toString(), + message: 'Could not initialize quality control page', + }); + } + } + + try { + const settingsRequest = core.analytics.quality.settings.get({ taskID: taskInstance.id }); + + await Promise.all([settingsRequest]).then(([settings]) => { + dispatch(reducerActions.setQualitySettings(settings)); + }).catch(handleError).finally(() => { + dispatch(reducerActions.setQualitySettingsFetching(false)); + dispatch(reducerActions.setFetching(false)); + }); + } catch (error: unknown) { + handleError(error as Error); + } + }, [instance]); + + const onSaveQualitySettings = useCallback(async (values) => { + try { + const { settings } = state.qualitySettings; + if (settings) { + settings.targetMetric = values.targetMetric; + settings.targetMetricThreshold = values.targetMetricThreshold / 100; + + settings.maxValidationsPerJob = values.maxValidationsPerJob; + + settings.lowOverlapThreshold = values.lowOverlapThreshold / 100; + settings.iouThreshold = values.iouThreshold / 100; + settings.compareAttributes = values.compareAttributes; + + settings.oksSigma = values.oksSigma / 100; + + settings.lineThickness = values.lineThickness / 100; + settings.lineOrientationThreshold = values.lineOrientationThreshold / 100; + settings.orientedLines = values.orientedLines; + + settings.compareGroups = values.compareGroups; + settings.groupMatchThreshold = values.groupMatchThreshold / 100; + + settings.checkCoveredAnnotations = values.checkCoveredAnnotations; + settings.objectVisibilityThreshold = values.objectVisibilityThreshold / 100; + + settings.panopticComparison = values.panopticComparison; + try { + dispatch(reducerActions.setQualitySettingsFetching(true)); + const responseSettings = await settings.save(); + dispatch(reducerActions.setQualitySettings(responseSettings)); + notification.info({ message: 'Settings have been updated' }); + } catch (error: unknown) { + notification.error({ + message: 'Could not save quality settings', + description: typeof Error === 'object' ? (error as object).toString() : '', + }); + throw error; + } finally { + dispatch(reducerActions.setQualitySettingsFetching(false)); + } + } + return settings; + } catch (e) { + return false; + } + }, [state.qualitySettings.settings]); + + const updateMeta = (action: (frameID: number) => void) => async (frameIDs: number[]): Promise => { + const { instance: gtJob } = state.gtJob; + if (gtJob) { + dispatch(reducerActions.setFetching(true)); + await Promise.all(frameIDs.map((frameID: number): void => action(frameID))); + const [newMeta] = await gtJob.frames.save(); + dispatch(reducerActions.setGtJobMeta(newMeta)); + dispatch(reducerActions.setFetching(false)); + } + }; + + const onDeleteFrames = useCallback( + updateMeta((frameID: number) => (state.gtJob.instance?.frames.delete(frameID))), + [state.gtJob.instance], + ); + + const onRestoreFrames = useCallback( + updateMeta((frameID: number) => (state.gtJob.instance?.frames.restore(frameID))), + [state.gtJob.instance], + ); + + useEffect(() => { + if (Number.isInteger(requestedInstanceID) && ['task'].includes(requestedInstanceType)) { + dispatch(reducerActions.setFetching(true)); + receiveInstance(requestedInstanceType, requestedInstanceID).then((task) => { + if (task) { + receiveSettings(task); + } + }); + } else { + notification.error({ + message: 'Could not load this page', + description: `Not valid resource ${requestedInstanceType} #${requestedInstanceID}`, + }); + } + + return () => { + if (isMounted()) { + setInstance(null); + } + }; + }, [requestedInstanceType, requestedInstanceID]); + + useEffect(() => { + window.addEventListener('hashchange', () => { + const hash = getTabFromHash(supportedTabs); + setActiveTab(hash); + }); + }, []); + + useEffect(() => { + window.location.hash = activeTab; + }, [activeTab]); + + const onTabKeyChange = useCallback((key: string): void => { + setActiveTab(key); + }, []); + + let backNavigation: JSX.Element | null = null; + let title: JSX.Element | null = null; + let tabs: JSX.Element | null = null; + + const { + fetching, + gtJob: { + instance: gtJobInstance, + meta: gtJobMeta, + }, + qualitySettings: { + settings: qualitySettings, + fetching: qualitySettingsFetching, + targetMetric, + }, + } = state; + + const settingsInitialized = qualitySettings && targetMetric; + if (instanceType && instance && settingsInitialized) { + backNavigation = ( + + + + + + ); + + const qualityControlFor = {`Task #${instance.id}`}; + title = ( + + + Quality control for + {' '} + {qualityControlFor} + + + ); + + const tabsItems: [NonNullable[0], number][] = []; + tabsItems.push([{ + key: 'overview', + label: 'Overview', + children: ( + + ), + }, 10]); + + if (gtJobInstance && gtJobMeta) { + tabsItems.push([{ + key: 'management', + label: 'Management', + children: ( + + ), + }, 20]); + + tabsItems.push([{ + key: 'settings', + label: 'Settings', + children: ( + + ), + }, 30]); + } + + tabsItems.sort((item1, item2) => item1[1] - item2[1]); + + tabs = ( + item[0])} + /> + ); + } + + return ( +
+ {fetching && qualitySettingsFetching ? ( +
+ +
+ ) : ( + +
+ {backNavigation} + + + {title} + {tabs} + + + + + )} + + ); +} + +export default React.memo(QualityControlPage); diff --git a/cvat-ui/src/components/quality-control/quality-settings-tab.tsx b/cvat-ui/src/components/quality-control/quality-settings-tab.tsx new file mode 100644 index 000000000000..eceeab8c9672 --- /dev/null +++ b/cvat-ui/src/components/quality-control/quality-settings-tab.tsx @@ -0,0 +1,54 @@ +// Copyright (C) 2023-2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useCallback } from 'react'; +import Text from 'antd/lib/typography/Text'; +import Form from 'antd/lib/form'; +import { QualitySettings } from 'cvat-core-wrapper'; +import CVATLoadingSpinner from 'components/common/loading-spinner'; +import QualitySettingsForm from './task-quality/quality-settings-form'; + +interface Props { + fetching: boolean; + qualitySettings: QualitySettings | null; + setQualitySettings: (settings: QualitySettings) => void; +} + +function QualitySettingsTab(props: Readonly): JSX.Element | null { + const { + fetching, + qualitySettings: settings, + setQualitySettings, + } = props; + + const [form] = Form.useForm(); + const onSave = useCallback(async () => { + const values = await form.validateFields(); + setQualitySettings(values); + }, [form, setQualitySettings]); + + if (fetching) { + return ( +
+
+ +
+
+ ); + } + + return ( +
+ { settings ? ( + + ) : No quality settings found } +
+ ); +} + +export default React.memo(QualitySettingsTab); diff --git a/cvat-ui/src/components/quality-control/styles.scss b/cvat-ui/src/components/quality-control/styles.scss new file mode 100644 index 000000000000..6149d317d105 --- /dev/null +++ b/cvat-ui/src/components/quality-control/styles.scss @@ -0,0 +1,146 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base'; + +.cvat-quality-control-inner { + background: $background-color-1; + padding: $grid-unit-size * 4; + padding-bottom: $grid-unit-size; + padding-top: 0; + border-radius: $border-radius-base; +} + +.cvat-quality-settings-title { + margin-bottom: $grid-unit-size * 2; + align-items: center; +} + +.cvat-quality-settings-form { + display: block; + position: relative; + height: calc(100vh - $grid-unit-size * 32); + overflow-y: auto; + + &::-webkit-scrollbar { + background-color: #fff; + width: $grid-unit-size * 2; + } + + &::-webkit-scrollbar-track { + background-color: #fff; + } + + &::-webkit-scrollbar-thumb { + background-color: #babac0; + border-radius: $border-radius-base * 2; + border: 6px solid #fff; + } + + .cvat-quality-settings-save-btn { + position: sticky; + z-index: 1; + top: 0; + height: 0; + } + + .ant-divider-horizontal { + margin: $grid-unit-size 0; + } +} + + +$excluded-background: #d9d9d973; + +.cvat-allocation-frame-row-excluded:not(.ant-table-row-selected) { + background-color: $excluded-background; + + + .ant-table-cell-row-hover { + background-color: $excluded-background !important; + } +} + +.cvat-frame-allocation-list { + width: 100%; + height: auto; + margin-top: $grid-unit-size * 2; + overflow: hidden; + + td.ant-table-column-sort { + background: none; + } + + .react-resizable-handle { + position: absolute; + right: -28px; + bottom: 0; + z-index: 1; + width: 10px; + height: 100%; + cursor: ew-resize; + display: grid; + place-content: center; + } +} + +.cvat-frame-allocation-actions { + span[role='img'] { + padding: 0 $grid-unit-size; + } + + span[role='img']:hover { + cursor: pointer; + } +} + + +.cvat-frame-allocation-header { + margin-bottom: 0; + font-size: 20px; + font-weight: bold; +} + +.cvat-allocation-summary { + span { + font-size: 13px !important; + } +} + +.cvat-open-frame-button { + span { + text-overflow: ellipsis; + overflow: hidden; + } +} + +.cvat-quality-control-loading { + position: absolute; + right: 50%; + margin-top: 20%; +} + +.cvat-quality-control-overview-tab { + min-height: 50vh; +} + +.cvat-annotations-quality-allocation-table-summary { + margin-bottom: $grid-unit-size * 2; + + .ant-statistic { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .ant-card-body { + padding: $grid-unit-size * 2 $grid-unit-size * 3; + } +} + +.cvat-frame-allocation-table .ant-table-container { + max-height: calc(100vh - $grid-unit-size * 60); + overflow-y: auto; +} diff --git a/cvat-ui/src/components/quality-control/task-quality/allocation-table.tsx b/cvat-ui/src/components/quality-control/task-quality/allocation-table.tsx new file mode 100644 index 000000000000..d09333c7797b --- /dev/null +++ b/cvat-ui/src/components/quality-control/task-quality/allocation-table.tsx @@ -0,0 +1,185 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useState } from 'react'; +import { useHistory } from 'react-router'; +import { Row, Col } from 'antd/lib/grid'; +import Table from 'antd/lib/table'; +import Button from 'antd/lib/button'; +import Text from 'antd/lib/typography/Text'; +import { Key } from 'antd/lib/table/interface'; +import Icon, { DeleteOutlined } from '@ant-design/icons'; + +import { RestoreIcon } from 'icons'; +import { Task, Job, FramesMetaData } from 'cvat-core-wrapper'; +import CVATTooltip from 'components/common/cvat-tooltip'; +import { sorter } from 'utils/quality'; + +interface Props { + task: Task; + gtJob: Job; + gtJobMeta: FramesMetaData; + onDeleteFrames: (frames: number[]) => void; + onRestoreFrames: (frames: number[]) => void; +} + +interface RowData { + frame: number; + name: string; + active: boolean; +} + +function AllocationTableComponent(props: Readonly): JSX.Element { + const { + task, gtJob, gtJobMeta, + onDeleteFrames, onRestoreFrames, + } = props; + + const history = useHistory(); + const [selection, setSelection] = useState<{ selectedRowKeys: Key[], selectedRows: RowData[] }>({ + selectedRowKeys: [], + selectedRows: [], + }); + + const data = gtJobMeta.includedFrames.map((frameID: number) => ({ + key: frameID, + frame: frameID, + name: gtJobMeta.frames[frameID]?.name ?? gtJobMeta.frames[0].name, + active: !(frameID in gtJobMeta.deletedFrames), + })); + + const columns = [ + { + title: 'Frame', + dataIndex: 'frame', + key: 'frame', + width: 50, + sorter: sorter('frame'), + render: (frame: number): JSX.Element => ( +
+ +
+ ), + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 300, + sorter: sorter('name'), + render: (name: string, record: RowData) => ( + + + + ), + }, + { + title: 'Actions', + dataIndex: 'active', + key: 'actions', + align: 'center' as const, + width: 20, + filters: [ + { text: 'Active', value: true }, + { text: 'Excluded', value: false }, + ], + sorter: sorter('active'), + onFilter: (value: boolean | Key, record: RowData) => record.active === value, + render: (active: boolean, record: RowData): JSX.Element => ( + active ? ( + { onDeleteFrames([record.frame]); }} + /> + ) : ( + { onRestoreFrames([record.frame]); }} + component={RestoreIcon} + /> + ) + ), + }, + ]; + + return ( +
+ +
+ Frames + + { + selection.selectedRowKeys.length !== 0 ? ( + <> + + { + const framesToUpdate = selection.selectedRows + .filter((frameData) => frameData.active) + .map((frameData) => frameData.frame); + onDeleteFrames(framesToUpdate); + setSelection({ selectedRowKeys: [], selectedRows: [] }); + }} + /> + + + { + const framesToUpdate = selection.selectedRows + .filter((frameData) => !frameData.active) + .map((frameData) => frameData.frame); + onRestoreFrames(framesToUpdate); + setSelection({ selectedRowKeys: [], selectedRows: [] }); + }} + component={RestoreIcon} + /> + + + ) : null + } + +
{ + if (!rowData.active) { + return 'cvat-allocation-frame-row cvat-allocation-frame-row-excluded'; + } + return 'cvat-allocation-frame'; + }} + columns={columns} + dataSource={data} + rowSelection={{ + selectedRowKeys: selection.selectedRowKeys, + onChange: (selectedRowKeys: Key[], selectedRows: RowData[]) => { + setSelection({ + ...selection, + selectedRowKeys, + selectedRows, + }); + }, + }} + size='small' + pagination={{ showSizeChanger: true }} + /> + + ); +} + +export default React.memo(AllocationTableComponent); diff --git a/cvat-ui/src/components/quality-control/task-quality/quality-magement-tab.tsx b/cvat-ui/src/components/quality-control/task-quality/quality-magement-tab.tsx new file mode 100644 index 000000000000..e7bcddc2fbd0 --- /dev/null +++ b/cvat-ui/src/components/quality-control/task-quality/quality-magement-tab.tsx @@ -0,0 +1,67 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row, Col } from 'antd/es/grid'; +import Spin from 'antd/lib/spin'; + +import CustomizableComponents from 'components/customizable-components'; +import { FramesMetaData, Job, Task } from 'cvat-core-wrapper'; +import { SummaryComponent } from './summary'; + +interface Props { + task: Task; + gtJob: Job; + gtJobMeta: FramesMetaData; + fetching: boolean; + onDeleteFrames: (frames: number[]) => void; + onRestoreFrames: (frames: number[]) => void; +} + +function QualityManagementTab(props: Readonly): JSX.Element { + const { + task, gtJob, gtJobMeta, + onDeleteFrames, onRestoreFrames, fetching, + } = props; + + const activeCount = gtJobMeta.includedFrames + .filter((frameID: number) => !(frameID in gtJobMeta.deletedFrames)).length; + const excludedCount = Object.keys(gtJobMeta.deletedFrames) + .filter((frameID: string) => gtJobMeta.includedFrames.includes(+frameID)).length; + const [AllocationTableComponent] = CustomizableComponents.QUALITY_CONTROL_ALLOCATION_TABLE.slice(-1); + + return ( +
+ { + fetching && ( +
+ +
+ ) + } + +
+ + + + + + + + + + ); +} + +export default React.memo(QualityManagementTab); diff --git a/cvat-ui/src/components/quality-control/task-quality/quality-overview-tab.tsx b/cvat-ui/src/components/quality-control/task-quality/quality-overview-tab.tsx new file mode 100644 index 000000000000..032753f51252 --- /dev/null +++ b/cvat-ui/src/components/quality-control/task-quality/quality-overview-tab.tsx @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { TargetMetric, Task } from 'cvat-core-wrapper'; +import CustomizableComponents from 'components/customizable-components'; + +interface Props { + task: Task; + targetMetric: TargetMetric; +} + +function QualityOverviewTab(props: Readonly): JSX.Element { + const { task, targetMetric } = props; + const [Component] = CustomizableComponents.QUALITY_CONTROL_OVERVIEW.slice(-1); + + return ( +
+ +
+ ); +} + +export default React.memo(QualityOverviewTab); diff --git a/cvat-ui/src/components/analytics-page/task-quality/quality-settings-form.tsx b/cvat-ui/src/components/quality-control/task-quality/quality-settings-form.tsx similarity index 79% rename from cvat-ui/src/components/analytics-page/task-quality/quality-settings-form.tsx rename to cvat-ui/src/components/quality-control/task-quality/quality-settings-form.tsx index 965f30ad7513..f1746fe675ac 100644 --- a/cvat-ui/src/components/analytics-page/task-quality/quality-settings-form.tsx +++ b/cvat-ui/src/components/quality-control/task-quality/quality-settings-form.tsx @@ -10,17 +10,19 @@ import { Col, Row } from 'antd/lib/grid'; import Divider from 'antd/lib/divider'; import Form, { FormInstance } from 'antd/lib/form'; import Checkbox from 'antd/lib/checkbox/Checkbox'; +import Button from 'antd/lib/button'; import Select from 'antd/lib/select'; import CVATTooltip from 'components/common/cvat-tooltip'; import { QualitySettings, TargetMetric } from 'cvat-core-wrapper'; -interface FormProps { +interface Props { form: FormInstance; settings: QualitySettings; + onSave: () => void; } -export default function QualitySettingsForm(props: FormProps): JSX.Element | null { - const { form, settings } = props; +export default function QualitySettingsForm(props: Readonly): JSX.Element | null { + const { form, settings, onSave } = props; const initialValues = { targetMetric: settings.targetMetric, @@ -45,123 +47,85 @@ export default function QualitySettingsForm(props: FormProps): JSX.Element | nul objectVisibilityThreshold: settings.objectVisibilityThreshold * 100, panopticComparison: settings.panopticComparison, }; + const targetMetricDescription = `${settings.descriptions.targetMetric .replaceAll(/\* [a-z` -]+[A-Z]+/g, '') .replaceAll(/\n/g, '')}.`; - const generalTooltip = ( -
- - Target metric - - {' '} - {targetMetricDescription} - This parameter affects display of the quality computed. - + const makeTooltipFragment = (metric: string, description: string): JSX.Element => ( +
+ {`${metric}:`} - Target metric threshold - - {' '} - {settings.descriptions.targetMetricThreshold} - This parameter affects display of the quality numbers. + {description}
); - const jobValidationTooltip = ( + const makeTooltip = (jsx: JSX.Element): JSX.Element => (
- - Max validations per job - - {' '} - {settings.descriptions.maxValidationsPerJob} - + {jsx}
); - const shapeComparisonTooltip = ( -
- - Min overlap threshold(IoU) - - {' '} - {settings.descriptions.iouThreshold} - - - Low overlap threshold - - {' '} - {settings.descriptions.lowOverlapThreshold} - -
+ const generalTooltip = makeTooltip( + <> + {makeTooltipFragment('Target metric', targetMetricDescription)} + {makeTooltipFragment('Target metric threshold', settings.descriptions.targetMetricThreshold)} + , ); - const keypointTooltip = ( -
- - Object Keypoint Similarity (OKS) - - {' '} - {settings.descriptions.oksSigma} - -
+ const jobValidationTooltip = makeTooltip( + makeTooltipFragment('Max validations per job', settings.descriptions.maxValidationsPerJob), ); - const linesTooltip = ( -
- - Line thickness - - {' '} - {settings.descriptions.lineThickness} - - - Check orientation - - {' '} - {settings.descriptions.compareLineOrientation} - - - Min similarity gain - - {' '} - {settings.descriptions.lineOrientationThreshold} - -
+ const shapeComparisonTooltip = makeTooltip( + <> + {makeTooltipFragment('Min overlap threshold (IoU)', settings.descriptions.iouThreshold)} + {makeTooltipFragment('Low overlap threshold', settings.descriptions.lowOverlapThreshold)} + , ); - const groupTooltip = ( -
- - Compare groups - - {' '} - {settings.descriptions.compareGroups} - - - Min group match threshold - - {' '} - {settings.descriptions.groupMatchThreshold} - -
+ const keypointTooltip = makeTooltip( + makeTooltipFragment('Object Keypoint Similarity (OKS)', settings.descriptions.oksSigma), ); - const segmentationTooltip = ( -
- - Check object visibility - - {' '} - {settings.descriptions.checkCoveredAnnotations} - - - Min visibility threshold - - {' '} - {settings.descriptions.objectVisibilityThreshold} - - - Match only visible parts - - {' '} - {settings.descriptions.panopticComparison} - -
+ const linesTooltip = makeTooltip( + <> + {makeTooltipFragment('Line thickness', settings.descriptions.lineThickness)} + {makeTooltipFragment('Check orientation', settings.descriptions.compareLineOrientation)} + {makeTooltipFragment('Min similarity gain', settings.descriptions.lineOrientationThreshold)} + , + ); + + const groupTooltip = makeTooltip( + <> + {makeTooltipFragment('Compare groups', settings.descriptions.compareGroups)} + {makeTooltipFragment('Min group match threshold', settings.descriptions.groupMatchThreshold)} + , + ); + + const segmentationTooltip = makeTooltip( + <> + {makeTooltipFragment('Check object visibility', settings.descriptions.checkCoveredAnnotations)} + {makeTooltipFragment('Min visibility threshold', settings.descriptions.objectVisibilityThreshold)} + {makeTooltipFragment('Match only visible parts', settings.descriptions.panopticComparison)} + , ); return (
+ +
+ + + General diff --git a/cvat-ui/src/components/quality-control/task-quality/summary.tsx b/cvat-ui/src/components/quality-control/task-quality/summary.tsx new file mode 100644 index 000000000000..e91828909a1d --- /dev/null +++ b/cvat-ui/src/components/quality-control/task-quality/summary.tsx @@ -0,0 +1,59 @@ +// Copyright (C) 2024 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { Row, Col } from 'antd/es/grid'; +import Text from 'antd/lib/typography/Text'; +import AnalyticsCard from 'components/analytics-page/views/analytics-card'; + +export interface Props { + excludedCount: number; + totalCount: number; + activeCount: number; +} + +export function SummaryComponent(props: Readonly): JSX.Element { + const { excludedCount, totalCount, activeCount } = props; + + const reportInfo = ( + + + + + + Excluded count: + {' '} + {excludedCount} + + + + + Total count: + {' '} + {totalCount} + + + + + + + Active count: + {' '} + {activeCount} + + + + + + ); + + return ( + + ); +} diff --git a/cvat-ui/src/components/signing-common/cvat-signing-input.tsx b/cvat-ui/src/components/signing-common/cvat-signing-input.tsx index 2187d083dd2b..70a46e716cda 100644 --- a/cvat-ui/src/components/signing-common/cvat-signing-input.tsx +++ b/cvat-ui/src/components/signing-common/cvat-signing-input.tsx @@ -1,10 +1,10 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React, { useEffect, useState } from 'react'; import Icon from '@ant-design/icons'; import { ClearIcon } from 'icons'; -import { Input } from 'antd'; +import Input from 'antd/lib/input'; import Text from 'antd/lib/typography/Text'; interface SocialAccountLinkProps { diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 3fb42523df08..bbd59652da2f 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -29,7 +29,7 @@ const FilteringComponent = ResourceFilterHOC( interface Props { task: Task; - onUpdateJob(job: Job, data: Parameters[0]): void; + onJobUpdate(job: Job, data: Parameters[0]): void; } const PAGE_SIZE = 10; @@ -63,7 +63,7 @@ function setUpJobsList(jobs: Job[], query: JobsQuery): Job[] { } function JobListComponent(props: Props): JSX.Element { - const { task: taskInstance, onUpdateJob } = props; + const { task: taskInstance, onJobUpdate } = props; const [visibility, setVisibility] = useState(defaultVisibility); const history = useHistory(); @@ -87,7 +87,14 @@ function JobListComponent(props: Props): JSX.Element { const filteredJobs = setUpJobsList(jobs, query); const jobViews = filteredJobs .slice((query.page - 1) * PAGE_SIZE, query.page * PAGE_SIZE) - .map((job: Job) => ); + .map((job: Job) => ( + + )); useEffect(() => { history.replace({ search: updateHistoryFromQuery(query), diff --git a/cvat-ui/src/components/task-page/styles.scss b/cvat-ui/src/components/task-page/styles.scss index 02e7bc116807..ba2a9e00441b 100644 --- a/cvat-ui/src/components/task-page/styles.scss +++ b/cvat-ui/src/components/task-page/styles.scss @@ -120,15 +120,6 @@ font-weight: bold; } -/* Pagination in center */ -.cvat-task-jobs-table > div > div { - text-align: center; - - > .ant-table-pagination.ant-pagination { - float: none; - } -} - .cvat-job-item-stage { .ant-select { margin-right: $grid-unit-size; diff --git a/cvat-ui/src/components/task-page/task-page.tsx b/cvat-ui/src/components/task-page/task-page.tsx index 4f4f4f5254b8..1d92a5e4eb59 100644 --- a/cvat-ui/src/components/task-page/task-page.tsx +++ b/cvat-ui/src/components/task-page/task-page.tsx @@ -134,7 +134,7 @@ function TaskPageComponent(): JSX.Element { - + diff --git a/cvat-ui/src/components/task-page/top-bar.tsx b/cvat-ui/src/components/task-page/top-bar.tsx index cab6e21bac0d..a242aef44410 100644 --- a/cvat-ui/src/components/task-page/top-bar.tsx +++ b/cvat-ui/src/components/task-page/top-bar.tsx @@ -25,6 +25,9 @@ export default function DetailsComponent(props: DetailsComponentProps): JSX.Elem const onViewAnalytics = useCallback(() => { history.push(`/tasks/${taskInstance.id}/analytics`); }, [history]); + const onViewQualityControl = (): void => { + history.push(`/tasks/${taskInstance.id}/quality-control`); + }; return ( @@ -59,6 +62,7 @@ export default function DetailsComponent(props: DetailsComponentProps): JSX.Elem )} > diff --git a/cvat-ui/src/components/tasks-page/task-item.tsx b/cvat-ui/src/components/tasks-page/task-item.tsx index 4719c0a0d6fe..9bc5fdec3136 100644 --- a/cvat-ui/src/components/tasks-page/task-item.tsx +++ b/cvat-ui/src/components/tasks-page/task-item.tsx @@ -238,6 +238,9 @@ class TaskItemComponent extends React.PureComponent { history.push(`/tasks/${taskInstance.id}/analytics`); }; + const onViewQualityControl = (): void => { + history.push(`/tasks/${taskInstance.id}/quality-control`); + }; return ( @@ -267,6 +270,7 @@ class TaskItemComponent extends React.PureComponent )} > diff --git a/cvat-ui/src/config.tsx b/cvat-ui/src/config.tsx index 9719a0bc2920..7e0b404b093a 100644 --- a/cvat-ui/src/config.tsx +++ b/cvat-ui/src/config.tsx @@ -138,6 +138,16 @@ const BLACKLISTED_GO_BACK_PATHS = [ /\/auth.+/, ]; +const PAID_PLACEHOLDER_CONFIG = { + url: 'https://www.cvat.ai/pricing/cloud', + features: { + qualityControl: + 'The Quality Control feature enables effortless evaluation of annotation quality by creating' + + ' a Ground Truth job that works as benchmark. CVAT automatically compares all task-related jobs' + + ' to this benchmark, calculating annotation precision to ensure high-quality results.', + }, +}; + export default { UNDEFINED_ATTRIBUTE_VALUE, NO_BREAK_SPACE, @@ -179,4 +189,5 @@ export default { LOCAL_STORAGE_LAST_FRAME_MEMORY_LIMIT, REQUEST_SUCCESS_NOTIFICATION_DURATION, BLACKLISTED_GO_BACK_PATHS, + PAID_PLACEHOLDER_CONFIG, }; diff --git a/cvat-ui/src/containers/actions-menu/actions-menu.tsx b/cvat-ui/src/containers/actions-menu/actions-menu.tsx index 0883e8067c64..e9773c2b9051 100644 --- a/cvat-ui/src/containers/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/containers/actions-menu/actions-menu.tsx @@ -21,6 +21,7 @@ import { importActions } from 'actions/import-actions'; interface OwnProps { taskInstance: any; onViewAnalytics: () => void; + onViewQualityControl: () => void; } interface StateToProps { @@ -86,6 +87,7 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): openRunModelWindow, openMoveTaskToProjectWindow, onViewAnalytics, + onViewQualityControl, } = props; const onClickMenu = (params: MenuInfo): void | JSX.Element => { const [action] = params.keyPath; @@ -105,6 +107,8 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps): showImportModal(taskInstance); } else if (action === Actions.VIEW_ANALYTICS) { onViewAnalytics(); + } else if (action === Actions.QUALITY_CONTROL) { + onViewQualityControl(); } }; diff --git a/cvat-ui/src/utils/quality.ts b/cvat-ui/src/utils/quality.ts index 60885c40bf4d..a7042cddfe99 100644 --- a/cvat-ui/src/utils/quality.ts +++ b/cvat-ui/src/utils/quality.ts @@ -1,50 +1,7 @@ -// Copyright (C) 2024 CVAT.ai Corporation +// Copyright (C) 2023-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -import { ColumnFilterItem } from 'antd/lib/table/interface'; -import { QualityReport } from 'cvat-core-wrapper'; -import config from 'config'; - -export enum QualityColors { - GREEN = '#237804', - YELLOW = '#ed9c00', - RED = '#ff4d4f', - GRAY = '#8c8c8c', -} - -const ratios = { - low: 0.82, - middle: 0.9, - high: 1, -}; - -export const qualityColorGenerator = (targetMetric: number) => (value?: number) => { - const baseValue = targetMetric * 100; - - const thresholds = { - low: baseValue * ratios.low, - middle: baseValue * ratios.middle, - high: baseValue * ratios.high, - }; - - if (!value) { - return QualityColors.GRAY; - } - - if (value >= thresholds.high) { - return QualityColors.GREEN; - } - if (value >= thresholds.middle) { - return QualityColors.YELLOW; - } - if (value >= thresholds.low) { - return QualityColors.RED; - } - - return QualityColors.GRAY; -}; - export function sorter(path: string) { return (obj1: any, obj2: any): number => { let currentObj1 = obj1; @@ -79,45 +36,3 @@ export function sorter(path: string) { return 1; }; } - -export function collectAssignees(reports: QualityReport[]): ColumnFilterItem[] { - return Array.from( - new Set( - reports.map((report: QualityReport) => report.assignee?.username ?? null), - ), - ).map((value: string | null) => ({ text: value ?? 'Is Empty', value: value ?? false })); -} - -export function toRepresentation(val?: number, isPercent = true, decimals = 1): string { - if (!Number.isFinite(val)) { - return 'N/A'; - } - - let repr = ''; - if (!val || (isPercent && (val === 100))) { - repr = `${val}`; // remove noise in the fractional part - } else { - repr = `${val?.toFixed(decimals)}`; - } - - if (isPercent) { - repr += `${isPercent ? '%' : ''}`; - } - - return repr; -} - -export function percent(a?: number, b?: number, decimals = 1): string | number { - if (typeof a !== 'undefined' && Number.isFinite(a) && b) { - return toRepresentation(Number(a / b) * 100, true, decimals); - } - return 'N/A'; -} - -export function clampValue(a?: number): string | number { - if (typeof a !== 'undefined' && Number.isFinite(a)) { - if (a <= config.NUMERIC_VALUE_CLAMP_THRESHOLD) return a; - return `> ${config.NUMERIC_VALUE_CLAMP_THRESHOLD}`; - } - return 'N/A'; -} diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index c5850cea0df8..db82ec975faf 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -189,6 +189,10 @@ module.exports = (env) => { from: 'src/assets/opencv_4.8.0.js', to : 'assets/opencv_4.8.0.js', }, + { + from: 'src/assets/*.png', + to : 'assets/[name][ext]', + }, { from: 'plugins/**/assets/*.(onnx|js)', to : 'assets/[name][ext]', diff --git a/tests/cypress/e2e/features/ground_truth_jobs.js b/tests/cypress/e2e/features/ground_truth_jobs.js index 098f41282394..9eba445b76a2 100644 --- a/tests/cypress/e2e/features/ground_truth_jobs.js +++ b/tests/cypress/e2e/features/ground_truth_jobs.js @@ -60,70 +60,13 @@ context('Ground truth jobs', () => { }, ]; - const rectangles = [ - { - points: 'By 2 Points', - type: 'Shape', - labelName, - firstX: 270, - firstY: 350, - secondX: 370, - secondY: 450, - }, - { - points: 'By 2 Points', - type: 'Shape', - labelName, - firstX: 270, - firstY: 350, - secondX: 370, - secondY: 450, - }, - { - id: 3, - points: 'By 2 Points', - type: 'Shape', - labelName, - firstX: 350, - firstY: 450, - secondX: 450, - secondY: 550, - }, - { - points: 'By 2 Points', - type: 'Shape', - labelName, - firstX: 130, - firstY: 200, - secondX: 150, - secondY: 250, - }, - ]; - let groundTruthJobID = null; let jobID = null; let taskID = null; - let qualityReportID = null; // With seed = 1, frameCount = 4, totalFrames = 10 - predifined ground truth frames are: const groundTruthFrames = [0, 1, 5, 6]; - function checkCardValue(className, value) { - cy.get(className) - .should('be.visible') - .within(() => { - cy.get('.cvat-analytics-card-value').should('have.text', value); - }); - } - - function openQualityTab() { - cy.clickInTaskMenu('View analytics', true); - cy.get('.cvat-task-analytics-tabs') - .within(() => { - cy.contains('Quality').click(); - }); - } - function checkRectangleAndObjectMenu(rectangle, isGroundTruthJob = false) { if (isGroundTruthJob) { cy.get(`#cvat_canvas_shape_${rectangle.id}`) @@ -145,77 +88,6 @@ context('Ground truth jobs', () => { .should('be.visible'); } - function checkConflicts(type = '', amount = 0, sidebar = true) { - switch (type) { - case 'warning': { - cy.get('.cvat-conflict-warning').should('have.length', amount); - if (sidebar) { - cy.get('.cvat-objects-sidebar-warning-item').should('have.length', amount); - } - break; - } - case 'error': { - cy.get('.cvat-conflict-error').should('have.length', amount); - if (sidebar) { - cy.get('.cvat-objects-sidebar-conflict-item').should('have.length', amount); - } - break; - } - default: { - cy.get('.cvat-conflict-warning').should('not.exist'); - cy.get('.cvat-conflict-error').should('not.exist'); - if (sidebar) { - cy.get('.cvat-objects-sidebar-warning-item').should('not.exist'); - cy.get('.cvat-objects-sidebar-conflict-item').should('not.exist'); - } - } - } - } - - function checkHighlight(darkenConflicts) { - cy.get('.cvat-conflict-label').first().trigger('mouseover'); - cy.get('.cvat-conflict-label.cvat-conflict-darken').should('have.length', darkenConflicts); - } - - function waitForReport(cvat, rqID) { - return new Promise((resolve) => { - function request() { - cvat.server.request(`/api/quality/reports?rq_id=${rqID}`, { - method: 'POST', - }).then((response) => { - if (response.status === 201) { - qualityReportID = response.data.id; - resolve(qualityReportID); - } else { - setTimeout(request, 500); - } - }); - } - - setTimeout(request, 500); - }); - } - - function createTaskQualityReport(taskId) { - cy.window().then((window) => window.cvat.server.request('/api/quality/reports', { - method: 'POST', - data: { - task_id: taskId, - }, - }).then((response) => { - const rqID = response.data.rq_id; - return waitForReport(window.cvat, rqID); - })).then(() => { - cy.visit('/tasks'); - cy.get('.cvat-spinner').should('not.exist'); - cy.intercept('GET', '/api/quality/reports**').as('getReport'); - - cy.openTask(taskName); - openQualityTab(); - cy.wait('@getReport'); - }); - } - before(() => { cy.visit('auth/login'); cy.login(); @@ -248,53 +120,24 @@ context('Ground truth jobs', () => { }); }); - it('Create ground truth job from task page', () => { - cy.createJob({ - ...jobOptions, - quantity: 15, - }); - cy.url().then((url) => { - groundTruthJobID = Number(url.split('/').slice(-1)[0].split('?')[0]); - - cy.interactMenu('Open the task'); - cy.get('.cvat-job-item').contains('a', `Job #${groundTruthJobID}`) - .parents('.cvat-job-item') - .find('.ant-tag') - .should('have.text', 'Ground truth'); - }); - }); - - it('Delete ground truth job', () => { - cy.deleteJob(groundTruthJobID); + after(() => { + cy.headlessDeleteTask(taskID); }); - it('Check quality page, create ground truth job from quality page', () => { - openQualityTab(); - - cy.get('.cvat-job-empty-ground-truth-item') - .should('be.visible') - .within(() => { - cy.contains('button', 'Create new').click(); - }); + it('Create ground truth job from task page', () => { cy.createJob({ ...jobOptions, frameCount: 4, seed: 1, - fromTaskPage: false, }); - cy.url().then((url) => { groundTruthJobID = Number(url.split('/').slice(-1)[0].split('?')[0]); cy.interactMenu('Open the task'); - openQualityTab(); cy.get('.cvat-job-item').contains('a', `Job #${groundTruthJobID}`) .parents('.cvat-job-item') .find('.ant-tag') .should('have.text', 'Ground truth'); - checkCardValue('.cvat-task-mean-annotation-quality', 'N/A'); - checkCardValue('.cvat-task-gt-conflicts', 'N/A'); - checkCardValue('.cvat-task-issues', '0'); }); }); @@ -326,9 +169,8 @@ context('Ground truth jobs', () => { cy.saveJob(); cy.interactMenu('Open the task'); - // job index is 2 because one gt job has been removed - cy.getJobIDFromIdx(2).then((gtJobID) => cy.setJobStage(gtJobID, 'acceptance')); - cy.getJobIDFromIdx(2).then((gtJobID) => cy.setJobState(gtJobID, 'completed')); + cy.getJobIDFromIdx(1).then((gtJobID) => cy.setJobStage(gtJobID, 'acceptance')); + cy.getJobIDFromIdx(1).then((gtJobID) => cy.setJobState(gtJobID, 'completed')); cy.get('.cvat-job-item').contains('a', `Job #${jobID}`).click(); cy.changeWorkspace('Review'); @@ -339,82 +181,9 @@ context('Ground truth jobs', () => { }); }); - it('Add annotations to regular job, check quality report', () => { - cy.changeWorkspace('Standard'); - groundTruthFrames.forEach((frame, index) => { - cy.goCheckFrameNumber(frame); - cy.createRectangle(rectangles[index]); - }); - cy.saveJob(); - - createTaskQualityReport(taskID); - checkCardValue('.cvat-task-mean-annotation-quality', '33.3%'); - checkCardValue('.cvat-task-gt-conflicts', '5'); - checkCardValue('.cvat-task-issues', '0'); - }); - - it('Check quality report is available for download', () => { - cy.get('.cvat-analytics-download-report-button').click(); - cy.verifyDownload(`quality-report-task_${taskID}-${qualityReportID}.json`); - }); - - it('Conflicts on canvas and sidebar', () => { - cy.get('.cvat-task-job-list').within(() => { - cy.contains('a', `Job #${jobID}`).click(); - }); - cy.get('.cvat-spinner').should('not.exist'); - - cy.changeWorkspace('Review'); - cy.get('.cvat-objects-sidebar-tabs').within(() => { - cy.contains('[role="tab"]', 'Issues').click(); - }); - cy.get('.cvat-objects-sidebar-show-ground-truth').filter(':visible').click(); - - cy.goCheckFrameNumber(groundTruthFrames[0]); - checkConflicts('error', 2); - checkHighlight(1); - - cy.goCheckFrameNumber(groundTruthFrames[1]); - checkConflicts('warning', 1); - - cy.goCheckFrameNumber(groundTruthFrames[2]); - checkConflicts(); - - cy.goCheckFrameNumber(groundTruthFrames[3]); - checkConflicts('error', 2); - checkHighlight(1); - }); - - it('Conflicts with annotation filter enabled', () => { - cy.addFiltersRule(0); - cy.setFilter({ - groupIndex: 0, - ruleIndex: 0, - field: 'ObjectID', - operator: '>', - value: '5', - submit: true, - }); - - groundTruthFrames.forEach((frame, index) => { - if (index !== groundTruthFrames.length - 1) { - cy.goCheckFrameNumber(frame); - checkConflicts('', 0, false); - } - }); - - cy.goCheckFrameNumber(groundTruthFrames[groundTruthFrames.length - 1]); - checkConflicts('error', 1, false); - }); - - it('Frames with conflicts navigation', () => { - cy.goCheckFrameNumber(groundTruthFrames[0]); - - cy.get('.cvat-issues-sidebar-next-frame').click(); - cy.checkFrameNum(groundTruthFrames[1]); - - cy.get('.cvat-issues-sidebar-next-frame').click(); - cy.checkFrameNum(groundTruthFrames[3]); + it('Delete ground truth job', () => { + cy.interactMenu('Open the task'); + cy.deleteJob(groundTruthJobID); }); }); @@ -430,7 +199,6 @@ context('Ground truth jobs', () => { const archivePath = `cypress/fixtures/${archiveName}`; const imagesFolder = `cypress/fixtures/${imageFileName}`; const directoryToArchive = imagesFolder; - let labels = []; before(() => { cy.visit('/tasks'); @@ -445,83 +213,10 @@ context('Ground truth jobs', () => { cy.url().then((url) => { taskID = Number(url.split('/').slice(-1)[0].split('?')[0]); }); - cy.get('.cvat-job-item').first().invoke('attr', 'data-row-id').then((val) => { - jobID = val; - }).then(() => { - cy.intercept(`/api/labels?**job_id=${jobID}**`).as('getJobLabels'); - cy.visit(`/tasks/${taskID}/jobs/${jobID}`); - cy.wait('@getJobLabels').then((interception) => { - labels = interception.response.body.results; - }); - }); }); afterEach(() => { - cy.window().then((window) => { - window.cvat.server.request(`/api/jobs/${jobID}`, { - method: 'DELETE', - }); - }); - }); - - it('Create ground truth job, compute quality report, check jobs table', () => { - cy.window().then((window) => window.cvat.server.request('/api/jobs', { - method: 'POST', - data: { - task_id: taskID, - frame_count: 20, - type: 'ground_truth', - frame_selection_method: 'random_uniform', - }, - }).then((response) => { - jobID = response.data.id; - return window.cvat.server.request(`/api/jobs/${jobID}/annotations`, { - method: 'PUT', - data: { - shapes: [], - tracks: [{ - label_id: labels[0].id, - frame: 0, - group: 0, - source: 'manual', - attributes: [], - elements: [], - shapes: [{ - type: 'rectangle', - occluded: false, - z_order: 0, - rotation: 0, - outside: false, - attributes: [], - frame: 0, - points: [250, 350, 350, 450], - }], - }], - tags: [], - }, - }); - }).then(() => ( - window.cvat.server.request(`/api/jobs/${jobID}`, { - method: 'PATCH', - data: { - stage: 'acceptance', - state: 'completed', - }, - }) - ))).then(() => { - createTaskQualityReport(taskID); - cy.get('.cvat-task-jobs-table .ant-pagination-item').last().invoke('text').then((page) => { - const lastPage = parseInt(page, 10); - - for (let i = 0; i < lastPage; i++) { - cy.get('.cvat-task-jobs-table-row').each((row) => { - cy.get(row).should('not.include.text', 'N/A'); - }); - - cy.get('.cvat-task-jobs-table .ant-pagination-next').click(); - } - }); - }); + cy.headlessDeleteTask(taskID); }); it('Check GT button should be disabled while waiting for GT job creation', () => { diff --git a/tests/cypress/support/commands_review_pipeline.js b/tests/cypress/support/commands_review_pipeline.js index 974d61e6ee8a..e399b22dbc32 100644 --- a/tests/cypress/support/commands_review_pipeline.js +++ b/tests/cypress/support/commands_review_pipeline.js @@ -1,5 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation -// Copyright (C) 2023 CVAT.ai Corporation +// Copyright (C) 2023-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -42,23 +42,6 @@ Cypress.Commands.add('assignJobToUser', (jobID, user) => { cy.get('.cvat-spinner').should('not.exist'); }); -Cypress.Commands.add('checkJobStatus', (jobIdx, status, assignee, reviewer) => { - cy.getJobIDFromIdx(jobIdx).then((jobID) => { - cy.get('.cvat-task-jobs-table') - .contains('a', `Job #${jobID}`) - .parents('.cvat-task-jobs-table-row') - .within(() => { - cy.get('.cvat-job-item-status').should('have.text', status); - cy.get('.cvat-job-assignee-selector').within(() => { - cy.get('input[type="search"]').should('have.value', assignee); - }); - cy.get('.cvat-job-reviewer-selector').within(() => { - cy.get('input[type="search"]').should('have.value', reviewer); - }); - }); - }); -}); - Cypress.Commands.add('collectIssueLabel', () => { cy.document().then((doc) => Array.from(doc.querySelectorAll('.cvat-hidden-issue-label'))); }); diff --git a/yarn.lock b/yarn.lock index d8d8df44fe45..f88cb75b2492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4413,7 +4413,7 @@ custom-error-instance@2.1.1: svg.select.js "3.0.1" "cvat-core@link:./cvat-core": - version "15.1.3" + version "15.2.0" dependencies: axios "^1.6.0" axios-retry "^4.0.0"