diff --git a/package-lock.json b/package-lock.json index a43c4e7a..16add4a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,15 @@ "@popperjs/core": "^2.11.5", "backbone": "^1.4.1", "bootstrap": "^5.2.0", - "bootstrap-icons": "^1.9.1", + "bootstrap-icons": "^1.11.0", "dompurify": "^2.5.4", "jquery": "^3.6.0", "marked": "^4.2.12", "mousetrap": "^1.6.5", "raphael": "^2.3.0", "sortablejs": "^1.15.2", - "underscore": "^1.13.4" + "underscore": "^1.13.4", + "zarrita": "^0.4.0-next.19" }, "devDependencies": { "sass": "^1.54.1", @@ -618,6 +619,40 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@zarrita/core": { + "version": "0.1.0-next.17", + "resolved": "https://registry.npmjs.org/@zarrita/core/-/core-0.1.0-next.17.tgz", + "integrity": "sha512-VTf1KWLz3vqX4IUdg1lJYBpHo/cT3NbpFQ47JOCEaVsmGv09So5yY7UkXPTQ6ef7oy9BFven7sD5EKSutiFn8A==", + "dependencies": { + "@zarrita/storage": "^0.1.0-next.8", + "@zarrita/typedarray": "^0.1.0-next.4", + "numcodecs": "^0.3.2" + } + }, + "node_modules/@zarrita/indexing": { + "version": "0.1.0-next.19", + "resolved": "https://registry.npmjs.org/@zarrita/indexing/-/indexing-0.1.0-next.19.tgz", + "integrity": "sha512-GRu6CxtEeXnZUYR0Z/sEGFPcll7pG2Ek9muUkdpl5U5fUdPW7YaSj1tMBxDFhkgw9MNy87ksnN2VO4Tgjjl5fw==", + "dependencies": { + "@zarrita/core": "^0.1.0-next.17", + "@zarrita/storage": "^0.1.0-next.8", + "@zarrita/typedarray": "^0.1.0-next.4" + } + }, + "node_modules/@zarrita/storage": { + "version": "0.1.0-next.8", + "resolved": "https://registry.npmjs.org/@zarrita/storage/-/storage-0.1.0-next.8.tgz", + "integrity": "sha512-9d8bIaR2JuiG98gg5lF15Tpgc/+G/XOXlY53UtVP0LABwHbrIGFzpO2djzGB5bQe1jDj92VeXE8Z525Bs2vb6Q==", + "dependencies": { + "reference-spec-reader": "^0.2.0", + "unzipit": "^1.4.3" + } + }, + "node_modules/@zarrita/typedarray": { + "version": "0.1.0-next.4", + "resolved": "https://registry.npmjs.org/@zarrita/typedarray/-/typedarray-0.1.0-next.4.tgz", + "integrity": "sha512-sGqc5Ldh8nt/FE9gDA89OsL+FH37wgSxCF1Liv08O8SbZ4w3N48Wngv0EWAXvU1aSEdGhb2ABtSfErVDwnp45Q==" + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -667,9 +702,19 @@ } }, "node_modules/bootstrap-icons": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.9.1.tgz", - "integrity": "sha512-d4ZkO30MIkAhQ2nNRJqKXJVEQorALGbLWTuRxyCTJF96lRIV6imcgMehWGJUiJMJhglN0o2tqLIeDnMdiQEE9g==" + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ] }, "node_modules/braces": { "version": "3.0.3", @@ -758,6 +803,11 @@ "resolved": "https://registry.npmjs.org/eve-raphael/-/eve-raphael-0.5.0.tgz", "integrity": "sha512-jrxnPsCGqng1UZuEp9DecX/AuSyAszATSjf4oEcRxvfxa1Oux4KkIPKBAAWWnpdwfARtr+Q0o9aPYWjsROD7ug==" }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -901,6 +951,14 @@ "node": ">=0.10.0" } }, + "node_modules/numcodecs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/numcodecs/-/numcodecs-0.3.2.tgz", + "integrity": "sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==", + "dependencies": { + "fflate": "^0.8.0" + } + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -967,6 +1025,11 @@ "node": ">=8.10.0" } }, + "node_modules/reference-spec-reader": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz", + "integrity": "sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==" + }, "node_modules/rollup": { "version": "4.22.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", @@ -1050,6 +1113,22 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==" }, + "node_modules/unzipit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", + "integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==", + "dependencies": { + "uzip-module": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + }, "node_modules/vite": { "version": "5.4.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", @@ -1123,6 +1202,16 @@ "bin": { "watch": "cli.js" } + }, + "node_modules/zarrita": { + "version": "0.4.0-next.19", + "resolved": "https://registry.npmjs.org/zarrita/-/zarrita-0.4.0-next.19.tgz", + "integrity": "sha512-7/O3ph+5BGnZ36Bc+DjMym2M1C/xY/klMn4V4N0FpOXFlAsUpvNqFqgYID1p8SdjJNh+aF4QFmuOoxceyz6KKA==", + "dependencies": { + "@zarrita/core": "^0.1.0-next.17", + "@zarrita/indexing": "^0.1.0-next.19", + "@zarrita/storage": "^0.1.0-next.8" + } } } } diff --git a/package.json b/package.json index dc08902b..1c07ebe4 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "mousetrap": "^1.6.5", "raphael": "^2.3.0", "sortablejs": "^1.15.2", - "underscore": "^1.13.4" + "underscore": "^1.13.4", + "zarrita": "^0.4.0-next.19" } } diff --git a/src/js/models/figure_model.js b/src/js/models/figure_model.js index 89c87b47..0da2569a 100644 --- a/src/js/models/figure_model.js +++ b/src/js/models/figure_model.js @@ -508,6 +508,11 @@ // new image panels appropriately in a grid. var invalidIds = []; for (var i=0; i rsp.json()); + let zarrName = zarrUrl.split("/").pop(); + let multiscales = zattrs?.multiscales; + console.log("zarr zattrs", zattrs); + if (!multiscales) { + alert(`Image loading from ${imgDataUrl} included an Error: ${message}`); + return; + } + + // if we got multiscales, load first dataset... + // TODO: handle bioformats2raw.layout + let dsPath = multiscales[0]?.datasets[0].path; + let imgName = multiscales[0].name || zarrName; + let axes = multiscales[0].axes; + let axesNames = axes.map(axis => axis.name); + + let datasets = multiscales[0].datasets; + let zarrays = {}; + // 'consolidate' the metadata for all arrays + for (let ds of datasets) { + let path = ds.path; + let zarray = await fetch(`${zarrUrl}/${path}/.zarray`).then(rsp => rsp.json()); + zarrays[path] = zarray; + } + // store under 'arrays' key + zattrs['arrays'] = zarrays; + + let zarray = zarrays[0]; + console.log("zarray", zarray); + + // e.g. " "uint8" + let dtypeToPixelsType = (dtype) => { + let dt = ""; + if (dtype.includes("u")) { + dt += "uint"; + } else if (dtype.includes("i")) { + dt += "int"; + } else if (dtype.includes("f")) { + dt += "float"; + } + if (dtype.includes("8")) { + dt += "64"; + } else if (dtype.includes("4")) { + dt += "32"; + } else if (dtype.includes("2")) { + dt += "16"; + } else if (dtype.includes("1")) { + dt += "8"; + } + return dt; + } + let dtype = zarray.dtype; + let shape = zarray.shape; + let dims = shape.length; + let sizeX = shape[dims - 1]; + let sizeY = shape[dims - 2]; + let sizeZ = 1; + let sizeT = 1; + if (axesNames.includes('z')) { + sizeZ = shape[axesNames.indexOf('z')] + } + let defaultZ = parseInt(sizeZ / 2); + if (axesNames.includes('t')) { + sizeT = shape[axesNames.indexOf('t')] + } + let defaultT = parseInt(sizeT / 2); + self.set('loading_count', self.get('loading_count') - 1); + + // channels... + // TODO: if no omero data, need to construct channels! + let channels = zattrs.omero?.channels || []; + + coords.spacer = coords.spacer || sizeX/20; + var full_width = (coords.colCount * (sizeX + coords.spacer)) - coords.spacer, + full_height = (coords.rowCount * (sizeY + coords.spacer)) - coords.spacer; + coords.scale = coords.paper_width / (full_width + (2 * coords.spacer)); + coords.scale = Math.min(coords.scale, 1); // only scale down + // For the FIRST IMAGE ONLY (coords.px etc undefined), we + // need to work out where to start (px,py) now that we know size of panel + // (assume all panels are same size) + coords.px = coords.px || coords.c.x - (full_width * coords.scale)/2; + coords.py = coords.py || coords.c.y - (full_height * coords.scale)/2; + + // calculate panel coordinates from index... + var row = parseInt(index / coords.colCount, 10); + var col = index % coords.colCount; + var panelX = coords.px + ((sizeX + coords.spacer) * coords.scale * col); + var panelY = coords.py + ((sizeY + coords.spacer) * coords.scale * row); + + // ****** This is the Data Model ****** + //------------------------------------- + // Any changes here will create a new version + // of the model and will also have to be applied + // to the 'version_transform()' function so that + // older files can be brought up to date. + // Also check 'previewSetId()' for changes. + var n = { + 'imageId': zarrUrl, + 'name': imgName, + 'width': sizeX * coords.scale, + 'height': sizeY * coords.scale, + 'sizeZ': sizeZ, + 'theZ': defaultZ, + 'sizeT': sizeT, + 'theT': defaultT, + 'rdefs': {'model': "-"}, + 'channels': channels, + 'orig_width': sizeX, + 'orig_height': sizeY, + 'x': panelX, + 'y': panelY, + // 'datasetName': data.meta.datasetName, + // 'datasetId': data.meta.datasetId, + // 'pixel_size_x': data.pixel_size.valueX, + // 'pixel_size_y': data.pixel_size.valueY, + // 'pixel_size_z': data.pixel_size.valueZ, + // 'pixel_size_x_symbol': data.pixel_size.symbolX, + // 'pixel_size_z_symbol': data.pixel_size.symbolZ, + // 'pixel_size_x_unit': data.pixel_size.unitX, + // 'pixel_size_z_unit': data.pixel_size.unitZ, + // 'deltaT': data.deltaT, + 'pixelsType': dtypeToPixelsType(dtype), + // 'pixel_range': data.pixel_range, + // let's dump the zarr data into the panel + 'zarr': zattrs, + }; + // create Panel (and select it) + // We do some additional processing in Panel.parse() + self.panels.create(n, {'parse': true}).set('selected', true); + self.notifySelectionChange(); + }, + importImage: function(imgDataUrl, coords, baseUrl, index) { var self = this, diff --git a/src/js/models/panel_model.js b/src/js/models/panel_model.js index 059516cb..7ce73513 100644 --- a/src/js/models/panel_model.js +++ b/src/js/models/panel_model.js @@ -2,6 +2,7 @@ import Backbone from "backbone"; import _ from "underscore"; import $ from "jquery"; + import {renderZarrToSrc} from "./zarr_utils"; // Corresponds to css - allows us to calculate size of labels var LINE_HEIGHT = 1.43; @@ -870,7 +871,14 @@ return this.get('orig_width') * this.get('orig_height') > MAX_PLANE_SIZE; }, - get_img_src: function(force_no_padding) { + get_zarr_img_src: async function() { + return renderZarrToSrc(this.get('imageId'), this.get('zarr'), this.get('theZ'), this.get('theT'), this.get('channels')); + }, + + get_img_src: async function(force_no_padding) { + if (this.get("zarr")) { + return this.get_zarr_img_src(); + } var chs = this.get('channels'); var cStrings = chs.map(function(c, i){ return (c.active ? '' : '-') + (1+i) + "|" + c.window.start + ":" + c.window.end + "$" + c.color; diff --git a/src/js/models/zarr_utils.js b/src/js/models/zarr_utils.js new file mode 100644 index 00000000..9e89f810 --- /dev/null +++ b/src/js/models/zarr_utils.js @@ -0,0 +1,231 @@ + +import * as zarr from "zarrita"; +import { slice } from "@zarrita/indexing"; + +const ZARRITA_ARRAY_CACHE = {}; +const ZARR_DATA_CACHE = {}; + +export async function renderZarrToSrc(source, attrs, theZ, theT, channels) { + let paths = attrs.multiscales[0].datasets.map((d) => d.path); + let axes = attrs.multiscales[0].axes.map((a) => a.name); + let zarrays = attrs.arrays; + + // Pick first resolution that is below a max size... + const MAX_SIZE = 2000; + console.log("Zarr pick size to render..."); + let path; + for (let p of paths) { + let arrayAttrs = zarrays[p]; + console.log(path, arrayAttrs); + let shape = arrayAttrs.shape; + if (shape.at(-1) * shape.at(-2) < MAX_SIZE * MAX_SIZE) { + path = p; + break; + } + } + + if (!path) { + console.error(`Lowest resolution too large for rendering: > ${MAX_SIZE} x ${MAX_SIZE}`); + return; + } + + console.log("Init zarr.FetchStore:", source + "/" + path); + let storeArrayPath = source + "/" + path; + let arr; + if (ZARRITA_ARRAY_CACHE[storeArrayPath]) { + arr = ZARRITA_ARRAY_CACHE[storeArrayPath]; + } else { + let store = new zarr.FetchStore(source + "/" + path); + arr = await zarr.open(store, { kind: "array" }); + ZARRITA_ARRAY_CACHE[storeArrayPath] = arr; + } + + let chDim = axes.indexOf("c"); + let shape = zarrays[path].shape; + let dims = shape.length; + + let activeChIndicies = []; + let colors = []; + let minMaxValues = []; + channels.forEach((ch, index) => { + if (ch.active) { + activeChIndicies.push(index); + colors.push(hexToRGB(ch.color)); + minMaxValues.push([ch.window.start, ch.window.end]); + } + }); + console.log("activeChIndicies", activeChIndicies); + console.log("colors", colors); + console.log("minMaxValues", minMaxValues); + + let promises = activeChIndicies.map((chIndex) => { + let sliceKey = []; + let slices = shape.map((dimSize, index) => { + // channel + if (index == chDim) { + sliceKey.push("" + chIndex); + return chIndex; + } + // x and y + if (index >= dims - 2) { + sliceKey.push(`${0}:${dimSize}`); + return slice(0, dimSize); + } + // z + if (axes[index] == "z") { + sliceKey.push("" + theZ); + return theZ; + } + if (axes[index] == "t") { + sliceKey.push("" + theT); + return theT; + } + return 0; + }); + let cacheKey = `${source}/${path}/${sliceKey.join(",")}`; + console.log("cacheKey", cacheKey); + console.log("Zarr chIndex slices:", chIndex, "" + slices); + console.log("Zarr chIndex shape:", chIndex, shape); + // TODO: add controller: { opts: { signal: controller.signal } } + // check cache! + if (ZARR_DATA_CACHE[cacheKey]) { + console.log("RETURN cache!", ZARR_DATA_CACHE[cacheKey]); + return ZARR_DATA_CACHE[cacheKey]; + } + + return zarr.get(arr, slices).then(data => { + console.log("populate cache..."); + ZARR_DATA_CACHE[cacheKey] = data; + return data; + }); + }); + + let ndChunks = await Promise.all(promises); + // let minMaxValues = ndChunks.map((ch) => getMinMaxValues(ch)); + let rbgData = renderTo8bitArray(ndChunks, minMaxValues, colors); + + let width = shape.at(-1); + let height = shape.at(-2); + + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + const context = canvas.getContext("2d"); + context.putImageData(new ImageData(rbgData, width, height), 0, 0); + let dataUrl = canvas.toDataURL("image/png"); + return dataUrl; +} + + +export function renderTo8bitArray(ndChunks, minMaxValues, colors) { + // Render chunks (array) into 2D 8-bit data for new ImageData(arr) + // ndChunks is list of zarr arrays + + // assume all chunks are same shape + const shape = ndChunks[0].shape; + const height = shape[0]; + const width = shape[1]; + const pixels = height * width; + + if (!minMaxValues) { + minMaxValues = ndChunks.map(getMinMaxValues); + } + + // let rgb = [255, 255, 255]; + + let rgba = new Uint8ClampedArray(4 * height * width).fill(0); + let offset = 0; + for (let y = 0; y < pixels; y++) { + for (let p = 0; p < ndChunks.length; p++) { + let rgb = colors[p]; + let data = ndChunks[p].data; + let range = minMaxValues[p]; + let rawValue = data[y]; + let fraction = (rawValue - range[0]) / (range[1] - range[0]); + // for red, green, blue, + for (let i = 0; i < 3; i++) { + // rgb[i] is 0-255... + let v = (fraction * rgb[i]) << 0; + // increase pixel intensity if value is higher + rgba[offset * 4 + i] = Math.max(rgba[offset * 4 + i], v); + } + } + rgba[offset * 4 + 3] = 255; // alpha + offset += 1; + } + + return rgba; +} + +export function getMinMaxValues(chunk2d) { + const data = chunk2d.data; + let maxV = 0; + let minV = Infinity; + let length = chunk2d.data.length; + for (let y = 0; y < length; y++) { + let rawValue = data[y]; + maxV = Math.max(maxV, rawValue); + minV = Math.min(minV, rawValue); + } + return [minV, maxV]; +} + +export const MAX_CHANNELS = 4; +export const COLORS = { + cyan: "#00FFFF", + yellow: "#FFFF00", + magenta: "#FF00FF", + red: "#FF0000", + green: "#00FF00", + blue: "#0000FF", + white: "#FFFFFF", +}; +export const MAGENTA_GREEN = [COLORS.magenta, COLORS.green]; +export const RGB = [COLORS.red, COLORS.green, COLORS.blue]; +export const CYMRGB = Object.values(COLORS).slice(0, -2); + +export function getDefaultVisibilities(n) { + let visibilities; + if (n <= MAX_CHANNELS) { + // Default to all on if visibilities not specified and less than 6 channels. + visibilities = Array(n).fill(true); + } else { + // If more than MAX_CHANNELS, only make first set on by default. + visibilities = [ + ...Array(MAX_CHANNELS).fill(true), + ...Array(n - MAX_CHANNELS).fill(false), + ]; + } + return visibilities; +} + +export function getDefaultColors(n, visibilities) { + let colors = []; + if (n == 1) { + colors = [COLORS.white]; + } else if (n == 2) { + colors = MAGENTA_GREEN; + } else if (n === 3) { + colors = RGB; + } else if (n <= MAX_CHANNELS) { + colors = CYMRGB.slice(0, n); + } else { + // Default color for non-visible is white + colors = Array(n).fill(COLORS.white); + // Get visible indices + const visibleIndices = visibilities.flatMap((bool, i) => (bool ? i : [])); + // Set visible indices to CYMRGB colors. visibleIndices.length === MAX_CHANNELS from above. + for (const [i, visibleIndex] of visibleIndices.entries()) { + colors[visibleIndex] = CYMRGB[i]; + } + } + return colors.map(hexToRGB); +} + +export function hexToRGB(hex) { + if (hex.startsWith("#")) hex = hex.slice(1); + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + return [r, g, b]; +} diff --git a/src/js/views/crop_modal_view.js b/src/js/views/crop_modal_view.js index e323ad3e..01b3abca 100644 --- a/src/js/views/crop_modal_view.js +++ b/src/js/views/crop_modal_view.js @@ -513,7 +513,6 @@ export const CropModalView = Backbone.View.extend({ let rotation = rect.rotation || 0; if (rect.theT > -1) this.m.set('theT', rect.theT, {'silent': true}); if (rect.theZ > -1) this.m.set('theZ', rect.theZ, {'silent': true}); - src = this.m.get_img_src(true); if (rect.width > rect.height) { div_w = size; div_h = (rect.height/rect.width) * div_w; @@ -529,8 +528,12 @@ export const CropModalView = Backbone.View.extend({ rect.theT = rect.theT !== undefined ? rect.theT : origT; rect.theZ = rect.theZ !== undefined ? rect.theZ : origZ; let css = this.m._viewport_css(left, top, img_w, img_h, size, size, rotation); + let random_id = "rect_" + Math.random(); + this.m.get_img_src(true) + .then(src => document.getElementById(random_id).src = src); var json = { + 'id': random_id, 'msg': msg, 'src': src, 'rect': rect, @@ -586,15 +589,15 @@ export const CropModalView = Backbone.View.extend({ this.m.set('zoom', 100); this.m.set('width', newW); this.m.set('height', newH); - var src = this.m.get_img_src(true); + this.m.get_img_src(true) + .then(src => this.$cropImg.attr('src', src)); var css = this.m.get_vp_full_plane_css(100, newW, newH); this.paper.setSize(newW, newH); $("#crop_paper").css({'height': newH, 'width': newW}); $("#cropViewer").css({'height': newH, 'width': newW}); - this.$cropImg.css(css) - .attr('src', src); + this.$cropImg.css(css); var roiX = this.currentROI.x * scale, roiY = this.currentROI.y * scale, diff --git a/src/js/views/panel_view.js b/src/js/views/panel_view.js index 3a8b01a4..8181b6da 100644 --- a/src/js/views/panel_view.js +++ b/src/js/views/panel_view.js @@ -191,8 +191,8 @@ this.$img_panel.show(); }.bind(this)); - var src = this.model.get_img_src(); - this.$img_panel.attr('src', src); + this.model.get_img_src() + .then(src => this.$img_panel.attr('src', src)); // if a 'reasonable' dpi is set, we don't pixelate if (this.model.get('min_export_dpi') > 100) { diff --git a/src/js/views/right_panel_view.js b/src/js/views/right_panel_view.js index 35558b4f..523bf13a 100644 --- a/src/js/views/right_panel_view.js +++ b/src/js/views/right_panel_view.js @@ -1043,9 +1043,14 @@ var frame_h = this.full_size / wh; } this.models.forEach(function(m){ - var src = m.get_img_src(); + var img_id = "image_" + Math.random(); + // src is async, so we set an id instead. When src returns we + // use the ID to find the element and set the src. + m.get_img_src().then(src => { + document.getElementById(img_id).src = src; + }); var img_css = m.get_vp_img_css(m.get('zoom'), frame_w, frame_h); - img_css.src = src; + img_css.id = img_id; // if a 'reasonable' dpi is set, we don't pixelate var dpiSet = m.get('min_export_dpi') > 100; img_css.pixelated = !dpiSet; diff --git a/src/js/views/roi_modal_view.js b/src/js/views/roi_modal_view.js index 883549cf..639779d0 100644 --- a/src/js/views/roi_modal_view.js +++ b/src/js/views/roi_modal_view.js @@ -466,48 +466,9 @@ export const RoiModalView = Backbone.View.extend({ $("#roiModalTip").show().html(tip); }, - // for rendering bounding-box viewports for shapes - getBboxJson: function(bbox, theZ, theT) { - var size = 50; // longest side - var orig_width = this.m.get('orig_width'), - orig_height = this.m.get('orig_height'); - // origT = this.m.get('theT'), - // origZ = this.m.get('theZ'); - // theT = (theT !== undefined ? theT : this.m.get('theT')) - var div_w, div_h; - // get src for image by temp setting Z & T - if (theT !== undefined) this.m.set('theT', bbox.theT, {'silent': true}); - if (theZ !== undefined) this.m.set('theZ', bbox.theZ, {'silent': true}); - var src = this.m.get_img_src(); - if (bbox.width > bbox.height) { - div_w = size; - div_h = (bbox.height/bbox.width) * div_w; - } else { - div_h = size; - div_w = (bbox.width/bbox.height) * div_h; - } - var zoom = div_w/bbox.width; - var img_w = orig_width * zoom; - var img_h = orig_height * zoom; - var top = -(zoom * bbox.y); - var left = -(zoom * bbox.x); - // bbox.theT = bbox.theT !== undefined ? bbox.theT : origT; - // bbox.theZ = bbox.theZ !== undefined ? bbox.theZ : origZ; - - return { - 'src': src, - 'w': div_w, - 'h': div_h, - 'top': top, - 'left': left, - 'img_w': img_w, - 'img_h': img_h - }; - }, - renderImagePlane: function() { - var src = this.m.get_img_src(); - this.$roiImg.attr('src', src); + this.m.get_img_src() + .then(src => this.$roiImg.attr('src', src)); var orig_model = this.model.getSelected().head(); var json = {'theZ': this.m.get('theZ'), diff --git a/src/templates/modal_dialogs/crop_modal_roi.template.html b/src/templates/modal_dialogs/crop_modal_roi.template.html index 53ecab39..1901cb50 100644 --- a/src/templates/modal_dialogs/crop_modal_roi.template.html +++ b/src/templates/modal_dialogs/crop_modal_roi.template.html @@ -2,7 +2,8 @@
- -