Skip to content

Commit b03aabf

Browse files
authored
Merge pull request #1019 from biigle/videoAnnotation
Video single frame annotation
2 parents ea98502 + 2321d1b commit b03aabf

File tree

10 files changed

+147
-18
lines changed

10 files changed

+147
-18
lines changed

resources/assets/js/annotations/components/annotationCanvas/drawInteractions.vue

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@ import Styles from '../../stores/styles';
55
import { shiftKeyOnly } from '@biigle/ol/events/condition';
66
import snapInteraction from '../../snapInteraction.vue';
77
import { Point } from '@biigle/ol/geom';
8+
import * as preventDoubleclick from '../../../prevent-doubleclick';
89
910
10-
function computeDistance(point1, point2) {
11-
let p1=point1.getCoordinates();
12-
let p2=point2.getCoordinates();
13-
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
14-
}
15-
1611
/**
1712
* Mixin for the annotationCanvas component that contains logic for the draw interactions.
1813
*
@@ -21,9 +16,6 @@ function computeDistance(point1, point2) {
2116
2217
let drawInteraction;
2318
24-
const POINT_CLICK_COOLDOWN = 400;
25-
const POINT_CLICK_DISTANCE = 5;
26-
2719
// Custom OpenLayers freehandCondition that is true if a pen is used for input or
2820
// if Shift is pressed otherwise.
2921
let penOrShift = function (mapBrowserEvent) {
@@ -142,8 +134,8 @@ export default {
142134
}
143135
},
144136
isPointDoubleClick(e) {
145-
return new Date().getTime() - this.lastDrawnPointTime < POINT_CLICK_COOLDOWN
146-
&& computeDistance(this.lastDrawnPoint,e.feature.getGeometry()) < POINT_CLICK_DISTANCE;
137+
return new Date().getTime() - this.lastDrawnPointTime < preventDoubleclick.POINT_CLICK_COOLDOWN
138+
&& preventDoubleclick.computeDistance(this.lastDrawnPoint,e.feature.getGeometry()) < preventDoubleclick.POINT_CLICK_DISTANCE;
147139
},
148140
},
149141
watch: {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Compute the Euclidean distance between two points.
3+
*
4+
* @param Object point1 - The first point with getCoordinates() method.
5+
* @param Object point2 - The second point with getCoordinates() method.
6+
* @returns number - The computed distance between the two points.
7+
*/
8+
9+
const POINT_CLICK_COOLDOWN = 400;
10+
const POINT_CLICK_DISTANCE = 5;
11+
12+
let computeDistance = function (point1, point2) {
13+
let p1 = point1.getCoordinates();
14+
let p2 = point2.getCoordinates();
15+
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
16+
};
17+
18+
export { computeDistance, POINT_CLICK_COOLDOWN, POINT_CLICK_DISTANCE };

resources/assets/js/videos/components/settingsTab.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export default {
4848
'enableJumpByFrame',
4949
'jumpStep',
5050
'muteVideo',
51+
'singleAnnotation',
5152
],
5253
annotationOpacity: 1,
5354
showMinimap: true,
@@ -60,6 +61,7 @@ export default {
6061
showThumbnailPreview: true,
6162
enableJumpByFrame: false,
6263
muteVideo: true,
64+
singleAnnotation: false,
6365
};
6466
},
6567
computed: {
@@ -110,6 +112,12 @@ export default {
110112
handleUnmuteVideo() {
111113
this.muteVideo = false;
112114
},
115+
handleSingleAnnotation() {
116+
this.singleAnnotation = true;
117+
},
118+
handleDisableSingleAnnotation() {
119+
this.singleAnnotation = false;
120+
},
113121
toggleAnnotationOpacity() {
114122
if (this.annotationOpacity > 0) {
115123
this.annotationOpacity = 0;
@@ -170,6 +178,10 @@ export default {
170178
this.$emit('update', 'muteVideo', show);
171179
Settings.set('muteVideo', show);
172180
},
181+
singleAnnotation(show) {
182+
this.$emit('update', 'singleAnnotation', show);
183+
Settings.set('singleAnnotation', show);
184+
},
173185
},
174186
created() {
175187
this.restoreKeys.forEach((key) => {

resources/assets/js/videos/components/videoScreen.vue

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,34 @@
6969
<div v-if="canAdd" class="btn-group">
7070
<control-button
7171
icon="icon-point"
72-
title="Start a point annotation 𝗔"
72+
:title="(singleAnnotation ? 'Set a point' : 'Start a point annotation') + ' 𝗔'"
7373
:hover="false"
7474
:open="isDrawingPoint"
7575
:active="isDrawingPoint"
7676
:disabled="hasError"
7777
@click="drawPoint"
7878
>
7979
<control-button
80+
v-if="singleAnnotation"
81+
icon="fa-check"
82+
title="Disable the single-frame annotation option to create multi-frame annotations"
83+
:disabled="true"
84+
></control-button>
85+
<control-button
86+
v-else
8087
icon="fa-check"
8188
title="Finish the point annotation 𝗘𝗻𝘁𝗲𝗿"
8289
:disabled="cantFinishDrawAnnotation"
8390
@click="finishDrawAnnotation"
8491
></control-button>
8592
<control-button
93+
v-if="singleAnnotation"
94+
icon="fa-project-diagram"
95+
title="Disable the single-frame annotation option to track annotations"
96+
:disabled="true"
97+
></control-button>
98+
<control-button
99+
v-else
86100
icon="fa-project-diagram"
87101
title="Finish and track the point annotation"
88102
v-on:click="finishTrackAnnotation"
@@ -92,14 +106,21 @@
92106
</control-button>
93107
<control-button
94108
icon="icon-rectangle"
95-
title="Start a rectangle annotation 𝗦"
109+
:title="(singleAnnotation ? 'Draw a rectangle' : 'Start a rectangle annotation') + ' 𝗦'"
96110
:hover="false"
97111
:open="isDrawingRectangle"
98112
:active="isDrawingRectangle"
99113
:disabled="hasError"
100114
@click="drawRectangle"
101115
>
102116
<control-button
117+
v-if="singleAnnotation"
118+
icon="fa-check"
119+
title="Disable the single-frame annotation option to create multi-frame annotations"
120+
:disabled="true"
121+
></control-button>
122+
<control-button
123+
v-else
103124
icon="fa-check"
104125
title="Finish the rectangle annotation 𝗘𝗻𝘁𝗲𝗿"
105126
:disabled="cantFinishDrawAnnotation"
@@ -108,20 +129,34 @@
108129
</control-button>
109130
<control-button
110131
icon="icon-circle"
111-
title="Start a circle annotation 𝗗"
132+
:title="(singleAnnotation ? 'Draw a circle' : 'Start a circle annotation') + ' 𝗗'"
112133
:hover="false"
113134
:open="isDrawingCircle"
114135
:active="isDrawingCircle"
115136
:disabled="hasError"
116137
@click="drawCircle"
117138
>
118139
<control-button
140+
v-if="singleAnnotation"
141+
icon="fa-check"
142+
title="Disable the single-frame annotation option to create multi-frame annotations"
143+
:disabled="true"
144+
></control-button>
145+
<control-button
146+
v-else
119147
icon="fa-check"
120148
title="Finish the circle annotation 𝗘𝗻𝘁𝗲𝗿"
121149
:disabled="cantFinishDrawAnnotation"
122150
@click="finishDrawAnnotation"
123151
></control-button>
124152
<control-button
153+
v-if="singleAnnotation"
154+
icon="fa-project-diagram"
155+
title="Disable the single-frame annotation option to track annotations"
156+
:disabled="true"
157+
></control-button>
158+
<control-button
159+
v-else
125160
icon="fa-project-diagram"
126161
title="Finish and track the circle annotation"
127162
v-on:click="finishTrackAnnotation"
@@ -131,14 +166,21 @@
131166
</control-button>
132167
<control-button
133168
icon="icon-linestring"
134-
title="Start a line annotation 𝗙"
169+
:title="(singleAnnotation ? 'Draw a line string' : 'Start a line annotation') + ' 𝗙'"
135170
:hover="false"
136171
:open="isDrawingLineString"
137172
:active="isDrawingLineString"
138173
:disabled="hasError"
139174
@click="drawLineString"
140175
>
141176
<control-button
177+
v-if="singleAnnotation"
178+
icon="fa-check"
179+
title="Disable the single-frame annotation option to create multi-frame annotations"
180+
:disabled="true"
181+
></control-button>
182+
<control-button
183+
v-else
142184
icon="fa-check"
143185
title="Finish the line annotation 𝗘𝗻𝘁𝗲𝗿"
144186
:disabled="cantFinishDrawAnnotation"
@@ -147,14 +189,20 @@
147189
</control-button>
148190
<control-button
149191
icon="icon-polygon"
150-
title="Start a polygon annotation 𝗚"
192+
:title="(singleAnnotation ? 'Draw a polygon' : 'Start a polygon annotation') + ' 𝗚'"
151193
:open="isDrawingPolygon"
152194
:active="isDrawingPolygon"
153195
:disabled="hasError"
154196
@click="drawPolygon"
155197
>
156198
<control-button
157-
v-if="isDrawingPolygon || isUsingPolygonBrush"
199+
v-if="(isDrawingPolygon || isUsingPolygonBrush) && singleAnnotation"
200+
icon="fa-check"
201+
title="Disable the single-frame annotation option to create multi-frame annotations"
202+
:disabled="true"
203+
></control-button>
204+
<control-button
205+
v-else-if="isDrawingPolygon || isUsingPolygonBrush"
158206
icon="fa-check"
159207
title="Finish the polygon annotation 𝗘𝗻𝘁𝗲𝗿"
160208
:disabled="cantFinishDrawAnnotation"
@@ -181,14 +229,21 @@
181229
</control-button>
182230
<control-button
183231
icon="icon-wholeframe"
184-
title="Start a whole frame annotation 𝗛"
232+
:title="(singleAnnotation ? 'Create a whole frame annotation' : 'Start a whole frame annotation') + ' 𝗛'"
185233
:hover="false"
186234
:open="isDrawingWholeFrame"
187235
:active="isDrawingWholeFrame"
188236
:disabled="hasError"
189237
@click="drawWholeFrame"
190238
>
191239
<control-button
240+
v-if="singleAnnotation"
241+
icon="fa-check"
242+
title="Disable the single-frame annotation option to create multi-frame annotations"
243+
:disabled="true"
244+
></control-button>
245+
<control-button
246+
v-else
192247
icon="fa-check"
193248
title="Finish the whole frame annotation 𝗘𝗻𝘁𝗲𝗿"
194249
:disabled="cantFinishDrawAnnotation"
@@ -360,6 +415,10 @@ export default {
360415
type: Boolean,
361416
default: true,
362417
},
418+
singleAnnotation: {
419+
type: Boolean,
420+
default: false,
421+
},
363422
showMousePosition: {
364423
type: Boolean,
365424
default: true,

resources/assets/js/videos/components/videoScreen/drawInteractions.vue

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,32 @@ import VectorLayer from '@biigle/ol/layer/Vector';
77
import VectorSource from '@biigle/ol/source/Vector';
88
import snapInteraction from "./snapInteraction.vue";
99
import { isInvalidShape } from '../../../annotations/utils';
10+
import { Point } from '@biigle/ol/geom';
11+
import * as preventDoubleclick from '../../../prevent-doubleclick';
1012
1113
/**
1214
* Mixin for the videoScreen component that contains logic for the draw interactions.
1315
*
1416
* @type {Object}
1517
*/
18+
1619
export default {
1720
mixins: [snapInteraction],
1821
data() {
1922
return {
2023
pendingAnnotation: {},
2124
autoplayDrawTimeout: null,
2225
drawEnded: true,
26+
lastDrawnPoint: new Point(0, 0),
27+
lastDrawnPointTime: 0,
2328
};
2429
},
30+
props: {
31+
singleAnnotation: {
32+
type: Boolean,
33+
default: false
34+
}
35+
},
2536
computed: {
2637
hasSelectedLabel() {
2738
return !!this.selectedLabel;
@@ -123,6 +134,9 @@ export default {
123134
if (this.isDrawingWholeFrame) {
124135
this.pendingAnnotation.frames.push(this.video.currentTime);
125136
this.$emit('pending-annotation', this.pendingAnnotation);
137+
if (this.singleAnnotation) {
138+
this.finishDrawAnnotation();
139+
}
126140
} else {
127141
this.drawInteraction = new DrawInteraction({
128142
source: this.pendingAnnotationSource,
@@ -207,6 +221,24 @@ export default {
207221
window.clearTimeout(this.autoplayDrawTimeout);
208222
this.autoplayDrawTimeout = window.setTimeout(this.pause, this.autoplayDraw * 1000);
209223
}
224+
225+
if (this.singleAnnotation) {
226+
if (this.isDrawingPoint) {
227+
if (this.isPointDoubleClick(e)) {
228+
// The feature is added to the source only after this event
229+
// is handled, so remove has to happen after the addfeature
230+
// event.
231+
this.pendingAnnotationSource.once('addfeature', function (e) {
232+
this.removeFeature(e.feature);
233+
});
234+
this.resetPendingAnnotation(this.pendingAnnotation.shape);
235+
return;
236+
}
237+
this.lastDrawnPointTime = new Date().getTime();
238+
this.lastDrawnPoint = e.feature.getGeometry();
239+
}
240+
this.pendingAnnotationSource.once('addfeature', this.finishDrawAnnotation);
241+
}
210242
} else {
211243
// If the pending annotation (time) is invalid, remove it again.
212244
// We have to wait for this feature to be added to the source to be able
@@ -217,6 +249,11 @@ export default {
217249
}
218250
219251
this.$emit('pending-annotation', this.pendingAnnotation);
252+
253+
},
254+
isPointDoubleClick(e) {
255+
return new Date().getTime() - this.lastDrawnPointTime < preventDoubleclick.POINT_CLICK_COOLDOWN
256+
&& preventDoubleclick.computeDistance(this.lastDrawnPoint, e.feature.getGeometry()) < preventDoubleclick.POINT_CLICK_DISTANCE;
220257
},
221258
},
222259
created() {

resources/assets/js/videos/stores/settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ let defaults = {
1111
enableJumpByFrame: false,
1212
jumpStep: 5.0,
1313
muteVideo: true,
14+
singleAnnotation: false,
1415
};
1516

1617
export default new Settings({

resources/assets/js/videos/videoContainer.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default {
7373
showThumbnailPreview: true,
7474
enableJumpByFrame: false,
7575
muteVideo: true,
76+
singleAnnotation: false,
7677
},
7778
openTab: '',
7879
urlParams: {

resources/views/manual/tutorials/videos/sidebar.blade.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,9 @@
8787
<p>
8888
The mute video switch enables or disables the audio track of the video.
8989
</p>
90+
91+
<p>
92+
The Single Frame Annotation switch allows you to add annotations with a single click by automatically completing them after the first frame. When enabled, additional controls for finishing and tracking are disabled.
93+
</p>
9094
</div>
9195
@endsection

resources/views/videos/show/content.blade.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
:selected-label="selectedLabel"
3939
:show-label-tooltip="settings.showLabelTooltip"
4040
:show-minimap="settings.showMinimap"
41+
:single-annotation="settings.singleAnnotation"
4142
:show-mouse-position="settings.showMousePosition"
4243
:enable-jump-by-frame="settings.enableJumpByFrame"
4344
:video="video"

resources/views/videos/show/sidebar-settings.blade.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
<div class="sidebar-tab__section">
6969
<power-toggle :active="muteVideo" title-off="Mute video" title-on="Unmute video" v-on:on="handleMuteVideo" v-on:off="handleUnmuteVideo">Mute Video</power-toggle>
7070
</div>
71+
72+
<div class="sidebar-tab__section">
73+
<power-toggle :active="singleAnnotation" title-off="Enable always creating single-frame annotations" title-on="Disable always creating single-frame annotations" v-on:on="handleSingleAnnotation" v-on:off="handleDisableSingleAnnotation">Single-Frame Annotation</power-toggle>
74+
</div>
7175
</div>
7276
</settings-tab>
7377
</sidebar-tab>

0 commit comments

Comments
 (0)