Skip to content

Commit e0ccd81

Browse files
committed
Add fingerprints
1 parent 6e92193 commit e0ccd81

File tree

9 files changed

+199
-27
lines changed

9 files changed

+199
-27
lines changed

src/components/features/bottom/BottomPanel.svelte

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import MicrobitLiveGraph from '../graphs/MicrobitLiveGraph.svelte';
1818
import StandardButton from '../../ui/buttons/StandardButton.svelte';
1919
import { stores } from '../../../lib/stores/Stores';
20+
import LiveDataFingerprint from './LiveDataFingerprint.svelte';
2021
2122
const devices = stores.getDevices();
2223
@@ -55,7 +56,7 @@
5556
<!-- Input microbit is assigned -->
5657
<div class="relative w-full h-full">
5758
<div class="absolute w-full h-full">
58-
<MicrobitLiveGraph width={componentWidth - 160} />
59+
<MicrobitLiveGraph width={componentWidth - 180} />
5960
</div>
6061
{#if $devices.isInputInitializing}
6162
<div
@@ -66,22 +67,27 @@
6667
</div>
6768
{/if}
6869
<div
69-
class="h-full p-0 m-0 absolute top-0 left-0 right-40 border-r border-solid border-black border-opacity-60">
70+
class="h-full p-0 m-0 absolute top-0 left-0 right-45 border-r border-solid border-black border-opacity-60">
7071
<!-- The live text and info box -->
7172
<div class="float-left mt-2 ml-2">
7273
<LiveGraphInformationSection />
7374
</div>
74-
<div class="absolute right-2 top-2 m-0 float-right">
75+
<div class="absolute right-4 top-2 m-0 float-right">
7576
<ConnectedLiveGraphButtons
7677
onInputDisconnectButtonClicked={inputDisconnectButtonClicked}
7778
onOutputConnectButtonClicked={connectButtonClicked}
7879
onOutputDisconnectButtonClicked={outputDisconnectButtonClicked} />
7980
</div>
8081
</div>
8182
<div
82-
class="absolute right-0 cursor-pointer hover:bg-secondary hover:bg-opacity-10 transition"
83+
class="absolute right-0 cursor-pointer w-45 hover:bg-secondary hover:bg-opacity-10 transition"
8384
on:click={() => (isLive3DOpen = true)}>
84-
<View3DLive width={160} height={160} freeze={isLive3DOpen} />
85+
<div class="flex flex-row">
86+
<div class="absolute">
87+
<LiveDataFingerprint gestureName="Live" />
88+
</div>
89+
<View3DLive width={160} height={160} freeze={isLive3DOpen} />
90+
</div>
8591
</div>
8692
<BaseDialog isOpen={isLive3DOpen} onClose={() => (isLive3DOpen = false)}>
8793
<!-- hardcoded margin-left matches the size of the sidebar -->

src/components/features/bottom/ConnectedLiveGraphButtons.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,28 @@
2525
<!-- Output is assigned -->
2626
{#if !$devices.isOutputConnected || $devices.isOutputReady}
2727
<!-- Output MB is not in the connection process -->
28-
<StandardButton onClick={onOutputDisconnectButtonClicked} color="warning">
28+
<StandardButton medium onClick={onOutputDisconnectButtonClicked} color="warning">
2929
{$tr('menu.model.disconnect')}
3030
</StandardButton>
3131
{:else}
3232
<!-- svelte-ignore missing-declaration -->
33-
<StandardButton onClick={TypingUtils.emptyFunction} color="disabled">
33+
<StandardButton medium onClick={TypingUtils.emptyFunction} color="disabled">
3434
<img alt="loading" src="imgs/loadingspinner.gif" style="height:24px" />
3535
</StandardButton>
3636
{/if}
3737
{:else}
38-
<StandardButton onClick={onOutputConnectButtonClicked}>
38+
<StandardButton medium onClick={onOutputConnectButtonClicked}>
3939
{$tr('menu.model.connectOutputButton')}
4040
</StandardButton>
4141
{/if}
4242
{/if}
4343
<div class="ml-2">
4444
{#if !$devices.isInputConnected || $devices.isInputReady}
4545
<!-- Input MB is not in the connection process -->
46-
<StandardButton onClick={onInputDisconnectButtonClicked} color="warning"
46+
<StandardButton medium onClick={onInputDisconnectButtonClicked} color="warning"
4747
>{$tr('footer.disconnectButton')}</StandardButton>
4848
{:else}
49-
<StandardButton onClick={TypingUtils.emptyFunction} color="disabled">
49+
<StandardButton medium onClick={TypingUtils.emptyFunction} color="disabled">
5050
<img alt="loading" src="/imgs/loadingspinner.gif" style="height:24px" />
5151
</StandardButton>
5252
{/if}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!--
2+
(c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
<script lang="ts">
7+
import { stores } from '../../../lib/stores/Stores';
8+
import Fingerprint from '../../ui/recording/Fingerprint.svelte';
9+
10+
export let gestureName: string;
11+
const classifier = stores.getClassifier();
12+
const filters = classifier.getFilters();
13+
14+
$: filtersLabels = $filters.flatMap(filter => {
15+
const filterName = filter.getName();
16+
return [`${filterName} - x`, `${filterName} - y`, `${filterName} - z`];
17+
});
18+
$: fingerprint = $classifier.filteredInput.normalized.getValue();
19+
</script>
20+
21+
<div class="w-7 overflow-hidden">
22+
<div style="margin-left: -8px;margin-bottom: -7px">
23+
<Fingerprint
24+
height={166}
25+
width={100}
26+
filterLabels={filtersLabels}
27+
title={gestureName}
28+
{fingerprint} />
29+
</div>
30+
</div>

src/components/features/datacollection/Gesture.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
MicrobitInteractions,
1515
chosenGesture,
1616
} from '../../../lib/stores/uiStore';
17-
import Recording from '../../ui/Recording.svelte';
17+
import Recording from '../../ui/recording/Recording.svelte';
1818
import { t } from '../../../i18n';
1919
import ImageSkeleton from '../../ui/skeletonloading/ImageSkeleton.svelte';
2020
import GestureCard from '../../ui/Card.svelte';
@@ -219,6 +219,7 @@
219219
<div class="flex p-2 h-30">
220220
{#each $gesture.recordings as recording (String($gesture.ID) + String(recording.ID))}
221221
<Recording
222+
enableFingerprint={true}
222223
downloadable
223224
{recording}
224225
gestureId={$gesture.ID}

src/components/ui/buttons/StandardButton.svelte

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
padding: 12px 40px;
1010
font-size: 16px;
1111
}
12+
.medium {
13+
padding: 8px 24px;
14+
font-size: 15px;
15+
}
1216
.small {
1317
padding: 1px 10px;
1418
font-size: 14px;
@@ -59,6 +63,7 @@
5963
export let onClick: (e: Event) => void = TypingUtils.emptyFunction;
6064
export let disabled = false;
6165
export let small = false;
66+
export let medium = false;
6267
export let tiny = false;
6368
export let outlined = false;
6469
export let fillOnHover = false;
@@ -94,7 +99,8 @@
9499
class:font-bold={bold}
95100
class:tiny
96101
class:small
97-
class:normal={!small && !tiny}
102+
class:medium
103+
class:normal={!small && !tiny && !medium}
98104
class:outlined
99105
class:cursor-default={disabled}
100106
on:click={onClick}>
@@ -115,7 +121,8 @@
115121
class:font-bold={bold}
116122
class:small
117123
class:tiny
118-
class:normal={!small && !tiny}
124+
class:medium
125+
class:normal={!small && !tiny && !medium}
119126
class:outlined
120127
class:filled={!outlined}
121128
class:fillOnHover
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!--
2+
(c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
<script lang="ts">
7+
import * as tfvis from '@tensorflow/tfjs-vis';
8+
import { onMount } from 'svelte';
9+
import Logger from '../../../lib/utils/Logger';
10+
11+
export let fingerprint: number[];
12+
export let title: string;
13+
export let filterLabels: string[];
14+
export let height: number = 100;
15+
export let width: number = 80;
16+
17+
let surface: undefined | tfvis.Drawable;
18+
19+
// Reactive chart data that updates when props change
20+
$: chartData = {
21+
values: [fingerprint],
22+
xTickLabels: [title],
23+
yTickLabels: filterLabels,
24+
};
25+
26+
const render = async () => {
27+
if (!surface) {
28+
return;
29+
}
30+
try {
31+
await tfvis.render.heatmap(surface, chartData, {
32+
colorMap: 'viridis',
33+
height: height,
34+
width: width,
35+
domain: [0, 1],
36+
fontSize: 0,
37+
});
38+
} catch (error) {
39+
Logger.log('Fingerprint failed to render', error);
40+
}
41+
};
42+
43+
onMount(() => {
44+
// Initial render
45+
render();
46+
});
47+
48+
// Reactive statement to rerender when data changes
49+
$: if (surface && (fingerprint || filterLabels || title)) {
50+
render();
51+
}
52+
</script>
53+
54+
<div bind:this={surface}></div>

src/components/ui/Recording.svelte renamed to src/components/ui/recording/Recording.svelte

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@
66

77
<script lang="ts">
88
import { fade } from 'svelte/transition';
9-
import type { GestureID } from '../../lib/domain/stores/gesture/Gesture';
10-
import { stores } from '../../lib/stores/Stores';
11-
import GestureDot from './GestureDot.svelte';
12-
import RecordingGraph from '../features/graphs/recording/RecordingGraph.svelte';
13-
import type { RecordingData } from '../../lib/domain/RecordingData';
14-
import Tooltip from './Tooltip.svelte';
15-
import { serializeRecordingToCsvWithoutGestureName } from '../../lib/utils/CSVUtils';
9+
import type { GestureID } from '../../../lib/domain/stores/gesture/Gesture';
10+
import { stores } from '../../../lib/stores/Stores';
11+
import GestureDot from './../GestureDot.svelte';
12+
import RecordingGraph from '../../features/graphs/recording/RecordingGraph.svelte';
13+
import type { RecordingData } from '../../../lib/domain/RecordingData';
14+
import Tooltip from './../Tooltip.svelte';
15+
import { serializeRecordingToCsvWithoutGestureName } from '../../../lib/utils/CSVUtils';
16+
import Fingerprint from './Fingerprint.svelte';
17+
import RecordingFingerprint from './RecordingFingerprint.svelte';
1618
1719
// get recording from mother prop
1820
export let recording: RecordingData;
1921
export let gestureId: GestureID;
2022
export let onDelete: (recording: RecordingData) => void;
2123
export let dot: { gesture: GestureID; color: string } | undefined = undefined;
2224
export let downloadable: boolean = false;
25+
export let enableFingerprint: boolean = false;
2326
2427
$: dotGesture = dot?.gesture
2528
? stores.getGestures().getGesture(dot?.gesture)
@@ -56,17 +59,38 @@
5659
}
5760
</script>
5861

59-
<div class="h-28 w-40 pr-3 pt-1 relative rounded-md">
62+
<div
63+
class="h-28 w-50 pr-3 pt-1 relative rounded-md"
64+
class:w-40={!enableFingerprint}
65+
class:w-50={enableFingerprint}>
6066
{#if dotGesture !== undefined}
61-
<div class="absolute px-1 py-0.5 z-3 right-1 top-2">
67+
<div
68+
class="absolute px-1 py-0.5 z-3 right-1 top-2"
69+
class:right-1={!enableFingerprint}
70+
class:right-10={enableFingerprint}>
6271
<GestureDot gesture={dotGesture} />
6372
</div>
6473
{/if}
6574
{#if hide}
66-
<div transition:fade class="absolute h-26 w-40 bg-white" />
75+
<div
76+
transition:fade
77+
class="absolute h-26 bg-white"
78+
class:w-40={!enableFingerprint}
79+
class:w-50={enableFingerprint} />
6780
{:else}
68-
<div transition:fade class="absolute h-26 w-40 bg-white rounded-md">
69-
<RecordingGraph {recording} />
81+
<div
82+
transition:fade
83+
class="absolute h-26 bg-white rounded-md"
84+
class:w-40={!enableFingerprint}
85+
class:w-50={enableFingerprint}>
86+
<div class="w-40 h-26">
87+
<RecordingGraph {recording} />
88+
</div>
89+
{#if enableFingerprint}
90+
<div class="absolute right-0 top-0.5 left-40 w-10 overflow-hidden">
91+
<RecordingFingerprint {recording} gestureName={$gesture.name} />
92+
</div>
93+
{/if}
7094
</div>
7195
{/if}
7296
<button class="absolute -left-2.8px top-0px outline-none">
@@ -83,7 +107,9 @@
83107
{#if downloadable}
84108
<Tooltip title="CSV" offset={{ x: 125, y: 125 }}>
85109
<button
86-
class="absolute bottom-1 right-0.5 text-primarytext bg-primary bg-opacity-10 px-2 py-1 text-sm rounded-full shadow-md hover:bg-secondary hover:bg-opacity-30 transition z-1"
110+
class="absolute bottom-4 text-primarytext bg-primary bg-opacity-10 px-2 py-1 text-sm rounded-full shadow-md hover:bg-secondary hover:bg-opacity-30 transition z-1"
111+
class:right-10.5={enableFingerprint}
112+
class:right-0.5={!enableFingerprint}
87113
on:click={bottomRightButtonClicked}>
88114
<i class="fas fa-download" />
89115
</button>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
(c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
<script lang="ts">
7+
import type { RecordingData } from '../../../lib/domain/RecordingData';
8+
import { stores } from '../../../lib/stores/Stores';
9+
import type { Vector } from '../../../lib/domain/Vector';
10+
import BaseVector from '../../../lib/domain/BaseVector';
11+
import Fingerprint from './Fingerprint.svelte';
12+
13+
export let recording: RecordingData;
14+
export let gestureName: string;
15+
const classifier = stores.getClassifier();
16+
17+
const filtersLabels: string[] = [];
18+
const filters = classifier.getFilters();
19+
$filters.forEach(filter => {
20+
const filterName = filter.getName();
21+
filtersLabels.push(`${filterName} - x`, `${filterName} - y`, `${filterName} - z`);
22+
});
23+
const getFilteredInput = (): Vector => {
24+
const [xs, ys, zs] = recording.samples.reduce(
25+
(pre, cur) => {
26+
pre[0].push(cur.vector[0]);
27+
pre[1].push(cur.vector[1]);
28+
pre[2].push(cur.vector[2]);
29+
return pre;
30+
},
31+
[[] as number[], [] as number[], [] as number[]],
32+
);
33+
34+
return new BaseVector([
35+
...filters.computeNormalized(xs),
36+
...filters.computeNormalized(ys),
37+
...filters.computeNormalized(zs),
38+
]);
39+
};
40+
const fingerprint: number[] = getFilteredInput().getValue();
41+
</script>
42+
43+
<div class="relative w-full w-11 h-93px overflow-hidden rounded-sm mr-3">
44+
<div class="absolute h-full w-full -left-10px overflow-hidden">
45+
<Fingerprint filterLabels={filtersLabels} title={gestureName} {fingerprint} />
46+
</div>
47+
</div>

src/pages/validation/ValidationGestureRecordingsCard.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
<script lang="ts">
88
import { derived } from 'svelte/store';
99
import Card from '../../components/ui/Card.svelte';
10-
import Recording from '../../components/ui/Recording.svelte';
1110
import Gesture from '../../lib/domain/stores/gesture/Gesture';
1211
import type { GestureID } from '../../lib/domain/stores/gesture/Gesture';
1312
import { stores } from '../../lib/stores/Stores';
13+
import Recording from '../../components/ui/recording/Recording.svelte';
1414
1515
export let gesture: Gesture;
1616
@@ -47,6 +47,7 @@
4747
{#each recordings as recording}
4848
{#key recording.ID}
4949
<Recording
50+
enableFingerprint={true}
5051
dot={$dotGetter(recording.ID)}
5152
gestureId={$gesture.ID}
5253
{recording}

0 commit comments

Comments
 (0)