Skip to content

Commit

Permalink
Merge pull request #9106 from cvat-ai/release-2.30.0
Browse files Browse the repository at this point in the history
Release v2.30.0
  • Loading branch information
cvat-bot[bot] authored Feb 14, 2025
2 parents 0567afa + 719d496 commit 552bd53
Show file tree
Hide file tree
Showing 70 changed files with 834 additions and 1,631 deletions.
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"background": {
"activeOnStart": true,
"beginsPattern": "webpack-dev-server",
"endsPattern": "Compiled"
"endsPattern": "compiled"
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- scriv-insert-here -->

<a id='changelog-2.30.0'></a>
## \[2.30.0\] - 2025-02-14

### Added

- Gamma filter settings are now automatically saved and restored upon reload
(<https://github.com/cvat-ai/cvat/pull/9032>)

- Ability to customize `api/sever/about` endpoint via settings including logo and sign-in page subtitle
(<https://github.com/cvat-ai/cvat/pull/9052>)

### Changed

- Client settings are now saved automatically
(<https://github.com/cvat-ai/cvat/pull/9032>)

### Fixed

- \[SDK\] `skeleton_label_spec` now correctly forwards `kwargs` to
`PatchedLabelRequest`
(<https://github.com/cvat-ai/cvat/pull/9087>)

- Error: Cannot read properties of undefined (reading 'width') that occurs when changing frames in a video-based GT job
(<https://github.com/cvat-ai/cvat/pull/9095>)

<a id='changelog-2.29.0'></a>
## \[2.29.0\] - 2025-02-10

Expand Down
2 changes: 1 addition & 1 deletion cvat-cli/requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cvat-sdk==2.29.0
cvat-sdk==2.30.0

attrs>=24.2.0
Pillow>=10.3.0
Expand Down
2 changes: 1 addition & 1 deletion cvat-cli/src/cvat_cli/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "2.29.0"
VERSION = "2.30.0"
41 changes: 41 additions & 0 deletions cvat-core/src/about.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (C) CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import { SerializedAbout } from './server-response-types';

export default class AboutData {
#description: string;
#name: string;
#version: string;
#logoURL: string;
#subtitle: string;

constructor(initialData: SerializedAbout) {
this.#description = initialData.description;
this.#name = initialData.name;
this.#version = initialData.version;
this.#logoURL = initialData.logo_url;
this.#subtitle = initialData.subtitle;
}

get description(): string {
return this.#description;
}

get name(): string {
return this.#name;
}

get version(): string {
return this.#version;
}

get logoURL(): string {
return this.#logoURL;
}

get subtitle(): string {
return this.#subtitle;
}
}
3 changes: 2 additions & 1 deletion cvat-core/src/api-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
QualitySettingsFilter, SerializedAsset,
} from './server-response-types';
import QualityReport from './quality-report';
import AboutData from './about';
import QualityConflict, { ConflictSeverity } from './quality-conflict';
import QualitySettings from './quality-settings';
import { getFramesMeta } from './frames';
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function implementAPI(cvat: CVATCore): CVATCore {

implementationMixin(cvat.server.about, async () => {
const result = await serverProxy.server.about();
return result;
return new AboutData(result);
});
implementationMixin(cvat.server.share, async (directory: string, searchPrefix?: string) => {
const result = await serverProxy.server.share(directory, searchPrefix);
Expand Down
5 changes: 2 additions & 3 deletions cvat-core/src/core-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT

import {
ModelKind, ModelReturnType, RQStatus, ShapeType,
LabelType, ModelKind, RQStatus,
} from './enums';

export interface ModelAttribute {
Expand All @@ -28,7 +28,7 @@ export interface MLModelTip {

export interface MLModelLabel {
name: string;
type: ShapeType | 'unknown';
type: LabelType;
attributes: ModelAttribute[];
sublabels?: MLModelLabel[];
svg?: string,
Expand All @@ -42,7 +42,6 @@ export interface SerializedModel {
description?: string;
kind?: ModelKind;
type?: string;
return_type?: ModelReturnType;
owner?: any;
provider?: string;
url?: string;
Expand Down
7 changes: 0 additions & 7 deletions cvat-core/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,6 @@ export enum ModelProviders {
CVAT = 'cvat',
}

export enum ModelReturnType {
RECTANGLE = 'rectangle',
TAG = 'tag',
POLYGON = 'polygon',
MASK = 'mask',
}

export const colors = [
'#33ddff',
'#fa3253',
Expand Down
4 changes: 3 additions & 1 deletion cvat-core/src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,9 @@ export async function getFrame(
await refreshJobCacheIfOutdated(jobID);

const framesMetaData = await frameDataCache[jobID].getMeta();
const frameMeta = framesMetaData.frames[frame - jobStartFrame];
const dataFrameNumber = framesMetaData.getDataFrameNumber(frame - jobStartFrame);
const frameIndex = framesMetaData.getFrameIndex(dataFrameNumber);
const frameMeta = framesMetaData.frames[frameIndex];
frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height);
frameDataCache[jobID].decodeForward = isPlaying;
frameDataCache[jobID].forwardStep = step;
Expand Down
3 changes: 2 additions & 1 deletion cvat-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import AnalyticsReport from './analytics-report';
import AnnotationGuide from './guide';
import { JobValidationLayout, TaskValidationLayout } from './validation-layout';
import { Request } from './request';
import AboutData from './about';
import {
runAction,
callAction,
Expand Down Expand Up @@ -61,7 +62,7 @@ export default interface CVATCore {
requests: typeof lambdaManager.requests;
};
server: {
about: typeof serverProxy.server.about;
about: () => Promise<AboutData>;
share: (dir: string) => Promise<{
mimeType: string;
name: string;
Expand Down
18 changes: 13 additions & 5 deletions cvat-core/src/ml-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import PluginRegistry from './plugins';
import {
ModelProviders, ModelKind, ModelReturnType,
LabelType, ModelProviders, ModelKind,
} from './enums';
import {
SerializedModel, ModelParams, MLModelTip, MLModelLabel,
Expand Down Expand Up @@ -44,8 +44,11 @@ export default class MLModel {

public get displayKind(): string {
if (this.kind === ModelKind.DETECTOR) {
if (this.returnType === ModelReturnType.TAG) return 'classifier';
if (this.returnType === ModelReturnType.MASK) return 'segmenter';
switch (this.returnType) {
case LabelType.TAG: return 'classifier';
case LabelType.MASK: return 'segmenter';
default: // fall back on the original kind
}
}
return this.kind;
}
Expand Down Expand Up @@ -94,8 +97,13 @@ export default class MLModel {
return this.serialized?.url;
}

public get returnType(): ModelReturnType | undefined {
return this.serialized?.return_type;
public get returnType(): LabelType {
const uniqueLabelTypes = new Set(this.labels.map((label) => label.type));

if (uniqueLabelTypes.size !== 1) return LabelType.ANY;

const [labelType] = uniqueLabelTypes;
return labelType;
}

public async preview(): Promise<string> {
Expand Down
2 changes: 2 additions & 0 deletions cvat-core/src/server-response-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ export interface SerializedAbout {
description: string;
name: string;
version: string;
logo_url: string;
subtitle: string;
}

export interface SerializedRemoteFile {
Expand Down
2 changes: 1 addition & 1 deletion cvat-sdk/cvat_sdk/auto_annotation/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def skeleton_label_spec(
name: str, id: int, sublabels: Sequence[models.SublabelRequest], **kwargs
) -> models.PatchedLabelRequest:
"""Helper factory function for PatchedLabelRequest with type="skeleton"."""
return models.PatchedLabelRequest(name=name, id=id, type="skeleton", sublabels=sublabels)
return label_spec(name, id, type="skeleton", sublabels=sublabels, **kwargs)


# pylint: disable-next=redefined-builtin
Expand Down
2 changes: 1 addition & 1 deletion cvat-sdk/gen/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set -e

GENERATOR_VERSION="v6.0.1"

VERSION="2.29.0"
VERSION="2.30.0"
LIB_NAME="cvat_sdk"
LAYER1_LIB_NAME="${LIB_NAME}/api_client"
DST_DIR="$(cd "$(dirname -- "$0")/.." && pwd)"
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "2.29.0",
"version": "2.30.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions cvat-ui/src/actions/about-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT

import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { getCore } from 'cvat-core-wrapper';
import { AboutData, getCore } from 'cvat-core-wrapper';

const core = getCore();

Expand All @@ -15,7 +15,7 @@ export enum AboutActionTypes {

const aboutActions = {
getAbout: () => createAction(AboutActionTypes.GET_ABOUT),
getAboutSuccess: (server: any) => createAction(AboutActionTypes.GET_ABOUT_SUCCESS, { server }),
getAboutSuccess: (server: AboutData) => createAction(AboutActionTypes.GET_ABOUT_SUCCESS, { server }),
getAboutFailed: (error: any) => createAction(AboutActionTypes.GET_ABOUT_FAILED, { error }),
};

Expand Down
103 changes: 102 additions & 1 deletion cvat-ui/src/actions/settings-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
//
// SPDX-License-Identifier: MIT

import _ from 'lodash';
import { AnyAction } from 'redux';
import { ThunkAction } from 'utils/redux';
import {
GridColor, ColorBy, SettingsState, ToolsBlockerState,
CombinedState,
} from 'reducers';
import { ImageFilter, ImageFilterAlias } from 'utils/image-processing';
import { ImageFilter, ImageFilterAlias, SerializedImageFilter } from 'utils/image-processing';
import { conflict, conflictDetector } from 'utils/conflict-detector';
import GammaCorrection, { GammaFilterOptions } from 'utils/fabric-wrapper/gamma-correciton';
import { shortcutsActions } from './shortcuts-actions';

export enum SettingsActionTypes {
SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL',
Expand Down Expand Up @@ -409,3 +415,98 @@ export function resetImageFilters(): AnyAction {
payload: {},
};
}

export function restoreSettingsAsync(): ThunkAction {
return async (dispatch, getState): Promise<void> => {
const state: CombinedState = getState();
const { settings, shortcuts } = state;

dispatch(shortcutsActions.setDefaultShortcuts(structuredClone(shortcuts.keyMap)));

const settingsString = localStorage.getItem('clientSettings') as string;
if (!settingsString) return;

const loadedSettings = JSON.parse(settingsString);
const newSettings = {
player: settings.player,
workspace: settings.workspace,
imageFilters: [],
} as Pick<SettingsState, 'player' | 'workspace' | 'imageFilters'>;

Object.entries(_.pick(newSettings, ['player', 'workspace'])).forEach(([sectionKey, section]) => {
for (const key of Object.keys(section)) {
const settedValue = loadedSettings[sectionKey]?.[key];
if (settedValue !== undefined) {
Object.defineProperty(newSettings[sectionKey as 'player' | 'workspace'], key, { value: settedValue });
}
}
});

if ('imageFilters' in loadedSettings) {
loadedSettings.imageFilters.forEach((filter: SerializedImageFilter) => {
if (filter.alias === ImageFilterAlias.GAMMA_CORRECTION) {
const modifier = new GammaCorrection(filter.params as GammaFilterOptions);
newSettings.imageFilters.push({
modifier,
alias: ImageFilterAlias.GAMMA_CORRECTION,
});
}
});
}

dispatch(setSettings(newSettings));

if ('shortcuts' in loadedSettings) {
const updateKeyMap = structuredClone(shortcuts.keyMap);
for (const [key, value] of Object.entries(loadedSettings.shortcuts.keyMap)) {
if (key in updateKeyMap) {
updateKeyMap[key].sequences = (value as { sequences: string[] }).sequences;
}
}

for (const key of Object.keys(updateKeyMap)) {
const currValue = {
[key]: { ...updateKeyMap[key] },
};
const conflictingShortcuts = conflictDetector(currValue, shortcuts.keyMap);
if (conflictingShortcuts) {
for (const conflictingShortcut of Object.keys(conflictingShortcuts)) {
for (const sequence of currValue[key].sequences) {
for (const conflictingSequence of conflictingShortcuts[conflictingShortcut].sequences) {
if (conflict(sequence, conflictingSequence)) {
updateKeyMap[conflictingShortcut].sequences = [
...updateKeyMap[conflictingShortcut].sequences.filter(
(s: string) => s !== conflictingSequence,
),
];
}
}
}
}
}
}
dispatch(shortcutsActions.registerShortcuts(updateKeyMap));
}
};
}

export function updateCachedSettings(settings: CombinedState['settings'], shortcuts: CombinedState['shortcuts']): void {
const supportedImageFilters = [ImageFilterAlias.GAMMA_CORRECTION];
const settingsForSaving = {
player: settings.player,
workspace: settings.workspace,
shortcuts: {
keyMap: Object.entries(shortcuts.keyMap).reduce<Record<string, { sequences: string[] }>>(
(acc, [key, value]) => {
if (key in shortcuts.defaultState) {
acc[key] = { sequences: value.sequences };
}
return acc;
}, {}),
},
imageFilters: settings.imageFilters.filter((imageFilter) => supportedImageFilters.includes(imageFilter.alias))
.map((imageFilter) => imageFilter.modifier.toJSON()),
};

localStorage.setItem('clientSettings', JSON.stringify(settingsForSaving));
}
Loading

0 comments on commit 552bd53

Please sign in to comment.