From 8327f634c17a894ac4f67d3be1eaef805b5ff8d2 Mon Sep 17 00:00:00 2001 From: r7rohan Date: Thu, 5 Aug 2021 04:40:12 +0530 Subject: [PATCH 01/10] Segmentation extension Save segmentations --- README.md | 4 +- apps/segment/segment.css | 24 +-- apps/segment/segment.html | 1 + apps/segment/segment.js | 135 ++++++++------ apps/segment/segmentpanel/segmentpanel.css | 20 ++- apps/segment/segmentpanel/segmentpanel.js | 16 +- apps/table.html | 14 +- apps/table.js | 2 +- apps/viewer/tut.js | 1 + common/smartpen/README.md | 17 ++ common/smartpen/autoalign.css | 63 +++---- common/smartpen/autoalign.js | 13 +- .../openseadragon-canvas-draw-overlay.js | 29 +-- core/extension/segment-annotation.js | 168 ++++++++++++++++++ 14 files changed, 377 insertions(+), 130 deletions(-) create mode 100644 common/smartpen/README.md create mode 100644 core/extension/segment-annotation.js diff --git a/README.md b/README.md index 423a42dbd..96f36cf37 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ The toolbar is in the top-left of the main content window. Use the toolbar butto | ![](https://fonts.gstatic.com/s/i/materialicons/apps/v4/24px.svg) | Annotations | 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. | | ![](https://fonts.gstatic.com/s/i/materialicons/view_list/v4/24px.svg) | Layer Manager | Opens the Layers Manager panel, where you can select which layers to view. | | ![](https://fonts.gstatic.com/s/i/materialicons/home/v4/24px.svg) | Home | Return to the data table so that you can open another slide.| -| ![](https://fonts.gstatic.com/s/i/materialicons/create/v4/24px.svg) | Draw | Draw thin lines, thick lines, or polygons on the image. To maintain the integrity of measurements, avoid drawing shapes that overlap or intersect one another. | -| ![](https://fonts.gstatic.com/s/i/materialicons/colorize/v4/24px.svg) | Preset Labels | Use a preset annotation type immediately to quickly annotate a silde consistently. | +| ![](https://fonts.gstatic.com/s/i/materialicons/create/v4/24px.svg) | Draw | 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. | +| ![](https://fonts.gstatic.com/s/i/materialicons/colorize/v4/24px.svg) | Preset Labels | Use a preset annotation type immediately to quickly annotate a slide consistently. | | ![](https://fonts.gstatic.com/s/i/materialicons/search/v4/24px.svg) | Magnifier |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. | | ![](https://fonts.gstatic.com/s/i/materialicons/space_bar/v4/24px.svg) | Measurement | Drag this tool on the slide to learn the measurement in micrometers. | | ![](https://fonts.gstatic.com/s/i/materialicons/share/v4/24px.svg) | Share View |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.| diff --git a/apps/segment/segment.css b/apps/segment/segment.css index c82e7a8fe..63031bf5a 100644 --- a/apps/segment/segment.css +++ b/apps/segment/segment.css @@ -197,7 +197,7 @@ ul.disabled { #choice_panel .modalbox-content{ width: 25%; - + } #details_panel .modalbox-content{ width: 25%; @@ -313,11 +313,11 @@ ul.disabled { } .btn-del{ background-color: red; - border: none; - color: white; - padding: 12px 16px; - font-size: 14px; - cursor: pointer; + border: none; + color: white; + padding: 12px 16px; + font-size: 14px; + cursor: pointer; } body{ color: black; @@ -325,7 +325,7 @@ body{ .btn-sel { - + border: 10px ; border-color: black; padding: 10px; @@ -431,21 +431,21 @@ body{ } #ext1{ - + text-align: center; font-size: 16px; } #ext2{ - + text-align: center; font-size: 16px; } .extract{ padding : 3% 6% 3% 6%; - + color: white; background-color:#46a049 !important; border-radius: 7%; @@ -454,7 +454,7 @@ body{ } .extract:hover { - + transform: scale(1.2); cursor: pointer; @@ -505,4 +505,4 @@ body{ @keyframes fadeout { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} -} \ No newline at end of file +} diff --git a/apps/segment/segment.html b/apps/segment/segment.html index c23ba8925..d151c635c 100644 --- a/apps/segment/segment.html +++ b/apps/segment/segment.html @@ -135,6 +135,7 @@ + diff --git a/apps/segment/segment.js b/apps/segment/segment.js index ed0b80810..ba91a628a 100644 --- a/apps/segment/segment.js +++ b/apps/segment/segment.js @@ -46,7 +46,7 @@ const objAreaMin = 60; const objAreaMax = 4500; const lineWidth = 2; const timeOutMs = 10; - +let anno = {}; function initialize() { var checkPackageIsReady = setInterval(async function() { @@ -80,7 +80,7 @@ async function initUIcomponents() {
  • - The image size on which the model is trained + The image size on which the model is trained (y x y)

  • @@ -100,7 +100,7 @@ async function initUIcomponents() {

    Select model.json first followed by the weight binaries.

    - +



    URL to the ModelAndWeightsConfig JSON describing the model.



    @@ -147,7 +147,7 @@ async function initUIcomponents() { provideContent: true, content: `
    - +

    Please select a model


    @@ -356,6 +356,7 @@ function initCore() { slideQuery.name = $D.params.slide; slideQuery.location = $D.params.location; $CAMIC = new CaMic('main_viewer', slideQuery, opt); + anno = new segmentationanno($CAMIC, $D, $UI, slideQuery.id); } catch (error) { Loading.close(); $UI.message.addError('Core Initialization Failed'); @@ -378,11 +379,6 @@ function initCore() { // add stop draw function viewer.canvasDrawInstance.addHandler('stop-drawing', camicStopDraw); - viewer.addHandler('zoom', (e) => { - const mask = $UI.segmentPanel.__mask; - fitCvs(mask); - }); - $UI.segmentPanel = new SegmentPanel(viewer); // add event for threshold @@ -473,6 +469,10 @@ function initCore() { const fname = $D.params.slideId + '_roi.csv'; buildAndDownloadCSV($UI.segmentPanel.__contours, fname); }); + + $UI.segmentPanel.__annotation.addEventListener('click', function(e) { + saveAnnotation(); + }); }); } @@ -551,10 +551,13 @@ function camicStopDraw(e) { const args = $UI.args; if ( flag != -1) { extractRoi(choices1, flag); + $UI.segmentPanel.toggleMask(1); } else if (!args || args.status == 'watershed') { segmentROI(box); + $UI.segmentPanel.toggleMask(2); } else { segmentModel(args.status); + $UI.segmentPanel.toggleMask(1); } $UI.segmentPanel.setPosition(box.rect.x, box.rect.y, box.rect.width, box.rect.height); if ($UI.segmentPanel.__spImgWidth != 0) { @@ -568,6 +571,36 @@ function camicStopDraw(e) { } } +function saveAnnotation() { + let notes = {name: $UI.segmentPanel.__name.value, notes: $UI.segmentPanel.__notes.value}; + let canv = !$UI.args||$UI.args.status == 'watershed'? $UI.segmentPanel.__out: $UI.segmentPanel.__mask; + // let image = canv.getContext('2d').getImageData(0,0,canv.width,canv.height); + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; const viewer = $CAMIC.viewer; + + canvasDraw.clear(); + canvasDraw._simplify = false; + if ($UI.args && $UI.args.status != 'watershed') { + findContour(canv, canv, 0.11); + } + let data = $UI.segmentPanel.__contours; + const vpx = $UI.segmentPanel.__top_left[0]; + const vpy = $UI.segmentPanel.__top_left[1]; + $UI.segmentPanel.close(); + // let te = {data:Array.from(image.data),width:image.width,height:image.height,x:vpx,y:vpy}; + for (var i=0; i $UI.segmentPanel.__minarea.value) { + cntr.push(cnt); + } + } + src.delete(); dst.delete(); contours.delete(); hierarchy.delete(); + $UI.segmentPanel.__contours = cntr; +} /** * WATERSHED SEGMENTATION @@ -1087,7 +1137,7 @@ function watershed(inn, out, save=null, thresh) { // Find the stuff that IS an object cv.dilate(gray, opening, M); // remove any small white noises in the image - cv.dilate(opening, imageBg, M, new cv.Point(-1, -1), 3); // remove any small holes in the object + // cv.dilate(opening, imageBg, M, new cv.Point(-1, -1), 3); // remove any small holes in the object // Distance transform - for the stuff we're not sure about cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5); @@ -1099,10 +1149,10 @@ function watershed(inn, out, save=null, thresh) { // Mark (label) the regions starting with 1 (color output) imageFg.convertTo(imageFg, cv.CV_8U, 1, 0); - cv.subtract(imageBg, imageFg, unknown); + // cv.subtract(imageBg, imageFg, unknown); // Get connected components markers - const x = cv.connectedComponents(imageFg, markers); + // const x = cv.connectedComponents(imageFg, markers); // Get Polygons const contours = new cv.MatVector(); @@ -1112,16 +1162,16 @@ function watershed(inn, out, save=null, thresh) { $UI.segmentPanel.__contours = contours; console.log('Getting contours.'); - for (let i = 0; i < markers.rows; i++) { - for (let j = 0; j < markers.cols; j++) { - markers.intPtr(i, j)[0] = markers.ucharPtr(i, j)[0] + 1; - if (unknown.ucharPtr(i, j)[0] === 255) { - markers.intPtr(i, j)[0] = 0; - } - } - } + // for (let i = 0; i < markers.rows; i++) { + // for (let j = 0; j < markers.cols; j++) { + // markers.intPtr(i, j)[0] = markers.ucharPtr(i, j)[0] + 1; + // if (unknown.ucharPtr(i, j)[0] === 255) { + // markers.intPtr(i, j)[0] = 0; + // } + // } + // } cv.cvtColor(src, dst, cv.COLOR_RGBA2RGB, 0); - cv.watershed(dst, markers); + // cv.watershed(dst, markers); const cloneSrc = cv.Mat.zeros(src.rows, src.cols, cv.CV_8UC4); const listContours = cv.Mat.zeros(src.rows, src.cols, cv.CV_8UC4); console.log(cv.COLOR_RGBA2RGB); @@ -1148,6 +1198,7 @@ function watershed(inn, out, save=null, thresh) { const tmp = new cv.Mat(); console.log('Drawing Contours'); + let cntr = []; // console.log($UI.segmentPanel.__minarea.value); // console.log($UI.segmentPanel.__maxarea.value);segmentMode for (let i = 1; i < contours.size(); ++i) { @@ -1159,11 +1210,13 @@ function watershed(inn, out, save=null, thresh) { ++segcount; cv.approxPolyDP(cnt, tmp, 1, true); // console.log(tmp.data32S); + cntr.push(cnt); cv.drawContours(cloneSrc, contours, i, color, lineWidth, cv.FILLED, hierarchy, 1); cv.drawContours(i2s, contours, i, color, lineWidth, cv.FILLED, hierarchy, 1); if (save) cv.drawContours(dc, contours, i, color, lineWidth, cv.FILLED, hierarchy, 1); } } + $UI.segmentPanel.__contours = cntr; console.log(segcount); console.log('Done Drawing Contours'); window.segcnt = segcount; // Will be used later in downloadCSV function @@ -1310,42 +1363,6 @@ function _size(element) { return [width, height]; } - -/** - * Fit a canvas to its parent element - * @param {Object} canvas The canvas to be resized - * @param {Object} parent Parent according to which canvas is resized. Defaults to immediate parent. - * @param {Object} scale Scale to which it has to be resized. Defaults to 1 (Same size) - * @return {Object} - */ -function fitCvs(canvas, parent, scale) { - canvas.style.position = canvas.style.position || 'absolute'; - canvas.style.top = 0; - canvas.style.left = 0; - - resize.scale = parseFloat(scale || 1); - resize.parent = parent; - - return resize(); // if (segcnt === 0) { - // alert('Nothing to download'); - // return; - // } - - function resize() { - var p = resize.parent || canvas.parentNode; - // console.log(resize.parent , canvas.parentNode) - var psize = _size(p); - var width = psize[0]|0; - var height = psize[1]|0; - - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; - - return resize; - } -} - - /** * Convert a dataURI to a Blob * diff --git a/apps/segment/segmentpanel/segmentpanel.css b/apps/segment/segmentpanel/segmentpanel.css index 6651c5485..a01426542 100644 --- a/apps/segment/segmentpanel/segmentpanel.css +++ b/apps/segment/segmentpanel/segmentpanel.css @@ -170,6 +170,24 @@ padding-right: 5px; } +.annotation{ + width:200px; + font-size: 12px; + text-align: center; + vertical-align: middle; +} +.annotation *{ + padding: 1px 1px 1px 5px !important; + display:flex; + background-color:white; +} +.annotation textarea{ + width:150px; + height:15px; + word-wrap: break-word; + word-break: break-all; +} + #csvDLB { display: none; -} \ No newline at end of file +} diff --git a/apps/segment/segmentpanel/segmentpanel.js b/apps/segment/segmentpanel/segmentpanel.js index ba79562b9..2379bb53e 100644 --- a/apps/segment/segmentpanel/segmentpanel.js +++ b/apps/segment/segmentpanel/segmentpanel.js @@ -31,6 +31,13 @@ function SegmentPanel(viewer) { + +
    + + + +
    + @@ -92,6 +99,11 @@ function SegmentPanel(viewer) { this.__oplabel = this.elt.querySelector('.segment-setting label#olabel'); this.__opwrap = this.elt.querySelector('#owrap'); + // annotation + this.__name = this.elt.querySelector('#_name'); + this.__notes = this.elt.querySelector('#_notes'); + this.__annotation = this.elt.querySelector('#_save'); + this.viewer.addOverlay({ element: this.elt, location: new OpenSeadragon.Rect(0, 0, 0, 0), @@ -186,10 +198,10 @@ SegmentPanel.prototype.hideProgress = function() { this.__indicator.style.display = 'none'; }; -SegmentPanel.prototype.toggleMask = function() { +SegmentPanel.prototype.toggleMask = function(e=0) { console.log('toggleMask'); - if (this.__mask.style.display == 'none') { + if (this.__mask.style.display == 'none' && e!=2 || e==1) { this.__mask.style.display = ''; this.__out.style.display = 'none'; } else { diff --git a/apps/table.html b/apps/table.html index c6fbbab50..68f2a81cc 100644 --- a/apps/table.html +++ b/apps/table.html @@ -29,9 +29,9 @@ - @@ -110,7 +110,7 @@

    caMicroscope

    -
    - -
    + +
    + @@ -172,10 +172,15 @@ class smartpen { var s = document.getElementById('align_s'); var t = document.getElementById('align_t'); var blink; + // logo change, blink and width, text openbtn.onclick = function() { - check1.checked=!check1.checked; if (!check1.checked)check2.checked=false; + check1.checked=!check1.checked; + if (!check1.checked) { + check2.checked=false; openbtn.innerHTML = 'Smart-Pen'; openbtn.className = ''; + } else { + openbtn.innerHTML = 'auto_graph'; openbtn.className = 'material-icons'; + } }; - // logo change, blink and width mode.onclick = () => { this.mode = (this.mode+1)%3; if (this.mode == 0) { @@ -241,7 +246,7 @@ class mathtoolSmartpen { var n = Math.min(~~(Math.sqrt(d)/c), m); for (var j=1; j<=n; j++) { var x = a[0] + j*(b[0]-a[0])/n; var y = a[1] + j*(b[1]-a[1])/n; - dist.push([x, y]); + dist.push([~~x, ~~y]); } prev = b; } else if (d>=low*low) { diff --git a/core/extension/openseadragon-canvas-draw-overlay.js b/core/extension/openseadragon-canvas-draw-overlay.js index 217541c20..3c43c005c 100644 --- a/core/extension/openseadragon-canvas-draw-overlay.js +++ b/core/extension/openseadragon-canvas-draw-overlay.js @@ -125,6 +125,7 @@ this._hash_data = new Map(); // aligndata this._align_data = []; + this._simplify = true; // -- create container div, and draw, display canvas -- // this._containerWidth = 0; @@ -356,7 +357,7 @@ */ drawOn: function() { // stop turning on draw mode if already turn on - if (this.isOn === true) return; + if (this.isOn === true && this.moveOn === true) return; // clock viewer // this._viewer.controls.bottomright.style.display = 'none'; this.updateView(); @@ -386,7 +387,7 @@ drawOff: function() { // stop turning off draw mode if already turn off // if(this.contextMenu) this.contextMenu. - if (this.isOn === false) return; + if (this.isOn === false && this.moveOn == false) return; // unclock viewer // this._viewer.controls.bottomright.style.display = ''; this._viewer.setMouseNavEnabled(true); @@ -530,12 +531,12 @@ } img_point.x = Math.round(img_point.x); img_point.y = Math.round(img_point.y); - img_point = this.__align_real(img_point); // set style for ctx DrawHelper.setStyle(this._draw_ctx_, this.style); this._draw_ctx_.fillStyle = hexToRgbA(this.style.color, 0.3); if (this.isDrawing) { + img_point = this.__align_real(img_point); switch (this.drawMode) { case 'free': // draw line @@ -895,15 +896,14 @@ // simplify and postprocess if(this.isMoving) spen.smoothness = spen.s; - if (!(this.drawMode === 'grid')) - if(spen.mode == 1) + if (!(this.drawMode === 'grid') && this._simplify) + if(spen.mode != 0) points = mtool.populate(points, 500000, ~~this.scaleWindowtoImage(2), 150); else points = simplify(points, 3.5); // float to integer points = this._convert_integer(points); - console.log(points.length); if (!(this.drawMode === 'line' || this.drawMode == 'grid')) { let isIntersect = false; @@ -1120,12 +1120,11 @@ }, __align_undo() { if (this._path_index > 0 && this._align_data.length) { - this._current_path_ = this._draws_data_[--this._path_index]; - this._draws_data_ = this._draws_data_.slice(0, this._path_index); - this._current_path_.geometry.coordinates[0] = this._align_data; var tm = spen.mode; spen.mode = 3; - this.__endNewFeature(); - this.refresh_data(); + var type = this._draws_data_[--this._path_index].geometry.type; + this._draws_data_ = this._draws_data_.slice(0, this._path_index); + this._redraw(this._align_data,type); + this.refresh_data(); spen.mode = tm; } else this.undo(); }, @@ -1182,6 +1181,14 @@ let dx = this._viewer.viewport.windowToImageCoordinates(pt).x - this._viewer.viewport.windowToImageCoordinates(pt2).x; return dx; }, + _redraw(data, type){ + var t = this.drawMode + this.drawMode = type; + this.__newFeature(data[0]); + this._current_path_.geometry.coordinates[0] = data; + this.__endNewFeature(); + this.drawMode = t; + } }; diff --git a/core/extension/segment-annotation.js b/core/extension/segment-annotation.js new file mode 100644 index 000000000..80211f77e --- /dev/null +++ b/core/extension/segment-annotation.js @@ -0,0 +1,168 @@ +// segmentation display supports toggling, deletion, loading, saving +class segmentationanno{ + constructor(camic, d, ui, slide, deleteCallback){ + this.data = []; //internal storage + this.slide = slide; + this.camic = camic; + this.ui = ui; + this.deleteCallback = deleteCallback; + // popup panels + this.annotPopup = new PopupPanel({ + footer: [{ + title: 'Delete', + class: 'material-icons', + text: 'delete_forever', + callback: this.deleteSegment,},], + }); + camic.viewer.addHandler('canvas-lay-click', e => { + if (!e.data) { + this.annotPopup.close(); + return;} + let data = Array.isArray(e.data) ? e.data[e.data.selected] : e.data; + let attributes = data.properties.annotations; + attributes.area = `${Math.round(data.geometries.features[data.selected].properties.area,)} μm²` + attributes.circumference = `${Math.round(data.geometries.features[data.selected].properties.circumference,)} μm`; + let body = convertHumanAnnotationToPopupBody(attributes); + this.annotPopup.data = { + id: data.provenance.analysis.execution_id, + oid: data._id.$oid, + annotation: attributes, + selected: data.selected, + }; + this.annotPopup.dataType = null; + this.annotPopup.setTitle(`id:${data.provenance.analysis.execution_id}`); + this.annotPopup.setBody(body); + this.annotPopup.showFooter(); + this.annotPopup.open(e.position); + }); + } + saveSegment = (image,data,notes) => { + if(!data.features.length) //empty + return; + if(!data.features[0].geometry.coordinates.length) + return; + data = ImageFeaturesToVieweportFeatures( // scale + this.camic.viewer, data); + // Add new lines to notes to prevent overflow + let str = notes.notes || ''; + var resultString = ''; + while (typeof str==='string' && str.length > 0) { + resultString += str.substring(0, 36) + '\n'; + str = str.substring(36);} + notes.notes = resultString; + notes.name = "segment_"+notes.name; + const execId = notes.name+randomId(); + + const annotJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: this.slide, + }, + analysis: { + source: 'human', + execution_id: execId, + name: notes.name, + }, + }, + properties: { + annotations: notes + }, + geometries:data, + // image:image + }; + this.camic.store + .addMark(annotJson) + .then((data) => { + console.log(data) + this.loadsegmentation({id:execId}, true); + }) + .catch((e) => { + this.ui.message.addError(e); + console.log('save failed', e); + }) + .finally(() => {}); + } + loadsegmentation = (p, isShow) => { + let u = this.data.findIndex(e => (e.id==p.id)); + if(u==-1) this.loadSegById(p.id); + else{ + if(isShow == undefined) isShow = !this.data[u].layer.isShow; + if (!isShow && this.annotPopup.data && this.annotPopup.data.id===this.data[u].id) this.annotPopup.close(); + this.data[u].layer.isShow = isShow; + this.camic.viewer.omanager.updateView(); + } + } + loadSegById(id) { + this.camic.store + .getMarkByIds([id], this.slide, null) + .then((data) => { + // response error + if(data.error || !data[0]){ + if(this.deleteCallback) + this.deleteCallback(id); + return;} + data[0].geometries = VieweportFeaturesToImageFeatures( // scale + this.camic.viewer, data[0].geometries); + let item = {data:data[0]}; + let temp = {id:id,item:item}; + item.id = id; + item.render = this.annoRender; + item.viewer = this.camic.viewer; + // if(item.data.image) // display whole segmentation image + // item.data.image.data = new ImageData(new Uint8ClampedArray(item.data.image.data),item.data.image.width,item.data.image.height); + // preprocess + item.data.geometries.features.forEach((e)=> + {e.properties.style = { + color: '#FFFF00', + lineCap: 'round', + lineJoin: 'round', + isFill: true};}); + // overlays + temp.layer = this.camic.viewer.omanager.addOverlay(item); + this.camic.viewer.omanager.updateView(); + this.data.push(temp); + }) + .catch((e) => { + console.error(e); + }) + .finally(() => { + }); + } + deleteSegment = (p) => { + if (!confirm(`Are You Sure You Want To Delete This Segmentation {ID:${p.id}}?`)) return; + this.data.splice(this.data.findIndex(e => e.id === p.id),1); + this.camic.viewer.omanager.removeOverlay(p.id); + this.annotPopup.close(); + if(this.deleteCallback) + this.deleteCallback(p.id); + this.camic.store + .deleteMarkByExecId(p.id, this.slide) + .then((datas) => { + console.log(datas); + }) + .catch((e) => { + this.ui.message.addError(e); + }) + .finally(() => { + }); + } + annoRender(ctx, data) { + DrawHelper.draw(ctx, data.geometries.features); + } +}; +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; +} From 7b7c6fb32272afcf1dd32995b2b1e71aae7c3d4e Mon Sep 17 00:00:00 2001 From: Ryan Birmingham Date: Fri, 10 Sep 2021 11:29:35 -0400 Subject: [PATCH 02/10] replace gh link with docs link --- apps/landing/landing.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/landing/landing.html b/apps/landing/landing.html index 962c21f66..48a4191e7 100644 --- a/apps/landing/landing.html +++ b/apps/landing/landing.html @@ -97,11 +97,11 @@

    caMicroscope

    - +
    -

    Source Code

    -

    Get and use the code, or file issues on our github repositories.

    - More +

    Documentation

    +

    Read documentation for using and developing caMicroscope.

    + More
    From 0f62340daa1336ab911010552322a6432a0190db Mon Sep 17 00:00:00 2001 From: Ryan Birmingham Date: Wed, 15 Sep 2021 10:09:38 -0400 Subject: [PATCH 03/10] use idlookup for quad urls --- apps/viewer/viewer.html | 13 ++++++++----- common/PathdbMods.js | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/viewer/viewer.html b/apps/viewer/viewer.html index 5c0354c87..59c147eda 100644 --- a/apps/viewer/viewer.html +++ b/apps/viewer/viewer.html @@ -178,7 +178,7 @@ - + @@ -386,13 +386,13 @@
    - +
    - +