From 2203564681ab5c674e40e3a30a8f350b07138f37 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 11:55:11 -0600 Subject: [PATCH 01/16] Properly vanish annotations by not drawing them instead of setting their size --- api_spec.md | 6 +- src/configuration.ts | 2 - src/index.js | 25 +++-- src/listeners.ts | 33 +++++++ src/subtask.ts | 2 + src/toolbox.ts | 231 +++++++++++++++---------------------------- 6 files changed, 133 insertions(+), 166 deletions(-) diff --git a/api_spec.md b/api_spec.md index 1a59b89b..2d3a5395 100644 --- a/api_spec.md +++ b/api_spec.md @@ -55,7 +55,6 @@ class ULabel({ reset_zoom_keybind: string, show_full_image_keybind: string, create_point_annotation_keybind: string, - default_annotation_size: number, delete_annotation_keybind: string, keypoint_slider_default_value: number, filter_annotations_on_load: boolean, @@ -316,7 +315,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. When this value is not included, the default value of `4` is used. +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 `5` is used. ### `anno_scaling_mode` @@ -433,9 +432,6 @@ Keybind to set the zoom level to show the full image. Default is `shift+r`. ### `create_point_annotation_keybind` Keybind to create a point annotation at the mouse location. Default is `c`. Requires the active subtask to have a `point` mode. -### `default_annotation_size` -Default size of annotations in pixels. Default is `6`. - ### `delete_annotation_keybind` Keybind to delete the annotation that the mouse is hovering over. Default is `d`. diff --git a/src/configuration.ts b/src/configuration.ts index 3a3ad05d..0d9ad005 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -196,8 +196,6 @@ export class Configuration { public create_point_annotation_keybind: string = "c"; - public default_annotation_size: number = 6; - public delete_annotation_keybind: string = "d"; public keypoint_slider_default_value: number; diff --git a/src/index.js b/src/index.js index 0e2dcb6a..a6708d64 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,7 @@ import { remove_ulabel_listeners } from "../build/listeners"; import { log_message, LogLevel } from "../build/error_logging"; import { initialize_annotation_canvases } from "../build/canvas_utils"; import { record_action, record_finish, record_finish_edit, record_finish_move, undo, redo } from "../build/actions"; +import { DEFAULT_ANNOTATION_SIZE } from "../build/toolbox"; import $ from "jquery"; const jQuery = $; @@ -270,7 +271,7 @@ export class ULabel { if ( (cand.line_size === undefined) || (cand.line_size == null) ) { - cand.line_size = ul.get_initial_line_size(); + cand.line_size = ul.get_subtask_line_size(subtask_key); } // Add created by attribute if there is none @@ -446,9 +447,11 @@ export class ULabel { starting_complex_polygon: false, is_in_brush_mode: false, is_in_erase_mode: false, + is_vanished: false, edit_candidate: null, move_candidate: null, fly_to_idx: null, + line_size: ul.config.initial_line_size, // Rendering context front_context: null, @@ -520,7 +523,7 @@ export class ULabel { annotation_meta: arguments[6] ?? {}, px_per_px: arguments[7] ?? 1, initial_crop: arguments[8] ?? null, - initial_line_size: arguments[9] ?? 4, + initial_line_size: arguments[9] ?? DEFAULT_ANNOTATION_SIZE, config_data: arguments[10] ?? null, toolbox_order: arguments[11] ?? null, }; @@ -584,7 +587,6 @@ export class ULabel { // Global annotation state (subtasks also maintain an annotation state) current_subtask: null, // The key of the current subtask last_brush_stroke: null, - line_size: this.config.initial_line_size, anno_scaling_mode: this.config.anno_scaling_mode, // Keybind editing state @@ -1906,6 +1908,10 @@ export class ULabel { redraw_all_annotations_in_annotation_context(canvas_id, subtask, offset = null, annotation_ids_to_offset = null) { // Clear the canvas this.clear_annotation_canvas(canvas_id, subtask); + + // If the subtask is vanished, don't draw anything + if (this.subtasks[subtask]["state"]["is_vanished"]) return; + // Handle redraw of each annotation in the context for (const annid of this.subtasks[subtask]["state"]["annotation_contexts"][canvas_id]["annotation_ids"]) { // Only draw with offset if the annotation is in the list of annotations to offset, or if the list is null @@ -2994,7 +3000,7 @@ export class ULabel { spatial_payload: spatial_payload, classification_payloads: this.get_init_id_payload(spatial_type), text_payload: "", - line_size: this.get_initial_line_size(), + line_size: this.get_subtask_line_size(), canvas_id: this.get_init_canvas_context_id(unique_id), }; @@ -3282,7 +3288,7 @@ export class ULabel { if ("line_size" in annotation && annotation["line_size"] !== null) { line_size = annotation["line_size"]; } else { - line_size = this.get_initial_line_size(); + line_size = this.get_subtask_line_size(); } // fixed: line size is independent of zoom level @@ -3297,8 +3303,11 @@ export class ULabel { return line_size; } - get_initial_line_size() { - return this.state.line_size; + get_subtask_line_size(subtask_key = null) { + if (subtask_key === null) { + subtask_key = this.get_current_subtask_key(); + } + return this.subtasks[subtask_key]["state"]["line_size"]; } // Action Stream Events @@ -3394,7 +3403,7 @@ export class ULabel { if (redo_payload === null) { annotation_id = this.make_new_annotation_id(); - line_size = this.get_initial_line_size(); + line_size = this.get_subtask_line_size(subtask_key); annotation_mode = current_subtask["state"]["annotation_mode"]; [gmx, gmy] = this.get_image_aware_mouse_x_y(mouse_event); init_spatial = this.get_init_spatial(gmx, gmy, annotation_mode, mouse_event); diff --git a/src/listeners.ts b/src/listeners.ts index b5f74910..663704b0 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -10,6 +10,7 @@ import type { ULabel } from ".."; import { NightModeCookie } from "./cookies"; import { DELETE_CLASS_ID, DELETE_MODES, NONSPATIAL_MODES } from "./annotation"; import { set_local_storage_item } from "./utilities"; +import { AnnotationResizeItem, SMALL_ANNOTATION_SIZE, LARGE_ANNOTATION_SIZE, INCREMENT_ANNOTATION_SIZE } from "./toolbox"; const ULABEL_NAMESPACE = ".ulabel"; @@ -314,6 +315,38 @@ function handle_keydown_event( ulabel.fly_to_next_annotation(-1, ulabel.config.fly_to_max_zoom); return false; } + + // Handle annotation resizing keybinds + // TODO: vanish all subtasks? + if (event_matches_keybind(keydown_event, ulabel.config.annotation_vanish_keybind)) { + keydown_event.preventDefault(); + AnnotationResizeItem.toggle_subtask_vanished(ulabel, ulabel.get_current_subtask_key()); + return false; + } + + if (event_matches_keybind(keydown_event, ulabel.config.annotation_size_small_keybind)) { + keydown_event.preventDefault(); + AnnotationResizeItem.update_annotation_size(ulabel, ulabel.get_current_subtask_key(), SMALL_ANNOTATION_SIZE); + return false; + } + + if (event_matches_keybind(keydown_event, ulabel.config.annotation_size_large_keybind)) { + keydown_event.preventDefault(); + AnnotationResizeItem.update_annotation_size(ulabel, ulabel.get_current_subtask_key(), LARGE_ANNOTATION_SIZE); + return false; + } + + if (event_matches_keybind(keydown_event, ulabel.config.annotation_size_minus_keybind)) { + keydown_event.preventDefault(); + AnnotationResizeItem.update_annotation_size(ulabel, ulabel.get_current_subtask_key(), -INCREMENT_ANNOTATION_SIZE, true); + return false; + } + + if (event_matches_keybind(keydown_event, ulabel.config.annotation_size_plus_keybind)) { + keydown_event.preventDefault(); + AnnotationResizeItem.update_annotation_size(ulabel, ulabel.get_current_subtask_key(), INCREMENT_ANNOTATION_SIZE, true); + return false; + } } /** diff --git a/src/subtask.ts b/src/subtask.ts index a1b2b219..9c7336a4 100644 --- a/src/subtask.ts +++ b/src/subtask.ts @@ -43,11 +43,13 @@ export class ULabelSubtask { starting_complex_polygon: boolean; is_in_brush_mode: boolean; is_in_erase_mode: boolean; + is_vanished: boolean; visible_dialogs: { [key: string]: ULabelDialogPosition; }; spatial_type: ULabelSpatialType; fly_to_idx: number | null; + line_size: number; }; constructor( diff --git a/src/toolbox.ts b/src/toolbox.ts index be6ffac7..528a687b 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -24,6 +24,7 @@ import { get_local_storage_item, set_local_storage_item, } from "./utilities"; +import { log_message, LogLevel } from "./error_logging"; // For ResizeToolboxItem enum ValidResizeValues { @@ -34,8 +35,13 @@ enum ValidResizeValues { DECREMENT = "dec", } +export const SMALL_ANNOTATION_SIZE = 1.5; +export const LARGE_ANNOTATION_SIZE = 5.0; +export const INCREMENT_ANNOTATION_SIZE = 0.5; +export const MINIMUM_ANNOTATION_SIZE = 0.01; +export const DEFAULT_ANNOTATION_SIZE = 5.0; + const toolboxDividerDiv = "
"; -const vanish_size = 0.01; /** Chains the replaceAll method and the toLowerCase method. * Optionally concatenates a string at the end of the method. @@ -1208,21 +1214,14 @@ export class AnnotationResizeItem extends ToolboxItem { this.ulabel = ulabel; // First check for a size cookie, if one isn't found then check the config - // for a default annotation size. If neither are found it will use the size - // that the annotation was saved as. - for (const subtask in ulabel.subtasks) { - const cached_size_property = ulabel.subtasks[subtask].display_name.replaceLowerConcat(" ", "-", "-cached-size"); - const size_cookie = this.read_size_cookie(ulabel.subtasks[subtask]); + for (const subtask_key in ulabel.subtasks) { + const size_cookie = this.read_size_cookie(ulabel.subtasks[subtask_key]); if ((size_cookie != null) && size_cookie != "NaN") { - 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, ulabel.subtasks[subtask], ulabel.config.default_annotation_size); - this[cached_size_property] = ulabel.config.default_annotation_size; + AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, Number(size_cookie), false, false); + } else if (ulabel.config.initial_line_size != undefined) { + AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, ulabel.config.initial_line_size, false, false); } else { - const DEFAULT_SIZE = 5; - this.update_annotation_size(ulabel, ulabel.subtasks[subtask], DEFAULT_SIZE); - this[cached_size_property] = DEFAULT_SIZE; + AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, DEFAULT_ANNOTATION_SIZE, false, false); } } @@ -1308,181 +1307,111 @@ export class AnnotationResizeItem extends ToolboxItem { $(document).on("click.ulabel", ".annotation-resize-button", (event) => { // Get the current subtask const current_subtask_key = this.ulabel.get_current_subtask_key(); - const current_subtask = this.ulabel.get_current_subtask(); // Get the clicked button const button = $(event.currentTarget); // Use the button id to get what size to resize the annotations to - const annotation_size = button.attr("id").slice(18); + const button_value = button.attr("id").slice(18); - // Update the size of all annotations in the subtask - this.update_annotation_size(this.ulabel, current_subtask, annotation_size); - - this.ulabel.redraw_all_annotations(current_subtask_key, null, false); - }); - - $(document).on("keydown.ulabel", (event) => { - // Get the current subtask - const current_subtask = this.ulabel.get_current_subtask(); - - switch (event.key) { - case this.ulabel.config.annotation_vanish_keybind.toUpperCase(): - this.update_all_subtask_annotation_size(this.ulabel, ValidResizeValues.VANISH); - break; - case this.ulabel.config.annotation_vanish_keybind.toLowerCase(): - this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.VANISH); + let annotation_size: number; + let increment: boolean = false; + switch (button_value) { + case ValidResizeValues.SMALL: + annotation_size = SMALL_ANNOTATION_SIZE; break; - case this.ulabel.config.annotation_size_small_keybind: - this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.SMALL); + case ValidResizeValues.LARGE: + annotation_size = LARGE_ANNOTATION_SIZE; break; - case this.ulabel.config.annotation_size_large_keybind: - this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.LARGE); + case ValidResizeValues.INCREMENT: + annotation_size = INCREMENT_ANNOTATION_SIZE; + increment = true; break; - case this.ulabel.config.annotation_size_minus_keybind: - this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.DECREMENT); + case ValidResizeValues.DECREMENT: + annotation_size = -INCREMENT_ANNOTATION_SIZE; + increment = true; break; - case this.ulabel.config.annotation_size_plus_keybind: - this.update_annotation_size(this.ulabel, current_subtask, ValidResizeValues.INCREMENT); - break; - default: - // Return if no valid keybind was pressed + case ValidResizeValues.VANISH: + // Toggle the vanished flag for the current subtask and return + AnnotationResizeItem.toggle_subtask_vanished(this.ulabel, current_subtask_key); return; + default: + log_message(`Invalid Resize Value: ${button_value}`, LogLevel.ERROR, true); + break; } - // If the sizes were updated resize the annotations - this.ulabel.redraw_all_annotations(null, null, false); + // Update the size of all annotations in the subtask + AnnotationResizeItem.update_annotation_size(this.ulabel, current_subtask_key, annotation_size, increment); }); } /** - * Takes in either a number or a ValidResizeValues.value. If given a number it will resize all annotations in the subtask to - * be that size. The ValidResizeValues will either set the size of all annotations to set values or increment/decrement the - * current size of the annotations. + * Update the size of all annotations in a subtask. * - * @param subtask Subtask which holds the annotations to act on - * @param size How to resize the annotations + * @param {ULabel} ulabel ULabel instance + * @param {string} subtask_key Key of the subtask to update + * @param {number} size Size to set/increment/decrement the annotations + * @param {boolean} increment Whether or not to increment (true) or set (false) the size + * @param {boolean} redraw Whether to redraw the annotation */ - public update_annotation_size(ulabel: ULabel, subtask: ULabelSubtask, size: number | ValidResizeValues): void { + public static update_annotation_size(ulabel: ULabel, subtask_key: string, size: number, increment: boolean = false, redraw: boolean = true): void { + const subtask = ulabel.subtasks[subtask_key]; if (subtask === null) return; - const small_size = 1.5; - const large_size = 5; - const increment_size = 0.5; - const subtask_cached_size = subtask.display_name.replaceLowerConcat(" ", "-", "-cached-size"); - const subtask_vanished_flag = subtask.display_name.replaceLowerConcat(" ", "-", "-vanished"); - - // If the annotations are currently vanished and a button other than the vanish button is - // pressed, then we want to ignore the input - if (this[subtask_vanished_flag] && size !== "v") return; - - // If a number was passed in, set all annotations to be the size of the number - if (typeof (size) === "number") { - this.loop_through_annotations(subtask, size, "="); - return; - } + // If the annotations are currently vanished, don't resize them + if (subtask.state.is_vanished) return; - // Otherwise handle each ValidResizeValues case here - switch (size) { - case ValidResizeValues.SMALL: - this.loop_through_annotations(subtask, small_size, "="); - this[subtask_cached_size] = small_size; - break; - case ValidResizeValues.LARGE: - this.loop_through_annotations(subtask, large_size, "="); - this[subtask_cached_size] = large_size; - break; - case ValidResizeValues.DECREMENT: - this.loop_through_annotations(subtask, increment_size, "-"); - if (this[subtask_cached_size] - increment_size > vanish_size) { - this[subtask_cached_size] -= increment_size; - } else { - this[subtask_cached_size] = vanish_size; - } - break; - case ValidResizeValues.INCREMENT: - this.loop_through_annotations(subtask, increment_size, "+"); - this[subtask_cached_size] += increment_size; - break; - case ValidResizeValues.VANISH: - if (this[subtask_vanished_flag]) { - // Re-apply the cashed annotation size - this.loop_through_annotations(subtask, this[subtask_cached_size], "="); - - // Filp the state - this[subtask_vanished_flag] = !this[subtask_vanished_flag]; - - // Unlock the vanish button - $("#annotation-resize-v").removeClass("locked"); - } else { - // Apply the vanish size to make the annotations to small to see - this.loop_through_annotations(subtask, vanish_size, "="); - - // Filp the state - this[subtask_vanished_flag] = !this[subtask_vanished_flag]; - - // Lock the vanish button - $("#annotation-resize-v").addClass("locked"); - } - break; - default: - console.error("update_annotation_size called with unknown size"); - } + // Set the size of the annotations to the given size + AnnotationResizeItem.loop_through_annotations(subtask, size, increment); - // 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]; + // If the sizes were updated, resize the annotations + if (redraw) { + ulabel.redraw_all_annotations(subtask_key, null, false); } } // Loop through all annotations in a subtask and change their line size - public loop_through_annotations(subtask: ULabelSubtask, size: number, operation: "=" | "+" | "-") { - for (const annotation_id in subtask.annotations.access) { - switch (operation) { - case "=": - subtask.annotations.access[annotation_id].line_size = size; - break; - case "+": - subtask.annotations.access[annotation_id].line_size += size; - break; - case "-": - // Check to make sure annotation line size won't go 0 or negative. - // If it would, set it equal to a small positive number - if (subtask.annotations.access[annotation_id].line_size - size <= vanish_size) { - subtask.annotations.access[annotation_id].line_size = vanish_size; - } else { - subtask.annotations.access[annotation_id].line_size -= size; - } - break; - default: - throw Error("Invalid Operation given to loop_through_annotations"); + public static loop_through_annotations(subtask: ULabelSubtask, size: number, increment: boolean) { + // Update subtask line size + if (increment) { + // Guard against the line size going too small or negative + if (subtask.state.line_size + size < MINIMUM_ANNOTATION_SIZE) { + subtask.state.line_size = MINIMUM_ANNOTATION_SIZE; + } else { + subtask.state.line_size += size; } + } else { + // Set the line size to the given size + subtask.state.line_size = size; } - if (subtask.annotations.ordering.length > 0) { - const line_size = subtask.annotations.access[subtask.annotations.ordering[0]].line_size; - if (line_size !== vanish_size) { - this.set_size_cookie(line_size, subtask); - } + for (const annotation of Object.values(subtask.annotations.access)) { + // Update each annotation's line size + // TODO: do individual annotations need their own line_size property? + annotation.line_size = subtask.state.line_size; } } - // Loop through all subtasks and apply a size to them all - public update_all_subtask_annotation_size(ulabel, size) { - for (const subtask in ulabel.subtasks) { - this.update_annotation_size(ulabel, ulabel.subtasks[subtask], size); - } - } + /** + * Set the vanished flag for a subtask and update the vanish button + * + * @param {ULabel} ulabel ULabel + * @param {string} subtask_key Key of the subtask to update + */ + public static toggle_subtask_vanished(ulabel: ULabel, subtask_key: string) { + // Toggle the vanished flag for the subtask + const new_value = !ulabel.subtasks[subtask_key].state.is_vanished; + ulabel.subtasks[subtask_key].state.is_vanished = new_value; - public redraw_update(ulabel: ULabel): void { - // Ensure the vanish button reflects the vanish state of the current subtask - const current_subtask = ulabel.get_current_subtask(); - const subtask_vanished_flag = current_subtask.display_name.replaceLowerConcat(" ", "-", "-vanished"); - if (this[subtask_vanished_flag]) { + // Lock/unlock the vanish button + if (new_value) { $("#annotation-resize-v").addClass("locked"); } else { $("#annotation-resize-v").removeClass("locked"); } + + // Redraw the annotations + ulabel.redraw_all_annotations(subtask_key, null, false); } private set_size_cookie(cookie_value, subtask) { From b2a71ebdcaae84b33bc2d5ec8467a253e673fba5 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 12:06:54 -0600 Subject: [PATCH 02/16] Add vanish all keybind --- api_spec.md | 5 ++++- index.d.ts | 1 + src/configuration.ts | 2 ++ src/index.js | 1 + src/listeners.ts | 16 +++++++++++++++- src/toolbox_items/keybinds.ts | 10 +++++++++- 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/api_spec.md b/api_spec.md index 2d3a5395..35db3ae7 100644 --- a/api_spec.md +++ b/api_spec.md @@ -481,7 +481,10 @@ Keybind to increment the annotation size for the current subtask. Default is `=` Keybind to decrement the annotation size for the current subtask. Default is `-`. ### `annotation_vanish_keybind` -Keybind to toggle vanish mode for annotations in the current subtask (lowercase toggles current subtask, uppercase toggles all subtasks). Default is `v`. +Keybind to toggle vanish mode for annotations in the current subtask. Default is `v`. + +### `annotation_vanish_all_keybind` +Keybind to toggle vanish mode for all subtasks. Default is `shift+v` ### `fly_to_max_zoom` Maximum zoom factor used when flying-to an annotation. Default is `10`, value must be > `0`. diff --git a/index.d.ts b/index.d.ts index 68a5e619..7ec35d68 100644 --- a/index.d.ts +++ b/index.d.ts @@ -258,6 +258,7 @@ export class ULabel { // TODO (joshua-dean): this is never assigned, is it used? demo_canvas_context: CanvasRenderingContext2D; edited: boolean; + all_subtasks_vanished: boolean; }; config: Configuration; diff --git a/src/configuration.ts b/src/configuration.ts index 0d9ad005..b685b2d5 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -226,6 +226,8 @@ export class Configuration { public annotation_vanish_keybind: string = "v"; + public annotation_vanish_all_keybind: string = "shift+v"; + public fly_to_next_annotation_keybind: string = "tab"; public fly_to_previous_annotation_keybind: string = "shift+tab"; diff --git a/src/index.js b/src/index.js index a6708d64..f1aa1c37 100644 --- a/src/index.js +++ b/src/index.js @@ -595,6 +595,7 @@ export class ULabel { // Renderings state demo_canvas_context: null, edited: false, + all_subtasks_vanished: false, }; // Create a place on ulabel to store resize observer objects diff --git a/src/listeners.ts b/src/listeners.ts index 663704b0..05007836 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -317,7 +317,21 @@ function handle_keydown_event( } // Handle annotation resizing keybinds - // TODO: vanish all subtasks? + + if (event_matches_keybind(keydown_event, ulabel.config.annotation_vanish_all_keybind)) { + keydown_event.preventDefault(); + // Toggle global vanish flag + ulabel.state.all_subtasks_vanished = !ulabel.state.all_subtasks_vanished; + + // For each subtask, toggle vanished state if needed + for (const subtask_key in ulabel.subtasks) { + if (ulabel.subtasks[subtask_key].state.is_vanished !== ulabel.state.all_subtasks_vanished) { + AnnotationResizeItem.toggle_subtask_vanished(ulabel, subtask_key); + } + } + return false; + } + if (event_matches_keybind(keydown_event, ulabel.config.annotation_vanish_keybind)) { keydown_event.preventDefault(); AnnotationResizeItem.toggle_subtask_vanished(ulabel, ulabel.get_current_subtask_key()); diff --git a/src/toolbox_items/keybinds.ts b/src/toolbox_items/keybinds.ts index 97fe8619..a19a924f 100644 --- a/src/toolbox_items/keybinds.ts +++ b/src/toolbox_items/keybinds.ts @@ -423,11 +423,19 @@ export class KeybindsToolboxItem extends ToolboxItem { keybinds.push({ key: config.annotation_vanish_keybind, label: "Toggle Vanish", - description: "Toggle annotation vanish mode", + description: "Toggle annotation vanish mode for current subtask", configurable: true, config_key: "annotation_vanish_keybind", }); + keybinds.push({ + key: config.annotation_vanish_all_keybind, + label: "Toggle Vanish All", + description: "Toggle vanish mode for all subtasks", + configurable: true, + config_key: "annotation_vanish_all_keybind", + }); + // Add class keybinds const current_subtask = this.ulabel.get_current_subtask(); if (current_subtask && current_subtask.class_defs) { From c4891e4817eb0a4deef8b5d932d961bf0e18a3cf Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 12:19:10 -0600 Subject: [PATCH 03/16] show/hide dialogs on vanish/unvanish --- src/index.js | 1 + src/toolbox.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/index.js b/src/index.js index f1aa1c37..35557d40 100644 --- a/src/index.js +++ b/src/index.js @@ -4837,6 +4837,7 @@ export class ULabel { // Don't show any dialogs when currently drawing/editing an annotation, // And hide just edit dialogs when moving if ( + current_subtask["state"]["is_vanished"] || current_subtask["state"]["is_in_progress"] || current_subtask["state"]["starting_complex_polygon"] || current_subtask["state"]["is_in_brush_mode"] || diff --git a/src/toolbox.ts b/src/toolbox.ts index 528a687b..316e224d 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -1412,6 +1412,9 @@ export class AnnotationResizeItem extends ToolboxItem { // Redraw the annotations ulabel.redraw_all_annotations(subtask_key, null, false); + + // Show/hide dialogs + ulabel.suggest_edits(); } private set_size_cookie(cookie_value, subtask) { From 706a2ac25678f5de030df404d4ddc8a675e05b32 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 12:27:26 -0600 Subject: [PATCH 04/16] remove size cookie --- src/configuration.ts | 2 +- src/index.js | 3 +-- src/toolbox.ts | 42 +----------------------------------------- 3 files changed, 3 insertions(+), 44 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index b685b2d5..0e57f9b6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -124,7 +124,7 @@ export class Configuration { public allow_soft_id: boolean = false; public default_annotation_color: string = "#fa9d2a"; public username: string = "ULabelUser"; - public initial_line_size: number = 4; + public initial_line_size: number = 5; // ID Dialog config public cl_opacity: number = 0.4; diff --git a/src/index.js b/src/index.js index 35557d40..29542c0c 100644 --- a/src/index.js +++ b/src/index.js @@ -30,7 +30,6 @@ import { remove_ulabel_listeners } from "../build/listeners"; import { log_message, LogLevel } from "../build/error_logging"; import { initialize_annotation_canvases } from "../build/canvas_utils"; import { record_action, record_finish, record_finish_edit, record_finish_move, undo, redo } from "../build/actions"; -import { DEFAULT_ANNOTATION_SIZE } from "../build/toolbox"; import $ from "jquery"; const jQuery = $; @@ -523,7 +522,7 @@ export class ULabel { annotation_meta: arguments[6] ?? {}, px_per_px: arguments[7] ?? 1, initial_crop: arguments[8] ?? null, - initial_line_size: arguments[9] ?? DEFAULT_ANNOTATION_SIZE, + initial_line_size: arguments[9] ?? 5, config_data: arguments[10] ?? null, toolbox_order: arguments[11] ?? null, }; diff --git a/src/toolbox.ts b/src/toolbox.ts index 316e224d..a06eb4c6 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -39,7 +39,6 @@ export const SMALL_ANNOTATION_SIZE = 1.5; export const LARGE_ANNOTATION_SIZE = 5.0; export const INCREMENT_ANNOTATION_SIZE = 0.5; export const MINIMUM_ANNOTATION_SIZE = 0.01; -export const DEFAULT_ANNOTATION_SIZE = 5.0; const toolboxDividerDiv = "
"; @@ -1215,14 +1214,7 @@ export class AnnotationResizeItem extends ToolboxItem { // First check for a size cookie, if one isn't found then check the config for (const subtask_key in ulabel.subtasks) { - const size_cookie = this.read_size_cookie(ulabel.subtasks[subtask_key]); - if ((size_cookie != null) && size_cookie != "NaN") { - AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, Number(size_cookie), false, false); - } else if (ulabel.config.initial_line_size != undefined) { - AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, ulabel.config.initial_line_size, false, false); - } else { - AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, DEFAULT_ANNOTATION_SIZE, false, false); - } + AnnotationResizeItem.update_annotation_size(ulabel, subtask_key, ulabel.config.initial_line_size, false, false); } this.add_styles(); @@ -1417,38 +1409,6 @@ export class AnnotationResizeItem extends ToolboxItem { ulabel.suggest_edits(); } - private set_size_cookie(cookie_value, subtask) { - const d = new Date(); - d.setTime(d.getTime() + (10000 * 24 * 60 * 60 * 1000)); - - const subtask_name = subtask.display_name.replaceLowerConcat(" ", "_"); - - document.cookie = subtask_name + "_size=" + cookie_value + ";" + d.toUTCString() + ";path=/"; - } - - private read_size_cookie(subtask) { - const subtask_name = subtask.display_name.replaceLowerConcat(" ", "_"); - - const cookie_name = subtask_name + "_size="; - - const cookie_array = document.cookie.split(";"); - - for (let i = 0; i < cookie_array.length; i++) { - let current_cookie = cookie_array[i]; - - // while there's whitespace at the front of the cookie, loop through and remove it - while (current_cookie.charAt(0) == " ") { - current_cookie = current_cookie.substring(1); - } - - if (current_cookie.indexOf(cookie_name) == 0) { - return current_cookie.substring(cookie_name.length, current_cookie.length); - } - } - - return null; - } - public get_html() { return `
From 683735363d453f7c460a8661ca2a2f87d94633f5 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 12:36:58 -0600 Subject: [PATCH 05/16] fix resume from --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 29542c0c..41df469e 100644 --- a/src/index.js +++ b/src/index.js @@ -270,7 +270,7 @@ export class ULabel { if ( (cand.line_size === undefined) || (cand.line_size == null) ) { - cand.line_size = ul.get_subtask_line_size(subtask_key); + cand.line_size = ul.config.initial_line_size; } // Add created by attribute if there is none From 3bca9aaf24f7ce0ef5faab821b7118eda6fd3dad Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 12:58:36 -0600 Subject: [PATCH 06/16] fix vanish test --- tests/e2e/keybind-functionality.spec.js | 11 +++++------ tests/testing-utils/init_utils.js | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/e2e/keybind-functionality.spec.js b/tests/e2e/keybind-functionality.spec.js index 0bfd210e..547828c8 100644 --- a/tests/e2e/keybind-functionality.spec.js +++ b/tests/e2e/keybind-functionality.spec.js @@ -378,14 +378,14 @@ test.describe("Keybind Functionality Tests", () => { }); test("annotation_size keybinds should control annotation display size", async ({ page }) => { - await wait_for_ulabel_init(page); + const ulabel = await wait_for_ulabel_init(page); // Get all annotation size keybinds from the toolbox const small_keybind = await get_keybind_value(page, "Size: Small"); const large_keybind = await get_keybind_value(page, "Size: Large"); const plus_keybind = await get_keybind_value(page, "Size: Increase"); const minus_keybind = await get_keybind_value(page, "Size: Decrease"); - const vanish_keybind = await get_keybind_value(page, "Toggle Vanish"); + const vanish_keybind = await get_keybind_value(page, "Toggle Vanish All"); // Create an annotation await draw_bbox(page, [200, 200], [400, 400]); @@ -416,12 +416,11 @@ test.describe("Keybind Functionality Tests", () => { annotation = await get_annotation_by_index(page, 0); expect(annotation.line_size).toBe(size_before_minus - 0.5); - // Test vanish keybind - should set size to 0.01 (vanished) - const size_before_vanish = annotation.line_size; + // Test vanish keybind - should toggle vanish mode + const vanish_state_before = ulabel.get_current_subtask().state.is_vanished; await press_keybind(page, vanish_keybind); await page.waitForTimeout(200); - annotation = await get_annotation_by_index(page, 0); - expect(annotation.line_size).toBe(0.01); + expect(ulabel.get_current_subtask().state.is_vanished).not.toBe(vanish_state_before); // Press vanish again to restore - should go back to previous size await press_keybind(page, vanish_keybind); diff --git a/tests/testing-utils/init_utils.js b/tests/testing-utils/init_utils.js index 469c569c..5ece584f 100644 --- a/tests/testing-utils/init_utils.js +++ b/tests/testing-utils/init_utils.js @@ -12,4 +12,6 @@ export async function wait_for_ulabel_init(page, url = "/multi-class.html") { await page.goto(url); await page.waitForFunction(() => window.ulabel && window.ulabel.is_init); + // Return the ulabel instance for convenience + return await page.evaluate(() => window.ulabel); } From 8c0b3f6def99f4d2bfb1a1b79fda10d6ae2738d0 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 13:22:32 -0600 Subject: [PATCH 07/16] actually return ulabel after init --- tests/testing-utils/init_utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/testing-utils/init_utils.js b/tests/testing-utils/init_utils.js index 5ece584f..5370a126 100644 --- a/tests/testing-utils/init_utils.js +++ b/tests/testing-utils/init_utils.js @@ -7,11 +7,13 @@ * Navigate to a page and wait for ULabel to initialize. * @param {Page} page - The Playwright page object * @param {string} url - The URL to navigate to (default: "/multi-class.html") - * @returns {Promise} + * @returns {Promise} - The initialized ULabel instance */ export async function wait_for_ulabel_init(page, url = "/multi-class.html") { await page.goto(url); await page.waitForFunction(() => window.ulabel && window.ulabel.is_init); // Return the ulabel instance for convenience - return await page.evaluate(() => window.ulabel); + return await page.evaluate(() => { + return window.ulabel; + }); } From 690e6bac86d06708c45454e7464d445b2a8550e7 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Mon, 3 Nov 2025 13:57:30 -0600 Subject: [PATCH 08/16] actually fix test --- tests/e2e/keybind-functionality.spec.js | 13 ++++++------- tests/testing-utils/subtask_utils.js | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/e2e/keybind-functionality.spec.js b/tests/e2e/keybind-functionality.spec.js index 547828c8..b7e2e4df 100644 --- a/tests/e2e/keybind-functionality.spec.js +++ b/tests/e2e/keybind-functionality.spec.js @@ -3,7 +3,7 @@ import { test, expect } from "./fixtures"; import { wait_for_ulabel_init } from "../testing-utils/init_utils"; import { get_annotation_count, get_annotation_by_index, get_annotation_class_id } from "../testing-utils/annotation_utils"; import { draw_bbox } from "../testing-utils/drawing_utils"; -import { get_current_subtask_key } from "../testing-utils/subtask_utils"; +import { get_current_subtask_key, get_current_subtask } from "../testing-utils/subtask_utils"; /** * Helper function to press a keybind with modifiers @@ -378,7 +378,7 @@ test.describe("Keybind Functionality Tests", () => { }); test("annotation_size keybinds should control annotation display size", async ({ page }) => { - const ulabel = await wait_for_ulabel_init(page); + await wait_for_ulabel_init(page); // Get all annotation size keybinds from the toolbox const small_keybind = await get_keybind_value(page, "Size: Small"); @@ -417,16 +417,15 @@ test.describe("Keybind Functionality Tests", () => { expect(annotation.line_size).toBe(size_before_minus - 0.5); // Test vanish keybind - should toggle vanish mode - const vanish_state_before = ulabel.get_current_subtask().state.is_vanished; + const vanish_state_before = (await get_current_subtask(page)).state.is_vanished; await press_keybind(page, vanish_keybind); await page.waitForTimeout(200); - expect(ulabel.get_current_subtask().state.is_vanished).not.toBe(vanish_state_before); + expect((await get_current_subtask(page)).state.is_vanished).not.toBe(vanish_state_before); - // Press vanish again to restore - should go back to previous size + // Press vanish again to restore - should go back to previous state await press_keybind(page, vanish_keybind); await page.waitForTimeout(200); - annotation = await get_annotation_by_index(page, 0); - expect(annotation.line_size).toBe(size_before_vanish); + expect((await get_current_subtask(page)).state.is_vanished).toBe(vanish_state_before); }); test("brush mode keybinds should control brush state and size", async ({ page }) => { diff --git a/tests/testing-utils/subtask_utils.js b/tests/testing-utils/subtask_utils.js index 756e0f58..0f026199 100644 --- a/tests/testing-utils/subtask_utils.js +++ b/tests/testing-utils/subtask_utils.js @@ -12,6 +12,15 @@ export async function get_current_subtask_key(page) { return await page.evaluate(() => window.ulabel.get_current_subtask_key()); } +/** + * Get the current subtask + * @param {Page} page - The Playwright page object + * @returns {Promise} The current subtask + */ +export async function get_current_subtask(page) { + return await page.evaluate(() => window.ulabel.get_current_subtask()); +} + /** * Switch to a subtask by its index. * @param {Page} page - The Playwright page object From 2df564f57cbbc144faec6bbb4176dc5fd755e7cc Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 13 Jan 2026 09:18:16 -0600 Subject: [PATCH 09/16] clear fly to idx on set_annotations --- src/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 41df469e..4ecc7396 100644 --- a/src/index.js +++ b/src/index.js @@ -5854,9 +5854,7 @@ export class ULabel { this.subtasks[q[i]]["state"]["is_in_move"] = false; this.subtasks[q[i]]["state"]["is_in_progress"] = false; this.subtasks[q[i]]["state"]["active_id"] = null; - // TODO (joshua-dean): this line was probably a mistake - // It's at least 3 years old, and is a nop as far as I can tell - // this.show + this.subtasks[q[i]]["state"]["fly_to_idx"] = null; } this.drag_state = { active_key: null, From 90b660308e5545a7519e32d5d64447dc50ece9be Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 13 Jan 2026 09:34:14 -0600 Subject: [PATCH 10/16] fix collapse button positioning --- demo/resume-from.html | 20 +++++++++++++++++++- src/toolbox.ts | 7 ++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/demo/resume-from.html b/demo/resume-from.html index 8ed38b0c..7f9687ad 100644 --- a/demo/resume-from.html +++ b/demo/resume-from.html @@ -299,8 +299,26 @@ }); + -
+
+

Test Banner - This tests that the collapse button respects container positioning

+
+
+
+
\ No newline at end of file diff --git a/src/toolbox.ts b/src/toolbox.ts index 10c85c9a..7ba320c3 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -108,6 +108,11 @@ export class Toolbox { static add_styles() { const css = ` + .full_ulabel_container_ { + position: relative; + height: 100%; + } + #toolbox { width: 320px; background-color: white; @@ -130,7 +135,7 @@ export class Toolbox { } .toolbox-collapse-btn { - position: fixed; + position: absolute; top: 0; right: 0; z-index: 1000; From 1677c83a01867ef920fc7dbe94e73d4ce3a790bf Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 13 Jan 2026 09:52:10 -0600 Subject: [PATCH 11/16] hide frame dialogs on collapse --- .gitignore | 3 ++- src/toolbox.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cb723ffd..ee23a3dc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ build/* output.txt test-results/ playwright-report/ -dist \ No newline at end of file +dist +*.log \ No newline at end of file diff --git a/src/toolbox.ts b/src/toolbox.ts index 7ba320c3..2fca00a2 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -134,6 +134,10 @@ export class Toolbox { width: 100% !important; } + .full_ulabel_container_.toolbox-collapsed div.frame_annotation_dialog { + display: none !important; + } + .toolbox-collapse-btn { position: absolute; top: 0; From 39f741854f9e7f335d27a6a7f2502a3fb4c96de5 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 13 Jan 2026 10:29:21 -0600 Subject: [PATCH 12/16] Remove line_size from annotation object --- .github/tasks.md | 76 +------------------------ api_spec.md | 3 - demo/box-roi.html | 3 - demo/resume-from.html | 5 -- demo/row-filtering-example.html | 20 +------ src/annotation.ts | 1 - src/index.js | 50 +++++----------- src/toolbox.ts | 6 -- tests/annotation.test.js | 1 - tests/e2e/keybind-functionality.spec.js | 20 +++---- 10 files changed, 27 insertions(+), 158 deletions(-) diff --git a/.github/tasks.md b/.github/tasks.md index 1cdb8238..b5f9274e 100644 --- a/.github/tasks.md +++ b/.github/tasks.md @@ -1,77 +1,3 @@ ## Tasks -- [x] Add a sideways, clickable arrow to minimize the entire toolbox - - [x] Add collapse button to toolbox HTML - - [x] Add CSS styles for collapsed state - - [x] Add click handler to toggle collapsed state - - [x] Store collapsed state in localStorage - - [x] Test functionality - - [x] Move arrow to top of toolbox (instead of middle) - - [x] Make annbox expand when toolbox is collapsed - - [x] Make collapsed button visible -- [x] Create a keybinds toolbox item - - [x] Research existing keybinds in the codebase - - [x] Create basic keybinds toolbox item file - - [x] Register keybinds toolbox item in configuration - - [x] Display list of all keybinds (the key) labeled with the name - - [x] Add hover tooltips with detailed descriptions (using title attribute) - - [x] Add collision detection and red highlighting - - [x] Implement editing for configurable keybinds - - [x] Test functionality -- [x] Add support for keybind "chords" (ie, "shift+i") - - [x] Update keybind edit handler to capture modifier keys (shift, ctrl, alt) - - [x] Create chord string format (e.g., "shift+i", "ctrl+alt+d") - - [x] Update key comparison logic in listeners to support chords - - [x] Update display to show chords properly (displays captured chord automatically) - - [x] Test chord functionality -- [x] Store collapse/expand for applicable toolbox items - - [x] Keybinds - - [x] Annotation List - - [x] Image Filters -- [x] Make all keybinds configurable -- [x] Minor changes to existing keybinds - - [x] Rename "Change Zoom" keybind to "Reset Zoom" - - [x] Change "Toggle Mode" label in the keybind toolbox item to "Toggle Annotation Mode" -- [x] Make class keybinds configurable in the keybinds toolbox item -- [x] Store keybinds in local storage - - [x] Only save them when a user explicitly sets it - - [x] For keybinds using a user setting, add a button to reset that keybind to default (should change keybind and delete stored keybind) - - [x] Add "Reset All to Default" button in the keybinds toolbox item that resets all keybinds and deletes stored user keybinds - - [x] Add a light yellow highlight on keybinds that are using a user setting instead of a default - - [x] Make sure that we update collison highlights after resetting a keybind to default - - [x] Only show the reset to default for keybinds with user settings, not on those already at the default - - [x] Make sure the class keybinds also are included in the keybind collision checks - - [x] Fix reset to default to use constructor-provided config values instead of hardcoded Configuration defaults - - [x] Centralize keybind config property names in Configuration.KEYBIND_CONFIG_KEYS constant - - [x] Make KEYBIND_CONFIG_KEYS dynamically generated from Configuration class properties - - [x] Rename create_bbox_on_initial_crop to create_bbox_on_initial_crop_keybind for consistency -- [x] Replace any console outputs with `log_message` -- [x] Replace the "reset_zoom_keybind" with two separate keybinds: - - [x] Add "show_full_image_keybind" property to Configuration - - [x] Update listeners.ts to use both keybinds independently - - [x] Update toolbox.ts to use both keybinds independently - - [x] Update api_spec.md to document both keybinds -- [x] Write e2e tests for the keybind toolbox item - - [x] Ability to set keybind to a chord - - [x] Ability to reset keybind - - [x] Ability to set a class keybind - - [x] Run tests to verify they pass -- [x] Write a e2e test for each keybind - - [x] reset_zoom_keybind (r) - - [x] show_full_image_keybind (shift+r) - - [x] create_point_annotation_keybind (c) - - [x] delete_annotation_keybind (d) - - [x] switch_subtask_keybind (z) - - [x] toggle_annotation_mode_keybind (u) - - [x] create_bbox_on_initial_crop_keybind (f) - - [x] toggle_brush_mode_keybind (g) - - [x] toggle_erase_mode_keybind (e) - - [x] increase_brush_size_keybind (]) - - [x] decrease_brush_size_keybind ([) - - [x] annotation_size_small_keybind (s) - - [x] annotation_size_large_keybind (l) - - [x] annotation_size_plus_keybind (=) - - [x] annotation_size_minus_keybind (-) - - [x] annotation_vanish_keybind (v) - - [x] fly_to_next_annotation_keybind (tab) - - [x] fly_to_previous_annotation_keybind (shift+tab) +- [ ] Remove the "line_size" property from ULabelAnnotation. Update the entire codebase to ensure no reference to it remains. Instead, use the line_size defined for the annotation's subtask when we need a line size for drawing the annotation. diff --git a/api_spec.md b/api_spec.md index 6a976b5f..e4fddceb 100644 --- a/api_spec.md +++ b/api_spec.md @@ -195,9 +195,6 @@ As you can see, each subtask will have a corresponding list of annotation object } ], - // size in underlying image pixels - "line_size": "", - // (nullable) frame ann was created for "frame": "", diff --git a/demo/box-roi.html b/demo/box-roi.html index 0d5b08e2..15ca7527 100644 --- a/demo/box-roi.html +++ b/demo/box-roi.html @@ -106,9 +106,6 @@ } ], - // size in underlying image pixels - "line_size": roi_line_size, - // (nullable) frame ann was created for "frame": 0, diff --git a/demo/resume-from.html b/demo/resume-from.html index 7f9687ad..bef0f67a 100644 --- a/demo/resume-from.html +++ b/demo/resume-from.html @@ -39,7 +39,6 @@ {"class_id": 11, "confidence": 1.0}, {"class_id": 10, "confidence": 0.0}, ], - "line_size": 4 }, { "id": "4a586dc4-0efa-4563-a42b-8eea3dd5b04b", @@ -48,7 +47,6 @@ "classification_payloads": [ {"class_id": 10, "confidence": 1.0}, ], - "line_size": 4 }, { "id": "4a686dc4-0efa-4563-a42b-8eea3dd5b04b", @@ -57,7 +55,6 @@ "classification_payloads": [ {"class_id": 12, "confidence": 1.0}, ], - "line_size": 4 }, { "id": "4a186dc4-0efa-4563-a42b-8eea3dd5b04b", @@ -185,7 +182,6 @@ "confidence": 0 } ], - "line_size": 3, "text_payload": "", "annotation_meta": null }, @@ -224,7 +220,6 @@ "confidence": 0 } ], - "line_size": 4, "text_payload": "", "annotation_meta": null } diff --git a/demo/row-filtering-example.html b/demo/row-filtering-example.html index 7dd6d789..2f17ae3c 100644 --- a/demo/row-filtering-example.html +++ b/demo/row-filtering-example.html @@ -50,7 +50,6 @@ "confidence": 0.21 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 112.73736341325133, "tly": 263.0492900187559, @@ -84,7 +83,6 @@ "confidence": 0.51 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 370.6589647476967, "tly": 459.4796486509494, @@ -118,7 +116,6 @@ "confidence": 0.41 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 546.5922424791396, "tly": 823.302834639273, @@ -152,7 +149,6 @@ "confidence": 0.01 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 444.10683797538644, "tly": 210.0984976918168, @@ -186,7 +182,6 @@ "confidence": 0.81 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 806.2219338886475, "tly": 206.68231754169167, @@ -220,7 +215,6 @@ "confidence": 0.71 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 869.4212666659619, "tly": 705.4446194599569, @@ -254,7 +248,6 @@ "confidence": 0.71 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1163.2127595767208, "tly": 693.4879889345191, @@ -288,7 +281,6 @@ "confidence": 0.61 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 968.49049101959, "tly": 452.64728835069917, @@ -322,7 +314,6 @@ "confidence": 0.51 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 929.2044192931512, "tly": 153.73152521475257, @@ -356,7 +347,6 @@ "confidence": 1 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1084.6406161238435, "tly": 881.3778971913998, @@ -390,7 +380,6 @@ "confidence": 0.41 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1390.3887395600402, "tly": 100.78073288781346, @@ -424,7 +413,6 @@ "confidence": 0.5 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1393.8049197101654, "tly": 377.4913250479469, @@ -458,7 +446,6 @@ "confidence": 1 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1600.4838187927342, "tly": 712.2769797602072, @@ -492,7 +479,7 @@ "confidence": 0.91 } ], - "line_size": 6.831220813875917, + "": 6.831220813875917, "containing_box": { "tlx": 1537.2844860154198, "tly": 263.0492900187559, @@ -526,7 +513,6 @@ "confidence": 0.21 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1889.1510414783056, "tly": 582.4621340554531, @@ -560,7 +546,6 @@ "confidence": 0.11 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1308.4004159570377, "tly": 563.6731432297651, @@ -603,7 +588,6 @@ "confidence": 0 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 251.09265949331802, "tly": 148.60725498956492, @@ -646,7 +630,6 @@ "confidence": 0 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 1311.816596107163, "tly": 105.90500311300111, @@ -697,7 +680,6 @@ "confidence": 1 } ], - "line_size": 6.831220813875917, "containing_box": { "tlx": 792.5572132881471, "tly": 114.44545348831386, diff --git a/src/annotation.ts b/src/annotation.ts index 342a0e24..b88fa3bf 100644 --- a/src/annotation.ts +++ b/src/annotation.ts @@ -37,7 +37,6 @@ export class ULabelAnnotation { public created_by?: string, public distance_from?: DistanceFromPolylineClasses, public frame?: number, - public line_size?: number, public id?: string, public canvas_id?: string, // Polygons use complex spatial payloads diff --git a/src/index.js b/src/index.js index 4ecc7396..1ccdca84 100644 --- a/src/index.js +++ b/src/index.js @@ -266,13 +266,6 @@ export class ULabel { cand.id = ul.make_new_annotation_id(); } - // Set to default line size if there is none, check for null and undefined using == - if ( - (cand.line_size === undefined) || (cand.line_size == null) - ) { - cand.line_size = ul.config.initial_line_size; - } - // Add created by attribute if there is none if ( (cand.created_by === undefined) @@ -412,9 +405,6 @@ export class ULabel { // Process allowed classes // They are placed in ul.subtasks[subtask_key]["class_defs"] ULabel.process_classes(ul, subtask_key, raw_subtask); - // Process imported annoations - // They are placed in ul.subtasks[subtask_key]["annotations"] - ULabel.process_resume_from(ul, subtask_key, raw_subtask); // Label canvasses and initialize context with null ul.subtasks[subtask_key]["canvas_fid"] = ul.config["canvas_fid_pfx"] + "__" + subtask_key; @@ -460,6 +450,10 @@ export class ULabel { // Generic dialogs visible_dialogs: {}, }; + + // Process imported annoations + // They are placed in ul.subtasks[subtask_key]["annotations"] + ULabel.process_resume_from(ul, subtask_key, raw_subtask); } if (first_non_ro === null) { log_message( @@ -1502,7 +1496,7 @@ export class ULabel { diffY = offset["diffY"]; } - const line_size = this.get_scaled_line_size(annotation_object); + const line_size = this.get_scaled_line_size(); // Prep for bbox drawing const color = this.get_annotation_color(annotation_object); @@ -1535,7 +1529,7 @@ export class ULabel { diffY = offset["diffY"]; } - const line_size = this.get_scaled_line_size(annotation_object); + const line_size = this.get_scaled_line_size(); // Prep for bbox drawing const color = this.get_annotation_color(annotation_object); @@ -1583,7 +1577,7 @@ export class ULabel { fill = true; } - const line_size = this.get_scaled_line_size(annotation_object); + const line_size = this.get_scaled_line_size(); // Prep for bbox drawing const color = this.get_annotation_color(annotation_object); @@ -1619,10 +1613,7 @@ export class ULabel { diffY = offset["diffY"]; } - const line_size = this.get_scaled_line_size(annotation_object); - - // Hack to turn off fills during vanish - let is_in_vanish_mode = line_size <= 0.01; + const line_size = this.get_scaled_line_size(); // Prep for bbox drawing const color = this.get_annotation_color(annotation_object); @@ -1661,7 +1652,7 @@ export class ULabel { // If not in vanish mode and polygon is closed, fill it or draw a hole layer_is_closed = GeometricUtils.is_polygon_closed(active_spatial_payload); - if (!is_in_vanish_mode && spatial_type === "polygon" && layer_is_closed) { + if (spatial_type === "polygon" && layer_is_closed) { if (annotation_object["spatial_payload_holes"][i]) { ctx.globalCompositeOperation = "destination-out"; } else { @@ -1708,7 +1699,7 @@ export class ULabel { diffY = offset["diffY"]; } - const line_size = this.get_scaled_line_size(annotation_object); + const line_size = this.get_scaled_line_size(); // Prep for bbox drawing const color = this.get_annotation_color(annotation_object); @@ -1739,7 +1730,7 @@ export class ULabel { diffY = offset["diffY"]; } - const line_size = this.get_scaled_line_size(annotation_object); + const line_size = this.get_scaled_line_size(); // Prep for tbar drawing const color = this.get_annotation_color(annotation_object); @@ -3282,14 +3273,9 @@ export class ULabel { return ret; } - get_scaled_line_size(annotation) { - // If a line size isn't provided, use the default line size - let line_size; - if ("line_size" in annotation && annotation["line_size"] !== null) { - line_size = annotation["line_size"]; - } else { - line_size = this.get_subtask_line_size(); - } + get_scaled_line_size() { + // Use the subtask's line size for the annotation + let line_size = this.get_subtask_line_size(); // fixed: line size is independent of zoom level // match-zoom: line size increases with increased zoom level @@ -3389,7 +3375,6 @@ export class ULabel { begin_annotation(mouse_event, annotation_id = null, redo_payload = null) { // Give the new annotation a unique ID - let line_size = null; let annotation_mode = null; let redoing = false; let gmx = null; @@ -3403,13 +3388,11 @@ export class ULabel { if (redo_payload === null) { annotation_id = this.make_new_annotation_id(); - line_size = this.get_subtask_line_size(subtask_key); annotation_mode = current_subtask["state"]["annotation_mode"]; [gmx, gmy] = this.get_image_aware_mouse_x_y(mouse_event); init_spatial = this.get_init_spatial(gmx, gmy, annotation_mode, mouse_event); init_id_payload = this.get_init_id_payload(annotation_mode); } else { - line_size = redo_payload.line_size; mouse_event = redo_payload.mouse_event; annotation_mode = redo_payload.annotation_mode; redoing = true; @@ -3423,7 +3406,6 @@ export class ULabel { // TODO(3d) if (NONSPATIAL_MODES.includes(annotation_mode)) { - line_size = null; init_spatial = null; } else { containing_box = { @@ -3451,7 +3433,6 @@ export class ULabel { spatial_type: annotation_mode, spatial_payload: init_spatial, classification_payloads: init_id_payload, - line_size: line_size, containing_box: containing_box, frame: frame, canvas_id: canvas_id, @@ -3482,7 +3463,6 @@ export class ULabel { frame: frame, redo_payload: { mouse_event: mouse_event, - line_size: line_size, annotation_mode: annotation_mode, gmx: gmx, gmy: gmy, @@ -3573,7 +3553,7 @@ export class ULabel { this.update_containing_box(spatial_payload[pti], actid, subtask); } if (spatial_type) { - let line_size = this.subtasks[subtask]["annotations"]["access"][actid]["line_size"]; + let line_size = this.get_subtask_line_size(subtask); this.subtasks[subtask]["annotations"]["access"][actid]["containing_box"]["tlx"] -= 3 * line_size; this.subtasks[subtask]["annotations"]["access"][actid]["containing_box"]["tly"] -= 3 * line_size; this.subtasks[subtask]["annotations"]["access"][actid]["containing_box"]["brx"] += 3 * line_size; diff --git a/src/toolbox.ts b/src/toolbox.ts index 2fca00a2..a1db37cf 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -1389,12 +1389,6 @@ export class AnnotationResizeItem extends ToolboxItem { // Set the line size to the given size subtask.state.line_size = size; } - - for (const annotation of Object.values(subtask.annotations.access)) { - // Update each annotation's line size - // TODO: do individual annotations need their own line_size property? - annotation.line_size = subtask.state.line_size; - } } /** diff --git a/tests/annotation.test.js b/tests/annotation.test.js index c13284ab..12599014 100644 --- a/tests/annotation.test.js +++ b/tests/annotation.test.js @@ -63,7 +63,6 @@ describe("Annotation Processing", () => { expect(annotation.classification_payloads).toEqual([{ class_id: 1, confidence: 1.0 }]); // Other properties - expect(annotation.line_size).toBe(ulabel_with_resume.config.initial_line_size); expect(annotation.created_by).toBe("unknown"); expect(annotation.created_at).toBe(null); expect(annotation.last_edited_by).toBe("unknown"); diff --git a/tests/e2e/keybind-functionality.spec.js b/tests/e2e/keybind-functionality.spec.js index b7e2e4df..7a9c79e8 100644 --- a/tests/e2e/keybind-functionality.spec.js +++ b/tests/e2e/keybind-functionality.spec.js @@ -393,28 +393,28 @@ test.describe("Keybind Functionality Tests", () => { // Test small keybind - should set size to 1.5 await press_keybind(page, small_keybind); await page.waitForTimeout(200); - let annotation = await get_annotation_by_index(page, 0); - expect(annotation.line_size).toBe(1.5); + let subtask = await get_current_subtask(page); + expect(subtask.state.line_size).toBe(1.5); // Test large keybind - should set size to 5 await press_keybind(page, large_keybind); await page.waitForTimeout(200); - annotation = await get_annotation_by_index(page, 0); - expect(annotation.line_size).toBe(5); + subtask = await get_current_subtask(page); + expect(subtask.state.line_size).toBe(5); // Test plus keybind - should increase by 0.5 - const size_before_plus = annotation.line_size; + const size_before_plus = subtask.state.line_size; await press_keybind(page, plus_keybind); await page.waitForTimeout(200); - annotation = await get_annotation_by_index(page, 0); - expect(annotation.line_size).toBe(size_before_plus + 0.5); + subtask = await get_current_subtask(page); + expect(subtask.state.line_size).toBe(size_before_plus + 0.5); // Test minus keybind - should decrease by 0.5 - const size_before_minus = annotation.line_size; + const size_before_minus = subtask.state.line_size; await press_keybind(page, minus_keybind); await page.waitForTimeout(200); - annotation = await get_annotation_by_index(page, 0); - expect(annotation.line_size).toBe(size_before_minus - 0.5); + subtask = await get_current_subtask(page); + expect(subtask.state.line_size).toBe(size_before_minus - 0.5); // Test vanish keybind - should toggle vanish mode const vanish_state_before = (await get_current_subtask(page)).state.is_vanished; From d4ed051b1e97d430cd81f24d3adc7d9badbba886 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 13 Jan 2026 10:34:24 -0600 Subject: [PATCH 13/16] Bump version and update npm packages --- CHANGELOG.md | 10 +++ package-lock.json | 172 +++++++++++++++++++++++++++++++++------------- package.json | 3 +- src/version.js | 2 +- 4 files changed, 136 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16198b81..dd4efd04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented here. ## [unreleased] +## [0.22.1] - Jan 13th, 2026 +- Don't draw annotations when a subtask is vanished +- Add configurable `annotation_vanish_all_keybind` +- Track annotation size per subtask + - Remove annotation size cookie, since it was tracking just one size per annotation session +- Deprecated the `default_annotation_size` argument in the configuration object. Use the `initial_line_size` argument instead. See api_spec.md for details. +- Fix toolbox collapse arrow positioning, and make frame annotation dialogs disappear on collapse +- Deprecated the `default_annotation_size` argument in the configuration object. Use the `initial_line_size` argument instead. See api_spec.md for details. +- Removed the `line_size` property from annotation objects. `subtask.state.line_size` determined the size of drawn annotations within a subtask. + ## [0.22.0] - Oct 30th, 2025 - Add collapsible toolbox with arrow button at top - Toolbox collapse state persists in browser diff --git a/package-lock.json b/package-lock.json index 94ce77d5..4bb0c772 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ulabel", - "version": "0.20.0", + "version": "0.22.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ulabel", - "version": "0.20.0", + "version": "0.22.1", "license": "MIT", "devDependencies": { "@eslint/config-inspector": "^1.3.0", @@ -18,6 +18,7 @@ "@types/jquery": "^3.5.31", "@typescript-eslint/eslint-plugin": "^8.46.1", "@typescript-eslint/parser": "^8.46.1", + "baseline-browser-mapping": "^2.9.14", "cross-env": "^7.0.3", "eslint": "^9.11.1", "eslint-config-prettier": "^9.1.0", @@ -69,6 +70,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1386,9 +1388,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -3912,6 +3914,7 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -4375,6 +4378,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4715,9 +4719,9 @@ "dev": true }, "node_modules/baseline-browser-mapping": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz", - "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==", + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4725,24 +4729,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -4759,6 +4763,27 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4766,6 +4791,16 @@ "dev": true, "license": "MIT" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4809,6 +4844,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -5912,6 +5948,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6014,6 +6051,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6414,40 +6452,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -8389,9 +8427,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -9555,13 +9593,13 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -9630,17 +9668,48 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9943,6 +10012,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10550,6 +10620,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10720,6 +10791,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10961,6 +11033,7 @@ "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -11009,6 +11082,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/package.json b/package.json index 41acbd10..0e3b5dde 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ulabel", "description": "An image annotation tool.", - "version": "0.22.0", + "version": "0.22.1", "main": "dist/ulabel.min.js", "module": "dist/ulabel.min.js", "exports": { @@ -56,6 +56,7 @@ "@types/jquery": "^3.5.31", "@typescript-eslint/eslint-plugin": "^8.46.1", "@typescript-eslint/parser": "^8.46.1", + "baseline-browser-mapping": "^2.9.14", "cross-env": "^7.0.3", "eslint": "^9.11.1", "eslint-config-prettier": "^9.1.0", diff --git a/src/version.js b/src/version.js index 7026927c..4b5eb401 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -export const ULABEL_VERSION = "0.22.0"; +export const ULABEL_VERSION = "0.22.1"; From 4f3006e6d761c675ec4ba92fb2bb6315e87fad05 Mon Sep 17 00:00:00 2001 From: Trevor Burgoyne <82477095+TrevorBurgoyne@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:39:35 -0600 Subject: [PATCH 14/16] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/tasks.md | 2 +- CHANGELOG.md | 1 - demo/row-filtering-example.html | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/tasks.md b/.github/tasks.md index b5f9274e..b78a8a9e 100644 --- a/.github/tasks.md +++ b/.github/tasks.md @@ -1,3 +1,3 @@ ## Tasks -- [ ] Remove the "line_size" property from ULabelAnnotation. Update the entire codebase to ensure no reference to it remains. Instead, use the line_size defined for the annotation's subtask when we need a line size for drawing the annotation. +- [x] Remove the "line_size" property from ULabelAnnotation. Update the entire codebase to ensure no reference to it remains. Instead, use the line_size defined for the annotation's subtask when we need a line size for drawing the annotation. diff --git a/CHANGELOG.md b/CHANGELOG.md index dd4efd04..435d8fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ All notable changes to this project will be documented here. - Remove annotation size cookie, since it was tracking just one size per annotation session - Deprecated the `default_annotation_size` argument in the configuration object. Use the `initial_line_size` argument instead. See api_spec.md for details. - Fix toolbox collapse arrow positioning, and make frame annotation dialogs disappear on collapse -- Deprecated the `default_annotation_size` argument in the configuration object. Use the `initial_line_size` argument instead. See api_spec.md for details. - Removed the `line_size` property from annotation objects. `subtask.state.line_size` determined the size of drawn annotations within a subtask. ## [0.22.0] - Oct 30th, 2025 diff --git a/demo/row-filtering-example.html b/demo/row-filtering-example.html index 2f17ae3c..b4e37e03 100644 --- a/demo/row-filtering-example.html +++ b/demo/row-filtering-example.html @@ -479,7 +479,6 @@ "confidence": 0.91 } ], - "": 6.831220813875917, "containing_box": { "tlx": 1537.2844860154198, "tly": 263.0492900187559, From efc62071ca4f6973806fc8fc9321cf3af23effc2 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 13 Jan 2026 10:58:13 -0600 Subject: [PATCH 15/16] Prevent drawing when vanished --- src/index.js | 13 +++++++++++-- src/toolbox.ts | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 1ccdca84..4e64975f 100644 --- a/src/index.js +++ b/src/index.js @@ -2964,6 +2964,12 @@ export class ULabel { create_annotation(spatial_type, spatial_payload, unique_id = null, is_redo = false) { // Grab constants for convenience const current_subtask = this.get_current_subtask(); + + // Exit if subtask is vanished + if (current_subtask["state"]["is_vanished"]) { + return; + } + const annotation_access = current_subtask["annotations"]["access"]; const annotation_ordering = current_subtask["annotations"]["ordering"]; @@ -5284,8 +5290,11 @@ export class ULabel { handle_mouse_down(mouse_event) { const drag_key = ULabel.get_drag_key_start(mouse_event, this); if (drag_key != null) { - // Don't start new drag while id_dialog is visible - if (this.get_current_subtask()["state"]["idd_visible"] && !this.get_current_subtask()["state"]["idd_thumbnail"]) { + // Don't start new drag while id_dialog is visible or subtask is vanished + if ( + (this.get_current_subtask()["state"]["idd_visible"] && !this.get_current_subtask()["state"]["idd_thumbnail"]) || + this.get_current_subtask()["state"]["is_vanished"] + ) { return; } mouse_event.preventDefault(); diff --git a/src/toolbox.ts b/src/toolbox.ts index a1db37cf..0e6df80f 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -1339,10 +1339,10 @@ export class AnnotationResizeItem extends ToolboxItem { case ValidResizeValues.VANISH: // Toggle the vanished flag for the current subtask and return AnnotationResizeItem.toggle_subtask_vanished(this.ulabel, current_subtask_key); - return; + break; default: log_message(`Invalid Resize Value: ${button_value}`, LogLevel.ERROR, true); - break; + return; } // Update the size of all annotations in the subtask From 7b1b1dcb5c8ecad55233a1dd3b33d70ebd8fcb3b Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Wed, 14 Jan 2026 09:50:52 -0600 Subject: [PATCH 16/16] Update function name --- src/toolbox.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/toolbox.ts b/src/toolbox.ts index 0e6df80f..5e2d9b50 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -1366,8 +1366,8 @@ export class AnnotationResizeItem extends ToolboxItem { // If the annotations are currently vanished, don't resize them if (subtask.state.is_vanished) return; - // Set the size of the annotations to the given size - AnnotationResizeItem.loop_through_annotations(subtask, size, increment); + // Set the size of the subtask to the given size + AnnotationResizeItem.update_subtask_line_size(subtask, size, increment); // If the sizes were updated, resize the annotations if (redraw) { @@ -1375,8 +1375,8 @@ export class AnnotationResizeItem extends ToolboxItem { } } - // Loop through all annotations in a subtask and change their line size - public static loop_through_annotations(subtask: ULabelSubtask, size: number, increment: boolean) { + // Update the line size for a subtask + public static update_subtask_line_size(subtask: ULabelSubtask, size: number, increment: boolean) { // Update subtask line size if (increment) { // Guard against the line size going too small or negative