Skip to content

Commit 7a9590a

Browse files
Merge pull request #232 from SenteraLLC/release/v0.20.0
Release/v0.20.0
2 parents a94f623 + 916d181 commit 7a9590a

25 files changed

Lines changed: 63719 additions & 930 deletions

.github/tasks.md

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,48 @@
11
## Tasks
2-
- [x] Read the discussion in [#209](https://github.com/SenteraLLC/ulabel/issues/209) and understand the requirements.
3-
- [x] Implement the fix requested in [#209](https://github.com/SenteraLLC/ulabel/issues/209).
4-
- [x] Build the fix and ensure the build succeeds by running `npm run build`.
5-
- [x] Receive confirmation that the fix works as expected.
6-
- [x] Configure Jest to suppress verbose stack traces (added --noStackTrace flag)
7-
- [x] Fix class ID test to check ID is not in existing list (implementation-agnostic)
8-
- [x] Increase max workers from 1 to 2 (reduced test time from 200s+ to ~23s)
9-
- [ ] Fix remaining unit test failures (6 failures, 14 passed)
10-
- Spatial payload tests need DOM mocking
11-
- ID payload tests need DOM mocking
12-
- Note: Some error messages contain minified code context - this is expected when testing against dist/ulabel.js
13-
- [x] Refactor e2e tests to use utility functions
14-
- [x] Create init_utils.js with wait_for_ulabel_init
15-
- [x] Create annotation_utils.js with get_annotation_count, get_annotation_by_index, get_all_annotations
16-
- [x] Create mode_utils.js with switch_to_mode
17-
- [x] Create subtask_utils.js with get_current_subtask_key, switch_to_subtask, get_subtask_count
18-
- [x] Update basic-functionality.spec.js to use new utilities
19-
- [x] All 6 basic functionality tests passing
20-
- [x] Refactor tests/ folder to use snake_case naming convention
21-
- [x] Updated all utility function names to snake_case
22-
- [x] Updated all variable names in e2e tests to snake_case
23-
- [x] Updated all variable names in unit tests to snake_case
24-
- [x] Updated all variable names in setup.js to snake_case
25-
- [x] Updated all variable names in utility files to snake_case
26-
- [x] Verified unit tests pass (14 passed)
27-
- [x] Verified e2e tests pass (6 passed)
2+
- [x] Read the description in [#164](https://github.com/SenteraLLC/ulabel/issues/164)
3+
- [x] Write a clear summary of the requested change
4+
- [x] Propose some options of how to proceed. Wait for user input to decide which to try first.
5+
6+
### Decision: Proceeding with Option 3 (Webpack modernization + dependency cleanup)
7+
8+
#### Step 1: Move dependencies to devDependencies
9+
- [x] Move `@turf/turf` from dependencies to devDependencies
10+
- [x] Move `jquery` from dependencies to devDependencies
11+
- [x] Move `polygon-clipping` from dependencies to devDependencies
12+
- [x] Move `uuidv4` from dependencies to devDependencies
13+
- [x] Test: Run `npm install` and verify it works
14+
- [x] Test: Run `npm run build` and verify output is identical (both files 1039.6 KB)
15+
16+
#### Step 2: Enable and modernize webpack minification ✅ COMPLETE
17+
- [x] Remove commented-out UglifyJsPlugin code (deprecated)
18+
- [x] Enable webpack 5's built-in TerserPlugin for minification
19+
- [x] Configure it to only minify `ulabel.min.js`, not `ulabel.js`
20+
- [x] Test: Run `npm run build` and verify both files are created
21+
- [x] Test: Verify `ulabel.js` is NOT minified (readable) - 2.33 MB with webpack runtime and formatted code
22+
- [x] Test: Verify `ulabel.min.js` IS minified (smaller size) - 1.02 MB minified
23+
- [x] Test: Run `npm run lint` - No errors
24+
- [x] Note: File size difference (2.33 MB vs 1.02 MB) is expected - readable version includes webpack runtime overhead
25+
26+
#### Step 3: Verify and document ✅ COMPLETE
27+
- [x] Compare file sizes before/after
28+
- Before: Both files 1039 KB (both minified, minification was disabled)
29+
- After: ulabel.js 2.33 MB (readable), ulabel.min.js 1.02 MB (minified)
30+
- Result: Minification now working correctly, file size increase for non-min version is expected
31+
- [x] Update CHANGELOG.md with changes
32+
- [x] Document any findings or recommendations
33+
34+
#### Step 4: Security and dependency updates ✅ COMPLETE
35+
- [x] Run `npm audit` to identify vulnerabilities
36+
- [x] Fix 12 vulnerabilities using `npm audit fix`
37+
- [x] Apply breaking changes for remaining issues with `npm audit fix --force`
38+
- [x] Update `typescript-eslint` packages to be compatible with ESLint 9.37.0
39+
- [x] Fix linting issues in `tests/e2e/fixtures.js`
40+
- [x] Verify all tests still pass (28 unit tests + 36 e2e tests)
41+
- [x] Final audit: 0 vulnerabilities
42+
43+
#### Step 5: Configure package exports for minified by default ✅ COMPLETE
44+
- [x] Update `main` and `module` fields to point to `dist/ulabel.min.js`
45+
- [x] Add `exports` field with options: `.` (minified), `./min` (minified), `./debug` (unminified)
46+
- [x] Update `unpkg` field to serve minified version by default
47+
- [x] Update README.md with usage examples for both minified and unminified versions
48+
- [x] Document clear import patterns for users

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- name: Install dependencies
2727
run: npm install
2828

29-
- name: Run unit tests
29+
- name: Build and run unit tests (both builds)
3030
run: npm run build-and-test
3131

3232
e2e-tests:
@@ -50,7 +50,7 @@ jobs:
5050
- name: Build project
5151
run: npm run build
5252

53-
- name: Run E2E tests
53+
- name: Run E2E tests (all browsers, both builds)
5454
run: npm run test:e2e
5555

5656
- name: Upload Playwright report

CHANGELOG.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,28 @@
22

33
All notable changes to this project will be documented here.
44

5-
## [unreleased]
6-
7-
Nothing yet.
5+
## [unreleased]https://github.com/SenteraLLC/ulabel/pull/233
6+
7+
## [0.20.0] - Oct 15th, 2025
8+
- Add `fly-to` functions, which sets the zoom and focus to a specific annotation
9+
- `fly_to_next_annotation()`
10+
- `fly_to_annotation_id()`
11+
- `fly_to_annotation()`
12+
- Add `Tab` and `Tab+Shift` default keybinds to fly-to the next/previous annotation, respectively
13+
- Keybinds are configurable:
14+
- `fly_to_next_annotation_keybind`
15+
- `fly_to_previous_annotation_keybind`
16+
- Add `ImageFilters` toolbox item to expose sliders for the following image css filters:
17+
- brightness
18+
- contrast
19+
- hue rotate
20+
- invert
21+
- saturate
22+
- Removed redundant dependencies that were being unnecessarily installed by users using npm to install ulabel
23+
- Updated webpack build process to properly provide both a minified (default) and unminified build (for better debugging)
24+
- Added package `exports` field with options: `.` (minified), `./min` (minified), `./debug` (unminified)
25+
- Add test coverage for both minified and unminified builds
26+
- Update dependencies and fix 12 security vulnerabilities
827

928
## [0.19.1] - Oct 9th, 2025
1029
- Add automated testing to the repo

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,35 @@ A browser-based tool for annotating images.
1111
ULabel is an entirely "frontend" tool. It can be incorporated into any HTML page using either the unpkg cdn
1212

1313
```html
14+
<!-- Use minified version (recommended for production) -->
15+
<script src="https://unpkg.com/ulabel"></script>
16+
17+
<!-- Or use unminified version for debugging -->
1418
<script src="https://unpkg.com/ulabel/dist/ulabel.js"></script>
1519
```
1620

17-
ULabel is also published on [npm](https://www.npmjs.com/package/ulabel). You can use npm to install it and serve the `dist/ulabel.js` file from `node_modules` locally.
21+
ULabel is also published on [npm](https://www.npmjs.com/package/ulabel). You can use npm to install it and serve the files from `node_modules` locally.
1822

1923
```bash
2024
npm install ulabel
2125
```
2226

2327
```html
28+
<!-- Use minified version (recommended for production) -->
29+
<script src="/node_modules/ulabel/dist/ulabel.min.js"></script>
30+
31+
<!-- Or use unminified version for debugging -->
2432
<script src="/node_modules/ulabel/dist/ulabel.js"></script>
2533
```
2634

2735
Or you can import it directly in your JavaScript code:
2836

2937
```javascript
38+
// Use minified version (default, recommended for production)
3039
import ULabel from 'ulabel';
40+
41+
// Or use unminified version for debugging
42+
import ULabel from 'ulabel/debug';
3143
```
3244

3345
An API spec can be found [here](https://github.com/SenteraLLC/ulabel/blob/main/api_spec.md), but as a brief overview: Once the script is included in your HTML doc, you can create a ULabel annotation session as follows.

api_spec.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This should eventually be replaced with a more comprehensive approach to documen
1313
- Hold `shift` when moving the cursor inside a polygon to begin annotating a new region or hole.
1414
- Press `Escape` or `crtl+z` to cancel the start of a new region or hole.
1515
- Press `Escape` to exit brush/erase mode.
16+
- Press `Tab` to set the zoom to focus on the next annotation
17+
- Press `Shift+Tab` to set the zoom to focus on the previous annotation
1618

1719
## ULabel Constructor
1820

@@ -56,6 +58,7 @@ class ULabel({
5658
"annotation_vanish": string
5759
},
5860
distance_filter_toolbox_item: FilterDistanceConfig,
61+
image_filters_toolbox_item: ImageFiltersConfig,
5962
change_zoom_keybind: string,
6063
create_point_annotation_keybind: string,
6164
default_annotation_size: number,
@@ -69,6 +72,9 @@ class ULabel({
6972
toggle_erase_mode_keybind: string,
7073
increase_brush_size_keybind: string,
7174
decrease_brush_size_keybind: string,
75+
fly_to_next_annotation_keybind: string,
76+
fly_to_previous_annotation_keybind: string | null,
77+
fly_to_max_zoom: number,
7278
n_annos_per_canvas: number
7379
})
7480
```
@@ -340,7 +346,8 @@ enum AllowedToolboxItem {
340346
KeypointSlider, // 6
341347
SubmitButtons, // 7
342348
FilterDistance, // 8
343-
Brush // 9
349+
Brush, // 9
350+
ImageFilters // 10
344351
}
345352
```
346353
You can access the AllowedToolboxItem enum by calling the static method:
@@ -385,11 +392,27 @@ type FilterDistanceConfig = {
385392
"show_options"?: boolean, // Default: true
386393
"show_overlay"?: boolean, // Default: false
387394
"toggle_overlay_keybind"?: string, // Default: "p"
388-
"filter_during_polyline_move"?: boolean, // Default: true. Set to false for performance boost,
395+
"filter_during_polyline_move"?: boolean, // Default: true. Set to false for performance boost,
389396
// since it will not update the filter/overlay until polyline moves/edits are complete.
390397
}
391398
```
392399
400+
### `image_filters_toolbox_item`
401+
Configuration object for the `ImageFilters` toolbox item with the following custom definitions:
402+
```javascript
403+
type ImageFiltersConfig = {
404+
"default_values"?: {
405+
"brightness"?: number, // Default: 100 (0-200%)
406+
"contrast"?: number, // Default: 100 (0-200%)
407+
"hueRotate"?: number, // Default: 0 (0-360 degrees)
408+
"invert"?: number, // Default: 0 (0-100%)
409+
"saturate"?: number // Default: 100 (0-200%)
410+
}
411+
}
412+
```
413+
414+
This toolbox item provides CSS filter controls that apply only to the image, not to the UI elements. Users can adjust brightness, contrast, hue rotation, inversion, and saturation using sliders. The filters are hardware-accelerated by modern browsers for optimal performance.
415+
393416
### `change_zoom_keybind`
394417
Keybind to change the zoom level. Must be a letter, and the lowercase version of the letter will set the zoom level to the `initial_crop`, while the capitalized version will show the full image. Default is `r`.
395418
@@ -429,6 +452,15 @@ Keybind to increase the brush size. Default is `]`. Requires the active subtask
429452
### `decrease_brush_size_keybind`
430453
Keybind to decrease the brush size. Default is `[`. Requires the active subtask to have a `polygon` mode.
431454
455+
### `fly_to_next_annotation_keybind`
456+
Keybind to set the zoom to focus on the next annotation. Default is `Tab`, which also will disable any default browser behavior for `Tab`.
457+
458+
### `fly_to_previous_annotation_keybind`
459+
Keybind to set the zoom to focus on the previous annotation. Default is `<null>`, which will default to `Shift+<fly_to_next_annotation_keybind>`.
460+
461+
### `fly_to_max_zoom`
462+
Maximum zoom factor used when flying-to an annotation. Default is `10`, value must be > `0`.
463+
432464
### `n_annos_per_canvas`
433465
The number of annotations to render on a single canvas. Default is `100`. Increasing this number may improve performance for jobs with a large number of annotations.
434466
@@ -476,6 +508,15 @@ Display utilities are provided for a constructed `ULabel` object.
476508
*() => void* -- Removes persistent event listeners from the document and window. Listeners attached directly to html elements are not explicitly removed.
477509
Note that ULabel will not function properly after this method is called. Designed for use in single-page applications before navigating away from the annotation page.
478510
511+
### `fly_to_next_annotation(increment)`
512+
Sets the zoom to focus on a non-deprecated, spatial annotation in the active subtask's ordering that is an `<increment>` number away from the previously focused annotation, if any. Returns `true` on success and `false` on failure (eg, no valid annotations exist, or an annotation is currently actively being edited).
513+
514+
### `fly_to_annotation_id(annotation_id, subtask_key, max_zoom)`
515+
Sets the zoom to focus on the provided annotation id, and switches to its subtask. Returns `true` on success and `false` on failure (eg, annotation doesn't exist in subtask, is not a spatial annotation, or is deprecated).
516+
517+
### `fly_to_annotation(annotation, subtask_key, max_zoom)`
518+
Sets the zoom to focus on the provided annotation, and switches to its subtask if provided. Returns `true` on success and `false` on failure (eg, annotation doesn't exist in subtask, is not a spatial annotation, or is deprecated).
519+
479520
## Generic Callbacks
480521
481522
Callbacks can be provided by calling `.on(fn, callback)` on a `ULabel` object.

demo/multi-class.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"toolbox_order": [
121121
AllowedToolboxItem.SubmitButtons,
122122
AllowedToolboxItem.ModeSelect,
123+
AllowedToolboxItem.ImageFilters,
123124
AllowedToolboxItem.ZoomPan,
124125
AllowedToolboxItem.AnnotationID,
125126
AllowedToolboxItem.ClassCounter,
@@ -133,6 +134,11 @@
133134
"click_and_drag_poly_annotations": false,
134135
"anno_scaling_mode": "fixed",
135136
"allow_annotations_outside_image": false,
137+
"image_filters_toolbox_item": {
138+
"default_values": {
139+
"brightness": 120
140+
},
141+
},
136142
});
137143
// Wait for ULabel instance to finish initialization
138144
ulabel.init(function () {

demo/resume-from.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@
288288
"subtasks": subtasks,
289289
"anno_scaling_mode": "inverse-zoom",
290290
"allow_annotations_outside_image": false,
291+
"fly_to_next_annotation_keybind": "w",
292+
"fly_to_previous_annotation_keybind": "`",
293+
"fly_to_max_zoom": 6
291294
});
292295
// Wait for ULabel instance to finish initialization
293296
ulabel.init(function() {

dist/ulabel.js

Lines changed: 61536 additions & 2 deletions
Large diffs are not rendered by default.

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.

index.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ export type RecolorActiveConfig = {
8484
gradient_turned_on: boolean;
8585
};
8686

87+
/**
88+
* Config object for the ImageFilters ToolboxItem.
89+
*/
90+
export type ImageFiltersConfig = {
91+
default_values?: {
92+
brightness?: number;
93+
contrast?: number;
94+
hueRotate?: number;
95+
invert?: number;
96+
saturate?: number;
97+
};
98+
};
99+
87100
/**
88101
* Config object for the FilterPointDistanceFromRow ToolboxItem.
89102
*/
@@ -320,6 +333,9 @@ export class ULabel {
320333
force_filter_all?: boolean,
321334
offset?: Offset,
322335
): void;
336+
public fly_to_next_annotation(increment: number, max_zoom?: number): boolean;
337+
public fly_to_annotation_id(annotation_id: string, subtask_key?: string, max_zoom?: number): boolean;
338+
public fly_to_annotation(annotation: ULabelAnnotation, subtask_key?: string, max_zoom?: number): boolean;
323339

324340
// Brush
325341
// TODO (joshua-dean): should these actually be optional?

0 commit comments

Comments
 (0)