diff --git a/anatomy/static/js/anatomy.js b/anatomy/static/js/anatomy.js
index 05e04981..55d78abc 100644
--- a/anatomy/static/js/anatomy.js
+++ b/anatomy/static/js/anatomy.js
@@ -35,57 +35,22 @@ $(document).ready(function () {
});
}
- if($(".dwv-container").length) {
- $(".dwv-container").append($('
Only for HTML5 compatible browsers... '));
+ if($("#dicom-element").length) {
+const element = document.getElementById('dicom-element');
+const imageId = element.dataset.url;
- var app = new dwv.App();
- dwvapp = app;
- //DEBUG
- var listenerWL = function (event) {
- console.log("event: " + event.type);
- console.log(event);
- $(".infotc").text(event.wc);
- };
- app.addEventListener("wl-width-change", listenerWL);
- //app.addEventListener("wl-center-change", listener);
- app.init({
+cornerstone.enable(element);
+cornerstone.loadAndCacheImage(imageId).then(function(image) {
+ cornerstone.displayImage(element, image);
- "containerDivId": "dwv",
- "fitToWindow": true,
- "isMobile": true,
- "gui": ["tool"],
- "filters": ["Threshold", "Sharpen", "Sobel"],
- "tools": ["Scroll", "WindowLevel", "ZoomAndPan"], // or try "ZoomAndPan"
- });
-
-
-
-
- var range = document.getElementById("sliceRange");
- range.min = 0;
- app.addEventListener("load-end", function () {
- range.max = app.getImage().getGeometry().getSize().getNumberOfSlices() - 1;
-
- if(range.max == 0) {
- $(range).hide();
- }
- });
- app.addEventListener("slice-change", function () {
- range.value = app.getViewController().getCurrentPosition().k;
- });
- range.oninput = function () {
- var pos = app.getViewController().getCurrentPosition();
- pos.k = this.value;
- app.getViewController().setCurrentPosition(pos);
- }
-
- try {
- app.loadURLs([$(".dwv-container").data("url")]);
- } catch(error) {
- toastr.error(error.message);
-
- }
- dwv.gui.appendResetHtml(app);
+ // Enable our tools
+ cornerstoneTools.mouseInput.enable(element);
+ cornerstoneTools.mouseWheelInput.enable(element);
+ cornerstoneTools.wwwc.activate(element, 1); // Left Click
+ cornerstoneTools.pan.activate(element, 2); // Middle Click
+ cornerstoneTools.zoom.activate(element, 4); // Right Click
+ cornerstoneTools.zoomWheel.activate(element); // Mouse Wheel
+});
}
});
diff --git a/anatomy/static/js/dwv/appgui.js b/anatomy/static/js/dwv/appgui.js
deleted file mode 100644
index acfd931c..00000000
--- a/anatomy/static/js/dwv/appgui.js
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
- * Application GUI.
- */
-
-// Default window level presets.
-dwv.tool.defaultpresets = {
- "default (127+/-255)": {"center": 127, "width": 255},
- //"default": {"center": 127, "width": 100},
-};
-dwv.tool.defaultpresets.SC = {
- "default (127+/-255)": {"center": 127, "width": 255},
-};
-// Default window level presets for CT.
-dwv.tool.defaultpresets.CT = {
- "mediastinum": {"center": 40, "width": 400},
- "lung": {"center": -500, "width": 1500},
- "bone": {"center": 500, "width": 2000},
-};
-
-// decode query
-dwv.utils.decodeQuery = dwv.utils.base.decodeQuery;
-
-// Window
-dwv.gui.getWindowSize = function() {
-d = dwv.gui.base.getWindowSize();
-//return {"width": d["width"], "height": d["height"]+100}
-h = $(window).height() - ($(document).height()-d["height"]) - 100;
-return {"width": d["width"], "height": h}
-}
-
-// Progress
-dwv.gui.displayProgress = function (/*percent*/) { /*does nothing*/ };
-// get element
-dwv.gui.getElement = dwv.gui.base.getElement;
-// refresh
-dwv.gui.refreshElement = dwv.gui.base.refreshElement;
-// Slider
-dwv.gui.Slider = null;
-// Tags table
-dwv.gui.DicomTags = null;
-
-// Toolbox
-dwv.gui.Toolbox = function (app)
-{
- this.setup = function (/*list*/)
- {
- // does nothing
- };
- this.display = function (/*bool*/)
- {
- // does nothing
- };
- this.initialise = function (list)
- {
- // not wonderful: first one should be scroll is more than one slice
- if ( list[0] === false ) {
- var inputScroll = app.getElement("scroll-button");
- inputScroll.style.display = "none";
- var inputZoom = app.getElement("zoom-button");
- inputZoom.checked = true;
- }
- };
-};
-
-// Window/level
-dwv.gui.WindowLevel = function (app)
-{
- this.setup = function ()
- {
- var button = document.createElement("button");
- button.className = "wl-button";
- button.value = "WindowLevel";
- button.onclick = app.onChangeTool;
- button.appendChild(document.createTextNode("Window/Level"));
-
- var node = app.getElement("toolbar");
- node.appendChild(button);
- };
- this.display = function (bool)
- {
- var button = app.getElement("wl-button");
- button.disabled = bool;
- };
- this.initialise = function ()
- {
- // clear previous
- dwv.html.removeNode(app.getElement("presetSelect"));
- dwv.html.removeNode(app.getElement("presetLabel"));
-
- // create preset select
- var select = dwv.html.createHtmlSelect("presetSelect",
- app.getViewController().getWindowLevelPresetsNames(), "wl.presets", true);
- select.className = "presetSelect";
- select.onchange = app.onChangeWindowLevelPreset;
- select.title = "Select w/l preset.";
- select.setAttribute("data-inline","true");
- var label = document.createElement("label");
- label.className = "presetLabel";
- label.setAttribute("for", "presetSelect");
- label.appendChild(document.createTextNode("Select Preset"));
-
- var node = app.getElement("toolbar");
- node.appendChild(label);
- node.appendChild(select);
- };
-};
-
-// Zoom
-dwv.gui.ZoomAndPan = function (app)
-{
- this.setup = function ()
- {
- var button = document.createElement("button");
- button.className = "zoom-button";
- button.value = "ZoomAndPan";
- button.onclick = app.onChangeTool;
- button.appendChild(document.createTextNode("ZoomAndPan"));
-
- var node = app.getElement("toolbar");
- node.appendChild(button);
- };
- this.display = function (bool)
- {
- var button = app.getElement("zoom-button");
- button.disabled = bool;
- };
-};
-
-// Scroll
-dwv.gui.Scroll = function (app)
-{
- this.setup = function ()
- {
- var button = document.createElement("button");
- button.className = "scroll-button";
- button.value = "Scroll";
- button.onclick = app.onChangeTool;
- button.appendChild(document.createTextNode("Scroll"));
-
- var node = app.getElement("toolbar");
- node.appendChild(button);
- };
- this.display = function (bool)
- {
- var button = app.getElement("scroll-button");
- button.disabled = bool;
- };
-};
-
-dwv.gui.Slider = dwv.gui.base.Slider;
-// NEEDS UI
-dwv.gui.Filter = dwv.gui.base.Filter;
-
-
-// Filter: threshold
-dwv.gui.Threshold = dwv.gui.base.Threshold;
-// Filter: sharpen
-dwv.gui.Sharpen = dwv.gui.base.Sharpen;
-// Filter: sobel
-dwv.gui.Sobel = dwv.gui.base.Sobel;
-
-
-//Reset
-dwv.gui.appendResetHtml = function (app)
-{
- var button = document.createElement("button");
- button.className = "reset-button";
- button.value = "reset";
- button.onclick = app.onDisplayReset;
- button.appendChild(document.createTextNode("Reset"));
-
- var node = app.getElement("toolbar");
- node.appendChild(button);
-};
diff --git a/anatomy/static/js/dwv/dwv.js b/anatomy/static/js/dwv/dwv.js
deleted file mode 100644
index cb2ef018..00000000
--- a/anatomy/static/js/dwv/dwv.js
+++ /dev/null
@@ -1,26806 +0,0 @@
-// Inspired from umdjs
-// See https://github.com/umdjs/umd/blob/master/templates/returnExports.js
-(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
-
- // magic-wand-js: not importable?
-
- define([
- 'i18next',
- 'i18next-xhr-backend',
- 'i18next-browser-languagedetector',
- 'jszip',
- 'konva'
- ], factory);
- } else if (typeof module === 'object' && module.exports) {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like environments that support module.exports,
- // like Node.
-
- // i18next-xhr-backend: requires XMlHttpRequest
- // Konva (requires 'canvas') and MagicWand are gui specific
- // -> deactivated for now...
-
- module.exports = factory(
- require('i18next'),
- require('i18next-xhr-backend'),
- require('i18next-browser-languagedetector'),
- require('jszip'),
- null,
- null
- );
- } else {
- // Browser globals (root is window)
- root.dwv = factory(
- root.i18next,
- root.i18nextXHRBackend,
- root.i18nextBrowserLanguageDetector,
- root.JSZip,
- root.Konva,
- root.MagicWand
- );
- }
-}(this, function (
- i18next,
- i18nextXHRBackend,
- i18nextBrowserLanguageDetector,
- JSZip,
- Konva,
- MagicWand) {
-
- // similar to what browserify does but reversed
- //https://www.contentful.com/blog/2017/01/17/the-global-object-in-javascript/
- var window = typeof window !== 'undefined' ?
- window : typeof self !== 'undefined' ?
- self : typeof global !== 'undefined' ?
- global : {};
-
-/** @namespace */
-var dwv = dwv || {};
-
-/**
- * Main application class.
- * @constructor
- */
-dwv.App = function ()
-{
- // Local object
- var self = this;
-
- // Image
- var image = null;
- // Original image
- var originalImage = null;
- // Image data array
- var imageData = null;
- // Image data width
- var dataWidth = 0;
- // Image data height
- var dataHeight = 0;
- // Is the data mono-slice?
- var isMonoSliceData = 0;
-
- // Default character set
- var defaultCharacterSet;
-
- // Container div id
- var containerDivId = null;
- // Display window scale
- var windowScale = 1;
- // Fit display to window flag
- var fitToWindow = false;
- // main scale
- var scale = 1;
- // zoom center
- var scaleCenter = {"x": 0, "y": 0};
- // translation
- var translation = {"x": 0, "y": 0};
-
- // View
- var view = null;
- // View controller
- var viewController = null;
-
- // Info layer controller
- var infoController = null;
-
- // Dicom tags gui
- var tagsGui = null;
-
- // Drawing list gui
- var drawListGui = null;
-
- // Image layer
- var imageLayer = null;
-
- // Draw controller
- var drawController = null;
-
- // Generic style
- var style = new dwv.html.Style();
-
- // Toolbox controller
- var toolboxController = null;
-
- // Loadbox
- var loadbox = null;
- // Current loader
- var currentLoader = null;
-
- // UndoStack
- var undoStack = null;
-
- // listeners
- var listeners = {};
-
- // help resources path
- var helpResourcesPath = "./";
-
- /**
- * Get the image.
- * @return {Image} The associated image.
- */
- this.getImage = function () { return image; };
- /**
- * Set the view.
- * @param {Image} img The associated image.
- */
- this.setImage = function (img)
- {
- image = img;
- view.setImage(img);
- };
- /**
- * Restore the original image.
- */
- this.restoreOriginalImage = function ()
- {
- image = originalImage;
- view.setImage(originalImage);
- };
- /**
- * Get the image data array.
- * @return {Array} The image data array.
- */
- this.getImageData = function () { return imageData; };
- /**
- * Is the data mono-slice?
- * @return {Boolean} True if the data is mono-slice.
- */
- this.isMonoSliceData = function () { return isMonoSliceData; };
-
- /**
- * Get the main scale.
- * @return {Number} The main scale.
- */
- this.getScale = function () { return scale / windowScale; };
-
- /**
- * Get the window scale.
- * @return {Number} The window scale.
- */
- this.getWindowScale = function () { return windowScale; };
-
- /**
- * Get the scale center.
- * @return {Object} The coordinates of the scale center.
- */
- this.getScaleCenter = function () { return scaleCenter; };
-
- /**
- * Get the translation.
- * @return {Object} The translation.
- */
- this.getTranslation = function () { return translation; };
-
- /**
- * Get the view controller.
- * @return {Object} The controller.
- */
- this.getViewController = function () { return viewController; };
-
- /**
- * Get the draw controller.
- * @return {Object} The controller.
- */
- this.getDrawController = function () { return drawController; };
-
- /**
- * Get the image layer.
- * @return {Object} The image layer.
- */
- this.getImageLayer = function () { return imageLayer; };
-
- /**
- * Get the draw stage.
- * @return {Object} The draw stage.
- */
- this.getDrawStage = function () {
- return drawController.getDrawStage();
- };
-
- /**
- * Get the app style.
- * @return {Object} The app style.
- */
- this.getStyle = function () { return style; };
-
- /**
- * Get the help resources path.
- * @return {String} The path.
- */
- this.getHelpResourcesPath = function () {
- return helpResourcesPath;
- };
-
- /**
- * Add a command to the undo stack.
- * @param {Object} The command to add.
- */
- this.addToUndoStack = function (cmd) {
- if ( undoStack !== null ) {
- undoStack.add(cmd);
- }
- };
-
- /**
- * Initialise the HTML for the application.
- */
- this.init = function ( config ) {
- containerDivId = config.containerDivId;
- // tools
- if ( config.tools && config.tools.length !== 0 ) {
- // setup the tool list
- var toolList = {};
- for ( var t = 0; t < config.tools.length; ++t ) {
- var toolName = config.tools[t];
- if ( toolName === "Draw" ) {
- if ( config.shapes !== 0 ) {
- // setup the shape list
- var shapeList = {};
- for ( var s = 0; s < config.shapes.length; ++s ) {
- var shapeName = config.shapes[s];
- var shapeFactoryClass = shapeName+"Factory";
- if (typeof dwv.tool[shapeFactoryClass] !== "undefined") {
- shapeList[shapeName] = dwv.tool[shapeFactoryClass];
- }
- else {
- console.warn("Could not initialise unknown shape: "+shapeName);
- }
- }
- toolList.Draw = new dwv.tool.Draw(this, shapeList);
- toolList.Draw.addEventListener("draw-create", fireEvent);
- toolList.Draw.addEventListener("draw-change", fireEvent);
- toolList.Draw.addEventListener("draw-move", fireEvent);
- toolList.Draw.addEventListener("draw-delete", fireEvent);
- }
- }
- else if ( toolName === "Filter" ) {
- if ( config.filters.length !== 0 ) {
- // setup the filter list
- var filterList = {};
- for ( var f = 0; f < config.filters.length; ++f ) {
- var filterName = config.filters[f];
- if (typeof dwv.tool.filter[filterName] !== "undefined") {
- filterList[filterName] = new dwv.tool.filter[filterName](this);
- }
- else {
- console.warn("Could not initialise unknown filter: "+filterName);
- }
- }
- toolList.Filter = new dwv.tool.Filter(filterList, this);
- toolList.Filter.addEventListener("filter-run", fireEvent);
- toolList.Filter.addEventListener("filter-undo", fireEvent);
- }
- }
- else {
- // default: find the tool in the dwv.tool namespace
- var toolClass = toolName;
- if (typeof dwv.tool[toolClass] !== "undefined") {
- toolList[toolClass] = new dwv.tool[toolClass](this);
- if (typeof toolList[toolClass].addEventListener !== "undefined") {
- toolList[toolClass].addEventListener(fireEvent);
- }
- }
- else {
- console.warn("Could not initialise unknown tool: "+toolName);
- }
- }
- }
- toolboxController = new dwv.ToolboxController();
- toolboxController.create(toolList, this);
- }
- // gui
- if ( config.gui ) {
- // tools
- if ( config.gui.indexOf("tool") !== -1 && toolboxController) {
- toolboxController.setup();
- }
- // load
- if ( config.gui.indexOf("load") !== -1 ) {
- var loaderList = {};
- for ( var l = 0; l < config.loaders.length; ++l ) {
- var loaderName = config.loaders[l];
- var loaderClass = loaderName + "Load";
- // default: find the loader in the dwv.gui namespace
- if (typeof dwv.gui[loaderClass] !== "undefined") {
- loaderList[loaderName] = new dwv.gui[loaderClass](this);
- }
- else {
- console.warn("Could not initialise unknown loader: "+loaderName);
- }
- }
- loadbox = new dwv.gui.Loadbox(this, loaderList);
- loadbox.setup();
- var loaderKeys = Object.keys(loaderList);
- for ( var lk = 0; lk < loaderKeys.length; ++lk ) {
- loaderList[loaderKeys[lk]].setup();
- }
- loadbox.displayLoader(loaderKeys[0]);
- }
- // undo
- if ( config.gui.indexOf("undo") !== -1 ) {
- undoStack = new dwv.tool.UndoStack(this);
- undoStack.setup();
- }
- // DICOM Tags
- if ( config.gui.indexOf("tags") !== -1 ) {
- tagsGui = new dwv.gui.DicomTags(this);
- }
- // Draw list
- if ( config.gui.indexOf("drawList") !== -1 ) {
- drawListGui = new dwv.gui.DrawList(this);
- // update list on draw events
- this.addEventListener("draw-create", drawListGui.update);
- this.addEventListener("draw-change", drawListGui.update);
- this.addEventListener("draw-delete", drawListGui.update);
- }
- // version number
- if ( config.gui.indexOf("version") !== -1 ) {
- dwv.gui.appendVersionHtml(dwv.getVersion());
- }
- // help
- if ( config.gui.indexOf("help") !== -1 ) {
- var isMobile = true;
- if ( config.isMobile !== "undefined" ) {
- isMobile = config.isMobile;
- }
- // help resources path
- if ( typeof config.helpResourcesPath !== "undefined" ) {
- helpResourcesPath = config.helpResourcesPath;
- }
- dwv.gui.appendHelpHtml( toolboxController.getToolList(), isMobile, this );
- }
- }
-
- // listen to drag&drop
- var box = this.getElement("dropBox");
- if ( box ) {
- box.addEventListener("dragover", onDragOver);
- box.addEventListener("dragleave", onDragLeave);
- box.addEventListener("drop", onDrop);
- // initial size
- var size = dwv.gui.getWindowSize();
- var dropBoxSize = 2 * size.height / 3;
- box.setAttribute("style","width:"+dropBoxSize+"px;height:"+dropBoxSize+"px");
- }
-
- // possible load from URL
- if ( typeof config.skipLoadUrl === "undefined" ) {
- var query = dwv.utils.getUriQuery(window.location.href);
- // check query
- if ( query && typeof query.input !== "undefined" ) {
- dwv.utils.decodeQuery(query, this.onInputURLs);
- // optional display state
- if ( typeof query.state !== "undefined" ) {
- var onLoadEnd = function (/*event*/) {
- loadStateUrl(query.state);
- };
- this.addEventListener( "load-end", onLoadEnd );
- }
- }
- }
- else{
- console.log("Not loading url from address since skipLoadUrl is defined.");
- }
-
- // align layers when the window is resized
- if ( config.fitToWindow ) {
- fitToWindow = true;
- window.onresize = this.onResize;
- }
-
- // default character set
- if ( typeof config.defaultCharacterSet !== "undefined" ) {
- defaultCharacterSet = config.defaultCharacterSet;
- }
- };
-
- /**
- * Get a HTML element associated to the application.
- * @param name The name or id to find.
- * @return The found element or null.
- */
- this.getElement = function (name)
- {
- return dwv.gui.getElement(containerDivId, name);
- };
-
- /**
- * Reset the application.
- */
- this.reset = function ()
- {
- // clear tools
- if ( toolboxController ) {
- toolboxController.reset();
- }
- // clear draw
- if ( drawController ) {
- drawController.reset();
- }
- // clear objects
- image = null;
- view = null;
- isMonoSliceData = false;
- // reset undo/redo
- if ( undoStack ) {
- undoStack = new dwv.tool.UndoStack(this);
- undoStack.initialise();
- }
- };
-
- /**
- * Reset the layout of the application.
- */
- this.resetLayout = function () {
- var previousScale = scale;
- var previousSC = scaleCenter;
- var previousTrans = translation;
- // reset values
- scale = windowScale;
- scaleCenter = {"x": 0, "y": 0};
- translation = {"x": 0, "y": 0};
- // apply new values
- if ( imageLayer ) {
- imageLayer.resetLayout(windowScale);
- imageLayer.draw();
- }
- if ( drawController ) {
- drawController.resetStage(windowScale);
- }
- // fire events
- if (previousScale != scale) {
- fireEvent({"type": "zoom-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y });
- }
- if ( (previousSC.x !== scaleCenter.x || previousSC.y !== scaleCenter.y) ||
- (previousTrans.x !== translation.x || previousTrans.y !== translation.y)) {
- fireEvent({"type": "offset-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y });
- }
- };
-
- /**
- * Add an event listener on the app.
- * @param {String} type The event type.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.addEventListener = function (type, listener)
- {
- if ( typeof listeners[type] === "undefined" ) {
- listeners[type] = [];
- }
- listeners[type].push(listener);
- };
-
- /**
- * Remove an event listener from the app.
- * @param {String} type The event type.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.removeEventListener = function (type, listener)
- {
- if( typeof listeners[type] === "undefined" ) {
- return;
- }
- for ( var i = 0; i < listeners[type].length; ++i )
- {
- if ( listeners[type][i] === listener ) {
- listeners[type].splice(i,1);
- }
- }
- };
-
- /**
- * Load a list of files. Can be image files or a state file.
- * @param {Array} files The list of files to load.
- */
- this.loadFiles = function (files)
- {
- // has been checked for emptiness.
- var ext = files[0].name.split('.').pop().toLowerCase();
- if ( ext === "json" ) {
- loadStateFile(files[0]);
- }
- else {
- loadImageFiles(files);
- }
- };
-
- /**
- * Load a list of image files.
- * @private
- * @param {Array} files The list of image files to load.
- */
- function loadImageFiles(files)
- {
- // create IO
- var fileIO = new dwv.io.FilesLoader();
- // load data
- loadImageData(files, fileIO);
- }
-
- /**
- * Load a State file.
- * @private
- * @param {String} file The state file to load.
- */
- function loadStateFile(file)
- {
- // create IO
- var fileIO = new dwv.io.FilesLoader();
- // load data
- loadStateData([file], fileIO);
- }
-
- /**
- * Load a list of URLs. Can be image files or a state file.
- * @param {Array} urls The list of urls to load.
- * @param {Array} requestHeaders An array of {name, value} to use as request headers.
- */
- this.loadURLs = function (urls, requestHeaders)
- {
- // has been checked for emptiness.
- var ext = urls[0].split('.').pop().toLowerCase();
- if ( ext === "json" ) {
- loadStateUrl(urls[0], requestHeaders);
- }
- else {
- loadImageUrls(urls, requestHeaders);
- }
- };
-
- /**
- * Abort the current load.
- */
- this.abortLoad = function ()
- {
- if ( currentLoader ) {
- currentLoader.abort();
- currentLoader = null;
- }
- };
-
- /**
- * Load a list of ArrayBuffers.
- * @param {Array} data The list of ArrayBuffers to load
- * in the form of [{name: "", filename: "", data: data}].
- */
- this.loadImageObject = function (data)
- {
- // create IO
- var memoryIO = new dwv.io.MemoryLoader();
- // create options
- var options = {};
- // load data
- loadImageData(data, memoryIO, options);
- };
-
- /**
- * Load a list of image URLs.
- * @private
- * @param {Array} urls The list of urls to load.
- * @param {Array} requestHeaders An array of {name, value} to use as request headers.
- */
- function loadImageUrls(urls, requestHeaders)
- {
- // create IO
- var urlIO = new dwv.io.UrlsLoader();
- // create options
- var options = {'requestHeaders': requestHeaders};
- // load data
- loadImageData(urls, urlIO, options);
- }
-
- /**
- * Load a State url.
- * @private
- * @param {String} url The state url to load.
- * @param {Array} requestHeaders An array of {name, value} to use as request headers.
- */
- function loadStateUrl(url, requestHeaders)
- {
- // create IO
- var urlIO = new dwv.io.UrlsLoader();
- // create options
- var options = {'requestHeaders': requestHeaders};
- // load data
- loadStateData([url], urlIO, options);
- }
-
- /**
- * Load a list of image data.
- * @private
- * @param {Array} data Array of data to load.
- * @param {Object} loader The data loader.
- * @param {Object} options Options passed to the final loader.
- */
- function loadImageData(data, loader, options)
- {
- // store loader
- currentLoader = loader;
-
- // allow to cancel
- var previousOnKeyDown = window.onkeydown;
- window.onkeydown = function (event) {
- if (event.ctrlKey && event.keyCode === 88 ) // crtl-x
- {
- console.log("crtl-x pressed!");
- self.abortLoad();
- }
- };
-
- // clear variables
- self.reset();
- // first data name
- var firstName = "";
- if (typeof data[0].name !== "undefined") {
- firstName = data[0].name;
- } else {
- firstName = data[0];
- }
- // flag used by scroll to decide wether to activate or not
- // TODO: supposing multi-slice for zip files, could not be...
- isMonoSliceData = (data.length === 1 &&
- firstName.split('.').pop().toLowerCase() !== "zip");
- // set IO
- loader.setDefaultCharacterSet(defaultCharacterSet);
- loader.onload = function (data) {
- if ( image ) {
- view.append( data.view );
- if ( drawController ) {
- //drawController.appendDrawLayer(image.getNumberOfFrames());
- }
- }
- postLoadInit(data);
- };
- loader.onerror = function (error) { handleError(error); };
- loader.onabort = function (error) { handleAbort(error); };
- loader.onloadend = function (/*event*/) {
- window.onkeydown = previousOnKeyDown;
- if ( drawController ) {
- drawController.activateDrawLayer(viewController);
- }
- fireEvent({type: "load-progress", lengthComputable: true,
- loaded: 100, total: 100});
- fireEvent({ 'type': 'load-end' });
- // reset member
- currentLoader = null;
- };
- loader.onprogress = onLoadProgress;
- // main load (asynchronous)
- fireEvent({ 'type': 'load-start' });
- loader.load(data, options);
- }
-
- /**
- * Load a State data.
- * @private
- * @param {Array} data Array of data to load.
- * @param {Object} loader The data loader.
- * @param {Object} options Options passed to the final loader.
- */
- function loadStateData(data, loader, options)
- {
- // set IO
- loader.onload = function (data) {
- // load state
- var state = new dwv.State();
- state.apply( self, state.fromJSON(data) );
- };
- loader.onerror = function (error) { handleError(error); };
- // main load (asynchronous)
- loader.load(data, options);
- }
-
- /**
- * Fit the display to the given size. To be called once the image is loaded.
- */
- this.fitToSize = function (size)
- {
- // previous width
- var oldWidth = parseInt(windowScale*dataWidth, 10);
- // find new best fit
- windowScale = Math.min( (size.width / dataWidth), (size.height / dataHeight) );
- // new sizes
- var newWidth = parseInt(windowScale*dataWidth, 10);
- var newHeight = parseInt(windowScale*dataHeight, 10);
- // ratio previous/new to add to zoom
- var mul = newWidth / oldWidth;
- scale *= mul;
-
- // update style
- style.setScale(windowScale);
-
- // resize container
- var container = this.getElement("layerContainer");
- container.setAttribute("style","width:"+newWidth+"px;height:"+newHeight+"px");
- // resize image layer
- if ( imageLayer ) {
- imageLayer.setWidth(newWidth);
- imageLayer.setHeight(newHeight);
- imageLayer.zoom(scale, scale, 0, 0);
- imageLayer.draw();
- }
- // resize draw stage
- if ( drawController ) {
- drawController.resizeStage(newWidth, newHeight, scale);
- }
- };
-
- /**
- * Toggle the display of the information layer.
- */
- this.toggleInfoLayerDisplay = function ()
- {
- // toggle html
- var infoLayer = self.getElement("infoLayer");
- dwv.html.toggleDisplay(infoLayer);
- // toggle listeners
- infoController.toggleListeners(self, view);
- };
-
- /**
- * Init the Window/Level display
- */
- this.initWLDisplay = function ()
- {
- // set window/level to first preset
- viewController.setWindowLevelPresetById(0);
- // default position
- viewController.setCurrentPosition2D(0,0);
- // default frame
- viewController.setCurrentFrame(0);
- };
-
- /**
- * Add canvas mouse and touch listeners.
- * @param {Object} canvas The canvas to listen to.
- */
- this.addToolCanvasListeners = function (layer)
- {
- toolboxController.addCanvasListeners(layer);
- };
-
- /**
- * Remove layer mouse and touch listeners.
- * @param {Object} canvas The canvas to stop listening to.
- */
- this.removeToolCanvasListeners = function (layer)
- {
- toolboxController.removeCanvasListeners(layer);
- };
-
- /**
- * Render the current image.
- */
- this.render = function ()
- {
- generateAndDrawImage();
- };
-
- /**
- * Zoom to the layers.
- * @param {Number} zoom The zoom to apply.
- * @param {Number} cx The zoom center X coordinate.
- * @param {Number} cy The zoom center Y coordinate.
- */
- this.zoom = function (zoom, cx, cy) {
- scale = zoom * windowScale;
- if ( scale <= 0.1 ) {
- scale = 0.1;
- }
- scaleCenter = {"x": cx, "y": cy};
- zoomLayers();
- };
-
- /**
- * Add a step to the layers zoom.
- * @param {Number} step The zoom step increment. A good step is of 0.1.
- * @param {Number} cx The zoom center X coordinate.
- * @param {Number} cy The zoom center Y coordinate.
- */
- this.stepZoom = function (step, cx, cy) {
- scale += step;
- if ( scale <= 0.1 ) {
- scale = 0.1;
- }
- scaleCenter = {"x": cx, "y": cy};
- zoomLayers();
- };
-
- /**
- * Apply a translation to the layers.
- * @param {Number} tx The translation along X.
- * @param {Number} ty The translation along Y.
- */
- this.translate = function (tx, ty)
- {
- translation = {"x": tx, "y": ty};
- translateLayers();
- };
-
- /**
- * Add a translation to the layers.
- * @param {Number} tx The step translation along X.
- * @param {Number} ty The step translation along Y.
- */
- this.stepTranslate = function (tx, ty)
- {
- var txx = translation.x + tx / scale;
- var tyy = translation.y + ty / scale;
- translation = {"x": txx, "y": tyy};
- translateLayers();
- };
-
- /**
- * Get the list of drawing display details.
- * @return {Object} The list of draw details including id, slice, frame...
- */
- this.getDrawDisplayDetails = function ()
- {
- return drawController.getDrawDisplayDetails();
- };
-
- /**
- * Get a list of drawing store details.
- * @return {Object} A list of draw details including id, text, quant...
- */
- this.getDrawStoreDetails = function ()
- {
- return drawController.getDrawStoreDetails();
- };
- /**
- * Set the drawings on the current stage.
- * @param {Array} drawings An array of drawings.
- * @param {Array} drawingsDetails An array of drawings details.
- */
- this.setDrawings = function (drawings, drawingsDetails)
- {
- drawController.setDrawings(drawings, drawingsDetails, fireEvent, this.addToUndoStack);
- drawController.activateDrawLayer(viewController);
- };
- /**
- * Update a drawing from its details.
- * @param {Object} drawDetails Details of the drawing to update.
- */
- this.updateDraw = function (drawDetails)
- {
- drawController.updateDraw(drawDetails);
- };
- /**
- * Delete all Draws from all layers.
- */
- this.deleteDraws = function () {
- drawController.deleteDraws(fireEvent, this.addToUndoStack);
- };
- /**
- * Check the visibility of a given group.
- * @param {Object} drawDetails Details of the drawing to check.
- */
- this.isGroupVisible = function (drawDetails)
- {
- return drawController.isGroupVisible(drawDetails);
- };
- /**
- * Toggle group visibility.
- * @param {Object} drawDetails Details of the drawing to update.
- */
- this.toogleGroupVisibility = function (drawDetails)
- {
- drawController.toogleGroupVisibility(drawDetails);
- };
-
- // Handler Methods -----------------------------------------------------------
-
- /**
- * Handle window/level change.
- * @param {Object} event The event fired when changing the window/level.
- */
- this.onWLChange = function (event)
- {
- // generate and draw if no skip flag
- if (typeof event.skipGenerate === "undefined" ||
- event.skipGenerate === false) {
- generateAndDrawImage();
- }
- };
-
- /**
- * Handle colour map change.
- * @param {Object} event The event fired when changing the colour map.
- */
- this.onColourChange = function (/*event*/)
- {
- generateAndDrawImage();
- };
-
- /**
- * Handle frame change.
- * @param {Object} event The event fired when changing the frame.
- */
- this.onFrameChange = function (/*event*/)
- {
- generateAndDrawImage();
- if ( drawController ) {
- drawController.activateDrawLayer(viewController);
- }
- };
-
- /**
- * Handle slice change.
- * @param {Object} event The event fired when changing the slice.
- */
- this.onSliceChange = function (/*event*/)
- {
- generateAndDrawImage();
- if ( drawController ) {
- drawController.activateDrawLayer(viewController);
- }
- };
-
- /**
- * Handle key down event.
- * - CRTL-Z: undo
- * - CRTL-Y: redo
- * - CRTL-ARROW_LEFT: next frame
- * - CRTL-ARROW_UP: next slice
- * - CRTL-ARROW_RIGHT: previous frame
- * - CRTL-ARROW_DOWN: previous slice
- * Default behavior. Usually used in tools.
- * @param {Object} event The key down event.
- */
- this.onKeydown = function (event)
- {
- if (event.ctrlKey) {
- if ( event.keyCode === 37 ) // crtl-arrow-left
- {
- event.preventDefault();
- self.getViewController().decrementFrameNb();
- }
- else if ( event.keyCode === 38 ) // crtl-arrow-up
- {
- event.preventDefault();
- self.getViewController().incrementSliceNb();
- }
- else if ( event.keyCode === 39 ) // crtl-arrow-right
- {
- event.preventDefault();
- self.getViewController().incrementFrameNb();
- }
- else if ( event.keyCode === 40 ) // crtl-arrow-down
- {
- event.preventDefault();
- self.getViewController().decrementSliceNb();
- }
- else if ( event.keyCode === 89 ) // crtl-y
- {
- undoStack.redo();
- }
- else if ( event.keyCode === 90 ) // crtl-z
- {
- undoStack.undo();
- }
- }
- };
-
- /**
- * Handle resize.
- * Fit the display to the window. To be called once the image is loaded.
- * @param {Object} event The change event.
- */
- this.onResize = function (/*event*/)
- {
- self.fitToSize(dwv.gui.getWindowSize());
- };
-
- /**
- * Handle zoom reset.
- * @param {Object} event The change event.
- */
- this.onZoomReset = function (/*event*/)
- {
- self.resetLayout();
- };
-
- /**
- * Handle loader change. Will activate the loader using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeLoader = function (event)
- {
- loadbox.displayLoader( event.currentTarget.value );
- };
-
- /**
- * Reset the load box to its original state.
- */
- this.resetLoadbox = function ()
- {
- loadbox.reset();
- };
-
- /**
- * Handle change url event.
- * @param {Object} event The event fired when changing the url field.
- */
- this.onChangeURL = function (event)
- {
- self.loadURLs([event.target.value]);
- };
-
- /**
- * Handle input urls.
- * @param {Array} urls The list of input urls.
- * @param {Array} requestHeaders An array of {name, value} to use as request headers.
- */
- this.onInputURLs = function (urls, requestHeaders)
- {
- self.loadURLs(urls, requestHeaders);
- };
-
- /**
- * Handle change files event.
- * @param {Object} event The event fired when changing the file field.
- */
- this.onChangeFiles = function (event)
- {
- var files = event.target.files;
- if ( files.length !== 0 ) {
- self.loadFiles(files);
- }
- };
-
- /**
- * Handle state save event.
- * @param {Object} event The event fired when changing the state save field.
- */
- this.onStateSave = function (/*event*/)
- {
- var state = new dwv.State();
- // add href to link (html5)
- var element = self.getElement("download-state");
- element.href = "data:application/json," + state.toJSON(self);
- };
-
- /**
- * Handle colour map change. Will activate the tool using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeColourMap = function (event)
- {
- viewController.setColourMapFromName( event.currentTarget.value );
- };
-
- /**
- * Handle window/level preset change. Will activate the preset using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeWindowLevelPreset = function (event)
- {
- viewController.setWindowLevelPreset( event.currentTarget.value );
- };
-
- /**
- * Handle tool change. Will activate the tool using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeTool = function (event)
- {
- toolboxController.setSelectedTool( event.currentTarget.value );
- };
-
- /**
- * Handle shape change. Will activate the shape using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeShape = function (event)
- {
- toolboxController.setSelectedShape( event.currentTarget.value );
- };
-
- /**
- * Handle filter change. Will activate the filter using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeFilter = function (event)
- {
- toolboxController.setSelectedFilter( event.currentTarget.value );
- };
-
- /**
- * Handle filter run.
- * @param {Object} event The run event.
- */
- this.onRunFilter = function (/*event*/)
- {
- toolboxController.runSelectedFilter();
- };
-
- /**
- * Handle line colour change. Will activate the colour using
- * the value property of the 'event.currentTarget'.
- * @param {Object} event The change event.
- */
- this.onChangeLineColour = function (event)
- {
- // called from an HTML select, use its value
- toolboxController.setLineColour( event.currentTarget.value );
- };
-
- /**
- * Handle min/max slider change.
- * @param {Object} range The new range of the data.
- */
- this.onChangeMinMax = function (range)
- {
- toolboxController.setRange(range);
- };
-
- /**
- * Handle undo.
- * @param {Object} event The associated event.
- */
- this.onUndo = function (/*event*/)
- {
- undoStack.undo();
- };
-
- /**
- * Handle redo.
- * @param {Object} event The associated event.
- */
- this.onRedo = function (/*event*/)
- {
- undoStack.redo();
- };
-
- /**
- * Handle toggle of info layer.
- * @param {Object} event The associated event.
- */
- this.onToggleInfoLayer = function (/*event*/)
- {
- self.toggleInfoLayerDisplay();
- };
-
- /**
- * Handle display reset.
- * @param {Object} event The change event.
- */
- this.onDisplayReset = function (/*event*/)
- {
- self.resetLayout();
- self.initWLDisplay();
- // update preset select
- var select = self.getElement("presetSelect");
- if (select) {
- select.selectedIndex = 0;
- dwv.gui.refreshElement(select);
- }
- };
-
-
- // Private Methods -----------------------------------------------------------
-
- /**
- * Fire an event: call all associated listeners.
- * @param {Object} event The event to fire.
- */
- function fireEvent (event)
- {
- if ( typeof listeners[event.type] === "undefined" ) {
- return;
- }
- for ( var i = 0; i < listeners[event.type].length; ++i )
- {
- listeners[event.type][i](event);
- }
- }
-
- /**
- * Generate the image data and draw it.
- */
- function generateAndDrawImage()
- {
- // generate image data from DICOM
- view.generateImageData(imageData);
- // set the image data of the layer
- imageLayer.setImageData(imageData);
- // draw the image
- imageLayer.draw();
- }
-
- /**
- * Apply the stored zoom to the layers.
- */
- function zoomLayers()
- {
- // image layer
- if( imageLayer ) {
- imageLayer.zoom(scale, scale, scaleCenter.x, scaleCenter.y);
- imageLayer.draw();
- }
- // draw layer
- if( drawController ) {
- drawController.zoomStage(scale, scaleCenter);
- }
- // fire event
- fireEvent({"type": "zoom-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y });
- }
-
- /**
- * Apply the stored translation to the layers.
- */
- function translateLayers()
- {
- // image layer
- if( imageLayer ) {
- imageLayer.translate(translation.x, translation.y);
- imageLayer.draw();
- // draw layer
- if( drawController ) {
- var ox = - imageLayer.getOrigin().x / scale - translation.x;
- var oy = - imageLayer.getOrigin().y / scale - translation.y;
- drawController.translateStage(ox, oy);
- }
- // fire event
- fireEvent({"type": "offset-change", "scale": scale,
- "cx": imageLayer.getTrans().x, "cy": imageLayer.getTrans().y });
- }
- }
-
- /**
- * Handle a drag over.
- * @private
- * @param {Object} event The event to handle.
- */
- function onDragOver(event)
- {
- // prevent default handling
- event.stopPropagation();
- event.preventDefault();
- // update box
- var box = self.getElement("dropBox");
- if ( box ) {
- box.className = 'dropBox hover';
- }
- }
-
- /**
- * Handle a drag leave.
- * @private
- * @param {Object} event The event to handle.
- */
- function onDragLeave(event)
- {
- // prevent default handling
- event.stopPropagation();
- event.preventDefault();
- // update box
- var box = self.getElement("dropBox hover");
- if ( box ) {
- box.className = 'dropBox';
- }
- }
-
- /**
- * Handle a drop event.
- * @private
- * @param {Object} event The event to handle.
- */
- function onDrop(event)
- {
- // prevent default handling
- event.stopPropagation();
- event.preventDefault();
- // load files
- self.loadFiles(event.dataTransfer.files);
- }
-
- /**
- * Handle an error: display it to the user.
- * @private
- * @param {Object} error The error to handle.
- */
- function handleError(error)
- {
- // alert window
- if ( error.name && error.message) {
- alert(error.name+": "+error.message);
- }
- else {
- alert("Error: "+error+".");
- }
- // log
- if ( error.stack ) {
- console.error(error.stack);
- }
- // stop progress
- dwv.gui.displayProgress(100);
- }
-
- /**
- * Handle an abort: display it to the user.
- * @param {Object} error The error to handle.
- * @private
- */
- function handleAbort(error)
- {
- // log
- if ( error.message ) {
- console.warn(error.message);
- }
- else {
- console.warn("Abort called.");
- }
- // stop progress
- dwv.gui.displayProgress(100);
- }
-
- /**
- * Handle a load progress.
- * @private
- * @param {Object} event The event to handle.
- */
- function onLoadProgress(event)
- {
- fireEvent(event);
- if( event.lengthComputable )
- {
- var percent = Math.ceil((event.loaded / event.total) * 100);
- dwv.gui.displayProgress(percent);
- }
- }
-
- /**
- * Create the application layers.
- * @private
- * @param {Number} dataWidth The width of the input data.
- * @param {Number} dataHeight The height of the input data.
- */
- function createLayers(dataWidth, dataHeight)
- {
- // image layer
- var canImgLay = self.getElement("imageLayer");
- imageLayer = new dwv.html.Layer(canImgLay);
- imageLayer.initialise(dataWidth, dataHeight);
- imageLayer.fillContext();
- imageLayer.setStyleDisplay(true);
- // draw layer
- var drawDiv = self.getElement("drawDiv");
- if ( drawDiv ) {
- drawController = new dwv.DrawController(drawDiv);
- drawController.create(dataWidth, dataHeight);
- }
- // resize app
- if ( fitToWindow ) {
- self.fitToSize( dwv.gui.getWindowSize() );
- }
- else {
- self.fitToSize( {
- 'width': self.getElement("layerContainer").offsetWidth,
- 'height': self.getElement("layerContainer").offsetHeight } );
- }
- self.resetLayout();
- }
-
- /**
- * Post load application initialisation. To be called once the DICOM has been parsed.
- * @private
- * @param {Object} data The data to display.
- */
- function postLoadInit(data)
- {
- // only initialise the first time
- if ( view ) {
- return;
- }
-
- // get the view from the loaded data
- view = data.view;
- viewController = new dwv.ViewController(view);
-
- // append the DICOM tags table
- if ( tagsGui ) {
- tagsGui.update(data.info);
- }
- // store image
- originalImage = view.getImage();
- image = originalImage;
-
- // layout
- var size = image.getGeometry().getSize();
- dataWidth = size.getNumberOfColumns();
- dataHeight = size.getNumberOfRows();
- createLayers(dataWidth, dataHeight);
-
- // get the image data from the image layer
- imageData = imageLayer.getContext().createImageData(
- dataWidth, dataHeight);
-
- // image listeners
- view.addEventListener("wl-width-change", self.onWLChange);
- view.addEventListener("wl-center-change", self.onWLChange);
- view.addEventListener("colour-change", self.onColourChange);
- view.addEventListener("slice-change", self.onSliceChange);
- view.addEventListener("frame-change", self.onFrameChange);
-
- // connect with local listeners
- view.addEventListener("wl-width-change", fireEvent);
- view.addEventListener("wl-center-change", fireEvent);
- view.addEventListener("colour-change", fireEvent);
- view.addEventListener("position-change", fireEvent);
- view.addEventListener("slice-change", fireEvent);
- view.addEventListener("frame-change", fireEvent);
-
- // append draw layers (before initialising the toolbox)
- if ( drawController ) {
- //drawController.appendDrawLayer(image.getNumberOfFrames());
- }
-
- // initialise the toolbox
- if ( toolboxController ) {
- toolboxController.initAndDisplay( imageLayer );
- }
-
- // stop box listening to drag (after first drag)
- var box = self.getElement("dropBox");
- if ( box ) {
- box.removeEventListener("dragover", onDragOver);
- box.removeEventListener("dragleave", onDragLeave);
- box.removeEventListener("drop", onDrop);
- dwv.html.removeNode(box);
- // switch listening to layerContainer
- var div = self.getElement("layerContainer");
- div.addEventListener("dragover", onDragOver);
- div.addEventListener("dragleave", onDragLeave);
- div.addEventListener("drop", onDrop);
- }
-
- // info layer
- var infoLayer = self.getElement("infoLayer");
- if ( infoLayer ) {
- infoController = new dwv.InfoController(containerDivId);
- infoController.create(self);
- infoController.toggleListeners(self, view);
- }
-
- // init W/L display
- self.initWLDisplay();
- // generate first image
- generateAndDrawImage();
- }
-
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.draw = dwv.draw || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Get the draw group id for a given position.
- * @return {Number} The group id.
- */
-dwv.draw.getDrawPositionGroupId = function (sliceNumber, frameNumber) {
- return "slice-"+sliceNumber+"_frame-"+frameNumber;
-};
-
-/**
- * Get the slice and frame position from a group id.
- * @param {String} groupId The group id.
- * @return {Object} The slice and frame number.
- */
-dwv.draw.getPositionFromGroupId = function (groupId) {
- var sepIndex = groupId.indexOf("_");
- if (sepIndex === -1) {
- console.warn("Badly formed PositionGroupId: "+groupId);
- }
- return { 'sliceNumber': groupId.substring(6, sepIndex),
- 'frameNumber': groupId.substring(sepIndex + 7) };
-};
-
-/**
- * Is an input node's name 'shape'.
- * @param {Object} node A Konva node.
- */
-dwv.draw.isNodeNameShape = function ( node ) {
- return node.name() === "shape";
-};
-
-/**
- * Is a node an extra shape associated with a main one.
- * @param {Object} node A Konva node.
- */
-dwv.draw.isNodeNameShapeExtra = function ( node ) {
- return node.name().startsWith("shape-");
-};
-
-/**
- * Is an input node's name 'label'.
- * @param {Object} node A Konva node.
- */
-dwv.draw.isNodeNameLabel = function ( node ) {
- return node.name() === "label";
-};
-
-/**
- * Is an input node a position node.
- * @param {Object} node A Konva node.
- */
-dwv.draw.isPositionNode = function ( node ) {
- return node.name() === 'position-group';
-};
-
-/**
- * Get a lambda to check a node's id.
- * @param {String} id The id to check.
- * @return A function to check a node's id.
- */
-dwv.draw.isNodeWithId = function ( id ) {
- return function (node) {
- return node.id() === id;
- };
-};
-
-/**
- * Is the input node a node that has the 'stroke' method.
- * @param {Object} node A Konva node.
-*/
-dwv.draw.canNodeChangeColour = function ( node ) {
- return node.name() !== "anchor" && node.name() !== "label";
-};
-
-/**
- * Debug function to output the layer hierarchy as text.
- * @param {Object} layer The Konva layer.
- * @param {String} prefix A display prefix (used in recursion).
- * @return {String} A text representation of the hierarchy.
- */
-dwv.draw.getHierarchyLog = function (layer, prefix) {
- if ( typeof prefix === "undefined" ) {
- prefix = "";
- }
- var kids = layer.getChildren();
- var log = prefix + "|__ " + layer.name() + ": " + layer.id() + "\n";
- for ( var i = 0; i < kids.length; ++i ) {
- log += dwv.draw.getHierarchyLog( kids[i], prefix + " ");
- }
- return log;
-};
-
-/**
- * Draw controller.
- * @constructor
- * @param {Object} drawDiv The HTML div used to store the drawings.
- * @external Konva
- */
-dwv.DrawController = function (drawDiv)
-{
- // Draw stage
- var drawStage = null;
- // Draw layer
- var drawLayer;
- // current position group id
- var currentPosGroupId = null;
-
- /**
- * Get the current position group.
- * @return {Object} The Konva.Group.
- */
- this.getCurrentPosGroup = function () {
- // get position groups
- var posGroups = drawLayer.getChildren( function (node) {
- return node.id() === currentPosGroupId;
- });
- // if one group, use it
- // if no group, create one
- var posGroup = null;
- if ( posGroups.length === 1 ) {
- posGroup = posGroups[0];
- } else if ( posGroups.length === 0 ) {
- posGroup = new Konva.Group();
- posGroup.name("position-group");
- posGroup.id(currentPosGroupId);
- posGroup.visible(true); // dont inherit
- // add new group to layer
- drawLayer.add(posGroup);
- } else {
- console.warn("Unexpected number of draw position groups.");
- }
- // return
- return posGroup;
- };
-
- /**
- * Create the controller: sets up the draw stage.
- * @param {Number} width The width of the stage.
- * @param {Number} height The height of the stage.
- */
- this.create = function (width, height) {
- // create stage
- drawStage = new Konva.Stage({
- 'container': drawDiv,
- 'width': width,
- 'height': height,
- 'listening': false
- });
- // reset style
- // (avoids a not needed vertical scrollbar)
- drawStage.getContent().setAttribute("style", "");
-
- // create layer
- drawLayer = new Konva.Layer({
- 'listening': false,
- 'hitGraphEnabled': false,
- 'visible': true
- });
- drawStage.add(drawLayer);
- };
-
- /**
- * Get the draw layer.
- * @return {Object} The draw layer.
- */
- this.getDrawLayer = function () {
- return drawLayer;
- };
-
- /**
- * Reset: clear the layers array.
- */
- this.reset = function () {
- drawLayer = null;
- };
-
- /**
- * Get the draw stage.
- * @return {Object} The draw layer.
- */
- this.getDrawStage = function () {
- return drawStage;
- };
-
- /**
- * Activate the current draw layer.
- * @param {Object} viewController The associated view controller.
- */
- this.activateDrawLayer = function (viewController)
- {
- // set current position
- var currentSlice = viewController.getCurrentPosition().k;
- var currentFrame = viewController.getCurrentFrame();
- // get and store the position group id
- currentPosGroupId = dwv.draw.getDrawPositionGroupId(currentSlice,currentFrame);
-
- // get all position groups
- var posGroups = drawLayer.getChildren( dwv.draw.isPositionNode );
- // reset or set the visible property
- var visible;
- for ( var i = 0, leni = posGroups.length; i < leni; ++i ) {
- visible = false;
- if ( posGroups[i].id() === currentPosGroupId ) {
- visible = true;
- }
- // group members inherit the visible property
- posGroups[i].visible(visible);
- }
-
- // show current draw layer
- drawLayer.draw();
- };
-
- /**
- * Reset the stage with a new window scale.
- * @param {Number} windowScale The window scale.
- */
- this.resetStage = function (windowScale) {
- drawStage.offset( {'x': 0, 'y': 0} );
- drawStage.scale( {'x': windowScale, 'y': windowScale} );
- drawStage.draw();
- };
-
- /**
- * Resize the current stage.
- * @param {Number} width the stage width.
- * @param {Number} height the stage height.
- * @param {Number} scale the stage scale.
- */
- this.resizeStage = function (width, height, scale) {
- // resize div
- drawDiv.setAttribute("style","width:"+width+"px;height:"+height+"px");
- // resize stage
- drawStage.setWidth(width);
- drawStage.setHeight(height);
- drawStage.scale( {'x': scale, 'y': scale} );
- drawStage.draw();
- };
-
- /**
- * Zoom the stage.
- * @param {Number} scale The scale factor.
- * @param {Object} scaleCenter The scale center point.
- */
- this.zoomStage = function (scale, scaleCenter) {
- // zoom
- var newScale = {'x': scale, 'y': scale};
- // offset
- // TODO different from the imageLayer offset?
- var oldScale = drawStage.scale();
- var oldOffset = drawStage.offset();
- var newOffsetX = (scaleCenter.x / oldScale.x) +
- oldOffset.x - (scaleCenter.x / newScale.x);
- var newOffsetY = (scaleCenter.y / oldScale.y) +
- oldOffset.y - (scaleCenter.y / newScale.y);
- var newOffset = {'x': newOffsetX, 'y': newOffsetY};
- // store
- drawStage.offset( newOffset );
- drawStage.scale( newScale );
- drawStage.draw();
- };
-
- /**
- * Translate the stage.
- * @param {Number} tx The X translation.
- * @param {Number} ty The Y translation.
- */
- this.translateStage = function (tx, ty) {
- drawStage.offset( {'x': tx, 'y': ty} );
- drawStage.draw();
- };
-
- /**
- * Get a list of drawing display details.
- * @return {Object} A list of draw details including id, slice, frame...
- */
- this.getDrawDisplayDetails = function ()
- {
- var list = [];
- var groups = drawLayer.getChildren();
- for ( var j = 0, lenj = groups.length; j < lenj; ++j ) {
- var position = dwv.draw.getPositionFromGroupId(groups[j].id());
- var collec = groups[j].getChildren();
- for ( var i = 0, leni = collec.length; i < leni; ++i ) {
- var shape = collec[i].getChildren( dwv.draw.isNodeNameShape )[0];
- var label = collec[i].getChildren( dwv.draw.isNodeNameLabel )[0];
- var text = label.getChildren()[0];
- var type = shape.className;
- if (type === "Line") {
- var shapeExtrakids = collec[i].getChildren( dwv.draw.isNodeNameShapeExtra );
- if (shape.closed()) {
- type = "Roi";
- } else if (shapeExtrakids.length !== 0) {
- if ( shapeExtrakids[0].name().indexOf("triangle") !== -1 ) {
- type = "Arrow";
- }
- else {
- type = "Ruler";
- }
- }
- }
- if (type === "Rect") {
- type = "Rectangle";
- }
- list.push( {
- "id": collec[i].id(),
- "slice": position.sliceNumber,
- "frame": position.frameNumber,
- "type": type,
- "color": shape.stroke(),
- "label": text.textExpr,
- "description": text.longText
- });
- }
- }
- return list;
- };
-
- /**
- * Get a list of drawing store details.
- * @return {Object} A list of draw details including id, text, quant...
- * TODO Unify with getDrawDisplayDetails?
- */
- this.getDrawStoreDetails = function ()
- {
- var drawingsDetails = {};
-
- // get all position groups
- var posGroups = drawLayer.getChildren( dwv.draw.isPositionNode );
-
- var posKids;
- var group;
- for ( var i = 0, leni = posGroups.length; i < leni; ++i ) {
- posKids = posGroups[i].getChildren();
- for ( var j = 0, lenj = posKids.length; j < lenj; ++j ) {
- group = posKids[j];
- // remove anchors
- var anchors = group.find(".anchor");
- for ( var a = 0; a < anchors.length; ++a ) {
- anchors[a].remove();
- }
- // get text
- var texts = group.find(".text");
- if ( texts.length !== 1 ) {
- console.warn("There should not be more than one text per shape.");
- }
- // get details (non konva vars)
- drawingsDetails[ group.id() ] = {
- "textExpr": encodeURIComponent(texts[0].textExpr),
- "longText": encodeURIComponent(texts[0].longText),
- "quant": texts[0].quant
- };
- }
- }
- return drawingsDetails;
- };
-
- /**
- * Set the drawings on the current stage.
- * @param {Array} drawings An array of drawings.
- * @param {Array} drawingsDetails An array of drawings details.
- * @param {Object} cmdCallback The DrawCommand callback.
- * @param {Object} exeCallback The callback to call once the DrawCommand has been executed.
- */
- this.setDrawings = function (drawings, drawingsDetails, cmdCallback, exeCallback)
- {
- // regular Konva deserialize
- var stateLayer = Konva.Node.create(drawings);
-
- // get all position groups
- var statePosGroups = stateLayer.getChildren( dwv.draw.isPositionNode );
-
- for ( var i = 0, leni = statePosGroups.length; i < leni; ++i ) {
- var statePosGroup = statePosGroups[i];
-
- // Get or create position-group if it does not exist and append it to drawLayer
- var posGroup = drawLayer.getChildren( dwv.draw.isNodeWithId( statePosGroup.id() ) )[0];
- if( typeof posGroup === "undefined" ) {
- posGroup = new Konva.Group({
- 'id': statePosGroup.id(),
- 'name': "position-group",
- 'visible': false
- });
- drawLayer.add(posGroup);
- }
-
- var statePosKids = statePosGroup.getChildren();
- for ( var j = 0, lenj = statePosKids.length; j < lenj; ++j ) {
- // shape group
- var stateGroup = statePosKids[j];
- // add group to posGroup (switches its parent)
- posGroup.add( stateGroup );
- // shape
- var shape = stateGroup.getChildren( dwv.draw.isNodeNameShape )[0];
- // create the draw command
- var cmd = new dwv.tool.DrawGroupCommand(
- stateGroup, shape.className,
- drawLayer );
- // draw command callbacks
- cmd.onExecute = cmdCallback;
- cmd.onUndo = cmdCallback;
- // details
- if (drawingsDetails) {
- var details = drawingsDetails[ stateGroup.id() ];
- var label = stateGroup.getChildren( dwv.draw.isNodeNameLabel )[0];
- var text = label.getText();
- // store details
- text.textExpr = details.textExpr;
- text.longText = details.longText;
- text.quant = details.quant;
- // reset text (it was not encoded)
- text.setText(dwv.utils.replaceFlags(text.textExpr, text.quant));
- }
- // execute
- cmd.execute();
- exeCallback(cmd);
- }
- }
- };
-
- /**
- * Update a drawing from its details.
- * @param {Object} drawDetails Details of the drawing to update.
- */
- this.updateDraw = function (drawDetails)
- {
- // get the group
- var group = drawLayer.findOne( "#"+drawDetails.id );
- if ( typeof group === "undefined" ) {
- console.warn("[updateDraw] Cannot find group with id: "+drawDetails.id);
- return;
- }
- // shape
- var shapes = group.getChildren( dwv.draw.isNodeNameShape );
- for (var i = 0; i < shapes.length; ++i ) {
- shapes[i].stroke(drawDetails.color);
- }
- // shape extra
- var shapesExtra = group.getChildren( dwv.draw.isNodeNameShapeExtra );
- for (var j = 0; j < shapesExtra.length; ++j ) {
- if (typeof shapesExtra[j].stroke() !== "undefined") {
- shapesExtra[j].stroke(drawDetails.color);
- }
- else if (typeof shapesExtra[j].fill() !== "undefined") {
- shapesExtra[j].fill(drawDetails.color);
- }
- }
- // label
- var label = group.getChildren( dwv.draw.isNodeNameLabel )[0];
- var text = label.getChildren()[0];
- text.fill(drawDetails.color);
- text.textExpr = drawDetails.label;
- text.longText = drawDetails.description;
- text.setText(dwv.utils.replaceFlags(text.textExpr, text.quant));
-
- // udpate current layer
- drawLayer.draw();
- };
-
- /**
- * Check the visibility of a given group.
- * @param {Object} drawDetails Details of the group to check.
- */
- this.isGroupVisible = function (drawDetails) {
- // get the group
- var group = drawLayer.findOne( "#"+drawDetails.id );
- if ( typeof group === "undefined" ) {
- console.warn("[isGroupVisible] Cannot find node with id: "+drawDetails.id);
- return false;
- }
- // get visibility
- return group.isVisible();
- };
-
- /**
- * Toggle the visibility of a given group.
- * @param {Object} drawDetails Details of the group to update.
- */
- this.toogleGroupVisibility = function (drawDetails) {
- // get the group
- var group = drawLayer.findOne( "#"+drawDetails.id );
- if ( typeof group === "undefined" ) {
- console.warn("[toogleGroupVisibility] Cannot find node with id: "+drawDetails.id);
- return false;
- }
- // toggle visible
- group.visible(!group.isVisible());
-
- // udpate current layer
- drawLayer.draw();
- };
-
- /**
- * Delete all Draws from the stage.
- * @param {Object} cmdCallback The DeleteCommand callback.
- * @param {Object} exeCallback The callback to call once the DeleteCommand has been executed.
- */
- this.deleteDraws = function (cmdCallback, exeCallback) {
- var delcmd;
- var groups = drawLayer.getChildren();
- while (groups.length) {
- var shape = groups[0].getChildren( dwv.draw.isNodeNameShape )[0];
- delcmd = new dwv.tool.DeleteGroupCommand( groups[0],
- dwv.tool.GetShapeDisplayName(shape), drawLayer);
- delcmd.onExecute = cmdCallback;
- delcmd.onUndo = cmdCallback;
- delcmd.execute();
- exeCallback(delcmd);
- }
- };
-
-}; // class dwv.DrawController
-
-// namespaces
-var dwv = dwv || {};
-
-/**
- * Info controller.
- * @constructor
- */
-dwv.InfoController = function (containerDivId)
-{
-
- // Info layer plot gui
- var plotInfo = null;
- // Info layer colour map gui
- var miniColourMap = null;
- // Info layer overlay
- var overlayInfos = [];
- // flag to know if the info layer is listening on the image.
- var isInfoLayerListening = false;
-
- /**
- * Create the different info elements.
- * TODO Get rid of the app input arg...
- */
- this.create = function (app)
- {
- var infocm = getElement("infocm");
- if (infocm) {
- miniColourMap = new dwv.gui.info.MiniColourMap(infocm, app);
- miniColourMap.create();
- }
-
- // create overlay info at each corner
- var pos_list = [
- "tl", "tc", "tr",
- "cl", "cr",
- "bl", "bc", "br" ];
-
- var num = 0;
- for (var n=0; n
0){
- for (var n=0; n 0){
- for (var n=0; n 0;
-};
-
-/**
- * Get the utfLabel (used by the TextDecoder) from a character set term
- * References:
- * - DICOM [Value Encoding]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_6.html}
- * - DICOM [Specific Character Set]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2}
- * - [TextDecoder#Parameters]{@link https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/TextDecoder#Parameters}
- */
-dwv.dicom.getUtfLabel = function (charSetTerm)
-{
- var label = "utf-8";
- if (charSetTerm === "ISO_IR 100" ) {
- label = "iso-8859-1";
- }
- else if (charSetTerm === "ISO_IR 101" ) {
- label = "iso-8859-2";
- }
- else if (charSetTerm === "ISO_IR 109" ) {
- label = "iso-8859-3";
- }
- else if (charSetTerm === "ISO_IR 110" ) {
- label = "iso-8859-4";
- }
- else if (charSetTerm === "ISO_IR 144" ) {
- label = "iso-8859-5";
- }
- else if (charSetTerm === "ISO_IR 127" ) {
- label = "iso-8859-6";
- }
- else if (charSetTerm === "ISO_IR 126" ) {
- label = "iso-8859-7";
- }
- else if (charSetTerm === "ISO_IR 138" ) {
- label = "iso-8859-8";
- }
- else if (charSetTerm === "ISO_IR 148" ) {
- label = "iso-8859-9";
- }
- else if (charSetTerm === "ISO_IR 13" ) {
- label = "shift-jis";
- }
- else if (charSetTerm === "ISO_IR 166" ) {
- label = "iso-8859-11";
- }
- else if (charSetTerm === "ISO 2022 IR 87" ) {
- label = "iso-2022-jp";
- }
- else if (charSetTerm === "ISO 2022 IR 149" ) {
- // not supported by TextDecoder when it says it should...
- //label = "iso-2022-kr";
- }
- else if (charSetTerm === "ISO 2022 IR 58") {
- // not supported by TextDecoder...
- //label = "iso-2022-cn";
- }
- else if (charSetTerm === "ISO_IR 192" ) {
- label = "utf-8";
- }
- else if (charSetTerm === "GB18030" ) {
- label = "gb18030";
- }
- else if (charSetTerm === "GB2312" ) {
- label = "gb2312";
- }
- else if (charSetTerm === "GBK" ) {
- label = "chinese";
- }
- return label;
-};
-
-/**
- * Data reader.
- * @constructor
- * @param {Array} buffer The input array buffer.
- * @param {Boolean} isLittleEndian Flag to tell if the data is little or big endian.
- */
-dwv.dicom.DataReader = function (buffer, isLittleEndian)
-{
- // Set endian flag if not defined.
- if ( typeof isLittleEndian === 'undefined' ) {
- isLittleEndian = true;
- }
-
- // Default text decoder
- var defaultTextDecoder = {};
- defaultTextDecoder.decode = function (buffer) {
- var result = "";
- for ( var i = 0, leni = buffer.length; i < leni; ++i ) {
- result += String.fromCharCode( buffer[ i ] );
- }
- return result;
- };
- // Text decoder
- var textDecoder = defaultTextDecoder;
- if (typeof window.TextDecoder !== "undefined") {
- textDecoder = new TextDecoder("iso-8859-1");
- }
-
- /**
- * Set the utfLabel used to construct the TextDecoder.
- * @param {String} label The encoding label.
- */
- this.setUtfLabel = function (label) {
- if (typeof window.TextDecoder !== "undefined") {
- textDecoder = new TextDecoder(label);
- }
- };
-
- /**
- * Is the Native endianness Little Endian.
- * @private
- * @type Boolean
- */
- var isNativeLittleEndian = dwv.dicom.isNativeLittleEndian();
-
- /**
- * Flag to know if the TypedArray data needs flipping.
- * @private
- * @type Boolean
- */
- var needFlip = (isLittleEndian !== isNativeLittleEndian);
-
- /**
- * The main data view.
- * @private
- * @type DataView
- */
- var view = new DataView(buffer);
-
- /**
- * Flip an array's endianness.
- * Inspired from [DataStream.js]{@link https://github.com/kig/DataStream.js}.
- * @param {Object} array The array to flip (modified).
- */
- this.flipArrayEndianness = function (array) {
- var blen = array.byteLength;
- var u8 = new Uint8Array(array.buffer, array.byteOffset, blen);
- var bpel = array.BYTES_PER_ELEMENT;
- var tmp;
- for ( var i = 0; i < blen; i += bpel ) {
- for ( var j = i + bpel - 1, k = i; j > k; j--, k++ ) {
- tmp = u8[k];
- u8[k] = u8[j];
- u8[j] = tmp;
- }
- }
- };
-
- /**
- * Read Uint16 (2 bytes) data.
- * @param {Number} byteOffset The offset to start reading from.
- * @return {Number} The read data.
- */
- this.readUint16 = function (byteOffset) {
- return view.getUint16(byteOffset, isLittleEndian);
- };
- /**
- * Read Uint32 (4 bytes) data.
- * @param {Number} byteOffset The offset to start reading from.
- * @return {Number} The read data.
- */
- this.readUint32 = function (byteOffset) {
- return view.getUint32(byteOffset, isLittleEndian);
- };
- /**
- * Read Int32 (4 bytes) data.
- * @param {Number} byteOffset The offset to start reading from.
- * @return {Number} The read data.
- */
- this.readInt32 = function (byteOffset) {
- return view.getInt32(byteOffset, isLittleEndian);
- };
- /**
- * Read Uint8 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readUint8Array = function (byteOffset, size) {
- return new Uint8Array(buffer, byteOffset, size);
- };
- /**
- * Read Int8 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readInt8Array = function (byteOffset, size) {
- return new Int8Array(buffer, byteOffset, size);
- };
- /**
- * Read Uint16 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readUint16Array = function (byteOffset, size) {
- var arraySize = size / Uint16Array.BYTES_PER_ELEMENT;
- var data = null;
- // byteOffset should be a multiple of Uint16Array.BYTES_PER_ELEMENT (=2)
- if ( (byteOffset % Uint16Array.BYTES_PER_ELEMENT) === 0 ) {
- data = new Uint16Array(buffer, byteOffset, arraySize);
- if ( needFlip ) {
- this.flipArrayEndianness(data);
- }
- }
- else {
- data = new Uint16Array(arraySize);
- for ( var i = 0; i < arraySize; ++i ) {
- data[i] = view.getInt16( (byteOffset +
- Uint16Array.BYTES_PER_ELEMENT * i),
- isLittleEndian);
- }
- }
- return data;
- };
- /**
- * Read Int16 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readInt16Array = function (byteOffset, size) {
- var arraySize = size / Int16Array.BYTES_PER_ELEMENT;
- var data = null;
- // byteOffset should be a multiple of Int16Array.BYTES_PER_ELEMENT (=2)
- if ( (byteOffset % Int16Array.BYTES_PER_ELEMENT) === 0 ) {
- data = new Int16Array(buffer, byteOffset, arraySize);
- if ( needFlip ) {
- this.flipArrayEndianness(data);
- }
- }
- else {
- data = new Int16Array(arraySize);
- for ( var i = 0; i < arraySize; ++i ) {
- data[i] = view.getInt16( (byteOffset +
- Int16Array.BYTES_PER_ELEMENT * i),
- isLittleEndian);
- }
- }
- return data;
- };
- /**
- * Read Uint32 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readUint32Array = function (byteOffset, size) {
- var arraySize = size / Uint32Array.BYTES_PER_ELEMENT;
- var data = null;
- // byteOffset should be a multiple of Uint32Array.BYTES_PER_ELEMENT (=4)
- if ( (byteOffset % Uint32Array.BYTES_PER_ELEMENT) === 0 ) {
- data = new Uint32Array(buffer, byteOffset, arraySize);
- if ( needFlip ) {
- this.flipArrayEndianness(data);
- }
- }
- else {
- data = new Uint32Array(arraySize);
- for ( var i = 0; i < arraySize; ++i ) {
- data[i] = view.getUint32( (byteOffset +
- Uint32Array.BYTES_PER_ELEMENT * i),
- isLittleEndian);
- }
- }
- return data;
- };
- /**
- * Read Int32 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readInt32Array = function (byteOffset, size) {
- var arraySize = size / Int32Array.BYTES_PER_ELEMENT;
- var data = null;
- // byteOffset should be a multiple of Int32Array.BYTES_PER_ELEMENT (=4)
- if ( (byteOffset % Int32Array.BYTES_PER_ELEMENT) === 0 ) {
- data = new Int32Array(buffer, byteOffset, arraySize);
- if ( needFlip ) {
- this.flipArrayEndianness(data);
- }
- }
- else {
- data = new Int32Array(arraySize);
- for ( var i = 0; i < arraySize; ++i ) {
- data[i] = view.getInt32( (byteOffset +
- Int32Array.BYTES_PER_ELEMENT * i),
- isLittleEndian);
- }
- }
- return data;
- };
- /**
- * Read Float32 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readFloat32Array = function (byteOffset, size) {
- var arraySize = size / Float32Array.BYTES_PER_ELEMENT;
- var data = null;
- // byteOffset should be a multiple of Float32Array.BYTES_PER_ELEMENT (=4)
- if ( (byteOffset % Float32Array.BYTES_PER_ELEMENT) === 0 ) {
- data = new Float32Array(buffer, byteOffset, arraySize);
- if ( needFlip ) {
- this.flipArrayEndianness(data);
- }
- }
- else {
- data = new Float32Array(arraySize);
- for ( var i = 0; i < arraySize; ++i ) {
- data[i] = view.getFloat32( (byteOffset +
- Float32Array.BYTES_PER_ELEMENT * i),
- isLittleEndian);
- }
- }
- return data;
- };
- /**
- * Read Float64 array.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} size The size of the array.
- * @return {Array} The read data.
- */
- this.readFloat64Array = function (byteOffset, size) {
- var arraySize = size / Float64Array.BYTES_PER_ELEMENT;
- var data = null;
- // byteOffset should be a multiple of Float64Array.BYTES_PER_ELEMENT (=8)
- if ( (byteOffset % Float64Array.BYTES_PER_ELEMENT) === 0 ) {
- data = new Float64Array(buffer, byteOffset, arraySize);
- if ( needFlip ) {
- this.flipArrayEndianness(data);
- }
- }
- else {
- data = new Float64Array(arraySize);
- for ( var i = 0; i < arraySize; ++i ) {
- data[i] = view.getFloat64( (byteOffset +
- Float64Array.BYTES_PER_ELEMENT*i),
- isLittleEndian);
- }
- }
- return data;
- };
- /**
- * Read data as an hexadecimal string.
- * @param {Number} byteOffset The offset to start reading from.
- * @return {Array} The read data.
- */
- this.readHex = function (byteOffset) {
- // read and convert to hex string
- var str = this.readUint16(byteOffset).toString(16);
- // return padded
- return "0x0000".substr(0, 6 - str.length) + str.toUpperCase();
- };
-
- /**
- * Read data as a string.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} nChars The number of characters to read.
- * @return {String} The read data.
- */
- this.readString = function (byteOffset, nChars) {
- var data = this.readUint8Array(byteOffset, nChars);
- return defaultTextDecoder.decode(data);
- };
-
- /**
- * Read data as a 'special' string, decoding it if the TextDecoder is available.
- * @param {Number} byteOffset The offset to start reading from.
- * @param {Number} nChars The number of characters to read.
- * @return {String} The read data.
- */
- this.readSpecialString = function (byteOffset, nChars) {
- var data = this.readUint8Array(byteOffset, nChars);
- return textDecoder.decode(data);
- };
-
-};
-
-/**
- * Get the group-element pair from a tag string name.
- * @param {String} tagName The tag string name.
- * @return {Object} group-element pair.
- */
-dwv.dicom.getGroupElementFromName = function (tagName)
-{
- var group = null;
- var element = null;
- var dict = dwv.dicom.dictionary;
- var keys0 = Object.keys(dict);
- var keys1 = null;
- // label for nested loop break
- outLabel:
- // search through dictionary
- for ( var k0 = 0, lenK0 = keys0.length; k0 < lenK0; ++k0 ) {
- group = keys0[k0];
- keys1 = Object.keys( dict[group] );
- for ( var k1 = 0, lenK1 = keys1.length; k1 < lenK1; ++k1 ) {
- element = keys1[k1];
- if ( dict[group][element][2] === tagName ) {
- break outLabel;
- }
- }
- }
- return { 'group': group, 'element': element };
-};
-
-/**
- * Immutable tag.
- * @constructor
- * @param {String} group The tag group.
- * @param {String} element The tag element.
- */
-dwv.dicom.Tag = function (group, element)
-{
- /**
- * Get the tag group.
- * @return {String} The tag group.
- */
- this.getGroup = function () { return group; };
- /**
- * Get the tag element.
- * @return {String} The tag element.
- */
- this.getElement = function () { return element; };
-}; // Tag class
-
-/**
- * Check for Tag equality.
- * @param {Object} rhs The other tag to compare to.
- * @return {Boolean} True if both tags are equal.
- */
-dwv.dicom.Tag.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getGroup() === rhs.getGroup() &&
- this.getElement() === rhs.getElement();
-};
-
-/**
- * Check for Tag equality.
- * @param {Object} rhs The other tag to compare to provided as a simple object.
- * @return {Boolean} True if both tags are equal.
- */
-dwv.dicom.Tag.prototype.equals2 = function (rhs) {
- if (rhs === null ||
- typeof rhs.group === "undefined" ||
- typeof rhs.element === "undefined" ) {
- return false;
- }
- return this.equals(new dwv.dicom.Tag(rhs.group, rhs.element));
-};
-
-// Get the FileMetaInformationGroupLength Tag.
-dwv.dicom.getFileMetaInformationGroupLengthTag = function () {
- return new dwv.dicom.Tag("0x0002", "0x0000");
-};
-// Get the Item Tag.
-dwv.dicom.getItemTag = function () {
- return new dwv.dicom.Tag("0xFFFE", "0xE000");
-};
-// Get the ItemDelimitationItem Tag.
-dwv.dicom.getItemDelimitationItemTag = function () {
- return new dwv.dicom.Tag("0xFFFE", "0xE00D");
-};
-// Get the SequenceDelimitationItem Tag.
-dwv.dicom.getSequenceDelimitationItemTag = function () {
- return new dwv.dicom.Tag("0xFFFE", "0xE0DD");
-};
-// Get the PixelData Tag.
-dwv.dicom.getPixelDataTag = function () {
- return new dwv.dicom.Tag("0x7FE0", "0x0010");
-};
-
-/**
- * Get the group-element key used to store DICOM elements.
- * @param {Number} group The DICOM group.
- * @param {Number} element The DICOM element.
- * @return {String} The key.
- */
-dwv.dicom.getGroupElementKey = function (group, element)
-{
- return 'x' + group.substr(2,6) + element.substr(2,6);
-};
-
-/**
- * Split a group-element key used to store DICOM elements.
- * @param {String} key The key in form "x00280102.
- * @return {Object} The DICOM group and element.
- */
-dwv.dicom.splitGroupElementKey = function (key)
-{
- return {'group': key.substr(1,4), 'element': key.substr(5,8) };
-};
-
-/**
- * Get patient orientation label in the reverse direction.
- * @param {String} ori Patient Orientation value.
- * @return {String} Reverse Orientation Label.
- */
-dwv.dicom.getReverseOrientation = function (ori)
-{
- if (!ori) {
- return null;
- }
- // reverse labels
- var rlabels = {
- "L": "R",
- "R": "L",
- "A": "P",
- "P": "A",
- "H": "F",
- "F": "H"
- };
-
- var rori = "";
- for (var n=0; n regular explicit: 8 + X
- * | 4 | 2+2 | 4 | X | -> 32bit VL: 12 + X
- *
- * | Tag | VL | Value |
- * | 4 | 4 | X | -> implicit (32bit VL): 8 + X
- *
- * | Tag | Len | Value |
- * | 4 | 4 | X | -> item: 8 + X
- */
-dwv.dicom.getDataElementPrefixByteSize = function (vr, isImplicit) {
- return isImplicit ? 8 : dwv.dicom.is32bitVLVR(vr) ? 12 : 8;
-};
-
-/**
- * DicomParser class.
- * @constructor
- */
-dwv.dicom.DicomParser = function ()
-{
- /**
- * The list of DICOM elements.
- * @type Array
- */
- this.dicomElements = {};
-
- /**
- * Default character set (optional).
- * @private
- * @type String
- */
- var defaultCharacterSet;
- /**
- * Get the default character set.
- * @return {String} The default character set.
- */
- this.getDefaultCharacterSet = function () {
- return defaultCharacterSet;
- };
- /**
- * Set the default character set.
- * param {String} The character set.
- */
- this.setDefaultCharacterSet = function (characterSet) {
- defaultCharacterSet = characterSet;
- };
-};
-
-/**
- * Get the raw DICOM data elements.
- * @return {Object} The raw DICOM elements.
- */
-dwv.dicom.DicomParser.prototype.getRawDicomElements = function ()
-{
- return this.dicomElements;
-};
-
-/**
- * Get the DICOM data elements.
- * @return {Object} The DICOM elements.
- */
-dwv.dicom.DicomParser.prototype.getDicomElements = function ()
-{
- return new dwv.dicom.DicomElementsWrapper(this.dicomElements);
-};
-
-/**
- * Read a DICOM tag.
- * @param reader The raw data reader.
- * @param offset The offset where to start to read.
- * @return An object containing the tags 'group', 'element' and 'name'.
- */
-dwv.dicom.DicomParser.prototype.readTag = function (reader, offset)
-{
- // group
- var group = reader.readHex(offset);
- offset += Uint16Array.BYTES_PER_ELEMENT;
- // element
- var element = reader.readHex(offset);
- offset += Uint16Array.BYTES_PER_ELEMENT;
- // name
- var name = dwv.dicom.getGroupElementKey(group, element);
- // return
- return {
- 'group': group,
- 'element': element,
- 'name': name,
- 'endOffset': offset };
-};
-
-/**
- * Read an item data element.
- * @param {Object} reader The raw data reader.
- * @param {Number} offset The offset where to start to read.
- * @param {Boolean} implicit Is the DICOM VR implicit?
- * @returns {Object} The item data as a list of data elements.
- */
-dwv.dicom.DicomParser.prototype.readItemDataElement = function (reader, offset, implicit)
-{
- var itemData = {};
-
- // read the first item
- var item = this.readDataElement(reader, offset, implicit);
- offset = item.endOffset;
-
- // exit if it is a sequence delimitation item
- var isSeqDelim = ( item.tag.name === "xFFFEE0DD" );
- if (isSeqDelim) {
- return {
- data: itemData,
- endOffset: item.endOffset,
- isSeqDelim: isSeqDelim };
- }
-
- // store it
- itemData[item.tag.name] = item;
-
- // explicit VR items
- if (item.vl !== "u/l") {
- // not empty
- if (item.vl !== 0) {
- // read until the end offset
- var endOffset = offset;
- offset -= item.vl;
- while (offset < endOffset) {
- item = this.readDataElement(reader, offset, implicit);
- offset = item.endOffset;
- itemData[item.tag.name] = item;
- }
- }
- }
- // implicit VR items
- else {
- // read until the item delimitation item
- var isItemDelim = false;
- while (!isItemDelim) {
- item = this.readDataElement(reader, offset, implicit);
- offset = item.endOffset;
- isItemDelim = ( item.tag.name === "xFFFEE00D" );
- if (!isItemDelim) {
- itemData[item.tag.name] = item;
- }
- }
- }
-
- return {
- 'data': itemData,
- 'endOffset': offset,
- 'isSeqDelim': false };
-};
-
-/**
- * Read the pixel item data element.
- * Ref: [Single frame fragments]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_A.4.html#table_A.4-1}.
- * @param {Object} reader The raw data reader.
- * @param {Number} offset The offset where to start to read.
- * @param {Boolean} implicit Is the DICOM VR implicit?
- * @returns {Array} The item data as an array of data elements.
- */
-dwv.dicom.DicomParser.prototype.readPixelItemDataElement = function (reader, offset, implicit)
-{
- var itemData = [];
-
- // first item: basic offset table
- var item = this.readDataElement(reader, offset, implicit);
- var offsetTableVl = item.vl;
- offset = item.endOffset;
-
- // read until the sequence delimitation item
- var isSeqDelim = false;
- while (!isSeqDelim) {
- item = this.readDataElement(reader, offset, implicit);
- offset = item.endOffset;
- isSeqDelim = ( item.tag.name === "xFFFEE0DD" );
- if (!isSeqDelim) {
- itemData.push(item.value);
- }
- }
-
- return {
- 'data': itemData,
- 'endOffset': offset,
- 'offsetTableVl': offsetTableVl };
-};
-
-/**
- * Read a DICOM data element.
- * Reference: [DICOM VRs]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#table_6.2-1}.
- * @param {Object} reader The raw data reader.
- * @param {Number} offset The offset where to start to read.
- * @param {Boolean} implicit Is the DICOM VR implicit?
- * @return {Object} An object containing the element 'tag', 'vl', 'vr', 'data' and 'endOffset'.
- */
-dwv.dicom.DicomParser.prototype.readDataElement = function (reader, offset, implicit)
-{
- // Tag: group, element
- var tag = this.readTag(reader, offset);
- offset = tag.endOffset;
-
- // Value Representation (VR)
- var vr = null;
- var is32bitVLVR = false;
- if (dwv.dicom.isTagWithVR(tag.group, tag.element)) {
- // implicit VR
- if (implicit) {
- vr = "UN";
- var dict = dwv.dicom.dictionary;
- if ( typeof dict[tag.group] !== "undefined" &&
- typeof dict[tag.group][tag.element] !== "undefined" ) {
- vr = dwv.dicom.dictionary[tag.group][tag.element][0];
- }
- is32bitVLVR = true;
- }
- else {
- vr = reader.readString( offset, 2 );
- offset += 2 * Uint8Array.BYTES_PER_ELEMENT;
- is32bitVLVR = dwv.dicom.is32bitVLVR(vr);
- // reserved 2 bytes
- if ( is32bitVLVR ) {
- offset += 2 * Uint8Array.BYTES_PER_ELEMENT;
- }
- }
- }
- else {
- vr = "UN";
- is32bitVLVR = true;
- }
-
- // Value Length (VL)
- var vl = 0;
- if ( is32bitVLVR ) {
- vl = reader.readUint32( offset );
- offset += Uint32Array.BYTES_PER_ELEMENT;
- }
- else {
- vl = reader.readUint16( offset );
- offset += Uint16Array.BYTES_PER_ELEMENT;
- }
-
- // check the value of VL
- var vlString = vl;
- if( vl === 0xffffffff ) {
- vlString = "u/l";
- vl = 0;
- }
-
- var startOffset = offset;
-
- // data
- var data = null;
- var isPixelData = (tag.name === "x7FE00010");
- // pixel data sequence (implicit)
- if (isPixelData && vlString === "u/l")
- {
- var pixItemData = this.readPixelItemDataElement(reader, offset, implicit);
- offset = pixItemData.endOffset;
- startOffset += pixItemData.offsetTableVl;
- data = pixItemData.data;
- }
- else if (isPixelData && (vr === "OB" || vr === "OW" || vr === "OF" || vr === "ox")) {
- // BitsAllocated
- var bitsAllocated = 16;
- if ( typeof this.dicomElements.x00280100 !== 'undefined' ) {
- bitsAllocated = this.dicomElements.x00280100.value[0];
- } else {
- console.warn("Reading DICOM pixel data with default bitsAllocated.");
- }
- if (bitsAllocated === 8 && vr === "OW") {
- console.warn("Reading DICOM pixel data with vr=OW and bitsAllocated=8 (should be 16).");
- }
- if (bitsAllocated === 16 && vr === "OB") {
- console.warn("Reading DICOM pixel data with vr=OB and bitsAllocated=16 (should be 8).");
- }
- // PixelRepresentation 0->unsigned, 1->signed
- var pixelRepresentation = 0;
- if ( typeof this.dicomElements.x00280103 !== 'undefined' ) {
- pixelRepresentation = this.dicomElements.x00280103.value[0];
- }
- // read
- if ( bitsAllocated === 8 ) {
- if (pixelRepresentation === 0) {
- data = reader.readUint8Array( offset, vl );
- }
- else {
- data = reader.readInt8Array( offset, vl );
- }
- }
- else if ( bitsAllocated === 16 ) {
- if (pixelRepresentation === 0) {
- data = reader.readUint16Array( offset, vl );
- }
- else {
- data = reader.readInt16Array( offset, vl );
- }
- }
- else if ( bitsAllocated === 32 ) {
- if (pixelRepresentation === 0) {
- data = reader.readUint32Array( offset, vl );
- }
- else {
- data = reader.readInt32Array( offset, vl );
- }
- }
- else if ( bitsAllocated === 64 ) {
- if (pixelRepresentation === 0) {
- data = reader.readUint64Array( offset, vl );
- }
- else {
- data = reader.readInt64Array( offset, vl );
- }
- }
- offset += vl;
- }
- // others
- else if ( vr === "OB" )
- {
- data = reader.readInt8Array( offset, vl );
- offset += vl;
- }
- else if ( vr === "OW" )
- {
- data = reader.readInt16Array( offset, vl );
- offset += vl;
- }
- else if ( vr === "OF" )
- {
- data = reader.readInt32Array( offset, vl );
- offset += vl;
- }
- else if ( vr === "OD" )
- {
- data = reader.readInt64Array( offset, vl );
- offset += vl;
- }
- // numbers
- else if( vr === "US")
- {
- data = reader.readUint16Array( offset, vl );
- offset += vl;
- }
- else if( vr === "UL")
- {
- data = reader.readUint32Array( offset, vl );
- offset += vl;
- }
- else if( vr === "SS")
- {
- data = reader.readInt16Array( offset, vl );
- offset += vl;
- }
- else if( vr === "SL")
- {
- data = reader.readInt32Array( offset, vl );
- offset += vl;
- }
- else if( vr === "FL")
- {
- data = reader.readFloat32Array( offset, vl );
- offset += vl;
- }
- else if( vr === "FD")
- {
- data = reader.readFloat64Array( offset, vl );
- offset += vl;
- }
- // attribute
- else if( vr === "AT")
- {
- var raw = reader.readUint16Array( offset, vl );
- offset += vl;
- data = [];
- for ( var i = 0, leni = raw.length; i < leni; i+=2 ) {
- var stri = raw[i].toString(16);
- var stri1 = raw[i+1].toString(16);
- var str = "(";
- str += "0000".substr(0, 4 - stri.length) + stri.toUpperCase();
- str += ",";
- str += "0000".substr(0, 4 - stri1.length) + stri1.toUpperCase();
- str += ")";
- data.push(str);
- }
- }
- // not available
- else if( vr === "UN")
- {
- data = reader.readUint8Array( offset, vl );
- offset += vl;
- }
- // sequence
- else if (vr === "SQ")
- {
- data = [];
- var itemData;
- // explicit VR sequence
- if (vlString !== "u/l") {
- // not empty
- if (vl !== 0) {
- var sqEndOffset = offset + vl;
- while (offset < sqEndOffset) {
- itemData = this.readItemDataElement(reader, offset, implicit);
- data.push( itemData.data );
- offset = itemData.endOffset;
- }
- }
- }
- // implicit VR sequence
- else {
- // read until the sequence delimitation item
- var isSeqDelim = false;
- while (!isSeqDelim) {
- itemData = this.readItemDataElement(reader, offset, implicit);
- isSeqDelim = itemData.isSeqDelim;
- offset = itemData.endOffset;
- // do not store the delimitation item
- if (!isSeqDelim) {
- data.push( itemData.data );
- }
- }
- }
- }
- // raw
- else
- {
- if ( vr === "SH" || vr === "LO" || vr === "ST" ||
- vr === "PN" || vr === "LT" || vr === "UT" ) {
- data = reader.readSpecialString( offset, vl );
- } else {
- data = reader.readString( offset, vl );
- }
- offset += vl;
- data = data.split("\\");
- }
-
- // return
- return {
- 'tag': tag,
- 'vr': vr,
- 'vl': vlString,
- 'value': data,
- 'startOffset': startOffset,
- 'endOffset': offset
- };
-};
-
-/**
- * Parse the complete DICOM file (given as input to the class).
- * Fills in the member object 'dicomElements'.
- * @param buffer The input array buffer.
- */
-dwv.dicom.DicomParser.prototype.parse = function (buffer)
-{
- var offset = 0;
- var implicit = false;
- // default readers
- var metaReader = new dwv.dicom.DataReader(buffer);
- var dataReader = new dwv.dicom.DataReader(buffer);
-
- // 128 -> 132: magic word
- offset = 128;
- var magicword = metaReader.readString( offset, 4 );
- offset += 4 * Uint8Array.BYTES_PER_ELEMENT;
- if(magicword !== "DICM")
- {
- throw new Error("Not a valid DICOM file (no magic DICM word found)");
- }
-
- // 0x0002, 0x0000: FileMetaInformationGroupLength
- var dataElement = this.readDataElement(metaReader, offset, false);
- offset = dataElement.endOffset;
- // store the data element
- this.dicomElements[dataElement.tag.name] = dataElement;
- // get meta length
- var metaLength = parseInt(dataElement.value[0], 10);
-
- // meta elements
- var metaEnd = offset + metaLength;
- while( offset < metaEnd )
- {
- // get the data element
- dataElement = this.readDataElement(metaReader, offset, false);
- offset = dataElement.endOffset;
- // store the data element
- this.dicomElements[dataElement.tag.name] = dataElement;
- }
-
- // check the TransferSyntaxUID (has to be there!)
- if (typeof this.dicomElements.x00020010 === "undefined")
- {
- throw new Error("Not a valid DICOM file (no TransferSyntaxUID found)");
- }
- var syntax = dwv.dicom.cleanString(this.dicomElements.x00020010.value[0]);
-
- // check support
- if (!dwv.dicom.isReadSupportedTransferSyntax(syntax)) {
- throw new Error("Unsupported DICOM transfer syntax: '"+syntax+
- "' ("+dwv.dicom.getTransferSyntaxName(syntax)+")");
- }
-
- // Implicit VR
- if (dwv.dicom.isImplicitTransferSyntax(syntax)) {
- implicit = true;
- }
-
- // Big Endian
- if (dwv.dicom.isBigEndianTransferSyntax(syntax)) {
- dataReader = new dwv.dicom.DataReader(buffer,false);
- }
-
- // default character set
- if (typeof this.getDefaultCharacterSet() !== "undefined") {
- dataReader.setUtfLabel(this.getDefaultCharacterSet());
- }
-
- // DICOM data elements
- while ( offset < buffer.byteLength )
- {
- // get the data element
- dataElement = this.readDataElement(dataReader, offset, implicit);
- // check character set
- if (dataElement.tag.name === "x00080005") {
- var charSetTerm;
- if (dataElement.value.length === 1) {
- charSetTerm = dwv.dicom.cleanString(dataElement.value[0]);
- }
- else {
- charSetTerm = dwv.dicom.cleanString(dataElement.value[1]);
- console.warn("Unsupported character set with code extensions: '"+charSetTerm+"'.");
- }
- dataReader.setUtfLabel(dwv.dicom.getUtfLabel(charSetTerm));
- }
- // increment offset
- offset = dataElement.endOffset;
- // store the data element
- this.dicomElements[dataElement.tag.name] = dataElement;
- }
-
- // safety check...
- if (buffer.byteLength !== offset) {
- console.warn("Did not reach the end of the buffer: "+
- offset+" != "+buffer.byteLength);
- }
-
- // pixel buffer
- if (typeof this.dicomElements.x7FE00010 !== "undefined") {
-
- var numberOfFrames = 1;
- if (typeof this.dicomElements.x00280008 !== "undefined") {
- numberOfFrames = this.dicomElements.x00280008.value[0];
- }
-
- if (this.dicomElements.x7FE00010.vl !== "u/l") {
- // compressed should be encapsulated...
- if (dwv.dicom.isJpeg2000TransferSyntax( syntax ) ||
- dwv.dicom.isJpegBaselineTransferSyntax( syntax ) ||
- dwv.dicom.isJpegLosslessTransferSyntax( syntax ) ) {
- console.warn("Compressed but no items...");
- }
-
- // calculate the slice size
- var pixData = this.dicomElements.x7FE00010.value;
- var columns = this.dicomElements.x00280011.value[0];
- var rows = this.dicomElements.x00280010.value[0];
- var samplesPerPixel = this.dicomElements.x00280002.value[0];
- var sliceSize = columns * rows * samplesPerPixel;
- // slice data in an array of frames
- var newPixData = [];
- var frameOffset = 0;
- for (var g = 0; g < numberOfFrames; ++g) {
- newPixData[g] = pixData.slice(frameOffset, frameOffset+sliceSize);
- frameOffset += sliceSize;
- }
- // store as pixel data
- this.dicomElements.x7FE00010.value = newPixData;
- }
- else {
- // handle fragmented pixel buffer
- // Reference: http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_8.2.html
- // (third note, "Depending on the transfer syntax...")
- var pixItems = this.dicomElements.x7FE00010.value;
- if (pixItems.length > 1 && pixItems.length > numberOfFrames ) {
-
- // concatenate pixel data items
- // concat does not work on typed arrays
- //this.pixelBuffer = this.pixelBuffer.concat( dataElement.data );
- // manual concat...
- var nItemPerFrame = pixItems.length / numberOfFrames;
- var newPixItems = [];
- var index = 0;
- for (var f = 0; f < numberOfFrames; ++f) {
- index = f * nItemPerFrame;
- // calculate the size of a frame
- var size = 0;
- for (var i = 0; i < nItemPerFrame; ++i) {
- size += pixItems[index + i].length;
- }
- // create new buffer
- var newBuffer = new pixItems[0].constructor(size);
- // fill new buffer
- var fragOffset = 0;
- for (var j = 0; j < nItemPerFrame; ++j) {
- newBuffer.set( pixItems[index + j], fragOffset );
- fragOffset += pixItems[index + j].length;
- }
- newPixItems[f] = newBuffer;
- }
- // store as pixel data
- this.dicomElements.x7FE00010.value = newPixItems;
- }
- }
- }
-};
-
-/**
- * DicomElements wrapper.
- * @constructor
- * @param {Array} dicomElements The elements to wrap.
- */
-dwv.dicom.DicomElementsWrapper = function (dicomElements) {
-
- /**
- * Get a DICOM Element value from a group/element key.
- * @param {String} groupElementKey The key to retrieve.
- * @return {Object} The DICOM element.
- */
- this.getDEFromKey = function ( groupElementKey ) {
- return dicomElements[groupElementKey];
- };
-
- /**
- * Get a DICOM Element value from a group/element key.
- * @param {String} groupElementKey The key to retrieve.
- * @param {Boolean} asArray Get the value as an Array.
- * @return {Object} The DICOM element value.
- */
- this.getFromKey = function ( groupElementKey, asArray ) {
- // default
- if ( typeof asArray === "undefined" ) {
- asArray = false;
- }
- var value = null;
- var dElement = dicomElements[groupElementKey];
- if ( typeof dElement !== "undefined" ) {
- // raw value if only one
- if ( dElement.value.length === 1 && asArray === false) {
- value = dElement.value[0];
- }
- else {
- value = dElement.value;
- }
- }
- return value;
- };
-
- /**
- * Dump the DICOM tags to an array.
- * @return {Array}
- */
- this.dumpToTable = function () {
- var keys = Object.keys(dicomElements);
- var dict = dwv.dicom.dictionary;
- var table = [];
- var dicomElement = null;
- var dictElement = null;
- var row = null;
- for ( var i = 0, leni = keys.length; i < leni; ++i ) {
- dicomElement = dicomElements[keys[i]];
- row = {};
- // dictionnary entry (to get name)
- dictElement = null;
- if ( typeof dict[dicomElement.tag.group] !== "undefined" &&
- typeof dict[dicomElement.tag.group][dicomElement.tag.element] !== "undefined") {
- dictElement = dict[dicomElement.tag.group][dicomElement.tag.element];
- }
- // name
- if ( dictElement !== null ) {
- row.name = dictElement[2];
- }
- else {
- row.name = "Unknown Tag & Data";
- }
- // value
- row.value = this.getElementValueAsString(dicomElement);
- // others
- row.group = dicomElement.tag.group;
- row.element = dicomElement.tag.element;
- row.vr = dicomElement.vr;
- row.vl = dicomElement.vl;
-
- table.push( row );
- }
- return table;
- };
-
- /**
- * Dump the DICOM tags to a string.
- * @return {String} The dumped file.
- */
- this.dump = function () {
- var keys = Object.keys(dicomElements);
- var result = "\n";
- result += "# Dicom-File-Format\n";
- result += "\n";
- result += "# Dicom-Meta-Information-Header\n";
- result += "# Used TransferSyntax: ";
- if ( dwv.dicom.isNativeLittleEndian() ) {
- result += "Little Endian Explicit\n";
- }
- else {
- result += "NOT Little Endian Explicit\n";
- }
- var dicomElement = null;
- var checkHeader = true;
- for ( var i = 0, leni = keys.length; i < leni; ++i ) {
- dicomElement = dicomElements[keys[i]];
- if ( checkHeader && dicomElement.tag.group !== "0x0002" ) {
- result += "\n";
- result += "# Dicom-Data-Set\n";
- result += "# Used TransferSyntax: ";
- var syntax = dwv.dicom.cleanString(dicomElements.x00020010.value[0]);
- result += dwv.dicom.getTransferSyntaxName(syntax);
- result += "\n";
- checkHeader = false;
- }
- result += this.getElementAsString(dicomElement) + "\n";
- }
- return result;
- };
-
-};
-
-/**
- * Get a data element value as a string.
- * @param {Object} dicomElement The DICOM element.
- * @param {Boolean} pretty When set to true, returns a 'pretified' content.
- * @return {String} A string representation of the DICOM element.
- */
-dwv.dicom.DicomElementsWrapper.prototype.getElementValueAsString = function ( dicomElement, pretty )
-{
- var str = "";
- var strLenLimit = 65;
-
- // dafault to pretty output
- if ( typeof pretty === "undefined" ) {
- pretty = true;
- }
- // check dicom element input
- if ( typeof dicomElement === "undefined" || dicomElement === null ) {
- return str;
- }
-
- // Polyfill for Number.isInteger.
- var isInteger = Number.isInteger || function (value) {
- return typeof value === 'number' &&
- isFinite(value) &&
- Math.floor(value) === value;
- };
-
- // TODO Support sequences.
-
- if ( dicomElement.vr !== "SQ" &&
- dicomElement.value.length === 1 && dicomElement.value[0] === "" ) {
- str += "(no value available)";
- } else if ( dicomElement.tag.group === '0x7FE0' &&
- dicomElement.tag.element === '0x0010' &&
- dicomElement.vl === 'u/l' ) {
- str = "(PixelSequence)";
- } else if ( dicomElement.vr === "DA" && pretty ) {
- var daValue = dicomElement.value[0];
- var daYear = parseInt( daValue.substr(0,4), 10 );
- var daMonth = parseInt( daValue.substr(4,2), 10 ) - 1; // 0-11
- var daDay = parseInt( daValue.substr(6,2), 10 );
- var da = new Date(daYear, daMonth, daDay);
- str = da.toLocaleDateString();
- } else if ( dicomElement.vr === "TM" && pretty ) {
- var tmValue = dicomElement.value[0];
- var tmHour = tmValue.substr(0,2);
- var tmMinute = tmValue.length >= 4 ? tmValue.substr(2,2) : "00";
- var tmSeconds = tmValue.length >= 6 ? tmValue.substr(4,2) : "00";
- str = tmHour + ':' + tmMinute + ':' + tmSeconds;
- } else {
- var isOtherVR = ( dicomElement.vr[0].toUpperCase() === "O" );
- var isFloatNumberVR = ( dicomElement.vr === "FL" ||
- dicomElement.vr === "FD" ||
- dicomElement.vr === "DS");
- var valueStr = "";
- for ( var k = 0, lenk = dicomElement.value.length; k < lenk; ++k ) {
- valueStr = "";
- if ( k !== 0 ) {
- valueStr += "\\";
- }
- if ( isFloatNumberVR ) {
- var val = dicomElement.value[k];
- if (typeof val === "string") {
- val = dwv.dicom.cleanString(val);
- }
- var num = Number( val );
- if ( !isInteger( num ) && pretty ) {
- valueStr += num.toPrecision(4);
- } else {
- valueStr += num.toString();
- }
- } else if ( isOtherVR ) {
- var tmp = dicomElement.value[k].toString(16);
- if ( dicomElement.vr === "OB" ) {
- tmp = "00".substr(0, 2 - tmp.length) + tmp;
- }
- else {
- tmp = "0000".substr(0, 4 - tmp.length) + tmp;
- }
- valueStr += tmp;
- } else if ( typeof dicomElement.value[k] === "string" ) {
- valueStr += dwv.dicom.cleanString(dicomElement.value[k]);
- } else {
- valueStr += dicomElement.value[k];
- }
- // check length
- if ( str.length + valueStr.length <= strLenLimit ) {
- str += valueStr;
- } else {
- str += "...";
- break;
- }
- }
- }
- return str;
-};
-
-/**
- * Get a data element value as a string.
- * @param {String} groupElementKey The key to retrieve.
- */
-dwv.dicom.DicomElementsWrapper.prototype.getElementValueAsStringFromKey = function ( groupElementKey )
-{
- return this.getElementValueAsString( this.getDEFromKey(groupElementKey) );
-};
-
-/**
- * Get a data element as a string.
- * @param {Object} dicomElement The DICOM element.
- * @param {String} prefix A string to prepend this one.
- */
-dwv.dicom.DicomElementsWrapper.prototype.getElementAsString = function ( dicomElement, prefix )
-{
- // default prefix
- prefix = prefix || "";
-
- // get element from dictionary
- var dict = dwv.dicom.dictionary;
- var dictElement = null;
- if ( typeof dict[dicomElement.tag.group] !== "undefined" &&
- typeof dict[dicomElement.tag.group][dicomElement.tag.element] !== "undefined") {
- dictElement = dict[dicomElement.tag.group][dicomElement.tag.element];
- }
-
- var deSize = dicomElement.value.length;
- var isOtherVR = ( dicomElement.vr[0].toUpperCase() === "O" );
-
- // no size for delimitations
- if ( dicomElement.tag.group === "0xFFFE" && (
- dicomElement.tag.element === "0xE00D" ||
- dicomElement.tag.element === "0xE0DD" ) ) {
- deSize = 0;
- }
- else if ( isOtherVR ) {
- deSize = 1;
- }
-
- var isPixSequence = (dicomElement.tag.group === '0x7FE0' &&
- dicomElement.tag.element === '0x0010' &&
- dicomElement.vl === 'u/l');
-
- var line = null;
-
- // (group,element)
- line = "(";
- line += dicomElement.tag.group.substr(2,5).toLowerCase();
- line += ",";
- line += dicomElement.tag.element.substr(2,5).toLowerCase();
- line += ") ";
- // value representation
- line += dicomElement.vr;
- // value
- if ( dicomElement.vr !== "SQ" && dicomElement.value.length === 1 && dicomElement.value[0] === "" ) {
- line += " (no value available)";
- deSize = 0;
- }
- else {
- // simple number display
- if ( dicomElement.vr === "na" ) {
- line += " ";
- line += dicomElement.value[0];
- }
- // pixel sequence
- else if ( isPixSequence ) {
- line += " (PixelSequence #=" + deSize + ")";
- }
- else if ( dicomElement.vr === 'SQ' ) {
- line += " (Sequence with";
- if ( dicomElement.vl === "u/l" ) {
- line += " undefined";
- }
- else {
- line += " explicit";
- }
- line += " length #=";
- line += dicomElement.value.length;
- line += ")";
- }
- // 'O'ther array, limited display length
- else if ( isOtherVR ||
- dicomElement.vr === 'pi' ||
- dicomElement.vr === "UL" ||
- dicomElement.vr === "US" ||
- dicomElement.vr === "SL" ||
- dicomElement.vr === "SS" ||
- dicomElement.vr === "FL" ||
- dicomElement.vr === "FD" ||
- dicomElement.vr === "AT" ) {
- line += " ";
- line += this.getElementValueAsString(dicomElement, false);
- }
- // default
- else {
- line += " [";
- line += this.getElementValueAsString(dicomElement, false);
- line += "]";
- }
- }
-
- // align #
- var nSpaces = 55 - line.length;
- if ( nSpaces > 0 ) {
- for ( var s = 0; s < nSpaces; ++s ) {
- line += " ";
- }
- }
- line += " # ";
- if ( dicomElement.vl < 100 ) {
- line += " ";
- }
- if ( dicomElement.vl < 10 ) {
- line += " ";
- }
- line += dicomElement.vl;
- line += ", ";
- line += deSize; //dictElement[1];
- line += " ";
- if ( dictElement !== null ) {
- line += dictElement[2];
- }
- else {
- line += "Unknown Tag & Data";
- }
-
- var message = null;
-
- // continue for sequence
- if ( dicomElement.vr === 'SQ' ) {
- var item = null;
- for ( var l = 0, lenl = dicomElement.value.length; l < lenl; ++l ) {
- item = dicomElement.value[l];
- var itemKeys = Object.keys(item);
- if ( itemKeys.length === 0 ) {
- continue;
- }
-
- // get the item element
- var itemElement = item.xFFFEE000;
- message = "(Item with";
- if ( itemElement.vl === "u/l" ) {
- message += " undefined";
- }
- else {
- message += " explicit";
- }
- message += " length #="+(itemKeys.length - 1)+")";
- itemElement.value = [message];
- itemElement.vr = "na";
-
- line += "\n";
- line += this.getElementAsString(itemElement, prefix + " ");
-
- for ( var m = 0, lenm = itemKeys.length; m < lenm; ++m ) {
- if ( itemKeys[m] !== "xFFFEE000" ) {
- line += "\n";
- line += this.getElementAsString(item[itemKeys[m]], prefix + " ");
- }
- }
-
- message = "(ItemDelimitationItem";
- if ( itemElement.vl !== "u/l" ) {
- message += " for re-encoding";
- }
- message += ")";
- var itemDelimElement = {
- "tag": { "group": "0xFFFE", "element": "0xE00D" },
- "vr": "na",
- "vl": "0",
- "value": [message]
- };
- line += "\n";
- line += this.getElementAsString(itemDelimElement, prefix + " ");
-
- }
-
- message = "(SequenceDelimitationItem";
- if ( dicomElement.vl !== "u/l" ) {
- message += " for re-encod.";
- }
- message += ")";
- var sqDelimElement = {
- "tag": { "group": "0xFFFE", "element": "0xE0DD" },
- "vr": "na",
- "vl": "0",
- "value": [message]
- };
- line += "\n";
- line += this.getElementAsString(sqDelimElement, prefix);
- }
- // pixel sequence
- else if ( isPixSequence ) {
- var pixItem = null;
- for ( var n = 0, lenn = dicomElement.value.length; n < lenn; ++n ) {
- pixItem = dicomElement.value[n];
- line += "\n";
- pixItem.vr = 'pi';
- line += this.getElementAsString(pixItem, prefix + " ");
- }
-
- var pixDelimElement = {
- "tag": { "group": "0xFFFE", "element": "0xE0DD" },
- "vr": "na",
- "vl": "0",
- "value": ["(SequenceDelimitationItem)"]
- };
- line += "\n";
- line += this.getElementAsString(pixDelimElement, prefix);
- }
-
- return prefix + line;
-};
-
-/**
- * Get a DICOM Element value from a group and an element.
- * @param {Number} group The group.
- * @param {Number} element The element.
- * @return {Object} The DICOM element value.
- */
-dwv.dicom.DicomElementsWrapper.prototype.getFromGroupElement = function (
- group, element )
-{
- return this.getFromKey(
- dwv.dicom.getGroupElementKey(group, element) );
-};
-
-/**
- * Get a DICOM Element value from a tag name.
- * Uses the DICOM dictionary.
- * @param {String} name The tag name.
- * @return {Object} The DICOM element value.
- */
-dwv.dicom.DicomElementsWrapper.prototype.getFromName = function ( name )
-{
- var value = null;
- var tagGE = dwv.dicom.getGroupElementFromName(name);
- // check that we are not at the end of the dictionary
- if ( tagGE.group !== null && tagGE.element !== null ) {
- value = this.getFromKey(dwv.dicom.getGroupElementKey(tagGE.group, tagGE.element));
- }
- return value;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.dicom = dwv.dicom || {};
-
-/**
- * Data writer.
- *
- * Example usage:
- * var parser = new dwv.dicom.DicomParser();
- * parser.parse(this.response);
- *
- * var writer = new dwv.dicom.DicomWriter(parser.getRawDicomElements());
- * var blob = new Blob([writer.getBuffer()], {type: 'application/dicom'});
- *
- * var element = document.getElementById("download");
- * element.href = URL.createObjectURL(blob);
- * element.download = "anonym.dcm";
- *
- * @constructor
- * @param {Array} buffer The input array buffer.
- * @param {Boolean} isLittleEndian Flag to tell if the data is little or big endian.
- */
-dwv.dicom.DataWriter = function (buffer, isLittleEndian)
-{
- // Set endian flag if not defined.
- if ( typeof isLittleEndian === 'undefined' ) {
- isLittleEndian = true;
- }
-
- // private DataView
- var view = new DataView(buffer);
-
- /**
- * Write Uint8 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeUint8 = function (byteOffset, value) {
- view.setUint8(byteOffset, value);
- return byteOffset + Uint8Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Int8 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeInt8 = function (byteOffset, value) {
- view.setInt8(byteOffset, value);
- return byteOffset + Int8Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Uint16 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeUint16 = function (byteOffset, value) {
- view.setUint16(byteOffset, value, isLittleEndian);
- return byteOffset + Uint16Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Int16 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeInt16 = function (byteOffset, value) {
- view.setInt16(byteOffset, value, isLittleEndian);
- return byteOffset + Int16Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Uint32 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeUint32 = function (byteOffset, value) {
- view.setUint32(byteOffset, value, isLittleEndian);
- return byteOffset + Uint32Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Int32 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeInt32 = function (byteOffset, value) {
- view.setInt32(byteOffset, value, isLittleEndian);
- return byteOffset + Int32Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Float32 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeFloat32 = function (byteOffset, value) {
- view.setFloat32(byteOffset, value, isLittleEndian);
- return byteOffset + Float32Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write Float64 data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} value The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeFloat64 = function (byteOffset, value) {
- view.setFloat64(byteOffset, value, isLittleEndian);
- return byteOffset + Float64Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write string data as hexadecimal.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} str The padded hexadecimal string to write ('0x####').
- * @returns {Number} The new offset position.
- */
- this.writeHex = function (byteOffset, str) {
- // remove first two chars and parse
- var value = parseInt(str.substr(2), 16);
- view.setUint16(byteOffset, value, isLittleEndian);
- return byteOffset + Uint16Array.BYTES_PER_ELEMENT;
- };
-
- /**
- * Write string data.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Number} str The data to write.
- * @returns {Number} The new offset position.
- */
- this.writeString = function (byteOffset, str) {
- for ( var i = 0, len = str.length; i < len; ++i ) {
- view.setUint8(byteOffset, str.charCodeAt(i));
- byteOffset += Uint8Array.BYTES_PER_ELEMENT;
- }
- return byteOffset;
- };
-
-};
-
-/**
- * Write Uint8 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeUint8Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeUint8(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Int8 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeInt8Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeInt8(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Uint16 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeUint16Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeUint16(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Int16 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeInt16Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeInt16(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Uint32 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeUint32Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeUint32(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Int32 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeInt32Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeInt32(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Float32 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeFloat32Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeFloat32(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write Float64 array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeFloat64Array = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- byteOffset = this.writeFloat64(byteOffset, array[i]);
- }
- return byteOffset;
-};
-
-/**
- * Write string array.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} array The array to write.
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeStringArray = function (byteOffset, array) {
- for ( var i = 0, len = array.length; i < len; ++i ) {
- // separator
- if ( i !== 0 ) {
- byteOffset = this.writeString(byteOffset, "\\");
- }
- // value
- byteOffset = this.writeString(byteOffset, array[i].toString());
- }
- return byteOffset;
-};
-
-/**
- * Write a list of items.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} items The list of items to write.
- * @param {Boolean} isImplicit Is the DICOM VR implicit?
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeDataElementItems = function (byteOffset, items, isImplicit) {
- var item = null;
- for ( var i = 0; i < items.length; ++i ) {
- item = items[i];
- var itemKeys = Object.keys(item);
- if ( itemKeys.length === 0 ) {
- continue;
- }
- // write item
- var itemElement = item.xFFFEE000;
- itemElement.value = [];
- var implicitLength = (itemElement.vl === "u/l");
- if (implicitLength) {
- itemElement.vl = 0xffffffff;
- }
- byteOffset = this.writeDataElement(itemElement, byteOffset, isImplicit);
- // write rest
- for ( var m = 0; m < itemKeys.length; ++m ) {
- if ( itemKeys[m] !== "xFFFEE000" && itemKeys[m] !== "xFFFEE00D") {
- byteOffset = this.writeDataElement(item[itemKeys[m]], byteOffset, isImplicit);
- }
- }
- // item delimitation
- if (implicitLength) {
- var itemDelimElement = {
- 'tag': { group: "0xFFFE",
- element: "0xE00D",
- name: "ItemDelimitationItem" },
- 'vr': "NONE",
- 'vl': 0,
- 'value': []
- };
- byteOffset = this.writeDataElement(itemDelimElement, byteOffset, isImplicit);
- }
- }
-
- // return new offset
- return byteOffset;
-};
-
-/**
- * Write data with a specific Value Representation (VR).
- * @param {String} vr The data Value Representation (VR).
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} value The array to write.
- * @param {Boolean} isImplicit Is the DICOM VR implicit?
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeDataElementValue = function (vr, byteOffset, value, isImplicit) {
- // first check input type to know how to write
- if (value instanceof Uint8Array) {
- byteOffset = this.writeUint8Array(byteOffset, value);
- } else if (value instanceof Int8Array) {
- byteOffset = this.writeInt8Array(byteOffset, value);
- } else if (value instanceof Uint16Array) {
- byteOffset = this.writeUint16Array(byteOffset, value);
- } else if (value instanceof Int16Array) {
- byteOffset = this.writeInt16Array(byteOffset, value);
- } else if (value instanceof Uint32Array) {
- byteOffset = this.writeUint32Array(byteOffset, value);
- } else if (value instanceof Int32Array) {
- byteOffset = this.writeInt32Array(byteOffset, value);
- } else {
- // switch according to VR if input type is undefined
- if ( vr === "UN" ) {
- byteOffset = this.writeUint8Array(byteOffset, value);
- } else if ( vr === "OB" ) {
- byteOffset = this.writeInt8Array(byteOffset, value);
- } else if ( vr === "OW" ) {
- byteOffset = this.writeInt16Array(byteOffset, value);
- } else if ( vr === "OF" ) {
- byteOffset = this.writeInt32Array(byteOffset, value);
- } else if ( vr === "OD" ) {
- byteOffset = this.writeInt64Array(byteOffset, value);
- } else if ( vr === "US") {
- byteOffset = this.writeUint16Array(byteOffset, value);
- } else if ( vr === "SS") {
- byteOffset = this.writeInt16Array(byteOffset, value);
- } else if ( vr === "UL") {
- byteOffset = this.writeUint32Array(byteOffset, value);
- } else if ( vr === "SL") {
- byteOffset = this.writeInt32Array(byteOffset, value);
- } else if ( vr === "FL") {
- byteOffset = this.writeFloat32Array(byteOffset, value);
- } else if ( vr === "FD") {
- byteOffset = this.writeFloat64Array(byteOffset, value);
- } else if ( vr === "SQ") {
- byteOffset = this.writeDataElementItems(byteOffset, value, isImplicit);
- } else if ( vr === "AT") {
- for ( var i = 0; i < value.length; ++i ) {
- var hexString = value[i] + '';
- var hexString1 = hexString.substring(1, 5);
- var hexString2 = hexString.substring(6, 10);
- var dec1 = parseInt(hexString1, 16);
- var dec2 = parseInt(hexString2, 16);
- var atValue = new Uint16Array([dec1, dec2]);
- byteOffset = this.writeUint16Array(byteOffset, atValue);
- }
- } else {
- byteOffset = this.writeStringArray(byteOffset, value);
- }
- }
- // return new offset
- return byteOffset;
-};
-
-/**
- * Write a pixel data element.
- * @param {String} vr The data Value Representation (VR).
- * @param {String} vl The data Value Length (VL).
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Array} value The array to write.
- * @param {Boolean} isImplicit Is the DICOM VR implicit?
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writePixelDataElementValue = function (vr, vl, byteOffset, value, isImplicit) {
- // explicit length
- if (vl !== "u/l") {
- var finalValue = value[0];
- // flatten multi frame
- if (value.length > 1) {
- finalValue = dwv.dicom.flattenArrayOfTypedArrays(value);
- }
- // write
- byteOffset = this.writeDataElementValue(vr, byteOffset, finalValue, isImplicit);
- } else {
- // pixel data as sequence
- var item = {};
- // first item: basic offset table
- item.xFFFEE000 = {
- 'tag': { group: "0xFFFE",
- element: "0xE000",
- name: "xFFFEE000" },
- 'vr': "UN",
- 'vl': 0,
- 'value': []
- };
- // data
- for (var i = 0; i < value.length; ++i) {
- item[i] = {
- 'tag': { group: "0xFFFE",
- element: "0xE000",
- name: "xFFFEE000" },
- 'vr': vr,
- 'vl': value[i].length,
- 'value': value[i]
- };
- }
- // write
- byteOffset = this.writeDataElementItems(byteOffset, [item], isImplicit);
- }
-
- // return new offset
- return byteOffset;
-};
-
-/**
- * Write a data element.
- * @param {Object} element The DICOM data element to write.
- * @param {Number} byteOffset The offset to start writing from.
- * @param {Boolean} isImplicit Is the DICOM VR implicit?
- * @returns {Number} The new offset position.
- */
-dwv.dicom.DataWriter.prototype.writeDataElement = function (element, byteOffset, isImplicit) {
- var isTagWithVR = dwv.dicom.isTagWithVR(element.tag.group, element.tag.element);
- var is32bitVLVR = (isImplicit || !isTagWithVR) ? true : dwv.dicom.is32bitVLVR(element.vr);
- // group
- byteOffset = this.writeHex(byteOffset, element.tag.group);
- // element
- byteOffset = this.writeHex(byteOffset, element.tag.element);
- // VR
- if ( isTagWithVR && !isImplicit ) {
- byteOffset = this.writeString(byteOffset, element.vr);
- // reserved 2 bytes for 32bit VL
- if ( is32bitVLVR ) {
- byteOffset += 2;
- }
- }
-
- // update vl for sequence or item with implicit length
- var vl = element.vl;
- if ( dwv.dicom.isImplicitLengthSequence(element) ||
- dwv.dicom.isImplicitLengthItem(element) ||
- dwv.dicom.isImplicitLengthPixels(element) ) {
- vl = 0xffffffff;
- }
- // VL
- if ( is32bitVLVR ) {
- byteOffset = this.writeUint32(byteOffset, vl);
- }
- else {
- byteOffset = this.writeUint16(byteOffset, vl);
- }
-
- // value
- var value = element.value;
- // check value
- if (typeof value === 'undefined') {
- value = [];
- }
- // write
- if (element.tag.name === "x7FE00010") {
- byteOffset = this.writePixelDataElementValue(element.vr, element.vl, byteOffset, value, isImplicit);
- } else {
- byteOffset = this.writeDataElementValue(element.vr, byteOffset, value, isImplicit);
- }
-
- // sequence delimitation item for sequence with implicit length
- if ( dwv.dicom.isImplicitLengthSequence(element) ||
- dwv.dicom.isImplicitLengthPixels(element) ) {
- var seqDelimElement = {
- 'tag': { group: "0xFFFE",
- element: "0xE0DD",
- name: "SequenceDelimitationItem" },
- 'vr': "NONE",
- 'vl': 0,
- 'value': []
- };
- byteOffset = this.writeDataElement(seqDelimElement, byteOffset, isImplicit);
- }
-
- // return new offset
- return byteOffset;
-};
-
-/**
- * Is this element an implicit length sequence?
- * @param {Object} element The element to check.
- * @returns {Boolean} True if it is.
- */
-dwv.dicom.isImplicitLengthSequence = function (element) {
- // sequence with no length
- return (element.vr === "SQ") &&
- (element.vl === "u/l");
-};
-
-/**
- * Is this element an implicit length item?
- * @param {Object} element The element to check.
- * @returns {Boolean} True if it is.
- */
-dwv.dicom.isImplicitLengthItem = function (element) {
- // item with no length
- return (element.tag.name === "xFFFEE000") &&
- (element.vl === "u/l");
-};
-
-/**
- * Is this element an implicit length pixel data?
- * @param {Object} element The element to check.
- * @returns {Boolean} True if it is.
- */
-dwv.dicom.isImplicitLengthPixels = function (element) {
- // pixel data with no length
- return (element.tag.name === "x7FE00010") &&
- (element.vl === "u/l");
-};
-
-/**
- * Helper method to flatten an array of typed arrays to 2D typed array
- * @param {Array} array of typed arrays
- * @returns {Object} a typed array containing all values
- */
-dwv.dicom.flattenArrayOfTypedArrays = function(initialArray) {
- var initialArrayLength = initialArray.length;
- var arrayLength = initialArray[0].length;
- var flattenendArrayLength = initialArrayLength * arrayLength;
-
- var flattenedArray = new initialArray[0].constructor(flattenendArrayLength);
-
- for (var i = 0; i < initialArrayLength; i++) {
- var indexFlattenedArray = i * arrayLength;
- flattenedArray.set(initialArray[i], indexFlattenedArray);
- }
- return flattenedArray;
-};
-
-/**
- * DICOM writer.
- * @constructor
- */
-dwv.dicom.DicomWriter = function () {
-
- // possible tag actions
- var actions = {
- 'copy': function (item) { return item; },
- 'remove': function () { return null; },
- 'clear': function (item) {
- item.value[0] = "";
- item.vl = 0;
- item.endOffset = item.startOffset;
- return item;
- },
- 'replace': function (item, value) {
- item.value[0] = value;
- item.vl = value.length;
- item.endOffset = item.startOffset + value.length;
- return item;
- }
- };
-
- // default rules: just copy
- var defaultRules = {
- 'default': {action: 'copy', value: null }
- };
-
- /**
- * Public (modifiable) rules.
- * Set of objects as:
- * name : { action: 'actionName', value: 'optionalValue }
- * The names are either 'default', tagName or groupName.
- * Each DICOM element will be checked to see if a rule is applicable.
- * First checked by tagName and then by groupName,
- * if nothing is found the default rule is applied.
- */
- this.rules = defaultRules;
-
- /**
- * Example anonymisation rules.
- */
- this.anonymisationRules = {
- 'default': {action: 'remove', value: null },
- 'PatientName': {action: 'replace', value: 'Anonymized'}, // tag
- 'Meta Element' : {action: 'copy', value: null }, // group 'x0002'
- 'Acquisition' : {action: 'copy', value: null }, // group 'x0018'
- 'Image Presentation' : {action: 'copy', value: null }, // group 'x0028'
- 'Procedure' : {action: 'copy', value: null }, // group 'x0040'
- 'Pixel Data' : {action: 'copy', value: null } // group 'x7fe0'
- };
-
- /**
- * Get the element to write according to the class rules.
- * Priority order: tagName, groupName, default.
- * @param {Object} element The element to check
- * @return {Object} The element to write, can be null.
- */
- this.getElementToWrite = function (element) {
- // get group and tag string name
- var tagName = null;
- var dict = dwv.dicom.dictionary;
- var group = element.tag.group;
- var groupName = dwv.dicom.TagGroups[group.substr(1)]; // remove first 0
-
- if ( typeof dict[group] !== 'undefined' && typeof dict[group][element.tag.element] !== 'undefined') {
- tagName = dict[group][element.tag.element][2];
- }
- // apply rules:
- var rule;
- // 1. tag itself
- if (typeof this.rules[element.tag.name] !== 'undefined') {
- rule = this.rules[element.tag.name];
- }
- // 2. tag name
- else if ( tagName !== null && typeof this.rules[tagName] !== 'undefined' ) {
- rule = this.rules[tagName];
- }
- // 3. group name
- else if ( typeof this.rules[groupName] !== 'undefined' ) {
- rule = this.rules[groupName];
- }
- // 4. default
- else {
- rule = this.rules['default'];
- }
- // apply action on element and return
- return actions[rule.action](element, rule.value);
- };
-};
-
-/**
- * Get the ArrayBuffer corresponding to input DICOM elements.
- * @param {Array} dicomElements The wrapped elements to write.
- * @returns {ArrayBuffer} The elements as a buffer.
- */
-dwv.dicom.DicomWriter.prototype.getBuffer = function (dicomElements) {
- // array keys
- var keys = Object.keys(dicomElements);
-
- // transfer syntax
- var syntax = dwv.dicom.cleanString(dicomElements.x00020010.value[0]);
- var isImplicit = dwv.dicom.isImplicitTransferSyntax(syntax);
- var isBigEndian = dwv.dicom.isBigEndianTransferSyntax(syntax);
-
- // calculate buffer size and split elements (meta and non meta)
- var totalSize = 128 + 4; // DICM
- var localSize = 0;
- var metaElements = [];
- var rawElements = [];
- var element;
- var groupName;
- var metaLength = 0;
- var fmiglTag = dwv.dicom.getFileMetaInformationGroupLengthTag();
- var icUIDTag = new dwv.dicom.Tag("0x0002", "0x0012"); // ImplementationClassUID
- var ivnTag = new dwv.dicom.Tag("0x0002", "0x0013"); // ImplementationVersionName
- for ( var i = 0, leni = keys.length; i < leni; ++i ) {
- element = this.getElementToWrite(dicomElements[keys[i]]);
- if ( element !== null &&
- !fmiglTag.equals2(element.tag) &&
- !icUIDTag.equals2(element.tag) &&
- !ivnTag.equals2(element.tag) ) {
- localSize = 0;
- // tag group name
- groupName = dwv.dicom.TagGroups[element.tag.group.substr(1)]; // remove first 0
-
- // prefix
- if ( groupName === 'Meta Element' ) {
- localSize += dwv.dicom.getDataElementPrefixByteSize(element.vr, false);
- } else {
- localSize += dwv.dicom.getDataElementPrefixByteSize(element.vr, isImplicit);
- }
-
- // value
- var realVl = element.endOffset - element.startOffset;
- localSize += parseInt(realVl, 10);
-
- // add size of sequence delimitation item
- if ( dwv.dicom.isImplicitLengthSequence(element) ||
- dwv.dicom.isImplicitLengthPixels(element) ) {
- localSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
- }
-
- // sort elements
- if ( groupName === 'Meta Element' ) {
- metaElements.push(element);
- metaLength += localSize;
- }
- else {
- rawElements.push(element);
- }
-
- // add to total size
- totalSize += localSize;
- }
- }
-
- // ImplementationClassUID
- var icUID = dwv.dicom.getDicomElement("ImplementationClassUID");
- var icUIDSize = dwv.dicom.getDataElementPrefixByteSize(icUID.vr, isImplicit);
- icUIDSize += dwv.dicom.setElementValue(icUID, "1.2.826.0.1.3680043.9.7278.1."+dwv.getVersion(), false);
- metaElements.push(icUID);
- metaLength += icUIDSize;
- totalSize += icUIDSize;
- // ImplementationVersionName
- var ivn = dwv.dicom.getDicomElement("ImplementationVersionName");
- var ivnSize = dwv.dicom.getDataElementPrefixByteSize(ivn.vr, isImplicit);
- var ivnValue = "DWV_" + dwv.getVersion();
- // odd IDs should be padded
- if ( ivnValue.length % 2 === 1 ) {
- ivnValue += '\0';
- }
- ivnSize += dwv.dicom.setElementValue(ivn, ivnValue, false);
- metaElements.push(ivn);
- metaLength += ivnSize;
- totalSize += ivnSize;
-
- // create the FileMetaInformationGroupLength element
- var fmigl = dwv.dicom.getDicomElement("FileMetaInformationGroupLength");
- var fmiglSize = dwv.dicom.getDataElementPrefixByteSize(fmigl.vr, isImplicit);
- fmiglSize += dwv.dicom.setElementValue(fmigl, metaLength, false);
-
- // add its size to the total one
- totalSize += fmiglSize;
-
- // create buffer
- var buffer = new ArrayBuffer(totalSize);
- var metaWriter = new dwv.dicom.DataWriter(buffer);
- var dataWriter = new dwv.dicom.DataWriter(buffer, !isBigEndian);
- var offset = 128;
- // DICM
- offset = metaWriter.writeString(offset, "DICM");
- // FileMetaInformationGroupLength
- offset = metaWriter.writeDataElement(fmigl, offset, false);
- // write meta
- for ( var j = 0, lenj = metaElements.length; j < lenj; ++j ) {
- offset = metaWriter.writeDataElement(metaElements[j], offset, false);
- }
- // write non meta
- for ( var k = 0, lenk = rawElements.length; k < lenk; ++k ) {
- offset = dataWriter.writeDataElement(rawElements[k], offset, isImplicit);
- }
-
- // return
- return buffer;
-};
-
-/**
- * Get a DICOM element from its tag name (value set separatly).
- * @param {String} tagName The string tag name.
- * @return {Object} The DICOM element.
- */
-dwv.dicom.getDicomElement = function (tagName)
-{
- var tagGE = dwv.dicom.getGroupElementFromName(tagName);
- var dict = dwv.dicom.dictionary;
- // return element definition
- return {
- 'tag': { 'group': tagGE.group, 'element': tagGE.element },
- 'vr': dict[tagGE.group][tagGE.element][0],
- 'vl': dict[tagGE.group][tagGE.element][1]
- };
-};
-
-/**
- * Set a DICOM element value according to its VR (Value Representation).
- * @param {Object} element The DICOM element to set the value.
- * @param {Object} value The value to set.
- * @param {Boolean} isImplicit Does the data use implicit VR?
- * @return {Number} The total element size.
- */
-dwv.dicom.setElementValue = function (element, value, isImplicit) {
- // byte size of the element
- var size = 0;
- // special sequence case
- if ( element.vr === "SQ") {
-
- // set the value
- element.value = value;
- element.vl = 0;
-
- if ( value !== null && value !== 0 ) {
- var sqItems = [];
- var name;
-
- // explicit or implicit length
- var explicitLength = true;
- if ( typeof value.explicitLength !== "undefined" ) {
- explicitLength = value.explicitLength;
- delete value.explicitLength;
- }
-
- // items
- var itemData;
- var itemKeys = Object.keys(value);
- for ( var i = 0, leni = itemKeys.length; i < leni; ++i )
- {
- var itemElements = {};
- var subSize = 0;
- itemData = value[itemKeys[i]];
-
- // check data
- if ( itemData === null || itemData === 0 ) {
- continue;
- }
-
- // elements
- var subElement;
- var elemKeys = Object.keys(itemData);
- for ( var j = 0, lenj = elemKeys.length; j < lenj; ++j )
- {
- subElement = dwv.dicom.getDicomElement(elemKeys[j]);
- subSize += dwv.dicom.setElementValue(subElement, itemData[elemKeys[j]]);
-
- // add sequence delimitation size for sub sequences
- if (dwv.dicom.isImplicitLengthSequence(subElement)) {
- subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
- }
-
- name = dwv.dicom.getGroupElementKey(subElement.tag.group, subElement.tag.element);
- itemElements[name] = subElement;
- subSize += dwv.dicom.getDataElementPrefixByteSize(subElement.vr, isImplicit);
- }
-
- // item (after elements to get the size)
- var itemElement = {
- 'tag': { 'group': "0xFFFE", 'element': "0xE000" },
- 'vr': "NONE",
- 'vl': (explicitLength ? subSize : "u/l"),
- 'value': []
- };
- name = dwv.dicom.getGroupElementKey(itemElement.tag.group, itemElement.tag.element);
- itemElements[name] = itemElement;
- subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
-
- // item delimitation
- if (!explicitLength) {
- var itemDelimElement = {
- 'tag': { 'group': "0xFFFE", 'element': "0xE00D" },
- 'vr': "NONE",
- 'vl': 0,
- 'value': []
- };
- name = dwv.dicom.getGroupElementKey(itemDelimElement.tag.group, itemDelimElement.tag.element);
- itemElements[name] = itemDelimElement;
- subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
- }
-
- size += subSize;
- sqItems.push(itemElements);
- }
-
- element.value = sqItems;
- if (explicitLength) {
- element.vl = size;
- } else {
- element.vl = "u/l";
- }
- }
- }
- else {
- // set the value and calculate size
- size = 0;
- if (value instanceof Array) {
- element.value = value;
- for (var k = 0; k < value.length; ++k) {
- // spearator
- if (k !== 0) {
- size += 1;
- }
- // value
- size += value[k].toString().length;
- }
- }
- else {
- element.value = [value];
- if (typeof value !== "undefined" && typeof value.length !== "undefined") {
- size = value.length;
- }
- else {
- // numbers
- size = 1;
- }
- }
-
- // convert size to bytes
- if ( element.vr === "US" || element.vr === "OW") {
- size *= Uint16Array.BYTES_PER_ELEMENT;
- }
- else if ( element.vr === "SS") {
- size *= Int16Array.BYTES_PER_ELEMENT;
- }
- else if ( element.vr === "UL") {
- size *= Uint32Array.BYTES_PER_ELEMENT;
- }
- else if ( element.vr === "SL") {
- size *= Int32Array.BYTES_PER_ELEMENT;
- }
- else if ( element.vr === "FL") {
- size *= Float32Array.BYTES_PER_ELEMENT;
- }
- else if ( element.vr === "FD") {
- size *= Float64Array.BYTES_PER_ELEMENT;
- }
- else {
- size *= Uint8Array.BYTES_PER_ELEMENT;
- }
- element.vl = size;
- }
-
- // return the size of that data
- return size;
-};
-
-/**
- * Get the DICOM element from a DICOM tags object.
- * @param {Object} tags The DICOM tags object.
- * @return {Object} The DICOM elements and the end offset.
- */
-dwv.dicom.getElementsFromJSONTags = function (tags) {
- // transfer syntax
- var isImplicit = dwv.dicom.isImplicitTransferSyntax(tags.TransferSyntaxUID);
- // convert JSON to DICOM element object
- var keys = Object.keys(tags);
- var dicomElements = {};
- var dicomElement;
- var name;
- var offset = 128 + 4; // preamble
- var size;
- for ( var k = 0, len = keys.length; k < len; ++k )
- {
- // get the DICOM element definition from its name
- dicomElement = dwv.dicom.getDicomElement(keys[k]);
- // set its value
- size = dwv.dicom.setElementValue(dicomElement, tags[keys[k]], isImplicit);
- // set offsets
- offset += dwv.dicom.getDataElementPrefixByteSize(dicomElement.vr, isImplicit);
- dicomElement.startOffset = offset;
- offset += size;
- dicomElement.endOffset = offset;
- // create the tag group/element key
- name = dwv.dicom.getGroupElementKey(dicomElement.tag.group, dicomElement.tag.element);
- // store
- dicomElements[name] = dicomElement;
- }
- // return
- return {'elements': dicomElements, 'offset': offset };
-};
-
-/**
- * GradSquarePixGenerator
- * Generates a small gradient square.
- * @param {Number} numberOfColumns The image number of columns.
- * @param {Number} numberOfRows The image number of rows.
- * @constructor
- */
-var GradSquarePixGenerator = function (numberOfColumns, numberOfRows) {
-
- var halfCols = numberOfColumns * 0.5;
- var halfRows = numberOfRows * 0.5;
- var maxNoBounds = (numberOfColumns/2 + halfCols/2) * (numberOfRows/2 + halfRows/2);
- var max = 100;
-
- /**
- * Get a grey value.
- * @param {Number} i The column index.
- * @param {Number} j The row index.
- * @return {Array} The grey value.
- */
- this.getGrey = function (i,j) {
- var value = max;
- var jc = Math.abs( j - halfRows );
- var ic = Math.abs( i - halfCols );
- if ( jc < halfRows/2 && ic < halfCols/2 ) {
- value += (i * j) * (max / maxNoBounds);
- }
- return [value];
- };
-
- /**
- * Get RGB values.
- * @param {Number} i The column index.
- * @param {Number} j The row index.
- * @return {Array} The [R,G,B] values.
- */
- this.getRGB = function (i,j) {
- var value = 0;
- var jc = Math.abs( j - halfRows );
- var ic = Math.abs( i - halfCols );
- if ( jc < halfRows/2 && ic < halfCols/2 ) {
- value += (i * j) * (max / maxNoBounds);
- }
- if (value > 255 ) {
- value = 200;
- }
- return [0, value, value];
- };
-};
-
-// List of pixel generators.
-dwv.dicom.pixelGenerators = {
- 'gradSquare': GradSquarePixGenerator
-};
-
-/**
- * Get the DICOM pixel data from a DICOM tags object.
- * @param {Object} tags The DICOM tags object.
- * @param {Object} startOffset The start offset of the pixel data.
- * @param {String} pixGeneratorName The name of a pixel generator.
- * @return {Object} The DICOM pixel data element.
- */
-dwv.dicom.generatePixelDataFromJSONTags = function (tags, startOffset, pixGeneratorName) {
-
- // default generator
- if ( typeof pixGeneratorName === "undefined" ) {
- pixGeneratorName = "gradSquare";
- }
-
- // check tags
- if ( typeof tags.TransferSyntaxUID === "undefined" ) {
- throw new Error("Missing transfer syntax for pixel generation.");
- } else if ( typeof tags.Rows === "undefined" ) {
- throw new Error("Missing number of rows for pixel generation.");
- } else if ( typeof tags.Columns === "undefined" ) {
- throw new Error("Missing number of columns for pixel generation.");
- } else if ( typeof tags.BitsAllocated === "undefined" ) {
- throw new Error("Missing BitsAllocated for pixel generation.");
- } else if ( typeof tags.PixelRepresentation === "undefined" ) {
- throw new Error("Missing PixelRepresentation for pixel generation.");
- } else if ( typeof tags.SamplesPerPixel === "undefined" ) {
- throw new Error("Missing SamplesPerPixel for pixel generation.");
- } else if ( typeof tags.PhotometricInterpretation === "undefined" ) {
- throw new Error("Missing PhotometricInterpretation for pixel generation.");
- }
-
- // extract info from tags
- var isImplicit = dwv.dicom.isImplicitTransferSyntax(tags.TransferSyntaxUID);
- var numberOfRows = tags.Rows;
- var numberOfColumns = tags.Columns;
- var bitsAllocated = tags.BitsAllocated;
- var pixelRepresentation = tags.PixelRepresentation;
- var samplesPerPixel = tags.SamplesPerPixel;
- var photometricInterpretation = tags.PhotometricInterpretation;
-
- var sliceLength = numberOfRows * numberOfColumns;
- var dataLength = sliceLength * samplesPerPixel;
-
- // check values
- if (samplesPerPixel !== 1 && samplesPerPixel !== 3) {
- throw new Error("Unsupported SamplesPerPixel for pixel generation: "+samplesPerPixel);
- }
- if ( (samplesPerPixel === 1 && !( photometricInterpretation === "MONOCHROME1" ||
- photometricInterpretation === "MONOCHROME2" ) ) ||
- ( samplesPerPixel === 3 && photometricInterpretation !== "RGB" ) ) {
- throw new Error("Unsupported PhotometricInterpretation for pixel generation: " +
- photometricInterpretation + " with SamplesPerPixel: " + samplesPerPixel);
- }
-
- var nSamples = 1;
- var nColourPlanes = 1;
- if ( samplesPerPixel === 3 ) {
- if ( typeof tags.PlanarConfiguration === "undefined" ) {
- throw new Error("Missing PlanarConfiguration for pixel generation.");
- }
- var planarConfiguration = tags.PlanarConfiguration;
- if ( planarConfiguration !== 0 && planarConfiguration !== 1 ) {
- throw new Error("Unsupported PlanarConfiguration for pixel generation: "+planarConfiguration);
- }
- if ( planarConfiguration === 0 ) {
- nSamples = 3;
- } else {
- nColourPlanes = 3;
- }
- }
-
- // create pixel array
- var pixels = dwv.dicom.getTypedArray(
- bitsAllocated, pixelRepresentation, dataLength );
-
- // pixels generator
- if (typeof dwv.dicom.pixelGenerators[pixGeneratorName] === "undefined" ) {
- throw new Error("Unknown PixelData generator: "+pixGeneratorName);
- }
- var generator = new dwv.dicom.pixelGenerators[pixGeneratorName](numberOfColumns, numberOfRows);
- var generate = generator.getGrey;
- if (photometricInterpretation === "RGB") {
- generate = generator.getRGB;
- }
-
- // main loop
- var offset = 0;
- for ( var c = 0; c < nColourPlanes; ++c ) {
- for ( var j = 0; j < numberOfRows; ++j ) {
- for ( var i = 0; i < numberOfColumns; ++i ) {
- for ( var s = 0; s < nSamples; ++s ) {
- if ( nColourPlanes !== 1 ) {
- pixels[offset] = generate(i,j)[c];
- } else {
- pixels[offset] = generate(i,j)[s];
- }
- ++offset;
- }
- }
- }
- }
-
- // create and return the DICOM element
- var vr = "OW";
- if ( bitsAllocated === 8 ) {
- vr = "OB";
- }
- var pixVL = dwv.dicom.getDataElementPrefixByteSize(vr, isImplicit) +
- (pixels.BYTES_PER_ELEMENT * dataLength);
- return {
- 'tag': { 'group': "0x7FE0", 'element': "0x0010" },
- 'vr': vr,
- 'vl': pixVL,
- 'value': pixels,
- 'startOffset': startOffset,
- 'endOffset': startOffset + pixVL
- };
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.dicom = dwv.dicom || {};
-
-/**
- * DICOM tag dictionary.
- * Generated using xml standard conversion
- * from {@link https://github.com/ivmartel/dcmbench/tree/master/view/part06}
- * with {@link http://medical.nema.org/medical/dicom/current/source/docbook/part06/part06.xml}
- * Conversion changes:
- * - (vr) "See Note" -> "NONE", "OB or OW" -> "ox", "US or SS" -> "xs"
- * - added "GenericGroupLength" element to each group
- * Local changes:
- * - tag numbers with 'xx' were replaced with '00', 'xxx' with '001' and 'xxxx' with '0004'
- */
-dwv.dicom.dictionary = {
- '0x0000': {
- '0x0000': ['UL', '1', 'GroupLength'],
- '0x0001': ['UL', '1', 'CommandLengthToEnd'],
- '0x0002': ['UI', '1', 'AffectedSOPClassUID'],
- '0x0003': ['UI', '1', 'RequestedSOPClassUID'],
- '0x0010': ['CS', '1', 'CommandRecognitionCode'],
- '0x0100': ['US', '1', 'CommandField'],
- '0x0110': ['US', '1', 'MessageID'],
- '0x0120': ['US', '1', 'MessageIDBeingRespondedTo'],
- '0x0200': ['AE', '1', 'Initiator'],
- '0x0300': ['AE', '1', 'Receiver'],
- '0x0400': ['AE', '1', 'FindLocation'],
- '0x0600': ['AE', '1', 'MoveDestination'],
- '0x0700': ['US', '1', 'Priority'],
- '0x0800': ['US', '1', 'DataSetType'],
- '0x0850': ['US', '1', 'NumberOfMatches'],
- '0x0860': ['US', '1', 'ResponseSequenceNumber'],
- '0x0900': ['US', '1', 'Status'],
- '0x0901': ['AT', '1-n', 'OffendingElement'],
- '0x0902': ['LO', '1', 'ErrorComment'],
- '0x0903': ['US', '1', 'ErrorID'],
- '0x0904': ['OT', '1-n', 'ErrorInformation'],
- '0x1000': ['UI', '1', 'AffectedSOPInstanceUID'],
- '0x1001': ['UI', '1', 'RequestedSOPInstanceUID'],
- '0x1002': ['US', '1', 'EventTypeID'],
- '0x1003': ['OT', '1-n', 'EventInformation'],
- '0x1005': ['AT', '1-n', 'AttributeIdentifierList'],
- '0x1007': ['AT', '1-n', 'ModificationList'],
- '0x1008': ['US', '1', 'ActionTypeID'],
- '0x1009': ['OT', '1-n', 'ActionInformation'],
- '0x1013': ['UI', '1-n', 'SuccessfulSOPInstanceUIDList'],
- '0x1014': ['UI', '1-n', 'FailedSOPInstanceUIDList'],
- '0x1015': ['UI', '1-n', 'WarningSOPInstanceUIDList'],
- '0x1020': ['US', '1', 'NumberOfRemainingSuboperations'],
- '0x1021': ['US', '1', 'NumberOfCompletedSuboperations'],
- '0x1022': ['US', '1', 'NumberOfFailedSuboperations'],
- '0x1023': ['US', '1', 'NumberOfWarningSuboperations'],
- '0x1030': ['AE', '1', 'MoveOriginatorApplicationEntityTitle'],
- '0x1031': ['US', '1', 'MoveOriginatorMessageID'],
- '0x4000': ['AT', '1', 'DialogReceiver'],
- '0x4010': ['AT', '1', 'TerminalType'],
- '0x5010': ['SH', '1', 'MessageSetID'],
- '0x5020': ['SH', '1', 'EndMessageSet'],
- '0x5110': ['AT', '1', 'DisplayFormat'],
- '0x5120': ['AT', '1', 'PagePositionID'],
- '0x5130': ['CS', '1', 'TextFormatID'],
- '0x5140': ['CS', '1', 'NormalReverse'],
- '0x5150': ['CS', '1', 'AddGrayScale'],
- '0x5160': ['CS', '1', 'Borders'],
- '0x5170': ['IS', '1', 'Copies'],
- '0x5180': ['CS', '1', 'OldMagnificationType'],
- '0x5190': ['CS', '1', 'Erase'],
- '0x51A0': ['CS', '1', 'Print'],
- '0x51B0': ['US', '1-n', 'Overlays']
- },
- '0x0002': {
- '0x0000': ['UL', '1', 'FileMetaInformationGroupLength'],
- '0x0001': ['OB', '1', 'FileMetaInformationVersion'],
- '0x0002': ['UI', '1', 'MediaStorageSOPClassUID'],
- '0x0003': ['UI', '1', 'MediaStorageSOPInstanceUID'],
- '0x0010': ['UI', '1', 'TransferSyntaxUID'],
- '0x0012': ['UI', '1', 'ImplementationClassUID'],
- '0x0013': ['SH', '1', 'ImplementationVersionName'],
- '0x0016': ['AE', '1', 'SourceApplicationEntityTitle'],
- '0x0017': ['AE', '1', 'SendingApplicationEntityTitle'],
- '0x0018': ['AE', '1', 'ReceivingApplicationEntityTitle'],
- '0x0100': ['UI', '1', 'PrivateInformationCreatorUID'],
- '0x0102': ['OB', '1', 'PrivateInformation']
- },
- '0x0004': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x1130': ['CS', '1', 'FileSetID'],
- '0x1141': ['CS', '1-8', 'FileSetDescriptorFileID'],
- '0x1142': ['CS', '1', 'SpecificCharacterSetOfFileSetDescriptorFile'],
- '0x1200': ['UL', '1', 'OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity'],
- '0x1202': ['UL', '1', 'OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity'],
- '0x1212': ['US', '1', 'FileSetConsistencyFlag'],
- '0x1220': ['SQ', '1', 'DirectoryRecordSequence'],
- '0x1400': ['UL', '1', 'OffsetOfTheNextDirectoryRecord'],
- '0x1410': ['US', '1', 'RecordInUseFlag'],
- '0x1420': ['UL', '1', 'OffsetOfReferencedLowerLevelDirectoryEntity'],
- '0x1430': ['CS', '1', 'DirectoryRecordType'],
- '0x1432': ['UI', '1', 'PrivateRecordUID'],
- '0x1500': ['CS', '1-8', 'ReferencedFileID'],
- '0x1504': ['UL', '1', 'MRDRDirectoryRecordOffset'],
- '0x1510': ['UI', '1', 'ReferencedSOPClassUIDInFile'],
- '0x1511': ['UI', '1', 'ReferencedSOPInstanceUIDInFile'],
- '0x1512': ['UI', '1', 'ReferencedTransferSyntaxUIDInFile'],
- '0x151A': ['UI', '1-n', 'ReferencedRelatedGeneralSOPClassUIDInFile'],
- '0x1600': ['UL', '1', 'NumberOfReferences']
- },
- '0x0008': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['UL', '1', 'LengthToEnd'],
- '0x0005': ['CS', '1-n', 'SpecificCharacterSet'],
- '0x0006': ['SQ', '1', 'LanguageCodeSequence'],
- '0x0008': ['CS', '2-n', 'ImageType'],
- '0x0010': ['SH', '1', 'RecognitionCode'],
- '0x0012': ['DA', '1', 'InstanceCreationDate'],
- '0x0013': ['TM', '1', 'InstanceCreationTime'],
- '0x0014': ['UI', '1', 'InstanceCreatorUID'],
- '0x0015': ['DT', '1', 'InstanceCoercionDateTime'],
- '0x0016': ['UI', '1', 'SOPClassUID'],
- '0x0018': ['UI', '1', 'SOPInstanceUID'],
- '0x001A': ['UI', '1-n', 'RelatedGeneralSOPClassUID'],
- '0x001B': ['UI', '1', 'OriginalSpecializedSOPClassUID'],
- '0x0020': ['DA', '1', 'StudyDate'],
- '0x0021': ['DA', '1', 'SeriesDate'],
- '0x0022': ['DA', '1', 'AcquisitionDate'],
- '0x0023': ['DA', '1', 'ContentDate'],
- '0x0024': ['DA', '1', 'OverlayDate'],
- '0x0025': ['DA', '1', 'CurveDate'],
- '0x002A': ['DT', '1', 'AcquisitionDateTime'],
- '0x0030': ['TM', '1', 'StudyTime'],
- '0x0031': ['TM', '1', 'SeriesTime'],
- '0x0032': ['TM', '1', 'AcquisitionTime'],
- '0x0033': ['TM', '1', 'ContentTime'],
- '0x0034': ['TM', '1', 'OverlayTime'],
- '0x0035': ['TM', '1', 'CurveTime'],
- '0x0040': ['US', '1', 'DataSetType'],
- '0x0041': ['LO', '1', 'DataSetSubtype'],
- '0x0042': ['CS', '1', 'NuclearMedicineSeriesType'],
- '0x0050': ['SH', '1', 'AccessionNumber'],
- '0x0051': ['SQ', '1', 'IssuerOfAccessionNumberSequence'],
- '0x0052': ['CS', '1', 'QueryRetrieveLevel'],
- '0x0053': ['CS', '1', 'QueryRetrieveView'],
- '0x0054': ['AE', '1-n', 'RetrieveAETitle'],
- '0x0056': ['CS', '1', 'InstanceAvailability'],
- '0x0058': ['UI', '1-n', 'FailedSOPInstanceUIDList'],
- '0x0060': ['CS', '1', 'Modality'],
- '0x0061': ['CS', '1-n', 'ModalitiesInStudy'],
- '0x0062': ['UI', '1-n', 'SOPClassesInStudy'],
- '0x0064': ['CS', '1', 'ConversionType'],
- '0x0068': ['CS', '1', 'PresentationIntentType'],
- '0x0070': ['LO', '1', 'Manufacturer'],
- '0x0080': ['LO', '1', 'InstitutionName'],
- '0x0081': ['ST', '1', 'InstitutionAddress'],
- '0x0082': ['SQ', '1', 'InstitutionCodeSequence'],
- '0x0090': ['PN', '1', 'ReferringPhysicianName'],
- '0x0092': ['ST', '1', 'ReferringPhysicianAddress'],
- '0x0094': ['SH', '1-n', 'ReferringPhysicianTelephoneNumbers'],
- '0x0096': ['SQ', '1', 'ReferringPhysicianIdentificationSequence'],
- '0x009C': ['PN', '1-n', 'ConsultingPhysicianName'],
- '0x009D': ['SQ', '1', 'ConsultingPhysicianIdentificationSequence'],
- '0x0100': ['SH', '1', 'CodeValue'],
- '0x0101': ['LO', '1', 'ExtendedCodeValue'],
- '0x0102': ['SH', '1', 'CodingSchemeDesignator'],
- '0x0103': ['SH', '1', 'CodingSchemeVersion'],
- '0x0104': ['LO', '1', 'CodeMeaning'],
- '0x0105': ['CS', '1', 'MappingResource'],
- '0x0106': ['DT', '1', 'ContextGroupVersion'],
- '0x0107': ['DT', '1', 'ContextGroupLocalVersion'],
- '0x0108': ['LT', '1', 'ExtendedCodeMeaning'],
- '0x010B': ['CS', '1', 'ContextGroupExtensionFlag'],
- '0x010C': ['UI', '1', 'CodingSchemeUID'],
- '0x010D': ['UI', '1', 'ContextGroupExtensionCreatorUID'],
- '0x010F': ['CS', '1', 'ContextIdentifier'],
- '0x0110': ['SQ', '1', 'CodingSchemeIdentificationSequence'],
- '0x0112': ['LO', '1', 'CodingSchemeRegistry'],
- '0x0114': ['ST', '1', 'CodingSchemeExternalID'],
- '0x0115': ['ST', '1', 'CodingSchemeName'],
- '0x0116': ['ST', '1', 'CodingSchemeResponsibleOrganization'],
- '0x0117': ['UI', '1', 'ContextUID'],
- '0x0118': ['UI', '1', 'MappingResourceUID'],
- '0x0119': ['UC', '1', 'LongCodeValue'],
- '0x0120': ['UR', '1', 'URNCodeValue'],
- '0x0121': ['SQ', '1', 'EquivalentCodeSequence'],
- '0x0201': ['SH', '1', 'TimezoneOffsetFromUTC'],
- '0x0300': ['SQ', '1', 'PrivateDataElementCharacteristicsSequence'],
- '0x0301': ['US', '1', 'PrivateGroupReference'],
- '0x0302': ['LO', '1', 'PrivateCreatorReference'],
- '0x0303': ['CS', '1', 'BlockIdentifyingInformationStatus'],
- '0x0304': ['US', '1-n', 'NonidentifyingPrivateElements'],
- '0x0306': ['US', '1-n', 'IdentifyingPrivateElements'],
- '0x0305': ['SQ', '1', 'DeidentificationActionSequence'],
- '0x0307': ['CS', '1', 'DeidentificationAction'],
- '0x1000': ['AE', '1', 'NetworkID'],
- '0x1010': ['SH', '1', 'StationName'],
- '0x1030': ['LO', '1', 'StudyDescription'],
- '0x1032': ['SQ', '1', 'ProcedureCodeSequence'],
- '0x103E': ['LO', '1', 'SeriesDescription'],
- '0x103F': ['SQ', '1', 'SeriesDescriptionCodeSequence'],
- '0x1040': ['LO', '1', 'InstitutionalDepartmentName'],
- '0x1048': ['PN', '1-n', 'PhysiciansOfRecord'],
- '0x1049': ['SQ', '1', 'PhysiciansOfRecordIdentificationSequence'],
- '0x1050': ['PN', '1-n', 'PerformingPhysicianName'],
- '0x1052': ['SQ', '1', 'PerformingPhysicianIdentificationSequence'],
- '0x1060': ['PN', '1-n', 'NameOfPhysiciansReadingStudy'],
- '0x1062': ['SQ', '1', 'PhysiciansReadingStudyIdentificationSequence'],
- '0x1070': ['PN', '1-n', 'OperatorsName'],
- '0x1072': ['SQ', '1', 'OperatorIdentificationSequence'],
- '0x1080': ['LO', '1-n', 'AdmittingDiagnosesDescription'],
- '0x1084': ['SQ', '1', 'AdmittingDiagnosesCodeSequence'],
- '0x1090': ['LO', '1', 'ManufacturerModelName'],
- '0x1100': ['SQ', '1', 'ReferencedResultsSequence'],
- '0x1110': ['SQ', '1', 'ReferencedStudySequence'],
- '0x1111': ['SQ', '1', 'ReferencedPerformedProcedureStepSequence'],
- '0x1115': ['SQ', '1', 'ReferencedSeriesSequence'],
- '0x1120': ['SQ', '1', 'ReferencedPatientSequence'],
- '0x1125': ['SQ', '1', 'ReferencedVisitSequence'],
- '0x1130': ['SQ', '1', 'ReferencedOverlaySequence'],
- '0x1134': ['SQ', '1', 'ReferencedStereometricInstanceSequence'],
- '0x113A': ['SQ', '1', 'ReferencedWaveformSequence'],
- '0x1140': ['SQ', '1', 'ReferencedImageSequence'],
- '0x1145': ['SQ', '1', 'ReferencedCurveSequence'],
- '0x114A': ['SQ', '1', 'ReferencedInstanceSequence'],
- '0x114B': ['SQ', '1', 'ReferencedRealWorldValueMappingInstanceSequence'],
- '0x1150': ['UI', '1', 'ReferencedSOPClassUID'],
- '0x1155': ['UI', '1', 'ReferencedSOPInstanceUID'],
- '0x115A': ['UI', '1-n', 'SOPClassesSupported'],
- '0x1160': ['IS', '1-n', 'ReferencedFrameNumber'],
- '0x1161': ['UL', '1-n', 'SimpleFrameList'],
- '0x1162': ['UL', '3-3n', 'CalculatedFrameList'],
- '0x1163': ['FD', '2', 'TimeRange'],
- '0x1164': ['SQ', '1', 'FrameExtractionSequence'],
- '0x1167': ['UI', '1', 'MultiFrameSourceSOPInstanceUID'],
- '0x1190': ['UR', '1', 'RetrieveURL'],
- '0x1195': ['UI', '1', 'TransactionUID'],
- '0x1196': ['US', '1', 'WarningReason'],
- '0x1197': ['US', '1', 'FailureReason'],
- '0x1198': ['SQ', '1', 'FailedSOPSequence'],
- '0x1199': ['SQ', '1', 'ReferencedSOPSequence'],
- '0x1200': ['SQ', '1', 'StudiesContainingOtherReferencedInstancesSequence'],
- '0x1250': ['SQ', '1', 'RelatedSeriesSequence'],
- '0x2110': ['CS', '1', 'LossyImageCompressionRetired'],
- '0x2111': ['ST', '1', 'DerivationDescription'],
- '0x2112': ['SQ', '1', 'SourceImageSequence'],
- '0x2120': ['SH', '1', 'StageName'],
- '0x2122': ['IS', '1', 'StageNumber'],
- '0x2124': ['IS', '1', 'NumberOfStages'],
- '0x2127': ['SH', '1', 'ViewName'],
- '0x2128': ['IS', '1', 'ViewNumber'],
- '0x2129': ['IS', '1', 'NumberOfEventTimers'],
- '0x212A': ['IS', '1', 'NumberOfViewsInStage'],
- '0x2130': ['DS', '1-n', 'EventElapsedTimes'],
- '0x2132': ['LO', '1-n', 'EventTimerNames'],
- '0x2133': ['SQ', '1', 'EventTimerSequence'],
- '0x2134': ['FD', '1', 'EventTimeOffset'],
- '0x2135': ['SQ', '1', 'EventCodeSequence'],
- '0x2142': ['IS', '1', 'StartTrim'],
- '0x2143': ['IS', '1', 'StopTrim'],
- '0x2144': ['IS', '1', 'RecommendedDisplayFrameRate'],
- '0x2200': ['CS', '1', 'TransducerPosition'],
- '0x2204': ['CS', '1', 'TransducerOrientation'],
- '0x2208': ['CS', '1', 'AnatomicStructure'],
- '0x2218': ['SQ', '1', 'AnatomicRegionSequence'],
- '0x2220': ['SQ', '1', 'AnatomicRegionModifierSequence'],
- '0x2228': ['SQ', '1', 'PrimaryAnatomicStructureSequence'],
- '0x2229': ['SQ', '1', 'AnatomicStructureSpaceOrRegionSequence'],
- '0x2230': ['SQ', '1', 'PrimaryAnatomicStructureModifierSequence'],
- '0x2240': ['SQ', '1', 'TransducerPositionSequence'],
- '0x2242': ['SQ', '1', 'TransducerPositionModifierSequence'],
- '0x2244': ['SQ', '1', 'TransducerOrientationSequence'],
- '0x2246': ['SQ', '1', 'TransducerOrientationModifierSequence'],
- '0x2251': ['SQ', '1', 'AnatomicStructureSpaceOrRegionCodeSequenceTrial'],
- '0x2253': ['SQ', '1', 'AnatomicPortalOfEntranceCodeSequenceTrial'],
- '0x2255': ['SQ', '1', 'AnatomicApproachDirectionCodeSequenceTrial'],
- '0x2256': ['ST', '1', 'AnatomicPerspectiveDescriptionTrial'],
- '0x2257': ['SQ', '1', 'AnatomicPerspectiveCodeSequenceTrial'],
- '0x2258': ['ST', '1', 'AnatomicLocationOfExaminingInstrumentDescriptionTrial'],
- '0x2259': ['SQ', '1', 'AnatomicLocationOfExaminingInstrumentCodeSequenceTrial'],
- '0x225A': ['SQ', '1', 'AnatomicStructureSpaceOrRegionModifierCodeSequenceTrial'],
- '0x225C': ['SQ', '1', 'OnAxisBackgroundAnatomicStructureCodeSequenceTrial'],
- '0x3001': ['SQ', '1', 'AlternateRepresentationSequence'],
- '0x3010': ['UI', '1-n', 'IrradiationEventUID'],
- '0x3011': ['SQ', '1', 'SourceIrradiationEventSequence'],
- '0x3012': ['UI', '1', 'RadiopharmaceuticalAdministrationEventUID'],
- '0x4000': ['LT', '1', 'IdentifyingComments'],
- '0x9007': ['CS', '4', 'FrameType'],
- '0x9092': ['SQ', '1', 'ReferencedImageEvidenceSequence'],
- '0x9121': ['SQ', '1', 'ReferencedRawDataSequence'],
- '0x9123': ['UI', '1', 'CreatorVersionUID'],
- '0x9124': ['SQ', '1', 'DerivationImageSequence'],
- '0x9154': ['SQ', '1', 'SourceImageEvidenceSequence'],
- '0x9205': ['CS', '1', 'PixelPresentation'],
- '0x9206': ['CS', '1', 'VolumetricProperties'],
- '0x9207': ['CS', '1', 'VolumeBasedCalculationTechnique'],
- '0x9208': ['CS', '1', 'ComplexImageComponent'],
- '0x9209': ['CS', '1', 'AcquisitionContrast'],
- '0x9215': ['SQ', '1', 'DerivationCodeSequence'],
- '0x9237': ['SQ', '1', 'ReferencedPresentationStateSequence'],
- '0x9410': ['SQ', '1', 'ReferencedOtherPlaneSequence'],
- '0x9458': ['SQ', '1', 'FrameDisplaySequence'],
- '0x9459': ['FL', '1', 'RecommendedDisplayFrameRateInFloat'],
- '0x9460': ['CS', '1', 'SkipFrameRangeFlag']
- },
- '0x0010': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['PN', '1', 'PatientName'],
- '0x0020': ['LO', '1', 'PatientID'],
- '0x0021': ['LO', '1', 'IssuerOfPatientID'],
- '0x0022': ['CS', '1', 'TypeOfPatientID'],
- '0x0024': ['SQ', '1', 'IssuerOfPatientIDQualifiersSequence'],
- '0x0030': ['DA', '1', 'PatientBirthDate'],
- '0x0032': ['TM', '1', 'PatientBirthTime'],
- '0x0040': ['CS', '1', 'PatientSex'],
- '0x0050': ['SQ', '1', 'PatientInsurancePlanCodeSequence'],
- '0x0101': ['SQ', '1', 'PatientPrimaryLanguageCodeSequence'],
- '0x0102': ['SQ', '1', 'PatientPrimaryLanguageModifierCodeSequence'],
- '0x0200': ['CS', '1', 'QualityControlSubject'],
- '0x0201': ['SQ', '1', 'QualityControlSubjectTypeCodeSequence'],
- '0x1000': ['LO', '1-n', 'OtherPatientIDs'],
- '0x1001': ['PN', '1-n', 'OtherPatientNames'],
- '0x1002': ['SQ', '1', 'OtherPatientIDsSequence'],
- '0x1005': ['PN', '1', 'PatientBirthName'],
- '0x1010': ['AS', '1', 'PatientAge'],
- '0x1020': ['DS', '1', 'PatientSize'],
- '0x1021': ['SQ', '1', 'PatientSizeCodeSequence'],
- '0x1030': ['DS', '1', 'PatientWeight'],
- '0x1040': ['LO', '1', 'PatientAddress'],
- '0x1050': ['LO', '1-n', 'InsurancePlanIdentification'],
- '0x1060': ['PN', '1', 'PatientMotherBirthName'],
- '0x1080': ['LO', '1', 'MilitaryRank'],
- '0x1081': ['LO', '1', 'BranchOfService'],
- '0x1090': ['LO', '1', 'MedicalRecordLocator'],
- '0x1100': ['SQ', '1', 'ReferencedPatientPhotoSequence'],
- '0x2000': ['LO', '1-n', 'MedicalAlerts'],
- '0x2110': ['LO', '1-n', 'Allergies'],
- '0x2150': ['LO', '1', 'CountryOfResidence'],
- '0x2152': ['LO', '1', 'RegionOfResidence'],
- '0x2154': ['SH', '1-n', 'PatientTelephoneNumbers'],
- '0x2155': ['LT', '1', 'PatientTelecomInformation'],
- '0x2160': ['SH', '1', 'EthnicGroup'],
- '0x2180': ['SH', '1', 'Occupation'],
- '0x21A0': ['CS', '1', 'SmokingStatus'],
- '0x21B0': ['LT', '1', 'AdditionalPatientHistory'],
- '0x21C0': ['US', '1', 'PregnancyStatus'],
- '0x21D0': ['DA', '1', 'LastMenstrualDate'],
- '0x21F0': ['LO', '1', 'PatientReligiousPreference'],
- '0x2201': ['LO', '1', 'PatientSpeciesDescription'],
- '0x2202': ['SQ', '1', 'PatientSpeciesCodeSequence'],
- '0x2203': ['CS', '1', 'PatientSexNeutered'],
- '0x2210': ['CS', '1', 'AnatomicalOrientationType'],
- '0x2292': ['LO', '1', 'PatientBreedDescription'],
- '0x2293': ['SQ', '1', 'PatientBreedCodeSequence'],
- '0x2294': ['SQ', '1', 'BreedRegistrationSequence'],
- '0x2295': ['LO', '1', 'BreedRegistrationNumber'],
- '0x2296': ['SQ', '1', 'BreedRegistryCodeSequence'],
- '0x2297': ['PN', '1', 'ResponsiblePerson'],
- '0x2298': ['CS', '1', 'ResponsiblePersonRole'],
- '0x2299': ['LO', '1', 'ResponsibleOrganization'],
- '0x4000': ['LT', '1', 'PatientComments'],
- '0x9431': ['FL', '1', 'ExaminedBodyThickness']
- },
- '0x0012': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['LO', '1', 'ClinicalTrialSponsorName'],
- '0x0020': ['LO', '1', 'ClinicalTrialProtocolID'],
- '0x0021': ['LO', '1', 'ClinicalTrialProtocolName'],
- '0x0030': ['LO', '1', 'ClinicalTrialSiteID'],
- '0x0031': ['LO', '1', 'ClinicalTrialSiteName'],
- '0x0040': ['LO', '1', 'ClinicalTrialSubjectID'],
- '0x0042': ['LO', '1', 'ClinicalTrialSubjectReadingID'],
- '0x0050': ['LO', '1', 'ClinicalTrialTimePointID'],
- '0x0051': ['ST', '1', 'ClinicalTrialTimePointDescription'],
- '0x0060': ['LO', '1', 'ClinicalTrialCoordinatingCenterName'],
- '0x0062': ['CS', '1', 'PatientIdentityRemoved'],
- '0x0063': ['LO', '1-n', 'DeidentificationMethod'],
- '0x0064': ['SQ', '1', 'DeidentificationMethodCodeSequence'],
- '0x0071': ['LO', '1', 'ClinicalTrialSeriesID'],
- '0x0072': ['LO', '1', 'ClinicalTrialSeriesDescription'],
- '0x0081': ['LO', '1', 'ClinicalTrialProtocolEthicsCommitteeName'],
- '0x0082': ['LO', '1', 'ClinicalTrialProtocolEthicsCommitteeApprovalNumber'],
- '0x0083': ['SQ', '1', 'ConsentForClinicalTrialUseSequence'],
- '0x0084': ['CS', '1', 'DistributionType'],
- '0x0085': ['CS', '1', 'ConsentForDistributionFlag']
- },
- '0x0014': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0023': ['ST', '1-n', 'CADFileFormat'],
- '0x0024': ['ST', '1-n', 'ComponentReferenceSystem'],
- '0x0025': ['ST', '1-n', 'ComponentManufacturingProcedure'],
- '0x0028': ['ST', '1-n', 'ComponentManufacturer'],
- '0x0030': ['DS', '1-n', 'MaterialThickness'],
- '0x0032': ['DS', '1-n', 'MaterialPipeDiameter'],
- '0x0034': ['DS', '1-n', 'MaterialIsolationDiameter'],
- '0x0042': ['ST', '1-n', 'MaterialGrade'],
- '0x0044': ['ST', '1-n', 'MaterialPropertiesDescription'],
- '0x0045': ['ST', '1-n', 'MaterialPropertiesFileFormatRetired'],
- '0x0046': ['LT', '1', 'MaterialNotes'],
- '0x0050': ['CS', '1', 'ComponentShape'],
- '0x0052': ['CS', '1', 'CurvatureType'],
- '0x0054': ['DS', '1', 'OuterDiameter'],
- '0x0056': ['DS', '1', 'InnerDiameter'],
- '0x1010': ['ST', '1', 'ActualEnvironmentalConditions'],
- '0x1020': ['DA', '1', 'ExpiryDate'],
- '0x1040': ['ST', '1', 'EnvironmentalConditions'],
- '0x2002': ['SQ', '1', 'EvaluatorSequence'],
- '0x2004': ['IS', '1', 'EvaluatorNumber'],
- '0x2006': ['PN', '1', 'EvaluatorName'],
- '0x2008': ['IS', '1', 'EvaluationAttempt'],
- '0x2012': ['SQ', '1', 'IndicationSequence'],
- '0x2014': ['IS', '1', 'IndicationNumber'],
- '0x2016': ['SH', '1', 'IndicationLabel'],
- '0x2018': ['ST', '1', 'IndicationDescription'],
- '0x201A': ['CS', '1-n', 'IndicationType'],
- '0x201C': ['CS', '1', 'IndicationDisposition'],
- '0x201E': ['SQ', '1', 'IndicationROISequence'],
- '0x2030': ['SQ', '1', 'IndicationPhysicalPropertySequence'],
- '0x2032': ['SH', '1', 'PropertyLabel'],
- '0x2202': ['IS', '1', 'CoordinateSystemNumberOfAxes'],
- '0x2204': ['SQ', '1', 'CoordinateSystemAxesSequence'],
- '0x2206': ['ST', '1', 'CoordinateSystemAxisDescription'],
- '0x2208': ['CS', '1', 'CoordinateSystemDataSetMapping'],
- '0x220A': ['IS', '1', 'CoordinateSystemAxisNumber'],
- '0x220C': ['CS', '1', 'CoordinateSystemAxisType'],
- '0x220E': ['CS', '1', 'CoordinateSystemAxisUnits'],
- '0x2210': ['OB', '1', 'CoordinateSystemAxisValues'],
- '0x2220': ['SQ', '1', 'CoordinateSystemTransformSequence'],
- '0x2222': ['ST', '1', 'TransformDescription'],
- '0x2224': ['IS', '1', 'TransformNumberOfAxes'],
- '0x2226': ['IS', '1-n', 'TransformOrderOfAxes'],
- '0x2228': ['CS', '1', 'TransformedAxisUnits'],
- '0x222A': ['DS', '1-n', 'CoordinateSystemTransformRotationAndScaleMatrix'],
- '0x222C': ['DS', '1-n', 'CoordinateSystemTransformTranslationMatrix'],
- '0x3011': ['DS', '1', 'InternalDetectorFrameTime'],
- '0x3012': ['DS', '1', 'NumberOfFramesIntegrated'],
- '0x3020': ['SQ', '1', 'DetectorTemperatureSequence'],
- '0x3022': ['ST', '1', 'SensorName'],
- '0x3024': ['DS', '1', 'HorizontalOffsetOfSensor'],
- '0x3026': ['DS', '1', 'VerticalOffsetOfSensor'],
- '0x3028': ['DS', '1', 'SensorTemperature'],
- '0x3040': ['SQ', '1', 'DarkCurrentSequence'],
- '0x3050': ['ox', '1', 'DarkCurrentCounts'],
- '0x3060': ['SQ', '1', 'GainCorrectionReferenceSequence'],
- '0x3070': ['ox', '1', 'AirCounts'],
- '0x3071': ['DS', '1', 'KVUsedInGainCalibration'],
- '0x3072': ['DS', '1', 'MAUsedInGainCalibration'],
- '0x3073': ['DS', '1', 'NumberOfFramesUsedForIntegration'],
- '0x3074': ['LO', '1', 'FilterMaterialUsedInGainCalibration'],
- '0x3075': ['DS', '1', 'FilterThicknessUsedInGainCalibration'],
- '0x3076': ['DA', '1', 'DateOfGainCalibration'],
- '0x3077': ['TM', '1', 'TimeOfGainCalibration'],
- '0x3080': ['OB', '1', 'BadPixelImage'],
- '0x3099': ['LT', '1', 'CalibrationNotes'],
- '0x4002': ['SQ', '1', 'PulserEquipmentSequence'],
- '0x4004': ['CS', '1', 'PulserType'],
- '0x4006': ['LT', '1', 'PulserNotes'],
- '0x4008': ['SQ', '1', 'ReceiverEquipmentSequence'],
- '0x400A': ['CS', '1', 'AmplifierType'],
- '0x400C': ['LT', '1', 'ReceiverNotes'],
- '0x400E': ['SQ', '1', 'PreAmplifierEquipmentSequence'],
- '0x400F': ['LT', '1', 'PreAmplifierNotes'],
- '0x4010': ['SQ', '1', 'TransmitTransducerSequence'],
- '0x4011': ['SQ', '1', 'ReceiveTransducerSequence'],
- '0x4012': ['US', '1', 'NumberOfElements'],
- '0x4013': ['CS', '1', 'ElementShape'],
- '0x4014': ['DS', '1', 'ElementDimensionA'],
- '0x4015': ['DS', '1', 'ElementDimensionB'],
- '0x4016': ['DS', '1', 'ElementPitchA'],
- '0x4017': ['DS', '1', 'MeasuredBeamDimensionA'],
- '0x4018': ['DS', '1', 'MeasuredBeamDimensionB'],
- '0x4019': ['DS', '1', 'LocationOfMeasuredBeamDiameter'],
- '0x401A': ['DS', '1', 'NominalFrequency'],
- '0x401B': ['DS', '1', 'MeasuredCenterFrequency'],
- '0x401C': ['DS', '1', 'MeasuredBandwidth'],
- '0x401D': ['DS', '1', 'ElementPitchB'],
- '0x4020': ['SQ', '1', 'PulserSettingsSequence'],
- '0x4022': ['DS', '1', 'PulseWidth'],
- '0x4024': ['DS', '1', 'ExcitationFrequency'],
- '0x4026': ['CS', '1', 'ModulationType'],
- '0x4028': ['DS', '1', 'Damping'],
- '0x4030': ['SQ', '1', 'ReceiverSettingsSequence'],
- '0x4031': ['DS', '1', 'AcquiredSoundpathLength'],
- '0x4032': ['CS', '1', 'AcquisitionCompressionType'],
- '0x4033': ['IS', '1', 'AcquisitionSampleSize'],
- '0x4034': ['DS', '1', 'RectifierSmoothing'],
- '0x4035': ['SQ', '1', 'DACSequence'],
- '0x4036': ['CS', '1', 'DACType'],
- '0x4038': ['DS', '1-n', 'DACGainPoints'],
- '0x403A': ['DS', '1-n', 'DACTimePoints'],
- '0x403C': ['DS', '1-n', 'DACAmplitude'],
- '0x4040': ['SQ', '1', 'PreAmplifierSettingsSequence'],
- '0x4050': ['SQ', '1', 'TransmitTransducerSettingsSequence'],
- '0x4051': ['SQ', '1', 'ReceiveTransducerSettingsSequence'],
- '0x4052': ['DS', '1', 'IncidentAngle'],
- '0x4054': ['ST', '1', 'CouplingTechnique'],
- '0x4056': ['ST', '1', 'CouplingMedium'],
- '0x4057': ['DS', '1', 'CouplingVelocity'],
- '0x4058': ['DS', '1', 'ProbeCenterLocationX'],
- '0x4059': ['DS', '1', 'ProbeCenterLocationZ'],
- '0x405A': ['DS', '1', 'SoundPathLength'],
- '0x405C': ['ST', '1', 'DelayLawIdentifier'],
- '0x4060': ['SQ', '1', 'GateSettingsSequence'],
- '0x4062': ['DS', '1', 'GateThreshold'],
- '0x4064': ['DS', '1', 'VelocityOfSound'],
- '0x4070': ['SQ', '1', 'CalibrationSettingsSequence'],
- '0x4072': ['ST', '1', 'CalibrationProcedure'],
- '0x4074': ['SH', '1', 'ProcedureVersion'],
- '0x4076': ['DA', '1', 'ProcedureCreationDate'],
- '0x4078': ['DA', '1', 'ProcedureExpirationDate'],
- '0x407A': ['DA', '1', 'ProcedureLastModifiedDate'],
- '0x407C': ['TM', '1-n', 'CalibrationTime'],
- '0x407E': ['DA', '1-n', 'CalibrationDate'],
- '0x4080': ['SQ', '1', 'ProbeDriveEquipmentSequence'],
- '0x4081': ['CS', '1', 'DriveType'],
- '0x4082': ['LT', '1', 'ProbeDriveNotes'],
- '0x4083': ['SQ', '1', 'DriveProbeSequence'],
- '0x4084': ['DS', '1', 'ProbeInductance'],
- '0x4085': ['DS', '1', 'ProbeResistance'],
- '0x4086': ['SQ', '1', 'ReceiveProbeSequence'],
- '0x4087': ['SQ', '1', 'ProbeDriveSettingsSequence'],
- '0x4088': ['DS', '1', 'BridgeResistors'],
- '0x4089': ['DS', '1', 'ProbeOrientationAngle'],
- '0x408B': ['DS', '1', 'UserSelectedGainY'],
- '0x408C': ['DS', '1', 'UserSelectedPhase'],
- '0x408D': ['DS', '1', 'UserSelectedOffsetX'],
- '0x408E': ['DS', '1', 'UserSelectedOffsetY'],
- '0x4091': ['SQ', '1', 'ChannelSettingsSequence'],
- '0x4092': ['DS', '1', 'ChannelThreshold'],
- '0x409A': ['SQ', '1', 'ScannerSettingsSequence'],
- '0x409B': ['ST', '1', 'ScanProcedure'],
- '0x409C': ['DS', '1', 'TranslationRateX'],
- '0x409D': ['DS', '1', 'TranslationRateY'],
- '0x409F': ['DS', '1', 'ChannelOverlap'],
- '0x40A0': ['LO', '1', 'ImageQualityIndicatorType'],
- '0x40A1': ['LO', '1', 'ImageQualityIndicatorMaterial'],
- '0x40A2': ['LO', '1', 'ImageQualityIndicatorSize'],
- '0x5002': ['IS', '1', 'LINACEnergy'],
- '0x5004': ['IS', '1', 'LINACOutput'],
- '0x5100': ['US', '1', 'ActiveAperture'],
- '0x5101': ['DS', '1', 'TotalAperture'],
- '0x5102': ['DS', '1', 'ApertureElevation'],
- '0x5103': ['DS', '1', 'MainLobeAngle'],
- '0x5104': ['DS', '1', 'MainRoofAngle'],
- '0x5105': ['CS', '1', 'ConnectorType'],
- '0x5106': ['SH', '1', 'WedgeModelNumber'],
- '0x5107': ['DS', '1', 'WedgeAngleFloat'],
- '0x5108': ['DS', '1', 'WedgeRoofAngle'],
- '0x5109': ['CS', '1', 'WedgeElement1Position'],
- '0x510A': ['DS', '1', 'WedgeMaterialVelocity'],
- '0x510B': ['SH', '1', 'WedgeMaterial'],
- '0x510C': ['DS', '1', 'WedgeOffsetZ'],
- '0x510D': ['DS', '1', 'WedgeOriginOffsetX'],
- '0x510E': ['DS', '1', 'WedgeTimeDelay'],
- '0x510F': ['SH', '1', 'WedgeName'],
- '0x5110': ['SH', '1', 'WedgeManufacturerName'],
- '0x5111': ['LO', '1', 'WedgeDescription'],
- '0x5112': ['DS', '1', 'NominalBeamAngle'],
- '0x5113': ['DS', '1', 'WedgeOffsetX'],
- '0x5114': ['DS', '1', 'WedgeOffsetY'],
- '0x5115': ['DS', '1', 'WedgeTotalLength'],
- '0x5116': ['DS', '1', 'WedgeInContactLength'],
- '0x5117': ['DS', '1', 'WedgeFrontGap'],
- '0x5118': ['DS', '1', 'WedgeTotalHeight'],
- '0x5119': ['DS', '1', 'WedgeFrontHeight'],
- '0x511A': ['DS', '1', 'WedgeRearHeight'],
- '0x511B': ['DS', '1', 'WedgeTotalWidth'],
- '0x511C': ['DS', '1', 'WedgeInContactWidth'],
- '0x511D': ['DS', '1', 'WedgeChamferHeight'],
- '0x511E': ['CS', '1', 'WedgeCurve'],
- '0x511F': ['DS', '1', 'RadiusAlongWedge']
- },
- '0x0018': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['LO', '1', 'ContrastBolusAgent'],
- '0x0012': ['SQ', '1', 'ContrastBolusAgentSequence'],
- '0x0013': ['FL', '1', 'ContrastBolusT1Relaxivity'],
- '0x0014': ['SQ', '1', 'ContrastBolusAdministrationRouteSequence'],
- '0x0015': ['CS', '1', 'BodyPartExamined'],
- '0x0020': ['CS', '1-n', 'ScanningSequence'],
- '0x0021': ['CS', '1-n', 'SequenceVariant'],
- '0x0022': ['CS', '1-n', 'ScanOptions'],
- '0x0023': ['CS', '1', 'MRAcquisitionType'],
- '0x0024': ['SH', '1', 'SequenceName'],
- '0x0025': ['CS', '1', 'AngioFlag'],
- '0x0026': ['SQ', '1', 'InterventionDrugInformationSequence'],
- '0x0027': ['TM', '1', 'InterventionDrugStopTime'],
- '0x0028': ['DS', '1', 'InterventionDrugDose'],
- '0x0029': ['SQ', '1', 'InterventionDrugCodeSequence'],
- '0x002A': ['SQ', '1', 'AdditionalDrugSequence'],
- '0x0030': ['LO', '1-n', 'Radionuclide'],
- '0x0031': ['LO', '1', 'Radiopharmaceutical'],
- '0x0032': ['DS', '1', 'EnergyWindowCenterline'],
- '0x0033': ['DS', '1-n', 'EnergyWindowTotalWidth'],
- '0x0034': ['LO', '1', 'InterventionDrugName'],
- '0x0035': ['TM', '1', 'InterventionDrugStartTime'],
- '0x0036': ['SQ', '1', 'InterventionSequence'],
- '0x0037': ['CS', '1', 'TherapyType'],
- '0x0038': ['CS', '1', 'InterventionStatus'],
- '0x0039': ['CS', '1', 'TherapyDescription'],
- '0x003A': ['ST', '1', 'InterventionDescription'],
- '0x0040': ['IS', '1', 'CineRate'],
- '0x0042': ['CS', '1', 'InitialCineRunState'],
- '0x0050': ['DS', '1', 'SliceThickness'],
- '0x0060': ['DS', '1', 'KVP'],
- '0x0070': ['IS', '1', 'CountsAccumulated'],
- '0x0071': ['CS', '1', 'AcquisitionTerminationCondition'],
- '0x0072': ['DS', '1', 'EffectiveDuration'],
- '0x0073': ['CS', '1', 'AcquisitionStartCondition'],
- '0x0074': ['IS', '1', 'AcquisitionStartConditionData'],
- '0x0075': ['IS', '1', 'AcquisitionTerminationConditionData'],
- '0x0080': ['DS', '1', 'RepetitionTime'],
- '0x0081': ['DS', '1', 'EchoTime'],
- '0x0082': ['DS', '1', 'InversionTime'],
- '0x0083': ['DS', '1', 'NumberOfAverages'],
- '0x0084': ['DS', '1', 'ImagingFrequency'],
- '0x0085': ['SH', '1', 'ImagedNucleus'],
- '0x0086': ['IS', '1-n', 'EchoNumbers'],
- '0x0087': ['DS', '1', 'MagneticFieldStrength'],
- '0x0088': ['DS', '1', 'SpacingBetweenSlices'],
- '0x0089': ['IS', '1', 'NumberOfPhaseEncodingSteps'],
- '0x0090': ['DS', '1', 'DataCollectionDiameter'],
- '0x0091': ['IS', '1', 'EchoTrainLength'],
- '0x0093': ['DS', '1', 'PercentSampling'],
- '0x0094': ['DS', '1', 'PercentPhaseFieldOfView'],
- '0x0095': ['DS', '1', 'PixelBandwidth'],
- '0x1000': ['LO', '1', 'DeviceSerialNumber'],
- '0x1002': ['UI', '1', 'DeviceUID'],
- '0x1003': ['LO', '1', 'DeviceID'],
- '0x1004': ['LO', '1', 'PlateID'],
- '0x1005': ['LO', '1', 'GeneratorID'],
- '0x1006': ['LO', '1', 'GridID'],
- '0x1007': ['LO', '1', 'CassetteID'],
- '0x1008': ['LO', '1', 'GantryID'],
- '0x1010': ['LO', '1', 'SecondaryCaptureDeviceID'],
- '0x1011': ['LO', '1', 'HardcopyCreationDeviceID'],
- '0x1012': ['DA', '1', 'DateOfSecondaryCapture'],
- '0x1014': ['TM', '1', 'TimeOfSecondaryCapture'],
- '0x1016': ['LO', '1', 'SecondaryCaptureDeviceManufacturer'],
- '0x1017': ['LO', '1', 'HardcopyDeviceManufacturer'],
- '0x1018': ['LO', '1', 'SecondaryCaptureDeviceManufacturerModelName'],
- '0x1019': ['LO', '1-n', 'SecondaryCaptureDeviceSoftwareVersions'],
- '0x101A': ['LO', '1-n', 'HardcopyDeviceSoftwareVersion'],
- '0x101B': ['LO', '1', 'HardcopyDeviceManufacturerModelName'],
- '0x1020': ['LO', '1-n', 'SoftwareVersions'],
- '0x1022': ['SH', '1', 'VideoImageFormatAcquired'],
- '0x1023': ['LO', '1', 'DigitalImageFormatAcquired'],
- '0x1030': ['LO', '1', 'ProtocolName'],
- '0x1040': ['LO', '1', 'ContrastBolusRoute'],
- '0x1041': ['DS', '1', 'ContrastBolusVolume'],
- '0x1042': ['TM', '1', 'ContrastBolusStartTime'],
- '0x1043': ['TM', '1', 'ContrastBolusStopTime'],
- '0x1044': ['DS', '1', 'ContrastBolusTotalDose'],
- '0x1045': ['IS', '1', 'SyringeCounts'],
- '0x1046': ['DS', '1-n', 'ContrastFlowRate'],
- '0x1047': ['DS', '1-n', 'ContrastFlowDuration'],
- '0x1048': ['CS', '1', 'ContrastBolusIngredient'],
- '0x1049': ['DS', '1', 'ContrastBolusIngredientConcentration'],
- '0x1050': ['DS', '1', 'SpatialResolution'],
- '0x1060': ['DS', '1', 'TriggerTime'],
- '0x1061': ['LO', '1', 'TriggerSourceOrType'],
- '0x1062': ['IS', '1', 'NominalInterval'],
- '0x1063': ['DS', '1', 'FrameTime'],
- '0x1064': ['LO', '1', 'CardiacFramingType'],
- '0x1065': ['DS', '1-n', 'FrameTimeVector'],
- '0x1066': ['DS', '1', 'FrameDelay'],
- '0x1067': ['DS', '1', 'ImageTriggerDelay'],
- '0x1068': ['DS', '1', 'MultiplexGroupTimeOffset'],
- '0x1069': ['DS', '1', 'TriggerTimeOffset'],
- '0x106A': ['CS', '1', 'SynchronizationTrigger'],
- '0x106C': ['US', '2', 'SynchronizationChannel'],
- '0x106E': ['UL', '1', 'TriggerSamplePosition'],
- '0x1070': ['LO', '1', 'RadiopharmaceuticalRoute'],
- '0x1071': ['DS', '1', 'RadiopharmaceuticalVolume'],
- '0x1072': ['TM', '1', 'RadiopharmaceuticalStartTime'],
- '0x1073': ['TM', '1', 'RadiopharmaceuticalStopTime'],
- '0x1074': ['DS', '1', 'RadionuclideTotalDose'],
- '0x1075': ['DS', '1', 'RadionuclideHalfLife'],
- '0x1076': ['DS', '1', 'RadionuclidePositronFraction'],
- '0x1077': ['DS', '1', 'RadiopharmaceuticalSpecificActivity'],
- '0x1078': ['DT', '1', 'RadiopharmaceuticalStartDateTime'],
- '0x1079': ['DT', '1', 'RadiopharmaceuticalStopDateTime'],
- '0x1080': ['CS', '1', 'BeatRejectionFlag'],
- '0x1081': ['IS', '1', 'LowRRValue'],
- '0x1082': ['IS', '1', 'HighRRValue'],
- '0x1083': ['IS', '1', 'IntervalsAcquired'],
- '0x1084': ['IS', '1', 'IntervalsRejected'],
- '0x1085': ['LO', '1', 'PVCRejection'],
- '0x1086': ['IS', '1', 'SkipBeats'],
- '0x1088': ['IS', '1', 'HeartRate'],
- '0x1090': ['IS', '1', 'CardiacNumberOfImages'],
- '0x1094': ['IS', '1', 'TriggerWindow'],
- '0x1100': ['DS', '1', 'ReconstructionDiameter'],
- '0x1110': ['DS', '1', 'DistanceSourceToDetector'],
- '0x1111': ['DS', '1', 'DistanceSourceToPatient'],
- '0x1114': ['DS', '1', 'EstimatedRadiographicMagnificationFactor'],
- '0x1120': ['DS', '1', 'GantryDetectorTilt'],
- '0x1121': ['DS', '1', 'GantryDetectorSlew'],
- '0x1130': ['DS', '1', 'TableHeight'],
- '0x1131': ['DS', '1', 'TableTraverse'],
- '0x1134': ['CS', '1', 'TableMotion'],
- '0x1135': ['DS', '1-n', 'TableVerticalIncrement'],
- '0x1136': ['DS', '1-n', 'TableLateralIncrement'],
- '0x1137': ['DS', '1-n', 'TableLongitudinalIncrement'],
- '0x1138': ['DS', '1', 'TableAngle'],
- '0x113A': ['CS', '1', 'TableType'],
- '0x1140': ['CS', '1', 'RotationDirection'],
- '0x1141': ['DS', '1', 'AngularPosition'],
- '0x1142': ['DS', '1-n', 'RadialPosition'],
- '0x1143': ['DS', '1', 'ScanArc'],
- '0x1144': ['DS', '1', 'AngularStep'],
- '0x1145': ['DS', '1', 'CenterOfRotationOffset'],
- '0x1146': ['DS', '1-n', 'RotationOffset'],
- '0x1147': ['CS', '1', 'FieldOfViewShape'],
- '0x1149': ['IS', '1-2', 'FieldOfViewDimensions'],
- '0x1150': ['IS', '1', 'ExposureTime'],
- '0x1151': ['IS', '1', 'XRayTubeCurrent'],
- '0x1152': ['IS', '1', 'Exposure'],
- '0x1153': ['IS', '1', 'ExposureInuAs'],
- '0x1154': ['DS', '1', 'AveragePulseWidth'],
- '0x1155': ['CS', '1', 'RadiationSetting'],
- '0x1156': ['CS', '1', 'RectificationType'],
- '0x115A': ['CS', '1', 'RadiationMode'],
- '0x115E': ['DS', '1', 'ImageAndFluoroscopyAreaDoseProduct'],
- '0x1160': ['SH', '1', 'FilterType'],
- '0x1161': ['LO', '1-n', 'TypeOfFilters'],
- '0x1162': ['DS', '1', 'IntensifierSize'],
- '0x1164': ['DS', '2', 'ImagerPixelSpacing'],
- '0x1166': ['CS', '1-n', 'Grid'],
- '0x1170': ['IS', '1', 'GeneratorPower'],
- '0x1180': ['SH', '1', 'CollimatorGridName'],
- '0x1181': ['CS', '1', 'CollimatorType'],
- '0x1182': ['IS', '1-2', 'FocalDistance'],
- '0x1183': ['DS', '1-2', 'XFocusCenter'],
- '0x1184': ['DS', '1-2', 'YFocusCenter'],
- '0x1190': ['DS', '1-n', 'FocalSpots'],
- '0x1191': ['CS', '1', 'AnodeTargetMaterial'],
- '0x11A0': ['DS', '1', 'BodyPartThickness'],
- '0x11A2': ['DS', '1', 'CompressionForce'],
- '0x11A4': ['LO', '1', 'PaddleDescription'],
- '0x1200': ['DA', '1-n', 'DateOfLastCalibration'],
- '0x1201': ['TM', '1-n', 'TimeOfLastCalibration'],
- '0x1202': ['DT', '1', 'DateTimeOfLastCalibration'],
- '0x1210': ['SH', '1-n', 'ConvolutionKernel'],
- '0x1240': ['IS', '1-n', 'UpperLowerPixelValues'],
- '0x1242': ['IS', '1', 'ActualFrameDuration'],
- '0x1243': ['IS', '1', 'CountRate'],
- '0x1244': ['US', '1', 'PreferredPlaybackSequencing'],
- '0x1250': ['SH', '1', 'ReceiveCoilName'],
- '0x1251': ['SH', '1', 'TransmitCoilName'],
- '0x1260': ['SH', '1', 'PlateType'],
- '0x1261': ['LO', '1', 'PhosphorType'],
- '0x1300': ['DS', '1', 'ScanVelocity'],
- '0x1301': ['CS', '1-n', 'WholeBodyTechnique'],
- '0x1302': ['IS', '1', 'ScanLength'],
- '0x1310': ['US', '4', 'AcquisitionMatrix'],
- '0x1312': ['CS', '1', 'InPlanePhaseEncodingDirection'],
- '0x1314': ['DS', '1', 'FlipAngle'],
- '0x1315': ['CS', '1', 'VariableFlipAngleFlag'],
- '0x1316': ['DS', '1', 'SAR'],
- '0x1318': ['DS', '1', 'dBdt'],
- '0x1400': ['LO', '1', 'AcquisitionDeviceProcessingDescription'],
- '0x1401': ['LO', '1', 'AcquisitionDeviceProcessingCode'],
- '0x1402': ['CS', '1', 'CassetteOrientation'],
- '0x1403': ['CS', '1', 'CassetteSize'],
- '0x1404': ['US', '1', 'ExposuresOnPlate'],
- '0x1405': ['IS', '1', 'RelativeXRayExposure'],
- '0x1411': ['DS', '1', 'ExposureIndex'],
- '0x1412': ['DS', '1', 'TargetExposureIndex'],
- '0x1413': ['DS', '1', 'DeviationIndex'],
- '0x1450': ['DS', '1', 'ColumnAngulation'],
- '0x1460': ['DS', '1', 'TomoLayerHeight'],
- '0x1470': ['DS', '1', 'TomoAngle'],
- '0x1480': ['DS', '1', 'TomoTime'],
- '0x1490': ['CS', '1', 'TomoType'],
- '0x1491': ['CS', '1', 'TomoClass'],
- '0x1495': ['IS', '1', 'NumberOfTomosynthesisSourceImages'],
- '0x1500': ['CS', '1', 'PositionerMotion'],
- '0x1508': ['CS', '1', 'PositionerType'],
- '0x1510': ['DS', '1', 'PositionerPrimaryAngle'],
- '0x1511': ['DS', '1', 'PositionerSecondaryAngle'],
- '0x1520': ['DS', '1-n', 'PositionerPrimaryAngleIncrement'],
- '0x1521': ['DS', '1-n', 'PositionerSecondaryAngleIncrement'],
- '0x1530': ['DS', '1', 'DetectorPrimaryAngle'],
- '0x1531': ['DS', '1', 'DetectorSecondaryAngle'],
- '0x1600': ['CS', '1-3', 'ShutterShape'],
- '0x1602': ['IS', '1', 'ShutterLeftVerticalEdge'],
- '0x1604': ['IS', '1', 'ShutterRightVerticalEdge'],
- '0x1606': ['IS', '1', 'ShutterUpperHorizontalEdge'],
- '0x1608': ['IS', '1', 'ShutterLowerHorizontalEdge'],
- '0x1610': ['IS', '2', 'CenterOfCircularShutter'],
- '0x1612': ['IS', '1', 'RadiusOfCircularShutter'],
- '0x1620': ['IS', '2-2n', 'VerticesOfThePolygonalShutter'],
- '0x1622': ['US', '1', 'ShutterPresentationValue'],
- '0x1623': ['US', '1', 'ShutterOverlayGroup'],
- '0x1624': ['US', '3', 'ShutterPresentationColorCIELabValue'],
- '0x1700': ['CS', '1-3', 'CollimatorShape'],
- '0x1702': ['IS', '1', 'CollimatorLeftVerticalEdge'],
- '0x1704': ['IS', '1', 'CollimatorRightVerticalEdge'],
- '0x1706': ['IS', '1', 'CollimatorUpperHorizontalEdge'],
- '0x1708': ['IS', '1', 'CollimatorLowerHorizontalEdge'],
- '0x1710': ['IS', '2', 'CenterOfCircularCollimator'],
- '0x1712': ['IS', '1', 'RadiusOfCircularCollimator'],
- '0x1720': ['IS', '2-2n', 'VerticesOfThePolygonalCollimator'],
- '0x1800': ['CS', '1', 'AcquisitionTimeSynchronized'],
- '0x1801': ['SH', '1', 'TimeSource'],
- '0x1802': ['CS', '1', 'TimeDistributionProtocol'],
- '0x1803': ['LO', '1', 'NTPSourceAddress'],
- '0x2001': ['IS', '1-n', 'PageNumberVector'],
- '0x2002': ['SH', '1-n', 'FrameLabelVector'],
- '0x2003': ['DS', '1-n', 'FramePrimaryAngleVector'],
- '0x2004': ['DS', '1-n', 'FrameSecondaryAngleVector'],
- '0x2005': ['DS', '1-n', 'SliceLocationVector'],
- '0x2006': ['SH', '1-n', 'DisplayWindowLabelVector'],
- '0x2010': ['DS', '2', 'NominalScannedPixelSpacing'],
- '0x2020': ['CS', '1', 'DigitizingDeviceTransportDirection'],
- '0x2030': ['DS', '1', 'RotationOfScannedFilm'],
- '0x2041': ['SQ', '1', 'BiopsyTargetSequence'],
- '0x2042': ['UI', '1', 'TargetUID'],
- '0x2043': ['FL', '2', 'LocalizingCursorPosition'],
- '0x2044': ['FL', '3', 'CalculatedTargetPosition'],
- '0x2045': ['SH', '1', 'TargetLabel'],
- '0x2046': ['FL', '1', 'DisplayedZValue'],
- '0x3100': ['CS', '1', 'IVUSAcquisition'],
- '0x3101': ['DS', '1', 'IVUSPullbackRate'],
- '0x3102': ['DS', '1', 'IVUSGatedRate'],
- '0x3103': ['IS', '1', 'IVUSPullbackStartFrameNumber'],
- '0x3104': ['IS', '1', 'IVUSPullbackStopFrameNumber'],
- '0x3105': ['IS', '1-n', 'LesionNumber'],
- '0x4000': ['LT', '1', 'AcquisitionComments'],
- '0x5000': ['SH', '1-n', 'OutputPower'],
- '0x5010': ['LO', '1-n', 'TransducerData'],
- '0x5012': ['DS', '1', 'FocusDepth'],
- '0x5020': ['LO', '1', 'ProcessingFunction'],
- '0x5021': ['LO', '1', 'PostprocessingFunction'],
- '0x5022': ['DS', '1', 'MechanicalIndex'],
- '0x5024': ['DS', '1', 'BoneThermalIndex'],
- '0x5026': ['DS', '1', 'CranialThermalIndex'],
- '0x5027': ['DS', '1', 'SoftTissueThermalIndex'],
- '0x5028': ['DS', '1', 'SoftTissueFocusThermalIndex'],
- '0x5029': ['DS', '1', 'SoftTissueSurfaceThermalIndex'],
- '0x5030': ['DS', '1', 'DynamicRange'],
- '0x5040': ['DS', '1', 'TotalGain'],
- '0x5050': ['IS', '1', 'DepthOfScanField'],
- '0x5100': ['CS', '1', 'PatientPosition'],
- '0x5101': ['CS', '1', 'ViewPosition'],
- '0x5104': ['SQ', '1', 'ProjectionEponymousNameCodeSequence'],
- '0x5210': ['DS', '6', 'ImageTransformationMatrix'],
- '0x5212': ['DS', '3', 'ImageTranslationVector'],
- '0x6000': ['DS', '1', 'Sensitivity'],
- '0x6011': ['SQ', '1', 'SequenceOfUltrasoundRegions'],
- '0x6012': ['US', '1', 'RegionSpatialFormat'],
- '0x6014': ['US', '1', 'RegionDataType'],
- '0x6016': ['UL', '1', 'RegionFlags'],
- '0x6018': ['UL', '1', 'RegionLocationMinX0'],
- '0x601A': ['UL', '1', 'RegionLocationMinY0'],
- '0x601C': ['UL', '1', 'RegionLocationMaxX1'],
- '0x601E': ['UL', '1', 'RegionLocationMaxY1'],
- '0x6020': ['SL', '1', 'ReferencePixelX0'],
- '0x6022': ['SL', '1', 'ReferencePixelY0'],
- '0x6024': ['US', '1', 'PhysicalUnitsXDirection'],
- '0x6026': ['US', '1', 'PhysicalUnitsYDirection'],
- '0x6028': ['FD', '1', 'ReferencePixelPhysicalValueX'],
- '0x602A': ['FD', '1', 'ReferencePixelPhysicalValueY'],
- '0x602C': ['FD', '1', 'PhysicalDeltaX'],
- '0x602E': ['FD', '1', 'PhysicalDeltaY'],
- '0x6030': ['UL', '1', 'TransducerFrequency'],
- '0x6031': ['CS', '1', 'TransducerType'],
- '0x6032': ['UL', '1', 'PulseRepetitionFrequency'],
- '0x6034': ['FD', '1', 'DopplerCorrectionAngle'],
- '0x6036': ['FD', '1', 'SteeringAngle'],
- '0x6038': ['UL', '1', 'DopplerSampleVolumeXPositionRetired'],
- '0x6039': ['SL', '1', 'DopplerSampleVolumeXPosition'],
- '0x603A': ['UL', '1', 'DopplerSampleVolumeYPositionRetired'],
- '0x603B': ['SL', '1', 'DopplerSampleVolumeYPosition'],
- '0x603C': ['UL', '1', 'TMLinePositionX0Retired'],
- '0x603D': ['SL', '1', 'TMLinePositionX0'],
- '0x603E': ['UL', '1', 'TMLinePositionY0Retired'],
- '0x603F': ['SL', '1', 'TMLinePositionY0'],
- '0x6040': ['UL', '1', 'TMLinePositionX1Retired'],
- '0x6041': ['SL', '1', 'TMLinePositionX1'],
- '0x6042': ['UL', '1', 'TMLinePositionY1Retired'],
- '0x6043': ['SL', '1', 'TMLinePositionY1'],
- '0x6044': ['US', '1', 'PixelComponentOrganization'],
- '0x6046': ['UL', '1', 'PixelComponentMask'],
- '0x6048': ['UL', '1', 'PixelComponentRangeStart'],
- '0x604A': ['UL', '1', 'PixelComponentRangeStop'],
- '0x604C': ['US', '1', 'PixelComponentPhysicalUnits'],
- '0x604E': ['US', '1', 'PixelComponentDataType'],
- '0x6050': ['UL', '1', 'NumberOfTableBreakPoints'],
- '0x6052': ['UL', '1-n', 'TableOfXBreakPoints'],
- '0x6054': ['FD', '1-n', 'TableOfYBreakPoints'],
- '0x6056': ['UL', '1', 'NumberOfTableEntries'],
- '0x6058': ['UL', '1-n', 'TableOfPixelValues'],
- '0x605A': ['FL', '1-n', 'TableOfParameterValues'],
- '0x6060': ['FL', '1-n', 'RWaveTimeVector'],
- '0x7000': ['CS', '1', 'DetectorConditionsNominalFlag'],
- '0x7001': ['DS', '1', 'DetectorTemperature'],
- '0x7004': ['CS', '1', 'DetectorType'],
- '0x7005': ['CS', '1', 'DetectorConfiguration'],
- '0x7006': ['LT', '1', 'DetectorDescription'],
- '0x7008': ['LT', '1', 'DetectorMode'],
- '0x700A': ['SH', '1', 'DetectorID'],
- '0x700C': ['DA', '1', 'DateOfLastDetectorCalibration'],
- '0x700E': ['TM', '1', 'TimeOfLastDetectorCalibration'],
- '0x7010': ['IS', '1', 'ExposuresOnDetectorSinceLastCalibration'],
- '0x7011': ['IS', '1', 'ExposuresOnDetectorSinceManufactured'],
- '0x7012': ['DS', '1', 'DetectorTimeSinceLastExposure'],
- '0x7014': ['DS', '1', 'DetectorActiveTime'],
- '0x7016': ['DS', '1', 'DetectorActivationOffsetFromExposure'],
- '0x701A': ['DS', '2', 'DetectorBinning'],
- '0x7020': ['DS', '2', 'DetectorElementPhysicalSize'],
- '0x7022': ['DS', '2', 'DetectorElementSpacing'],
- '0x7024': ['CS', '1', 'DetectorActiveShape'],
- '0x7026': ['DS', '1-2', 'DetectorActiveDimensions'],
- '0x7028': ['DS', '2', 'DetectorActiveOrigin'],
- '0x702A': ['LO', '1', 'DetectorManufacturerName'],
- '0x702B': ['LO', '1', 'DetectorManufacturerModelName'],
- '0x7030': ['DS', '2', 'FieldOfViewOrigin'],
- '0x7032': ['DS', '1', 'FieldOfViewRotation'],
- '0x7034': ['CS', '1', 'FieldOfViewHorizontalFlip'],
- '0x7036': ['FL', '2', 'PixelDataAreaOriginRelativeToFOV'],
- '0x7038': ['FL', '1', 'PixelDataAreaRotationAngleRelativeToFOV'],
- '0x7040': ['LT', '1', 'GridAbsorbingMaterial'],
- '0x7041': ['LT', '1', 'GridSpacingMaterial'],
- '0x7042': ['DS', '1', 'GridThickness'],
- '0x7044': ['DS', '1', 'GridPitch'],
- '0x7046': ['IS', '2', 'GridAspectRatio'],
- '0x7048': ['DS', '1', 'GridPeriod'],
- '0x704C': ['DS', '1', 'GridFocalDistance'],
- '0x7050': ['CS', '1-n', 'FilterMaterial'],
- '0x7052': ['DS', '1-n', 'FilterThicknessMinimum'],
- '0x7054': ['DS', '1-n', 'FilterThicknessMaximum'],
- '0x7056': ['FL', '1-n', 'FilterBeamPathLengthMinimum'],
- '0x7058': ['FL', '1-n', 'FilterBeamPathLengthMaximum'],
- '0x7060': ['CS', '1', 'ExposureControlMode'],
- '0x7062': ['LT', '1', 'ExposureControlModeDescription'],
- '0x7064': ['CS', '1', 'ExposureStatus'],
- '0x7065': ['DS', '1', 'PhototimerSetting'],
- '0x8150': ['DS', '1', 'ExposureTimeInuS'],
- '0x8151': ['DS', '1', 'XRayTubeCurrentInuA'],
- '0x9004': ['CS', '1', 'ContentQualification'],
- '0x9005': ['SH', '1', 'PulseSequenceName'],
- '0x9006': ['SQ', '1', 'MRImagingModifierSequence'],
- '0x9008': ['CS', '1', 'EchoPulseSequence'],
- '0x9009': ['CS', '1', 'InversionRecovery'],
- '0x9010': ['CS', '1', 'FlowCompensation'],
- '0x9011': ['CS', '1', 'MultipleSpinEcho'],
- '0x9012': ['CS', '1', 'MultiPlanarExcitation'],
- '0x9014': ['CS', '1', 'PhaseContrast'],
- '0x9015': ['CS', '1', 'TimeOfFlightContrast'],
- '0x9016': ['CS', '1', 'Spoiling'],
- '0x9017': ['CS', '1', 'SteadyStatePulseSequence'],
- '0x9018': ['CS', '1', 'EchoPlanarPulseSequence'],
- '0x9019': ['FD', '1', 'TagAngleFirstAxis'],
- '0x9020': ['CS', '1', 'MagnetizationTransfer'],
- '0x9021': ['CS', '1', 'T2Preparation'],
- '0x9022': ['CS', '1', 'BloodSignalNulling'],
- '0x9024': ['CS', '1', 'SaturationRecovery'],
- '0x9025': ['CS', '1', 'SpectrallySelectedSuppression'],
- '0x9026': ['CS', '1', 'SpectrallySelectedExcitation'],
- '0x9027': ['CS', '1', 'SpatialPresaturation'],
- '0x9028': ['CS', '1', 'Tagging'],
- '0x9029': ['CS', '1', 'OversamplingPhase'],
- '0x9030': ['FD', '1', 'TagSpacingFirstDimension'],
- '0x9032': ['CS', '1', 'GeometryOfKSpaceTraversal'],
- '0x9033': ['CS', '1', 'SegmentedKSpaceTraversal'],
- '0x9034': ['CS', '1', 'RectilinearPhaseEncodeReordering'],
- '0x9035': ['FD', '1', 'TagThickness'],
- '0x9036': ['CS', '1', 'PartialFourierDirection'],
- '0x9037': ['CS', '1', 'CardiacSynchronizationTechnique'],
- '0x9041': ['LO', '1', 'ReceiveCoilManufacturerName'],
- '0x9042': ['SQ', '1', 'MRReceiveCoilSequence'],
- '0x9043': ['CS', '1', 'ReceiveCoilType'],
- '0x9044': ['CS', '1', 'QuadratureReceiveCoil'],
- '0x9045': ['SQ', '1', 'MultiCoilDefinitionSequence'],
- '0x9046': ['LO', '1', 'MultiCoilConfiguration'],
- '0x9047': ['SH', '1', 'MultiCoilElementName'],
- '0x9048': ['CS', '1', 'MultiCoilElementUsed'],
- '0x9049': ['SQ', '1', 'MRTransmitCoilSequence'],
- '0x9050': ['LO', '1', 'TransmitCoilManufacturerName'],
- '0x9051': ['CS', '1', 'TransmitCoilType'],
- '0x9052': ['FD', '1-2', 'SpectralWidth'],
- '0x9053': ['FD', '1-2', 'ChemicalShiftReference'],
- '0x9054': ['CS', '1', 'VolumeLocalizationTechnique'],
- '0x9058': ['US', '1', 'MRAcquisitionFrequencyEncodingSteps'],
- '0x9059': ['CS', '1', 'Decoupling'],
- '0x9060': ['CS', '1-2', 'DecoupledNucleus'],
- '0x9061': ['FD', '1-2', 'DecouplingFrequency'],
- '0x9062': ['CS', '1', 'DecouplingMethod'],
- '0x9063': ['FD', '1-2', 'DecouplingChemicalShiftReference'],
- '0x9064': ['CS', '1', 'KSpaceFiltering'],
- '0x9065': ['CS', '1-2', 'TimeDomainFiltering'],
- '0x9066': ['US', '1-2', 'NumberOfZeroFills'],
- '0x9067': ['CS', '1', 'BaselineCorrection'],
- '0x9069': ['FD', '1', 'ParallelReductionFactorInPlane'],
- '0x9070': ['FD', '1', 'CardiacRRIntervalSpecified'],
- '0x9073': ['FD', '1', 'AcquisitionDuration'],
- '0x9074': ['DT', '1', 'FrameAcquisitionDateTime'],
- '0x9075': ['CS', '1', 'DiffusionDirectionality'],
- '0x9076': ['SQ', '1', 'DiffusionGradientDirectionSequence'],
- '0x9077': ['CS', '1', 'ParallelAcquisition'],
- '0x9078': ['CS', '1', 'ParallelAcquisitionTechnique'],
- '0x9079': ['FD', '1-n', 'InversionTimes'],
- '0x9080': ['ST', '1', 'MetaboliteMapDescription'],
- '0x9081': ['CS', '1', 'PartialFourier'],
- '0x9082': ['FD', '1', 'EffectiveEchoTime'],
- '0x9083': ['SQ', '1', 'MetaboliteMapCodeSequence'],
- '0x9084': ['SQ', '1', 'ChemicalShiftSequence'],
- '0x9085': ['CS', '1', 'CardiacSignalSource'],
- '0x9087': ['FD', '1', 'DiffusionBValue'],
- '0x9089': ['FD', '3', 'DiffusionGradientOrientation'],
- '0x9090': ['FD', '3', 'VelocityEncodingDirection'],
- '0x9091': ['FD', '1', 'VelocityEncodingMinimumValue'],
- '0x9092': ['SQ', '1', 'VelocityEncodingAcquisitionSequence'],
- '0x9093': ['US', '1', 'NumberOfKSpaceTrajectories'],
- '0x9094': ['CS', '1', 'CoverageOfKSpace'],
- '0x9095': ['UL', '1', 'SpectroscopyAcquisitionPhaseRows'],
- '0x9096': ['FD', '1', 'ParallelReductionFactorInPlaneRetired'],
- '0x9098': ['FD', '1-2', 'TransmitterFrequency'],
- '0x9100': ['CS', '1-2', 'ResonantNucleus'],
- '0x9101': ['CS', '1', 'FrequencyCorrection'],
- '0x9103': ['SQ', '1', 'MRSpectroscopyFOVGeometrySequence'],
- '0x9104': ['FD', '1', 'SlabThickness'],
- '0x9105': ['FD', '3', 'SlabOrientation'],
- '0x9106': ['FD', '3', 'MidSlabPosition'],
- '0x9107': ['SQ', '1', 'MRSpatialSaturationSequence'],
- '0x9112': ['SQ', '1', 'MRTimingAndRelatedParametersSequence'],
- '0x9114': ['SQ', '1', 'MREchoSequence'],
- '0x9115': ['SQ', '1', 'MRModifierSequence'],
- '0x9117': ['SQ', '1', 'MRDiffusionSequence'],
- '0x9118': ['SQ', '1', 'CardiacSynchronizationSequence'],
- '0x9119': ['SQ', '1', 'MRAveragesSequence'],
- '0x9125': ['SQ', '1', 'MRFOVGeometrySequence'],
- '0x9126': ['SQ', '1', 'VolumeLocalizationSequence'],
- '0x9127': ['UL', '1', 'SpectroscopyAcquisitionDataColumns'],
- '0x9147': ['CS', '1', 'DiffusionAnisotropyType'],
- '0x9151': ['DT', '1', 'FrameReferenceDateTime'],
- '0x9152': ['SQ', '1', 'MRMetaboliteMapSequence'],
- '0x9155': ['FD', '1', 'ParallelReductionFactorOutOfPlane'],
- '0x9159': ['UL', '1', 'SpectroscopyAcquisitionOutOfPlanePhaseSteps'],
- '0x9166': ['CS', '1', 'BulkMotionStatus'],
- '0x9168': ['FD', '1', 'ParallelReductionFactorSecondInPlane'],
- '0x9169': ['CS', '1', 'CardiacBeatRejectionTechnique'],
- '0x9170': ['CS', '1', 'RespiratoryMotionCompensationTechnique'],
- '0x9171': ['CS', '1', 'RespiratorySignalSource'],
- '0x9172': ['CS', '1', 'BulkMotionCompensationTechnique'],
- '0x9173': ['CS', '1', 'BulkMotionSignalSource'],
- '0x9174': ['CS', '1', 'ApplicableSafetyStandardAgency'],
- '0x9175': ['LO', '1', 'ApplicableSafetyStandardDescription'],
- '0x9176': ['SQ', '1', 'OperatingModeSequence'],
- '0x9177': ['CS', '1', 'OperatingModeType'],
- '0x9178': ['CS', '1', 'OperatingMode'],
- '0x9179': ['CS', '1', 'SpecificAbsorptionRateDefinition'],
- '0x9180': ['CS', '1', 'GradientOutputType'],
- '0x9181': ['FD', '1', 'SpecificAbsorptionRateValue'],
- '0x9182': ['FD', '1', 'GradientOutput'],
- '0x9183': ['CS', '1', 'FlowCompensationDirection'],
- '0x9184': ['FD', '1', 'TaggingDelay'],
- '0x9185': ['ST', '1', 'RespiratoryMotionCompensationTechniqueDescription'],
- '0x9186': ['SH', '1', 'RespiratorySignalSourceID'],
- '0x9195': ['FD', '1', 'ChemicalShiftMinimumIntegrationLimitInHz'],
- '0x9196': ['FD', '1', 'ChemicalShiftMaximumIntegrationLimitInHz'],
- '0x9197': ['SQ', '1', 'MRVelocityEncodingSequence'],
- '0x9198': ['CS', '1', 'FirstOrderPhaseCorrection'],
- '0x9199': ['CS', '1', 'WaterReferencedPhaseCorrection'],
- '0x9200': ['CS', '1', 'MRSpectroscopyAcquisitionType'],
- '0x9214': ['CS', '1', 'RespiratoryCyclePosition'],
- '0x9217': ['FD', '1', 'VelocityEncodingMaximumValue'],
- '0x9218': ['FD', '1', 'TagSpacingSecondDimension'],
- '0x9219': ['SS', '1', 'TagAngleSecondAxis'],
- '0x9220': ['FD', '1', 'FrameAcquisitionDuration'],
- '0x9226': ['SQ', '1', 'MRImageFrameTypeSequence'],
- '0x9227': ['SQ', '1', 'MRSpectroscopyFrameTypeSequence'],
- '0x9231': ['US', '1', 'MRAcquisitionPhaseEncodingStepsInPlane'],
- '0x9232': ['US', '1', 'MRAcquisitionPhaseEncodingStepsOutOfPlane'],
- '0x9234': ['UL', '1', 'SpectroscopyAcquisitionPhaseColumns'],
- '0x9236': ['CS', '1', 'CardiacCyclePosition'],
- '0x9239': ['SQ', '1', 'SpecificAbsorptionRateSequence'],
- '0x9240': ['US', '1', 'RFEchoTrainLength'],
- '0x9241': ['US', '1', 'GradientEchoTrainLength'],
- '0x9250': ['CS', '1', 'ArterialSpinLabelingContrast'],
- '0x9251': ['SQ', '1', 'MRArterialSpinLabelingSequence'],
- '0x9252': ['LO', '1', 'ASLTechniqueDescription'],
- '0x9253': ['US', '1', 'ASLSlabNumber'],
- '0x9254': ['FD', '1', 'ASLSlabThickness'],
- '0x9255': ['FD', '3', 'ASLSlabOrientation'],
- '0x9256': ['FD', '3', 'ASLMidSlabPosition'],
- '0x9257': ['CS', '1', 'ASLContext'],
- '0x9258': ['UL', '1', 'ASLPulseTrainDuration'],
- '0x9259': ['CS', '1', 'ASLCrusherFlag'],
- '0x925A': ['FD', '1', 'ASLCrusherFlowLimit'],
- '0x925B': ['LO', '1', 'ASLCrusherDescription'],
- '0x925C': ['CS', '1', 'ASLBolusCutoffFlag'],
- '0x925D': ['SQ', '1', 'ASLBolusCutoffTimingSequence'],
- '0x925E': ['LO', '1', 'ASLBolusCutoffTechnique'],
- '0x925F': ['UL', '1', 'ASLBolusCutoffDelayTime'],
- '0x9260': ['SQ', '1', 'ASLSlabSequence'],
- '0x9295': ['FD', '1', 'ChemicalShiftMinimumIntegrationLimitInppm'],
- '0x9296': ['FD', '1', 'ChemicalShiftMaximumIntegrationLimitInppm'],
- '0x9297': ['CS', '1', 'WaterReferenceAcquisition'],
- '0x9298': ['IS', '1', 'EchoPeakPosition'],
- '0x9301': ['SQ', '1', 'CTAcquisitionTypeSequence'],
- '0x9302': ['CS', '1', 'AcquisitionType'],
- '0x9303': ['FD', '1', 'TubeAngle'],
- '0x9304': ['SQ', '1', 'CTAcquisitionDetailsSequence'],
- '0x9305': ['FD', '1', 'RevolutionTime'],
- '0x9306': ['FD', '1', 'SingleCollimationWidth'],
- '0x9307': ['FD', '1', 'TotalCollimationWidth'],
- '0x9308': ['SQ', '1', 'CTTableDynamicsSequence'],
- '0x9309': ['FD', '1', 'TableSpeed'],
- '0x9310': ['FD', '1', 'TableFeedPerRotation'],
- '0x9311': ['FD', '1', 'SpiralPitchFactor'],
- '0x9312': ['SQ', '1', 'CTGeometrySequence'],
- '0x9313': ['FD', '3', 'DataCollectionCenterPatient'],
- '0x9314': ['SQ', '1', 'CTReconstructionSequence'],
- '0x9315': ['CS', '1', 'ReconstructionAlgorithm'],
- '0x9316': ['CS', '1', 'ConvolutionKernelGroup'],
- '0x9317': ['FD', '2', 'ReconstructionFieldOfView'],
- '0x9318': ['FD', '3', 'ReconstructionTargetCenterPatient'],
- '0x9319': ['FD', '1', 'ReconstructionAngle'],
- '0x9320': ['SH', '1', 'ImageFilter'],
- '0x9321': ['SQ', '1', 'CTExposureSequence'],
- '0x9322': ['FD', '2', 'ReconstructionPixelSpacing'],
- '0x9323': ['CS', '1', 'ExposureModulationType'],
- '0x9324': ['FD', '1', 'EstimatedDoseSaving'],
- '0x9325': ['SQ', '1', 'CTXRayDetailsSequence'],
- '0x9326': ['SQ', '1', 'CTPositionSequence'],
- '0x9327': ['FD', '1', 'TablePosition'],
- '0x9328': ['FD', '1', 'ExposureTimeInms'],
- '0x9329': ['SQ', '1', 'CTImageFrameTypeSequence'],
- '0x9330': ['FD', '1', 'XRayTubeCurrentInmA'],
- '0x9332': ['FD', '1', 'ExposureInmAs'],
- '0x9333': ['CS', '1', 'ConstantVolumeFlag'],
- '0x9334': ['CS', '1', 'FluoroscopyFlag'],
- '0x9335': ['FD', '1', 'DistanceSourceToDataCollectionCenter'],
- '0x9337': ['US', '1', 'ContrastBolusAgentNumber'],
- '0x9338': ['SQ', '1', 'ContrastBolusIngredientCodeSequence'],
- '0x9340': ['SQ', '1', 'ContrastAdministrationProfileSequence'],
- '0x9341': ['SQ', '1', 'ContrastBolusUsageSequence'],
- '0x9342': ['CS', '1', 'ContrastBolusAgentAdministered'],
- '0x9343': ['CS', '1', 'ContrastBolusAgentDetected'],
- '0x9344': ['CS', '1', 'ContrastBolusAgentPhase'],
- '0x9345': ['FD', '1', 'CTDIvol'],
- '0x9346': ['SQ', '1', 'CTDIPhantomTypeCodeSequence'],
- '0x9351': ['FL', '1', 'CalciumScoringMassFactorPatient'],
- '0x9352': ['FL', '3', 'CalciumScoringMassFactorDevice'],
- '0x9353': ['FL', '1', 'EnergyWeightingFactor'],
- '0x9360': ['SQ', '1', 'CTAdditionalXRaySourceSequence'],
- '0x9401': ['SQ', '1', 'ProjectionPixelCalibrationSequence'],
- '0x9402': ['FL', '1', 'DistanceSourceToIsocenter'],
- '0x9403': ['FL', '1', 'DistanceObjectToTableTop'],
- '0x9404': ['FL', '2', 'ObjectPixelSpacingInCenterOfBeam'],
- '0x9405': ['SQ', '1', 'PositionerPositionSequence'],
- '0x9406': ['SQ', '1', 'TablePositionSequence'],
- '0x9407': ['SQ', '1', 'CollimatorShapeSequence'],
- '0x9410': ['CS', '1', 'PlanesInAcquisition'],
- '0x9412': ['SQ', '1', 'XAXRFFrameCharacteristicsSequence'],
- '0x9417': ['SQ', '1', 'FrameAcquisitionSequence'],
- '0x9420': ['CS', '1', 'XRayReceptorType'],
- '0x9423': ['LO', '1', 'AcquisitionProtocolName'],
- '0x9424': ['LT', '1', 'AcquisitionProtocolDescription'],
- '0x9425': ['CS', '1', 'ContrastBolusIngredientOpaque'],
- '0x9426': ['FL', '1', 'DistanceReceptorPlaneToDetectorHousing'],
- '0x9427': ['CS', '1', 'IntensifierActiveShape'],
- '0x9428': ['FL', '1-2', 'IntensifierActiveDimensions'],
- '0x9429': ['FL', '2', 'PhysicalDetectorSize'],
- '0x9430': ['FL', '2', 'PositionOfIsocenterProjection'],
- '0x9432': ['SQ', '1', 'FieldOfViewSequence'],
- '0x9433': ['LO', '1', 'FieldOfViewDescription'],
- '0x9434': ['SQ', '1', 'ExposureControlSensingRegionsSequence'],
- '0x9435': ['CS', '1', 'ExposureControlSensingRegionShape'],
- '0x9436': ['SS', '1', 'ExposureControlSensingRegionLeftVerticalEdge'],
- '0x9437': ['SS', '1', 'ExposureControlSensingRegionRightVerticalEdge'],
- '0x9438': ['SS', '1', 'ExposureControlSensingRegionUpperHorizontalEdge'],
- '0x9439': ['SS', '1', 'ExposureControlSensingRegionLowerHorizontalEdge'],
- '0x9440': ['SS', '2', 'CenterOfCircularExposureControlSensingRegion'],
- '0x9441': ['US', '1', 'RadiusOfCircularExposureControlSensingRegion'],
- '0x9442': ['SS', '2-n', 'VerticesOfThePolygonalExposureControlSensingRegion'],
- '0x9445': ['', '', ''],
- '0x9447': ['FL', '1', 'ColumnAngulationPatient'],
- '0x9449': ['FL', '1', 'BeamAngle'],
- '0x9451': ['SQ', '1', 'FrameDetectorParametersSequence'],
- '0x9452': ['FL', '1', 'CalculatedAnatomyThickness'],
- '0x9455': ['SQ', '1', 'CalibrationSequence'],
- '0x9456': ['SQ', '1', 'ObjectThicknessSequence'],
- '0x9457': ['CS', '1', 'PlaneIdentification'],
- '0x9461': ['FL', '1-2', 'FieldOfViewDimensionsInFloat'],
- '0x9462': ['SQ', '1', 'IsocenterReferenceSystemSequence'],
- '0x9463': ['FL', '1', 'PositionerIsocenterPrimaryAngle'],
- '0x9464': ['FL', '1', 'PositionerIsocenterSecondaryAngle'],
- '0x9465': ['FL', '1', 'PositionerIsocenterDetectorRotationAngle'],
- '0x9466': ['FL', '1', 'TableXPositionToIsocenter'],
- '0x9467': ['FL', '1', 'TableYPositionToIsocenter'],
- '0x9468': ['FL', '1', 'TableZPositionToIsocenter'],
- '0x9469': ['FL', '1', 'TableHorizontalRotationAngle'],
- '0x9470': ['FL', '1', 'TableHeadTiltAngle'],
- '0x9471': ['FL', '1', 'TableCradleTiltAngle'],
- '0x9472': ['SQ', '1', 'FrameDisplayShutterSequence'],
- '0x9473': ['FL', '1', 'AcquiredImageAreaDoseProduct'],
- '0x9474': ['CS', '1', 'CArmPositionerTabletopRelationship'],
- '0x9476': ['SQ', '1', 'XRayGeometrySequence'],
- '0x9477': ['SQ', '1', 'IrradiationEventIdentificationSequence'],
- '0x9504': ['SQ', '1', 'XRay3DFrameTypeSequence'],
- '0x9506': ['SQ', '1', 'ContributingSourcesSequence'],
- '0x9507': ['SQ', '1', 'XRay3DAcquisitionSequence'],
- '0x9508': ['FL', '1', 'PrimaryPositionerScanArc'],
- '0x9509': ['FL', '1', 'SecondaryPositionerScanArc'],
- '0x9510': ['FL', '1', 'PrimaryPositionerScanStartAngle'],
- '0x9511': ['FL', '1', 'SecondaryPositionerScanStartAngle'],
- '0x9514': ['FL', '1', 'PrimaryPositionerIncrement'],
- '0x9515': ['FL', '1', 'SecondaryPositionerIncrement'],
- '0x9516': ['DT', '1', 'StartAcquisitionDateTime'],
- '0x9517': ['DT', '1', 'EndAcquisitionDateTime'],
- '0x9518': ['SS', '1', 'PrimaryPositionerIncrementSign'],
- '0x9519': ['SS', '1', 'SecondaryPositionerIncrementSign'],
- '0x9524': ['LO', '1', 'ApplicationName'],
- '0x9525': ['LO', '1', 'ApplicationVersion'],
- '0x9526': ['LO', '1', 'ApplicationManufacturer'],
- '0x9527': ['CS', '1', 'AlgorithmType'],
- '0x9528': ['LO', '1', 'AlgorithmDescription'],
- '0x9530': ['SQ', '1', 'XRay3DReconstructionSequence'],
- '0x9531': ['LO', '1', 'ReconstructionDescription'],
- '0x9538': ['SQ', '1', 'PerProjectionAcquisitionSequence'],
- '0x9541': ['SQ', '1', 'DetectorPositionSequence'],
- '0x9542': ['SQ', '1', 'XRayAcquisitionDoseSequence'],
- '0x9543': ['FD', '1', 'XRaySourceIsocenterPrimaryAngle'],
- '0x9544': ['FD', '1', 'XRaySourceIsocenterSecondaryAngle'],
- '0x9545': ['FD', '1', 'BreastSupportIsocenterPrimaryAngle'],
- '0x9546': ['FD', '1', 'BreastSupportIsocenterSecondaryAngle'],
- '0x9547': ['FD', '1', 'BreastSupportXPositionToIsocenter'],
- '0x9548': ['FD', '1', 'BreastSupportYPositionToIsocenter'],
- '0x9549': ['FD', '1', 'BreastSupportZPositionToIsocenter'],
- '0x9550': ['FD', '1', 'DetectorIsocenterPrimaryAngle'],
- '0x9551': ['FD', '1', 'DetectorIsocenterSecondaryAngle'],
- '0x9552': ['FD', '1', 'DetectorXPositionToIsocenter'],
- '0x9553': ['FD', '1', 'DetectorYPositionToIsocenter'],
- '0x9554': ['FD', '1', 'DetectorZPositionToIsocenter'],
- '0x9555': ['SQ', '1', 'XRayGridSequence'],
- '0x9556': ['SQ', '1', 'XRayFilterSequence'],
- '0x9557': ['FD', '3', 'DetectorActiveAreaTLHCPosition'],
- '0x9558': ['FD', '6', 'DetectorActiveAreaOrientation'],
- '0x9559': ['CS', '1', 'PositionerPrimaryAngleDirection'],
- '0x9601': ['SQ', '1', 'DiffusionBMatrixSequence'],
- '0x9602': ['FD', '1', 'DiffusionBValueXX'],
- '0x9603': ['FD', '1', 'DiffusionBValueXY'],
- '0x9604': ['FD', '1', 'DiffusionBValueXZ'],
- '0x9605': ['FD', '1', 'DiffusionBValueYY'],
- '0x9606': ['FD', '1', 'DiffusionBValueYZ'],
- '0x9607': ['FD', '1', 'DiffusionBValueZZ'],
- '0x9701': ['DT', '1', 'DecayCorrectionDateTime'],
- '0x9715': ['FD', '1', 'StartDensityThreshold'],
- '0x9716': ['FD', '1', 'StartRelativeDensityDifferenceThreshold'],
- '0x9717': ['FD', '1', 'StartCardiacTriggerCountThreshold'],
- '0x9718': ['FD', '1', 'StartRespiratoryTriggerCountThreshold'],
- '0x9719': ['FD', '1', 'TerminationCountsThreshold'],
- '0x9720': ['FD', '1', 'TerminationDensityThreshold'],
- '0x9721': ['FD', '1', 'TerminationRelativeDensityThreshold'],
- '0x9722': ['FD', '1', 'TerminationTimeThreshold'],
- '0x9723': ['FD', '1', 'TerminationCardiacTriggerCountThreshold'],
- '0x9724': ['FD', '1', 'TerminationRespiratoryTriggerCountThreshold'],
- '0x9725': ['CS', '1', 'DetectorGeometry'],
- '0x9726': ['FD', '1', 'TransverseDetectorSeparation'],
- '0x9727': ['FD', '1', 'AxialDetectorDimension'],
- '0x9729': ['US', '1', 'RadiopharmaceuticalAgentNumber'],
- '0x9732': ['SQ', '1', 'PETFrameAcquisitionSequence'],
- '0x9733': ['SQ', '1', 'PETDetectorMotionDetailsSequence'],
- '0x9734': ['SQ', '1', 'PETTableDynamicsSequence'],
- '0x9735': ['SQ', '1', 'PETPositionSequence'],
- '0x9736': ['SQ', '1', 'PETFrameCorrectionFactorsSequence'],
- '0x9737': ['SQ', '1', 'RadiopharmaceuticalUsageSequence'],
- '0x9738': ['CS', '1', 'AttenuationCorrectionSource'],
- '0x9739': ['US', '1', 'NumberOfIterations'],
- '0x9740': ['US', '1', 'NumberOfSubsets'],
- '0x9749': ['SQ', '1', 'PETReconstructionSequence'],
- '0x9751': ['SQ', '1', 'PETFrameTypeSequence'],
- '0x9755': ['CS', '1', 'TimeOfFlightInformationUsed'],
- '0x9756': ['CS', '1', 'ReconstructionType'],
- '0x9758': ['CS', '1', 'DecayCorrected'],
- '0x9759': ['CS', '1', 'AttenuationCorrected'],
- '0x9760': ['CS', '1', 'ScatterCorrected'],
- '0x9761': ['CS', '1', 'DeadTimeCorrected'],
- '0x9762': ['CS', '1', 'GantryMotionCorrected'],
- '0x9763': ['CS', '1', 'PatientMotionCorrected'],
- '0x9764': ['CS', '1', 'CountLossNormalizationCorrected'],
- '0x9765': ['CS', '1', 'RandomsCorrected'],
- '0x9766': ['CS', '1', 'NonUniformRadialSamplingCorrected'],
- '0x9767': ['CS', '1', 'SensitivityCalibrated'],
- '0x9768': ['CS', '1', 'DetectorNormalizationCorrection'],
- '0x9769': ['CS', '1', 'IterativeReconstructionMethod'],
- '0x9770': ['CS', '1', 'AttenuationCorrectionTemporalRelationship'],
- '0x9771': ['SQ', '1', 'PatientPhysiologicalStateSequence'],
- '0x9772': ['SQ', '1', 'PatientPhysiologicalStateCodeSequence'],
- '0x9801': ['FD', '1-n', 'DepthsOfFocus'],
- '0x9803': ['SQ', '1', 'ExcludedIntervalsSequence'],
- '0x9804': ['DT', '1', 'ExclusionStartDateTime'],
- '0x9805': ['FD', '1', 'ExclusionDuration'],
- '0x9806': ['SQ', '1', 'USImageDescriptionSequence'],
- '0x9807': ['SQ', '1', 'ImageDataTypeSequence'],
- '0x9808': ['CS', '1', 'DataType'],
- '0x9809': ['SQ', '1', 'TransducerScanPatternCodeSequence'],
- '0x980B': ['CS', '1', 'AliasedDataType'],
- '0x980C': ['CS', '1', 'PositionMeasuringDeviceUsed'],
- '0x980D': ['SQ', '1', 'TransducerGeometryCodeSequence'],
- '0x980E': ['SQ', '1', 'TransducerBeamSteeringCodeSequence'],
- '0x980F': ['SQ', '1', 'TransducerApplicationCodeSequence'],
- '0x9810': ['xs', '1', 'ZeroVelocityPixelValue'],
- '0xA001': ['SQ', '1', 'ContributingEquipmentSequence'],
- '0xA002': ['DT', '1', 'ContributionDateTime'],
- '0xA003': ['ST', '1', 'ContributionDescription']
- },
- '0x0020': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x000D': ['UI', '1', 'StudyInstanceUID'],
- '0x000E': ['UI', '1', 'SeriesInstanceUID'],
- '0x0010': ['SH', '1', 'StudyID'],
- '0x0011': ['IS', '1', 'SeriesNumber'],
- '0x0012': ['IS', '1', 'AcquisitionNumber'],
- '0x0013': ['IS', '1', 'InstanceNumber'],
- '0x0014': ['IS', '1', 'IsotopeNumber'],
- '0x0015': ['IS', '1', 'PhaseNumber'],
- '0x0016': ['IS', '1', 'IntervalNumber'],
- '0x0017': ['IS', '1', 'TimeSlotNumber'],
- '0x0018': ['IS', '1', 'AngleNumber'],
- '0x0019': ['IS', '1', 'ItemNumber'],
- '0x0020': ['CS', '2', 'PatientOrientation'],
- '0x0022': ['IS', '1', 'OverlayNumber'],
- '0x0024': ['IS', '1', 'CurveNumber'],
- '0x0026': ['IS', '1', 'LUTNumber'],
- '0x0030': ['DS', '3', 'ImagePosition'],
- '0x0032': ['DS', '3', 'ImagePositionPatient'],
- '0x0035': ['DS', '6', 'ImageOrientation'],
- '0x0037': ['DS', '6', 'ImageOrientationPatient'],
- '0x0050': ['DS', '1', 'Location'],
- '0x0052': ['UI', '1', 'FrameOfReferenceUID'],
- '0x0060': ['CS', '1', 'Laterality'],
- '0x0062': ['CS', '1', 'ImageLaterality'],
- '0x0070': ['LO', '1', 'ImageGeometryType'],
- '0x0080': ['CS', '1-n', 'MaskingImage'],
- '0x00AA': ['IS', '1', 'ReportNumber'],
- '0x0100': ['IS', '1', 'TemporalPositionIdentifier'],
- '0x0105': ['IS', '1', 'NumberOfTemporalPositions'],
- '0x0110': ['DS', '1', 'TemporalResolution'],
- '0x0200': ['UI', '1', 'SynchronizationFrameOfReferenceUID'],
- '0x0242': ['UI', '1', 'SOPInstanceUIDOfConcatenationSource'],
- '0x1000': ['IS', '1', 'SeriesInStudy'],
- '0x1001': ['IS', '1', 'AcquisitionsInSeries'],
- '0x1002': ['IS', '1', 'ImagesInAcquisition'],
- '0x1003': ['IS', '1', 'ImagesInSeries'],
- '0x1004': ['IS', '1', 'AcquisitionsInStudy'],
- '0x1005': ['IS', '1', 'ImagesInStudy'],
- '0x1020': ['LO', '1-n', 'Reference'],
- '0x1040': ['LO', '1', 'PositionReferenceIndicator'],
- '0x1041': ['DS', '1', 'SliceLocation'],
- '0x1070': ['IS', '1-n', 'OtherStudyNumbers'],
- '0x1200': ['IS', '1', 'NumberOfPatientRelatedStudies'],
- '0x1202': ['IS', '1', 'NumberOfPatientRelatedSeries'],
- '0x1204': ['IS', '1', 'NumberOfPatientRelatedInstances'],
- '0x1206': ['IS', '1', 'NumberOfStudyRelatedSeries'],
- '0x1208': ['IS', '1', 'NumberOfStudyRelatedInstances'],
- '0x1209': ['IS', '1', 'NumberOfSeriesRelatedInstances'],
- '0x3100': ['CS', '1-n', 'SourceImageIDs'],
- '0x3401': ['CS', '1', 'ModifyingDeviceID'],
- '0x3402': ['CS', '1', 'ModifiedImageID'],
- '0x3403': ['DA', '1', 'ModifiedImageDate'],
- '0x3404': ['LO', '1', 'ModifyingDeviceManufacturer'],
- '0x3405': ['TM', '1', 'ModifiedImageTime'],
- '0x3406': ['LO', '1', 'ModifiedImageDescription'],
- '0x4000': ['LT', '1', 'ImageComments'],
- '0x5000': ['AT', '1-n', 'OriginalImageIdentification'],
- '0x5002': ['LO', '1-n', 'OriginalImageIdentificationNomenclature'],
- '0x9056': ['SH', '1', 'StackID'],
- '0x9057': ['UL', '1', 'InStackPositionNumber'],
- '0x9071': ['SQ', '1', 'FrameAnatomySequence'],
- '0x9072': ['CS', '1', 'FrameLaterality'],
- '0x9111': ['SQ', '1', 'FrameContentSequence'],
- '0x9113': ['SQ', '1', 'PlanePositionSequence'],
- '0x9116': ['SQ', '1', 'PlaneOrientationSequence'],
- '0x9128': ['UL', '1', 'TemporalPositionIndex'],
- '0x9153': ['FD', '1', 'NominalCardiacTriggerDelayTime'],
- '0x9154': ['FL', '1', 'NominalCardiacTriggerTimePriorToRPeak'],
- '0x9155': ['FL', '1', 'ActualCardiacTriggerTimePriorToRPeak'],
- '0x9156': ['US', '1', 'FrameAcquisitionNumber'],
- '0x9157': ['UL', '1-n', 'DimensionIndexValues'],
- '0x9158': ['LT', '1', 'FrameComments'],
- '0x9161': ['UI', '1', 'ConcatenationUID'],
- '0x9162': ['US', '1', 'InConcatenationNumber'],
- '0x9163': ['US', '1', 'InConcatenationTotalNumber'],
- '0x9164': ['UI', '1', 'DimensionOrganizationUID'],
- '0x9165': ['AT', '1', 'DimensionIndexPointer'],
- '0x9167': ['AT', '1', 'FunctionalGroupPointer'],
- '0x9170': ['SQ', '1', 'UnassignedSharedConvertedAttributesSequence'],
- '0x9171': ['SQ', '1', 'UnassignedPerFrameConvertedAttributesSequence'],
- '0x9172': ['SQ', '1', 'ConversionSourceAttributesSequence'],
- '0x9213': ['LO', '1', 'DimensionIndexPrivateCreator'],
- '0x9221': ['SQ', '1', 'DimensionOrganizationSequence'],
- '0x9222': ['SQ', '1', 'DimensionIndexSequence'],
- '0x9228': ['UL', '1', 'ConcatenationFrameOffsetNumber'],
- '0x9238': ['LO', '1', 'FunctionalGroupPrivateCreator'],
- '0x9241': ['FL', '1', 'NominalPercentageOfCardiacPhase'],
- '0x9245': ['FL', '1', 'NominalPercentageOfRespiratoryPhase'],
- '0x9246': ['FL', '1', 'StartingRespiratoryAmplitude'],
- '0x9247': ['CS', '1', 'StartingRespiratoryPhase'],
- '0x9248': ['FL', '1', 'EndingRespiratoryAmplitude'],
- '0x9249': ['CS', '1', 'EndingRespiratoryPhase'],
- '0x9250': ['CS', '1', 'RespiratoryTriggerType'],
- '0x9251': ['FD', '1', 'RRIntervalTimeNominal'],
- '0x9252': ['FD', '1', 'ActualCardiacTriggerDelayTime'],
- '0x9253': ['SQ', '1', 'RespiratorySynchronizationSequence'],
- '0x9254': ['FD', '1', 'RespiratoryIntervalTime'],
- '0x9255': ['FD', '1', 'NominalRespiratoryTriggerDelayTime'],
- '0x9256': ['FD', '1', 'RespiratoryTriggerDelayThreshold'],
- '0x9257': ['FD', '1', 'ActualRespiratoryTriggerDelayTime'],
- '0x9301': ['FD', '3', 'ImagePositionVolume'],
- '0x9302': ['FD', '6', 'ImageOrientationVolume'],
- '0x9307': ['CS', '1', 'UltrasoundAcquisitionGeometry'],
- '0x9308': ['FD', '3', 'ApexPosition'],
- '0x9309': ['FD', '16', 'VolumeToTransducerMappingMatrix'],
- '0x930A': ['FD', '16', 'VolumeToTableMappingMatrix'],
- '0x930B': ['CS', '1', 'VolumeToTransducerRelationship'],
- '0x930C': ['CS', '1', 'PatientFrameOfReferenceSource'],
- '0x930D': ['FD', '1', 'TemporalPositionTimeOffset'],
- '0x930E': ['SQ', '1', 'PlanePositionVolumeSequence'],
- '0x930F': ['SQ', '1', 'PlaneOrientationVolumeSequence'],
- '0x9310': ['SQ', '1', 'TemporalPositionSequence'],
- '0x9311': ['CS', '1', 'DimensionOrganizationType'],
- '0x9312': ['UI', '1', 'VolumeFrameOfReferenceUID'],
- '0x9313': ['UI', '1', 'TableFrameOfReferenceUID'],
- '0x9421': ['LO', '1', 'DimensionDescriptionLabel'],
- '0x9450': ['SQ', '1', 'PatientOrientationInFrameSequence'],
- '0x9453': ['LO', '1', 'FrameLabel'],
- '0x9518': ['US', '1-n', 'AcquisitionIndex'],
- '0x9529': ['SQ', '1', 'ContributingSOPInstancesReferenceSequence'],
- '0x9536': ['US', '1', 'ReconstructionIndex']
- },
- '0x0022': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['US', '1', 'LightPathFilterPassThroughWavelength'],
- '0x0002': ['US', '2', 'LightPathFilterPassBand'],
- '0x0003': ['US', '1', 'ImagePathFilterPassThroughWavelength'],
- '0x0004': ['US', '2', 'ImagePathFilterPassBand'],
- '0x0005': ['CS', '1', 'PatientEyeMovementCommanded'],
- '0x0006': ['SQ', '1', 'PatientEyeMovementCommandCodeSequence'],
- '0x0007': ['FL', '1', 'SphericalLensPower'],
- '0x0008': ['FL', '1', 'CylinderLensPower'],
- '0x0009': ['FL', '1', 'CylinderAxis'],
- '0x000A': ['FL', '1', 'EmmetropicMagnification'],
- '0x000B': ['FL', '1', 'IntraOcularPressure'],
- '0x000C': ['FL', '1', 'HorizontalFieldOfView'],
- '0x000D': ['CS', '1', 'PupilDilated'],
- '0x000E': ['FL', '1', 'DegreeOfDilation'],
- '0x0010': ['FL', '1', 'StereoBaselineAngle'],
- '0x0011': ['FL', '1', 'StereoBaselineDisplacement'],
- '0x0012': ['FL', '1', 'StereoHorizontalPixelOffset'],
- '0x0013': ['FL', '1', 'StereoVerticalPixelOffset'],
- '0x0014': ['FL', '1', 'StereoRotation'],
- '0x0015': ['SQ', '1', 'AcquisitionDeviceTypeCodeSequence'],
- '0x0016': ['SQ', '1', 'IlluminationTypeCodeSequence'],
- '0x0017': ['SQ', '1', 'LightPathFilterTypeStackCodeSequence'],
- '0x0018': ['SQ', '1', 'ImagePathFilterTypeStackCodeSequence'],
- '0x0019': ['SQ', '1', 'LensesCodeSequence'],
- '0x001A': ['SQ', '1', 'ChannelDescriptionCodeSequence'],
- '0x001B': ['SQ', '1', 'RefractiveStateSequence'],
- '0x001C': ['SQ', '1', 'MydriaticAgentCodeSequence'],
- '0x001D': ['SQ', '1', 'RelativeImagePositionCodeSequence'],
- '0x001E': ['FL', '1', 'CameraAngleOfView'],
- '0x0020': ['SQ', '1', 'StereoPairsSequence'],
- '0x0021': ['SQ', '1', 'LeftImageSequence'],
- '0x0022': ['SQ', '1', 'RightImageSequence'],
- '0x0028': ['CS', '1', 'StereoPairsPresent'],
- '0x0030': ['FL', '1', 'AxialLengthOfTheEye'],
- '0x0031': ['SQ', '1', 'OphthalmicFrameLocationSequence'],
- '0x0032': ['FL', '2-2n', 'ReferenceCoordinates'],
- '0x0035': ['FL', '1', 'DepthSpatialResolution'],
- '0x0036': ['FL', '1', 'MaximumDepthDistortion'],
- '0x0037': ['FL', '1', 'AlongScanSpatialResolution'],
- '0x0038': ['FL', '1', 'MaximumAlongScanDistortion'],
- '0x0039': ['CS', '1', 'OphthalmicImageOrientation'],
- '0x0041': ['FL', '1', 'DepthOfTransverseImage'],
- '0x0042': ['SQ', '1', 'MydriaticAgentConcentrationUnitsSequence'],
- '0x0048': ['FL', '1', 'AcrossScanSpatialResolution'],
- '0x0049': ['FL', '1', 'MaximumAcrossScanDistortion'],
- '0x004E': ['DS', '1', 'MydriaticAgentConcentration'],
- '0x0055': ['FL', '1', 'IlluminationWaveLength'],
- '0x0056': ['FL', '1', 'IlluminationPower'],
- '0x0057': ['FL', '1', 'IlluminationBandwidth'],
- '0x0058': ['SQ', '1', 'MydriaticAgentSequence'],
- '0x1007': ['SQ', '1', 'OphthalmicAxialMeasurementsRightEyeSequence'],
- '0x1008': ['SQ', '1', 'OphthalmicAxialMeasurementsLeftEyeSequence'],
- '0x1009': ['CS', '1', 'OphthalmicAxialMeasurementsDeviceType'],
- '0x1010': ['CS', '1', 'OphthalmicAxialLengthMeasurementsType'],
- '0x1012': ['SQ', '1', 'OphthalmicAxialLengthSequence'],
- '0x1019': ['FL', '1', 'OphthalmicAxialLength'],
- '0x1024': ['SQ', '1', 'LensStatusCodeSequence'],
- '0x1025': ['SQ', '1', 'VitreousStatusCodeSequence'],
- '0x1028': ['SQ', '1', 'IOLFormulaCodeSequence'],
- '0x1029': ['LO', '1', 'IOLFormulaDetail'],
- '0x1033': ['FL', '1', 'KeratometerIndex'],
- '0x1035': ['SQ', '1', 'SourceOfOphthalmicAxialLengthCodeSequence'],
- '0x1037': ['FL', '1', 'TargetRefraction'],
- '0x1039': ['CS', '1', 'RefractiveProcedureOccurred'],
- '0x1040': ['SQ', '1', 'RefractiveSurgeryTypeCodeSequence'],
- '0x1044': ['SQ', '1', 'OphthalmicUltrasoundMethodCodeSequence'],
- '0x1050': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSequence'],
- '0x1053': ['FL', '1', 'IOLPower'],
- '0x1054': ['FL', '1', 'PredictedRefractiveError'],
- '0x1059': ['FL', '1', 'OphthalmicAxialLengthVelocity'],
- '0x1065': ['LO', '1', 'LensStatusDescription'],
- '0x1066': ['LO', '1', 'VitreousStatusDescription'],
- '0x1090': ['SQ', '1', 'IOLPowerSequence'],
- '0x1092': ['SQ', '1', 'LensConstantSequence'],
- '0x1093': ['LO', '1', 'IOLManufacturer'],
- '0x1094': ['LO', '1', 'LensConstantDescription'],
- '0x1095': ['LO', '1', 'ImplantName'],
- '0x1096': ['SQ', '1', 'KeratometryMeasurementTypeCodeSequence'],
- '0x1097': ['LO', '1', 'ImplantPartNumber'],
- '0x1100': ['SQ', '1', 'ReferencedOphthalmicAxialMeasurementsSequence'],
- '0x1101': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSegmentNameCodeSequence'],
- '0x1103': ['SQ', '1', 'RefractiveErrorBeforeRefractiveSurgeryCodeSequence'],
- '0x1121': ['FL', '1', 'IOLPowerForExactEmmetropia'],
- '0x1122': ['FL', '1', 'IOLPowerForExactTargetRefraction'],
- '0x1125': ['SQ', '1', 'AnteriorChamberDepthDefinitionCodeSequence'],
- '0x1127': ['SQ', '1', 'LensThicknessSequence'],
- '0x1128': ['SQ', '1', 'AnteriorChamberDepthSequence'],
- '0x1130': ['FL', '1', 'LensThickness'],
- '0x1131': ['FL', '1', 'AnteriorChamberDepth'],
- '0x1132': ['SQ', '1', 'SourceOfLensThicknessDataCodeSequence'],
- '0x1133': ['SQ', '1', 'SourceOfAnteriorChamberDepthDataCodeSequence'],
- '0x1134': ['SQ', '1', 'SourceOfRefractiveMeasurementsSequence'],
- '0x1135': ['SQ', '1', 'SourceOfRefractiveMeasurementsCodeSequence'],
- '0x1140': ['CS', '1', 'OphthalmicAxialLengthMeasurementModified'],
- '0x1150': ['SQ', '1', 'OphthalmicAxialLengthDataSourceCodeSequence'],
- '0x1153': ['SQ', '1', 'OphthalmicAxialLengthAcquisitionMethodCodeSequence'],
- '0x1155': ['FL', '1', 'SignalToNoiseRatio'],
- '0x1159': ['LO', '1', 'OphthalmicAxialLengthDataSourceDescription'],
- '0x1210': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsTotalLengthSequence'],
- '0x1211': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSegmentalLengthSequence'],
- '0x1212': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsLengthSummationSequence'],
- '0x1220': ['SQ', '1', 'UltrasoundOphthalmicAxialLengthMeasurementsSequence'],
- '0x1225': ['SQ', '1', 'OpticalOphthalmicAxialLengthMeasurementsSequence'],
- '0x1230': ['SQ', '1', 'UltrasoundSelectedOphthalmicAxialLengthSequence'],
- '0x1250': ['SQ', '1', 'OphthalmicAxialLengthSelectionMethodCodeSequence'],
- '0x1255': ['SQ', '1', 'OpticalSelectedOphthalmicAxialLengthSequence'],
- '0x1257': ['SQ', '1', 'SelectedSegmentalOphthalmicAxialLengthSequence'],
- '0x1260': ['SQ', '1', 'SelectedTotalOphthalmicAxialLengthSequence'],
- '0x1262': ['SQ', '1', 'OphthalmicAxialLengthQualityMetricSequence'],
- '0x1265': ['SQ', '1', 'OphthalmicAxialLengthQualityMetricTypeCodeSequence'],
- '0x1273': ['LO', '1', 'OphthalmicAxialLengthQualityMetricTypeDescription'],
- '0x1300': ['SQ', '1', 'IntraocularLensCalculationsRightEyeSequence'],
- '0x1310': ['SQ', '1', 'IntraocularLensCalculationsLeftEyeSequence'],
- '0x1330': ['SQ', '1', 'ReferencedOphthalmicAxialLengthMeasurementQCImageSequence'],
- '0x1415': ['CS', '1', 'OphthalmicMappingDeviceType'],
- '0x1420': ['SQ', '1', 'AcquisitionMethodCodeSequence'],
- '0x1423': ['SQ', '1', 'AcquisitionMethodAlgorithmSequence'],
- '0x1436': ['SQ', '1', 'OphthalmicThicknessMapTypeCodeSequence'],
- '0x1443': ['SQ', '1', 'OphthalmicThicknessMappingNormalsSequence'],
- '0x1445': ['SQ', '1', 'RetinalThicknessDefinitionCodeSequence'],
- '0x1450': ['SQ', '1', 'PixelValueMappingToCodedConceptSequence'],
- '0x1452': ['xs', '1', 'MappedPixelValue'],
- '0x1454': ['LO', '1', 'PixelValueMappingExplanation'],
- '0x1458': ['SQ', '1', 'OphthalmicThicknessMapQualityThresholdSequence'],
- '0x1460': ['FL', '1', 'OphthalmicThicknessMapThresholdQualityRating'],
- '0x1463': ['FL', '2', 'AnatomicStructureReferencePoint'],
- '0x1465': ['SQ', '1', 'RegistrationToLocalizerSequence'],
- '0x1466': ['CS', '1', 'RegisteredLocalizerUnits'],
- '0x1467': ['FL', '2', 'RegisteredLocalizerTopLeftHandCorner'],
- '0x1468': ['FL', '2', 'RegisteredLocalizerBottomRightHandCorner'],
- '0x1470': ['SQ', '1', 'OphthalmicThicknessMapQualityRatingSequence'],
- '0x1472': ['SQ', '1', 'RelevantOPTAttributesSequence'],
- '0x1512': ['SQ', '1', 'TransformationMethodCodeSequence'],
- '0x1513': ['SQ', '1', 'TransformationAlgorithmSequence'],
- '0x1515': ['CS', '1', 'OphthalmicAxialLengthMethod'],
- '0x1517': ['FL', '1', 'OphthalmicFOV'],
- '0x1518': ['SQ', '1', 'TwoDimensionalToThreeDimensionalMapSequence'],
- '0x1525': ['SQ', '1', 'WideFieldOphthalmicPhotographyQualityRatingSequence'],
- '0x1526': ['SQ', '1', 'WideFieldOphthalmicPhotographyQualityThresholdSequence'],
- '0x1527': ['FL', '1', 'WideFieldOphthalmicPhotographyThresholdQualityRating'],
- '0x1528': ['FL', '1', 'XCoordinatesCenterPixelViewAngle'],
- '0x1529': ['FL', '1', 'YCoordinatesCenterPixelViewAngle'],
- '0x1530': ['UL', '1', 'NumberOfMapPoints'],
- '0x1531': ['OF', '1', 'TwoDimensionalToThreeDimensionalMapData']
- },
- '0x0024': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['FL', '1', 'VisualFieldHorizontalExtent'],
- '0x0011': ['FL', '1', 'VisualFieldVerticalExtent'],
- '0x0012': ['CS', '1', 'VisualFieldShape'],
- '0x0016': ['SQ', '1', 'ScreeningTestModeCodeSequence'],
- '0x0018': ['FL', '1', 'MaximumStimulusLuminance'],
- '0x0020': ['FL', '1', 'BackgroundLuminance'],
- '0x0021': ['SQ', '1', 'StimulusColorCodeSequence'],
- '0x0024': ['SQ', '1', 'BackgroundIlluminationColorCodeSequence'],
- '0x0025': ['FL', '1', 'StimulusArea'],
- '0x0028': ['FL', '1', 'StimulusPresentationTime'],
- '0x0032': ['SQ', '1', 'FixationSequence'],
- '0x0033': ['SQ', '1', 'FixationMonitoringCodeSequence'],
- '0x0034': ['SQ', '1', 'VisualFieldCatchTrialSequence'],
- '0x0035': ['US', '1', 'FixationCheckedQuantity'],
- '0x0036': ['US', '1', 'PatientNotProperlyFixatedQuantity'],
- '0x0037': ['CS', '1', 'PresentedVisualStimuliDataFlag'],
- '0x0038': ['US', '1', 'NumberOfVisualStimuli'],
- '0x0039': ['CS', '1', 'ExcessiveFixationLossesDataFlag'],
- '0x0040': ['CS', '1', 'ExcessiveFixationLosses'],
- '0x0042': ['US', '1', 'StimuliRetestingQuantity'],
- '0x0044': ['LT', '1', 'CommentsOnPatientPerformanceOfVisualField'],
- '0x0045': ['CS', '1', 'FalseNegativesEstimateFlag'],
- '0x0046': ['FL', '1', 'FalseNegativesEstimate'],
- '0x0048': ['US', '1', 'NegativeCatchTrialsQuantity'],
- '0x0050': ['US', '1', 'FalseNegativesQuantity'],
- '0x0051': ['CS', '1', 'ExcessiveFalseNegativesDataFlag'],
- '0x0052': ['CS', '1', 'ExcessiveFalseNegatives'],
- '0x0053': ['CS', '1', 'FalsePositivesEstimateFlag'],
- '0x0054': ['FL', '1', 'FalsePositivesEstimate'],
- '0x0055': ['CS', '1', 'CatchTrialsDataFlag'],
- '0x0056': ['US', '1', 'PositiveCatchTrialsQuantity'],
- '0x0057': ['CS', '1', 'TestPointNormalsDataFlag'],
- '0x0058': ['SQ', '1', 'TestPointNormalsSequence'],
- '0x0059': ['CS', '1', 'GlobalDeviationProbabilityNormalsFlag'],
- '0x0060': ['US', '1', 'FalsePositivesQuantity'],
- '0x0061': ['CS', '1', 'ExcessiveFalsePositivesDataFlag'],
- '0x0062': ['CS', '1', 'ExcessiveFalsePositives'],
- '0x0063': ['CS', '1', 'VisualFieldTestNormalsFlag'],
- '0x0064': ['SQ', '1', 'ResultsNormalsSequence'],
- '0x0065': ['SQ', '1', 'AgeCorrectedSensitivityDeviationAlgorithmSequence'],
- '0x0066': ['FL', '1', 'GlobalDeviationFromNormal'],
- '0x0067': ['SQ', '1', 'GeneralizedDefectSensitivityDeviationAlgorithmSequence'],
- '0x0068': ['FL', '1', 'LocalizedDeviationFromNormal'],
- '0x0069': ['LO', '1', 'PatientReliabilityIndicator'],
- '0x0070': ['FL', '1', 'VisualFieldMeanSensitivity'],
- '0x0071': ['FL', '1', 'GlobalDeviationProbability'],
- '0x0072': ['CS', '1', 'LocalDeviationProbabilityNormalsFlag'],
- '0x0073': ['FL', '1', 'LocalizedDeviationProbability'],
- '0x0074': ['CS', '1', 'ShortTermFluctuationCalculated'],
- '0x0075': ['FL', '1', 'ShortTermFluctuation'],
- '0x0076': ['CS', '1', 'ShortTermFluctuationProbabilityCalculated'],
- '0x0077': ['FL', '1', 'ShortTermFluctuationProbability'],
- '0x0078': ['CS', '1', 'CorrectedLocalizedDeviationFromNormalCalculated'],
- '0x0079': ['FL', '1', 'CorrectedLocalizedDeviationFromNormal'],
- '0x0080': ['CS', '1', 'CorrectedLocalizedDeviationFromNormalProbabilityCalculated'],
- '0x0081': ['FL', '1', 'CorrectedLocalizedDeviationFromNormalProbability'],
- '0x0083': ['SQ', '1', 'GlobalDeviationProbabilitySequence'],
- '0x0085': ['SQ', '1', 'LocalizedDeviationProbabilitySequence'],
- '0x0086': ['CS', '1', 'FovealSensitivityMeasured'],
- '0x0087': ['FL', '1', 'FovealSensitivity'],
- '0x0088': ['FL', '1', 'VisualFieldTestDuration'],
- '0x0089': ['SQ', '1', 'VisualFieldTestPointSequence'],
- '0x0090': ['FL', '1', 'VisualFieldTestPointXCoordinate'],
- '0x0091': ['FL', '1', 'VisualFieldTestPointYCoordinate'],
- '0x0092': ['FL', '1', 'AgeCorrectedSensitivityDeviationValue'],
- '0x0093': ['CS', '1', 'StimulusResults'],
- '0x0094': ['FL', '1', 'SensitivityValue'],
- '0x0095': ['CS', '1', 'RetestStimulusSeen'],
- '0x0096': ['FL', '1', 'RetestSensitivityValue'],
- '0x0097': ['SQ', '1', 'VisualFieldTestPointNormalsSequence'],
- '0x0098': ['FL', '1', 'QuantifiedDefect'],
- '0x0100': ['FL', '1', 'AgeCorrectedSensitivityDeviationProbabilityValue'],
- '0x0102': ['CS', '1', 'GeneralizedDefectCorrectedSensitivityDeviationFlag'],
- '0x0103': ['FL', '1', 'GeneralizedDefectCorrectedSensitivityDeviationValue'],
- '0x0104': ['FL', '1', 'GeneralizedDefectCorrectedSensitivityDeviationProbabilityValue'],
- '0x0105': ['FL', '1', 'MinimumSensitivityValue'],
- '0x0106': ['CS', '1', 'BlindSpotLocalized'],
- '0x0107': ['FL', '1', 'BlindSpotXCoordinate'],
- '0x0108': ['FL', '1', 'BlindSpotYCoordinate'],
- '0x0110': ['SQ', '1', 'VisualAcuityMeasurementSequence'],
- '0x0112': ['SQ', '1', 'RefractiveParametersUsedOnPatientSequence'],
- '0x0113': ['CS', '1', 'MeasurementLaterality'],
- '0x0114': ['SQ', '1', 'OphthalmicPatientClinicalInformationLeftEyeSequence'],
- '0x0115': ['SQ', '1', 'OphthalmicPatientClinicalInformationRightEyeSequence'],
- '0x0117': ['CS', '1', 'FovealPointNormativeDataFlag'],
- '0x0118': ['FL', '1', 'FovealPointProbabilityValue'],
- '0x0120': ['CS', '1', 'ScreeningBaselineMeasured'],
- '0x0122': ['SQ', '1', 'ScreeningBaselineMeasuredSequence'],
- '0x0124': ['CS', '1', 'ScreeningBaselineType'],
- '0x0126': ['FL', '1', 'ScreeningBaselineValue'],
- '0x0202': ['LO', '1', 'AlgorithmSource'],
- '0x0306': ['LO', '1', 'DataSetName'],
- '0x0307': ['LO', '1', 'DataSetVersion'],
- '0x0308': ['LO', '1', 'DataSetSource'],
- '0x0309': ['LO', '1', 'DataSetDescription'],
- '0x0317': ['SQ', '1', 'VisualFieldTestReliabilityGlobalIndexSequence'],
- '0x0320': ['SQ', '1', 'VisualFieldGlobalResultsIndexSequence'],
- '0x0325': ['SQ', '1', 'DataObservationSequence'],
- '0x0338': ['CS', '1', 'IndexNormalsFlag'],
- '0x0341': ['FL', '1', 'IndexProbability'],
- '0x0344': ['SQ', '1', 'IndexProbabilitySequence']
- },
- '0x0028': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['US', '1', 'SamplesPerPixel'],
- '0x0003': ['US', '1', 'SamplesPerPixelUsed'],
- '0x0004': ['CS', '1', 'PhotometricInterpretation'],
- '0x0005': ['US', '1', 'ImageDimensions'],
- '0x0006': ['US', '1', 'PlanarConfiguration'],
- '0x0008': ['IS', '1', 'NumberOfFrames'],
- '0x0009': ['AT', '1-n', 'FrameIncrementPointer'],
- '0x000A': ['AT', '1-n', 'FrameDimensionPointer'],
- '0x0010': ['US', '1', 'Rows'],
- '0x0011': ['US', '1', 'Columns'],
- '0x0012': ['US', '1', 'Planes'],
- '0x0014': ['US', '1', 'UltrasoundColorDataPresent'],
- '0x0020': ['', '', ''],
- '0x0030': ['DS', '2', 'PixelSpacing'],
- '0x0031': ['DS', '2', 'ZoomFactor'],
- '0x0032': ['DS', '2', 'ZoomCenter'],
- '0x0034': ['IS', '2', 'PixelAspectRatio'],
- '0x0040': ['CS', '1', 'ImageFormat'],
- '0x0050': ['LO', '1-n', 'ManipulatedImage'],
- '0x0051': ['CS', '1-n', 'CorrectedImage'],
- '0x005F': ['LO', '1', 'CompressionRecognitionCode'],
- '0x0060': ['CS', '1', 'CompressionCode'],
- '0x0061': ['SH', '1', 'CompressionOriginator'],
- '0x0062': ['LO', '1', 'CompressionLabel'],
- '0x0063': ['SH', '1', 'CompressionDescription'],
- '0x0065': ['CS', '1-n', 'CompressionSequence'],
- '0x0066': ['AT', '1-n', 'CompressionStepPointers'],
- '0x0068': ['US', '1', 'RepeatInterval'],
- '0x0069': ['US', '1', 'BitsGrouped'],
- '0x0070': ['US', '1-n', 'PerimeterTable'],
- '0x0071': ['xs', '1', 'PerimeterValue'],
- '0x0080': ['US', '1', 'PredictorRows'],
- '0x0081': ['US', '1', 'PredictorColumns'],
- '0x0082': ['US', '1-n', 'PredictorConstants'],
- '0x0090': ['CS', '1', 'BlockedPixels'],
- '0x0091': ['US', '1', 'BlockRows'],
- '0x0092': ['US', '1', 'BlockColumns'],
- '0x0093': ['US', '1', 'RowOverlap'],
- '0x0094': ['US', '1', 'ColumnOverlap'],
- '0x0100': ['US', '1', 'BitsAllocated'],
- '0x0101': ['US', '1', 'BitsStored'],
- '0x0102': ['US', '1', 'HighBit'],
- '0x0103': ['US', '1', 'PixelRepresentation'],
- '0x0104': ['xs', '1', 'SmallestValidPixelValue'],
- '0x0105': ['xs', '1', 'LargestValidPixelValue'],
- '0x0106': ['xs', '1', 'SmallestImagePixelValue'],
- '0x0107': ['xs', '1', 'LargestImagePixelValue'],
- '0x0108': ['xs', '1', 'SmallestPixelValueInSeries'],
- '0x0109': ['xs', '1', 'LargestPixelValueInSeries'],
- '0x0110': ['xs', '1', 'SmallestImagePixelValueInPlane'],
- '0x0111': ['xs', '1', 'LargestImagePixelValueInPlane'],
- '0x0120': ['xs', '1', 'PixelPaddingValue'],
- '0x0121': ['xs', '1', 'PixelPaddingRangeLimit'],
- '0x0122': ['FL', '1', 'FloatPixelPaddingValue'],
- '0x0123': ['FD', '1', 'DoubleFloatPixelPaddingValue'],
- '0x0124': ['FL', '1', 'FloatPixelPaddingRangeLimit'],
- '0x0125': ['FD', '1', 'DoubleFloatPixelPaddingRangeLimit'],
- '0x0200': ['US', '1', 'ImageLocation'],
- '0x0300': ['CS', '1', 'QualityControlImage'],
- '0x0301': ['CS', '1', 'BurnedInAnnotation'],
- '0x0302': ['CS', '1', 'RecognizableVisualFeatures'],
- '0x0303': ['CS', '1', 'LongitudinalTemporalInformationModified'],
- '0x0304': ['UI', '1', 'ReferencedColorPaletteInstanceUID'],
- '0x0400': ['LO', '1', 'TransformLabel'],
- '0x0401': ['LO', '1', 'TransformVersionNumber'],
- '0x0402': ['US', '1', 'NumberOfTransformSteps'],
- '0x0403': ['LO', '1-n', 'SequenceOfCompressedData'],
- '0x0404': ['AT', '1-n', 'DetailsOfCoefficients'],
- '0x04x0': ['US', '1', 'RowsForNthOrderCoefficients'],
- '0x04x1': ['US', '1', 'ColumnsForNthOrderCoefficients'],
- '0x04x2': ['LO', '1-n', 'CoefficientCoding'],
- '0x04x3': ['AT', '1-n', 'CoefficientCodingPointers'],
- '0x0700': ['LO', '1', 'DCTLabel'],
- '0x0701': ['CS', '1-n', 'DataBlockDescription'],
- '0x0702': ['AT', '1-n', 'DataBlock'],
- '0x0710': ['US', '1', 'NormalizationFactorFormat'],
- '0x0720': ['US', '1', 'ZonalMapNumberFormat'],
- '0x0721': ['AT', '1-n', 'ZonalMapLocation'],
- '0x0722': ['US', '1', 'ZonalMapFormat'],
- '0x0730': ['US', '1', 'AdaptiveMapFormat'],
- '0x0740': ['US', '1', 'CodeNumberFormat'],
- '0x08x0': ['CS', '1-n', 'CodeLabel'],
- '0x08x2': ['US', '1', 'NumberOfTables'],
- '0x08x3': ['AT', '1-n', 'CodeTableLocation'],
- '0x08x4': ['US', '1', 'BitsForCodeWord'],
- '0x08x8': ['AT', '1-n', 'ImageDataLocation'],
- '0x0A02': ['CS', '1', 'PixelSpacingCalibrationType'],
- '0x0A04': ['LO', '1', 'PixelSpacingCalibrationDescription'],
- '0x1040': ['CS', '1', 'PixelIntensityRelationship'],
- '0x1041': ['SS', '1', 'PixelIntensityRelationshipSign'],
- '0x1050': ['DS', '1-n', 'WindowCenter'],
- '0x1051': ['DS', '1-n', 'WindowWidth'],
- '0x1052': ['DS', '1', 'RescaleIntercept'],
- '0x1053': ['DS', '1', 'RescaleSlope'],
- '0x1054': ['LO', '1', 'RescaleType'],
- '0x1055': ['LO', '1-n', 'WindowCenterWidthExplanation'],
- '0x1056': ['CS', '1', 'VOILUTFunction'],
- '0x1080': ['CS', '1', 'GrayScale'],
- '0x1090': ['CS', '1', 'RecommendedViewingMode'],
- '0x1100': ['xs', '3', 'GrayLookupTableDescriptor'],
- '0x1101': ['xs', '3', 'RedPaletteColorLookupTableDescriptor'],
- '0x1102': ['xs', '3', 'GreenPaletteColorLookupTableDescriptor'],
- '0x1103': ['xs', '3', 'BluePaletteColorLookupTableDescriptor'],
- '0x1104': ['US', '3', 'AlphaPaletteColorLookupTableDescriptor'],
- '0x1111': ['xs', '4', 'LargeRedPaletteColorLookupTableDescriptor'],
- '0x1112': ['xs', '4', 'LargeGreenPaletteColorLookupTableDescriptor'],
- '0x1113': ['xs', '4', 'LargeBluePaletteColorLookupTableDescriptor'],
- '0x1199': ['UI', '1', 'PaletteColorLookupTableUID'],
- '0x1200': ['US or SS or OW', '1-n or 1', 'GrayLookupTableData'],
- '0x1201': ['OW', '1', 'RedPaletteColorLookupTableData'],
- '0x1202': ['OW', '1', 'GreenPaletteColorLookupTableData'],
- '0x1203': ['OW', '1', 'BluePaletteColorLookupTableData'],
- '0x1204': ['OW', '1', 'AlphaPaletteColorLookupTableData'],
- '0x1211': ['OW', '1', 'LargeRedPaletteColorLookupTableData'],
- '0x1212': ['OW', '1', 'LargeGreenPaletteColorLookupTableData'],
- '0x1213': ['OW', '1', 'LargeBluePaletteColorLookupTableData'],
- '0x1214': ['UI', '1', 'LargePaletteColorLookupTableUID'],
- '0x1221': ['OW', '1', 'SegmentedRedPaletteColorLookupTableData'],
- '0x1222': ['OW', '1', 'SegmentedGreenPaletteColorLookupTableData'],
- '0x1223': ['OW', '1', 'SegmentedBluePaletteColorLookupTableData'],
- '0x1300': ['CS', '1', 'BreastImplantPresent'],
- '0x1350': ['CS', '1', 'PartialView'],
- '0x1351': ['ST', '1', 'PartialViewDescription'],
- '0x1352': ['SQ', '1', 'PartialViewCodeSequence'],
- '0x135A': ['CS', '1', 'SpatialLocationsPreserved'],
- '0x1401': ['SQ', '1', 'DataFrameAssignmentSequence'],
- '0x1402': ['CS', '1', 'DataPathAssignment'],
- '0x1403': ['US', '1', 'BitsMappedToColorLookupTable'],
- '0x1404': ['SQ', '1', 'BlendingLUT1Sequence'],
- '0x1405': ['CS', '1', 'BlendingLUT1TransferFunction'],
- '0x1406': ['FD', '1', 'BlendingWeightConstant'],
- '0x1407': ['US', '3', 'BlendingLookupTableDescriptor'],
- '0x1408': ['OW', '1', 'BlendingLookupTableData'],
- '0x140B': ['SQ', '1', 'EnhancedPaletteColorLookupTableSequence'],
- '0x140C': ['SQ', '1', 'BlendingLUT2Sequence'],
- '0x140D': ['CS', '1', 'BlendingLUT2TransferFunction'],
- '0x140E': ['CS', '1', 'DataPathID'],
- '0x140F': ['CS', '1', 'RGBLUTTransferFunction'],
- '0x1410': ['CS', '1', 'AlphaLUTTransferFunction'],
- '0x2000': ['OB', '1', 'ICCProfile'],
- '0x2110': ['CS', '1', 'LossyImageCompression'],
- '0x2112': ['DS', '1-n', 'LossyImageCompressionRatio'],
- '0x2114': ['CS', '1-n', 'LossyImageCompressionMethod'],
- '0x3000': ['SQ', '1', 'ModalityLUTSequence'],
- '0x3002': ['xs', '3', 'LUTDescriptor'],
- '0x3003': ['LO', '1', 'LUTExplanation'],
- '0x3004': ['LO', '1', 'ModalityLUTType'],
- '0x3006': ['US or OW', '1-n or 1', 'LUTData'],
- '0x3010': ['SQ', '1', 'VOILUTSequence'],
- '0x3110': ['SQ', '1', 'SoftcopyVOILUTSequence'],
- '0x4000': ['LT', '1', 'ImagePresentationComments'],
- '0x5000': ['SQ', '1', 'BiPlaneAcquisitionSequence'],
- '0x6010': ['US', '1', 'RepresentativeFrameNumber'],
- '0x6020': ['US', '1-n', 'FrameNumbersOfInterest'],
- '0x6022': ['LO', '1-n', 'FrameOfInterestDescription'],
- '0x6023': ['CS', '1-n', 'FrameOfInterestType'],
- '0x6030': ['US', '1-n', 'MaskPointers'],
- '0x6040': ['US', '1-n', 'RWavePointer'],
- '0x6100': ['SQ', '1', 'MaskSubtractionSequence'],
- '0x6101': ['CS', '1', 'MaskOperation'],
- '0x6102': ['US', '2-2n', 'ApplicableFrameRange'],
- '0x6110': ['US', '1-n', 'MaskFrameNumbers'],
- '0x6112': ['US', '1', 'ContrastFrameAveraging'],
- '0x6114': ['FL', '2', 'MaskSubPixelShift'],
- '0x6120': ['SS', '1', 'TIDOffset'],
- '0x6190': ['ST', '1', 'MaskOperationExplanation'],
- '0x7000': ['SQ', '1', 'EquipmentAdministratorSequence'],
- '0x7001': ['US', '1', 'NumberOfDisplaySubsystems'],
- '0x7002': ['US', '1', 'CurrentConfigurationID'],
- '0x7003': ['US', '1', 'DisplaySubsystemID'],
- '0x7004': ['SH', '1', 'DisplaySubsystemName'],
- '0x7005': ['LO', '1', 'DisplaySubsystemDescription'],
- '0x7006': ['CS', '1', 'SystemStatus'],
- '0x7007': ['LO', '1', 'SystemStatusComment'],
- '0x7008': ['SQ', '1', 'TargetLuminanceCharacteristicsSequence'],
- '0x7009': ['US', '1', 'LuminanceCharacteristicsID'],
- '0x700A': ['SQ', '1', 'DisplaySubsystemConfigurationSequence'],
- '0x700B': ['US', '1', 'ConfigurationID'],
- '0x700C': ['SH', '1', 'ConfigurationName'],
- '0x700D': ['LO', '1', 'ConfigurationDescription'],
- '0x700E': ['US', '1', 'ReferencedTargetLuminanceCharacteristicsID'],
- '0x700F': ['SQ', '1', 'QAResultsSequence'],
- '0x7010': ['SQ', '1', 'DisplaySubsystemQAResultsSequence'],
- '0x7011': ['SQ', '1', 'ConfigurationQAResultsSequence'],
- '0x7012': ['SQ', '1', 'MeasurementEquipmentSequence'],
- '0x7013': ['CS', '1-n', 'MeasurementFunctions'],
- '0x7014': ['CS', '1', 'MeasurementEquipmentType'],
- '0x7015': ['SQ', '1', 'VisualEvaluationResultSequence'],
- '0x7016': ['SQ', '1', 'DisplayCalibrationResultSequence'],
- '0x7017': ['US', '1', 'DDLValue'],
- '0x7018': ['FL', '2', 'CIExyWhitePoint'],
- '0x7019': ['CS', '1', 'DisplayFunctionType'],
- '0x701A': ['FL', '1', 'GammaValue'],
- '0x701B': ['US', '1', 'NumberOfLuminancePoints'],
- '0x701C': ['SQ', '1', 'LuminanceResponseSequence'],
- '0x701D': ['FL', '1', 'TargetMinimumLuminance'],
- '0x701E': ['FL', '1', 'TargetMaximumLuminance'],
- '0x701F': ['FL', '1', 'LuminanceValue'],
- '0x7020': ['LO', '1', 'LuminanceResponseDescription'],
- '0x7021': ['CS', '1', 'WhitePointFlag'],
- '0x7022': ['SQ', '1', 'DisplayDeviceTypeCodeSequence'],
- '0x7023': ['SQ', '1', 'DisplaySubsystemSequence'],
- '0x7024': ['SQ', '1', 'LuminanceResultSequence'],
- '0x7025': ['CS', '1', 'AmbientLightValueSource'],
- '0x7026': ['CS', '1-n', 'MeasuredCharacteristics'],
- '0x7027': ['SQ', '1', 'LuminanceUniformityResultSequence'],
- '0x7028': ['SQ', '1', 'VisualEvaluationTestSequence'],
- '0x7029': ['CS', '1', 'TestResult'],
- '0x702A': ['LO', '1', 'TestResultComment'],
- '0x702B': ['CS', '1', 'TestImageValidation'],
- '0x702C': ['SQ', '1', 'TestPatternCodeSequence'],
- '0x702D': ['SQ', '1', 'MeasurementPatternCodeSequence'],
- '0x702E': ['SQ', '1', 'VisualEvaluationMethodCodeSequence'],
- '0x7FE0': ['UR', '1', 'PixelDataProviderURL'],
- '0x9001': ['UL', '1', 'DataPointRows'],
- '0x9002': ['UL', '1', 'DataPointColumns'],
- '0x9003': ['CS', '1', 'SignalDomainColumns'],
- '0x9099': ['US', '1', 'LargestMonochromePixelValue'],
- '0x9108': ['CS', '1', 'DataRepresentation'],
- '0x9110': ['SQ', '1', 'PixelMeasuresSequence'],
- '0x9132': ['SQ', '1', 'FrameVOILUTSequence'],
- '0x9145': ['SQ', '1', 'PixelValueTransformationSequence'],
- '0x9235': ['CS', '1', 'SignalDomainRows'],
- '0x9411': ['FL', '1', 'DisplayFilterPercentage'],
- '0x9415': ['SQ', '1', 'FramePixelShiftSequence'],
- '0x9416': ['US', '1', 'SubtractionItemID'],
- '0x9422': ['SQ', '1', 'PixelIntensityRelationshipLUTSequence'],
- '0x9443': ['SQ', '1', 'FramePixelDataPropertiesSequence'],
- '0x9444': ['CS', '1', 'GeometricalProperties'],
- '0x9445': ['FL', '1', 'GeometricMaximumDistortion'],
- '0x9446': ['CS', '1-n', 'ImageProcessingApplied'],
- '0x9454': ['CS', '1', 'MaskSelectionMode'],
- '0x9474': ['CS', '1', 'LUTFunction'],
- '0x9478': ['FL', '1', 'MaskVisibilityPercentage'],
- '0x9501': ['SQ', '1', 'PixelShiftSequence'],
- '0x9502': ['SQ', '1', 'RegionPixelShiftSequence'],
- '0x9503': ['SS', '2-2n', 'VerticesOfTheRegion'],
- '0x9505': ['SQ', '1', 'MultiFramePresentationSequence'],
- '0x9506': ['US', '2-2n', 'PixelShiftFrameRange'],
- '0x9507': ['US', '2-2n', 'LUTFrameRange'],
- '0x9520': ['DS', '16', 'ImageToEquipmentMappingMatrix'],
- '0x9537': ['CS', '1', 'EquipmentCoordinateSystemIdentification']
- },
- '0x0032': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x000A': ['CS', '1', 'StudyStatusID'],
- '0x000C': ['CS', '1', 'StudyPriorityID'],
- '0x0012': ['LO', '1', 'StudyIDIssuer'],
- '0x0032': ['DA', '1', 'StudyVerifiedDate'],
- '0x0033': ['TM', '1', 'StudyVerifiedTime'],
- '0x0034': ['DA', '1', 'StudyReadDate'],
- '0x0035': ['TM', '1', 'StudyReadTime'],
- '0x1000': ['DA', '1', 'ScheduledStudyStartDate'],
- '0x1001': ['TM', '1', 'ScheduledStudyStartTime'],
- '0x1010': ['DA', '1', 'ScheduledStudyStopDate'],
- '0x1011': ['TM', '1', 'ScheduledStudyStopTime'],
- '0x1020': ['LO', '1', 'ScheduledStudyLocation'],
- '0x1021': ['AE', '1-n', 'ScheduledStudyLocationAETitle'],
- '0x1030': ['LO', '1', 'ReasonForStudy'],
- '0x1031': ['SQ', '1', 'RequestingPhysicianIdentificationSequence'],
- '0x1032': ['PN', '1', 'RequestingPhysician'],
- '0x1033': ['LO', '1', 'RequestingService'],
- '0x1034': ['SQ', '1', 'RequestingServiceCodeSequence'],
- '0x1040': ['DA', '1', 'StudyArrivalDate'],
- '0x1041': ['TM', '1', 'StudyArrivalTime'],
- '0x1050': ['DA', '1', 'StudyCompletionDate'],
- '0x1051': ['TM', '1', 'StudyCompletionTime'],
- '0x1055': ['CS', '1', 'StudyComponentStatusID'],
- '0x1060': ['LO', '1', 'RequestedProcedureDescription'],
- '0x1064': ['SQ', '1', 'RequestedProcedureCodeSequence'],
- '0x1070': ['LO', '1', 'RequestedContrastAgent'],
- '0x4000': ['LT', '1', 'StudyComments']
- },
- '0x0038': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0004': ['SQ', '1', 'ReferencedPatientAliasSequence'],
- '0x0008': ['CS', '1', 'VisitStatusID'],
- '0x0010': ['LO', '1', 'AdmissionID'],
- '0x0011': ['LO', '1', 'IssuerOfAdmissionID'],
- '0x0014': ['SQ', '1', 'IssuerOfAdmissionIDSequence'],
- '0x0016': ['LO', '1', 'RouteOfAdmissions'],
- '0x001A': ['DA', '1', 'ScheduledAdmissionDate'],
- '0x001B': ['TM', '1', 'ScheduledAdmissionTime'],
- '0x001C': ['DA', '1', 'ScheduledDischargeDate'],
- '0x001D': ['TM', '1', 'ScheduledDischargeTime'],
- '0x001E': ['LO', '1', 'ScheduledPatientInstitutionResidence'],
- '0x0020': ['DA', '1', 'AdmittingDate'],
- '0x0021': ['TM', '1', 'AdmittingTime'],
- '0x0030': ['DA', '1', 'DischargeDate'],
- '0x0032': ['TM', '1', 'DischargeTime'],
- '0x0040': ['LO', '1', 'DischargeDiagnosisDescription'],
- '0x0044': ['SQ', '1', 'DischargeDiagnosisCodeSequence'],
- '0x0050': ['LO', '1', 'SpecialNeeds'],
- '0x0060': ['LO', '1', 'ServiceEpisodeID'],
- '0x0061': ['LO', '1', 'IssuerOfServiceEpisodeID'],
- '0x0062': ['LO', '1', 'ServiceEpisodeDescription'],
- '0x0064': ['SQ', '1', 'IssuerOfServiceEpisodeIDSequence'],
- '0x0100': ['SQ', '1', 'PertinentDocumentsSequence'],
- '0x0101': ['SQ', '1', 'PertinentResourcesSequence'],
- '0x0102': ['LO', '1', 'ResourceDescription'],
- '0x0300': ['LO', '1', 'CurrentPatientLocation'],
- '0x0400': ['LO', '1', 'PatientInstitutionResidence'],
- '0x0500': ['LO', '1', 'PatientState'],
- '0x0502': ['SQ', '1', 'PatientClinicalTrialParticipationSequence'],
- '0x4000': ['LT', '1', 'VisitComments']
- },
- '0x003A': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0004': ['CS', '1', 'WaveformOriginality'],
- '0x0005': ['US', '1', 'NumberOfWaveformChannels'],
- '0x0010': ['UL', '1', 'NumberOfWaveformSamples'],
- '0x001A': ['DS', '1', 'SamplingFrequency'],
- '0x0020': ['SH', '1', 'MultiplexGroupLabel'],
- '0x0200': ['SQ', '1', 'ChannelDefinitionSequence'],
- '0x0202': ['IS', '1', 'WaveformChannelNumber'],
- '0x0203': ['SH', '1', 'ChannelLabel'],
- '0x0205': ['CS', '1-n', 'ChannelStatus'],
- '0x0208': ['SQ', '1', 'ChannelSourceSequence'],
- '0x0209': ['SQ', '1', 'ChannelSourceModifiersSequence'],
- '0x020A': ['SQ', '1', 'SourceWaveformSequence'],
- '0x020C': ['LO', '1', 'ChannelDerivationDescription'],
- '0x0210': ['DS', '1', 'ChannelSensitivity'],
- '0x0211': ['SQ', '1', 'ChannelSensitivityUnitsSequence'],
- '0x0212': ['DS', '1', 'ChannelSensitivityCorrectionFactor'],
- '0x0213': ['DS', '1', 'ChannelBaseline'],
- '0x0214': ['DS', '1', 'ChannelTimeSkew'],
- '0x0215': ['DS', '1', 'ChannelSampleSkew'],
- '0x0218': ['DS', '1', 'ChannelOffset'],
- '0x021A': ['US', '1', 'WaveformBitsStored'],
- '0x0220': ['DS', '1', 'FilterLowFrequency'],
- '0x0221': ['DS', '1', 'FilterHighFrequency'],
- '0x0222': ['DS', '1', 'NotchFilterFrequency'],
- '0x0223': ['DS', '1', 'NotchFilterBandwidth'],
- '0x0230': ['FL', '1', 'WaveformDataDisplayScale'],
- '0x0231': ['US', '3', 'WaveformDisplayBackgroundCIELabValue'],
- '0x0240': ['SQ', '1', 'WaveformPresentationGroupSequence'],
- '0x0241': ['US', '1', 'PresentationGroupNumber'],
- '0x0242': ['SQ', '1', 'ChannelDisplaySequence'],
- '0x0244': ['US', '3', 'ChannelRecommendedDisplayCIELabValue'],
- '0x0245': ['FL', '1', 'ChannelPosition'],
- '0x0246': ['CS', '1', 'DisplayShadingFlag'],
- '0x0247': ['FL', '1', 'FractionalChannelDisplayScale'],
- '0x0248': ['FL', '1', 'AbsoluteChannelDisplayScale'],
- '0x0300': ['SQ', '1', 'MultiplexedAudioChannelsDescriptionCodeSequence'],
- '0x0301': ['IS', '1', 'ChannelIdentificationCode'],
- '0x0302': ['CS', '1', 'ChannelMode']
- },
- '0x0040': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['AE', '1-n', 'ScheduledStationAETitle'],
- '0x0002': ['DA', '1', 'ScheduledProcedureStepStartDate'],
- '0x0003': ['TM', '1', 'ScheduledProcedureStepStartTime'],
- '0x0004': ['DA', '1', 'ScheduledProcedureStepEndDate'],
- '0x0005': ['TM', '1', 'ScheduledProcedureStepEndTime'],
- '0x0006': ['PN', '1', 'ScheduledPerformingPhysicianName'],
- '0x0007': ['LO', '1', 'ScheduledProcedureStepDescription'],
- '0x0008': ['SQ', '1', 'ScheduledProtocolCodeSequence'],
- '0x0009': ['SH', '1', 'ScheduledProcedureStepID'],
- '0x000A': ['SQ', '1', 'StageCodeSequence'],
- '0x000B': ['SQ', '1', 'ScheduledPerformingPhysicianIdentificationSequence'],
- '0x0010': ['SH', '1-n', 'ScheduledStationName'],
- '0x0011': ['SH', '1', 'ScheduledProcedureStepLocation'],
- '0x0012': ['LO', '1', 'PreMedication'],
- '0x0020': ['CS', '1', 'ScheduledProcedureStepStatus'],
- '0x0026': ['SQ', '1', 'OrderPlacerIdentifierSequence'],
- '0x0027': ['SQ', '1', 'OrderFillerIdentifierSequence'],
- '0x0031': ['UT', '1', 'LocalNamespaceEntityID'],
- '0x0032': ['UT', '1', 'UniversalEntityID'],
- '0x0033': ['CS', '1', 'UniversalEntityIDType'],
- '0x0035': ['CS', '1', 'IdentifierTypeCode'],
- '0x0036': ['SQ', '1', 'AssigningFacilitySequence'],
- '0x0039': ['SQ', '1', 'AssigningJurisdictionCodeSequence'],
- '0x003A': ['SQ', '1', 'AssigningAgencyOrDepartmentCodeSequence'],
- '0x0100': ['SQ', '1', 'ScheduledProcedureStepSequence'],
- '0x0220': ['SQ', '1', 'ReferencedNonImageCompositeSOPInstanceSequence'],
- '0x0241': ['AE', '1', 'PerformedStationAETitle'],
- '0x0242': ['SH', '1', 'PerformedStationName'],
- '0x0243': ['SH', '1', 'PerformedLocation'],
- '0x0244': ['DA', '1', 'PerformedProcedureStepStartDate'],
- '0x0245': ['TM', '1', 'PerformedProcedureStepStartTime'],
- '0x0250': ['DA', '1', 'PerformedProcedureStepEndDate'],
- '0x0251': ['TM', '1', 'PerformedProcedureStepEndTime'],
- '0x0252': ['CS', '1', 'PerformedProcedureStepStatus'],
- '0x0253': ['SH', '1', 'PerformedProcedureStepID'],
- '0x0254': ['LO', '1', 'PerformedProcedureStepDescription'],
- '0x0255': ['LO', '1', 'PerformedProcedureTypeDescription'],
- '0x0260': ['SQ', '1', 'PerformedProtocolCodeSequence'],
- '0x0261': ['CS', '1', 'PerformedProtocolType'],
- '0x0270': ['SQ', '1', 'ScheduledStepAttributesSequence'],
- '0x0275': ['SQ', '1', 'RequestAttributesSequence'],
- '0x0280': ['ST', '1', 'CommentsOnThePerformedProcedureStep'],
- '0x0281': ['SQ', '1', 'PerformedProcedureStepDiscontinuationReasonCodeSequence'],
- '0x0293': ['SQ', '1', 'QuantitySequence'],
- '0x0294': ['DS', '1', 'Quantity'],
- '0x0295': ['SQ', '1', 'MeasuringUnitsSequence'],
- '0x0296': ['SQ', '1', 'BillingItemSequence'],
- '0x0300': ['US', '1', 'TotalTimeOfFluoroscopy'],
- '0x0301': ['US', '1', 'TotalNumberOfExposures'],
- '0x0302': ['US', '1', 'EntranceDose'],
- '0x0303': ['US', '1-2', 'ExposedArea'],
- '0x0306': ['DS', '1', 'DistanceSourceToEntrance'],
- '0x0307': ['DS', '1', 'DistanceSourceToSupport'],
- '0x030E': ['SQ', '1', 'ExposureDoseSequence'],
- '0x0310': ['ST', '1', 'CommentsOnRadiationDose'],
- '0x0312': ['DS', '1', 'XRayOutput'],
- '0x0314': ['DS', '1', 'HalfValueLayer'],
- '0x0316': ['DS', '1', 'OrganDose'],
- '0x0318': ['CS', '1', 'OrganExposed'],
- '0x0320': ['SQ', '1', 'BillingProcedureStepSequence'],
- '0x0321': ['SQ', '1', 'FilmConsumptionSequence'],
- '0x0324': ['SQ', '1', 'BillingSuppliesAndDevicesSequence'],
- '0x0330': ['SQ', '1', 'ReferencedProcedureStepSequence'],
- '0x0340': ['SQ', '1', 'PerformedSeriesSequence'],
- '0x0400': ['LT', '1', 'CommentsOnTheScheduledProcedureStep'],
- '0x0440': ['SQ', '1', 'ProtocolContextSequence'],
- '0x0441': ['SQ', '1', 'ContentItemModifierSequence'],
- '0x0500': ['SQ', '1', 'ScheduledSpecimenSequence'],
- '0x050A': ['LO', '1', 'SpecimenAccessionNumber'],
- '0x0512': ['LO', '1', 'ContainerIdentifier'],
- '0x0513': ['SQ', '1', 'IssuerOfTheContainerIdentifierSequence'],
- '0x0515': ['SQ', '1', 'AlternateContainerIdentifierSequence'],
- '0x0518': ['SQ', '1', 'ContainerTypeCodeSequence'],
- '0x051A': ['LO', '1', 'ContainerDescription'],
- '0x0520': ['SQ', '1', 'ContainerComponentSequence'],
- '0x0550': ['SQ', '1', 'SpecimenSequence'],
- '0x0551': ['LO', '1', 'SpecimenIdentifier'],
- '0x0552': ['SQ', '1', 'SpecimenDescriptionSequenceTrial'],
- '0x0553': ['ST', '1', 'SpecimenDescriptionTrial'],
- '0x0554': ['UI', '1', 'SpecimenUID'],
- '0x0555': ['SQ', '1', 'AcquisitionContextSequence'],
- '0x0556': ['ST', '1', 'AcquisitionContextDescription'],
- '0x059A': ['SQ', '1', 'SpecimenTypeCodeSequence'],
- '0x0560': ['SQ', '1', 'SpecimenDescriptionSequence'],
- '0x0562': ['SQ', '1', 'IssuerOfTheSpecimenIdentifierSequence'],
- '0x0600': ['LO', '1', 'SpecimenShortDescription'],
- '0x0602': ['UT', '1', 'SpecimenDetailedDescription'],
- '0x0610': ['SQ', '1', 'SpecimenPreparationSequence'],
- '0x0612': ['SQ', '1', 'SpecimenPreparationStepContentItemSequence'],
- '0x0620': ['SQ', '1', 'SpecimenLocalizationContentItemSequence'],
- '0x06FA': ['LO', '1', 'SlideIdentifier'],
- '0x071A': ['SQ', '1', 'ImageCenterPointCoordinatesSequence'],
- '0x072A': ['DS', '1', 'XOffsetInSlideCoordinateSystem'],
- '0x073A': ['DS', '1', 'YOffsetInSlideCoordinateSystem'],
- '0x074A': ['DS', '1', 'ZOffsetInSlideCoordinateSystem'],
- '0x08D8': ['SQ', '1', 'PixelSpacingSequence'],
- '0x08DA': ['SQ', '1', 'CoordinateSystemAxisCodeSequence'],
- '0x08EA': ['SQ', '1', 'MeasurementUnitsCodeSequence'],
- '0x09F8': ['SQ', '1', 'VitalStainCodeSequenceTrial'],
- '0x1001': ['SH', '1', 'RequestedProcedureID'],
- '0x1002': ['LO', '1', 'ReasonForTheRequestedProcedure'],
- '0x1003': ['SH', '1', 'RequestedProcedurePriority'],
- '0x1004': ['LO', '1', 'PatientTransportArrangements'],
- '0x1005': ['LO', '1', 'RequestedProcedureLocation'],
- '0x1006': ['SH', '1', 'PlacerOrderNumberProcedure'],
- '0x1007': ['SH', '1', 'FillerOrderNumberProcedure'],
- '0x1008': ['LO', '1', 'ConfidentialityCode'],
- '0x1009': ['SH', '1', 'ReportingPriority'],
- '0x100A': ['SQ', '1', 'ReasonForRequestedProcedureCodeSequence'],
- '0x1010': ['PN', '1-n', 'NamesOfIntendedRecipientsOfResults'],
- '0x1011': ['SQ', '1', 'IntendedRecipientsOfResultsIdentificationSequence'],
- '0x1012': ['SQ', '1', 'ReasonForPerformedProcedureCodeSequence'],
- '0x1060': ['LO', '1', 'RequestedProcedureDescriptionTrial'],
- '0x1101': ['SQ', '1', 'PersonIdentificationCodeSequence'],
- '0x1102': ['ST', '1', 'PersonAddress'],
- '0x1103': ['LO', '1-n', 'PersonTelephoneNumbers'],
- '0x1104': ['LT', '1', 'PersonTelecomInformation'],
- '0x1400': ['LT', '1', 'RequestedProcedureComments'],
- '0x2001': ['LO', '1', 'ReasonForTheImagingServiceRequest'],
- '0x2004': ['DA', '1', 'IssueDateOfImagingServiceRequest'],
- '0x2005': ['TM', '1', 'IssueTimeOfImagingServiceRequest'],
- '0x2006': ['SH', '1', 'PlacerOrderNumberImagingServiceRequestRetired'],
- '0x2007': ['SH', '1', 'FillerOrderNumberImagingServiceRequestRetired'],
- '0x2008': ['PN', '1', 'OrderEnteredBy'],
- '0x2009': ['SH', '1', 'OrderEntererLocation'],
- '0x2010': ['SH', '1', 'OrderCallbackPhoneNumber'],
- '0x2011': ['LT', '1', 'OrderCallbackTelecomInformation'],
- '0x2016': ['LO', '1', 'PlacerOrderNumberImagingServiceRequest'],
- '0x2017': ['LO', '1', 'FillerOrderNumberImagingServiceRequest'],
- '0x2400': ['LT', '1', 'ImagingServiceRequestComments'],
- '0x3001': ['LO', '1', 'ConfidentialityConstraintOnPatientDataDescription'],
- '0x4001': ['CS', '1', 'GeneralPurposeScheduledProcedureStepStatus'],
- '0x4002': ['CS', '1', 'GeneralPurposePerformedProcedureStepStatus'],
- '0x4003': ['CS', '1', 'GeneralPurposeScheduledProcedureStepPriority'],
- '0x4004': ['SQ', '1', 'ScheduledProcessingApplicationsCodeSequence'],
- '0x4005': ['DT', '1', 'ScheduledProcedureStepStartDateTime'],
- '0x4006': ['CS', '1', 'MultipleCopiesFlag'],
- '0x4007': ['SQ', '1', 'PerformedProcessingApplicationsCodeSequence'],
- '0x4009': ['SQ', '1', 'HumanPerformerCodeSequence'],
- '0x4010': ['DT', '1', 'ScheduledProcedureStepModificationDateTime'],
- '0x4011': ['DT', '1', 'ExpectedCompletionDateTime'],
- '0x4015': ['SQ', '1', 'ResultingGeneralPurposePerformedProcedureStepsSequence'],
- '0x4016': ['SQ', '1', 'ReferencedGeneralPurposeScheduledProcedureStepSequence'],
- '0x4018': ['SQ', '1', 'ScheduledWorkitemCodeSequence'],
- '0x4019': ['SQ', '1', 'PerformedWorkitemCodeSequence'],
- '0x4020': ['CS', '1', 'InputAvailabilityFlag'],
- '0x4021': ['SQ', '1', 'InputInformationSequence'],
- '0x4022': ['SQ', '1', 'RelevantInformationSequence'],
- '0x4023': ['UI', '1', 'ReferencedGeneralPurposeScheduledProcedureStepTransactionUID'],
- '0x4025': ['SQ', '1', 'ScheduledStationNameCodeSequence'],
- '0x4026': ['SQ', '1', 'ScheduledStationClassCodeSequence'],
- '0x4027': ['SQ', '1', 'ScheduledStationGeographicLocationCodeSequence'],
- '0x4028': ['SQ', '1', 'PerformedStationNameCodeSequence'],
- '0x4029': ['SQ', '1', 'PerformedStationClassCodeSequence'],
- '0x4030': ['SQ', '1', 'PerformedStationGeographicLocationCodeSequence'],
- '0x4031': ['SQ', '1', 'RequestedSubsequentWorkitemCodeSequence'],
- '0x4032': ['SQ', '1', 'NonDICOMOutputCodeSequence'],
- '0x4033': ['SQ', '1', 'OutputInformationSequence'],
- '0x4034': ['SQ', '1', 'ScheduledHumanPerformersSequence'],
- '0x4035': ['SQ', '1', 'ActualHumanPerformersSequence'],
- '0x4036': ['LO', '1', 'HumanPerformerOrganization'],
- '0x4037': ['PN', '1', 'HumanPerformerName'],
- '0x4040': ['CS', '1', 'RawDataHandling'],
- '0x4041': ['CS', '1', 'InputReadinessState'],
- '0x4050': ['DT', '1', 'PerformedProcedureStepStartDateTime'],
- '0x4051': ['DT', '1', 'PerformedProcedureStepEndDateTime'],
- '0x4052': ['DT', '1', 'ProcedureStepCancellationDateTime'],
- '0x8302': ['DS', '1', 'EntranceDoseInmGy'],
- '0x9092': ['SQ', '1', 'ParametricMapFrameTypeSequence'],
- '0x9094': ['SQ', '1', 'ReferencedImageRealWorldValueMappingSequence'],
- '0x9096': ['SQ', '1', 'RealWorldValueMappingSequence'],
- '0x9098': ['SQ', '1', 'PixelValueMappingCodeSequence'],
- '0x9210': ['SH', '1', 'LUTLabel'],
- '0x9211': ['xs', '1', 'RealWorldValueLastValueMapped'],
- '0x9212': ['FD', '1-n', 'RealWorldValueLUTData'],
- '0x9216': ['xs', '1', 'RealWorldValueFirstValueMapped'],
- '0x9220': ['SQ', '1', 'QuantityDefinitionSequence'],
- '0x9224': ['FD', '1', 'RealWorldValueIntercept'],
- '0x9225': ['FD', '1', 'RealWorldValueSlope'],
- '0xA007': ['CS', '1', 'FindingsFlagTrial'],
- '0xA010': ['CS', '1', 'RelationshipType'],
- '0xA020': ['SQ', '1', 'FindingsSequenceTrial'],
- '0xA021': ['UI', '1', 'FindingsGroupUIDTrial'],
- '0xA022': ['UI', '1', 'ReferencedFindingsGroupUIDTrial'],
- '0xA023': ['DA', '1', 'FindingsGroupRecordingDateTrial'],
- '0xA024': ['TM', '1', 'FindingsGroupRecordingTimeTrial'],
- '0xA026': ['SQ', '1', 'FindingsSourceCategoryCodeSequenceTrial'],
- '0xA027': ['LO', '1', 'VerifyingOrganization'],
- '0xA028': ['SQ', '1', 'DocumentingOrganizationIdentifierCodeSequenceTrial'],
- '0xA030': ['DT', '1', 'VerificationDateTime'],
- '0xA032': ['DT', '1', 'ObservationDateTime'],
- '0xA040': ['CS', '1', 'ValueType'],
- '0xA043': ['SQ', '1', 'ConceptNameCodeSequence'],
- '0xA047': ['LO', '1', 'MeasurementPrecisionDescriptionTrial'],
- '0xA050': ['CS', '1', 'ContinuityOfContent'],
- '0xA057': ['CS', '1-n', 'UrgencyOrPriorityAlertsTrial'],
- '0xA060': ['LO', '1', 'SequencingIndicatorTrial'],
- '0xA066': ['SQ', '1', 'DocumentIdentifierCodeSequenceTrial'],
- '0xA067': ['PN', '1', 'DocumentAuthorTrial'],
- '0xA068': ['SQ', '1', 'DocumentAuthorIdentifierCodeSequenceTrial'],
- '0xA070': ['SQ', '1', 'IdentifierCodeSequenceTrial'],
- '0xA073': ['SQ', '1', 'VerifyingObserverSequence'],
- '0xA074': ['OB', '1', 'ObjectBinaryIdentifierTrial'],
- '0xA075': ['PN', '1', 'VerifyingObserverName'],
- '0xA076': ['SQ', '1', 'DocumentingObserverIdentifierCodeSequenceTrial'],
- '0xA078': ['SQ', '1', 'AuthorObserverSequence'],
- '0xA07A': ['SQ', '1', 'ParticipantSequence'],
- '0xA07C': ['SQ', '1', 'CustodialOrganizationSequence'],
- '0xA080': ['CS', '1', 'ParticipationType'],
- '0xA082': ['DT', '1', 'ParticipationDateTime'],
- '0xA084': ['CS', '1', 'ObserverType'],
- '0xA085': ['SQ', '1', 'ProcedureIdentifierCodeSequenceTrial'],
- '0xA088': ['SQ', '1', 'VerifyingObserverIdentificationCodeSequence'],
- '0xA089': ['OB', '1', 'ObjectDirectoryBinaryIdentifierTrial'],
- '0xA090': ['SQ', '1', 'EquivalentCDADocumentSequence'],
- '0xA0B0': ['US', '2-2n', 'ReferencedWaveformChannels'],
- '0xA110': ['DA', '1', 'DateOfDocumentOrVerbalTransactionTrial'],
- '0xA112': ['TM', '1', 'TimeOfDocumentCreationOrVerbalTransactionTrial'],
- '0xA120': ['DT', '1', 'DateTime'],
- '0xA121': ['DA', '1', 'Date'],
- '0xA122': ['TM', '1', 'Time'],
- '0xA123': ['PN', '1', 'PersonName'],
- '0xA124': ['UI', '1', 'UID'],
- '0xA125': ['CS', '2', 'ReportStatusIDTrial'],
- '0xA130': ['CS', '1', 'TemporalRangeType'],
- '0xA132': ['UL', '1-n', 'ReferencedSamplePositions'],
- '0xA136': ['US', '1-n', 'ReferencedFrameNumbers'],
- '0xA138': ['DS', '1-n', 'ReferencedTimeOffsets'],
- '0xA13A': ['DT', '1-n', 'ReferencedDateTime'],
- '0xA160': ['UT', '1', 'TextValue'],
- '0xA161': ['FD', '1-n', 'FloatingPointValue'],
- '0xA162': ['SL', '1-n', 'RationalNumeratorValue'],
- '0xA163': ['UL', '1-n', 'RationalDenominatorValue'],
- '0xA167': ['SQ', '1', 'ObservationCategoryCodeSequenceTrial'],
- '0xA168': ['SQ', '1', 'ConceptCodeSequence'],
- '0xA16A': ['ST', '1', 'BibliographicCitationTrial'],
- '0xA170': ['SQ', '1', 'PurposeOfReferenceCodeSequence'],
- '0xA171': ['UI', '1', 'ObservationUID'],
- '0xA172': ['UI', '1', 'ReferencedObservationUIDTrial'],
- '0xA173': ['CS', '1', 'ReferencedObservationClassTrial'],
- '0xA174': ['CS', '1', 'ReferencedObjectObservationClassTrial'],
- '0xA180': ['US', '1', 'AnnotationGroupNumber'],
- '0xA192': ['DA', '1', 'ObservationDateTrial'],
- '0xA193': ['TM', '1', 'ObservationTimeTrial'],
- '0xA194': ['CS', '1', 'MeasurementAutomationTrial'],
- '0xA195': ['SQ', '1', 'ModifierCodeSequence'],
- '0xA224': ['ST', '1', 'IdentificationDescriptionTrial'],
- '0xA290': ['CS', '1', 'CoordinatesSetGeometricTypeTrial'],
- '0xA296': ['SQ', '1', 'AlgorithmCodeSequenceTrial'],
- '0xA297': ['ST', '1', 'AlgorithmDescriptionTrial'],
- '0xA29A': ['SL', '2-2n', 'PixelCoordinatesSetTrial'],
- '0xA300': ['SQ', '1', 'MeasuredValueSequence'],
- '0xA301': ['SQ', '1', 'NumericValueQualifierCodeSequence'],
- '0xA307': ['PN', '1', 'CurrentObserverTrial'],
- '0xA30A': ['DS', '1-n', 'NumericValue'],
- '0xA313': ['SQ', '1', 'ReferencedAccessionSequenceTrial'],
- '0xA33A': ['ST', '1', 'ReportStatusCommentTrial'],
- '0xA340': ['SQ', '1', 'ProcedureContextSequenceTrial'],
- '0xA352': ['PN', '1', 'VerbalSourceTrial'],
- '0xA353': ['ST', '1', 'AddressTrial'],
- '0xA354': ['LO', '1', 'TelephoneNumberTrial'],
- '0xA358': ['SQ', '1', 'VerbalSourceIdentifierCodeSequenceTrial'],
- '0xA360': ['SQ', '1', 'PredecessorDocumentsSequence'],
- '0xA370': ['SQ', '1', 'ReferencedRequestSequence'],
- '0xA372': ['SQ', '1', 'PerformedProcedureCodeSequence'],
- '0xA375': ['SQ', '1', 'CurrentRequestedProcedureEvidenceSequence'],
- '0xA380': ['SQ', '1', 'ReportDetailSequenceTrial'],
- '0xA385': ['SQ', '1', 'PertinentOtherEvidenceSequence'],
- '0xA390': ['SQ', '1', 'HL7StructuredDocumentReferenceSequence'],
- '0xA402': ['UI', '1', 'ObservationSubjectUIDTrial'],
- '0xA403': ['CS', '1', 'ObservationSubjectClassTrial'],
- '0xA404': ['SQ', '1', 'ObservationSubjectTypeCodeSequenceTrial'],
- '0xA491': ['CS', '1', 'CompletionFlag'],
- '0xA492': ['LO', '1', 'CompletionFlagDescription'],
- '0xA493': ['CS', '1', 'VerificationFlag'],
- '0xA494': ['CS', '1', 'ArchiveRequested'],
- '0xA496': ['CS', '1', 'PreliminaryFlag'],
- '0xA504': ['SQ', '1', 'ContentTemplateSequence'],
- '0xA525': ['SQ', '1', 'IdenticalDocumentsSequence'],
- '0xA600': ['CS', '1', 'ObservationSubjectContextFlagTrial'],
- '0xA601': ['CS', '1', 'ObserverContextFlagTrial'],
- '0xA603': ['CS', '1', 'ProcedureContextFlagTrial'],
- '0xA730': ['SQ', '1', 'ContentSequence'],
- '0xA731': ['SQ', '1', 'RelationshipSequenceTrial'],
- '0xA732': ['SQ', '1', 'RelationshipTypeCodeSequenceTrial'],
- '0xA744': ['SQ', '1', 'LanguageCodeSequenceTrial'],
- '0xA992': ['ST', '1', 'UniformResourceLocatorTrial'],
- '0xB020': ['SQ', '1', 'WaveformAnnotationSequence'],
- '0xDB00': ['CS', '1', 'TemplateIdentifier'],
- '0xDB06': ['DT', '1', 'TemplateVersion'],
- '0xDB07': ['DT', '1', 'TemplateLocalVersion'],
- '0xDB0B': ['CS', '1', 'TemplateExtensionFlag'],
- '0xDB0C': ['UI', '1', 'TemplateExtensionOrganizationUID'],
- '0xDB0D': ['UI', '1', 'TemplateExtensionCreatorUID'],
- '0xDB73': ['UL', '1-n', 'ReferencedContentItemIdentifier'],
- '0xE001': ['ST', '1', 'HL7InstanceIdentifier'],
- '0xE004': ['DT', '1', 'HL7DocumentEffectiveTime'],
- '0xE006': ['SQ', '1', 'HL7DocumentTypeCodeSequence'],
- '0xE008': ['SQ', '1', 'DocumentClassCodeSequence'],
- '0xE010': ['UR', '1', 'RetrieveURI'],
- '0xE011': ['UI', '1', 'RetrieveLocationUID'],
- '0xE020': ['CS', '1', 'TypeOfInstances'],
- '0xE021': ['SQ', '1', 'DICOMRetrievalSequence'],
- '0xE022': ['SQ', '1', 'DICOMMediaRetrievalSequence'],
- '0xE023': ['SQ', '1', 'WADORetrievalSequence'],
- '0xE024': ['SQ', '1', 'XDSRetrievalSequence'],
- '0xE025': ['SQ', '1', 'WADORSRetrievalSequence'],
- '0xE030': ['UI', '1', 'RepositoryUniqueID'],
- '0xE031': ['UI', '1', 'HomeCommunityID']
- },
- '0x0042': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['ST', '1', 'DocumentTitle'],
- '0x0011': ['OB', '1', 'EncapsulatedDocument'],
- '0x0012': ['LO', '1', 'MIMETypeOfEncapsulatedDocument'],
- '0x0013': ['SQ', '1', 'SourceInstanceSequence'],
- '0x0014': ['LO', '1-n', 'ListOfMIMETypes']
- },
- '0x0044': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['ST', '1', 'ProductPackageIdentifier'],
- '0x0002': ['CS', '1', 'SubstanceAdministrationApproval'],
- '0x0003': ['LT', '1', 'ApprovalStatusFurtherDescription'],
- '0x0004': ['DT', '1', 'ApprovalStatusDateTime'],
- '0x0007': ['SQ', '1', 'ProductTypeCodeSequence'],
- '0x0008': ['LO', '1-n', 'ProductName'],
- '0x0009': ['LT', '1', 'ProductDescription'],
- '0x000A': ['LO', '1', 'ProductLotIdentifier'],
- '0x000B': ['DT', '1', 'ProductExpirationDateTime'],
- '0x0010': ['DT', '1', 'SubstanceAdministrationDateTime'],
- '0x0011': ['LO', '1', 'SubstanceAdministrationNotes'],
- '0x0012': ['LO', '1', 'SubstanceAdministrationDeviceID'],
- '0x0013': ['SQ', '1', 'ProductParameterSequence'],
- '0x0019': ['SQ', '1', 'SubstanceAdministrationParameterSequence']
- },
- '0x0046': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0012': ['LO', '1', 'LensDescription'],
- '0x0014': ['SQ', '1', 'RightLensSequence'],
- '0x0015': ['SQ', '1', 'LeftLensSequence'],
- '0x0016': ['SQ', '1', 'UnspecifiedLateralityLensSequence'],
- '0x0018': ['SQ', '1', 'CylinderSequence'],
- '0x0028': ['SQ', '1', 'PrismSequence'],
- '0x0030': ['FD', '1', 'HorizontalPrismPower'],
- '0x0032': ['CS', '1', 'HorizontalPrismBase'],
- '0x0034': ['FD', '1', 'VerticalPrismPower'],
- '0x0036': ['CS', '1', 'VerticalPrismBase'],
- '0x0038': ['CS', '1', 'LensSegmentType'],
- '0x0040': ['FD', '1', 'OpticalTransmittance'],
- '0x0042': ['FD', '1', 'ChannelWidth'],
- '0x0044': ['FD', '1', 'PupilSize'],
- '0x0046': ['FD', '1', 'CornealSize'],
- '0x0050': ['SQ', '1', 'AutorefractionRightEyeSequence'],
- '0x0052': ['SQ', '1', 'AutorefractionLeftEyeSequence'],
- '0x0060': ['FD', '1', 'DistancePupillaryDistance'],
- '0x0062': ['FD', '1', 'NearPupillaryDistance'],
- '0x0063': ['FD', '1', 'IntermediatePupillaryDistance'],
- '0x0064': ['FD', '1', 'OtherPupillaryDistance'],
- '0x0070': ['SQ', '1', 'KeratometryRightEyeSequence'],
- '0x0071': ['SQ', '1', 'KeratometryLeftEyeSequence'],
- '0x0074': ['SQ', '1', 'SteepKeratometricAxisSequence'],
- '0x0075': ['FD', '1', 'RadiusOfCurvature'],
- '0x0076': ['FD', '1', 'KeratometricPower'],
- '0x0077': ['FD', '1', 'KeratometricAxis'],
- '0x0080': ['SQ', '1', 'FlatKeratometricAxisSequence'],
- '0x0092': ['CS', '1', 'BackgroundColor'],
- '0x0094': ['CS', '1', 'Optotype'],
- '0x0095': ['CS', '1', 'OptotypePresentation'],
- '0x0097': ['SQ', '1', 'SubjectiveRefractionRightEyeSequence'],
- '0x0098': ['SQ', '1', 'SubjectiveRefractionLeftEyeSequence'],
- '0x0100': ['SQ', '1', 'AddNearSequence'],
- '0x0101': ['SQ', '1', 'AddIntermediateSequence'],
- '0x0102': ['SQ', '1', 'AddOtherSequence'],
- '0x0104': ['FD', '1', 'AddPower'],
- '0x0106': ['FD', '1', 'ViewingDistance'],
- '0x0121': ['SQ', '1', 'VisualAcuityTypeCodeSequence'],
- '0x0122': ['SQ', '1', 'VisualAcuityRightEyeSequence'],
- '0x0123': ['SQ', '1', 'VisualAcuityLeftEyeSequence'],
- '0x0124': ['SQ', '1', 'VisualAcuityBothEyesOpenSequence'],
- '0x0125': ['CS', '1', 'ViewingDistanceType'],
- '0x0135': ['SS', '2', 'VisualAcuityModifiers'],
- '0x0137': ['FD', '1', 'DecimalVisualAcuity'],
- '0x0139': ['LO', '1', 'OptotypeDetailedDefinition'],
- '0x0145': ['SQ', '1', 'ReferencedRefractiveMeasurementsSequence'],
- '0x0146': ['FD', '1', 'SpherePower'],
- '0x0147': ['FD', '1', 'CylinderPower'],
- '0x0201': ['CS', '1', 'CornealTopographySurface'],
- '0x0202': ['FL', '2', 'CornealVertexLocation'],
- '0x0203': ['FL', '1', 'PupilCentroidXCoordinate'],
- '0x0204': ['FL', '1', 'PupilCentroidYCoordinate'],
- '0x0205': ['FL', '1', 'EquivalentPupilRadius'],
- '0x0207': ['SQ', '1', 'CornealTopographyMapTypeCodeSequence'],
- '0x0208': ['IS', '2-2n', 'VerticesOfTheOutlineOfPupil'],
- '0x0210': ['SQ', '1', 'CornealTopographyMappingNormalsSequence'],
- '0x0211': ['SQ', '1', 'MaximumCornealCurvatureSequence'],
- '0x0212': ['FL', '1', 'MaximumCornealCurvature'],
- '0x0213': ['FL', '2', 'MaximumCornealCurvatureLocation'],
- '0x0215': ['SQ', '1', 'MinimumKeratometricSequence'],
- '0x0218': ['SQ', '1', 'SimulatedKeratometricCylinderSequence'],
- '0x0220': ['FL', '1', 'AverageCornealPower'],
- '0x0224': ['FL', '1', 'CornealISValue'],
- '0x0227': ['FL', '1', 'AnalyzedArea'],
- '0x0230': ['FL', '1', 'SurfaceRegularityIndex'],
- '0x0232': ['FL', '1', 'SurfaceAsymmetryIndex'],
- '0x0234': ['FL', '1', 'CornealEccentricityIndex'],
- '0x0236': ['FL', '1', 'KeratoconusPredictionIndex'],
- '0x0238': ['FL', '1', 'DecimalPotentialVisualAcuity'],
- '0x0242': ['CS', '1', 'CornealTopographyMapQualityEvaluation'],
- '0x0244': ['SQ', '1', 'SourceImageCornealProcessedDataSequence'],
- '0x0247': ['FL', '3', 'CornealPointLocation'],
- '0x0248': ['CS', '1', 'CornealPointEstimated'],
- '0x0249': ['FL', '1', 'AxialPower'],
- '0x0250': ['FL', '1', 'TangentialPower'],
- '0x0251': ['FL', '1', 'RefractivePower'],
- '0x0252': ['FL', '1', 'RelativeElevation'],
- '0x0253': ['FL', '1', 'CornealWavefront']
- },
- '0x0048': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['FL', '1', 'ImagedVolumeWidth'],
- '0x0002': ['FL', '1', 'ImagedVolumeHeight'],
- '0x0003': ['FL', '1', 'ImagedVolumeDepth'],
- '0x0006': ['UL', '1', 'TotalPixelMatrixColumns'],
- '0x0007': ['UL', '1', 'TotalPixelMatrixRows'],
- '0x0008': ['SQ', '1', 'TotalPixelMatrixOriginSequence'],
- '0x0010': ['CS', '1', 'SpecimenLabelInImage'],
- '0x0011': ['CS', '1', 'FocusMethod'],
- '0x0012': ['CS', '1', 'ExtendedDepthOfField'],
- '0x0013': ['US', '1', 'NumberOfFocalPlanes'],
- '0x0014': ['FL', '1', 'DistanceBetweenFocalPlanes'],
- '0x0015': ['US', '3', 'RecommendedAbsentPixelCIELabValue'],
- '0x0100': ['SQ', '1', 'IlluminatorTypeCodeSequence'],
- '0x0102': ['DS', '6', 'ImageOrientationSlide'],
- '0x0105': ['SQ', '1', 'OpticalPathSequence'],
- '0x0106': ['SH', '1', 'OpticalPathIdentifier'],
- '0x0107': ['ST', '1', 'OpticalPathDescription'],
- '0x0108': ['SQ', '1', 'IlluminationColorCodeSequence'],
- '0x0110': ['SQ', '1', 'SpecimenReferenceSequence'],
- '0x0111': ['DS', '1', 'CondenserLensPower'],
- '0x0112': ['DS', '1', 'ObjectiveLensPower'],
- '0x0113': ['DS', '1', 'ObjectiveLensNumericalAperture'],
- '0x0120': ['SQ', '1', 'PaletteColorLookupTableSequence'],
- '0x0200': ['SQ', '1', 'ReferencedImageNavigationSequence'],
- '0x0201': ['US', '2', 'TopLeftHandCornerOfLocalizerArea'],
- '0x0202': ['US', '2', 'BottomRightHandCornerOfLocalizerArea'],
- '0x0207': ['SQ', '1', 'OpticalPathIdentificationSequence'],
- '0x021A': ['SQ', '1', 'PlanePositionSlideSequence'],
- '0x021E': ['SL', '1', 'ColumnPositionInTotalImagePixelMatrix'],
- '0x021F': ['SL', '1', 'RowPositionInTotalImagePixelMatrix'],
- '0x0301': ['CS', '1', 'PixelOriginInterpretation']
- },
- '0x0050': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0004': ['CS', '1', 'CalibrationImage'],
- '0x0010': ['SQ', '1', 'DeviceSequence'],
- '0x0012': ['SQ', '1', 'ContainerComponentTypeCodeSequence'],
- '0x0013': ['FD', '1', 'ContainerComponentThickness'],
- '0x0014': ['DS', '1', 'DeviceLength'],
- '0x0015': ['FD', '1', 'ContainerComponentWidth'],
- '0x0016': ['DS', '1', 'DeviceDiameter'],
- '0x0017': ['CS', '1', 'DeviceDiameterUnits'],
- '0x0018': ['DS', '1', 'DeviceVolume'],
- '0x0019': ['DS', '1', 'InterMarkerDistance'],
- '0x001A': ['CS', '1', 'ContainerComponentMaterial'],
- '0x001B': ['LO', '1', 'ContainerComponentID'],
- '0x001C': ['FD', '1', 'ContainerComponentLength'],
- '0x001D': ['FD', '1', 'ContainerComponentDiameter'],
- '0x001E': ['LO', '1', 'ContainerComponentDescription'],
- '0x0020': ['LO', '1', 'DeviceDescription']
- },
- '0x0052': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['FL', '1', 'ContrastBolusIngredientPercentByVolume'],
- '0x0002': ['FD', '1', 'OCTFocalDistance'],
- '0x0003': ['FD', '1', 'BeamSpotSize'],
- '0x0004': ['FD', '1', 'EffectiveRefractiveIndex'],
- '0x0006': ['CS', '1', 'OCTAcquisitionDomain'],
- '0x0007': ['FD', '1', 'OCTOpticalCenterWavelength'],
- '0x0008': ['FD', '1', 'AxialResolution'],
- '0x0009': ['FD', '1', 'RangingDepth'],
- '0x0011': ['FD', '1', 'ALineRate'],
- '0x0012': ['US', '1', 'ALinesPerFrame'],
- '0x0013': ['FD', '1', 'CatheterRotationalRate'],
- '0x0014': ['FD', '1', 'ALinePixelSpacing'],
- '0x0016': ['SQ', '1', 'ModeOfPercutaneousAccessSequence'],
- '0x0025': ['SQ', '1', 'IntravascularOCTFrameTypeSequence'],
- '0x0026': ['CS', '1', 'OCTZOffsetApplied'],
- '0x0027': ['SQ', '1', 'IntravascularFrameContentSequence'],
- '0x0028': ['FD', '1', 'IntravascularLongitudinalDistance'],
- '0x0029': ['SQ', '1', 'IntravascularOCTFrameContentSequence'],
- '0x0030': ['SS', '1', 'OCTZOffsetCorrection'],
- '0x0031': ['CS', '1', 'CatheterDirectionOfRotation'],
- '0x0033': ['FD', '1', 'SeamLineLocation'],
- '0x0034': ['FD', '1', 'FirstALineLocation'],
- '0x0036': ['US', '1', 'SeamLineIndex'],
- '0x0038': ['US', '1', 'NumberOfPaddedALines'],
- '0x0039': ['CS', '1', 'InterpolationType'],
- '0x003A': ['CS', '1', 'RefractiveIndexApplied']
- },
- '0x0054': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['US', '1-n', 'EnergyWindowVector'],
- '0x0011': ['US', '1', 'NumberOfEnergyWindows'],
- '0x0012': ['SQ', '1', 'EnergyWindowInformationSequence'],
- '0x0013': ['SQ', '1', 'EnergyWindowRangeSequence'],
- '0x0014': ['DS', '1', 'EnergyWindowLowerLimit'],
- '0x0015': ['DS', '1', 'EnergyWindowUpperLimit'],
- '0x0016': ['SQ', '1', 'RadiopharmaceuticalInformationSequence'],
- '0x0017': ['IS', '1', 'ResidualSyringeCounts'],
- '0x0018': ['SH', '1', 'EnergyWindowName'],
- '0x0020': ['US', '1-n', 'DetectorVector'],
- '0x0021': ['US', '1', 'NumberOfDetectors'],
- '0x0022': ['SQ', '1', 'DetectorInformationSequence'],
- '0x0030': ['US', '1-n', 'PhaseVector'],
- '0x0031': ['US', '1', 'NumberOfPhases'],
- '0x0032': ['SQ', '1', 'PhaseInformationSequence'],
- '0x0033': ['US', '1', 'NumberOfFramesInPhase'],
- '0x0036': ['IS', '1', 'PhaseDelay'],
- '0x0038': ['IS', '1', 'PauseBetweenFrames'],
- '0x0039': ['CS', '1', 'PhaseDescription'],
- '0x0050': ['US', '1-n', 'RotationVector'],
- '0x0051': ['US', '1', 'NumberOfRotations'],
- '0x0052': ['SQ', '1', 'RotationInformationSequence'],
- '0x0053': ['US', '1', 'NumberOfFramesInRotation'],
- '0x0060': ['US', '1-n', 'RRIntervalVector'],
- '0x0061': ['US', '1', 'NumberOfRRIntervals'],
- '0x0062': ['SQ', '1', 'GatedInformationSequence'],
- '0x0063': ['SQ', '1', 'DataInformationSequence'],
- '0x0070': ['US', '1-n', 'TimeSlotVector'],
- '0x0071': ['US', '1', 'NumberOfTimeSlots'],
- '0x0072': ['SQ', '1', 'TimeSlotInformationSequence'],
- '0x0073': ['DS', '1', 'TimeSlotTime'],
- '0x0080': ['US', '1-n', 'SliceVector'],
- '0x0081': ['US', '1', 'NumberOfSlices'],
- '0x0090': ['US', '1-n', 'AngularViewVector'],
- '0x0100': ['US', '1-n', 'TimeSliceVector'],
- '0x0101': ['US', '1', 'NumberOfTimeSlices'],
- '0x0200': ['DS', '1', 'StartAngle'],
- '0x0202': ['CS', '1', 'TypeOfDetectorMotion'],
- '0x0210': ['IS', '1-n', 'TriggerVector'],
- '0x0211': ['US', '1', 'NumberOfTriggersInPhase'],
- '0x0220': ['SQ', '1', 'ViewCodeSequence'],
- '0x0222': ['SQ', '1', 'ViewModifierCodeSequence'],
- '0x0300': ['SQ', '1', 'RadionuclideCodeSequence'],
- '0x0302': ['SQ', '1', 'AdministrationRouteCodeSequence'],
- '0x0304': ['SQ', '1', 'RadiopharmaceuticalCodeSequence'],
- '0x0306': ['SQ', '1', 'CalibrationDataSequence'],
- '0x0308': ['US', '1', 'EnergyWindowNumber'],
- '0x0400': ['SH', '1', 'ImageID'],
- '0x0410': ['SQ', '1', 'PatientOrientationCodeSequence'],
- '0x0412': ['SQ', '1', 'PatientOrientationModifierCodeSequence'],
- '0x0414': ['SQ', '1', 'PatientGantryRelationshipCodeSequence'],
- '0x0500': ['CS', '1', 'SliceProgressionDirection'],
- '0x0501': ['CS', '1', 'ScanProgressionDirection'],
- '0x1000': ['CS', '2', 'SeriesType'],
- '0x1001': ['CS', '1', 'Units'],
- '0x1002': ['CS', '1', 'CountsSource'],
- '0x1004': ['CS', '1', 'ReprojectionMethod'],
- '0x1006': ['CS', '1', 'SUVType'],
- '0x1100': ['CS', '1', 'RandomsCorrectionMethod'],
- '0x1101': ['LO', '1', 'AttenuationCorrectionMethod'],
- '0x1102': ['CS', '1', 'DecayCorrection'],
- '0x1103': ['LO', '1', 'ReconstructionMethod'],
- '0x1104': ['LO', '1', 'DetectorLinesOfResponseUsed'],
- '0x1105': ['LO', '1', 'ScatterCorrectionMethod'],
- '0x1200': ['DS', '1', 'AxialAcceptance'],
- '0x1201': ['IS', '2', 'AxialMash'],
- '0x1202': ['IS', '1', 'TransverseMash'],
- '0x1203': ['DS', '2', 'DetectorElementSize'],
- '0x1210': ['DS', '1', 'CoincidenceWindowWidth'],
- '0x1220': ['CS', '1-n', 'SecondaryCountsType'],
- '0x1300': ['DS', '1', 'FrameReferenceTime'],
- '0x1310': ['IS', '1', 'PrimaryPromptsCountsAccumulated'],
- '0x1311': ['IS', '1-n', 'SecondaryCountsAccumulated'],
- '0x1320': ['DS', '1', 'SliceSensitivityFactor'],
- '0x1321': ['DS', '1', 'DecayFactor'],
- '0x1322': ['DS', '1', 'DoseCalibrationFactor'],
- '0x1323': ['DS', '1', 'ScatterFractionFactor'],
- '0x1324': ['DS', '1', 'DeadTimeFactor'],
- '0x1330': ['US', '1', 'ImageIndex'],
- '0x1400': ['CS', '1-n', 'CountsIncluded'],
- '0x1401': ['CS', '1', 'DeadTimeCorrectionFlag']
- },
- '0x0060': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x3000': ['SQ', '1', 'HistogramSequence'],
- '0x3002': ['US', '1', 'HistogramNumberOfBins'],
- '0x3004': ['xs', '1', 'HistogramFirstBinValue'],
- '0x3006': ['xs', '1', 'HistogramLastBinValue'],
- '0x3008': ['US', '1', 'HistogramBinWidth'],
- '0x3010': ['LO', '1', 'HistogramExplanation'],
- '0x3020': ['UL', '1-n', 'HistogramData']
- },
- '0x0062': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['CS', '1', 'SegmentationType'],
- '0x0002': ['SQ', '1', 'SegmentSequence'],
- '0x0003': ['SQ', '1', 'SegmentedPropertyCategoryCodeSequence'],
- '0x0004': ['US', '1', 'SegmentNumber'],
- '0x0005': ['LO', '1', 'SegmentLabel'],
- '0x0006': ['ST', '1', 'SegmentDescription'],
- '0x0008': ['CS', '1', 'SegmentAlgorithmType'],
- '0x0009': ['LO', '1', 'SegmentAlgorithmName'],
- '0x000A': ['SQ', '1', 'SegmentIdentificationSequence'],
- '0x000B': ['US', '1-n', 'ReferencedSegmentNumber'],
- '0x000C': ['US', '1', 'RecommendedDisplayGrayscaleValue'],
- '0x000D': ['US', '3', 'RecommendedDisplayCIELabValue'],
- '0x000E': ['US', '1', 'MaximumFractionalValue'],
- '0x000F': ['SQ', '1', 'SegmentedPropertyTypeCodeSequence'],
- '0x0010': ['CS', '1', 'SegmentationFractionalType'],
- '0x0011': ['SQ', '1', 'SegmentedPropertyTypeModifierCodeSequence'],
- '0x0012': ['SQ', '1', 'UsedSegmentsSequence']
- },
- '0x0064': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['SQ', '1', 'DeformableRegistrationSequence'],
- '0x0003': ['UI', '1', 'SourceFrameOfReferenceUID'],
- '0x0005': ['SQ', '1', 'DeformableRegistrationGridSequence'],
- '0x0007': ['UL', '3', 'GridDimensions'],
- '0x0008': ['FD', '3', 'GridResolution'],
- '0x0009': ['OF', '1', 'VectorGridData'],
- '0x000F': ['SQ', '1', 'PreDeformationMatrixRegistrationSequence'],
- '0x0010': ['SQ', '1', 'PostDeformationMatrixRegistrationSequence']
- },
- '0x0066': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['UL', '1', 'NumberOfSurfaces'],
- '0x0002': ['SQ', '1', 'SurfaceSequence'],
- '0x0003': ['UL', '1', 'SurfaceNumber'],
- '0x0004': ['LT', '1', 'SurfaceComments'],
- '0x0009': ['CS', '1', 'SurfaceProcessing'],
- '0x000A': ['FL', '1', 'SurfaceProcessingRatio'],
- '0x000B': ['LO', '1', 'SurfaceProcessingDescription'],
- '0x000C': ['FL', '1', 'RecommendedPresentationOpacity'],
- '0x000D': ['CS', '1', 'RecommendedPresentationType'],
- '0x000E': ['CS', '1', 'FiniteVolume'],
- '0x0010': ['CS', '1', 'Manifold'],
- '0x0011': ['SQ', '1', 'SurfacePointsSequence'],
- '0x0012': ['SQ', '1', 'SurfacePointsNormalsSequence'],
- '0x0013': ['SQ', '1', 'SurfaceMeshPrimitivesSequence'],
- '0x0015': ['UL', '1', 'NumberOfSurfacePoints'],
- '0x0016': ['OF', '1', 'PointCoordinatesData'],
- '0x0017': ['FL', '3', 'PointPositionAccuracy'],
- '0x0018': ['FL', '1', 'MeanPointDistance'],
- '0x0019': ['FL', '1', 'MaximumPointDistance'],
- '0x001A': ['FL', '6', 'PointsBoundingBoxCoordinates'],
- '0x001B': ['FL', '3', 'AxisOfRotation'],
- '0x001C': ['FL', '3', 'CenterOfRotation'],
- '0x001E': ['UL', '1', 'NumberOfVectors'],
- '0x001F': ['US', '1', 'VectorDimensionality'],
- '0x0020': ['FL', '1-n', 'VectorAccuracy'],
- '0x0021': ['OF', '1', 'VectorCoordinateData'],
- '0x0023': ['OW', '1', 'TrianglePointIndexList'],
- '0x0024': ['OW', '1', 'EdgePointIndexList'],
- '0x0025': ['OW', '1', 'VertexPointIndexList'],
- '0x0026': ['SQ', '1', 'TriangleStripSequence'],
- '0x0027': ['SQ', '1', 'TriangleFanSequence'],
- '0x0028': ['SQ', '1', 'LineSequence'],
- '0x0029': ['OW', '1', 'PrimitivePointIndexList'],
- '0x002A': ['UL', '1', 'SurfaceCount'],
- '0x002B': ['SQ', '1', 'ReferencedSurfaceSequence'],
- '0x002C': ['UL', '1', 'ReferencedSurfaceNumber'],
- '0x002D': ['SQ', '1', 'SegmentSurfaceGenerationAlgorithmIdentificationSequence'],
- '0x002E': ['SQ', '1', 'SegmentSurfaceSourceInstanceSequence'],
- '0x002F': ['SQ', '1', 'AlgorithmFamilyCodeSequence'],
- '0x0030': ['SQ', '1', 'AlgorithmNameCodeSequence'],
- '0x0031': ['LO', '1', 'AlgorithmVersion'],
- '0x0032': ['LT', '1', 'AlgorithmParameters'],
- '0x0034': ['SQ', '1', 'FacetSequence'],
- '0x0035': ['SQ', '1', 'SurfaceProcessingAlgorithmIdentificationSequence'],
- '0x0036': ['LO', '1', 'AlgorithmName'],
- '0x0037': ['FL', '1', 'RecommendedPointRadius'],
- '0x0038': ['FL', '1', 'RecommendedLineThickness'],
- '0x0040': ['UL', '1-n', 'LongPrimitivePointIndexList'],
- '0x0041': ['UL', '3-3n', 'LongTrianglePointIndexList'],
- '0x0042': ['UL', '2-2n', 'LongEdgePointIndexList'],
- '0x0043': ['UL', '1-n', 'LongVertexPointIndexList']
- },
- '0x0068': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x6210': ['LO', '1', 'ImplantSize'],
- '0x6221': ['LO', '1', 'ImplantTemplateVersion'],
- '0x6222': ['SQ', '1', 'ReplacedImplantTemplateSequence'],
- '0x6223': ['CS', '1', 'ImplantType'],
- '0x6224': ['SQ', '1', 'DerivationImplantTemplateSequence'],
- '0x6225': ['SQ', '1', 'OriginalImplantTemplateSequence'],
- '0x6226': ['DT', '1', 'EffectiveDateTime'],
- '0x6230': ['SQ', '1', 'ImplantTargetAnatomySequence'],
- '0x6260': ['SQ', '1', 'InformationFromManufacturerSequence'],
- '0x6265': ['SQ', '1', 'NotificationFromManufacturerSequence'],
- '0x6270': ['DT', '1', 'InformationIssueDateTime'],
- '0x6280': ['ST', '1', 'InformationSummary'],
- '0x62A0': ['SQ', '1', 'ImplantRegulatoryDisapprovalCodeSequence'],
- '0x62A5': ['FD', '1', 'OverallTemplateSpatialTolerance'],
- '0x62C0': ['SQ', '1', 'HPGLDocumentSequence'],
- '0x62D0': ['US', '1', 'HPGLDocumentID'],
- '0x62D5': ['LO', '1', 'HPGLDocumentLabel'],
- '0x62E0': ['SQ', '1', 'ViewOrientationCodeSequence'],
- '0x62F0': ['FD', '9', 'ViewOrientationModifier'],
- '0x62F2': ['FD', '1', 'HPGLDocumentScaling'],
- '0x6300': ['OB', '1', 'HPGLDocument'],
- '0x6310': ['US', '1', 'HPGLContourPenNumber'],
- '0x6320': ['SQ', '1', 'HPGLPenSequence'],
- '0x6330': ['US', '1', 'HPGLPenNumber'],
- '0x6340': ['LO', '1', 'HPGLPenLabel'],
- '0x6345': ['ST', '1', 'HPGLPenDescription'],
- '0x6346': ['FD', '2', 'RecommendedRotationPoint'],
- '0x6347': ['FD', '4', 'BoundingRectangle'],
- '0x6350': ['US', '1-n', 'ImplantTemplate3DModelSurfaceNumber'],
- '0x6360': ['SQ', '1', 'SurfaceModelDescriptionSequence'],
- '0x6380': ['LO', '1', 'SurfaceModelLabel'],
- '0x6390': ['FD', '1', 'SurfaceModelScalingFactor'],
- '0x63A0': ['SQ', '1', 'MaterialsCodeSequence'],
- '0x63A4': ['SQ', '1', 'CoatingMaterialsCodeSequence'],
- '0x63A8': ['SQ', '1', 'ImplantTypeCodeSequence'],
- '0x63AC': ['SQ', '1', 'FixationMethodCodeSequence'],
- '0x63B0': ['SQ', '1', 'MatingFeatureSetsSequence'],
- '0x63C0': ['US', '1', 'MatingFeatureSetID'],
- '0x63D0': ['LO', '1', 'MatingFeatureSetLabel'],
- '0x63E0': ['SQ', '1', 'MatingFeatureSequence'],
- '0x63F0': ['US', '1', 'MatingFeatureID'],
- '0x6400': ['SQ', '1', 'MatingFeatureDegreeOfFreedomSequence'],
- '0x6410': ['US', '1', 'DegreeOfFreedomID'],
- '0x6420': ['CS', '1', 'DegreeOfFreedomType'],
- '0x6430': ['SQ', '1', 'TwoDMatingFeatureCoordinatesSequence'],
- '0x6440': ['US', '1', 'ReferencedHPGLDocumentID'],
- '0x6450': ['FD', '2', 'TwoDMatingPoint'],
- '0x6460': ['FD', '4', 'TwoDMatingAxes'],
- '0x6470': ['SQ', '1', 'TwoDDegreeOfFreedomSequence'],
- '0x6490': ['FD', '3', 'ThreeDDegreeOfFreedomAxis'],
- '0x64A0': ['FD', '2', 'RangeOfFreedom'],
- '0x64C0': ['FD', '3', 'ThreeDMatingPoint'],
- '0x64D0': ['FD', '9', 'ThreeDMatingAxes'],
- '0x64F0': ['FD', '3', 'TwoDDegreeOfFreedomAxis'],
- '0x6500': ['SQ', '1', 'PlanningLandmarkPointSequence'],
- '0x6510': ['SQ', '1', 'PlanningLandmarkLineSequence'],
- '0x6520': ['SQ', '1', 'PlanningLandmarkPlaneSequence'],
- '0x6530': ['US', '1', 'PlanningLandmarkID'],
- '0x6540': ['LO', '1', 'PlanningLandmarkDescription'],
- '0x6545': ['SQ', '1', 'PlanningLandmarkIdentificationCodeSequence'],
- '0x6550': ['SQ', '1', 'TwoDPointCoordinatesSequence'],
- '0x6560': ['FD', '2', 'TwoDPointCoordinates'],
- '0x6590': ['FD', '3', 'ThreeDPointCoordinates'],
- '0x65A0': ['SQ', '1', 'TwoDLineCoordinatesSequence'],
- '0x65B0': ['FD', '4', 'TwoDLineCoordinates'],
- '0x65D0': ['FD', '6', 'ThreeDLineCoordinates'],
- '0x65E0': ['SQ', '1', 'TwoDPlaneCoordinatesSequence'],
- '0x65F0': ['FD', '4', 'TwoDPlaneIntersection'],
- '0x6610': ['FD', '3', 'ThreeDPlaneOrigin'],
- '0x6620': ['FD', '3', 'ThreeDPlaneNormal']
- },
- '0x0070': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['SQ', '1', 'GraphicAnnotationSequence'],
- '0x0002': ['CS', '1', 'GraphicLayer'],
- '0x0003': ['CS', '1', 'BoundingBoxAnnotationUnits'],
- '0x0004': ['CS', '1', 'AnchorPointAnnotationUnits'],
- '0x0005': ['CS', '1', 'GraphicAnnotationUnits'],
- '0x0006': ['ST', '1', 'UnformattedTextValue'],
- '0x0008': ['SQ', '1', 'TextObjectSequence'],
- '0x0009': ['SQ', '1', 'GraphicObjectSequence'],
- '0x0010': ['FL', '2', 'BoundingBoxTopLeftHandCorner'],
- '0x0011': ['FL', '2', 'BoundingBoxBottomRightHandCorner'],
- '0x0012': ['CS', '1', 'BoundingBoxTextHorizontalJustification'],
- '0x0014': ['FL', '2', 'AnchorPoint'],
- '0x0015': ['CS', '1', 'AnchorPointVisibility'],
- '0x0020': ['US', '1', 'GraphicDimensions'],
- '0x0021': ['US', '1', 'NumberOfGraphicPoints'],
- '0x0022': ['FL', '2-n', 'GraphicData'],
- '0x0023': ['CS', '1', 'GraphicType'],
- '0x0024': ['CS', '1', 'GraphicFilled'],
- '0x0040': ['IS', '1', 'ImageRotationRetired'],
- '0x0041': ['CS', '1', 'ImageHorizontalFlip'],
- '0x0042': ['US', '1', 'ImageRotation'],
- '0x0050': ['US', '2', 'DisplayedAreaTopLeftHandCornerTrial'],
- '0x0051': ['US', '2', 'DisplayedAreaBottomRightHandCornerTrial'],
- '0x0052': ['SL', '2', 'DisplayedAreaTopLeftHandCorner'],
- '0x0053': ['SL', '2', 'DisplayedAreaBottomRightHandCorner'],
- '0x005A': ['SQ', '1', 'DisplayedAreaSelectionSequence'],
- '0x0060': ['SQ', '1', 'GraphicLayerSequence'],
- '0x0062': ['IS', '1', 'GraphicLayerOrder'],
- '0x0066': ['US', '1', 'GraphicLayerRecommendedDisplayGrayscaleValue'],
- '0x0067': ['US', '3', 'GraphicLayerRecommendedDisplayRGBValue'],
- '0x0068': ['LO', '1', 'GraphicLayerDescription'],
- '0x0080': ['CS', '1', 'ContentLabel'],
- '0x0081': ['LO', '1', 'ContentDescription'],
- '0x0082': ['DA', '1', 'PresentationCreationDate'],
- '0x0083': ['TM', '1', 'PresentationCreationTime'],
- '0x0084': ['PN', '1', 'ContentCreatorName'],
- '0x0086': ['SQ', '1', 'ContentCreatorIdentificationCodeSequence'],
- '0x0087': ['SQ', '1', 'AlternateContentDescriptionSequence'],
- '0x0100': ['CS', '1', 'PresentationSizeMode'],
- '0x0101': ['DS', '2', 'PresentationPixelSpacing'],
- '0x0102': ['IS', '2', 'PresentationPixelAspectRatio'],
- '0x0103': ['FL', '1', 'PresentationPixelMagnificationRatio'],
- '0x0207': ['LO', '1', 'GraphicGroupLabel'],
- '0x0208': ['ST', '1', 'GraphicGroupDescription'],
- '0x0209': ['SQ', '1', 'CompoundGraphicSequence'],
- '0x0226': ['UL', '1', 'CompoundGraphicInstanceID'],
- '0x0227': ['LO', '1', 'FontName'],
- '0x0228': ['CS', '1', 'FontNameType'],
- '0x0229': ['LO', '1', 'CSSFontName'],
- '0x0230': ['FD', '1', 'RotationAngle'],
- '0x0231': ['SQ', '1', 'TextStyleSequence'],
- '0x0232': ['SQ', '1', 'LineStyleSequence'],
- '0x0233': ['SQ', '1', 'FillStyleSequence'],
- '0x0234': ['SQ', '1', 'GraphicGroupSequence'],
- '0x0241': ['US', '3', 'TextColorCIELabValue'],
- '0x0242': ['CS', '1', 'HorizontalAlignment'],
- '0x0243': ['CS', '1', 'VerticalAlignment'],
- '0x0244': ['CS', '1', 'ShadowStyle'],
- '0x0245': ['FL', '1', 'ShadowOffsetX'],
- '0x0246': ['FL', '1', 'ShadowOffsetY'],
- '0x0247': ['US', '3', 'ShadowColorCIELabValue'],
- '0x0248': ['CS', '1', 'Underlined'],
- '0x0249': ['CS', '1', 'Bold'],
- '0x0250': ['CS', '1', 'Italic'],
- '0x0251': ['US', '3', 'PatternOnColorCIELabValue'],
- '0x0252': ['US', '3', 'PatternOffColorCIELabValue'],
- '0x0253': ['FL', '1', 'LineThickness'],
- '0x0254': ['CS', '1', 'LineDashingStyle'],
- '0x0255': ['UL', '1', 'LinePattern'],
- '0x0256': ['OB', '1', 'FillPattern'],
- '0x0257': ['CS', '1', 'FillMode'],
- '0x0258': ['FL', '1', 'ShadowOpacity'],
- '0x0261': ['FL', '1', 'GapLength'],
- '0x0262': ['FL', '1', 'DiameterOfVisibility'],
- '0x0273': ['FL', '2', 'RotationPoint'],
- '0x0274': ['CS', '1', 'TickAlignment'],
- '0x0278': ['CS', '1', 'ShowTickLabel'],
- '0x0279': ['CS', '1', 'TickLabelAlignment'],
- '0x0282': ['CS', '1', 'CompoundGraphicUnits'],
- '0x0284': ['FL', '1', 'PatternOnOpacity'],
- '0x0285': ['FL', '1', 'PatternOffOpacity'],
- '0x0287': ['SQ', '1', 'MajorTicksSequence'],
- '0x0288': ['FL', '1', 'TickPosition'],
- '0x0289': ['SH', '1', 'TickLabel'],
- '0x0294': ['CS', '1', 'CompoundGraphicType'],
- '0x0295': ['UL', '1', 'GraphicGroupID'],
- '0x0306': ['CS', '1', 'ShapeType'],
- '0x0308': ['SQ', '1', 'RegistrationSequence'],
- '0x0309': ['SQ', '1', 'MatrixRegistrationSequence'],
- '0x030A': ['SQ', '1', 'MatrixSequence'],
- '0x030C': ['CS', '1', 'FrameOfReferenceTransformationMatrixType'],
- '0x030D': ['SQ', '1', 'RegistrationTypeCodeSequence'],
- '0x030F': ['ST', '1', 'FiducialDescription'],
- '0x0310': ['SH', '1', 'FiducialIdentifier'],
- '0x0311': ['SQ', '1', 'FiducialIdentifierCodeSequence'],
- '0x0312': ['FD', '1', 'ContourUncertaintyRadius'],
- '0x0314': ['SQ', '1', 'UsedFiducialsSequence'],
- '0x0318': ['SQ', '1', 'GraphicCoordinatesDataSequence'],
- '0x031A': ['UI', '1', 'FiducialUID'],
- '0x031C': ['SQ', '1', 'FiducialSetSequence'],
- '0x031E': ['SQ', '1', 'FiducialSequence'],
- '0x0401': ['US', '3', 'GraphicLayerRecommendedDisplayCIELabValue'],
- '0x0402': ['SQ', '1', 'BlendingSequence'],
- '0x0403': ['FL', '1', 'RelativeOpacity'],
- '0x0404': ['SQ', '1', 'ReferencedSpatialRegistrationSequence'],
- '0x0405': ['CS', '1', 'BlendingPosition']
- },
- '0x0072': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['SH', '1', 'HangingProtocolName'],
- '0x0004': ['LO', '1', 'HangingProtocolDescription'],
- '0x0006': ['CS', '1', 'HangingProtocolLevel'],
- '0x0008': ['LO', '1', 'HangingProtocolCreator'],
- '0x000A': ['DT', '1', 'HangingProtocolCreationDateTime'],
- '0x000C': ['SQ', '1', 'HangingProtocolDefinitionSequence'],
- '0x000E': ['SQ', '1', 'HangingProtocolUserIdentificationCodeSequence'],
- '0x0010': ['LO', '1', 'HangingProtocolUserGroupName'],
- '0x0012': ['SQ', '1', 'SourceHangingProtocolSequence'],
- '0x0014': ['US', '1', 'NumberOfPriorsReferenced'],
- '0x0020': ['SQ', '1', 'ImageSetsSequence'],
- '0x0022': ['SQ', '1', 'ImageSetSelectorSequence'],
- '0x0024': ['CS', '1', 'ImageSetSelectorUsageFlag'],
- '0x0026': ['AT', '1', 'SelectorAttribute'],
- '0x0028': ['US', '1', 'SelectorValueNumber'],
- '0x0030': ['SQ', '1', 'TimeBasedImageSetsSequence'],
- '0x0032': ['US', '1', 'ImageSetNumber'],
- '0x0034': ['CS', '1', 'ImageSetSelectorCategory'],
- '0x0038': ['US', '2', 'RelativeTime'],
- '0x003A': ['CS', '1', 'RelativeTimeUnits'],
- '0x003C': ['SS', '2', 'AbstractPriorValue'],
- '0x003E': ['SQ', '1', 'AbstractPriorCodeSequence'],
- '0x0040': ['LO', '1', 'ImageSetLabel'],
- '0x0050': ['CS', '1', 'SelectorAttributeVR'],
- '0x0052': ['AT', '1-n', 'SelectorSequencePointer'],
- '0x0054': ['LO', '1-n', 'SelectorSequencePointerPrivateCreator'],
- '0x0056': ['LO', '1', 'SelectorAttributePrivateCreator'],
- '0x0060': ['AT', '1-n', 'SelectorATValue'],
- '0x0062': ['CS', '1-n', 'SelectorCSValue'],
- '0x0064': ['IS', '1-n', 'SelectorISValue'],
- '0x0066': ['LO', '1-n', 'SelectorLOValue'],
- '0x0068': ['LT', '1', 'SelectorLTValue'],
- '0x006A': ['PN', '1-n', 'SelectorPNValue'],
- '0x006C': ['SH', '1-n', 'SelectorSHValue'],
- '0x006E': ['ST', '1', 'SelectorSTValue'],
- '0x0070': ['UT', '1', 'SelectorUTValue'],
- '0x0072': ['DS', '1-n', 'SelectorDSValue'],
- '0x0074': ['FD', '1-n', 'SelectorFDValue'],
- '0x0076': ['FL', '1-n', 'SelectorFLValue'],
- '0x0078': ['UL', '1-n', 'SelectorULValue'],
- '0x007A': ['US', '1-n', 'SelectorUSValue'],
- '0x007C': ['SL', '1-n', 'SelectorSLValue'],
- '0x007E': ['SS', '1-n', 'SelectorSSValue'],
- '0x007F': ['UI', '1-n', 'SelectorUIValue'],
- '0x0080': ['SQ', '1', 'SelectorCodeSequenceValue'],
- '0x0100': ['US', '1', 'NumberOfScreens'],
- '0x0102': ['SQ', '1', 'NominalScreenDefinitionSequence'],
- '0x0104': ['US', '1', 'NumberOfVerticalPixels'],
- '0x0106': ['US', '1', 'NumberOfHorizontalPixels'],
- '0x0108': ['FD', '4', 'DisplayEnvironmentSpatialPosition'],
- '0x010A': ['US', '1', 'ScreenMinimumGrayscaleBitDepth'],
- '0x010C': ['US', '1', 'ScreenMinimumColorBitDepth'],
- '0x010E': ['US', '1', 'ApplicationMaximumRepaintTime'],
- '0x0200': ['SQ', '1', 'DisplaySetsSequence'],
- '0x0202': ['US', '1', 'DisplaySetNumber'],
- '0x0203': ['LO', '1', 'DisplaySetLabel'],
- '0x0204': ['US', '1', 'DisplaySetPresentationGroup'],
- '0x0206': ['LO', '1', 'DisplaySetPresentationGroupDescription'],
- '0x0208': ['CS', '1', 'PartialDataDisplayHandling'],
- '0x0210': ['SQ', '1', 'SynchronizedScrollingSequence'],
- '0x0212': ['US', '2-n', 'DisplaySetScrollingGroup'],
- '0x0214': ['SQ', '1', 'NavigationIndicatorSequence'],
- '0x0216': ['US', '1', 'NavigationDisplaySet'],
- '0x0218': ['US', '1-n', 'ReferenceDisplaySets'],
- '0x0300': ['SQ', '1', 'ImageBoxesSequence'],
- '0x0302': ['US', '1', 'ImageBoxNumber'],
- '0x0304': ['CS', '1', 'ImageBoxLayoutType'],
- '0x0306': ['US', '1', 'ImageBoxTileHorizontalDimension'],
- '0x0308': ['US', '1', 'ImageBoxTileVerticalDimension'],
- '0x0310': ['CS', '1', 'ImageBoxScrollDirection'],
- '0x0312': ['CS', '1', 'ImageBoxSmallScrollType'],
- '0x0314': ['US', '1', 'ImageBoxSmallScrollAmount'],
- '0x0316': ['CS', '1', 'ImageBoxLargeScrollType'],
- '0x0318': ['US', '1', 'ImageBoxLargeScrollAmount'],
- '0x0320': ['US', '1', 'ImageBoxOverlapPriority'],
- '0x0330': ['FD', '1', 'CineRelativeToRealTime'],
- '0x0400': ['SQ', '1', 'FilterOperationsSequence'],
- '0x0402': ['CS', '1', 'FilterByCategory'],
- '0x0404': ['CS', '1', 'FilterByAttributePresence'],
- '0x0406': ['CS', '1', 'FilterByOperator'],
- '0x0420': ['US', '3', 'StructuredDisplayBackgroundCIELabValue'],
- '0x0421': ['US', '3', 'EmptyImageBoxCIELabValue'],
- '0x0422': ['SQ', '1', 'StructuredDisplayImageBoxSequence'],
- '0x0424': ['SQ', '1', 'StructuredDisplayTextBoxSequence'],
- '0x0427': ['SQ', '1', 'ReferencedFirstFrameSequence'],
- '0x0430': ['SQ', '1', 'ImageBoxSynchronizationSequence'],
- '0x0432': ['US', '2-n', 'SynchronizedImageBoxList'],
- '0x0434': ['CS', '1', 'TypeOfSynchronization'],
- '0x0500': ['CS', '1', 'BlendingOperationType'],
- '0x0510': ['CS', '1', 'ReformattingOperationType'],
- '0x0512': ['FD', '1', 'ReformattingThickness'],
- '0x0514': ['FD', '1', 'ReformattingInterval'],
- '0x0516': ['CS', '1', 'ReformattingOperationInitialViewDirection'],
- '0x0520': ['CS', '1-n', 'ThreeDRenderingType'],
- '0x0600': ['SQ', '1', 'SortingOperationsSequence'],
- '0x0602': ['CS', '1', 'SortByCategory'],
- '0x0604': ['CS', '1', 'SortingDirection'],
- '0x0700': ['CS', '2', 'DisplaySetPatientOrientation'],
- '0x0702': ['CS', '1', 'VOIType'],
- '0x0704': ['CS', '1', 'PseudoColorType'],
- '0x0705': ['SQ', '1', 'PseudoColorPaletteInstanceReferenceSequence'],
- '0x0706': ['CS', '1', 'ShowGrayscaleInverted'],
- '0x0710': ['CS', '1', 'ShowImageTrueSizeFlag'],
- '0x0712': ['CS', '1', 'ShowGraphicAnnotationFlag'],
- '0x0714': ['CS', '1', 'ShowPatientDemographicsFlag'],
- '0x0716': ['CS', '1', 'ShowAcquisitionTechniquesFlag'],
- '0x0717': ['CS', '1', 'DisplaySetHorizontalJustification'],
- '0x0718': ['CS', '1', 'DisplaySetVerticalJustification']
- },
- '0x0074': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0120': ['FD', '1', 'ContinuationStartMeterset'],
- '0x0121': ['FD', '1', 'ContinuationEndMeterset'],
- '0x1000': ['CS', '1', 'ProcedureStepState'],
- '0x1002': ['SQ', '1', 'ProcedureStepProgressInformationSequence'],
- '0x1004': ['DS', '1', 'ProcedureStepProgress'],
- '0x1006': ['ST', '1', 'ProcedureStepProgressDescription'],
- '0x1008': ['SQ', '1', 'ProcedureStepCommunicationsURISequence'],
- '0x100A': ['UR', '1', 'ContactURI'],
- '0x100C': ['LO', '1', 'ContactDisplayName'],
- '0x100E': ['SQ', '1', 'ProcedureStepDiscontinuationReasonCodeSequence'],
- '0x1020': ['SQ', '1', 'BeamTaskSequence'],
- '0x1022': ['CS', '1', 'BeamTaskType'],
- '0x1024': ['IS', '1', 'BeamOrderIndexTrial'],
- '0x1025': ['CS', '1', 'AutosequenceFlag'],
- '0x1026': ['FD', '1', 'TableTopVerticalAdjustedPosition'],
- '0x1027': ['FD', '1', 'TableTopLongitudinalAdjustedPosition'],
- '0x1028': ['FD', '1', 'TableTopLateralAdjustedPosition'],
- '0x102A': ['FD', '1', 'PatientSupportAdjustedAngle'],
- '0x102B': ['FD', '1', 'TableTopEccentricAdjustedAngle'],
- '0x102C': ['FD', '1', 'TableTopPitchAdjustedAngle'],
- '0x102D': ['FD', '1', 'TableTopRollAdjustedAngle'],
- '0x1030': ['SQ', '1', 'DeliveryVerificationImageSequence'],
- '0x1032': ['CS', '1', 'VerificationImageTiming'],
- '0x1034': ['CS', '1', 'DoubleExposureFlag'],
- '0x1036': ['CS', '1', 'DoubleExposureOrdering'],
- '0x1038': ['DS', '1', 'DoubleExposureMetersetTrial'],
- '0x103A': ['DS', '4', 'DoubleExposureFieldDeltaTrial'],
- '0x1040': ['SQ', '1', 'RelatedReferenceRTImageSequence'],
- '0x1042': ['SQ', '1', 'GeneralMachineVerificationSequence'],
- '0x1044': ['SQ', '1', 'ConventionalMachineVerificationSequence'],
- '0x1046': ['SQ', '1', 'IonMachineVerificationSequence'],
- '0x1048': ['SQ', '1', 'FailedAttributesSequence'],
- '0x104A': ['SQ', '1', 'OverriddenAttributesSequence'],
- '0x104C': ['SQ', '1', 'ConventionalControlPointVerificationSequence'],
- '0x104E': ['SQ', '1', 'IonControlPointVerificationSequence'],
- '0x1050': ['SQ', '1', 'AttributeOccurrenceSequence'],
- '0x1052': ['AT', '1', 'AttributeOccurrencePointer'],
- '0x1054': ['UL', '1', 'AttributeItemSelector'],
- '0x1056': ['LO', '1', 'AttributeOccurrencePrivateCreator'],
- '0x1057': ['IS', '1-n', 'SelectorSequencePointerItems'],
- '0x1200': ['CS', '1', 'ScheduledProcedureStepPriority'],
- '0x1202': ['LO', '1', 'WorklistLabel'],
- '0x1204': ['LO', '1', 'ProcedureStepLabel'],
- '0x1210': ['SQ', '1', 'ScheduledProcessingParametersSequence'],
- '0x1212': ['SQ', '1', 'PerformedProcessingParametersSequence'],
- '0x1216': ['SQ', '1', 'UnifiedProcedureStepPerformedProcedureSequence'],
- '0x1220': ['SQ', '1', 'RelatedProcedureStepSequence'],
- '0x1222': ['LO', '1', 'ProcedureStepRelationshipType'],
- '0x1224': ['SQ', '1', 'ReplacedProcedureStepSequence'],
- '0x1230': ['LO', '1', 'DeletionLock'],
- '0x1234': ['AE', '1', 'ReceivingAE'],
- '0x1236': ['AE', '1', 'RequestingAE'],
- '0x1238': ['LT', '1', 'ReasonForCancellation'],
- '0x1242': ['CS', '1', 'SCPStatus'],
- '0x1244': ['CS', '1', 'SubscriptionListStatus'],
- '0x1246': ['CS', '1', 'UnifiedProcedureStepListStatus'],
- '0x1324': ['UL', '1', 'BeamOrderIndex'],
- '0x1338': ['FD', '1', 'DoubleExposureMeterset'],
- '0x133A': ['FD', '4', 'DoubleExposureFieldDelta']
- },
- '0x0076': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['LO', '1', 'ImplantAssemblyTemplateName'],
- '0x0003': ['LO', '1', 'ImplantAssemblyTemplateIssuer'],
- '0x0006': ['LO', '1', 'ImplantAssemblyTemplateVersion'],
- '0x0008': ['SQ', '1', 'ReplacedImplantAssemblyTemplateSequence'],
- '0x000A': ['CS', '1', 'ImplantAssemblyTemplateType'],
- '0x000C': ['SQ', '1', 'OriginalImplantAssemblyTemplateSequence'],
- '0x000E': ['SQ', '1', 'DerivationImplantAssemblyTemplateSequence'],
- '0x0010': ['SQ', '1', 'ImplantAssemblyTemplateTargetAnatomySequence'],
- '0x0020': ['SQ', '1', 'ProcedureTypeCodeSequence'],
- '0x0030': ['LO', '1', 'SurgicalTechnique'],
- '0x0032': ['SQ', '1', 'ComponentTypesSequence'],
- '0x0034': ['CS', '1', 'ComponentTypeCodeSequence'],
- '0x0036': ['CS', '1', 'ExclusiveComponentType'],
- '0x0038': ['CS', '1', 'MandatoryComponentType'],
- '0x0040': ['SQ', '1', 'ComponentSequence'],
- '0x0055': ['US', '1', 'ComponentID'],
- '0x0060': ['SQ', '1', 'ComponentAssemblySequence'],
- '0x0070': ['US', '1', 'Component1ReferencedID'],
- '0x0080': ['US', '1', 'Component1ReferencedMatingFeatureSetID'],
- '0x0090': ['US', '1', 'Component1ReferencedMatingFeatureID'],
- '0x00A0': ['US', '1', 'Component2ReferencedID'],
- '0x00B0': ['US', '1', 'Component2ReferencedMatingFeatureSetID'],
- '0x00C0': ['US', '1', 'Component2ReferencedMatingFeatureID']
- },
- '0x0078': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['LO', '1', 'ImplantTemplateGroupName'],
- '0x0010': ['ST', '1', 'ImplantTemplateGroupDescription'],
- '0x0020': ['LO', '1', 'ImplantTemplateGroupIssuer'],
- '0x0024': ['LO', '1', 'ImplantTemplateGroupVersion'],
- '0x0026': ['SQ', '1', 'ReplacedImplantTemplateGroupSequence'],
- '0x0028': ['SQ', '1', 'ImplantTemplateGroupTargetAnatomySequence'],
- '0x002A': ['SQ', '1', 'ImplantTemplateGroupMembersSequence'],
- '0x002E': ['US', '1', 'ImplantTemplateGroupMemberID'],
- '0x0050': ['FD', '3', 'ThreeDImplantTemplateGroupMemberMatchingPoint'],
- '0x0060': ['FD', '9', 'ThreeDImplantTemplateGroupMemberMatchingAxes'],
- '0x0070': ['SQ', '1', 'ImplantTemplateGroupMemberMatching2DCoordinatesSequence'],
- '0x0090': ['FD', '2', 'TwoDImplantTemplateGroupMemberMatchingPoint'],
- '0x00A0': ['FD', '4', 'TwoDImplantTemplateGroupMemberMatchingAxes'],
- '0x00B0': ['SQ', '1', 'ImplantTemplateGroupVariationDimensionSequence'],
- '0x00B2': ['LO', '1', 'ImplantTemplateGroupVariationDimensionName'],
- '0x00B4': ['SQ', '1', 'ImplantTemplateGroupVariationDimensionRankSequence'],
- '0x00B6': ['US', '1', 'ReferencedImplantTemplateGroupMemberID'],
- '0x00B8': ['US', '1', 'ImplantTemplateGroupVariationDimensionRank']
- },
- '0x0080': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['SQ', '1', 'SurfaceScanAcquisitionTypeCodeSequence'],
- '0x0002': ['SQ', '1', 'SurfaceScanModeCodeSequence'],
- '0x0003': ['SQ', '1', 'RegistrationMethodCodeSequence'],
- '0x0004': ['FD', '1', 'ShotDurationTime'],
- '0x0005': ['FD', '1', 'ShotOffsetTime'],
- '0x0006': ['US', '1-n', 'SurfacePointPresentationValueData'],
- '0x0007': ['US', '3-3n', 'SurfacePointColorCIELabValueData'],
- '0x0008': ['SQ', '1', 'UVMappingSequence'],
- '0x0009': ['SH', '1', 'TextureLabel'],
- '0x0010': ['OF', '1-n', 'UValueData'],
- '0x0011': ['OF', '1-n', 'VValueData'],
- '0x0012': ['SQ', '1', 'ReferencedTextureSequence'],
- '0x0013': ['SQ', '1', 'ReferencedSurfaceDataSequence']
- },
- '0x0088': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0130': ['SH', '1', 'StorageMediaFileSetID'],
- '0x0140': ['UI', '1', 'StorageMediaFileSetUID'],
- '0x0200': ['SQ', '1', 'IconImageSequence'],
- '0x0904': ['LO', '1', 'TopicTitle'],
- '0x0906': ['ST', '1', 'TopicSubject'],
- '0x0910': ['LO', '1', 'TopicAuthor'],
- '0x0912': ['LO', '1-32', 'TopicKeywords']
- },
- '0x0100': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0410': ['CS', '1', 'SOPInstanceStatus'],
- '0x0420': ['DT', '1', 'SOPAuthorizationDateTime'],
- '0x0424': ['LT', '1', 'SOPAuthorizationComment'],
- '0x0426': ['LO', '1', 'AuthorizationEquipmentCertificationNumber']
- },
- '0x0400': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0005': ['US', '1', 'MACIDNumber'],
- '0x0010': ['UI', '1', 'MACCalculationTransferSyntaxUID'],
- '0x0015': ['CS', '1', 'MACAlgorithm'],
- '0x0020': ['AT', '1-n', 'DataElementsSigned'],
- '0x0100': ['UI', '1', 'DigitalSignatureUID'],
- '0x0105': ['DT', '1', 'DigitalSignatureDateTime'],
- '0x0110': ['CS', '1', 'CertificateType'],
- '0x0115': ['OB', '1', 'CertificateOfSigner'],
- '0x0120': ['OB', '1', 'Signature'],
- '0x0305': ['CS', '1', 'CertifiedTimestampType'],
- '0x0310': ['OB', '1', 'CertifiedTimestamp'],
- '0x0401': ['SQ', '1', 'DigitalSignaturePurposeCodeSequence'],
- '0x0402': ['SQ', '1', 'ReferencedDigitalSignatureSequence'],
- '0x0403': ['SQ', '1', 'ReferencedSOPInstanceMACSequence'],
- '0x0404': ['OB', '1', 'MAC'],
- '0x0500': ['SQ', '1', 'EncryptedAttributesSequence'],
- '0x0510': ['UI', '1', 'EncryptedContentTransferSyntaxUID'],
- '0x0520': ['OB', '1', 'EncryptedContent'],
- '0x0550': ['SQ', '1', 'ModifiedAttributesSequence'],
- '0x0561': ['SQ', '1', 'OriginalAttributesSequence'],
- '0x0562': ['DT', '1', 'AttributeModificationDateTime'],
- '0x0563': ['LO', '1', 'ModifyingSystem'],
- '0x0564': ['LO', '1', 'SourceOfPreviousValues'],
- '0x0565': ['CS', '1', 'ReasonForTheAttributeModification']
- },
- '0x1000': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['US', '3', 'EscapeTriplet'],
- '0x0011': ['US', '3', 'RunLengthTriplet'],
- '0x0012': ['US', '1', 'HuffmanTableSize'],
- '0x0013': ['US', '3', 'HuffmanTableTriplet'],
- '0x0014': ['US', '1', 'ShiftTableSize'],
- '0x0015': ['US', '3', 'ShiftTableTriplet']
- },
- '0x1010': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0004': ['US', '1-n', 'ZonalMap']
- },
- '0x2000': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['IS', '1', 'NumberOfCopies'],
- '0x001E': ['SQ', '1', 'PrinterConfigurationSequence'],
- '0x0020': ['CS', '1', 'PrintPriority'],
- '0x0030': ['CS', '1', 'MediumType'],
- '0x0040': ['CS', '1', 'FilmDestination'],
- '0x0050': ['LO', '1', 'FilmSessionLabel'],
- '0x0060': ['IS', '1', 'MemoryAllocation'],
- '0x0061': ['IS', '1', 'MaximumMemoryAllocation'],
- '0x0062': ['CS', '1', 'ColorImagePrintingFlag'],
- '0x0063': ['CS', '1', 'CollationFlag'],
- '0x0065': ['CS', '1', 'AnnotationFlag'],
- '0x0067': ['CS', '1', 'ImageOverlayFlag'],
- '0x0069': ['CS', '1', 'PresentationLUTFlag'],
- '0x006A': ['CS', '1', 'ImageBoxPresentationLUTFlag'],
- '0x00A0': ['US', '1', 'MemoryBitDepth'],
- '0x00A1': ['US', '1', 'PrintingBitDepth'],
- '0x00A2': ['SQ', '1', 'MediaInstalledSequence'],
- '0x00A4': ['SQ', '1', 'OtherMediaAvailableSequence'],
- '0x00A8': ['SQ', '1', 'SupportedImageDisplayFormatsSequence'],
- '0x0500': ['SQ', '1', 'ReferencedFilmBoxSequence'],
- '0x0510': ['SQ', '1', 'ReferencedStoredPrintSequence']
- },
- '0x2010': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['ST', '1', 'ImageDisplayFormat'],
- '0x0030': ['CS', '1', 'AnnotationDisplayFormatID'],
- '0x0040': ['CS', '1', 'FilmOrientation'],
- '0x0050': ['CS', '1', 'FilmSizeID'],
- '0x0052': ['CS', '1', 'PrinterResolutionID'],
- '0x0054': ['CS', '1', 'DefaultPrinterResolutionID'],
- '0x0060': ['CS', '1', 'MagnificationType'],
- '0x0080': ['CS', '1', 'SmoothingType'],
- '0x00A6': ['CS', '1', 'DefaultMagnificationType'],
- '0x00A7': ['CS', '1-n', 'OtherMagnificationTypesAvailable'],
- '0x00A8': ['CS', '1', 'DefaultSmoothingType'],
- '0x00A9': ['CS', '1-n', 'OtherSmoothingTypesAvailable'],
- '0x0100': ['CS', '1', 'BorderDensity'],
- '0x0110': ['CS', '1', 'EmptyImageDensity'],
- '0x0120': ['US', '1', 'MinDensity'],
- '0x0130': ['US', '1', 'MaxDensity'],
- '0x0140': ['CS', '1', 'Trim'],
- '0x0150': ['ST', '1', 'ConfigurationInformation'],
- '0x0152': ['LT', '1', 'ConfigurationInformationDescription'],
- '0x0154': ['IS', '1', 'MaximumCollatedFilms'],
- '0x015E': ['US', '1', 'Illumination'],
- '0x0160': ['US', '1', 'ReflectedAmbientLight'],
- '0x0376': ['DS', '2', 'PrinterPixelSpacing'],
- '0x0500': ['SQ', '1', 'ReferencedFilmSessionSequence'],
- '0x0510': ['SQ', '1', 'ReferencedImageBoxSequence'],
- '0x0520': ['SQ', '1', 'ReferencedBasicAnnotationBoxSequence']
- },
- '0x2020': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['US', '1', 'ImageBoxPosition'],
- '0x0020': ['CS', '1', 'Polarity'],
- '0x0030': ['DS', '1', 'RequestedImageSize'],
- '0x0040': ['CS', '1', 'RequestedDecimateCropBehavior'],
- '0x0050': ['CS', '1', 'RequestedResolutionID'],
- '0x00A0': ['CS', '1', 'RequestedImageSizeFlag'],
- '0x00A2': ['CS', '1', 'DecimateCropResult'],
- '0x0110': ['SQ', '1', 'BasicGrayscaleImageSequence'],
- '0x0111': ['SQ', '1', 'BasicColorImageSequence'],
- '0x0130': ['SQ', '1', 'ReferencedImageOverlayBoxSequence'],
- '0x0140': ['SQ', '1', 'ReferencedVOILUTBoxSequence']
- },
- '0x2030': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['US', '1', 'AnnotationPosition'],
- '0x0020': ['LO', '1', 'TextString']
- },
- '0x2040': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['SQ', '1', 'ReferencedOverlayPlaneSequence'],
- '0x0011': ['US', '1-99', 'ReferencedOverlayPlaneGroups'],
- '0x0020': ['SQ', '1', 'OverlayPixelDataSequence'],
- '0x0060': ['CS', '1', 'OverlayMagnificationType'],
- '0x0070': ['CS', '1', 'OverlaySmoothingType'],
- '0x0072': ['CS', '1', 'OverlayOrImageMagnification'],
- '0x0074': ['US', '1', 'MagnifyToNumberOfColumns'],
- '0x0080': ['CS', '1', 'OverlayForegroundDensity'],
- '0x0082': ['CS', '1', 'OverlayBackgroundDensity'],
- '0x0090': ['CS', '1', 'OverlayMode'],
- '0x0100': ['CS', '1', 'ThresholdDensity'],
- '0x0500': ['SQ', '1', 'ReferencedImageBoxSequenceRetired']
- },
- '0x2050': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['SQ', '1', 'PresentationLUTSequence'],
- '0x0020': ['CS', '1', 'PresentationLUTShape'],
- '0x0500': ['SQ', '1', 'ReferencedPresentationLUTSequence']
- },
- '0x2100': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['SH', '1', 'PrintJobID'],
- '0x0020': ['CS', '1', 'ExecutionStatus'],
- '0x0030': ['CS', '1', 'ExecutionStatusInfo'],
- '0x0040': ['DA', '1', 'CreationDate'],
- '0x0050': ['TM', '1', 'CreationTime'],
- '0x0070': ['AE', '1', 'Originator'],
- '0x0140': ['AE', '1', 'DestinationAE'],
- '0x0160': ['SH', '1', 'OwnerID'],
- '0x0170': ['IS', '1', 'NumberOfFilms'],
- '0x0500': ['SQ', '1', 'ReferencedPrintJobSequencePullStoredPrint']
- },
- '0x2110': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['CS', '1', 'PrinterStatus'],
- '0x0020': ['CS', '1', 'PrinterStatusInfo'],
- '0x0030': ['LO', '1', 'PrinterName'],
- '0x0099': ['SH', '1', 'PrintQueueID']
- },
- '0x2120': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['CS', '1', 'QueueStatus'],
- '0x0050': ['SQ', '1', 'PrintJobDescriptionSequence'],
- '0x0070': ['SQ', '1', 'ReferencedPrintJobSequence']
- },
- '0x2130': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['SQ', '1', 'PrintManagementCapabilitiesSequence'],
- '0x0015': ['SQ', '1', 'PrinterCharacteristicsSequence'],
- '0x0030': ['SQ', '1', 'FilmBoxContentSequence'],
- '0x0040': ['SQ', '1', 'ImageBoxContentSequence'],
- '0x0050': ['SQ', '1', 'AnnotationContentSequence'],
- '0x0060': ['SQ', '1', 'ImageOverlayBoxContentSequence'],
- '0x0080': ['SQ', '1', 'PresentationLUTContentSequence'],
- '0x00A0': ['SQ', '1', 'ProposedStudySequence'],
- '0x00C0': ['SQ', '1', 'OriginalImageSequence']
- },
- '0x2200': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['CS', '1', 'LabelUsingInformationExtractedFromInstances'],
- '0x0002': ['UT', '1', 'LabelText'],
- '0x0003': ['CS', '1', 'LabelStyleSelection'],
- '0x0004': ['LT', '1', 'MediaDisposition'],
- '0x0005': ['LT', '1', 'BarcodeValue'],
- '0x0006': ['CS', '1', 'BarcodeSymbology'],
- '0x0007': ['CS', '1', 'AllowMediaSplitting'],
- '0x0008': ['CS', '1', 'IncludeNonDICOMObjects'],
- '0x0009': ['CS', '1', 'IncludeDisplayApplication'],
- '0x000A': ['CS', '1', 'PreserveCompositeInstancesAfterMediaCreation'],
- '0x000B': ['US', '1', 'TotalNumberOfPiecesOfMediaCreated'],
- '0x000C': ['LO', '1', 'RequestedMediaApplicationProfile'],
- '0x000D': ['SQ', '1', 'ReferencedStorageMediaSequence'],
- '0x000E': ['AT', '1-n', 'FailureAttributes'],
- '0x000F': ['CS', '1', 'AllowLossyCompression'],
- '0x0020': ['CS', '1', 'RequestPriority']
- },
- '0x3002': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['SH', '1', 'RTImageLabel'],
- '0x0003': ['LO', '1', 'RTImageName'],
- '0x0004': ['ST', '1', 'RTImageDescription'],
- '0x000A': ['CS', '1', 'ReportedValuesOrigin'],
- '0x000C': ['CS', '1', 'RTImagePlane'],
- '0x000D': ['DS', '3', 'XRayImageReceptorTranslation'],
- '0x000E': ['DS', '1', 'XRayImageReceptorAngle'],
- '0x0010': ['DS', '6', 'RTImageOrientation'],
- '0x0011': ['DS', '2', 'ImagePlanePixelSpacing'],
- '0x0012': ['DS', '2', 'RTImagePosition'],
- '0x0020': ['SH', '1', 'RadiationMachineName'],
- '0x0022': ['DS', '1', 'RadiationMachineSAD'],
- '0x0024': ['DS', '1', 'RadiationMachineSSD'],
- '0x0026': ['DS', '1', 'RTImageSID'],
- '0x0028': ['DS', '1', 'SourceToReferenceObjectDistance'],
- '0x0029': ['IS', '1', 'FractionNumber'],
- '0x0030': ['SQ', '1', 'ExposureSequence'],
- '0x0032': ['DS', '1', 'MetersetExposure'],
- '0x0034': ['DS', '4', 'DiaphragmPosition'],
- '0x0040': ['SQ', '1', 'FluenceMapSequence'],
- '0x0041': ['CS', '1', 'FluenceDataSource'],
- '0x0042': ['DS', '1', 'FluenceDataScale'],
- '0x0050': ['SQ', '1', 'PrimaryFluenceModeSequence'],
- '0x0051': ['CS', '1', 'FluenceMode'],
- '0x0052': ['SH', '1', 'FluenceModeID']
- },
- '0x3004': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['CS', '1', 'DVHType'],
- '0x0002': ['CS', '1', 'DoseUnits'],
- '0x0004': ['CS', '1', 'DoseType'],
- '0x0005': ['CS', '1', 'SpatialTransformOfDose'],
- '0x0006': ['LO', '1', 'DoseComment'],
- '0x0008': ['DS', '3', 'NormalizationPoint'],
- '0x000A': ['CS', '1', 'DoseSummationType'],
- '0x000C': ['DS', '2-n', 'GridFrameOffsetVector'],
- '0x000E': ['DS', '1', 'DoseGridScaling'],
- '0x0010': ['SQ', '1', 'RTDoseROISequence'],
- '0x0012': ['DS', '1', 'DoseValue'],
- '0x0014': ['CS', '1-3', 'TissueHeterogeneityCorrection'],
- '0x0040': ['DS', '3', 'DVHNormalizationPoint'],
- '0x0042': ['DS', '1', 'DVHNormalizationDoseValue'],
- '0x0050': ['SQ', '1', 'DVHSequence'],
- '0x0052': ['DS', '1', 'DVHDoseScaling'],
- '0x0054': ['CS', '1', 'DVHVolumeUnits'],
- '0x0056': ['IS', '1', 'DVHNumberOfBins'],
- '0x0058': ['DS', '2-2n', 'DVHData'],
- '0x0060': ['SQ', '1', 'DVHReferencedROISequence'],
- '0x0062': ['CS', '1', 'DVHROIContributionType'],
- '0x0070': ['DS', '1', 'DVHMinimumDose'],
- '0x0072': ['DS', '1', 'DVHMaximumDose'],
- '0x0074': ['DS', '1', 'DVHMeanDose']
- },
- '0x3006': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['SH', '1', 'StructureSetLabel'],
- '0x0004': ['LO', '1', 'StructureSetName'],
- '0x0006': ['ST', '1', 'StructureSetDescription'],
- '0x0008': ['DA', '1', 'StructureSetDate'],
- '0x0009': ['TM', '1', 'StructureSetTime'],
- '0x0010': ['SQ', '1', 'ReferencedFrameOfReferenceSequence'],
- '0x0012': ['SQ', '1', 'RTReferencedStudySequence'],
- '0x0014': ['SQ', '1', 'RTReferencedSeriesSequence'],
- '0x0016': ['SQ', '1', 'ContourImageSequence'],
- '0x0018': ['SQ', '1', 'PredecessorStructureSetSequence'],
- '0x0020': ['SQ', '1', 'StructureSetROISequence'],
- '0x0022': ['IS', '1', 'ROINumber'],
- '0x0024': ['UI', '1', 'ReferencedFrameOfReferenceUID'],
- '0x0026': ['LO', '1', 'ROIName'],
- '0x0028': ['ST', '1', 'ROIDescription'],
- '0x002A': ['IS', '3', 'ROIDisplayColor'],
- '0x002C': ['DS', '1', 'ROIVolume'],
- '0x0030': ['SQ', '1', 'RTRelatedROISequence'],
- '0x0033': ['CS', '1', 'RTROIRelationship'],
- '0x0036': ['CS', '1', 'ROIGenerationAlgorithm'],
- '0x0038': ['LO', '1', 'ROIGenerationDescription'],
- '0x0039': ['SQ', '1', 'ROIContourSequence'],
- '0x0040': ['SQ', '1', 'ContourSequence'],
- '0x0042': ['CS', '1', 'ContourGeometricType'],
- '0x0044': ['DS', '1', 'ContourSlabThickness'],
- '0x0045': ['DS', '3', 'ContourOffsetVector'],
- '0x0046': ['IS', '1', 'NumberOfContourPoints'],
- '0x0048': ['IS', '1', 'ContourNumber'],
- '0x0049': ['IS', '1-n', 'AttachedContours'],
- '0x0050': ['DS', '3-3n', 'ContourData'],
- '0x0080': ['SQ', '1', 'RTROIObservationsSequence'],
- '0x0082': ['IS', '1', 'ObservationNumber'],
- '0x0084': ['IS', '1', 'ReferencedROINumber'],
- '0x0085': ['SH', '1', 'ROIObservationLabel'],
- '0x0086': ['SQ', '1', 'RTROIIdentificationCodeSequence'],
- '0x0088': ['ST', '1', 'ROIObservationDescription'],
- '0x00A0': ['SQ', '1', 'RelatedRTROIObservationsSequence'],
- '0x00A4': ['CS', '1', 'RTROIInterpretedType'],
- '0x00A6': ['PN', '1', 'ROIInterpreter'],
- '0x00B0': ['SQ', '1', 'ROIPhysicalPropertiesSequence'],
- '0x00B2': ['CS', '1', 'ROIPhysicalProperty'],
- '0x00B4': ['DS', '1', 'ROIPhysicalPropertyValue'],
- '0x00B6': ['SQ', '1', 'ROIElementalCompositionSequence'],
- '0x00B7': ['US', '1', 'ROIElementalCompositionAtomicNumber'],
- '0x00B8': ['FL', '1', 'ROIElementalCompositionAtomicMassFraction'],
- '0x00B9': ['SQ', '1', 'AdditionalRTROIIdentificationCodeSequence'],
- '0x00C0': ['SQ', '1', 'FrameOfReferenceRelationshipSequence'],
- '0x00C2': ['UI', '1', 'RelatedFrameOfReferenceUID'],
- '0x00C4': ['CS', '1', 'FrameOfReferenceTransformationType'],
- '0x00C6': ['DS', '16', 'FrameOfReferenceTransformationMatrix'],
- '0x00C8': ['LO', '1', 'FrameOfReferenceTransformationComment']
- },
- '0x3008': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['SQ', '1', 'MeasuredDoseReferenceSequence'],
- '0x0012': ['ST', '1', 'MeasuredDoseDescription'],
- '0x0014': ['CS', '1', 'MeasuredDoseType'],
- '0x0016': ['DS', '1', 'MeasuredDoseValue'],
- '0x0020': ['SQ', '1', 'TreatmentSessionBeamSequence'],
- '0x0021': ['SQ', '1', 'TreatmentSessionIonBeamSequence'],
- '0x0022': ['IS', '1', 'CurrentFractionNumber'],
- '0x0024': ['DA', '1', 'TreatmentControlPointDate'],
- '0x0025': ['TM', '1', 'TreatmentControlPointTime'],
- '0x002A': ['CS', '1', 'TreatmentTerminationStatus'],
- '0x002B': ['SH', '1', 'TreatmentTerminationCode'],
- '0x002C': ['CS', '1', 'TreatmentVerificationStatus'],
- '0x0030': ['SQ', '1', 'ReferencedTreatmentRecordSequence'],
- '0x0032': ['DS', '1', 'SpecifiedPrimaryMeterset'],
- '0x0033': ['DS', '1', 'SpecifiedSecondaryMeterset'],
- '0x0036': ['DS', '1', 'DeliveredPrimaryMeterset'],
- '0x0037': ['DS', '1', 'DeliveredSecondaryMeterset'],
- '0x003A': ['DS', '1', 'SpecifiedTreatmentTime'],
- '0x003B': ['DS', '1', 'DeliveredTreatmentTime'],
- '0x0040': ['SQ', '1', 'ControlPointDeliverySequence'],
- '0x0041': ['SQ', '1', 'IonControlPointDeliverySequence'],
- '0x0042': ['DS', '1', 'SpecifiedMeterset'],
- '0x0044': ['DS', '1', 'DeliveredMeterset'],
- '0x0045': ['FL', '1', 'MetersetRateSet'],
- '0x0046': ['FL', '1', 'MetersetRateDelivered'],
- '0x0047': ['FL', '1-n', 'ScanSpotMetersetsDelivered'],
- '0x0048': ['DS', '1', 'DoseRateDelivered'],
- '0x0050': ['SQ', '1', 'TreatmentSummaryCalculatedDoseReferenceSequence'],
- '0x0052': ['DS', '1', 'CumulativeDoseToDoseReference'],
- '0x0054': ['DA', '1', 'FirstTreatmentDate'],
- '0x0056': ['DA', '1', 'MostRecentTreatmentDate'],
- '0x005A': ['IS', '1', 'NumberOfFractionsDelivered'],
- '0x0060': ['SQ', '1', 'OverrideSequence'],
- '0x0061': ['AT', '1', 'ParameterSequencePointer'],
- '0x0062': ['AT', '1', 'OverrideParameterPointer'],
- '0x0063': ['IS', '1', 'ParameterItemIndex'],
- '0x0064': ['IS', '1', 'MeasuredDoseReferenceNumber'],
- '0x0065': ['AT', '1', 'ParameterPointer'],
- '0x0066': ['ST', '1', 'OverrideReason'],
- '0x0068': ['SQ', '1', 'CorrectedParameterSequence'],
- '0x006A': ['FL', '1', 'CorrectionValue'],
- '0x0070': ['SQ', '1', 'CalculatedDoseReferenceSequence'],
- '0x0072': ['IS', '1', 'CalculatedDoseReferenceNumber'],
- '0x0074': ['ST', '1', 'CalculatedDoseReferenceDescription'],
- '0x0076': ['DS', '1', 'CalculatedDoseReferenceDoseValue'],
- '0x0078': ['DS', '1', 'StartMeterset'],
- '0x007A': ['DS', '1', 'EndMeterset'],
- '0x0080': ['SQ', '1', 'ReferencedMeasuredDoseReferenceSequence'],
- '0x0082': ['IS', '1', 'ReferencedMeasuredDoseReferenceNumber'],
- '0x0090': ['SQ', '1', 'ReferencedCalculatedDoseReferenceSequence'],
- '0x0092': ['IS', '1', 'ReferencedCalculatedDoseReferenceNumber'],
- '0x00A0': ['SQ', '1', 'BeamLimitingDeviceLeafPairsSequence'],
- '0x00B0': ['SQ', '1', 'RecordedWedgeSequence'],
- '0x00C0': ['SQ', '1', 'RecordedCompensatorSequence'],
- '0x00D0': ['SQ', '1', 'RecordedBlockSequence'],
- '0x00E0': ['SQ', '1', 'TreatmentSummaryMeasuredDoseReferenceSequence'],
- '0x00F0': ['SQ', '1', 'RecordedSnoutSequence'],
- '0x00F2': ['SQ', '1', 'RecordedRangeShifterSequence'],
- '0x00F4': ['SQ', '1', 'RecordedLateralSpreadingDeviceSequence'],
- '0x00F6': ['SQ', '1', 'RecordedRangeModulatorSequence'],
- '0x0100': ['SQ', '1', 'RecordedSourceSequence'],
- '0x0105': ['LO', '1', 'SourceSerialNumber'],
- '0x0110': ['SQ', '1', 'TreatmentSessionApplicationSetupSequence'],
- '0x0116': ['CS', '1', 'ApplicationSetupCheck'],
- '0x0120': ['SQ', '1', 'RecordedBrachyAccessoryDeviceSequence'],
- '0x0122': ['IS', '1', 'ReferencedBrachyAccessoryDeviceNumber'],
- '0x0130': ['SQ', '1', 'RecordedChannelSequence'],
- '0x0132': ['DS', '1', 'SpecifiedChannelTotalTime'],
- '0x0134': ['DS', '1', 'DeliveredChannelTotalTime'],
- '0x0136': ['IS', '1', 'SpecifiedNumberOfPulses'],
- '0x0138': ['IS', '1', 'DeliveredNumberOfPulses'],
- '0x013A': ['DS', '1', 'SpecifiedPulseRepetitionInterval'],
- '0x013C': ['DS', '1', 'DeliveredPulseRepetitionInterval'],
- '0x0140': ['SQ', '1', 'RecordedSourceApplicatorSequence'],
- '0x0142': ['IS', '1', 'ReferencedSourceApplicatorNumber'],
- '0x0150': ['SQ', '1', 'RecordedChannelShieldSequence'],
- '0x0152': ['IS', '1', 'ReferencedChannelShieldNumber'],
- '0x0160': ['SQ', '1', 'BrachyControlPointDeliveredSequence'],
- '0x0162': ['DA', '1', 'SafePositionExitDate'],
- '0x0164': ['TM', '1', 'SafePositionExitTime'],
- '0x0166': ['DA', '1', 'SafePositionReturnDate'],
- '0x0168': ['TM', '1', 'SafePositionReturnTime'],
- '0x0171': ['SQ', '1', 'PulseSpecificBrachyControlPointDeliveredSequence'],
- '0x0172': ['US', '1', 'PulseNumber'],
- '0x0173': ['SQ', '1', 'BrachyPulseControlPointDeliveredSequence'],
- '0x0200': ['CS', '1', 'CurrentTreatmentStatus'],
- '0x0202': ['ST', '1', 'TreatmentStatusComment'],
- '0x0220': ['SQ', '1', 'FractionGroupSummarySequence'],
- '0x0223': ['IS', '1', 'ReferencedFractionNumber'],
- '0x0224': ['CS', '1', 'FractionGroupType'],
- '0x0230': ['CS', '1', 'BeamStopperPosition'],
- '0x0240': ['SQ', '1', 'FractionStatusSummarySequence'],
- '0x0250': ['DA', '1', 'TreatmentDate'],
- '0x0251': ['TM', '1', 'TreatmentTime']
- },
- '0x300A': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['SH', '1', 'RTPlanLabel'],
- '0x0003': ['LO', '1', 'RTPlanName'],
- '0x0004': ['ST', '1', 'RTPlanDescription'],
- '0x0006': ['DA', '1', 'RTPlanDate'],
- '0x0007': ['TM', '1', 'RTPlanTime'],
- '0x0009': ['LO', '1-n', 'TreatmentProtocols'],
- '0x000A': ['CS', '1', 'PlanIntent'],
- '0x000B': ['LO', '1-n', 'TreatmentSites'],
- '0x000C': ['CS', '1', 'RTPlanGeometry'],
- '0x000E': ['ST', '1', 'PrescriptionDescription'],
- '0x0010': ['SQ', '1', 'DoseReferenceSequence'],
- '0x0012': ['IS', '1', 'DoseReferenceNumber'],
- '0x0013': ['UI', '1', 'DoseReferenceUID'],
- '0x0014': ['CS', '1', 'DoseReferenceStructureType'],
- '0x0015': ['CS', '1', 'NominalBeamEnergyUnit'],
- '0x0016': ['LO', '1', 'DoseReferenceDescription'],
- '0x0018': ['DS', '3', 'DoseReferencePointCoordinates'],
- '0x001A': ['DS', '1', 'NominalPriorDose'],
- '0x0020': ['CS', '1', 'DoseReferenceType'],
- '0x0021': ['DS', '1', 'ConstraintWeight'],
- '0x0022': ['DS', '1', 'DeliveryWarningDose'],
- '0x0023': ['DS', '1', 'DeliveryMaximumDose'],
- '0x0025': ['DS', '1', 'TargetMinimumDose'],
- '0x0026': ['DS', '1', 'TargetPrescriptionDose'],
- '0x0027': ['DS', '1', 'TargetMaximumDose'],
- '0x0028': ['DS', '1', 'TargetUnderdoseVolumeFraction'],
- '0x002A': ['DS', '1', 'OrganAtRiskFullVolumeDose'],
- '0x002B': ['DS', '1', 'OrganAtRiskLimitDose'],
- '0x002C': ['DS', '1', 'OrganAtRiskMaximumDose'],
- '0x002D': ['DS', '1', 'OrganAtRiskOverdoseVolumeFraction'],
- '0x0040': ['SQ', '1', 'ToleranceTableSequence'],
- '0x0042': ['IS', '1', 'ToleranceTableNumber'],
- '0x0043': ['SH', '1', 'ToleranceTableLabel'],
- '0x0044': ['DS', '1', 'GantryAngleTolerance'],
- '0x0046': ['DS', '1', 'BeamLimitingDeviceAngleTolerance'],
- '0x0048': ['SQ', '1', 'BeamLimitingDeviceToleranceSequence'],
- '0x004A': ['DS', '1', 'BeamLimitingDevicePositionTolerance'],
- '0x004B': ['FL', '1', 'SnoutPositionTolerance'],
- '0x004C': ['DS', '1', 'PatientSupportAngleTolerance'],
- '0x004E': ['DS', '1', 'TableTopEccentricAngleTolerance'],
- '0x004F': ['FL', '1', 'TableTopPitchAngleTolerance'],
- '0x0050': ['FL', '1', 'TableTopRollAngleTolerance'],
- '0x0051': ['DS', '1', 'TableTopVerticalPositionTolerance'],
- '0x0052': ['DS', '1', 'TableTopLongitudinalPositionTolerance'],
- '0x0053': ['DS', '1', 'TableTopLateralPositionTolerance'],
- '0x0055': ['CS', '1', 'RTPlanRelationship'],
- '0x0070': ['SQ', '1', 'FractionGroupSequence'],
- '0x0071': ['IS', '1', 'FractionGroupNumber'],
- '0x0072': ['LO', '1', 'FractionGroupDescription'],
- '0x0078': ['IS', '1', 'NumberOfFractionsPlanned'],
- '0x0079': ['IS', '1', 'NumberOfFractionPatternDigitsPerDay'],
- '0x007A': ['IS', '1', 'RepeatFractionCycleLength'],
- '0x007B': ['LT', '1', 'FractionPattern'],
- '0x0080': ['IS', '1', 'NumberOfBeams'],
- '0x0082': ['DS', '3', 'BeamDoseSpecificationPoint'],
- '0x0084': ['DS', '1', 'BeamDose'],
- '0x0086': ['DS', '1', 'BeamMeterset'],
- '0x0088': ['FL', '1', 'BeamDosePointDepth'],
- '0x0089': ['FL', '1', 'BeamDosePointEquivalentDepth'],
- '0x008A': ['FL', '1', 'BeamDosePointSSD'],
- '0x008B': ['CS', '1', 'BeamDoseMeaning'],
- '0x008C': ['SQ', '1', 'BeamDoseVerificationControlPointSequence'],
- '0x008D': ['FL', '1', 'AverageBeamDosePointDepth'],
- '0x008E': ['FL', '1', 'AverageBeamDosePointEquivalentDepth'],
- '0x008F': ['FL', '1', 'AverageBeamDosePointSSD'],
- '0x00A0': ['IS', '1', 'NumberOfBrachyApplicationSetups'],
- '0x00A2': ['DS', '3', 'BrachyApplicationSetupDoseSpecificationPoint'],
- '0x00A4': ['DS', '1', 'BrachyApplicationSetupDose'],
- '0x00B0': ['SQ', '1', 'BeamSequence'],
- '0x00B2': ['SH', '1', 'TreatmentMachineName'],
- '0x00B3': ['CS', '1', 'PrimaryDosimeterUnit'],
- '0x00B4': ['DS', '1', 'SourceAxisDistance'],
- '0x00B6': ['SQ', '1', 'BeamLimitingDeviceSequence'],
- '0x00B8': ['CS', '1', 'RTBeamLimitingDeviceType'],
- '0x00BA': ['DS', '1', 'SourceToBeamLimitingDeviceDistance'],
- '0x00BB': ['FL', '1', 'IsocenterToBeamLimitingDeviceDistance'],
- '0x00BC': ['IS', '1', 'NumberOfLeafJawPairs'],
- '0x00BE': ['DS', '3-n', 'LeafPositionBoundaries'],
- '0x00C0': ['IS', '1', 'BeamNumber'],
- '0x00C2': ['LO', '1', 'BeamName'],
- '0x00C3': ['ST', '1', 'BeamDescription'],
- '0x00C4': ['CS', '1', 'BeamType'],
- '0x00C5': ['FD', '1', 'BeamDeliveryDurationLimit'],
- '0x00C6': ['CS', '1', 'RadiationType'],
- '0x00C7': ['CS', '1', 'HighDoseTechniqueType'],
- '0x00C8': ['IS', '1', 'ReferenceImageNumber'],
- '0x00CA': ['SQ', '1', 'PlannedVerificationImageSequence'],
- '0x00CC': ['LO', '1-n', 'ImagingDeviceSpecificAcquisitionParameters'],
- '0x00CE': ['CS', '1', 'TreatmentDeliveryType'],
- '0x00D0': ['IS', '1', 'NumberOfWedges'],
- '0x00D1': ['SQ', '1', 'WedgeSequence'],
- '0x00D2': ['IS', '1', 'WedgeNumber'],
- '0x00D3': ['CS', '1', 'WedgeType'],
- '0x00D4': ['SH', '1', 'WedgeID'],
- '0x00D5': ['IS', '1', 'WedgeAngle'],
- '0x00D6': ['DS', '1', 'WedgeFactor'],
- '0x00D7': ['FL', '1', 'TotalWedgeTrayWaterEquivalentThickness'],
- '0x00D8': ['DS', '1', 'WedgeOrientation'],
- '0x00D9': ['FL', '1', 'IsocenterToWedgeTrayDistance'],
- '0x00DA': ['DS', '1', 'SourceToWedgeTrayDistance'],
- '0x00DB': ['FL', '1', 'WedgeThinEdgePosition'],
- '0x00DC': ['SH', '1', 'BolusID'],
- '0x00DD': ['ST', '1', 'BolusDescription'],
- '0x00DE': ['DS', '1', 'EffectiveWedgeAngle'],
- '0x00E0': ['IS', '1', 'NumberOfCompensators'],
- '0x00E1': ['SH', '1', 'MaterialID'],
- '0x00E2': ['DS', '1', 'TotalCompensatorTrayFactor'],
- '0x00E3': ['SQ', '1', 'CompensatorSequence'],
- '0x00E4': ['IS', '1', 'CompensatorNumber'],
- '0x00E5': ['SH', '1', 'CompensatorID'],
- '0x00E6': ['DS', '1', 'SourceToCompensatorTrayDistance'],
- '0x00E7': ['IS', '1', 'CompensatorRows'],
- '0x00E8': ['IS', '1', 'CompensatorColumns'],
- '0x00E9': ['DS', '2', 'CompensatorPixelSpacing'],
- '0x00EA': ['DS', '2', 'CompensatorPosition'],
- '0x00EB': ['DS', '1-n', 'CompensatorTransmissionData'],
- '0x00EC': ['DS', '1-n', 'CompensatorThicknessData'],
- '0x00ED': ['IS', '1', 'NumberOfBoli'],
- '0x00EE': ['CS', '1', 'CompensatorType'],
- '0x00EF': ['SH', '1', 'CompensatorTrayID'],
- '0x00F0': ['IS', '1', 'NumberOfBlocks'],
- '0x00F2': ['DS', '1', 'TotalBlockTrayFactor'],
- '0x00F3': ['FL', '1', 'TotalBlockTrayWaterEquivalentThickness'],
- '0x00F4': ['SQ', '1', 'BlockSequence'],
- '0x00F5': ['SH', '1', 'BlockTrayID'],
- '0x00F6': ['DS', '1', 'SourceToBlockTrayDistance'],
- '0x00F7': ['FL', '1', 'IsocenterToBlockTrayDistance'],
- '0x00F8': ['CS', '1', 'BlockType'],
- '0x00F9': ['LO', '1', 'AccessoryCode'],
- '0x00FA': ['CS', '1', 'BlockDivergence'],
- '0x00FB': ['CS', '1', 'BlockMountingPosition'],
- '0x00FC': ['IS', '1', 'BlockNumber'],
- '0x00FE': ['LO', '1', 'BlockName'],
- '0x0100': ['DS', '1', 'BlockThickness'],
- '0x0102': ['DS', '1', 'BlockTransmission'],
- '0x0104': ['IS', '1', 'BlockNumberOfPoints'],
- '0x0106': ['DS', '2-2n', 'BlockData'],
- '0x0107': ['SQ', '1', 'ApplicatorSequence'],
- '0x0108': ['SH', '1', 'ApplicatorID'],
- '0x0109': ['CS', '1', 'ApplicatorType'],
- '0x010A': ['LO', '1', 'ApplicatorDescription'],
- '0x010C': ['DS', '1', 'CumulativeDoseReferenceCoefficient'],
- '0x010E': ['DS', '1', 'FinalCumulativeMetersetWeight'],
- '0x0110': ['IS', '1', 'NumberOfControlPoints'],
- '0x0111': ['SQ', '1', 'ControlPointSequence'],
- '0x0112': ['IS', '1', 'ControlPointIndex'],
- '0x0114': ['DS', '1', 'NominalBeamEnergy'],
- '0x0115': ['DS', '1', 'DoseRateSet'],
- '0x0116': ['SQ', '1', 'WedgePositionSequence'],
- '0x0118': ['CS', '1', 'WedgePosition'],
- '0x011A': ['SQ', '1', 'BeamLimitingDevicePositionSequence'],
- '0x011C': ['DS', '2-2n', 'LeafJawPositions'],
- '0x011E': ['DS', '1', 'GantryAngle'],
- '0x011F': ['CS', '1', 'GantryRotationDirection'],
- '0x0120': ['DS', '1', 'BeamLimitingDeviceAngle'],
- '0x0121': ['CS', '1', 'BeamLimitingDeviceRotationDirection'],
- '0x0122': ['DS', '1', 'PatientSupportAngle'],
- '0x0123': ['CS', '1', 'PatientSupportRotationDirection'],
- '0x0124': ['DS', '1', 'TableTopEccentricAxisDistance'],
- '0x0125': ['DS', '1', 'TableTopEccentricAngle'],
- '0x0126': ['CS', '1', 'TableTopEccentricRotationDirection'],
- '0x0128': ['DS', '1', 'TableTopVerticalPosition'],
- '0x0129': ['DS', '1', 'TableTopLongitudinalPosition'],
- '0x012A': ['DS', '1', 'TableTopLateralPosition'],
- '0x012C': ['DS', '3', 'IsocenterPosition'],
- '0x012E': ['DS', '3', 'SurfaceEntryPoint'],
- '0x0130': ['DS', '1', 'SourceToSurfaceDistance'],
- '0x0131': ['FL', '1', 'AverageBeamDosePointSourceToExternalContourSurfaceDistance'],
- '0x0132': ['FL', '1', 'SourceToExternalContourDistance'],
- '0x0133': ['FL', '3', 'ExternalContourEntryPoint'],
- '0x0134': ['DS', '1', 'CumulativeMetersetWeight'],
- '0x0140': ['FL', '1', 'TableTopPitchAngle'],
- '0x0142': ['CS', '1', 'TableTopPitchRotationDirection'],
- '0x0144': ['FL', '1', 'TableTopRollAngle'],
- '0x0146': ['CS', '1', 'TableTopRollRotationDirection'],
- '0x0148': ['FL', '1', 'HeadFixationAngle'],
- '0x014A': ['FL', '1', 'GantryPitchAngle'],
- '0x014C': ['CS', '1', 'GantryPitchRotationDirection'],
- '0x014E': ['FL', '1', 'GantryPitchAngleTolerance'],
- '0x0180': ['SQ', '1', 'PatientSetupSequence'],
- '0x0182': ['IS', '1', 'PatientSetupNumber'],
- '0x0183': ['LO', '1', 'PatientSetupLabel'],
- '0x0184': ['LO', '1', 'PatientAdditionalPosition'],
- '0x0190': ['SQ', '1', 'FixationDeviceSequence'],
- '0x0192': ['CS', '1', 'FixationDeviceType'],
- '0x0194': ['SH', '1', 'FixationDeviceLabel'],
- '0x0196': ['ST', '1', 'FixationDeviceDescription'],
- '0x0198': ['SH', '1', 'FixationDevicePosition'],
- '0x0199': ['FL', '1', 'FixationDevicePitchAngle'],
- '0x019A': ['FL', '1', 'FixationDeviceRollAngle'],
- '0x01A0': ['SQ', '1', 'ShieldingDeviceSequence'],
- '0x01A2': ['CS', '1', 'ShieldingDeviceType'],
- '0x01A4': ['SH', '1', 'ShieldingDeviceLabel'],
- '0x01A6': ['ST', '1', 'ShieldingDeviceDescription'],
- '0x01A8': ['SH', '1', 'ShieldingDevicePosition'],
- '0x01B0': ['CS', '1', 'SetupTechnique'],
- '0x01B2': ['ST', '1', 'SetupTechniqueDescription'],
- '0x01B4': ['SQ', '1', 'SetupDeviceSequence'],
- '0x01B6': ['CS', '1', 'SetupDeviceType'],
- '0x01B8': ['SH', '1', 'SetupDeviceLabel'],
- '0x01BA': ['ST', '1', 'SetupDeviceDescription'],
- '0x01BC': ['DS', '1', 'SetupDeviceParameter'],
- '0x01D0': ['ST', '1', 'SetupReferenceDescription'],
- '0x01D2': ['DS', '1', 'TableTopVerticalSetupDisplacement'],
- '0x01D4': ['DS', '1', 'TableTopLongitudinalSetupDisplacement'],
- '0x01D6': ['DS', '1', 'TableTopLateralSetupDisplacement'],
- '0x0200': ['CS', '1', 'BrachyTreatmentTechnique'],
- '0x0202': ['CS', '1', 'BrachyTreatmentType'],
- '0x0206': ['SQ', '1', 'TreatmentMachineSequence'],
- '0x0210': ['SQ', '1', 'SourceSequence'],
- '0x0212': ['IS', '1', 'SourceNumber'],
- '0x0214': ['CS', '1', 'SourceType'],
- '0x0216': ['LO', '1', 'SourceManufacturer'],
- '0x0218': ['DS', '1', 'ActiveSourceDiameter'],
- '0x021A': ['DS', '1', 'ActiveSourceLength'],
- '0x021B': ['SH', '1', 'SourceModelID'],
- '0x021C': ['LO', '1', 'SourceDescription'],
- '0x0222': ['DS', '1', 'SourceEncapsulationNominalThickness'],
- '0x0224': ['DS', '1', 'SourceEncapsulationNominalTransmission'],
- '0x0226': ['LO', '1', 'SourceIsotopeName'],
- '0x0228': ['DS', '1', 'SourceIsotopeHalfLife'],
- '0x0229': ['CS', '1', 'SourceStrengthUnits'],
- '0x022A': ['DS', '1', 'ReferenceAirKermaRate'],
- '0x022B': ['DS', '1', 'SourceStrength'],
- '0x022C': ['DA', '1', 'SourceStrengthReferenceDate'],
- '0x022E': ['TM', '1', 'SourceStrengthReferenceTime'],
- '0x0230': ['SQ', '1', 'ApplicationSetupSequence'],
- '0x0232': ['CS', '1', 'ApplicationSetupType'],
- '0x0234': ['IS', '1', 'ApplicationSetupNumber'],
- '0x0236': ['LO', '1', 'ApplicationSetupName'],
- '0x0238': ['LO', '1', 'ApplicationSetupManufacturer'],
- '0x0240': ['IS', '1', 'TemplateNumber'],
- '0x0242': ['SH', '1', 'TemplateType'],
- '0x0244': ['LO', '1', 'TemplateName'],
- '0x0250': ['DS', '1', 'TotalReferenceAirKerma'],
- '0x0260': ['SQ', '1', 'BrachyAccessoryDeviceSequence'],
- '0x0262': ['IS', '1', 'BrachyAccessoryDeviceNumber'],
- '0x0263': ['SH', '1', 'BrachyAccessoryDeviceID'],
- '0x0264': ['CS', '1', 'BrachyAccessoryDeviceType'],
- '0x0266': ['LO', '1', 'BrachyAccessoryDeviceName'],
- '0x026A': ['DS', '1', 'BrachyAccessoryDeviceNominalThickness'],
- '0x026C': ['DS', '1', 'BrachyAccessoryDeviceNominalTransmission'],
- '0x0280': ['SQ', '1', 'ChannelSequence'],
- '0x0282': ['IS', '1', 'ChannelNumber'],
- '0x0284': ['DS', '1', 'ChannelLength'],
- '0x0286': ['DS', '1', 'ChannelTotalTime'],
- '0x0288': ['CS', '1', 'SourceMovementType'],
- '0x028A': ['IS', '1', 'NumberOfPulses'],
- '0x028C': ['DS', '1', 'PulseRepetitionInterval'],
- '0x0290': ['IS', '1', 'SourceApplicatorNumber'],
- '0x0291': ['SH', '1', 'SourceApplicatorID'],
- '0x0292': ['CS', '1', 'SourceApplicatorType'],
- '0x0294': ['LO', '1', 'SourceApplicatorName'],
- '0x0296': ['DS', '1', 'SourceApplicatorLength'],
- '0x0298': ['LO', '1', 'SourceApplicatorManufacturer'],
- '0x029C': ['DS', '1', 'SourceApplicatorWallNominalThickness'],
- '0x029E': ['DS', '1', 'SourceApplicatorWallNominalTransmission'],
- '0x02A0': ['DS', '1', 'SourceApplicatorStepSize'],
- '0x02A2': ['IS', '1', 'TransferTubeNumber'],
- '0x02A4': ['DS', '1', 'TransferTubeLength'],
- '0x02B0': ['SQ', '1', 'ChannelShieldSequence'],
- '0x02B2': ['IS', '1', 'ChannelShieldNumber'],
- '0x02B3': ['SH', '1', 'ChannelShieldID'],
- '0x02B4': ['LO', '1', 'ChannelShieldName'],
- '0x02B8': ['DS', '1', 'ChannelShieldNominalThickness'],
- '0x02BA': ['DS', '1', 'ChannelShieldNominalTransmission'],
- '0x02C8': ['DS', '1', 'FinalCumulativeTimeWeight'],
- '0x02D0': ['SQ', '1', 'BrachyControlPointSequence'],
- '0x02D2': ['DS', '1', 'ControlPointRelativePosition'],
- '0x02D4': ['DS', '3', 'ControlPoint3DPosition'],
- '0x02D6': ['DS', '1', 'CumulativeTimeWeight'],
- '0x02E0': ['CS', '1', 'CompensatorDivergence'],
- '0x02E1': ['CS', '1', 'CompensatorMountingPosition'],
- '0x02E2': ['DS', '1-n', 'SourceToCompensatorDistance'],
- '0x02E3': ['FL', '1', 'TotalCompensatorTrayWaterEquivalentThickness'],
- '0x02E4': ['FL', '1', 'IsocenterToCompensatorTrayDistance'],
- '0x02E5': ['FL', '1', 'CompensatorColumnOffset'],
- '0x02E6': ['FL', '1-n', 'IsocenterToCompensatorDistances'],
- '0x02E7': ['FL', '1', 'CompensatorRelativeStoppingPowerRatio'],
- '0x02E8': ['FL', '1', 'CompensatorMillingToolDiameter'],
- '0x02EA': ['SQ', '1', 'IonRangeCompensatorSequence'],
- '0x02EB': ['LT', '1', 'CompensatorDescription'],
- '0x0302': ['IS', '1', 'RadiationMassNumber'],
- '0x0304': ['IS', '1', 'RadiationAtomicNumber'],
- '0x0306': ['SS', '1', 'RadiationChargeState'],
- '0x0308': ['CS', '1', 'ScanMode'],
- '0x030A': ['FL', '2', 'VirtualSourceAxisDistances'],
- '0x030C': ['SQ', '1', 'SnoutSequence'],
- '0x030D': ['FL', '1', 'SnoutPosition'],
- '0x030F': ['SH', '1', 'SnoutID'],
- '0x0312': ['IS', '1', 'NumberOfRangeShifters'],
- '0x0314': ['SQ', '1', 'RangeShifterSequence'],
- '0x0316': ['IS', '1', 'RangeShifterNumber'],
- '0x0318': ['SH', '1', 'RangeShifterID'],
- '0x0320': ['CS', '1', 'RangeShifterType'],
- '0x0322': ['LO', '1', 'RangeShifterDescription'],
- '0x0330': ['IS', '1', 'NumberOfLateralSpreadingDevices'],
- '0x0332': ['SQ', '1', 'LateralSpreadingDeviceSequence'],
- '0x0334': ['IS', '1', 'LateralSpreadingDeviceNumber'],
- '0x0336': ['SH', '1', 'LateralSpreadingDeviceID'],
- '0x0338': ['CS', '1', 'LateralSpreadingDeviceType'],
- '0x033A': ['LO', '1', 'LateralSpreadingDeviceDescription'],
- '0x033C': ['FL', '1', 'LateralSpreadingDeviceWaterEquivalentThickness'],
- '0x0340': ['IS', '1', 'NumberOfRangeModulators'],
- '0x0342': ['SQ', '1', 'RangeModulatorSequence'],
- '0x0344': ['IS', '1', 'RangeModulatorNumber'],
- '0x0346': ['SH', '1', 'RangeModulatorID'],
- '0x0348': ['CS', '1', 'RangeModulatorType'],
- '0x034A': ['LO', '1', 'RangeModulatorDescription'],
- '0x034C': ['SH', '1', 'BeamCurrentModulationID'],
- '0x0350': ['CS', '1', 'PatientSupportType'],
- '0x0352': ['SH', '1', 'PatientSupportID'],
- '0x0354': ['LO', '1', 'PatientSupportAccessoryCode'],
- '0x0356': ['FL', '1', 'FixationLightAzimuthalAngle'],
- '0x0358': ['FL', '1', 'FixationLightPolarAngle'],
- '0x035A': ['FL', '1', 'MetersetRate'],
- '0x0360': ['SQ', '1', 'RangeShifterSettingsSequence'],
- '0x0362': ['LO', '1', 'RangeShifterSetting'],
- '0x0364': ['FL', '1', 'IsocenterToRangeShifterDistance'],
- '0x0366': ['FL', '1', 'RangeShifterWaterEquivalentThickness'],
- '0x0370': ['SQ', '1', 'LateralSpreadingDeviceSettingsSequence'],
- '0x0372': ['LO', '1', 'LateralSpreadingDeviceSetting'],
- '0x0374': ['FL', '1', 'IsocenterToLateralSpreadingDeviceDistance'],
- '0x0380': ['SQ', '1', 'RangeModulatorSettingsSequence'],
- '0x0382': ['FL', '1', 'RangeModulatorGatingStartValue'],
- '0x0384': ['FL', '1', 'RangeModulatorGatingStopValue'],
- '0x0386': ['FL', '1', 'RangeModulatorGatingStartWaterEquivalentThickness'],
- '0x0388': ['FL', '1', 'RangeModulatorGatingStopWaterEquivalentThickness'],
- '0x038A': ['FL', '1', 'IsocenterToRangeModulatorDistance'],
- '0x0390': ['SH', '1', 'ScanSpotTuneID'],
- '0x0392': ['IS', '1', 'NumberOfScanSpotPositions'],
- '0x0394': ['FL', '1-n', 'ScanSpotPositionMap'],
- '0x0396': ['FL', '1-n', 'ScanSpotMetersetWeights'],
- '0x0398': ['FL', '2', 'ScanningSpotSize'],
- '0x039A': ['IS', '1', 'NumberOfPaintings'],
- '0x03A0': ['SQ', '1', 'IonToleranceTableSequence'],
- '0x03A2': ['SQ', '1', 'IonBeamSequence'],
- '0x03A4': ['SQ', '1', 'IonBeamLimitingDeviceSequence'],
- '0x03A6': ['SQ', '1', 'IonBlockSequence'],
- '0x03A8': ['SQ', '1', 'IonControlPointSequence'],
- '0x03AA': ['SQ', '1', 'IonWedgeSequence'],
- '0x03AC': ['SQ', '1', 'IonWedgePositionSequence'],
- '0x0401': ['SQ', '1', 'ReferencedSetupImageSequence'],
- '0x0402': ['ST', '1', 'SetupImageComment'],
- '0x0410': ['SQ', '1', 'MotionSynchronizationSequence'],
- '0x0412': ['FL', '3', 'ControlPointOrientation'],
- '0x0420': ['SQ', '1', 'GeneralAccessorySequence'],
- '0x0421': ['SH', '1', 'GeneralAccessoryID'],
- '0x0422': ['ST', '1', 'GeneralAccessoryDescription'],
- '0x0423': ['CS', '1', 'GeneralAccessoryType'],
- '0x0424': ['IS', '1', 'GeneralAccessoryNumber'],
- '0x0425': ['FL', '1', 'SourceToGeneralAccessoryDistance'],
- '0x0431': ['SQ', '1', 'ApplicatorGeometrySequence'],
- '0x0432': ['CS', '1', 'ApplicatorApertureShape'],
- '0x0433': ['FL', '1', 'ApplicatorOpening'],
- '0x0434': ['FL', '1', 'ApplicatorOpeningX'],
- '0x0435': ['FL', '1', 'ApplicatorOpeningY'],
- '0x0436': ['FL', '1', 'SourceToApplicatorMountingPositionDistance'],
- '0x0440': ['IS', '1', 'NumberOfBlockSlabItems'],
- '0x0441': ['SQ', '1', 'BlockSlabSequence'],
- '0x0442': ['DS', '1', 'BlockSlabThickness'],
- '0x0443': ['US', '1', 'BlockSlabNumber'],
- '0x0450': ['SQ', '1', 'DeviceMotionControlSequence'],
- '0x0451': ['CS', '1', 'DeviceMotionExecutionMode'],
- '0x0452': ['CS', '1', 'DeviceMotionObservationMode'],
- '0x0453': ['SQ', '1', 'DeviceMotionParameterCodeSequence']
- },
- '0x300C': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['SQ', '1', 'ReferencedRTPlanSequence'],
- '0x0004': ['SQ', '1', 'ReferencedBeamSequence'],
- '0x0006': ['IS', '1', 'ReferencedBeamNumber'],
- '0x0007': ['IS', '1', 'ReferencedReferenceImageNumber'],
- '0x0008': ['DS', '1', 'StartCumulativeMetersetWeight'],
- '0x0009': ['DS', '1', 'EndCumulativeMetersetWeight'],
- '0x000A': ['SQ', '1', 'ReferencedBrachyApplicationSetupSequence'],
- '0x000C': ['IS', '1', 'ReferencedBrachyApplicationSetupNumber'],
- '0x000E': ['IS', '1', 'ReferencedSourceNumber'],
- '0x0020': ['SQ', '1', 'ReferencedFractionGroupSequence'],
- '0x0022': ['IS', '1', 'ReferencedFractionGroupNumber'],
- '0x0040': ['SQ', '1', 'ReferencedVerificationImageSequence'],
- '0x0042': ['SQ', '1', 'ReferencedReferenceImageSequence'],
- '0x0050': ['SQ', '1', 'ReferencedDoseReferenceSequence'],
- '0x0051': ['IS', '1', 'ReferencedDoseReferenceNumber'],
- '0x0055': ['SQ', '1', 'BrachyReferencedDoseReferenceSequence'],
- '0x0060': ['SQ', '1', 'ReferencedStructureSetSequence'],
- '0x006A': ['IS', '1', 'ReferencedPatientSetupNumber'],
- '0x0080': ['SQ', '1', 'ReferencedDoseSequence'],
- '0x00A0': ['IS', '1', 'ReferencedToleranceTableNumber'],
- '0x00B0': ['SQ', '1', 'ReferencedBolusSequence'],
- '0x00C0': ['IS', '1', 'ReferencedWedgeNumber'],
- '0x00D0': ['IS', '1', 'ReferencedCompensatorNumber'],
- '0x00E0': ['IS', '1', 'ReferencedBlockNumber'],
- '0x00F0': ['IS', '1', 'ReferencedControlPointIndex'],
- '0x00F2': ['SQ', '1', 'ReferencedControlPointSequence'],
- '0x00F4': ['IS', '1', 'ReferencedStartControlPointIndex'],
- '0x00F6': ['IS', '1', 'ReferencedStopControlPointIndex'],
- '0x0100': ['IS', '1', 'ReferencedRangeShifterNumber'],
- '0x0102': ['IS', '1', 'ReferencedLateralSpreadingDeviceNumber'],
- '0x0104': ['IS', '1', 'ReferencedRangeModulatorNumber'],
- '0x0111': ['SQ', '1', 'OmittedBeamTaskSequence'],
- '0x0112': ['CS', '1', 'ReasonForOmission'],
- '0x0113': ['LO', '1', 'ReasonForOmissionDescription']
- },
- '0x300E': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0002': ['CS', '1', 'ApprovalStatus'],
- '0x0004': ['DA', '1', 'ReviewDate'],
- '0x0005': ['TM', '1', 'ReviewTime'],
- '0x0008': ['PN', '1', 'ReviewerName']
- },
- '0x4000': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['LT', '1', 'Arbitrary'],
- '0x4000': ['LT', '1', 'TextComments']
- },
- '0x4008': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0040': ['SH', '1', 'ResultsID'],
- '0x0042': ['LO', '1', 'ResultsIDIssuer'],
- '0x0050': ['SQ', '1', 'ReferencedInterpretationSequence'],
- '0x00FF': ['CS', '1', 'ReportProductionStatusTrial'],
- '0x0100': ['DA', '1', 'InterpretationRecordedDate'],
- '0x0101': ['TM', '1', 'InterpretationRecordedTime'],
- '0x0102': ['PN', '1', 'InterpretationRecorder'],
- '0x0103': ['LO', '1', 'ReferenceToRecordedSound'],
- '0x0108': ['DA', '1', 'InterpretationTranscriptionDate'],
- '0x0109': ['TM', '1', 'InterpretationTranscriptionTime'],
- '0x010A': ['PN', '1', 'InterpretationTranscriber'],
- '0x010B': ['ST', '1', 'InterpretationText'],
- '0x010C': ['PN', '1', 'InterpretationAuthor'],
- '0x0111': ['SQ', '1', 'InterpretationApproverSequence'],
- '0x0112': ['DA', '1', 'InterpretationApprovalDate'],
- '0x0113': ['TM', '1', 'InterpretationApprovalTime'],
- '0x0114': ['PN', '1', 'PhysicianApprovingInterpretation'],
- '0x0115': ['LT', '1', 'InterpretationDiagnosisDescription'],
- '0x0117': ['SQ', '1', 'InterpretationDiagnosisCodeSequence'],
- '0x0118': ['SQ', '1', 'ResultsDistributionListSequence'],
- '0x0119': ['PN', '1', 'DistributionName'],
- '0x011A': ['LO', '1', 'DistributionAddress'],
- '0x0200': ['SH', '1', 'InterpretationID'],
- '0x0202': ['LO', '1', 'InterpretationIDIssuer'],
- '0x0210': ['CS', '1', 'InterpretationTypeID'],
- '0x0212': ['CS', '1', 'InterpretationStatusID'],
- '0x0300': ['ST', '1', 'Impressions'],
- '0x4000': ['ST', '1', 'ResultsComments']
- },
- '0x4010': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['CS', '1', 'LowEnergyDetectors'],
- '0x0002': ['CS', '1', 'HighEnergyDetectors'],
- '0x0004': ['SQ', '1', 'DetectorGeometrySequence'],
- '0x1001': ['SQ', '1', 'ThreatROIVoxelSequence'],
- '0x1004': ['FL', '3', 'ThreatROIBase'],
- '0x1005': ['FL', '3', 'ThreatROIExtents'],
- '0x1006': ['OB', '1', 'ThreatROIBitmap'],
- '0x1007': ['SH', '1', 'RouteSegmentID'],
- '0x1008': ['CS', '1', 'GantryType'],
- '0x1009': ['CS', '1', 'OOIOwnerType'],
- '0x100A': ['SQ', '1', 'RouteSegmentSequence'],
- '0x1010': ['US', '1', 'PotentialThreatObjectID'],
- '0x1011': ['SQ', '1', 'ThreatSequence'],
- '0x1012': ['CS', '1', 'ThreatCategory'],
- '0x1013': ['LT', '1', 'ThreatCategoryDescription'],
- '0x1014': ['CS', '1', 'ATDAbilityAssessment'],
- '0x1015': ['CS', '1', 'ATDAssessmentFlag'],
- '0x1016': ['FL', '1', 'ATDAssessmentProbability'],
- '0x1017': ['FL', '1', 'Mass'],
- '0x1018': ['FL', '1', 'Density'],
- '0x1019': ['FL', '1', 'ZEffective'],
- '0x101A': ['SH', '1', 'BoardingPassID'],
- '0x101B': ['FL', '3', 'CenterOfMass'],
- '0x101C': ['FL', '3', 'CenterOfPTO'],
- '0x101D': ['FL', '6-n', 'BoundingPolygon'],
- '0x101E': ['SH', '1', 'RouteSegmentStartLocationID'],
- '0x101F': ['SH', '1', 'RouteSegmentEndLocationID'],
- '0x1020': ['CS', '1', 'RouteSegmentLocationIDType'],
- '0x1021': ['CS', '1-n', 'AbortReason'],
- '0x1023': ['FL', '1', 'VolumeOfPTO'],
- '0x1024': ['CS', '1', 'AbortFlag'],
- '0x1025': ['DT', '1', 'RouteSegmentStartTime'],
- '0x1026': ['DT', '1', 'RouteSegmentEndTime'],
- '0x1027': ['CS', '1', 'TDRType'],
- '0x1028': ['CS', '1', 'InternationalRouteSegment'],
- '0x1029': ['LO', '1-n', 'ThreatDetectionAlgorithmandVersion'],
- '0x102A': ['SH', '1', 'AssignedLocation'],
- '0x102B': ['DT', '1', 'AlarmDecisionTime'],
- '0x1031': ['CS', '1', 'AlarmDecision'],
- '0x1033': ['US', '1', 'NumberOfTotalObjects'],
- '0x1034': ['US', '1', 'NumberOfAlarmObjects'],
- '0x1037': ['SQ', '1', 'PTORepresentationSequence'],
- '0x1038': ['SQ', '1', 'ATDAssessmentSequence'],
- '0x1039': ['CS', '1', 'TIPType'],
- '0x103A': ['CS', '1', 'DICOSVersion'],
- '0x1041': ['DT', '1', 'OOIOwnerCreationTime'],
- '0x1042': ['CS', '1', 'OOIType'],
- '0x1043': ['FL', '3', 'OOISize'],
- '0x1044': ['CS', '1', 'AcquisitionStatus'],
- '0x1045': ['SQ', '1', 'BasisMaterialsCodeSequence'],
- '0x1046': ['CS', '1', 'PhantomType'],
- '0x1047': ['SQ', '1', 'OOIOwnerSequence'],
- '0x1048': ['CS', '1', 'ScanType'],
- '0x1051': ['LO', '1', 'ItineraryID'],
- '0x1052': ['SH', '1', 'ItineraryIDType'],
- '0x1053': ['LO', '1', 'ItineraryIDAssigningAuthority'],
- '0x1054': ['SH', '1', 'RouteID'],
- '0x1055': ['SH', '1', 'RouteIDAssigningAuthority'],
- '0x1056': ['CS', '1', 'InboundArrivalType'],
- '0x1058': ['SH', '1', 'CarrierID'],
- '0x1059': ['CS', '1', 'CarrierIDAssigningAuthority'],
- '0x1060': ['FL', '3', 'SourceOrientation'],
- '0x1061': ['FL', '3', 'SourcePosition'],
- '0x1062': ['FL', '1', 'BeltHeight'],
- '0x1064': ['SQ', '1', 'AlgorithmRoutingCodeSequence'],
- '0x1067': ['CS', '1', 'TransportClassification'],
- '0x1068': ['LT', '1', 'OOITypeDescriptor'],
- '0x1069': ['FL', '1', 'TotalProcessingTime'],
- '0x106C': ['OB', '1', 'DetectorCalibrationData'],
- '0x106D': ['CS', '1', 'AdditionalScreeningPerformed'],
- '0x106E': ['CS', '1', 'AdditionalInspectionSelectionCriteria'],
- '0x106F': ['SQ', '1', 'AdditionalInspectionMethodSequence'],
- '0x1070': ['CS', '1', 'AITDeviceType'],
- '0x1071': ['SQ', '1', 'QRMeasurementsSequence'],
- '0x1072': ['SQ', '1', 'TargetMaterialSequence'],
- '0x1073': ['FD', '1', 'SNRThreshold'],
- '0x1075': ['DS', '1', 'ImageScaleRepresentation'],
- '0x1076': ['SQ', '1', 'ReferencedPTOSequence'],
- '0x1077': ['SQ', '1', 'ReferencedTDRInstanceSequence'],
- '0x1078': ['ST', '1', 'PTOLocationDescription'],
- '0x1079': ['SQ', '1', 'AnomalyLocatorIndicatorSequence'],
- '0x107A': ['FL', '3', 'AnomalyLocatorIndicator'],
- '0x107B': ['SQ', '1', 'PTORegionSequence'],
- '0x107C': ['CS', '1', 'InspectionSelectionCriteria'],
- '0x107D': ['SQ', '1', 'SecondaryInspectionMethodSequence'],
- '0x107E': ['DS', '6', 'PRCSToRCSOrientation']
- },
- '0x4FFE': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0001': ['SQ', '1', 'MACParametersSequence']
- },
- '0x5000': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0005': ['US', '1', 'CurveDimensions'],
- '0x0010': ['US', '1', 'NumberOfPoints'],
- '0x0020': ['CS', '1', 'TypeOfData'],
- '0x0022': ['LO', '1', 'CurveDescription'],
- '0x0030': ['SH', '1-n', 'AxisUnits'],
- '0x0040': ['SH', '1-n', 'AxisLabels'],
- '0x0103': ['US', '1', 'DataValueRepresentation'],
- '0x0104': ['US', '1-n', 'MinimumCoordinateValue'],
- '0x0105': ['US', '1-n', 'MaximumCoordinateValue'],
- '0x0106': ['SH', '1-n', 'CurveRange'],
- '0x0110': ['US', '1-n', 'CurveDataDescriptor'],
- '0x0112': ['US', '1-n', 'CoordinateStartValue'],
- '0x0114': ['US', '1-n', 'CoordinateStepValue'],
- '0x1001': ['CS', '1', 'CurveActivationLayer'],
- '0x2000': ['US', '1', 'AudioType'],
- '0x2002': ['US', '1', 'AudioSampleFormat'],
- '0x2004': ['US', '1', 'NumberOfChannels'],
- '0x2006': ['UL', '1', 'NumberOfSamples'],
- '0x2008': ['UL', '1', 'SampleRate'],
- '0x200A': ['UL', '1', 'TotalTime'],
- '0x200C': ['ox', '1', 'AudioSampleData'],
- '0x200E': ['LT', '1', 'AudioComments'],
- '0x2500': ['LO', '1', 'CurveLabel'],
- '0x2600': ['SQ', '1', 'CurveReferencedOverlaySequence'],
- '0x2610': ['US', '1', 'CurveReferencedOverlayGroup'],
- '0x3000': ['ox', '1', 'CurveData']
- },
- '0x5200': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x9229': ['SQ', '1', 'SharedFunctionalGroupsSequence'],
- '0x9230': ['SQ', '1', 'PerFrameFunctionalGroupsSequence']
- },
- '0x5400': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0100': ['SQ', '1', 'WaveformSequence'],
- '0x0110': ['ox', '1', 'ChannelMinimumValue'],
- '0x0112': ['ox', '1', 'ChannelMaximumValue'],
- '0x1004': ['US', '1', 'WaveformBitsAllocated'],
- '0x1006': ['CS', '1', 'WaveformSampleInterpretation'],
- '0x100A': ['ox', '1', 'WaveformPaddingValue'],
- '0x1010': ['ox', '1', 'WaveformData']
- },
- '0x5600': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['OF', '1', 'FirstOrderPhaseCorrectionAngle'],
- '0x0020': ['OF', '1', 'SpectroscopyData']
- },
- '0x6000': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['US', '1', 'OverlayRows'],
- '0x0011': ['US', '1', 'OverlayColumns'],
- '0x0012': ['US', '1', 'OverlayPlanes'],
- '0x0015': ['IS', '1', 'NumberOfFramesInOverlay'],
- '0x0022': ['LO', '1', 'OverlayDescription'],
- '0x0040': ['CS', '1', 'OverlayType'],
- '0x0045': ['LO', '1', 'OverlaySubtype'],
- '0x0050': ['SS', '2', 'OverlayOrigin'],
- '0x0051': ['US', '1', 'ImageFrameOrigin'],
- '0x0052': ['US', '1', 'OverlayPlaneOrigin'],
- '0x0060': ['CS', '1', 'OverlayCompressionCode'],
- '0x0061': ['SH', '1', 'OverlayCompressionOriginator'],
- '0x0062': ['SH', '1', 'OverlayCompressionLabel'],
- '0x0063': ['CS', '1', 'OverlayCompressionDescription'],
- '0x0066': ['AT', '1-n', 'OverlayCompressionStepPointers'],
- '0x0068': ['US', '1', 'OverlayRepeatInterval'],
- '0x0069': ['US', '1', 'OverlayBitsGrouped'],
- '0x0100': ['US', '1', 'OverlayBitsAllocated'],
- '0x0102': ['US', '1', 'OverlayBitPosition'],
- '0x0110': ['CS', '1', 'OverlayFormat'],
- '0x0200': ['US', '1', 'OverlayLocation'],
- '0x0800': ['CS', '1-n', 'OverlayCodeLabel'],
- '0x0802': ['US', '1', 'OverlayNumberOfTables'],
- '0x0803': ['AT', '1-n', 'OverlayCodeTableLocation'],
- '0x0804': ['US', '1', 'OverlayBitsForCodeWord'],
- '0x1001': ['CS', '1', 'OverlayActivationLayer'],
- '0x1100': ['US', '1', 'OverlayDescriptorGray'],
- '0x1101': ['US', '1', 'OverlayDescriptorRed'],
- '0x1102': ['US', '1', 'OverlayDescriptorGreen'],
- '0x1103': ['US', '1', 'OverlayDescriptorBlue'],
- '0x1200': ['US', '1-n', 'OverlaysGray'],
- '0x1201': ['US', '1-n', 'OverlaysRed'],
- '0x1202': ['US', '1-n', 'OverlaysGreen'],
- '0x1203': ['US', '1-n', 'OverlaysBlue'],
- '0x1301': ['IS', '1', 'ROIArea'],
- '0x1302': ['DS', '1', 'ROIMean'],
- '0x1303': ['DS', '1', 'ROIStandardDeviation'],
- '0x1500': ['LO', '1', 'OverlayLabel'],
- '0x3000': ['ox', '1', 'OverlayData'],
- '0x4000': ['LT', '1', 'OverlayComments']
- },
- '0x7FE0': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0008': ['OF', '1', 'FloatPixelData'],
- '0x0009': ['OD', '1', 'DoubleFloatPixelData'],
- '0x0010': ['ox', '1', 'PixelData'],
- '0x0020': ['OW', '1', 'CoefficientsSDVN'],
- '0x0030': ['OW', '1', 'CoefficientsSDHN'],
- '0x0040': ['OW', '1', 'CoefficientsSDDN']
- },
- '0x7F00': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0x0010': ['ox', '1', 'VariablePixelData'],
- '0x0011': ['US', '1', 'VariableNextDataGroup'],
- '0x0020': ['OW', '1', 'VariableCoefficientsSDVN'],
- '0x0030': ['OW', '1', 'VariableCoefficientsSDHN'],
- '0x0040': ['OW', '1', 'VariableCoefficientsSDDN']
- },
- '0xFFFA': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0xFFFA': ['SQ', '1', 'DigitalSignaturesSequence']
- },
- '0xFFFC': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0xFFFC': ['OB', '1', 'DataSetTrailingPadding']
- },
- '0xFFFE': {
- '0x0000': ['UL', '1', 'GenericGroupLength'],
- '0xE000': ['NONE', '1', 'Item'],
- '0xE00D': ['NONE', '1', 'ItemDelimitationItem'],
- '0xE0DD': ['NONE', '1', 'SequenceDelimitationItem']
- }
-}; // dwv.dicom.Dictionnary
-
-// taken from gdcm-2.6.1\Source\DataDictionary\GroupName.dic
-// -> removed duplicates (commented)
-dwv.dicom.TagGroups = {
- 'x0000' :'Command',
- 'x0002': 'Meta Element',
- 'x0004': 'File Set',
- //'x0004': 'Directory',
- 'x0008': 'Identifying',
- 'x0009': 'SPI Identifying',
- 'x0010': 'Patient',
- 'x0012': 'Clinical Trial',
- 'x0018': 'Acquisition',
- 'x0019': 'SPI Acquisition',
- 'x0020': 'Image',
- 'x0021': 'SPI Image',
- 'x0022': 'Ophtalmology',
- 'x0028': 'Image Presentation',
- 'x0032': 'Study',
- 'x0038': 'Visit',
- 'x003A': 'Waveform',
- 'x0040': 'Procedure',
- //'x0040': ''Modality Worklist',
- 'x0042': 'Encapsulated Document',
- 'x0050': 'Device Informations',
- //'x0050': 'XRay Angio Device',
- 'x0054': 'Nuclear Medicine',
- 'x0060': 'Histogram',
- 'x0070': 'Presentation State',
- 'x0072': 'Hanging Protocol',
- 'x0088': 'Storage',
- //'x0088': 'Medicine',
- 'x0100': 'Authorization',
- 'x0400': 'Digital Signature',
- 'x1000': 'Code Table',
- 'x1010': 'Zonal Map',
- 'x2000': 'Film Session',
- 'x2010': 'Film Box',
- 'x2020': 'Image Box',
- 'x2030': 'Annotation',
- 'x2040': 'Overlay Box',
- 'x2050': 'Presentation LUT',
- 'x2100': 'Print Job',
- 'x2110': 'Printer',
- 'x2120': 'Queue',
- 'x2130': 'Print Content',
- 'x2200': 'Media Creation',
- 'x3002': 'RT Image',
- 'x3004': 'RT Dose',
- 'x3006': 'RT StructureSet',
- 'x3008': 'RT Treatment',
- 'x300A': 'RT Plan',
- 'x300C': 'RT Relationship',
- 'x300E': 'RT Approval',
- 'x4000': 'Text',
- 'x4008': 'Results',
- 'x4FFE': 'MAC Parameters',
- 'x5000': 'Curve',
- 'x5002': 'Curve',
- 'x5004': 'Curve',
- 'x5006': 'Curve',
- 'x5008': 'Curve',
- 'x500A': 'Curve',
- 'x500C': 'Curve',
- 'x500E': 'Curve',
- 'x5400': 'Waveform Data',
- 'x6000': 'Overlays',
- 'x6002': 'Overlays',
- 'x6004': 'Overlays',
- 'x6008': 'Overlays',
- 'x600A': 'Overlays',
- 'x600C': 'Overlays',
- 'x600E': 'Overlays',
- 'xFFFC': 'Generic',
- 'x7FE0': 'Pixel Data',
- 'xFFFF': 'Unknown'
- };
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.gui = dwv.gui || {};
-/** @namespace */
-dwv.gui.base = dwv.gui.base || {};
-/** @namespace */
-dwv.gui.filter = dwv.gui.filter || {};
-/** @namespace */
-dwv.gui.filter.base = dwv.gui.filter.base || {};
-
-/**
- * Filter tool base gui.
- * @constructor
- */
-dwv.gui.base.Filter = function (app)
-{
- /**
- * Setup the filter tool HTML.
- */
- this.setup = function (list)
- {
- // filter select
- var filterSelector = dwv.html.createHtmlSelect("filterSelect", list, "filter");
- filterSelector.onchange = app.onChangeFilter;
-
- // filter list element
- var filterLi = dwv.html.createHiddenElement("li", "filterLi");
- filterLi.className += " ui-block-b";
- filterLi.appendChild(filterSelector);
-
- // append element
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- dwv.html.appendElement(node, filterLi);
- };
-
- /**
- * Display the tool HTML.
- * @param {Boolean} flag True to display, false to hide.
- */
- this.display = function (flag)
- {
- var node = app.getElement("filterLi");
- dwv.html.displayElement(node, flag);
- };
-
- /**
- * Initialise the tool HTML.
- */
- this.initialise = function ()
- {
- // filter select: reset selected options
- var filterSelector = app.getElement("filterSelect");
- filterSelector.selectedIndex = 0;
- // refresh
- dwv.gui.refreshElement(filterSelector);
- };
-
-}; // class dwv.gui.base.Filter
-
-/**
- * Threshold filter base gui.
- * @constructor
- */
-dwv.gui.base.Threshold = function (app)
-{
- /**
- * Threshold slider.
- * @private
- * @type Object
- */
- var slider = new dwv.gui.Slider(app);
-
- /**
- * Setup the threshold filter HTML.
- */
- this.setup = function ()
- {
- // threshold list element
- var thresholdLi = dwv.html.createHiddenElement("li", "thresholdLi");
- thresholdLi.className += " ui-block-c";
-
- // node
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- // append threshold
- node.appendChild(thresholdLi);
- // threshold slider
- slider.append();
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Clear the threshold filter HTML.
- * @param {Boolean} flag True to display, false to hide.
- */
- this.display = function (flag)
- {
- // only initialise at display time
- // (avoids min/max calculation at startup)
- if (flag) {
- slider.initialise();
- }
-
- var node = app.getElement("thresholdLi");
- dwv.html.displayElement(node, flag);
- };
-
- /**
- * Initialise the threshold filter HTML.
- */
- this.initialise = function ()
- {
- // nothing to do
- };
-
-}; // class dwv.gui.base.Threshold
-
-/**
- * Create the apply filter button.
- */
-dwv.gui.filter.base.createFilterApplyButton = function (app)
-{
- var button = document.createElement("button");
- button.id = "runFilterButton";
- button.onclick = app.onRunFilter;
- button.setAttribute("style","width:100%; margin-top:0.5em;");
- button.setAttribute("class","ui-btn ui-btn-b");
- button.appendChild(document.createTextNode(dwv.i18n("basics.apply")));
- return button;
-};
-
-/**
- * Sharpen filter base gui.
- * @constructor
- */
-dwv.gui.base.Sharpen = function (app)
-{
- /**
- * Setup the sharpen filter HTML.
- */
- this.setup = function ()
- {
- // sharpen list element
- var sharpenLi = dwv.html.createHiddenElement("li", "sharpenLi");
- sharpenLi.className += " ui-block-c";
- sharpenLi.appendChild( dwv.gui.filter.base.createFilterApplyButton(app) );
- // append element
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- dwv.html.appendElement(node, sharpenLi);
- };
-
- /**
- * Display the sharpen filter HTML.
- * @param {Boolean} flag True to display, false to hide.
- */
- this.display = function (flag)
- {
- var node = app.getElement("sharpenLi");
- dwv.html.displayElement(node, flag);
- };
-
-}; // class dwv.gui.base.Sharpen
-
-/**
- * Sobel filter base gui.
- * @constructor
- */
-dwv.gui.base.Sobel = function (app)
-{
- /**
- * Setup the sobel filter HTML.
- */
- this.setup = function ()
- {
- // sobel list element
- var sobelLi = dwv.html.createHiddenElement("li", "sobelLi");
- sobelLi.className += " ui-block-c";
- sobelLi.appendChild( dwv.gui.filter.base.createFilterApplyButton(app) );
- // append element
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- dwv.html.appendElement(node, sobelLi);
- };
-
- /**
- * Display the sobel filter HTML.
- * @param {Boolean} flag True to display, false to hide.
- */
- this.display = function (flag)
- {
- var node = app.getElement("sobelLi");
- dwv.html.displayElement(node, flag);
- };
-
-}; // class dwv.gui.base.Sobel
-
-// namespaces
-var dwv = dwv || {};
-dwv.gui = dwv.gui || {};
-dwv.gui.base = dwv.gui.base || {};
-
-/**
- * Get the size of the image display window.
- */
-dwv.gui.base.getWindowSize = function ()
-{
- return { 'width': window.innerWidth, 'height': window.innerHeight - 147 };
-};
-
-/**
- * Ask some text to the user.
- * @param {String} message Text to display to the user.
- * @param {String} defaultText Default value displayed in the text input field.
- * @return {String} Text entered by the user.
- */
-dwv.gui.base.prompt = function (message, defaultText)
-{
- return prompt(message, defaultText);
-};
-
-/**
- * Display a progress value.
- * @param {Number} percent The progress percentage.
- */
-dwv.gui.base.displayProgress = function (/*percent*/)
-{
- // default does nothing...
-};
-
-/**
- * Focus the view on the image.
- */
-dwv.gui.base.focusImage = function ()
-{
- // default does nothing...
-};
-
-/**
- * Post process a HTML table.
- * @param {Object} table The HTML table to process.
- * @return The processed HTML table.
- */
-dwv.gui.base.postProcessTable = function (/*table*/)
-{
- // default does nothing...
-};
-
-/**
- * Get a HTML element associated to a container div.
- * @param {Number} containerDivId The id of the container div.
- * @param {String} name The name or id to find.
- * @return {Object} The found element or null.
- */
-dwv.gui.base.getElement = function (containerDivId, name)
-{
- // get by class in the container div
- var parent = document.getElementById(containerDivId);
- if ( !parent ) {
- return null;
- }
- var elements = parent.getElementsByClassName(name);
- // getting the last element since some libraries (ie jquery-mobile) create
- // span in front of regular tags (such as select)...
- var element = elements[elements.length-1];
- // if not found get by id with 'containerDivId-className'
- if ( typeof element === "undefined" ) {
- element = document.getElementById(containerDivId + '-' + name);
- }
- return element;
- };
-
- /**
- * Refresh a HTML element. Mainly for jquery-mobile.
- * @param {String} element The HTML element to refresh.
- */
-dwv.gui.base.refreshElement = function (/*element*/)
-{
- // base does nothing...
-};
-
-/**
- * Set the selected item of a HTML select.
- * @param {String} element The HTML select element.
- * @param {String} value The value of the option to mark as selected.
- */
-dwv.gui.setSelected = function (element, value)
-{
- if ( element ) {
- var index = 0;
- for( index in element.options){
- if( element.options[index].value === value ) {
- break;
- }
- }
- element.selectedIndex = index;
- dwv.gui.refreshElement(element);
- }
-};
-
-/**
- * Slider base gui.
- * @constructor
- */
-dwv.gui.base.Slider = function (app)
-{
- /**
- * Append the slider HTML.
- */
- this.append = function ()
- {
- // default values
- var min = 0;
- var max = 1;
-
- // jquery-mobile range slider
- // minimum input
- var inputMin = document.createElement("input");
- inputMin.id = "threshold-min";
- inputMin.type = "range";
- inputMin.max = max;
- inputMin.min = min;
- inputMin.value = min;
- // maximum input
- var inputMax = document.createElement("input");
- inputMax.id = "threshold-max";
- inputMax.type = "range";
- inputMax.max = max;
- inputMax.min = min;
- inputMax.value = max;
- // slicer div
- var div = document.createElement("div");
- div.id = "threshold-div";
- div.setAttribute("data-role", "rangeslider");
- div.appendChild(inputMin);
- div.appendChild(inputMax);
- div.setAttribute("data-mini", "true");
- // append to document
- app.getElement("thresholdLi").appendChild(div);
- // bind change
- $("#threshold-div").on("change",
- function(/*event*/) {
- app.onChangeMinMax(
- { "min":$("#threshold-min").val(),
- "max":$("#threshold-max").val() } );
- }
- );
- // refresh
- dwv.gui.refreshElement(app.getElement("toolList"));
- };
-
- /**
- * Initialise the slider HTML.
- */
- this.initialise = function ()
- {
- var min = app.getImage().getDataRange().min;
- var max = app.getImage().getDataRange().max;
-
- // minimum input
- var inputMin = document.getElementById("threshold-min");
- inputMin.max = max;
- inputMin.min = min;
- inputMin.value = min;
- // maximum input
- var inputMax = document.getElementById("threshold-max");
- inputMax.max = max;
- inputMax.min = min;
- inputMax.value = max;
- // refresh
- dwv.gui.refreshElement(app.getElement("toolList"));
- };
-
-}; // class dwv.gui.base.Slider
-
-/**
- * DICOM tags base gui.
- * @param {Object} app The associated application.
- * @constructor
- */
-dwv.gui.base.DicomTags = function (app)
-{
- /**
- * Update the DICOM tags table with the input info.
- * @param {Object} dataInfo The data information.
- */
- this.update = function (dataInfo)
- {
- // HTML node
- var node = app.getElement("tags");
- if( node === null ) {
- console.warn("Cannot find a node to append the DICOM tags.");
- return;
- }
- // remove possible previous
- while (node.hasChildNodes()) {
- node.removeChild(node.firstChild);
- }
-
- // exit if no tags
- if (dataInfo.length === 0) {
- console.warn("No DICOM tags to show.");
- return;
- }
-
- // tags HTML table
- var table = dwv.html.toTable(dataInfo);
- table.className = "tagsTable";
-
- // optional gui specific table post process
- dwv.gui.postProcessTable(table);
-
- // check processed table
- if (table.rows.length === 0) {
- console.warn("The processed table does not contain data.");
- return;
- }
-
- // translate first row
- dwv.html.translateTableRow(table.rows.item(0));
-
- // append search form
- node.appendChild(dwv.html.getHtmlSearchForm(table));
- // append tags table
- node.appendChild(table);
-
- // refresh
- dwv.gui.refreshElement(node);
- };
-
-}; // class dwv.gui.base.DicomTags
-
-/**
- * Drawing list base gui.
- * @param {Object} app The associated application.
- * @constructor
- */
-dwv.gui.base.DrawList = function (app)
-{
- /**
- * Closure to self.
- */
- var self = this;
-
- /**
- * Update the draw list html element
- * @param {Object} event A change event, decides if the table is editable or not.
- */
- this.update = function (event)
- {
- var isEditable = false;
- if (typeof event.editable !== "undefined") {
- isEditable = event.editable;
- }
-
- // HTML node
- var node = app.getElement("drawList");
- if( node === null ) {
- console.warn("Cannot find a node to append the drawing list.");
- return;
- }
- // remove possible previous
- while (node.hasChildNodes()) {
- node.removeChild(node.firstChild);
- }
-
- // drawing details
- var drawDisplayDetails = app.getDrawDisplayDetails();
-
- // exit if no details
- if (drawDisplayDetails.length === 0) {
- return;
- }
-
- // tags HTML table
- var table = dwv.html.toTable(drawDisplayDetails);
- table.className = "drawsTable";
-
- // optional gui specific table post process
- dwv.gui.postProcessTable(table);
-
- // check processed table
- if (table.rows.length === 0) {
- console.warn("The processed table does not contain data.");
- return;
- }
-
- // translate first row
- dwv.html.translateTableRow(table.rows.item(0));
-
- // translate shape names
- dwv.html.translateTableColumn(table, 3, "shape", "name");
-
- // create a color onkeyup handler
- var createColorOnKeyUp = function (details) {
- return function () {
- details.color = this.value;
- app.updateDraw(details);
- };
- };
- // create a text onkeyup handler
- var createTextOnKeyUp = function (details) {
- return function () {
- details.label = this.value;
- app.updateDraw(details);
- };
- };
- // create a long text onkeyup handler
- var createLongTextOnKeyUp = function (details) {
- return function () {
- details.description = this.value;
- app.updateDraw(details);
- };
- };
- // create a row onclick handler
- var createRowOnClick = function (slice, frame) {
- return function () {
- // update slice
- var pos = app.getViewController().getCurrentPosition();
- pos.k = slice;
- app.getViewController().setCurrentPosition(pos);
- // update frame
- app.getViewController().setCurrentFrame(frame);
- // focus on the image
- dwv.gui.focusImage();
- };
- };
- // create visibility handler
- var createVisibleOnClick = function (details) {
- return function () {
- app.toogleGroupVisibility(details);
- };
- };
-
- // append visible column to the header row
- var row0 = table.rows.item(0);
- var cell00 = row0.insertCell(0);
- cell00.outerHTML = "" + dwv.i18n("basics.visible") + " ";
-
- // loop through rows
- for (var r = 1; r < table.rows.length; ++r) {
- var drawId = r - 1;
- var drawDetails = drawDisplayDetails[drawId];
- var row = table.rows.item(r);
- var cells = row.cells;
-
- // loop through cells
- for (var c = 0; c < cells.length; ++c) {
- // show short ID
- if (c === 0) {
- cells[c].firstChild.data = cells[c].firstChild.data.substring(0, 5);
- }
-
- if (isEditable) {
- // color
- if (c === 4) {
- dwv.html.makeCellEditable(cells[c], createColorOnKeyUp(drawDetails), "color");
- }
- // text
- else if (c === 5) {
- dwv.html.makeCellEditable(cells[c], createTextOnKeyUp(drawDetails));
- }
- // long text
- else if (c === 6) {
- dwv.html.makeCellEditable(cells[c], createLongTextOnKeyUp(drawDetails));
- }
- }
- else {
- // id: link to image
- cells[0].onclick = createRowOnClick(
- cells[1].firstChild.data,
- cells[2].firstChild.data);
- cells[0].onmouseover = dwv.html.setCursorToPointer;
- cells[0].onmouseout = dwv.html.setCursorToDefault;
- // color: just display the input color with no callback
- if (c === 4) {
- dwv.html.makeCellEditable(cells[c], null, "color");
- }
- }
- }
-
- // append visible column
- var cell0 = row.insertCell(0);
- var input = document.createElement("input");
- input.setAttribute("type", "checkbox");
- input.checked = app.isGroupVisible(drawDetails);
- input.onclick = createVisibleOnClick(drawDetails);
- cell0.appendChild(input);
- }
-
- // editable checkbox
- var tickBox = document.createElement("input");
- tickBox.setAttribute("type", "checkbox");
- tickBox.id = "checkbox-editable";
- tickBox.checked = isEditable;
- tickBox.onclick = function () { self.update({"editable": this.checked}); };
- // checkbox label
- var tickLabel = document.createElement("label");
- tickLabel.setAttribute( "for", tickBox.id );
- tickLabel.setAttribute( "class", "inline" );
- tickLabel.appendChild( document.createTextNode( dwv.i18n("basics.editMode") ) );
- // checkbox div
- var tickDiv = document.createElement("div");
- tickDiv.appendChild(tickLabel);
- tickDiv.appendChild(tickBox);
-
- // search form
- node.appendChild(dwv.html.getHtmlSearchForm(table));
- // tick form
- node.appendChild(tickDiv);
-
- // draw list table
- node.appendChild(table);
-
- // delete draw button
- var deleteButton = document.createElement("button");
- deleteButton.onclick = function () { app.deleteDraws(); };
- deleteButton.setAttribute( "class", "ui-btn ui-btn-inline" );
- deleteButton.appendChild( document.createTextNode( dwv.i18n("basics.deleteDraws") ) );
- if (!isEditable) {
- deleteButton.style.display = "none";
- }
- node.appendChild(deleteButton);
-
- // refresh
- dwv.gui.refreshElement(node);
- };
-
-}; // class dwv.gui.base.DrawList
-
-// namespaces
-var dwv = dwv || {};
-dwv.gui = dwv.gui || {};
-dwv.gui.base = dwv.gui.base || {};
-
-/**
- * Append the version HTML.
- */
-dwv.gui.base.appendVersionHtml = function (version)
-{
- var nodes = document.getElementsByClassName("dwv-version");
- if ( nodes ) {
- for( var i = 0; i < nodes.length; ++i ){
- nodes[i].appendChild( document.createTextNode(version) );
- }
- }
-};
-
-/**
- * Build the help HTML.
- * @param {Boolean} mobile Flag for mobile or not environement.
- */
-dwv.gui.base.appendHelpHtml = function(toolList, mobile, app)
-{
- var actionType = "mouse";
- if( mobile ) {
- actionType = "touch";
- }
-
- var toolHelpDiv = document.createElement("div");
-
- var tool = null;
- var tkeys = Object.keys(toolList);
- for ( var t=0; t < tkeys.length; ++t )
- {
- tool = toolList[tkeys[t]];
- // title
- var title = document.createElement("h3");
- title.appendChild(document.createTextNode(tool.getHelp().title));
- // doc div
- var docDiv = document.createElement("div");
- // brief
- var brief = document.createElement("p");
- brief.appendChild(document.createTextNode(tool.getHelp().brief));
- docDiv.appendChild(brief);
- // details
- if( tool.getHelp()[actionType] ) {
- var keys = Object.keys(tool.getHelp()[actionType]);
- for( var i=0; i 10 ) {
- content = Array.prototype.slice.call( content, 0, 10 );
- content[10] = "...";
- }
- str = Array.prototype.join.call( content, ', ' );
- }
- // append
- cell.appendChild(document.createTextNode(str));
-};
-
-/**
- * Append a header cell to a given row.
- * @param {Object} row The row to append the header cell to.
- * @param {String} text The text of the header cell.
- */
-dwv.html.appendHCell = function (row, text)
-{
- var cell = document.createElement("th");
- cell.appendChild(document.createTextNode(text));
- row.appendChild(cell);
-};
-
-/**
- * Append a row to an array.
- * @param {Object} table The HTML table to append a row to.
- * @param {Array} input The input row array.
- * @param {Number} level The depth level of the input array.
- * @param {Number} maxLevel The maximum depth level.
- * @param {String} rowHeader The content of the first cell of a row (mainly for objects).
- */
-dwv.html.appendRowForArray = function (table, input, level, maxLevel, rowHeader)
-{
- var row = null;
- // loop through
- for ( var i=0; i= maxLevel ) {
- if ( !row ) {
- row = table.insertRow(-1);
- }
- dwv.html.appendCell(row, value);
- }
- // more to come
- else {
- dwv.html.appendRow(table, value, level+i, maxLevel, rowHeader);
- }
- }
-};
-
-/**
- * Append a row to an object.
- * @param {Object} table The HTML table to append a row to.
- * @param {Array} input The input row array.
- * @param {Number} level The depth level of the input array.
- * @param {Number} maxLevel The maximum depth level.
- * @param {String} rowHeader The content of the first cell of a row (mainly for objects).
- */
-dwv.html.appendRowForObject = function (table, input, level, maxLevel, rowHeader)
-{
- var keys = Object.keys(input);
- var row = null;
- for ( var o=0; o= maxLevel ) {
- if ( !row ) {
- row = table.insertRow(-1);
- }
- if ( o === 0 && rowHeader) {
- dwv.html.appendCell(row, rowHeader);
- }
- dwv.html.appendCell(row, value);
- }
- // more to come
- else {
- dwv.html.appendRow(table, value, level+o, maxLevel, keys[o]);
- }
- }
- // header row
- // warn: need to create the header after the rest
- // otherwise the data will inserted in the thead...
- if ( level === 2 ) {
- var header = table.createTHead();
- var th = header.insertRow(-1);
- if ( rowHeader ) {
- dwv.html.appendHCell(th, "");
- }
- for ( var k=0; k]+>/g, "").toLowerCase();
- if (text.indexOf(terms[i]) < 0) {
- display = 'none';
- } else {
- if (terms[i].length) {
- dwv.html.highlight(terms[i], table.rows[r]);
- }
- }
- table.rows[r].style.display = display;
- }
- }
-};
-
-/**
- * Transform back each
- * 'preText term postText'
- * into its original 'preText term postText'.
- * @param {Object} container The container to de-highlight.
- */
-dwv.html.dehighlight = function (container) {
- for (var i = 0; i < container.childNodes.length; i++) {
- var node = container.childNodes[i];
-
- if (node.attributes &&
- node.attributes['class'] &&
- node.attributes['class'].value === 'highlighted') {
- node.parentNode.parentNode.replaceChild(
- document.createTextNode(
- node.parentNode.innerHTML.replace(/<[^>]+>/g, "")),
- node.parentNode);
- // Stop here and process next parent
- return;
- } else if (node.nodeType !== 3) {
- // Keep going onto other elements
- dwv.html.dehighlight(node);
- }
- }
-};
-
-/**
- * Create a
- * 'preText term postText'
- * around each search term.
- * @param {String} term The term to highlight.
- * @param {Object} container The container where to highlight the term.
- */
-dwv.html.highlight = function (term, container) {
- for (var i = 0; i < container.childNodes.length; i++) {
- var node = container.childNodes[i];
-
- if (node.nodeType === 3) {
- // Text node
- var data = node.data;
- var data_low = data.toLowerCase();
- if (data_low.indexOf(term) >= 0) {
- //term found!
- var new_node = document.createElement('span');
- node.parentNode.replaceChild(new_node, node);
-
- var result;
- while ((result = data_low.indexOf(term)) !== -1) {
- // before term
- new_node.appendChild(document.createTextNode(
- data.substr(0, result)));
- // term
- new_node.appendChild(dwv.html.createHighlightNode(
- document.createTextNode(data.substr(
- result, term.length))));
- // reduce search string
- data = data.substr(result + term.length);
- data_low = data_low.substr(result + term.length);
- }
- new_node.appendChild(document.createTextNode(data));
- }
- } else {
- // Keep going onto other elements
- dwv.html.highlight(term, node);
- }
- }
-};
-
-/**
- * Highlight a HTML node.
- * @param {Object} child The child to highlight.
- * @return {Object} The created HTML node.
- */
-dwv.html.createHighlightNode = function (child) {
- var node = document.createElement('span');
- node.setAttribute('class', 'highlighted');
- node.attributes['class'].value = 'highlighted';
- node.appendChild(child);
- return node;
-};
-
-/**
- * Remove all children of a HTML node.
- * @param {Object} node The node to remove kids.
- */
-dwv.html.cleanNode = function (node) {
- // remove its children if node exists
- if ( !node ) {
- return;
- }
- while (node.hasChildNodes()) {
- node.removeChild(node.firstChild);
- }
-};
-
-/**
- * Remove a HTML node and all its children.
- * @param {String} nodeId The string id of the node to delete.
- */
-dwv.html.removeNode = function (node) {
- // check node
- if ( !node ) {
- return;
- }
- // remove its children
- dwv.html.cleanNode(node);
- // remove it from its parent
- var top = node.parentNode;
- top.removeChild(node);
-};
-
-/**
- * Remove a list of HTML nodes and all their children.
- * @param {Array} nodes The list of nodes to delete.
- */
-dwv.html.removeNodes = function (nodes) {
- for ( var i = 0; i < nodes.length; ++i ) {
- dwv.html.removeNode(nodes[i]);
- }
-};
-
-/**
- * Translate the content of an HTML row.
- * @param {Object} row The HTML row to parse.
- * @param {String} i18nPrefix The i18n prefix to use to find the translation.
- */
-dwv.html.translateTableRow = function (row, i18nPrefix) {
- var prefix = (typeof i18nPrefix === "undefined") ? "basics" : i18nPrefix;
- if (prefix.length !== 0) {
- prefix += ".";
- }
- var cells = row.cells;
- for (var c = 0; c < cells.length; ++c) {
- var text = cells[c].firstChild.data;
- cells[c].firstChild.data = dwv.i18n( prefix + text );
- }
-};
-
-/**
- * Translate the content of an HTML column.
- * @param {Object} table The HTML table to parse.
- * @param {Number} columnNumber The number of the column to translate.
- * @param {String} i18nPrefix The i18n prefix to use to find the translation.
- * @param {String} i18nSuffix The i18n suffix to use to find the translation.
- */
-dwv.html.translateTableColumn = function (table, columnNumber, i18nPrefix, i18nSuffix) {
- var prefix = (typeof i18nPrefix === "undefined") ? "basics" : i18nPrefix;
- if (prefix.length !== 0) {
- prefix += ".";
- }
- var suffix = (typeof i18nSuffix === "undefined") ? "" : i18nSuffix;
- if (suffix.length !== 0) {
- suffix = "." + suffix;
- }
- if (table.rows.length !== 0) {
- for (var r = 1; r < table.rows.length; ++r) {
- var cells = table.rows.item(r).cells;
- if (cells.length >= columnNumber) {
- var text = cells[columnNumber].firstChild.data;
- cells[columnNumber].firstChild.data = dwv.i18n( prefix + text + suffix );
- }
- }
- }
-};
-
-/**
- * Make a HTML table cell editable by putting its content inside an input element.
- * @param {Object} cell The cell to make editable.
- * @param {Function} onchange The callback to call when cell's content is changed.
- * if set to null, the HTML input will be disabled.
- * @param {String} inputType The type of the HTML input, default to 'text'.
- */
-dwv.html.makeCellEditable = function (cell, onchange, inputType) {
- // check event
- if (typeof cell === "undefined" ) {
- console.warn("Cannot create input for non existing cell.");
- return;
- }
- // HTML input
- var input = document.createElement("input");
- // handle change
- if (onchange) {
- input.onchange = onchange;
- }
- else {
- input.disabled = true;
- }
- // set input value
- input.value = cell.firstChild.data;
- // input type
- if (typeof inputType === "undefined" ||
- (inputType === "color" && !dwv.browser.hasInputColor() ) ) {
- input.type = "text";
- }
- else {
- input.type = inputType;
- }
-
- // clean cell
- dwv.html.cleanNode(cell);
-
- // HTML form
- var form = document.createElement("form");
- form.onsubmit = function (event) {
- event.preventDefault();
- };
- form.appendChild(input);
- // add form to cell
- cell.appendChild(form);
-};
-
-/**
- * Set the document cursor to 'pointer'.
- */
-dwv.html.setCursorToPointer = function () {
- document.body.style.cursor = 'pointer';
-};
-
-/**
- * Set the document cursor to 'default'.
- */
-dwv.html.setCursorToDefault = function () {
- document.body.style.cursor = 'default';
-};
-
-
-/**
- * Create a HTML select from an input array of options.
- * The values of the options are the name of the option made lower case.
- * It is left to the user to set the 'onchange' method of the select.
- * @param {String} name The name of the HTML select.
- * @param {Mixed} list The list of options of the HTML select.
- * @param {String} i18nPrefix An optional namespace prefix to find the translation values.
- * @param {Bool} i18nSafe An optional flag to check translation existence.
- * @return {Object} The created HTML select.
- */
-dwv.html.createHtmlSelect = function (name, list, i18nPrefix, i18nSafe) {
- // select
- var select = document.createElement("select");
- //select.name = name;
- select.className = name;
- var prefix = (typeof i18nPrefix === "undefined") ? "" : i18nPrefix + ".";
- var safe = (typeof i18nSafe === "undefined") ? false : true;
- var getText = function(value) {
- var key = prefix + value + ".name";
- var text = "";
- if (safe) {
- if (dwv.i18nExists(key)) {
- text = dwv.i18n(key);
- }
- else {
- text = value;
- }
- }
- else {
- text = dwv.i18n(key);
- }
- return text;
- };
- // options
- var option;
- if ( list instanceof Array )
- {
- for ( var i in list ) {
- if ( list.hasOwnProperty(i) ) {
- option = document.createElement("option");
- option.value = list[i];
- option.appendChild(document.createTextNode(getText(list[i])));
- select.appendChild(option);
- }
- }
- }
- else if ( typeof list === 'object')
- {
- for ( var item in list )
- {
- option = document.createElement("option");
- option.value = item;
- option.appendChild(document.createTextNode(getText(item)));
- select.appendChild(option);
- }
- }
- else
- {
- throw new Error("Unsupported input list type.");
- }
- return select;
-};
-
-/**
- * Display or not an element.
- * @param {Object} element The HTML element to display.
- * @param {Boolean} flag True to display the element.
- */
-dwv.html.displayElement = function (element, flag)
-{
- element.style.display = flag ? "" : "none";
-};
-
-/**
- * Toggle the display of an element.
- * @param {Object} element The HTML element to display.
- */
-dwv.html.toggleDisplay = function (element)
-{
- if ( element.style.display === "none" ) {
- element.style.display = '';
- }
- else {
- element.style.display = "none";
- }
-};
-
-/**
- * Append an element.
- * @param {Object} parent The HTML element to append to.
- * @param {Object} element The HTML element to append.
- */
-dwv.html.appendElement = function (parent, element)
-{
- // append
- parent.appendChild(element);
- // refresh
- dwv.gui.refreshElement(parent);
-};
-
-/**
- * Create an element.
- * @param {String} type The type of the elemnt.
- * @param {String} className The className of the element.
- */
-dwv.html.createHiddenElement = function (type, className)
-{
- var element = document.createElement(type);
- element.className = className;
- // hide by default
- element.style.display = "none";
- // return
- return element;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.gui = dwv.gui || {};
-dwv.gui.base = dwv.gui.base || {};
-dwv.gui.info = dwv.gui.info || {};
-
-/**
- * Plot some data in a given div.
- * @param {Object} div The HTML element to add WindowLevel info to.
- * @param {Array} data The data array to plot.
- * @param {Object} options Plot options.
- */
-dwv.gui.base.plot = function (/*div, data, options*/)
-{
- // default does nothing...
-};
-
-/**
- * MiniColourMap info layer.
- * @constructor
- * @param {Object} div The HTML element to add colourMap info to.
- * @param {Object} app The associated application.
- */
-dwv.gui.info.MiniColourMap = function ( div, app )
-{
- /**
- * Create the mini colour map info div.
- */
- this.create = function ()
- {
- // clean div
- var elems = div.getElementsByClassName("colour-map-info");
- if ( elems.length !== 0 ) {
- dwv.html.removeNodes(elems);
- }
- // colour map
- var canvas = document.createElement("canvas");
- canvas.className = "colour-map-info";
- canvas.width = 98;
- canvas.height = 10;
- // add canvas to div
- div.appendChild(canvas);
- };
-
- /**
- * Update the mini colour map info div.
- * @param {Object} event The windowing change event containing the new values.
- * Warning: expects the mini colour map div to exist (use after createMiniColourMap).
- */
- this.update = function (event)
- {
- var windowCenter = event.wc;
- var windowWidth = event.ww;
- // retrieve canvas and context
- var canvas = div.getElementsByClassName("colour-map-info")[0];
- var context = canvas.getContext('2d');
- // fill in the image data
- var colourMap = app.getViewController().getColourMap();
- var imageData = context.getImageData(0,0,canvas.width, canvas.height);
- // histogram sampling
- var c = 0;
- var minInt = app.getImage().getRescaledDataRange().min;
- var range = app.getImage().getRescaledDataRange().max - minInt;
- var incrC = range / canvas.width;
- // Y scale
- var y = 0;
- var yMax = 255;
- var yMin = 0;
- // X scale
- var xMin = windowCenter - 0.5 - (windowWidth-1) / 2;
- var xMax = windowCenter - 0.5 + (windowWidth-1) / 2;
- // loop through values
- var index;
- for ( var j = 0; j < canvas.height; ++j ) {
- c = minInt;
- for ( var i = 0; i < canvas.width; ++i ) {
- if ( c <= xMin ) {
- y = yMin;
- }
- else if ( c > xMax ) {
- y = yMax;
- }
- else {
- y = ( (c - (windowCenter-0.5) ) / (windowWidth-1) + 0.5 ) *
- (yMax-yMin) + yMin;
- y = parseInt(y,10);
- }
- index = (i + j * canvas.width) * 4;
- imageData.data[index] = colourMap.red[y];
- imageData.data[index+1] = colourMap.green[y];
- imageData.data[index+2] = colourMap.blue[y];
- imageData.data[index+3] = 0xff;
- c += incrC;
- }
- }
- // put the image data in the context
- context.putImageData(imageData, 0, 0);
- };
-}; // class dwv.gui.info.MiniColourMap
-
-
-/**
- * Plot info layer.
- * @constructor
- * @param {Object} div The HTML element to add colourMap info to.
- * @param {Object} app The associated application.
- */
-dwv.gui.info.Plot = function (div, app)
-{
- /**
- * Create the plot info.
- */
- this.create = function()
- {
- // clean div
- if ( div ) {
- dwv.html.cleanNode(div);
- }
- // plot
- dwv.gui.plot(div, app.getImage().getHistogram());
- };
-
- /**
- * Update plot.
- * @param {Object} event The windowing change event containing the new values.
- * Warning: expects the plot to exist (use after createPlot).
- */
- this.update = function (event)
- {
- var wc = event.wc;
- var ww = event.ww;
-
- var half = parseInt( (ww-1) / 2, 10 );
- var center = parseInt( (wc-0.5), 10 );
- var min = center - half;
- var max = center + half;
-
- var markings = [
- { "color": "#faa", "lineWidth": 1, "xaxis": { "from": min, "to": min } },
- { "color": "#aaf", "lineWidth": 1, "xaxis": { "from": max, "to": max } }
- ];
-
- // plot
- dwv.gui.plot(div, app.getImage().getHistogram(), {markings: markings});
- };
-
-}; // class dwv.gui.info.Plot
-
-/**
- * DICOM Header overlay info layer.
- * @constructor
- * @param {Object} div The HTML element to add Header overlay info to.
- * @param {String} pos The string to specify the corner position. (tl,tc,tr,cl,cr,bl,bc,br)
- */
-dwv.gui.info.Overlay = function ( div, pos, app )
-{
- // closure to self
- var self = this;
-
- /**
- * Get the overlay array of the current position.
- * @return {Array} The overlay information.
- */
- this.getOverlays = function ()
- {
- var image = app.getImage();
- if (!image) {
- return;
- }
- var allOverlays = image.getOverlays();
- if (!allOverlays) {
- return;
- }
- var position = app.getViewController().getCurrentPosition();
- var sliceOverlays = allOverlays[position.k];
- if (!sliceOverlays) {
- return;
- }
- return sliceOverlays[pos];
- };
-
- /**
- * Create the overlay info div.
- */
- this.create = function ()
- {
- // remove all elements from div
- dwv.html.cleanNode(div);
-
- // get overlay string array of the current position
- var overlays = self.getOverlays();
- if (!overlays) {
- return;
- }
-
- if (pos === "bc" || pos === "tc" ||
- pos === "cr" || pos === "cl") {
- div.textContent = overlays[0].value;
- } else {
- // create element
- var ul = document.createElement("ul");
-
- for (var n=0; overlays[n]; n++){
- var li;
- if (overlays[n].value === "window-center") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-window-center";
- ul.appendChild(li);
- } else if (overlays[n].value === "window-width") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-window-width";
- ul.appendChild(li);
- } else if (overlays[n].value === "zoom") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-zoom";
- ul.appendChild(li);
- } else if (overlays[n].value === "offset") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-offset";
- ul.appendChild(li);
- } else if (overlays[n].value === "value") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-value";
- ul.appendChild(li);
- } else if (overlays[n].value === "position") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-position";
- ul.appendChild(li);
- } else if (overlays[n].value === "frame") {
- li = document.createElement("li");
- li.className = "info-" + pos + "-frame";
- ul.appendChild(li);
- } else {
- li = document.createElement("li");
- li.className = "info-" + pos + "-" + n;
- li.appendChild( document.createTextNode( overlays[n].value ) );
- ul.appendChild(li);
- }
- }
-
- // append element before color map
- div.appendChild(ul);
- }
- };
-
- /**
- * Update the overlay info div.
- * @param {Object} event A change event.
- */
- this.update = function ( event )
- {
- // get overlay string array of the current position
- var overlays = self.getOverlays();
- if (!overlays) {
- return;
- }
-
- if (pos === "bc" || pos === "tc" ||
- pos === "cr" || pos === "cl") {
- div.textContent = overlays[0].value;
- } else {
- var li;
- var n;
-
- for (n=0; overlays[n]; n++) {
- if (overlays[n].value === "window-center") {
- if (event.type === "wl-center-change") {
- li = div.getElementsByClassName("info-" + pos + "-window-center")[0];
- dwv.html.cleanNode(li);
- var wcStr = dwv.utils.replaceFlags2( overlays[n].format, [Math.round(event.wc)] );
- if (li) {
- li.appendChild( document.createTextNode(wcStr) );
- }
- }
- } else if (overlays[n].value === "window-width") {
- if (event.type === "wl-width-change") {
- li = div.getElementsByClassName("info-" + pos + "-window-width")[0];
- dwv.html.cleanNode(li);
- var wwStr = dwv.utils.replaceFlags2( overlays[n].format, [Math.round(event.ww)] );
- if (li) {
- li.appendChild( document.createTextNode(wwStr) );
- }
- }
- } else if (overlays[n].value === "zoom") {
- if (event.type === "zoom-change") {
- li = div.getElementsByClassName("info-" + pos + "-zoom")[0];
- dwv.html.cleanNode(li);
- var zoom = Number(event.scale).toPrecision(3);
- var zoomStr = dwv.utils.replaceFlags2( overlays[n].format, [zoom] );
- if (li) {
- li.appendChild( document.createTextNode( zoomStr ) );
- }
- }
- } else if (overlays[n].value === "offset") {
- if (event.type === "zoom-change") {
- li = div.getElementsByClassName("info-" + pos + "-offset")[0];
- dwv.html.cleanNode(li);
- var offset = [ Number(event.cx).toPrecision(3),
- Number(event.cy).toPrecision(3)];
- var offStr = dwv.utils.replaceFlags2( overlays[n].format, offset );
- if (li) {
- li.appendChild( document.createTextNode( offStr ) );
- }
- }
- } else if (overlays[n].value === "value") {
- if (event.type === "position-change") {
- li = div.getElementsByClassName("info-" + pos + "-value")[0];
- dwv.html.cleanNode(li);
- var valueStr = dwv.utils.replaceFlags2( overlays[n].format, [event.value] );
- if (li) {
- li.appendChild( document.createTextNode( valueStr ) );
- }
- }
- } else if (overlays[n].value === "position") {
- if (event.type === "position-change") {
- li = div.getElementsByClassName("info-" + pos + "-position")[0];
- dwv.html.cleanNode(li);
- var posStr = dwv.utils.replaceFlags2( overlays[n].format, [event.i, event.j, event.k] );
- if (li) {
- li.appendChild( document.createTextNode( posStr ) );
- }
- }
- } else if (overlays[n].value === "frame") {
- if (event.type === "frame-change") {
- li = div.getElementsByClassName("info-" + pos + "-frame")[0];
- dwv.html.cleanNode(li);
- var frameStr = dwv.utils.replaceFlags2( overlays[n].format, [event.frame] );
- if (li) {
- li.appendChild( document.createTextNode( frameStr ) );
- }
- }
- } else {
- if (event.type === "position-change") {
- li = div.getElementsByClassName("info-" + pos + "-" + n)[0];
- dwv.html.cleanNode(li);
- if (li) {
- li.appendChild( document.createTextNode( overlays[n].value ) );
- }
- }
- }
- }
- }
- };
-}; // class dwv.gui.info.Overlay
-
-/**
- * Create overlay string array of the image in each corner
- * @param {Object} dicomElements DICOM elements of the image
- * @return {Array} Array of string to be shown in each corner
- */
-dwv.gui.info.createOverlays = function (dicomElements)
-{
- var overlays = {};
- var modality = dicomElements.getFromKey("x00080060");
- if (!modality){
- return overlays;
- }
-
- var omaps = dwv.gui.info.overlayMaps;
- if (!omaps){
- return overlays;
- }
- var omap = omaps[modality] || omaps['*'];
-
- for (var n=0; omap[n]; n++){
- var value = omap[n].value;
- var tags = omap[n].tags;
- var format = omap[n].format;
- var pos = omap[n].pos;
-
- if (typeof tags !== "undefined" && tags.length !== 0) {
- // get values
- var values = [];
- for ( var i = 0; i < tags.length; ++i ) {
- values.push( dicomElements.getElementValueAsStringFromKey( tags[i] ) );
- }
- // format
- if (typeof format === "undefined" || format === null) {
- format = dwv.utils.createDefaultReplaceFormat( values );
- }
- value = dwv.utils.replaceFlags2( format, values );
- }
-
- if (!value || value.length === 0){
- continue;
- }
-
- // add value to overlays
- if (!overlays[pos]) {
- overlays[pos] = [];
- }
- overlays[pos].push({'value': value.trim(), 'format': format});
- }
-
- // (0020,0020) Patient Orientation
- var valuePO = dicomElements.getFromKey("x00200020");
- if (typeof valuePO !== "undefined" && valuePO !== null && valuePO.length == 2){
- var po0 = dwv.dicom.cleanString(valuePO[0]);
- var po1 = dwv.dicom.cleanString(valuePO[1]);
- overlays.cr = [{'value': po0}];
- overlays.cl = [{'value': dwv.dicom.getReverseOrientation(po0)}];
- overlays.bc = [{'value': po1}];
- overlays.tc = [{'value': dwv.dicom.getReverseOrientation(po1)}];
- }
-
- return overlays;
-};
-
-/**
- * Create overlay string array of the image in each corner
- * @param {Object} dicomElements DICOM elements of the image
- * @return {Array} Array of string to be shown in each corner
- */
-dwv.gui.info.createOverlaysForDom = function (info)
-{
- var overlays = {};
- var omaps = dwv.gui.info.overlayMaps;
- if (!omaps){
- return overlays;
- }
- var omap = omaps.DOM;
- if (!omap){
- return overlays;
- }
-
- for (var n=0; omap[n]; n++){
- var value = omap[n].value;
- var tags = omap[n].tags;
- var format = omap[n].format;
- var pos = omap[n].pos;
-
- if (typeof tags !== "undefined" && tags.length !== 0) {
- // get values
- var values = [];
- for ( var i = 0; i < tags.length; ++i ) {
- for ( var j = 0; j < info.length; ++j ) {
- if (tags[i] === info[j].name) {
- values.push( info[j].value );
- }
- }
- }
- // format
- if (typeof format === "undefined" || format === null) {
- format = dwv.utils.createDefaultReplaceFormat( values );
- }
- value = dwv.utils.replaceFlags2( format, values );
- }
-
- if (!value || value.length === 0){
- continue;
- }
-
- // add value to overlays
- if (!overlays[pos]) {
- overlays[pos] = [];
- }
- overlays[pos].push({'value': value.trim(), 'format': format});
- }
-
- return overlays;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.html = dwv.html || {};
-
-/**
- * Window layer.
- * @constructor
- * @param {String} name The name of the layer.
- */
-dwv.html.Layer = function(canvas)
-{
- /**
- * A cache of the initial canvas.
- * @private
- * @type Object
- */
- var cacheCanvas = null;
- /**
- * The associated CanvasRenderingContext2D.
- * @private
- * @type Object
- */
- var context = null;
-
- /**
- * Get the layer canvas.
- * @return {Object} The layer canvas.
- */
- this.getCanvas = function() { return canvas; };
- /**
- * Get the layer context.
- * @return {Object} The layer context.
- */
- this.getContext = function() { return context; };
- /**
- * Get the layer offset on page.
- * @return {Number} The layer offset on page.
- */
- this.getOffset = function() { return canvas.offset(); };
-
- /**
- * The image data array.
- * @private
- * @type Array
- */
- var imageData = null;
-
- /**
- * The layer origin.
- * @private
- * @type {Object}
- */
- var origin = {'x': 0, 'y': 0};
- /**
- * Get the layer origin.
- * @return {Object} The layer origin as {'x','y'}.
- */
- this.getOrigin = function () {
- return origin;
- };
- /**
- * The layer zoom.
- * @private
- * @type {Object}
- */
- var zoom = {'x': 1, 'y': 1};
- /**
- * Get the layer zoom.
- * @return {Object} The layer zoom as {'x','y'}.
- */
- this.getZoom = function () {
- return zoom;
- };
-
- /**
- * The layer translation.
- * @private
- * @type {Object}
- */
- var trans = {'x': 0, 'y': 0};
- /**
- * Get the layer translation.
- * @return {Object} The layer translation as {'x','y'}.
- */
- this.getTrans = function () {
- return trans;
- };
-
- /**
- * Set the canvas width.
- * @param {Number} width The new width.
- */
- this.setWidth = function ( width ) {
- canvas.width = width;
- };
- /**
- * Set the canvas height.
- * @param {Number} height The new height.
- */
- this.setHeight = function ( height ) {
- canvas.height = height;
- };
-
- /**
- * Set the layer zoom.
- * @param {Number} newZoomX The zoom in the X direction.
- * @param {Number} newZoomY The zoom in the Y direction.
- * @param {Number} centerX The zoom center in the X direction.
- * @param {Number} centerY The zoom center in the Y direction.
- */
- this.zoom = function(newZoomX,newZoomY,centerX,centerY)
- {
- // The zoom is the ratio between the differences from the center
- // to the origins:
- // centerX - originX = ( centerX - originX0 ) * zoomX
- // (center in ~world coordinate system)
- //originX = (centerX / zoomX) + originX - (centerX / newZoomX);
- //originY = (centerY / zoomY) + originY - (centerY / newZoomY);
-
- // center in image coordinate system
- origin.x = centerX - (centerX - origin.x) * (newZoomX / zoom.x);
- origin.y = centerY - (centerY - origin.y) * (newZoomY / zoom.y);
-
- // save zoom
- zoom.x = newZoomX;
- zoom.y = newZoomY;
- };
-
- /**
- * Set the layer translation.
- * Translation is according to the last one.
- * @param {Number} tx The translation in the X direction.
- * @param {Number} ty The translation in the Y direction.
- */
- this.translate = function(tx,ty)
- {
- trans.x = tx;
- trans.y = ty;
- };
-
- /**
- * Set the image data array.
- * @param {Array} data The data array.
- */
- this.setImageData = function(data)
- {
- imageData = data;
- // update the cached canvas
- cacheCanvas.getContext("2d").putImageData(imageData, 0, 0);
- };
-
- /**
- * Reset the layout.
- */
- this.resetLayout = function(izoom)
- {
- origin.x = 0;
- origin.y = 0;
- zoom.x = izoom;
- zoom.y = izoom;
- trans.x = 0;
- trans.y = 0;
- };
-
- /**
- * Transform a display position to an index.
- */
- this.displayToIndex = function ( point2D ) {
- return {'x': ( (point2D.x - origin.x) / zoom.x ) - trans.x,
- 'y': ( (point2D.y - origin.y) / zoom.y ) - trans.y};
- };
-
- /**
- * Draw the content (imageData) of the layer.
- * The imageData variable needs to be set
- */
- this.draw = function ()
- {
- // clear the context: reset the transform first
- // store the current transformation matrix
- context.save();
- // use the identity matrix while clearing the canvas
- context.setTransform( 1, 0, 0, 1, 0, 0 );
- context.clearRect( 0, 0, canvas.width, canvas.height );
- // restore the transform
- context.restore();
-
- // draw the cached canvas on the context
- // transform takes as input a, b, c, d, e, f to create
- // the transform matrix (column-major order):
- // [ a c e ]
- // [ b d f ]
- // [ 0 0 1 ]
- context.setTransform( zoom.x, 0, 0, zoom.y,
- origin.x + (trans.x * zoom.x),
- origin.y + (trans.y * zoom.y) );
- context.drawImage( cacheCanvas, 0, 0 );
- };
-
- /**
- * Initialise the layer: set the canvas and context
- * @input {Number} inputWidth The width of the canvas.
- * @input {Number} inputHeight The height of the canvas.
- */
- this.initialise = function(inputWidth, inputHeight)
- {
- // find the canvas element
- //canvas = document.getElementById(name);
- //if (!canvas)
- //{
- // alert("Error: cannot find the canvas element for '" + name + "'.");
- // return;
- //}
- // check that the getContext method exists
- if (!canvas.getContext)
- {
- alert("Error: no canvas.getContext method.");
- return;
- }
- // get the 2D context
- context = canvas.getContext('2d');
- if (!context)
- {
- alert("Error: failed to get the 2D context.");
- return;
- }
- // canvas sizes
- canvas.width = inputWidth;
- canvas.height = inputHeight;
- // original empty image data array
- context.clearRect (0, 0, canvas.width, canvas.height);
- imageData = context.getImageData(0, 0, canvas.width, canvas.height);
- // cached canvas
- cacheCanvas = document.createElement("canvas");
- cacheCanvas.width = inputWidth;
- cacheCanvas.height = inputHeight;
- };
-
- /**
- * Fill the full context with the current style.
- */
- this.fillContext = function()
- {
- context.fillRect( 0, 0, canvas.width, canvas.height );
- };
-
- /**
- * Clear the context and reset the image data.
- */
- this.clear = function()
- {
- context.clearRect(0, 0, canvas.width, canvas.height);
- imageData = context.getImageData(0, 0, canvas.width, canvas.height);
- this.resetLayout();
- };
-
- /**
- * Merge two layers.
- * @input {Layer} layerToMerge The layer to merge. It will also be emptied.
- */
- this.merge = function(layerToMerge)
- {
- // basic resampling of the merge data to put it at zoom 1:1
- var mergeImageData = layerToMerge.getContext().getImageData(
- 0, 0, canvas.width, canvas.height);
- var offMerge = 0;
- var offMergeJ = 0;
- var offThis = 0;
- var offThisJ = 0;
- var alpha = 0;
- for( var j=0; j < canvas.height; ++j ) {
- offMergeJ = parseInt( (origin.y + j * zoom.y), 10 ) * canvas.width;
- offThisJ = j * canvas.width;
- for( var i=0; i < canvas.width; ++i ) {
- // 4 component data: RGB + alpha
- offMerge = 4 * ( parseInt( (origin.x + i * zoom.x), 10 ) + offMergeJ );
- offThis = 4 * ( i + offThisJ );
- // merge non transparent
- alpha = mergeImageData.data[offMerge+3];
- if( alpha !== 0 ) {
- imageData.data[offThis] = mergeImageData.data[offMerge];
- imageData.data[offThis+1] = mergeImageData.data[offMerge+1];
- imageData.data[offThis+2] = mergeImageData.data[offMerge+2];
- imageData.data[offThis+3] = alpha;
- }
- }
- }
- // empty and reset merged layer
- layerToMerge.clear();
- // draw the layer
- this.draw();
- };
-
- /**
- * Set the line colour for the layer.
- * @input {String} colour The line colour.
- */
- this.setLineColour = function(colour)
- {
- context.fillStyle = colour;
- context.strokeStyle = colour;
- };
-
- /**
- * Display the layer.
- * @input {Boolean} val Whether to display the layer or not.
- */
- this.setStyleDisplay = function(val)
- {
- if( val === true )
- {
- canvas.style.display = '';
- }
- else
- {
- canvas.style.display = "none";
- }
- };
-
- /**
- * Check if the layer is visible.
- * @return {Boolean} True if the layer is visible.
- */
- this.isVisible = function()
- {
- if( canvas.style.display === "none" ) {
- return false;
- }
- else {
- return true;
- }
- };
-
- /**
- * Align on another layer.
- * @param {Layer} rhs The layer to align on.
- */
- this.align = function(rhs)
- {
- canvas.style.top = rhs.getCanvas().offsetTop;
- canvas.style.left = rhs.getCanvas().offsetLeft;
- };
-}; // Layer class
-
-/**
- * Get the offset of an input event.
- * @param {Object} event The event to get the offset from.
- * @return {Array} The array of offsets.
- */
-dwv.html.getEventOffset = function (event) {
- var positions = [];
- var ex = 0;
- var ey = 0;
- if ( event.targetTouches ) {
- // get the touch offset from all its parents
- var offsetLeft = 0;
- var offsetTop = 0;
- var offsetParent = event.targetTouches[0].target.offsetParent;
- while ( offsetParent ) {
- if (!isNaN(offsetParent.offsetLeft)) {
- offsetLeft += offsetParent.offsetLeft;
- }
- if (!isNaN(offsetParent.offsetTop)) {
- offsetTop += offsetParent.offsetTop;
- }
- offsetParent = offsetParent.offsetParent;
- }
- // set its position
- var touch = null;
- for ( var i = 0 ; i < event.targetTouches.length; ++i ) {
- touch = event.targetTouches[i];
- ex = touch.pageX - offsetLeft;
- ey = touch.pageY - offsetTop;
- positions.push({'x': ex, 'y': ey});
- }
- }
- else {
- // layerX is used by Firefox
- ex = event.offsetX === undefined ? event.layerX : event.offsetX;
- ey = event.offsetY === undefined ? event.layerY : event.offsetY;
- positions.push({'x': ex, 'y': ey});
- }
- return positions;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.gui = dwv.gui || {};
-dwv.gui.base = dwv.gui.base || {};
-
-/**
- * Loadbox base gui.
- * @constructor
- */
-dwv.gui.base.Loadbox = function (app, loaders)
-{
- /**
- * Loader HTML select.
- * @private
- */
- var loaderSelector = null;
-
- /**
- * Setup the loadbox HTML.
- */
- this.setup = function ()
- {
- // loader select
- loaderSelector = dwv.html.createHtmlSelect("loaderSelect", loaders, "io");
- loaderSelector.onchange = app.onChangeLoader;
-
- // node
- var node = app.getElement("loaderlist");
- // clear it
- while(node.hasChildNodes()) {
- node.removeChild(node.firstChild);
- }
- // append
- node.appendChild(loaderSelector);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display a loader.
- * @param {String} name The name of the loader to show.
- */
- this.displayLoader = function (name)
- {
- var keys = Object.keys(loaders);
- for ( var i = 0; i < keys.length; ++i ) {
- if ( keys[i] === name ) {
- loaders[keys[i]].display(true);
- }
- else {
- loaders[keys[i]].display(false);
- }
- }
- };
-
- /**
- * Reset to its original state.
- */
- this.reset = function ()
- {
- // display first loader
- var keys = Object.keys(loaders);
- this.displayLoader(keys[0]);
- // reset HTML select
- if (loaderSelector) {
- loaderSelector.selectedIndex = 0;
- }
- };
-
-}; // class dwv.gui.base.Loadbox
-
-/**
- * FileLoad base gui.
- * @constructor
- */
-dwv.gui.base.FileLoad = function (app)
-{
- // closure to self
- var self = this;
-
- /**
- * Internal file input change handler.
- * @param {Object} event The change event.
- */
- function onchangeinternal(event) {
- if (typeof self.onchange === "function") {
- self.onchange(event);
- }
- app.onChangeFiles(event);
- }
-
- /**
- * Setup the file load HTML to the page.
- */
- this.setup = function()
- {
- // input
- var fileLoadInput = document.createElement("input");
- fileLoadInput.onchange = onchangeinternal;
- fileLoadInput.type = "file";
- fileLoadInput.multiple = true;
- fileLoadInput.className = "imagefiles";
- fileLoadInput.setAttribute("data-clear-btn","true");
- fileLoadInput.setAttribute("data-mini","true");
-
- // associated div
- var fileLoadDiv = document.createElement("div");
- fileLoadDiv.className = "imagefilesdiv";
- fileLoadDiv.style.display = "none";
- fileLoadDiv.appendChild(fileLoadInput);
-
- // node
- var node = app.getElement("loaderlist");
- // append
- node.appendChild(fileLoadDiv);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the file load HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // file div element
- var node = app.getElement("loaderlist");
- var filediv = node.getElementsByClassName("imagefilesdiv")[0];
- filediv.style.display = bool ? "" : "none";
- };
-
-}; // class dwv.gui.base.FileLoad
-
-/**
- * FolderLoad base gui.
- * @constructor
- */
-dwv.gui.base.FolderLoad = function (app)
-{
- // closure to self
- var self = this;
-
- /**
- * Internal file input change handler.
- * @param {Object} event The change event.
- */
- function onchangeinternal(event) {
- if (typeof self.onchange === "function") {
- self.onchange(event);
- }
- app.onChangeFiles(event);
- }
-
- /**
- * Setup the file load HTML to the page.
- */
- this.setup = function()
- {
- // input
- var fileLoadInput = document.createElement("input");
- fileLoadInput.onchange = onchangeinternal;
- fileLoadInput.type = "file";
- fileLoadInput.multiple = true;
- fileLoadInput.webkitdirectory = true;
- fileLoadInput.className = "imagefolder";
- fileLoadInput.setAttribute("data-clear-btn","true");
- fileLoadInput.setAttribute("data-mini","true");
-
- // associated div
- var folderLoadDiv = document.createElement("div");
- folderLoadDiv.className = "imagefolderdiv";
- folderLoadDiv.style.display = "none";
- folderLoadDiv.appendChild(fileLoadInput);
-
- // node
- var node = app.getElement("loaderlist");
- // append
- node.appendChild(folderLoadDiv);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the folder load HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // file div element
- var node = app.getElement("loaderlist");
- var folderdiv = node.getElementsByClassName("imagefolderdiv")[0];
- folderdiv.style.display = bool ? "" : "none";
- };
-
-}; // class dwv.gui.base.FileLoad
-
-/**
- * UrlLoad base gui.
- * @constructor
- */
-dwv.gui.base.UrlLoad = function (app)
-{
- // closure to self
- var self = this;
-
- /**
- * Internal url input change handler.
- * @param {Object} event The change event.
- */
- function onchangeinternal(event) {
- if (typeof self.onchange === "function") {
- self.onchange(event);
- }
- app.onChangeURL(event);
- }
-
- /**
- * Setup the url load HTML to the page.
- */
- this.setup = function ()
- {
- // input
- var urlLoadInput = document.createElement("input");
- urlLoadInput.onchange = onchangeinternal;
- urlLoadInput.type = "url";
- urlLoadInput.className = "imageurl";
- urlLoadInput.setAttribute("data-clear-btn","true");
- urlLoadInput.setAttribute("data-mini","true");
-
- // associated div
- var urlLoadDiv = document.createElement("div");
- urlLoadDiv.className = "imageurldiv";
- urlLoadDiv.style.display = "none";
- urlLoadDiv.appendChild(urlLoadInput);
-
- // node
- var node = app.getElement("loaderlist");
- // append
- node.appendChild(urlLoadDiv);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the url load HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // url div element
- var node = app.getElement("loaderlist");
- var urldiv = node.getElementsByClassName("imageurldiv")[0];
- urldiv.style.display = bool ? "" : "none";
- };
-
-}; // class dwv.gui.base.UrlLoad
-
-// namespaces
-var dwv = dwv || {};
-dwv.html = dwv.html || {};
-
-/**
- * Style class.
- * @constructor
- */
-dwv.html.Style = function ()
-{
- /**
- * Font size.
- * @private
- * @type Number
- */
- var fontSize = 12;
- /**
- * Font family.
- * @private
- * @type String
- */
- var fontFamily = "Verdana";
- /**
- * Text colour.
- * @private
- * @type String
- */
- var textColour = "#fff";
- /**
- * Line colour.
- * @private
- * @type String
- */
- var lineColour = "#ffff80";
- /**
- * Display scale.
- * @private
- * @type Number
- */
- var displayScale = 1;
- /**
- * Stroke width.
- * @private
- * @type Number
- */
- var strokeWidth = 2;
-
- /**
- * Get the font family.
- * @return {String} The font family.
- */
- this.getFontFamily = function () { return fontFamily; };
-
- /**
- * Get the font size.
- * @return {Number} The font size.
- */
- this.getFontSize = function () { return fontSize; };
-
- /**
- * Get the stroke width.
- * @return {Number} The stroke width.
- */
- this.getStrokeWidth = function () { return strokeWidth; };
-
- /**
- * Get the text colour.
- * @return {String} The text colour.
- */
- this.getTextColour = function () { return textColour; };
-
- /**
- * Get the line colour.
- * @return {String} The line colour.
- */
- this.getLineColour = function () { return lineColour; };
-
- /**
- * Set the line colour.
- * @param {String} colour The line colour.
- */
- this.setLineColour = function (colour) { lineColour = colour; };
-
- /**
- * Set the display scale.
- * @param {String} scale The display scale.
- */
- this.setScale = function (scale) { displayScale = scale; };
-
- /**
- * Scale an input value.
- * @param {Number} value The value to scale.
- */
- this.scale = function (value) { return value / displayScale; };
-};
-
-/**
- * Get the font definition string.
- * @return {String} The font definition string.
- */
-dwv.html.Style.prototype.getFontStr = function ()
-{
- return ("normal " + this.getFontSize() + "px sans-serif");
-};
-
-/**
- * Get the line height.
- * @return {Number} The line height.
- */
-dwv.html.Style.prototype.getLineHeight = function ()
-{
- return ( this.getFontSize() + this.getFontSize() / 5 );
-};
-
-/**
- * Get the font size scaled to the display.
- * @return {Number} The scaled font size.
- */
-dwv.html.Style.prototype.getScaledFontSize = function ()
-{
- return this.scale( this.getFontSize() );
-};
-
-/**
- * Get the stroke width scaled to the display.
- * @return {Number} The scaled stroke width.
- */
-dwv.html.Style.prototype.getScaledStrokeWidth = function ()
-{
- return this.scale( this.getStrokeWidth() );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.gui = dwv.gui || {};
-dwv.gui.base = dwv.gui.base || {};
-
-/**
- * Toolbox base gui.
- * @constructor
- */
-dwv.gui.base.Toolbox = function (app)
-{
- /**
- * Setup the toolbox HTML.
- */
- this.setup = function (list)
- {
- // tool select
- var toolSelector = dwv.html.createHtmlSelect("toolSelect", list, "tool");
- toolSelector.onchange = app.onChangeTool;
-
- // tool list element
- var toolLi = document.createElement("li");
- toolLi.className = "toolLi ui-block-a";
- toolLi.style.display = "none";
- toolLi.appendChild(toolSelector);
-
- // tool ul
- var toolUl = document.createElement("ul");
- toolUl.appendChild(toolLi);
- toolUl.className = "ui-grid-b";
-
- // node
- var node = app.getElement("toolList");
- // append
- node.appendChild(toolUl);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the toolbox HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // tool list element
- var node = app.getElement("toolLi");
- dwv.html.displayElement(node, bool);
- };
-
- /**
- * Initialise the toolbox HTML.
- */
- this.initialise = function (displays)
- {
- // tool select: reset selected option
- var toolSelector = app.getElement("toolSelect");
-
- // update list
- var options = toolSelector.options;
- var selectedIndex = -1;
- for ( var i = 0; i < options.length; ++i ) {
- if ( !displays[i] ) {
- options[i].style.display = "none";
- }
- else {
- if ( selectedIndex === -1 ) {
- selectedIndex = i;
- }
- options[i].style.display = "";
- }
- }
- toolSelector.selectedIndex = selectedIndex;
-
- // refresh
- dwv.gui.refreshElement(toolSelector);
- };
-
-}; // dwv.gui.base.Toolbox
-
-/**
- * WindowLevel tool base gui.
- * @constructor
- */
-dwv.gui.base.WindowLevel = function (app)
-{
- /**
- * Setup the tool HTML.
- */
- this.setup = function ()
- {
- // preset select
- var wlSelector = dwv.html.createHtmlSelect("presetSelect", []);
- wlSelector.onchange = app.onChangeWindowLevelPreset;
- // colour map select
- var cmSelector = dwv.html.createHtmlSelect("colourMapSelect", dwv.tool.colourMaps, "colourmap");
- cmSelector.onchange = app.onChangeColourMap;
-
- // preset list element
- var wlLi = document.createElement("li");
- wlLi.className = "wlLi ui-block-b";
- //wlLi.className = "wlLi";
- wlLi.style.display = "none";
- wlLi.appendChild(wlSelector);
- // colour map list element
- var cmLi = document.createElement("li");
- cmLi.className = "cmLi ui-block-c";
- //cmLi.className = "cmLi";
- cmLi.style.display = "none";
- cmLi.appendChild(cmSelector);
-
- // node
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- // append preset
- node.appendChild(wlLi);
- // append colour map
- node.appendChild(cmLi);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the tool HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // presets list element
- var node = app.getElement("wlLi");
- dwv.html.displayElement(node, bool);
- // colour map list element
- node = app.getElement("cmLi");
- dwv.html.displayElement(node, bool);
- };
-
- /**
- * Initialise the tool HTML.
- */
- this.initialise = function ()
- {
- // create new preset select
- var wlSelector = dwv.html.createHtmlSelect("presetSelect",
- app.getViewController().getWindowLevelPresetsNames(), "wl.presets", true);
- wlSelector.onchange = app.onChangeWindowLevelPreset;
- wlSelector.title = "Select w/l preset.";
-
- // copy html list
- var wlLi = app.getElement("wlLi");
- // clear node
- dwv.html.cleanNode(wlLi);
- // add children
- wlLi.appendChild(wlSelector);
- // refresh
- dwv.gui.refreshElement(wlLi);
-
- // colour map select
- var cmSelector = app.getElement("colourMapSelect");
- cmSelector.selectedIndex = 0;
- // special monochrome1 case
- if( app.getImage().getPhotometricInterpretation() === "MONOCHROME1" )
- {
- cmSelector.selectedIndex = 1;
- }
- // refresh
- dwv.gui.refreshElement(cmSelector);
- };
-
-}; // class dwv.gui.base.WindowLevel
-
-/**
- * Draw tool base gui.
- * @constructor
- */
-dwv.gui.base.Draw = function (app)
-{
- // default colours
- var colours = [
- "Yellow", "Red", "White", "Green", "Blue", "Lime", "Fuchsia", "Black"
- ];
- /**
- * Get the default colour.
- */
- this.getDefaultColour = function () {
- if ( dwv.browser.hasInputColor() ) {
- return "#FFFF80";
- }
- else {
- return colours[0];
- }
- };
-
- /**
- * Setup the tool HTML.
- */
- this.setup = function (shapeList)
- {
- // shape select
- var shapeSelector = dwv.html.createHtmlSelect("shapeSelect", shapeList, "shape");
- shapeSelector.onchange = app.onChangeShape;
- // colour select
- var colourSelector = null;
- if ( dwv.browser.hasInputColor() ) {
- colourSelector = document.createElement("input");
- colourSelector.className = "colourSelect";
- colourSelector.type = "color";
- colourSelector.value = "#FFFF80";
- }
- else {
- colourSelector = dwv.html.createHtmlSelect("colourSelect", colours, "colour");
- }
- colourSelector.onchange = app.onChangeLineColour;
-
- // shape list element
- var shapeLi = document.createElement("li");
- shapeLi.className = "shapeLi ui-block-c";
- shapeLi.style.display = "none";
- shapeLi.appendChild(shapeSelector);
- //shapeLi.setAttribute("class","ui-block-c");
- // colour list element
- var colourLi = document.createElement("li");
- colourLi.className = "colourLi ui-block-b";
- colourLi.style.display = "none";
- colourLi.appendChild(colourSelector);
- //colourLi.setAttribute("class","ui-block-b");
-
- // node
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- // apend shape
- node.appendChild(shapeLi);
- // append colour
- node.appendChild(colourLi);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the tool HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // colour list element
- var node = app.getElement("colourLi");
- dwv.html.displayElement(node, bool);
- // shape list element
- node = app.getElement("shapeLi");
- dwv.html.displayElement(node, bool);
- };
-
- /**
- * Initialise the tool HTML.
- */
- this.initialise = function ()
- {
- // shape select: reset selected option
- var shapeSelector = app.getElement("shapeSelect");
- shapeSelector.selectedIndex = 0;
- // refresh
- dwv.gui.refreshElement(shapeSelector);
-
- // colour select: reset selected option
- var colourSelector = app.getElement("colourSelect");
- if ( !dwv.browser.hasInputColor() ) {
- colourSelector.selectedIndex = 0;
- }
- // refresh
- dwv.gui.refreshElement(colourSelector);
- };
-
-}; // class dwv.gui.base.Draw
-
-/**
- * Base gui for a tool with a colour setting.
- * @constructor
- */
-dwv.gui.base.ColourTool = function (app, prefix)
-{
- // default colours
- var colours = [
- "Yellow", "Red", "White", "Green", "Blue", "Lime", "Fuchsia", "Black"
- ];
- // colour selector class
- var colourSelectClassName = prefix + "ColourSelect";
- // colour selector class
- var colourLiClassName = prefix + "ColourLi";
-
- /**
- * Get the default colour.
- */
- this.getDefaultColour = function () {
- if ( dwv.browser.hasInputColor() ) {
- return "#FFFF80";
- }
- else {
- return colours[0];
- }
- };
-
- /**
- * Setup the tool HTML.
- */
- this.setup = function ()
- {
- // colour select
- var colourSelector = null;
- if ( dwv.browser.hasInputColor() ) {
- colourSelector = document.createElement("input");
- colourSelector.className = colourSelectClassName;
- colourSelector.type = "color";
- colourSelector.value = "#FFFF80";
- }
- else {
- colourSelector = dwv.html.createHtmlSelect(colourSelectClassName, colours, "colour");
- }
- colourSelector.onchange = app.onChangeLineColour;
-
- // colour list element
- var colourLi = document.createElement("li");
- colourLi.className = colourLiClassName + " ui-block-b";
- colourLi.style.display = "none";
- //colourLi.setAttribute("class","ui-block-b");
- colourLi.appendChild(colourSelector);
-
- // node
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- // apend colour
- node.appendChild(colourLi);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the tool HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function (bool)
- {
- // colour list
- var node = app.getElement(colourLiClassName);
- dwv.html.displayElement(node, bool);
- };
-
- /**
- * Initialise the tool HTML.
- */
- this.initialise = function ()
- {
- var colourSelector = app.getElement(colourSelectClassName);
- if ( !dwv.browser.hasInputColor() ) {
- colourSelector.selectedIndex = 0;
- }
- dwv.gui.refreshElement(colourSelector);
- };
-
-}; // class dwv.gui.base.ColourTool
-
-/**
- * ZoomAndPan tool base gui.
- * @constructor
- */
-dwv.gui.base.ZoomAndPan = function (app)
-{
- /**
- * Setup the tool HTML.
- */
- this.setup = function()
- {
- // reset button
- var button = document.createElement("button");
- button.className = "zoomResetButton";
- button.name = "zoomResetButton";
- button.onclick = app.onZoomReset;
- button.setAttribute("style","width:100%; margin-top:0.5em;");
- button.setAttribute("class","ui-btn ui-btn-b");
- var text = document.createTextNode(dwv.i18n("basics.reset"));
- button.appendChild(text);
-
- // list element
- var liElement = document.createElement("li");
- liElement.className = "zoomLi ui-block-c";
- liElement.style.display = "none";
- //liElement.setAttribute("class","ui-block-c");
- liElement.appendChild(button);
-
- // node
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- // append element
- node.appendChild(liElement);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the tool HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function(bool)
- {
- // display list element
- var node = app.getElement("zoomLi");
- dwv.html.displayElement(node, bool);
- };
-
-}; // class dwv.gui.base.ZoomAndPan
-
-/**
- * Scroll tool base gui.
- * @constructor
- */
-dwv.gui.base.Scroll = function (app)
-{
- /**
- * Setup the tool HTML.
- */
- this.setup = function()
- {
- // list element
- var liElement = document.createElement("li");
- liElement.className = "scrollLi ui-block-c";
- liElement.style.display = "none";
-
- // node
- var node = app.getElement("toolList").getElementsByTagName("ul")[0];
- // append element
- node.appendChild(liElement);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Display the tool HTML.
- * @param {Boolean} bool True to display, false to hide.
- */
- this.display = function(bool)
- {
- // display list element
- var node = app.getElement("scrollLi");
- dwv.html.displayElement(node, bool);
- };
-
-}; // class dwv.gui.base.Scroll
-
-// namespaces
-var dwv = dwv || {};
-dwv.gui = dwv.gui || {};
-dwv.gui.base = dwv.gui.base || {};
-
-/**
- * Undo base gui.
- * @constructor
- */
-dwv.gui.base.Undo = function (app)
-{
- /**
- * Setup the undo HTML.
- */
- this.setup = function ()
- {
- var paragraph = document.createElement("p");
- paragraph.appendChild(document.createTextNode("History:"));
- paragraph.appendChild(document.createElement("br"));
-
- var select = document.createElement("select");
- select.className = "history_list";
- select.name = "history_list";
- select.multiple = "multiple";
- paragraph.appendChild(select);
-
- // node
- var node = app.getElement("history");
- // clear it
- while(node.hasChildNodes()) {
- node.removeChild(node.firstChild);
- }
- // append
- node.appendChild(paragraph);
- // refresh
- dwv.gui.refreshElement(node);
- };
-
- /**
- * Clear the command list of the undo HTML.
- */
- this.initialise = function ()
- {
- var select = app.getElement("history_list");
- if ( select && select.length !== 0 ) {
- for( var i = select.length - 1; i >= 0; --i)
- {
- select.remove(i);
- }
- }
- // refresh
- dwv.gui.refreshElement(select);
- };
-
- /**
- * Add a command to the undo HTML.
- * @param {String} commandName The name of the command to add.
- */
- this.addCommandToUndoHtml = function (commandName)
- {
- var select = app.getElement("history_list");
- // remove undone commands
- var count = select.length - (select.selectedIndex+1);
- if( count > 0 )
- {
- for( var i = 0; i < count; ++i)
- {
- select.remove(select.length-1);
- }
- }
- // add new option
- var option = document.createElement("option");
- option.text = commandName;
- option.value = commandName;
- select.add(option);
- // increment selected index
- select.selectedIndex++;
- // refresh
- dwv.gui.refreshElement(select);
- };
-
- /**
- * Enable the last command of the undo HTML.
- * @param {Boolean} enable Flag to enable or disable the command.
- */
- this.enableInUndoHtml = function (enable)
- {
- var select = app.getElement("history_list");
- // enable or not (order is important)
- var option;
- if( enable )
- {
- // increment selected index
- select.selectedIndex++;
- // enable option
- option = select.options[select.selectedIndex];
- option.disabled = false;
- }
- else
- {
- // disable option
- option = select.options[select.selectedIndex];
- option.disabled = true;
- // decrement selected index
- select.selectedIndex--;
- }
- // refresh
- dwv.gui.refreshElement(select);
- };
-
-}; // class dwv.gui.base.Undo
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-
-// JPEG Baseline
-var hasJpegBaselineDecoder = (typeof JpegImage !== "undefined");
-var JpegImage = JpegImage || {};
-// JPEG Lossless
-var hasJpegLosslessDecoder = (typeof jpeg !== "undefined") &&
- (typeof jpeg.lossless !== "undefined");
-var jpeg = jpeg || {};
-jpeg.lossless = jpeg.lossless || {};
-// JPEG 2000
-var hasJpeg2000Decoder = (typeof JpxImage !== "undefined");
-var JpxImage = JpxImage || {};
-
-/**
- * Asynchronous pixel buffer decoder.
- * @param {String} script The path to the decoder script to be used by the web worker.
- */
-dwv.image.AsynchPixelBufferDecoder = function (script)
-{
- // initialise the thread pool
- var pool = new dwv.utils.ThreadPool(15);
- pool.init();
-
- /**
- * Decode a pixel buffer.
- * @param {Array} pixelBuffer The pixel buffer.
- * @param {Number} bitsAllocated The bits allocated per element in the buffer.
- * @param {Boolean} isSigned Is the data signed.
- * @param {Function} callback Callback function to handle decoded data.
- */
- this.decode = function (pixelBuffer, bitsAllocated, isSigned, callback) {
- // (re)set event handler
- pool.onpoolworkend = this.ondecodeend;
- pool.onworkerend = this.ondecoded;
- // create worker task
- var workerTask = new dwv.utils.WorkerTask(script, callback, {
- 'buffer': pixelBuffer,
- 'bitsAllocated': bitsAllocated,
- 'isSigned': isSigned } );
- // add it the queue and run it
- pool.addWorkerTask(workerTask);
- };
-
- /**
- * Abort decoding.
- */
- this.abort = function () {
- // abort the thread pool
- pool.abort();
- };
-};
-
-/**
- * Handle a decode end event.
- */
-dwv.image.AsynchPixelBufferDecoder.prototype.ondecodeend = function ()
-{
- // default does nothing.
-};
-
-/**
- * Handle a decode event.
- */
-dwv.image.AsynchPixelBufferDecoder.prototype.ondecoded = function ()
-{
- // default does nothing.
-};
-
-/**
- * Synchronous pixel buffer decoder.
- * @param {String} algoName The decompression algorithm name.
- */
-dwv.image.SynchPixelBufferDecoder = function (algoName)
-{
- /**
- * Decode a pixel buffer.
- * @param {Array} pixelBuffer The pixel buffer.
- * @param {Number} bitsAllocated The bits allocated per element in the buffer.
- * @param {Boolean} isSigned Is the data signed.
- * @param {Function} callback Callback function to handle decoded data.
- * @external jpeg
- * @external JpegImage
- * @external JpxImage
- */
- this.decode = function (pixelBuffer, bitsAllocated, isSigned, callback) {
- var decoder = null;
- var decodedBuffer = null;
- if( algoName === "jpeg-lossless" ) {
- if ( !hasJpegLosslessDecoder ) {
- throw new Error("No JPEG Lossless decoder provided");
- }
- // bytes per element
- var bpe = bitsAllocated / 8;
- var buf = new Uint8Array( pixelBuffer );
- decoder = new jpeg.lossless.Decoder();
- var decoded = decoder.decode(buf.buffer, 0, buf.buffer.byteLength, bpe);
- if (bitsAllocated === 8) {
- if (isSigned) {
- decodedBuffer = new Int8Array(decoded.buffer);
- }
- else {
- decodedBuffer = new Uint8Array(decoded.buffer);
- }
- }
- else if (bitsAllocated === 16) {
- if (isSigned) {
- decodedBuffer = new Int16Array(decoded.buffer);
- }
- else {
- decodedBuffer = new Uint16Array(decoded.buffer);
- }
- }
- }
- else if ( algoName === "jpeg-baseline" ) {
- if ( !hasJpegBaselineDecoder ) {
- throw new Error("No JPEG Baseline decoder provided");
- }
- decoder = new JpegImage();
- decoder.parse( pixelBuffer );
- decodedBuffer = decoder.getData(decoder.width,decoder.height);
- }
- else if( algoName === "jpeg2000" ) {
- if ( !hasJpeg2000Decoder ) {
- throw new Error("No JPEG 2000 decoder provided");
- }
- // decompress pixel buffer into Int16 image
- decoder = new JpxImage();
- decoder.parse( pixelBuffer );
- // set the pixel buffer
- decodedBuffer = decoder.tiles[0].items;
- }
- // send events
- this.ondecoded();
- this.ondecodeend();
- // call callback with decoded buffer as array
- callback({data: [decodedBuffer]});
- };
-
- /**
- * Abort decoding.
- */
- this.abort = function () {
- // nothing to do in the synchronous case.
- };
-};
-
-/**
- * Handle a decode end event.
- */
-dwv.image.SynchPixelBufferDecoder.prototype.ondecodeend = function ()
-{
- // default does nothing.
-};
-
-/**
- * Handle a decode event.
- */
-dwv.image.SynchPixelBufferDecoder.prototype.ondecoded = function ()
-{
- // default does nothing.
-};
-
-/**
- * Decode a pixel buffer.
- * @constructor
- * @param {String} algoName The decompression algorithm name.
- * If the 'dwv.image.decoderScripts' variable does not contain the desired algorythm,
- * the decoder will switch to the synchronous mode.
- */
-dwv.image.PixelBufferDecoder = function (algoName)
-{
- /**
- * Pixel decoder.
- * Defined only once.
- * @private
- * @type Object
- */
- var pixelDecoder = null;
-
- // initialise the asynch decoder (if possible)
- if (typeof dwv.image.decoderScripts !== "undefined" &&
- typeof dwv.image.decoderScripts[algoName] !== "undefined") {
- pixelDecoder = new dwv.image.AsynchPixelBufferDecoder(dwv.image.decoderScripts[algoName]);
- } else {
- pixelDecoder = new dwv.image.SynchPixelBufferDecoder(algoName);
- }
-
- /**
- * Get data from an input buffer using a DICOM parser.
- * @param {Array} pixelBuffer The input data buffer.
- * @param {Number} bitsAllocated The bits allocated per element in the buffer.
- * @param {Boolean} isSigned Is the data signed.
- * @param {Object} callback The callback on the conversion.
- */
- this.decode = function (pixelBuffer, bitsAllocated, isSigned, callback)
- {
- // set event handler
- pixelDecoder.ondecodeend = this.ondecodeend;
- pixelDecoder.ondecoded = this.ondecoded;
- // decode and call the callback
- pixelDecoder.decode(pixelBuffer, bitsAllocated, isSigned, callback);
- };
-
- /**
- * Abort decoding.
- */
- this.abort = function ()
- {
- // decoder classes should define an abort
- pixelDecoder.abort();
- };
-};
-
-/**
- * Handle a decode end event.
- */
-dwv.image.PixelBufferDecoder.prototype.ondecodeend = function ()
-{
- // default does nothing.
-};
-
-/**
- * Handle a decode end event.
- */
-dwv.image.PixelBufferDecoder.prototype.ondecoded = function ()
-{
- // default does nothing.
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-
-/**
- * Create a dwv.image.View from a DICOM buffer.
- * @constructor
- */
-dwv.image.DicomBufferToView = function ()
-{
- // closure to self
- var self = this;
-
- /**
- * The default character set (optional).
- * @private
- * @type String
- */
- var defaultCharacterSet;
-
- /**
- * Set the default character set.
- * param {String} The character set.
- */
- this.setDefaultCharacterSet = function (characterSet) {
- defaultCharacterSet = characterSet;
- };
-
- /**
- * Pixel buffer decoder.
- * Define only once to allow optional asynchronous mode.
- * @private
- * @type Object
- */
- var pixelDecoder = null;
-
- /**
- * Get data from an input buffer using a DICOM parser.
- * @param {Array} buffer The input data buffer.
- * @param {Number} dataIndex The data index.
- */
- this.convert = function (buffer, dataIndex)
- {
- // DICOM parser
- var dicomParser = new dwv.dicom.DicomParser();
- dicomParser.setDefaultCharacterSet(defaultCharacterSet);
- // parse the buffer
- dicomParser.parse(buffer);
-
- var pixelBuffer = dicomParser.getRawDicomElements().x7FE00010.value;
- var syntax = dwv.dicom.cleanString(dicomParser.getRawDicomElements().x00020010.value[0]);
- var algoName = dwv.dicom.getSyntaxDecompressionName(syntax);
- var needDecompression = (algoName !== null);
-
- // worker callback
- var onDecodedFirstFrame = function (/*event*/) {
- // create the image
- var imageFactory = new dwv.image.ImageFactory();
- var image = imageFactory.create( dicomParser.getDicomElements(), pixelBuffer );
- // create the view
- var viewFactory = new dwv.image.ViewFactory();
- var view = viewFactory.create( dicomParser.getDicomElements(), image );
- // return
- self.onload({"view": view, "info": dicomParser.getDicomElements().dumpToTable()});
- };
-
- if ( needDecompression ) {
- var bitsAllocated = dicomParser.getRawDicomElements().x00280100.value[0];
- var pixelRepresentation = dicomParser.getRawDicomElements().x00280103.value[0];
- var isSigned = (pixelRepresentation === 1);
- var nFrames = pixelBuffer.length;
-
- if (!pixelDecoder){
- pixelDecoder = new dwv.image.PixelBufferDecoder(algoName);
- }
-
- // loadend event
- pixelDecoder.ondecodeend = function () {
- self.onloadend();
- };
-
- // send an onload event for mono frame
- if ( nFrames === 1 ) {
- pixelDecoder.ondecoded = function () {
- self.onloadend();
- };
- }
-
- // decoder callback
- var countDecodedFrames = 0;
- var onDecodedFrame = function (frame) {
- return function (event) {
- // send progress
- ++countDecodedFrames;
- var ev = {'type': "load-progress", 'lengthComputable': true,
- 'loaded': (countDecodedFrames * 100 / nFrames), 'total': 100};
- if ( typeof dataIndex !== "undefined") {
- ev.index = dataIndex;
- }
- self.onprogress(ev);
- // store data
- pixelBuffer[frame] = event.data[0];
- // create image for first frame
- if ( frame === 0 ) {
- onDecodedFirstFrame();
- }
- };
- };
-
- // decompress synchronously the first frame to create the image
- pixelDecoder.decode(pixelBuffer[0],
- bitsAllocated, isSigned, onDecodedFrame(0), false);
-
- // decompress the possible other frames
- if ( nFrames !== 1 ) {
- // decode (asynchronously if possible)
- for (var f = 1; f < nFrames; ++f) {
- pixelDecoder.decode(pixelBuffer[f],
- bitsAllocated, isSigned, onDecodedFrame(f));
- }
- }
- }
- // no decompression
- else {
- // send progress
- var evnodec = {'type': 'load-progress', 'lengthComputable': true,
- 'loaded': 100, 'total': 100};
- if ( typeof dataIndex !== "undefined") {
- evnodec.index = dataIndex;
- }
- self.onprogress(evnodec);
- // create image
- onDecodedFirstFrame();
- // send load events
- self.onloadend();
- }
- };
-
- /**
- * Abort a conversion.
- */
- this.abort = function () {
- if ( pixelDecoder ) {
- pixelDecoder.abort();
- }
- };
-};
-
-/**
- * Handle a load end event.
- * @param {Object} event The load end event.
- * Default does nothing.
- */
-dwv.image.DicomBufferToView.prototype.onloadend = function (/*event*/) {};
-/**
- * Handle a load event.
- * @param {Object} event The load event.
- * Default does nothing.
- */
-dwv.image.DicomBufferToView.prototype.onload = function (/*event*/) {};
-/**
- * Handle a load progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.image.DicomBufferToView.prototype.onprogress = function (/*event*/) {};
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-
-/**
- * Create a simple array buffer from an ImageData buffer.
- * @param {Object} imageData The ImageData taken from a context.
- * @return {Array} The image buffer.
- */
-dwv.image.imageDataToBuffer = function (imageData) {
- // remove alpha
- // TODO support passing the full image data
- var dataLen = imageData.data.length;
- var buffer = new Uint8Array( (dataLen / 4) * 3);
- var j = 0;
- for( var i = 0; i < dataLen; i+=4 ) {
- buffer[j] = imageData.data[i];
- buffer[j+1] = imageData.data[i+1];
- buffer[j+2] = imageData.data[i+2];
- j+=3;
- }
- return buffer;
-};
-
-/**
- * Get data from an input context imageData.
- * @param {Number} width The width of the coresponding image.
- * @param {Number} height The height of the coresponding image.
- * @param {Number} sliceIndex The slice index of the imageData.
- * @param {Object} imageBuffer The image buffer.
- * @param {Number} numberOfFrames The final number of frames.
- * @return {Object} The corresponding view.
- */
-dwv.image.getDefaultView = function (
- width, height, sliceIndex,
- imageBuffer, numberOfFrames, info) {
- // image size
- var imageSize = new dwv.image.Size(width, height);
- // default spacing
- // TODO: misleading...
- var imageSpacing = new dwv.image.Spacing(1,1);
- // default origin
- var origin = new dwv.math.Point3D(0,0,sliceIndex);
- // create image
- var geometry = new dwv.image.Geometry(origin, imageSize, imageSpacing );
- var image = new dwv.image.Image( geometry, imageBuffer, numberOfFrames );
- image.setPhotometricInterpretation("RGB");
- // meta information
- var meta = {};
- meta.BitsStored = 8;
- image.setMeta(meta);
- // overlay
- image.setFirstOverlay( dwv.gui.info.createOverlaysForDom(info) );
- // view
- var view = new dwv.image.View(image);
- // defaut preset
- view.setWindowLevelMinMax();
- // return
- return view;
-};
-
-/**
- * Get data from an input image using a canvas.
- * @param {Object} image The DOM Image.
- * @return {Mixed} The corresponding view and info.
- */
-dwv.image.getViewFromDOMImage = function (image)
-{
- // image size
- var width = image.width;
- var height = image.height;
-
- // draw the image in the canvas in order to get its data
- var canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- var ctx = canvas.getContext('2d');
- ctx.drawImage(image, 0, 0);
- // get the image data
- var imageData = ctx.getImageData(0, 0, width, height);
-
- // image properties
- var info = [];
- if ( typeof image.origin === "string" ) {
- info.push({ "name": "origin", "value": image.origin });
- } else {
- info.push({ "name": "fileName", "value": image.origin.name });
- info.push({ "name": "fileType", "value": image.origin.type });
- info.push({ "name": "fileLastModifiedDate", "value": image.origin.lastModifiedDate });
- }
- info.push({ "name": "imageWidth", "value": width });
- info.push({ "name": "imageHeight", "value": height });
-
- // create view
- var sliceIndex = image.index ? image.index : 0;
- var imageBuffer = dwv.image.imageDataToBuffer(imageData);
- var view = dwv.image.getDefaultView(
- width, height, sliceIndex, [imageBuffer], 1, info);
-
- // return
- return {"view": view, "info": info};
-};
-
-/**
- * Get data from an input image using a canvas.
- * @param {Object} video The DOM Video.
- * @param {Object} callback The function to call once the data is loaded.
- * @param {Object} cbprogress The function to call to report progress.
- * @param {Object} cbonloadend The function to call to report load end.
- * @param {Number} dataindex The data index.
- */
-dwv.image.getViewFromDOMVideo = function (video, callback, cbprogress, cbonloadend, dataIndex)
-{
- // video size
- var width = video.videoWidth;
- var height = video.videoHeight;
-
- // default frame rate...
- var frameRate = 30;
- // number of frames
- var numberOfFrames = Math.floor(video.duration * frameRate);
-
- // video properties
- var info = [];
- if( video.file )
- {
- info.push({ "name": "fileName", "value": video.file.name });
- info.push({ "name": "fileType", "value": video.file.type });
- info.push({ "name": "fileLastModifiedDate", "value": video.file.lastModifiedDate });
- }
- info.push({ "name": "imageWidth", "value": width });
- info.push({ "name": "imageHeight", "value": height });
- info.push({ "name": "numberOfFrames", "value": numberOfFrames });
-
- // draw the image in the canvas in order to get its data
- var canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- var ctx = canvas.getContext('2d');
-
- // using seeked to loop through all video frames
- video.addEventListener('seeked', onseeked, false);
-
- // current frame index
- var frameIndex = 0;
- // video view
- var view = null;
-
- // draw the context and store it as a frame
- function storeFrame() {
- // send progress
- var evprog = {'type': 'load-progress', 'lengthComputable': true,
- 'loaded': frameIndex, 'total': numberOfFrames};
- if (typeof dataIndex !== "undefined") {
- evprog.index = dataIndex;
- }
- cbprogress(evprog);
- // draw image
- ctx.drawImage(video, 0, 0);
- // context to image buffer
- var imgBuffer = dwv.image.imageDataToBuffer(
- ctx.getImageData(0, 0, width, height) );
- if (frameIndex === 0) {
- // create view
- view = dwv.image.getDefaultView(
- width, height, 1, [imgBuffer], numberOfFrames, info);
- // call callback
- callback( {"view": view, "info": info } );
- } else {
- view.appendFrameBuffer(imgBuffer);
- }
- }
-
- // handle seeked event
- function onseeked(/*event*/) {
- // store
- storeFrame();
- // increment index
- ++frameIndex;
- // set the next time
- // (not using currentTime, it seems to get offseted)
- var nextTime = frameIndex / frameRate;
- if (nextTime <= this.duration) {
- this.currentTime = nextTime;
- } else {
- cbonloadend();
- // stop listening
- video.removeEventListener('seeked', onseeked);
- }
- }
-
- // trigger the first seeked
- video.currentTime = 0;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-/** @namespace */
-dwv.image.filter = dwv.image.filter || {};
-
-/**
- * Threshold an image between an input minimum and maximum.
- * @constructor
- */
-dwv.image.filter.Threshold = function()
-{
- /**
- * Threshold minimum.
- * @private
- * @type Number
- */
- var min = 0;
- /**
- * Threshold maximum.
- * @private
- * @type Number
- */
- var max = 0;
-
- /**
- * Get the threshold minimum.
- * @return {Number} The threshold minimum.
- */
- this.getMin = function() { return min; };
- /**
- * Set the threshold minimum.
- * @param {Number} val The threshold minimum.
- */
- this.setMin = function(val) { min = val; };
- /**
- * Get the threshold maximum.
- * @return {Number} The threshold maximum.
- */
- this.getMax = function() { return max; };
- /**
- * Set the threshold maximum.
- * @param {Number} val The threshold maximum.
- */
- this.setMax = function(val) { max = val; };
- /**
- * Get the name of the filter.
- * @return {String} The name of the filter.
- */
- this.getName = function() { return "Threshold"; };
-
- /**
- * Original image.
- * @private
- * @type Object
- */
- var originalImage = null;
- /**
- * Set the original image.
- * @param {Object} image The original image.
- */
- this.setOriginalImage = function (image) { originalImage = image; };
- /**
- * Get the original image.
- * @return {Object} image The original image.
- */
- this.getOriginalImage = function () { return originalImage; };
-};
-
-/**
- * Transform the main image using this filter.
- * @return {Object} The transformed image.
- */
-dwv.image.filter.Threshold.prototype.update = function ()
-{
- var image = this.getOriginalImage();
- var imageMin = image.getDataRange().min;
- var self = this;
- var threshFunction = function (value) {
- if ( value < self.getMin() || value > self.getMax() ) {
- return imageMin;
- }
- else {
- return value;
- }
- };
- return image.transform( threshFunction );
-};
-
-/**
- * Sharpen an image using a sharpen convolution matrix.
- * @constructor
- */
-dwv.image.filter.Sharpen = function()
-{
- /**
- * Get the name of the filter.
- * @return {String} The name of the filter.
- */
- this.getName = function() { return "Sharpen"; };
- /**
- * Original image.
- * @private
- * @type Object
- */
- var originalImage = null;
- /**
- * Set the original image.
- * @param {Object} image The original image.
- */
- this.setOriginalImage = function (image) { originalImage = image; };
- /**
- * Get the original image.
- * @return {Object} image The original image.
- */
- this.getOriginalImage = function () { return originalImage; };
-};
-
-/**
- * Transform the main image using this filter.
- * @return {Object} The transformed image.
- */
-dwv.image.filter.Sharpen.prototype.update = function()
-{
- var image = this.getOriginalImage();
-
- return image.convolute2D(
- [ 0, -1, 0,
- -1, 5, -1,
- 0, -1, 0 ] );
-};
-
-/**
- * Apply a Sobel filter to an image.
- * @constructor
- */
-dwv.image.filter.Sobel = function()
-{
- /**
- * Get the name of the filter.
- * @return {String} The name of the filter.
- */
- this.getName = function() { return "Sobel"; };
- /**
- * Original image.
- * @private
- * @type Object
- */
- var originalImage = null;
- /**
- * Set the original image.
- * @param {Object} image The original image.
- */
- this.setOriginalImage = function (image) { originalImage = image; };
- /**
- * Get the original image.
- * @return {Object} image The original image.
- */
- this.getOriginalImage = function () { return originalImage; };
-};
-
-/**
- * Transform the main image using this filter.
- * @return {Object} The transformed image.
- */
-dwv.image.filter.Sobel.prototype.update = function()
-{
- var image = this.getOriginalImage();
-
- var gradX = image.convolute2D(
- [ 1, 0, -1,
- 2, 0, -2,
- 1, 0, -1 ] );
-
- var gradY = image.convolute2D(
- [ 1, 2, 1,
- 0, 0, 0,
- -1, -2, -1 ] );
-
- return gradX.compose( gradY, function (x,y) { return Math.sqrt(x*x+y*y); } );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-
-/**
- * 2D/3D Size class.
- * @constructor
- * @param {Number} numberOfColumns The number of columns.
- * @param {Number} numberOfRows The number of rows.
- * @param {Number} numberOfSlices The number of slices.
-*/
-dwv.image.Size = function ( numberOfColumns, numberOfRows, numberOfSlices )
-{
- /**
- * Get the number of columns.
- * @return {Number} The number of columns.
- */
- this.getNumberOfColumns = function () { return numberOfColumns; };
- /**
- * Get the number of rows.
- * @return {Number} The number of rows.
- */
- this.getNumberOfRows = function () { return numberOfRows; };
- /**
- * Get the number of slices.
- * @return {Number} The number of slices.
- */
- this.getNumberOfSlices = function () { return (numberOfSlices || 1.0); };
-};
-
-/**
- * Get the size of a slice.
- * @return {Number} The size of a slice.
- */
-dwv.image.Size.prototype.getSliceSize = function () {
- return this.getNumberOfColumns() * this.getNumberOfRows();
-};
-
-/**
- * Get the total size.
- * @return {Number} The total size.
- */
-dwv.image.Size.prototype.getTotalSize = function () {
- return this.getSliceSize() * this.getNumberOfSlices();
-};
-
-/**
- * Check for equality.
- * @param {Size} rhs The object to compare to.
- * @return {Boolean} True if both objects are equal.
- */
-dwv.image.Size.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getNumberOfColumns() === rhs.getNumberOfColumns() &&
- this.getNumberOfRows() === rhs.getNumberOfRows() &&
- this.getNumberOfSlices() === rhs.getNumberOfSlices();
-};
-
-/**
- * Check that coordinates are within bounds.
- * @param {Number} i The column coordinate.
- * @param {Number} j The row coordinate.
- * @param {Number} k The slice coordinate.
- * @return {Boolean} True if the given coordinates are within bounds.
- */
-dwv.image.Size.prototype.isInBounds = function ( i, j, k ) {
- if( i < 0 || i > this.getNumberOfColumns() - 1 ||
- j < 0 || j > this.getNumberOfRows() - 1 ||
- k < 0 || k > this.getNumberOfSlices() - 1 ) {
- return false;
- }
- return true;
-};
-
-/**
- * Get a string representation of the Vector3D.
- * @return {String} The vector as a string.
- */
-dwv.image.Size.prototype.toString = function () {
- return "(" + this.getNumberOfColumns() +
- ", " + this.getNumberOfRows() +
- ", " + this.getNumberOfSlices() + ")";
-};
-
-/**
- * 2D/3D Spacing class.
- * @constructor
- * @param {Number} columnSpacing The column spacing.
- * @param {Number} rowSpacing The row spacing.
- * @param {Number} sliceSpacing The slice spacing.
- */
-dwv.image.Spacing = function ( columnSpacing, rowSpacing, sliceSpacing )
-{
- /**
- * Get the column spacing.
- * @return {Number} The column spacing.
- */
- this.getColumnSpacing = function () { return columnSpacing; };
- /**
- * Get the row spacing.
- * @return {Number} The row spacing.
- */
- this.getRowSpacing = function () { return rowSpacing; };
- /**
- * Get the slice spacing.
- * @return {Number} The slice spacing.
- */
- this.getSliceSpacing = function () { return (sliceSpacing || 1.0); };
-};
-
-/**
- * Check for equality.
- * @param {Spacing} rhs The object to compare to.
- * @return {Boolean} True if both objects are equal.
- */
-dwv.image.Spacing.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getColumnSpacing() === rhs.getColumnSpacing() &&
- this.getRowSpacing() === rhs.getRowSpacing() &&
- this.getSliceSpacing() === rhs.getSliceSpacing();
-};
-
-/**
- * Get a string representation of the Vector3D.
- * @return {String} The vector as a string.
- */
-dwv.image.Spacing.prototype.toString = function () {
- return "(" + this.getColumnSpacing() +
- ", " + this.getRowSpacing() +
- ", " + this.getSliceSpacing() + ")";
-};
-
-
-/**
- * 2D/3D Geometry class.
- * @constructor
- * @param {Object} origin The object origin (a 3D point).
- * @param {Object} size The object size.
- * @param {Object} spacing The object spacing.
- * @param {Object} orientation The object orientation (3*3 matrix, default to 3*3 identity).
- */
-dwv.image.Geometry = function ( origin, size, spacing, orientation )
-{
- // check input origin
- if( typeof origin === 'undefined' ) {
- origin = new dwv.math.Point3D(0,0,0);
- }
- var origins = [origin];
- // check input orientation
- if( typeof orientation === 'undefined' ) {
- orientation = new dwv.math.getIdentityMat33();
- }
-
- /**
- * Get the object first origin.
- * @return {Object} The object first origin.
- */
- this.getOrigin = function () { return origin; };
- /**
- * Get the object origins.
- * @return {Array} The object origins.
- */
- this.getOrigins = function () { return origins; };
- /**
- * Get the object size.
- * @return {Object} The object size.
- */
- this.getSize = function () { return size; };
- /**
- * Get the object spacing.
- * @return {Object} The object spacing.
- */
- this.getSpacing = function () { return spacing; };
- /**
- * Get the object orientation.
- * @return {Object} The object orientation.
- */
- this.getOrientation = function () { return orientation; };
-
- /**
- * Get the slice position of a point in the current slice layout.
- * @param {Object} point The point to evaluate.
- */
- this.getSliceIndex = function (point)
- {
- // cannot use this.worldToIndex(point).getK() since
- // we cannot guaranty consecutive slices...
-
- // find the closest index
- var closestSliceIndex = 0;
- var minDist = point.getDistance(origins[0]);
- var dist = 0;
- for( var i = 0; i < origins.length; ++i )
- {
- dist = point.getDistance(origins[i]);
- if( dist < minDist )
- {
- minDist = dist;
- closestSliceIndex = i;
- }
- }
- // we have the closest point, are we before or after
- var normal = new dwv.math.Vector3D(
- orientation.get(2,0), orientation.get(2,1), orientation.get(2,2));
- var dotProd = normal.dotProduct( point.minus(origins[closestSliceIndex]) );
- var sliceIndex = ( dotProd > 0 ) ? closestSliceIndex + 1 : closestSliceIndex;
- return sliceIndex;
- };
-
- /**
- * Append an origin to the geometry.
- * @param {Object} origin The origin to append.
- * @param {Number} index The index at which to append.
- */
- this.appendOrigin = function (origin, index)
- {
- // add in origin array
- origins.splice(index, 0, origin);
- // increment slice number
- size = new dwv.image.Size(
- size.getNumberOfColumns(),
- size.getNumberOfRows(),
- size.getNumberOfSlices() + 1);
- };
-
-};
-
-/**
- * Check for equality.
- * @param {Geometry} rhs The object to compare to.
- * @return {Boolean} True if both objects are equal.
- */
-dwv.image.Geometry.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getOrigin() === rhs.getOrigin() &&
- this.getSize() === rhs.getSize() &&
- this.getSpacing() === rhs.getSpacing();
-};
-
-/**
- * Convert an index to an offset in memory.
- * @param {Object} index The index to convert.
- */
-dwv.image.Geometry.prototype.indexToOffset = function (index) {
- var size = this.getSize();
- return index.getI() +
- index.getJ() * size.getNumberOfColumns() +
- index.getK() * size.getSliceSize();
-};
-
-/**
- * Convert an index into world coordinates.
- * @param {Object} index The index to convert.
- */
-dwv.image.Geometry.prototype.indexToWorld = function (index) {
- var origin = this.getOrigin();
- var spacing = this.getSpacing();
- return new dwv.math.Point3D(
- origin.getX() + index.getI() * spacing.getColumnSpacing(),
- origin.getY() + index.getJ() * spacing.getRowSpacing(),
- origin.getZ() + index.getK() * spacing.getSliceSpacing() );
-};
-
-/**
- * Convert world coordinates into an index.
- * @param {Object} THe point to convert.
- */
-dwv.image.Geometry.prototype.worldToIndex = function (point) {
- var origin = this.getOrigin();
- var spacing = this.getSpacing();
- return new dwv.math.Point3D(
- point.getX() / spacing.getColumnSpacing() - origin.getX(),
- point.getY() / spacing.getRowSpacing() - origin.getY(),
- point.getZ() / spacing.getSliceSpacing() - origin.getZ() );
-};
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.image = dwv.image || {};
-
-/**
- * Rescale Slope and Intercept
- * @constructor
- * @param slope
- * @param intercept
- */
-dwv.image.RescaleSlopeAndIntercept = function (slope, intercept)
-{
- /*// Check the rescale slope.
- if(typeof(slope) === 'undefined') {
- slope = 1;
- }
- // Check the rescale intercept.
- if(typeof(intercept) === 'undefined') {
- intercept = 0;
- }*/
-
- /**
- * Get the slope of the RSI.
- * @return {Number} The slope of the RSI.
- */
- this.getSlope = function ()
- {
- return slope;
- };
- /**
- * Get the intercept of the RSI.
- * @return {Number} The intercept of the RSI.
- */
- this.getIntercept = function ()
- {
- return intercept;
- };
- /**
- * Apply the RSI on an input value.
- * @return {Number} The value to rescale.
- */
- this.apply = function (value)
- {
- return value * slope + intercept;
- };
-};
-
-/**
- * Check for RSI equality.
- * @param {Object} rhs The other RSI to compare to.
- * @return {Boolean} True if both RSI are equal.
- */
-dwv.image.RescaleSlopeAndIntercept.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getSlope() === rhs.getSlope() &&
- this.getIntercept() === rhs.getIntercept();
-};
-
-/**
- * Get a string representation of the RSI.
- * @return {String} The RSI as a string.
- */
-dwv.image.RescaleSlopeAndIntercept.prototype.toString = function () {
- return (this.getSlope() + ", " + this.getIntercept());
-};
-
-/**
- * Is this RSI an ID RSI.
- * @return {Boolean} True if the RSI has a slope of 1 and no intercept.
- */
-dwv.image.RescaleSlopeAndIntercept.prototype.isID = function () {
- return (this.getSlope() === 1 && this.getIntercept() === 0);
-};
-
-/**
- * Image class.
- * Usable once created, optional are:
- * - rescale slope and intercept (default 1:0),
- * - photometric interpretation (default MONOCHROME2),
- * - planar configuration (default RGBRGB...).
- * @constructor
- * @param {Object} geometry The geometry of the image.
- * @param {Array} buffer The image data as an array of frame buffers.
- * @param {Number} numberOfFrames The number of frames (optional, can be used
- to anticipate the final number after appends).
- */
-dwv.image.Image = function(geometry, buffer, numberOfFrames)
-{
- // use buffer length in not specified
- if (typeof numberOfFrames === "undefined") {
- numberOfFrames = buffer.length;
- }
-
- /**
- * Get the number of frames.
- * @returns {Number} The number of frames.
- */
- this.getNumberOfFrames = function () {
- return numberOfFrames;
- };
-
- /**
- * Rescale slope and intercept.
- * @private
- * @type Number
- */
- var rsis = [];
- // initialise RSIs
- for ( var s = 0, nslices = geometry.getSize().getNumberOfSlices(); s < nslices; ++s ) {
- rsis.push( new dwv.image.RescaleSlopeAndIntercept( 1, 0 ) );
- }
- /**
- * Flag to know if the RSIs are all identity (1,0).
- * @private
- * @type Boolean
- */
- var isIdentityRSI = true;
- /**
- * Flag to know if the RSIs are all equals.
- * @private
- * @type Boolean
- */
- var isConstantRSI = true;
- /**
- * Photometric interpretation (MONOCHROME, RGB...).
- * @private
- * @type String
- */
- var photometricInterpretation = "MONOCHROME2";
- /**
- * Planar configuration for RGB data (0:RGBRGBRGBRGB... or 1:RRR...GGG...BBB...).
- * @private
- * @type Number
- */
- var planarConfiguration = 0;
- /**
- * Number of components.
- * @private
- * @type Number
- */
- var numberOfComponents = buffer[0].length / (
- geometry.getSize().getTotalSize() );
- /**
- * Meta information.
- * @private
- * @type Object
- */
- var meta = {};
-
- /**
- * Data range.
- * @private
- * @type Object
- */
- var dataRange = null;
- /**
- * Rescaled data range.
- * @private
- * @type Object
- */
- var rescaledDataRange = null;
- /**
- * Histogram.
- * @private
- * @type Array
- */
- var histogram = null;
-
- /**
- * Overlay.
- * @private
- * @type Array
- */
- var overlays = [];
-
- /**
- * Set the first overlay.
- * @param {Array} over The first overlay.
- */
- this.setFirstOverlay = function (over) { overlays[0] = over; };
-
- /**
- * Get the overlays.
- * @return {Array} The overlays array.
- */
- this.getOverlays = function () { return overlays; };
-
- /**
- * Get the geometry of the image.
- * @return {Object} The size of the image.
- */
- this.getGeometry = function() { return geometry; };
-
- /**
- * Get the data buffer of the image.
- * @todo dangerous...
- * @return {Array} The data buffer of the image.
- */
- this.getBuffer = function() { return buffer; };
- /**
- * Get the data buffer of the image.
- * @todo dangerous...
- * @return {Array} The data buffer of the frame.
- */
- this.getFrame = function (frame) { return buffer[frame]; };
-
- /**
- * Get the rescale slope and intercept.
- * @param {Number} k The slice index.
- * @return {Object} The rescale slope and intercept.
- */
- this.getRescaleSlopeAndIntercept = function(k) { return rsis[k]; };
- /**
- * Set the rescale slope and intercept.
- * @param {Array} inRsi The input rescale slope and intercept.
- * @param {Number} k The slice index (optional).
- */
- this.setRescaleSlopeAndIntercept = function(inRsi, k) {
- if ( typeof k === 'undefined' ) {
- k = 0;
- }
- rsis[k] = inRsi;
-
- // update RSI flags
- isIdentityRSI = true;
- isConstantRSI = true;
- for ( var s = 0, lens = rsis.length; s < lens; ++s ) {
- if (!rsis[s].isID()) {
- isIdentityRSI = false;
- }
- if (s > 0 && !rsis[s].equals(rsis[s-1])) {
- isConstantRSI = false;
- }
- }
- };
- /**
- * Are all the RSIs identity (1,0).
- * @return {Boolean} True if they are.
- */
- this.isIdentityRSI = function () { return isIdentityRSI; };
- /**
- * Are all the RSIs equal.
- * @return {Boolean} True if they are.
- */
- this.isConstantRSI = function () { return isConstantRSI; };
- /**
- * Get the photometricInterpretation of the image.
- * @return {String} The photometricInterpretation of the image.
- */
- this.getPhotometricInterpretation = function() { return photometricInterpretation; };
- /**
- * Set the photometricInterpretation of the image.
- * @pqrqm {String} interp The photometricInterpretation of the image.
- */
- this.setPhotometricInterpretation = function(interp) { photometricInterpretation = interp; };
- /**
- * Get the planarConfiguration of the image.
- * @return {Number} The planarConfiguration of the image.
- */
- this.getPlanarConfiguration = function() { return planarConfiguration; };
- /**
- * Set the planarConfiguration of the image.
- * @param {Number} config The planarConfiguration of the image.
- */
- this.setPlanarConfiguration = function(config) { planarConfiguration = config; };
- /**
- * Get the numberOfComponents of the image.
- * @return {Number} The numberOfComponents of the image.
- */
- this.getNumberOfComponents = function() { return numberOfComponents; };
-
- /**
- * Get the meta information of the image.
- * @return {Object} The meta information of the image.
- */
- this.getMeta = function() { return meta; };
- /**
- * Set the meta information of the image.
- * @param {Object} rhs The meta information of the image.
- */
- this.setMeta = function(rhs) { meta = rhs; };
-
- /**
- * Get value at offset. Warning: No size check...
- * @param {Number} offset The desired offset.
- * @param {Number} frame The desired frame.
- * @return {Number} The value at offset.
- */
- this.getValueAtOffset = function (offset, frame) {
- return buffer[frame][offset];
- };
-
- /**
- * Clone the image.
- * @return {Image} A clone of this image.
- */
- this.clone = function()
- {
- // clone the image buffer
- var clonedBuffer = [];
- for (var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f) {
- clonedBuffer[f] = buffer[f].slice(0);
- }
- // create the image copy
- var copy = new dwv.image.Image(this.getGeometry(), clonedBuffer);
- // copy the RSIs
- var nslices = this.getGeometry().getSize().getNumberOfSlices();
- for ( var k = 0; k < nslices; ++k ) {
- copy.setRescaleSlopeAndIntercept(this.getRescaleSlopeAndIntercept(k), k);
- }
- // copy extras
- copy.setPhotometricInterpretation(this.getPhotometricInterpretation());
- copy.setPlanarConfiguration(this.getPlanarConfiguration());
- copy.setMeta(this.getMeta());
- // return
- return copy;
- };
-
- /**
- * Append a slice to the image.
- * @param {Image} The slice to append.
- * @return {Number} The number of the inserted slice.
- */
- this.appendSlice = function (rhs, frame)
- {
- // check input
- if( rhs === null ) {
- throw new Error("Cannot append null slice");
- }
- var rhsSize = rhs.getGeometry().getSize();
- var size = geometry.getSize();
- if( rhsSize.getNumberOfSlices() !== 1 ) {
- throw new Error("Cannot append more than one slice");
- }
- if( size.getNumberOfColumns() !== rhsSize.getNumberOfColumns() ) {
- throw new Error("Cannot append a slice with different number of columns");
- }
- if( size.getNumberOfRows() !== rhsSize.getNumberOfRows() ) {
- throw new Error("Cannot append a slice with different number of rows");
- }
- if( photometricInterpretation !== rhs.getPhotometricInterpretation() ) {
- throw new Error("Cannot append a slice with different photometric interpretation");
- }
- // all meta should be equal
- for( var key in meta ) {
- if( meta[key] !== rhs.getMeta()[key] ) {
- throw new Error("Cannot append a slice with different "+key);
- }
- }
-
- var f = (typeof frame === "undefined") ? 0 : frame;
-
- // calculate slice size
- var mul = 1;
- if( photometricInterpretation === "RGB" || photometricInterpretation === "YBR_FULL_422") {
- mul = 3;
- }
- var sliceSize = mul * size.getSliceSize();
-
- // create the new buffer
- var newBuffer = dwv.dicom.getTypedArray(
- buffer[f].BYTES_PER_ELEMENT * 8,
- meta.IsSigned ? 1 : 0,
- sliceSize * (size.getNumberOfSlices() + 1) );
-
- // append slice at new position
- var newSliceNb = geometry.getSliceIndex( rhs.getGeometry().getOrigin() );
- if( newSliceNb === 0 )
- {
- newBuffer.set(rhs.getFrame(f));
- newBuffer.set(buffer[f], sliceSize);
- }
- else if( newSliceNb === size.getNumberOfSlices() )
- {
- newBuffer.set(buffer[f]);
- newBuffer.set(rhs.getFrame(f), size.getNumberOfSlices() * sliceSize);
- }
- else
- {
- var offset = newSliceNb * sliceSize;
- newBuffer.set(buffer[f].subarray(0, offset - 1));
- newBuffer.set(rhs.getFrame(f), offset);
- newBuffer.set(buffer[f].subarray(offset), offset + sliceSize);
- }
-
- // update geometry
- geometry.appendOrigin( rhs.getGeometry().getOrigin(), newSliceNb );
- // update rsi
- rsis.splice(newSliceNb, 0, rhs.getRescaleSlopeAndIntercept(0));
-
- // copy to class variables
- buffer[f] = newBuffer;
-
- // insert overlay information of the slice to the image
- overlays.splice(newSliceNb, 0, rhs.getOverlays()[0]);
-
- // return the appended slice number
- return newSliceNb;
- };
-
- /**
- * Append a frame buffer to the image.
- * @param {Object} frameBuffer The frame buffer to append.
- */
- this.appendFrameBuffer = function (frameBuffer)
- {
- buffer.push(frameBuffer);
- };
-
- /**
- * Get the data range.
- * @return {Object} The data range.
- */
- this.getDataRange = function() {
- if( !dataRange ) {
- dataRange = this.calculateDataRange();
- }
- return dataRange;
- };
-
- /**
- * Get the rescaled data range.
- * @return {Object} The rescaled data range.
- */
- this.getRescaledDataRange = function() {
- if( !rescaledDataRange ) {
- rescaledDataRange = this.calculateRescaledDataRange();
- }
- return rescaledDataRange;
- };
-
- /**
- * Get the histogram.
- * @return {Array} The histogram.
- */
- this.getHistogram = function() {
- if( !histogram ) {
- var res = this.calculateHistogram();
- dataRange = res.dataRange;
- rescaledDataRange = res.rescaledDataRange;
- histogram = res.histogram;
- }
- return histogram;
- };
-};
-
-/**
- * Get the value of the image at a specific coordinate.
- * @param {Number} i The X index.
- * @param {Number} j The Y index.
- * @param {Number} k The Z index.
- * @param {Number} f The frame number.
- * @return {Number} The value at the desired position.
- * Warning: No size check...
- */
-dwv.image.Image.prototype.getValue = function( i, j, k, f )
-{
- var frame = (f || 0);
- var index = new dwv.math.Index3D(i,j,k);
- return this.getValueAtOffset( this.getGeometry().indexToOffset(index), frame );
-};
-
-/**
- * Get the rescaled value of the image at a specific coordinate.
- * @param {Number} i The X index.
- * @param {Number} j The Y index.
- * @param {Number} k The Z index.
- * @param {Number} f The frame number.
- * @return {Number} The rescaled value at the desired position.
- * Warning: No size check...
- */
-dwv.image.Image.prototype.getRescaledValue = function( i, j, k, f )
-{
- var frame = (f || 0);
- var val = this.getValue(i,j,k,frame);
- if (!this.isIdentityRSI()) {
- val = this.getRescaleSlopeAndIntercept(k).apply(val);
- }
- return val;
-};
-
-/**
- * Calculate the data range of the image.
- * WARNING: for speed reasons, only calculated on the first frame...
- * @return {Object} The range {min, max}.
- */
-dwv.image.Image.prototype.calculateDataRange = function ()
-{
- var size = this.getGeometry().getSize().getTotalSize();
- var nFrames = 1; //this.getNumberOfFrames();
- var min = this.getValueAtOffset(0,0);
- var max = min;
- var value = 0;
- for ( var f = 0; f < nFrames; ++f ) {
- for ( var i = 0; i < size; ++i ) {
- value = this.getValueAtOffset(i,f);
- if( value > max ) { max = value; }
- if( value < min ) { min = value; }
- }
- }
- // return
- return { "min": min, "max": max };
-};
-
-/**
- * Calculate the rescaled data range of the image.
- * WARNING: for speed reasons, only calculated on the first frame...
- * @return {Object} The range {min, max}.
- */
-dwv.image.Image.prototype.calculateRescaledDataRange = function ()
-{
- if (this.isIdentityRSI()) {
- return this.getDataRange();
- }
- else if (this.isConstantRSI()) {
- var range = this.getDataRange();
- var resmin = this.getRescaleSlopeAndIntercept(0).apply(range.min);
- var resmax = this.getRescaleSlopeAndIntercept(0).apply(range.max);
- return {
- "min": ((resmin < resmax) ? resmin : resmax),
- "max": ((resmin > resmax) ? resmin : resmax)
- };
- }
- else {
- var size = this.getGeometry().getSize();
- var nFrames = 1; //this.getNumberOfFrames();
- var rmin = this.getRescaledValue(0,0,0);
- var rmax = rmin;
- var rvalue = 0;
- for ( var f = 0, nframes = nFrames; f < nframes; ++f ) {
- for ( var k = 0, nslices = size.getNumberOfSlices(); k < nslices; ++k ) {
- for ( var j = 0, nrows = size.getNumberOfRows(); j < nrows; ++j ) {
- for ( var i = 0, ncols = size.getNumberOfColumns(); i < ncols; ++i ) {
- rvalue = this.getRescaledValue(i,j,k,f);
- if( rvalue > rmax ) { rmax = rvalue; }
- if( rvalue < rmin ) { rmin = rvalue; }
- }
- }
- }
- }
- // return
- return { "min": rmin, "max": rmax };
- }
-};
-
-/**
- * Calculate the histogram of the image.
- * @return {Object} The histogram, data range and rescaled data range.
- */
-dwv.image.Image.prototype.calculateHistogram = function ()
-{
- var size = this.getGeometry().getSize();
- var histo = [];
- var min = this.getValue(0,0,0);
- var max = min;
- var value = 0;
- var rmin = this.getRescaledValue(0,0,0);
- var rmax = rmin;
- var rvalue = 0;
- for ( var f = 0, nframes = this.getNumberOfFrames(); f < nframes; ++f ) {
- for ( var k = 0, nslices = size.getNumberOfSlices(); k < nslices; ++k ) {
- for ( var j = 0, nrows = size.getNumberOfRows(); j < nrows; ++j ) {
- for ( var i = 0, ncols = size.getNumberOfColumns(); i < ncols; ++i ) {
- value = this.getValue(i,j,k,f);
- if( value > max ) { max = value; }
- if( value < min ) { min = value; }
- rvalue = this.getRescaleSlopeAndIntercept(k).apply(value);
- if( rvalue > rmax ) { rmax = rvalue; }
- if( rvalue < rmin ) { rmin = rvalue; }
- histo[rvalue] = ( histo[rvalue] || 0 ) + 1;
- }
- }
- }
- }
- // set data range
- var dataRange = { "min": min, "max": max };
- var rescaledDataRange = { "min": rmin, "max": rmax };
- // generate data for plotting
- var histogram = [];
- for ( var b = rmin; b <= rmax; ++b ) {
- histogram.push([b, ( histo[b] || 0 ) ]);
- }
- // return
- return { 'dataRange': dataRange, 'rescaledDataRange': rescaledDataRange,
- 'histogram': histogram };
-};
-
-/**
- * Convolute the image with a given 2D kernel.
- * @param {Array} weights The weights of the 2D kernel as a 3x3 matrix.
- * @return {Image} The convoluted image.
- * Note: Uses the raw buffer values.
- */
-dwv.image.Image.prototype.convolute2D = function(weights)
-{
- if(weights.length !== 9) {
- throw new Error("The convolution matrix does not have a length of 9; it has "+weights.length);
- }
-
- var newImage = this.clone();
- var newBuffer = newImage.getBuffer();
-
- var imgSize = this.getGeometry().getSize();
- var ncols = imgSize.getNumberOfColumns();
- var nrows = imgSize.getNumberOfRows();
- var nslices = imgSize.getNumberOfSlices();
- var nframes = this.getNumberOfFrames();
- var ncomp = this.getNumberOfComponents();
-
- // adapt to number of component and planar configuration
- var factor = 1;
- var componentOffset = 1;
- var frameOffset = imgSize.getTotalSize();
- if( ncomp === 3 )
- {
- frameOffset *= 3;
- if( this.getPlanarConfiguration() === 0 )
- {
- factor = 3;
- }
- else
- {
- componentOffset = imgSize.getTotalSize();
- }
- }
-
- // allow special indent for matrices
- /*jshint indent:false */
-
- // default weight offset matrix
- var wOff = [];
- wOff[0] = (-ncols-1) * factor; wOff[1] = (-ncols) * factor; wOff[2] = (-ncols+1) * factor;
- wOff[3] = -factor; wOff[4] = 0; wOff[5] = 1 * factor;
- wOff[6] = (ncols-1) * factor; wOff[7] = (ncols) * factor; wOff[8] = (ncols+1) * factor;
-
- // border weight offset matrices
- // borders are extended (see http://en.wikipedia.org/wiki/Kernel_%28image_processing%29)
-
- // i=0, j=0
- var wOff00 = [];
- wOff00[0] = wOff[4]; wOff00[1] = wOff[4]; wOff00[2] = wOff[5];
- wOff00[3] = wOff[4]; wOff00[4] = wOff[4]; wOff00[5] = wOff[5];
- wOff00[6] = wOff[7]; wOff00[7] = wOff[7]; wOff00[8] = wOff[8];
- // i=0, j=*
- var wOff0x = [];
- wOff0x[0] = wOff[1]; wOff0x[1] = wOff[1]; wOff0x[2] = wOff[2];
- wOff0x[3] = wOff[4]; wOff0x[4] = wOff[4]; wOff0x[5] = wOff[5];
- wOff0x[6] = wOff[7]; wOff0x[7] = wOff[7]; wOff0x[8] = wOff[8];
- // i=0, j=nrows
- var wOff0n = [];
- wOff0n[0] = wOff[1]; wOff0n[1] = wOff[1]; wOff0n[2] = wOff[2];
- wOff0n[3] = wOff[4]; wOff0n[4] = wOff[4]; wOff0n[5] = wOff[5];
- wOff0n[6] = wOff[4]; wOff0n[7] = wOff[4]; wOff0n[8] = wOff[5];
-
- // i=*, j=0
- var wOffx0 = [];
- wOffx0[0] = wOff[3]; wOffx0[1] = wOff[4]; wOffx0[2] = wOff[5];
- wOffx0[3] = wOff[3]; wOffx0[4] = wOff[4]; wOffx0[5] = wOff[5];
- wOffx0[6] = wOff[6]; wOffx0[7] = wOff[7]; wOffx0[8] = wOff[8];
- // i=*, j=* -> wOff
- // i=*, j=nrows
- var wOffxn = [];
- wOffxn[0] = wOff[0]; wOffxn[1] = wOff[1]; wOffxn[2] = wOff[2];
- wOffxn[3] = wOff[3]; wOffxn[4] = wOff[4]; wOffxn[5] = wOff[5];
- wOffxn[6] = wOff[3]; wOffxn[7] = wOff[4]; wOffxn[8] = wOff[5];
-
- // i=ncols, j=0
- var wOffn0 = [];
- wOffn0[0] = wOff[3]; wOffn0[1] = wOff[4]; wOffn0[2] = wOff[4];
- wOffn0[3] = wOff[3]; wOffn0[4] = wOff[4]; wOffn0[5] = wOff[4];
- wOffn0[6] = wOff[6]; wOffn0[7] = wOff[7]; wOffn0[8] = wOff[7];
- // i=ncols, j=*
- var wOffnx = [];
- wOffnx[0] = wOff[0]; wOffnx[1] = wOff[1]; wOffnx[2] = wOff[1];
- wOffnx[3] = wOff[3]; wOffnx[4] = wOff[4]; wOffnx[5] = wOff[4];
- wOffnx[6] = wOff[6]; wOffnx[7] = wOff[7]; wOffnx[8] = wOff[7];
- // i=ncols, j=nrows
- var wOffnn = [];
- wOffnn[0] = wOff[0]; wOffnn[1] = wOff[1]; wOffnn[2] = wOff[1];
- wOffnn[3] = wOff[3]; wOffnn[4] = wOff[4]; wOffnn[5] = wOff[4];
- wOffnn[6] = wOff[3]; wOffnn[7] = wOff[4]; wOffnn[8] = wOff[4];
-
- // restore indent for rest of method
- /*jshint indent:4 */
-
- // loop vars
- var pixelOffset = 0;
- var newValue = 0;
- var wOffFinal = [];
- // go through the destination image pixels
- for (var f=0; f is signed
- var pixelRepresentation = dicomElements.getFromKey("x00280103");
- meta.IsSigned = false;
- if ( pixelRepresentation ) {
- meta.IsSigned = (pixelRepresentation === 1);
- }
- image.setMeta(meta);
-
- // overlay
- image.setFirstOverlay( dwv.gui.info.createOverlays(dicomElements) );
-
- return image;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-/** @namespace */
-dwv.image.lut = dwv.image.lut || {};
-
-/**
- * Rescale LUT class.
- * @constructor
- * @param {Object} rsi The rescale slope and intercept.
- * @param {Number} bitsStored The number of bits used to store the data.
- */
-dwv.image.lut.Rescale = function (rsi, bitsStored)
-{
- /**
- * The internal array.
- * @private
- * @type Array
- */
- var lut = null;
-
- /**
- * Flag to know if the lut is ready or not.
- * @private
- * @type Boolean
- */
- var isReady = false;
-
- /**
- * The size of the LUT array.
- * @private
- * @type Number
- */
- var length = Math.pow(2, bitsStored);
-
- /**
- * Get the Rescale Slope and Intercept (RSI).
- * @return {Object} The rescale slope and intercept.
- */
- this.getRSI = function () { return rsi; };
-
- /**
- * Is the lut ready to use or not? If not, the user must
- * call 'initialise'.
- * @return {Boolean} True if the lut is ready to use.
- */
- this.isReady = function () { return isReady; };
-
- /**
- * Initialise the LUT.
- */
- this.initialise = function ()
- {
- // check if already initialised
- if (isReady) {
- return;
- }
- // create lut and fill it
- lut = new Float32Array(length);
- for ( var i = 0; i < length; ++i ) {
- lut[i] = rsi.apply(i);
- }
- // update ready flag
- isReady = true;
- };
-
- /**
- * Get the length of the LUT array.
- * @return {Number} The length of the LUT array.
- */
- this.getLength = function () { return length; };
-
- /**
- * Get the value of the LUT at the given offset.
- * @return {Number} The value of the LUT at the given offset.
- */
- this.getValue = function (offset)
- {
- return lut[ offset ];
- };
-};
-
-/**
- * Window LUT class.
- * @constructor
- * @param {Number} rescaleLut The associated rescale LUT.
- * @param {Boolean} isSigned Flag to know if the data is signed or not.
- */
-dwv.image.lut.Window = function (rescaleLut, isSigned)
-{
- /**
- * The internal array: Uint8ClampedArray clamps between 0 and 255.
- * @private
- * @type Array
- */
- var lut = null;
-
- /**
- * The window level.
- * @private
- * @type {Object}
- */
- var windowLevel = null;
-
- /**
- * Flag to know if the lut is ready or not.
- * @private
- * @type Boolean
- */
- var isReady = false;
-
- /**
- * Shift for signed data.
- * @private
- * @type Number
- */
- var signedShift = 0;
-
- /**
- * Get the window / level.
- * @return {Object} The window / level.
- */
- this.getWindowLevel = function () { return windowLevel; };
- /**
- * Get the signed flag.
- * @return {Boolean} The signed flag.
- */
- this.isSigned = function () { return isSigned; };
- /**
- * Get the rescale lut.
- * @return {Object} The rescale lut.
- */
- this.getRescaleLut = function () { return rescaleLut; };
-
- /**
- * Is the lut ready to use or not? If not, the user must
- * call 'update'.
- * @return {Boolean} True if the lut is ready to use.
- */
- this.isReady = function () { return isReady; };
-
- /**
- * Set the window center and width.
- * @param {Object} wl The window level.
- */
- this.setWindowLevel = function (wl)
- {
- // store the window values
- windowLevel = wl;
- // possible signed shift
- signedShift = 0;
- windowLevel.setSignedOffset(0);
- if ( isSigned ) {
- var size = rescaleLut.getLength();
- signedShift = size / 2;
- windowLevel.setSignedOffset(rescaleLut.getRSI().getSlope() * signedShift);
- }
- // update ready flag
- isReady = false;
- };
-
- /**
- * Update the lut if needed..
- */
- this.update = function ()
- {
- // check if we need to update
- if ( isReady ) {
- return;
- }
-
- // check rescale lut
- if (!rescaleLut.isReady()) {
- rescaleLut.initialise();
- }
- // create window lut
- var size = rescaleLut.getLength();
- if (!lut) {
- // use clamped array (polyfilled in browser.js)
- lut = new Uint8ClampedArray(size);
- }
- // by default WindowLevel returns a value in the [0,255] range
- // this is ok with regular Arrays and ClampedArray.
- for ( var i = 0; i < size; ++i )
- {
- lut[i] = windowLevel.apply( rescaleLut.getValue(i) );
- }
-
- // update ready flag
- isReady = true;
- };
-
- /**
- * Get the length of the LUT array.
- * @return {Number} The length of the LUT array.
- */
- this.getLength = function () { return lut.length; };
-
- /**
- * Get the value of the LUT at the given offset.
- * @return {Number} The value of the LUT at the given offset.
- */
- this.getValue = function (offset)
- {
- return lut[ offset + signedShift ];
- };
-};
-
-/**
-* Lookup tables for image colour display.
-*/
-
-dwv.image.lut.range_max = 256;
-
-dwv.image.lut.buildLut = function(func)
-{
- var lut = [];
- for( var i=0; i= third && i < 2*third ) {
- return dwv.image.lut.range_max-1;
- }
- return 0;
-};
-
-dwv.image.lut.maxThirdThird = function(i)
-{
- if( i >= 2*dwv.image.lut.range_max/3 ) {
- return dwv.image.lut.range_max-1;
- }
- return 0;
-};
-
-dwv.image.lut.toMaxFirstThird = function(i)
-{
- var val = i * 3;
- if( val > dwv.image.lut.range_max-1 ) {
- return dwv.image.lut.range_max-1;
- }
- return val;
-};
-
-dwv.image.lut.toMaxSecondThird = function(i)
-{
- var third = dwv.image.lut.range_max/3;
- var val = 0;
- if( i >= third ) {
- val = (i-third) * 3;
- if( val > dwv.image.lut.range_max-1 ) {
- return dwv.image.lut.range_max-1;
- }
- }
- return val;
-};
-
-dwv.image.lut.toMaxThirdThird = function(i)
-{
- var third = dwv.image.lut.range_max/3;
- var val = 0;
- if( i >= 2*third ) {
- val = (i-2*third) * 3;
- if( val > dwv.image.lut.range_max-1 ) {
- return dwv.image.lut.range_max-1;
- }
- }
- return val;
-};
-
-dwv.image.lut.zero = function(/*i*/)
-{
- return 0;
-};
-
-dwv.image.lut.id = function(i)
-{
- return i;
-};
-
-dwv.image.lut.invId = function(i)
-{
- return (dwv.image.lut.range_max-1)-i;
-};
-
-// plain
-dwv.image.lut.plain = {
- "red": dwv.image.lut.buildLut(dwv.image.lut.id),
- "green": dwv.image.lut.buildLut(dwv.image.lut.id),
- "blue": dwv.image.lut.buildLut(dwv.image.lut.id)
-};
-
-// inverse plain
-dwv.image.lut.invPlain = {
- "red": dwv.image.lut.buildLut(dwv.image.lut.invId),
- "green": dwv.image.lut.buildLut(dwv.image.lut.invId),
- "blue": dwv.image.lut.buildLut(dwv.image.lut.invId)
-};
-
-//rainbow
-dwv.image.lut.rainbow = {
- "blue": [0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252, 255, 247, 239, 231, 223, 215, 207, 199, 191, 183, 175, 167, 159, 151, 143, 135, 127, 119, 111, 103, 95, 87, 79, 71, 63, 55, 47, 39, 31, 23, 15, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
- "green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 251, 249, 247, 245, 243, 241, 239, 237, 235, 233, 231, 229, 227, 225, 223, 221, 219, 217, 215, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 192, 189, 186, 183, 180, 177, 174, 171, 168, 165, 162, 159, 156, 153, 150, 147, 144, 141, 138, 135, 132, 129, 126, 123, 120, 117, 114, 111, 108, 105, 102, 99, 96, 93, 90, 87, 84, 81, 78, 75, 72, 69, 66, 63, 60, 57, 54, 51, 48, 45, 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9, 6, 3],
- "red": [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
-};
-
-// hot
-dwv.image.lut.hot = {
- "red": dwv.image.lut.buildLut(dwv.image.lut.toMaxFirstThird),
- "green": dwv.image.lut.buildLut(dwv.image.lut.toMaxSecondThird),
- "blue": dwv.image.lut.buildLut(dwv.image.lut.toMaxThirdThird)
-};
-
-// hot iron
-dwv.image.lut.hot_iron = {
- "red": [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
- "green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 255],
- "blue": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252, 255]
-};
-
-// pet
-dwv.image.lut.pet = {
- "red": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
- "green": [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 128, 126, 124, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 255],
- "blue": [0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 252, 248, 244, 240, 236, 232, 228, 224, 220, 216, 212, 208, 204, 200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 156, 152, 148, 144, 140, 136, 132, 128, 124, 120, 116, 112, 108, 104, 100, 96, 92, 88, 84, 80, 76, 72, 68, 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 214, 218, 222, 226, 230, 234, 238, 242, 246, 250, 255]
-};
-
-// hot metal blue
-dwv.image.lut.hot_metal_blue = {
- "red": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 6, 9, 12, 15, 18, 21, 24, 26, 29, 32, 35, 38, 41, 44, 47, 50, 52, 55, 57, 59, 62, 64, 66, 69, 71, 74, 76, 78, 81, 83, 85, 88, 90, 93, 96, 99, 102, 105, 108, 111, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 166, 169, 172, 175, 178, 181, 184, 187, 190, 194, 198, 201, 205, 209, 213, 217, 221, 224, 228, 232, 236, 240, 244, 247, 251, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
- "green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 6, 8, 9, 11, 13, 15, 17, 19, 21, 23, 24, 26, 28, 30, 32, 34, 36, 38, 40, 41, 43, 45, 47, 49, 51, 53, 55, 56, 58, 60, 62, 64, 66, 68, 70, 72, 73, 75, 77, 79, 81, 83, 85, 87, 88, 90, 92, 94, 96, 98, 100, 102, 104, 105, 107, 109, 111, 113, 115, 117, 119, 120, 122, 124, 126, 128, 130, 132, 134, 136, 137, 139, 141, 143, 145, 147, 149, 151, 152, 154, 156, 158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 201, 203, 205, 207, 209, 211, 213, 215, 216, 218, 220, 222, 224, 226, 228, 229, 231, 233, 235, 237, 239, 240, 242, 244, 246, 248, 250, 251, 253, 255],
- "blue": [0, 2, 4, 6, 8, 10, 12, 14, 16, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 197, 194, 191, 188, 185, 182, 179, 176, 174, 171, 168, 165, 162, 159, 156, 153, 150, 144, 138, 132, 126, 121, 115, 109, 103, 97, 91, 85, 79, 74, 68, 62, 56, 50, 47, 44, 41, 38, 35, 32, 29, 26, 24, 21, 18, 15, 12, 9, 6, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 6, 9, 12, 15, 18, 21, 24, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 126, 129, 132, 135, 138, 141, 144, 147, 150, 153, 156, 159, 162, 165, 168, 171, 174, 176, 179, 182, 185, 188, 191, 194, 197, 200, 203, 206, 210, 213, 216, 219, 223, 226, 229, 232, 236, 239, 242, 245, 249, 252, 255]
-};
-
-// pet 20 step
-dwv.image.lut.pet_20step = {
- "red": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
- "green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
- "blue": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
-};
-
-// test
-dwv.image.lut.test = {
- "red": dwv.image.lut.buildLut(dwv.image.lut.id),
- "green": dwv.image.lut.buildLut(dwv.image.lut.zero),
- "blue": dwv.image.lut.buildLut(dwv.image.lut.zero)
-};
-
-//red
-/*dwv.image.lut.red = {
- "red": dwv.image.lut.buildLut(dwv.image.lut.max),
- "green": dwv.image.lut.buildLut(dwv.image.lut.id),
- "blue": dwv.image.lut.buildLut(dwv.image.lut.id)
-};*/
-
-// namespaces
-var dwv = dwv || {};
-dwv.image = dwv.image || {};
-
-/**
- * WindowLevel class.
- * References:
- * - DICOM [Window Center and Window Width]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.11.html#sect_C.11.2.1.2}
- * Pseudo-code:
- * if (x <= c - 0.5 - (w-1)/2), then y = ymin
- * else if (x > c - 0.5 + (w-1)/2), then y = ymax,
- * else y = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax - ymin) + ymin
- */
-dwv.image.WindowLevel = function (center, width)
-{
- // avoid zero width
- if ( width === 0 ) {
- //throw new Error("A window level with a width of zero is not possible.");
- console.log("A window level with a width of zero is not possible.");
- console.log("try width = 200");
- width = 200;
- }
-
- /**
- * Signed data offset.
- * @private
- * @type Number
- */
- var signedOffset = 0;
- /**
- * Output value minimum.
- * @private
- * @type Number
- */
- var ymin = 0;
- /**
- * Output value maximum.
- * @private
- * @type Number
- */
- var ymax = 255;
-
- /**
- * Input value minimum (calculated).
- * @private
- * @type Number
- */
- var xmin = null;
- /**
- * Input value maximum (calculated).
- * @private
- * @type Number
- */
- var xmax = null;
- /**
- * Window level equation slope (calculated).
- * @private
- * @type Number
- */
- var slope = null;
- /**
- * Window level equation intercept (calculated).
- * @private
- * @type Number
- */
- var inter = null;
-
- /**
- * Initialise members.
- */
- function init() {
- var c = center + signedOffset;
- // from the standard
- xmin = c - 0.5 - ( (width-1) / 2 );
- xmax = c - 0.5 + ( (width-1) / 2 );
- // develop the equation:
- // y = ( ( x - (c - 0.5) ) / (w-1) + 0.5 ) * (ymax - ymin) + ymin
- // y = ( x / (w-1) ) * (ymax - ymin) + ( -(c - 0.5) / (w-1) + 0.5 ) * (ymax - ymin) + ymin
- slope = (ymax - ymin) / (width-1);
- inter = ( -(c - 0.5) / (width-1) + 0.5 ) * (ymax - ymin) + ymin;
- }
-
- // call init
- init();
-
- /**
- * Get the window center.
- * @return {Number} The window center.
- */
- this.getCenter = function () { return center; };
- /**
- * Get the window width.
- * @return {Number} The window width.
- */
- this.getWidth = function () { return width; };
-
- /**
- * Set the output value range.
- * @param {Number} min The output value minimum.
- * @param {Number} max The output value maximum.
- */
- this.setRange = function (min, max) {
- ymin = parseInt( min, 10 );
- ymax = parseInt( max, 10 ) ;
- // re-initialise
- init();
- };
- /**
- * Set the signed offset.
- * @param {Number} The signed data offset, typically: slope * ( size / 2).
- */
- this.setSignedOffset = function (offset) {
- signedOffset = offset;
- // re-initialise
- init();
- };
-
- /**
- * Apply the window level on an input value.
- * @param {Number} The value to rescale as an integer.
- * @return {Number} The leveled value, in the
- * [ymin, ymax] range (default [0,255]).
- */
- this.apply = function (value)
- {
- if ( value <= xmin ) {
- return ymin;
- } else if ( value > xmax ) {
- return ymax;
- } else {
- return parseInt( ((value * slope) + inter), 10);
- }
- };
-
-};
-
-/**
- * Check for window level equality.
- * @param {Object} rhs The other window level to compare to.
- * @return {Boolean} True if both window level are equal.
- */
-dwv.image.WindowLevel.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getCenter() === rhs.getCenter() &&
- this.getWidth() === rhs.getWidth();
-};
-
-/**
- * Get a string representation of the window level.
- * @return {String} The window level as a string.
- */
-dwv.image.WindowLevel.prototype.toString = function () {
- return (this.getCenter() + ", " + this.getWidth());
-};
-
-/**
- * View class.
- * @constructor
- * @param {Image} image The associated image.
- * Need to set the window lookup table once created
- * (either directly or with helper methods).
- */
-dwv.image.View = function (image)
-{
- /**
- * Window lookup tables, indexed per Rescale Slope and Intercept (RSI).
- * @private
- * @type Window
- */
- var windowLuts = {};
-
- /**
- * Window presets.
- * Minmax will be filled at first use (see view.setWindowLevelPreset).
- * @private
- * @type Object
- */
- var windowPresets = { "minmax": {"name": "minmax"} };
-
- /**
- * Current window preset name.
- * @private
- * @type String
- */
- var currentPresetName = null;
-
- /**
- * colour map.
- * @private
- * @type Object
- */
- var colourMap = dwv.image.lut.plain;
- /**
- * Current position.
- * @private
- * @type Object
- */
- var currentPosition = {"i":0,"j":0,"k":0};
- /**
- * Current frame. Zero based.
- * @private
- * @type Number
- */
- var currentFrame = null;
-
- /**
- * Get the associated image.
- * @return {Image} The associated image.
- */
- this.getImage = function() { return image; };
- /**
- * Set the associated image.
- * @param {Image} inImage The associated image.
- */
- this.setImage = function(inImage) { image = inImage; };
-
- /**
- * Get the window LUT of the image.
- * Warning: can be undefined in no window/level was set.
- * @param {Object} rsi Optional image rsi, will take the one of the current slice otherwise.
- * @return {Window} The window LUT of the image.
- */
- this.getCurrentWindowLut = function (rsi) {
- var sliceNumber = this.getCurrentPosition().k;
- // use current rsi if not provided
- if ( typeof rsi === "undefined" ) {
- rsi = image.getRescaleSlopeAndIntercept(sliceNumber);
- }
- // get the lut
- var wlut = windowLuts[ rsi.toString() ];
-
- // special case for 'perslice' presets
- if (currentPresetName &&
- typeof windowPresets[currentPresetName] !== "undefined" &&
- typeof windowPresets[currentPresetName].perslice !== "undefined" &&
- windowPresets[currentPresetName].perslice === true ) {
- // get the preset for this slice
- var wl = windowPresets[currentPresetName].wl[sliceNumber];
- // apply it if different from previous
- if (!wlut.getWindowLevel().equals(wl)) {
- // previous values
- var previousWidth = wlut.getWindowLevel().getWidth();
- var previousCenter = wlut.getWindowLevel().getCenter();
- // set slice window level
- wlut.setWindowLevel(wl);
- // fire event
- if ( previousWidth !== wl.getWidth() ) {
- this.fireEvent({"type": "wl-width-change",
- "wc": wl.getCenter(), "ww": wl.getWidth(),
- "skipGenerate": true});
- }
- if ( previousCenter !== wl.getCenter() ) {
- this.fireEvent({"type": "wl-center-change",
- "wc": wl.getCenter(), "ww": wl.getWidth(),
- "skipGenerate": true});
- }
- }
- }
-
- // update in case of wl change
- // TODO: should not be run in a getter...
- wlut.update();
-
- // return
- return wlut;
- };
- /**
- * Add the window LUT to the list.
- * @param {Window} wlut The window LUT of the image.
- */
- this.addWindowLut = function (wlut)
- {
- var rsi = wlut.getRescaleLut().getRSI();
- windowLuts[rsi.toString()] = wlut;
- };
-
- /**
- * Get the window presets.
- * @return {Object} The window presets.
- */
- this.getWindowPresets = function () {
- return windowPresets;
- };
-
- /**
- * Get the window presets names.
- * @return {Object} The list of window presets names.
- */
- this.getWindowPresetsNames = function () {
- return Object.keys(windowPresets);
- };
-
- /**
- * Set the window presets.
- * @param {Object} presets The window presets.
- */
- this.setWindowPresets = function (presets) {
- windowPresets = presets;
- };
-
- /**
- * Set the default colour map.
- * @param {Object} map The colour map.
- */
- this.setDefaultColourMap = function (map) {
- colourMap = map;
- };
-
- /**
- * Add window presets to the existing ones.
- * @param {Object} presets The window presets.
- * @param {Number} k The slice the preset belong to.
- */
- this.addWindowPresets = function (presets, k) {
- var keys = Object.keys(presets);
- var key = null;
- for (var i = 0; i < keys.length; ++i) {
- key = keys[i];
- if (typeof windowPresets[key] !== "undefined") {
- if (typeof windowPresets[key].perslice !== "undefined" &&
- windowPresets[key].perslice === true) {
- // use first new preset wl...
- windowPresets[key].wl.splice(k, 0, presets[key].wl[0]);
- } else {
- windowPresets[key] = presets[key];
- }
- } else {
- // add new
- windowPresets[key] = presets[key];
- }
- }
- };
-
- /**
- * Get the colour map of the image.
- * @return {Object} The colour map of the image.
- */
- this.getColourMap = function() { return colourMap; };
- /**
- * Set the colour map of the image.
- * @param {Object} map The colour map of the image.
- */
- this.setColourMap = function(map) {
- colourMap = map;
- this.fireEvent({"type": "colour-change",
- "wc": this.getCurrentWindowLut().getWindowLevel().getCenter(),
- "ww": this.getCurrentWindowLut().getWindowLevel().getWidth() });
- };
-
- /**
- * Get the current position.
- * @return {Object} The current position.
- */
- this.getCurrentPosition = function() {
- // return a clone to avoid reference problems
- return {"i": currentPosition.i, "j": currentPosition.j, "k": currentPosition.k};
- };
- /**
- * Set the current position.
- * @param {Object} pos The current position.
- * @param {Boolean} silent If true, does not fire a slice-change event.
- * @return {Boolean} False if not in bounds
- */
- this.setCurrentPosition = function(pos, silent) {
- // default silent flag to false
- if ( typeof silent === "undefined" ) {
- silent = false;
- }
- // check if possible
- if( !image.getGeometry().getSize().isInBounds(pos.i,pos.j,pos.k) ) {
- return false;
- }
- var oldPosition = currentPosition;
- currentPosition = pos;
-
- // fire a 'position-change' event
- if( image.getPhotometricInterpretation().match(/MONOCHROME/) !== null )
- {
- this.fireEvent({"type": "position-change",
- "i": pos.i, "j": pos.j, "k": pos.k,
- "value": image.getRescaledValue(pos.i,pos.j,pos.k, this.getCurrentFrame())});
- }
- else
- {
- this.fireEvent({"type": "position-change",
- "i": pos.i, "j": pos.j, "k": pos.k});
- }
-
- // fire a slice change event (used to trigger redraw)
- if ( !silent ) {
- if( oldPosition.k !== currentPosition.k ) {
- this.fireEvent({"type": "slice-change"});
- }
- }
-
- // all good
- return true;
- };
-
- /**
- * Get the current frame number.
- * @return {Number} The current frame number.
- */
- this.getCurrentFrame = function() {
- return currentFrame;
- };
-
- /**
- * Set the current frame number.
- * @param {Number} The current frame number.
- * @return {Boolean} False if not in bounds
- */
- this.setCurrentFrame = function (frame) {
- // check if possible
- if( frame < 0 || frame >= image.getNumberOfFrames() ) {
- return false;
- }
- // assign
- var oldFrame = currentFrame;
- currentFrame = frame;
- // fire event
- if( oldFrame !== currentFrame && image.getNumberOfFrames() !== 1 ) {
- this.fireEvent({"type": "frame-change", "frame": currentFrame});
- // silent set current position to update info text
- this.setCurrentPosition(this.getCurrentPosition(),true);
- }
- // all good
- return true;
- };
-
- /**
- * Append another view to this one.
- * @param {Object} rhs The view to append.
- */
- this.append = function( rhs )
- {
- // append images
- var newSliceNumber = this.getImage().appendSlice( rhs.getImage() );
- // update position if a slice was appended before
- if ( newSliceNumber <= this.getCurrentPosition().k ) {
- this.setCurrentPosition(
- {"i": this.getCurrentPosition().i,
- "j": this.getCurrentPosition().j,
- "k": this.getCurrentPosition().k + 1}, true );
- }
- // add window presets
- this.addWindowPresets( rhs.getWindowPresets(), newSliceNumber );
- };
-
- /**
- * Append a frame buffer to the included image.
- * @param {Object} frameBuffer The frame buffer to append.
- */
- this.appendFrameBuffer = function (frameBuffer)
- {
- this.getImage().appendFrameBuffer(frameBuffer);
- };
-
- /**
- * Set the view window/level.
- * @param {Number} center The window center.
- * @param {Number} width The window width.
- * @param {String} name Associated preset name, defaults to 'manual'.
- * Warning: uses the latest set rescale LUT or the default linear one.
- */
- this.setWindowLevel = function ( center, width, name )
- {
- // window width shall be >= 1 (see https://www.dabsoft.ch/dicom/3/C.11.2.1.2/)
- if ( width >= 1 ) {
-
- // get current window/level (before updating name)
- var sliceNumber = this.getCurrentPosition().k;
- var currentWl = null;
- var rsi = image.getRescaleSlopeAndIntercept(sliceNumber);
- if ( rsi && typeof rsi !== "undefined" ) {
- var currentLut = windowLuts[ rsi.toString() ];
- if ( currentLut && typeof currentLut !== "undefined") {
- currentWl = currentLut.getWindowLevel();
- }
- }
-
- if ( typeof name === "undefined" ) {
- name = "manual";
- }
- // update current preset name
- currentPresetName = name;
-
- var wl = new dwv.image.WindowLevel(center, width);
- var keys = Object.keys(windowLuts);
-
- // create the first lut if none exists
- if (keys.length === 0) {
- // create the rescale lookup table
- var rescaleLut = new dwv.image.lut.Rescale(
- image.getRescaleSlopeAndIntercept(0), image.getMeta().BitsStored );
- // create the window lookup table
- var windowLut = new dwv.image.lut.Window(rescaleLut, image.getMeta().IsSigned);
- this.addWindowLut(windowLut);
- }
-
- // set window level on luts
- for ( var key in windowLuts ) {
- windowLuts[key].setWindowLevel(wl);
- }
-
- // fire window level change event
- if (currentWl && typeof currentWl !== "undefined") {
- if (currentWl.getWidth() !== width) {
- this.fireEvent({"type": "wl-width-change", "wc": center, "ww": width });
- }
- if (currentWl.getCenter() !== center) {
- this.fireEvent({"type": "wl-center-change", "wc": center, "ww": width });
- }
- } else {
- this.fireEvent({"type": "wl-width-change", "wc": center, "ww": width });
- this.fireEvent({"type": "wl-center-change", "wc": center, "ww": width });
- }
- }
- };
-
- /**
- * Set the window level to the preset with the input name.
- * @param {String} name The name of the preset to activate.
- */
- this.setWindowLevelPreset = function (name) {
- var preset = this.getWindowPresets()[name];
- if ( typeof preset === "undefined" ) {
- throw new Error("Unknown window level preset: '" + name + "'");
- }
- // special min/max
- if (name === "minmax" && typeof preset.wl === "undefined") {
- preset.wl = this.getWindowLevelMinMax();
- }
- // special 'perslice' case
- if (typeof preset.perslice !== "undefined" &&
- preset.perslice === true) {
- preset = { "wl": preset.wl[this.getCurrentPosition().k] };
- }
- // set w/l
- this.setWindowLevel( preset.wl.getCenter(), preset.wl.getWidth(), name );
- };
-
- /**
- * Set the window level to the preset with the input id.
- * @param {Number} id The id of the preset to activate.
- */
- this.setWindowLevelPresetById = function (id) {
- var keys = Object.keys(this.getWindowPresets());
- this.setWindowLevelPreset( keys[id] );
- };
-
- /**
- * Clone the image using all meta data and the original data buffer.
- * @return {View} A full copy of this {dwv.image.View}.
- */
- this.clone = function ()
- {
- var copy = new dwv.image.View(this.getImage());
- for ( var key in windowLuts ) {
- copy.addWindowLut(windowLuts[key]);
- }
- copy.setListeners(this.getListeners());
- return copy;
- };
-
- /**
- * View listeners
- * @private
- * @type Object
- */
- var listeners = {};
- /**
- * Get the view listeners.
- * @return {Object} The view listeners.
- */
- this.getListeners = function() { return listeners; };
- /**
- * Set the view listeners.
- * @param {Object} list The view listeners.
- */
- this.setListeners = function(list) { listeners = list; };
-};
-
-/**
- * Get the image window/level that covers the full data range.
- * Warning: uses the latest set rescale LUT or the default linear one.
- */
-dwv.image.View.prototype.getWindowLevelMinMax = function ()
-{
- var range = this.getImage().getRescaledDataRange();
- var min = range.min;
- var max = range.max;
- var width = max - min;
- var center = min + width/2;
- return new dwv.image.WindowLevel(center, width);
-};
-
-/**
- * Set the image window/level to cover the full data range.
- * Warning: uses the latest set rescale LUT or the default linear one.
- */
-dwv.image.View.prototype.setWindowLevelMinMax = function()
-{
- // calculate center and width
- var wl = this.getWindowLevelMinMax();
- // set window level
- this.setWindowLevel(wl.getCenter(), wl.getWidth(), "minmax");
-};
-
-/**
- * Generate display image data to be given to a canvas.
- * @param {Array} array The array to fill in.
- */
-dwv.image.View.prototype.generateImageData = function( array )
-{
- var windowLut = this.getCurrentWindowLut();
-
- var image = this.getImage();
- var sliceSize = image.getGeometry().getSize().getSliceSize();
- var sliceOffset = sliceSize * this.getCurrentPosition().k;
- var frame = (this.getCurrentFrame()) ? this.getCurrentFrame() : 0;
-
- var index = 0;
- var pxValue = 0;
- var stepPos = 0;
-
- var photoInterpretation = image.getPhotometricInterpretation();
- switch (photoInterpretation)
- {
- case "MONOCHROME1":
- case "MONOCHROME2":
- var colourMap = this.getColourMap();
- var iMax = sliceOffset + sliceSize;
- for(var i=sliceOffset; i < iMax; ++i)
- {
- pxValue = parseInt( windowLut.getValue(
- image.getValueAtOffset(i, frame) ), 10 );
- array.data[index] = colourMap.red[pxValue];
- array.data[index+1] = colourMap.green[pxValue];
- array.data[index+2] = colourMap.blue[pxValue];
- array.data[index+3] = 0xff;
- index += 4;
- }
- break;
-
- case "RGB":
- // 3 times bigger...
- sliceOffset *= 3;
- // the planar configuration defines the memory layout
- var planarConfig = image.getPlanarConfiguration();
- if( planarConfig !== 0 && planarConfig !== 1 ) {
- throw new Error("Unsupported planar configuration: "+planarConfig);
- }
- // default: RGBRGBRGBRGB...
- var posR = sliceOffset;
- var posG = sliceOffset + 1;
- var posB = sliceOffset + 2;
- stepPos = 3;
- // RRRR...GGGG...BBBB...
- if (planarConfig === 1) {
- posR = sliceOffset;
- posG = sliceOffset + sliceSize;
- posB = sliceOffset + 2 * sliceSize;
- stepPos = 1;
- }
-
- for(var j=0; j < sliceSize; ++j)
- {
- array.data[index] = parseInt( windowLut.getValue(
- image.getValueAtOffset(posR, frame) ), 10 );
- array.data[index+1] = parseInt( windowLut.getValue(
- image.getValueAtOffset(posG, frame) ), 10 );
- array.data[index+2] = parseInt( windowLut.getValue(
- image.getValueAtOffset(posB, frame) ), 10 );
- array.data[index+3] = 0xff;
- index += 4;
-
- posR += stepPos;
- posG += stepPos;
- posB += stepPos;
- }
- break;
-
- case "YBR_FULL_422":
- // theory:
- // http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.7.html#sect_C.7.6.3.1.2
- // reverse equation:
- // https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
-
- // 3 times bigger...
- sliceOffset *= 3;
- // the planar configuration defines the memory layout
- var planarConfigYBR = image.getPlanarConfiguration();
- if( planarConfigYBR !== 0 && planarConfigYBR !== 1 ) {
- throw new Error("Unsupported planar configuration: "+planarConfigYBR);
- }
- // default: YBRYBRYBR...
- var posY = sliceOffset;
- var posCB = sliceOffset + 1;
- var posCR = sliceOffset + 2;
- stepPos = 3;
- // YYYY...BBBB...RRRR...
- if (planarConfigYBR === 1) {
- posY = sliceOffset;
- posCB = sliceOffset + sliceSize;
- posCR = sliceOffset + 2 * sliceSize;
- stepPos = 1;
- }
-
- var y, cb, cr;
- var r, g, b;
- for (var k=0; k < sliceSize; ++k)
- {
- y = image.getValueAtOffset(posY, frame);
- cb = image.getValueAtOffset(posCB, frame);
- cr = image.getValueAtOffset(posCR, frame);
-
- r = y + 1.402 * (cr - 128);
- g = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128);
- b = y + 1.772 * (cb - 128);
-
- array.data[index] = parseInt( windowLut.getValue(r), 10 );
- array.data[index+1] = parseInt( windowLut.getValue(g), 10 );
- array.data[index+2] = parseInt( windowLut.getValue(b), 10 );
- array.data[index+3] = 0xff;
- index += 4;
-
- posY += stepPos;
- posCB += stepPos;
- posCR += stepPos;
- }
- break;
-
- default:
- throw new Error("Unsupported photometric interpretation: "+photoInterpretation);
- }
-};
-
-/**
- * Add an event listener on the view.
- * @param {String} type The event type.
- * @param {Object} listener The method associated with the provided event type.
- */
-dwv.image.View.prototype.addEventListener = function(type, listener)
-{
- var listeners = this.getListeners();
- if( !listeners[type] ) {
- listeners[type] = [];
- }
- listeners[type].push(listener);
-};
-
-/**
- * Remove an event listener on the view.
- * @param {String} type The event type.
- * @param {Object} listener The method associated with the provided event type.
- */
-dwv.image.View.prototype.removeEventListener = function(type, listener)
-{
- var listeners = this.getListeners();
- if( !listeners[type] ) {
- return;
- }
- for(var i=0; i < listeners[type].length; ++i)
- {
- if( listeners[type][i] === listener ) {
- listeners[type].splice(i,1);
- }
- }
-};
-
-/**
- * Fire an event: call all associated listeners.
- * @param {Object} event The event to fire.
- */
-dwv.image.View.prototype.fireEvent = function(event)
-{
- var listeners = this.getListeners();
- if( !listeners[event.type] ) {
- return;
- }
- for(var i=0; i < listeners[event.type].length; ++i)
- {
- listeners[event.type][i](event);
- }
-};
-
-/**
- * View factory.
- * @constructor
- */
-dwv.image.ViewFactory = function () {};
-
-/**
- * Get an View object from the read DICOM file.
- * @param {Object} dicomElements The DICOM tags.
- * @param {Object} image The associated image.
- * @return {View} The new View.
- */
-dwv.image.ViewFactory.prototype.create = function (dicomElements, image)
-{
- // view
- var view = new dwv.image.View(image);
-
- // default color map
- if( image.getPhotometricInterpretation() === "MONOCHROME1") {
- view.setDefaultColourMap(dwv.image.lut.invPlain);
- }
-
- // presets
- var windowPresets = {};
-
- // DICOM presets
- var windowCenter = dicomElements.getFromKey("x00281050", true);
- var windowWidth = dicomElements.getFromKey("x00281051", true);
- var windowCWExplanation = dicomElements.getFromKey("x00281055", true);
- if ( windowCenter && windowWidth ) {
- var name;
- for ( var j = 0; j < windowCenter.length; ++j) {
- var center = parseFloat( windowCenter[j], 10 );
- var width = parseFloat( windowWidth[j], 10 );
- if ( center && width ) {
- name = "";
- if ( windowCWExplanation ) {
- name = dwv.dicom.cleanString(windowCWExplanation[j]);
- }
- if (name === "") {
- name = "Default"+j;
- }
- windowPresets[name] = {
- "wl": [new dwv.image.WindowLevel(center, width)],
- "name": name,
- "perslice": true};
- }
- }
- }
-
- // min/max
- // Not filled yet since it is stil too costly to calculate min/max
- // for each slice... It will be filled at first use (see view.setWindowLevelPreset).
- // Order is important, if no wl from DICOM, this will be the default.
- windowPresets.minmax = { "name": "minmax" };
-
- // optional modality presets
- if ( typeof dwv.tool.defaultpresets !== "undefined" ) {
- var modality = image.getMeta().Modality;
- for( var key in dwv.tool.defaultpresets[modality] ) {
- var preset = dwv.tool.defaultpresets[modality][key];
- windowPresets[key] = {
- "wl": new dwv.image.WindowLevel(preset.center, preset.width),
- "name": key};
- }
- }
-
- // store
- view.setWindowPresets( windowPresets );
-
- return view;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-
-/**
- * DICOM data loader.
- */
-dwv.io.DicomDataLoader = function ()
-{
- // closure to self
- var self = this;
-
- /**
- * Loader options.
- * @private
- * @type Object
- */
- var options = {};
-
- /**
- * Loading flag.
- * @private
- * @type Boolean
- */
- var isLoading = false;
-
- /**
- * Set the loader options.
- * @param {Object} opt The input options.
- */
- this.setOptions = function (opt) {
- options = opt;
- };
-
- /**
- * Is the load ongoing?
- * @return {Boolean} True if loading.
- */
- this.isLoading = function () {
- return isLoading;
- };
-
- /**
- * DICOM buffer to dwv.image.View (asynchronous)
- */
- var db2v = new dwv.image.DicomBufferToView();
-
- /**
- * Load data.
- * @param {Object} buffer The DICOM buffer.
- * @param {String} origin The data origin.
- * @param {Number} index The data index.
- */
- this.load = function (buffer, origin, index) {
- // set loading flag
- isLoading = true;
- // set character set
- if (typeof options.defaultCharacterSet !== "undefined") {
- db2v.setDefaultCharacterSet(options.defaultCharacterSet);
- }
- // connect handlers
- db2v.onload = self.onload;
- db2v.onloadend = function () {
- // reset loading flag
- isLoading = false;
- // call listeners
- self.onloadend();
- };
- db2v.onprogress = self.onprogress;
- // convert
- try {
- db2v.convert( buffer, index );
- } catch (error) {
- // TODO: error will be for individual file, isLoading is global...
- //isLoading = false;
- self.onerror(error);
- }
- };
-
- /**
- * Abort load.
- */
- this.abort = function () {
- // abort conversion
- db2v.abort();
- // reset loading flag
- isLoading = false;
- // call listeners
- self.onabort({message: "Abort while loading DICOM data."});
- };
-
- /**
- * Get a file load handler.
- * @param {Object} file The file to load.
- * @param {Number} index The index 'id' of the file.
- * @return {Function} A file load handler.
- */
- this.getFileLoadHandler = function (file, index) {
- return function (event) {
- self.load(event.target.result, file, index);
- };
- };
-
- /**
- * Get a url load handler.
- * @param {String} url The url to load.
- * @param {Number} index The index 'id' of the url.
- * @return {Function} A url load handler.
- */
- this.getUrlLoadHandler = function (url, index) {
- return function (/*event*/) {
- // check response status
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
- // status 200: "OK"; status 0: "debug"
- if (this.status !== 200 && this.status !== 0) {
- self.onerror({'name': "RequestError",
- 'message': "Error status: " + this.status +
- " while loading '" + url + "' [DicomDataLoader]" });
- return;
- }
- // load
- self.load(this.response, url, index);
- };
- };
-
-}; // class DicomDataLoader
-
-/**
- * Check if the loader can load the provided file.
- * @param {Object} file The file to check.
- * @return True if the file can be loaded.
- */
-dwv.io.DicomDataLoader.prototype.canLoadFile = function (file) {
- var split = file.name.split('.');
- var ext = "";
- if (split.length !== 1) {
- ext = split.pop().toLowerCase();
- }
- var hasExt = (ext.length !== 0);
- return !hasExt || (ext === "dcm");
-};
-
-/**
- * Check if the loader can load the provided url.
- * @param {String} url The url to check.
- * @return True if the url can be loaded.
- */
-dwv.io.DicomDataLoader.prototype.canLoadUrl = function (url) {
- var split = url.split('.');
- var ext = "";
- if (split.length !== 1) {
- ext = split.pop().toLowerCase();
- }
- var hasExt = (ext.length !== 0) && (ext.length < 5);
- // wado url
- var isDicomContentType = (url.indexOf("contentType=application/dicom") !== -1);
-
- return isDicomContentType || (ext === "dcm") || !hasExt;
-};
-
-/**
- * Get the file content type needed by the loader.
- * @return One of the 'dwv.io.fileContentTypes'.
- */
-dwv.io.DicomDataLoader.prototype.loadFileAs = function () {
- return dwv.io.fileContentTypes.ArrayBuffer;
-};
-
-/**
- * Get the url content type needed by the loader.
- * @return One of the 'dwv.io.urlContentTypes'.
- */
-dwv.io.DicomDataLoader.prototype.loadUrlAs = function () {
- return dwv.io.urlContentTypes.ArrayBuffer;
-};
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.DicomDataLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle an load end event.
- * Default does nothing.
- */
-dwv.io.DicomDataLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.DicomDataLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.DicomDataLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.DicomDataLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Add to Loader list.
- */
-dwv.io.loaderList = dwv.io.loaderList || [];
-dwv.io.loaderList.push( "DicomDataLoader" );
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.io = dwv.io || {};
-
-// file content types
-dwv.io.fileContentTypes = {
- 'Text': 0,
- 'ArrayBuffer': 1,
- 'DataURL': 2
-};
-
-/**
- * Files loader.
- * @constructor
- */
-dwv.io.FilesLoader = function ()
-{
- /**
- * Closure to self.
- * @private
- * @type Object
- */
- var self = this;
-
- /**
- * Array of launched readers (used in abort).
- * @private
- * @type Array
- */
- var readers = [];
-
- /**
- * Launched loader (used in abort).
- * @private
- * @type Object
- */
- var runningLoader = [];
-
- /**
- * Number of data to load.
- * @private
- * @type Number
- */
- var nToLoad = 0;
- /**
- * Number of loaded data.
- * @private
- * @type Number
- */
- var nLoaded = 0;
-
- /**
- * The default character set (optional).
- * @private
- * @type String
- */
- var defaultCharacterSet;
-
- /**
- * Get the default character set.
- * @return {String} The default character set.
- */
- this.getDefaultCharacterSet = function () {
- return defaultCharacterSet;
- };
-
- /**
- * Set the default character set.
- * @param {String} characterSet The character set.
- */
- this.setDefaultCharacterSet = function (characterSet) {
- defaultCharacterSet = characterSet;
- };
-
- /**
- * Store a launched reader.
- * @param {Object} request The launched reader.
- */
- this.storeReader = function (reader) {
- readers.push(reader);
- };
-
- /**
- * Clear the stored readers.
- */
- this.clearStoredReaders = function () {
- readers = [];
- };
-
- /**
- * Store the launched loader.
- * @param {Object} loader The launched loader.
- */
- this.storeLoader = function (loader) {
- runningLoader = loader;
- };
-
- /**
- * Clear the stored loader.
- */
- this.clearStoredLoader = function () {
- runningLoader = null;
- };
-
- /**
- * Abort a URLs load.
- */
- this.abort = function () {
- // abort readers
- for ( var i = 0; i < readers.length; ++i ) {
- // 0: EMPTY, 1: LOADING, 2: DONE
- if ( readers[i].readyState === 1 ) {
- readers[i].abort();
- }
- }
- this.clearStoredReaders();
- // abort loader
- if ( runningLoader ) {
- runningLoader.abort();
- }
- this.clearStoredLoader();
- };
-
- /**
- * Set the number of data to load.
- * @param {Number} n The number of data to load.
- */
- this.setNToLoad = function (n) {
- nToLoad = n;
- };
-
- /**
- * Increment the number of loaded data
- * and call onloadend if loaded all data.
- */
- this.addLoaded = function () {
- nLoaded++;
- if ( nLoaded === nToLoad ) {
- self.onloadend();
- }
- };
-
-}; // class File
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.FilesLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle a load end event.
- * Default does nothing.
- */
-dwv.io.FilesLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.FilesLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.FilesLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.FilesLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Load a list of files.
- * @param {Array} ioArray The list of files to load.
- * @external FileReader
- */
-dwv.io.FilesLoader.prototype.load = function (ioArray)
-{
- // clear storage
- this.clearStoredReaders();
- this.clearStoredLoader();
-
- // closure to self for handlers
- var self = this;
- // set the number of data to load
- this.setNToLoad( ioArray.length );
-
- var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress);
- mproghandler.setNToLoad( ioArray.length );
-
- // get loaders
- var loaders = [];
- for (var m = 0; m < dwv.io.loaderList.length; ++m) {
- loaders.push( new dwv.io[dwv.io.loaderList[m]]() );
- }
-
- // set loaders callbacks
- var loader = null;
- for (var k = 0; k < loaders.length; ++k) {
- loader = loaders[k];
- loader.onload = self.onload;
- loader.onloadend = self.addLoaded;
- loader.onerror = self.onerror;
- loader.onabort = self.onabort;
- loader.setOptions({
- 'defaultCharacterSet': this.getDefaultCharacterSet()
- });
- loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1);
- }
-
- // request onerror handler
- var getReaderOnError = function (origin) {
- return function (event) {
- var message = "An error occurred while reading '" + origin + "'";
- if (typeof event.getMessage !== "undefined") {
- message += " (" + event.getMessage() + ")";
- }
- message += ".";
- self.onerror( {'name': "FileReaderError", 'message': message } );
- };
- };
-
- // request onabort handler
- var getReaderOnAbort = function (origin) {
- return function () {
- self.onabort( {'message': "Abort while reading '" + origin + "'" } );
- };
- };
-
- // loop on I/O elements
- for (var i = 0; i < ioArray.length; ++i)
- {
- var file = ioArray[i];
- var reader = new FileReader();
-
- // store reader
- this.storeReader(reader);
-
- // bind reader progress
- reader.onprogress = mproghandler.getMonoProgressHandler(i, 0);
-
- // find a loader
- var foundLoader = false;
- for (var l = 0; l < loaders.length; ++l) {
- loader = loaders[l];
- if (loader.canLoadFile(file)) {
- foundLoader = true;
- // store loader
- this.storeLoader(loader);
- // set reader callbacks
- reader.onload = loader.getFileLoadHandler(file, i);
- reader.onerror = getReaderOnError(file.name);
- reader.onabort = getReaderOnAbort(file.name);
- // read
- if (loader.loadFileAs() === dwv.io.fileContentTypes.Text) {
- reader.readAsText(file);
- } else if (loader.loadFileAs() === dwv.io.fileContentTypes.DataURL) {
- reader.readAsDataURL(file);
- } else if (loader.loadFileAs() === dwv.io.fileContentTypes.ArrayBuffer) {
- reader.readAsArrayBuffer(file);
- }
- // next file
- break;
- }
- }
- // TODO: throw?
- if (!foundLoader) {
- throw new Error("No loader found for file: "+file);
- }
- }
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-
-/**
- * JSON text loader.
- */
-dwv.io.JSONTextLoader = function ()
-{
- // closure to self
- var self = this;
-
- /**
- * Loading flag.
- * @private
- * @type Boolean
- */
- var isLoading = false;
-
- /**
- * Set the loader options.
- * @param {Object} opt The input options.
- */
- this.setOptions = function () {
- // does nothing
- };
-
- /**
- * Is the load ongoing?
- * @return {Boolean} True if loading.
- */
- this.isLoading = function () {
- return isLoading;
- };
-
- /**
- * Load data.
- * @param {Object} text The input text.
- * @param {String} origin The data origin.
- * @param {Number} index The data index.
- */
- this.load = function (text, origin, index) {
- // set loading flag
- isLoading = true;
- try {
- self.onload( text );
- // reset loading flag
- isLoading = false;
- // call listeners
- self.onloadend();
- } catch (error) {
- self.onerror(error);
- }
- self.onprogress({'type': 'read-progress', 'lengthComputable': true,
- 'loaded': 100, 'total': 100, 'index': index});
- };
-
- /**
- * Abort load: pass to listeners.
- */
- this.abort = function () {
- // reset loading flag
- isLoading = false;
- // call listeners
- self.onabort();
- };
-
- /**
- * Get a file load handler.
- * @param {Object} file The file to load.
- * @param {Number} index The index 'id' of the file.
- * @return {Function} A file load handler.
- */
- this.getFileLoadHandler = function (file, index) {
- return function (event) {
- self.load(event.target.result, file, index);
- };
- };
-
- /**
- * Get a url load handler.
- * @param {String} url The url to load.
- * @param {Number} index The index 'id' of the url.
- * @return {Function} A url load handler.
- */
- this.getUrlLoadHandler = function (url, index) {
- return function (/*event*/) {
- // check response status
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
- // status 200: "OK"; status 0: "debug"
- if (this.status !== 200 && this.status !== 0) {
- self.onerror({'name': "RequestError",
- 'message': "Error status: " + this.status +
- " while loading '" + url + "' [JSONTextLoader]" });
- return;
- }
- // load
- self.load(this.responseText, url, index);
- };
- };
-
-}; // class JSONTextLoader
-
-/**
- * Check if the loader can load the provided file.
- * @param {Object} file The file to check.
- * @return True if the file can be loaded.
- */
-dwv.io.JSONTextLoader.prototype.canLoadFile = function (file) {
- var ext = file.name.split('.').pop().toLowerCase();
- return (ext === "json");
-};
-
-/**
- * Check if the loader can load the provided url.
- * @param {String} url The url to check.
- * @return True if the url can be loaded.
- */
-dwv.io.JSONTextLoader.prototype.canLoadUrl = function (url) {
- var ext = url.split('.').pop().toLowerCase();
- return (ext === "json");
-};
-
-/**
- * Get the file content type needed by the loader.
- * @return One of the 'dwv.io.fileContentTypes'.
- */
-dwv.io.JSONTextLoader.prototype.loadFileAs = function () {
- return dwv.io.fileContentTypes.Text;
-};
-
-/**
- * Get the url content type needed by the loader.
- * @return One of the 'dwv.io.urlContentTypes'.
- */
-dwv.io.JSONTextLoader.prototype.loadUrlAs = function () {
- return dwv.io.urlContentTypes.Text;
-};
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.JSONTextLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle an load end event.
- * Default does nothing.
- */
-dwv.io.JSONTextLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.JSONTextLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.JSONTextLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.JSONTextLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Add to Loader list.
- */
-dwv.io.loaderList = dwv.io.loaderList || [];
-dwv.io.loaderList.push( "JSONTextLoader" );
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-
-/**
- * Memory loader.
- * @constructor
- */
-dwv.io.MemoryLoader = function ()
-{
- /**
- * Closure to self.
- * @private
- * @type Object
- */
- var self = this;
-
- /**
- * Launched loader (used in abort).
- * @private
- * @type Object
- */
- var runningLoader = null;
-
- /**
- * Number of data to load.
- * @private
- * @type Number
- */
- var nToLoad = 0;
- /**
- * Number of loaded data.
- * @private
- * @type Number
- */
- var nLoaded = 0;
-
- /**
- * The default character set (optional).
- * @private
- * @type String
- */
- var defaultCharacterSet;
-
- /**
- * Get the default character set.
- * @return {String} The default character set.
- */
- this.getDefaultCharacterSet = function () {
- return defaultCharacterSet;
- };
-
- /**
- * Set the default character set.
- * @param {String} characterSet The character set.
- */
- this.setDefaultCharacterSet = function (characterSet) {
- defaultCharacterSet = characterSet;
- };
-
- /**
- * Store a launched loader.
- * @param {Object} loader The launched loader.
- */
- this.storeLoader = function (loader) {
- runningLoader = loader;
- };
-
- /**
- * Clear the stored loader.
- */
- this.clearStoredLoader = function () {
- runningLoader = null;
- };
-
- /**
- * Abort a memory load.
- */
- this.abort = function () {
- // abort loader
- runningLoader.abort();
- this.clearStoredLoaders();
- };
-
- /**
- * Set the number of data to load.
- * @param {Number} n The number of data to load.
- */
- this.setNToLoad = function (n) {
- nToLoad = n;
- };
-
- /**
- * Increment the number of loaded data
- * and call onloadend if loaded all data.
- */
- this.addLoaded = function () {
- nLoaded++;
- if ( nLoaded === nToLoad ) {
- self.onloadend();
- }
- };
-
-}; // class Memory
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.MemoryLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle a load end event.
- * Default does nothing.
- */
-dwv.io.MemoryLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.MemoryLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.MemoryLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.MemoryLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Load a list of buffers.
- * @param {Array} ioArray The list of buffers to load.
- */
-dwv.io.MemoryLoader.prototype.load = function (ioArray)
-{
- // clear storage
- this.clearStoredLoader();
-
- // closure to self for handlers
- var self = this;
- // set the number of data to load
- this.setNToLoad( ioArray.length );
-
- var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress);
- mproghandler.setNToLoad( ioArray.length );
-
- // get loaders
- var loaders = [];
- for (var m = 0; m < dwv.io.loaderList.length; ++m) {
- loaders.push( new dwv.io[dwv.io.loaderList[m]]() );
- }
-
- // set loaders callbacks
- var loader = null;
- for (var k = 0; k < loaders.length; ++k) {
- loader = loaders[k];
- loader.onload = self.onload;
- loader.onloadend = self.addLoaded;
- loader.onerror = self.onerror;
- loader.onabort = self.onabort;
- loader.setOptions({
- 'defaultCharacterSet': this.getDefaultCharacterSet()
- });
- loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1);
- }
-
- // loop on I/O elements
- for (var i = 0; i < ioArray.length; ++i)
- {
- var iodata = ioArray[i];
-
- // find a loader
- var foundLoader = false;
- for (var l = 0; l < loaders.length; ++l) {
- loader = loaders[l];
- if (loader.canLoadUrl(iodata.filename)) {
- foundLoader = true;
- // store loader
- this.storeLoader(loader);
- // read
- loader.load(iodata.data, iodata.filename, i);
- // next file
- break;
- }
- }
- // TODO: throw?
- if (!foundLoader) {
- throw new Error("No loader found for file: "+iodata.filename);
- }
- }
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-
-/**
- * Raw image loader.
- */
-dwv.io.RawImageLoader = function ()
-{
- // closure to self
- var self = this;
-
- /**
- * Set the loader options.
- * @param {Object} opt The input options.
- */
- this.setOptions = function () {
- // does nothing
- };
-
- /**
- * Is the load ongoing? TODO...
- * @return {Boolean} True if loading.
- */
- this.isLoading = function () {
- return true;
- };
-
- /**
- * Create a Data URI from an HTTP request response.
- * @param {Object} response The HTTP request response.
- * @param {String} dataType The data type.
- */
- function createDataUri(response, dataType) {
- // image data as string
- var bytes = new Uint8Array(response);
- var imageDataStr = '';
- for( var i = 0; i < bytes.byteLength; ++i ) {
- imageDataStr += String.fromCharCode(bytes[i]);
- }
- // image type
- var imageType = dataType;
- if (imageType === "jpg") {
- imageType = "jpeg";
- }
- // create uri
- var uri = "data:image/" + imageType + ";base64," + window.btoa(imageDataStr);
- return uri;
- }
-
- /**
- * Load data.
- * @param {Object} dataUri The data URI.
- * @param {String} origin The data origin.
- * @param {Number} index The data index.
- */
- this.load = function ( dataUri, origin, index ) {
- // create a DOM image
- var image = new Image();
- image.src = dataUri;
- // storing values to pass them on
- image.origin = origin;
- image.index = index;
- // triggered by ctx.drawImage
- image.onload = function (/*event*/) {
- try {
- self.onload( dwv.image.getViewFromDOMImage(this) );
- self.onloadend();
- } catch (error) {
- self.onerror(error);
- }
- self.onprogress({'type': 'read-progress', 'lengthComputable': true,
- 'loaded': 100, 'total': 100, 'index': index});
- };
- };
-
- /**
- * Abort load. TODO...
- */
- this.abort = function () {
- self.onabort();
- };
-
- /**
- * Get a file load handler.
- * @param {Object} file The file to load.
- * @param {Number} index The index 'id' of the file.
- * @return {Function} A file load handler.
- */
- this.getFileLoadHandler = function (file, index) {
- return function (event) {
- self.load(event.target.result, file, index);
- };
- };
-
- /**
- * Get a url load handler.
- * @param {String} url The url to load.
- * @param {Number} index The index 'id' of the url.
- * @return {Function} A url load handler.
- */
- this.getUrlLoadHandler = function (url, index) {
- return function (/*event*/) {
- // check response status
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
- // status 200: "OK"; status 0: "debug"
- if (this.status !== 200 && this.status !== 0) {
- self.onerror({'name': "RequestError",
- 'message': "Error status: " + this.status +
- " while loading '" + url + "' [RawImageLoader]" });
- return;
- }
- // load
- var ext = url.split('.').pop().toLowerCase();
- self.load(createDataUri(this.response, ext), url, index);
- };
- };
-
-}; // class RawImageLoader
-
-/**
- * Check if the loader can load the provided file.
- * @param {Object} file The file to check.
- * @return True if the file can be loaded.
- */
-dwv.io.RawImageLoader.prototype.canLoadFile = function (file) {
- return file.type.match("image.*");
-};
-
-/**
- * Check if the loader can load the provided url.
- * @param {String} url The url to check.
- * @return True if the url can be loaded.
- */
-dwv.io.RawImageLoader.prototype.canLoadUrl = function (url) {
- var ext = url.split('.').pop().toLowerCase();
- var hasImageExt = (ext === "jpeg") || (ext === "jpg") ||
- (ext === "png") || (ext === "gif");
- // wado url
- var isImageContentType = (url.indexOf("contentType=image/jpeg") !== -1) ||
- (url.indexOf("contentType=image/png") !== -1) ||
- (url.indexOf("contentType=image/gif") !== -1);
-
- return isImageContentType || hasImageExt;
-};
-
-/**
- * Get the file content type needed by the loader.
- * @return One of the 'dwv.io.fileContentTypes'.
- */
-dwv.io.RawImageLoader.prototype.loadFileAs = function () {
- return dwv.io.fileContentTypes.DataURL;
-};
-
-/**
- * Get the url content type needed by the loader.
- * @return One of the 'dwv.io.urlContentTypes'.
- */
-dwv.io.RawImageLoader.prototype.loadUrlAs = function () {
- return dwv.io.urlContentTypes.ArrayBuffer;
-};
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.RawImageLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle an load end event.
- * Default does nothing.
- */
-dwv.io.RawImageLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.RawImageLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.RawImageLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.RawImageLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Add to Loader list.
- */
-dwv.io.loaderList = dwv.io.loaderList || [];
-dwv.io.loaderList.push( "RawImageLoader" );
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-
-/**
- * Raw video loader.
- * url example (cors enabled):
- * https://raw.githubusercontent.com/clappr/clappr/master/test/fixtures/SampleVideo_360x240_1mb.mp4
- */
-dwv.io.RawVideoLoader = function ()
-{
- // closure to self
- var self = this;
-
- /**
- * Set the loader options.
- * @param {Object} opt The input options.
- */
- this.setOptions = function () {
- // does nothing
- };
-
- /**
- * Is the load ongoing? TODO...
- * @return {Boolean} True if loading.
- */
- this.isLoading = function () {
- return true;
- };
-
- /**
- * Create a Data URI from an HTTP request response.
- * @param {Object} response The HTTP request response.
- * @param {String} dataType The data type.
- */
- function createDataUri(response, dataType) {
- // image data as string
- var bytes = new Uint8Array(response);
- var videoDataStr = '';
- for( var i = 0; i < bytes.byteLength; ++i ) {
- videoDataStr += String.fromCharCode(bytes[i]);
- }
- // create uri
- var uri = "data:video/" + dataType + ";base64," + window.btoa(videoDataStr);
- return uri;
- }
-
- /**
- * Internal Data URI load.
- * @param {Object} dataUri The data URI.
- * @param {String} origin The data origin.
- * @param {Number} index The data index.
- */
- this.load = function ( dataUri, origin, index ) {
- // create a DOM video
- var video = document.createElement('video');
- video.src = dataUri;
- // storing values to pass them on
- video.file = origin;
- video.index = index;
- // onload handler
- video.onloadedmetadata = function (/*event*/) {
- try {
- dwv.image.getViewFromDOMVideo(this,
- self.onload, self.onprogress, self.onloadend, index);
- } catch (error) {
- self.onerror(error);
- }
- };
- };
-
- /**
- * Abort load. TODO...
- */
- this.abort = function () {
- self.onabort();
- };
-
- /**
- * Get a file load handler.
- * @param {Object} file The file to load.
- * @param {Number} index The index 'id' of the file.
- * @return {Function} A file load handler.
- */
- this.getFileLoadHandler = function (file, index) {
- return function (event) {
- self.load(event.target.result, file, index);
- };
- };
-
- /**
- * Get a url load handler.
- * @param {String} url The url to load.
- * @param {Number} index The index 'id' of the url.
- * @return {Function} A url load handler.
- */
- this.getUrlLoadHandler = function (url, index) {
- return function (/*event*/) {
- // check response status
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
- // status 200: "OK"; status 0: "debug"
- if (this.status !== 200 && this.status !== 0) {
- self.onerror({'name': "RequestError",
- 'message': "Error status: " + this.status +
- " while loading '" + url + "' [RawVideoLoader]" });
- return;
- }
- // load
- var ext = url.split('.').pop().toLowerCase();
- self.load(createDataUri(this.response, ext), url, index);
- };
- };
-
-}; // class RawVideoLoader
-
-/**
- * Check if the loader can load the provided file.
- * @param {Object} file The file to check.
- * @return True if the file can be loaded.
- */
-dwv.io.RawVideoLoader.prototype.canLoadFile = function (file) {
- return file.type.match("video.*");
-};
-
-/**
- * Check if the loader can load the provided url.
- * @param {String} url The url to check.
- * @return True if the url can be loaded.
- */
-dwv.io.RawVideoLoader.prototype.canLoadUrl = function (url) {
- var ext = url.split('.').pop().toLowerCase();
- return (ext === "mp4") || (ext === "ogg") ||
- (ext === "webm");
-};
-
-/**
- * Get the file content type needed by the loader.
- * @return One of the 'dwv.io.fileContentTypes'.
- */
-dwv.io.RawVideoLoader.prototype.loadFileAs = function () {
- return dwv.io.fileContentTypes.DataURL;
-};
-
-/**
- * Get the url content type needed by the loader.
- * @return One of the 'dwv.io.urlContentTypes'.
- */
-dwv.io.RawVideoLoader.prototype.loadUrlAs = function () {
- return dwv.io.urlContentTypes.ArrayBuffer;
-};
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.RawVideoLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle an load end event.
- * Default does nothing.
- */
-dwv.io.RawVideoLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.RawVideoLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.RawVideoLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.RawVideoLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Add to Loader list.
- */
-dwv.io.loaderList = dwv.io.loaderList || [];
-dwv.io.loaderList.push( "RawVideoLoader" );
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-
-// url content types
-dwv.io.urlContentTypes = {
- 'Text': 0,
- 'ArrayBuffer': 1,
- 'oups': 2
-};
-
-/**
- * Urls loader.
- * @constructor
- */
-dwv.io.UrlsLoader = function ()
-{
- /**
- * Closure to self.
- * @private
- * @type Object
- */
- var self = this;
-
- /**
- * Array of launched requests used in abort.
- * @private
- * @type Array
- */
- var requests = [];
-
- /**
- * Launched loader used in abort.
- * @private
- * @type Object
- */
- var runningLoader = null;
-
- /**
- * Number of data to load.
- * @private
- * @type Number
- */
- var nToLoad = 0;
- /**
- * Number of loaded data.
- * @private
- * @type Number
- */
- var nLoaded = 0;
-
- /**
- * The default character set (optional).
- * @private
- * @type String
- */
- var defaultCharacterSet;
-
- /**
- * Get the default character set.
- * @return {String} The default character set.
- */
- this.getDefaultCharacterSet = function () {
- return defaultCharacterSet;
- };
-
- /**
- * Set the default character set.
- * @param {String} characterSet The character set.
- */
- this.setDefaultCharacterSet = function (characterSet) {
- defaultCharacterSet = characterSet;
- };
-
- /**
- * Store a launched request.
- * @param {Object} request The launched request.
- */
- this.storeRequest = function (request) {
- requests.push(request);
- };
-
- /**
- * Clear the stored requests.
- */
- this.clearStoredRequests = function () {
- requests = [];
- };
-
- /**
- * Store a launched loader.
- * @param {Object} loader The launched loader.
- */
- this.storeLoader = function (loader) {
- runningLoader = loader;
- };
-
- /**
- * Clear the stored loader.
- */
- this.clearStoredLoader = function () {
- runningLoader = null;
- };
-
- /**
- * Abort a URLs load.
- */
- this.abort = function () {
- // abort requests
- for ( var i = 0; i < requests.length; ++i ) {
- // 0: UNSENT, 1: OPENED, 2: HEADERS_RECEIVED (send()), 3: LOADING, 4: DONE
- if ( requests[i].readyState === 2 || requests[i].readyState === 3 ) {
- requests[i].abort();
- }
- }
- this.clearStoredRequests();
- // abort loader
- if ( runningLoader && runningLoader.isLoading() ) {
- runningLoader.abort();
- }
- this.clearStoredLoader();
- };
-
- /**
- * Set the number of data to load.
- * @param {Number} n The number of data to load.
- */
- this.setNToLoad = function (n) {
- nToLoad = n;
- };
-
- /**
- * Increment the number of loaded data
- * and call onloadend if loaded all data.
- */
- this.addLoaded = function () {
- nLoaded++;
- if ( nLoaded === nToLoad ) {
- self.onloadend();
- }
- };
-
-}; // class Url
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.UrlsLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle a load end event.
- * Default does nothing.
- */
-dwv.io.UrlsLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.UrlsLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.UrlsLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.UrlsLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Load a list of URLs.
- * @param {Array} ioArray The list of urls to load.
- * @param {Object} options Load options.
- * @external XMLHttpRequest
- */
-dwv.io.UrlsLoader.prototype.load = function (ioArray, options)
-{
- // clear storage
- this.clearStoredRequests();
- this.clearStoredLoader();
-
- // closure to self for handlers
- var self = this;
- // set the number of data to load
- this.setNToLoad( ioArray.length );
-
- var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress);
- mproghandler.setNToLoad( ioArray.length );
-
- // get loaders
- var loaders = [];
- for (var m = 0; m < dwv.io.loaderList.length; ++m) {
- loaders.push( new dwv.io[dwv.io.loaderList[m]]() );
- }
-
- // set loaders callbacks
- var loader = null;
- for (var k = 0; k < loaders.length; ++k) {
- loader = loaders[k];
- loader.onload = self.onload;
- loader.onloadend = self.addLoaded;
- loader.onerror = self.onerror;
- loader.onabort = self.onabort;
- loader.setOptions({
- 'defaultCharacterSet': this.getDefaultCharacterSet()
- });
- loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1);
- }
-
- // request onerror handler
- var getRequestOnError = function (origin) {
- return function (/*event*/) {
- var message = "An error occurred while downloading '" + origin + "'";
- if (typeof this.status !== "undefined") {
- message += " (http status: " + this.status + ")";
- }
- message += ".";
- self.onerror( {'name': "RequestError", 'message': message } );
- };
- };
-
- // request onabort handler
- var getRequestOnAbort = function (origin) {
- return function () {
- self.onabort( {'message': "Abort while downloading '" + origin + "'." } );
- };
- };
-
- // loop on I/O elements
- for (var i = 0; i < ioArray.length; ++i)
- {
- var url = ioArray[i];
- var request = new XMLHttpRequest();
- request.open('GET', url, true);
-
- // store request
- this.storeRequest(request);
-
- // optional request headers
- if ( typeof options.requestHeaders !== "undefined" ) {
- var requestHeaders = options.requestHeaders;
- for (var j = 0; j < requestHeaders.length; ++j) {
- if ( typeof requestHeaders[j].name !== "undefined" &&
- typeof requestHeaders[j].value !== "undefined" ) {
- request.setRequestHeader(requestHeaders[j].name, requestHeaders[j].value);
- }
- }
- }
-
- // bind reader progress
- request.onprogress = mproghandler.getMonoProgressHandler(i, 0);
- request.onloadend = mproghandler.getMonoOnLoadEndHandler(i, 0);
-
- // find a loader
- var foundLoader = false;
- for (var l = 0; l < loaders.length; ++l) {
- loader = loaders[l];
- if (loader.canLoadUrl(url)) {
- foundLoader = true;
- // store loader
- this.storeLoader(loader);
- // set reader callbacks
- request.onload = loader.getUrlLoadHandler(url, i);
- request.onerror = getRequestOnError(url);
- request.onabort = getRequestOnAbort(url);
- // response type (default is 'text')
- if (loader.loadUrlAs() === dwv.io.urlContentTypes.ArrayBuffer) {
- request.responseType = "arraybuffer";
- }
- // read
- request.send(null);
- // next file
- break;
- }
- }
- // TODO: throw?
- if (!foundLoader) {
- throw new Error("No loader found for url: "+url);
- }
- }
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.io = dwv.io || {};
-// external
-var JSZip = JSZip || {};
-
-/**
- * ZIP data loader.
- */
-dwv.io.ZipLoader = function ()
-{
- // closure to self
- var self = this;
-
- /**
- * Loader options.
- * @private
- * @type Object
- */
- var options = {};
-
- /**
- * Loading flag.
- * @private
- * @type Boolean
- */
- var isLoading = false;
-
- /**
- * Set the loader options.
- * @param {Object} opt The input options.
- */
- this.setOptions = function (opt) {
- options = opt;
- };
-
- /**
- * Is the load ongoing?
- * @return {Boolean} True if loading.
- */
- this.isLoading = function () {
- return isLoading;
- };
-
- var filename = "";
- var files = [];
- var zobjs = null;
-
- /**
- * JSZip.async callback
- * @param {ArrayBuffer} content unzipped file image
- * @return {}
- */
- function zipAsyncCallback(content)
- {
- files.push({"filename": filename, "data": content});
-
- if (files.length < zobjs.length){
- var num = files.length;
- filename = zobjs[num].name;
- zobjs[num].async("arrayBuffer").then(zipAsyncCallback);
- }
- else {
- var memoryIO = new dwv.io.MemoryLoader();
- memoryIO.onload = self.onload;
- memoryIO.onloadend = function () {
- // reset loading flag
- isLoading = false;
- // call listeners
- self.onloadend();
- };
- memoryIO.onprogress = self.onprogress;
- memoryIO.onerror = self.onerror;
- memoryIO.onabort = self.onabort;
-
- memoryIO.load(files);
- }
- }
-
- /**
- * Load data.
- * @param {Object} buffer The DICOM buffer.
- * @param {String} origin The data origin.
- * @param {Number} index The data index.
- */
- this.load = function (buffer/*, origin, index*/) {
- // set loading flag
- isLoading = true;
-
- JSZip.loadAsync(buffer).then( function(zip) {
- files = [];
- zobjs = zip.file(/.*\.dcm/);
- // recursively load zip files into the files array
- var num = files.length;
- filename = zobjs[num].name;
- zobjs[num].async("arrayBuffer").then(zipAsyncCallback);
- });
- };
-
- /**
- * Abort load: pass to listeners.
- */
- this.abort = function () {
- // reset loading flag
- isLoading = false;
- // call listeners
- self.onabort();
- };
-
- /**
- * Get a file load handler.
- * @param {Object} file The file to load.
- * @param {Number} index The index 'id' of the file.
- * @return {Function} A file load handler.
- */
- this.getFileLoadHandler = function (file, index) {
- return function (event) {
- self.load(event.target.result, file, index);
- };
- };
-
- /**
- * Get a url load handler.
- * @param {String} url The url to load.
- * @param {Number} index The index 'id' of the url.
- * @return {Function} A url load handler.
- */
- this.getUrlLoadHandler = function (url, index) {
- return function (/*event*/) {
- // check response status
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
- // status 200: "OK"; status 0: "debug"
- if (this.status !== 200 && this.status !== 0) {
- self.onerror({'name': "RequestError",
- 'message': "Error status: " + this.status +
- " while loading '" + url + "' [ZipLoader]" });
- return;
- }
- // load
- self.load(this.response, url, index);
- };
- };
-
-}; // class DicomDataLoader
-
-/**
- * Check if the loader can load the provided file.
- * @param {Object} file The file to check.
- * @return True if the file can be loaded.
- */
-dwv.io.ZipLoader.prototype.canLoadFile = function (file) {
- var ext = file.name.split('.').pop().toLowerCase();
- return (ext === "zip");
-};
-
-/**
- * Check if the loader can load the provided url.
- * @param {String} url The url to check.
- * @return True if the url can be loaded.
- */
-dwv.io.ZipLoader.prototype.canLoadUrl = function (url) {
- var ext = url.split('.').pop().toLowerCase();
- return (ext === "zip");
-};
-
-/**
- * Get the file content type needed by the loader.
- * @return One of the 'dwv.io.fileContentTypes'.
- */
-dwv.io.ZipLoader.prototype.loadFileAs = function () {
- return dwv.io.fileContentTypes.ArrayBuffer;
-};
-
-/**
- * Get the url content type needed by the loader.
- * @return One of the 'dwv.io.urlContentTypes'.
- */
-dwv.io.ZipLoader.prototype.loadUrlAs = function () {
- return dwv.io.urlContentTypes.ArrayBuffer;
-};
-
-/**
- * Handle a load event.
- * @param {Object} event The load event, 'event.target'
- * should be the loaded data.
- * Default does nothing.
- */
-dwv.io.ZipLoader.prototype.onload = function (/*event*/) {};
-/**
- * Handle an load end event.
- * Default does nothing.
- */
-dwv.io.ZipLoader.prototype.onloadend = function () {};
-/**
- * Handle a progress event.
- * @param {Object} event The progress event.
- * Default does nothing.
- */
-dwv.io.ZipLoader.prototype.onprogress = function (/*event*/) {};
-/**
- * Handle an error event.
- * @param {Object} event The error event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.ZipLoader.prototype.onerror = function (/*event*/) {};
-/**
- * Handle an abort event.
- * @param {Object} event The abort event with an
- * optional 'event.message'.
- * Default does nothing.
- */
-dwv.io.ZipLoader.prototype.onabort = function (/*event*/) {};
-
-/**
- * Add to Loader list.
- */
-dwv.io.loaderList = dwv.io.loaderList || [];
-dwv.io.loaderList.push( "ZipLoader" );
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.math = dwv.math || {};
-
-/**
- * Circular Bucket Queue.
- *
- * Returns input'd points in sorted order. All operations run in roughly O(1)
- * time (for input with small cost values), but it has a strict requirement:
- *
- * If the most recent point had a cost of c, any points added should have a cost
- * c' in the range c <= c' <= c + (capacity - 1).
- *
- * @constructor
- * @input bits
- * @input cost_functor
- */
-dwv.math.BucketQueue = function(bits, cost_functor)
-{
- this.bucketCount = 1 << bits; // # of buckets = 2^bits
- this.mask = this.bucketCount - 1; // 2^bits - 1 = index mask
- this.size = 0;
-
- this.loc = 0; // Current index in bucket list
-
- // Cost defaults to item value
- this.cost = (typeof(cost_functor) !== 'undefined') ? cost_functor : function(item) {
- return item;
- };
-
- this.buckets = this.buildArray(this.bucketCount);
-};
-
-dwv.math.BucketQueue.prototype.push = function(item) {
- // Prepend item to the list in the appropriate bucket
- var bucket = this.getBucket(item);
- item.next = this.buckets[bucket];
- this.buckets[bucket] = item;
-
- this.size++;
-};
-
-dwv.math.BucketQueue.prototype.pop = function() {
- if ( this.size === 0 ) {
- throw new Error("Cannot pop, bucketQueue is empty.");
- }
-
- // Find first empty bucket
- while ( this.buckets[this.loc] === null ) {
- this.loc = (this.loc + 1) % this.bucketCount;
- }
-
- // All items in bucket have same cost, return the first one
- var ret = this.buckets[this.loc];
- this.buckets[this.loc] = ret.next;
- ret.next = null;
-
- this.size--;
- return ret;
-};
-
-dwv.math.BucketQueue.prototype.remove = function(item) {
- // Tries to remove item from queue. Returns true on success, false otherwise
- if ( !item ) {
- return false;
- }
-
- // To find node, go to bucket and search through unsorted list.
- var bucket = this.getBucket(item);
- var node = this.buckets[bucket];
-
- while ( node !== null && !item.equals(node.next) ) {
- node = node.next;
- }
-
- if ( node === null ) {
- // Item not in list, ergo item not in queue
- return false;
- }
- else {
- // Found item, do standard list node deletion
- node.next = node.next.next;
-
- this.size--;
- return true;
- }
-};
-
-dwv.math.BucketQueue.prototype.isEmpty = function() {
- return this.size === 0;
-};
-
-dwv.math.BucketQueue.prototype.getBucket = function(item) {
- // Bucket index is the masked cost
- return this.cost(item) & this.mask;
-};
-
-dwv.math.BucketQueue.prototype.buildArray = function(newSize) {
- // Create array and initialze pointers to null
- var buckets = new Array(newSize);
-
- for ( var i = 0; i < buckets.length; i++ ) {
- buckets[i] = null;
- }
-
- return buckets;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.math = dwv.math || {};
-
-/**
- * Immutable 3x3 Matrix.
- * @constructor
- */
-dwv.math.Matrix33 = function (
- m00, m01, m02,
- m10, m11, m12,
- m20, m21, m22 )
-{
- // row-major order
- var mat = new Float32Array(9);
- mat[0] = m00; mat[1] = m01; mat[2] = m02;
- mat[3] = m10; mat[4] = m11; mat[5] = m12;
- mat[6] = m20; mat[7] = m21; mat[8] = m22;
-
- /**
- * Get a value of the matrix.
- * @param {Number} row The row at wich to get the value.
- * @param {Number} col The column at wich to get the value.
- */
- this.get = function (row, col) {
- return mat[row*3 + col];
- };
-}; // Matrix33
-
-/**
- * Check for Matrix33 equality.
- * @param {Object} rhs The other matrix to compare to.
- * @return {Boolean} True if both matrices are equal.
- */
-dwv.math.Matrix33.prototype.equals = function (rhs) {
- return this.get(0,0) === rhs.get(0,0) && this.get(0,1) === rhs.get(0,1) &&
- this.get(0,2) === rhs.get(0,2) && this.get(1,0) === rhs.get(1,0) &&
- this.get(1,1) === rhs.get(1,1) && this.get(1,2) === rhs.get(1,2) &&
- this.get(2,0) === rhs.get(2,0) && this.get(2,1) === rhs.get(2,1) &&
- this.get(2,2) === rhs.get(2,2);
-};
-
-/**
- * Get a string representation of the Matrix33.
- * @return {String} The matrix as a string.
- */
-dwv.math.Matrix33.prototype.toString = function () {
- return "[" + this.get(0,0) + ", " + this.get(0,1) + ", " + this.get(0,2) +
- "\n " + this.get(1,0) + ", " + this.get(1,1) + ", " + this.get(1,2) +
- "\n " + this.get(2,0) + ", " + this.get(2,1) + ", " + this.get(2,2) + "]";
-};
-
-/**
- * Multiply this matrix by a 3D vector.
- * @param {Object} vector3D The input 3D vector
- * @return {Object} The result 3D vector
- */
-dwv.math.Matrix33.multiplyVector3D = function (vector3D) {
- // cache matrix values
- var m00 = this.get(0,0); var m01 = this.get(0,1); var m02 = this.get(0,2);
- var m10 = this.get(1,0); var m11 = this.get(1,1); var m12 = this.get(1,2);
- var m20 = this.get(2,0); var m21 = this.get(2,1); var m22 = this.get(2,2);
- // cache vector values
- var vx = vector3D.getX();
- var vy = vector3D.getY();
- var vz = vector3D.getZ();
- // calculate
- return new dwv.math.Vector3D(
- (m00 * vx) + (m01 * vy) + (m02 * vz),
- (m10 * vx) + (m11 * vy) + (m12 * vz),
- (m20 * vx) + (m21 * vy) + (m22 * vz) );
-};
-
-/**
- * Create a 3x3 identity matrix.
- * @return {Object} The identity matrix.
- */
-dwv.math.getIdentityMat33= function () {
- return new dwv.math.Matrix33(
- 1, 0, 0,
- 0, 1, 0,
- 0, 0, 1 );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.math = dwv.math || {};
-
-/**
- * Immutable 2D point.
- * @constructor
- * @param {Number} x The X coordinate for the point.
- * @param {Number} y The Y coordinate for the point.
- */
-dwv.math.Point2D = function (x,y)
-{
- /**
- * Get the X position of the point.
- * @return {Number} The X position of the point.
- */
- this.getX = function () { return x; };
- /**
- * Get the Y position of the point.
- * @return {Number} The Y position of the point.
- */
- this.getY = function () { return y; };
-}; // Point2D class
-
-/**
- * Check for Point2D equality.
- * @param {Object} rhs The other point to compare to.
- * @return {Boolean} True if both points are equal.
- */
-dwv.math.Point2D.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getX() === rhs.getX() &&
- this.getY() === rhs.getY();
-};
-
-/**
- * Get a string representation of the Point2D.
- * @return {String} The point as a string.
- */
-dwv.math.Point2D.prototype.toString = function () {
- return "(" + this.getX() + ", " + this.getY() + ")";
-};
-
-/**
- * Get the distance to another Point2D.
- * @param {Object} point2D The input point.
- */
-dwv.math.Point2D.prototype.getDistance = function (point2D) {
- return Math.sqrt(
- (this.getX() - point2D.getX()) * (this.getX() - point2D.getX()) +
- (this.getY() - point2D.getY()) * (this.getY() - point2D.getY()) );
-};
-
-/**
- * Mutable 2D point.
- * @constructor
- * @param {Number} x The X coordinate for the point.
- * @param {Number} y The Y coordinate for the point.
- */
-dwv.math.FastPoint2D = function (x,y)
-{
- this.x = x;
- this.y = y;
-}; // FastPoint2D class
-
-/**
- * Check for FastPoint2D equality.
- * @param {Object} other The other point to compare to.
- * @return {Boolean} True if both points are equal.
- */
-dwv.math.FastPoint2D.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.x === rhs.x &&
- this.y === rhs.y;
-};
-
-/**
- * Get a string representation of the FastPoint2D.
- * @return {String} The point as a string.
- */
-dwv.math.FastPoint2D.prototype.toString = function () {
- return "(" + this.x + ", " + this.y + ")";
-};
-
-/**
- * Immutable 3D point.
- * @constructor
- * @param {Number} x The X coordinate for the point.
- * @param {Number} y The Y coordinate for the point.
- * @param {Number} z The Z coordinate for the point.
- */
-dwv.math.Point3D = function (x,y,z)
-{
- /**
- * Get the X position of the point.
- * @return {Number} The X position of the point.
- */
- this.getX = function () { return x; };
- /**
- * Get the Y position of the point.
- * @return {Number} The Y position of the point.
- */
- this.getY = function () { return y; };
- /**
- * Get the Z position of the point.
- * @return {Number} The Z position of the point.
- */
- this.getZ = function () { return z; };
-}; // Point3D class
-
-/**
- * Check for Point3D equality.
- * @param {Object} rhs The other point to compare to.
- * @return {Boolean} True if both points are equal.
- */
-dwv.math.Point3D.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getX() === rhs.getX() &&
- this.getY() === rhs.getY() &&
- this.getZ() === rhs.getZ();
-};
-
-/**
- * Get a string representation of the Point3D.
- * @return {String} The point as a string.
- */
-dwv.math.Point3D.prototype.toString = function () {
- return "(" + this.getX() +
- ", " + this.getY() +
- ", " + this.getZ() + ")";
-};
-
-/**
- * Get the distance to another Point3D.
- * @param {Object} point3D The input point.
- */
-dwv.math.Point3D.prototype.getDistance = function (point3D) {
- return Math.sqrt(
- (this.getX() - point3D.getX()) * (this.getX() - point3D.getX()) +
- (this.getY() - point3D.getY()) * (this.getY() - point3D.getY()) +
- (this.getZ() - point3D.getZ()) * (this.getZ() - point3D.getZ()) );
-};
-
-/**
- * Get the difference to another Point3D.
- * @param {Object} point3D The input point.
- * @return {Object} The 3D vector from the input point to this one.
- */
-dwv.math.Point3D.prototype.minus = function (point3D) {
- return new dwv.math.Vector3D(
- (this.getX() - point3D.getX()),
- (this.getY() - point3D.getY()),
- (this.getZ() - point3D.getZ()) );
-};
-
-/**
- * Immutable 3D index.
- * @constructor
- * @param {Number} i The column index.
- * @param {Number} j The row index.
- * @param {Number} k The slice index.
- */
-dwv.math.Index3D = function (i,j,k)
-{
- /**
- * Get the column index.
- * @return {Number} The column index.
- */
- this.getI = function () { return i; };
- /**
- * Get the row index.
- * @return {Number} The row index.
- */
- this.getJ = function () { return j; };
- /**
- * Get the slice index.
- * @return {Number} The slice index.
- */
- this.getK = function () { return k; };
-}; // Index3D class
-
-/**
- * Check for Index3D equality.
- * @param {Object} rhs The other index to compare to.
- * @return {Boolean} True if both indices are equal.
- */
-dwv.math.Index3D.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getI() === rhs.getI() &&
- this.getJ() === rhs.getJ() &&
- this.getK() === rhs.getK();
-};
-
-/**
- * Get a string representation of the Index3D.
- * @return {String} The Index3D as a string.
- */
-dwv.math.Index3D.prototype.toString = function () {
- return "(" + this.getI() +
- ", " + this.getJ() +
- ", " + this.getK() + ")";
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.math = dwv.math || {};
-
-// Pre-created to reduce allocation in inner loops
-var __twothirdpi = ( 2 / (3 * Math.PI) );
-
-/**
- *
- */
-dwv.math.computeGreyscale = function(data, width, height) {
- // Returns 2D augmented array containing greyscale data
- // Greyscale values found by averaging colour channels
- // Input should be in a flat RGBA array, with values between 0 and 255
- var greyscale = [];
-
- // Compute actual values
- for (var y = 0; y < height; y++) {
- greyscale[y] = [];
-
- for (var x = 0; x < width; x++) {
- var p = (y*width + x)*4;
- greyscale[y][x] = (data[p] + data[p+1] + data[p+2]) / (3*255);
- }
- }
-
- // Augment with convenience functions
- greyscale.dx = function(x,y) {
- if ( x+1 === this[y].length ) {
- // If we're at the end, back up one
- x--;
- }
- return this[y][x+1] - this[y][x];
- };
-
- greyscale.dy = function(x,y) {
- if ( y+1 === this.length ) {
- // If we're at the end, back up one
- y--;
- }
- return this[y][x] - this[y+1][x];
- };
-
- greyscale.gradMagnitude = function(x,y) {
- var dx = this.dx(x,y);
- var dy = this.dy(x,y);
- return Math.sqrt(dx*dx + dy*dy);
- };
-
- greyscale.laplace = function(x,y) {
- // Laplacian of Gaussian
- var lap = -16 * this[y][x];
- lap += this[y-2][x];
- lap += this[y-1][x-1] + 2*this[y-1][x] + this[y-1][x+1];
- lap += this[y][x-2] + 2*this[y][x-1] + 2*this[y][x+1] + this[y][x+2];
- lap += this[y+1][x-1] + 2*this[y+1][x] + this[y+1][x+1];
- lap += this[y+2][x];
-
- return lap;
- };
-
- return greyscale;
-};
-
-/**
- *
- */
-dwv.math.computeGradient = function(greyscale) {
- // Returns a 2D array of gradient magnitude values for greyscale. The values
- // are scaled between 0 and 1, and then flipped, so that it works as a cost
- // function.
- var gradient = [];
-
- var max = 0; // Maximum gradient found, for scaling purposes
-
- var x = 0;
- var y = 0;
-
- for (y = 0; y < greyscale.length-1; y++) {
- gradient[y] = [];
-
- for (x = 0; x < greyscale[y].length-1; x++) {
- gradient[y][x] = greyscale.gradMagnitude(x,y);
- max = Math.max(gradient[y][x], max);
- }
-
- gradient[y][greyscale[y].length-1] = gradient[y][greyscale.length-2];
- }
-
- gradient[greyscale.length-1] = [];
- for (var i = 0; i < gradient[0].length; i++) {
- gradient[greyscale.length-1][i] = gradient[greyscale.length-2][i];
- }
-
- // Flip and scale.
- for (y = 0; y < gradient.length; y++) {
- for (x = 0; x < gradient[y].length; x++) {
- gradient[y][x] = 1 - (gradient[y][x] / max);
- }
- }
-
- return gradient;
-};
-
-/**
- *
- */
-dwv.math.computeLaplace = function(greyscale) {
- // Returns a 2D array of Laplacian of Gaussian values
- var laplace = [];
-
- // Make the edges low cost here.
-
- laplace[0] = [];
- laplace[1] = [];
- for (var i = 1; i < greyscale.length; i++) {
- // Pad top, since we can't compute Laplacian
- laplace[0][i] = 1;
- laplace[1][i] = 1;
- }
-
- for (var y = 2; y < greyscale.length-2; y++) {
- laplace[y] = [];
- // Pad left, ditto
- laplace[y][0] = 1;
- laplace[y][1] = 1;
-
- for (var x = 2; x < greyscale[y].length-2; x++) {
- // Threshold needed to get rid of clutter.
- laplace[y][x] = (greyscale.laplace(x,y) > 0.33) ? 0 : 1;
- }
-
- // Pad right, ditto
- laplace[y][greyscale[y].length-2] = 1;
- laplace[y][greyscale[y].length-1] = 1;
- }
-
- laplace[greyscale.length-2] = [];
- laplace[greyscale.length-1] = [];
- for (var j = 1; j < greyscale.length; j++) {
- // Pad bottom, ditto
- laplace[greyscale.length-2][j] = 1;
- laplace[greyscale.length-1][j] = 1;
- }
-
- return laplace;
-};
-
-dwv.math.computeGradX = function(greyscale) {
- // Returns 2D array of x-gradient values for greyscale
- var gradX = [];
-
- for ( var y = 0; y < greyscale.length; y++ ) {
- gradX[y] = [];
-
- for ( var x = 0; x < greyscale[y].length-1; x++ ) {
- gradX[y][x] = greyscale.dx(x,y);
- }
-
- gradX[y][greyscale[y].length-1] = gradX[y][greyscale[y].length-2];
- }
-
- return gradX;
-};
-
-dwv.math.computeGradY = function(greyscale) {
- // Returns 2D array of y-gradient values for greyscale
- var gradY = [];
-
- for (var y = 0; y < greyscale.length-1; y++) {
- gradY[y] = [];
-
- for ( var x = 0; x < greyscale[y].length; x++ ) {
- gradY[y][x] = greyscale.dy(x,y);
- }
- }
-
- gradY[greyscale.length-1] = [];
- for ( var i = 0; i < greyscale[0].length; i++ ) {
- gradY[greyscale.length-1][i] = gradY[greyscale.length-2][i];
- }
-
- return gradY;
-};
-
-dwv.math.gradUnitVector = function(gradX, gradY, px, py, out) {
- // Returns the gradient vector at (px,py), scaled to a magnitude of 1
- var ox = gradX[py][px];
- var oy = gradY[py][px];
-
- var gvm = Math.sqrt(ox*ox + oy*oy);
- gvm = Math.max(gvm, 1e-100); // To avoid possible divide-by-0 errors
-
- out.x = ox / gvm;
- out.y = oy / gvm;
-};
-
-dwv.math.gradDirection = function(gradX, gradY, px, py, qx, qy) {
- var __dgpuv = new dwv.math.FastPoint2D(-1, -1);
- var __gdquv = new dwv.math.FastPoint2D(-1, -1);
- // Compute the gradiant direction, in radians, between to points
- dwv.math.gradUnitVector(gradX, gradY, px, py, __dgpuv);
- dwv.math.gradUnitVector(gradX, gradY, qx, qy, __gdquv);
-
- var dp = __dgpuv.y * (qx - px) - __dgpuv.x * (qy - py);
- var dq = __gdquv.y * (qx - px) - __gdquv.x * (qy - py);
-
- // Make sure dp is positive, to keep things consistant
- if (dp < 0) {
- dp = -dp;
- dq = -dq;
- }
-
- if ( px !== qx && py !== qy ) {
- // We're going diagonally between pixels
- dp *= Math.SQRT1_2;
- dq *= Math.SQRT1_2;
- }
-
- return __twothirdpi * (Math.acos(dp) + Math.acos(dq));
-};
-
-dwv.math.computeSides = function(dist, gradX, gradY, greyscale) {
- // Returns 2 2D arrays, containing inside and outside greyscale values.
- // These greyscale values are the intensity just a little bit along the
- // gradient vector, in either direction, from the supplied point. These
- // values are used when using active-learning Intelligent Scissors
-
- var sides = {};
- sides.inside = [];
- sides.outside = [];
-
- var guv = new dwv.math.FastPoint2D(-1, -1); // Current gradient unit vector
-
- for ( var y = 0; y < gradX.length; y++ ) {
- sides.inside[y] = [];
- sides.outside[y] = [];
-
- for ( var x = 0; x < gradX[y].length; x++ ) {
- dwv.math.gradUnitVector(gradX, gradY, x, y, guv);
-
- //(x, y) rotated 90 = (y, -x)
-
- var ix = Math.round(x + dist*guv.y);
- var iy = Math.round(y - dist*guv.x);
- var ox = Math.round(x - dist*guv.y);
- var oy = Math.round(y + dist*guv.x);
-
- ix = Math.max(Math.min(ix, gradX[y].length-1), 0);
- ox = Math.max(Math.min(ox, gradX[y].length-1), 0);
- iy = Math.max(Math.min(iy, gradX.length-1), 0);
- oy = Math.max(Math.min(oy, gradX.length-1), 0);
-
- sides.inside[y][x] = greyscale[iy][ix];
- sides.outside[y][x] = greyscale[oy][ox];
- }
- }
-
- return sides;
-};
-
-dwv.math.gaussianBlur = function(buffer, out) {
- // Smooth values over to fill in gaps in the mapping
- out[0] = 0.4*buffer[0] + 0.5*buffer[1] + 0.1*buffer[1];
- out[1] = 0.25*buffer[0] + 0.4*buffer[1] + 0.25*buffer[2] + 0.1*buffer[3];
-
- for ( var i = 2; i < buffer.length-2; i++ ) {
- out[i] = 0.05*buffer[i-2] + 0.25*buffer[i-1] + 0.4*buffer[i] + 0.25*buffer[i+1] + 0.05*buffer[i+2];
- }
-
- var len = buffer.length;
- out[len-2] = 0.25*buffer[len-1] + 0.4*buffer[len-2] + 0.25*buffer[len-3] + 0.1*buffer[len-4];
- out[len-1] = 0.4*buffer[len-1] + 0.5*buffer[len-2] + 0.1*buffer[len-3];
-};
-
-
-/**
- * Scissors
- *
- * Ref: Eric N. Mortensen, William A. Barrett, Interactive Segmentation with
- * Intelligent Scissors, Graphical Models and Image Processing, Volume 60,
- * Issue 5, September 1998, Pages 349-384, ISSN 1077-3169,
- * DOI: 10.1006/gmip.1998.0480.
- *
- * {@link http://www.sciencedirect.com/science/article/B6WG4-45JB8WN-9/2/6fe59d8089fd1892c2bfb82283065579}
- *
- * Highly inspired from {@link http://code.google.com/p/livewire-javascript/}
- * @constructor
- */
-dwv.math.Scissors = function()
-{
- this.width = -1;
- this.height = -1;
-
- this.curPoint = null; // Corrent point we're searching on.
- this.searchGranBits = 8; // Bits of resolution for BucketQueue.
- this.searchGran = 1 << this.earchGranBits; //bits.
- this.pointsPerPost = 500;
-
- // Precomputed image data. All in ranges 0 >= x >= 1 and all inverted (1 - x).
- this.greyscale = null; // Greyscale of image
- this.laplace = null; // Laplace zero-crossings (either 0 or 1).
- this.gradient = null; // Gradient magnitudes.
- this.gradX = null; // X-differences.
- this.gradY = null; // Y-differences.
-
- this.parents = null; // Matrix mapping point => parent along shortest-path to root.
-
- this.working = false; // Currently computing shortest paths?
-
- // Begin Training:
- this.trained = false;
- this.trainingPoints = null;
-
- this.edgeWidth = 2;
- this.trainingLength = 32;
-
- this.edgeGran = 256;
- this.edgeTraining = null;
-
- this.gradPointsNeeded = 32;
- this.gradGran = 1024;
- this.gradTraining = null;
-
- this.insideGran = 256;
- this.insideTraining = null;
-
- this.outsideGran = 256;
- this.outsideTraining = null;
- // End Training
-}; // Scissors class
-
-// Begin training methods //
-dwv.math.Scissors.prototype.getTrainingIdx = function(granularity, value) {
- return Math.round((granularity - 1) * value);
-};
-
-dwv.math.Scissors.prototype.getTrainedEdge = function(edge) {
- return this.edgeTraining[this.getTrainingIdx(this.edgeGran, edge)];
-};
-
-dwv.math.Scissors.prototype.getTrainedGrad = function(grad) {
- return this.gradTraining[this.getTrainingIdx(this.gradGran, grad)];
-};
-
-dwv.math.Scissors.prototype.getTrainedInside = function(inside) {
- return this.insideTraining[this.getTrainingIdx(this.insideGran, inside)];
-};
-
-dwv.math.Scissors.prototype.getTrainedOutside = function(outside) {
- return this.outsideTraining[this.getTrainingIdx(this.outsideGran, outside)];
-};
-// End training methods //
-
-dwv.math.Scissors.prototype.setWorking = function(working) {
- // Sets working flag
- this.working = working;
-};
-
-dwv.math.Scissors.prototype.setDimensions = function(width, height) {
- this.width = width;
- this.height = height;
-};
-
-dwv.math.Scissors.prototype.setData = function(data) {
- if ( this.width === -1 || this.height === -1 ) {
- // The width and height should have already been set
- throw new Error("Dimensions have not been set.");
- }
-
- this.greyscale = dwv.math.computeGreyscale(data, this.width, this.height);
- this.laplace = dwv.math.computeLaplace(this.greyscale);
- this.gradient = dwv.math.computeGradient(this.greyscale);
- this.gradX = dwv.math.computeGradX(this.greyscale);
- this.gradY = dwv.math.computeGradY(this.greyscale);
-
- var sides = dwv.math.computeSides(this.edgeWidth, this.gradX, this.gradY, this.greyscale);
- this.inside = sides.inside;
- this.outside = sides.outside;
- this.edgeTraining = [];
- this.gradTraining = [];
- this.insideTraining = [];
- this.outsideTraining = [];
-};
-
-dwv.math.Scissors.prototype.findTrainingPoints = function(p) {
- // Grab the last handful of points for training
- var points = [];
-
- if ( this.parents !== null ) {
- for ( var i = 0; i < this.trainingLength && p; i++ ) {
- points.push(p);
- p = this.parents[p.y][p.x];
- }
- }
-
- return points;
-};
-
-dwv.math.Scissors.prototype.resetTraining = function() {
- this.trained = false; // Training is ignored with this flag set
-};
-
-dwv.math.Scissors.prototype.doTraining = function(p) {
- // Compute training weights and measures
- this.trainingPoints = this.findTrainingPoints(p);
-
- if ( this.trainingPoints.length < 8 ) {
- return; // Not enough points, I think. It might crash if length = 0.
- }
-
- var buffer = [];
- this.calculateTraining(buffer, this.edgeGran, this.greyscale, this.edgeTraining);
- this.calculateTraining(buffer, this.gradGran, this.gradient, this.gradTraining);
- this.calculateTraining(buffer, this.insideGran, this.inside, this.insideTraining);
- this.calculateTraining(buffer, this.outsideGran, this.outside, this.outsideTraining);
-
- if ( this.trainingPoints.length < this.gradPointsNeeded ) {
- // If we have two few training points, the gradient weight map might not
- // be smooth enough, so average with normal weights.
- this.addInStaticGrad(this.trainingPoints.length, this.gradPointsNeeded);
- }
-
- this.trained = true;
-};
-
-dwv.math.Scissors.prototype.calculateTraining = function(buffer, granularity, input, output) {
- var i = 0;
- // Build a map of raw-weights to trained-weights by favoring input values
- buffer.length = granularity;
- for ( i = 0; i < granularity; i++ ) {
- buffer[i] = 0;
- }
-
- var maxVal = 1;
- for ( i = 0; i < this.trainingPoints.length; i++ ) {
- var p = this.trainingPoints[i];
- var idx = this.getTrainingIdx(granularity, input[p.y][p.x]);
- buffer[idx] += 1;
-
- maxVal = Math.max(maxVal, buffer[idx]);
- }
-
- // Invert and scale.
- for ( i = 0; i < granularity; i++ ) {
- buffer[i] = 1 - buffer[i] / maxVal;
- }
-
- // Blur it, as suggested. Gets rid of static.
- dwv.math.gaussianBlur(buffer, output);
-};
-
-dwv.math.Scissors.prototype.addInStaticGrad = function(have, need) {
- // Average gradient raw-weights to trained-weights map with standard weight
- // map so that we don't end up with something to spiky
- for ( var i = 0; i < this.gradGran; i++ ) {
- this.gradTraining[i] = Math.min(this.gradTraining[i], 1 - i*(need - have)/(need*this.gradGran));
- }
-};
-
-dwv.math.Scissors.prototype.gradDirection = function(px, py, qx, qy) {
- return dwv.math.gradDirection(this.gradX, this.gradY, px, py, qx, qy);
-};
-
-dwv.math.Scissors.prototype.dist = function(px, py, qx, qy) {
- // The grand culmunation of most of the code: the weighted distance function
- var grad = this.gradient[qy][qx];
-
- if ( px === qx || py === qy ) {
- // The distance is Euclidean-ish; non-diagonal edges should be shorter
- grad *= Math.SQRT1_2;
- }
-
- var lap = this.laplace[qy][qx];
- var dir = this.gradDirection(px, py, qx, qy);
-
- if ( this.trained ) {
- // Apply training magic
- var gradT = this.getTrainedGrad(grad);
- var edgeT = this.getTrainedEdge(this.greyscale[py][px]);
- var insideT = this.getTrainedInside(this.inside[py][px]);
- var outsideT = this.getTrainedOutside(this.outside[py][px]);
-
- return 0.3*gradT + 0.3*lap + 0.1*(dir + edgeT + insideT + outsideT);
- } else {
- // Normal weights
- return 0.43*grad + 0.43*lap + 0.11*dir;
- }
-};
-
-dwv.math.Scissors.prototype.adj = function(p) {
- var list = [];
-
- var sx = Math.max(p.x-1, 0);
- var sy = Math.max(p.y-1, 0);
- var ex = Math.min(p.x+1, this.greyscale[0].length-1);
- var ey = Math.min(p.y+1, this.greyscale.length-1);
-
- var idx = 0;
- for ( var y = sy; y <= ey; y++ ) {
- for ( var x = sx; x <= ex; x++ ) {
- if ( x !== p.x || y !== p.y ) {
- list[idx++] = new dwv.math.FastPoint2D(x,y);
- }
- }
- }
-
- return list;
-};
-
-dwv.math.Scissors.prototype.setPoint = function(sp) {
- this.setWorking(true);
-
- this.curPoint = sp;
-
- var x = 0;
- var y = 0;
-
- this.visited = [];
- for ( y = 0; y < this.height; y++ ) {
- this.visited[y] = [];
- for ( x = 0; x < this.width; x++ ) {
- this.visited[y][x] = false;
- }
- }
-
- this.parents = [];
- for ( y = 0; y < this.height; y++ ) {
- this.parents[y] = [];
- }
-
- this.cost = [];
- for ( y = 0; y < this.height; y++ ) {
- this.cost[y] = [];
- for ( x = 0; x < this.width; x++ ) {
- this.cost[y][x] = Number.MAX_VALUE;
- }
- }
-
- this.pq = new dwv.math.BucketQueue(this.searchGranBits, function(p) {
- return Math.round(this.searchGran * this.costArr[p.y][p.x]);
- });
- this.pq.searchGran = this.searchGran;
- this.pq.costArr = this.cost;
-
- this.pq.push(sp);
- this.cost[sp.y][sp.x] = 0;
-};
-
-dwv.math.Scissors.prototype.doWork = function() {
- if ( !this.working ) {
- return;
- }
-
- this.timeout = null;
-
- var pointCount = 0;
- var newPoints = [];
- while ( !this.pq.isEmpty() && pointCount < this.pointsPerPost ) {
- var p = this.pq.pop();
- newPoints.push(p);
- newPoints.push(this.parents[p.y][p.x]);
-
- this.visited[p.y][p.x] = true;
-
- var adjList = this.adj(p);
- for ( var i = 0; i < adjList.length; i++) {
- var q = adjList[i];
-
- var pqCost = this.cost[p.y][p.x] + this.dist(p.x, p.y, q.x, q.y);
-
- if ( pqCost < this.cost[q.y][q.x] ) {
- if ( this.cost[q.y][q.x] !== Number.MAX_VALUE ) {
- // Already in PQ, must remove it so we can re-add it.
- this.pq.remove(q);
- }
-
- this.cost[q.y][q.x] = pqCost;
- this.parents[q.y][q.x] = p;
- this.pq.push(q);
- }
- }
-
- pointCount++;
- }
-
- return newPoints;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.math = dwv.math || {};
-
-/**
- * Mulitply the three inputs if the last two are not null.
- * @param {Number} a The first input.
- * @param {Number} b The second input.
- * @param {Number} c The third input.
- * @return {Number} The multiplication of the three inputs or
- * null if one of the last two is null.
- */
-function mulABC( a, b, c) {
- var res = null;
- if (b !== null && c !== null) {
- res = a * b * c;
- }
- return res;
-}
-
-/**
- * Circle shape.
- * @constructor
- * @param {Object} centre A Point2D representing the centre of the circle.
- * @param {Number} radius The radius of the circle.
- */
-dwv.math.Circle = function(centre, radius)
-{
- /**
- * Circle surface.
- * @private
- * @type Number
- */
- var surface = Math.PI*radius*radius;
-
- /**
- * Get the centre (point) of the circle.
- * @return {Object} The center (point) of the circle.
- */
- this.getCenter = function() { return centre; };
- /**
- * Get the radius of the circle.
- * @return {Number} The radius of the circle.
- */
- this.getRadius = function() { return radius; };
- /**
- * Get the surface of the circle.
- * @return {Number} The surface of the circle.
- */
- this.getSurface = function() { return surface; };
- /**
- * Get the surface of the circle according to a spacing.
- * @param {Number} spacingX The X spacing.
- * @param {Number} spacingY The Y spacing.
- * @return {Number} The surface of the circle multiplied by the given
- * spacing or null for null spacings.
- */
- this.getWorldSurface = function(spacingX, spacingY)
- {
- return mulABC(surface, spacingX, spacingY);
- };
-}; // Circle class
-
-/**
- * Ellipse shape.
- * @constructor
- * @param {Object} centre A Point2D representing the centre of the ellipse.
- * @param {Number} a The radius of the ellipse on the horizontal axe.
- * @param {Number} b The radius of the ellipse on the vertical axe.
- */
-dwv.math.Ellipse = function(centre, a, b)
-{
- /**
- * Circle surface.
- * @private
- * @type Number
- */
- var surface = Math.PI*a*b;
-
- /**
- * Get the centre (point) of the ellipse.
- * @return {Object} The center (point) of the ellipse.
- */
- this.getCenter = function() { return centre; };
- /**
- * Get the radius of the ellipse on the horizontal axe.
- * @return {Number} The radius of the ellipse on the horizontal axe.
- */
- this.getA = function() { return a; };
- /**
- * Get the radius of the ellipse on the vertical axe.
- * @return {Number} The radius of the ellipse on the vertical axe.
- */
- this.getB = function() { return b; };
- /**
- * Get the surface of the ellipse.
- * @return {Number} The surface of the ellipse.
- */
- this.getSurface = function() { return surface; };
- /**
- * Get the surface of the ellipse according to a spacing.
- * @param {Number} spacingX The X spacing.
- * @param {Number} spacingY The Y spacing.
- * @return {Number} The surface of the ellipse multiplied by the given
- * spacing or null for null spacings.
- */
- this.getWorldSurface = function(spacingX, spacingY)
- {
- return mulABC(surface, spacingX, spacingY);
- };
-}; // Circle class
-
-/**
- * Line shape.
- * @constructor
- * @param {Object} begin A Point2D representing the beginning of the line.
- * @param {Object} end A Point2D representing the end of the line.
- */
-dwv.math.Line = function(begin, end)
-{
- /**
- * Line delta in the X direction.
- * @private
- * @type Number
- */
- var dx = end.getX() - begin.getX();
- /**
- * Line delta in the Y direction.
- * @private
- * @type Number
- */
- var dy = end.getY() - begin.getY();
- /**
- * Line length.
- * @private
- * @type Number
- */
- var length = Math.sqrt( dx * dx + dy * dy );
-
- /**
- * Get the begin point of the line.
- * @return {Object} The beginning point of the line.
- */
- this.getBegin = function() { return begin; };
- /**
- * Get the end point of the line.
- * @return {Object} The ending point of the line.
- */
- this.getEnd = function() { return end; };
- /**
- * Get the line delta in the X direction.
- * @return {Number} The delta in the X direction.
- */
- this.getDeltaX = function() { return dx; };
- /**
- * Get the line delta in the Y direction.
- * @return {Number} The delta in the Y direction.
- */
- this.getDeltaY = function() { return dy; };
- /**
- * Get the length of the line.
- * @return {Number} The length of the line.
- */
- this.getLength = function() { return length; };
- /**
- * Get the length of the line according to a spacing.
- * @param {Number} spacingX The X spacing.
- * @param {Number} spacingY The Y spacing.
- * @return {Number} The length of the line with spacing
- * or null for null spacings.
- */
- this.getWorldLength = function(spacingX, spacingY)
- {
- var wlen = null;
- if (spacingX !== null && spacingY !== null) {
- var dxs = dx * spacingX;
- var dys = dy * spacingY;
- wlen = Math.sqrt( dxs * dxs + dys * dys );
- }
- return wlen;
- };
- /**
- * Get the mid point of the line.
- * @return {Object} The mid point of the line.
- */
- this.getMidpoint = function()
- {
- return new dwv.math.Point2D(
- parseInt( (begin.getX()+end.getX()) / 2, 10 ),
- parseInt( (begin.getY()+end.getY()) / 2, 10 ) );
- };
- /**
- * Get the slope of the line.
- * @return {Number} The slope of the line.
- */
- this.getSlope = function()
- {
- return dy / dx;
- };
- /**
- * Get the intercept of the line.
- * @return {Number} The slope of the line.
- */
- this.getIntercept = function()
- {
- return (end.getX() * begin.getY() - begin.getX() * end.getY()) / dx;
- };
- /**
- * Get the inclination of the line.
- * @return {Number} The inclination of the line.
- */
- this.getInclination = function()
- {
- // tan(theta) = slope
- var angle = Math.atan2( dy, dx ) * 180 / Math.PI;
- // shift?
- return 180 - angle;
- };
-}; // Line class
-
-/**
- * Get the angle between two lines.
- * @param line0 The first line.
- * @param line1 The second line.
- */
-dwv.math.getAngle = function (line0, line1)
-{
- var dx0 = line0.getDeltaX();
- var dy0 = line0.getDeltaY();
- var dx1 = line1.getDeltaX();
- var dy1 = line1.getDeltaY();
- // dot = ||a||*||b||*cos(theta)
- var dot = dx0 * dx1 + dy0 * dy1;
- // cross = ||a||*||b||*sin(theta)
- var det = dx0 * dy1 - dy0 * dx1;
- // tan = sin / cos
- var angle = Math.atan2( det, dot ) * 180 / Math.PI;
- // complementary angle
- // shift?
- return 360 - (180 - angle);
-};
-
-/**
- * Get a perpendicular line to an input one.
- * @param {Object} line The line to be perpendicular to.
- * @param {Object} point The middle point of the perpendicular line.
- * @param {Number} length The length of the perpendicular line.
- */
-dwv.math.getPerpendicularLine = function (line, point, length)
-{
- // begin point
- var beginX = 0;
- var beginY = 0;
- // end point
- var endX = 0;
- var endY = 0;
-
- // check slope:
- // 0 -> horizontal
- // Infinite -> vertical (a/Infinite = 0)
- if ( line.getSlope() !== 0 ) {
- // a0 * a1 = -1
- var slope = -1 / line.getSlope();
- // y0 = a1*x0 + b1 -> b1 = y0 - a1*x0
- var intercept = point.getY() - slope * point.getX();
-
- // 1. (x - x0)^2 + (y - y0)^2 = d^2
- // 2. a = (y - y0) / (x - x0) -> y = a*(x - x0) + y0
- // -> (x - x0)^2 + m^2 * (x - x0)^2 = d^2
- // -> x = x0 +- d / sqrt(1+m^2)
-
- // length is the distance between begin and end,
- // point is half way between both -> d = length / 2
- var dx = length / ( 2 * Math.sqrt( 1 + slope * slope ) );
-
- // begin point
- beginX = point.getX() - dx;
- beginY = slope * beginX + intercept;
- // end point
- endX = point.getX() + dx;
- endY = slope * endX + intercept;
- }
- else {
- // horizontal input line -> perpendicular is vertical!
- // begin point
- beginX = point.getX();
- beginY = point.getY() - length / 2;
- // end point
- endX = point.getX();
- endY = point.getY() + length / 2;
- }
- // perpendicalar line
- return new dwv.math.Line(
- new dwv.math.Point2D(beginX, beginY),
- new dwv.math.Point2D(endX, endY) );
-};
-
-/**
- * Rectangle shape.
- * @constructor
- * @param {Object} begin A Point2D representing the beginning of the rectangle.
- * @param {Object} end A Point2D representing the end of the rectangle.
- */
-dwv.math.Rectangle = function(begin, end)
-{
- if ( end.getX() < begin.getX() ) {
- var tmpX = begin.getX();
- begin = new dwv.math.Point2D( end.getX(), begin.getY() );
- end = new dwv.math.Point2D( tmpX, end.getY() );
- }
- if ( end.getY() < begin.getY() ) {
- var tmpY = begin.getY();
- begin = new dwv.math.Point2D( begin.getX(), end.getY() );
- end = new dwv.math.Point2D( end.getX(), tmpY );
- }
-
- /**
- * Rectangle surface.
- * @private
- * @type Number
- */
- var surface = Math.abs(end.getX() - begin.getX()) * Math.abs(end.getY() - begin.getY() );
-
- /**
- * Get the begin point of the rectangle.
- * @return {Object} The begin point of the rectangle
- */
- this.getBegin = function() { return begin; };
- /**
- * Get the end point of the rectangle.
- * @return {Object} The end point of the rectangle
- */
- this.getEnd = function() { return end; };
- /**
- * Get the real width of the rectangle.
- * @return {Number} The real width of the rectangle.
- */
- this.getRealWidth = function() { return end.getX() - begin.getX(); };
- /**
- * Get the real height of the rectangle.
- * @return {Number} The real height of the rectangle.
- */
- this.getRealHeight = function() { return end.getY() - begin.getY(); };
- /**
- * Get the width of the rectangle.
- * @return {Number} The width of the rectangle.
- */
- this.getWidth = function() { return Math.abs(this.getRealWidth()); };
- /**
- * Get the height of the rectangle.
- * @return {Number} The height of the rectangle.
- */
- this.getHeight = function() { return Math.abs(this.getRealHeight()); };
- /**
- * Get the surface of the rectangle.
- * @return {Number} The surface of the rectangle.
- */
- this.getSurface = function() { return surface; };
- /**
- * Get the surface of the circle according to a spacing.
- * @param {Number} spacingX The X spacing.
- * @param {Number} spacingY The Y spacing.
- * @return {Number} The surface of the rectangle multiplied by the given
- * spacing or null for null spacings.
- */
- this.getWorldSurface = function(spacingX, spacingY)
- {
- return mulABC(surface, spacingX, spacingY);
- };
-}; // Rectangle class
-
-/**
- * Region Of Interest shape.
- * Note: should be a closed path.
- * @constructor
- */
-dwv.math.ROI = function()
-{
- /**
- * List of points.
- * @private
- * @type Array
- */
- var points = [];
-
- /**
- * Get a point of the list at a given index.
- * @param {Number} index The index of the point to get (beware, no size check).
- * @return {Object} The Point2D at the given index.
- */
- this.getPoint = function(index) { return points[index]; };
- /**
- * Get the length of the point list.
- * @return {Number} The length of the point list.
- */
- this.getLength = function() { return points.length; };
- /**
- * Add a point to the ROI.
- * @param {Object} point The Point2D to add.
- */
- this.addPoint = function(point) { points.push(point); };
- /**
- * Add points to the ROI.
- * @param {Array} rhs The array of POints2D to add.
- */
- this.addPoints = function(rhs) { points=points.concat(rhs);};
-}; // ROI class
-
-/**
- * Path shape.
- * @constructor
- * @param {Array} inputPointArray The list of Point2D that make the path (optional).
- * @param {Array} inputControlPointIndexArray The list of control point of path,
- * as indexes (optional).
- * Note: first and last point do not need to be equal.
- */
-dwv.math.Path = function(inputPointArray, inputControlPointIndexArray)
-{
- /**
- * List of points.
- * @type Array
- */
- this.pointArray = inputPointArray ? inputPointArray.slice() : [];
- /**
- * List of control points.
- * @type Array
- */
- this.controlPointIndexArray = inputControlPointIndexArray ?
- inputControlPointIndexArray.slice() : [];
-}; // Path class
-
-/**
- * Get a point of the list.
- * @param {Number} index The index of the point to get (beware, no size check).
- * @return {Object} The Point2D at the given index.
- */
-dwv.math.Path.prototype.getPoint = function(index) {
- return this.pointArray[index];
-};
-
-/**
- * Is the given point a control point.
- * @param {Object} point The Point2D to check.
- * @return {Boolean} True if a control point.
- */
-dwv.math.Path.prototype.isControlPoint = function(point) {
- var index = this.pointArray.indexOf(point);
- if( index !== -1 ) {
- return this.controlPointIndexArray.indexOf(index) !== -1;
- }
- else {
- throw new Error("Error: isControlPoint called with not in list point.");
- }
-};
-
-/**
- * Get the length of the path.
- * @return {Number} The length of the path.
- */
-dwv.math.Path.prototype.getLength = function() {
- return this.pointArray.length;
-};
-
-/**
- * Add a point to the path.
- * @param {Object} point The Point2D to add.
- */
-dwv.math.Path.prototype.addPoint = function(point) {
- this.pointArray.push(point);
-};
-
-/**
- * Add a control point to the path.
- * @param {Object} point The Point2D to make a control point.
- */
-dwv.math.Path.prototype.addControlPoint = function(point) {
- var index = this.pointArray.indexOf(point);
- if( index !== -1 ) {
- this.controlPointIndexArray.push(index);
- }
- else {
- throw new Error("Error: addControlPoint called with no point in list point.");
- }
-};
-
-/**
- * Add points to the path.
- * @param {Array} points The list of Point2D to add.
- */
-dwv.math.Path.prototype.addPoints = function(newPointArray) {
- this.pointArray = this.pointArray.concat(newPointArray);
-};
-
-/**
- * Append a Path to this one.
- * @param {Path} other The Path to append.
- */
-dwv.math.Path.prototype.appenPath = function(other) {
- var oldSize = this.pointArray.length;
- this.pointArray = this.pointArray.concat(other.pointArray);
- var indexArray = [];
- for( var i=0; i < other.controlPointIndexArray.length; ++i ) {
- indexArray[i] = other.controlPointIndexArray[i] + oldSize;
- }
- this.controlPointIndexArray = this.controlPointIndexArray.concat(indexArray);
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.math = dwv.math || {};
-
-/**
- * Get the minimum, maximum, mean and standard deviation
- * of an array of values.
- * Note: could use {@link https://github.com/tmcw/simple-statistics}.
- */
-dwv.math.getStats = function (array)
-{
- var min = array[0];
- var max = min;
- var mean = 0;
- var sum = 0;
- var sumSqr = 0;
- var stdDev = 0;
- var variance = 0;
-
- var val = 0;
- for ( var i = 0; i < array.length; ++i ) {
- val = array[i];
- if ( val < min ) {
- min = val;
- }
- else if ( val > max ) {
- max = val;
- }
- sum += val;
- sumSqr += val * val;
- }
-
- mean = sum / array.length;
- // see http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
- variance = sumSqr / array.length - mean * mean;
- stdDev = Math.sqrt(variance);
-
- return { 'min': min, 'max': max, 'mean': mean, 'stdDev': stdDev };
-};
-
-/**
- * Unique ID generator.
- * See {@link http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript}
- * and this {@link http://stackoverflow.com/a/13403498 answer}.
- */
-dwv.math.guid = function ()
-{
- return Math.random().toString(36).substring(2, 15);
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.math = dwv.math || {};
-
-/**
- * Immutable 3D vector.
- * @constructor
- * @param {Number} x The X component of the vector.
- * @param {Number} y The Y component of the vector.
- * @param {Number} z The Z component of the vector.
- */
-dwv.math.Vector3D = function (x,y,z)
-{
- /**
- * Get the X component of the vector.
- * @return {Number} The X component of the vector.
- */
- this.getX = function () { return x; };
- /**
- * Get the Y component of the vector.
- * @return {Number} The Y component of the vector.
- */
- this.getY = function () { return y; };
- /**
- * Get the Z component of the vector.
- * @return {Number} The Z component of the vector.
- */
- this.getZ = function () { return z; };
-}; // Vector3D class
-
-/**
- * Check for Vector3D equality.
- * @param {Object} rhs The other vector to compare to.
- * @return {Boolean} True if both vectors are equal.
- */
-dwv.math.Vector3D.prototype.equals = function (rhs) {
- return rhs !== null &&
- this.getX() === rhs.getX() &&
- this.getY() === rhs.getY() &&
- this.getZ() === rhs.getZ();
-};
-
-/**
- * Get a string representation of the Vector3D.
- * @return {String} The vector as a string.
- */
-dwv.math.Vector3D.prototype.toString = function () {
- return "(" + this.getX() +
- ", " + this.getY() +
- ", " + this.getZ() + ")";
-};
-
-/**
- * Get the norm of the vector.
- * @return {Number} The norm.
- */
-dwv.math.Vector3D.prototype.norm = function () {
- return Math.sqrt( (this.getX() * this.getX()) +
- (this.getY() * this.getY()) +
- (this.getZ() * this.getZ()) );
-};
-
-/**
- * Get the cross product with another Vector3D, ie the
- * vector that is perpendicular to both a and b.
- * If both vectors are parallel, the cross product is a zero vector.
- * @param {Object} vector3D The input vector.
- * @return {Object} The result vector.
- */
-dwv.math.Vector3D.prototype.crossProduct = function (vector3D) {
- return new dwv.math.Vector3D(
- (this.getY() * vector3D.getZ()) - (vector3D.getY() * this.getZ()),
- (this.getZ() * vector3D.getX()) - (vector3D.getZ() * this.getX()),
- (this.getX() * vector3D.getY()) - (vector3D.getX() * this.getY()) );
-};
-
-/**
- * Get the dot product with another Vector3D.
- * @param {Object} vector3D The input vector.
- * @return {Number} The dot product.
- */
-dwv.math.Vector3D.prototype.dotProduct = function (vector3D) {
- return (this.getX() * vector3D.getX()) +
- (this.getY() * vector3D.getY()) +
- (this.getZ() * vector3D.getZ());
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Arrow factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.ArrowFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 2; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 0; };
-};
-
-/**
- * Create an arrow shape to be displayed.
- * @param {Array} points The points from which to extract the line.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.ArrowFactory.prototype.create = function (points, style/*, image*/)
-{
- // physical shape
- var line = new dwv.math.Line(points[0], points[1]);
- // draw shape
- var kshape = new Konva.Line({
- points: [line.getBegin().getX(), line.getBegin().getY(),
- line.getEnd().getX(), line.getEnd().getY() ],
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape"
- });
- // larger hitfunc
- var linePerp0 = dwv.math.getPerpendicularLine( line, points[0], 10 );
- var linePerp1 = dwv.math.getPerpendicularLine( line, points[1], 10 );
- kshape.hitFunc( function (context) {
- context.beginPath();
- context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
- context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
- context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
- context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
- context.closePath();
- context.fillStrokeShape(this);
- });
- // triangle
- var beginTy = new dwv.math.Point2D(line.getBegin().getX(), line.getBegin().getY() - 10);
- var verticalLine = new dwv.math.Line(line.getBegin(), beginTy);
- var angle = dwv.math.getAngle(line, verticalLine);
- var angleRad = angle * Math.PI / 180;
- var radius = 5;
- var kpoly = new Konva.RegularPolygon({
- x: line.getBegin().getX() + radius * Math.sin(angleRad),
- y: line.getBegin().getY() + radius * Math.cos(angleRad),
- sides: 3,
- radius: radius,
- rotation: -angle,
- fill: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape-triangle"
- });
- // quantification
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "";
- ktext.longText = "";
- ktext.quant = null;
- ktext.setText(ktext.textExpr);
- // label
- var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
- var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
- var klabel = new Konva.Label({
- x: line.getEnd().getX() + dX * 25,
- y: line.getEnd().getY() + dY * 15,
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // return group
- var group = new Konva.Group();
- group.name("line-group");
- group.add(kshape);
- group.add(kpoly);
- group.add(klabel);
- group.visible(true); // dont inherit
- return group;
-};
-
-/**
- * Update an arrow shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateArrow = function (anchor/*, image*/)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var kline = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated triangle shape
- var ktriangle = group.getChildren( function (node) {
- return node.name() === 'shape-triangle';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
- // find special points
- var begin = group.getChildren( function (node) {
- return node.id() === 'begin';
- })[0];
- var end = group.getChildren( function (node) {
- return node.id() === 'end';
- })[0];
- // update special points
- switch ( anchor.id() ) {
- case 'begin':
- begin.x( anchor.x() );
- begin.y( anchor.y() );
- break;
- case 'end':
- end.x( anchor.x() );
- end.y( anchor.y() );
- break;
- }
- // update shape and compensate for possible drag
- // note: shape.position() and shape.size() won't work...
- var bx = begin.x() - kline.x();
- var by = begin.y() - kline.y();
- var ex = end.x() - kline.x();
- var ey = end.y() - kline.y();
- kline.points( [bx,by,ex,ey] );
- // new line
- var p2d0 = new dwv.math.Point2D(begin.x(), begin.y());
- var p2d1 = new dwv.math.Point2D(end.x(), end.y());
- var line = new dwv.math.Line(p2d0, p2d1);
- // larger hitfunc
- var p2b = new dwv.math.Point2D(bx, by);
- var p2e = new dwv.math.Point2D(ex, ey);
- var linePerp0 = dwv.math.getPerpendicularLine( line, p2b, 10 );
- var linePerp1 = dwv.math.getPerpendicularLine( line, p2e, 10 );
- kline.hitFunc( function (context) {
- context.beginPath();
- context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
- context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
- context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
- context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
- context.closePath();
- context.fillStrokeShape(this);
- });
- // udate triangle
- var beginTy = new dwv.math.Point2D(line.getBegin().getX(), line.getBegin().getY() - 10);
- var verticalLine = new dwv.math.Line(line.getBegin(), beginTy);
- var angle = dwv.math.getAngle(line, verticalLine);
- var angleRad = angle * Math.PI / 180;
- ktriangle.x(line.getBegin().getX() + ktriangle.radius() * Math.sin(angleRad));
- ktriangle.y(line.getBegin().getY() + ktriangle.radius() * Math.cos(angleRad));
- ktriangle.rotation(-angle);
- // update text
- var ktext = klabel.getText();
- ktext.quant = null;
- ktext.setText(ktext.textExpr);
- // update position
- var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
- var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
- var textPos = {
- 'x': line.getEnd().getX() + dX * 25,
- 'y': line.getEnd().getY() + dY * 15 };
- klabel.position( textPos );
-};
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Drawing tool.
- *
- * This tool is responsible for the draw layer group structure. The layout is:
- *
- * drawLayer
- * |_ positionGroup: name="position-group", id="slice-#_frame-#""
- * |_ shapeGroup: name="{shape name}-group", id="#"
- * |_ shape: name="shape"
- * |_ label: name="label"
- * |_ extra: line tick, protractor arc...
- *
- * Discussion:
- * - posGroup > shapeGroup
- * pro: slice/frame display: 1 loop
- * cons: multi-slice shape splitted in positionGroups
- * - shapeGroup > posGroup
- * pros: more logical
- * cons: slice/frame display: 2 loops
- *
- * @constructor
- * @param {Object} app The associated application.
- * @external Konva
- */
-dwv.tool.Draw = function (app, shapeFactoryList)
-{
- /**
- * Closure to self: to be used by event handlers.
- * @private
- * @type WindowLevel
- */
- var self = this;
- /**
- * Draw GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Interaction start flag.
- * @private
- * @type Boolean
- */
- var started = false;
-
- /**
- * Shape factory list
- * @type Object
- */
- this.shapeFactoryList = shapeFactoryList;
- /**
- * Draw command.
- * @private
- * @type Object
- */
- var command = null;
- /**
- * Current shape group.
- * @private
- * @type Object
- */
- var tmpShapeGroup = null;
-
- /**
- * Shape name.
- * @type String
- */
- this.shapeName = 0;
-
- /**
- * List of points.
- * @private
- * @type Array
- */
- var points = [];
-
- /**
- * Last selected point.
- * @private
- * @type Object
- */
- var lastPoint = null;
-
- /**
- * Shape editor.
- * @private
- * @type Object
- */
- var shapeEditor = new dwv.tool.ShapeEditor(app);
-
- // associate the event listeners of the editor
- // with those of the draw tool
- shapeEditor.setDrawEventCallback(fireEvent);
-
- /**
- * Trash draw: a cross.
- * @private
- * @type Object
- */
- var trash = new Konva.Group();
-
- // first line of the cross
- var trashLine1 = new Konva.Line({
- points: [-10, -10, 10, 10 ],
- stroke: 'red'
- });
- // second line of the cross
- var trashLine2 = new Konva.Line({
- points: [10, -10, -10, 10 ],
- stroke: 'red'
- });
- trash.add(trashLine1);
- trash.add(trashLine2);
-
- /**
- * Drawing style.
- * @type Style
- */
- this.style = new dwv.html.Style();
-
- /**
- * Event listeners.
- * @private
- */
- var listeners = {};
-
- /**
- * The associated draw layer.
- * @private
- * @type Object
- */
- var drawLayer = null;
-
- /**
- * Handle mouse down event.
- * @param {Object} event The mouse down event.
- */
- this.mousedown = function(event){
- // determine if the click happened in an existing shape
- var stage = app.getDrawStage();
- var kshape = stage.getIntersection({
- x: event._xs,
- y: event._ys
- });
-
- if ( kshape ) {
- var group = kshape.getParent();
- var selectedShape = group.find(".shape")[0];
- // reset editor if click on other shape
- // (and avoid anchors mouse down)
- if ( selectedShape && selectedShape !== shapeEditor.getShape() ) {
- shapeEditor.disable();
- shapeEditor.setShape(selectedShape);
- shapeEditor.setImage(app.getImage());
- shapeEditor.enable();
- }
- }
- else {
- // disable edition
- shapeEditor.disable();
- shapeEditor.setShape(null);
- shapeEditor.setImage(null);
- // start storing points
- started = true;
- // clear array
- points = [];
- // store point
- lastPoint = new dwv.math.Point2D(event._x, event._y);
- points.push(lastPoint);
- }
- };
-
- /**
- * Handle mouse move event.
- * @param {Object} event The mouse move event.
- */
- this.mousemove = function(event){
- if (!started)
- {
- return;
- }
- if ( Math.abs( event._x - lastPoint.getX() ) > 0 ||
- Math.abs( event._y - lastPoint.getY() ) > 0 )
- {
- // current point
- lastPoint = new dwv.math.Point2D(event._x, event._y);
- // clear last added point from the list (but not the first one)
- if ( points.length != 1 ) {
- points.pop();
- }
- // add current one to the list
- points.push( lastPoint );
- // allow for anchor points
- var factory = new self.shapeFactoryList[self.shapeName]();
- if( points.length < factory.getNPoints() ) {
- clearTimeout(this.timer);
- this.timer = setTimeout( function () {
- points.push( lastPoint );
- }, factory.getTimeout() );
- }
- // remove previous draw
- if ( tmpShapeGroup ) {
- tmpShapeGroup.destroy();
- }
- // create shape group
- tmpShapeGroup = factory.create(points, self.style, app.getImage());
- // do not listen during creation
- var shape = tmpShapeGroup.getChildren( dwv.draw.isNodeNameShape )[0];
- shape.listening(false);
- drawLayer.hitGraphEnabled(false);
- // draw shape
- drawLayer.add(tmpShapeGroup);
- drawLayer.draw();
- }
- };
-
- /**
- * Handle mouse up event.
- * @param {Object} event The mouse up event.
- */
- this.mouseup = function (/*event*/){
- if (started && points.length > 1 )
- {
- // reset shape group
- if ( tmpShapeGroup ) {
- tmpShapeGroup.destroy();
- }
- // create final shape
- var factory = new self.shapeFactoryList[self.shapeName]();
- var finalShapeGroup = factory.create(points, self.style, app.getImage());
- finalShapeGroup.id( dwv.math.guid() );
-
- // get the position group
- var posGroup = app.getDrawController().getCurrentPosGroup();
- // add shape group to position group
- posGroup.add(finalShapeGroup);
-
- // re-activate layer
- drawLayer.hitGraphEnabled(true);
- // draw shape command
- command = new dwv.tool.DrawGroupCommand(finalShapeGroup, self.shapeName, drawLayer);
- command.onExecute = fireEvent;
- command.onUndo = fireEvent;
- // execute it
- command.execute();
- // save it in undo stack
- app.addToUndoStack(command);
-
- // activate shape listeners
- self.setShapeOn( finalShapeGroup );
- }
- // reset flag
- started = false;
- };
-
- /**
- * Handle mouse out event.
- * @param {Object} event The mouse out event.
- */
- this.mouseout = function(event){
- self.mouseup(event);
- };
-
- /**
- * Handle touch start event.
- * @param {Object} event The touch start event.
- */
- this.touchstart = function(event){
- self.mousedown(event);
- };
-
- /**
- * Handle touch move event.
- * @param {Object} event The touch move event.
- */
- this.touchmove = function(event){
- self.mousemove(event);
- };
-
- /**
- * Handle touch end event.
- * @param {Object} event The touch end event.
- */
- this.touchend = function(event){
- self.mouseup(event);
- };
-
- /**
- * Handle key down event.
- * @param {Object} event The key down event.
- */
- this.keydown = function(event){
- app.onKeydown(event);
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui = new dwv.gui.Draw(app);
- gui.setup(this.shapeFactoryList);
- };
-
- /**
- * Enable the tool.
- * @param {Boolean} flag The flag to enable or not.
- */
- this.display = function ( flag ){
- if ( gui ) {
- gui.display( flag );
- }
- // reset shape display properties
- shapeEditor.disable();
- shapeEditor.setShape(null);
- shapeEditor.setImage(null);
- document.body.style.cursor = 'default';
- // make layer listen or not to events
- app.getDrawStage().listening( flag );
- // get the current draw layer
- drawLayer = app.getDrawController().getDrawLayer();
- renderDrawLayer(flag);
- // listen to app change to update the draw layer
- if (flag) {
- app.addEventListener("slice-change", updateDrawLayer);
- app.addEventListener("frame-change", updateDrawLayer);
- }
- else {
- app.removeEventListener("slice-change", updateDrawLayer);
- app.removeEventListener("frame-change", updateDrawLayer);
- }
- };
-
- /**
- * Update the draw layer.
- */
- function updateDrawLayer() {
- // activate the draw layer
- renderDrawLayer(true);
- }
-
- /**
- * Render (or not) the draw layer.
- * @param {Boolean} visible Set the draw layer visible or not.
- */
- function renderDrawLayer(visible) {
-
- drawLayer.listening( visible );
- drawLayer.hitGraphEnabled( visible );
-
- // get shape groups at the current position
- var shapeGroups = app.getDrawController().getCurrentPosGroup().getChildren();
-
- // set shape display properties
- if ( visible ) {
- // activate tool listeners
- app.addToolCanvasListeners( app.getDrawStage().getContent() );
- // activate shape listeners
- shapeGroups.forEach( function (group) { self.setShapeOn( group ); });
- }
- else {
- // de-activate tool listeners
- app.removeToolCanvasListeners( app.getDrawStage().getContent() );
- // de-activate shape listeners
- shapeGroups.forEach( function (group) { setShapeOff( group ); });
- }
- // draw
- drawLayer.draw();
- }
-
- /**
- * Set shape group off properties.
- * @param {Object} shapeGroup The shape group to set off.
- */
- function setShapeOff( shapeGroup ) {
- // mouse styling
- shapeGroup.off('mouseover');
- shapeGroup.off('mouseout');
- // drag
- shapeGroup.draggable(false);
- shapeGroup.off('dragstart.draw');
- shapeGroup.off('dragmove.draw');
- shapeGroup.off('dragend.draw');
- shapeGroup.off('dblclick');
- }
-
- /**
- * Get the real position from an event.
- */
- function getRealPosition( index ) {
- var stage = app.getDrawStage();
- return { 'x': stage.offset().x + index.x / stage.scale().x,
- 'y': stage.offset().y + index.y / stage.scale().y };
- }
-
- /**
- * Set shape group on properties.
- * @param {Object} shapeGroup The shape group to set on.
- */
- this.setShapeOn = function ( shapeGroup ) {
- // mouse over styling
- shapeGroup.on('mouseover', function () {
- document.body.style.cursor = 'pointer';
- });
- // mouse out styling
- shapeGroup.on('mouseout', function () {
- document.body.style.cursor = 'default';
- });
-
- // make it draggable
- shapeGroup.draggable(true);
- // cache drag start position
- var dragStartPos = {'x': shapeGroup.x(), 'y': shapeGroup.y()};
-
- // command name based on shape type
- var shapeDisplayName = dwv.tool.GetShapeDisplayName(
- shapeGroup.getChildren( dwv.draw.isNodeNameShape )[0]);
-
- var colour = null;
-
- // drag start event handling
- shapeGroup.on('dragstart.draw', function (/*event*/) {
- // store colour
- colour = shapeGroup.getChildren( dwv.draw.isNodeNameShape )[0].stroke();
- // display trash
- var stage = app.getDrawStage();
- var scale = stage.scale();
- var invscale = {'x': 1/scale.x, 'y': 1/scale.y};
- trash.x( stage.offset().x + ( 256 / scale.x ) );
- trash.y( stage.offset().y + ( 20 / scale.y ) );
- trash.scale( invscale );
- drawLayer.add( trash );
- // deactivate anchors to avoid events on null shape
- shapeEditor.setAnchorsActive(false);
- // draw
- drawLayer.draw();
- });
- // drag move event handling
- shapeGroup.on('dragmove.draw', function (event) {
- // highlight trash when on it
- var offset = dwv.html.getEventOffset( event.evt )[0];
- var eventPos = getRealPosition( offset );
- if ( Math.abs( eventPos.x - trash.x() ) < 10 &&
- Math.abs( eventPos.y - trash.y() ) < 10 ) {
- trash.getChildren().each( function (tshape){ tshape.stroke('orange'); });
- // change the group shapes colour
- shapeGroup.getChildren( dwv.draw.canNodeChangeColour ).forEach(
- function (ashape) { ashape.stroke( 'red' ); });
- }
- else {
- trash.getChildren().each( function (tshape){ tshape.stroke('red'); });
- // reset the group shapes colour
- shapeGroup.getChildren( dwv.draw.canNodeChangeColour ).forEach(
- function (ashape) { ashape.stroke( colour ); });
- }
- // draw
- drawLayer.draw();
- });
- // drag end event handling
- shapeGroup.on('dragend.draw', function (event) {
- var pos = {'x': this.x(), 'y': this.y()};
- // remove trash
- trash.remove();
- // delete case
- var offset = dwv.html.getEventOffset( event.evt )[0];
- var eventPos = getRealPosition( offset );
- if ( Math.abs( eventPos.x - trash.x() ) < 10 &&
- Math.abs( eventPos.y - trash.y() ) < 10 ) {
- // compensate for the drag translation
- this.x( dragStartPos.x );
- this.y( dragStartPos.y );
- // disable editor
- shapeEditor.disable();
- shapeEditor.setShape(null);
- shapeEditor.setImage(null);
- // reset colour
- shapeGroup.getChildren( dwv.draw.canNodeChangeColour ).forEach(
- function (ashape) { ashape.stroke( colour ); });
- // reset cursor
- document.body.style.cursor = 'default';
- // delete command
- var delcmd = new dwv.tool.DeleteGroupCommand(this,
- shapeDisplayName, drawLayer);
- delcmd.onExecute = fireEvent;
- delcmd.onUndo = fireEvent;
- delcmd.execute();
- app.addToUndoStack(delcmd);
- }
- else {
- // save drag move
- var translation = {'x': pos.x - dragStartPos.x,
- 'y': pos.y - dragStartPos.y};
- if ( translation.x !== 0 || translation.y !== 0 ) {
- var mvcmd = new dwv.tool.MoveGroupCommand(this,
- shapeDisplayName, translation, drawLayer);
- mvcmd.onExecute = fireEvent;
- mvcmd.onUndo = fireEvent;
- app.addToUndoStack(mvcmd);
-
- // the move is handled by Konva, trigger an event manually
- fireEvent({'type': 'draw-move'});
- }
- // reset anchors
- shapeEditor.setAnchorsActive(true);
- shapeEditor.resetAnchors();
- }
- // draw
- drawLayer.draw();
- // reset start position
- dragStartPos = {'x': this.x(), 'y': this.y()};
- });
- // double click handling: update label
- shapeGroup.on('dblclick', function () {
-
- // get the label object for this shape
- var label = this.findOne('Label');
- // should just be one
- if ( typeof label === "undefined" ) {
- throw new Error("Could not find the shape label.");
- }
- var ktext = label.getText();
-
- // ask user for new label
- var labelText = dwv.gui.prompt("Shape label", ktext.textExpr);
-
- // if press cancel do nothing
- if (labelText === null) {
- return;
- }
- else if (labelText === ktext.textExpr) {
- return;
- }
- // update text expression and set text
- ktext.textExpr = labelText;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
-
- // trigger event
- fireEvent({'type': 'draw-change'});
-
- // draw
- drawLayer.draw();
- });
- };
-
- /**
- * Initialise the tool.
- */
- this.init = function() {
- // set the default to the first in the list
- var shapeName = 0;
- for( var key in this.shapeFactoryList ){
- shapeName = key;
- break;
- }
- this.setShapeName(shapeName);
- // init gui
- if ( gui ) {
- // init with the app window scale
- this.style.setScale(app.getWindowScale());
- // same for colour
- this.setLineColour(this.style.getLineColour());
- // init html
- gui.initialise();
- }
- return true;
- };
-
- /**
- * Add an event listener on the app.
- * @param {String} type The event type.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.addEventListener = function (type, listener)
- {
- if ( typeof listeners[type] === "undefined" ) {
- listeners[type] = [];
- }
- listeners[type].push(listener);
- };
-
- /**
- * Remove an event listener from the app.
- * @param {String} type The event type.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.removeEventListener = function (type, listener)
- {
- if( typeof listeners[type] === "undefined" ) {
- return;
- }
- for ( var i = 0; i < listeners[type].length; ++i )
- {
- if ( listeners[type][i] === listener ) {
- listeners[type].splice(i,1);
- }
- }
- };
-
- /**
- * Set the line colour of the drawing.
- * @param {String} colour The colour to set.
- */
- this.setLineColour = function (colour)
- {
- this.style.setLineColour(colour);
- };
-
- // Private Methods -----------------------------------------------------------
-
- /**
- * Fire an event: call all associated listeners.
- * @param {Object} event The event to fire.
- */
- function fireEvent (event)
- {
- if ( typeof listeners[event.type] === "undefined" ) {
- return;
- }
- for ( var i=0; i < listeners[event.type].length; ++i )
- {
- listeners[event.type][i](event);
- }
- }
-
-}; // Draw class
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.Draw.prototype.getHelp = function()
-{
- return {
- "title": dwv.i18n("tool.Draw.name"),
- "brief": dwv.i18n("tool.Draw.brief"),
- "mouse": {
- "mouse_drag": dwv.i18n("tool.Draw.mouse_drag")
- },
- "touch": {
- "touch_drag": dwv.i18n("tool.Draw.touch_drag")
- }
- };
-};
-
-/**
- * Set the shape name of the drawing.
- * @param {String} name The name of the shape.
- */
-dwv.tool.Draw.prototype.setShapeName = function(name)
-{
- // check if we have it
- if( !this.hasShape(name) )
- {
- throw new Error("Unknown shape: '" + name + "'");
- }
- this.shapeName = name;
-};
-
-/**
- * Check if the shape is in the shape list.
- * @param {String} name The name of the shape.
- */
-dwv.tool.Draw.prototype.hasShape = function(name) {
- return this.shapeFactoryList[name];
-};
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Get the display name of the input shape.
- * @param {Object} shape The Konva shape.
- * @return {String} The display name.
- * @external Konva
- */
-dwv.tool.GetShapeDisplayName = function (shape)
-{
- var displayName = "shape";
- if ( shape instanceof Konva.Line ) {
- if ( shape.points().length === 4 ) {
- displayName = "line";
- }
- else if ( shape.points().length === 6 ) {
- displayName = "protractor";
- }
- else {
- displayName = "roi";
- }
- }
- else if ( shape instanceof Konva.Rect ) {
- displayName = "rectangle";
- }
- else if ( shape instanceof Konva.Ellipse ) {
- displayName = "ellipse";
- }
- // return
- return displayName;
-};
-
-/**
- * Draw group command.
- * @param {Object} group The group draw.
- * @param {String} name The shape display name.
- * @param {Object} layer The layer where to draw the group.
- * @param {Object} silent Whether to send a creation event or not.
- * @constructor
- */
-dwv.tool.DrawGroupCommand = function (group, name, layer, silent)
-{
- var isSilent = (typeof silent === "undefined") ? false : true;
-
- // group parent
- var parent = group.getParent();
-
- /**
- * Get the command name.
- * @return {String} The command name.
- */
- this.getName = function () { return "Draw-"+name; };
- /**
- * Execute the command.
- */
- this.execute = function () {
- // add the group to the parent (in case of undo/redo)
- parent.add(group);
- // draw
- layer.draw();
- // callback
- if (!isSilent) {
- this.onExecute({'type': 'draw-create', 'id': group.id()});
- }
- };
- /**
- * Undo the command.
- */
- this.undo = function () {
- // remove the group from the parent layer
- group.remove();
- // draw
- layer.draw();
- // callback
- this.onUndo({'type': 'draw-delete', 'id': group.id()});
- };
-}; // DrawGroupCommand class
-
-/**
- * Handle an execute event.
- * @param {Object} event The execute event with type and id.
- */
-dwv.tool.DrawGroupCommand.prototype.onExecute = function (/*event*/)
-{
- // default does nothing.
-};
-/**
- * Handle an undo event.
- * @param {Object} event The undo event with type and id.
- */
-dwv.tool.DrawGroupCommand.prototype.onUndo = function (/*event*/)
-{
- // default does nothing.
-};
-
-/**
- * Move group command.
- * @param {Object} group The group draw.
- * @param {String} name The shape display name.
- * @param {Object} translation A 2D translation to move the group by.
- * @param {Object} layer The layer where to move the group.
- * @constructor
- */
-dwv.tool.MoveGroupCommand = function (group, name, translation, layer)
-{
- /**
- * Get the command name.
- * @return {String} The command name.
- */
- this.getName = function () { return "Move-"+name; };
-
- /**
- * Execute the command.
- */
- this.execute = function () {
- // translate group
- group.move(translation);
- // draw
- layer.draw();
- // callback
- this.onExecute({'type': 'draw-move', 'id': group.id()});
- };
- /**
- * Undo the command.
- */
- this.undo = function () {
- // invert translate group
- var minusTrans = { 'x': -translation.x, 'y': -translation.y};
- group.move(minusTrans);
- // draw
- layer.draw();
- // callback
- this.onUndo({'type': 'draw-move', 'id': group.id()});
- };
-}; // MoveGroupCommand class
-
-/**
- * Handle an execute event.
- * @param {Object} event The execute event with type and id.
- */
-dwv.tool.MoveGroupCommand.prototype.onExecute = function (/*event*/)
-{
- // default does nothing.
-};
-/**
- * Handle an undo event.
- * @param {Object} event The undo event with type and id.
- */
-dwv.tool.MoveGroupCommand.prototype.onUndo = function (/*event*/)
-{
- // default does nothing.
-};
-
-/**
- * Change group command.
- * @param {String} name The shape display name.
- * @param {Object} func The change function.
- * @param {Object} startAnchor The anchor that starts the change.
- * @param {Object} endAnchor The anchor that ends the change.
- * @param {Object} layer The layer where to change the group.
- * @param {Object} image The associated image.
- * @constructor
- */
-dwv.tool.ChangeGroupCommand = function (name, func, startAnchor, endAnchor, layer, image)
-{
- /**
- * Get the command name.
- * @return {String} The command name.
- */
- this.getName = function () { return "Change-"+name; };
-
- /**
- * Execute the command.
- */
- this.execute = function () {
- // change shape
- func( endAnchor, image );
- // draw
- layer.draw();
- // callback
- this.onExecute({'type': 'draw-change'});
- };
- /**
- * Undo the command.
- */
- this.undo = function () {
- // invert change shape
- func( startAnchor, image );
- // draw
- layer.draw();
- // callback
- this.onUndo({'type': 'draw-change'});
- };
-}; // ChangeGroupCommand class
-
-/**
- * Handle an execute event.
- * @param {Object} event The execute event with type and id.
- */
-dwv.tool.ChangeGroupCommand.prototype.onExecute = function (/*event*/)
-{
- // default does nothing.
-};
-/**
- * Handle an undo event.
- * @param {Object} event The undo event with type and id.
- */
-dwv.tool.ChangeGroupCommand.prototype.onUndo = function (/*event*/)
-{
- // default does nothing.
-};
-
-/**
- * Delete group command.
- * @param {Object} group The group draw.
- * @param {String} name The shape display name.
- * @param {Object} layer The layer where to delete the group.
- * @constructor
- */
-dwv.tool.DeleteGroupCommand = function (group, name, layer)
-{
- // group parent
- var parent = group.getParent();
-
- /**
- * Get the command name.
- * @return {String} The command name.
- */
- this.getName = function () { return "Delete-"+name; };
- /**
- * Execute the command.
- */
- this.execute = function () {
- // remove the group from its parent
- group.remove();
- // draw
- layer.draw();
- // callback
- this.onExecute({'type': 'draw-delete', 'id': group.id()});
- };
- /**
- * Undo the command.
- */
- this.undo = function () {
- // add the group to its parent
- parent.add(group);
- // draw
- layer.draw();
- // callback
- this.onUndo({'type': 'draw-create', 'id': group.id()});
- };
-}; // DeleteGroupCommand class
-
-/**
- * Handle an execute event.
- * @param {Object} event The execute event with type and id.
- */
-dwv.tool.DeleteGroupCommand.prototype.onExecute = function (/*event*/)
-{
- // default does nothing.
-};
-/**
- * Handle an undo event.
- * @param {Object} event The undo event with type and id.
- */
-dwv.tool.DeleteGroupCommand.prototype.onUndo = function (/*event*/)
-{
- // default does nothing.
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Shape editor.
- * @constructor
- * @external Konva
- */
-dwv.tool.ShapeEditor = function (app)
-{
- /**
- * Edited shape.
- * @private
- * @type Object
- */
- var shape = null;
- /**
- * Edited image. Used for quantification update.
- * @private
- * @type Object
- */
- var image = null;
- /**
- * Active flag.
- * @private
- * @type Boolean
- */
- var isActive = false;
- /**
- * Update function used by anchors to update the shape.
- * @private
- * @type Function
- */
- var updateFunction = null;
- /**
- * Draw event callback.
- * @private
- * @type Function
- */
- var drawEventCallback = null;
-
- /**
- * Set the shape to edit.
- * @param {Object} inshape The shape to edit.
- */
- this.setShape = function ( inshape ) {
- shape = inshape;
- // reset anchors
- if ( shape ) {
- removeAnchors();
- addAnchors();
- }
- };
-
- /**
- * Set the associated image.
- * @param {Object} img The associated image.
- */
- this.setImage = function ( img ) {
- image = img;
- };
-
- /**
- * Get the edited shape.
- * @return {Object} The edited shape.
- */
- this.getShape = function () {
- return shape;
- };
-
- /**
- * Get the active flag.
- * @return {Boolean} The active flag.
- */
- this.isActive = function () {
- return isActive;
- };
-
- /**
- * Set the draw event callback.
- * @param {Object} callback The callback.
- */
- this.setDrawEventCallback = function ( callback ) {
- drawEventCallback = callback;
- };
-
- /**
- * Enable the editor. Redraws the layer.
- */
- this.enable = function () {
- isActive = true;
- if ( shape ) {
- setAnchorsVisible( true );
- if ( shape.getLayer() ) {
- shape.getLayer().draw();
- }
- }
- };
-
- /**
- * Disable the editor. Redraws the layer.
- */
- this.disable = function () {
- isActive = false;
- if ( shape ) {
- setAnchorsVisible( false );
- if ( shape.getLayer() ) {
- shape.getLayer().draw();
- }
- }
- };
-
- /**
- * Reset the anchors.
- */
- this.resetAnchors = function () {
- // remove previous controls
- removeAnchors();
- // add anchors
- addAnchors();
- // set them visible
- setAnchorsVisible( true );
- };
-
- /**
- * Apply a function on all anchors.
- * @param {Object} func A f(shape) function.
- */
- function applyFuncToAnchors( func ) {
- if ( shape && shape.getParent() ) {
- var anchors = shape.getParent().find('.anchor');
- anchors.each( func );
- }
- }
-
- /**
- * Set anchors visibility.
- * @param {Boolean} flag The visible flag.
- */
- function setAnchorsVisible( flag ) {
- applyFuncToAnchors( function (anchor) {
- anchor.visible( flag );
- });
- }
-
- /**
- * Set anchors active.
- * @param {Boolean} flag The active (on/off) flag.
- */
- this.setAnchorsActive = function ( flag ) {
- var func = null;
- if ( flag ) {
- func = function (anchor) {
- setAnchorOn( anchor );
- };
- }
- else {
- func = function (anchor) {
- setAnchorOff( anchor );
- };
- }
- applyFuncToAnchors( func );
- };
-
- /**
- * Remove anchors.
- */
- function removeAnchors() {
- applyFuncToAnchors( function (anchor) {
- anchor.remove();
- });
- }
-
- /**
- * Add the shape anchors.
- */
- function addAnchors() {
- // exit if no shape or no layer
- if ( !shape || !shape.getLayer() ) {
- return;
- }
- // get shape group
- var group = shape.getParent();
- // add shape specific anchors to the shape group
- if ( shape instanceof Konva.Line ) {
- var points = shape.points();
- if ( points.length === 4 || points.length === 6) {
- // add shape offset
- var p0x = points[0] + shape.x();
- var p0y = points[1] + shape.y();
- var p1x = points[2] + shape.x();
- var p1y = points[3] + shape.y();
- addAnchor(group, p0x, p0y, 'begin');
- if ( points.length === 4 ) {
- var shapekids = group.getChildren( dwv.draw.isNodeNameShapeExtra );
- if (shapekids.length === 2) {
- updateFunction = dwv.tool.UpdateRuler;
- } else {
- updateFunction = dwv.tool.UpdateArrow;
- }
- addAnchor(group, p1x, p1y, 'end');
- }
- else {
- updateFunction = dwv.tool.UpdateProtractor;
- addAnchor(group, p1x, p1y, 'mid');
- var p2x = points[4] + shape.x();
- var p2y = points[5] + shape.y();
- addAnchor(group, p2x, p2y, 'end');
- }
- }
- else {
- updateFunction = dwv.tool.UpdateRoi;
- var px = 0;
- var py = 0;
- for ( var i = 0; i < points.length; i=i+2 ) {
- px = points[i] + shape.x();
- py = points[i+1] + shape.y();
- addAnchor(group, px, py, i);
- }
- }
- }
- else if ( shape instanceof Konva.Rect ) {
- updateFunction = dwv.tool.UpdateRect;
- var rectX = shape.x();
- var rectY = shape.y();
- var rectWidth = shape.width();
- var rectHeight = shape.height();
- addAnchor(group, rectX, rectY, 'topLeft');
- addAnchor(group, rectX+rectWidth, rectY, 'topRight');
- addAnchor(group, rectX+rectWidth, rectY+rectHeight, 'bottomRight');
- addAnchor(group, rectX, rectY+rectHeight, 'bottomLeft');
- }
- else if ( shape instanceof Konva.Ellipse ) {
- updateFunction = dwv.tool.UpdateEllipse;
- var ellipseX = shape.x();
- var ellipseY = shape.y();
- var radius = shape.radius();
- addAnchor(group, ellipseX-radius.x, ellipseY-radius.y, 'topLeft');
- addAnchor(group, ellipseX+radius.x, ellipseY-radius.y, 'topRight');
- addAnchor(group, ellipseX+radius.x, ellipseY+radius.y, 'bottomRight');
- addAnchor(group, ellipseX-radius.x, ellipseY+radius.y, 'bottomLeft');
- }
- // add group to layer
- //shape.getLayer().add( group );
- //shape.getParent().add( group );
- }
-
- /**
- * Create shape editor controls, i.e. the anchors.
- * @param {Object} group The group associated with this anchor.
- * @param {Number} x The X position of the anchor.
- * @param {Number} y The Y position of the anchor.
- * @param {Number} id The id of the anchor.
- */
- function addAnchor(group, x, y, id) {
- // anchor shape
- var anchor = new Konva.Circle({
- x: x,
- y: y,
- stroke: '#999',
- fill: 'rgba(100,100,100,0.7',
- strokeWidth: app.getStyle().getScaledStrokeWidth() / app.getScale(),
- radius: app.getStyle().scale(6) / app.getScale(),
- name: 'anchor',
- id: id,
- dragOnTop: false,
- draggable: true,
- visible: false
- });
- // set anchor on
- setAnchorOn( anchor );
- // add the anchor to the group
- group.add(anchor);
- }
-
- /**
- * Get a simple clone of the input anchor.
- * @param {Object} anchor The anchor to clone.
- */
- function getClone( anchor ) {
- // create closure to properties
- var parent = anchor.getParent();
- var id = anchor.id();
- var x = anchor.x();
- var y = anchor.y();
- // create clone object
- var clone = {};
- clone.getParent = function () {
- return parent;
- };
- clone.id = function () {
- return id;
- };
- clone.x = function () {
- return x;
- };
- clone.y = function () {
- return y;
- };
- return clone;
- }
-
- /**
- * Set the anchor on listeners.
- * @param {Object} anchor The anchor to set on.
- */
- function setAnchorOn( anchor ) {
- var startAnchor = null;
-
- // command name based on shape type
- var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape);
-
- // drag start listener
- anchor.on('dragstart.edit', function (evt) {
- startAnchor = getClone(this);
- // prevent bubbling upwards
- evt.cancelBubble = true;
- });
- // drag move listener
- anchor.on('dragmove.edit', function (evt) {
- if ( updateFunction ) {
- updateFunction(this, image);
- }
- if ( this.getLayer() ) {
- this.getLayer().draw();
- }
- else {
- console.warn("No layer to draw the anchor!");
- }
- // prevent bubbling upwards
- evt.cancelBubble = true;
- });
- // drag end listener
- anchor.on('dragend.edit', function (evt) {
- var endAnchor = getClone(this);
- // store the change command
- var chgcmd = new dwv.tool.ChangeGroupCommand(
- shapeDisplayName, updateFunction, startAnchor, endAnchor, this.getLayer(), image);
- chgcmd.onExecute = drawEventCallback;
- chgcmd.onUndo = drawEventCallback;
- chgcmd.execute();
- app.addToUndoStack(chgcmd);
- // reset start anchor
- startAnchor = endAnchor;
- // prevent bubbling upwards
- evt.cancelBubble = true;
- });
- // mouse down listener
- anchor.on('mousedown touchstart', function () {
- this.moveToTop();
- });
- // mouse over styling
- anchor.on('mouseover.edit', function () {
- // style is handled by the group
- this.stroke('#ddd');
- if ( this.getLayer() ) {
- this.getLayer().draw();
- }
- else {
- console.warn("No layer to draw the anchor!");
- }
- });
- // mouse out styling
- anchor.on('mouseout.edit', function () {
- // style is handled by the group
- this.stroke('#999');
- if ( this.getLayer() ) {
- this.getLayer().draw();
- }
- else {
- console.warn("No layer to draw the anchor!");
- }
- });
- }
-
- /**
- * Set the anchor off listeners.
- * @param {Object} anchor The anchor to set off.
- */
- function setAnchorOff( anchor ) {
- anchor.off('dragstart.edit');
- anchor.off('dragmove.edit');
- anchor.off('dragend.edit');
- anchor.off('mousedown touchstart');
- anchor.off('mouseover.edit');
- anchor.off('mouseout.edit');
- }
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Ellipse factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.EllipseFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 2; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 0; };
-};
-
-/**
- * Create an ellipse shape to be displayed.
- * @param {Array} points The points from which to extract the ellipse.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.EllipseFactory.prototype.create = function (points, style, image)
-{
- // calculate radius
- var a = Math.abs(points[0].getX() - points[1].getX());
- var b = Math.abs(points[0].getY() - points[1].getY());
- // physical shape
- var ellipse = new dwv.math.Ellipse(points[0], a, b);
- // draw shape
- var kshape = new Konva.Ellipse({
- x: ellipse.getCenter().getX(),
- y: ellipse.getCenter().getY(),
- radius: { x: ellipse.getA(), y: ellipse.getB() },
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape"
- });
- // quantification
- var quant = image.quantifyEllipse( ellipse );
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "{surface}";
- ktext.longText = "";
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // label
- var klabel = new Konva.Label({
- x: ellipse.getCenter().getX(),
- y: ellipse.getCenter().getY(),
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // return group
- var group = new Konva.Group();
- group.name("ellipse-group");
- group.add(kshape);
- group.add(klabel);
- group.visible(true); // dont inherit
- return group;
-};
-
-/**
- * Update an ellipse shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateEllipse = function (anchor, image)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var kellipse = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
- // find special points
- var topLeft = group.getChildren( function (node) {
- return node.id() === 'topLeft';
- })[0];
- var topRight = group.getChildren( function (node) {
- return node.id() === 'topRight';
- })[0];
- var bottomRight = group.getChildren( function (node) {
- return node.id() === 'bottomRight';
- })[0];
- var bottomLeft = group.getChildren( function (node) {
- return node.id() === 'bottomLeft';
- })[0];
- // update 'self' (undo case) and special points
- switch ( anchor.id() ) {
- case 'topLeft':
- topLeft.x( anchor.x() );
- topLeft.y( anchor.y() );
- topRight.y( anchor.y() );
- bottomLeft.x( anchor.x() );
- break;
- case 'topRight':
- topRight.x( anchor.x() );
- topRight.y( anchor.y() );
- topLeft.y( anchor.y() );
- bottomRight.x( anchor.x() );
- break;
- case 'bottomRight':
- bottomRight.x( anchor.x() );
- bottomRight.y( anchor.y() );
- bottomLeft.y( anchor.y() );
- topRight.x( anchor.x() );
- break;
- case 'bottomLeft':
- bottomLeft.x( anchor.x() );
- bottomLeft.y( anchor.y() );
- bottomRight.y( anchor.y() );
- topLeft.x( anchor.x() );
- break;
- default :
- console.error('Unhandled anchor id: '+anchor.id());
- break;
- }
- // update shape
- var radiusX = ( topRight.x() - topLeft.x() ) / 2;
- var radiusY = ( bottomRight.y() - topRight.y() ) / 2;
- var center = { 'x': topLeft.x() + radiusX, 'y': topRight.y() + radiusY };
- kellipse.position( center );
- var radiusAbs = { 'x': Math.abs(radiusX), 'y': Math.abs(radiusY) };
- if ( radiusAbs ) {
- kellipse.radius( radiusAbs );
- }
- // new ellipse
- var ellipse = new dwv.math.Ellipse(center, radiusAbs.x, radiusAbs.y);
- // update text
- var quant = image.quantifyEllipse( ellipse );
- var ktext = klabel.getText();
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // update position
- var textPos = { 'x': center.x, 'y': center.y };
- klabel.position( textPos );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-/** @namespace */
-dwv.tool.filter = dwv.tool.filter || {};
-
-/**
- * Filter tool.
- * @constructor
- * @param {Array} filterList The list of filter objects.
- * @param {Object} app The associated app.
- */
-dwv.tool.Filter = function ( filterList, app )
-{
- /**
- * Filter GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Filter list
- * @type Object
- */
- this.filterList = filterList;
- /**
- * Selected filter.
- * @type Object
- */
- this.selectedFilter = 0;
- /**
- * Default filter name.
- * @type String
- */
- this.defaultFilterName = 0;
- /**
- * Display Flag.
- * @type Boolean
- */
- this.displayed = false;
- /**
- * Listener handler.
- * @type Object
- */
- var listenerHandler = new dwv.utils.ListenerHandler();
-
- /**
- * Setup the filter GUI. Called at app startup.
- */
- this.setup = function ()
- {
- if ( Object.keys(this.filterList).length !== 0 ) {
- gui = new dwv.gui.Filter(app);
- gui.setup(this.filterList);
- for( var key in this.filterList ){
- this.filterList[key].setup();
- this.filterList[key].addEventListener("filter-run", fireEvent);
- this.filterList[key].addEventListener("filter-undo", fireEvent);
- }
- }
- };
-
- /**
- * Display the tool.
- * @param {Boolean} bool Flag to enable or not.
- */
- this.display = function (bool)
- {
- if ( gui ) {
- gui.display(bool);
- }
- this.displayed = bool;
- // display the selected filter
- this.selectedFilter.display(bool);
- };
-
- /**
- * Initialise the filter. Called once the image is loaded.
- */
- this.init = function ()
- {
- // set the default to the first in the list
- for( var key in this.filterList ){
- this.defaultFilterName = key;
- break;
- }
- this.setSelectedFilter(this.defaultFilterName);
- // init all filters
- for( key in this.filterList ) {
- this.filterList[key].init();
- }
- // init html
- if ( gui ) {
- gui.initialise();
- }
- return true;
- };
-
- /**
- * Handle keydown event.
- * @param {Object} event The keydown event.
- */
- this.keydown = function (event)
- {
- app.onKeydown(event);
- };
-
- /**
- * Add an event listener to this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type,
- * will be called with the fired event.
- */
- this.addEventListener = function (type, callback) {
- listenerHandler.add(type, callback);
- };
- /**
- * Remove an event listener from this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type.
- */
- this.removeEventListener = function (type, callback) {
- listenerHandler.remove(type, callback);
- };
- /**
- * Fire an event: call all associated listeners with the input event object.
- * @param {Object} event The event to fire.
- * @private
- */
- function fireEvent (event) {
- listenerHandler.fireEvent(event);
- }
-
-}; // class dwv.tool.Filter
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.Filter.prototype.getHelp = function ()
-{
- return {
- "title": dwv.i18n("tool.Filter.name"),
- "brief": dwv.i18n("tool.Filter.brief")
- };
-};
-
-/**
- * Get the selected filter.
- * @return {Object} The selected filter.
- */
-dwv.tool.Filter.prototype.getSelectedFilter = function ()
-{
- return this.selectedFilter;
-};
-
-/**
- * Set the selected filter.
- * @return {String} The name of the filter to select.
- */
-dwv.tool.Filter.prototype.setSelectedFilter = function (name)
-{
- // check if we have it
- if ( !this.hasFilter(name) )
- {
- throw new Error("Unknown filter: '" + name + "'");
- }
- // hide last selected
- if ( this.displayed )
- {
- this.selectedFilter.display(false);
- }
- // enable new one
- this.selectedFilter = this.filterList[name];
- // display the selected filter
- if ( this.displayed )
- {
- this.selectedFilter.display(true);
- }
-};
-
-/**
- * Get the list of filters.
- * @return {Array} The list of filter objects.
- */
-dwv.tool.Filter.prototype.getFilterList = function ()
-{
- return this.filterList;
-};
-
-/**
- * Check if a filter is in the filter list.
- * @param {String} name The name to check.
- * @return {String} The filter list element for the given name.
- */
-dwv.tool.Filter.prototype.hasFilter = function (name)
-{
- return this.filterList[name];
-};
-
-/**
- * Threshold filter tool.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.filter.Threshold = function ( app )
-{
- /**
- * Associated filter.
- * @type Object
- */
- var filter = new dwv.image.filter.Threshold();
- /**
- * Filter GUI.
- * @type Object
- */
- var gui = new dwv.gui.Threshold(app);
- /**
- * Flag to know wether to reset the image or not.
- * @type Boolean
- */
- var resetImage = true;
- /**
- * Listener handler.
- * @type Object
- */
- var listenerHandler = new dwv.utils.ListenerHandler();
-
- /**
- * Setup the filter GUI. Called at app startup.
- */
- this.setup = function ()
- {
- gui.setup();
- };
-
- /**
- * Display the filter.
- * @param {Boolean} bool Flag to display or not.
- */
- this.display = function (bool)
- {
- gui.display(bool);
- // reset the image when the tool is displayed
- if ( bool ) {
- resetImage = true;
- }
- };
-
- /**
- * Initialise the filter. Called once the image is loaded.
- */
- this.init = function ()
- {
- gui.initialise();
- };
-
- /**
- * Run the filter.
- * @param {Mixed} args The filter arguments.
- */
- this.run = function (args)
- {
- filter.setMin(args.min);
- filter.setMax(args.max);
- // reset the image if asked
- if ( resetImage ) {
- filter.setOriginalImage(app.getImage());
- resetImage = false;
- }
- var command = new dwv.tool.RunFilterCommand(filter, app);
- command.onExecute = fireEvent;
- command.onUndo = fireEvent;
- command.execute();
- // save command in undo stack
- app.addToUndoStack(command);
- };
-
- /**
- * Add an event listener to this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type,
- * will be called with the fired event.
- */
- this.addEventListener = function (type, callback) {
- listenerHandler.add(type, callback);
- };
- /**
- * Remove an event listener from this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type.
- */
- this.removeEventListener = function (type, callback) {
- listenerHandler.remove(type, callback);
- };
- /**
- * Fire an event: call all associated listeners with the input event object.
- * @param {Object} event The event to fire.
- * @private
- */
- function fireEvent (event) {
- listenerHandler.fireEvent(event);
- }
-
-}; // class dwv.tool.filter.Threshold
-
-
-/**
- * Sharpen filter tool.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.filter.Sharpen = function ( app )
-{
- /**
- * Filter GUI.
- * @type Object
- */
- var gui = new dwv.gui.Sharpen(app);
- /**
- * Listener handler.
- * @type Object
- */
- var listenerHandler = new dwv.utils.ListenerHandler();
-
- /**
- * Setup the filter GUI. Called at app startup.
- */
- this.setup = function ()
- {
- gui.setup();
- };
-
- /**
- * Display the filter.
- * @param {Boolean} bool Flag to enable or not.
- */
- this.display = function (bool)
- {
- gui.display(bool);
- };
-
- /**
- * Initialise the filter. Called once the image is loaded.
- */
- this.init = function ()
- {
- // nothing to do...
- };
-
- /**
- * Run the filter.
- * @param {Mixed} args The filter arguments.
- */
- this.run = function (/*args*/)
- {
- var filter = new dwv.image.filter.Sharpen();
- filter.setOriginalImage(app.getImage());
- var command = new dwv.tool.RunFilterCommand(filter, app);
- command.onExecute = fireEvent;
- command.onUndo = fireEvent;
- command.execute();
- // save command in undo stack
- app.addToUndoStack(command);
- };
-
- /**
- * Add an event listener to this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type,
- * will be called with the fired event.
- */
- this.addEventListener = function (type, callback) {
- listenerHandler.add(type, callback);
- };
- /**
- * Remove an event listener from this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type.
- */
- this.removeEventListener = function (type, callback) {
- listenerHandler.remove(type, callback);
- };
- /**
- * Fire an event: call all associated listeners with the input event object.
- * @param {Object} event The event to fire.
- * @private
- */
- function fireEvent (event) {
- listenerHandler.fireEvent(event);
- }
-
-}; // dwv.tool.filter.Sharpen
-
-/**
- * Sobel filter tool.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.filter.Sobel = function ( app )
-{
- /**
- * Filter GUI.
- * @type Object
- */
- var gui = new dwv.gui.Sobel(app);
- /**
- * Listener handler.
- * @type Object
- */
- var listenerHandler = new dwv.utils.ListenerHandler();
-
- /**
- * Setup the filter GUI. Called at app startup.
- */
- this.setup = function ()
- {
- gui.setup();
- };
-
- /**
- * Enable the filter.
- * @param {Boolean} bool Flag to enable or not.
- */
- this.display = function (bool)
- {
- gui.display(bool);
- };
-
- /**
- * Initialise the filter. Called once the image is loaded.
- */
- this.init = function ()
- {
- // nothing to do...
- };
-
- /**
- * Run the filter.
- * @param {Mixed} args The filter arguments.
- */
- dwv.tool.filter.Sobel.prototype.run = function (/*args*/)
- {
- var filter = new dwv.image.filter.Sobel();
- filter.setOriginalImage(app.getImage());
- var command = new dwv.tool.RunFilterCommand(filter, app);
- command.onExecute = fireEvent;
- command.onUndo = fireEvent;
- command.execute();
- // save command in undo stack
- app.addToUndoStack(command);
- };
-
- /**
- * Add an event listener to this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type,
- * will be called with the fired event.
- */
- this.addEventListener = function (type, callback) {
- listenerHandler.add(type, callback);
- };
- /**
- * Remove an event listener from this class.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type.
- */
- this.removeEventListener = function (type, callback) {
- listenerHandler.remove(type, callback);
- };
- /**
- * Fire an event: call all associated listeners with the input event object.
- * @param {Object} event The event to fire.
- * @private
- */
- function fireEvent (event) {
- listenerHandler.fireEvent(event);
- }
-
-}; // class dwv.tool.filter.Sobel
-
-/**
- * Run filter command.
- * @constructor
- * @param {Object} filter The filter to run.
- * @param {Object} app The associated application.
- */
-dwv.tool.RunFilterCommand = function (filter, app) {
-
- /**
- * Get the command name.
- * @return {String} The command name.
- */
- this.getName = function () { return "Filter-" + filter.getName(); };
-
- /**
- * Execute the command.
- */
- this.execute = function ()
- {
- // run filter and set app image
- app.setImage(filter.update());
- // update display
- app.render();
- // callback
- this.onExecute({'type': 'filter-run', 'id': this.getName()});
- };
-
- /**
- * Undo the command.
- */
- this.undo = function () {
- // reset the image
- app.setImage(filter.getOriginalImage());
- // update display
- app.render();
- // callback
- this.onUndo({'type': 'filter-undo', 'id': this.getName()});
- };
-
-}; // RunFilterCommand class
-
-/**
- * Handle an execute event.
- * @param {Object} event The execute event with type and id.
- */
-dwv.tool.RunFilterCommand.prototype.onExecute = function (/*event*/)
-{
- // default does nothing.
-};
-/**
- * Handle an undo event.
- * @param {Object} event The undo event with type and id.
- */
-dwv.tool.RunFilterCommand.prototype.onUndo = function (/*event*/)
-{
- // default does nothing.
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var MagicWand = MagicWand || {};
-
-/**
- * Floodfill painting tool.
- * @constructor
- * @param {Object} app The associated application.
- * @external MagicWand
- * @see {@link https://github.com/Tamersoul/magic-wand-js}
- */
-dwv.tool.Floodfill = function(app)
-{
- /**
- * Original variables from external library. Used as in the lib example.
- * @private
- * @type Number
- */
- var blurRadius = 5;
- /**
- * Original variables from external library. Used as in the lib example.
- * @private
- * @type Number
- */
- var simplifyTolerant = 0;
- /**
- * Original variables from external library. Used as in the lib example.
- * @private
- * @type Number
- */
- var simplifyCount = 2000;
- /**
- * Canvas info
- * @private
- * @type Object
- */
- var imageInfo = null;
- /**
- * Object created by MagicWand lib containing border points
- * @private
- * @type Object
- */
- var mask = null;
- /**
- * threshold default tolerance of the tool border
- * @private
- * @type Number
- */
- var initialthreshold = 10;
- /**
- * threshold tolerance of the tool border
- * @private
- * @type Number
- */
- var currentthreshold = null;
- /**
- * Closure to self: to be used by event handlers.
- * @private
- * @type WindowLevel
- */
- var self = this;
- /**
- * Interaction start flag.
- * @type Boolean
- */
- this.started = false;
- /**
- * Livewire GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Draw command.
- * @private
- * @type Object
- */
- var command = null;
- /**
- * Current shape group.
- * @private
- * @type Object
- */
- var shapeGroup = null;
- /**
- * Coordinates of the fist mousedown event.
- * @private
- * @type Object
- */
- var initialpoint;
- /**
- * Floodfill border.
- * @private
- * @type Object
- */
- var border = null;
- /**
- * List of parent points.
- * @private
- * @type Array
- */
- var parentPoints = [];
- /**
- * Assistant variable to paint border on all slices.
- * @private
- * @type Boolean
- */
- var extender = false;
- /**
- * Timeout for painting on mousemove.
- * @private
- */
- var painterTimeout;
- /**
- * Drawing style.
- * @type Style
- */
- this.style = new dwv.html.Style();
-
- /**
- * Event listeners.
- * @private
- */
- var listeners = [];
-
- /**
- * Set extend option for painting border on all slices.
- * @param {Boolean} The option to set
- */
- this.setExtend = function(Bool){
- extender = Bool;
- };
-
- /**
- * Get extend option for painting border on all slices.
- * @return {Boolean} The actual value of of the variable to use Floodfill on museup.
- */
- this.getExtend = function(){
- return extender;
- };
-
- /**
- * Get (x, y) coordinates referenced to the canvas
- * @param {Object} event The original event.
- */
- var getCoord = function(event){
- return { x: event._x, y: event._y };
- };
-
- /**
- * Calculate border.
- * @private
- * @param {Object} Start point.
- * @param {Number} Threshold tolerance.
- */
- var calcBorder = function(points, threshold, simple){
-
- parentPoints = [];
- var image = {
- data: imageInfo.data,
- width: imageInfo.width,
- height: imageInfo.height,
- bytes: 4
- };
-
- // var p = new dwv.math.FastPoint2D(points.x, points.y);
- mask = MagicWand.floodFill(image, points.x, points.y, threshold);
- mask = MagicWand.gaussBlurOnlyBorder(mask, blurRadius);
-
- var cs = MagicWand.traceContours(mask);
- cs = MagicWand.simplifyContours(cs, simplifyTolerant, simplifyCount);
-
- if(cs.length > 0 && cs[0].points[0].x){
- if(simple){
- return cs[0].points;
- }
- for(var j=0, icsl=cs[0].points.length; jjl ; j--){
- if(!paintBorder(initialpoint, threshold)){
- break;
- }
- app.getViewController().decrementSliceNb();
- }
- app.getViewController().setCurrentPosition(pos);
- };
-
- /**
- * Modify tolerance threshold and redraw ROI.
- * @param {Number} New threshold.
- */
- this.modifyThreshold = function(modifyThreshold, shape){
-
- if(!shape && shapeGroup){
- shape = shapeGroup.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- }
- else{
- throw 'No shape found';
- }
-
- clearTimeout(painterTimeout);
- painterTimeout = setTimeout( function () {
- border = calcBorder(initialpoint, modifyThreshold, true);
- if(!border){
- return false;
- }
- var arr = [];
- for( var i = 0, bl = border.length; i < bl ; ++i )
- {
- arr.push( border[i].x );
- arr.push( border[i].y );
- }
- shape.setPoints(arr);
- var shapeLayer = shape.getLayer();
- shapeLayer.draw();
- self.onThresholdChange(modifyThreshold);
- }, 100);
- };
-
- /**
- * Event fired when threshold change
- * @param {Number} Current threshold
- */
- this.onThresholdChange = function(/*value*/){
- // Defaults do nothing
- };
-
- /**
- * Handle mouse down event.
- * @param {Object} event The mouse down event.
- */
- this.mousedown = function(event){
- imageInfo = app.getImageData();
- if (!imageInfo){ return console.error('No image found');}
-
- self.started = true;
- initialpoint = getCoord(event);
- paintBorder(initialpoint, initialthreshold);
- self.onThresholdChange(initialthreshold);
- };
-
- /**
- * Handle mouse move event.
- * @param {Object} event The mouse move event.
- */
- this.mousemove = function(event){
- if (!self.started)
- {
- return;
- }
- var movedpoint = getCoord(event);
- currentthreshold = Math.round(Math.sqrt( Math.pow((initialpoint.x-movedpoint.x), 2) + Math.pow((initialpoint.y-movedpoint.y), 2) )/2);
- currentthreshold = currentthreshold < initialthreshold ? initialthreshold : currentthreshold - initialthreshold;
- self.modifyThreshold(currentthreshold);
- };
-
- /**
- * Handle mouse up event.
- * @param {Object} event The mouse up event.
- */
- this.mouseup = function(/*event*/){
- self.started = false;
- if(extender){
- self.extend();
- }
- };
-
- /**
- * Handle mouse out event.
- * @param {Object} event The mouse out event.
- */
- this.mouseout = function(/*event*/){
- self.mouseup(/*event*/);
- };
-
- /**
- * Handle touch start event.
- * @param {Object} event The touch start event.
- */
- this.touchstart = function(event){
- // treat as mouse down
- self.mousedown(event);
- };
-
- /**
- * Handle touch move event.
- * @param {Object} event The touch move event.
- */
- this.touchmove = function(event){
- // treat as mouse move
- self.mousemove(event);
- };
-
- /**
- * Handle touch end event.
- * @param {Object} event The touch end event.
- */
- this.touchend = function(/*event*/){
- // treat as mouse up
- self.mouseup(/*event*/);
- };
-
- /**
- * Handle key down event.
- * @param {Object} event The key down event.
- */
- this.keydown = function(event){
- app.onKeydown(event);
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui = new dwv.gui.ColourTool(app, "ff");
- gui.setup();
- };
-
- /**
- * Enable the tool.
- * @param {Boolean} bool The flag to enable or not.
- */
- this.display = function(bool){
- if ( gui ) {
- gui.display(bool);
- }
- // TODO why twice?
- this.init();
- };
-
- /**
- * Initialise the tool.
- */
- this.init = function()
- {
- if ( gui ) {
- // init with the app window scale
- this.style.setScale(app.getWindowScale());
- // set the default to the first in the list
- this.setLineColour(this.style.getLineColour());
- // init html
- gui.initialise();
- }
-
- return true;
- };
-
- /**
- * Add an event listener on the app.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.addEventListener = function (listener)
- {
- listeners.push(listener);
- };
-
- /**
- * Remove an event listener from the app.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.removeEventListener = function (listener)
- {
- for ( var i = 0; i < listeners.length; ++i )
- {
- if ( listeners[i] === listener ) {
- listeners.splice(i,1);
- }
- }
- };
-
- // Private Methods -----------------------------------------------------------
-
- /**
- * Fire an event: call all associated listeners.
- * @param {Object} event The event to fire.
- */
- function fireEvent (event)
- {
- for ( var i=0; i < listeners.length; ++i )
- {
- listeners[i](event);
- }
- }
-
-}; // Floodfill class
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.Floodfill.prototype.getHelp = function()
-{
- return {
- 'title': dwv.i18n("tool.Floodfill.name"),
- 'brief': dwv.i18n("tool.Floodfill.brief"),
- "mouse": {
- "click": dwv.i18n("tool.Floodfill.click")
- },
- "touch": {
- "tap": dwv.i18n("tool.Floodfill.tap")
- }
- };
-};
-
-/**
- * Set the line colour of the drawing.
- * @param {String} colour The colour to set.
- */
-dwv.tool.Floodfill.prototype.setLineColour = function(colour)
-{
- // set style var
- this.style.setLineColour(colour);
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * FreeHand factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.FreeHandFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 1000; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 25; };
-};
-
-/**
- * Create a roi shape to be displayed.
- * @param {Array} points The points from which to extract the line.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.FreeHandFactory.prototype.create = function (points, style /*, image*/)
-{
- // points stored the Konvajs way
- var arr = [];
- for( var i = 0; i < points.length; ++i )
- {
- arr.push( points[i].getX() );
- arr.push( points[i].getY() );
- }
- // draw shape
- var kshape = new Konva.Line({
- points: arr,
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape",
- tension: 0.5
- });
-
- // text
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "";
- ktext.longText = "";
- ktext.quant = null;
- ktext.setText(ktext.textExpr);
-
- // label
- var klabel = new Konva.Label({
- x: points[0].getX(),
- y: points[0].getY() + 10,
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // return group
- var group = new Konva.Group();
- group.name("freeHand-group");
- group.add(kshape);
- group.add(klabel);
- group.visible(true); // dont inherit
- return group;
-};
-
-/**
- * Update a FreeHand shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateFreeHand = function (anchor /*, image*/)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var kline = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
-
- // update self
- var point = group.getChildren( function (node) {
- return node.id() === anchor.id();
- })[0];
- point.x( anchor.x() );
- point.y( anchor.y() );
- // update the roi point and compensate for possible drag
- // (the anchor id is the index of the point in the list)
- var points = kline.points();
- points[anchor.id()] = anchor.x() - kline.x();
- points[anchor.id()+1] = anchor.y() - kline.y();
- kline.points( points );
-
- // update text
- var ktext = klabel.getText();
- ktext.quant = null;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // update position
- var textPos = { 'x': points[0] + kline.x(), 'y': points[1] + kline.y() + 10 };
- klabel.position( textPos );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-
-/**
- * Livewire painting tool.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.Livewire = function(app)
-{
- /**
- * Closure to self: to be used by event handlers.
- * @private
- * @type WindowLevel
- */
- var self = this;
- /**
- * Livewire GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Interaction start flag.
- * @type Boolean
- */
- this.started = false;
-
- /**
- * Draw command.
- * @private
- * @type Object
- */
- var command = null;
- /**
- * Current shape group.
- * @private
- * @type Object
- */
- var shapeGroup = null;
- /**
- * Drawing style.
- * @type Style
- */
- this.style = new dwv.html.Style();
- // init with the app window scale
- this.style.setScale(app.getWindowScale());
-
- /**
- * Path storage. Paths are stored in reverse order.
- * @private
- * @type Path
- */
- var path = new dwv.math.Path();
- /**
- * Current path storage. Paths are stored in reverse order.
- * @private
- * @type Path
- */
- var currentPath = new dwv.math.Path();
- /**
- * List of parent points.
- * @private
- * @type Array
- */
- var parentPoints = [];
- /**
- * Tolerance.
- * @private
- * @type Number
- */
- var tolerance = 5;
-
- /**
- * Event listeners.
- * @private
- */
- var listeners = [];
-
- /**
- * Clear the parent points list.
- * @private
- */
- function clearParentPoints() {
- var nrows = app.getImage().getGeometry().getSize().getNumberOfRows();
- for( var i = 0; i < nrows; ++i ) {
- parentPoints[i] = [];
- }
- }
-
- /**
- * Clear the stored paths.
- * @private
- */
- function clearPaths() {
- path = new dwv.math.Path();
- currentPath = new dwv.math.Path();
- }
-
- /**
- * Scissor representation.
- * @private
- * @type Scissors
- */
- var scissors = new dwv.math.Scissors();
-
- /**
- * Handle mouse down event.
- * @param {Object} event The mouse down event.
- */
- this.mousedown = function(event){
- // first time
- if( !self.started ) {
- self.started = true;
- self.x0 = event._x;
- self.y0 = event._y;
- // clear vars
- clearPaths();
- clearParentPoints();
- // do the training from the first point
- var p = new dwv.math.FastPoint2D(event._x, event._y);
- scissors.doTraining(p);
- // add the initial point to the path
- var p0 = new dwv.math.Point2D(event._x, event._y);
- path.addPoint(p0);
- path.addControlPoint(p0);
- }
- else {
- // final point: at 'tolerance' of the initial point
- if( (Math.abs(event._x - self.x0) < tolerance) && (Math.abs(event._y - self.y0) < tolerance) ) {
- // draw
- self.mousemove(event);
- // listen
- command.onExecute = fireEvent;
- command.onUndo = fireEvent;
- // debug
- console.log("Done.");
- // save command in undo stack
- app.addToUndoStack(command);
- // set flag
- self.started = false;
- }
- // anchor point
- else {
- path = currentPath;
- clearParentPoints();
- var pn = new dwv.math.FastPoint2D(event._x, event._y);
- scissors.doTraining(pn);
- path.addControlPoint(currentPath.getPoint(0));
- }
- }
- };
-
- /**
- * Handle mouse move event.
- * @param {Object} event The mouse move event.
- */
- this.mousemove = function(event){
- if (!self.started)
- {
- return;
- }
- // set the point to find the path to
- var p = new dwv.math.FastPoint2D(event._x, event._y);
- scissors.setPoint(p);
- // do the work
- var results = 0;
- var stop = false;
- while( !parentPoints[p.y][p.x] && !stop)
- {
- console.log("Getting ready...");
- results = scissors.doWork();
-
- if( results.length === 0 ) {
- stop = true;
- }
- else {
- // fill parents
- for( var i = 0; i < results.length-1; i+=2 ) {
- var _p = results[i];
- var _q = results[i+1];
- parentPoints[_p.y][_p.x] = _q;
- }
- }
- }
- console.log("Ready!");
-
- // get the path
- currentPath = new dwv.math.Path();
- stop = false;
- while (p && !stop) {
- currentPath.addPoint(new dwv.math.Point2D(p.x, p.y));
- if(!parentPoints[p.y]) {
- stop = true;
- }
- else {
- if(!parentPoints[p.y][p.x]) {
- stop = true;
- }
- else {
- p = parentPoints[p.y][p.x];
- }
- }
- }
- currentPath.appenPath(path);
-
- // remove previous draw
- if ( shapeGroup ) {
- shapeGroup.destroy();
- }
- // create shape
- var factory = new dwv.tool.RoiFactory();
- shapeGroup = factory.create(currentPath.pointArray, self.style);
- shapeGroup.id( dwv.math.guid() );
-
- // get the position group
- var posGroup = app.getDrawController().getCurrentPosGroup();
- // add shape group to position group
- posGroup.add(shapeGroup);
-
- // draw shape command
- command = new dwv.tool.DrawGroupCommand(shapeGroup, "livewire",
- app.getDrawController().getDrawLayer());
- // draw
- command.execute();
- };
-
- /**
- * Handle mouse up event.
- * @param {Object} event The mouse up event.
- */
- this.mouseup = function(/*event*/){
- // nothing to do
- };
-
- /**
- * Handle mouse out event.
- * @param {Object} event The mouse out event.
- */
- this.mouseout = function(event){
- // treat as mouse up
- self.mouseup(event);
- };
-
- /**
- * Handle double click event.
- * @param {Object} event The double click event.
- */
- this.dblclick = function(/*event*/){
- console.log("dblclick");
- // save command in undo stack
- app.addToUndoStack(command);
- // set flag
- self.started = false;
- };
-
- /**
- * Handle touch start event.
- * @param {Object} event The touch start event.
- */
- this.touchstart = function(event){
- // treat as mouse down
- self.mousedown(event);
- };
-
- /**
- * Handle touch move event.
- * @param {Object} event The touch move event.
- */
- this.touchmove = function(event){
- // treat as mouse move
- self.mousemove(event);
- };
-
- /**
- * Handle touch end event.
- * @param {Object} event The touch end event.
- */
- this.touchend = function(event){
- // treat as mouse up
- self.mouseup(event);
- };
-
- /**
- * Handle key down event.
- * @param {Object} event The key down event.
- */
- this.keydown = function(event){
- app.onKeydown(event);
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui = new dwv.gui.ColourTool(app, "lw");
- gui.setup();
- };
-
- /**
- * Enable the tool.
- * @param {Boolean} bool The flag to enable or not.
- */
- this.display = function(bool){
- if ( gui ) {
- gui.display(bool);
- }
- // start scissors if displayed
- if (bool) {
- //scissors = new dwv.math.Scissors();
- var size = app.getImage().getGeometry().getSize();
- scissors.setDimensions(
- size.getNumberOfColumns(),
- size.getNumberOfRows() );
- scissors.setData(app.getImageData().data);
- }
- };
-
- /**
- * Initialise the tool.
- */
- this.init = function()
- {
- if ( gui ) {
- // init with the app window scale
- this.style.setScale(app.getWindowScale());
- // set the default to the first in the list
- this.setLineColour(this.style.getLineColour());
- // init html
- gui.initialise();
- }
-
- return true;
- };
-
- /**
- * Add an event listener on the app.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.addEventListener = function (listener)
- {
- listeners.push(listener);
- };
-
- /**
- * Remove an event listener from the app.
- * @param {Object} listener The method associated with the provided event type.
- */
- this.removeEventListener = function (listener)
- {
- for ( var i = 0; i < listeners.length; ++i )
- {
- if ( listeners[i] === listener ) {
- listeners.splice(i,1);
- }
- }
- };
-
- // Private Methods -----------------------------------------------------------
-
- /**
- * Fire an event: call all associated listeners.
- * @param {Object} event The event to fire.
- */
- function fireEvent (event)
- {
- for ( var i=0; i < listeners.length; ++i )
- {
- listeners[i](event);
- }
- }
-
-}; // Livewire class
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.Livewire.prototype.getHelp = function()
-{
- return {
- "title": dwv.i18n("tool.Livewire.name"),
- "brief": dwv.i18n("tool.Livewire.brief")
- };
-};
-
-/**
- * Set the line colour of the drawing.
- * @param {String} colour The colour to set.
- */
-dwv.tool.Livewire.prototype.setLineColour = function(colour)
-{
- // set style var
- this.style.setLineColour(colour);
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Protractor factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.ProtractorFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 3; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 500; };
-};
-
-/**
- * Create a protractor shape to be displayed.
- * @param {Array} points The points from which to extract the protractor.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.ProtractorFactory.prototype.create = function (points, style/*, image*/)
-{
- // physical shape
- var line0 = new dwv.math.Line(points[0], points[1]);
- // points stored the Konvajs way
- var pointsArray = [];
- for( var i = 0; i < points.length; ++i )
- {
- pointsArray.push( points[i].getX() );
- pointsArray.push( points[i].getY() );
- }
- // draw shape
- var kshape = new Konva.Line({
- points: pointsArray,
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape"
- });
- var group = new Konva.Group();
- group.name("protractor-group");
- group.add(kshape);
- group.visible(true); // dont inherit
- // text and decoration
- if ( points.length === 3 ) {
- var line1 = new dwv.math.Line(points[1], points[2]);
- // larger hitfunc
- kshape.hitFunc( function (context) {
- context.beginPath();
- context.moveTo( points[0].getX(), points[0].getY() );
- context.lineTo( points[1].getX(), points[1].getY() );
- context.lineTo( points[2].getX(), points[2].getY() );
- context.closePath();
- context.fillStrokeShape(this);
- });
- // quantification
- var angle = dwv.math.getAngle(line0, line1);
- var inclination = line0.getInclination();
- if ( angle > 180 ) {
- angle = 360 - angle;
- inclination += angle;
- }
-
- // quantification
- var quant = { "angle": { "value": angle, "unit": dwv.i18n("unit.degree")} };
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "{angle}";
- ktext.longText = "";
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
-
- // label
- var midX = ( line0.getMidpoint().getX() + line1.getMidpoint().getX() ) / 2;
- var midY = ( line0.getMidpoint().getY() + line1.getMidpoint().getY() ) / 2;
- var klabel = new Konva.Label({
- x: midX,
- y: midY - 15,
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // arc
- var radius = Math.min(line0.getLength(), line1.getLength()) * 33 / 100;
- var karc = new Konva.Arc({
- innerRadius: radius,
- outerRadius: radius,
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- angle: angle,
- rotation: -inclination,
- x: points[1].getX(),
- y: points[1].getY(),
- name: "shape-arc"
- });
- // add to group
- group.add(klabel);
- group.add(karc);
- }
- // return group
- return group;
-};
-
-/**
- * Update a protractor shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateProtractor = function (anchor/*, image*/)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var kline = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
- // associated arc
- var karc = group.getChildren( function (node) {
- return node.name() === 'shape-arc';
- })[0];
- // find special points
- var begin = group.getChildren( function (node) {
- return node.id() === 'begin';
- })[0];
- var mid = group.getChildren( function (node) {
- return node.id() === 'mid';
- })[0];
- var end = group.getChildren( function (node) {
- return node.id() === 'end';
- })[0];
- // update special points
- switch ( anchor.id() ) {
- case 'begin':
- begin.x( anchor.x() );
- begin.y( anchor.y() );
- break;
- case 'mid':
- mid.x( anchor.x() );
- mid.y( anchor.y() );
- break;
- case 'end':
- end.x( anchor.x() );
- end.y( anchor.y() );
- break;
- }
- // update shape and compensate for possible drag
- // note: shape.position() and shape.size() won't work...
- var bx = begin.x() - kline.x();
- var by = begin.y() - kline.y();
- var mx = mid.x() - kline.x();
- var my = mid.y() - kline.y();
- var ex = end.x() - kline.x();
- var ey = end.y() - kline.y();
- kline.points( [bx,by,mx,my,ex,ey] );
- // larger hitfunc
- kline.hitFunc( function (context) {
- context.beginPath();
- context.moveTo( bx, by );
- context.lineTo( mx, my );
- context.lineTo( ex, ey );
- context.closePath();
- context.fillStrokeShape(this);
- });
- // update text
- var p2d0 = new dwv.math.Point2D(begin.x(), begin.y());
- var p2d1 = new dwv.math.Point2D(mid.x(), mid.y());
- var p2d2 = new dwv.math.Point2D(end.x(), end.y());
- var line0 = new dwv.math.Line(p2d0, p2d1);
- var line1 = new dwv.math.Line(p2d1, p2d2);
- var angle = dwv.math.getAngle(line0, line1);
- var inclination = line0.getInclination();
- if ( angle > 180 ) {
- angle = 360 - angle;
- inclination += angle;
- }
-
- // update text
- var quant = { "angle": { "value": angle, "unit": dwv.i18n("unit.degree")} };
- var ktext = klabel.getText();
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // update position
- var midX = ( line0.getMidpoint().getX() + line1.getMidpoint().getX() ) / 2;
- var midY = ( line0.getMidpoint().getY() + line1.getMidpoint().getY() ) / 2;
- var textPos = { 'x': midX, 'y': midY - 15 };
- klabel.position( textPos );
-
- // arc
- var radius = Math.min(line0.getLength(), line1.getLength()) * 33 / 100;
- karc.innerRadius(radius);
- karc.outerRadius(radius);
- karc.angle(angle);
- karc.rotation(-inclination);
- var arcPos = { 'x': mid.x(), 'y': mid.y() };
- karc.position(arcPos);
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Rectangle factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.RectangleFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 2; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 0; };
-};
-
-/**
- * Create a rectangle shape to be displayed.
- * @param {Array} points The points from which to extract the rectangle.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.RectangleFactory.prototype.create = function (points, style, image)
-{
- // physical shape
- var rectangle = new dwv.math.Rectangle(points[0], points[1]);
- // draw shape
- var kshape = new Konva.Rect({
- x: rectangle.getBegin().getX(),
- y: rectangle.getBegin().getY(),
- width: rectangle.getWidth(),
- height: rectangle.getHeight(),
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape"
- });
- // quantification
- var quant = image.quantifyRect( rectangle );
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "{surface}";
- ktext.longText = "";
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
-
- // label
- var klabel = new Konva.Label({
- x: rectangle.getBegin().getX(),
- y: rectangle.getEnd().getY() + 10,
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // return group
- var group = new Konva.Group();
- group.name("rectangle-group");
- group.add(kshape);
- group.add(klabel);
- group.visible(true); // dont inherit
- return group;
-};
-
-/**
- * Update a rectangle shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateRect = function (anchor, image)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var krect = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
- // find special points
- var topLeft = group.getChildren( function (node) {
- return node.id() === 'topLeft';
- })[0];
- var topRight = group.getChildren( function (node) {
- return node.id() === 'topRight';
- })[0];
- var bottomRight = group.getChildren( function (node) {
- return node.id() === 'bottomRight';
- })[0];
- var bottomLeft = group.getChildren( function (node) {
- return node.id() === 'bottomLeft';
- })[0];
- // update 'self' (undo case) and special points
- switch ( anchor.id() ) {
- case 'topLeft':
- topLeft.x( anchor.x() );
- topLeft.y( anchor.y() );
- topRight.y( anchor.y() );
- bottomLeft.x( anchor.x() );
- break;
- case 'topRight':
- topRight.x( anchor.x() );
- topRight.y( anchor.y() );
- topLeft.y( anchor.y() );
- bottomRight.x( anchor.x() );
- break;
- case 'bottomRight':
- bottomRight.x( anchor.x() );
- bottomRight.y( anchor.y() );
- bottomLeft.y( anchor.y() );
- topRight.x( anchor.x() );
- break;
- case 'bottomLeft':
- bottomLeft.x( anchor.x() );
- bottomLeft.y( anchor.y() );
- bottomRight.y( anchor.y() );
- topLeft.x( anchor.x() );
- break;
- default :
- console.error('Unhandled anchor id: '+anchor.id());
- break;
- }
- // update shape
- krect.position(topLeft.position());
- var width = topRight.x() - topLeft.x();
- var height = bottomLeft.y() - topLeft.y();
- if ( width && height ) {
- krect.size({'width': width, 'height': height});
- }
- // new rect
- var p2d0 = new dwv.math.Point2D(topLeft.x(), topLeft.y());
- var p2d1 = new dwv.math.Point2D(bottomRight.x(), bottomRight.y());
- var rect = new dwv.math.Rectangle(p2d0, p2d1);
- // update text
- var quant = image.quantifyRect( rect );
- var ktext = klabel.getText();
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // update position
- var textPos = { 'x': rect.getBegin().getX(), 'y': rect.getEnd().getY() + 10 };
- klabel.position( textPos );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * ROI factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.RoiFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 50; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 100; };
-};
-
-/**
- * Create a roi shape to be displayed.
- * @param {Array} points The points from which to extract the line.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.RoiFactory.prototype.create = function (points, style /*, image*/)
-{
- // physical shape
- var roi = new dwv.math.ROI();
- // add input points to the ROI
- roi.addPoints(points);
- // points stored the Konvajs way
- var arr = [];
- for( var i = 0; i < roi.getLength(); ++i )
- {
- arr.push( roi.getPoint(i).getX() );
- arr.push( roi.getPoint(i).getY() );
- }
- // draw shape
- var kshape = new Konva.Line({
- points: arr,
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape",
- closed: true
- });
-
- // text
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "";
- ktext.longText = "";
- ktext.quant = null;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
-
- // label
- var klabel = new Konva.Label({
- x: roi.getPoint(0).getX(),
- y: roi.getPoint(0).getY() + 10,
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // return group
- var group = new Konva.Group();
- group.name("roi-group");
- group.add(kshape);
- group.add(klabel);
- group.visible(true); // dont inherit
- return group;
-};
-
-/**
- * Update a roi shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateRoi = function (anchor /*, image*/)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var kroi = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
-
- // update self
- var point = group.getChildren( function (node) {
- return node.id() === anchor.id();
- })[0];
- point.x( anchor.x() );
- point.y( anchor.y() );
- // update the roi point and compensate for possible drag
- // (the anchor id is the index of the point in the list)
- var points = kroi.points();
- points[anchor.id()] = anchor.x() - kroi.x();
- points[anchor.id()+1] = anchor.y() - kroi.y();
- kroi.points( points );
-
- // update text
- var ktext = klabel.getText();
- ktext.quant = null;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // update position
- var textPos = { 'x': points[0] + kroi.x(), 'y': points[1] + kroi.y() + 10 };
- klabel.position( textPos );
-
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-// external
-var Konva = Konva || {};
-
-/**
- * Ruler factory.
- * @constructor
- * @external Konva
- */
-dwv.tool.RulerFactory = function ()
-{
- /**
- * Get the number of points needed to build the shape.
- * @return {Number} The number of points.
- */
- this.getNPoints = function () { return 2; };
- /**
- * Get the timeout between point storage.
- * @return {Number} The timeout in milliseconds.
- */
- this.getTimeout = function () { return 0; };
-};
-
-/**
- * Create a ruler shape to be displayed.
- * @param {Array} points The points from which to extract the line.
- * @param {Object} style The drawing style.
- * @param {Object} image The associated image.
- */
-dwv.tool.RulerFactory.prototype.create = function (points, style, image)
-{
- // physical shape
- var line = new dwv.math.Line(points[0], points[1]);
- // draw shape
- var kshape = new Konva.Line({
- points: [line.getBegin().getX(), line.getBegin().getY(),
- line.getEnd().getX(), line.getEnd().getY() ],
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape"
- });
-
- // tick begin
- var linePerp0 = dwv.math.getPerpendicularLine( line, points[0], 10 );
- var ktick0 = new Konva.Line({
- points: [linePerp0.getBegin().getX(), linePerp0.getBegin().getY(),
- linePerp0.getEnd().getX(), linePerp0.getEnd().getY() ],
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape-tick0"
- });
-
- // tick end
- var linePerp1 = dwv.math.getPerpendicularLine( line, points[1], 10 );
- var ktick1 = new Konva.Line({
- points: [linePerp1.getBegin().getX(), linePerp1.getBegin().getY(),
- linePerp1.getEnd().getX(), linePerp1.getEnd().getY() ],
- stroke: style.getLineColour(),
- strokeWidth: style.getScaledStrokeWidth(),
- name: "shape-tick1"
- });
-
- // larger hitfunc
- kshape.hitFunc( function (context) {
- context.beginPath();
- context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
- context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
- context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
- context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
- context.closePath();
- context.fillStrokeShape(this);
- });
-
- // quantification
- var quant = image.quantifyLine( line );
- var ktext = new Konva.Text({
- fontSize: style.getScaledFontSize(),
- fontFamily: style.getFontFamily(),
- fill: style.getLineColour(),
- name: "text"
- });
- ktext.textExpr = "{length}";
- ktext.longText = "";
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // label
- var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
- var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
- var klabel = new Konva.Label({
- x: line.getEnd().getX() + dX * 25,
- y: line.getEnd().getY() + dY * 15,
- name: "label"
- });
- klabel.add(ktext);
- klabel.add(new Konva.Tag());
-
- // return group
- var group = new Konva.Group();
- group.name("ruler-group");
- group.add(kshape);
- group.add(ktick0);
- group.add(ktick1);
- group.add(klabel);
- group.visible(true); // dont inherit
- return group;
-};
-
-/**
- * Update a ruler shape.
- * @param {Object} anchor The active anchor.
- * @param {Object} image The associated image.
- */
-dwv.tool.UpdateRuler = function (anchor, image)
-{
- // parent group
- var group = anchor.getParent();
- // associated shape
- var kline = group.getChildren( function (node) {
- return node.name() === 'shape';
- })[0];
- // associated tick0
- var ktick0 = group.getChildren( function (node) {
- return node.name() === 'shape-tick0';
- })[0];
- // associated tick1
- var ktick1 = group.getChildren( function (node) {
- return node.name() === 'shape-tick1';
- })[0];
- // associated label
- var klabel = group.getChildren( function (node) {
- return node.name() === 'label';
- })[0];
- // find special points
- var begin = group.getChildren( function (node) {
- return node.id() === 'begin';
- })[0];
- var end = group.getChildren( function (node) {
- return node.id() === 'end';
- })[0];
- // update special points
- switch ( anchor.id() ) {
- case 'begin':
- begin.x( anchor.x() );
- begin.y( anchor.y() );
- break;
- case 'end':
- end.x( anchor.x() );
- end.y( anchor.y() );
- break;
- }
- // update shape and compensate for possible drag
- // note: shape.position() and shape.size() won't work...
- var bx = begin.x() - kline.x();
- var by = begin.y() - kline.y();
- var ex = end.x() - kline.x();
- var ey = end.y() - kline.y();
- kline.points( [bx,by,ex,ey] );
- // new line
- var p2d0 = new dwv.math.Point2D(begin.x(), begin.y());
- var p2d1 = new dwv.math.Point2D(end.x(), end.y());
- var line = new dwv.math.Line(p2d0, p2d1);
- // tick
- var p2b = new dwv.math.Point2D(bx, by);
- var p2e = new dwv.math.Point2D(ex, ey);
- var linePerp0 = dwv.math.getPerpendicularLine( line, p2b, 10 );
- ktick0.points( [linePerp0.getBegin().getX(), linePerp0.getBegin().getY(),
- linePerp0.getEnd().getX(), linePerp0.getEnd().getY()] );
- var linePerp1 = dwv.math.getPerpendicularLine( line, p2e, 10 );
- ktick1.points( [linePerp1.getBegin().getX(), linePerp1.getBegin().getY(),
- linePerp1.getEnd().getX(), linePerp1.getEnd().getY()] );
- // larger hitfunc
- kline.hitFunc( function (context) {
- context.beginPath();
- context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
- context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
- context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
- context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
- context.closePath();
- context.fillStrokeShape(this);
- });
- // update text
- var quant = image.quantifyLine( line );
- var ktext = klabel.getText();
- ktext.quant = quant;
- ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
- // update position
- var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
- var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
- var textPos = {
- 'x': line.getEnd().getX() + dX * 25,
- 'y': line.getEnd().getY() + dY * 15 };
- klabel.position( textPos );
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-
-/**
- * Scroll class.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.Scroll = function(app)
-{
- /**
- * Closure to self: to be used by event handlers.
- * @private
- * @type WindowLevel
- */
- var self = this;
- /**
- * Scroll GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Interaction start flag.
- * @type Boolean
- */
- this.started = false;
- // touch timer ID (created by setTimeout)
- var touchTimerID = null;
-
- /**
- * Handle mouse down event.
- * @param {Object} event The mouse down event.
- */
- this.mousedown = function(event){
- // stop viewer if playing
- if ( app.getViewController().isPlaying() ) {
- app.getViewController().stop();
- }
- // start flag
- self.started = true;
- // first position
- self.x0 = event._x;
- self.y0 = event._y;
- };
-
- /**
- * Handle mouse move event.
- * @param {Object} event The mouse move event.
- */
- this.mousemove = function(event){
- if (!self.started) {
- return;
- }
-
- // difference to last Y position
- var diffY = event._y - self.y0;
- var yMove = (Math.abs(diffY) > 15);
- // do not trigger for small moves
- if( yMove ) {
- // update GUI
- if( diffY > 0 ) {
- app.getViewController().decrementSliceNb();
- }
- else {
- app.getViewController().incrementSliceNb();
- }
- }
-
- // difference to last X position
- var diffX = event._x - self.x0;
- var xMove = (Math.abs(diffX) > 15);
- // do not trigger for small moves
- if( xMove ) {
- // update GUI
- if( diffX > 0 ) {
- app.getViewController().incrementFrameNb();
- }
- else {
- app.getViewController().decrementFrameNb();
- }
- }
-
- // reset origin point
- if (xMove) {
- self.x0 = event._x;
- }
- if (yMove) {
- self.y0 = event._y;
- }
- };
-
- /**
- * Handle mouse up event.
- * @param {Object} event The mouse up event.
- */
- this.mouseup = function(/*event*/){
- if (self.started)
- {
- // stop recording
- self.started = false;
- }
- };
-
- /**
- * Handle mouse out event.
- * @param {Object} event The mouse out event.
- */
- this.mouseout = function(event){
- self.mouseup(event);
- };
-
- /**
- * Handle touch start event.
- * @param {Object} event The touch start event.
- */
- this.touchstart = function(event){
- // long touch triggers the dblclick
- touchTimerID = setTimeout(self.dblclick, 500);
- // call mouse equivalent
- self.mousedown(event);
- };
-
- /**
- * Handle touch move event.
- * @param {Object} event The touch move event.
- */
- this.touchmove = function(event){
- // abort timer if move
- if (touchTimerID !== null) {
- clearTimeout(touchTimerID);
- touchTimerID = null;
- }
- // call mouse equivalent
- self.mousemove(event);
- };
-
- /**
- * Handle touch end event.
- * @param {Object} event The touch end event.
- */
- this.touchend = function(event){
- // abort timer
- if (touchTimerID !== null) {
- clearTimeout(touchTimerID);
- touchTimerID = null;
- }
- // call mouse equivalent
- self.mouseup(event);
- };
-
- /**
- * Handle mouse scroll event (fired by Firefox).
- * @param {Object} event The mouse scroll event.
- */
- this.DOMMouseScroll = function (event) {
- // ev.detail on firefox is 3
- if ( event.detail < 0 ) {
- mouseScroll(true);
- } else {
- mouseScroll(false);
- }
- };
-
- /**
- * Handle mouse wheel event.
- * @param {Object} event The mouse wheel event.
- */
- this.mousewheel = function (event) {
- // ev.wheelDelta on chrome is 120
- if ( event.wheelDelta > 0 ) {
- mouseScroll(true);
- } else {
- mouseScroll(false);
- }
- };
-
- /**
- * Mouse scroll action.
- * @param {Boolean} up True to increment, false to decrement.
- */
- function mouseScroll (up) {
- var hasSlices = (app.getImage().getGeometry().getSize().getNumberOfSlices() !== 1);
- var hasFrames = (app.getImage().getNumberOfFrames() !== 1);
- if ( up ) {
- if (hasSlices) {
- app.getViewController().incrementSliceNb();
- } else if (hasFrames) {
- app.getViewController().incrementFrameNb();
- }
- } else {
- if (hasSlices) {
- app.getViewController().decrementSliceNb();
- } else if (hasFrames) {
- app.getViewController().decrementFrameNb();
- }
- }
- }
-
- /**
- * Handle key down event.
- * @param {Object} event The key down event.
- */
- this.keydown = function(event){
- app.onKeydown(event);
- };
- /**
- * Handle double click.
- * @param {Object} event The key down event.
- */
- this.dblclick = function (/*event*/) {
- app.getViewController().play();
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui = new dwv.gui.Scroll(app);
- gui.setup();
- };
-
- /**
- * Enable the tool.
- * @param {Boolean} bool The flag to enable or not.
- */
- this.display = function(bool){
- if ( gui ) {
- gui.display(bool);
- }
- };
-
- /**
- * Initialise the tool.
- */
- this.init = function() {
- if ( app.isMonoSliceData() && app.getImage().getNumberOfFrames() === 1 ) {
- return false;
- }
- return true;
- };
-
-}; // Scroll class
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.Scroll.prototype.getHelp = function()
-{
- return {
- "title": dwv.i18n("tool.Scroll.name"),
- "brief": dwv.i18n("tool.Scroll.brief"),
- "mouse": {
- "mouse_drag": dwv.i18n("tool.Scroll.mouse_drag"),
- "double_click": dwv.i18n("tool.Scroll.double_click")
- },
- "touch": {
- 'touch_drag': dwv.i18n("tool.Scroll.touch_drag"),
- 'tap_and_hold': dwv.i18n("tool.Scroll.tap_and_hold")
- }
- };
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-
-/**
- * Tool box.
- * @constructor
- * @param {Array} toolList The list of tool objects.
- * @param {Object} gui The associated gui.
- */
-dwv.tool.Toolbox = function( toolList, app )
-{
- /**
- * Toolbox GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Selected tool.
- * @type Object
- */
- var selectedTool = null;
- /**
- * Default tool name.
- * @type String
- */
- var defaultToolName = null;
-
- /**
- * Get the list of tools.
- * @return {Array} The list of tool objects.
- */
- this.getToolList = function ()
- {
- return toolList;
- };
-
- /**
- * Get the selected tool.
- * @return {Object} The selected tool.
- */
- this.getSelectedTool = function ()
- {
- return selectedTool;
- };
-
- /**
- * Setup the toolbox GUI.
- */
- this.setup = function ()
- {
- if ( Object.keys(toolList).length !== 0 ) {
- gui = new dwv.gui.Toolbox(app);
- gui.setup(toolList);
-
- for( var key in toolList ) {
- toolList[key].setup();
- }
- }
- };
-
- /**
- * Display the toolbox.
- * @param {Boolean} bool Flag to display or not.
- */
- this.display = function (bool)
- {
- if ( Object.keys(toolList).length !== 0 && gui ) {
- gui.display(bool);
- }
- };
-
- /**
- * Initialise the tool box.
- */
- this.init = function ()
- {
- var keys = Object.keys(toolList);
- // check if we have tools
- if ( keys.length === 0 ) {
- return;
- }
- // init all tools
- defaultToolName = "";
- var displays = [];
- var display = null;
- for( var key in toolList ) {
- display = toolList[key].init();
- if ( display && defaultToolName === "" ) {
- defaultToolName = key;
- }
- displays.push(display);
- }
- this.setSelectedTool(defaultToolName);
- // init html
- if ( gui ) {
- gui.initialise(displays);
- }
- };
-
- /**
- * Set the selected tool.
- * @return {String} The name of the tool to select.
- */
- this.setSelectedTool = function (name)
- {
- // check if we have it
- if( !this.hasTool(name) )
- {
- throw new Error("Unknown tool: '" + name + "'");
- }
- // hide last selected
- if( selectedTool )
- {
- selectedTool.display(false);
- }
- // enable new one
- selectedTool = toolList[name];
- // display it
- selectedTool.display(true);
- };
-
- /**
- * Reset the tool box.
- */
- this.reset = function ()
- {
- // hide last selected
- if ( selectedTool ) {
- selectedTool.display(false);
- }
- selectedTool = null;
- defaultToolName = null;
- };
-};
-
-/**
- * Check if a tool is in the tool list.
- * @param {String} name The name to check.
- * @return {String} The tool list element for the given name.
- */
-dwv.tool.Toolbox.prototype.hasTool = function (name)
-{
- return this.getToolList()[name];
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-
-/**
- * UndoStack class.
- * @constructor
- */
-dwv.tool.UndoStack = function (app)
-{
- /**
- * Undo GUI.
- * @type Object
- */
- var gui = new dwv.gui.Undo(app);
- /**
- * Array of commands.
- * @private
- * @type Array
- */
- var stack = [];
-
- /**
- * Get the stack.
- * @return {Array} The list of stored commands.
- */
- this.getStack = function () { return stack; };
-
- /**
- * Current command index.
- * @private
- * @type Number
- */
- var curCmdIndex = 0;
-
- /**
- * Add a command to the stack.
- * @param {Object} cmd The command to add.
- */
- this.add = function(cmd)
- {
- // clear commands after current index
- stack = stack.slice(0,curCmdIndex);
- // store command
- stack.push(cmd);
- //stack[curCmdIndex] = cmd;
- // increment index
- ++curCmdIndex;
- // add command to display history
- gui.addCommandToUndoHtml(cmd.getName());
- };
-
- /**
- * Undo the last command.
- */
- this.undo = function()
- {
- // a bit inefficient...
- if( curCmdIndex > 0 )
- {
- // decrement command index
- --curCmdIndex;
- // undo last command
- stack[curCmdIndex].undo();
- // disable last in display history
- gui.enableInUndoHtml(false);
- }
- };
-
- /**
- * Redo the last command.
- */
- this.redo = function()
- {
- if( curCmdIndex < stack.length )
- {
- // run last command
- stack[curCmdIndex].execute();
- // increment command index
- ++curCmdIndex;
- // enable next in display history
- gui.enableInUndoHtml(true);
- }
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui.setup();
- };
-
- /**
- * Initialise the tool GUI.
- */
- this.initialise = function ()
- {
- gui.initialise();
- };
-
-}; // UndoStack class
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-
-/**
- * WindowLevel tool: handle window/level related events.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.WindowLevel = function(app)
-{
- /**
- * Closure to self: to be used by event handlers.
- * @private
- * @type WindowLevel
- */
- var self = this;
- /**
- * WindowLevel GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Interaction start flag.
- * @type Boolean
- */
- this.started = false;
-
- /**
- * Handle mouse down event.
- * @param {Object} event The mouse down event.
- */
- this.mousedown = function(event){
- // set start flag
- self.started = true;
- // store initial position
- self.x0 = event._x;
- self.y0 = event._y;
- // update GUI
- app.getViewController().setCurrentPosition2D(event._x, event._y);
- };
-
- /**
- * Handle mouse move event.
- * @param {Object} event The mouse move event.
- */
- this.mousemove = function(event){
- // check start flag
- if( !self.started ) {
- return;
- }
- // difference to last position
- var diffX = event._x - self.x0;
- var diffY = self.y0 - event._y;
- // calculate new window level
- var windowCenter = parseInt(app.getViewController().getWindowLevel().center, 10) + diffY;
- var windowWidth = parseInt(app.getViewController().getWindowLevel().width, 10) + diffX;
-
- // add the manual preset to the view
- app.getViewController().addWindowLevelPresets( { "manual": {
- "wl": new dwv.image.WindowLevel(windowCenter, windowWidth),
- "name": "manual"} } );
- app.getViewController().setWindowLevelPreset("manual");
-
- // update gui
- if ( gui ) {
- // initialise to add the manual preset
- gui.initialise();
- // set selected preset
- dwv.gui.setSelected(app.getElement("presetSelect"), "manual");
- }
-
- // store position
- self.x0 = event._x;
- self.y0 = event._y;
- };
-
- /**
- * Handle mouse up event.
- * @param {Object} event The mouse up event.
- */
- this.mouseup = function(/*event*/){
- // set start flag
- if( self.started ) {
- self.started = false;
- }
- };
-
- /**
- * Handle mouse out event.
- * @param {Object} event The mouse out event.
- */
- this.mouseout = function(event){
- // treat as mouse up
- self.mouseup(event);
- };
-
- /**
- * Handle touch start event.
- * @param {Object} event The touch start event.
- */
- this.touchstart = function(event){
- self.mousedown(event);
- };
-
- /**
- * Handle touch move event.
- * @param {Object} event The touch move event.
- */
- this.touchmove = function(event){
- self.mousemove(event);
- };
-
- /**
- * Handle touch end event.
- * @param {Object} event The touch end event.
- */
- this.touchend = function(event){
- self.mouseup(event);
- };
-
- /**
- * Handle double click event.
- * @param {Object} event The double click event.
- */
- this.dblclick = function(event){
- // update GUI
- app.getViewController().setWindowLevel(
- parseInt(app.getImage().getRescaledValue(
- event._x, event._y, app.getViewController().getCurrentPosition().k), 10),
- parseInt(app.getViewController().getWindowLevel().width, 10) );
- };
-
- /**
- * Handle key down event.
- * @param {Object} event The key down event.
- */
- this.keydown = function(event){
- // let the app handle it
- app.onKeydown(event);
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui = new dwv.gui.WindowLevel(app);
- gui.setup();
- };
-
- /**
- * Display the tool.
- * @param {Boolean} bool The flag to display or not.
- */
- this.display = function (bool)
- {
- if ( gui )
- {
- if( app.getImage().getPhotometricInterpretation().match(/MONOCHROME/) !== null ) {
- gui.display(bool);
- }
- else {
- gui.display(false);
- }
- }
- };
-
- /**
- * Initialise the tool.
- */
- this.init = function() {
- if ( gui ) {
- gui.initialise();
- }
- return true;
- };
-}; // WindowLevel class
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.WindowLevel.prototype.getHelp = function()
-{
- return {
- "title": dwv.i18n("tool.WindowLevel.name"),
- "brief": dwv.i18n("tool.WindowLevel.brief"),
- "mouse": {
- "mouse_drag": dwv.i18n("tool.WindowLevel.mouse_drag"),
- "double_click": dwv.i18n("tool.WindowLevel.double_click")
- },
- "touch": {
- "touch_drag": dwv.i18n("tool.WindowLevel.touch_drag")
- }
- };
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.tool = dwv.tool || {};
-
-/**
- * ZoomAndPan class.
- * @constructor
- * @param {Object} app The associated application.
- */
-dwv.tool.ZoomAndPan = function(app)
-{
- /**
- * Closure to self: to be used by event handlers.
- * @private
- * @type Object
- */
- var self = this;
- /**
- * ZoomAndPan GUI.
- * @type Object
- */
- var gui = null;
- /**
- * Interaction start flag.
- * @type Boolean
- */
- this.started = false;
-
- /**
- * Handle mouse down event.
- * @param {Object} event The mouse down event.
- */
- this.mousedown = function(event){
- self.started = true;
- // first position
- self.x0 = event._xs;
- self.y0 = event._ys;
- };
-
- /**
- * Handle two touch down event.
- * @param {Object} event The touch down event.
- */
- this.twotouchdown = function(event){
- self.started = true;
- // store first point
- self.x0 = event._x;
- self.y0 = event._y;
- // first line
- var point0 = new dwv.math.Point2D(event._x, event._y);
- var point1 = new dwv.math.Point2D(event._x1, event._y1);
- self.line0 = new dwv.math.Line(point0, point1);
- self.midPoint = self.line0.getMidpoint();
- };
-
- /**
- * Handle mouse move event.
- * @param {Object} event The mouse move event.
- */
- this.mousemove = function(event){
- if (!self.started)
- {
- return;
- }
- // calculate translation
- var tx = event._xs - self.x0;
- var ty = event._ys - self.y0;
- // apply translation
- //app.translate(tx, ty);
- app.stepTranslate(tx, ty);
- // reset origin point
- self.x0 = event._xs;
- self.y0 = event._ys;
- };
-
- /**
- * Handle two touch move event.
- * @param {Object} event The touch move event.
- */
- this.twotouchmove = function(event){
- if (!self.started)
- {
- return;
- }
- var point0 = new dwv.math.Point2D(event._x, event._y);
- var point1 = new dwv.math.Point2D(event._x1, event._y1);
- var newLine = new dwv.math.Line(point0, point1);
- var lineRatio = newLine.getLength() / self.line0.getLength();
-
- if( lineRatio === 1 )
- {
- // scroll mode
- // difference to last position
- var diffY = event._y - self.y0;
- // do not trigger for small moves
- if( Math.abs(diffY) < 15 ) {
- return;
- }
- // update GUI
- if( diffY > 0 ) {
- app.getViewController().incrementSliceNb();
- }
- else {
- app.getViewController().decrementSliceNb();
- }
- }
- else
- {
- // zoom mode
- var zoom = (lineRatio - 1) / 2;
- if( Math.abs(zoom) % 0.1 <= 0.05 ) {
- app.stepZoom(zoom, event._xs, event._ys);
- }
- }
- };
-
- /**
- * Handle mouse up event.
- * @param {Object} event The mouse up event.
- */
- this.mouseup = function(/*event*/){
- if (self.started)
- {
- // stop recording
- self.started = false;
- }
- };
-
- /**
- * Handle mouse out event.
- * @param {Object} event The mouse out event.
- */
- this.mouseout = function(event){
- self.mouseup(event);
- };
-
- /**
- * Handle touch start event.
- * @param {Object} event The touch start event.
- */
- this.touchstart = function(event){
- var touches = event.targetTouches;
- if( touches.length === 1 ){
- self.mousedown(event);
- }
- else if( touches.length === 2 ){
- self.twotouchdown(event);
- }
- };
-
- /**
- * Handle touch move event.
- * @param {Object} event The touch move event.
- */
- this.touchmove = function(event){
- var touches = event.targetTouches;
- if( touches.length === 1 ){
- self.mousemove(event);
- }
- else if( touches.length === 2 ){
- self.twotouchmove(event);
- }
- };
-
- /**
- * Handle touch end event.
- * @param {Object} event The touch end event.
- */
- this.touchend = function(event){
- self.mouseup(event);
- };
-
- /**
- * Handle mouse scroll event (fired by Firefox).
- * @param {Object} event The mouse scroll event.
- */
- this.DOMMouseScroll = function(event){
- // ev.detail on firefox is 3
- var step = - event.detail / 30;
- app.stepZoom(step, event._xs, event._ys);
- };
-
- /**
- * Handle mouse wheel event.
- * @param {Object} event The mouse wheel event.
- */
- this.mousewheel = function(event){
- // ev.wheelDelta on chrome is 120
- var step = event.wheelDelta / 1200;
- app.stepZoom(step, event._xs, event._ys);
- };
-
- /**
- * Handle key down event.
- * @param {Object} event The key down event.
- */
- this.keydown = function(event){
- app.onKeydown(event);
- };
-
- /**
- * Setup the tool GUI.
- */
- this.setup = function ()
- {
- gui = new dwv.gui.ZoomAndPan(app);
- gui.setup();
- };
-
- /**
- * Enable the tool.
- * @param {Boolean} bool The flag to enable or not.
- */
- this.display = function(bool){
- if ( gui ) {
- gui.display(bool);
- }
- };
-
-}; // ZoomAndPan class
-
-/**
- * Help for this tool.
- * @return {Object} The help content.
- */
-dwv.tool.ZoomAndPan.prototype.getHelp = function()
-{
- return {
- "title": dwv.i18n("tool.ZoomAndPan.name"),
- "brief": dwv.i18n("tool.ZoomAndPan.brief"),
- "mouse": {
- "mouse_wheel": dwv.i18n("tool.ZoomAndPan.mouse_wheel"),
- "mouse_drag": dwv.i18n("tool.ZoomAndPan.mouse_drag")
- },
- "touch": {
- 'twotouch_pinch': dwv.i18n("tool.ZoomAndPan.twotouch_pinch"),
- 'touch_drag': dwv.i18n("tool.ZoomAndPan.touch_drag")
- }
- };
-};
-
-/**
- * Initialise the tool.
- */
-dwv.tool.ZoomAndPan.prototype.init = function() {
- return true;
-};
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.browser = dwv.browser || {};
-
-/**
- * Local function to ask Modernizr if a property is supported.
- * @parma {String} property The property to test.
- */
-function askModernizr( property ) {
- if ( typeof dwv.Modernizr === "undefined" ) {
- dwv.ModernizrInit(window, document);
- }
- var props = property.split('.');
- var prop = dwv.Modernizr;
- for ( var i = 0; i < props.length; ++i ) {
- prop = prop[props[i]];
- }
- return prop;
-}
-
-/**
- * Browser check for the FileAPI.
- * Assume support for Safari5.
- */
-dwv.browser.hasFileApi = function()
-{
- // regular test does not work on Safari 5
- var isSafari5 = (navigator.appVersion.indexOf("Safari") !== -1) &&
- (navigator.appVersion.indexOf("Chrome") === -1) &&
- ( (navigator.appVersion.indexOf("5.0.") !== -1) ||
- (navigator.appVersion.indexOf("5.1.") !== -1) );
- if( isSafari5 )
- {
- console.warn("Assuming FileAPI support for Safari5...");
- return true;
- }
- // regular test
- return askModernizr("filereader");
-};
-
-/**
- * Browser check for the XMLHttpRequest.
- */
-dwv.browser.hasXmlHttpRequest = function()
-{
- return askModernizr("xhrresponsetype") &&
- askModernizr("xhrresponsetypearraybuffer") &&
- askModernizr("xhrresponsetypetext") &&
- "XMLHttpRequest" in window && "withCredentials" in new XMLHttpRequest();
-};
-
-/**
- * Browser check for typed array.
- */
-dwv.browser.hasTypedArray = function()
-{
- return askModernizr("dataview") && askModernizr("typedarrays");
-};
-
-/**
- * Browser check for input with type='color'.
- * Missing in IE and Safari.
- */
-dwv.browser.hasInputColor = function()
-{
- return askModernizr("inputtypes.color");
-};
-
-/**
- * Browser check for input with type='files' and webkitdirectory flag.
- * Missing in IE and Safari.
- */
-dwv.browser.hasInputDirectory = function()
-{
- return askModernizr("fileinputdirectory");
-};
-
-
-//only check at startup (since we propose a replacement)
-dwv.browser._hasTypedArraySlice = (typeof Uint8Array.prototype.slice !== "undefined");
-
-/**
- * Browser check for typed array slice method.
- * Missing in Internet Explorer 11.
- */
-dwv.browser.hasTypedArraySlice = function()
-{
- return dwv.browser._hasTypedArraySlice;
-};
-
-// only check at startup (since we propose a replacement)
-dwv.browser._hasFloat64Array = ("Float64Array" in window);
-
-/**
- * Browser check for Float64Array array.
- * Missing in PhantomJS 1.9.20 (on Travis).
- */
-dwv.browser.hasFloat64Array = function()
-{
- return dwv.browser._hasFloat64Array;
-};
-
-//only check at startup (since we propose a replacement)
-dwv.browser._hasClampedArray = ("Uint8ClampedArray" in window);
-
-/**
- * Browser check for clamped array.
- * Missing in:
- * - Safari 5.1.7 for Windows
- * - PhantomJS 1.9.20 (on Travis).
- */
-dwv.browser.hasClampedArray = function()
-{
- return dwv.browser._hasClampedArray;
-};
-
-/**
- * Browser checks to see if it can run dwv. Throws an error if not.
- * Silently replaces basic functions.
- */
-dwv.browser.check = function()
-{
-
- // Required --------------
-
- var appnorun = "The application cannot be run.";
- var message = "";
- // Check for the File API support
- if( !dwv.browser.hasFileApi() ) {
- message = "The File APIs are not supported in this browser. ";
- alert(message+appnorun);
- throw new Error(message);
- }
- // Check for XMLHttpRequest
- if( !dwv.browser.hasXmlHttpRequest() ) {
- message = "The XMLHttpRequest is not supported in this browser. ";
- alert(message+appnorun);
- throw new Error(message);
- }
- // Check typed array
- if( !dwv.browser.hasTypedArray() ) {
- message = "The Typed arrays are not supported in this browser. ";
- alert(message+appnorun);
- throw new Error(message);
- }
-
- // Replaced if not present ------------
-
- // Check typed array slice
- if( !dwv.browser.hasTypedArraySlice() ) {
- // silent fail with warning
- console.warn("The TypedArray.slice method is not supported in this browser. This may impair performance. ");
- // basic Uint16Array implementation
- Uint16Array.prototype.slice = function (begin, end) {
- var size = end - begin;
- var cloned = new Uint16Array(size);
- for (var i = 0; i < size; i++) {
- cloned[i] = this[begin + i];
- }
- return cloned;
- };
- // basic Int16Array implementation
- Int16Array.prototype.slice = function (begin, end) {
- var size = end - begin;
- var cloned = new Int16Array(size);
- for (var i = 0; i < size; i++) {
- cloned[i] = this[begin + i];
- }
- return cloned;
- };
- // basic Uint8Array implementation
- Uint8Array.prototype.slice = function (begin, end) {
- var size = end - begin;
- var cloned = new Uint8Array(size);
- for (var i = 0; i < size; i++) {
- cloned[i] = this[begin + i];
- }
- return cloned;
- };
- // basic Int8Array implementation
- Int8Array.prototype.slice = function (begin, end) {
- var size = end - begin;
- var cloned = new Int8Array(size);
- for (var i = 0; i < size; i++) {
- cloned[i] = this[begin + i];
- }
- return cloned;
- };
- }
- // check clamped array
- if( !dwv.browser.hasClampedArray() ) {
- // silent fail with warning
- console.warn("The Uint8ClampedArray is not supported in this browser. This may impair performance. ");
- // Use Uint8Array instead... Not good
- // TODO Find better replacement!
- window.Uint8ClampedArray = window.Uint8Array;
- }
- // check Float64 array
- if( !dwv.browser.hasFloat64Array() ) {
- // silent fail with warning
- console.warn("The Float64Array is not supported in this browser. This may impair performance. ");
- // Use Float32Array instead... Not good
- // TODO Find better replacement!
- window.Float64Array = window.Float32Array;
- }
-
- // check string startsWith
- if ( !String.prototype.startsWith ) {
- String.prototype.startsWith = function (search, pos) {
- return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
- };
- }
-};
-
-// namespaces
-var dwv = dwv || {};
-// external
-var i18next = i18next || {};
-var i18nextXHRBackend = i18nextXHRBackend || {};
-var i18nextBrowserLanguageDetector = i18nextBrowserLanguageDetector || {};
-
-// This is mainly a wrapper around the i18next object.
-// see its API: http://i18next.com/docs/api/
-
-// global locales path
-dwv.i18nLocalesPath = null;
-
-/**
- * Initialise i18n.
- * @param {String} language The language to translate to. Defaults to 'auto' and
- * gets the language from the browser.
- * @param {String} localesPath Path to the locales directory.
- * @external i18next
- * @external i18nextXHRBackend
- * @external i18nextBrowserLanguageDetector
- */
-dwv.i18nInitialise = function (language, localesPath)
-{
- var lng = (typeof language === "undefined") ? "auto" : language;
- var lpath = (typeof localesPath === "undefined") ? "../.." : localesPath;
- // store as global
- dwv.i18nLocalesPath = lpath;
- // i18n options: default 'en' language and
- // only load language, not specialised (for ex en-GB)
- var options = {
- fallbackLng: "en",
- load: "languageOnly",
- backend: { loadPath: lpath + "/locales/{{lng}}/{{ns}}.json" }
- };
- // use the XHR backend to get translation files
- var i18n = i18next.use(i18nextXHRBackend);
- // use browser language or the specified one
- if (lng === "auto") {
- i18n.use(i18nextBrowserLanguageDetector);
- }
- else {
- options.lng = lng;
- }
- // init i18n: will be ready when the 'loaded' event is fired
- i18n.init(options);
-};
-
-/**
- * Initialise i18n with recources as input.
- * @param {String} language The language to translate to. Defaults to 'auto' and
- * gets the language from the browser.
- * @param {Object} resources Languages provided as object.
- * @external i18next
- * @external i18nextBrowserLanguageDetector
- */
-dwv.i18nInitialiseWithResources = function (language, resources)
-{
- var lng = (typeof language === "undefined") ? "auto" : language;
- // i18n options: default 'en' language and
- // only load language, not specialised (for ex en-GB)
- var options = {
- fallbackLng: "en",
- load: "languageOnly",
- resources: resources
- };
- // use browser language or the specified one
- // init i18n: will be ready when the 'loaded' event is fired
- if (lng === "auto") {
- var i18n = i18next.use(i18nextBrowserLanguageDetector);
- i18n.init(options);
- }
- else {
- options.lng = lng;
- i18next.init(options);
- }
-};
-
-/**
- * Handle i18n 'initialized' event.
- * @param {Object} callback The callback function to call when i18n is initialised.
- * It can take one argument that will be replaced with the i18n options.
- * @external i18next
- */
-dwv.i18nOnInitialised = function (callback) {
- i18next.on('initialized', callback);
-};
-
-/**
- * Stop handling i18n load event.
- * @external i18next
- */
-dwv.i18nOffInitialised = function () {
- i18next.off('initialized');
-};
-
-/**
- * Handle i18n failed load event.
- * @param {Object} callback The callback function to call when i18n is loaded.
- * It can take three arguments: lng, ns and msg.
- * @external i18next
- */
-dwv.i18nOnFailedLoad = function (callback) {
- i18next.on('failedLoading', callback);
-};
-
-/**
- * Stop handling i18n failed load event.
- * @external i18next
- */
-dwv.i18nOffFailedLoad = function () {
- i18next.off('failedLoading');
-};
-
-/**
- * Get the translated text.
- * @param {String} key The key to the text entry.
- * @param {Object} options The translation options such as plural, context...
- * @external i18next
- */
-dwv.i18n = function (key, options) {
- return i18next.t(key, options);
-};
-
-/**
- * Check the existence of a translation.
- * @param {String} key The key to the text entry.
- * @param {Object} options The translation options such as plural, context...
- * @external i18next
- */
-dwv.i18nExists = function (key, options) {
- return i18next.exists(key, options);
-};
-
-/**
- * Translate all data-i18n tags in the current html page. If an html tag defines the
- * data-i18n attribute, its value will be used as key to find its corresponding text
- * and will replace the content of the html tag.
- */
-dwv.i18nPage = function () {
- // get all elements
- var elements = document.getElementsByTagName("*");
- // if the element defines data-i18n, replace its content with the tranlation
- for (var i = 0; i < elements.length; ++i) {
- if (typeof elements[i].dataset.i18n !== "undefined") {
- elements[i].innerHTML = dwv.i18n(elements[i].dataset.i18n);
- }
- }
-};
-
-/**
- * Get the current locale resource path.
- * Warning: to be used once i18next is initialised.
- * @return {String} The path to the locale resource.
- */
-dwv.i18nGetLocalePath = function (filename) {
- var lng = i18next.language.substr(0, 2);
- return dwv.i18nLocalesPath +
- "/locales/" + lng + "/" + filename;
-};
-
-/**
- * Get the current locale resource path.
- * Warning: to be used once i18next is initialised.
- * @return {String} The path to the locale resource.
- */
-dwv.i18nGetFallbackLocalePath = function (filename) {
- var lng = i18next.languages[i18next.languages.length-1].substr(0, 2);
- return dwv.i18nLocalesPath +
- "/locales/" + lng + "/" + filename;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.utils = dwv.utils || {};
-
-/**
- * ListenerHandler class: handles add/removing and firing listeners.
- * @constructor
- */
-dwv.utils.ListenerHandler = function ()
-{
- /**
- * listeners.
- * @private
- */
- var listeners = {};
-
- /**
- * Add an event listener.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type,
- * will be called with the fired event.
- */
- this.add = function (type, callback)
- {
- // create array if not present
- if ( typeof listeners[type] === "undefined" ) {
- listeners[type] = [];
- }
- // add callback to listeners array
- listeners[type].push(callback);
- };
-
- /**
- * Remove an event listener.
- * @param {String} type The event type.
- * @param {Object} callback The method associated with the provided event type.
- */
- this.remove = function (type, callback)
- {
- // check if the type is present
- if( typeof listeners[type] === "undefined" ) {
- return;
- }
- // remove from listeners array
- for ( var i = 0; i < listeners[type].length; ++i ) {
- if ( listeners[type][i] === callback ) {
- listeners[type].splice(i,1);
- }
- }
- };
-
- /**
- * Fire an event: call all associated listeners with the input event object.
- * @param {Object} event The event to fire.
- */
- this.fireEvent = function (event)
- {
- // check if they are listeners for the event type
- if ( typeof listeners[event.type] === "undefined" ) {
- return;
- }
- // fire events
- for ( var i=0; i < listeners[event.type].length; ++i ) {
- listeners[event.type][i](event);
- }
- };
-};
-
-// namespaces
-var dwv = dwv || {};
-
-/*!
- * modernizr v3.6.0
- * Build https://modernizr.com/download?-dataview-directory-filereader-inputtypes-typedarrays-xhrresponsetype-xhrresponsetypearraybuffer-xhrresponsetypejson-xhrresponsetypetext-dontmin
- *
- * Copyright (c)
- * Faruk Ates
- * Paul Irish
- * Alex Sexton
- * Ryan Seddon
- * Patrick Kettner
- * Stu Cox
- * Richard Herrera
-
- * MIT License
- */
-
-/*
- * Modernizr tests which native CSS3 and HTML5 features are available in the
- * current UA and makes the results available to you in two ways: as properties on
- * a global `Modernizr` object, and as classes on the `` element. This
- * information allows you to progressively enhance your pages with a granular level
- * of control over the experience.
-*/
-
-dwv.ModernizrInit = function (window, document, undefined) {
-//;(function(window, document, undefined){
- var tests = [];
-
-
- /**
- *
- * ModernizrProto is the constructor for Modernizr
- *
- * @class
- * @access public
- */
-
- var ModernizrProto = {
- // The current version, dummy
- _version: '3.6.0',
-
- // Any settings that don't work as separate modules
- // can go in here as configuration.
- _config: {
- 'classPrefix': '',
- 'enableClasses': true,
- 'enableJSClass': true,
- 'usePrefixes': true
- },
-
- // Queue of tests
- _q: [],
-
- // Stub these for people who are listening
- on: function(test, cb) {
- // I don't really think people should do this, but we can
- // safe guard it a bit.
- // -- NOTE:: this gets WAY overridden in src/addTest for actual async tests.
- // This is in case people listen to synchronous tests. I would leave it out,
- // but the code to *disallow* sync tests in the real version of this
- // function is actually larger than this.
- var self = this;
- setTimeout(function() {
- cb(self[test]);
- }, 0);
- },
-
- addTest: function(name, fn, options) {
- tests.push({name: name, fn: fn, options: options});
- },
-
- addAsyncTest: function(fn) {
- tests.push({name: null, fn: fn});
- }
- };
-
-
-
- // Fake some of Object.create so we can force non test results to be non "own" properties.
- var Modernizr = function() {};
- Modernizr.prototype = ModernizrProto;
-
- // Leak modernizr globally when you `require` it rather than force it here.
- // Overwrite name so constructor name is nicer :D
- Modernizr = new Modernizr();
-
-
-/*!
-{
- "name": "DataView",
- "property": "dataview",
- "authors": ["Addy Osmani"],
- "builderAliases": ["dataview_api"],
- "notes": [{
- "name": "MDN documentation",
- "href": "https://developer.mozilla.org/en/JavaScript_typed_arrays/DataView"
- }],
- "polyfills": ["jdataview"]
-}
-!*/
-/* DOC
-Detects support for the DataView interface for reading data from an ArrayBuffer as part of the Typed Array spec.
-*/
-
- Modernizr.addTest('dataview', (typeof DataView !== 'undefined' && 'getFloat64' in DataView.prototype));
-
-/*!
-{
- "name": "Typed arrays",
- "property": "typedarrays",
- "caniuse": "typedarrays",
- "tags": ["js"],
- "authors": ["Stanley Stuart (@fivetanley)"],
- "notes": [{
- "name": "MDN documentation",
- "href": "https://developer.mozilla.org/en-US/docs/JavaScript_typed_arrays"
- },{
- "name": "Kronos spec",
- "href": "https://www.khronos.org/registry/typedarray/specs/latest/"
- }],
- "polyfills": ["joshuabell-polyfill"]
-}
-!*/
-/* DOC
-Detects support for native binary data manipulation via Typed Arrays in JavaScript.
-
-Does not check for DataView support; use `Modernizr.dataview` for that.
-*/
-
- // Should fail in:
- // Internet Explorer <= 9
- // Firefox <= 3.6
- // Chrome <= 6.0
- // iOS Safari < 4.2
- // Safari < 5.1
- // Opera < 11.6
- // Opera Mini, <= 7.0
- // Android Browser < 4.0
- // Blackberry Browser < 10.0
-
- Modernizr.addTest('typedarrays', 'ArrayBuffer' in window);
-
-/*!
-{
- "name": "File API",
- "property": "filereader",
- "caniuse": "fileapi",
- "notes": [{
- "name": "W3C Working Draft",
- "href": "https://www.w3.org/TR/FileAPI/"
- }],
- "tags": ["file"],
- "builderAliases": ["file_api"],
- "knownBugs": ["Will fail in Safari 5 due to its lack of support for the standards defined FileReader object"]
-}
-!*/
-/* DOC
-`filereader` tests for the File API specification
-
-Tests for objects specific to the File API W3C specification without
-being redundant (don't bother testing for Blob since it is assumed
-to be the File object's prototype.)
-*/
-
- Modernizr.addTest('filereader', !!(window.File && window.FileList && window.FileReader));
-
-/*!
-{
- "name": "XHR responseType",
- "property": "xhrresponsetype",
- "tags": ["network"],
- "notes": [{
- "name": "XMLHttpRequest Living Standard",
- "href": "https://xhr.spec.whatwg.org/#the-responsetype-attribute"
- }]
-}
-!*/
-/* DOC
-Tests for XMLHttpRequest xhr.responseType.
-*/
-
- Modernizr.addTest('xhrresponsetype', (function() {
- if (typeof XMLHttpRequest == 'undefined') {
- return false;
- }
- var xhr = new XMLHttpRequest();
- xhr.open('get', '/', true);
- return 'response' in xhr;
- }()));
-
-
- var classes = [];
-
-
- /**
- * is returns a boolean if the typeof an obj is exactly type.
- *
- * @access private
- * @function is
- * @param {*} obj - A thing we want to check the type of
- * @param {string} type - A string to compare the typeof against
- * @returns {boolean}
- */
-
- function is(obj, type) {
- return typeof obj === type;
- }
- ;
-
- /**
- * Run through all tests and detect their support in the current UA.
- *
- * @access private
- */
-
- function testRunner() {
- var featureNames;
- var feature;
- var aliasIdx;
- var result;
- var nameIdx;
- var featureName;
- var featureNameSplit;
-
- for (var featureIdx in tests) {
- if (tests.hasOwnProperty(featureIdx)) {
- featureNames = [];
- feature = tests[featureIdx];
- // run the test, throw the return value into the Modernizr,
- // then based on that boolean, define an appropriate className
- // and push it into an array of classes we'll join later.
- //
- // If there is no name, it's an 'async' test that is run,
- // but not directly added to the object. That should
- // be done with a post-run addTest call.
- if (feature.name) {
- featureNames.push(feature.name.toLowerCase());
-
- if (feature.options && feature.options.aliases && feature.options.aliases.length) {
- // Add all the aliases into the names list
- for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) {
- featureNames.push(feature.options.aliases[aliasIdx].toLowerCase());
- }
- }
- }
-
- // Run the test, or use the raw value if it's not a function
- result = is(feature.fn, 'function') ? feature.fn() : feature.fn;
-
-
- // Set each of the names on the Modernizr object
- for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) {
- featureName = featureNames[nameIdx];
- // Support dot properties as sub tests. We don't do checking to make sure
- // that the implied parent tests have been added. You must call them in
- // order (either in the test, or make the parent test a dependency).
- //
- // Cap it to TWO to make the logic simple and because who needs that kind of subtesting
- // hashtag famous last words
- featureNameSplit = featureName.split('.');
-
- if (featureNameSplit.length === 1) {
- Modernizr[featureNameSplit[0]] = result;
- } else {
- // cast to a Boolean, if not one already
- if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) {
- Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]);
- }
-
- Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result;
- }
-
- classes.push((result ? '' : 'no-') + featureNameSplit.join('-'));
- }
- }
- }
- }
- ;
-
- /**
- * docElement is a convenience wrapper to grab the root element of the document
- *
- * @access private
- * @returns {HTMLElement|SVGElement} The root element of the document
- */
-
- var docElement = document.documentElement;
-
-
- /**
- * http://mathiasbynens.be/notes/xhr-responsetype-json#comment-4
- *
- * @access private
- * @function testXhrType
- * @param {string} type - String name of the XHR type you want to detect
- * @returns {boolean}
- * @author Mathias Bynens
- */
-
- /* istanbul ignore next */
- var testXhrType = function(type) {
- if (typeof XMLHttpRequest == 'undefined') {
- return false;
- }
- var xhr = new XMLHttpRequest();
- xhr.open('get', '/', true);
- try {
- xhr.responseType = type;
- } catch (error) {
- return false;
- }
- return 'response' in xhr && xhr.responseType == type;
- };
-
-
-/*!
-{
- "name": "XHR responseType='arraybuffer'",
- "property": "xhrresponsetypearraybuffer",
- "tags": ["network"],
- "notes": [{
- "name": "XMLHttpRequest Living Standard",
- "href": "https://xhr.spec.whatwg.org/#the-responsetype-attribute"
- }]
-}
-!*/
-/* DOC
-Tests for XMLHttpRequest xhr.responseType='arraybuffer'.
-*/
-
- Modernizr.addTest('xhrresponsetypearraybuffer', testXhrType('arraybuffer'));
-
-/*!
-{
- "name": "XHR responseType='json'",
- "property": "xhrresponsetypejson",
- "tags": ["network"],
- "notes": [{
- "name": "XMLHttpRequest Living Standard",
- "href": "https://xhr.spec.whatwg.org/#the-responsetype-attribute"
- },{
- "name": "Explanation of xhr.responseType='json'",
- "href": "https://mathiasbynens.be/notes/xhr-responsetype-json"
- }]
-}
-!*/
-/* DOC
-Tests for XMLHttpRequest xhr.responseType='json'.
-*/
-
- Modernizr.addTest('xhrresponsetypejson', testXhrType('json'));
-
-/*!
-{
- "name": "XHR responseType='text'",
- "property": "xhrresponsetypetext",
- "tags": ["network"],
- "notes": [{
- "name": "XMLHttpRequest Living Standard",
- "href": "https://xhr.spec.whatwg.org/#the-responsetype-attribute"
- }]
-}
-!*/
-/* DOC
-Tests for XMLHttpRequest xhr.responseType='text'.
-*/
-
- Modernizr.addTest('xhrresponsetypetext', testXhrType('text'));
-
-
- /**
- * A convenience helper to check if the document we are running in is an SVG document
- *
- * @access private
- * @returns {boolean}
- */
-
- var isSVG = docElement.nodeName.toLowerCase() === 'svg';
-
-
- /**
- * createElement is a convenience wrapper around document.createElement. Since we
- * use createElement all over the place, this allows for (slightly) smaller code
- * as well as abstracting away issues with creating elements in contexts other than
- * HTML documents (e.g. SVG documents).
- *
- * @access private
- * @function createElement
- * @returns {HTMLElement|SVGElement} An HTML or SVG element
- */
-
- function createElement() {
- if (typeof document.createElement !== 'function') {
- // This is the case in IE7, where the type of createElement is "object".
- // For this reason, we cannot call apply() as Object is not a Function.
- return document.createElement(arguments[0]);
- } else if (isSVG) {
- return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]);
- } else {
- return document.createElement.apply(document, arguments);
- }
- }
-
- ;
-
- /**
- * since we have a fairly large number of input tests that don't mutate the input
- * we create a single element that can be shared with all of those tests for a
- * minor perf boost
- *
- * @access private
- * @returns {HTMLInputElement}
- */
- var inputElem = createElement('input');
-
-/*!
-{
- "name": "Form input types",
- "property": "inputtypes",
- "caniuse": "forms",
- "tags": ["forms"],
- "authors": ["Mike Taylor"],
- "polyfills": [
- "jquerytools",
- "webshims",
- "h5f",
- "webforms2",
- "nwxforms",
- "fdslider",
- "html5slider",
- "galleryhtml5forms",
- "jscolor",
- "html5formshim",
- "selectedoptionsjs",
- "formvalidationjs"
- ]
-}
-!*/
-/* DOC
-Detects support for HTML5 form input types and exposes Boolean subproperties with the results:
-
-```javascript
-Modernizr.inputtypes.color
-Modernizr.inputtypes.date
-Modernizr.inputtypes.datetime
-Modernizr.inputtypes['datetime-local']
-Modernizr.inputtypes.email
-Modernizr.inputtypes.month
-Modernizr.inputtypes.number
-Modernizr.inputtypes.range
-Modernizr.inputtypes.search
-Modernizr.inputtypes.tel
-Modernizr.inputtypes.time
-Modernizr.inputtypes.url
-Modernizr.inputtypes.week
-```
-*/
-
- // Run through HTML5's new input types to see if the UA understands any.
- // This is put behind the tests runloop because it doesn't return a
- // true/false like all the other tests; instead, it returns an object
- // containing each input type with its corresponding true/false value
-
- // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
- var inputtypes = 'search tel url email datetime date month week time datetime-local number range color'.split(' ');
- var inputs = {};
-
- Modernizr.inputtypes = (function(props) {
- var len = props.length;
- var smile = '1)';
- var inputElemType;
- var defaultView;
- var bool;
-
- for (var i = 0; i < len; i++) {
-
- inputElem.setAttribute('type', inputElemType = props[i]);
- bool = inputElem.type !== 'text' && 'style' in inputElem;
-
- // We first check to see if the type we give it sticks..
- // If the type does, we feed it a textual value, which shouldn't be valid.
- // If the value doesn't stick, we know there's input sanitization which infers a custom UI
- if (bool) {
-
- inputElem.value = smile;
- inputElem.style.cssText = 'position:absolute;visibility:hidden;';
-
- if (/^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined) {
-
- docElement.appendChild(inputElem);
- defaultView = document.defaultView;
-
- // Safari 2-4 allows the smiley as a value, despite making a slider
- bool = defaultView.getComputedStyle &&
- defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
- // Mobile android web browser has false positive, so must
- // check the height to see if the widget is actually there.
- (inputElem.offsetHeight !== 0);
-
- docElement.removeChild(inputElem);
-
- } else if (/^(search|tel)$/.test(inputElemType)) {
- // Spec doesn't define any special parsing or detectable UI
- // behaviors so we pass these through as true
-
- // Interestingly, opera fails the earlier test, so it doesn't
- // even make it here.
-
- } else if (/^(url|email)$/.test(inputElemType)) {
- // Real url and email support comes with prebaked validation.
- bool = inputElem.checkValidity && inputElem.checkValidity() === false;
-
- } else {
- // If the upgraded input compontent rejects the :) text, we got a winner
- bool = inputElem.value != smile;
- }
- }
-
- inputs[ props[i] ] = !!bool;
- }
- return inputs;
- })(inputtypes);
-
-
- /**
- * If the browsers follow the spec, then they would expose vendor-specific styles as:
- * elem.style.WebkitBorderRadius
- * instead of something like the following (which is technically incorrect):
- * elem.style.webkitBorderRadius
-
- * WebKit ghosts their properties in lowercase but Opera & Moz do not.
- * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
- * erik.eae.net/archives/2008/03/10/21.48.10/
-
- * More here: github.com/Modernizr/Modernizr/issues/issue/21
- *
- * @access private
- * @returns {string} The string representing the vendor-specific style properties
- */
-
- var omPrefixes = 'Moz O ms Webkit';
-
-
- /**
- * List of JavaScript DOM values used for tests
- *
- * @memberof Modernizr
- * @name Modernizr._domPrefixes
- * @optionName Modernizr._domPrefixes
- * @optionProp domPrefixes
- * @access public
- * @example
- *
- * Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather
- * than kebab-case properties, all properties are their Capitalized variant
- *
- * ```js
- * Modernizr._domPrefixes === [ "Moz", "O", "ms", "Webkit" ];
- * ```
- */
-
- var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []);
- ModernizrProto._domPrefixes = domPrefixes;
-
-/*!
-{
- "name": "input[directory] Attribute",
- "property": "directory",
- "authors": ["silverwind"],
- "tags": ["file", "input", "attribute"]
-}
-!*/
-/* DOC
-When used on an ` `, the `directory` attribute instructs
-the user agent to present a directory selection dialog instead of the usual
-file selection dialog.
-*/
-
- Modernizr.addTest('fileinputdirectory', function() {
- var elem = createElement('input'), dir = 'directory';
- elem.type = 'file';
- if (dir in elem) {
- return true;
- } else {
- for (var i = 0, len = domPrefixes.length; i < len; i++) {
- if (domPrefixes[i] + dir in elem) {
- return true;
- }
- }
- }
- return false;
- });
-
-
- // Run each test
- testRunner();
-
- delete ModernizrProto.addTest;
- delete ModernizrProto.addAsyncTest;
-
- // Run the things that are supposed to run after the tests
- for (var i = 0; i < Modernizr._q.length; i++) {
- Modernizr._q[i]();
- }
-
- // Leak Modernizr namespace
- //window.Modernizr = Modernizr;
- dwv.Modernizr = Modernizr;
-
-
-;
-
-//})(window, document);
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.utils = dwv.utils || {};
-
-/**
- * Multiple progresses handler.
- * Stores a multi dimensional list of progresses to allow to
- * calculate a global progress.
- * @param {Function} callback The function to pass the global progress to.
- */
-dwv.utils.MultiProgressHandler = function (callback)
-{
- // closure to self
- var self = this;
-
- /**
- * List of progresses.
- * @private
- * @type Array
- */
- var progresses = [];
-
- /**
- * Number of dimensions.
- * @private
- * @type Number
- */
- var numberOfDimensions = 2;
-
- /**
- * Set the number of dimensions.
- * @param {Number} num The number.
- */
- this.setNumberOfDimensions = function (num) {
- numberOfDimensions = num;
- };
-
- /**
- * Set the number of data to load.
- * @param {Number} n The number of data to load.
- */
- this.setNToLoad = function (n) {
- for ( var i = 0; i < n; ++i ) {
- progresses[i] = [];
- for ( var j = 0; j < numberOfDimensions; ++j ) {
- progresses[i][j] = 0;
- }
- }
- };
-
- /**
- * Handle a load progress.
- * @param {Object} event The progress event.
- */
- this.onprogress = function (event) {
- // check event
- if ( !event.lengthComputable ) {
- return;
- }
- if ( typeof event.subindex === "undefined" ) {
- return;
- }
- if ( typeof event.index === "undefined" ) {
- return;
- }
- // calculate percent
- var percent = (event.loaded * 100) / event.total;
- // set percent for index
- progresses[event.index][event.subindex] = percent;
-
- // call callback
- callback({'type': event.type, 'lengthComputable': true,
- 'loaded': getGlobalPercent(), 'total': 100});
- };
-
- /**
- * Get the global load percent including the provided one.
- * @return {Number} The accumulated percentage.
- */
- function getGlobalPercent() {
- var sum = 0;
- var lenprog = progresses.length;
- for ( var i = 0; i < lenprog; ++i ) {
- for ( var j = 0; j < numberOfDimensions; ++j ) {
- sum += progresses[i][j];
- }
- }
- return Math.round( sum / (lenprog * numberOfDimensions) );
- }
-
- /**
- * Create a mono progress event handler.
- * @param {Number} index The index of the data.
- * @param {Number} subindex The sub-index of the data.
- */
- this.getMonoProgressHandler = function (index, subindex) {
- return function (event) {
- event.index = index;
- event.subindex = subindex;
- self.onprogress(event);
- };
- };
-
- /**
- * Create a mono loadend event handler: sends a 100% progress.
- * @param {Number} index The index of the data.
- * @param {Number} subindex The sub-index of the data.
- */
- this.getMonoOnLoadEndHandler = function (index, subindex) {
- return function () {
- self.onprogress({'type': 'load-progress', 'lengthComputable': true,
- 'loaded': 100, 'total': 100,
- 'index': index, 'subindex': subindex});
- };
- };
-
- /**
- * Create a mono progress event handler with an undefined index.
- * Warning: The caller handles the progress index.
- * @param {Number} subindex The sub-index of the data.
- */
- this.getUndefinedMonoProgressHandler = function (subindex) {
- return function (event) {
- event.subindex = subindex;
- self.onprogress(event);
- };
- };
-};
-
-// namespaces
-var dwv = dwv || {};
-/** @namespace */
-dwv.utils = dwv.utils || {};
-
-/**
- * Capitalise the first letter of a string.
- * @param {String} string The string to capitalise the first letter.
- * @return {String} The new string.
- */
-dwv.utils.capitaliseFirstLetter = function (string)
-{
- var res = string;
- if ( string ) {
- res = string.charAt(0).toUpperCase() + string.slice(1);
- }
- return res;
-};
-
-/**
- * Split key/value string:
- * key0=val00&key0=val01&key1=val10 returns
- * { key0 : [val00, val01], key1 : val1 }
- * @param {String} inputStr The string to split.
- * @return {Object} The split string.
- */
-dwv.utils.splitKeyValueString = function (inputStr)
-{
- // result
- var result = {};
- // check input string
- if ( inputStr ) {
- // split key/value pairs
- var pairs = inputStr.split('&');
- for ( var i = 0; i < pairs.length; ++i )
- {
- var pair = pairs[i].split('=');
- // if the key does not exist, create it
- if ( !result[pair[0]] )
- {
- result[pair[0]] = pair[1];
- }
- else
- {
- // make it an array
- if ( !( result[pair[0]] instanceof Array ) ) {
- result[pair[0]] = [result[pair[0]]];
- }
- result[pair[0]].push(pair[1]);
- }
- }
- }
- return result;
-};
-
-/**
- * Replace flags in a input string. Flags are keywords surrounded with curly
- * braces.
- * @param {String} inputStr The input string.
- * @param {Object} values A object of {value, unit}.
- * @example
- * var values = {"length": { "value": 33, "unit": "cm" } };
- * var str = "The length is: {length}.";
- * var res = dwv.utils.replaceFlags(str, values); // "The length is: 33 cm."
- * @return {String} The result string.
- */
-dwv.utils.replaceFlags = function (inputStr, values)
-{
- var res = "";
- // check input string
- if (inputStr === null || typeof inputStr === "undefined") {
- return res;
- }
- res = inputStr;
- // check values
- if (values === null || typeof values === "undefined") {
- return res;
- }
- // loop through values keys
- var keys = Object.keys(values);
- for (var i = 0; i < keys.length; ++i) {
- var valueObj = values[keys[i]];
- if ( valueObj !== null && typeof valueObj !== "undefined" &&
- valueObj.value !== null && typeof valueObj.value !== "undefined") {
- // value string
- var valueStr = valueObj.value.toPrecision(4);
- // add unit if available
- // space or no space? Yes apart from degree...
- // check: https://en.wikipedia.org/wiki/Space_(punctuation)#Spaces_and_unit_symbols
- if (valueObj.unit !== null && typeof valueObj.unit !== "undefined" &&
- valueObj.unit.length !== 0) {
- if (valueObj.unit !== "degree") {
- valueStr += " ";
- }
- valueStr += valueObj.unit;
- }
- // flag to replace
- var flag = '{' + keys[i] + '}';
- // replace
- res = res.replace(flag, valueStr);
- }
- }
- // return
- return res;
-};
-
-/**
- * Replace flags in a input string. Flags are keywords surrounded with curly
- * braces.
- * @param {String} inputStr The input string.
- * @param {Array} values An array of strings.
- * @example
- * var values = ["a", "b"];
- * var str = "The length is: {v0}. The size is: {v1}";
- * var res = dwv.utils.replaceFlags2(str, values); // "The length is: a. The size is: b"
- * @return {String} The result string.
- */
-dwv.utils.replaceFlags2 = function (inputStr, values)
-{
- var res = inputStr;
- for ( var j = 0; j < values.length; ++j ) {
- res = res.replace("{v"+j+"}", values[j]);
- }
- return res;
-};
-
-dwv.utils.createDefaultReplaceFormat = function (values)
-{
- var res = "";
- for ( var j = 0; j < values.length; ++j ) {
- if ( j !== 0 ) {
- res += ", ";
- }
- res += "{v"+j+"}";
- }
- return res;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.utils = dwv.utils || {};
-
-/**
- * Thread Pool.
- * Highly inspired from {@link http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool}.
- * @constructor
- * @param {Number} poolSize The size of the pool.
- */
-dwv.utils.ThreadPool = function (poolSize) {
-
- // task queue
- var taskQueue = [];
- // lsit of available threads
- var freeThreads = [];
- // list of running threads (unsed in abort)
- var runningThreads = [];
-
- /**
- * Initialise.
- */
- this.init = function () {
- // create 'poolSize' number of worker threads
- for (var i = 0; i < poolSize; ++i) {
- freeThreads.push(new dwv.utils.WorkerThread(this));
- }
- };
-
- /**
- * Add a worker task to the queue.
- * Will be run when a thread is made available.
- * @return {Object} workerTask The task to add.
- */
- this.addWorkerTask = function (workerTask) {
- if (freeThreads.length > 0) {
- // get the first free worker thread
- var workerThread = freeThreads.shift();
- // run the input task
- workerThread.run(workerTask);
- // add the thread to the runnning list
- runningThreads.push(workerThread);
- } else {
- // no free thread, add task to queue
- taskQueue.push(workerTask);
- }
- };
-
- /**
- * Abort all threads.
- */
- this.abort = function () {
- // clear tasks
- taskQueue = [];
- // cancel running workers
- for (var i = 0; i < runningThreads.length; ++i) {
- runningThreads[i].stop();
- }
- runningThreads = [];
- // re-init
- this.init();
- };
-
- /**
- * Free a worker thread.
- * @param {Object} workerThread The thread to free.
- */
- this.freeWorkerThread = function (workerThread) {
- // send worker end
- this.onworkerend();
-
- if (taskQueue.length > 0) {
- // get waiting task
- var workerTask = taskQueue.shift();
- // use input thread to run the waiting task
- workerThread.run(workerTask);
- } else {
- // no task to run, add to free list
- freeThreads.push(workerThread);
- // remove from running list
- for ( var i = 0; i < runningThreads.length; ++i ) {
- if ( runningThreads[i].getId() === workerThread.getId() ) {
- runningThreads.splice(i, 1);
- }
- }
- // the work is done when the queue is back to its initial size
- if ( freeThreads.length === poolSize ) {
- this.onpoolworkend();
- }
- }
- };
-};
-
-/**
- * Handle a pool work end event.
- * Default does nothing.
- */
-dwv.utils.ThreadPool.prototype.onpoolworkend = function () {};
-/**
- * Handle a pool worker end event.
- * Default does nothing.
- */
-dwv.utils.ThreadPool.prototype.onworkerend = function () {};
-
-/**
- * Worker thread.
- * @external Worker
- * @constructor
- * @param {Object} parentPool The parent pool.
- */
-dwv.utils.WorkerThread = function (parentPool) {
- // closure to self
- var self = this;
-
- // thread ID
- var id = Math.random().toString(36).substring(2, 15);
-
- // running task
- var runningTask = {};
- // worker used to run task
- var worker;
-
- /**
- * Get the thread ID.
- * @return {String} The thread ID (alphanumeric).
- */
- this.getId = function () {
- return id;
- };
-
- /**
- * Run a worker task
- * @param {Object} workerTask The task to run.
- */
- this.run = function (workerTask) {
- // store task
- runningTask = workerTask;
- // create a new web worker
- if (runningTask.script !== null) {
- worker = new Worker(runningTask.script);
- worker.addEventListener('message', ontaskend, false);
- // launch the worker
- worker.postMessage(runningTask.startMessage);
- }
- };
-
- /**
- * Stop a run and free the thread.
- */
- this.stop = function () {
- // stop the worker
- worker.terminate();
- // tell the parent pool this thread is free
- parentPool.freeWorkerThread(this);
- };
-
- /**
- * Handle once the task is done.
- * For now assume we only get a single callback from a worker
- * which also indicates the end of this worker.
- * @param {Object} event The callback event.
- */
- function ontaskend(event) {
- // pass to original callback
- runningTask.callback(event);
- // stop the worker and free the thread
- self.stop();
- }
-
-};
-
-/**
- * Worker task.
- * @constructor
- * @param {String} script The worker script.
- * @param {Function} callback The worker callback.
- * @param {Object} message The data to pass to the worker.
- */
-dwv.utils.WorkerTask = function (script, callback, message) {
- // worker script
- this.script = script;
- // worker callback
- this.callback = callback;
- // worker start message
- this.startMessage = message;
-};
-
-// namespaces
-var dwv = dwv || {};
-dwv.utils = dwv.utils || {};
-/** @namespace */
-dwv.utils.base = dwv.utils.base || {};
-
-/**
- * Split an input URI:
- * 'root?key0=val00&key0=val01&key1=val10' returns
- * { base : root, query : [ key0 : [val00, val01], key1 : val1 ] }
- * Returns an empty object if the input string is not correct (null, empty...)
- * or if it is not a query string (no question mark).
- * @param {String} inputStr The string to split.
- * @return {Object} The split string.
- */
-dwv.utils.splitUri = function (uri)
-{
- // result
- var result = {};
- // check if query string
- var sepIndex = null;
- if ( uri && (sepIndex = uri.indexOf('?')) !== -1 ) {
- // base: before the '?'
- result.base = uri.substr(0, sepIndex);
- // query : after the '?' and until possible '#'
- var hashIndex = uri.indexOf('#');
- if ( hashIndex === -1 ) {
- hashIndex = uri.length;
- }
- var query = uri.substr(sepIndex + 1, (hashIndex - 1 - sepIndex));
- // split key/value pairs of the query
- result.query = dwv.utils.splitKeyValueString(query);
- }
- // return
- return result;
-};
-
-/**
- * Get the query part, split into an array, of an input URI.
- * The URI scheme is: 'base?query#fragment'
- * @param {String } uri The input URI.
- * @return {Object} The query part, split into an array, of the input URI.
- */
-dwv.utils.getUriQuery = function (uri)
-{
- // split
- var parts = dwv.utils.splitUri(uri);
- // check not empty
- if ( Object.keys(parts).length === 0 ) {
- return null;
- }
- // return query
- return parts.query;
-};
-
-/**
- * Generic URI query decoder.
- * Supports manifest:
- * [dwv root]?input=encodeURIComponent('[manifest file]')&type=manifest
- * or encoded URI with base and key value/pairs:
- * [dwv root]?input=encodeURIComponent([root]?key0=value0&key1=value1)
- * @param {String} query The query part to the input URI.
- * @param {Function} callback The function to call with the decoded file urls.
- */
-dwv.utils.base.decodeQuery = function (query, callback)
-{
- // manifest
- if ( query.type && query.type === "manifest" ) {
- dwv.utils.decodeManifestQuery( query, callback );
- }
- // default case: encoded URI with base and key/value pairs
- else {
- callback( dwv.utils.decodeKeyValueUri( query.input, query.dwvReplaceMode ) );
- }
-};
-
-/**
- * Decode a Key/Value pair URI. If a key is repeated, the result
- * be an array of base + each key.
- * @param {String} uri The URI to decode.
- * @param {String} replaceMode The key replace more.
- * replaceMode can be:
- * - key (default): keep the key
- * - other than key: do not use the key
- * 'file' is a special case where the '?' of the query is not kept.
- * @return The list of input file urls.
- */
-dwv.utils.decodeKeyValueUri = function (uri, replaceMode)
-{
- var result = [];
-
- // repeat key replace mode (default to keep key)
- var repeatKeyReplaceMode = "key";
- if ( replaceMode ) {
- repeatKeyReplaceMode = replaceMode;
- }
-
- // decode input URI
- var queryUri = decodeURIComponent(uri);
- // get key/value pairs from input URI
- var inputQueryPairs = dwv.utils.splitUri(queryUri);
- if ( Object.keys(inputQueryPairs).length === 0 )
- {
- result.push(queryUri);
- }
- else
- {
- var keys = Object.keys(inputQueryPairs.query);
- // find repeat key
- var repeatKey = null;
- for ( var i = 0; i < keys.length; ++i )
- {
- if ( inputQueryPairs.query[keys[i]] instanceof Array )
- {
- repeatKey = keys[i];
- break;
- }
- }
-
- if ( !repeatKey )
- {
- result.push(queryUri);
- }
- else
- {
- var repeatList = inputQueryPairs.query[repeatKey];
- // build base uri
- var baseUrl = inputQueryPairs.base;
- // do not add '?' when the repeatKey is 'file'
- // root/path/to/?file=0.jpg&file=1.jpg
- if ( repeatKey !== "file" ) {
- baseUrl += "?";
- }
- var gotOneArg = false;
- for ( var j = 0; j < keys.length; ++j )
- {
- if ( keys[j] !== repeatKey ) {
- if ( gotOneArg ) {
- baseUrl += "&";
- }
- baseUrl += keys[j] + "=" + inputQueryPairs.query[keys[j]];
- gotOneArg = true;
- }
- }
- // append built urls to result
- var url;
- for ( var k = 0; k < repeatList.length; ++k )
- {
- url = baseUrl;
- if ( gotOneArg ) {
- url += "&";
- }
- if ( repeatKeyReplaceMode === "key" ) {
- url += repeatKey + "=";
- }
- // other than 'key' mode: do nothing
- url += repeatList[k];
- result.push(url);
- }
- }
- }
- // return
- return result;
-};
-
-/**
- * Decode a manifest query.
- * @external XMLHttpRequest
- * @param {Object} query The manifest query: {input, nslices},
- * with input the input URI and nslices the number of slices.
- * @param {Function} The function to call with the decoded urls.
- */
-dwv.utils.decodeManifestQuery = function (query, callback)
-{
- var uri = "";
- if ( query.input[0] === '/' ) {
- uri = window.location.protocol + "//" + window.location.host;
- }
- // TODO: needs to be decoded (decodeURIComponent?
- uri += query.input;
-
- // handle error
- var onError = function (/*event*/)
- {
- console.warn( "RequestError while receiving manifest: "+this.status );
- };
-
- // handle load
- var onLoad = function (/*event*/)
- {
- callback( dwv.utils.decodeManifest(this.responseXML, query.nslices) );
- };
-
- var request = new XMLHttpRequest();
- request.open('GET', decodeURIComponent(uri), true);
- request.responseType = "document";
- request.onload = onLoad;
- request.onerror = onError;
- request.send(null);
-};
-
-/**
- * Decode an XML manifest.
- * @param {Object} manifest The manifest to decode.
- * @param {Number} nslices The number of slices to load.
- */
-dwv.utils.decodeManifest = function (manifest, nslices)
-{
- var result = [];
- // wado url
- var wadoElement = manifest.getElementsByTagName("wado_query");
- var wadoURL = wadoElement[0].getAttribute("wadoURL");
- var rootURL = wadoURL + "?requestType=WADO&contentType=application/dicom&";
- // patient list
- var patientList = manifest.getElementsByTagName("Patient");
- if ( patientList.length > 1 ) {
- console.warn("More than one patient, loading first one.");
- }
- // study list
- var studyList = patientList[0].getElementsByTagName("Study");
- if ( studyList.length > 1 ) {
- console.warn("More than one study, loading first one.");
- }
- var studyUID = studyList[0].getAttribute("StudyInstanceUID");
- // series list
- var seriesList = studyList[0].getElementsByTagName("Series");
- if ( seriesList.length > 1 ) {
- console.warn("More than one series, loading first one.");
- }
- var seriesUID = seriesList[0].getAttribute("SeriesInstanceUID");
- // instance list
- var instanceList = seriesList[0].getElementsByTagName("Instance");
- // loop on instances and push links
- var max = instanceList.length;
- if ( nslices < max ) {
- max = nslices;
- }
- for ( var i = 0; i < max; ++i ) {
- var sopInstanceUID = instanceList[i].getAttribute("SOPInstanceUID");
- var link = rootURL +
- "&studyUID=" + studyUID +
- "&seriesUID=" + seriesUID +
- "&objectUID=" + sopInstanceUID;
- result.push( link );
- }
- // return
- return result;
-};
-
- return dwv;
-}));
diff --git a/anatomy/static/js/dwv/dwv.min.js b/anatomy/static/js/dwv/dwv.min.js
deleted file mode 100644
index 67477b10..00000000
--- a/anatomy/static/js/dwv/dwv.min.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/*! dwv 0.23.3 25-03-2018 */
-
-!function(e,t){"function"==typeof define&&define.amd?define(["i18next","i18next-xhr-backend","i18next-browser-languagedetector","jszip","konva"],t):"object"==typeof module&&module.exports?module.exports=t(require("i18next"),require("i18next-xhr-backend"),require("i18next-browser-languagedetector"),require("jszip"),null,null):e.dwv=t(e.i18next,e.i18nextXHRBackend,e.i18nextBrowserLanguageDetector,e.JSZip,e.Konva,e.MagicWand)}(this,function(a,s,l,t,T,f){var W=void 0!==W?W:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};(H=H||{}).App=function(){var v,o=this,a=null,s=null,l=null,u=0,c=0,d=0,T=null,r=1,L=!1,S=1,i={x:0,y:0},x={x:0,y:0},g=null,m=null,h=null,I=null,P=null,p=null,f=null,C=new H.html.Style,F=null,A=null,y=null,E=null,D={},w="./";function O(e,t){R([e],new H.io.UrlsLoader,{requestHeaders:t})}function b(e,t,n){y=t;var i=W.onkeydown;W.onkeydown=function(e){e.ctrlKey&&88===e.keyCode&&(console.log("crtl-x pressed!"),o.abortLoad())},o.reset();var r="";r=void 0!==e[0].name?e[0].name:e[0],d=1===e.length&&"zip"!==r.split(".").pop().toLowerCase(),t.setDefaultCharacterSet(v),t.onload=function(e){a&&g.append(e.view),function(e){if(g)return;g=e.view,m=new H.ViewController(g),I&&I.update(e.info);s=g.getImage();var t=(a=s).getGeometry().getSize();u=t.getNumberOfColumns(),c=t.getNumberOfRows(),function(e,t){var n=o.getElement("imageLayer");(p=new H.html.Layer(n)).initialise(e,t),p.fillContext(),p.setStyleDisplay(!0);var i=o.getElement("drawDiv");i&&(f=new H.DrawController(i)).create(e,t),L?o.fitToSize(H.gui.getWindowSize()):o.fitToSize({width:o.getElement("layerContainer").offsetWidth,height:o.getElement("layerContainer").offsetHeight}),o.resetLayout()}(u,c),l=p.getContext().createImageData(u,c),g.addEventListener("wl-width-change",o.onWLChange),g.addEventListener("wl-center-change",o.onWLChange),g.addEventListener("colour-change",o.onColourChange),g.addEventListener("slice-change",o.onSliceChange),g.addEventListener("frame-change",o.onFrameChange),g.addEventListener("wl-width-change",q),g.addEventListener("wl-center-change",q),g.addEventListener("colour-change",q),g.addEventListener("position-change",q),g.addEventListener("slice-change",q),g.addEventListener("frame-change",q);F&&F.initAndDisplay(p);var n=o.getElement("dropBox");if(n){n.removeEventListener("dragover",Q),n.removeEventListener("dragleave",B),n.removeEventListener("drop",V),H.html.removeNode(n);var i=o.getElement("layerContainer");i.addEventListener("dragover",Q),i.addEventListener("dragleave",B),i.addEventListener("drop",V)}o.getElement("infoLayer")&&((h=new H.InfoController(T)).create(o),h.toggleListeners(o,g));o.initWLDisplay(),U()}(e)},t.onerror=function(e){G(e)},t.onabort=function(e){!function(e){e.message?console.warn(e.message):console.warn("Abort called.");H.gui.displayProgress(100)}(e)},t.onloadend=function(){W.onkeydown=i,f&&f.activateDrawLayer(m),q({type:"load-progress",lengthComputable:!0,loaded:100,total:100}),q({type:"load-end"}),y=null},t.onprogress=k,q({type:"load-start"}),t.load(e,n)}function R(e,t,n){t.onload=function(e){var t=new H.State;t.apply(o,t.fromJSON(e))},t.onerror=function(e){G(e)},t.load(e,n)}function q(e){if(void 0!==D[e.type])for(var t=0;tc){for(var p=h.length/c,f=[],C=0,y=0;y"+H.i18n("basics.visible")+"";for(var c=1;c]+>/g,"").toLowerCase().indexOf(n[o])<0?i="none":n[o].length&&H.html.highlight(n[o],t.rows[r]),t.rows[r].style.display=i}},H.html.dehighlight=function(e){for(var t=0;t]+>/g,"")),n.parentNode);3!==n.nodeType&&H.html.dehighlight(n)}},H.html.highlight=function(e,t){for(var n=0;n=t){var l=s[t].firstChild.data;s[t].firstChild.data=H.i18n(r+l+o)}}},H.html.makeCellEditable=function(e,t,n){if(void 0!==e){var i=document.createElement("input");t?i.onchange=t:i.disabled=!0,i.value=e.firstChild.data,void 0===n||"color"===n&&!H.browser.hasInputColor()?i.type="text":i.type=n,H.html.cleanNode(e);var r=document.createElement("form");r.onsubmit=function(e){e.preventDefault()},r.appendChild(i),e.appendChild(r)}else console.warn("Cannot create input for non existing cell.")},H.html.setCursorToPointer=function(){document.body.style.cursor="pointer"},H.html.setCursorToDefault=function(){document.body.style.cursor="default"},H.html.createHtmlSelect=function(e,t,n,i){var r=document.createElement("select");r.className=e;var o,a=void 0===n?"":n+".",s=void 0!==i,l=function(e){var t=a+e+".name";return s?H.i18nExists(t)?H.i18n(t):e:H.i18n(t)};if(t instanceof Array)for(var u in t)t.hasOwnProperty(u)&&((o=document.createElement("option")).value=t[u],o.appendChild(document.createTextNode(l(t[u]))),r.appendChild(o));else{if("object"!=typeof t)throw new Error("Unsupported input list type.");for(var c in t)(o=document.createElement("option")).value=c,o.appendChild(document.createTextNode(l(c))),r.appendChild(o)}return r},H.html.displayElement=function(e,t){e.style.display=t?"":"none"},H.html.toggleDisplay=function(e){"none"===e.style.display?e.style.display="":e.style.display="none"},H.html.appendElement=function(e,t){e.appendChild(t),H.gui.refreshElement(e)},H.html.createHiddenElement=function(e,t){var n=document.createElement(e);return n.className=t,n.style.display="none",n},(H=H||{}).gui=H.gui||{},H.gui.base=H.gui.base||{},H.gui.info=H.gui.info||{},H.gui.base.plot=function(){},H.gui.info.MiniColourMap=function(h,p){this.create=function(){var e=h.getElementsByClassName("colour-map-info");0!==e.length&&H.html.removeNodes(e);var t=document.createElement("canvas");t.className="colour-map-info",t.width=98,t.height=10,h.appendChild(t)},this.update=function(e){for(var t,n=e.wc,i=e.ww,r=h.getElementsByClassName("colour-map-info")[0],o=r.getContext("2d"),a=p.getViewController().getColourMap(),s=o.getImageData(0,0,r.width,r.height),l=0,u=p.getImage().getRescaledDataRange().min,c=(p.getImage().getRescaledDataRange().max-u)/r.width,d=0,S=n-.5-(i-1)/2,x=n-.5+(i-1)/2,g=0;gn.getMax()?t:e})},H.image.filter.Sharpen=function(){this.getName=function(){return"Sharpen"};var t=null;this.setOriginalImage=function(e){t=e},this.getOriginalImage=function(){return t}},H.image.filter.Sharpen.prototype.update=function(){return this.getOriginalImage().convolute2D([0,-1,0,-1,5,-1,0,-1,0])},H.image.filter.Sobel=function(){this.getName=function(){return"Sobel"};var t=null;this.setOriginalImage=function(e){t=e},this.getOriginalImage=function(){return t}},H.image.filter.Sobel.prototype.update=function(){var e=this.getOriginalImage(),t=e.convolute2D([1,0,-1,2,0,-2,1,0,-1]),n=e.convolute2D([1,2,1,0,0,0,-1,-2,-1]);return t.compose(n,function(e,t){return Math.sqrt(e*e+t*t)})},(H=H||{}).image=H.image||{},H.image.Size=function(e,t,n){this.getNumberOfColumns=function(){return e},this.getNumberOfRows=function(){return t},this.getNumberOfSlices=function(){return n||1}},H.image.Size.prototype.getSliceSize=function(){return this.getNumberOfColumns()*this.getNumberOfRows()},H.image.Size.prototype.getTotalSize=function(){return this.getSliceSize()*this.getNumberOfSlices()},H.image.Size.prototype.equals=function(e){return null!==e&&this.getNumberOfColumns()===e.getNumberOfColumns()&&this.getNumberOfRows()===e.getNumberOfRows()&&this.getNumberOfSlices()===e.getNumberOfSlices()},H.image.Size.prototype.isInBounds=function(e,t,n){return!(e<0||e>this.getNumberOfColumns()-1||t<0||t>this.getNumberOfRows()-1||n<0||n>this.getNumberOfSlices()-1)},H.image.Size.prototype.toString=function(){return"("+this.getNumberOfColumns()+", "+this.getNumberOfRows()+", "+this.getNumberOfSlices()+")"},H.image.Spacing=function(e,t,n){this.getColumnSpacing=function(){return e},this.getRowSpacing=function(){return t},this.getSliceSpacing=function(){return n||1}},H.image.Spacing.prototype.equals=function(e){return null!==e&&this.getColumnSpacing()===e.getColumnSpacing()&&this.getRowSpacing()===e.getRowSpacing()&&this.getSliceSpacing()===e.getSliceSpacing()},H.image.Spacing.prototype.toString=function(){return"("+this.getColumnSpacing()+", "+this.getRowSpacing()+", "+this.getSliceSpacing()+")"},H.image.Geometry=function(e,n,t,o){void 0===e&&(e=new H.math.Point3D(0,0,0));var a=[e];void 0===o&&(o=new H.math.getIdentityMat33),this.getOrigin=function(){return e},this.getOrigins=function(){return a},this.getSize=function(){return n},this.getSpacing=function(){return t},this.getOrientation=function(){return o},this.getSliceIndex=function(e){for(var t=0,n=e.getDistance(a[0]),i=0,r=0;r=2*H.image.lut.range_max/3?H.image.lut.range_max-1:0},H.image.lut.toMaxFirstThird=function(e){var t=3*e;return t>H.image.lut.range_max-1?H.image.lut.range_max-1:t},H.image.lut.toMaxSecondThird=function(e){var t=H.image.lut.range_max/3,n=0;return t<=e&&(n=3*(e-t))>H.image.lut.range_max-1?H.image.lut.range_max-1:n},H.image.lut.toMaxThirdThird=function(e){var t=H.image.lut.range_max/3,n=0;return 2*t<=e&&(n=3*(e-2*t))>H.image.lut.range_max-1?H.image.lut.range_max-1:n},H.image.lut.zero=function(){return 0},H.image.lut.id=function(e){return e},H.image.lut.invId=function(e){return H.image.lut.range_max-1-e},H.image.lut.plain={red:H.image.lut.buildLut(H.image.lut.id),green:H.image.lut.buildLut(H.image.lut.id),blue:H.image.lut.buildLut(H.image.lut.id)},H.image.lut.invPlain={red:H.image.lut.buildLut(H.image.lut.invId),green:H.image.lut.buildLut(H.image.lut.invId),blue:H.image.lut.buildLut(H.image.lut.invId)},H.image.lut.rainbow={blue:[0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,255,247,239,231,223,215,207,199,191,183,175,167,159,151,143,135,127,119,111,103,95,87,79,71,63,55,47,39,31,23,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],green:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128,136,144,152,160,168,176,184,192,200,208,216,224,232,240,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,253,251,249,247,245,243,241,239,237,235,233,231,229,227,225,223,221,219,217,215,213,211,209,207,205,203,201,199,197,195,193,192,189,186,183,180,177,174,171,168,165,162,159,156,153,150,147,144,141,138,135,132,129,126,123,120,117,114,111,108,105,102,99,96,93,90,87,84,81,78,75,72,69,66,63,60,57,54,51,48,45,42,39,36,33,30,27,24,21,18,15,12,9,6,3],red:[0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,62,60,58,56,54,52,50,48,46,44,42,40,38,36,34,32,30,28,26,24,22,20,18,16,14,12,10,8,6,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]},H.image.lut.hot={red:H.image.lut.buildLut(H.image.lut.toMaxFirstThird),green:H.image.lut.buildLut(H.image.lut.toMaxSecondThird),blue:H.image.lut.buildLut(H.image.lut.toMaxThirdThird)},H.image.lut.hot_iron={red:[0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,228,230,232,234,236,238,240,242,244,246,248,250,252,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],green:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,228,230,232,234,236,238,240,242,244,246,248,250,252,255],blue:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,255]},H.image.lut.pet={red:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205,207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],green:[0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99,101,103,105,107,109,111,113,115,117,119,121,123,125,128,126,124,122,120,118,116,114,112,110,108,106,104,102,100,98,96,94,92,90,88,86,84,82,80,78,76,74,72,70,68,66,64,63,61,59,57,55,53,51,49,47,45,43,41,39,37,35,33,31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,228,230,232,234,236,238,240,242,244,246,248,250,252,255],blue:[0,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147,149,151,153,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,195,197,199,201,203,205,207,209,211,213,215,217,219,221,223,225,227,229,231,233,235,237,239,241,243,245,247,249,251,253,255,252,248,244,240,236,232,228,224,220,216,212,208,204,200,196,192,188,184,180,176,172,168,164,160,156,152,148,144,140,136,132,128,124,120,116,112,108,104,100,96,92,88,84,80,76,72,68,64,60,56,52,48,44,40,36,32,28,24,20,16,12,8,4,0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,85,89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,170,174,178,182,186,190,194,198,202,206,210,214,218,222,226,230,234,238,242,246,250,255]},H.image.lut.hot_metal_blue={red:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,6,9,12,15,18,21,24,26,29,32,35,38,41,44,47,50,52,55,57,59,62,64,66,69,71,74,76,78,81,83,85,88,90,93,96,99,102,105,108,111,114,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,166,169,172,175,178,181,184,187,190,194,198,201,205,209,213,217,221,224,228,232,236,240,244,247,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],green:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,6,8,9,11,13,15,17,19,21,23,24,26,28,30,32,34,36,38,40,41,43,45,47,49,51,53,55,56,58,60,62,64,66,68,70,72,73,75,77,79,81,83,85,87,88,90,92,94,96,98,100,102,104,105,107,109,111,113,115,117,119,120,122,124,126,128,130,132,134,136,137,139,141,143,145,147,149,151,152,154,156,158,160,162,164,166,168,169,171,173,175,177,179,181,183,184,186,188,190,192,194,196,198,200,201,203,205,207,209,211,213,215,216,218,220,222,224,226,228,229,231,233,235,237,239,240,242,244,246,248,250,251,253,255],blue:[0,2,4,6,8,10,12,14,16,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,117,119,121,123,125,127,129,131,133,135,137,139,141,143,145,147,149,151,153,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,184,186,188,190,192,194,196,198,200,197,194,191,188,185,182,179,176,174,171,168,165,162,159,156,153,150,144,138,132,126,121,115,109,103,97,91,85,79,74,68,62,56,50,47,44,41,38,35,32,29,26,24,21,18,15,12,9,6,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,6,9,12,15,18,21,24,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,74,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,126,129,132,135,138,141,144,147,150,153,156,159,162,165,168,171,174,176,179,182,185,188,191,194,197,200,203,206,210,213,216,219,223,226,229,232,236,239,242,245,249,252,255]},H.image.lut.pet_20step={red:[0,0,0,0,0,0,0,0,0,0,0,0,0,96,96,96,96,96,96,96,96,96,96,96,96,96,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,80,80,80,80,80,80,80,80,80,80,80,80,80,96,96,96,96,96,96,96,96,96,96,96,96,96,112,112,112,112,112,112,112,112,112,112,112,112,112,128,128,128,128,128,128,128,128,128,128,128,128,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,80,80,80,80,80,80,80,80,80,80,80,80,80,64,64,64,64,64,64,64,64,64,64,64,64,224,224,224,224,224,224,224,224,224,224,224,224,224,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,192,192,192,192,192,192,192,192,192,192,192,192,192,176,176,176,176,176,176,176,176,176,176,176,176,176,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],green:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,80,80,80,80,80,80,80,80,80,80,80,80,80,96,96,96,96,96,96,96,96,96,96,96,96,96,112,112,112,112,112,112,112,112,112,112,112,112,112,128,128,128,128,128,128,128,128,128,128,128,128,96,96,96,96,96,96,96,96,96,96,96,96,96,144,144,144,144,144,144,144,144,144,144,144,144,144,192,192,192,192,192,192,192,192,192,192,192,192,192,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,208,208,208,208,208,208,208,208,208,208,208,208,208,176,176,176,176,176,176,176,176,176,176,176,176,176,144,144,144,144,144,144,144,144,144,144,144,144,96,96,96,96,96,96,96,96,96,96,96,96,96,48,48,48,48,48,48,48,48,48,48,48,48,48,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255],blue:[0,0,0,0,0,0,0,0,0,0,0,0,0,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,112,112,112,112,112,112,112,112,112,112,112,112,128,128,128,128,128,128,128,128,128,128,128,128,128,176,176,176,176,176,176,176,176,176,176,176,176,176,192,192,192,192,192,192,192,192,192,192,192,192,192,224,224,224,224,224,224,224,224,224,224,224,224,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,80,80,80,80,80,80,80,80,80,80,80,80,80,64,64,64,64,64,64,64,64,64,64,64,64,80,80,80,80,80,80,80,80,80,80,80,80,80,96,96,96,96,96,96,96,96,96,96,96,96,96,64,64,64,64,64,64,64,64,64,64,64,64,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255]},H.image.lut.test={red:H.image.lut.buildLut(H.image.lut.id),green:H.image.lut.buildLut(H.image.lut.zero),blue:H.image.lut.buildLut(H.image.lut.zero)},(H=H||{}).image=H.image||{},H.image.WindowLevel=function(t,n){if(0===n)throw new Error("A window level with a width of zero is not possible.");var i=0,r=0,o=255,a=null,s=null,l=null,u=null;function c(){var e=t+i;a=e-.5-(n-1)/2,s=e-.5+(n-1)/2,l=(o-r)/(n-1),u=(-(e-.5)/(n-1)+.5)*(o-r)+r}c(),this.getCenter=function(){return t},this.getWidth=function(){return n},this.setRange=function(e,t){r=parseInt(e,10),o=parseInt(t,10),c()},this.setSignedOffset=function(e){i=e,c()},this.apply=function(e){return e<=a?r:s=d.getNumberOfFrames())&&(n!==(n=e)&&1!==d.getNumberOfFrames()&&(this.fireEvent({type:"frame-change",frame:n}),this.setCurrentPosition(this.getCurrentPosition(),!0)),!0)},this.append=function(e){var t=this.getImage().appendSlice(e.getImage());t<=this.getCurrentPosition().k&&this.setCurrentPosition({i:this.getCurrentPosition().i,j:this.getCurrentPosition().j,k:this.getCurrentPosition().k+1},!0),this.addWindowPresets(e.getWindowPresets(),t)},this.appendFrameBuffer=function(e){this.getImage().appendFrameBuffer(e)},this.setWindowLevel=function(e,t,n){if(1<=t){var i=this.getCurrentPosition().k,r=null,o=d.getRescaleSlopeAndIntercept(i);if(o&&void 0!==o){var a=S[o.toString()];a&&void 0!==a&&(r=a.getWindowLevel())}void 0===n&&(n="manual"),x=n;var s=new H.image.WindowLevel(e,t);if(0===Object.keys(S).length){var l=new H.image.lut.Rescale(d.getRescaleSlopeAndIntercept(0),d.getMeta().BitsStored),u=new H.image.lut.Window(l,d.getMeta().IsSigned);this.addWindowLut(u)}for(var c in S)S[c].setWindowLevel(s);r&&void 0!==r?(r.getWidth()!==t&&this.fireEvent({type:"wl-width-change",wc:e,ww:t}),r.getCenter()!==e&&this.fireEvent({type:"wl-center-change",wc:e,ww:t})):(this.fireEvent({type:"wl-width-change",wc:e,ww:t}),this.fireEvent({type:"wl-center-change",wc:e,ww:t}))}},this.setWindowLevelPreset=function(e){var t=this.getWindowPresets()[e];if(void 0===t)throw new Error("Unknown window level preset: '"+e+"'");"minmax"===e&&void 0===t.wl&&(t.wl=this.getWindowLevelMinMax()),void 0!==t.perslice&&!0===t.perslice&&(t={wl:t.wl[this.getCurrentPosition().k]}),this.setWindowLevel(t.wl.getCenter(),t.wl.getWidth(),e)},this.setWindowLevelPresetById=function(e){var t=Object.keys(this.getWindowPresets());this.setWindowLevelPreset(t[e])},this.clone=function(){var e=new H.image.View(this.getImage());for(var t in S)e.addWindowLut(S[t]);return e.setListeners(this.getListeners()),e};var r={};this.getListeners=function(){return r},this.setListeners=function(e){r=e}},H.image.View.prototype.getWindowLevelMinMax=function(){var e=this.getImage().getRescaledDataRange(),t=e.min,n=e.max-t,i=t+n/2;return new H.image.WindowLevel(i,n)},H.image.View.prototype.setWindowLevelMinMax=function(){var e=this.getWindowLevelMinMax();this.setWindowLevel(e.getCenter(),e.getWidth(),"minmax")},H.image.View.prototype.generateImageData=function(e){var t=this.getCurrentWindowLut(),n=this.getImage(),i=n.getGeometry().getSize().getSliceSize(),r=i*this.getCurrentPosition().k,o=this.getCurrentFrame()?this.getCurrentFrame():0,a=0,s=0,l=0,u=n.getPhotometricInterpretation();switch(u){case"MONOCHROME1":case"MONOCHROME2":for(var c=this.getColourMap(),d=r+i,S=r;Sn.getEnd().getX()?0:-1,x=n.getBegin().getY()>n.getEnd().getY()?-1:.5,g=new T.Label({x:n.getEnd().getX()+25*S,y:n.getEnd().getY()+15*x,name:"label"});g.add(d),g.add(new T.Tag);var m=new T.Group;return m.name("line-group"),m.add(i),m.add(c),m.add(g),m.visible(!0),m},H.tool.UpdateArrow=function(e){var t=e.getParent(),n=t.getChildren(function(e){return"shape"===e.name()})[0],i=t.getChildren(function(e){return"shape-triangle"===e.name()})[0],r=t.getChildren(function(e){return"label"===e.name()})[0],o=t.getChildren(function(e){return"begin"===e.id()})[0],a=t.getChildren(function(e){return"end"===e.id()})[0];switch(e.id()){case"begin":o.x(e.x()),o.y(e.y());break;case"end":a.x(e.x()),a.y(e.y())}var s=o.x()-n.x(),l=o.y()-n.y(),u=a.x()-n.x(),c=a.y()-n.y();n.points([s,l,u,c]);var d=new H.math.Point2D(o.x(),o.y()),S=new H.math.Point2D(a.x(),a.y()),x=new H.math.Line(d,S),g=new H.math.Point2D(s,l),m=new H.math.Point2D(u,c),h=H.math.getPerpendicularLine(x,g,10),p=H.math.getPerpendicularLine(x,m,10);n.hitFunc(function(e){e.beginPath(),e.moveTo(h.getBegin().getX(),h.getBegin().getY()),e.lineTo(h.getEnd().getX(),h.getEnd().getY()),e.lineTo(p.getEnd().getX(),p.getEnd().getY()),e.lineTo(p.getBegin().getX(),p.getBegin().getY()),e.closePath(),e.fillStrokeShape(this)});var f=new H.math.Point2D(x.getBegin().getX(),x.getBegin().getY()-10),C=new H.math.Line(x.getBegin(),f),y=H.math.getAngle(x,C),D=y*Math.PI/180;i.x(x.getBegin().getX()+i.radius()*Math.sin(D)),i.y(x.getBegin().getY()+i.radius()*Math.cos(D)),i.rotation(-y);var v=r.getText();v.quant=null,v.setText(v.textExpr);var T=x.getBegin().getX()>x.getEnd().getX()?0:-1,L=x.getBegin().getY()>x.getEnd().getY()?-1:.5,I={x:x.getEnd().getX()+25*T,y:x.getEnd().getY()+15*L};r.position(I)},(H=H||{}).tool=H.tool||{};T=T||{};H.tool.Draw=function(d,e){var n=this,i=null,r=!1;this.shapeFactoryList=e;var t=null,o=null;this.shapeName=0;var a=[],s=null,S=new H.tool.ShapeEditor(d);S.setDrawEventCallback(f);var x=new T.Group,l=new T.Line({points:[-10,-10,10,10],stroke:"red"}),u=new T.Line({points:[10,-10,-10,10],stroke:"red"});x.add(l),x.add(u),this.style=new H.html.Style;var c={},g=null;function m(){h(!0)}function h(e){g.listening(e),g.hitGraphEnabled(e);var t=d.getDrawController().getCurrentPosGroup().getChildren();e?(d.addToolCanvasListeners(d.getDrawStage().getContent()),t.forEach(function(e){n.setShapeOn(e)})):(d.removeToolCanvasListeners(d.getDrawStage().getContent()),t.forEach(function(e){var t;(t=e).off("mouseover"),t.off("mouseout"),t.draggable(!1),t.off("dragstart.draw"),t.off("dragmove.draw"),t.off("dragend.draw"),t.off("dblclick")})),g.draw()}function p(e){var t=d.getDrawStage();return{x:t.offset().x+e.x/t.scale().x,y:t.offset().y+e.y/t.scale().y}}function f(e){if(void 0!==c[e.type])for(var t=0;ti.getEnd().getX()?0:-1,S=i.getBegin().getY()>i.getEnd().getY()?-1:.5,x=new T.Label({x:i.getEnd().getX()+25*d,y:i.getEnd().getY()+15*S,name:"label"});x.add(c),x.add(new T.Tag);var g=new T.Group;return g.name("ruler-group"),g.add(r),g.add(a),g.add(l),g.add(x),g.visible(!0),g},H.tool.UpdateRuler=function(e,t){var n=e.getParent(),i=n.getChildren(function(e){return"shape"===e.name()})[0],r=n.getChildren(function(e){return"shape-tick0"===e.name()})[0],o=n.getChildren(function(e){return"shape-tick1"===e.name()})[0],a=n.getChildren(function(e){return"label"===e.name()})[0],s=n.getChildren(function(e){return"begin"===e.id()})[0],l=n.getChildren(function(e){return"end"===e.id()})[0];switch(e.id()){case"begin":s.x(e.x()),s.y(e.y());break;case"end":l.x(e.x()),l.y(e.y())}var u=s.x()-i.x(),c=s.y()-i.y(),d=l.x()-i.x(),S=l.y()-i.y();i.points([u,c,d,S]);var x=new H.math.Point2D(s.x(),s.y()),g=new H.math.Point2D(l.x(),l.y()),m=new H.math.Line(x,g),h=new H.math.Point2D(u,c),p=new H.math.Point2D(d,S),f=H.math.getPerpendicularLine(m,h,10);r.points([f.getBegin().getX(),f.getBegin().getY(),f.getEnd().getX(),f.getEnd().getY()]);var C=H.math.getPerpendicularLine(m,p,10);o.points([C.getBegin().getX(),C.getBegin().getY(),C.getEnd().getX(),C.getEnd().getY()]),i.hitFunc(function(e){e.beginPath(),e.moveTo(f.getBegin().getX(),f.getBegin().getY()),e.lineTo(f.getEnd().getX(),f.getEnd().getY()),e.lineTo(C.getEnd().getX(),C.getEnd().getY()),e.lineTo(C.getBegin().getX(),C.getBegin().getY()),e.closePath(),e.fillStrokeShape(this)});var y=t.quantifyLine(m),D=a.getText();D.quant=y,D.setText(H.utils.replaceFlags(D.textExpr,D.quant));var v=m.getBegin().getX()>m.getEnd().getX()?0:-1,T=m.getBegin().getY()>m.getEnd().getY()?-1:.5,L={x:m.getEnd().getX()+25*v,y:m.getEnd().getY()+15*T};a.position(L)},(H=H||{}).tool=H.tool||{},H.tool.Scroll=function(o){var a=this,t=null;this.started=!1;var n=null;function i(e){var t=1!==o.getImage().getGeometry().getSize().getNumberOfSlices(),n=1!==o.getImage().getNumberOfFrames();e?t?o.getViewController().incrementSliceNb():n&&o.getViewController().incrementFrameNb():t?o.getViewController().decrementSliceNb():n&&o.getViewController().decrementFrameNb()}this.mousedown=function(e){o.getViewController().isPlaying()&&o.getViewController().stop(),a.started=!0,a.x0=e._x,a.y0=e._y},this.mousemove=function(e){if(a.started){var t=e._y-a.y0,n=15-1?t.replace(/###/g,"."):t}function r(){return!t||"string"==typeof t}for(var i="string"!=typeof e?[].concat(e):e.split(".");i.length>1;){if(r())return{};var s=o(i.shift());!t[s]&&n&&(t[s]=new n),t=t[s]}return r()?{}:{obj:t,k:o(i.shift())}}function o(t,e,o){var r=n(t,e,Object),i=r.obj,s=r.k;i[s]=o}function r(t,e,o,r){var i=n(t,e,Object),s=i.obj,a=i.k;s[a]=s[a]||[],r&&(s[a]=s[a].concat(o)),r||s[a].push(o)}function i(t,e){var o=n(t,e),r=o.obj,i=o.k;if(r)return r[i]}function s(t,e,n){for(var o in e)o in t?"string"==typeof t[o]||t[o]instanceof String||"string"==typeof e[o]||e[o]instanceof String?n&&(t[o]=e[o]):s(t[o],e[o],n):t[o]=e[o];return t}function a(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}function u(t){return"string"==typeof t?t.replace(/[&<>"'\/]/g,function(t){return L[t]}):t}function l(t){return t.charAt(0).toUpperCase()+t.slice(1)}function p(){var t={};return P.forEach(function(e){e.lngs.forEach(function(n){t[n]={numbers:e.nr,plurals:F[e.fc]}})}),t}function c(t,e){for(var n=t.indexOf(e);n!==-1;)t.splice(n,1),n=t.indexOf(e)}function f(){return{debug:!1,initImmediate:!0,ns:["translation"],defaultNS:["translation"],fallbackLng:["dev"],fallbackNS:!1,whitelist:!1,nonExplicitWhitelist:!1,load:"all",preload:!1,simplifyPluralSuffix:!0,keySeparator:".",nsSeparator:":",pluralSeparator:"_",contextSeparator:"_",saveMissing:!1,updateMissing:!1,saveMissingTo:"fallback",saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,returnNull:!0,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:function(){},parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:function(t){var e={};return t[1]&&(e.defaultValue=t[1]),t[2]&&(e.tDescription=t[2]),e},interpolation:{escapeValue:!0,format:function(t,e,n){return t},prefix:"{{",suffix:"}}",formatSeparator:",",unescapePrefix:"-",nestingPrefix:"$t(",nestingSuffix:")",maxReplaces:1e3}}}function h(t){return"string"==typeof t.ns&&(t.ns=[t.ns]),"string"==typeof t.fallbackLng&&(t.fallbackLng=[t.fallbackLng]),"string"==typeof t.fallbackNS&&(t.fallbackNS=[t.fallbackNS]),t.whitelist&&t.whitelist.indexOf("cimode")<0&&(t.whitelist=t.whitelist.concat(["cimode"])),t}function g(){}var d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},y=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},v=Object.assign||function(t){for(var e=1;e1&&void 0!==arguments[1]?arguments[1]:{};y(this,t),this.init(e,n)}return t.prototype.init=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.prefix=e.prefix||"i18next:",this.logger=t||S,this.options=e,this.debug=e.debug},t.prototype.setDebug=function(t){this.debug=t},t.prototype.log=function(){for(var t=arguments.length,e=Array(t),n=0;n-1&&n.observers[t].splice(o,1)}else delete n.observers[t]})},t.prototype.emit=function(t){for(var e=arguments.length,n=Array(e>1?e-1:0),o=1;o":">",'"':""","'":"'","/":"/"},N=function(t){function e(n){var o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{ns:["translation"],defaultNS:"translation"};y(this,e);var r=b(this,t.call(this));return r.data=n||{},r.options=o,void 0===r.options.keySeparator&&(r.options.keySeparator="."),r}return m(e,t),e.prototype.addNamespaces=function(t){this.options.ns.indexOf(t)<0&&this.options.ns.push(t)},e.prototype.removeNamespaces=function(t){var e=this.options.ns.indexOf(t);e>-1&&this.options.ns.splice(e,1)},e.prototype.getResource=function(t,e,n){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},r=void 0!==o.keySeparator?o.keySeparator:this.options.keySeparator,s=[t,e];return n&&"string"!=typeof n&&(s=s.concat(n)),n&&"string"==typeof n&&(s=s.concat(r?n.split(r):n)),t.indexOf(".")>-1&&(s=t.split(".")),i(this.data,s)},e.prototype.addResource=function(t,e,n,r){var i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{silent:!1},s=this.options.keySeparator;void 0===s&&(s=".");var a=[t,e];n&&(a=a.concat(s?n.split(s):n)),t.indexOf(".")>-1&&(a=t.split("."),r=e,e=a[1]),this.addNamespaces(e),o(this.data,a,r),i.silent||this.emit("added",t,e,n,r)},e.prototype.addResources=function(t,e,n){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{silent:!1};for(var r in n)"string"==typeof n[r]&&this.addResource(t,e,r,n[r],{silent:!0});o.silent||this.emit("added",t,e,n)},e.prototype.addResourceBundle=function(t,e,n,r,a){var u=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{silent:!1},l=[t,e];t.indexOf(".")>-1&&(l=t.split("."),r=n,n=e,e=l[1]),this.addNamespaces(e);var p=i(this.data,l)||{};r?s(p,n,a):p=v({},p,n),o(this.data,l,p),u.silent||this.emit("added",t,e,n)},e.prototype.removeResourceBundle=function(t,e){this.hasResourceBundle(t,e)&&delete this.data[t][e],this.removeNamespaces(e),this.emit("removed",t,e)},e.prototype.hasResourceBundle=function(t,e){return void 0!==this.getResource(t,e)},e.prototype.getResourceBundle=function(t,e){return e||(e=this.options.defaultNS),"v1"===this.options.compatibilityAPI?v({},this.getResource(t,e)):this.getResource(t,e)},e.prototype.toJSON=function(){return this.data},e}(R),C={processors:{},addPostProcessor:function(t){this.processors[t.name]=t},handle:function(t,e,n,o,r){var i=this;return t.forEach(function(t){i.processors[t]&&(e=i.processors[t].process(e,n,o,r))}),e}},j=function(t){function n(o){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};y(this,n);var i=b(this,t.call(this));return e(["resourceStore","languageUtils","pluralResolver","interpolator","backendConnector","i18nFormat"],o,i),i.options=r,void 0===i.options.keySeparator&&(i.options.keySeparator="."),i.logger=O.create("translator"),i}return m(n,t),n.prototype.changeLanguage=function(t){t&&(this.language=t)},n.prototype.exists=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{interpolation:{}},n=this.resolve(t,e);return n&&void 0!==n.res},n.prototype.extractFromKey=function(t,e){var n=e.nsSeparator||this.options.nsSeparator;void 0===n&&(n=":");var o=void 0!==e.keySeparator?e.keySeparator:this.options.keySeparator,r=e.ns||this.options.defaultNS;if(n&&t.indexOf(n)>-1){var i=t.split(n);(n!==o||n===o&&this.options.ns.indexOf(i[0])>-1)&&(r=i.shift()),t=i.join(o)}return"string"==typeof r&&(r=[r]),{key:t,namespaces:r}},n.prototype.translate=function(t,e){var n=this;if("object"!==("undefined"==typeof e?"undefined":d(e))&&this.options.overloadTranslationOptionHandler&&(e=this.options.overloadTranslationOptionHandler(arguments)),e||(e={}),void 0===t||null===t||""===t)return"";"number"==typeof t&&(t=String(t)),"string"==typeof t&&(t=[t]);var o=void 0!==e.keySeparator?e.keySeparator:this.options.keySeparator,r=this.extractFromKey(t[t.length-1],e),i=r.key,s=r.namespaces,a=s[s.length-1],u=e.lng||this.language,l=e.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(u&&"cimode"===u.toLowerCase()){if(l){var p=e.nsSeparator||this.options.nsSeparator;return a+p+i}return i}var c=this.resolve(t,e),f=c&&c.res,h=c&&c.usedKey||i,g=Object.prototype.toString.apply(f),y=["[object Number]","[object Function]","[object RegExp]"],m=void 0!==e.joinArrays?e.joinArrays:this.options.joinArrays,b="string"!=typeof f&&"boolean"!=typeof f&&"number"!=typeof f;if(f&&b&&y.indexOf(g)<0&&(!m||"[object Array]"!==g)){if(!e.returnObjects&&!this.options.returnObjects)return this.logger.warn("accessing an object - but returnObjects options is not enabled!"),this.options.returnedObjectHandler?this.options.returnedObjectHandler(h,f,e):"key '"+i+" ("+this.language+")' returned an object instead of string.";if(o){var x="[object Array]"===g?[]:{};for(var k in f)if(Object.prototype.hasOwnProperty.call(f,k)){var S=""+h+o+k;x[k]=this.translate(S,v({},e,{joinArrays:!1,ns:s})),x[k]===S&&(x[k]=f[k])}f=x}}else if(m&&"[object Array]"===g)f=f.join(m),f&&(f=this.extendTranslation(f,t,e));else{var w=!1,O=!1;this.isValidLookup(f)||void 0===e.defaultValue||(w=!0,f=e.defaultValue),this.isValidLookup(f)||(O=!0,f=i);var R=e.defaultValue&&e.defaultValue!==f&&this.options.updateMissing;if(O||w||R){this.logger.log(R?"updateKey":"missingKey",u,a,i,R?e.defaultValue:f);var L=[],N=this.languageUtils.getFallbackCodes(this.options.fallbackLng,e.lng||this.language);if("fallback"===this.options.saveMissingTo&&N&&N[0])for(var C=0;C1&&void 0!==arguments[1]?arguments[1]:{},o=void 0,r=void 0,i=void 0,s=void 0;return"string"==typeof t&&(t=[t]),t.forEach(function(t){if(!e.isValidLookup(o)){var a=e.extractFromKey(t,n),u=a.key;r=u;var l=a.namespaces;e.options.fallbackNS&&(l=l.concat(e.options.fallbackNS));var p=void 0!==n.count&&"string"!=typeof n.count,c=void 0!==n.context&&"string"==typeof n.context&&""!==n.context,f=n.lngs?n.lngs:e.languageUtils.toResolveHierarchy(n.lng||e.language);l.forEach(function(t){e.isValidLookup(o)||(s=t,f.forEach(function(r){if(!e.isValidLookup(o)){i=r;var s=u,a=[s];if(e.i18nFormat&&e.i18nFormat.addLookupKeys)e.i18nFormat.addLookupKeys(a,u,r,t,n);else{var l=void 0;p&&(l=e.pluralResolver.getSuffix(r,n.count)),p&&c&&a.push(s+l),c&&a.push(s+=""+e.options.contextSeparator+n.context),p&&a.push(s+=l)}for(var f=void 0;f=a.pop();)e.isValidLookup(o)||(o=e.getResource(r,t,f,n))}}))})}}),{res:o,usedKey:r,usedLng:i,usedNS:s}},n.prototype.isValidLookup=function(t){return!(void 0===t||!this.options.returnNull&&null===t||!this.options.returnEmptyString&&""===t)},n.prototype.getResource=function(t,e,n){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};return this.resourceStore.getResource(t,e,n,o)},n}(R),E=function(){function t(e){y(this,t),this.options=e,this.whitelist=this.options.whitelist||!1,this.logger=O.create("languageUtils")}return t.prototype.getScriptPartFromCode=function(t){if(!t||t.indexOf("-")<0)return null;var e=t.split("-");return 2===e.length?null:(e.pop(),this.formatLanguageCode(e.join("-")))},t.prototype.getLanguagePartFromCode=function(t){if(!t||t.indexOf("-")<0)return t;var e=t.split("-");return this.formatLanguageCode(e[0])},t.prototype.formatLanguageCode=function(t){if("string"==typeof t&&t.indexOf("-")>-1){var e=["hans","hant","latn","cyrl","cans","mong","arab"],n=t.split("-");return this.options.lowerCaseLng?n=n.map(function(t){return t.toLowerCase()}):2===n.length?(n[0]=n[0].toLowerCase(),n[1]=n[1].toUpperCase(),e.indexOf(n[1].toLowerCase())>-1&&(n[1]=l(n[1].toLowerCase()))):3===n.length&&(n[0]=n[0].toLowerCase(),2===n[1].length&&(n[1]=n[1].toUpperCase()),"sgn"!==n[0]&&2===n[2].length&&(n[2]=n[2].toUpperCase()),e.indexOf(n[1].toLowerCase())>-1&&(n[1]=l(n[1].toLowerCase())),e.indexOf(n[2].toLowerCase())>-1&&(n[2]=l(n[2].toLowerCase()))),n.join("-")}return this.options.cleanCode||this.options.lowerCaseLng?t.toLowerCase():t},t.prototype.isWhitelisted=function(t){return("languageOnly"===this.options.load||this.options.nonExplicitWhitelist)&&(t=this.getLanguagePartFromCode(t)),!this.whitelist||!this.whitelist.length||this.whitelist.indexOf(t)>-1},t.prototype.getFallbackCodes=function(t,e){if(!t)return[];if("string"==typeof t&&(t=[t]),"[object Array]"===Object.prototype.toString.apply(t))return t;if(!e)return t.default||[];var n=t[e];return n||(n=t[this.getScriptPartFromCode(e)]),n||(n=t[this.formatLanguageCode(e)]),n||(n=t.default),n||[]},t.prototype.toResolveHierarchy=function(t,e){var n=this,o=this.getFallbackCodes(e||this.options.fallbackLng||[],t),r=[],i=function(t){t&&(n.isWhitelisted(t)?r.push(t):n.logger.warn("rejecting non-whitelisted language code: "+t))};return"string"==typeof t&&t.indexOf("-")>-1?("languageOnly"!==this.options.load&&i(this.formatLanguageCode(t)),"languageOnly"!==this.options.load&&"currentOnly"!==this.options.load&&i(this.getScriptPartFromCode(t)),"currentOnly"!==this.options.load&&i(this.getLanguagePartFromCode(t))):"string"==typeof t&&i(this.formatLanguageCode(t)),o.forEach(function(t){r.indexOf(t)<0&&i(n.formatLanguageCode(t))}),r},t}(),P=[{lngs:["ach","ak","am","arn","br","fil","gun","ln","mfe","mg","mi","oc","pt","pt-BR","tg","ti","tr","uz","wa"],nr:[1,2],fc:1},{lngs:["af","an","ast","az","bg","bn","ca","da","de","dev","el","en","eo","es","et","eu","fi","fo","fur","fy","gl","gu","ha","he","hi","hu","hy","ia","it","kn","ku","lb","mai","ml","mn","mr","nah","nap","nb","ne","nl","nn","no","nso","pa","pap","pms","ps","pt-PT","rm","sco","se","si","so","son","sq","sv","sw","ta","te","tk","ur","yo"],nr:[1,2],fc:2},{lngs:["ay","bo","cgg","fa","id","ja","jbo","ka","kk","km","ko","ky","lo","ms","sah","su","th","tt","ug","vi","wo","zh"],nr:[1],fc:3},{lngs:["be","bs","dz","hr","ru","sr","uk"],nr:[1,2,5],fc:4},{lngs:["ar"],nr:[0,1,2,3,11,100],fc:5},{lngs:["cs","sk"],nr:[1,2,5],fc:6},{lngs:["csb","pl"],nr:[1,2,5],fc:7},{lngs:["cy"],nr:[1,2,3,8],fc:8},{lngs:["fr"],nr:[1,2],fc:9},{lngs:["ga"],nr:[1,2,3,7,11],fc:10},{lngs:["gd"],nr:[1,2,3,20],fc:11},{lngs:["is"],nr:[1,2],fc:12},{lngs:["jv"],nr:[0,1],fc:13},{lngs:["kw"],nr:[1,2,3,4],fc:14},{lngs:["lt"],nr:[1,2,10],fc:15},{lngs:["lv"],nr:[1,2,0],fc:16},{lngs:["mk"],nr:[1,2],fc:17},{lngs:["mnk"],nr:[0,1,2],fc:18},{lngs:["mt"],nr:[1,2,11,20],fc:19},{lngs:["or"],nr:[2,1],fc:2},{lngs:["ro"],nr:[1,2,20],fc:20},{lngs:["sl"],nr:[5,1,2,3],fc:21}],F={1:function(t){return Number(t>1)},2:function(t){return Number(1!=t)},3:function(t){return 0},4:function(t){return Number(t%10==1&&t%100!=11?0:t%10>=2&&t%10<=4&&(t%100<10||t%100>=20)?1:2)},5:function(t){return Number(0===t?0:1==t?1:2==t?2:t%100>=3&&t%100<=10?3:t%100>=11?4:5)},6:function(t){return Number(1==t?0:t>=2&&t<=4?1:2)},7:function(t){return Number(1==t?0:t%10>=2&&t%10<=4&&(t%100<10||t%100>=20)?1:2)},8:function(t){return Number(1==t?0:2==t?1:8!=t&&11!=t?2:3)},9:function(t){return Number(t>=2)},10:function(t){return Number(1==t?0:2==t?1:t<7?2:t<11?3:4)},11:function(t){return Number(1==t||11==t?0:2==t||12==t?1:t>2&&t<20?2:3)},12:function(t){return Number(t%10!=1||t%100==11)},13:function(t){return Number(0!==t)},14:function(t){return Number(1==t?0:2==t?1:3==t?2:3)},15:function(t){return Number(t%10==1&&t%100!=11?0:t%10>=2&&(t%100<10||t%100>=20)?1:2)},16:function(t){return Number(t%10==1&&t%100!=11?0:0!==t?1:2)},17:function(t){return Number(1==t||t%10==1?0:1)},18:function(t){return Number(0==t?0:1==t?1:2)},19:function(t){return Number(1==t?0:0===t||t%100>1&&t%100<11?1:t%100>10&&t%100<20?2:3)},20:function(t){return Number(1==t?0:0===t||t%100>0&&t%100<20?1:2)},21:function(t){return Number(t%100==1?1:t%100==2?2:t%100==3||t%100==4?3:0)}},A=function(){function t(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};y(this,t),this.languageUtils=e,this.options=n,this.logger=O.create("pluralResolver"),this.rules=p()}return t.prototype.addRule=function(t,e){this.rules[t]=e},t.prototype.getRule=function(t){return this.rules[t]||this.rules[this.languageUtils.getLanguagePartFromCode(t)]},t.prototype.needsPlural=function(t){var e=this.getRule(t);return e&&e.numbers.length>1},t.prototype.getPluralFormsOfKey=function(t,e){var n=this,o=[],r=this.getRule(t);return r?(r.numbers.forEach(function(r){var i=n.getSuffix(t,r);o.push(""+e+i)}),o):o},t.prototype.getSuffix=function(t,e){var n=this,o=this.getRule(t);if(o){var r=o.noAbs?o.plurals(e):o.plurals(Math.abs(e)),i=o.numbers[r];this.options.simplifyPluralSuffix&&2===o.numbers.length&&1===o.numbers[0]&&(2===i?i="plural":1===i&&(i=""));var s=function(){return n.options.prepend&&i.toString()?n.options.prepend+i.toString():i.toString()};return"v1"===this.options.compatibilityJSON?1===i?"":"number"==typeof i?"_plural_"+i.toString():s():"v2"===this.options.compatibilityJSON||2===o.numbers.length&&1===o.numbers[0]?s():2===o.numbers.length&&1===o.numbers[0]?s():this.options.prepend&&r.toString()?this.options.prepend+r.toString():r.toString()}return this.logger.warn("no plural rule found for: "+t),""},t}(),U=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};y(this,e),this.logger=O.create("interpolator"),this.init(t,!0)}return e.prototype.init=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments[1];e&&(this.options=t,this.format=t.interpolation&&t.interpolation.format||function(t){return t},this.escape=t.interpolation&&t.interpolation.escape||u),t.interpolation||(t.interpolation={escapeValue:!0});var n=t.interpolation;this.escapeValue=void 0===n.escapeValue||n.escapeValue,this.prefix=n.prefix?a(n.prefix):n.prefixEscaped||"{{",this.suffix=n.suffix?a(n.suffix):n.suffixEscaped||"}}",this.formatSeparator=n.formatSeparator?n.formatSeparator:n.formatSeparator||",",this.unescapePrefix=n.unescapeSuffix?"":n.unescapePrefix||"-",this.unescapeSuffix=this.unescapePrefix?"":n.unescapeSuffix||"",this.nestingPrefix=n.nestingPrefix?a(n.nestingPrefix):n.nestingPrefixEscaped||a("$t("),this.nestingSuffix=n.nestingSuffix?a(n.nestingSuffix):n.nestingSuffixEscaped||a(")"),this.maxReplaces=n.maxReplaces?n.maxReplaces:1e3,this.resetRegExp()},e.prototype.reset=function(){this.options&&this.init(this.options)},e.prototype.resetRegExp=function(){var t=this.prefix+"(.+?)"+this.suffix;this.regexp=new RegExp(t,"g");var e=""+this.prefix+this.unescapePrefix+"(.+?)"+this.unescapeSuffix+this.suffix;this.regexpUnescape=new RegExp(e,"g");var n=this.nestingPrefix+"(.+?)"+this.nestingSuffix;this.nestingRegexp=new RegExp(n,"g")},e.prototype.interpolate=function(e,n,o){function r(t){return t.replace(/\$/g,"$$$$")}var s=this,a=void 0,u=void 0,l=void 0,p=function(t){if(t.indexOf(s.formatSeparator)<0)return i(n,t);var e=t.split(s.formatSeparator),r=e.shift().trim(),a=e.join(s.formatSeparator).trim();return s.format(i(n,r),a,o)};for(this.resetRegExp(),l=0;(a=this.regexpUnescape.exec(e))&&(u=p(a[1].trim()),e=e.replace(a[0],u),this.regexpUnescape.lastIndex=0,l++,!(l>=this.maxReplaces)););for(l=0;a=this.regexp.exec(e);){if(u=p(a[1].trim()),void 0===u)if("function"==typeof this.options.missingInterpolationHandler){var c=this.options.missingInterpolationHandler(e,a);u="string"==typeof c?c:""}else this.logger.warn("missed to pass in variable "+a[1]+" for interpolating "+e),u="";else"string"!=typeof u&&(u=t(u));if(u=r(this.escapeValue?this.escape(u):u),e=e.replace(a[0],u),this.regexp.lastIndex=0,l++,l>=this.maxReplaces)break}return e},e.prototype.nest=function(e,n){function o(t,e){if(t.indexOf(",")<0)return t;var n=t.split(",");t=n.shift();var o=n.join(",");o=this.interpolate(o,a),o=o.replace(/'/g,'"');try{a=JSON.parse(o),e&&(a=v({},e,a))}catch(e){this.logger.error("failed parsing options string in nesting for key "+t,e)}return t}var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=void 0,s=void 0,a=v({},r);for(a.applyPostProcessor=!1;i=this.nestingRegexp.exec(e);){if(s=n(o.call(this,i[1].trim(),a),a),s&&i[0]===e&&"string"!=typeof s)return s;"string"!=typeof s&&(s=t(s)),s||(this.logger.warn("missed to resolve "+i[1]+" for nesting "+e),s=""),e=e.replace(i[0],s),this.regexp.lastIndex=0}return e},e}(),T=function(t){function e(n,o,r){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};y(this,e);var s=b(this,t.call(this));return s.backend=n,s.store=o,s.languageUtils=r.languageUtils,s.options=i,s.logger=O.create("backendConnector"),s.state={},s.queue=[],s.backend&&s.backend.init&&s.backend.init(r,i.backend,i),s}return m(e,t),e.prototype.queueLoad=function(t,e,n){var o=this,r=[],i=[],s=[],a=[];return t.forEach(function(t){var n=!0;e.forEach(function(e){var s=t+"|"+e;o.store.hasResourceBundle(t,e)?o.state[s]=2:o.state[s]<0||(1===o.state[s]?i.indexOf(s)<0&&i.push(s):(o.state[s]=1,n=!1,i.indexOf(s)<0&&i.push(s),r.indexOf(s)<0&&r.push(s),a.indexOf(e)<0&&a.push(e)))}),n||s.push(t)}),(r.length||i.length)&&this.queue.push({pending:i,loaded:{},errors:[],callback:n}),{toLoad:r,pending:i,toLoadLanguages:s,toLoadNamespaces:a}},e.prototype.loaded=function(t,e,n){var o=this,i=t.split("|"),s=x(i,2),a=s[0],u=s[1];e&&this.emit("failedLoading",a,u,e),n&&this.store.addResourceBundle(a,u,n),this.state[t]=e?-1:2,this.queue.forEach(function(n){r(n.loaded,[a],u),c(n.pending,t),e&&n.errors.push(e),0!==n.pending.length||n.done||(o.emit("loaded",n.loaded),n.done=!0,n.errors.length?n.callback(n.errors):n.callback())}),this.queue=this.queue.filter(function(t){return!t.done})},e.prototype.read=function(t,e,n){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,r=this,i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:250,s=arguments[5];return t.length?this.backend[n](t,e,function(a,u){return a&&u&&o<5?void setTimeout(function(){r.read.call(r,t,e,n,o+1,2*i,s)},i):void s(a,u)}):s(null,{})},e.prototype.load=function(t,e,n){var o=this;if(!this.backend)return this.logger.warn("No backend was added via i18next.use. Will not load resources."),n&&n();"string"==typeof t&&(t=this.languageUtils.toResolveHierarchy(t)),"string"==typeof e&&(e=[e]);var r=this.queueLoad(t,e,n);return r.toLoad.length?void r.toLoad.forEach(function(t){o.loadOne(t)}):(r.pending.length||n(),null)},e.prototype.reload=function(t,e){var n=this;this.backend||this.logger.warn("No backend was added via i18next.use. Will not load resources."),"string"==typeof t&&(t=this.languageUtils.toResolveHierarchy(t)),"string"==typeof e&&(e=[e]),t.forEach(function(t){e.forEach(function(e){n.loadOne(t+"|"+e,"re")})})},e.prototype.loadOne=function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",o=t.split("|"),r=x(o,2),i=r[0],s=r[1];this.read(i,s,"read",null,null,function(o,r){o&&e.logger.warn(n+"loading namespace "+s+" for language "+i+" failed",o),!o&&r&&e.logger.log(n+"loaded namespace "+s+" for language "+i,r),e.loaded(t,o,r)})},e.prototype.saveMissing=function(t,e,n,o,r){var i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{};this.backend&&this.backend.create&&this.backend.create(t,e,n,o,null,v({},i,{isUpdate:r})),t&&t[0]&&this.store.addResource(t[0],e,n,o)},e}(R),H=function(t){function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=arguments[1];y(this,e);var r=b(this,t.call(this));if(r.options=h(n),r.services={},r.logger=O,r.modules={external:[]},o&&!r.isInitialized&&!n.isClone){var i;if(!r.options.initImmediate)return i=r.init(n,o),b(r,i);setTimeout(function(){r.init(n,o)},0)}return r}return m(e,t),e.prototype.init=function(){function t(t){return t?"function"==typeof t?new t:t:null}var e=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=arguments[1];if("function"==typeof n&&(o=n,n={}),this.options=v({},f(),this.options,h(n)),this.format=this.options.interpolation.format,o||(o=g),!this.options.isClone){this.modules.logger?O.init(t(this.modules.logger),this.options):O.init(null,this.options);var r=new E(this.options);this.store=new N(this.options.resources,this.options);var i=this.services;i.logger=O,i.resourceStore=this.store,i.languageUtils=r,i.pluralResolver=new A(r,{prepend:this.options.pluralSeparator,compatibilityJSON:this.options.compatibilityJSON,simplifyPluralSuffix:this.options.simplifyPluralSuffix}),i.interpolator=new U(this.options),i.backendConnector=new T(t(this.modules.backend),i.resourceStore,i,this.options),i.backendConnector.on("*",function(t){for(var n=arguments.length,o=Array(n>1?n-1:0),r=1;r1?n-1:0),r=1;r0&&void 0!==arguments[0]?arguments[0]:g;if(this.options.resources)e(null);else{if(this.language&&"cimode"===this.language.toLowerCase())return e();var n=[],o=function(e){if(e){var o=t.services.languageUtils.toResolveHierarchy(e);o.forEach(function(t){n.indexOf(t)<0&&n.push(t)})}};if(this.language)o(this.language);else{var r=this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);r.forEach(function(t){return o(t)})}this.options.preload&&this.options.preload.forEach(function(t){return o(t)}),this.services.backendConnector.load(n,this.options.ns,e)}},e.prototype.reloadResources=function(t,e){t||(t=this.languages),e||(e=this.options.ns),this.services.backendConnector.reload(t,e)},e.prototype.use=function(t){return"backend"===t.type&&(this.modules.backend=t),("logger"===t.type||t.log&&t.warn&&t.error)&&(this.modules.logger=t),"languageDetector"===t.type&&(this.modules.languageDetector=t),"i18nFormat"===t.type&&(this.modules.i18nFormat=t),"postProcessor"===t.type&&C.addPostProcessor(t),"3rdParty"===t.type&&this.modules.external.push(t),this},e.prototype.changeLanguage=function(t,e){var n=this,o=function(t,o){n.translator.changeLanguage(o),o&&(n.emit("languageChanged",o),n.logger.log("languageChanged",o)),e&&e(t,function(){return n.t.apply(n,arguments)})},r=function(t){t&&(n.language=t,n.languages=n.services.languageUtils.toResolveHierarchy(t),n.translator.language||n.translator.changeLanguage(t),n.services.languageDetector&&n.services.languageDetector.cacheUserLanguage(t)),n.loadResources(function(e){o(e,t)})};t||!this.services.languageDetector||this.services.languageDetector.async?!t&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect(r):r(t):r(this.services.languageDetector.detect())},e.prototype.getFixedT=function(t,e){var n=this,o=function t(e,o){for(var r=arguments.length,i=Array(r>2?r-2:0),s=2;s0?this.languages[0]:this.language),
-!t)return"rtl";var e=["ar","shu","sqr","ssh","xaa","yhd","yud","aao","abh","abv","acm","acq","acw","acx","acy","adf","ads","aeb","aec","afb","ajp","apc","apd","arb","arq","ars","ary","arz","auz","avl","ayh","ayl","ayn","ayp","bbz","pga","he","iw","ps","pbt","pbu","pst","prp","prd","ur","ydd","yds","yih","ji","yi","hbo","men","xmn","fa","jpr","peo","pes","prs","dv","sam"];return e.indexOf(this.services.languageUtils.getLanguagePartFromCode(t))>=0?"rtl":"ltr"},e.prototype.createInstance=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=arguments[1];return new e(t,n)},e.prototype.cloneInstance=function(){var t=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:g,r=v({},this.options,n,{isClone:!0}),i=new e(r),s=["store","services","language"];return s.forEach(function(e){i[e]=t[e]}),i.translator=new j(i.services,i.options),i.translator.on("*",function(t){for(var e=arguments.length,n=Array(e>1?e-1:0),o=1;oAnatomy Quiz
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/anatomy/templates/anatomy/mark.html b/anatomy/templates/anatomy/mark.html
index ae66ffe2..bbcaca8c 100644
--- a/anatomy/templates/anatomy/mark.html
+++ b/anatomy/templates/anatomy/mark.html
@@ -38,6 +38,6 @@
{{ form.as_p }}
-
+
{% endblock %}
\ No newline at end of file