// 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. define([ 'modernizr', 'i18next', 'i18nextXHRBackend', 'i18nextBrowserLanguageDetector', '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' -> deactivated for now... // MagicWand: no package -> deactivated module.exports = factory( require('modernizr'), require('i18next'), require('i18next-xhr-backend'), require('i18next-browser-languagedetector'), require('jszip'), null, null ); } else { // Browser globals (root is window) root.dwv = factory( root.Modernizr, root.i18next, root.i18nextXHRBackend, root.i18nextBrowserLanguageDetector, root.JSZip, root.Konva, root.MagicWand ); } }(this, function ( Modernizr, 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 = {}; /** * 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 image layer. * @return {Object} The image layer. */ this.getImageLayer = function () { return imageLayer; }; /** * Get the current draw layer. * @return {Object} The draw layer. */ this.getCurrentDrawLayer = function () { return drawController.getCurrentDrawLayer(); }; /** * 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; }; /** * 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; } 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(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 the list of drawings. * @return {Object} The list of drawings. */ this.getDraws = function () { return drawController.getDraws(); }; /** * 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); }; /** * 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. * @param {Object} event The change event. */ this.onChangeLoader = function (/*event*/) { // called from an HTML select, use its value loadbox.displayLoader( this.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(self); // add href to link (html5) var element = self.getElement("download-state"); element.href = "data:application/json," + state.toJSON(); }; /** * Handle colour map change. * @param {Object} event The change event. */ this.onChangeColourMap = function (/*event*/) { // called from an HTML select, use its value viewController.setColourMapFromName(this.value); }; /** * Handle window/level preset change. * @param {Object} event The change event. */ this.onChangeWindowLevelPreset = function (/*event*/) { // value should be the name of the preset viewController.setWindowLevelPreset( this.value ); }; /** * Handle tool change. * @param {Object} event The change event. */ this.onChangeTool = function (/*event*/) { // called from an HTML select, use its value toolboxController.setSelectedTool(this.value); }; /** * Handle shape change. * @param {Object} event The change event. */ this.onChangeShape = function (/*event*/) { // called from an HTML select, use its value toolboxController.setSelectedShape(this.value); }; /** * Handle filter change. * @param {Object} event The change event. */ this.onChangeFilter = function (/*event*/) { // called from an HTML select, use its value toolboxController.setSelectedFilter(this.value); }; /** * Handle filter run. * @param {Object} event The run event. */ this.onRunFilter = function (/*event*/) { toolboxController.runSelectedFilter(); }; /** * Handle line colour change. * @param {Object} event The change event. */ this.onChangeLineColour = function (/*event*/) { // called from an HTML select, use its value toolboxController.setLineColour(this.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 || {}; // external var Konva = Konva || {}; /** * 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 layers: 2 dimension array: [slice][frame] var drawLayers = []; // current slice position var currentSlice = 0; // current frame position var currentFrame = 0; /** * 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", ""); }; /** * Get the current draw layer. * @return {Object} The draw layer. */ this.getCurrentDrawLayer = function () { //return this.getDrawLayer(currentSlice, currentFrame); return drawLayers[currentSlice][currentFrame]; }; /** * Reset: clear the layers array. */ this.reset = function () { drawLayers = []; }; /** * 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) { // hide all draw layers for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) { for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) { drawLayers[k][f].visible( false ); } } // set current position currentSlice = viewController.getCurrentPosition().k; currentFrame = viewController.getCurrentFrame(); // show current draw layer var currentLayer = this.getCurrentDrawLayer(); currentLayer.visible( true ); currentLayer.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(); }; /** * Append a new draw layer list to the list. * @param {Number} nLayers The size of the layers array to append to the current one. */ this.appendDrawLayer = function (nLayers) { // add a new dimension drawLayers.push([]); // fill it for (var i = 0; i < nLayers; ++i) { // create draw layer var drawLayer = new Konva.Layer({ 'listening': false, 'hitGraphEnabled': false, 'visible': false }); drawLayers[drawLayers.length - 1].push(drawLayer); // add the layer to the stage drawStage.add(drawLayer); } }; /** * Get a list of drawing display details. * @return {Object} A list of draw details including id, slice, frame... */ this.getDrawDisplayDetails = function () { var list = []; for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) { for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) { var collec = drawLayers[k][f].getChildren(); for ( var i = 0, leni = collec.length; i < leni; ++i ) { var shape = collec[i].getChildren( isNodeNameShape )[0]; var label = collec[i].getChildren( isNodeNameLabel )[0]; var text = label.getChildren()[0]; var type = shape.className; if (type === "Line") { var shapeExtrakids = collec[i].getChildren( 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": k, "frame": f, "type": type, "color": shape.stroke(), "label": text.textExpr, "description": text.longText }); } } } // return return list; }; /** * Get all the draws of the stage. */ this.getDraws = function () { var drawGroups = []; for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) { drawGroups[k] = []; for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) { // getChildren always return, so drawings will have the good size var groups = drawLayers[k][f].getChildren(); drawGroups[k].push(groups); } } return drawGroups; }; /** * 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 = []; for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) { drawingsDetails[k] = []; for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) { // getChildren always return, so drawings will have the good size var groups = drawLayers[k][f].getChildren(); var details = []; for ( var i = 0, leni = groups.length; i < leni; ++i ) { // remove anchors var anchors = groups[i].find(".anchor"); for ( var a = 0; a < anchors.length; ++a ) { anchors[a].remove(); } // get text var texts = groups[i].find(".text"); if ( texts.length !== 1 ) { console.warn("There should not be more than one text per shape."); } // get details (non konva vars) details.push({ "id": groups[i].id(), "textExpr": encodeURIComponent(texts[0].textExpr), "longText": encodeURIComponent(texts[0].longText), "quant": texts[0].quant }); } drawingsDetails[k].push(details); } } 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) { // loop through layers for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) { for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) { for ( var i = 0, leni = drawings[k][f].length; i < leni; ++i ) { // create the group var group = Konva.Node.create(drawings[k][f][i]); var shape = group.getChildren( isNodeNameShape )[0]; // create the draw command var cmd = new dwv.tool.DrawGroupCommand( group, shape.className, drawLayers[k][f] ); // draw command callbacks cmd.onExecute = cmdCallback; cmd.onUndo = cmdCallback; // text (new in v0.2) // TODO Verify ID? if (drawingsDetails) { var details = drawingsDetails[k][f][i]; var label = group.getChildren( 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 = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id); // shape var shapes = group.getChildren( isNodeNameShape ); for (var i = 0; i < shapes.length; ++i ) { shapes[i].stroke(drawDetails.color); } // shape extra var shapesExtra = group.getChildren( 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( 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 this.getCurrentDrawLayer().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 = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id); // 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 = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id); // toggle visible group.visible(!group.isVisible()); // udpate current layer this.getCurrentDrawLayer().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, layer, groups; for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) { for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) { layer = drawLayers[k][f]; groups = layer.getChildren(); while (groups.length) { var shape = groups[0].getChildren( isNodeNameShape )[0]; delcmd = new dwv.tool.DeleteGroupCommand( groups[0], dwv.tool.GetShapeDisplayName(shape), layer); delcmd.onExecute = cmdCallback; delcmd.onUndo = cmdCallback; delcmd.execute(); exeCallback(delcmd); } } } }; /** * Get a draw group. * @param {Number} slice The slice position. * @param {Number} frame The frame position. * @param {Number} id The group id. */ function getDrawGroup(slice, frame, id) { var layer = drawLayers[slice][frame]; //var collec = layer.getChildren()[drawDetails.id]; var collec = layer.getChildren( function (node) { return node.id() === id; }); var res = null; if (collec.length !== 0) { res = collec[0]; } else { console.warn("Could not find draw group for slice='" + slice + "', frame='" + frame + "', id='" + id + "'."); } return res; } /** * Is an input node's name 'shape'. * @param {Object} node A Konva node. */ function isNodeNameShape( node ) { return node.name() === "shape"; } /** * Is a node an extra shape associated with a main one. * @param {Object} node A Konva node. */ function isNodeNameShapeExtra( node ) { return node.name().startsWith("shape-"); } /** * Is an input node's name 'label'. * @param {Object} node A Konva node. */ function isNodeNameLabel( node ) { return node.name() === "label"; } }; // 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); ivnSize += dwv.dicom.setElementValue(ivn, "DWV_"+dwv.getVersion(), 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); var elements = parent.getElementsByClassName(name); // getting the last element since some libraries (ie jquery-mobile) creates // 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"); // current location var loc = window.location.pathname; var dir = loc.substring(0, loc.lastIndexOf('/')); 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."); } /** * 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. * @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 shapeGroup = 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 ( shapeGroup ) { shapeGroup.destroy(); } // create shape group shapeGroup = factory.create(points, self.style, app.getImage()); // do not listen during creation var shape = shapeGroup.getChildren( function (node) { return node.name() === 'shape'; })[0]; shape.listening(false); drawLayer.hitGraphEnabled(false); // draw shape command command = new dwv.tool.DrawGroupCommand(shapeGroup, self.shapeName, drawLayer, true); // draw command.execute(); } }; /** * Handle mouse up event. * @param {Object} event The mouse up event. */ this.mouseup = function (/*event*/){ if (started && points.length > 1 ) { // reset shape group if ( shapeGroup ) { shapeGroup.destroy(); } // create final shape var factory = new self.shapeFactoryList[self.shapeName](); var group = factory.create(points, self.style, app.getImage()); group.id( dwv.math.guid() ); // re-activate layer drawLayer.hitGraphEnabled(true); // draw shape command command = new dwv.tool.DrawGroupCommand(group, self.shapeName, drawLayer); command.onExecute = fireEvent; command.onUndo = fireEvent; // execute it command.execute(); // save it in undo stack app.addToUndoStack(command); // set shape on var shape = group.getChildren( function (node) { return node.name() === 'shape'; })[0]; self.setShapeOn( shape ); } // 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.getCurrentDrawLayer(); 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); } }; /** * Get the current app draw layer. */ function updateDrawLayer() { // deactivate the old draw layer renderDrawLayer(false); // get the current draw layer drawLayer = app.getCurrentDrawLayer(); // activate the new 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 the list of shapes var groups = drawLayer.getChildren(); var shapes = []; var fshape = function (node) { return node.name() === 'shape'; }; for ( var i = 0; i < groups.length; ++i ) { // should only be one shape per group shapes.push( groups[i].getChildren(fshape)[0] ); } // set shape display properties if ( visible ) { app.addToolCanvasListeners( app.getDrawStage().getContent() ); shapes.forEach( function (shape){ self.setShapeOn( shape ); }); } else { app.removeToolCanvasListeners( app.getDrawStage().getContent() ); shapes.forEach( function (shape){ setShapeOff( shape ); }); } // draw drawLayer.draw(); } /** * Set shape off properties. * @param {Object} shape The shape to set off. */ function setShapeOff( shape ) { // mouse styling shape.off('mouseover'); shape.off('mouseout'); // drag shape.draggable(false); shape.off('dragstart'); shape.off('dragmove'); shape.off('dragend'); shape.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 on properties. * @param {Object} shape The shape to set on. */ this.setShapeOn = function ( shape ) { // mouse over styling shape.on('mouseover', function () { document.body.style.cursor = 'pointer'; }); // mouse out styling shape.on('mouseout', function () { document.body.style.cursor = 'default'; }); // make it draggable shape.draggable(true); var dragStartPos = null; var dragLastPos = null; // command name based on shape type var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape); // store original colour var colour = null; // save start position dragStartPos = {'x': shape.x(), 'y': shape.y()}; // drag start event handling shape.on('dragstart', function (/*event*/) { // colour colour = shape.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 shape.on('dragmove', function (event) { var pos = {'x': this.x(), 'y': this.y()}; var translation; if ( dragLastPos ) { translation = {'x': pos.x - dragLastPos.x, 'y': pos.y - dragLastPos.y}; } else { translation = {'x': pos.x - dragStartPos.x, 'y': pos.y - dragStartPos.y}; } dragLastPos = pos; // 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'); }); shape.stroke('red'); } else { trash.getChildren().each( function (tshape){ tshape.stroke('red'); }); shape.stroke(colour); } // update group but not 'this' shape var group = this.getParent(); group.getChildren().each( function (ashape) { if ( ashape === shape ) { return; } ashape.x( ashape.x() + translation.x ); ashape.y( ashape.y() + translation.y ); }); // reset anchors shapeEditor.resetAnchors(); // draw drawLayer.draw(); }); // drag end event handling shape.on('dragend', function (event) { var pos = {'x': this.x(), 'y': this.y()}; dragLastPos = null; // 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 var delTranslation = {'x': eventPos.x - dragStartPos.x, 'y': eventPos.y - dragStartPos.y}; var group = this.getParent(); group.getChildren().each( function (ashape) { ashape.x( ashape.x() - delTranslation.x ); ashape.y( ashape.y() - delTranslation.y ); }); // disable editor shapeEditor.disable(); shapeEditor.setShape(null); shapeEditor.setImage(null); // reset shape.stroke(colour); document.body.style.cursor = 'default'; // delete command var delcmd = new dwv.tool.DeleteGroupCommand(this.getParent(), 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.getParent(), shapeDisplayName, translation, drawLayer); mvcmd.onExecute = fireEvent; mvcmd.onUndo = fireEvent; app.addToUndoStack(mvcmd); // reset start position dragStartPos = {'x': this.x(), 'y': this.y()}; // the move is handled by Konva, trigger an event manually fireEvent({'type': 'draw-move'}); } // reset anchors shapeEditor.setAnchorsActive(true); shapeEditor.resetAnchors(); } // draw drawLayer.draw(); }); // double click handling: update label shape.on('dblclick', function () { // get the label object for this shape var group = this.getParent(); var labels = group.find('Label'); // should just be one if (labels.length !== 1) { throw new Error("Could not find the shape label."); } var ktext = labels[0].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; /** * 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 layer layer.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 all children of group group.getChildren().each( function (shape) { shape.x( shape.x() + translation.x ); shape.y( shape.y() + translation.y ); }); // draw layer.draw(); // callback this.onExecute({'type': 'draw-move', 'id': group.id()}); }; /** * Undo the command. */ this.undo = function () { // invert translate all children of group group.getChildren().each( function (shape) { shape.x( shape.x() - translation.x ); shape.y( shape.y() - translation.y ); }); // 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) { /** * 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 the parent layer group.remove(); // draw layer.draw(); // callback this.onExecute({'type': 'draw-delete', 'id': group.id()}); }; /** * Undo the command. */ this.undo = function () { // add the group to the layer layer.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( function ( node ) { return node.name().startsWith("shape-"); }); 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 ); } /** * 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', function () { startAnchor = getClone(this); }); // drag move listener anchor.on('dragmove', function () { if ( updateFunction ) { updateFunction(this, image); } if ( this.getLayer() ) { this.getLayer().draw(); } else { console.warn("No layer to draw the anchor!"); } }); // drag end listener anchor.on('dragend', function () { 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; }); // mouse down listener anchor.on('mousedown touchstart', function () { this.moveToTop(); }); // mouse over styling anchor.on('mouseover', function () { document.body.style.cursor = 'pointer'; this.stroke('#ddd'); if ( this.getLayer() ) { this.getLayer().draw(); } else { console.warn("No layer to draw the anchor!"); } }); // mouse out styling anchor.on('mouseout', function () { document.body.style.cursor = 'default'; 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'); anchor.off('dragmove'); anchor.off('dragend'); anchor.off('mousedown touchstart'); anchor.off('mouseover'); anchor.off('mouseout'); } }; // 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() ); // draw shape command command = new dwv.tool.DrawGroupCommand(shapeGroup, "livewire", app.getCurrentDrawLayer()); // 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 || {}; // external var Modernizr = Modernizr || {}; /** * 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 Modernizr.filereader; }; /** * Browser check for the XMLHttpRequest. */ dwv.browser.hasXmlHttpRequest = function() { return Modernizr.xhrresponsetype && Modernizr.xhrresponsetypearraybuffer && Modernizr.xhrresponsetypetext && "XMLHttpRequest" in window && "withCredentials" in new XMLHttpRequest(); }; /** * Browser check for typed array. */ dwv.browser.hasTypedArray = function() { return Modernizr.dataview && Modernizr.typedarrays; }; /** * Browser check for input with type='color'. * Missing in IE and Safari. */ dwv.browser.hasInputColor = function() { return Modernizr.inputtypes.color; }; /** * Browser check for input with type='files' and webkitdirectory flag. * Missing in IE and Safari. */ dwv.browser.hasInputDirectory = function() { return Modernizr.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; } }; // 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 || {}; 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; }));