Skip to content

Commit

Permalink
Merge branch 'mk/manifest' of https://github.com/opencv/cvat into mk/…
Browse files Browse the repository at this point in the history
…manifest
  • Loading branch information
Marishka17 committed Mar 23, 2021
2 parents 3e58fa2 + 7e9389b commit c0b8a53
Show file tree
Hide file tree
Showing 37 changed files with 856 additions and 898 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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>)
- Ability of upload manifest for dataset with images (<https://github.com/openvinotoolkit/cvat/pull/2763>)
- Annotations filters UI using react-awesome-query-builder (https://github.com/openvinotoolkit/cvat/issues/1418)

### Changed

Expand Down
31 changes: 10 additions & 21 deletions cvat-core/package-lock.json

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

4 changes: 2 additions & 2 deletions cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"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",
"main": "babel.config.js",
"scripts": {
Expand Down Expand Up @@ -40,8 +40,8 @@
"error-stack-parser": "^2.0.2",
"form-data": "^2.5.0",
"jest-config": "^26.6.3",
"json-logic-js": "^2.0.0",
"js-cookie": "^2.2.0",
"jsonpath": "^1.1.0",
"platform": "^1.3.5",
"quickhull": "^1.0.3",
"store": "^2.0.12",
Expand Down
23 changes: 8 additions & 15 deletions cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2019-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -213,13 +213,9 @@
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 filtered = this.annotationsFilter.filter(visible.data, filters);

visible.data.forEach((stateData, idx) => {
if (!filters.length || filtered.includes(stateData.clientID)) {
const model = visible.models[idx];
Expand Down Expand Up @@ -777,6 +773,7 @@
}

// Add constructed objects to a collection
// eslint-disable-next-line no-unsanitized/method
const imported = this.import(constructed);
const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes);

Expand Down Expand Up @@ -865,13 +862,9 @@
}

search(filters, frameFrom, frameTo) {
const [groups, query] = this.annotationsFilter.toJSONQuery(filters);
const sign = Math.sign(frameTo - frameFrom);

const flattenedQuery = groups.flat(Number.MAX_SAFE_INTEGER);
const containsDifficultProperties = flattenedQuery.some(
(fragment) => fragment.match(/^width/) || fragment.match(/^height/),
);
const filtersStr = JSON.stringify(filters);
const containsDifficultProperties = filtersStr.match(/"var":"width"/) || filtersStr.match(/"var":"height"/);

const deepSearch = (deepSearchFrom, deepSearchTo) => {
// deepSearchFrom is expected to be a frame that doesn't satisfy a filter
Expand All @@ -882,7 +875,7 @@
while (!(Math.abs(prev - next) === 1)) {
const middle = next + Math.floor((prev - next) / 2);
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) {
next = middle;
} else {
Expand Down Expand Up @@ -919,7 +912,7 @@
}

// 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
// Deep search is needed in some difficult cases
Expand Down
189 changes: 18 additions & 171 deletions cvat-core/src/annotations-filter.js
Original file line number Diff line number Diff line change
@@ -1,143 +1,15 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

const jsonpath = require('jsonpath');
const jsonLogic = require('json-logic-js');
const { AttributeType, ObjectType } = require('./enums');
const { ArgumentError } = require('./exceptions');

class AnnotationsFilter {
constructor() {
// 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;
}
function adjustName(name) {
return name.replaceAll('.', '\u2219');
}

class AnnotationsFilter {
_convertObjects(statesData) {
const objects = statesData.map((state) => {
const labelAttributes = state.label.attributes.reduce((acc, attr) => {
Expand Down Expand Up @@ -169,63 +41,38 @@ class AnnotationsFilter {
const attributes = {};
Object.keys(state.attributes).reduce((acc, key) => {
const attr = labelAttributes[key];
let value = state.attributes[key].replace(/\\"/g, '`');
let value = state.attributes[key];
if (attr.inputType === AttributeType.NUMBER) {
value = +value;
} else if (attr.inputType === AttributeType.CHECKBOX) {
value = value === 'true';
}
acc[attr.name] = value;
acc[adjustName(attr.name)] = value;
return acc;
}, attributes);

return {
width,
height,
attr: attributes,
label: state.label.name.replace(/\\"/g, '`'),
attr: Object.fromEntries([[adjustName(state.label.name), attributes]]),
label: state.label.name,
serverID: state.serverID,
clientID: state.clientID,
objectID: state.clientID,
type: state.objectType,
shape: state.shapeType,
occluded: state.occluded,
};
});

return {
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()}`);
}
return objects;
}

filter(statesData, query) {
try {
const objects = this._convertObjects(statesData);
return jsonpath.query(objects, query);
} catch (error) {
throw new ArgumentError(`Could not apply the filter. ${error.toString()}`);
}
filter(statesData, filters) {
if (!filters.length) return statesData;
const converted = this._convertObjects(statesData);
return converted
.map((state) => state.objectID)
.filter((_, index) => jsonLogic.apply(filters[0], converted[index]));
}
}

Expand Down
Loading

0 comments on commit c0b8a53

Please sign in to comment.