123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- <template>
- <div class="options-bar">
- <div ref="draw_box"
- class="image"
- title="draw bounding box (Q)"
- :class="{active: interaction === 'draw-box'}"
- @click="$emit('interaction', 'draw-box')">
- <img alt="draw bounding box" src="@/assets/icons/screen-full.svg">
- </div>
- <div ref="extreme_clicking"
- class="image"
- title="extreme clicking (E)"
- :class="{active: interaction === 'extreme-clicking'}"
- @click="$emit('interaction', interaction === 'extreme-clicking' ? 'draw-box' : 'extreme-clicking')">
- <img v-if="interaction === 'extreme-clicking'"
- alt="extreme clicking" src="@/assets/icons/check.svg">
- <img v-else
- alt="extreme clicking" src="@/assets/icons/flame.svg">
- </div>
- <div class="spacer"/>
- <div ref="move_box"
- class="image"
- title="move and resize bounding box (R)"
- :class="{active: interaction === 'move-box'}"
- @click="$emit('interaction', 'move-box')">
- <img alt="move and resize bounding box" src="@/assets/icons/four-directions.svg">
- </div>
- <div v-if="labelsEnabled"
- ref="labels"
- class="image"
- title="label bounding box (T)"
- :class="{active: interaction === 'label-box'}"
- @click="labelSelector=true">
- <img alt="extreme clicking" src="@/assets/icons/tag.svg">
- </div>
- <label-selector v-if="labelSelector"
- :labels="labels"
- @close="labelSelector=false"
- @label="$emit('interaction', 'label-box'); $emit('label', $event)"/>
- <div ref="confirm_box"
- class="image"
- title="confirm bounding box (G)"
- :class="{active: interaction === 'confirm-box'}"
- @click="$emit('interaction', 'confirm-box')">
- <img alt="confirm bounding box" src="@/assets/icons/check-circle.svg">
- </div>
- <div ref="remove_box"
- class="image"
- title="remove bounding box (F)"
- :class="{active: interaction === 'remove-box'}"
- @click="$emit('interaction', 'remove-box')">
- <img alt="remove bounding box" src="@/assets/icons/trash.svg">
- </div>
- <div class="spacer"/>
- <div ref="crop_info"
- class="image"
- title="Show info of a bounding box (I)"
- :class="{active: interaction === 'info-box'}"
- @click="$emit('infoBox')">
- <img alt="zoom bounding box" src="@/assets/icons/info.svg">
- </div>
- <div ref="zoom_annotation"
- class="image"
- title="zoom bounding box (X)"
- :class="{active: interaction === 'zoom-box' || zoomBox}"
- @click="$emit(zoomBox ? 'unzoom' : 'interaction', 'zoom-box')">
- <img alt="zoom bounding box" src="@/assets/icons/codescan.svg">
- </div>
- <div ref="zoom_prev_annotation"
- class="image"
- title="zoom previous bounding box (C)"
- @click="$emit('prevzoom', true)">
- <img alt="zoom previous bounding box" src="@/assets/icons/chevron-left.svg">
- </div>
- <div ref="zoom_next_annotation"
- class="image"
- title="zoom next bounding box (V)"
- @click="$emit('nextzoom', true)">
- <img alt="zoom next bounding box" src="@/assets/icons/chevron-right.svg">
- </div>
- <div class="spacer"/>
- <div ref="show_user_annotations"
- class="image"
- title="show user annotations"
- :class="{active: filter && filter.includes('user')}"
- @click="invertFilter('user')">
- <img alt="show user annotations" src="@/assets/icons/smiley.svg">
- </div>
- <div ref="show_pipeline_annotations"
- class="image"
- title="show pipeline annotations"
- :class="{active: filter && filter.includes('pipeline')}"
- @click="invertFilter('pipeline')">
- <img alt="show pipeline annotations" src="@/assets/icons/cpu.svg">
- </div>
- <div class="spacer"/>
- <div ref="create_predictions"
- class="image"
- title="create predictions (B)"
- :class="{active: isPredictionRunning}"
- @click="predict">
- <img alt="create predictions" src="@/assets/icons/rocket.svg">
- </div>
- </div>
- </template>
- <script>
- import LabelSelector from "@/components/media/label-selector";
- export default {
- name: "options-bar",
- components: {LabelSelector},
- props: ['file', 'interaction', 'filter', 'label', 'labels', 'zoomBox'],
- created: function () {
- // get data
- this.getJobs();
- this.$root.socket.get(`/projects/${this.$root.project.identifier}/model`)
- .then(response => response.json())
- .then(model => this.model = model);
- // subscribe to changes
- this.$root.socket.on('connect', this.getJobs);
- this.$root.socket.on('create-job', this.addJob);
- this.$root.socket.on('remove-job', this.removeJob);
- this.$root.socket.on('edit-job', this.editJob);
- // subscribe to keypress events
- window.addEventListener('keypress', this.keypressEvent);
- },
- destroyed: function () {
- this.$root.socket.off('connect', this.getJobs);
- this.$root.socket.off('create-job', this.addJob);
- this.$root.socket.off('remove-job', this.removeJob);
- this.$root.socket.off('edit-job', this.editJob);
- window.removeEventListener('keypress', this.keypressEvent)
- },
- data: function () {
- return {
- jobs: [],
- labelSelector: false,
- model: null
- }
- },
- computed: {
- isPredictionRunning: function () {
- return this.jobs.filter(j => !j.finished && j.type === 'Model Interaction').length > 0;
- },
- labelsEnabled: function () {
- return this.model
- && (
- this.model.supports.includes('labeled-images')
- || this.model.supports.includes('labeled-bounding-boxes')
- );
- }
- },
- methods: {
- invertFilter: function (name) {
- if (!this.filter)
- return;
- if (this.filter.includes(name))
- this.$emit('filter', this.filter.filter(f => f !== name));
- else
- this.$emit('filter', [...this.filter, name]);
- },
- predict: function () {
- if (!this.isPredictionRunning) {
- // TODO then / error
- this.$root.socket.post(`/data/${this.file.identifier}/predict`, {
- predict: true
- });
- }
- },
- keypressEvent: function (event) {
- switch (event.key) {
- case 'r':
- this.$refs.move_box.click();
- break;
- case 'q':
- this.$refs.draw_box.click();
- break;
- case 'e':
- this.$refs.extreme_clicking.click();
- break;
- case 't':
- this.$refs.labels.click();
- event.preventDefault();
- break;
- case 'f':
- this.$refs.remove_box.click();
- break;
- case 'g':
- this.$refs.confirm_box.click();
- break;
- case 'b':
- this.$refs.create_predictions.click();
- break;
- case 'x':
- this.$refs.zoom_annotation.click();
- break;
- case 'c':
- this.$refs.zoom_prev_annotation.click();
- break;
- case 'v':
- this.$refs.zoom_next_annotation.click();
- break;
- }
- },
- getJobs: function () {
- this.$root.socket.get('/jobs')
- .then(response => response.json())
- .then(jobs => {
- this.jobs = [];
- jobs.forEach(this.addJob)
- });
- },
- addJob: function (job) {
- for (let j of this.jobs)
- if (j.identifier === job.identifier)
- return;
- this.jobs.push(job);
- },
- removeJob: function (job) {
- for (let i = 0; i < this.jobs.length; i++) {
- if (this.jobs[i].identifier === job.identifier) {
- this.jobs.splice(i, 1);
- return;
- }
- }
- },
- editJob: function (job) {
- for (let i = 0; i < this.jobs.length; i++) {
- if (this.jobs[i].identifier === job.identifier) {
- this.$set(this.jobs, i, job);
- return;
- }
- }
- }
- }
- }
- </script>
- <style scoped>
- .options-bar {
- /* background-color: rgba(0, 0, 0, 0.25); */
- background-color: #858585;
- z-index: 1;
- }
- .spacer {
- border-top: 1px solid rgba(255, 255, 255, 0.25);
- }
- .image {
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- margin: 0.4rem;
- border: 1px solid whitesmoke;
- border-radius: 0.5rem;
- width: 1.6rem;
- height: 1.6rem;
- }
- .image.active {
- background-color: rgba(0, 0, 0, 0.2);
- box-shadow: inset 0 0 5px 0 rgba(0, 0, 0, 0.2);
- }
- img {
- max-width: 1.1rem;
- max-height: 1.1rem;
- filter: invert(1);
- }
- .label-selector {
- position: absolute;
- top: 0;
- left: 50%;
- z-index: 101;
- width: 30rem;
- max-width: 90vw;
- max-height: 90vh;
- animation: label-selector-animation 0.5s ease-out forwards;
- }
- @keyframes label-selector-animation {
- from {
- transform: translateX(-50%) translateY(-100%);
- }
- to {
- transform: translateX(-50%) translateY(0%);
- }
- }
- </style>
|