зеркало из https://github.com/microsoft/ELT.git
Strip suggestion code
This commit is contained in:
Родитель
43f21812e8
Коммит
0dd037e8e4
Двоичные данные
app/img/accelerometer_icon.png
Двоичные данные
app/img/accelerometer_icon.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 8.4 KiB |
Двоичные данные
app/img/gyroscope_icon.png
Двоичные данные
app/img/gyroscope_icon.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 39 KiB |
Двоичные данные
app/img/magnetometer_icon.png
Двоичные данные
app/img/magnetometer_icon.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 19 KiB |
Двоичные данные
app/img/video_icon.png
Двоичные данные
app/img/video_icon.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 15 KiB |
|
@ -1,99 +0,0 @@
|
|||
// HOWTO:
|
||||
// Call setupRecognizer(0.2) first.
|
||||
// Then, call String className = recognize(sample);
|
||||
// 'NONE' will be returned if nothing is detected.
|
||||
// sample should be a float array, in the same order you put the training data in the tool.
|
||||
|
||||
struct DTWInfo {
|
||||
int dim; // The dimension of the signal.
|
||||
float* prototype; // The data for the prototype.
|
||||
int* s; // Matching array: start points.
|
||||
float* d; // Matching array: distances.
|
||||
int prototypeSize; // The length of the prototype.
|
||||
int t; // Current time in samples.
|
||||
float variance; // The variance.
|
||||
float bestMatchEndsAtTDistance; // The distance of the best match that ends at t.
|
||||
int bestMatchEndsAtTStart; // The start of the best match that ends at t.
|
||||
};
|
||||
|
||||
float DTWDistanceFunction(int dim, float* a, float* b) {
|
||||
int s = 0;
|
||||
for(int i = 0; i < dim; i++) {
|
||||
s += abs(a[i] - b[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void DTWInit(struct DTWInfo* DTW, int dim, float* prototype, int prototypeSize, float variance) {
|
||||
DTW->dim = dim;
|
||||
DTW->prototypeSize = prototypeSize;
|
||||
DTW->prototype = prototype;
|
||||
DTW->d = new float[prototypeSize + 1];
|
||||
DTW->s = new int[prototypeSize + 1];
|
||||
DTW->t = 0;
|
||||
DTW->variance = variance;
|
||||
for(int i = 0; i <= prototypeSize; i++) {
|
||||
DTW->d[i] = 1e10;
|
||||
DTW->s[i] = 0;
|
||||
}
|
||||
DTW->d[0] = 0;
|
||||
}
|
||||
|
||||
void DTWReset(struct DTWInfo* DTW) {
|
||||
for(int i = 0; i <= DTW->prototypeSize; i++) {
|
||||
DTW->d[i] = 1e10;
|
||||
DTW->s[i] = 0;
|
||||
}
|
||||
DTW->d[0] = 0;
|
||||
}
|
||||
|
||||
void DTWFeed(struct DTWInfo* DTW, float* sample) {
|
||||
float* d = DTW->d;
|
||||
int* s = DTW->s;
|
||||
DTW->t += 1;
|
||||
d[0] = 0;
|
||||
s[0] = DTW->t;
|
||||
float dp = d[0];
|
||||
int sp = s[0];
|
||||
for(int i = 1; i <= DTW->prototypeSize; i++) {
|
||||
float dist = DTWDistanceFunction(DTW->dim, DTW->prototype + (i - 1) * DTW->dim, sample);
|
||||
float d_i_minus_1 = d[i - 1]; int s_i_minus_1 = s[i - 1];
|
||||
float d_i_p = d[i]; int s_i_p = s[i];
|
||||
float d_i_p_minus_1 = dp; int s_i_p_minus_1 = sp;
|
||||
dp = d[i];
|
||||
sp = s[i];
|
||||
if(d_i_minus_1 <= d_i_p && d_i_minus_1 <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_minus_1;
|
||||
s[i] = s_i_minus_1;
|
||||
} else if(d_i_p <= d_i_minus_1 && d_i_p <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_p;
|
||||
s[i] = s_i_p;
|
||||
} else {
|
||||
d[i] = dist + d_i_p_minus_1;
|
||||
s[i] = s_i_p_minus_1;
|
||||
}
|
||||
}
|
||||
DTW->bestMatchEndsAtTDistance = d[DTW->prototypeSize] / DTW->variance;
|
||||
DTW->bestMatchEndsAtTStart = s[DTW->prototypeSize];
|
||||
if(DTW->t - DTW->bestMatchEndsAtTStart > DTW->prototypeSize * 0.8 && DTW->t - DTW->bestMatchEndsAtTStart < DTW->prototypeSize * 1.2) {
|
||||
} else DTW->bestMatchEndsAtTDistance = 1e10;
|
||||
}
|
||||
|
||||
%%GLOBAL%%
|
||||
|
||||
void setupRecognizer(float confidenceThreshold) {
|
||||
float threshold = sqrt(-2 * log(confidenceThreshold));
|
||||
%%SETUP%%
|
||||
}
|
||||
|
||||
void resetRecognizer() {
|
||||
%%RESET%%
|
||||
}
|
||||
|
||||
String recognize(float* sample) {
|
||||
%%MATCH%%
|
||||
String minClass = 'NONE';
|
||||
float minClassScore = 1e10;
|
||||
%%MATCH_COMPARISON%%
|
||||
return minClass;
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// HOWTO:
|
||||
// Call setupRecognizer() first.
|
||||
// Then, call let className = recognize([ input.acceleration(Dimension.X), input.acceleration(Dimension.Y), input.acceleration(Dimension.Z) ]);
|
||||
// 'NONE' will be returned if nothing is detected.
|
||||
// An example is at the end of this file.
|
||||
|
||||
// IMPORTANT: All number in microbit are integers.
|
||||
function DTWDistanceFunction(dim: number, a: number[], astart: number, b: number[]) {
|
||||
let s = 0;
|
||||
for (let i = 0; i < dim; i++) {
|
||||
s += (a[i + astart] - b[i]) * (a[i + astart] - b[i]);
|
||||
}
|
||||
return Math.sqrt(s);
|
||||
}
|
||||
class DTWInfo {
|
||||
public dim: number;
|
||||
public prototype: number[];
|
||||
public s: number[];
|
||||
public d: number[];
|
||||
public prototypeSize: number;
|
||||
public t: number;
|
||||
public bestMatchEndsAtTDistance: number;
|
||||
// The distance of the best match that ends at t.
|
||||
public variance: number;
|
||||
// The variance computed.
|
||||
public bestMatchEndsAtTStart: number;
|
||||
// The start of the best match that ends at t.
|
||||
constructor(dim: number, prototype: number[], prototypeSize: number, variance: number) {
|
||||
this.dim = dim;
|
||||
this.prototypeSize = prototypeSize;
|
||||
this.prototype = prototype;
|
||||
this.d = %%ARRAY_INITIALIZATION%%;
|
||||
this.s = %%ARRAY_INITIALIZATION%%;
|
||||
this.t = 0;
|
||||
this.variance = variance;
|
||||
for (let i = 0; i <= prototypeSize; i++) {
|
||||
this.d[i] = 500000000;
|
||||
this.s[i] = 0;
|
||||
}
|
||||
this.d[0] = 0;
|
||||
}
|
||||
public feed(sample: number[]) {
|
||||
let d = this.d;
|
||||
let s = this.s;
|
||||
this.t += 1;
|
||||
d[0] = 0;
|
||||
s[0] = this.t;
|
||||
let dp = d[0];
|
||||
let sp = s[0];
|
||||
for (let i = 1; i <= this.prototypeSize; i++) {
|
||||
let dist = DTWDistanceFunction(this.dim, this.prototype, (i - 1) * this.dim, sample);
|
||||
let d_i_minus_1 = d[i - 1];
|
||||
let s_i_minus_1 = s[i - 1];
|
||||
let d_i_p = d[i];
|
||||
let s_i_p = s[i];
|
||||
let d_i_p_minus_1 = dp;
|
||||
let s_i_p_minus_1 = sp;
|
||||
dp = d[i];
|
||||
sp = s[i];
|
||||
if (d_i_minus_1 <= d_i_p && d_i_minus_1 <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_minus_1;
|
||||
s[i] = s_i_minus_1;
|
||||
} else if (d_i_p <= d_i_minus_1 && d_i_p <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_p;
|
||||
s[i] = s_i_p;
|
||||
} else {
|
||||
d[i] = dist + d_i_p_minus_1;
|
||||
s[i] = s_i_p_minus_1;
|
||||
}
|
||||
}
|
||||
this.bestMatchEndsAtTDistance = d[this.prototypeSize] / this.variance;
|
||||
this.bestMatchEndsAtTStart = s[this.prototypeSize];
|
||||
}
|
||||
}
|
||||
|
||||
%%GLOBAL%%
|
||||
|
||||
function setupRecognizer() {
|
||||
let threshold = 1794; // confidenceThreshold = 0.2
|
||||
%%SETUP%%
|
||||
}
|
||||
|
||||
function recognize(sample: number[]): string {
|
||||
%%MATCH%%
|
||||
let minClass = 'NONE';
|
||||
let minClassScore = 5000000;
|
||||
%%MATCH_COMPARISON%%
|
||||
return minClass;
|
||||
}
|
||||
|
||||
// Example application code:
|
||||
setupRecognizer();
|
||||
basic.forever(() => {
|
||||
// Recognize the gesture.
|
||||
let className = recognize([input.acceleration(Dimension.X), input.acceleration(Dimension.Y), input.acceleration(Dimension.Z)]);
|
||||
// Send the result over serial.
|
||||
serial.writeLine(className);
|
||||
}
|
|
@ -2,7 +2,6 @@ import { TabID } from '../stores/dataStructures/types';
|
|||
import * as stores from '../stores/stores';
|
||||
import { NavigationColumn, NavigationColumnItem } from './common/NavigationColumn';
|
||||
import { SharedAlignmentLabelingPane } from './common/SharedAlignmentLabelingPane';
|
||||
import { DeploymentPanel } from './deployment/DeploymentPanel';
|
||||
import { HomeMenu } from './home/HomeMenu';
|
||||
import { remote } from 'electron';
|
||||
import { observer } from 'mobx-react';
|
||||
|
@ -82,9 +81,6 @@ export class App extends React.Component<{}, {}> {
|
|||
<NavigationColumnItem title='Labeling' name='labeling' showButton={true} iconClass={'glyphicon glyphicon-tags'}>
|
||||
<SharedAlignmentLabelingPane mode='labeling' toolbarHeight={40} />
|
||||
</NavigationColumnItem>
|
||||
<NavigationColumnItem title='Deployment' name='deploying' iconClass='glyphicon glyphicon-export'>
|
||||
<DeploymentPanel toolbarHeight={40} />
|
||||
</NavigationColumnItem>
|
||||
</NavigationColumn>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {autocorrelogram} from '../../stores/dataStructures/Autocorrelation';
|
||||
import {SensorTimeSeries} from '../../stores/dataStructures/dataset';
|
||||
import {autocorrelogram} from '../../suggestion/algorithms/Autocorrelation';
|
||||
import * as d3 from 'd3';
|
||||
import * as React from 'react';
|
||||
|
||||
|
|
|
@ -1,218 +0,0 @@
|
|||
import * as stores from '../../stores/stores';
|
||||
import { labelingSuggestionGenerator } from '../../stores/stores';
|
||||
import { generateEllModel } from '../../suggestion/ELLDtwModelGeneration';
|
||||
import { DeploymentToolbar } from './DeploymentToolbar';
|
||||
import { ScriptEditor } from './ScriptEditor';
|
||||
import { ToolOutputPanel } from './ToolOutputPanel';
|
||||
import { execFileSync, spawn } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
|
||||
|
||||
// Configuration
|
||||
const useEllModel = true; // vs. Donghao model
|
||||
const confidenceThreshold = 0.2;
|
||||
const useStoredModel = false;
|
||||
|
||||
// utility function for copying file
|
||||
function copyTextFile(sourceFilename: string, targetFilename: string): void {
|
||||
const text = fs.readFileSync(sourceFilename, 'utf-8');
|
||||
fs.writeFileSync(targetFilename, text);
|
||||
}
|
||||
|
||||
interface Tab {
|
||||
isReadOnly: boolean;
|
||||
label: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface DeploymentPanelProps {
|
||||
toolbarHeight: number;
|
||||
}
|
||||
|
||||
interface DeploymentPanelState {
|
||||
arduinoAppCodeTemplate?: string;
|
||||
arduinoModelCode?: string;
|
||||
microbitAppCodeTemplate?: string;
|
||||
microbitModelCode?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
tabs?: Tab[];
|
||||
currentTab?: Tab;
|
||||
toolOutput?: string;
|
||||
}
|
||||
|
||||
export class DeploymentPanel extends React.Component<DeploymentPanelProps, DeploymentPanelState> {
|
||||
public refs: {
|
||||
[key: string]: Element,
|
||||
container: Element,
|
||||
};
|
||||
|
||||
private toolOutputPanel: ToolOutputPanel;
|
||||
|
||||
constructor(props: DeploymentPanelProps, context: any) {
|
||||
super(props, context);
|
||||
let appCode: string;
|
||||
let modelCodeFilename: string;
|
||||
if (useEllModel) {
|
||||
appCode = fs.readFileSync('arduino/dtw_ELL.ino', 'utf-8');
|
||||
modelCodeFilename = 'model.asm';
|
||||
} else {
|
||||
appCode = fs.readFileSync('arduino/dtw.ino', 'utf-8');
|
||||
modelCodeFilename = 'model.ino';
|
||||
}
|
||||
// let appCode = fs.readFileSync('arduino/dtw_TEST.ino', 'utf-8');
|
||||
const tabs = [
|
||||
{
|
||||
label: 'dtw.ino',
|
||||
isReadOnly: false,
|
||||
text: appCode
|
||||
},
|
||||
{
|
||||
label: modelCodeFilename,
|
||||
isReadOnly: true,
|
||||
text: ''
|
||||
}
|
||||
];
|
||||
this.state = {
|
||||
arduinoAppCodeTemplate: appCode,
|
||||
arduinoModelCode: '',
|
||||
microbitAppCodeTemplate: appCode,
|
||||
microbitModelCode: '',
|
||||
tabs: tabs,
|
||||
currentTab: tabs[0],
|
||||
toolOutput: ''
|
||||
};
|
||||
this.compile = this.compile.bind(this);
|
||||
this.deploy = this.deploy.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const prototypes = stores.dtwModelStore.prototypes;
|
||||
if (prototypes.length === 0) { return; }
|
||||
const sampleRate = stores.dtwModelStore.prototypeSampleRate;
|
||||
const model = generateEllModel(sampleRate, 30, prototypes, confidenceThreshold);
|
||||
const ellModelCode = model.GetCodeString();
|
||||
const header = model.GetHeaderString();
|
||||
labelingSuggestionGenerator.getDeploymentCode('arduino', oldModelCode => {
|
||||
const modelCode = useEllModel ? ellModelCode : oldModelCode;
|
||||
// #### hack: replace code passed in with current code from the store
|
||||
const appCode = this.state.arduinoAppCodeTemplate.replace('// %%HEADER%%', header);
|
||||
this.setState({ arduinoModelCode: modelCode });
|
||||
const modelCodeFilename = useEllModel ? 'model.asm' : 'model.ino';
|
||||
this.state.tabs
|
||||
.filter(t => t.label === modelCodeFilename)
|
||||
.forEach(t => t.text = modelCode);
|
||||
this.state.tabs
|
||||
.filter(t => t.label === 'dtw.ino')
|
||||
.forEach(t => t.text = appCode);
|
||||
});
|
||||
labelingSuggestionGenerator.getDeploymentCode('microbit', oldModelCode => {
|
||||
const modelCode = useEllModel ? ellModelCode : oldModelCode;
|
||||
// #### hack: replace code passed in with current code from the store
|
||||
// let appCode = this.state.microbitAppCodeTemplate.replace('// %%HEADER%%', header);
|
||||
this.setState({ microbitModelCode: modelCode });
|
||||
// TODO: set code tabs
|
||||
});
|
||||
const containerWidth = this.refs.container.getBoundingClientRect().width;
|
||||
const containerHeight = 500; // this.refs.container.getBoundingClientRect().height;
|
||||
this.setState({ width: containerWidth, height: containerHeight });
|
||||
}
|
||||
|
||||
private compile(): void {
|
||||
this.runScript('--verify');
|
||||
}
|
||||
|
||||
private deploy(): void {
|
||||
this.runScript('--upload');
|
||||
}
|
||||
|
||||
private runScript(mode: string): void {
|
||||
// Create a temp sketch with our files.
|
||||
const mainTempDir = os.tmpdir() + path.sep + 'DTWSketches';
|
||||
if (!fs.existsSync(mainTempDir)) {
|
||||
fs.mkdirSync(mainTempDir);
|
||||
}
|
||||
const topdir = fs.mkdtempSync(os.tmpdir() + path.sep + 'DTWSketches' + path.sep + 'sketch-');
|
||||
const tmpdir = topdir + path.sep + 'dtw';
|
||||
fs.mkdirSync(tmpdir);
|
||||
copyTextFile('arduino/BluefruitConfig.h', tmpdir + path.sep + 'BluefruitConfig.h');
|
||||
this.state.tabs.forEach(tab => {
|
||||
const filename = tab.label;
|
||||
const asmExt = '.asm';
|
||||
if (filename.indexOf(asmExt, filename.length - asmExt.length) !== -1) {
|
||||
if (useStoredModel) {
|
||||
copyTextFile('arduino/model.asm', tmpdir + path.sep + filename);
|
||||
} else {
|
||||
fs.writeFileSync(tmpdir + path.sep + filename, tab.text);
|
||||
}
|
||||
const llcArgs = [
|
||||
'-mtriple=armv6m-unknown-none-eabi',
|
||||
'-march=thumb -mcpu=cortex-m0',
|
||||
'-float-abi=soft',
|
||||
'-mattr=+armv6-m,+v6m',
|
||||
'-filetype=asm -asm-verbose=0',
|
||||
'-o=model.S',
|
||||
'model.asm'
|
||||
];
|
||||
const cmdOutput = execFileSync('llc', llcArgs, { cwd: tmpdir });
|
||||
} else {
|
||||
fs.writeFileSync(tmpdir + path.sep + tab.label, tab.text);
|
||||
}
|
||||
});
|
||||
this.setState({ toolOutput: '' });
|
||||
// Run the arduino command line tool.
|
||||
const arduinoCmd = 'C:\\Program Files (x86)\\Arduino\\arduino_debug.exe';
|
||||
const board = 'adafruit:samd:adafruit_feather_m0';
|
||||
const port = 'COM8';
|
||||
const proc = spawn(
|
||||
arduinoCmd,
|
||||
[mode, 'dtw.ino', '--board', board, '--port', port, '--verbose-upload'],
|
||||
{ cwd: tmpdir });
|
||||
const report = (line: string, isError: boolean) => {
|
||||
this.setState({ toolOutput: this.state.toolOutput + line });
|
||||
};
|
||||
proc.stdout.on('data', data => report(data.toString(), false));
|
||||
proc.stderr.on('data', data => report(data.toString(), true));
|
||||
proc.on('close', m => report('Done', false));
|
||||
this.toolOutputPanel.open();
|
||||
}
|
||||
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div ref='container'>
|
||||
<div className='app-menu deployment-menu'>
|
||||
<DeploymentToolbar
|
||||
top={0}
|
||||
left={0}
|
||||
viewWidth={this.state.width}
|
||||
viewHeight={this.props.toolbarHeight}
|
||||
compileClick={this.compile}
|
||||
deployClick={this.deploy} />
|
||||
</div>
|
||||
<ul className='nav nav-tabs'>
|
||||
{
|
||||
this.state.tabs.map((tab, i) =>
|
||||
<li className={tab.label === this.state.currentTab.label ? 'active' : ''}
|
||||
key={tab.label}>
|
||||
<a onClick={() => this.setState({ currentTab: tab })} href='#'>
|
||||
{tab.label}
|
||||
</a>
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
<ScriptEditor
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
text={this.state.currentTab.text}
|
||||
readOnly={this.state.currentTab.isReadOnly} />
|
||||
<ToolOutputPanel ref={e => this.toolOutputPanel = e}
|
||||
titleText='Deployment Output'
|
||||
outputText={this.state.toolOutput} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import * as React from 'react';
|
||||
|
||||
export interface DeploymentToolbarProps {
|
||||
top: number;
|
||||
left: number;
|
||||
viewWidth: number;
|
||||
viewHeight: number;
|
||||
compileClick: () => void;
|
||||
deployClick: () => void;
|
||||
}
|
||||
|
||||
export class DeploymentToolbar extends React.Component<DeploymentToolbarProps, {}> {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className='toolbar-view form-inline' style={{
|
||||
position: 'absolute',
|
||||
top: this.props.top + 'px',
|
||||
left: this.props.left + 'px',
|
||||
width: this.props.viewWidth + 'px',
|
||||
height: this.props.viewHeight + 'px',
|
||||
}}>
|
||||
<div className='form-group-sm'>
|
||||
<label className='col-sm-1 control-label' htmlFor='targetPlatform'>Target</label>
|
||||
<select className='form-control col-xs-2' id='targetPlatform'>
|
||||
<option>Arduino</option>
|
||||
<option>Microbit</option>
|
||||
</select>
|
||||
</div>
|
||||
<button className='tbtn tbtn-l3 breathing-room' title='Compile' type='button'
|
||||
onClick={ () => this.props.compileClick() } >
|
||||
Compile
|
||||
</button>
|
||||
<button className='tbtn tbtn-l3' title='Deploy' type='button'
|
||||
onClick={ () => this.props.deployClick() } >
|
||||
Deploy
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// tslint:disable:no-reference
|
||||
/// <reference path="../../../../node_modules/monaco-editor/monaco.d.ts" />
|
||||
// tslint:enable:no-reference
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ScriptEditorProps {
|
||||
width: number;
|
||||
height: number;
|
||||
text: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export class ScriptEditor extends React.Component<ScriptEditorProps, {}> {
|
||||
private editor: monaco.editor.IStandaloneCodeEditor;
|
||||
|
||||
public refs: {
|
||||
[key: string]: Element,
|
||||
container: HTMLElement
|
||||
};
|
||||
|
||||
constructor(props: ScriptEditorProps, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const monaco = (global as any).monaco;
|
||||
this.editor = monaco.editor.create(this.refs.container, {
|
||||
value: '',
|
||||
language: 'c',
|
||||
lineNumbers: false,
|
||||
glyphMargin: false,
|
||||
parameterHints: true,
|
||||
suggestOnTriggerCharacters: true
|
||||
});
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(newProps: ScriptEditorProps): void {
|
||||
this.editor.getModel().setValue(newProps.text);
|
||||
this.editor.updateOptions({ readOnly: newProps.readOnly });
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className='monaco-container'>
|
||||
<div ref='container' className='monaco-toplevel'
|
||||
style={{
|
||||
width: this.props.width,
|
||||
height: this.props.height
|
||||
}}>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import * as $ from 'jquery';
|
||||
import * as React from 'react';
|
||||
|
||||
|
||||
export interface ToolOutputPanelProps {
|
||||
titleText: string;
|
||||
outputText: string;
|
||||
}
|
||||
|
||||
export class ToolOutputPanel extends React.Component<ToolOutputPanelProps, {}> {
|
||||
public refs: {
|
||||
[key: string]: Element,
|
||||
collapser: Element,
|
||||
};
|
||||
public open(): void {
|
||||
if (!$('.panel-collapse').hasClass('in')) {
|
||||
($('.collapse') as any).collapse('toggle');
|
||||
}
|
||||
}
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className='panel panel-default toolOutputPanel'>
|
||||
<div className='panel-heading'>
|
||||
<div className='panel-title'>
|
||||
<span className='panelTitle'>{this.props.titleText}</span>
|
||||
<a ref='collapser' data-toggle='collapse' data-target='.toolOutput' className='toolOutputPanelCollapse'>
|
||||
<span className='toolOutput collapse in glyphicon glyphicon-collapse-up'></span>
|
||||
<span className='toolOutput collapse glyphicon glyphicon-collapse-down'></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className='panel-collapse collapse toolOutput'>
|
||||
<div className='feedbackText'>{this.props.outputText}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import * as stores from '../../stores/stores';
|
||||
import { OptionsToolbar } from '../common/OptionsToolbar';
|
||||
import { InlineClassesListView } from './ClassesListView';
|
||||
import { SuggestionsToolbar } from './SuggestionsToolbar';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
|
@ -43,7 +42,6 @@ export class LabelingToolbar extends React.Component<LabelingToolbarProps, {}> {
|
|||
</button>
|
||||
<span className='sep' />
|
||||
<OptionsToolbar />
|
||||
<SuggestionsToolbar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
import * as stores from '../../stores/stores';
|
||||
import { labelingSuggestionGenerator } from '../../stores/stores';
|
||||
import { LabelingSuggestionLogicType } from '../../suggestion/LabelingSuggestionLogic';
|
||||
import { ConfidenceSlider } from '../common/ConfidenceSlider';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
|
||||
@observer
|
||||
export class SuggestionsToolbar extends React.Component<{}, {}> {
|
||||
private setSuggestionLogicThunk: { [logicType: number]: () => void } = {};
|
||||
|
||||
constructor(props: {}, context: any) {
|
||||
super(props, context);
|
||||
|
||||
Object.keys(LabelingSuggestionLogicType).forEach(name => {
|
||||
const val = LabelingSuggestionLogicType[name];
|
||||
this.setSuggestionLogicThunk[val] = this.setSuggestionLogic.bind(this, val);
|
||||
});
|
||||
}
|
||||
|
||||
private setSuggestionLogic(logicType: LabelingSuggestionLogicType): void {
|
||||
stores.labelingUiStore.setSuggestionLogic(logicType);
|
||||
}
|
||||
|
||||
private setConfidenceThreshold(value: number): void {
|
||||
stores.labelingUiStore.setSuggestionConfidenceThreshold(Math.max(0.001, Math.min(0.999, Math.pow(value, 1.0 / 0.3))));
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const logicClassName = (log: LabelingSuggestionLogicType) => {
|
||||
return stores.labelingUiStore.suggestionLogic.getType() === log ? 'visible' : 'hidden';
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{display: 'inline-block'}}>
|
||||
Suggestions:
|
||||
<span className='tbtn-group' style={{marginLeft: '2pt'}} >
|
||||
<button
|
||||
type='button'
|
||||
className={`tbtn ${stores.labelingUiStore.suggestionEnabled ? 'tbtn-l1 active' : 'tbtn-l3'}`}
|
||||
onClick={() => stores.labelingUiStore.suggestionEnabled = true}
|
||||
>On</button>
|
||||
<button
|
||||
type='button'
|
||||
className={`tbtn ${!stores.labelingUiStore.suggestionEnabled ? 'tbtn-l1 active' : 'tbtn-l3'}`}
|
||||
onClick={() => {
|
||||
stores.labelingUiStore.suggestionEnabled = false;
|
||||
stores.labelingStore.removeAllSuggestions();
|
||||
labelingSuggestionGenerator.removeAllSuggestions();
|
||||
}}
|
||||
>Off</button>
|
||||
</span>
|
||||
<button
|
||||
type='button'
|
||||
className='tbtn tbtn-red'
|
||||
title='Confirm all suggested labels'
|
||||
onClick={() => stores.labelingStore.confirmVisibleSuggestions()}
|
||||
><span className='glyphicon icon-only glyphicon-ok'></span></button>
|
||||
<div className='btn-group' style={{marginLeft: '2pt'}} >
|
||||
<span className='glyphicon icon-only glyphicon-option-vertical clickable' data-toggle='dropdown'></span>
|
||||
<ul
|
||||
className='dropdown-menu options-menu'>
|
||||
<li className='dropdown-header'>Suggestion Confidence Threshold</li>
|
||||
{stores.labelingUiStore.suggestionEnabled ? (
|
||||
<div>
|
||||
<ConfidenceSlider
|
||||
viewWidth={400}
|
||||
viewHeight={50}
|
||||
min={0}
|
||||
max={1}
|
||||
value={Math.pow(stores.labelingUiStore.suggestionConfidenceThreshold, 0.3)}
|
||||
histogram={stores.labelingUiStore.suggestionConfidenceHistogram}
|
||||
onChange={this.setConfidenceThreshold}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<li role='separator' className='divider'></li>
|
||||
<li className='dropdown-header'>Suggestion Logic</li>
|
||||
<li className='option-item'
|
||||
role='button'
|
||||
onClick={this.setSuggestionLogicThunk[LabelingSuggestionLogicType.CURRENT_VIEW]}>
|
||||
<span className='glyphicon icon-only glyphicon-ok'
|
||||
style={{visibility: `${logicClassName(LabelingSuggestionLogicType.CURRENT_VIEW)}`,
|
||||
marginRight: '5pt'}}/>
|
||||
Suggest Anywhere, Manual Confirm
|
||||
</li>
|
||||
<li className='option-item'
|
||||
role='button'
|
||||
onClick={this.setSuggestionLogicThunk[LabelingSuggestionLogicType.FORWARD]}>
|
||||
<span className='glyphicon icon-only glyphicon-ok'
|
||||
style={{visibility: `${logicClassName(LabelingSuggestionLogicType.FORWARD)}`,
|
||||
marginRight: '5pt'}}/>
|
||||
Suggest Forward, Manual Confirm
|
||||
</li>
|
||||
<li className='option-item'
|
||||
role='button'
|
||||
onClick={this.setSuggestionLogicThunk[LabelingSuggestionLogicType.FORWARD_CONFIRM]}>
|
||||
<span className='glyphicon icon-only glyphicon-ok'
|
||||
style={{visibility: `${logicClassName(LabelingSuggestionLogicType.FORWARD_CONFIRM)}`,
|
||||
marginRight: '5pt'}}/>
|
||||
Suggest Forward, Auto Confirm Inbetween
|
||||
</li>
|
||||
<li className='option-item'
|
||||
role='button'
|
||||
onClick={this.setSuggestionLogicThunk[LabelingSuggestionLogicType.FORWARD_REJECT]}>
|
||||
<span className='glyphicon icon-only glyphicon-ok'
|
||||
style={{visibility: `${logicClassName(LabelingSuggestionLogicType.FORWARD_REJECT)}`,
|
||||
marginRight: '5pt'}}/>
|
||||
Suggest Forward, Auto Reject Inbetween
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { observable } from 'mobx';
|
||||
|
||||
// The set of prototypes to use for the DTW classifier
|
||||
|
||||
export interface ReferenceLabel {
|
||||
series: number[][];
|
||||
variance: number;
|
||||
className: string;
|
||||
}
|
||||
|
||||
export class DtwModelStore {
|
||||
@observable public prototypes: ReferenceLabel[];
|
||||
@observable public prototypeSampleRate: number;
|
||||
|
||||
constructor() {
|
||||
this.prototypes = [];
|
||||
this.prototypeSampleRate = 30;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ export class LabelingStore {
|
|||
private _labelsIndex: TimeRangeIndex<Label>;
|
||||
private _windowLabelsIndex: TimeRangeIndex<Label>;
|
||||
private _windowAccuracyLabelsIndex: TimeRangeIndex<Label>;
|
||||
private _suggestedLabelsIndex: TimeRangeIndex<Label>;
|
||||
|
||||
private _windowLabelIndexHistory: TimeRangeIndex<Label>[];
|
||||
@observable public classes: string[];
|
||||
|
@ -38,7 +37,6 @@ export class LabelingStore {
|
|||
constructor(alignmentStore: AlignmentStore) {
|
||||
this._labelsIndex = new TimeRangeIndex<Label>();
|
||||
this._windowLabelsIndex = new TimeRangeIndex<Label>();
|
||||
this._suggestedLabelsIndex = new TimeRangeIndex<Label>();
|
||||
this._windowAccuracyLabelsIndex = new TimeRangeIndex<Label>();
|
||||
|
||||
this._windowLabelIndexHistory = [];
|
||||
|
@ -54,9 +52,7 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
public getLabelsInRange(timeRange: TimeRange): Label[] {
|
||||
return mergeTimeRangeArrays(
|
||||
this._labelsIndex.getRangesInRange(timeRange),
|
||||
this._suggestedLabelsIndex.getRangesInRange(timeRange));
|
||||
return this._labelsIndex.getRangesInRange(timeRange);
|
||||
}
|
||||
|
||||
|
||||
|
@ -70,9 +66,6 @@ export class LabelingStore {
|
|||
if (this._labelsIndex.has(label)) {
|
||||
this._labelsIndex.remove(label);
|
||||
}
|
||||
if (this._suggestedLabelsIndex.has(label)) {
|
||||
label.state = LabelConfirmationState.REJECTED;
|
||||
}
|
||||
}
|
||||
|
||||
@action public updateLabel(label: Label, newLabel: PartialLabel): void {
|
||||
|
@ -85,89 +78,11 @@ export class LabelingStore {
|
|||
if (newLabel.suggestionConfidence !== undefined) { label.suggestionConfidence = newLabel.suggestionConfidence; }
|
||||
if (newLabel.suggestionGeneration !== undefined) { label.suggestionGeneration = newLabel.suggestionGeneration; }
|
||||
|
||||
// Turn a suggestion into a label, criteria: BOTH ends confirmed.
|
||||
if (this._suggestedLabelsIndex.has(label)) {
|
||||
if (label.state === LabelConfirmationState.CONFIRMED_BOTH) {
|
||||
this._suggestedLabelsIndex.remove(label);
|
||||
this._labelsIndex.add(label);
|
||||
|
||||
const decision = labelingUiStore.suggestionLogic.onConfirmLabels({
|
||||
labelsConfirmed: [label],
|
||||
currentSuggestions: this._suggestedLabelsIndex
|
||||
});
|
||||
if (decision) {
|
||||
if (decision.confirmLabels) {
|
||||
decision.confirmLabels.forEach(lab => {
|
||||
lab.state = LabelConfirmationState.CONFIRMED_BOTH;
|
||||
this._suggestedLabelsIndex.remove(lab);
|
||||
this._labelsIndex.add(lab);
|
||||
});
|
||||
}
|
||||
if (decision.deleteLabels) {
|
||||
decision.deleteLabels.forEach(lab => {
|
||||
this._suggestedLabelsIndex.remove(lab);
|
||||
});
|
||||
}
|
||||
if (decision.rejectLabels) {
|
||||
decision.rejectLabels.forEach(lab => {
|
||||
lab.state = LabelConfirmationState.REJECTED;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action public suggestLabels(
|
||||
labels: Label[],
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
timestampCompleted: number,
|
||||
generation: number): void {
|
||||
|
||||
const lastLabelTimestampEnd = timestampCompleted;
|
||||
const thisGeneration = generation;
|
||||
let labelsChanged = false;
|
||||
const selectedLabels = labelingUiStore.selectedLabels;
|
||||
|
||||
if (lastLabelTimestampEnd) {
|
||||
// get labels that are earlier than the current suggestion timestamp.
|
||||
const refreshDecision = labelingUiStore.suggestionLogic.refreshSuggestions({
|
||||
suggestionProgress: {
|
||||
timestampStart: timestampStart,
|
||||
timestampEnd: timestampEnd,
|
||||
timestampCompleted: timestampCompleted
|
||||
},
|
||||
currentSuggestions: this._suggestedLabelsIndex
|
||||
});
|
||||
let labelsToRemove = refreshDecision.deleteLabels;
|
||||
// Only remove unconfirmed suggestions of older generations.
|
||||
labelsToRemove = labelsToRemove.filter(label =>
|
||||
label.suggestionGeneration < thisGeneration && label.state === LabelConfirmationState.UNCONFIRMED);
|
||||
// Don't remove selected suggestions.
|
||||
labelsToRemove = labelsToRemove.filter(label => !selectedLabels.has(label));
|
||||
// Remove them.
|
||||
labelsToRemove.forEach(label => this._suggestedLabelsIndex.remove(label));
|
||||
labelsChanged = labelsChanged || labelsToRemove.length > 0;
|
||||
}
|
||||
labels.forEach(label => {
|
||||
const margin = (label.timestampEnd - label.timestampStart) * 0.15;
|
||||
if (this._labelsIndex.getRangesWithinMargin(label, margin).length === 0 &&
|
||||
this._suggestedLabelsIndex.getRangesWithinMargin(label, margin).length === 0) {
|
||||
this._suggestedLabelsIndex.add(label);
|
||||
labelsChanged = labelsChanged || true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action public removeAllSuggestions(): void {
|
||||
this._suggestedLabelsIndex.clear();
|
||||
}
|
||||
|
||||
@action public removeAllLabels(): void {
|
||||
projectStore.recordLabelingSnapshot();
|
||||
this._labelsIndex.clear();
|
||||
this._suggestedLabelsIndex.clear();
|
||||
}
|
||||
|
||||
@action public addClass(className: string): void {
|
||||
|
@ -209,13 +124,7 @@ export class LabelingStore {
|
|||
renamed = true;
|
||||
}
|
||||
});
|
||||
this._suggestedLabelsIndex.forEach(label => {
|
||||
if (label.className === oldClassName) {
|
||||
label.className = newClassName;
|
||||
renamed = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const index = this.classes.indexOf(oldClassName);
|
||||
if (index >= 0) {
|
||||
this.classes[index] = newClassName;
|
||||
|
@ -225,19 +134,6 @@ export class LabelingStore {
|
|||
}
|
||||
}
|
||||
|
||||
@action public confirmVisibleSuggestions(): void {
|
||||
projectStore.recordLabelingSnapshot();
|
||||
// Get visible suggestions.
|
||||
let visibleSuggestions = this._suggestedLabelsIndex.getRangesInRange(
|
||||
projectUiStore.referenceTrackTimeRange);
|
||||
// Filter out rejected suggestions.
|
||||
visibleSuggestions = visibleSuggestions.filter(x => x.state !== LabelConfirmationState.REJECTED);
|
||||
visibleSuggestions.forEach(label => {
|
||||
this.updateLabel(label, {state: LabelConfirmationState.CONFIRMED_BOTH});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@action private updateColors(): void {
|
||||
// Update class colors, try to keep original colors.
|
||||
this.classColors = this.classes.map(() => null);
|
||||
|
@ -354,7 +250,6 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
this._labelsIndex.clear();
|
||||
this._suggestedLabelsIndex.clear();
|
||||
for (const label of state.labels) {
|
||||
this._labelsIndex.add(label);
|
||||
}
|
||||
|
@ -366,7 +261,6 @@ export class LabelingStore {
|
|||
|
||||
public reset(): void {
|
||||
this._labelsIndex.clear();
|
||||
this._suggestedLabelsIndex.clear();
|
||||
this.classes = ['IGNORE', 'Positive'];
|
||||
this.updateColors();
|
||||
const nonIgnoreClases = this.classes.filter(x => x !== 'IGNORE');
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { getLabelingSuggestionLogic, LabelingSuggestionLogic, LabelingSuggestionLogicType } from '../suggestion/LabelingSuggestionLogic';
|
||||
import { Label, SignalsViewMode, TimeRange } from './dataStructures/labeling';
|
||||
import { ObservableSet } from './dataStructures/ObservableSet';
|
||||
import { LabelingStore } from './LabelingStore';
|
||||
|
@ -18,9 +17,7 @@ export class LabelingUiStore {
|
|||
|
||||
@observable public suggestionEnabled: boolean;
|
||||
@observable public suggestionConfidenceThreshold: number;
|
||||
@observable public suggestionLogic: LabelingSuggestionLogic;
|
||||
|
||||
|
||||
|
||||
constructor(labelingStore: LabelingStore) {
|
||||
|
||||
this.signalsViewMode = SignalsViewMode.TIMESERIES;
|
||||
|
@ -29,7 +26,6 @@ export class LabelingUiStore {
|
|||
lab => lab.className + ':' + lab.timestampStart + '-' + lab.timestampEnd);
|
||||
|
||||
this.suggestionEnabled = true;
|
||||
this.suggestionLogic = getLabelingSuggestionLogic(LabelingSuggestionLogicType.CURRENT_VIEW);
|
||||
this.suggestionConfidenceThreshold = 0.2;
|
||||
this.suggestionConfidenceHistogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
|
@ -86,10 +82,6 @@ export class LabelingUiStore {
|
|||
}
|
||||
}
|
||||
|
||||
@action public setSuggestionLogic(logic: LabelingSuggestionLogicType): void {
|
||||
this.suggestionLogic = getLabelingSuggestionLogic(logic);
|
||||
}
|
||||
|
||||
public getLabelsInRange(timeRange: TimeRange): Label[] {
|
||||
const labels = labelingStore.getLabelsInRange(timeRange);
|
||||
return labels.filter(l => !this.selectedLabels.has(l)).concat(
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import {LabelingSuggestionGenerator} from '../suggestion/LabelingSuggestionGenerator';
|
||||
import {AlignmentStore} from './AlignmentStore';
|
||||
import {DtwModelStore} from './DtwModelStore';
|
||||
import {LabelingStore} from './LabelingStore';
|
||||
import {LabelingUiStore} from './LabelingUiStore';
|
||||
import {ProjectStore} from './ProjectStore';
|
||||
|
@ -11,6 +9,3 @@ export const alignmentStore = new AlignmentStore();
|
|||
export const projectUiStore = new ProjectUiStore(projectStore);
|
||||
export const labelingStore = new LabelingStore(alignmentStore);
|
||||
export const labelingUiStore = new LabelingUiStore(labelingStore);
|
||||
export const dtwModelStore = new DtwModelStore();
|
||||
|
||||
export const labelingSuggestionGenerator = new LabelingSuggestionGenerator(labelingStore, labelingUiStore, projectUiStore);
|
||||
|
|
|
@ -1,318 +0,0 @@
|
|||
// Generate DTW code for Arduino and Microbit.
|
||||
import {resampleColumn} from '../stores/dataStructures/sampling';
|
||||
|
||||
export interface ReferenceLabel {
|
||||
series: number[][];
|
||||
variance: number;
|
||||
className: string;
|
||||
}
|
||||
|
||||
|
||||
const templateArduino = `// HOWTO:
|
||||
// Call setupRecognizer(0.2) first.
|
||||
// Then, call String className = recognize(sample);
|
||||
// "NONE" will be returned if nothing is detected.
|
||||
// sample should be a float array, in the same order you put the training data in the tool.
|
||||
|
||||
struct DTWInfo {
|
||||
int dim; // The dimension of the signal.
|
||||
float* prototype; // The data for the prototype.
|
||||
int* s; // Matching array: start points.
|
||||
float* d; // Matching array: distances.
|
||||
int prototypeSize; // The length of the prototype.
|
||||
int t; // Current time in samples.
|
||||
float variance; // The variance.
|
||||
float bestMatchEndsAtTDistance; // The distance of the best match that ends at t.
|
||||
int bestMatchEndsAtTStart; // The start of the best match that ends at t.
|
||||
};
|
||||
|
||||
float DTWDistanceFunction(int dim, float* a, float* b) {
|
||||
int s = 0;
|
||||
for(int i = 0; i < dim; i++) {
|
||||
s += abs(a[i] - b[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void DTWInit(struct DTWInfo* DTW, int dim, float* prototype, int prototypeSize, float variance) {
|
||||
DTW->dim = dim;
|
||||
DTW->prototypeSize = prototypeSize;
|
||||
DTW->prototype = prototype;
|
||||
DTW->d = new float[prototypeSize + 1];
|
||||
DTW->s = new int[prototypeSize + 1];
|
||||
DTW->t = 0;
|
||||
DTW->variance = variance;
|
||||
for(int i = 0; i <= prototypeSize; i++) {
|
||||
DTW->d[i] = 1e10;
|
||||
DTW->s[i] = 0;
|
||||
}
|
||||
DTW->d[0] = 0;
|
||||
}
|
||||
|
||||
void DTWReset(struct DTWInfo* DTW) {
|
||||
for(int i = 0; i <= DTW->prototypeSize; i++) {
|
||||
DTW->d[i] = 1e10;
|
||||
DTW->s[i] = 0;
|
||||
}
|
||||
DTW->d[0] = 0;
|
||||
}
|
||||
|
||||
void DTWFeed(struct DTWInfo* DTW, float* sample) {
|
||||
float* d = DTW->d;
|
||||
int* s = DTW->s;
|
||||
DTW->t += 1;
|
||||
d[0] = 0;
|
||||
s[0] = DTW->t;
|
||||
float dp = d[0];
|
||||
int sp = s[0];
|
||||
for(int i = 1; i <= DTW->prototypeSize; i++) {
|
||||
float dist = DTWDistanceFunction(DTW->dim, DTW->prototype + (i - 1) * DTW->dim, sample);
|
||||
float d_i_minus_1 = d[i - 1]; int s_i_minus_1 = s[i - 1];
|
||||
float d_i_p = d[i]; int s_i_p = s[i];
|
||||
float d_i_p_minus_1 = dp; int s_i_p_minus_1 = sp;
|
||||
dp = d[i];
|
||||
sp = s[i];
|
||||
if(d_i_minus_1 <= d_i_p && d_i_minus_1 <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_minus_1;
|
||||
s[i] = s_i_minus_1;
|
||||
} else if(d_i_p <= d_i_minus_1 && d_i_p <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_p;
|
||||
s[i] = s_i_p;
|
||||
} else {
|
||||
d[i] = dist + d_i_p_minus_1;
|
||||
s[i] = s_i_p_minus_1;
|
||||
}
|
||||
}
|
||||
DTW->bestMatchEndsAtTDistance = d[DTW->prototypeSize] / DTW->variance;
|
||||
DTW->bestMatchEndsAtTStart = s[DTW->prototypeSize];
|
||||
if(DTW->t - DTW->bestMatchEndsAtTStart > DTW->prototypeSize * 0.8 && DTW->t - DTW->bestMatchEndsAtTStart < DTW->prototypeSize * 1.2) {
|
||||
} else DTW->bestMatchEndsAtTDistance = 1e10;
|
||||
}
|
||||
|
||||
%%GLOBAL%%
|
||||
|
||||
void setupRecognizer(float confidenceThreshold) {
|
||||
float threshold = sqrt(-2 * log(confidenceThreshold));
|
||||
%%SETUP%%
|
||||
}
|
||||
|
||||
void resetRecognizer() {
|
||||
%%RESET%%
|
||||
}
|
||||
|
||||
String recognize(float* sample) {
|
||||
%%MATCH%%
|
||||
String minClass = "NONE";
|
||||
float minClassScore = 1e10;
|
||||
%%MATCH_COMPARISON%%
|
||||
return minClass;
|
||||
}
|
||||
`;
|
||||
|
||||
const templateMicrobit = `// HOWTO:
|
||||
// Call setupRecognizer() first.
|
||||
// Then, call let className = recognize([ input.acceleration(Dimension.X), input.acceleration(Dimension.Y), input.acceleration(Dimension.Z) ]);
|
||||
// "NONE" will be returned if nothing is detected.
|
||||
// An example is at the end of this file.
|
||||
|
||||
// IMPORTANT: All number in microbit are integers.
|
||||
function DTWDistanceFunction(dim: number, a: number[], astart: number, b: number[]) {
|
||||
let s = 0;
|
||||
for (let i = 0; i < dim; i++) {
|
||||
s += (a[i + astart] - b[i]) * (a[i + astart] - b[i]);
|
||||
}
|
||||
return Math.sqrt(s);
|
||||
}
|
||||
class DTWInfo {
|
||||
public dim: number;
|
||||
public prototype: number[];
|
||||
public s: number[];
|
||||
public d: number[];
|
||||
public prototypeSize: number;
|
||||
public t: number;
|
||||
public bestMatchEndsAtTDistance: number;
|
||||
// The distance of the best match that ends at t.
|
||||
public variance: number;
|
||||
// The variance computed.
|
||||
public bestMatchEndsAtTStart: number;
|
||||
// The start of the best match that ends at t.
|
||||
constructor(dim: number, prototype: number[], prototypeSize: number, variance: number) {
|
||||
this.dim = dim;
|
||||
this.prototypeSize = prototypeSize;
|
||||
this.prototype = prototype;
|
||||
this.d = %%ARRAY_INITIALIZATION%%;
|
||||
this.s = %%ARRAY_INITIALIZATION%%;
|
||||
this.t = 0;
|
||||
this.variance = variance;
|
||||
for (let i = 0; i <= prototypeSize; i++) {
|
||||
this.d[i] = 500000000;
|
||||
this.s[i] = 0;
|
||||
}
|
||||
this.d[0] = 0;
|
||||
}
|
||||
public feed(sample: number[]) {
|
||||
let d = this.d;
|
||||
let s = this.s;
|
||||
this.t += 1;
|
||||
d[0] = 0;
|
||||
s[0] = this.t;
|
||||
let dp = d[0];
|
||||
let sp = s[0];
|
||||
for (let i = 1; i <= this.prototypeSize; i++) {
|
||||
let dist = DTWDistanceFunction(this.dim, this.prototype, (i - 1) * this.dim, sample);
|
||||
let d_i_minus_1 = d[i - 1];
|
||||
let s_i_minus_1 = s[i - 1];
|
||||
let d_i_p = d[i];
|
||||
let s_i_p = s[i];
|
||||
let d_i_p_minus_1 = dp;
|
||||
let s_i_p_minus_1 = sp;
|
||||
dp = d[i];
|
||||
sp = s[i];
|
||||
if (d_i_minus_1 <= d_i_p && d_i_minus_1 <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_minus_1;
|
||||
s[i] = s_i_minus_1;
|
||||
} else if (d_i_p <= d_i_minus_1 && d_i_p <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_p;
|
||||
s[i] = s_i_p;
|
||||
} else {
|
||||
d[i] = dist + d_i_p_minus_1;
|
||||
s[i] = s_i_p_minus_1;
|
||||
}
|
||||
}
|
||||
this.bestMatchEndsAtTDistance = d[this.prototypeSize] / this.variance;
|
||||
this.bestMatchEndsAtTStart = s[this.prototypeSize];
|
||||
}
|
||||
}
|
||||
|
||||
%%GLOBAL%%
|
||||
|
||||
function setupRecognizer() {
|
||||
let threshold = 1794; // confidenceThreshold = 0.2
|
||||
%%SETUP%%
|
||||
}
|
||||
|
||||
function recognize(sample: number[]): string {
|
||||
%%MATCH%%
|
||||
let minClass = "NONE";
|
||||
let minClassScore = 5000000;
|
||||
%%MATCH_COMPARISON%%
|
||||
return minClass;
|
||||
}
|
||||
|
||||
// Example application code:
|
||||
setupRecognizer();
|
||||
basic.forever(() => {
|
||||
// Recognize the gesture.
|
||||
let className = recognize([input.acceleration(Dimension.X), input.acceleration(Dimension.Y), input.acceleration(Dimension.Z)]);
|
||||
// Send the result over serial.
|
||||
serial.writeLine(className);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
//Deprecated: marked for removal
|
||||
export function generateArduinoCodeForDtwModel(sampleRate: number, arduinoSampleRate: number, references: ReferenceLabel[]): string {
|
||||
let index = 1;
|
||||
const lines = [];
|
||||
const setupLines = [];
|
||||
const matchLines = [];
|
||||
const resetLines = [];
|
||||
const matchComparisonLines = [];
|
||||
for (const ref of references) {
|
||||
const dim = ref.series[0].length;
|
||||
const name = 'classPrototype' + index.toString();
|
||||
const length = Math.ceil(ref.series.length / sampleRate * arduinoSampleRate);
|
||||
const dimensions: Float32Array[] = [];
|
||||
for (let i = 0; i < dim; i++) {
|
||||
dimensions.push(resampleColumn(ref.series.map(x => x[i]), 0, 1, 0, 1, length));
|
||||
}
|
||||
const newSamples = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
for (let j = 0; j < dim; j++) {
|
||||
newSamples.push(dimensions[j][i]);
|
||||
}
|
||||
}
|
||||
lines.push(`float ${name}_samples[] = { ${newSamples.join(', ')} };`);
|
||||
lines.push(`int ${name}_dim = ${dim};`);
|
||||
lines.push(`int ${name}_length = ${length};`);
|
||||
lines.push(`float ${name}_variance = ${ref.variance / ref.series.length * length};`);
|
||||
lines.push(`struct DTWInfo ${name}_DTW;`);
|
||||
|
||||
setupLines.push(`DTWInit(&${name}_DTW, ${dim}, ${name}_samples, ${name}_length, ${name}_variance * threshold);`);
|
||||
resetLines.push(`DTWReset(&${name}_DTW);`);
|
||||
|
||||
matchLines.push(`DTWFeed(&${name}_DTW, sample);`);
|
||||
matchComparisonLines.push(`if(${name}_DTW.bestMatchEndsAtTDistance < 1 && minClassScore > ${name}_DTW.bestMatchEndsAtTDistance) {`);
|
||||
matchComparisonLines.push(` minClass = '${ref.className}';`);
|
||||
matchComparisonLines.push(` minClassScore = ${name}_DTW.bestMatchEndsAtTDistance;`);
|
||||
matchComparisonLines.push('}');
|
||||
|
||||
index += 1;
|
||||
}
|
||||
matchComparisonLines.push('if(minClassScore < 1) {');
|
||||
matchComparisonLines.push(' resetRecognizer();');
|
||||
matchComparisonLines.push('}');
|
||||
|
||||
return templateArduino
|
||||
.replace('%%GLOBAL%%', lines.join('\n'))
|
||||
.replace('%%SETUP%%', setupLines.map(x => ' ' + x).join('\n'))
|
||||
.replace('%%RESET%%', resetLines.map(x => ' ' + x).join('\n'))
|
||||
.replace('%%MATCH%%', matchLines.map(x => ' ' + x).join('\n'))
|
||||
.replace('%%MATCH_COMPARISON%%', matchComparisonLines.map(x => ' ' + x).join('\n'))
|
||||
;
|
||||
}
|
||||
|
||||
export function generateMicrobitCodeForDtwModel(sampleRate: number, arduinoSampleRate: number, references: ReferenceLabel[]): string {
|
||||
let index = 1;
|
||||
const lines = [];
|
||||
const setupLines = [];
|
||||
const matchLines = [];
|
||||
const matchComparisonLines = [];
|
||||
let maxLength = 0;
|
||||
for (const ref of references) {
|
||||
const dim = ref.series[0].length;
|
||||
const name = 'classPrototype' + index.toString();
|
||||
const length = Math.ceil(ref.series.length / sampleRate * arduinoSampleRate);
|
||||
maxLength = Math.max(length, maxLength);
|
||||
const dimensions: Float32Array[] = [];
|
||||
for (let i = 0; i < dim; i++) {
|
||||
dimensions.push(resampleColumn(ref.series.map(x => x[i]), 0, 1, 0, 1, length));
|
||||
}
|
||||
const newSamples = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
for (let j = 0; j < dim; j++) {
|
||||
newSamples.push(dimensions[j][i]);
|
||||
}
|
||||
}
|
||||
lines.push(`let ${name}_samples = [ ${newSamples.map(Math.round).join(', ')} ];`);
|
||||
lines.push(`let ${name}_dim = ${dim};`);
|
||||
lines.push(`let ${name}_length = ${length};`);
|
||||
lines.push(`let ${name}_variance = ${ref.variance / ref.series.length * length};`);
|
||||
lines.push(`let ${name}_DTW: DTWInfo = null;`);
|
||||
|
||||
setupLines.push(`${name}_DTW = new DTWInfo(${dim}, ${name}_samples, ${name}_length, ${name}_variance * threshold / 1000000);`);
|
||||
|
||||
matchLines.push(`${name}_DTW.feed(sample);`);
|
||||
matchComparisonLines.push(
|
||||
`if(${name}_DTW.bestMatchEndsAtTDistance < 1000 && minClassScore > ${name}_DTW.bestMatchEndsAtTDistance) {`);
|
||||
matchComparisonLines.push(` minClass = '${ref.className}';`);
|
||||
matchComparisonLines.push(` minClassScore = ${name}_DTW.bestMatchEndsAtTDistance;`);
|
||||
matchComparisonLines.push('}');
|
||||
|
||||
index += 1;
|
||||
}
|
||||
const initialArray: number[] = [];
|
||||
for (let i = 0; i < maxLength + 1; i++) { initialArray[i] = 0; }
|
||||
|
||||
return templateMicrobit
|
||||
.replace('%%ARRAY_INITIALIZATION%%', '[ ' + initialArray.join(', ') + ' ]')
|
||||
.replace('%%ARRAY_INITIALIZATION%%', '[ ' + initialArray.join(', ') + ' ]')
|
||||
.replace('%%GLOBAL%%', lines.join('\n'))
|
||||
.replace('%%SETUP%%', setupLines.map(x => ' ' + x).join('\n'))
|
||||
.replace('%%MATCH%%', matchLines.map(x => ' ' + x).join('\n'))
|
||||
.replace('%%MATCH_COMPARISON%%', matchComparisonLines.map(x => ' ' + x).join('\n'))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
// Wraps SPRINGDTWSuggestionModelFactory with a WebWorker.
|
||||
// Wraps DtwSuggestionModelBuilder with a WebWorker?
|
||||
|
||||
import { Dataset } from '../stores/dataStructures/dataset';
|
||||
import { Label } from '../stores/dataStructures/labeling';
|
||||
import { LabelingSuggestionModelBuilder } from './LabelingSuggestionEngine';
|
||||
import { LabelingSuggestionCallback, LabelingSuggestionModel } from './LabelingSuggestionModel';
|
||||
import { ModelSpecificMessage, SuggestionWorkerMessage } from './worker/SuggestionWorkerMessage';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
|
||||
|
||||
class WorkerModel extends LabelingSuggestionModel {
|
||||
private _modelID: string;
|
||||
private _parent: DtwSuggestionModelBuilder;
|
||||
private _currentCallbackID: number;
|
||||
private _registeredCallbacks: Map<string, Function>;
|
||||
private _callback2ID: WeakMap<Function, string>;
|
||||
|
||||
constructor(parent: DtwSuggestionModelBuilder, modelID: string) {
|
||||
super();
|
||||
this._modelID = modelID;
|
||||
this._parent = parent;
|
||||
this._currentCallbackID = 1;
|
||||
this._registeredCallbacks = new Map<string, Function>();
|
||||
this._callback2ID = new WeakMap<Function, string>();
|
||||
parent.onModelMessage(modelID, this.onMessage.bind(this));
|
||||
}
|
||||
|
||||
private onMessage(data: any): void {
|
||||
const message = data.message;
|
||||
if (message.kind === 'callback') {
|
||||
const cb = this._registeredCallbacks.get(message.callbackID);
|
||||
if (cb) {
|
||||
cb(message.labels, message.progress, message.completed);
|
||||
}
|
||||
}
|
||||
if (message.kind === 'get-deployment-code-callback') {
|
||||
const cb = this._registeredCallbacks.get(message.callbackID);
|
||||
if (cb) {
|
||||
cb(message.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getDeploymentCode(platform: string, callback: (code: string) => any): void {
|
||||
const callbackID = 'cb' + this._currentCallbackID.toString();
|
||||
this._currentCallbackID += 1;
|
||||
|
||||
this._registeredCallbacks.set(callbackID, callback);
|
||||
this._callback2ID.set(callback, callbackID);
|
||||
|
||||
this._parent.postModelMessage(this._modelID, {
|
||||
kind: 'get-deployment-code',
|
||||
callbackID: callbackID,
|
||||
platform: platform
|
||||
});
|
||||
}
|
||||
|
||||
public computeSuggestion(
|
||||
dataset: Dataset,
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
confidenceThreshold: number,
|
||||
generation: number,
|
||||
callback: LabelingSuggestionCallback): void {
|
||||
|
||||
this._parent.setDataset(dataset);
|
||||
|
||||
const callbackID = 'cb' + this._currentCallbackID.toString();
|
||||
this._currentCallbackID += 1;
|
||||
|
||||
this._registeredCallbacks.set(callbackID, callback);
|
||||
this._callback2ID.set(callback, callbackID);
|
||||
|
||||
this._parent.postModelMessage(this._modelID, {
|
||||
kind: 'compute',
|
||||
callbackID: callbackID,
|
||||
timestampStart: timestampStart,
|
||||
timestampEnd: timestampEnd,
|
||||
confidenceThreshold: confidenceThreshold,
|
||||
generation: generation
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public cancelSuggestion(callback: LabelingSuggestionCallback): void {
|
||||
if (this._callback2ID.has(callback)) {
|
||||
this._parent.postModelMessage(this._modelID, {
|
||||
kind: 'compute.cancel',
|
||||
callbackID: this._callback2ID.get(callback)
|
||||
});
|
||||
this._callback2ID.delete(callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public dispose(): void {
|
||||
this._parent.postModelMessage(this._modelID, {
|
||||
kind: 'dispose'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class DtwSuggestionModelBuilder extends LabelingSuggestionModelBuilder {
|
||||
private _worker: Worker;
|
||||
private _currentDataset: Dataset;
|
||||
private _registeredCallbacks: Map<string, (model: LabelingSuggestionModel, progress: number, error: string) => void>;
|
||||
private _currentCallbackID: number;
|
||||
private _emitter: EventEmitter;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._worker = new Worker('./app/js/suggestion/worker/suggestionWorker.browserified.js');
|
||||
this._emitter = new EventEmitter();
|
||||
this._registeredCallbacks = new Map<string, (model: LabelingSuggestionModel, progress: number, error: string) => void>();
|
||||
|
||||
this._currentDataset = null;
|
||||
this._worker.onmessage = event => {
|
||||
const data = event.data;
|
||||
this._emitter.emit(data.kind, data);
|
||||
};
|
||||
this._currentCallbackID = 1;
|
||||
|
||||
this._emitter.addListener('model.build.callback', data => {
|
||||
const callbackID = data.callbackID;
|
||||
const cb = this._registeredCallbacks.get(callbackID);
|
||||
if (cb) {
|
||||
cb(data.modelID ? new WorkerModel(this, data.modelID) : null, data.progress, data.error);
|
||||
this._registeredCallbacks.delete(callbackID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onModelMessage(modelID: string, callback: (message: any) => void): void {
|
||||
this._emitter.addListener('model.message.' + modelID, callback);
|
||||
}
|
||||
|
||||
private postMessage(message: SuggestionWorkerMessage): void {
|
||||
this._worker.postMessage(message);
|
||||
}
|
||||
|
||||
public postModelMessage(modelID: string, message: ModelSpecificMessage): void {
|
||||
this.postMessage({
|
||||
kind: 'model.message.' + modelID,
|
||||
modelID: modelID,
|
||||
message: message
|
||||
});
|
||||
}
|
||||
|
||||
public setDataset(dataset: Dataset): void {
|
||||
if (dataset !== this._currentDataset) {
|
||||
this.postMessage({
|
||||
kind: 'dataset.set',
|
||||
dataset: dataset.serialize()
|
||||
});
|
||||
this._currentDataset = dataset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public buildModelAsync(
|
||||
dataset: Dataset,
|
||||
labels: Label[],
|
||||
callback: (model: LabelingSuggestionModel, progress: number, error: string) => void): void {
|
||||
|
||||
this.setDataset(dataset);
|
||||
|
||||
const callbackID = 'cb' + this._currentCallbackID.toString();
|
||||
this._currentCallbackID += 1;
|
||||
|
||||
this._registeredCallbacks.set(callbackID, callback);
|
||||
|
||||
this.postMessage({
|
||||
kind: 'model.build',
|
||||
callbackID: callbackID,
|
||||
labels: labels
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import { resampleColumn } from '../stores/dataStructures/sampling';
|
||||
import * as ell from 'emll';
|
||||
|
||||
// Generate DTW code for Arduino and Microbit.
|
||||
|
||||
|
||||
export interface ReferenceLabel {
|
||||
series: number[][];
|
||||
variance: number;
|
||||
className: string;
|
||||
}
|
||||
export function makeVector(l: number[]): ell.DoubleVector;
|
||||
export function makeVector(l: number[][]): ell.DoubleVectorVector;
|
||||
export function makeVector(l: any): any {
|
||||
if (typeof l === 'object' && typeof (l[0]) === 'number') {
|
||||
const result1 = new ell.DoubleVector();
|
||||
for (const x of l) {
|
||||
result1.add(x);
|
||||
}
|
||||
return result1;
|
||||
} else if (typeof l === 'object' && typeof (l[0]) === 'object') {
|
||||
const result2 = new ell.DoubleVectorVector();
|
||||
for (const row of l) {
|
||||
result2.add(makeVector(row));
|
||||
}
|
||||
return result2;
|
||||
}
|
||||
}
|
||||
|
||||
function generateEllPrototypes(
|
||||
sampleRate: number,
|
||||
arduinoSampleRate: number,
|
||||
references: ReferenceLabel[]): ell.PrototypeList {
|
||||
|
||||
let currentIndex = 1;
|
||||
let maxLength = 0;
|
||||
const labels: { [name: string]: number; } = {};
|
||||
const prototypes = new ell.PrototypeList();
|
||||
for (const ref of references) {
|
||||
let label;
|
||||
if (ref.className in labels) {
|
||||
label = labels[ref.className];
|
||||
} else {
|
||||
label = currentIndex;
|
||||
labels[ref.className] = label;
|
||||
currentIndex += 1;
|
||||
}
|
||||
const dim = ref.series[0].length;
|
||||
const length = Math.ceil(ref.series.length / sampleRate * arduinoSampleRate);
|
||||
maxLength = Math.max(length, maxLength);
|
||||
const dimensions: Float32Array[] = [];
|
||||
for (let i = 0; i < dim; i++) {
|
||||
dimensions.push(resampleColumn(ref.series.map(x => x[i]), 0, 1, 0, 1, length));
|
||||
}
|
||||
const newSamples: number[][] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
const newRow = [];
|
||||
for (let j = 0; j < dim; j++) {
|
||||
newRow.push(dimensions[j][i]);
|
||||
}
|
||||
newSamples.push(newRow);
|
||||
}
|
||||
const variance = ref.variance / ref.series.length * length;
|
||||
const prototype = new ell.ELL_LabeledPrototype(label, makeVector(newSamples), variance);
|
||||
prototypes.add(prototype);
|
||||
}
|
||||
return prototypes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function generateEllModel(
|
||||
sampleRate: number,
|
||||
arduinoSampleRate: number,
|
||||
references: ReferenceLabel[],
|
||||
confidenceThreshold: number): ell.ELL_CompiledMap {
|
||||
if (references.length === 0) { throw 'generalEllModel empty references'; }
|
||||
|
||||
const prototypes = generateEllPrototypes(sampleRate, arduinoSampleRate, references);
|
||||
return ell.GenerateMulticlassDTWClassifier(prototypes, confidenceThreshold);
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
// Suggestion model classes and suggestion engine.
|
||||
|
||||
import { Dataset } from '../stores/dataStructures/dataset';
|
||||
import { Label } from '../stores/dataStructures/labeling';
|
||||
import * as stores from '../stores/stores';
|
||||
import { LabelingSuggestionCallback, LabelingSuggestionModel, LabelingSuggestionProgress } from './LabelingSuggestionModel';
|
||||
import { DtwAlgorithm } from './worker/DtwAlgorithm';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
|
||||
export abstract class LabelingSuggestionModelBuilder {
|
||||
public abstract buildModelAsync(
|
||||
dataset: Dataset,
|
||||
labels: Label[],
|
||||
callback: (model: LabelingSuggestionModel, progress: number, error: string) => void): void;
|
||||
}
|
||||
|
||||
|
||||
interface LabelingSuggestionCallbackInfo {
|
||||
callback: LabelingSuggestionCallback;
|
||||
model: LabelingSuggestionModel;
|
||||
parameters: {
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
confidenceThreshold: number,
|
||||
callback: LabelingSuggestionCallback,
|
||||
generation: number
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Manages the life-cycle of suggestion models and run suggestions with the newest built model.
|
||||
export class LabelingSuggestionEngine extends EventEmitter {
|
||||
private _dataset: Dataset;
|
||||
private _labels: Label[];
|
||||
private _modelBuilder: LabelingSuggestionModelBuilder;
|
||||
private _currentModel: LabelingSuggestionModel;
|
||||
private _computingInstances: Map<LabelingSuggestionCallback, LabelingSuggestionCallbackInfo>;
|
||||
private _shouldRebuildModel: boolean;
|
||||
private _isRebuildingModel: boolean;
|
||||
private _onModelBuiltCallbackQueue: ((model: LabelingSuggestionModel) => void)[];
|
||||
|
||||
constructor(modelBuilder: LabelingSuggestionModelBuilder) {
|
||||
super();
|
||||
this._dataset = null;
|
||||
this._labels = [];
|
||||
this._modelBuilder = modelBuilder;
|
||||
this._currentModel = null;
|
||||
this._computingInstances = new Map<LabelingSuggestionCallback, LabelingSuggestionCallbackInfo>();
|
||||
this._shouldRebuildModel = true;
|
||||
this._isRebuildingModel = false;
|
||||
this._onModelBuiltCallbackQueue = [];
|
||||
}
|
||||
|
||||
public setDataset(dataset: Dataset): void {
|
||||
this._dataset = dataset;
|
||||
this._shouldRebuildModel = true;
|
||||
this.rebuildModel();
|
||||
}
|
||||
|
||||
public setLabels(labels: Label[]): void {
|
||||
this._labels = labels;
|
||||
this._shouldRebuildModel = true;
|
||||
this.rebuildModel();
|
||||
}
|
||||
|
||||
public setDatasetAndLabels(dataset: Dataset, labels: Label[]): void {
|
||||
this._dataset = dataset;
|
||||
this._labels = labels;
|
||||
this._shouldRebuildModel = true;
|
||||
this.rebuildModel();
|
||||
}
|
||||
|
||||
public getDeploymentCode(platform: string, callback: (code: string) => any): void {
|
||||
if (this._currentModel) {
|
||||
this._currentModel.getDeploymentCode(platform, callback);
|
||||
} else {
|
||||
callback('// Model unavailabel right now. Please go to labeling and turn on suggestions.');
|
||||
}
|
||||
}
|
||||
|
||||
private storeModel(dataset: Dataset, labels: Label[]): void {
|
||||
const maxDuration = labels
|
||||
.map(label => label.timestampEnd - label.timestampStart)
|
||||
.reduce((a, b) => Math.max(a, b), 0);
|
||||
const sampleRate = 100 / maxDuration; // / referenceDuration;
|
||||
stores.dtwModelStore.prototypes = DtwAlgorithm.getReferenceLabels(dataset, labels);
|
||||
stores.dtwModelStore.prototypeSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
private rebuildModel(): void {
|
||||
if (this._shouldRebuildModel && this._dataset && !this._isRebuildingModel) {
|
||||
this._isRebuildingModel = true;
|
||||
this._shouldRebuildModel = false;
|
||||
|
||||
this.storeModel(this._dataset, this._labels);
|
||||
|
||||
this._modelBuilder.buildModelAsync(
|
||||
this._dataset, this._labels,
|
||||
(model, progress, error) => {
|
||||
if (model) {
|
||||
const restartInfos: LabelingSuggestionCallbackInfo[] = [];
|
||||
if (this._currentModel && this._currentModel !== model) {
|
||||
this._computingInstances.forEach((info, callback) => {
|
||||
this._currentModel.cancelSuggestion(callback);
|
||||
restartInfos.push(info);
|
||||
});
|
||||
this._currentModel.dispose();
|
||||
}
|
||||
this._currentModel = model;
|
||||
this._isRebuildingModel = false;
|
||||
|
||||
this._onModelBuiltCallbackQueue.forEach(callback => callback(model));
|
||||
this._onModelBuiltCallbackQueue = [];
|
||||
|
||||
// Restart any existing calculation.
|
||||
restartInfos.forEach(info => {
|
||||
this.computeSuggestion(
|
||||
info.parameters.timestampStart,
|
||||
info.parameters.timestampEnd,
|
||||
info.parameters.confidenceThreshold,
|
||||
info.parameters.generation,
|
||||
info.parameters.callback);
|
||||
});
|
||||
|
||||
this.rebuildModel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Compute suggestions in the background.
|
||||
// Calling computeSuggestion should cancel the one currently running.
|
||||
// Callback will be called multiple times, the last one should have completed set to true OR error not null.
|
||||
public computeSuggestion(
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
confidenceThreshold: number,
|
||||
generation: number,
|
||||
callback: LabelingSuggestionCallback): void {
|
||||
|
||||
const cbProxy: LabelingSuggestionCallback = (labels: Label[], progress: LabelingSuggestionProgress, completed: boolean) => {
|
||||
if (completed) {
|
||||
this._computingInstances.delete(callback);
|
||||
}
|
||||
callback(labels, progress, completed);
|
||||
};
|
||||
const cbInfo = {
|
||||
callback: cbProxy,
|
||||
parameters: {
|
||||
timestampStart: timestampStart,
|
||||
timestampEnd: timestampEnd,
|
||||
confidenceThreshold: confidenceThreshold,
|
||||
callback: callback,
|
||||
generation: generation
|
||||
},
|
||||
model: this._currentModel
|
||||
};
|
||||
this._computingInstances.set(callback, cbInfo);
|
||||
|
||||
if (!this._currentModel) {
|
||||
this._onModelBuiltCallbackQueue.push(model => {
|
||||
if (this._computingInstances.has(callback)) {
|
||||
cbInfo.model = model;
|
||||
model.computeSuggestion(this._dataset, timestampStart, timestampEnd, confidenceThreshold, generation, cbProxy);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._currentModel.computeSuggestion(this._dataset, timestampStart, timestampEnd, confidenceThreshold, generation, cbProxy);
|
||||
}
|
||||
}
|
||||
|
||||
public cancelSuggestion(callback: LabelingSuggestionCallback): void {
|
||||
if (this._computingInstances.has(callback)) {
|
||||
const info = this._computingInstances.get(callback);
|
||||
this._computingInstances.delete(callback);
|
||||
if (info.model) { info.model.cancelSuggestion(info.callback); }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
import { computeDimensionsMipmapLevels } from '../components/common/Mipmap';
|
||||
import { SensorTimeSeries } from '../stores/dataStructures/dataset';
|
||||
import { Label, LabelConfirmationState } from '../stores/dataStructures/labeling';
|
||||
import { LabelingStore } from '../stores/LabelingStore';
|
||||
import { LabelingUiStore } from '../stores/LabelingUiStore';
|
||||
import { ProjectUiStore } from '../stores/ProjectUiStore';
|
||||
import { labelingStore, labelingUiStore, projectUiStore } from '../stores/stores';
|
||||
import { ArrayThrottler } from '../stores/utils';
|
||||
import { pelt } from '../suggestion/algorithms/pelt';
|
||||
import { DtwSuggestionModelBuilder } from '../suggestion/DtwSuggestionModelBuilder';
|
||||
import { LabelingSuggestionEngine } from '../suggestion/LabelingSuggestionEngine';
|
||||
import { LabelingSuggestionCallback, LabelingSuggestionProgress } from '../suggestion/LabelingSuggestionModel';
|
||||
import { EventEmitter } from 'events';
|
||||
import { action, autorun, observable, reaction, runInAction } from 'mobx';
|
||||
|
||||
|
||||
function delayAction(name: string, millisec: number, fun: () => void): NodeJS.Timer {
|
||||
return setTimeout(() => runInAction(fun), millisec);
|
||||
}
|
||||
|
||||
|
||||
// This object is not exactly a store - it doesn't listen to actions, but listen to store updates and dispatch actions.
|
||||
export class LabelingSuggestionGenerator {
|
||||
|
||||
private _engine: LabelingSuggestionEngine;
|
||||
private _throttler: ArrayThrottler<Label, number[]>;
|
||||
private _generation: number;
|
||||
private _currentSuggestionCallback: LabelingSuggestionCallback;
|
||||
|
||||
constructor(labelingStore: LabelingStore, labelingUiStore: LabelingUiStore, alignmentLabelingUiStore: ProjectUiStore) {
|
||||
this._engine = new LabelingSuggestionEngine(new DtwSuggestionModelBuilder());
|
||||
|
||||
// Controls the speed to add suggestions to the label array.
|
||||
this._throttler = new ArrayThrottler<Label, number[]>(100, this.addSuggestions.bind(this));
|
||||
|
||||
reaction(
|
||||
() => observable([labelingStore.alignedDataset, labelingStore.labels]),
|
||||
() => this.onLabelsChanged(),
|
||||
{ name: 'LabelingSuggestionGenerator.onLabelsChanged' });
|
||||
reaction(
|
||||
() => alignmentLabelingUiStore.referenceTrackPanZoom,
|
||||
() => this.runSuggestionsZoomChanged(),
|
||||
{ name: 'LabelingSuggestionGenerator.runSuggestionsZoomChanged' });
|
||||
reaction(
|
||||
() => observable([
|
||||
labelingUiStore.suggestionConfidenceThreshold,
|
||||
labelingUiStore.suggestionEnabled,
|
||||
labelingUiStore.suggestionLogic
|
||||
]),
|
||||
() => this.scheduleRunSuggestions(),
|
||||
{ name: 'LabelingSuggestionGenerator.scheduleRunSuggestions' });
|
||||
|
||||
}
|
||||
|
||||
@action public removeAllSuggestions(): void {
|
||||
delayAction('removeAllSuggestions delay', 1, () => {
|
||||
this._engine.cancelSuggestion(this._currentSuggestionCallback);
|
||||
labelingUiStore.setSuggestionProgress(false, null, null, null);
|
||||
});
|
||||
}
|
||||
|
||||
private _currentModelStatus: string = 'IDLE';
|
||||
|
||||
public get modelStatus(): string {
|
||||
return this._currentModelStatus;
|
||||
}
|
||||
|
||||
// Get the deployment code for the current model.
|
||||
public getDeploymentCode(platform: string, callback: (code: string) => any): void {
|
||||
this._engine.getDeploymentCode(platform, callback);
|
||||
}
|
||||
|
||||
@action private onLabelsChanged(): void {
|
||||
this._engine.setDataset(labelingStore.alignedDataset);
|
||||
this._engine.setLabels(labelingStore.labels);
|
||||
this.scheduleRunSuggestions();
|
||||
}
|
||||
|
||||
private _runSuggestionsTimeout: NodeJS.Timer;
|
||||
|
||||
@action private scheduleRunSuggestions(): void {
|
||||
setImmediate(() => runInAction('scheduleRunSuggestions', () => {
|
||||
// Cancel current suggestion if running.
|
||||
this._engine.cancelSuggestion(this._currentSuggestionCallback);
|
||||
labelingUiStore.setSuggestionProgress(false, null, null, null);
|
||||
if (this._runSuggestionsTimeout) { clearTimeout(this._runSuggestionsTimeout); }
|
||||
|
||||
if (labelingUiStore.suggestionEnabled) {
|
||||
this._runSuggestionsTimeout = delayAction('scheduleRunSuggestions delay', 100, () => {
|
||||
this.doRunSuggestions();
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@action private runSuggestionsZoomChanged(): void {
|
||||
if (labelingUiStore.suggestionLogic.shouldTriggerSuggestionUpdate({ didViewportChange: true })) {
|
||||
this.scheduleRunSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
private doRunSuggestions(): void {
|
||||
// If no dataset, do nothing.
|
||||
const dataset = labelingStore.alignedDataset;
|
||||
if (!dataset) { return; }
|
||||
|
||||
// Cancel the current suggestion.
|
||||
this._engine.cancelSuggestion(this._currentSuggestionCallback);
|
||||
labelingUiStore.setSuggestionProgress(false, null, null, null);
|
||||
|
||||
// Get a new generation number.
|
||||
this._generation = new Date().getTime();
|
||||
|
||||
// Create a new suggestion callback (bind will return a new one).
|
||||
this._currentSuggestionCallback = this.onSuggestion.bind(this);
|
||||
|
||||
// Suggestion range from suggestion logic.
|
||||
const suggestionRange = labelingUiStore.suggestionLogic.calculateSuggestionRange({
|
||||
dataset: null /* TODO */,
|
||||
labels: labelingStore.labels,
|
||||
detailedViewRange: projectUiStore.referenceTrackTimeRange
|
||||
});
|
||||
|
||||
// Start computing suggestions.
|
||||
this._engine.computeSuggestion(
|
||||
suggestionRange.timestampStart,
|
||||
suggestionRange.timestampEnd,
|
||||
labelingUiStore.suggestionConfidenceThreshold,
|
||||
this._generation,
|
||||
this._currentSuggestionCallback
|
||||
);
|
||||
}
|
||||
|
||||
private onSuggestion(labels: Label[], progress: LabelingSuggestionProgress, completed: boolean): void {
|
||||
runInAction('onSuggestion', () => {
|
||||
// Throttle suggestions so we don't update the view too often.
|
||||
this._throttler.setStationary([progress.timestampStart, progress.timestampEnd, progress.timestampCompleted, this._generation]);
|
||||
this._throttler.addItems(labels);
|
||||
labelingUiStore.setSuggestionProgress(
|
||||
!completed, progress.timestampStart, progress.timestampCompleted, progress.timestampEnd, progress.confidenceHistogram);
|
||||
});
|
||||
}
|
||||
|
||||
private addSuggestions(labels: Label[], stat: [number, number, number, number]): void {
|
||||
// On add suggestions.
|
||||
runInAction('addSuggestions', () => labelingStore.suggestLabels(labels, stat[0], stat[1], stat[2], stat[3]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export class LabelingChangePointSuggestionGenerator extends EventEmitter {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.runSuggestions = this.runSuggestions.bind(this);
|
||||
|
||||
autorun('LabelingChangePointSuggestionGenerator.runSuggestions', () => this.runSuggestions());
|
||||
}
|
||||
|
||||
@action private runSuggestions(): void {
|
||||
const dataset = labelingStore.alignedDataset; // labelingStore.dataset;
|
||||
if (!dataset) { return; }
|
||||
|
||||
// AlignedDataset is of the same sample rate always.
|
||||
|
||||
let data: Float32Array[] = [];
|
||||
for (const ts of dataset.timeSeries) {
|
||||
data = data.concat((ts as SensorTimeSeries).dimensions.map(x => new Float32Array(x)));
|
||||
}
|
||||
|
||||
data = computeDimensionsMipmapLevels(data)[2];
|
||||
|
||||
const n = data[0].length;
|
||||
const sampleRate = (dataset.timestampEnd - dataset.timestampStart) / (n - 1);
|
||||
const pts = pelt(data, 20 * Math.log(n), Math.ceil(3 / sampleRate));
|
||||
const timestamps = pts.map(p => (p / (n - 1)) * (dataset.timestampEnd - dataset.timestampStart) + dataset.timestampStart);
|
||||
//labelingStore.changePoints = timestamps;
|
||||
}
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
// Declares logics to run suggestions.
|
||||
|
||||
import { Dataset } from '../stores/dataStructures/dataset';
|
||||
import { Label, TimeRange } from '../stores/dataStructures/labeling';
|
||||
import { TimeRangeIndex } from '../stores/dataStructures/TimeRangeIndex';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
|
||||
|
||||
|
||||
export enum LabelingSuggestionLogicType {
|
||||
FORWARD, // Suggest from the last label only, on select suggestion, keep previous ones.
|
||||
FORWARD_CONFIRM, // Forward, on select suggestion, confirm all before.
|
||||
FORWARD_REJECT, // Forward, on select suggestion, reject all before.
|
||||
CURRENT_VIEW // Suggest from the current view, towards the end of the dataset.
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface LabelingSuggestionLogic {
|
||||
getType(): LabelingSuggestionLogicType;
|
||||
getDescription(): string;
|
||||
|
||||
// If the event in info happened, should we recompute the suggestions?
|
||||
shouldTriggerSuggestionUpdate(info: {
|
||||
didViewportChange: boolean
|
||||
}): boolean;
|
||||
|
||||
// On what time range should we compute suggestions?
|
||||
calculateSuggestionRange(info: {
|
||||
dataset: Dataset,
|
||||
labels: Label[],
|
||||
detailedViewRange: TimeRange
|
||||
}): TimeRange;
|
||||
|
||||
// During the suggestion progress, how should we refresh existing suggestions.
|
||||
// By default, we update matching suggestions.
|
||||
refreshSuggestions(info: {
|
||||
suggestionProgress: {
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
timestampCompleted: number
|
||||
},
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
deleteLabels: Label[]
|
||||
};
|
||||
|
||||
// When a (or a set of) label has been confirmed, which suggestions should we update or delete.
|
||||
onConfirmLabels(info: {
|
||||
labelsConfirmed: Label[],
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
confirmLabels: Label[],
|
||||
rejectLabels: Label[],
|
||||
deleteLabels: Label[]
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CurrentView implements LabelingSuggestionLogic {
|
||||
public getType(): LabelingSuggestionLogicType {
|
||||
return LabelingSuggestionLogicType.CURRENT_VIEW;
|
||||
}
|
||||
public getDescription(): string {
|
||||
return 'Suggest in the current view and two views forward.';
|
||||
}
|
||||
// If the event in info happened, should we recompute the suggestions?
|
||||
public shouldTriggerSuggestionUpdate(info: {
|
||||
didViewportChange: boolean
|
||||
}): boolean {
|
||||
return info.didViewportChange;
|
||||
}
|
||||
|
||||
// On what time range should we compute suggestions?
|
||||
public calculateSuggestionRange(info: {
|
||||
dataset: Dataset,
|
||||
labels: Label[],
|
||||
detailedViewRange: TimeRange
|
||||
}): TimeRange {
|
||||
const start = info.detailedViewRange.timestampStart;
|
||||
const end = info.detailedViewRange.timestampEnd +
|
||||
(info.detailedViewRange.timestampEnd - info.detailedViewRange.timestampStart) * 2;
|
||||
return {
|
||||
timestampStart: start,
|
||||
timestampEnd: end
|
||||
};
|
||||
}
|
||||
|
||||
// During the suggestion progress, how should we refresh existing suggestions.
|
||||
// By default, we update matching suggestions.
|
||||
public refreshSuggestions(info: {
|
||||
suggestionProgress: {
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
timestampCompleted: number
|
||||
},
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
deleteLabels: Label[]
|
||||
} {
|
||||
return {
|
||||
deleteLabels: info.currentSuggestions.getRangesInRange(
|
||||
{ timestampStart: -1e10, timestampEnd: info.suggestionProgress.timestampCompleted })
|
||||
};
|
||||
}
|
||||
|
||||
// When a (or a set of) label has been confirmed, which suggestions should we update or delete.
|
||||
public onConfirmLabels(info: {
|
||||
labelsConfirmed: Label[],
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
confirmLabels: Label[],
|
||||
rejectLabels: Label[],
|
||||
deleteLabels: Label[]
|
||||
} {
|
||||
return {
|
||||
confirmLabels: [],
|
||||
rejectLabels: [],
|
||||
deleteLabels: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Forward implements LabelingSuggestionLogic {
|
||||
public getType(): LabelingSuggestionLogicType {
|
||||
return LabelingSuggestionLogicType.FORWARD;
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
return 'Suggest forward from the last confirmed label towards the end of the dataset.';
|
||||
}
|
||||
// If the event in info happened, should we recompute the suggestions?
|
||||
public shouldTriggerSuggestionUpdate(info: {
|
||||
didViewportChange: boolean
|
||||
}): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// On what time range should we compute suggestions?
|
||||
public calculateSuggestionRange(info: {
|
||||
dataset: Dataset,
|
||||
labels: Label[],
|
||||
detailedViewRange: TimeRange
|
||||
}): TimeRange {
|
||||
return {
|
||||
timestampStart: d3.max(info.labels, l => l.timestampEnd - (l.timestampEnd - l.timestampStart) * 0.1),
|
||||
timestampEnd: info.dataset.timestampEnd
|
||||
};
|
||||
}
|
||||
|
||||
// During the suggestion progress, how should we refresh existing suggestions.
|
||||
// By default, we update matching suggestions.
|
||||
public refreshSuggestions(info: {
|
||||
suggestionProgress: {
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
timestampCompleted: number
|
||||
},
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
deleteLabels: Label[]
|
||||
} {
|
||||
return {
|
||||
deleteLabels: info.currentSuggestions.getRangesInRange(
|
||||
{
|
||||
timestampStart: info.suggestionProgress.timestampStart,
|
||||
timestampEnd: info.suggestionProgress.timestampCompleted
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// When a (or a set of) label has been confirmed, which suggestions should we update or delete.
|
||||
public onConfirmLabels(info: {
|
||||
labelsConfirmed: Label[],
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
confirmLabels: Label[],
|
||||
rejectLabels: Label[],
|
||||
deleteLabels: Label[]
|
||||
} {
|
||||
return {
|
||||
confirmLabels: [],
|
||||
rejectLabels: [],
|
||||
deleteLabels: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ForwardConfirm extends Forward implements LabelingSuggestionLogic {
|
||||
public getType(): LabelingSuggestionLogicType {
|
||||
return LabelingSuggestionLogicType.FORWARD_CONFIRM;
|
||||
}
|
||||
public getDescription(): string {
|
||||
return 'Suggest forward from the last confirmed label towards the end of the dataset. ' +
|
||||
'Once a label has been confirmed, confirm suggestions before it.';
|
||||
}
|
||||
public onConfirmLabels(info: {
|
||||
labelsConfirmed: Label[],
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
confirmLabels: Label[],
|
||||
rejectLabels: Label[],
|
||||
deleteLabels: Label[]
|
||||
} {
|
||||
return {
|
||||
confirmLabels: info.currentSuggestions.getRangesInRange(
|
||||
{ timestampStart: -1e10, timestampEnd: d3.min(info.labelsConfirmed, l => l.timestampStart) }),
|
||||
rejectLabels: [],
|
||||
deleteLabels: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ForwardReject extends Forward implements LabelingSuggestionLogic {
|
||||
public getType(): LabelingSuggestionLogicType {
|
||||
return LabelingSuggestionLogicType.FORWARD_REJECT;
|
||||
}
|
||||
public getDescription(): string {
|
||||
return 'Suggest forward from the last confirmed label towards the end of the dataset. ' +
|
||||
'Once a label has been confirmed, delete suggestions before it.';
|
||||
}
|
||||
public onConfirmLabels(info: {
|
||||
labelsConfirmed: Label[],
|
||||
currentSuggestions: TimeRangeIndex<Label>
|
||||
}): {
|
||||
confirmLabels: Label[],
|
||||
rejectLabels: Label[],
|
||||
deleteLabels: Label[]
|
||||
} {
|
||||
return {
|
||||
confirmLabels: [],
|
||||
rejectLabels: [],
|
||||
deleteLabels: info.currentSuggestions.getRangesInRange(
|
||||
{ timestampStart: -1e10, timestampEnd: d3.min(info.labelsConfirmed, l => l.timestampStart) })
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getLabelingSuggestionLogic(logicType: LabelingSuggestionLogicType): LabelingSuggestionLogic {
|
||||
switch (logicType) {
|
||||
case LabelingSuggestionLogicType.CURRENT_VIEW:
|
||||
return new CurrentView();
|
||||
case LabelingSuggestionLogicType.FORWARD:
|
||||
return new Forward();
|
||||
case LabelingSuggestionLogicType.FORWARD_CONFIRM:
|
||||
return new ForwardConfirm();
|
||||
case LabelingSuggestionLogicType.FORWARD_REJECT:
|
||||
return new ForwardReject();
|
||||
default:
|
||||
throw 'bad suggestion logic type ' + logicType;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import { Dataset } from '../stores/dataStructures/dataset';
|
||||
import { Label } from '../stores/dataStructures/labeling';
|
||||
|
||||
|
||||
|
||||
export interface LabelingSuggestionProgress {
|
||||
timestampStart: number;
|
||||
timestampCompleted: number;
|
||||
timestampEnd: number;
|
||||
generation: number;
|
||||
confidenceHistogram?: number[];
|
||||
}
|
||||
|
||||
export interface LabelingSuggestionEngineStatus {
|
||||
status: 'BUILDING_MODEL' | 'MODEL_READY';
|
||||
}
|
||||
|
||||
export type LabelingSuggestionCallback = (labels: Label[], progress: LabelingSuggestionProgress, completed: boolean) => void;
|
||||
|
||||
export abstract class LabelingSuggestionModel {
|
||||
// Compute suggestions in the background.
|
||||
// Calling computeSuggestion should cancel the one currently running.
|
||||
// Callback will be called multiple times, the last one should have completed set to true OR error not null.
|
||||
public abstract computeSuggestion(
|
||||
dataset: Dataset,
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
confidenceThreshold: number,
|
||||
generation: number,
|
||||
callback: LabelingSuggestionCallback): void;
|
||||
|
||||
public abstract cancelSuggestion(callback: LabelingSuggestionCallback): void;
|
||||
|
||||
//TODO: should model implement this?
|
||||
public abstract getDeploymentCode(platform: string, callback: (code: string) => any): void;
|
||||
|
||||
public abstract dispose(): void;
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
import {RC4} from './RC4';
|
||||
|
||||
|
||||
/*
|
||||
Implements the DBA algorithm in:
|
||||
|
||||
[1] Petitjean, F., Ketterlin, A., & Gançarski, P. (2011).
|
||||
A global averaging method for dynamic time warping, with applications to clustering.
|
||||
Pattern Recognition, 44(3), 678-693.
|
||||
|
||||
[2] Petitjean, F., Forestier, G., Webb, G. I., Nicholson, A. E., Chen, Y., & Keogh, E. (2014, December).
|
||||
Dynamic time warping averaging of time series allows faster and more accurate classification.
|
||||
In 2014 IEEE International Conference on Data Mining (pp. 470-479). IEEE.
|
||||
*/
|
||||
|
||||
|
||||
export class DBA<SampleType> {
|
||||
private _currentAverage: SampleType[];
|
||||
private _series: SampleType[][];
|
||||
private _distanceFunction: (a: SampleType, b: SampleType) => number;
|
||||
private _meanFunction: (x: SampleType[]) => SampleType;
|
||||
|
||||
constructor(distanceFunction: (a: SampleType, b: SampleType) => number, meanFunction: (x: SampleType[]) => SampleType) {
|
||||
this._distanceFunction = distanceFunction;
|
||||
this._meanFunction = meanFunction;
|
||||
this._currentAverage = [];
|
||||
this._series = [];
|
||||
}
|
||||
|
||||
// Compute DTW between two series, return [ cost, [ [ i, j ], ... ] ].
|
||||
public dynamicTimeWarp(a: SampleType[], b: SampleType[]): [number, [number, number][]] {
|
||||
const matrix: [number, number][][] = [];
|
||||
for (let i = 0; i <= a.length; i++) {
|
||||
matrix[i] = [];
|
||||
for (let j = 0; j <= b.length; j++) {
|
||||
matrix[i][j] = [1e20, 0];
|
||||
}
|
||||
}
|
||||
matrix[0][0] = [0, 0];
|
||||
for (let i = 1; i <= a.length; i++) {
|
||||
for (let j = 1; j <= b.length; j++) {
|
||||
const cost = this._distanceFunction(a[i - 1], b[j - 1]);
|
||||
const c1 = matrix[i - 1][j][0];
|
||||
const c2 = matrix[i][j - 1][0];
|
||||
const c3 = matrix[i - 1][j - 1][0];
|
||||
if (c1 <= c2 && c1 <= c3) {
|
||||
matrix[i][j] = [cost + c1, 1];
|
||||
} else if (c2 <= c1 && c2 <= c3) {
|
||||
matrix[i][j] = [cost + c2, 2];
|
||||
} else {
|
||||
matrix[i][j] = [cost + c3, 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
const result: [number, number][] = [];
|
||||
let i = a.length; let j = b.length;
|
||||
while (i > 0 && j > 0) {
|
||||
const s = matrix[i][j][1];
|
||||
result.push([i - 1, j - 1]);
|
||||
if (s === 1) { i -= 1; }
|
||||
if (s === 2) { j -= 1; }
|
||||
if (s === 3) { i -= 1; j -= 1; }
|
||||
}
|
||||
result.reverse();
|
||||
return [matrix[a.length][b.length][0], result];
|
||||
}
|
||||
|
||||
// Init the DBA algorithm with series.
|
||||
public init(series: SampleType[][]): void {
|
||||
this._series = series;
|
||||
|
||||
// Initialize the average series naively to the first sereis.
|
||||
// TODO: Implement better initialization methods, see [1] for more detail.
|
||||
this._currentAverage = series[0];
|
||||
}
|
||||
|
||||
// Do one DBA iteration, return the average amount of update (in the distanceFunction).
|
||||
// Usually 5-10 iterations is sufficient to get a good average series.
|
||||
// You can also test if the returned value (the average update distance of this iteration)
|
||||
// is sufficiently small to determine convergence.
|
||||
public iterate(): number {
|
||||
const s = this._currentAverage;
|
||||
const alignments: SampleType[][] = [];
|
||||
for (let i = 0; i < s.length; i++) { alignments[i] = []; }
|
||||
for (const series of this._series) {
|
||||
const [, match] = this.dynamicTimeWarp(s, series);
|
||||
for (const [i, j] of match) {
|
||||
alignments[i].push(series[j]);
|
||||
}
|
||||
}
|
||||
this._currentAverage = alignments.map(this._meanFunction);
|
||||
return s.map((k, i) =>
|
||||
this._distanceFunction(k, this._currentAverage[i])).reduce((a, b) => a + b, 0) / s.length;
|
||||
}
|
||||
|
||||
// Get the current average series.
|
||||
public get average(): SampleType[] {
|
||||
return this._currentAverage;
|
||||
}
|
||||
|
||||
public computeAverage(series: SampleType[][], iterations: number, TOL: number): SampleType[] {
|
||||
this.init(series);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const change = this.iterate();
|
||||
if (change < TOL) { break; }
|
||||
}
|
||||
return this.average;
|
||||
}
|
||||
|
||||
public computeVariance(series: SampleType[][], center: SampleType[]): number {
|
||||
if (series.length < 3) { return null; }
|
||||
const distances = series.map(s => this.dynamicTimeWarp(s, center)[0]);
|
||||
let sumsq = 0;
|
||||
for (const d of distances) { sumsq += d * d; }
|
||||
return Math.sqrt(sumsq / (distances.length - 1));
|
||||
}
|
||||
|
||||
public computeKMeans(
|
||||
series: SampleType[][],
|
||||
k: number,
|
||||
kMeansIterations: number = 10,
|
||||
abcIterations: number = 10,
|
||||
dbaTolerance: number = 0.001): { variance: number, mean: SampleType[] }[] {
|
||||
if (k > series.length) {
|
||||
return series.map(s => ({ variance: 0, mean: s }));
|
||||
}
|
||||
if (k === 1) {
|
||||
const mean = this.computeAverage(series, abcIterations, dbaTolerance);
|
||||
return [{ variance: this.computeVariance(series, mean), mean }];
|
||||
}
|
||||
const random = new RC4('Labeling');
|
||||
const maxIterations = kMeansIterations;
|
||||
|
||||
const assignSeriesToCenters = (centers: SampleType[][]) => {
|
||||
const classSeries: SampleType[][][] = [];
|
||||
for (let i = 0; i < k; i++) { classSeries[i] = []; }
|
||||
for (const s of series) {
|
||||
let minD = null; let minI = null;
|
||||
for (let i = 0; i < k; i++) {
|
||||
const d = this.dynamicTimeWarp(centers[i], s)[0];
|
||||
if (minI === null || d < minD) {
|
||||
minI = i;
|
||||
minD = d;
|
||||
}
|
||||
}
|
||||
classSeries[minI].push(s);
|
||||
}
|
||||
return classSeries;
|
||||
};
|
||||
|
||||
const currentCenters = random.choose(series.length, k).map(i => series[i]);
|
||||
let assigned = assignSeriesToCenters(currentCenters);
|
||||
|
||||
// KMeans iterations.
|
||||
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
||||
// Update means.
|
||||
for (let i = 0; i < k; i++) {
|
||||
currentCenters[i] = this.computeAverage(assigned[i], abcIterations, dbaTolerance);
|
||||
}
|
||||
assigned = assignSeriesToCenters(currentCenters);
|
||||
}
|
||||
return currentCenters.map((center, i) => ({
|
||||
variance: this.computeVariance(assigned[i], center),
|
||||
mean: center
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
// RC4 random generator. See https://en.wikipedia.org/wiki/RC4
|
||||
// Ported from the Multiclass ModelTracker (now called Squares) project (originally in Javascript, now in Typescript).
|
||||
export class RC4 {
|
||||
private S: number[];
|
||||
private i: number;
|
||||
private j: number;
|
||||
|
||||
// Initialze the algorithm with a seed.
|
||||
constructor(seed: string | number[]) {
|
||||
this.S = [];
|
||||
this.i = 0;
|
||||
this.j = 0;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
this.S[i] = i;
|
||||
}
|
||||
if (seed) {
|
||||
if (typeof (seed) === 'string') {
|
||||
const seed_as_string = seed as string;
|
||||
const aseed: number[] = [];
|
||||
for (let i = 0; i < seed.length; i++) { aseed[i] = seed_as_string.charCodeAt(i); }
|
||||
seed = aseed;
|
||||
}
|
||||
let j = 0;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
j += this.S[i] + (seed as number[])[i % seed.length];
|
||||
j %= 256;
|
||||
const t = this.S[i]; this.S[i] = this.S[j]; this.S[j] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the next byte and update internal states.
|
||||
public nextByte(): number {
|
||||
this.i = (this.i + 1) % 256;
|
||||
this.j = (this.j + this.S[this.i]) % 256;
|
||||
const t = this.S[this.i]; this.S[this.i] = this.S[this.j]; this.S[this.j] = t;
|
||||
return this.S[(this.S[this.i] + this.S[this.j]) % 256];
|
||||
}
|
||||
|
||||
// Generate a random number from [ 0, 1 ] uniformally.
|
||||
public uniform(): number {
|
||||
// Generate 6 bytes.
|
||||
let value = 0;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
value *= 256;
|
||||
value += this.nextByte();
|
||||
}
|
||||
return value / 281474976710656;
|
||||
}
|
||||
|
||||
// Generate a random integer from min to max (both inclusive).
|
||||
public randint(min: number, max: number): number {
|
||||
let value = 0;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
value *= 256;
|
||||
value += this.nextByte();
|
||||
}
|
||||
return value % (max - min + 1) + min;
|
||||
}
|
||||
|
||||
// Choose K numbers from 0 to N - 1 randomly.
|
||||
// Using Algorithm R by Jeffrey Vitter.
|
||||
public choose(n: number, k: number): number[] {
|
||||
const chosen: number[] = [];
|
||||
for (let i = 0; i < k; i++) {
|
||||
chosen[i] = i;
|
||||
}
|
||||
for (let i = k; i < n; i++) {
|
||||
const j = this.randint(0, i);
|
||||
if (j < k) {
|
||||
chosen[j] = i;
|
||||
}
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
}
|
|
@ -1,332 +0,0 @@
|
|||
/*
|
||||
Implement the SPRING algorithm in the paper:
|
||||
|
||||
[1] Sakurai, Y., Faloutsos, C., & Yamamuro, M. (2007, April).
|
||||
Stream monitoring under the time warping distance.
|
||||
In 2007 IEEE 23rd International Conference on Data Engineering (pp. 1046-1055). IEEE.
|
||||
*/
|
||||
|
||||
export class SpringAlgorithm<InputType, SampleType> {
|
||||
private _distanceFunction: (a: InputType, b: SampleType) => number;
|
||||
private _report: (dmin: number, ts: number, te: number) => void;
|
||||
private _input: InputType[];
|
||||
private _threshold: number;
|
||||
private _s: number[];
|
||||
private _d: number[];
|
||||
private _s2: number[];
|
||||
private _d2: number[];
|
||||
private _dmin: number;
|
||||
private _t: number;
|
||||
private _te: number;
|
||||
private _ts: number;
|
||||
private _margin: number; // How many samples of overlap do we allow.
|
||||
|
||||
// Constructor:
|
||||
// input: The label to search for.
|
||||
// threshold: The threshold of reporting a match.
|
||||
// distanceFunction: Compute the distance between two samples.
|
||||
// report: Called once the algorithm finds something to report.
|
||||
constructor(
|
||||
input: InputType[],
|
||||
threshold: number,
|
||||
margin: number,
|
||||
distanceFunction: (a: InputType, b: SampleType) => number,
|
||||
report: (dmin: number, ts: number, te: number) => void) {
|
||||
|
||||
this._distanceFunction = distanceFunction;
|
||||
this._report = report;
|
||||
this._input = input;
|
||||
this._margin = margin;
|
||||
this._threshold = threshold;
|
||||
this._s = [];
|
||||
this._d = [];
|
||||
this._s2 = [];
|
||||
this._d2 = [];
|
||||
for (let i = 1; i <= this._input.length; i++) {
|
||||
this._d[i] = 1e10;
|
||||
this._s[i] = 0;
|
||||
}
|
||||
this._dmin = 1e10;
|
||||
this._t = 0;
|
||||
this._ts = 0;
|
||||
this._te = 0;
|
||||
}
|
||||
|
||||
// Process a new sample.
|
||||
// If a match is found, callback will be called with the indices of the starting and ending samples (zero-based).
|
||||
public feed(xt: SampleType): void {
|
||||
const t = this._t + 1;
|
||||
const m = this._input.length;
|
||||
const d: number[] = this._d2;
|
||||
const s: number[] = this._s2;
|
||||
d[0] = 0;
|
||||
s[0] = t;
|
||||
for (let i = 1; i <= m; i++) {
|
||||
const dist = this._distanceFunction(this._input[i - 1], xt);
|
||||
const d_i_minus_1 = d[i - 1];
|
||||
const d_i_p = this._d[i];
|
||||
const d_i_p_minus_1 = this._d[i - 1];
|
||||
if (d_i_minus_1 <= d_i_p && d_i_minus_1 <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_minus_1;
|
||||
s[i] = s[i - 1];
|
||||
} else if (d_i_p <= d_i_minus_1 && d_i_p <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_p;
|
||||
s[i] = this._s[i];
|
||||
} else {
|
||||
d[i] = dist + d_i_p_minus_1;
|
||||
s[i] = this._s[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (this._dmin <= this._threshold) {
|
||||
let condition = true;
|
||||
for (let i = 0; i <= m; i++) {
|
||||
if (!(d[i] >= this._dmin || s[i] > this._te - this._margin)) {
|
||||
condition = false;
|
||||
}
|
||||
}
|
||||
if (condition) {
|
||||
this._report(this._dmin, this._ts - 1, this._te - 1);
|
||||
this._dmin = 1e10;
|
||||
for (let i = 1; i <= m; i++) {
|
||||
if (s[i] <= this._te - this._margin) {
|
||||
d[i] = 1e10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (d[m] <= this._threshold && d[m] < this._dmin) {
|
||||
this._dmin = d[m];
|
||||
this._ts = s[m];
|
||||
this._te = t;
|
||||
}
|
||||
|
||||
this._d2 = this._d; this._d = d;
|
||||
this._s2 = this._s; this._s = s;
|
||||
this._t = t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SpringAlgorithmCore<InputType, SampleType> {
|
||||
private _distanceFunction: (a: InputType, b: SampleType) => number;
|
||||
private _input: InputType[];
|
||||
private _s: number[];
|
||||
private _d: number[];
|
||||
private _s2: number[];
|
||||
private _d2: number[];
|
||||
private _t: number;
|
||||
|
||||
// Constructor:
|
||||
// input: The label to search for.
|
||||
// threshold: The threshold of reporting a match.
|
||||
// distanceFunction: Compute the distance between two samples.
|
||||
// report: Called once the algorithm finds something to report.
|
||||
constructor(input: InputType[], distanceFunction: (a: InputType, b: SampleType) => number) {
|
||||
this._distanceFunction = distanceFunction;
|
||||
this._input = input;
|
||||
this._s = [];
|
||||
this._d = [];
|
||||
this._s2 = [];
|
||||
this._d2 = [];
|
||||
for (let i = 1; i <= this._input.length; i++) {
|
||||
this._d[i] = 1e10;
|
||||
this._s[i] = 0;
|
||||
}
|
||||
this._t = 0;
|
||||
}
|
||||
|
||||
// Process a new sample.
|
||||
// If a match is found, callback will be called with the indices of the starting and ending samples (zero-based).
|
||||
public feed(xt: SampleType): void {
|
||||
const t = this._t + 1;
|
||||
const m = this._input.length;
|
||||
const d: number[] = this._d2;
|
||||
const s: number[] = this._s2;
|
||||
d[0] = 0;
|
||||
s[0] = t;
|
||||
for (let i = 1; i <= m; i++) {
|
||||
const dist = this._distanceFunction(this._input[i - 1], xt);
|
||||
const d_i_minus_1 = d[i - 1];
|
||||
const d_i_p = this._d[i];
|
||||
const d_i_p_minus_1 = this._d[i - 1];
|
||||
if (d_i_minus_1 <= d_i_p && d_i_minus_1 <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_minus_1;
|
||||
s[i] = s[i - 1];
|
||||
} else if (d_i_p <= d_i_minus_1 && d_i_p <= d_i_p_minus_1) {
|
||||
d[i] = dist + d_i_p;
|
||||
s[i] = this._s[i];
|
||||
} else {
|
||||
d[i] = dist + d_i_p_minus_1;
|
||||
s[i] = this._s[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
this._d2 = this._d; this._d = d;
|
||||
this._s2 = this._s; this._s = s;
|
||||
this._t = t;
|
||||
}
|
||||
|
||||
public get d(): number[] {
|
||||
return this._d;
|
||||
}
|
||||
public get t(): number {
|
||||
return this._t;
|
||||
}
|
||||
public get s(): number[] {
|
||||
return this._s;
|
||||
}
|
||||
}
|
||||
|
||||
export class MultipleSpringAlgorithm<InputType, SampleType> {
|
||||
private _cores: SpringAlgorithmCore<InputType, SampleType>[];
|
||||
private _M: number[];
|
||||
private _report: (which: number, dmin: number, ts: number, te: number) => void;
|
||||
private _thresholdScales: number[];
|
||||
private _lengthRanges: number[][];
|
||||
private _margin: number;
|
||||
|
||||
private _dmin: number;
|
||||
private _whichMin: number;
|
||||
private _ts: number;
|
||||
private _te: number;
|
||||
|
||||
constructor(
|
||||
inputs: InputType[][],
|
||||
thresholdScales: number[],
|
||||
lengthRanges: number[][],
|
||||
margin: number,
|
||||
distanceFunction: (a: InputType, b: SampleType) => number,
|
||||
report: (which: number, dmin: number, ts: number, te: number) => void
|
||||
) {
|
||||
this._cores = inputs.map(input => new SpringAlgorithmCore(input, distanceFunction));
|
||||
this._M = inputs.map(input => input.length);
|
||||
this._thresholdScales = thresholdScales;
|
||||
this._lengthRanges = lengthRanges;
|
||||
this._margin = margin;
|
||||
this._report = report;
|
||||
this._dmin = 1e10;
|
||||
this._ts = 0;
|
||||
this._te = 0;
|
||||
}
|
||||
|
||||
public feed(xt: SampleType): [number, number] {
|
||||
this._cores.forEach(core => core.feed(xt));
|
||||
|
||||
const t = this._cores[0].t;
|
||||
const ds = this._cores.map(core => core.d);
|
||||
const ss = this._cores.map(core => core.s);
|
||||
|
||||
|
||||
if (this._dmin <= 1) {
|
||||
let condition = true;
|
||||
for (let i = 0; i < this._cores.length; i++) {
|
||||
const m = this._M[i];
|
||||
for (let j = 0; j <= m; j++) {
|
||||
if (!(ds[i][j] / this._thresholdScales[i] >= this._dmin || ss[i][j] > this._te - this._margin)) {
|
||||
condition = false;
|
||||
break;
|
||||
}
|
||||
if (!condition) { break; }
|
||||
}
|
||||
}
|
||||
if (condition) {
|
||||
this._report(this._whichMin, this._dmin * this._thresholdScales[this._whichMin], this._ts - 1, this._te - 1);
|
||||
this._dmin = 1e10;
|
||||
for (let i = 0; i < this._cores.length; i++) {
|
||||
const m = this._M[i];
|
||||
for (let j = 1; j <= m; j++) {
|
||||
if (ss[i][j] <= this._te - this._margin) {
|
||||
ds[i][j] = 1e10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the minimum d and s.
|
||||
let minI = null; let minD = null; let minS = null;
|
||||
for (let i = 0; i < this._cores.length; i++) {
|
||||
const d = ds[i][this._M[i]] / this._thresholdScales[i];
|
||||
const s = ss[i][this._M[i]];
|
||||
// Is the length acceptable?
|
||||
if (t - s >= this._lengthRanges[i][0] && t - s <= this._lengthRanges[i][1]) {
|
||||
if (minI === null || d < minD) {
|
||||
minI = i;
|
||||
minD = d;
|
||||
minS = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (minI !== null && minD <= 1 && minD < this._dmin) {
|
||||
this._dmin = minD;
|
||||
this._whichMin = minI;
|
||||
this._ts = minS;
|
||||
this._te = t;
|
||||
}
|
||||
|
||||
if (minI !== null) {
|
||||
return [minI, minD * this._thresholdScales[minI]];
|
||||
} else {
|
||||
return [null, null];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MultipleSpringAlgorithmBestMatch<InputType, SampleType> {
|
||||
private _cores: SpringAlgorithmCore<InputType, SampleType>[];
|
||||
private _M: number[];
|
||||
private _thresholdScales: number[];
|
||||
|
||||
private _dmin: number;
|
||||
private _whichMin: number;
|
||||
private _ts: number;
|
||||
private _te: number;
|
||||
|
||||
constructor(
|
||||
inputs: InputType[][],
|
||||
thresholdScales: number[],
|
||||
distanceFunction: (a: InputType, b: SampleType) => number
|
||||
) {
|
||||
this._cores = inputs.map(input => new SpringAlgorithmCore(input, distanceFunction));
|
||||
this._M = inputs.map(input => input.length);
|
||||
this._thresholdScales = thresholdScales;
|
||||
this._dmin = null;
|
||||
this._whichMin = null;
|
||||
this._ts = null;
|
||||
this._te = null;
|
||||
}
|
||||
|
||||
public feed(xt: SampleType): void {
|
||||
this._cores.forEach(core => core.feed(xt));
|
||||
|
||||
const t = this._cores[0].t;
|
||||
const ds = this._cores.map(core => core.d);
|
||||
const ss = this._cores.map(core => core.s);
|
||||
|
||||
// Find the minimum d and s.
|
||||
let minI = null; let minD = null; let minS = null;
|
||||
for (let i = 0; i < this._cores.length; i++) {
|
||||
const d = ds[i][this._M[i]] / this._thresholdScales[i];
|
||||
const s = ss[i][this._M[i]];
|
||||
// Is the length acceptable?
|
||||
if (minI === null || d < minD) {
|
||||
minI = i;
|
||||
minD = d;
|
||||
minS = s;
|
||||
}
|
||||
}
|
||||
if (minD !== null && (this._dmin === null || minD < this._dmin)) {
|
||||
this._dmin = minD;
|
||||
this._whichMin = minI;
|
||||
this._ts = minS;
|
||||
this._te = t;
|
||||
}
|
||||
}
|
||||
|
||||
public getBestMatch(): [number, number, number, number] {
|
||||
if (this._whichMin === null) { return [null, null, null, null]; }
|
||||
return [this._whichMin, this._dmin * this._thresholdScales[this._whichMin], this._ts - 1, this._te - 1];
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
// Implements the PELT Methods for Change Point Detection.
|
||||
|
||||
const log2PIplus1 = Math.log(2 * Math.PI) + 1;
|
||||
|
||||
function mbicMeanVar(x: number, x2: number, x3: number, n: number): number {
|
||||
let sigsq = (x2 - ((x * x) / n)) / n;
|
||||
if (sigsq <= 0) {
|
||||
sigsq = 0.00000000001;
|
||||
}
|
||||
return n * (log2PIplus1 + Math.log(sigsq)) + Math.log(n);
|
||||
}
|
||||
|
||||
function makeSumstats(array: number[]): number[][] {
|
||||
const n = array.length;
|
||||
|
||||
const cX: number[] = [];
|
||||
const cX2: number[] = [];
|
||||
const cX3: number[] = [];
|
||||
|
||||
// mean.
|
||||
const mu = array.reduce((a, b) => a + b, 0) / n;
|
||||
|
||||
cX[0] = 0;
|
||||
cX2[0] = 0;
|
||||
cX3[0] = 0;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
cX[i + 1] = cX[i] + array[i];
|
||||
cX2[i + 1] = cX2[i] + array[i] * array[i];
|
||||
cX3[i + 1] = cX3[i] + (array[i] - mu) * (array[i] - mu);
|
||||
}
|
||||
|
||||
return [cX, cX2, cX3];
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/rkillick/changepoint/blob/master/src/PELT_one_func_minseglen.c
|
||||
// PELT (Pruned Exact Linear Time) Method
|
||||
// beta: The penality on the number of change points.
|
||||
// minSegmentLength: Minimum distance between change points.
|
||||
// TODO: Check license!! Are we allowed to redistribute the code from the changepoint R package?
|
||||
export function pelt(data: (number[] | Float32Array)[], beta: number, minSegmentLength: number = 10): number[] {
|
||||
const sumStatistics = data.map(makeSumstats);
|
||||
const n = data[0].length;
|
||||
const rangeCost = (j: number, i: number) => {
|
||||
let r = 0;
|
||||
sumStatistics.forEach(ss => {
|
||||
r += mbicMeanVar(ss[0][j] - ss[0][i], ss[1][j] - ss[1][i], ss[2][j] - ss[2][i], j - i);
|
||||
});
|
||||
return r;
|
||||
};
|
||||
|
||||
const lastChangeLike: number[] = [];
|
||||
const lastChangeCpts: number[] = [];
|
||||
|
||||
const checkList: number[] = [];
|
||||
let nCheckList = 0;
|
||||
|
||||
const tmpLike: number[] = [];
|
||||
const tmpT: number[] = [];
|
||||
|
||||
[tmpLike, tmpT, checkList, lastChangeLike, lastChangeCpts].forEach(x => {
|
||||
for (let i = 0; i < x.length; i++) { x[i] = 0; }
|
||||
});
|
||||
|
||||
lastChangeLike[0] = -beta;
|
||||
lastChangeCpts[0] = 0;
|
||||
|
||||
for (let j = minSegmentLength; j < 2 * minSegmentLength; j++) {
|
||||
lastChangeLike[j] = rangeCost(j, 0);
|
||||
}
|
||||
|
||||
for (let j = minSegmentLength; j < 2 * minSegmentLength; j++) {
|
||||
lastChangeCpts[j] = 0;
|
||||
}
|
||||
|
||||
nCheckList = 2;
|
||||
checkList[0] = 0;
|
||||
checkList[1] = minSegmentLength;
|
||||
|
||||
const k = -4 * Math.log(n);
|
||||
|
||||
for (let tStar = 2 * minSegmentLength; tStar <= n; tStar++) {
|
||||
if ((lastChangeLike[tStar]) === 0) {
|
||||
let minOut = 0; let whichOut = -1;
|
||||
for (let i = 0; i < nCheckList; i++) {
|
||||
tmpLike[i] = lastChangeLike[checkList[i]] + rangeCost(tStar, checkList[i]) + beta;
|
||||
if (whichOut === -1 || tmpLike[i] < minOut) {
|
||||
minOut = tmpLike[i];
|
||||
whichOut = i;
|
||||
}
|
||||
}
|
||||
// Updates minout and whichout with min and which element.
|
||||
lastChangeLike[tStar] = minOut;
|
||||
lastChangeCpts[tStar] = checkList[whichOut];
|
||||
// Update checklist for next iteration, first element is next tau.
|
||||
let nCheckTmp = 0;
|
||||
for (let i = 0; i < nCheckList; i++) {
|
||||
if (tmpLike[i] + k <= (lastChangeLike[tStar])) {
|
||||
checkList[nCheckTmp] = checkList[i];
|
||||
nCheckTmp += 1;
|
||||
}
|
||||
}
|
||||
nCheckList = nCheckTmp;
|
||||
}
|
||||
checkList[nCheckList] = tStar - minSegmentLength - 1; // at least 1 obs per seg
|
||||
nCheckList += 1;
|
||||
}
|
||||
|
||||
|
||||
// Put final set of changepoints together
|
||||
let last = n;
|
||||
const cptsOut: number[] = [];
|
||||
while (last !== 0) {
|
||||
if (last !== n) {
|
||||
cptsOut.push(last - 1);
|
||||
}
|
||||
last = lastChangeCpts[last];
|
||||
}
|
||||
cptsOut.reverse();
|
||||
return cptsOut;
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
// Do suggestions with the Spring DTW algorithm.
|
||||
|
||||
import { Dataset } from '../../stores/dataStructures/dataset';
|
||||
import { Label, LabelConfirmationState } from '../../stores/dataStructures/labeling';
|
||||
import { resampleDatasetRowMajor } from '../../stores/dataStructures/sampling';
|
||||
import { DBA } from '../algorithms/DBA';
|
||||
import { MultipleSpringAlgorithm, MultipleSpringAlgorithmBestMatch } from '../algorithms/SpringAlgorithm';
|
||||
import { generateArduinoCodeForDtwModel, generateMicrobitCodeForDtwModel } from '../DtwDeployment';
|
||||
import { LabelingSuggestionCallback, LabelingSuggestionModel } from '../LabelingSuggestionModel';
|
||||
|
||||
|
||||
interface ReferenceLabel {
|
||||
series: number[][];
|
||||
variance: number;
|
||||
className: string;
|
||||
adjustmentsBegin: number;
|
||||
adjustmentsEnd: number;
|
||||
}
|
||||
|
||||
|
||||
// We use the sum of absolute distance as distance function, rather than euclidean distance
|
||||
function distanceFunction(a: number[], b: number[]): number {
|
||||
let s = 0;
|
||||
const dim = a.length;
|
||||
for (let i = 0; i < dim; i++) { s += Math.abs(a[i] - b[i]); }
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
function averageFunction(x: number[][]): number[] {
|
||||
const mean = x[0].slice();
|
||||
const N = x.length;
|
||||
for (let i = 1; i < N; i++) {
|
||||
for (let j = 0; j < mean.length; j++) {
|
||||
mean[j] += x[i][j];
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < mean.length; j++) {
|
||||
mean[j] /= N;
|
||||
}
|
||||
return mean;
|
||||
}
|
||||
|
||||
|
||||
function groupBy<InputType>(input: InputType[], groupFunc: (itype: InputType) => string): { group: string, items: InputType[] }[] {
|
||||
const groupName2Items: { [name: string]: InputType[] } = {};
|
||||
input.forEach(inp => {
|
||||
const group = groupFunc(inp);
|
||||
if (groupName2Items[group]) {
|
||||
groupName2Items[group].push(inp);
|
||||
} else {
|
||||
groupName2Items[group] = [inp];
|
||||
}
|
||||
});
|
||||
const result: { group: string, items: InputType[] }[] = [];
|
||||
Object.keys(groupName2Items).forEach(group => {
|
||||
result.push({
|
||||
group: group,
|
||||
items: groupName2Items[group]
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getAverageLabelsPerClass(
|
||||
dataset: Dataset,
|
||||
labels: Label[],
|
||||
sampleRate: number): ReferenceLabel[] {
|
||||
|
||||
if (!labels || labels.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dba = new DBA<number[]>(distanceFunction, averageFunction);
|
||||
|
||||
const classes_array = groupBy(
|
||||
labels.map(reference => ({
|
||||
name: reference.className,
|
||||
timestampStart: reference.timestampStart,
|
||||
timestampEnd: reference.timestampEnd,
|
||||
samples: resampleDatasetRowMajor(
|
||||
dataset,
|
||||
reference.timestampStart, reference.timestampEnd,
|
||||
Math.round(sampleRate * (reference.timestampEnd - reference.timestampStart))
|
||||
)
|
||||
})),
|
||||
input => input.name);
|
||||
|
||||
const result: ReferenceLabel[] = [];
|
||||
|
||||
for (const { group, items } of classes_array) {
|
||||
const means = dba.computeKMeans(items.map(x => x.samples), 1, 10, 10, 0.01);
|
||||
|
||||
const errors1: number[] = [];
|
||||
const errors2: number[] = [];
|
||||
|
||||
items.forEach(item => {
|
||||
const itemDuration = item.timestampEnd - item.timestampStart;
|
||||
const margin = 0.1 * itemDuration;
|
||||
const samplesWithMargin = resampleDatasetRowMajor(
|
||||
dataset,
|
||||
item.timestampStart - margin, item.timestampEnd + margin,
|
||||
Math.round(sampleRate * (item.timestampEnd - item.timestampStart + margin * 2))
|
||||
);
|
||||
const sampleIndex2Time = index => {
|
||||
return index / (samplesWithMargin.length - 1) * (item.timestampEnd - item.timestampStart + margin * 2) +
|
||||
(item.timestampStart - margin);
|
||||
};
|
||||
|
||||
const spring = new MultipleSpringAlgorithmBestMatch<number[], number[]>(
|
||||
means.map(x => x.mean),
|
||||
means.map(x => x.variance),
|
||||
distanceFunction
|
||||
);
|
||||
samplesWithMargin.forEach(x => spring.feed(x));
|
||||
|
||||
const [which, , ts, te] = spring.getBestMatch();
|
||||
if (which !== null) {
|
||||
const t1 = sampleIndex2Time(ts);
|
||||
const t2 = sampleIndex2Time(te);
|
||||
if (Math.abs(t1 - item.timestampStart) < margin && Math.abs(t2 - item.timestampEnd) < margin) {
|
||||
errors1.push(t1 - item.timestampStart);
|
||||
errors2.push(t2 - item.timestampEnd);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const off1 = errors1.length > 0 ? errors1.reduce((a, b) => a + b, 0) / errors1.length : 0;
|
||||
const off2 = errors2.length > 0 ? errors2.reduce((a, b) => a + b, 0) / errors2.length : 0;
|
||||
|
||||
for (const { mean, variance } of means) {
|
||||
result.push({
|
||||
className: group,
|
||||
variance: variance,
|
||||
adjustmentsBegin: off1,
|
||||
adjustmentsEnd: off2,
|
||||
series: mean
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class DtwSuggestionModel extends LabelingSuggestionModel {
|
||||
private _references: ReferenceLabel[];
|
||||
private _sampleRate: number;
|
||||
private _callback2Timer: WeakMap<LabelingSuggestionCallback, NodeJS.Timer>;
|
||||
private _allTimers: Set<NodeJS.Timer>;
|
||||
|
||||
constructor(references: ReferenceLabel[], sampleRate: number) {
|
||||
super();
|
||||
this._references = references;
|
||||
this._sampleRate = sampleRate;
|
||||
this._callback2Timer = new WeakMap<LabelingSuggestionCallback, NodeJS.Timer>();
|
||||
this._allTimers = new Set<NodeJS.Timer>();
|
||||
}
|
||||
|
||||
public getDeploymentCode(platform: string, callback: (code: string) => any): void {
|
||||
if (platform === 'arduino') {
|
||||
callback(generateArduinoCodeForDtwModel(this._sampleRate, 30, this._references));
|
||||
}
|
||||
if (platform === 'microbit') {
|
||||
callback(generateMicrobitCodeForDtwModel(this._sampleRate, 30, this._references));
|
||||
}
|
||||
}
|
||||
|
||||
// Compute suggestions in the background.
|
||||
// Calling computeSuggestion should cancel the one currently running.
|
||||
// Callback will be called multiple times, the last one should have completed set to true OR error not null.
|
||||
public computeSuggestion(
|
||||
dataset: Dataset,
|
||||
timestampStart: number,
|
||||
timestampEnd: number,
|
||||
confidenceThreshold: number,
|
||||
generation: number,
|
||||
callback: LabelingSuggestionCallback): void {
|
||||
|
||||
timestampStart = Math.round(this._sampleRate * timestampStart) / this._sampleRate;
|
||||
timestampEnd = Math.round(this._sampleRate * timestampEnd) / this._sampleRate;
|
||||
|
||||
let labels = this._references;
|
||||
labels = labels.filter(x => x.variance !== null);
|
||||
if (labels.length === 0) {
|
||||
callback(
|
||||
[],
|
||||
{
|
||||
timestampCompleted: timestampEnd,
|
||||
timestampStart: timestampStart,
|
||||
timestampEnd: timestampEnd,
|
||||
generation: generation
|
||||
},
|
||||
true);
|
||||
return;
|
||||
}
|
||||
|
||||
const resampledLength = Math.round(this._sampleRate * (timestampEnd - timestampStart));
|
||||
|
||||
const confidenceHistogram = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
const getLikelihood = (variance: number, distance: number) => {
|
||||
// return 1 - invertGaussianPercentPoint(-distance / variance) * 2;
|
||||
return Math.exp(-distance * distance / variance / variance / 2);
|
||||
};
|
||||
const thresholds = labels.map(label => {
|
||||
return Math.sqrt(-2 * Math.log(confidenceThreshold)) * label.variance;
|
||||
});
|
||||
|
||||
let labelCache: Label[] = [];
|
||||
|
||||
const algo = new MultipleSpringAlgorithm<number[], number[]>(
|
||||
labels.map(label => label.series),
|
||||
thresholds,
|
||||
labels.map(label => [label.series.length * 0.8, label.series.length * 1.2]),
|
||||
10,
|
||||
distanceFunction,
|
||||
(which, dist, ts, te) => {
|
||||
const label = labels[which];
|
||||
let t1 = ts / (resampledLength - 1) * (timestampEnd - timestampStart) + timestampStart;
|
||||
let t2 = te / (resampledLength - 1) * (timestampEnd - timestampStart) + timestampStart;
|
||||
t1 -= labels[which].adjustmentsBegin;
|
||||
t2 -= labels[which].adjustmentsEnd;
|
||||
const likelihood = getLikelihood(label.variance, dist);
|
||||
labelCache.push({
|
||||
timestampStart: t1,
|
||||
timestampEnd: t2,
|
||||
className: label.className,
|
||||
state: LabelConfirmationState.UNCONFIRMED,
|
||||
suggestionConfidence: likelihood,
|
||||
suggestionGeneration: generation
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
let iCurrent = 0;
|
||||
const doNextChunk = () => {
|
||||
const chunkSize = Math.ceil(100 / labels.length);
|
||||
labelCache = [];
|
||||
|
||||
// Subsample and normalize the data.
|
||||
const tChunkStart = iCurrent / (resampledLength - 1) * (timestampEnd - timestampStart) + timestampStart;
|
||||
const tChunkEnd = (iCurrent + chunkSize - 1) / (resampledLength - 1) * (timestampEnd - timestampStart) + timestampStart;
|
||||
const chunkSamples = resampleDatasetRowMajor(dataset, tChunkStart, tChunkEnd, chunkSize);
|
||||
|
||||
// Feed data into the SPRING algorithm.
|
||||
for (const s of chunkSamples) {
|
||||
const [minI, minD] = algo.feed(s);
|
||||
if (minD !== null) {
|
||||
const cf = getLikelihood(labels[minI].variance, minD);
|
||||
const histIndex = Math.floor(Math.pow(cf, 0.3) * (confidenceHistogram.length - 1));
|
||||
if (histIndex >= 0 && histIndex < confidenceHistogram.length) {
|
||||
confidenceHistogram[histIndex] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call user's callback.
|
||||
callback(
|
||||
labelCache,
|
||||
{
|
||||
timestampStart: timestampStart,
|
||||
timestampEnd: timestampEnd,
|
||||
timestampCompleted: (iCurrent + chunkSize) / (resampledLength - 1) * (timestampEnd - timestampStart) + timestampStart,
|
||||
generation: generation,
|
||||
confidenceHistogram: confidenceHistogram
|
||||
},
|
||||
false);
|
||||
// Clear the cache after user callback.
|
||||
labelCache = [];
|
||||
|
||||
// Determine the next chunk.
|
||||
if (iCurrent + chunkSize < resampledLength) {
|
||||
iCurrent += chunkSize;
|
||||
const timer = setTimeout(doNextChunk, 1);
|
||||
this._callback2Timer.set(callback, timer);
|
||||
this._allTimers.add(timer);
|
||||
} else {
|
||||
callback(
|
||||
[],
|
||||
{
|
||||
timestampStart: timestampStart,
|
||||
timestampEnd: timestampEnd,
|
||||
timestampCompleted: timestampEnd,
|
||||
generation: generation,
|
||||
confidenceHistogram: confidenceHistogram
|
||||
},
|
||||
true);
|
||||
}
|
||||
};
|
||||
|
||||
const timer = setTimeout(doNextChunk, 1);
|
||||
this._callback2Timer.set(callback, timer);
|
||||
this._allTimers.add(timer);
|
||||
}
|
||||
|
||||
public cancelSuggestion(callback: LabelingSuggestionCallback): void {
|
||||
if (this._callback2Timer.has(callback)) {
|
||||
clearTimeout(this._callback2Timer.get(callback));
|
||||
this._allTimers.delete(this._callback2Timer.get(callback));
|
||||
this._callback2Timer.delete(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._allTimers.forEach(x => clearTimeout(x));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export module DtwAlgorithm {
|
||||
|
||||
export function createModel(dataset: Dataset, labels: Label[]): LabelingSuggestionModel {
|
||||
const maxDuration = labels.map(label => label.timestampEnd - label.timestampStart).reduce((a, b) => Math.max(a, b), 0);
|
||||
const sampleRate = 100 / maxDuration; // / referenceDuration;
|
||||
const references = getAverageLabelsPerClass(dataset, labels, sampleRate);
|
||||
return new DtwSuggestionModel(references, sampleRate);
|
||||
}
|
||||
|
||||
export function getReferenceLabels(dataset: Dataset, labels: Label[]): ReferenceLabel[] {
|
||||
const maxDuration = labels.map(label => label.timestampEnd - label.timestampStart).reduce((a, b) => Math.max(a, b), 0);
|
||||
const sampleRate = 100 / maxDuration; // / referenceDuration;
|
||||
return getAverageLabelsPerClass(dataset, labels, sampleRate);
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// The webworker entry point for the worker-based DTW model factory.
|
||||
|
||||
import { Dataset } from '../../stores/dataStructures/dataset';
|
||||
import { Label } from '../../stores/dataStructures/labeling';
|
||||
import { LabelingSuggestionCallback, LabelingSuggestionModel, LabelingSuggestionProgress }
|
||||
from '../../suggestion/LabelingSuggestionModel';
|
||||
import { DtwAlgorithm } from '../../suggestion/worker/DtwAlgorithm';
|
||||
import { ModelBuildMessage, ModelMessage, SetDatasetMessage } from './SuggestionWorkerMessage';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class DtwSuggestionWorker extends EventEmitter {
|
||||
private _dataset: Dataset;
|
||||
private _currentModelID: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._currentModelID = 1;
|
||||
|
||||
this.addListener('dataset.set', (data: SetDatasetMessage) => {
|
||||
const ds = new Dataset();
|
||||
ds.deserialize(data.dataset);
|
||||
this._dataset = ds;
|
||||
});
|
||||
|
||||
this.addListener('model.build', (data: ModelBuildMessage) => {
|
||||
const labels = data.labels;
|
||||
const buildCallbackID = data.callbackID;
|
||||
|
||||
const model = DtwAlgorithm.createModel(this._dataset, labels);
|
||||
|
||||
const modelID = 'model-' + this._currentModelID.toString();
|
||||
this._currentModelID += 1;
|
||||
|
||||
this.emit('.post', {
|
||||
kind: 'model.build.callback',
|
||||
modelID: modelID,
|
||||
callbackID: buildCallbackID
|
||||
});
|
||||
const callbackID2Mapping = new Map<string, [LabelingSuggestionModel, Function]>();
|
||||
|
||||
this.addListener('model.message.' + modelID, (modelData: ModelMessage) => {
|
||||
const message = modelData.message;
|
||||
if (message.kind === 'compute') {
|
||||
const callback = (computeLabels: Label[], computeProgress: LabelingSuggestionProgress, completed: boolean) => {
|
||||
this.emit('.post', {
|
||||
kind: 'model.message.' + modelID,
|
||||
message: {
|
||||
kind: 'callback',
|
||||
callbackID: message.callbackID,
|
||||
labels: computeLabels,
|
||||
progress: computeProgress,
|
||||
completed: completed
|
||||
}
|
||||
});
|
||||
};
|
||||
callbackID2Mapping.set(message.callbackID, [model, callback]);
|
||||
model.computeSuggestion(
|
||||
this._dataset,
|
||||
message.timestampStart,
|
||||
message.timestampEnd,
|
||||
message.confidenceThreshold,
|
||||
message.generation,
|
||||
callback);
|
||||
}
|
||||
if (message.kind === 'get-deployment-code') {
|
||||
const callback = (code: string) => {
|
||||
this.emit('.post', {
|
||||
kind: 'model.message.' + modelID,
|
||||
message: {
|
||||
kind: 'get-deployment-code-callback',
|
||||
callbackID: message.callbackID,
|
||||
code: code
|
||||
}
|
||||
});
|
||||
};
|
||||
callbackID2Mapping.set(message.callbackID, [model, callback]);
|
||||
model.getDeploymentCode(message.platform, callback);
|
||||
}
|
||||
if (message.kind === 'compute.cancel') {
|
||||
if (callbackID2Mapping.has(message.callbackID)) {
|
||||
const [tModel, tCallback] = callbackID2Mapping.get(message.callbackID);
|
||||
tModel.cancelSuggestion(tCallback as LabelingSuggestionCallback);
|
||||
}
|
||||
}
|
||||
if (message.kind === 'dispose') {
|
||||
if (model) {
|
||||
model.dispose();
|
||||
this.removeAllListeners('model.message.' + modelID);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public handleMessage(data: any): void {
|
||||
this.emit(data.kind, data);
|
||||
}
|
||||
}
|
||||
|
||||
const worker = new DtwSuggestionWorker();
|
||||
|
||||
self.onmessage = message => {
|
||||
const data = message.data;
|
||||
worker.handleMessage(data);
|
||||
};
|
||||
|
||||
worker.addListener('.post', message => {
|
||||
self.postMessage(message, undefined);
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
import {Label} from '../../stores/dataStructures/labeling';
|
||||
|
||||
interface ModelMessageBase {
|
||||
kind: string;
|
||||
}
|
||||
|
||||
export interface ComputeModelMessage extends ModelMessageBase {
|
||||
kind: 'compute';
|
||||
callbackID: string;
|
||||
timestampStart: number;
|
||||
timestampEnd: number;
|
||||
confidenceThreshold: number;
|
||||
generation: number;
|
||||
}
|
||||
|
||||
|
||||
export interface CancelModelMessage extends ModelMessageBase {
|
||||
kind: 'compute.cancel';
|
||||
callbackID: string;
|
||||
}
|
||||
|
||||
export interface GetDeploymentCodeMessage extends ModelMessageBase {
|
||||
kind: 'get-deployment-code';
|
||||
callbackID: string;
|
||||
platform: string;
|
||||
}
|
||||
|
||||
export interface DisposeModelMessage extends ModelMessageBase {
|
||||
kind: 'dispose';
|
||||
}
|
||||
|
||||
export type ModelSpecificMessage =
|
||||
ComputeModelMessage |
|
||||
CancelModelMessage |
|
||||
GetDeploymentCodeMessage |
|
||||
DisposeModelMessage;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
interface SuggestionWorkerMessageBase {
|
||||
kind: string;
|
||||
}
|
||||
|
||||
export interface ModelMessage extends SuggestionWorkerMessageBase {
|
||||
modelID: string;
|
||||
message: ModelSpecificMessage;
|
||||
}
|
||||
|
||||
export interface SetDatasetMessage extends SuggestionWorkerMessageBase {
|
||||
dataset: Object; // serialized Dataset
|
||||
}
|
||||
|
||||
export interface ModelBuildMessage extends SuggestionWorkerMessageBase {
|
||||
callbackID: string;
|
||||
labels: Label[];
|
||||
}
|
||||
|
||||
export type SuggestionWorkerMessage =
|
||||
ModelMessage |
|
||||
SetDatasetMessage |
|
||||
ModelBuildMessage;
|
|
@ -1,56 +0,0 @@
|
|||
// COMMON SETTINGS
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// These settings are used in both SW UART, HW UART and SPI mode
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
#define BUFSIZE 128 // Size of the read buffer for incoming data
|
||||
#define VERBOSE_MODE true // If set to 'true' enables debug output
|
||||
|
||||
|
||||
// SOFTWARE UART SETTINGS
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// The following macros declare the pins that will be used for 'SW' serial.
|
||||
// You should use this option if you are connecting the UART Friend to an UNO
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
#define BLUEFRUIT_SWUART_RXD_PIN 9 // Required for software serial!
|
||||
#define BLUEFRUIT_SWUART_TXD_PIN 10 // Required for software serial!
|
||||
#define BLUEFRUIT_UART_CTS_PIN 11 // Required for software serial!
|
||||
#define BLUEFRUIT_UART_RTS_PIN -1 // Optional, set to -1 if unused
|
||||
|
||||
|
||||
// HARDWARE UART SETTINGS
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// The following macros declare the HW serial port you are using. Uncomment
|
||||
// this line if you are connecting the BLE to Leonardo/Micro or Flora
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
#ifdef Serial1 // this makes it not complain on compilation if there's no Serial1
|
||||
#define BLUEFRUIT_HWSERIAL_NAME Serial1
|
||||
#endif
|
||||
|
||||
|
||||
// SHARED UART SETTINGS
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// The following sets the optional Mode pin, its recommended but not required
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
#define BLUEFRUIT_UART_MODE_PIN 12 // Set to -1 if unused
|
||||
|
||||
|
||||
// SHARED SPI SETTINGS
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// The following macros declare the pins to use for HW and SW SPI communication.
|
||||
// SCK, MISO and MOSI should be connected to the HW SPI pins on the Uno when
|
||||
// using HW SPI. This should be used with nRF51822 based Bluefruit LE modules
|
||||
// that use SPI (Bluefruit LE SPI Friend).
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
#define BLUEFRUIT_SPI_CS 8
|
||||
#define BLUEFRUIT_SPI_IRQ 7
|
||||
#define BLUEFRUIT_SPI_RST 4 // Optional but recommended, set to -1 if unused
|
||||
|
||||
// SOFTWARE SPI SETTINGS
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// The following macros declare the pins to use for SW SPI communication.
|
||||
// This should be used with nRF51822 based Bluefruit LE modules that use SPI
|
||||
// (Bluefruit LE SPI Friend).
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
#define BLUEFRUIT_SPI_SCK 13
|
||||
#define BLUEFRUIT_SPI_MISO 12
|
||||
#define BLUEFRUIT_SPI_MOSI 11
|
193
arduino/dtw.ino
193
arduino/dtw.ino
|
@ -1,193 +0,0 @@
|
|||
/*
|
||||
* Sample DTW Model on accelerometer and gyroscope with generated recognizer code.
|
||||
*
|
||||
* There are two modes for this code:
|
||||
* - Data Collection Mode: change RUN_MODE to 0.
|
||||
* - Running Mode: change RUN_MODE to 1, and paste in the recognizer code generated by the integrated app.
|
||||
*
|
||||
* This file WILL produce non-uniform samples as there are glitches in the SD card write.
|
||||
* It mark each sample at its own timestamp using millis().
|
||||
* We should create a script that process non-uniform samples to uniform samples, or
|
||||
* fix this in the data loader code.
|
||||
*
|
||||
* Based on Saleema's 1 class DTW, which is:
|
||||
* Based on Donghao's SPRING.ts code which implements the algorithm in
|
||||
* [1] Sakurai, Y., Faloutsos, C., & Yamamuro, M. (2007, April).
|
||||
* Stream monitoring under the time warping distance.
|
||||
* In 2007 IEEE 23rd International Conference on Data Engineering (pp. 1046-1055). IEEE.
|
||||
*
|
||||
* The circuit:
|
||||
* button is wired to GND and digital pin 5
|
||||
*
|
||||
* SD card attached to SPI bus as follows (integrated in Feather board):
|
||||
** MOSI - pin 11
|
||||
** MISO - pin 12
|
||||
** CLK - pin 13
|
||||
** CS - pin 4
|
||||
|
||||
* created 9/16/2016
|
||||
* by Donghao Ren
|
||||
|
||||
*/
|
||||
|
||||
// Some of this configuration and setup is based on the dataCollection code. We might not need all of these.....
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Wire.h>
|
||||
#include <RTClib.h>
|
||||
#include <Adafruit_LSM303_U.h>
|
||||
#include <Adafruit_L3GD20_U.h>
|
||||
#include <Adafruit_BMP085_U.h>
|
||||
#include <Adafruit_Sensor.h>
|
||||
|
||||
const int RUN_MODE = 1; // 0 for data collection, 1 for prediction.
|
||||
|
||||
// Constants
|
||||
const float frequency = 50; // Unit: Hz
|
||||
const String boardNum = "COM2"; // Serial number
|
||||
const int buttonPin = 9;
|
||||
|
||||
const int chipSelect = 4;
|
||||
const int rate = (int)(1000.0/frequency);
|
||||
|
||||
const int ledPin = 13;
|
||||
|
||||
// Sensor configuration variables
|
||||
Adafruit_LSM303_Accel_Unified accel = Adafruit_LSM303_Accel_Unified(54321);
|
||||
Adafruit_LSM303_Mag_Unified mag = Adafruit_LSM303_Mag_Unified(12345);
|
||||
Adafruit_L3GD20_Unified gyro = Adafruit_L3GD20_Unified(20);
|
||||
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085);
|
||||
RTC_DS3231 rtc;
|
||||
|
||||
float acc_x, acc_y, acc_z;
|
||||
float mag_x, mag_y, mag_z;
|
||||
float gyro_x, gyro_y, gyro_z;
|
||||
float prs;
|
||||
float temp;
|
||||
float span;
|
||||
uint32_t buttonClick;
|
||||
int old = 1;
|
||||
|
||||
// global variables for timing
|
||||
uint32_t nextSampleTimeInMs = 0;
|
||||
uint32_t startTimeInS = 0;
|
||||
|
||||
// This function waits until the next valid sample time. If the function is called before the next sample time
|
||||
// it spins until the sample time arrives. If the function is called after the next sample time, it waits until the next valid sample time.
|
||||
void wait()
|
||||
{
|
||||
while(true) {
|
||||
uint32_t t = millis();
|
||||
if(t >= nextSampleTimeInMs) {
|
||||
nextSampleTimeInMs = t + rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File dataFile;
|
||||
|
||||
void setup() {
|
||||
setupRecognizer(0.2);
|
||||
/******** Button Setup ********************/
|
||||
// Set up pin mode for external modules
|
||||
pinMode(buttonPin, INPUT_PULLUP);
|
||||
pinMode(ledPin, OUTPUT);
|
||||
digitalWrite(ledPin, HIGH);
|
||||
Serial.begin(115200);
|
||||
|
||||
// Confirm that all of the sensors started
|
||||
gyro.enableAutoRange(true);
|
||||
mag.enableAutoRange(true);
|
||||
accel.begin(); // Accelerometer setup
|
||||
mag.begin(); // Magnetometer setup
|
||||
gyro.begin(); // Gyroscope setup
|
||||
bmp.begin(); // Pressure setup
|
||||
rtc.begin(); // RTC setup
|
||||
|
||||
if(RUN_MODE == 0) {
|
||||
SD.begin(chipSelect);
|
||||
for(int i = 0; ; i++) {
|
||||
String filename = "log" + String(i) + ".tsv";
|
||||
if(SD.exists(filename)) continue;
|
||||
else {
|
||||
dataFile = SD.open(filename, FILE_WRITE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the clock lost power (e.g, its coin cell was removed), set its time to the compile time of this sketch
|
||||
if(rtc.lostPower()) {
|
||||
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
|
||||
}
|
||||
|
||||
if(RUN_MODE == 0) {
|
||||
dataFile.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int sampleIndex = 0;
|
||||
|
||||
char floatToStringBuf[20];
|
||||
const char* floatToString(float f) {
|
||||
int intValue = f * 10000;
|
||||
if(intValue >= 0) {
|
||||
sprintf(floatToStringBuf, "%d.%04d", intValue / 10000, intValue % 10000);
|
||||
} else {
|
||||
sprintf(floatToStringBuf, "-%d.%04d", (-intValue) / 10000, (-intValue) % 10000);
|
||||
}
|
||||
return floatToStringBuf;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
wait();
|
||||
|
||||
sampleIndex++;
|
||||
|
||||
digitalWrite(ledPin, (sampleIndex / 10) % 2 == 0 ? LOW : HIGH);
|
||||
|
||||
/****************** Accelerometer & Compass Reading ******************/
|
||||
// Get a new sensor event
|
||||
sensors_event_t event;
|
||||
if(accel.getEvent(&event)) {
|
||||
acc_x = event.acceleration.x;
|
||||
acc_y = event.acceleration.y;
|
||||
acc_z = event.acceleration.z;
|
||||
}
|
||||
|
||||
// Get a new sensor event
|
||||
sensors_event_t event2;
|
||||
if(gyro.getEvent(&event2)) {
|
||||
gyro_x = event2.gyro.x;
|
||||
gyro_y = event2.gyro.y;
|
||||
gyro_z = event2.gyro.z;
|
||||
}
|
||||
|
||||
if(RUN_MODE == 0) {
|
||||
char buffer[200];
|
||||
sprintf(buffer, "%d\t", millis());
|
||||
strcat(buffer, floatToString(acc_x)); strcat(buffer, "\t");
|
||||
strcat(buffer, floatToString(acc_y)); strcat(buffer, "\t");
|
||||
strcat(buffer, floatToString(acc_z)); strcat(buffer, "\t");
|
||||
strcat(buffer, "\t\t\t"); // skip the mag fields.
|
||||
strcat(buffer, floatToString(gyro_x)); strcat(buffer, "\t");
|
||||
strcat(buffer, floatToString(gyro_y)); strcat(buffer, "\t");
|
||||
strcat(buffer, floatToString(gyro_z)); strcat(buffer, "\n");
|
||||
dataFile.print(buffer);
|
||||
|
||||
if(sampleIndex % 100 == 0) {
|
||||
dataFile.flush();
|
||||
}
|
||||
}
|
||||
if(RUN_MODE == 1) {
|
||||
float sensor_array[] = { acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z };
|
||||
String result = recognize(sensor_array);
|
||||
if(result != "NONE") {
|
||||
Serial.println(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_LSM303_U.h>
|
||||
#include <Adafruit_L3GD20_U.h>
|
||||
#include <Adafruit_Sensor.h>
|
||||
|
||||
#include "Adafruit_BLE.h"
|
||||
#include "Adafruit_BluefruitLE_SPI.h"
|
||||
#include "Adafruit_BluefruitLE_UART.h"
|
||||
#include "BluefruitConfig.h"
|
||||
|
||||
// ELL model function
|
||||
// %%HEADER%%
|
||||
|
||||
enum { NONE = 0, NEXT_SLIDE = 1, PREV_SLIDE = 2};
|
||||
|
||||
// Constants
|
||||
const bool useBle = true;
|
||||
const float frequency = 30; // Unit: Hz
|
||||
const String boardNum = "COM2"; // Serial number
|
||||
const int buttonPin = 9;
|
||||
const int rate = (int)(1000.0/frequency);
|
||||
|
||||
const int ledPin1 = 13;
|
||||
const int ledPin2 = 6;
|
||||
|
||||
// Sensor configuration variables
|
||||
Adafruit_LSM303_Accel_Unified accel = Adafruit_LSM303_Accel_Unified(54321);
|
||||
Adafruit_LSM303_Mag_Unified mag = Adafruit_LSM303_Mag_Unified(12345);
|
||||
Adafruit_L3GD20_Unified gyro = Adafruit_L3GD20_Unified(20);
|
||||
|
||||
float acc_x, acc_y, acc_z;
|
||||
float gyro_x, gyro_y, gyro_z;
|
||||
|
||||
// global variables for timing
|
||||
uint32_t nextSampleTimeInMs = 0;
|
||||
uint32_t ledOffTimeInMs = 0;
|
||||
enum {OFF, SOLID, BLINK} ledMode = OFF;
|
||||
bool ledIsOn = false;
|
||||
|
||||
#define FACTORYRESET_ENABLE 0
|
||||
|
||||
// Create the bluefruit object, either software serial...uncomment these lines
|
||||
// hardware SPI, using SCK/MOSI/MISO hardware SPI pins and then user selected CS/IRQ/RST
|
||||
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);
|
||||
|
||||
// This function waits until the next valid sample time. If the function is called before the next sample time
|
||||
// it spins until the sample time arrives. If the function is called after the next sample time, it waits until the next valid sample time.
|
||||
uint32_t wait()
|
||||
{
|
||||
while(true) {
|
||||
uint32_t t = millis();
|
||||
if(t >= nextSampleTimeInMs) {
|
||||
nextSampleTimeInMs = t + rate;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ledOn()
|
||||
{
|
||||
digitalWrite(ledPin1, HIGH);
|
||||
digitalWrite(ledPin2, LOW);
|
||||
ledIsOn = true;
|
||||
}
|
||||
|
||||
void ledOff()
|
||||
{
|
||||
digitalWrite(ledPin1, LOW);
|
||||
digitalWrite(ledPin2, HIGH);
|
||||
ledIsOn = false;
|
||||
}
|
||||
|
||||
void updateLed(uint32_t currentTime)
|
||||
{
|
||||
if(currentTime > ledOffTimeInMs) {
|
||||
ledMode = OFF;
|
||||
}
|
||||
|
||||
switch(ledMode) {
|
||||
case OFF:
|
||||
ledOff();
|
||||
break;
|
||||
case SOLID:
|
||||
ledOn();
|
||||
break;
|
||||
case BLINK:
|
||||
ledIsOn = !ledIsOn;
|
||||
if(ledIsOn)
|
||||
ledOn();
|
||||
else
|
||||
ledOff();
|
||||
}
|
||||
}
|
||||
|
||||
void printError(const __FlashStringHelper* err) {
|
||||
Serial.println(err);
|
||||
while (1);
|
||||
}
|
||||
|
||||
void blePrint(const char* str)
|
||||
{
|
||||
if(useBle && ble.isConnected())
|
||||
{
|
||||
ble.print("AT+BLEUARTTX=");
|
||||
ble.print(str);
|
||||
ble.println();
|
||||
ble.flush();
|
||||
}
|
||||
}
|
||||
|
||||
int filterPrediction(double prediction)
|
||||
{
|
||||
const int minTimeBeforeRepeat = 1000; // ms
|
||||
static uint32_t lastEventTime = 0;
|
||||
|
||||
uint32_t t = millis();
|
||||
if(prediction != 0.0 && (t-lastEventTime > minTimeBeforeRepeat)) {
|
||||
lastEventTime = t;
|
||||
return static_cast<int>(prediction);
|
||||
}
|
||||
|
||||
return NONE;
|
||||
}
|
||||
|
||||
void startBle() {
|
||||
// Initialise the BLE module
|
||||
Serial.print(F("Initialising the Bluefruit LE module: "));
|
||||
|
||||
if ( !ble.begin(VERBOSE_MODE) )
|
||||
{
|
||||
printError(F("Couldn't find Bluefruit"));
|
||||
}
|
||||
Serial.println( F("OK!") );
|
||||
|
||||
if (FACTORYRESET_ENABLE)
|
||||
{
|
||||
// Perform a factory reset to make sure everything is in a known state
|
||||
Serial.println(F("Performing a factory reset: "));
|
||||
if ( ! ble.factoryReset() ){
|
||||
printError(F("Couldn't factory reset"));
|
||||
}
|
||||
}
|
||||
|
||||
// Disable command echo from Bluefruit
|
||||
ble.echo(false);
|
||||
|
||||
// Print Bluefruit information
|
||||
Serial.println("Requesting Bluefruit info:");
|
||||
ble.info();
|
||||
|
||||
// Wait for a connection before starting the test
|
||||
Serial.println("Waiting for a BLE connection to continue ...");
|
||||
|
||||
ble.verbose(false); // debug info is a little annoying after this point!
|
||||
|
||||
// Wait for connection to finish
|
||||
while (! ble.isConnected()) {
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Wait for the connection to complete
|
||||
delay(1000);
|
||||
|
||||
Serial.println(F("BLE connected"));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(ledPin1, OUTPUT);
|
||||
digitalWrite(ledPin1, HIGH);
|
||||
pinMode(ledPin2, OUTPUT);
|
||||
digitalWrite(ledPin2, HIGH);
|
||||
|
||||
// Confirm that all of the sensors started
|
||||
gyro.enableAutoRange(true);
|
||||
accel.begin(); // Accelerometer setup
|
||||
gyro.begin(); // Gyroscope setup
|
||||
|
||||
if(useBle)
|
||||
startBle();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
auto currentTime = wait();
|
||||
updateLed(currentTime);
|
||||
|
||||
// Accelerometer & gyro Reading
|
||||
// Get a new sensor event
|
||||
sensors_event_t accelEvent;
|
||||
if(accel.getEvent(&accelEvent)) {
|
||||
acc_x = accelEvent.acceleration.x;
|
||||
acc_y = accelEvent.acceleration.y;
|
||||
acc_z = accelEvent.acceleration.z;
|
||||
}
|
||||
|
||||
// Get a new sensor event
|
||||
sensors_event_t gyroEvent;
|
||||
if(gyro.getEvent(&gyroEvent)) {
|
||||
gyro_x = gyroEvent.gyro.x;
|
||||
gyro_y = gyroEvent.gyro.y;
|
||||
gyro_z = gyroEvent.gyro.z;
|
||||
}
|
||||
|
||||
double sensorArray[] = { acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z };
|
||||
double output[2];
|
||||
predict(sensorArray, output);
|
||||
int prediction = filterPrediction(output[0]); // predicted class (0 == 'none')
|
||||
if(prediction == NONE)
|
||||
return;
|
||||
|
||||
ledOn();
|
||||
ledOffTimeInMs = currentTime + 1000;
|
||||
switch(prediction) {
|
||||
case NEXT_SLIDE:
|
||||
ledMode = SOLID;
|
||||
blePrint("next\\r\\n");
|
||||
break;
|
||||
case PREV_SLIDE:
|
||||
ledMode = BLINK;
|
||||
blePrint("back\\r\\n");
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Sample DTW Model on accelerometer and gyroscope with generated recognizer code.
|
||||
*
|
||||
* Based on Saleema's 1 class DTW, which is:
|
||||
* Based on Donghao's SPRING.ts code which implements the algorithm in
|
||||
* [1] Sakurai, Y., Faloutsos, C., & Yamamuro, M. (2007, April).
|
||||
* Stream monitoring under the time warping distance.
|
||||
* In 2007 IEEE 23rd International Conference on Data Engineering (pp. 1046-1055). IEEE.
|
||||
*
|
||||
* created 9/16/2016
|
||||
* by Donghao Ren
|
||||
*/
|
||||
|
||||
// Some of this configuration and setup is based on the dataCollection code. We might not need all of these.....
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_LSM303_U.h>
|
||||
#include <Adafruit_L3GD20_U.h>
|
||||
#include <Adafruit_Sensor.h>
|
||||
|
||||
// ELL model function
|
||||
// %%HEADER%%
|
||||
|
||||
// Constants
|
||||
const float frequency = 50; // Unit: Hz
|
||||
const String boardNum = "COM2"; // Serial number
|
||||
const int buttonPin = 9;
|
||||
|
||||
const int chipSelect = 4;
|
||||
const int rate = (int)(1000.0/frequency);
|
||||
|
||||
const int ledPin1 = 13;
|
||||
const int ledPin2 = 6;
|
||||
|
||||
// Sensor configuration variables
|
||||
Adafruit_LSM303_Accel_Unified accel = Adafruit_LSM303_Accel_Unified(54321);
|
||||
Adafruit_LSM303_Mag_Unified mag = Adafruit_LSM303_Mag_Unified(12345);
|
||||
Adafruit_L3GD20_Unified gyro = Adafruit_L3GD20_Unified(20);
|
||||
|
||||
float acc_x, acc_y, acc_z;
|
||||
float gyro_x, gyro_y, gyro_z;
|
||||
float mag_x, mag_y, mag_z;
|
||||
|
||||
// global variables for timing
|
||||
uint32_t nextSampleTimeInMs = 0;
|
||||
uint32_t ledOffTimeInMs = 0;
|
||||
|
||||
// This function waits until the next valid sample time. If the function is called before the next sample time
|
||||
// it spins until the sample time arrives. If the function is called after the next sample time, it waits until the next valid sample time.
|
||||
uint32_t wait()
|
||||
{
|
||||
while(true) {
|
||||
uint32_t t = millis();
|
||||
if(t >= nextSampleTimeInMs) {
|
||||
nextSampleTimeInMs = t + rate;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ledOn()
|
||||
{
|
||||
digitalWrite(ledPin1, HIGH);
|
||||
digitalWrite(ledPin2, LOW);
|
||||
}
|
||||
|
||||
void ledOff()
|
||||
{
|
||||
digitalWrite(ledPin1, LOW);
|
||||
digitalWrite(ledPin2, HIGH);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(ledPin1, OUTPUT);
|
||||
digitalWrite(ledPin1, HIGH);
|
||||
pinMode(ledPin2, OUTPUT);
|
||||
digitalWrite(ledPin2, HIGH);
|
||||
|
||||
// Confirm that all of the sensors started
|
||||
gyro.enableAutoRange(true);
|
||||
mag.enableAutoRange(true);
|
||||
accel.begin(); // Accelerometer setup
|
||||
gyro.begin(); // Gyroscope setup
|
||||
mag.begin(); // Magnetometer setup
|
||||
}
|
||||
|
||||
void loop() {
|
||||
auto currentTime = wait();
|
||||
|
||||
if(currentTime > ledOffTimeInMs) {
|
||||
ledOff();
|
||||
}
|
||||
|
||||
/****************** Accelerometer & Compass Reading ******************/
|
||||
// Get a new sensor event
|
||||
sensors_event_t accelEvent;
|
||||
if(accel.getEvent(&accelEvent)) {
|
||||
acc_x = accelEvent.acceleration.x;
|
||||
acc_y = accelEvent.acceleration.y;
|
||||
acc_z = accelEvent.acceleration.z;
|
||||
}
|
||||
|
||||
// Get a new sensor event
|
||||
sensors_event_t gyroEvent;
|
||||
if(gyro.getEvent(&gyroEvent)) {
|
||||
gyro_x = gyroEvent.gyro.x;
|
||||
gyro_y = gyroEvent.gyro.y;
|
||||
gyro_z = gyroEvent.gyro.z;
|
||||
}
|
||||
|
||||
double sensor_array[] = { acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z };
|
||||
double output[2];
|
||||
predict(sensor_array, output);
|
||||
int prediction = static_cast<int>(output[0]); // predicted class (0.0 == 'none')
|
||||
// Serial.println(output[0]);
|
||||
if(prediction != 0) {
|
||||
ledOn();
|
||||
ledOffTimeInMs = currentTime + 500*prediction;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
|
||||
// Some of this configuration and setup is based on the dataCollection code. We might not need all of these.....
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Wire.h>
|
||||
#include <RTClib.h>
|
||||
|
||||
const int buttonPin = 9;
|
||||
|
||||
const int chipSelect = 4;
|
||||
const int ledPin = 13;
|
||||
|
||||
void setup() {
|
||||
|
||||
/******** Button Setup ********************/
|
||||
// Set up pin mode for external modules
|
||||
pinMode(buttonPin, INPUT_PULLUP);
|
||||
pinMode(ledPin, OUTPUT);
|
||||
digitalWrite(ledPin, HIGH);
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(ledPin, HIGH);
|
||||
delay(200);
|
||||
digitalWrite(ledPin, LOW);
|
||||
delay(600);
|
||||
digitalWrite(ledPin, HIGH);
|
||||
delay(200);
|
||||
digitalWrite(ledPin, LOW);
|
||||
delay(600);
|
||||
digitalWrite(ledPin, HIGH);
|
||||
delay(600);
|
||||
digitalWrite(ledPin, LOW);
|
||||
delay(400);
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -8,10 +8,9 @@
|
|||
"start": "cross-env NODE_ENV=production electron main.js",
|
||||
"run": "cross-env NODE_ENV=production electron main.js",
|
||||
"debug": "cross-env NODE_ENV=debug electron main.js --debugging --remote-debugging-port=9222 ",
|
||||
"build": "npm run build:ts && npm run build:worker && npm run build:less",
|
||||
"build": "npm run build:ts && npm run build:less",
|
||||
"build:ts": "tsc && tsc -p app",
|
||||
"build:less": "lessc app/less/styles.less app/css/styles.compiled.css",
|
||||
"build:worker": "browserify app/js/suggestion/worker/dtwsuggestionWorker.js -o app/js/suggestion/worker/suggestionWorker.browserified.js",
|
||||
"prepublish": "npm run build",
|
||||
"package:win32": "electron-packager . --overwrite --platform=win32 --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged",
|
||||
"package:darwin": "electron-packager . --overwrite --platform=darwin --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged"
|
||||
|
@ -37,7 +36,6 @@
|
|||
"browserify": "^13.1.1",
|
||||
"d3": "^4.4.0",
|
||||
"electron": "^1.4.12",
|
||||
"emll": "file:../../emllModule",
|
||||
"es6-map": "^0.1.4",
|
||||
"es6-promise": "^3.2.1",
|
||||
"jquery": "^2.2.2",
|
||||
|
|
Загрузка…
Ссылка в новой задаче