feat(thumbnails) Use cornerstone.renderToCanvas for Series Thumbnails

This commit is contained in:
Bruno Alves de Faria 2018-04-30 16:27:02 -03:00 committed by Erik Ziegler
parent 5373963176
commit e474fdff2c
7 changed files with 8659 additions and 78 deletions

File diff suppressed because one or more lines are too long

View File

@ -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';

View File

@ -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}}

View File

@ -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();
} }
}); });

View File

@ -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

View File

@ -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">

View File

@ -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