refactor: improve code readability and performance in viewer.js
This commit is contained in:
+114
-86
@@ -566,16 +566,6 @@ export function keydown_handler(event) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* View cornstone image
|
|
||||||
* @param {*} t
|
|
||||||
* @param {*} source
|
|
||||||
*/
|
|
||||||
export function openMainImage(current_question, t, source) {
|
|
||||||
/**
|
|
||||||
* Load image
|
|
||||||
* @param {*} images - list of images
|
|
||||||
*/
|
|
||||||
function loadAnnotation(imageId, annotation) {
|
function loadAnnotation(imageId, annotation) {
|
||||||
const toolStateManager =
|
const toolStateManager =
|
||||||
cornerstoneTools.globalImageIdSpecificToolStateManager;
|
cornerstoneTools.globalImageIdSpecificToolStateManager;
|
||||||
@@ -592,67 +582,58 @@ export function openMainImage(current_question, t, source) {
|
|||||||
toolStateManager.restoreToolState(tool_state);
|
toolStateManager.restoreToolState(tool_state);
|
||||||
}
|
}
|
||||||
async function load(images, annotations) {
|
async function load(images, annotations) {
|
||||||
//console.debug("Load function", images)
|
// Parallelize image preparation and annotation loading
|
||||||
const imageIds = [];
|
const imageTasks = images.map(async (data_url, i) => {
|
||||||
for (let i = 0; i < images.length; i++) {
|
|
||||||
const data_url = images[i];
|
|
||||||
const annotation = annotations[i];
|
const annotation = annotations[i];
|
||||||
|
let imageId;
|
||||||
// check stack type
|
|
||||||
if (data_url.startsWith("data:image")) {
|
if (data_url.startsWith("data:image")) {
|
||||||
const imageId = "base64://" + data_url.split(",")[1];
|
imageId = "base64://" + data_url.split(",")[1];
|
||||||
|
|
||||||
loadAnnotation(imageId, annotation);
|
|
||||||
|
|
||||||
imageIds.push(imageId);
|
|
||||||
//} else if (data_url.startsWith("data:application/dicom")) {
|
|
||||||
} else if (data_url.startsWith("data:")) {
|
} else if (data_url.startsWith("data:")) {
|
||||||
// stack = stack.split(";")[1];
|
|
||||||
|
|
||||||
const dfile = await urltoFile(data_url, "dicom", "application/dicom");
|
const dfile = await urltoFile(data_url, "dicom", "application/dicom");
|
||||||
|
imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(dfile);
|
||||||
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(
|
|
||||||
dfile
|
|
||||||
);
|
|
||||||
|
|
||||||
loadAnnotation(imageId, annotation);
|
|
||||||
|
|
||||||
imageIds.push(imageId);
|
|
||||||
// cornerstone.loadImage(imageId).then(function(image) {
|
|
||||||
// tempFunction(image);
|
|
||||||
// });
|
|
||||||
} else {
|
} else {
|
||||||
let url = data_url;
|
let url = data_url;
|
||||||
//if (data_url.startsWith("http")) {
|
|
||||||
// url = data_url;
|
|
||||||
//} else {
|
|
||||||
// url = window.location.href.replace(/\/\#\/?$/, "") + "/" + data_url;
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (url.endsWith("dcm")) {
|
if (url.endsWith("dcm")) {
|
||||||
url = "wadouri:" + url;
|
url = "wadouri:" + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is no extension treat it as a dicom
|
|
||||||
if (/(?:\/|^)[^.\/]+$/.test(url)) {
|
if (/(?:\/|^)[^.\/]+$/.test(url)) {
|
||||||
url = "wadouri:" + url;
|
url = "wadouri:" + url;
|
||||||
}
|
}
|
||||||
//console.debug(url)
|
imageId = url;
|
||||||
|
|
||||||
loadAnnotation(url, annotation);
|
|
||||||
|
|
||||||
imageIds.push(url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return {imageId, annotation};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for all image tasks to complete
|
||||||
|
const imageResults = await Promise.all(imageTasks);
|
||||||
|
const imageIds = imageResults.map(r => r.imageId);
|
||||||
|
|
||||||
|
// Display the first image as soon as possible
|
||||||
|
cornerstone.loadAndCacheImage(imageIds[0]).then(function(image) {
|
||||||
const stack = {
|
const stack = {
|
||||||
currentImageIdIndex: 0,
|
currentImageIdIndex: 0,
|
||||||
imageIds,
|
imageIds,
|
||||||
};
|
};
|
||||||
cornerstone.loadAndCacheImage(imageIds[0]).then(function (image) {
|
|
||||||
loadMainImage(image, stack);
|
loadMainImage(image, stack);
|
||||||
|
// Restore annotations for all images after display
|
||||||
|
imageResults.forEach(({imageId, annotation}) => {
|
||||||
|
loadAnnotation(imageId, annotation);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View cornstone image
|
||||||
|
* @param {*} t
|
||||||
|
* @param {*} source
|
||||||
|
*/
|
||||||
|
export function openMainImage(current_question, t, source) {
|
||||||
|
/**
|
||||||
|
* Load image
|
||||||
|
* @param {*} images - list of images
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
if (current_question) {
|
if (current_question) {
|
||||||
// Check if the figure is already loaded (or if another one is)
|
// Check if the figure is already loaded (or if another one is)
|
||||||
const open_figure = $("#dicom-image").data("figure");
|
const open_figure = $("#dicom-image").data("figure");
|
||||||
@@ -680,44 +661,91 @@ export function openMainImage(current_question, t, source) {
|
|||||||
|
|
||||||
source.className = "figure-open";
|
source.className = "figure-open";
|
||||||
|
|
||||||
console.log(current_question, figure_to_load, source);
|
|
||||||
let image_title = source.getElementsByClassName("figcaption")[0].textContent;
|
let image_title = source.getElementsByClassName("figcaption")[0].textContent;
|
||||||
|
|
||||||
$(".question").prepend(
|
// Native DOM creation for performance
|
||||||
`<div class="canvas-panel">
|
const questionElem = document.querySelector('.question');
|
||||||
<div id="viewer-title-bar">
|
const canvasPanel = document.createElement('div');
|
||||||
<div style="flex: 1 1 auto;">
|
canvasPanel.className = 'canvas-panel';
|
||||||
<div id="viewer-title" style="color: white;">${image_title}</div>
|
|
||||||
</div>
|
const viewerTitleBar = document.createElement('div');
|
||||||
<select title="SELECT CLICK ACTION" class="dicom-select-control title-bar-button" style="margin-right: 8px;">
|
viewerTitleBar.id = 'viewer-title-bar';
|
||||||
<option value="pan">pan [p]</option>
|
|
||||||
<option value="zoom">zoom [z]</option>
|
const flexDiv = document.createElement('div');
|
||||||
<option value="rotate">rotate [r]</option>
|
flexDiv.style.flex = '1 1 auto';
|
||||||
<option value="window">window (128 ± 127.5) [w]</option>
|
|
||||||
<option value="measure">measure [m]</option>
|
const viewerTitle = document.createElement('div');
|
||||||
<option value="ellipse">ellipse [l]</option>
|
viewerTitle.id = 'viewer-title';
|
||||||
<option value="rectangle">rectangle [c]</option>
|
viewerTitle.style.color = 'white';
|
||||||
<option value="reset">reset [e]</option>
|
viewerTitle.textContent = image_title;
|
||||||
<option value="notes" disabled="true">[modality = SC]</option>
|
flexDiv.appendChild(viewerTitle);
|
||||||
</select>
|
viewerTitleBar.appendChild(flexDiv);
|
||||||
<button id="reset-viewer-button" class="title-bar-button" title="RESET VIEW" class="reset" style="margin-right: 8px;"><span class="viewer-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2z"/>
|
// Select control
|
||||||
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466"/>
|
const select = document.createElement('select');
|
||||||
</svg></span></button>
|
select.title = 'SELECT CLICK ACTION';
|
||||||
<button id="fullscreen-viewer-button" class="title-bar-button" title="TOGGLE FULLSCREEN" class="expand" style="margin-right: 8px;"><span class="viewer-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fullscreen" viewBox="0 0 16 16">
|
select.className = 'dicom-select-control title-bar-button';
|
||||||
<path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5"/>
|
select.style.marginRight = '8px';
|
||||||
</svg></span></button>
|
const options = [
|
||||||
<button style="display:none; margin-right: 8px;" id="disable-fullscreen-viewer-button" class="title-bar-button" title="TOGGLE FULLSCREEN" class="expand"><span class="viewer-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fullscreen-exit" viewBox="0 0 16 16">
|
{ value: 'pan', text: 'pan [p]' },
|
||||||
<path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5m5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5M0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5m10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0z"/>
|
{ value: 'zoom', text: 'zoom [z]' },
|
||||||
</svg></span></button>
|
{ value: 'rotate', text: 'rotate [r]' },
|
||||||
<button id="close-viewer-button" class="title-bar-button" title="CLOSE VIEWER"><span class="viewer-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
|
{ value: 'window', text: 'window (128 ± 127.5) [w]' },
|
||||||
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
|
{ value: 'measure', text: 'measure [m]' },
|
||||||
</svg></span></button>
|
{ value: 'ellipse', text: 'ellipse [l]' },
|
||||||
</div>
|
{ value: 'rectangle', text: 'rectangle [c]' },
|
||||||
<div id="dicom-image" data-figure="` +
|
{ value: 'reset', text: 'reset [e]' },
|
||||||
figure_to_load +
|
{ value: 'notes', text: '[modality = SC]', disabled: true }
|
||||||
'"></div></div>'
|
];
|
||||||
);
|
options.forEach(opt => {
|
||||||
|
const o = document.createElement('option');
|
||||||
|
o.value = opt.value;
|
||||||
|
o.textContent = opt.text;
|
||||||
|
if (opt.disabled) o.disabled = true;
|
||||||
|
select.appendChild(o);
|
||||||
|
});
|
||||||
|
viewerTitleBar.appendChild(select);
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
function createButton(id, title, svg, style = '') {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.id = id;
|
||||||
|
btn.className = 'title-bar-button';
|
||||||
|
btn.title = title;
|
||||||
|
btn.style.marginRight = '8px';
|
||||||
|
if (style) btn.style.display = style;
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'viewer-icon';
|
||||||
|
span.innerHTML = svg;
|
||||||
|
btn.appendChild(span);
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
// SVGs
|
||||||
|
const svgReset = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2z"/><path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466"/></svg>`;
|
||||||
|
const svgFullscreen = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fullscreen" viewBox="0 0 16 16"><path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0-.5-.5v-4a.5.5 0 0 1 .5-.5"/></svg>`;
|
||||||
|
const svgFullscreenExit = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fullscreen-exit" viewBox="0 0 16 16"><path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5m5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5M0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5m10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0z"/></svg>`;
|
||||||
|
const svgClose = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16"><path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/></svg>`;
|
||||||
|
|
||||||
|
const btnReset = createButton('reset-viewer-button', 'RESET VIEW', svgReset);
|
||||||
|
const btnFullscreen = createButton('fullscreen-viewer-button', 'TOGGLE FULLSCREEN', svgFullscreen);
|
||||||
|
const btnFullscreenExit = createButton('disable-fullscreen-viewer-button', 'TOGGLE FULLSCREEN', svgFullscreenExit, 'none');
|
||||||
|
const btnClose = createButton('close-viewer-button', 'CLOSE VIEWER', svgClose);
|
||||||
|
|
||||||
|
viewerTitleBar.appendChild(btnReset);
|
||||||
|
viewerTitleBar.appendChild(btnFullscreen);
|
||||||
|
viewerTitleBar.appendChild(btnFullscreenExit);
|
||||||
|
viewerTitleBar.appendChild(btnClose);
|
||||||
|
|
||||||
|
canvasPanel.appendChild(viewerTitleBar);
|
||||||
|
|
||||||
|
// DICOM image container
|
||||||
|
const dicomImageDiv = document.createElement('div');
|
||||||
|
dicomImageDiv.id = 'dicom-image';
|
||||||
|
dicomImageDiv.setAttribute('data-figure', figure_to_load);
|
||||||
|
canvasPanel.appendChild(dicomImageDiv);
|
||||||
|
|
||||||
|
// Prepend to question
|
||||||
|
questionElem.insertBefore(canvasPanel, questionElem.firstChild);
|
||||||
|
|
||||||
let images = current_question.images[figure_to_load.split("-")[1]];
|
let images = current_question.images[figure_to_load.split("-")[1]];
|
||||||
// images = current_question.images
|
// images = current_question.images
|
||||||
|
|||||||
Reference in New Issue
Block a user