Skip to content

Commit

Permalink
Merge pull request #136 from SenteraLLC/fix/erase-mode
Browse files Browse the repository at this point in the history
[WS-436] [WS-441] [WS-442] [WS-468] Fix/erase mode
  • Loading branch information
TrevorBurgoyne authored Mar 22, 2024
2 parents 76157eb + f177e52 commit daefee7
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 47 deletions.
2 changes: 1 addition & 1 deletion api_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ In some cases, you may want the annotations to render at a higher or lower resol
### `initial_line_size`
The line width with which new annotations are drawn initially. Units are pixels in the underlying image.
The line width with which new annotations are drawn initially. Units are pixels in the underlying image. When this value is not included, the default value of `4` is used and scaled by the current zoom value when a new annotation is drawn. When an `initial_line_size` is included, it is used as the line width for new annotations regardless of the current zoom value.
### `instructions_url`
Expand Down
8 changes: 7 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ All notable changes to this project will be documented here.

Nothing yet.

## [0.9.2] - March 21th, 2024
## [0.9.3] - March 22nd, 2024
- Fix bug where erase mode could perist when changing annotation mode
- When user provides an `initial_line_size`, use that as the default size for new annotations, independent of zoom
- When this argument is not provided, the behavior is unchanged from before (the size of new annotations will still scale with the zoom level).
- Fix bug where the `DELETE_CLASS_ID` would show up as an option in the id dialog for polygons and bounding boxes

## [0.9.2] - March 21st, 2024
- Small fixes to `submit_buttons` in the toolbox.
- Added optional `set_saved` argument for each `submit_button` to allow for the page to unload without warning if the button is clicked.
- Changed default button css to better center the text.
Expand Down
3 changes: 2 additions & 1 deletion demo/multi-class.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@
"name": "Submit",
"hook": on_submit,
}],
"subtasks": subtasks
"subtasks": subtasks,
"initial_line_size": 2,
});
// Wait for ULabel instance to finish initialization
ulabel.init(function () {
Expand Down
3 changes: 2 additions & 1 deletion demo/resume-from.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@
]
],
"classification_payloads": [
{"id": "c72ea9a1-79f9-444c-a01a-7983f0697b4a", "class_id": 11}
{"id": "c72ea9a1-79f9-444c-a01a-7983f0697b4a", "class_id": 11},
{"class_id": -1, "confidence": 0.0}
],
}
];
Expand Down
2 changes: 1 addition & 1 deletion dist/ulabel.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ulabel.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ulabel",
"description": "An image annotation tool.",
"version": "0.9.2",
"version": "0.9.3",
"main": "dist/ulabel.js",
"module": "dist/ulabel.js",
"scripts": {
Expand Down
14 changes: 11 additions & 3 deletions src/annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
} from "..";
import { GeometricUtils } from "./geometric_utils";


// Modes used to draw an area in the which to delete all annotations
export const DELETE_MODES = ["delete_polygon", "delete_bbox"]
export const DELETE_CLASS_ID = -1;

export class ULabelAnnotation {
constructor(
Expand All @@ -20,7 +22,7 @@ export class ULabelAnnotation {
public text_payload: string = "",

// Optional properties
public classification_payloads?: [ULabelClassificationPayload],
public classification_payloads?:ULabelClassificationPayload[],
public containing_box?: ULabelContainingBox,
public created_by?: string,
public distance_from_any_line?: number,
Expand All @@ -43,9 +45,15 @@ export class ULabelAnnotation {
let j: number;
let conf_not_found_j = null;
let remaining_confidence = 1.0;

// Filter out any classification payloads items that use the DELETE_CLASS_ID
this.classification_payloads = this.classification_payloads.filter((payload) => {
return payload.class_id !== DELETE_CLASS_ID;
});

for (j = 0; j < this.classification_payloads.length;j++) {
let this_id = this.classification_payloads[j].class_id;
if(!ulabel_class_ids.includes(this_id)) {
if (!ulabel_class_ids.includes(this_id)) {
alert(`Found class id ${this_id} in "resume_from" data but not in "allowed_classes"`);
throw `Found class id ${this_id} in "resume_from" data but not in "allowed_classes"`;
}
Expand Down
9 changes: 6 additions & 3 deletions src/html_builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SliderInfo, ULabel } from "..";
import { Toolbox, ZoomPanToolboxItem } from "./toolbox";
import { ULABEL_VERSION } from '../src/version';

import { DELETE_CLASS_ID } from "./annotation";
import {
BBOX_SVG,
DELETE_BBOX_SVG,
Expand Down Expand Up @@ -399,8 +399,11 @@ export function build_id_dialogs(ulabel: ULabel) {

// TODO noconflict
let toolbox_html: string = `<div id="tb-id-app--${st}" class="tb-id-app">`;
const class_ids: number[] = ulabel.subtasks[st]["class_ids"];

let class_ids: number[] = JSON.parse(JSON.stringify(ulabel.subtasks[st]["class_ids"]));
// Add the reserved DELETE_CLASS_ID if it is present in the class_defs
if (ulabel.subtasks[st]["class_defs"].at(-1)["id"] === DELETE_CLASS_ID) {
class_ids.push(DELETE_CLASS_ID);
}

for (let i = 0; i < class_ids.length; i++) {

Expand Down
52 changes: 30 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Uncertain Labeling Tool
Sentera Inc.
*/
import { ULabelAnnotation } from '../build/annotation';
import { ULabelAnnotation, DELETE_CLASS_ID, DELETE_MODES } from '../build/annotation';
import { ULabelSubtask } from '../build/subtask';
import { GeometricUtils } from '../build/geometric_utils';
import { Configuration, AllowedToolboxItem } from '../build/configuration';
Expand Down Expand Up @@ -42,9 +42,6 @@ jQuery.fn.outer_html = function () {

const MODES_3D = ["global", "bbox3"];
const NONSPATIAL_MODES = ["whole-image", "global"];
// Modes used to draw an area in the which to delete all annotations
const DELETE_MODES = ["delete_polygon", "delete_bbox"]
const DELETE_CLASS_ID = -1;

export class ULabel {

Expand Down Expand Up @@ -607,7 +604,6 @@ export class ULabel {
// Default to crimson
"color": COLORS[1]
})
subtask.class_ids.push(DELETE_CLASS_ID)
ulabel.valid_class_ids.push(DELETE_CLASS_ID)
ulabel.color_info[DELETE_CLASS_ID] = COLORS[1]
}
Expand Down Expand Up @@ -787,6 +783,7 @@ export class ULabel {
"starting_complex_polygon": false,
"is_in_brush_mode": false,
"is_in_erase_mode": false,
"line_size": ul.subtasks[subtask_key]["default_line_size"],
"edit_candidate": null,
"move_candidate": null,

Expand Down Expand Up @@ -886,7 +883,7 @@ export class ULabel {
"annotation_meta": arguments[6] ?? null, // Use default if optional argument is undefined
"px_per_px": arguments[7] ?? 1, // Use default if optional argument is undefined
"initial_crop": arguments[8] ?? null, // Use default if optional argument is undefined
"initial_line_size": arguments[9] ?? 4, // Use default if optional argument is undefined
"initial_line_size": arguments[9] ?? null, // Use default if optional argument is undefined
"config_data": arguments[10] ?? null, // Use default if optional argument is undefined
"toolbox_order": arguments[11] ?? null // Use default if optional argument is undefined
}
Expand Down Expand Up @@ -942,7 +939,7 @@ export class ULabel {
const annotation_meta = kwargs["annotation_meta"] ?? null
const px_per_px = kwargs["px_per_px"] ?? 1
const initial_crop = kwargs["initial_crop"] ?? null // {top: #, left: #, height: #, width: #,}
const initial_line_size = kwargs["initial_line_size"] ?? 4
const initial_line_size = kwargs["initial_line_size"] ?? null
const instructions_url = kwargs["instructions_url"] ?? null
const config_data = kwargs["config_data"] ?? null
const toolbox_order = kwargs["toolbox_order"] ?? null
Expand Down Expand Up @@ -1563,8 +1560,13 @@ export class ULabel {
this.set_id_dialog_payload_to_init(null);
if (DELETE_MODES.includes(spatial_type)) {
// Use special id payload for delete modes
return [{
"class_id": DELETE_CLASS_ID,
"confidence": 1.0,
}]
} else {
return JSON.parse(JSON.stringify(this.subtasks[this.state["current_subtask"]]["state"]["id_payload"]));
}
return JSON.parse(JSON.stringify(this.subtasks[this.state["current_subtask"]]["state"]["id_payload"]));
}

// Create a new canvas for an individual annotation and return its context
Expand Down Expand Up @@ -1877,8 +1879,7 @@ export class ULabel {
let line_size = null;
if ("line_size" in annotation_object) {
line_size = annotation_object["line_size"];
}
else {
} else {
line_size = this.get_line_size(demo);
}

Expand Down Expand Up @@ -3628,18 +3629,25 @@ export class ULabel {
}

get_line_size(demo = false) {
let line_size = this.state["line_size"] * this.config["px_per_px"];
if (demo) {
if (this.state["size_mode"] === "dynamic") {
line_size *= this.state["zoom_val"];
}
return line_size;
}
else {
if (this.state["size_mode"] === "fixed") {
line_size /= this.state["zoom_val"];
// If the user did not specify an initial_line_size, then this.state["line_size"] will be null.
// This indicates that we will scale the line size based on the zoom level
if (this.state["line_size"] === null) {
// 4 is the legacy default line size
let line_size = 4 * this.config["px_per_px"];
if (demo) {
if (this.state["size_mode"] === "dynamic") {
line_size *= this.state["zoom_val"];
}
return line_size;
} else {
if (this.state["size_mode"] === "fixed") {
line_size /= this.state["zoom_val"];
}
return line_size;
}
return line_size;
} else {
// Default to the user-specified line size
return this.state["line_size"] * this.config["px_per_px"];
}
}

Expand Down Expand Up @@ -3947,7 +3955,7 @@ export class ULabel {
gmx = this.get_global_mouse_x(mouse_event);
gmy = this.get_global_mouse_y(mouse_event);
init_spatial = this.get_init_spatial(gmx, gmy, annotation_mode);
init_id_payload = this.get_init_id_payload();
init_id_payload = this.get_init_id_payload(annotation_mode);
this.hide_edit_suggestion();
this.hide_global_edit_suggestion();
} else {
Expand Down
37 changes: 26 additions & 11 deletions src/toolbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ export class ModeSelectionToolboxItem extends ToolboxItem {
BrushToolboxItem.show_brush_toolbox_item();
} else {
BrushToolboxItem.hide_brush_toolbox_item();
// Turn off erase mode if it's on
if (ulabel.subtasks[current_subtask]["state"]["is_in_erase_mode"]) {
ulabel.toggle_erase_mode(e);
}
// Turn off brush mode if it's on
if (ulabel.subtasks[current_subtask]["state"]["is_in_brush_mode"]) {
ulabel.toggle_brush_mode(e);
Expand Down Expand Up @@ -1014,6 +1018,12 @@ export class AnnotationIDToolboxItem extends ToolboxItem {
}
}

/**
* Get the html skeleton for this ToolboxItem. The actual ID selection items will be added
* in html_builder.ts in the function build_id_dialogs()
*
* @returns html string
*/
public get_html() {
return `
<div class="classification">
Expand Down Expand Up @@ -1155,16 +1165,16 @@ export class AnnotationResizeItem extends ToolboxItem {
let cached_size_property = ulabel.subtasks[subtask].display_name.replaceLowerConcat(" ", "-", "-cached-size")
let size_cookie = this.read_size_cookie(ulabel.subtasks[subtask])
if ((size_cookie != null) && size_cookie != "NaN") {
this.update_annotation_size(ulabel.subtasks[subtask], Number(size_cookie));
this.update_annotation_size(ulabel, ulabel.subtasks[subtask], Number(size_cookie));
this[cached_size_property] = Number(size_cookie)
}
else if (ulabel.config.default_annotation_size != undefined) {
this.update_annotation_size(ulabel.subtasks[subtask], ulabel.config.default_annotation_size);
this.update_annotation_size(ulabel, ulabel.subtasks[subtask], ulabel.config.default_annotation_size);
this[cached_size_property] = ulabel.config.default_annotation_size
}
else {
const DEFAULT_SIZE = 5
this.update_annotation_size(ulabel.subtasks[subtask], DEFAULT_SIZE)
this.update_annotation_size(ulabel, ulabel.subtasks[subtask], DEFAULT_SIZE)
this[cached_size_property] = DEFAULT_SIZE
}
}
Expand Down Expand Up @@ -1260,7 +1270,7 @@ export class AnnotationResizeItem extends ToolboxItem {
const annotation_size = <ValidResizeValues> button.attr("id").slice(18);

// Update the size of all annotations in the subtask
this.update_annotation_size(current_subtask, annotation_size);
this.update_annotation_size(this.ulabel, current_subtask, annotation_size);

this.ulabel.redraw_all_annotations(null, null, false);
})
Expand All @@ -1275,19 +1285,19 @@ export class AnnotationResizeItem extends ToolboxItem {
this.update_all_subtask_annotation_size(this.ulabel, ValidResizeValues.VANISH)
break
case this.keybind_configuration.annotation_vanish.toLowerCase():
this.update_annotation_size(current_subtask, ValidResizeValues.VANISH)
this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.VANISH)
break
case this.keybind_configuration.annotation_size_small:
this.update_annotation_size(current_subtask, ValidResizeValues.SMALL)
this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.SMALL)
break
case this.keybind_configuration.annotation_size_large:
this.update_annotation_size(current_subtask, ValidResizeValues.LARGE)
this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.LARGE)
break
case this.keybind_configuration.annotation_size_minus:
this.update_annotation_size(current_subtask, ValidResizeValues.DECREMENT)
this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.DECREMENT)
break
case this.keybind_configuration.annotation_size_plus:
this.update_annotation_size(current_subtask, ValidResizeValues.INCREMENT)
this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.INCREMENT)
break
default:
// Return if no valid keybind was pressed
Expand All @@ -1307,7 +1317,7 @@ export class AnnotationResizeItem extends ToolboxItem {
* @param subtask Subtask which holds the annotations to act on
* @param size How to resize the annotations
*/
public update_annotation_size(subtask: ULabelSubtask, size: number | ValidResizeValues): void {
public update_annotation_size(ulabel: ULabel, subtask: ULabelSubtask, size: number | ValidResizeValues): void {
if (subtask === null) return;

// If in brush mode, the keybind was probably meant for the brush
Expand Down Expand Up @@ -1371,6 +1381,11 @@ export class AnnotationResizeItem extends ToolboxItem {
default:
console.error("update_annotation_size called with unknown size");
}

// Store the new size as the default if we should be tracking it
if (ulabel.state.line_size !== null) {
ulabel.state.line_size = this[subtask_cached_size];
}
}
//loops through all annotations in a subtask to change their line size
public loop_through_annotations(subtask, size, operation) {
Expand Down Expand Up @@ -1416,7 +1431,7 @@ export class AnnotationResizeItem extends ToolboxItem {
//Loop through all subtasks and apply a size to them all
public update_all_subtask_annotation_size(ulabel, size) {
for (let subtask in ulabel.subtasks) {
this.update_annotation_size(ulabel.subtasks[subtask], size)
this.update_annotation_size(ulabel, ulabel.subtasks[subtask], size)
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { ULabel } from ".."
import { ULabelSubtask } from "./subtask"
import { DELETE_CLASS_ID, DELETE_MODES } from "./annotation"

/**
* Checks if something is an object, not an array, and not null
Expand Down Expand Up @@ -45,6 +46,9 @@ export function get_active_class_id(ulabel: ULabel): number {
// If in single_class_mode return the only valid class id
if (current_subtask.single_class_mode) return current_subtask.class_ids[0]

// If currently in a delete mode, return the DELETE_CLASS_ID
if (DELETE_MODES.includes(current_subtask.state.annotation_mode)) return DELETE_CLASS_ID

// If the current subtask has more than one valid class id, loop through the id_payloads
for (const payload of current_subtask.state.id_payload) {
// If the payload is a number then the user hasn't selected a class yet, so the first class id is the current one
Expand Down
2 changes: 1 addition & 1 deletion src/version.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const ULABEL_VERSION = "0.9.2";
export const ULABEL_VERSION = "0.9.3";

0 comments on commit daefee7

Please sign in to comment.