Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2.30.0 #9106

Merged
merged 14 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading