@viamrobotics/test-widgets 0.1.8 → 0.1.10
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/widgets/camera/camera.svelte +52 -10
- package/dist/components/widgets/camera/get-source-names.d.ts +4 -0
- package/dist/components/widgets/camera/get-source-names.js +4 -0
- package/dist/components/widgets/vision-service/detection.svelte +13 -43
- package/dist/components/widgets/vision-service/image.svelte +10 -6
- package/dist/components/widgets/vision-service/image.svelte.d.ts +1 -1
- package/dist/components/widgets/vision-service/scene.svelte +3 -21
- package/dist/components/widgets/vision-service/scene.svelte.d.ts +0 -1
- package/dist/components/widgets/vision-service/use-slow-request.svelte.d.ts +3 -0
- package/dist/components/widgets/vision-service/use-slow-request.svelte.js +22 -0
- package/dist/components/widgets/vision-service/vision-service.svelte +25 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Button, Label, Switch, ToggleButtons } from '@viamrobotics/prime-core'
|
|
2
|
+
import { Button, Label, Select, Switch, ToggleButtons } from '@viamrobotics/prime-core'
|
|
3
3
|
import { CameraClient } from '@viamrobotics/sdk'
|
|
4
4
|
import { createResourceClient, createResourceQuery } from '@viamrobotics/svelte-sdk'
|
|
5
5
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import PCDWidget from '../pcd/pcd-widget.svelte'
|
|
16
16
|
import ExportScreenshot from './export-screenshot.svelte'
|
|
17
|
+
import { getSourceNames } from './get-source-names'
|
|
17
18
|
import LiveOrPollingVideo from './live-or-polling-video.svelte'
|
|
18
19
|
import PictureInPictureButton from './picture-in-picture-button.svelte'
|
|
19
20
|
|
|
@@ -30,6 +31,8 @@
|
|
|
30
31
|
'camera'
|
|
31
32
|
)
|
|
32
33
|
let isShowingPointcloud = $state(false)
|
|
34
|
+
let selectedSource = $state('')
|
|
35
|
+
let sourceNames = $state<string[]>([])
|
|
33
36
|
|
|
34
37
|
const { addImageToDataset } = useAddImageToDataset()
|
|
35
38
|
const setIsShowingPointcloud = (event: CustomEvent<boolean>) => {
|
|
@@ -42,10 +45,25 @@
|
|
|
42
45
|
() => resourceName
|
|
43
46
|
)
|
|
44
47
|
|
|
45
|
-
const imageQuery = createResourceQuery(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const imageQuery = createResourceQuery(
|
|
49
|
+
client,
|
|
50
|
+
'getImages',
|
|
51
|
+
() => (selectedSource ? ([[selectedSource]] as [string[]]) : ([] as [])),
|
|
52
|
+
() => ({
|
|
53
|
+
enabled: refetchInterval.current !== RefetchIntervals.LIVE,
|
|
54
|
+
refetchInterval: refetchInterval.current,
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
$effect(() => {
|
|
59
|
+
if (sourceNames.length === 0 && imageQuery.data?.images) {
|
|
60
|
+
const names = getSourceNames(imageQuery.data.images)
|
|
61
|
+
if (names.length > 0) {
|
|
62
|
+
sourceNames = names
|
|
63
|
+
selectedSource = names[0]!
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
49
67
|
|
|
50
68
|
const pointcloudQuery = createResourceQuery(client, 'getPointCloud', () => ({
|
|
51
69
|
enabled: isShowingPointcloud,
|
|
@@ -59,15 +77,25 @@
|
|
|
59
77
|
// A separate, disabled query for exporting a screenshot
|
|
60
78
|
// since this button should work regardless of refetch
|
|
61
79
|
// interval and not affect the image feed.
|
|
62
|
-
const exportScreenshotQuery = createResourceQuery(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
const exportScreenshotQuery = createResourceQuery(
|
|
81
|
+
client,
|
|
82
|
+
'getImages',
|
|
83
|
+
() => (selectedSource ? ([[selectedSource]] as [string[]]) : ([] as [])),
|
|
84
|
+
{
|
|
85
|
+
enabled: false,
|
|
86
|
+
refetchInterval: false,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
66
89
|
|
|
67
90
|
// Firefox has native support for picture-in-picture and does not need
|
|
68
91
|
// to be requested separately. Don't show a "toggle pip" button in Firefox.
|
|
69
92
|
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
|
|
70
93
|
|
|
94
|
+
const onSourceSelect = (event: Event) => {
|
|
95
|
+
const { value } = event.target as HTMLSelectElement
|
|
96
|
+
selectedSource = value
|
|
97
|
+
}
|
|
98
|
+
|
|
71
99
|
let mousePostionTooltip = $state<'On' | 'Off'>('Off')
|
|
72
100
|
const setMousePostionTooltip = (event: CustomEvent<string>) => {
|
|
73
101
|
mousePostionTooltip = event.detail as 'On' | 'Off'
|
|
@@ -78,12 +106,26 @@
|
|
|
78
106
|
|
|
79
107
|
<ConnectionStatus {partID}>
|
|
80
108
|
{#snippet connected()}
|
|
81
|
-
<div class="flex gap-
|
|
109
|
+
<div class="flex gap-4 p-4 pb-3">
|
|
82
110
|
<RefetchController
|
|
83
111
|
{refetchInterval}
|
|
84
112
|
allowLive
|
|
85
113
|
queries={[imageQuery, pointcloudQuery]}
|
|
86
114
|
/>
|
|
115
|
+
{#if sourceNames.length > 0 && refetchInterval.current !== RefetchIntervals.LIVE}
|
|
116
|
+
<Label position="left">
|
|
117
|
+
Source
|
|
118
|
+
<Select
|
|
119
|
+
value={selectedSource}
|
|
120
|
+
on:change={onSourceSelect}
|
|
121
|
+
slot="input"
|
|
122
|
+
>
|
|
123
|
+
{#each sourceNames as name (name)}
|
|
124
|
+
<option value={name}>{name}</option>
|
|
125
|
+
{/each}
|
|
126
|
+
</Select>
|
|
127
|
+
</Label>
|
|
128
|
+
{/if}
|
|
87
129
|
</div>
|
|
88
130
|
<div class="flex h-full w-full gap-4 p-4">
|
|
89
131
|
<div class="grow">
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useThrelte } from '@threlte/core'
|
|
3
|
-
import { useInteractivity } from '@threlte/extras'
|
|
4
|
-
import { onMount } from 'svelte'
|
|
5
|
-
import { Vector2 } from 'three'
|
|
6
3
|
import { Container, Text } from 'threlte-uikit'
|
|
7
4
|
|
|
8
5
|
import { lightTextColors } from './color'
|
|
@@ -17,19 +14,6 @@
|
|
|
17
14
|
|
|
18
15
|
const { detection, factor = 0 }: Props = $props()
|
|
19
16
|
|
|
20
|
-
const normalizedDeviceCoordsToPixel = (
|
|
21
|
-
ndcCoords: Vector2,
|
|
22
|
-
canvasWidth: number,
|
|
23
|
-
canvasHeight: number
|
|
24
|
-
) => {
|
|
25
|
-
const screenX = (ndcCoords.x + 1) / 2
|
|
26
|
-
const screenY = (ndcCoords.y + 1) / 2
|
|
27
|
-
const pixelX = Math.round(screenX * canvasWidth)
|
|
28
|
-
const pixelY = Math.round((1 - screenY) * canvasHeight)
|
|
29
|
-
return new Vector2(pixelX, pixelY)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const { pointer } = useInteractivity()
|
|
33
17
|
const context = useDetections()
|
|
34
18
|
|
|
35
19
|
const factoredxMin = $derived(Number(detection.xMin) * factor)
|
|
@@ -37,46 +21,32 @@
|
|
|
37
21
|
const factoredyMin = $derived(Number(detection.yMin) * factor)
|
|
38
22
|
const factoredyMax = $derived(Number(detection.yMax) * factor)
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
* Check preexisting mouse coords for hover state
|
|
42
|
-
*/
|
|
43
|
-
$effect.pre(() => {
|
|
44
|
-
const pixelCoords = normalizedDeviceCoordsToPixel($pointer, $size.width, $size.height)
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
factoredxMin < pixelCoords.x &&
|
|
48
|
-
factoredxMax > pixelCoords.x &&
|
|
49
|
-
factoredyMin < pixelCoords.y &&
|
|
50
|
-
factoredyMax > pixelCoords.y
|
|
51
|
-
) {
|
|
52
|
-
context.hovered.add(detection.id)
|
|
53
|
-
} else {
|
|
54
|
-
context.hovered.delete(detection.id)
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
const hovering = $derived(context.hovered.has(detection.id))
|
|
24
|
+
let hovering = $state(false)
|
|
59
25
|
|
|
60
26
|
// These are ballparking, but seem good enough for nearly all cases
|
|
61
27
|
const isDetectionNearTop = $derived(factoredyMin < 30)
|
|
62
28
|
const isDetectionNearRight = $derived(factoredxMin > $size.width * 0.66)
|
|
63
|
-
|
|
64
|
-
onMount(() => {
|
|
65
|
-
return () => {
|
|
66
|
-
context.hovered.delete(detection.id)
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
29
|
</script>
|
|
70
30
|
|
|
71
31
|
<Container
|
|
72
32
|
positionType="absolute"
|
|
73
33
|
positionLeft={factoredxMin}
|
|
74
34
|
positionTop={factoredyMin}
|
|
75
|
-
borderColor=
|
|
35
|
+
borderColor="#aaa"
|
|
36
|
+
hover={{
|
|
37
|
+
borderColor: detection.color,
|
|
38
|
+
}}
|
|
76
39
|
borderWidth={2}
|
|
77
40
|
width={factoredxMax - factoredxMin}
|
|
78
41
|
height={factoredyMax - factoredyMin}
|
|
79
|
-
|
|
42
|
+
onpointerenter={() => {
|
|
43
|
+
context.hovered.add(detection.id)
|
|
44
|
+
hovering = true
|
|
45
|
+
}}
|
|
46
|
+
onpointerleave={() => {
|
|
47
|
+
context.hovered.delete(detection.id)
|
|
48
|
+
hovering = false
|
|
49
|
+
}}
|
|
80
50
|
>
|
|
81
51
|
{#if hovering}
|
|
82
52
|
<Text
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
interface Props {
|
|
17
17
|
data?: {
|
|
18
18
|
detections: Detection[]
|
|
19
|
-
image: ({ image: Uint8Array } &
|
|
19
|
+
image: ({ image: Uint8Array } & Omit<cameraApi.Image, 'image'>) | undefined
|
|
20
20
|
classifications: Classification[]
|
|
21
21
|
}
|
|
22
22
|
detectionsSupported: boolean
|
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
provideFontFamilies({
|
|
63
63
|
publicSans: {
|
|
64
64
|
// This must remain `/static/*` while the frontend directory still exist
|
|
65
|
+
light: '/static/fonts/public-sans.json',
|
|
65
66
|
medium: '/static/fonts/public-sans.json',
|
|
67
|
+
'extra-light': '/static/fonts/public-sans.json',
|
|
66
68
|
},
|
|
67
69
|
})
|
|
68
70
|
|
|
@@ -108,7 +110,7 @@
|
|
|
108
110
|
</div>
|
|
109
111
|
{:else}
|
|
110
112
|
<div
|
|
111
|
-
class="relative max-h-full min-h-0 max-w-full"
|
|
113
|
+
class="border-light relative max-h-full min-h-0 max-w-full border"
|
|
112
114
|
style:width={`${size.width.toString()}px`}
|
|
113
115
|
style:height={`${size.height.toString()}px`}
|
|
114
116
|
>
|
|
@@ -117,11 +119,13 @@
|
|
|
117
119
|
>
|
|
118
120
|
{fps.current.toFixed(1)}fps
|
|
119
121
|
</div>
|
|
122
|
+
<img
|
|
123
|
+
class="absolute top-0 left-0"
|
|
124
|
+
alt=""
|
|
125
|
+
{src}
|
|
126
|
+
/>
|
|
120
127
|
<Canvas>
|
|
121
|
-
<Scene
|
|
122
|
-
{src}
|
|
123
|
-
factor={size.factor}
|
|
124
|
-
/>
|
|
128
|
+
<Scene factor={size.factor} />
|
|
125
129
|
</Canvas>
|
|
126
130
|
</div>
|
|
127
131
|
<div
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { T } from '@threlte/core'
|
|
3
3
|
import { interactivity } from '@threlte/extras'
|
|
4
|
-
import { Fullscreen
|
|
4
|
+
import { Fullscreen } from 'threlte-uikit'
|
|
5
5
|
|
|
6
6
|
import { useDetections } from './context.svelte'
|
|
7
7
|
import Detection from './detection.svelte'
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
|
-
src: string
|
|
11
10
|
factor?: number
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const {
|
|
13
|
+
const { factor = 0 }: Props = $props()
|
|
15
14
|
|
|
16
15
|
const context = useDetections()
|
|
17
16
|
|
|
@@ -22,24 +21,7 @@
|
|
|
22
21
|
makeDefault
|
|
23
22
|
position={[0, 0, 1]}
|
|
24
23
|
>
|
|
25
|
-
<Fullscreen
|
|
26
|
-
positionType="relative"
|
|
27
|
-
width="100%"
|
|
28
|
-
height="100%"
|
|
29
|
-
>
|
|
30
|
-
<Image
|
|
31
|
-
positionType="absolute"
|
|
32
|
-
width="100%"
|
|
33
|
-
height="100%"
|
|
34
|
-
{src}
|
|
35
|
-
/>
|
|
36
|
-
</Fullscreen>
|
|
37
|
-
|
|
38
|
-
<Fullscreen
|
|
39
|
-
positionType="relative"
|
|
40
|
-
width="100%"
|
|
41
|
-
height="100%"
|
|
42
|
-
>
|
|
24
|
+
<Fullscreen>
|
|
43
25
|
{#each context.current as detection (detection.id)}
|
|
44
26
|
<Detection
|
|
45
27
|
{detection}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const SLOW_REQUEST_THRESHOLD_MS = 5000;
|
|
2
|
+
export const useSlowRequest = (isFetching) => {
|
|
3
|
+
let isSlow = $state(false);
|
|
4
|
+
$effect(() => {
|
|
5
|
+
if (!isFetching()) {
|
|
6
|
+
isSlow = false;
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
isSlow = false;
|
|
10
|
+
const timeout = setTimeout(() => {
|
|
11
|
+
isSlow = true;
|
|
12
|
+
}, SLOW_REQUEST_THRESHOLD_MS);
|
|
13
|
+
return () => {
|
|
14
|
+
clearTimeout(timeout);
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
get isSlow() {
|
|
19
|
+
return isSlow;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import Image from './image.svelte'
|
|
17
17
|
import ObjectPointClouds from './object-point-clouds.svelte'
|
|
18
|
+
import { useSlowRequest } from './use-slow-request.svelte.js'
|
|
18
19
|
|
|
19
20
|
const { addImageToDataset } = useAddImageToDataset()
|
|
20
21
|
|
|
@@ -106,10 +107,16 @@
|
|
|
106
107
|
: (Math.max(getObjectPointCloudsRefetchInterval.current, 1000 / 20) as number | false),
|
|
107
108
|
}))
|
|
108
109
|
|
|
110
|
+
const captureAllSlow = useSlowRequest(() => captureAllQuery.isFetching)
|
|
111
|
+
const propertiesSlow = useSlowRequest(() => propertiesQuery.isFetching)
|
|
112
|
+
const objectPointCloudsSlow = useSlowRequest(() => getObjectPointCloudsQuery.isFetching)
|
|
113
|
+
|
|
109
114
|
const onCameraSelect = (event: Event) => {
|
|
110
115
|
const { value } = event.target as HTMLSelectElement
|
|
111
116
|
cameraName = value
|
|
112
117
|
}
|
|
118
|
+
|
|
119
|
+
const detectionsSlow = $derived(captureAllSlow.isSlow || propertiesSlow.isSlow)
|
|
113
120
|
</script>
|
|
114
121
|
|
|
115
122
|
<ConnectionStatus {partID}>
|
|
@@ -183,10 +190,19 @@
|
|
|
183
190
|
{/if}
|
|
184
191
|
</div>
|
|
185
192
|
|
|
193
|
+
{#if detectionsSlow}
|
|
194
|
+
<p class="text-subtle-2 text-xs italic">This request is taking a long time to complete.</p>
|
|
195
|
+
{/if}
|
|
196
|
+
|
|
186
197
|
<Queries
|
|
187
198
|
queries={[propertiesQuery, captureAllQuery]}
|
|
188
199
|
contentCx="p-4 h-14"
|
|
189
200
|
>
|
|
201
|
+
{#if detectionsSlow}
|
|
202
|
+
<p class="text-subtle-2 text-xs italic">
|
|
203
|
+
This request is taking a long time to complete.
|
|
204
|
+
</p>
|
|
205
|
+
{/if}
|
|
190
206
|
{#if captureAllQuery.data}
|
|
191
207
|
<Image
|
|
192
208
|
data={captureAllQuery.data}
|
|
@@ -213,11 +229,20 @@
|
|
|
213
229
|
/>
|
|
214
230
|
</div>
|
|
215
231
|
|
|
232
|
+
{#if showObjectPointClouds && objectPointCloudsSlow}
|
|
233
|
+
<p class="text-subtle-2 text-xs italic">This request is taking a long time to complete.</p>
|
|
234
|
+
{/if}
|
|
235
|
+
|
|
216
236
|
{#if showObjectPointClouds}
|
|
217
237
|
<Queries
|
|
218
238
|
queries={[getObjectPointCloudsQuery]}
|
|
219
239
|
contentCx="p-4 h-14"
|
|
220
240
|
>
|
|
241
|
+
{#if objectPointCloudsSlow.isSlow}
|
|
242
|
+
<p class="text-subtle-2 text-xs italic">
|
|
243
|
+
This request is taking a long time to complete.
|
|
244
|
+
</p>
|
|
245
|
+
{/if}
|
|
221
246
|
{#if getObjectPointCloudsQuery.data !== undefined}
|
|
222
247
|
<ObjectPointClouds objects={getObjectPointCloudsQuery.data} />
|
|
223
248
|
{/if}
|