зеркало из https://github.com/microsoft/ELT.git
Merged PR 208: Merge dev/samershi/fixes to master
This commit is contained in:
Коммит
43f21812e8
28
README.md
28
README.md
|
@ -1,14 +1,28 @@
|
|||
README
|
||||
======
|
||||
|
||||
Requires NodeJS
|
||||
-----------------------------------
|
||||
Windows:
|
||||
- node.js 4.3.2
|
||||
Download from [https://nodejs.org]. Node.js installs npm, the node package manager
|
||||
|
||||
OS X:
|
||||
- node.js
|
||||
homebrew install node (got version 5.0.0) --- this installs node and
|
||||
npm (node package manager)
|
||||
|
||||
Linux:
|
||||
- node.js
|
||||
apt-get node
|
||||
|
||||
|
||||
Installation
|
||||
-----------------------------------
|
||||
To initialize the project and install node packages, run `npm install`
|
||||
|
||||
To build the project, run `npm run build`; you can also build individual parts only:
|
||||
To build the project, run `npm run build`;
|
||||
|
||||
- Web worker: `npm run build:worker`
|
||||
- TypeScript code: `npm run build:ts`
|
||||
- Less CSS code: `npm run build:less`
|
||||
To debug the project, run `npm run debug`;
|
||||
|
||||
To start the project, run `npm start`
|
||||
|
||||
To create a package of the project, run `npm run package`
|
||||
To run the project, run `npm start`
|
||||
|
|
|
@ -78,6 +78,15 @@
|
|||
.tbtn-style-mixin(#d9534f, white);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.flipped-icon {
|
||||
transform: scaleX(-1);
|
||||
-webkit-transform: scaleX(-1);
|
||||
}
|
||||
|
||||
// Button groups.
|
||||
.tbtn-group {
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
.labeling-detailed-view {
|
||||
position: absolute;
|
||||
top: 50px; left: 0;
|
||||
.time-cursor { // FIXME: seems to only be used in referenceTrackOverview (why is this a class of labeling-detailed-view?
|
||||
.time-cursor {
|
||||
line {
|
||||
stroke: black;
|
||||
fill: none;
|
||||
|
@ -257,7 +257,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME (remove when we remove slider)
|
||||
.confidence-slider {
|
||||
rect.histogram-bin {
|
||||
fill: #CCC;
|
||||
|
|
|
@ -1,20 +1,13 @@
|
|||
// The main view for the app.
|
||||
|
||||
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 './menus/HomeMenu';
|
||||
import { TabPane } from './TabPane';
|
||||
import { HomeMenu } from './home/HomeMenu';
|
||||
import { remote } from 'electron';
|
||||
import { observer } from 'mobx-react';
|
||||
// tslint:disable-next-line:import-name
|
||||
import DevTools from 'mobx-react-devtools';
|
||||
import * as React from 'react';
|
||||
|
||||
|
||||
|
||||
// Labeling app has some configuration code, then it calls LabelingView.
|
||||
@observer
|
||||
export class App extends React.Component<{}, {}> {
|
||||
constructor(props: {}, context: any) {
|
||||
|
@ -64,11 +57,17 @@ export class App extends React.Component<{}, {}> {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: TabPane just refers to alignment/labeling relocate or split like other tabs
|
||||
public render(): JSX.Element {
|
||||
const debugging = remote.getGlobal('debugging');
|
||||
return (
|
||||
<div className='app-container container-fluid'>
|
||||
<DevTools />
|
||||
|
||||
{debugging ?
|
||||
// The following weird construct is to ensure that the devtools
|
||||
// module is only loaded when the debugging flag is true.
|
||||
// tslint:disable-next-line:no-require-imports
|
||||
(DevTools => <DevTools />)(require('mobx-react-devtools').default) : null}
|
||||
|
||||
<NavigationColumn selected={stores.projectUiStore.currentTab} onSelect={
|
||||
tab => {
|
||||
stores.projectUiStore.currentTab = tab as TabID;
|
||||
|
@ -78,13 +77,13 @@ export class App extends React.Component<{}, {}> {
|
|||
<HomeMenu />
|
||||
</NavigationColumnItem>
|
||||
<NavigationColumnItem title='Alignment' name='alignment' showButton={true} iconClass={'glyphicon glyphicon-time'}>
|
||||
<TabPane mode='alignment' toolbarHeight={40}/>
|
||||
<SharedAlignmentLabelingPane mode='alignment' toolbarHeight={40} />
|
||||
</NavigationColumnItem>
|
||||
<NavigationColumnItem title='Labeling' name='labeling' showButton={true} iconClass={'glyphicon glyphicon-tags'}>
|
||||
<TabPane mode='labeling' toolbarHeight={40}/>
|
||||
<SharedAlignmentLabelingPane mode='labeling' toolbarHeight={40} />
|
||||
</NavigationColumnItem>
|
||||
<NavigationColumnItem title='Deployment' name='deploying' iconClass='glyphicon glyphicon-export'>
|
||||
<DeploymentPanel toolbarHeight={40}/>
|
||||
<DeploymentPanel toolbarHeight={40} />
|
||||
</NavigationColumnItem>
|
||||
</NavigationColumn>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// Alignment toolbar view.
|
||||
// - Toolbar buttons for alignment.
|
||||
import { alignmentStore, projectStore } from '../../stores/stores';
|
||||
import { projectStore } from '../../stores/stores';
|
||||
import { OptionsToolbar } from '../common/OptionsToolbar';
|
||||
import { remote } from 'electron';
|
||||
import { observer } from 'mobx-react';
|
||||
|
@ -29,7 +27,7 @@ export class AlignmentToolbar extends React.Component<AlignmentToolbarProps, {}>
|
|||
{
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Videos', extensions: ['mov', 'mp4', 'webm'] },
|
||||
{ name: 'Videos', extensions: ['mov', 'mp4', 'webm'] }
|
||||
]
|
||||
},
|
||||
(fileNames: string[]) => {
|
||||
|
@ -76,9 +74,9 @@ export class AlignmentToolbar extends React.Component<AlignmentToolbarProps, {}>
|
|||
<span className='glyphicon glyphicon-folder-open'></span>Load Reference Video...
|
||||
</button>
|
||||
<button className='tbtn tbtn-l1'
|
||||
title='Load a sensor track from collected data'
|
||||
title='Load collected sensor data'
|
||||
onClick={this.loadDataOrVideo}>
|
||||
<span className='glyphicon glyphicon-folder-open'></span>Load Video or Sensor...
|
||||
<span className='glyphicon glyphicon-folder-open'></span>Load Sensor Data...
|
||||
</button>
|
||||
<OptionsToolbar />
|
||||
</div>
|
||||
|
|
|
@ -72,12 +72,6 @@ export class AlignmentView extends React.Component<AlignmentViewProps, Alignment
|
|||
}
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode === 'Z'.charCodeAt(0)) {
|
||||
stores.projectStore.alignmentUndo();
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode === 'Y'.charCodeAt(0)) {
|
||||
stores.projectStore.alignmentRedo();
|
||||
}
|
||||
}
|
||||
|
||||
private onTrackMouseDown(
|
||||
|
@ -227,7 +221,7 @@ export class AlignmentView extends React.Component<AlignmentViewProps, Alignment
|
|||
const map = new Map<string, TrackLayout>();
|
||||
|
||||
let trackYCurrent = 50;
|
||||
const trackMinimizedHeight = 40; // FIXME: I don't think minimized is every used
|
||||
const trackMinimizedHeight = 40;
|
||||
|
||||
const referenceTrack = stores.projectStore.referenceTrack;
|
||||
if (referenceTrack) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { SignalsViewMode } from '../../stores/dataStructures/labeling';
|
||||
import { KeyCode } from '../../stores/dataStructures/types';
|
||||
import * as stores from '../../stores/stores';
|
||||
import { projectStore, projectUiStore } from '../../stores/stores';
|
||||
import { observer } from 'mobx-react';
|
||||
|
@ -7,22 +8,48 @@ import * as React from 'react';
|
|||
@observer
|
||||
export class OptionsToolbar extends React.Component<{}, {}> {
|
||||
|
||||
private setViewModeThunk: { [viewMode: number]: () => void } = {};
|
||||
private changeFadeVideo(): void {
|
||||
projectStore.fadeBackground(!projectStore.shouldFadeVideoBackground);
|
||||
}
|
||||
|
||||
constructor(props: {}, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
Object.keys(SignalsViewMode).forEach(name => {
|
||||
const val = SignalsViewMode[name];
|
||||
this.setViewModeThunk[val] = this.setViewMode.bind(this, val);
|
||||
});
|
||||
}
|
||||
|
||||
private setViewModeThunk: { [viewMode: number]: () => void } = {};
|
||||
private changeFadeVideo(): void {
|
||||
projectStore.fadeBackground(!projectStore.shouldFadeVideoBackground);
|
||||
}
|
||||
|
||||
private changeTimeSeriesColor(): void {
|
||||
projectUiStore.timeSeriesGrayscale = !projectUiStore.timeSeriesGrayscale;
|
||||
}
|
||||
private onKeyDown(event: KeyboardEvent): void {
|
||||
if (event.keyCode === KeyCode.LEFT) {
|
||||
stores.projectUiStore.zoomReferenceTrackByPercentage(-0.6);
|
||||
}
|
||||
if (event.keyCode === KeyCode.RIGHT) {
|
||||
stores.projectUiStore.zoomReferenceTrackByPercentage(+0.6);
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode === 'Z'.charCodeAt(0)) { // Ctrl-Z
|
||||
stores.projectStore.undo();
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode === 'Y'.charCodeAt(0)) { // Ctrl-Y
|
||||
stores.projectStore.redo();
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
window.addEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.removeEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
private setViewMode(viewMode: SignalsViewMode): void {
|
||||
stores.labelingUiStore.setSignalsViewMode(viewMode);
|
||||
stores.labelingUiStore.signalsViewMode = viewMode;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
|
@ -31,17 +58,35 @@ export class OptionsToolbar extends React.Component<{}, {}> {
|
|||
};
|
||||
return (
|
||||
<div className='pull-right'>
|
||||
<button className='tbtn tbtn-l3'
|
||||
onClick={stores.projectStore.labelingUndo}>
|
||||
Undo
|
||||
<button className={'tbtn tbtn-l3' + (stores.projectStore.canUndo ? '' : ' disabled')}
|
||||
onClick={stores.projectStore.undo} title='Undo' >
|
||||
<span className='glyphicon icon-only glyphicon-share-alt flipped-icon'></span>
|
||||
</button>
|
||||
<button className={'tbtn tbtn-l3' + (stores.projectStore.canRedo ? '' : ' disabled')}
|
||||
onClick={stores.projectStore.redo} title='Redo'>
|
||||
<span className='glyphicon icon-only glyphicon-share-alt'></span>
|
||||
</button>
|
||||
<span className='sep' />
|
||||
<span className='message' style={{marginRight: '5pt'}}>{projectStore.statusMessage}</span>
|
||||
<div className='btn-group'>
|
||||
<button className='tbtn tbtn-l3 dropdown-toggle' data-toggle='dropdown' title='Options'>
|
||||
<span className='glyphicon icon-only glyphicon-cog'></span>
|
||||
</button>
|
||||
<ul className='dropdown-menu options-menu'>
|
||||
<li className='dropdown-header'>Signals Display</li>
|
||||
<li className='dropdown-header'>Time Series Color</li>
|
||||
<li className='option-item'
|
||||
role='button'
|
||||
onClick={this.changeTimeSeriesColor}>
|
||||
<span className='glyphicon icon-only glyphicon-ok'
|
||||
style={{
|
||||
visibility: projectUiStore.timeSeriesGrayscale ? 'visible' : 'hidden',
|
||||
marginRight: '5pt'}
|
||||
}>
|
||||
</span>
|
||||
Grayscale
|
||||
</li>
|
||||
<li role='separator' className='divider'></li>
|
||||
<li className='dropdown-header'>Signals Display Type</li>
|
||||
<li className='option-item'
|
||||
role='button'
|
||||
onClick={this.setViewModeThunk[SignalsViewMode.TIMESERIES]}>
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
// The 'Overview' view that is shared by both alignment and labeling.
|
||||
|
||||
import { getLabelKey } from '../../stores/dataStructures/labeling';
|
||||
import { PanZoomParameters } from '../../stores/dataStructures/PanZoomParameters';
|
||||
import { KeyCode } from '../../stores/dataStructures/types';
|
||||
import * as stores from '../../stores/stores';
|
||||
import { makePathDFromPoints, startDragging } from '../../stores/utils';
|
||||
import { LabelType, LabelView } from '../labeling/LabelView';
|
||||
|
@ -20,7 +17,7 @@ export interface ReferenceTrackOverviewProps {
|
|||
downReach: number;
|
||||
}
|
||||
|
||||
|
||||
// The 'Overview' view that is shared by both alignment and labeling.
|
||||
@observer
|
||||
export class ReferenceTrackOverview extends React.Component<ReferenceTrackOverviewProps, {}> {
|
||||
public refs: {
|
||||
|
@ -30,18 +27,7 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
|
|||
|
||||
constructor(props: ReferenceTrackOverviewProps, context: any) {
|
||||
super(props, context);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
private onKeyDown(event: KeyboardEvent): void {
|
||||
if (event.srcElement === document.body) {
|
||||
if (event.keyCode === KeyCode.LEFT) {
|
||||
stores.projectUiStore.zoomReferenceTrackByPercentage(-0.6);
|
||||
}
|
||||
if (event.keyCode === KeyCode.RIGHT) {
|
||||
stores.projectUiStore.zoomReferenceTrackByPercentage(+0.6);
|
||||
}
|
||||
}
|
||||
// this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
private onMouseWheel(event: React.WheelEvent<Element>): void {
|
||||
|
@ -58,14 +44,6 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
|
|||
new PanZoomParameters(t - timeWindow / 2, null), true);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
window.addEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.removeEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
private raiseOnDrag(mode: string, t0: number, t1: number): void {
|
||||
const newStart = Math.min(t0, t1);
|
||||
const newEnd = Math.max(t0, t1);
|
||||
|
@ -154,17 +132,17 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
|
|||
if (this.props.mode === 'labeling') {
|
||||
labelsView = (
|
||||
<g transform={`translate(${-globalPanZoom.pixelsPerSecond * globalPanZoom.rangeStart},0)`}>
|
||||
{labels.map(label =>
|
||||
<LabelView
|
||||
key={getLabelKey(label)}
|
||||
label={label}
|
||||
pixelsPerSecond={globalPanZoom.pixelsPerSecond}
|
||||
height={labelsY1 - labelsY0}
|
||||
classColormap={stores.labelingStore.classColormap}
|
||||
labelType={LabelType.Overview}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
{labels.map(label =>
|
||||
<LabelView
|
||||
key={getLabelKey(label)}
|
||||
label={label}
|
||||
pixelsPerSecond={globalPanZoom.pixelsPerSecond}
|
||||
height={labelsY1 - labelsY0}
|
||||
classColormap={stores.labelingStore.classColormap}
|
||||
labelType={LabelType.Overview}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -222,10 +200,11 @@ export class ReferenceTrackOverview extends React.Component<ReferenceTrackOvervi
|
|||
</g>
|
||||
</g>
|
||||
|
||||
<g className='time-cursor' transform='translate(0, 0)'>
|
||||
<line className='bg' x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
|
||||
<line x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
|
||||
</g>
|
||||
{!isNaN(cursorX) ?
|
||||
<g className='time-cursor' transform='translate(0, 0)'>
|
||||
<line className='bg' x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
|
||||
<line x1={cursorX} y1={0} x2={cursorX} y2={this.props.viewHeight} />
|
||||
</g> : null}
|
||||
|
||||
<g className='brackets' transform='translate(0, 0)'>
|
||||
<rect x={rangeX0} y={0} width={rangeX1 - rangeX0} height={this.props.viewHeight}
|
||||
|
|
|
@ -14,7 +14,8 @@ const mipmapCache = new MipmapCache();
|
|||
export interface SensorPlotProps {
|
||||
timeSeries: SensorTimeSeries; // The timeseries object to show, replace it with a NEW object if you need to update its contents.
|
||||
dimensionVisibility?: boolean[]; // boolean array to show/hide individual dimensions.
|
||||
colorScale?: d3.ScaleOrdinal<number, string>; // Colors to use, if null, use d3.category10 or 20.
|
||||
grayscale: boolean;
|
||||
//colorScale?: d3.ScaleOrdinal<number, string>; // Colors to use, if null, use d3.category10 or 20.
|
||||
pixelsPerSecond: number; // Scaling factor (assume the dataset's timeunit is seconds).
|
||||
plotHeight: number; // The height of the plot.
|
||||
yDomain?: number[]; // The y domain: [ min, max ], similar to D3's scale.domain([min, max]).
|
||||
|
@ -28,7 +29,7 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
|
|||
// We consider the timeSeries object and colorScale constant, so any change INSIDE these objects will not trigger an update.
|
||||
// To change the timeSeries, replace it with another object, don't update it directly.
|
||||
return nextProps.timeSeries !== this.props.timeSeries ||
|
||||
nextProps.colorScale !== this.props.colorScale ||
|
||||
nextProps.grayscale !== this.props.grayscale ||
|
||||
nextProps.pixelsPerSecond !== this.props.pixelsPerSecond ||
|
||||
nextProps.plotHeight !== this.props.plotHeight ||
|
||||
nextProps.alternateDimensions !== this.props.alternateDimensions ||
|
||||
|
@ -61,8 +62,7 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
|
|||
const yScale = 1 / (y0 - y1) * this.props.plotHeight;
|
||||
|
||||
// Determine color scale.
|
||||
const colors = this.props.colorScale ||
|
||||
(numSeries <= 10 ?
|
||||
const colors = (numSeries <= 10 ?
|
||||
d3.scaleOrdinal<number, string>(d3.schemeCategory10) :
|
||||
d3.scaleOrdinal<number, string>(d3.schemeCategory20));
|
||||
|
||||
|
@ -87,16 +87,19 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
|
|||
}
|
||||
}
|
||||
const d: string = 'M' + positions.join('L');
|
||||
const style = {
|
||||
fill: 'none',
|
||||
stroke: colors(dimIndex)
|
||||
};
|
||||
|
||||
// const grayscale = .6 - dimIndex * ((.6 - .2) / dimensions.length); //grayscale range from .6-.2 (dark to light)
|
||||
// const style = {
|
||||
// fill: 'none',
|
||||
// stroke: 'rgba(0, 0, 0, ' + grayscale + ')'
|
||||
// };
|
||||
const grayscale = .6 - dimIndex * ((.6 - .2) / dimensions.length); //grayscale range from .6-.2 (dark to light)
|
||||
let style = {
|
||||
fill: 'none',
|
||||
stroke: 'rgba(0, 0, 0, ' + grayscale + ')'
|
||||
};
|
||||
if ( !this.props.grayscale) {
|
||||
style = {
|
||||
fill: 'none',
|
||||
stroke: colors(dimIndex)
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<path
|
||||
d={d} style={style} key={dimIndex}
|
||||
|
@ -118,7 +121,7 @@ export class SensorPlot extends React.Component<SensorPlotProps, {}> {
|
|||
export interface SensorRangePlotProps {
|
||||
timeSeries: SensorTimeSeries; // The timeseries object to show, replace it with a NEW object if you need to update its contents.
|
||||
dimensionVisibility?: boolean[]; // boolean array to show/hide individual dimensions.
|
||||
colorScale?: any; // Colors to use, if null, use d3.category10 or 20.
|
||||
grayscale: boolean; // Colors to use, if null, use d3.category10 or 20.
|
||||
pixelsPerSecond: number; // Scaling factor (assume the dataset's timeunit is seconds).
|
||||
plotWidth: number; // The width of the plot.
|
||||
plotHeight: number; // The height of the plot.
|
||||
|
@ -135,7 +138,7 @@ export class SensorRangePlot extends React.Component<SensorRangePlotProps, {}> {
|
|||
|
||||
public shouldComponentUpdate(nextProps: SensorRangePlotProps): boolean {
|
||||
return nextProps.timeSeries !== this.props.timeSeries ||
|
||||
nextProps.colorScale !== this.props.colorScale ||
|
||||
nextProps.grayscale !== this.props.grayscale ||
|
||||
nextProps.pixelsPerSecond !== this.props.pixelsPerSecond ||
|
||||
nextProps.plotHeight !== this.props.plotHeight ||
|
||||
!isSameArray(nextProps.yDomain, this.props.yDomain) ||
|
||||
|
@ -225,7 +228,7 @@ export class SensorRangePlot extends React.Component<SensorRangePlotProps, {}> {
|
|||
<SensorPlot
|
||||
key={`slice-${slice[0]}`}
|
||||
timeSeries={timeseries}
|
||||
colorScale={this.props.colorScale}
|
||||
grayscale={this.props.grayscale}
|
||||
pixelsPerSecond={this.props.pixelsPerSecond}
|
||||
plotHeight={this.props.plotHeight}
|
||||
yDomain={this.props.yDomain}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {KeyCode} from '../stores/dataStructures/types';
|
||||
import * as stores from '../stores/stores';
|
||||
import {AlignmentToolbar} from './alignment/AlignmentToolbar';
|
||||
import {AlignmentView} from './alignment/AlignmentView';
|
||||
import {LabelingToolbar} from './labeling/LabelingToolbar';
|
||||
import {LabelingView} from './labeling/LabelingView';
|
||||
import {ReferenceTrackDetail} from './common/ReferenceTrackDetail';
|
||||
import {ReferenceTrackOverview} from './common/ReferenceTrackOverview';
|
||||
import {KeyCode} from '../../stores/dataStructures/types';
|
||||
import * as stores from '../../stores/stores';
|
||||
import {AlignmentToolbar} from '../alignment/AlignmentToolbar';
|
||||
import {AlignmentView} from '../alignment/AlignmentView';
|
||||
import {ReferenceTrackDetail} from '../common/ReferenceTrackDetail';
|
||||
import {ReferenceTrackOverview} from '../common/ReferenceTrackOverview';
|
||||
import {LabelingToolbar} from '../labeling/LabelingToolbar';
|
||||
import {LabelingView} from '../labeling/LabelingView';
|
||||
import * as React from 'react';
|
||||
|
||||
|
||||
|
@ -18,7 +18,6 @@ interface AlignmentLabelingViewState {
|
|||
layout: AlignmentLabelingViewLayout;
|
||||
}
|
||||
|
||||
// FIXME: why are these not state variables?
|
||||
interface AlignmentLabelingViewLayout {
|
||||
viewWidth: number;
|
||||
viewHeight: number;
|
||||
|
@ -46,8 +45,7 @@ interface AlignmentLabelingViewLayout {
|
|||
detailedViewY1: number;
|
||||
}
|
||||
|
||||
// FIXME: this is not really a tabpane, its the tab for both alignment and labeling (see props/state names)
|
||||
export class TabPane extends React.Component<AlignmentLabelingViewProps, AlignmentLabelingViewState> {
|
||||
export class SharedAlignmentLabelingPane extends React.Component<AlignmentLabelingViewProps, AlignmentLabelingViewState> {
|
||||
public refs: {
|
||||
[key: string]: Element,
|
||||
container: Element,
|
||||
|
@ -106,7 +104,6 @@ export class TabPane extends React.Component<AlignmentLabelingViewProps, Alignme
|
|||
}
|
||||
|
||||
public computeLayoutAttributes(viewWidth: number, viewHeight: number, props: AlignmentLabelingViewProps): AlignmentLabelingViewLayout {
|
||||
// FIXME: rename svgs
|
||||
// Layout parameters.
|
||||
const overviewDetailsSVGXPadding1 = 8;
|
||||
const overviewDetailsSVGXPadding2 = 8 + 20;
|
||||
|
@ -233,8 +230,8 @@ export class TabPane extends React.Component<AlignmentLabelingViewProps, Alignme
|
|||
<AlignmentView ref='alignmentView'
|
||||
viewWidth={layout.detailedViewX1 - layout.detailedViewX0}
|
||||
viewHeight={layout.detailedViewY1 - layout.detailedViewY0}
|
||||
trackHeight={250} // FIXME: should this go in App?
|
||||
trackGap={40} // FIXME: should this go in App?
|
||||
trackHeight={250}
|
||||
trackGap={40}
|
||||
referenceDetailedViewHeight={layout.referenceDetailedViewHeight}
|
||||
timeAxisHeight={layout.timeAxisHeight}
|
||||
/>
|
|
@ -20,7 +20,6 @@ export interface TrackViewProps {
|
|||
viewHeight: number;
|
||||
zoomTransform: PanZoomParameters;
|
||||
signalsViewMode?: SignalsViewMode;
|
||||
colorScale?: any;
|
||||
useMipmap?: boolean;
|
||||
videoDetail?: boolean;
|
||||
|
||||
|
@ -70,7 +69,6 @@ export class TrackView extends React.Component<TrackViewProps, {}> {
|
|||
startX={startX} endX={endX}
|
||||
height={seriesHeight}
|
||||
startTime={startTime} endTime={endTime}
|
||||
colorScale={this.props.colorScale}
|
||||
useMipmap={this.props.useMipmap}
|
||||
videoDetail={this.props.videoDetail}
|
||||
onMouseDown={this.props.onMouseDown}
|
||||
|
@ -97,7 +95,6 @@ export interface TimeSeriesViewProps {
|
|||
height: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
colorScale?: any;
|
||||
useMipmap?: boolean;
|
||||
videoDetail?: boolean;
|
||||
signalsViewMode?: SignalsViewMode;
|
||||
|
@ -231,7 +228,7 @@ export class TimeSeriesView extends React.Component<TimeSeriesViewProps, {}> {
|
|||
rangeStart={this.props.startTime} pixelsPerSecond={this.pixelsPerSecond}
|
||||
plotWidth={this.width} plotHeight={this.props.height}
|
||||
useMipmap={this.props.useMipmap}
|
||||
colorScale={this.props.colorScale}
|
||||
grayscale={stores.projectUiStore.timeSeriesGrayscale}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -281,7 +278,7 @@ export class TimeSeriesView extends React.Component<TimeSeriesViewProps, {}> {
|
|||
onWheel={this.onWheel}
|
||||
/>
|
||||
{
|
||||
timeCursor != null ? (
|
||||
timeCursor != null && !isNaN(timeCursor) ? (
|
||||
<line
|
||||
x1={timeCursorX} y1={0}
|
||||
x2={timeCursorX} y2={this.props.height}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
|
|||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
|
||||
// FIXME: rename menus to home
|
||||
// The 'Home' menu.
|
||||
@observer
|
||||
export class HomeMenu extends React.Component<{}, {}> {
|
|
@ -123,7 +123,6 @@ export class LabelView extends React.Component<LabelViewProps, LabelViewState> {
|
|||
this.props.label.state === LabelConfirmationState.CONFIRMED_BOTH;
|
||||
}
|
||||
|
||||
// FIXME: what does this do?
|
||||
private getSuggestionConfidenceOrOne(): number {
|
||||
const suggestionConfidence = this.props.label.suggestionConfidence;
|
||||
if (suggestionConfidence && this.props.label.state !== LabelConfirmationState.CONFIRMED_BOTH) {
|
||||
|
|
|
@ -46,12 +46,6 @@ export class LabelingView extends React.Component<LabelingViewProps, LabelingVie
|
|||
}
|
||||
}
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode === 'Z'.charCodeAt(0)) { // Ctrl-Z
|
||||
stores.projectStore.labelingUndo();
|
||||
}
|
||||
if (event.ctrlKey && event.keyCode === 'Y'.charCodeAt(0)) { // Ctrl-Y
|
||||
stores.projectStore.labelingRedo();
|
||||
}
|
||||
}
|
||||
|
||||
private getRelativePosition(event: { clientX: number; clientY: number }): number[] {
|
||||
|
|
|
@ -24,13 +24,13 @@ export class AlignmentStore {
|
|||
}
|
||||
|
||||
@action public addMarker(marker: Marker): void {
|
||||
projectStore.alignmentHistoryRecord();
|
||||
projectStore.recordAlignmentSnapshot();
|
||||
this.markers.push(marker);
|
||||
}
|
||||
|
||||
@action public updateMarker(marker: Marker, newLocalTimestamp: number, recompute: boolean = true, recordState: boolean = true): void {
|
||||
if (recordState) {
|
||||
projectStore.alignmentHistoryRecord();
|
||||
projectStore.recordAlignmentSnapshot();
|
||||
}
|
||||
marker.localTimestamp = newLocalTimestamp;
|
||||
if (recompute) {
|
||||
|
@ -39,7 +39,7 @@ export class AlignmentStore {
|
|||
}
|
||||
|
||||
@action public deleteMarker(marker: Marker): void {
|
||||
projectStore.alignmentHistoryRecord();
|
||||
projectStore.recordAlignmentSnapshot();
|
||||
const index = this.markers.indexOf(marker);
|
||||
if (index >= 0) {
|
||||
this.markers.splice(index, 1);
|
||||
|
@ -50,7 +50,7 @@ export class AlignmentStore {
|
|||
}
|
||||
|
||||
@action public addMarkerCorrespondence(marker1: Marker, marker2: Marker): MarkerCorrespondence {
|
||||
projectStore.alignmentHistoryRecord();
|
||||
projectStore.recordAlignmentSnapshot();
|
||||
const newCorr = new MarkerCorrespondence(marker1, marker2);
|
||||
// Remove all conflicting correspondence.
|
||||
this.correspondences = this.correspondences.filter(c => c.compatibleWith(newCorr));
|
||||
|
@ -60,7 +60,7 @@ export class AlignmentStore {
|
|||
}
|
||||
|
||||
@action public deleteMarkerCorrespondence(correspondence: MarkerCorrespondence): void {
|
||||
projectStore.alignmentHistoryRecord();
|
||||
projectStore.recordAlignmentSnapshot();
|
||||
const index = this.correspondences.indexOf(correspondence);
|
||||
if (index >= 0) {
|
||||
this.correspondences.splice(index, 1);
|
||||
|
@ -113,7 +113,6 @@ export class AlignmentStore {
|
|||
return blocks;
|
||||
}
|
||||
|
||||
|
||||
public alignAllTracks(animate: boolean = false): void {
|
||||
if (this.correspondences.length === 0) { return; }
|
||||
projectStore.tracks.forEach(track => {
|
||||
|
@ -172,7 +171,7 @@ export class AlignmentStore {
|
|||
track.referenceStart = tsState.referenceStart;
|
||||
track.referenceEnd = tsState.referenceEnd;
|
||||
});
|
||||
this.alignAllTracks(false);
|
||||
//this.alignAllTracks(false);
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
|
|
|
@ -53,10 +53,6 @@ export class LabelingStore {
|
|||
return this._labelsIndex.items;
|
||||
}
|
||||
|
||||
@computed public get suggestions(): Label[] {
|
||||
return this._suggestedLabelsIndex.items;
|
||||
}
|
||||
|
||||
public getLabelsInRange(timeRange: TimeRange): Label[] {
|
||||
return mergeTimeRangeArrays(
|
||||
this._labelsIndex.getRangesInRange(timeRange),
|
||||
|
@ -65,12 +61,12 @@ export class LabelingStore {
|
|||
|
||||
|
||||
@action public addLabel(label: Label): void {
|
||||
projectStore.recordLabelingSnapshot();
|
||||
this._labelsIndex.add(label);
|
||||
projectStore.labelingHistoryRecord();
|
||||
}
|
||||
|
||||
@action public removeLabel(label: Label): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
if (this._labelsIndex.has(label)) {
|
||||
this._labelsIndex.remove(label);
|
||||
}
|
||||
|
@ -80,7 +76,7 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
@action public updateLabel(label: Label, newLabel: PartialLabel): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
// Update the label info.
|
||||
if (newLabel.timestampStart !== undefined) { label.timestampStart = newLabel.timestampStart; }
|
||||
if (newLabel.timestampEnd !== undefined) { label.timestampEnd = newLabel.timestampEnd; }
|
||||
|
@ -169,13 +165,13 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
@action public removeAllLabels(): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
this._labelsIndex.clear();
|
||||
this._suggestedLabelsIndex.clear();
|
||||
}
|
||||
|
||||
@action public addClass(className: string): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
if (this.classes.indexOf(className) < 0) {
|
||||
this.classes.push(className);
|
||||
this.updateColors();
|
||||
|
@ -183,7 +179,7 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
@action public removeClass(className: string): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
// Remove the labels of that class.
|
||||
const toRemove = [];
|
||||
this._labelsIndex.forEach(label => {
|
||||
|
@ -204,7 +200,7 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
@action public renameClass(oldClassName: string, newClassName: string): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
if (this.classes.indexOf(newClassName) < 0) {
|
||||
let renamed = false;
|
||||
this._labelsIndex.forEach(label => {
|
||||
|
@ -230,7 +226,7 @@ export class LabelingStore {
|
|||
}
|
||||
|
||||
@action public confirmVisibleSuggestions(): void {
|
||||
projectStore.labelingHistoryRecord();
|
||||
projectStore.recordLabelingSnapshot();
|
||||
// Get visible suggestions.
|
||||
let visibleSuggestions = this._suggestedLabelsIndex.getRangesInRange(
|
||||
projectUiStore.referenceTrackTimeRange);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Label, SignalsViewMode, TimeRange } from './dataStructures/labeling';
|
|||
import { ObservableSet } from './dataStructures/ObservableSet';
|
||||
import { LabelingStore } from './LabelingStore';
|
||||
import { labelingStore } from './stores';
|
||||
import { action, computed, observable } from 'mobx';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
|
||||
export class LabelingUiStore {
|
||||
|
@ -90,10 +90,6 @@ export class LabelingUiStore {
|
|||
this.suggestionLogic = getLabelingSuggestionLogic(logic);
|
||||
}
|
||||
|
||||
@action public setSignalsViewMode(mode: SignalsViewMode): void {
|
||||
this.signalsViewMode = mode;
|
||||
}
|
||||
|
||||
public getLabelsInRange(timeRange: TimeRange): Label[] {
|
||||
const labels = labelingStore.getLabelsInRange(timeRange);
|
||||
return labels.filter(l => !this.selectedLabels.has(l)).concat(
|
||||
|
|
|
@ -5,10 +5,10 @@ import { Track } from './dataStructures/alignment';
|
|||
import { loadMultipleSensorTimeSeriesFromFile, loadRawSensorTimeSeriesFromFile, loadVideoTimeSeriesFromFile }
|
||||
from './dataStructures/dataset';
|
||||
import { DeferredCallbacks } from './dataStructures/DeferredCallbacks';
|
||||
import { HistoryTracker } from './dataStructures/HistoryTracker';
|
||||
import { PanZoomParameters } from './dataStructures/PanZoomParameters';
|
||||
import { SavedAlignmentSnapshot, SavedLabelingSnapshot, SavedProject, SavedTrack }
|
||||
from './dataStructures/project';
|
||||
import { UndoRedoHistory } from './dataStructures/UndoRedoHistory';
|
||||
import { convertToWebm, fadeBackground, isWebm } from './dataStructures/video';
|
||||
import { alignmentStore, labelingStore, projectUiStore } from './stores';
|
||||
import * as fs from 'fs';
|
||||
|
@ -49,16 +49,19 @@ export class ProjectStore {
|
|||
|
||||
|
||||
// Stores alignment and labeling history (undo is implemented separately, you can't undo alignment from labeling or vice versa).
|
||||
private _alignmentHistory: HistoryTracker<SavedAlignmentSnapshot>;
|
||||
private _labelingHistory: HistoryTracker<SavedLabelingSnapshot>;
|
||||
private _alignmentUndoRedoHistory: UndoRedoHistory<SavedAlignmentSnapshot>;
|
||||
private _labelingUndoRedoHistory: UndoRedoHistory<SavedLabelingSnapshot>;
|
||||
|
||||
constructor() {
|
||||
this._alignmentHistory = new HistoryTracker<SavedAlignmentSnapshot>();
|
||||
this._labelingHistory = new HistoryTracker<SavedLabelingSnapshot>();
|
||||
this._alignmentUndoRedoHistory = new UndoRedoHistory<SavedAlignmentSnapshot>();
|
||||
this._labelingUndoRedoHistory = new UndoRedoHistory<SavedLabelingSnapshot>();
|
||||
this.referenceTrack = null;
|
||||
this.tracks = [];
|
||||
this.projectFileLocation = null;
|
||||
this.statusMessage = '';
|
||||
|
||||
this.undo = this.undo.bind(this);
|
||||
this.redo = this.redo.bind(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -84,15 +87,28 @@ export class ProjectStore {
|
|||
return this.referenceTimestampEnd - this.referenceTimestampStart;
|
||||
}
|
||||
|
||||
@computed public get canUndo(): boolean {
|
||||
const tab = projectUiStore.currentTab;
|
||||
const canUndoAlignment = this._alignmentUndoRedoHistory.canUndo;
|
||||
const canUndoLabeling = this._labelingUndoRedoHistory.canUndo;
|
||||
return tab === 'alignment' && canUndoAlignment || tab === 'labeling' && canUndoLabeling;
|
||||
}
|
||||
|
||||
@computed public get canRedo(): boolean {
|
||||
const tab = projectUiStore.currentTab;
|
||||
const canRedoAlignment = this._alignmentUndoRedoHistory.canRedo;
|
||||
const canRedoLabeling = this._labelingUndoRedoHistory.canRedo;
|
||||
return tab === 'alignment' && canRedoAlignment || tab === 'labeling' && canRedoLabeling;
|
||||
}
|
||||
|
||||
@action public loadReferenceTrack(path: string): void {
|
||||
this.alignmentHistoryRecord();
|
||||
this.recordAlignmentSnapshot();
|
||||
loadVideoTimeSeriesFromFile(path, video => {
|
||||
if (!isWebm(path)) {
|
||||
const newPath = convertToWebm(
|
||||
convertToWebm(
|
||||
path, video.videoDuration,
|
||||
pctDone => {
|
||||
this.statusMessage = 'converting video: ' + (pctDone * 100).toFixed(0) + '%';
|
||||
this.statusMessage = 'converting video: ' + (pctDone * 100).toFixed(0) + '%';
|
||||
},
|
||||
webmVideo => {
|
||||
this.referenceTrack = Track.fromFile(webmVideo.filename, [webmVideo]);
|
||||
|
@ -108,19 +124,19 @@ export class ProjectStore {
|
|||
}
|
||||
|
||||
@action public loadVideoTrack(fileName: string): void {
|
||||
this.alignmentHistoryRecord();
|
||||
this.recordAlignmentSnapshot();
|
||||
loadVideoTimeSeriesFromFile(fileName, video => {
|
||||
this.tracks.push(Track.fromFile(fileName, [video]));
|
||||
});
|
||||
}
|
||||
|
||||
@action public loadSensorTrack(fileName: string): void {
|
||||
this.alignmentHistoryRecord();
|
||||
this.recordAlignmentSnapshot();
|
||||
const sensors = loadMultipleSensorTimeSeriesFromFile(fileName);
|
||||
this.tracks.push(Track.fromFile(fileName, sensors));
|
||||
}
|
||||
|
||||
@action public fadeBackground(userChoice: boolean): void {
|
||||
@action public fadeBackground(userChoice: boolean): void {
|
||||
this.shouldFadeVideoBackground = userChoice;
|
||||
if (this.shouldFadeVideoBackground) {
|
||||
this.originalReferenceTrackFilename = this.referenceTrack.source;
|
||||
|
@ -142,7 +158,7 @@ export class ProjectStore {
|
|||
}
|
||||
|
||||
@action public deleteTrack(track: Track): void {
|
||||
this.alignmentHistoryRecord();
|
||||
this.recordAlignmentSnapshot();
|
||||
const index = this.tracks.map(t => t.id).indexOf(track.id);
|
||||
this.tracks.splice(index, 1);
|
||||
}
|
||||
|
@ -169,8 +185,8 @@ export class ProjectStore {
|
|||
const json = fs.readFileSync(fileName, 'utf-8');
|
||||
const project = JSON.parse(json);
|
||||
this.projectFileLocation = null;
|
||||
this.alignmentHistoryReset();
|
||||
this.labelingHistoryReset();
|
||||
this.resetAlignmentUndoRedoHistory();
|
||||
this.resetLabelingUndoRedoHistory();
|
||||
this.loadProjectHelper(project as SavedProject, () => {
|
||||
this.projectFileLocation = fileName;
|
||||
this.addToRecentProjects(fileName);
|
||||
|
@ -354,6 +370,14 @@ export class ProjectStore {
|
|||
alignmentStore.loadState(snapshot.alignment);
|
||||
}
|
||||
|
||||
@action public recordAlignmentSnapshot(): void {
|
||||
this._alignmentUndoRedoHistory.add(this.getAlignmentSnapshot());
|
||||
}
|
||||
|
||||
@action private resetAlignmentUndoRedoHistory(): void {
|
||||
this._alignmentUndoRedoHistory.reset();
|
||||
}
|
||||
|
||||
private getLabelingSnapshot(): SavedLabelingSnapshot {
|
||||
return { labeling: deepClone(labelingStore.saveState()) };
|
||||
}
|
||||
|
@ -362,47 +386,39 @@ export class ProjectStore {
|
|||
labelingStore.loadState(snapshot.labeling);
|
||||
}
|
||||
|
||||
public alignmentHistoryRecord(): void {
|
||||
this._alignmentHistory.add(this.getAlignmentSnapshot());
|
||||
@action public recordLabelingSnapshot(): void {
|
||||
this._labelingUndoRedoHistory.add(this.getLabelingSnapshot());
|
||||
}
|
||||
|
||||
private alignmentHistoryReset(): void {
|
||||
this._alignmentHistory.reset();
|
||||
@action private resetLabelingUndoRedoHistory(): void {
|
||||
this._labelingUndoRedoHistory.reset();
|
||||
}
|
||||
|
||||
public labelingHistoryRecord(): void {
|
||||
this._labelingHistory.add(this.getLabelingSnapshot());
|
||||
}
|
||||
|
||||
private labelingHistoryReset(): void {
|
||||
this._labelingHistory.reset();
|
||||
}
|
||||
|
||||
public alignmentUndo(): void {
|
||||
const snapshot = this._alignmentHistory.undo(this.getAlignmentSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadAlignmentSnapshot(snapshot);
|
||||
@action public undo(): void {
|
||||
if (projectUiStore.currentTab === 'alignment') {
|
||||
const snapshot = this._alignmentUndoRedoHistory.undo(this.getAlignmentSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadAlignmentSnapshot(snapshot);
|
||||
}
|
||||
} else {
|
||||
const snapshot = this._labelingUndoRedoHistory.undo(this.getLabelingSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadLabelingSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public alignmentRedo(): void {
|
||||
const snapshot = this._alignmentHistory.redo(this.getAlignmentSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadAlignmentSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
public labelingUndo(): void {
|
||||
const snapshot = this._labelingHistory.undo(this.getLabelingSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadLabelingSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
public labelingRedo(): void {
|
||||
const snapshot = this._labelingHistory.redo(this.getLabelingSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadLabelingSnapshot(snapshot);
|
||||
@action public redo(): void {
|
||||
if (projectUiStore.currentTab === 'alignment') {
|
||||
const snapshot = this._alignmentUndoRedoHistory.redo(this.getAlignmentSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadAlignmentSnapshot(snapshot);
|
||||
}
|
||||
} else {
|
||||
const snapshot = this._labelingUndoRedoHistory.redo(this.getLabelingSnapshot());
|
||||
if (snapshot) {
|
||||
this.loadLabelingSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ export class ProjectUiStore {
|
|||
@observable public viewWidth: number;
|
||||
@observable public currentTab: TabID;
|
||||
|
||||
@observable public timeSeriesGrayscale: boolean;
|
||||
|
||||
// Current transition.
|
||||
private _referenceViewTransition: TransitionController;
|
||||
|
||||
|
@ -37,8 +39,8 @@ export class ProjectUiStore {
|
|||
this._panZoomParameterMap = observable.map<PanZoomParameters>();
|
||||
this.selectedMarker = null;
|
||||
this.selectedCorrespondence = null;
|
||||
|
||||
this.getTimeCursor = this.getTimeCursor.bind(this);
|
||||
this.timeSeriesGrayscale = false;
|
||||
|
||||
autorun('ProjectUiStore.onTracksChanged', () => this.onTracksChanged());
|
||||
reaction(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { action, computed, observable, ObservableMap } from 'mobx';
|
||||
import * as Map from 'es6-map';
|
||||
import { action, computed, observable, ObservableMap } from 'mobx';
|
||||
|
||||
// FIXME: this class requires the es6-map polyfill for Map. Consider changing the implementation to avoid this.
|
||||
// This class requires the es6-map polyfill for Map.
|
||||
export class ObservableSet<T> {
|
||||
private map: ObservableMap<boolean>; //Observable<string,boolean>
|
||||
private map: ObservableMap<boolean>;
|
||||
private keyMap: Map<string, T>;
|
||||
|
||||
constructor(private getKey: (item: T) => string) {
|
||||
|
|
|
@ -22,21 +22,23 @@
|
|||
// Snapshots need to be decoupled
|
||||
// - They shouldn't reference to the same object which can be updated by the app.
|
||||
// - Referencing to the same object is okay (and save space) if the object never changes.
|
||||
import { action, computed, observable } from 'mobx';
|
||||
|
||||
export class UndoRedoHistory<TSnapshot> {
|
||||
@observable private _undoHistory: TSnapshot[];
|
||||
@observable private _redoHistory: TSnapshot[];
|
||||
|
||||
export class HistoryTracker<Snapshot> {
|
||||
private _undoHistory: Snapshot[];
|
||||
private _redoHistory: Snapshot[];
|
||||
constructor() {
|
||||
this._undoHistory = [];
|
||||
this._redoHistory = [];
|
||||
}
|
||||
|
||||
public add(item: Snapshot): void {
|
||||
@action public add(item: TSnapshot): void {
|
||||
this._undoHistory.push(item);
|
||||
this._redoHistory = [];
|
||||
}
|
||||
|
||||
public undo(current: Snapshot): Snapshot {
|
||||
@action public undo(current: TSnapshot): TSnapshot {
|
||||
const lastIndex = this._undoHistory.length - 1;
|
||||
if (lastIndex >= 0) {
|
||||
const [lastItem] = this._undoHistory.splice(lastIndex, 1);
|
||||
|
@ -47,7 +49,7 @@ export class HistoryTracker<Snapshot> {
|
|||
}
|
||||
}
|
||||
|
||||
public redo(current: Snapshot): Snapshot {
|
||||
@action public redo(current: TSnapshot): TSnapshot {
|
||||
const lastIndex = this._redoHistory.length - 1;
|
||||
if (lastIndex >= 0) {
|
||||
const [lastItem] = this._redoHistory.splice(lastIndex, 1);
|
||||
|
@ -58,15 +60,15 @@ export class HistoryTracker<Snapshot> {
|
|||
}
|
||||
}
|
||||
|
||||
public canUndo(): boolean {
|
||||
@computed public get canUndo(): boolean {
|
||||
return this._undoHistory.length > 0;
|
||||
}
|
||||
|
||||
public canRndo(): boolean {
|
||||
@computed public get canRedo(): boolean {
|
||||
return this._redoHistory.length > 0;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
@action public reset(): void {
|
||||
this._undoHistory = [];
|
||||
this._redoHistory = [];
|
||||
}
|
|
@ -130,6 +130,7 @@ export class Track {
|
|||
});
|
||||
|
||||
// Find the translation and scale for correspondences.
|
||||
if (tCorrespondences.length === 0) { return; } // The correspondences don't involve this track.
|
||||
let [k, b] = leastSquares(tCorrespondences);
|
||||
if (isNaN(k) || isNaN(b)) { k = 1; b = 0; } // Is this the right thing to do?
|
||||
const project = x => k * x + b;
|
||||
|
@ -140,10 +141,6 @@ export class Track {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// leastSquares([[yi, xi], ... ]) => [ k, b ] such that sum(k xi + b - yi)^2 is minimized.
|
||||
function leastSquares(correspondences: [number, number][]): [number, number] {
|
||||
if (correspondences.length === 0) { throw 'leastSquares empty array'; }
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
import { autocorrelogram } from '../suggestion/algorithms/Autocorrelation';
|
||||
import { Dataset, SensorTimeSeries, TimeSeriesKind } from './dataStructures/dataset';
|
||||
import { Label, LabelConfirmationState } from './dataStructures/labeling';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
|
||||
|
||||
export function startDragging(
|
||||
move?: (e: MouseEvent) => void,
|
||||
up?: (e: MouseEvent) => void,
|
||||
|
@ -24,8 +17,6 @@ export function startDragging(
|
|||
window.addEventListener('mouseup', handler_up, useCapture);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function isSameArray<T>(arr1?: T[], arr2?: T[]): boolean {
|
||||
return arr1 === arr2 || arr1 && arr2 && arr1.length === arr2.length && arr1.every((d, i) => d === arr2[i]);
|
||||
}
|
||||
|
@ -35,31 +26,6 @@ export function makePathDFromPoints(points: number[][]): string {
|
|||
return 'M' + points.map(([x, y]) => x + ',' + y).join('L');
|
||||
}
|
||||
|
||||
|
||||
export function updateLabelConfirmationState(label: Label, endpoint: string): LabelConfirmationState {
|
||||
let newState = label.state;
|
||||
if (endpoint === 'start') {
|
||||
if (label.state === LabelConfirmationState.UNCONFIRMED) {
|
||||
newState = LabelConfirmationState.CONFIRMED_START;
|
||||
} else if (label.state === LabelConfirmationState.CONFIRMED_END) {
|
||||
newState = LabelConfirmationState.CONFIRMED_BOTH;
|
||||
}
|
||||
}
|
||||
if (endpoint === 'end') {
|
||||
if (label.state === LabelConfirmationState.UNCONFIRMED) {
|
||||
newState = LabelConfirmationState.CONFIRMED_END;
|
||||
} else if (label.state === LabelConfirmationState.CONFIRMED_START) {
|
||||
newState = LabelConfirmationState.CONFIRMED_BOTH;
|
||||
}
|
||||
}
|
||||
if (endpoint === 'both') {
|
||||
newState = LabelConfirmationState.CONFIRMED_BOTH;
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class TransitionController {
|
||||
private _timer: number;
|
||||
private _onProgress: (t: number, finish?: boolean) => void;
|
||||
|
@ -92,9 +58,6 @@ export class TransitionController {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export class ArrayThrottler<ItemType, StationaryType> {
|
||||
private _minInterval: number;
|
||||
private _callback: (items: ItemType[], stationary: StationaryType) => void;
|
||||
|
@ -146,82 +109,3 @@ export class ArrayThrottler<ItemType, StationaryType> {
|
|||
}
|
||||
}
|
||||
|
||||
export interface DatasetMetadata {
|
||||
name: string;
|
||||
sensors: {
|
||||
name: string;
|
||||
path: string;
|
||||
timestampStart?: number;
|
||||
timestampEnd?: number;
|
||||
alignmentFix?: [number, number];
|
||||
}[];
|
||||
videos: {
|
||||
name: string;
|
||||
path: string;
|
||||
timestampStart?: number;
|
||||
timestampEnd?: number;
|
||||
alignmentFix?: [number, number];
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||
const autocorrelogramCache = new WeakMap<SensorTimeSeries, SensorTimeSeries>();
|
||||
|
||||
export function computeSensorTimeSeriesAutocorrelogram(timeSeries: SensorTimeSeries): SensorTimeSeries {
|
||||
if (autocorrelogramCache.has(timeSeries)) { return autocorrelogramCache.get(timeSeries); }
|
||||
|
||||
const sampleRate = (timeSeries.dimensions[0].length - 1) / (timeSeries.timestampEnd - timeSeries.timestampStart);
|
||||
const windowSize = Math.ceil(sampleRate * 4);
|
||||
const sliceSize = Math.ceil(windowSize / 4);
|
||||
const dimension = new Float32Array(timeSeries.dimensions[0].length);
|
||||
for (let i = 0; i < timeSeries.dimensions[0].length; i++) {
|
||||
dimension[i] = 0;
|
||||
for (let j = 0; j < timeSeries.dimensions.length; j++) {
|
||||
if (timeSeries.dimensions[j][i] === timeSeries.dimensions[j][i]) {
|
||||
dimension[i] += timeSeries.dimensions[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = autocorrelogram(dimension, windowSize, sliceSize);
|
||||
const sliceCount = result.length / windowSize;
|
||||
const dimensions: Float32Array[] = [];
|
||||
const samplesScale = d3.scaleLinear() // sample index <> sample timestamp
|
||||
.domain([0, dimension.length - 1])
|
||||
.range([timeSeries.timestampStart, timeSeries.timestampEnd]);
|
||||
const sliceScale = d3.scaleLinear() // slice index <> slice timestamp
|
||||
.domain([0, sliceCount - 1])
|
||||
.range([samplesScale(0 + windowSize / 2), samplesScale(sliceSize * (sliceCount - 1) + windowSize / 2)]);
|
||||
for (let i = 0; i < windowSize; i++) {
|
||||
const t = dimensions[i] = new Float32Array(sliceCount);
|
||||
for (let j = 0; j < sliceCount; j++) {
|
||||
t[j] = result[j * windowSize + i];
|
||||
if (t[j] !== t[j]) { t[j] = 0; }
|
||||
}
|
||||
}
|
||||
const r = {
|
||||
name: timeSeries.name + '.autocorrelogram',
|
||||
kind: timeSeries.kind,
|
||||
timestampStart: sliceScale.range()[0],
|
||||
timestampEnd: sliceScale.range()[1],
|
||||
dimensions: dimensions,
|
||||
sampleRate: (sliceScale.range()[1] - sliceScale.range()[0]) / (dimension.length - 1),
|
||||
scales: [[-1, 1]]
|
||||
};
|
||||
autocorrelogramCache.set(timeSeries, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
export function computeDatasetAutocorrelogram(dataset: Dataset): Dataset {
|
||||
const datasetOut = new Dataset();
|
||||
datasetOut.name = dataset.name;
|
||||
datasetOut.timestampStart = dataset.timestampStart;
|
||||
datasetOut.timestampEnd = dataset.timestampEnd;
|
||||
for (const series of dataset.timeSeries) {
|
||||
if (series.kind === TimeSeriesKind.VIDEO) {
|
||||
datasetOut.timeSeries.push(series);
|
||||
} else {
|
||||
datasetOut.timeSeries.push(computeSensorTimeSeriesAutocorrelogram(series as SensorTimeSeries));
|
||||
}
|
||||
}
|
||||
return datasetOut;
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
Prerequisites for using Element with native modules
|
||||
===================================================
|
||||
|
||||
- SWIG
|
||||
- node.js
|
||||
- node-gyp
|
||||
- electron
|
||||
- electron-rebuild
|
||||
- d3 node.js module
|
||||
- serial node.js module (modified for electron)
|
||||
- the FeatureExtraction library
|
||||
|
||||
How to install requirements on Windows
|
||||
--------------------------------------
|
||||
|
||||
- SWIG
|
||||
Download the Windows zip file from swig.org's [download section](http://www.swig.org/download.html). Unzip this directory
|
||||
and put it somewhere convenient (I used C:\swigwin-3.0.8). Make sure you set your PATH environment
|
||||
variable to include the swigwin directory.
|
||||
|
||||
- node.js 4.3.2
|
||||
Download from [https://nodejs.org]. Node.js installs npm, the node package manager
|
||||
|
||||
- node-gyp
|
||||
npm install -g node-gyp
|
||||
|
||||
- electron
|
||||
npm install electron-prebuilt -g
|
||||
|
||||
- electron-rebuild
|
||||
npm install --save-dev electron-rebuild (note to self: what is save-dev?)
|
||||
|
||||
- d3
|
||||
npm install d3
|
||||
|
||||
- serial
|
||||
???
|
||||
|
||||
|
||||
How to install requirements on OS X
|
||||
-----------------------------------
|
||||
|
||||
- SWIG
|
||||
homebrew install swig
|
||||
|
||||
- node.js
|
||||
homebrew install node (got version 5.0.0) --- this installs node and
|
||||
npm (node package manager)
|
||||
|
||||
- node-gyp
|
||||
npm install -g node-gyp
|
||||
|
||||
- electron
|
||||
npm install electron-prebuilt -g
|
||||
|
||||
- electron-rebuild
|
||||
npm install --save-dev electron-rebuild (what is save-dev?)
|
||||
|
||||
- d3
|
||||
npm install d3
|
||||
|
||||
- serial
|
||||
???
|
||||
|
||||
|
||||
How to install requirements on Linux
|
||||
------------------------------------
|
||||
|
||||
- SWIG
|
||||
apt-get swig
|
||||
|
||||
- node.js
|
||||
apt-get node
|
||||
|
||||
- node-gyp
|
||||
npm install -g node-gyp
|
||||
|
||||
- electron
|
||||
npm install electron-prebuilt -g
|
||||
|
||||
- electron-rebuild
|
||||
npm install --save-dev electron-rebuild (what is save-dev?)
|
||||
|
||||
- d3
|
||||
npm install d3
|
||||
|
||||
- serial
|
||||
???
|
|
@ -1,125 +0,0 @@
|
|||
Intelligent Devices UX style guide
|
||||
====================================================
|
||||
|
||||
This is the coding style guide for UX projects.
|
||||
|
||||
Directory hierarchy
|
||||
--------------
|
||||
|
||||
TODO: after setting up intern project spaces. Include build scripts?
|
||||
|
||||
|
||||
File structure
|
||||
--------------
|
||||
Use one file per logical component (one main class per file plus any helpers).
|
||||
|
||||
Files should be organized in the following way:
|
||||
* import statements
|
||||
* props interface
|
||||
* state interface
|
||||
* class definition
|
||||
* refs
|
||||
* private fields
|
||||
* public fields
|
||||
* constructors
|
||||
* getters/setters
|
||||
* lifecycle methods (componentsWillMount, componentsDidMount...)
|
||||
* event handlers
|
||||
* other methods
|
||||
* render
|
||||
|
||||
Naming
|
||||
--------------
|
||||
All names should be as descriptive as possible. Prefer whole words to abbreviations, especially if those abbreviations could be ambiguous, e.g., prefer `CurrentMousePosition` to `CurrMousePos`
|
||||
|
||||
Filenames:
|
||||
Use camelCase, e.g., `dataStore.ts`
|
||||
Component filenames should match the main component class, e.g., the `dataView.tsx` file should contain a `class DataView`.
|
||||
|
||||
Classes, interfaces, and enums:
|
||||
Use PascalCase, e.g., `class DataView`.
|
||||
Don't use "I" as a prefix for interface names.
|
||||
|
||||
Component props and state interfaces:
|
||||
Use the same name as the corresponding component followed by "Props" or "State", e.g., `DataViewProps` and `DataViewState` for the `DataView` component.
|
||||
|
||||
Methods:
|
||||
Use camelCase, e.g., `onDragStart()`, `zoomLevel()`
|
||||
|
||||
Member variables:
|
||||
Use `_` (underscore) followed by camelCase, e.g., `_zoomLevel`
|
||||
|
||||
Getter and setter methods:
|
||||
Use the same name as the corresponding member variable, minus the underscore, e.g., `public get zoomLevel()`
|
||||
|
||||
Enum values (TODO: double check this):
|
||||
Use all caps with `_` (underscore) between words, e.g., `INPUT_DATA_CHANGED`
|
||||
|
||||
Event emitters, addListeners and removeListeners:
|
||||
Use `private emit<event name>`, `public add<event name>Listener`, `public remove<event name>Listener`
|
||||
|
||||
Import statements:
|
||||
Only import what you need. If you only need to import one class from a file, use `import {ClassName} from 'path'`. If you need to import more than one class, use `import * as className from 'path'`. Do not import using requires because these will not pick up the interface description from typings.
|
||||
|
||||
|
||||
Types and modifiers
|
||||
--------------
|
||||
All methods and member variables should have access modifiers. Always opt for `private` where possible and `public` when necessary.
|
||||
|
||||
Always specify types for member variables, e.g., prefer `private _dataFileName : string = "";` to `private _dataFileName = "";`.
|
||||
|
||||
There is no need to specify types for local variables.
|
||||
|
||||
`any` types should be avoided.
|
||||
|
||||
`refs` should always start with `[key: string]: React.ReactInstance`. All additional refs should also be of type `React.ReactInstance`. For example:
|
||||
|
||||
refs: {
|
||||
[key: string]: React.ReactInstance,
|
||||
dataView: React.ReactInstance,
|
||||
}
|
||||
|
||||
Always do type checks and set default values on state variables. e.g., TODO
|
||||
|
||||
|
||||
Miscellaneous styling
|
||||
-------
|
||||
|
||||
Keep lambdas simple e.g., prefer `x => x + x` to `(x) => x + x`.
|
||||
|
||||
Open curly braces always go on the same line as whatever necessitates them.
|
||||
|
||||
`else` and `else if` goes on a separate line from the closing curly brace.
|
||||
|
||||
Use fat arrow syntax `() => {` over `function() {`. The word function shouldn't appear in your code.
|
||||
|
||||
Use `let` not `var` for local variables.
|
||||
|
||||
Do not export types/functions unless you need to share them across components.
|
||||
|
||||
For anything not documented here, consult this guide: https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines
|
||||
|
||||
|
||||
Comments
|
||||
-------
|
||||
|
||||
Only add comments for anything that is not obvious.
|
||||
|
||||
TODO statements can be used before checking in to master, e.g., for communicating to code reviewers. No TODOs should be checked in to master.
|
||||
|
||||
|
||||
Interfacing with EMLL
|
||||
-------
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
HTML and CSS
|
||||
--------
|
||||
|
||||
Use bootstrap css as much as possible: http://getbootstrap.com/css/
|
||||
|
||||
Add custom styles to `styles.css`
|
||||
|
||||
Note that Typescript html code uses `className` not `class` whereas .html files use `class`
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
</script>
|
||||
<script src="./node_modules/bootstrap/dist/js/bootstrap.js"></script>
|
||||
|
||||
<title>Gesture Builder - Intelligent Device Project</title>
|
||||
<title>Embedded Learning Toolkit</title>
|
||||
</head>
|
||||
|
||||
<!--
|
||||
|
|
1
main.ts
1
main.ts
|
@ -9,6 +9,7 @@ interface ExpectedEnv {
|
|||
|
||||
const debugging = process.argv.some(a => a === '--debugging');
|
||||
if (!debugging) { (<ExpectedEnv>process.env).NODE_ENV = 'production'; }
|
||||
(global as any).debugging = debugging;
|
||||
|
||||
// Module to create native browser window.
|
||||
const window = electron.BrowserWindow;
|
||||
|
|
30
package.json
30
package.json
|
@ -1,67 +1,55 @@
|
|||
{
|
||||
"name": "integrated-app",
|
||||
"name": "elt",
|
||||
"productName": "Embedded Learning Toolkit",
|
||||
"version": "0.0.1",
|
||||
"description": "An application to create labels",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=production electron main.js",
|
||||
"run": "cross-env NODE_ENV=production electron main.js --debugging",
|
||||
"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: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 . --platform=win32 --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged",
|
||||
"package:darwin": "electron-packager . --platform=darwin --arch=x64 --ignore=\"/data($|/)\" --asar --out=../packaged"
|
||||
"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"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://cjacobs.visualstudio.com/DefaultCollection/_git/ElectronApp"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "cjacobs",
|
||||
"devDependencies": {
|
||||
"cross-env": "^2.0.1",
|
||||
"electron-packager": "^8.1.0",
|
||||
"electron": "^1.4.4",
|
||||
"electron-rebuild": "^1.1.5",
|
||||
"mobx-react-devtools": "^4.2.11",
|
||||
"tslint": "^3.15.1"
|
||||
"tslint": "^3.15.1",
|
||||
"tslint-microsoft-contrib": "^2.0.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/d3": "^4.4.0",
|
||||
"@types/electron": "^1.4.29",
|
||||
"@types/es6-promise": "0.0.32",
|
||||
"@types/flux": "0.0.33",
|
||||
"@types/es6-promise": "0.0.32",
|
||||
"@types/node": "^6.0.52",
|
||||
"@types/react": "^0.14.55",
|
||||
"@types/react-bootstrap": "0.0.37",
|
||||
"@types/react-dom": "^0.14.19",
|
||||
"bootstrap": "^3.3.6",
|
||||
"browserify": "^13.1.1",
|
||||
"d3": "^4.4.0",
|
||||
"digitalsignals": "^1.0.0",
|
||||
"electron": "^1.4.12",
|
||||
"electron-packager": "^8.4.0",
|
||||
"emll": "file:../../emllModule",
|
||||
"es6-map": "^0.1.4",
|
||||
"es6-promise": "^3.2.1",
|
||||
"flux": "^2.1.1",
|
||||
"jquery": "^2.2.2",
|
||||
"less": "^2.7.1",
|
||||
"mobx": "^3.0.0",
|
||||
"mobx-react": "^4.1.0",
|
||||
"monaco-editor": "^0.7.0",
|
||||
"react": "^15.4.1",
|
||||
"react-bootstrap": "^0.30.3",
|
||||
"react-dom": "^15.1.0",
|
||||
"requirejs": "^2.2.0",
|
||||
"serialport": "^2.1.2",
|
||||
"tslint-microsoft-contrib": "^2.0.13",
|
||||
"typescript": "^2.1.6",
|
||||
"typescript-require": "^0.2.9-1",
|
||||
"underscore": "^1.8.3"
|
||||
"typescript-require": "^0.2.9-1"
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче