123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- <template>
- <div class="annotation-overlay" :style="position"
- @touchstart="press" @touchmove="track" @touchend="release"
- @mousedown="press" @mousemove="track" @mouseup="release"
- @dragstart.stop>
- <annotation-box v-for="box in boundingBoxes"
- v-bind:key="box.identifier"
- :box="box"
- :position="box.data"
- :draggable="interaction === 'move-box'"
- :deletable="interaction === 'remove-box'"
- :taggable="interaction === 'label-box' ? label : false"
- :confirmable="interaction === 'confirm-box'"
- :labels="labels"
- @move="move"
- @resize="resize"/>
- <annotation-box v-if="current"
- :position="current"/>
- <div v-if="imageLabelResult"
- class="image-label"
- :class="{
- user: imageLabelResult.origin === 'user',
- pipeline: imageLabelResult.origin === 'pipeline'
- }">
- {{ imageLabelObject.name }}
- </div>
- </div>
- </template>
- <script>
- import AnnotationBox from "@/components/media/annotation-box";
- export default {
- name: "annotation-overlay",
- components: {AnnotationBox},
- props: ['file', 'position', 'size', 'interaction', 'filter', 'label', 'video', 'results', 'labels'],
- data: function () {
- return {
- callback: false,
- start: false,
- fixed: false,
- current: false,
- mousedown: false
- }
- },
- computed: {
- boundingBoxes: function () {
- return this.results.filter(b => {
- return b.type === 'bounding-box' && this.filter.includes(b.origin)
- && (!('frame' in b.data) || b.data.frame === this.video.frame)
- });
- },
- imageLabelResult: function () {
- for (let result of this.results)
- if (result.type === 'labeled-image')
- return result;
- return false;
- },
- imageLabelObject: function () {
- if (!this.imageLabelResult)
- return false;
- for (let label of this.labels)
- if (label.identifier === this.imageLabelResult.label)
- return label;
- return 'unknown label';
- }
- },
- methods: {
- getEventCoordinates: function (event) {
- if (!('clientX' in event)) {
- if ('touches' in event && event.touches.length > 0)
- event = event.touches[0];
- else if ('changedTouches' in event && event.changedTouches.length > 0)
- event = event.changedTouches[0];
- }
- return {
- x: Math.max(0, Math.min(1, (event.clientX - this.size.left) / this.size.width)),
- y: Math.max(0, Math.min(1, (event.clientY - this.size.top) / this.size.height))
- }
- },
- buildRectangle: function (start, end) {
- let lx, hx, ly, hy;
- if (this.fixed && 'offsetX' in this.fixed && 'offsetY' in this.fixed) {
- lx = Math.max(end.x + this.fixed.offsetX, 0);
- hx = Math.min(end.x + this.fixed.offsetX + this.fixed.w, 1);
- ly = Math.max(end.y + this.fixed.offsetY, 0);
- hy = Math.min(end.y + this.fixed.offsetY + this.fixed.h, 1);
- } else {
- lx = this.fixed && 'lx' in this.fixed ? this.fixed.lx : Math.max(Math.min(start.x, end.x), 0);
- hx = this.fixed && 'hx' in this.fixed ? this.fixed.hx : Math.min(Math.max(start.x, end.x), 1);
- ly = this.fixed && 'ly' in this.fixed ? this.fixed.ly : Math.max(Math.min(start.y, end.y), 0);
- hy = this.fixed && 'hy' in this.fixed ? this.fixed.hy : Math.min(Math.max(start.y, end.y), 1);
- }
- if (this.file.type === 'image') {
- return {
- x: lx,
- y: ly,
- w: hx - lx,
- h: hy - ly
- }
- } else {
- return {
- x: lx,
- y: ly,
- w: hx - lx,
- h: hy - ly,
- frame: this.video.frame
- }
- }
- },
- press: function (event) {
- this.mousedown = true;
- if (this.interaction === 'draw-box') {
- this.start = this.getEventCoordinates(event);
- }
- if (this.interaction === 'extreme-clicking') {
- const coordinates = this.getEventCoordinates(event);
- if (!this.start) {
- this.start = coordinates;
- } else if (!this.current) {
- this.current = this.buildRectangle(this.start, coordinates);
- } else {
- this.current = this.buildRectangle({
- x: Math.min(coordinates.x, this.current.x),
- y: Math.min(coordinates.y, this.current.y)
- }, {
- x: Math.max(coordinates.x, this.current.x + this.current.w),
- y: Math.max(coordinates.y, this.current.y + this.current.h)
- });
- }
- }
- if (this.interaction === 'label-box') {
- this.$root.socket.post(`/data/${this.file.identifier}/results`, {
- type: 'labeled-image',
- label: this.label.identifier
- });
- }
- if (this.interaction === 'confirm-box') {
- if (this.imageLabelResult) {
- this.$root.socket.post(`/results/${this.imageLabelResult.identifier}/confirm`, {
- confirm: true
- });
- }
- }
- if (this.interaction === 'remove-box') {
- if (this.imageLabelResult) {
- this.$root.socket.post(`/results/${this.imageLabelResult.identifier}/remove`, {
- remove: true
- });
- }
- }
- },
- track: function (event) {
- if (this.interaction === 'extreme-clicking') {
- if (this.mousedown)
- this.press(event);
- return;
- }
- if (this.start) {
- const coordinates = this.getEventCoordinates(event);
- this.current = this.buildRectangle(this.start, coordinates);
- }
- },
- release: function () {
- this.mousedown = false;
- if (this.interaction === 'extreme-clicking')
- return;
- if (this.current) {
- if (this.callback)
- this.callback(this.current);
- else
- // TODO then / error
- this.$root.socket.post(`/data/${this.file.identifier}/results`, {
- type: 'bounding-box',
- data: this.current
- });
- }
- this.callback = false;
- this.start = false;
- this.fixed = false;
- this.current = false;
- },
- move: function (event, position, callback) {
- if (this.interaction !== 'move-box')
- return;
- this.callback = callback;
- const coordinates = this.getEventCoordinates(event);
- this.start = position;
- this.fixed = {
- w: position.w,
- h: position.h,
- offsetX: position.x - coordinates.x,
- offsetY: position.y - coordinates.y
- };
- },
- resize: function (mode, position, callback) {
- if (this.interaction !== 'move-box')
- return;
- this.callback = callback;
- switch (mode) {
- case 'nw':
- this.start = {x: position.x + position.w, y: position.y + position.h};
- this.fixed = false;
- break;
- case 'ne':
- this.start = {x: position.x, y: position.y + position.h};
- this.fixed = false;
- break;
- case 'sw':
- this.start = {x: position.x + position.w, y: position.y};
- this.fixed = false;
- break;
- case 'se':
- this.start = {x: position.x, y: position.y};
- this.fixed = false;
- break;
- case 'nn':
- this.start = {x: position.x, y: position.y + position.h};
- this.fixed = {lx: position.x, hx: position.x + position.w};
- break;
- case 'ww':
- this.start = {x: position.x + position.w, y: position.y};
- this.fixed = {ly: position.y, hy: position.y + position.h};
- break;
- case 'ee':
- this.start = {x: position.x, y: position.y};
- this.fixed = {ly: position.y, hy: position.y + position.h};
- break;
- case 'ss':
- this.start = {x: position.x, y: position.y};
- this.fixed = {lx: position.x, hx: position.x + position.w};
- break;
- }
- }
- },
- watch: {
- interaction: function (newVal, oldVal) {
- if (oldVal === 'extreme-clicking')
- this.release();
- }
- }
- }
- </script>
- <style scoped>
- .annotation-overlay {
- position: absolute;
- }
- .image-label {
- position: absolute;
- right: 0;
- bottom: 0;
- padding: 0.7rem 1rem;
- color: whitesmoke;
- border-top-left-radius: 1rem;
- }
- .image-label.user {
- background-color: rgba(255, 0, 0, 0.4);
- }
- .image-label.pipeline {
- background-color: rgba(0, 0, 255, 0.4);
- }
- </style>
|