Skip to content

Commit 67d3645

Browse files
Merge pull request #8527 from cvat-ai/release-2.21.0
Release v2.21.0
2 parents 941f5c0 + 61270d9 commit 67d3645

File tree

114 files changed

+11875
-3459
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+11875
-3459
lines changed

CHANGELOG.md

+67
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616

1717
<!-- scriv-insert-here -->
1818

19+
<a id='changelog-2.21.0'></a>
20+
## \[2.21.0\] - 2024-10-10
21+
22+
### Added
23+
24+
- New task mode: Honeypots (GT pool)
25+
(<https://github.com/cvat-ai/cvat/pull/8348>)
26+
- New task creation options for quality control: Honeypots (GT pool), GT job
27+
(<https://github.com/cvat-ai/cvat/pull/8348>)
28+
- New GT job frame selection method: `random_per_job`,
29+
which guarantees each job will have GT overlap
30+
(<https://github.com/cvat-ai/cvat/pull/8348>)
31+
- \[Server API\] POST `/jobs/`: new frame selection parameters,
32+
which accept percentages, instead of absolute values
33+
(<https://github.com/cvat-ai/cvat/pull/8348>)
34+
- \[Server API\] GET `/api/tasks/{id}/` got a new `validation_mode` field,
35+
reflecting the current validation configuration (immutable)
36+
(<https://github.com/cvat-ai/cvat/pull/8348>)
37+
- \[Server API\] POST `/api/tasks/{id}/data` got a new `validation_params` field,
38+
which allows to enable `GT` and `GT_POOL` validation for a task on its creation
39+
(<https://github.com/cvat-ai/cvat/pull/8348>)
40+
41+
- Added custom certificates documentation
42+
(<https://github.com/cvat-ai/cvat/pull/7508>)
43+
44+
- Support for YOLOv8 Classification format
45+
(<https://github.com/cvat-ai/cvat/pull/8475>)
46+
47+
- \[Server API\] An option to change real frames for honeypot frames in tasks with honeypots
48+
(<https://github.com/cvat-ai/cvat/pull/8471>)
49+
- \[Server API\] New endpoints for validation configuration management in tasks and jobs
50+
`/api/tasks/{id}/validation_layout`, `/api/jobs/{id}/validation_layout`
51+
(<https://github.com/cvat-ai/cvat/pull/8471>)
52+
53+
- \[Helm\] Readiness and liveness probes
54+
(<https://github.com/cvat-ai/cvat/pull/8488>)
55+
56+
### Changed
57+
58+
- \[Server API\] POST `/jobs/` `.frames` field now expects relative frame numbers
59+
instead of absolute (source data) ones
60+
(<https://github.com/cvat-ai/cvat/pull/8348>)
61+
62+
- \[Server API\] Now chunks in tasks can be changed.
63+
There are new API elements to check chunk relevancy, if they are cached:
64+
`/api/tasks/{id}/data/meta` got a new field `chunks_updated_date`,
65+
`/api/tasks/{id}/data/?type=chunk` got 2 new headers: `X-Updated-Date`, `X-Checksum`
66+
(<https://github.com/cvat-ai/cvat/pull/8471>)
67+
68+
- Made the `PATCH` endpoints for projects, tasks, jobs and memberships check
69+
the input more strictly
70+
(<https://github.com/cvat-ai/cvat/pull/8493>):
71+
72+
- unknown fields are rejected;
73+
- updating a field now requires the same level of permissions regardless of
74+
whether the new value is the same as the old value.
75+
76+
- \[Server API\] Quality report computation is now allowed to regular users
77+
(<https://github.com/cvat-ai/cvat/pull/8511>)
78+
79+
### Fixed
80+
81+
- Invalid chunks for GT jobs when `?number` is used in the request and task frame step > 1
82+
(<https://github.com/cvat-ai/cvat/pull/8510>)
83+
- Invalid output of frames for specific GT frame requests with `api/jobs/{id}/data/?type=frame`
84+
(<https://github.com/cvat-ai/cvat/pull/8510>)
85+
1986
<a id='changelog-2.20.0'></a>
2087
## \[2.20.0\] - 2024-10-01
2188

README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,10 @@ For more information about the supported formats, see:
176176
| [LFW](http://vis-www.cs.umass.edu/lfw/) | ✔️ | ✔️ |
177177
| [Supervisely Point Cloud Format](https://docs.supervise.ly/data-organization/00_ann_format_navi) | ✔️ | ✔️ |
178178
| [YOLOv8 Detection](https://docs.ultralytics.com/datasets/detect/) | ✔️ | ✔️ |
179-
| [YOLOv8 Oriented Bounding Boxes](https://docs.ultralytics.com/datasets/obb/) | ✔️ | ✔️ |
180-
| [YOLOv8 Segmentation](https://docs.ultralytics.com/datasets/segment/) | ✔️ | ✔️ |
181-
| [YOLOv8 Pose](https://docs.ultralytics.com/datasets/pose/) | ✔️ | ✔️ |
179+
| [YOLOv8 Oriented Bounding Boxes](https://docs.ultralytics.com/datasets/obb/) | ✔️ | ✔️ |
180+
| [YOLOv8 Segmentation](https://docs.ultralytics.com/datasets/segment/) | ✔️ | ✔️ |
181+
| [YOLOv8 Pose](https://docs.ultralytics.com/datasets/pose/) | ✔️ | ✔️ |
182+
| [YOLOv8 Classification](https://docs.ultralytics.com/datasets/classify/) | ✔️ | ✔️ |
182183

183184
<!--lint enable maximum-line-length-->
184185

cvat-cli/requirements/base.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
cvat-sdk~=2.20.0
1+
cvat-sdk~=2.21.0
22
Pillow>=10.3.0
33
setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability

cvat-cli/src/cvat_cli/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "2.20.0"
1+
VERSION = "2.21.0"

cvat-core/src/api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import QualityReport from './quality-report';
2626
import QualityConflict from './quality-conflict';
2727
import QualitySettings from './quality-settings';
2828
import AnalyticsReport from './analytics-report';
29+
import ValidationLayout from './validation-layout';
2930
import { Request } from './request';
3031

3132
import * as enums from './enums';
@@ -426,6 +427,7 @@ function build(): CVATCore {
426427
QualityReport,
427428
Request,
428429
FramesMetaData,
430+
ValidationLayout,
429431
},
430432
utils: {
431433
mask2Rle,

cvat-core/src/frames.ts

+59-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { FieldUpdateTrigger } from './common';
1616
// frame storage by job id
1717
const frameDataCache: Record<string, {
1818
meta: FramesMetaData;
19+
metaFetchedTimestamp: number;
1920
chunkSize: number;
2021
mode: 'annotation' | 'interpolation';
2122
startFrame: number;
@@ -57,6 +58,7 @@ export class FramesMetaData {
5758
public stopFrame: number;
5859
public frameStep: number;
5960
public chunkCount: number;
61+
public chunksUpdatedDate: string;
6062

6163
#updateTrigger: FieldUpdateTrigger;
6264

@@ -71,6 +73,7 @@ export class FramesMetaData {
7173
size: undefined,
7274
start_frame: undefined,
7375
stop_frame: undefined,
76+
chunks_updated_date: undefined,
7477
};
7578

7679
this.#updateTrigger = new FieldUpdateTrigger();
@@ -149,6 +152,9 @@ export class FramesMetaData {
149152
frameStep: {
150153
get: () => frameStep,
151154
},
155+
chunksUpdatedDate: {
156+
get: () => data.chunks_updated_date,
157+
},
152158
}),
153159
);
154160

@@ -592,13 +598,45 @@ function getFrameMeta(jobID, frame): SerializedFramesMetaData['frames'][0] {
592598
return frameMeta;
593599
}
594600

601+
async function refreshJobCacheIfOutdated(jobID: number): Promise<void> {
602+
const cached = frameDataCache[jobID];
603+
if (!cached) {
604+
throw new Error('Frame data cache is abscent');
605+
}
606+
607+
const META_DATA_RELOAD_PERIOD = 1 * 60 * 60 * 1000; // 1 hour
608+
const isOutdated = (Date.now() - cached.metaFetchedTimestamp) > META_DATA_RELOAD_PERIOD;
609+
610+
if (isOutdated) {
611+
// get metadata again if outdated
612+
const meta = await getFramesMeta('job', jobID, true);
613+
if (new Date(meta.chunksUpdatedDate) > new Date(cached.meta.chunksUpdatedDate)) {
614+
// chunks were re-defined. Existing data not relevant anymore
615+
// currently we only re-write meta, remove all cached frames from provider and clear cached context images
616+
// other parameters (e.g. chunkSize) are not supposed to be changed
617+
cached.meta = meta;
618+
cached.provider.cleanup(Number.MAX_SAFE_INTEGER);
619+
for (const frame of Object.keys(cached.contextCache)) {
620+
for (const image of Object.values(cached.contextCache[+frame].data)) {
621+
// close images to immediate memory release
622+
image.close();
623+
}
624+
}
625+
cached.contextCache = {};
626+
}
627+
628+
cached.metaFetchedTimestamp = Date.now();
629+
}
630+
}
631+
595632
export function getContextImage(jobID: number, frame: number): Promise<Record<string, ImageBitmap>> {
596633
return new Promise<Record<string, ImageBitmap>>((resolve, reject) => {
597634
if (!(jobID in frameDataCache)) {
598635
reject(new Error(
599636
'Frame data was not initialized for this job. Try first requesting any frame.',
600637
));
601638
}
639+
602640
const frameData = frameDataCache[jobID];
603641
const requestId = frame;
604642
const { startFrame } = frameData;
@@ -695,7 +733,9 @@ export async function getFrame(
695733
dimension: DimensionType,
696734
getChunk: (chunkIndex: number, quality: ChunkQuality) => Promise<ArrayBuffer>,
697735
): Promise<FrameData> {
698-
if (!(jobID in frameDataCache)) {
736+
const dataCacheExists = jobID in frameDataCache;
737+
738+
if (!dataCacheExists) {
699739
const blockType = chunkType === 'video' ? BlockType.MP4VIDEO : BlockType.ARCHIVE;
700740
const meta = await getFramesMeta('job', jobID);
701741

@@ -718,6 +758,7 @@ export async function getFrame(
718758

719759
frameDataCache[jobID] = {
720760
meta,
761+
metaFetchedTimestamp: Date.now(),
721762
chunkSize,
722763
mode,
723764
startFrame,
@@ -743,6 +784,22 @@ export async function getFrame(
743784
};
744785
}
745786

787+
// basically the following functions may be affected if job cache is outdated
788+
// - getFrame
789+
// - getContextImage
790+
// - getCachedChunks
791+
// And from this idea we should call refreshJobCacheIfOutdated from each one
792+
// Hovewer, following from the order, these methods are usually called
793+
// it may lead to even more confusing behaviour
794+
//
795+
// Usually user first receives frame, then user receives ranges and finally user receives context images
796+
// In this case (extremely rare, but nevertheless possible) user may get context images related to another frame
797+
// - if cache gets outdated after getFrame() call
798+
// - and before getContextImage() call
799+
// - and both calls refer to the same frame that is refreshed honeypot frame and this frame has context images
800+
// Thus, it is better to only call `refreshJobCacheIfOutdated` from getFrame()
801+
await refreshJobCacheIfOutdated(jobID);
802+
746803
const frameMeta = getFrameMeta(jobID, frame);
747804
frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height);
748805
frameDataCache[jobID].decodeForward = isPlaying;
@@ -759,7 +816,7 @@ export async function getFrame(
759816
});
760817
}
761818

762-
export async function getDeletedFrames(instanceType: 'job' | 'task', id): Promise<Record<number, boolean>> {
819+
export async function getDeletedFrames(instanceType: 'job' | 'task', id: number): Promise<Record<number, boolean>> {
763820
if (instanceType === 'job') {
764821
const { meta } = frameDataCache[id];
765822
return meta.deletedFrames;

cvat-core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import QualityConflict from './quality-conflict';
3232
import QualitySettings from './quality-settings';
3333
import AnalyticsReport from './analytics-report';
3434
import AnnotationGuide from './guide';
35+
import ValidationLayout from './validation-layout';
3536
import { Request } from './request';
3637
import BaseSingleFrameAction, { listActions, registerAction, runActions } from './annotations-actions';
3738
import {
@@ -215,6 +216,7 @@ export default interface CVATCore {
215216
AnalyticsReport: typeof AnalyticsReport;
216217
Request: typeof Request;
217218
FramesMetaData: typeof FramesMetaData;
219+
ValidationLayout: typeof ValidationLayout;
218220
};
219221
utils: {
220222
mask2Rle: typeof mask2Rle;

0 commit comments

Comments
 (0)