Skip to content

Commit 4a39a92

Browse files
authored
Merge pull request #636 from microbit-foundation/development
v3.5 - Refine KNN training and validation page mechanisms
2 parents 228e5af + 466346a commit 4a39a92

File tree

97 files changed

+1667
-710
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1667
-710
lines changed

features.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
"makecode": true,
66
"liveGraphInputValues": true,
77
"recordingScrubberValues": true,
8-
"modelValidation": true
8+
"modelValidation": true,
9+
"modelSettings": true,
10+
"fingerprint": true
911
}

src/App.svelte

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import Router from './router/Router.svelte';
3131
import { Feature, getFeature } from './lib/FeatureToggles';
3232
import { welcomeLog } from './lib/utils/Logger';
33-
import { DeviceRequestStates, state } from './lib/stores/Stores';
3433
import MediaQuery from './components/layout/MediaQuery.svelte';
3534
import BottomBarMenuView from './components/layout/BottomBarMenuView.svelte';
3635
import CookieBanner from './components/features/cookie-bannner/CookieBanner.svelte';
@@ -39,11 +38,17 @@
3938
import OverlayView from './components/layout/OverlayView.svelte';
4039
import SideBarMenuView from './components/layout/SideBarMenuView.svelte';
4140
import PageContentView from './components/layout/PageContentView.svelte';
41+
import { stores } from './lib/stores/Stores';
42+
import { DeviceRequestStates } from './lib/domain/Devices';
43+
import { isLoading } from './lib/stores/ApplicationState';
44+
45+
const devices = stores.getDevices();
46+
4247
welcomeLog();
4348
4449
if (CookieManager.isReconnectFlagSet()) {
45-
$state.offerReconnect = true;
46-
$state.reconnectState = DeviceRequestStates.INPUT;
50+
$devices.offerReconnect = true;
51+
$devices.reconnectState = DeviceRequestStates.INPUT;
4752
CookieManager.unsetReconnectFlag();
4853
}
4954
@@ -56,7 +61,7 @@
5661
<!-- Denies mobile users access to the platform -->
5762
<IncompatiblePlatformView />
5863
{:else}
59-
{#if $state.isLoading}
64+
{#if $isLoading}
6065
<main class="h-screen w-screen bg-primary flex absolute z-10" transition:fade>
6166
<LoadingSpinner />
6267
</main>

src/StaticConfiguration.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,10 @@ class StaticConfiguration {
148148
* Whether the knn model should be normalized by default
149149
*/
150150
public static readonly knnNormalizedDefault = false;
151+
152+
/**
153+
* Whether fingerprinting should be enabled by default
154+
*/
155+
public static readonly enableFingerprintByDefault: boolean = false;
151156
}
152157
export default StaticConfiguration;

src/__tests__/csv/csv.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
/**
5+
* (c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors
6+
*
7+
* SPDX-License-Identifier: MIT
8+
*/
9+
10+
import { writable } from 'svelte/store';
11+
import type { RecordingData } from '../../lib/domain/RecordingData';
12+
import Gesture from '../../lib/domain/stores/gesture/Gesture';
13+
import type { PersistedGestureData } from '../../lib/domain/stores/gesture/Gestures';
14+
import type GestureConfidence from '../../lib/domain/stores/gesture/GestureConfidence';
15+
import {
16+
serializeGestureRecordingsToCSV,
17+
serializeRecordingToCsvWithoutGestureName,
18+
} from '../../lib/utils/CSVUtils';
19+
20+
describe('CSV Test', () => {
21+
// A crude way to enforce direction of dependencies, inspired by ArchUnit for java
22+
test('Convert recording', () => {
23+
const input: RecordingData = {
24+
ID: 123,
25+
labels: ['x', 'y', 'z'],
26+
samples: [
27+
{
28+
vector: [1, 2, 3],
29+
},
30+
{
31+
vector: [4, 5, 6],
32+
},
33+
{
34+
vector: [7, 8, 9],
35+
},
36+
],
37+
};
38+
const data = writable({
39+
recordings: [input],
40+
name: 'Test;Gesture',
41+
} as PersistedGestureData);
42+
const confidence = writable({}) as unknown as GestureConfidence;
43+
const gesture: Gesture = new Gesture(data, confidence, () => void 0);
44+
const result = serializeGestureRecordingsToCSV([gesture]);
45+
expect(result).toBe(
46+
'gesture;sample;x;y;z\nTest\\;Gesture;0;1;2;3\nTest\\;Gesture;1;4;5;6\nTest\\;Gesture;2;7;8;9',
47+
);
48+
});
49+
50+
test('Convert multiple gestures', () => {
51+
const input1: RecordingData = {
52+
ID: 123,
53+
labels: ['x', 'y', 'z'],
54+
samples: [
55+
{
56+
vector: [1, 2, 3],
57+
},
58+
{
59+
vector: [4, 5, 6],
60+
},
61+
],
62+
};
63+
const input2: RecordingData = {
64+
ID: 456,
65+
labels: ['x', 'y', 'z'],
66+
samples: [
67+
{
68+
vector: [7, 8, 9],
69+
},
70+
{
71+
vector: [10, 11, 12],
72+
},
73+
],
74+
};
75+
const data1 = writable({
76+
recordings: [input1],
77+
name: 'Gesture1',
78+
} as PersistedGestureData);
79+
const data2 = writable({
80+
recordings: [input2],
81+
name: 'Gesture2',
82+
} as PersistedGestureData);
83+
const confidence = writable({}) as unknown as GestureConfidence;
84+
const gesture1: Gesture = new Gesture(data1, confidence, () => void 0);
85+
const gesture2: Gesture = new Gesture(data2, confidence, () => void 0);
86+
const result = serializeGestureRecordingsToCSV([gesture1, gesture2]);
87+
expect(result).toBe(
88+
'gesture;sample;x;y;z\n' +
89+
'Gesture1;0;1;2;3\n' +
90+
'Gesture1;1;4;5;6\n' +
91+
'Gesture2;0;7;8;9\n' +
92+
'Gesture2;1;10;11;12',
93+
);
94+
});
95+
96+
test('Serialize recording without gesture name (with headers)', () => {
97+
const input: RecordingData = {
98+
ID: 123,
99+
labels: ['x', 'y', 'z'],
100+
samples: [
101+
{
102+
vector: [1, 2, 3],
103+
},
104+
{
105+
vector: [4, 5, 6],
106+
},
107+
{
108+
vector: [7, 8, 9],
109+
},
110+
],
111+
};
112+
113+
const result = serializeRecordingToCsvWithoutGestureName(input);
114+
expect(result).toBe('sample;x;y;z\n' + '0;1;2;3\n' + '1;4;5;6\n' + '2;7;8;9');
115+
});
116+
});

src/__tests__/mocks/mlmodel/TestMLModelTrainer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
*
44
* SPDX-License-Identifier: MIT
55
*/
6+
import type { ModelInfo } from '../../../lib/domain/ModelRegistry';
7+
import ModelRegistry from '../../../lib/domain/ModelRegistry';
68
import type { ModelTrainer } from '../../../lib/domain/ModelTrainer';
79
import type { TrainingDataRepository } from '../../../lib/domain/TrainingDataRepository';
810
import TestMLModel from './TestMLModel';
911

1012
class TestMLModelTrainer implements ModelTrainer<TestMLModel> {
1113
constructor(private numberOfGestures: number) {}
14+
getModelInfo(): ModelInfo {
15+
return ModelRegistry.NeuralNetwork;
16+
}
1217
public trainModel(trainingData: TrainingDataRepository): Promise<TestMLModel> {
1318
return Promise.resolve(new TestMLModel(this.numberOfGestures));
1419
}

src/__viteBuildVariants__/ml-machine-simple/features.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
"makecode": false,
66
"liveGraphInputValues": false,
77
"recordingScrubberValues": false,
8-
"modelValidation": false
8+
"modelValidation": false,
9+
"modelSettings": false,
10+
"fingerprint": false
911
}

src/__viteBuildVariants__/ml-machine/features.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
"makecode": true,
66
"liveGraphInputValues": true,
77
"recordingScrubberValues": true,
8-
"modelValidation": true
8+
"modelValidation": true,
9+
"modelSettings": true,
10+
"fingerprint": true
911
}

src/__viteBuildVariants__/unbranded/features.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
"makecode": true,
66
"liveGraphInputValues": true,
77
"recordingScrubberValues": true,
8-
"modelValidation": true
8+
"modelValidation": true,
9+
"modelSettings": true,
10+
"fingerprint": true
911
}

src/assets/messages/ui.da.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"content.data.noData.exampleName.shake": "Ryste",
5151
"content.data.noData.exampleName.still": "Stille",
5252
"content.data.noData.exampleName.circle": "Cirkel",
53+
"content.data.tooltip.remove": "Fjern",
5354
"content.trainer.failure.header": "Træning mislykkedes",
5455
"content.trainer.failure.body": "Træningen resulterede ikke i en brugbar model. Grunden til dette ligger sandsynligvis i dataet. Hvis dataet i forskellige klasser minder for meget om hinanden, kan dette resultere i nogle forskellige problemer i træningsprocessen, der ikke gør det muligt at træne modellen ordentligt.",
5556
"content.trainer.failure.todo": "Gå tilbage til datasiden og ændr i din data.",
@@ -111,8 +112,9 @@
111112
"content.validation.infobox.classesContent": "Her kan du se de klasser, du har defineret på datasiden.",
112113
"content.validation.noGestures.title": "Ingen klasser...",
113114
"content.validation.noGestures.description": "Du har endnu ikke tilføjet nogen klasser. Gå til datatrinnet i menuen til venstre og tilføj nogle.",
114-
"content.validation.tutorial.title": "Validierungssätze",
115-
"content.validation.tutorial.description": "Erstelle einen separaten Datensatz, um die Leistung deines Modells zu bewerten. Das Modell wird nicht mit diesen Daten trainiert.",
115+
"content.validation.tutorial.title": "Valideringssæt",
116+
"content.validation.tutorial.description": "Opret et separat datasæt til at evaluere dit models ydeevne. Modellen vil ikke blive trænet på disse data.",
117+
"content.validation.tutorial.trainmodelfirst": "Zuerst ein Modell trainieren",
116118
"footer.connectButtonNotConnected": "Tilslut din BBC micro:bit",
117119
"footer.disconnectButton": "Frakobl",
118120
"footer.helpHeader": "Live graf",

src/assets/messages/ui.de.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"content.data.noData.exampleName.shake": "Schütteln",
5151
"content.data.noData.exampleName.still": "Still",
5252
"content.data.noData.exampleName.circle": "Kreis",
53+
"content.data.tooltip.remove": "Entfernen",
5354
"content.trainer.failure.header": "Training fehlgeschlagen",
5455
"content.trainer.failure.body": "Das Training führte nicht zu einem brauchbaren Modell. Der Grund dafür sind höchstwahrscheinlich die für das Training verwendeten Daten. Wenn die Daten für verschiedene Klassen zu ähnlich sind, kann dies zu Problemen im Trainingsprozess führen.",
5556
"content.trainer.failure.todo": "Gehe zur Datenseite zurück und ändere die Daten.",
@@ -111,8 +112,9 @@
111112
"content.validation.infobox.classesContent": "Hier siehst du die Klassen, die du auf der Datenseite definiert hast.",
112113
"content.validation.noGestures.title": "Keine Gesten...",
113114
"content.validation.noGestures.description": "Du hast noch keine Gesten hinzugefügt. Gehe zum Schritt „Daten“ im Menü auf der linken Seite und füge welche hinzu.",
114-
"content.validation.tutorial.title": "Valideringssæt",
115-
"content.validation.tutorial.description": "Opret et separat datasæt til at evaluere dit models ydeevne. Modellen vil ikke blive trænet på disse data.",
115+
"content.validation.tutorial.title": "Validierungssätze",
116+
"content.validation.tutorial.description": "Erstelle einen separaten Datensatz, um die Leistung deines Modells zu bewerten. Das Modell wird nicht mit diesen Daten trainiert.",
117+
"content.validation.tutorial.trainmodelfirst": "Zuerst ein Modell trainieren",
116118
"footer.connectButtonNotConnected": "Verbinde deinen micro:bit",
117119
"footer.disconnectButton": "Trennen",
118120
"footer.helpHeader": "Live-Diagramm",

0 commit comments

Comments
 (0)