options-bar.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <template>
  2. <div class="options-bar">
  3. <div ref="draw_box"
  4. class="image"
  5. title="draw bounding box (Q)"
  6. :class="{active: interaction === 'draw-box'}"
  7. @click="$emit('interaction', 'draw-box')">
  8. <img alt="draw bounding box" src="@/assets/icons/screen-full.svg">
  9. </div>
  10. <div ref="extreme_clicking"
  11. class="image"
  12. title="extreme clicking (E)"
  13. :class="{active: interaction === 'extreme-clicking'}"
  14. @click="$emit('interaction', interaction === 'extreme-clicking' ? 'draw-box' : 'extreme-clicking')">
  15. <img v-if="interaction === 'extreme-clicking'"
  16. alt="extreme clicking" src="@/assets/icons/check.svg">
  17. <img v-else
  18. alt="extreme clicking" src="@/assets/icons/flame.svg">
  19. </div>
  20. <div class="spacer"/>
  21. <div ref="move_box"
  22. class="image"
  23. title="move and resize bounding box (R)"
  24. :class="{active: interaction === 'move-box'}"
  25. @click="$emit('interaction', 'move-box')">
  26. <img alt="move and resize bounding box" src="@/assets/icons/four-directions.svg">
  27. </div>
  28. <div v-if="labelsEnabled"
  29. ref="labels"
  30. class="image"
  31. title="label bounding box (T)"
  32. :class="{active: interaction === 'label-box'}"
  33. @click="labelSelector=true">
  34. <img alt="extreme clicking" src="@/assets/icons/tag.svg">
  35. </div>
  36. <label-selector v-if="labelSelector"
  37. :labels="labels"
  38. @close="labelSelector=false"
  39. @label="$emit('interaction', 'label-box'); $emit('label', $event)"/>
  40. <div ref="confirm_box"
  41. class="image"
  42. title="confirm bounding box (G)"
  43. :class="{active: interaction === 'confirm-box'}"
  44. @click="$emit('interaction', 'confirm-box')">
  45. <img alt="confirm bounding box" src="@/assets/icons/check-circle.svg">
  46. </div>
  47. <div ref="remove_box"
  48. class="image"
  49. title="remove bounding box (F)"
  50. :class="{active: interaction === 'remove-box'}"
  51. @click="$emit('interaction', 'remove-box')">
  52. <img alt="remove bounding box" src="@/assets/icons/trash.svg">
  53. </div>
  54. <div class="spacer"/>
  55. <div ref="crop_info"
  56. class="image"
  57. title="Show info of a bounding box (I)"
  58. :class="{active: interaction === 'info-box'}"
  59. @click="$emit('interaction', 'info-box')">
  60. <img alt="zoom bounding box" src="@/assets/icons/info.svg">
  61. </div>
  62. <!--
  63. <div ref="zoom_annotation"
  64. class="image"
  65. title="zoom bounding box (X)"
  66. :class="{active: interaction === 'zoom-box' || zoomBox}"
  67. @click="$emit(zoomBox ? 'unzoom' : 'interaction', 'zoom-box')">
  68. <img alt="zoom bounding box" src="@/assets/icons/codescan.svg">
  69. </div>
  70. <div ref="zoom_prev_annotation"
  71. class="image"
  72. title="zoom previous bounding box (C)"
  73. @click="$emit('prevzoom', true)">
  74. <img alt="zoom previous bounding box" src="@/assets/icons/chevron-left.svg">
  75. </div>
  76. <div ref="zoom_next_annotation"
  77. class="image"
  78. title="zoom next bounding box (V)"
  79. @click="$emit('nextzoom', true)">
  80. <img alt="zoom next bounding box" src="@/assets/icons/chevron-right.svg">
  81. </div>
  82. -->
  83. <div class="spacer"/>
  84. <div ref="show_user_annotations"
  85. class="image"
  86. title="show user annotations"
  87. :class="{active: filter && filter.includes('user')}"
  88. @click="invertFilter('user')">
  89. <img alt="show user annotations" src="@/assets/icons/smiley.svg">
  90. </div>
  91. <div ref="show_pipeline_annotations"
  92. class="image"
  93. title="show pipeline annotations"
  94. :class="{active: filter && filter.includes('pipeline')}"
  95. @click="invertFilter('pipeline')">
  96. <img alt="show pipeline annotations" src="@/assets/icons/cpu.svg">
  97. </div>
  98. <div class="spacer"/>
  99. <div ref="create_predictions"
  100. class="image"
  101. title="create predictions (B)"
  102. :class="{active: isPredictionRunning}"
  103. @click="predict">
  104. <img alt="create predictions" src="@/assets/icons/rocket.svg">
  105. </div>
  106. </div>
  107. </template>
  108. <script>
  109. import LabelSelector from "@/components/media/label-selector";
  110. export default {
  111. name: "options-bar",
  112. components: {LabelSelector},
  113. props: ['file', 'interaction', 'filter', 'label', 'labels', 'infoBox', 'zoomBox'],
  114. created: function () {
  115. // get data
  116. this.getJobs();
  117. this.$root.socket.get(`/projects/${this.$root.project.identifier}/model`)
  118. .then(response => response.json())
  119. .then(model => this.model = model);
  120. // subscribe to changes
  121. this.$root.socket.on('connect', this.getJobs);
  122. this.$root.socket.on('create-job', this.addJob);
  123. this.$root.socket.on('remove-job', this.removeJob);
  124. this.$root.socket.on('edit-job', this.editJob);
  125. // subscribe to keypress events
  126. window.addEventListener('keypress', this.keypressEvent);
  127. },
  128. destroyed: function () {
  129. this.$root.socket.off('connect', this.getJobs);
  130. this.$root.socket.off('create-job', this.addJob);
  131. this.$root.socket.off('remove-job', this.removeJob);
  132. this.$root.socket.off('edit-job', this.editJob);
  133. window.removeEventListener('keypress', this.keypressEvent)
  134. },
  135. data: function () {
  136. return {
  137. jobs: [],
  138. labelSelector: false,
  139. model: null
  140. }
  141. },
  142. computed: {
  143. isPredictionRunning: function () {
  144. return this.jobs.filter(j => !j.finished && j.type === 'Model Interaction').length > 0;
  145. },
  146. labelsEnabled: function () {
  147. return this.model
  148. && (
  149. this.model.supports.includes('labeled-images')
  150. || this.model.supports.includes('labeled-bounding-boxes')
  151. );
  152. }
  153. },
  154. methods: {
  155. invertFilter: function (name) {
  156. if (!this.filter)
  157. return;
  158. if (this.filter.includes(name))
  159. this.$emit('filter', this.filter.filter(f => f !== name));
  160. else
  161. this.$emit('filter', [...this.filter, name]);
  162. },
  163. predict: function () {
  164. if (!this.isPredictionRunning) {
  165. // TODO then / error
  166. this.$root.socket.post(`/data/${this.file.identifier}/predict`, {
  167. predict: true
  168. });
  169. }
  170. },
  171. keypressEvent: function (event) {
  172. switch (event.key) {
  173. case 'r':
  174. this.$refs.move_box.click();
  175. break;
  176. case 'q':
  177. this.$refs.draw_box.click();
  178. break;
  179. case 'e':
  180. this.$refs.extreme_clicking.click();
  181. break;
  182. case 't':
  183. this.$refs.labels.click();
  184. event.preventDefault();
  185. break;
  186. case 'f':
  187. this.$refs.remove_box.click();
  188. break;
  189. case 'g':
  190. this.$refs.confirm_box.click();
  191. break;
  192. case 'b':
  193. this.$refs.create_predictions.click();
  194. break;
  195. case 'x':
  196. this.$refs.zoom_annotation.click();
  197. break;
  198. case 'c':
  199. this.$refs.zoom_prev_annotation.click();
  200. break;
  201. case 'v':
  202. this.$refs.zoom_next_annotation.click();
  203. break;
  204. case 'i':
  205. this.$refs.crop_info.click();
  206. break;
  207. }
  208. },
  209. getJobs: function () {
  210. this.$root.socket.get('/jobs')
  211. .then(response => response.json())
  212. .then(jobs => {
  213. this.jobs = [];
  214. jobs.forEach(this.addJob)
  215. });
  216. },
  217. addJob: function (job) {
  218. for (let j of this.jobs)
  219. if (j.identifier === job.identifier)
  220. return;
  221. this.jobs.push(job);
  222. },
  223. removeJob: function (job) {
  224. for (let i = 0; i < this.jobs.length; i++) {
  225. if (this.jobs[i].identifier === job.identifier) {
  226. this.jobs.splice(i, 1);
  227. return;
  228. }
  229. }
  230. },
  231. editJob: function (job) {
  232. for (let i = 0; i < this.jobs.length; i++) {
  233. if (this.jobs[i].identifier === job.identifier) {
  234. this.$set(this.jobs, i, job);
  235. return;
  236. }
  237. }
  238. }
  239. }
  240. }
  241. </script>
  242. <style scoped>
  243. .options-bar {
  244. /* background-color: rgba(0, 0, 0, 0.25); */
  245. background-color: #858585;
  246. z-index: 1;
  247. }
  248. .spacer {
  249. border-top: 1px solid rgba(255, 255, 255, 0.25);
  250. }
  251. .image {
  252. display: flex;
  253. justify-content: center;
  254. align-items: center;
  255. cursor: pointer;
  256. margin: 0.4rem;
  257. border: 1px solid whitesmoke;
  258. border-radius: 0.5rem;
  259. width: 1.6rem;
  260. height: 1.6rem;
  261. }
  262. .image.active {
  263. background-color: rgba(0, 0, 0, 0.2);
  264. box-shadow: inset 0 0 5px 0 rgba(0, 0, 0, 0.2);
  265. }
  266. img {
  267. max-width: 1.1rem;
  268. max-height: 1.1rem;
  269. filter: invert(1);
  270. }
  271. .label-selector {
  272. position: absolute;
  273. top: 0;
  274. left: 50%;
  275. z-index: 101;
  276. width: 30rem;
  277. max-width: 90vw;
  278. max-height: 100%;
  279. padding-bottom: 0;
  280. animation: label-selector-animation 0.5s ease-out forwards;
  281. }
  282. @keyframes label-selector-animation {
  283. from {
  284. transform: translateX(-50%) translateY(-100%);
  285. }
  286. to {
  287. transform: translateX(-50%) translateY(0%);
  288. }
  289. }
  290. </style>