@abi-software/scaffoldvuer 1.3.3 → 1.4.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,9 +15,12 @@
15
15
  :x="tData.x"
16
16
  :y="tData.y"
17
17
  :annotationDisplay="annotationDisplay"
18
+ :imageThumbnailSidebar="imageThumbnailSidebar"
19
+ :imageThumbnails="imageThumbnailsEntry"
18
20
  @confirm-create="confirmCreate($event)"
19
21
  @cancel-create="cancelCreate()"
20
22
  @confirm-delete="confirmDelete($event)"
23
+ @image-thumbnail-open="onImageThumbnailOpen"
21
24
  />
22
25
  <div
23
26
  id="organsDisplayArea"
@@ -284,29 +287,66 @@
284
287
  ref="backgroundPopover"
285
288
  :virtual-ref="backgroundIconRef"
286
289
  placement="top-start"
287
- width="128"
290
+ width="330"
288
291
  :teleported="false"
289
292
  trigger="click"
290
- popper-class="background-popper non-selectable"
293
+ popper-class="background-popper non-selectable h-auto"
291
294
  virtual-triggering
292
295
  >
293
296
  <div>
294
297
  <el-row class="backgroundText">Viewing Mode</el-row>
295
- <el-row class="backgroundControl">
296
- <el-select
297
- :teleported="false"
298
- v-model="viewingMode"
299
- placeholder="Select"
300
- class="scaffold-select-box viewing-mode"
301
- popper-class="scaffold_viewer_dropdown"
302
- @change="viewingModeChange"
303
- >
304
- <el-option v-for="item in viewingModes" :key="item" :label="item" :value="item">
305
- <el-row>
306
- <el-col :span="12">{{ item }}</el-col>
307
- </el-row>
308
- </el-option>
309
- </el-select>
298
+ <el-row class="backgroundChooser">
299
+ <div style="margin-bottom: 2px;">
300
+ <template
301
+ v-for="(value, key, index) in viewingModes"
302
+ :key="key"
303
+ >
304
+ <template v-if="key === viewingMode">
305
+ <span class="viewing-mode-title"><b >{{ key }}</b></span>
306
+ </template>
307
+ <template v-else>
308
+ <span class="viewing-mode-unselected" @click="changeViewingMode(key)">{{ key }}</span>
309
+ </template>
310
+ </template>
311
+ </div>
312
+ <el-row class="viewing-mode-description">
313
+ {{ viewingModes[viewingMode] }}
314
+ </el-row>
315
+ </el-row>
316
+ <el-row class="backgroundSpacer" v-if="viewingMode === 'Exploration'"></el-row>
317
+ <el-row class="backgroundText" v-if="viewingMode === 'Exploration'">Markers display</el-row>
318
+ <el-row class="backgroundChooser" v-if="viewingMode === 'Exploration'">
319
+ <el-col :span="14">
320
+ <el-radio-group
321
+ v-model="imageRadio"
322
+ class="flatmap-radio"
323
+ @change="setImage"
324
+ >
325
+ <el-radio :value="false">Standard</el-radio>
326
+ <el-radio :value="true">Image</el-radio>
327
+ </el-radio-group>
328
+ </el-col>
329
+ <el-col :span="10" v-if="imageRadio">
330
+ <el-select
331
+ :teleported="false"
332
+ v-model="imageType"
333
+ placeholder="Select"
334
+ class="scaffold-select-box imageSelector"
335
+ popper-class="scaffold_viewer_dropdown"
336
+ @change="setImageType"
337
+ >
338
+ <el-option
339
+ v-for="item in imageTypes"
340
+ :key="item"
341
+ :label="item"
342
+ :value="item"
343
+ >
344
+ <el-row>
345
+ <el-col :span="12">{{ item }}</el-col>
346
+ </el-row>
347
+ </el-option>
348
+ </el-select>
349
+ </el-col>
310
350
  </el-row>
311
351
  <el-row class="backgroundSpacer"></el-row>
312
352
  <el-row class="backgroundText"> Change background </el-row>
@@ -380,7 +420,7 @@
380
420
 
381
421
  <script>
382
422
  /* eslint-disable no-alert, no-console */
383
- import { markRaw, shallowRef } from 'vue';
423
+ import { markRaw, toRaw, shallowRef } from 'vue';
384
424
  import {
385
425
  WarningFilled as ElIconWarningFilled,
386
426
  ArrowDown as ElIconArrowDown,
@@ -419,8 +459,16 @@ import { AnnotationService } from '@abi-software/sparc-annotation';
419
459
  import { EventNotifier } from "../scripts/EventNotifier.js";
420
460
  import { OrgansViewer } from "../scripts/OrgansRenderer.js";
421
461
  import { SearchIndex } from "../scripts/Search.js";
422
- import { mapState } from 'pinia';
423
- import { useMainStore } from "@/store/index";
462
+ import { mapState, mapStores } from 'pinia';
463
+ import { useMainStore } from "@/stores/index";
464
+ import { useSettingsStore } from '@/stores/settings'
465
+ import {
466
+ getBiolucidaThumbnails,
467
+ getSegmentationThumbnails,
468
+ getScaffoldThumbnails,
469
+ getPlotThumbnails
470
+ } from '../services/scicrunchQueries'
471
+ import imageMixin from '../mixins/imageMixin.js'
424
472
 
425
473
  /**
426
474
  * A vue component of the scaffold viewer.
@@ -430,6 +478,7 @@ import { useMainStore } from "@/store/index";
430
478
  */
431
479
  export default {
432
480
  name: "ScaffoldVuer",
481
+ mixins: [imageMixin],
433
482
  components: {
434
483
  Button,
435
484
  Col,
@@ -564,6 +613,20 @@ export default {
564
613
  type: Boolean,
565
614
  default: false,
566
615
  },
616
+ /**
617
+ * GroupName to value pair.
618
+ * The value can be a single number or and object in the following
619
+ * form:
620
+ *
621
+ * {
622
+ * number: Number,
623
+ * imgURL: String
624
+ * }
625
+ *
626
+ * When imgURL is specified, scaffoldvuer will attempt to render
627
+ * the image in imgURL as marker instead.
628
+ *
629
+ */
567
630
  markerLabels : {
568
631
  type: Object,
569
632
  default: function () {
@@ -673,10 +736,24 @@ export default {
673
736
  /**
674
737
  * Enable local annotations
675
738
  */
676
- enableLocalAnnotations: {
739
+ enableLocalAnnotations: {
677
740
  type: Boolean,
678
741
  default: false
679
742
  },
743
+ /**
744
+ * Specify the endpoint of the SPARC API.
745
+ */
746
+ sparcAPI: {
747
+ type: String,
748
+ default: 'https://api.sparc.science/',
749
+ },
750
+ /**
751
+ * The option to show image thumbnail in sidebar
752
+ */
753
+ imageThumbnailSidebar: {
754
+ type: Boolean,
755
+ default: false,
756
+ },
680
757
  },
681
758
  provide() {
682
759
  return {
@@ -684,6 +761,7 @@ export default {
684
761
  scaffoldUrl: this.url,
685
762
  $annotator: this.annotator,
686
763
  boundingDims: this.boundingDims,
764
+ getFeaturesAlert: () => undefined,
687
765
  };
688
766
  },
689
767
  data: function () {
@@ -774,12 +852,13 @@ export default {
774
852
  active: false,
775
853
  },
776
854
  fileFormat: "metadata",
777
- previousMarkerLabels: markRaw({}),
855
+ markerLabelEntry: markRaw({}),
856
+ previousMarkerLabelEntry: markRaw({}),
778
857
  viewingMode: "Exploration",
779
- viewingModes: [
780
- "Annotation",
781
- "Exploration",
782
- ],
858
+ viewingModes: {
859
+ "Exploration": "View and explore detailed visualization of 3D scaffolds",
860
+ "Annotation": "View internal identifiers of features",
861
+ },
783
862
  openMapRef: undefined,
784
863
  backgroundIconRef: undefined,
785
864
  userInformation: undefined,
@@ -796,6 +875,10 @@ export default {
796
875
  centre: [0, 0, 0],
797
876
  size:[1, 1, 1],
798
877
  },
878
+ imageRadio: false,
879
+ imageType: 'Image',
880
+ imageTypes: ['Image', 'Segmentation', 'Scaffold', 'Plot'],
881
+ imageClicked: '',
799
882
  };
800
883
  },
801
884
  watch: {
@@ -875,14 +958,17 @@ export default {
875
958
  },
876
959
  immediate: true,
877
960
  },
878
- markerLabels: function(labels) {
879
- for (const [key, value] of Object.entries(this.previousMarkerLabels)) {
961
+ markerLabels: function (labels) {
962
+ this.markerLabelEntry = markRaw({...labels})
963
+ },
964
+ markerLabelEntry: function (entry) {
965
+ for (const [key, value] of Object.entries(this.previousMarkerLabelEntry)) {
880
966
  this.setMarkerModeForObjectsWithName(key, value, "off");
881
967
  }
882
- for (const [key, value] of Object.entries(labels)) {
968
+ for (const [key, value] of Object.entries(entry)) {
883
969
  this.setMarkerModeForObjectsWithName(key, value, "on");
884
970
  }
885
- this.previousMarkerLabels = markRaw({...labels});
971
+ this.previousMarkerLabelEntry = markRaw({...entry});
886
972
  },
887
973
  },
888
974
  beforeCreate: function () {
@@ -922,9 +1008,13 @@ export default {
922
1008
  },
923
1009
  computed: {
924
1010
  ...mapState(useMainStore, ['userToken']),
1011
+ ...mapStores(useSettingsStore),
925
1012
  annotationDisplay: function() {
926
1013
  return this.viewingMode === 'Annotation' && this.tData.active === true &&
927
1014
  (this.activeDrawMode === 'Edit' || this.activeDrawMode === 'Delete');
1015
+ },
1016
+ imageThumbnailsEntry: function() {
1017
+ return this.imageClicked ? this.convertUberonToName() : {};
928
1018
  }
929
1019
  },
930
1020
  methods: {
@@ -1487,6 +1577,13 @@ export default {
1487
1577
  this.$refs.scaffoldTreeControls.removeActive(false);
1488
1578
  }
1489
1579
  }
1580
+ if (this.imageRadio && event.identifiers.length && event.identifiers[0]) {
1581
+ this.imageClicked = event.identifiers[0].data.id
1582
+ ? event.identifiers[0].data.id
1583
+ : event.identifiers[0].data.group;
1584
+ } else {
1585
+ this.imageClicked = ''
1586
+ }
1490
1587
  //Emit when an object is selected
1491
1588
  //@arg Identifier of selected objects
1492
1589
  this.$emit("scaffold-selected", event.identifiers);
@@ -1537,6 +1634,12 @@ export default {
1537
1634
  this.createEditTemporaryLines(event.identifiers[0].extraData.worldCoords);
1538
1635
  }
1539
1636
  this.createEditTemporaryLines(event.identifiers[0].extraData.worldCoords);
1637
+ const id = event.identifiers[0].data.id
1638
+ ? event.identifiers[0].data.id
1639
+ : event.identifiers[0].data.group;
1640
+ if (this.imageClicked !== id) {
1641
+ this.imageClicked = ''
1642
+ }
1540
1643
  }
1541
1644
  }
1542
1645
  }
@@ -1863,9 +1966,13 @@ export default {
1863
1966
  },
1864
1967
  /**
1865
1968
  * Callback on viewing mode change
1969
+ * Optional, can be used to update the view mode.
1866
1970
  */
1867
- viewingModeChange: function () {
1971
+ changeViewingMode: function (modeName) {
1868
1972
  if (this.$module) {
1973
+ if (modeName) {
1974
+ this.viewingMode = modeName
1975
+ }
1869
1976
  if (this.viewingMode === "Annotation") {
1870
1977
  let authenticated = false;
1871
1978
  if (this.userInformation) {
@@ -1919,13 +2026,19 @@ export default {
1919
2026
  /**
1920
2027
  * Set the marker modes for objects with the provided name, mode can
1921
2028
  * be "on", "off" or "inherited".
2029
+ * Value can either be number or an object containing number and
2030
+ * imgURL.
1922
2031
  */
1923
- setMarkerModeForObjectsWithName: function (name, number, mode) {
2032
+ setMarkerModeForObjectsWithName: function (name, value, mode) {
1924
2033
  if (name && this.$module.scene) {
2034
+ let options = value;
2035
+ if (typeof value === 'number') {
2036
+ options = { number: value, imgURL: undefined };
2037
+ }
1925
2038
  const rootRegion = this.$module.scene.getRootRegion();
1926
2039
  const groups = [name];
1927
2040
  const objects = findObjectsWithNames(rootRegion, groups, "", true);
1928
- objects.forEach(object => object.setMarkerMode(mode, { number }));
2041
+ objects.forEach(object => object.setMarkerMode(mode, options));
1929
2042
  }
1930
2043
  },
1931
2044
  /**
@@ -2301,15 +2414,67 @@ export default {
2301
2414
  this.$module.toggleSyncControl(flag, rotateMode);
2302
2415
  this.$module.setSyncControlCallback(this.syncControlCallback);
2303
2416
  },
2304
-
2305
2417
  /**
2306
2418
  * Set the markers for the scene.
2307
2419
  */
2308
2420
  setMarkers: function () {
2309
- for (const [key, value] of Object.entries(this.markerLabels)) {
2421
+ for (const [key, value] of Object.entries(this.markerLabelEntry)) {
2310
2422
  this.setMarkerModeForObjectsWithName(key, value, "on");
2311
2423
  }
2312
2424
  },
2425
+ removeImageThumbnails: function () {
2426
+ this.imageThumbnails = {}
2427
+ this.markerLabelEntry = markRaw(this.markerLabels)
2428
+ },
2429
+ setImage: function (flag) {
2430
+ if (flag) {
2431
+ this.setImageType(this.imageType)
2432
+ } else {
2433
+ this.removeImageThumbnails()
2434
+ }
2435
+ },
2436
+ setImageType: async function (type) {
2437
+ this.imageType = type
2438
+ if (!this.settingsStore.imageTypeCached(type)) {
2439
+ this.loading = true
2440
+ await this.fetchImageThumbnails(type)
2441
+ this.loading = false
2442
+ }
2443
+ this.populateImageThumbnails(type)
2444
+ },
2445
+ fetchImageThumbnails: async function (type) {
2446
+ let thumbnails = {}
2447
+ const organCuries = this.settingsStore.organCuries
2448
+ if (type === 'Image') {
2449
+ thumbnails = await getBiolucidaThumbnails(this.sparcAPI, organCuries, type)
2450
+ } else if (type === 'Segmentation') {
2451
+ thumbnails = await getSegmentationThumbnails(this.sparcAPI, organCuries, type)
2452
+ } else if (type === 'Scaffold') {
2453
+ thumbnails = await getScaffoldThumbnails(this.sparcAPI, organCuries, type)
2454
+ } else if (type === 'Plot') {
2455
+ thumbnails = await getPlotThumbnails(this.sparcAPI, organCuries, type)
2456
+ }
2457
+ this.settingsStore.updateImageThumbnails(type, thumbnails)
2458
+ },
2459
+ convertUberonToName: function () {
2460
+ const organCuries = this.settingsStore.organCuries
2461
+ const identifiers = organCuries.filter((curie) => curie.name in this.markerLabels).map((curie) => curie.id)
2462
+ const imageThumbnails = this.settingsStore.getImageThumbnails(this.imageType, identifiers)
2463
+ return Object.assign({},
2464
+ Object.fromEntries(
2465
+ Object.entries(imageThumbnails)
2466
+ .map(([key, value]) => [organCuries.filter((curie) => curie.id === key)[0].name, value])))
2467
+ },
2468
+ populateImageThumbnails: async function (type) {
2469
+ this.removeImageThumbnails()
2470
+ const thumbnails = this.convertUberonToName()
2471
+ this.loading = true
2472
+ this.markerLabelEntry = markRaw(await this.populateMapWithImages(thumbnails, type))
2473
+ this.loading = false
2474
+ },
2475
+ onImageThumbnailOpen: function (payload) {
2476
+ this.$emit('image-thumbnail-open', payload);
2477
+ },
2313
2478
  },
2314
2479
  };
2315
2480
  </script>
@@ -2469,7 +2634,7 @@ export default {
2469
2634
  background-color: #ffffff;
2470
2635
  border: 1px solid $app-primary-color;
2471
2636
  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.06);
2472
- height: 140px;
2637
+ height: fit-content;
2473
2638
  min-width: 200px;
2474
2639
  .el-popper__arrow {
2475
2640
  &:before {
@@ -2478,6 +2643,10 @@ export default {
2478
2643
  }
2479
2644
  }
2480
2645
 
2646
+ :deep(.background-popper.el-popover.el-popper.h-auto) {
2647
+ height: auto !important;
2648
+ }
2649
+
2481
2650
  :deep(.open-map-popper.el-popover.el-popper) {
2482
2651
  padding-top: 5px;
2483
2652
  padding-bottom: 5px;
@@ -2680,15 +2849,38 @@ export default {
2680
2849
  }
2681
2850
  }
2682
2851
 
2852
+ .viewing-mode-title {
2853
+ font-size: 14px;
2854
+ font-weight: 600;
2855
+ color: $app-primary-color;
2856
+ margin: 8px;
2857
+ text-decoration: underline;
2858
+ cursor: pointer;
2859
+ }
2860
+
2861
+ .viewing-mode-unselected {
2862
+ font-size: 11px;
2863
+ font-weight: 600;
2864
+ color: rgb(48, 49, 51);
2865
+ margin: 8px;
2866
+ opacity: 0.5;
2867
+ cursor: pointer;
2868
+ }
2869
+
2870
+ .viewing-mode-description {
2871
+ font-size: 12px;
2872
+ color: rgb(48, 49, 51);
2873
+ text-align: left;
2874
+ padding-bottom: 4px;
2875
+ margin-left: 8px;
2876
+ }
2877
+
2683
2878
  .scaffold-select-box {
2684
2879
  border-radius: 4px;
2685
2880
  border: 1px solid rgb(144, 147, 153);
2686
2881
  background-color: var(--white);
2687
2882
  font-weight: 500;
2688
2883
  color: rgb(48, 49, 51);
2689
- &.viewing-mode {
2690
- width: 150px!important;
2691
- }
2692
2884
 
2693
2885
  &.speed {
2694
2886
  margin-left: 8px;
@@ -2699,6 +2891,10 @@ export default {
2699
2891
  min-height: 24px
2700
2892
  }
2701
2893
  }
2894
+
2895
+ &.imageSelector {
2896
+ width: 125px!important;
2897
+ }
2702
2898
  }
2703
2899
 
2704
2900
  :deep(.scaffold_viewer_dropdown) {
@@ -79,6 +79,7 @@ import {
79
79
  ElSlider as Slider,
80
80
  ElOption as Option,
81
81
  } from "element-plus";
82
+ import { markRaw } from 'vue';
82
83
 
83
84
  /**
84
85
  * A component to control the opacity of the target object.
@@ -116,39 +117,37 @@ export default {
116
117
  label: "z",
117
118
  },
118
119
  ],
120
+ zincObject: undefined,
119
121
  };
120
122
  },
121
- mounted: function () {
122
- this._zincObject = undefined;
123
- },
124
123
  methods: {
125
124
  setObject: function (object) {
126
125
  if (object.isTextureSlides) {
127
- this._zincObject = object;
128
- this.settings = this._zincObject.getTextureSettings();
126
+ this.zincObject = markRaw(object);
127
+ this.settings = this.zincObject.getTextureSettings();
129
128
  } else {
130
- this._zincObject = undefined;
129
+ this.zincObject = undefined;
131
130
  this.settings = [];
132
131
  }
133
132
  },
134
133
  addNewSlide: function () {
135
134
  const newSettings = { direction: "x", value: 0 };
136
- const returnSettings = this._zincObject.createSlide(newSettings);
135
+ const returnSettings = this.zincObject.createSlide(newSettings);
137
136
  this.settings.push(returnSettings);
138
137
  },
139
138
  modifyDirection: function(direction, slide) {
140
139
  if (slide) {
141
140
  slide.direction = direction;
142
- this._zincObject.modifySlideSettings(slide);
141
+ this.zincObject.modifySlideSettings(slide);
143
142
  }
144
143
  },
145
144
  modifySlide: function (slide) {
146
145
  if (slide) {
147
- this._zincObject.modifySlideSettings(slide);
146
+ this.zincObject.modifySlideSettings(slide);
148
147
  }
149
148
  },
150
149
  removeSlide: function (index, slide) {
151
- this._zincObject.removeSlideWithId(slide.id);
150
+ this.zincObject.removeSlideWithId(slide.id);
152
151
  this.settings.splice(index, 1);
153
152
  },
154
153
  },
@@ -119,6 +119,7 @@ import {
119
119
  ElMain as Main,
120
120
  ElSlider as Slider,
121
121
  } from "element-plus";
122
+ import { markRaw } from "vue";
122
123
 
123
124
  /**
124
125
  * A component to control the opacity of the target object.
@@ -141,6 +142,7 @@ export default {
141
142
  scale: 1,
142
143
  min: [0, 0, 0],
143
144
  max: [1, 1, 1],
145
+ zincObject: undefined,
144
146
  };
145
147
  },
146
148
  watch: {
@@ -163,14 +165,11 @@ export default {
163
165
  deep: true,
164
166
  },
165
167
  },
166
- mounted: function () {
167
- this._zincObject = undefined;
168
- },
169
168
  methods: {
170
169
  setObject: function (object) {
171
170
  if (object.isZincObject) {
172
- this._zincObject = object;
173
- const morph = this._zincObject.getGroup();
171
+ this.zincObject = markRaw(object);
172
+ const morph = this.zincObject.getGroup();
174
173
  if (morph && morph.position) {
175
174
  this.x = morph.position.x;
176
175
  this.y = morph.position.y;
@@ -178,7 +177,7 @@ export default {
178
177
  this.scale = morph.scale.x;
179
178
  }
180
179
  } else {
181
- this._zincObject = undefined;
180
+ this.zincObject = undefined;
182
181
  this.x = 0;
183
182
  this.y = 0;
184
183
  this.z = 0;
@@ -186,10 +185,10 @@ export default {
186
185
  }
187
186
  },
188
187
  modifyPosition: function() {
189
- this._zincObject.setPosition(this.x, this.y, this.z);
188
+ this.zincObject.setPosition(this.x, this.y, this.z);
190
189
  },
191
190
  modifyScale: function() {
192
- this._zincObject.setScaleAll(this.scale);
191
+ this.zincObject.setScaleAll(this.scale);
193
192
  },
194
193
  },
195
194
  };
@@ -26,6 +26,8 @@ declare module 'vue' {
26
26
  ElMain: typeof import('element-plus/es')['ElMain']
27
27
  ElOption: typeof import('element-plus/es')['ElOption']
28
28
  ElPopover: typeof import('element-plus/es')['ElPopover']
29
+ ElRadio: typeof import('element-plus/es')['ElRadio']
30
+ ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
29
31
  ElRow: typeof import('element-plus/es')['ElRow']
30
32
  ElSelect: typeof import('element-plus/es')['ElSelect']
31
33
  ElSlider: typeof import('element-plus/es')['ElSlider']
package/src/main.js CHANGED
@@ -2,7 +2,7 @@ import { createApp } from 'vue'
2
2
  import { createPinia } from 'pinia'
3
3
  import * as VueRouter from 'vue-router'
4
4
  import App from './App.vue'
5
- import { useMainStore } from '@/store/index'
5
+ import { useMainStore } from '@/stores/index'
6
6
 
7
7
  const routes = [
8
8
  { path: '/'},
@@ -0,0 +1,89 @@
1
+ export default {
2
+ methods: {
3
+ populateMapWithImages: async function (images, type) {
4
+ let imageMarkerLabels = {};
5
+ for (const [key, list] of Object.entries(images)) {
6
+ const response = await this.downloadImageThumbnail(key, list, type);
7
+ if (response) {
8
+ imageMarkerLabels[key] = response;
9
+ } else {
10
+ imageMarkerLabels[key] = this.markerLabels[key];
11
+ }
12
+ }
13
+ return imageMarkerLabels;
14
+ },
15
+ downloadImageThumbnail: async function (key, list, type) {
16
+ const count = list.length;
17
+ if (count > 0) {
18
+ //Pick a random image
19
+ const index = Math.floor(Math.random() * count);
20
+ const thumbnail = list[index].thumbnail;
21
+ try {
22
+ const response = await this.getImageThumbnail(thumbnail, type);
23
+ const markerObject = await this.addImageThumbnailMarker(key, response);
24
+ return markerObject;
25
+ } catch (error) {
26
+ // Failed to download, pick another one
27
+ list.splice(index);
28
+ this.downloadImageThumbnail(key, list, type);
29
+ }
30
+ }
31
+ },
32
+ getImageThumbnail: async function (url, type) {
33
+ return new Promise((resolve, reject) => {
34
+ if (type === "Image" || type === "Segmentation") {
35
+ this.getBinaryThumbnail(url)
36
+ .then((response) => resolve(response))
37
+ .catch((response) => reject(response));
38
+ } else {
39
+ this.getGenericThumbnail(url)
40
+ .then((response) => resolve(response))
41
+ .catch((response) => reject(response));
42
+ }
43
+ });
44
+ },
45
+ getBinaryThumbnail: async function (url) {
46
+ return new Promise((resolve, reject) => {
47
+ fetch(url)
48
+ .then((response) => {
49
+ if (response.status >= 200 && response.status < 300) {
50
+ return response.text();
51
+ } else {
52
+ reject();
53
+ }
54
+ })
55
+ .then((data) => {
56
+ if (data) {
57
+ let img = new Image();
58
+ img.onload = function () {
59
+ resolve(`data:'image/png';base64,${data}`);
60
+ };
61
+ img.onerror = function () {
62
+ reject(new Error("Failed to load image at " + url));
63
+ };
64
+ img.src = `data:'image/png';base64,${data}`;
65
+ } else {
66
+ reject(new Error("Failed to load image at " + url));
67
+ }
68
+ });
69
+ });
70
+ },
71
+ getGenericThumbnail: async function (url) {
72
+ return new Promise((resolve, reject) => {
73
+ let img = new Image();
74
+ img.onload = function () {
75
+ resolve(url);
76
+ };
77
+ img.onerror = function () {
78
+ reject(new Error("Failed to load image at " + url));
79
+ };
80
+ img.src = url;
81
+ });
82
+ },
83
+ addImageThumbnailMarker: async function (id, source) {
84
+ const blob = await (await fetch(source)).blob();
85
+ const blobUrl = URL.createObjectURL(blob);
86
+ return { number: this.markerLabels[id], imgURL: blobUrl };
87
+ },
88
+ },
89
+ };