diff --git a/apps/mini/dataloaders.js b/apps/mini/dataloaders.js new file mode 100644 index 000000000..de65438f5 --- /dev/null +++ b/apps/mini/dataloaders.js @@ -0,0 +1,111 @@ + + +function FormTempaltesLoader() { + function loadingFormTemplates() { + $CAMIC.store.findTemplate() + // + .then(function(temps) { + // get templates data + $D.templates = temps; + }) + // + .catch(function(error) { + // templates schema + + console.error(error); + }) + // + .finally(function() { + if ($D.templates) { + // load UI + } else { + // set message + $UI.message.addError('Loading Templates is Error'); + } + }); + } + + var checkCoreIsReady = setInterval(function() { + if ($CAMIC) { + clearInterval(checkCoreIsReady); + // load data + loadingFormTemplates(); + } + }, 500); +} + +function layersLoader() { + // human + function loadingHumanOverlayers() { + $CAMIC.store.findMarkTypes($D.params.slideId, 'human').then(function(layers) { + // convert part not nesscary + $D.humanlayers = [...layers.map(covertToHumanLayer)]; + + // add data and create ui item + addHumanLayerItems(); + }).catch(function(error) { + // overlayers schema + $UI.message.addError('Loading Human Layers is Error'); + console.error(error); + }); + } + // ruler + function loadingRulerOverlayers() { + $CAMIC.store.findMarkTypes($D.params.slideId, 'ruler').then(function(layers) { + // convert part not nesscary + $D.rulerlayers = [...layers.map(covertToRulerLayer)]; + + // add data and create ui item + addRulerLayerItems(); + }).catch(function(error) { + // overlayers schema + $UI.message.addError('Loading Ruler Layers is Error'); + console.error(error); + }); + } + // heatmap + function loadingHeatmapOverlayers() { + $CAMIC.store.findHeatmapType($D.params.slideId).then(function(layers) { + $D.heatmaplayers = []; + // convert and load heatmap layer + for (let i = 0; i < layers.length; i++) { + const item = layers[i].provenance.analysis; + $D.heatmaplayers.push({id: item.execution_id, + name: item.execution_id, + typeId: 'heatmap', + typeName: 'heatmap', + }); + } + // add data and create ui item + addHeatmapLayerItems(); + }).catch(function(error) { + // overlayers schema + $UI.message.addError('Loading heatmap Overlayers is Error'); + console.error(error); + }); + } + + // segmentation + function loadingComputerOverlayers() { + $CAMIC.store.findMarkTypes($D.params.slideId, 'computer').then(function(layers) { + // convert part not nesscary + $D.computerlayers=[...layers.map(covertToCumputerLayer)]; + // add data and create ui item + addComputerLayerItems(); + }).catch(function(error) { + $UI.message.addError('Loading Computer Layers is Error'); + console.error(error); + }); + } + var checkCoreIsReady = setInterval(function() { + if ($UI.layersViewer && $UI.layersViewerMinor) { + clearInterval(checkCoreIsReady); + loadingHumanOverlayers(); + loadingRulerOverlayers(); + loadingHeatmapOverlayers(); + loadingComputerOverlayers(); + } + }, 500); +} + + diff --git a/apps/mini/init.js b/apps/mini/init.js new file mode 100644 index 000000000..da010fe3e --- /dev/null +++ b/apps/mini/init.js @@ -0,0 +1,1090 @@ +// CAMIC is an instance of camicroscope core +// $CAMIC in there +let $CAMIC = null; +let tracker; +let $minorCAMIC = null; +// for all instances of UI components +const $UI = new Map(); + +const $D = { + pages: { + home: '../table.html', + table: '../table.html', + }, + params: null, // parameter from url - slide Id and status in it (object). + overlayers: null, // array for each layers + templates: null, // json schema for prue-form + segments: [], +}; + +window.addEventListener('keydown', (e) => { + if (!$CAMIC || !$CAMIC.viewer) return; + const key = e.key; + // escape key to close all operations + if ('escape' == key.toLocaleLowerCase()) { + magnifierOff(); + measurementOff(); + annotationOff(); + presetLabelOff(); + } + + // open annotation (ctrl + a) + if (e.ctrlKey && 'a' == key.toLocaleLowerCase() && $CAMIC.viewer.canvasDrawInstance) { + const li = $UI.toolbar.getSubTool('annotation'); + eventFire(li, 'click'); + return; + } + // open magnifier (ctrl + m) + if (e.ctrlKey && 'm' == key.toLocaleLowerCase() && $UI.spyglass) { + const li = $UI.toolbar.getSubTool('magnifier'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'change'); + return; + } + // open measurement (ctrl + r) + if (e.ctrlKey && 'r' == key.toLocaleLowerCase() && $CAMIC.viewer.measureInstance) { + e.preventDefault(); + const li = $UI.toolbar.getSubTool('measurement'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'change'); + return; + } + // open side-by-side (ctrl + s) + if (e.ctrlKey && 's' == key.toLocaleLowerCase()) { + e.preventDefault(); + const li = $UI.toolbar.getSubTool('sbsviewer'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'click'); + return; + } + // open side-by-side (ctrl + l) + if (e.ctrlKey && 'l' == key.toLocaleLowerCase()) { + e.preventDefault(); + const li = $UI.toolbar.getSubTool('preset_label'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'click'); + return; + } + + // shortcuts for preset labels + if ($D.labels && + $D.labels.configuration && + Array.isArray($D.labels.configuration) && + $D.labels.configuration.length > 0 && + e.ctrlKey) { + e.key; + const elt = $UI.labelsViewer.allLabels.find((l)=>l.dataset.key&&l.dataset.key.toLowerCase()==e.key.toLowerCase()); + if (elt) { + $UI.toolbar + .getSubTool('preset_label') + .querySelector('input[type=checkbox]').checked = true; + $UI.labelsViewer.selectLabel(elt); + } + } +}); + +// initialize viewer page +function initialize() { + var checkPackageIsReady = setInterval(function() { + if (IsPackageLoading) { + clearInterval(checkPackageIsReady); + // create a viewer and set up + initCore(); + + // loading the form template data + FormTempaltesLoader(); + + // loading the overlayers data + layersLoader(); + } + }, 100); +} + +// setting core functionalities +function initCore() { + // start initial + + // create the message queue + $UI.message = new MessageQueue({position: 'bottom-left'}); + + // zoom info and mmp + const opt = { + draw: { + // extend context menu btn group + btns: [ + { + // annotation + type: 'btn', + title: 'Annotation', + class: 'material-icons', + text: 'description', + callback: saveAnnotation, + }, + { + // analytics + type: 'btn', + title: 'Analytics', + class: 'material-icons', + text: 'settings_backup_restore', + callback: saveAnalytics, + }, + ], + }, + }; + // set states if exist + if ($D.params.states) { + opt.states = $D.params.states; + } + // pathdb home directly + if ($D.params.mode == 'pathdb') { + $D.pages.home = '../../../'; + $D.pages.table = '../../../'; + } + + try { + const slideQuery = {}; + slideQuery.id = $D.params.slideId; + slideQuery.name = $D.params.slide; + slideQuery.location = $D.params.location; + opt.addRulerCallback = onAddRuler; + opt.deleteRulerCallback = onDeleteRuler; + $CAMIC = new CaMic('main_viewer', slideQuery, opt); + } catch (error) { + Loading.close(); + $UI.message.addError('Core Initialization Failed'); + console.error(error); + return; + } + + $CAMIC.loadImg(function(e) { + // image loaded + if (e.hasError) { + // if this is a retry, assume normal behavior (one retry per slide) + if ($D.params.retry) { + $UI.message.addError(e.message); + // can't reach Slide and return to home page + if (e.isServiceError) redirect($D.pages.table, e.message, 1); + } else { + // If this is our first attempt, try one more time. + let params = new URLSearchParams(window.location.search); + params.set('retry', '1'); + window.location.search = params.toString(); + } + } else { + $D.params.data = e; + // popup panel + $CAMIC.viewer.addHandler('canvas-lay-click', function(e) { + if (!e.data) { + $UI.annotPopup.close(); + return; + } + // for support QUIP 2.0 + const data = Array.isArray(e.data) ? e.data[e.data.selected] : e.data; + + const type = data.provenance.analysis.source; + let body; + let attributes; + let warning = null; + switch (type) { + case 'human': + let area; + let circumference; + if (data.geometries) { + if ( + (data.selected != null || data.selected != undefined) && + data.geometries.features[data.selected] && + data.geometries.features[data.selected].properties.area + ) { + area = `${Math.round( + data.geometries.features[data.selected].properties.area, + )} μm²`; + } + if ( + (data.selected != null || data.selected != undefined) && + data.geometries.features[data.selected] && + data.geometries.features[data.selected].properties.circumference + ) { + circumference = `${Math.round( + data.geometries.features[data.selected].properties + .circumference, + )} μm`; + } + } // othereise, don't try to calculate area and circumference + // human + + attributes = data.properties.annotations; + if (area) attributes.area = area; + if (circumference) attributes.circumference = circumference; + body = convertHumanAnnotationToPopupBody(attributes); + if ( + data.geometries && + data.geometries.features[data.selected].properties.isIntersect + ) { + warning = `
Inaccurate Area and Circumference
`; + } + if ( + data.geometries && + data.geometries.features[data.selected].properties.nommp + ) { + warning = `
This slide has no mpp
`; + } + + $UI.annotPopup.showFooter(); + break; + case 'computer': + // handle data.provenance.analysis.computation = `segmentation` + attributes = data.properties.scalar_features[0].nv; + body = {type: 'map', data: attributes}; + $UI.annotPopup.hideFooter(); + break; + default: + return; + // statements_def + break; + } + + $UI.annotPopup.data = { + id: data.provenance.analysis.execution_id, + oid: data._id.$oid, + annotation: attributes, + selected: e.data.selected, + }; + const getCateName = () => { + const items = $UI.layersViewer.setting.categoricalData.human.items; + var dataType = null; + for (const key in items) { + if ({}.hasOwnProperty.call(items, key)) { + dataType = key; + if (items.hasOwnProperty(key)&& + Array.isArray(items[key].items)&& + items[key].items.some((i)=>i.item.id == $UI.annotPopup.data.id)) break; + } + } + return dataType; + }; + + + $UI.annotPopup.dataType = null; + $UI.annotPopup.dataType = data.provenance && data.provenance.analysis && + data.provenance.analysis.source && data.provenance.analysis.source=='human'? + getCateName($UI.annotPopup.data.id):null; + + $UI.annotPopup.setTitle(`id:${data.provenance.analysis.execution_id}`); + $UI.annotPopup.setBody(body); + if (warning) $UI.annotPopup.body.innerHTML += warning; + + $UI.annotPopup.open(e.position); + }); + + // create the message bar TODO for reading slide Info TODO + $UI.slideInfos = new CaMessage({ + /* opts that need to think of*/ + id: 'cames', + defaultText: `Slide: ${$D.params.data.name}`, + }); + + // spyglass + $UI.spyglass = new Spyglass({ + targetViewer: $CAMIC.viewer, + imgsrc: $D.params.data.url, + }); + } + }); + + $CAMIC.viewer.addHandler('open', function() { + $CAMIC.viewer.canvasDrawInstance.addHandler('start-drawing', startDrawing); + $CAMIC.viewer.canvasDrawInstance.addHandler('stop-drawing', stopDrawing); + // init UI -- some of them need to wait data loader to load data + // because UI components need data to initialize + initUIcomponents(); + // action tracker start + tracker = new Tracker($CAMIC, $D.params.data._id.$oid, getUserId()); + tracker.start(); + }); +} + +// initialize all UI components +async function initUIcomponents() { + /* create UI components */ + $UI.downloadSelectionModal = new ModalBox({ + id: 'downloadSelectionModal', + hasHeader: true, + headerText: 'Download Selection', + hasFooter: true, + }); + const header = $UI.downloadSelectionModal.elt.querySelector('.modalbox-header'); + header.querySelector('span.close').style.display = 'None'; + const footer = $UI.downloadSelectionModal.elt.querySelector('.modalbox-footer'); + const cancelBtn = document.createElement('button'); + cancelBtn.classList.add('btn'); + cancelBtn.classList.add('btn-sm'); + cancelBtn.classList.add('btn-secondary'); + cancelBtn.textContent = 'Cancel'; + const downloadBtn = document.createElement('button'); + downloadBtn.classList.add('btn'); + downloadBtn.classList.add('btn-sm'); + downloadBtn.classList.add('btn-info'); + downloadBtn.textContent = 'Download'; + footer.innerHTML = ''; + footer.classList.add('footer'); + footer.appendChild(cancelBtn); + footer.appendChild(downloadBtn); + + cancelBtn.addEventListener('click', hideDownloadSelection); + downloadBtn.addEventListener('click', downloadSelection); + + + $UI.modalbox = new ModalBox({ + id: 'modalbox', + hasHeader: true, + headerText: 'HeatMap List', + hasFooter: false, + }); + + const subToolsOpt = []; + // home + if (ImgloaderMode == 'iip') { + subToolsOpt.push({ + name: 'home', + icon: 'home', // material icons' name + title: 'Home', + type: 'btn', // btn/check/dropdown + value: 'home', + callback: goHome, + }); + } + // pen + subToolsOpt.push({ + name: 'annotation', + icon: 'create', // material icons' name + title: 'Draw', + type: 'multistates', + callback: draw, + }); + + subToolsOpt.push({ + name: 'preset_label', + icon: 'colorize', // material icons' name + title: 'Preset Labels', + type: 'check', + value: 'prelabels', + callback: drawLabel, + }); + + // magnifier + subToolsOpt.push({ + name: 'magnifier', + icon: 'search', + title: 'Magnifier', + type: 'dropdown', + value: 'magn', + dropdownList: [ + { + value: 0.5, + title: '0.5', + checked: true, + }, + { + value: 1, + title: '1.0', + }, + { + value: 2, + title: '2.0', + }, + ], + callback: toggleMagnifier, + }); + // measurement tool + if ($CAMIC.viewer.measureInstance) { + subToolsOpt.push({ + name: 'measurement', + icon: 'straighten', + title: 'Measurement', + type: 'dropdown', + value: 'measure', + dropdownList: [ + { + value: 'straight', + title: 'straight', + icon: 'straighten', + checked: true, + }, + { + value: 'coordinate', + title: 'coordinate', + icon: 'square_foot', + }, + ], + callback: toggleMeasurement, + }); + } + // donwload selection + subToolsOpt.push({ + name: 'download_selection', + icon: 'get_app', // material icons' name + title: 'Download Selection', + type: 'check', + value: 'download', + callback: toggleDownloadSelection, + }); + // enhance + subToolsOpt.push({ + name: 'Enhance', + icon: 'invert_colors', + title: 'Enhance', + type: 'dropdown', + value: 'Enhance', + dropdownList: [ + { + value: 'Histogram Eq', + title: 'Histogram Equalization', + icon: 'leaderboard', + checked: true, + }, + { + value: 'Edge', + title: 'Edge', + icon: 'show_chart', + }, + { + value: 'Sharpen', + title: 'Sharpen', + icon: 'change_history', + }, + { + value: 'Custom', + title: 'Custom', + icon: 'api', + }, + ], + callback: enhance, + }); + // share + if (ImgloaderMode == 'iip') { + subToolsOpt.push({ + name: 'share', + icon: 'share', + title: 'Share View', + type: 'btn', + value: 'share', + callback: shareURL, + }); + } + // side-by-side + subToolsOpt.push({ + name: 'sbsviewer', + icon: 'view_carousel', + title: 'Side By Side Viewer', + value: 'dbviewers', + type: 'check', + callback: toggleViewerMode, + }); + // heatmap + subToolsOpt.push({ + name: 'heatmap', + icon: 'satellite', + title: 'Heat Map', + value: 'heatmap', + type: 'btn', + callback: openHeatmap, + }); + subToolsOpt.push({ + name: 'labeling', + icon: 'label', + title: 'Labeling', + value: 'labeling', + type: 'btn', + callback: function() { + window.location.href = `../labeling/labeling.html${window.location.search}`; + }, + }); + subToolsOpt.push({ + name: 'segment', + icon: 'timeline', + type: 'btn', + value: 'rect', + title: 'Segment', + callback: function() { + if (window.location.search.length > 0) { + window.location.href = + '../segment/segment.html' + window.location.search; + } else { + window.location.href = '../segment/segment.html'; + } + }, + }); + subToolsOpt.push({ + name: 'model', + icon: 'aspect_ratio', + type: 'btn', + value: 'rect', + title: 'Predict', + callback: function() { + if (window.location.search.length > 0) { + window.location.href = '../model/model.html' + window.location.search; + } else { + window.location.href = '../model/model.html'; + } + }, + }); + + // -- For Nano borb Start -- // + if (ImgloaderMode == 'imgbox') { + // download + subToolsOpt.push({ + name: 'downloadmarks', + icon: 'cloud_download', + title: 'Download Marks', + type: 'btn', + value: 'download', + callback: Store.prototype.DownloadMarksToFile, + }); + subToolsOpt.push({ + name: 'uploadmarks', + icon: 'cloud_upload', + title: 'Load Marks', + type: 'btn', + value: 'upload', + callback: Store.prototype.LoadMarksFromFile, + }); + } + // -- For Nano borb End -- // + + // -- view btn START -- // + if (!($D.params.data.hasOwnProperty('review') && $D.params.data['review']=='true')) { + subToolsOpt.push({ + name: 'review', + icon: 'playlist_add_check', + title: 'has reviewed', + type: 'btn', + value: 'review', + callback: updateSlideView, + }); + } + // screenshot + subToolsOpt.push({ + name: 'slideCapture', + icon: 'camera_enhance', + title: 'Slide Capture', + type: 'btn', + value: 'slCap', + callback: captureSlide, + }); + subToolsOpt.push({ + name: 'tutorial', + icon: 'help', + title: 'Tutorial', + value: 'tutorial', + type: 'btn', + callback: function() { + tour.init(); + tour.start(true); + }, + }); + + // Additional Links handler + function additionalLinksHandler(url, openInNewTab, appendSlide) { + if (appendSlide === true) { + url = url + '?slide=' + $D.params.slideId; + url = url + '&state=' + StatesHelper.encodeStates(StatesHelper.getCurrentStates()); + } + if (openInNewTab === true) { + window.open(url, '_blank').focus(); + } else { + window.location.href = url; + } + } + var additionalLinksConfig = await $CAMIC.store.getConfigByName('additional_links') + .then((list)=>list.length==0?null:list[0]); + if (additionalLinksConfig&&additionalLinksConfig.configuration&&Array.isArray(additionalLinksConfig.configuration)) { + additionalLinksConfig.configuration.forEach((link)=>{ + var openInNewTab = link.openInNewTab === false ? false : true; + var appendSlide = link.appendSlide === true ? true : false; + var url = link.url; + subToolsOpt.push({ + name: link.displayName, + icon: link.icon ? link.icon : 'link', + title: link.displayName, + value: link.displayName, + type: 'btn', + callback: function() { + additionalLinksHandler(url, openInNewTab, appendSlide); + }, + }); + }); + } + + // create the tool bar + $UI.toolbar = new CaToolbar({ + /* opts that need to think of*/ + id: 'ca_tools', + zIndex: 601, + mainToolsCallback: mainMenuChange, + subTools: subToolsOpt, + }); + + // create two side menus for tools + $UI.appsSideMenu = new SideMenu({ + id: 'side_apps', + width: 300, + // , isOpen:true + callback: toggleSideMenu, + }); + + $UI.layersSideMenu = new SideMenu({ + id: 'side_layers', + width: 250, + contentPadding: 5, + // , isOpen:true + callback: toggleSideMenu, + }); + + const loading = `
loading layers...
`; + $UI.layersSideMenu.addContent(loading); + // TODO add layer viewer + + + /* annotation popup */ + $UI.annotPopup = new PopupPanel({ + footer: [ + // { // edit + // title:'Edit', + // class:'material-icons', + // text:'notes', + // callback:annoEdit + // }, + { + // delete + title: 'Delete', + class: 'material-icons', + text: 'delete_forever', + callback: annoDelete, + }, + ], + }); + + // TODO -- labels // + $UI.labelsSideMenu = new SideMenu({ + id: 'labels_layers', + width: 180, + contentPadding: 5, + }); + var labelsTitle = document.createElement('div'); + labelsTitle.classList.add('item_head'); + labelsTitle.textContent = 'Label Manager'; + + $UI.labelsSideMenu.addContent(labelsTitle); + + $D.labels = await $CAMIC.store.getConfigByName('preset_label').then((list)=>list.length==0?null:list[0]); + + + // onAdd() + // onRemove(labels) + // onUpdate(labels) + // onSelected() + $UI.labelsViewer = new LabelsViewer({ + id: 'labelmanager', + data: $D.labels?$D.labels.configuration:[], + onAdd: addPresetLabelsHandler, + onEdit: editPresetLabelsHandler, + onRemove: removePresetLabelsHandler, + onSelected: selectedPresetLabelsHandler, + }, + ); + $UI.labelsViewer.elt.parentNode.removeChild($UI.labelsViewer.elt); + $UI.labelsSideMenu.addContent($UI.labelsViewer.elt); + + // == end -- // + + var checkOverlaysDataReady = setInterval(function() { + if ( + $D.params.data && + $CAMIC && + $CAMIC.viewer && + $CAMIC.viewer.omanager + ) { + clearInterval(checkOverlaysDataReady); + // for segmentation + $CAMIC.viewer.createSegment({ + store: $CAMIC.store, + slide: $D.params.data.slide, + data: [], + }); + + // create control + + // TODO move to add layers + // create main layer viewer items with states + // const mainViewerData = $D.overlayers.map((d) => { + // const isShow = + // $D.params.states && + // $D.params.states.l && + // $D.params.states.l.includes(d.id) ? + // true : + // false; + // return {item: d, isShow: isShow}; + // }); + + + // TODO move to add layers + // create monir layer viewer items + // const minorViewerData = $D.overlayers.map((d) => { + // return {item: d, isShow: false}; + // }); + + // create UI and set data + $UI.layersViewer = createLayerViewer( + 'overlayers', + null, + callback.bind('main'), + rootCallback.bind('main'), + ); + // create UI and set data - minor + $UI.layersViewerMinor = createLayerViewer( + 'overlayersMinor', + null, + callback.bind('minor'), + rootCallback.bind('minor'), + ); + + // TODO move to add layers + // if ($D.params.states && $D.params.states.l) { + // $D.params.states.l.forEach((id) => + // loadAnnotationById($CAMIC, $UI.layersViewer.getDataItemById(id), null), + // ); + // } + + $UI.layersList = new CollapsibleList({ + id: 'layerslist', + list: [ + { + id: 'left', + title: 'Left Viewer', + content: 'No Template Loaded', // $UI.annotOptPanel.elt + // isExpand:true + }, + { + id: 'right', + title: 'Right Viewer', + content: 'No Template Loaded', // $UI.algOptPanel.elt, + }, + ], + changeCallBack: function(e) { + // console.log(e); + }, + }); + $UI.layersSideMenu.clearContent(); + // add to layers side menu + const title = document.createElement('div'); + title.classList.add('item_head'); + title.textContent = 'Layers Manager'; + + $UI.layersSideMenu.addContent(title); + + // loading status + $UI.loadStatus = document.createElement('div'); + $UI.loadStatus.style.display = 'none'; + $UI.loadStatus.classList.add('load-status'); + $UI.loadStatus.innerHTML = `
cached
Loading
`; + $UI.layersSideMenu.addContent($UI.loadStatus); + + // zoom locker control + $UI.lockerPanel = document.createElement('div'); + $UI.lockerPanel.classList.add('lock_panel'); + $UI.lockerPanel.style.display = 'none'; + $UI.lockerPanel.innerHTML = ``; + $UI.lockerPanel + .querySelector('input[type=checkbox]') + .addEventListener('change', (e) => { + isLock = !isLock; + if (isLock) { + $minorCAMIC.viewer.viewport.zoomTo( + $CAMIC.viewer.viewport.getZoom(true), + $CAMIC.viewer.viewport.getCenter(true), + true, + ); + $CAMIC.viewer.controls.bottomright.style.display = 'none'; + } else { + $CAMIC.viewer.controls.bottomright.style.display = ''; + } + }); + + $UI.layersSideMenu.addContent($UI.lockerPanel); + + $UI.layersList.clearContent('left'); + $UI.layersList.addContent('left', $UI.layersViewer.elt); + $UI.layersList.clearContent('right'); + $UI.layersList.addContent('right', $UI.layersViewerMinor.elt); + + $UI.layersList.elt.parentNode.removeChild($UI.layersList.elt); + closeMinorControlPanel(); + $UI.layersSideMenu.addContent($UI.layersList.elt); + } + }, 300); + + var checkTemplateSchemasDataReady = setInterval(function() { + if ($D.templates) { + clearInterval(checkTemplateSchemasDataReady); + const annotRegex = new RegExp('annotation', 'gi'); + const annotSchemas = $D.templates.filter((item) => + item.id.match(annotRegex), + ); + /* annotation control */ + $UI.annotOptPanel = new OperationPanel({ + // id: + // element: + formSchemas: annotSchemas, + resetCallback: resetCallback, + action: { + title: 'Save', + callback: annoCallback, + }, + }); + + const title = document.createElement('div'); + title.classList.add('item_head'); + title.textContent = 'Annotation'; + $UI.appsSideMenu.addContent(title); + $UI.annotOptPanel.elt.classList.add('item_body'); + $UI.appsSideMenu.addContent($UI.annotOptPanel.elt); + } + }, 300); +} +function createLayerViewer(id, viewerData, callback, rootCallback) { + const layersViewer = new LayersViewer({ + id: id, + data: viewerData, + removeCallback: removeCallback, + locationCallback: locationCallback, + callback: callback, + rootCallback: rootCallback, + + }); + layersViewer.elt.parentNode.removeChild(layersViewer.elt); + return layersViewer; +} + +// create lay panel for side-by-side control +function createLayPanelControl() { + $UI.layCtrlbar = document.createElement('div'); + $UI.layCtrlbar.style = ` + display:none; + margin: .2rem; + background-color: #365f9c; + cursor: default; + padding: .5rem; + width: 100%; + border: none; + text-align: left; + outline: none; + font-size: 1.2rem;`; + + createRadios(); + $UI.layersSideMenu.addContent($UI.layCtrlbar); + // control + const radios = $UI.layCtrlbar.querySelectorAll('input[name=ctrlPane]'); + radios.forEach((r) => { + r.addEventListener('change', function(e) { + const val = e.target.value; + switch (val) { + case 'main': + $UI.layersViewer.elt.style.display = 'flex'; + $UI.layersViewerMinor.elt.style.display = 'none'; + break; + case 'minor': + $UI.layersViewer.elt.style.display = 'none'; + $UI.layersViewerMinor.elt.style.display = 'flex'; + break; + default: + // statements_def + break; + } + }); + }); +} + +function createRadios() { + const temp = ` + + + + + `; + $UI.layCtrlbar.innerHTML = temp; +} + +function redirect(url, text = '', sec = 5) { + let timer = sec; + if (!timer) { + window.location.href = url; + } + setInterval(function() { + if (!timer) { + window.location.href = url; + } + if (Loading.instance && Loading.instance.parentNode) { + Loading.text.textContent = `${text} ${timer}s.`; + } else { + Loading.open(document.body, `${text} ${timer}s.`); + } + // Hint Message for clients that page is going to redirect to Flex table in 5s + timer--; + }, 1000); +} + +function updateSlideView() { + if (!confirm(`Do you want to mark this slide as reviewed?`)) return; + Loading.open(document.body, 'changing review status ...'); + $CAMIC.store.updateSlideReview($D.params.slideId, 'true').then(function(e) { + if (e.status==200) { + $UI.toolbar.getSubTool('review').style.display = 'none'; + } + }).finally(function() { + Loading.close(); + }); +} + + +function addHumanLayerItems() { + const mainViewerItems = { + 'other': { + item: { + id: 'other', + name: 'other', + }, + items: [], + }, + }; + + $D.humanlayers.reduce((items, d)=> { + const isShow = + $D.params.states && + $D.params.states.l && + $D.params.states.l.includes(d.id) ? + true: + false; + if (d.label&&d.label.id&&d.label.name) { // preset label + const {id, name} = d.label; + if (!items[id]) { + items[id] = { + item: { + id: id, + name: name, + }, + items: [], + }; + } + items[id].items.push({item: d, isShow}); + } else { // other + items['other'].items.push({item: d, isShow}); + } + return items; + }, mainViewerItems); + + $UI.layersViewer.addHumanItems(mainViewerItems); + + // minor viewer minorViewer + const minorViewerItems = { + 'other': { + item: { + id: 'other', + name: 'other', + }, + items: [], + }, + }; + $D.humanlayers.reduce((items, d)=> { + const isShow = + $D.params.states && + $D.params.states.l && + $D.params.states.l.includes(d.id) ? + true: + false; + if (d.label&&d.label.id&&d.label.name) { + + } + if (d.label&&d.label.id&&d.label.name) { // preset label + const {id, name} = d.label; + if (!items[id]) { + items[id] = { + item: { + id: id, + name: name, + }, + items: [], + }; + } + items[id].items.push({item: d, isShow}); + } else { // other + items['other'].items.push({item: d, isShow}); + } + return items; + }, minorViewerItems); + + $UI.layersViewerMinor.addHumanItems(minorViewerItems); +} +function openLoadStatus(text) { + const txt = $UI.loadStatus.querySelector('.text'); + txt.textContent = `Loading ${text}`; + $UI.loadStatus.style.display = null; +} +function closeLoadStatus() { + $UI.loadStatus.style.display = 'none'; +} +function addRulerLayerItems(data) { + const mainViewerData = $D.rulerlayers.map((d) => { + return {item: d, isShow: false}; + }); + // create monir layer viewer items + const minorViewerData = $D.rulerlayers.map((d) => { + return {item: d, isShow: false}; + }); + $UI.layersViewer.addItems(mainViewerData, 'ruler'); + $UI.layersViewerMinor.addItems(minorViewerData, 'ruler'); +} + +function addComputerLayerItems(data) { + const mainViewerData = $D.computerlayers.map((d) => { + return {item: d, isShow: false}; + }); + // create monir layer viewer items + const minorViewerData = $D.computerlayers.map((d) => { + return {item: d, isShow: false}; + }); + $UI.layersViewer.addItems(mainViewerData, 'segmentation'); + $UI.layersViewerMinor.addItems(minorViewerData, 'segmentation'); +} + +function addHeatmapLayerItems(data) { + const mainViewerData = $D.heatmaplayers.map((d) => { + return {item: d, isShow: false}; + }); + // create monir layer viewer items + const minorViewerData = $D.heatmaplayers.map((d) => { + return {item: d, isShow: false}; + }); + $UI.layersViewer.addItems(mainViewerData, 'heatmap'); + $UI.layersViewerMinor.addItems(minorViewerData, 'heatmap'); +} + +// const mainViewerData = $D.overlayers.map((d) => { +// const isShow = +// $D.params.states && +// $D.params.states.l && +// $D.params.states.l.includes(d.id) ? +// true : +// false; +// return {item: d, isShow: isShow}; +// }); + + +// TODO move to add layers +// create monir layer viewer items +// const minorViewerData = $D.overlayers.map((d) => { +// return {item: d, isShow: false}; +// }); diff --git a/apps/mini/tut.js b/apps/mini/tut.js new file mode 100644 index 000000000..8e135ad9c --- /dev/null +++ b/apps/mini/tut.js @@ -0,0 +1,147 @@ +var tour = new Tour({ + name: 'Tour', + steps: [{ + element: 'label[title=\'Applications\']', + title: 'Annotations', + content: 'Opens the Annotation panel,'+ + ' where you can select which annotation set to view,'+ + ' name that annotation set, add optional notes about'+ + ' the annotation set, save the annotation set, and reset the panel to its original state.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Layers\']', + title: 'Layer Manager', + content: 'Opens the Layers Manager panel,'+ + ' where you can select which layers to view.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Home\']', + title: 'Home', + content: 'Return to the data table so that'+ + ' you can open another slide.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Draw\']', + title: 'Draw', + content: 'Draw thin lines, thick lines, or polygons on the image.'+ + ' Annotations can also be computer aided using the Smart-pen tool. Draw them, stretch them, remove them.'+ + ' To maintain the integrity of measurements, avoid drawing shapes that overlap or intersect one another.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Magnifier\']', + title: 'Magnifier', + content: 'The Magnifier works like a magnifying glass and allows'+ + ' you to see the slide at normal magnification (1.0), low magnification (0.5),'+ + ' or high magnification (2.0). Click a magnification level and place the'+ + ' bounding box on the area of the slide you want to magnify.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Measurement\']', + title: 'Measurement', + content: 'Drag this tool on the slide to learn the measurement in micrometers.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Share View\']', + title: 'Share View', + content: 'Opens a window with a URL to the current presentation state of the'+ + ' slide including the magnification level, layers that are currently open, and your position on the image.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Side By Side Viewer\']', + title: 'Side By Side Viewer', + content: 'Shows the Layer Manager panel, the left and right'+ + ' layers, and inset window. For the right and left layer, select which layer you want to view.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Heat Map\']', + title: 'Heat Map', + content: 'For a slide with heatmap data, opens the choices of heatmaps available,'+ + ' as well as ways of displaying the heatmaps.'+ + ' The gradient shows all of the values on the selected spectrum for the field you selected.'+ + ' Contains a heatmap edit pen function.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Labeling\']', + title: 'Labeling', + content: 'Use this tool to draw a circle or rectangle around a tumor region,'+ + ' measure an area on the slide, and download labels.'+ + ' The Labeling tool has its own toolbar with tools in the following order from left to right:'+ + ' return to the previous slide, place a square on the slide, place a circle on the slide,'+ + ' measure an area, and download labels.'+ + ' Click the left arrow at the far right of the toolbar to hide it,'+ + ' then click the right arrow to show it.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Segment\']', + title: 'Segment', + content: 'This tool allows you to display, count, and export nuclear segmentations on the image.'+ + ' Clicking this tool opens the following custom toolbar.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Predict\']', + title: 'Model', + content: 'Show results from a pre-trained tensorflow compatible model on a ROI of the slide.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Download Slide\']', + title: 'Download Slide', + content: + 'Download the slide image to your system', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Slide Capture\']', + title: 'Slide Capture', + content: 'Take Screenshot of the slide.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Tutorial\']', + title: 'Tutorial', + content: 'Click here to start viewer Tour.', + placement: 'auto bottom', + smartPlacement: true, + }], + template: '
'+ + '

'+ + '

'+ + '
'+ + '
|'+ + '
'+ + '
'+ + '
', + backdrop: true, + // resets tour always to resume always from the beginning + onEnd: function reset() { + window.localStorage.setItem('Tour_current_step', 0); + }, +}); +tour.init(); diff --git a/apps/mini/uicallbacks.js b/apps/mini/uicallbacks.js new file mode 100644 index 000000000..aea890805 --- /dev/null +++ b/apps/mini/uicallbacks.js @@ -0,0 +1,2670 @@ +/* +UI and Core callback and hook +all functions help the UI and Core part together that makes workflows. +*/ + +// UI callback functions list + +/** + * Toggles side-by-side viewer + * @param {Object} opt + */ +function toggleViewerMode(opt) { + if (opt.checked) { + // turn off preset label + toolsOff(); + + // turn off magnifier + // magnifierOff(); + // turn off measurement + // measurementOff(); + + // open layers menu + $UI.toolbar._mainTools[1].querySelector( + 'input[type=checkbox]', + ).checked = true; + $UI.layersSideMenu.open(); + + // close apps menu + $UI.toolbar._mainTools[0].querySelector( + 'input[type=checkbox]', + ).checked = false; + $UI.appsSideMenu.close(); + + openMinorControlPanel(); + openSecondaryViewer(); + } else { + $UI.appsSideMenu.close(); + $UI.layersSideMenu.close(); + closeMinorControlPanel(); + closeSecondaryViewer(); + } +} + +/** + * Finds the slide for minor-viewer in side-by-side viewer + * @param {Object} size object with height and width + */ +function multSelectorAction(size) { + // hidden main viewer's bottom right control and get navigator + $CAMIC.viewer.controls.bottomright.style.display = 'none'; + $UI.lockerPanel.style.display = ''; + $UI.lockerPanel.querySelector('input[type=checkbox]').checked = true; + isLock = true; + // open new instance camic + try { + const slideQuery = {}; + slideQuery.id = $D.params.slideId; + slideQuery.name = $D.params.slide; + slideQuery.location = $D.params.location; + $minorCAMIC = new CaMic('minor_viewer', slideQuery, { + // osd options + // mouseNavEnabled:false, + // panVertical:false, + // panHorizontal:false, + // showNavigator:false, + // customized options + // hasZoomControl:false, + hasDrawLayer: false, + addRulerCallback: onAddRuler, + deleteRulerCallback: onDeleteRuler, + // hasLayerManager:false, + // hasScalebar:false, + // states options + navigatorHeight: size.height, + navigatorWidth: size.width, + states: { + x: $CAMIC.viewer.viewport.getCenter().x, + y: + $CAMIC.viewer.viewport.getCenter().y * + $CAMIC.viewer.imagingHelper.imgAspectRatio, + z: $CAMIC.viewer.viewport.getZoom(), + }, + }); + + // synchornic zoom and move + // coordinated Viewer - zoom + $CAMIC.viewer.addHandler('zoom', synchornicView1, {type: 'zoom'}); + + // coordinated Viewer - pan + $CAMIC.viewer.addHandler('pan', synchornicView1, {type: 'pan'}); + + // loading image + $minorCAMIC.loadImg(function(e) { + // image loaded + if (e.hasError) { + $UI.message.addError(e.message); + } + }); + $minorCAMIC.viewer.addOnceHandler('tile-drawing', function() { + $minorCAMIC.viewer.addHandler('zoom', synchornicView2, {type: 'zoom'}); + $minorCAMIC.viewer.addHandler('pan', synchornicView2, {type: 'pan'}); + // create segment display + $minorCAMIC.viewer.createSegment({ + store: $minorCAMIC.store, + slide: $D.params.data.name, + data: [], + }); + }); + } catch (error) { + Loading.close(); + console.error(error); + $UI.message.addError('Core Initialization Failed'); + return; + } +} + +var active1 = false; +var active2 = false; +var isLock = true; +function synchornicView1(data) { + if (active2) return; + active1 = true; + switch (data.userData.type) { + case 'zoom': + if (isLock) { + $minorCAMIC.viewer.viewport.zoomTo(data.zoom, data.refPoint); + } else { + $minorCAMIC.viewer.viewport.panTo( + $CAMIC.viewer.viewport.getCenter(true), + ); + } + break; + case 'pan': + $minorCAMIC.viewer.viewport.panTo(data.center); + break; + default: + $minorCAMIC.viewer.viewport.zoomTo($CAMIC.viewer.viewport.getZoom()); + $minorCAMIC.viewer.viewport.panTo($CAMIC.viewer.viewport.getCenter()); + break; + } + active1 = false; +} + +function synchornicView2(data) { + if (active1) return; + active2 = true; + switch (data.userData.type) { + case 'zoom': + if (isLock) { + $CAMIC.viewer.viewport.zoomTo(data.zoom, data.refPoint); + } else { + $CAMIC.viewer.viewport.panTo( + $minorCAMIC.viewer.viewport.getCenter(true), + ); + } + break; + case 'pan': + $CAMIC.viewer.viewport.panTo(data.center); + break; + default: + $CAMIC.viewer.viewport.zoomTo($minorCAMIC.viewer.viewport.getZoom()); + $CAMIC.viewer.viewport.panTo($minorCAMIC.viewer.viewport.getCenter()); + break; + } + + active2 = false; +} + +/** + * Opens the side-by-side secondary viewer + */ +function openSecondaryViewer() { + // ui changed + const main = document.getElementById('main_viewer'); + const minor = document.getElementById('minor_viewer'); + main.classList.remove('main'); + main.classList.add('left'); + + minor.classList.remove('none'); + // minor.classList.add('display'); + minor.classList.add('right'); + + const navSize = { + height: $CAMIC.viewer.controls.bottomright.querySelector('.navigator').style + .height, + width: $CAMIC.viewer.controls.bottomright.querySelector('.navigator').style + .width, + }; + setTimeout(function() { + multSelectorAction(navSize); + }, 100); +} + +/** + * Closes the side-by-side secondary viewer + */ +function closeSecondaryViewer() { + // ui changed + const main = document.getElementById('main_viewer'); + const minor = document.getElementById('minor_viewer'); + main.classList.add('main'); + main.classList.remove('left'); + minor.classList.add('none'); + minor.classList.remove('right'); + $CAMIC.viewer.controls.bottomright.style.display = ''; + $UI.lockerPanel.style.display = 'none'; + + const li = $UI.toolbar.getSubTool('sbsviewer'); + li.querySelector('input[type="checkbox"]').checked = false; + Loading.close(); + + // destory + if ($minorCAMIC) { + // remove handler + $CAMIC.viewer.removeHandler('zoom', synchornicView1); + $CAMIC.viewer.removeHandler('pan', synchornicView1); + + // destroy object + $minorCAMIC.destroy(); + $minorCAMIC = null; + } + + // hide on layersViewer TODO + $UI.layersViewerMinor.toggleAllItems(); + $UI.layersViewerMinor.setting.data.forEach((d) => delete d.layer); +} + +/** + * side menu toggle callback, called from layerManager and annotation side menu + * @param {Object} opt + */ +function toggleSideMenu(opt) { + if (!opt.isOpen) { + const id = opt.target.id.split('_')[1]; + $UI.toolbar.changeMainToolStatus(id, false); + } +} + +/** + * side menu toggle callback, called from layerManager and annotation side menu + * @param {Object} opt + */ + +/** + * Home callback for redirecting to table page + * @param {Object} data + */ +function goHome(data) { + redirect($D.pages.home, `GO Home Page`, 0); +} + +// --- Annotation Tool ---// +// pen draw callback +const label = document.createElement('div'); +label.style.transformOrigin = 'center'; +label.style.height = 0; +label.style.width = 0; + +function draw(e) { + if (!$CAMIC.viewer.canvasDrawInstance) { + alert('Draw Doesn\'t Initialize'); + return; + } + const state = +e.state; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + const target = this.srcElement || this.target || this.eventSource.canvas; + if (state) { + // on + + // off magnifier + // magnifierOff(); + // off measurement + // measurementOff(); + + // turn off annotaiton + if (state==1) { + // show pop-up message to user + $UI.message.add('infoFor More Options, Right Click Anywhere On The Image', 4000); + } + if ($CAMIC.status == 'normal') { + annotationOn.call(this, state, target); + return; + } + toolsOff(); + var checkAllToolsOff = setInterval( + function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + annotationOn.call(this, state, target); + } + }.bind(this), + 100, + ); + } else { + // off + annotationOff(); + } +} + +/** + * utility function for switching off given tool + */ +function toolsOff() { + switch ($CAMIC.status) { + case 'magnifier': + magnifierOff(); + break; + case 'measure': + measurementOff(); + break; + case 'normal': + annotationOff(); + break; + case 'label': + presetLabelOff(); + break; + case 'download_selection': + downloadSelectionOff(); + break; + } +} + +function annotationOn(state, target) { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + const li = $UI.toolbar.getSubTool('annotation'); + const {style, model} = $CAMIC.drawContextmenu.getStyle(); + + canvasDraw.drawMode = model; + canvasDraw.style.color = style.color; + + li.appendChild(label); + switch (state) { + case 1: + spen.menu(65, 0.2); + spen.undo.onclick=()=>canvasDraw.__align_undo(); + // open menu + $UI.annotOptPanel._action_.style.display = ''; + label.style.transform = 'translateY(-12px) translateX(18px)'; + label.textContent = '1'; + label.style.color = ''; + break; + case 2: + $UI.annotOptPanel._action_.style.display = ''; + label.style.transform = + ' rotate(-90deg) translateX(2px) translateY(13px)'; + label.textContent = '8'; + label.style.color = 'white'; + break; + default: + // statements_def + break; + } + + canvasDraw.drawOn(); + $CAMIC.drawContextmenu.on(); + $CAMIC.drawContextmenu.open({ + x: this.clientX, + y: this.clientY, + target: target, + }); + $CAMIC.status = 'normal'; + // close layers menu + $UI.layersSideMenu.close(); + // open annotation menu + $UI.appsSideMenu.open(); + // -- START QUIP550 -- // + // $UI.appsList.triggerContent('annotation','open'); + // -- END QUIP550 -- // + const input = $UI.annotOptPanel._form_.querySelector('#name'); + input.focus(); + input.select(); +} + +function annotationOff() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + + if ( + canvasDraw._draws_data_.length && + confirm(`Do You Want To Save Annotation Before You Leave?`) + ) { + saveAnnotation(); + } else { + spen.close(); + // close menu + canvasDraw.clear(); + canvasDraw.drawOff(); + $CAMIC.drawContextmenu.off(); + $UI.appsSideMenu.close(); + toggleOffDrawBtns(); + $CAMIC.status = null; + } +} + +function toggleOffDrawBtns() { + const li = $UI.toolbar.getSubTool('annotation'); + const lab = li.querySelector('label'); + const state = +lab.dataset.state; + lab.classList.remove(`s${state}`); + lab.dataset.state = 0; + lab.classList.add(`s0`); + if (label.parentNode) li.removeChild(label); +} + + +function brushOn(d) { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const bctrl = document.getElementById('bctrl'); + bctrl.style.display = 'block'; + + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + canvasDraw.drawMode = d.mode; + canvasDraw.size = [parseInt(d.size), parseInt(d.size)]; + canvasDraw.style.color = d.color; + canvasDraw.brushType = d.type; + canvasDraw.drawOn(); + $CAMIC.status = 'brush'; + + $UI.toolbar.getSubTool('brush').querySelector('label').style.color = d.color; + // //close layers menu + // $UI.layersSideMenu.close(); +} + + +// --- Measurement Tool ---// +/** + * Callback measurement tool to toggle measurement tool + * @param {Object} data + */ +function toggleMeasurement(data) { + if (!$CAMIC.viewer.measureInstance) { + console.warn('No Measurement Tool'); + return; + } + // $UI.message.add(`Measument Tool ${data.checked?'ON':'OFF'}`); + if (data.checked) { + // trun off the main menu + $UI.layersSideMenu.close(); + if ($CAMIC.status == 'measure') { + $CAMIC.viewer.measureInstance.mode = data.status; + measurementOn(); + return; + } + // turn off annotation + toolsOff(); + var checkAllToolsOff = setInterval(function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + $CAMIC.viewer.measureInstance.mode = data.status; + measurementOn(); + } + }, 100); + // turn off magnifier + // magnifierOff(); + } else { + measurementOff(); + } +} + +/** + * switches download Selection tool on, called from toggleMeasurement + */ +function downloadSelectionOn() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + $CAMIC.viewer.canvasDrawInstance.drawOn(); + getDrawOption(); + $CAMIC.viewer.canvasDrawInstance.drawMode = 'rect'; + $CAMIC.viewer.canvasDrawInstance.style.color = '#000'; + const li = $UI.toolbar.getSubTool('download_selection'); + li.querySelector('input[type=checkbox]').checked = true; + $CAMIC.status = 'download_selection'; +} + +/** + * switches downloadSelection tool off, called from toggleMeasurement + */ +function downloadSelectionOff() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + $CAMIC.viewer.canvasDrawInstance.drawOff(); + $CAMIC.viewer.canvasDrawInstance.clear(); + setDrawOption(); + const li = $UI.toolbar.getSubTool('download_selection'); + li.querySelector('input[type=checkbox]').checked = false; + $CAMIC.status = null; +} + +/** + * switches measuring tool on, called from toggleMeasurement + */ +function measurementOn() { + if (!$CAMIC.viewer.measureInstance) return; + $CAMIC.viewer.measureInstance.on(); + const li = $UI.toolbar.getSubTool('measurement'); + li.querySelector('input[type=checkbox]').checked = true; + const value = li.querySelector('.drop_down input[type=radio]:checked').value; + if (value=='straight') { + li.querySelector('label').textContent = 'straighten'; + } else if (value=='coordinate') { + li.querySelector('label').textContent = 'square_foot'; + } + $CAMIC.status = 'measure'; +} + +/** + * switches measuring tool off, called from toggleMeasurement + */ +function measurementOff() { + if (!$CAMIC.viewer.measureInstance) return; + $CAMIC.viewer.measureInstance.off(); + const li = $UI.toolbar.getSubTool('measurement'); + li.querySelector('input[type=checkbox]').checked = false; + $CAMIC.status = null; +} + +// --- Magnifier tool ---// +/** + * Callback magnifier tool to toggle measurement tool + * @param {Object} data + */ +function toggleMagnifier(data) { + if (data.checked) { + if ($CAMIC.status == 'magnifier') { + // all tool has turn off + clearInterval(checkAllToolsOff); + magnifierOn(+data.status, this.clientX, this.clientY); + // turn off the main menu + $UI.layersSideMenu.close(); + $UI.appsSideMenu.close(); + return; + } + // annotation off + toolsOff(); + var checkAllToolsOff = setInterval( + function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + magnifierOn(+data.status, this.clientX, this.clientY); + // trun off the main menu + $UI.layersSideMenu.close(); + $UI.appsSideMenu.close(); + } + }.bind(this), + 100, + ); + // measurement off + // measurementOff(); + } else { + magnifierOff(); + } +} + +/** + * switches magnifier tool on, called from toggleMagnifier + */ +function magnifierOn(factor = 1, x = 0, y = 0) { + if (!$UI.spyglass) return; + $UI.spyglass.factor = factor; + $UI.spyglass.open(x, y); + const li = $UI.toolbar.getSubTool('magnifier'); + li.querySelector('input[type=checkbox]').checked = true; + $CAMIC.status = 'magnifier'; +} + +/** + * switches magnifier tool off, called from toggleMagnifier + */ +function magnifierOff() { + if (!$UI.spyglass) return; + $UI.spyglass.close(); + const li = $UI.toolbar.getSubTool('magnifier'); + li.querySelector('input[type=checkbox]').checked = false; + $CAMIC.status = null; +} + +// image download +function imageDownload() { + var downloadURL = '../../loader/getSlide/'; + var store = new Store('../../data/'); + var id=$D.params.slideId; + var fileName = ''; + store + .getSlide(id) + .then((response) => { + if (response[0]) { + if (response[0]['filepath']) { + return response[0]['filepath']; + } + let location = response[0]['location']; + return location.substring( + location.lastIndexOf('/') + 1, + location.length, + ); + } else { + throw new Error('Slide not found'); + } + }) + .then((fileName) => { + fetch(downloadURL + fileName, { + credentials: 'same-origin', + method: 'GET', + }) + .then((response) => { + if (response.status == 404) { + throw response; + } else { + return response.blob(); + } + }) + .then((blob) => { + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + a.remove(); // afterwards we remove the element again + window.URL.revokeObjectURL(blob); + }) + .catch((error) => { + console.log(error); + alert('Error! Can\'t download file.'); + }); + }) + .catch((error) => { + console.log(error); + }); +} + +/** + * share url callback + * @param {Object} data + */ +function shareURL(data) { + const URL = StatesHelper.getCurrentStatesURL(true); + window.prompt('Share this link', URL); +} + +/** + * main menu changed callback, toggles Layer Manager side app + * @param {Object} data + */ +function mainMenuChange(data) { + if (data.apps) { + $UI.appsSideMenu.open(); + } else { + $UI.appsSideMenu.close(); + } + + if (data.layers) { + $UI.layersSideMenu.open(); + } else { + $UI.layersSideMenu.close(); + } + + if (data.labels) { + $UI.labelsSideMenu.open(); + } else { + presetLabelOff(); + } +} + +function convertHumanAnnotationToPopupBody(notes) { + const rs = {type: 'map', data: []}; + for (let field in notes) { + if (notes.hasOwnProperty(field)) { + const val = notes[field]; + field = field + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + rs.data.push({key: field, value: val}); + } + } + return rs; +} + +/** + * Deletes the annotation with given data + * called from removeCallback + * @param {Object} data + */ +function annoDelete(data, parentType) { + if (!data.id) return; + + + const annotationData = $D.humanlayers.find( + (d) => d.data && d.data._id.$oid == data.oid, + ); + let message; + if (annotationData.data.geometries) { + message = `Are You Sure You Want To Delete This Annotation {ID:${data.id}} With ${annotationData.data.geometries.features.length} Mark(s)?`; + } else { + message = `Are You Sure You Want To Delete This Markup {ID:${data.id}}?`; + } + $UI.annotPopup.close(); + if (!confirm(message)) return; + $CAMIC.store + .deleteMark(data.oid, $D.params.data.slide) + .then((datas) => { + // server error + if (datas.error) { + const errorMessage = `${datas.text}: ${datas.url}`; + $UI.message.addError(errorMessage, 4000); + // close + return; + } + + // no data found + if (!datas.deletedCount || datas.deletedCount < 1) { + $UI.message.addWarning(`Delete Annotations Failed.`, 4000); + return; + } + + const index = $D.humanlayers.findIndex((layer) => layer.id == data.id); + + if (index == -1) return; + + data.index = index; + const layer = $D.humanlayers[data.index]; + // update UI + if (Array.isArray(layer.data)) deleteCallbackOld(data, parentType); + else deleteCallback(data, parentType); + }) + .catch((e) => { + $UI.message.addError(e); + console.error(e); + }) + .finally(() => { + console.log('delete end'); + }); +} + +/** + * Callback for deleting annotation + * @param {Object} data + */ +function deleteCallback(data, parentType) { + // remove overlay + $D.humanlayers.splice(data.index, 1); + // update layer manager + $UI.layersViewer.removeItemById(data.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(data.id, 'human', parentType); + + $CAMIC.viewer.omanager.removeOverlay(data.id); + if ($minorCAMIC && $minorCAMIC.viewer) { + $minorCAMIC.viewer.omanager.removeOverlay(data.id); + } + // update layers Viewer + // $UI.layersViewer.update(); + // close popup panel + $UI.annotPopup.close(); +} + +// for support QUIP2.0 Data model - delete callback +function deleteCallbackOld(data, parentType) { + const layer = $D.humanlayers[data.index]; + // for support QUIP2.0 + const idx = layer.data.findIndex((d) => d._id.$oid === data.oid); + if (idx == -1) return; + layer.data.splice(idx, 1); + + // update layer manager + $UI.layersViewer.removeItemById(data.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(data.id, 'human', parentType); + + // delete entire layer if there is no data. + if (layer.data.length == 0) { + $D.humanlayers.splice(data.index, 1); + $CAMIC.viewer.omanager.removeOverlay(data.id); + if ($minorCAMIC && $minorCAMIC.viewer) { + $minorCAMIC.viewer.omanager.removeOverlay(data.id); + } + } + + $CAMIC.viewer.omanager.updateView(); + // update layers Viewer + // $UI.layersViewer.update(); + // close popup panel + $UI.annotPopup.close(); +} + +function sortChange(sort) { + $CAMIC.layersManager.sort(sort); +} + +function resetCallback(data) { + if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) return; + if (confirm(`Do You Want To Clear Markups?`)) { + $CAMIC.viewer.canvasDrawInstance.clear(); + } +} + +function convertToNormalized(points, size, viewer) { + const height = Math.round(viewer.imagingHelper.imgHeight); + const width = Math.round(viewer.imagingHelper.imgWidth); + // convert + const normalizedPoints = points.map((p) => [p[0] / width, p[1] / height]); + const normalizedSize = [size[0] / width, size[0] / height]; + + return { + points: normalizedPoints, + bound: getBounds(normalizedPoints), + size: normalizedSize, + }; +} + +function convertGeometries(features, data) { + const {points, bound, size} = convertToNormalized( + features, + data.size, + $CAMIC.viewer, + ); + + return { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + style: { + color: data.color, + lineJoin: 'round', + lineCap: 'round', + isFill: true, + }, + size: size, + }, + geometry: { + type: 'LineString', + coordinates: [points], + }, + bound: { + type: 'Polygon', + coordinates: [bound], + }, + }, + ], + }; +} + +/** + * Callback for saving annotations + * called from saveAnnotation + * @param {Object} data + */ +function annoCallback(data) { + spen.close(); + // is form ok? + const noteData = $UI.annotOptPanel._form_.value; + if ($UI.annotOptPanel._action_.disabled || noteData.name == '') { + // close layer silde + $UI.toolbar._mainTools[1].querySelector('[type=checkbox]').checked = false; + $UI.layersSideMenu.close(); + + // open app silde + $UI.toolbar._mainTools[0].querySelector('[type=checkbox]').checked = true; + $UI.appsSideMenu.open(); + + // open annotation list + // -- START QUIP550 -- // + // $UI.appsList.triggerContent('annotation','open'); + // -- END QUIP550 -- // + return; + } + // has Path? + + if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) { + // toast + $UI.message.addWarning('info No Markup On Annotation. Try Holding And Dragging.'); + return; + } + + // Add new lines to notes to prevent overflow + + let str = noteData.notes || ''; + var resultString = ''; + while (typeof str==='string' && str.length > 0) { + resultString += str.substring(0, 36) + '\n'; + str = str.substring(36); + } + noteData.notes = resultString; + + // save + // provenance + Loading.open($UI.annotOptPanel.elt, 'Saving Annotation...'); + const execId = randomId(); + + const annotJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'human', + execution_id: execId, + name: noteData.name, + }, + }, + properties: { + annotations: noteData, + }, + geometries: ImageFeaturesToVieweportFeatures( + $CAMIC.viewer, + $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection(), + ), + }; + + // save annotation + $CAMIC.store + .addMark(annotJson) + .then((data) => { + // server error + if (data.error) { + $UI.message.addError(`${data.text}:${data.url}`); + Loading.close(); + return; + } + + // no data added + if (data.count < 1) { + Loading.close(); + $UI.message.addWarning(`Annotation Save Failed`); + return; + } + // create layer data + const newItem = { + id: execId, + name: noteData.name, + typeId: 'human', + typeName: 'human', + creator: getUserId(), + shape: annotJson.geometries.features[0].geometry.type, + data: null, + }; + $D.humanlayers.push(newItem); + $UI.layersViewer.addHumanItem(newItem, 'human', 'other'); + $UI.layersViewerMinor.addHumanItem( + newItem, + 'human', + 'other', + $minorCAMIC && $minorCAMIC.viewer ? true : false, + ); + + // data for UI + // return; + loadAnnotationById( + $CAMIC, + $UI.layersViewer.getDataItemById(execId, 'human', 'other'), + 'other', + saveAnnotCallback, + ); + if ($minorCAMIC && $minorCAMIC.viewer) { + loadAnnotationById( + $minorCAMIC, + $UI.layersViewerMinor.getDataItemById(execId, 'human', 'other'), + 'other', + null, + ); + } + }) + .catch((e) => { + Loading.close(); + console.log('save failed', e); + }) + .finally(() => {}); +} + +function saveAnnotCallback() { + /* reset as default */ + // clear draw data and UI + $CAMIC.viewer.canvasDrawInstance.drawOff(); + $CAMIC.drawContextmenu.off(); + toggleOffDrawBtns(); + $CAMIC.viewer.canvasDrawInstance.clear(); + // uncheck pen draw icon and checkbox + // $UI.toolbar._subTools[1].querySelector('[type=checkbox]').checked = false; + // clear form + $UI.annotOptPanel.clear(); + + // close app side + $UI.toolbar._mainTools[0].querySelector('[type=checkbox]').checked = false; + $UI.appsSideMenu.close(); + // -- START QUIP550 -- // + // $UI.appsList.triggerContent('annotation','close'); + // -- END QUIP550 -- // + // open layer side + $UI.toolbar._mainTools[1].querySelector('[type=checkbox]').checked = true; + $UI.layersSideMenu.open(); + // $UI.layersViewer.update(); + $CAMIC.status = null; +} +function algoCallback(data) { + console.log(data); +} + +/** + * overlayer manager callback function for show or hide + * @param {Object} data + */ +const heatmapDefaultColor = '#1034a6'; + +const drawOptionRecord = {}; +function getDrawOption() { + drawOptionRecord.drawMode = $CAMIC.viewer.canvasDrawInstance.drawMode; + drawOptionRecord.color = $CAMIC.viewer.canvasDrawInstance.style.color; +} +function setDrawOption() { + $CAMIC.viewer.canvasDrawInstance.drawMode = drawOptionRecord.drawMode || 'free'; + $CAMIC.viewer.canvasDrawInstance.style.color = drawOptionRecord.color || '#7cfc00'; +} + +function toggleDownloadSelection(data) { + // const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + + // if(e.checked) { + // downloadSelectionOn(); + // } else { + // downloadSelectionOff(); + // } + if (!$CAMIC.viewer.canvasDrawInstance) { + console.warn('No Draw Tool'); + return; + } + if (data.checked) { + // trun off the main menu + $UI.layersSideMenu.close(); + if ($CAMIC.status == 'download_selection') { + downloadSelectionOn(); + return; + } + // turn off download SelectionOn + toolsOff(); + var checkAllToolsOff = setInterval(function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + downloadSelectionOn(); + } + }, 100); + // turn off magnifier + // magnifierOff(); + } else { + downloadSelectionOff(); + } +} + + +async function callback(data) { + const viewerName = this.toString(); + let camic = null; + switch (viewerName) { + case 'main': + camic = $CAMIC; + break; + case 'minor': + camic = $minorCAMIC; + break; + default: + break; + } + + // return; + data.forEach(function(d) { + const item = d.item; + if (item.typeName == 'ruler') { + if (!item.data) { + loadRulerById(camic, d, null); + } else { + if (!d.layer) { + const [xmin, ymin] = item.data.geometries.features[0].geometry.coordinates[0][0]; + const [xmax, ymax] = item.data.geometries.features[0].geometry.coordinates[0][2]; + // create lay and update view + d.layer = camic.viewer.measureInstance.addRuler({ + id: item.id, + mode: item.data.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: item.data.properties.innerHTML, + isShow: d.isShow, + }); + } + if (d.isShow) d.layer.element.style.display =''; + else d.layer.element.style.display ='none'; + } + + return; + } + + if (item.typeName == 'segmentation') { + if (d.isShow) { + // add + camic.viewer.segment.addSegmentId(item.id); + } else { + // remove + camic.viewer.segment.removeSegmentId(item.id); + } + return; + } + if (item.typeName == 'heatmap') { + if ( + $D.heatMapData && + $D.heatMapData.provenance.analysis.execution_id == item.id && + camic.viewer.heatmap + ) { + // show or hide heatmap + if (d.isShow) { + camic.viewer.heatmap.on(); + } else { + camic.viewer.heatmap.off(); + } + } else if ( + $D.heatMapData && + $D.heatMapData.provenance.analysis.execution_id == item.id + ) { + const opt = { + opacity: 0.65, // inputs[2].value, + coverOpacity: 0.001, + data: $D.heatMapData.data, + // editedData:$D.editedDataClusters, + mode: 'binal', + size: $D.heatMapData.provenance.analysis.size, + fields: $D.heatMapData.provenance.analysis.fields, + color: heatmapDefaultColor, // inputs[3].value + }; + + if ($D.heatMapData.provenance.analysis.setting) { + opt.mode = $D.heatMapData.provenance.analysis.setting.mode; + if ($D.heatMapData.provenance.analysis.setting.field) { + opt.currentFieldName = + $D.heatMapData.provenance.analysis.setting.field; + } + } + camic.viewer.createHeatmap(opt); + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='binal') { + camic.viewer.heatmap.setColor($D.heatMapData.provenance.analysis.setting.colors[0]); + } + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='gradient') { + camic.viewer.heatmap.setColors($D.heatMapData.provenance.analysis.setting.colors); + } + } else { + Loading.open(document.body, 'Loading Heatmap Data...'); + // load heatmap + camic.store + .getHeatmap($D.params.slideId, item.id) + .then(function(data) { + if (Array.isArray(data) && data.length > 0) { + $D.heatMapData = data[0]; + const opt = { + opacity: 0.65, // inputs[2].value, + coverOpacity: 0.001, + data: $D.heatMapData.data, + mode: 'binal', + // editedData:$D.editedDataClusters, + size: $D.heatMapData.provenance.analysis.size, + fields: $D.heatMapData.provenance.analysis.fields, + color: heatmapDefaultColor, // inputs[3].value + }; + + if ($D.heatMapData.provenance.analysis.setting) { + opt.mode = $D.heatMapData.provenance.analysis.setting.mode; + if ($D.heatMapData.provenance.analysis.setting.field) { + opt.currentFieldName = + $D.heatMapData.provenance.analysis.setting.field; + } + } + camic.viewer.createHeatmap(opt); + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='binal') { + camic.viewer.heatmap.setColor($D.heatMapData.provenance.analysis.setting.colors[0]); + } + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='gradient') { + camic.viewer.heatmap.setColors($D.heatMapData.provenance.analysis.setting.colors); + } + } + }) + .catch(function(error) { + // heatmap schema + console.error(error); + }) + .finally(function() { + Loading.close(); + if (!$D.heatMapData) { + $UI.message.addError('Loading Heatmap Data Is Error'); + } + }); + } + + // rest other check box + const cates = + viewerName == 'main' ? + $UI.layersViewer.setting.categoricalData : + $UI.layersViewerMinor.setting.categoricalData; + if (d.isShow) { + for (const key in cates) { + if (cates.hasOwnProperty(key)) { + cate = cates[key]; + if (cate.item.name == 'heatmap') { + cate.items.forEach((i) => { + if (d !== i && i.isShow) { + i.elt.querySelector('input[type=checkbox]').checked = false; + i.isShow = false; + } + }); + } + } + } + } + return; + } + + if (!item.data) { + // load annotation layer data + loadAnnotationById(camic, d, null); + } else { + if (!d.layer) d.layer = camic.viewer.omanager.addOverlay(item); + // remove popup if segment is hidden + if ($UI.annotPopup.data && !d.isShow && $UI.annotPopup.data.id===d.item.id) $UI.annotPopup.close(); + d.layer.isShow = d.isShow; + camic.viewer.omanager.updateView(); + } + }); +} +function minorCallback(data) { + console.log(data); +} + +function openMinorControlPanel() { + $UI.layersList.displayContent('left', true, 'head'); + $UI.layersList.triggerContent('left', 'close'); + $UI.layersList.displayContent('right', true); + $UI.layersList.triggerContent('right', 'open'); +} + +function closeMinorControlPanel() { + $UI.layersList.displayContent('left', false, 'head'); + $UI.layersList.triggerContent('left', 'open'); + $UI.layersList.displayContent('right', false); + $UI.layersList.triggerContent('right', 'close'); +} + +function loadRulerById(camic, rulerData, callback) { + const {item, elt} = rulerData; + $CAMIC.store + .getMarkByIds([item.id], $D.params.slideId, null, item.typeId) + .then((data) => { + // response error + if (data.error) { + const errorMessage = `${data.text}: ${data.url}`; + console.error(errorMessage); + $UI.message.addError(errorMessage, 4000); + // delete item form layview + removeElement($D.rulerlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'ruler'); + $UI.layersViewerMinor.removeItemById(item.id, 'ruler'); + return; + } + + // no data found + // if(data.length < 1){ + if (!data[0]) { + console.warn(`Ruler: ${item.name}(${item.id}) doesn't exist.`); + $UI.message.addWarning( + `Ruler: ${item.name}(${item.id}) doesn't exist.`, + 4000, + ); + // delete item form layview + removeElement($D.rulerlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'ruler'); + $UI.layersViewerMinor.removeItemById(item.id, 'ruler'); + return; + } + + item.data = data[0]; + if (data[0].provenance&& + data[0].provenance.analysis&& + data[0].provenance.analysis.coordinate&& + data[0].provenance.analysis.coordinate == 'image') { + data[0].geometries = ImageFeaturesToVieweportFeatures( + camic.viewer, + data[0].geometries, + ); + } + + item.data.properties.innerHTML = item.data.properties.innerHTML.split('<').join('<'); + item.data.properties.innerHTML = item.data.properties.innerHTML.split('>').join('>'); + item.data.properties.innerHTML = item.data.properties.innerHTML.split(' ').join(' '); + let [xmin, ymin] = item.data.geometries.features[0].geometry.coordinates[0][0]; + let [xmax, ymax] = item.data.geometries.features[0].geometry.coordinates[0][2]; + + // create lay and update view + rulerData.layer = camic.viewer.measureInstance.addRuler({ + id: item.id, + mode: item.data.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: item.data.properties.innerHTML, + isShow: rulerData.isShow, + }); + }) + .catch((e) => { + console.error(e); + }) + .finally(() => { + Loading.close(); + }); +} +function loadAnnotationById(camic, layerData, parentType, callback) { + layerData.item.loading = true; + const item = layerData.item; + + Loading.open(document.body, 'Loading Layers...'); + + $CAMIC.store + .getMarkByIds([item.id], $D.params.slideId, null, item.typeId) + .then((data) => { + delete item.loading; + + // response error + if (data.error) { + const errorMessage = `${data.text}: ${data.url}`; + console.error(errorMessage); + $UI.message.addError(errorMessage, 4000); + // delete item form layview + removeElement($D.humanlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(item.id, 'human', parentType); + return; + } + + // no data found + // if(data.length < 1){ + if (!data[0]) { + console.warn(`Annotation: ${item.name}(${item.id}) doesn't exist.`); + $UI.message.addWarning( + `Annotation: ${item.name}(${item.id}) doesn't exist.`, + 4000, + ); + // delete item form layview + removeElement($D.humanlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(item.id, 'human', parentType); + return; + } + // for support quip 2.0 data model + if (data[0].geometry) { + // twist them + var image = camic.viewer.world.getItemAt(0); + var imgWidth = image.source.dimensions.x; + var imgHeight = image.source.dimensions.y; + item.data = data.map((d) => { + d.geometry.coordinates[0] = d.geometry.coordinates[0].map((point) => { + return [ + Math.round(point[0] * imgWidth), + Math.round(point[1] * imgHeight), + ]; + }); + d.properties = {}; + d.properties.style = { + color: '#000080', + lineCap: 'round', + lineJoin: 'round', + isFill: false, + }; + return { + _id: d._id, + provenance: d.provenance, + properties: d.properties, + geometry: d.geometry, + }; + }); + // if(item) data[0].isShow = item.isShow; + item.render = oldAnnoRender; + item.clickable = false; + item.hoverable = false; + } else { + if (data[0].provenance && + data[0].provenance.analysis&& + (data[0].provenance.analysis.coordinate == undefined|| + data[0].provenance.analysis.coordinate == 'normalized')) { + data[0].geometries = VieweportFeaturesToImageFeatures( + camic.viewer, + data[0].geometries, + ); + } + + if (data[0].provenance.analysis.isGrid) { + const width = $CAMIC.viewer.imagingHelper.imgWidth; + const height = $CAMIC.viewer.imagingHelper.imgHeight; + + const feature = data[0].geometries.features[0]; + const size = feature.properties.size; + // const points = feature.geometry.coordinates[0]; + // const bounds = feature.bound.coordinates[0]; + feature.properties.size = [ + Math.round(size[0] * width), + Math.round(size[1] * height), + ]; + // feature.geometry.coordinates[0] = points.map(p => [ + // Math.round(p[0] * width), + // Math.round(p[1] * height) + // ]); + // feature.bound.coordinates[0] = bounds.map(p => [ + // Math.round(p[0] * width), + // Math.round(p[1] * height) + // ]); + // item.data = data[0]; + // item.render = annoBrushRender; + } else { + // data[0].geometries = VieweportFeaturesToImageFeatures( + // camic.viewer, + // data[0].geometries + // ); + // item.data = data[0]; + // item.render = annoRender; + } + + item.data = data[0]; + item.render = annoRender; + } + + // create lay and update view + if (layerData.isShow) { + layerData.layer = camic.viewer.omanager.addOverlay(item); + camic.viewer.omanager.updateView(); + } + + if (callback) callback.call(layerData); + }) + .catch((e) => { + console.error(e); + }) + .finally(() => { + Loading.close(); + }); +} + +/** + * Deletes annotation from layer manager view + * @param {Object} layerData + */ +function removeCallback(layerData, parentType) { + item = layerData.item; + if (item.typeName == 'ruler') { + deleteRulerHandler(item.id); + return; + } + if (item.typeName !== 'human') return; + if (!item.data) { + // load layer data + loadAnnotationById($CAMIC, layerData, parentType, function() { + annoDelete({ + id: layerData.item.id, + oid: layerData.item.data._id.$oid, + annotation: layerData.item.data.properties.annotation, + }, parentType); + }); + } else { + annoDelete({ + id: layerData.item.id, + oid: layerData.item.data._id.$oid, + annotation: layerData.item.data.properties.annotation, + }, parentType); + } +} + +/** + * Callback for locating annotation on image + * @param {Object} layerData + */ +function locationCallback(layerData) { + let isImageViewer = true; + const item = layerData.item; + if ((item.typeName !== 'human'&&item.typeName !== 'ruler') || item.data == null) return; + if (item.typeName == 'ruler') isImageViewer = false; + + // loaction annotation 2.0 + if (Array.isArray(item.data)) { + let maxx = 0; + let maxy = 0; + let minx = Number.POSITIVE_INFINITY; + let miny = Number.POSITIVE_INFINITY; + item.data.forEach((d)=>{ + d.geometry.coordinates[0].forEach(([x, y])=>{ + maxx = Math.max(maxx, x); + maxy = Math.max(maxy, y); + minx = Math.min(minx, x); + miny = Math.min(miny, y); + }); + }); + const bound = [ + [minx, miny], + [maxx, miny], + [maxx, maxy], + [minx, maxy], + [minx, miny], + ]; + locateAnnotation(bound, isImageViewer); + return; + } + // locate annotation 3.0 + if (item.data.geometries.features[0].geometry.type == 'Point') { + const bound = item.data.geometries.features[0].bound.coordinates; + const center = $CAMIC.viewer.viewport.imageToViewportCoordinates( + bound[0], + bound[1], + ); + $CAMIC.viewer.viewport.panTo(center); + } else { + const bound = [...item.data.geometries.features[0].bound.coordinates[0]]; + if (item.data.provenance&&item.data.provenance.analysis&&item.data.provenance.analysis.isGrid) { + bound[2] = [bound[2][0] + + item.data.geometries.features[0].properties.size[0], bound[2][1] + + item.data.geometries.features[0].properties.size[1]]; + } + locateAnnotation(bound, isImageViewer); + } +} + +/** + * Locates annotation on the image + * called from locationCallback + * @param {Object} bound + */ +function locateAnnotation(bound, isImageViewer) { + // if(){ + + // } + const [minx, miny] = bound[0]; + const [maxx, maxy] = bound[2]; + const rectangle = isImageViewer? + $CAMIC.viewer.viewport.imageToViewportRectangle( + minx, + miny, + maxx - minx, + maxy - miny, + ):new OpenSeadragon.Rect( + minx, + miny, + maxx - minx, + maxy - miny, + ); + const center = rectangle.getCenter(); + $CAMIC.viewer.viewport.fitBounds(rectangle); + + setTimeout(() => { + const max = $CAMIC.viewer.cazoomctrlInstance.getMaxImageZoom($CAMIC.viewer); + const current = $CAMIC.viewer.viewport.viewportToImageZoom( + $CAMIC.viewer.viewport.getZoom(), + ); + if (current > max) { + $CAMIC.viewer.viewport.zoomTo( + $CAMIC.viewer.viewport.imageToViewportZoom(max), + center, + ); + } + }, 50); +} + +/* + collapsible list + 1. Annotation + 2. Analytics +*/ +function getCurrentItem(data) { + console.log(data); +} +// some fake events callback for demo + +function annotationSave() { + $UI.message.add('Annotation Saved'); +} +function algoRun() { + $UI.message.add('Algo is running...'); +} + +/** + * Saves annotation after drawing is stopped + */ +function saveAnnotation() { + annoCallback.call(null, { + id: + $UI.annotOptPanel.setting.formSchemas[$UI.annotOptPanel._select_.value] + .id, + data: $UI.annotOptPanel._form_.value, + }); +} + +function saveAnalytics() { + console.log('saveAnalytics'); +} +function startDrawing(e) { + // + if ( + $UI.toolbar.getSubTool('download_selection') && + $UI.toolbar.getSubTool('download_selection').querySelector('input[type=checkbox]') + .checked + ) { + console.log('start download_selection'); + return; + } + + if ( + $UI.toolbar.getSubTool('preset_label') && + $UI.toolbar.getSubTool('preset_label').querySelector('input[type=checkbox]') + .checked + ) { + // || + // $UI.toolbar.getSubTool("brush").querySelector("input[type=checkbox]") + // .checked + // ) + $CAMIC.viewer.canvasDrawInstance.stop = false; + } else { + $CAMIC.viewer.canvasDrawInstance.stop = !$UI.annotOptPanel._form_.isValid(); + } + // $CAMIC.viewer.canvasDrawInstance.stop = $UI.toolbar + // .getSubTool("preset_label") + // .querySelector("input[type=checkbox]").checked + // ? false + // : !$UI.annotOptPanel._form_.isValid(); + return; +} +function downloadSelection() { + const chks = $UI.downloadSelectionModal.body.querySelectorAll('input[type=checkbox][name=downloadSelection]:checked'); + if (!chks.length) { + alert('Please Check Annotation to Download.'); + return; + } + // + const execIds = []; + chks.forEach((chk)=>execIds.push(chk.dataset.id)); + // get annotation by + const bbox = $CAMIC.viewer.canvasDrawInstance._draws_data_[0].bound.coordinates[0]; + const height = $CAMIC.viewer.imagingHelper.imgHeight; + const width = $CAMIC.viewer.imagingHelper.imgWidth; + const x0 = bbox[0][0]/width; + const y0 = bbox[0][1]/height; + const x1 = bbox[2][0]/width; + const y1 = bbox[2][1]/height; + // + $CAMIC.store.getMarkByIds(execIds, $D.params.slideId, null, 'computer', null, x0, x1, y0, y1).then((data)=>{ + const element = document.createElement('a'); + const blob = new Blob([JSON.stringify(data)], {type: 'application/json'}); + const uri = URL.createObjectURL(blob); + element.setAttribute('href', uri); + element.setAttribute('download', `${$D.params.data.name}_${new Date().toISOString()}.json`); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + hideDownloadSelection(); + }); +} + +function showDownloadSelection() { + // get selection area + const height = $CAMIC.viewer.imagingHelper.imgHeight; + const width = $CAMIC.viewer.imagingHelper.imgWidth; + // get annotation by + const bbox = $CAMIC.viewer.canvasDrawInstance._draws_data_[0].bound.coordinates[0]; + const x0 = bbox[0][0]/width; + const y0 = bbox[0][1]/height; + const x1 = bbox[2][0]/width; + const y1 = bbox[2][1]/height; + // show up loading + $UI.downloadSelectionModal.body.innerHTML = 'Loading ...'; + $CAMIC.store.markSegmentationCount($D.params.slideId, x0, x1, y0, y1).then((data)=>{ + if (data&&Array.isArray(data)&&data.length) { + createSegmentTable(data); + } else { + $UI.downloadSelectionModal.body.innerHTML = 'No Data loaded ...'; + } + }); + $UI.downloadSelectionModal.open(); +} +function createSegmentTable(data) { + $UI.downloadSelectionModal.body.innerHTML = ''; + $UI.downloadSelectionModal.body.innerHTML = ` + + + + + + + + + ${data.sort((a, b)=> b._id - a._id).map((d)=>` + + + + `).join('')} + +
NameCountDownload
Segmentation / ${d._id}${d.count}
`; +} +function hideDownloadSelection() { + downloadSelectionOff(); + $UI.downloadSelectionModal.close(); +} + +function stopDrawing(e) { + if ( + $UI.toolbar.getSubTool('download_selection') && + $UI.toolbar.getSubTool('download_selection').querySelector('input[type=checkbox]') + .checked + ) { + // + showDownloadSelection(); + return; + } + + // preset label annotation + if ( + $UI.toolbar.getSubTool('preset_label') && + $UI.toolbar.getSubTool('preset_label').querySelector('input[type=checkbox]').checked + ) { + // save preset label + savePresetLabel(); + } else { + // annotation + const li = $UI.toolbar.getSubTool('annotation'); + const state = +li.querySelector('label').dataset.state; + if ( + state === 1 && + $CAMIC.viewer.canvasDrawInstance._draws_data_.length > 0 + ) { + $CAMIC.viewer.canvasDrawInstance.isOn = false; + } + } +} + +function openHeatmap() { + switch (ImgloaderMode) { + case 'iip': + // hosted + hostedHeatmap(); + break; + case 'imgbox': + // nano borb + imgboxHeatmap(); + break; + default: + // statements_def + break; + } +} +function hostedHeatmap() { + const slide = $D.params.slideId; + const slideName = $D.params.data.name; + $CAMIC.store + .findHeatmapType(slide) + // + .then(function(list) { + if (typeof list === 'undefined') { + list = []; + } + // get heatmap data + if (!list.length) { + // No heatmap data + $UI.message.addWarning(`info ${slideName} Has No Heatmap Data.`); + return; + } + createHeatMapList(list); + }) + // + .catch(function(error) { + console.error(error); + }) + // + .finally(function() { + if ($D.templates) { + // load UI + } else { + // set message + $UI.message.addError('HeatmapList'); + } + }); +} + +function imgboxHeatmap() { + let slide = $D.params.id; + slide = decodeURIComponent(slide); + // create file input + var element = document.createElement('input'); + element.setAttribute('type', 'file'); + element.style.display = 'none'; + document.body.appendChild(element); + + element.onchange = function(event) { + var input = event.target; + var reader = new FileReader(); + reader.onload = function() { + var text = reader.result; + try { + const data = JSON.parse(text); + + var valid = $VALIDATION.heatmap(data); + if (!valid) { + alert($VALIDATION.heatmap.errors); + return; + } + + $CAMIC.store.clearHeatmaps(); + + data.provenance.image.slide = slide; + const execId = data.provenance.analysis.execution_id; + Loading.open(document.body, 'Loading Heatmap...'); + $CAMIC.store + .addHeatmap(data) + .then((rs) => { + window.location.href = `../heatmap/heatmap.html${window.location.search}&execId=${execId}`; + }) + .catch((e) => { + $UI.message.addError(e); + console.error(e); + }) + .finally(() => { + Loading.close(); + }); + } catch (e) { + console.error(e); + Loading.close(); + } + }; + reader.readAsText(input.files[0]); + }; + element.click(); + document.body.removeChild(element); +} + +function createHeatMapList(list) { + empty($UI.modalbox.body); + $UI.modalbox.setHeaderText('HeatMap List'); + list.forEach((data) => { + const execId = data.provenance.analysis.execution_id; + const a = document.createElement('a'); + const params = getUrlVars(); + a.href = params.mode ? + `../heatmap/heatmap.html?slideId=${$D.params.slideId}&execId=${execId}&mode=pathdb` : + `../heatmap/heatmap.html?slideId=${$D.params.slideId}&execId=${execId}`; + a.textContent = execId; + $UI.modalbox.body.appendChild(a); + $UI.modalbox.body.appendChild(document.createElement('br')); + }); + $UI.modalbox.open(); +} + + +async function addPresetLabelsHandler(label) { + const rs = await $CAMIC.store.addPresetLabels(label).then((d)=>d.result); + + if (rs.ok&&rs.nModified > 0) { + $UI.labelsViewer.addLabel(label); + $UI.message.add(`Label "${label.type}" Has been Added`); + } else { + $UI.message.addError('Creating The New Label Has Failed'); + } + $UI.labelsViewer.__switch('view'); +} + +async function editPresetLabelsHandler(oldElt, newLabel) { + Loading.open($UI.labelsViewer.elt, 'Saving Label ...'); + + const rs = await $CAMIC.store.updatePresetLabels(oldElt.dataset.id, newLabel).then((d)=>d.result); + + if (rs.ok) { + // update + const rs1 = await $CAMIC.store.updateMarksLabel(newLabel.id, newLabel.type).then((d)=>d.result); + if (rs1.ok) { + // update UI + updateMarksLabel(newLabel.id, newLabel.type, $UI.layersViewer); + updateMarksLabel(newLabel.id, newLabel.type, $UI.layersViewerMinor); + + // + $UI.labelsViewer.setLabels(oldElt, newLabel); + $UI.message.add(`Label "${newLabel.type}" Has been Updated`); + if (oldElt.classList.contains('selected')) drawLabel({value: 'prelabels', checked: true}); + } else { + $UI.message.addError(`Updating The Marks' Label Has Failed`); + } + } else { + $UI.message.addError('Updating The Label Has Failed'); + } + Loading.close(); + $UI.labelsViewer.__switch('view'); +} +function updateMarksLabel(id, name, layersViewer) { + const cate = layersViewer.setting.categoricalData.human.items[id]; + if (cate) { + cate.item.name = name; + cate.elt.querySelector('label > div').textContent = name; + cate.elt.querySelector('input[type=checkbox]').dataset.name = name; + cate.items.forEach((e)=>{ + e.item.name = name; + e.item.label.name = name; + const newName = `${name}${e.elt.dataset.id}`; + e.elt.dataset.title = newName; + e.elt.querySelector('label > div').textContent = newName; + + if (e.item.data) { + e.item.data.provenance.analysis.name = name; + e.item.data.properties.annotations.name = name; + e.item.data.properties.annotations.notes = name; + } + }); + } +} +async function removePresetLabelsHandler(elt, label) { + const rs = await $CAMIC.store.removePresetLabels(label.id).then((d)=>d.result); + if (rs.ok&&rs.nModified > 0) { + $UI.message.add(`Label "${label.type}" Has been removed`); + $UI.labelsViewer.removeLabel(label.id); + } else { + $UI.message.addError(`Deleting The '${label.type}' Label Has Failed`); + } +} + +function selectedPresetLabelsHandler(label) { + drawLabel({value: 'prelabels', checked: true}); +} + +function drawLabel(e) { + if (!$CAMIC.viewer.canvasDrawInstance) { + alert('Draw Doesn\'t Initialize'); + return; + } + const labels = $UI.labelsViewer.getSelectedLabels(); + if (e.checked) { + if ($CAMIC.status == 'label') { + presetLabelOn.call(this, labels); + return; + } + // turn off annotation + toolsOff(); + + var checkAllToolsOff = setInterval( + function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + presetLabelOn.call(this, labels); + } + }.bind(this), + 100, + ); + } else { + // off preset label + presetLabelOff(); + } +} + +function presetLabelOn(label) { + if (!$CAMIC.viewer.canvasDrawInstance) return; + // open labels viewer + mainMenuChange({apps: false, layers: false, labels: true}); + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + if (!label) { + $UI.message.addWarning('No Label Exist. Please Add A Label'); + $UI.toolbar.getSubTool('preset_label').querySelector('label').style.color = ''; + canvasDraw.clear(); + canvasDraw.drawOff(); + return; + } + + spen.menu(65, 0.2); + // open menu + canvasDraw.drawMode = label.mode; + if (label.mode == 'grid') { + canvasDraw.size = [parseInt(label.size), parseInt(label.size)]; + } + canvasDraw.style.color = label.color; + canvasDraw.drawOn(); + $CAMIC.status = 'label'; + $UI.toolbar.getSubTool('preset_label').querySelector('label').style.color = + label.color; +} + +function presetLabelOff() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + spen.close(); + // close spen + if ( + canvasDraw._draws_data_.length && + confirm(`Do You Want To Save Annotation Label Before You Leave?`) + ) { + savePresetLabel(); + } else { + canvasDraw.clear(); + canvasDraw.drawOff(); + $UI.appsSideMenu.close(); + $UI.toolbar + .getSubTool('preset_label') + .querySelector('input[type=checkbox]').checked = false; + $UI.toolbar.getSubTool('preset_label').querySelector('label').style.color = + ''; + $UI.labelsSideMenu.close(); + $CAMIC.status = null; + } +} + +function savePresetLabel() { + if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) { + // toast + $UI.message.addWarning('info'+ + 'No Markup On Annotation. Try Holding And Dragging.', 4000); + return; + } + const data = $UI.labelsViewer.getSelectedLabels(); + if (!data) { + $UI.message.addWarning('No Label Selected. Please select One.', 4000); + return; + } + const execId = randomId(); + const labelId = data.id; + const labelName = data.type; + // const parent = data.type; + const noteData = { + id: execId, + labelId: labelId, + name: labelName, + notes: data.type, + }; + const feature = $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection() + .features[0]; + let annotJson; + if (feature.properties.size) { + // brush + const values = getGrids( + feature.geometry.coordinates[0], + feature.properties.size, + ); + const set = new Set(); + values.map((i) => i.toString()).forEach((v) => set.add(v)); + const points = Array.from(set).map((d) => d.split(',')); + annotJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'human', + execution_id: execId, // randomId + name: labelName, // labelName + labelId: labelId, + type: 'label', + isGrid: true, + }, + }, + properties: { + annotations: noteData, + }, + geometries: convertGeometries(points, { + note: data.type, + size: feature.properties.size, + color: feature.properties.style.color, + }), + }; + } else { + // point / polygon / stringLine + annotJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'human', + execution_id: execId, // randomId + name: labelName, // labelName + labelId: labelId, + type: 'label', + }, + }, + properties: { + annotations: noteData, + }, + geometries: ImageFeaturesToVieweportFeatures( + $CAMIC.viewer, + $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection(), + ), + }; + } + + $CAMIC.store + .addMark(annotJson) + .then((data) => { + // server error + if (data.error) { + $UI.message.addError(`${data.text}:${data.url}`); + Loading.close(); + return; + } + + // no data added + if (data.count < 1) { + Loading.close(); + $UI.message.addWarning(`Annotation Save Failed`); + return; + } + const __data = data.ops[0]; + // create layer data + const newItem = { + id: execId, + name: noteData.name, + typeId: 'human', + typeName: 'human', + creator: getUserId(), + shape: annotJson.geometries.features[0].geometry.type, + isGrid: annotJson.provenance.analysis.isGrid? true: false, + label: { + id: annotJson.provenance.analysis.labelId, + name: annotJson.provenance.analysis.name, + }, + data: null, + }; + $D.humanlayers.push(newItem); + $UI.layersViewer.addHumanItem(newItem, 'human', labelId); + $UI.layersViewerMinor.addHumanItem( + newItem, + 'human', + labelId, + $minorCAMIC && $minorCAMIC.viewer ? true : false, + ); + + __data._id = {$oid: __data._id}; + addAnnotation( + execId, + __data, + 'human', + labelId, + ); + }) + .catch((e) => { + Loading.close(); + console.log('save failed', e); + }) + .finally(() => { + $UI.message.addSmall(`Added The '${noteData.name}' Annotation.`); + }); +} + +function addAnnotation(id, data, type, parent) { + const layerData = $UI.layersViewer.getDataItemById(id, type, parent); + const layerDataMinor = $UI.layersViewerMinor.getDataItemById(id, type, parent); + const item = layerData.item; + data.geometries = VieweportFeaturesToImageFeatures( + $CAMIC.viewer, + data.geometries, + ); + if (data.provenance.analysis.isGrid) { + const width = $CAMIC.viewer.imagingHelper.imgWidth; + const height = $CAMIC.viewer.imagingHelper.imgHeight; + + const feature = data.geometries.features[0]; + const size = feature.properties.size; + feature.properties.size = [ + Math.round(size[0] * width), + Math.round(size[1] * height), + ]; + } + item.data = data; + item.render = annoRender; + // create lay and update view + if (layerData.isShow) { + layerData.layer = $CAMIC.viewer.omanager.addOverlay(item); + $CAMIC.viewer.omanager.updateView(); + } + if ($minorCAMIC && $minorCAMIC.viewer && layerDataMinor.isShow) { + layerDataMinor.layer = $minorCAMIC.viewer.omanager.addOverlay(item); + $minorCAMIC.viewer.omanager.updateView(); + } + + $CAMIC.drawContextmenu.off(); + $CAMIC.viewer.canvasDrawInstance.clear(); + // close app side + $UI.toolbar._mainTools[0].querySelector('[type=checkbox]').checked = false; + $UI.appsSideMenu.close(); + // $UI.layersViewer.update(); + // $UI.layersViewerMinor.update(); +} + +function onDeleteRuler(ruler) { + const id = ruler.element.dataset.id; + deleteRulerHandler(id); +} + +function deleteRulerHandler(execId) { + if (!confirm(message = `Are You Sure You Want To Delete This Ruler {ID:${execId}}?`)) return; + $CAMIC.store + .deleteMarkByExecId(execId, $D.params.data.slide) + .then((datas) => { + // server error + if (datas.error) { + const errorMessage = `${datas.text}: ${datas.url}`; + $UI.message.addError(errorMessage, 4000); + // close + return; + } + + // no data found + if (!datas.deletedCount || datas.deletedCount < 1) { + $UI.message.addWarning(`Delete Ruler Failed.`, 4000); + return; + } + // update UI + removeElement($D.rulerlayers, execId); + $UI.layersViewer.removeItemById(execId, 'ruler'); + $UI.layersViewerMinor.removeItemById(execId, 'ruler'); + $CAMIC.viewer.measureInstance.removeRulerById(execId); + if ($minorCAMIC && + $minorCAMIC.viewer && + $minorCAMIC.viewer.measureInstance) { + $minorCAMIC.viewer.measureInstance.removeRulerById(execId); + } + $UI.message.addSmall(`Deleted The '${execId}' Ruler.`); + }) + .catch((e) => { + $UI.message.addError(e); + console.error(e); + }) + .finally(() => { + // console.log('delete end'); + }); +} + + +function onAddRuler(ruler) { + const {x, y, width, height} = ruler.getBounds($CAMIC.viewer.viewport); + const execId = 'ruler' + randomId(); + ruler.element.dataset.id = execId; + let innerHTML = ruler.element.innerHTML; + innerHTML = innerHTML.split('<').join('<'); + innerHTML = innerHTML.split('>').join('>'); + innerHTML = innerHTML.split(' ').join(' '); + let rulerJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'ruler', + execution_id: execId, + name: execId, + type: 'ruler', + }, + }, + properties: { + annotations: { + name: execId, + notes: 'ruler', + }, + mode: ruler.element.dataset.mode, + innerHTML: innerHTML, + }, + geometries: { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [ + x, + y, + ], + [ + x + width, + y, + ], + [ + x + width, + y + height, + ], + [ + x, + y + height, + ], + [ + x, + y, + ], + ], + ], + }, + bound: { + type: 'Polygon', + coordinates: [ + [ + [ + x, + y, + ], + [ + x + width, + y, + ], + [ + x + width, + y + height, + ], + [ + x, + y + height, + ], + [ + x, + y, + ], + ], + ], + }, + }, + ], + }, + + }; + + $CAMIC.store + .addMark(rulerJson) + .then((data) => { + // server error + if (data.error) { + $UI.message.addError(`${data.text}:${data.url}`); + return; + } + + // no data added + if (data.result && data.result.ok && data.result.n < 1) { + Loading.close(); + $UI.message.addWarning(`Ruler Save Failed`); + return; + } + + const __data = data.ops[0]; + // create layer data + const newItem = { + id: execId, + name: execId, + typeId: 'ruler', + typeName: 'ruler', + data: data.ops[0], + creator: getUserId(), + }; + newItem.data.properties.innerHTML = newItem.data.properties.innerHTML.split('<').join('<'); + newItem.data.properties.innerHTML = newItem.data.properties.innerHTML.split('>').join('>'); + newItem.data.properties.innerHTML = newItem.data.properties.innerHTML.split(' ').join(' '); + newItem.data._id = {$oid: newItem.data._id}; + $D.rulerlayers.push(newItem); + $UI.layersViewer.addItem(newItem, 'ruler'); + $UI.layersViewerMinor.addItem( + newItem, + 'ruler', + $minorCAMIC && $minorCAMIC.viewer ? true : false, + ); + + const rulerData = $UI.layersViewer.getDataItemById(execId, 'ruler'); + rulerData.layer = $CAMIC.viewer.getOverlayById(ruler.element); + + const rulerDataMinor = $UI.layersViewerMinor.getDataItemById(execId, 'ruler'); + // create lay and update view + if ($minorCAMIC && $minorCAMIC.viewer && rulerDataMinor.isShow) { + const [xmin, ymin] = newItem.data.geometries.features[0].geometry.coordinates[0][0]; + const [xmax, ymax] = newItem.data.geometries.features[0].geometry.coordinates[0][2]; + rulerDataMinor.layer = $minorCAMIC.viewer.measureInstance.addRuler({ + id: newItem.id, + mode: newItem.data.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: newItem.data.properties.innerHTML, + isShow: rulerDataMinor.isShow, + }); + } + + // close app side + // $UI.layersViewer.update(); + // $UI.layersViewerMinor.update(); + + $UI.message.addSmall(`Added The '${execId}' Ruler.`); + }) + .catch((e) => { + Loading.close(); + console.log('Ruler Save Failed'); + }); +} + +async function rootCallback({root, parent, parentName, items}) { + // start a message + openLoadStatus(`${root==parent?root:`${root} - ${parentName}`}`); + // + const viewerName = this.toString(); + let camic = null; + switch (viewerName) { + case 'main': + camic = $CAMIC; + break; + case 'minor': + camic = $minorCAMIC; + break; + default: + break; + } + + parent = parent=='other'?null:parent; + // human other + var data; + // const ids = items.filter(d => d.item.id); + const ids = items.reduce(function(rs, d) { + if (!d.item.data) rs.push(d.item.id); + return rs; + }, []); + // loading + if (ids.length) { + // mult rulers + try { + data = await $CAMIC.store.getMarkByIds(ids, $D.params.slideId, (root=='human'&&parent!==null)?parentName:parent, root); + + if (data.error) { // error + closeLoadStatus(); + const errorMessage = `${data.text}: ${data.url}`; + console.error(errorMessage); + $UI.message.addError(errorMessage, 4000); + return; + } + // covert the ruler data + if (root == 'ruler') { + data.forEach((d) => { + if (d.provenance&& + d.provenance.analysis&& + d.provenance.analysis.coordinate&& + d.provenance.analysis.coordinate == 'image') { + d.geometries = ImageFeaturesToVieweportFeatures(camic.viewer, d.geometries); + } + + d.properties.innerHTML = d.properties.innerHTML.split('<').join('<'); + d.properties.innerHTML = d.properties.innerHTML.split('>').join('>'); + d.properties.innerHTML = d.properties.innerHTML.split(' ').join(' '); + }); + } else { + // covert the human data + data.forEach((d)=>{ + // for support quip 2.0 data model + if (d.geometry) { + var image = camic.viewer.world.getItemAt(0); + var imgWidth = image.source.dimensions.x; + var imgHeight = image.source.dimensions.y; + + // convert coordinates + d.geometry.coordinates[0] = d.geometry.coordinates[0].map((point) => { + return [ + Math.round(point[0] * imgWidth), + Math.round(point[1] * imgHeight), + ]; + }); + // set default color + d.properties = {}; + d.properties.style = { + color: '#000080', + lineCap: 'round', + lineJoin: 'round', + isFill: false, + }; + } else { + // conver coordinate if is `normalized` + if (d.provenance && + d.provenance.analysis&& + (d.provenance.analysis.coordinate == undefined|| + d.provenance.analysis.coordinate == 'normalized')) { + d.geometries = VieweportFeaturesToImageFeatures( camic.viewer, d.geometries ); + } + // if annotaion is brush then covert the size's coordinates + if (d.provenance.analysis.isGrid) { + const width = camic.viewer.imagingHelper.imgWidth; + const height = camic.viewer.imagingHelper.imgHeight; + + const feature = d.geometries.features[0]; + const size = feature.properties.size; + feature.properties.size = [ + Math.round(size[0] * width), + Math.round(size[1] * height), + ]; + } + } + }); + } + + // add to layer + } catch (error) { + closeLoadStatus(); + // finish loaded + console.error('loading human annotations error', error); + $UI.message.addError('loading human annotations error', 4000); + return; + } + } + + if (root == 'ruler') { + items.forEach((rulerData) => { + const item = rulerData.item; + // if no data then find and add to layer + if (!item.data) { + // TODO change to things else + const d = data.find( (d) => d.provenance.analysis.execution_id==item.id); + item.data = d; + let [xmin, ymin] = d.geometries.features[0].geometry.coordinates[0][0]; + let [xmax, ymax] = d.geometries.features[0].geometry.coordinates[0][2]; + // create lay and update view + rulerData.layer = camic.viewer.measureInstance.addRuler({ + id: item.id, + mode: d.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: item.data.properties.innerHTML, + isShow: rulerData.isShow, + }); + } + if (rulerData.isShow) rulerData.layer.element.style.display =''; + else rulerData.layer.element.style.display ='none'; + }); + } + + if (root == 'human') { + items.forEach((HumanData) => { + const item = HumanData.item; + + if (!item.data) { + const d = data.filter( (d) => d.provenance.analysis.execution_id==item.id); + item.data = d[0].geometry?d:d[0]; + if (Array.isArray(item.data)&&item.data[0].geometry) { // 2.0 + item.render = oldAnnoRender; + item.clickable = false; + item.hoverable = false; + } else { + item.render = annoRender; + } + // HumanData.layer = camic.viewer.omanager.addOverlay(item); + } + if (!HumanData.layer) HumanData.layer = camic.viewer.omanager.addOverlay(item); + HumanData.layer.isShow = HumanData.isShow; + }); + + camic.viewer.omanager.updateView(); + } + closeLoadStatus(); +} + +/* Enhance Tool */ +function enhance(e) { + document.querySelector('[title="Enhance"]').previousSibling.checked = true; + if (!setEnhance) { + // $UI.message.add('infoOn Movement, enhance would be undone', 2000); + $CAMIC.viewer.addHandler('zoom', unenhance); + $CAMIC.viewer.addHandler('pan', unenhance); + setEnhance = true; + } + // Canvas information + const canvas = $CAMIC.viewer.canvas.firstChild; + const context = canvas.getContext('2d'); + let width = canvas.width; let height = canvas.height; + var img = context.getImageData(0, 0, width, height); + var data = img.data; + + if (e.status == 'Histogram Eq') { + context.putImageData(clahe(img, 64, 0.015), 0, 0); + } else if (e.status == 'Edge') { + context.putImageData(edgedetect_sobel(img, 80), 0, 0); + } else if (e.status == 'Sharpen') { + var filter = [[-1, -1, -1], [-1, 14, -1], [-1, -1, -1]]; + context.putImageData(applyfilter(img, filter), 0, 0); + } else if (e.status == 'Custom') { + var input = prompt('Enter the 2D Kernel: Eg : [[1, 0], [0, 1]]'); var f=0; + // JSON Parse test + try { + var filter = JSON.parse(input); var sz = filter[0].length; + } catch (e) { + alert('Invalid Kernel : ' + input); + return; + } + // 2D array check + for (var r=0; r + + + + + + + + + + + + + caMicroscope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+ + + +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+ + +
+ + + + + + + +