From a3e647f1376f04dd1e725470ac2b16b8ef7aeb5a Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 1 May 2024 21:44:33 +0200 Subject: [PATCH 001/106] Setup test for data representation --- src/__tests__/data-representation.test.ts | 48 +++++++++++++++++++ src/__tests__/testUtils.ts | 10 ++++ .../AccelerometerDataSynthesizer.ts | 9 ++-- src/script/domain/LiveDataBuffer.ts | 6 ++- src/script/domain/stores/LiveData.ts | 3 +- src/script/domain/stores/LiveDataVector.ts | 11 +++++ src/script/engine/PollingPredictorEngine.ts | 13 ++--- .../livedata/MicrobitAccelerometerData.ts | 29 +++++++---- .../connection-behaviours/InputBehaviour.ts | 5 +- src/script/stores/Stores.ts | 13 +++-- 10 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 src/__tests__/data-representation.test.ts create mode 100644 src/__tests__/testUtils.ts create mode 100644 src/script/domain/stores/LiveDataVector.ts diff --git a/src/__tests__/data-representation.test.ts b/src/__tests__/data-representation.test.ts new file mode 100644 index 000000000..14a9342ef --- /dev/null +++ b/src/__tests__/data-representation.test.ts @@ -0,0 +1,48 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import exp from 'constants'; +import LiveDataBuffer from '../script/domain/LiveDataBuffer'; +import LiveData from '../script/domain/stores/LiveData'; +import MicrobitAccelerometerLiveData, { MicrobitAccelerometerDataVector } from '../script/livedata/MicrobitAccelerometerData'; +import { gestures } from '../script/stores/Stores'; +import { repeat } from './testUtils'; +import { get } from 'svelte/store'; +import { LiveDataVector } from '../script/domain/stores/LiveDataVector'; + +describe('Data representation tests', () => { + test('Creating accelerometer live data does not throw', () => { + expect(() => { + new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) + }).not.toThrow(); + }); + + test('Number of elements in buffer does not exceed set amount', () => { + const elemsInBuffer = 10; + const liveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(elemsInBuffer)) + + repeat(() => liveData.put(new MicrobitAccelerometerDataVector({ x: 0, y: 0, z: 0 })), 20) + + + expect(() => liveData.getBuffer().getSeries(100, elemsInBuffer)).not.toThrow(); + expect(liveData.getBuffer().getSeries(100, 10).length).toEqual(10); + + expect(() => liveData.getBuffer().getSeries(100, elemsInBuffer + 1)).toThrow(); + }) + + test("Can extract vectors from live data", () => { + const liveData: LiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) + + repeat(() => liveData.put(new MicrobitAccelerometerDataVector({ x: 1, y: 2, z: 3 })), 20) + + expect(() => get(liveData).getVector()).not.toThrow(); + expect(get(liveData).getVector()).toEqual([1, 2, 3]) + }) +}); + diff --git a/src/__tests__/testUtils.ts b/src/__tests__/testUtils.ts new file mode 100644 index 000000000..1f1f06222 --- /dev/null +++ b/src/__tests__/testUtils.ts @@ -0,0 +1,10 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +export const repeat = (func: (a?: any) => any, n: number) => { + for (let i = 0; i < n; i++) { + func(); + } +} \ No newline at end of file diff --git a/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts b/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts index 695da9948..1ead02769 100644 --- a/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts +++ b/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts @@ -12,8 +12,9 @@ import { writable, } from 'svelte/store'; import { liveAccelerometerData } from '../../../script/stores/Stores'; -import { MicrobitAccelerometerData } from '../../../script/livedata/MicrobitAccelerometerData'; +import MicrobitAccelerometerLiveData, { MicrobitAccelerometerData, MicrobitAccelerometerDataVector } from '../../../script/livedata/MicrobitAccelerometerData'; import LiveData from '../../../script/domain/stores/LiveData'; +import { LiveDataVector } from '../../../script/domain/stores/LiveDataVector'; type AccelerometerSynthesizerData = { intervalSpeed: number; @@ -27,7 +28,7 @@ class AccelerometerSynthesizer implements Readable private interval: NodeJS.Timeout | undefined = undefined; private store: Writable; - constructor(private liveData: LiveData) { + constructor(private liveData: LiveData) { this.store = writable({ intervalSpeed: this.getInitialIntervalValue(), xSpeed: this.getInitialSineSpeed(), @@ -79,11 +80,11 @@ class AccelerometerSynthesizer implements Readable public generateData() { const val = new Date().getTime(); - this.liveData.put({ + this.liveData.put(new MicrobitAccelerometerDataVector({ x: Math.sin(val * get(this.store).xSpeed), y: Math.sin(val * get(this.store).ySpeed), z: Math.sin(val * get(this.store).zSpeed), - }); + })); } public setXSpeed(value: number) { diff --git a/src/script/domain/LiveDataBuffer.ts b/src/script/domain/LiveDataBuffer.ts index 8a2483838..8d6859017 100644 --- a/src/script/domain/LiveDataBuffer.ts +++ b/src/script/domain/LiveDataBuffer.ts @@ -1,13 +1,15 @@ +import { LiveDataVector } from "./stores/LiveDataVector"; + /** * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors * * SPDX-License-Identifier: MIT */ -export type TimestampedData = { +export type TimestampedData = { value: T; timestamp: number; }; -class LiveDataBuffer { +class LiveDataBuffer { private buffer: (TimestampedData | null)[]; private bufferPtr = 0; // The buffer pointer keeps increasing from 0 to infinity each time a new item is added private bufferUtilization = 0; diff --git a/src/script/domain/stores/LiveData.ts b/src/script/domain/stores/LiveData.ts index f7e92f886..8edffba0a 100644 --- a/src/script/domain/stores/LiveData.ts +++ b/src/script/domain/stores/LiveData.ts @@ -5,11 +5,12 @@ */ import { Readable } from 'svelte/store'; import LiveDataBuffer from '../LiveDataBuffer'; +import { LiveDataVector } from './LiveDataVector'; /** * A container for real-time data. Uses a LiveDataBuffer to store data points. */ -interface LiveData extends Readable { +interface LiveData extends Readable { /** * Inserts a new data point to the LiveData object */ diff --git a/src/script/domain/stores/LiveDataVector.ts b/src/script/domain/stores/LiveDataVector.ts new file mode 100644 index 000000000..a6d56a465 --- /dev/null +++ b/src/script/domain/stores/LiveDataVector.ts @@ -0,0 +1,11 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +export interface LiveDataVector { + + getVector(): number[] + +} \ No newline at end of file diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts index 0ab69b149..a481f8ab5 100644 --- a/src/script/engine/PollingPredictorEngine.ts +++ b/src/script/engine/PollingPredictorEngine.ts @@ -5,12 +5,12 @@ */ import { Subscriber, Unsubscriber, Writable, derived, get, writable } from 'svelte/store'; import AccelerometerClassifierInput from '../mlmodels/AccelerometerClassifierInput'; -import { MicrobitAccelerometerData } from '../livedata/MicrobitAccelerometerData'; import StaticConfiguration from '../../StaticConfiguration'; import { TimestampedData } from '../domain/LiveDataBuffer'; import Engine, { EngineData } from '../domain/stores/Engine'; import Classifier from '../domain/stores/Classifier'; import LiveData from '../domain/stores/LiveData'; +import { LiveDataVector } from '../domain/stores/LiveDataVector'; class PollingPredictorEngine implements Engine { private pollingInterval: ReturnType | undefined; @@ -18,7 +18,7 @@ class PollingPredictorEngine implements Engine { constructor( private classifier: Classifier, - private liveData: LiveData, + private liveData: LiveData, ) { this.isRunning = writable(true); this.startPolling(); @@ -63,9 +63,10 @@ class PollingPredictorEngine implements Engine { const bufferedData = this.getRawDataFromBuffer( StaticConfiguration.pollingPredictionSampleSize, ); - const xs = bufferedData.map(data => data.value.x); - const ys = bufferedData.map(data => data.value.y); - const zs = bufferedData.map(data => data.value.z); + const xs = bufferedData.map(data => data.value.getVector()[0]); + const ys = bufferedData.map(data => data.value.getVector()[1]); + const zs = bufferedData.map(data => data.value.getVector()[2]); + // TODO: Generalize return new AccelerometerClassifierInput(xs, ys, zs); } @@ -74,7 +75,7 @@ class PollingPredictorEngine implements Engine { */ private getRawDataFromBuffer( sampleSize: number, - ): TimestampedData[] { + ): TimestampedData[] { try { return this.liveData .getBuffer() diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts index 56896039b..1d17600c7 100644 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ b/src/script/livedata/MicrobitAccelerometerData.ts @@ -6,6 +6,7 @@ import { Subscriber, Unsubscriber, Writable, get, writable } from 'svelte/store'; import LiveDataBuffer from '../domain/LiveDataBuffer'; import LiveData from '../domain/stores/LiveData'; +import { LiveDataVector } from '../domain/stores/LiveDataVector'; export type MicrobitAccelerometerData = { x: number; @@ -13,21 +14,31 @@ export type MicrobitAccelerometerData = { z: number; }; -class MicrobitAccelerometerLiveData implements LiveData { - private store: Writable; - constructor(private dataBuffer: LiveDataBuffer) { - this.store = writable({ +export class MicrobitAccelerometerDataVector implements LiveDataVector { + + public constructor(private data: MicrobitAccelerometerData) { + } + + getVector(): number[] { + return [this.data.x, this.data.y, this.data.z] + } +} + +class MicrobitAccelerometerLiveData implements LiveData { + private store: Writable; + constructor(private dataBuffer: LiveDataBuffer) { + this.store = writable(new MicrobitAccelerometerDataVector({ x: 0, y: 0, z: 0, - }); + })); } - public getBuffer(): LiveDataBuffer { + public getBuffer(): LiveDataBuffer { return this.dataBuffer; } - public put(data: MicrobitAccelerometerData): void { + public put(data: MicrobitAccelerometerDataVector): void { this.store.set(data); this.dataBuffer.addValue(data); } @@ -45,8 +56,8 @@ class MicrobitAccelerometerLiveData implements LiveData, - invalidate?: ((value?: MicrobitAccelerometerData | undefined) => void) | undefined, + run: Subscriber, + invalidate?: ((value?: MicrobitAccelerometerDataVector | undefined) => void) | undefined, ): Unsubscriber { return this.store.subscribe(run, invalidate); } diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index 6aab4ac36..7f32ec8c5 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -19,6 +19,7 @@ import TypingUtils from '../../TypingUtils'; import { DeviceRequestStates } from '../../stores/connectDialogStore'; import StaticConfiguration from '../../../StaticConfiguration'; import { liveAccelerometerData } from '../../stores/Stores'; +import { MicrobitAccelerometerDataVector } from '../../livedata/MicrobitAccelerometerData'; let text = get(t); t.subscribe(t => (text = t)); @@ -147,11 +148,11 @@ class InputBehaviour extends LoggingDecorator { const accelY = y / 1000.0; const accelZ = z / 1000.0; - liveAccelerometerData.put({ + liveAccelerometerData.put(new MicrobitAccelerometerDataVector({ x: accelX, y: accelY, z: accelZ, - }); + })); } buttonChange(buttonState: MBSpecs.ButtonState, button: MBSpecs.Button): void { diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index db51a8fa8..6a1de11b4 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -5,9 +5,7 @@ */ import LocalStorageRepositories from '../repository/LocalStorageRepositories'; import PollingPredictorEngine from '../engine/PollingPredictorEngine'; -import MicrobitAccelerometerLiveData, { - MicrobitAccelerometerData, -} from '../livedata/MicrobitAccelerometerData'; +import MicrobitAccelerometerLiveData, { MicrobitAccelerometerDataVector } from '../livedata/MicrobitAccelerometerData'; import LiveDataBuffer from '../domain/LiveDataBuffer'; import StaticConfiguration from '../../StaticConfiguration'; import Repositories from '../domain/Repositories'; @@ -15,20 +13,21 @@ import Gestures from '../domain/stores/gesture/Gestures'; import Classifier from '../domain/stores/Classifier'; import Engine from '../domain/stores/Engine'; import LiveData from '../domain/stores/LiveData'; +import { LiveDataVector } from '../domain/stores/LiveDataVector'; const repositories: Repositories = new LocalStorageRepositories(); const gestures: Gestures = new Gestures(repositories.getGestureRepository()); const classifier: Classifier = repositories.getClassifierRepository().getClassifier(); -const accelerometerDataBuffer = new LiveDataBuffer( +const accelerometerDataBuffer = new LiveDataBuffer( StaticConfiguration.accelerometerLiveDataBufferSize, ); -const liveAccelerometerData: LiveData = +const liveData: LiveData = new MicrobitAccelerometerLiveData(accelerometerDataBuffer); -const engine: Engine = new PollingPredictorEngine(classifier, liveAccelerometerData); +const engine: Engine = new PollingPredictorEngine(classifier, liveData); // Export the stores here. Please be mindful when exporting stores, avoid whenever possible. // This helps us avoid leaking too many objects, that aren't meant to be interacted with -export { engine, gestures, classifier, liveAccelerometerData }; +export { engine, gestures, classifier, liveData as liveAccelerometerData }; From 71c988016b1f75d3a98d52352e32ef3aa9949ebb Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 1 May 2024 22:22:36 +0200 Subject: [PATCH 002/106] Fixed livegraph --- src/components/3d-inspector/View3DLive.svelte | 6 +++++- src/components/Gesture.svelte | 6 +++--- src/components/graphs/DimensionLabels.svelte | 7 ++++--- src/components/graphs/LiveGraph.svelte | 11 ++++++----- src/pages/filter/D3Plot.svelte | 6 +++--- src/script/livedata/PureVector.ts | 17 +++++++++++++++++ src/script/livedata/SmoothedLiveData.ts | 15 +++++++++------ 7 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 src/script/livedata/PureVector.ts diff --git a/src/components/3d-inspector/View3DLive.svelte b/src/components/3d-inspector/View3DLive.svelte index 205f7ff37..a780b4d4f 100644 --- a/src/components/3d-inspector/View3DLive.svelte +++ b/src/components/3d-inspector/View3DLive.svelte @@ -21,7 +21,11 @@ $: { if (!freeze) { - liveDataPoint = $smoothedLiveData; + liveDataPoint = { + x: $smoothedLiveData.getVector()[0], + y: $smoothedLiveData.getVector()[1], + z: $smoothedLiveData.getVector()[2], + }; } } diff --git a/src/components/Gesture.svelte b/src/components/Gesture.svelte index 437b2e3c8..672a248e7 100644 --- a/src/components/Gesture.svelte +++ b/src/components/Gesture.svelte @@ -74,9 +74,9 @@ // Set timeout to allow recording in 1s const unsubscribe = liveAccelerometerData.subscribe(data => { - newData.x.push(data.x); - newData.y.push(data.y); - newData.z.push(data.z); + newData.x.push(data.getVector()[0]); + newData.y.push(data.getVector()[1]); + newData.z.push(data.getVector()[2]); }); // Once duration is over (1000ms default), stop recording diff --git a/src/components/graphs/DimensionLabels.svelte b/src/components/graphs/DimensionLabels.svelte index 4dfe89c56..e017b0fcc 100644 --- a/src/components/graphs/DimensionLabels.svelte +++ b/src/components/graphs/DimensionLabels.svelte @@ -17,6 +17,7 @@ import { state } from '../../script/stores/uiStore'; import StaticConfiguration from '../../StaticConfiguration'; import SmoothedLiveData from '../../script/livedata/SmoothedLiveData'; + import { LiveDataVector } from '../../script/domain/stores/LiveDataVector'; type LabelData = { id: number; @@ -28,7 +29,7 @@ // The height of character is used to fix overlapping line labels const CHARACTER_HEIGHT = 16; - export let liveData: SmoothedLiveData; + export let liveData: SmoothedLiveData; export let maxValue: number; export let minValue: number; export let graphHeight: number; @@ -39,8 +40,8 @@ onMount(() => { unsubscribeFromLiveData = liveData.subscribe(data => { const dataInArray = []; - for (const property in data) { - dataInArray.push(data[property]); + for (const num of data.getVector()) { + dataInArray.push(num); } updateDimensionLabels(dataInArray); }); diff --git a/src/components/graphs/LiveGraph.svelte b/src/components/graphs/LiveGraph.svelte index 176648f5e..e518dec43 100644 --- a/src/components/graphs/LiveGraph.svelte +++ b/src/components/graphs/LiveGraph.svelte @@ -14,6 +14,7 @@ import StaticConfiguration from '../../StaticConfiguration'; import SmoothedLiveData from '../../script/livedata/SmoothedLiveData'; import { classifier } from '../../script/stores/Stores'; + import { LiveDataVector } from '../../script/domain/stores/LiveDataVector'; /** * TimesSeries, but with the data array added. @@ -24,12 +25,12 @@ // Updates width to ensure that the canvas fills the whole screen export let width: number; - export let liveData: LiveData; + export let liveData: LiveData; export let maxValue: number; export let minValue: number; // Smoothes real-time data by using the 3 most recent data points - const smoothedLiveData = new SmoothedLiveData(liveData, 3); + const smoothedLiveData = new SmoothedLiveData(liveData, 3); var canvas: HTMLCanvasElement | undefined = undefined; var chart: SmoothieChart | undefined; @@ -139,15 +140,15 @@ } } - const addDataToGraphLines = (data: any) => { + const addDataToGraphLines = (data: LiveDataVector) => { const t = new Date().getTime(); let i = 0; - for (const property in data) { + for (const num of data.getVector()) { const line: TimeSeriesWithData = lines[i]; if (!line) { break; } - const newValue = data[property]; + const newValue = num; line.append(t, newValue, false); i++; } diff --git a/src/pages/filter/D3Plot.svelte b/src/pages/filter/D3Plot.svelte index 303fc5c52..85710ee3e 100644 --- a/src/pages/filter/D3Plot.svelte +++ b/src/pages/filter/D3Plot.svelte @@ -146,9 +146,9 @@ ID: uniqueLiveDataID, gestureClassName: 'live', gestureClassID: uniqueLiveDataID, - x: filterFunction([liveData!.x]), - y: filterFunction([liveData!.y]), - z: filterFunction([liveData!.z]), + x: filterFunction([liveData!.getVector()[0]]), + y: filterFunction([liveData!.getVector()[1]]), + z: filterFunction([liveData!.getVector()[2]]), }; return filteredData; } diff --git a/src/script/livedata/PureVector.ts b/src/script/livedata/PureVector.ts new file mode 100644 index 000000000..6e5ff3334 --- /dev/null +++ b/src/script/livedata/PureVector.ts @@ -0,0 +1,17 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { LiveDataVector } from "../domain/stores/LiveDataVector"; + +class PureVector implements LiveDataVector { + public constructor(private numbers: number[]) { } + + public getVector(): number[] { + return this.numbers; + } +} + +export default PureVector; \ No newline at end of file diff --git a/src/script/livedata/SmoothedLiveData.ts b/src/script/livedata/SmoothedLiveData.ts index 404039e3d..5994a8d76 100644 --- a/src/script/livedata/SmoothedLiveData.ts +++ b/src/script/livedata/SmoothedLiveData.ts @@ -7,13 +7,15 @@ import { Readable, Subscriber, Unsubscriber, derived } from 'svelte/store'; import LiveDataBuffer from '../domain/LiveDataBuffer'; import { smoothNewValue } from '../utils/graphUtils'; import LiveData from '../domain/stores/LiveData'; +import { LiveDataVector } from '../domain/stores/LiveDataVector'; +import PureVector from './PureVector'; /** * Uses interpolation to produce a 'smoothed' representation of a live data object. * * Each entry in the SmoothedLiveData will be interpolated with previous values seen. I.e `y_i = 0.75x_(i-1) + 0.25x_i` */ -class SmoothedLiveData implements LiveData { +class SmoothedLiveData implements LiveData { private smoothedStore: Readable; /** @@ -81,12 +83,13 @@ class SmoothedLiveData implements LiveData { return referenceData; } - const newObject: T = { ...referenceData }; - for (const property in newObject) { - const values = oldValues.map(val => val![property] as number); - newObject[property] = smoothNewValue(...values) as never; + const newVector: LiveDataVector = new PureVector(referenceData.getVector()); + + for (let i = 0; i < newVector.getVector().length; i++) { + const values = oldValues.map(val => val!.getVector()[i]); + newVector.getVector()[i] = smoothNewValue(...values); } - return newObject; + return newVector; }); } } From 9c8a49479c66d0bdb97991afb44bd40df681645f Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 8 May 2024 18:25:34 +0200 Subject: [PATCH 003/106] Make Stores container --- src/__tests__/classifier.test.ts | 2 +- src/__tests__/data-representation.test.ts | 19 ++- src/__tests__/gestures.test.ts | 2 +- src/components/3d-inspector/View3DLive.svelte | 4 +- src/components/Gesture.svelte | 6 +- src/components/NewGestureButton.svelte | 4 +- .../bottom/ConnectedLiveGraphButtons.svelte | 4 +- src/components/graphs/DimensionLabels.svelte | 3 - src/components/graphs/LiveGraph.svelte | 3 +- .../graphs/LiveGraphHighlighted.svelte | 3 +- .../graphs/MicrobitLiveGraph.svelte | 11 +- .../knngraph/AxesFilterVectorView.svelte | 17 +- .../knngraph/KNNModelGraphController.ts | 6 +- .../graphs/knngraph/KNNModelGraphDrawer.ts | 12 +- .../graphs/knngraph/KnnModelGraph.svelte | 7 +- .../knngraph/KnnPointToolTipView.svelte | 4 +- .../output/OutputGestureStack.svelte | 3 +- src/components/output/OutputMatrix.svelte | 6 +- .../EngineInteractionButtons.svelte | 6 +- ...LiveDataBufferUtilizationPercentage.svelte | 6 +- .../playground/StoresDisplay.svelte | 15 +- .../playground/TrainKNNModelButton.svelte | 4 +- .../playground/TrainLayersModelButton.svelte | 4 +- .../AccelerometerDataSynthesizer.ts | 38 +++-- .../inputSynthesizer/IntervalSlider.svelte | 12 +- ...icrobitAccelerometerDataSynthesizer.svelte | 9 +- .../inputSynthesizer/SpeedSliders.svelte | 26 +-- .../inputSynthesizer/SynthesizerGraph.svelte | 156 +++++++++++++++++- .../SynthesizerToggleButton.svelte | 16 +- .../inputSynthesizer/SyntheticLiveData .ts | 47 ++++++ src/menus/DataMenu.svelte | 5 +- src/menus/ModelMenu.svelte | 4 +- src/menus/TrainingMenu.svelte | 4 +- src/pages/DataPage.svelte | 3 +- src/pages/filter/D3Plot.svelte | 15 +- src/pages/filter/FilterPage.svelte | 4 +- src/pages/filter/FilterToggler.svelte | 4 +- .../model/stackview/ModelPageStackView.svelte | 3 +- .../ModelPageStackViewContent.svelte | 3 +- .../model/tileview/ModelPageTileView.svelte | 3 +- .../tileview/ModelPageTileViewTiles.svelte | 8 +- src/pages/training/TrainModelButton.svelte | 6 +- .../training/TrainingFailedDialog.svelte | 4 +- src/pages/training/TrainingPage.svelte | 5 +- src/script/domain/stores/LiveData.ts | 4 - src/script/domain/stores/LiveDataVector.ts | 4 + src/script/domain/stores/gesture/Gesture.ts | 14 +- src/script/domain/stores/gesture/Gestures.ts | 17 +- src/script/livedata/BaseVector.ts | 25 +++ .../livedata/MicrobitAccelerometerData.ts | 17 +- src/script/livedata/PureVector.ts | 17 -- src/script/livedata/SmoothedLiveData.ts | 19 +-- .../connection-behaviours/InputBehaviour.ts | 4 +- src/script/repository/FileUtility.ts | 6 +- .../LocalStorageGestureRepository.ts | 4 +- src/script/stores/Stores.ts | 100 ++++++----- src/script/stores/uiStore.ts | 4 +- 57 files changed, 530 insertions(+), 231 deletions(-) create mode 100644 src/components/playground/inputSynthesizer/SyntheticLiveData .ts create mode 100644 src/script/livedata/BaseVector.ts delete mode 100644 src/script/livedata/PureVector.ts diff --git a/src/__tests__/classifier.test.ts b/src/__tests__/classifier.test.ts index c63888687..bc8cd65dc 100644 --- a/src/__tests__/classifier.test.ts +++ b/src/__tests__/classifier.test.ts @@ -7,7 +7,7 @@ * SPDX-License-Identifier: MIT */ -import { classifier, gestures } from '../script/stores/Stores'; +import { classifier, gestures } from '../script/stores/availableStores'; import TestMLModelTrainer from './mocks/mlmodel/TestMLModelTrainer'; describe('Classifier tests', () => { diff --git a/src/__tests__/data-representation.test.ts b/src/__tests__/data-representation.test.ts index 14a9342ef..02a61a38f 100644 --- a/src/__tests__/data-representation.test.ts +++ b/src/__tests__/data-representation.test.ts @@ -7,14 +7,14 @@ * SPDX-License-Identifier: MIT */ -import exp from 'constants'; import LiveDataBuffer from '../script/domain/LiveDataBuffer'; import LiveData from '../script/domain/stores/LiveData'; import MicrobitAccelerometerLiveData, { MicrobitAccelerometerDataVector } from '../script/livedata/MicrobitAccelerometerData'; -import { gestures } from '../script/stores/Stores'; import { repeat } from './testUtils'; import { get } from 'svelte/store'; import { LiveDataVector } from '../script/domain/stores/LiveDataVector'; +import SmoothedLiveData from '../script/livedata/SmoothedLiveData'; +import { smoothNewValue } from '../script/utils/graphUtils'; describe('Data representation tests', () => { test('Creating accelerometer live data does not throw', () => { @@ -44,5 +44,20 @@ describe('Data representation tests', () => { expect(() => get(liveData).getVector()).not.toThrow(); expect(get(liveData).getVector()).toEqual([1, 2, 3]) }) + + test("Test smoothed values", () => { + const liveData: LiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(20)); + const smoothLiveData = new SmoothedLiveData(liveData, 2); + + const point1 = new MicrobitAccelerometerDataVector({ x: 3, y: 2, z: 1 }); + const point2 = new MicrobitAccelerometerDataVector({ x: 1, y: 2, z: 3 }); + + liveData.put(point1) + liveData.put(point2) + + expect(get(smoothLiveData).getVector()[0]).toBeCloseTo(smoothNewValue(point2.getVector()[0], point1.getVector()[0]), 10) + expect(get(smoothLiveData).getVector()[1]).toBeCloseTo(smoothNewValue(point2.getVector()[1], point1.getVector()[1]), 10) + expect(get(smoothLiveData).getVector()[2]).toBeCloseTo(smoothNewValue(point2.getVector()[2], point1.getVector()[2]), 10) + }) }); diff --git a/src/__tests__/gestures.test.ts b/src/__tests__/gestures.test.ts index 0a517354d..5d3fef96b 100644 --- a/src/__tests__/gestures.test.ts +++ b/src/__tests__/gestures.test.ts @@ -7,7 +7,7 @@ * SPDX-License-Identifier: MIT */ -import { gestures } from '../script/stores/Stores'; +import { gestures } from '../script/stores/availableStores'; describe('Tests of Gestures', () => { beforeEach(() => { diff --git a/src/components/3d-inspector/View3DLive.svelte b/src/components/3d-inspector/View3DLive.svelte index a780b4d4f..641cfb223 100644 --- a/src/components/3d-inspector/View3DLive.svelte +++ b/src/components/3d-inspector/View3DLive.svelte @@ -6,7 +6,7 @@ diff --git a/src/components/graphs/DimensionLabels.svelte b/src/components/graphs/DimensionLabels.svelte index e017b0fcc..17b507def 100644 --- a/src/components/graphs/DimensionLabels.svelte +++ b/src/components/graphs/DimensionLabels.svelte @@ -14,7 +14,6 @@ @@ -23,7 +24,7 @@ {/if} @@ -31,7 +32,7 @@ {/if} @@ -39,7 +40,7 @@ {/if} @@ -47,6 +48,6 @@ {/if} diff --git a/src/components/graphs/knngraph/AxesFilterVectorView.svelte b/src/components/graphs/knngraph/AxesFilterVectorView.svelte index 6b2c3c3a4..074a97d05 100644 --- a/src/components/graphs/knngraph/AxesFilterVectorView.svelte +++ b/src/components/graphs/knngraph/AxesFilterVectorView.svelte @@ -5,16 +5,18 @@ --> diff --git a/src/components/playground/EngineInteractionButtons.svelte b/src/components/playground/EngineInteractionButtons.svelte index 18e5196df..41b86fb5d 100644 --- a/src/components/playground/EngineInteractionButtons.svelte +++ b/src/components/playground/EngineInteractionButtons.svelte @@ -6,11 +6,15 @@ diff --git a/src/components/playground/StoresDisplay.svelte b/src/components/playground/StoresDisplay.svelte index 7b8123d63..f25cb98f9 100644 --- a/src/components/playground/StoresDisplay.svelte +++ b/src/components/playground/StoresDisplay.svelte @@ -4,14 +4,13 @@ SPDX-License-Identifier: MIT --> @@ -46,9 +45,9 @@

LiveData store

- {JSON.stringify($liveAccelerometerData, null, 2).substring( + {JSON.stringify($liveDataStore, null, 2).substring( 2, - JSON.stringify($liveAccelerometerData, null, 2).length - 1, + JSON.stringify($liveDataStore, null, 2).length - 1, )}

diff --git a/src/components/playground/TrainKNNModelButton.svelte b/src/components/playground/TrainKNNModelButton.svelte index 797e22385..b10e767d0 100644 --- a/src/components/playground/TrainKNNModelButton.svelte +++ b/src/components/playground/TrainKNNModelButton.svelte @@ -6,9 +6,11 @@ -

Synthesis interval ({$accelerometerSynthesizer.intervalSpeed}), lower is faster

+

Synthesis interval ({$liveDataSynthesizer.intervalSpeed}), lower is faster

setIntervalValue(e.detail.value)} /> diff --git a/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte b/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte index 46d19555a..27d14a21a 100644 --- a/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte +++ b/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte @@ -6,12 +6,11 @@
@@ -24,9 +23,9 @@
-

{JSON.stringify($accelerometerSynthesizer, null, 2)}

+

{JSON.stringify($liveDataSynthesizer, null, 2)}

diff --git a/src/components/playground/inputSynthesizer/SpeedSliders.svelte b/src/components/playground/inputSynthesizer/SpeedSliders.svelte index a328c4c4f..c53224bfe 100644 --- a/src/components/playground/inputSynthesizer/SpeedSliders.svelte +++ b/src/components/playground/inputSynthesizer/SpeedSliders.svelte @@ -4,7 +4,7 @@ SPDX-License-Identifier: MIT -->

x Speed (Frequency)

accelerometerSynthesizer.setXSpeed(e.detail.value)} /> + on:change={e => liveDataSynthesizer.setXSpeed(e.detail.value)} />

y Speed (Frequency)

accelerometerSynthesizer.setYSpeed(e.detail.value)} /> + on:change={e => liveDataSynthesizer.setYSpeed(e.detail.value)} />

z Speed (Frequency)

accelerometerSynthesizer.setZSpeed(e.detail.value)} /> + on:change={e => liveDataSynthesizer.setZSpeed(e.detail.value)} /> diff --git a/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte b/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte index a602c9921..0d095d0db 100644 --- a/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte +++ b/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte @@ -3,15 +3,14 @@ SPDX-License-Identifier: MIT --> - + + + + + +
+ + +
diff --git a/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte b/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte index 46204d025..e2cb5d401 100644 --- a/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte +++ b/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte @@ -4,13 +4,14 @@ SPDX-License-Identifier: MIT --> @@ -18,8 +19,7 @@
+ on:click={toggleSynthesizer}> + {$liveDataSynthesizer.isActive ? 'Stop synthesizer' : 'Start synthesizer'} +
diff --git a/src/components/playground/inputSynthesizer/SyntheticLiveData .ts b/src/components/playground/inputSynthesizer/SyntheticLiveData .ts new file mode 100644 index 000000000..d581da0d4 --- /dev/null +++ b/src/components/playground/inputSynthesizer/SyntheticLiveData .ts @@ -0,0 +1,47 @@ +import { Subscriber, Invalidator, Unsubscriber, Writable, writable, get } from "svelte/store"; +import LiveDataBuffer from "../../../script/domain/LiveDataBuffer"; +import LiveData from "../../../script/domain/stores/LiveData"; +import { LiveDataVector } from "../../../script/domain/stores/LiveDataVector"; +import BaseVector from "../../../script/livedata/BaseVector"; + +export class Synthetic5AxisData implements LiveDataVector { + public constructor(private base: BaseVector) { + } + + getVector(): number[] { + return this.base.getVector(); + } + getSize(): number { + return this.base.getSize(); + } + getLabels(): string[] { + return this.base.getLabels(); + } + +} + +export class SyntheticLiveData implements LiveData { + private store: Writable; + private buffer: LiveDataBuffer + public constructor() { + this.store = writable(new Synthetic5AxisData(new BaseVector([0, 0, 0, 0, 0], ["A", "B", "C", "D", "F"]))) + this.buffer = new LiveDataBuffer(200); + } + put(data: Synthetic5AxisData): void { + this.store.set(data); + this.buffer.addValue(data); + } + getBuffer(): LiveDataBuffer { + return this.buffer; + } + getSeriesSize(): number { + return get(this.store).getSize(); + } + getLabels(): string[] { + return get(this.store).getLabels(); + } + subscribe(run: Subscriber, invalidate?: Invalidator | undefined): Unsubscriber { + return this.store.subscribe(run, invalidate); + } + +} \ No newline at end of file diff --git a/src/menus/DataMenu.svelte b/src/menus/DataMenu.svelte index 1f2852958..3ef02616c 100644 --- a/src/menus/DataMenu.svelte +++ b/src/menus/DataMenu.svelte @@ -5,9 +5,10 @@ -->
diff --git a/src/pages/DataPage.svelte b/src/pages/DataPage.svelte index 51765e3ec..402ec69e5 100644 --- a/src/pages/DataPage.svelte +++ b/src/pages/DataPage.svelte @@ -18,13 +18,14 @@ import DataPageControlBar from '../components/datacollection/DataPageControlBar.svelte'; import Information from '../components/information/Information.svelte'; import { onMount } from 'svelte'; - import { gestures } from '../script/stores/Stores'; import FileUtility from '../script/repository/FileUtility'; import { get } from 'svelte/store'; import exampleDataset from '../exampleDataset.json'; import { GestureData } from '../script/domain/stores/gesture/Gesture'; + import { stores } from '../script/stores/Stores'; let isConnectionDialogOpen = false; + const gestures = stores.getGestures(); $: hasSomeData = (): boolean => { if ($gestures.length === 0) { diff --git a/src/pages/filter/D3Plot.svelte b/src/pages/filter/D3Plot.svelte index f536e9d70..e771719d4 100644 --- a/src/pages/filter/D3Plot.svelte +++ b/src/pages/filter/D3Plot.svelte @@ -9,12 +9,12 @@ import { get } from 'svelte/store'; import * as d3 from 'd3'; import { state } from '../../script/stores/uiStore'; - import { gestures, liveAccelerometerData } from '../../script/stores/Stores'; import FilterTypes, { FilterType } from '../../script/domain/FilterTypes'; import FilterGraphLimits from '../../script/utils/FilterLimits'; import { GestureData } from '../../script/domain/stores/gesture/Gesture'; import { RecordingData } from '../../script/domain/stores/gesture/Gestures'; import StaticConfiguration from '../../StaticConfiguration'; + import { stores } from '../../script/stores/Stores'; export let filterType: FilterType; export let fullScreen: boolean = false; @@ -141,7 +141,8 @@ } function createLiveData() { - const liveData = liveAccelerometerData + const liveD = stores + .getLiveData() .getBuffer() .getSeries( StaticConfiguration.recordingDuration, @@ -149,11 +150,11 @@ ) .map(d => d.value); - const xs = liveData.map(d => d!.getVector()[0]); - const ys = liveData.map(d => d!.getVector()[1]); - const zs = liveData.map(d => d!.getVector()[2]); + const xs = liveD.map(d => d!.getVector()[0]); + const ys = liveD.map(d => d!.getVector()[1]); + const zs = liveD.map(d => d!.getVector()[2]); - if (liveData === undefined) return undefined; + if (stores.getLiveData() === undefined) return undefined; const filteredData: RecordingRepresentation = { ID: uniqueLiveDataID, gestureClassName: 'live', @@ -168,7 +169,7 @@ // Side effect: updates classList and color function createDataRepresentation() { const classes: { name: string; id: number }[] = []; - const data: GestureData[] = get(gestures); + const data: GestureData[] = get(stores.getGestures()); const recordings: RecordingRepresentation[] = []; data.map(gestureClassObject => { const gestureClassName: string = gestureClassObject.name; diff --git a/src/pages/filter/FilterPage.svelte b/src/pages/filter/FilterPage.svelte index 194deb5b1..0b2c93743 100644 --- a/src/pages/filter/FilterPage.svelte +++ b/src/pages/filter/FilterPage.svelte @@ -8,14 +8,16 @@ import FilterToggler from './FilterToggler.svelte'; import ControlBar from '../../components/control-bar/ControlBar.svelte'; import { t } from '../../i18n'; - import { gestures } from '../../script/stores/Stores'; import StandardButton from '../../components/buttons/StandardButton.svelte'; import { Paths, navigate } from '../../router/paths'; import FilterTypes, { FilterType } from '../../script/domain/FilterTypes'; + import { stores } from '../../script/stores/Stores'; let isFilterInspectorDialogOpen = false; let currentFilter: FilterType | undefined = undefined; + const gestures = stores.getGestures(); + const openFilterInspector = (filter: FilterType, fullScreen: boolean) => { currentFilter = filter; isFilterInspectorDialogOpen = fullScreen; diff --git a/src/pages/filter/FilterToggler.svelte b/src/pages/filter/FilterToggler.svelte index 36a9c3b75..32a8f1b13 100644 --- a/src/pages/filter/FilterToggler.svelte +++ b/src/pages/filter/FilterToggler.svelte @@ -8,9 +8,11 @@ import Information from '../../components/information/Information.svelte'; import Filter from '../../script/domain/Filter'; import FilterTypes, { FilterType } from '../../script/domain/FilterTypes'; - import { classifier } from '../../script/stores/Stores'; + import { stores } from '../../script/stores/Stores'; import D3Plot from './D3Plot.svelte'; + const classifier = stores.getClassifier(); + export let filterType: FilterType; export let openFilterInspector: (filter: FilterType, fullScreen: boolean) => void; export let fullScreen = false; diff --git a/src/pages/model/stackview/ModelPageStackView.svelte b/src/pages/model/stackview/ModelPageStackView.svelte index c1e5bbd11..ed14309e5 100644 --- a/src/pages/model/stackview/ModelPageStackView.svelte +++ b/src/pages/model/stackview/ModelPageStackView.svelte @@ -15,9 +15,10 @@ import TrainModelFirstTitle from '../../../components/TrainModelFirstTitle.svelte'; import ModelPageStackViewContent from './ModelPageStackViewContent.svelte'; import PleaseConnectFirst from '../../../components/PleaseConnectFirst.svelte'; - import { classifier } from '../../../script/stores/Stores'; import StaticConfiguration from '../../../StaticConfiguration'; + import { stores } from '../../../script/stores/Stores'; + const classifier = stores.getClassifier(); // In case of manual classification, variables for evaluation let recordingTime = 0; diff --git a/src/pages/model/stackview/ModelPageStackViewContent.svelte b/src/pages/model/stackview/ModelPageStackViewContent.svelte index 2a6aa3012..94573e9b2 100644 --- a/src/pages/model/stackview/ModelPageStackViewContent.svelte +++ b/src/pages/model/stackview/ModelPageStackViewContent.svelte @@ -7,10 +7,11 @@ import { fade } from 'svelte/transition'; import Information from '../../../components/information/Information.svelte'; import OutputGesture from '../../../components/output/OutputGesture.svelte'; - import { gestures } from '../../../script/stores/Stores'; import { state } from '../../../script/stores/uiStore'; import { t } from './../../../i18n'; + import { stores } from '../../../script/stores/Stores'; + const gestures = stores.getGestures(); // Bool flags to know whether output microbit popup should be show let hasClosedPopup = false; diff --git a/src/pages/model/tileview/ModelPageTileView.svelte b/src/pages/model/tileview/ModelPageTileView.svelte index ace31c904..c2cb04456 100644 --- a/src/pages/model/tileview/ModelPageTileView.svelte +++ b/src/pages/model/tileview/ModelPageTileView.svelte @@ -15,9 +15,10 @@ import { onMount } from 'svelte'; import Microbits from '../../../script/microbit-interfacing/Microbits'; import ModelPageTileViewTiles from './ModelPageTileViewTiles.svelte'; - import { classifier } from '../../../script/stores/Stores'; import StaticConfiguration from '../../../StaticConfiguration'; + import { stores } from '../../../script/stores/Stores'; + const classifier = stores.getClassifier(); // In case of manual classification, variables for evaluation let recordingTime = 0; // let lastRecording; diff --git a/src/pages/model/tileview/ModelPageTileViewTiles.svelte b/src/pages/model/tileview/ModelPageTileViewTiles.svelte index 5c824c6bd..0276615ef 100644 --- a/src/pages/model/tileview/ModelPageTileViewTiles.svelte +++ b/src/pages/model/tileview/ModelPageTileViewTiles.svelte @@ -13,9 +13,9 @@ } from '../../../script/stores/uiStore'; import Microbits from '../../../script/microbit-interfacing/Microbits'; import MediaQuery from '../../../components/MediaQuery.svelte'; - import { gestures } from '../../../script/stores/Stores'; import OutputGesture from '../../../components/output/OutputGesture.svelte'; import StaticConfiguration from '../../../StaticConfiguration'; + import { stores } from '../../../script/stores/Stores'; // In case of manual classification, variables for evaluation let recordingTime = 0; @@ -84,7 +84,7 @@ {#if matches}
- {#each gestures.getGestures() as gesture} + {#each stores.getGestures().getGestures() as gesture} {/each}
@@ -93,7 +93,7 @@ {#if matches}
- {#each gestures.getGestures() as gesture} + {#each stores.getGestures().getGestures() as gesture} {/each}
@@ -102,7 +102,7 @@ {#if matches}
- {#each gestures.getGestures() as gesture} + {#each stores.getGestures().getGestures() as gesture} {/each}
diff --git a/src/pages/training/TrainModelButton.svelte b/src/pages/training/TrainModelButton.svelte index 75d5db58b..a7b974051 100644 --- a/src/pages/training/TrainModelButton.svelte +++ b/src/pages/training/TrainModelButton.svelte @@ -6,11 +6,9 @@
@@ -19,13 +22,15 @@

Uses sine-waves to produce LiveData

- +
- + {#key keycnt} + + {/key}

{JSON.stringify($liveDataSynthesizer, null, 2)}

diff --git a/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte b/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte new file mode 100644 index 000000000..14f16275c --- /dev/null +++ b/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte @@ -0,0 +1,11 @@ + + +

No of axes ({$liveDataSynthesizer.noOfAxes})

+ setNoOfAxes(e.detail.value)} /> diff --git a/src/components/playground/inputSynthesizer/SpeedSliders.svelte b/src/components/playground/inputSynthesizer/SpeedSliders.svelte index c53224bfe..4d3311583 100644 --- a/src/components/playground/inputSynthesizer/SpeedSliders.svelte +++ b/src/components/playground/inputSynthesizer/SpeedSliders.svelte @@ -6,36 +6,14 @@ -

x Speed (Frequency)

- liveDataSynthesizer.setXSpeed(e.detail.value)} /> - -

y Speed (Frequency)

- liveDataSynthesizer.setYSpeed(e.detail.value)} /> - -

z Speed (Frequency)

- liveDataSynthesizer.setZSpeed(e.detail.value)} /> +{#each values as val, index} +

Speed {index}

+ liveDataSynthesizer.setSpeed(index, e.detail.value)} /> +{/each} diff --git a/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte b/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte index 0d095d0db..7fcb9019b 100644 --- a/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte +++ b/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte @@ -107,7 +107,7 @@ import { LiveDataVector } from '../../../script/domain/stores/LiveDataVector'; import StaticConfiguration from '../../../StaticConfiguration'; import SmoothedLiveData from '../../../script/livedata/SmoothedLiveData'; - import { onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; import { Unsubscriber } from 'svelte/motion'; import DimensionLabels from '../../graphs/DimensionLabels.svelte'; import liveDataSynthesizer from './AccelerometerDataSynthesizer'; @@ -133,10 +133,6 @@ // Smoothes real-time data by using the 3 most recent data points const smoothedLiveData = new SmoothedLiveData(liveData, 3); - smoothedLiveData.subscribe(e => { - //console.log(e.getVector()); - }); - var canvas: HTMLCanvasElement | undefined = undefined; var chart: SmoothieChart | undefined; const lines: TimeSeriesWithData[] = []; @@ -209,6 +205,10 @@ let unsubscribeFromData: Unsubscriber | undefined; + onDestroy(() => { + unsubscribeFromData && unsubscribeFromData(); + }); + // If state is connected. Start updating the graph whenever there is new data // From the Micro:Bit function updateCanvas(isConnected: boolean) { diff --git a/src/components/playground/inputSynthesizer/SyntheticLiveData .ts b/src/components/playground/inputSynthesizer/SyntheticLiveData .ts index d581da0d4..5ce8d41a9 100644 --- a/src/components/playground/inputSynthesizer/SyntheticLiveData .ts +++ b/src/components/playground/inputSynthesizer/SyntheticLiveData .ts @@ -3,35 +3,20 @@ import LiveDataBuffer from "../../../script/domain/LiveDataBuffer"; import LiveData from "../../../script/domain/stores/LiveData"; import { LiveDataVector } from "../../../script/domain/stores/LiveDataVector"; import BaseVector from "../../../script/livedata/BaseVector"; +import { lab } from "d3"; -export class Synthetic5AxisData implements LiveDataVector { - public constructor(private base: BaseVector) { - } - - getVector(): number[] { - return this.base.getVector(); - } - getSize(): number { - return this.base.getSize(); - } - getLabels(): string[] { - return this.base.getLabels(); - } - -} - -export class SyntheticLiveData implements LiveData { - private store: Writable; - private buffer: LiveDataBuffer - public constructor() { - this.store = writable(new Synthetic5AxisData(new BaseVector([0, 0, 0, 0, 0], ["A", "B", "C", "D", "F"]))) +export class SyntheticLiveData implements LiveData { + private store: Writable; + private buffer: LiveDataBuffer + public constructor(labels: string[]) { + this.store = writable(new BaseVector(new Array(labels.length).fill(0), labels)) this.buffer = new LiveDataBuffer(200); } - put(data: Synthetic5AxisData): void { + put(data: BaseVector): void { this.store.set(data); this.buffer.addValue(data); } - getBuffer(): LiveDataBuffer { + getBuffer(): LiveDataBuffer { return this.buffer; } getSeriesSize(): number { @@ -40,7 +25,7 @@ export class SyntheticLiveData implements LiveData { getLabels(): string[] { return get(this.store).getLabels(); } - subscribe(run: Subscriber, invalidate?: Invalidator | undefined): Unsubscriber { + subscribe(run: Subscriber, invalidate?: Invalidator | undefined): Unsubscriber { return this.store.subscribe(run, invalidate); } diff --git a/src/script/livedata/SmoothedLiveData.ts b/src/script/livedata/SmoothedLiveData.ts index e06757593..c8f9b0bd8 100644 --- a/src/script/livedata/SmoothedLiveData.ts +++ b/src/script/livedata/SmoothedLiveData.ts @@ -79,9 +79,8 @@ class SmoothedLiveData implements LiveData val!.getVector()[i]); newVector.getVector()[i] = smoothNewValue(...values); diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index 315c0b7d2..b0f04bdde 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: MIT */ +import { Invalidator, Readable, Subscriber, Unsubscriber, Writable, derived, get, writable } from "svelte/store"; import Repositories from "../domain/Repositories"; import Classifier from "../domain/stores/Classifier"; import Engine from "../domain/stores/Engine"; @@ -13,38 +14,52 @@ import Gestures from "../domain/stores/gesture/Gestures"; import PollingPredictorEngine from "../engine/PollingPredictorEngine"; import LocalStorageRepositories from "../repository/LocalStorageRepositories"; +type StoresType = { + liveData: LiveData +} /** * Stores is a container object, that allows for management of global stores. */ -class Stores { +class Stores implements Readable{ - private liveData: LiveData | undefined; + private liveData: Writable | undefined>; private engine: Engine | undefined; private classifier: Classifier; private gestures: Gestures; public constructor() { - this.liveData = undefined; + this.liveData = writable(undefined); this.engine = undefined; const repositories: Repositories = new LocalStorageRepositories(); this.classifier = repositories.getClassifierRepository().getClassifier(); this.gestures = new Gestures(repositories.getGestureRepository()); } + public subscribe(run: Subscriber, invalidate?: Invalidator | undefined): Unsubscriber { + return derived([this.liveData], stores => { + if (!stores[0]) { + throw new Error("Cannot subscribe to stores, livedata is null or undefined, set it user setLiveData(...) first"); + } + return { + liveData: stores[0] + } + }).subscribe(run, invalidate); + } + public getLiveData(): LiveData { - if (!this.liveData) { + if (!get(this.liveData)) { throw new Error("Cannot get liveData store. You must initialize it using setLiveData(...)") } - return this.liveData; + return get(this.liveData)!; } public setLiveData>(liveDataStore: T): T { if (!liveDataStore) { throw new Error("Cannot set live data store to undefined/null"); } - this.liveData = liveDataStore; - this.engine = new PollingPredictorEngine(this.classifier, this.liveData); - return this.liveData as T; + this.liveData.set(liveDataStore); + this.engine = new PollingPredictorEngine(this.classifier, this.getLiveData()); + return get(this.liveData) as T; } public getClassifier(): Classifier { From 5702eb7e48a0eb65d9a44f90f463465f0d87f2ea Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 8 May 2024 19:48:12 +0200 Subject: [PATCH 005/106] Rename file --- .../playground/inputSynthesizer/IntervalSlider.svelte | 2 +- .../{AccelerometerDataSynthesizer.ts => LiveDataSynthesizer.ts} | 0 .../MicrobitAccelerometerDataSynthesizer.svelte | 2 +- .../playground/inputSynthesizer/NoOfAxesCounter.svelte | 2 +- src/components/playground/inputSynthesizer/SpeedSliders.svelte | 2 +- .../playground/inputSynthesizer/SynthesizerGraph.svelte | 2 +- .../playground/inputSynthesizer/SynthesizerToggleButton.svelte | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename src/components/playground/inputSynthesizer/{AccelerometerDataSynthesizer.ts => LiveDataSynthesizer.ts} (100%) diff --git a/src/components/playground/inputSynthesizer/IntervalSlider.svelte b/src/components/playground/inputSynthesizer/IntervalSlider.svelte index b055db92b..c2bdd9c35 100644 --- a/src/components/playground/inputSynthesizer/IntervalSlider.svelte +++ b/src/components/playground/inputSynthesizer/IntervalSlider.svelte @@ -6,7 +6,7 @@ diff --git a/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte b/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte index 7fcb9019b..c8747a6c0 100644 --- a/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte +++ b/src/components/playground/inputSynthesizer/SynthesizerGraph.svelte @@ -110,7 +110,7 @@ import { onDestroy, onMount } from 'svelte'; import { Unsubscriber } from 'svelte/motion'; import DimensionLabels from '../../graphs/DimensionLabels.svelte'; - import liveDataSynthesizer from './AccelerometerDataSynthesizer'; + import liveDataSynthesizer from './LiveDataSynthesizer'; import { stores } from '../../../script/stores/Stores'; const classifier = stores.getClassifier(); diff --git a/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte b/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte index e2cb5d401..4fa228cc6 100644 --- a/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte +++ b/src/components/playground/inputSynthesizer/SynthesizerToggleButton.svelte @@ -5,7 +5,7 @@ --> @@ -24,7 +22,7 @@ {/if} @@ -32,7 +30,7 @@ {/if} @@ -40,7 +38,7 @@ {/if} @@ -48,6 +46,6 @@ {/if} diff --git a/src/components/graphs/knngraph/KNNModelGraphController.ts b/src/components/graphs/knngraph/KNNModelGraphController.ts index a7ab53aaf..e57f0240c 100644 --- a/src/components/graphs/knngraph/KNNModelGraphController.ts +++ b/src/components/graphs/knngraph/KNNModelGraphController.ts @@ -7,13 +7,14 @@ import * as d3 from 'd3'; import { TrainingData } from '../../../script/domain/ModelTrainer'; import { Unsubscriber, Writable, derived, get, writable } from 'svelte/store'; import KNNModelGraphDrawer, { GraphDrawConfig } from './KNNModelGraphDrawer'; -import { MicrobitAccelerometerData } from '../../../script/livedata/MicrobitAccelerometerData'; +import { MicrobitAccelerometerData, MicrobitAccelerometerDataVector } from '../../../script/livedata/MicrobitAccelerometerData'; import { TimestampedData } from '../../../script/domain/LiveDataBuffer'; import Axes from '../../../script/domain/Axes'; import Filters from '../../../script/domain/Filters'; import { Point3D } from '../../../script/utils/graphUtils'; import StaticConfiguration from '../../../StaticConfiguration'; import { stores } from '../../../script/stores/Stores'; +import { LiveDataVector } from '../../../script/domain/stores/LiveDataVector'; type SampleData = { value: number[]; @@ -21,7 +22,7 @@ type SampleData = { type UpdateCall = { config: GraphDrawConfig; - data: TimestampedData[]; + data: TimestampedData[]; }; class KNNModelGraphController { @@ -37,7 +38,7 @@ class KNNModelGraphController { private redrawTrainingData = false; // Only draw training data when rotation/scale/origin changes private unsubscriber; private liveDataRecordsSize = 3; - private liveDataRecords: TimestampedData[][] = []; // Used to 'smoothe' live data point. Expected to contain a few points(liveDataRecordsSize), and points are replaced at each update + private liveDataRecords: TimestampedData[][] = []; // Used to 'smoothe' live data point. Expected to contain a few points(liveDataRecordsSize), and points are replaced at each update public constructor( svg: d3.Selection, @@ -111,17 +112,30 @@ class KNNModelGraphController { } private getControllerData() { + const classifier = stores.getClassifier(); const xRot = get(this.rotationX); const yRot = get(this.rotationY); const zRot = get(this.rotationZ); const scale = get(this.scale); const origin = get(this.origin); - let liveData: TimestampedData[] = []; + let liveData: TimestampedData[] = []; try { const sampleDuration = StaticConfiguration.pollingPredictionSampleDuration; const sampleSize = StaticConfiguration.pollingPredictionSampleSize; - liveData = stores.getLiveData().getBuffer().getSeries(sampleDuration, sampleSize); + liveData = stores.getLiveData().getBuffer().getSeries(sampleDuration, sampleSize).map(el => { + if (el.value.getSize() != 3) { + throw new Error("Couldn't convert vector to accelerometer data vector") + } + return { + ...el, + value: new MicrobitAccelerometerDataVector({ + x: el.value.getVector()[0], + y: el.value.getVector()[1], + z: el.value.getVector()[2] + }) + } + }); this.liveDataRecords.push(liveData); if (this.liveDataRecords.length > this.liveDataRecordsSize) { this.liveDataRecords.shift(); @@ -143,7 +157,7 @@ class KNNModelGraphController { }; } - private calculateLiveDataRecordsAverage(): TimestampedData[] { + private calculateLiveDataRecordsAverage(): TimestampedData[] { const noOfRecords = this.liveDataRecords.length; const vals = this.liveDataRecords.map(e => e.map(e => e.value)); const samples1 = vals[0]; @@ -159,7 +173,7 @@ class KNNModelGraphController { return combined.map(e => ({ timestamp: 0, // ignored - value: e, + value: new MicrobitAccelerometerDataVector(e), })); } @@ -178,28 +192,28 @@ class KNNModelGraphController { }; } - private sumAccelData(data: MicrobitAccelerometerData[]): MicrobitAccelerometerData { + private sumAccelData(data: MicrobitAccelerometerDataVector[]): MicrobitAccelerometerData { const sum = (nums: number[]): number => nums.reduce((pre, cur) => cur + pre, 0); return { - x: sum(data.map(e => e.x)), - y: sum(data.map(e => e.y)), - z: sum(data.map(e => e.z)), + x: sum(data.map(e => e.getVector()[0])), + y: sum(data.map(e => e.getVector()[1])), + z: sum(data.map(e => e.getVector()[2])), }; } // Called whenever any subscribed store is altered private onUpdate(draw: UpdateCall, axis?: Axes) { - let data: TimestampedData[] = draw.data; + let data: TimestampedData[] = draw.data; const getLiveFilteredData = () => { switch (axis) { case Axes.X: - return this.filters.compute(data.map(d => d.value.x)); + return this.filters.compute(data.map(d => d.value.getAccelerometerData().x)); case Axes.Y: - return this.filters.compute(data.map(d => d.value.y)); + return this.filters.compute(data.map(d => d.value.getAccelerometerData().y)); case Axes.Z: - return this.filters.compute(data.map(d => d.value.z)); + return this.filters.compute(data.map(d => d.value.getAccelerometerData().z)); default: throw new Error("Shouldn't happen"); } diff --git a/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte b/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte index 8e83e2745..bc7be5d7f 100644 --- a/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte +++ b/src/components/playground/inputSynthesizer/NoOfAxesCounter.svelte @@ -1,3 +1,8 @@ + {#if showhighlit} {#if $highlightedAxis === Axes.X} - {/if} {#if $highlightedAxis === Axes.Y} diff --git a/src/components/graphs/knngraph/KnnModelGraph.svelte b/src/components/graphs/knngraph/KnnModelGraph.svelte index 997ac206f..1fad9b51c 100644 --- a/src/components/graphs/knngraph/KnnModelGraph.svelte +++ b/src/components/graphs/knngraph/KnnModelGraph.svelte @@ -106,7 +106,6 @@ width={650} classID={'d3-3d-single'} {controller} /> -
diff --git a/src/components/playground/LiveDataBufferUtilizationPercentage.svelte b/src/components/playground/LiveDataBufferUtilizationPercentage.svelte index 3d7738435..21cba9f05 100644 --- a/src/components/playground/LiveDataBufferUtilizationPercentage.svelte +++ b/src/components/playground/LiveDataBufferUtilizationPercentage.svelte @@ -7,8 +7,7 @@ diff --git a/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts b/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts index 26fbd9300..aa60b4677 100644 --- a/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts +++ b/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts @@ -36,7 +36,7 @@ class LiveDataSynthesizer implements Readable { noOfAxes: 1 } as LiveDataSynthesizerOptions); stores.setLiveData(new SyntheticLiveData([letters[0]])) - this.referenceStoreGetter = () => stores.getLiveData() as SyntheticLiveData + this.referenceStoreGetter = () => get(stores).liveData as SyntheticLiveData } public subscribe( diff --git a/src/pages/training/TrainModelButton.svelte b/src/pages/training/TrainModelButton.svelte index a7b974051..895031464 100644 --- a/src/pages/training/TrainModelButton.svelte +++ b/src/pages/training/TrainModelButton.svelte @@ -41,6 +41,7 @@ const getModelTrainer = (modelEntry: ModelEntry): ModelTrainer => { if (modelEntry.id === 'KNN') { if ($highlightedAxis === undefined) { + Logger.log('TrainModelButton', 'training knn, setting highlighted axis to X'); highlightedAxis.set(Axes.X); } const noOfRecordings = gestures diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index 4e3b76400..445973bb9 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -152,7 +152,7 @@ class InputBehaviour extends LoggingDecorator { const accelY = y / 1000.0; const accelZ = z / 1000.0; - stores.getLiveData().put(new MicrobitAccelerometerDataVector({ + get(stores).liveData.put(new MicrobitAccelerometerDataVector({ x: accelX, y: accelY, z: accelZ, diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index ebc063462..608ffd975 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -47,12 +47,13 @@ class Stores implements Readable{ }).subscribe(run, invalidate); } - public getLiveData(): LiveData { + /*public getLiveData(): LiveData { + Logger.log("stores", "getting live data, maybe use $stores.liveData instead") if (!get(this.liveData)) { throw new Error("Cannot get liveData store. You must initialize it using setLiveData(...)") } return get(this.liveData)!; - } + }*/ public setLiveData>(liveDataStore: T): T { Logger.log("stores", "setting live data") @@ -60,7 +61,7 @@ class Stores implements Readable{ throw new Error("Cannot set live data store to undefined/null"); } this.liveData.set(liveDataStore); - this.engine = new PollingPredictorEngine(this.classifier, this.getLiveData()); + this.engine = new PollingPredictorEngine(this.classifier, liveDataStore); return get(this.liveData) as T; } From 16c528c3a3cfea6ca38b97084d3b8598a515c281 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 21:21:55 +0200 Subject: [PATCH 010/106] Clarified import --- prepEnv.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/prepEnv.js b/prepEnv.js index 2bffca672..4cac3f219 100644 --- a/prepEnv.js +++ b/prepEnv.js @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ // This script is used to setup different build configurations // run by ` node prepEnv.js branded ` for ml-machine-branded config -import { copyFile } from 'node:fs/promises'; +import { copyFile } from 'fs'; // Validate input const args = process.argv; @@ -45,10 +45,11 @@ const copyFiles = fileMoveTargets[buildVariantTarget]; copyFiles.forEach(element => { const source = element[0]; const destination = element[1]; - copyFile(source, destination).then(() => { + copyFile(source, destination, (err) => { console.log("Copied ", element[0], " -> ", element[1]) - }).catch((err) => { - console.error("Failed to move ", source, " to ", destination) - throw new Error(err) + if (err) { + console.error("Failed to move ", source, " to ", destination) + throw new Error(err) + } }) }); \ No newline at end of file From c40354b86291d1db04e29cc5d581c9a7ab3385c2 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 21:24:23 +0200 Subject: [PATCH 011/106] Add whitespace --- prepEnv.js | 1 + 1 file changed, 1 insertion(+) diff --git a/prepEnv.js b/prepEnv.js index 4cac3f219..6270ee897 100644 --- a/prepEnv.js +++ b/prepEnv.js @@ -31,6 +31,7 @@ const fileMoveTargets = { ['./src/__viteBuildVariants__/ml-machine-simple/features.json', './features.json'] ] } + const availableTargets = Object.getOwnPropertyNames(fileMoveTargets); const buildVariantTarget = args[2]; if (!availableTargets.includes(buildVariantTarget)) { From eafb18d682d923921d852b14897a117849700cb7 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 21:37:30 +0200 Subject: [PATCH 012/106] Whitespace --- prepEnv.js | 1 - 1 file changed, 1 deletion(-) diff --git a/prepEnv.js b/prepEnv.js index 6270ee897..15b1f0fa4 100644 --- a/prepEnv.js +++ b/prepEnv.js @@ -42,7 +42,6 @@ if (!availableTargets.includes(buildVariantTarget)) { // The actual work const copyFiles = fileMoveTargets[buildVariantTarget]; - copyFiles.forEach(element => { const source = element[0]; const destination = element[1]; From c5c196ca7525bf30cdc7e8cbf62931f06f0616d9 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 21:46:01 +0200 Subject: [PATCH 013/106] Whitespace --- prepEnv.js | 1 + 1 file changed, 1 insertion(+) diff --git a/prepEnv.js b/prepEnv.js index 15b1f0fa4..6270ee897 100644 --- a/prepEnv.js +++ b/prepEnv.js @@ -42,6 +42,7 @@ if (!availableTargets.includes(buildVariantTarget)) { // The actual work const copyFiles = fileMoveTargets[buildVariantTarget]; + copyFiles.forEach(element => { const source = element[0]; const destination = element[1]; From fb8b4b76b1930d65c48adf43874d0cfcd6e314f5 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 21:49:16 +0200 Subject: [PATCH 014/106] Fix classifier test --- src/__tests__/classifier.test.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/__tests__/classifier.test.ts b/src/__tests__/classifier.test.ts index bc8cd65dc..d341ac81b 100644 --- a/src/__tests__/classifier.test.ts +++ b/src/__tests__/classifier.test.ts @@ -7,37 +7,37 @@ * SPDX-License-Identifier: MIT */ -import { classifier, gestures } from '../script/stores/availableStores'; +import { stores } from '../script/stores/Stores'; import TestMLModelTrainer from './mocks/mlmodel/TestMLModelTrainer'; describe('Classifier tests', () => { test('Changing matrix does not mark model as untrained', async () => { - const gesture = gestures.createGesture('some gesture'); - gestures.createGesture('some gesture2'); - await classifier.getModel().train(new TestMLModelTrainer(2)); + const gesture = stores.getGestures().createGesture('some gesture'); + stores.getGestures().createGesture('some gesture2'); + await stores.getClassifier().getModel().train(new TestMLModelTrainer(2)); gesture.setLEDOutput(new Array(25).fill(false) as boolean[]); - expect(classifier.getModel().isTrained()).toBe(true); + expect(stores.getClassifier().getModel().isTrained()).toBe(true); }); test('Adding gesture marks model as untrained', async () => { - gestures.createGesture('some gesture'); - gestures.createGesture('some gesture2'); - await classifier.getModel().train(new TestMLModelTrainer(2)); + stores.getGestures().createGesture('some gesture'); + stores.getGestures().createGesture('some gesture2'); + await stores.getClassifier().getModel().train(new TestMLModelTrainer(2)); - gestures.createGesture('Added gesture'); + stores.getGestures().createGesture('Added gesture'); - expect(classifier.getModel().isTrained()).toBe(false); + expect(stores.getClassifier().getModel().isTrained()).toBe(false); }); test('Removing gesture marks model as untrained', async () => { - gestures.createGesture('some gesture'); - gestures.createGesture('some gesture2'); - const gesture3 = gestures.createGesture('some gesture2'); - await classifier.getModel().train(new TestMLModelTrainer(2)); + stores.getGestures().createGesture('some gesture'); + stores.getGestures().createGesture('some gesture2'); + const gesture3 = stores.getGestures().createGesture('some gesture2'); + await stores.getClassifier().getModel().train(new TestMLModelTrainer(2)); - gestures.removeGesture(gesture3.getId()); + stores.getGestures().removeGesture(gesture3.getId()); - expect(classifier.getModel().isTrained()).toBe(false); + expect(stores.getClassifier().getModel().isTrained()).toBe(false); }); }); From d4df57a12717be629b0761852ba17bb83dbe42d3 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 22:05:27 +0200 Subject: [PATCH 015/106] whitespace --- prepEnv.js | 1 - 1 file changed, 1 deletion(-) diff --git a/prepEnv.js b/prepEnv.js index 6270ee897..15b1f0fa4 100644 --- a/prepEnv.js +++ b/prepEnv.js @@ -42,7 +42,6 @@ if (!availableTargets.includes(buildVariantTarget)) { // The actual work const copyFiles = fileMoveTargets[buildVariantTarget]; - copyFiles.forEach(element => { const source = element[0]; const destination = element[1]; From d80f1a0ff91880262dac77d3c3b64ce57db02074 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 22:12:51 +0200 Subject: [PATCH 016/106] Added test message --- src/App.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App.svelte b/src/App.svelte index 6c5e4f6a6..974cc6560 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -42,6 +42,7 @@ import { welcomeLog } from './script/utils/Logger'; welcomeLog(); + console.log('test'); ConnectionBehaviours.setInputBehaviour(new InputBehaviour()); ConnectionBehaviours.setOutputBehaviour(new OutputBehaviour()); From ac494759b18204de8a201ad803defb3a998393bf Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 22:16:36 +0200 Subject: [PATCH 017/106] Removed test message --- src/App.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/App.svelte b/src/App.svelte index 974cc6560..6c5e4f6a6 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -42,7 +42,6 @@ import { welcomeLog } from './script/utils/Logger'; welcomeLog(); - console.log('test'); ConnectionBehaviours.setInputBehaviour(new InputBehaviour()); ConnectionBehaviours.setOutputBehaviour(new OutputBehaviour()); From 15a812247baceefa5ab79817c9436dc274339075 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 22:26:55 +0200 Subject: [PATCH 018/106] Enable development message in prod --- src/script/utils/Logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/utils/Logger.ts b/src/script/utils/Logger.ts index 4b473a26e..d9a863dcb 100644 --- a/src/script/utils/Logger.ts +++ b/src/script/utils/Logger.ts @@ -29,7 +29,7 @@ class Logger { } } export const welcomeLog = () => { - if (!Environment.isInDevelopment || (window as typeof window & { hasLogged: boolean }).hasLogged) { + if (Environment.isInDevelopment || (window as typeof window & { hasLogged: boolean }).hasLogged) { return } console.log(`⚙️ Development Mode : From dee0fff980ab1222371070860fb22d796973face Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 22:34:34 +0200 Subject: [PATCH 019/106] Reverted logging --- src/script/utils/Logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/utils/Logger.ts b/src/script/utils/Logger.ts index d9a863dcb..4b473a26e 100644 --- a/src/script/utils/Logger.ts +++ b/src/script/utils/Logger.ts @@ -29,7 +29,7 @@ class Logger { } } export const welcomeLog = () => { - if (Environment.isInDevelopment || (window as typeof window & { hasLogged: boolean }).hasLogged) { + if (!Environment.isInDevelopment || (window as typeof window & { hasLogged: boolean }).hasLogged) { return } console.log(`⚙️ Development Mode : From 8bba1259045976b9c6aaf617e35b3052dc89f951 Mon Sep 17 00:00:00 2001 From: r59q Date: Fri, 10 May 2024 22:38:25 +0200 Subject: [PATCH 020/106] Whitespace --- src/script/utils/Logger.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/script/utils/Logger.ts b/src/script/utils/Logger.ts index 4b473a26e..ae2c59d7d 100644 --- a/src/script/utils/Logger.ts +++ b/src/script/utils/Logger.ts @@ -7,7 +7,8 @@ import Environment from '../Environment'; class Logger { - constructor(private origin: any) { } + constructor(private origin: any) { + } public log(message: any, ...params: any[]) { Logger.log(this.origin, message, params); From 418c79a7d289cb92809b9d0887e4ae5aef4e5b00 Mon Sep 17 00:00:00 2001 From: r59q Date: Sat, 11 May 2024 00:15:37 +0200 Subject: [PATCH 021/106] Add workflow for deployment site --- .github/workflows/prcomment.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/prcomment.yml diff --git a/.github/workflows/prcomment.yml b/.github/workflows/prcomment.yml new file mode 100644 index 000000000..6278be14e --- /dev/null +++ b/.github/workflows/prcomment.yml @@ -0,0 +1,17 @@ +on: + pull_request: + types: [opened, reopened] + +jobs: + comment: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'A deployment is being made to https://ml-machine-${{github.event.pull_request.number}}.r59q.com/. This site will be continuously updated as changes are made to this PR.' + }) \ No newline at end of file From 3c56b7618e0105457a607963df6c905ca8d68487 Mon Sep 17 00:00:00 2001 From: r59q Date: Sat, 11 May 2024 01:32:32 +0200 Subject: [PATCH 022/106] Improve consistency for microbits object --- src/script/microbit-interfacing/Microbits.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/script/microbit-interfacing/Microbits.ts b/src/script/microbit-interfacing/Microbits.ts index 1adbb262d..48f333bc4 100644 --- a/src/script/microbit-interfacing/Microbits.ts +++ b/src/script/microbit-interfacing/Microbits.ts @@ -31,7 +31,7 @@ export enum HexOrigin { type UARTMessageType = 'g' | 's'; /** - * Entry point for microbit interfaces / Facade pattern + * Entry point for microbit interfaces */ class Microbits { public static hexFiles: { 1: string; 2: string; universal: string } = { @@ -39,13 +39,13 @@ class Microbits { 2: 'firmware/MICROBIT.hex', universal: 'firmware/universal-hex.hex', }; - private static assignedInputMicrobit: MicrobitBluetooth | undefined = undefined; - private static assignedOutputMicrobit: MicrobitBluetooth | undefined = undefined; - private static inputName: string | undefined = undefined; - private static outputName: string | undefined = undefined; + private static assignedInputMicrobit: MicrobitBluetooth | undefined; + private static assignedOutputMicrobit: MicrobitBluetooth | undefined; + private static inputName: string | undefined; + private static outputName: string | undefined; private static inputVersion: MBSpecs.MBVersion | undefined; private static outputVersion: MBSpecs.MBVersion | undefined; - private static linkedMicrobit: MicrobitUSB | undefined = undefined; + private static linkedMicrobit: MicrobitUSB | undefined; private static outputIO: BluetoothRemoteGATTCharacteristic | undefined; private static outputMatrix: BluetoothRemoteGATTCharacteristic | undefined; From aa8c9db4a2760bf883f6360158a5497d07555a34 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 13 May 2024 20:33:24 +0200 Subject: [PATCH 023/106] Using key on LiveGraph --- .../graphs/MicrobitLiveGraph.svelte | 24 ++++--------------- src/script/microbit-interfacing/Microbits.ts | 10 ++++---- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/components/graphs/MicrobitLiveGraph.svelte b/src/components/graphs/MicrobitLiveGraph.svelte index c879bb67f..4873e12d8 100644 --- a/src/components/graphs/MicrobitLiveGraph.svelte +++ b/src/components/graphs/MicrobitLiveGraph.svelte @@ -17,33 +17,19 @@ export let width: number; $: showhighlit = hasFeature(Feature.KNN_MODEL) && $highlightedAxis !== undefined; console.log(hasFeature(Feature.KNN_MODEL) && $highlightedAxis !== undefined); + $: highlightedVectorIndex = + $highlightedAxis === Axes.X ? 0 : $highlightedAxis === Axes.Y ? 1 : 2; {#if showhighlit} - {#if $highlightedAxis === Axes.X} + {#key highlightedVectorIndex} - {/if} - {#if $highlightedAxis === Axes.Y} - - {/if} - {#if $highlightedAxis === Axes.Z} - - {/if} + {/key} {:else} Date: Mon, 13 May 2024 21:06:33 +0200 Subject: [PATCH 024/106] Remove Highlighted LiveGraph component --- .../graphs/LiveGraphHighlighted.svelte | 198 ------------------ .../graphs/MicrobitLiveGraph.svelte | 1 - 2 files changed, 199 deletions(-) delete mode 100644 src/components/graphs/LiveGraphHighlighted.svelte diff --git a/src/components/graphs/LiveGraphHighlighted.svelte b/src/components/graphs/LiveGraphHighlighted.svelte deleted file mode 100644 index 0f259efe8..000000000 --- a/src/components/graphs/LiveGraphHighlighted.svelte +++ /dev/null @@ -1,198 +0,0 @@ - - - - -
- - {#key cnt} -
diff --git a/src/components/graphs/MicrobitLiveGraph.svelte b/src/components/graphs/MicrobitLiveGraph.svelte index 4873e12d8..e6832c49e 100644 --- a/src/components/graphs/MicrobitLiveGraph.svelte +++ b/src/components/graphs/MicrobitLiveGraph.svelte @@ -11,7 +11,6 @@ import { stores } from '../../script/stores/Stores'; import { highlightedAxis } from '../../script/stores/uiStore'; import LiveGraph from './LiveGraph.svelte'; - import LiveGraphHighlighted from './LiveGraphHighlighted.svelte'; //axis={Axes.X} export let width: number; From de2b61b820af3da050c2666b4653fefba516746f Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 13 May 2024 21:58:25 +0200 Subject: [PATCH 025/106] Fix flickering confidences --- src/__tests__/engine.test.ts | 27 +++++++++++++++++++ src/components/graphs/LiveGraph.svelte | 1 - .../knngraph/AxesFilterVectorView.svelte | 10 ++++--- .../knngraph/KNNModelGraphController.ts | 2 +- .../graphs/knngraph/KnnModelGraph.svelte | 4 ++- src/menus/ModelMenu.svelte | 1 + src/pages/filter/D3Plot.svelte | 6 ++--- .../livedata/MicrobitAccelerometerData.ts | 18 +++++++++++++ src/script/stores/Stores.ts | 13 ++++----- 9 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 src/__tests__/engine.test.ts diff --git a/src/__tests__/engine.test.ts b/src/__tests__/engine.test.ts new file mode 100644 index 000000000..a28f0fdec --- /dev/null +++ b/src/__tests__/engine.test.ts @@ -0,0 +1,27 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { get } from "svelte/store"; +import LiveDataBuffer from "../script/domain/LiveDataBuffer"; +import MicrobitAccelerometerLiveData from "../script/livedata/MicrobitAccelerometerData"; +import { stores } from "../script/stores/Stores"; + +describe('Engine behaviour test', () => { + test('Engine should stop predicting when the LiveData store is set', () => { + const liveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) + stores.setLiveData(liveData) + const engine = stores.getEngine() + expect(get(engine).isRunning).toBe(true); + const newLiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) + stores.setLiveData(newLiveData) + expect(get(engine).isRunning).toBe(false); + expect(get(stores.getEngine()).isRunning).toBe(true) + }); +}); + diff --git a/src/components/graphs/LiveGraph.svelte b/src/components/graphs/LiveGraph.svelte index fd0791d7f..af49f92c7 100644 --- a/src/components/graphs/LiveGraph.svelte +++ b/src/components/graphs/LiveGraph.svelte @@ -79,7 +79,6 @@ } } const color = axisColors[index] + (opaque ? 'ff' : '30'); - console.log(color); chart!.addTimeSeries(line, { lineWidth, strokeStyle: color, diff --git a/src/components/graphs/knngraph/AxesFilterVectorView.svelte b/src/components/graphs/knngraph/AxesFilterVectorView.svelte index 074a97d05..b1dc4002a 100644 --- a/src/components/graphs/knngraph/AxesFilterVectorView.svelte +++ b/src/components/graphs/knngraph/AxesFilterVectorView.svelte @@ -15,9 +15,12 @@ import { onMount } from 'svelte'; import { vectorArrows } from './AxesFilterVector'; import { stores } from '../../../script/stores/Stores'; + import { asAccelerometerData } from '../../../script/livedata/MicrobitAccelerometerData'; const classifier = stores.getClassifier(); + $: liveData = $stores.liveData; + const drawArrows = (fromId: string) => { get(vectorArrows).forEach(arr => arr.clear()); const from = document.getElementById(fromId)!; @@ -68,14 +71,15 @@ return Array(classifier.getFilters().count()).fill(0); } try { - const seriesTimestamped = stores - .getLiveData() + const seriesTimestamped = liveData .getBuffer() .getSeries( StaticConfiguration.pollingPredictionSampleDuration, StaticConfiguration.pollingPredictionSampleSize, ); - const series = seriesTimestamped.map(s => s.value); + const series = seriesTimestamped.map(s => + asAccelerometerData(s.value).getAccelerometerData(), + ); const filteredSeries = stores .getClassifier() .getFilters() diff --git a/src/components/graphs/knngraph/KNNModelGraphController.ts b/src/components/graphs/knngraph/KNNModelGraphController.ts index e57f0240c..ba12f87eb 100644 --- a/src/components/graphs/knngraph/KNNModelGraphController.ts +++ b/src/components/graphs/knngraph/KNNModelGraphController.ts @@ -123,7 +123,7 @@ class KNNModelGraphController { try { const sampleDuration = StaticConfiguration.pollingPredictionSampleDuration; const sampleSize = StaticConfiguration.pollingPredictionSampleSize; - liveData = stores.getLiveData().getBuffer().getSeries(sampleDuration, sampleSize).map(el => { + liveData = get(stores).liveData.getBuffer().getSeries(sampleDuration, sampleSize).map(el => { if (el.value.getSize() != 3) { throw new Error("Couldn't convert vector to accelerometer data vector") } diff --git a/src/components/graphs/knngraph/KnnModelGraph.svelte b/src/components/graphs/knngraph/KnnModelGraph.svelte index 1fad9b51c..4c051f087 100644 --- a/src/components/graphs/knngraph/KnnModelGraph.svelte +++ b/src/components/graphs/knngraph/KnnModelGraph.svelte @@ -93,7 +93,9 @@

{gesture.name}

{#if $state.isInputReady} -

{($confidences.get(gesture.ID).currentConfidence * 100).toFixed(2)}%

+

+ {(($confidences.get(gesture.ID)?.currentConfidence ?? 0) * 100).toFixed(2)}% +

{/if} {/each} diff --git a/src/menus/ModelMenu.svelte b/src/menus/ModelMenu.svelte index bb2b275d7..a3178a6b6 100644 --- a/src/menus/ModelMenu.svelte +++ b/src/menus/ModelMenu.svelte @@ -10,6 +10,7 @@ import Gesture from '../script/domain/stores/gesture/Gesture'; import { stores } from '../script/stores/Stores'; + const gestures = stores.getGestures(); const bestPrediction = gestures.getBestPrediction(); $: confidence = diff --git a/src/pages/filter/D3Plot.svelte b/src/pages/filter/D3Plot.svelte index e771719d4..722b8cea0 100644 --- a/src/pages/filter/D3Plot.svelte +++ b/src/pages/filter/D3Plot.svelte @@ -20,6 +20,7 @@ export let fullScreen: boolean = false; $: showLive = $state.isInputConnected; + $: liveData = $stores.liveData; type RecordingRepresentation = { ID: number; @@ -141,8 +142,7 @@ } function createLiveData() { - const liveD = stores - .getLiveData() + const liveD = liveData .getBuffer() .getSeries( StaticConfiguration.recordingDuration, @@ -154,7 +154,7 @@ const ys = liveD.map(d => d!.getVector()[1]); const zs = liveD.map(d => d!.getVector()[2]); - if (stores.getLiveData() === undefined) return undefined; + if (liveData === undefined) return undefined; const filteredData: RecordingRepresentation = { ID: uniqueLiveDataID, gestureClassName: 'live', diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts index 944c7df4f..3e1d39449 100644 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ b/src/script/livedata/MicrobitAccelerometerData.ts @@ -14,6 +14,24 @@ export type MicrobitAccelerometerData = { z: number; }; +export const asAccelerometerData = (input: LiveDataVector) => { + if (input.getSize() != 3) { + throw new Error("Cannot cast input as accelerometer data, size is not 3") + } + const data = new MicrobitAccelerometerDataVector({ + x: input.getVector()[0], + y: input.getVector()[1], + z: input.getVector()[2] + }) + + input.getLabels().forEach((label, index) => { + if (data.getLabels()[index] !== label) { + throw new Error("Cannot cast input as accelerometer data, labels do not match") + } + }) + return data; +} + export class MicrobitAccelerometerDataVector implements LiveDataVector { public constructor(private data: MicrobitAccelerometerData) { diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index 608ffd975..709821490 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -47,20 +47,17 @@ class Stores implements Readable{ }).subscribe(run, invalidate); } - /*public getLiveData(): LiveData { - Logger.log("stores", "getting live data, maybe use $stores.liveData instead") - if (!get(this.liveData)) { - throw new Error("Cannot get liveData store. You must initialize it using setLiveData(...)") - } - return get(this.liveData)!; - }*/ - public setLiveData>(liveDataStore: T): T { Logger.log("stores", "setting live data") if (!liveDataStore) { throw new Error("Cannot set live data store to undefined/null"); } this.liveData.set(liveDataStore); + + // We stop the previous engine from making predictions + if (this.engine) { + this.engine.stop(); + } this.engine = new PollingPredictorEngine(this.classifier, liveDataStore); return get(this.liveData) as T; } From 0599d6e90179754457956f7d29b5bd63b47524be Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 14 May 2024 20:53:19 +0200 Subject: [PATCH 026/106] Fix issue where switching models would cause memory leaks --- .../graphs/knngraph/AxesFilterVectorView.svelte | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/graphs/knngraph/AxesFilterVectorView.svelte b/src/components/graphs/knngraph/AxesFilterVectorView.svelte index b1dc4002a..edebd8098 100644 --- a/src/components/graphs/knngraph/AxesFilterVectorView.svelte +++ b/src/components/graphs/knngraph/AxesFilterVectorView.svelte @@ -5,7 +5,7 @@ --> From 392498825d52b27d9b08d376d15634551d04ab0d Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 14 May 2024 21:02:41 +0200 Subject: [PATCH 028/106] Remove log line --- src/pages/training/TrainModelButton.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/training/TrainModelButton.svelte b/src/pages/training/TrainModelButton.svelte index b50611a21..7ce39e475 100644 --- a/src/pages/training/TrainModelButton.svelte +++ b/src/pages/training/TrainModelButton.svelte @@ -125,7 +125,6 @@ onMount(() => { const unsubscribe = highlightedAxis.subscribe(axis => { - Logger.log('TrainModelButton', 'Highlighted axis changed', axis); if (!axis) { return; } From 1dc8bc1f74771447ecb6283b5a8deef804eed3f1 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 22 May 2024 19:38:41 +0200 Subject: [PATCH 029/106] Stabilize switching between models --- src/components/graphs/LossGraph.svelte | 2 +- src/pages/training/TrainModelButton.svelte | 82 +++++----------------- src/pages/training/TrainModelButton.ts | 76 ++++++++++++++++++++ src/pages/training/TrainingPage.svelte | 2 +- 4 files changed, 97 insertions(+), 65 deletions(-) create mode 100644 src/pages/training/TrainModelButton.ts diff --git a/src/components/graphs/LossGraph.svelte b/src/components/graphs/LossGraph.svelte index ad6da1abe..1ea8998d3 100644 --- a/src/components/graphs/LossGraph.svelte +++ b/src/components/graphs/LossGraph.svelte @@ -71,7 +71,7 @@ }, }, y: { - type: 'logarithmic', + type: 'linear', min: 0, max: 1, grid: { diff --git a/src/pages/training/TrainModelButton.svelte b/src/pages/training/TrainModelButton.svelte index 7ce39e475..33b25ea3b 100644 --- a/src/pages/training/TrainModelButton.svelte +++ b/src/pages/training/TrainModelButton.svelte @@ -18,7 +18,6 @@ import Filters from '../../script/domain/Filters'; import { Writable } from 'svelte/store'; - export let selectedOption: Writable; import { LossTrainingIteration } from '../../components/graphs/LossGraphUtil'; import { ModelEntry, @@ -26,52 +25,17 @@ highlightedAxis, prevHighlightedAxis, } from '../../script/stores/uiStore'; - import Axes from '../../script/domain/Axes'; - import Logger from '../../script/utils/Logger'; - import { extractAxisFromTrainingData } from '../../script/utils/graphUtils'; - import KNNNonNormalizedModelTrainer from '../../script/mlmodels/KNNNonNormalizedModelTrainer'; import { stores } from '../../script/stores/Stores'; import { onMount } from 'svelte'; + import { getModelTrainer, options, trainModel } from './TrainModelButton'; + import Axes from '../../script/domain/Axes'; export let onTrainingIteration: (iteration: LossTrainingIteration) => void; export let onClick: () => void; - const gestures = stores.getGestures(); - const classifier = stores.getClassifier(); - - const getModelTrainer = (modelEntry: ModelEntry): ModelTrainer => { - if (modelEntry.id === 'KNN') { - if ($highlightedAxis === undefined) { - Logger.log('TrainModelButton', 'training knn, setting highlighted axis to X'); - highlightedAxis.set(Axes.X); - } - const noOfRecordings = gestures - .getGestures() - .map(gesture => gesture.getRecordings().length) - .reduce((prev, cur) => cur + prev, 0); - - if (noOfRecordings / 2 < StaticConfiguration.knnNeighbourCount) { - Logger.log( - 'TrainModelButton', - 'The number of recordings is probably too low for an effective KNN model if using ' + - StaticConfiguration.knnNeighbourCount + - ' neighbours ', - ); - } - - const offset = - $highlightedAxis === Axes.X ? 0 : $highlightedAxis === Axes.Y ? 1 : 2; - - return new KNNNonNormalizedModelTrainer( - StaticConfiguration.knnNeighbourCount, - data => extractAxisFromTrainingData(data, offset, 3), - ); - } + export let selectedOption: Writable; - return new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, h => { - onTrainingIteration(h); - }); - }; + const classifier = stores.getClassifier(); const model = classifier.getModel(); @@ -94,34 +58,24 @@ }; const clickHandler = () => { - const selectedModel = availableModels.find(model => model.id === $selectedOption.id); - - if (selectedModel?.id === 'KNN') { - // TODO: We set the filters to 2 different filters giving us a 2d graph - const knnFilters = [FilterType.MAX, FilterType.MEAN]; - const filters: Filters = classifier.getFilters(); - filters.clear(); - for (const filter of knnFilters) { - filters.add(filter); - } - } - - if (selectedModel) { - onClick(); - model.train(getModelTrainer(selectedModel)); - } + stores.getEngine().stop(); + trainModel(selectedOption, onTrainingIteration); + stores.getEngine().start(); + onClick(); }; const onSelect = (option: DropdownOption) => { selectedOption.set(option); }; - const options: DropdownOption[] = availableModels.map(model => { - return { - id: model.id, - label: model.title, - }; - }); + $: { + if ($selectedOption.id === 'KNN' && !$highlightedAxis) { + highlightedAxis.set(Axes.X); + } + if ($selectedOption.id === 'NN' && $highlightedAxis) { + highlightedAxis.set(undefined); + } + } onMount(() => { const unsubscribe = highlightedAxis.subscribe(axis => { @@ -132,7 +86,9 @@ return; } if ($selectedOption.id === 'KNN') { - model.train(getModelTrainer(getModelFromOption($selectedOption))); + model.train( + getModelTrainer(getModelFromOption($selectedOption), onTrainingIteration), + ); } prevHighlightedAxis.set(axis); }); diff --git a/src/pages/training/TrainModelButton.ts b/src/pages/training/TrainModelButton.ts new file mode 100644 index 000000000..3b53060df --- /dev/null +++ b/src/pages/training/TrainModelButton.ts @@ -0,0 +1,76 @@ +import { Writable, get } from "svelte/store"; +import { DropdownOption } from "../../components/buttons/Buttons"; +import { ModelEntry, availableModels, highlightedAxis } from "../../script/stores/uiStore"; +import ModelTrainer from "../../script/domain/ModelTrainer"; +import MLModel from "../../script/domain/MLModel"; +import Logger from "../../script/utils/Logger"; +import Axes from "../../script/domain/Axes"; +import { stores } from "../../script/stores/Stores"; +import StaticConfiguration from "../../StaticConfiguration"; +import KNNNonNormalizedModelTrainer from "../../script/mlmodels/KNNNonNormalizedModelTrainer"; +import { extractAxisFromTrainingData } from "../../script/utils/graphUtils"; +import LayersModelTrainer from "../../script/mlmodels/LayersModelTrainer"; +import { LossTrainingIteration } from "../../components/graphs/LossGraphUtil"; +import { FilterType } from "../../script/domain/FilterTypes"; +import Filters from "../../script/domain/Filters"; + +const gestures = stores.getGestures(); +const classifier = stores.getClassifier(); + +export const options: DropdownOption[] = availableModels.map(model => { + return { + id: model.id, + label: model.title, + }; +}); + +export const getModelTrainer = (modelEntry: ModelEntry, onTrainingIteration: (iteration: LossTrainingIteration) => void): ModelTrainer => { + const currentAxis = get(highlightedAxis); + if (modelEntry.id === 'KNN') { + const noOfRecordings = gestures + .getGestures() + .map(gesture => gesture.getRecordings().length) + .reduce((prev, cur) => cur + prev, 0); + + if (noOfRecordings / 2 < StaticConfiguration.knnNeighbourCount) { + Logger.log( + 'TrainModelButton', + 'The number of recordings is probably too low for an effective KNN model if using ' + + StaticConfiguration.knnNeighbourCount + + ' neighbours ', + ); + } + + const offset = + currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; + + return new KNNNonNormalizedModelTrainer( + StaticConfiguration.knnNeighbourCount, + data => extractAxisFromTrainingData(data, offset, 3), + ); + } + highlightedAxis.set(undefined); + + return new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, h => { + onTrainingIteration(h); + }); +}; + +export const trainModel = (selectedOption: Writable, onTrainingIteration: (iteration: LossTrainingIteration) => void) => { + const selectedModel = availableModels.find(model => model.id === get(selectedOption).id); + const model = classifier.getModel(); + + if (selectedModel?.id === 'KNN') { + // TODO: We set the filters to 2 different filters giving us a 2d graph + const knnFilters = [FilterType.MAX, FilterType.MEAN]; + const filters: Filters = classifier.getFilters(); + filters.clear(); + for (const filter of knnFilters) { + filters.add(filter); + } + } + + if (selectedModel) { + model.train(getModelTrainer(selectedModel, onTrainingIteration)); + } +} \ No newline at end of file diff --git a/src/pages/training/TrainingPage.svelte b/src/pages/training/TrainingPage.svelte index 47cca7124..4be000353 100644 --- a/src/pages/training/TrainingPage.svelte +++ b/src/pages/training/TrainingPage.svelte @@ -110,7 +110,7 @@ {/if} {/if} - {#if !$state.isInputConnected && !isUsingKNNModel} + {#if !isUsingKNNModel}
{#if $loss.length > 0 || $model.isTraining} {#if !CookieManager.hasFeatureFlag('loss-graph')} From 4f3ee0929a17eab8efc62b52210b831b55f47aa0 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 22 May 2024 19:45:35 +0200 Subject: [PATCH 030/106] Added license identifier --- src/pages/training/TrainModelButton.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/training/TrainModelButton.ts b/src/pages/training/TrainModelButton.ts index 3b53060df..8fd2ed636 100644 --- a/src/pages/training/TrainModelButton.ts +++ b/src/pages/training/TrainModelButton.ts @@ -1,3 +1,8 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ import { Writable, get } from "svelte/store"; import { DropdownOption } from "../../components/buttons/Buttons"; import { ModelEntry, availableModels, highlightedAxis } from "../../script/stores/uiStore"; From e1ec8e6cee454c9725ed80a1abdc09a8e8dbe4c4 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 22 May 2024 20:11:40 +0200 Subject: [PATCH 031/106] Fixed pipeline --- .github/workflows/main.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9de949bdf..ff4b5bd35 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,13 +45,7 @@ jobs: shell: bash run: | npm install - npm run prettier - # if we find the string modified after running git status, it indicates that prettier has changed files! - if git status | grep -c "modified" -eq 0; then - exit 1 - else - exit 0 - fi + npm run checkFormat - name: Missing format report shell: bash if: ${{ failure() }} From 19a5e0a1d1060c1e83d78c684d6f330a081ebdac Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 22 May 2024 20:17:37 +0200 Subject: [PATCH 032/106] stop if error --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff4b5bd35..e0729d93c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,7 @@ jobs: run: | npm install npm run checkFormat + continue-on-error: false - name: Missing format report shell: bash if: ${{ failure() }} From a002be584b6bfb5f3679d6e9f780fd6217416e83 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 22 May 2024 20:20:21 +0200 Subject: [PATCH 033/106] Simplified workflow --- .github/workflows/main.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0729d93c..ac0a98424 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,22 +46,9 @@ jobs: run: | npm install npm run checkFormat - continue-on-error: false - - name: Missing format report - shell: bash - if: ${{ failure() }} - run: | - echo "Missing prettier formatting. Please format files using 'npm run prettier' and resubmit!" - exit 1 - name: Svelte check shell: bash run: npm run check - - name: Svelte check report - shell: bash - if: ${{ failure() }} - run: | - echo "Running npm run check found warnings, please resolve them before merging!" - exit 1 build_and_deploy: if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') From 18fe90b39711b6a58f484a5a02e56840ac26ae33 Mon Sep 17 00:00:00 2001 From: r59q Date: Thu, 23 May 2024 21:51:15 +0200 Subject: [PATCH 034/106] Ran prettier --- src/StaticConfiguration.ts | 11 +- src/__tests__/data-representation.test.ts | 84 ++++++----- src/__tests__/engine.test.ts | 29 ++-- src/__tests__/gestures.test.ts | 2 +- src/__tests__/testUtils.ts | 8 +- .../knngraph/KNNModelGraphController.ts | 38 +++-- .../graphs/knngraph/KNNModelGraphDrawer.ts | 5 +- .../inputSynthesizer/LiveDataSynthesizer.ts | 47 ++++--- .../inputSynthesizer/SyntheticLiveData .ts | 65 +++++---- src/pages/training/TrainModelButton.ts | 130 +++++++++-------- src/script/domain/LiveDataBuffer.ts | 2 +- src/script/domain/stores/LiveData.ts | 1 - src/script/domain/stores/LiveDataVector.ts | 10 +- src/script/domain/stores/gesture/Gesture.ts | 4 +- src/script/domain/stores/gesture/Gestures.ts | 2 +- src/script/engine/PollingPredictorEngine.ts | 4 +- src/script/livedata/BaseVector.ts | 27 ++-- .../livedata/MicrobitAccelerometerData.ts | 36 ++--- src/script/livedata/SmoothedLiveData.ts | 5 +- .../microbit-interfacing/MicrobitBluetooth.ts | 2 +- .../connection-behaviours/InputBehaviour.ts | 22 +-- src/script/repository/FileUtility.ts | 2 +- src/script/stores/Stores.ts | 131 ++++++++++-------- src/script/utils/Logger.ts | 26 ++-- 24 files changed, 394 insertions(+), 299 deletions(-) diff --git a/src/StaticConfiguration.ts b/src/StaticConfiguration.ts index 78c6c6192..cd66da880 100644 --- a/src/StaticConfiguration.ts +++ b/src/StaticConfiguration.ts @@ -58,7 +58,16 @@ class StaticConfiguration { }; // Line colors are picked in the order of this array. - public static readonly liveGraphColors = ['#f9808e', '#80f98e', '#808ef9', "#58355E", "#E0FF4F", "#FF2ECC", "#F28F3B", "#C8553D"]; + public static readonly liveGraphColors = [ + '#f9808e', + '#80f98e', + '#808ef9', + '#58355E', + '#E0FF4F', + '#FF2ECC', + '#F28F3B', + '#C8553D', + ]; // Colors to assign to gestures, useful for identifying gestures on graphs. public static readonly gestureColors = [ diff --git a/src/__tests__/data-representation.test.ts b/src/__tests__/data-representation.test.ts index 02a61a38f..f0cc4b4c1 100644 --- a/src/__tests__/data-representation.test.ts +++ b/src/__tests__/data-representation.test.ts @@ -9,7 +9,9 @@ import LiveDataBuffer from '../script/domain/LiveDataBuffer'; import LiveData from '../script/domain/stores/LiveData'; -import MicrobitAccelerometerLiveData, { MicrobitAccelerometerDataVector } from '../script/livedata/MicrobitAccelerometerData'; +import MicrobitAccelerometerLiveData, { + MicrobitAccelerometerDataVector, +} from '../script/livedata/MicrobitAccelerometerData'; import { repeat } from './testUtils'; import { get } from 'svelte/store'; import { LiveDataVector } from '../script/domain/stores/LiveDataVector'; @@ -17,47 +19,63 @@ import SmoothedLiveData from '../script/livedata/SmoothedLiveData'; import { smoothNewValue } from '../script/utils/graphUtils'; describe('Data representation tests', () => { - test('Creating accelerometer live data does not throw', () => { - expect(() => { - new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) - }).not.toThrow(); - }); + test('Creating accelerometer live data does not throw', () => { + expect(() => { + new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)); + }).not.toThrow(); + }); - test('Number of elements in buffer does not exceed set amount', () => { - const elemsInBuffer = 10; - const liveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(elemsInBuffer)) + test('Number of elements in buffer does not exceed set amount', () => { + const elemsInBuffer = 10; + const liveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(elemsInBuffer)); - repeat(() => liveData.put(new MicrobitAccelerometerDataVector({ x: 0, y: 0, z: 0 })), 20) + repeat( + () => liveData.put(new MicrobitAccelerometerDataVector({ x: 0, y: 0, z: 0 })), + 20, + ); + expect(() => liveData.getBuffer().getSeries(100, elemsInBuffer)).not.toThrow(); + expect(liveData.getBuffer().getSeries(100, 10).length).toEqual(10); - expect(() => liveData.getBuffer().getSeries(100, elemsInBuffer)).not.toThrow(); - expect(liveData.getBuffer().getSeries(100, 10).length).toEqual(10); + expect(() => liveData.getBuffer().getSeries(100, elemsInBuffer + 1)).toThrow(); + }); - expect(() => liveData.getBuffer().getSeries(100, elemsInBuffer + 1)).toThrow(); - }) + test('Can extract vectors from live data', () => { + const liveData: LiveData = new MicrobitAccelerometerLiveData( + new LiveDataBuffer(10), + ); - test("Can extract vectors from live data", () => { - const liveData: LiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) + repeat( + () => liveData.put(new MicrobitAccelerometerDataVector({ x: 1, y: 2, z: 3 })), + 20, + ); - repeat(() => liveData.put(new MicrobitAccelerometerDataVector({ x: 1, y: 2, z: 3 })), 20) + expect(() => get(liveData).getVector()).not.toThrow(); + expect(get(liveData).getVector()).toEqual([1, 2, 3]); + }); - expect(() => get(liveData).getVector()).not.toThrow(); - expect(get(liveData).getVector()).toEqual([1, 2, 3]) - }) + test('Test smoothed values', () => { + const liveData: LiveData = + new MicrobitAccelerometerLiveData(new LiveDataBuffer(20)); + const smoothLiveData = new SmoothedLiveData(liveData, 2); - test("Test smoothed values", () => { - const liveData: LiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(20)); - const smoothLiveData = new SmoothedLiveData(liveData, 2); + const point1 = new MicrobitAccelerometerDataVector({ x: 3, y: 2, z: 1 }); + const point2 = new MicrobitAccelerometerDataVector({ x: 1, y: 2, z: 3 }); - const point1 = new MicrobitAccelerometerDataVector({ x: 3, y: 2, z: 1 }); - const point2 = new MicrobitAccelerometerDataVector({ x: 1, y: 2, z: 3 }); + liveData.put(point1); + liveData.put(point2); - liveData.put(point1) - liveData.put(point2) - - expect(get(smoothLiveData).getVector()[0]).toBeCloseTo(smoothNewValue(point2.getVector()[0], point1.getVector()[0]), 10) - expect(get(smoothLiveData).getVector()[1]).toBeCloseTo(smoothNewValue(point2.getVector()[1], point1.getVector()[1]), 10) - expect(get(smoothLiveData).getVector()[2]).toBeCloseTo(smoothNewValue(point2.getVector()[2], point1.getVector()[2]), 10) - }) + expect(get(smoothLiveData).getVector()[0]).toBeCloseTo( + smoothNewValue(point2.getVector()[0], point1.getVector()[0]), + 10, + ); + expect(get(smoothLiveData).getVector()[1]).toBeCloseTo( + smoothNewValue(point2.getVector()[1], point1.getVector()[1]), + 10, + ); + expect(get(smoothLiveData).getVector()[2]).toBeCloseTo( + smoothNewValue(point2.getVector()[2], point1.getVector()[2]), + 10, + ); + }); }); - diff --git a/src/__tests__/engine.test.ts b/src/__tests__/engine.test.ts index a28f0fdec..6259c49a5 100644 --- a/src/__tests__/engine.test.ts +++ b/src/__tests__/engine.test.ts @@ -7,21 +7,20 @@ * SPDX-License-Identifier: MIT */ -import { get } from "svelte/store"; -import LiveDataBuffer from "../script/domain/LiveDataBuffer"; -import MicrobitAccelerometerLiveData from "../script/livedata/MicrobitAccelerometerData"; -import { stores } from "../script/stores/Stores"; +import { get } from 'svelte/store'; +import LiveDataBuffer from '../script/domain/LiveDataBuffer'; +import MicrobitAccelerometerLiveData from '../script/livedata/MicrobitAccelerometerData'; +import { stores } from '../script/stores/Stores'; describe('Engine behaviour test', () => { - test('Engine should stop predicting when the LiveData store is set', () => { - const liveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) - stores.setLiveData(liveData) - const engine = stores.getEngine() - expect(get(engine).isRunning).toBe(true); - const newLiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)) - stores.setLiveData(newLiveData) - expect(get(engine).isRunning).toBe(false); - expect(get(stores.getEngine()).isRunning).toBe(true) - }); + test('Engine should stop predicting when the LiveData store is set', () => { + const liveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)); + stores.setLiveData(liveData); + const engine = stores.getEngine(); + expect(get(engine).isRunning).toBe(true); + const newLiveData = new MicrobitAccelerometerLiveData(new LiveDataBuffer(10)); + stores.setLiveData(newLiveData); + expect(get(engine).isRunning).toBe(false); + expect(get(stores.getEngine()).isRunning).toBe(true); + }); }); - diff --git a/src/__tests__/gestures.test.ts b/src/__tests__/gestures.test.ts index c45c0045a..85ff6a529 100644 --- a/src/__tests__/gestures.test.ts +++ b/src/__tests__/gestures.test.ts @@ -7,7 +7,7 @@ * SPDX-License-Identifier: MIT */ -import { stores } from "../script/stores/Stores"; +import { stores } from '../script/stores/Stores'; describe('Tests of Gestures', () => { beforeEach(() => { diff --git a/src/__tests__/testUtils.ts b/src/__tests__/testUtils.ts index 1f1f06222..602db82f7 100644 --- a/src/__tests__/testUtils.ts +++ b/src/__tests__/testUtils.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ export const repeat = (func: (a?: any) => any, n: number) => { - for (let i = 0; i < n; i++) { - func(); - } -} \ No newline at end of file + for (let i = 0; i < n; i++) { + func(); + } +}; diff --git a/src/components/graphs/knngraph/KNNModelGraphController.ts b/src/components/graphs/knngraph/KNNModelGraphController.ts index ba12f87eb..39a7afe17 100644 --- a/src/components/graphs/knngraph/KNNModelGraphController.ts +++ b/src/components/graphs/knngraph/KNNModelGraphController.ts @@ -7,7 +7,10 @@ import * as d3 from 'd3'; import { TrainingData } from '../../../script/domain/ModelTrainer'; import { Unsubscriber, Writable, derived, get, writable } from 'svelte/store'; import KNNModelGraphDrawer, { GraphDrawConfig } from './KNNModelGraphDrawer'; -import { MicrobitAccelerometerData, MicrobitAccelerometerDataVector } from '../../../script/livedata/MicrobitAccelerometerData'; +import { + MicrobitAccelerometerData, + MicrobitAccelerometerDataVector, +} from '../../../script/livedata/MicrobitAccelerometerData'; import { TimestampedData } from '../../../script/domain/LiveDataBuffer'; import Axes from '../../../script/domain/Axes'; import Filters from '../../../script/domain/Filters'; @@ -123,19 +126,22 @@ class KNNModelGraphController { try { const sampleDuration = StaticConfiguration.pollingPredictionSampleDuration; const sampleSize = StaticConfiguration.pollingPredictionSampleSize; - liveData = get(stores).liveData.getBuffer().getSeries(sampleDuration, sampleSize).map(el => { - if (el.value.getSize() != 3) { - throw new Error("Couldn't convert vector to accelerometer data vector") - } - return { - ...el, - value: new MicrobitAccelerometerDataVector({ - x: el.value.getVector()[0], - y: el.value.getVector()[1], - z: el.value.getVector()[2] - }) - } - }); + liveData = get(stores) + .liveData.getBuffer() + .getSeries(sampleDuration, sampleSize) + .map(el => { + if (el.value.getSize() != 3) { + throw new Error("Couldn't convert vector to accelerometer data vector"); + } + return { + ...el, + value: new MicrobitAccelerometerDataVector({ + x: el.value.getVector()[0], + y: el.value.getVector()[1], + z: el.value.getVector()[2], + }), + }; + }); this.liveDataRecords.push(liveData); if (this.liveDataRecords.length > this.liveDataRecordsSize) { this.liveDataRecords.shift(); @@ -192,7 +198,9 @@ class KNNModelGraphController { }; } - private sumAccelData(data: MicrobitAccelerometerDataVector[]): MicrobitAccelerometerData { + private sumAccelData( + data: MicrobitAccelerometerDataVector[], + ): MicrobitAccelerometerData { const sum = (nums: number[]): number => nums.reduce((pre, cur) => cur + pre, 0); return { diff --git a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts index 43ad9f1ad..2bafa1de3 100644 --- a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts +++ b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts @@ -37,14 +37,15 @@ class KNNModelGraphDrawer { constructor( private svg: d3.Selection, private classId: string, - ) { } + ) {} public drawLiveData = (drawConfig: GraphDrawConfig, drawData: Point3D) => { if (isNaN(drawData.y)) { return; } const pointTransformer = this.getPointTransformer(drawConfig); - const color = StaticConfiguration.gestureColors[stores.getGestures().getNumberOfGestures()]; + const color = + StaticConfiguration.gestureColors[stores.getGestures().getNumberOfGestures()]; const drawableLivePoint: DrawablePoint = { pointTransformed: pointTransformer(drawData), color, diff --git a/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts b/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts index aa60b4677..d779bf821 100644 --- a/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts +++ b/src/components/playground/inputSynthesizer/LiveDataSynthesizer.ts @@ -17,11 +17,27 @@ import BaseVector from '../../../script/livedata/BaseVector'; type LiveDataSynthesizerOptions = { intervalSpeed: number; - speeds: number[] + speeds: number[]; isActive: boolean; - noOfAxes: number + noOfAxes: number; }; -const letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "L", "M", "N", "O", "P"] +const letters = [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'L', + 'M', + 'N', + 'O', + 'P', +]; class LiveDataSynthesizer implements Readable { private interval: NodeJS.Timeout | undefined = undefined; @@ -33,10 +49,10 @@ class LiveDataSynthesizer implements Readable { intervalSpeed: this.getInitialIntervalValue(), speeds: [this.getInitialSineSpeed()], isActive: false, - noOfAxes: 1 + noOfAxes: 1, } as LiveDataSynthesizerOptions); - stores.setLiveData(new SyntheticLiveData([letters[0]])) - this.referenceStoreGetter = () => get(stores).liveData as SyntheticLiveData + stores.setLiveData(new SyntheticLiveData([letters[0]])); + this.referenceStoreGetter = () => get(stores).liveData as SyntheticLiveData; } public subscribe( @@ -82,26 +98,26 @@ class LiveDataSynthesizer implements Readable { public setNoOfAxes(axes: number) { this.store.update(e => { if (e.noOfAxes !== axes) { - console.log("changed liveDatra") - stores.setLiveData(new SyntheticLiveData(letters.slice(0, axes))) + console.log('changed liveDatra'); + stores.setLiveData(new SyntheticLiveData(letters.slice(0, axes))); } e.noOfAxes = axes; if (axes > e.speeds.length) { - e.speeds = [...e.speeds, ...new Array(axes - e.speeds.length).fill(0)] + e.speeds = [...e.speeds, ...new Array(axes - e.speeds.length).fill(0)]; } else { e.speeds = e.speeds.slice(0, axes); } return e; - }) + }); } public generateData() { const val = new Date().getTime(); let newVector = new Array(get(this.store).noOfAxes).fill(0); - newVector = newVector.map((x, i) => Math.sin(val * get(this.store).speeds[i])) - const vectorLetters = letters.slice(0, newVector.length) - const newValue = new BaseVector(newVector, vectorLetters) + newVector = newVector.map((x, i) => Math.sin(val * get(this.store).speeds[i])); + const vectorLetters = letters.slice(0, newVector.length); + const newValue = new BaseVector(newVector, vectorLetters); this.referenceStoreGetter().put(newValue); } @@ -109,8 +125,8 @@ class LiveDataSynthesizer implements Readable { public setSpeed(index: number, speed: number) { this.store.update(s => { s.speeds[index] = speed / 3000; - return s - }) + return s; + }); } public setIntervalSpeed(value: number) { @@ -144,5 +160,4 @@ class LiveDataSynthesizer implements Readable { const liveDataSynthesizer = new LiveDataSynthesizer(); - export default liveDataSynthesizer; diff --git a/src/components/playground/inputSynthesizer/SyntheticLiveData .ts b/src/components/playground/inputSynthesizer/SyntheticLiveData .ts index bc4a8f88a..ac2ca271d 100644 --- a/src/components/playground/inputSynthesizer/SyntheticLiveData .ts +++ b/src/components/playground/inputSynthesizer/SyntheticLiveData .ts @@ -3,33 +3,42 @@ * * SPDX-License-Identifier: MIT */ -import { Subscriber, Invalidator, Unsubscriber, Writable, writable, get } from "svelte/store"; -import LiveDataBuffer from "../../../script/domain/LiveDataBuffer"; -import LiveData from "../../../script/domain/stores/LiveData"; -import BaseVector from "../../../script/livedata/BaseVector"; +import { + Subscriber, + Invalidator, + Unsubscriber, + Writable, + writable, + get, +} from 'svelte/store'; +import LiveDataBuffer from '../../../script/domain/LiveDataBuffer'; +import LiveData from '../../../script/domain/stores/LiveData'; +import BaseVector from '../../../script/livedata/BaseVector'; export class SyntheticLiveData implements LiveData { - private store: Writable; - private buffer: LiveDataBuffer - public constructor(labels: string[]) { - this.store = writable(new BaseVector(new Array(labels.length).fill(0), labels)) - this.buffer = new LiveDataBuffer(200); - } - put(data: BaseVector): void { - this.store.set(data); - this.buffer.addValue(data); - } - getBuffer(): LiveDataBuffer { - return this.buffer; - } - getSeriesSize(): number { - return get(this.store).getSize(); - } - getLabels(): string[] { - return get(this.store).getLabels(); - } - subscribe(run: Subscriber, invalidate?: Invalidator | undefined): Unsubscriber { - return this.store.subscribe(run, invalidate); - } - -} \ No newline at end of file + private store: Writable; + private buffer: LiveDataBuffer; + public constructor(labels: string[]) { + this.store = writable(new BaseVector(new Array(labels.length).fill(0), labels)); + this.buffer = new LiveDataBuffer(200); + } + put(data: BaseVector): void { + this.store.set(data); + this.buffer.addValue(data); + } + getBuffer(): LiveDataBuffer { + return this.buffer; + } + getSeriesSize(): number { + return get(this.store).getSize(); + } + getLabels(): string[] { + return get(this.store).getLabels(); + } + subscribe( + run: Subscriber, + invalidate?: Invalidator | undefined, + ): Unsubscriber { + return this.store.subscribe(run, invalidate); + } +} diff --git a/src/pages/training/TrainModelButton.ts b/src/pages/training/TrainModelButton.ts index 8fd2ed636..a6b58b6b5 100644 --- a/src/pages/training/TrainModelButton.ts +++ b/src/pages/training/TrainModelButton.ts @@ -3,79 +3,89 @@ * * SPDX-License-Identifier: MIT */ -import { Writable, get } from "svelte/store"; -import { DropdownOption } from "../../components/buttons/Buttons"; -import { ModelEntry, availableModels, highlightedAxis } from "../../script/stores/uiStore"; -import ModelTrainer from "../../script/domain/ModelTrainer"; -import MLModel from "../../script/domain/MLModel"; -import Logger from "../../script/utils/Logger"; -import Axes from "../../script/domain/Axes"; -import { stores } from "../../script/stores/Stores"; -import StaticConfiguration from "../../StaticConfiguration"; -import KNNNonNormalizedModelTrainer from "../../script/mlmodels/KNNNonNormalizedModelTrainer"; -import { extractAxisFromTrainingData } from "../../script/utils/graphUtils"; -import LayersModelTrainer from "../../script/mlmodels/LayersModelTrainer"; -import { LossTrainingIteration } from "../../components/graphs/LossGraphUtil"; -import { FilterType } from "../../script/domain/FilterTypes"; -import Filters from "../../script/domain/Filters"; +import { Writable, get } from 'svelte/store'; +import { DropdownOption } from '../../components/buttons/Buttons'; +import { + ModelEntry, + availableModels, + highlightedAxis, +} from '../../script/stores/uiStore'; +import ModelTrainer from '../../script/domain/ModelTrainer'; +import MLModel from '../../script/domain/MLModel'; +import Logger from '../../script/utils/Logger'; +import Axes from '../../script/domain/Axes'; +import { stores } from '../../script/stores/Stores'; +import StaticConfiguration from '../../StaticConfiguration'; +import KNNNonNormalizedModelTrainer from '../../script/mlmodels/KNNNonNormalizedModelTrainer'; +import { extractAxisFromTrainingData } from '../../script/utils/graphUtils'; +import LayersModelTrainer from '../../script/mlmodels/LayersModelTrainer'; +import { LossTrainingIteration } from '../../components/graphs/LossGraphUtil'; +import { FilterType } from '../../script/domain/FilterTypes'; +import Filters from '../../script/domain/Filters'; const gestures = stores.getGestures(); const classifier = stores.getClassifier(); export const options: DropdownOption[] = availableModels.map(model => { - return { - id: model.id, - label: model.title, - }; + return { + id: model.id, + label: model.title, + }; }); -export const getModelTrainer = (modelEntry: ModelEntry, onTrainingIteration: (iteration: LossTrainingIteration) => void): ModelTrainer => { - const currentAxis = get(highlightedAxis); - if (modelEntry.id === 'KNN') { - const noOfRecordings = gestures - .getGestures() - .map(gesture => gesture.getRecordings().length) - .reduce((prev, cur) => cur + prev, 0); +export const getModelTrainer = ( + modelEntry: ModelEntry, + onTrainingIteration: (iteration: LossTrainingIteration) => void, +): ModelTrainer => { + const currentAxis = get(highlightedAxis); + if (modelEntry.id === 'KNN') { + const noOfRecordings = gestures + .getGestures() + .map(gesture => gesture.getRecordings().length) + .reduce((prev, cur) => cur + prev, 0); - if (noOfRecordings / 2 < StaticConfiguration.knnNeighbourCount) { - Logger.log( - 'TrainModelButton', - 'The number of recordings is probably too low for an effective KNN model if using ' + - StaticConfiguration.knnNeighbourCount + - ' neighbours ', - ); - } + if (noOfRecordings / 2 < StaticConfiguration.knnNeighbourCount) { + Logger.log( + 'TrainModelButton', + 'The number of recordings is probably too low for an effective KNN model if using ' + + StaticConfiguration.knnNeighbourCount + + ' neighbours ', + ); + } - const offset = - currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; + const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; - return new KNNNonNormalizedModelTrainer( - StaticConfiguration.knnNeighbourCount, - data => extractAxisFromTrainingData(data, offset, 3), - ); - } - highlightedAxis.set(undefined); + return new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => + extractAxisFromTrainingData(data, offset, 3), + ); + } + highlightedAxis.set(undefined); - return new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, h => { - onTrainingIteration(h); - }); + return new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, h => { + onTrainingIteration(h); + }); }; -export const trainModel = (selectedOption: Writable, onTrainingIteration: (iteration: LossTrainingIteration) => void) => { - const selectedModel = availableModels.find(model => model.id === get(selectedOption).id); - const model = classifier.getModel(); +export const trainModel = ( + selectedOption: Writable, + onTrainingIteration: (iteration: LossTrainingIteration) => void, +) => { + const selectedModel = availableModels.find( + model => model.id === get(selectedOption).id, + ); + const model = classifier.getModel(); - if (selectedModel?.id === 'KNN') { - // TODO: We set the filters to 2 different filters giving us a 2d graph - const knnFilters = [FilterType.MAX, FilterType.MEAN]; - const filters: Filters = classifier.getFilters(); - filters.clear(); - for (const filter of knnFilters) { - filters.add(filter); - } + if (selectedModel?.id === 'KNN') { + // TODO: We set the filters to 2 different filters giving us a 2d graph + const knnFilters = [FilterType.MAX, FilterType.MEAN]; + const filters: Filters = classifier.getFilters(); + filters.clear(); + for (const filter of knnFilters) { + filters.add(filter); } + } - if (selectedModel) { - model.train(getModelTrainer(selectedModel, onTrainingIteration)); - } -} \ No newline at end of file + if (selectedModel) { + model.train(getModelTrainer(selectedModel, onTrainingIteration)); + } +}; diff --git a/src/script/domain/LiveDataBuffer.ts b/src/script/domain/LiveDataBuffer.ts index 8d6859017..ccbaa12ce 100644 --- a/src/script/domain/LiveDataBuffer.ts +++ b/src/script/domain/LiveDataBuffer.ts @@ -1,4 +1,4 @@ -import { LiveDataVector } from "./stores/LiveDataVector"; +import { LiveDataVector } from './stores/LiveDataVector'; /** * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors diff --git a/src/script/domain/stores/LiveData.ts b/src/script/domain/stores/LiveData.ts index 029c154e3..1c1b019aa 100644 --- a/src/script/domain/stores/LiveData.ts +++ b/src/script/domain/stores/LiveData.ts @@ -31,7 +31,6 @@ interface LiveData extends Readable { * Returns labels accociated with each data point (Such as for the LiveGraph) */ getLabels(): string[]; - } export default LiveData; diff --git a/src/script/domain/stores/LiveDataVector.ts b/src/script/domain/stores/LiveDataVector.ts index 81bedb7a9..60c3822d7 100644 --- a/src/script/domain/stores/LiveDataVector.ts +++ b/src/script/domain/stores/LiveDataVector.ts @@ -5,11 +5,9 @@ */ export interface LiveDataVector { + getVector(): number[]; - getVector(): number[] + getSize(): number; - getSize(): number - - getLabels(): string[] - -} \ No newline at end of file + getLabels(): string[]; +} diff --git a/src/script/domain/stores/gesture/Gesture.ts b/src/script/domain/stores/gesture/Gesture.ts index f6b36b110..9ea6b1d43 100644 --- a/src/script/domain/stores/gesture/Gesture.ts +++ b/src/script/domain/stores/gesture/Gesture.ts @@ -16,9 +16,9 @@ export type Confidence = { currentConfidence: number; requiredConfidence: number; isConfident: boolean; -} +}; -export type GestureData = PersistantGestureData & { confidence: Confidence } +export type GestureData = PersistantGestureData & { confidence: Confidence }; export type GestureOutput = { matrix?: boolean[]; diff --git a/src/script/domain/stores/gesture/Gestures.ts b/src/script/domain/stores/gesture/Gestures.ts index 787a1c05f..c3a96bc27 100644 --- a/src/script/domain/stores/gesture/Gestures.ts +++ b/src/script/domain/stores/gesture/Gestures.ts @@ -35,7 +35,7 @@ export type RecordingData = { class Gestures implements Readable { private static subscribableGestures: Writable; private repository: GestureRepository; - private confidenceStore: Readable> + private confidenceStore: Readable>; constructor(repository: GestureRepository) { this.repository = repository; diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts index eb201b516..1de995bd7 100644 --- a/src/script/engine/PollingPredictorEngine.ts +++ b/src/script/engine/PollingPredictorEngine.ts @@ -77,9 +77,7 @@ class PollingPredictorEngine implements Engine { /** * Searches for an applicable amount of data, by iterately trying fewer data points if buffer fetch fails */ - private getRawDataFromBuffer( - sampleSize: number, - ): TimestampedData[] { + private getRawDataFromBuffer(sampleSize: number): TimestampedData[] { try { return this.liveData .getBuffer() diff --git a/src/script/livedata/BaseVector.ts b/src/script/livedata/BaseVector.ts index 3897e52fa..0ff56cec5 100644 --- a/src/script/livedata/BaseVector.ts +++ b/src/script/livedata/BaseVector.ts @@ -4,22 +4,25 @@ * SPDX-License-Identifier: MIT */ -import { LiveDataVector } from "../domain/stores/LiveDataVector"; +import { LiveDataVector } from '../domain/stores/LiveDataVector'; class BaseVector implements LiveDataVector { - public constructor(private numbers: number[], private labels: string[]) { } + public constructor( + private numbers: number[], + private labels: string[], + ) {} - public getLabels(): string[] { - return this.labels - } + public getLabels(): string[] { + return this.labels; + } - public getSize(): number { - return this.numbers.length; - } + public getSize(): number { + return this.numbers.length; + } - public getVector(): number[] { - return this.numbers; - } + public getVector(): number[] { + return this.numbers; + } } -export default BaseVector; \ No newline at end of file +export default BaseVector; diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts index 3e1d39449..4cdf39b7e 100644 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ b/src/script/livedata/MicrobitAccelerometerData.ts @@ -16,36 +16,34 @@ export type MicrobitAccelerometerData = { export const asAccelerometerData = (input: LiveDataVector) => { if (input.getSize() != 3) { - throw new Error("Cannot cast input as accelerometer data, size is not 3") + throw new Error('Cannot cast input as accelerometer data, size is not 3'); } const data = new MicrobitAccelerometerDataVector({ x: input.getVector()[0], y: input.getVector()[1], - z: input.getVector()[2] - }) + z: input.getVector()[2], + }); input.getLabels().forEach((label, index) => { if (data.getLabels()[index] !== label) { - throw new Error("Cannot cast input as accelerometer data, labels do not match") + throw new Error('Cannot cast input as accelerometer data, labels do not match'); } - }) + }); return data; -} +}; export class MicrobitAccelerometerDataVector implements LiveDataVector { - - public constructor(private data: MicrobitAccelerometerData) { - } + public constructor(private data: MicrobitAccelerometerData) {} public getLabels(): string[] { - return ["X", "Y", "Z"] + return ['X', 'Y', 'Z']; } public getSize(): number { return this.getVector().length; } public getVector(): number[] { - return [this.data.x, this.data.y, this.data.z] + return [this.data.x, this.data.y, this.data.z]; } public getAccelerometerData(): MicrobitAccelerometerData { @@ -56,11 +54,13 @@ export class MicrobitAccelerometerDataVector implements LiveDataVector { class MicrobitAccelerometerLiveData implements LiveData { private store: Writable; constructor(private dataBuffer: LiveDataBuffer) { - this.store = writable(new MicrobitAccelerometerDataVector({ - x: 0, - y: 0, - z: 0, - })); + this.store = writable( + new MicrobitAccelerometerDataVector({ + x: 0, + y: 0, + z: 0, + }), + ); } public getBuffer(): LiveDataBuffer { @@ -84,7 +84,9 @@ class MicrobitAccelerometerLiveData implements LiveData, - invalidate?: ((value?: MicrobitAccelerometerDataVector | undefined) => void) | undefined, + invalidate?: + | ((value?: MicrobitAccelerometerDataVector | undefined) => void) + | undefined, ): Unsubscriber { return this.store.subscribe(run, invalidate); } diff --git a/src/script/livedata/SmoothedLiveData.ts b/src/script/livedata/SmoothedLiveData.ts index c8f9b0bd8..42fd0ed11 100644 --- a/src/script/livedata/SmoothedLiveData.ts +++ b/src/script/livedata/SmoothedLiveData.ts @@ -79,7 +79,10 @@ class SmoothedLiveData implements LiveData val!.getVector()[i]); diff --git a/src/script/microbit-interfacing/MicrobitBluetooth.ts b/src/script/microbit-interfacing/MicrobitBluetooth.ts index e01d80424..137b8d8f7 100644 --- a/src/script/microbit-interfacing/MicrobitBluetooth.ts +++ b/src/script/microbit-interfacing/MicrobitBluetooth.ts @@ -315,7 +315,7 @@ export class MicrobitBluetooth { this.onReconnect?.(this); }) .catch(e => { - Logger.log("MicrobitBluetooth", e); + Logger.log('MicrobitBluetooth', e); void this.onReconnectFailed(); }); } else { diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index 445973bb9..ecb690fd7 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -18,7 +18,9 @@ import LoggingDecorator from './LoggingDecorator'; import TypingUtils from '../../TypingUtils'; import { DeviceRequestStates } from '../../stores/connectDialogStore'; import StaticConfiguration from '../../../StaticConfiguration'; -import MicrobitAccelerometerLiveData, { MicrobitAccelerometerDataVector } from '../../livedata/MicrobitAccelerometerData'; +import MicrobitAccelerometerLiveData, { + MicrobitAccelerometerDataVector, +} from '../../livedata/MicrobitAccelerometerData'; import { stores } from '../../stores/Stores'; import LiveDataBuffer from '../../domain/LiveDataBuffer'; @@ -126,8 +128,10 @@ class InputBehaviour extends LoggingDecorator { onConnected(name: string): void { super.onConnected(name); - const buffer = new LiveDataBuffer(StaticConfiguration.accelerometerLiveDataBufferSize) - stores.setLiveData(new MicrobitAccelerometerLiveData(buffer)) + const buffer = new LiveDataBuffer( + StaticConfiguration.accelerometerLiveDataBufferSize, + ); + stores.setLiveData(new MicrobitAccelerometerLiveData(buffer)); state.update(s => { s.isInputConnected = true; @@ -152,11 +156,13 @@ class InputBehaviour extends LoggingDecorator { const accelY = y / 1000.0; const accelZ = z / 1000.0; - get(stores).liveData.put(new MicrobitAccelerometerDataVector({ - x: accelX, - y: accelY, - z: accelZ, - })); + get(stores).liveData.put( + new MicrobitAccelerometerDataVector({ + x: accelX, + y: accelY, + z: accelZ, + }), + ); } buttonChange(buttonState: MBSpecs.ButtonState, button: MBSpecs.Button): void { diff --git a/src/script/repository/FileUtility.ts b/src/script/repository/FileUtility.ts index 49f6ab6f1..af278fe1f 100644 --- a/src/script/repository/FileUtility.ts +++ b/src/script/repository/FileUtility.ts @@ -27,7 +27,7 @@ class FileUtility { element.setAttribute( 'href', 'data:application/json;charset=utf-8,' + - encodeURIComponent(JSON.stringify(gestureData, null, 2)), + encodeURIComponent(JSON.stringify(gestureData, null, 2)), ); element.setAttribute('download', 'dataset'); diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index 709821490..ad67ff00d 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -4,78 +4,93 @@ * SPDX-License-Identifier: MIT */ -import { Invalidator, Readable, Subscriber, Unsubscriber, Writable, derived, get, writable } from "svelte/store"; -import Repositories from "../domain/Repositories"; -import Classifier from "../domain/stores/Classifier"; -import Engine from "../domain/stores/Engine"; -import LiveData from "../domain/stores/LiveData"; -import { LiveDataVector } from "../domain/stores/LiveDataVector"; -import Gestures from "../domain/stores/gesture/Gestures"; -import PollingPredictorEngine from "../engine/PollingPredictorEngine"; -import LocalStorageRepositories from "../repository/LocalStorageRepositories"; -import Logger from "../utils/Logger"; +import { + Invalidator, + Readable, + Subscriber, + Unsubscriber, + Writable, + derived, + get, + writable, +} from 'svelte/store'; +import Repositories from '../domain/Repositories'; +import Classifier from '../domain/stores/Classifier'; +import Engine from '../domain/stores/Engine'; +import LiveData from '../domain/stores/LiveData'; +import { LiveDataVector } from '../domain/stores/LiveDataVector'; +import Gestures from '../domain/stores/gesture/Gestures'; +import PollingPredictorEngine from '../engine/PollingPredictorEngine'; +import LocalStorageRepositories from '../repository/LocalStorageRepositories'; +import Logger from '../utils/Logger'; type StoresType = { - liveData: LiveData -} + liveData: LiveData; +}; /** * Stores is a container object, that allows for management of global stores. */ -class Stores implements Readable{ +class Stores implements Readable { + private liveData: Writable | undefined>; + private engine: Engine | undefined; + private classifier: Classifier; + private gestures: Gestures; - private liveData: Writable | undefined>; - private engine: Engine | undefined; - private classifier: Classifier; - private gestures: Gestures; + public constructor() { + this.liveData = writable(undefined); + this.engine = undefined; + const repositories: Repositories = new LocalStorageRepositories(); + this.classifier = repositories.getClassifierRepository().getClassifier(); + this.gestures = new Gestures(repositories.getGestureRepository()); + } - public constructor() { - this.liveData = writable(undefined); - this.engine = undefined; - const repositories: Repositories = new LocalStorageRepositories(); - this.classifier = repositories.getClassifierRepository().getClassifier(); - this.gestures = new Gestures(repositories.getGestureRepository()); - } + public subscribe( + run: Subscriber, + invalidate?: Invalidator | undefined, + ): Unsubscriber { + return derived([this.liveData], stores => { + if (!stores[0]) { + throw new Error( + 'Cannot subscribe to stores, livedata is null or undefined, set it user setLiveData(...) first', + ); + } + return { + liveData: stores[0], + }; + }).subscribe(run, invalidate); + } - public subscribe(run: Subscriber, invalidate?: Invalidator | undefined): Unsubscriber { - return derived([this.liveData], stores => { - if (!stores[0]) { - throw new Error("Cannot subscribe to stores, livedata is null or undefined, set it user setLiveData(...) first"); - } - return { - liveData: stores[0] - } - }).subscribe(run, invalidate); + public setLiveData>(liveDataStore: T): T { + Logger.log('stores', 'setting live data'); + if (!liveDataStore) { + throw new Error('Cannot set live data store to undefined/null'); } + this.liveData.set(liveDataStore); - public setLiveData>(liveDataStore: T): T { - Logger.log("stores", "setting live data") - if (!liveDataStore) { - throw new Error("Cannot set live data store to undefined/null"); - } - this.liveData.set(liveDataStore); - - // We stop the previous engine from making predictions - if (this.engine) { - this.engine.stop(); - } - this.engine = new PollingPredictorEngine(this.classifier, liveDataStore); - return get(this.liveData) as T; + // We stop the previous engine from making predictions + if (this.engine) { + this.engine.stop(); } + this.engine = new PollingPredictorEngine(this.classifier, liveDataStore); + return get(this.liveData) as T; + } - public getClassifier(): Classifier { - return this.classifier; - } + public getClassifier(): Classifier { + return this.classifier; + } - public getGestures(): Gestures { - return this.gestures; - } + public getGestures(): Gestures { + return this.gestures; + } - public getEngine(): Engine { - if (!this.engine) { - throw new Error("Cannot get engine store, the liveData store has not been set. You must set it using setLiveData(...)") - } - return this.engine; + public getEngine(): Engine { + if (!this.engine) { + throw new Error( + 'Cannot get engine store, the liveData store has not been set. You must set it using setLiveData(...)', + ); } + return this.engine; + } } -export const stores = new Stores(); \ No newline at end of file +export const stores = new Stores(); diff --git a/src/script/utils/Logger.ts b/src/script/utils/Logger.ts index ae2c59d7d..5e0eda0fc 100644 --- a/src/script/utils/Logger.ts +++ b/src/script/utils/Logger.ts @@ -6,9 +6,7 @@ import Environment from '../Environment'; class Logger { - - constructor(private origin: any) { - } + constructor(private origin: any) {} public log(message: any, ...params: any[]) { Logger.log(this.origin, message, params); @@ -19,31 +17,35 @@ class Logger { */ public static log(origin: any, message: any, ...params: any[]) { if (!Environment.isInDevelopment) { - return + return; } if (!(window as typeof window & { hasLogged: boolean }).hasLogged) { welcomeLog(); } - const outputMessage = `[${origin}] ${message} ${params}` + const outputMessage = `[${origin}] ${message} ${params}`; !(window as typeof window & { ns: boolean }).ns && console.trace(outputMessage); (window as typeof window & { ns: boolean }).ns && console.log(outputMessage); } } export const welcomeLog = () => { - if (!Environment.isInDevelopment || (window as typeof window & { hasLogged: boolean }).hasLogged) { - return + if ( + !Environment.isInDevelopment || + (window as typeof window & { hasLogged: boolean }).hasLogged + ) { + return; } console.log(`⚙️ Development Mode : Welcome to the ML-Machine development mode. To disable stacktrace in logs, type ns=true or ns() in console - If you experience any bugs, please report them at https://github.com/microbit-foundation/cctd-ml-machine/issues`) + If you experience any bugs, please report them at https://github.com/microbit-foundation/cctd-ml-machine/issues`); Object.assign(window, { hasLogged: true }); -} +}; if (!(window as typeof window & { ns: boolean }).ns) { - Object.assign(window, { ns: false, ds: () => (window as typeof window & { ns: boolean }).ns = true }) + Object.assign(window, { + ns: false, + ds: () => ((window as typeof window & { ns: boolean }).ns = true), + }); } - - export default Logger; From feb70b5ab6498911fef50402616c5eb950e8c5e2 Mon Sep 17 00:00:00 2001 From: r59q Date: Thu, 23 May 2024 21:55:48 +0200 Subject: [PATCH 035/106] Using ci instead of i --- .github/workflows/main.yml | 4 ++-- .github/workflows/production.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac0a98424..fccebd173 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v3 - name: Run Tests run: | - npm install + npm ci npm test formatting_and_linting: name: Prettier / svelte Check @@ -44,7 +44,7 @@ jobs: - name: Run prettier shell: bash run: | - npm install + npm ci npm run checkFormat - name: Svelte check shell: bash diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 2269771ae..9eb66a708 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v3 - name: Run Tests run: | - npm install + npm ci npm test build_and_deploy: if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') From c6ea397df8b9458fb7c7afb03e200856cd69d4bb Mon Sep 17 00:00:00 2001 From: r59q Date: Thu, 23 May 2024 21:58:16 +0200 Subject: [PATCH 036/106] Ignore bad files --- .prettierignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 4958c8934..71b433bc3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,3 @@ -src/translations.ts \ No newline at end of file +src/translations.ts +src/appInsights.ts +src/messages/* \ No newline at end of file From ef25c9a9483b912a7795628616ec7903d334c452 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 27 May 2024 19:39:16 +0200 Subject: [PATCH 037/106] Changed the makecode hex to use the new version --- src/StaticConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StaticConfiguration.ts b/src/StaticConfiguration.ts index cd66da880..47924edc8 100644 --- a/src/StaticConfiguration.ts +++ b/src/StaticConfiguration.ts @@ -45,7 +45,7 @@ class StaticConfiguration { // Link to the MakeCode firmware template public static readonly makecodeFirmwareUrl = - 'https://makecode.microbit.org/#pub:54705-16835-80762-83855'; + 'https://makecode.microbit.org/#pub:52042-28239-00563-08630'; public static readonly isMicrobitOutdated = (origin: HexOrigin, version: number) => { // Current versions, remember to update these, whenever changes to firmware are made! From 9afcab9f02c6715f19777f607c2c137d8f8ae487 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 27 May 2024 20:25:53 +0200 Subject: [PATCH 038/106] Fix noname use-same feature --- src/script/microbit-interfacing/Microbits.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/script/microbit-interfacing/Microbits.ts b/src/script/microbit-interfacing/Microbits.ts index 27bdd2cce..d947d0e2d 100644 --- a/src/script/microbit-interfacing/Microbits.ts +++ b/src/script/microbit-interfacing/Microbits.ts @@ -887,11 +887,6 @@ class Microbits { 'No input microbit has be defined! Please check that it is connected before using it', ); } - if (!this.inputName) { - throw new Error( - 'Something went wrong. Input microbit was specified, but without name!', - ); - } this.assignedOutputMicrobit = this.getInput(); this.outputName = this.inputName; this.outputVersion = this.inputVersion; From 54484b9b3067859890028c7a6ebc90df70e80e1e Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 27 May 2024 22:24:31 +0200 Subject: [PATCH 039/106] Create filters list --- features.json | 2 +- src/components/filters/FilterList.ts | 10 ++ src/components/filters/FilterListRow.svelte | 38 +++++ src/components/filters/FiltersList.svelte | 12 ++ src/pages/training/TrainingPage.svelte | 146 ++++++++++---------- windi.config.js | 6 +- 6 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 src/components/filters/FilterList.ts create mode 100644 src/components/filters/FilterListRow.svelte create mode 100644 src/components/filters/FiltersList.svelte diff --git a/features.json b/features.json index b770c3d48..a45488f93 100644 --- a/features.json +++ b/features.json @@ -1,4 +1,4 @@ { - "title": "Learning tool", + "title": "ML-Machine", "knnModel": true } diff --git a/src/components/filters/FilterList.ts b/src/components/filters/FilterList.ts new file mode 100644 index 000000000..460ca43b2 --- /dev/null +++ b/src/components/filters/FilterList.ts @@ -0,0 +1,10 @@ +import { FilterType } from "../../script/domain/FilterTypes"; +import { stores } from "../../script/stores/Stores"; + +export const toggleFilterCheckmarkClickHandler = (filterType: FilterType) => (e: MouseEvent) => { + e.preventDefault(); + const selectedFilters = stores.getClassifier().getFilters(); + selectedFilters.has(filterType) + ? selectedFilters.remove(filterType) + : selectedFilters.add(filterType); +}; \ No newline at end of file diff --git a/src/components/filters/FilterListRow.svelte b/src/components/filters/FilterListRow.svelte new file mode 100644 index 000000000..7a7bc768b --- /dev/null +++ b/src/components/filters/FilterListRow.svelte @@ -0,0 +1,38 @@ + + +{#key `filter-${filterType}-${checked}`} +
+
+
+ + +
+
+ {}} + on:mouseleave={() => {}} + src="imgs/ML_predict.svg" + alt="data representation icon" + class="w-6 hover:opacity-60 cursor-pointer" /> +
+
+
+{/key} diff --git a/src/components/filters/FiltersList.svelte b/src/components/filters/FiltersList.svelte new file mode 100644 index 000000000..36a44cd61 --- /dev/null +++ b/src/components/filters/FiltersList.svelte @@ -0,0 +1,12 @@ + + +
+ {#each availableFilters as filterType} + + {/each} +
diff --git a/src/pages/training/TrainingPage.svelte b/src/pages/training/TrainingPage.svelte index 1b7ceedfe..4efad871a 100644 --- a/src/pages/training/TrainingPage.svelte +++ b/src/pages/training/TrainingPage.svelte @@ -22,6 +22,7 @@ import CookieManager from '../../script/CookieManager'; import { stores } from '../../script/stores/Stores'; import { appInsights } from '../../appInsights'; + import FiltersList from '../../components/filters/FiltersList.svelte'; const classifier = stores.getClassifier(); const gestures = stores.getGestures(); @@ -77,84 +78,89 @@ {$t('content.trainer.controlbar.filters')} -
- {#if isUsingKNNModel} - - {/if} - {#if !sufficientData} -
-

- {$t('menu.trainer.notEnoughDataHeader1')} -

-

- {$t('menu.trainer.notEnoughDataInfoBody')} -

-
- {:else} -
- {#if !$model.isTraining} +
+
+ +
+
+ {#if isUsingKNNModel} + + {/if} + {#if !sufficientData} +
+

+ {$t('menu.trainer.notEnoughDataHeader1')} +

+

+ {$t('menu.trainer.notEnoughDataInfoBody')} +

+
+ {:else} +
+ {#if !$model.isTraining} + {#if $filters.length == 0} +

+ {$t('menu.trainer.noFilters')} +

+ {:else} +
+ { + resetLoss(); + trackModelEvent(); + }} + onTrainingIteration={trainingIterationHandler} /> +
+ {/if} + {/if} + + {#if $model.isTrained} +

+ {$t('menu.trainer.TrainingFinished')} +

+

+ {$t('menu.trainer.TrainingFinished.body')} +

+ {/if} {#if $filters.length == 0}

{$t('menu.trainer.noFilters')}

- {:else} -
- { - resetLoss(); - trackModelEvent(); - }} - onTrainingIteration={trainingIterationHandler} /> -
{/if} - {/if} - - {#if $model.isTrained} -

- {$t('menu.trainer.TrainingFinished')} -

-

- {$t('menu.trainer.TrainingFinished.body')} -

- {/if} - {#if $filters.length == 0} -

- {$t('menu.trainer.noFilters')} -

- {/if} -
- {/if} - {#if !isUsingKNNModel} -
- {#if $loss.length > 0 || $model.isTraining} - {#if !CookieManager.hasFeatureFlag('loss-graph')} - {#if $model.isTraining} -
-
-
- +
+ {/if} + {#if !isUsingKNNModel} +
+ {#if $loss.length > 0 || $model.isTraining} + {#if !CookieManager.hasFeatureFlag('loss-graph')} + {#if $model.isTraining} +
+
+
+ +
+

+ {$t('menu.trainer.isTrainingModelButton')} +

-

- {$t('menu.trainer.isTrainingModelButton')} -

-
+ {/if} + {:else} + {/if} - {:else} - {/if} - {/if} -
- {/if} - {#if !$state.isInputConnected} -
- -
- {/if} +
+ {/if} + {#if !$state.isInputConnected} +
+ +
+ {/if} +
diff --git a/windi.config.js b/windi.config.js index a5525d9a8..e41e5fe6e 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#3a3a3a', + primary: '#2B5EA7', primarytext: '#000000', - secondary: '#a0a0a0', + secondary: '#2CCAC0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#ffaaaa', + warning: '#FF7777', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From 56781804ddb7c272016d4c29cdbbb689a3a9867f Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 27 May 2024 22:29:26 +0200 Subject: [PATCH 040/106] Add license identifier --- src/components/filters/FilterList.ts | 5 +++++ src/components/filters/FilterListRow.svelte | 5 +++++ src/components/filters/FiltersList.svelte | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/components/filters/FilterList.ts b/src/components/filters/FilterList.ts index 460ca43b2..3fa3e0177 100644 --- a/src/components/filters/FilterList.ts +++ b/src/components/filters/FilterList.ts @@ -1,3 +1,8 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ import { FilterType } from "../../script/domain/FilterTypes"; import { stores } from "../../script/stores/Stores"; diff --git a/src/components/filters/FilterListRow.svelte b/src/components/filters/FilterListRow.svelte index 7a7bc768b..a14e5c566 100644 --- a/src/components/filters/FilterListRow.svelte +++ b/src/components/filters/FilterListRow.svelte @@ -1,3 +1,8 @@ + + +{#if $showHighlighted} +
+
+

{filter.getName()}

+

{filter.getDescription()}

+
+ +
+{/if} diff --git a/src/components/filters/FilterListRow.svelte b/src/components/filters/FilterListRow.svelte index a14e5c566..006204819 100644 --- a/src/components/filters/FilterListRow.svelte +++ b/src/components/filters/FilterListRow.svelte @@ -6,7 +6,11 @@ -
+
{#each availableFilters as filterType} {/each} diff --git a/src/views/OverlayView.svelte b/src/views/OverlayView.svelte index 522001c49..3bccd3682 100644 --- a/src/views/OverlayView.svelte +++ b/src/views/OverlayView.svelte @@ -11,6 +11,7 @@ import ReconnectPrompt from '../components/ReconnectPrompt.svelte'; import OutdatedMicrobitWarning from '../components/OutdatedMicrobitWarning.svelte'; import { isInputPatternValid } from '../script/stores/connectionStore'; + import FilterListFilterPreview from '../components/filters/FilterListFilterPreview.svelte'; // Helps show error messages on top of page let latestMessage = ''; @@ -55,4 +56,5 @@ {#if $state.isInputOutdated || $state.isOutputOutdated} {/if} +
diff --git a/windi.config.js b/windi.config.js index a5525d9a8..e41e5fe6e 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#3a3a3a', + primary: '#2B5EA7', primarytext: '#000000', - secondary: '#a0a0a0', + secondary: '#2CCAC0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#ffaaaa', + warning: '#FF7777', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From 24bffeb628be4b2c2e44bd0d866e9962d200f69e Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 28 May 2024 20:32:48 +0200 Subject: [PATCH 044/106] revert brand --- features.json | 2 +- windi.config.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features.json b/features.json index a45488f93..b770c3d48 100644 --- a/features.json +++ b/features.json @@ -1,4 +1,4 @@ { - "title": "ML-Machine", + "title": "Learning tool", "knnModel": true } diff --git a/windi.config.js b/windi.config.js index e41e5fe6e..a5525d9a8 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#2B5EA7', + primary: '#3a3a3a', primarytext: '#000000', - secondary: '#2CCAC0', + secondary: '#a0a0a0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#FF7777', + warning: '#ffaaaa', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From d5f1a4dc573322b638007f284e3499ced3776d87 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 28 May 2024 21:34:14 +0200 Subject: [PATCH 045/106] Added some more icons --- features.json | 2 +- public/imgs/dotted_graph_line.svg | 6 ++++++ public/imgs/graph_left.svg | 6 ++++++ public/imgs/graph_line_rounded.svg | 12 ++++++++++++ public/imgs/parallel.svg | 15 +++++++++++++++ src/components/filters/FilterListRow.svelte | 21 +++++++++++++++++++-- src/components/filters/FiltersList.svelte | 3 ++- windi.config.js | 6 +++--- 8 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 public/imgs/dotted_graph_line.svg create mode 100644 public/imgs/graph_left.svg create mode 100644 public/imgs/graph_line_rounded.svg create mode 100644 public/imgs/parallel.svg diff --git a/features.json b/features.json index b770c3d48..a45488f93 100644 --- a/features.json +++ b/features.json @@ -1,4 +1,4 @@ { - "title": "Learning tool", + "title": "ML-Machine", "knnModel": true } diff --git a/public/imgs/dotted_graph_line.svg b/public/imgs/dotted_graph_line.svg new file mode 100644 index 000000000..96cfb156d --- /dev/null +++ b/public/imgs/dotted_graph_line.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/public/imgs/graph_left.svg b/public/imgs/graph_left.svg new file mode 100644 index 000000000..9dc611b5f --- /dev/null +++ b/public/imgs/graph_left.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/public/imgs/graph_line_rounded.svg b/public/imgs/graph_line_rounded.svg new file mode 100644 index 000000000..0483197e2 --- /dev/null +++ b/public/imgs/graph_line_rounded.svg @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/public/imgs/parallel.svg b/public/imgs/parallel.svg new file mode 100644 index 000000000..245fe8836 --- /dev/null +++ b/public/imgs/parallel.svg @@ -0,0 +1,15 @@ + + + + + + + chart--parallel + + + \ No newline at end of file diff --git a/src/components/filters/FilterListRow.svelte b/src/components/filters/FilterListRow.svelte index 006204819..f7004e184 100644 --- a/src/components/filters/FilterListRow.svelte +++ b/src/components/filters/FilterListRow.svelte @@ -30,11 +30,27 @@ on:click={toggleFilterCheckmarkClickHandler(filterType)} {checked} class="w-5 h-5 self-center" /> -
-
+
+ { + highlightedFilter.set(filterType); + showHighlighted.set(true); + }} + on:mouseleave={() => { + showHighlighted.set(false); + }} + src="imgs/parallel.svg" + alt="data representation icon" + class="w-6 hover:opacity-60 mr-0.5" /> +
+
{/key} diff --git a/src/components/filters/FiltersList.svelte b/src/components/filters/FiltersList.svelte index 7b2b674d0..dcd61a31d 100644 --- a/src/components/filters/FiltersList.svelte +++ b/src/components/filters/FiltersList.svelte @@ -4,6 +4,7 @@ SPDX-License-Identifier: MIT -->
{#each availableFilters as filterType} diff --git a/windi.config.js b/windi.config.js index a5525d9a8..e41e5fe6e 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#3a3a3a', + primary: '#2B5EA7', primarytext: '#000000', - secondary: '#a0a0a0', + secondary: '#2CCAC0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#ffaaaa', + warning: '#FF7777', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From 2f6eb6f84f148054892c06af05de25edc7bef2aa Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 28 May 2024 21:38:55 +0200 Subject: [PATCH 046/106] Navigate by clicking filter preview --- src/components/filters/FilterListRow.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/filters/FilterListRow.svelte b/src/components/filters/FilterListRow.svelte index f7004e184..9cd6e1ab1 100644 --- a/src/components/filters/FilterListRow.svelte +++ b/src/components/filters/FilterListRow.svelte @@ -4,6 +4,7 @@ SPDX-License-Identifier: MIT --> -
+
diff --git a/src/pages/training/TrainingPage.svelte b/src/pages/training/TrainingPage.svelte index 4efad871a..f041e3e4f 100644 --- a/src/pages/training/TrainingPage.svelte +++ b/src/pages/training/TrainingPage.svelte @@ -5,7 +5,7 @@ --> + + +
+ +
+

Neural Network

+
+ +
+

Neural Network

+
+
+
diff --git a/src/script/stores/uiStore.ts b/src/script/stores/uiStore.ts index d7da1b94c..de6e9f77a 100644 --- a/src/script/stores/uiStore.ts +++ b/src/script/stores/uiStore.ts @@ -91,6 +91,7 @@ export function alertUser(text: string): void { }); } + // Assess whether an action is allowed. Alert user if not export function areActionsAllowed(actionAllowed = true, alertIfNotReady = true): boolean { const status = assessStateStatus(actionAllowed); @@ -146,20 +147,17 @@ export const availableModels: ModelEntry[] = [ }, ]; -const defaultModel: ModelEntry | undefined = availableModels.find( +const defaultModel: ModelEntry = availableModels.find( model => model.id === 'NN', -); - +)!; if (!defaultModel) { throw new Error('Default model not found!'); } + // TODO: Should just be model id instead of dropdown option -export const preferredModel = new PersistantWritable( - { - id: defaultModel.id, - label: defaultModel.label, - }, - 'prefferedModel', +export const selectedModel = new PersistantWritable( + defaultModel, + 'selectedModel', ); // TODO: Should probably be elsewhere From 2fe195b07604542bae819071d0db380c9269ad38 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 28 May 2024 22:02:04 +0200 Subject: [PATCH 048/106] Fix pipe --- features.json | 2 +- src/pages/training/TrainingPageTabs.svelte | 5 +++++ src/script/stores/uiStore.ts | 5 +---- windi.config.js | 6 +++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/features.json b/features.json index a45488f93..b770c3d48 100644 --- a/features.json +++ b/features.json @@ -1,4 +1,4 @@ { - "title": "ML-Machine", + "title": "Learning tool", "knnModel": true } diff --git a/src/pages/training/TrainingPageTabs.svelte b/src/pages/training/TrainingPageTabs.svelte index 6a5ab4597..44ddb8d51 100644 --- a/src/pages/training/TrainingPageTabs.svelte +++ b/src/pages/training/TrainingPageTabs.svelte @@ -1,3 +1,8 @@ + diff --git a/src/script/stores/uiStore.ts b/src/script/stores/uiStore.ts index de6e9f77a..19620fa36 100644 --- a/src/script/stores/uiStore.ts +++ b/src/script/stores/uiStore.ts @@ -91,7 +91,6 @@ export function alertUser(text: string): void { }); } - // Assess whether an action is allowed. Alert user if not export function areActionsAllowed(actionAllowed = true, alertIfNotReady = true): boolean { const status = assessStateStatus(actionAllowed); @@ -147,9 +146,7 @@ export const availableModels: ModelEntry[] = [ }, ]; -const defaultModel: ModelEntry = availableModels.find( - model => model.id === 'NN', -)!; +const defaultModel: ModelEntry = availableModels.find(model => model.id === 'NN')!; if (!defaultModel) { throw new Error('Default model not found!'); } diff --git a/windi.config.js b/windi.config.js index e41e5fe6e..a5525d9a8 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#2B5EA7', + primary: '#3a3a3a', primarytext: '#000000', - secondary: '#2CCAC0', + secondary: '#a0a0a0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#FF7777', + warning: '#ffaaaa', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From af5358fcb01f13cf086f198e75b50c05e4514406 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 28 May 2024 22:05:11 +0200 Subject: [PATCH 049/106] update prcomment --- .github/workflows/prcomment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prcomment.yml b/.github/workflows/prcomment.yml index 6278be14e..89278dfad 100644 --- a/.github/workflows/prcomment.yml +++ b/.github/workflows/prcomment.yml @@ -13,5 +13,5 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'A deployment is being made to https://ml-machine-${{github.event.pull_request.number}}.r59q.com/. This site will be continuously updated as changes are made to this PR.' + body: 'A deployment is being made to https://ml-machine-${{github.event.pull_request.number}}.r59q.com/. This site will be continuously updated as changes are made to this PR. Deployment should only take a few minutes' }) \ No newline at end of file From ae79d2725196a5bcd591322bfc16628cc5b5e81e Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 29 May 2024 19:37:47 +0200 Subject: [PATCH 050/106] Tabification --- src/components/control-bar/ControlBar.svelte | 4 +- .../knngraph/KNNModelGraphController.ts | 1 + .../graphs/knngraph/KnnModelGraph.svelte | 17 ++++---- .../KnnModelGraphSvgWithControls.svelte | 1 - src/pages/training/TrainModelButton.svelte | 18 -------- src/pages/training/TrainModelButton.ts | 15 ------- src/pages/training/TrainingPage.svelte | 43 +++++++++++++------ src/pages/training/TrainingPage.ts | 41 ++++++++++++++++++ src/pages/training/TrainingPageTabs.svelte | 33 +++++++++++--- 9 files changed, 111 insertions(+), 62 deletions(-) create mode 100644 src/pages/training/TrainingPage.ts diff --git a/src/components/control-bar/ControlBar.svelte b/src/components/control-bar/ControlBar.svelte index 932daa73e..78f3c8e63 100644 --- a/src/components/control-bar/ControlBar.svelte +++ b/src/components/control-bar/ControlBar.svelte @@ -6,10 +6,12 @@
diff --git a/src/components/graphs/knngraph/KNNModelGraphController.ts b/src/components/graphs/knngraph/KNNModelGraphController.ts index 39a7afe17..9cb518d09 100644 --- a/src/components/graphs/knngraph/KNNModelGraphController.ts +++ b/src/components/graphs/knngraph/KNNModelGraphController.ts @@ -256,4 +256,5 @@ class KNNModelGraphController { return nums.map(el => el / magnitude); } } +export const controller = writable(undefined); export default KNNModelGraphController; diff --git a/src/components/graphs/knngraph/KnnModelGraph.svelte b/src/components/graphs/knngraph/KnnModelGraph.svelte index 4c051f087..059cf207a 100644 --- a/src/components/graphs/knngraph/KnnModelGraph.svelte +++ b/src/components/graphs/knngraph/KnnModelGraph.svelte @@ -5,7 +5,7 @@ --> @@ -107,7 +106,7 @@ height={350} width={650} classID={'d3-3d-single'} - {controller} /> + controller={$controller} />
diff --git a/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte b/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte index 97a5e62d9..52d209cbe 100644 --- a/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte +++ b/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte @@ -69,7 +69,6 @@
- { - const unsubscribe = highlightedAxis.subscribe(axis => { - if (!axis) { - return; - } - if ($prevHighlightedAxis === axis) { - return; - } - if ($selectedOption.id === 'KNN') { - model.train( - getModelTrainer(getModelFromOption($selectedOption), onTrainingIteration), - ); - } - prevHighlightedAxis.set(axis); - }); - return unsubscribe; - }); {#if hasFeature(Feature.KNN_MODEL)} diff --git a/src/pages/training/TrainModelButton.ts b/src/pages/training/TrainModelButton.ts index a6b58b6b5..84a6f4f5e 100644 --- a/src/pages/training/TrainModelButton.ts +++ b/src/pages/training/TrainModelButton.ts @@ -39,22 +39,7 @@ export const getModelTrainer = ( ): ModelTrainer => { const currentAxis = get(highlightedAxis); if (modelEntry.id === 'KNN') { - const noOfRecordings = gestures - .getGestures() - .map(gesture => gesture.getRecordings().length) - .reduce((prev, cur) => cur + prev, 0); - - if (noOfRecordings / 2 < StaticConfiguration.knnNeighbourCount) { - Logger.log( - 'TrainModelButton', - 'The number of recordings is probably too low for an effective KNN model if using ' + - StaticConfiguration.knnNeighbourCount + - ' neighbours ', - ); - } - const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; - return new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => extractAxisFromTrainingData(data, offset, 3), ); diff --git a/src/pages/training/TrainingPage.svelte b/src/pages/training/TrainingPage.svelte index f041e3e4f..f67cf34a2 100644 --- a/src/pages/training/TrainingPage.svelte +++ b/src/pages/training/TrainingPage.svelte @@ -5,7 +5,13 @@ --> diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts new file mode 100644 index 000000000..05d71dd9e --- /dev/null +++ b/src/pages/training/TrainingPage.ts @@ -0,0 +1,41 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +import { get, writable } from 'svelte/store'; +import { LossTrainingIteration } from '../../components/graphs/LossGraphUtil'; +import { highlightedAxis } from '../../script/stores/uiStore'; +import Axes from '../../script/domain/Axes'; +import KNNNonNormalizedModelTrainer from '../../script/mlmodels/KNNNonNormalizedModelTrainer'; +import StaticConfiguration from '../../StaticConfiguration'; +import { extractAxisFromTrainingData } from '../../script/utils/graphUtils'; +import { stores } from '../../script/stores/Stores'; +import { FilterType } from '../../script/domain/FilterTypes'; + +export const loss = writable([]); +export const resetLoss = () => loss.set([]); +export const trainingIterationHandler = (h: LossTrainingIteration) => { + loss.update(newLoss => { + newLoss.push(h); + return newLoss; + }); +}; + +export const trainKNNModel = async () => { + if (stores.getClassifier().getFilters().count() !== 2) { + stores.getClassifier().getFilters().clear(); + stores.getClassifier().getFilters().add(FilterType.MAX); + stores.getClassifier().getFilters().add(FilterType.MEAN); + } + const model = stores.getClassifier().getModel(); + await model.train(getKNNTrainer()); +}; + +const getKNNTrainer = () => { + const currentAxis = get(highlightedAxis); + const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; + return new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => + extractAxisFromTrainingData(data, offset, 3), + ); +}; diff --git a/src/pages/training/TrainingPageTabs.svelte b/src/pages/training/TrainingPageTabs.svelte index 44ddb8d51..ca7ab410a 100644 --- a/src/pages/training/TrainingPageTabs.svelte +++ b/src/pages/training/TrainingPageTabs.svelte @@ -5,17 +5,38 @@ --> - -
+ +
-
-

Neural Network

+
+

Neural Network

-
-

Neural Network

+
+

KNN Model

From 512a62367333787ad2f37c12b06c1e7751bcd0d2 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 3 Jun 2024 19:55:51 +0200 Subject: [PATCH 051/106] Fix flicker --- src/components/graphs/knngraph/KnnModelGraph.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/graphs/knngraph/KnnModelGraph.svelte b/src/components/graphs/knngraph/KnnModelGraph.svelte index 059cf207a..415fd2a67 100644 --- a/src/components/graphs/knngraph/KnnModelGraph.svelte +++ b/src/components/graphs/knngraph/KnnModelGraph.svelte @@ -35,7 +35,8 @@ const accelYData = extractAxisFromTrainingData(allData, 1, 3); const accelZData = extractAxisFromTrainingData(allData, 2, 3); - const dataGetter = (axis: Axes): TrainingData => { + const dataGetter = (): TrainingData => { + const axis = get(highlightedAxis); if (axis === Axes.X) { return accelXData; } @@ -52,7 +53,7 @@ const svgSingle = d3.select('.d3-3d-single'); const controller = new KNNModelGraphController( svgSingle, - () => dataGetter(axis), + () => dataGetter(), { x: 650 / 2, y: 350 / 2 }, 'd3-3d-single-', axis, From be188a381366e141de07ce5fb4511bcf49f87d92 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 3 Jun 2024 22:46:51 +0200 Subject: [PATCH 052/106] Refactoring training page --- .vscode/settings.json | 4 +- ...nnectFirst.svelte => PleaseConnect.svelte} | 6 +- .../knngraph/KNNModelGraphController.ts | 4 +- src/pages/DataPage.svelte | 4 +- .../model/stackview/ModelPageStackView.svelte | 4 +- src/pages/training/InsufficientData.svelte | 19 ++ .../training/KnnModelTrainingPageView.svelte | 143 +++++++++++++++ .../NeuralNetworkTrainingPageView.svelte | 54 ++++++ src/pages/training/TrainingPage.svelte | 165 ++---------------- src/pages/training/TrainingPage.ts | 55 +++++- .../training/TrainingPageModelView.svelte | 26 +++ src/script/domain/ModelRegistry.ts | 23 +++ src/script/stores/uiStore.ts | 15 +- 13 files changed, 349 insertions(+), 173 deletions(-) rename src/components/{PleaseConnectFirst.svelte => PleaseConnect.svelte} (84%) create mode 100644 src/pages/training/InsufficientData.svelte create mode 100644 src/pages/training/KnnModelTrainingPageView.svelte create mode 100644 src/pages/training/NeuralNetworkTrainingPageView.svelte create mode 100644 src/pages/training/TrainingPageModelView.svelte create mode 100644 src/script/domain/ModelRegistry.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 965dbc675..0999a1039 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,7 +22,7 @@ "a11y-structure": "ignore", "a11y-click-events-have-key-events": "ignore", "a11y-missing-content": "ignore", - "a11y-no-noninteractive-element-interactions":"ignore", + "a11y-no-noninteractive-element-interactions": "ignore", "a11y-no-static-element-interactions": "ignore" }, "files.exclude": { @@ -34,4 +34,4 @@ "**/Thumbs.db": true }, "hide-files.files": [] -} +} \ No newline at end of file diff --git a/src/components/PleaseConnectFirst.svelte b/src/components/PleaseConnect.svelte similarity index 84% rename from src/components/PleaseConnectFirst.svelte rename to src/components/PleaseConnect.svelte index 014d8e99c..764d018bc 100644 --- a/src/components/PleaseConnectFirst.svelte +++ b/src/components/PleaseConnect.svelte @@ -16,10 +16,10 @@
-

+

{$t('menu.trainer.notConnected1')}

-

+

{$t('menu.trainer.notConnected2')}

@@ -27,6 +27,6 @@ class="m-auto arrow-filter-color" src="/imgs/down_arrow.svg" alt="down arrow icon" - width="50px" /> + width="35px" />
diff --git a/src/components/graphs/knngraph/KNNModelGraphController.ts b/src/components/graphs/knngraph/KNNModelGraphController.ts index 9cb518d09..4b0089197 100644 --- a/src/components/graphs/knngraph/KNNModelGraphController.ts +++ b/src/components/graphs/knngraph/KNNModelGraphController.ts @@ -3,9 +3,8 @@ * * SPDX-License-Identifier: MIT */ -import * as d3 from 'd3'; import { TrainingData } from '../../../script/domain/ModelTrainer'; -import { Unsubscriber, Writable, derived, get, writable } from 'svelte/store'; +import { Writable, derived, get, writable } from 'svelte/store'; import KNNModelGraphDrawer, { GraphDrawConfig } from './KNNModelGraphDrawer'; import { MicrobitAccelerometerData, @@ -17,7 +16,6 @@ import Filters from '../../../script/domain/Filters'; import { Point3D } from '../../../script/utils/graphUtils'; import StaticConfiguration from '../../../StaticConfiguration'; import { stores } from '../../../script/stores/Stores'; -import { LiveDataVector } from '../../../script/domain/stores/LiveDataVector'; type SampleData = { value: number[]; diff --git a/src/pages/DataPage.svelte b/src/pages/DataPage.svelte index 402ec69e5..dbf8f2de5 100644 --- a/src/pages/DataPage.svelte +++ b/src/pages/DataPage.svelte @@ -14,7 +14,6 @@ import NewGestureButton from '../components/NewGestureButton.svelte'; import StandardButton from '../components/buttons/StandardButton.svelte'; import { startConnectionProcess } from '../script/stores/connectDialogStore'; - import PleaseConnectFirst from '../components/PleaseConnectFirst.svelte'; import DataPageControlBar from '../components/datacollection/DataPageControlBar.svelte'; import Information from '../components/information/Information.svelte'; import { onMount } from 'svelte'; @@ -23,6 +22,7 @@ import exampleDataset from '../exampleDataset.json'; import { GestureData } from '../script/domain/stores/gesture/Gesture'; import { stores } from '../script/stores/Stores'; + import PleaseConnect from '../components/PleaseConnect.svelte'; let isConnectionDialogOpen = false; const gestures = stores.getGestures(); @@ -89,7 +89,7 @@
{#if !hasSomeData() && !$state.isInputConnected}
- +
{:else}
diff --git a/src/pages/model/stackview/ModelPageStackView.svelte b/src/pages/model/stackview/ModelPageStackView.svelte index ed14309e5..4fe528de4 100644 --- a/src/pages/model/stackview/ModelPageStackView.svelte +++ b/src/pages/model/stackview/ModelPageStackView.svelte @@ -14,9 +14,9 @@ import Microbits from '../../../script/microbit-interfacing/Microbits'; import TrainModelFirstTitle from '../../../components/TrainModelFirstTitle.svelte'; import ModelPageStackViewContent from './ModelPageStackViewContent.svelte'; - import PleaseConnectFirst from '../../../components/PleaseConnectFirst.svelte'; import StaticConfiguration from '../../../StaticConfiguration'; import { stores } from '../../../script/stores/Stores'; + import PleaseConnect from '../../../components/PleaseConnect.svelte'; const classifier = stores.getClassifier(); // In case of manual classification, variables for evaluation @@ -83,7 +83,7 @@ {#if $state.isInputReady} {:else} - + {/if} {:else} diff --git a/src/pages/training/InsufficientData.svelte b/src/pages/training/InsufficientData.svelte new file mode 100644 index 000000000..2be4d53b5 --- /dev/null +++ b/src/pages/training/InsufficientData.svelte @@ -0,0 +1,19 @@ + + + +
+
+

+ {$t('menu.trainer.notEnoughDataHeader1')} +

+

+ {$t('menu.trainer.notEnoughDataInfoBody')} +

+
+
diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte new file mode 100644 index 000000000..a7b68bb84 --- /dev/null +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -0,0 +1,143 @@ + + + +
+
+ +
+ {#each $gestures as gesture, index} +
+
+
+
+
+

{gesture.name}

+
+ {#if $state.isInputReady} +

+ {(($confidences.get(gesture.ID)?.currentConfidence ?? 0) * 100).toFixed(2)}% +

+ {/if} +
+ {/each} +
+
+
+
+
diff --git a/src/pages/training/NeuralNetworkTrainingPageView.svelte b/src/pages/training/NeuralNetworkTrainingPageView.svelte new file mode 100644 index 000000000..73c07ce1c --- /dev/null +++ b/src/pages/training/NeuralNetworkTrainingPageView.svelte @@ -0,0 +1,54 @@ + + + +
+
+ {#if $model.isTraining} +
+ +
+ {:else} + {$t(trainButtonSimpleLabel)} + {/if} + {#if $loss.length > 0} +
+
+

+ {$t('menu.trainer.isTrainingModelButton')} +

+
+
+ + {/if} +
+
diff --git a/src/pages/training/TrainingPage.svelte b/src/pages/training/TrainingPage.svelte index f67cf34a2..de29f8e79 100644 --- a/src/pages/training/TrainingPage.svelte +++ b/src/pages/training/TrainingPage.svelte @@ -5,168 +5,29 @@ -->
-
-
- -
-
- {#if isUsingKNNModel} - - {/if} - {#if !sufficientData} -
-

- {$t('menu.trainer.notEnoughDataHeader1')} -

-

- {$t('menu.trainer.notEnoughDataInfoBody')} -

-
- {:else} -
- {#if !$model.isTraining} - {#if $filters.length == 0} -

- {$t('menu.trainer.noFilters')} -

- {:else} -
- { - resetLoss(); - trackModelEvent(); - }} - onTrainingIteration={trainingIterationHandler} /> -
- {/if} - {/if} - - {#if $model.isTrained} -

- {$t('menu.trainer.TrainingFinished')} -

-

- {$t('menu.trainer.TrainingFinished.body')} -

- {/if} - {#if $filters.length == 0} -

- {$t('menu.trainer.noFilters')} -

- {/if} -
- {/if} - {#if !isUsingKNNModel} -
- {#if $loss.length > 0 || $model.isTraining} - {#if !CookieManager.hasFeatureFlag('loss-graph')} - {#if $model.isTraining} -
-
-
- -
-

- {$t('menu.trainer.isTrainingModelButton')} -

-
-
- {/if} - {:else} - - {/if} - {/if} -
- {/if} - {#if !$state.isInputConnected} -
- -
- {/if} + {#if !sufficientData} + + {:else} + + {/if} + {#if !$state.isInputConnected} +
+
-
+ {/if}
diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index 05d71dd9e..e062a0117 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -5,23 +5,30 @@ */ import { get, writable } from 'svelte/store'; import { LossTrainingIteration } from '../../components/graphs/LossGraphUtil'; -import { highlightedAxis } from '../../script/stores/uiStore'; +import { highlightedAxis, selectedModel } from '../../script/stores/uiStore'; import Axes from '../../script/domain/Axes'; import KNNNonNormalizedModelTrainer from '../../script/mlmodels/KNNNonNormalizedModelTrainer'; import StaticConfiguration from '../../StaticConfiguration'; import { extractAxisFromTrainingData } from '../../script/utils/graphUtils'; import { stores } from '../../script/stores/Stores'; import { FilterType } from '../../script/domain/FilterTypes'; +import CookieManager from '../../script/CookieManager'; +import { appInsights } from '../../appInsights'; +import ModelRegistry, { ModelInfo } from '../../script/domain/ModelRegistry'; +import LayersModelTrainer from '../../script/mlmodels/LayersModelTrainer'; export const loss = writable([]); -export const resetLoss = () => loss.set([]); -export const trainingIterationHandler = (h: LossTrainingIteration) => { + +const trainingIterationHandler = (h: LossTrainingIteration) => { loss.update(newLoss => { newLoss.push(h); return newLoss; }); }; +/** + * @deprecated + */ export const trainKNNModel = async () => { if (stores.getClassifier().getFilters().count() !== 2) { stores.getClassifier().getFilters().clear(); @@ -29,13 +36,55 @@ export const trainKNNModel = async () => { stores.getClassifier().getFilters().add(FilterType.MEAN); } const model = stores.getClassifier().getModel(); + + trackModelEvent(); await model.train(getKNNTrainer()); }; +/** + * @deprecated + */ const getKNNTrainer = () => { const currentAxis = get(highlightedAxis); const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; + trackModelEvent(); return new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => extractAxisFromTrainingData(data, offset, 3), ); }; + +const trainNNModel = async () => { + loss.set([]); + const modelTrainer = new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, trainingIterationHandler); + await stores.getClassifier().getModel().train(modelTrainer); +} + +// TODO: Replace with name trainKNNModel after removing deprecated trainKNNModel +const trainKNN = async () => { + const currentAxis = get(highlightedAxis); + const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; + const modelTrainer = new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => + extractAxisFromTrainingData(data, offset, 3) // 3 assumes 3 axis + ) + await stores.getClassifier().getModel().train(modelTrainer); +} + +export const trainModel = async (model: ModelInfo) => { + if (ModelRegistry.KNN.id === model.id) { + await trainKNN(); + } else if (ModelRegistry.NeuralNetwork.id === model.id) { + await trainNNModel(); + } + trackModelEvent(); +} + +const trackModelEvent = () => { + if (CookieManager.getComplianceChoices().analytics) { + appInsights.trackEvent({ + name: 'ModelTrained', + properties: { + modelType: get(selectedModel).id, + }, + }); + } +}; \ No newline at end of file diff --git a/src/pages/training/TrainingPageModelView.svelte b/src/pages/training/TrainingPageModelView.svelte new file mode 100644 index 000000000..a7db31af9 --- /dev/null +++ b/src/pages/training/TrainingPageModelView.svelte @@ -0,0 +1,26 @@ + + + + +
+
+
+ +
+ {#if $selectedModel.id === ModelRegistry.KNN.id} + + {:else if $selectedModel.id === ModelRegistry.NeuralNetwork.id} + + {/if} +
+
diff --git a/src/script/domain/ModelRegistry.ts b/src/script/domain/ModelRegistry.ts new file mode 100644 index 000000000..900e8ca78 --- /dev/null +++ b/src/script/domain/ModelRegistry.ts @@ -0,0 +1,23 @@ +export type ModelInfo = { + id: string; + title: string; + label: string; +}; + +class ModelRegistry { + + public static NeuralNetwork: ModelInfo = { + id: 'NN', + title: 'Neural network', + label: 'neural network', + }; + + public static KNN: ModelInfo = { + id: 'KNN', + title: 'KNN', + label: 'KNN', + }; + +} + +export default ModelRegistry; \ No newline at end of file diff --git a/src/script/stores/uiStore.ts b/src/script/stores/uiStore.ts index 19620fa36..58b4b0996 100644 --- a/src/script/stores/uiStore.ts +++ b/src/script/stores/uiStore.ts @@ -18,6 +18,7 @@ import Axes from '../domain/Axes'; import PersistantWritable from '../repository/PersistantWritable'; import { DropdownOption } from '../../components/buttons/Buttons'; import { stores } from './Stores'; +import ModelRegistry, { ModelInfo } from '../domain/ModelRegistry'; let text: (key: string, vars?: object) => string; t.subscribe(t => (text = t)); @@ -127,12 +128,18 @@ export enum MicrobitInteractions { AB, } +/** + * @deprecated + */ export type ModelEntry = { id: string; title: string; label: string; }; +/** + * @deprecated + */ export const availableModels: ModelEntry[] = [ { id: 'NN', @@ -146,13 +153,9 @@ export const availableModels: ModelEntry[] = [ }, ]; -const defaultModel: ModelEntry = availableModels.find(model => model.id === 'NN')!; -if (!defaultModel) { - throw new Error('Default model not found!'); -} +const defaultModel: ModelInfo = ModelRegistry.NeuralNetwork; -// TODO: Should just be model id instead of dropdown option -export const selectedModel = new PersistantWritable( +export const selectedModel = new PersistantWritable( defaultModel, 'selectedModel', ); From 7539399a6995a44c863ca0bab10060666c603d4c Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:23:22 +0200 Subject: [PATCH 053/106] Fix issues with knn --- src/StaticConfiguration.ts | 6 +- .../knngraph/AxesFilterVectorView.svelte | 10 +- .../graphs/knngraph/KNNModelGraphDrawer.ts | 52 +++---- .../graphs/knngraph/KnnModelGraph.svelte | 50 ++----- src/messages/ui.da.json | 1 + src/messages/ui.en.json | 1 + .../training/KnnModelTrainingPageView.svelte | 127 ++++-------------- .../NeuralNetworkTrainingPageView.svelte | 35 ++--- src/pages/training/TrainingPage.ts | 37 +---- 9 files changed, 93 insertions(+), 226 deletions(-) diff --git a/src/StaticConfiguration.ts b/src/StaticConfiguration.ts index 47924edc8..2ea9d5a78 100644 --- a/src/StaticConfiguration.ts +++ b/src/StaticConfiguration.ts @@ -59,9 +59,9 @@ class StaticConfiguration { // Line colors are picked in the order of this array. public static readonly liveGraphColors = [ - '#f9808e', - '#80f98e', - '#808ef9', + '#ff606e', + '#30f09e', + '#3030ff', '#58355E', '#E0FF4F', '#FF2ECC', diff --git a/src/components/graphs/knngraph/AxesFilterVectorView.svelte b/src/components/graphs/knngraph/AxesFilterVectorView.svelte index edebd8098..13162f41c 100644 --- a/src/components/graphs/knngraph/AxesFilterVectorView.svelte +++ b/src/components/graphs/knngraph/AxesFilterVectorView.svelte @@ -137,7 +137,7 @@
{#if $highlightedAxis}
-
+
{/each}
-
- left bracket -
{#each liveFilteredAxesData as val, index} -

+

{val.toFixed(3)}

{/each}
-
- left bracket -
{/if}
diff --git a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts index 2bafa1de3..5725453d4 100644 --- a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts +++ b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts @@ -12,6 +12,8 @@ import { distanceBetween, } from '../../../script/utils/graphUtils'; import { stores } from '../../../script/stores/Stores'; +import { state } from '../../../script/stores/uiStore'; +import { get } from 'svelte/store'; export type GraphDrawConfig = { xRot: number; @@ -37,7 +39,7 @@ class KNNModelGraphDrawer { constructor( private svg: d3.Selection, private classId: string, - ) {} + ) { } public drawLiveData = (drawConfig: GraphDrawConfig, drawData: Point3D) => { if (isNaN(drawData.y)) { @@ -52,31 +54,33 @@ class KNNModelGraphDrawer { id: `live`, }; - this.addPoint(drawableLivePoint, 'live'); + if (get(state).isInputReady) { + this.addPoint(drawableLivePoint, 'live'); - // Draw lines from live point to the nearest neighbours - const predictedPoints = [...this.drawnTrainingPoints] - .sort((a, b) => { - const aDist = distanceBetween(drawableLivePoint.pointTransformed, a); - const bDist = distanceBetween(drawableLivePoint.pointTransformed, b); - return aDist - bDist; - }) - .slice(0, StaticConfiguration.knnNeighbourCount); + // Draw lines from live point to the nearest neighbours + const predictedPoints = [...this.drawnTrainingPoints] + .sort((a, b) => { + const aDist = distanceBetween(drawableLivePoint.pointTransformed, a); + const bDist = distanceBetween(drawableLivePoint.pointTransformed, b); + return aDist - bDist; + }) + .slice(0, StaticConfiguration.knnNeighbourCount); - const lines = this.svg.selectAll(`line.points-class`).data(predictedPoints); - lines - .enter() - .append('line') - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .merge(lines) - .attr('x1', d => drawableLivePoint.pointTransformed.projected.x) - .attr('y1', d => drawableLivePoint.pointTransformed.projected.y) - .attr('x2', d => d.projected.x) - .attr('y2', d => d.projected.y) - .attr('class', `${this.classId} points-class`) - .attr('stroke', '#1a1a1a'); - lines.exit().remove(); + const lines = this.svg.selectAll(`line.points-class`).data(predictedPoints); + lines + .enter() + .append('line') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + .merge(lines) + .attr('x1', d => drawableLivePoint.pointTransformed.projected.x) + .attr('y1', d => drawableLivePoint.pointTransformed.projected.y) + .attr('x2', d => d.projected.x) + .attr('y2', d => d.projected.y) + .attr('class', `${this.classId} points-class`) + .attr('stroke', '#1a1a1a'); + lines.exit().remove(); + } }; public draw(drawConfig: GraphDrawConfig, drawData: Point3D[][][]) { diff --git a/src/components/graphs/knngraph/KnnModelGraph.svelte b/src/components/graphs/knngraph/KnnModelGraph.svelte index 415fd2a67..cf3adeca5 100644 --- a/src/components/graphs/knngraph/KnnModelGraph.svelte +++ b/src/components/graphs/knngraph/KnnModelGraph.svelte @@ -12,10 +12,8 @@ import { extractAxisFromTrainingData } from '../../../script/utils/graphUtils'; import Axes from '../../../script/domain/Axes'; import { TrainingData } from '../../../script/domain/ModelTrainer'; - import { highlightedAxis, state } from '../../../script/stores/uiStore'; - import StaticConfiguration from '../../../StaticConfiguration'; + import { highlightedAxis } from '../../../script/stores/uiStore'; import KnnPointToolTipView from './KnnPointToolTipView.svelte'; - import AxesFilterVectorView from './AxesFilterVectorView.svelte'; import { stores } from '../../../script/stores/Stores'; import { get } from 'svelte/store'; @@ -23,7 +21,9 @@ const classifier = stores.getClassifier(); const gestures = stores.getGestures(); - const confidences = gestures.getConfidences(); + + const canvasWidth = 450; + const canvasHeight = 300; // Cache training data to avoid fetching them again and again const allData = classifierFactory.buildTrainingData( @@ -54,7 +54,7 @@ const controller = new KNNModelGraphController( svgSingle, () => dataGetter(), - { x: 650 / 2, y: 350 / 2 }, + { x: canvasWidth / 2, y: canvasHeight / 2 }, 'd3-3d-single-', axis, ); @@ -78,36 +78,12 @@ }); -
-
- -
- {#each $gestures as gesture, index} -
-
-
-
-
-

{gesture.name}

-
- {#if $state.isInputReady} -

- {(($confidences.get(gesture.ID)?.currentConfidence ?? 0) * 100).toFixed(2)}% -

- {/if} -
- {/each} -
-
-
-
+
+
diff --git a/src/messages/ui.da.json b/src/messages/ui.da.json index 68be5e698..16b46cd17 100644 --- a/src/messages/ui.da.json +++ b/src/messages/ui.da.json @@ -123,6 +123,7 @@ "menu.trainer.TrainingFinished.body": "Gå til Model-siden for at undersøge hvor godt din model virker", "menu.trainer.noFilters": "Du har ikke valgt nogle filtre. Der skal mindst være valgt 1 filter for at kunne træne en model", "menu.trainer.isTrainingModelButton": "Træner model...", + "menu.trainer.knn.onlyTwoFilters": "For at kunne se visualisering skal du vælge præcist 2 filtre", "menu.model.helpHeading": "Model", "menu.model.helpBody": "Modellen kan bruges i et interaktivt system. Her bruger vi den trænede model til at forudsige bevægelser. Du kan tilslutte endnu en micro:bit og få den til at reagere på de bevægelser du laver.", "menu.model.noModel": "Ingen model", diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 8cbe68866..e71ec71c0 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -123,6 +123,7 @@ "menu.trainer.TrainingFinished.body": "Go to the Model-page to examine how well your model works", "menu.trainer.noFilters": "No filters have been selected. You must enable at least 1 filter in order to train a model", "menu.trainer.isTrainingModelButton": "Training model", + "menu.trainer.knn.onlyTwoFilters": "In order to visualize the data, you need to select exactly two filters", "menu.model.helpHeading": "Model", "menu.model.helpBody": "The model can be used in an interactive system. Here we use the trained model to predict gestures. You can connect another micro:bit and make it respond to the predicted gestures.", "menu.model.noModel": "No model", diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte index a7b68bb84..74f68fa7b 100644 --- a/src/pages/training/KnnModelTrainingPageView.svelte +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -4,111 +4,38 @@ SPDX-License-Identifier: MIT --> -
+
@@ -131,13 +58,13 @@ {/each}
-
-
+ {#if $filters.length == 2} + + {:else} +
+

+ {$t('menu.trainer.knn.onlyTwoFilters')} +

+
+ {/if}
diff --git a/src/pages/training/NeuralNetworkTrainingPageView.svelte b/src/pages/training/NeuralNetworkTrainingPageView.svelte index 73c07ce1c..46c4c9cb3 100644 --- a/src/pages/training/NeuralNetworkTrainingPageView.svelte +++ b/src/pages/training/NeuralNetworkTrainingPageView.svelte @@ -28,27 +28,16 @@
-
- {#if $model.isTraining} -
- -
- {:else} - {$t(trainButtonSimpleLabel)} - {/if} - {#if $loss.length > 0} -
-
-

- {$t('menu.trainer.isTrainingModelButton')} -

-
-
- - {/if} -
+ {#if $model.isTraining} +
+ +
+ {:else} + {$t(trainButtonSimpleLabel)} + {/if} + {#if $loss.length > 0} + + {/if}
diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index e062a0117..92e7505c5 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -11,7 +11,6 @@ import KNNNonNormalizedModelTrainer from '../../script/mlmodels/KNNNonNormalized import StaticConfiguration from '../../StaticConfiguration'; import { extractAxisFromTrainingData } from '../../script/utils/graphUtils'; import { stores } from '../../script/stores/Stores'; -import { FilterType } from '../../script/domain/FilterTypes'; import CookieManager from '../../script/CookieManager'; import { appInsights } from '../../appInsights'; import ModelRegistry, { ModelInfo } from '../../script/domain/ModelRegistry'; @@ -26,41 +25,16 @@ const trainingIterationHandler = (h: LossTrainingIteration) => { }); }; -/** - * @deprecated - */ -export const trainKNNModel = async () => { - if (stores.getClassifier().getFilters().count() !== 2) { - stores.getClassifier().getFilters().clear(); - stores.getClassifier().getFilters().add(FilterType.MAX); - stores.getClassifier().getFilters().add(FilterType.MEAN); - } - const model = stores.getClassifier().getModel(); - - trackModelEvent(); - await model.train(getKNNTrainer()); -}; - -/** - * @deprecated - */ -const getKNNTrainer = () => { - const currentAxis = get(highlightedAxis); - const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; - trackModelEvent(); - return new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => - extractAxisFromTrainingData(data, offset, 3), - ); -}; - const trainNNModel = async () => { loss.set([]); const modelTrainer = new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, trainingIterationHandler); await stores.getClassifier().getModel().train(modelTrainer); } -// TODO: Replace with name trainKNNModel after removing deprecated trainKNNModel -const trainKNN = async () => { +const trainKNNModel = async () => { + if (get(highlightedAxis) === undefined) { + highlightedAxis.set(Axes.X); + } const currentAxis = get(highlightedAxis); const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; const modelTrainer = new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => @@ -70,8 +44,9 @@ const trainKNN = async () => { } export const trainModel = async (model: ModelInfo) => { + highlightedAxis.set(undefined); if (ModelRegistry.KNN.id === model.id) { - await trainKNN(); + await trainKNNModel(); } else if (ModelRegistry.NeuralNetwork.id === model.id) { await trainNNModel(); } From 853275b6a1839f3e0ebc360f217b23e4745ed9ef Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:30:24 +0200 Subject: [PATCH 054/106] embed the controls in plot --- .../graphs/knngraph/KnnModelGraphSvgWithControls.svelte | 8 +++++--- src/pages/training/TrainingPageModelView.svelte | 6 ++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte b/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte index 52d209cbe..0758482ca 100644 --- a/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte +++ b/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte @@ -66,9 +66,11 @@ }; -
- - +
+
+ + +
-
-
- -
+
+ {#if $selectedModel.id === ModelRegistry.KNN.id} {:else if $selectedModel.id === ModelRegistry.NeuralNetwork.id} From 23576b2a073af6727ecfe412d5e30fbe629e78c6 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:37:01 +0200 Subject: [PATCH 055/106] fix test --- src/script/domain/ModelRegistry.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/script/domain/ModelRegistry.ts b/src/script/domain/ModelRegistry.ts index 900e8ca78..03197b424 100644 --- a/src/script/domain/ModelRegistry.ts +++ b/src/script/domain/ModelRegistry.ts @@ -1,3 +1,8 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ export type ModelInfo = { id: string; title: string; From fe92aa079806e060d231ecddd9cd27da1741fb14 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:37:14 +0200 Subject: [PATCH 056/106] Ran prettier --- .../graphs/knngraph/KNNModelGraphDrawer.ts | 2 +- src/pages/training/TrainingPage.ts | 74 ++++++++++--------- src/script/domain/ModelRegistry.ts | 30 ++++---- 3 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts index 5725453d4..0b7ee01fd 100644 --- a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts +++ b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts @@ -39,7 +39,7 @@ class KNNModelGraphDrawer { constructor( private svg: d3.Selection, private classId: string, - ) { } + ) {} public drawLiveData = (drawConfig: GraphDrawConfig, drawData: Point3D) => { if (isNaN(drawData.y)) { diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index 92e7505c5..ba95d52d5 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -19,47 +19,51 @@ import LayersModelTrainer from '../../script/mlmodels/LayersModelTrainer'; export const loss = writable([]); const trainingIterationHandler = (h: LossTrainingIteration) => { - loss.update(newLoss => { - newLoss.push(h); - return newLoss; - }); + loss.update(newLoss => { + newLoss.push(h); + return newLoss; + }); }; const trainNNModel = async () => { - loss.set([]); - const modelTrainer = new LayersModelTrainer(StaticConfiguration.layersModelTrainingSettings, trainingIterationHandler); - await stores.getClassifier().getModel().train(modelTrainer); -} + loss.set([]); + const modelTrainer = new LayersModelTrainer( + StaticConfiguration.layersModelTrainingSettings, + trainingIterationHandler, + ); + await stores.getClassifier().getModel().train(modelTrainer); +}; const trainKNNModel = async () => { - if (get(highlightedAxis) === undefined) { - highlightedAxis.set(Axes.X); - } - const currentAxis = get(highlightedAxis); - const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; - const modelTrainer = new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => - extractAxisFromTrainingData(data, offset, 3) // 3 assumes 3 axis - ) - await stores.getClassifier().getModel().train(modelTrainer); -} + if (get(highlightedAxis) === undefined) { + highlightedAxis.set(Axes.X); + } + const currentAxis = get(highlightedAxis); + const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; + const modelTrainer = new KNNNonNormalizedModelTrainer( + StaticConfiguration.knnNeighbourCount, + data => extractAxisFromTrainingData(data, offset, 3), // 3 assumes 3 axis + ); + await stores.getClassifier().getModel().train(modelTrainer); +}; export const trainModel = async (model: ModelInfo) => { - highlightedAxis.set(undefined); - if (ModelRegistry.KNN.id === model.id) { - await trainKNNModel(); - } else if (ModelRegistry.NeuralNetwork.id === model.id) { - await trainNNModel(); - } - trackModelEvent(); -} + highlightedAxis.set(undefined); + if (ModelRegistry.KNN.id === model.id) { + await trainKNNModel(); + } else if (ModelRegistry.NeuralNetwork.id === model.id) { + await trainNNModel(); + } + trackModelEvent(); +}; const trackModelEvent = () => { - if (CookieManager.getComplianceChoices().analytics) { - appInsights.trackEvent({ - name: 'ModelTrained', - properties: { - modelType: get(selectedModel).id, - }, - }); - } -}; \ No newline at end of file + if (CookieManager.getComplianceChoices().analytics) { + appInsights.trackEvent({ + name: 'ModelTrained', + properties: { + modelType: get(selectedModel).id, + }, + }); + } +}; diff --git a/src/script/domain/ModelRegistry.ts b/src/script/domain/ModelRegistry.ts index 03197b424..95db92cde 100644 --- a/src/script/domain/ModelRegistry.ts +++ b/src/script/domain/ModelRegistry.ts @@ -4,25 +4,23 @@ * SPDX-License-Identifier: MIT */ export type ModelInfo = { - id: string; - title: string; - label: string; + id: string; + title: string; + label: string; }; class ModelRegistry { + public static NeuralNetwork: ModelInfo = { + id: 'NN', + title: 'Neural network', + label: 'neural network', + }; - public static NeuralNetwork: ModelInfo = { - id: 'NN', - title: 'Neural network', - label: 'neural network', - }; - - public static KNN: ModelInfo = { - id: 'KNN', - title: 'KNN', - label: 'KNN', - }; - + public static KNN: ModelInfo = { + id: 'KNN', + title: 'KNN', + label: 'KNN', + }; } -export default ModelRegistry; \ No newline at end of file +export default ModelRegistry; From 42f7146c91ff73574d933104b1fac59c9417b58f Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:37:59 +0200 Subject: [PATCH 057/106] used unused variable --- .../graphs/knngraph/KnnModelGraphSvgWithControls.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte b/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte index 0758482ca..d2e08e11c 100644 --- a/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte +++ b/src/components/graphs/knngraph/KnnModelGraphSvgWithControls.svelte @@ -66,7 +66,7 @@ }; -
+
From 0bbc1e016156395d493b3403d352e178eabd8448 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:38:58 +0200 Subject: [PATCH 058/106] Removed irrelevant comment --- src/pages/training/KnnModelTrainingPageView.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte index 74f68fa7b..9fd3dd543 100644 --- a/src/pages/training/KnnModelTrainingPageView.svelte +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -19,7 +19,7 @@ const gestures = stores.getGestures(); const confidences = gestures.getConfidences(); const filters = classifier.getFilters(); - // Should be in KNNModelGraph instead + onMount(() => { trainModel(ModelRegistry.KNN); }); From 0f3875137c457e87f4d5bb0ffd611c2c1a6a0783 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 4 Jun 2024 21:44:05 +0200 Subject: [PATCH 059/106] Remove deprecated objects --- src/pages/training/TrainModelButton.svelte | 19 +++----------- src/pages/training/TrainModelButton.ts | 14 ++++------ src/pages/training/TrainingPageTabs.svelte | 7 ++--- src/script/domain/ModelRegistry.ts | 30 ++++++++++++---------- src/script/stores/uiStore.ts | 26 ------------------- 5 files changed, 30 insertions(+), 66 deletions(-) diff --git a/src/pages/training/TrainModelButton.svelte b/src/pages/training/TrainModelButton.svelte index 1e1628a19..565ebbc7f 100644 --- a/src/pages/training/TrainModelButton.svelte +++ b/src/pages/training/TrainModelButton.svelte @@ -6,29 +6,18 @@
- + {#if showFilterList} + + {/if} {#if $selectedModel.id === ModelRegistry.KNN.id} {:else if $selectedModel.id === ModelRegistry.NeuralNetwork.id} diff --git a/src/pages/training/TrainingPageTabs.svelte b/src/pages/training/TrainingPageTabs.svelte index 3c82fdd97..97e2d4709 100644 --- a/src/pages/training/TrainingPageTabs.svelte +++ b/src/pages/training/TrainingPageTabs.svelte @@ -4,9 +4,18 @@ SPDX-License-Identifier: MIT --> - -
- -
-

Neural Network

-
- -
-

KNN Model

+{#if showTabBar} + +
+ +
+

Neural Network

+
+ +
+

KNN Model

+
-
- + +{:else} + +
+ { + navigate(Paths.FILTERS); + }}> + {$t('content.trainer.controlbar.filters')} + + +{/if} diff --git a/src/script/utils/graphUtils.ts b/src/script/utils/graphUtils.ts index 9bb805a8d..85a78eaac 100644 --- a/src/script/utils/graphUtils.ts +++ b/src/script/utils/graphUtils.ts @@ -124,7 +124,4 @@ export const distanceBetween = (point1: Point3D, point2: Point3D): number => { return Math.sqrt(squaredDistance); }; -export type LossTrainingIteration = { - loss: number; - epoch: number; -}; + From cf4d8bfa1976a5bffbec86b6f7320c8bae703428 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 9 Jul 2024 21:26:09 +0200 Subject: [PATCH 070/106] Prettier --- src/script/utils/graphUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/script/utils/graphUtils.ts b/src/script/utils/graphUtils.ts index 85a78eaac..80105acb6 100644 --- a/src/script/utils/graphUtils.ts +++ b/src/script/utils/graphUtils.ts @@ -123,5 +123,3 @@ export const distanceBetween = (point1: Point3D, point2: Point3D): number => { return Math.sqrt(squaredDistance); }; - - From 694d87ee06b7b61848a67d8af4e86b4ab1e8e17d Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 10 Jul 2024 22:40:14 +0200 Subject: [PATCH 071/106] Make K configurable --- features.json | 2 +- src/StaticConfiguration.ts | 2 +- .../graphs/knngraph/KNNModelGraphDrawer.ts | 8 +++- src/messages/ui.da.json | 1 + src/messages/ui.en.json | 1 + .../training/KnnModelTrainingPageView.svelte | 43 ++++++++++++++++++- src/pages/training/TrainModelButton.ts | 3 +- src/pages/training/TrainingPage.ts | 5 ++- src/script/domain/Repositories.ts | 3 ++ src/script/domain/stores/gesture/Gestures.ts | 17 ++++++-- .../LocalStorageClassifierRepository.ts | 4 ++ src/script/stores/Stores.ts | 2 +- src/script/stores/knnConfig.ts | 15 +++++++ 13 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 src/script/stores/knnConfig.ts diff --git a/features.json b/features.json index c8fb448b5..478984906 100644 --- a/features.json +++ b/features.json @@ -2,4 +2,4 @@ "title": "Learning tool", "knnModel": true, "lossGraph": true -} \ No newline at end of file +} diff --git a/src/StaticConfiguration.ts b/src/StaticConfiguration.ts index 2ea9d5a78..739ebbfa6 100644 --- a/src/StaticConfiguration.ts +++ b/src/StaticConfiguration.ts @@ -132,6 +132,6 @@ class StaticConfiguration { noOfUnits: 16, // size of hidden layer }; - public static readonly knnNeighbourCount = 3; + public static readonly defaultKnnNeighbourCount = 3; } export default StaticConfiguration; diff --git a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts index 4ec2ac491..d208a66aa 100644 --- a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts +++ b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts @@ -14,6 +14,7 @@ import { import { stores } from '../../../script/stores/Stores'; import { state } from '../../../script/stores/uiStore'; import { get } from 'svelte/store'; +import { knnConfig } from '../../../script/stores/knnConfig'; export type GraphDrawConfig = { xRot: number; @@ -40,7 +41,7 @@ class KNNModelGraphDrawer { constructor( private svg: d3.Selection, private classId: string, - ) {} + ) { } public drawLiveData = (drawConfig: GraphDrawConfig, drawData: Point3D) => { if (isNaN(drawData.y)) { @@ -56,6 +57,9 @@ class KNNModelGraphDrawer { color, id: `live`, }; + if (isNaN(drawableLivePoint.pointTransformed.projected.x)) { + return; // May happen if the model has just been trained. + } if (get(state).isInputReady) { this.addPoint(drawableLivePoint, 'live'); @@ -67,7 +71,7 @@ class KNNModelGraphDrawer { const bDist = distanceBetween(drawableLivePoint.pointTransformed, b); return aDist - bDist; }) - .slice(0, StaticConfiguration.knnNeighbourCount); + .slice(0, get(knnConfig).k); const lines = this.svg.selectAll(`line.points-class`).data(predictedPoints); lines diff --git a/src/messages/ui.da.json b/src/messages/ui.da.json index 16b46cd17..05072efba 100644 --- a/src/messages/ui.da.json +++ b/src/messages/ui.da.json @@ -56,6 +56,7 @@ "content.trainer.failure.todo": "Gå tilbage til datasiden og ændr i din data.", "content.trainer.controlbar.filters": "Filtre", "content.trianer.lossGraph.title": "Loss over tid", + "content.trainer.knn.neighbours": "Naboer", "content.filters.NoDataHeader": "Der er ikke noget data", "content.filters.NoDataBody": "Gå til Data-siden for at indsamle data.", "content.filters.max.title": "Maksværdier", diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index e71ec71c0..f3b6502b6 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -56,6 +56,7 @@ "content.trainer.failure.todo": "Return to the data page and change your data.", "content.trainer.controlbar.filters": "Filters", "content.trianer.lossGraph.title": "Loss over time", + "content.trainer.knn.neighbours": "Neighbours", "content.filters.NoDataHeader": "No available data", "content.filters.NoDataBody": "Go to the Data page to collect data samples.", "content.filters.max.title": "Max values", diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte index 18d1b2f27..bb3efd69c 100644 --- a/src/pages/training/KnnModelTrainingPageView.svelte +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -14,12 +14,18 @@ import Axes from '../../script/domain/Axes'; import { t } from '../../i18n'; import { onMount } from 'svelte'; - + import Range from '../../components/Range.svelte'; + import { knnConfig } from '../../script/stores/knnConfig'; + import { get } from 'svelte/store'; + import StandardButton from '../../components/buttons/StandardButton.svelte'; const classifier = stores.getClassifier(); const gestures = stores.getGestures(); const confidences = gestures.getConfidences(); const filters = classifier.getFilters(); + console.log('GESTURES:', $gestures); + console.log('CONFIDENCES:', $confidences); + onMount(() => { trainModel(ModelRegistry.KNN); }); @@ -31,12 +37,47 @@ trainModel(ModelRegistry.KNN); } } + + const maxK = Math.min( + $gestures.length * StaticConfiguration.minNoOfRecordingsPerGesture, + 10, + ); + const changeK = (amount: number) => { + const newVal = Math.min(Math.max($knnConfig.k + amount, 1), 10); + knnConfig.set({ k: newVal }); + trainModel(ModelRegistry.KNN); + }; + $: { + if ($knnConfig.k > maxK) { + knnConfig.set({ k: maxK }); + } + } + + console.log($confidences);
+
+
+
changeK(-1)} + class="bg-secondary font-bold text-secondarytext cursor-pointer select-none hover:bg-opacity-60 border-primary border-r-1 content-center px-2 rounded-l-xl"> + - +
+
changeK(1)} + class="bg-secondary border-primary text-secondarytext cursor-pointer hover:bg-opacity-60 select-none content-center px-2 rounded-r-xl"> + + +
+
+

+ {$knnConfig.k} + {$t('content.trainer.knn.neighbours')} +

+
{#each $gestures as gesture, index} diff --git a/src/pages/training/TrainModelButton.ts b/src/pages/training/TrainModelButton.ts index bd95cdf06..41ca3cb76 100644 --- a/src/pages/training/TrainModelButton.ts +++ b/src/pages/training/TrainModelButton.ts @@ -19,6 +19,7 @@ import LayersModelTrainer, { import { FilterType } from '../../script/domain/FilterTypes'; import Filters from '../../script/domain/Filters'; import ModelRegistry, { ModelInfo } from '../../script/domain/ModelRegistry'; +import { knnConfig } from '../../script/stores/knnConfig'; const classifier = stores.getClassifier(); @@ -36,7 +37,7 @@ export const getModelTrainer = ( const currentAxis = get(highlightedAxis); if (model.id === ModelRegistry.KNN.id) { const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; - return new KNNNonNormalizedModelTrainer(StaticConfiguration.knnNeighbourCount, data => + return new KNNNonNormalizedModelTrainer(get(knnConfig).k, data => extractAxisFromTrainingData(data, offset, 3), ); } diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index bf2b29da2..2844449a9 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -16,6 +16,8 @@ import ModelRegistry, { ModelInfo } from '../../script/domain/ModelRegistry'; import LayersModelTrainer, { LossTrainingIteration, } from '../../script/mlmodels/LayersModelTrainer'; +import { knnConfig } from '../../script/stores/knnConfig'; +import Logger from '../../script/utils/Logger'; export const loss = writable([]); @@ -42,13 +44,14 @@ const trainKNNModel = async () => { const currentAxis = get(highlightedAxis); const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; const modelTrainer = new KNNNonNormalizedModelTrainer( - StaticConfiguration.knnNeighbourCount, + get(knnConfig).k, data => extractAxisFromTrainingData(data, offset, 3), // 3 assumes 3 axis ); await stores.getClassifier().getModel().train(modelTrainer); }; export const trainModel = async (model: ModelInfo) => { + Logger.log('TrainingPage', "Training new model: " + model.title); highlightedAxis.set(undefined); if (ModelRegistry.KNN.id === model.id) { await trainKNNModel(); diff --git a/src/script/domain/Repositories.ts b/src/script/domain/Repositories.ts index b1340367b..6c0a5ca2c 100644 --- a/src/script/domain/Repositories.ts +++ b/src/script/domain/Repositories.ts @@ -3,13 +3,16 @@ * * SPDX-License-Identifier: MIT */ +import { Writable } from 'svelte/store'; import ClassifierRepository from './ClassifierRepository'; import GestureRepository from './GestureRepository'; +import { GestureID } from './stores/gesture/Gesture'; interface Repositories { getGestureRepository(): GestureRepository; getClassifierRepository(): ClassifierRepository; + } export default Repositories; diff --git a/src/script/domain/stores/gesture/Gestures.ts b/src/script/domain/stores/gesture/Gestures.ts index 072d7a0bc..19f7958f2 100644 --- a/src/script/domain/stores/gesture/Gestures.ts +++ b/src/script/domain/stores/gesture/Gestures.ts @@ -15,6 +15,7 @@ import { import Gesture, { Confidence, GestureData, GestureID, GestureOutput } from './Gesture'; import StaticConfiguration from '../../../../StaticConfiguration'; import GestureRepository from '../../GestureRepository'; +import ClassifierRepository from '../../ClassifierRepository'; export type PersistantGestureData = { name: string; @@ -38,19 +39,27 @@ class Gestures implements Readable { private repository: GestureRepository; private confidenceStore: Readable>; - constructor(repository: GestureRepository) { + constructor(classifierRepository: ClassifierRepository, repository: GestureRepository) { this.repository = repository; Gestures.subscribableGestures = writable(); this.repository.subscribe(storeArray => { Gestures.subscribableGestures.set(storeArray); + }); this.confidenceStore = derived([this, ...this.getGestures()], stores => { const confidenceMap: Map = new Map(); const [_, ...gestureStores] = stores; - gestureStores.forEach(store => { - confidenceMap.set(store.ID, store.confidence); + const thiz = stores[0] as GestureData[]; + thiz.forEach(gesture => { + // TODO: The following ought to be fixed. See https://github.com/microbit-foundation/cctd-ml-machine/issues/508 + const store = gestureStores.find(store => store.ID === gesture.ID)?.confidence || { currentConfidence: 0, requiredConfidence: 0, isConfident: false }; + confidenceMap.set(gesture.ID, { ...store, currentConfidence: classifierRepository.getGestureConfidence(gesture.ID).getCurrentConfidence() }); }); + + /*gestureStores.forEach(store => { + confidenceMap.set(store.ID, store.confidence); + });*/ return confidenceMap; }); } @@ -93,7 +102,7 @@ class Gestures implements Readable { const newId = Date.now(); const color = StaticConfiguration.gestureColors[ - this.getNumberOfGestures() % StaticConfiguration.gestureColors.length + this.getNumberOfGestures() % StaticConfiguration.gestureColors.length ]; return this.addGestureFromPersistedData({ ID: newId, diff --git a/src/script/repository/LocalStorageClassifierRepository.ts b/src/script/repository/LocalStorageClassifierRepository.ts index e2cf659bc..ea479ef66 100644 --- a/src/script/repository/LocalStorageClassifierRepository.ts +++ b/src/script/repository/LocalStorageClassifierRepository.ts @@ -128,6 +128,10 @@ class LocalStorageClassifierRepository implements ClassifierRepository { derivedConfidence, ); } + + public getConfidences(): Writable> { + return LocalStorageClassifierRepository.confidences; + } } export default LocalStorageClassifierRepository; diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index ad67ff00d..db97eb2c2 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -41,7 +41,7 @@ class Stores implements Readable { this.engine = undefined; const repositories: Repositories = new LocalStorageRepositories(); this.classifier = repositories.getClassifierRepository().getClassifier(); - this.gestures = new Gestures(repositories.getGestureRepository()); + this.gestures = new Gestures(repositories.getClassifierRepository(), repositories.getGestureRepository()); } public subscribe( diff --git a/src/script/stores/knnConfig.ts b/src/script/stores/knnConfig.ts new file mode 100644 index 000000000..5cfe6dc2a --- /dev/null +++ b/src/script/stores/knnConfig.ts @@ -0,0 +1,15 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +import { persistantWritable } from "./storeUtil"; +import StaticConfiguration from "../../StaticConfiguration"; + +export type KNNSettings = { + k: number; +}; + +export const knnConfig = persistantWritable("knnConfig", { + k: StaticConfiguration.defaultKnnNeighbourCount, +}); \ No newline at end of file From 7dbe1510455bedc4d5db2815b163e21adc6e2335 Mon Sep 17 00:00:00 2001 From: r59q Date: Thu, 11 Jul 2024 10:29:45 +0200 Subject: [PATCH 072/106] Prettier --- .../graphs/knngraph/KNNModelGraphDrawer.ts | 2 +- src/pages/training/TrainingPage.ts | 2 +- src/script/domain/Repositories.ts | 1 - src/script/domain/stores/gesture/Gestures.ts | 17 +++++++++++++---- src/script/stores/Stores.ts | 5 ++++- src/script/stores/knnConfig.ts | 12 ++++++------ 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts index d208a66aa..307a8b735 100644 --- a/src/components/graphs/knngraph/KNNModelGraphDrawer.ts +++ b/src/components/graphs/knngraph/KNNModelGraphDrawer.ts @@ -41,7 +41,7 @@ class KNNModelGraphDrawer { constructor( private svg: d3.Selection, private classId: string, - ) { } + ) {} public drawLiveData = (drawConfig: GraphDrawConfig, drawData: Point3D) => { if (isNaN(drawData.y)) { diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index 2844449a9..2fd277ae3 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -51,7 +51,7 @@ const trainKNNModel = async () => { }; export const trainModel = async (model: ModelInfo) => { - Logger.log('TrainingPage', "Training new model: " + model.title); + Logger.log('TrainingPage', 'Training new model: ' + model.title); highlightedAxis.set(undefined); if (ModelRegistry.KNN.id === model.id) { await trainKNNModel(); diff --git a/src/script/domain/Repositories.ts b/src/script/domain/Repositories.ts index 6c0a5ca2c..4782d8f69 100644 --- a/src/script/domain/Repositories.ts +++ b/src/script/domain/Repositories.ts @@ -12,7 +12,6 @@ interface Repositories { getGestureRepository(): GestureRepository; getClassifierRepository(): ClassifierRepository; - } export default Repositories; diff --git a/src/script/domain/stores/gesture/Gestures.ts b/src/script/domain/stores/gesture/Gestures.ts index 19f7958f2..f487bf1a6 100644 --- a/src/script/domain/stores/gesture/Gestures.ts +++ b/src/script/domain/stores/gesture/Gestures.ts @@ -44,7 +44,6 @@ class Gestures implements Readable { Gestures.subscribableGestures = writable(); this.repository.subscribe(storeArray => { Gestures.subscribableGestures.set(storeArray); - }); this.confidenceStore = derived([this, ...this.getGestures()], stores => { const confidenceMap: Map = new Map(); @@ -53,8 +52,18 @@ class Gestures implements Readable { const thiz = stores[0] as GestureData[]; thiz.forEach(gesture => { // TODO: The following ought to be fixed. See https://github.com/microbit-foundation/cctd-ml-machine/issues/508 - const store = gestureStores.find(store => store.ID === gesture.ID)?.confidence || { currentConfidence: 0, requiredConfidence: 0, isConfident: false }; - confidenceMap.set(gesture.ID, { ...store, currentConfidence: classifierRepository.getGestureConfidence(gesture.ID).getCurrentConfidence() }); + const store = gestureStores.find(store => store.ID === gesture.ID) + ?.confidence || { + currentConfidence: 0, + requiredConfidence: 0, + isConfident: false, + }; + confidenceMap.set(gesture.ID, { + ...store, + currentConfidence: classifierRepository + .getGestureConfidence(gesture.ID) + .getCurrentConfidence(), + }); }); /*gestureStores.forEach(store => { @@ -102,7 +111,7 @@ class Gestures implements Readable { const newId = Date.now(); const color = StaticConfiguration.gestureColors[ - this.getNumberOfGestures() % StaticConfiguration.gestureColors.length + this.getNumberOfGestures() % StaticConfiguration.gestureColors.length ]; return this.addGestureFromPersistedData({ ID: newId, diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index db97eb2c2..a9f5aa552 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -41,7 +41,10 @@ class Stores implements Readable { this.engine = undefined; const repositories: Repositories = new LocalStorageRepositories(); this.classifier = repositories.getClassifierRepository().getClassifier(); - this.gestures = new Gestures(repositories.getClassifierRepository(), repositories.getGestureRepository()); + this.gestures = new Gestures( + repositories.getClassifierRepository(), + repositories.getGestureRepository(), + ); } public subscribe( diff --git a/src/script/stores/knnConfig.ts b/src/script/stores/knnConfig.ts index 5cfe6dc2a..bb7256633 100644 --- a/src/script/stores/knnConfig.ts +++ b/src/script/stores/knnConfig.ts @@ -3,13 +3,13 @@ * * SPDX-License-Identifier: MIT */ -import { persistantWritable } from "./storeUtil"; -import StaticConfiguration from "../../StaticConfiguration"; +import { persistantWritable } from './storeUtil'; +import StaticConfiguration from '../../StaticConfiguration'; export type KNNSettings = { - k: number; + k: number; }; -export const knnConfig = persistantWritable("knnConfig", { - k: StaticConfiguration.defaultKnnNeighbourCount, -}); \ No newline at end of file +export const knnConfig = persistantWritable('knnConfig', { + k: StaticConfiguration.defaultKnnNeighbourCount, +}); From 3fc60ea45f16b97f4ce9a97b1c19272aeb9abbbf Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 16 Jul 2024 22:23:07 +0200 Subject: [PATCH 073/106] Remove unused imports --- src/script/domain/Repositories.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/script/domain/Repositories.ts b/src/script/domain/Repositories.ts index 4782d8f69..b1340367b 100644 --- a/src/script/domain/Repositories.ts +++ b/src/script/domain/Repositories.ts @@ -3,10 +3,8 @@ * * SPDX-License-Identifier: MIT */ -import { Writable } from 'svelte/store'; import ClassifierRepository from './ClassifierRepository'; import GestureRepository from './GestureRepository'; -import { GestureID } from './stores/gesture/Gesture'; interface Repositories { getGestureRepository(): GestureRepository; From 67c98538a1fecf2cb6cfae964184a2cfe3fe89e2 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 16 Jul 2024 22:42:41 +0200 Subject: [PATCH 074/106] Cleanup: Remove logs --- .../training/KnnModelTrainingPageView.svelte | 17 +++++------------ .../LocalStorageClassifierRepository.ts | 8 ++++++-- .../repository/LocalStorageGestureRepository.ts | 10 ++++++++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte index bb3efd69c..2a812a4ed 100644 --- a/src/pages/training/KnnModelTrainingPageView.svelte +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -14,18 +14,12 @@ import Axes from '../../script/domain/Axes'; import { t } from '../../i18n'; import { onMount } from 'svelte'; - import Range from '../../components/Range.svelte'; import { knnConfig } from '../../script/stores/knnConfig'; - import { get } from 'svelte/store'; - import StandardButton from '../../components/buttons/StandardButton.svelte'; const classifier = stores.getClassifier(); const gestures = stores.getGestures(); const confidences = gestures.getConfidences(); const filters = classifier.getFilters(); - console.log('GESTURES:', $gestures); - console.log('CONFIDENCES:', $confidences); - onMount(() => { trainModel(ModelRegistry.KNN); }); @@ -38,12 +32,13 @@ } } - const maxK = Math.min( - $gestures.length * StaticConfiguration.minNoOfRecordingsPerGesture, - 10, + const noOfRecordings = $gestures.reduce( + (acc, gesture) => acc + gesture.recordings.length, + 0, ); + const maxK = noOfRecordings; const changeK = (amount: number) => { - const newVal = Math.min(Math.max($knnConfig.k + amount, 1), 10); + const newVal = Math.max($knnConfig.k + amount, 1); knnConfig.set({ k: newVal }); trainModel(ModelRegistry.KNN); }; @@ -52,8 +47,6 @@ knnConfig.set({ k: maxK }); } } - - console.log($confidences);
(trainer: ModelTrainer) => this.trainModel(trainer); } - private setGestureConfidence(gestureId: GestureID, confidence: number) { + public setGestureConfidence(gestureId: GestureID, confidence: number) { if (confidence < 0 || confidence > 1) { throw new Error('Cannot set gesture confidence. Must be in the range 0.0-1.0'); } @@ -120,7 +120,7 @@ class LocalStorageClassifierRepository implements ClassifierRepository { if (confidenceStore.has(gestureId)) { return confidenceStore.get(gestureId) as number; } - return 0; + throw new Error("No confidence found for gesture with id '" + gestureId + "'"); }, ); return new GestureConfidence( @@ -129,6 +129,10 @@ class LocalStorageClassifierRepository implements ClassifierRepository { ); } + public hasGestureConfidence(gestureId: number): boolean { + return get(LocalStorageClassifierRepository.confidences).has(gestureId); + } + public getConfidences(): Writable> { return LocalStorageClassifierRepository.confidences; } diff --git a/src/script/repository/LocalStorageGestureRepository.ts b/src/script/repository/LocalStorageGestureRepository.ts index b1185318d..21259ea30 100644 --- a/src/script/repository/LocalStorageGestureRepository.ts +++ b/src/script/repository/LocalStorageGestureRepository.ts @@ -14,7 +14,7 @@ import { stores } from '../stores/Stores'; class LocalStorageGestureRepository implements GestureRepository { private readonly LOCAL_STORAGE_KEY = 'gestureData'; private static gestureStore: Writable; - constructor(private modelRepository: LocalStorageClassifierRepository) { + constructor(private classifierRepository: LocalStorageClassifierRepository) { LocalStorageGestureRepository.gestureStore = writable([]); LocalStorageGestureRepository.gestureStore.set(this.getPersistedGestures()); } @@ -105,9 +105,15 @@ class LocalStorageGestureRepository implements GestureRepository { const store = this.buildPersistedGestureStore(persistedData); // TODO: The classifier object should be accessed through the repository, not the store. This cannot be done until the classifier is cached. const onRecordingsChanged = () => stores.getClassifier().getModel().markAsUntrained(); + + if (!this.classifierRepository.hasGestureConfidence(get(store).ID)) { + this.classifierRepository.setGestureConfidence(get(store).ID, 0); + } + const confidence = this.classifierRepository.getGestureConfidence(get(store).ID); + return new Gesture( store, - this.modelRepository.getGestureConfidence(get(store).ID), + confidence, onRecordingsChanged, ); } From 25b21e7adfd71ad9ac76389551ff965c3e5c38c7 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 30 Jul 2024 19:14:35 +0200 Subject: [PATCH 075/106] PRettier --- src/script/domain/stores/gesture/Gestures.ts | 1 + src/script/repository/LocalStorageGestureRepository.ts | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/script/domain/stores/gesture/Gestures.ts b/src/script/domain/stores/gesture/Gestures.ts index f487bf1a6..9c3ecdeb2 100644 --- a/src/script/domain/stores/gesture/Gestures.ts +++ b/src/script/domain/stores/gesture/Gestures.ts @@ -45,6 +45,7 @@ class Gestures implements Readable { this.repository.subscribe(storeArray => { Gestures.subscribableGestures.set(storeArray); }); + this.confidenceStore = derived([this, ...this.getGestures()], stores => { const confidenceMap: Map = new Map(); diff --git a/src/script/repository/LocalStorageGestureRepository.ts b/src/script/repository/LocalStorageGestureRepository.ts index 21259ea30..5bb5d65c2 100644 --- a/src/script/repository/LocalStorageGestureRepository.ts +++ b/src/script/repository/LocalStorageGestureRepository.ts @@ -111,11 +111,7 @@ class LocalStorageGestureRepository implements GestureRepository { } const confidence = this.classifierRepository.getGestureConfidence(get(store).ID); - return new Gesture( - store, - confidence, - onRecordingsChanged, - ); + return new Gesture(store, confidence, onRecordingsChanged); } private getPersistedData(): PersistantGestureData[] { From f33a49b279b5692739dfa2db760be9649260520d Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 30 Jul 2024 20:33:58 +0200 Subject: [PATCH 076/106] Create confidences store --- .../training/KnnModelTrainingPageView.svelte | 5 +-- src/script/domain/ClassifierRepository.ts | 3 ++ src/script/domain/Repositories.ts | 1 + src/script/domain/stores/Confidences.ts | 36 +++++++++++++++++++ .../LocalStorageClassifierRepository.ts | 18 ++++------ .../repository/LocalStorageRepositories.ts | 4 ++- src/script/stores/Stores.ts | 7 ++++ 7 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 src/script/domain/stores/Confidences.ts diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte index 2a812a4ed..3b188279d 100644 --- a/src/pages/training/KnnModelTrainingPageView.svelte +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -16,8 +16,9 @@ import { onMount } from 'svelte'; import { knnConfig } from '../../script/stores/knnConfig'; const classifier = stores.getClassifier(); + const confidences = stores.getConfidences(); const gestures = stores.getGestures(); - const confidences = gestures.getConfidences(); + //const confidences = gestures.getConfidences(); const filters = classifier.getFilters(); onMount(() => { @@ -85,7 +86,7 @@
{#if $state.isInputReady}

- {(($confidences.get(gesture.ID)?.currentConfidence ?? 0) * 100).toFixed(2)}% + {(($confidences.get(gesture.ID) ?? 0) * 100).toFixed(2)}%

{/if}
diff --git a/src/script/domain/ClassifierRepository.ts b/src/script/domain/ClassifierRepository.ts index edec00ef2..5eb367dfb 100644 --- a/src/script/domain/ClassifierRepository.ts +++ b/src/script/domain/ClassifierRepository.ts @@ -4,12 +4,15 @@ * SPDX-License-Identifier: MIT */ import Classifier from './stores/Classifier'; +import Confidences from './stores/Confidences'; import GestureConfidence from './stores/gesture/GestureConfidence'; interface ClassifierRepository { getClassifier(): Classifier; getGestureConfidence(gestureId: number): GestureConfidence; + + getConfidences(): Confidences; } export default ClassifierRepository; diff --git a/src/script/domain/Repositories.ts b/src/script/domain/Repositories.ts index b1340367b..e49450864 100644 --- a/src/script/domain/Repositories.ts +++ b/src/script/domain/Repositories.ts @@ -5,6 +5,7 @@ */ import ClassifierRepository from './ClassifierRepository'; import GestureRepository from './GestureRepository'; +import Confidences from './stores/Confidences'; interface Repositories { getGestureRepository(): GestureRepository; diff --git a/src/script/domain/stores/Confidences.ts b/src/script/domain/stores/Confidences.ts new file mode 100644 index 000000000..ced999d42 --- /dev/null +++ b/src/script/domain/stores/Confidences.ts @@ -0,0 +1,36 @@ +import { Readable, Subscriber, Unsubscriber, Writable, get, writable } from "svelte/store"; +import { GestureID } from "./gesture/Gesture"; + +type GestureConfidenceMap = Map; + +class Confidences implements Readable { + private confidenceStore: Writable; + + constructor() { + this.confidenceStore = writable(new Map()); + } + + public subscribe( + run: Subscriber, + invalidate?: ((value?: GestureConfidenceMap | undefined) => void) | undefined + ): Unsubscriber { + return this.confidenceStore.subscribe(run, invalidate); + } + + public setConfidence(gestureId: GestureID, confidence: number): void { + this.confidenceStore.update((map: GestureConfidenceMap) => { + map.set(gestureId, confidence); + return map; + }); + } + + public getConfidence(gestureId: GestureID): number { + const confidence = get(this.confidenceStore).get(gestureId); + if (confidence === undefined) { + throw new Error(`No confidence value found for gesture with ID ${gestureId}`); + } + return confidence; + } +} + +export default Confidences; \ No newline at end of file diff --git a/src/script/repository/LocalStorageClassifierRepository.ts b/src/script/repository/LocalStorageClassifierRepository.ts index 870e2b4a8..2518190d1 100644 --- a/src/script/repository/LocalStorageClassifierRepository.ts +++ b/src/script/repository/LocalStorageClassifierRepository.ts @@ -17,6 +17,7 @@ import ClassifierRepository from '../domain/ClassifierRepository'; import Gesture, { GestureID } from '../domain/stores/gesture/Gesture'; import Classifier from '../domain/stores/Classifier'; import GestureConfidence from '../domain/stores/gesture/GestureConfidence'; +import Confidences from '../domain/stores/Confidences'; export type TrainerConsumer = ( trainer: ModelTrainer, @@ -24,15 +25,12 @@ export type TrainerConsumer = ( class LocalStorageClassifierRepository implements ClassifierRepository { private static readonly PERSISTANT_FILTERS_KEY = 'filters'; - private static confidences: Writable>; private static mlModel: Writable; private static filters: Filters; private static persistedFilters: PersistantWritable; private classifierFactory: ClassifierFactory; - constructor() { - const initialConfidence = new Map(); - LocalStorageClassifierRepository.confidences = writable(initialConfidence); + constructor(private confidences: Confidences) { LocalStorageClassifierRepository.mlModel = writable(undefined); LocalStorageClassifierRepository.persistedFilters = new PersistantWritable( FilterTypes.toIterable(), @@ -82,9 +80,7 @@ class LocalStorageClassifierRepository implements ClassifierRepository { if (confidence < 0 || confidence > 1) { throw new Error('Cannot set gesture confidence. Must be in the range 0.0-1.0'); } - const newConfidences = get(LocalStorageClassifierRepository.confidences); - newConfidences.set(gestureId, confidence); - LocalStorageClassifierRepository.confidences.set(newConfidences); + this.confidences.setConfidence(gestureId, confidence); } private getFilters(): Writable { @@ -114,7 +110,7 @@ class LocalStorageClassifierRepository implements ClassifierRepository { public getGestureConfidence(gestureId: number): GestureConfidence { const derivedConfidence = derived( - [LocalStorageClassifierRepository.confidences], + [this.confidences], stores => { const confidenceStore = stores[0]; if (confidenceStore.has(gestureId)) { @@ -130,11 +126,11 @@ class LocalStorageClassifierRepository implements ClassifierRepository { } public hasGestureConfidence(gestureId: number): boolean { - return get(LocalStorageClassifierRepository.confidences).has(gestureId); + return get(this.confidences).has(gestureId); } - public getConfidences(): Writable> { - return LocalStorageClassifierRepository.confidences; + public getConfidences(): Confidences { + return this.confidences; } } diff --git a/src/script/repository/LocalStorageRepositories.ts b/src/script/repository/LocalStorageRepositories.ts index 5020108d6..2e27b6188 100644 --- a/src/script/repository/LocalStorageRepositories.ts +++ b/src/script/repository/LocalStorageRepositories.ts @@ -6,6 +6,7 @@ import LocalStorageGestureRepository from './LocalStorageGestureRepository'; import LocalStorageClassifierRepository from './LocalStorageClassifierRepository'; import Repositories from '../domain/Repositories'; +import Confidences from '../domain/stores/Confidences'; class LocalStorageRepositories implements Repositories { private gestureRepository: LocalStorageGestureRepository; @@ -20,7 +21,8 @@ class LocalStorageRepositories implements Repositories { throw new Error('Could not instantiate repository. It is already instantiated!'); } LocalStorageRepositories.instance = this; - this.classifierRepository = new LocalStorageClassifierRepository(); + const confidences = new Confidences(); + this.classifierRepository = new LocalStorageClassifierRepository(confidences); this.gestureRepository = new LocalStorageGestureRepository(this.classifierRepository); } diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index a9f5aa552..55e566ba7 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -23,6 +23,7 @@ import Gestures from '../domain/stores/gesture/Gestures'; import PollingPredictorEngine from '../engine/PollingPredictorEngine'; import LocalStorageRepositories from '../repository/LocalStorageRepositories'; import Logger from '../utils/Logger'; +import Confidences from '../domain/stores/Confidences'; type StoresType = { liveData: LiveData; @@ -35,12 +36,14 @@ class Stores implements Readable { private engine: Engine | undefined; private classifier: Classifier; private gestures: Gestures; + private confidences: Confidences; public constructor() { this.liveData = writable(undefined); this.engine = undefined; const repositories: Repositories = new LocalStorageRepositories(); this.classifier = repositories.getClassifierRepository().getClassifier(); + this.confidences = repositories.getClassifierRepository().getConfidences(); this.gestures = new Gestures( repositories.getClassifierRepository(), repositories.getGestureRepository(), @@ -94,6 +97,10 @@ class Stores implements Readable { } return this.engine; } + + public getConfidences(): Confidences { + return this.confidences; + } } export const stores = new Stores(); From 01d8ef06a09ed8b50d629cc96dbb77e8a98cc6c6 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 30 Jul 2024 20:53:07 +0200 Subject: [PATCH 077/106] Rework confidences --- src/script/domain/stores/Confidences.ts | 62 +++++++++++-------- src/script/domain/stores/gesture/Gestures.ts | 34 +--------- .../LocalStorageClassifierRepository.ts | 17 +++-- src/script/stores/Stores.ts | 5 +- 4 files changed, 46 insertions(+), 72 deletions(-) diff --git a/src/script/domain/stores/Confidences.ts b/src/script/domain/stores/Confidences.ts index ced999d42..2efbb380b 100644 --- a/src/script/domain/stores/Confidences.ts +++ b/src/script/domain/stores/Confidences.ts @@ -1,36 +1,48 @@ -import { Readable, Subscriber, Unsubscriber, Writable, get, writable } from "svelte/store"; -import { GestureID } from "./gesture/Gesture"; +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +import { + Readable, + Subscriber, + Unsubscriber, + Writable, + get, + writable, +} from 'svelte/store'; +import { GestureID } from './gesture/Gesture'; type GestureConfidenceMap = Map; class Confidences implements Readable { - private confidenceStore: Writable; + private confidenceStore: Writable; - constructor() { - this.confidenceStore = writable(new Map()); - } + constructor() { + this.confidenceStore = writable(new Map()); + } - public subscribe( - run: Subscriber, - invalidate?: ((value?: GestureConfidenceMap | undefined) => void) | undefined - ): Unsubscriber { - return this.confidenceStore.subscribe(run, invalidate); - } + public subscribe( + run: Subscriber, + invalidate?: ((value?: GestureConfidenceMap | undefined) => void) | undefined, + ): Unsubscriber { + return this.confidenceStore.subscribe(run, invalidate); + } - public setConfidence(gestureId: GestureID, confidence: number): void { - this.confidenceStore.update((map: GestureConfidenceMap) => { - map.set(gestureId, confidence); - return map; - }); - } + public setConfidence(gestureId: GestureID, confidence: number): void { + this.confidenceStore.update((map: GestureConfidenceMap) => { + map.set(gestureId, confidence); + return map; + }); + } - public getConfidence(gestureId: GestureID): number { - const confidence = get(this.confidenceStore).get(gestureId); - if (confidence === undefined) { - throw new Error(`No confidence value found for gesture with ID ${gestureId}`); - } - return confidence; + public getConfidence(gestureId: GestureID): number { + const confidence = get(this.confidenceStore).get(gestureId); + if (confidence === undefined) { + throw new Error(`No confidence value found for gesture with ID ${gestureId}`); } + return confidence; + } } -export default Confidences; \ No newline at end of file +export default Confidences; diff --git a/src/script/domain/stores/gesture/Gestures.ts b/src/script/domain/stores/gesture/Gestures.ts index 9c3ecdeb2..cd6033356 100644 --- a/src/script/domain/stores/gesture/Gestures.ts +++ b/src/script/domain/stores/gesture/Gestures.ts @@ -37,41 +37,13 @@ export type RecordingData = { class Gestures implements Readable { private static subscribableGestures: Writable; private repository: GestureRepository; - private confidenceStore: Readable>; - constructor(classifierRepository: ClassifierRepository, repository: GestureRepository) { + constructor(repository: GestureRepository) { this.repository = repository; Gestures.subscribableGestures = writable(); this.repository.subscribe(storeArray => { Gestures.subscribableGestures.set(storeArray); }); - - this.confidenceStore = derived([this, ...this.getGestures()], stores => { - const confidenceMap: Map = new Map(); - - const [_, ...gestureStores] = stores; - const thiz = stores[0] as GestureData[]; - thiz.forEach(gesture => { - // TODO: The following ought to be fixed. See https://github.com/microbit-foundation/cctd-ml-machine/issues/508 - const store = gestureStores.find(store => store.ID === gesture.ID) - ?.confidence || { - currentConfidence: 0, - requiredConfidence: 0, - isConfident: false, - }; - confidenceMap.set(gesture.ID, { - ...store, - currentConfidence: classifierRepository - .getGestureConfidence(gesture.ID) - .getCurrentConfidence(), - }); - }); - - /*gestureStores.forEach(store => { - confidenceMap.set(store.ID, store.confidence); - });*/ - return confidenceMap; - }); } public subscribe( @@ -159,10 +131,6 @@ class Gestures implements Readable { ); } - public getConfidences(): Readable> { - return this.confidenceStore; - } - private addGestureFromPersistedData(gestureData: PersistantGestureData): Gesture { return this.repository.addGesture(gestureData); } diff --git a/src/script/repository/LocalStorageClassifierRepository.ts b/src/script/repository/LocalStorageClassifierRepository.ts index 2518190d1..2b994950c 100644 --- a/src/script/repository/LocalStorageClassifierRepository.ts +++ b/src/script/repository/LocalStorageClassifierRepository.ts @@ -109,16 +109,13 @@ class LocalStorageClassifierRepository implements ClassifierRepository { } public getGestureConfidence(gestureId: number): GestureConfidence { - const derivedConfidence = derived( - [this.confidences], - stores => { - const confidenceStore = stores[0]; - if (confidenceStore.has(gestureId)) { - return confidenceStore.get(gestureId) as number; - } - throw new Error("No confidence found for gesture with id '" + gestureId + "'"); - }, - ); + const derivedConfidence = derived([this.confidences], stores => { + const confidenceStore = stores[0]; + if (confidenceStore.has(gestureId)) { + return confidenceStore.get(gestureId) as number; + } + throw new Error("No confidence found for gesture with id '" + gestureId + "'"); + }); return new GestureConfidence( StaticConfiguration.defaultRequiredConfidence, derivedConfidence, diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts index 55e566ba7..b6fdd071f 100644 --- a/src/script/stores/Stores.ts +++ b/src/script/stores/Stores.ts @@ -44,10 +44,7 @@ class Stores implements Readable { const repositories: Repositories = new LocalStorageRepositories(); this.classifier = repositories.getClassifierRepository().getClassifier(); this.confidences = repositories.getClassifierRepository().getConfidences(); - this.gestures = new Gestures( - repositories.getClassifierRepository(), - repositories.getGestureRepository(), - ); + this.gestures = new Gestures(repositories.getGestureRepository()); } public subscribe( From c8f164b3e231d6670dab587e215d6bcab6c78017 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 30 Jul 2024 21:23:40 +0200 Subject: [PATCH 078/106] Add check to test number of samples required by filters --- src/script/domain/ClassifierInput.ts | 2 ++ src/script/domain/Filter.ts | 2 ++ src/script/engine/PollingPredictorEngine.ts | 17 +++++++++-- src/script/filters/FilterWithMaths.ts | 1 + src/script/filters/MaxFilter.ts | 3 ++ src/script/filters/MeanFilter.ts | 4 +++ src/script/filters/MinFilter.ts | 3 ++ src/script/filters/PeaksFilter.ts | 28 +++++++++++-------- src/script/filters/RootMeanSquareFilter.ts | 4 +++ src/script/filters/StandardDeviationFilter.ts | 4 +++ src/script/filters/TotalAccFilter.ts | 3 ++ src/script/filters/ZeroCrossingRateFilter.ts | 4 +++ .../mlmodels/AccelerometerClassifierInput.ts | 4 +++ 13 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/script/domain/ClassifierInput.ts b/src/script/domain/ClassifierInput.ts index 30449a1b8..7ff74e507 100644 --- a/src/script/domain/ClassifierInput.ts +++ b/src/script/domain/ClassifierInput.ts @@ -7,6 +7,8 @@ import Filters from './Filters'; interface ClassifierInput { getInput(filters: Filters): number[]; + + getNumberOfSamples(): number; } export default ClassifierInput; diff --git a/src/script/domain/Filter.ts b/src/script/domain/Filter.ts index e65eb34ad..46d90653d 100644 --- a/src/script/domain/Filter.ts +++ b/src/script/domain/Filter.ts @@ -13,6 +13,8 @@ interface Filter { getName(): string; getDescription(): string; + + getMinNumberOfSamples(): number; } export default Filter; diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts index 1de995bd7..8b4aadad5 100644 --- a/src/script/engine/PollingPredictorEngine.ts +++ b/src/script/engine/PollingPredictorEngine.ts @@ -58,9 +58,21 @@ class PollingPredictorEngine implements Engine { } private predict() { - if (this.classifier.getModel().isTrained() && get(this.isRunning)) { - void this.classifier.classify(this.bufferToInput()); + if (!this.classifier.getModel().isTrained()) { + return; } + if (!get(this.isRunning)) { + return; + } + const input = this.bufferToInput(); + const numberOfSamples = input.getNumberOfSamples(); + const requiredNumberOfSamples = Math.max( + ...get(this.classifier.getFilters()).map(filter => filter.getMinNumberOfSamples()), + ); + if (numberOfSamples < requiredNumberOfSamples) { + return; + } + void this.classifier.classify(input); } private bufferToInput(): AccelerometerClassifierInput { @@ -86,6 +98,7 @@ class PollingPredictorEngine implements Engine { if (sampleSize < 8) { return []; // The minimum number of points is 8, otherwise the filters will throw an exception } else { + // If too few samples are available, try again with fewer samples return this.getRawDataFromBuffer( sampleSize - StaticConfiguration.pollingPredictionSampleSizeSearchStepSize, ); diff --git a/src/script/filters/FilterWithMaths.ts b/src/script/filters/FilterWithMaths.ts index 2baa29d92..c70c93947 100644 --- a/src/script/filters/FilterWithMaths.ts +++ b/src/script/filters/FilterWithMaths.ts @@ -8,6 +8,7 @@ import Filter from '../domain/Filter'; import { FilterType } from '../domain/FilterTypes'; abstract class FilterWithMaths implements Filter { + abstract getMinNumberOfSamples(): number; abstract filter(inValues: number[]): number; abstract getType(): FilterType; diff --git a/src/script/filters/MaxFilter.ts b/src/script/filters/MaxFilter.ts index ea5deb011..1499621c9 100644 --- a/src/script/filters/MaxFilter.ts +++ b/src/script/filters/MaxFilter.ts @@ -21,5 +21,8 @@ class MaxFilter implements Filter { public filter(inValues: number[]): number { return Math.max(...inValues); } + public getMinNumberOfSamples(): number { + return 1; + } } export default MaxFilter; diff --git a/src/script/filters/MeanFilter.ts b/src/script/filters/MeanFilter.ts index 0eb72c011..c23f2bc72 100644 --- a/src/script/filters/MeanFilter.ts +++ b/src/script/filters/MeanFilter.ts @@ -23,6 +23,10 @@ class MeanFilter extends FilterWithMaths { public getName(): string { return get(t)('content.filters.mean.title'); } + + public getMinNumberOfSamples(): number { + return 1; + } } export default MeanFilter; diff --git a/src/script/filters/MinFilter.ts b/src/script/filters/MinFilter.ts index ecf7648be..449321728 100644 --- a/src/script/filters/MinFilter.ts +++ b/src/script/filters/MinFilter.ts @@ -22,6 +22,9 @@ class MinFilter implements Filter { public filter(inValues: number[]): number { return Math.min(...inValues); } + public getMinNumberOfSamples(): number { + return 1; + } } export default MinFilter; diff --git a/src/script/filters/PeaksFilter.ts b/src/script/filters/PeaksFilter.ts index 5d7265062..0b6f6a1ec 100644 --- a/src/script/filters/PeaksFilter.ts +++ b/src/script/filters/PeaksFilter.ts @@ -9,6 +9,9 @@ import FilterWithMaths from './FilterWithMaths'; import { t } from 'svelte-i18n'; class PeaksFilter extends FilterWithMaths { + private lag = 5; + private threshold = 3.5; + private influence = 0.5; public getName(): string { return get(t)('content.filters.peaks.title'); } @@ -20,30 +23,26 @@ class PeaksFilter extends FilterWithMaths { } public filter(inValues: number[]): number { - const lag = 5; - const threshold = 3.5; - const influence = 0.5; - let peaksCounter = 0; - if (inValues.length < lag + 2) { + if (inValues.length < this.lag + 2) { throw new Error('data sample is too short'); } // init variables const signals = Array(inValues.length).fill(0) as number[]; const filteredY = inValues.slice(0); - const lead_in = inValues.slice(0, lag); + const lead_in = inValues.slice(0, this.lag); const avgFilter: number[] = []; - avgFilter[lag - 1] = this.mean(lead_in); + avgFilter[this.lag - 1] = this.mean(lead_in); const stdFilter: number[] = []; - stdFilter[lag - 1] = this.stddev(lead_in); + stdFilter[this.lag - 1] = this.stddev(lead_in); - for (let i = lag; i < inValues.length; i++) { + for (let i = this.lag; i < inValues.length; i++) { if ( Math.abs(inValues[i] - avgFilter[i - 1]) > 0.1 && - Math.abs(inValues[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1] + Math.abs(inValues[i] - avgFilter[i - 1]) > this.threshold * stdFilter[i - 1] ) { if (inValues[i] > avgFilter[i - 1]) { signals[i] = +1; // positive signal @@ -54,19 +53,24 @@ class PeaksFilter extends FilterWithMaths { signals[i] = -1; // negative signal } // make influence lower - filteredY[i] = influence * inValues[i] + (1 - influence) * filteredY[i - 1]; + filteredY[i] = + this.influence * inValues[i] + (1 - this.influence) * filteredY[i - 1]; } else { signals[i] = 0; // no signal filteredY[i] = inValues[i]; } // adjust the filters - const y_lag = filteredY.slice(i - lag, i); + const y_lag = filteredY.slice(i - this.lag, i); avgFilter[i] = this.mean(y_lag); stdFilter[i] = this.stddev(y_lag); } return peaksCounter; } + + public getMinNumberOfSamples(): number { + return this.lag + 2; + } } export default PeaksFilter; diff --git a/src/script/filters/RootMeanSquareFilter.ts b/src/script/filters/RootMeanSquareFilter.ts index 3f1e68e8d..6520a1ef5 100644 --- a/src/script/filters/RootMeanSquareFilter.ts +++ b/src/script/filters/RootMeanSquareFilter.ts @@ -22,6 +22,10 @@ class RootMeanSquareFilter implements Filter { public filter(inValues: number[]): number { return Math.sqrt(inValues.reduce((a, b) => a + Math.pow(b, 2), 0) / inValues.length); } + + public getMinNumberOfSamples(): number { + return 1; + } } export default RootMeanSquareFilter; diff --git a/src/script/filters/StandardDeviationFilter.ts b/src/script/filters/StandardDeviationFilter.ts index ed8859f4e..925abcc94 100644 --- a/src/script/filters/StandardDeviationFilter.ts +++ b/src/script/filters/StandardDeviationFilter.ts @@ -26,6 +26,10 @@ class StandardDeviationFilter extends FilterWithMaths { public getDescription(): string { return get(t)('content.filters.std.description'); } + + public getMinNumberOfSamples(): number { + return 2; + } } export default StandardDeviationFilter; diff --git a/src/script/filters/TotalAccFilter.ts b/src/script/filters/TotalAccFilter.ts index 21bb87e5b..6d9a6bc05 100644 --- a/src/script/filters/TotalAccFilter.ts +++ b/src/script/filters/TotalAccFilter.ts @@ -21,6 +21,9 @@ class TotalAccFilter implements Filter { public filter(inValues: number[]): number { return inValues.reduce((a, b) => a + Math.abs(b)); } + public getMinNumberOfSamples(): number { + return 2; + } } export default TotalAccFilter; diff --git a/src/script/filters/ZeroCrossingRateFilter.ts b/src/script/filters/ZeroCrossingRateFilter.ts index 338df0265..44d46e530 100644 --- a/src/script/filters/ZeroCrossingRateFilter.ts +++ b/src/script/filters/ZeroCrossingRateFilter.ts @@ -31,6 +31,10 @@ class ZeroCrossingRateFilter implements Filter { } return count / (inValues.length - 1); } + + public getMinNumberOfSamples(): number { + return 2; + } } export default ZeroCrossingRateFilter; diff --git a/src/script/mlmodels/AccelerometerClassifierInput.ts b/src/script/mlmodels/AccelerometerClassifierInput.ts index 8e425ea35..0b7a63269 100644 --- a/src/script/mlmodels/AccelerometerClassifierInput.ts +++ b/src/script/mlmodels/AccelerometerClassifierInput.ts @@ -37,6 +37,10 @@ class AccelerometerClassifierInput implements ClassifierInput { ...filters.compute(this.zs), ]; } + + public getNumberOfSamples(): number { + return this.xs.length; // Assuming all axes have the same length + } } export default AccelerometerClassifierInput; From e4bf3c51fa3ae4b55ce7af26046a81f7215b4630 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 31 Jul 2024 20:25:16 +0200 Subject: [PATCH 079/106] Fix bugs where wrongly axes were selected and model wasn't trained correctly --- features.json | 2 +- src/pages/training/KnnModelTrainingPageView.svelte | 6 ++++++ src/pages/training/TrainingPage.ts | 9 +++++++-- src/script/mlmodels/KNNNonNormalizedModelTrainer.ts | 7 ++++++- windi.config.js | 6 +++--- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/features.json b/features.json index 478984906..dcedd3554 100644 --- a/features.json +++ b/features.json @@ -1,5 +1,5 @@ { - "title": "Learning tool", + "title": "ML-Machine", "knnModel": true, "lossGraph": true } diff --git a/src/pages/training/KnnModelTrainingPageView.svelte b/src/pages/training/KnnModelTrainingPageView.svelte index 3b188279d..c35cd9c78 100644 --- a/src/pages/training/KnnModelTrainingPageView.svelte +++ b/src/pages/training/KnnModelTrainingPageView.svelte @@ -15,6 +15,7 @@ import { t } from '../../i18n'; import { onMount } from 'svelte'; import { knnConfig } from '../../script/stores/knnConfig'; + import Logger from '../../script/utils/Logger'; const classifier = stores.getClassifier(); const confidences = stores.getConfidences(); const gestures = stores.getGestures(); @@ -23,6 +24,7 @@ onMount(() => { trainModel(ModelRegistry.KNN); + return () => unsubscribe(); }); $: { if ($highlightedAxis === undefined) { @@ -33,6 +35,10 @@ } } + const unsubscribe = highlightedAxis.subscribe(axis => { + trainModel(ModelRegistry.KNN); + }); + const noOfRecordings = $gestures.reduce( (acc, gesture) => acc + gesture.recordings.length, 0, diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index 2fd277ae3..98be689f0 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -29,6 +29,7 @@ const trainingIterationHandler = (h: LossTrainingIteration) => { }; const trainNNModel = async () => { + highlightedAxis.set(undefined); loss.set([]); const modelTrainer = new LayersModelTrainer( StaticConfiguration.layersModelTrainingSettings, @@ -45,14 +46,18 @@ const trainKNNModel = async () => { const offset = currentAxis === Axes.X ? 0 : currentAxis === Axes.Y ? 1 : 2; const modelTrainer = new KNNNonNormalizedModelTrainer( get(knnConfig).k, - data => extractAxisFromTrainingData(data, offset, 3), // 3 assumes 3 axis + data => { + const extractedData = extractAxisFromTrainingData(data, offset, 3); + Logger.log('TrainingPage', 'Extracted data: \n' + JSON.stringify(extractedData)); + return extractedData + } // 3 assumes 3 axis ); await stores.getClassifier().getModel().train(modelTrainer); }; export const trainModel = async (model: ModelInfo) => { Logger.log('TrainingPage', 'Training new model: ' + model.title); - highlightedAxis.set(undefined); + // highlightedAxis.set(undefined); if (ModelRegistry.KNN.id === model.id) { await trainKNNModel(); } else if (ModelRegistry.NeuralNetwork.id === model.id) { diff --git a/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts b/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts index 16d9605a8..718b395aa 100644 --- a/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts +++ b/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: MIT */ import ModelTrainer, { TrainingData } from '../domain/ModelTrainer'; +import Logger from '../utils/Logger'; import KNNNonNormalizedMLModel, { LabelledPoint } from './KNNNonNormalizedMLModel'; /** @@ -14,11 +15,15 @@ class KNNNonNormalizedModelTrainer implements ModelTrainer TrainingData, - ) {} + ) { } public trainModel(trainingData: TrainingData): Promise { + Logger.log('KNNNonNormalizedModelTrainer', 'Training model'); if (this.dataFilterer) { + Logger.log('KNNNonNormalizedModelTrainer', 'Filtering training data'); trainingData = this.dataFilterer(trainingData); + } else { + Logger.log('KNNNonNormalizedModelTrainer', 'No data filtering'); } const points: LabelledPoint[] = []; diff --git a/windi.config.js b/windi.config.js index a5525d9a8..e41e5fe6e 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#3a3a3a', + primary: '#2B5EA7', primarytext: '#000000', - secondary: '#a0a0a0', + secondary: '#2CCAC0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#ffaaaa', + warning: '#FF7777', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From e4383923a8f85107ef6edc17a9d53a2ed7d13f41 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 31 Jul 2024 20:27:15 +0200 Subject: [PATCH 080/106] Ensure no axes are highlighted when training neural network --- src/pages/training/NeuralNetworkTrainingPageView.svelte | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/training/NeuralNetworkTrainingPageView.svelte b/src/pages/training/NeuralNetworkTrainingPageView.svelte index d68f17aca..60bf1b891 100644 --- a/src/pages/training/NeuralNetworkTrainingPageView.svelte +++ b/src/pages/training/NeuralNetworkTrainingPageView.svelte @@ -13,6 +13,8 @@ import ModelRegistry from '../../script/domain/ModelRegistry'; import Logger from '../../script/utils/Logger'; import { Feature, hasFeature } from '../../script/FeatureToggles'; + import { onMount } from 'svelte'; + import { highlightedAxis } from '../../script/stores/uiStore'; const classifier = stores.getClassifier(); const model = classifier.getModel(); @@ -26,6 +28,10 @@ $: trainButtonSimpleLabel = !$model.hasModel ? 'menu.trainer.trainModelButtonSimple' : 'menu.trainer.trainNewModelButtonSimple'; + + onMount(() => { + $highlightedAxis = undefined; + });
From 15519d7e1c1dc2c255534eafd12ebf0bfdc69c02 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 31 Jul 2024 20:28:18 +0200 Subject: [PATCH 081/106] Prettier and unbranded config --- features.json | 2 +- src/pages/training/TrainingPage.ts | 4 ++-- src/script/mlmodels/KNNNonNormalizedModelTrainer.ts | 2 +- windi.config.js | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/features.json b/features.json index dcedd3554..478984906 100644 --- a/features.json +++ b/features.json @@ -1,5 +1,5 @@ { - "title": "ML-Machine", + "title": "Learning tool", "knnModel": true, "lossGraph": true } diff --git a/src/pages/training/TrainingPage.ts b/src/pages/training/TrainingPage.ts index 98be689f0..5fef88df3 100644 --- a/src/pages/training/TrainingPage.ts +++ b/src/pages/training/TrainingPage.ts @@ -49,8 +49,8 @@ const trainKNNModel = async () => { data => { const extractedData = extractAxisFromTrainingData(data, offset, 3); Logger.log('TrainingPage', 'Extracted data: \n' + JSON.stringify(extractedData)); - return extractedData - } // 3 assumes 3 axis + return extractedData; + }, // 3 assumes 3 axis ); await stores.getClassifier().getModel().train(modelTrainer); }; diff --git a/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts b/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts index 718b395aa..912dbe969 100644 --- a/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts +++ b/src/script/mlmodels/KNNNonNormalizedModelTrainer.ts @@ -15,7 +15,7 @@ class KNNNonNormalizedModelTrainer implements ModelTrainer TrainingData, - ) { } + ) {} public trainModel(trainingData: TrainingData): Promise { Logger.log('KNNNonNormalizedModelTrainer', 'Training model'); diff --git a/windi.config.js b/windi.config.js index e41e5fe6e..a5525d9a8 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#2B5EA7', + primary: '#3a3a3a', primarytext: '#000000', - secondary: '#2CCAC0', + secondary: '#a0a0a0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#FF7777', + warning: '#ffaaaa', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From ab0a13b655ef6892e819c34cb8d10bf5185c4026 Mon Sep 17 00:00:00 2001 From: r59q Date: Wed, 31 Jul 2024 20:48:25 +0200 Subject: [PATCH 082/106] Formatting --- src/pages/training/NeuralNetworkTrainingPageView.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/training/NeuralNetworkTrainingPageView.svelte b/src/pages/training/NeuralNetworkTrainingPageView.svelte index 60bf1b891..304d671d5 100644 --- a/src/pages/training/NeuralNetworkTrainingPageView.svelte +++ b/src/pages/training/NeuralNetworkTrainingPageView.svelte @@ -41,8 +41,9 @@ class="fa fa-solid fa-circle-notch text-5xl animate-spin animate-duration-[2s]" />
{:else} - {$t(trainButtonSimpleLabel)} + + {$t(trainButtonSimpleLabel)} + {/if} {#if $loss.length > 0 && hasFeature(Feature.LOSS_GRAPH)} From 3d3b543e2d9dc8172df10792bb7696ca9e62deb2 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 12 Aug 2024 17:57:03 +0200 Subject: [PATCH 083/106] Identified as outdated is disabled --- .../connection-behaviours/InputBehaviour.ts | 3 +++ .../connection-behaviours/OutputBehaviour.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index 24fdbce72..d55d753ed 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -44,10 +44,13 @@ class InputBehaviour extends LoggingDecorator { onIdentifiedAsOutdated(): void { super.onIdentifiedAsOutdated(); + /* + TODO: Disabled for now as the results are unpredictable state.update(s => { s.isInputOutdated = true; return s; }); + */ } onVersionIdentified(versionNumber: number): void { diff --git a/src/script/microbit-interfacing/connection-behaviours/OutputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/OutputBehaviour.ts index 286cc8fd1..71a042981 100644 --- a/src/script/microbit-interfacing/connection-behaviours/OutputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/OutputBehaviour.ts @@ -33,10 +33,13 @@ class OutputBehaviour extends LoggingDecorator { onIdentifiedAsOutdated(): void { super.onIdentifiedAsOutdated(); + /* + TODO: Disabled for now as the results are unpredictable state.update(s => { s.isOutputOutdated = true; return s; }); + */ } onVersionIdentified(versionNumber: number): void { From 1aa89f01376d7ca78d36c9afed69ec81358a381e Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 12 Aug 2024 18:02:56 +0200 Subject: [PATCH 084/106] Disabled bad firmware warning --- .../connection-prompt/ConnectDialogContainer.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/connection-prompt/ConnectDialogContainer.svelte b/src/components/connection-prompt/ConnectDialogContainer.svelte index 31c765bd2..9458850b5 100644 --- a/src/components/connection-prompt/ConnectDialogContainer.svelte +++ b/src/components/connection-prompt/ConnectDialogContainer.svelte @@ -22,6 +22,7 @@ import { btPatternInput, btPatternOutput } from '../../script/stores/connectionStore'; import MBSpecs from '../../script/microbit-interfacing/MBSpecs'; import BrokenFirmwareDetected from './usb/BrokenFirmwareDetected.svelte'; + import { onMount } from 'svelte'; let flashProgress = 0; @@ -56,12 +57,15 @@ }) .catch((e: Error) => { // Couldn't find name. Set to manual transfer progress instead + + $connectionDialogState.connectionState = ConnectDialogStates.MANUAL_TUTORIAL; + /* TODO: Disabled the broken firmware warning for now if (e.message.includes('No valid interfaces found')) { // Edge case, caused by a bad micro:bit firmware $connectionDialogState.connectionState = ConnectDialogStates.BAD_FIRMWARE; } else { - $connectionDialogState.connectionState = ConnectDialogStates.MANUAL_TUTORIAL; } +*/ }); } From c9f4163ffc10ff5f042e2d0bc92743e1e00fc454 Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 12 Aug 2024 18:23:16 +0200 Subject: [PATCH 085/106] Allow custom colors and add graph colors to axis selector --- src/components/buttons/StandardButton.svelte | 13 +++++++++++-- .../graphs/knngraph/AxesFilterVectorView.svelte | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/buttons/StandardButton.svelte b/src/components/buttons/StandardButton.svelte index 3ba59abbd..dbeb1de1f 100644 --- a/src/components/buttons/StandardButton.svelte +++ b/src/components/buttons/StandardButton.svelte @@ -36,7 +36,14 @@ import TypingUtils from './../../script/TypingUtils'; import windi from './../../../windi.config.js'; - type variants = 'secondary' | 'primary' | 'warning' | 'info' | 'infolight' | 'disabled'; + type variants = + | 'secondary' + | 'primary' + | 'warning' + | 'info' + | 'infolight' + | 'disabled' + | string; export let color: variants = 'secondary'; export let onClick: (e: Event) => void = TypingUtils.emptyFunction; @@ -55,12 +62,14 @@ infolight: windi.theme.extend.colors.infolight, disabled: windi.theme.extend.colors.disabled, }; + const isKey = Object.keys(bgColors).includes(color); + const colorParam = isKey ? bgColors[disabled ? 'disabled' : color] : color;
From 976c956350d0e61c97039225342eb1599223e808 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 17 Sep 2024 21:22:52 +0200 Subject: [PATCH 095/106] Ran prettier --- .../ml-machine-simple/features.json | 2 +- .../ml-machine/features.json | 2 +- .../unbranded/features.json | 2 +- src/exampleDataset.json | 7630 ++--------------- 4 files changed, 710 insertions(+), 6926 deletions(-) diff --git a/src/__viteBuildVariants__/ml-machine-simple/features.json b/src/__viteBuildVariants__/ml-machine-simple/features.json index 586c89af2..1c4b872b5 100644 --- a/src/__viteBuildVariants__/ml-machine-simple/features.json +++ b/src/__viteBuildVariants__/ml-machine-simple/features.json @@ -3,4 +3,4 @@ "knnModel": false, "lossGraph": false, "makecode": false -} \ No newline at end of file +} diff --git a/src/__viteBuildVariants__/ml-machine/features.json b/src/__viteBuildVariants__/ml-machine/features.json index 6e0c81394..15cb272de 100644 --- a/src/__viteBuildVariants__/ml-machine/features.json +++ b/src/__viteBuildVariants__/ml-machine/features.json @@ -3,4 +3,4 @@ "knnModel": true, "lossGraph": true, "makecode": true -} \ No newline at end of file +} diff --git a/src/__viteBuildVariants__/unbranded/features.json b/src/__viteBuildVariants__/unbranded/features.json index ec4ddb583..97c1de396 100644 --- a/src/__viteBuildVariants__/unbranded/features.json +++ b/src/__viteBuildVariants__/unbranded/features.json @@ -3,4 +3,4 @@ "knnModel": true, "lossGraph": true, "makecode": true -} \ No newline at end of file +} diff --git a/src/exampleDataset.json b/src/exampleDataset.json index eaf9c4599..dadd8e717 100644 --- a/src/exampleDataset.json +++ b/src/exampleDataset.json @@ -8,289 +8,39 @@ "ID": 1705437992143, "data": { "x": [ - -0.112, - -0.356, - -0.62, - -0.892, - -1.508, - -1.848, - -2.04, - -2.04, - -1.868, - -1.3, - -0.824, - -0.48, - 0.256, - 0.752, - 0.864, - -0.044, - -0.908, - -1.716, - -2.04, - -2.008, - -1.856, - -1.336, - -0.84, - 0.192, - 0.58, - 0.504, - -0.008, - -0.232, - -1.132, - -2.04, - -2.04, - -2.04, - -1.012, - -0.48, - 0.028, - 0.256, - 0.608, - 0.332, - -0.004, - -0.876, - -2.04, - -2.04, - -2.04, - -1.688, - -1.396, - -0.828, - -0.032, - 0.36, - 0.492, - 0.288, - 0.008, - -0.492, - -1.368, - -2.04, - -2.04, - -1.828, - -1.368, - -0.668, - -0.248, - 0.108, - 0.664, - 0.544, - 0.248, - -0.404, - -1.388, - -2.04, - -2.04, - -1.86, - -1.372, - -1.072, - -0.612, - 0.16, - 0.52, - 0.488, - 0.256, - -0.144, - -0.752, - -1.732, - -2.04, - -2.04, - -1.668, - -1.208, - -0.884, - -0.556, - 0.024, - 0.48, - 0.556, - 0.248, - -0.096, - -0.9, - -2.04, - -2.04, - -1.992 + -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, + -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, + -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, + -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, + -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, + 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, + 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, + -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, + -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, + -2.04, -2.04, -1.992 ], "y": [ - 0.436, - 0.416, - 0.308, - 0.168, - 0.148, - -0.056, - 0.352, - 0.356, - 0.3, - 0.244, - 0.324, - 0.264, - 0.16, - 0.396, - 0.456, - 0.208, - 0.244, - 0.252, - 0.312, - 0.396, - 0.328, - 0.364, - 0.204, - 0.188, - 0.548, - 0.536, - 0.388, - 0.22, - 0.052, - 0.54, - 1.076, - -0.116, - 0.048, - 0.132, - 0.448, - 0.528, - 0.608, - 0.6, - 0.412, - 0.152, - 0.524, - 0.416, - 0.252, - 0.308, - 0.376, - 0.316, - 0.356, - 0.644, - 0.672, - 0.544, - 0.404, - 0.164, - 0.208, - 0.624, - 0.536, - 0.256, - 0.272, - 0.404, - 0.492, - 0.5, - 0.712, - 0.656, - 0.568, - 0.264, - 0.296, - 0.496, - 0.284, - 0.28, - 0.288, - 0.42, - 0.492, - 0.488, - 0.616, - 0.684, - 0.588, - 0.396, - 0.232, - 0.452, - 0.98, - 0.484, - 0.36, - 0.372, - 0.544, - 0.48, - 0.496, - 0.6, - 0.692, - 0.528, - 0.288, - 0.204, - 0.476, - 1.112, - 0.2 + 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, + 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, + 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, + 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, + 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, + 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, + 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, + 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, + 0.204, 0.476, 1.112, 0.2 ], "z": [ - -0.576, - -0.592, - -0.516, - -0.38, - -0.368, - 0.208, - -0.028, - 0.048, - 0.008, - -0.096, - -0.288, - -0.396, - -0.516, - -0.556, - -0.312, - -0.056, - -0.168, - -0.108, - 0.196, - 0.268, - 0.1, - -0.16, - -0.32, - -0.64, - -0.692, - -0.56, - -0.26, - -0.092, - -0.204, - 0.304, - 0.78, - 0.364, - 0.108, - -0.24, - -0.528, - -0.608, - -0.552, - -0.208, - -0.048, - -0.14, - 0.42, - 0.552, - 0.42, - 0.128, - 0.276, - -0.332, - -0.648, - -0.648, - -0.544, - -0.212, - 0.036, - -0.004, - -0.148, - 0.304, - 0.212, - 0.344, - 0.352, - 0.064, - -0.572, - -0.628, - -0.616, - -0.276, - -0.008, - -0.18, - -0.044, - 0.012, - 0.368, - 0.32, - 0.372, - -0.092, - -0.288, - -0.608, - -0.712, - -0.432, - -0.252, - -0.024, - 0.044, - -0.052, - 0.4, - 0.504, - 0.196, - -0.016, - -0.172, - -0.308, - -0.66, - -0.72, - -0.52, - -0.268, - -0.072, - -0.176, - 0.136, - 0.34, - 0.7 + -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, + -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, + 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, + 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, + 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, + -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, + -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, + -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, + -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, + 0.34, 0.7 ] } }, @@ -298,286 +48,37 @@ "ID": 1705437989960, "data": { "x": [ - -0.4, - 0.232, - 0.708, - 1.068, - 1.284, - 1.36, - 1.02, - 0.816, - 0.332, - -0.152, - -1.496, - -2.04, - -2.04, - -1.14, - -0.968, - -0.648, - 0.232, - 1, - 1.34, - 1.32, - 0.864, - 0.256, - -0.696, - -1.884, - -2.04, - -2.04, - -1.62, - -1.028, - -0.196, - 0.42, - 0.608, - 0.488, - 0.168, - -0.528, - -1.488, - -2.028, - -2.04, - -2.04, - -1.588, - -1.08, - -0.512, - -0.028, - 0.216, - 0.268, - 0.052, - -0.38, - -0.684, - -0.788, - -1.056, - -1.768, - -2.04, - -2.04, - -1.344, - -0.8, - -0.436, - -0.172, - 0.22, - 0.456, - -0.124, - -0.504, - -0.864, - -1.14, - -1.544, - -2.02, - -2.016, - -1.52, - -1.004, - -0.456, - -0.156, - 0.064, - 0.128, - -0.204, - -0.46, - -0.816, - -1.22, - -1.596, - -1.884, - -1.816, - -1.44, - -0.948, - -0.516, - -0.196, - 0.06, - 0.124, - -0.16, - -0.504, - -0.72, - -0.928, - -1.42, - -1.8, - -2.032, - -1.76 + -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, + -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, + -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, + 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, + 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, + -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, + -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, + -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, + 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 ], "y": [ - 0.592, - 0.364, - 0.128, - -0.004, - -0.06, - -0.156, - -0.04, - 0.08, - 0.284, - 0.456, - 0.188, - 1.104, - 0.9, - 0.6, - 0.536, - 0.388, - 0.34, - 0.316, - 0.292, - 0.32, - 0.252, - 0.208, - 0.152, - 0.3, - 0.792, - 0.776, - 0.748, - 0.588, - 0.44, - 0.372, - 0.4, - 0.424, - 0.376, - 0.296, - 0.448, - 0.596, - 0.644, - 0.732, - 0.58, - 0.496, - 0.46, - 0.444, - 0.568, - 0.56, - 0.456, - 0.408, - 0.38, - 0.284, - 0.22, - 0.352, - 0.792, - 0.636, - 0.452, - 0.456, - 0.468, - 0.392, - 0.376, - 0.348, - 0.34, - 0.396, - 0.456, - 0.364, - 0.432, - 0.704, - 0.728, - 0.584, - 0.416, - 0.424, - 0.428, - 0.432, - 0.288, - 0.3, - 0.368, - 0.392, - 0.436, - 0.512, - 0.684, - 0.664, - 0.556, - 0.496, - 0.472, - 0.456, - 0.444, - 0.7, - 0.304, - 0.36, - 0.292, - 0.276, - 0.328, - 0.536, - 0.78, - 0.7 + 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, + 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, + 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, + 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, + 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, + 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, + 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, + 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, + 0.78, 0.7 ], "z": [ - -1.196, - -0.908, - -0.712, - -0.556, - -0.44, - -0.42, - -0.548, - -0.512, - -0.596, - -0.696, - -1.364, - -0.26, - -0.592, - -0.788, - -0.876, - -0.84, - -0.888, - -0.948, - -0.972, - -1.192, - -1.148, - -0.808, - -0.636, - -0.42, - 0.076, - 0.42, - 0.124, - -0.224, - -0.572, - -0.924, - -1.032, - -0.972, - -0.656, - -0.708, - -0.492, - -0.096, - 0.428, - 0.548, - 0.196, - -0.012, - -0.364, - -0.744, - -0.996, - -1.08, - -1.448, - -1.444, - -1.136, - -0.652, - -0.104, - 0.528, - 1.116, - 0.88, - 0.384, - 0.048, - -0.348, - -0.804, - -1.34, - -1.604, - -1.624, - -1.3, - -0.7, - -0.116, - 0.544, - 0.996, - 0.784, - 0.448, - 0.084, - -0.468, - -0.964, - -1.508, - -1.86, - -1.5, - -1.08, - -0.54, - 0.112, - 0.68, - 1.224, - 1.028, - 0.66, - 0.256, - -0.24, - -0.712, - -1.24, - -1.232, - -1.604, - -1.456, - -1.004, - -0.428, - 0.18, - 0.576, - 1.292, - 0.92 + -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, + -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, + -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, + -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, + -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, + 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, + -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, + -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, + -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 ] } }, @@ -585,286 +86,38 @@ "ID": 1705437987590, "data": { "x": [ - -1.464, - -1.144, - -0.92, - -0.696, - -0.22, - -0.016, - 0.168, - 0.384, - 0.552, - 0.872, - 1.164, - 1.284, - 1.192, - 0.904, - 0.38, - -1.068, - -1.9, - -0.784, - -0.312, - -0.144, - 0.076, - 0.36, - 0.812, - 1.116, - 1.18, - 1.072, - 0.752, - 0.004, - -1.244, - -0.88, - -0.372, - -0.04, - 0.252, - 0.616, - 1.016, - 1.332, - 1.296, - 1.068, - 0.748, - 0.384, - -0.748, - -1.776, - -0.68, - -0.312, - -0.052, - 0.396, - 0.932, - 1.264, - 1.364, - 1.144, - 0.84, - 0.328, - -0.696, - -1.504, - -0.78, - -0.38, - -0.1, - 0.244, - 0.608, - 0.86, - 1.172, - 1.436, - 1.492, - 1.268, - 0.784, - -0.5, - -1.484, - -0.62, - -0.272, - -0.128, - 0.324, - 0.716, - 1.168, - 1.568, - 1.692, - 1.452, - 0.88, - -0.108, - -1.46, - -1.128, - -0.616, - -0.432, - 0.008, - 0.68, - 1.264, - 1.68, - 1.804, - 1.496, - 0.884, - -0.14, - -1.08, - -0.852 + -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, + 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, + 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, + 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, + -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, + -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, + 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, + 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, + 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 ], "y": [ - 0.668, - 0.576, - 0.556, - 0.508, - 0.36, - 0.472, - 0.384, - 0.34, - 0.24, - 0.124, - 0.068, - 0.052, - 0.052, - 0.12, - 0.02, - 0.124, - 0.836, - 0.556, - 0.612, - 0.62, - 0.588, - 0.452, - 0.304, - 0.26, - 0.108, - 0.096, - 0.108, - -0.108, - 0.072, - 0.372, - 0.344, - 0.456, - 0.368, - 0.3, - 0.316, - 0.332, - 0.272, - 0.204, - 0.196, - 0.104, - 0, - 0.484, - 0.416, - 0.384, - 0.356, - 0.32, - 0.216, - 0.244, - 0.284, - 0.208, - 0.196, - 0.136, - -0.06, - 0.52, - 0.34, - 0.332, - 0.336, - 0.26, - 0.224, - 0.184, - 0.104, - 0.116, - 0.132, - 0.116, - 0.084, - 0.068, - 0.588, - 0.42, - 0.396, - 0.364, - 0.272, - 0.196, - 0.044, - -0.008, - 0.02, - 0, - 0.048, - -0.02, - 0.428, - 0.592, - 0.552, - 0.524, - 0.324, - 0.128, - -0.1, - -0.128, - -0.068, - -0.052, - 0.044, - 0.08, - 0.352, - 0.632 + 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, + 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, + 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, + 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, + 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, + 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, + 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, + 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, + 0.352, 0.632 ], "z": [ - -0.148, - -0.472, - -0.476, - -0.388, - -0.684, - -0.404, - -0.46, - -0.38, - -0.36, - -0.384, - -0.572, - -0.792, - -1.084, - -1.36, - -1.62, - -2.04, - -1.748, - -1.216, - -0.844, - -0.48, - -0.168, - 0.036, - 0.048, - -0.032, - -0.232, - -0.596, - -1.076, - -1.596, - -1.952, - -2.016, - -1.496, - -1.008, - -0.52, - -0.216, - -0.04, - 0.04, - -0.06, - -0.324, - -0.676, - -1.124, - -1.784, - -2.04, - -1.748, - -1.408, - -0.936, - -0.444, - -0.108, - 0.104, - 0.088, - -0.116, - -0.396, - -0.836, - -1.608, - -2.04, - -1.964, - -1.612, - -1.184, - -0.764, - -0.46, - -0.156, - 0.112, - 0.204, - 0.056, - -0.304, - -0.76, - -1.54, - -2.04, - -2.04, - -1.768, - -1.204, - -0.708, - -0.244, - 0.152, - 0.332, - 0.264, - -0.024, - -0.424, - -0.984, - -1.7, - -2.04, - -1.984, - -1.372, - -0.764, - -0.296, - 0.084, - 0.216, - 0.12, - -0.136, - -0.472, - -0.992, - -1.876, - -2.04 + -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, + -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, + -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, + -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, + -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, + -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, + 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, + -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, + -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, + -1.876, -2.04 ] } }, @@ -872,289 +125,39 @@ "ID": 1705437985014, "data": { "x": [ - -0.392, - -0.532, - -0.452, - -0.576, - -0.748, - -0.908, - -0.984, - -1.12, - -1.04, - -0.82, - -0.668, - -0.184, - 0.18, - 0.428, - 0.816, - 1.248, - 1.38, - 1.188, - 0.936, - 0.456, - 0.132, - -0.5, - -1.94, - -2.04, - -2.04, - -1.536, - -0.74, - -0.38, - 0.056, - 0.4, - 0.984, - 1.208, - 0.812, - 0.396, - -0.2, - -1.508, - -2.024, - -1.576, - -0.732, - -0.212, - 0.252, - 0.536, - 0.776, - 0.716, - 0.46, - -0.036, - -1.18, - -1.752, - -1.272, - -0.952, - -0.592, - -0.348, - 0.148, - 0.316, - 0.884, - 0.832, - 0.688, - 0.452, - -0.216, - -1.084, - -1.94, - -1.252, - -0.792, - -0.544, - -0.16, - 0.284, - 0.676, - 0.672, - 0.56, - 0.34, - -0.036, - -0.916, - -2.036, - -1.516, - -0.892, - -0.536, - -0.256, - -0.068, - 0.256, - 0.528, - 0.584, - 0.452, - 0.124, - -0.424, - -1.404, - -1.912, - -1.208, - -0.78, - -0.54, - -0.264, - 0.076, - 0.544, + -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, + -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, + -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, + 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, + 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, + 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, + -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, + -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, + 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, 0.528 ], "y": [ - 0.328, - 0.336, - 0.336, - 0.304, - 0.328, - 0.38, - 0.396, - 0.384, - 0.348, - 0.46, - 0.512, - 0.408, - 0.348, - 0.18, - -0.028, - -0.08, - -0.036, - 0.02, - 0.136, - 0.164, - 0.048, - -0.22, - 0.464, - 1.092, - 1.044, - 0.812, - 0.496, - 0.348, - 0.236, - 0.132, - 0.064, - 0.124, - 0.152, - 0.304, - 0.248, - 0.896, - 1.34, - 1.08, - 0.76, - 0.636, - 0.436, - 0.244, - 0.224, - 0.176, - 0.236, - 0.24, - 0.66, - 1.26, - 1.124, - 1.04, - 0.784, - 0.572, - 0.008, - 0.272, - 0.068, - 0.124, - 0.192, - 0.244, - 0.564, - 0.636, - 1.372, - 0.976, - 0.904, - 0.68, - 0.416, - 0.036, - 0.076, - 0.092, - 0.208, - 0.348, - 0.412, - 0.768, - 1.088, - 1, - 0.844, - 0.708, - 0.54, - 0.424, - 0.228, - 0.196, - 0.116, - 0.232, - 0.34, - 0.492, - 0.84, - 1.196, - 0.956, - 0.844, - 0.672, - 0.496, - 0.26, - 0.232, - 0.228 + 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, + 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, + 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, + 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, + 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, + 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, + 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, + 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, + 0.496, 0.26, 0.232, 0.228 ], "z": [ - -0.932, - -0.884, - -1.076, - -0.972, - -0.776, - -0.744, - -0.856, - -0.884, - -0.892, - -0.844, - -0.744, - -0.572, - -0.504, - -0.576, - -0.668, - -1, - -1.204, - -1.144, - -1.044, - -0.832, - -0.664, - -1.024, - -0.46, - 0.276, - -0.064, - -0.16, - -0.476, - -0.416, - -0.764, - -0.892, - -1.332, - -1.872, - -1.152, - -0.744, - -0.904, - -0.704, - -0.028, - -0.296, - -0.256, - -0.592, - -0.848, - -1, - -1.404, - -1.22, - -1.3, - -0.76, - -0.848, - 0.028, - -0.14, - -0.14, - -0.3, - -0.576, - -1.052, - -0.276, - -2.04, - -1.556, - -1.308, - -1.328, - -0.572, - -0.492, - -0.212, - 0.076, - -0.28, - -0.44, - -0.688, - -0.856, - -1.768, - -1.464, - -1.268, - -0.9, - -0.588, - -0.46, - -0.352, - -0.18, - -0.328, - -0.404, - -0.576, - -0.9, - -1.328, - -1.48, - -1.444, - -1.12, - -0.704, - -0.876, - -0.452, - -0.156, - -0.256, - -0.28, - -0.424, - -0.608, - -0.652, - -1.504, - -1.412 + -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, + -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, + -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, + -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, + -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, + -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, + -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, + -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, + -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, + -0.608, -0.652, -1.504, -1.412 ] } }, @@ -1162,286 +165,39 @@ "ID": 1705437822437, "data": { "x": [ - -0.684, - -0.688, - -0.672, - -0.668, - -0.628, - -0.564, - -0.576, - -0.7, - -0.896, - -0.96, - -1.016, - -0.888, - -0.752, - -0.564, - -0.46, - -0.36, - -0.288, - -0.204, - -0.12, - -0.164, - -0.248, - -0.236, - -0.088, - -0.644, - -1.068, - -0.812, - -0.644, - -0.56, - -0.372, - -0.292, - -0.272, - -0.076, - -0.04, - -0.08, - -0.172, - -0.288, - -0.276, - -0.06, - -0.532, - -1.132, - -0.828, - -0.72, - -0.712, - -0.368, - -0.296, - -0.34, - -0.28, - -0.216, - -0.164, - -0.18, - -0.272, - -0.408, - -0.644, - -0.764, - -0.732, - -0.636, - -0.62, - -0.524, - -0.344, - -0.16, - -0.032, - -0.088, - -0.136, - -0.224, - -0.252, - -0.18, - -0.484, - -0.872, - -0.844, - -0.72, - -0.624, - -0.572, - -0.48, - -0.392, - -0.356, - -0.292, - -0.244, - -0.208, - -0.288, - -0.372, - -0.364, - -0.216, - -0.464, - -1.016, - -0.904, - -0.956, - -0.812, - -0.564, - -0.632, - -0.468, - -0.34, - -0.272 + -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, + -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, + -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, + -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, + -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, + -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, + -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, + -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, + -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, + -0.468, -0.34, -0.272 ], "y": [ - 0.1, - 0.048, - 0.028, - 0.24, - 0.336, - 0.376, - 0.208, - -0.328, - -1.1, - -1.368, - -1.216, - -0.936, - -0.488, - -0.032, - 0.468, - 1.056, - 1.708, - 1.856, - 1.896, - 1.692, - 1.224, - 0.592, - -0.744, - -2.04, - -2.04, - -2.04, - -1.404, - -0.66, - -0.204, - 0.568, - 0.92, - 1.304, - 1.768, - 1.876, - 1.5, - 1.056, - 0.596, - -0.508, - -2.04, - -2.04, - -2.032, - -1.288, - -0.796, - 0.108, - 1.112, - 1.588, - 1.832, - 1.832, - 1.588, - 1.176, - 0.232, - -0.968, - -2.04, - -2.04, - -2.04, - -1.68, - -1.08, - -0.388, - 0.248, - 1.02, - 1.704, - 2.04, - 1.98, - 1.488, - 0.868, - -0.08, - -1.912, - -2.04, - -2.04, - -1.916, - -1.296, - -0.684, - 0.088, - 0.532, - 0.92, - 1.156, - 1.348, - 1.58, - 1.616, - 1.312, - 0.816, - -0.012, - -2.04, - -2.04, - -2.04, - -2.004, - -1.492, - -0.904, - 0.028, - 0.564, - 0.976, - 1.292 + 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, + -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, + 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, + 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, + 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, + -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, + -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, + 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, + -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 ], "z": [ - -0.736, - -0.788, - -0.756, - -0.768, - -0.796, - -0.908, - -0.936, - -1.024, - -0.764, - -0.412, - -0.34, - -0.408, - -0.56, - -0.62, - -0.624, - -0.896, - -1.092, - -1.164, - -1.144, - -0.992, - -0.844, - -0.808, - -0.736, - -0.016, - 0.356, - -0.172, - -0.32, - -0.58, - -0.672, - -0.952, - -0.988, - -1.092, - -1.16, - -1.104, - -1.032, - -0.924, - -0.872, - -0.736, - -0.256, - 0.276, - -0.392, - -0.444, - -0.476, - -0.656, - -1.124, - -1.136, - -1.156, - -1.18, - -1.004, - -0.884, - -0.812, - -0.664, - -0.508, - -0.284, - -0.312, - -0.392, - -0.5, - -0.64, - -0.84, - -1.172, - -1.3, - -1.352, - -1.128, - -0.968, - -0.836, - -0.764, - -0.5, - -0.152, - -0.372, - -0.372, - -0.432, - -0.612, - -0.892, - -1.08, - -1.212, - -1.324, - -1.208, - -0.992, - -0.828, - -0.744, - -0.86, - -0.872, - -0.732, - 0.012, - 0.256, - -0.084, - -0.124, - -0.208, - -0.512, - -0.816, - -1.18, - -1.332 + -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, + -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, + -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, + -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, + 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, + -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, + -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, + -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, + -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, + -0.816, -1.18, -1.332 ] } }, @@ -1449,283 +205,37 @@ "ID": 1705437819739, "data": { "x": [ - -0.268, - -0.048, - 0.06, - 0.028, - -0.108, - -0.3, - -0.412, - -0.536, - -0.668, - -0.928, - -1.18, - -1.26, - -0.86, - -0.372, - 0.344, - 0.564, - 0.548, - 0.392, - 0.14, - -0.124, - -0.264, - -0.324, - -0.376, - -0.636, - -1.312, - -1.216, - -1.036, - -0.744, - -0.256, - 0.188, - 0.132, - 0.112, - 0.164, - 0.008, - -0.132, - -0.22, - -0.28, - -0.504, - -0.924, - -1.192, - -1.176, - -0.988, - -0.648, - -0.088, - 0.08, - 0.212, - 0.104, - 0.144, - 0.008, - -0.112, - -0.264, - -0.336, - -0.312, - -0.468, - -0.856, - -1.132, - -1.12, - -0.932, - -0.692, - -0.384, - -0.084, - 0.132, - 0.064, - 0.02, - 0.016, - -0.116, - -0.212, - -0.288, - -0.28, - -0.468, - -1.032, - -1.404, - -1.112, - -0.844, - -0.616, - -0.22, - -0.044, - -0.132, - -0.22, - -0.088, - -0.164, - -0.252, - -0.32, - -0.368, - -0.432, - -0.604, - -0.876, - -1.016, - -1, - -0.92, - -0.848 + -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, + -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, + -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, + 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, + -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, + -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, + 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, + -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, + -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 ], "y": [ - -0.22, - -0.116, - -0.016, - 0.048, - 0.056, - 0, - -0.02, - 0.036, - 0.056, - -0.256, - -1.256, - -1.636, - -1.248, - -0.856, - -0.344, - 0.048, - 0.54, - 1.044, - 1.216, - 1.132, - 0.848, - 0.468, - -0.156, - -1.38, - -1.892, - -1.296, - -0.94, - -0.836, - -0.496, - -0.092, - 0.288, - 0.664, - 0.7, - 0.648, - 0.716, - 0.6, - 0.232, - -0.704, - -1.436, - -1.448, - -1.124, - -0.968, - -0.736, - -0.62, - 0.052, - 0.148, - 0.328, - 0.54, - 0.572, - 0.564, - 0.544, - 0.32, - -0.064, - -0.828, - -1.444, - -1.42, - -1.224, - -0.952, - -0.748, - -0.524, - -0.44, - -0.744, - 0.068, - 0.456, - 0.816, - 0.788, - 0.824, - 0.536, - 0.124, - -0.688, - -1.372, - -1.516, - -0.964, - -0.58, - -0.488, - -0.46, - -0.132, - 0.264, - 0.432, - 0.58, - 0.7, - 0.624, - 0.556, - 0.312, - 0.036, - -0.832, - -1.244, - -1.18, - -0.9, - -0.764, - -0.8 + -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, + -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, + 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, + 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, + -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, + -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, + 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, + -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, + 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 ], "z": [ - -1.448, - -1.808, - -2.04, - -2.04, - -1.684, - -1.22, - -0.936, - -0.54, - 0.412, - 1.76, - 2.04, - 1.732, - 0.708, - -0.084, - -1.284, - -2.04, - -2.04, - -2.04, - -2.04, - -1.44, - -1.12, - -0.88, - -0.312, - 0.328, - 0.988, - 1.404, - 1.352, - 0.476, - -0.152, - -1.276, - -2.04, - -2.04, - -2.04, - -2.04, - -1.668, - -1.176, - -0.596, - -0.04, - 0.844, - 1.492, - 1.636, - 1.064, - 0.636, - -0.136, - -1.344, - -2.04, - -2.04, - -2.04, - -2.04, - -1.82, - -1.36, - -1.044, - -0.616, - 0.18, - 0.912, - 1.432, - 1.756, - 1.2, - 0.528, - 0.064, - -0.612, - -1.708, - -2.04, - -2.04, - -2.04, - -1.932, - -1.668, - -1.3, - -0.744, - -0.032, - 0.692, - 1.284, - 1.32, - 0.712, - 0.16, - -0.344, - -1.06, - -2.04, - -2.04, - -2.04, - -2.04, - -1.692, - -1.396, - -1.04, - -0.636, - 0.04, - 0.568, - 0.784, - 0.948, - 1.012, - 0.68 + -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, + 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, + -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, + -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, + 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, + -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, + -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, + 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, + -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 ] } }, @@ -1733,286 +243,39 @@ "ID": 1705437816208, "data": { "x": [ - -0.692, - -0.644, - -0.548, - -0.516, - -0.74, - -0.872, - -0.26, - 0.108, - 0.268, - 0.436, - 0.248, - -0.04, - -0.224, - -0.272, - -0.176, - 0.316, - -0.408, - -1.336, - -1.112, - -0.712, - -0.52, - -0.432, - -0.188, - 0.088, - 0.148, - 0.028, - -0.072, - -0.164, - -0.204, - -0.144, - 0.252, - -0.232, - -1.312, - -1.132, - -0.92, - -0.764, - -0.432, - -0.072, - 0.096, - 0.184, - 0.156, - 0.052, - -0.02, - -0.068, - 0.136, - -0.276, - -1.244, - -1.136, - -0.996, - -0.872, - -0.576, - -0.328, - -0.172, - -0.084, - -0.072, - -0.08, - -0.168, - -0.22, - -0.128, - -0.112, - -1.036, - -1.22, - -1.008, - -0.856, - -0.688, - -0.368, - -0.196, - -0.036, - 0.052, - 0.092, - 0.076, - 0.04, - -0.084, - -0.212, - -0.168, - 0.124, - -0.724, - -1.376, - -1.048, - -0.872, - -0.776, - -0.608, - -0.324, - -0.128, - -0.004, - 0.024, - 0.024, - -0.008, - -0.12, - -0.248, - -0.384, - -0.356 + -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, + 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, + -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, + 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, + 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, + -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, + -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, + 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, + -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, + -0.384, -0.356 ], "y": [ - -1.164, - -1.268, - -1.532, - -1.984, - -2.04, - -2.04, - -1.672, - -0.532, - 0.236, - 1.008, - 1.736, - 2.012, - 1.868, - 1.056, - 0.692, - -0.156, - -2.04, - -2.04, - -2.04, - -2.04, - -1.464, - -0.956, - -0.604, - 0.364, - 1.252, - 1.812, - 2.04, - 1.88, - 1.476, - 0.836, - -0.228, - -2.04, - -2.04, - -2.04, - -2.024, - -1.712, - -1.132, - -0.2, - 0.816, - 1.624, - 2.04, - 2.04, - 1.904, - 1.084, - 0.184, - -2.04, - -2.04, - -2.04, - -1.536, - -1.236, - -0.616, - 0, - 0.796, - 1.36, - 1.648, - 1.532, - 1.3, - 0.904, - 0.28, - -1.288, - -2.04, - -2.04, - -1.864, - -1.692, - -1.2, - -0.572, - 0.008, - 0.548, - 1.228, - 1.792, - 2.02, - 1.8, - 1.408, - 1.012, - 0.528, - -0.748, - -2.04, - -2.04, - -1.896, - -1.128, - -1.06, - -0.936, - -0.468, - 0.244, - 0.868, - 1.392, - 1.772, - 1.712, - 1.304, - 0.924, - 0.544, - 0.144 + -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, + 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, + -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, + -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, + 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, + 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, + -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, + -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, + 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 ], "z": [ - -0.116, - -0.136, - -0.024, - 0.108, - 0.696, - 0.724, - 0.424, - 0.18, - 0.196, - -0.072, - -0.788, - -1.488, - -1.564, - -0.98, - -0.712, - -0.504, - -0.528, - 0.736, - 0.544, - 0.124, - -0.104, - -0.204, - -0.084, - -0.356, - -0.752, - -1.048, - -1.136, - -1, - -0.92, - -0.82, - -0.736, - -0.34, - 0.824, - 0.26, - -0.084, - -0.072, - -0.176, - -0.42, - -0.6, - -0.852, - -1.132, - -1.308, - -1.16, - -0.968, - -0.868, - -0.744, - 0.668, - 0.048, - -0.156, - -0.248, - -0.364, - -0.6, - -0.932, - -1.116, - -1.224, - -1.196, - -1.16, - -1.044, - -0.884, - -0.748, - 0.392, - 0.376, - -0.156, - -0.228, - -0.268, - -0.54, - -0.74, - -0.784, - -0.816, - -0.92, - -1.024, - -0.984, - -0.904, - -0.868, - -0.86, - -1.072, - 0.016, - 0.704, - -0.076, - -0.348, - -0.336, - -0.372, - -0.38, - -0.628, - -0.84, - -0.964, - -1.06, - -1.116, - -1.044, - -0.972, - -0.912, - -0.944 + -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, + -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, + -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, + -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, + -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, + -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, + 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, + -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, + -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, + -0.912, -0.944 ] } }, @@ -2020,286 +283,37 @@ "ID": 1705437808407, "data": { "x": [ - -0.756, - -0.972, - -0.836, - -0.82, - -0.832, - -0.728, - -0.904, - -0.376, - -0.464, - -0.328, - -0.112, - 0.084, - 0.172, - 0.292, - 0.404, - 0.356, - 0.3, - 0.116, - 0.068, - -0.084, - -0.348, - -0.396, - -0.628, - -0.752, - -0.744, - -0.856, - -0.82, - -0.808, - -0.688, - -0.572, - -0.436, - -0.308, - -0.324, - -0.068, - 0.184, - 0.24, - 0.276, - 0.268, - 0.284, - 0.26, - 0.028, - 0.016, - -0.224, - -0.472, - -0.54, - -0.612, - -0.764, - -0.916, - -0.976, - -0.984, - -0.94, - -0.844, - -0.8, - -0.524, - -0.432, - -0.256, - -0.1, - 0.124, - 0.208, - 0.284, - 0.272, - 0.288, - 0.232, - 0.152, - 0.004, - -0.28, - -0.408, - -0.656, - -0.836, - -1.068, - -1.036, - -1.016, - -0.952, - -0.844, - -0.724, - -0.68, - -0.796, - -0.564, - -0.34, - -0.208, - -0.104, - 0.052, - 0.168, - 0.292, - 0.42, - 0.408, - 0.436, - 0.352, - 0.164, - -0.04, - -0.352, - -0.488 + -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, + -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, + -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, + -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, + -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, + -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, + 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, + -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, + 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 ], "y": [ - 0.668, - 0.968, - 0.696, - 0.572, - 0.688, - 0.628, - 0.932, - 0.596, - 0.836, - 0.78, - 0.556, - 0.52, - 0.492, - 0.38, - 0.452, - 0.304, - 0.08, - 0.144, - 0.264, - 0.184, - 0.144, - 0.472, - 0.904, - 1.004, - 0.752, - 0.7, - 0.808, - 1.032, - 0.988, - 0.676, - 0.524, - 0.688, - 0.664, - 0.536, - 0.4, - 0.304, - 0.256, - 0.276, - 0.32, - 0.296, - 0.408, - 0.18, - 0.468, - 0.744, - 0.724, - 0.684, - 0.88, - 1.072, - 1.108, - 1, - 0.912, - 0.816, - 0.912, - 0.756, - 0.728, - 0.668, - 0.5, - 0.404, - 0.364, - 0.368, - 0.372, - 0.364, - 0.424, - 0.356, - 0.316, - 0.532, - 0.62, - 0.908, - 1.184, - 1.192, - 1.028, - 0.988, - 1.188, - 1.344, - 1.268, - 0.952, - 0.816, - 0.824, - 0.812, - 0.78, - 0.704, - 0.56, - 0.428, - 0.328, - 0.26, - 0.256, - 0.104, - 0.172, - 0.096, - 0.452, - 0.732, - 0.976 + 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, + 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, + 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, + 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, + 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, + 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, + 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, + 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, + 0.452, 0.732, 0.976 ], "z": [ - -1.704, - -1.86, - -1.568, - -1.4, - -1.512, - -1.552, - -1.24, - -1.304, - -1.22, - -0.76, - -0.304, - -0.084, - 0.088, - 0.2, - 0.396, - 0.468, - 0.192, - -0.008, - -0.492, - -0.796, - -0.96, - -1.336, - -1.768, - -1.676, - -1.424, - -1.296, - -1.3, - -1.488, - -1.508, - -1.12, - -1.056, - -0.924, - -0.6, - -0.18, - -0.084, - -0.008, - -0.012, - 0, - 0.016, - 0.028, - -0.012, - -0.3, - -0.692, - -1.236, - -1.444, - -1.212, - -1.236, - -1.4, - -1.524, - -1.456, - -1.54, - -1.524, - -1.288, - -0.888, - -0.58, - -0.268, - -0.092, - -0.04, - 0.02, - 0.104, - 0.14, - 0.144, - 0.18, - 0.116, - -0.116, - -0.504, - -0.78, - -0.996, - -1.284, - -1.42, - -1.38, - -1.304, - -1.312, - -1.304, - -1.288, - -1.24, - -1.036, - -0.704, - -0.484, - -0.292, - -0.132, - -0.004, - 0.032, - 0.156, - 0.248, - 0.28, - 0.22, - 0.092, - -0.144, - -0.56, - -1.076, - -1.304 + -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, + -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, + -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, + -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, + -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, + -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, + 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, + -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, + -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 ] } }, @@ -2307,286 +321,38 @@ "ID": 1705437804840, "data": { "x": [ - 0.42, - 0.624, - 0.688, - 0.412, - 0.552, - 0.592, - 0.532, - 0.592, - 0.292, - -0.084, - -0.284, - -0.044, - -0.428, - 0.824, - 1.172, - 0.508, - 0.428, - 0.344, - 0.512, - 0, - -0.616, - 0.156, - 0.232, - 0.268, - 0.78, - 0.636, - 0.428, - 0.688, - 0.764, - 0.416, - 0.432, - 0.444, - 0.32, - 0.312, - 0.348, - 0.32, - 0.472, - 0.292, - 0.092, - 0.268, - 0.348, - 0.424, - 0.508, - 0.716, - 0.708, - 0.684, - 0.516, - 0.46, - 0.3, - 0.084, - 0.08, - 0.108, - -0.036, - -0.012, - 0.088, - 0.032, - 0.092, - 0.136, - 0.252, - 0.032, - 0.36, - 0.476, - 0.656, - 0.744, - 0.72, - 0.692, - 0.62, - 0.572, - 0.464, - 0.248, - -0.016, - 0.096, - -0.18, - -0.176, - -0.004, - -0.04, - 0.112, - 0.22, - 0.024, - 0.02, - 0.136, - 0.208, - 0.136, - 0.448, - 0.764, - 0.808, - 0.752, - 0.72, - 0.7, - 0.48, - 0.304, - 0.052 + 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, + -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, + 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, + 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, + 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, + 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, + 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, + 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, + 0.48, 0.304, 0.052 ], "y": [ - -1.136, - -1.324, - -0.948, - -1, - -1.404, - -1.46, - -1.192, - -0.868, - -0.432, - 0.144, - 0.528, - 0.496, - 1.672, - 2.04, - 2.04, - 2.04, - 2.04, - 1.416, - 1.6, - 1.212, - 0.732, - -1.24, - -0.648, - -0.616, - -1.356, - -1.124, - -0.792, - -0.728, - -0.816, - -0.044, - 0.556, - 1.332, - 1.496, - 1.224, - 1.184, - 1.48, - 1.644, - 1.06, - 0.524, - 0.012, - -0.1, - -0.472, - -0.9, - -1.224, - -1.252, - -1.116, - -1.024, - -0.592, - -0.216, - 0.372, - 1.112, - 1.648, - 2.008, - 1.844, - 1.728, - 1.544, - 1.108, - 0.768, - 0.512, - 0.396, - -0.436, - -0.692, - -0.892, - -0.976, - -0.912, - -0.756, - -0.58, - -0.432, - -0.296, - -0.108, - 0.472, - 1.108, - 1.772, - 1.7, - 1.284, - 1.516, - 1.564, - 1.552, - 0.992, - 0.436, - 0.384, - 0.356, - -0.376, - -0.936, - -1.132, - -1.172, - -0.964, - -0.828, - -0.592, - -0.408, - -0.208, - 0.248 + -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, + 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, + -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, + 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, + -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, + 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, + -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, + 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, + -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 ], "z": [ - -0.468, - -0.332, - -0.256, - 0.184, - -0.152, - -0.46, - -0.5, - -0.568, - -0.408, - -0.296, - -0.144, - -0.636, - -1.18, - -1.696, - -0.276, - -0.22, - 0.472, - 0.252, - -0.284, - -0.884, - -0.368, - -0.308, - -0.66, - -0.488, - -1.112, - -1.176, - -0.8, - -0.732, - -0.292, - -0.34, - -0.56, - -0.452, - -0.856, - -0.984, - -1.1, - -0.888, - -0.752, - -0.66, - -0.652, - -0.716, - -0.632, - -0.48, - -0.452, - -0.532, - -0.684, - -0.684, - -0.568, - -0.404, - -0.608, - -0.8, - -1.24, - -1, - -0.872, - -0.684, - -0.516, - -0.828, - -0.7, - -0.732, - -0.432, - -0.452, - -0.54, - -0.552, - -0.868, - -0.7, - -0.56, - -0.56, - -0.772, - -0.772, - -0.792, - -0.796, - -1.1, - -0.88, - -0.996, - -0.608, - -0.492, - -0.436, - -0.648, - -0.608, - -0.6, - -0.716, - -0.628, - -0.592, - -0.848, - -0.792, - -0.816, - -0.768, - -0.604, - -0.56, - -0.504, - -0.652, - -0.704, - -0.916 + -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, + -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, + -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, + -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, + -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, + -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, + -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, + -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, + -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, + -0.704, -0.916 ] } } @@ -2607,288 +373,38 @@ "ID": 1705437969952, "data": { "x": [ - -0.064, - -0.06, - -0.06, - -0.06, - -0.06, - -0.064, - -0.056, - -0.056, - -0.06, - -0.06, - -0.064, - -0.056, - -0.056, - -0.056, - -0.06, - -0.052, - -0.064, - -0.06, - -0.064, - -0.06, - -0.06, - -0.052, - -0.056, - -0.06, - -0.064, - -0.06, - -0.064, - -0.06, - -0.06, - -0.068, - -0.056, - -0.056, - -0.048, - -0.064, - -0.056, - -0.06, - -0.056, - -0.056, - -0.056, - -0.056, - -0.06, - -0.06, - -0.056, - -0.064, - -0.064, - -0.056, - -0.056, - -0.056, - -0.06, - -0.056, - -0.064, - -0.06, - -0.056, - -0.064, - -0.064, - -0.056, - -0.056, - -0.064, - -0.056, - -0.06, - -0.052, - -0.06, - -0.056, - -0.06, - -0.06, - -0.06, - -0.06, - -0.056, - -0.06, - -0.052, - -0.048, - -0.06, - -0.06, - -0.056, - -0.064, - -0.06, - -0.056, - -0.056, - -0.06, - -0.056, - -0.064, - -0.06, - -0.056, - -0.06, - -0.06, - -0.052, - -0.064, - -0.056, - -0.068, - -0.06, - -0.06, - -0.052, - -0.06 + -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, + -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, + -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, + -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, + -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, + -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, + -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, + -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, + -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, + -0.06, -0.052, -0.06 ], "y": [ - 0.768, - 0.768, - 0.772, - 0.76, - 0.76, - 0.772, - 0.764, - 0.76, - 0.764, - 0.764, - 0.772, - 0.764, - 0.76, - 0.764, - 0.76, - 0.768, - 0.764, - 0.76, - 0.764, - 0.76, - 0.772, - 0.76, - 0.764, - 0.768, - 0.764, - 0.764, - 0.76, - 0.764, - 0.756, - 0.764, - 0.764, - 0.76, - 0.76, - 0.764, - 0.764, - 0.76, - 0.756, - 0.76, - 0.76, - 0.756, - 0.76, - 0.764, - 0.768, - 0.764, - 0.752, - 0.76, - 0.76, - 0.756, - 0.756, - 0.772, - 0.76, - 0.764, - 0.76, - 0.764, - 0.76, - 0.768, - 0.764, - 0.756, - 0.76, - 0.764, - 0.764, - 0.764, - 0.76, - 0.756, - 0.768, - 0.764, - 0.756, - 0.76, - 0.764, - 0.764, - 0.764, - 0.764, - 0.764, - 0.756, - 0.76, - 0.76, - 0.772, - 0.76, - 0.764, - 0.76, - 0.772, - 0.764, - 0.756, - 0.76, - 0.768, - 0.752, - 0.76, - 0.76, - 0.76, - 0.756, - 0.76, - 0.764, - 0.752 + 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, + 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, + 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, + 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, + 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, + 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, + 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, + 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, + 0.764, 0.752 ], "z": [ - -0.7, - -0.684, - -0.7, - -0.688, - -0.696, - -0.696, - -0.7, - -0.696, - -0.7, - -0.692, - -0.696, - -0.704, - -0.696, - -0.692, - -0.7, - -0.696, - -0.696, - -0.688, - -0.692, - -0.684, - -0.7, - -0.696, - -0.704, - -0.692, - -0.704, - -0.7, - -0.7, - -0.7, - -0.708, - -0.7, - -0.696, - -0.692, - -0.696, - -0.7, - -0.7, - -0.692, - -0.704, - -0.692, - -0.708, - -0.696, - -0.704, - -0.7, - -0.692, - -0.692, - -0.708, - -0.704, - -0.696, - -0.704, - -0.704, - -0.704, - -0.704, - -0.704, - -0.7, - -0.696, - -0.708, - -0.7, - -0.696, - -0.696, - -0.692, - -0.704, - -0.696, - -0.696, - -0.688, - -0.7, - -0.708, - -0.696, - -0.704, - -0.696, - -0.708, - -0.7, - -0.712, - -0.708, - -0.696, - -0.7, - -0.696, - -0.696, - -0.7, - -0.696, - -0.704, - -0.696, - -0.708, - -0.7, - -0.7, - -0.7, - -0.704, - -0.7, - -0.684, - -0.692, - -0.696, - -0.704, - -0.704, - -0.7, + -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, + -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, + -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, + -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, + -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, + -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, + -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, + -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, + -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, -0.7 ] } @@ -2897,286 +413,40 @@ "ID": 1705437870493, "data": { "x": [ - -0.804, - -0.836, - -0.848, - -0.812, - -0.768, - -0.784, - -0.808, - -0.776, - -0.7, - -0.648, - -0.636, - -0.664, - -0.672, - -0.704, - -0.712, - -0.72, - -0.756, - -0.752, - -0.704, - -0.68, - -0.692, - -0.708, - -0.72, - -0.756, - -0.764, - -0.768, - -0.772, - -0.736, - -0.748, - -0.74, - -0.772, - -0.764, - -0.752, - -0.736, - -0.732, - -0.756, - -0.76, - -0.772, - -0.74, - -0.74, - -0.748, - -0.748, - -0.752, - -0.772, - -0.756, - -0.748, - -0.732, - -0.74, - -0.752, - -0.756, - -0.748, - -0.732, - -0.74, - -0.76, - -0.752, - -0.772, - -0.764, - -0.74, - -0.736, - -0.748, - -0.764, - -0.756, - -0.728, - -0.74, - -0.756, - -0.752, - -0.744, - -0.756, - -0.744, - -0.732, - -0.728, - -0.74, - -0.752, - -0.76, - -0.776, - -0.78, - -0.772, - -0.74, - -0.732, - -0.74, - -0.764, - -0.776, - -0.772, - -0.764, - -0.748, - -0.76, - -0.736, - -0.748, - -0.744, - -0.768, - -0.812, - -0.808 + -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, + -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, + -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, + -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, + -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, + -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, + -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, + -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, + -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, + -0.812, -0.808 ], "y": [ - -0.48, - -0.472, - -0.48, - -0.492, - -0.476, - -0.432, - -0.428, - -0.476, - -0.548, - -0.552, - -0.536, - -0.516, - -0.496, - -0.484, - -0.476, - -0.484, - -0.484, - -0.5, - -0.504, - -0.508, - -0.484, - -0.492, - -0.496, - -0.484, - -0.48, - -0.476, - -0.484, - -0.492, - -0.468, - -0.484, - -0.476, - -0.484, - -0.492, - -0.5, - -0.488, - -0.484, - -0.476, - -0.476, - -0.488, - -0.496, - -0.492, - -0.472, - -0.464, - -0.464, - -0.476, - -0.48, - -0.488, - -0.484, - -0.468, - -0.468, - -0.484, - -0.488, - -0.484, - -0.488, - -0.472, - -0.476, - -0.476, - -0.48, - -0.492, - -0.476, - -0.472, - -0.476, - -0.484, - -0.484, - -0.484, - -0.484, - -0.492, - -0.488, - -0.456, - -0.48, - -0.484, - -0.472, - -0.476, - -0.46, - -0.472, - -0.472, - -0.46, - -0.472, - -0.488, - -0.476, - -0.472, - -0.488, - -0.492, - -0.5, - -0.492, - -0.488, - -0.484, - -0.492, - -0.488, - -0.472, - -0.428, - -0.436 + -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, + -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, + -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, + -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, + -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, + -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, + -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, + -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, + -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, + -0.428, -0.436 ], "z": [ - -0.56, - -0.592, - -0.572, - -0.52, - -0.512, - -0.568, - -0.656, - -0.656, - -0.56, - -0.504, - -0.54, - -0.608, - -0.636, - -0.652, - -0.66, - -0.64, - -0.66, - -0.66, - -0.584, - -0.56, - -0.576, - -0.6, - -0.592, - -0.58, - -0.62, - -0.608, - -0.616, - -0.576, - -0.596, - -0.572, - -0.604, - -0.592, - -0.568, - -0.572, - -0.56, - -0.584, - -0.584, - -0.612, - -0.572, - -0.568, - -0.576, - -0.588, - -0.6, - -0.596, - -0.572, - -0.564, - -0.568, - -0.592, - -0.6, - -0.604, - -0.588, - -0.572, - -0.58, - -0.584, - -0.588, - -0.596, - -0.584, - -0.564, - -0.568, - -0.592, - -0.596, - -0.592, - -0.588, - -0.572, - -0.588, - -0.6, - -0.576, - -0.592, - -0.624, - -0.572, - -0.564, - -0.584, - -0.588, - -0.6, - -0.6, - -0.592, - -0.58, - -0.564, - -0.552, - -0.564, - -0.58, - -0.592, - -0.588, - -0.584, - -0.568, - -0.58, - -0.56, - -0.568, - -0.548, - -0.58, - -0.636, - -0.608 + -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, + -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, + -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, + -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, + -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, + -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, + -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, + -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, + -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, + -0.636, -0.608 ] } }, @@ -3184,289 +454,39 @@ "ID": 1705437864426, "data": { "x": [ - 0.892, - 0.972, - 0.972, - 0.92, - 0.916, - 0.964, - 0.924, - 0.932, - 0.984, - 0.932, - 0.876, - 0.88, - 0.904, - 0.92, - 0.948, - 0.984, - 0.992, - 0.948, - 0.948, - 0.992, - 0.98, - 0.952, - 0.948, - 0.928, - 0.964, - 0.94, - 0.94, - 0.948, - 0.976, - 0.964, - 0.956, - 0.936, - 0.948, - 0.94, - 0.932, - 0.96, - 0.952, - 0.936, - 0.98, - 0.984, - 0.948, - 0.94, - 0.928, - 0.928, - 0.94, - 0.96, - 0.94, - 0.94, - 0.94, - 0.936, - 0.948, - 0.948, - 0.952, - 0.952, - 0.936, - 0.96, - 0.96, - 0.956, - 0.96, - 0.952, - 0.94, - 0.956, - 0.94, - 0.94, - 0.948, - 0.94, - 0.952, - 0.952, - 0.952, - 0.944, - 0.944, - 0.96, - 0.976, - 0.968, - 0.944, - 0.928, - 0.94, - 0.956, - 0.952, - 0.952, - 0.948, - 0.952, - 0.948, - 0.944, - 0.944, - 0.94, - 0.92, - 0.94, - 0.952, - 0.964, - 0.968, - 0.968, - 0.944 + 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, + 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, + 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, + 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, + 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, + 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, + 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, + 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, + 0.968, 0.944 ], "y": [ - -0.02, - -0.088, - -0.116, - -0.144, - -0.14, - -0.016, - 0.012, - -0.04, - -0.1, - -0.076, - -0.088, - -0.096, - -0.104, - -0.088, - -0.092, - -0.096, - -0.076, - -0.072, - -0.072, - -0.08, - -0.076, - -0.084, - -0.104, - -0.088, - -0.104, - -0.088, - -0.116, - -0.116, - -0.132, - -0.116, - -0.108, - -0.104, - -0.12, - -0.116, - -0.1, - -0.108, - -0.112, - -0.096, - -0.108, - -0.112, - -0.1, - -0.1, - -0.1, - -0.104, - -0.116, - -0.12, - -0.096, - -0.108, - -0.12, - -0.116, - -0.108, - -0.104, - -0.112, - -0.112, - -0.096, - -0.096, - -0.104, - -0.088, - -0.096, - -0.1, - -0.108, - -0.1, - -0.108, - -0.096, - -0.1, - -0.104, - -0.108, - -0.104, - -0.1, - -0.088, - -0.092, - -0.1, - -0.1, - -0.096, - -0.088, - -0.092, - -0.096, - -0.096, - -0.104, - -0.088, - -0.08, - -0.108, - -0.092, - -0.096, - -0.1, - -0.1, - -0.08, - -0.1, - -0.096, - -0.092, - -0.092, - -0.112, - -0.088 + -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, + -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, + -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, + -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, + -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, + -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, + -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, + -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, + -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, + -0.096, -0.092, -0.092, -0.112, -0.088 ], "z": [ - 0.208, - -0.032, - -0.252, - -0.316, - -0.292, - -0.224, - -0.14, - 0.116, - 0, - -0.196, - -0.188, - -0.16, - -0.172, - -0.096, - -0.152, - -0.228, - -0.268, - -0.248, - -0.172, - -0.14, - -0.136, - -0.188, - -0.204, - -0.14, - -0.116, - -0.104, - -0.18, - -0.216, - -0.192, - -0.168, - -0.132, - -0.16, - -0.188, - -0.184, - -0.14, - -0.124, - -0.176, - -0.176, - -0.16, - -0.18, - -0.176, - -0.152, - -0.156, - -0.168, - -0.176, - -0.172, - -0.168, - -0.152, - -0.16, - -0.156, - -0.18, - -0.168, - -0.176, - -0.188, - -0.172, - -0.18, - -0.176, - -0.188, - -0.168, - -0.16, - -0.196, - -0.192, - -0.184, - -0.152, - -0.148, - -0.2, - -0.2, - -0.172, - -0.168, - -0.168, - -0.18, - -0.176, - -0.168, - -0.16, - -0.148, - -0.148, - -0.164, - -0.184, - -0.184, - -0.176, - -0.144, - -0.156, - -0.176, - -0.184, - -0.172, - -0.172, - -0.144, - -0.168, - -0.188, - -0.18, - -0.176, - -0.14, - -0.144 + 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, + -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, + -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, + -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, + -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, + -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, + -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, + -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, + -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, + -0.176, -0.14, -0.144 ] } }, @@ -3474,286 +494,37 @@ "ID": 1705437860385, "data": { "x": [ - 0, - 0.004, - 0, - 0.004, - 0.016, - 0, - 0.004, - -0.06, - -0.004, - -0.04, - -0.048, - -0.052, - -0.056, - -0.048, - -0.052, - -0.044, - -0.052, - -0.044, - -0.048, - -0.04, - -0.056, - -0.052, - -0.052, - -0.056, - -0.056, - -0.044, - -0.044, - -0.056, - -0.052, - -0.048, - -0.048, - -0.048, - -0.048, - -0.048, - -0.06, - -0.056, - -0.052, - -0.056, - -0.056, - -0.052, - -0.056, - -0.056, - -0.056, - -0.048, - -0.048, - -0.048, - -0.06, - -0.052, - -0.056, - -0.048, - -0.052, - -0.048, - -0.048, - -0.048, - -0.044, - -0.048, - -0.052, - -0.052, - -0.056, - -0.056, - -0.044, - -0.048, - -0.052, - -0.052, - -0.048, - -0.052, - -0.056, - -0.052, - -0.056, - -0.044, - -0.048, - -0.052, - -0.048, - -0.052, - -0.052, - -0.052, - -0.048, - -0.056, - -0.056, - -0.048, - -0.052, - -0.056, - -0.048, - -0.056, - -0.048, - -0.048, - -0.052, - -0.052, - -0.048, - -0.052, - -0.052, - -0.044 + 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, + -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, + -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, + -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, + -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, + -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, + -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, + -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, + -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, + -0.052, -0.048, -0.052, -0.052, -0.044 ], "y": [ - 0.16, - 0.172, - 0.184, - 0.184, - 0.168, - 0.188, - 0.192, - 0.212, - 0.16, - 0.208, - 0.224, - 0.224, - 0.22, - 0.22, - 0.224, - 0.22, - 0.22, - 0.216, - 0.2, - 0.216, - 0.228, - 0.224, - 0.224, - 0.232, - 0.228, - 0.232, - 0.224, - 0.224, - 0.228, - 0.228, - 0.228, - 0.228, - 0.232, - 0.236, - 0.232, - 0.232, - 0.228, - 0.22, - 0.232, - 0.228, - 0.228, - 0.232, - 0.236, - 0.232, - 0.232, - 0.224, - 0.224, - 0.232, - 0.224, - 0.236, - 0.228, - 0.232, - 0.228, - 0.224, - 0.228, - 0.232, - 0.228, - 0.228, - 0.232, - 0.232, - 0.228, - 0.224, - 0.224, - 0.232, - 0.236, - 0.228, - 0.228, - 0.236, - 0.232, - 0.224, - 0.228, - 0.224, - 0.236, - 0.236, - 0.232, - 0.228, - 0.228, - 0.236, - 0.232, - 0.228, - 0.232, - 0.232, - 0.228, - 0.232, - 0.228, - 0.228, - 0.228, - 0.228, - 0.224, - 0.232, - 0.232, - 0.232 + 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, + 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, + 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, + 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, + 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, + 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, + 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, + 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, + 0.232, 0.232, 0.232 ], "z": [ - 1, - 1.016, - 1.032, - 1.02, - 0.98, - 1.004, - 0.788, - 1.06, - 1.028, - 1.004, - 0.996, - 1.012, - 1, - 1.012, - 1.008, - 1.004, - 0.996, - 1.008, - 1.012, - 1.012, - 1.012, - 1.012, - 1.012, - 1, - 1.012, - 1.008, - 1.004, - 1.012, - 1.008, - 1.008, - 1.008, - 1.008, - 0.996, - 0.996, - 1.008, - 1.012, - 1.008, - 1.012, - 1.008, - 1.012, - 1.004, - 1.008, - 1.004, - 1.004, - 1.004, - 1, - 1.008, - 1.004, - 1.004, - 1.012, - 1.008, - 1.004, - 1.004, - 1.008, - 1.008, - 1.008, - 1, - 1.004, - 1, - 1, - 1, - 1.008, - 1.004, - 1, - 1.004, - 1.004, - 1.004, - 1, - 1.008, - 1.012, - 1.004, - 1.004, - 1.008, - 1.008, - 1, - 1.012, - 1.004, - 0.996, - 1.008, - 1, - 1.008, - 1.012, - 0.996, - 0.996, - 1.004, - 1.008, - 1.012, - 1.008, - 1.004, - 1.008, - 1.008, - 1.008 + 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, + 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, + 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, + 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, + 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, + 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, + 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, + 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 ] } }, @@ -3761,289 +532,39 @@ "ID": 1705437854703, "data": { "x": [ - -0.076, - -0.076, - -0.072, - -0.068, - -0.076, - -0.072, - -0.072, - -0.056, - -0.104, - -0.068, - -0.072, - -0.064, - -0.072, - -0.044, - -0.072, - -0.064, - -0.064, - -0.068, - -0.076, - -0.076, - -0.068, - -0.068, - -0.072, - -0.068, - -0.072, - -0.068, - -0.072, - -0.068, - -0.056, - -0.064, - -0.068, - -0.068, - -0.068, - -0.068, - -0.06, - -0.072, - -0.076, - -0.072, - -0.068, - -0.064, - -0.072, - -0.06, - -0.072, - -0.068, - -0.064, - -0.06, - -0.064, - -0.068, - -0.064, - -0.072, - -0.064, - -0.068, - -0.076, - -0.068, - -0.064, - -0.064, - -0.064, - -0.068, - -0.064, - -0.072, - -0.068, - -0.072, - -0.06, - -0.06, - -0.076, - -0.06, - -0.064, - -0.064, - -0.06, - -0.068, - -0.076, - -0.064, - -0.064, - -0.064, - -0.072, - -0.068, - -0.064, - -0.064, - -0.088, - -0.06, - -0.06, - -0.06, - -0.064, - -0.056, - -0.06, - -0.064, - -0.064, - -0.064, - -0.064, - -0.068, - -0.072, - -0.068, - -0.06 + -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, + -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, + -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, + -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, + -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, + -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, + -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, + -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, + -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, + -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 ], "y": [ - 0.968, - 0.96, - 0.956, - 0.964, - 0.964, - 0.96, - 0.96, - 0.976, - 0.96, - 0.96, - 0.96, - 0.96, - 0.956, - 0.948, - 0.952, - 0.956, - 0.944, - 0.944, - 0.944, - 0.944, - 0.948, - 0.952, - 0.944, - 0.948, - 0.948, - 0.94, - 0.948, - 0.944, - 0.956, - 0.948, - 0.944, - 0.952, - 0.952, - 0.952, - 0.952, - 0.952, - 0.948, - 0.948, - 0.948, - 0.952, - 0.944, - 0.944, - 0.948, - 0.948, - 0.944, - 0.948, - 0.952, - 0.948, - 0.952, - 0.948, - 0.94, - 0.944, - 0.952, - 0.952, - 0.936, - 0.948, - 0.948, - 0.944, - 0.948, - 0.944, - 0.944, - 0.944, - 0.94, - 0.956, - 0.944, - 0.952, - 0.94, - 0.952, - 0.944, - 0.944, - 0.944, - 0.956, - 0.948, - 0.952, - 0.944, - 0.94, - 0.936, - 0.944, - 0.936, - 0.94, - 0.956, - 0.952, - 0.948, - 0.944, - 0.952, - 0.94, - 0.948, - 0.948, - 0.952, - 0.948, - 0.948, - 0.948, - 0.948 + 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, + 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, + 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, + 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, + 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, + 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, + 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, + 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, + 0.948, 0.948, 0.948, 0.948 ], "z": [ - -0.396, - -0.388, - -0.392, - -0.388, - -0.38, - -0.36, - -0.404, - -0.416, - -0.396, - -0.42, - -0.388, - -0.42, - -0.42, - -0.564, - -0.424, - -0.416, - -0.424, - -0.44, - -0.432, - -0.428, - -0.428, - -0.432, - -0.428, - -0.428, - -0.432, - -0.428, - -0.424, - -0.432, - -0.436, - -0.428, - -0.428, - -0.432, - -0.432, - -0.428, - -0.424, - -0.436, - -0.428, - -0.432, - -0.432, - -0.436, - -0.424, - -0.424, - -0.428, - -0.424, - -0.436, - -0.432, - -0.42, - -0.428, - -0.436, - -0.424, - -0.424, - -0.424, - -0.436, - -0.428, - -0.428, - -0.424, - -0.428, - -0.436, - -0.424, - -0.428, - -0.44, - -0.424, - -0.428, - -0.432, - -0.432, - -0.42, - -0.428, - -0.428, - -0.428, - -0.436, - -0.428, - -0.428, - -0.42, - -0.428, - -0.436, - -0.432, - -0.436, - -0.44, - -0.428, - -0.432, - -0.432, - -0.432, - -0.432, - -0.428, - -0.44, - -0.444, - -0.436, - -0.424, - -0.424, - -0.432, - -0.424, - -0.432, - -0.436 + -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, + -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, + -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, + -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, + -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, + -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, + -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, + -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, + -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, + -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 ] } }, @@ -4051,286 +572,38 @@ "ID": 1705437847993, "data": { "x": [ - 0.744, - 0.76, - 0.66, - 0.688, - 0.784, - 0.736, - 0.704, - 0.712, - 0.732, - 0.72, - 0.768, - 0.772, - 0.76, - 0.784, - 0.76, - 0.776, - 0.776, - 0.776, - 0.744, - 0.744, - 0.76, - 0.748, - 0.764, - 0.76, - 0.764, - 0.776, - 0.772, - 0.752, - 0.76, - 0.752, - 0.764, - 0.76, - 0.748, - 0.752, - 0.744, - 0.76, - 0.78, - 0.772, - 0.768, - 0.756, - 0.756, - 0.748, - 0.752, - 0.744, - 0.744, - 0.752, - 0.756, - 0.756, - 0.752, - 0.752, - 0.748, - 0.752, - 0.756, - 0.764, - 0.756, - 0.756, - 0.752, - 0.748, - 0.752, - 0.748, - 0.748, - 0.744, - 0.744, - 0.752, - 0.76, - 0.76, - 0.756, - 0.752, - 0.756, - 0.756, - 0.752, - 0.744, - 0.732, - 0.74, - 0.744, - 0.748, - 0.732, - 0.736, - 0.732, - 0.748, - 0.752, - 0.748, - 0.74, - 0.744, - 0.752, - 0.744, - 0.748, - 0.732, - 0.736, - 0.74, - 0.752, - 0.74 + 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, + 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, + 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, + 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, + 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, + 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, + 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, + 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, + 0.736, 0.74, 0.752, 0.74 ], "y": [ - 0.292, - 0.284, - 0.24, - 0.264, - 0.224, - 0.176, - 0.232, - 0.272, - 0.268, - 0.264, - 0.248, - 0.224, - 0.248, - 0.248, - 0.252, - 0.232, - 0.248, - 0.264, - 0.268, - 0.268, - 0.268, - 0.272, - 0.268, - 0.268, - 0.256, - 0.256, - 0.26, - 0.268, - 0.264, - 0.264, - 0.264, - 0.268, - 0.268, - 0.276, - 0.276, - 0.256, - 0.256, - 0.264, - 0.268, - 0.272, - 0.268, - 0.272, - 0.272, - 0.3, - 0.276, - 0.276, - 0.284, - 0.268, - 0.284, - 0.276, - 0.276, - 0.272, - 0.276, - 0.264, - 0.276, - 0.276, - 0.28, - 0.272, - 0.276, - 0.28, - 0.28, - 0.28, - 0.284, - 0.276, - 0.272, - 0.264, - 0.268, - 0.272, - 0.272, - 0.272, - 0.272, - 0.276, - 0.28, - 0.276, - 0.284, - 0.288, - 0.284, - 0.3, - 0.288, - 0.272, - 0.28, - 0.28, - 0.276, - 0.272, - 0.276, - 0.284, - 0.284, - 0.288, - 0.284, - 0.276, - 0.272, - 0.276 + 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, + 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, + 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, + 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, + 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, + 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, + 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, + 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, + 0.284, 0.276, 0.272, 0.276 ], "z": [ - -0.56, - -0.604, - -0.56, - -0.588, - -0.616, - -0.616, - -0.644, - -0.572, - -0.564, - -0.552, - -0.572, - -0.552, - -0.54, - -0.556, - -0.56, - -0.568, - -0.568, - -0.572, - -0.552, - -0.548, - -0.54, - -0.544, - -0.556, - -0.56, - -0.56, - -0.544, - -0.536, - -0.54, - -0.532, - -0.556, - -0.564, - -0.572, - -0.556, - -0.552, - -0.536, - -0.544, - -0.544, - -0.548, - -0.552, - -0.56, - -0.552, - -0.556, - -0.568, - -0.56, - -0.56, - -0.552, - -0.552, - -0.556, - -0.556, - -0.536, - -0.528, - -0.536, - -0.56, - -0.564, - -0.556, - -0.544, - -0.536, - -0.54, - -0.568, - -0.56, - -0.552, - -0.54, - -0.544, - -0.56, - -0.568, - -0.552, - -0.544, - -0.548, - -0.548, - -0.56, - -0.556, - -0.544, - -0.552, - -0.56, - -0.556, - -0.56, - -0.568, - -0.564, - -0.56, - -0.548, - -0.556, - -0.56, - -0.568, - -0.572, - -0.564, - -0.56, - -0.556, - -0.552, - -0.568, - -0.56, - -0.564, - -0.56 + -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, + -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, + -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, + -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, + -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, + -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, + -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, + -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, + -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, + -0.564, -0.56 ] } } @@ -4351,283 +624,39 @@ "ID": 1705438034019, "data": { "x": [ - -0.148, - -0.388, - -0.552, - -0.64, - -0.544, - -0.616, - -0.528, - -0.612, - -0.536, - -0.456, - -0.532, - -0.672, - -0.796, - -0.836, - -0.716, - -0.756, - -0.916, - -0.956, - -1.032, - -1.1, - -1.228, - -1.284, - -1.312, - -1.36, - -1.356, - -1.296, - -1.224, - -1.096, - -0.844, - -0.964, - -0.948, - -0.984, - -0.896, - -0.884, - -0.884, - -0.772, - -0.64, - -0.508, - -0.388, - -0.348, - -0.392, - -0.392, - -0.396, - -0.344, - -0.3, - -0.264, - -0.28, - -0.252, - -0.2, - -0.22, - -0.236, - -0.244, - -0.208, - -0.188, - -0.22, - -0.252, - -0.244, - -0.288, - -0.36, - -0.476, - -0.496, - -0.472, - -0.452, - -0.504, - -0.552, - -0.576, - -0.532, - -0.532, - -0.608, - -0.652, - -0.668, - -0.652, - -0.764, - -0.892, - -0.92, - -0.864, - -0.848, - -0.884, - -0.872, - -0.848, - -0.888, - -0.952, - -0.992, - -1.02, - -1.084, - -1.068, - -0.952, - -0.82, - -0.748, - -0.732, - -0.84 + -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, + -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, + -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, + -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, + -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, + -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, + -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, + -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, + -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, + -0.732, -0.84 ], "y": [ - 0.48, - 0.444, - 0.34, - 0.376, - 0.392, - 0.424, - 0.44, - 0.42, - 0.416, - 0.42, - 0.392, - 0.376, - 0.352, - 0.384, - 0.348, - 0.284, - 0.22, - 0.204, - 0.284, - 0.28, - 0.2, - 0.152, - 0.128, - 0.16, - 0.144, - 0.108, - 0.06, - 0.072, - 0.02, - 0.224, - 0.284, - 0.372, - 0.284, - 0.348, - 0.412, - 0.408, - 0.376, - 0.36, - 0.396, - 0.452, - 0.484, - 0.424, - 0.36, - 0.296, - 0.296, - 0.296, - 0.312, - 0.34, - 0.356, - 0.332, - 0.344, - 0.344, - 0.344, - 0.336, - 0.336, - 0.332, - 0.336, - 0.324, - 0.308, - 0.328, - 0.328, - 0.316, - 0.304, - 0.28, - 0.284, - 0.296, - 0.328, - 0.348, - 0.348, - 0.348, - 0.392, - 0.428, - 0.432, - 0.432, - 0.452, - 0.424, - 0.38, - 0.356, - 0.384, - 0.392, - 0.336, - 0.272, - 0.28, - 0.328, - 0.348, - 0.304, - 0.256, - 0.224, - 0.24, - 0.292, + 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, + 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, + 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, + 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, + 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, + 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, + 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, + 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, 0.324 ], "z": [ - -0.764, - -0.612, - -0.512, - -0.524, - -0.512, - -0.404, - -0.344, - -0.372, - -0.388, - -0.396, - -0.328, - -0.252, - -0.18, - -0.208, - -0.228, - -0.22, - -0.204, - -0.272, - -0.308, - -0.308, - -0.388, - -0.424, - -0.512, - -0.592, - -0.66, - -0.696, - -0.772, - -0.812, - -0.968, - -0.948, - -1.02, - -1.168, - -1.064, - -1.072, - -1.128, - -1.148, - -1.128, - -1.148, - -1.164, - -1.12, - -1.108, - -1.12, - -1.156, - -1.248, - -1.26, - -1.196, - -1.124, - -1.008, - -0.96, - -0.924, - -0.896, - -0.848, - -0.796, - -0.728, - -0.708, - -0.68, - -0.636, - -0.556, - -0.532, - -0.5, - -0.48, - -0.456, - -0.412, - -0.384, - -0.364, - -0.384, - -0.392, - -0.356, - -0.364, - -0.388, - -0.376, - -0.388, - -0.42, - -0.408, - -0.416, - -0.376, - -0.372, - -0.368, - -0.412, - -0.464, - -0.492, - -0.516, - -0.58, - -0.584, - -0.68, - -0.768, - -0.848, - -0.876, - -0.924, - -0.976, - -0.972 + -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, + -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, + -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, + -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, + -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, + -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, + -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, + -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, + -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, + -0.976, -0.972 ] } }, @@ -4635,285 +664,38 @@ "ID": 1705438029559, "data": { "x": [ - -0.4, - -0.504, - -0.556, - -0.656, - -0.988, - -0.568, - -0.644, - -0.664, - -0.644, - -0.536, - -0.496, - -0.776, - -0.804, - -0.888, - -0.868, - -0.904, - -0.94, - -0.912, - -0.888, - -0.816, - -0.836, - -0.872, - -0.844, - -0.848, - -0.832, - -0.792, - -0.756, - -0.74, - -0.692, - -0.684, - -0.664, - -0.636, - -0.624, - -0.592, - -0.568, - -0.532, - -0.516, - -0.412, - -0.412, - -0.364, - -0.292, - -0.2, - -0.164, - -0.1, - -0.1, - -0.072, - -0.048, - 0.004, - -0.02, - 0.068, - -0.004, - -0.072, - -0.124, - -0.188, - -0.192, - -0.172, - -0.152, - -0.172, - -0.264, - -0.292, - -0.312, - -0.288, - -0.276, - -0.348, - -0.48, - -0.612, - -0.552, - -0.576, - -0.676, - -0.804, - -0.876, - -0.864, - -0.856, - -0.844, - -0.836, - -0.836, - -0.864, - -0.92, - -0.924, - -0.856, - -0.792, - -0.744, - -0.672, - -0.664, - -0.644, - -0.66, - -0.68, - -0.724, - -0.716, - -0.668, - -0.588, - -0.508 + -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, + -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, + -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, + -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, + -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, + -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, + -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, + -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, + -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, + -0.668, -0.588, -0.508 ], "y": [ - 0.228, - 0.248, - 0.252, - 0.252, - 0.192, - 0.132, - 0.2, - 0.28, - 0.364, - 0.276, - 0.132, - 0.216, - 0.24, - 0.296, - 0.26, - 0.228, - 0.26, - 0.268, - 0.304, - 0.232, - 0.232, - 0.256, - 0.24, - 0.232, - 0.276, - 0.268, - 0.268, - 0.304, - 0.292, - 0.296, - 0.296, - 0.292, - 0.304, - 0.312, - 0.3, - 0.304, - 0.328, - 0.308, - 0.32, - 0.348, - 0.368, - 0.352, - 0.356, - 0.328, - 0.304, - 0.308, - 0.32, - 0.3, - 0.324, - 0.24, - 0.184, - 0.26, - 0.276, - 0.28, - 0.216, - 0.224, - 0.24, - 0.248, - 0.232, - 0.196, - 0.184, - 0.22, - 0.232, - 0.196, - 0.212, - 0.188, - 0.18, - 0.176, - 0.152, - 0.144, - 0.14, - 0.176, - 0.184, - 0.18, - 0.184, - 0.18, - 0.184, - 0.216, - 0.252, - 0.188, - 0.2, - 0.18, - 0.208, - 0.244, - 0.252, - 0.248, - 0.244, - 0.236, - 0.244, - 0.276, - 0.248, - 0.236 + 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, + 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, + 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, + 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, + 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, + 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, + 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, + 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, + 0.276, 0.248, 0.236 ], "z": [ - -1.18, - -1.18, - -1.12, - -1.32, - -0.928, - -1.048, - -1.18, - -1.024, - -1.224, - -1.064, - -1.008, - -1.02, - -0.888, - -0.88, - -0.812, - -0.78, - -0.736, - -0.696, - -0.672, - -0.712, - -0.784, - -0.516, - -0.64, - -0.54, - -0.524, - -0.536, - -0.524, - -0.484, - -0.468, - -0.516, - -0.54, - -0.596, - -0.624, - -0.6, - -0.596, - -0.6, - -0.624, - -0.632, - -0.656, - -0.632, - -0.7, - -0.744, - -0.76, - -0.768, - -0.788, - -0.816, - -0.904, - -0.908, - -1.012, - -0.996, - -0.78, - -1.112, - -1.06, - -1, - -1.06, - -1.1, - -1.136, - -1.184, - -1.216, - -1.264, - -1.24, - -1.228, - -1.228, - -1.172, - -1.16, - -1.068, - -1.12, - -1.112, - -1.068, - -1.064, - -0.996, - -1.008, - -0.976, - -0.924, - -0.88, - -0.864, - -0.824, - -0.796, - -0.808, - -0.812, - -0.74, - -0.76, - -0.72, - -0.744, - -0.728, - -0.704, - -0.692, - -0.636, - -0.644, - -0.656, - -0.676, + -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, + -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, + -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, + -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, + -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, + -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, + -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, + -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, + -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, -0.616 ] } @@ -4922,289 +704,39 @@ "ID": 1705438027130, "data": { "x": [ - -0.064, - -0.104, - -0.14, - -0.208, - -0.3, - -0.316, - -0.428, - -0.564, - -0.456, - -0.532, - -0.676, - -0.74, - -0.728, - -0.692, - -0.792, - -1.024, - -1.076, - -1.056, - -0.968, - -0.9, - -0.912, - -0.86, - -0.876, - -0.872, - -0.848, - -0.848, - -0.8, - -0.788, - -0.748, - -0.728, - -0.636, - -0.56, - -0.532, - -0.504, - -0.492, - -0.432, - -0.424, - -0.464, - -0.428, - -0.408, - -0.336, - -0.296, - -0.252, - -0.24, - -0.248, - -0.28, - -0.22, - -0.204, - -0.128, - 0, - -0.168, - -0.136, - -0.064, - -0.088, - -0.148, - -0.228, - -0.26, - -0.296, - -0.352, - -0.392, - -0.376, - -0.352, - -0.364, - -0.436, - -0.46, - -0.496, - -0.52, - -0.596, - -0.628, - -0.624, - -0.628, - -0.668, - -0.792, - -0.808, - -0.812, - -0.824, - -0.84, - -0.864, - -0.888, - -0.924, - -0.936, - -0.88, - -0.848, - -0.888, - -0.88, - -0.832, - -0.768, - -0.724, - -0.68, - -0.664, - -0.664, - -0.62, - -0.572 + -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, + -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, + -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, + -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, + -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, + -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, + -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, + -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, + -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, + -0.664, -0.62, -0.572 ], "y": [ - 0.228, - 0.212, - 0.224, - 0.236, - 0.192, - 0.204, - 0.208, - 0.34, - 0.208, - 0.184, - 0.164, - 0.148, - 0.144, - 0.104, - 0.064, - 0.132, - 0.104, - 0.124, - 0.14, - 0.188, - 0.208, - 0.212, - 0.24, - 0.224, - 0.212, - 0.24, - 0.256, - 0.288, - 0.296, - 0.308, - 0.324, - 0.316, - 0.32, - 0.316, - 0.348, - 0.348, - 0.312, - 0.308, - 0.3, - 0.3, - 0.276, - 0.248, - 0.232, - 0.252, - 0.248, - 0.152, - 0.172, - 0.204, - 0.18, - -0.08, - 0.176, - 0.156, - 0.136, - 0.16, - 0.18, - 0.208, - 0.224, - 0.204, - 0.18, - 0.188, - 0.2, - 0.188, - 0.184, - 0.168, - 0.16, - 0.14, - 0.108, - 0.104, - 0.112, - 0.084, - 0.052, - 0.004, - 0.056, - 0.056, - 0.096, - 0.084, - 0.092, - 0.12, - 0.1, - 0.14, - 0.152, - 0.164, - 0.156, - 0.184, - 0.212, - 0.224, - 0.228, - 0.228, - 0.236, - 0.268, - 0.276, - 0.284, - 0.296 + 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, + 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, + 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, + 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, + 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, + 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, + 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, + 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, + 0.268, 0.276, 0.284, 0.296 ], "z": [ - -0.908, - -0.98, - -0.956, - -1.076, - -1.096, - -1.176, - -1.12, - -1.06, - -1.124, - -1.176, - -1.12, - -1.112, - -1.064, - -1.06, - -1.024, - -0.984, - -1.012, - -0.964, - -0.904, - -0.824, - -0.816, - -0.748, - -0.7, - -0.676, - -0.64, - -0.628, - -0.568, - -0.56, - -0.528, - -0.468, - -0.492, - -0.488, - -0.48, - -0.492, - -0.472, - -0.532, - -0.544, - -0.6, - -0.644, - -0.656, - -0.704, - -0.732, - -0.764, - -0.776, - -0.82, - -0.68, - -0.912, - -0.988, - -0.868, - -1.148, - -1.056, - -1.152, - -1.072, - -1.12, - -1.088, - -1.092, - -1.092, - -1.152, - -1.208, - -1.208, - -1.18, - -1.172, - -1.2, - -1.192, - -1.184, - -1.18, - -1.104, - -1.076, - -1.072, - -1.028, - -1.028, - -0.984, - -0.944, - -0.968, - -0.96, - -0.964, - -0.964, - -0.952, - -0.904, - -0.856, - -0.808, - -0.788, - -0.756, - -0.696, - -0.708, - -0.652, - -0.672, - -0.64, - -0.62, - -0.584, - -0.604, - -0.592, - -0.588 + -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, + -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, + -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, + -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, + -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, + -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, + -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, + -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, + -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, + -0.604, -0.592, -0.588 ] } }, @@ -5212,289 +744,39 @@ "ID": 1705437979405, "data": { "x": [ - 0.24, - 0.22, - 0.104, - -0.036, - -0.1, - -0.096, - -0.152, - -0.22, - -0.256, - -0.204, - -0.256, - -0.056, - -0.028, - -0.076, - -0.108, - -0.088, - -0.068, - -0.136, - -0.28, - -0.392, - -0.468, - -0.508, - -0.52, - -0.516, - -0.476, - -0.46, - -0.464, - -0.516, - -0.616, - -0.648, - -0.6, - -0.52, - -0.48, - -0.468, - -0.468, - -0.46, - -0.432, - -0.404, - -0.392, - -0.304, - -0.256, - -0.208, - -0.204, - -0.292, - -0.388, - -0.488, - -0.544, - -0.656, - -0.736, - -0.792, - -0.776, - -0.716, - -0.668, - -0.572, - -0.504, - -0.484, - -0.456, - -0.404, - -0.38, - -0.356, - -0.34, - -0.328, - -0.324, - -0.276, - -0.2, - -0.148, - -0.128, - -0.136, - -0.104, - 0.016, - 0.152, - 0.268, - 0.244, - 0.168, - 0.128, - 0.096, - 0.096, - 0.092, - 0.04, - 0, - -0.028, - -0.004, - 0.128, - 0.184, - 0.276, - 0.348, - 0.364, - 0.312, - 0.188, - 0.036, - -0.06, - -0.156, - -0.248 + 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, + -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, + -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, + -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, + -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, + -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, + -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, + 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, + -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, + -0.156, -0.248 ], "y": [ - 0.488, - 0.44, - 0.364, - 0.304, - 0.268, - 0.272, - 0.416, - 0.436, - 0.408, - 0.364, - 0.364, - 0.216, - 0.224, - 0.264, - 0.26, - 0.248, - 0.288, - 0.344, - 0.388, - 0.392, - 0.392, - 0.364, - 0.344, - 0.332, - 0.308, - 0.304, - 0.316, - 0.336, - 0.34, - 0.336, - 0.312, - 0.296, - 0.312, - 0.336, - 0.336, - 0.34, - 0.34, - 0.364, - 0.372, - 0.38, - 0.376, - 0.316, - 0.292, - 0.308, - 0.296, - 0.324, - 0.44, - 0.54, - 0.584, - 0.624, - 0.708, - 0.728, - 0.74, - 0.748, - 0.716, - 0.68, - 0.624, - 0.592, - 0.564, - 0.536, - 0.524, - 0.504, - 0.48, - 0.464, - 0.444, - 0.428, - 0.416, - 0.408, - 0.42, - 0.408, - 0.412, - 0.38, - 0.388, - 0.408, - 0.436, - 0.448, - 0.436, - 0.448, - 0.44, - 0.464, - 0.452, - 0.456, - 0.312, - 0.424, - 0.376, - 0.388, - 0.36, - 0.336, - 0.348, - 0.368, - 0.412, - 0.484, - 0.5 + 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, + 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, + 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, + 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, + 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, + 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, + 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, + 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, + 0.348, 0.368, 0.412, 0.484, 0.5 ], "z": [ - -0.996, - -0.96, - -0.964, - -0.896, - -0.82, - -0.696, - -0.552, - -0.52, - -0.568, - -0.652, - -0.652, - -1.104, - -1.196, - -1.4, - -1.516, - -1.58, - -1.54, - -1.456, - -1.364, - -1.312, - -1.248, - -1.208, - -1.172, - -1.112, - -1.072, - -1.032, - -1.004, - -0.996, - -1.036, - -1.056, - -1.032, - -0.996, - -0.932, - -0.868, - -0.864, - -0.824, - -0.808, - -0.764, - -0.708, - -0.652, - -0.624, - -0.632, - -0.644, - -0.628, - -0.612, - -0.616, - -0.692, - -0.788, - -0.884, - -0.932, - -0.956, - -0.936, - -0.852, - -0.824, - -0.812, - -0.788, - -0.784, - -0.74, - -0.732, - -0.7, - -0.668, - -0.64, - -0.6, - -0.56, - -0.552, - -0.56, - -0.56, - -0.556, - -0.528, - -0.56, - -0.616, - -0.7, - -0.78, - -0.844, - -0.88, - -0.884, - -0.92, - -0.96, - -1, - -1.004, - -0.956, - -1.008, - -1.024, - -1.092, - -1.076, - -1.092, - -1.132, - -1.152, - -1.148, - -1.132, - -1.088, - -1.084, - -1.06 + -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, + -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, + -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, + -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, + -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, + -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, + -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, + -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, + -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, + -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 ] } }, @@ -5502,283 +784,36 @@ "ID": 1705437977128, "data": { "x": [ - -0.084, - -0.124, - -0.188, - -0.112, - -0.264, - -0.244, - -0.092, - 0.12, - 0.228, - 0.16, - 0.316, - 0.296, - 0.224, - -0.084, - -0.28, - -0.372, - -0.372, - -0.352, - -0.42, - -0.596, - -0.792, - -0.7, - -0.612, - -0.696, - -0.82, - -0.82, - -0.824, - -0.8, - -0.868, - -0.968, - -0.94, - -0.852, - -0.756, - -0.684, - -0.632, - -0.516, - -0.456, - -0.396, - -0.436, - -0.476, - -0.416, - -0.364, - -0.296, - -0.292, - -0.304, - -0.332, - -0.324, - -0.272, - -0.208, - -0.156, - -0.112, - -0.08, - -0.008, - 0.016, - 0.04, - 0.092, - 0.14, - 0.16, - 0.136, - 0.144, - 0.168, - 0.176, - 0.152, - 0.236, - 0.304, - 0.248, - 0.22, - 0.12, - 0.044, - 0.04, - 0.128, - 0.156, - 0.108, - -0.044, - -0.188, - -0.248, - -0.256, - -0.408, - -0.516, - -0.484, - -0.404, - -0.396, - -0.504, - -0.592, - -0.636, - -0.64, - -0.636, - -0.596, - -0.58, - -0.52, - -0.428 + -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, + 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, + -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, + -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, + -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, + -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, + 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, + 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, + -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 ], "y": [ - 0.3, - 0.304, - 0.392, - 0.324, - 0.236, - 0.268, - 0.268, - 0.36, - 0.256, - 0.332, - 0.316, - 0.5, - 0.288, - 0.336, - 0.392, - 0.448, - 0.452, - 0.46, - 0.46, - 0.5, - 0.52, - 0.44, - 0.44, - 0.464, - 0.488, - 0.464, - 0.456, - 0.484, - 0.536, - 0.584, - 0.604, - 0.584, - 0.62, - 0.608, - 0.58, - 0.56, - 0.564, - 0.576, - 0.592, - 0.58, - 0.532, - 0.492, - 0.444, - 0.444, - 0.452, - 0.476, - 0.456, - 0.44, - 0.4, - 0.376, - 0.352, - 0.356, - 0.324, - 0.316, - 0.296, - 0.332, - 0.348, - 0.38, - 0.392, - 0.436, - 0.452, - 0.384, - 0.34, - 0.3, - 0.236, - 0.208, - 0.22, - 0.232, - 0.212, - 0.244, - 0.28, - 0.308, - 0.32, - 0.312, - 0.332, - 0.352, - 0.34, - 0.372, - 0.392, - 0.392, - 0.352, - 0.34, - 0.332, - 0.36, - 0.436, - 0.372, - 0.396, - 0.412, - 0.464, - 0.416, - 0.428 + 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, + 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, + 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, + 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, + 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, + 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, + 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, + 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 ], "z": [ - -0.652, - -0.644, - -0.74, - -0.748, - -0.732, - -0.756, - -0.924, - -0.8, - -0.828, - -0.816, - -0.872, - -0.832, - -1.012, - -0.992, - -1.04, - -1.124, - -1.144, - -1.18, - -1.196, - -1.236, - -1.2, - -1.244, - -1.228, - -1.112, - -1.104, - -1.084, - -1.04, - -0.964, - -0.888, - -0.856, - -0.772, - -0.696, - -0.62, - -0.564, - -0.6, - -0.592, - -0.536, - -0.48, - -0.432, - -0.448, - -0.48, - -0.484, - -0.516, - -0.464, - -0.456, - -0.452, - -0.46, - -0.46, - -0.504, - -0.544, - -0.62, - -0.668, - -0.76, - -0.8, - -0.808, - -0.844, - -0.832, - -0.804, - -0.788, - -0.816, - -0.84, - -0.876, - -1, - -1.048, - -1.356, - -1.068, - -1.184, - -1.252, - -1.392, - -1.36, - -1.32, - -1.256, - -1.204, - -1.208, - -1.248, - -1.24, - -1.22, - -1.212, - -1.184, - -1.152, - -1.108, - -1.084, - -1.08, - -1.092, - -1.184, - -1.136, - -1.084, - -1.044, - -1, - -1, - -1.012 + -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, + -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, + -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, + -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, + -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, + -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, + -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, + -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, + -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 ] } }, @@ -5786,289 +821,39 @@ "ID": 1705437922352, "data": { "x": [ - -0.244, - -0.216, - -0.076, - -0.112, - -0.196, - -0.184, - -0.136, - -0.044, - 0.064, - 0.064, - -0.088, - -0.244, - -0.416, - -0.452, - -0.48, - -0.516, - -0.568, - -0.688, - -0.728, - -0.772, - -0.784, - -0.768, - -0.732, - -0.696, - -0.648, - -0.576, - -0.544, - -0.52, - -0.504, - -0.492, - -0.492, - -0.484, - -0.436, - -0.388, - -0.376, - -0.352, - -0.32, - -0.268, - -0.252, - -0.244, - -0.248, - -0.22, - -0.208, - -0.212, - -0.212, - -0.18, - -0.208, - -0.252, - -0.32, - -0.356, - -0.332, - -0.252, - -0.228, - -0.236, - -0.212, - -0.148, - -0.116, - -0.112, - -0.088, - -0.056, - -0.056, - -0.04, - 0.032, - 0.076, - 0.004, - -0.148, - -0.188, - -0.176, - -0.176, - -0.264, - -0.412, - -0.476, - -0.42, - -0.344, - -0.32, - -0.312, - -0.376, - -0.46, - -0.436, - -0.348, - -0.26, - -0.268, - -0.376, - -0.484, - -0.508, - -0.5, - -0.512, - -0.492, - -0.488, - -0.456, - -0.436, - -0.448, - -0.46 + -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, + -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, + -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, + -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, + -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, + -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, + -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, + -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, + -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, + -0.456, -0.436, -0.448, -0.46 ], "y": [ - 0.664, - 0.58, - 0.64, - 0.796, - 0.9, - 0.844, - 0.764, - 0.792, - 0.788, - 0.78, - 0.884, - 1.036, - 1.16, - 1.228, - 1.248, - 1.252, - 1.264, - 1.292, - 1.26, - 1.264, - 1.236, - 1.188, - 1.176, - 1.136, - 1.124, - 1.06, - 1.004, - 0.964, - 0.964, - 1.008, - 1.036, - 1.004, - 1.004, - 1, - 0.992, - 0.98, - 0.94, - 0.92, - 0.896, - 0.884, - 0.884, - 0.852, - 0.816, - 0.792, - 0.792, - 0.764, - 0.76, - 0.728, - 0.712, - 0.724, - 0.728, - 0.728, - 0.72, - 0.7, - 0.708, - 0.728, - 0.708, - 0.696, - 0.732, - 0.752, - 0.76, - 0.752, - 0.756, - 0.684, - 0.668, - 0.752, - 0.9, - 0.96, - 0.932, - 0.892, - 0.94, - 1, - 1.028, - 1.04, - 1.04, - 1.044, - 1.052, - 1.06, - 1.02, - 0.98, - 0.932, - 0.916, - 0.928, - 0.96, - 0.996, - 0.98, - 1.024, - 1.028, - 1, - 0.968, - 0.924, - 0.928, + 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, + 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, + 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, + 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, + 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, + 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, + 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, + 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, 0.9 ], "z": [ - -0.524, - -0.62, - -0.732, - -0.652, - -0.576, - -0.608, - -0.648, - -0.796, - -0.868, - -0.848, - -0.744, - -0.648, - -0.604, - -0.576, - -0.568, - -0.528, - -0.504, - -0.444, - -0.416, - -0.38, - -0.36, - -0.316, - -0.26, - -0.2, - -0.152, - -0.136, - -0.116, - -0.116, - -0.108, - -0.044, - -0.004, - 0.036, - 0.072, - 0.072, - 0.064, - 0.072, - 0.068, - 0.076, - 0.08, - 0.088, - 0.104, - 0.096, - 0.092, - 0.068, - -0.008, - -0.084, - -0.092, - -0.144, - -0.184, - -0.224, - -0.272, - -0.324, - -0.332, - -0.352, - -0.404, - -0.436, - -0.452, - -0.484, - -0.492, - -0.532, - -0.56, - -0.612, - -0.688, - -0.74, - -0.744, - -0.688, - -0.624, - -0.608, - -0.628, - -0.656, - -0.672, - -0.524, - -0.46, - -0.468, - -0.532, - -0.596, - -0.604, - -0.6, - -0.644, - -0.66, - -0.748, - -0.752, - -0.76, - -0.676, - -0.608, - -0.508, - -0.392, - -0.34, - -0.296, - -0.304, - -0.344, - -0.372, - -0.38 + -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, + -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, + -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, + -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, + 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, + -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, + -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, + -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, + -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, + -0.372, -0.38 ] } }, @@ -6076,289 +861,38 @@ "ID": 1705437913803, "data": { "x": [ - 0.864, - 0.94, - 1.108, - 1.224, - 1.232, - 1.22, - 1.212, - 1.228, - 1.012, - 0.944, - 0.86, - 0.804, - 0.788, - 0.76, - 0.732, - 0.68, - 0.64, - 0.676, - 0.496, - 0.356, - 0.204, - 0.044, - -0.02, - -0.008, - -0.028, - -0.072, - -0.184, - -0.324, - -0.488, - -0.536, - -0.488, - -0.404, - -0.4, - -0.448, - -0.316, - -0.34, - -0.516, - -0.652, - -0.548, - -0.376, - -0.444, - -0.496, - -0.496, - -0.428, - -0.344, - -0.724, - -0.196, - 0.044, - 0.112, - 0.248, - 0.328, - 0.392, - 0.372, - 0.34, - 0.296, - 0.22, - 0.192, - 0.212, - 0.228, - 0.32, - 0.332, - 0.38, - 0.416, - 0.456, - 0.5, - 0.516, - 0.508, - 0.496, - 0.46, - 0.448, - 0.444, - 0.424, - 0.34, - 0.324, - 0.344, - 0.252, - 0.228, - 0.18, - 0.088, - 0.016, - 0.012, - 0.016, - 0.004, - -0.024, - -0.108, - -0.16, - -0.212, - -0.316, - -0.412, - -0.42, - -0.38, - -0.296, - -0.244 + 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, + 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, + -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, + -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, + -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, + 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, + 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, + 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, + -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 ], "y": [ - 0.056, - 0.036, - 0.02, - -0.044, - -0.104, - -0.136, - -0.152, - 0.064, - -0.228, - -0.192, - -0.224, - -0.2, - -0.216, - -0.2, - -0.188, - -0.184, - -0.164, - -0.08, - -0.044, - -0.004, - 0.068, - 0.176, - 0.236, - 0.24, - 0.232, - 0.244, - 0.308, - 0.36, - 0.396, - 0.424, - 0.4, - 0.368, - 0.348, - 0.388, - 0.36, - 0.328, - 0.3, - 0.332, - 0.344, - 0.244, - 0.068, - -0.096, - -0.092, - -0.06, - -0.02, - 0.452, - -0.112, - -0.112, - -0.032, - 0.056, - 0.152, - 0.096, - 0.06, - 0.072, - 0.072, - 0.18, - 0.236, - 0.212, - 0.136, - 0.1, - 0.092, - 0.108, - 0.108, - 0.076, - 0.04, - 0.028, - 0.052, - 0.096, - 0.124, - 0.088, - 0.064, - 0.056, - 0.148, - 0.26, - 0.304, - 0.272, - 0.26, - 0.308, - 0.34, - 0.416, - 0.452, - 0.404, - 0.428, - 0.456, - 0.52, - 0.536, - 0.528, - 0.552, - 0.604, - 0.648, - 0.696, - 0.64, - 0.596 + 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, + -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, + 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, + 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, + -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, + 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, + 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, + 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, + 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 ], "z": [ - -1.236, - -1.128, - -0.992, - -0.916, - -0.844, - -0.796, - -0.724, - -0.672, - -0.576, - -0.496, - -0.552, - -0.448, - -0.472, - -0.452, - -0.38, - -0.312, - -0.28, - -0.18, - -0.248, - -0.324, - -0.428, - -0.468, - -0.464, - -0.432, - -0.48, - -0.524, - -0.58, - -0.66, - -0.78, - -0.808, - -0.784, - -0.732, - -0.76, - -0.744, - -0.744, - -0.8, - -0.904, - -1.032, - -1.076, - -1.14, - -1.3, - -1.504, - -1.62, - -1.652, - -1.604, - -1.556, - -1.52, - -1.432, - -1.252, - -1.08, - -1.064, - -1.1, - -1.1, - -1.048, - -1.044, - -1.064, - -1.132, - -1.184, - -1.24, - -1.332, - -1.292, - -1.268, - -1.232, - -1.224, - -1.18, - -1.14, - -1.088, - -1.016, - -0.892, - -0.832, - -0.812, - -0.836, - -0.82, - -0.792, - -0.748, - -0.708, - -0.764, - -0.736, - -0.712, - -0.68, - -0.668, - -0.68, - -0.66, - -0.672, - -0.656, - -0.636, - -0.64, - -0.692, - -0.724, - -0.752, - -0.768, - -0.772, - -0.792 + -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, + -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, + -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, + -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, + -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, + -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, + -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, + -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, + -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, + -0.752, -0.768, -0.772, -0.792 ] } }, @@ -6366,289 +900,39 @@ "ID": 1705437908508, "data": { "x": [ - -0.432, - -0.432, - -0.404, - -0.48, - -0.588, - -0.9, - -0.752, - -0.852, - -1.02, - -1.108, - -1.044, - -0.944, - -0.888, - -0.876, - -0.852, - -0.756, - -0.828, - -0.868, - -0.908, - -0.928, - -0.84, - -0.756, - -0.66, - -0.576, - -0.512, - -0.484, - -0.424, - -0.42, - -0.436, - -0.408, - -0.352, - -0.272, - -0.216, - -0.208, - -0.236, - -0.188, - -0.148, - -0.104, - -0.084, - -0.08, - -0.076, - -0.064, - -0.08, - -0.108, - -0.132, - -0.188, - -0.232, - -0.212, - -0.212, - -0.288, - -0.384, - -0.392, - -0.372, - -0.348, - -0.388, - -0.528, - -0.732, - -0.76, - -0.684, - -0.732, - -0.9, - -1.032, - -1.092, - -1.004, - -0.98, - -1, - -0.972, - -0.992, - -1, - -1.06, - -1.084, - -1.052, - -1.012, - -0.996, - -0.904, - -0.796, - -0.692, - -0.564, - -0.404, - -0.388, - -0.312, - -0.32, - -0.324, - -0.324, - -0.364, - -0.428, - -0.484, - -0.508, - -0.496, - -0.508, - -0.508, - -0.508, - -0.496 + -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, + -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, + -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, + -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, + -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, + -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, + -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, + -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, + -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, + -0.508, -0.508, -0.508, -0.496 ], "y": [ - 0.68, - 0.644, - 0.576, - 0.52, - 0.496, - 0.492, - 0.548, - 0.68, - 0.704, - 0.696, - 0.712, - 0.772, - 0.804, - 0.82, - 0.812, - 0.84, - 0.888, - 0.948, - 0.94, - 0.96, - 0.96, - 0.972, - 0.96, - 0.924, - 0.916, - 0.888, - 0.9, - 0.904, - 0.88, - 0.86, - 0.876, - 0.832, - 0.824, - 0.8, - 0.74, - 0.716, - 0.692, - 0.672, - 0.628, - 0.572, - 0.604, - 0.608, - 0.608, - 0.592, - 0.572, - 0.564, - 0.54, - 0.528, - 0.54, - 0.544, - 0.512, - 0.512, - 0.524, - 0.536, - 0.524, - 0.536, - 0.468, - 0.472, - 0.492, - 0.52, - 0.564, - 0.596, - 0.692, - 0.748, - 0.764, - 0.748, - 0.812, - 0.876, - 0.872, - 0.816, - 0.8, - 0.86, - 0.904, - 0.892, - 0.892, - 0.912, - 0.932, - 0.936, - 0.824, - 1, - 0.844, - 0.86, - 0.832, - 0.808, - 0.788, - 0.776, - 0.78, - 0.76, - 0.756, - 0.776, - 0.772, - 0.772, - 0.776 + 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, + 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, + 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, + 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, + 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, + 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, + 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, + 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, + 0.772, 0.776 ], "z": [ - -0.252, - -0.272, - -0.324, - -0.252, - -0.216, - -0.104, - -0.072, - -0.012, - -0.036, - -0.128, - -0.248, - -0.308, - -0.364, - -0.424, - -0.408, - -0.572, - -0.464, - -0.508, - -0.468, - -0.504, - -0.452, - -0.464, - -0.496, - -0.58, - -0.664, - -0.72, - -0.704, - -0.744, - -0.784, - -0.856, - -0.884, - -0.94, - -0.856, - -0.84, - -0.86, - -0.864, - -0.832, - -0.788, - -0.744, - -0.744, - -0.716, - -0.648, - -0.556, - -0.516, - -0.468, - -0.38, - -0.312, - -0.272, - -0.228, - -0.184, - -0.16, - -0.144, - -0.108, - -0.048, - -0.044, - -0.004, - 0.02, - -0.012, - 0.044, - 0.1, - 0.144, - 0.152, - 0.156, - 0.128, - 0.104, - 0.116, - 0.172, - 0.148, - 0.088, - 0.032, - -0.032, - -0.092, - -0.188, - -0.34, - -0.452, - -0.548, - -0.592, - -0.684, - -0.652, - -0.912, - -0.672, - -0.604, - -0.6, - -0.584, - -0.564, - -0.504, - -0.504, - -0.496, - -0.484, - -0.472, - -0.46, - -0.456, - -0.448 + -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, + -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, + -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, + -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, + -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, + -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, + 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, + 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, + -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, + -0.472, -0.46, -0.456, -0.448 ] } }, @@ -6656,289 +940,39 @@ "ID": 1705437905957, "data": { "x": [ - -0.436, - -0.42, - -0.432, - -0.388, - -0.36, - -0.22, - -0.12, - -0.156, - -0.336, - -0.496, - -0.56, - -0.6, - -0.684, - -0.856, - -1.036, - -1.14, - -1.048, - -0.908, - -0.884, - -0.936, - -1.016, - -1.056, - -1.06, - -1.024, - -1.028, - -1.04, - -0.964, - -0.872, - -0.824, - -0.796, - -0.832, - -0.868, - -0.804, - -0.716, - -0.64, - -0.608, - -0.584, - -0.572, - -0.544, - -0.512, - -0.516, - -0.5, - -0.476, - -0.464, - -0.448, - -0.444, - -0.444, - -0.364, - -0.28, - -0.212, - -0.228, - -0.312, - -0.392, - -0.4, - -0.376, - -0.332, - -0.256, - -0.232, - -0.244, - -0.244, - -0.26, - -0.396, - -0.552, - -0.68, - -0.836, - -0.888, - -0.852, - -0.928, - -0.972, - -1.004, - -0.976, - -0.928, - -0.92, - -0.94, - -0.952, - -0.904, - -0.848, - -0.828, - -0.856, - -0.844, - -0.804, - -0.78, - -0.764, - -0.764, - -0.732, - -0.712, - -0.712, - -0.692, - -0.688, - -0.692, - -0.64, - -0.6, - -0.588 + -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, + -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, + -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, + -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, + -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, + -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, + -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, + -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, + -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, + -0.64, -0.6, -0.588 ], "y": [ - 0.54, - 0.504, - 0.48, - 0.512, - 0.524, - 0.6, - 0.752, - 0.936, - 0.916, - 0.936, - 0.92, - 0.992, - 0.992, - 1.028, - 1.056, - 1.072, - 1.064, - 1.016, - 0.976, - 0.908, - 0.856, - 0.848, - 0.812, - 0.796, - 0.784, - 0.792, - 0.78, - 0.752, - 0.744, - 0.732, - 0.724, - 0.708, - 0.7, - 0.692, - 0.672, - 0.672, - 0.68, - 0.652, - 0.644, - 0.64, - 0.628, - 0.6, - 0.56, - 0.552, - 0.516, - 0.496, - 0.492, - 0.472, - 0.508, - 0.524, - 0.556, - 0.592, - 0.632, - 0.664, - 0.66, - 0.712, - 0.768, - 0.808, - 0.812, - 0.848, - 0.876, - 0.908, - 0.932, - 0.984, - 1.012, - 1.02, - 1.004, - 0.968, - 0.916, - 0.86, - 0.868, - 0.816, - 0.784, - 0.74, - 0.72, - 0.692, - 0.684, - 0.676, - 0.7, - 0.712, - 0.732, - 0.74, - 0.764, - 0.788, - 0.776, - 0.756, - 0.776, - 0.764, - 0.752, - 0.732, - 0.716, - 0.724, - 0.712 + 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, + 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, + 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, + 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, + 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, + 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, + 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, + 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, + 0.724, 0.712 ], "z": [ - -0.288, - -0.312, - -0.356, - -0.36, - -0.44, - -0.7, - -0.964, - -0.852, - -0.704, - -0.612, - -0.592, - -0.588, - -0.524, - -0.468, - -0.368, - -0.364, - -0.364, - -0.356, - -0.328, - -0.288, - -0.248, - -0.256, - -0.296, - -0.292, - -0.232, - -0.204, - -0.176, - -0.148, - -0.132, - -0.072, - -0.004, - 0.08, - 0.132, - 0.164, - 0.188, - 0.216, - 0.204, - 0.164, - 0.088, - 0.032, - 0.044, - 0.04, - 0.068, - 0.06, - 0.016, - -0.048, - -0.124, - -0.208, - -0.296, - -0.404, - -0.428, - -0.4, - -0.38, - -0.408, - -0.512, - -0.624, - -0.7, - -0.712, - -0.76, - -0.8, - -0.872, - -0.88, - -0.824, - -0.684, - -0.6, - -0.528, - -0.468, - -0.384, - -0.292, - -0.272, - -0.28, - -0.3, - -0.296, - -0.276, - -0.3, - -0.32, - -0.34, - -0.372, - -0.388, - -0.376, - -0.396, - -0.412, - -0.404, - -0.376, - -0.38, - -0.368, - -0.364, - -0.396, - -0.4, - -0.38, - -0.356, - -0.32, - -0.328 + -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, + -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, + -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, + -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, + 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, + -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, + -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, + -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, + -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, + -0.38, -0.356, -0.32, -0.328 ] } }, @@ -6946,289 +980,39 @@ "ID": 1705437896254, "data": { "x": [ - -0.632, - -0.608, - -0.592, - -0.584, - -0.644, - -0.868, - -0.492, - -0.528, - -0.712, - -0.784, - -0.708, - -0.588, - -0.632, - -0.752, - -0.876, - -0.852, - -0.808, - -0.84, - -0.944, - -0.936, - -0.9, - -0.936, - -1.02, - -1.104, - -1.084, - -1.076, - -1.048, - -1.048, - -1.112, - -1.144, - -1.188, - -1.208, - -1.228, - -1.288, - -1.296, - -1.264, - -1.2, - -1.18, - -1.132, - -1.02, - -0.956, - -0.912, - -0.872, - -0.848, - -0.824, - -0.876, - -0.892, - -0.844, - -0.808, - -0.732, - -0.716, - -0.78, - -0.852, - -0.76, - -0.62, - -0.588, - -0.636, - -0.632, - -0.564, - -0.52, - -0.508, - -0.52, - -0.484, - -0.412, - -0.432, - -0.428, - -0.444, - -0.496, - -0.48, - -0.452, - -0.46, - -0.508, - -0.516, - -0.512, - -0.448, - -0.4, - -0.404, - -0.46, - -0.532, - -0.568, - -0.484, - -0.38, - -0.444, - -0.572, - -0.728, - -0.728, - -0.616, - -0.644, - -0.656, - -0.704, - -0.688, - -0.644, - -0.652 + -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, + -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, + -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, + -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, + -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, + -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, + -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, + -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, + -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, + -0.704, -0.688, -0.644, -0.652 ], "y": [ - 0.364, - 0.344, - 0.352, - 0.38, - 0.396, - 0.604, - 0.4, - 0.428, - 0.468, - 0.48, - 0.472, - 0.436, - 0.42, - 0.408, - 0.42, - 0.424, - 0.38, - 0.304, - 0.26, - 0.244, - 0.244, - 0.232, - 0.184, - 0.176, - 0.112, - 0.128, - 0.14, - 0.12, - 0.092, - 0.072, - 0.056, - 0.076, - 0.104, - 0.124, - 0.14, - 0.164, - 0.2, - 0.216, - 0.224, - 0.248, - 0.28, - 0.308, - 0.324, - 0.348, - 0.38, - 0.404, - 0.408, - 0.408, - 0.384, - 0.372, - 0.356, - 0.372, - 0.3, - 0.284, - 0.352, - 0.372, - 0.38, - 0.364, - 0.412, - 0.32, - 0.36, - 0.32, - 0.308, - 0.276, - 0.332, - 0.34, - 0.324, - 0.344, - 0.372, - 0.368, - 0.368, - 0.408, - 0.464, - 0.476, - 0.44, - 0.424, - 0.468, - 0.484, - 0.496, - 0.468, - 0.46, - 0.484, - 0.5, - 0.504, - 0.464, - 0.448, - 0.432, - 0.396, - 0.352, - 0.34, - 0.32, - 0.32, - 0.316 + 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, + 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, + 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, + 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, + 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, + 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, + 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, + 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, + 0.32, 0.316 ], "z": [ - -0.524, - -0.552, - -0.576, - -0.58, - -0.58, - -0.724, - -0.484, - -0.496, - -0.556, - -0.524, - -0.552, - -0.58, - -0.648, - -0.732, - -0.784, - -0.596, - -0.644, - -0.72, - -0.756, - -0.744, - -0.724, - -0.7, - -0.684, - -0.716, - -0.716, - -0.68, - -0.668, - -0.656, - -0.708, - -0.736, - -0.696, - -0.712, - -0.684, - -0.7, - -0.688, - -0.644, - -0.596, - -0.556, - -0.572, - -0.552, - -0.524, - -0.496, - -0.472, - -0.504, - -0.464, - -0.368, - -0.412, - -0.42, - -0.42, - -0.436, - -0.372, - -0.352, - -0.352, - -0.4, - -0.372, - -0.336, - -0.292, - -0.376, - -0.392, - -0.444, - -0.456, - -0.436, - -0.504, - -0.56, - -0.58, - -0.62, - -0.68, - -0.716, - -0.76, - -0.76, - -0.768, - -0.764, - -0.792, - -0.784, - -0.76, - -0.74, - -0.728, - -0.66, - -0.7, - -0.756, - -0.86, - -0.776, - -0.76, - -0.812, - -0.876, - -0.872, - -0.908, - -0.96, - -0.976, - -0.996, - -1.032, - -0.976, - -0.96 + -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, + -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, + -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, + -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, + -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, + -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, + -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, + -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, + -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, + -1.032, -0.976, -0.96 ] } } @@ -7240,4 +1024,4 @@ "isConfident": false } } -] \ No newline at end of file +] From f0c442910b16d6e8ff16e9ca81b14705fb157a6e Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 17 Sep 2024 21:44:59 +0200 Subject: [PATCH 096/106] Only put Z --- .../livedata/MicrobitAccelerometerData.ts | 17 ++++++++++++++++- .../connection-behaviours/InputBehaviour.ts | 12 ++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts index 4cdf39b7e..388b60f95 100644 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ b/src/script/livedata/MicrobitAccelerometerData.ts @@ -33,7 +33,7 @@ export const asAccelerometerData = (input: LiveDataVector) => { }; export class MicrobitAccelerometerDataVector implements LiveDataVector { - public constructor(private data: MicrobitAccelerometerData) {} + public constructor(private data: MicrobitAccelerometerData) { } public getLabels(): string[] { return ['X', 'Y', 'Z']; @@ -51,6 +51,21 @@ export class MicrobitAccelerometerDataVector implements LiveDataVector { } } +export class MicrobitAccelerometerZDataVector implements LiveDataVector { + public constructor(private data: number) { } + getVector(): number[] { + return [this.data]; + } + getSize(): number { + return this.getVector().length; + } + getLabels(): string[] { + return ['Z']; + } +} + + + class MicrobitAccelerometerLiveData implements LiveData { private store: Writable; constructor(private dataBuffer: LiveDataBuffer) { diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index d55d753ed..4797a7749 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -19,7 +19,7 @@ import TypingUtils from '../../TypingUtils'; import { DeviceRequestStates } from '../../stores/connectDialogStore'; import StaticConfiguration from '../../../StaticConfiguration'; import MicrobitAccelerometerLiveData, { - MicrobitAccelerometerDataVector, + MicrobitAccelerometerDataVector, MicrobitAccelerometerZDataVector, } from '../../livedata/MicrobitAccelerometerData'; import { stores } from '../../stores/Stores'; import LiveDataBuffer from '../../domain/LiveDataBuffer'; @@ -155,16 +155,12 @@ class InputBehaviour extends LoggingDecorator { accelerometerChange(x: number, y: number, z: number): void { super.accelerometerChange(x, y, z); - const accelX = x / 1000.0; - const accelY = y / 1000.0; + //const accelX = x / 1000.0; + //const accelY = y / 1000.0; const accelZ = z / 1000.0; get(stores).liveData.put( - new MicrobitAccelerometerDataVector({ - x: accelX, - y: accelY, - z: accelZ, - }), + new MicrobitAccelerometerZDataVector(accelZ), ); } From 1fe3105c13db86f810f560e5c3d3b5d69e86725e Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 17 Sep 2024 21:50:56 +0200 Subject: [PATCH 097/106] Fixed problematic uses of buttons --- .../bluetooth/BluetoothConnectDialog.svelte | 7 +++++-- .../connection-prompt/usb/FindUsbDialog.svelte | 18 ++++++++++-------- .../usb/manual/ManualInstallTutorial.svelte | 5 +++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte b/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte index 94ee6a805..a38381306 100644 --- a/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte +++ b/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte @@ -162,8 +162,11 @@
- {$t('popup.connectMB.bluetooth.connect')} +
+ + {$t('popup.connectMB.bluetooth.connect')} + +
{/if} diff --git a/src/components/connection-prompt/usb/FindUsbDialog.svelte b/src/components/connection-prompt/usb/FindUsbDialog.svelte index 8e652b52c..4f625dc81 100644 --- a/src/components/connection-prompt/usb/FindUsbDialog.svelte +++ b/src/components/connection-prompt/usb/FindUsbDialog.svelte @@ -43,13 +43,15 @@

{/if}
- { - step = 2; - }}> - {$t(step === 1 ? 'connectMB.usb.button1' : 'connectMB.usb.button2')} - +
+ { + step = 2; + }}> + {$t(step === 1 ? 'connectMB.usb.button1' : 'connectMB.usb.button2')} + +
diff --git a/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte b/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte index ae678ef9d..df8b73fe0 100644 --- a/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte +++ b/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte @@ -68,7 +68,8 @@
- {$t('connectMB.usb.manual.done')} + + {$t('connectMB.usb.manual.done')} +
From f240afaf7857c9e114d23b35f71e4126b84052fc Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 17 Sep 2024 22:04:32 +0200 Subject: [PATCH 098/106] Add only Z-axis --- .../graphs/MicrobitLiveGraph.svelte | 10 ++---- .../livedata/MicrobitAccelerometerData.ts | 36 +++++++++++++++++++ .../connection-behaviours/InputBehaviour.ts | 6 ++-- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/components/graphs/MicrobitLiveGraph.svelte b/src/components/graphs/MicrobitLiveGraph.svelte index e6832c49e..5a413d86f 100644 --- a/src/components/graphs/MicrobitLiveGraph.svelte +++ b/src/components/graphs/MicrobitLiveGraph.svelte @@ -23,16 +23,12 @@ {#if showhighlit} {#key highlightedVectorIndex} {/key} {:else} - + {/if} diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts index 388b60f95..e63fa7470 100644 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ b/src/script/livedata/MicrobitAccelerometerData.ts @@ -65,6 +65,42 @@ export class MicrobitAccelerometerZDataVector implements LiveDataVector { } +export class MicrobitAccelerometerZLiveData implements LiveData { + private store: Writable; + constructor(private dataBuffer: LiveDataBuffer) { + this.store = writable( + new MicrobitAccelerometerZDataVector(0), + ); + } + + public getBuffer(): LiveDataBuffer { + return this.dataBuffer; + } + + public put(data: MicrobitAccelerometerZDataVector): void { + this.store.set(data); + this.dataBuffer.addValue(data); + } + + public getSeriesSize(): number { + // TODO: Could be replaced with the version in the store, as it is initialized in constructor + return new MicrobitAccelerometerZDataVector(0).getSize(); + } + + public getLabels(): string[] { + // TODO: Could be replaced with the version in the store, as it is initialized in constructor + return new MicrobitAccelerometerZDataVector(0).getLabels(); + } + + public subscribe( + run: Subscriber, + invalidate?: + | ((value?: MicrobitAccelerometerZDataVector | undefined) => void) + | undefined, + ): Unsubscriber { + return this.store.subscribe(run, invalidate); + } +} class MicrobitAccelerometerLiveData implements LiveData { private store: Writable; diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index 4797a7749..7ac406645 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -19,7 +19,7 @@ import TypingUtils from '../../TypingUtils'; import { DeviceRequestStates } from '../../stores/connectDialogStore'; import StaticConfiguration from '../../../StaticConfiguration'; import MicrobitAccelerometerLiveData, { - MicrobitAccelerometerDataVector, MicrobitAccelerometerZDataVector, + MicrobitAccelerometerDataVector, MicrobitAccelerometerZDataVector, MicrobitAccelerometerZLiveData, } from '../../livedata/MicrobitAccelerometerData'; import { stores } from '../../stores/Stores'; import LiveDataBuffer from '../../domain/LiveDataBuffer'; @@ -131,10 +131,10 @@ class InputBehaviour extends LoggingDecorator { onConnected(name?: string): void { super.onConnected(name); - const buffer = new LiveDataBuffer( + const buffer = new LiveDataBuffer( StaticConfiguration.accelerometerLiveDataBufferSize, ); - stores.setLiveData(new MicrobitAccelerometerLiveData(buffer)); + stores.setLiveData(new MicrobitAccelerometerZLiveData(buffer)); state.update(s => { s.isInputConnected = true; From 2c63201765d68719e5c5435719c5907e3a73db14 Mon Sep 17 00:00:00 2001 From: r59q Date: Tue, 17 Sep 2024 22:12:48 +0200 Subject: [PATCH 099/106] Map non-existant values to 0 --- features.json | 2 +- src/script/engine/PollingPredictorEngine.ts | 4 ++-- src/script/livedata/MicrobitAccelerometerData.ts | 13 ++++++------- .../connection-behaviours/InputBehaviour.ts | 8 ++++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/features.json b/features.json index ec4ddb583..97c1de396 100644 --- a/features.json +++ b/features.json @@ -3,4 +3,4 @@ "knnModel": true, "lossGraph": true, "makecode": true -} \ No newline at end of file +} diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts index 8b4aadad5..4ed870b1f 100644 --- a/src/script/engine/PollingPredictorEngine.ts +++ b/src/script/engine/PollingPredictorEngine.ts @@ -80,8 +80,8 @@ class PollingPredictorEngine implements Engine { StaticConfiguration.pollingPredictionSampleSize, ); const xs = bufferedData.map(data => data.value.getVector()[0]); - const ys = bufferedData.map(data => data.value.getVector()[1]); - const zs = bufferedData.map(data => data.value.getVector()[2]); + const ys = xs.map(e => 0); + const zs = xs.map(e => 0); // TODO: Generalize return new AccelerometerClassifierInput(xs, ys, zs); } diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts index e63fa7470..df0d88049 100644 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ b/src/script/livedata/MicrobitAccelerometerData.ts @@ -33,7 +33,7 @@ export const asAccelerometerData = (input: LiveDataVector) => { }; export class MicrobitAccelerometerDataVector implements LiveDataVector { - public constructor(private data: MicrobitAccelerometerData) { } + public constructor(private data: MicrobitAccelerometerData) {} public getLabels(): string[] { return ['X', 'Y', 'Z']; @@ -52,7 +52,7 @@ export class MicrobitAccelerometerDataVector implements LiveDataVector { } export class MicrobitAccelerometerZDataVector implements LiveDataVector { - public constructor(private data: number) { } + public constructor(private data: number) {} getVector(): number[] { return [this.data]; } @@ -64,13 +64,12 @@ export class MicrobitAccelerometerZDataVector implements LiveDataVector { } } - -export class MicrobitAccelerometerZLiveData implements LiveData { +export class MicrobitAccelerometerZLiveData + implements LiveData +{ private store: Writable; constructor(private dataBuffer: LiveDataBuffer) { - this.store = writable( - new MicrobitAccelerometerZDataVector(0), - ); + this.store = writable(new MicrobitAccelerometerZDataVector(0)); } public getBuffer(): LiveDataBuffer { diff --git a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts index 7ac406645..b7cc2829e 100644 --- a/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts +++ b/src/script/microbit-interfacing/connection-behaviours/InputBehaviour.ts @@ -19,7 +19,9 @@ import TypingUtils from '../../TypingUtils'; import { DeviceRequestStates } from '../../stores/connectDialogStore'; import StaticConfiguration from '../../../StaticConfiguration'; import MicrobitAccelerometerLiveData, { - MicrobitAccelerometerDataVector, MicrobitAccelerometerZDataVector, MicrobitAccelerometerZLiveData, + MicrobitAccelerometerDataVector, + MicrobitAccelerometerZDataVector, + MicrobitAccelerometerZLiveData, } from '../../livedata/MicrobitAccelerometerData'; import { stores } from '../../stores/Stores'; import LiveDataBuffer from '../../domain/LiveDataBuffer'; @@ -159,9 +161,7 @@ class InputBehaviour extends LoggingDecorator { //const accelY = y / 1000.0; const accelZ = z / 1000.0; - get(stores).liveData.put( - new MicrobitAccelerometerZDataVector(accelZ), - ); + get(stores).liveData.put(new MicrobitAccelerometerZDataVector(accelZ)); } buttonChange(buttonState: MBSpecs.ButtonState, button: MBSpecs.Button): void { From f8882067708ebef22332e2791fdc1f347fadc4fb Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 23 Sep 2024 20:42:43 +0200 Subject: [PATCH 100/106] Logging --- features.json | 8 ++++---- src/pages/training/TrainingPageTabs.svelte | 12 ------------ src/script/domain/stores/Classifier.ts | 5 ++++- src/script/engine/PollingPredictorEngine.ts | 2 ++ .../repository/LocalStorageClassifierRepository.ts | 2 ++ windi.config.js | 6 +++--- 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/features.json b/features.json index 97c1de396..1c4b872b5 100644 --- a/features.json +++ b/features.json @@ -1,6 +1,6 @@ { - "title": "Learning tool", - "knnModel": true, - "lossGraph": true, - "makecode": true + "title": "ML-Machine", + "knnModel": false, + "lossGraph": false, + "makecode": false } diff --git a/src/pages/training/TrainingPageTabs.svelte b/src/pages/training/TrainingPageTabs.svelte index 97e2d4709..5c21040b0 100644 --- a/src/pages/training/TrainingPageTabs.svelte +++ b/src/pages/training/TrainingPageTabs.svelte @@ -54,17 +54,5 @@ {:else}
- { - navigate(Paths.FILTERS); - }}> - {$t('content.trainer.controlbar.filters')} - {/if} diff --git a/src/script/domain/stores/Classifier.ts b/src/script/domain/stores/Classifier.ts index 8cc2a33a2..69c6d84e9 100644 --- a/src/script/domain/stores/Classifier.ts +++ b/src/script/domain/stores/Classifier.ts @@ -19,7 +19,7 @@ class Classifier implements Readable { private filters: Filters, private gestures: Readable, private confidenceSetter: (gestureId: GestureID, confidence: number) => void, - ) {} + ) { } public subscribe( run: Subscriber, @@ -40,6 +40,9 @@ class Classifier implements Readable { const filteredInput = input.getInput(this.filters); const predictions = await this.getModel().predict(filteredInput); predictions.forEach((confidence, index) => { + if (isNaN(confidence)) { + throw new Error(`Classifier returned NaN confidence for gesture at index ${index}`); + } const gesture = get(this.gestures)[index]; this.confidenceSetter(gesture.getId(), confidence); }); diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts index 4ed870b1f..070e7c762 100644 --- a/src/script/engine/PollingPredictorEngine.ts +++ b/src/script/engine/PollingPredictorEngine.ts @@ -11,6 +11,7 @@ import Engine, { EngineData } from '../domain/stores/Engine'; import Classifier from '../domain/stores/Classifier'; import LiveData from '../domain/stores/LiveData'; import { LiveDataVector } from '../domain/stores/LiveDataVector'; +import Logger from '../utils/Logger'; /** * The PollingPredictorEngine will predict on the current input with consistent intervals. @@ -96,6 +97,7 @@ class PollingPredictorEngine implements Engine { .getSeries(StaticConfiguration.pollingPredictionSampleDuration, sampleSize); } catch (_e) { if (sampleSize < 8) { + Logger.log("PollingPredictorEngine", "Too few samples available, returning empty array"); return []; // The minimum number of points is 8, otherwise the filters will throw an exception } else { // If too few samples are available, try again with fewer samples diff --git a/src/script/repository/LocalStorageClassifierRepository.ts b/src/script/repository/LocalStorageClassifierRepository.ts index 2b994950c..9518f140d 100644 --- a/src/script/repository/LocalStorageClassifierRepository.ts +++ b/src/script/repository/LocalStorageClassifierRepository.ts @@ -68,6 +68,8 @@ class LocalStorageClassifierRepository implements ClassifierRepository { get(gestureRepository), LocalStorageClassifierRepository.filters, ); + + console.log(trainingData) const model = await trainer.trainModel(trainingData); LocalStorageClassifierRepository.mlModel.set(model); } diff --git a/windi.config.js b/windi.config.js index a5525d9a8..e41e5fe6e 100644 --- a/windi.config.js +++ b/windi.config.js @@ -10,16 +10,16 @@ export default { theme: { extend: { colors: { - primary: '#3a3a3a', + primary: '#2B5EA7', primarytext: '#000000', - secondary: '#a0a0a0', + secondary: '#2CCAC0', secondarytext: '#FFFFFF', info: '#98A2B3', backgrounddark: '#F5F5F5', backgroundlight: '#ffffff', infolight: '#93c5fd', link: '#258aff', - warning: '#ffaaaa', + warning: '#FF7777', disabled: '#8892A3', primaryborder: '#E5E7EB', infobglight: '#E7E5E4', From 7d45b973983d207b7855c0d4d07831c5ce312e1d Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 23 Sep 2024 20:51:46 +0200 Subject: [PATCH 101/106] Better styling --- src/script/engine/PollingPredictorEngine.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts index 070e7c762..a49f5f0f8 100644 --- a/src/script/engine/PollingPredictorEngine.ts +++ b/src/script/engine/PollingPredictorEngine.ts @@ -81,8 +81,9 @@ class PollingPredictorEngine implements Engine { StaticConfiguration.pollingPredictionSampleSize, ); const xs = bufferedData.map(data => data.value.getVector()[0]); - const ys = xs.map(e => 0); - const zs = xs.map(e => 0); + // Make it same size as xs + const ys = new Array(xs.length).fill(0); + const zs = new Array(xs.length).fill(0); // TODO: Generalize return new AccelerometerClassifierInput(xs, ys, zs); } From 446db70430ec2b342a3e6429f1ec3c1f47b8f5eb Mon Sep 17 00:00:00 2001 From: r59q Date: Mon, 23 Sep 2024 21:18:34 +0200 Subject: [PATCH 102/106] Fix stability issues --- features.json | 8 ++--- src/components/Gesture.svelte | 8 ++--- src/components/graphs/RecordingGraph.svelte | 34 ++++--------------- .../EngineInteractionButtons.svelte | 8 ++--- src/pages/DataPage.svelte | 8 ----- src/pages/filter/D3Plot.svelte | 10 ++---- src/script/ControlledStorage.ts | 2 +- src/script/domain/ClassifierFactory.ts | 6 +--- src/script/domain/stores/Classifier.ts | 6 ++-- src/script/domain/stores/gesture/Gestures.ts | 2 -- src/script/engine/PollingPredictorEngine.ts | 19 ++++++----- .../mlmodels/AccelerometerClassifierInput.ts | 12 +++++++ .../LocalStorageClassifierRepository.ts | 2 +- windi.config.js | 6 ++-- 14 files changed, 52 insertions(+), 79 deletions(-) diff --git a/features.json b/features.json index 1c4b872b5..97c1de396 100644 --- a/features.json +++ b/features.json @@ -1,6 +1,6 @@ { - "title": "ML-Machine", - "knnModel": false, - "lossGraph": false, - "makecode": false + "title": "Learning tool", + "knnModel": true, + "lossGraph": true, + "makecode": true } diff --git a/src/components/Gesture.svelte b/src/components/Gesture.svelte index dcf8aabe4..ea8ff72dd 100644 --- a/src/components/Gesture.svelte +++ b/src/components/Gesture.svelte @@ -72,13 +72,11 @@ isThisRecording = true; // New array for data - let newData: { x: number[]; y: number[]; z: number[] } = { x: [], y: [], z: [] }; + let newData: { z: number[] } = { z: [] }; // Set timeout to allow recording in 1s const unsubscribe = liveData.subscribe(data => { - newData.x.push(data.getVector()[0]); - newData.y.push(data.getVector()[1]); - newData.z.push(data.getVector()[2]); + newData.z.push(data.getVector()[0]); }); // Once duration is over (1000ms default), stop recording @@ -86,7 +84,7 @@ $state.isRecording = false; isThisRecording = false; unsubscribe(); - if (StaticConfiguration.pollingPredictionSampleSize <= newData.x.length) { + if (StaticConfiguration.pollingPredictionSampleSize <= newData.z.length) { const recording = { ID: Date.now(), data: newData } as RecordingData; gesture.addRecording(recording); } else { diff --git a/src/components/graphs/RecordingGraph.svelte b/src/components/graphs/RecordingGraph.svelte index 324ec0654..b882826d4 100644 --- a/src/components/graphs/RecordingGraph.svelte +++ b/src/components/graphs/RecordingGraph.svelte @@ -18,7 +18,7 @@ } from 'chart.js'; import RecordingInspector from '../3d-inspector/RecordingInspector.svelte'; - export let data: { x: number[]; y: number[]; z: number[] }; + export let data: { z: number[] }; let verticalLineX = NaN; let hoverIndex = NaN; @@ -29,11 +29,9 @@ const getDataByIndex = (index: number) => { if (isNaN(index)) { - return { x: 0, y: 0, z: 0 }; + return { z: 0 }; } return { - x: data.x[index], - y: data.y[index], z: data.z[index], }; }; @@ -81,37 +79,17 @@ { x: number; y: number }[], string > { - const x: { x: number; y: number }[] = []; - const y: { x: number; y: number }[] = []; const z: { x: number; y: number }[] = []; - for (let i = 1; i < data.x.length; i++) { - x.push({ x: i, y: data.x[i - 1] }); - y.push({ x: i, y: data.y[i - 1] }); + for (let i = 1; i < data.z.length; i++) { z.push({ x: i, y: data.z[i - 1] }); } return { type: 'line', data: { datasets: [ - { - label: 'x', - borderColor: 'red', - borderWidth: 1, - pointRadius: 0, - pointHoverRadius: 0, - data: x, - }, - { - label: 'y', - borderColor: 'green', - borderWidth: 1, - pointRadius: 0, - pointHoverRadius: 0, - data: y, - }, { label: 'z', - borderColor: 'blue', + borderColor: 'red', borderWidth: 1, pointRadius: 0, pointHoverRadius: 0, @@ -131,7 +109,7 @@ x: { type: 'linear', min: 0, - max: data.x.length, + max: data.z.length, grid: { color: '#f3f3f3', }, @@ -232,7 +210,7 @@
diff --git a/src/components/playground/EngineInteractionButtons.svelte b/src/components/playground/EngineInteractionButtons.svelte index 41b86fb5d..1c08ab6a3 100644 --- a/src/components/playground/EngineInteractionButtons.svelte +++ b/src/components/playground/EngineInteractionButtons.svelte @@ -5,7 +5,9 @@ -->