This commit is contained in:
Saleema Amershi 2017-02-23 15:48:24 -08:00
Родитель 43f21812e8
Коммит 0dd037e8e4
41 изменённых файлов: 5 добавлений и 4136 удалений

Двоичные данные
app/img/accelerometer_icon.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.4 KiB

Двоичные данные
app/img/gyroscope_icon.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 39 KiB

Двоичные данные
app/img/magnetometer_icon.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 19 KiB

Двоичные данные
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

Просмотреть файл

@ -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",