Skip to content

Commit

Permalink
Merge pull request #511 from microbit-foundation/min-samples-check
Browse files Browse the repository at this point in the history
Min samples check
  • Loading branch information
r59q authored Jul 30, 2024
2 parents 0aa8618 + c8f164b commit 928f1e5
Show file tree
Hide file tree
Showing 13 changed files with 65 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/script/domain/ClassifierInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Filters from './Filters';

interface ClassifierInput {
getInput(filters: Filters): number[];

getNumberOfSamples(): number;
}

export default ClassifierInput;
2 changes: 2 additions & 0 deletions src/script/domain/Filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface Filter {
getName(): string;

getDescription(): string;

getMinNumberOfSamples(): number;
}

export default Filter;
17 changes: 15 additions & 2 deletions src/script/engine/PollingPredictorEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
);
Expand Down
1 change: 1 addition & 0 deletions src/script/filters/FilterWithMaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/script/filters/MaxFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 4 additions & 0 deletions src/script/filters/MeanFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
3 changes: 3 additions & 0 deletions src/script/filters/MinFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
28 changes: 16 additions & 12 deletions src/script/filters/PeaksFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand All @@ -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
Expand All @@ -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;
4 changes: 4 additions & 0 deletions src/script/filters/RootMeanSquareFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 4 additions & 0 deletions src/script/filters/StandardDeviationFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
3 changes: 3 additions & 0 deletions src/script/filters/TotalAccFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 4 additions & 0 deletions src/script/filters/ZeroCrossingRateFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class ZeroCrossingRateFilter implements Filter {
}
return count / (inValues.length - 1);
}

public getMinNumberOfSamples(): number {
return 2;
}
}

export default ZeroCrossingRateFilter;
4 changes: 4 additions & 0 deletions src/script/mlmodels/AccelerometerClassifierInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit 928f1e5

Please sign in to comment.