Annotations filters new UI (#2871)

* Filters new UI implemented

* Build fix

* ESLint unmached pattern error fix

* ESLint unmached pattern error fix in cvat-ui

* ESLint unmached pattern error fix in github action workflow

* Old test exclude from jest scope

* Build fix

* Build fix 1

* Build fix 2

* Tests failure fix

* Review comments fix 1 and lock-hide test fix

* lock-hide test fix

* packages fix for cor & ui

* Review comments fix

* Top bar right group layout justify fix

* Annotation page header responsive fix

* Filters modal layout fix

* Build fix. E2E case 13 workaround

* Linters fix

* Recently used empty rows fix

* Comparable fields config fix

* Build linters fix

* Minor fixes

* Fixed broken navigation

* Fixed createObjectURL

* Removed extra import

* Fixed issues with attributes

* Extra line were removed

* Fixed typos

* All renamed clientID -> objectID

* Fixed small issues

* Code refactoring

* Fixed dot-contained names

* Reordered import

Co-authored-by: Boris Sekachev <boris.sekachev@intel.com>
main
Vitaliy Nishukov 5 years ago committed by GitHub
parent ce1666f6f8
commit 11d818dac2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Backup/Restore guide](cvat/apps/documentation/backup_guide.md) (<https://github.com/openvinotoolkit/cvat/pull/2964>) - [Backup/Restore guide](cvat/apps/documentation/backup_guide.md) (<https://github.com/openvinotoolkit/cvat/pull/2964>)
- Label deletion from tasks and projects (<https://github.com/openvinotoolkit/cvat/pull/2881>) - Label deletion from tasks and projects (<https://github.com/openvinotoolkit/cvat/pull/2881>)
- [Market-1501](https://www.aitribune.com/dataset/2018051063) format support (<https://github.com/openvinotoolkit/cvat/pull/2869>) - [Market-1501](https://www.aitribune.com/dataset/2018051063) format support (<https://github.com/openvinotoolkit/cvat/pull/2869>)
- Annotations filters UI using react-awesome-query-builder (https://github.com/openvinotoolkit/cvat/issues/1418)
### Changed ### Changed

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.11.0", "version": "3.12.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -5051,8 +5051,8 @@
"cvat-data": { "cvat-data": {
"version": "file:../cvat-data", "version": "file:../cvat-data",
"requires": { "requires": {
"async-mutex": "^0.2.6", "async-mutex": "^0.3.0",
"jszip": "3.5.0" "jszip": "3.6.0"
}, },
"dependencies": { "dependencies": {
"@babel/cli": { "@babel/cli": {
@ -14078,7 +14078,8 @@
"ini": { "ini": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
}, },
"inquirer": { "inquirer": {
"version": "6.5.2", "version": "6.5.2",
@ -18342,6 +18343,11 @@
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
}, },
"json-logic-js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json-logic-js/-/json-logic-js-2.0.0.tgz",
"integrity": "sha512-cQBDOXgFtFladCg99wnQ7YfN+nv1+Sznj4K6bp3CTgDJNJKgEXJE2VCXzVBjEU2e1UagDHSek52IQk5Ha38n7Q=="
},
"json-parse-better-errors": { "json-parse-better-errors": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@ -18397,23 +18403,6 @@
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
"dev": true "dev": true
}, },
"jsonpath": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.0.tgz",
"integrity": "sha512-CZHwa1sZOf42qkIyK7evwToFXeTB4iplbl6Z9CVwU0wmBQPffL6gtYJXCoeciJoZENMuzaidPjhp2iOLRei4wQ==",
"requires": {
"esprima": "1.2.2",
"static-eval": "2.0.2",
"underscore": "1.7.0"
},
"dependencies": {
"esprima": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz",
"integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs="
}
}
},
"jsonpointer": { "jsonpointer": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz",

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.11.0", "version": "3.12.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration", "description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js", "main": "babel.config.js",
"scripts": { "scripts": {
@ -40,8 +40,8 @@
"error-stack-parser": "^2.0.2", "error-stack-parser": "^2.0.2",
"form-data": "^2.5.0", "form-data": "^2.5.0",
"jest-config": "^26.6.3", "jest-config": "^26.6.3",
"json-logic-js": "^2.0.0",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"jsonpath": "^1.1.0",
"platform": "^1.3.5", "platform": "^1.3.5",
"quickhull": "^1.0.3", "quickhull": "^1.0.3",
"store": "^2.0.12", "store": "^2.0.12",

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation // Copyright (C) 2019-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -213,13 +213,9 @@
visible.data.push(stateData); visible.data.push(stateData);
} }
const [, query] = this.annotationsFilter.toJSONQuery(filters);
let filtered = [];
if (filters.length) {
filtered = this.annotationsFilter.filter(visible.data, query);
}
const objectStates = []; const objectStates = [];
const filtered = this.annotationsFilter.filter(visible.data, filters);
visible.data.forEach((stateData, idx) => { visible.data.forEach((stateData, idx) => {
if (!filters.length || filtered.includes(stateData.clientID)) { if (!filters.length || filtered.includes(stateData.clientID)) {
const model = visible.models[idx]; const model = visible.models[idx];
@ -777,6 +773,7 @@
} }
// Add constructed objects to a collection // Add constructed objects to a collection
// eslint-disable-next-line no-unsanitized/method
const imported = this.import(constructed); const imported = this.import(constructed);
const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes); const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes);
@ -865,13 +862,9 @@
} }
search(filters, frameFrom, frameTo) { search(filters, frameFrom, frameTo) {
const [groups, query] = this.annotationsFilter.toJSONQuery(filters);
const sign = Math.sign(frameTo - frameFrom); const sign = Math.sign(frameTo - frameFrom);
const filtersStr = JSON.stringify(filters);
const flattenedQuery = groups.flat(Number.MAX_SAFE_INTEGER); const containsDifficultProperties = filtersStr.match(/"var":"width"/) || filtersStr.match(/"var":"height"/);
const containsDifficultProperties = flattenedQuery.some(
(fragment) => fragment.match(/^width/) || fragment.match(/^height/),
);
const deepSearch = (deepSearchFrom, deepSearchTo) => { const deepSearch = (deepSearchFrom, deepSearchTo) => {
// deepSearchFrom is expected to be a frame that doesn't satisfy a filter // deepSearchFrom is expected to be a frame that doesn't satisfy a filter
@ -882,7 +875,7 @@
while (!(Math.abs(prev - next) === 1)) { while (!(Math.abs(prev - next) === 1)) {
const middle = next + Math.floor((prev - next) / 2); const middle = next + Math.floor((prev - next) / 2);
const shapesData = this.tracks.map((track) => track.get(middle)); const shapesData = this.tracks.map((track) => track.get(middle));
const filtered = this.annotationsFilter.filter(shapesData, query); const filtered = this.annotationsFilter.filter(shapesData, filters);
if (filtered.length) { if (filtered.length) {
next = middle; next = middle;
} else { } else {
@ -919,7 +912,7 @@
} }
// Filtering // Filtering
const filtered = this.annotationsFilter.filter(statesData, query); const filtered = this.annotationsFilter.filter(statesData, filters);
// Now we are checking whether we need deep search or not // Now we are checking whether we need deep search or not
// Deep search is needed in some difficult cases // Deep search is needed in some difficult cases

@ -1,143 +1,15 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
const jsonpath = require('jsonpath'); const jsonLogic = require('json-logic-js');
const { AttributeType, ObjectType } = require('./enums'); const { AttributeType, ObjectType } = require('./enums');
const { ArgumentError } = require('./exceptions');
class AnnotationsFilter { function adjustName(name) {
constructor() { return name.replaceAll('.', '\u2219');
// eslint-disable-next-line security/detect-unsafe-regex }
this.operatorRegex = /(==|!=|<=|>=|>|<)(?=(?:[^"]*(["])[^"]*\2)*[^"]*$)/g;
}
// Method splits expression by operators that are outside of any brackets
_splitWithOperator(container, expression) {
const operators = ['|', '&'];
const splitted = [];
let nestedCounter = 0;
let isQuotes = false;
let start = -1;
for (let i = 0; i < expression.length; i++) {
if (expression[i] === '"') {
// all quotes inside other quotes must
// be escaped by a user and changed to ` above
isQuotes = !isQuotes;
}
// We don't split with operator inside brackets
// It will be done later in recursive call
if (!isQuotes && expression[i] === '(') {
nestedCounter++;
}
if (!isQuotes && expression[i] === ')') {
nestedCounter--;
}
if (operators.includes(expression[i])) {
if (!nestedCounter) {
const subexpression = expression.substr(start + 1, i - start - 1).trim();
splitted.push(subexpression);
splitted.push(expression[i]);
start = i;
}
}
}
const subexpression = expression.substr(start + 1).trim();
splitted.push(subexpression);
splitted.forEach((internalExpression) => {
if (internalExpression === '|' || internalExpression === '&') {
container.push(internalExpression);
} else {
this._groupByBrackets(container, internalExpression);
}
});
}
// Method groups bracket containings to nested arrays of container
_groupByBrackets(container, expression) {
if (!(expression.startsWith('(') && expression.endsWith(')'))) {
container.push(expression);
}
let nestedCounter = 0;
let startBracket = null;
let endBracket = null;
let isQuotes = false;
for (let i = 0; i < expression.length; i++) {
if (expression[i] === '"') {
// all quotes inside other quotes must
// be escaped by a user and changed to ` above
isQuotes = !isQuotes;
}
if (!isQuotes && expression[i] === '(') {
nestedCounter++;
if (startBracket === null) {
startBracket = i;
}
}
if (!isQuotes && expression[i] === ')') {
nestedCounter--;
if (!nestedCounter) {
endBracket = i;
const subcontainer = [];
const subexpression = expression.substr(startBracket + 1, endBracket - 1 - startBracket);
this._splitWithOperator(subcontainer, subexpression);
container.push(subcontainer);
startBracket = null;
endBracket = null;
}
}
}
if (startBracket !== null) {
throw Error('Extra opening bracket found');
}
if (endBracket !== null) {
throw Error('Extra closing bracket found');
}
}
_parse(expression) {
const groups = [];
this._splitWithOperator(groups, expression);
}
_join(groups) {
let expression = '';
for (const group of groups) {
if (Array.isArray(group)) {
expression += `(${this._join(group)})`;
} else if (typeof group === 'string') {
// it can be operator or expression
if (group === '|' || group === '&') {
expression += group;
} else {
let [field, operator, , value] = group.split(this.operatorRegex);
field = `@.${field.trim()}`;
operator = operator.trim();
value = value.trim();
if (value === 'width' || value === 'height' || value.startsWith('attr')) {
value = `@.${value}`;
}
expression += [field, operator, value].join('');
}
}
}
return expression;
}
class AnnotationsFilter {
_convertObjects(statesData) { _convertObjects(statesData) {
const objects = statesData.map((state) => { const objects = statesData.map((state) => {
const labelAttributes = state.label.attributes.reduce((acc, attr) => { const labelAttributes = state.label.attributes.reduce((acc, attr) => {
@ -169,63 +41,38 @@ class AnnotationsFilter {
const attributes = {}; const attributes = {};
Object.keys(state.attributes).reduce((acc, key) => { Object.keys(state.attributes).reduce((acc, key) => {
const attr = labelAttributes[key]; const attr = labelAttributes[key];
let value = state.attributes[key].replace(/\\"/g, '`'); let value = state.attributes[key];
if (attr.inputType === AttributeType.NUMBER) { if (attr.inputType === AttributeType.NUMBER) {
value = +value; value = +value;
} else if (attr.inputType === AttributeType.CHECKBOX) { } else if (attr.inputType === AttributeType.CHECKBOX) {
value = value === 'true'; value = value === 'true';
} }
acc[attr.name] = value; acc[adjustName(attr.name)] = value;
return acc; return acc;
}, attributes); }, attributes);
return { return {
width, width,
height, height,
attr: attributes, attr: Object.fromEntries([[adjustName(state.label.name), attributes]]),
label: state.label.name.replace(/\\"/g, '`'), label: state.label.name,
serverID: state.serverID, serverID: state.serverID,
clientID: state.clientID, objectID: state.clientID,
type: state.objectType, type: state.objectType,
shape: state.shapeType, shape: state.shapeType,
occluded: state.occluded, occluded: state.occluded,
}; };
}); });
return { return objects;
objects,
};
}
toJSONQuery(filters) {
try {
if (!Array.isArray(filters) || filters.some((value) => typeof value !== 'string')) {
throw Error('Argument must be an array of strings');
}
if (!filters.length) {
return [[], '$.objects[*].clientID'];
}
const groups = [];
const expression = filters
.map((filter) => `(${filter})`)
.join('|')
.replace(/\\"/g, '`');
this._splitWithOperator(groups, expression);
return [groups, `$.objects[?(${this._join(groups)})].clientID`];
} catch (error) {
throw new ArgumentError(`Wrong filter expression. ${error.toString()}`);
}
} }
filter(statesData, query) { filter(statesData, filters) {
try { if (!filters.length) return statesData;
const objects = this._convertObjects(statesData); const converted = this._convertObjects(statesData);
return jsonpath.query(objects, query); return converted
} catch (error) { .map((state) => state.objectID)
throw new ArgumentError(`Could not apply the filter. ${error.toString()}`); .filter((_, index) => jsonLogic.apply(filters[0], converted[index]));
}
} }
} }

@ -379,7 +379,7 @@
* @param {integer} frame get objects from the frame * @param {integer} frame get objects from the frame
* @param {boolean} allTracks show all tracks * @param {boolean} allTracks show all tracks
* even if they are outside and not keyframe * even if they are outside and not keyframe
* @param {string[]} [filters = []] * @param {any[]} [filters = []]
* get only objects that satisfied to specific filters * get only objects that satisfied to specific filters
* @returns {module:API.cvat.classes.ObjectState[]} * @returns {module:API.cvat.classes.ObjectState[]}
* @memberof Session.annotations * @memberof Session.annotations
@ -1743,8 +1743,8 @@
// TODO: Check filter for annotations // TODO: Check filter for annotations
Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) {
if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) { if (!Array.isArray(filters)) {
throw new ArgumentError('The filters argument must be an array of strings'); throw new ArgumentError('Filters must be an array');
} }
if (!Number.isInteger(frame)) { if (!Number.isInteger(frame)) {
@ -1760,8 +1760,8 @@
}; };
Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) {
if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) { if (!Array.isArray(filters)) {
throw new ArgumentError('The filters argument must be an array of strings'); throw new ArgumentError('Filters must be an array');
} }
if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) {

@ -1,121 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
// Setup mock for a server
jest.mock('../../src/server-proxy', () => {
const mock = require('../mocks/server-proxy.mock');
return mock;
});
const AnnotationsFilter = require('../../src/annotations-filter');
// Initialize api
window.cvat = require('../../src/api');
// Test cases
describe('Feature: toJSONQuery', () => {
test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery([]);
expect(Array.isArray(groups)).toBeTruthy();
expect(typeof query).toBe('string');
});
test('convert empty fitlers to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [, query] = annotationsFilter.toJSONQuery([]);
expect(query).toBe('$.objects[*].clientID');
});
test('convert wrong fitlers (empty string) to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
expect(() => {
annotationsFilter.toJSONQuery(['']);
}).toThrow(window.cvat.exceptions.ArgumentError);
});
test('convert wrong fitlers (wrong number argument) to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
expect(() => {
annotationsFilter.toJSONQuery(1);
}).toThrow(window.cvat.exceptions.ArgumentError);
});
test('convert wrong fitlers (wrong array argument) to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
expect(() => {
annotationsFilter.toJSONQuery(['clientID ==6', 1]);
}).toThrow(window.cvat.exceptions.ArgumentError);
});
test('convert wrong filters (wrong expression) to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
expect(() => {
annotationsFilter.toJSONQuery(['clientID=5']);
}).toThrow(window.cvat.exceptions.ArgumentError);
});
test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery(['clientID==5 & shape=="rectangle" & label==["car"]']);
expect(groups).toEqual([['clientID==5', '&', 'shape=="rectangle"', '&', 'label==["car"]']]);
expect(query).toBe('$.objects[?((@.clientID==5&@.shape=="rectangle"&@.label==["car"]))].clientID');
});
test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery(['label=="car" | width >= height & type=="track"']);
expect(groups).toEqual([['label=="car"', '|', 'width >= height', '&', 'type=="track"']]);
expect(query).toBe('$.objects[?((@.label=="car"|@.width>=@.height&@.type=="track"))].clientID');
});
test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery([
'label=="person" & attr["Attribute 1"] ==attr["Attribute 2"]',
]);
expect(groups).toEqual([['label=="person"', '&', 'attr["Attribute 1"] ==attr["Attribute 2"]']]);
expect(query).toBe('$.objects[?((@.label=="person"&@.attr["Attribute 1"]==@.attr["Attribute 2"]))].clientID');
});
test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery([
'label=="car" & attr["parked"]==true',
'label=="pedestrian" & width > 150',
]);
expect(groups).toEqual([
['label=="car"', '&', 'attr["parked"]==true'],
'|',
['label=="pedestrian"', '&', 'width > 150'],
]);
expect(query).toBe(
'$.objects[?((@.label=="car"&@.attr["parked"]==true)|(@.label=="pedestrian"&@.width>150))].clientID',
);
});
test('convert filters to a json query', () => {
const annotationsFilter = new AnnotationsFilter();
const [groups, query] = annotationsFilter.toJSONQuery([
// eslint-disable-next-line
'(( label==["car \\"mazda\\""]) & (attr["sunglass ( help ) es"]==true | (width > 150 | height > 150 & (clientID == serverID))))) ',
]);
expect(groups).toEqual([
[
[
['label==["car `mazda`"]'],
'&',
[
'attr["sunglass ( help ) es"]==true',
'|',
['width > 150', '|', 'height > 150', '&', ['clientID == serverID']],
],
],
],
]);
expect(query).toBe(
// eslint-disable-next-line
'$.objects[?((((@.label==["car `mazda`"])&(@.attr["sunglass ( help ) es"]==true|(@.width>150|@.height>150&(@.clientID==serverID))))))].clientID',
);
});
});

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.15.5", "version": "1.16.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1094,6 +1094,19 @@
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
}, },
"@date-io/core": {
"version": "1.3.13",
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz",
"integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA=="
},
"@date-io/moment": {
"version": "1.3.13",
"resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.3.13.tgz",
"integrity": "sha512-3kJYusJtQuOIxq6byZlzAHoW/18iExJer9qfRF5DyyzdAk074seTuJfdofjz4RFfTd/Idk8WylOQpWtERqvFuQ==",
"requires": {
"@date-io/core": "^1.3.13"
}
},
"@eslint/eslintrc": { "@eslint/eslintrc": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
@ -3382,6 +3395,11 @@
} }
} }
}, },
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
"clone-deep": { "clone-deep": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@ -12923,7 +12941,7 @@
"form-data": "^2.5.0", "form-data": "^2.5.0",
"jest-config": "^26.6.3", "jest-config": "^26.6.3",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"jsonpath": "^1.1.0", "json-logic-js": "^2.0.0",
"platform": "^1.3.5", "platform": "^1.3.5",
"quickhull": "^1.0.3", "quickhull": "^1.0.3",
"store": "^2.0.12", "store": "^2.0.12",
@ -15410,7 +15428,8 @@
"cvat-data": { "cvat-data": {
"version": "file:../cvat-data", "version": "file:../cvat-data",
"requires": { "requires": {
"jszip": "3.5.0" "async-mutex": "^0.3.1",
"jszip": "3.6.0"
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
@ -15430,11 +15449,11 @@
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ=="
}, },
"async-mutex": { "async-mutex": {
"version": "0.2.6", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.1.tgz",
"integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", "integrity": "sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.1.0"
} }
}, },
"big.js": { "big.js": {
@ -15491,9 +15510,9 @@
} }
}, },
"jszip": { "jszip": {
"version": "3.5.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz",
"integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==",
"requires": { "requires": {
"lie": "~3.3.0", "lie": "~3.3.0",
"pako": "~1.0.2", "pako": "~1.0.2",
@ -18357,6 +18376,11 @@
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
}, },
"json-logic-js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json-logic-js/-/json-logic-js-2.0.0.tgz",
"integrity": "sha512-cQBDOXgFtFladCg99wnQ7YfN+nv1+Sznj4K6bp3CTgDJNJKgEXJE2VCXzVBjEU2e1UagDHSek52IQk5Ha38n7Q=="
},
"json-parse-even-better-errors": { "json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@ -24749,6 +24773,11 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true "dev": true
}, },
"immutable": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
},
"import-cwd": { "import-cwd": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@ -28860,6 +28889,23 @@
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"react-awesome-query-builder": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-awesome-query-builder/-/react-awesome-query-builder-3.0.0.tgz",
"integrity": "sha512-EWnbqbLZUqpR60fypLQBuko++2JOQkDzqtszZlVWC6IXIyrYM2GOgP+YdQod01SKIV1QWNCyUWjQlTOCVt96FA==",
"requires": {
"@date-io/moment": "^1.3.13",
"classnames": "^2.2.5",
"clone": "^2.1.1",
"immutable": "^3.7.6",
"lodash": "^4.17.20",
"moment": "^2.28.0",
"prop-types": "^15.6.0",
"react-redux": "^7.2.1",
"redux": "^4.0.5",
"sqlstring": "^2.3.1"
}
},
"react-color": { "react-color": {
"version": "2.19.3", "version": "2.19.3",
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz",
@ -30324,6 +30370,11 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true "dev": true
}, },
"sqlstring": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz",
"integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg=="
},
"sshpk": { "sshpk": {
"version": "1.16.1", "version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.15.5", "version": "1.16.0",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {
@ -73,6 +73,7 @@
"platform": "^1.3.6", "platform": "^1.3.6",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.14.0", "react": "^16.14.0",
"react-awesome-query-builder": "^3.0.0",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",

@ -4,30 +4,28 @@
import { MutableRefObject } from 'react'; import { MutableRefObject } from 'react';
import { import {
AnyAction, Dispatch, ActionCreator, Store, ActionCreator, AnyAction, Dispatch, Store,
} from 'redux'; } from 'redux';
import { ThunkAction } from 'utils/redux'; import { ThunkAction } from 'utils/redux';
import { RectDrawingMethod } from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper';
import logger, { LogType } from 'cvat-logger';
import { getCVATStore } from 'cvat-store';
import { import {
CombinedState,
ActiveControl, ActiveControl,
ShapeType, CombinedState,
ObjectType,
Task,
FrameSpeed,
Rotation,
ContextMenuType, ContextMenuType,
Workspace,
Model,
DimensionType, DimensionType,
FrameSpeed,
Model,
ObjectType,
OpenCVTool, OpenCVTool,
Rotation,
ShapeType,
Task,
Workspace,
} from 'reducers/interfaces'; } from 'reducers/interfaces';
import getCore from 'cvat-core-wrapper';
import logger, { LogType } from 'cvat-logger';
import { RectDrawingMethod } from 'cvat-canvas-wrapper';
import { getCVATStore } from 'cvat-store';
interface AnnotationsParameters { interface AnnotationsParameters {
filters: string[]; filters: string[];
frame: number; frame: number;
@ -161,6 +159,7 @@ export enum AnnotationActionTypes {
PROPAGATE_OBJECT_FAILED = 'PROPAGATE_OBJECT_FAILED', PROPAGATE_OBJECT_FAILED = 'PROPAGATE_OBJECT_FAILED',
CHANGE_PROPAGATE_FRAMES = 'CHANGE_PROPAGATE_FRAMES', CHANGE_PROPAGATE_FRAMES = 'CHANGE_PROPAGATE_FRAMES',
SWITCH_SHOWING_STATISTICS = 'SWITCH_SHOWING_STATISTICS', SWITCH_SHOWING_STATISTICS = 'SWITCH_SHOWING_STATISTICS',
SWITCH_SHOWING_FILTERS = 'SWITCH_SHOWING_FILTERS',
COLLECT_STATISTICS = 'COLLECT_STATISTICS', COLLECT_STATISTICS = 'COLLECT_STATISTICS',
COLLECT_STATISTICS_SUCCESS = 'COLLECT_STATISTICS_SUCCESS', COLLECT_STATISTICS_SUCCESS = 'COLLECT_STATISTICS_SUCCESS',
COLLECT_STATISTICS_FAILED = 'COLLECT_STATISTICS_FAILED', COLLECT_STATISTICS_FAILED = 'COLLECT_STATISTICS_FAILED',
@ -276,24 +275,10 @@ export function fetchAnnotationsAsync(): ThunkAction {
}; };
} }
export function changeAnnotationsFilters(filters: string[]): AnyAction { export function changeAnnotationsFilters(filters: any[]): AnyAction {
const state: CombinedState = getStore().getState();
const { filtersHistory, filters: oldFilters } = state.annotation.annotations;
filters.forEach((element: string) => {
if (!(filtersHistory.includes(element) || oldFilters.includes(element))) {
filtersHistory.push(element);
}
});
window.localStorage.setItem('filtersHistory', JSON.stringify(filtersHistory.slice(-10)));
return { return {
type: AnnotationActionTypes.CHANGE_ANNOTATIONS_FILTERS, type: AnnotationActionTypes.CHANGE_ANNOTATIONS_FILTERS,
payload: { payload: { filters },
filters,
filtersHistory: filtersHistory.slice(-10),
},
}; };
} }
@ -443,6 +428,14 @@ export function showStatistics(visible: boolean): AnyAction {
}, },
}; };
} }
export function showFilters(visible: boolean): AnyAction {
return {
type: AnnotationActionTypes.SWITCH_SHOWING_FILTERS,
payload: {
visible,
},
};
}
export function propagateObjectAsync(sessionInstance: any, objectState: any, from: number, to: number): ThunkAction { export function propagateObjectAsync(sessionInstance: any, objectState: any, from: number, to: number): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
@ -876,7 +869,7 @@ export function closeJob(): ThunkAction {
}; };
} }
export function getJobAsync(tid: number, jid: number, initialFrame: number, initialFilters: string[]): ThunkAction { export function getJobAsync(tid: number, jid: number, initialFrame: number, initialFilters: object[]): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();

@ -2,25 +2,26 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import './styles.scss';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import Layout from 'antd/lib/layout'; import Layout from 'antd/lib/layout';
import Spin from 'antd/lib/spin';
import Result from 'antd/lib/result'; import Result from 'antd/lib/result';
import Spin from 'antd/lib/spin';
import notification from 'antd/lib/notification'; import notification from 'antd/lib/notification';
import { Workspace } from 'reducers/interfaces';
import { usePrevious } from 'utils/hooks';
import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar';
import StatisticsModalContainer from 'containers/annotation-page/top-bar/statistics-modal';
import StandardWorkspaceComponent from 'components/annotation-page/standard-workspace/standard-workspace';
import AttributeAnnotationWorkspace from 'components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace'; import AttributeAnnotationWorkspace from 'components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace';
import TagAnnotationWorkspace from 'components/annotation-page/tag-annotation-workspace/tag-annotation-workspace';
import ReviewAnnotationsWorkspace from 'components/annotation-page/review-workspace/review-workspace';
import SubmitAnnotationsModal from 'components/annotation-page/request-review-modal'; import SubmitAnnotationsModal from 'components/annotation-page/request-review-modal';
import ReviewAnnotationsWorkspace from 'components/annotation-page/review-workspace/review-workspace';
import SubmitReviewModal from 'components/annotation-page/review/submit-review-modal'; import SubmitReviewModal from 'components/annotation-page/review/submit-review-modal';
import StandardWorkspaceComponent from 'components/annotation-page/standard-workspace/standard-workspace';
import StandardWorkspace3DComponent from 'components/annotation-page/standard3D-workspace/standard3D-workspace'; import StandardWorkspace3DComponent from 'components/annotation-page/standard3D-workspace/standard3D-workspace';
import TagAnnotationWorkspace from 'components/annotation-page/tag-annotation-workspace/tag-annotation-workspace';
import FiltersModalContainer from 'containers/annotation-page/top-bar/filters-modal';
import StatisticsModalContainer from 'containers/annotation-page/top-bar/statistics-modal';
import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar';
import { Workspace } from 'reducers/interfaces';
import { usePrevious } from 'utils/hooks';
import './styles.scss';
interface Props { interface Props {
job: any | null | undefined; job: any | null | undefined;
@ -130,6 +131,7 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
<ReviewAnnotationsWorkspace /> <ReviewAnnotationsWorkspace />
</Layout.Content> </Layout.Content>
)} )}
<FiltersModalContainer visible={false} />
<StatisticsModalContainer /> <StatisticsModalContainer />
<SubmitAnnotationsModal /> <SubmitAnnotationsModal />
<SubmitReviewModal /> <SubmitReviewModal />

@ -1,183 +0,0 @@
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { useState } from 'react';
import { connect } from 'react-redux';
import Select, { SelectValue, LabeledValue } from 'antd/lib/select';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import Paragraph from 'antd/lib/typography/Paragraph';
import Modal from 'antd/lib/modal';
import { FilterOutlined } from '@ant-design/icons';
import {
changeAnnotationsFilters as changeAnnotationsFiltersAction,
fetchAnnotationsAsync,
} from 'actions/annotation-actions';
import CVATTooltip from 'components/common/cvat-tooltip';
import { CombinedState } from 'reducers/interfaces';
interface StateToProps {
annotationsFilters: string[];
annotationsFiltersHistory: string[];
searchForwardShortcut: string;
searchBackwardShortcut: string;
}
interface DispatchToProps {
changeAnnotationsFilters(value: SelectValue): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
annotations: { filters: annotationsFilters, filtersHistory: annotationsFiltersHistory },
},
shortcuts: { normalizedKeyMap },
} = state;
return {
annotationsFilters,
annotationsFiltersHistory,
searchForwardShortcut: normalizedKeyMap.SEARCH_FORWARD,
searchBackwardShortcut: normalizedKeyMap.SEARCH_BACKWARD,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
changeAnnotationsFilters(value: SelectValue) {
if (typeof value === 'string') {
dispatch(changeAnnotationsFiltersAction([value]));
dispatch(fetchAnnotationsAsync());
} else if (
Array.isArray(value) &&
!value.some((element: string | number | LabeledValue): boolean => typeof element !== 'string')
) {
dispatch(changeAnnotationsFiltersAction(value as string[]));
dispatch(fetchAnnotationsAsync());
}
},
};
}
function filtersHelpModalContent(searchForwardShortcut: string, searchBackwardShortcut: string): JSX.Element {
return (
<>
<Paragraph>
<Title level={3}>General</Title>
</Paragraph>
<Paragraph>
You can use filters to display only subset of objects on a frame or to search objects that satisfy the
filters using hotkeys
<Text strong>{` ${searchForwardShortcut} `}</Text>
and
<Text strong>{` ${searchBackwardShortcut} `}</Text>
</Paragraph>
<Paragraph>
<Text strong>Supported properties: </Text>
width, height, label, serverID, clientID, type, shape, occluded
<br />
<Text strong>Supported operators: </Text>
==, !=, &gt;, &gt;=, &lt;, &lt;=, (), &amp; and |
<br />
<Text strong>
If you have double quotes in your query string, please escape them using back slash: \&quot; (see
the latest example)
</Text>
<br />
All properties and values are case-sensitive. CVAT uses json queries to perform search.
</Paragraph>
<Paragraph>
<Title level={3}>Examples</Title>
<ul>
<li>label==&quot;car&quot; | label==[&quot;road sign&quot;]</li>
<li>shape == &quot;polygon&quot;</li>
<li>width &gt;= height</li>
<li>attr[&quot;Attribute 1&quot;] == attr[&quot;Attribute 2&quot;]</li>
<li>clientID == 50</li>
<li>
(label==&quot;car&quot; &amp; attr[&quot;parked&quot;]==true) | (label==&quot;pedestrian&quot;
&amp; width &gt; 150)
</li>
<li>
(( label==[&quot;car \&quot;mazda\&quot;&quot;]) &amp; (attr[&quot;sunglasses&quot;]==true |
(width &gt; 150 | height &gt; 150 &amp; (clientID == serverID)))))
</li>
</ul>
</Paragraph>
</>
);
}
function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Element {
const {
annotationsFilters,
annotationsFiltersHistory,
searchForwardShortcut,
searchBackwardShortcut,
changeAnnotationsFilters,
} = props;
const [underCursor, setUnderCursor] = useState<boolean>(false);
const [dropdownVisible, setDropdownVisible] = useState<boolean>(true);
return (
<Select
className='cvat-annotations-filters-input'
allowClear
value={annotationsFilters}
mode='tags'
disabled={!dropdownVisible}
style={{ width: '100%' }}
placeholder={
underCursor ? (
<>
<CVATTooltip title='Click to open help'>
<FilterOutlined
style={{ pointerEvents: 'all' }}
onClick={() => {
// also opens the select dropdown
// looks like it is done on capturing state
// so we cannot cancel it somehow via the event handling
// to avoid it we use also onMouseEnter, onMouseLeave below
Modal.info({
width: 700,
title: 'How to use filters?',
content: filtersHelpModalContent(searchForwardShortcut, searchBackwardShortcut),
className: 'cvat-annotations-filters-help-modal-window',
});
}}
onMouseEnter={() => setDropdownVisible(false)}
onMouseLeave={() => setDropdownVisible(true)}
/>
</CVATTooltip>
</>
) : (
<>
<FilterOutlined style={{ transform: 'scale(0.9)' }} />
<span style={{ marginLeft: 5 }}>Annotations filters</span>
</>
)
}
onChange={changeAnnotationsFilters}
onMouseEnter={() => setUnderCursor(true)}
onMouseLeave={() => setUnderCursor(false)}
>
{annotationsFiltersHistory.map(
(element: string): JSX.Element => (
<Select.Option
key={element}
value={element}
className='cvat-annotations-filters-input-history-element'
>
{element}
</Select.Option>
),
)}
</Select>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(AnnotationsFiltersInput);

@ -2,32 +2,29 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React, { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Layout, { SiderProps } from 'antd/lib/layout'; import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import { SelectValue } from 'antd/lib/select'; import { SelectValue } from 'antd/lib/select';
import { Row, Col } from 'antd/lib/grid'; import Layout, { SiderProps } from 'antd/lib/layout';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import { ThunkDispatch } from 'utils/redux';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { import {
activateObject as activateObjectAction, activateObject as activateObjectAction,
updateAnnotationsAsync,
changeFrameAsync, changeFrameAsync,
updateAnnotationsAsync,
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import { CombinedState, ObjectType } from 'reducers/interfaces'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; import { ThunkDispatch } from 'utils/redux';
import AppearanceBlock from 'components/annotation-page/appearance-block'; import AppearanceBlock from 'components/annotation-page/appearance-block';
import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons';
import { CombinedState, ObjectType } from 'reducers/interfaces';
import ObjectSwitcher from './object-switcher'; import AttributeEditor from './attribute-editor';
import AttributeSwitcher from './attribute-switcher'; import AttributeSwitcher from './attribute-switcher';
import ObjectBasicsEditor from './object-basics-edtior'; import ObjectBasicsEditor from './object-basics-edtior';
import AttributeEditor from './attribute-editor'; import ObjectSwitcher from './object-switcher';
interface StateToProps { interface StateToProps {
activatedStateID: number | null; activatedStateID: number | null;
@ -296,11 +293,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />} {sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span> </span>
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers} /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} />
<Row> <div className='cvat-sidebar-collapse-button-spacer' />
<Col span={24}>
<AnnotationsFiltersInput />
</Col>
</Row>
<ObjectSwitcher <ObjectSwitcher
currentLabel={activeObjectState.label.name} currentLabel={activeObjectState.label.name}
clientID={activeObjectState.clientID} clientID={activeObjectState.clientID}
@ -375,11 +368,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
> >
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />} {sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span> </span>
<Row> <div className='cvat-sidebar-collapse-button-spacer' />
<Col span={24}>
<AnnotationsFiltersInput />
</Col>
</Row>
<div className='attribute-annotations-sidebar-not-found-wrapper'> <div className='attribute-annotations-sidebar-not-found-wrapper'>
<Text strong>No objects found</Text> <Text strong>No objects found</Text>
</div> </div>

@ -63,3 +63,7 @@
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
} }
.cvat-sidebar-collapse-button-spacer {
height: 32px;
}

@ -231,7 +231,7 @@ export default function ItemMenu(props: Props): JSX.Element {
} = props; } = props;
return ( return (
<Menu className='cvat-object-item-menu'> <Menu className='cvat-object-item-menu' selectable={false}>
<CreateURLItem toolProps={props} /> <CreateURLItem toolProps={props} />
{!readonly && <MakeCopyItem toolProps={props} />} {!readonly && <MakeCopyItem toolProps={props} />}
{!readonly && <PropagateItem toolProps={props} />} {!readonly && <PropagateItem toolProps={props} />}

@ -3,19 +3,18 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import { import {
LockFilled,
UnlockOutlined,
EyeInvisibleFilled,
EyeOutlined,
CaretDownOutlined, CaretDownOutlined,
CaretUpFilled, CaretUpFilled,
EyeInvisibleFilled,
EyeOutlined,
LockFilled,
UnlockOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Col, Row } from 'antd/lib/grid';
import CVATTooltip from 'components/common/cvat-tooltip';
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input';
import StatesOrderingSelector from 'components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector'; import StatesOrderingSelector from 'components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector';
import CVATTooltip from 'components/common/cvat-tooltip';
import { StatesOrdering } from 'reducers/interfaces'; import { StatesOrdering } from 'reducers/interfaces';
interface Props { interface Props {
@ -85,11 +84,6 @@ function ObjectListHeader(props: Props): JSX.Element {
return ( return (
<div className='cvat-objects-sidebar-states-header'> <div className='cvat-objects-sidebar-states-header'>
<Row>
<Col span={24}>
<AnnotationsFiltersInput />
</Col>
</Row>
<Row justify='space-between' align='middle'> <Row justify='space-between' align='middle'>
{!readonly && ( {!readonly && (
<> <>

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -18,7 +18,7 @@ function StatesOrderingSelectorComponent(props: StatesOrderingSelectorComponentP
const { statesOrdering, changeStatesOrdering } = props; const { statesOrdering, changeStatesOrdering } = props;
return ( return (
<Col span={16}> <Col>
<Text strong>Sort by</Text> <Text strong>Sort by</Text>
<Select <Select
className='cvat-objects-sidebar-ordering-selector' className='cvat-objects-sidebar-ordering-selector'

@ -130,13 +130,8 @@
.cvat-objects-sidebar-states-header { .cvat-objects-sidebar-states-header {
background: $objects-bar-tabs-color; background: $objects-bar-tabs-color;
padding: 5px; padding: 5px;
height: 80px;
> div:nth-child(1) > div:nth-child(1) { > div:nth-child(1) {
height: 32px;
}
> div:nth-child(2) {
margin-top: $grid-unit-size; margin-top: $grid-unit-size;
> div { > div {
@ -161,7 +156,7 @@
.cvat-objects-sidebar-states-list { .cvat-objects-sidebar-states-list {
background-color: $background-color-2; background-color: $background-color-2;
height: calc(100% - 80px); height: calc(100% - 50px);
overflow-y: auto; overflow-y: auto;
} }

@ -18,9 +18,17 @@
border-bottom: 1px solid $border-color-1; border-bottom: 1px solid $border-color-1;
height: 54px; height: 54px;
padding: 0; padding: 0;
> div:first-child {
display: flex;
flex: 1;
flex-flow: unset;
}
} }
.cvat-annotation-header-left-group { .cvat-annotation-header-left-group {
display: flex;
> button:first-child { > button:first-child {
filter: invert(0.9); filter: invert(0.9);
background: $background-color-1; background: $background-color-1;
@ -33,7 +41,6 @@
padding: 0; padding: 0;
width: 54px; width: 54px;
height: 54px; height: 54px;
float: left;
text-align: center; text-align: center;
user-select: none; user-select: none;
color: $text-color; color: $text-color;
@ -64,6 +71,14 @@
display: block; display: block;
line-height: 0; line-height: 0;
} }
&.filters-armed {
color: $info-icon-color;
path {
fill: $info-icon-color;
}
}
} }
.cvat-annotation-disabled-header-button { .cvat-annotation-disabled-header-button {
@ -76,6 +91,7 @@
.cvat-annotation-header-player-group > div { .cvat-annotation-header-player-group > div {
height: 54px; height: 54px;
line-height: 0; line-height: 0;
flex-wrap: nowrap;
} }
.cvat-player-buttons { .cvat-player-buttons {
@ -146,8 +162,10 @@
} }
.cvat-annotation-header-right-group { .cvat-annotation-header-right-group {
display: flex;
justify-content: flex-end;
> div { > div {
float: left;
display: block; display: block;
height: 54px; height: 54px;
margin-right: 15px; margin-right: 15px;
@ -407,3 +425,28 @@
} }
} }
} }
.cvat-filters-modal {
.ant-modal-body {
padding: 1px;
.recently-used-wrapper {
padding-top: $grid-unit-size * 2;
}
.query-builder-container {
.query-builder > :first-child {
padding-right: 0;
}
.group-or-rule {
border-radius: $grid-unit-size / 4;
}
.group {
background: rgba(216, 233, 250, 0.5);
border: 1px solid #d3e0ec;
}
}
}
}

@ -0,0 +1,288 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Builder, Config, ImmutableTree, JsonLogicTree, Query, Utils as QbUtils,
} from 'react-awesome-query-builder';
import AntdWidgets from 'react-awesome-query-builder/lib/components/widgets/antd';
import AntdConfig from 'react-awesome-query-builder/lib/config/antd';
import 'react-awesome-query-builder/lib/css/styles.css';
import { DownOutlined } from '@ant-design/icons';
import { Dropdown, Menu } from 'antd';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import { omit } from 'lodash';
import { CombinedState } from 'reducers/interfaces';
import { changeAnnotationsFilters, fetchAnnotationsAsync, showFilters } from 'actions/annotation-actions';
const { FieldDropdown } = AntdWidgets;
const FILTERS_HISTORY = 'annotationFiltersHistory';
interface Props {
visible: boolean;
}
interface StoredFilter {
id: string;
logic: JsonLogicTree;
}
export default function FiltersModalComponent(props: Props): JSX.Element {
const { visible } = props;
const { labels } = useSelector((state: CombinedState) => state.annotation.job);
const { filters: activeFilters } = useSelector((state: CombinedState) => state.annotation.annotations);
const getConvertedInputType = (inputType: string): string => {
switch (inputType) {
case 'checkbox':
return 'boolean';
case 'radio':
return 'select';
default:
return inputType;
}
};
const adjustName = (name: string): string => name.replaceAll('.', '\u2219');
const getAttributesSubfields = (): Record<string, any> => {
const subfields: Record<string, any> = {};
labels.forEach((label: any): void => {
const adjustedLabelName = adjustName(label.name);
subfields[adjustedLabelName] = {
type: '!struct', // nested complex field
label: label.name,
subfields: {},
};
const labelSubfields = subfields[adjustedLabelName].subfields;
label.attributes.forEach((attr: any): void => {
const adjustedAttrName = adjustName(attr.name);
labelSubfields[adjustedAttrName] = {
label: attr.name,
type: getConvertedInputType(attr.inputType),
};
if (labelSubfields[adjustedAttrName].type === 'select') {
labelSubfields[adjustedAttrName] = {
...labelSubfields[adjustedAttrName],
fieldSettings: {
listValues: attr.values,
},
};
}
});
});
return subfields;
};
const config: Config = {
...AntdConfig,
fields: {
label: {
label: 'Label',
type: 'select',
valueSources: ['value'],
fieldSettings: {
listValues: labels.map((label: any) => label.name),
},
},
type: {
label: 'Type',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'shape', title: 'Shape' },
{ value: 'track', title: 'Track' },
{ value: 'tag', title: 'Tag' },
],
},
},
shape: {
label: 'Shape',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'rectangle', title: 'Rectangle' },
{ value: 'points', title: 'Points' },
{ value: 'polyline', title: 'Polyline' },
{ value: 'polygon', title: 'Polygon' },
{ value: 'cuboids', title: 'Cuboids' },
],
},
},
occluded: {
label: 'Occluded',
type: 'boolean',
},
width: {
label: 'Width',
type: 'number',
fieldSettings: { min: 0 },
},
height: {
label: 'Height',
type: 'number',
fieldSettings: { min: 0 },
},
objectID: {
label: 'ObjectID',
type: 'number',
hideForCompare: true,
fieldSettings: { min: 0 },
},
serverID: {
label: 'ServerID',
type: 'number',
hideForCompare: true,
fieldSettings: { min: 0 },
},
attr: {
label: 'Attributes',
type: '!struct',
subfields: getAttributesSubfields(),
fieldSettings: {
treeSelectOnlyLeafs: true,
},
},
},
settings: {
...AntdConfig.settings,
renderField: (_props: any) => (
<FieldDropdown {...omit(_props)} customProps={omit(_props.customProps, 'showSearch')} />
),
// using FieldDropdown because we cannot use antd because of antd-related bugs
// https://github.com/ukrbublik/react-awesome-query-builder/issues/224
},
};
const initialState = {
tree: QbUtils.checkTree(
QbUtils.loadTree({ id: QbUtils.uuid(), type: 'group' }),
config as Config,
) as ImmutableTree,
config,
};
const dispatch = useDispatch();
const [state, setState] = useState(initialState);
const [filters, setFilters] = useState([] as StoredFilter[]);
useEffect(() => {
const filtersHistory = window.localStorage.getItem(FILTERS_HISTORY)?.trim() || '[]';
try {
setFilters(JSON.parse(filtersHistory));
} catch (_) {
setFilters([]);
}
}, []);
useEffect(() => {
window.localStorage.setItem(FILTERS_HISTORY, JSON.stringify(filters));
}, [filters]);
useEffect(() => {
if (visible) {
const treeFromActiveFilters = activeFilters.length ?
QbUtils.checkTree(QbUtils.loadFromJsonLogic(activeFilters[0], config), config) :
null;
setState({
tree: treeFromActiveFilters || initialState.tree,
config,
});
}
}, [visible]);
const applyFilters = (filtersData: any[]): void => {
dispatch(changeAnnotationsFilters(filtersData));
dispatch(fetchAnnotationsAsync());
dispatch(showFilters(false));
};
const confirmModal = (): void => {
const currentFilter: StoredFilter = {
id: QbUtils.uuid(),
logic: QbUtils.jsonLogicFormat(state.tree, config).logic || {},
};
const updatedFilters = filters.filter(
(filter) => JSON.stringify(filter.logic) !== JSON.stringify(currentFilter.logic),
);
setFilters([currentFilter, ...updatedFilters].slice(0, 10));
applyFilters([QbUtils.jsonLogicFormat(state.tree, config).logic]);
};
const isModalConfirmable = (): boolean =>
QbUtils.queryString(state.tree, config)?.trim().length > 0 && QbUtils.isValidTree(state.tree);
const renderBuilder = (builderProps: any): JSX.Element => (
<div className='query-builder-container'>
<div className='query-builder qb-lite'>
<Builder {...builderProps} />
</div>
</div>
);
const onChange = (tree: ImmutableTree): void => {
setState({ tree, config });
};
const menu = (
<Menu>
{filters
.filter((filter: StoredFilter) => {
const tree = QbUtils.loadFromJsonLogic(filter.logic, config);
return !!QbUtils.queryString(tree, config);
})
.map((filter: StoredFilter) => {
const tree = QbUtils.loadFromJsonLogic(filter.logic, config);
return (
<Menu.Item key={filter.id} onClick={() => setState({ tree, config })}>
{QbUtils.queryString(tree, config)}
</Menu.Item>
);
})}
</Menu>
);
return (
<Modal
className='cvat-filters-modal'
visible={visible}
closable={false}
width={800}
centered
onCancel={() => dispatch(showFilters(false))}
footer={[
<Button key='clear' disabled={!activeFilters.length} onClick={() => applyFilters([])}>
Clear filters
</Button>,
<Button key='cancel' onClick={() => dispatch(showFilters(false))}>
Cancel
</Button>,
<Button key='submit' type='primary' disabled={!isModalConfirmable()} onClick={confirmModal}>
Submit
</Button>,
]}
>
<div
key='used'
className='recently-used-wrapper'
style={{ display: filters.length ? 'inline-block' : 'none' }}
>
<Dropdown overlay={menu}>
<Button type='text'>
Recently used
{' '}
<DownOutlined />
</Button>
</Dropdown>
</div>
<Query {...config} value={state.tree} onChange={onChange} renderBuilder={renderBuilder} />
</Modal>
);
}

@ -1,4 +1,4 @@
// Copyright (C) 2021 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -7,22 +7,26 @@ import { Col } from 'antd/lib/grid';
import Icon from '@ant-design/icons'; import Icon from '@ant-design/icons';
import Select from 'antd/lib/select'; import Select from 'antd/lib/select';
import Button from 'antd/lib/button'; import Button from 'antd/lib/button';
import { useSelector } from 'react-redux';
import { DimensionType, Workspace } from 'reducers/interfaces'; import { FilterIcon, FullscreenIcon, InfoIcon } from 'icons';
import { InfoIcon, FullscreenIcon } from 'icons'; import { CombinedState, DimensionType, Workspace } from 'reducers/interfaces';
interface Props { interface Props {
workspace: Workspace; workspace: Workspace;
showStatistics(): void; showStatistics(): void;
showFilters(): void;
changeWorkspace(workspace: Workspace): void; changeWorkspace(workspace: Workspace): void;
jobInstance: any; jobInstance: any;
} }
function RightGroup(props: Props): JSX.Element { function RightGroup(props: Props): JSX.Element {
const { const {
showStatistics, changeWorkspace, workspace, jobInstance, showFilters, showStatistics, changeWorkspace, workspace, jobInstance,
} = props; } = props;
const filters = useSelector((state: CombinedState) => state.annotation.annotations.filters);
return ( return (
<Col className='cvat-annotation-header-right-group'> <Col className='cvat-annotation-header-right-group'>
<Button <Button
@ -45,6 +49,14 @@ function RightGroup(props: Props): JSX.Element {
<Icon component={InfoIcon} /> <Icon component={InfoIcon} />
Info Info
</Button> </Button>
<Button
type='link'
className={`cvat-annotation-header-button ${filters.length ? 'filters-armed' : ''}`}
onClick={showFilters}
>
<Icon component={FilterIcon} />
Filters
</Button>
<div> <div>
<Select <Select
dropdownClassName='cvat-workspace-selector-dropdown' dropdownClassName='cvat-workspace-selector-dropdown'

@ -3,15 +3,14 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Input from 'antd/lib/input'; import Input from 'antd/lib/input';
import { Col, Row } from 'antd/lib/grid';
import { Workspace } from 'reducers/interfaces'; import { Workspace } from 'reducers/interfaces';
import LeftGroup from './left-group'; import LeftGroup from './left-group';
import RightGroup from './right-group';
import PlayerNavigation from './player-navigation';
import PlayerButtons from './player-buttons'; import PlayerButtons from './player-buttons';
import PlayerNavigation from './player-navigation';
import RightGroup from './right-group';
interface Props { interface Props {
playing: boolean; playing: boolean;
@ -38,6 +37,7 @@ interface Props {
focusFrameInputShortcut: string; focusFrameInputShortcut: string;
changeWorkspace(workspace: Workspace): void; changeWorkspace(workspace: Workspace): void;
showStatistics(): void; showStatistics(): void;
showFilters(): void;
onSwitchPlay(): void; onSwitchPlay(): void;
onSaveAnnotation(): void; onSaveAnnotation(): void;
onPrevFrame(): void; onPrevFrame(): void;
@ -82,6 +82,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
nextButtonType, nextButtonType,
focusFrameInputShortcut, focusFrameInputShortcut,
showStatistics, showStatistics,
showFilters,
changeWorkspace, changeWorkspace,
onSwitchPlay, onSwitchPlay,
onSaveAnnotation, onSaveAnnotation,
@ -154,6 +155,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
workspace={workspace} workspace={workspace}
changeWorkspace={changeWorkspace} changeWorkspace={changeWorkspace}
showStatistics={showStatistics} showStatistics={showStatistics}
showFilters={showFilters}
/> />
</Row> </Row>
); );

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -31,7 +31,7 @@ export default function LabelSelector(props: Props): JSX.Element {
showSearch showSearch
filterOption={(input: string, option?: OptionData | OptionGroupData) => { filterOption={(input: string, option?: OptionData | OptionGroupData) => {
if (option) { if (option) {
const { children } = option; const { children } = option.props;
if (typeof children === 'string') { if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase()); return children.toLowerCase().includes(input.toLowerCase());
} }

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -50,7 +50,7 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps {
const taskID = +params.tid; const taskID = +params.tid;
const jobID = +params.jid; const jobID = +params.jid;
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
const initialFilters: string[] = []; const initialFilters: object[] = [];
let initialFrame = 0; let initialFrame = 0;
if (searchParams.has('frame')) { if (searchParams.has('frame')) {
@ -64,7 +64,9 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps {
const serverID = searchParams.get('serverID'); const serverID = searchParams.get('serverID');
const type = searchParams.get('type'); const type = searchParams.get('type');
if (serverID && !Number.isNaN(+serverID)) { if (serverID && !Number.isNaN(+serverID)) {
initialFilters.push(`serverID==${serverID} & type=="${type}"`); initialFilters.push({
and: [{ '==': [{ var: 'serverID' }, serverID] }, { '==': [{ var: 'type' }, type] }],
});
} }
} }

@ -34,13 +34,12 @@ interface StateToProps {
statesCollapsedAll: boolean; statesCollapsedAll: boolean;
collapsedStates: Record<number, boolean>; collapsedStates: Record<number, boolean>;
objectStates: any[]; objectStates: any[];
annotationsFilters: string[]; annotationsFilters: any[];
colors: string[]; colors: string[];
colorBy: ColorBy; colorBy: ColorBy;
activatedStateID: number | null; activatedStateID: number | null;
minZLayer: number; minZLayer: number;
maxZLayer: number; maxZLayer: number;
annotationsFiltersHistory: string[];
keyMap: KeyMap; keyMap: KeyMap;
normalizedKeyMap: Record<string, string>; normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas; canvasInstance: Canvas;
@ -62,7 +61,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
annotations: { annotations: {
states: objectStates, states: objectStates,
filters: annotationsFilters, filters: annotationsFilters,
filtersHistory: annotationsFiltersHistory,
collapsed, collapsed,
collapsedAll, collapsedAll,
activatedStateID, activatedStateID,
@ -110,7 +108,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
activatedStateID, activatedStateID,
minZLayer, minZLayer,
maxZLayer, maxZLayer,
annotationsFiltersHistory,
keyMap, keyMap,
normalizedKeyMap, normalizedKeyMap,
canvasInstance, canvasInstance,

@ -0,0 +1,26 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
import FiltersModalComponent from 'components/annotation-page/top-bar/filters-modal';
interface StateToProps {
visible: boolean;
}
const mapStateToProps = (state: CombinedState): StateToProps => {
const {
annotation: { filtersPanelVisible: visible },
} = state;
return { visible };
};
function FiltersModalContainer(props: StateToProps): JSX.Element {
const { visible } = props;
return <FiltersModalComponent visible={visible} />;
}
export default connect(mapStateToProps, null)(FiltersModalContainer);

@ -3,31 +3,30 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import copy from 'copy-to-clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import Input from 'antd/lib/input'; import Input from 'antd/lib/input';
import copy from 'copy-to-clipboard';
import { import {
activateObject,
changeFrameAsync, changeFrameAsync,
switchPlay, changeWorkspace as changeWorkspaceAction,
saveAnnotationsAsync,
collectStatisticsAsync, collectStatisticsAsync,
showStatistics as showStatisticsAction,
undoActionAsync,
redoActionAsync, redoActionAsync,
saveAnnotationsAsync,
searchAnnotationsAsync, searchAnnotationsAsync,
searchEmptyFrameAsync, searchEmptyFrameAsync,
changeWorkspace as changeWorkspaceAction,
activateObject,
setForceExitAnnotationFlag as setForceExitAnnotationFlagAction, setForceExitAnnotationFlag as setForceExitAnnotationFlagAction,
showFilters as showFiltersAction,
showStatistics as showStatisticsAction,
switchPlay,
undoActionAsync,
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import { Canvas } from 'cvat-canvas-wrapper';
import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar';
import { Canvas } from 'cvat-canvas-wrapper';
import { CombinedState, FrameSpeed, Workspace } from 'reducers/interfaces'; import { CombinedState, FrameSpeed, Workspace } from 'reducers/interfaces';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
interface StateToProps { interface StateToProps {
jobInstance: any; jobInstance: any;
@ -56,6 +55,7 @@ interface DispatchToProps {
onSwitchPlay(playing: boolean): void; onSwitchPlay(playing: boolean): void;
onSaveAnnotation(sessionInstance: any): void; onSaveAnnotation(sessionInstance: any): void;
showStatistics(sessionInstance: any): void; showStatistics(sessionInstance: any): void;
showFilters(sessionInstance: any): void;
undo(sessionInstance: any, frameNumber: any): void; undo(sessionInstance: any, frameNumber: any): void;
redo(sessionInstance: any, frameNumber: any): void; redo(sessionInstance: any, frameNumber: any): void;
searchAnnotations(sessionInstance: any, frameFrom: number, frameTo: number): void; searchAnnotations(sessionInstance: any, frameFrom: number, frameTo: number): void;
@ -124,6 +124,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
dispatch(collectStatisticsAsync(sessionInstance)); dispatch(collectStatisticsAsync(sessionInstance));
dispatch(showStatisticsAction(true)); dispatch(showStatisticsAction(true));
}, },
showFilters(): void {
dispatch(showFiltersAction(true));
},
undo(sessionInstance: any, frameNumber: any): void { undo(sessionInstance: any, frameNumber: any): void {
dispatch(undoActionAsync(sessionInstance, frameNumber)); dispatch(undoActionAsync(sessionInstance, frameNumber));
}, },
@ -274,6 +277,12 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
showStatistics(jobInstance); showStatistics(jobInstance);
}; };
private showFilters = (): void => {
const { jobInstance, showFilters } = this.props;
showFilters(jobInstance);
};
private onSwitchPlay = (): void => { private onSwitchPlay = (): void => {
const { const {
frameNumber, jobInstance, onSwitchPlay, playing, frameNumber, jobInstance, onSwitchPlay, playing,
@ -587,6 +596,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers} /> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers} />
<AnnotationTopBarComponent <AnnotationTopBarComponent
showStatistics={this.showStatistics} showStatistics={this.showStatistics}
showFilters={this.showFilters}
onSwitchPlay={this.onSwitchPlay} onSwitchPlay={this.onSwitchPlay}
onSaveAnnotation={this.onSaveAnnotation} onSaveAnnotation={this.onSaveAnnotation}
onPrevFrame={this.onPrevFrame} onPrevFrame={this.onPrevFrame}

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -48,6 +48,7 @@ import SVGResetPerspectiveIcon from './assets/reset-perspective.svg';
import SVGColorizeIcon from './assets/colorize-icon.svg'; import SVGColorizeIcon from './assets/colorize-icon.svg';
import SVGAITools from './assets/ai-tools-icon.svg'; import SVGAITools from './assets/ai-tools-icon.svg';
import SVGOpenCV from './assets/opencv.svg'; import SVGOpenCV from './assets/opencv.svg';
import SVGFilterIcon from './assets/object-filter-icon.svg';
export const CVATLogo = React.memo((): JSX.Element => <SVGCVATLogo />); export const CVATLogo = React.memo((): JSX.Element => <SVGCVATLogo />);
export const AccountIcon = React.memo((): JSX.Element => <SVGAccountIcon />); export const AccountIcon = React.memo((): JSX.Element => <SVGAccountIcon />);
@ -93,3 +94,4 @@ export const ResetPerspectiveIcon = React.memo((): JSX.Element => <SVGResetPersp
export const AIToolsIcon = React.memo((): JSX.Element => <SVGAITools />); export const AIToolsIcon = React.memo((): JSX.Element => <SVGAITools />);
export const ColorizeIcon = React.memo((): JSX.Element => <SVGColorizeIcon />); export const ColorizeIcon = React.memo((): JSX.Element => <SVGColorizeIcon />);
export const OpenCVIcon = React.memo((): JSX.Element => <SVGOpenCV />); export const OpenCVIcon = React.memo((): JSX.Element => <SVGOpenCV />);
export const FilterIcon = React.memo((): JSX.Element => <SVGFilterIcon />);

@ -4,21 +4,20 @@
import React from 'react'; import React from 'react';
import { AnyAction } from 'redux'; import { AnyAction } from 'redux';
import { Canvas, CanvasMode } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions';
import { AuthActionTypes } from 'actions/auth-actions'; import { AuthActionTypes } from 'actions/auth-actions';
import { BoundariesActionTypes } from 'actions/boundaries-actions'; import { BoundariesActionTypes } from 'actions/boundaries-actions';
import { Canvas, CanvasMode } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { import {
AnnotationState,
ActiveControl, ActiveControl,
ShapeType, AnnotationState,
ObjectType,
ContextMenuType, ContextMenuType,
Workspace,
TaskStatus,
DimensionType, DimensionType,
ObjectType,
ShapeType,
TaskStatus,
Workspace,
} from './interfaces'; } from './interfaces';
const defaultState: AnnotationState = { const defaultState: AnnotationState = {
@ -81,7 +80,6 @@ const defaultState: AnnotationState = {
collapsedAll: true, collapsedAll: true,
states: [], states: [],
filters: [], filters: [],
filtersHistory: JSON.parse(window.localStorage.getItem('filtersHistory') || '[]'),
resetGroupFlag: false, resetGroupFlag: false,
history: { history: {
undo: [], undo: [],
@ -106,6 +104,7 @@ const defaultState: AnnotationState = {
colors: [], colors: [],
sidebarCollapsed: false, sidebarCollapsed: false,
appearanceCollapsed: false, appearanceCollapsed: false,
filtersPanelVisible: false,
requestReviewDialogVisible: false, requestReviewDialogVisible: false,
submitReviewDialogVisible: false, submitReviewDialogVisible: false,
tabContentHeight: 0, tabContentHeight: 0,
@ -806,6 +805,14 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}, },
}; };
} }
case AnnotationActionTypes.SWITCH_SHOWING_FILTERS: {
const { visible } = action.payload;
return {
...state,
filtersPanelVisible: visible,
};
}
case AnnotationActionTypes.COLLECT_STATISTICS: { case AnnotationActionTypes.COLLECT_STATISTICS: {
return { return {
...state, ...state,
@ -976,13 +983,11 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}; };
} }
case AnnotationActionTypes.CHANGE_ANNOTATIONS_FILTERS: { case AnnotationActionTypes.CHANGE_ANNOTATIONS_FILTERS: {
const { filters, filtersHistory } = action.payload; const { filters } = action.payload;
return { return {
...state, ...state,
annotations: { annotations: {
...state.annotations, ...state.annotations,
filtersHistory,
filters, filters,
}, },
}; };

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { MutableRefObject } from 'react'; import { MutableRefObject } from 'react';
import { Canvas, RectDrawingMethod } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d/src/typescript/canvas3d'; import { Canvas3d } from 'cvat-canvas3d/src/typescript/canvas3d';
import { Canvas, RectDrawingMethod } from 'cvat-canvas-wrapper';
import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors'; import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors';
import { KeyMap } from 'utils/mousetrap-react'; import { KeyMap } from 'utils/mousetrap-react';
@ -428,8 +428,7 @@ export interface AnnotationState {
collapsed: Record<number, boolean>; collapsed: Record<number, boolean>;
collapsedAll: boolean; collapsedAll: boolean;
states: any[]; states: any[];
filters: string[]; filters: any[];
filtersHistory: string[];
resetGroupFlag: boolean; resetGroupFlag: boolean;
history: { history: {
undo: [string, number][]; undo: [string, number][];
@ -456,6 +455,7 @@ export interface AnnotationState {
data: any; data: any;
}; };
colors: any[]; colors: any[];
filtersPanelVisible: boolean;
requestReviewDialogVisible: boolean; requestReviewDialogVisible: boolean;
submitReviewDialogVisible: boolean; submitReviewDialogVisible: boolean;
sidebarCollapsed: boolean; sidebarCollapsed: boolean;

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -129,6 +129,7 @@ context('Merge/split features', () => {
cy.get('.cvat-split-track-control').click(); cy.get('.cvat-split-track-control').click();
// A single click does not reproduce the split a track scenario in cypress test. // A single click does not reproduce the split a track scenario in cypress test.
cy.get('#cvat_canvas_shape_3').click().click(); cy.get('#cvat_canvas_shape_3').click().click();
cy.get('#cvat_canvas_shape_3').click(); // TODO: workaraund. Need to figure out and make it work with just single click
cy.get('#cvat_canvas_shape_4').should('exist').and('be.hidden'); cy.get('#cvat_canvas_shape_4').should('exist').and('be.hidden');
cy.get('#cvat-objects-sidebar-state-item-4') cy.get('#cvat-objects-sidebar-state-item-4')
.should('contain', '4') .should('contain', '4')

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -102,6 +102,15 @@ context('Lock/hide features.', () => {
cy.openJob(); cy.openJob();
}); });
beforeEach(() => {
cy.document().then((doc) => {
const tooltips = Array.from(doc.querySelectorAll('.ant-tooltip'));
if (tooltips.length > 0) {
cy.get('.ant-tooltip').invoke('hide');
}
});
});
describe(`Testing case "${caseId}"`, () => { describe(`Testing case "${caseId}"`, () => {
it('Draw several objects (different shapes, tracks, tags, labels)', () => { it('Draw several objects (different shapes, tracks, tags, labels)', () => {
cy.createPolygon(createPolygonShape); cy.createPolygon(createPolygonShape);

@ -1,183 +1,185 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/// <reference types="cypress" /> // /// <reference types="cypress" />
import { taskName } from '../../support/const'; // import { taskName } from '../../support/const';
context('Filters functionality.', () => { // TODO: Update with new filters UI
const caseId = '18';
const labelShape = 'shape 3 points';
const additionalAttrsLabelShape = [
{ additionalAttrName: 'type', additionalValue: 'shape', typeAttribute: 'Text' },
{ additionalAttrName: 'count points', additionalValue: '3', typeAttribute: 'Text' },
{ additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' },
];
const labelTrack = 'track 4 points';
const additionalAttrsLabelTrack = [
{ additionalAttrName: 'type', additionalValue: 'track', typeAttribute: 'Text' },
{ additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' },
{ additionalAttrName: 'count points', additionalValue: '4', typeAttribute: 'Text' },
];
const createPolygonShape = { // context('Filters functionality.', () => {
reDraw: false, // const caseId = '18';
type: 'Shape', // const labelShape = 'shape 3 points';
labelName: labelShape, // const additionalAttrsLabelShape = [
pointsMap: [ // { additionalAttrName: 'type', additionalValue: 'shape', typeAttribute: 'Text' },
{ x: 200, y: 200 }, // { additionalAttrName: 'count points', additionalValue: '3', typeAttribute: 'Text' },
{ x: 250, y: 200 }, // { additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' },
{ x: 250, y: 240 }, // ];
], // const labelTrack = 'track 4 points';
complete: true, // const additionalAttrsLabelTrack = [
numberOfPoints: null, // { additionalAttrName: 'type', additionalValue: 'track', typeAttribute: 'Text' },
}; // { additionalAttrName: 'polygon', additionalValue: 'True', typeAttribute: 'Checkbox' },
const createRectangleTrack2Points = { // { additionalAttrName: 'count points', additionalValue: '4', typeAttribute: 'Text' },
points: 'By 2 Points', // ];
type: 'Track',
labelName: labelTrack,
firstX: 260,
firstY: 200,
secondX: 360,
secondY: 250,
};
const createRectangleShape4Points = {
points: 'By 4 Points',
type: 'Shape',
labelName: labelShape,
firstX: 550,
firstY: 350,
secondX: 650,
secondY: 350,
thirdX: 650,
thirdY: 450,
fourthX: 550,
fourthY: 450,
};
const createPolygonTrack = {
reDraw: false,
type: 'Track',
labelName: labelTrack,
pointsMap: [
{ x: 700, y: 350 },
{ x: 850, y: 350 },
{ x: 850, y: 450 },
{ x: 700, y: 450 },
],
numberOfPoints: 4,
};
let cvatCanvasShapeList = []; // const createPolygonShape = {
let cvatFiltesList = []; // reDraw: false,
// type: 'Shape',
// labelName: labelShape,
// pointsMap: [
// { x: 200, y: 200 },
// { x: 250, y: 200 },
// { x: 250, y: 240 },
// ],
// complete: true,
// numberOfPoints: null,
// };
// const createRectangleTrack2Points = {
// points: 'By 2 Points',
// type: 'Track',
// labelName: labelTrack,
// firstX: 260,
// firstY: 200,
// secondX: 360,
// secondY: 250,
// };
// const createRectangleShape4Points = {
// points: 'By 4 Points',
// type: 'Shape',
// labelName: labelShape,
// firstX: 550,
// firstY: 350,
// secondX: 650,
// secondY: 350,
// thirdX: 650,
// thirdY: 450,
// fourthX: 550,
// fourthY: 450,
// };
// const createPolygonTrack = {
// reDraw: false,
// type: 'Track',
// labelName: labelTrack,
// pointsMap: [
// { x: 700, y: 350 },
// { x: 850, y: 350 },
// { x: 850, y: 450 },
// { x: 700, y: 450 },
// ],
// numberOfPoints: 4,
// };
function checkingFilterApplication(ids) { // let cvatCanvasShapeList = [];
for (let i = 0; i < cvatCanvasShapeList.length; i++) { // let cvatFiltesList = [];
if (ids.indexOf(cvatCanvasShapeList[i]) > -1) {
cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('exist');
cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('exist');
} else {
cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('not.exist');
cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('not.exist');
}
}
}
before(() => { // function checkingFilterApplication(ids) {
cy.openTask(taskName); // for (let i = 0; i < cvatCanvasShapeList.length; i++) {
cy.addNewLabel(labelShape, additionalAttrsLabelShape); // if (ids.indexOf(cvatCanvasShapeList[i]) > -1) {
cy.addNewLabel(labelTrack, additionalAttrsLabelTrack); // cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('exist');
cy.openJob(); // cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('exist');
}); // } else {
// cy.get(`#cvat_canvas_shape_${cvatCanvasShapeList[i]}`).should('not.exist');
// cy.get(`#cvat-objects-sidebar-state-item-${cvatCanvasShapeList[i]}`).should('not.exist');
// }
// }
// }
describe(`Testing case "${caseId}"`, () => { // before(() => {
it('Draw several objects (different shapes, tracks, labels)', () => { // cy.openTask(taskName);
cy.createPolygon(createPolygonShape); // cy.addNewLabel(labelShape, additionalAttrsLabelShape);
cy.createRectangle(createRectangleTrack2Points); // cy.addNewLabel(labelTrack, additionalAttrsLabelTrack);
cy.createRectangle(createRectangleShape4Points); // cy.openJob();
cy.createPolygon(createPolygonTrack); // });
cy.get('.cvat_canvas_shape').then(($cvatCanvasShapeList) => {
for (let i = 0; i < $cvatCanvasShapeList.length; i++) { // describe(`Testing case "${caseId}"`, () => {
cvatCanvasShapeList.push(Number($cvatCanvasShapeList[i].id.match(/\d+$/))); // it('Draw several objects (different shapes, tracks, labels)', () => {
} // cy.createPolygon(createPolygonShape);
}); // cy.createRectangle(createRectangleTrack2Points);
}); // cy.createRectangle(createRectangleShape4Points);
it('Filter: shape=="polygon". Only the polygon exist.', () => { // cy.createPolygon(createPolygonTrack);
const textFilter = 'shape=="polygon"'; // cy.get('.cvat_canvas_shape').then(($cvatCanvasShapeList) => {
cvatFiltesList.push(textFilter); // for (let i = 0; i < $cvatCanvasShapeList.length; i++) {
cy.writeFilterValue(false, textFilter); // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4 // cvatCanvasShapeList.push(Number($cvatCanvasShapeList[i].id.match(/\d+$/)));
checkingFilterApplication([1, 4]); // }
}); // });
it('Filter: shape=="polygon" | shape=="rectangle". Only the rectangle and polygon exist.', () => { // });
const textFilter = 'shape=="polygon" | shape=="rectangle"'; // it('Filter: shape=="polygon". Only the polygon exist.', () => {
cvatFiltesList.push(textFilter); // const textFilter = 'shape=="polygon"';
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4 // cvatFiltesList.push(textFilter);
checkingFilterApplication([1, 2, 3, 4]); // cy.writeFilterValue(false, textFilter); // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4
}); // checkingFilterApplication([1, 4]);
it('Filter: type=="shape". Only the objects with shape type exist.', () => { // });
const textFilter = 'type=="shape"'; // it('Filter: shape=="polygon" | shape=="rectangle". Only the rectangle and polygon exist.', () => {
cvatFiltesList.push(textFilter); // const textFilter = 'shape=="polygon" | shape=="rectangle"';
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3 // cvatFiltesList.push(textFilter);
checkingFilterApplication([1, 3]); // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4
}); // checkingFilterApplication([1, 2, 3, 4]);
it('Filter: label=="track 4 points". Only the polygon exist.', () => { // });
const textFilter = `label=="${labelTrack}"`; // it('Filter: type=="shape". Only the objects with shape type exist.', () => {
cvatFiltesList.push(textFilter); // const textFilter = 'type=="shape"';
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 // cvatFiltesList.push(textFilter);
checkingFilterApplication([2, 4]); // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3
}); // checkingFilterApplication([1, 3]);
it('Filter: attr["count points"] == "4". Only the objects with same attr exist.', () => { // });
const textFilter = 'attr["count points"] == "4"'; // it('Filter: label=="track 4 points". Only the polygon exist.', () => {
cvatFiltesList.push(textFilter); // const textFilter = `label=="${labelTrack}"`;
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 // cvatFiltesList.push(textFilter);
checkingFilterApplication([2, 4]); // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4
}); // checkingFilterApplication([2, 4]);
it('Filter: width >= height. All objects exist.', () => { // });
const textFilter = 'width >= height'; // it('Filter: attr["count points"] == "4". Only the objects with same attr exist.', () => {
cvatFiltesList.push(textFilter); // const textFilter = 'attr["count points"] == "4"';
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4 // cvatFiltesList.push(textFilter);
checkingFilterApplication([1, 2, 3, 4]); // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4
}); // checkingFilterApplication([2, 4]);
it('Filter: clientID == 4. Only the objects with same id exist (polygon track).', () => { // });
const textFilter = 'clientID == 4'; // it('Filter: width >= height. All objects exist.', () => {
cvatFiltesList.push(textFilter); // const textFilter = 'width >= height';
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_7, #cvat-objects-sidebar-state-item-4 // cvatFiltesList.push(textFilter);
checkingFilterApplication([4]); // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_1,2,3,4, #cvat-objects-sidebar-state-item-1,2,3,4
}); // checkingFilterApplication([1, 2, 3, 4]);
it('Filter: (label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60). Only the objects polygon and rectangle exist.', () => { // });
const textFilter = // it('Filter: clientID == 4. Only the objects with same id exist (polygon track).', () => {
'(label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60)'; // const textFilter = 'clientID == 4';
cvatFiltesList.push(textFilter); // cvatFiltesList.push(textFilter);
cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_7, #cvat-objects-sidebar-state-item-4
checkingFilterApplication([2, 4]); // checkingFilterApplication([4]);
}); // });
it('Filter: (( label==["shape 3 points"]) | (attr["type"]=="shape" & width > 50)) & (height > 50 & (clientID == serverID))). All objects not exist.', () => { // it('Filter: (label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60). Only the objects polygon and rectangle exist.', () => {
const textFilter = // const textFilter =
'(( label==["points shape"]) | (attr["type"]=="shape" & width > 50)) & (height > 50 & (clientID == serverID)))'; // '(label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60)';
cvatFiltesList.push(textFilter); // cvatFiltesList.push(textFilter);
cy.writeFilterValue(true, textFilter); // cy.writeFilterValue(true, textFilter); // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4
checkingFilterApplication([]); // checkingFilterApplication([2, 4]);
}); // });
it('Verify to show all filters', () => { // it('Filter: (( label==["shape 3 points"]) | (attr["type"]=="shape" & width > 50)) & (height > 50 & (clientID == serverID))). All objects not exist.', () => {
cvatFiltesList.forEach(function (filterValue) { // const textFilter =
cy.contains('.cvat-annotations-filters-input-history-element', filterValue); // '(( label==["points shape"]) | (attr["type"]=="shape" & width > 50)) & (height > 50 & (clientID == serverID)))';
}); // cvatFiltesList.push(textFilter);
}); // cy.writeFilterValue(true, textFilter);
it('Select filter: type=="shape"', () => { // checkingFilterApplication([]);
cy.selectFilterValue(true, 'type=="shape"'); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3 // });
checkingFilterApplication([1, 3]); // it('Verify to show all filters', () => {
}); // cvatFiltesList.forEach(function (filterValue) {
it('Select filter: clientID == 4', () => { // cy.contains('.cvat-annotations-filters-input-history-element', filterValue);
cy.selectFilterValue(true, 'clientID == 4'); // #cvat_canvas_shape_7, #cvat-objects-sidebar-state-item-4 // });
checkingFilterApplication([4]); // });
}); // it('Select filter: type=="shape"', () => {
it('Select two filters', () => { // cy.selectFilterValue(true, 'type=="shape"'); // #cvat_canvas_shape_1,3, #cvat-objects-sidebar-state-item-1,3
const textFirstFilter = // checkingFilterApplication([1, 3]);
'(label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60)'; // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4 // });
const textSecondFilter = 'shape=="polygon"'; // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4 // it('Select filter: clientID == 4', () => {
cy.selectFilterValue(true, textFirstFilter); // cy.selectFilterValue(true, 'clientID == 4'); // #cvat_canvas_shape_7, #cvat-objects-sidebar-state-item-4
cy.selectFilterValue(false, textSecondFilter); // checkingFilterApplication([4]);
checkingFilterApplication([1, 2, 4]); // });
}); // it('Select two filters', () => {
}); // const textFirstFilter =
}); // '(label=="shape 3 points" & attr["polylines"]==true) | (label=="track 4 points" & width > 60)'; // #cvat_canvas_shape_2,4, #cvat-objects-sidebar-state-item-2,4
// const textSecondFilter = 'shape=="polygon"'; // #cvat_canvas_shape_1,4, #cvat-objects-sidebar-state-item-1,4
// cy.selectFilterValue(true, textFirstFilter);
// cy.selectFilterValue(false, textSecondFilter);
// checkingFilterApplication([1, 2, 4]);
// });
// });
// });

@ -1,10 +1,10 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { labelName, taskName } from '../../support/const';
context('Filter property "shape" work correctly', () => { context('Filter property "shape" work correctly', () => {
const issueId = '1444'; const issueId = '1444';
@ -43,12 +43,13 @@ context('Filter property "shape" work correctly', () => {
cy.createPolygon(createPolygonShape); cy.createPolygon(createPolygonShape);
cy.get('#cvat-objects-sidebar-state-item-2').should('contain', '2').and('contain', 'POLYGON SHAPE'); cy.get('#cvat-objects-sidebar-state-item-2').should('contain', '2').and('contain', 'POLYGON SHAPE');
}); });
it('Input filter "shape == "polygon""', () => { // TODO: Update with new filters UI
cy.get('.cvat-annotations-filters-input').type('shape == "polygon"{Enter}'); // it('Input filter "shape == "polygon""', () => {
}); // cy.get('.cvat-annotations-filters-input').type('shape == "polygon"{Enter}');
it('Only polygon is visible', () => { // });
cy.get('#cvat_canvas_shape_2').should('exist'); // it('Only polygon is visible', () => {
cy.get('#cvat_canvas_shape_1').should('not.exist'); // cy.get('#cvat_canvas_shape_2').should('exist');
}); // cy.get('#cvat_canvas_shape_1').should('not.exist');
// });
}); });
}); });

@ -1,10 +1,10 @@
// Copyright (C) 2020 Intel Corporation // Copyright (C) 2020-2021 Intel Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, labelName } from '../../support/const'; import { labelName, taskName } from '../../support/const';
context('Navigation to empty frames', () => { context('Navigation to empty frames', () => {
const issueId = '2485'; const issueId = '2485';
@ -33,10 +33,11 @@ context('Navigation to empty frames', () => {
cy.createRectangle(createRectangleShape2Points); cy.createRectangle(createRectangleShape2Points);
}); });
it('Input a filter to see the created objects.', () => { // TODO: Update with new filters UI
cy.writeFilterValue(false, 'shape=="rectangle"'); // it('Input a filter to see the created objects.', () => {
cy.get('#cvat_canvas_shape_2').should('exist'); // cy.writeFilterValue(false, 'shape=="rectangle"');
}); // cy.get('#cvat_canvas_shape_2').should('exist');
// });
it('Go to 3rd frame.', () => { it('Go to 3rd frame.', () => {
cy.goCheckFrameNumber(3); cy.goCheckFrameNumber(3);
@ -50,21 +51,22 @@ context('Navigation to empty frames', () => {
} }
}); });
it("Press go previous with a filter. CVAT get 2nd frame. Press again. Frame wasn't changed.", () => { // TODO: Update with new filters UI
for (let i = 1; i <= 2; i++) { // it("Press go previous with a filter. CVAT get 2nd frame. Press again. Frame wasn't changed.", () => {
cy.get('.cvat-player-previous-button-filtered').click({ force: true }); // for (let i = 1; i <= 2; i++) {
cy.checkFrameNum(2); // cy.get('.cvat-player-previous-button-filtered').click({ force: true });
cy.get('#cvat_canvas_shape_1').should('exist'); // cy.checkFrameNum(2);
} // cy.get('#cvat_canvas_shape_1').should('exist');
}); // }
// });
it("Press go next with a filter. CVAT get 4th frame. Press again. Frame wasn't changed.", () => { // it("Press go next with a filter. CVAT get 4th frame. Press again. Frame wasn't changed.", () => {
for (let i = 1; i <= 2; i++) { // for (let i = 1; i <= 2; i++) {
cy.get('.cvat-player-next-button-filtered').click({ force: true }); // cy.get('.cvat-player-next-button-filtered').click({ force: true });
cy.checkFrameNum(4); // cy.checkFrameNum(4);
cy.get('#cvat_canvas_shape_2').should('exist'); // cy.get('#cvat_canvas_shape_2').should('exist');
} // }
}); // });
it('Change navigation buttons mode to "Go next/previous to an empty frame".', () => { it('Change navigation buttons mode to "Go next/previous to an empty frame".', () => {
for (const i of ['previous', 'next']) { for (const i of ['previous', 'next']) {
@ -73,16 +75,17 @@ context('Navigation to empty frames', () => {
} }
}); });
it('Go previous to an empty frame. CVAT get 3rd frame.', () => { // TODO: Update with new filters UI
cy.get('.cvat-player-previous-button-empty').click({ force: true }); // it('Go previous to an empty frame. CVAT get 3rd frame.', () => {
cy.checkFrameNum(3); // cy.get('.cvat-player-previous-button-empty').click({ force: true });
cy.get('.cvat_canvas_shape').should('not.exist'); // cy.checkFrameNum(3);
}); // cy.get('.cvat_canvas_shape').should('not.exist');
// });
it('Go next to an empty frame. CVAT get 5th frame.', () => { // it('Go next to an empty frame. CVAT get 5th frame.', () => {
cy.get('.cvat-player-next-button-empty').click({ force: true }); // cy.get('.cvat-player-next-button-empty').click({ force: true });
cy.checkFrameNum(5); // cy.checkFrameNum(5);
cy.get('.cvat_canvas_shape').should('not.exist'); // cy.get('.cvat_canvas_shape').should('not.exist');
}); // });
}); });
}); });

@ -14,20 +14,20 @@ context('Annotation filter help dialog window.', () => {
}); });
describe(`Testing issue "${issueId}"`, () => { describe(`Testing issue "${issueId}"`, () => {
it('Open annotation filters help dialog window. The window is visible.', () => { // TODO: Update with new filters UI
cy.get('.cvat-annotations-filters-input').within(() => { // it('Open annotation filters help dialog window. The window is visible.', () => {
// class="ant-select-selection-placeholder" has CSS pointer-events: none // cy.get('.cvat-annotations-filters-input').within(() => {
cy.get('.ant-select-selection-placeholder').invoke('css', 'pointer-events', 'auto'); // Replace CSS "pointer-events" to auto // // class="ant-select-selection-placeholder" has CSS pointer-events: none
cy.get('[aria-label="filter"]').click(); // cy.get('.ant-select-selection-placeholder').invoke('css', 'pointer-events', 'auto'); // Replace CSS "pointer-events" to auto
}); // cy.get('[aria-label="filter"]').click();
cy.get('.cvat-annotations-filters-help-modal-window').should('exist').and('be.visible'); // });
}); // cy.get('.cvat-annotations-filters-help-modal-window').should('exist').and('be.visible');
// });
it('Close annotation filters help dialog window. The window is closed.', () => { // it('Close annotation filters help dialog window. The window is closed.', () => {
cy.get('.cvat-annotations-filters-help-modal-window').within(() => { // cy.get('.cvat-annotations-filters-help-modal-window').within(() => {
cy.contains('button', 'OK').click(); // cy.contains('button', 'OK').click();
}); // });
cy.get('.cvat-annotations-filters-help-modal-window').should('not.exist'); // cy.get('.cvat-annotations-filters-help-modal-window').should('not.exist');
}); // });
}); });
}); });

Loading…
Cancel
Save