827 lines
26 KiB
JavaScript
827 lines
26 KiB
JavaScript
/* global cornerstone, cornerstoneTools, cornerstoneBase64ImageLoader, cornerstoneWebImageLoader, cornerstoneWADOImageLoader */
|
|
/**
|
|
* Load the main image
|
|
* @param {*} image -
|
|
* @param {*} stack -
|
|
*/
|
|
export function loadMainImage(image, stack) {
|
|
const PanTool = cornerstoneTools.PanTool;
|
|
const ZoomTool = cornerstoneTools.ZoomTool;
|
|
const WwwcTool = cornerstoneTools.WwwcTool;
|
|
const RotateTool = cornerstoneTools.RotateTool;
|
|
const StackScrollTool = cornerstoneTools.StackScrollTool;
|
|
const ArrowAnnotateTool = cornerstoneTools.ArrowAnnotateTool;
|
|
const LengthTool = cornerstoneTools.LengthTool;
|
|
const RectangleRoiTool = cornerstoneTools.RectangleRoiTool;
|
|
const EllipseRoiTool = cornerstoneTools.EllipseRoiTool;
|
|
const AngleTool = cornerstoneTools.AngleTool;
|
|
const CobbAngleTool = cornerstoneTools.CobbAngleTool;
|
|
const EraserTool = cornerstoneTools.EraserTool;
|
|
|
|
const element = document.getElementById("dicom-image");
|
|
cornerstone.enable(element);
|
|
|
|
cornerstone.displayImage(element, image);
|
|
|
|
cornerstoneTools.addStackStateManager(element, ["stack"]);
|
|
cornerstoneTools.addToolState(element, "stack", stack);
|
|
|
|
cornerstoneTools.addToolForElement(element, PanTool);
|
|
cornerstoneTools.addToolForElement(element, ZoomTool, {
|
|
configuration: {
|
|
invert: true,
|
|
},
|
|
});
|
|
cornerstoneTools.addToolForElement(element, WwwcTool);
|
|
cornerstoneTools.addToolForElement(element, RotateTool);
|
|
cornerstoneTools.addToolForElement(element, AngleTool);
|
|
cornerstoneTools.addToolForElement(element, CobbAngleTool);
|
|
cornerstoneTools.addToolForElement(element, StackScrollTool);
|
|
cornerstoneTools.addToolForElement(element, EraserTool);
|
|
cornerstoneTools.addToolForElement(element, LengthTool);
|
|
cornerstoneTools.addToolForElement(element, RectangleRoiTool);
|
|
//cornerstoneTools.addToolForElement(element, EllipseRoiTool);
|
|
|
|
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, {
|
|
configuration: {
|
|
getTextCallback: () => {},
|
|
changeTextCallback: () => {},
|
|
allowEmptyLabel: true,
|
|
renderDashed: false,
|
|
drawHandles: false,
|
|
drawHandlesOnHover: true,
|
|
},
|
|
});
|
|
|
|
cornerstoneTools.setToolActiveForElement(element, "Pan", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
cornerstoneTools.setToolEnabledForElement(element, "ArrowAnnotate");
|
|
|
|
//cornerstoneTools.keyboardInput.enable(element);
|
|
|
|
element.addEventListener("cornerstoneimagerendered", onImageRendered);
|
|
|
|
setDicomCanvasNonFullscreen(element);
|
|
cornerstone.reset(element);
|
|
let viewerTitleBar = document.getElementById("viewer-title-bar");
|
|
viewerTitleBar.scrollIntoView();
|
|
//element.scrollIntoView(false);
|
|
//element.scrollTo(0);
|
|
|
|
$(element).dblclick(function() {
|
|
if ($(".canvas-panel").length == 0) {
|
|
// already fullscreen (disable it)
|
|
disableFullscreen(this);
|
|
} else {
|
|
enableFullscreen(this);
|
|
}
|
|
});
|
|
|
|
element.removeEventListener("wheel", element.wheelEventHandler);
|
|
|
|
$(".dicom-select-control")
|
|
.get(0)
|
|
.addEventListener("change", function() {
|
|
changeControlSelection();
|
|
});
|
|
|
|
// Register Key Event Listener
|
|
document.addEventListener("keydown", keydown_handler);
|
|
}
|
|
|
|
/**
|
|
* Called when a dicom image is rendered
|
|
* @param {*} e - Event
|
|
*/
|
|
function onImageRendered(e) {
|
|
const eventData = e.detail;
|
|
|
|
// Update ww/wl
|
|
const sel = $(".dicom-select-control").get(0);
|
|
sel.options[find_option(sel, "window")].firstChild.textContent =
|
|
"window (" +
|
|
Math.round(eventData.viewport.voi.windowCenter) +
|
|
" \u00b1 " +
|
|
Math.round(eventData.viewport.voi.windowWidth) +
|
|
")";
|
|
|
|
// update stack data
|
|
const stack =
|
|
eventData.enabledElement.toolStateManager.toolState.stack.data[0];
|
|
if (stack.imageIds.length > 1) {
|
|
$("option[value=scroll").prop("disabled", false);
|
|
$("option[value=scroll").prop("hidden", false);
|
|
$("option[value=scroll").text(
|
|
"scroll (" +
|
|
(stack.currentImageIdIndex + 1) +
|
|
"/" +
|
|
stack.imageIds.length +
|
|
")"
|
|
);
|
|
|
|
// Temp way to enable CT window presets
|
|
// Enable for all stacked images...
|
|
$("option[value=abdomen").prop("hidden", false);
|
|
$("option[value=pulmonary").prop("hidden", false);
|
|
$("option[value=brain").prop("hidden", false);
|
|
$("option[value=bone").prop("hidden", false);
|
|
$("option[value=abdomen").prop("disabled", false);
|
|
$("option[value=pulmonary").prop("disabled", false);
|
|
$("option[value=brain").prop("disabled", false);
|
|
$("option[value=bone").prop("disabled", false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds a select options but its text value
|
|
* @param {*} select - select element
|
|
* @param {*} text - text value
|
|
* @return {number} - number of found option or 'undefined'
|
|
*/
|
|
export function find_option(select, text) {
|
|
for (let i = 0; i < select.length; ++i) {
|
|
if (select.options[i].value == text) {
|
|
return i;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Disables fullscreen mode
|
|
* @param {*} dicom_element -
|
|
*/
|
|
export function disableFullscreen(dicom_element) {
|
|
// TODO: rescale the image to stop it getting zoomed in
|
|
$(".canvas-panel-fullscreen").toggleClass(
|
|
"canvas-panel canvas-panel-fullscreen"
|
|
);
|
|
|
|
$("#disable-fullscreen-viewer-button").hide();
|
|
$("#fullscreen-viewer-button").show();
|
|
$(".question").prepend($(".canvas-panel"));
|
|
$(".canvas-panel").get(0).scrollIntoView();
|
|
|
|
setDicomCanvasNonFullscreen(dicom_element);
|
|
}
|
|
|
|
/**
|
|
* Enable fullscreen
|
|
* @param {*} element -
|
|
*/
|
|
export function setDicomCanvasNonFullscreen(element) {
|
|
const h = window.innerHeight - $(".nav-bar").height() - 4;
|
|
$("#dicom-image").attr("height", h);
|
|
$("#dicom-image").height(h);
|
|
// $("#dicom-image.cornerstone-canvas").attr("height", h);
|
|
// $("#dicom-image.cornerstone-canvas").height(h);
|
|
|
|
$("#dicom-image .cornerstone-canvas").attr("width", "100%");
|
|
|
|
cornerstone.resize(element, true);
|
|
}
|
|
|
|
export function manualPanDicom(x, y) {
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
|
|
viewport.translation.x += x;
|
|
viewport.translation.y += y;
|
|
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
}
|
|
|
|
export function manualZoomDicom(delta) {
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
|
|
viewport.scale += delta / 100;
|
|
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
}
|
|
|
|
export function manualRotateDicom(delta) {
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
|
|
viewport.rotation += delta;
|
|
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
}
|
|
|
|
export function manualScrollDicom(n) {
|
|
// There must be a better way to do this...
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
let c = cornerstone.getEnabledElement(dicom_element);
|
|
|
|
let max = c.toolStateManager.toolState.stack.data[0].imageIds.length;
|
|
|
|
if (max < 2) {
|
|
return;
|
|
}
|
|
|
|
let current_index =
|
|
c.toolStateManager.toolState.stack.data[0].currentImageIdIndex;
|
|
|
|
// Loop through stack if out of bounds
|
|
// Yes it is annoying but it's the way it is done...
|
|
let new_index = current_index + n;
|
|
if (new_index >= max) {
|
|
new_index = new_index - max;
|
|
} else if (new_index < 0) {
|
|
new_index = new_index + max;
|
|
}
|
|
|
|
c.toolStateManager.toolState.stack.data[0].currentImageIdIndex = new_index;
|
|
let id = c.toolStateManager.toolState.stack.data[0].imageIds[new_index];
|
|
cornerstone.loadImage(id).then((b) => {
|
|
cornerstone.displayImage(dicom_element, b);
|
|
});
|
|
// c = cornerstone.getEnabledElement(dicom_element)
|
|
}
|
|
|
|
export function manualWindowDicom(wl, ww) {
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
|
|
// For reference
|
|
// brightness ~= wl
|
|
// contrast ~= ww
|
|
|
|
viewport.voi.windowCenter += wl;
|
|
viewport.voi.windowWidth += ww;
|
|
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
}
|
|
|
|
export function debugCornerstone() {
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
let viewport = cornerstone.getViewport(dicom_element);
|
|
let c = cornerstone.getEnabledElement(dicom_element);
|
|
}
|
|
|
|
/**
|
|
* Registers the selected dicom tool
|
|
*
|
|
* This also allows selecting of window levels
|
|
*/
|
|
export function changeControlSelection() {
|
|
// We also duplicate the sel that are available
|
|
const sel = $(".dicom-select-control").get(0);
|
|
|
|
const old = sel.oldSelectedIndex;
|
|
sel.oldSelectedIndex = sel.selectedIndex;
|
|
|
|
const dicom_element = document.getElementById("dicom-image");
|
|
switch (sel.options[sel.selectedIndex].value) {
|
|
case "pan": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Pan", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "zoom": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Zoom", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "rotate": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Rotate", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "measure": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Length", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "rectangle": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "RectangleRoi", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "ellipse": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "EllipseRoi", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case "angle": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Angle", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "cobbangle": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "CobbAngle", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "eraser": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Eraser", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "scroll": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "StackScroll", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
// _this.left.onstart = _this.scroll_start;
|
|
// _this.selected_control = t.selectedIndex;
|
|
break;
|
|
}
|
|
case "window": {
|
|
cornerstoneTools.setToolActiveForElement(dicom_element, "Wwwc", {
|
|
mouseButtonMask: 1,
|
|
});
|
|
break;
|
|
}
|
|
case "abdomen": {
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
viewport.voi.windowCenter = 150;
|
|
viewport.voi.windowWidth = 500;
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
sel.selectedIndex = find_option(sel, "window");
|
|
changeControlSelection();
|
|
break;
|
|
}
|
|
case "pulmonary": {
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
viewport.voi.windowCenter = -500;
|
|
viewport.voi.windowWidth = 1500;
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
sel.selectedIndex = find_option(sel, "window");
|
|
changeControlSelection();
|
|
break;
|
|
}
|
|
case "brain": {
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
viewport.voi.windowCenter = 50;
|
|
viewport.voi.windowWidth = 80;
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
sel.selectedIndex = find_option(sel, "window");
|
|
changeControlSelection();
|
|
break;
|
|
}
|
|
case "bone": {
|
|
const viewport = cornerstone.getViewport(dicom_element);
|
|
viewport.voi.windowCenter = 570;
|
|
viewport.voi.windowWidth = 3000;
|
|
cornerstone.setViewport(dicom_element, viewport);
|
|
sel.selectedIndex = find_option(sel, "window");
|
|
changeControlSelection();
|
|
break;
|
|
}
|
|
case "reset": {
|
|
resetViewer(dicom_element);
|
|
sel.selectedIndex = old;
|
|
sel.oldSelectedIndex = old;
|
|
break;
|
|
}
|
|
case "close": {
|
|
closeViewer(dicom_element);
|
|
break;
|
|
}
|
|
}
|
|
sel.blur();
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
|
|
function resetViewer(dicom_element) {
|
|
cornerstoneTools.clearToolState(dicom_element, "RectangleRoi");
|
|
cornerstoneTools.clearToolState(dicom_element, "Length");
|
|
cornerstone.reset(dicom_element);
|
|
}
|
|
|
|
/**
|
|
* Handle key button events
|
|
* These are the same as in the RCR pratique implementation
|
|
* @param {*} event - KeyEvent
|
|
*/
|
|
export function keydown_handler(event) {
|
|
//console.debug(event);
|
|
const target_element = event.target.tagName;
|
|
|
|
// Cancel if no active element
|
|
let dicom_element = document.getElementById("dicom-image");
|
|
if (dicom_element == undefined) {
|
|
return;
|
|
}
|
|
|
|
// Catch all keypresses unless user is typing
|
|
if (target_element == "INPUT" || target_element == "TEXTAREA") {
|
|
return;
|
|
}
|
|
const sel = $(".dicom-select-control").get(0);
|
|
switch (event.code) {
|
|
case "KeyP":
|
|
sel.selectedIndex = find_option(sel, "pan");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyC":
|
|
sel.selectedIndex = find_option(sel, "rectangle");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyZ":
|
|
sel.selectedIndex = find_option(sel, "zoom");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyR":
|
|
sel.selectedIndex = find_option(sel, "rotate");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyS":
|
|
sel.selectedIndex = find_option(sel, "scroll");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyW":
|
|
sel.selectedIndex = find_option(sel, "window");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyA":
|
|
sel.selectedIndex = find_option(sel, "abdomen");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyU":
|
|
sel.selectedIndex = find_option(sel, "pulmonary");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyB":
|
|
sel.selectedIndex = find_option(sel, "brain");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyO":
|
|
sel.selectedIndex = find_option(sel, "bone");
|
|
changeControlSelection();
|
|
break;
|
|
case "KeyE":
|
|
sel.selectedIndex = find_option(sel, "reset");
|
|
// $("#dicom-image").get(0).scrollIntoView(false);
|
|
changeControlSelection();
|
|
break;
|
|
// Escape and C do the same
|
|
//case "KeyC":
|
|
case "Escape":
|
|
sel.selectedIndex = find_option(sel, "close");
|
|
changeControlSelection();
|
|
break;
|
|
case "Enter":
|
|
t.focus();
|
|
break;
|
|
// TODO: implement arrow stuff
|
|
//
|
|
case "ArrowUp": {
|
|
// Arrow function depends on the currently selected option
|
|
switch (sel.options[sel.selectedIndex].value) {
|
|
case "pan":
|
|
manualPanDicom(0, -1);
|
|
break;
|
|
case "zoom":
|
|
// _this.current_dicom.current_view.pre_scale_at(1.01, 1.01, _this.current_dicom.cols / 2, _this.current_dicom.rows / 2);
|
|
manualZoomDicom(1);
|
|
break;
|
|
case "rotate":
|
|
manualRotateDicom(-1);
|
|
break;
|
|
case "scroll":
|
|
manualScrollDicom(-1);
|
|
break;
|
|
case "window":
|
|
manualWindowDicom(1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case "ArrowDown": {
|
|
switch (sel.options[sel.selectedIndex].value) {
|
|
case "pan":
|
|
manualPanDicom(0, 1);
|
|
break;
|
|
case "zoom":
|
|
manualZoomDicom(-1);
|
|
break;
|
|
case "rotate":
|
|
manualRotateDicom(1);
|
|
break;
|
|
case "scroll":
|
|
manualScrollDicom(1);
|
|
break;
|
|
case "window":
|
|
manualWindowDicom(-1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case "ArrowLeft": {
|
|
switch (sel.options[sel.selectedIndex].value) {
|
|
case "pan":
|
|
manualPanDicom(-1, 0);
|
|
break;
|
|
case "zoom":
|
|
manualZoomDicom(-1);
|
|
break;
|
|
case "rotate":
|
|
manualRotateDicom(-1);
|
|
break;
|
|
case "scroll":
|
|
manualScrollDicom(1);
|
|
break;
|
|
case "window":
|
|
manualWindowDicom(0, -1);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case "ArrowRight": {
|
|
switch (sel.options[sel.selectedIndex].value) {
|
|
case "pan":
|
|
manualPanDicom(1, 0);
|
|
break;
|
|
case "zoom":
|
|
manualZoomDicom(1);
|
|
break;
|
|
case "rotate":
|
|
manualRotateDicom(1);
|
|
break;
|
|
case "scroll":
|
|
manualScrollDicom(-1);
|
|
break;
|
|
case "window":
|
|
manualWindowDicom(0, 1);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
|
|
function loadAnnotation(imageId, annotation) {
|
|
const toolStateManager =
|
|
cornerstoneTools.globalImageIdSpecificToolStateManager;
|
|
|
|
if (annotation == undefined || annotation.length < 1) {
|
|
return;
|
|
}
|
|
|
|
let tool_state_no_id = JSON.parse(annotation);
|
|
|
|
let tool_state = {};
|
|
tool_state[imageId] = tool_state_no_id;
|
|
|
|
toolStateManager.restoreToolState(tool_state);
|
|
}
|
|
async function load(images, annotations) {
|
|
// Parallelize image preparation and annotation loading
|
|
const imageTasks = images.map(async (data_url, i) => {
|
|
const annotation = annotations[i];
|
|
let imageId;
|
|
if (data_url.startsWith("data:image")) {
|
|
imageId = "base64://" + data_url.split(",")[1];
|
|
} else if (data_url.startsWith("data:")) {
|
|
const dfile = await urltoFile(data_url, "dicom", "application/dicom");
|
|
imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(dfile);
|
|
} else {
|
|
let url = data_url;
|
|
if (url.endsWith("dcm")) {
|
|
url = "wadouri:" + url;
|
|
}
|
|
if (/(?:\/|^)[^.\/]+$/.test(url)) {
|
|
url = "wadouri:" + url;
|
|
}
|
|
imageId = 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 = {
|
|
currentImageIdIndex: 0,
|
|
imageIds,
|
|
};
|
|
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) {
|
|
// Check if the figure is already loaded (or if another one is)
|
|
const open_figure = $("#dicom-image").data("figure");
|
|
const figure_to_load = source.id;
|
|
|
|
if (open_figure != undefined) {
|
|
// No full size figure / dicom loaded yet
|
|
if (figure_to_load == open_figure) {
|
|
// Scroll to the image if it is already open
|
|
document.getElementById("viewer-title-bar").scrollIntoView();
|
|
return;
|
|
} else {
|
|
let el;
|
|
el = document.getElementById("dicom-image");
|
|
if (el != undefined) {
|
|
cornerstone.disable(el);
|
|
// Explicitly remove the dicom-image element
|
|
el.remove();
|
|
$(".canvas-panel").remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
$(".figure-open").removeClass("figure-open").addClass("figure");
|
|
|
|
source.className = "figure-open";
|
|
|
|
let image_title = source.getElementsByClassName("figcaption")[0].textContent;
|
|
|
|
// Native DOM creation for performance
|
|
const questionElem = document.querySelector('.question');
|
|
const canvasPanel = document.createElement('div');
|
|
canvasPanel.className = 'canvas-panel';
|
|
|
|
const viewerTitleBar = document.createElement('div');
|
|
viewerTitleBar.id = 'viewer-title-bar';
|
|
|
|
const flexDiv = document.createElement('div');
|
|
flexDiv.style.flex = '1 1 auto';
|
|
|
|
const viewerTitle = document.createElement('div');
|
|
viewerTitle.id = 'viewer-title';
|
|
viewerTitle.style.color = 'white';
|
|
viewerTitle.textContent = image_title;
|
|
flexDiv.appendChild(viewerTitle);
|
|
viewerTitleBar.appendChild(flexDiv);
|
|
|
|
// Select control
|
|
const select = document.createElement('select');
|
|
select.title = 'SELECT CLICK ACTION';
|
|
select.className = 'dicom-select-control title-bar-button';
|
|
select.style.marginRight = '8px';
|
|
const options = [
|
|
{ value: 'pan', text: 'pan [p]' },
|
|
{ value: 'zoom', text: 'zoom [z]' },
|
|
{ value: 'rotate', text: 'rotate [r]' },
|
|
{ value: 'scroll', text: 'scroll [s]' },
|
|
{ value: 'window', text: 'window (128 ± 127.5) [w]' },
|
|
{ value: 'abdomen', text: 'window = abdomen [a]', hidden: true, disabled: true },
|
|
{ value: 'pulmonary', text: 'window = pulmonary [p]', hidden: true, disabled: true },
|
|
{ value: 'brain', text: 'window = brain [b]', hidden: true, disabled: true },
|
|
{ value: 'bone', text: 'window = bone [o]', hidden: true, disabled: true },
|
|
{ value: 'measure', text: 'measure [m]' },
|
|
{ value: 'ellipse', text: 'ellipse [l]' },
|
|
{ value: 'rectangle', text: 'rectangle [c]' },
|
|
{ value: 'reset', text: 'reset [e]' },
|
|
{ value: 'notes', text: '[modality = SC]', disabled: true }
|
|
];
|
|
options.forEach(opt => {
|
|
const o = document.createElement('option');
|
|
o.value = opt.value;
|
|
o.textContent = opt.text;
|
|
if (opt.disabled) o.disabled = true;
|
|
if (opt.hidden) o.hidden = 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]];
|
|
// images = current_question.images
|
|
|
|
// Make sure we have an array
|
|
if (!Array.isArray(images)) {
|
|
images = [images];
|
|
}
|
|
|
|
let annotations = [];
|
|
if (current_question.annotations) {
|
|
annotations = current_question.annotations[figure_to_load.split("-")[1]];
|
|
if (!Array.isArray(annotations)) {
|
|
annotations = [annotations];
|
|
}
|
|
}
|
|
|
|
const dicom_element = document.getElementById("dicom-image");
|
|
// Bind button actions
|
|
$("#close-viewer-button").click(function() {
|
|
closeViewer(dicom_element);
|
|
});
|
|
$("#reset-viewer-button").click(function() {
|
|
resetViewer(dicom_element);
|
|
});
|
|
$("#fullscreen-viewer-button").click(function() {
|
|
enableFullscreen(dicom_element);
|
|
});
|
|
$("#disable-fullscreen-viewer-button").click(function() {
|
|
disableFullscreen(dicom_element);
|
|
});
|
|
|
|
load(images, annotations);
|
|
}
|
|
}
|
|
|
|
function closeViewer(dicom_element) {
|
|
// disable fullscreen if required
|
|
if ($(".canvas-panel").length == 0) {
|
|
disableFullscreen(dicom_element);
|
|
}
|
|
if (dicom_element != undefined) {
|
|
// See https://github.com/cornerstonejs/cornerstoneTools/issues/1337
|
|
cornerstone.removeElementData(dicom_element);
|
|
cornerstone.disable(dicom_element);
|
|
$(".canvas-panel").remove();
|
|
$(".figure-open").removeClass("figure-open").addClass("figure");
|
|
$(dicom_element).remove();
|
|
}
|
|
}
|
|
|
|
export function urltoFile(url, filename, mimeType) {
|
|
return fetch(url)
|
|
.then(function(res) {
|
|
return res.arrayBuffer();
|
|
})
|
|
.then(function(buf) {
|
|
return new File([buf], filename, {type: mimeType});
|
|
});
|
|
}
|
|
|
|
|
|
function enableFullscreen(dicom_element) {
|
|
$(".content-panel").prepend($(".canvas-panel"));
|
|
$(".canvas-panel").toggleClass("canvas-panel canvas-panel-fullscreen");
|
|
$("#dicom-image").attr("height", "100%");
|
|
$("#dicom-image").height("100%");
|
|
$("#disable-fullscreen-viewer-button").show();
|
|
$("#fullscreen-viewer-button").hide();
|
|
// $(".cornerstone-canvas").attr("height", "100%");
|
|
// $(".cornerstone-canvas").height("100%");
|
|
cornerstone.resize(dicom_element, true);
|
|
} |