Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"recordingScrubberValues": true,
"modelValidation": true,
"modelSettings": true,
"fingerprint": true
"fingerprint": true,
"recordingDuration": 1800,
"printableRecordings": true,
"dialogRecordings": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"license": "MIT",
"scripts": {
"build": "node prepEnv.js branded && vite build",
"build": "node prepEnv.js simple && vite build",
"dev": "node prepEnv.js unbranded && vite",
"devML": "node prepEnv.js branded && vite",
"devSimple": "node prepEnv.js simple && vite",
Expand Down
4 changes: 4 additions & 0 deletions prepEnv.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const fileMoveTargets = {
"simple": [
['./src/__viteBuildVariants__/ml-machine-simple/windi.config.js', './windi.config.js'],
['./src/__viteBuildVariants__/ml-machine-simple/features.json', './features.json']
],
"experimental": [
['./src/__viteBuildVariants__/experimental/windi.config.js', './windi.config.js'],
['./src/__viteBuildVariants__/experimental/features.json', './features.json']
]
}

Expand Down
8 changes: 0 additions & 8 deletions src/StaticConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ class StaticConfiguration {
// After how long should we consider the connection lost if ping was not able to conclude?
public static readonly connectionLostTimeoutDuration: number = 3000;

// In milliseconds, how long should each recording be?
public static readonly recordingDuration = 1800;

// Which pins are supported?
public static supportedPins: MBSpecs.UsableIOPin[] = [0, 1, 2];
public static readonly defaultOutputPin: MBSpecs.UsableIOPin = 0; // Which pin should be selected by default?
Expand Down Expand Up @@ -101,11 +98,6 @@ class StaticConfiguration {
*/
public static readonly pollingPredictionSampleSize = 35;

/**
* How far back in time should the engine look for sample data for it's current prediction? (in milliseconds).
*/
public static readonly pollingPredictionSampleDuration = 1800;

/**
* The size od the accelerometer livedata buffer. Larger means more memory is consumed.
* Insertions are O(1) and fetching is O(n) where n is the number of items fetched.
Expand Down
191 changes: 191 additions & 0 deletions src/__tests__/csv/csv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { writable } from 'svelte/store';
import { locale } from 'svelte-i18n';
import type { RecordingData } from '../../lib/domain/RecordingData';
import Gesture from '../../lib/domain/stores/gesture/Gesture';
import type { PersistedGestureData } from '../../lib/domain/stores/gesture/Gestures';
Expand Down Expand Up @@ -113,4 +114,194 @@ describe('CSV Test', () => {
const result = serializeRecordingToCsvWithoutGestureName(input);
expect(result).toBe('sample;x;y;z\n' + '0;1;2;3\n' + '1;4;5;6\n' + '2;7;8;9');
});

describe('Locale-specific decimal formatting', () => {
// Helper function to create test data with decimal numbers
const createTestRecordingWithDecimals = (): RecordingData => ({
ID: 123,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [1.23, 4.56, 7.89],
},
{
vector: [10.1, 20.2, 30.3],
},
{
vector: [0.001, 999.999, -5.5],
},
],
});

const createTestGestureWithDecimals = (name: string): Gesture => {
const data = writable({
recordings: [createTestRecordingWithDecimals()],
name,
} as PersistedGestureData);
const confidence = writable({}) as unknown as GestureConfidence;
return new Gesture(data, confidence, () => void 0);
};

test('English locale uses period as decimal separator', () => {
// Set English locale
locale.set('en');

const gesture = createTestGestureWithDecimals('TestGesture');
const result = serializeGestureRecordingsToCSV([gesture]);

expect(result).toContain('1.23;4.56;7.89');
expect(result).toContain('10.1;20.2;30.3');
expect(result).toContain('0.001;999.999;-5.5');
expect(result).not.toContain(','); // Should not contain commas
});

test('German locale uses comma as decimal separator', () => {
// Set German locale
locale.set('de');

const gesture = createTestGestureWithDecimals('TestGesture');
const result = serializeGestureRecordingsToCSV([gesture]);

expect(result).toContain('1,23;4,56;7,89');
expect(result).toContain('10,1;20,2;30,3');
expect(result).toContain('0,001;999,999;-5,5');
expect(result).not.toContain('1.23'); // Should not contain periods in numbers
});

test('Danish locale uses comma as decimal separator', () => {
// Set Danish locale
locale.set('da');

const gesture = createTestGestureWithDecimals('TestGesture');
const result = serializeGestureRecordingsToCSV([gesture]);

expect(result).toContain('1,23;4,56;7,89');
expect(result).toContain('10,1;20,2;30,3');
expect(result).toContain('0,001;999,999;-5,5');
expect(result).not.toContain('1.23'); // Should not contain periods in numbers
});

test('Unknown locale defaults to English format', () => {
// Set unknown locale
locale.set('fr'); // French is not supported, should default to English

const gesture = createTestGestureWithDecimals('TestGesture');
const result = serializeGestureRecordingsToCSV([gesture]);

expect(result).toContain('1.23;4.56;7.89');
expect(result).toContain('10.1;20.2;30.3');
expect(result).toContain('0.001;999.999;-5.5');
expect(result).not.toContain(','); // Should not contain commas
});

test('serializeRecordingToCsvWithoutGestureName uses locale-specific formatting', () => {
const input = createTestRecordingWithDecimals();

// Test English
locale.set('en');
const englishResult = serializeRecordingToCsvWithoutGestureName(input);
expect(englishResult).toContain('0;1.23;4.56;7.89');
expect(englishResult).toContain('1;10.1;20.2;30.3');

// Test German
locale.set('de');
const germanResult = serializeRecordingToCsvWithoutGestureName(input);
expect(germanResult).toContain('0;1,23;4,56;7,89');
expect(germanResult).toContain('1;10,1;20,2;30,3');

// Test Danish
locale.set('da');
const danishResult = serializeRecordingToCsvWithoutGestureName(input);
expect(danishResult).toContain('0;1,23;4,56;7,89');
expect(danishResult).toContain('1;10,1;20,2;30,3');
});

test('Integer values are not affected by locale formatting', () => {
const integerInput: RecordingData = {
ID: 123,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [1, 2, 3],
},
{
vector: [10, 20, 30],
},
],
};

// Test with German locale (comma separator)
locale.set('de');
const result = serializeRecordingToCsvWithoutGestureName(integerInput);

// Integer values should remain unchanged
expect(result).toContain('0;1;2;3');
expect(result).toContain('1;10;20;30');
expect(result).not.toContain(','); // No commas should appear for integers
});

test('Negative numbers are formatted correctly with locale', () => {
const negativeInput: RecordingData = {
ID: 123,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [-1.5, -2.75, -3.125],
},
],
};

// Test English
locale.set('en');
const englishResult = serializeRecordingToCsvWithoutGestureName(negativeInput);
expect(englishResult).toContain('0;-1.5;-2.75;-3.125');

// Test German
locale.set('de');
const germanResult = serializeRecordingToCsvWithoutGestureName(negativeInput);
expect(germanResult).toContain('0;-1,5;-2,75;-3,125');
});

test('Large numbers (>1000) are formatted without thousands separator', () => {
const largeInput: RecordingData = {
ID: 999,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [1000.5, 1234.56, 1000000.001],
},
],
};

// English: period as decimal separator, no thousands separator
locale.set('en');
const enResult = serializeRecordingToCsvWithoutGestureName(largeInput);
expect(enResult).toContain('0;1000.5;1234.56;1000000.001');
// Ensure no thousands separators like '1,234' or '1.234' or spaces
expect(enResult).not.toContain('1,234');
expect(enResult).not.toContain('1.234');
expect(enResult).not.toContain('1 234');

// German: comma as decimal separator, no thousands separator
locale.set('de');
const deResult = serializeRecordingToCsvWithoutGestureName(largeInput);
expect(deResult).toContain('0;1000,5;1234,56;1000000,001');
expect(deResult).not.toContain('1.234');
expect(deResult).not.toContain('1,234');
expect(deResult).not.toContain('1 234');

// Danish: comma as decimal separator, no thousands separator
locale.set('da');
const daResult = serializeRecordingToCsvWithoutGestureName(largeInput);
expect(daResult).toContain('0;1000,5;1234,56;1000000,001');
expect(daResult).not.toContain('1.234');
expect(daResult).not.toContain('1,234');
expect(daResult).not.toContain('1 234');
});

// Reset locale after tests to avoid affecting other tests
afterEach(() => {
locale.set('en');
});
});
});
14 changes: 14 additions & 0 deletions src/__viteBuildVariants__/experimental/features.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"title": "ML-Machine",
"knnModel": true,
"lossGraph": true,
"makecode": true,
"liveGraphInputValues": true,
"recordingScrubberValues": true,
"modelValidation": true,
"modelSettings": true,
"fingerprint": true,
"recordingDuration": 2500,
"printableRecordings": true,
"dialogRecordings": true
}
36 changes: 36 additions & 0 deletions src/__viteBuildVariants__/experimental/windi.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* (c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors
*
* SPDX-License-Identifier: MIT
*/

//Ml-Machine colors

export default {
theme: {
extend: {
colors: {
primary: '#2B5EA7',
primarytext: '#000000',
primaryaccent: '#8BbEf7',
secondary: '#2CCAC0',
secondarytext: '#FFFFFF',
info: '#98A2B3',
backgrounddark: '#F5F5F5',
backgroundlight: '#ffffff',
infolight: '#93c5fd',
link: '#258aff',
warning: '#FF7777',
disabled: '#8892A3',
primaryborder: '#E5E7EB',
primaryborderaccent: '#b5b7bB',
infobglight: '#E7E5E4',
infobgdark: '#57534E',
infoiconlight: '#FFFFFF7F',
infoicondark: '#787878',
infotextlight: '#ffffff',
infotextdark: '#787878',
},
},
},
};
5 changes: 4 additions & 1 deletion src/__viteBuildVariants__/ml-machine-simple/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"recordingScrubberValues": false,
"modelValidation": false,
"modelSettings": false,
"fingerprint": false
"fingerprint": false,
"recordingDuration": 1800,
"printableRecordings": false,
"dialogRecordings": false
}
5 changes: 4 additions & 1 deletion src/__viteBuildVariants__/ml-machine/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"recordingScrubberValues": true,
"modelValidation": true,
"modelSettings": true,
"fingerprint": true
"fingerprint": true,
"recordingDuration": 1800,
"printableRecordings": false,
"dialogRecordings": false
}
5 changes: 4 additions & 1 deletion src/__viteBuildVariants__/unbranded/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"recordingScrubberValues": true,
"modelValidation": true,
"modelSettings": true,
"fingerprint": true
"fingerprint": true,
"recordingDuration": 1800,
"printableRecordings": true,
"dialogRecordings": true
}
3 changes: 3 additions & 0 deletions src/assets/messages/ui.da.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"content.data.noData.exampleName.still": "Stille",
"content.data.noData.exampleName.circle": "Cirkel",
"content.data.tooltip.remove": "Fjern",
"content.data.dialog.download": "Download CSV",
"content.data.dialog.delete": "Slet",
"content.data.dialog.details": "Detaljer",
"content.trainer.failure.header": "Træning mislykkedes",
"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.",
"content.trainer.failure.todo": "Gå tilbage til datasiden og ændr i din data.",
Expand Down
3 changes: 3 additions & 0 deletions src/assets/messages/ui.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"content.data.noData.exampleName.still": "Still",
"content.data.noData.exampleName.circle": "Kreis",
"content.data.tooltip.remove": "Entfernen",
"content.data.dialog.download": "CSV herunterladen",
"content.data.dialog.delete": "Löschen",
"content.data.dialog.details": "Details",
"content.trainer.failure.header": "Training fehlgeschlagen",
"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.",
"content.trainer.failure.todo": "Gehe zur Datenseite zurück und ändere die Daten.",
Expand Down
3 changes: 3 additions & 0 deletions src/assets/messages/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"content.data.noData.exampleName.still": "Still",
"content.data.noData.exampleName.circle": "Circle",
"content.data.tooltip.remove": "Remove",
"content.data.dialog.download": "Download CSV",
"content.data.dialog.delete": "Delete",
"content.data.dialog.details": "Details",
"content.trainer.failure.header": "Training Failed",
"content.trainer.failure.body": "The training did not result in a usable model. The reason for this is most likely the data used for training. If the data for different classes are too similar, this can result in issues in the training process.",
"content.trainer.failure.todo": "Return to the data page and change your data.",
Expand Down
Loading
Loading