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

Update gsoc/consensus-feature with develop and update schema.yml #8895

Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ __pycache__
.coverage
.husky/
.python-version
tmp*cvat/
temp*/

# Ignore generated test files
docker-compose.tests.yml

# Ignore npm logs file
npm-debug.log*
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,11 @@ For more information about the supported formats, see:
| [Kitti Raw Format](https://www.cvlibs.net/datasets/kitti/raw_data.php) | ✔️ | ✔️ |
| [LFW](http://vis-www.cs.umass.edu/lfw/) | ✔️ | ✔️ |
| [Supervisely Point Cloud Format](https://docs.supervise.ly/data-organization/00_ann_format_navi) | ✔️ | ✔️ |
| [YOLOv8 Detection](https://docs.ultralytics.com/datasets/detect/) | ✔️ | ✔️ |
| [YOLOv8 Oriented Bounding Boxes](https://docs.ultralytics.com/datasets/obb/) | ✔️ | ✔️ |
| [YOLOv8 Segmentation](https://docs.ultralytics.com/datasets/segment/) | ✔️ | ✔️ |
| [YOLOv8 Pose](https://docs.ultralytics.com/datasets/pose/) | ✔️ | ✔️ |
| [YOLOv8 Classification](https://docs.ultralytics.com/datasets/classify/) | ✔️ | ✔️ |
| [Ultralytics YOLO Detection](https://docs.ultralytics.com/datasets/detect/) | ✔️ | ✔️ |
| [Ultralytics YOLO Oriented Bounding Boxes](https://docs.ultralytics.com/datasets/obb/) | ✔️ | ✔️ |
| [Ultralytics YOLO Segmentation](https://docs.ultralytics.com/datasets/segment/) | ✔️ | ✔️ |
| [Ultralytics YOLO Pose](https://docs.ultralytics.com/datasets/pose/) | ✔️ | ✔️ |
| [Ultralytics YOLO Classification](https://docs.ultralytics.com/datasets/classify/) | ✔️ | ✔️ |

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Changed

- YOLOv8 formats renamed to Ultralytics YOLO formats
(<https://github.com/cvat-ai/cvat/pull/8863>)
124 changes: 92 additions & 32 deletions cvat-core/src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import config from './config';

// frame storage by job id
const frameDataCache: Record<string, {
meta: FramesMetaData;
metaFetchedTimestamp: number;
chunkSize: number;
mode: 'annotation' | 'interpolation';
Expand All @@ -36,11 +35,21 @@ const frameDataCache: Record<string, {
size: number;
}>;
getChunk: (chunkIndex: number, quality: ChunkQuality) => Promise<ArrayBuffer>;
getMeta: () => Promise<FramesMetaData>;
}> = {};

// frame meta data storage by job id
const frameMetaCache: Record<string, Promise<FramesMetaData>> = {};

enum DeletedFrameState {
DELETED = 'deleted',
RESTORED = 'restored',
}

interface FramesMetaDataUpdatedData {
deletedFrames: Record<number, DeletedFrameState>;
}

export class FramesMetaData {
public chunkSize: number;
public deletedFrames: Record<number, boolean>;
Expand Down Expand Up @@ -82,10 +91,13 @@ export class FramesMetaData {
if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) {
if (property === 'deleted_frames') {
const update = (frame: string, remove: boolean): void => {
if (this.#updateTrigger.get(`deletedFrames:${frame}:${!remove}`)) {
this.#updateTrigger.resetField(`deletedFrames:${frame}:${!remove}`);
const [state, oppositeState] = remove ?
[DeletedFrameState.DELETED, DeletedFrameState.RESTORED] :
[DeletedFrameState.RESTORED, DeletedFrameState.DELETED];
if (this.#updateTrigger.get(`deletedFrames:${frame}:${oppositeState}`)) {
this.#updateTrigger.resetField(`deletedFrames:${frame}:${oppositeState}`);
} else {
this.#updateTrigger.update(`deletedFrames:${frame}:${remove}`);
this.#updateTrigger.update(`deletedFrames:${frame}:${state}`);
}
};

Expand Down Expand Up @@ -178,8 +190,17 @@ export class FramesMetaData {
return (dataFrameNumber - this.startFrame) / this.frameStep;
}

getUpdated(): Record<string, unknown> {
return this.#updateTrigger.getUpdated(this);
getUpdated(): FramesMetaDataUpdatedData {
const updatedFields = this.#updateTrigger.getUpdated(this);
const deletedFrames: FramesMetaDataUpdatedData['deletedFrames'] = {};
for (const key in updatedFields) {
if (Object.hasOwn(updatedFields, key) && key.startsWith('deletedFrames')) {
const [, frame, state] = key.split(':');
deletedFrames[frame] = state;
}
}

return { deletedFrames };
}

resetUpdated(): void {
Expand Down Expand Up @@ -340,17 +361,18 @@ class PrefetchAnalyzer {
}

Object.defineProperty(FrameData.prototype.data, 'implementation', {
value(this: FrameData, onServerRequest) {
async value(this: FrameData, onServerRequest) {
const {
provider, prefetchAnalyzer, chunkSize, jobStartFrame,
decodeForward, forwardStep, decodedBlocksCacheSize,
} = frameDataCache[this.jobID];
const meta = await frameDataCache[this.jobID].getMeta();

return new Promise<{
renderWidth: number;
renderHeight: number;
imageData: ImageBitmap | Blob;
} | Blob>((resolve, reject) => {
const {
meta, provider, prefetchAnalyzer, chunkSize, jobStartFrame,
decodeForward, forwardStep, decodedBlocksCacheSize,
} = frameDataCache[this.jobID];

const requestId = +_.uniqueId();
const requestedDataFrameNumber = meta.getDataFrameNumber(this.number - jobStartFrame);
const chunkIndex = meta.getFrameChunkIndex(requestedDataFrameNumber);
Expand Down Expand Up @@ -536,6 +558,34 @@ Object.defineProperty(FrameData.prototype.data, 'implementation', {
writable: false,
});

function mergeMetaData(
nextData: SerializedFramesMetaData,
previousData?: Promise<FramesMetaData>,
): Promise<FramesMetaData> {
const framesMetaData = new FramesMetaData({
...nextData,
deleted_frames: Object.fromEntries(nextData.deleted_frames.map((_frame) => [_frame, true])),
});

if (previousData instanceof Promise) {
return previousData.then((prevMeta) => {
const updatedFields = prevMeta.getUpdated();
const updatedDeletedFrames = updatedFields.deletedFrames;
for (const [frame, state] of Object.entries(updatedDeletedFrames)) {
if (state === DeletedFrameState.DELETED) {
framesMetaData.deletedFrames[frame] = true;
} else if (state === DeletedFrameState.RESTORED) {
delete framesMetaData.deletedFrames[frame];
}
}

return framesMetaData;
});
}

return Promise.resolve(framesMetaData);
}

export function getFramesMeta(type: 'job' | 'task', id: number, forceReload = false): Promise<FramesMetaData> {
if (type === 'task') {
// we do not cache task meta currently. So, each new call will results to the server request
Expand All @@ -551,11 +601,11 @@ export function getFramesMeta(type: 'job' | 'task', id: number, forceReload = fa
const previousCache = frameMetaCache[id];
frameMetaCache[id] = new Promise((resolve, reject) => {
serverProxy.frames.getMeta('job', id).then((serialized) => {
const framesMetaData = new FramesMetaData({
...serialized,
deleted_frames: Object.fromEntries(serialized.deleted_frames.map((_frame) => [_frame, true])),
// When we get new framesMetaData from server there can be some unsaved data
// here we merge new meta data with cached one
mergeMetaData(serialized, previousCache).then((mergedData) => {
resolve(mergedData);
});
resolve(framesMetaData);
}).catch((error: unknown) => {
delete frameMetaCache[id];
if (previousCache instanceof Promise) {
Expand Down Expand Up @@ -588,8 +638,9 @@ function saveJobMeta(meta: FramesMetaData, jobID: number): Promise<FramesMetaDat
return frameMetaCache[jobID];
}

function getFrameMeta(jobID, frame): SerializedFramesMetaData['frames'][0] {
const { meta, mode, jobStartFrame } = frameDataCache[jobID];
async function getFrameMeta(jobID, frame): Promise<SerializedFramesMetaData['frames'][0]> {
const { mode, jobStartFrame } = frameDataCache[jobID];
const meta = await frameDataCache[jobID].getMeta();
let frameMeta = null;
if (mode === 'interpolation' && meta.frames.length === 1) {
// video tasks have 1 frame info, but image tasks will have many infos
Expand All @@ -616,12 +667,12 @@ async function refreshJobCacheIfOutdated(jobID: number): Promise<void> {

if (isOutdated) {
// get metadata again if outdated
const prevMeta = await cached.getMeta();
const meta = await getFramesMeta('job', jobID, true);
if (new Date(meta.chunksUpdatedDate) > new Date(cached.meta.chunksUpdatedDate)) {
if (new Date(meta.chunksUpdatedDate) > new Date(prevMeta.chunksUpdatedDate)) {
// chunks were re-defined. Existing data not relevant anymore
// currently we only re-write meta, remove all cached frames from provider and clear cached context images
// other parameters (e.g. chunkSize) are not supposed to be changed
cached.meta = meta;
cached.provider.cleanup(Number.MAX_SAFE_INTEGER);
for (const frame of Object.keys(cached.contextCache)) {
for (const image of Object.values(cached.contextCache[+frame].data)) {
Expand All @@ -636,19 +687,19 @@ async function refreshJobCacheIfOutdated(jobID: number): Promise<void> {
}
}

export function getContextImage(jobID: number, frame: number): Promise<Record<string, ImageBitmap>> {
export async function getContextImage(jobID: number, frame: number): Promise<Record<string, ImageBitmap>> {
const frameData = frameDataCache[jobID];
const meta = await frameData.getMeta();
const requestId = frame;
const { jobStartFrame } = frameData;
const { related_files: relatedFiles } = meta.frames[frame - jobStartFrame];
return new Promise<Record<string, ImageBitmap>>((resolve, reject) => {
if (!(jobID in frameDataCache)) {
reject(new Error(
'Frame data was not initialized for this job. Try first requesting any frame.',
));
}

const frameData = frameDataCache[jobID];
const requestId = frame;
const { jobStartFrame } = frameData;
const { related_files: relatedFiles } = frameData.meta.frames[frame - jobStartFrame];

if (relatedFiles === 0) {
resolve({});
} else if (frame in frameData.contextCache) {
Expand Down Expand Up @@ -761,7 +812,6 @@ export async function getFrame(
);

frameDataCache[jobID] = {
meta,
metaFetchedTimestamp: Date.now(),
chunkSize,
mode,
Expand All @@ -784,6 +834,13 @@ export async function getFrame(
latestContextImagesRequest: null,
contextCache: {},
getChunk,
getMeta: () => {
const cached = frameMetaCache[jobID];
if (!(cached instanceof Promise)) {
throw new Error('Frame meta data is not initialized');
}
return cached;
},
};
}

Expand All @@ -803,25 +860,27 @@ export async function getFrame(
// Thus, it is better to only call `refreshJobCacheIfOutdated` from getFrame()
await refreshJobCacheIfOutdated(jobID);

const frameMeta = getFrameMeta(jobID, frame);
const frameMeta = await getFrameMeta(jobID, frame);
frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height);
frameDataCache[jobID].decodeForward = isPlaying;
frameDataCache[jobID].forwardStep = step;

const meta = await frameDataCache[jobID].getMeta();

return new FrameData({
width: frameMeta.width,
height: frameMeta.height,
name: frameMeta.name,
related_files: frameMeta.related_files,
frameNumber: frame,
deleted: frame in frameDataCache[jobID].meta.deletedFrames,
deleted: frame in meta.deletedFrames,
jobID,
});
}

export async function getDeletedFrames(instanceType: 'job' | 'task', id: number): Promise<Record<number, boolean>> {
if (instanceType === 'job') {
const { meta } = frameDataCache[id];
const meta = await frameDataCache[id].getMeta();
return meta.deletedFrames;
}

Expand Down Expand Up @@ -900,12 +959,13 @@ export function getCachedChunks(jobID: number): number[] {
return frameDataCache[jobID].provider.cachedChunks(true);
}

export function getJobFrameNumbers(jobID: number): number[] {
export async function getJobFrameNumbers(jobID: number): Promise<number[]> {
if (!(jobID in frameDataCache)) {
return [];
}

const { meta, jobStartFrame } = frameDataCache[jobID];
const { jobStartFrame } = frameDataCache[jobID];
const meta = await frameDataCache[jobID].getMeta();
return meta.getSegmentFrameNumbers(jobStartFrame);
}

Expand Down
2 changes: 1 addition & 1 deletion cvat-core/src/session-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export function implementJob(Job: typeof JobClass): typeof JobClass {
value: function includedFramesImplementation(
this: JobClass,
): ReturnType<typeof JobClass.prototype.frames.frameNumbers> {
return Promise.resolve(getJobFrameNumbers(this.id));
return getJobFrameNumbers(this.id);
},
});

Expand Down
Empty file.
Loading