Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 34 additions & 26 deletions .github/tasks.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
## 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 [#128](https://github.com/SenteraLLC/ulabel/issues/128)
- [x] Update this checklist with steps to take in order to implement the requested functionality

### Implementation Steps
- [x] Design and implement UI for image filters
- [x] Create a collapsible/popup menu component for filter controls
- [x] Add sliders for each CSS filter property:
- [x] Brightness (0-200%, default 100%)
- [x] Contrast (0-200%, default 100%)
- [x] Hue-rotate (0-360deg, default 0)
- [x] Invert (0-100%, default 0%)
- [x] Saturate (0-200%, default 100%)
- [x] Add reset button to restore default values

- [x] Implement filter state management
- [x] Add filter state to configuration/state management
- [x] Make default filter values configurable
- [x] Add getter/setter methods for filter values

- [x] Apply CSS filters to canvas/image
- [x] Apply CSS filter property to the image canvas element only (not whole screen)
- [x] Update filter values dynamically as sliders change
- [x] Ensure filters don't affect UI elements (text, buttons, etc.)

- [x] Testing
- [x] Test all filter controls work correctly
- [x] Verify filters only apply to image, not UI
- [x] Test filter combinations
- [x] Run linting: `npm run lint`
- [x] Run build: `npm run build`

- [x] Documentation
- [x] Update API documentation with new filter methods
- [x] Tested on demo page (multi-class demo works great!)
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ Nothing yet.
- 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

## [0.19.1] - Oct 9th, 2025
- Add automated testing to the repo
Expand Down
22 changes: 20 additions & 2 deletions api_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,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 Down Expand Up @@ -345,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 @@ -390,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
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
2 changes: 1 addition & 1 deletion dist/ulabel.js

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

13 changes: 13 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
18 changes: 18 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
FilterDistanceConfig,
ImageFiltersConfig,
InitialCrop,
ImageData,
RecolorActiveConfig,
Expand All @@ -19,6 +20,7 @@ import {
ToolboxItem,
} from "./toolbox";
import { SubmitButtons } from "./toolbox_items/submit_buttons";
import { ImageFiltersToolboxItem } from "./toolbox_items/image_filters";
import { is_object_and_not_array } from "./utilities";

/* eslint-disable @stylistic/no-multi-spaces */
Expand All @@ -33,6 +35,7 @@ export enum AllowedToolboxItem {
SubmitButtons, // 7
FilterDistance, // 8
Brush, // 9
ImageFilters, // 10
}
/* eslint-enable @stylistic/no-multi-spaces */

Expand All @@ -54,6 +57,16 @@ export const DEFAULT_FILTER_DISTANCE_CONFIG: FilterDistanceConfig = {
filter_during_polyline_move: true,
};

export const DEFAULT_IMAGE_FILTERS_CONFIG: ImageFiltersConfig = {
default_values: {
brightness: 100,
contrast: 100,
hueRotate: 0,
invert: 0,
saturate: 100,
},
};

export class Configuration {
// Values useful for generating HTML for tool
public container_id: string = "container";
Expand Down Expand Up @@ -118,12 +131,14 @@ export class Configuration {
[AllowedToolboxItem.SubmitButtons, SubmitButtons],
[AllowedToolboxItem.FilterDistance, FilterPointDistanceFromRow],
[AllowedToolboxItem.Brush, BrushToolboxItem],
[AllowedToolboxItem.ImageFilters, ImageFiltersToolboxItem],
]);

// Default toolbox order used when the user doesn't specify one
public toolbox_order: AllowedToolboxItem[] = [
AllowedToolboxItem.ModeSelect,
AllowedToolboxItem.Brush,
AllowedToolboxItem.ImageFilters,
AllowedToolboxItem.ZoomPan,
AllowedToolboxItem.AnnotationResize,
AllowedToolboxItem.AnnotationID,
Expand All @@ -149,6 +164,9 @@ export class Configuration {
// Config for FilterDistanceToolboxItem
public distance_filter_toolbox_item: FilterDistanceConfig = DEFAULT_FILTER_DISTANCE_CONFIG;

// Config for ImageFiltersToolboxItem
public image_filters_toolbox_item: ImageFiltersConfig = DEFAULT_IMAGE_FILTERS_CONFIG;

public change_zoom_keybind: string = "r";

public create_point_annotation_keybind: string = "c";
Expand Down
Loading