Skip to content

Commit

Permalink
Merge pull request #1019 from biigle/videoAnnotation
Browse files Browse the repository at this point in the history
Video single frame annotation
  • Loading branch information
mzur authored Feb 12, 2025
2 parents ea98502 + 2321d1b commit b03aabf
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@ import Styles from '../../stores/styles';
import { shiftKeyOnly } from '@biigle/ol/events/condition';
import snapInteraction from '../../snapInteraction.vue';
import { Point } from '@biigle/ol/geom';
import * as preventDoubleclick from '../../../prevent-doubleclick';
function computeDistance(point1, point2) {
let p1=point1.getCoordinates();
let p2=point2.getCoordinates();
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
}
/**
* Mixin for the annotationCanvas component that contains logic for the draw interactions.
*
Expand All @@ -21,9 +16,6 @@ function computeDistance(point1, point2) {
let drawInteraction;
const POINT_CLICK_COOLDOWN = 400;
const POINT_CLICK_DISTANCE = 5;
// Custom OpenLayers freehandCondition that is true if a pen is used for input or
// if Shift is pressed otherwise.
let penOrShift = function (mapBrowserEvent) {
Expand Down Expand Up @@ -142,8 +134,8 @@ export default {
}
},
isPointDoubleClick(e) {
return new Date().getTime() - this.lastDrawnPointTime < POINT_CLICK_COOLDOWN
&& computeDistance(this.lastDrawnPoint,e.feature.getGeometry()) < POINT_CLICK_DISTANCE;
return new Date().getTime() - this.lastDrawnPointTime < preventDoubleclick.POINT_CLICK_COOLDOWN
&& preventDoubleclick.computeDistance(this.lastDrawnPoint,e.feature.getGeometry()) < preventDoubleclick.POINT_CLICK_DISTANCE;
},
},
watch: {
Expand Down
18 changes: 18 additions & 0 deletions resources/assets/js/prevent-doubleclick.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Compute the Euclidean distance between two points.
*
* @param Object point1 - The first point with getCoordinates() method.
* @param Object point2 - The second point with getCoordinates() method.
* @returns number - The computed distance between the two points.
*/

const POINT_CLICK_COOLDOWN = 400;
const POINT_CLICK_DISTANCE = 5;

let computeDistance = function (point1, point2) {
let p1 = point1.getCoordinates();
let p2 = point2.getCoordinates();
return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
};

export { computeDistance, POINT_CLICK_COOLDOWN, POINT_CLICK_DISTANCE };
12 changes: 12 additions & 0 deletions resources/assets/js/videos/components/settingsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default {
'enableJumpByFrame',
'jumpStep',
'muteVideo',
'singleAnnotation',
],
annotationOpacity: 1,
showMinimap: true,
Expand All @@ -60,6 +61,7 @@ export default {
showThumbnailPreview: true,
enableJumpByFrame: false,
muteVideo: true,
singleAnnotation: false,
};
},
computed: {
Expand Down Expand Up @@ -110,6 +112,12 @@ export default {
handleUnmuteVideo() {
this.muteVideo = false;
},
handleSingleAnnotation() {
this.singleAnnotation = true;
},
handleDisableSingleAnnotation() {
this.singleAnnotation = false;
},
toggleAnnotationOpacity() {
if (this.annotationOpacity > 0) {
this.annotationOpacity = 0;
Expand Down Expand Up @@ -170,6 +178,10 @@ export default {
this.$emit('update', 'muteVideo', show);
Settings.set('muteVideo', show);
},
singleAnnotation(show) {
this.$emit('update', 'singleAnnotation', show);
Settings.set('singleAnnotation', show);
},
},
created() {
this.restoreKeys.forEach((key) => {
Expand Down
73 changes: 66 additions & 7 deletions resources/assets/js/videos/components/videoScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,34 @@
<div v-if="canAdd" class="btn-group">
<control-button
icon="icon-point"
title="Start a point annotation 𝗔"
:title="(singleAnnotation ? 'Set a point' : 'Start a point annotation') + ' 𝗔'"
:hover="false"
:open="isDrawingPoint"
:active="isDrawingPoint"
:disabled="hasError"
@click="drawPoint"
>
<control-button
v-if="singleAnnotation"
icon="fa-check"
title="Disable the single-frame annotation option to create multi-frame annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-check"
title="Finish the point annotation 𝗘𝗻𝘁𝗲𝗿"
:disabled="cantFinishDrawAnnotation"
@click="finishDrawAnnotation"
></control-button>
<control-button
v-if="singleAnnotation"
icon="fa-project-diagram"
title="Disable the single-frame annotation option to track annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-project-diagram"
title="Finish and track the point annotation"
v-on:click="finishTrackAnnotation"
Expand All @@ -92,14 +106,21 @@
</control-button>
<control-button
icon="icon-rectangle"
title="Start a rectangle annotation 𝗦"
:title="(singleAnnotation ? 'Draw a rectangle' : 'Start a rectangle annotation') + ' 𝗦'"
:hover="false"
:open="isDrawingRectangle"
:active="isDrawingRectangle"
:disabled="hasError"
@click="drawRectangle"
>
<control-button
v-if="singleAnnotation"
icon="fa-check"
title="Disable the single-frame annotation option to create multi-frame annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-check"
title="Finish the rectangle annotation 𝗘𝗻𝘁𝗲𝗿"
:disabled="cantFinishDrawAnnotation"
Expand All @@ -108,20 +129,34 @@
</control-button>
<control-button
icon="icon-circle"
title="Start a circle annotation 𝗗"
:title="(singleAnnotation ? 'Draw a circle' : 'Start a circle annotation') + ' 𝗗'"
:hover="false"
:open="isDrawingCircle"
:active="isDrawingCircle"
:disabled="hasError"
@click="drawCircle"
>
<control-button
v-if="singleAnnotation"
icon="fa-check"
title="Disable the single-frame annotation option to create multi-frame annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-check"
title="Finish the circle annotation 𝗘𝗻𝘁𝗲𝗿"
:disabled="cantFinishDrawAnnotation"
@click="finishDrawAnnotation"
></control-button>
<control-button
v-if="singleAnnotation"
icon="fa-project-diagram"
title="Disable the single-frame annotation option to track annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-project-diagram"
title="Finish and track the circle annotation"
v-on:click="finishTrackAnnotation"
Expand All @@ -131,14 +166,21 @@
</control-button>
<control-button
icon="icon-linestring"
title="Start a line annotation 𝗙"
:title="(singleAnnotation ? 'Draw a line string' : 'Start a line annotation') + ' 𝗙'"
:hover="false"
:open="isDrawingLineString"
:active="isDrawingLineString"
:disabled="hasError"
@click="drawLineString"
>
<control-button
v-if="singleAnnotation"
icon="fa-check"
title="Disable the single-frame annotation option to create multi-frame annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-check"
title="Finish the line annotation 𝗘𝗻𝘁𝗲𝗿"
:disabled="cantFinishDrawAnnotation"
Expand All @@ -147,14 +189,20 @@
</control-button>
<control-button
icon="icon-polygon"
title="Start a polygon annotation 𝗚"
:title="(singleAnnotation ? 'Draw a polygon' : 'Start a polygon annotation') + ' 𝗚'"
:open="isDrawingPolygon"
:active="isDrawingPolygon"
:disabled="hasError"
@click="drawPolygon"
>
<control-button
v-if="isDrawingPolygon || isUsingPolygonBrush"
v-if="(isDrawingPolygon || isUsingPolygonBrush) && singleAnnotation"
icon="fa-check"
title="Disable the single-frame annotation option to create multi-frame annotations"
:disabled="true"
></control-button>
<control-button
v-else-if="isDrawingPolygon || isUsingPolygonBrush"
icon="fa-check"
title="Finish the polygon annotation 𝗘𝗻𝘁𝗲𝗿"
:disabled="cantFinishDrawAnnotation"
Expand All @@ -181,14 +229,21 @@
</control-button>
<control-button
icon="icon-wholeframe"
title="Start a whole frame annotation 𝗛"
:title="(singleAnnotation ? 'Create a whole frame annotation' : 'Start a whole frame annotation') + ' 𝗛'"
:hover="false"
:open="isDrawingWholeFrame"
:active="isDrawingWholeFrame"
:disabled="hasError"
@click="drawWholeFrame"
>
<control-button
v-if="singleAnnotation"
icon="fa-check"
title="Disable the single-frame annotation option to create multi-frame annotations"
:disabled="true"
></control-button>
<control-button
v-else
icon="fa-check"
title="Finish the whole frame annotation 𝗘𝗻𝘁𝗲𝗿"
:disabled="cantFinishDrawAnnotation"
Expand Down Expand Up @@ -360,6 +415,10 @@ export default {
type: Boolean,
default: true,
},
singleAnnotation: {
type: Boolean,
default: false,
},
showMousePosition: {
type: Boolean,
default: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@ import VectorLayer from '@biigle/ol/layer/Vector';
import VectorSource from '@biigle/ol/source/Vector';
import snapInteraction from "./snapInteraction.vue";
import { isInvalidShape } from '../../../annotations/utils';
import { Point } from '@biigle/ol/geom';
import * as preventDoubleclick from '../../../prevent-doubleclick';
/**
* Mixin for the videoScreen component that contains logic for the draw interactions.
*
* @type {Object}
*/
export default {
mixins: [snapInteraction],
data() {
return {
pendingAnnotation: {},
autoplayDrawTimeout: null,
drawEnded: true,
lastDrawnPoint: new Point(0, 0),
lastDrawnPointTime: 0,
};
},
props: {
singleAnnotation: {
type: Boolean,
default: false
}
},
computed: {
hasSelectedLabel() {
return !!this.selectedLabel;
Expand Down Expand Up @@ -123,6 +134,9 @@ export default {
if (this.isDrawingWholeFrame) {
this.pendingAnnotation.frames.push(this.video.currentTime);
this.$emit('pending-annotation', this.pendingAnnotation);
if (this.singleAnnotation) {
this.finishDrawAnnotation();
}
} else {
this.drawInteraction = new DrawInteraction({
source: this.pendingAnnotationSource,
Expand Down Expand Up @@ -207,6 +221,24 @@ export default {
window.clearTimeout(this.autoplayDrawTimeout);
this.autoplayDrawTimeout = window.setTimeout(this.pause, this.autoplayDraw * 1000);
}
if (this.singleAnnotation) {
if (this.isDrawingPoint) {
if (this.isPointDoubleClick(e)) {
// The feature is added to the source only after this event
// is handled, so remove has to happen after the addfeature
// event.
this.pendingAnnotationSource.once('addfeature', function (e) {
this.removeFeature(e.feature);
});
this.resetPendingAnnotation(this.pendingAnnotation.shape);
return;
}
this.lastDrawnPointTime = new Date().getTime();
this.lastDrawnPoint = e.feature.getGeometry();
}
this.pendingAnnotationSource.once('addfeature', this.finishDrawAnnotation);
}
} else {
// If the pending annotation (time) is invalid, remove it again.
// We have to wait for this feature to be added to the source to be able
Expand All @@ -217,6 +249,11 @@ export default {
}
this.$emit('pending-annotation', this.pendingAnnotation);
},
isPointDoubleClick(e) {
return new Date().getTime() - this.lastDrawnPointTime < preventDoubleclick.POINT_CLICK_COOLDOWN
&& preventDoubleclick.computeDistance(this.lastDrawnPoint, e.feature.getGeometry()) < preventDoubleclick.POINT_CLICK_DISTANCE;
},
},
created() {
Expand Down
1 change: 1 addition & 0 deletions resources/assets/js/videos/stores/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let defaults = {
enableJumpByFrame: false,
jumpStep: 5.0,
muteVideo: true,
singleAnnotation: false,
};

export default new Settings({
Expand Down
1 change: 1 addition & 0 deletions resources/assets/js/videos/videoContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default {
showThumbnailPreview: true,
enableJumpByFrame: false,
muteVideo: true,
singleAnnotation: false,
},
openTab: '',
urlParams: {
Expand Down
4 changes: 4 additions & 0 deletions resources/views/manual/tutorials/videos/sidebar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,9 @@
<p>
The mute video switch enables or disables the audio track of the video.
</p>

<p>
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.
</p>
</div>
@endsection
1 change: 1 addition & 0 deletions resources/views/videos/show/content.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
:selected-label="selectedLabel"
:show-label-tooltip="settings.showLabelTooltip"
:show-minimap="settings.showMinimap"
:single-annotation="settings.singleAnnotation"
:show-mouse-position="settings.showMousePosition"
:enable-jump-by-frame="settings.enableJumpByFrame"
:video="video"
Expand Down
4 changes: 4 additions & 0 deletions resources/views/videos/show/sidebar-settings.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
<div class="sidebar-tab__section">
<power-toggle :active="muteVideo" title-off="Mute video" title-on="Unmute video" v-on:on="handleMuteVideo" v-on:off="handleUnmuteVideo">Mute Video</power-toggle>
</div>

<div class="sidebar-tab__section">
<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>
</div>
</div>
</settings-tab>
</sidebar-tab>

0 comments on commit b03aabf

Please sign in to comment.