1279 lines
42 KiB
JavaScript
1279 lines
42 KiB
JavaScript
// Dicom saved settings
|
|
var dicom_settings_db = new Dexie("dicom_settings");
|
|
dicom_settings_db.version(1).stores({
|
|
mouse_bindings: "button,mode,tool",
|
|
});
|
|
|
|
|
|
cornerstoneBase64ImageLoader.external.cornerstone = cornerstone;
|
|
cornerstoneWebImageLoader.external.cornerstone = cornerstone;
|
|
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
|
|
|
|
cornerstoneTools.init({
|
|
keyboardEnabled: true
|
|
});
|
|
|
|
let tool_state = {};
|
|
|
|
export function loadCornerstone(main_element, db, images, annotations_to_load, load_as_stack = false) {
|
|
// canvas-panel holds the enabled elements and all tools / menus
|
|
const canvas_panel = $("<div class='canvas-panel'></div>");
|
|
main_element.append(canvas_panel);
|
|
main_element[0].dataset.collapsed = "none";
|
|
|
|
|
|
const cornerstone_element = $("<div class='cornerstone-element'></div>");
|
|
const element = cornerstone_element.get(0);
|
|
canvas_panel.append(cornerstone_element);
|
|
canvas_panel.append($(`<div id='dicom-summary-panel'>
|
|
<span id="dicom-summary-close" class="close-button"><a href="#">close</a></span>
|
|
<div class="container-fluid">
|
|
<h1>DICOM Summary</h1>
|
|
|
|
<div class="panel panel-default ">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">Patient Information</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div>
|
|
Patient Name: <span data-dicom="x00100010"></span>
|
|
</div>
|
|
<div>
|
|
Patient ID: <span data-dicom="x00100020"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Patient Birth Date: <span data-dicom="x00100030"></span>
|
|
</div>
|
|
<div>
|
|
Patient Sex: <span data-dicom="x00100040"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel panel-default">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">Study Information</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div>
|
|
Study Description: <span data-dicom="x00081030"></span>
|
|
</div>
|
|
<div>
|
|
Protocol Name: <span data-dicom="x00181030"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Accession #: <span data-dicom="x00080050"></span>
|
|
</div>
|
|
<div>
|
|
Study Id: <span data-dicom="x00200010"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Study Date: <span data-dicom="x00080020"></span>
|
|
</div>
|
|
<div>
|
|
Study Time: <span data-dicom="x00080030"></span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel panel-default">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">Series Information</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div>
|
|
Series Description: <span data-dicom="x0008103e"></span>
|
|
</div>
|
|
<div>
|
|
Series #: <span data-dicom="x00200011"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Modality: <span data-dicom="x00080060"></span>
|
|
</div>
|
|
<div>
|
|
Body Part: <span data-dicom="x00180015"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Series Date: <span data-dicom="x00080021"></span>
|
|
</div>
|
|
<div>
|
|
Series Time: <span data-dicom="x00080031"></span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel panel-default">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">Instance Information</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div>
|
|
Instance #: <span data-dicom="x00200013"></span>
|
|
</div>
|
|
<div>
|
|
Acquisition #: <span data-dicom="x00200012"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Acquisition Date: <span data-dicom="x00080022"></span>
|
|
</div>
|
|
<div>
|
|
Acquisition Time: <span data-dicom="x00080032"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Content Date: <span data-dicom="x00080023"></span>
|
|
</div>
|
|
<div>
|
|
Content Time: <span data-dicom="x00080033"></span>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel panel-default">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">Image Information</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div>
|
|
Rows: <span data-dicomUint="x00280010"></span>
|
|
</div>
|
|
<div>
|
|
Columns: <span data-dicomUint="x00280011"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div>
|
|
Photometric Interpretation: <span data-dicom="x00280004"></span>
|
|
</div>
|
|
<div>
|
|
Image Type: <span data-dicom="x00080008"></span>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
<div>
|
|
Bits Allocated: <span data-dicomUint="x00280100"></span>
|
|
</div>
|
|
<div>
|
|
Bits Stored: <span data-dicomUint="x00280101"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div>
|
|
HighBit: <span data-dicomUint="x00280102"></span>
|
|
</div>
|
|
<div>
|
|
Pixel Representation (0=us): <span data-dicomUint="x00280103"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div>
|
|
Rescale Slope: <span data-dicom="x00281053"></span>
|
|
</div>
|
|
<div>
|
|
Rescale Intercept: <span data-dicom="x00281052"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Image Position Patient: <span data-dicom="x00200032"></span>
|
|
</div>
|
|
<div>
|
|
Image Orientation Patient: <span data-dicom="x00200037"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Pixel Spacing: <span data-dicom="x00280030"></span>
|
|
</div>
|
|
<div>
|
|
Samples Per Pixel: <span data-dicomUint="x00280002"></span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel panel-default">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">Equipment Information</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div>
|
|
Manufacturer: <span data-dicom="x00080070"></span>
|
|
</div>
|
|
<div>
|
|
Model: <span data-dicom="x00081090"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
Station Name: <span data-dicom="x00081010"></span>
|
|
</div>
|
|
<div>
|
|
AE Title: <span data-dicom="x00020016"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Institution Name: <span data-dicom="x00080080"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Software Version: <span data-dicom="x00181020"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Implementation Version Name: <span data-dicom="x00020013"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel panel-default">
|
|
<div class="panel-heading">
|
|
<h3 class="panel-title">UIDS</h3>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Study UID: <span data-dicom="x0020000d"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Series UID: <span data-dicom="x0020000e"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Instance UID: <span data-dicom="x00080018"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
SOP Class UID: <span data-dicom="x00080016"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Transfer Syntax UID: <span data-dicom="x00020010"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
Frame of Reference UID: <span data-dicom="x00200052"></span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col-xs-12">
|
|
WADO URL:
|
|
<div style="overflow:auto">
|
|
<span>http://localhost:3333/wado?requestType=WADO&studyUID=</span><span
|
|
data-dicom="x0020000d"></span><span>&seriesUID=</span><span
|
|
data-dicom="x0020000e"></span><span>&objectUID=</span><span
|
|
data-dicom="x00080018"></span><span>&contentType=application%2Fdicom&transferSyntax=1.2.840.10008.1.2.1</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`));
|
|
|
|
|
|
canvas_panel.append($(`<div id='dicom-log-panel'>
|
|
<span id="dicom-log-close" class="close-button"><a href="#">close</a></span>
|
|
</div>`));
|
|
|
|
// Add generic settings menus to canvas-panel
|
|
$(".canvas-panel").append(
|
|
$(`<div id='dicom-settings-panel'>
|
|
<span id="dicom-settings-close" class="close-button"><a href="#">close</a></span>
|
|
<span class="view-log-button"><a href="#">view log</a></span>
|
|
<h3>Image viewer settings:</h3>
|
|
|
|
Primary
|
|
<div id="primary-mouse-binding">
|
|
<label for="left-mouse-dicom">Left mouse button:</label>
|
|
<select
|
|
id="left-mouse-dicom"
|
|
class="mouse-binding-select"
|
|
data-button="1"
|
|
data-mode="0"
|
|
>
|
|
</select>
|
|
<label for="middle-mouse-dicom">Middle mouse button:</label>
|
|
<select
|
|
id="middle-mouse-dicom"
|
|
class="mouse-binding-select"
|
|
data-button="4"
|
|
data-mode="0"
|
|
>
|
|
</select>
|
|
<label for="right-mouse-dicom">Right mouse button:</label>
|
|
<select
|
|
id="right-mouse-dicom"
|
|
class="mouse-binding-select"
|
|
data-button="2"
|
|
data-mode="0"
|
|
>
|
|
</select>
|
|
<label for="left-right-mouse-dicom">Left+Right mouse buttons:</label>
|
|
<select
|
|
id="left-right-mouse-dicom"
|
|
class="mouse-binding-select"
|
|
data-button="3"
|
|
data-mode="0"
|
|
>
|
|
</select>
|
|
</div>
|
|
<br />
|
|
Secondary (Ctrl)
|
|
<div id="secondary-mouse-binding">
|
|
<label for="left-mouse-dicom-secondary">Left mouse button:</label>
|
|
<select
|
|
id="left-mouse-dicom-secondary"
|
|
class="mouse-binding-select"
|
|
data-button="1"
|
|
data-mode="1"
|
|
>
|
|
</select>
|
|
<label for="middle-mouse-dicom-secondary">Middle mouse button:</label>
|
|
<select
|
|
id="middle-mouse-dicom-secondary"
|
|
class="mouse-binding-select"
|
|
data-button="4"
|
|
data-mode="1"
|
|
>
|
|
</select>
|
|
<label for="right-mouse-dicom-secondary">Right mouse button:</label>
|
|
<select
|
|
id="right-mouse-dicom-secondary"
|
|
class="mouse-binding-select"
|
|
data-button="2"
|
|
data-mode="1"
|
|
>
|
|
</select>
|
|
<label for="left-right-mouse-dicom-secondary">Left+Right mouse buttons:</label>
|
|
<select
|
|
id="left-right-mouse-dicom-secondary"
|
|
class="mouse-binding-select"
|
|
data-button="3"
|
|
data-mode="1"
|
|
>
|
|
</select>
|
|
</div>
|
|
|
|
</div>`)
|
|
)
|
|
.append($("<span id='dicom-toggle-mode-button' class='dicom-button' title='toggle stack/thumbnail view'>↻</span>"))
|
|
.append($("<span id='dicom-fullscreen-button' class='dicom-button' title='toggle stack/thumbnail view'>⛶</span>"))
|
|
.append($("<span id='dicom-settings-button' class='dicom-button' title='open settings'>⚙</span>"))
|
|
.append($("<span id='dicom-collapse-button' class='dicom-button' title='collapse viewer'>⇄</span>"))
|
|
.append($("<span id='dicom-info-button' class='dicom-button' title='view image info'>🛈</span>"))
|
|
.append(
|
|
$(`<div id='dicom-window-panel'>
|
|
<button id='window-btn-abdomen'>Abdomen</button>
|
|
<button id='window-btn-liver'>Liver</button>
|
|
<button id='window-btn-mediastinum'>Mediastinum</button>
|
|
<button id='window-btn-lung'>Lungs</button>
|
|
<button id='window-btn-brain'>Brain</button>
|
|
<button id='window-btn-stroke'>Stroke</button>
|
|
<button id='window-btn-bone'>Bone</button>
|
|
</div>`)
|
|
);
|
|
|
|
cornerstone_element.append(
|
|
$(
|
|
"<div id='dicom-overlay'>Image <span id='current_image_number'></span> of <span id='total_image_number'></span><br />wc: <span id='wc'></span> ww: <span id='ww'></span></div>"
|
|
)
|
|
);
|
|
// Add buttons
|
|
|
|
let window_presets = {
|
|
"window-btn-abdomen": [400, 50],
|
|
"window-btn-liver": [150, 30],
|
|
"window-btn-mediastinum": [350, 50],
|
|
"window-btn-lung": [1500, -600],
|
|
"window-btn-brain": [80, 40],
|
|
"window-btn-stroke": [40, 40],
|
|
"window-btn-bone": [1800, 400]
|
|
}
|
|
|
|
$("#dicom-window-panel button").each((n, el) => {
|
|
$(el).click((e) => {
|
|
let button_id = e.target.id;
|
|
let [ww, wc] = window_presets[button_id];
|
|
|
|
let viewport = cornerstone.getViewport(element);
|
|
viewport.voi.windowWidth = parseFloat(ww);
|
|
viewport.voi.windowCenter = parseFloat(wc);
|
|
cornerstone.setViewport(element, viewport);
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
})
|
|
})
|
|
|
|
$("#dicom-settings-close").click(e => {
|
|
$("#dicom-settings-panel").hide();
|
|
e.preventDefault();
|
|
});
|
|
$("#dicom-summary-close").click(e => {
|
|
$("#dicom-summary-panel").hide();
|
|
e.preventDefault();
|
|
});
|
|
$("#dicom-log-close").click(e => {
|
|
$("#dicom-log-panel").hide();
|
|
e.preventDefault();
|
|
});
|
|
$(".view-log-button").click(e => {
|
|
$("#dicom-settings-panel").hide();
|
|
$("#dicom-log-panel").show();
|
|
e.preventDefault();
|
|
});
|
|
$("#dicom-settings-button").click(e => {
|
|
$("#dicom-settings-panel").toggle();
|
|
});
|
|
$("#dicom-info-button").click(e => {
|
|
try {
|
|
// todo improve
|
|
dumpDataSet(cornerstone.getEnabledElement(element).image.data);
|
|
} catch {
|
|
console.log("unable to extract dicom data")
|
|
|
|
}
|
|
$("#dicom-summary-panel").toggle();
|
|
});
|
|
$("#dicom-fullscreen-button").click(e => {
|
|
if (!document.fullscreenElement) {
|
|
$(".canvas-panel").get(0).requestFullscreen().catch(err => {
|
|
alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
|
|
});
|
|
} else {
|
|
document.exitFullscreen();
|
|
}
|
|
});
|
|
$("#dicom-toggle-mode-button").click(e => {
|
|
//$("#dicom-settings-panel").toggle();
|
|
let stack = !load_as_stack;
|
|
$(main_element).empty();
|
|
loadCornerstone(main_element, db, images, annotations_to_load, stack)
|
|
});
|
|
|
|
$("#dicom-collapse-button").click(e => {
|
|
//$("#dicom-settings-panel").toggle();
|
|
//let stack = !load_as_stack;
|
|
if (main_element[0].dataset.collapsed == "none") {
|
|
main_element[0].dataset.collapsed = main_element.width();
|
|
main_element.width("50px");
|
|
$("#dicom-window-panel").hide();
|
|
$(e.target).addClass("dicom-button-highlight");
|
|
} else {
|
|
main_element.width(main_element[0].dataset.collapsed);
|
|
main_element[0].dataset.collapsed = "none"
|
|
$("#dicom-window-panel").show();
|
|
$(e.target).removeClass("dicom-button-highlight");
|
|
}
|
|
resizeHandler(element);
|
|
//loadCornerstone(main_element, db, images, annotations_to_load, stack)
|
|
});
|
|
|
|
//let images = data["images"];
|
|
|
|
// Make sure we have an array
|
|
if (!Array.isArray(images)) {
|
|
images = [images];
|
|
}
|
|
|
|
|
|
//let annotations = [];
|
|
//if (annotations_to_load) {
|
|
//annotations = annotations_to_load[figure_to_load.split("-")[1]];
|
|
//if (!Array.isArray(annotations)) {
|
|
//annotations = [annotations];
|
|
//}
|
|
//}
|
|
let annotations = annotations_to_load;
|
|
if (!Array.isArray(annotations)) {
|
|
annotations = [annotations];
|
|
}
|
|
|
|
|
|
function loadAnnotation(imageId, annotation) {
|
|
const toolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager;
|
|
|
|
if (annotation == undefined || annotation.length < 1 || annotation == [undefined]) {
|
|
return
|
|
}
|
|
|
|
let tool_state_no_id = JSON.parse(annotation);
|
|
|
|
tool_state[imageId] = tool_state_no_id;
|
|
|
|
toolStateManager.restoreToolState(tool_state);
|
|
}
|
|
|
|
async function load(images, annotations) {
|
|
let imageIds = [];
|
|
for (let i = 0; i < images.length; i++) {
|
|
let data_url = images[i];
|
|
const annotation = annotations[i];
|
|
// check stack type
|
|
if (data_url.startsWith("data:image")) {
|
|
let imageId = "base64://" + data_url.split(",")[1];
|
|
|
|
loadAnnotation(imageId, annotation);
|
|
|
|
imageIds.push(imageId);
|
|
} else if (data_url.startsWith("base64://") || data_url.startsWith("wadouri:")) {
|
|
loadAnnotation(data_url, annotation);
|
|
|
|
imageIds.push(data_url);
|
|
|
|
// Treat application/octet-stream as if they are dicoms
|
|
} else if (data_url.startsWith("data:application/dicom") || data_url.startsWith("data:application/octet-stream")) {
|
|
//stack = stack.split(";")[1];
|
|
|
|
let dfile = await urltoFile(data_url, "dicom", "application/dicom");
|
|
|
|
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(
|
|
dfile
|
|
);
|
|
|
|
loadAnnotation(imageId, annotation);
|
|
|
|
imageIds.push(imageId);
|
|
//cornerstone.loadImage(imageId).then(function(image) {
|
|
// tempFunction(image);
|
|
//});
|
|
} else {
|
|
let url;
|
|
// This doesn't seem to have any benefit
|
|
//if (data_url.startsWith("http")) {
|
|
// url = data_url;
|
|
//} else {
|
|
// url = window.location.href.replace(/\/\#\/?$/, '') + "/" + data_url
|
|
//}
|
|
url = data_url;
|
|
|
|
if (url.endsWith("dcm")) {
|
|
url = "wadouri:" + url;
|
|
}
|
|
|
|
// if there is no extension treat it as a dicom
|
|
if (/(?:\/|^)[^.\/]+$/.test(url)) {
|
|
url = "wadouri:" + url;
|
|
}
|
|
|
|
loadAnnotation(url, annotation);
|
|
|
|
imageIds.push(url);
|
|
|
|
|
|
}
|
|
}
|
|
const stack = {
|
|
currentImageIdIndex: 0,
|
|
imageIds
|
|
};
|
|
//cornerstone.loadAndCacheImage(imageIds[0]).then(function(image) {
|
|
cornerstone.loadAndCacheImage(imageIds[0]).then(function (image) {
|
|
|
|
//console.log("LOAD and cache, then", image);
|
|
loadCornerstoneMainImage(element, image, stack, db, load_as_stack);
|
|
checkAnonStatus(image.data)
|
|
|
|
let load_annotation_image = true;
|
|
if (load_annotation_image) {
|
|
getNextAnnotationImage(element)
|
|
}
|
|
})
|
|
.catch((err, err2) => {
|
|
console.log(err);
|
|
$("#dicom-log-panel").append("<br/>").append(err.stack);
|
|
|
|
});
|
|
}
|
|
|
|
function loadCornerstoneThumb(image, id) {
|
|
let img = $("<div></div>").get(0);
|
|
img.id = "thumb-image-" + id;
|
|
img.class = "thumbnail";
|
|
img.title =
|
|
"Click on the thumbnail to view and manipulate the image.";
|
|
img.draggable = "false";
|
|
img.style = "height: 100px; width: 100px";
|
|
$("#thumb-" + id).append(img);
|
|
|
|
const element = document.getElementById("thumb-image-" + id);
|
|
cornerstone.enable(element);
|
|
cornerstone.displayImage(element, image);
|
|
cornerstone.resize(element);
|
|
}
|
|
|
|
if (images.length > 1 && load_as_stack == false) {
|
|
$(".canvas-panel").append("<div id='image-thumbs'></div>");
|
|
for (let id = 0; id < images.length; id++) {
|
|
let n = id + 1;
|
|
let thumb = $(
|
|
"<div class='thumb' id='thumb-" +
|
|
id +
|
|
"' data-id=" +
|
|
id +
|
|
"><span>" +
|
|
n +
|
|
"</span></div>"
|
|
);
|
|
$("#image-thumbs").append(thumb);
|
|
$("#thumb-" + id).click((evt) => {
|
|
selectThumbClick(evt, element)
|
|
}).mousedown(stopEvent);
|
|
|
|
let image_url = images[id];
|
|
|
|
let img;
|
|
|
|
|
|
if (image_url.startsWith("data")) {
|
|
// based (image) data url, just load the image directly
|
|
if (image_url.startsWith("data:image/")) {
|
|
img = $("<img />", {
|
|
src: image_url,
|
|
id: "thumb-image-" + id,
|
|
class: "thumbnail",
|
|
title: "Click on the thumbnail to view and manipulate the image.",
|
|
draggable: "false",
|
|
style: "height: 100px;"
|
|
});
|
|
|
|
$("#thumb-" + id).append(img);
|
|
|
|
// otherwise try to load it as a dicom
|
|
} else {
|
|
// convert the data url to a file
|
|
urltoFile(image_url, "dicom", "application/dicom").then(function (
|
|
dfile
|
|
) {
|
|
// load the file using cornerstoneWADO file loader
|
|
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(
|
|
dfile
|
|
);
|
|
cornerstone.loadAndCacheImage(imageId).then(function (image) {
|
|
loadCornerstoneThumb(image, id);
|
|
});
|
|
});
|
|
}
|
|
} else {
|
|
if (image_url.startsWith("wadouri") || image_url.startsWith("base64")) {
|
|
cornerstone.loadAndCacheImage(image_url).then(function (image) {
|
|
loadCornerstoneThumb(image, id);
|
|
});
|
|
} else if ((!image_url.endsWith("dicom") && !image_url.endsWith("dcm"))) {
|
|
img = $("<img />", {
|
|
src: image_url,
|
|
id: "thumb-image-" + id,
|
|
class: "thumbnail",
|
|
title: "Click on the thumbnail to view and manipulate the image.",
|
|
draggable: "false",
|
|
style: "height: 100px;"
|
|
});
|
|
|
|
$("#thumb-" + id).append(img);
|
|
|
|
// otherwise try to load it as a dicom
|
|
} else {
|
|
|
|
let url = "wadouri:" + image_url;
|
|
cornerstone.loadAndCacheImage(url).then(function (image) {
|
|
loadCornerstoneThumb(image, id);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
load(images, annotations);
|
|
|
|
main_element.keydown((evt) => {
|
|
keyDownHandler(evt, element)
|
|
});
|
|
main_element.keyup((evt) => {
|
|
keyUpHandler(evt, element)
|
|
});
|
|
|
|
|
|
}
|
|
|
|
function keyUpHandler(e, element) {
|
|
if (e.key == "Control") {
|
|
registerPrimaryDicomInterface(element);
|
|
window.control_pressed = false;
|
|
}
|
|
}
|
|
|
|
function keyDownHandler(e, element) {
|
|
//// Ignore our custom keybindings if we are currently in a field that
|
|
//// accepts some kind of input
|
|
//if ($("*:focus:not(disabled)").is("textarea, input")) {
|
|
// // unless a modifier key is pressed (not shift)
|
|
// if (e.altKey ? true : false || e.ctrlKey ? true : false) {} else {
|
|
// return;
|
|
// }
|
|
//}
|
|
|
|
if (e.key == "Control") {
|
|
if (window.control_pressed != true) {
|
|
window.control_pressed = true;
|
|
registerAltDicomInterface(e, element);
|
|
}
|
|
}
|
|
|
|
var charCode = typeof e.which == "number" ? e.which : e.keyCode;
|
|
|
|
function numberKeyPressed(e, x) {
|
|
if (e.altKey ? true : false) {
|
|
loadStackIndex(x, element);
|
|
e.preventDefault();
|
|
} else {}
|
|
}
|
|
|
|
switch (charCode) {
|
|
case 13: // Return
|
|
getNextAnnotationImage(element);
|
|
if (e.shiftKey ? true : false) {
|
|
//$(".next-button:last").click();
|
|
} else {
|
|
//$(".check-button:last").click();
|
|
}
|
|
break;
|
|
case 32: // Space
|
|
if (e.shiftKey ? true : false) {
|
|
//$(".previous-button:last").click();
|
|
} else {
|
|
//$(".next-button:last").click();
|
|
}
|
|
e.preventDefault(); // Needed to stop the default action (scroll)
|
|
break;
|
|
case 46: // .
|
|
//toggleFlagged();
|
|
break;
|
|
|
|
// Numbers 1-9 select the corresponding answer (if it exists)
|
|
// TODO: fix for multi question questions
|
|
case 49: // 1
|
|
numberKeyPressed(e, 0);
|
|
break;
|
|
case 50: // 2
|
|
numberKeyPressed(e, 1);
|
|
break;
|
|
case 51: // 3
|
|
numberKeyPressed(e, 2);
|
|
break;
|
|
case 52: // 4
|
|
numberKeyPressed(e, 3);
|
|
break;
|
|
case 53: // 5
|
|
numberKeyPressed(e, 4);
|
|
break;
|
|
case 54: // 6
|
|
numberKeyPressed(e, 5);
|
|
break;
|
|
case 55: // 7
|
|
numberKeyPressed(e, 6);
|
|
break;
|
|
case 56: // 8
|
|
numberKeyPressed(e, 7);
|
|
break;
|
|
case 57: // 9
|
|
numberKeyPressed(e, 8);
|
|
break;
|
|
case 72: // H
|
|
previousQuestion();
|
|
break;
|
|
case 76: // L
|
|
//nextQuestion();
|
|
break;
|
|
|
|
case 102: // f
|
|
//$("#filter-toggle").click();
|
|
break;
|
|
case 103: // g
|
|
//$("#options").slideDown("slow");
|
|
//$("#goto-question-input").focus();
|
|
//e.preventDefault();
|
|
break;
|
|
|
|
// Vim like scrolling (incredibly important)
|
|
case 106: // j
|
|
//window.scrollBy(0, 25);
|
|
break;
|
|
case 107: // k
|
|
//window.scrollBy(0, -25);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
function loadCornerstoneMainImage(element, image, stack, db, load_as_stack) {
|
|
// It is probably silly to do this each time we load a question
|
|
const PanTool = cornerstoneTools.PanTool;
|
|
const ZoomTool = cornerstoneTools.ZoomTool;
|
|
const ZoomMouseWheelTool = cornerstoneTools.ZoomMouseWheelTool;
|
|
const StackScrollMouseWheelTool = cornerstoneTools.StackScrollMouseWheelTool;
|
|
const WwwcTool = cornerstoneTools.WwwcTool;
|
|
const WwwcRegionTool = cornerstoneTools.WwwcRegionTool;
|
|
const RotateTool = cornerstoneTools.RotateTool;
|
|
const StackScrollTool = cornerstoneTools.StackScrollTool;
|
|
const MagnifyTool = cornerstoneTools.MagnifyTool;
|
|
const ArrowAnnotateTool = cornerstoneTools.ArrowAnnotateTool;
|
|
const RectangleRoiTool = cornerstoneTools.RectangleRoiTool;
|
|
const LengthTool = cornerstoneTools.LengthTool;
|
|
const AngleTool = cornerstoneTools.AngleTool;
|
|
const CobbAngleTool = cornerstoneTools.CobbAngleTool;
|
|
const EraserTool = cornerstoneTools.EraserTool;
|
|
|
|
cornerstone.enable(element);
|
|
element.tabIndex = 0;
|
|
|
|
cornerstone.displayImage(element, image);
|
|
|
|
//cornerstoneTools.keyboardInput.enable(element);
|
|
|
|
cornerstoneTools.addStackStateManager(element, ["stack"]);
|
|
cornerstoneTools.addToolState(element, "stack", stack);
|
|
|
|
cornerstoneTools.addToolForElement(element, PanTool);
|
|
cornerstoneTools.addToolForElement(element, ZoomTool);
|
|
cornerstoneTools.addToolForElement(element, ZoomMouseWheelTool);
|
|
cornerstoneTools.addToolForElement(element, StackScrollMouseWheelTool);
|
|
cornerstoneTools.addToolForElement(element, WwwcTool);
|
|
cornerstoneTools.addToolForElement(element, WwwcRegionTool);
|
|
cornerstoneTools.addToolForElement(element, RotateTool);
|
|
cornerstoneTools.addToolForElement(element, StackScrollTool);
|
|
cornerstoneTools.addToolForElement(element, MagnifyTool);
|
|
cornerstoneTools.addToolForElement(element, LengthTool);
|
|
cornerstoneTools.addToolForElement(element, AngleTool);
|
|
cornerstoneTools.addToolForElement(element, CobbAngleTool);
|
|
cornerstoneTools.addToolForElement(element, EraserTool);
|
|
//cornerstoneTools.addTool(ArrowAnnotateTool);
|
|
//cornerstoneTools.addTool(RectangleRoiTool);
|
|
|
|
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, {
|
|
configuration: {
|
|
getTextCallback: () => {},
|
|
changeTextCallback: () => {},
|
|
allowEmptyLabel: true,
|
|
renderDashed: false,
|
|
drawHandles: false,
|
|
drawHandlesOnHover: true,
|
|
},
|
|
});
|
|
cornerstoneTools.addToolForElement(element, RectangleRoiTool, {
|
|
configuration: {
|
|
showHounsfieldUnits: false,
|
|
showMinMax: false,
|
|
allowEmptyLabel: true,
|
|
renderDashed: false,
|
|
drawHandles: false,
|
|
drawHandlesOnHover: true,
|
|
},
|
|
});
|
|
|
|
cornerstoneTools.setToolEnabledForElement(element, "ArrowAnnotate");
|
|
|
|
|
|
let available_tools = [
|
|
"Pan",
|
|
"Zoom",
|
|
"Wwwc",
|
|
"WwwcRegion",
|
|
"Rotate",
|
|
"StackScroll",
|
|
"Magnify",
|
|
"Length",
|
|
"ArrowAnnotate",
|
|
"RectangleRoi",
|
|
"Angle",
|
|
"CobbAngle",
|
|
"Eraser",
|
|
];
|
|
$(".mouse-binding-select option").remove();
|
|
|
|
available_tools.forEach(function (tool) {
|
|
let option = "<option value=" + tool + ">" + tool + "</option>";
|
|
$(".mouse-binding-select").append(option);
|
|
//$("#left-mouse-dicom").append(option);
|
|
//$("#middle-mouse-dicom").append(option);
|
|
//$("#right-mouse-dicom").append(option);
|
|
});
|
|
$(".mouse-binding-select").on("change", function (evt) {
|
|
changeMouseBinding(evt, db, element);
|
|
});
|
|
|
|
// Set default tools
|
|
$("#primary-mouse-binding .mouse-binding-select[data-button=1]").val("Pan");
|
|
$("#primary-mouse-binding .mouse-binding-select[data-button=2]").val("Wwwc");
|
|
$("#primary-mouse-binding .mouse-binding-select[data-button=3]").val("Zoom");
|
|
$("#primary-mouse-binding .mouse-binding-select[data-button=4]").val("Zoom");
|
|
//cornerstoneTools.setToolActive("Pan", { mouseButtonMask: 1 });
|
|
|
|
if (load_as_stack) {
|
|
cornerstoneTools.setToolActiveForElement(element, "StackScrollMouseWheel", {
|
|
//mouseButtonMask: [4]
|
|
});
|
|
} else {
|
|
cornerstoneTools.setToolActiveForElement(element, "ZoomMouseWheel", {
|
|
//mouseButtonMask: [4]
|
|
});
|
|
}
|
|
//cornerstoneTools.setToolActive("Zoom", { mouseButtonMask: 4 });
|
|
//cornerstoneTools.setToolActive("Wwwc", { mouseButtonMask: 2 });
|
|
$("#secondary-mouse-binding .mouse-binding-select[data-button=1]").val(
|
|
"Magnify"
|
|
);
|
|
$("#secondary-mouse-binding .mouse-binding-select[data-button=2]").val(
|
|
"Rotate"
|
|
);
|
|
$("#secondary-mouse-binding .mouse-binding-select[data-button=3]").val(
|
|
"StackScroll"
|
|
);
|
|
$("#secondary-mouse-binding .mouse-binding-select[data-button=4]").val(
|
|
"WwwcRegion"
|
|
);
|
|
|
|
loadPrimaryDicomInterface(db, element);
|
|
loadAltDicomInterface(db);
|
|
|
|
element.addEventListener("cornerstoneimagerendered", onImageRendered);
|
|
element.addEventListener("cornerstonenewimage", onNewImage);
|
|
//element.addEventListener("cornerstonetoolskeydown", keyDownHandler);
|
|
|
|
//setDicomCanvasNonFullscreen(element);
|
|
cornerstone.reset(element);
|
|
//element.scrollIntoView(false);
|
|
//element.scrollTo(0);
|
|
|
|
$(element).dblclick(function () {
|
|
cornerstone.reset(this);
|
|
});
|
|
|
|
element.removeEventListener("wheel", element.wheelEventHandler);
|
|
|
|
// Add tool selector
|
|
//$(".canvas-panel").append(
|
|
// '<select class="control-overlay"><option value="pan">pan</option><option value="zoom">zoom</option><option value="rotate">rotate</option><option value="scroll" hidden="" disabled="">scroll (1/1)</option><option value="window">window ()</option><option value="abdomen" hidden="" disabled="">window = abdomen</option><option value="pulmonary" hidden="" disabled="">window = pulmonary</option><option value="brain" hidden="" disabled="">window = brain</option><option value="bone" hidden="" disabled="">window = bone</option><option value="reset">reset</option></select>'
|
|
//);
|
|
|
|
//$(".control-overlay")
|
|
// .get(0)
|
|
// .addEventListener("change", changeControlSelection);
|
|
|
|
resizeHandler(element);
|
|
|
|
window.addEventListener("resize", () => {
|
|
resizeHandler(element)
|
|
});
|
|
element.addEventListener(
|
|
"contextmenu",
|
|
function (e) {
|
|
// do something here...
|
|
e.preventDefault();
|
|
},
|
|
false
|
|
);
|
|
|
|
|
|
$(element).on("mouseup", function (event) {
|
|
console.log(event)
|
|
if (event.which == 4) {
|
|
getNextAnnotationImage(element)
|
|
}
|
|
$(".magnifyTool").hide();
|
|
});
|
|
}
|
|
|
|
function resizeHandler(element) {
|
|
let h = window.innerHeight - $("#header").height() - 16;
|
|
$(element).height(h);
|
|
cornerstone.resize(element, true);
|
|
}
|
|
|
|
async function loadPrimaryDicomInterface(db, element) {
|
|
const bindings = await dicom_settings_db.mouse_bindings.where({
|
|
mode: "0"
|
|
}).toArray().catch((err) => {
|
|
console.log(err);
|
|
});
|
|
bindings.forEach(function (b) {
|
|
let sel = $("#primary-mouse-binding select[data-button=" + b.button + "]").get(
|
|
0
|
|
);
|
|
sel.value = b.tool;
|
|
//sel.dispatchEvent(new Event("change"));
|
|
});
|
|
registerPrimaryDicomInterface(element);
|
|
}
|
|
|
|
async function loadAltDicomInterface(db) {
|
|
const bindings = await dicom_settings_db.mouse_bindings.where({
|
|
mode: "1"
|
|
}).toArray().catch((err) => {
|
|
console.log(err);
|
|
});
|
|
bindings.forEach(function (b) {
|
|
let sel = $(
|
|
"#secondary-mouse-binding select[data-button=" + b.button + "]"
|
|
).get(0);
|
|
sel.value = b.tool;
|
|
});
|
|
}
|
|
|
|
function onNewImage(e) {
|
|
let dataSet = e.detail.image.data;
|
|
checkAnonStatus(dataSet)
|
|
}
|
|
|
|
function checkAnonStatus(dataSet) {
|
|
let accession_number = dataSet.string('x00080050').toLowerCase();
|
|
let patient_id = dataSet.string('x00100020').toLowerCase();
|
|
|
|
let not_anon = false;
|
|
|
|
let site_codes = ["ref", "rk9", "ra9", "rh8", "rbz", "rba"]
|
|
|
|
for (let i = 0; i < site_codes.length; i++) {
|
|
if (
|
|
accession_number.startsWith(site_codes[i]) ||
|
|
patient_id.startsWith(site_codes[i])) {
|
|
not_anon = true;
|
|
}
|
|
}
|
|
|
|
if (not_anon) {
|
|
toastr.warning(`File does not appear to be annonymised<br/>Accession: ${accession_number} <br/>Patient ID: ${patient_id}<br/>`, 'Anonymisation warning', {
|
|
"closeButton": false,
|
|
"debug": false,
|
|
"newestOnTop": false,
|
|
"progressBar": false,
|
|
"positionClass": "toast-top-full-width",
|
|
"preventDuplicates": false,
|
|
"onclick": null,
|
|
"showDuration": "0",
|
|
"hideDuration": "0",
|
|
"timeOut": 0,
|
|
"extendedTimeOut": 0,
|
|
"showEasing": "swing",
|
|
"hideEasing": "linear",
|
|
"showMethod": "fadeIn",
|
|
"hideMethod": "fadeOut",
|
|
});
|
|
}
|
|
}
|
|
|
|
// Called when the dicom image is loaded / rendered
|
|
function onImageRendered(e) {
|
|
const eventData = e.detail;
|
|
|
|
// Update ww/wl
|
|
$("#wc").text(Math.round(eventData.viewport.voi.windowCenter));
|
|
$("#ww").text(Math.round(eventData.viewport.voi.windowWidth));
|
|
|
|
// update stack data
|
|
let stack = eventData.enabledElement.toolStateManager.toolState.stack.data[0];
|
|
|
|
$("#total_image_number").text(stack.imageIds.length);
|
|
$("#current_image_number").text(parseInt(stack.currentImageIdIndex) + 1);
|
|
|
|
if (stack.imageIds.length > 1) {
|
|
$(".thumb").removeClass("thumb-active");
|
|
$("#thumb-" + stack.currentImageIdIndex).addClass("thumb-active");
|
|
}
|
|
//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 +
|
|
// ")"
|
|
// );
|
|
}
|
|
|
|
function stopEvent(evt) {
|
|
//console.log("stop", evt)
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
}
|
|
|
|
function selectThumbClick(evt, element) {
|
|
let new_index = evt.currentTarget.dataset.id;
|
|
loadStackIndex(new_index, element);
|
|
evt.preventDefault();
|
|
}
|
|
|
|
|
|
export function registerPrimaryDicomInterface(element) {
|
|
console.log("reg primary")
|
|
// Set mousetools based upon the selected options
|
|
let selections = $("#primary-mouse-binding select");
|
|
selections.each((i, option) => {
|
|
cornerstoneTools.setToolActiveForElement(element, option.value, {
|
|
mouseButtonMask: parseInt(option.dataset.button)
|
|
});
|
|
});
|
|
}
|
|
|
|
export function registerAltDicomInterface(e, element) {
|
|
// Called when control is pressed
|
|
|
|
// Set mousetools based upon the selected options
|
|
//let element = document.getElementsByClassName("cornerstone-element")[0];
|
|
console.log("reg alt", element)
|
|
let selections = $("#secondary-mouse-binding select");
|
|
selections.each((i, option) => {
|
|
cornerstoneTools.setToolActiveForElement(element, option.value, {
|
|
mouseButtonMask: parseInt(option.dataset.button)
|
|
});
|
|
});
|
|
}
|
|
|
|
function changeMouseBinding(evt, db, element) {
|
|
let select = evt.currentTarget;
|
|
let button = select.dataset.button;
|
|
let mode = select.dataset.mode;
|
|
let tool = select.value;
|
|
|
|
// Directly activate primary tools (secondary will be activated when modifier
|
|
// key is pressed
|
|
if (mode == "0") {
|
|
cornerstoneTools.setToolActiveForElement(element, tool, {
|
|
mouseButtonMask: parseInt(button)
|
|
});
|
|
}
|
|
|
|
dicom_settings_db.mouse_bindings.put({
|
|
button: button,
|
|
mode: mode,
|
|
tool: tool
|
|
});
|
|
//db.mouse_bindings.put({button: button, mode: mode, tool: tool}).then(loadPrimaryDicomInterface());
|
|
}
|
|
|
|
export function loadStackIndex(new_index, dicom_element) {
|
|
console.log("select new stack index", new_index);
|
|
// There must be a better way to do this...
|
|
if (dicom_element == null) {
|
|
return;
|
|
}
|
|
let c = cornerstone.getEnabledElement(dicom_element);
|
|
// let current_index =
|
|
// c.toolStateManager.toolState.stack.data[0].currentImageIdIndex;
|
|
c.toolStateManager.toolState.stack.data[0].currentImageIdIndex = new_index;
|
|
let id = c.toolStateManager.toolState.stack.data[0].imageIds[new_index];
|
|
////console.log("select thumb id", id);
|
|
//console.log("select thumb el", dicom_element);
|
|
cornerstone.loadImage(id).then(b => {
|
|
//console.log("b", b);
|
|
cornerstone.displayImage(dicom_element, b);
|
|
});
|
|
//c = cornerstone.getEnabledElement(dicom_element)
|
|
}
|
|
|
|
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 dumpDataSet(dataSet) {
|
|
$('span[data-dicom]').each(function (index, value) {
|
|
var attr = $(value).attr('data-dicom');
|
|
var element = dataSet.elements[attr];
|
|
var text = "";
|
|
if (element !== undefined) {
|
|
var str = dataSet.string(attr);
|
|
if (str !== undefined) {
|
|
text = str;
|
|
}
|
|
}
|
|
$(value).text(text);
|
|
});
|
|
|
|
$('span[data-dicomUint]').each(function (index, value) {
|
|
var attr = $(value).attr('data-dicomUint');
|
|
var element = dataSet.elements[attr];
|
|
var text = "";
|
|
if (element !== undefined) {
|
|
if (element.length === 2) {
|
|
text += dataSet.uint16(attr);
|
|
} else if (element.length === 4) {
|
|
text += dataSet.uint32(attr);
|
|
}
|
|
}
|
|
|
|
$(value).text(text);
|
|
});
|
|
|
|
}
|
|
|
|
export function getNextAnnotationImage(element) {
|
|
const enabled_element = cornerstone.getEnabledElement(element)
|
|
const image_ids = enabled_element.toolStateManager.toolState.stack.data[0].imageIds;
|
|
const current_image = enabled_element.toolStateManager.toolState.stack.data[0].currentImageIdIndex;
|
|
const annotated_images = Object.getOwnPropertyNames(cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState());
|
|
|
|
if (annotated_images.length > 0) {
|
|
let offset = current_image;
|
|
for (let i = 0; i < image_ids.length; i++) {
|
|
let pointer = (i + offset) % image_ids.length;
|
|
if (annotated_images.includes(image_ids[pointer])) {
|
|
loadStackIndex(pointer, element);
|
|
|
|
}
|
|
}
|
|
}
|
|
} |