Skip to content

Commit 7a7dc2b

Browse files
authored
Merge pull request #202 from SenteraLLC/rc/v0.16.0
Release v0.16.0
2 parents cbbab17 + 5b704ce commit 7a7dc2b

19 files changed

Lines changed: 1147 additions & 655 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ All notable changes to this project will be documented here.
66

77
Nothing yet.
88

9+
## [0.16.0] - Nov 8th, 2024
10+
- Added loading overlay with spinning icon while ULabel is initializing
11+
- Moved several existing utilties (mostly static methods) to isolated files:
12+
- Event listener management to `listeners.ts`
13+
- Night mode cookie to `cookies.ts`
14+
- Logging to `error_logging.ts`
15+
- Canvas initialization to `canvas_utils.ts`
16+
- Initialization logic (vastly simplified) to `initializer.ts`
17+
- Changed `after_init` to be an instance function
18+
- Removed `load_image_promise` in favor of `.decode()`
19+
- Added many new entries in `index.d.ts`
20+
921
## [0.15.3] - Oct 4th, 2024
1022
- Fix issue where legacy submit button functionality would break loading
1123

dist/ulabel.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/ulabel.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default [
2323
},
2424
stylistic.configs.customize(
2525
{
26+
arrowParens: true,
2627
braceStyle: "1tbs",
2728
commaDangle: "always-multiline",
2829
indent: 4,

index.d.ts

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export type ClassDefinition = {
6363
name: string;
6464
id: number;
6565
color: string;
66+
keybind?: string;
6667
};
6768

6869
export type SliderInfo = {
@@ -168,7 +169,7 @@ export type ImageData = {
168169
export type ULabelSubtasks = { [key: string]: ULabelSubtask };
169170

170171
export class ULabel {
171-
subtasks: ULabelSubtask[];
172+
subtasks: ULabelSubtasks;
172173
state: {
173174
// Viewer state
174175
zoom_val: number;
@@ -194,6 +195,9 @@ export class ULabel {
194195
valid_class_ids: number[];
195196
toolbox_order?: number[];
196197
filter_distance_overlay?: FilterDistanceOverlay;
198+
begining_time: number;
199+
is_init: boolean;
200+
resize_observers: ResizeObserver[];
197201
/**
198202
* @link https://github.com/SenteraLLC/ulabel/blob/main/api_spec.md#ulabel-constructor
199203
*/
@@ -217,12 +221,20 @@ export class ULabel {
217221
* @link https://github.com/SenteraLLC/ulabel/blob/main/api_spec.md#display-utility-functions
218222
*/
219223
public init(callback: () => void): void;
224+
public after_init(): void;
220225
public show_initial_crop(): void;
221226
public show_whole_image(): void;
222227
public swap_frame_image(new_src: string, frame?: number): string;
223228
public swap_anno_bg_color(new_bg_color: string): string;
229+
230+
// Subtasks
224231
public get_current_subtask_key(): string;
225232
public get_current_subtask(): ULabelSubtask;
233+
public readjust_subtask_opacities(): void;
234+
public set_subtask(st_key: string): void;
235+
public switch_to_next_subtask(): void;
236+
237+
// Annotations
226238
public get_annotations(subtask: ULabelSubtask): ULabelAnnotation[];
227239
public set_annotations(annotations: ULabelAnnotation[], subtask: ULabelSubtask);
228240
public set_saved(saved: boolean);
@@ -235,8 +247,6 @@ export class ULabel {
235247
public show_annotation_mode(
236248
target_jq?: JQuery<HTMLElement>, // TODO (joshua-dean): validate this type
237249
);
238-
public raise_error(message: string, level?: number);
239-
public rezoom(): void;
240250
public update_frame(delta?: number, new_frame?: number): void;
241251
public handle_id_dialog_hover(
242252
mouse_event: JQuery.TriggeredEvent,
@@ -245,14 +255,120 @@ export class ULabel {
245255
dist_prop: number;
246256
},
247257
): void;
248-
public toggle_erase_mode(mouse_event: JQuery.TriggeredEvent): void;
249-
public toggle_brush_mode(mouse_event: JQuery.TriggeredEvent): void;
258+
259+
// Brush
260+
// TODO (joshua-dean): should these actually be optional?
261+
public toggle_erase_mode(mouse_event?: JQuery.TriggeredEvent): void;
262+
public toggle_brush_mode(mouse_event?: JQuery.TriggeredEvent): void;
250263
public toggle_delete_class_id_in_toolbox(): void;
251264
public change_brush_size(scale_factor: number): void;
265+
public recolor_brush_circle(): void;
266+
public destroy_brush_circle(): void;
267+
268+
// Listeners
252269
public remove_listeners(): void;
253270
static get_allowed_toolbox_item_enum(): AllowedToolboxItem;
254-
static process_classes(ulabel_obj: ULabel, arg1: string, subtask_obj: ULabelSubtask);
255-
static build_id_dialogs(ulabel_obj: ULabel);
271+
static process_classes(ulabel_obj: ULabel, arg1: string, subtask_obj: ULabelSubtask): void;
272+
static build_id_dialogs(ulabel_obj: ULabel): void;
273+
274+
// Instance init functions
275+
public create_overlays(): void;
276+
277+
// nops
278+
public redraw_demo(): void;
279+
280+
// Annotation lifecycle
281+
// TODO (joshua-dean): type for redo_payload
282+
public begin_annotation(mouse_event: JQuery.TriggeredEvent, redo_payload?: object): void;
283+
public create_annotation(
284+
spatial_type: ULabelSpatialType,
285+
spatial_payload: ULabelSpatialPayload,
286+
unique_id?: string,
287+
): void;
288+
public create_nonspatial_annotation(
289+
redo_payload?: object,
290+
): void;
291+
public delete_annotation(
292+
annotation_id: string,
293+
redo_payload?: object,
294+
record_action?: boolean,
295+
): void;
296+
public cancel_annotation(redo_payload?: object): void;
297+
public get_active_class_id(): number;
298+
public get_active_class_id_idx(): number;
299+
public undo(is_internal_undo?: boolean): void;
300+
public redo(): void;
301+
302+
// Mouse event handlers
303+
public handle_mouse_down(mouse_event: JQuery.TriggeredEvent): void;
304+
public handle_mouse_move(mouse_event: JQuery.TriggeredEvent): void;
305+
public handle_mouse_up(mouse_event: JQuery.TriggeredEvent): void;
306+
public handle_aux_click(mouse_event: JQuery.TriggeredEvent): void;
307+
public handle_wheel(wheel_event: WheelEvent): void;
308+
public start_drag(
309+
drag_key: string,
310+
release_button: string,
311+
mouse_event: JQuery.TriggeredEvent,
312+
): void;
313+
public end_drag(mouse_event: JQuery.TriggeredEvent): void;
314+
public drag_repan(mouse_event: JQuery.TriggeredEvent): void;
315+
public drag_rezoom(mouse_event: JQuery.TriggeredEvent): void;
316+
317+
// "Mouse event interpreters"
318+
public get_global_mouse_x(mouse_event: JQuery.TriggeredEvent): number;
319+
public get_global_mouse_y(mouse_event: JQuery.TriggeredEvent): number;
320+
321+
// Edit suggestions
322+
public suggest_edits(
323+
mouse_event?: JQuery.TriggeredEvent,
324+
nonspatial_id?: string,
325+
): void;
326+
public show_global_edit_suggestion(
327+
annid: string,
328+
offset?: {
329+
diffX: number;
330+
diffY: number;
331+
diffZ?: number;
332+
},
333+
nonspatial_id?: string,
334+
): void;
335+
public hide_global_edit_suggestion(): void;
336+
337+
// Drawing
338+
public rezoom(
339+
foc_x?: number,
340+
foc_y?: number,
341+
abs?: boolean,
342+
): void;
343+
public reposition_dialogs(): void;
344+
public handle_toolbox_overflow(): void;
345+
346+
// ID Dialog
347+
public set_id_dialog_payload_nopin(
348+
class_ind: number,
349+
dist_prop: number
350+
): void;
351+
public update_id_dialog_display(
352+
front?: boolean,
353+
): void;
354+
public handle_id_dialog_click(
355+
mouse_event: JQuery.TriggeredEvent,
356+
annotation_id?: string,
357+
new_class_idx?: number,
358+
): void;
359+
public show_id_dialog(
360+
gbx: number,
361+
gby: number,
362+
active_ann: string, // annotation id
363+
thumbnail?: boolean,
364+
nonspatial?: boolean,
365+
): void;
366+
367+
// Canvases
368+
public get_init_canvas_context_id(
369+
annotation_id: string,
370+
subtask?: string, // SUBTASK KEY
371+
): string;
256372
}
257373

258374
declare global {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ulabel",
33
"description": "An image annotation tool.",
4-
"version": "0.15.4",
4+
"version": "0.16.0",
55
"main": "dist/ulabel.js",
66
"module": "dist/ulabel.js",
77
"scripts": {

src/annotation_operators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export function mark_deprecated(
7171
annotation.deprecated_by[deprecated_by_key] = deprecated;
7272

7373
// If the annotation has been deprecated by any method, then deprecate the annotation
74-
if (Object.values(annotation.deprecated_by).some(x => x)) {
74+
if (Object.values(annotation.deprecated_by).some((x) => x)) {
7575
annotation.deprecated = true;
7676
return;
7777
}

src/canvas_utils.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Annotation canvas utilities.
3+
* TODO (joshua-dean): Pull the rest of the canvas functions into here
4+
*/
5+
6+
import type { ULabel, ULabelSubtasks } from "..";
7+
import { NONSPATIAL_MODES } from "./annotation";
8+
import { Configuration, DEFAULT_N_ANNOS_PER_CANVAS, TARGET_MAX_N_CANVASES_PER_SUBTASK } from "./configuration";
9+
10+
/**
11+
* If the user doesn't provide a number of annotations per canvas, set it dynamically.
12+
* This should help with performance.
13+
*
14+
* @param config ULabel configuration
15+
* @param subtasks ULabel subtasks
16+
*/
17+
function dynamically_set_n_annos_per_canvas(
18+
config: Configuration,
19+
subtasks: ULabelSubtasks,
20+
) {
21+
// If they didn't provide a value, we'll still be using the default
22+
if (config.n_annos_per_canvas === DEFAULT_N_ANNOS_PER_CANVAS) {
23+
// Count max annotations per subtask
24+
const max_annos = Math.max(
25+
...Object.values(subtasks).map((subtask) => subtask.annotations.ordering.length),
26+
);
27+
28+
// Performance starts to deteriorate when we require many canvases to be drawn on
29+
// To be safe, check if max_annos / DEFAULT_N_ANNOS_PER_CANVAS is greater than TARGET_MAX_N_CANVASES_PER_SUBTASK
30+
if (max_annos / DEFAULT_N_ANNOS_PER_CANVAS > TARGET_MAX_N_CANVASES_PER_SUBTASK) {
31+
// If so, raise the default
32+
config.n_annos_per_canvas = Math.ceil(max_annos / TARGET_MAX_N_CANVASES_PER_SUBTASK);
33+
}
34+
}
35+
}
36+
37+
/**
38+
* Initialize annotation canvases and assign annotations to them
39+
*
40+
* @param ulabel ULabel instance
41+
* @param subtask_key Subtask key. If null, this will dynamically initialize for all subtasks.
42+
*/
43+
export function initialize_annotation_canvases(
44+
ulabel: ULabel,
45+
subtask_key: string = null,
46+
) {
47+
if (subtask_key === null) {
48+
dynamically_set_n_annos_per_canvas(
49+
ulabel.config,
50+
ulabel.subtasks,
51+
);
52+
for (const subtask_key in ulabel.subtasks) {
53+
initialize_annotation_canvases(ulabel, subtask_key);
54+
}
55+
return;
56+
}
57+
58+
// TODO (joshua-dean): shouldn't this just be a separate function?
59+
// Create the canvas for each annotation
60+
const subtask = ulabel.subtasks[subtask_key];
61+
for (const annotation_id in subtask.annotations.access) {
62+
const annotation = subtask.annotations.access[annotation_id];
63+
if (!NONSPATIAL_MODES.includes(annotation.spatial_type)) {
64+
annotation["canvas_id"] = ulabel.get_init_canvas_context_id(
65+
annotation_id,
66+
subtask_key,
67+
);
68+
}
69+
}
70+
}

src/cookies.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* ULabel cookie utilities.
3+
*/
4+
5+
export abstract class NightModeCookie {
6+
/**
7+
* The name of the cookie that stores the night mode preference.
8+
*/
9+
public static readonly COOKIE_NAME: string = "nightmode";
10+
11+
/**
12+
* Return whether the document has a night mode cookie.
13+
*/
14+
public static exists_in_document(): boolean {
15+
const cookie_components = document.cookie.split(";");
16+
const night_mode_comp = cookie_components.find(
17+
(row) => row.trim().startsWith(`${NightModeCookie.COOKIE_NAME}=true`),
18+
);
19+
return night_mode_comp !== undefined;
20+
}
21+
22+
/**
23+
* Set the night mode cookie.
24+
*/
25+
public static set_cookie(): void {
26+
const d = new Date();
27+
d.setTime(d.getTime() + (10000 * 24 * 60 * 60 * 1000));
28+
document.cookie = [
29+
NightModeCookie.COOKIE_NAME + "=true",
30+
"expires=" + d.toUTCString(),
31+
"path=/",
32+
].join(";");
33+
}
34+
35+
/**
36+
* Destroy the night mode cookie.
37+
*/
38+
public static destroy_cookie() {
39+
document.cookie = [
40+
NightModeCookie.COOKIE_NAME + "=true",
41+
"expires=Thu, 01 Jan 1970 00:00:00 UTC",
42+
"path=/",
43+
].join(";");
44+
}
45+
}

0 commit comments

Comments
 (0)