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