diff --git a/README.md b/README.md
index 499455d..ac4cff0 100644
--- a/README.md
+++ b/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`
\ No newline at end of file
+To run the project, run `npm start`
diff --git a/app/less/common.less b/app/less/common.less
index 67655aa..46747d9 100644
--- a/app/less/common.less
+++ b/app/less/common.less
@@ -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 {
diff --git a/app/less/labeling.less b/app/less/labeling.less
index 830b881..23198f4 100644
--- a/app/less/labeling.less
+++ b/app/less/labeling.less
@@ -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;
diff --git a/app/ts/components/App.tsx b/app/ts/components/App.tsx
index bdba10e..c9fc0ec 100644
--- a/app/ts/components/App.tsx
+++ b/app/ts/components/App.tsx
@@ -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 (
-
+
+ {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 => )(require('mobx-react-devtools').default) : null}
+
{
stores.projectUiStore.currentTab = tab as TabID;
@@ -78,13 +77,13 @@ export class App extends React.Component<{}, {}> {
-
+
-
+
-
+
diff --git a/app/ts/components/alignment/AlignmentToolbar.tsx b/app/ts/components/alignment/AlignmentToolbar.tsx
index d9b28e8..0a0c56f 100644
--- a/app/ts/components/alignment/AlignmentToolbar.tsx
+++ b/app/ts/components/alignment/AlignmentToolbar.tsx
@@ -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
{
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
Load Reference Video...
- Load Video or Sensor...
+ Load Sensor Data...
diff --git a/app/ts/components/alignment/AlignmentView.tsx b/app/ts/components/alignment/AlignmentView.tsx
index 7d7e67b..fe81ba2 100644
--- a/app/ts/components/alignment/AlignmentView.tsx
+++ b/app/ts/components/alignment/AlignmentView.tsx
@@ -72,12 +72,6 @@ export class AlignmentView extends React.Component();
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) {
diff --git a/app/ts/components/common/OptionsToolbar.tsx b/app/ts/components/common/OptionsToolbar.tsx
index 2c4e447..04031da 100644
--- a/app/ts/components/common/OptionsToolbar.tsx
+++ b/app/ts/components/common/OptionsToolbar.tsx
@@ -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 (
-
- Undo
+
+
+
+
+
+
{projectStore.statusMessage}
- Signals Display
+ Time Series Color
+
+
+
+ Grayscale
+
+
+ Signals Display Type
diff --git a/app/ts/components/common/ReferenceTrackOverview.tsx b/app/ts/components/common/ReferenceTrackOverview.tsx
index 4103c44..a686404 100644
--- a/app/ts/components/common/ReferenceTrackOverview.tsx
+++ b/app/ts/components/common/ReferenceTrackOverview.tsx
@@ -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 {
public refs: {
@@ -30,18 +27,7 @@ export class ReferenceTrackOverview extends React.Component): void {
@@ -58,14 +44,6 @@ export class ReferenceTrackOverview extends React.Component
- {labels.map(label =>
-
- )}
-
+ {labels.map(label =>
+
+ )}
+
);
}
@@ -222,10 +200,11 @@ export class ReferenceTrackOverview extends React.Component
-
-
-
-
+ {!isNaN(cursorX) ?
+
+
+
+ : null}
; // Colors to use, if null, use d3.category10 or 20.
+ grayscale: boolean;
+ //colorScale?: d3.ScaleOrdinal; // 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 {
// 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 {
const yScale = 1 / (y0 - y1) * this.props.plotHeight;
// Determine color scale.
- const colors = this.props.colorScale ||
- (numSeries <= 10 ?
+ const colors = (numSeries <= 10 ?
d3.scaleOrdinal(d3.schemeCategory10) :
d3.scaleOrdinal(d3.schemeCategory20));
@@ -87,16 +87,19 @@ export class SensorPlot extends React.Component {
}
}
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 (
{
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 {
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 {
{
+export class SharedAlignmentLabelingPane extends React.Component {
public refs: {
[key: string]: Element,
container: Element,
@@ -106,7 +104,6 @@ export class TabPane extends React.Component
diff --git a/app/ts/components/common/TrackView.tsx b/app/ts/components/common/TrackView.tsx
index 5c9a87a..144d20a 100644
--- a/app/ts/components/common/TrackView.tsx
+++ b/app/ts/components/common/TrackView.tsx
@@ -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 {
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 {
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 {
onWheel={this.onWheel}
/>
{
- timeCursor != null ? (
+ timeCursor != null && !isNaN(timeCursor) ? (
{
diff --git a/app/ts/components/labeling/LabelView.tsx b/app/ts/components/labeling/LabelView.tsx
index 49cc9b8..20d54f5 100644
--- a/app/ts/components/labeling/LabelView.tsx
+++ b/app/ts/components/labeling/LabelView.tsx
@@ -123,7 +123,6 @@ export class LabelView extends React.Component {
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) {
diff --git a/app/ts/components/labeling/LabelingView.tsx b/app/ts/components/labeling/LabelingView.tsx
index a63098c..682861a 100644
--- a/app/ts/components/labeling/LabelingView.tsx
+++ b/app/ts/components/labeling/LabelingView.tsx
@@ -46,12 +46,6 @@ export class LabelingView extends React.Component= 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 {
diff --git a/app/ts/stores/LabelingStore.ts b/app/ts/stores/LabelingStore.ts
index 6afe431..f43f6cf 100644
--- a/app/ts/stores/LabelingStore.ts
+++ b/app/ts/stores/LabelingStore.ts
@@ -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);
diff --git a/app/ts/stores/LabelingUiStore.ts b/app/ts/stores/LabelingUiStore.ts
index d055878..954bb27 100644
--- a/app/ts/stores/LabelingUiStore.ts
+++ b/app/ts/stores/LabelingUiStore.ts
@@ -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(
diff --git a/app/ts/stores/ProjectStore.ts b/app/ts/stores/ProjectStore.ts
index 222ba6f..ada52d5 100644
--- a/app/ts/stores/ProjectStore.ts
+++ b/app/ts/stores/ProjectStore.ts
@@ -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;
- private _labelingHistory: HistoryTracker;
+ private _alignmentUndoRedoHistory: UndoRedoHistory;
+ private _labelingUndoRedoHistory: UndoRedoHistory;
constructor() {
- this._alignmentHistory = new HistoryTracker();
- this._labelingHistory = new HistoryTracker();
+ this._alignmentUndoRedoHistory = new UndoRedoHistory();
+ this._labelingUndoRedoHistory = new UndoRedoHistory();
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);
+ }
}
}
diff --git a/app/ts/stores/ProjectUiStore.ts b/app/ts/stores/ProjectUiStore.ts
index c333818..7a9e8de 100644
--- a/app/ts/stores/ProjectUiStore.ts
+++ b/app/ts/stores/ProjectUiStore.ts
@@ -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();
this.selectedMarker = null;
this.selectedCorrespondence = null;
-
this.getTimeCursor = this.getTimeCursor.bind(this);
+ this.timeSeriesGrayscale = false;
autorun('ProjectUiStore.onTracksChanged', () => this.onTracksChanged());
reaction(
diff --git a/app/ts/stores/dataStructures/ObservableSet.ts b/app/ts/stores/dataStructures/ObservableSet.ts
index 6e0ce98..c346ac9 100644
--- a/app/ts/stores/dataStructures/ObservableSet.ts
+++ b/app/ts/stores/dataStructures/ObservableSet.ts
@@ -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 {
- private map: ObservableMap; //Observable
+ private map: ObservableMap;
private keyMap: Map;
constructor(private getKey: (item: T) => string) {
diff --git a/app/ts/stores/dataStructures/HistoryTracker.ts b/app/ts/stores/dataStructures/UndoRedoHistory.ts
similarity index 81%
rename from app/ts/stores/dataStructures/HistoryTracker.ts
rename to app/ts/stores/dataStructures/UndoRedoHistory.ts
index 77151d9..4be6eb4 100644
--- a/app/ts/stores/dataStructures/HistoryTracker.ts
+++ b/app/ts/stores/dataStructures/UndoRedoHistory.ts
@@ -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 {
+ @observable private _undoHistory: TSnapshot[];
+ @observable private _redoHistory: TSnapshot[];
-export class HistoryTracker {
- 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 {
}
}
- 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 {
}
}
- 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 = [];
}
diff --git a/app/ts/stores/dataStructures/alignment.ts b/app/ts/stores/dataStructures/alignment.ts
index f172bed..3ee681c 100644
--- a/app/ts/stores/dataStructures/alignment.ts
+++ b/app/ts/stores/dataStructures/alignment.ts
@@ -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'; }
diff --git a/app/ts/stores/utils.ts b/app/ts/stores/utils.ts
index 4a0b68a..794165c 100644
--- a/app/ts/stores/utils.ts
+++ b/app/ts/stores/utils.ts
@@ -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(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 {
private _minInterval: number;
private _callback: (items: ItemType[], stationary: StationaryType) => void;
@@ -146,82 +109,3 @@ export class ArrayThrottler {
}
}
-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();
-
-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;
-}
diff --git a/docs/INSTALL.md b/docs/INSTALL.md
deleted file mode 100644
index a351cfa..0000000
--- a/docs/INSTALL.md
+++ /dev/null
@@ -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
-???
diff --git a/docs/StyleGuide.md b/docs/StyleGuide.md
deleted file mode 100644
index cff79ba..0000000
--- a/docs/StyleGuide.md
+++ /dev/null
@@ -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`, `public addListener`, `public removeListener`
-
-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`
-
diff --git a/index.html b/index.html
index 569d3e1..06c8ec8 100644
--- a/index.html
+++ b/index.html
@@ -21,7 +21,7 @@
- Gesture Builder - Intelligent Device Project
+ Embedded Learning Toolkit