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

Added ability to search for an empty frame #2221

Merged
merged 7 commits into from
Sep 28, 2020
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Datumaro] CLI command for dataset equality comparison (<https://github.com/opencv/cvat/pull/1989>)
- [Datumaro] Merging of datasets with different labels (<https://github.com/opencv/cvat/pull/2098>)
- Add FBRS interactive segmentation serverless function (<https://github.com/openvinotoolkit/cvat/pull/2094>)
- Ability to change default behaviour of previous/next buttons of a player.
It supports regular navigation, searching a frame according to annotations
filters and searching the nearest frame without any annotations (<https://github.com/openvinotoolkit/cvat/pull/2221>)
- MacOS users notes in CONTRIBUTING.md

### Changed
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "3.7.1",
"version": "3.8.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {
Expand Down
37 changes: 37 additions & 0 deletions cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,43 @@
};
}

searchEmpty(frameFrom, frameTo) {
const sign = Math.sign(frameTo - frameFrom);
const predicate = sign > 0
? (frame) => frame <= frameTo
: (frame) => frame >= frameTo;
const update = sign > 0
? (frame) => frame + 1
: (frame) => frame - 1;
for (let frame = frameFrom; predicate(frame); frame = update(frame)) {
if (frame in this.shapes && this.shapes[frame].some((shape) => !shape.removed)) {
continue;
}
if (frame in this.tags && this.tags[frame].some((tag) => !tag.removed)) {
continue;
}
const filteredTracks = this.tracks.filter((track) => !track.removed);
let found = false;
for (const track of filteredTracks) {
const keyframes = track.boundedKeyframes(frame);
const { prev, first } = keyframes;
const last = prev === null ? first : prev;
const lastShape = track.shapes[last];
const isKeyfame = frame in track.shapes;
if (first <= frame && (!lastShape.outside || isKeyfame)) {
found = true;
break;
}
}

if (found) continue;

return frame;
}

return null;
}

search(filters, frameFrom, frameTo) {
const [groups, query] = this.annotationsFilter.toJSONQuery(filters);
const sign = Math.sign(frameTo - frameFrom);
Expand Down
4 changes: 3 additions & 1 deletion cvat-core/src/annotations-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,9 @@
}, [this.clientID], frame);
}

_appendShapeActionToHistory(actionType, frame, undoShape, redoShape, undoSource, redoSource) {
_appendShapeActionToHistory(
actionType, frame, undoShape, redoShape, undoSource, redoSource,
) {
this.history.do(actionType, () => {
if (!undoShape) {
delete this.shapes[frame];
Expand Down
14 changes: 14 additions & 0 deletions cvat-core/src/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@
);
}

function searchEmptyFrame(session, frameFrom, frameTo) {
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);

if (cache.has(session)) {
return cache.get(session).collection.searchEmpty(frameFrom, frameTo);
}

throw new DataError(
'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before',
);
}

function mergeAnnotations(session, objectStates) {
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
Expand Down Expand Up @@ -373,6 +386,7 @@
hasUnsavedChanges,
mergeAnnotations,
searchAnnotations,
searchEmptyFrame,
splitAnnotations,
groupAnnotations,
clearAnnotations,
Expand Down
8 changes: 6 additions & 2 deletions cvat-core/src/api-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,19 @@
await serverProxy.server.logout();
};

cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
cvat.server.changePassword.implementation = async (
oldPassword, newPassword1, newPassword2,
) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};

cvat.server.requestPasswordReset.implementation = async (email) => {
await serverProxy.server.requestPasswordReset(email);
};

cvat.server.resetPassword.implementation = async(newPassword1, newPassword2, uid, token) => {
cvat.server.resetPassword.implementation = async (
newPassword1, newPassword2, uid, token,
) => {
await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token);
};

Expand Down
4 changes: 3 additions & 1 deletion cvat-core/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ function build() {
*/
async changePassword(oldPassword, newPassword1, newPassword2) {
const result = await PluginRegistry
.apiWrapper(cvat.server.changePassword, oldPassword, newPassword1, newPassword2);
.apiWrapper(
cvat.server.changePassword, oldPassword, newPassword1, newPassword2,
);
return result;
},
/**
Expand Down
6 changes: 3 additions & 3 deletions cvat-core/src/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* @property {string} UNKNOWN 'unknown'
* @readonly
*/
const RQStatus = Object.freeze({
const RQStatus = Object.freeze({
QUEUED: 'queued',
STARTED: 'started',
FINISHED: 'finished',
Expand Down Expand Up @@ -134,8 +134,8 @@
* @readonly
*/
const Source = Object.freeze({
MANUAL:'manual',
AUTO:'auto',
MANUAL: 'manual',
AUTO: 'auto',
});

/**
Expand Down
68 changes: 68 additions & 0 deletions cvat-core/src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
return result;
},

async searchEmpty(frameFrom, frameTo) {
const result = await PluginRegistry
.apiWrapper.call(this, prototype.annotations.searchEmpty,
frameFrom, frameTo);
return result;
},

async select(objectStates, x, y) {
const result = await PluginRegistry
.apiWrapper.call(this,
Expand Down Expand Up @@ -347,6 +354,18 @@
* @instance
* @async
*/
/**
* Find the nearest empty frame without any annotations
* @method searchEmpty
* @memberof Session.annotations
* @param {integer} from lower bound of a search
* @param {integer} to upper bound of a search
* @returns {integer|null} a frame that contains objects according to the filter
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
* @async
*/
/**
* Select shape under a cursor by using minimal distance
* between a cursor and a shape edge or a shape point
Expand Down Expand Up @@ -746,6 +765,7 @@
group: Object.getPrototypeOf(this).annotations.group.bind(this),
clear: Object.getPrototypeOf(this).annotations.clear.bind(this),
search: Object.getPrototypeOf(this).annotations.search.bind(this),
searchEmpty: Object.getPrototypeOf(this).annotations.searchEmpty.bind(this),
upload: Object.getPrototypeOf(this).annotations.upload.bind(this),
select: Object.getPrototypeOf(this).annotations.select.bind(this),
import: Object.getPrototypeOf(this).annotations.import.bind(this),
Expand Down Expand Up @@ -1318,6 +1338,7 @@
group: Object.getPrototypeOf(this).annotations.group.bind(this),
clear: Object.getPrototypeOf(this).annotations.clear.bind(this),
search: Object.getPrototypeOf(this).annotations.search.bind(this),
searchEmpty: Object.getPrototypeOf(this).annotations.searchEmpty.bind(this),
upload: Object.getPrototypeOf(this).annotations.upload.bind(this),
select: Object.getPrototypeOf(this).annotations.select.bind(this),
import: Object.getPrototypeOf(this).annotations.import.bind(this),
Expand Down Expand Up @@ -1411,6 +1432,7 @@
saveAnnotations,
hasUnsavedChanges,
searchAnnotations,
searchEmptyFrame,
mergeAnnotations,
splitAnnotations,
groupAnnotations,
Expand Down Expand Up @@ -1537,6 +1559,29 @@
return result;
};

Job.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) {
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError(
'The start and end frames both must be an integer',
);
}

if (frameFrom < this.startFrame || frameFrom > this.stopFrame) {
throw new ArgumentError(
'The start frame is out of the job',
);
}

if (frameTo < this.startFrame || frameTo > this.stopFrame) {
throw new ArgumentError(
'The stop frame is out of the job',
);
}

const result = searchEmptyFrame(this, frameFrom, frameTo);
return result;
};

Job.prototype.annotations.save.implementation = async function (onUpdate) {
const result = await saveAnnotations(this, onUpdate);
return result;
Expand Down Expand Up @@ -1807,6 +1852,29 @@
return result;
};

Task.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) {
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {
throw new ArgumentError(
'The start and end frames both must be an integer',
);
}

if (frameFrom < 0 || frameFrom >= this.size) {
throw new ArgumentError(
'The start frame is out of the task',
);
}

if (frameTo < 0 || frameTo >= this.size) {
throw new ArgumentError(
'The stop frame is out of the task',
);
}

const result = searchEmptyFrame(this, frameFrom, frameTo);
return result;
};

Task.prototype.annotations.save.implementation = async function (onUpdate) {
const result = await saveAnnotations(this, onUpdate);
return result;
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/src/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
* @readonly
* @instance
*/
get: () => !data.email_verification_required,
get: () => !data.email_verification_required,
},
}));
}
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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": "1.9.7",
"version": "1.9.8",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
23 changes: 23 additions & 0 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export enum AnnotationActionTypes {
SWITCH_Z_LAYER = 'SWITCH_Z_LAYER',
ADD_Z_LAYER = 'ADD_Z_LAYER',
SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED',
SEARCH_EMPTY_FRAME_FAILED = 'SEARCH_EMPTY_FRAME_FAILED',
CHANGE_WORKSPACE = 'CHANGE_WORKSPACE',
SAVE_LOGS_SUCCESS = 'SAVE_LOGS_SUCCESS',
SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED',
Expand Down Expand Up @@ -1331,6 +1332,28 @@ export function searchAnnotationsAsync(
};
}

export function searchEmptyFrameAsync(
sessionInstance: any,
frameFrom: number,
frameTo: number,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const frame = await sessionInstance.annotations.searchEmpty(frameFrom, frameTo);
if (frame !== null) {
dispatch(changeFrameAsync(frame));
}
} catch (error) {
dispatch({
type: AnnotationActionTypes.SEARCH_EMPTY_FRAME_FAILED,
payload: {
error,
},
});
}
};
}

export function pasteShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
Expand Down
3 changes: 3 additions & 0 deletions cvat-ui/src/assets/next-empty-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions cvat-ui/src/assets/next-filtered-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions cvat-ui/src/assets/previous-empty-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions cvat-ui/src/assets/previous-filtered-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions cvat-ui/src/components/annotation-page/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,20 @@
}
}
}

.cvat-player-previous-inlined-button,
.cvat-player-next-inlined-button,
.cvat-player-previous-filtered-inlined-button,
.cvat-player-next-filtered-inlined-button,
.cvat-player-previous-empty-inlined-button,
.cvat-player-next-empty-inlined-button {
color: $player-buttons-color;

&:not(:first-child) {
margin-left: 12px;
}

> svg {
transform: scale(1.8);
}
}
Loading