Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed possible color collisions in the generated colormap #4007

Merged
merged 11 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Order of the label attributes in the object item details(<https://github.com/openvinotoolkit/cvat/pull/3945>)
- Order of labels in tasks and projects (<https://github.com/openvinotoolkit/cvat/pull/3987>)
- Added information to export CVAT_HOST when performing local installation for accessing over network (<https://github.com/openvinotoolkit/cvat/pull/4014>)
- Fixed possible color collisions in the generated colormap (<https://github.com/openvinotoolkit/cvat/pull/4007>)

### Security
- TDB
Expand Down
41 changes: 32 additions & 9 deletions cvat/apps/dataset_manager/formats/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Copyright (C) 2019 Intel Corporation
# Copyright (C) 2019-2021 Intel Corporation
#
# SPDX-License-Identifier: MIT

import os.path as osp
from hashlib import blake2s
import itertools

from datumaro.util.os_util import make_file_name

Expand Down Expand Up @@ -62,18 +63,40 @@ def make_colormap(instance_data):
return {label['name']: [hex2rgb(label['color']), [], []] for label in labels}


def get_label_color(label_name, label_names):

def generate_color(color, used_colors):
def tint_shade_color():
for added_color in (255, 0):
for factor in range(1, 10):
yield tuple(map(lambda c: int(c + (added_color - c) * factor / 10), color))

def get_unused_color():
azhavoro marked this conversation as resolved.
Show resolved Hide resolved
for r in range (255, 0, -1):
for g in range (255, 0, -1):
for b in range (255, 0, -1):
new_color = (r, g, b)
if new_color not in used_colors:
return new_color

#try to tint and shade color firstly
for new_color in tint_shade_color():
if new_color not in used_colors:
return new_color

return get_unused_color()

def get_label_color(label_name, label_colors):
predefined = parse_default_colors()
normalized_names = [normalize_label(l_name) for l_name in label_names]
label_colors = tuple(hex2rgb(c) for c in label_colors.values() if c)
used_colors = set(itertools.chain(predefined.values(), label_colors))
normalized_name = normalize_label(label_name)

color = predefined.get(normalized_name, None)
name_hash = int.from_bytes(blake2s(normalized_name.encode(), digest_size=4).digest(), byteorder="big")
offset = name_hash + normalized_names.count(normalized_name)

if color is None:
color = get_color_from_index(DEFAULT_COLORMAP_CAPACITY + offset)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this const is not used, so should be deleted

elif normalized_names.count(normalized_name):
color = get_color_from_index(DEFAULT_COLORMAP_CAPACITY + offset - 1)
name_hash = int.from_bytes(blake2s(normalized_name.encode(), digest_size=3).digest(), byteorder="big")
color = get_color_from_index(name_hash)

if color in label_colors:
color = generate_color(color, used_colors)

return rgb2hex(color)
4 changes: 2 additions & 2 deletions cvat/apps/engine/migrations/0028_labelcolor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ def alter_label_colors(apps, schema_editor):

for task in Task.objects.all():
labels = Label.objects.filter(task_id=task.id).order_by('id')
label_names = list()
label_names = {}
azhavoro marked this conversation as resolved.
Show resolved Hide resolved
for label in labels:
label.color = get_label_color(label.name, label_names)
label_names.append(label.name)
label_names[label.name] = label.color
label.save()

class Migration(migrations.Migration):
Expand Down
18 changes: 9 additions & 9 deletions cvat/apps/engine/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ def update_instance(validated_data, parent_instance):
db_label.delete()
return
if not validated_data.get('color', None):
label_names = [l.name for l in
label_colors = {l.name: l.color for l in
instance[tuple(instance.keys())[0]].label_set.exclude(id=db_label.id).order_by('id')
]
db_label.color = get_label_color(db_label.name, label_names)
}
db_label.color = get_label_color(db_label.name, label_colors)
else:
db_label.color = validated_data.get('color', db_label.color)
db_label.save()
Expand Down Expand Up @@ -373,12 +373,12 @@ def create(self, validated_data):

labels = validated_data.pop('label_set', [])
db_task = models.Task.objects.create(**validated_data)
label_names = list()
label_colors = {}
for label in labels:
attributes = label.pop('attributespec_set')
if not label.get('color', None):
label['color'] = get_label_color(label['name'], label_names)
label_names.append(label['name'])
label['color'] = get_label_color(label['name'], label_colors)
label_colors[label['name']] = label['color']
db_label = models.Label.objects.create(task=db_task, **label)
for attr in attributes:
models.AttributeSpec.objects.create(label=db_label, **attr)
Expand Down Expand Up @@ -541,12 +541,12 @@ def create(self, validated_data):
training_project=tr_p)
else:
db_project = models.Project.objects.create(**validated_data)
label_names = list()
label_colors = {}
for label in labels:
attributes = label.pop('attributespec_set')
if not label.get('color', None):
label['color'] = get_label_color(label['name'], label_names)
label_names.append(label['name'])
label['color'] = get_label_color(label['name'], label_colors)
label_colors[label['name']] = label['color']
db_label = models.Label.objects.create(project=db_project, **label)
for attr in attributes:
models.AttributeSpec.objects.create(label=db_label, **attr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ import { taskName, labelName } from '../../support/const_canvas3d';

context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
const caseId = '78';
const secondLabel = 'car';

const screenshotsPath = 'cypress/screenshots/canvas3d_functionality_2/case_78_canvas3d_functionality_cuboid_label.js';
const cuboidCreationParams = {
labelName: labelName,
labelName,
};
const secondLabel = 'car';
const secondLabelAdditionalAttrs = false;
const secondLabelColorRed = 'ff0000';

before(() => {
cy.openTask(taskName)
cy.addNewLabel(secondLabel);
cy.openTask(taskName);
cy.addNewLabel(secondLabel, secondLabelAdditionalAttrs, secondLabelColorRed);
cy.openJob();
cy.wait(1000); // Waiting for the point cloud to display
cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_before_all');
Expand All @@ -32,7 +33,7 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
cy.get('#cvat-objects-sidebar-state-item-1')
.trigger('mouseover')
.should('have.class', 'cvat-objects-sidebar-state-active-item')
.wait(1000); //Wating for cuboid activation
.wait(1000); // Wating for cuboid activation
cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_after_activating_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_before_all.png`,
Expand All @@ -46,7 +47,10 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
['canvas3d_sideview_before_all.png', 'canvas3d_sideview_activating_cuboid.png'],
['canvas3d_frontview_before_all.png', 'canvas3d_frontview_activating_cuboid.png'],
].forEach(([viewBefore, viewAfterCubiodActivation]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewBefore}`, `${screenshotsPath}/${viewAfterCubiodActivation}`);
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewBefore}`,
`${screenshotsPath}/${viewAfterCubiodActivation}`,
);
});
});

Expand All @@ -67,14 +71,17 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
['canvas3d_sideview_activating_cuboid.png', 'canvas3d_sideview_change_label_cuboid.png'],
['canvas3d_frontview_activating_cuboid.png', 'canvas3d_frontview_change_label_cuboid.png'],
].forEach(([viewAfterCubiodActivation, viewAfterCubiodChangeLabel]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewAfterCubiodActivation}`, `${screenshotsPath}/${viewAfterCubiodChangeLabel}`);
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewAfterCubiodActivation}`,
`${screenshotsPath}/${viewAfterCubiodChangeLabel}`,
);
});
});

it('Lock/unlock a cuboid via sidear. The control points of the cuboid on the top/side/front view are locked/unlocked.', () => {
cy.get('#cvat-objects-sidebar-state-item-1')
.find('.cvat-object-item-button-lock')
.click({force: true}); // Lock the cubiod
.click({ force: true }); // Lock the cubiod
cy.get('.cvat-object-item-button-lock-enabled').should('exist');
['topview', 'sideview', 'frontview'].forEach((view) => {
cy.customScreenshot(`.cvat-canvas3d-${view}`, `canvas3d_${view}_lock_cuboid`);
Expand All @@ -84,9 +91,12 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
['canvas3d_sideview_change_label_cuboid.png', 'canvas3d_sideview_lock_cuboid.png'],
['canvas3d_frontview_change_label_cuboid.png', 'canvas3d_frontview_lock_cuboid.png'],
].forEach(([viewAfterCubiodChangeLabel, viewAfterCubiodLock]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewAfterCubiodChangeLabel}`, `${screenshotsPath}/${viewAfterCubiodLock}`);
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewAfterCubiodChangeLabel}`,
`${screenshotsPath}/${viewAfterCubiodLock}`,
);
});
cy.get('.cvat-object-item-button-lock-enabled').click({force: true}); // Unlock the cubiod
cy.get('.cvat-object-item-button-lock-enabled').click({ force: true }); // Unlock the cubiod
cy.get('.cvat-object-item-button-lock').should('exist').trigger('mouseout');
['topview', 'sideview', 'frontview'].forEach((view) => {
cy.customScreenshot(`.cvat-canvas3d-${view}`, `canvas3d_${view}_unlock_cuboid`);
Expand All @@ -96,20 +106,23 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
['canvas3d_sideview_lock_cuboid.png', 'canvas3d_sideview_unlock_cuboid.png'],
['canvas3d_frontview_lock_cuboid.png', 'canvas3d_frontview_unlock_cuboid.png'],
].forEach(([viewAfterCubiodLock, viewAfterCubiodUnlock]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewAfterCubiodLock}`, `${screenshotsPath}/${viewAfterCubiodUnlock}`);
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewAfterCubiodLock}`,
`${screenshotsPath}/${viewAfterCubiodUnlock}`,
);
});
});

it('Switch occluded property for a cuboid via sidear. The cuboid on the perpective view are occluded.', () => {
cy.get('#cvat-objects-sidebar-state-item-1')
.find('.cvat-object-item-button-occluded')
.click({force: true}); // Switch occluded property
.click({ force: true }); // Switch occluded property
cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_enable_occlud_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_after_activating_cuboid.png`,
`${screenshotsPath}/canvas3d_perspective_enable_occlud_cuboid.png`,
);
cy.get('.cvat-object-item-button-occluded-enabled').click({force: true}); // Switch occluded property again
cy.get('.cvat-object-item-button-occluded-enabled').click({ force: true }); // Switch occluded property again
cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_disable_occlud_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_enable_occlud_cuboid.png`,
Expand All @@ -120,7 +133,7 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
it('Hide/unhide a cuboid via sidear. The cuboid on the perpective/top/side/front view be hidden/unhidden.', () => {
cy.get('#cvat-objects-sidebar-state-item-1')
.find('.cvat-object-item-button-hidden')
.click({force: true}); // Hide the cuboid
.click({ force: true }); // Hide the cuboid
cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_hide_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_disable_occlud_cuboid.png`,
Expand All @@ -134,9 +147,12 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
['canvas3d_sideview_unlock_cuboid.png', 'canvas3d_sideview_hide_cuboid.png'],
['canvas3d_frontview_unlock_cuboid.png', 'canvas3d_frontview_hide_cuboid.png'],
].forEach(([viewAfterCubiodUnlock, viewAfterCubiodHide]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewAfterCubiodUnlock}`, `${screenshotsPath}/${viewAfterCubiodHide}`);
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewAfterCubiodUnlock}`,
`${screenshotsPath}/${viewAfterCubiodHide}`,
);
});
cy.get('.cvat-object-item-button-hidden-enabled').click({force: true}); // Unhide the cuboid
cy.get('.cvat-object-item-button-hidden-enabled').click({ force: true }); // Unhide the cuboid
cy.customScreenshot('.cvat-canvas3d-perspective', 'canvas3d_perspective_unhide_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_hide_cuboid.png`,
Expand All @@ -150,7 +166,10 @@ context('Canvas 3D functionality. Interaction with cuboid via sidebar.', () => {
['canvas3d_sideview_hide_cuboid.png', 'canvas3d_sideview_unhide_cuboid.png'],
['canvas3d_frontview_hide_cuboid.png', 'canvas3d_frontview_unhide_cuboid.png'],
].forEach(([viewAfterCubiodHide, viewAfterCubiodUnhide]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewAfterCubiodHide}`, `${screenshotsPath}/${viewAfterCubiodUnhide}`);
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewAfterCubiodHide}`,
`${screenshotsPath}/${viewAfterCubiodUnhide}`,
);
});
});
});
Expand Down