diff --git a/.gitignore b/.gitignore index e66fee60..08e66091 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ docs/.vitepress/cache docs/components .DS_Store /.vitepress +.env +.env.local diff --git a/package-lock.json b/package-lock.json index 6c804078..8eef7922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@abi-software/flatmap-viewer": "3.1.4", - "@abi-software/map-utilities": "1.1.0", + "@abi-software/map-utilities": "^1.1.1-beta.0", "@abi-software/sparc-annotation": "0.3.1", "@abi-software/svg-sprite": "1.0.0", "@element-plus/icons-vue": "^2.3.1", @@ -71,11 +71,23 @@ "polylabel": "^1.1.0" } }, + "node_modules/@abi-software/gallery": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abi-software/gallery/-/gallery-1.1.1.tgz", + "integrity": "sha512-EOnKEQpL/6R7UZQpjiaBn5g4KPxc8RHrIfyTf4ukA+WJ2SU5T+R5GIYzi8Kuzgh797NehPpe/iFzPa21FvN+HQ==", + "dependencies": { + "axios": "^1.6.5", + "element-plus": "^2.4.4", + "unplugin-vue-components": "^0.26.0", + "vue": "^3.3.13" + } + }, "node_modules/@abi-software/map-utilities": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@abi-software/map-utilities/-/map-utilities-1.1.0.tgz", - "integrity": "sha512-icRE0/VTa4RollDDHSqKsK63gwUAxAepDoAYPJCljgsnW6TxVyXOt9Olacwd3k2u0jfKOaUt1QfdTlpVxY4QpQ==", + "version": "1.1.1-beta.0", + "resolved": "https://registry.npmjs.org/@abi-software/map-utilities/-/map-utilities-1.1.1-beta.0.tgz", + "integrity": "sha512-W0IHpSHiNU4dqB+0tCmZ7qGv8eO/gd9eBgZeCAC2vG6I+l33dImKprWr6SpGq6/97XGmn2v/hyI68ARw8DWqAw==", "dependencies": { + "@abi-software/gallery": "^1.1.0", "@abi-software/svg-sprite": "^1.0.0", "@element-plus/icons-vue": "^2.3.1", "element-plus": "^2.7.3", @@ -4035,8 +4047,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -4095,6 +4106,34 @@ "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", @@ -4716,7 +4755,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5484,7 +5522,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -7164,7 +7201,6 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, "funding": [ { "type": "individual", @@ -9711,7 +9747,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -9720,7 +9755,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, diff --git a/package.json b/package.json index 15a30fe7..5f531a4e 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "@abi-software/flatmap-viewer": "3.1.4", - "@abi-software/map-utilities": "1.1.0", + "@abi-software/map-utilities": "^1.1.1-beta.0", "@abi-software/sparc-annotation": "0.3.1", "@abi-software/svg-sprite": "1.0.0", "@element-plus/icons-vue": "^2.3.1", diff --git a/src/App.vue b/src/App.vue index 1713a030..79812b02 100644 --- a/src/App.vue +++ b/src/App.vue @@ -77,6 +77,7 @@ :displayMinimap="true" :enableOpenMapUI="true" :flatmapAPI="flatmapAPI" + :sparcAPI="sparcAPI" :disableUI="disableUI" @open-pubmed-url="onOpenPubmedUrl" @pathway-selection-changed="onPathwaySelectionChanged" @@ -145,6 +146,7 @@ export default { if (this.consoleOn) console.log(component) let taxon = component.mapImp.describes let id = component.mapImp.addMarker('UBERON:0000948') + window.flatmapImp = component.mapImp component.enablePanZoomEvents(true) //component.showPathwaysDrawer(false); @@ -285,14 +287,16 @@ export default { useHelpModeDialog: true, multiflatmapRef: null, mapSettings: [], + sparcAPI: import.meta.env.VITE_SPARC_API, + flatmapAPI: import.meta.env.VITE_FLATMAP_API, //flatmapAPI: "https://mapcore-demo.org/current/flatmap/v2/" //flatmapAPI: "https://mapcore-demo.org/devel/flatmap/v3/" - flatmapAPI: "https://mapcore-demo.org/current/flatmap/v3/", + //flatmapAPI: "https://mapcore-demo.org/current/flatmap/v3/", //flatmapAPI: 'https://mapcore-demo.org/devel/flatmap/v4/', //flatmapAPI: 'https://mapcore-demo.org/curation/flatmap/', //flatmapAPI: "https://mapcore-demo.org/fccb/flatmap/" //flatmapAPI: "https://mapcore-demo.org/staging/flatmap/v1/" - // flatmapAPI: "https://mapcore-demo.org/devel/flatmap/v1/", + //flatmapAPI: "https://mapcore-demo.org/devel/flatmap/v1/", ElIconSetting: shallowRef(ElIconSetting) } }, diff --git a/src/components/FlatmapVuer.vue b/src/components/FlatmapVuer.vue index db2e17f0..9da157e0 100644 --- a/src/components/FlatmapVuer.vue +++ b/src/components/FlatmapVuer.vue @@ -452,6 +452,30 @@ Please use `const` to assign meaningful names to them... {{ viewingModes[viewingMode] }} + @@ -639,13 +671,15 @@ import { FlatmapQueries, findTaxonomyLabel, } from '../services/flatmapQueries.js' +import scicrunchMixin from '../services/scicrunchMixin.js' +import flatmapImageMixin from '../mixins/flatmapImageMixin.js' import yellowstar from '../icons/yellowstar' import ResizeSensor from 'css-element-queries/src/ResizeSensor' import * as flatmap from '@abi-software/flatmap-viewer' import { AnnotationService } from '@abi-software/sparc-annotation' import { mapState } from 'pinia' import { useMainStore } from '@/store/index' -import { DrawToolbar, Tooltip, TreeControls } from '@abi-software/map-utilities' +import { DrawToolbar, Tooltip, TreeControls, IframeImageDialog } from '@abi-software/map-utilities' import '@abi-software/map-utilities/dist/style.css' const centroid = (geometry) => { @@ -735,6 +769,7 @@ const createUnfilledTooltipData = function () { */ export default { name: 'FlatmapVuer', + mixins: [scicrunchMixin, flatmapImageMixin], components: { Button, Col, @@ -754,7 +789,8 @@ export default { ElIconWarningFilled, ElIconArrowDown, ElIconArrowLeft, - DrawToolbar + DrawToolbar, + IframeImageDialog }, beforeCreate: function () { //The state watcher may triggered before @@ -1669,7 +1705,12 @@ export default { } if ( data && - data.type !== 'marker' && + ( + data.type !== 'marker' || + ( + data.type === 'marker' && (this.imageType === 'Segmentations' || this.imageType === 'Images') + ) + ) && eventType === 'click' && !(this.viewingMode === 'Neuron Connection') && // Disable popup when drawing @@ -1703,6 +1744,17 @@ export default { * @arg data */ checkAndCreatePopups: async function (data) { + console.log("checkandcreate") + if (data.feature.type === 'marker') { + this.tooltipType = 'image-gallery' + console.log('marker data', data) + console.log('saved images', this.images) + let filteredImages = this.findImagesForAnatomy(this.images, data.resource[0]) + console.log('filtered images:',filteredImages) + this.galleryItems = filteredImages + this.displayTooltip(data.feature.models) + } else { + // Call flatmap database to get the connection data if (this.viewingMode === 'Annotation') { if (data.feature) { @@ -1710,6 +1762,7 @@ export default { ...data.feature, resourceId: this.serverURL, } + this.tooltipType = 'annotation' if (data.feature.featureId && data.feature.models) { this.displayTooltip(data.feature.models) } else if (data.feature.feature) { @@ -1743,9 +1796,11 @@ export default { results[1] || (data.feature.hyperlinks && data.feature.hyperlinks.length > 0) ) { + this.tooltipType = 'provenance' this.resourceForTooltip = data.resource[0] data.resourceForTooltip = this.resourceForTooltip this.createTooltipFromNeuronCuration(data) + } } } }, @@ -2001,6 +2056,23 @@ export default { } this.$emit('connectivity-info-open', this.tooltipEntry); } + + // Images on Flatmap + if (this.imageType === 'Segmentations' || this.imageType === 'Images') { + // Images in Sidebar + if (this.connectivityInfoSidebar && this.viewingMode !== 'Annotation') { + this.$emit('show-flatmap-images', this.galleryItems); + } + // Images in popup + else { + this.tooltipDisplay = true; + this.$nextTick(() => { + this.mapImp.showPopup(featureId, this.$refs.tooltip.$el, options); + this.popUpCssHacks(); + }); + } + } + // If UI is not disabled, // And connectivityInfoSidebar is not set (default) or set to `false` // Provenance popup will be shown on map @@ -2334,9 +2406,9 @@ export default { this.addResizeButtonToMinimap() this.loading = false this.computePathControlsMaximumHeight() - this.drawerOpen = !this.isCentreLine + this.drawerOpen = !this.isCentreLine this.mapResize() - this.handleMapClick(); + this.handleMapClick() /** * This is ``onFlatmapReady`` event. * @arg ``this`` (Component Vue Instance) @@ -2431,6 +2503,29 @@ export default { if (this.mapImp) return this.mapImp.search(term) return [] }, + viewIframeImage: function (url) { + this.imageIframeURL = url + this.imageIframeOpen = true + }, + closeImageIframe: function () { + this.imageIframeURL = '' + this.imageIframeOpen = false + }, + setImageType: async function (type) { + if (this.mapImp) { + this.mapImp.clearMarkers() + const anatomicalIdentifiers = this.mapImp.anatomicalIdentifiers + if (type === "Segmentations") { + let images = await this.getSegmentationsThumbnails("id", anatomicalIdentifiers) + this.images = images + this.populateFlatmapWithImages(this.mapImp, images, type) + } else if (type === "Images") { + let images = await this.getBiolucidaThumbnails("id", anatomicalIdentifiers) + this.images = images + this.populateFlatmapWithImages(this.mapImp, images, type) + } + } + }, }, props: { /** @@ -2639,6 +2734,10 @@ export default { serverURL: undefined, layers: [], pathways: [], + imageIframeOpen: false, + imageIframeURL: '', + galleryItems: [], + tooltipType: 'provenance', sckanDisplay: [ { label: 'Display Path with SCKAN', @@ -2704,6 +2803,8 @@ export default { }, drawnType: 'All tools', drawnTypes: ['All tools', 'Point', 'LineString', 'Polygon', 'None'], + imageType: 'None', + imageTypes: ['Segmentations', 'Images', 'None'], annotatedType: 'Anyone', annotatedTypes: ['Anyone', 'Me', 'Others'], openMapRef: undefined, diff --git a/src/components/MultiFlatmapVuer.vue b/src/components/MultiFlatmapVuer.vue index 5393cfda..e6ebf97d 100644 --- a/src/components/MultiFlatmapVuer.vue +++ b/src/components/MultiFlatmapVuer.vue @@ -59,6 +59,7 @@ :connectivityInfoSidebar="connectivityInfoSidebar" @connectivity-info-open="onConnectivityInfoOpen" @connectivity-info-close="onConnectivityInfoClose" + @show-flatmap-images="onShowFlatmapImages" @open-map=" /** * This event is emitted when the user chooses a different map option @@ -267,6 +268,9 @@ export default { onSelectionsDataChanged: function (data) { this.$emit('pathway-selection-changed', data); }, + onShowFlatmapImages: function (data) { + this.$emit('show-flatmap-images', data); + }, /** * @vuese * Function to show popup on map. diff --git a/src/mixins/flatmapImageMixin.js b/src/mixins/flatmapImageMixin.js new file mode 100644 index 00000000..6d15df92 --- /dev/null +++ b/src/mixins/flatmapImageMixin.js @@ -0,0 +1,135 @@ +export default { + // Note that the setting store is included in MapContent.vue + methods: { + downloadAndCreateImageThumbnailMarkerUrl: function(mapImp, key, list, type) { + const count = list.length + if (count > 0) { + //Pick a random image + const index = Math.floor(Math.random() * count) + const thumbnail = list[index].thumbnail + this.getThumbnail(thumbnail, type) + .then((wrappedElement) => { + this.createImageThumbnailMarkerUrl(mapImp, key, wrappedElement) + }) + .catch(() => { + //Failed to download, pick another one + list.splice(index) + this.downloadAndCreateImageThumbnailMarkerUrl(mapImp, key, list, type) + }) + } + }, + populateFlatmapWithImages: function (mapImp, images = [], type) { + for (const [key, list] of Object.entries(images)) { + this.downloadAndCreateImageThumbnailMarkerUrl(mapImp, key, list, type) + } + /* + images.forEach((image) => { + if (image.value && image.value.length > 0) + image.value.forEach((image) => { + if (image.anatomy && image.anatomy.length > 0) { + image.anatomy.forEach((anatomy) => { + if (!anatomyList.includes(anatomy.curie) && ids.includes(anatomy.curie)) { + console.log(anatomy.curie) + anatomyList.push(anatomy.curie) + this.createImageThumbnailMarkerUrl(mapImp, anatomy.curie, image.thumbnail) + } + }) + } + }) + }) + */ + }, + findImagesForAnatomy: function (images = [], anatomyToFind) { + if (anatomyToFind in images) { + return images[anatomyToFind] + } + return [] + /* + images.forEach((image) => { + if (image.value && image.value.length > 0) + image.value.forEach((image) => { + if (image.anatomy && image.anatomy.length > 0) { + image.anatomy.forEach((anatomy) => { + if (anatomy.curie === anatomyToFind) { + imageList.push(image) + } + }) + } + }) + }) + return imageList + */ + }, + getThumbnail: async function(url, type) { + return new Promise((resolve, reject) => { + if (type === "Segmentations" || + type === "Images") { + this.getBinaryThumbnail(url) + .then((response) => resolve(response)) + .catch((response) => reject(response)) + } else { + this.getGenericThumbnail(url) + .then((response) => resolve(response)) + .catch((response) => reject(response)) + } + }) + }, + getBinaryThumbnail: async function(url) { + return new Promise((resolve, reject) => { + fetch(url) + .then((response) => { + if (response.status >= 200 && response.status < 300) { + return response.text() + } else { + reject(); + } + }) + .then((data) => { + if (data) { + let img = new Image(); + let wrapperElement = document.createElement("div") + img.style = "height: auto;width: 50px;margin-right: 80px;" + img.onload = function() { + wrapperElement.appendChild(img); + resolve(wrapperElement); + } + img.onerror = function() { + reject(new Error("Failed to load image at " + url)); + }; + img.src= `data:'image/png';base64,${data}` + } else { + reject(new Error("Failed to load image at " + url)); + } + }) + }) + }, + getGenericThumbnail: async function(url) { + return new Promise((resolve, reject) => { + let img = new Image(); + let wrapperElement = document.createElement("div"); + img.style = "height: auto;width: 50px;margin-right: 80px;" + img.onload = function() { + wrapperElement.appendChild(img); + resolve(wrapperElement); + } + img.onerror = function() { + reject(new Error("Failed to load image at " + url)); + }; + img.src = url; + }); + }, + createImageThumbnailMarkerUrl: function (mapImp, id, wrapperElement) { + // add it to the flatmap + const markerIdentifier = mapImp.addMarker(id, { + element: wrapperElement, + className: "highlight-marker", + cluster: false, + type: "image", + }); + + const marker = mapImp.addMarker(id); + return marker + } + + } +} diff --git a/src/services/scicrunchMixin.js b/src/services/scicrunchMixin.js new file mode 100644 index 00000000..954d7355 --- /dev/null +++ b/src/services/scicrunchMixin.js @@ -0,0 +1,185 @@ +/* eslint-disable no-alert, no-console */ +const imageQuery = '"*jp2* OR *vnd.ome.xml* OR *jpx*"'; + +const getBiolucidaInfo = async (sparcAPI, datasetId) => { + return new Promise((resolve, reject) => { + const endpoint = `${sparcAPI}/image_search/${datasetId}` + fetch(endpoint) + .then((response) => { + if (response.status >= 200 && response.status < 300) { + return response.json(); + } else { + reject(); + } + }) + .then((data) => { + if (data.status == 'success') { + resolve(data); + } else { + reject(); + } + }) + }) +} + +const getFilesInfo = async (api, key, idsList, types) => { + let params = new URLSearchParams(); + types.forEach((type) => { + params.append('arrayparams', type); + params.append('arrayparams', type); + }); + let response = await fetch(`${api}/get-organ-curies/?${params}`); + let data = await response.json(); + const identifiers = []; + data.uberon.array.forEach(pair => { + const identifier = { + id: pair.id.toUpperCase(), + name: pair.name + }; + if (idsList.includes(identifier[key])) { + identifiers.push(identifier); + } + }); + const keys = identifiers.map((item) => item[key]); + response = await fetch(`${api}/get-files-info-for-curies`, { + method: "POST", + body: JSON.stringify( + { + filetypes: types, + curies: keys, + } + ), + headers: { + "Content-Type": "application/json", + }, + }); + data = await response.json(); + return data; +} + +export default { + // Note that the setting store is included in MapContent.vue + methods: { + getThumbnailURL: function(thumbnailId) { + return `${this.sparcAPI}/thumbnail/${thumbnailId}` + }, + getSegmentationThumbnailURL: function(datasetId, datasetVersion, filePath) { + return `${this.sparcAPI}/thumbnail/neurolucida?datasetId=${datasetId}&version=${datasetVersion}&path=files/${filePath}`; + }, + getBiolucidaThumbnails: async function (key, idsList) { + try { + const data = await getFilesInfo(this.sparcAPI, key, idsList, ["biolucida-2d", "biolucida-3d"]); + if (data['files_info']) { + const images = {}; + for (const [key, value] of Object.entries(data['files_info'])) { + if (value.length > 0) { + const list = []; + value.forEach((entry) => { + if (entry.biolucida_id) { + let image = { + thumbnail: this.getThumbnailURL(entry.biolucida_id), + datasetId: entry.id, + } + list.push(image); + } + }); + images[key] = list; + } + } + return images; + } + } catch (error) { + console.error('Error:', error); + } + return {}; + }, + //Get representative segmentations thumbnails + // key - can either be + // id - use the uberon id as key or + // name - anatomical name as key + // idsList - Only id / name from the server matching the one in this list + // will be used + getSegmentationsThumbnails: async function (key, idsList) { + try { + const data = await getFilesInfo(this.sparcAPI, key, idsList, ["mbf-segmentation"]); + if (data['files_info']) { + const images = {}; + for (const [key, value] of Object.entries(data['files_info'])) { + if (value.length > 0) { + const list = []; + value.forEach((entry) => { + let image = { + thumbnail: this.getSegmentationThumbnailURL(entry.id, + entry.version, entry.file_path), + datasetId: entry.id, + } + list.push(image); + }); + images[key] = list; + } + } + return images; + } + } catch (error) { + console.error('Error:', error); + } + return {}; + }, + getImagesFromScicrunch: async function () { + try { + const response = await fetch(`${this.sparcAPI}/multiple_dataset_info/using_multiple_mimetype/?${new URLSearchParams({q: imageQuery})}`); + const data = await response.json(); + + console.log('number of hits:', data.numberOfHits); + if (data.numberOfHits >= 1) { + let images = await this.processResults(data.results); + return {success: true, images: images}; + } + return {success: false}; + } catch (error) { + console.error('Error:', error); + return {success: false}; + } + }, + + processResults: async function (results) { + try { + let images = []; + const dataType = 'segmentation'; + console.log('starting promise list') + let promiseList = results.map(async (result, i) => { + const datasetId = result.dataset_identifier; + const datasetVersion = result.dataset_version; + const s3uri = result.s3uri; + const biolucidaInfo = await getBiolucidaInfo(this.sparcAPI, datasetId); + return biolucidaInfo.dataset_images.map(bioImage => { + return { + resource: { + share_link: bioImage.share_link, + }, + title: 'Image', + anatomy: result.organs, + species: result.organisms, + datasetId: datasetId, + datasetVersion: datasetVersion, + link: bioImage.share_link, + s3uri: s3uri, + type: dataType, + thumbnail: bioImage.thumbnail_url, + }; + }); + }); + console.log('promiseList:', promiseList); + let biolucidaInfos = await Promise.allSettled(promiseList); + images = biolucidaInfos.flat(); + console.log('biolucidaInfos:', biolucidaInfos); + console.log('finished!') + return images; + } catch (error) { + console.error('Error:', error); + return []; + } + } + } +} + \ No newline at end of file