Skip to content

Commit cc692f5

Browse files
author
Mishig Davaadorj
authored
Widget Image Segmentation (#378)
* [WIP] Image Segmentation Widget * Add css animations & refactoring ImageSegWidget * Lint/formamt * Update implemnetation * Refactor * Turn off canvas animation * Turn off widget on Safari * Chore * Make dev change for demo * Add demo img * Bring back canvas animation * Detect safari specifically * Revert "Detect safari specifically" This reverts commit 9e9091b. * Chore * Performant animation * Rename files * Give feedback to safari users * polyfillCreateImageBitmap for safari * Rm unnecessary lines * New img-seg mask output shape * Refactor * Call api * Rm dev changes * Disable file selector when loading * Add resize handler (FireFox issue) * Revert dev change * Fix snake_case -> cameCase * Rename clip -> clamp * Add EOF tailwind.css * Rm setSlice method * Make addOutputColor functional * Create consts file * Obj det use consts * Update consts file * Rm unnecessary parts of polufill * Rm rgb values from colors const * css drag n drop * Chore img class * Rename `modelId` -> `id` * Apply ObjDet updates * Update WidgetOutputchart css * Add examples picker for the img sg * Fix dropzone pointer-events-none * chore * Better canvas interactivity * Prevent drop on loading doprozone * Disable examples picker when loading * Activate outputjson
1 parent 254e837 commit cc692f5

File tree

33 files changed

+507
-33
lines changed

33 files changed

+507
-33
lines changed

widgets/src/lib/InferenceWidget/InferenceWidget.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import FeatureExtractionWidget from "./widgets/FeatureExtractionWidget/FeatureExtractionWidget.svelte";
1111
import FillMaskWidget from "./widgets/FillMaskWidget/FillMaskWidget.svelte";
1212
import ImageClassificationWidget from "./widgets/ImageClassificationWidget/ImageClassificationWidget.svelte";
13+
import ImageSegmentationWidget from "./widgets/ImageSegmentationWidget/ImageSegmentationWidget.svelte";
1314
import ObjectDetectionWidget from "./widgets/ObjectDetectionWidget/ObjectDetectionWidget.svelte";
1415
import QuestionAnsweringWidget from "./widgets/QuestionAnsweringWidget/QuestionAnsweringWidget.svelte";
1516
import SentenceSimilarityWidget from "./widgets/SentenceSimilarityWidget/SentenceSimilarityWidget.svelte";
@@ -44,6 +45,7 @@
4445
"feature-extraction": FeatureExtractionWidget,
4546
"fill-mask": FillMaskWidget,
4647
"image-classification": ImageClassificationWidget,
48+
"image-segmentation": ImageSegmentationWidget,
4749
"object-detection": ObjectDetectionWidget,
4850
"question-answering": QuestionAnsweringWidget,
4951
"sentence-similarity": SentenceSimilarityWidget,

widgets/src/lib/InferenceWidget/shared/ViewUtils.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ const ESCAPED = {
88
">": ">",
99
};
1010

11+
/**
12+
* Returns a function that clamps input value to range [min <= x <= max].
13+
*/
14+
export function clamp(x: number, min: number, max: number): number {
15+
return Math.max(min, Math.min(x, max));
16+
}
17+
1118
/**
1219
* HTML escapes the passed string
1320
*/
@@ -120,9 +127,9 @@ export function convertTableToData(table: (string | number)[][]): TableData {
120127
}
121128

122129
/*
123-
* Converts data from {Header0: [ColumnVal0, ...], Header1: [Column1Val0, ...], Header2: [Column2Val0, ...]}
124-
* to [[Header0, Header1, Header2], [Column0Val0, Column1Val0, Column2Val0], ...]
125-
*/
130+
* Converts data from {Header0: [ColumnVal0, ...], Header1: [Column1Val0, ...], Header2: [Column2Val0, ...]}
131+
* to [[Header0, Header1, Header2], [Column0Val0, Column1Val0, Column2Val0], ...]
132+
*/
126133
export function convertDataToTable(data: TableData): (string | number)[][] {
127134
const dataArray = Object.entries(data); // [header, cell[]][]
128135
const nbCols = dataArray.length;
@@ -136,11 +143,22 @@ export function convertDataToTable(data: TableData): (string | number)[][] {
136143
);
137144
}
138145

146+
/*
147+
* Converts hex string to rgb array (i.e. [r,g,b])
148+
* original from https://stackoverflow.com/a/39077686/6558628
149+
*/
150+
export function hexToRgb(hex: string): number[]{
151+
return hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i
152+
,(_, r, g, b) => '#' + r + r + g + g + b + b)
153+
.substring(1).match(/.{2}/g)
154+
.map(x => parseInt(x, 16))
155+
}
156+
139157
/*
140158
* For Tailwind:
141159
bg-blue-100 border-blue-100 dark:bg-blue-800 dark:border-blue-800
142160
bg-green-100 border-green-100 dark:bg-green-800 dark:border-green-800
143161
bg-yellow-100 border-yellow-100 dark:bg-yellow-800 dark:border-yellow-800
144162
bg-purple-100 border-purple-100 dark:bg-purple-800 dark:border-purple-800
145163
bg-red-100 border-red-100 dark:bg-red-800 dark:border-red-800
146-
*/
164+
*/

widgets/src/lib/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
async function onDrop(e: DragEvent) {
2424
isDragging = false;
2525
const itemList = e.dataTransfer?.items;
26-
if (!itemList) {
26+
if (!itemList || isLoading) {
2727
return;
2828
}
2929
const items: DataTransferItem[] = [];
@@ -56,6 +56,7 @@
5656
{accept}
5757
bind:this={fileInput}
5858
on:change={onChange}
59+
disabled={isLoading}
5960
style="display: none;"
6061
type="file"
6162
/>
@@ -78,7 +79,9 @@
7879
{#if !imgSrc}
7980
<span class="pointer-events-none text-sm">{label}</span>
8081
{:else}
81-
<slot />
82+
<div class={isDragging && "pointer-events-none"}>
83+
<slot />
84+
</div>
8285
{/if}
8386
{#if isLoading}
8487
<div

widgets/src/lib/InferenceWidget/shared/WidgetFileInput/WidgetFileInput.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
export let classNames = "";
77
export let isLoading = false;
88
export let label = "Browse for file";
9-
export let onSelectFile: (file: File) => void;
9+
export let onSelectFile: (file: File | Blob) => void;
1010
1111
let fileInput: HTMLInputElement;
1212
let isDragging = false;

widgets/src/lib/InferenceWidget/shared/WidgetInputSamples/WidgetInputSamples.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script>
22
import { slide } from "svelte/transition";
33
4+
export let isLoading = false;
45
export let inputSamples: Record<string, any>[];
56
export let applyInputSample: (sample: Record<string, any>) => void;
67
export let previewInputSample: (sample: Record<string, any>) => void;
@@ -52,7 +53,10 @@
5253

5354
<svelte:window on:click={onClick} />
5455

55-
<div class="relative z-10 ml-2" bind:this={containerEl}>
56+
<div
57+
class="relative z-10 ml-2 {isLoading && 'pointer-events-none opacity-50'}"
58+
bind:this={containerEl}
59+
>
5660
<div
5761
class="no-hover:hidden inline-flex justify-between w-32 lg:w-44 rounded-md border border-gray-100 px-4 py-1"
5862
on:click={toggleOptionsVisibility}

widgets/src/lib/InferenceWidget/shared/WidgetOutputChart/WidgetOutputChart.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ from-lime-400 to-lime-200 dark:from-lime-400 dark:to-lime-600
2424
</script>
2525

2626
{#if output.length}
27-
<div class="space-y-4 {classNames}">
27+
<div>
2828
{#each output as { label, score, color="purple" }, index}
2929
<div
3030
class="flex items-start justify-between font-mono text-xs animate__animated animate__fadeIn leading-none
31-
transition duration-200 ease-in-out
31+
transition duration-200 ease-in-out {classNames}
3232
{highlightIndex !== -1 &&
3333
highlightIndex !== index &&
3434
'opacity-30 filter grayscale'}

widgets/src/lib/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
export let apiUrl: string;
1414
export let computeTime: string;
1515
export let error: string;
16+
export let isLoading = false;
1617
export let model: WidgetProps["model"];
1718
export let modelLoading = {
1819
isLoading: false,
@@ -64,6 +65,7 @@
6465
{#if inputSamples.length}
6566
<!-- Show samples selector when there are more than one sample -->
6667
<WidgetInputSamples
68+
{isLoading}
6769
{inputSamples}
6870
{applyInputSample}
6971
{previewInputSample}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Color palette for obj-det & img-seg widgets
3+
*/
4+
export const COLORS = [
5+
{
6+
"color": "red",
7+
"hex": "#f87171",
8+
},
9+
{
10+
"color": "green",
11+
"hex": "#4ade80",
12+
},
13+
{
14+
"color": "yellow",
15+
"hex": "#facc15",
16+
},
17+
{
18+
"color": "blue",
19+
"hex": "#60a5fa",
20+
},
21+
{
22+
"color": "orange",
23+
"hex": "#fb923c",
24+
},
25+
{
26+
"color": "purple",
27+
"hex": "#c084fc",
28+
},
29+
{
30+
"color": "cyan",
31+
"hex": "#22d3ee",
32+
},
33+
{
34+
"color": "lime",
35+
"hex": "#a3e635",
36+
}
37+
] as const;

widgets/src/lib/InferenceWidget/shared/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,11 @@ export type DetectedObject = {
2929
score: number;
3030
color?: string;
3131
}
32+
export interface ImageSegment {
33+
label: string;
34+
score: number;
35+
mask: string;
36+
color?: string;
37+
imgData?: ImageData;
38+
bitmap?: ImageBitmap;
39+
};

widgets/src/lib/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
{applyInputSample}
147147
{computeTime}
148148
{error}
149+
{isLoading}
149150
{model}
150151
{modelLoading}
151152
{noTitle}
@@ -187,6 +188,6 @@
187188
</form>
188189
</svelte:fragment>
189190
<svelte:fragment slot="bottom">
190-
<WidgetOutputChart classNames="mt-4" {output} />
191+
<WidgetOutputChart classNames="pt-4" {output} />
191192
</svelte:fragment>
192193
</WidgetWrapper>

0 commit comments

Comments
 (0)