Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2ebb957
functional fly to specific annotation
TrevorBurgoyne Oct 15, 2025
d81377f
better flyto zoom, more methods
TrevorBurgoyne Oct 15, 2025
d7473c0
prevent infinite loop when there are not valid flyto targets
TrevorBurgoyne Oct 15, 2025
5b3f9f3
Update changelog and api spec
TrevorBurgoyne Oct 15, 2025
3c4cb99
Bump version and build.
TrevorBurgoyne Oct 15, 2025
cdee4db
Make keybinds configurable
TrevorBurgoyne Oct 15, 2025
dee20d2
Apply suggestions from code review
TrevorBurgoyne Oct 15, 2025
78e50d5
add max_zoom arg to config how zoomed in the flyto goes
TrevorBurgoyne Oct 15, 2025
5809962
Merge branch 'feature/flyto' of github.com:SenteraLLC/ulabel into fea…
TrevorBurgoyne Oct 15, 2025
3ea19c2
initial implementation
TrevorBurgoyne Oct 16, 2025
7d00a9b
Add configurable initial filter values, update docs
TrevorBurgoyne Oct 16, 2025
ab54cfc
Update changelog
TrevorBurgoyne Oct 16, 2025
afd7733
Apply suggestions from review
TrevorBurgoyne Oct 16, 2025
20da616
move all deps to dev, and dont minify ulabel.js
TrevorBurgoyne Oct 17, 2025
0e3d8db
unit tests for both builds
TrevorBurgoyne Oct 17, 2025
83cffc3
e2e tests for both builds
TrevorBurgoyne Oct 17, 2025
688378c
fix npm audit issues
TrevorBurgoyne Oct 17, 2025
77505c4
default to minified
TrevorBurgoyne Oct 17, 2025
f554c99
update changelog
TrevorBurgoyne Oct 17, 2025
193efe6
Apply changes from review
TrevorBurgoyne Oct 17, 2025
880ddb5
handle letter + shift by comparing fly to keybinds to lowercase
TrevorBurgoyne Oct 21, 2025
e84542b
add zoom val setter to ensure it stays > 0
TrevorBurgoyne Oct 21, 2025
eb3ff30
Merge branch 'feature/flyto' into feature/image-filters
TrevorBurgoyne Oct 21, 2025
c3a0b33
rebuild after merge
TrevorBurgoyne Oct 21, 2025
574c519
Merge branch 'feature/image-filters' into fix/webpack
TrevorBurgoyne Oct 21, 2025
916d181
rebuild after merge
TrevorBurgoyne Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 47 additions & 26 deletions .github/tasks.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
## Tasks
- [x] Read the discussion in [#209](https://github.com/SenteraLLC/ulabel/issues/209) and understand the requirements.
- [x] Implement the fix requested in [#209](https://github.com/SenteraLLC/ulabel/issues/209).
- [x] Build the fix and ensure the build succeeds by running `npm run build`.
- [x] Receive confirmation that the fix works as expected.
- [x] Configure Jest to suppress verbose stack traces (added --noStackTrace flag)
- [x] Fix class ID test to check ID is not in existing list (implementation-agnostic)
- [x] Increase max workers from 1 to 2 (reduced test time from 200s+ to ~23s)
- [ ] Fix remaining unit test failures (6 failures, 14 passed)
- Spatial payload tests need DOM mocking
- ID payload tests need DOM mocking
- Note: Some error messages contain minified code context - this is expected when testing against dist/ulabel.js
- [x] Refactor e2e tests to use utility functions
- [x] Create init_utils.js with wait_for_ulabel_init
- [x] Create annotation_utils.js with get_annotation_count, get_annotation_by_index, get_all_annotations
- [x] Create mode_utils.js with switch_to_mode
- [x] Create subtask_utils.js with get_current_subtask_key, switch_to_subtask, get_subtask_count
- [x] Update basic-functionality.spec.js to use new utilities
- [x] All 6 basic functionality tests passing
- [x] Refactor tests/ folder to use snake_case naming convention
- [x] Updated all utility function names to snake_case
- [x] Updated all variable names in e2e tests to snake_case
- [x] Updated all variable names in unit tests to snake_case
- [x] Updated all variable names in setup.js to snake_case
- [x] Updated all variable names in utility files to snake_case
- [x] Verified unit tests pass (14 passed)
- [x] Verified e2e tests pass (6 passed)
- [x] Read the description in [#164](https://github.com/SenteraLLC/ulabel/issues/164)
- [x] Write a clear summary of the requested change
- [x] Propose some options of how to proceed. Wait for user input to decide which to try first.

### Decision: Proceeding with Option 3 (Webpack modernization + dependency cleanup)

#### Step 1: Move dependencies to devDependencies
- [x] Move `@turf/turf` from dependencies to devDependencies
- [x] Move `jquery` from dependencies to devDependencies
- [x] Move `polygon-clipping` from dependencies to devDependencies
- [x] Move `uuidv4` from dependencies to devDependencies
- [x] Test: Run `npm install` and verify it works
- [x] Test: Run `npm run build` and verify output is identical (both files 1039.6 KB)

#### Step 2: Enable and modernize webpack minification ✅ COMPLETE
- [x] Remove commented-out UglifyJsPlugin code (deprecated)
- [x] Enable webpack 5's built-in TerserPlugin for minification
- [x] Configure it to only minify `ulabel.min.js`, not `ulabel.js`
- [x] Test: Run `npm run build` and verify both files are created
- [x] Test: Verify `ulabel.js` is NOT minified (readable) - 2.33 MB with webpack runtime and formatted code
- [x] Test: Verify `ulabel.min.js` IS minified (smaller size) - 1.02 MB minified
- [x] Test: Run `npm run lint` - No errors
- [x] Note: File size difference (2.33 MB vs 1.02 MB) is expected - readable version includes webpack runtime overhead

#### Step 3: Verify and document ✅ COMPLETE
- [x] Compare file sizes before/after
- Before: Both files 1039 KB (both minified, minification was disabled)
- After: ulabel.js 2.33 MB (readable), ulabel.min.js 1.02 MB (minified)
- Result: Minification now working correctly, file size increase for non-min version is expected
- [x] Update CHANGELOG.md with changes
- [x] Document any findings or recommendations

#### Step 4: Security and dependency updates ✅ COMPLETE
- [x] Run `npm audit` to identify vulnerabilities
- [x] Fix 12 vulnerabilities using `npm audit fix`
- [x] Apply breaking changes for remaining issues with `npm audit fix --force`
- [x] Update `typescript-eslint` packages to be compatible with ESLint 9.37.0
- [x] Fix linting issues in `tests/e2e/fixtures.js`
- [x] Verify all tests still pass (28 unit tests + 36 e2e tests)
- [x] Final audit: 0 vulnerabilities

#### Step 5: Configure package exports for minified by default ✅ COMPLETE
- [x] Update `main` and `module` fields to point to `dist/ulabel.min.js`
- [x] Add `exports` field with options: `.` (minified), `./min` (minified), `./debug` (unminified)
- [x] Update `unpkg` field to serve minified version by default
- [x] Update README.md with usage examples for both minified and unminified versions
- [x] Document clear import patterns for users
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Install dependencies
run: npm install

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

e2e-tests:
Expand All @@ -50,7 +50,7 @@ jobs:
- name: Build project
run: npm run build

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

- name: Upload Playwright report
Expand Down
25 changes: 22 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,28 @@

All notable changes to this project will be documented here.

## [unreleased]

Nothing yet.
## [unreleased]https://github.com/SenteraLLC/ulabel/pull/233

## [0.20.0] - Oct 15th, 2025
- Add `fly-to` functions, which sets the zoom and focus to a specific annotation
- `fly_to_next_annotation()`
- `fly_to_annotation_id()`
- `fly_to_annotation()`
- Add `Tab` and `Tab+Shift` default keybinds to fly-to the next/previous annotation, respectively
- Keybinds are configurable:
- `fly_to_next_annotation_keybind`
- `fly_to_previous_annotation_keybind`
- Add `ImageFilters` toolbox item to expose sliders for the following image css filters:
- brightness
- contrast
- hue rotate
- invert
- saturate
- Removed redundant dependencies that were being unnecessarily installed by users using npm to install ulabel
- Updated webpack build process to properly provide both a minified (default) and unminified build (for better debugging)
- Added package `exports` field with options: `.` (minified), `./min` (minified), `./debug` (unminified)
- Add test coverage for both minified and unminified builds
- Update dependencies and fix 12 security vulnerabilities

## [0.19.1] - Oct 9th, 2025
- Add automated testing to the repo
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,35 @@ A browser-based tool for annotating images.
ULabel is an entirely "frontend" tool. It can be incorporated into any HTML page using either the unpkg cdn

```html
<!-- Use minified version (recommended for production) -->
<script src="https://unpkg.com/ulabel"></script>

<!-- Or use unminified version for debugging -->
<script src="https://unpkg.com/ulabel/dist/ulabel.js"></script>
```

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.
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.

```bash
npm install ulabel
```

```html
<!-- Use minified version (recommended for production) -->
<script src="/node_modules/ulabel/dist/ulabel.min.js"></script>

<!-- Or use unminified version for debugging -->
<script src="/node_modules/ulabel/dist/ulabel.js"></script>
```

Or you can import it directly in your JavaScript code:

```javascript
// Use minified version (default, recommended for production)
import ULabel from 'ulabel';

// Or use unminified version for debugging
import ULabel from 'ulabel/debug';
```

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.
Expand Down
45 changes: 43 additions & 2 deletions api_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This should eventually be replaced with a more comprehensive approach to documen
- Hold `shift` when moving the cursor inside a polygon to begin annotating a new region or hole.
- Press `Escape` or `crtl+z` to cancel the start of a new region or hole.
- Press `Escape` to exit brush/erase mode.
- Press `Tab` to set the zoom to focus on the next annotation
- Press `Shift+Tab` to set the zoom to focus on the previous annotation

## ULabel Constructor

Expand Down Expand Up @@ -56,6 +58,7 @@ class ULabel({
"annotation_vanish": string
},
distance_filter_toolbox_item: FilterDistanceConfig,
image_filters_toolbox_item: ImageFiltersConfig,
change_zoom_keybind: string,
create_point_annotation_keybind: string,
default_annotation_size: number,
Expand All @@ -69,6 +72,9 @@ class ULabel({
toggle_erase_mode_keybind: string,
increase_brush_size_keybind: string,
decrease_brush_size_keybind: string,
fly_to_next_annotation_keybind: string,
fly_to_previous_annotation_keybind: string | null,
fly_to_max_zoom: number,
n_annos_per_canvas: number
})
```
Expand Down Expand Up @@ -340,7 +346,8 @@ enum AllowedToolboxItem {
KeypointSlider, // 6
SubmitButtons, // 7
FilterDistance, // 8
Brush // 9
Brush, // 9
ImageFilters // 10
}
```
You can access the AllowedToolboxItem enum by calling the static method:
Expand Down Expand Up @@ -385,11 +392,27 @@ type FilterDistanceConfig = {
"show_options"?: boolean, // Default: true
"show_overlay"?: boolean, // Default: false
"toggle_overlay_keybind"?: string, // Default: "p"
"filter_during_polyline_move"?: boolean, // Default: true. Set to false for performance boost,
"filter_during_polyline_move"?: boolean, // Default: true. Set to false for performance boost,
// since it will not update the filter/overlay until polyline moves/edits are complete.
}
```

### `image_filters_toolbox_item`
Configuration object for the `ImageFilters` toolbox item with the following custom definitions:
```javascript
type ImageFiltersConfig = {
"default_values"?: {
"brightness"?: number, // Default: 100 (0-200%)
"contrast"?: number, // Default: 100 (0-200%)
"hueRotate"?: number, // Default: 0 (0-360 degrees)
"invert"?: number, // Default: 0 (0-100%)
"saturate"?: number // Default: 100 (0-200%)
}
}
```

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.

### `change_zoom_keybind`
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`.

Expand Down Expand Up @@ -429,6 +452,15 @@ Keybind to increase the brush size. Default is `]`. Requires the active subtask
### `decrease_brush_size_keybind`
Keybind to decrease the brush size. Default is `[`. Requires the active subtask to have a `polygon` mode.

### `fly_to_next_annotation_keybind`
Keybind to set the zoom to focus on the next annotation. Default is `Tab`, which also will disable any default browser behavior for `Tab`.

### `fly_to_previous_annotation_keybind`
Keybind to set the zoom to focus on the previous annotation. Default is `<null>`, which will default to `Shift+<fly_to_next_annotation_keybind>`.

### `fly_to_max_zoom`
Maximum zoom factor used when flying-to an annotation. Default is `10`, value must be > `0`.

### `n_annos_per_canvas`
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.

Expand Down Expand Up @@ -476,6 +508,15 @@ Display utilities are provided for a constructed `ULabel` object.
*() => void* -- Removes persistent event listeners from the document and window. Listeners attached directly to html elements are not explicitly removed.
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.

### `fly_to_next_annotation(increment)`
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).

### `fly_to_annotation_id(annotation_id, subtask_key, max_zoom)`
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).

### `fly_to_annotation(annotation, subtask_key, max_zoom)`
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).

## Generic Callbacks

Callbacks can be provided by calling `.on(fn, callback)` on a `ULabel` object.
Expand Down
6 changes: 6 additions & 0 deletions demo/multi-class.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"toolbox_order": [
AllowedToolboxItem.SubmitButtons,
AllowedToolboxItem.ModeSelect,
AllowedToolboxItem.ImageFilters,
AllowedToolboxItem.ZoomPan,
AllowedToolboxItem.AnnotationID,
AllowedToolboxItem.ClassCounter,
Expand All @@ -133,6 +134,11 @@
"click_and_drag_poly_annotations": false,
"anno_scaling_mode": "fixed",
"allow_annotations_outside_image": false,
"image_filters_toolbox_item": {
"default_values": {
"brightness": 120
},
},
});
// Wait for ULabel instance to finish initialization
ulabel.init(function () {
Expand Down
3 changes: 3 additions & 0 deletions demo/resume-from.html
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@
"subtasks": subtasks,
"anno_scaling_mode": "inverse-zoom",
"allow_annotations_outside_image": false,
"fly_to_next_annotation_keybind": "w",
"fly_to_previous_annotation_keybind": "`",
"fly_to_max_zoom": 6
});
// Wait for ULabel instance to finish initialization
ulabel.init(function() {
Expand Down
61,538 changes: 61,536 additions & 2 deletions dist/ulabel.js

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ export type RecolorActiveConfig = {
gradient_turned_on: boolean;
};

/**
* Config object for the ImageFilters ToolboxItem.
*/
export type ImageFiltersConfig = {
default_values?: {
brightness?: number;
contrast?: number;
hueRotate?: number;
invert?: number;
saturate?: number;
};
};

/**
* Config object for the FilterPointDistanceFromRow ToolboxItem.
*/
Expand Down Expand Up @@ -320,6 +333,9 @@ export class ULabel {
force_filter_all?: boolean,
offset?: Offset,
): void;
public fly_to_next_annotation(increment: number, max_zoom?: number): boolean;
public fly_to_annotation_id(annotation_id: string, subtask_key?: string, max_zoom?: number): boolean;
public fly_to_annotation(annotation: ULabelAnnotation, subtask_key?: string, max_zoom?: number): boolean;

// Brush
// TODO (joshua-dean): should these actually be optional?
Expand Down
Loading