feat(thumbnails) Use cornerstone.renderToCanvas for Series Thumbnails
This commit is contained in:
parent
5373963176
commit
e474fdff2c
8579
Packages/ohif-cornerstone/imports/both/cornerstone.js
Normal file
8579
Packages/ohif-cornerstone/imports/both/cornerstone.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,5 +1,8 @@
|
|||||||
import Hammer from 'hammerjs';
|
import Hammer from 'hammerjs';
|
||||||
import * as cornerstone from 'cornerstone-core/dist/cornerstone.js';
|
// TODO: [PWV-379] delete local cornerstone.js and switch back to the npm library after
|
||||||
|
// merging https://github.com/cornerstonejs/cornerstone/pull/2
|
||||||
|
// import * as cornerstone from 'cornerstone-core/dist/cornerstone.js';
|
||||||
|
import * as cornerstone from './cornerstone.js';
|
||||||
import * as cornerstoneMath from 'cornerstone-math/dist/cornerstoneMath.js';
|
import * as cornerstoneMath from 'cornerstone-math/dist/cornerstoneMath.js';
|
||||||
import * as cornerstoneTools from 'cornerstone-tools/dist/cornerstoneTools.js';
|
import * as cornerstoneTools from 'cornerstone-tools/dist/cornerstoneTools.js';
|
||||||
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader/dist/cornerstoneWADOImageLoader.js';
|
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader/dist/cornerstoneWADOImageLoader.js';
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
<template name="imageThumbnail">
|
<template name="imageThumbnail">
|
||||||
<div class="imageThumbnail">
|
<div class="imageThumbnail">
|
||||||
<div class="imageThumbnailCanvas"></div>
|
<div class="imageThumbnailCanvas">
|
||||||
<div class="imageThumbnailLoadingIndicator thumbnailLoadingIndicator">
|
<img class="static-image"/>
|
||||||
|
</div>
|
||||||
|
<div class="imageThumbnailLoadingIndicator
|
||||||
|
thumbnailLoadingIndicator
|
||||||
|
{{ #if isLoading }}d-block{{/if}}">
|
||||||
<p>Loading {{percentComplete}}</p>
|
<p>Loading {{percentComplete}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="imageThumbnailErrorLoadingIndicator thumbnailLoadingIndicator">
|
<div class="imageThumbnailErrorLoadingIndicator
|
||||||
|
thumbnailLoadingIndicator
|
||||||
|
{{ #if hasLoadingError }}d-block{{/if}}">
|
||||||
<p>Error</p>
|
<p>Error</p>
|
||||||
</div>
|
</div>
|
||||||
{{#if showStackLoadingProgressBar}}
|
{{#if showStackLoadingProgressBar}}
|
||||||
|
|||||||
@ -1,13 +1,34 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { Template } from 'meteor/templating';
|
import { Template } from 'meteor/templating';
|
||||||
import { Tracker } from 'meteor/tracker';
|
|
||||||
import { Session } from 'meteor/session';
|
import { Session } from 'meteor/session';
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import { cornerstone } from 'meteor/ohif:cornerstone';
|
import { cornerstone } from 'meteor/ohif:cornerstone';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous wrapper around Cornerstone's renderToCanvas method.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} canvasElement An HTML <canvas> element
|
||||||
|
* @param {Image} image A Cornerstone Image
|
||||||
|
*
|
||||||
|
* @return {Promise} A promise tracking the progress of the rendering. Resolves empty.
|
||||||
|
*/
|
||||||
|
function renderAsync(canvasElement, image) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
cornerstone.renderToCanvas(canvasElement, image);
|
||||||
|
resolve();
|
||||||
|
} catch(error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Template.imageThumbnail.onCreated(() => {
|
Template.imageThumbnail.onCreated(() => {
|
||||||
const instance = Template.instance();
|
const instance = Template.instance();
|
||||||
|
|
||||||
|
instance.isLoading = new ReactiveVar(false);
|
||||||
|
instance.hasLoadingError = new ReactiveVar(false);
|
||||||
|
|
||||||
// Get the image ID for current thumbnail
|
// Get the image ID for current thumbnail
|
||||||
instance.getThumbnailImageId = () => {
|
instance.getThumbnailImageId = () => {
|
||||||
const settingPath = 'public.ui.useMiddleSeriesInstanceAsThumbnail';
|
const settingPath = 'public.ui.useMiddleSeriesInstanceAsThumbnail';
|
||||||
@ -33,69 +54,42 @@ Template.imageThumbnail.onRendered(() => {
|
|||||||
|
|
||||||
// Declare DOM and jQuery objects
|
// Declare DOM and jQuery objects
|
||||||
const $parent = instance.$('.imageThumbnail');
|
const $parent = instance.$('.imageThumbnail');
|
||||||
const $loading = $parent.find('.imageThumbnailLoadingIndicator');
|
const $thumbnailElement = $parent.find('.imageThumbnailCanvas');
|
||||||
const $loadingError = $parent.find('.imageThumbnailErrorLoadingIndicator');
|
|
||||||
const $element = $parent.find('.imageThumbnailCanvas');
|
|
||||||
const element = $element.get(0);
|
|
||||||
|
|
||||||
instance.refreshImage = () => {
|
instance.refreshImage = () => {
|
||||||
if (!element) {
|
const imageElement = $thumbnailElement.find('img').get(0);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove previously generated static images
|
|
||||||
$element.find('.static-image').remove();
|
|
||||||
|
|
||||||
// Enable cornerstone for thumbnail element again creating a new canvas
|
|
||||||
cornerstone.enable(element, { renderer: '' });
|
|
||||||
|
|
||||||
// Activate the loading state
|
// Activate the loading state
|
||||||
$loading.css('display', 'block');
|
instance.isLoading.set(true);
|
||||||
|
instance.hasLoadingError.set(false);
|
||||||
|
|
||||||
|
// Clear the previous image
|
||||||
|
imageElement.removeAttribute('src');
|
||||||
|
|
||||||
// Define a handler for success on image load
|
// Define a handler for success on image load
|
||||||
const loadSuccess = image => {
|
const loadSuccess = image => {
|
||||||
// Check to make sure the element is enabled.
|
// This is an off-screen canvas. It's used to get dataURL images by using
|
||||||
try {
|
// cornerstone.renderToCanvas function.
|
||||||
cornerstone.getEnabledElement(element);
|
const canvasElement = document.createElement('canvas');
|
||||||
} catch(error) {
|
canvasElement.width = 193;
|
||||||
return;
|
canvasElement.height = 123;
|
||||||
}
|
|
||||||
|
|
||||||
cornerstone.displayImage(element, image);
|
// Render the image to canvas to be able to get its dataURL
|
||||||
|
renderAsync(canvasElement, image).then(() => {
|
||||||
|
instance.isLoading.set(false);
|
||||||
|
|
||||||
$element.one('cornerstoneimagerendered', event => {
|
imageElement.src = canvasElement.toDataURL('image/jpeg', 1);
|
||||||
const eventData = event.originalEvent.detail;
|
|
||||||
const { element } = eventData;
|
|
||||||
const enabledElement = cornerstone.getEnabledElement(element);
|
|
||||||
|
|
||||||
// Create a static image from
|
|
||||||
const imageElement = document.createElement('img');
|
|
||||||
imageElement.classList.add('static-image');
|
|
||||||
const dataUrl = enabledElement.canvas.toDataURL('image/jpeg', 1);
|
|
||||||
imageElement.src = dataUrl;
|
|
||||||
|
|
||||||
// Try to disable cornerstone for thumbnail element and remove its canvas
|
|
||||||
try {
|
|
||||||
cornerstone.disable(element);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
$element.append(imageElement);
|
|
||||||
|
|
||||||
$loading.css('display', 'none');
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define a handler for error on image load
|
// Define a handler for error on image load
|
||||||
const loadError = () => {
|
const loadError = () => {
|
||||||
$loading.css('display', 'none');
|
instance.isLoading.set(false);
|
||||||
$loadingError.css('display', 'block');
|
instance.hasLoadingError.set(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the image ID
|
|
||||||
const imageId = instance.getThumbnailImageId();
|
|
||||||
|
|
||||||
// Call cornerstone image loader with the defined handlers
|
// Call cornerstone image loader with the defined handlers
|
||||||
cornerstone.loadAndCacheImage(imageId).then(loadSuccess, loadError);
|
cornerstone.loadAndCacheImage(instance.imageId).then(loadSuccess, loadError);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run this computation every time the current study is changed
|
// Run this computation every time the current study is changed
|
||||||
@ -109,32 +103,24 @@ Template.imageThumbnail.onRendered(() => {
|
|||||||
// Depend on external data and re-run this computation when it changes
|
// Depend on external data and re-run this computation when it changes
|
||||||
Template.currentData();
|
Template.currentData();
|
||||||
|
|
||||||
// Wait for the new data and refresh the image thumbnail
|
// Get the image ID. If it is the same as the currently rendered imageId,
|
||||||
Tracker.afterFlush(() => instance.refreshImage());
|
// refresh the image.
|
||||||
|
const imageId = instance.getThumbnailImageId();
|
||||||
|
if (imageId !== instance.imageId) {
|
||||||
|
instance.imageId = imageId;
|
||||||
|
|
||||||
|
instance.refreshImage();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.imageThumbnail.onDestroyed(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
|
|
||||||
// Declare DOM and jQuery objects
|
|
||||||
const $parent = instance.$('.imageThumbnail');
|
|
||||||
const $element = $parent.find('.imageThumbnailCanvas');
|
|
||||||
const element = $element.get(0);
|
|
||||||
|
|
||||||
// Try to disable the element if it still exists
|
|
||||||
try {
|
|
||||||
cornerstone.disable(element);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.imageThumbnail.helpers({
|
Template.imageThumbnail.helpers({
|
||||||
// Executed every time the thumbnail image loading progress is changed
|
// Executed every time the thumbnail image loading progress is changed
|
||||||
percentComplete() {
|
percentComplete() {
|
||||||
const instance = Template.instance();
|
const instance = Template.instance();
|
||||||
|
|
||||||
// Get the encoded image ID for thumbnail
|
// Get the encoded image ID for thumbnail
|
||||||
const encodedImageId = OHIF.string.encodeId(instance.getThumbnailImageId());
|
const encodedImageId = OHIF.string.encodeId(instance.imageId);
|
||||||
|
|
||||||
// Register a dependency from this computation on Session key
|
// Register a dependency from this computation on Session key
|
||||||
const percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + encodedImageId);
|
const percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + encodedImageId);
|
||||||
@ -147,16 +133,20 @@ Template.imageThumbnail.helpers({
|
|||||||
|
|
||||||
// Return how much the stack has already loaded
|
// Return how much the stack has already loaded
|
||||||
stackPercentComplete() {
|
stackPercentComplete() {
|
||||||
const instance = Template.instance();
|
const stack = Template.instance().data.thumbnail.stack;
|
||||||
const stack = instance.data.thumbnail.stack;
|
const progress = Session.get(`StackProgress:${stack.displaySetInstanceUid}`);
|
||||||
const displaySetInstanceUid = stack.displaySetInstanceUid;
|
return progress && progress.percentComplete;
|
||||||
const progress = Session.get('StackProgress:' + displaySetInstanceUid);
|
|
||||||
const percentComplete = progress && progress.percentComplete;
|
|
||||||
|
|
||||||
return percentComplete;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showStackLoadingProgressBar() {
|
showStackLoadingProgressBar() {
|
||||||
return OHIF.uiSettings.showStackLoadingProgressBar;
|
return OHIF.uiSettings.showStackLoadingProgressBar;
|
||||||
|
},
|
||||||
|
|
||||||
|
isLoading() {
|
||||||
|
return Template.instance().isLoading.get();
|
||||||
|
},
|
||||||
|
|
||||||
|
hasLoadingError() {
|
||||||
|
return Template.instance().hasLoadingError.get();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "{ohif:design}/app"
|
@require '{ohif:design}/app'
|
||||||
|
|
||||||
.thumbnailEntry.active .imageThumbnail
|
.thumbnailEntry.active .imageThumbnail
|
||||||
theme('border-color', '$activeColor')
|
theme('border-color', '$activeColor')
|
||||||
@ -47,6 +47,9 @@
|
|||||||
margin: auto
|
margin: auto
|
||||||
position: absolute
|
position: absolute
|
||||||
|
|
||||||
|
&.d-block
|
||||||
|
display: block;
|
||||||
|
|
||||||
p
|
p
|
||||||
text-align: center
|
text-align: center
|
||||||
font-size: 10pt
|
font-size: 10pt
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
{{#let stack=this.thumbnail.stack}}
|
{{#let stack=this.thumbnail.stack}}
|
||||||
<div class="thumbnailEntry noselect m-t-1 m-b-2 {{draggableClass}} {{#if isDisplaySetActive stack.displaySetInstanceUid viewportIndex}}active{{/if}}">
|
<div class="thumbnailEntry noselect m-t-1 m-b-2 {{draggableClass}} {{#if isDisplaySetActive stack.displaySetInstanceUid viewportIndex}}active{{/if}}">
|
||||||
<div class="p-x-1">
|
<div class="p-x-1">
|
||||||
{{>imageThumbnail (clone this)}}
|
{{>imageThumbnail thumbnail=thumbnail currentStudy=currentStudy}}
|
||||||
</div>
|
</div>
|
||||||
<div class="seriesDetails flex-h m-x-1 {{#unless stack.seriesDescription}}info-only{{/unless}}">
|
<div class="seriesDetails flex-h m-x-1 {{#unless stack.seriesDescription}}info-only{{/unless}}">
|
||||||
<div class="seriesDescription flex-grow">
|
<div class="seriesDescription flex-grow">
|
||||||
|
|||||||
@ -74,7 +74,7 @@ Template.studyTimepointBrowser.onRendered(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
instance.autorun(() => {
|
instance.autorun(() => {
|
||||||
// Runs this computation every time the curenty study is changed
|
// Runs this computation every time the current study is changed
|
||||||
const currentStudy = instance.data.currentStudy && instance.data.currentStudy.get();
|
const currentStudy = instance.data.currentStudy && instance.data.currentStudy.get();
|
||||||
|
|
||||||
// Stop here if there's no current study set
|
// Stop here if there's no current study set
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user