@viamrobotics/test-widgets 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.d.ts +0 -2
- package/dist/components/index.js +0 -2
- package/dist/components/widgets/do-command/do-command.svelte +14 -4
- package/dist/components/widgets/switch/position-view.svelte +6 -6
- package/dist/components/widgets/vision-service/context.svelte.js +6 -6
- package/package.json +1 -1
- package/dist/components/image-annotations/annotation-edit-utils.d.ts +0 -21
- package/dist/components/image-annotations/annotation-edit-utils.js +0 -183
- package/dist/components/image-annotations/bounding-box-labeler.svelte +0 -521
- package/dist/components/image-annotations/bounding-box-labeler.svelte.d.ts +0 -51
- package/dist/components/image-annotations/bounding-box-types.d.ts +0 -29
- package/dist/components/image-annotations/bounding-box-types.js +0 -17
- package/dist/components/image-annotations/bounding-box.svelte +0 -230
- package/dist/components/image-annotations/bounding-box.svelte.d.ts +0 -44
- package/dist/components/image-annotations/get-image-size.d.ts +0 -7
- package/dist/components/image-annotations/get-image-size.js +0 -29
- package/dist/components/image-annotations/labeler-constants.d.ts +0 -4
- package/dist/components/image-annotations/labeler-constants.js +0 -4
- package/dist/components/image-annotations/resize-corner.svelte +0 -47
- package/dist/components/image-annotations/resize-corner.svelte.d.ts +0 -9
- package/dist/components/image-annotations/resize-edge.svelte +0 -51
- package/dist/components/image-annotations/resize-edge.svelte.d.ts +0 -9
- package/dist/components/image-annotations/resize-handle-props.d.ts +0 -60
- package/dist/components/image-annotations/resize-handle-props.js +0 -66
- package/dist/components/image-annotations/resize-handles.svelte +0 -53
- package/dist/components/image-annotations/resize-handles.svelte.d.ts +0 -8
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { resize } from '@svelte-put/resize'
|
|
3
|
-
import { clickOutside, Progress } from '@viamrobotics/prime-core'
|
|
4
|
-
import { onMount, untrack } from 'svelte'
|
|
5
|
-
|
|
6
|
-
import { getImageSize } from '../widgets/vision-service/get-image-size'
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
BoundingBox as BoundingBoxData,
|
|
10
|
-
BoundingCoords,
|
|
11
|
-
Coordinates,
|
|
12
|
-
ResizeHandleLocation,
|
|
13
|
-
} from './bounding-box-types'
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
calculatePastedBox,
|
|
17
|
-
createNormalizedTempBox,
|
|
18
|
-
getBoundingRect,
|
|
19
|
-
getModifyKeyForOS,
|
|
20
|
-
} from './annotation-edit-utils'
|
|
21
|
-
import BoundingBox from './bounding-box.svelte'
|
|
22
|
-
import { getImageSize as getImageSizeDynamic, type Size } from './get-image-size'
|
|
23
|
-
import {
|
|
24
|
-
INVERT_X_FLOAT_DISTANCE,
|
|
25
|
-
INVERT_Y_FLOAT_DISTANCE,
|
|
26
|
-
ZOOM_MAX,
|
|
27
|
-
ZOOM_MIN,
|
|
28
|
-
} from './labeler-constants'
|
|
29
|
-
import { ResizeHandleClassMap } from './resize-handle-props'
|
|
30
|
-
|
|
31
|
-
const MODIFY_KEY = getModifyKeyForOS()
|
|
32
|
-
|
|
33
|
-
interface Props {
|
|
34
|
-
/** whether to allow drawing of new bounding boxes */
|
|
35
|
-
allowDrawing: boolean
|
|
36
|
-
/** inference candidates displayed using readonly bounding boxes */
|
|
37
|
-
candidateBoundingBoxes?: BoundingBoxData[]
|
|
38
|
-
/** currently selected box */
|
|
39
|
-
editTarget?: BoundingBoxData | undefined
|
|
40
|
-
/** map of annotations displayed in their hovered states */
|
|
41
|
-
hoveredAnnotations: Record<string, boolean>
|
|
42
|
-
/** whether image is loading */
|
|
43
|
-
imageLoading: boolean
|
|
44
|
-
/** URI of image */
|
|
45
|
-
imageSrc: string
|
|
46
|
-
/** used to set the max size of the display container, used for bounding boxes */
|
|
47
|
-
maxSize?: { maxHeight: number; maxWidth: number }
|
|
48
|
-
/** whether to allow any interactivity (i.e. drawing, editing, deleting) */
|
|
49
|
-
readonly?: boolean
|
|
50
|
-
/** bounding boxes saved on the image */
|
|
51
|
-
savedBoundingBoxes: BoundingBoxData[]
|
|
52
|
-
/** user selected label to apply to drawn boxes */
|
|
53
|
-
selectedLabel?: string
|
|
54
|
-
/** whether to display candidates and saved boxes or just candidates */
|
|
55
|
-
showSavedBoundingBoxes?: boolean
|
|
56
|
-
/** how much image is zoomed */
|
|
57
|
-
zoom?: number
|
|
58
|
-
/** accept the selected inference candidate box */
|
|
59
|
-
acceptCandidate?: (id: string) => void
|
|
60
|
-
/** saves drawn bounding box to image */
|
|
61
|
-
createBoundingBox?: (newBox: BoundingBoxData) => void
|
|
62
|
-
/** deletes saved bounding box */
|
|
63
|
-
deleteBoundingBox?: (annotationID: string, targetLabel: string) => void
|
|
64
|
-
/** save changes to existing box */
|
|
65
|
-
editBoundingBox?: (id: string, newBox: BoundingBoxData) => void
|
|
66
|
-
/** invoked when displayed image loaded */
|
|
67
|
-
onImageLoad?: VoidFunction
|
|
68
|
-
/** reject the selected inference candidate box */
|
|
69
|
-
rejectCandidate?: (id: string) => void
|
|
70
|
-
/** set which box is being edited */
|
|
71
|
-
setEditTarget?: (target: BoundingBoxData | undefined) => void
|
|
72
|
-
/** used to change which boxes are hovered */
|
|
73
|
-
setHoveredAnnotations?: (newHovered: Record<string, boolean>) => void
|
|
74
|
-
/** sets zoom */
|
|
75
|
-
setZoom?: (value: number) => void
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const {
|
|
79
|
-
allowDrawing,
|
|
80
|
-
candidateBoundingBoxes,
|
|
81
|
-
editTarget,
|
|
82
|
-
hoveredAnnotations,
|
|
83
|
-
imageLoading,
|
|
84
|
-
imageSrc,
|
|
85
|
-
maxSize,
|
|
86
|
-
readonly = false,
|
|
87
|
-
savedBoundingBoxes,
|
|
88
|
-
selectedLabel,
|
|
89
|
-
showSavedBoundingBoxes = true,
|
|
90
|
-
zoom = 1,
|
|
91
|
-
acceptCandidate,
|
|
92
|
-
createBoundingBox,
|
|
93
|
-
deleteBoundingBox,
|
|
94
|
-
editBoundingBox,
|
|
95
|
-
onImageLoad,
|
|
96
|
-
rejectCandidate,
|
|
97
|
-
setEditTarget,
|
|
98
|
-
setHoveredAnnotations,
|
|
99
|
-
setZoom,
|
|
100
|
-
}: Props = $props()
|
|
101
|
-
|
|
102
|
-
let dataURI = $state<string>()
|
|
103
|
-
const img = $state(document.createElement('img'))
|
|
104
|
-
let container = $state<HTMLElement>()
|
|
105
|
-
let size = $state<Size>()
|
|
106
|
-
let firstClickedPoint = $state<Coordinates>()
|
|
107
|
-
let normalizedDrawnBox = $state<BoundingBoxData>()
|
|
108
|
-
let drawingContainer = $state<HTMLDivElement>()
|
|
109
|
-
let floatingCoords = $state<HTMLDivElement>()
|
|
110
|
-
let currentMousePosition = $state<Coordinates>({ x: 0, y: 0 })
|
|
111
|
-
let activeResizeHandle = $state<ResizeHandleLocation>()
|
|
112
|
-
let inEditMode = $state(false)
|
|
113
|
-
let showMouseCoords = $state(false)
|
|
114
|
-
let copiedBox = $state<BoundingBoxData>()
|
|
115
|
-
let allowPaste = $state(true)
|
|
116
|
-
|
|
117
|
-
const dragDistance = $derived(
|
|
118
|
-
firstClickedPoint
|
|
119
|
-
? {
|
|
120
|
-
x: currentMousePosition.x - firstClickedPoint.x,
|
|
121
|
-
y: currentMousePosition.y - firstClickedPoint.y,
|
|
122
|
-
}
|
|
123
|
-
: undefined
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
const getOffset = (el: HTMLElement | undefined) => {
|
|
127
|
-
if (el) {
|
|
128
|
-
const { height, width } = el.getBoundingClientRect()
|
|
129
|
-
return { x: width, y: height }
|
|
130
|
-
}
|
|
131
|
-
return undefined
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let zoomOffset = $derived(getOffset(drawingContainer))
|
|
135
|
-
|
|
136
|
-
const setSize = () => {
|
|
137
|
-
if (maxSize) {
|
|
138
|
-
size = getImageSize(img, maxSize)
|
|
139
|
-
} else if (container) {
|
|
140
|
-
size = getImageSizeDynamic(img, container)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (drawingContainer) {
|
|
144
|
-
zoomOffset = getOffset(drawingContainer)
|
|
145
|
-
drawingContainer?.scrollIntoView()
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const backgroundImageLoad = () => {
|
|
150
|
-
setSize()
|
|
151
|
-
|
|
152
|
-
// gets image binary from img element
|
|
153
|
-
const cvs = document.createElement('canvas')
|
|
154
|
-
cvs.width = img.width
|
|
155
|
-
cvs.height = img.height
|
|
156
|
-
cvs.getContext('2d')?.drawImage(img, 0, 0)
|
|
157
|
-
dataURI = cvs.toDataURL()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const resetDrawing = (clearTarget: boolean | undefined = true) => {
|
|
161
|
-
if (clearTarget) {
|
|
162
|
-
setEditTarget?.(undefined)
|
|
163
|
-
}
|
|
164
|
-
firstClickedPoint = undefined
|
|
165
|
-
normalizedDrawnBox = undefined
|
|
166
|
-
activeResizeHandle = undefined
|
|
167
|
-
inEditMode = false
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
onMount(() => {
|
|
171
|
-
img.addEventListener('load', backgroundImageLoad)
|
|
172
|
-
globalThis.addEventListener('mousemove', handleOverlayMove)
|
|
173
|
-
globalThis.addEventListener('mouseup', handleMouseUp)
|
|
174
|
-
globalThis.addEventListener('keydown', handleKeyDown)
|
|
175
|
-
return () => {
|
|
176
|
-
img.removeEventListener('load', backgroundImageLoad)
|
|
177
|
-
globalThis.removeEventListener('mouseup', handleMouseUp)
|
|
178
|
-
globalThis.removeEventListener('keydown', handleKeyDown)
|
|
179
|
-
globalThis.removeEventListener('mousemove', handleOverlayMove)
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
$effect.pre(() => {
|
|
184
|
-
if (imageSrc) {
|
|
185
|
-
img.src = imageSrc
|
|
186
|
-
}
|
|
187
|
-
img.crossOrigin = 'anonymous'
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
$effect.pre(() => {
|
|
191
|
-
void container
|
|
192
|
-
setSize()
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
$effect.pre(() => {
|
|
196
|
-
if (allowDrawing) {
|
|
197
|
-
setHoveredAnnotations?.({})
|
|
198
|
-
} else {
|
|
199
|
-
resetDrawing()
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
const getRelativeMousePosition = (event: MouseEvent): Coordinates => {
|
|
204
|
-
if (drawingContainer) {
|
|
205
|
-
const boundingRect = drawingContainer.getBoundingClientRect()
|
|
206
|
-
const { clientX, clientY } = event
|
|
207
|
-
|
|
208
|
-
const { left, top } = boundingRect
|
|
209
|
-
const x = (clientX - left) / zoom
|
|
210
|
-
const y = (clientY - top) / zoom
|
|
211
|
-
|
|
212
|
-
return { x, y }
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return { x: 0, y: 0 }
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const sendEditAnnotationRequest = (annotation: BoundingBoxData) => {
|
|
219
|
-
editBoundingBox?.(annotation.id, annotation)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const findAndSetTarget = (id: string | undefined) => {
|
|
223
|
-
setEditTarget?.(savedBoundingBoxes.find((ann) => ann.id === id))
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const handleMouseDown = (event: MouseEvent) => {
|
|
227
|
-
if (readonly) {
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (editTarget) {
|
|
232
|
-
setEditTarget?.(undefined)
|
|
233
|
-
} else if (allowDrawing) {
|
|
234
|
-
firstClickedPoint = getRelativeMousePosition(event)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const handleMouseUp = () => {
|
|
239
|
-
if (readonly) {
|
|
240
|
-
return
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (editTarget && inEditMode && size) {
|
|
244
|
-
if (dragDistance) {
|
|
245
|
-
// in edit mode
|
|
246
|
-
const boundingRect = getBoundingRect(
|
|
247
|
-
editTarget,
|
|
248
|
-
dragDistance,
|
|
249
|
-
size.width,
|
|
250
|
-
size.height,
|
|
251
|
-
activeResizeHandle
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
const normalized: BoundingCoords = {
|
|
255
|
-
xMinNormalized: boundingRect.xMinNormalized / size.width,
|
|
256
|
-
xMaxNormalized: boundingRect.xMaxNormalized / size.width,
|
|
257
|
-
yMinNormalized: boundingRect.yMinNormalized / size.height,
|
|
258
|
-
yMaxNormalized: boundingRect.yMaxNormalized / size.height,
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const editedBox = {
|
|
262
|
-
...editTarget,
|
|
263
|
-
...normalized,
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
resetDrawing(false)
|
|
267
|
-
sendEditAnnotationRequest(editedBox)
|
|
268
|
-
} else {
|
|
269
|
-
resetDrawing(false)
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
// in create mode
|
|
273
|
-
if (normalizedDrawnBox) {
|
|
274
|
-
createBoundingBox?.(normalizedDrawnBox)
|
|
275
|
-
}
|
|
276
|
-
resetDrawing(true)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// show coordinates and allow paste only when over image
|
|
281
|
-
const handleMove = () => {
|
|
282
|
-
if (readonly) {
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
showMouseCoords = true
|
|
287
|
-
allowPaste = true
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// track mouse movement over entire screen
|
|
291
|
-
const handleOverlayMove = (event: MouseEvent) => {
|
|
292
|
-
const coords = getRelativeMousePosition(event)
|
|
293
|
-
|
|
294
|
-
currentMousePosition = coords
|
|
295
|
-
|
|
296
|
-
if (firstClickedPoint && size && !editTarget) {
|
|
297
|
-
// prevent mouse from highlighting when dragging over text outside of image, but only if a point on the image has been clicked first
|
|
298
|
-
event.preventDefault()
|
|
299
|
-
normalizedDrawnBox = createNormalizedTempBox(
|
|
300
|
-
selectedLabel ?? '',
|
|
301
|
-
size,
|
|
302
|
-
firstClickedPoint,
|
|
303
|
-
coords
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const onMouseDownAnnotation = (
|
|
309
|
-
event: MouseEvent,
|
|
310
|
-
annotationID: string,
|
|
311
|
-
corner?: ResizeHandleLocation
|
|
312
|
-
) => {
|
|
313
|
-
event.preventDefault()
|
|
314
|
-
if (readonly) {
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (annotationID !== editTarget?.id) {
|
|
319
|
-
findAndSetTarget(annotationID)
|
|
320
|
-
}
|
|
321
|
-
inEditMode = true
|
|
322
|
-
firstClickedPoint = getRelativeMousePosition(event)
|
|
323
|
-
activeResizeHandle = corner
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// sorting by distance from top so that index can represent a top-to-bottom tab order
|
|
327
|
-
const sortedCandidatesByTop = $derived(
|
|
328
|
-
candidateBoundingBoxes
|
|
329
|
-
? candidateBoundingBoxes.toSorted((boxA, boxB) => boxA.yMinNormalized - boxB.yMinNormalized)
|
|
330
|
-
: []
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
const addToHovered = (id: string) => {
|
|
334
|
-
setHoveredAnnotations?.({ ...hoveredAnnotations, [id]: true })
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const removeFromHovered = (id: string) => {
|
|
338
|
-
setHoveredAnnotations?.({ ...hoveredAnnotations, [id]: false })
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const handleWheel = (event: WheelEvent) => {
|
|
342
|
-
if (event.metaKey && zoom <= ZOOM_MAX && zoom >= ZOOM_MIN) {
|
|
343
|
-
showMouseCoords = false
|
|
344
|
-
const { deltaY } = event
|
|
345
|
-
event.preventDefault()
|
|
346
|
-
const newZoom = zoom + deltaY / 100
|
|
347
|
-
|
|
348
|
-
if (newZoom > ZOOM_MAX) {
|
|
349
|
-
setZoom?.(ZOOM_MAX)
|
|
350
|
-
return
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (newZoom < ZOOM_MIN) {
|
|
354
|
-
setZoom?.(ZOOM_MIN)
|
|
355
|
-
return
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
setZoom?.(newZoom)
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
$effect(() => {
|
|
363
|
-
void zoom
|
|
364
|
-
untrack(() => {
|
|
365
|
-
container?.scrollTo({
|
|
366
|
-
left: currentMousePosition.x * (zoom - 1),
|
|
367
|
-
top: currentMousePosition.y * (zoom - 1),
|
|
368
|
-
behavior: 'instant',
|
|
369
|
-
})
|
|
370
|
-
})
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
const handleLeave = () => {
|
|
374
|
-
showMouseCoords = false
|
|
375
|
-
allowPaste = false
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
379
|
-
if (event[MODIFY_KEY] && event.code === 'KeyV' && size && copiedBox && allowPaste) {
|
|
380
|
-
const pastedBox = calculatePastedBox(currentMousePosition, copiedBox, size.width, size.height)
|
|
381
|
-
|
|
382
|
-
createBoundingBox?.(pastedBox)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const copyBoundingBox = () => {
|
|
387
|
-
copiedBox = editTarget
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const floatingHeight = $derived(floatingCoords?.getBoundingClientRect().height ?? 0)
|
|
391
|
-
</script>
|
|
392
|
-
|
|
393
|
-
<div
|
|
394
|
-
class={[
|
|
395
|
-
'flex h-full min-h-0 w-full min-w-0 grow flex-col items-center justify-center',
|
|
396
|
-
{ 'overflow-x-auto overflow-y-auto': !maxSize },
|
|
397
|
-
]}
|
|
398
|
-
bind:this={container}
|
|
399
|
-
use:resize
|
|
400
|
-
onwheel={handleWheel}
|
|
401
|
-
onresized={setSize}
|
|
402
|
-
style:cursor={activeResizeHandle ? ResizeHandleClassMap[activeResizeHandle].cursor : undefined}
|
|
403
|
-
>
|
|
404
|
-
{#if size && dataURI}
|
|
405
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
406
|
-
<div
|
|
407
|
-
class="relative h-fit w-fit"
|
|
408
|
-
style:left={zoom > 1 && zoomOffset ? `${(zoomOffset.x * (zoom - 1)) / 2}px` : ''}
|
|
409
|
-
style:top={zoom > 1 && zoomOffset ? `${(zoomOffset.y * (zoom - 1)) / 2}px` : ''}
|
|
410
|
-
bind:this={drawingContainer}
|
|
411
|
-
onmousedown={handleMouseDown}
|
|
412
|
-
onmousemove={handleMove}
|
|
413
|
-
onmouseleave={handleLeave}
|
|
414
|
-
use:clickOutside={() => resetDrawing()}
|
|
415
|
-
style:scale={zoom}
|
|
416
|
-
>
|
|
417
|
-
<div
|
|
418
|
-
bind:this={floatingCoords}
|
|
419
|
-
class={[
|
|
420
|
-
'z-max absolute origin-bottom-left rounded-md bg-black px-1.5',
|
|
421
|
-
{
|
|
422
|
-
hidden: !showMouseCoords,
|
|
423
|
-
'origin-bottom-left': currentMousePosition.y > INVERT_Y_FLOAT_DISTANCE / zoom,
|
|
424
|
-
'origin-top-left': currentMousePosition.y <= INVERT_Y_FLOAT_DISTANCE / zoom,
|
|
425
|
-
},
|
|
426
|
-
]}
|
|
427
|
-
style:left={`${currentMousePosition.x + (size.width - currentMousePosition.x < INVERT_X_FLOAT_DISTANCE / zoom ? -95 : 4) / zoom}px`}
|
|
428
|
-
style:top={`${currentMousePosition.y + (currentMousePosition.y > INVERT_Y_FLOAT_DISTANCE / zoom ? -1 * (floatingHeight + 5 / zoom) : 5 / zoom)}px`}
|
|
429
|
-
style:scale={1 / zoom}
|
|
430
|
-
>
|
|
431
|
-
<span class="font-roboto-mono text-xs whitespace-nowrap text-white"
|
|
432
|
-
>{`{${Math.round(currentMousePosition.x)},${Math.round(currentMousePosition.y)}}`}</span
|
|
433
|
-
>
|
|
434
|
-
</div>
|
|
435
|
-
<img
|
|
436
|
-
class={['z-0 select-none', { invisible: imageLoading }]}
|
|
437
|
-
height={size.height}
|
|
438
|
-
width={size.width}
|
|
439
|
-
src={dataURI}
|
|
440
|
-
alt={imageSrc}
|
|
441
|
-
crossorigin="anonymous"
|
|
442
|
-
onload={onImageLoad}
|
|
443
|
-
/>
|
|
444
|
-
{#if imageLoading}
|
|
445
|
-
<div class="absolute top-0 flex h-full w-full items-center justify-center">
|
|
446
|
-
<Progress size="large" />
|
|
447
|
-
</div>
|
|
448
|
-
{:else}
|
|
449
|
-
<div
|
|
450
|
-
style={`height: ${size.height}px; width: ${size.width}px;`}
|
|
451
|
-
class="absolute top-0 z-10 overflow-hidden"
|
|
452
|
-
>
|
|
453
|
-
{#if normalizedDrawnBox}
|
|
454
|
-
<BoundingBox
|
|
455
|
-
active={true}
|
|
456
|
-
annotation={normalizedDrawnBox}
|
|
457
|
-
allowInteractivity={false}
|
|
458
|
-
containerHeight={size.height}
|
|
459
|
-
containerWidth={size.width}
|
|
460
|
-
readonly={true}
|
|
461
|
-
selected={false}
|
|
462
|
-
isPending={false}
|
|
463
|
-
{zoom}
|
|
464
|
-
onEscapePress={resetDrawing}
|
|
465
|
-
/>
|
|
466
|
-
{/if}
|
|
467
|
-
{#each sortedCandidatesByTop as candidate, index (candidate.id)}
|
|
468
|
-
<BoundingBox
|
|
469
|
-
active={Boolean(hoveredAnnotations[candidate.id])}
|
|
470
|
-
allowInteractivity={true}
|
|
471
|
-
annotation={candidate}
|
|
472
|
-
containerHeight={size.height}
|
|
473
|
-
containerWidth={size.width}
|
|
474
|
-
{dragDistance}
|
|
475
|
-
isPending={true}
|
|
476
|
-
{readonly}
|
|
477
|
-
tabIndex={index + 1}
|
|
478
|
-
{zoom}
|
|
479
|
-
{addToHovered}
|
|
480
|
-
onEscapePress={resetDrawing}
|
|
481
|
-
onBackspacePress={rejectCandidate}
|
|
482
|
-
onEnterPress={acceptCandidate}
|
|
483
|
-
{removeFromHovered}
|
|
484
|
-
/>
|
|
485
|
-
{/each}
|
|
486
|
-
{#if showSavedBoundingBoxes}
|
|
487
|
-
{#each savedBoundingBoxes as boundingBox, index (boundingBox.id)}
|
|
488
|
-
<BoundingBox
|
|
489
|
-
active={Boolean(hoveredAnnotations[boundingBox.id])}
|
|
490
|
-
{activeResizeHandle}
|
|
491
|
-
allowInteractivity={!allowDrawing}
|
|
492
|
-
annotation={boundingBox}
|
|
493
|
-
containerHeight={size.height}
|
|
494
|
-
containerWidth={size.width}
|
|
495
|
-
{dragDistance}
|
|
496
|
-
isPending={false}
|
|
497
|
-
{readonly}
|
|
498
|
-
selected={editTarget?.id === boundingBox.id}
|
|
499
|
-
tabIndex={index + 1}
|
|
500
|
-
{zoom}
|
|
501
|
-
{addToHovered}
|
|
502
|
-
onBackspacePress={deleteBoundingBox}
|
|
503
|
-
onCopyPress={copyBoundingBox}
|
|
504
|
-
onEscapePress={resetDrawing}
|
|
505
|
-
onMouseDown={onMouseDownAnnotation}
|
|
506
|
-
{removeFromHovered}
|
|
507
|
-
/>
|
|
508
|
-
{/each}
|
|
509
|
-
{/if}
|
|
510
|
-
</div>
|
|
511
|
-
{/if}
|
|
512
|
-
</div>
|
|
513
|
-
{:else}
|
|
514
|
-
<div
|
|
515
|
-
class="flex w-full items-center justify-center"
|
|
516
|
-
style:height={maxSize?.maxHeight ? `${maxSize?.maxHeight}px` : '250px'}
|
|
517
|
-
>
|
|
518
|
-
<Progress size="large" />
|
|
519
|
-
</div>
|
|
520
|
-
{/if}
|
|
521
|
-
</div>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { BoundingBox as BoundingBoxData } from './bounding-box-types';
|
|
2
|
-
interface Props {
|
|
3
|
-
/** whether to allow drawing of new bounding boxes */
|
|
4
|
-
allowDrawing: boolean;
|
|
5
|
-
/** inference candidates displayed using readonly bounding boxes */
|
|
6
|
-
candidateBoundingBoxes?: BoundingBoxData[];
|
|
7
|
-
/** currently selected box */
|
|
8
|
-
editTarget?: BoundingBoxData | undefined;
|
|
9
|
-
/** map of annotations displayed in their hovered states */
|
|
10
|
-
hoveredAnnotations: Record<string, boolean>;
|
|
11
|
-
/** whether image is loading */
|
|
12
|
-
imageLoading: boolean;
|
|
13
|
-
/** URI of image */
|
|
14
|
-
imageSrc: string;
|
|
15
|
-
/** used to set the max size of the display container, used for bounding boxes */
|
|
16
|
-
maxSize?: {
|
|
17
|
-
maxHeight: number;
|
|
18
|
-
maxWidth: number;
|
|
19
|
-
};
|
|
20
|
-
/** whether to allow any interactivity (i.e. drawing, editing, deleting) */
|
|
21
|
-
readonly?: boolean;
|
|
22
|
-
/** bounding boxes saved on the image */
|
|
23
|
-
savedBoundingBoxes: BoundingBoxData[];
|
|
24
|
-
/** user selected label to apply to drawn boxes */
|
|
25
|
-
selectedLabel?: string;
|
|
26
|
-
/** whether to display candidates and saved boxes or just candidates */
|
|
27
|
-
showSavedBoundingBoxes?: boolean;
|
|
28
|
-
/** how much image is zoomed */
|
|
29
|
-
zoom?: number;
|
|
30
|
-
/** accept the selected inference candidate box */
|
|
31
|
-
acceptCandidate?: (id: string) => void;
|
|
32
|
-
/** saves drawn bounding box to image */
|
|
33
|
-
createBoundingBox?: (newBox: BoundingBoxData) => void;
|
|
34
|
-
/** deletes saved bounding box */
|
|
35
|
-
deleteBoundingBox?: (annotationID: string, targetLabel: string) => void;
|
|
36
|
-
/** save changes to existing box */
|
|
37
|
-
editBoundingBox?: (id: string, newBox: BoundingBoxData) => void;
|
|
38
|
-
/** invoked when displayed image loaded */
|
|
39
|
-
onImageLoad?: VoidFunction;
|
|
40
|
-
/** reject the selected inference candidate box */
|
|
41
|
-
rejectCandidate?: (id: string) => void;
|
|
42
|
-
/** set which box is being edited */
|
|
43
|
-
setEditTarget?: (target: BoundingBoxData | undefined) => void;
|
|
44
|
-
/** used to change which boxes are hovered */
|
|
45
|
-
setHoveredAnnotations?: (newHovered: Record<string, boolean>) => void;
|
|
46
|
-
/** sets zoom */
|
|
47
|
-
setZoom?: (value: number) => void;
|
|
48
|
-
}
|
|
49
|
-
declare const BoundingBoxLabeler: import("svelte").Component<Props, {}, "">;
|
|
50
|
-
type BoundingBoxLabeler = ReturnType<typeof BoundingBoxLabeler>;
|
|
51
|
-
export default BoundingBoxLabeler;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export interface BoundingCoords {
|
|
2
|
-
xMinNormalized: number;
|
|
3
|
-
xMaxNormalized: number;
|
|
4
|
-
yMinNormalized: number;
|
|
5
|
-
yMaxNormalized: number;
|
|
6
|
-
}
|
|
7
|
-
export interface BoundingBox extends BoundingCoords {
|
|
8
|
-
id: string;
|
|
9
|
-
label: string;
|
|
10
|
-
}
|
|
11
|
-
export declare enum BoxModifier {
|
|
12
|
-
Scaled = "scaled",
|
|
13
|
-
Translated = "translated",
|
|
14
|
-
Resized = "resized"
|
|
15
|
-
}
|
|
16
|
-
export declare enum ResizeHandleLocation {
|
|
17
|
-
T = "top",
|
|
18
|
-
R = "right",
|
|
19
|
-
B = "bottom",
|
|
20
|
-
L = "left",
|
|
21
|
-
TL = "top-left",
|
|
22
|
-
TR = "top-right",
|
|
23
|
-
BR = "bottom-right",
|
|
24
|
-
BL = "bottom-left"
|
|
25
|
-
}
|
|
26
|
-
export interface Coordinates {
|
|
27
|
-
x: number;
|
|
28
|
-
y: number;
|
|
29
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export var BoxModifier;
|
|
2
|
-
(function (BoxModifier) {
|
|
3
|
-
BoxModifier["Scaled"] = "scaled";
|
|
4
|
-
BoxModifier["Translated"] = "translated";
|
|
5
|
-
BoxModifier["Resized"] = "resized";
|
|
6
|
-
})(BoxModifier || (BoxModifier = {}));
|
|
7
|
-
export var ResizeHandleLocation;
|
|
8
|
-
(function (ResizeHandleLocation) {
|
|
9
|
-
ResizeHandleLocation["T"] = "top";
|
|
10
|
-
ResizeHandleLocation["R"] = "right";
|
|
11
|
-
ResizeHandleLocation["B"] = "bottom";
|
|
12
|
-
ResizeHandleLocation["L"] = "left";
|
|
13
|
-
ResizeHandleLocation["TL"] = "top-left";
|
|
14
|
-
ResizeHandleLocation["TR"] = "top-right";
|
|
15
|
-
ResizeHandleLocation["BR"] = "bottom-right";
|
|
16
|
-
ResizeHandleLocation["BL"] = "bottom-left";
|
|
17
|
-
})(ResizeHandleLocation || (ResizeHandleLocation = {}));
|