1// Inspired from umdjs 2// See https://github.com/umdjs/umd/blob/master/templates/returnExports.js 3(function(root, factory) { 4 if (typeof define === 'function' && define.amd) { 5 // AMD. Register as an anonymous module. 6 define([ 7 'modernizr', 8 'i18next', 9 'i18nextXHRBackend', 10 'i18nextBrowserLanguageDetector', 11 'jszip', 12 'konva', 13 '' 14 ], factory); 15 } else if (typeof module === 'object' && module.exports) { 16 // Node. Does not work with strict CommonJS, but 17 // only CommonJS-like environments that support module.exports, 18 // like Node. 19 20 // i18next-xhr-backend: requires XMlHttpRequest 21 // Konva: requires 'canvas' -> deactivated for now... 22 // MagicWand: no package -> deactivated 23 24 module.exports = factory( 25 require('modernizr'), 26 require('i18next'), 27 require('i18next-xhr-backend'), 28 require('i18next-browser-languagedetector'), 29 require('jszip'), 30 null, 31 null 32 ); 33 } else { 34 // Browser globals (root is window) 35 root.dwv = factory( 36 root.Modernizr, 37 root.i18next, 38 root.i18nextXHRBackend, 39 root.i18nextBrowserLanguageDetector, 40 root.JSZip, 41 root.Konva, 42 root.MagicWand 43 ); 44 } 45}(this, function( 46 Modernizr, 47 i18next, 48 i18nextXHRBackend, 49 i18nextBrowserLanguageDetector, 50 JSZip, 51 Konva, 52 MagicWand) { 53 54 // similar to what browserify does but reversed 55 //https://www.contentful.com/blog/2017/01/17/the-global-object-in-javascript/ 56 var window = typeof window !== 'undefined' ? 57 window : typeof self !== 'undefined' ? 58 self : typeof global !== 'undefined' ? 59 global : {}; 60 61 /** @namespace */ 62 var dwv = dwv || {}; 63 64 /** 65 * Main application class. 66 * @constructor 67 */ 68 dwv.App = function() { 69 // Local object 70 var self = this; 71 72 // Image 73 var image = null; 74 // Original image 75 var originalImage = null; 76 // Image data array 77 var imageData = null; 78 // Image data width 79 var dataWidth = 0; 80 // Image data height 81 var dataHeight = 0; 82 // Is the data mono-slice? 83 var isMonoSliceData = 0; 84 85 // Default character set 86 var defaultCharacterSet; 87 88 // Container div id 89 var containerDivId = null; 90 // Display window scale 91 var windowScale = 1; 92 // Fit display to window flag 93 var fitToWindow = false; 94 // main scale 95 var scale = 1; 96 // zoom center 97 var scaleCenter = { "x": 0, "y": 0 }; 98 // translation 99 var translation = { "x": 0, "y": 0 }; 100 101 // View 102 var view = null; 103 // View controller 104 var viewController = null; 105 106 // Info layer controller 107 var infoController = null; 108 109 // Dicom tags gui 110 var tagsGui = null; 111 112 // Drawing list gui 113 var drawListGui = null; 114 115 // Image layer 116 var imageLayer = null; 117 118 // Draw controller 119 var drawController = null; 120 121 // Generic style 122 var style = new dwv.html.Style(); 123 124 // Toolbox controller 125 var toolboxController = null; 126 127 // Loadbox 128 var loadbox = null; 129 // Current loader 130 var currentLoader = null; 131 132 // UndoStack 133 var undoStack = null; 134 135 // listeners 136 var listeners = {}; 137 138 /** 139 * Get the image. 140 * @return {Image} The associated image. 141 */ 142 this.getImage = function() { return image; }; 143 /** 144 * Set the view. 145 * @param {Image} img The associated image. 146 */ 147 this.setImage = function(img) { 148 image = img; 149 view.setImage(img); 150 }; 151 /** 152 * Restore the original image. 153 */ 154 this.restoreOriginalImage = function() { 155 image = originalImage; 156 view.setImage(originalImage); 157 }; 158 /** 159 * Get the image data array. 160 * @return {Array} The image data array. 161 */ 162 this.getImageData = function() { return imageData; }; 163 /** 164 * Is the data mono-slice? 165 * @return {Boolean} True if the data is mono-slice.
166 */ 167 this.isMonoSliceData = function() { return isMonoSliceData; }; 168 169 /** 170 * Get the main scale. 171 * @return {Number} The main scale. 172 */ 173 this.getScale = function() { return scale / windowScale; }; 174 175 /** 176 * Get the window scale. 177 * @return {Number} The window scale. 178 */ 179 this.getWindowScale = function() { return windowScale; }; 180 181 /** 182 * Get the scale center. 183 * @return {Object} The coordinates of the scale center. 184 */ 185 this.getScaleCenter = function() { return scaleCenter; }; 186 187 /** 188 * Get the translation. 189 * @return {Object} The translation. 190 */ 191 this.getTranslation = function() { return translation; }; 192 193 /** 194 * Get the view controller. 195 * @return {Object} The controller. 196 */ 197 this.getViewController = function() { return viewController; }; 198 199 /** 200 * Get the image layer. 201 * @return {Object} The image layer. 202 */ 203 this.getImageLayer = function() { return imageLayer; }; 204 /** 205 * Get the current draw layer. 206 * @return {Object} The draw layer. 207 */ 208 this.getCurrentDrawLayer = function() { 209 return drawController.getCurrentDrawLayer(); 210 }; 211 /** 212 * Get the draw stage. 213 * @return {Object} The draw stage. 214 */ 215 this.getDrawStage = function() { 216 return drawController.getDrawStage(); 217 }; 218 219 /** 220 * Get the app style. 221 * @return {Object} The app style. 222 */
223 this.getStyle = function() { return style; }; 224 225 /** 226 * Add a command to the undo stack. 227 * @param {Object} The command to add. 228 */ 229 this.addToUndoStack = function(cmd) { 230 if (undoStack !== null) { 231 undoStack.add(cmd); 232 } 233 }; 234 235 /** 236 * Initialise the HTML for the application. 237 */ 238 this.init = function(config) { 239 containerDivId = config.containerDivId; 240 // tools 241 if (config.tools && config.tools.length !== 0) { 242 // setup the tool list 243 var toolList = {}; 244 for (var t = 0; t < config.tools.length; ++t) { 245 var toolName = config.tools[t]; 246 if (toolName === "Draw") { 247 if (config.shapes !== 0) { 248 // setup the shape list 249 var shapeList = {}; 250 for (var s = 0; s < config.shapes.length; ++s) { 251 var shapeName = config.shapes[s]; 252 var shapeFactoryClass = shapeName + "Factory"; 253 if (typeof dwv.tool[shapeFactoryClass] !== "undefined") { 254 shapeList[shapeName] = dwv.tool[shapeFactoryClass]; 255 } else { 256 console.warn("Could not initialise unknown shape: " + shapeName); 257 } 258 } 259 toolList.Draw = new dwv.tool.Draw(this, shapeList); 260 toolList.Draw.addEventListener("draw-create", fireEvent); 261 toolList.Draw.addEventListener("draw-change", fireEvent); 262 toolList.Draw.addEventListener("draw-move", fireEvent); 263 toolList.Draw.addEventListener("draw-delete", fireEvent); 264 } 265 } else if (toolName === "Filter") { 266 if (config.filters.length !== 0) { 267 // setup the filter list 268 var filterList = {}; 269 for (var f = 0; f < config.filters.length; ++f) { 270 var filterName = config.filters[f]; 271 if (typeof dwv.tool.filter[filterName] !== "undefined") { 272 filterList[filterName] = new dwv.tool.filter[filterName](this); 273 } else { 274 console.warn("Could not initialise unknown filter: " + filterName); 275 } 276 } 277 toolList.Filter = new dwv.tool.Filter(filterList, this); 278 toolList.Filter.addEventListener("filter-run", fireEvent); 279 toolList.Filter.addEventListener("filter-undo", fireEvent); 280 } 281 } else { 282 // default: find the tool in the dwv.tool namespace 283 var toolClass = toolName; 284 if (typeof dwv.tool[toolClass] !== "undefined") { 285 toolList[toolClass] = new dwv.tool[toolClass](this); 286 if (typeof toolList[toolClass].addEventListener !== "undefined") { 287 toolList[toolClass].addEventListener(fireEvent); 288 } 289 } else { 290 console.warn("Could not initialise unknown tool: " + toolName); 291 } 292 } 293 } 294 toolboxController = new dwv.ToolboxController(); 295 toolboxController.create(toolList, this); 296 } 297 // gui 298 if (config.gui) { 299 // tools 300 if (config.gui.indexOf("tool") !== -1 && toolboxController) { 301 toolboxController.setup(); 302 } 303 // load 304 if (config.gui.indexOf("load") !== -1) { 305 var loaderList = {}; 306 for (var l = 0; l < config.loaders.length; ++l) { 307 var loaderName = config.loaders[l]; 308 var loaderClass = loaderName + "Load"; 309 // default: find the loader in the dwv.gui namespace 310 if (typeof dwv.gui[loaderClass] !== "undefined") { 311 loaderList[loaderName] = dwv.gui[loaderClass](this); 312 } else { 313 console.warn("Could not initialise unknown loader: " + loaderName); 314 } 315 }
316 loadbox = dwv.gui.Loadbox(this, loaderList); 317 loadbox.setup(); 318 var loaderKeys = Object.keys(loaderList); 319 for (var lk = 0; lk < loaderKeys.length; ++lk) { 320 loaderList[loaderKeys[lk]].setup(); 321 } 322 loadbox.displayLoader(loaderKeys[0]); 323 } 324 // undo 325 if (config.gui.indexOf("undo") !== -1) { 326 undoStack = new dwv.tool.UndoStack(this); 327 undoStack.setup(); 328 } 329 // DICOM Tags 330 if (config.gui.indexOf("tags") !== -1) { 331 tagsGui = new dwv.gui.DicomTags(this); 332 } 333 // Draw list 334 if (config.gui.indexOf("drawList") !== -1) { 335 drawListGui = new dwv.gui.DrawList(this); 336 // update list on draw events 337 this.addEventListener("draw-create", drawListGui.update); 338 this.addEventListener("draw-change", drawListGui.update); 339 this.addEventListener("draw-delete", drawListGui.update); 340 } 341 // version number 342 if (config.gui.indexOf("version") !== -1) { 343 dwv.gui.appendVersionHtml(dwv.getVersion()); 344 } 345 // help 346 if (config.gui.indexOf("help") !== -1) { 347 var isMobile = true; 348 if (config.isMobile !== "undefined") { 349 isMobile = config.isMobile; 350 } 351 dwv.gui.appendHelpHtml(toolboxController.getToolList(), isMobile, this); 352 } 353 } 354 355 // listen to drag&drop 356 var box = this.getElement("dropBox"); 357 if (box) { 358 box.addEventListener("dragover", onDragOver); 359 box.addEventListener("dragleave", onDragLeave); 360 box.addEventListener("drop", onDrop); 361 // initial size 362 var size = dwv.gui.getWindowSize(); 363 var dropBoxSize = 2 * size.height / 3; 364 box.setAttribute("style", "width:" + dropBoxSize + "px;height:" + dropBoxSize + "px"); 365 } 366 367 // possible load from URL 368 if (typeof config.skipLoadUrl === "undefined") { 369 var query = dwv.utils.getUriQuery(window.location.href); 370 // check query 371 if (query && typeof query.input !== "undefined") { 372 dwv.utils.decodeQuery(query, this.onInputURLs); 373 // optional display state 374 if (typeof query.state !== "undefined") { 375 var onLoadEnd = function( /*event*/ ) { 376 loadStateUrl(query.state); 377 }; 378 this.addEventListener("load-end", onLoadEnd); 379 } 380 } 381 } else { 382 console.log("Not loading url from address since skipLoadUrl is defined."); 383 } 384 385 // align layers when the window is resized 386 if (config.fitToWindow) { 387 fitToWindow = true; 388 window.onresize = this.onResize; 389 } 390 391 // default character set 392 if (typeof config.defaultCharacterSet !== "undefined") { 393 defaultCharacterSet = config.defaultCharacterSet; 394 } 395 }; 396 397 /** 398 * Get a HTML element associated to the application. 399 * @param name The name or id to find. 400 * @return The found element or null. 401 */ 402 this.getElement = function(name) { 403 return dwv.gui.getElement(containerDivId, name); 404 }; 405 406 /** 407 * Reset the application. 408 */ 409 this.reset = function() { 410 // clear tools 411 if (toolboxController) { 412 toolboxController.reset(); 413 } 414 // clear draw 415 if (drawController) { 416 drawController.reset(); 417 } 418 // clear objects 419 image = null; 420 view = null; 421 isMonoSliceData = false;
422 // reset undo/redo 423 if (undoStack) { 424 undoStack = new dwv.tool.UndoStack(this); 425 undoStack.initialise(); 426 } 427 }; 428 429 /** 430 * Reset the layout of the application. 431 */ 432 this.resetLayout = function() { 433 var previousScale = scale; 434 var previousSC = scaleCenter; 435 var previousTrans = translation; 436 // reset values 437 scale = windowScale; 438 scaleCenter = { "x": 0, "y": 0 }; 439 translation = { "x": 0, "y": 0 }; 440 // apply new values 441 if (imageLayer) { 442 imageLayer.resetLayout(windowScale); 443 imageLayer.draw(); 444 } 445 if (drawController) { 446 drawController.resetStage(windowScale); 447 } 448 // fire events 449 if (previousScale != scale) { 450 fireEvent({ "type": "zoom-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y }); 451 } 452 if ((previousSC.x !== scaleCenter.x || previousSC.y !== scaleCenter.y) || 453 (previousTrans.x !== translation.x || previousTrans.y !== translation.y)) { 454 fireEvent({ "type": "offset-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y }); 455 } 456 }; 457 458 /** 459 * Add an event listener on the app. 460 * @param {String} type The event type. 461 * @param {Object} listener The method associated with the provided event type. 462 */ 463 this.addEventListener = function(type, listener) { 464 if (typeof listeners[type] === "undefined") { 465 listeners[type] = []; 466 } 467 listeners[type].push(listener); 468 }; 469 470 /** 471 * Remove an event listener from the app. 472 * @param {String} type The event type. 473 * @param {Object} listener The method associated with the provided event type. 474 */ 475 this.removeEventListener = function(type, listener) { 476 if (typeof listeners[type] === "undefined") { 477 return; 478 } 479 for (var i = 0; i < listeners[type].length; ++i) { 480 if (listeners[type][i] === listener) { 481 listeners[type].splice(i, 1); 482 } 483 } 484 }; 485 486 /** 487 * Load a list of files. Can be image files or a state file. 488 * @param {Array} files The list of files to load. 489 */ 490 this.loadFiles = function(files) { 491 // has been checked for emptiness. 492 var ext = files[0].name.split('.').pop().toLowerCase(); 493 if (ext === "json") { 494 loadStateFile(files[0]); 495 } else { 496 loadImageFiles(files); 497 } 498 }; 499 500 /** 501 * Load a list of image files. 502 * @private 503 * @param {Array} files The list of image files to load. 504 */ 505 function loadImageFiles(files) { 506 // create IO 507 var fileIO = new dwv.io.FilesLoader(); 508 // load data 509 loadImageData(files, fileIO); 510 } 511 512 /** 513 * Load a State file. 514 * @private 515 * @param {String} file The state file to load. 516 */ 517 function loadStateFile(file) { 518 // create IO 519 var fileIO = new dwv.io.FilesLoader(); 520 // load data 521 loadStateData([file], fileIO); 522 } 523 524 /** 525 * Load a list of URLs. Can be image files or a state file. 526 * @param {Array} urls The list of urls to load. 527 * @param {Array} requestHeaders An array of {name, value} to use as request headers. 528 */ 529 this.loadURLs = function(urls, requestHeaders) { 530 // has been checked for emptiness. 531 var ext = urls[0].split('.').pop().toLowerCase(); 532 if (ext === "json") { 533 loadStateUrl(urls[0], requestHeaders); 534 } else { 535 loadImageUrls(urls, requestHeaders); 536 } 537 }; 538 539 /** 540 * Abort the current load. 541 */ 542 this.abortLoad = function() { 543 if (currentLoader) { 544 currentLoader.abort(); 545 currentLoader = null; 546 } 547 }; 548 549 /** 550 * Load a list of ArrayBuffers. 551 * @param {Array} data The list of ArrayBuffers to load 552 * in the form of [{name: "", filename: "", data: data}]. 553 */ 554 this.loadImageObject = function(data) { 555 // create IO 556 var memoryIO = new dwv.io.MemoryLoader(); 557 // create options 558 var options = {}; 559 // load data 560 loadImageData(data, memoryIO, options); 561 }; 562 563 /** 564 * Load a list of image URLs. 565 * @private 566 * @param {Array} urls The list of urls to load. 567 * @param {Array} requestHeaders An array of {name, value} to use as request headers. 568 */ 569 function loadImageUrls(urls, requestHeaders) { 570 // create IO 571 var urlIO = new dwv.io.UrlsLoader(); 572 // create options 573 var options = { 'requestHeaders': requestHeaders }; 574 // load data 575 loadImageData(urls, urlIO, options); 576 } 577 578 /** 579 * Load a State url. 580 * @private 581 * @param {String} url The state url to load. 582 * @param {Array} requestHeaders An array of {name, value} to use as request headers. 583 */ 584 function loadStateUrl(url, requestHeaders) { 585 // create IO 586 var urlIO = new dwv.io.UrlsLoader(); 587 // create options 588 var options = { 'requestHeaders': requestHeaders }; 589 // load data 590 loadStateData([url], urlIO, options); 591 } 592 593 /** 594 * Load a list of image data. 595 * @private 596 * @param {Array} data Array of data to load. 597 * @param {Object} loader The data loader. 598 * @param {Object} options Options passed to the final loader. 599 */ 600 function loadImageData(data, loader, options) { 601 // store loader 602 currentLoader = loader; 603 604 // allow to cancel 605 var previousOnKeyDown = window.onkeydown; 606 window.onkeydown = function(event) { 607 if (event.ctrlKey && event.keyCode === 88) // crtl-x 608 { 609 console.log("crtl-x pressed!");
610 self.abortLoad(); 611 } 612 }; 613 614 // clear variables 615 self.reset(); 616 // first data name 617 var firstName = ""; 618 if (typeof data[0].name !== "undefined") { 619 firstName = data[0].name; 620 } else { 621 firstName = data[0]; 622 } 623 // flag used by scroll to decide wether to activate or not 624 // TODO: supposing multi-slice for zip files, could not be... 625 isMonoSliceData = (data.length === 1 && 626 firstName.split('.').pop().toLowerCase() !== "zip"); 627 // set IO 628 loader.setDefaultCharacterSet(defaultCharacterSet); 629 loader.onload = function(data) { 630 if (image) { 631 view.append(data.view); 632 if (drawController) { 633 drawController.appendDrawLayer(image.getNumberOfFrames()); 634 } 635 } 636 postLoadInit(data); 637 }; 638 loader.onerror = function(error) { handleError(error); }; 639 loader.onabort = function(error) { handleAbort(error); }; 640 loader.onloadend = function( /*event*/ ) { 641 window.onkeydown = previousOnKeyDown; 642 if (drawController) { 643 drawController.activateDrawLayer(viewController); 644 } 645 fireEvent({ 646 type: "load-progress", 647 lengthComputable: true, 648 loaded: 100, 649 total: 100 650 }); 651 fireEvent({ 'type': 'load-end' }); 652 // reset member 653 currentLoader = null; 654 }; 655 loader.onprogress = onLoadProgress; 656 // main load (asynchronous) 657 fireEvent({ 'type': 'load-start' }); 658 loader.load(data, options); 659 } 660 661 /** 662 * Load a State data. 663 * @private 664 * @param {Array} data Array of data to load. 665 * @param {Object} loader The data loader. 666 * @param {Object} options Options passed to the final loader. 667 */ 668 function loadStateData(data, loader, options) { 669 // set IO 670 loader.onload = function(data) { 671 // load state 672 var state = new dwv.State(self); 673 state.fromJSON(data); 674 }; 675 loader.onerror = function(error) { handleError(error); }; 676 // main load (asynchronous) 677 loader.load(data, options); 678 } 679 680 /** 681 * Fit the display to the given size. To be called once the image is loaded. 682 */ 683 this.fitToSize = function(size) { 684 // previous width 685 var oldWidth = parseInt(windowScale * dataWidth, 10); 686 // find new best fit 687 windowScale = Math.min((size.width / dataWidth), (size.height / dataHeight)); 688 // new sizes 689 var newWidth = parseInt(windowScale * dataWidth, 10); 690 var newHeight = parseInt(windowScale * dataHeight, 10); 691 // ratio previous/new to add to zoom 692 var mul = newWidth / oldWidth; 693 scale *= mul; 694 695 // update style 696 style.setScale(windowScale); 697 698 // resize container 699 var container = this.getElement("layerContainer"); 700 container.setAttribute("style", "width:" + newWidth + "px;height:" + newHeight + "px"); 701 // resize image layer 702 if (imageLayer) { 703 imageLayer.setWidth(newWidth); 704 imageLayer.setHeight(newHeight); 705 imageLayer.zoom(scale, scale, 0, 0); 706 imageLayer.draw(); 707 } 708 // resize draw stage 709 if (drawController) { 710 drawController.resizeStage(newWidth, newHeight, scale); 711 } 712 }; 713 714 /** 715 * Toggle the display of the information layer. 716 */ 717 this.toggleInfoLayerDisplay = function() { 718 // toggle html 719 var infoLayer = self.getElement("infoLayer"); 720 dwv.html.toggleDisplay(infoLayer); 721 // toggle listeners 722 infoController.toggleListeners(self, view); 723 }; 724 725 /** 726 * Init the Window/Level display 727 */ 728 this.initWLDisplay = function() { 729 // set window/level to first preset 730 viewController.setWindowLevelPresetById(0); 731 // default position 732 viewController.setCurrentPosition2D(0, 0); 733 // default frame 734 viewController.setCurrentFrame(0); 735 }; 736 737 /** 738 * Add canvas mouse and touch listeners. 739 * @param {Object} canvas The canvas to listen to. 740 */ 741 this.addToolCanvasListeners = function(layer) { 742 toolboxController.addCanvasListeners(layer); 743 }; 744 745 /**
746 * Remove layer mouse and touch listeners. 747 * @param {Object} canvas The canvas to stop listening to. 748 */ 749 this.removeToolCanvasListeners = function(layer) { 750 toolboxController.removeCanvasListeners(layer); 751 }; 752 753 /** 754 * Render the current image. 755 */ 756 this.render = function() { 757 generateAndDrawImage(); 758 }; 759 760 /** 761 * Zoom to the layers. 762 * @param {Number} zoom The zoom to apply. 763 * @param {Number} cx The zoom center X coordinate. 764 * @param {Number} cy The zoom center Y coordinate. 765 */ 766 this.zoom = function(zoom, cx, cy) { 767 scale = zoom * windowScale; 768 if (scale <= 0.1) { 769 scale = 0.1; 770 } 771 scaleCenter = { "x": cx, "y": cy }; 772 zoomLayers(); 773 }; 774 775 /** 776 * Add a step to the layers zoom. 777 * @param {Number} step The zoom step increment. A good step is of 0.1. 778 * @param {Number} cx The zoom center X coordinate. 779 * @param {Number} cy The zoom center Y coordinate. 780 */ 781 this.stepZoom = function(step, cx, cy) { 782 scale += step; 783 if (scale <= 0.1) { 784 scale = 0.1; 785 } 786 scaleCenter = { "x": cx, "y": cy }; 787 zoomLayers(); 788 }; 789 790 /** 791 * Apply a translation to the layers. 792 * @param {Number} tx The translation along X. 793 * @param {Number} ty The translation along Y. 794 */ 795 this.translate = function(tx, ty) { 796 translation = { "x": tx, "y": ty }; 797 translateLayers(); 798 }; 799 800 /** 801 * Add a translation to the layers. 802 * @param {Number} tx The step translation along X. 803 * @param {Number} ty The step translation along Y. 804 */ 805 this.stepTranslate = function(tx, ty) { 806 var txx = translation.x + tx / scale; 807 var tyy = translation.y + ty / scale; 808 translation = { "x": txx, "y": tyy }; 809 translateLayers(); 810 }; 811 812 /** 813 * Get the list of drawing display details. 814 * @return {Object} The list of draw details including id, slice, frame... 815 */ 816 this.getDrawDisplayDetails = function() { 817 return drawController.getDrawDisplayDetails(); 818 }; 819 /** 820 * Get the list of drawings. 821 * @return {Object} The list of drawings. 822 */ 823 this.getDraws = function() { 824 return drawController.getDraws(); 825 }; 826 /** 827 * Get a list of drawing store details. 828 * @return {Object} A list of draw details including id, text, quant... 829 */ 830 this.getDrawStoreDetails = function() { 831 return drawController.getDrawStoreDetails(); 832 }; 833 /** 834 * Set the drawings on the current stage. 835 * @param {Array} drawings An array of drawings. 836 * @param {Array} drawingsDetails An array of drawings details. 837 */ 838 this.setDrawings = function(drawings, drawingsDetails) { 839 drawController.setDrawings(drawings, drawingsDetails, fireEvent, this.addToUndoStack); 840 }; 841 /** 842 * Update a drawing from its details. 843 * @param {Object} drawDetails Details of the drawing to update. 844 */ 845 this.updateDraw = function(drawDetails) { 846 drawController.updateDraw(drawDetails); 847 }; 848 /** 849 * Delete all Draws from all layers. 850 */ 851 this.deleteDraws = function() { 852 drawController.deleteDraws(fireEvent, this.addToUndoStack); 853 }; 854 /** 855 * Check the visibility of a given group. 856 * @param {Object} drawDetails Details of the drawing to check. 857 */ 858 this.isGroupVisible = function(drawDetails) { 859 return drawController.isGroupVisible(drawDetails); 860 }; 861 /** 862 * Toggle group visibility. 863 * @param {Object} drawDetails Details of the drawing to update. 864 */ 865 this.toogleGroupVisibility = function(drawDetails) { 866 drawController.toogleGroupVisibility(drawDetails); 867 }; 868 869 // Handler Methods ----------------------------------------------------------- 870 871 /** 872 * Handle window/level change. 873 * @param {Object} event The event fired when changing the window/level. 874 */ 875 this.onWLChange = function(event) { 876 // generate and draw if no skip flag 877 if (typeof event.skipGenerate === "undefined" || 878 event.skipGenerate === false) { 879 generateAndDrawImage(); 880 } 881 }; 882 883 /** 884 * Handle colour map change. 885 * @param {Object} event The event fired when changing the colour map. 886 */ 887 this.onColourChange = function( /*event*/ ) { 888 generateAndDrawImage(); 889 }; 890 891 /** 892 * Handle frame change. 893 * @param {Object} event The event fired when changing the frame. 894 */ 895 this.onFrameChange = function( /*event*/ ) { 896 generateAndDrawImage(); 897 if (drawController) { 898 drawController.activateDrawLayer(viewController); 899 } 900 }; 901 902 /** 903 * Handle slice change. 904 * @param {Object} event The event fired when changing the slice. 905 */ 906 this.onSliceChange = function( /*event*/ ) { 907 generateAndDrawImage(); 908 if (drawController) { 909 drawController.activateDrawLayer(viewController); 910 } 911 }; 912 913 /** 914 * Handle key down event. 915 * - CRTL-Z: undo 916 * - CRTL-Y: redo 917 * - CRTL-ARROW_LEFT: next frame 918 * - CRTL-ARROW_UP: next slice 919 * - CRTL-ARROW_RIGHT: previous frame 920 * - CRTL-ARROW_DOWN: previous slice 921 * Default behavior. Usually used in tools. 922 * @param {Object} event The key down event. 923 */ 924 this.onKeydown = function(event) { 925 if (event.ctrlKey) { 926 if (event.keyCode === 37) // crtl-arrow-left 927 { 928 event.preventDefault(); 929 self.getViewController().decrementFrameNb(); 930 } else if (event.keyCode === 38) // crtl-arrow-up 931 { 932 event.preventDefault(); 933 self.getViewController().incrementSliceNb(); 934 } else if (event.keyCode === 39) // crtl-arrow-right 935 { 936 event.preventDefault(); 937 self.getViewController().incrementFrameNb(); 938 } else if (event.keyCode === 40) // crtl-arrow-down 939 { 940 event.preventDefault(); 941 self.getViewController().decrementSliceNb(); 942 } else if (event.keyCode === 89) // crtl-y 943 { 944 undoStack.redo(); 945 } else if (event.keyCode === 90) // crtl-z 946 { 947 undoStack.undo(); 948 } 949 } 950 }; 951 952 /** 953 * Handle resize. 954 * Fit the display to the window. To be called once the image is loaded. 955 * @param {Object} event The change event. 956 */ 957 this.onResize = function( /*event*/ ) { 958 self.fitToSize(dwv.gui.getWindowSize()); 959 }; 960 961 /** 962 * Handle zoom reset.
963 * @param {Object} event The change event. 964 */ 965 this.onZoomReset = function( /*event*/ ) { 966 self.resetLayout(); 967 }; 968 969 /** 970 * Handle loader change. 971 * @param {Object} event The change event. 972 */ 973 this.onChangeLoader = function( /*event*/ ) { 974 // called from an HTML select, use its value 975 loadbox.displayLoader(this.value); 976 }; 977 978 /** 979 * Reset the load box to its original state. 980 */ 981 this.resetLoadbox = function() { 982 loadbox.reset(); 983 }; 984 985 /** 986 * Handle change url event. 987 * @param {Object} event The event fired when changing the url field. 988 */ 989 this.onChangeURL = function(event) { 990 self.loadURLs([event.target.value]); 991 }; 992 993 /** 994 * Handle input urls. 995 * @param {Array} urls The list of input urls. 996 * @param {Array} requestHeaders An array of {name, value} to use as request headers. 997 */ 998 this.onInputURLs = function(urls, requestHeaders) { 999 self.loadURLs(urls, requestHeaders); 1000 }; 1001 1002 /** 1003 * Handle change files event. 1004 * @param {Object} event The event fired when changing the file field. 1005 */ 1006 this.onChangeFiles = function(event) { 1007 var files = event.target.files; 1008 if (files.length !== 0) { 1009 self.loadFiles(files); 1010 } 1011 }; 1012 1013 /** 1014 * Handle state save event. 1015 * @param {Object} event The event fired when changing the state save field. 1016 */ 1017 this.onStateSave = function( /*event*/ ) { 1018 var state = new dwv.State(self); 1019 // add href to link (html5) 1020 var element = self.getElement("download-state"); 1021 element.href = "data:application/json," + state.toJSON(); 1022 }; 1023 1024 /** 1025 * Handle colour map change. 1026 * @param {Object} event The change event. 1027 */ 1028 this.onChangeColourMap = function( /*event*/ ) { 1029 // called from an HTML select, use its value 1030 viewController.setColourMapFromName(this.value); 1031 }; 1032 1033 /** 1034 * Handle window/level preset change. 1035 * @param {Object} event The change event. 1036 */ 1037 this.onChangeWindowLevelPreset = function( /*event*/ ) { 1038 // value should be the name of the preset 1039 viewController.setWindowLevelPreset(this.value); 1040 }; 1041 1042 /** 1043 * Handle tool change. 1044 * @param {Object} event The change event. 1045 */ 1046 this.onChangeTool = function( /*event*/ ) { 1047 // called from an HTML select, use its value 1048 toolboxController.setSelectedTool(this.value); 1049 }; 1050 1051 /** 1052 * Handle shape change. 1053 * @param {Object} event The change event. 1054 */ 1055 this.onChangeShape = function( /*event*/ ) { 1056 // called from an HTML select, use its value 1057 toolboxController.setSelectedShape(this.value); 1058 }; 1059 1060 /** 1061 * Handle filter change. 1062 * @param {Object} event The change event. 1063 */ 1064 this.onChangeFilter = function( /*event*/ ) { 1065 // called from an HTML select, use its value 1066 toolboxController.setSelectedFilter(this.value); 1067 }; 1068 1069 /** 1070 * Handle filter run. 1071 * @param {Object} event The run event. 1072 */ 1073 this.onRunFilter = function( /*event*/ ) { 1074 toolboxController.runSelectedFilter(); 1075 }; 1076 1077 /** 1078 * Handle line colour change. 1079 * @param {Object} event The change event. 1080 */ 1081 this.onChangeLineColour = function( /*event*/ ) { 1082 // called from an HTML select, use its value 1083 toolboxController.setLineColour(this.value); 1084 }; 1085 1086 /** 1087 * Handle min/max slider change. 1088 * @param {Object} range The new range of the data. 1089 */ 1090 this.onChangeMinMax = function(range) { 1091 toolboxController.setRange(range); 1092 }; 1093 1094 /** 1095 * Handle undo. 1096 * @param {Object} event The associated event. 1097 */ 1098 this.onUndo = function( /*event*/ ) { 1099 undoStack.undo(); 1100 }; 1101 1102 /** 1103 * Handle redo. 1104 * @param {Object} event The associated event. 1105 */ 1106 this.onRedo = function( /*event*/ ) { 1107 undoStack.redo(); 1108 }; 1109 1110 /** 1111 * Handle toggle of info layer. 1112 * @param {Object} event The associated event. 1113 */ 1114 this.onToggleInfoLayer = function( /*event*/ ) { 1115 self.toggleInfoLayerDisplay(); 1116 }; 1117 1118 /** 1119 * Handle display reset. 1120 * @param {Object} event The change event. 1121 */ 1122 this.onDisplayReset = function( /*event*/ ) { 1123 self.resetLayout(); 1124 self.initWLDisplay(); 1125 // update preset select 1126 var select = self.getElement("presetSelect"); 1127 if (select) { 1128 select.selectedIndex = 0; 1129 dwv.gui.refreshElement(select); 1130 } 1131 }; 1132 1133 1134 // Private Methods ----------------------------------------------------------- 1135 1136 /** 1137 * Fire an event: call all associated listeners. 1138 * @param {Object} event The event to fire. 1139 */ 1140 function fireEvent(event) { 1141 if (typeof listeners[event.type] === "undefined") { 1142 return; 1143 } 1144 for (var i = 0; i < listeners[event.type].length; ++i) { 1145 listeners[event.type][i](event); 1146 } 1147 } 1148 1149 /** 1150 * Generate the image data and draw it. 1151 */ 1152 function generateAndDrawImage() { 1153 // generate image data from DICOM 1154 view.generateImageData(imageData); 1155 // set the image data of the layer 1156 imageLayer.setImageData(imageData); 1157 // draw the image 1158 imageLayer.draw(); 1159 } 1160 1161 /** 1162 * Apply the stored zoom to the layers. 1163 */ 1164 function zoomLayers() { 1165 // image layer 1166 if (imageLayer) { 1167 imageLayer.zoom(scale, scale, scaleCenter.x, scaleCenter.y); 1168 imageLayer.draw(); 1169 } 1170 // draw layer 1171 if (drawController) { 1172 drawController.zoomStage(scale, scaleCenter); 1173 } 1174 // fire event 1175 fireEvent({ "type": "zoom-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y }); 1176 } 1177 1178 /** 1179 * Apply the stored translation to the layers. 1180 */ 1181 function translateLayers() { 1182 // image layer 1183 if (imageLayer) { 1184 imageLayer.translate(translation.x, translation.y); 1185 imageLayer.draw(); 1186 // draw layer 1187 if (drawController) { 1188 var ox = -imageLayer.getOrigin().x / scale - translation.x; 1189 var oy = -imageLayer.getOrigin().y / scale - translation.y; 1190 drawController.translateStage(ox, oy); 1191 } 1192 // fire event 1193 fireEvent({ 1194 "type": "offset-change", 1195 "scale": scale, 1196 "cx": imageLayer.getTrans().x, 1197 "cy": imageLayer.getTrans().y 1198 }); 1199 } 1200 } 1201 1202 /** 1203 * Handle a drag over. 1204 * @private 1205 * @param {Object} event The event to handle. 1206 */ 1207 function onDragOver(event) { 1208 // prevent default handling 1209 event.stopPropagation(); 1210 event.preventDefault(); 1211 // update box 1212 var box = self.getElement("dropBox"); 1213 if (box) { 1214 box.className = 'dropBox hover'; 1215 } 1216 } 1217 1218 /** 1219 * Handle a drag leave. 1220 * @private 1221 * @param {Object} event The event to handle. 1222 */ 1223 function onDragLeave(event) { 1224 // prevent default handling 1225 event.stopPropagation(); 1226 event.preventDefault(); 1227 // update box 1228 var box = self.getElement("dropBox hover"); 1229 if (box) { 1230 box.className = 'dropBox'; 1231 } 1232 } 1233 1234 /** 1235 * Handle a drop event. 1236 * @private 1237 * @param {Object} event The event to handle. 1238 */ 1239 function onDrop(event) { 1240 // prevent default handling 1241 event.stopPropagation(); 1242 event.preventDefault(); 1243 // load files 1244 self.loadFiles(event.dataTransfer.files); 1245 } 1246 1247 /** 1248 * Handle an error: display it to the user. 1249 * @private 1250 * @param {Object} error The error to handle. 1251 */ 1252 function handleError(error) { 1253 // alert window 1254 if (error.name && error.message) { 1255 alert(error.name + ": " + error.message); 1256 } else { 1257 alert("Error: " + error + "."); 1258 } 1259 // log 1260 if (error.stack) { 1261 console.error(error.stack); 1262 } 1263 // stop progress 1264 dwv.gui.displayProgress(100); 1265 } 1266 1267 /** 1268 * Handle an abort: display it to the user. 1269 * @param {Object} error The error to handle. 1270 * @private 1271 */ 1272 function handleAbort(error) { 1273 // log 1274 if (error.message) { 1275 console.warn(error.message); 1276 } else { 1277 console.warn("Abort called."); 1278 } 1279 // stop progress 1280 dwv.gui.displayProgress(100); 1281 } 1282 1283 /** 1284 * Handle a load progress. 1285 * @private 1286 * @param {Object} event The event to handle. 1287 */ 1288 function onLoadProgress(event) { 1289 fireEvent(event); 1290 if (event.lengthComputable) {
1291 var percent = Math.ceil((event.loaded / event.total) * 100); 1292 dwv.gui.displayProgress(percent); 1293 } 1294 } 1295 1296 /** 1297 * Create the application layers. 1298 * @private 1299 * @param {Number} dataWidth The width of the input data. 1300 * @param {Number} dataHeight The height of the input data. 1301 */ 1302 function createLayers(dataWidth, dataHeight) { 1303 // image layer 1304 var canImgLay = self.getElement("imageLayer"); 1305 imageLayer = new dwv.html.Layer(canImgLay); 1306 imageLayer.initialise(dataWidth, dataHeight); 1307 imageLayer.fillContext(); 1308 imageLayer.setStyleDisplay(true); 1309 // draw layer 1310 var drawDiv = self.getElement("drawDiv"); 1311 if (drawDiv) { 1312 drawController = new dwv.DrawController(drawDiv); 1313 drawController.create(dataWidth, dataHeight); 1314 } 1315 // resize app 1316 if (fitToWindow) { 1317 self.fitToSize(dwv.gui.getWindowSize()); 1318 } else { 1319 self.fitToSize({ 1320 'width': self.getElement("layerContainer").offsetWidth, 1321 'height': self.getElement("layerContainer").offsetHeight 1322 }); 1323 } 1324 self.resetLayout(); 1325 } 1326 1327 /** 1328 * Post load application initialisation. To be called once the DICOM has been parsed. 1329 * @private 1330 * @param {Object} data The data to display. 1331 */ 1332 function postLoadInit(data) { 1333 // only initialise the first time 1334 if (view) { 1335 return; 1336 } 1337 1338 // get the view from the loaded data 1339 view = data.view; 1340 viewController = new dwv.ViewController(view); 1341 1342 // append the DICOM tags table 1343 if (tagsGui) { 1344 tagsGui.update(data.info); 1345 } 1346 // store image 1347 originalImage = view.getImage(); 1348 image = originalImage; 1349 1350 // layout 1351 var size = image.getGeometry().getSize(); 1352 dataWidth = size.getNumberOfColumns(); 1353 dataHeight = size.getNumberOfRows(); 1354 createLayers(dataWidth, dataHeight); 1355 1356 // get the image data from the image layer 1357 imageData = imageLayer.getContext().createImageData( 1358 dataWidth, dataHeight); 1359 1360 // image listeners 1361 view.addEventListener("wl-width-change", self.onWLChange); 1362 view.addEventListener("wl-center-change", self.onWLChange); 1363 view.addEventListener("colour-change", self.onColourChange); 1364 view.addEventListener("slice-change", self.onSliceChange); 1365 view.addEventListener("frame-change", self.onFrameChange); 1366 1367 // connect with local listeners 1368 view.addEventListener("wl-width-change", fireEvent); 1369 view.addEventListener("wl-center-change", fireEvent); 1370 view.addEventListener("colour-change", fireEvent); 1371 view.addEventListener("position-change", fireEvent); 1372 view.addEventListener("slice-change", fireEvent); 1373 view.addEventListener("frame-change", fireEvent); 1374 1375 // append draw layers (before initialising the toolbox) 1376 if (drawController) { 1377 drawController.appendDrawLayer(image.getNumberOfFrames()); 1378 } 1379 1380 // initialise the toolbox 1381 if (toolboxController) { 1382 toolboxController.initAndDisplay(imageLayer); 1383 } 1384 1385 // stop box listening to drag (after first drag) 1386 var box = self.getElement("dropBox"); 1387 if (box) { 1388 box.removeEventListener("dragover", onDragOver); 1389 box.removeEventListener("dragleave", onDragLeave); 1390 box.removeEventListener("drop", onDrop); 1391 dwv.html.removeNode(box); 1392 // switch listening to layerContainer 1393 var div = self.getElement("layerContainer"); 1394 div.addEventListener("dragover", onDragOver); 1395 div.addEventListener("dragleave", onDragLeave); 1396 div.addEventListener("drop", onDrop); 1397 } 1398 1399 // info layer 1400 var infoLayer = self.getElement("infoLayer"); 1401 if (infoLayer) { 1402 infoController = new dwv.InfoController(containerDivId); 1403 infoController.create(self); 1404 infoController.toggleListeners(self, view); 1405 } 1406 1407 // init W/L display 1408 self.initWLDisplay(); 1409 // generate first image 1410 generateAndDrawImage(); 1411 } 1412 1413 }; 1414 1415 // namespaces 1416 var dwv = dwv || {}; 1417 // external 1418 var Konva = Konva || {}; 1419 1420 /** 1421 * Draw controller. 1422 * @constructor 1423 * @param {Object} drawDiv The HTML div used to store the drawings. 1424 * @external Konva 1425 */ 1426 dwv.DrawController = function(drawDiv) { 1427 1428 // Draw stage 1429 var drawStage = null; 1430 // Draw layers: 2 dimension array: [slice][frame] 1431 var drawLayers = []; 1432 1433 // current slice position 1434 var currentSlice = 0; 1435 // current frame position 1436 var currentFrame = 0; 1437 1438 /** 1439 * Create the controller: sets up the draw stage. 1440 * @param {Number} width The width of the stage. 1441 * @param {Number} height The height of the stage. 1442 */ 1443 this.create = function(width, height) { 1444 // create stage 1445 drawStage = new Konva.Stage({ 1446 'container': drawDiv, 1447 'width': width, 1448 'height': height, 1449 'listening': false 1450 }); 1451 // reset style 1452 // (avoids a not needed vertical scrollbar) 1453 drawStage.getContent().setAttribute("style", ""); 1454 }; 1455 1456 /** 1457 * Get the current draw layer. 1458 * @return {Object} The draw layer. 1459 */ 1460 this.getCurrentDrawLayer = function() { 1461 //return this.getDrawLayer(currentSlice, currentFrame); 1462 return drawLayers[currentSlice][currentFrame]; 1463 }; 1464 1465 /** 1466 * Reset: clear the layers array. 1467 */ 1468 this.reset = function() { 1469 drawLayers = []; 1470 }; 1471 1472 /** 1473 * Get the draw stage. 1474 * @return {Object} The draw layer. 1475 */ 1476 this.getDrawStage = function() { 1477 return drawStage; 1478 }; 1479 1480 /** 1481 * Activate the current draw layer. 1482 * @param {Object} viewController The associated view controller. 1483 */ 1484 this.activateDrawLayer = function(viewController) { 1485 // hide all draw layers 1486 for (var k = 0, lenk = drawLayers.length; k < lenk; ++k) { 1487 for (var f = 0, lenf = drawLayers[k].length; f < lenf; ++f) { 1488 drawLayers[k][f].visible(false); 1489 } 1490 } 1491 // set current position 1492 currentSlice = viewController.getCurrentPosition().k; 1493 currentFrame = viewController.getCurrentFrame(); 1494 // show current draw layer 1495 var currentLayer = this.getCurrentDrawLayer(); 1496 currentLayer.visible(true); 1497 currentLayer.draw(); 1498 }; 1499 1500 /** 1501 * Reset the stage with a new window scale. 1502 * @param {Number} windowScale The window scale. 1503 */ 1504 this.resetStage = function(windowScale) { 1505 drawStage.offset({ 'x': 0, 'y': 0 });
1506 drawStage.scale({ 'x': windowScale, 'y': windowScale }); 1507 drawStage.draw(); 1508 }; 1509 1510 /** 1511 * Resize the current stage. 1512 * @param {Number} width the stage width. 1513 * @param {Number} height the stage height. 1514 * @param {Number} scale the stage scale. 1515 */ 1516 this.resizeStage = function(width, height, scale) { 1517 // resize div 1518 drawDiv.setAttribute("style", "width:" + width + "px;height:" + height + "px"); 1519 // resize stage 1520 drawStage.setWidth(width); 1521 drawStage.setHeight(height); 1522 drawStage.scale({ 'x': scale, 'y': scale }); 1523 drawStage.draw(); 1524 }; 1525 1526 /** 1527 * Zoom the stage. 1528 * @param {Number} scale The scale factor. 1529 * @param {Object} scaleCenter The scale center point. 1530 */ 1531 this.zoomStage = function(scale, scaleCenter) { 1532 // zoom 1533 var newScale = { 'x': scale, 'y': scale }; 1534 // offset 1535 // TODO different from the imageLayer offset? 1536 var oldScale = drawStage.scale(); 1537 var oldOffset = drawStage.offset(); 1538 var newOffsetX = (scaleCenter.x / oldScale.x) + 1539 oldOffset.x - (scaleCenter.x / newScale.x); 1540 var newOffsetY = (scaleCenter.y / oldScale.y) + 1541 oldOffset.y - (scaleCenter.y / newScale.y); 1542 var newOffset = { 'x': newOffsetX, 'y': newOffsetY }; 1543 // store 1544 drawStage.offset(newOffset); 1545 drawStage.scale(newScale); 1546 drawStage.draw(); 1547 }; 1548 1549 /** 1550 * Translate the stage. 1551 * @param {Number} tx The X translation. 1552 * @param {Number} ty The Y translation. 1553 */ 1554 this.translateStage = function(tx, ty) { 1555 drawStage.offset({ 'x': tx, 'y': ty }); 1556 drawStage.draw(); 1557 }; 1558 1559 /** 1560 * Append a new draw layer list to the list. 1561 * @param {Number} nLayers The size of the layers array to append to the current one. 1562 */ 1563 this.appendDrawLayer = function(nLayers) { 1564 // add a new dimension 1565 drawLayers.push([]); 1566 // fill it 1567 for (var i = 0; i < nLayers; ++i) { 1568 // create draw layer 1569 var drawLayer = new Konva.Layer({ 1570 'listening': false, 1571 'hitGraphEnabled': false, 1572 'visible': false 1573 }); 1574 drawLayers[drawLayers.length - 1].push(drawLayer); 1575 // add the layer to the stage 1576 drawStage.add(drawLayer); 1577 } 1578 }; 1579 1580 /** 1581 * Get a list of drawing display details. 1582 * @return {Object} A list of draw details including id, slice, frame... 1583 */ 1584 this.getDrawDisplayDetails = function() { 1585 var list = []; 1586 for (var k = 0, lenk = drawLayers.length; k < lenk; ++k) { 1587 for (var f = 0, lenf = drawLayers[k].length; f < lenf; ++f) { 1588 var collec = drawLayers[k][f].getChildren(); 1589 for (var i = 0, leni = collec.length; i < leni; ++i) { 1590 var shape = collec[i].getChildren(isNodeNameShape)[0]; 1591 var label = collec[i].getChildren(isNodeNameLabel)[0]; 1592 var text = label.getChildren()[0]; 1593 var type = shape.className; 1594 if (type === "Line") { 1595 var shapeExtrakids = collec[i].getChildren(isNodeNameShapeExtra); 1596 if (shape.closed()) { 1597 type = "Roi"; 1598 } else if (shapeExtrakids.length !== 0) { 1599 if (shapeExtrakids[0].name().indexOf("triangle") !== -1) { 1600 type = "Arrow"; 1601 } else { 1602 type = "Ruler"; 1603 } 1604 } 1605 } 1606 if (type === "Rect") { 1607 type = "Rectangle"; 1608 } 1609 list.push({ 1610 "id": collec[i].id(), 1611 "slice": k, 1612 "frame": f, 1613 "type": type, 1614 "color": shape.stroke(), 1615 "label": text.textExpr, 1616 "description": text.longText 1617 }); 1618 } 1619 } 1620 } 1621 // return 1622 return list; 1623 }; 1624 1625 /** 1626 * Get all the draws of the stage. 1627 */ 1628 this.getDraws = function() { 1629 var drawGroups = []; 1630 for (var k = 0, lenk = drawLayers.length; k < lenk; ++k) { 1631 drawGroups[k] = []; 1632 for (var f = 0, lenf = drawLayers[k].length; f < lenf; ++f) { 1633 // getChildren always return, so drawings will have the good size 1634 var groups = drawLayers[k][f].getChildren();
1635 drawGroups[k].push(groups); 1636 } 1637 } 1638 return drawGroups; 1639 }; 1640 1641 /** 1642 * Get a list of drawing store details. 1643 * @return {Object} A list of draw details including id, text, quant... 1644 * TODO Unify with getDrawDisplayDetails? 1645 */ 1646 this.getDrawStoreDetails = function() { 1647 var drawingsDetails = []; 1648 for (var k = 0, lenk = drawLayers.length; k < lenk; ++k) { 1649 drawingsDetails[k] = []; 1650 for (var f = 0, lenf = drawLayers[k].length; f < lenf; ++f) { 1651 // getChildren always return, so drawings will have the good size 1652 var groups = drawLayers[k][f].getChildren(); 1653 var details = []; 1654 for (var i = 0, leni = groups.length; i < leni; ++i) { 1655 // remove anchors 1656 var anchors = groups[i].find(".anchor"); 1657 for (var a = 0; a < anchors.length; ++a) { 1658 anchors[a].remove(); 1659 } 1660 // get text 1661 var texts = groups[i].find(".text"); 1662 if (texts.length !== 1) { 1663 console.warn("There should not be more than one text per shape."); 1664 } 1665 // get details (non konva vars) 1666 details.push({ 1667 "id": groups[i].id(), 1668 "textExpr": encodeURIComponent(texts[0].textExpr), 1669 "longText": encodeURIComponent(texts[0].longText), 1670 "quant": texts[0].quant 1671 }); 1672 } 1673 drawingsDetails[k].push(details); 1674 } 1675 } 1676 return drawingsDetails; 1677 }; 1678 1679 /** 1680 * Set the drawings on the current stage. 1681 * @param {Array} drawings An array of drawings. 1682 * @param {Array} drawingsDetails An array of drawings details. 1683 * @param {Object} cmdCallback The DrawCommand callback. 1684 * @param {Object} exeCallback The callback to call once the DrawCommand has been executed. 1685 */ 1686 this.setDrawings = function(drawings, drawingsDetails, cmdCallback, exeCallback) { 1687 // loop through layers 1688 for (var k = 0, lenk = drawLayers.length; k < lenk; ++k) { 1689 for (var f = 0, lenf = drawLayers[k].length; f < lenf; ++f) { 1690 for (var i = 0, leni = drawings[k][f].length; i < leni; ++i) { 1691 // create the group 1692 var group = Konva.Node.create(drawings[k][f][i]); 1693 var shape = group.getChildren(isNodeNameShape)[0]; 1694 // create the draw command 1695 var cmd = new dwv.tool.DrawGroupCommand( 1696 group, shape.className, 1697 drawLayers[k][f]); 1698 // draw command callbacks 1699 cmd.onExecute = cmdCallback; 1700 cmd.onUndo = cmdCallback; 1701 // text (new in v0.2) 1702 // TODO Verify ID? 1703 if (drawingsDetails) { 1704 var details = drawingsDetails[k][f][i]; 1705 var label = group.getChildren(isNodeNameLabel)[0]; 1706 var text = label.getText(); 1707 // store details 1708 text.textExpr = details.textExpr; 1709 text.longText = details.longText; 1710 text.quant = details.quant; 1711 // reset text (it was not encoded) 1712 text.setText(dwv.utils.replaceFlags(text.textExpr, text.quant)); 1713 } 1714 // execute 1715 cmd.execute(); 1716 exeCallback(cmd); 1717 } 1718 } 1719 } 1720 }; 1721 1722 /** 1723 * Update a drawing from its details. 1724 * @param {Object} drawDetails Details of the drawing to update. 1725 */ 1726 this.updateDraw = function(drawDetails) { 1727 // get the group 1728 var group = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id); 1729 // shape 1730 var shapes = group.getChildren(isNodeNameShape); 1731 for (var i = 0; i < shapes.length; ++i) { 1732 shapes[i].stroke(drawDetails.color); 1733 } 1734 // shape extra 1735 var shapesExtra = group.getChildren(isNodeNameShapeExtra); 1736 for (var j = 0; j < shapesExtra.length; ++j) { 1737 if (typeof shapesExtra[j].stroke() !== "undefined") { 1738 shapesExtra[j].stroke(drawDetails.color); 1739 } else if (typeof shapesExtra[j].fill() !== "undefined") { 1740 shapesExtra[j].fill(drawDetails.color); 1741 } 1742 } 1743 // label 1744 var label = group.getChildren(isNodeNameLabel)[0]; 1745 var text = label.getChildren()[0]; 1746 text.fill(drawDetails.color); 1747 text.textExpr = drawDetails.label; 1748 text.longText = drawDetails.description; 1749 text.setText(dwv.utils.replaceFlags(text.textExpr, text.quant)); 1750 1751 // udpate current layer 1752 this.getCurrentDrawLayer().draw(); 1753 }; 1754 1755 /** 1756 * Check the visibility of a given group. 1757 * @param {Object} drawDetails Details of the group to check. 1758 */ 1759 this.isGroupVisible = function(drawDetails) { 1760 // get the group 1761 var group = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id); 1762 // get visibility 1763 return group.isVisible(); 1764 }; 1765 1766 /** 1767 * Toggle the visibility of a given group. 1768 * @param {Object} drawDetails Details of the group to update. 1769 */ 1770 this.toogleGroupVisibility = function(drawDetails) { 1771 // get the group 1772 var group = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id); 1773 // toggle visible 1774 group.visible(!group.isVisible()); 1775 1776 // udpate current layer 1777 this.getCurrentDrawLayer().draw(); 1778 }; 1779 1780 /** 1781 * Delete all Draws from the stage. 1782 * @param {Object} cmdCallback The DeleteCommand callback. 1783 * @param {Object} exeCallback The callback to call once the DeleteCommand has been executed. 1784 */ 1785 this.deleteDraws = function(cmdCallback, exeCallback) { 1786 var delcmd, layer, groups; 1787 for (var k = 0, lenk = drawLayers.length; k < lenk; ++k) { 1788 for (var f = 0, lenf = drawLayers[k].length; f < lenf; ++f) { 1789 layer = drawLayers[k][f]; 1790 groups = layer.getChildren();
1791 while (groups.length) { 1792 var shape = groups[0].getChildren(isNodeNameShape)[0]; 1793 delcmd = new dwv.tool.DeleteGroupCommand(groups[0], 1794 dwv.tool.GetShapeDisplayName(shape), layer); 1795 delcmd.onExecute = cmdCallback; 1796 delcmd.onUndo = cmdCallback; 1797 delcmd.execute(); 1798 exeCallback(delcmd); 1799 } 1800 } 1801 } 1802 }; 1803 1804 /** 1805 * Get a draw group. 1806 * @param {Number} slice The slice position. 1807 * @param {Number} frame The frame position. 1808 * @param {Number} id The group id. 1809 */ 1810 function getDrawGroup(slice, frame, id) { 1811 var layer = drawLayers[slice][frame]; 1812 //var collec = layer.getChildren()[drawDetails.id]; 1813 var collec = layer.getChildren(function(node) { 1814 return node.id() === id; 1815 }); 1816 1817 var res = null; 1818 if (collec.length !== 0) { 1819 res = collec[0]; 1820 } else { 1821 console.warn("Could not find draw group for slice='" + 1822 slice + "', frame='" + frame + "', id='" + id + "'."); 1823 } 1824 return res; 1825 } 1826 1827 /** 1828 * Is an input node's name 'shape'. 1829 * @param {Object} node A Konva node. 1830 */ 1831 function isNodeNameShape(node) { 1832 return node.name() === "shape"; 1833 } 1834 1835 /** 1836 * Is a node an extra shape associated with a main one. 1837 * @param {Object} node A Konva node. 1838 */ 1839 function isNodeNameShapeExtra(node) { 1840 return node.name().startsWith("shape-"); 1841 } 1842 1843 /** 1844 * Is an input node's name 'label'. 1845 * @param {Object} node A Konva node. 1846 */ 1847 function isNodeNameLabel(node) { 1848 return node.name() === "label"; 1849 } 1850 1851 }; // class dwv.DrawController 1852 1853 // namespaces 1854 var dwv = dwv || {}; 1855 1856 /** 1857 * Info controller. 1858 * @constructor 1859 */ 1860 dwv.InfoController = function(containerDivId) { 1861 1862 // Info layer plot gui 1863 var plotInfo = null; 1864 // Info layer colour map gui 1865 var miniColourMap = null; 1866 // Info layer overlay 1867 var overlayInfos = []; 1868 // flag to know if the info layer is listening on the image. 1869 var isInfoLayerListening = false; 1870 1871 /** 1872 * Create the different info elements. 1873 * TODO Get rid of the app input arg... 1874 */ 1875 this.create = function(app) { 1876 var infocm = getElement("infocm"); 1877 if (infocm) { 1878 miniColourMap = new dwv.gui.info.MiniColourMap(infocm, app); 1879 miniColourMap.create(); 1880 } 1881 1882 // create overlay info at each corner 1883 var pos_list = [ 1884 "tl", "tc", "tr", 1885 "cl", "cr", 1886 "bl", "bc", "br" 1887 ]; 1888 1889 var num = 0; 1890 for (var n = 0; n < pos_list.length; n++) { 1891 var pos = pos_list[n]; 1892 var info = getElement("info" + pos); 1893 if (info) { 1894 overlayInfos[num] = new dwv.gui.info.Overlay(info, pos, app); 1895 overlayInfos[num].create(); 1896 num++; 1897 } 1898 } 1899 1900 var plot = getElement("plot"); 1901 if (plot) { 1902 plotInfo = new dwv.gui.info.Plot(plot, app); 1903 plotInfo.create(); 1904 } 1905 }; 1906 1907 /** 1908 * Toggle info listeners to the app and the view. 1909 * @param {Object} app The app to listen or not to. 1910 * @param {Object} view The view to listen or not to. 1911 */ 1912 this.toggleListeners = function(app, view) { 1913 if (isInfoLayerListening) { 1914 removeListeners(app, view); 1915 } else { 1916 addListeners(app, view); 1917 } 1918 }; 1919 1920 /** 1921 * Get a HTML element associated to the application. 1922 * @param name The name or id to find. 1923 * @return The found element or null. 1924 */ 1925 function getElement(name) { 1926 return dwv.gui.getElement(containerDivId, name); 1927 } 1928 1929 /** 1930 * Add info listeners to the view. 1931 * @param {Object} app The app to listen to. 1932 * @param {Object} view The view to listen to. 1933 */ 1934 function addListeners(app, view) { 1935 if (plotInfo) { 1936 view.addEventListener("wl-width-change", plotInfo.update);
1937 view.addEventListener("wl-center-change", plotInfo.update); 1938 } 1939 if (miniColourMap) { 1940 view.addEventListener("wl-width-change", miniColourMap.update); 1941 view.addEventListener("wl-center-change", miniColourMap.update); 1942 view.addEventListener("colour-change", miniColourMap.update); 1943 } 1944 if (overlayInfos.length > 0) { 1945 for (var n = 0; n < overlayInfos.length; n++) { 1946 app.addEventListener("zoom-change", overlayInfos[n].update); 1947 view.addEventListener("wl-width-change", overlayInfos[n].update); 1948 view.addEventListener("wl-center-change", overlayInfos[n].update); 1949 view.addEventListener("position-change", overlayInfos[n].update); 1950 view.addEventListener("frame-change", overlayInfos[n].update); 1951 } 1952 } 1953 // udpate listening flag 1954 isInfoLayerListening = true; 1955 } 1956 1957 /** 1958 * Remove info listeners to the view. 1959 * @param {Object} app The app to stop listening to. 1960 * @param {Object} view The view to stop listening to. 1961 */ 1962 function removeListeners(app, view) { 1963 if (plotInfo) { 1964 view.removeEventListener("wl-width-change", plotInfo.update); 1965 view.removeEventListener("wl-center-change", plotInfo.update); 1966 } 1967 if (miniColourMap) { 1968 view.removeEventListener("wl-width-change", miniColourMap.update); 1969 view.removeEventListener("wl-center-change", miniColourMap.update); 1970 view.removeEventListener("colour-change", miniColourMap.update); 1971 } 1972 if (overlayInfos.length > 0) { 1973 for (var n = 0; n < overlayInfos.length; n++) { 1974 app.removeEventListener("zoom-change", overlayInfos[n].update); 1975 view.removeEventListener("wl-width-change", overlayInfos[n].update); 1976 view.removeEventListener("wl-center-change", overlayInfos[n].update); 1977 view.removeEventListener("position-change", overlayInfos[n].update); 1978 view.removeEventListener("frame-change", overlayInfos[n].update); 1979 } 1980 } 1981 // udpate listening flag 1982 isInfoLayerListening = false; 1983 } 1984 1985 }; // class dwv.InfoController 1986 1987 // namespaces 1988 var dwv = dwv || {}; 1989 1990 /** 1991 * State class. 1992 * Saves: data url/path, display info. 1993 * @constructor 1994 * @param {Object} app The associated application. 1995 */ 1996 dwv.State = function(app) { 1997 /** 1998 * Save the application state as JSON. 1999 */ 2000 this.toJSON = function() { 2001 // store each slice drawings group 2002 var drawings = app.getDraws(); 2003 var drawingsDetails = app.getDrawStoreDetails(); 2004 // return a JSON string 2005 return JSON.stringify({ 2006 "version": "0.2", 2007 "window-center": app.getViewController().getWindowLevel().center, 2008 "window-width": app.getViewController().getWindowLevel().width, 2009 "position": app.getViewController().getCurrentPosition(), 2010 "scale": app.getScale(), 2011 "scaleCenter": app.getScaleCenter(), 2012 "translation": app.getTranslation(), 2013 "drawings": drawings, 2014 // new in v0.2 2015 "drawingsDetails": drawingsDetails 2016 }); 2017 }; 2018 /** 2019 * Load an application state from JSON. 2020 * @param {String} json The JSON representation of the state. 2021 */ 2022 this.fromJSON = function(json) { 2023 var data = JSON.parse(json); 2024 if (data.version === "0.1") { 2025 readV01(data); 2026 } else if (data.version === "0.2") { 2027 readV02(data); 2028 } else { 2029 throw new Error("Unknown state file format version: '" + data.version + "'."); 2030 } 2031 }; 2032 /** 2033 * Read an application state from an Object in v0.1 format. 2034 * @param {Object} data The Object representation of the state. 2035 */ 2036 function readV01(data) { 2037 // display 2038 app.getViewController().setWindowLevel(data["window-center"], data["window-width"]); 2039 app.getViewController().setCurrentPosition(data.position); 2040 app.zoom(data.scale, data.scaleCenter.x, data.scaleCenter.y); 2041 app.translate(data.translation.x, data.translation.y); 2042 // drawings 2043 app.setDrawings(data.drawings, null); 2044 } 2045 /** 2046 * Read an application state from an Object in v0.2 format. 2047 * @param {Object} data The Object representation of the state. 2048 */ 2049 function readV02(data) { 2050 // display 2051 app.getViewController().setWindowLevel(data["window-center"], data["window-width"]); 2052 app.getViewController().setCurrentPosition(data.position); 2053 app.zoom(data.scale, data.scaleCenter.x, data.scaleCenter.y); 2054 app.translate(data.translation.x, data.translation.y); 2055 // drawings 2056 app.setDrawings(data.drawings, data.drawingsDetails); 2057 } 2058 }; // State class 2059 2060 // namespaces 2061 var dwv = dwv || {}; 2062 2063 /** 2064 * Toolbox controller. 2065 * @constructor 2066 */ 2067 dwv.ToolboxController = function() { 2068 // internal toolbox 2069 var toolbox = null; 2070 // point converter function 2071 var displayToIndexConverter = null; 2072 2073 /** 2074 * Create the internal toolbox. 2075 * @param {Array} toolList The list of tools instances. 2076 * @param {Object} app The associated app. 2077 */ 2078 this.create = function(toolList, app) { 2079 toolbox = new dwv.tool.Toolbox(toolList, app); 2080 }; 2081 2082 /** 2083 * Setup the internal toolbox. 2084 */ 2085 this.setup = function() { 2086 toolbox.setup(); 2087 }; 2088 2089 /** 2090 * Reset the internal toolbox. 2091 */ 2092 this.reset = function() { 2093 toolbox.reset(); 2094 }; 2095 2096 /** 2097 * Initialise and display the internal toolbox. 2098 */ 2099 this.initAndDisplay = function(layer) { 2100 // initialise 2101 toolbox.init(); 2102 // display 2103 toolbox.display(true); 2104 // TODO Would prefer to have this done in the addLayerListeners
2105 displayToIndexConverter = layer.displayToIndex; 2106 // add layer listeners 2107 this.addCanvasListeners(layer.getCanvas()); 2108 // keydown listener 2109 window.addEventListener("keydown", onMouch, true); 2110 }; 2111 2112 /** 2113 * Get the tool list. 2114 */ 2115 this.getToolList = function() { 2116 return toolbox.getToolList(); 2117 }; 2118 2119 /** 2120 * Get the selected tool event handler. 2121 * @param {String} eventType The event type, for example mousedown, touchstart... 2122 */ 2123 this.getSelectedToolEventHandler = function(eventType) { 2124 return toolbox.getSelectedTool()[eventType]; 2125 }; 2126 2127 /** 2128 * Set the selected tool. 2129 * @param {String} name The name of the tool. 2130 */ 2131 this.setSelectedTool = function(name) { 2132 toolbox.setSelectedTool(name); 2133 }; 2134 2135 /** 2136 * Set the selected shape. 2137 * @param {String} name The name of the shape. 2138 */ 2139 this.setSelectedShape = function(name) { 2140 toolbox.getSelectedTool().setShapeName(name); 2141 }; 2142 2143 /** 2144 * Set the selected filter. 2145 * @param {String} name The name of the filter. 2146 */ 2147 this.setSelectedFilter = function(name) { 2148 toolbox.getSelectedTool().setSelectedFilter(name); 2149 }; 2150 2151 /** 2152 * Run the selected filter. 2153 */ 2154 this.runSelectedFilter = function() { 2155 toolbox.getSelectedTool().getSelectedFilter().run(); 2156 }; 2157 2158 /** 2159 * Set the tool line colour. 2160 * @param {String} colour The colour. 2161 */ 2162 this.setLineColour = function(colour) { 2163 toolbox.getSelectedTool().setLineColour(colour); 2164 }; 2165 2166 /** 2167 * Set the tool range. 2168 * @param {Object} range The new range of the data. 2169 */ 2170 this.setRange = function(range) { 2171 // seems like jquery is checking if the method exists before it 2172 // is used... 2173 if (toolbox && toolbox.getSelectedTool() && 2174 toolbox.getSelectedTool().getSelectedFilter()) { 2175 toolbox.getSelectedTool().getSelectedFilter().run(range); 2176 } 2177 }; 2178 2179 /** 2180 * Add canvas mouse and touch listeners. 2181 * @param {Object} canvas The canvas to listen to. 2182 */ 2183 this.addCanvasListeners = function(canvas) { 2184 // allow pointer events 2185 canvas.setAttribute("style", "pointer-events: auto;"); 2186 // mouse listeners 2187 canvas.addEventListener("mousedown", onMouch); 2188 canvas.addEventListener("mousemove", onMouch); 2189 canvas.addEventListener("mouseup", onMouch); 2190 canvas.addEventListener("mouseout", onMouch); 2191 canvas.addEventListener("mousewheel", onMouch); 2192 canvas.addEventListener("DOMMouseScroll", onMouch); 2193 canvas.addEventListener("dblclick", onMouch); 2194 // touch listeners 2195 canvas.addEventListener("touchstart", onMouch); 2196 canvas.addEventListener("touchmove", onMouch); 2197 canvas.addEventListener("touchend", onMouch); 2198 }; 2199 2200 /** 2201 * Remove canvas mouse and touch listeners. 2202 * @param {Object} canvas The canvas to stop listening to. 2203 */ 2204 this.removeCanvasListeners = function(canvas) { 2205 // disable pointer events 2206 canvas.setAttribute("style", "pointer-events: none;"); 2207 // mouse listeners 2208 canvas.removeEventListener("mousedown", onMouch); 2209 canvas.removeEventListener("mousemove", onMouch); 2210 canvas.removeEventListener("mouseup", onMouch); 2211 canvas.removeEventListener("mouseout", onMouch); 2212 canvas.removeEventListener("mousewheel", onMouch); 2213 canvas.removeEventListener("DOMMouseScroll", onMouch); 2214 canvas.removeEventListener("dblclick", onMouch); 2215 // touch listeners 2216 canvas.removeEventListener("touchstart", onMouch); 2217 canvas.removeEventListener("touchmove", onMouch); 2218 canvas.removeEventListener("touchend", onMouch); 2219 }; 2220 2221 /** 2222 * Mou(se) and (T)ouch event handler. This function just determines the mouse/touch 2223 * position relative to the canvas element. It then passes it to the current tool. 2224 * @private 2225 * @param {Object} event The event to handle. 2226 */ 2227 function onMouch(event) { 2228 // flag not to get confused between touch and mouse 2229 var handled = false; 2230 // Store the event position relative to the image canvas 2231 // in an extra member of the event: 2232 // event._x and event._y. 2233 var offsets = null; 2234 var position = null; 2235 if (event.type === "touchstart" || 2236 event.type === "touchmove") { 2237 // event offset(s) 2238 offsets = dwv.html.getEventOffset(event); 2239 // should have at least one offset 2240 event._xs = offsets[0].x; 2241 event._ys = offsets[0].y;
2242 position = displayToIndexConverter(offsets[0]); 2243 event._x = parseInt(position.x, 10); 2244 event._y = parseInt(position.y, 10); 2245 // possible second 2246 if (offsets.length === 2) { 2247 event._x1s = offsets[1].x; 2248 event._y1s = offsets[1].y; 2249 position = displayToIndexConverter(offsets[1]); 2250 event._x1 = parseInt(position.x, 10); 2251 event._y1 = parseInt(position.y, 10); 2252 } 2253 // set handle event flag 2254 handled = true; 2255 } else if (event.type === "mousemove" || 2256 event.type === "mousedown" || 2257 event.type === "mouseup" || 2258 event.type === "mouseout" || 2259 event.type === "mousewheel" || 2260 event.type === "dblclick" || 2261 event.type === "DOMMouseScroll") { 2262 offsets = dwv.html.getEventOffset(event); 2263 event._xs = offsets[0].x; 2264 event._ys = offsets[0].y; 2265 position = displayToIndexConverter(offsets[0]); 2266 event._x = parseInt(position.x, 10); 2267 event._y = parseInt(position.y, 10); 2268 // set handle event flag 2269 handled = true; 2270 } else if (event.type === "keydown" || 2271 event.type === "touchend") { 2272 handled = true; 2273 } 2274 2275 // Call the event handler of the curently selected tool. 2276 if (handled) { 2277 if (event.type !== "keydown") { 2278 event.preventDefault(); 2279 } 2280 var func = toolbox.getSelectedTool()[event.type]; 2281 if (func) { 2282 func(event); 2283 } 2284 } 2285 } 2286 2287 }; // class dwv.ToolboxController 2288 2289 // namespaces 2290 var dwv = dwv || {}; 2291 2292 /** 2293 * View controller. 2294 * @constructor 2295 */ 2296 dwv.ViewController = function(view) { 2297 // closure to self 2298 var self = this; 2299 // Slice/frame player ID (created by setInterval) 2300 var playerID = null; 2301 2302 /** 2303 * Get the window/level presets names. 2304 * @return {Array} The presets names. 2305 */ 2306 this.getWindowLevelPresetsNames = function() { 2307 return view.getWindowPresetsNames(); 2308 }; 2309 2310 /** 2311 * Add window/level presets to the view. 2312 * @return {Object} The list of presets. 2313 */ 2314 this.addWindowLevelPresets = function(presets) { 2315 return view.addWindowPresets(presets); 2316 }; 2317 2318 /** 2319 * Set the window level to the preset with the input name. 2320 * @param {String} name The name of the preset to activate. 2321 */ 2322 this.setWindowLevelPreset = function(name) { 2323 view.setWindowLevelPreset(name); 2324 }; 2325 2326 /** 2327 * Set the window level to the preset with the input id. 2328 * @param {Number} id The id of the preset to activate. 2329 */ 2330 this.setWindowLevelPresetById = function(id) { 2331 view.setWindowLevelPresetById(id); 2332 }; 2333 2334 /** 2335 * Check if the controller is playing. 2336 * @return {Boolean} True is the controler is playing slices/frames. 2337 */ 2338 this.isPlaying = function() { return (playerID !== null); }; 2339 2340 /** 2341 * Get the current position. 2342 * @return {Object} The position. 2343 */ 2344 this.getCurrentPosition = function() { 2345 return view.getCurrentPosition(); 2346 }; 2347 2348 /** 2349 * Set the current position. 2350 * @param {Object} pos The position. 2351 * @return {Boolean} False if not in bounds. 2352 */ 2353 this.setCurrentPosition = function(pos) { 2354 return view.setCurrentPosition(pos); 2355 }; 2356 2357 /** 2358 * Set the current 2D (i,j) position. 2359 * @param {Number} i The column index. 2360 * @param {Number} j The row index. 2361 * @return {Boolean} False if not in bounds. 2362 */ 2363 this.setCurrentPosition2D = function(i, j) { 2364 return view.setCurrentPosition({ 2365 "i": i, 2366 "j": j, 2367 "k": view.getCurrentPosition().k 2368 }); 2369 }; 2370 2371 /** 2372 * Set the current slice position. 2373 * @param {Number} k The slice index. 2374 * @return {Boolean} False if not in bounds. 2375 */ 2376 this.setCurrentSlice = function(k) { 2377 return view.setCurrentPosition({ 2378 "i": view.getCurrentPosition().i, 2379 "j": view.getCurrentPosition().j, 2380 "k": k 2381 }); 2382 }; 2383 2384 /** 2385 * Increment the current slice number. 2386 * @return {Boolean} False if not in bounds. 2387 */ 2388 this.incrementSliceNb = function() { 2389 return self.setCurrentSlice(view.getCurrentPosition().k + 1); 2390 }; 2391 2392 /** 2393 * Decrement the current slice number. 2394 * @return {Boolean} False if not in bounds. 2395 */ 2396 this.decrementSliceNb = function() { 2397 return self.setCurrentSlice(view.getCurrentPosition().k - 1); 2398 }; 2399 2400 /** 2401 * Get the current frame. 2402 * @return {Number} The frame number. 2403 */ 2404 this.getCurrentFrame = function() { 2405 return view.getCurrentFrame(); 2406 }; 2407 2408 /** 2409 * Set the current frame. 2410 * @param {Number} number The frame number. 2411 * @return {Boolean} False if not in bounds. 2412 */ 2413 this.setCurrentFrame = function(number) { 2414 return view.setCurrentFrame(number); 2415 }; 2416 2417 /** 2418 * Increment the current frame. 2419 * @return {Boolean} False if not in bounds. 2420 */ 2421 this.incrementFrameNb = function() { 2422 return view.setCurrentFrame(view.getCurrentFrame() + 1); 2423 }; 2424 2425 /** 2426 * Decrement the current frame. 2427 * @return {Boolean} False if not in bounds. 2428 */ 2429 this.decrementFrameNb = function() { 2430 return view.setCurrentFrame(view.getCurrentFrame() - 1); 2431 }; 2432 2433 /** 2434 * Go to first slice . 2435 * @return {Boolean} False if not in bounds. 2436 * @deprecated Use the setCurrentSlice function. 2437 */ 2438 this.goFirstSlice = function() { 2439 return view.setCurrentPosition({ 2440 "i": view.getCurrentPosition().i, 2441 "j": view.getCurrentPosition().j, 2442 "k": 0 2443 }); 2444 }; 2445 2446 /** 2447 * 2448 */ 2449 this.play = function() { 2450 if (playerID === null) { 2451 var nSlices = view.getImage().getGeometry().getSize().getNumberOfSlices(); 2452 var nFrames = view.getImage().getNumberOfFrames(); 2453 2454 playerID = setInterval(function() { 2455 if (nSlices !== 1) { 2456 if (!self.incrementSliceNb()) { 2457 self.setCurrentSlice(0); 2458 } 2459 } else if (nFrames !== 1) { 2460 if (!self.incrementFrameNb()) { 2461 self.setCurrentFrame(0); 2462 } 2463 } 2464 2465 }, 300); 2466 } else { 2467 this.stop(); 2468 } 2469 }; 2470 2471 /** 2472 * 2473 */ 2474 this.stop = function() { 2475 if (playerID !== null) { 2476 clearInterval(playerID); 2477 playerID = null; 2478 } 2479 }; 2480 2481 /** 2482 * Get the window/level. 2483 * @return {Object} The window center and width. 2484 */ 2485 this.getWindowLevel = function() { 2486 return { 2487 "width": view.getCurrentWindowLut().getWindowLevel().getWidth(), 2488 "center": view.getCurrentWindowLut().getWindowLevel().getCenter() 2489 }; 2490 }; 2491 2492 /** 2493 * Set the window/level. 2494 * @param {Number} wc The window center.
2495 * @param {Number} ww The window width. 2496 */ 2497 this.setWindowLevel = function(wc, ww) { 2498 view.setWindowLevel(wc, ww); 2499 }; 2500 2501 /** 2502 * Get the colour map. 2503 * @return {Object} The colour map. 2504 */ 2505 this.getColourMap = function() { 2506 return view.getColourMap(); 2507 }; 2508 2509 /** 2510 * Set the colour map. 2511 * @param {Object} colourMap The colour map. 2512 */ 2513 this.setColourMap = function(colourMap) { 2514 view.setColourMap(colourMap); 2515 }; 2516 2517 /** 2518 * Set the colour map from a name. 2519 * @param {String} name The name of the colour map to set. 2520 */ 2521 this.setColourMapFromName = function(name) { 2522 // check if we have it 2523 if (!dwv.tool.colourMaps[name]) { 2524 throw new Error("Unknown colour map: '" + name + "'"); 2525 } 2526 // enable it 2527 this.setColourMap(dwv.tool.colourMaps[name]); 2528 }; 2529 2530 }; // class dwv.ViewController 2531 2532 // namespaces 2533 var dwv = dwv || {}; 2534 /** @namespace */ 2535 dwv.dicom = dwv.dicom || {}; 2536 2537 /** 2538 * Get the version of the library. 2539 * @return {String} The version of the library. 2540 */ 2541 dwv.getVersion = function() { return "0.22.1"; }; 2542 2543 /** 2544 * Clean string: trim and remove ending. 2545 * @param {String} inputStr The string to clean. 2546 * @return {String} The cleaned string. 2547 */ 2548 dwv.dicom.cleanString = function(inputStr) { 2549 var res = inputStr; 2550 if (inputStr) { 2551 // trim spaces 2552 res = inputStr.trim(); 2553 // get rid of ending zero-width space (u200B) 2554 if (res[res.length - 1] === String.fromCharCode("u200B")) { 2555 res = res.substring(0, res.length - 1); 2556 } 2557 } 2558 return res; 2559 }; 2560 2561 /** 2562 * Is the Native endianness Little Endian. 2563 * @type Boolean 2564 */ 2565 dwv.dicom.isNativeLittleEndian = function() { 2566 return new Int8Array(new Int16Array([1]).buffer)[0] > 0; 2567 }; 2568 2569 /** 2570 * Get the utfLabel (used by the TextDecoder) from a character set term 2571 * References: 2572 * - DICOM [Value Encoding]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_6.html} 2573 * - DICOM [Specific Character Set]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2} 2574 * - [TextDecoder#Parameters]{@link https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/TextDecoder#Parameters} 2575 */ 2576 dwv.dicom.getUtfLabel = function(charSetTerm) { 2577 var label = "utf-8"; 2578 if (charSetTerm === "ISO_IR 100") { 2579 label = "iso-8859-1"; 2580 } else if (charSetTerm === "ISO_IR 101") { 2581 label = "iso-8859-2"; 2582 } else if (charSetTerm === "ISO_IR 109") { 2583 label = "iso-8859-3"; 2584 } else if (charSetTerm === "ISO_IR 110") { 2585 label = "iso-8859-4"; 2586 } else if (charSetTerm === "ISO_IR 144") { 2587 label = "iso-8859-5"; 2588 } else if (charSetTerm === "ISO_IR 127") { 2589 label = "iso-8859-6"; 2590 } else if (charSetTerm === "ISO_IR 126") { 2591 label = "iso-8859-7"; 2592 } else if (charSetTerm === "ISO_IR 138") { 2593 label = "iso-8859-8"; 2594 } else if (charSetTerm === "ISO_IR 148") { 2595 label = "iso-8859-9"; 2596 } else if (charSetTerm === "ISO_IR 13") { 2597 label = "shift-jis"; 2598 } else if (charSetTerm === "ISO_IR 166") { 2599 label = "iso-8859-11"; 2600 } else if (charSetTerm === "ISO 2022 IR 87") { 2601 label = "iso-2022-jp"; 2602 } else if (charSetTerm === "ISO 2022 IR 149") { 2603 // not supported by TextDecoder when it says it should... 2604 //label = "iso-2022-kr"; 2605 } else if (charSetTerm === "ISO 2022 IR 58") { 2606 // not supported by TextDecoder... 2607 //label = "iso-2022-cn"; 2608 } else if (charSetTerm === "ISO_IR 192") { 2609 label = "utf-8"; 2610 } else if (charSetTerm === "GB18030") { 2611 label = "gb18030"; 2612 } else if (charSetTerm === "GB2312") { 2613 label = "gb2312"; 2614 } else if (charSetTerm === "GBK") { 2615 label = "chinese"; 2616 } 2617 return label; 2618 }; 2619 2620 /** 2621 * Data reader. 2622 * @constructor 2623 * @param {Array} buffer The input array buffer. 2624 * @param {Boolean} isLittleEndian Flag to tell if the data is little or big endian. 2625 */ 2626 dwv.dicom.DataReader = function(buffer, isLittleEndian) { 2627 // Set endian flag if not defined. 2628 if (typeof isLittleEndian === 'undefined') { 2629 isLittleEndian = true; 2630 } 2631 2632 // Default text decoder 2633 var defaultTextDecoder = {}; 2634 defaultTextDecoder.decode = function(buffer) { 2635 var result = ""; 2636 for (var i = 0, leni = buffer.length; i < leni; ++i) { 2637 result += String.fromCharCode(buffer[i]); 2638 } 2639 return result; 2640 }; 2641 // Text decoder 2642 var textDecoder = defaultTextDecoder; 2643 if (typeof window.TextDecoder !== "undefined") { 2644 textDecoder = new TextDecoder("iso-8859-1"); 2645 } 2646 2647 /** 2648 * Set the utfLabel used to construct the TextDecoder. 2649 * @param {String} label The encoding label. 2650 */ 2651 this.setUtfLabel = function(label) { 2652 if (typeof window.TextDecoder !== "undefined") { 2653 textDecoder = new TextDecoder(label); 2654 } 2655 }; 2656 2657 /** 2658 * Is the Native endianness Little Endian. 2659 * @private 2660 * @type Boolean 2661 */ 2662 var isNativeLittleEndian = dwv.dicom.isNativeLittleEndian(); 2663 2664 /** 2665 * Flag to know if the TypedArray data needs flipping. 2666 * @private 2667 * @type Boolean 2668 */ 2669 var needFlip = (isLittleEndian !== isNativeLittleEndian); 2670 2671 /** 2672 * The main data view. 2673 * @private 2674 * @type DataView 2675 */ 2676 var view = new DataView(buffer); 2677 2678 /** 2679 * Flip an array's endianness. 2680 * Inspired from [DataStream.js]{@link https://github.com/kig/DataStream.js}. 2681 * @param {Object} array The array to flip (modified). 2682 */ 2683 this.flipArrayEndianness = function(array) { 2684 var blen = array.byteLength; 2685 var u8 = new Uint8Array(array.buffer, array.byteOffset, blen); 2686 var bpel = array.BYTES_PER_ELEMENT; 2687 var tmp; 2688 for (var i = 0; i < blen; i += bpel) { 2689 for (var j = i + bpel - 1, k = i; j > k; j--, k++) { 2690 tmp = u8[k]; 2691 u8[k] = u8[j]; 2692 u8[j] = tmp; 2693 } 2694 } 2695 }; 2696 2697 /** 2698 * Read Uint16 (2 bytes) data. 2699 * @param {Number} byteOffset The offset to start reading from. 2700 * @return {Number} The read data. 2701 */ 2702 this.readUint16 = function(byteOffset) { 2703 return view.getUint16(byteOffset, isLittleEndian); 2704 }; 2705 /** 2706 * Read Uint32 (4 bytes) data. 2707 * @param {Number} byteOffset The offset to start reading from. 2708 * @return {Number} The read data. 2709 */ 2710 this.readUint32 = function(byteOffset) { 2711 return view.getUint32(byteOffset, isLittleEndian); 2712 }; 2713 /** 2714 * Read Int32 (4 bytes) data. 2715 * @param {Number} byteOffset The offset to start reading from. 2716 * @return {Number} The read data. 2717 */ 2718 this.readInt32 = function(byteOffset) { 2719 return view.getInt32(byteOffset, isLittleEndian); 2720 }; 2721 /** 2722 * Read Uint8 array. 2723 * @param {Number} byteOffset The offset to start reading from. 2724 * @param {Number} size The size of the array. 2725 * @return {Array} The read data. 2726 */ 2727 this.readUint8Array = function(byteOffset, size) { 2728 return new Uint8Array(buffer, byteOffset, size); 2729 }; 2730 /** 2731 * Read Int8 array. 2732 * @param {Number} byteOffset The offset to start reading from. 2733 * @param {Number} size The size of the array. 2734 * @return {Array} The read data. 2735 */ 2736 this.readInt8Array = function(byteOffset, size) { 2737 return new Int8Array(buffer, byteOffset, size); 2738 }; 2739 /** 2740 * Read Uint16 array. 2741 * @param {Number} byteOffset The offset to start reading from. 2742 * @param {Number} size The size of the array. 2743 * @return {Array} The read data. 2744 */ 2745 this.readUint16Array = function(byteOffset, size) { 2746 var arraySize = size / Uint16Array.BYTES_PER_ELEMENT; 2747 var data = null; 2748 // byteOffset should be a multiple of Uint16Array.BYTES_PER_ELEMENT (=2) 2749 if ((byteOffset % Uint16Array.BYTES_PER_ELEMENT) === 0) { 2750 data = new Uint16Array(buffer, byteOffset, arraySize); 2751 if (needFlip) { 2752 this.flipArrayEndianness(data); 2753 } 2754 } else { 2755 data = new Uint16Array(arraySize); 2756 for (var i = 0; i < arraySize;
2756 ++i) { 2757 data[i] = view.getInt16((byteOffset + 2758 Uint16Array.BYTES_PER_ELEMENT * i), 2759 isLittleEndian); 2760 } 2761 } 2762 return data; 2763 }; 2764 /** 2765 * Read Int16 array. 2766 * @param {Number} byteOffset The offset to start reading from. 2767 * @param {Number} size The size of the array. 2768 * @return {Array} The read data. 2769 */ 2770 this.readInt16Array = function(byteOffset, size) { 2771 var arraySize = size / Int16Array.BYTES_PER_ELEMENT; 2772 var data = null; 2773 // byteOffset should be a multiple of Int16Array.BYTES_PER_ELEMENT (=2) 2774 if ((byteOffset % Int16Array.BYTES_PER_ELEMENT) === 0) { 2775 data = new Int16Array(buffer, byteOffset, arraySize); 2776 if (needFlip) { 2777 this.flipArrayEndianness(data); 2778 } 2779 } else { 2780 data = new Int16Array(arraySize); 2781 for (var i = 0; i < arraySize; ++i) { 2782 data[i] = view.getInt16((byteOffset + 2783 Int16Array.BYTES_PER_ELEMENT * i), 2784 isLittleEndian); 2785 } 2786 } 2787 return data; 2788 }; 2789 /** 2790 * Read Uint32 array. 2791 * @param {Number} byteOffset The offset to start reading from. 2792 * @param {Number} size The size of the array. 2793 * @return {Array} The read data. 2794 */ 2795 this.readUint32Array = function(byteOffset, size) { 2796 var arraySize = size / Uint32Array.BYTES_PER_ELEMENT; 2797 var data = null; 2798 // byteOffset should be a multiple of Uint32Array.BYTES_PER_ELEMENT (=4) 2799 if ((byteOffset % Uint32Array.BYTES_PER_ELEMENT) === 0) { 2800 data = new Uint32Array(buffer, byteOffset, arraySize); 2801 if (needFlip) { 2802 this.flipArrayEndianness(data); 2803 } 2804 } else { 2805 data = new Uint32Array(arraySize); 2806 for (var i = 0; i < arraySize; ++i) { 2807 data[i] = view.getUint32((byteOffset + 2808 Uint32Array.BYTES_PER_ELEMENT * i), 2809 isLittleEndian); 2810 } 2811 } 2812 return data; 2813 }; 2814 /** 2815 * Read Int32 array. 2816 * @param {Number} byteOffset The offset to start reading from. 2817 * @param {Number} size The size of the array. 2818 * @return {Array} The read data. 2819 */ 2820 this.readInt32Array = function(byteOffset, size) { 2821 var arraySize = size / Int32Array.BYTES_PER_ELEMENT; 2822 var data = null; 2823 // byteOffset should be a multiple of Int32Array.BYTES_PER_ELEMENT (=4) 2824 if ((byteOffset % Int32Array.BYTES_PER_ELEMENT) === 0) { 2825 data = new Int32Array(buffer, byteOffset, arraySize); 2826 if (needFlip) { 2827 this.flipArrayEndianness(data); 2828 } 2829 } else { 2830 data = new Int32Array(arraySize); 2831 for (var i = 0; i < arraySize; ++i) { 2832 data[i] = view.getInt32((byteOffset + 2833 Int32Array.BYTES_PER_ELEMENT * i), 2834 isLittleEndian); 2835 } 2836 } 2837 return data; 2838 }; 2839 /** 2840 * Read Float32 array. 2841 * @param {Number} byteOffset The offset to start reading from. 2842 * @param {Number} size The size of the array. 2843 * @return {Array} The read data. 2844 */ 2845 this.readFloat32Array = function(byteOffset, size) { 2846 var arraySize = size / Float32Array.BYTES_PER_ELEMENT; 2847 var data = null; 2848 // byteOffset should be a multiple of Float32Array.BYTES_PER_ELEMENT (=4) 2849 if ((byteOffset % Float32Array.BYTES_PER_ELEMENT) === 0) { 2850 data = new Float32Array(buffer, byteOffset, arraySize); 2851 if (needFlip) { 2852 this.flipArrayEndianness(data); 2853 } 2854 } else { 2855 data = new Float32Array(arraySize); 2856 for (var i = 0; i < arraySize;
2856 ++i) { 2857 data[i] = view.getFloat32((byteOffset + 2858 Float32Array.BYTES_PER_ELEMENT * i), 2859 isLittleEndian); 2860 } 2861 } 2862 return data; 2863 }; 2864 /** 2865 * Read Float64 array. 2866 * @param {Number} byteOffset The offset to start reading from. 2867 * @param {Number} size The size of the array. 2868 * @return {Array} The read data. 2869 */ 2870 this.readFloat64Array = function(byteOffset, size) { 2871 var arraySize = size / Float64Array.BYTES_PER_ELEMENT; 2872 var data = null; 2873 // byteOffset should be a multiple of Float64Array.BYTES_PER_ELEMENT (=8) 2874 if ((byteOffset % Float64Array.BYTES_PER_ELEMENT) === 0) { 2875 data = new Float64Array(buffer, byteOffset, arraySize); 2876 if (needFlip) { 2877 this.flipArrayEndianness(data); 2878 } 2879 } else { 2880 data = new Float64Array(arraySize); 2881 for (var i = 0; i < arraySize; ++i) { 2882 data[i] = view.getFloat64((byteOffset + 2883 Float64Array.BYTES_PER_ELEMENT * i), 2884 isLittleEndian); 2885 } 2886 } 2887 return data; 2888 }; 2889 /** 2890 * Read data as an hexadecimal string. 2891 * @param {Number} byteOffset The offset to start reading from. 2892 * @return {Array} The read data. 2893 */ 2894 this.readHex = function(byteOffset) { 2895 // read and convert to hex string 2896 var str = this.readUint16(byteOffset).toString(16); 2897 // return padded 2898 return "0x0000".substr(0, 6 - str.length) + str.toUpperCase(); 2899 }; 2900 2901 /** 2902 * Read data as a string. 2903 * @param {Number} byteOffset The offset to start reading from. 2904 * @param {Number} nChars The number of characters to read. 2905 * @return {String} The read data. 2906 */ 2907 this.readString = function(byteOffset, nChars) { 2908 var data = this.readUint8Array(byteOffset, nChars); 2909 return defaultTextDecoder.decode(data); 2910 }; 2911 2912 /** 2913 * Read data as a 'special' string, decoding it if the TextDecoder is available. 2914 * @param {Number} byteOffset The offset to start reading from. 2915 * @param {Number} nChars The number of characters to read. 2916 * @return {String} The read data. 2917 */ 2918 this.readSpecialString = function(byteOffset, nChars) { 2919 var data = this.readUint8Array(byteOffset, nChars); 2920 return textDecoder.decode(data); 2921 }; 2922 2923 }; 2924 2925 /** 2926 * Get the group-element pair from a tag string name. 2927 * @param {String} tagName The tag string name. 2928 * @return {Object} group-element pair. 2929 */ 2930 dwv.dicom.getGroupElementFromName = function(tagName) { 2931 var group = null; 2932 var element = null; 2933 var dict = dwv.dicom.dictionary; 2934 var keys0 = Object.keys(dict); 2935 var keys1 = null; 2936 // label for nested loop break 2937 outLabel: 2938 // search through dictionary 2939 for (var k0 = 0, lenK0 = keys0.length; k0 < lenK0; ++k0) { 2940 group = keys0[k0]; 2941 keys1 = Object.keys(dict[group]); 2942 for (var k1 = 0, lenK1 = keys1.length; k1 < lenK1; ++k1) { 2943 element = keys1[k1]; 2944 if (dict[group][element][2] === tagName) { 2945 break outLabel; 2946 } 2947 } 2948 } 2949 return { 'group': group, 'element': element }; 2950 }; 2951 2952 /** 2953 * Immutable tag. 2954 * @constructor 2955 * @param {String} group The tag group. 2956 * @param {String} element The tag element. 2957 */ 2958 dwv.dicom.Tag = function(group, element) { 2959 /** 2960 * Get the tag group. 2961 * @return {String} The tag group. 2962 */ 2963 this.getGroup = function() { return group; }; 2964 /** 2965 * Get the tag element. 2966 * @return {String} The tag element. 2967 */ 2968 this.getElement = function() { return element; }; 2969 }; // Tag class 2970 2971 /** 2972 * Check for Tag equality. 2973 * @param {Object} rhs The other tag to compare to. 2974 * @return {Boolean} True if both tags are equal. 2975 */ 2976 dwv.dicom.Tag.prototype.equals = function(rhs) { 2977 return rhs !== null && 2978 this.getGroup() === rhs.getGroup() && 2979 this.getElement() === rhs.getElement(); 2980 }; 2981 2982 /** 2983 * Check for Tag equality. 2984 * @param {Object} rhs The other tag to compare to provided as a simple object. 2985 * @return {Boolean} True if both tags are equal. 2986 */ 2987 dwv.dicom.Tag.prototype.equals2 = function(rhs) { 2988 if (rhs === null || 2989 typeof rhs.group === "undefined" || 2990 typeof rhs.element === "undefined") { 2991 return false; 2992 } 2993 return this.equals(new dwv.dicom.Tag(rhs.group, rhs.element)); 2994 }; 2995 2996 // Get the FileMetaInformationGroupLength Tag. 2997 dwv.dicom.getFileMetaInformationGroupLengthTag = function() { 2998 return new dwv.dicom.Tag("0x0002", "0x0000"); 2999 }; 3000 // Get the Item Tag. 3001 dwv.dicom.getItemTag = function() { 3002 return new dwv.dicom.Tag("0xFFFE", "0xE000"); 3003 }; 3004 // Get the ItemDelimitationItem Tag. 3005 dwv.dicom.getItemDelimitationItemTag = function() { 3006 return new dwv.dicom.Tag("0xFFFE", "0xE00D"); 3007 }; 3008 // Get the SequenceDelimitationItem Tag. 3009 dwv.dicom.getSequenceDelimitationItemTag = function() { 3010 return new dwv.dicom.Tag("0xFFFE", "0xE0DD"); 3011 }; 3012 // Get the PixelData Tag. 3013 dwv.dicom.getPixelDataTag = function() { 3014 return new dwv.dicom.Tag("0x7FE0", "0x0010"); 3015 }; 3016 3017 /** 3018 * Get the group-element key used to store DICOM elements. 3019 * @param {Number} group The DICOM group. 3020 * @param {Number} element The DICOM element. 3021 * @return {String} The key. 3022 */ 3023 dwv.dicom.getGroupElementKey = function(group, element) { 3024 return 'x' + group.substr(2, 6) + element.substr(2, 6); 3025 }; 3026 3027 /** 3028 * Split a group-element key used to store DICOM elements. 3029 * @param {String} key The key in form "x00280102. 3030 * @return {Object} The DICOM group and element. 3031 */ 3032 dwv.dicom.splitGroupElementKey = function(key) {
3033 return { 'group': key.substr(1, 4), 'element': key.substr(5, 8) }; 3034 }; 3035 3036 /** 3037 * Get patient orientation label in the reverse direction. 3038 * @param {String} ori Patient Orientation value. 3039 * @return {String} Reverse Orientation Label. 3040 */ 3041 dwv.dicom.getReverseOrientation = function(ori) { 3042 if (!ori) { 3043 return null; 3044 } 3045 // reverse labels 3046 var rlabels = { 3047 "L": "R", 3048 "R": "L", 3049 "A": "P", 3050 "P": "A", 3051 "H": "F", 3052 "F": "H" 3053 }; 3054 3055 var rori = ""; 3056 for (var n = 0; n < ori.length; n++) { 3057 var o = ori.substr(n, 1); 3058 var r = rlabels[o]; 3059 if (r) { 3060 rori += r; 3061 } 3062 } 3063 // return 3064 return rori; 3065 }; 3066 3067 /** 3068 * Tell if a given syntax is an implicit one (element with no VR). 3069 * @param {String} syntax The transfer syntax to test. 3070 * @return {Boolean} True if an implicit syntax. 3071 */ 3072 dwv.dicom.isImplicitTransferSyntax = function(syntax) { 3073 return syntax === "1.2.840.10008.1.2"; 3074 }; 3075 3076 /** 3077 * Tell if a given syntax is a big endian syntax. 3078 * @param {String} syntax The transfer syntax to test. 3079 * @return {Boolean} True if a big endian syntax. 3080 */ 3081 dwv.dicom.isBigEndianTransferSyntax = function(syntax) { 3082 return syntax === "1.2.840.10008.1.2.2"; 3083 }; 3084 3085 /** 3086 * Tell if a given syntax is a JPEG baseline one. 3087 * @param {String} syntax The transfer syntax to test. 3088 * @return {Boolean} True if a jpeg baseline syntax. 3089 */ 3090 dwv.dicom.isJpegBaselineTransferSyntax = function(syntax) { 3091 return syntax === "1.2.840.10008.1.2.4.50" || 3092 syntax === "1.2.840.10008.1.2.4.51"; 3093 }; 3094 3095 /** 3096 * Tell if a given syntax is a retired JPEG one. 3097 * @param {String} syntax The transfer syntax to test. 3098 * @return {Boolean} True if a retired jpeg syntax. 3099 */ 3100 dwv.dicom.isJpegRetiredTransferSyntax = function(syntax) { 3101 return (syntax.match(/1.2.840.10008.1.2.4.5/) !== null && 3102 !dwv.dicom.isJpegBaselineTransferSyntax() && 3103 !dwv.dicom.isJpegLosslessTransferSyntax()) || 3104 syntax.match(/1.2.840.10008.1.2.4.6/) !== null; 3105 }; 3106 3107 /** 3108 * Tell if a given syntax is a JPEG Lossless one. 3109 * @param {String} syntax The transfer syntax to test. 3110 * @return {Boolean} True if a jpeg lossless syntax. 3111 */ 3112 dwv.dicom.isJpegLosslessTransferSyntax = function(syntax) { 3113 return syntax === "1.2.840.10008.1.2.4.57" || 3114 syntax === "1.2.840.10008.1.2.4.70"; 3115 }; 3116 3117 /** 3118 * Tell if a given syntax is a JPEG-LS one. 3119 * @param {String} syntax The transfer syntax to test. 3120 * @return {Boolean} True if a jpeg-ls syntax. 3121 */ 3122 dwv.dicom.isJpeglsTransferSyntax = function(syntax) { 3123 return syntax.match(/1.2.840.10008.1.2.4.8/) !== null; 3124 }; 3125 3126 /** 3127 * Tell if a given syntax is a JPEG 2000 one. 3128 * @param {String} syntax The transfer syntax to test. 3129 * @return {Boolean} True if a jpeg 2000 syntax. 3130 */ 3131 dwv.dicom.isJpeg2000TransferSyntax = function(syntax) { 3132 return syntax.match(/1.2.840.10008.1.2.4.9/) !== null; 3133 }; 3134 3135 /** 3136 * Tell if a given syntax needs decompression. 3137 * @param {String} syntax The transfer syntax to test. 3138 * @return {String} The name of the decompression algorithm. 3139 */ 3140 dwv.dicom.getSyntaxDecompressionName = function(syntax) { 3141 var algo = null; 3142 if (dwv.dicom.isJpeg2000TransferSyntax(syntax)) { 3143 algo = "jpeg2000"; 3144 } else if (dwv.dicom.isJpegBaselineTransferSyntax(syntax)) { 3145 algo = "jpeg-baseline"; 3146 } else if (dwv.dicom.isJpegLosslessTransferSyntax(syntax)) { 3147 algo = "jpeg-lossless"; 3148 } 3149 return algo; 3150 }; 3151 3152 /** 3153 * Tell if a given syntax is supported for reading. 3154 * @param {String} syntax The transfer syntax to test. 3155 * @return {Boolean} True if a supported syntax. 3156 */ 3157 dwv.dicom.isReadSupportedTransferSyntax = function(syntax) { 3158 3159 // Unsupported: 3160 // "1.2.840.10008.1.2.1.99": Deflated Explicit VR - Little Endian 3161 // "1.2.840.10008.1.2.4.100": MPEG2 Image Compression 3162 // dwv.dicom.isJpegRetiredTransferSyntax(syntax): non supported JPEG 3163 // dwv.dicom.isJpeglsTransferSyntax(syntax): JPEG-LS 3164 // "1.2.840.10008.1.2.5": RLE (lossless) 3165 3166 return (syntax === "1.2.840.10008.1.2" || // Implicit VR - Little Endian 3167 syntax === "1.2.840.10008.1.2.1" || // Explicit VR - Little Endian 3168 syntax === "1.2.840.10008.1.2.2" || // Explicit VR - Big Endian 3169 dwv.dicom.isJpegBaselineTransferSyntax(syntax) || // JPEG baseline 3170 dwv.dicom.isJpegLosslessTransferSyntax(syntax) || // JPEG Lossless 3171 dwv.dicom.isJpeg2000TransferSyntax(syntax)); // JPEG 2000 3172 }; 3173 3174 /** 3175 * Get the transfer syntax name. 3176 * Reference: [UID Values]{@link http://dicom.nema.org/dicom/2013/output/chtml/part06/chapter_A.html}. 3177 * @param {String} syntax The transfer syntax. 3178 * @return {String} The name of the transfer syntax. 3179 */ 3180 dwv.dicom.getTransferSyntaxName = function(syntax) { 3181 var name = "Unknown"; 3182 // Implicit VR - Little Endian 3183 if (syntax === "1.2.840.10008.1.2") { 3184 name = "Little Endian Implicit"; 3185 } 3186 // Explicit VR - Little Endian 3187 else if (syntax === "1.2.840.10008.1.2.1") { 3188 name = "Little Endian Explicit"; 3189 } 3190 // Deflated Explicit VR - Little Endian 3191 else if (syntax === "1.2.840.10008.1.2.1.99") { 3192 name = "Little Endian Deflated Explicit"; 3193 } 3194 // Explicit VR - Big Endian 3195 else if (syntax === "1.2.840.10008.1.2.2") { 3196 name = "Big Endian Explicit"; 3197 } 3198 // JPEG baseline 3199 else if (dwv.dicom.isJpegBaselineTransferSyntax(syntax)) { 3200 if (syntax === "1.2.840.10008.1.2.4.50") { 3201 name = "JPEG Baseline"; 3202 } else { // *.51 3203 name = "JPEG Extended, Process 2+4"; 3204 } 3205 } 3206 // JPEG Lossless 3207 else if (dwv.dicom.isJpegLosslessTransferSyntax(syntax)) { 3208 if (syntax === "1.2.840.10008.1.2.4.57") { 3209 name = "JPEG Lossless, Nonhierarchical (Processes 14)"; 3210 } else { // *.70 3211 name = "JPEG Lossless, Non-hierarchical, 1st Order Prediction"; 3212 } 3213 } 3214 // Retired JPEG 3215 else if (dwv.dicom.isJpegRetiredTransferSyntax(syntax)) { 3216 name = "Retired JPEG"; 3217 } 3218 // JPEG-LS 3219 else if (dwv.dicom.isJpeglsTransferSyntax(syntax)) { 3220 name = "JPEG-LS"; 3221 } 3222 // JPEG 2000 3223 else if (dwv.dicom.isJpeg2000TransferSyntax(syntax)) { 3224 if (syntax === "1.2.840.10008.1.2.4.91") { 3225 name = "JPEG 2000 (Lossless or Lossy)"; 3226 } else { // *.90 3227 name = "JPEG 2000 (Lossless only)"; 3228 } 3229 } 3230 // MPEG2 Image Compression 3231 else if (syntax === "1.2.840.10008.1.2.4.100") { 3232 name = "MPEG2"; 3233 } 3234 // RLE (lossless) 3235 else if (syntax === "1.2.840.10008.1.2.5") { 3236 name = "RLE"; 3237 } 3238 // return 3239 return name; 3240 }; 3241 3242 /** 3243 * Get the appropriate TypedArray in function of arguments. 3244 * @param {Number} bitsAllocated The number of bites used to store the data: [8, 16, 32]. 3245 * @param {Number} pixelRepresentation The pixel representation, 0:unsigned;1:signed. 3246 * @param {Size} size The size of the new array. 3247 * @return The good typed array. 3248 */ 3249 dwv.dicom.getTypedArray = function(bitsAllocated, pixelRepresentation, size) { 3250 var res = null; 3251 if (bitsAllocated === 8) { 3252 if (pixelRepresentation === 0) { 3253 res = new Uint8Array(size); 3254 } else { 3255 res = new Int8Array(size); 3256 } 3257 } else if (bitsAllocated === 16) { 3258 if (pixelRepresentation === 0) { 3259 res = new Uint16Array(size); 3260 } else { 3261 res = new Int16Array(size); 3262 } 3263 } else if (bitsAllocated === 32) { 3264 if (pixelRepresentation === 0) { 3265 res = new Uint32Array(size); 3266 } else { 3267 res = new Int32Array(size); 3268 } 3269 } 3270 return res; 3271 }; 3272 3273 /**
3274 * Does this Value Representation (VR) have a 32bit Value Length (VL). 3275 * Ref: [Data Element explicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_7.html#table_7.1-1}. 3276 * @param {String} vr The data Value Representation (VR). 3277 * @returns {Boolean} True if this VR has a 32-bit VL. 3278 */ 3279 dwv.dicom.is32bitVLVR = function(vr) { 3280 // added locally used 'ox' 3281 return (vr === "OB" || vr === "OW" || vr === "OF" || vr === "ox" || vr === "UT" || 3282 vr === "SQ" || vr === "UN"); 3283 }; 3284 3285 /** 3286 * Does this tag have a VR. 3287 * Basically the Item, ItemDelimitationItem and SequenceDelimitationItem tags. 3288 * @param {String} group The tag group. 3289 * @param {String} element The tag element. 3290 * @returns {Boolean} True if this tar has a VR. 3291 */ 3292 dwv.dicom.isTagWithVR = function(group, element) { 3293 return !(group === "0xFFFE" && 3294 (element === "0xE000" || element === "0xE00D" || element === "0xE0DD")); 3295 }; 3296 3297 3298 /** 3299 * Get the number of bytes occupied by a data element prefix, i.e. without its value. 3300 * @param {String} vr The Value Representation of the element. 3301 * @param {Boolean} isImplicit Does the data use implicit VR? 3302 * WARNING: this is valid for tags with a VR, if not sure use the 'isTagWithVR' function first. 3303 * Reference: 3304 * - [Data Element explicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_7.html#table_7.1-1}, 3305 * - [Data Element implicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html#table_7.5-1}. 3306 * 3307 * | Tag | VR | VL | Value | 3308 * | 4 | 2 | 2 | X | -> regular explicit: 8 + X 3309 * | 4 | 2+2 | 4 | X | -> 32bit VL: 12 + X 3310 * 3311 * | Tag | VL | Value | 3312 * | 4 | 4 | X | -> implicit (32bit VL): 8 + X 3313 * 3314 * | Tag | Len | Value | 3315 * | 4 | 4 | X | -> item: 8 + X 3316 */ 3317 dwv.dicom.getDataElementPrefixByteSize = function(vr, isImplicit) { 3318 return isImplicit ? 8 : dwv.dicom.is32bitVLVR(vr) ? 12 : 8; 3319 }; 3320 3321 /** 3322 * DicomParser class. 3323 * @constructor 3324 */ 3325 dwv.dicom.DicomParser = function() { 3326 /** 3327 * The list of DICOM elements. 3328 * @type Array 3329 */ 3330 this.dicomElements = {}; 3331 3332 /** 3333 * Default character set (optional). 3334 * @private
3335 * @type String 3336 */ 3337 var defaultCharacterSet; 3338 /** 3339 * Get the default character set. 3340 * @return {String} The default character set. 3341 */ 3342 this.getDefaultCharacterSet = function() { 3343 return defaultCharacterSet; 3344 }; 3345 /** 3346 * Set the default character set. 3347 * param {String} The character set. 3348 */ 3349 this.setDefaultCharacterSet = function(characterSet) { 3350 defaultCharacterSet = characterSet; 3351 }; 3352 }; 3353 3354 /** 3355 * Get the raw DICOM data elements. 3356 * @return {Object} The raw DICOM elements. 3357 */ 3358 dwv.dicom.DicomParser.prototype.getRawDicomElements = function() { 3359 return this.dicomElements; 3360 }; 3361 3362 /** 3363 * Get the DICOM data elements. 3364 * @return {Object} The DICOM elements. 3365 */ 3366 dwv.dicom.DicomParser.prototype.getDicomElements = function() { 3367 return new dwv.dicom.DicomElementsWrapper(this.dicomElements); 3368 }; 3369 3370 /** 3371 * Read a DICOM tag. 3372 * @param reader The raw data reader. 3373 * @param offset The offset where to start to read. 3374 * @return An object containing the tags 'group', 'element' and 'name'. 3375 */ 3376 dwv.dicom.DicomParser.prototype.readTag = function(reader, offset) { 3377 // group 3378 var group = reader.readHex(offset); 3379 offset += Uint16Array.BYTES_PER_ELEMENT; 3380 // element 3381 var element = reader.readHex(offset); 3382 offset += Uint16Array.BYTES_PER_ELEMENT; 3383 // name 3384 var name = dwv.dicom.getGroupElementKey(group, element); 3385 // return 3386 return { 3387 'group': group, 3388 'element': element, 3389 'name': name, 3390 'endOffset': offset 3391 }; 3392 }; 3393 3394 /** 3395 * Read an item data element. 3396 * @param {Object} reader The raw data reader. 3397 * @param {Number} offset The offset where to start to read. 3398 * @param {Boolean} implicit Is the DICOM VR implicit? 3399 * @returns {Object} The item data as a list of data elements. 3400 */ 3401 dwv.dicom.DicomParser.prototype.readItemDataElement = function(reader, offset, implicit) { 3402 var itemData = {}; 3403 3404 // read the first item 3405 var item = this.readDataElement(reader, offset, implicit); 3406 offset = item.endOffset; 3407 3408 // exit if it is a sequence delimitation item 3409 var isSeqDelim = (item.tag.name === "xFFFEE0DD"); 3410 if (isSeqDelim) { 3411 return { 3412 data: itemData, 3413 endOffset: item.endOffset, 3414 isSeqDelim: isSeqDelim 3415 }; 3416 } 3417 3418 // store it 3419 itemData[item.tag.name] = item; 3420 3421 // explicit VR items 3422 if (item.vl !== "u/l") { 3423 // not empty 3424 if (item.vl !== 0) { 3425 // read until the end offset 3426 var endOffset = offset; 3427 offset -= item.vl; 3428 while (offset < endOffset) { 3429 item = this.readDataElement(reader, offset, implicit); 3430 offset = item.endOffset; 3431 itemData[item.tag.name] = item; 3432 } 3433 } 3434 } 3435 // implicit VR items 3436 else { 3437 // read until the item delimitation item 3438 var isItemDelim = false; 3439 while (!isItemDelim) { 3440 item = this.readDataElement(reader, offset, implicit); 3441 offset = item.endOffset; 3442 isItemDelim = (item.tag.name === "xFFFEE00D"); 3443 if (!isItemDelim) { 3444 itemData[item.tag.name] = item; 3445 } 3446 } 3447 } 3448 3449 return { 3450 'data': itemData, 3451 'endOffset': offset, 3452 'isSeqDelim': false 3453 }; 3454 }; 3455 3456 /** 3457 * Read the pixel item data element. 3458 * Ref: [Single frame fragments]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_A.4.html#table_A.4-1}. 3459 * @param {Object} reader The raw data reader. 3460 * @param {Number} offset The offset where to start to read. 3461 * @param {Boolean} implicit Is the DICOM VR implicit? 3462 * @returns {Array} The item data as an array of data elements. 3463 */ 3464 dwv.dicom.DicomParser.prototype.readPixelItemDataElement = function(reader, offset, implicit) { 3465 var itemData = []; 3466 3467 // first item: basic offset table 3468 var item = this.readDataElement(reader, offset, implicit); 3469 var offsetTableVl = item.vl; 3470 offset = item.endOffset; 3471 3472 // read until the sequence delimitation item 3473 var isSeqDelim = false; 3474 while (!isSeqDelim) { 3475 item = this.readDataElement(reader, offset, implicit); 3476 offset = item.endOffset; 3477 isSeqDelim = (item.tag.name === "xFFFEE0DD"); 3478 if (!isSeqDelim) { 3479 itemData.push(item.value); 3480 } 3481 } 3482 3483 return { 3484 'data': itemData, 3485 'endOffset': offset, 3486 'offsetTableVl': offsetTableVl 3487 }; 3488 }; 3489 3490 /** 3491 * Read a DICOM data element. 3492 * Reference: [DICOM VRs]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#table_6.2-1}. 3493 * @param {Object} reader The raw data reader. 3494 * @param {Number} offset The offset where to start to read. 3495 * @param {Boolean} implicit Is the DICOM VR implicit? 3496 * @return {Object}
3496 An object containing the element 'tag', 'vl', 'vr', 'data' and 'endOffset'. 3497 */ 3498 dwv.dicom.DicomParser.prototype.readDataElement = function(reader, offset, implicit) { 3499 // Tag: group, element 3500 var tag = this.readTag(reader, offset); 3501 offset = tag.endOffset; 3502 3503 // Value Representation (VR) 3504 var vr = null; 3505 var is32bitVLVR = false; 3506 if (dwv.dicom.isTagWithVR(tag.group, tag.element)) { 3507 // implicit VR 3508 if (implicit) { 3509 vr = "UN"; 3510 var dict = dwv.dicom.dictionary; 3511 if (typeof dict[tag.group] !== "undefined" && 3512 typeof dict[tag.group][tag.element] !== "undefined") { 3513 vr = dwv.dicom.dictionary[tag.group][tag.element][0]; 3514 } 3515 is32bitVLVR = true; 3516 } else { 3517 vr = reader.readString(offset, 2); 3518 offset += 2 * Uint8Array.BYTES_PER_ELEMENT; 3519 is32bitVLVR = dwv.dicom.is32bitVLVR(vr); 3520 // reserved 2 bytes 3521 if (is32bitVLVR) { 3522 offset += 2 * Uint8Array.BYTES_PER_ELEMENT; 3523 } 3524 } 3525 } else { 3526 vr = "UN"; 3527 is32bitVLVR = true; 3528 } 3529 3530 // Value Length (VL) 3531 var vl = 0; 3532 if (is32bitVLVR) { 3533 vl = reader.readUint32(offset); 3534 offset += Uint32Array.BYTES_PER_ELEMENT; 3535 } else { 3536 vl = reader.readUint16(offset); 3537 offset += Uint16Array.BYTES_PER_ELEMENT; 3538 } 3539 3540 // check the value of VL 3541 var vlString = vl; 3542 if (vl === 0xffffffff) { 3543 vlString = "u/l"; 3544 vl = 0; 3545 } 3546 3547 var startOffset = offset; 3548 3549 // data 3550 var data = null; 3551 var isPixelData = (tag.name === "x7FE00010"); 3552 // pixel data sequence (implicit) 3553 if (isPixelData && vlString === "u/l") { 3554 var pixItemData = this.readPixelItemDataElement(reader, offset, implicit); 3555 offset = pixItemData.endOffset; 3556 startOffset += pixItemData.offsetTableVl; 3557 data = pixItemData.data; 3558 } else if (isPixelData && (vr === "OB" || vr === "OW" || vr === "OF" || vr === "ox")) { 3559 // BitsAllocated 3560 var bitsAllocated = 16; 3561 if (typeof this.dicomElements.x00280100 !== 'undefined') { 3562 bitsAllocated = this.dicomElements.x00280100.value[0]; 3563 } else { 3564 console.warn("Reading DICOM pixel data with default bitsAllocated."); 3565 } 3566 if (bitsAllocated === 8 && vr === "OW") { 3567 console.warn("Reading DICOM pixel data with vr=OW and bitsAllocated=8 (should be 16)."); 3568 } 3569 if (bitsAllocated === 16 && vr === "OB") { 3570 console.warn("Reading DICOM pixel data with vr=OB and bitsAllocated=16 (should be 8)."); 3571 } 3572 // PixelRepresentation 0->unsigned, 1->signed 3573 var pixelRepresentation = 0; 3574 if (typeof this.dicomElements.x00280103 !== 'undefined') { 3575 pixelRepresentation = this.dicomElements.x00280103.value[0]; 3576 } 3577 // read 3578 if (bitsAllocated === 8) { 3579 if (pixelRepresentation === 0) { 3580 data = reader.readUint8Array(offset, vl); 3581 } else { 3582 data = reader.readInt8Array(offset, vl); 3583 } 3584 } else if (bitsAllocated === 16) { 3585 if (pixelRepresentation === 0) { 3586 data = reader.readUint16Array(offset, vl); 3587 } else { 3588 data = reader.readInt16Array(offset, vl); 3589 } 3590 } else if (bitsAllocated === 32) { 3591 if (pixelRepresentation === 0) { 3592 data = reader.readUint32Array(offset, vl); 3593 } else { 3594 data = reader.readInt32Array(offset, vl); 3595 } 3596 } else if (bitsAllocated === 64) { 3597 if (pixelRepresentation === 0) { 3598 data = reader.readUint64Array(offset, vl); 3599 } else { 3600 data = reader.readInt64Array(offset, vl); 3601 } 3602 } 3603 offset += vl; 3604 } 3605 // others 3606 else if (vr === "OB") { 3607 data = reader.readInt8Array(offset, vl); 3608 offset += vl; 3609 } else if (vr === "OW") { 3610 data = reader.readInt16Array(offset, vl); 3611 offset += vl; 3612 } else if (vr === "OF") { 3613 data = reader.readInt32Array(offset, vl); 3614 offset += vl; 3615 } else if (vr === "OD") { 3616 data = reader.readInt64Array(offset, vl); 3617 offset += vl; 3618 } 3619 // numbers 3620 else if (vr === "US") { 3621 data = reader.readUint16Array(offset, vl); 3622 offset += vl; 3623 } else if (vr === "UL") { 3624 data = reader.readUint32Array(offset, vl); 3625 offset += vl; 3626 } else if (vr === "SS") { 3627 data = reader.readInt16Array(offset, vl); 3628 offset += vl; 3629 } else if (vr === "SL") { 3630 data = reader.readInt32Array(offset, vl); 3631 offset += vl; 3632 } else if (vr === "FL") { 3633 data = reader.readFloat32Array(offset, vl); 3634 offset += vl; 3635 } else if (vr === "FD") { 3636 data = reader.readFloat64Array(offset, vl); 3637 offset += vl; 3638 } 3639 // attribute 3640 else if (vr === "AT") { 3641 var raw = reader.readUint16Array(offset, vl); 3642 offset += vl; 3643 data = []; 3644 for (var i = 0, leni = raw.length; i < leni; i += 2) { 3645 var stri = raw[i].toString(16); 3646 var stri1 = raw[i + 1].toString(16); 3647 var str = "("; 3648 str += "0000".substr(0, 4 - stri.length) + stri.toUpperCase(); 3649 str += ","; 3650 str += "0000".substr(0, 4 - stri1.length) + stri1.toUpperCase(); 3651 str += ")"; 3652 data.push(str); 3653 } 3654 } 3655 // not available 3656 else if (vr === "UN") { 3657 data = reader.readUint8Array(offset, vl); 3658 offset += vl; 3659 } 3660 // sequence 3661 else if (vr === "SQ") { 3662 data = []; 3663 var itemData; 3664 // explicit VR sequence 3665 if (vlString !== "u/l") { 3666 // not empty 3667 if (vl !== 0) { 3668 var sqEndOffset = offset + vl; 3669 while (offset < sqEndOffset) { 3670 itemData = this.readItemDataElement(reader, offset, implicit); 3671 data.push(itemData.data); 3672 offset = itemData.endOffset; 3673 } 3674 } 3675 } 3676 // implicit VR sequence 3677 else { 3678 // read until the sequence delimitation item 3679 var isSeqDelim = false; 3680 while (!isSeqDelim) { 3681 itemData = this.readItemDataElement(reader, offset, implicit); 3682 isSeqDelim = itemData.isSeqDelim; 3683 offset = itemData.endOffset; 3684 // do not store the delimitation item 3685 if (!isSeqDelim) { 3686 data.push(itemData.data); 3687 } 3688 } 3689 } 3690 } 3691 // raw 3692 else { 3693 if (vr === "SH" || vr === "LO" || vr === "ST" || 3694 vr === "PN" || vr === "LT" || vr === "UT") { 3695 data = reader.readSpecialString(offset, vl); 3696 } else { 3697 data = reader.readString(offset, vl); 3698 } 3699 offset += vl; 3700 data = data.split("\\"); 3701 } 3702 3703 // return 3704 return { 3705 'tag': tag, 3706 'vr': vr, 3707 'vl': vlString, 3708 'value': data, 3709 'startOffset': startOffset, 3710 'endOffset': offset 3711 }; 3712 }; 3713 3714 /** 3715 * Parse the complete DICOM file (given as input to the class). 3716 * Fills in the member object 'dicomElements'. 3717 * @param buffer The input array buffer. 3718 */ 3719 dwv.dicom.DicomParser.prototype.parse = function(buffer) { 3720 var offset = 0; 3721 var implicit = false; 3722 // default readers 3723 var metaReader = new dwv.dicom.DataReader(buffer); 3724 var dataReader = new dwv.dicom.DataReader(buffer); 3725 3726 // 128 -> 132: magic word 3727 offset = 128; 3728 var magicword = metaReader.readString(offset, 4); 3729 offset += 4 * Uint8Array.BYTES_PER_ELEMENT; 3730 if (magicword !== "DICM") { 3731 throw new Error("Not a valid DICOM file (no magic DICM word found)"); 3732 } 3733 3734 // 0x0002, 0x0000: FileMetaInformationGroupLength 3735 var dataElement = this.readDataElement(metaReader, offset, false); 3736 offset = dataElement.endOffset; 3737 // store the data element 3738 this.dicomElements[dataElement.tag.name] = dataElement; 3739 // get meta length 3740 var metaLength = parseInt(dataElement.value[0], 10); 3741 3742 // meta elements 3743 var metaEnd = offset + metaLength; 3744 while (offset < metaEnd) { 3745 // get the data element 3746 dataElement = this.readDataElement(metaReader, offset, false); 3747 offset = dataElement.endOffset; 3748 // store the data element 3749 this.dicomElements[dataElement.tag.name] = dataElement; 3750 } 3751 3752 // check the TransferSyntaxUID (has to be there!) 3753 if (typeof this.dicomElements.x00020010 === "undefined") { 3754 throw new Error("Not a valid DICOM file (no TransferSyntaxUID found)"); 3755 } 3756 var syntax = dwv.dicom.cleanString(this.dicomElements.x00020010.value[0]); 3757 3758 // check support 3759 if (!dwv.dicom.isReadSupportedTransferSyntax(syntax)) { 3760 throw new Error("Unsupported DICOM transfer syntax: '" + syntax + 3761 "' (" + dwv.dicom.getTransferSyntaxName(syntax) + ")"); 3762 } 3763 3764 // Implicit VR 3765 if (dwv.dicom.isImplicitTransferSyntax(syntax)) { 3766 implicit = true; 3767 } 3768 3769 // Big Endian 3770 if (dwv.dicom.isBigEndianTransferSyntax(syntax)) { 3771 dataReader = new dwv.dicom.DataReader(buffer, false); 3772 } 3773 3774 // default character set 3775 if (typeof this.getDefaultCharacterSet() !== "undefined") { 3776 dataReader.setUtfLabel(this.getDefaultCharacterSet()
3776); 3777 } 3778 3779 // DICOM data elements 3780 while (offset < buffer.byteLength) { 3781 // get the data element 3782 dataElement = this.readDataElement(dataReader, offset, implicit); 3783 // check character set 3784 if (dataElement.tag.name === "x00080005") { 3785 var charSetTerm; 3786 if (dataElement.value.length === 1) { 3787 charSetTerm = dwv.dicom.cleanString(dataElement.value[0]); 3788 } else { 3789 charSetTerm = dwv.dicom.cleanString(dataElement.value[1]); 3790 console.warn("Unsupported character set with code extensions: '" + charSetTerm + "'."); 3791 } 3792 dataReader.setUtfLabel(dwv.dicom.getUtfLabel(charSetTerm)); 3793 } 3794 // increment offset 3795 offset = dataElement.endOffset; 3796 // store the data element 3797 this.dicomElements[dataElement.tag.name] = dataElement; 3798 } 3799 3800 // safety check... 3801 if (buffer.byteLength !== offset) { 3802 console.warn("Did not reach the end of the buffer: " + 3803 offset + " != " + buffer.byteLength); 3804 } 3805 3806 // pixel buffer 3807 if (typeof this.dicomElements.x7FE00010 !== "undefined") { 3808 3809 var numberOfFrames = 1; 3810 if (typeof this.dicomElements.x00280008 !== "undefined") { 3811 numberOfFrames = this.dicomElements.x00280008.value[0]; 3812 } 3813 3814 if (this.dicomElements.x7FE00010.vl !== "u/l") { 3815 // compressed should be encapsulated... 3816 if (dwv.dicom.isJpeg2000TransferSyntax(syntax) || 3817 dwv.dicom.isJpegBaselineTransferSyntax(syntax) || 3818 dwv.dicom.isJpegLosslessTransferSyntax(syntax)) { 3819 console.warn("Compressed but no items..."); 3820 } 3821 3822 // calculate the slice size 3823 var pixData = this.dicomElements.x7FE00010.value; 3824 var columns = this.dicomElements.x00280011.value[0]; 3825 var rows = this.dicomElements.x00280010.value[0]; 3826 var samplesPerPixel = this.dicomElements.x00280002.value[0]; 3827 var sliceSize = columns * rows * samplesPerPixel; 3828 // slice data in an array of frames 3829 var newPixData = []; 3830 var frameOffset = 0; 3831 for (var g = 0; g < numberOfFrames; ++g) { 3832 newPixData[g] = pixData.slice(frameOffset, frameOffset + sliceSize); 3833 frameOffset += sliceSize; 3834 } 3835 // store as pixel data 3836 this.dicomElements.x7FE00010.value = newPixData; 3837 } else { 3838 // handle fragmented pixel buffer 3839 // Reference: http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_8.2.html 3840 // (third note, "Depending on the transfer syntax...") 3841 var pixItems = this.dicomElements.x7FE00010.value; 3842 if (pixItems.length > 1 && pixItems.length > numberOfFrames) { 3843 3844 // concatenate pixel data items 3845 // concat does not work on typed arrays 3846 //this.pixelBuffer = this.pixelBuffer.concat( dataElement.data ); 3847 // manual concat... 3848 var nItemPerFrame = pixItems.length / numberOfFrames; 3849 var newPixItems = []; 3850 var index = 0; 3851 for (var f = 0; f < numberOfFrames; ++f) { 3852 index = f * nItemPerFrame; 3853 // calculate the size of a frame 3854 var size = 0; 3855 for (var i = 0; i < nItemPerFrame; ++i) { 3856 size += pixItems[index + i].length; 3857 } 3858 // create new buffer 3859 var newBuffer = new pixItems[0].constructor(size); 3860 // fill new buffer 3861 var fragOffset = 0; 3862 for (var j = 0; j < nItemPerFrame; ++j) { 3863 newBuffer.set(pixItems[index + j], fragOffset); 3864 fragOffset += pixItems[index + j].length; 3865 } 3866 newPixItems[f] = newBuffer; 3867 } 3868 // store as pixel data 3869 this.dicomElements.x7FE00010.value = newPixItems; 3870 } 3871 } 3872 } 3873 }; 3874 3875 /** 3876 * DicomElements wrapper. 3877 * @constructor 3878 * @param {Array} dicomElements The elements to wrap. 3879 */ 3880 dwv.dicom.DicomElementsWrapper = function(dicomElements) { 3881 3882 /** 3883 * Get a DICOM Element value from a group/element key. 3884 * @param {String} groupElementKey The key to retrieve. 3885 * @return {Object} The DICOM element. 3886 */ 3887 this.getDEFromKey = function(groupElementKey) { 3888 return dicomElements[groupElementKey]; 3889 }; 3890 3891 /** 3892 * Get a DICOM Element value from a group/element key. 3893 * @param {String} groupElementKey The key to retrieve. 3894 * @param {Boolean} asArray Get the value as an Array. 3895 * @return {Object} The DICOM element value. 3896 */ 3897 this.getFromKey = function(groupElementKey, asArray) { 3898 // default 3899 if (typeof asArray === "undefined") { 3900 asArray = false; 3901 } 3902 var value = null; 3903 var dElement = dicomElements[groupElementKey]; 3904 if (typeof dElement !== "undefined") { 3905 // raw value if only one 3906 if (dElement.value.length === 1 && asArray === false) { 3907 value = dElement.value[0]; 3908 } else { 3909 value = dElement.value; 3910 } 3911 } 3912 return value; 3913 }; 3914 3915 /** 3916 * Dump the DICOM tags to an array. 3917 * @return {Array} 3918 */ 3919 this.dumpToTable = function() { 3920 var keys = Object.keys(dicomElements); 3921 var dict = dwv.dicom.dictionary; 3922 var table = []; 3923 var dicomElement = null; 3924 var dictElement = null; 3925 var row = null; 3926 for (var i = 0, leni = keys.length; i < leni; ++i) { 3927 dicomElement = dicomElements[keys[i]]; 3928 row = {}; 3929 // dictionnary entry (to get name) 3930 dictElement = null; 3931 if (typeof dict[dicomElement.tag.group] !== "undefined" && 3932 typeof dict[dicomElement.tag.group][dicomElement.tag.element] !== "undefined") { 3933 dictElement = dict[dicomElement.tag.group][dicomElement.tag.element]; 3934 } 3935 // name 3936 if (dictElement !== null) { 3937 row.name = dictElement[2]; 3938 } else { 3939 row.name = "Unknown Tag & Data"; 3940 } 3941 // value 3942 row.value = this.getElementValueAsString(dicomElement); 3943 // others 3944 row.group = dicomElement.tag.group; 3945 row.element = dicomElement.tag.element; 3946 row.vr = dicomElement.vr; 3947 row.vl = dicomElement.vl; 3948 3949 table.push(row); 3950 } 3951 return table; 3952 }; 3953 3954 /** 3955 * Dump the DICOM tags to a string. 3956 * @return {String} The dumped file. 3957 */ 3958 this.dump = function() { 3959 var keys = Object.keys(dicomElements); 3960 var result = "\n"; 3961 result += "# Dicom-File-Format\n"; 3962 result += "\n"; 3963 result += "# Dicom-Meta-Information-Header\n"; 3964 result += "# Used TransferSyntax: "; 3965 if (dwv.dicom.isNativeLittleEndian()) { 3966 result += "Little Endian Explicit\n"; 3967 } else { 3968 result += "NOT Little Endian Explicit\n"; 3969 } 3970 var dicomElement = null; 3971 var checkHeader = true; 3972 for (var i = 0, leni = keys.length; i < leni; ++i) { 3973 dicomElement = dicomElements[keys[i]]; 3974 if (checkHeader && dicomElement.tag.group !== "0x0002") { 3975 result += "\n"; 3976 result += "# Dicom-Data-Set\n"; 3977 result += "# Used TransferSyntax: "; 3978 var syntax = dwv.dicom.cleanString(dicomElements.x00020010.value[0]); 3979 result += dwv.dicom.getTransferSyntaxName(syntax); 3980 result += "\n"; 3981 checkHeader = false; 3982 } 3983 result += this.getElementAsString(dicomElement) + "\n"; 3984 } 3985 return result; 3986 }; 3987 3988 }; 3989 3990 /** 3991 * Get a data element value as a string. 3992 * @param {Object} dicomElement The DICOM element. 3993 * @param {Boolean} pretty When set to true, returns a 'pretified' content. 3994 * @return {String} A string representation of the DICOM element. 3995 */ 3996 dwv.dicom.DicomElementsWrapper.prototype.getElementValueAsString = function(dicomElement, pretty
3996) { 3997 var str = ""; 3998 var strLenLimit = 65; 3999 4000 // dafault to pretty output 4001 if (typeof pretty === "undefined") { 4002 pretty = true; 4003 } 4004 // check dicom element input 4005 if (typeof dicomElement === "undefined" || dicomElement === null) { 4006 return str; 4007 } 4008 4009 // Polyfill for Number.isInteger. 4010 var isInteger = Number.isInteger || function(value) { 4011 return typeof value === 'number' && 4012 isFinite(value) && 4013 Math.floor(value) === value; 4014 }; 4015 4016 // TODO Support sequences. 4017 4018 if (dicomElement.vr !== "SQ" && 4019 dicomElement.value.length === 1 && dicomElement.value[0] === "") { 4020 str += "(no value available)"; 4021 } else if (dicomElement.tag.group === '0x7FE0' && 4022 dicomElement.tag.element === '0x0010' && 4023 dicomElement.vl === 'u/l') { 4024 str = "(PixelSequence)"; 4025 } else if (dicomElement.vr === "DA" && pretty) { 4026 var daValue = dicomElement.value[0]; 4027 var daYear = parseInt(daValue.substr(0, 4), 10); 4028 var daMonth = parseInt(daValue.substr(4, 2), 10) - 1; // 0-11 4029 var daDay = parseInt(daValue.substr(6, 2), 10); 4030 var da = new Date(daYear, daMonth, daDay); 4031 str = da.toLocaleDateString(); 4032 } else if (dicomElement.vr === "TM" && pretty) { 4033 var tmValue = dicomElement.value[0]; 4034 var tmHour = tmValue.substr(0, 2); 4035 var tmMinute = tmValue.length >= 4 ? tmValue.substr(2, 2) : "00"; 4036 var tmSeconds = tmValue.length >= 6 ? tmValue.substr(4, 2) : "00"; 4037 str = tmHour + ':' + tmMinute + ':' + tmSeconds; 4038 } else { 4039 var isOtherVR = (dicomElement.vr[0].toUpperCase() === "O"); 4040 var isFloatNumberVR = (dicomElement.vr === "FL" || 4041 dicomElement.vr === "FD" || 4042 dicomElement.vr === "DS"); 4043 var valueStr = ""; 4044 for (var k = 0, lenk = dicomElement.value.length; k < lenk; ++k) { 4045 valueStr = ""; 4046 if (k !== 0) { 4047 valueStr += "\\"; 4048 } 4049 if (isFloatNumberVR) { 4050 var val = dicomElement.value[k]; 4051 if (typeof val === "string") { 4052 val = dwv.dicom.cleanString(val); 4053 } 4054 var num = Number(val); 4055 if (!isInteger(num) && pretty) { 4056 valueStr += num.toPrecision(4); 4057 } else { 4058 valueStr += num.toString(); 4059 } 4060 } else if (isOtherVR) { 4061 var tmp = dicomElement.value[k].toString(16); 4062 if (dicomElement.vr === "OB") { 4063 tmp = "00".substr(0, 2 - tmp.length) + tmp; 4064 } else { 4065 tmp = "0000".substr(0, 4 - tmp.length) + tmp; 4066 } 4067 valueStr += tmp; 4068 } else if (typeof dicomElement.value[k] === "string") { 4069 valueStr += dwv.dicom.cleanString(dicomElement.value[k]); 4070 } else { 4071 valueStr += dicomElement.value[k]; 4072 } 4073 // check length 4074 if (str.length + valueStr.length <= strLenLimit) { 4075 str += valueStr; 4076 } else { 4077 str += "..."; 4078 break; 4079 } 4080 } 4081 } 4082 return str; 4083 }; 4084 4085 /** 4086 * Get a data element value as a string. 4087 * @param {String} groupElementKey The key to retrieve. 4088 */ 4089 dwv.dicom.DicomElementsWrapper.prototype.getElementValueAsStringFromKey = function(groupElementKey) { 4090 return this.getElementValueAsString(this.getDEFromKey(groupElementKey)); 4091 }; 4092 4093 /** 4094 * Get a data element as a string. 4095 * @param {Object} dicomElement The DICOM element. 4096 * @param {String}
4096 prefix A string to prepend this one. 4097 */ 4098 dwv.dicom.DicomElementsWrapper.prototype.getElementAsString = function(dicomElement, prefix) { 4099 // default prefix 4100 prefix = prefix || ""; 4101 4102 // get element from dictionary 4103 var dict = dwv.dicom.dictionary; 4104 var dictElement = null; 4105 if (typeof dict[dicomElement.tag.group] !== "undefined" && 4106 typeof dict[dicomElement.tag.group][dicomElement.tag.element] !== "undefined") { 4107 dictElement = dict[dicomElement.tag.group][dicomElement.tag.element]; 4108 } 4109 4110 var deSize = dicomElement.value.length; 4111 var isOtherVR = (dicomElement.vr[0].toUpperCase() === "O"); 4112 4113 // no size for delimitations 4114 if (dicomElement.tag.group === "0xFFFE" && ( 4115 dicomElement.tag.element === "0xE00D" || 4116 dicomElement.tag.element === "0xE0DD")) { 4117 deSize = 0; 4118 } else if (isOtherVR) { 4119 deSize = 1; 4120 } 4121 4122 var isPixSequence = (dicomElement.tag.group === '0x7FE0' && 4123 dicomElement.tag.element === '0x0010' && 4124 dicomElement.vl === 'u/l'); 4125 4126 var line = null; 4127 4128 // (group,element) 4129 line = "("; 4130 line += dicomElement.tag.group.substr(2, 5).toLowerCase(); 4131 line += ","; 4132 line += dicomElement.tag.element.substr(2, 5).toLowerCase(); 4133 line += ") "; 4134 // value representation 4135 line += dicomElement.vr; 4136 // value 4137 if (dicomElement.vr !== "SQ" && dicomElement.value.length === 1 && dicomElement.value[0] === "") { 4138 line += " (no value available)"; 4139 deSize = 0; 4140 } else { 4141 // simple number display 4142 if (dicomElement.vr === "na") { 4143 line += " "; 4144 line += dicomElement.value[0]; 4145 } 4146 // pixel sequence 4147 else if (isPixSequence) { 4148 line += " (PixelSequence #=" + deSize + ")"; 4149 } else if (dicomElement.vr === 'SQ') { 4150 line += " (Sequence with"; 4151 if (dicomElement.vl === "u/l") { 4152 line += " undefined"; 4153 } else { 4154 line += " explicit"; 4155 } 4156 line += " length #="; 4157 line += dicomElement.value.length; 4158 line += ")"; 4159 } 4160 // 'O'ther array, limited display length 4161 else if (isOtherVR || 4162 dicomElement.vr === 'pi' || 4163 dicomElement.vr === "UL" || 4164 dicomElement.vr === "US" || 4165 dicomElement.vr === "SL" || 4166 dicomElement.vr === "SS" || 4167 dicomElement.vr === "FL" || 4168 dicomElement.vr === "FD" || 4169 dicomElement.vr === "AT") { 4170 line += " "; 4171 line += this.getElementValueAsString(dicomElement, false); 4172 } 4173 // default 4174 else { 4175 line += " ["; 4176 line += this.getElementValueAsString(dicomElement, false); 4177 line += "]"; 4178 } 4179 } 4180 4181 // align # 4182 var nSpaces = 55 - line.length; 4183 if (nSpaces > 0) { 4184 for (var s = 0; s < nSpaces; ++s) { 4185 line += " "; 4186 } 4187 } 4188 line += " # "; 4189 if (dicomElement.vl < 100) { 4190 line += " "; 4191 } 4192 if (dicomElement.vl < 10) { 4193 line += " "; 4194 } 4195 line += dicomElement.vl; 4196 line += ", "; 4197 line += deSize; //dictElement[1]; 4198 line += " "; 4199 if (dictElement !== null) { 4200 line += dictElement[2]; 4201 } else { 4202 line += "Unknown Tag & Data"; 4203 } 4204 4205 var message = null; 4206 4207 // continue for sequence 4208 if (dicomElement.vr === 'SQ') { 4209 var item = null; 4210 for (var l = 0, lenl = dicomElement.value.length; l < lenl; ++l) { 4211 item = dicomElement.value[l]; 4212 var itemKeys = Object.keys(item); 4213 if (itemKeys.length === 0) { 4214 continue; 4215 } 4216 4217 // get the item element 4218 var itemElement = item.xFFFEE000;
4219 message = "(Item with"; 4220 if (itemElement.vl === "u/l") { 4221 message += " undefined"; 4222 } else { 4223 message += " explicit"; 4224 } 4225 message += " length #=" + (itemKeys.length - 1) + ")"; 4226 itemElement.value = [message]; 4227 itemElement.vr = "na"; 4228 4229 line += "\n"; 4230 line += this.getElementAsString(itemElement, prefix + " "); 4231 4232 for (var m = 0, lenm = itemKeys.length; m < lenm; ++m) { 4233 if (itemKeys[m] !== "xFFFEE000") { 4234 line += "\n"; 4235 line += this.getElementAsString(item[itemKeys[m]], prefix + " "); 4236 } 4237 } 4238 4239 message = "(ItemDelimitationItem"; 4240 if (itemElement.vl !== "u/l") { 4241 message += " for re-encoding"; 4242 } 4243 message += ")"; 4244 var itemDelimElement = { 4245 "tag": { "group": "0xFFFE", "element": "0xE00D" }, 4246 "vr": "na", 4247 "vl": "0", 4248 "value": [message] 4249 }; 4250 line += "\n"; 4251 line += this.getElementAsString(itemDelimElement, prefix + " "); 4252 4253 } 4254 4255 message = "(SequenceDelimitationItem"; 4256 if (dicomElement.vl !== "u/l") { 4257 message += " for re-encod."; 4258 } 4259 message += ")"; 4260 var sqDelimElement = { 4261 "tag": { "group": "0xFFFE", "element": "0xE0DD" }, 4262 "vr": "na", 4263 "vl": "0", 4264 "value": [message] 4265 }; 4266 line += "\n"; 4267 line += this.getElementAsString(sqDelimElement, prefix); 4268 } 4269 // pixel sequence 4270 else if (isPixSequence) { 4271 var pixItem = null; 4272 for (var n = 0, lenn = dicomElement.value.length; n < lenn; ++n) { 4273 pixItem = dicomElement.value[n]; 4274 line += "\n"; 4275 pixItem.vr = 'pi'; 4276 line += this.getElementAsString(pixItem, prefix + " "); 4277 } 4278 4279 var pixDelimElement = { 4280 "tag": { "group": "0xFFFE", "element": "0xE0DD" }, 4281 "vr": "na", 4282 "vl": "0", 4283 "value": ["(SequenceDelimitationItem)"] 4284 }; 4285 line += "\n"; 4286 line += this.getElementAsString(pixDelimElement, prefix); 4287 } 4288 4289 return prefix + line; 4290 }; 4291 4292 /** 4293 * Get a DICOM Element value from a group and an element. 4294 * @param {Number} group The group. 4295 * @param {Number} element The element. 4296 * @return {Object} The DICOM element value. 4297 */ 4298 dwv.dicom.DicomElementsWrapper.prototype.getFromGroupElement = function( 4299 group, element) { 4300 return this.getFromKey( 4301 dwv.dicom.getGroupElementKey(group, element)); 4302 }; 4303 4304 /** 4305 * Get a DICOM Element value from a tag name. 4306 * Uses the DICOM dictionary. 4307 * @param {String} name The tag name. 4308 * @return {Object} The DICOM element value. 4309 */ 4310 dwv.dicom.DicomElementsWrapper.prototype.getFromName = function(name) { 4311 var value = null; 4312 var tagGE = dwv.dicom.getGroupElementFromName(name); 4313 // check that we are not at the end of the dictionary 4314 if (tagGE.group !== null && tagGE.element !== null) { 4315 value = this.getFromKey(dwv.dicom.getGroupElementKey(tagGE.group, tagGE.element)); 4316 } 4317 return value; 4318 }; 4319 4320 // namespaces 4321 var dwv = dwv || {}; 4322 dwv.dicom = dwv.dicom || {}; 4323 4324 /** 4325 * Data writer. 4326 * 4327 * Example usage: 4328 * var parser = new dwv.dicom.DicomParser(); 4329 * parser.parse(this.response); 4330 * 4331 * var writer = new dwv.dicom.DicomWriter(parser.getRawDicomElements()); 4332 * var blob = new Blob([writer.getBuffer()], {type: 'application/dicom'}); 4333 * 4334 * var element = document.getElementById("download"); 4335 * element.href = URL.createObjectURL(blob); 4336 * element.download = "anonym.dcm"; 4337 * 4338 * @constructor 4339 * @param {Array} buffer The input array buffer. 4340 * @param {Boolean} isLittleEndian Flag to tell if the data is little or big endian. 4341 */ 4342 dwv.dicom.DataWriter = function(buffer, isLittleEndian) { 4343 // Set endian flag if not defined. 4344 if (typeof isLittleEndian === 'undefined') { 4345 isLittleEndian = true; 4346 } 4347 4348 // private DataView 4349 var view = new DataView(buffer); 4350 4351 /** 4352 * Write Uint8 data. 4353 * @param {Number} byteOffset The offset to start writing from. 4354 * @param {Number} value The data to write. 4355 * @returns {Number} The new offset position. 4356 */ 4357 this.writeUint8 = function(byteOffset, value) { 4358 view.setUint8(byteOffset, value); 4359 return byteOffset + Uint8Array.BYTES_PER_ELEMENT; 4360 }; 4361 4362 /** 4363 * Write Int8 data. 4364 * @param {Number} byteOffset The offset to start writing from. 4365 * @param {Number} value The data to write. 4366 * @returns {Number} The new offset position. 4367 */ 4368 this.writeInt8 = function(byteOffset, value) { 4369 view.setInt8(byteOffset, value); 4370 return byteOffset + Int8Array.BYTES_PER_ELEMENT; 4371 }; 4372 4373 /** 4374 * Write Uint16 data. 4375 * @param {Number} byteOffset The offset to start writing from. 4376 * @param {Number} value The data to write. 4377 * @returns {Number} The new offset position. 4378 */ 4379 this.writeUint16 = function(byteOffset, value) { 4380 view.setUint16(byteOffset, value, isLittleEndian); 4381 return byteOffset + Uint16Array.BYTES_PER_ELEMENT; 4382 }; 4383 4384 /** 4385 * Write Int16 data. 4386 * @param {Number} byteOffset The offset to start writing from. 4387 * @param {Number} value The data to write. 4388 * @returns {Number} The new offset position. 4389 */ 4390 this.writeInt16 = function(byteOffset, value) { 4391 view.setInt16(byteOffset, value, isLittleEndian); 4392 return byteOffset + Int16Array.BYTES_PER_ELEMENT; 4393 }; 4394 4395 /** 4396 * Write Uint32 data. 4397 * @param {Number} byteOffset The offset to start writing from. 4398 * @param {Number} value The data to write. 4399 * @returns {Number} The new offset position. 4400 */ 4401 this.writeUint32 = function(byteOffset, value) { 4402 view.setUint32(byteOffset, value, isLittleEndian); 4403 return byteOffset + Uint32Array.BYTES_PER_ELEMENT; 4404 }; 4405 4406 /** 4407 * Write Int32 data. 4408 * @param {Number} byteOffset The offset to start writing from. 4409 * @param {Number} value The data to write. 4410 * @returns {Number} The new offset position. 4411 */ 4412 this.writeInt32 = function(byteOffset, value) { 4413 view.setInt32(byteOffset, value, isLittleEndian); 4414 return byteOffset + Int32Array.BYTES_PER_ELEMENT; 4415 }; 4416 4417 /** 4418 * Write Float32 data. 4419 * @param {Number} byteOffset The offset to start writing from. 4420 * @param {Number} value The data to write. 4421 * @returns {Number} The new offset position. 4422 */ 4423 this.writeFloat32 = function(byteOffset, value) { 4424 view.setFloat32(byteOffset, value, isLittleEndian); 4425 return byteOffset + Float32Array.BYTES_PER_ELEMENT; 4426 }; 4427 4428 /** 4429 * Write Float64 data. 4430 * @param {Number} byteOffset The offset to start writing from. 4431 * @param {Number} value The data to write. 4432 * @returns {Number} The new offset position. 4433 */ 4434 this.writeFloat64 = function(byteOffset, value) { 4435 view.setFloat64(byteOffset, value, isLittleEndian); 4436 return byteOffset + Float64Array.BYTES_PER_ELEMENT; 4437 }; 4438 4439 /** 4440 * Write string data as hexadecimal. 4441 * @param {Number} byteOffset The offset to start writing from. 4442 * @param {Number} str The padded hexadecimal string to write ('0x####'). 4443 * @returns {Number} The new offset position. 4444 */ 4445 this.writeHex = function(byteOffset, str) { 4446 // remove first two chars and parse 4447 var value = parseInt(str.substr(2), 16); 4448 view.setUint16(byteOffset, value, isLittleEndian); 4449 return byteOffset + Uint16Array.BYTES_PER_ELEMENT; 4450 }; 4451 4452 /** 4453 * Write string data. 4454 * @param {Number} byteOffset The offset to start writing from. 4455 * @param {Number} str The data to write. 4456 * @returns {Number} The new offset position. 4457 */ 4458 this.writeString = function(byteOffset, str) { 4459 for (var i = 0, len = str.length; i < len; ++i) { 4460 view.setUint8(byteOffset, str.charCodeAt(i)); 4461 byteOffset += Uint8Array.BYTES_PER_ELEMENT; 4462 } 4463 return byteOffset; 4464 }; 4465 4466 }; 4467 4468 /** 4469 * Write Uint8 array. 4470 * @param {Number} byteOffset The offset to start writing from. 4471 * @param {Array} array The array to write. 4472 * @returns {Number} The new offset position. 4473 */ 4474 dwv.dicom.DataWriter.prototype.writeUint8Array = function(byteOffset, array) { 4475 for (var i = 0, len = array.length; i < len; ++i) { 4476 byteOffset = this.writeUint8(byteOffset, array[i]); 4477 } 4478 return byteOffset; 4479 }; 4480 4481 /** 4482 * Write Int8 array. 4483 * @param {Number} byteOffset The offset to start writing from. 4484 * @param {Array} array The array to write. 4485 * @returns {Number} The new offset position. 4486 */ 4487 dwv.dicom.DataWriter.prototype.writeInt8Array = function(byteOffset, array) { 4488 for (var i = 0, len = array.length; i < len; ++i) { 4489 byteOffset = this.writeInt8(byteOffset, array[i]); 4490 } 4491 return byteOffset; 4492 }; 4493 4494 /** 4495 * Write Uint16 array. 4496 * @param {Number} byteOffset The offset to start writing from. 4497 * @param {Array} array The array to write. 4498 * @returns {Number} The new offset position. 4499 */ 4500 dwv.dicom.DataWriter.prototype.writeUint16Array = function(byteOffset, array) { 4501 for (var i = 0, len = array.length; i < len; ++i) { 4502 byteOffset = this.writeUint16(byteOffset, array[i]); 4503 } 4504 return byteOffset; 4505 }; 4506 4507 /** 4508 * Write Int16 array. 4509 * @param {Number} byteOffset The offset to start writing from. 4510 * @param {Array} array The array to write. 4511 * @returns {Number} The new offset position. 4512 */ 4513 dwv.dicom.DataWriter.prototype.writeInt16Array = function(byteOffset, array) { 4514 for (var i = 0, len = array.length; i < len; ++i) { 4515 byteOffset = this.writeInt16(byteOffset, array[i]); 4516 } 4517 return byteOffset; 4518 }; 4519 4520 /** 4521 * Write Uint32 array. 4522 * @param {Number} byteOffset The offset to start writing from. 4523 * @param {Array} array The array to write. 4524 * @returns {Number} The new offset position. 4525 */ 4526 dwv.dicom.DataWriter.prototype.writeUint32Array = function(byteOffset, array) { 4527 for (var i = 0, len = array.length; i < len; ++i) { 4528 byteOffset = this.writeUint32(byteOffset, array[i]); 4529 } 4530 return byteOffset; 4531 }; 4532 4533 /** 4534 * Write Int32 array. 4535 * @param {Number} byteOffset The offset to start writing from. 4536 * @param {Array} array The array to write. 4537 * @returns {Number} The new offset position. 4538 */ 4539 dwv.dicom.DataWriter.prototype.writeInt32Array = function(byteOffset, array) { 4540 for (var i = 0, len = array.length; i < len; ++i) { 4541 byteOffset = this.writeInt32(byteOffset, array[i]); 4542 } 4543 return byteOffset; 4544 }; 4545 4546 /** 4547 * Write Float32 array. 4548 * @param {Number} byteOffset The offset to start writing from. 4549 * @param {Array} array The array to write. 4550 * @returns {Number} The new offset position. 4551 */ 4552 dwv.dicom.DataWriter.prototype.writeFloat32Array = function(byteOffset, array) { 4553 for (var i = 0, len = array.length; i < len; ++i) { 4554 byteOffset = this.writeFloat32(byteOffset, array[i]); 4555 } 4556 return byteOffset; 4557 }; 4558 4559 /** 4560 * Write Float64 array. 4561 * @param {Number} byteOffset The offset to start writing from. 4562 * @param {Array} array The array to write. 4563 * @returns {Number} The new offset position. 4564 */ 4565 dwv.dicom.DataWriter.prototype.writeFloat64Array = function(byteOffset, array) { 4566 for (var i = 0, len = array.length; i < len; ++i) { 4567 byteOffset = this.writeFloat64(byteOffset, array[i]); 4568 } 4569 return byteOffset; 4570 }; 4571 4572 /** 4573 * Write string array. 4574 * @param {Number} byteOffset The offset to start writing from. 4575 * @param {Array} array The array to write. 4576 * @returns {Number} The new offset position. 4577 */ 4578 dwv.dicom.DataWriter.prototype.writeStringArray = function(byteOffset, array) { 4579 for (var i = 0, len = array.length; i < len; ++i) { 4580 // separator 4581 if (i !== 0) { 4582 byteOffset = this.writeString(byteOffset, "\\"); 4583 } 4584 // value 4585 byteOffset = this.writeString(byteOffset, array[i].toString()); 4586 } 4587 return byteOffset; 4588 }; 4589 4590 /** 4591 * Write a list of items. 4592 * @param {Number} byteOffset The offset to start writing from. 4593 * @param {Array} items The list of items to write. 4594 * @param {Boolean} isImplicit Is the DICOM VR implicit? 4595 * @returns {Number} The new offset position. 4596 */ 4597 dwv.dicom.DataWriter.prototype.writeDataElementItems = function(byteOffset, items, isImplicit) { 4598 var item = null; 4599 for (var i = 0; i < items.length; ++i) { 4600 item = items[i]; 4601 var itemKeys = Object.keys(item); 4602 if (itemKeys.length === 0) { 4603 continue; 4604 } 4605 // write item 4606 var itemElement = item.xFFFEE000; 4607 itemElement.value = []; 4608 var implicitLength = (itemElement.vl === "u/l"); 4609 if (implicitLength) { 4610 itemElement.vl = 0xffffffff; 4611 } 4612 byteOffset = this.writeDataElement(itemElement, byteOffset, isImplicit); 4613 // write rest 4614 for (var m = 0; m < itemKeys.length; ++m) { 4615 if (itemKeys[m] !== "xFFFEE000" && itemKeys[m] !== "xFFFEE00D") { 4616 byteOffset = this.writeDataElement(item[itemKeys[m]], byteOffset, isImplicit); 4617 } 4618 } 4619 // item delimitation 4620 if (implicitLength) { 4621 var itemDelimElement = { 4622 'tag': { 4623 group: "0xFFFE", 4624 element: "0xE00D", 4625 name: "ItemDelimitationItem" 4626 }, 4627 'vr': "NONE", 4628 'vl': 0, 4629 'value': [] 4630 }; 4631 byteOffset = this.writeDataElement(itemDelimElement, byteOffset, isImplicit); 4632 } 4633 } 4634 4635 // return new offset 4636 return byteOffset; 4637 }; 4638 4639 /** 4640 * Write data with a specific Value Representation (VR). 4641 * @param {String} vr The data Value Representation (VR). 4642 * @param {Number} byteOffset The offset to start writing from. 4643 * @param {Array} value The array to write. 4644 * @param {Boolean} isImplicit Is the DICOM VR implicit? 4645 * @returns {Number} The new offset position. 4646 */ 4647 dwv.dicom.DataWriter.prototype.writeDataElementValue = function(vr, byteOffset, value, isImplicit) { 4648 // first check input type to know how to write 4649 if (value instanceof Uint8Array) { 4650 byteOffset = this.writeUint8Array(byteOffset, value); 4651 } else if (value instanceof Int8Array) { 4652
4652 byteOffset = this.writeInt8Array(byteOffset, value); 4653 } else if (value instanceof Uint16Array) { 4654 byteOffset = this.writeUint16Array(byteOffset, value); 4655 } else if (value instanceof Int16Array) { 4656 byteOffset = this.writeInt16Array(byteOffset, value); 4657 } else if (value instanceof Uint32Array) { 4658 byteOffset = this.writeUint32Array(byteOffset, value); 4659 } else if (value instanceof Int32Array) { 4660 byteOffset = this.writeInt32Array(byteOffset, value); 4661 } else { 4662 // switch according to VR if input type is undefined 4663 if (vr === "UN") { 4664 byteOffset = this.writeUint8Array(byteOffset, value); 4665 } else if (vr === "OB") { 4666 byteOffset = this.writeInt8Array(byteOffset, value); 4667 } else if (vr === "OW") { 4668 byteOffset = this.writeInt16Array(byteOffset, value); 4669 } else if (vr === "OF") { 4670 byteOffset = this.writeInt32Array(byteOffset, value); 4671 } else if (vr === "OD") { 4672 byteOffset = this.writeInt64Array(byteOffset, value); 4673 } else if (vr === "US") { 4674 byteOffset = this.writeUint16Array(byteOffset, value); 4675 } else if (vr === "SS") { 4676 byteOffset = this.writeInt16Array(byteOffset, value); 4677 } else if (vr === "UL") { 4678 byteOffset = this.writeUint32Array(byteOffset, value); 4679 } else if (vr === "SL") { 4680 byteOffset = this.writeInt32Array(byteOffset, value); 4681 } else if (vr === "FL") { 4682 byteOffset = this.writeFloat32Array(byteOffset, value); 4683 } else if (vr === "FD") { 4684 byteOffset = this.writeFloat64Array(byteOffset, value); 4685 } else if (vr === "SQ") { 4686 byteOffset = this.writeDataElementItems(byteOffset, value, isImplicit); 4687 } else if (vr === "AT") { 4688 for (var i = 0; i < value.length; ++i) { 4689 var hexString = value[i] + ''; 4690 var hexString1 = hexString.substring(1, 5);
4691 var hexString2 = hexString.substring(6, 10); 4692 var dec1 = parseInt(hexString1, 16); 4693 var dec2 = parseInt(hexString2, 16); 4694 var atValue = new Uint16Array([dec1, dec2]); 4695 byteOffset = this.writeUint16Array(byteOffset, atValue); 4696 } 4697 } else { 4698 byteOffset = this.writeStringArray(byteOffset, value); 4699 } 4700 } 4701 // return new offset 4702 return byteOffset; 4703 }; 4704 4705 /** 4706 * Write a pixel data element. 4707 * @param {String} vr The data Value Representation (VR). 4708 * @param {String} vl The data Value Length (VL). 4709 * @param {Number} byteOffset The offset to start writing from. 4710 * @param {Array} value The array to write. 4711 * @param {Boolean} isImplicit Is the DICOM VR implicit? 4712 * @returns {Number} The new offset position. 4713 */ 4714 dwv.dicom.DataWriter.prototype.writePixelDataElementValue = function(vr, vl, byteOffset, value, isImplicit) { 4715 // explicit length 4716 if (vl !== "u/l") { 4717 var finalValue = value[0]; 4718 // flatten multi frame 4719 if (value.length > 1) { 4720 finalValue = dwv.dicom.flattenArrayOfTypedArrays(value); 4721 } 4722 // write 4723 byteOffset = this.writeDataElementValue(vr, byteOffset, finalValue, isImplicit); 4724 } else { 4725 // pixel data as sequence 4726 var item = {}; 4727 // first item: basic offset table 4728 item.xFFFEE000 = { 4729 'tag': { 4730 group: "0xFFFE", 4731 element: "0xE000", 4732 name: "xFFFEE000" 4733 }, 4734 'vr': "UN", 4735 'vl': 0, 4736 'value': [] 4737 }; 4738 // data 4739 for (var i = 0; i < value.length; ++i) { 4740 item[i] = { 4741 'tag': { 4742 group: "0xFFFE", 4743 element: "0xE000", 4744 name: "xFFFEE000" 4745 }, 4746 'vr': vr, 4747 'vl': value[i].length, 4748 'value': value[i] 4749 }; 4750 } 4751 // write 4752 byteOffset = this.writeDataElementItems(byteOffset, [item], isImplicit); 4753 } 4754 4755 // return new offset 4756 return byteOffset; 4757 }; 4758 4759 /** 4760 * Write a data element. 4761 * @param {Object} element The DICOM data element to write. 4762 * @param {Number} byteOffset The offset to start writing from. 4763 * @param {Boolean} isImplicit Is the DICOM VR implicit? 4764 * @returns {Number} The new offset position. 4765 */ 4766 dwv.dicom.DataWriter.prototype.writeDataElement = function(element, byteOffset, isImplicit) { 4767 var isTagWithVR = dwv.dicom.isTagWithVR(element.tag.group, element.tag.element); 4768 var is32bitVLVR = (isImplicit || !isTagWithVR) ? true : dwv.dicom.is32bitVLVR(element.vr); 4769 // group 4770 byteOffset = this.writeHex(byteOffset, element.tag.group); 4771 // element 4772 byteOffset = this.writeHex(byteOffset, element.tag.element); 4773 // VR 4774 if (isTagWithVR && !isImplicit) { 4775 byteOffset = this.writeString(byteOffset, element.vr); 4776 // reserved 2 bytes for 32bit VL 4777 if (is32bitVLVR) { 4778 byteOffset += 2; 4779 } 4780 } 4781 4782 // update vl for sequence or item with implicit length 4783 var vl = element.vl; 4784 if (dwv.dicom.isImplicitLengthSequence(element) || 4785 dwv.dicom.isImplicitLengthItem(element) || 4786 dwv.dicom.isImplicitLengthPixels(element)) { 4787 vl = 0xffffffff; 4788 } 4789 // VL 4790 if (is32bitVLVR) { 4791 byteOffset = this.writeUint32(byteOffset, vl); 4792 } else { 4793 byteOffset = this.writeUint16(byteOffset, vl); 4794 } 4795 4796 // value 4797 var value = element.value; 4798 // check value 4799 if (typeof value === 'undefined') { 4800 value = []; 4801 } 4802 // write 4803 if (element.tag.name === "x7FE00010") { 4804 byteOffset = this.writePixelDataElementValue(element.vr, element.vl, byteOffset, value, isImplicit); 4805 } else { 4806 byteOffset = this.writeDataElementValue(element.vr, byteOffset, value, isImplicit); 4807 } 4808 4809 // sequence delimitation item for sequence with implicit length 4810 if (dwv.dicom.isImplicitLengthSequence(element) || 4811 dwv.dicom.isImplicitLengthPixels(element)) { 4812 var seqDelimElement = { 4813 'tag': { 4814 group: "0xFFFE", 4815 element: "0xE0DD", 4816 name: "SequenceDelimitationItem" 4817 }, 4818 'vr': "NONE", 4819 'vl': 0, 4820 'value': [] 4821 }; 4822 byteOffset = this.writeDataElement(seqDelimElement, byteOffset, isImplicit); 4823 } 4824 4825 // return new offset 4826 return byteOffset; 4827 }; 4828 4829 /** 4830 * Is this element an implicit length sequence? 4831 * @param {Object} element The element to check. 4832 * @returns {Boolean} True if it is. 4833 */ 4834 dwv.dicom.isImplicitLengthSequence = function(element) { 4835 // sequence with no length 4836 return (element.vr === "SQ") && 4837 (element.vl === "u/l"); 4838 }; 4839 4840 /** 4841 * Is this element an implicit length item? 4842 * @param {Object} element The element to check. 4843 * @returns {Boolean} True if it is. 4844 */ 4845 dwv.dicom.isImplicitLengthItem = function(element) { 4846 // item with no length 4847 return (element.tag.name === "xFFFEE000") && 4848 (element.vl === "u/l"); 4849 }; 4850 4851 /** 4852 * Is this element an implicit length pixel data? 4853 * @param {Object} element The element to check. 4854 * @returns {Boolean} True if it is. 4855 */ 4856 dwv.dicom.isImplicitLengthPixels = function(element) { 4857 // pixel data with no length 4858 return (element.tag.name === "x7FE00010") && 4859 (element.vl === "u/l"); 4860 }; 4861 4862 /** 4863 * Helper method to flatten an array of typed arrays to 2D typed array 4864 * @param {Array} array of typed arrays 4865 * @returns {Object} a typed array containing all values 4866 */ 4867 dwv.dicom.flattenArrayOfTypedArrays = function(initialArray) { 4868 var initialArrayLength = initialArray.length; 4869 var arrayLength = initialArray[0].length; 4870 var flattenendArrayLength = initialArrayLength * arrayLength; 4871 4872 var flattenedArray = new initialArray[0].constructor(flattenendArrayLength); 4873 4874 for (var i = 0; i < initialArrayLength; i++) { 4875 var indexFlattenedArray = i * arrayLength; 4876 flattenedArray.set(initialArray[i], indexFlattenedArray);
4877 } 4878 return flattenedArray; 4879 }; 4880 4881 /** 4882 * DICOM writer. 4883 * @constructor 4884 */ 4885 dwv.dicom.DicomWriter = function() { 4886 4887 // possible tag actions 4888 var actions = { 4889 'copy': function(item) { return item; }, 4890 'remove': function() { return null; }, 4891 'clear': function(item) { 4892 item.value[0] = ""; 4893 item.vl = 0; 4894 item.endOffset = item.startOffset; 4895 return item; 4896 }, 4897 'replace': function(item, value) { 4898 item.value[0] = value; 4899 item.vl = value.length; 4900 item.endOffset = item.startOffset + value.length; 4901 return item; 4902 } 4903 }; 4904 4905 // default rules: just copy 4906 var defaultRules = { 4907 'default': { action: 'copy', value: null } 4908 }; 4909 4910 /** 4911 * Public (modifiable) rules. 4912 * Set of objects as: 4913 * name : { action: 'actionName', value: 'optionalValue } 4914 * The names are either 'default', tagName or groupName. 4915 * Each DICOM element will be checked to see if a rule is applicable. 4916 * First checked by tagName and then by groupName, 4917 * if nothing is found the default rule is applied. 4918 */ 4919 this.rules = defaultRules; 4920 4921 /** 4922 * Example anonymisation rules. 4923 */ 4924 this.anonymisationRules = { 4925 'default': { action: 'remove', value: null }, 4926 'PatientName': { action: 'replace', value: 'Anonymized' }, // tag 4927 'Meta Element': { action: 'copy', value: null }, // group 'x0002' 4928 'Acquisition': { action: 'copy', value: null }, // group 'x0018' 4929 'Image Presentation': { action: 'copy', value: null }, // group 'x0028' 4930 'Procedure': { action: 'copy', value: null }, // group 'x0040' 4931 'Pixel Data': { action: 'copy', value: null } // group 'x7fe0' 4932 }; 4933 4934 /** 4935 * Get the element to write according to the class rules. 4936 * Priority order: tagName, groupName, default. 4937 * @param {Object} element The element to check 4938 * @return {Object} The element to write, can be null. 4939 */ 4940 this.getElementToWrite = function(element) { 4941 // get group and tag string name 4942 var tagName = null; 4943 var dict = dwv.dicom.dictionary; 4944 var group = element.tag.group; 4945 var groupName = dwv.dicom.TagGroups[group.substr(1)]; // remove first 0 4946 4947 if (typeof dict[group] !== 'undefined' && typeof dict[group][element.tag.element] !== 'undefined') { 4948 tagName = dict[group][element.tag.element][2]; 4949 } 4950 // apply rules: 4951 var rule; 4952 // 1. tag itself 4953 if (typeof this.rules[element.tag.name] !== 'undefined') { 4954 rule = this.rules[element.tag.name]; 4955 } 4956 // 2. tag name 4957 else if (tagName !== null && typeof this.rules[tagName] !== 'undefined') { 4958 rule = this.rules[tagName]; 4959 } 4960 // 3. group name 4961 else if (typeof this.rules[groupName] !== 'undefined') { 4962 rule = this.rules[groupName]; 4963 } 4964 // 4. default 4965 else { 4966 rule = this.rules['default']; 4967 } 4968 // apply action on element and return 4969 return actions[rule.action](element, rule.value); 4970 }; 4971 }; 4972 4973 /** 4974 * Get the ArrayBuffer corresponding to input DICOM elements. 4975 * @param {Array} dicomElements The wrapped elements to write. 4976 * @returns {ArrayBuffer} The elements as a buffer. 4977 */ 4978 dwv.dicom.DicomWriter.prototype.getBuffer = function(dicomElements) { 4979 // array keys 4980 var keys = Object.keys(dicomElements); 4981 4982 // transfer syntax 4983 var syntax = dwv.dicom.cleanString(dicomElements.x00020010.value[0]); 4984 var isImplicit = dwv.dicom.isImplicitTransferSyntax(syntax); 4985 var isBigEndian = dwv.dicom.isBigEndianTransferSyntax(syntax); 4986 4987 // calculate buffer size and split elements (meta and non meta) 4988 var totalSize = 128 + 4; // DICM 4989 var localSize = 0; 4990 var metaElements = []; 4991 var rawElements = []; 4992 var element; 4993 var groupName; 4994 var metaLength = 0; 4995 var fmiglTag = dwv.dicom.getFileMetaInformationGroupLengthTag(); 4996 var icUIDTag = new dwv.dicom.Tag("0x0002", "0x0012"); // ImplementationClassUID 4997 var ivnTag = new dwv.dicom.Tag("0x0002", "0x0013"); // ImplementationVersionName 4998 for (var i = 0, leni = keys.length; i < leni; ++i) { 4999 element = this.getElementToWrite(dicomElements[keys[i]]); 5000 if (element !== null && 5001 !fmiglTag.equals2(element.tag) && 5002 !icUIDTag.equals2(element.tag) && 5003 !ivnTag.equals2(element.tag)) { 5004 localSize = 0; 5005 // tag group name 5006 groupName = dwv.dicom.TagGroups[element.tag.group.substr(1)]; // remove first 0 5007 5008 // prefix 5009 if (groupName === 'Meta Element') { 5010 localSize += dwv.dicom.getDataElementPrefixByteSize(element.vr, false); 5011 } else { 5012 localSize += dwv.dicom.getDataElementPrefixByteSize(element.vr, isImplicit); 5013 } 5014 5015 // value 5016 var realVl = element.endOffset - element.startOffset; 5017 localSize += parseInt(realVl, 10); 5018 5019 // add size of sequence delimitation item 5020 if (dwv.dicom.isImplicitLengthSequence(element) || 5021 dwv.dicom.isImplicitLengthPixels(element)) { 5022 localSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit); 5023 } 5024 5025 // sort elements 5026 if (groupName === 'Meta Element') { 5027 metaElements.push(element); 5028 metaLength += localSize; 5029 } else { 5030 rawElements.push(element); 5031 } 5032 5033 // add to total size 5034 totalSize += localSize; 5035 } 5036 } 5037 5038 // ImplementationClassUID 5039 var icUID = dwv.dicom.getDicomElement("ImplementationClassUID"); 5040 var icUIDSize = dwv.dicom.getDataElementPrefixByteSize(icUID.vr, isImplicit); 5041 icUIDSize += dwv.dicom.setElementValue(icUID, "1.2.826.0.1.3680043.9.7278.1." + dwv.getVersion(), false); 5042 metaElements.push(icUID); 5043 metaLength += icUIDSize; 5044 totalSize += icUIDSize; 5045 // ImplementationVersionName 5046 var ivn = dwv.dicom.getDicomElement("ImplementationVersionName"); 5047 var ivnSize = dwv.dicom.getDataElementPrefixByteSize(ivn.vr, isImplicit); 5048 ivnSize += dwv.dicom.setElementValue(ivn, "DWV_" + dwv.getVersion(), false);
5049 metaElements.push(ivn); 5050 metaLength += ivnSize; 5051 totalSize += ivnSize; 5052 5053 // create the FileMetaInformationGroupLength element 5054 var fmigl = dwv.dicom.getDicomElement("FileMetaInformationGroupLength"); 5055 var fmiglSize = dwv.dicom.getDataElementPrefixByteSize(fmigl.vr, isImplicit); 5056 fmiglSize += dwv.dicom.setElementValue(fmigl, metaLength, false); 5057 5058 // add its size to the total one 5059 totalSize += fmiglSize; 5060 5061 // create buffer 5062 var buffer = new ArrayBuffer(totalSize); 5063 var metaWriter = new dwv.dicom.DataWriter(buffer); 5064 var dataWriter = new dwv.dicom.DataWriter(buffer, !isBigEndian); 5065 var offset = 128; 5066 // DICM 5067 offset = metaWriter.writeString(offset, "DICM"); 5068 // FileMetaInformationGroupLength 5069 offset = metaWriter.writeDataElement(fmigl, offset, false); 5070 // write meta 5071 for (var j = 0, lenj = metaElements.length; j < lenj; ++j) { 5072 offset = metaWriter.writeDataElement(metaElements[j], offset, false); 5073 } 5074 // write non meta 5075 for (var k = 0, lenk = rawElements.length; k < lenk; ++k) { 5076 offset = dataWriter.writeDataElement(rawElements[k], offset, isImplicit); 5077 } 5078 5079 // return 5080 return buffer; 5081 }; 5082 5083 /** 5084 * Get a DICOM element from its tag name (value set separatly). 5085 * @param {String} tagName The string tag name. 5086 * @return {Object} The DICOM element. 5087 */ 5088 dwv.dicom.getDicomElement = function(tagName) { 5089 var tagGE = dwv.dicom.getGroupElementFromName(tagName); 5090 var dict = dwv.dicom.dictionary; 5091 // return element definition 5092 return { 5093 'tag': { 'group': tagGE.group, 'element': tagGE.element }, 5094 'vr': dict[tagGE.group][tagGE.element][0], 5095 'vl': dict[tagGE.group][tagGE.element][1] 5096 }; 5097 }; 5098 5099 /** 5100 * Set a DICOM element value according to its VR (Value Representation). 5101 * @param {Object} element The DICOM element to set the value. 5102 * @param {Object} value The value to set. 5103 * @param {Boolean} isImplicit Does the data use implicit VR? 5104 * @return {Number} The total element size. 5105 */ 5106 dwv.dicom.setElementValue = function(element, value, isImplicit) { 5107 // byte size of the element 5108 var size = 0; 5109 // special sequence case 5110 if (element.vr === "SQ") { 5111 5112 // set the value 5113 element.value = value; 5114 element.vl = 0; 5115 5116 if (value !== null && value !== 0) { 5117 var sqItems = []; 5118 var name; 5119 5120 // explicit or implicit length 5121 var explicitLength = true; 5122 if (typeof value.explicitLength !== "undefined") { 5123 explicitLength = value.explicitLength; 5124 delete value.explicitLength; 5125 } 5126 5127 // items 5128 var itemData; 5129 var itemKeys = Object.keys(value); 5130 for (var i = 0, leni = itemKeys.length; i < leni; ++i) { 5131 var itemElements = {}; 5132 var subSize = 0; 5133 itemData = value[itemKeys[i]]; 5134 5135 // check data 5136 if (itemData === null || itemData === 0) { 5137 continue; 5138 } 5139 5140 // elements 5141 var subElement; 5142 var elemKeys = Object.keys(itemData); 5143 for (var j = 0, lenj = elemKeys.length; j < lenj; ++j) { 5144 subElement = dwv.dicom.getDicomElement(elemKeys[j]); 5145 subSize += dwv.dicom.setElementValue(subElement, itemData[elemKeys[j]]); 5146 5147 // add sequence delimitation size for sub sequences 5148 if (dwv.dicom.isImplicitLengthSequence(subElement)) { 5149 subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit); 5150 } 5151 5152 name = dwv.dicom.getGroupElementKey(subElement.tag.group, subElement.tag.element); 5153 itemElements[name] = subElement; 5154 subSize += dwv.dicom.getDataElementPrefixByteSize(subElement.vr, isImplicit); 5155 } 5156 5157 // item (after elements to get the size) 5158 var itemElement = { 5159 'tag': { 'group': "0xFFFE", 'element': "0xE000" }, 5160 'vr': "NONE", 5161 'vl': (explicitLength ? subSize : "u/l"), 5162 'value': [] 5163 }; 5164 name = dwv.dicom.getGroupElementKey(itemElement.tag.group, itemElement.tag.element); 5165 itemElements[name] = itemElement; 5166 subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit); 5167 5168 // item delimitation 5169 if (!explicitLength) { 5170 var itemDelimElement = { 5171 'tag': { 'group': "0xFFFE", 'element': "0xE00D" }, 5172 'vr': "NONE", 5173 'vl': 0, 5174 'value': [] 5175 }; 5176 name = dwv.dicom.getGroupElementKey(itemDelimElement.tag.group, itemDelimElement.tag.element); 5177 itemElements[name] = itemDelimElement; 5178 subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit); 5179 } 5180 5181 size += subSize; 5182 sqItems.push(itemElements); 5183 } 5184 5185 element.value = sqItems; 5186 if (explicitLength) { 5187 element.vl = size; 5188 } else { 5189 element.vl = "u/l"; 5190 } 5191 } 5192 } else { 5193 // set the value and calculate size 5194 size = 0; 5195 if (value instanceof Array) { 5196 element.value = value; 5197 for (var k = 0; k < value.length; ++k) { 5198 // spearator 5199 if (k !== 0) { 5200 size += 1; 5201 } 5202 // value 5203 size += value[k].toString().length; 5204 } 5205 } else { 5206 element.value = [value]; 5207 if (typeof value !== "undefined" && typeof value.length !== "undefined") { 5208 size = value.length; 5209 } else { 5210 // numbers 5211 size = 1; 5212 } 5213 } 5214 5215 // convert size to bytes 5216 if (element.vr === "US" || element.vr === "OW") { 5217 size *= Uint16Array.BYTES_PER_ELEMENT; 5218 } else if (element.vr === "SS") { 5219 size *= Int16Array.BYTES_PER_ELEMENT; 5220 } else if (element.vr === "UL") { 5221 size *= Uint32Array.BYTES_PER_ELEMENT; 5222 } else if (element.vr === "SL") { 5223 size *= Int32Array.BYTES_PER_ELEMENT; 5224 } else if (element.vr === "FL") { 5225 size *= Float32Array.BYTES_PER_ELEMENT; 5226 } else if (element.vr === "FD") { 5227 size *= Float64Array.BYTES_PER_ELEMENT; 5228 } else { 5229 size *= Uint8Array.BYTES_PER_ELEMENT; 5230 } 5231 element.vl = size; 5232 } 5233 5234 // return the size of that data 5235 return size; 5236 }; 5237 5238 /** 5239 * Get the DICOM element from a DICOM tags object. 5240 * @param {Object} tags The DICOM tags object. 5241 * @return {Object} The DICOM elements and the end offset. 5242 */ 5243 dwv.dicom.getElementsFromJSONTags = function(tags) { 5244 // transfer syntax 5245 var isImplicit = dwv.dicom.isImplicitTransferSyntax(tags.TransferSyntaxUID); 5246 // convert JSON to DICOM element object 5247 var keys = Object.keys(tags); 5248 var dicomElements = {}; 5249 var dicomElement; 5250 var name; 5251 var offset = 128 + 4; // preamble 5252 var size; 5253 for (var k = 0, len = keys.length; k < len; ++k) { 5254 // get the DICOM element definition from its name 5255 dicomElement = dwv.dicom.getDicomElement(keys[k]); 5256 // set its value 5257 size = dwv.dicom.setElementValue(dicomElement, tags[keys[k]], isImplicit); 5258 // set offsets 5259 offset += dwv.dicom.getDataElementPrefixByteSize(dicomElement.vr, isImplicit); 5260 dicomElement.startOffset = offset; 5261 offset += size; 5262 dicomElement.endOffset = offset; 5263 // create the tag group/element key 5264 name = dwv.dicom.getGroupElementKey(dicomElement.tag.group, dicomElement.tag.element); 5265 // store 5266 dicomElements[name] = dicomElement; 5267 } 5268 // return 5269 return { 'elements': dicomElements, 'offset': offset }; 5270 }; 5271 5272 /** 5273 * GradSquarePixGenerator 5274 * Generates a small gradient square. 5275 * @param {Number} numberOfColumns The image number of columns. 5276 * @param {Number} numberOfRows The image number of rows. 5277 * @constructor 5278 */ 5279 var GradSquarePixGenerator = function(numberOfColumns, numberOfRows) { 5280 5281 var halfCols = numberOfColumns * 0.5; 5282 var halfRows = numberOfRows * 0.5; 5283 var maxNoBounds = (numberOfColumns / 2 + halfCols / 2) * (numberOfRows / 2 + halfRows / 2); 5284 var max = 100; 5285 5286 /** 5287 * Get a grey value. 5288 * @param {Number} i The column index. 5289 * @param {Number} j The row index. 5290 * @return {Array} The grey value. 5291 */ 5292 this.getGrey = function(i, j) { 5293 var value = max; 5294 var jc = Math.abs(j - halfRows);
5295 var ic = Math.abs(i - halfCols); 5296 if (jc < halfRows / 2 && ic < halfCols / 2) { 5297 value += (i * j) * (max / maxNoBounds); 5298 } 5299 return [value]; 5300 }; 5301 5302 /** 5303 * Get RGB values. 5304 * @param {Number} i The column index. 5305 * @param {Number} j The row index. 5306 * @return {Array} The [R,G,B] values. 5307 */ 5308 this.getRGB = function(i, j) { 5309 var value = 0; 5310 var jc = Math.abs(j - halfRows); 5311 var ic = Math.abs(i - halfCols); 5312 if (jc < halfRows / 2 && ic < halfCols / 2) { 5313 value += (i * j) * (max / maxNoBounds); 5314 } 5315 if (value > 255) { 5316 value = 200; 5317 } 5318 return [0, value, value]; 5319 }; 5320 }; 5321 5322 // List of pixel generators. 5323 dwv.dicom.pixelGenerators = { 5324 'gradSquare': GradSquarePixGenerator 5325 }; 5326 5327 /** 5328 * Get the DICOM pixel data from a DICOM tags object. 5329 * @param {Object} tags The DICOM tags object. 5330 * @param {Object} startOffset The start offset of the pixel data. 5331 * @param {String} pixGeneratorName The name of a pixel generator. 5332 * @return {Object} The DICOM pixel data element. 5333 */ 5334 dwv.dicom.generatePixelDataFromJSONTags = function(tags, startOffset, pixGeneratorName) { 5335 5336 // default generator 5337 if (typeof pixGeneratorName === "undefined") { 5338 pixGeneratorName = "gradSquare"; 5339 } 5340 5341 // check tags 5342 if (typeof tags.TransferSyntaxUID === "undefined") { 5343 throw new Error("Missing transfer syntax for pixel generation."); 5344 } else if (typeof tags.Rows === "undefined") { 5345 throw new Error("Missing number of rows for pixel generation."); 5346 } else if (typeof tags.Columns === "undefined") { 5347 throw new Error("Missing number of columns for pixel generation."); 5348 } else if (typeof tags.BitsAllocated === "undefined") { 5349 throw new Error("Missing BitsAllocated for pixel generation."); 5350 } else if (typeof tags.PixelRepresentation === "undefined") { 5351 throw new Error("Missing PixelRepresentation for pixel generation."); 5352 } else if (typeof tags.SamplesPerPixel === "undefined") { 5353 throw new Error("Missing SamplesPerPixel for pixel generation."); 5354 } else if (typeof tags.PhotometricInterpretation === "undefined") { 5355 throw new Error("Missing PhotometricInterpretation for pixel generation."); 5356 } 5357 5358 // extract info from tags 5359 var isImplicit = dwv.dicom.isImplicitTransferSyntax(tags.TransferSyntaxUID); 5360 var numberOfRows = tags.Rows; 5361 var numberOfColumns = tags.Columns; 5362 var bitsAllocated = tags.BitsAllocated; 5363 var pixelRepresentation = tags.PixelRepresentation; 5364 var samplesPerPixel = tags.SamplesPerPixel; 5365 var photometricInterpretation = tags.PhotometricInterpretation; 5366 5367 var sliceLength = numberOfRows * numberOfColumns; 5368 var dataLength = sliceLength * samplesPerPixel; 5369 5370 // check values 5371 if (samplesPerPixel !== 1 && samplesPerPixel !== 3) { 5372 throw new Error("Unsupported SamplesPerPixel for pixel generation: " + samplesPerPixel); 5373 } 5374 if ((samplesPerPixel === 1 && !(photometricInterpretation === "MONOCHROME1" || 5375 photometricInterpretation === "MONOCHROME2")) || 5376 (samplesPerPixel === 3 && photometricInterpretation !== "RGB")) { 5377 throw new Error("Unsupported PhotometricInterpretation for pixel generation: " + 5378 photometricInterpretation + " with SamplesPerPixel: " + samplesPerPixel); 5379 } 5380 5381 var nSamples = 1; 5382 var nColourPlanes = 1; 5383 if (samplesPerPixel === 3) { 5384 if (typeof tags.PlanarConfiguration === "undefined") { 5385 throw new Error("Missing PlanarConfiguration for pixel generation."); 5386 } 5387 var planarConfiguration = tags.PlanarConfiguration; 5388 if (planarConfiguration !== 0 && planarConfiguration !== 1) { 5389 throw new Error("Unsupported PlanarConfiguration for pixel generation: " + planarConfiguration); 5390 } 5391 if (planarConfiguration === 0) { 5392 nSamples = 3; 5393 } else { 5394 nColourPlanes = 3; 5395 } 5396 } 5397 5398 // create pixel array 5399 var pixels = dwv.dicom.getTypedArray( 5400 bitsAllocated, pixelRepresentation, dataLength); 5401 5402 // pixels generator 5403 if (typeof dwv.dicom.pixelGenerators[pixGeneratorName] === "undefined") { 5404 throw new Error("Unknown PixelData generator: " + pixGeneratorName); 5405 } 5406 var generator = new dwv.dicom.pixelGenerators[pixGeneratorName](numberOfColumns, numberOfRows);
5407 var generate = generator.getGrey; 5408 if (photometricInterpretation === "RGB") { 5409 generate = generator.getRGB; 5410 } 5411 5412 // main loop 5413 var offset = 0; 5414 for (var c = 0; c < nColourPlanes; ++c) { 5415 for (var j = 0; j < numberOfRows; ++j) { 5416 for (var i = 0; i < numberOfColumns; ++i) { 5417 for (var s = 0; s < nSamples; ++s) { 5418 if (nColourPlanes !== 1) { 5419 pixels[offset] = generate(i, j)[c]; 5420 } else { 5421 pixels[offset] = generate(i, j)[s]; 5422 } 5423 ++offset; 5424 } 5425 } 5426 } 5427 } 5428 5429 // create and return the DICOM element 5430 var vr = "OW"; 5431 if (bitsAllocated === 8) { 5432 vr = "OB"; 5433 } 5434 var pixVL = dwv.dicom.getDataElementPrefixByteSize(vr, isImplicit) + 5435 (pixels.BYTES_PER_ELEMENT * dataLength); 5436 return { 5437 'tag': { 'group': "0x7FE0", 'element': "0x0010" }, 5438 'vr': vr, 5439 'vl': pixVL, 5440 'value': pixels, 5441 'startOffset': startOffset, 5442 'endOffset': startOffset + pixVL 5443 }; 5444 }; 5445 5446 // namespaces 5447 var dwv = dwv || {}; 5448 dwv.dicom = dwv.dicom || {}; 5449 5450 /** 5451 * DICOM tag dictionary. 5452 * Generated using xml standard conversion 5453 * from {@link https://github.com/ivmartel/dcmbench/tree/master/view/part06} 5454 * with {@link http://medical.nema.org/medical/dicom/current/source/docbook/part06/part06.xml} 5455 * Conversion changes: 5456 * - (vr) "See Note" -> "NONE", "OB or OW" -> "ox", "US or SS" -> "xs" 5457 * - added "GenericGroupLength" element to each group 5458 * Local changes: 5459 * - tag numbers with 'xx' were replaced with '00', 'xxx' with '001' and 'xxxx' with '0004' 5460 */ 5461 dwv.dicom.dictionary = { 5462 '0x0000': { 5463 '0x0000': ['UL', '1', 'GroupLength'], 5464 '0x0001': ['UL', '1', 'CommandLengthToEnd'], 5465 '0x0002': ['UI', '1', 'AffectedSOPClassUID'], 5466 '0x0003': ['UI', '1', 'RequestedSOPClassUID'], 5467 '0x0010': ['CS', '1', 'CommandRecognitionCode'], 5468 '0x0100': ['US', '1', 'CommandField'], 5469 '0x0110': ['US', '1', 'MessageID'], 5470 '0x0120': ['US', '1', 'MessageIDBeingRespondedTo'], 5471 '0x0200': ['AE', '1', 'Initiator'], 5472 '0x0300': ['AE', '1', 'Receiver'], 5473 '0x0400': ['AE', '1', 'FindLocation'], 5474 '0x0600': ['AE', '1', 'MoveDestination'], 5475 '0x0700': ['US', '1', 'Priority'], 5476 '0x0800': ['US', '1', 'DataSetType'], 5477 '0x0850': ['US', '1', 'NumberOfMatches'], 5478 '0x0860': ['US', '1', 'ResponseSequenceNumber'], 5479 '0x0900': ['US', '1', 'Status'], 5480 '0x0901': ['AT', '1-n', 'OffendingElement'], 5481 '0x0902': ['LO', '1', 'ErrorComment'], 5482 '0x0903': ['US', '1', 'ErrorID'], 5483 '0x0904': ['OT', '1-n', 'ErrorInformation'], 5484 '0x1000': ['UI', '1', 'AffectedSOPInstanceUID'], 5485 '0x1001': ['UI', '1', 'RequestedSOPInstanceUID'], 5486 '0x1002': ['US', '1', 'EventTypeID'], 5487 '0x1003': ['OT', '1-n', 'EventInformation'], 5488 '0x1005': ['AT', '1-n', 'AttributeIdentifierList'], 5489 '0x1007': ['AT', '1-n', 'ModificationList'], 5490 '0x1008': ['US', '1', 'ActionTypeID'], 5491 '0x1009': ['OT', '1-n', 'ActionInformation'], 5492 '0x1013': ['UI', '1-n', 'SuccessfulSOPInstanceUIDList'], 5493 '0x1014': ['UI', '1-n', 'FailedSOPInstanceUIDList'], 5494 '0x1015': ['UI', '1-n', 'WarningSOPInstanceUIDList'], 5495 '0x1020': ['US', '1', 'NumberOfRemainingSuboperations'], 5496 '0x1021': ['US', '1', 'NumberOfCompletedSuboperations'], 5497 '0x1022': ['US', '1', 'NumberOfFailedSuboperations'], 5498 '0x1023': ['US', '1', 'NumberOfWarningSuboperations'], 5499 '0x1030': ['AE', '1', 'MoveOriginatorApplicationEntityTitle'], 5500 '0x1031': ['US', '1', 'MoveOriginatorMessageID'], 5501 '0x4000': ['AT', '1', 'DialogReceiver'], 5502 '0x4010': ['AT', '1', 'TerminalType'], 5503 '0x5010': ['SH', '1', 'MessageSetID'], 5504 '0x5020': ['SH', '1', 'EndMessageSet'], 5505 '0x5110': ['AT', '1', 'DisplayFormat'], 5506 '0x5120': ['AT', '1', 'PagePositionID'], 5507 '0x5130': ['CS', '1', 'TextFormatID'], 5508 '0x5140': ['CS', '1', 'NormalReverse'], 5509 '0x5150': ['CS', '1', 'AddGrayScale'], 5510 '0x5160': ['CS', '1', 'Borders'], 5511 '0x5170': ['IS', '1', 'Copies'], 5512 '0x5180': ['CS', '1', 'OldMagnificationType'], 5513 '0x5190': ['CS', '1', 'Erase'], 5514 '0x51A0': ['CS', '1', 'Print'], 5515 '0x51B0': ['US', '1-n', 'Overlays'] 5516 }, 5517 '0x0002': { 5518 '0x0000': ['UL', '1', 'FileMetaInformationGroupLength'], 5519 '0x0001': ['OB', '1', 'FileMetaInformationVersion'], 5520 '0x0002': ['UI', '1', 'MediaStorageSOPClassUID'], 5521 '0x0003': ['UI', '1', 'MediaStorageSOPInstanceUID'], 5522 '0x0010': ['UI', '1', 'TransferSyntaxUID'], 5523 '0x0012': ['UI', '1', 'ImplementationClassUID'], 5524 '0x0013': ['SH', '1', 'ImplementationVersionName'], 5525 '0x0016': ['AE', '1', 'SourceApplicationEntityTitle'], 5526 '0x0017': ['AE', '1', 'SendingApplicationEntityTitle'], 5527 '0x0018': ['AE', '1', 'ReceivingApplicationEntityTitle'], 5528 '0x0100': ['UI', '1', 'PrivateInformationCreatorUID'], 5529 '0x0102': ['OB', '1', 'PrivateInformation'] 5530 }, 5531 '0x0004': { 5532 '0x0000': ['UL', '1', 'GenericGroupLength'], 5533 '0x1130': ['CS', '1', 'FileSetID'], 5534 '0x1141': ['CS', '1-8', 'FileSetDescriptorFileID'], 5535 '0x1142': ['CS', '1', 'SpecificCharacterSetOfFileSetDescriptorFile'], 5536 '0x1200': ['UL', '1', 'OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity'], 5537 '0x1202': ['UL', '1', 'OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity'], 5538 '0x1212': ['US', '1', 'FileSetConsistencyFlag'], 5539 '0x1220': ['SQ', '1', 'DirectoryRecordSequence'], 5540 '0x1400': ['UL', '1', 'OffsetOfTheNextDirectoryRecord'], 5541 '0x1410': ['US', '1', 'RecordInUseFlag'], 5542 '0x1420': ['UL', '1', 'OffsetOfReferencedLowerLevelDirectoryEntity'], 5543 '0x1430': ['CS', '1', 'DirectoryRecordType'], 5544 '0x1432': ['UI', '1', 'PrivateRecordUID'], 5545 '0x1500': ['CS', '1-8', 'ReferencedFileID'], 5546 '0x1504': ['UL', '1', 'MRDRDirectoryRecordOffset'], 5547 '0x1510': ['UI', '1', 'ReferencedSOPClassUIDInFile'], 5548 '0x1511': ['UI', '1', 'ReferencedSOPInstanceUIDInFile'], 5549 '0x1512': ['UI', '1', 'ReferencedTransferSyntaxUIDInFile'], 5550 '0x151A': ['UI', '1-n', 'ReferencedRelatedGeneralSOPClassUIDInFile'], 5551 '0x1600': ['UL', '1', 'NumberOfReferences'] 5552 }, 5553 '0x0008': { 5554 '0x0000': ['UL', '1', 'GenericGroupLength'], 5555 '0x0001': ['UL', '1', 'LengthToEnd'], 5556 '0x0005': ['CS', '1-n', 'SpecificCharacterSet'], 5557 '0x0006': ['SQ', '1', 'LanguageCodeSequence'], 5558 '0x0008': ['CS', '2-n', 'ImageType'], 5559 '0x0010': ['SH', '1', 'RecognitionCode'], 5560 '0x0012': ['DA', '1', 'InstanceCreationDate'], 5561 '0x0013': ['TM', '1', 'InstanceCreationTime'], 5562 '0x0014': ['UI', '1', 'InstanceCreatorUID'], 5563 '0x0015': ['DT', '1', 'InstanceCoercionDateTime'], 5564 '0x0016': ['UI', '1', 'SOPClassUID'], 5565 '0x0018': ['UI', '1', 'SOPInstanceUID'], 5566 '0x001A': ['UI', '1-n', 'RelatedGeneralSOPClassUID'], 5567 '0x001B': ['UI', '1', 'OriginalSpecializedSOPClassUID'], 5568 '0x0020': ['DA', '1', 'StudyDate'], 5569 '0x0021': ['DA', '1', 'SeriesDate'], 5570 '0x0022': ['DA', '1', 'AcquisitionDate'], 5571 '0x0023': ['DA', '1', 'ContentDate'], 5572 '0x0024': ['DA', '1', 'OverlayDate'], 5573 '0x0025': ['DA', '1', 'CurveDate'], 5574 '0x002A': ['DT', '1', 'AcquisitionDateTime'], 5575 '0x0030': ['TM', '1', 'StudyTime'], 5576 '0x0031': ['TM', '1', 'SeriesTime'], 5577 '0x0032': ['TM', '1', 'AcquisitionTime'], 5578 '0x0033': ['TM', '1', 'ContentTime'], 5579 '0x0034': ['TM', '1', 'OverlayTime'], 5580 '0x0035': ['TM', '1', 'CurveTime'], 5581 '0x0040': ['US', '1', 'DataSetType'], 5582 '0x0041': ['LO', '1', 'DataSetSubtype'], 5583 '0x0042': ['CS', '1', 'NuclearMedicineSeriesType'], 5584 '0x0050': ['SH', '1', 'AccessionNumber'], 5585 '0x0051': ['SQ', '1', 'IssuerOfAccessionNumberSequence'], 5586 '0x0052': ['CS', '1', 'QueryRetrieveLevel'], 5587 '0x0053': ['CS', '1', 'QueryRetrieveView'], 5588 '0x0054': ['AE', '1-n', 'RetrieveAETitle'], 5589 '0x0056': ['CS', '1', 'InstanceAvailability'], 5590 '0x0058': ['UI', '1-n', 'FailedSOPInstanceUIDList'], 5591 '0x0060': ['CS', '1', 'Modality'], 5592 '0x0061': ['CS', '1-n', 'ModalitiesInStudy'], 5593 '0x0062': ['UI', '1-n', 'SOPClassesInStudy'], 5594 '0x0064': ['CS', '1', 'ConversionType'], 5595 '0x0068': ['CS', '1', 'PresentationIntentType'], 5596 '0x0070': ['LO', '1', 'Manufacturer'], 5597 '0x0080': ['LO', '1', 'InstitutionName'], 5598 '0x0081': ['ST', '1', 'InstitutionAddress'], 5599 '0x0082': ['SQ', '1', 'InstitutionCodeSequence'], 5600 '0x0090': ['PN', '1', 'ReferringPhysicianName'], 5601 '0x0092': ['ST', '1', 'ReferringPhysicianAddress'], 5602 '0x0094': ['SH', '1-n', 'ReferringPhysicianTelephoneNumbers'], 5603 '0x0096': ['SQ', '1', 'ReferringPhysicianIdentificationSequence'], 5604 '0x009C': ['PN', '1-n', 'ConsultingPhysicianName'], 5605 '0x009D': ['SQ', '1', 'ConsultingPhysicianIdentificationSequence'], 5606 '0x0100': ['SH', '1', 'CodeValue'], 5607 '0x0101': ['LO', '1', 'ExtendedCodeValue'], 5608 '0x0102': ['SH', '1', 'CodingSchemeDesignator'], 5609 '0x0103': ['SH', '1', 'CodingSchemeVersion'], 5610 '0x0104': ['LO', '1', 'CodeMeaning'], 5611 '0x0105': ['CS', '1', 'MappingResource'], 5612 '0x0106': ['DT', '1', 'ContextGroupVersion'], 5613 '0x0107': ['DT', '1', 'ContextGroupLocalVersion'], 5614 '0x0108': ['LT', '1', 'ExtendedCodeMeaning'], 5615 '0x010B': ['CS', '1', 'ContextGroupExtensionFlag'], 5616 '0x010C': ['UI', '1', 'CodingSchemeUID'], 5617 '0x010D': ['UI', '1', 'ContextGroupExtensionCreatorUID'], 5618 '0x010F': ['CS', '1', 'ContextIdentifier'], 5619 '0x0110': ['SQ', '1', 'CodingSchemeIdentificationSequence'], 5620 '0x0112': ['LO', '1', 'CodingSchemeRegistry'],
5621 '0x0114': ['ST', '1', 'CodingSchemeExternalID'], 5622 '0x0115': ['ST', '1', 'CodingSchemeName'], 5623 '0x0116': ['ST', '1', 'CodingSchemeResponsibleOrganization'], 5624 '0x0117': ['UI', '1', 'ContextUID'], 5625 '0x0118': ['UI', '1', 'MappingResourceUID'], 5626 '0x0119': ['UC', '1', 'LongCodeValue'], 5627 '0x0120': ['UR', '1', 'URNCodeValue'], 5628 '0x0121': ['SQ', '1', 'EquivalentCodeSequence'], 5629 '0x0201': ['SH', '1', 'TimezoneOffsetFromUTC'], 5630 '0x0300': ['SQ', '1', 'PrivateDataElementCharacteristicsSequence'], 5631 '0x0301': ['US', '1', 'PrivateGroupReference'], 5632 '0x0302': ['LO', '1', 'PrivateCreatorReference'], 5633 '0x0303': ['CS', '1', 'BlockIdentifyingInformationStatus'], 5634 '0x0304': ['US', '1-n', 'NonidentifyingPrivateElements'], 5635 '0x0306': ['US', '1-n', 'IdentifyingPrivateElements'], 5636 '0x0305': ['SQ', '1', 'DeidentificationActionSequence'], 5637 '0x0307': ['CS', '1', 'DeidentificationAction'], 5638 '0x1000': ['AE', '1', 'NetworkID'], 5639 '0x1010': ['SH', '1', 'StationName'], 5640 '0x1030': ['LO', '1', 'StudyDescription'], 5641 '0x1032': ['SQ', '1', 'ProcedureCodeSequence'], 5642 '0x103E': ['LO', '1', 'SeriesDescription'], 5643 '0x103F': ['SQ', '1', 'SeriesDescriptionCodeSequence'], 5644 '0x1040': ['LO', '1', 'InstitutionalDepartmentName'], 5645 '0x1048': ['PN', '1-n', 'PhysiciansOfRecord'], 5646 '0x1049': ['SQ', '1', 'PhysiciansOfRecordIdentificationSequence'], 5647 '0x1050': ['PN', '1-n', 'PerformingPhysicianName'], 5648 '0x1052': ['SQ', '1', 'PerformingPhysicianIdentificationSequence'], 5649 '0x1060': ['PN', '1-n', 'NameOfPhysiciansReadingStudy'], 5650 '0x1062': ['SQ', '1', 'PhysiciansReadingStudyIdentificationSequence'], 5651 '0x1070': ['PN', '1-n', 'OperatorsName'], 5652 '0x1072': ['SQ', '1', 'OperatorIdentificationSequence'], 5653 '0x1080': ['LO', '1-n', 'AdmittingDiagnosesDescription'], 5654 '0x1084': ['SQ', '1', 'AdmittingDiagnosesCodeSequence'], 5655 '0x1090': ['LO', '1', 'ManufacturerModelName'], 5656 '0x1100': ['SQ', '1', 'ReferencedResultsSequence'], 5657 '0x1110': ['SQ', '1', 'ReferencedStudySequence'], 5658 '0x1111': ['SQ', '1', 'ReferencedPerformedProcedureStepSequence'], 5659 '0x1115': ['SQ', '1', 'ReferencedSeriesSequence'], 5660 '0x1120': ['SQ', '1', 'ReferencedPatientSequence'], 5661 '0x1125': ['SQ', '1', 'ReferencedVisitSequence'], 5662 '0x1130': ['SQ', '1', 'ReferencedOverlaySequence'], 5663 '0x1134': ['SQ', '1', 'ReferencedStereometricInstanceSequence'], 5664 '0x113A': ['SQ', '1', 'ReferencedWaveformSequence'], 5665 '0x1140': ['SQ', '1', 'ReferencedImageSequence'], 5666 '0x1145': ['SQ', '1', 'ReferencedCurveSequence'], 5667 '0x114A': ['SQ', '1', 'ReferencedInstanceSequence'], 5668 '0x114B': ['SQ', '1', 'ReferencedRealWorldValueMappingInstanceSequence'], 5669 '0x1150': ['UI', '1', 'ReferencedSOPClassUID'], 5670 '0x1155': ['UI', '1', 'ReferencedSOPInstanceUID'], 5671 '0x115A': ['UI', '1-n', 'SOPClassesSupported'], 5672 '0x1160': ['IS', '1-n', 'ReferencedFrameNumber'], 5673 '0x1161': ['UL', '1-n', 'SimpleFrameList'], 5674 '0x1162': ['UL', '3-3n', 'CalculatedFrameList'], 5675 '0x1163': ['FD', '2', 'TimeRange'], 5676 '0x1164': ['SQ', '1', 'FrameExtractionSequence'], 5677 '0x1167': ['UI', '1', 'MultiFrameSourceSOPInstanceUID'], 5678 '0x1190': ['UR', '1', 'RetrieveURL'], 5679 '0x1195': ['UI', '1', 'TransactionUID'], 5680 '0x1196': ['US', '1', 'WarningReason'], 5681 '0x1197': ['US', '1', 'FailureReason'], 5682 '0x1198': ['SQ', '1', 'FailedSOPSequence'], 5683 '0x1199': ['SQ', '1', 'ReferencedSOPSequence'], 5684 '0x1200': ['SQ', '1', 'StudiesContainingOtherReferencedInstancesSequence'], 5685 '0x1250': ['SQ', '1', 'RelatedSeriesSequence'], 5686 '0x2110': ['CS', '1', 'LossyImageCompressionRetired'], 5687 '0x2111': ['ST', '1', 'DerivationDescription'], 5688 '0x2112': ['SQ', '1', 'SourceImageSequence'], 5689 '0x2120': ['SH', '1', 'StageName'], 5690 '0x2122': ['IS', '1', 'StageNumber'], 5691 '0x2124': ['IS', '1', 'NumberOfStages'], 5692 '0x2127': ['SH', '1', 'ViewName'], 5693 '0x2128': ['IS', '1', 'ViewNumber'], 5694 '0x2129': ['IS', '1', 'NumberOfEventTimers'], 5695 '0x212A': ['IS', '1', 'NumberOfViewsInStage'], 5696 '0x2130': ['DS', '1-n', 'EventElapsedTimes'], 5697 '0x2132': ['LO', '1-n', 'EventTimerNames'], 5698 '0x2133': ['SQ', '1', 'EventTimerSequence'],
5699 '0x2134': ['FD', '1', 'EventTimeOffset'], 5700 '0x2135': ['SQ', '1', 'EventCodeSequence'], 5701 '0x2142': ['IS', '1', 'StartTrim'], 5702 '0x2143': ['IS', '1', 'StopTrim'], 5703 '0x2144': ['IS', '1', 'RecommendedDisplayFrameRate'], 5704 '0x2200': ['CS', '1', 'TransducerPosition'], 5705 '0x2204': ['CS', '1', 'TransducerOrientation'], 5706 '0x2208': ['CS', '1', 'AnatomicStructure'], 5707 '0x2218': ['SQ', '1', 'AnatomicRegionSequence'], 5708 '0x2220': ['SQ', '1', 'AnatomicRegionModifierSequence'], 5709 '0x2228': ['SQ', '1', 'PrimaryAnatomicStructureSequence'], 5710 '0x2229': ['SQ', '1', 'AnatomicStructureSpaceOrRegionSequence'], 5711 '0x2230': ['SQ', '1', 'PrimaryAnatomicStructureModifierSequence'], 5712 '0x2240': ['SQ', '1', 'TransducerPositionSequence'], 5713 '0x2242': ['SQ', '1', 'TransducerPositionModifierSequence'], 5714 '0x2244': ['SQ', '1', 'TransducerOrientationSequence'], 5715 '0x2246': ['SQ', '1', 'TransducerOrientationModifierSequence'], 5716 '0x2251': ['SQ', '1', 'AnatomicStructureSpaceOrRegionCodeSequenceTrial'], 5717 '0x2253': ['SQ', '1', 'AnatomicPortalOfEntranceCodeSequenceTrial'], 5718 '0x2255': ['SQ', '1', 'AnatomicApproachDirectionCodeSequenceTrial'], 5719 '0x2256': ['ST', '1', 'AnatomicPerspectiveDescriptionTrial'], 5720 '0x2257': ['SQ', '1', 'AnatomicPerspectiveCodeSequenceTrial'], 5721 '0x2258': ['ST', '1', 'AnatomicLocationOfExaminingInstrumentDescriptionTrial'], 5722 '0x2259': ['SQ', '1', 'AnatomicLocationOfExaminingInstrumentCodeSequenceTrial'], 5723 '0x225A': ['SQ', '1', 'AnatomicStructureSpaceOrRegionModifierCodeSequenceTrial'], 5724 '0x225C': ['SQ', '1', 'OnAxisBackgroundAnatomicStructureCodeSequenceTrial'], 5725 '0x3001': ['SQ', '1', 'AlternateRepresentationSequence'], 5726 '0x3010': ['UI', '1-n', 'IrradiationEventUID'], 5727 '0x3011': ['SQ', '1', 'SourceIrradiationEventSequence'], 5728 '0x3012': ['UI', '1', 'RadiopharmaceuticalAdministrationEventUID'], 5729 '0x4000': ['LT', '1', 'IdentifyingComments'], 5730 '0x9007': ['CS', '4', 'FrameType'], 5731 '0x9092': ['SQ', '1', 'ReferencedImageEvidenceSequence'], 5732 '0x9121': ['SQ', '1', 'ReferencedRawDataSequence'], 5733 '0x9123': ['UI', '1', 'CreatorVersionUID'], 5734 '0x9124': ['SQ', '1', 'DerivationImageSequence'], 5735 '0x9154': ['SQ', '1', 'SourceImageEvidenceSequence'], 5736 '0x9205': ['CS', '1', 'PixelPresentation'], 5737 '0x9206': ['CS', '1', 'VolumetricProperties'], 5738 '0x9207': ['CS', '1', 'VolumeBasedCalculationTechnique'], 5739 '0x9208': ['CS', '1', 'ComplexImageComponent'], 5740 '0x9209': ['CS', '1', 'AcquisitionContrast'], 5741 '0x9215': ['SQ', '1', 'DerivationCodeSequence'], 5742 '0x9237': ['SQ', '1', 'ReferencedPresentationStateSequence'], 5743 '0x9410': ['SQ', '1', 'ReferencedOtherPlaneSequence'], 5744 '0x9458': ['SQ', '1', 'FrameDisplaySequence'], 5745 '0x9459': ['FL', '1', 'RecommendedDisplayFrameRateInFloat'], 5746 '0x9460': ['CS', '1', 'SkipFrameRangeFlag'] 5747 }, 5748 '0x0010': { 5749 '0x0000': ['UL', '1', 'GenericGroupLength'], 5750 '0x0010': ['PN', '1', 'PatientName'], 5751 '0x0020': ['LO', '1', 'PatientID'], 5752 '0x0021': ['LO', '1', 'IssuerOfPatientID'], 5753 '0x0022': ['CS', '1', 'TypeOfPatientID'], 5754 '0x0024': ['SQ', '1', 'IssuerOfPatientIDQualifiersSequence'], 5755 '0x0030': ['DA', '1', 'PatientBirthDate'], 5756 '0x0032': ['TM', '1', 'PatientBirthTime'], 5757 '0x0040': ['CS', '1', 'PatientSex'], 5758 '0x0050': ['SQ', '1', 'PatientInsurancePlanCodeSequence'], 5759 '0x0101': ['SQ', '1', 'PatientPrimaryLanguageCodeSequence'], 5760 '0x0102': ['SQ', '1', 'PatientPrimaryLanguageModifierCodeSequence'], 5761 '0x0200': ['CS', '1', 'QualityControlSubject'], 5762 '0x0201': ['SQ', '1', 'QualityControlSubjectTypeCodeSequence'], 5763 '0x1000': ['LO', '1-n', 'OtherPatientIDs'], 5764 '0x1001': ['PN', '1-n', 'OtherPatientNames'], 5765 '0x1002': ['SQ', '1', 'OtherPatientIDsSequence'], 5766 '0x1005': ['PN', '1', 'PatientBirthName'], 5767 '0x1010': ['AS', '1', 'PatientAge'], 5768 '0x1020': ['DS', '1', 'PatientSize'], 5769 '0x1021': ['SQ', '1', 'PatientSizeCodeSequence'], 5770 '0x1030': ['DS', '1', 'PatientWeight'], 5771 '0x1040': ['LO', '1', 'PatientAddress'], 5772 '0x1050': ['LO', '1-n', 'InsurancePlanIdentification'], 5773 '0x1060': ['PN', '1', 'PatientMotherBirthName'], 5774 '0x1080': ['LO', '1', 'MilitaryRank'], 5775 '0x1081': ['LO', '1', 'BranchOfService'], 5776 '0x1090': ['LO', '1', 'MedicalRecordLocator'], 5777 '0x1100': ['SQ', '1', 'ReferencedPatientPhotoSequence'], 5778 '0x2000': ['LO', '1-n', 'MedicalAlerts'], 5779 '0x2110': ['LO', '1-n', 'Allergies'], 5780 '0x2150': ['LO', '1', 'CountryOfResidence'], 5781 '0x2152': ['LO', '1', 'RegionOfResidence'], 5782 '0x2154': ['SH', '1-n', 'PatientTelephoneNumbers'], 5783 '0x2155': ['LT', '1', 'PatientTelecomInformation'], 5784 '0x2160': ['SH', '1', 'EthnicGroup'], 5785 '0x2180': ['SH', '1', 'Occupation'], 5786 '0x21A0': ['CS', '1', 'SmokingStatus'], 5787 '0x21B0': ['LT', '1', 'AdditionalPatientHistory'], 5788 '0x21C0': ['US', '1', 'PregnancyStatus'], 5789 '0x21D0': ['DA', '1', 'LastMenstrualDate'], 5790 '0x21F0': ['LO', '1', 'PatientReligiousPreference'], 5791 '0x2201': ['LO', '1', 'PatientSpeciesDescription'], 5792 '0x2202': ['SQ', '1', 'PatientSpeciesCodeSequence'], 5793 '0x2203': ['CS', '1', 'PatientSexNeutered'], 5794 '0x2210': ['CS', '1', 'AnatomicalOrientationType'], 5795 '0x2292': ['LO', '1', 'PatientBreedDescription'], 5796 '0x2293': ['SQ', '1', 'PatientBreedCodeSequence'], 5797 '0x2294': ['SQ', '1', 'BreedRegistrationSequence'], 5798 '0x2295': ['LO', '1', 'BreedRegistrationNumber'], 5799 '0x2296': ['SQ', '1', 'BreedRegistryCodeSequence'], 5800 '0x2297': ['PN', '1', 'ResponsiblePerson'], 5801 '0x2298': ['CS', '1', 'ResponsiblePersonRole'], 5802 '0x2299': ['LO', '1', 'ResponsibleOrganization'], 5803 '0x4000': ['LT', '1', 'PatientComments'], 5804 '0x9431': ['FL', '1', 'ExaminedBodyThickness'] 5805 }, 5806 '0x0012': { 5807 '0x0000': ['UL', '1', 'GenericGroupLength'],
5808 '0x0010': ['LO', '1', 'ClinicalTrialSponsorName'], 5809 '0x0020': ['LO', '1', 'ClinicalTrialProtocolID'], 5810 '0x0021': ['LO', '1', 'ClinicalTrialProtocolName'], 5811 '0x0030': ['LO', '1', 'ClinicalTrialSiteID'], 5812 '0x0031': ['LO', '1', 'ClinicalTrialSiteName'], 5813 '0x0040': ['LO', '1', 'ClinicalTrialSubjectID'], 5814 '0x0042': ['LO', '1', 'ClinicalTrialSubjectReadingID'], 5815 '0x0050': ['LO', '1', 'ClinicalTrialTimePointID'], 5816 '0x0051': ['ST', '1', 'ClinicalTrialTimePointDescription'], 5817 '0x0060': ['LO', '1', 'ClinicalTrialCoordinatingCenterName'], 5818 '0x0062': ['CS', '1', 'PatientIdentityRemoved'], 5819 '0x0063': ['LO', '1-n', 'DeidentificationMethod'], 5820 '0x0064': ['SQ', '1', 'DeidentificationMethodCodeSequence'], 5821 '0x0071': ['LO', '1', 'ClinicalTrialSeriesID'], 5822 '0x0072': ['LO', '1', 'ClinicalTrialSeriesDescription'], 5823 '0x0081': ['LO', '1', 'ClinicalTrialProtocolEthicsCommitteeName'], 5824 '0x0082': ['LO', '1', 'ClinicalTrialProtocolEthicsCommitteeApprovalNumber'], 5825 '0x0083': ['SQ', '1', 'ConsentForClinicalTrialUseSequence'], 5826 '0x0084': ['CS', '1', 'DistributionType'], 5827 '0x0085': ['CS', '1', 'ConsentForDistributionFlag'] 5828 }, 5829 '0x0014': { 5830 '0x0000': ['UL', '1', 'GenericGroupLength'], 5831 '0x0023': ['ST', '1-n', 'CADFileFormat'], 5832 '0x0024': ['ST', '1-n', 'ComponentReferenceSystem'], 5833 '0x0025': ['ST', '1-n', 'ComponentManufacturingProcedure'], 5834 '0x0028': ['ST', '1-n', 'ComponentManufacturer'], 5835 '0x0030': ['DS', '1-n', 'MaterialThickness'], 5836 '0x0032': ['DS', '1-n', 'MaterialPipeDiameter'], 5837 '0x0034': ['DS', '1-n', 'MaterialIsolationDiameter'], 5838 '0x0042': ['ST', '1-n', 'MaterialGrade'], 5839 '0x0044': ['ST', '1-n', 'MaterialPropertiesDescription'], 5840 '0x0045': ['ST', '1-n', 'MaterialPropertiesFileFormatRetired'], 5841 '0x0046': ['LT', '1', 'MaterialNotes'], 5842 '0x0050': ['CS', '1', 'ComponentShape'], 5843 '0x0052': ['CS', '1', 'CurvatureType'], 5844 '0x0054': ['DS', '1', 'OuterDiameter'], 5845 '0x0056': ['DS', '1', 'InnerDiameter'], 5846 '0x1010': ['ST', '1', 'ActualEnvironmentalConditions'], 5847 '0x1020': ['DA', '1', 'ExpiryDate'], 5848 '0x1040': ['ST', '1', 'EnvironmentalConditions'], 5849 '0x2002': ['SQ', '1', 'EvaluatorSequence'], 5850 '0x2004': ['IS', '1', 'EvaluatorNumber'], 5851 '0x2006': ['PN', '1', 'EvaluatorName'], 5852 '0x2008': ['IS', '1', 'EvaluationAttempt'], 5853 '0x2012': ['SQ', '1', 'IndicationSequence'], 5854 '0x2014': ['IS', '1', 'IndicationNumber'], 5855 '0x2016': ['SH', '1', 'IndicationLabel'], 5856 '0x2018': ['ST', '1', 'IndicationDescription'], 5857 '0x201A': ['CS', '1-n', 'IndicationType'], 5858 '0x201C': ['CS', '1', 'IndicationDisposition'], 5859 '0x201E': ['SQ', '1', 'IndicationROISequence'], 5860 '0x2030': ['SQ', '1', 'IndicationPhysicalPropertySequence'], 5861 '0x2032': ['SH', '1', 'PropertyLabel'], 5862 '0x2202': ['IS', '1', 'CoordinateSystemNumberOfAxes'], 5863 '0x2204': ['SQ', '1', 'CoordinateSystemAxesSequence'], 5864 '0x2206': ['ST', '1', 'CoordinateSystemAxisDescription'], 5865 '0x2208': ['CS', '1', 'CoordinateSystemDataSetMapping'],
5866 '0x220A': ['IS', '1', 'CoordinateSystemAxisNumber'], 5867 '0x220C': ['CS', '1', 'CoordinateSystemAxisType'], 5868 '0x220E': ['CS', '1', 'CoordinateSystemAxisUnits'], 5869 '0x2210': ['OB', '1', 'CoordinateSystemAxisValues'], 5870 '0x2220': ['SQ', '1', 'CoordinateSystemTransformSequence'], 5871 '0x2222': ['ST', '1', 'TransformDescription'], 5872 '0x2224': ['IS', '1', 'TransformNumberOfAxes'], 5873 '0x2226': ['IS', '1-n', 'TransformOrderOfAxes'], 5874 '0x2228': ['CS', '1', 'TransformedAxisUnits'], 5875 '0x222A': ['DS', '1-n', 'CoordinateSystemTransformRotationAndScaleMatrix'], 5876 '0x222C': ['DS', '1-n', 'CoordinateSystemTransformTranslationMatrix'], 5877 '0x3011': ['DS', '1', 'InternalDetectorFrameTime'], 5878 '0x3012': ['DS', '1', 'NumberOfFramesIntegrated'], 5879 '0x3020': ['SQ', '1', 'DetectorTemperatureSequence'], 5880 '0x3022': ['ST', '1', 'SensorName'], 5881 '0x3024': ['DS', '1', 'HorizontalOffsetOfSensor'], 5882 '0x3026': ['DS', '1', 'VerticalOffsetOfSensor'], 5883 '0x3028': ['DS', '1', 'SensorTemperature'], 5884 '0x3040': ['SQ', '1', 'DarkCurrentSequence'], 5885 '0x3050': ['ox', '1', 'DarkCurrentCounts'], 5886 '0x3060': ['SQ', '1', 'GainCorrectionReferenceSequence'], 5887 '0x3070': ['ox', '1', 'AirCounts'], 5888 '0x3071': ['DS', '1', 'KVUsedInGainCalibration'], 5889 '0x3072': ['DS', '1', 'MAUsedInGainCalibration'], 5890 '0x3073': ['DS', '1', 'NumberOfFramesUsedForIntegration'], 5891 '0x3074': ['LO', '1', 'FilterMaterialUsedInGainCalibration'], 5892 '0x3075': ['DS', '1', 'FilterThicknessUsedInGainCalibration'], 5893 '0x3076': ['DA', '1', 'DateOfGainCalibration'], 5894 '0x3077': ['TM', '1', 'TimeOfGainCalibration'], 5895 '0x3080': ['OB', '1', 'BadPixelImage'], 5896 '0x3099': ['LT', '1', 'CalibrationNotes'], 5897 '0x4002': ['SQ', '1', 'PulserEquipmentSequence'], 5898 '0x4004': ['CS', '1', 'PulserType'], 5899 '0x4006': ['LT', '1', 'PulserNotes'], 5900 '0x4008': ['SQ', '1', 'ReceiverEquipmentSequence'], 5901 '0x400A': ['CS', '1', 'AmplifierType'], 5902 '0x400C': ['LT', '1', 'ReceiverNotes'], 5903 '0x400E': ['SQ', '1', 'PreAmplifierEquipmentSequence'], 5904 '0x400F': ['LT', '1', 'PreAmplifierNotes'], 5905 '0x4010': ['SQ', '1', 'TransmitTransducerSequence'], 5906 '0x4011': ['SQ', '1', 'ReceiveTransducerSequence'], 5907 '0x4012': ['US', '1', 'NumberOfElements'], 5908 '0x4013': ['CS', '1', 'ElementShape'], 5909 '0x4014': ['DS', '1', 'ElementDimensionA'], 5910 '0x4015': ['DS', '1', 'ElementDimensionB'], 5911 '0x4016': ['DS', '1', 'ElementPitchA'], 5912 '0x4017': ['DS', '1', 'MeasuredBeamDimensionA'], 5913 '0x4018': ['DS', '1', 'MeasuredBeamDimensionB'], 5914 '0x4019': ['DS', '1', 'LocationOfMeasuredBeamDiameter'], 5915 '0x401A': ['DS', '1', 'NominalFrequency'], 5916 '0x401B': ['DS', '1', 'MeasuredCenterFrequency'], 5917 '0x401C': ['DS', '1', 'MeasuredBandwidth'], 5918 '0x401D': ['DS', '1', 'ElementPitchB'], 5919 '0x4020': ['SQ', '1', 'PulserSettingsSequence'], 5920 '0x4022': ['DS', '1', 'PulseWidth'], 5921 '0x4024': ['DS', '1', 'ExcitationFrequency'], 5922 '0x4026': ['CS', '1', 'ModulationType'], 5923 '0x4028': ['DS', '1', 'Damping'], 5924 '0x4030': ['SQ', '1', 'ReceiverSettingsSequence'], 5925 '0x4031': ['DS', '1', 'AcquiredSoundpathLength'], 5926 '0x4032': ['CS', '1', 'AcquisitionCompressionType'], 5927 '0x4033': ['IS', '1', 'AcquisitionSampleSize'], 5928 '0x4034': ['DS', '1', 'RectifierSmoothing'], 5929 '0x4035': ['SQ', '1', 'DACSequence'], 5930 '0x4036': ['CS', '1', 'DACType'], 5931 '0x4038': ['DS', '1-n', 'DACGainPoints'], 5932 '0x403A': ['DS', '1-n', 'DACTimePoints'], 5933 '0x403C': ['DS', '1-n', 'DACAmplitude'], 5934 '0x4040': ['SQ', '1', 'PreAmplifierSettingsSequence'], 5935 '0x4050': ['SQ', '1', 'TransmitTransducerSettingsSequence'], 5936 '0x4051': ['SQ', '1', 'ReceiveTransducerSettingsSequence'], 5937 '0x4052': ['DS', '1', 'IncidentAngle'], 5938 '0x4054': ['ST', '1', 'CouplingTechnique'], 5939 '0x4056': ['ST', '1', 'CouplingMedium'], 5940 '0x4057': ['DS', '1', 'CouplingVelocity'], 5941 '0x4058': ['DS', '1', 'ProbeCenterLocationX'], 5942 '0x4059': ['DS', '1', 'ProbeCenterLocationZ'], 5943 '0x405A': ['DS', '1', 'SoundPathLength'], 5944 '0x405C': ['ST', '1', 'DelayLawIdentifier'], 5945 '0x4060': ['SQ', '1', 'GateSettingsSequence'], 5946 '0x4062': ['DS', '1', 'GateThreshold'], 5947 '0x4064': ['DS', '1', 'VelocityOfSound'], 5948 '0x4070': ['SQ', '1', 'CalibrationSettingsSequence'], 5949 '0x4072': ['ST', '1', 'CalibrationProcedure'], 5950 '0x4074': ['SH', '1', 'ProcedureVersion'], 5951 '0x4076': ['DA', '1', 'ProcedureCreationDate'], 5952 '0x4078': ['DA', '1', 'ProcedureExpirationDate'], 5953 '0x407A': ['DA', '1', 'ProcedureLastModifiedDate'], 5954 '0x407C': ['TM', '1-n', 'CalibrationTime'], 5955 '0x407E': ['DA', '1-n', 'CalibrationDate'], 5956 '0x4080': ['SQ', '1', 'ProbeDriveEquipmentSequence'], 5957 '0x4081': ['CS', '1', 'DriveType'], 5958 '0x4082': ['LT', '1', 'ProbeDriveNotes'], 5959 '0x4083': ['SQ', '1', 'DriveProbeSequence'], 5960 '0x4084': ['DS', '1', 'ProbeInductance'], 5961 '0x4085': ['DS', '1', 'ProbeResistance'], 5962 '0x4086': ['SQ', '1', 'ReceiveProbeSequence'], 5963 '0x4087': ['SQ', '1', 'ProbeDriveSettingsSequence'], 5964 '0x4088': ['DS', '1', 'BridgeResistors'], 5965 '0x4089': ['DS', '1', 'ProbeOrientationAngle'], 5966 '0x408B': ['DS', '1', 'UserSelectedGainY'], 5967 '0x408C': ['DS', '1', 'UserSelectedPhase'], 5968 '0x408D': ['DS', '1', 'UserSelectedOffsetX'], 5969 '0x408E': ['DS', '1', 'UserSelectedOffsetY'], 5970 '0x4091': ['SQ', '1', 'ChannelSettingsSequence'], 5971 '0x4092': ['DS', '1', 'ChannelThreshold'], 5972 '0x409A': ['SQ', '1', 'ScannerSettingsSequence'], 5973 '0x409B': ['ST', '1', 'ScanProcedure'], 5974 '0x409C': ['DS', '1', 'TranslationRateX'], 5975 '0x409D': ['DS', '1', 'TranslationRateY'], 5976 '0x409F': ['DS', '1', 'ChannelOverlap'], 5977 '0x40A0': ['LO', '1', 'ImageQualityIndicatorType'], 5978 '0x40A1': ['LO', '1', 'ImageQualityIndicatorMaterial'], 5979 '0x40A2': ['LO', '1', 'ImageQualityIndicatorSize'], 5980 '0x5002': ['IS', '1', 'LINACEnergy'], 5981 '0x5004': ['IS', '1', 'LINACOutput'], 5982 '0x5100': ['US', '1', 'ActiveAperture'], 5983 '0x5101': ['DS', '1', 'TotalAperture'], 5984 '0x5102': ['DS', '1', 'ApertureElevation'], 5985 '0x5103': ['DS', '1', 'MainLobeAngle'], 5986 '0x5104': ['DS', '1', 'MainRoofAngle'], 5987 '0x5105': ['CS', '1', 'ConnectorType'], 5988 '0x5106': ['SH', '1', 'WedgeModelNumber'], 5989 '0x5107': ['DS', '1', 'WedgeAngleFloat'], 5990 '0x5108': ['DS', '1', 'WedgeRoofAngle'], 5991 '0x5109': ['CS', '1', 'WedgeElement1Position'], 5992 '0x510A': ['DS', '1', 'WedgeMaterialVelocity'], 5993 '0x510B': ['SH', '1', 'WedgeMaterial'], 5994 '0x510C': ['DS', '1', 'WedgeOffsetZ'], 5995 '0x510D': ['DS', '1', 'WedgeOriginOffsetX'], 5996 '0x510E': ['DS', '1', 'WedgeTimeDelay'], 5997 '0x510F': ['SH', '1', 'WedgeName'], 5998 '0x5110': ['SH', '1', 'WedgeManufacturerName'], 5999 '0x5111': ['LO', '1', 'WedgeDescription'], 6000 '0x5112': ['DS', '1', 'NominalBeamAngle'], 6001 '0x5113': ['DS', '1', 'WedgeOffsetX'], 6002 '0x5114': ['DS', '1', 'WedgeOffsetY'], 6003 '0x5115': ['DS', '1', 'WedgeTotalLength'], 6004 '0x5116': ['DS', '1', 'WedgeInContactLength'], 6005 '0x5117': ['DS', '1', 'WedgeFrontGap'], 6006 '0x5118': ['DS', '1', 'WedgeTotalHeight'], 6007 '0x5119': ['DS', '1', 'WedgeFrontHeight'], 6008 '0x511A': ['DS', '1', 'WedgeRearHeight'], 6009 '0x511B': ['DS', '1', 'WedgeTotalWidth'], 6010 '0x511C': ['DS', '1', 'WedgeInContactWidth'], 6011 '0x511D': ['DS', '1', 'WedgeChamferHeight'], 6012 '0x511E': ['CS', '1', 'WedgeCurve'], 6013 '0x511F': ['DS', '1', 'RadiusAlongWedge'] 6014 }, 6015 '0x0018': { 6016 '0x0000': ['UL', '1', 'GenericGroupLength'], 6017 '0x0010': ['LO', '1', 'ContrastBolusAgent'], 6018 '0x0012': ['SQ', '1', 'ContrastBolusAgentSequence'], 6019 '0x0013': ['FL', '1', 'ContrastBolusT1Relaxivity'], 6020 '0x0014': ['SQ', '1', 'ContrastBolusAdministrationRouteSequence'], 6021 '0x0015': ['CS', '1', 'BodyPartExamined'], 6022 '0x0020': ['CS', '1-n', 'ScanningSequence'], 6023 '0x0021': ['CS', '1-n', 'SequenceVariant'], 6024 '0x0022': ['CS', '1-n', 'ScanOptions'], 6025 '0x0023': ['CS', '1', 'MRAcquisitionType'], 6026 '0x0024': ['SH', '1', 'SequenceName'], 6027 '0x0025': ['CS', '1', 'AngioFlag'], 6028 '0x0026': ['SQ', '1', 'InterventionDrugInformationSequence'], 6029 '0x0027': ['TM', '1', 'InterventionDrugStopTime'], 6030 '0x0028': ['DS', '1', 'InterventionDrugDose'], 6031 '0x0029': ['SQ', '1', 'InterventionDrugCodeSequence'], 6032 '0x002A': ['SQ', '1', 'AdditionalDrugSequence'], 6033 '0x0030': ['LO', '1-n', 'Radionuclide'], 6034 '0x0031': ['LO', '1', 'Radiopharmaceutical'], 6035 '0x0032': ['DS', '1', 'EnergyWindowCenterline'], 6036 '0x0033': ['DS', '1-n', 'EnergyWindowTotalWidth'], 6037 '0x0034': ['LO', '1', 'InterventionDrugName'], 6038 '0x0035': ['TM', '1', 'InterventionDrugStartTime'], 6039 '0x0036': ['SQ', '1', 'InterventionSequence'], 6040 '0x0037': ['CS', '1', 'TherapyType'], 6041 '0x0038': ['CS', '1', 'InterventionStatus'], 6042 '0x0039': ['CS', '1', 'TherapyDescription'], 6043 '0x003A': ['ST', '1', 'InterventionDescription'], 6044 '0x0040': ['IS', '1', 'CineRate'], 6045 '0x0042': ['CS', '1', 'InitialCineRunState'], 6046 '0x0050': ['DS', '1', 'SliceThickness'], 6047 '0x0060': ['DS', '1', 'KVP'], 6048 '0x0070': ['IS', '1', 'CountsAccumulated'], 6049 '0x0071': ['CS', '1', 'AcquisitionTerminationCondition'], 6050 '0x0072': ['DS', '1', 'EffectiveDuration'], 6051 '0x0073': ['CS', '1', 'AcquisitionStartCondition'], 6052 '0x0074': ['IS', '1', 'AcquisitionStartConditionData'], 6053 '0x0075': ['IS', '1', 'AcquisitionTerminationConditionData'], 6054 '0x0080': ['DS', '1', 'RepetitionTime'], 6055 '0x0081': ['DS', '1', 'EchoTime'], 6056 '0x0082': ['DS', '1', 'InversionTime'], 6057 '0x0083': ['DS', '1', 'NumberOfAverages'], 6058 '0x0084': ['DS', '1', 'ImagingFrequency'], 6059 '0x0085': ['SH', '1', 'ImagedNucleus'], 6060 '0x0086': ['IS', '1-n', 'EchoNumbers'], 6061 '0x0087': ['DS', '1', 'MagneticFieldStrength'], 6062 '0x0088': ['DS', '1', 'SpacingBetweenSlices'], 6063 '0x0089': ['IS', '1', 'NumberOfPhaseEncodingSteps'], 6064 '0x0090': ['DS', '1', 'DataCollectionDiameter'], 6065 '0x0091': ['IS', '1', 'EchoTrainLength'], 6066 '0x0093': ['DS', '1', 'PercentSampling'], 6067 '0x0094': ['DS', '1', 'PercentPhaseFieldOfView'], 6068 '0x0095': ['DS', '1', 'PixelBandwidth'], 6069 '0x1000': ['LO', '1', 'DeviceSerialNumber'], 6070 '0x1002': ['UI', '1', 'DeviceUID'], 6071 '0x1003': ['LO', '1', 'DeviceID'], 6072 '0x1004': ['LO', '1', 'PlateID'], 6073 '0x1005': ['LO', '1', 'GeneratorID'], 6074 '0x1006': ['LO', '1', 'GridID'], 6075 '0x1007': ['LO', '1', 'CassetteID'], 6076 '0x1008': ['LO', '1', 'GantryID'], 6077 '0x1010': ['LO', '1', 'SecondaryCaptureDeviceID'], 6078 '0x1011': ['LO', '1', 'HardcopyCreationDeviceID'], 6079 '0x1012': ['DA', '1', 'DateOfSecondaryCapture'], 6080 '0x1014': ['TM', '1', 'TimeOfSecondaryCapture'], 6081 '0x1016': ['LO', '1', 'SecondaryCaptureDeviceManufacturer'], 6082 '0x1017': ['LO', '1', 'HardcopyDeviceManufacturer'], 6083 '0x1018': ['LO', '1', 'SecondaryCaptureDeviceManufacturerModelName'], 6084 '0x1019': ['LO', '1-n', 'SecondaryCaptureDeviceSoftwareVersions'], 6085 '0x101A': ['LO', '1-n', 'HardcopyDeviceSoftwareVersion'], 6086 '0x101B': ['LO', '1', 'HardcopyDeviceManufacturerModelName'], 6087 '0x1020': ['LO', '1-n', 'SoftwareVersions'], 6088 '0x1022': ['SH', '1', 'VideoImageFormatAcquired'], 6089 '0x1023': ['LO', '1', 'DigitalImageFormatAcquired'], 6090 '0x1030': ['LO', '1', 'ProtocolName'], 6091 '0x1040': ['LO', '1', 'ContrastBolusRoute'], 6092 '0x1041': ['DS', '1', 'ContrastBolusVolume'], 6093 '0x1042': ['TM', '1', 'ContrastBolusStartTime'], 6094 '0x1043': ['TM', '1', 'ContrastBolusStopTime'], 6095 '0x1044': ['DS', '1', 'ContrastBolusTotalDose'], 6096 '0x1045': ['IS', '1', 'SyringeCounts'], 6097 '0x1046': ['DS', '1-n', 'ContrastFlowRate'], 6098 '0x1047': ['DS', '1-n', 'ContrastFlowDuration'], 6099 '0x1048': ['CS', '1', 'ContrastBolusIngredient'], 6100 '0x1049': ['DS', '1', 'ContrastBolusIngredientConcentration'], 6101 '0x1050': ['DS', '1', 'SpatialResolution'], 6102 '0x1060': ['DS', '1', 'TriggerTime'], 6103 '0x1061': ['LO', '1', 'TriggerSourceOrType'], 6104 '0x1062': ['IS', '1', 'NominalInterval'], 6105 '0x1063': ['DS', '1', 'FrameTime'], 6106 '0x1064': ['LO', '1', 'CardiacFramingType'], 6107 '0x1065': ['DS', '1-n', 'FrameTimeVector'], 6108 '0x1066': ['DS', '1', 'FrameDelay'], 6109 '0x1067': ['DS', '1', 'ImageTriggerDelay'], 6110 '0x1068': ['DS', '1', 'MultiplexGroupTimeOffset'], 6111 '0x1069': ['DS', '1', 'TriggerTimeOffset'], 6112 '0x106A': ['CS', '1', 'SynchronizationTrigger'], 6113 '0x106C': ['US', '2', 'SynchronizationChannel'], 6114 '0x106E': ['UL', '1', 'TriggerSamplePosition'], 6115 '0x1070': ['LO', '1', 'RadiopharmaceuticalRoute'], 6116 '0x1071': ['DS', '1', 'RadiopharmaceuticalVolume'], 6117 '0x1072': ['TM', '1', 'RadiopharmaceuticalStartTime'], 6118 '0x1073': ['TM', '1', 'RadiopharmaceuticalStopTime'], 6119 '0x1074': ['DS', '1', 'RadionuclideTotalDose'], 6120 '0x1075': ['DS', '1', 'RadionuclideHalfLife'], 6121 '0x1076': ['DS', '1', 'RadionuclidePositronFraction'], 6122 '0x1077': ['DS', '1', 'RadiopharmaceuticalSpecificActivity'], 6123 '0x1078': ['DT', '1', 'RadiopharmaceuticalStartDateTime'], 6124 '0x1079': ['DT', '1', 'RadiopharmaceuticalStopDateTime'], 6125 '0x1080': ['CS', '1', 'BeatRejectionFlag'], 6126 '0x1081': ['IS', '1', 'LowRRValue'], 6127 '0x1082': ['IS', '1', 'HighRRValue'], 6128 '0x1083': ['IS', '1', 'IntervalsAcquired'],
6129 '0x1084': ['IS', '1', 'IntervalsRejected'], 6130 '0x1085': ['LO', '1', 'PVCRejection'], 6131 '0x1086': ['IS', '1', 'SkipBeats'], 6132 '0x1088': ['IS', '1', 'HeartRate'], 6133 '0x1090': ['IS', '1', 'CardiacNumberOfImages'], 6134 '0x1094': ['IS', '1', 'TriggerWindow'], 6135 '0x1100': ['DS', '1', 'ReconstructionDiameter'], 6136 '0x1110': ['DS', '1', 'DistanceSourceToDetector'], 6137 '0x1111': ['DS', '1', 'DistanceSourceToPatient'], 6138 '0x1114': ['DS', '1', 'EstimatedRadiographicMagnificationFactor'], 6139 '0x1120': ['DS', '1', 'GantryDetectorTilt'], 6140 '0x1121': ['DS', '1', 'GantryDetectorSlew'], 6141 '0x1130': ['DS', '1', 'TableHeight'], 6142 '0x1131': ['DS', '1', 'TableTraverse'], 6143 '0x1134': ['CS', '1', 'TableMotion'], 6144 '0x1135': ['DS', '1-n', 'TableVerticalIncrement'], 6145 '0x1136': ['DS', '1-n', 'TableLateralIncrement'], 6146 '0x1137': ['DS', '1-n', 'TableLongitudinalIncrement'], 6147 '0x1138': ['DS', '1', 'TableAngle'], 6148 '0x113A': ['CS', '1', 'TableType'], 6149 '0x1140': ['CS', '1', 'RotationDirection'], 6150 '0x1141': ['DS', '1', 'AngularPosition'], 6151 '0x1142': ['DS', '1-n', 'RadialPosition'], 6152 '0x1143': ['DS', '1', 'ScanArc'], 6153 '0x1144': ['DS', '1', 'AngularStep'], 6154 '0x1145': ['DS', '1', 'CenterOfRotationOffset'], 6155 '0x1146': ['DS', '1-n', 'RotationOffset'], 6156 '0x1147': ['CS', '1', 'FieldOfViewShape'], 6157 '0x1149': ['IS', '1-2', 'FieldOfViewDimensions'], 6158 '0x1150': ['IS', '1', 'ExposureTime'], 6159 '0x1151': ['IS', '1', 'XRayTubeCurrent'], 6160 '0x1152': ['IS', '1', 'Exposure'], 6161 '0x1153': ['IS', '1', 'ExposureInuAs'], 6162 '0x1154': ['DS', '1', 'AveragePulseWidth'], 6163 '0x1155': ['CS', '1', 'RadiationSetting'], 6164 '0x1156': ['CS', '1', 'RectificationType'], 6165 '0x115A': ['CS', '1', 'RadiationMode'], 6166 '0x115E': ['DS', '1', 'ImageAndFluoroscopyAreaDoseProduct'], 6167 '0x1160': ['SH', '1', 'FilterType'], 6168 '0x1161': ['LO', '1-n', 'TypeOfFilters'], 6169 '0x1162': ['DS', '1', 'IntensifierSize'], 6170 '0x1164': ['DS', '2', 'ImagerPixelSpacing'], 6171 '0x1166': ['CS', '1-n', 'Grid'], 6172 '0x1170': ['IS', '1', 'GeneratorPower'], 6173 '0x1180': ['SH', '1', 'CollimatorGridName'], 6174 '0x1181': ['CS', '1', 'CollimatorType'], 6175 '0x1182': ['IS', '1-2', 'FocalDistance'], 6176 '0x1183': ['DS', '1-2', 'XFocusCenter'], 6177 '0x1184': ['DS', '1-2', 'YFocusCenter'], 6178 '0x1190': ['DS', '1-n', 'FocalSpots'], 6179 '0x1191': ['CS', '1', 'AnodeTargetMaterial'], 6180 '0x11A0': ['DS', '1', 'BodyPartThickness'], 6181 '0x11A2': ['DS', '1', 'CompressionForce'], 6182 '0x11A4': ['LO', '1', 'PaddleDescription'], 6183 '0x1200': ['DA', '1-n', 'DateOfLastCalibration'], 6184 '0x1201': ['TM', '1-n', 'TimeOfLastCalibration'], 6185 '0x1202': ['DT', '1', 'DateTimeOfLastCalibration'], 6186 '0x1210': ['SH', '1-n', 'ConvolutionKernel'], 6187 '0x1240': ['IS', '1-n', 'UpperLowerPixelValues'], 6188 '0x1242': ['IS', '1', 'ActualFrameDuration'], 6189 '0x1243': ['IS', '1', 'CountRate'], 6190 '0x1244': ['US', '1', 'PreferredPlaybackSequencing'], 6191 '0x1250': ['SH', '1', 'ReceiveCoilName'], 6192 '0x1251': ['SH', '1', 'TransmitCoilName'], 6193 '0x1260': ['SH', '1', 'PlateType'], 6194 '0x1261': ['LO', '1', 'PhosphorType'], 6195 '0x1300': ['DS', '1', 'ScanVelocity'], 6196 '0x1301': ['CS', '1-n', 'WholeBodyTechnique'], 6197 '0x1302': ['IS', '1', 'ScanLength'], 6198 '0x1310': ['US', '4', 'AcquisitionMatrix'], 6199 '0x1312': ['CS', '1', 'InPlanePhaseEncodingDirection'], 6200 '0x1314': ['DS', '1', 'FlipAngle'], 6201 '0x1315': ['CS', '1', 'VariableFlipAngleFlag'], 6202 '0x1316': ['DS', '1', 'SAR'], 6203 '0x1318': ['DS', '1', 'dBdt'], 6204 '0x1400': ['LO', '1', 'AcquisitionDeviceProcessingDescription'], 6205 '0x1401': ['LO', '1', 'AcquisitionDeviceProcessingCode'], 6206 '0x1402': ['CS', '1', 'CassetteOrientation'], 6207 '0x1403': ['CS', '1', 'CassetteSize'], 6208 '0x1404': ['US', '1', 'ExposuresOnPlate'], 6209 '0x1405': ['IS', '1', 'RelativeXRayExposure'], 6210 '0x1411': ['DS', '1', 'ExposureIndex'], 6211 '0x1412': ['DS', '1', 'TargetExposureIndex'], 6212 '0x1413': ['DS', '1', 'DeviationIndex'], 6213 '0x1450': ['DS', '1', 'ColumnAngulation'], 6214 '0x1460': ['DS', '1', 'TomoLayerHeight'], 6215 '0x1470': ['DS', '1', 'TomoAngle'], 6216 '0x1480': ['DS', '1', 'TomoTime'], 6217 '0x1490': ['CS', '1', 'TomoType'], 6218 '0x1491': ['CS', '1', 'TomoClass'], 6219 '0x1495': ['IS', '1', 'NumberOfTomosynthesisSourceImages'], 6220 '0x1500': ['CS', '1', 'PositionerMotion'], 6221 '0x1508': ['CS', '1', 'PositionerType'], 6222 '0x1510': ['DS', '1', 'PositionerPrimaryAngle'], 6223 '0x1511': ['DS', '1', 'PositionerSecondaryAngle'], 6224 '0x1520': ['DS', '1-n', 'PositionerPrimaryAngleIncrement'], 6225 '0x1521': ['DS', '1-n', 'PositionerSecondaryAngleIncrement'], 6226 '0x1530': ['DS', '1', 'DetectorPrimaryAngle'], 6227 '0x1531': ['DS', '1', 'DetectorSecondaryAngle'], 6228 '0x1600': ['CS', '1-3', 'ShutterShape'], 6229 '0x1602': ['IS', '1', 'ShutterLeftVerticalEdge'], 6230 '0x1604': ['IS', '1', 'ShutterRightVerticalEdge'], 6231 '0x1606': ['IS', '1', 'ShutterUpperHorizontalEdge'], 6232 '0x1608': ['IS', '1', 'ShutterLowerHorizontalEdge'], 6233 '0x1610': ['IS', '2', 'CenterOfCircularShutter'], 6234 '0x1612': ['IS', '1', 'RadiusOfCircularShutter'], 6235 '0x1620': ['IS', '2-2n', 'VerticesOfThePolygonalShutter'],
6236 '0x1622': ['US', '1', 'ShutterPresentationValue'], 6237 '0x1623': ['US', '1', 'ShutterOverlayGroup'], 6238 '0x1624': ['US', '3', 'ShutterPresentationColorCIELabValue'], 6239 '0x1700': ['CS', '1-3', 'CollimatorShape'], 6240 '0x1702': ['IS', '1', 'CollimatorLeftVerticalEdge'], 6241 '0x1704': ['IS', '1', 'CollimatorRightVerticalEdge'], 6242 '0x1706': ['IS', '1', 'CollimatorUpperHorizontalEdge'], 6243 '0x1708': ['IS', '1', 'CollimatorLowerHorizontalEdge'], 6244 '0x1710': ['IS', '2', 'CenterOfCircularCollimator'], 6245 '0x1712': ['IS', '1', 'RadiusOfCircularCollimator'], 6246 '0x1720': ['IS', '2-2n', 'VerticesOfThePolygonalCollimator'], 6247 '0x1800': ['CS', '1', 'AcquisitionTimeSynchronized'], 6248 '0x1801': ['SH', '1', 'TimeSource'], 6249 '0x1802': ['CS', '1', 'TimeDistributionProtocol'], 6250 '0x1803': ['LO', '1', 'NTPSourceAddress'], 6251 '0x2001': ['IS', '1-n', 'PageNumberVector'], 6252 '0x2002': ['SH', '1-n', 'FrameLabelVector'], 6253 '0x2003': ['DS', '1-n', 'FramePrimaryAngleVector'], 6254 '0x2004': ['DS', '1-n', 'FrameSecondaryAngleVector'], 6255 '0x2005': ['DS', '1-n', 'SliceLocationVector'], 6256 '0x2006': ['SH', '1-n', 'DisplayWindowLabelVector'], 6257 '0x2010': ['DS', '2', 'NominalScannedPixelSpacing'], 6258 '0x2020': ['CS', '1', 'DigitizingDeviceTransportDirection'], 6259 '0x2030': ['DS', '1', 'RotationOfScannedFilm'], 6260 '0x2041': ['SQ', '1', 'BiopsyTargetSequence'], 6261 '0x2042': ['UI', '1', 'TargetUID'], 6262 '0x2043': ['FL', '2', 'LocalizingCursorPosition'], 6263 '0x2044': ['FL', '3', 'CalculatedTargetPosition'], 6264 '0x2045': ['SH', '1', 'TargetLabel'], 6265 '0x2046': ['FL', '1', 'DisplayedZValue'], 6266 '0x3100': ['CS', '1', 'IVUSAcquisition'], 6267 '0x3101': ['DS', '1', 'IVUSPullbackRate'], 6268 '0x3102': ['DS', '1', 'IVUSGatedRate'], 6269 '0x3103': ['IS', '1', 'IVUSPullbackStartFrameNumber'], 6270 '0x3104': ['IS', '1', 'IVUSPullbackStopFrameNumber'], 6271 '0x3105': ['IS', '1-n', 'LesionNumber'], 6272 '0x4000': ['LT', '1', 'AcquisitionComments'], 6273 '0x5000': ['SH', '1-n', 'OutputPower'], 6274 '0x5010': ['LO', '1-n', 'TransducerData'], 6275 '0x5012': ['DS', '1', 'FocusDepth'], 6276 '0x5020': ['LO', '1', 'ProcessingFunction'], 6277 '0x5021': ['LO', '1', 'PostprocessingFunction'], 6278 '0x5022': ['DS', '1', 'MechanicalIndex'], 6279 '0x5024': ['DS', '1', 'BoneThermalIndex'], 6280 '0x5026': ['DS', '1', 'CranialThermalIndex'], 6281 '0x5027': ['DS', '1', 'SoftTissueThermalIndex'], 6282 '0x5028': ['DS', '1', 'SoftTissueFocusThermalIndex'], 6283 '0x5029': ['DS', '1', 'SoftTissueSurfaceThermalIndex'], 6284 '0x5030': ['DS', '1', 'DynamicRange'], 6285 '0x5040': ['DS', '1', 'TotalGain'], 6286 '0x5050': ['IS', '1', 'DepthOfScanField'], 6287 '0x5100': ['CS', '1', 'PatientPosition'], 6288 '0x5101': ['CS', '1', 'ViewPosition'], 6289 '0x5104': ['SQ', '1', 'ProjectionEponymousNameCodeSequence'], 6290 '0x5210': ['DS', '6', 'ImageTransformationMatrix'], 6291 '0x5212': ['DS', '3', 'ImageTranslationVector'], 6292 '0x6000': ['DS', '1', 'Sensitivity'], 6293 '0x6011': ['SQ', '1', 'SequenceOfUltrasoundRegions'], 6294 '0x6012': ['US', '1', 'RegionSpatialFormat'], 6295 '0x6014': ['US', '1', 'RegionDataType'], 6296 '0x6016': ['UL', '1', 'RegionFlags'], 6297 '0x6018': ['UL', '1', 'RegionLocationMinX0'], 6298 '0x601A': ['UL', '1', 'RegionLocationMinY0'], 6299 '0x601C': ['UL', '1', 'RegionLocationMaxX1'], 6300 '0x601E': ['UL', '1', 'RegionLocationMaxY1'], 6301 '0x6020': ['SL', '1', 'ReferencePixelX0'], 6302 '0x6022': ['SL', '1', 'ReferencePixelY0'], 6303 '0x6024': ['US', '1', 'PhysicalUnitsXDirection'], 6304 '0x6026': ['US', '1', 'PhysicalUnitsYDirection'], 6305 '0x6028': ['FD', '1', 'ReferencePixelPhysicalValueX'], 6306 '0x602A': ['FD', '1', 'ReferencePixelPhysicalValueY'], 6307 '0x602C': ['FD', '1', 'PhysicalDeltaX'], 6308 '0x602E': ['FD', '1', 'PhysicalDeltaY'], 6309 '0x6030': ['UL', '1', 'TransducerFrequency'], 6310 '0x6031': ['CS', '1', 'TransducerType'], 6311 '0x6032': ['UL', '1', 'PulseRepetitionFrequency'], 6312 '0x6034': ['FD', '1', 'DopplerCorrectionAngle'], 6313 '0x6036': ['FD', '1', 'SteeringAngle'], 6314 '0x6038': ['UL', '1', 'DopplerSampleVolumeXPositionRetired'], 6315 '0x6039': ['SL', '1', 'DopplerSampleVolumeXPosition'], 6316 '0x603A': ['UL', '1', 'DopplerSampleVolumeYPositionRetired'], 6317 '0x603B': ['SL', '1', 'DopplerSampleVolumeYPosition'], 6318 '0x603C': ['UL', '1', 'TMLinePositionX0Retired'], 6319 '0x603D': ['SL', '1', 'TMLinePositionX0'], 6320 '0x603E': ['UL', '1', 'TMLinePositionY0Retired'], 6321 '0x603F': ['SL', '1', 'TMLinePositionY0'], 6322 '0x6040': ['UL', '1', 'TMLinePositionX1Retired'], 6323 '0x6041': ['SL', '1', 'TMLinePositionX1'], 6324 '0x6042': ['UL', '1', 'TMLinePositionY1Retired'], 6325 '0x6043': ['SL', '1', 'TMLinePositionY1'], 6326 '0x6044': ['US', '1', 'PixelComponentOrganization'], 6327 '0x6046': ['UL', '1', 'PixelComponentMask'], 6328 '0x6048': ['UL', '1', 'PixelComponentRangeStart'], 6329 '0x604A': ['UL', '1', 'PixelComponentRangeStop'], 6330 '0x604C': ['US', '1', 'PixelComponentPhysicalUnits'], 6331 '0x604E': ['US', '1', 'PixelComponentDataType'], 6332 '0x6050': ['UL', '1', 'NumberOfTableBreakPoints'], 6333 '0x6052': ['UL', '1-n', 'TableOfXBreakPoints'], 6334 '0x6054': ['FD', '1-n', 'TableOfYBreakPoints'], 6335 '0x6056': ['UL', '1', 'NumberOfTableEntries'], 6336 '0x6058': ['UL', '1-n', 'TableOfPixelValues'], 6337 '0x605A': ['FL', '1-n', 'TableOfParameterValues'], 6338 '0x6060': ['FL', '1-n', 'RWaveTimeVector'], 6339 '0x7000': ['CS', '1', 'DetectorConditionsNominalFlag'], 6340 '0x7001': ['DS', '1', 'DetectorTemperature'], 6341 '0x7004': ['CS', '1', 'DetectorType'], 6342 '0x7005': ['CS', '1', 'DetectorConfiguration'], 6343 '0x7006': ['LT', '1', 'DetectorDescription'], 6344 '0x7008': ['LT', '1', 'DetectorMode'], 6345 '0x700A': ['SH', '1', 'DetectorID'],
6346 '0x700C': ['DA', '1', 'DateOfLastDetectorCalibration'], 6347 '0x700E': ['TM', '1', 'TimeOfLastDetectorCalibration'], 6348 '0x7010': ['IS', '1', 'ExposuresOnDetectorSinceLastCalibration'], 6349 '0x7011': ['IS', '1', 'ExposuresOnDetectorSinceManufactured'], 6350 '0x7012': ['DS', '1', 'DetectorTimeSinceLastExposure'], 6351 '0x7014': ['DS', '1', 'DetectorActiveTime'], 6352 '0x7016': ['DS', '1', 'DetectorActivationOffsetFromExposure'], 6353 '0x701A': ['DS', '2', 'DetectorBinning'], 6354 '0x7020': ['DS', '2', 'DetectorElementPhysicalSize'], 6355 '0x7022': ['DS', '2', 'DetectorElementSpacing'], 6356 '0x7024': ['CS', '1', 'DetectorActiveShape'], 6357 '0x7026': ['DS', '1-2', 'DetectorActiveDimensions'], 6358 '0x7028': ['DS', '2', 'DetectorActiveOrigin'], 6359 '0x702A': ['LO', '1', 'DetectorManufacturerName'], 6360 '0x702B': ['LO', '1', 'DetectorManufacturerModelName'], 6361 '0x7030': ['DS', '2', 'FieldOfViewOrigin'], 6362 '0x7032': ['DS', '1', 'FieldOfViewRotation'], 6363 '0x7034': ['CS', '1', 'FieldOfViewHorizontalFlip'], 6364 '0x7036': ['FL', '2', 'PixelDataAreaOriginRelativeToFOV'], 6365 '0x7038': ['FL', '1', 'PixelDataAreaRotationAngleRelativeToFOV'], 6366 '0x7040': ['LT', '1', 'GridAbsorbingMaterial'], 6367 '0x7041': ['LT', '1', 'GridSpacingMaterial'], 6368 '0x7042': ['DS', '1', 'GridThickness'], 6369 '0x7044': ['DS', '1', 'GridPitch'], 6370 '0x7046': ['IS', '2', 'GridAspectRatio'], 6371 '0x7048': ['DS', '1', 'GridPeriod'], 6372 '0x704C': ['DS', '1', 'GridFocalDistance'], 6373 '0x7050': ['CS', '1-n', 'FilterMaterial'], 6374 '0x7052': ['DS', '1-n', 'FilterThicknessMinimum'], 6375 '0x7054': ['DS', '1-n', 'FilterThicknessMaximum'], 6376 '0x7056': ['FL', '1-n', 'FilterBeamPathLengthMinimum'], 6377 '0x7058': ['FL', '1-n', 'FilterBeamPathLengthMaximum'], 6378 '0x7060': ['CS', '1', 'ExposureControlMode'], 6379 '0x7062': ['LT', '1', 'ExposureControlModeDescription'], 6380 '0x7064': ['CS', '1', 'ExposureStatus'], 6381 '0x7065': ['DS', '1', 'PhototimerSetting'], 6382 '0x8150': ['DS', '1', 'ExposureTimeInuS'], 6383 '0x8151': ['DS', '1', 'XRayTubeCurrentInuA'], 6384 '0x9004': ['CS', '1', 'ContentQualification'], 6385 '0x9005': ['SH', '1', 'PulseSequenceName'], 6386 '0x9006': ['SQ', '1', 'MRImagingModifierSequence'], 6387 '0x9008': ['CS', '1', 'EchoPulseSequence'], 6388 '0x9009': ['CS', '1', 'InversionRecovery'], 6389 '0x9010': ['CS', '1', 'FlowCompensation'], 6390 '0x9011': ['CS', '1', 'MultipleSpinEcho'], 6391 '0x9012': ['CS', '1', 'MultiPlanarExcitation'], 6392 '0x9014': ['CS', '1', 'PhaseContrast'], 6393 '0x9015': ['CS', '1', 'TimeOfFlightContrast'], 6394 '0x9016': ['CS', '1', 'Spoiling'], 6395 '0x9017': ['CS', '1', 'SteadyStatePulseSequence'], 6396 '0x9018': ['CS', '1', 'EchoPlanarPulseSequence'], 6397 '0x9019': ['FD', '1', 'TagAngleFirstAxis'], 6398 '0x9020': ['CS', '1', 'MagnetizationTransfer'], 6399 '0x9021': ['CS', '1', 'T2Preparation'], 6400 '0x9022': ['CS', '1', 'BloodSignalNulling'], 6401 '0x9024': ['CS', '1', 'SaturationRecovery'], 6402 '0x9025': ['CS', '1', 'SpectrallySelectedSuppression'], 6403 '0x9026': ['CS', '1', 'SpectrallySelectedExcitation'], 6404 '0x9027': ['CS', '1', 'SpatialPresaturation'], 6405 '0x9028': ['CS', '1', 'Tagging'], 6406 '0x9029': ['CS', '1', 'OversamplingPhase'], 6407 '0x9030': ['FD', '1', 'TagSpacingFirstDimension'], 6408 '0x9032': ['CS', '1', 'GeometryOfKSpaceTraversal'], 6409 '0x9033': ['CS', '1', 'SegmentedKSpaceTraversal'], 6410 '0x9034': ['CS', '1', 'RectilinearPhaseEncodeReordering'], 6411 '0x9035': ['FD', '1', 'TagThickness'], 6412 '0x9036': ['CS', '1', 'PartialFourierDirection'], 6413 '0x9037': ['CS', '1', 'CardiacSynchronizationTechnique'], 6414 '0x9041': ['LO', '1', 'ReceiveCoilManufacturerName'], 6415 '0x9042': ['SQ', '1', 'MRReceiveCoilSequence'], 6416 '0x9043': ['CS', '1', 'ReceiveCoilType'], 6417 '0x9044': ['CS', '1', 'QuadratureReceiveCoil'], 6418 '0x9045': ['SQ', '1', 'MultiCoilDefinitionSequence'], 6419 '0x9046': ['LO', '1', 'MultiCoilConfiguration'], 6420 '0x9047': ['SH', '1', 'MultiCoilElementName'], 6421 '0x9048': ['CS', '1', 'MultiCoilElementUsed'], 6422 '0x9049': ['SQ', '1', 'MRTransmitCoilSequence'], 6423 '0x9050': ['LO', '1', 'TransmitCoilManufacturerName'], 6424 '0x9051': ['CS', '1', 'TransmitCoilType'], 6425 '0x9052': ['FD', '1-2', 'SpectralWidth'], 6426 '0x9053': ['FD', '1-2', 'ChemicalShiftReference'], 6427 '0x9054': ['CS', '1', 'VolumeLocalizationTechnique'], 6428 '0x9058': ['US', '1', 'MRAcquisitionFrequencyEncodingSteps'], 6429 '0x9059': ['CS', '1', 'Decoupling'], 6430 '0x9060': ['CS', '1-2', 'DecoupledNucleus'], 6431 '0x9061': ['FD', '1-2', 'DecouplingFrequency'], 6432 '0x9062': ['CS', '1', 'DecouplingMethod'], 6433 '0x9063': ['FD', '1-2', 'DecouplingChemicalShiftReference'], 6434 '0x9064': ['CS', '1', 'KSpaceFiltering'], 6435 '0x9065': ['CS', '1-2', 'TimeDomainFiltering'], 6436 '0x9066': ['US', '1-2', 'NumberOfZeroFills'], 6437 '0x9067': ['CS', '1', 'BaselineCorrection'],
6438 '0x9069': ['FD', '1', 'ParallelReductionFactorInPlane'], 6439 '0x9070': ['FD', '1', 'CardiacRRIntervalSpecified'], 6440 '0x9073': ['FD', '1', 'AcquisitionDuration'], 6441 '0x9074': ['DT', '1', 'FrameAcquisitionDateTime'], 6442 '0x9075': ['CS', '1', 'DiffusionDirectionality'], 6443 '0x9076': ['SQ', '1', 'DiffusionGradientDirectionSequence'], 6444 '0x9077': ['CS', '1', 'ParallelAcquisition'], 6445 '0x9078': ['CS', '1', 'ParallelAcquisitionTechnique'], 6446 '0x9079': ['FD', '1-n', 'InversionTimes'], 6447 '0x9080': ['ST', '1', 'MetaboliteMapDescription'], 6448 '0x9081': ['CS', '1', 'PartialFourier'], 6449 '0x9082': ['FD', '1', 'EffectiveEchoTime'], 6450 '0x9083': ['SQ', '1', 'MetaboliteMapCodeSequence'], 6451 '0x9084': ['SQ', '1', 'ChemicalShiftSequence'], 6452 '0x9085': ['CS', '1', 'CardiacSignalSource'], 6453 '0x9087': ['FD', '1', 'DiffusionBValue'], 6454 '0x9089': ['FD', '3', 'DiffusionGradientOrientation'], 6455 '0x9090': ['FD', '3', 'VelocityEncodingDirection'], 6456 '0x9091': ['FD', '1', 'VelocityEncodingMinimumValue'], 6457 '0x9092': ['SQ', '1', 'VelocityEncodingAcquisitionSequence'], 6458 '0x9093': ['US', '1', 'NumberOfKSpaceTrajectories'], 6459 '0x9094': ['CS', '1', 'CoverageOfKSpace'], 6460 '0x9095': ['UL', '1', 'SpectroscopyAcquisitionPhaseRows'], 6461 '0x9096': ['FD', '1', 'ParallelReductionFactorInPlaneRetired'], 6462 '0x9098': ['FD', '1-2', 'TransmitterFrequency'], 6463 '0x9100': ['CS', '1-2', 'ResonantNucleus'], 6464 '0x9101': ['CS', '1', 'FrequencyCorrection'], 6465 '0x9103': ['SQ', '1', 'MRSpectroscopyFOVGeometrySequence'], 6466 '0x9104': ['FD', '1', 'SlabThickness'], 6467 '0x9105': ['FD', '3', 'SlabOrientation'], 6468 '0x9106': ['FD', '3', 'MidSlabPosition'], 6469 '0x9107': ['SQ', '1', 'MRSpatialSaturationSequence'], 6470 '0x9112': ['SQ', '1', 'MRTimingAndRelatedParametersSequence'], 6471 '0x9114': ['SQ', '1', 'MREchoSequence'], 6472 '0x9115': ['SQ', '1', 'MRModifierSequence'], 6473 '0x9117': ['SQ', '1', 'MRDiffusionSequence'], 6474 '0x9118': ['SQ', '1', 'CardiacSynchronizationSequence'], 6475 '0x9119': ['SQ', '1', 'MRAveragesSequence'], 6476 '0x9125': ['SQ', '1', 'MRFOVGeometrySequence'], 6477 '0x9126': ['SQ', '1', 'VolumeLocalizationSequence'], 6478 '0x9127': ['UL', '1', 'SpectroscopyAcquisitionDataColumns'], 6479 '0x9147': ['CS', '1', 'DiffusionAnisotropyType'], 6480 '0x9151': ['DT', '1', 'FrameReferenceDateTime'], 6481 '0x9152': ['SQ', '1', 'MRMetaboliteMapSequence'], 6482 '0x9155': ['FD', '1', 'ParallelReductionFactorOutOfPlane'], 6483 '0x9159': ['UL', '1', 'SpectroscopyAcquisitionOutOfPlanePhaseSteps'], 6484 '0x9166': ['CS', '1', 'BulkMotionStatus'], 6485 '0x9168': ['FD', '1', 'ParallelReductionFactorSecondInPlane'], 6486 '0x9169': ['CS', '1', 'CardiacBeatRejectionTechnique'], 6487 '0x9170': ['CS', '1', 'RespiratoryMotionCompensationTechnique'], 6488 '0x9171': ['CS', '1', 'RespiratorySignalSource'], 6489 '0x9172': ['CS', '1', 'BulkMotionCompensationTechnique'], 6490 '0x9173': ['CS', '1', 'BulkMotionSignalSource'], 6491 '0x9174': ['CS', '1', 'ApplicableSafetyStandardAgency'], 6492 '0x9175': ['LO', '1', 'ApplicableSafetyStandardDescription'], 6493 '0x9176': ['SQ', '1', 'OperatingModeSequence'], 6494 '0x9177': ['CS', '1', 'OperatingModeType'], 6495 '0x9178': ['CS', '1', 'OperatingMode'], 6496 '0x9179': ['CS', '1', 'SpecificAbsorptionRateDefinition'], 6497 '0x9180': ['CS', '1', 'GradientOutputType'], 6498 '0x9181': ['FD', '1', 'SpecificAbsorptionRateValue'], 6499 '0x9182': ['FD', '1', 'GradientOutput'], 6500 '0x9183': ['CS', '1', 'FlowCompensationDirection'], 6501 '0x9184': ['FD', '1', 'TaggingDelay'], 6502 '0x9185': ['ST', '1', 'RespiratoryMotionCompensationTechniqueDescription'], 6503 '0x9186': ['SH', '1', 'RespiratorySignalSourceID'], 6504 '0x9195': ['FD', '1', 'ChemicalShiftMinimumIntegrationLimitInHz'], 6505 '0x9196': ['FD', '1', 'ChemicalShiftMaximumIntegrationLimitInHz'], 6506 '0x9197': ['SQ', '1', 'MRVelocityEncodingSequence'], 6507 '0x9198': ['CS', '1', 'FirstOrderPhaseCorrection'], 6508 '0x9199': ['CS', '1', 'WaterReferencedPhaseCorrection'], 6509 '0x9200': ['CS', '1', 'MRSpectroscopyAcquisitionType'], 6510 '0x9214': ['CS', '1', 'RespiratoryCyclePosition'], 6511 '0x9217': ['FD', '1', 'VelocityEncodingMaximumValue'],
6512 '0x9218': ['FD', '1', 'TagSpacingSecondDimension'], 6513 '0x9219': ['SS', '1', 'TagAngleSecondAxis'], 6514 '0x9220': ['FD', '1', 'FrameAcquisitionDuration'], 6515 '0x9226': ['SQ', '1', 'MRImageFrameTypeSequence'], 6516 '0x9227': ['SQ', '1', 'MRSpectroscopyFrameTypeSequence'], 6517 '0x9231': ['US', '1', 'MRAcquisitionPhaseEncodingStepsInPlane'], 6518 '0x9232': ['US', '1', 'MRAcquisitionPhaseEncodingStepsOutOfPlane'], 6519 '0x9234': ['UL', '1', 'SpectroscopyAcquisitionPhaseColumns'], 6520 '0x9236': ['CS', '1', 'CardiacCyclePosition'], 6521 '0x9239': ['SQ', '1', 'SpecificAbsorptionRateSequence'], 6522 '0x9240': ['US', '1', 'RFEchoTrainLength'], 6523 '0x9241': ['US', '1', 'GradientEchoTrainLength'], 6524 '0x9250': ['CS', '1', 'ArterialSpinLabelingContrast'], 6525 '0x9251': ['SQ', '1', 'MRArterialSpinLabelingSequence'], 6526 '0x9252': ['LO', '1', 'ASLTechniqueDescription'], 6527 '0x9253': ['US', '1', 'ASLSlabNumber'], 6528 '0x9254': ['FD', '1', 'ASLSlabThickness'], 6529 '0x9255': ['FD', '3', 'ASLSlabOrientation'], 6530 '0x9256': ['FD', '3', 'ASLMidSlabPosition'], 6531 '0x9257': ['CS', '1', 'ASLContext'], 6532 '0x9258': ['UL', '1', 'ASLPulseTrainDuration'], 6533 '0x9259': ['CS', '1', 'ASLCrusherFlag'], 6534 '0x925A': ['FD', '1', 'ASLCrusherFlowLimit'], 6535 '0x925B': ['LO', '1', 'ASLCrusherDescription'], 6536 '0x925C': ['CS', '1', 'ASLBolusCutoffFlag'], 6537 '0x925D': ['SQ', '1', 'ASLBolusCutoffTimingSequence'], 6538 '0x925E': ['LO', '1', 'ASLBolusCutoffTechnique'], 6539 '0x925F': ['UL', '1', 'ASLBolusCutoffDelayTime'], 6540 '0x9260': ['SQ', '1', 'ASLSlabSequence'], 6541 '0x9295': ['FD', '1', 'ChemicalShiftMinimumIntegrationLimitInppm'], 6542 '0x9296': ['FD', '1', 'ChemicalShiftMaximumIntegrationLimitInppm'], 6543 '0x9297': ['CS', '1', 'WaterReferenceAcquisition'], 6544 '0x9298': ['IS', '1', 'EchoPeakPosition'], 6545 '0x9301': ['SQ', '1', 'CTAcquisitionTypeSequence'], 6546 '0x9302': ['CS', '1', 'AcquisitionType'], 6547 '0x9303': ['FD', '1', 'TubeAngle'], 6548 '0x9304': ['SQ', '1', 'CTAcquisitionDetailsSequence'], 6549 '0x9305': ['FD', '1', 'RevolutionTime'], 6550 '0x9306': ['FD', '1', 'SingleCollimationWidth'], 6551 '0x9307': ['FD', '1', 'TotalCollimationWidth'], 6552 '0x9308': ['SQ', '1', 'CTTableDynamicsSequence'], 6553 '0x9309': ['FD', '1', 'TableSpeed'], 6554 '0x9310': ['FD', '1', 'TableFeedPerRotation'], 6555 '0x9311': ['FD', '1', 'SpiralPitchFactor'], 6556 '0x9312': ['SQ', '1', 'CTGeometrySequence'], 6557 '0x9313': ['FD', '3', 'DataCollectionCenterPatient'], 6558 '0x9314': ['SQ', '1', 'CTReconstructionSequence'], 6559 '0x9315': ['CS', '1', 'ReconstructionAlgorithm'], 6560 '0x9316': ['CS', '1', 'ConvolutionKernelGroup'], 6561 '0x9317': ['FD', '2', 'ReconstructionFieldOfView'], 6562 '0x9318': ['FD', '3', 'ReconstructionTargetCenterPatient'], 6563 '0x9319': ['FD', '1', 'ReconstructionAngle'], 6564 '0x9320': ['SH', '1', 'ImageFilter'], 6565 '0x9321': ['SQ', '1', 'CTExposureSequence'], 6566 '0x9322': ['FD', '2', 'ReconstructionPixelSpacing'], 6567 '0x9323': ['CS', '1', 'ExposureModulationType'], 6568 '0x9324': ['FD', '1', 'EstimatedDoseSaving'], 6569 '0x9325': ['SQ', '1', 'CTXRayDetailsSequence'], 6570 '0x9326': ['SQ', '1', 'CTPositionSequence'], 6571 '0x9327': ['FD', '1', 'TablePosition'], 6572 '0x9328': ['FD', '1', 'ExposureTimeInms'], 6573 '0x9329': ['SQ', '1', 'CTImageFrameTypeSequence'], 6574 '0x9330': ['FD', '1', 'XRayTubeCurrentInmA'], 6575 '0x9332': ['FD', '1', 'ExposureInmAs'], 6576 '0x9333': ['CS', '1', 'ConstantVolumeFlag'], 6577 '0x9334': ['CS', '1', 'FluoroscopyFlag'], 6578 '0x9335': ['FD', '1', 'DistanceSourceToDataCollectionCenter'], 6579 '0x9337': ['US', '1', 'ContrastBolusAgentNumber'], 6580 '0x9338': ['SQ', '1', 'ContrastBolusIngredientCodeSequence'], 6581 '0x9340': ['SQ', '1', 'ContrastAdministrationProfileSequence'], 6582 '0x9341': ['SQ', '1', 'ContrastBolusUsageSequence'], 6583 '0x9342': ['CS', '1', 'ContrastBolusAgentAdministered'], 6584 '0x9343': ['CS', '1', 'ContrastBolusAgentDetected'], 6585 '0x9344': ['CS', '1', 'ContrastBolusAgentPhase'], 6586 '0x9345': ['FD', '1', 'CTDIvol'], 6587 '0x9346': ['SQ', '1', 'CTDIPhantomTypeCodeSequence'], 6588 '0x9351': ['FL', '1', 'CalciumScoringMassFactorPatient'], 6589 '0x9352': ['FL', '3', 'CalciumScoringMassFactorDevice'], 6590 '0x9353': ['FL', '1', 'EnergyWeightingFactor'], 6591 '0x9360': ['SQ', '1', 'CTAdditionalXRaySourceSequence'],
6592 '0x9401': ['SQ', '1', 'ProjectionPixelCalibrationSequence'], 6593 '0x9402': ['FL', '1', 'DistanceSourceToIsocenter'], 6594 '0x9403': ['FL', '1', 'DistanceObjectToTableTop'], 6595 '0x9404': ['FL', '2', 'ObjectPixelSpacingInCenterOfBeam'], 6596 '0x9405': ['SQ', '1', 'PositionerPositionSequence'], 6597 '0x9406': ['SQ', '1', 'TablePositionSequence'], 6598 '0x9407': ['SQ', '1', 'CollimatorShapeSequence'], 6599 '0x9410': ['CS', '1', 'PlanesInAcquisition'], 6600 '0x9412': ['SQ', '1', 'XAXRFFrameCharacteristicsSequence'], 6601 '0x9417': ['SQ', '1', 'FrameAcquisitionSequence'], 6602 '0x9420': ['CS', '1', 'XRayReceptorType'], 6603 '0x9423': ['LO', '1', 'AcquisitionProtocolName'], 6604 '0x9424': ['LT', '1', 'AcquisitionProtocolDescription'], 6605 '0x9425': ['CS', '1', 'ContrastBolusIngredientOpaque'], 6606 '0x9426': ['FL', '1', 'DistanceReceptorPlaneToDetectorHousing'], 6607 '0x9427': ['CS', '1', 'IntensifierActiveShape'], 6608 '0x9428': ['FL', '1-2', 'IntensifierActiveDimensions'], 6609 '0x9429': ['FL', '2', 'PhysicalDetectorSize'], 6610 '0x9430': ['FL', '2', 'PositionOfIsocenterProjection'], 6611 '0x9432': ['SQ', '1', 'FieldOfViewSequence'], 6612 '0x9433': ['LO', '1', 'FieldOfViewDescription'], 6613 '0x9434': ['SQ', '1', 'ExposureControlSensingRegionsSequence'], 6614 '0x9435': ['CS', '1', 'ExposureControlSensingRegionShape'], 6615 '0x9436': ['SS', '1', 'ExposureControlSensingRegionLeftVerticalEdge'], 6616 '0x9437': ['SS', '1', 'ExposureControlSensingRegionRightVerticalEdge'], 6617 '0x9438': ['SS', '1', 'ExposureControlSensingRegionUpperHorizontalEdge'], 6618 '0x9439': ['SS', '1', 'ExposureControlSensingRegionLowerHorizontalEdge'], 6619 '0x9440': ['SS', '2', 'CenterOfCircularExposureControlSensingRegion'], 6620 '0x9441': ['US', '1', 'RadiusOfCircularExposureControlSensingRegion'], 6621 '0x9442': ['SS', '2-n', 'VerticesOfThePolygonalExposureControlSensingRegion'], 6622 '0x9445': ['', '', ''], 6623 '0x9447': ['FL', '1', 'ColumnAngulationPatient'], 6624 '0x9449': ['FL', '1', 'BeamAngle'], 6625 '0x9451': ['SQ', '1', 'FrameDetectorParametersSequence'], 6626 '0x9452': ['FL', '1', 'CalculatedAnatomyThickness'], 6627 '0x9455': ['SQ', '1', 'CalibrationSequence'], 6628 '0x9456': ['SQ', '1', 'ObjectThicknessSequence'], 6629 '0x9457': ['CS', '1', 'PlaneIdentification'], 6630 '0x9461': ['FL', '1-2', 'FieldOfViewDimensionsInFloat'], 6631 '0x9462': ['SQ', '1', 'IsocenterReferenceSystemSequence'], 6632 '0x9463': ['FL', '1', 'PositionerIsocenterPrimaryAngle'], 6633 '0x9464': ['FL', '1', 'PositionerIsocenterSecondaryAngle'], 6634 '0x9465': ['FL', '1', 'PositionerIsocenterDetectorRotationAngle'], 6635 '0x9466': ['FL', '1', 'TableXPositionToIsocenter'], 6636 '0x9467': ['FL', '1', 'TableYPositionToIsocenter'], 6637 '0x9468': ['FL', '1', 'TableZPositionToIsocenter'], 6638 '0x9469': ['FL', '1', 'TableHorizontalRotationAngle'], 6639 '0x9470': ['FL', '1', 'TableHeadTiltAngle'], 6640 '0x9471': ['FL', '1', 'TableCradleTiltAngle'], 6641 '0x9472': ['SQ', '1', 'FrameDisplayShutterSequence'], 6642 '0x9473': ['FL', '1', 'AcquiredImageAreaDoseProduct'], 6643 '0x9474': ['CS', '1', 'CArmPositionerTabletopRelationship'], 6644 '0x9476': ['SQ', '1', 'XRayGeometrySequence'], 6645 '0x9477': ['SQ', '1', 'IrradiationEventIdentificationSequence'], 6646 '0x9504': ['SQ', '1', 'XRay3DFrameTypeSequence'], 6647 '0x9506': ['SQ', '1', 'ContributingSourcesSequence'], 6648 '0x9507': ['SQ', '1', 'XRay3DAcquisitionSequence'], 6649 '0x9508': ['FL', '1', 'PrimaryPositionerScanArc'], 6650 '0x9509': ['FL', '1', 'SecondaryPositionerScanArc'], 6651 '0x9510': ['FL', '1', 'PrimaryPositionerScanStartAngle'], 6652 '0x9511': ['FL', '1', 'SecondaryPositionerScanStartAngle'], 6653 '0x9514': ['FL', '1', 'PrimaryPositionerIncrement'], 6654 '0x9515': ['FL', '1', 'SecondaryPositionerIncrement'], 6655 '0x9516': ['DT', '1', 'StartAcquisitionDateTime'], 6656 '0x9517': ['DT', '1', 'EndAcquisitionDateTime'], 6657 '0x9518': ['SS', '1', 'PrimaryPositionerIncrementSign'], 6658 '0x9519': ['SS', '1', 'SecondaryPositionerIncrementSign'], 6659 '0x9524': ['LO', '1', 'ApplicationName'], 6660 '0x9525': ['LO', '1', 'ApplicationVersion'], 6661 '0x9526': ['LO', '1', 'ApplicationManufacturer'], 6662 '0x9527': ['CS', '1', 'AlgorithmType'], 6663 '0x9528': ['LO', '1', 'AlgorithmDescription'], 6664 '0x9530': ['SQ', '1', 'XRay3DReconstructionSequence'], 6665 '0x9531': ['LO', '1', 'ReconstructionDescription'], 6666 '0x9538': ['SQ', '1', 'PerProjectionAcquisitionSequence'], 6667 '0x9541': ['SQ', '1', 'DetectorPositionSequence'], 6668 '0x9542': ['SQ', '1', 'XRayAcquisitionDoseSequence'], 6669 '0x9543': ['FD', '1', 'XRaySourceIsocenterPrimaryAngle'], 6670 '0x9544': ['FD', '1', 'XRaySourceIsocenterSecondaryAngle'], 6671 '0x9545': ['FD', '1', 'BreastSupportIsocenterPrimaryAngle'], 6672 '0x9546': ['FD', '1', 'BreastSupportIsocenterSecondaryAngle'], 6673 '0x9547': ['FD', '1', 'BreastSupportXPositionToIsocenter'], 6674 '0x9548': ['FD', '1', 'BreastSupportYPositionToIsocenter'], 6675 '0x9549': ['FD', '1', 'BreastSupportZPositionToIsocenter'], 6676 '0x9550': ['FD', '1', 'DetectorIsocenterPrimaryAngle'], 6677 '0x9551': ['FD', '1', 'DetectorIsocenterSecondaryAngle'], 6678 '0x9552': ['FD', '1', 'DetectorXPositionToIsocenter'], 6679 '0x9553': ['FD', '1', 'DetectorYPositionToIsocenter'], 6680 '0x9554': ['FD', '1', 'DetectorZPositionToIsocenter'], 6681 '0x9555': ['SQ', '1', 'XRayGridSequence'], 6682 '0x9556': ['SQ', '1', 'XRayFilterSequence'], 6683 '0x9557': ['FD', '3', 'DetectorActiveAreaTLHCPosition'], 6684 '0x9558': ['FD', '6', 'DetectorActiveAreaOrientation'], 6685 '0x9559': ['CS', '1', 'PositionerPrimaryAngleDirection'], 6686 '0x9601': ['SQ', '1', 'DiffusionBMatrixSequence'], 6687 '0x9602': ['FD', '1', 'DiffusionBValueXX'], 6688 '0x9603': ['FD', '1', 'DiffusionBValueXY'], 6689 '0x9604': ['FD', '1', 'DiffusionBValueXZ'], 6690 '0x9605': ['FD', '1', 'DiffusionBValueYY'], 6691 '0x9606': ['FD', '1', 'DiffusionBValueYZ'], 6692 '0x9607': ['FD', '1', 'DiffusionBValueZZ'], 6693 '0x9701': ['DT', '1', 'DecayCorrectionDateTime'], 6694 '0x9715': ['FD', '1', 'StartDensityThreshold'], 6695 '0x9716': ['FD', '1', 'StartRelativeDensityDifferenceThreshold'], 6696 '0x9717': ['FD', '1', 'StartCardiacTriggerCountThreshold'], 6697 '0x9718': ['FD', '1', 'StartRespiratoryTriggerCountThreshold'], 6698 '0x9719': ['FD', '1', 'TerminationCountsThreshold'], 6699 '0x9720': ['FD', '1', 'TerminationDensityThreshold'], 6700 '0x9721': ['FD', '1', 'TerminationRelativeDensityThreshold'], 6701 '0x9722': ['FD', '1', 'TerminationTimeThreshold'], 6702 '0x9723': ['FD', '1', 'TerminationCardiacTriggerCountThreshold'], 6703 '0x9724': ['FD', '1', 'TerminationRespiratoryTriggerCountThreshold'], 6704 '0x9725': ['CS', '1', 'DetectorGeometry'], 6705 '0x9726': ['FD', '1', 'TransverseDetectorSeparation'], 6706 '0x9727': ['FD', '1', 'AxialDetectorDimension'], 6707 '0x9729': ['US', '1', 'RadiopharmaceuticalAgentNumber'], 6708 '0x9732': ['SQ', '1', 'PETFrameAcquisitionSequence'],
6709 '0x9733': ['SQ', '1', 'PETDetectorMotionDetailsSequence'], 6710 '0x9734': ['SQ', '1', 'PETTableDynamicsSequence'], 6711 '0x9735': ['SQ', '1', 'PETPositionSequence'], 6712 '0x9736': ['SQ', '1', 'PETFrameCorrectionFactorsSequence'], 6713 '0x9737': ['SQ', '1', 'RadiopharmaceuticalUsageSequence'], 6714 '0x9738': ['CS', '1', 'AttenuationCorrectionSource'], 6715 '0x9739': ['US', '1', 'NumberOfIterations'], 6716 '0x9740': ['US', '1', 'NumberOfSubsets'], 6717 '0x9749': ['SQ', '1', 'PETReconstructionSequence'], 6718 '0x9751': ['SQ', '1', 'PETFrameTypeSequence'], 6719 '0x9755': ['CS', '1', 'TimeOfFlightInformationUsed'], 6720 '0x9756': ['CS', '1', 'ReconstructionType'], 6721 '0x9758': ['CS', '1', 'DecayCorrected'], 6722 '0x9759': ['CS', '1', 'AttenuationCorrected'], 6723 '0x9760': ['CS', '1', 'ScatterCorrected'], 6724 '0x9761': ['CS', '1', 'DeadTimeCorrected'], 6725 '0x9762': ['CS', '1', 'GantryMotionCorrected'], 6726 '0x9763': ['CS', '1', 'PatientMotionCorrected'], 6727 '0x9764': ['CS', '1', 'CountLossNormalizationCorrected'], 6728 '0x9765': ['CS', '1', 'RandomsCorrected'], 6729 '0x9766': ['CS', '1', 'NonUniformRadialSamplingCorrected'], 6730 '0x9767': ['CS', '1', 'SensitivityCalibrated'], 6731 '0x9768': ['CS', '1', 'DetectorNormalizationCorrection'], 6732 '0x9769': ['CS', '1', 'IterativeReconstructionMethod'], 6733 '0x9770': ['CS', '1', 'AttenuationCorrectionTemporalRelationship'], 6734 '0x9771': ['SQ', '1', 'PatientPhysiologicalStateSequence'], 6735 '0x9772': ['SQ', '1', 'PatientPhysiologicalStateCodeSequence'], 6736 '0x9801': ['FD', '1-n', 'DepthsOfFocus'], 6737 '0x9803': ['SQ', '1', 'ExcludedIntervalsSequence'], 6738 '0x9804': ['DT', '1', 'ExclusionStartDateTime'], 6739 '0x9805': ['FD', '1', 'ExclusionDuration'], 6740 '0x9806': ['SQ', '1', 'USImageDescriptionSequence'], 6741 '0x9807': ['SQ', '1', 'ImageDataTypeSequence'], 6742 '0x9808': ['CS', '1', 'DataType'], 6743 '0x9809': ['SQ', '1', 'TransducerScanPatternCodeSequence'], 6744 '0x980B': ['CS', '1', 'AliasedDataType'], 6745 '0x980C': ['CS', '1', 'PositionMeasuringDeviceUsed'], 6746 '0x980D': ['SQ', '1', 'TransducerGeometryCodeSequence'], 6747 '0x980E': ['SQ', '1', 'TransducerBeamSteeringCodeSequence'], 6748 '0x980F': ['SQ', '1', 'TransducerApplicationCodeSequence'], 6749 '0x9810': ['xs', '1', 'ZeroVelocityPixelValue'], 6750 '0xA001': ['SQ', '1', 'ContributingEquipmentSequence'], 6751 '0xA002': ['DT', '1', 'ContributionDateTime'], 6752 '0xA003': ['ST', '1', 'ContributionDescription'] 6753 }, 6754 '0x0020': { 6755 '0x0000': ['UL', '1', 'GenericGroupLength'], 6756 '0x000D': ['UI', '1', 'StudyInstanceUID'], 6757 '0x000E': ['UI', '1', 'SeriesInstanceUID'], 6758 '0x0010': ['SH', '1', 'StudyID'], 6759 '0x0011': ['IS', '1', 'SeriesNumber'], 6760 '0x0012': ['IS', '1', 'AcquisitionNumber'], 6761 '0x0013': ['IS', '1', 'InstanceNumber'], 6762 '0x0014': ['IS', '1', 'IsotopeNumber'], 6763 '0x0015': ['IS', '1', 'PhaseNumber'], 6764 '0x0016': ['IS', '1', 'IntervalNumber'], 6765 '0x0017': ['IS', '1', 'TimeSlotNumber'], 6766 '0x0018': ['IS', '1', 'AngleNumber'], 6767 '0x0019': ['IS', '1', 'ItemNumber'], 6768 '0x0020': ['CS', '2', 'PatientOrientation'], 6769 '0x0022': ['IS', '1', 'OverlayNumber'], 6770 '0x0024': ['IS', '1', 'CurveNumber'], 6771 '0x0026': ['IS', '1', 'LUTNumber'], 6772 '0x0030': ['DS', '3', 'ImagePosition'], 6773 '0x0032': ['DS', '3', 'ImagePositionPatient'], 6774 '0x0035': ['DS', '6', 'ImageOrientation'], 6775 '0x0037': ['DS', '6', 'ImageOrientationPatient'], 6776 '0x0050': ['DS', '1', 'Location'], 6777 '0x0052': ['UI', '1', 'FrameOfReferenceUID'], 6778 '0x0060': ['CS', '1', 'Laterality'], 6779 '0x0062': ['CS', '1', 'ImageLaterality'], 6780 '0x0070': ['LO', '1', 'ImageGeometryType'], 6781 '0x0080': ['CS', '1-n', 'MaskingImage'], 6782 '0x00AA': ['IS', '1', 'ReportNumber'], 6783 '0x0100': ['IS', '1', 'TemporalPositionIdentifier'], 6784 '0x0105': ['IS', '1', 'NumberOfTemporalPositions'], 6785 '0x0110': ['DS', '1', 'TemporalResolution'], 6786 '0x0200': ['UI', '1', 'SynchronizationFrameOfReferenceUID'], 6787 '0x0242': ['UI', '1', 'SOPInstanceUIDOfConcatenationSource'], 6788 '0x1000': ['IS', '1', 'SeriesInStudy'], 6789 '0x1001': ['IS', '1', 'AcquisitionsInSeries'], 6790 '0x1002': ['IS', '1', 'ImagesInAcquisition'], 6791 '0x1003': ['IS', '1', 'ImagesInSeries'], 6792 '0x1004': ['IS', '1', 'AcquisitionsInStudy'], 6793 '0x1005': ['IS', '1', 'ImagesInStudy'], 6794 '0x1020': ['LO', '1-n', 'Reference'], 6795 '0x1040': ['LO', '1', 'PositionReferenceIndicator'], 6796 '0x1041': ['DS', '1', 'SliceLocation'], 6797 '0x1070': ['IS', '1-n', 'OtherStudyNumbers'], 6798 '0x1200': ['IS', '1', 'NumberOfPatientRelatedStudies'], 6799 '0x1202': ['IS', '1', 'NumberOfPatientRelatedSeries'], 6800 '0x1204': ['IS', '1', 'NumberOfPatientRelatedInstances'], 6801 '0x1206': ['IS', '1', 'NumberOfStudyRelatedSeries'], 6802 '0x1208': ['IS', '1', 'NumberOfStudyRelatedInstances'], 6803 '0x1209': ['IS', '1', 'NumberOfSeriesRelatedInstances'], 6804 '0x3100': ['CS', '1-n', 'SourceImageIDs'], 6805 '0x3401': ['CS', '1', 'ModifyingDeviceID'], 6806 '0x3402': ['CS', '1', 'ModifiedImageID'], 6807 '0x3403': ['DA', '1', 'ModifiedImageDate'], 6808 '0x3404': ['LO', '1', 'ModifyingDeviceManufacturer'], 6809 '0x3405': ['TM', '1', 'ModifiedImageTime'], 6810 '0x3406': ['LO', '1', 'ModifiedImageDescription'], 6811 '0x4000': ['LT', '1', 'ImageComments'], 6812 '0x5000': ['AT', '1-n', 'OriginalImageIdentification'], 6813 '0x5002': ['LO', '1-n', 'OriginalImageIdentificationNomenclature'], 6814 '0x9056': ['SH', '1', 'StackID'], 6815 '0x9057': ['UL', '1', 'InStackPositionNumber'], 6816 '0x9071': ['SQ', '1', 'FrameAnatomySequence'], 6817 '0x9072': ['CS', '1', 'FrameLaterality'], 6818 '0x9111': ['SQ', '1', 'FrameContentSequence'], 6819 '0x9113': ['SQ', '1', 'PlanePositionSequence'], 6820 '0x9116': ['SQ', '1', 'PlaneOrientationSequence'], 6821 '0x9128': ['UL', '1', 'TemporalPositionIndex'], 6822 '0x9153': ['FD', '1', 'NominalCardiacTriggerDelayTime'], 6823 '0x9154': ['FL', '1', 'NominalCardiacTriggerTimePriorToRPeak'], 6824 '0x9155': ['FL', '1', 'ActualCardiacTriggerTimePriorToRPeak'], 6825 '0x9156': ['US', '1', 'FrameAcquisitionNumber'], 6826 '0x9157': ['UL', '1-n', 'DimensionIndexValues'], 6827 '0x9158': ['LT', '1', 'FrameComments'], 6828 '0x9161': ['UI', '1', 'ConcatenationUID'], 6829 '0x9162': ['US', '1', 'InConcatenationNumber'], 6830 '0x9163': ['US', '1', 'InConcatenationTotalNumber'], 6831 '0x9164': ['UI', '1', 'DimensionOrganizationUID'], 6832 '0x9165': ['AT', '1', 'DimensionIndexPointer'], 6833 '0x9167': ['AT', '1', 'FunctionalGroupPointer'], 6834 '0x9170': ['SQ', '1', 'UnassignedSharedConvertedAttributesSequence'], 6835 '0x9171': ['SQ', '1', 'UnassignedPerFrameConvertedAttributesSequence'], 6836 '0x9172': ['SQ', '1', 'ConversionSourceAttributesSequence'], 6837 '0x9213': ['LO', '1', 'DimensionIndexPrivateCreator'], 6838 '0x9221': ['SQ', '1', 'DimensionOrganizationSequence'], 6839 '0x9222': ['SQ', '1', 'DimensionIndexSequence'], 6840 '0x9228': ['UL', '1', 'ConcatenationFrameOffsetNumber'], 6841 '0x9238': ['LO', '1', 'FunctionalGroupPrivateCreator'], 6842 '0x9241': ['FL', '1', 'NominalPercentageOfCardiacPhase'], 6843 '0x9245': ['FL', '1', 'NominalPercentageOfRespiratoryPhase'], 6844 '0x9246': ['FL', '1', 'StartingRespiratoryAmplitude'], 6845 '0x9247': ['CS', '1', 'StartingRespiratoryPhase'], 6846 '0x9248': ['FL', '1', 'EndingRespiratoryAmplitude'], 6847 '0x9249': ['CS', '1', 'EndingRespiratoryPhase'], 6848 '0x9250': ['CS', '1', 'RespiratoryTriggerType'], 6849 '0x9251': ['FD', '1', 'RRIntervalTimeNominal'], 6850 '0x9252': ['FD', '1', 'ActualCardiacTriggerDelayTime'], 6851 '0x9253': ['SQ', '1', 'RespiratorySynchronizationSequence'], 6852 '0x9254': ['FD', '1', 'RespiratoryIntervalTime'], 6853 '0x9255': ['FD', '1', 'NominalRespiratoryTriggerDelayTime'], 6854 '0x9256': ['FD', '1', 'RespiratoryTriggerDelayThreshold'], 6855 '0x9257': ['FD', '1', 'ActualRespiratoryTriggerDelayTime'], 6856 '0x9301': ['FD', '3', 'ImagePositionVolume'], 6857 '0x9302': ['FD', '6', 'ImageOrientationVolume'], 6858 '0x9307': ['CS', '1', 'UltrasoundAcquisitionGeometry'], 6859 '0x9308': ['FD', '3', 'ApexPosition'], 6860 '0x9309': ['FD', '16', 'VolumeToTransducerMappingMatrix'], 6861 '0x930A': ['FD', '16', 'VolumeToTableMappingMatrix'], 6862 '0x930B': ['CS', '1', 'VolumeToTransducerRelationship'], 6863 '0x930C': ['CS', '1', 'PatientFrameOfReferenceSource'], 6864 '0x930D': ['FD', '1', 'TemporalPositionTimeOffset'], 6865 '0x930E': ['SQ', '1', 'PlanePositionVolumeSequence'], 6866 '0x930F': ['SQ', '1', 'PlaneOrientationVolumeSequence'], 6867 '0x9310': ['SQ', '1', 'TemporalPositionSequence'], 6868 '0x9311': ['CS', '1', 'DimensionOrganizationType'], 6869 '0x9312': ['UI', '1', 'VolumeFrameOfReferenceUID'], 6870 '0x9313': ['UI', '1', 'TableFrameOfReferenceUID'], 6871 '0x9421': ['LO', '1', 'DimensionDescriptionLabel'], 6872 '0x9450': ['SQ', '1', 'PatientOrientationInFrameSequence'], 6873 '0x9453': ['LO', '1', 'FrameLabel'], 6874 '0x9518': ['US', '1-n', 'AcquisitionIndex'], 6875 '0x9529': ['SQ', '1', 'ContributingSOPInstancesReferenceSequence'], 6876 '0x9536': ['US', '1', 'ReconstructionIndex'] 6877 }, 6878 '0x0022': { 6879 '0x0000': ['UL', '1', 'GenericGroupLength'], 6880 '0x0001': ['US', '1', 'LightPathFilterPassThroughWavelength'], 6881 '0x0002': ['US', '2', 'LightPathFilterPassBand'], 6882 '0x0003': ['US', '1', 'ImagePathFilterPassThroughWavelength'], 6883 '0x0004': ['US', '2', 'ImagePathFilterPassBand'], 6884 '0x0005': ['CS', '1', 'PatientEyeMovementCommanded'], 6885 '0x0006': ['SQ', '1', 'PatientEyeMovementCommandCodeSequence'], 6886 '0x0007': ['FL', '1', 'SphericalLensPower'], 6887 '0x0008': ['FL', '1', 'CylinderLensPower'], 6888 '0x0009': ['FL', '1', 'CylinderAxis'], 6889 '0x000A': ['FL', '1', 'EmmetropicMagnification'], 6890 '0x000B': ['FL', '1', 'IntraOcularPressure'], 6891 '0x000C': ['FL', '1', 'HorizontalFieldOfView'], 6892 '0x000D': ['CS', '1', 'PupilDilated'], 6893 '0x000E': ['FL', '1', 'DegreeOfDilation'], 6894 '0x0010': ['FL', '1', 'StereoBaselineAngle'], 6895 '0x0011': ['FL', '1', 'StereoBaselineDisplacement'], 6896 '0x0012': ['FL', '1', 'StereoHorizontalPixelOffset'], 6897 '0x0013': ['FL', '1', 'StereoVerticalPixelOffset'], 6898 '0x0014': ['FL', '1', 'StereoRotation'], 6899 '0x0015': ['SQ', '1', 'AcquisitionDeviceTypeCodeSequence'], 6900 '0x0016': ['SQ', '1', 'IlluminationTypeCodeSequence'], 6901 '0x0017': ['SQ', '1', 'LightPathFilterTypeStackCodeSequence'], 6902 '0x0018': ['SQ', '1', 'ImagePathFilterTypeStackCodeSequence'], 6903 '0x0019': ['SQ', '1', 'LensesCodeSequence'], 6904 '0x001A': ['SQ', '1', 'ChannelDescriptionCodeSequence'], 6905 '0x001B': ['SQ', '1', 'RefractiveStateSequence'], 6906 '0x001C': ['SQ', '1', 'MydriaticAgentCodeSequence'], 6907 '0x001D': ['SQ', '1', 'RelativeImagePositionCodeSequence'], 6908 '0x001E': ['FL', '1', 'CameraAngleOfView'], 6909 '0x0020': ['SQ', '1', 'StereoPairsSequence'], 6910 '0x0021': ['SQ', '1', 'LeftImageSequence'], 6911 '0x0022': ['SQ', '1', 'RightImageSequence'], 6912 '0x0028': ['CS', '1', 'StereoPairsPresent'], 6913 '0x0030': ['FL', '1', 'AxialLengthOfTheEye'], 6914 '0x0031': ['SQ', '1', 'OphthalmicFrameLocationSequence'], 6915 '0x0032': ['FL', '2-2n', 'ReferenceCoordinates'], 6916 '0x0035': ['FL', '1', 'DepthSpatialResolution'], 6917 '0x0036': ['FL', '1', 'MaximumDepthDistortion'], 6918 '0x0037': ['FL', '1', 'AlongScanSpatialResolution'], 6919 '0x0038': ['FL', '1', 'MaximumAlongScanDistortion'], 6920 '0x0039': ['CS', '1', 'OphthalmicImageOrientation'], 6921 '0x0041': ['FL', '1', 'DepthOfTransverseImage'], 6922 '0x0042': ['SQ', '1', 'MydriaticAgentConcentrationUnitsSequence'], 6923 '0x0048': ['FL', '1', 'AcrossScanSpatialResolution'], 6924 '0x0049': ['FL', '1', 'MaximumAcrossScanDistortion'], 6925 '0x004E': ['DS', '1', 'MydriaticAgentConcentration'], 6926 '0x0055': ['FL', '1', 'IlluminationWaveLength'], 6927 '0x0056': ['FL', '1', 'IlluminationPower'], 6928 '0x0057': ['FL', '1', 'IlluminationBandwidth'], 6929 '0x0058': ['SQ', '1', 'MydriaticAgentSequence'], 6930 '0x1007': ['SQ', '1', 'OphthalmicAxialMeasurementsRightEyeSequence'], 6931 '0x1008': ['SQ', '1', 'OphthalmicAxialMeasurementsLeftEyeSequence'], 6932 '0x1009': ['CS', '1', 'OphthalmicAxialMeasurementsDeviceType'], 6933 '0x1010': ['CS', '1', 'OphthalmicAxialLengthMeasurementsType'], 6934 '0x1012': ['SQ', '1', 'OphthalmicAxialLengthSequence'], 6935 '0x1019': ['FL', '1', 'OphthalmicAxialLength'], 6936 '0x1024': ['SQ', '1', 'LensStatusCodeSequence'], 6937 '0x1025': ['SQ', '1', 'VitreousStatusCodeSequence'], 6938 '0x1028': ['SQ', '1', 'IOLFormulaCodeSequence'], 6939 '0x1029': ['LO', '1', 'IOLFormulaDetail'], 6940 '0x1033': ['FL', '1', 'KeratometerIndex'], 6941 '0x1035': ['SQ', '1', 'SourceOfOphthalmicAxialLengthCodeSequence'], 6942 '0x1037': ['FL', '1', 'TargetRefraction'], 6943 '0x1039': ['CS', '1', 'RefractiveProcedureOccurred'], 6944 '0x1040': ['SQ', '1', 'RefractiveSurgeryTypeCodeSequence'], 6945 '0x1044': ['SQ', '1', 'OphthalmicUltrasoundMethodCodeSequence'], 6946 '0x1050': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSequence'], 6947 '0x1053': ['FL', '1', 'IOLPower'], 6948 '0x1054': ['FL', '1', 'PredictedRefractiveError'], 6949 '0x1059': ['FL', '1', 'OphthalmicAxialLengthVelocity'], 6950 '0x1065': ['LO', '1', 'LensStatusDescription'], 6951 '0x1066': ['LO', '1', 'VitreousStatusDescription'], 6952 '0x1090': ['SQ', '1', 'IOLPowerSequence'], 6953 '0x1092': ['SQ', '1', 'LensConstantSequence'], 6954 '0x1093': ['LO', '1', 'IOLManufacturer'], 6955 '0x1094': ['LO', '1', 'LensConstantDescription'], 6956 '0x1095': ['LO', '1', 'ImplantName'], 6957 '0x1096': ['SQ', '1', 'KeratometryMeasurementTypeCodeSequence'], 6958 '0x1097': ['LO', '1', 'ImplantPartNumber'], 6959 '0x1100': ['SQ', '1', 'ReferencedOphthalmicAxialMeasurementsSequence'], 6960 '0x1101': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSegmentNameCodeSequence'], 6961 '0x1103': ['SQ', '1', 'RefractiveErrorBeforeRefractiveSurgeryCodeSequence'], 6962 '0x1121': ['FL', '1', 'IOLPowerForExactEmmetropia'], 6963 '0x1122': ['FL', '1', 'IOLPowerForExactTargetRefraction'], 6964 '0x1125': ['SQ', '1', 'AnteriorChamberDepthDefinitionCodeSequence'], 6965 '0x1127': ['SQ', '1', 'LensThicknessSequence'], 6966 '0x1128': ['SQ', '1', 'AnteriorChamberDepthSequence'], 6967 '0x1130': ['FL', '1', 'LensThickness'], 6968 '0x1131': ['FL', '1', 'AnteriorChamberDept
6968h'], 6969 '0x1132': ['SQ', '1', 'SourceOfLensThicknessDataCodeSequence'], 6970 '0x1133': ['SQ', '1', 'SourceOfAnteriorChamberDepthDataCodeSequence'], 6971 '0x1134': ['SQ', '1', 'SourceOfRefractiveMeasurementsSequence'], 6972 '0x1135': ['SQ', '1', 'SourceOfRefractiveMeasurementsCodeSequence'], 6973 '0x1140': ['CS', '1', 'OphthalmicAxialLengthMeasurementModified'], 6974 '0x1150': ['SQ', '1', 'OphthalmicAxialLengthDataSourceCodeSequence'], 6975 '0x1153': ['SQ', '1', 'OphthalmicAxialLengthAcquisitionMethodCodeSequence'], 6976 '0x1155': ['FL', '1', 'SignalToNoiseRatio'], 6977 '0x1159': ['LO', '1', 'OphthalmicAxialLengthDataSourceDescription'], 6978 '0x1210': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsTotalLengthSequence'], 6979 '0x1211': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSegmentalLengthSequence'], 6980 '0x1212': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsLengthSummationSequence'], 6981 '0x1220': ['SQ', '1', 'UltrasoundOphthalmicAxialLengthMeasurementsSequence'], 6982 '0x1225': ['SQ', '1', 'OpticalOphthalmicAxialLengthMeasurementsSequence'], 6983 '0x1230': ['SQ', '1', 'UltrasoundSelectedOphthalmicAxialLengthSequence'], 6984 '0x1250': ['SQ', '1', 'OphthalmicAxialLengthSelectionMethodCodeSequence'], 6985 '0x1255': ['SQ', '1', 'OpticalSelectedOphthalmicAxialLengthSequence'], 6986 '0x1257': ['SQ', '1', 'SelectedSegmentalOphthalmicAxialLengthSequence'], 6987 '0x1260': ['SQ', '1', 'SelectedTotalOphthalmicAxialLengthSequence'], 6988 '0x1262': ['SQ', '1', 'OphthalmicAxialLengthQualityMetricSequence'], 6989 '0x1265': ['SQ', '1', 'OphthalmicAxialLengthQualityMetricTypeCodeSequence'], 6990 '0x1273': ['LO', '1', 'OphthalmicAxialLengthQualityMetricTypeDescription'], 6991 '0x1300': ['SQ', '1', 'IntraocularLensCalculationsRightEyeSequence'], 6992 '0x1310': ['SQ', '1', 'IntraocularLensCalculationsLeftEyeSequence'], 6993 '0x1330': ['SQ', '1', 'ReferencedOphthalmicAxialLengthMeasurementQCImageSequence'], 6994 '0x1415': ['CS', '1', 'OphthalmicMappingDeviceType'], 6995 '0x1420': ['SQ', '1', 'AcquisitionMethodCodeSequence'], 6996 '0x1423': ['SQ', '1', 'AcquisitionMethodAlgorithmSequence'], 6997 '0x1436': ['SQ', '1', 'OphthalmicThicknessMapTypeCodeSequence'], 6998 '0x1443': ['SQ', '1', 'OphthalmicThicknessMappingNormalsSequence'], 6999 '0x1445': ['SQ', '1', 'RetinalThicknessDefinitionCodeSequence'], 7000 '0x1450': ['SQ', '1', 'PixelValueMappingToCodedConceptSequence'], 7001 '0x1452': ['xs', '1', 'MappedPixelValue'], 7002 '0x1454': ['LO', '1', 'PixelValueMappingExplanation'], 7003 '0x1458': ['SQ', '1', 'OphthalmicThicknessMapQualityThresholdSequence'], 7004 '0x1460': ['FL', '1', 'OphthalmicThicknessMapThresholdQualityRating'], 7005 '0x1463': ['FL', '2', 'AnatomicStructureReferencePoint'], 7006 '0x1465': ['SQ', '1', 'RegistrationToLocalizerSequence'], 7007 '0x1466': ['CS', '1', 'RegisteredLocalizerUnits'], 7008 '0x1467': ['FL', '2', 'RegisteredLocalizerTopLeftHandCorner'], 7009 '0x1468': ['FL', '2', 'RegisteredLocalizerBottomRightHandCorner'], 7010 '0x1470': ['SQ', '1', 'OphthalmicThicknessMapQualityRatingSequence'], 7011 '0x1472': ['SQ', '1', 'RelevantOPTAttributesSequence'], 7012 '0x1512': ['SQ', '1', 'TransformationMethodCodeSequence'], 7013 '0x1513': ['SQ', '1', 'TransformationAlgorithmSequence'], 7014 '0x1515': ['CS', '1', 'OphthalmicAxialLengthMethod'], 7015 '0x1517': ['FL', '1', 'OphthalmicFOV'], 7016 '0x1518': ['SQ', '1', 'TwoDimensionalToThreeDimensionalMapSequence'], 7017 '0x1525': ['SQ', '1', 'WideFieldOphthalmicPhotographyQualityRatingSequence'], 7018 '0x1526': ['SQ', '1', 'WideFieldOphthalmicPhotographyQualityThresholdSequence'], 7019 '0x1527': ['FL', '1', 'WideFieldOphthalmicPhotographyThresholdQualityRating'], 7020 '0x1528': ['FL', '1', 'XCoordinatesCenterPixelViewAngle'], 7021 '0x1529': ['FL', '1', 'YCoordinatesCenterPixelViewAngle'], 7022 '0x1530': ['UL', '1', 'NumberOfMapPoints'], 7023 '0x1531': ['OF', '1', 'TwoDimensionalToThreeDimensionalMapData'] 7024 }, 7025 '0x0024': { 7026 '0x0000': ['UL', '1', 'GenericGroupLength'], 7027 '0x0010': ['FL', '1', 'VisualFieldHorizontalExtent'], 7028 '0x0011': ['FL', '1', 'VisualFieldVerticalExtent'], 7029 '0x0012': ['CS', '1', 'VisualFieldShape'], 7030 '0x0016': ['SQ', '1', 'ScreeningTestModeCodeSequence'], 7031 '0x0018': ['FL', '1', 'MaximumStimulusLuminance'],
7032 '0x0020': ['FL', '1', 'BackgroundLuminance'], 7033 '0x0021': ['SQ', '1', 'StimulusColorCodeSequence'], 7034 '0x0024': ['SQ', '1', 'BackgroundIlluminationColorCodeSequence'], 7035 '0x0025': ['FL', '1', 'StimulusArea'], 7036 '0x0028': ['FL', '1', 'StimulusPresentationTime'], 7037 '0x0032': ['SQ', '1', 'FixationSequence'], 7038 '0x0033': ['SQ', '1', 'FixationMonitoringCodeSequence'], 7039 '0x0034': ['SQ', '1', 'VisualFieldCatchTrialSequence'], 7040 '0x0035': ['US', '1', 'FixationCheckedQuantity'], 7041 '0x0036': ['US', '1', 'PatientNotProperlyFixatedQuantity'], 7042 '0x0037': ['CS', '1', 'PresentedVisualStimuliDataFlag'], 7043 '0x0038': ['US', '1', 'NumberOfVisualStimuli'], 7044 '0x0039': ['CS', '1', 'ExcessiveFixationLossesDataFlag'], 7045 '0x0040': ['CS', '1', 'ExcessiveFixationLosses'], 7046 '0x0042': ['US', '1', 'StimuliRetestingQuantity'], 7047 '0x0044': ['LT', '1', 'CommentsOnPatientPerformanceOfVisualField'], 7048 '0x0045': ['CS', '1', 'FalseNegativesEstimateFlag'], 7049 '0x0046': ['FL', '1', 'FalseNegativesEstimate'], 7050 '0x0048': ['US', '1', 'NegativeCatchTrialsQuantity'], 7051 '0x0050': ['US', '1', 'FalseNegativesQuantity'], 7052 '0x0051': ['CS', '1', 'ExcessiveFalseNegativesDataFlag'], 7053 '0x0052': ['CS', '1', 'ExcessiveFalseNegatives'], 7054 '0x0053': ['CS', '1', 'FalsePositivesEstimateFlag'], 7055 '0x0054': ['FL', '1', 'FalsePositivesEstimate'], 7056 '0x0055': ['CS', '1', 'CatchTrialsDataFlag'], 7057 '0x0056': ['US', '1', 'PositiveCatchTrialsQuantity'], 7058 '0x0057': ['CS', '1', 'TestPointNormalsDataFlag'], 7059 '0x0058': ['SQ', '1', 'TestPointNormalsSequence'], 7060 '0x0059': ['CS', '1', 'GlobalDeviationProbabilityNormalsFlag'], 7061 '0x0060': ['US', '1', 'FalsePositivesQuantity'], 7062 '0x0061': ['CS', '1', 'ExcessiveFalsePositivesDataFlag'], 7063 '0x0062': ['CS', '1', 'ExcessiveFalsePositives'], 7064 '0x0063': ['CS', '1', 'VisualFieldTestNormalsFlag'], 7065 '0x0064': ['SQ', '1', 'ResultsNormalsSequence'], 7066 '0x0065': ['SQ', '1', 'AgeCorrectedSensitivityDeviationAlgorithmSequence'], 7067 '0x0066': ['FL', '1', 'GlobalDeviationFromNormal'], 7068 '0x0067': ['SQ', '1', 'GeneralizedDefectSensitivityDeviationAlgorithmSequence'], 7069 '0x0068': ['FL', '1', 'LocalizedDeviationFromNormal'], 7070 '0x0069': ['LO', '1', 'PatientReliabilityIndicator'], 7071 '0x0070': ['FL', '1', 'VisualFieldMeanSensitivity'], 7072 '0x0071': ['FL', '1', 'GlobalDeviationProbability'], 7073 '0x0072': ['CS', '1', 'LocalDeviationProbabilityNormalsFlag'], 7074 '0x0073': ['FL', '1', 'LocalizedDeviationProbability'], 7075 '0x0074': ['CS', '1', 'ShortTermFluctuationCalculated'], 7076 '0x0075': ['FL', '1', 'ShortTermFluctuation'], 7077 '0x0076': ['CS', '1', 'ShortTermFluctuationProbabilityCalculated'], 7078 '0x0077': ['FL', '1', 'ShortTermFluctuationProbability'], 7079 '0x0078': ['CS', '1', 'CorrectedLocalizedDeviationFromNormalCalculated'], 7080 '0x0079': ['FL', '1', 'CorrectedLocalizedDeviationFromNormal'], 7081 '0x0080': ['CS', '1', 'CorrectedLocalizedDeviationFromNormalProbabilityCalculated'], 7082 '0x0081': ['FL', '1', 'CorrectedLocalizedDeviationFromNormalProbability'], 7083 '0x0083': ['SQ', '1', 'GlobalDeviationProbabilitySequence'], 7084 '0x0085': ['SQ', '1', 'LocalizedDeviationProbabilitySequence'], 7085 '0x0086': ['CS', '1', 'FovealSensitivityMeasured'], 7086 '0x0087': ['FL', '1', 'FovealSensitivity'], 7087 '0x0088': ['FL', '1', 'VisualFieldTestDuration'], 7088 '0x0089': ['SQ', '1', 'VisualFieldTestPointSequence'], 7089 '0x0090': ['FL', '1', 'VisualFieldTestPointXCoordinate'], 7090 '0x0091': ['FL', '1', 'VisualFieldTestPointYCoordinate'], 7091 '0x0092': ['FL', '1', 'AgeCorrectedSensitivityDeviationValue'], 7092 '0x0093': ['CS', '1', 'StimulusResults'], 7093 '0x0094': ['FL', '1', 'SensitivityValue'], 7094 '0x0095': ['CS', '1', 'RetestStimulusSeen'], 7095 '0x0096': ['FL', '1', 'RetestSensitivityValue'], 7096 '0x0097': ['SQ', '1', 'VisualFieldTestPointNormalsSequence'], 7097 '0x0098': ['FL', '1', 'QuantifiedDefect'], 7098 '0x0100': ['FL', '1', 'AgeCorrectedSensitivityDeviationProbabilityValue'], 7099 '0x0102': ['CS', '1', 'GeneralizedDefectCorrectedSensitivityDeviationFlag'], 7100 '0x0103': ['FL', '1', 'GeneralizedDefectCorrectedSensitivityDeviationValue'], 7101 '0x0104': ['FL', '1', 'GeneralizedDefectCorrectedSensitivityDeviationProbabilityValue'], 7102 '0x0105': ['FL', '1', 'MinimumSensitivityValue'], 7103 '0x0106': ['CS', '1', 'BlindSpotLocalized'], 7104 '0x0107': ['FL', '1', 'BlindSpotXCoordinate'], 7105 '0x0108': ['FL', '1', 'BlindSpotYCoordinate'], 7106 '0x0110': ['SQ', '1', 'VisualAcuityMeasurementSequence'], 7107 '0x0112': ['SQ', '1', 'RefractiveParametersUsedOnPatientSequence'], 7108 '0x0113': ['CS', '1', 'MeasurementLaterality'], 7109 '0x0114': ['SQ', '1', 'OphthalmicPatientClinicalInformationLeftEyeSequence'], 7110 '0x0115': ['SQ', '1', 'OphthalmicPatientClinicalInformationRightEyeSequence'], 7111 '0x0117': ['CS', '1', 'FovealPointNormativeDataFlag'], 7112 '0x0118': ['FL', '1', 'FovealPointProbabilityValue'], 7113 '0x0120': ['CS', '1', 'ScreeningBaselineMeasured'], 7114 '0x0122': ['SQ', '1', 'ScreeningBaselineMeasuredSequence'], 7115 '0x0124': ['CS', '1', 'ScreeningBaselineType'], 7116 '0x0126': ['FL', '1', 'ScreeningBaselineValue'], 7117 '0x0202': ['LO', '1', 'AlgorithmSource'], 7118 '0x0306': ['LO', '1', 'DataSetName'], 7119 '0x0307': ['LO', '1', 'DataSetVersion'], 7120 '0x0308': ['LO', '1', 'DataSetSource'], 7121 '0x0309': ['LO', '1', 'DataSetDescription'], 7122 '0x0317': ['SQ', '1', 'VisualFieldTestReliabilityGlobalIndexSequence'], 7123 '0x0320': ['SQ', '1', 'VisualFieldGlobalResultsIndexSequence'], 7124 '0x0325': ['SQ', '1', 'DataObservationSequence'], 7125 '0x0338': ['CS', '1', 'IndexNormalsFlag'], 7126 '0x0341': ['FL', '1', 'IndexProbability'], 7127 '0x0344': ['SQ', '1', 'IndexProbabilitySequence'] 7128 }, 7129 '0x0028': { 7130 '0x0000': ['UL', '1', 'GenericGroupLength'], 7131 '0x0002': ['US', '1', 'SamplesPerPixel'], 7132 '0x0003': ['US', '1', 'SamplesPerPixelUsed'], 7133 '0x0004': ['CS', '1', 'PhotometricInterpretation'], 7134 '0x0005': ['US', '1', 'ImageDimensions'], 7135 '0x0006': ['US', '1', 'PlanarConfiguration'], 7136 '0x0008': ['IS', '1', 'NumberOfFrames'], 7137 '0x0009': ['AT', '1-n', 'FrameIncrementPointer'], 7138 '0x000A': ['AT', '1-n', 'FrameDimensionPointer'], 7139 '0x0010': ['US', '1', 'Rows'], 7140 '0x0011': ['US', '1', 'Columns'], 7141 '0x0012': ['US', '1', 'Planes'], 7142 '0x0014': ['US', '1', 'UltrasoundColorDataPresent'], 7143 '0x0020': ['', '', ''], 7144 '0x0030': ['DS', '2', 'PixelSpacing'], 7145 '0x0031': ['DS', '2', 'ZoomFactor'], 7146 '0x0032': ['DS', '2', 'ZoomCenter'], 7147 '0x0034': ['IS', '2', 'PixelAspectRatio'], 7148 '0x0040': ['CS', '1', 'ImageFormat'], 7149 '0x0050': ['LO', '1-n', 'ManipulatedImage'], 7150 '0x0051': ['CS', '1-n', 'CorrectedImage'], 7151 '0x005F': ['LO', '1', 'CompressionRecognitionCode'], 7152 '0x0060': ['CS', '1', 'CompressionCode'], 7153 '0x0061': ['SH', '1', 'CompressionOriginator'], 7154 '0x0062': ['LO', '1', 'CompressionLabel'], 7155 '0x0063': ['SH', '1', 'CompressionDescription'], 7156 '0x0065': ['CS', '1-n', 'CompressionSequence'], 7157 '0x0066': ['AT', '1-n', 'CompressionStepPointers'], 7158 '0x0068': ['US', '1', 'RepeatInterval'], 7159 '0x0069': ['US', '1', 'BitsGrouped'], 7160 '0x0070': ['US', '1-n', 'PerimeterTable'], 7161 '0x0071': ['xs', '1', 'PerimeterValue'], 7162 '0x0080': ['US', '1', 'PredictorRows'], 7163 '0x0081': ['US', '1', 'PredictorColumns'], 7164 '0x0082': ['US', '1-n', 'PredictorConstants'], 7165 '0x0090': ['CS', '1', 'BlockedPixels'], 7166 '0x0091': ['US', '1', 'BlockRows'], 7167 '0x0092': ['US', '1', 'BlockColumns'], 7168 '0x0093': ['US', '1', 'RowOverlap'], 7169 '0x0094': ['US', '1', 'ColumnOverlap'], 7170 '0x0100': ['US', '1', 'BitsAllocated'], 7171 '0x0101': ['US', '1', 'BitsStored'], 7172 '0x0102': ['US', '1', 'HighBit'], 7173 '0x0103': ['US', '1', 'PixelRepresentation'], 7174 '0x0104': ['xs', '1', 'SmallestValidPixelValue'], 7175 '0x0105': ['xs', '1', 'LargestValidPixelValue'], 7176 '0x0106': ['xs', '1', 'SmallestImagePixelValue'], 7177 '0x0107': ['xs', '1', 'LargestImagePixelValue'], 7178 '0x0108': ['xs', '1', 'SmallestPixelValueInSeries'], 7179 '0x0109': ['xs', '1', 'LargestPixelValueInSeries'], 7180 '0x0110': ['xs', '1', 'SmallestImagePixelValueInPlane'], 7181 '0x0111': ['xs', '1', 'LargestImagePixelValueInPlane'], 7182 '0x0120': ['xs', '1', 'PixelPaddingValue'], 7183 '0x0121': ['xs', '1', 'PixelPaddingRangeLimit'], 7184 '0x0122': ['FL', '1', 'FloatPixelPaddingValue'], 7185 '0x0123': ['FD', '1', 'DoubleFloatPixelPaddingValue'], 7186 '0x0124': ['FL', '1', 'FloatPixelPaddingRangeLimit'], 7187 '0x0125': ['FD', '1', 'DoubleFloatPixelPaddingRangeLimit'], 7188 '0x0200': ['US', '1', 'ImageLocation'], 7189 '0x0300': ['CS', '1', 'QualityControlImage'], 7190 '0x0301': ['CS', '1', 'BurnedInAnnotation'], 7191 '0x0302': ['CS', '1', 'RecognizableVisualFeatures'], 7192 '0x0303': ['CS', '1', 'LongitudinalTemporalInformationModified'], 7193 '0x0304': ['UI', '1', 'ReferencedColorPaletteInstanceUID'], 7194 '0x0400': ['LO', '1', 'TransformLabel'], 7195 '0x0401': ['LO', '1', 'TransformVersionNumber'], 7196 '0x0402': ['US', '1', 'NumberOfTransformSteps'], 7197 '0x0403': ['LO', '1-n', 'SequenceOfCompressedData'], 7198 '0x0404': ['AT', '1-n', 'DetailsOfCoefficients'], 7199 '0x04x0': ['US', '1', 'RowsForNthOrderCoefficients'], 7200 '0x04x1': ['US', '1', 'ColumnsForNthOrderCoefficients'], 7201 '0x04x2': ['LO', '1-n', 'CoefficientCoding'], 7202 '0x04x3': ['AT', '1-n', 'CoefficientCodingPointers'], 7203 '0x0700': ['LO', '1', 'DCTLabel'], 7204 '0x0701': ['CS', '1-n', 'DataBlockDescription'], 7205 '0x0702': ['AT', '1-n', 'DataBlock'], 7206 '0x0710': ['US', '1', 'NormalizationFactorFormat'], 7207 '0x0720': ['US', '1', 'ZonalMapNumberFormat'], 7208 '0x0721': ['AT', '1-n', 'ZonalMapLocation'], 7209 '0x0722': ['US', '1', 'ZonalMapFormat'], 7210 '0x0730': ['US', '1', 'AdaptiveMapFormat'], 7211 '0x0740': ['US', '1', 'CodeNumberFormat'], 7212 '0x08x0': ['CS', '1-n', 'CodeLabel'], 7213 '0x08x2': ['US', '1', 'NumberOfTables'], 7214 '0x08x3': ['AT', '1-n', 'CodeTableLocation'], 7215 '0x08x4': ['US', '1', 'BitsForCodeWord'], 7216 '0x08x8': ['AT', '1-n', 'ImageDataLocation'], 7217 '0x0A02': ['CS', '1', 'PixelSpacingCalibrationType'], 7218 '0x0A04': ['LO', '1', 'PixelSpacingCalibrationDescription'], 7219 '0x1040': ['CS', '1', 'PixelIntensityRelationship'], 7220 '0x1041': ['SS', '1', 'PixelIntensityRelationshipSign'], 7221 '0x1050': ['DS', '1-n', 'WindowCenter'], 7222 '0x1051': ['DS', '1-n', 'WindowWidth'], 7223 '0x1052': ['DS', '1', 'RescaleIntercept'], 7224 '0x1053': ['DS', '1', 'RescaleSlope'], 7225 '0x1054': ['LO', '1', 'RescaleType'], 7226 '0x1055': ['LO', '1-n', 'WindowCenterWidthExplanation'], 7227 '0x1056': ['CS', '1', 'VOILUTFunction'], 7228 '0x1080': ['CS', '1', 'GrayScale'], 7229 '0x1090': ['CS', '1', 'RecommendedViewingMode'], 7230 '0x1100': ['xs', '3', 'GrayLookupTableDescriptor'], 7231 '0x1101': ['xs', '3', 'RedPaletteColorLookupTableDescriptor'], 7232 '0x1102': ['xs', '3', 'GreenPaletteColorLookupTableDescriptor'], 7233 '0x1103': ['xs', '3', 'BluePaletteColorLookupTableDescriptor'], 7234 '0x1104': ['US', '3', 'AlphaPaletteColorLookupTableDescriptor'], 7235 '0x1111': ['xs', '4', 'LargeRedPaletteColorLookupTableDescriptor'], 7236 '0x1112': ['xs', '4', 'LargeGreenPaletteColorLookupTableDescriptor'], 7237 '0x1113': ['xs', '4', 'LargeBluePaletteColorLookupTableDescriptor'], 7238 '0x1199': ['UI', '1', 'PaletteColorLookupTableUID'], 7239 '0x1200': ['US or SS or OW', '1-n or 1', 'GrayLookupTableData'], 7240 '0x1201': ['OW', '1', 'RedPaletteColorLookupTableData'], 7241 '0x1202': ['OW', '1', 'GreenPaletteColorLookupTableData'], 7242 '0x1203': ['OW', '1', 'BluePaletteColorLookupTableData'], 7243 '0x1204': ['OW', '1', 'AlphaPaletteColorLookupTableData'], 7244 '0x1211': ['OW', '1', 'LargeRedPaletteColorLookupTableData'], 7245 '0x1212': ['OW', '1', 'LargeGreenPaletteColorLookupTableData'], 7246 '0x1213': ['OW', '1', 'LargeBluePaletteColorLookupTableData'], 7247 '0x1214': ['UI', '1', 'LargePaletteColorLookupTableUID'], 7248 '0x1221': ['OW', '1', 'SegmentedRedPaletteColorLookupTableData'], 7249 '0x1222': ['OW', '1', 'SegmentedGreenPaletteColorLookupTableData'], 7250 '0x1223': ['OW', '1', 'SegmentedBluePaletteColorLookupTableData'], 7251 '0x1300': ['CS', '1', 'BreastImplantPresent'], 7252 '0x1350': ['CS', '1', 'PartialView'], 7253 '0x1351': ['ST', '1', 'PartialViewDescription'], 7254 '0x1352': ['SQ', '1', 'PartialViewCodeSequence'], 7255 '0x135A': ['CS', '1', 'SpatialLocationsPreserved'], 7256 '0x1401': ['SQ', '1', 'DataFrameAssignmentSequence'], 7257 '0x1402': ['CS', '1', 'DataPathAssignment'],
7258 '0x1403': ['US', '1', 'BitsMappedToColorLookupTable'], 7259 '0x1404': ['SQ', '1', 'BlendingLUT1Sequence'], 7260 '0x1405': ['CS', '1', 'BlendingLUT1TransferFunction'], 7261 '0x1406': ['FD', '1', 'BlendingWeightConstant'], 7262 '0x1407': ['US', '3', 'BlendingLookupTableDescriptor'], 7263 '0x1408': ['OW', '1', 'BlendingLookupTableData'], 7264 '0x140B': ['SQ', '1', 'EnhancedPaletteColorLookupTableSequence'], 7265 '0x140C': ['SQ', '1', 'BlendingLUT2Sequence'], 7266 '0x140D': ['CS', '1', 'BlendingLUT2TransferFunction'], 7267 '0x140E': ['CS', '1', 'DataPathID'], 7268 '0x140F': ['CS', '1', 'RGBLUTTransferFunction'], 7269 '0x1410': ['CS', '1', 'AlphaLUTTransferFunction'], 7270 '0x2000': ['OB', '1', 'ICCProfile'], 7271 '0x2110': ['CS', '1', 'LossyImageCompression'], 7272 '0x2112': ['DS', '1-n', 'LossyImageCompressionRatio'], 7273 '0x2114': ['CS', '1-n', 'LossyImageCompressionMethod'], 7274 '0x3000': ['SQ', '1', 'ModalityLUTSequence'], 7275 '0x3002': ['xs', '3', 'LUTDescriptor'], 7276 '0x3003': ['LO', '1', 'LUTExplanation'], 7277 '0x3004': ['LO', '1', 'ModalityLUTType'], 7278 '0x3006': ['US or OW', '1-n or 1', 'LUTData'], 7279 '0x3010': ['SQ', '1', 'VOILUTSequence'], 7280 '0x3110': ['SQ', '1', 'SoftcopyVOILUTSequence'], 7281 '0x4000': ['LT', '1', 'ImagePresentationComments'], 7282 '0x5000': ['SQ', '1', 'BiPlaneAcquisitionSequence'], 7283 '0x6010': ['US', '1', 'RepresentativeFrameNumber'], 7284 '0x6020': ['US', '1-n', 'FrameNumbersOfInterest'], 7285 '0x6022': ['LO', '1-n', 'FrameOfInterestDescription'], 7286 '0x6023': ['CS', '1-n', 'FrameOfInterestType'], 7287 '0x6030': ['US', '1-n', 'MaskPointers'], 7288 '0x6040': ['US', '1-n', 'RWavePointer'], 7289 '0x6100': ['SQ', '1', 'MaskSubtractionSequence'], 7290 '0x6101': ['CS', '1', 'MaskOperation'], 7291 '0x6102': ['US', '2-2n', 'ApplicableFrameRange'], 7292 '0x6110': ['US', '1-n', 'MaskFrameNumbers'], 7293 '0x6112': ['US', '1', 'ContrastFrameAveraging'], 7294 '0x6114': ['FL', '2', 'MaskSubPixelShift'], 7295 '0x6120': ['SS', '1', 'TIDOffset'], 7296 '0x6190': ['ST', '1', 'MaskOperationExplanation'], 7297 '0x7000': ['SQ', '1', 'EquipmentAdministratorSequence'], 7298 '0x7001': ['US', '1', 'NumberOfDisplaySubsystems'], 7299 '0x7002': ['US', '1', 'CurrentConfigurationID'], 7300 '0x7003': ['US', '1', 'DisplaySubsystemID'], 7301 '0x7004': ['SH', '1', 'DisplaySubsystemName'], 7302 '0x7005': ['LO', '1', 'DisplaySubsystemDescription'], 7303 '0x7006': ['CS', '1', 'SystemStatus'], 7304 '0x7007': ['LO', '1', 'SystemStatusComment'], 7305 '0x7008': ['SQ', '1', 'TargetLuminanceCharacteristicsSequence'], 7306 '0x7009': ['US', '1', 'LuminanceCharacteristicsID'], 7307 '0x700A': ['SQ', '1', 'DisplaySubsystemConfigurationSequence'], 7308 '0x700B': ['US', '1', 'ConfigurationID'], 7309 '0x700C': ['SH', '1', 'ConfigurationName'], 7310 '0x700D': ['LO', '1', 'ConfigurationDescription'], 7311 '0x700E': ['US', '1', 'ReferencedTargetLuminanceCharacteristicsID'], 7312 '0x700F': ['SQ', '1', 'QAResultsSequence'], 7313 '0x7010': ['SQ', '1', 'DisplaySubsystemQAResultsSequence'], 7314 '0x7011': ['SQ', '1', 'ConfigurationQAResultsSequence'], 7315 '0x7012': ['SQ', '1', 'MeasurementEquipmentSequence'], 7316 '0x7013': ['CS', '1-n', 'MeasurementFunctions'], 7317 '0x7014': ['CS', '1', 'MeasurementEquipmentType'], 7318 '0x7015': ['SQ', '1', 'VisualEvaluationResultSequence'], 7319 '0x7016': ['SQ', '1', 'DisplayCalibrationResultSequence'], 7320 '0x7017': ['US', '1', 'DDLValue'], 7321 '0x7018': ['FL', '2', 'CIExyWhitePoint'], 7322 '0x7019': ['CS', '1', 'DisplayFunctionType'], 7323 '0x701A': ['FL', '1', 'GammaValue'], 7324 '0x701B': ['US', '1', 'NumberOfLuminancePoints'], 7325 '0x701C': ['SQ', '1', 'LuminanceResponseSequence'], 7326 '0x701D': ['FL', '1', 'TargetMinimumLuminance'], 7327 '0x701E': ['FL', '1', 'TargetMaximumLuminance'], 7328 '0x701F': ['FL', '1', 'LuminanceValue'], 7329 '0x7020': ['LO', '1', 'LuminanceResponseDescription'], 7330 '0x7021': ['CS', '1', 'WhitePointFlag'], 7331 '0x7022': ['SQ', '1', 'DisplayDeviceTypeCodeSequence'], 7332 '0x7023': ['SQ', '1', 'DisplaySubsystemSequence'], 7333 '0x7024': ['SQ', '1', 'LuminanceResultSequence'], 7334 '0x7025': ['CS', '1', 'AmbientLightValueSource'], 7335 '0x7026': ['CS', '1-n', 'MeasuredCharacteristics'], 7336 '0x7027': ['SQ', '1', 'LuminanceUniformityResultSequence'], 7337 '0x7028': ['SQ', '1', 'VisualEvaluationTestSequence'], 7338 '0x7029': ['CS', '1', 'TestResult'], 7339 '0x702A': ['LO', '1', 'TestResultComment'], 7340 '0x702B': ['CS', '1', 'TestImageValidation'], 7341 '0x702C': ['SQ', '1', 'TestPatternCodeSequence'], 7342 '0x702D': ['SQ', '1', 'MeasurementPatternCodeSequence'], 7343 '0x702E': ['SQ', '1', 'VisualEvaluationMethodCodeSequence'], 7344 '0x7FE0': ['UR', '1', 'PixelDataProviderURL'], 7345 '0x9001': ['UL', '1', 'DataPointRows'], 7346 '0x9002': ['UL', '1', 'DataPointColumns'], 7347 '0x9003': ['CS', '1', 'SignalDomainColumns'], 7348 '0x9099': ['US', '1', 'LargestMonochromePixelValue'], 7349 '0x9108': ['CS', '1', 'DataRepresentation'], 7350 '0x9110': ['SQ', '1', 'PixelMeasuresSequence'], 7351 '0x9132': ['SQ', '1', 'FrameVOILUTSequence'], 7352 '0x9145': ['SQ', '1', 'PixelValueTransformationSequence'], 7353 '0x9235': ['CS', '1', 'SignalDomainRows'], 7354 '0x9411': ['FL', '1', 'DisplayFilterPercentage'], 7355 '0x9415': ['SQ', '1', 'FramePixelShiftSequence'], 7356 '0x9416': ['US', '1', 'SubtractionItemID'], 7357 '0x9422': ['SQ', '1', 'PixelIntensityRelationshipLUTSequence'], 7358 '0x9443': ['SQ', '1', 'FramePixelDataPropertiesSequence'], 7359 '0x9444': ['CS', '1', 'GeometricalProperties'], 7360 '0x9445': ['FL', '1', 'GeometricMaximumDistortion'], 7361 '0x9446': ['CS', '1-n', 'ImageProcessingApplied'], 7362 '0x9454': ['CS', '1', 'MaskSelectionMode'], 7363 '0x9474': ['CS', '1', 'LUTFunction'], 7364 '0x9478': ['FL', '1', 'MaskVisibilityPercentage'], 7365 '0x9501': ['SQ', '1', 'PixelShiftSequence'], 7366 '0x9502': ['SQ', '1', 'RegionPixelShiftSequence'], 7367 '0x9503': ['SS', '2-2n', 'VerticesOfTheRegion'], 7368 '0x9505': ['SQ', '1', 'MultiFramePresentationSequence'], 7369 '0x9506': ['US', '2-2n', 'PixelShiftFrameRange'], 7370 '0x9507': ['US', '2-2n', 'LUTFrameRange'], 7371 '0x9520': ['DS', '16', 'ImageToEquipmentMappingMatrix'],
7372 '0x9537': ['CS', '1', 'EquipmentCoordinateSystemIdentification'] 7373 }, 7374 '0x0032': { 7375 '0x0000': ['UL', '1', 'GenericGroupLength'], 7376 '0x000A': ['CS', '1', 'StudyStatusID'], 7377 '0x000C': ['CS', '1', 'StudyPriorityID'], 7378 '0x0012': ['LO', '1', 'StudyIDIssuer'], 7379 '0x0032': ['DA', '1', 'StudyVerifiedDate'], 7380 '0x0033': ['TM', '1', 'StudyVerifiedTime'], 7381 '0x0034': ['DA', '1', 'StudyReadDate'], 7382 '0x0035': ['TM', '1', 'StudyReadTime'], 7383 '0x1000': ['DA', '1', 'ScheduledStudyStartDate'], 7384 '0x1001': ['TM', '1', 'ScheduledStudyStartTime'], 7385 '0x1010': ['DA', '1', 'ScheduledStudyStopDate'], 7386 '0x1011': ['TM', '1', 'ScheduledStudyStopTime'], 7387 '0x1020': ['LO', '1', 'ScheduledStudyLocation'], 7388 '0x1021': ['AE', '1-n', 'ScheduledStudyLocationAETitle'], 7389 '0x1030': ['LO', '1', 'ReasonForStudy'], 7390 '0x1031': ['SQ', '1', 'RequestingPhysicianIdentificationSequence'], 7391 '0x1032': ['PN', '1', 'RequestingPhysician'], 7392 '0x1033': ['LO', '1', 'RequestingService'], 7393 '0x1034': ['SQ', '1', 'RequestingServiceCodeSequence'], 7394 '0x1040': ['DA', '1', 'StudyArrivalDate'], 7395 '0x1041': ['TM', '1', 'StudyArrivalTime'], 7396 '0x1050': ['DA', '1', 'StudyCompletionDate'], 7397 '0x1051': ['TM', '1', 'StudyCompletionTime'], 7398 '0x1055': ['CS', '1', 'StudyComponentStatusID'], 7399 '0x1060': ['LO', '1', 'RequestedProcedureDescription'], 7400 '0x1064': ['SQ', '1', 'RequestedProcedureCodeSequence'], 7401 '0x1070': ['LO', '1', 'RequestedContrastAgent'], 7402 '0x4000': ['LT', '1', 'StudyComments'] 7403 }, 7404 '0x0038': { 7405 '0x0000': ['UL', '1', 'GenericGroupLength'], 7406 '0x0004': ['SQ', '1', 'ReferencedPatientAliasSequence'], 7407 '0x0008': ['CS', '1', 'VisitStatusID'], 7408 '0x0010': ['LO', '1', 'AdmissionID'], 7409 '0x0011': ['LO', '1', 'IssuerOfAdmissionID'], 7410 '0x0014': ['SQ', '1', 'IssuerOfAdmissionIDSequence'], 7411 '0x0016': ['LO', '1', 'RouteOfAdmissions'], 7412 '0x001A': ['DA', '1', 'ScheduledAdmissionDate'], 7413 '0x001B': ['TM', '1', 'ScheduledAdmissionTime'], 7414 '0x001C': ['DA', '1', 'ScheduledDischargeDate'], 7415 '0x001D': ['TM', '1', 'ScheduledDischargeTime'], 7416 '0x001E': ['LO', '1', 'ScheduledPatientInstitutionResidence'], 7417 '0x0020': ['DA', '1', 'AdmittingDate'], 7418 '0x0021': ['TM', '1', 'AdmittingTime'], 7419 '0x0030': ['DA', '1', 'DischargeDate'], 7420 '0x0032': ['TM', '1', 'DischargeTime'], 7421 '0x0040': ['LO', '1', 'DischargeDiagnosisDescription'], 7422 '0x0044': ['SQ', '1', 'DischargeDiagnosisCodeSequence'], 7423 '0x0050': ['LO', '1', 'SpecialNeeds'], 7424 '0x0060': ['LO', '1', 'ServiceEpisodeID'], 7425 '0x0061': ['LO', '1', 'IssuerOfServiceEpisodeID'], 7426 '0x0062': ['LO', '1', 'ServiceEpisodeDescription'], 7427 '0x0064': ['SQ', '1', 'IssuerOfServiceEpisodeIDSequence'], 7428 '0x0100': ['SQ', '1', 'PertinentDocumentsSequence'], 7429 '0x0101': ['SQ', '1', 'PertinentResourcesSequence'], 7430 '0x0102': ['LO', '1', 'ResourceDescription'], 7431 '0x0300': ['LO', '1', 'CurrentPatientLocation'], 7432 '0x0400': ['LO', '1', 'PatientInstitutionResidence'], 7433 '0x0500': ['LO', '1', 'PatientState'], 7434 '0x0502': ['SQ', '1', 'PatientClinicalTrialParticipationSequence'], 7435 '0x4000': ['LT', '1', 'VisitComments'] 7436 }, 7437 '0x003A': { 7438 '0x0000': ['UL', '1', 'GenericGroupLength'], 7439 '0x0004': ['CS', '1', 'WaveformOriginality'], 7440 '0x0005': ['US', '1', 'NumberOfWaveformChannels'], 7441 '0x0010': ['UL', '1', 'NumberOfWaveformSamples'], 7442 '0x001A': ['DS', '1', 'SamplingFrequency'], 7443 '0x0020': ['SH', '1', 'MultiplexGroupLabel'], 7444 '0x0200': ['SQ', '1', 'ChannelDefinitionSequence'], 7445 '0x0202': ['IS', '1', 'WaveformChannelNumber'], 7446 '0x0203': ['SH', '1', 'ChannelLabel'], 7447 '0x0205': ['CS', '1-n', 'ChannelStatus'], 7448 '0x0208': ['SQ', '1', 'ChannelSourceSequence'], 7449 '0x0209': ['SQ', '1', 'ChannelSourceModifiersSequence'], 7450 '0x020A': ['SQ', '1', 'SourceWaveformSequence'], 7451 '0x020C': ['LO', '1', 'ChannelDerivationDescription'], 7452 '0x0210': ['DS', '1', 'ChannelSensitivity'], 7453 '0x0211': ['SQ', '1', 'ChannelSensitivityUnitsSequence'], 7454 '0x0212': ['DS', '1', 'ChannelSensitivityCorrectionFactor'], 7455 '0x0213': ['DS', '1', 'ChannelBaseline'], 7456 '0x0214': ['DS', '1', 'ChannelTimeSkew'], 7457 '0x0215': ['DS', '1', 'ChannelSampleSkew'], 7458 '0x0218': ['DS', '1', 'ChannelOffset'], 7459 '0x021A': ['US', '1', 'WaveformBitsStored'], 7460 '0x0220': ['DS', '1', 'FilterLowFrequency'], 7461 '0x0221': ['DS', '1', 'FilterHighFrequency'], 7462 '0x0222': ['DS', '1', 'NotchFilterFrequency'], 7463 '0x0223': ['DS', '1', 'NotchFilterBandwidth'], 7464 '0x0230': ['FL', '1', 'WaveformDataDisplayScale'],
7465 '0x0231': ['US', '3', 'WaveformDisplayBackgroundCIELabValue'], 7466 '0x0240': ['SQ', '1', 'WaveformPresentationGroupSequence'], 7467 '0x0241': ['US', '1', 'PresentationGroupNumber'], 7468 '0x0242': ['SQ', '1', 'ChannelDisplaySequence'], 7469 '0x0244': ['US', '3', 'ChannelRecommendedDisplayCIELabValue'], 7470 '0x0245': ['FL', '1', 'ChannelPosition'], 7471 '0x0246': ['CS', '1', 'DisplayShadingFlag'], 7472 '0x0247': ['FL', '1', 'FractionalChannelDisplayScale'], 7473 '0x0248': ['FL', '1', 'AbsoluteChannelDisplayScale'], 7474 '0x0300': ['SQ', '1', 'MultiplexedAudioChannelsDescriptionCodeSequence'], 7475 '0x0301': ['IS', '1', 'ChannelIdentificationCode'], 7476 '0x0302': ['CS', '1', 'ChannelMode'] 7477 }, 7478 '0x0040': { 7479 '0x0000': ['UL', '1', 'GenericGroupLength'], 7480 '0x0001': ['AE', '1-n', 'ScheduledStationAETitle'], 7481 '0x0002': ['DA', '1', 'ScheduledProcedureStepStartDate'], 7482 '0x0003': ['TM', '1', 'ScheduledProcedureStepStartTime'], 7483 '0x0004': ['DA', '1', 'ScheduledProcedureStepEndDate'], 7484 '0x0005': ['TM', '1', 'ScheduledProcedureStepEndTime'], 7485 '0x0006': ['PN', '1', 'ScheduledPerformingPhysicianName'], 7486 '0x0007': ['LO', '1', 'ScheduledProcedureStepDescription'], 7487 '0x0008': ['SQ', '1', 'ScheduledProtocolCodeSequence'], 7488 '0x0009': ['SH', '1', 'ScheduledProcedureStepID'], 7489 '0x000A': ['SQ', '1', 'StageCodeSequence'], 7490 '0x000B': ['SQ', '1', 'ScheduledPerformingPhysicianIdentificationSequence'], 7491 '0x0010': ['SH', '1-n', 'ScheduledStationName'], 7492 '0x0011': ['SH', '1', 'ScheduledProcedureStepLocation'], 7493 '0x0012': ['LO', '1', 'PreMedication'], 7494 '0x0020': ['CS', '1', 'ScheduledProcedureStepStatus'], 7495 '0x0026': ['SQ', '1', 'OrderPlacerIdentifierSequence'], 7496 '0x0027': ['SQ', '1', 'OrderFillerIdentifierSequence'], 7497 '0x0031': ['UT', '1', 'LocalNamespaceEntityID'], 7498 '0x0032': ['UT', '1', 'UniversalEntityID'], 7499 '0x0033': ['CS', '1', 'UniversalEntityIDType'], 7500 '0x0035': ['CS', '1', 'IdentifierTypeCode'], 7501 '0x0036': ['SQ', '1', 'AssigningFacilitySequence'], 7502 '0x0039': ['SQ', '1', 'AssigningJurisdictionCodeSequence'], 7503 '0x003A': ['SQ', '1', 'AssigningAgencyOrDepartmentCodeSequence'], 7504 '0x0100': ['SQ', '1', 'ScheduledProcedureStepSequence'], 7505 '0x0220': ['SQ', '1', 'ReferencedNonImageCompositeSOPInstanceSequence'], 7506 '0x0241': ['AE', '1', 'PerformedStationAETitle'], 7507 '0x0242': ['SH', '1', 'PerformedStationName'], 7508 '0x0243': ['SH', '1', 'PerformedLocation'], 7509 '0x0244': ['DA', '1', 'PerformedProcedureStepStartDate'], 7510 '0x0245': ['TM', '1', 'PerformedProcedureStepStartTime'], 7511 '0x0250': ['DA', '1', 'PerformedProcedureStepEndDate'], 7512 '0x0251': ['TM', '1', 'PerformedProcedureStepEndTime'], 7513 '0x0252': ['CS', '1', 'PerformedProcedureStepStatus'], 7514 '0x0253': ['SH', '1', 'PerformedProcedureStepID'], 7515 '0x0254': ['LO', '1', 'PerformedProcedureStepDescription'], 7516 '0x0255': ['LO', '1', 'PerformedProcedureTypeDescription'], 7517 '0x0260': ['SQ', '1', 'PerformedProtocolCodeSequence'], 7518 '0x0261': ['CS', '1', 'PerformedProtocolType'], 7519 '0x0270': ['SQ', '1', 'ScheduledStepAttributesSequence'], 7520 '0x0275': ['SQ', '1', 'RequestAttributesSequence'], 7521 '0x0280': ['ST', '1', 'CommentsOnThePerformedProcedureStep'], 7522 '0x0281': ['SQ', '1', 'PerformedProcedureStepDiscontinuationReasonCodeSequence'], 7523 '0x0293': ['SQ', '1', 'QuantitySequence'], 7524 '0x0294': ['DS', '1', 'Quantity'], 7525 '0x0295': ['SQ', '1', 'MeasuringUnitsSequence'], 7526 '0x0296': ['SQ', '1', 'BillingItemSequence'], 7527 '0x0300': ['US', '1', 'TotalTimeOfFluoroscopy'], 7528 '0x0301': ['US', '1', 'TotalNumberOfExposures'], 7529 '0x0302': ['US', '1', 'EntranceDose'], 7530 '0x0303': ['US', '1-2', 'ExposedArea'], 7531 '0x0306': ['DS', '1', 'DistanceSourceToEntrance'], 7532 '0x0307': ['DS', '1', 'DistanceSourceToSupport'], 7533 '0x030E': ['SQ', '1', 'ExposureDoseSequence'], 7534 '0x0310': ['ST', '1', 'CommentsOnRadiationDose'], 7535 '0x0312': ['DS', '1', 'XRayOutput'], 7536 '0x0314': ['DS', '1', 'HalfValueLayer'], 7537 '0x0316': ['DS', '1', 'OrganDose'], 7538 '0x0318': ['CS', '1', 'OrganExposed'], 7539 '0x0320': ['SQ', '1', 'BillingProcedureStepSequence'], 7540 '0x0321': ['SQ', '1', 'FilmConsumptionSequence'], 7541 '0x0324': ['SQ', '1', 'BillingSuppliesAndDevicesSequence'], 7542 '0x0330': ['SQ', '1', 'ReferencedProcedureStepSequence'], 7543 '0x0340': ['SQ', '1', 'PerformedSeriesSequence'], 7544 '0x0400': ['LT', '1', 'CommentsOnTheScheduledProcedureStep'], 7545 '0x0440': ['SQ', '1', 'ProtocolContextSequence'], 7546 '0x0441': ['SQ', '1', 'ContentItemModifierSequence'], 7547 '0x0500': ['SQ', '1', 'ScheduledSpecimenSequence'], 7548 '0x050A': ['LO', '1', 'SpecimenAccessionNumber'], 7549 '0x0512': ['LO', '1', 'ContainerIdentifier'], 7550 '0x0513': ['SQ', '1', 'IssuerOfTheContainerIdentifierSequence'], 7551 '0x0515': ['SQ', '1', 'AlternateContainerIdentifierSequence'], 7552 '0x0518': ['SQ', '1', 'ContainerTypeCodeSequence'], 7553 '0x051A': ['LO', '1', 'ContainerDescription'], 7554 '0x0520': ['SQ', '1', 'ContainerComponentSequence'], 7555 '0x0550': ['SQ', '1', 'SpecimenSequence'], 7556 '0x0551': ['LO', '1', 'SpecimenIdentifier'], 7557 '0x0552': ['SQ', '1', 'SpecimenDescriptionSequenceTrial'], 7558 '0x0553': ['ST', '1', 'SpecimenDescriptionTrial'], 7559 '0x0554': ['UI', '1', 'SpecimenUID'], 7560 '0x0555': ['SQ', '1', 'AcquisitionContextSequence'], 7561 '0x0556': ['ST', '1', 'AcquisitionContextDescription'], 7562 '0x059A': ['SQ', '1', 'SpecimenTypeCodeSequence'], 7563 '0x0560': ['SQ', '1', 'SpecimenDescriptionSequence'], 7564 '0x0562': ['SQ', '1', 'IssuerOfTheSpecimenIdentifierSequence'], 7565 '0x0600': ['LO', '1', 'SpecimenShortDescription'], 7566 '0x0602': ['UT', '1', 'SpecimenDetailedDescription'], 7567 '0x0610': ['SQ', '1', 'SpecimenPreparationSequence'], 7568 '0x0612': ['SQ', '1', 'SpecimenPreparationStepContentItemSequence'], 7569 '0x0620': ['SQ', '1', 'SpecimenLocalizationContentItemSequence'], 7570 '0x06FA': ['LO', '1', 'SlideIdentifier'], 7571 '0x071A': ['SQ', '1', 'ImageCenterPointCoordinatesSequence'], 7572 '0x072A': ['DS', '1', 'XOffsetInSlideCoordinateSystem'], 7573 '0x073A': ['DS', '1', 'YOffsetInSlideCoordinateSystem'], 7574 '0x074A': ['DS', '1', 'ZOffsetInSlideCoordinateSystem'], 7575 '0x08D8': ['SQ', '1', 'PixelSpacingSequence'], 7576 '0x08DA': ['SQ', '1', 'CoordinateSystemAxisCodeSequence'], 7577 '0x08EA': ['SQ', '1', 'MeasurementUnitsCodeSequence'], 7578 '0x09F8': ['SQ', '1', 'VitalStainCodeSequenceTrial'], 7579 '0x1001': ['SH', '1', 'RequestedProcedureID'], 7580 '0x1002': ['LO', '1', 'ReasonForTheRequestedProcedure'], 7581 '0x1003': ['SH', '1', 'RequestedProcedurePriority'],
7582 '0x1004': ['LO', '1', 'PatientTransportArrangements'], 7583 '0x1005': ['LO', '1', 'RequestedProcedureLocation'], 7584 '0x1006': ['SH', '1', 'PlacerOrderNumberProcedure'], 7585 '0x1007': ['SH', '1', 'FillerOrderNumberProcedure'], 7586 '0x1008': ['LO', '1', 'ConfidentialityCode'], 7587 '0x1009': ['SH', '1', 'ReportingPriority'], 7588 '0x100A': ['SQ', '1', 'ReasonForRequestedProcedureCodeSequence'], 7589 '0x1010': ['PN', '1-n', 'NamesOfIntendedRecipientsOfResults'], 7590 '0x1011': ['SQ', '1', 'IntendedRecipientsOfResultsIdentificationSequence'], 7591 '0x1012': ['SQ', '1', 'ReasonForPerformedProcedureCodeSequence'], 7592 '0x1060': ['LO', '1', 'RequestedProcedureDescriptionTrial'], 7593 '0x1101': ['SQ', '1', 'PersonIdentificationCodeSequence'], 7594 '0x1102': ['ST', '1', 'PersonAddress'], 7595 '0x1103': ['LO', '1-n', 'PersonTelephoneNumbers'], 7596 '0x1104': ['LT', '1', 'PersonTelecomInformation'], 7597 '0x1400': ['LT', '1', 'RequestedProcedureComments'], 7598 '0x2001': ['LO', '1', 'ReasonForTheImagingServiceRequest'], 7599 '0x2004': ['DA', '1', 'IssueDateOfImagingServiceRequest'], 7600 '0x2005': ['TM', '1', 'IssueTimeOfImagingServiceRequest'], 7601 '0x2006': ['SH', '1', 'PlacerOrderNumberImagingServiceRequestRetired'], 7602 '0x2007': ['SH', '1', 'FillerOrderNumberImagingServiceRequestRetired'], 7603 '0x2008': ['PN', '1', 'OrderEnteredBy'], 7604 '0x2009': ['SH', '1', 'OrderEntererLocation'], 7605 '0x2010': ['SH', '1', 'OrderCallbackPhoneNumber'], 7606 '0x2011': ['LT', '1', 'OrderCallbackTelecomInformation'], 7607 '0x2016': ['LO', '1', 'PlacerOrderNumberImagingServiceRequest'], 7608 '0x2017': ['LO', '1', 'FillerOrderNumberImagingServiceRequest'], 7609 '0x2400': ['LT', '1', 'ImagingServiceRequestComments'], 7610 '0x3001': ['LO', '1', 'ConfidentialityConstraintOnPatientDataDescription'], 7611 '0x4001': ['CS', '1', 'GeneralPurposeScheduledProcedureStepStatus'], 7612 '0x4002': ['CS', '1', 'GeneralPurposePerformedProcedureStepStatus'], 7613 '0x4003': ['CS', '1', 'GeneralPurposeScheduledProcedureStepPriority'], 7614 '0x4004': ['SQ', '1', 'ScheduledProcessingApplicationsCodeSequence'], 7615 '0x4005': ['DT', '1', 'ScheduledProcedureStepStartDateTime'], 7616 '0x4006': ['CS', '1', 'MultipleCopiesFlag'], 7617 '0x4007': ['SQ', '1', 'PerformedProcessingApplicationsCodeSequence'], 7618 '0x4009': ['SQ', '1', 'HumanPerformerCodeSequence'], 7619 '0x4010': ['DT', '1', 'ScheduledProcedureStepModificationDateTime'], 7620 '0x4011': ['DT', '1', 'ExpectedCompletionDateTime'], 7621 '0x4015': ['SQ', '1', 'ResultingGeneralPurposePerformedProcedureStepsSequence'], 7622 '0x4016': ['SQ', '1', 'ReferencedGeneralPurposeScheduledProcedureStepSequence'], 7623 '0x4018': ['SQ', '1', 'ScheduledWorkitemCodeSequence'], 7624 '0x4019': ['SQ', '1', 'PerformedWorkitemCodeSequence'], 7625 '0x4020': ['CS', '1', 'InputAvailabilityFlag'], 7626 '0x4021': ['SQ', '1', 'InputInformationSequence'], 7627 '0x4022': ['SQ', '1', 'RelevantInformationSequence'], 7628 '0x4023': ['UI', '1', 'ReferencedGeneralPurposeScheduledProcedureStepTransactionUID'], 7629 '0x4025': ['SQ', '1', 'ScheduledStationNameCodeSequence'], 7630 '0x4026': ['SQ', '1', 'ScheduledStationClassCodeSequence'], 7631 '0x4027': ['SQ', '1', 'ScheduledStationGeographicLocationCodeSequence'], 7632 '0x4028': ['SQ', '1', 'PerformedStationNameCodeSequence'], 7633 '0x4029': ['SQ', '1', 'PerformedStationClassCodeSequence'], 7634 '0x4030': ['SQ', '1', 'PerformedStationGeographicLocationCodeSequence'], 7635 '0x4031': ['SQ', '1', 'RequestedSubsequentWorkitemCodeSequence'], 7636 '0x4032': ['SQ', '1', 'NonDICOMOutputCodeSequence'], 7637 '0x4033': ['SQ', '1', 'OutputInformationSequence'], 7638 '0x4034': ['SQ', '1', 'ScheduledHumanPerformersSequence'], 7639 '0x4035': ['SQ', '1', 'ActualHumanPerformersSequence'], 7640 '0x4036': ['LO', '1', 'HumanPerformerOrganization'], 7641 '0x4037': ['PN', '1', 'HumanPerformerName'], 7642 '0x4040': ['CS', '1', 'RawDataHandling'], 7643 '0x4041': ['CS', '1', 'InputReadinessState'], 7644 '0x4050': ['DT', '1', 'PerformedProcedureStepStartDateTime'], 7645 '0x4051': ['DT', '1', 'PerformedProcedureStepEndDateTime'], 7646 '0x4052': ['DT', '1', 'ProcedureStepCancellationDateTime'], 7647 '0x8302': ['DS', '1', 'EntranceDoseInmGy'], 7648 '0x9092': ['SQ', '1', 'ParametricMapFrameTypeSequence'], 7649 '0x9094': ['SQ', '1', 'ReferencedImageRealWorldValueMappingSequence'], 7650 '0x9096': ['SQ', '1', 'RealWorldValueMappingSequence'], 7651 '0x9098': ['SQ', '1', 'PixelValueMappingCodeSequence'], 7652 '0x9210': ['SH', '1', 'LUTLabel'], 7653 '0x9211': ['xs', '1', 'RealWorldValueLastValueMapped'], 7654 '0x9212': ['FD', '1-n', 'RealWorldValueLUTData'], 7655 '0x9216': ['xs', '1', 'RealWorldValueFirstValueMapped'], 7656 '0x9220': ['SQ', '1', 'QuantityDefinitionSequence'], 7657 '0x9224': ['FD', '1', 'RealWorldValueIntercept'], 7658 '0x9225': ['FD', '1', 'RealWorldValueSlope'], 7659 '0xA007': ['CS', '1', 'FindingsFlagTrial'], 7660 '0xA010': ['CS', '1', 'RelationshipType'], 7661 '0xA020': ['SQ', '1', 'FindingsSequenceTrial'], 7662 '0xA021': ['UI', '1', 'FindingsGroupUIDTrial'], 7663 '0xA022': ['UI', '1', 'ReferencedFindingsGroupUIDTrial'], 7664 '0xA023': ['DA', '1', 'FindingsGroupRecordingDateTrial'], 7665 '0xA024': ['TM', '1', 'FindingsGroupRecordingTimeTrial'], 7666 '0xA026': ['SQ', '1', 'FindingsSourceCategoryCodeSequenceTrial'], 7667 '0xA027': ['LO', '1', 'VerifyingOrganization'], 7668 '0xA028': ['SQ', '1', 'DocumentingOrganizationIdentifierCodeSequenceTrial'], 7669 '0xA030': ['DT', '1', 'VerificationDateTime'], 7670 '0xA032': ['DT', '1', 'ObservationDateTime'], 7671 '0xA040': ['CS', '1', 'ValueType'], 7672 '0xA043': ['SQ', '1', 'ConceptNameCodeSequence'], 7673 '0xA047': ['LO', '1', 'MeasurementPrecisionDescriptionTrial'], 7674 '0xA050': ['CS', '1', 'ContinuityOfContent'], 7675 '0xA057': ['CS', '1-n', 'UrgencyOrPriorityAlertsTrial'], 7676 '0xA060': ['LO', '1', 'SequencingIndicatorTrial'], 7677 '0xA066': ['SQ', '1', 'DocumentIdentifierCodeSequenceTrial'], 7678 '0xA067': ['PN', '1', 'DocumentAuthorTrial'], 7679 '0xA068': ['SQ', '1', 'DocumentAuthorIdentifierCodeSequenceTrial'], 7680 '0xA070': ['SQ', '1', 'IdentifierCodeSequenceTrial'], 7681 '0xA073': ['SQ', '1', 'VerifyingObserverSequence'], 7682 '0xA074': ['OB', '1', 'ObjectBinaryIdentifierTrial'], 7683 '0xA075': ['PN', '1', 'VerifyingObserverName'],
7684 '0xA076': ['SQ', '1', 'DocumentingObserverIdentifierCodeSequenceTrial'], 7685 '0xA078': ['SQ', '1', 'AuthorObserverSequence'], 7686 '0xA07A': ['SQ', '1', 'ParticipantSequence'], 7687 '0xA07C': ['SQ', '1', 'CustodialOrganizationSequence'], 7688 '0xA080': ['CS', '1', 'ParticipationType'], 7689 '0xA082': ['DT', '1', 'ParticipationDateTime'], 7690 '0xA084': ['CS', '1', 'ObserverType'], 7691 '0xA085': ['SQ', '1', 'ProcedureIdentifierCodeSequenceTrial'], 7692 '0xA088': ['SQ', '1', 'VerifyingObserverIdentificationCodeSequence'], 7693 '0xA089': ['OB', '1', 'ObjectDirectoryBinaryIdentifierTrial'], 7694 '0xA090': ['SQ', '1', 'EquivalentCDADocumentSequence'], 7695 '0xA0B0': ['US', '2-2n', 'ReferencedWaveformChannels'], 7696 '0xA110': ['DA', '1', 'DateOfDocumentOrVerbalTransactionTrial'], 7697 '0xA112': ['TM', '1', 'TimeOfDocumentCreationOrVerbalTransactionTrial'], 7698 '0xA120': ['DT', '1', 'DateTime'], 7699 '0xA121': ['DA', '1', 'Date'], 7700 '0xA122': ['TM', '1', 'Time'], 7701 '0xA123': ['PN', '1', 'PersonName'], 7702 '0xA124': ['UI', '1', 'UID'], 7703 '0xA125': ['CS', '2', 'ReportStatusIDTrial'], 7704 '0xA130': ['CS', '1', 'TemporalRangeType'], 7705 '0xA132': ['UL', '1-n', 'ReferencedSamplePositions'], 7706 '0xA136': ['US', '1-n', 'ReferencedFrameNumbers'], 7707 '0xA138': ['DS', '1-n', 'ReferencedTimeOffsets'], 7708 '0xA13A': ['DT', '1-n', 'ReferencedDateTime'], 7709 '0xA160': ['UT', '1', 'TextValue'], 7710 '0xA161': ['FD', '1-n', 'FloatingPointValue'], 7711 '0xA162': ['SL', '1-n', 'RationalNumeratorValue'], 7712 '0xA163': ['UL', '1-n', 'RationalDenominatorValue'], 7713 '0xA167': ['SQ', '1', 'ObservationCategoryCodeSequenceTrial'], 7714 '0xA168': ['SQ', '1', 'ConceptCodeSequence'], 7715 '0xA16A': ['ST', '1', 'BibliographicCitationTrial'], 7716 '0xA170': ['SQ', '1', 'PurposeOfReferenceCodeSequence'], 7717 '0xA171': ['UI', '1', 'ObservationUID'], 7718 '0xA172': ['UI', '1', 'ReferencedObservationUIDTrial'], 7719 '0xA173': ['CS', '1', 'ReferencedObservationClassTrial'], 7720 '0xA174': ['CS', '1', 'ReferencedObjectObservationClassTrial'], 7721 '0xA180': ['US', '1', 'AnnotationGroupNumber'], 7722 '0xA192': ['DA', '1', 'ObservationDateTrial'], 7723 '0xA193': ['TM', '1', 'ObservationTimeTrial'], 7724 '0xA194': ['CS', '1', 'MeasurementAutomationTrial'], 7725 '0xA195': ['SQ', '1', 'ModifierCodeSequence'], 7726 '0xA224': ['ST', '1', 'IdentificationDescriptionTrial'], 7727 '0xA290': ['CS', '1', 'CoordinatesSetGeometricTypeTrial'], 7728 '0xA296': ['SQ', '1', 'AlgorithmCodeSequenceTrial'], 7729 '0xA297': ['ST', '1', 'AlgorithmDescriptionTrial'], 7730 '0xA29A': ['SL', '2-2n', 'PixelCoordinatesSetTrial'], 7731 '0xA300': ['SQ', '1', 'MeasuredValueSequence'], 7732 '0xA301': ['SQ', '1', 'NumericValueQualifierCodeSequence'], 7733 '0xA307': ['PN', '1', 'CurrentObserverTrial'], 7734 '0xA30A': ['DS', '1-n', 'NumericValue'], 7735 '0xA313': ['SQ', '1', 'ReferencedAccessionSequenceTrial'], 7736 '0xA33A': ['ST', '1', 'ReportStatusCommentTrial'], 7737 '0xA340': ['SQ', '1', 'ProcedureContextSequenceTrial'], 7738 '0xA352': ['PN', '1', 'VerbalSourceTrial'], 7739 '0xA353': ['ST', '1', 'AddressTrial'], 7740 '0xA354': ['LO', '1', 'TelephoneNumberTrial'], 7741 '0xA358': ['SQ', '1', 'VerbalSourceIdentifierCodeSequenceTrial'], 7742 '0xA360': ['SQ', '1', 'PredecessorDocumentsSequence'], 7743 '0xA370': ['SQ', '1', 'ReferencedRequestSequence'], 7744 '0xA372': ['SQ', '1', 'PerformedProcedureCodeSequence'], 7745 '0xA375': ['SQ', '1', 'CurrentRequestedProcedureEvidenceSequence'], 7746 '0xA380': ['SQ', '1', 'ReportDetailSequenceTrial'], 7747 '0xA385': ['SQ', '1', 'PertinentOtherEvidenceSequence'], 7748 '0xA390': ['SQ', '1', 'HL7StructuredDocumentReferenceSequence'], 7749 '0xA402': ['UI', '1', 'ObservationSubjectUIDTrial'], 7750 '0xA403': ['CS', '1', 'ObservationSubjectClassTrial'], 7751 '0xA404': ['SQ', '1', 'ObservationSubjectTypeCodeSequenceTrial'], 7752 '0xA491': ['CS', '1', 'CompletionFlag'],
7753 '0xA492': ['LO', '1', 'CompletionFlagDescription'], 7754 '0xA493': ['CS', '1', 'VerificationFlag'], 7755 '0xA494': ['CS', '1', 'ArchiveRequested'], 7756 '0xA496': ['CS', '1', 'PreliminaryFlag'], 7757 '0xA504': ['SQ', '1', 'ContentTemplateSequence'], 7758 '0xA525': ['SQ', '1', 'IdenticalDocumentsSequence'], 7759 '0xA600': ['CS', '1', 'ObservationSubjectContextFlagTrial'], 7760 '0xA601': ['CS', '1', 'ObserverContextFlagTrial'], 7761 '0xA603': ['CS', '1', 'ProcedureContextFlagTrial'], 7762 '0xA730': ['SQ', '1', 'ContentSequence'], 7763 '0xA731': ['SQ', '1', 'RelationshipSequenceTrial'], 7764 '0xA732': ['SQ', '1', 'RelationshipTypeCodeSequenceTrial'], 7765 '0xA744': ['SQ', '1', 'LanguageCodeSequenceTrial'], 7766 '0xA992': ['ST', '1', 'UniformResourceLocatorTrial'], 7767 '0xB020': ['SQ', '1', 'WaveformAnnotationSequence'], 7768 '0xDB00': ['CS', '1', 'TemplateIdentifier'], 7769 '0xDB06': ['DT', '1', 'TemplateVersion'], 7770 '0xDB07': ['DT', '1', 'TemplateLocalVersion'], 7771 '0xDB0B': ['CS', '1', 'TemplateExtensionFlag'], 7772 '0xDB0C': ['UI', '1', 'TemplateExtensionOrganizationUID'], 7773 '0xDB0D': ['UI', '1', 'TemplateExtensionCreatorUID'], 7774 '0xDB73': ['UL', '1-n', 'ReferencedContentItemIdentifier'], 7775 '0xE001': ['ST', '1', 'HL7InstanceIdentifier'], 7776 '0xE004': ['DT', '1', 'HL7DocumentEffectiveTime'], 7777 '0xE006': ['SQ', '1', 'HL7DocumentTypeCodeSequence'], 7778 '0xE008': ['SQ', '1', 'DocumentClassCodeSequence'], 7779 '0xE010': ['UR', '1', 'RetrieveURI'], 7780 '0xE011': ['UI', '1', 'RetrieveLocationUID'], 7781 '0xE020': ['CS', '1', 'TypeOfInstances'], 7782 '0xE021': ['SQ', '1', 'DICOMRetrievalSequence'], 7783 '0xE022': ['SQ', '1', 'DICOMMediaRetrievalSequence'], 7784 '0xE023': ['SQ', '1', 'WADORetrievalSequence'], 7785 '0xE024': ['SQ', '1', 'XDSRetrievalSequence'], 7786 '0xE025': ['SQ', '1', 'WADORSRetrievalSequence'], 7787 '0xE030': ['UI', '1', 'RepositoryUniqueID'], 7788 '0xE031': ['UI', '1', 'HomeCommunityID'] 7789 }, 7790 '0x0042': { 7791 '0x0000': ['UL', '1', 'GenericGroupLength'], 7792 '0x0010': ['ST', '1', 'DocumentTitle'], 7793 '0x0011': ['OB', '1', 'EncapsulatedDocument'], 7794 '0x0012': ['LO', '1', 'MIMETypeOfEncapsulatedDocument'], 7795 '0x0013': ['SQ', '1', 'SourceInstanceSequence'], 7796 '0x0014': ['LO', '1-n', 'ListOfMIMETypes'] 7797 }, 7798 '0x0044': { 7799 '0x0000': ['UL', '1', 'GenericGroupLength'], 7800 '0x0001': ['ST', '1', 'ProductPackageIdentifier'], 7801 '0x0002': ['CS', '1', 'SubstanceAdministrationApproval'], 7802 '0x0003': ['LT', '1', 'ApprovalStatusFurtherDescription'], 7803 '0x0004': ['DT', '1', 'ApprovalStatusDateTime'], 7804 '0x0007': ['SQ', '1', 'ProductTypeCodeSequence'], 7805 '0x0008': ['LO', '1-n', 'ProductName'], 7806 '0x0009': ['LT', '1', 'ProductDescription'], 7807 '0x000A': ['LO', '1', 'ProductLotIdentifier'], 7808 '0x000B': ['DT', '1', 'ProductExpirationDateTime'], 7809 '0x0010': ['DT', '1', 'SubstanceAdministrationDateTime'], 7810 '0x0011': ['LO', '1', 'SubstanceAdministrationNotes'], 7811 '0x0012': ['LO', '1', 'SubstanceAdministrationDeviceID'], 7812 '0x0013': ['SQ', '1', 'ProductParameterSequence'], 7813 '0x0019': ['SQ', '1', 'SubstanceAdministrationParameterSequence'] 7814 }, 7815 '0x0046': { 7816 '0x0000': ['UL', '1', 'GenericGroupLength'], 7817 '0x0012': ['LO', '1', 'LensDescription'], 7818 '0x0014': ['SQ', '1', 'RightLensSequence'], 7819 '0x0015': ['SQ', '1', 'LeftLensSequence'], 7820 '0x0016': ['SQ', '1', 'UnspecifiedLateralityLensSequence'], 7821 '0x0018': ['SQ', '1', 'CylinderSequence'], 7822 '0x0028': ['SQ', '1', 'PrismSequence'], 7823 '0x0030': ['FD', '1', 'HorizontalPrismPower'], 7824 '0x0032': ['CS', '1', 'HorizontalPrismBase'], 7825 '0x0034': ['FD', '1', 'VerticalPrismPower'], 7826 '0x0036': ['CS', '1', 'VerticalPrismBase'], 7827 '0x0038': ['CS', '1', 'LensSegmentType'], 7828 '0x0040': ['FD', '1', 'OpticalTransmittance'], 7829 '0x0042': ['FD', '1', 'ChannelWidth'], 7830 '0x0044': ['FD', '1', 'PupilSize'], 7831 '0x0046': ['FD', '1', 'CornealSize'], 7832 '0x0050': ['SQ', '1', 'AutorefractionRightEyeSequence'], 7833 '0x0052': ['SQ', '1', 'AutorefractionLeftEyeSequence'], 7834 '0x0060': ['FD', '1', 'DistancePupillaryDistance'], 7835 '0x0062': ['FD', '1', 'NearPupillaryDistance'], 7836 '0x0063': ['FD', '1', 'IntermediatePupillaryDistance'], 7837 '0x0064': ['FD', '1', 'OtherPupillaryDistance'], 7838 '0x0070': ['SQ', '1', 'KeratometryRightEyeSequence'], 7839 '0x0071': ['SQ', '1', 'KeratometryLeftEyeSequence'], 7840 '0x0074': ['SQ', '1', 'SteepKeratometricAxisSequence'], 7841 '0x0075': ['FD', '1', 'RadiusOfCurvature'], 7842 '0x0076': ['FD', '1', 'KeratometricPower'], 7843 '0x0077': ['FD', '1', 'KeratometricAxis'], 7844 '0x0080': ['SQ', '1', 'FlatKeratometricAxisSequence'],
7845 '0x0092': ['CS', '1', 'BackgroundColor'], 7846 '0x0094': ['CS', '1', 'Optotype'], 7847 '0x0095': ['CS', '1', 'OptotypePresentation'], 7848 '0x0097': ['SQ', '1', 'SubjectiveRefractionRightEyeSequence'], 7849 '0x0098': ['SQ', '1', 'SubjectiveRefractionLeftEyeSequence'], 7850 '0x0100': ['SQ', '1', 'AddNearSequence'], 7851 '0x0101': ['SQ', '1', 'AddIntermediateSequence'], 7852 '0x0102': ['SQ', '1', 'AddOtherSequence'], 7853 '0x0104': ['FD', '1', 'AddPower'], 7854 '0x0106': ['FD', '1', 'ViewingDistance'], 7855 '0x0121': ['SQ', '1', 'VisualAcuityTypeCodeSequence'], 7856 '0x0122': ['SQ', '1', 'VisualAcuityRightEyeSequence'], 7857 '0x0123': ['SQ', '1', 'VisualAcuityLeftEyeSequence'], 7858 '0x0124': ['SQ', '1', 'VisualAcuityBothEyesOpenSequence'], 7859 '0x0125': ['CS', '1', 'ViewingDistanceType'], 7860 '0x0135': ['SS', '2', 'VisualAcuityModifiers'], 7861 '0x0137': ['FD', '1', 'DecimalVisualAcuity'], 7862 '0x0139': ['LO', '1', 'OptotypeDetailedDefinition'], 7863 '0x0145': ['SQ', '1', 'ReferencedRefractiveMeasurementsSequence'], 7864 '0x0146': ['FD', '1', 'SpherePower'], 7865 '0x0147': ['FD', '1', 'CylinderPower'], 7866 '0x0201': ['CS', '1', 'CornealTopographySurface'], 7867 '0x0202': ['FL', '2', 'CornealVertexLocation'], 7868 '0x0203': ['FL', '1', 'PupilCentroidXCoordinate'], 7869 '0x0204': ['FL', '1', 'PupilCentroidYCoordinate'], 7870 '0x0205': ['FL', '1', 'EquivalentPupilRadius'], 7871 '0x0207': ['SQ', '1', 'CornealTopographyMapTypeCodeSequence'], 7872 '0x0208': ['IS', '2-2n', 'VerticesOfTheOutlineOfPupil'], 7873 '0x0210': ['SQ', '1', 'CornealTopographyMappingNormalsSequence'], 7874 '0x0211': ['SQ', '1', 'MaximumCornealCurvatureSequence'], 7875 '0x0212': ['FL', '1', 'MaximumCornealCurvature'], 7876 '0x0213': ['FL', '2', 'MaximumCornealCurvatureLocation'], 7877 '0x0215': ['SQ', '1', 'MinimumKeratometricSequence'], 7878 '0x0218': ['SQ', '1', 'SimulatedKeratometricCylinderSequence'], 7879 '0x0220': ['FL', '1', 'AverageCornealPower'], 7880 '0x0224': ['FL', '1', 'CornealISValue'], 7881 '0x0227': ['FL', '1', 'AnalyzedArea'], 7882 '0x0230': ['FL', '1', 'SurfaceRegularityIndex'], 7883 '0x0232': ['FL', '1', 'SurfaceAsymmetryIndex'], 7884 '0x0234': ['FL', '1', 'CornealEccentricityIndex'], 7885 '0x0236': ['FL', '1', 'KeratoconusPredictionIndex'], 7886 '0x0238': ['FL', '1', 'DecimalPotentialVisualAcuity'], 7887 '0x0242': ['CS', '1', 'CornealTopographyMapQualityEvaluation'], 7888 '0x0244': ['SQ', '1', 'SourceImageCornealProcessedDataSequence'], 7889 '0x0247': ['FL', '3', 'CornealPointLocation'], 7890 '0x0248': ['CS', '1', 'CornealPointEstimated'], 7891 '0x0249': ['FL', '1', 'AxialPower'], 7892 '0x0250': ['FL', '1', 'TangentialPower'], 7893 '0x0251': ['FL', '1', 'RefractivePower'], 7894 '0x0252': ['FL', '1', 'RelativeElevation'], 7895 '0x0253': ['FL', '1', 'CornealWavefront'] 7896 }, 7897 '0x0048': { 7898 '0x0000': ['UL', '1', 'GenericGroupLength'], 7899 '0x0001': ['FL', '1', 'ImagedVolumeWidth'], 7900 '0x0002': ['FL', '1', 'ImagedVolumeHeight'], 7901 '0x0003': ['FL', '1', 'ImagedVolumeDepth'], 7902 '0x0006': ['UL', '1', 'TotalPixelMatrixColumns'], 7903 '0x0007': ['UL', '1', 'TotalPixelMatrixRows'], 7904 '0x0008': ['SQ', '1', 'TotalPixelMatrixOriginSequence'], 7905 '0x0010': ['CS', '1', 'SpecimenLabelInImage'], 7906 '0x0011': ['CS', '1', 'FocusMethod'], 7907 '0x0012': ['CS', '1', 'ExtendedDepthOfField'], 7908 '0x0013': ['US', '1', 'NumberOfFocalPlanes'], 7909 '0x0014': ['FL', '1', 'DistanceBetweenFocalPlanes'], 7910 '0x0015': ['US', '3', 'RecommendedAbsentPixelCIELabValue'], 7911 '0x0100': ['SQ', '1', 'IlluminatorTypeCodeSequence'], 7912 '0x0102': ['DS', '6', 'ImageOrientationSlide'], 7913 '0x0105': ['SQ', '1', 'OpticalPathSequence'], 7914 '0x0106': ['SH', '1', 'OpticalPathIdentifier'], 7915 '0x0107': ['ST', '1', 'OpticalPathDescription'], 7916 '0x0108': ['SQ', '1', 'IlluminationColorCodeSequence'], 7917 '0x0110': ['SQ', '1', 'SpecimenReferenceSequence'], 7918 '0x0111': ['DS', '1', 'CondenserLensPower'], 7919 '0x0112': ['DS', '1', 'ObjectiveLensPower'], 7920 '0x0113': ['DS', '1', 'ObjectiveLensNumericalAperture'], 7921 '0x0120': ['SQ', '1', 'PaletteColorLookupTableSequence'], 7922 '0x0200': ['SQ', '1', 'ReferencedImageNavigationSequence'], 7923 '0x0201': ['US', '2', 'TopLeftHandCornerOfLocalizerArea'], 7924 '0x0202': ['US', '2', 'BottomRightHandCornerOfLocalizerArea'], 7925 '0x0207': ['SQ', '1', 'OpticalPathIdentificationSequence'], 7926 '0x021A': ['SQ', '1', 'PlanePositionSlideSequence'], 7927 '0x021E': ['SL', '1', 'ColumnPositionInTotalImagePixelMatrix'], 7928 '0x021F': ['SL', '1', 'RowPositionInTotalImagePixelMatrix'], 7929 '0x0301': ['CS', '1', 'PixelOriginInterpretation'] 7930 }, 7931 '0x0050': { 7932 '0x0000': ['UL', '1', 'GenericGroupLength'], 7933 '0x0004': ['CS', '1', 'CalibrationImage'], 7934 '0x0010': ['SQ', '1', 'DeviceSequence'], 7935 '0x0012': ['SQ', '1', 'ContainerComponentTypeCodeSequence'], 7936 '0x0013': ['FD', '1', 'ContainerComponentThickness'], 7937 '0x0014': ['DS', '1', 'DeviceLength'], 7938 '0x0015': ['FD', '1', 'ContainerComponentWidth'], 7939 '0x0016': ['DS', '1', 'DeviceDiameter'], 7940 '0x0017': ['CS', '1', 'DeviceDiameterUnits'], 7941 '0x0018': ['DS', '1', 'DeviceVolume'], 7942 '0x0019': ['DS', '1', 'InterMarkerDistance'], 7943 '0x001A': ['CS', '1', 'ContainerComponentMaterial'], 7944 '0x001B': ['LO', '1', 'ContainerComponentID'], 7945 '0x001C': ['FD', '1', 'ContainerComponentLength'], 7946 '0x001D': ['FD', '1', 'ContainerComponentDiameter'], 7947 '0x001E': ['LO', '1', 'ContainerComponentDescription'], 7948 '0x0020': ['LO', '1', 'DeviceDescription'] 7949 }, 7950 '0x0052': { 7951 '0x0000': ['UL', '1', 'GenericGroupLength'], 7952 '0x0001': ['FL', '1', 'ContrastBolusIngredientPercentByVolume'], 7953 '0x0002': ['FD', '1', 'OCTFocalDistance'], 7954 '0x0003': ['FD', '1', 'BeamSpotSize'], 7955 '0x0004': ['FD', '1', 'EffectiveRefractiveIndex'], 7956 '0x0006': ['CS', '1', 'OCTAcquisitionDomain'], 7957 '0x0007': ['FD', '1', 'OCTOpticalCenterWavelength'], 7958 '0x0008': ['FD', '1', 'AxialResolution'], 7959 '0x0009': ['FD', '1', 'RangingDepth'], 7960 '0x0011': ['FD', '1', 'ALineRate'], 7961 '0x0012': ['US', '1', 'ALinesPerFrame'], 7962 '0x0013': ['FD', '1', 'CatheterRotationalRate'], 7963 '0x0014': ['FD', '1', 'ALinePixelSpacing'], 7964 '0x0016': ['SQ', '1', 'ModeOfPercutaneousAccessSequence'], 7965 '0x0025': ['SQ', '1', 'IntravascularOCTFrameTypeSequence'], 7966 '0x0026': ['CS', '1', 'OCTZOffsetApplied'], 7967 '0x0027': ['SQ', '1', 'IntravascularFrameContentSequence'], 7968 '0x0028': ['FD', '1', 'IntravascularLongitudinalDistance'], 7969 '0x0029': ['SQ', '1', 'IntravascularOCTFrameContentSequence'], 7970 '0x0030': ['SS', '1', 'OCTZOffsetCorrection'], 7971 '0x0031': ['CS', '1', 'CatheterDirectionOfRotation'], 7972 '0x0033': ['FD', '1', 'SeamLineLocation'], 7973 '0x0034': ['FD', '1', 'FirstALineLocation'], 7974 '0x0036': ['US', '1', 'SeamLineIndex'], 7975 '0x0038': ['US', '1', 'NumberOfPaddedALines'], 7976 '0x0039': ['CS', '1', 'InterpolationType'], 7977 '0x003A': ['CS', '1', 'RefractiveIndexApplied'] 7978 }, 7979 '0x0054': { 7980 '0x0000': ['UL', '1', 'GenericGroupLength'], 7981 '0x0010': ['US', '1-n', 'EnergyWindowVector'], 7982 '0x0011': ['US', '1', 'NumberOfEnergyWindows'], 7983 '0x0012': ['SQ', '1', 'EnergyWindowInformationSequence'], 7984 '0x0013': ['SQ', '1', 'EnergyWindowRangeSequence'], 7985 '0x0014': ['DS', '1', 'EnergyWindowLowerLimit'],
7986 '0x0015': ['DS', '1', 'EnergyWindowUpperLimit'], 7987 '0x0016': ['SQ', '1', 'RadiopharmaceuticalInformationSequence'], 7988 '0x0017': ['IS', '1', 'ResidualSyringeCounts'], 7989 '0x0018': ['SH', '1', 'EnergyWindowName'], 7990 '0x0020': ['US', '1-n', 'DetectorVector'], 7991 '0x0021': ['US', '1', 'NumberOfDetectors'], 7992 '0x0022': ['SQ', '1', 'DetectorInformationSequence'], 7993 '0x0030': ['US', '1-n', 'PhaseVector'], 7994 '0x0031': ['US', '1', 'NumberOfPhases'], 7995 '0x0032': ['SQ', '1', 'PhaseInformationSequence'], 7996 '0x0033': ['US', '1', 'NumberOfFramesInPhase'], 7997 '0x0036': ['IS', '1', 'PhaseDelay'], 7998 '0x0038': ['IS', '1', 'PauseBetweenFrames'], 7999 '0x0039': ['CS', '1', 'PhaseDescription'], 8000 '0x0050': ['US', '1-n', 'RotationVector'], 8001 '0x0051': ['US', '1', 'NumberOfRotations'], 8002 '0x0052': ['SQ', '1', 'RotationInformationSequence'], 8003 '0x0053': ['US', '1', 'NumberOfFramesInRotation'], 8004 '0x0060': ['US', '1-n', 'RRIntervalVector'], 8005 '0x0061': ['US', '1', 'NumberOfRRIntervals'], 8006 '0x0062': ['SQ', '1', 'GatedInformationSequence'], 8007 '0x0063': ['SQ', '1', 'DataInformationSequence'], 8008 '0x0070': ['US', '1-n', 'TimeSlotVector'], 8009 '0x0071': ['US', '1', 'NumberOfTimeSlots'], 8010 '0x0072': ['SQ', '1', 'TimeSlotInformationSequence'], 8011 '0x0073': ['DS', '1', 'TimeSlotTime'], 8012 '0x0080': ['US', '1-n', 'SliceVector'], 8013 '0x0081': ['US', '1', 'NumberOfSlices'], 8014 '0x0090': ['US', '1-n', 'AngularViewVector'], 8015 '0x0100': ['US', '1-n', 'TimeSliceVector'], 8016 '0x0101': ['US', '1', 'NumberOfTimeSlices'], 8017 '0x0200': ['DS', '1', 'StartAngle'], 8018 '0x0202': ['CS', '1', 'TypeOfDetectorMotion'], 8019 '0x0210': ['IS', '1-n', 'TriggerVector'], 8020 '0x0211': ['US', '1', 'NumberOfTriggersInPhase'], 8021 '0x0220': ['SQ', '1', 'ViewCodeSequence'], 8022 '0x0222': ['SQ', '1', 'ViewModifierCodeSequence'], 8023 '0x0300': ['SQ', '1', 'RadionuclideCodeSequence'], 8024 '0x0302': ['SQ', '1', 'AdministrationRouteCodeSequence'], 8025 '0x0304': ['SQ', '1', 'RadiopharmaceuticalCodeSequence'], 8026 '0x0306': ['SQ', '1', 'CalibrationDataSequence'], 8027 '0x0308': ['US', '1', 'EnergyWindowNumber'], 8028 '0x0400': ['SH', '1', 'ImageID'], 8029 '0x0410': ['SQ', '1', 'PatientOrientationCodeSequence'], 8030 '0x0412': ['SQ', '1', 'PatientOrientationModifierCodeSequence'], 8031 '0x0414': ['SQ', '1', 'PatientGantryRelationshipCodeSequence'], 8032 '0x0500': ['CS', '1', 'SliceProgressionDirection'], 8033 '0x0501': ['CS', '1', 'ScanProgressionDirection'], 8034 '0x1000': ['CS', '2', 'SeriesType'], 8035 '0x1001': ['CS', '1', 'Units'], 8036 '0x1002': ['CS', '1', 'CountsSource'], 8037 '0x1004': ['CS', '1', 'ReprojectionMethod'], 8038 '0x1006': ['CS', '1', 'SUVType'], 8039 '0x1100': ['CS', '1', 'RandomsCorrectionMethod'], 8040 '0x1101': ['LO', '1', 'AttenuationCorrectionMethod'], 8041 '0x1102': ['CS', '1', 'DecayCorrection'], 8042 '0x1103': ['LO', '1', 'ReconstructionMethod'], 8043 '0x1104': ['LO', '1', 'DetectorLinesOfResponseUsed'], 8044 '0x1105': ['LO', '1', 'ScatterCorrectionMethod'], 8045 '0x1200': ['DS', '1', 'AxialAcceptance'], 8046 '0x1201': ['IS', '2', 'AxialMash'], 8047 '0x1202': ['IS', '1', 'TransverseMash'], 8048 '0x1203': ['DS', '2', 'DetectorElementSize'], 8049 '0x1210': ['DS', '1', 'CoincidenceWindowWidth'], 8050 '0x1220': ['CS', '1-n', 'SecondaryCountsType'], 8051 '0x1300': ['DS', '1', 'FrameReferenceTime'], 8052 '0x1310': ['IS', '1', 'PrimaryPromptsCountsAccumulated'], 8053 '0x1311': ['IS', '1-n', 'SecondaryCountsAccumulated'], 8054 '0x1320': ['DS', '1', 'SliceSensitivityFactor'], 8055 '0x1321': ['DS', '1', 'DecayFactor'], 8056 '0x1322': ['DS', '1', 'DoseCalibrationFactor'], 8057 '0x1323': ['DS', '1', 'ScatterFractionFactor'], 8058 '0x1324': ['DS', '1', 'DeadTimeFactor'], 8059 '0x1330': ['US', '1', 'ImageIndex'], 8060 '0x1400': ['CS', '1-n', 'CountsIncluded'], 8061 '0x1401': ['CS', '1', 'DeadTimeCorrectionFlag'] 8062 }, 8063 '0x0060': { 8064 '0x0000': ['UL', '1', 'GenericGroupLength'], 8065 '0x3000': ['SQ', '1', 'HistogramSequence'], 8066 '0x3002': ['US', '1', 'HistogramNumberOfBins'], 8067 '0x3004': ['xs', '1', 'HistogramFirstBinValue'], 8068 '0x3006': ['xs', '1', 'HistogramLastBinValue'], 8069 '0x3008': ['US', '1', 'HistogramBinWidth'], 8070 '0x3010': ['LO', '1', 'HistogramExplanation'], 8071 '0x3020': ['UL', '1-n', 'HistogramData'] 8072 }, 8073 '0x0062': { 8074 '0x0000': ['UL', '1', 'GenericGroupLength'], 8075 '0x0001': ['CS', '1', 'SegmentationType'], 8076 '0x0002': ['SQ', '1', 'SegmentSequence'], 8077 '0x0003': ['SQ', '1', 'SegmentedPropertyCategoryCodeSequence'], 8078 '0x0004': ['US', '1', 'SegmentNumber'], 8079 '0x0005': ['LO', '1', 'SegmentLabel'], 8080 '0x0006': ['ST', '1', 'SegmentDescription'], 8081 '0x0008': ['CS', '1', 'SegmentAlgorithmType'], 8082 '0x0009': ['LO', '1', 'SegmentAlgorithmName'], 8083 '0x000A': ['SQ', '1', 'SegmentIdentificationSequence'], 8084 '0x000B': ['US', '1-n', 'ReferencedSegmentNumber'], 8085 '0x000C': ['US', '1', 'RecommendedDisplayGrayscaleValue'], 8086 '0x000D': ['US', '3', 'RecommendedDisplayCIELabValue'], 8087 '0x000E': ['US', '1', 'MaximumFractionalValue'],
8088 '0x000F': ['SQ', '1', 'SegmentedPropertyTypeCodeSequence'], 8089 '0x0010': ['CS', '1', 'SegmentationFractionalType'], 8090 '0x0011': ['SQ', '1', 'SegmentedPropertyTypeModifierCodeSequence'], 8091 '0x0012': ['SQ', '1', 'UsedSegmentsSequence'] 8092 }, 8093 '0x0064': { 8094 '0x0000': ['UL', '1', 'GenericGroupLength'], 8095 '0x0002': ['SQ', '1', 'DeformableRegistrationSequence'], 8096 '0x0003': ['UI', '1', 'SourceFrameOfReferenceUID'], 8097 '0x0005': ['SQ', '1', 'DeformableRegistrationGridSequence'], 8098 '0x0007': ['UL', '3', 'GridDimensions'], 8099 '0x0008': ['FD', '3', 'GridResolution'], 8100 '0x0009': ['OF', '1', 'VectorGridData'], 8101 '0x000F': ['SQ', '1', 'PreDeformationMatrixRegistrationSequence'], 8102 '0x0010': ['SQ', '1', 'PostDeformationMatrixRegistrationSequence'] 8103 }, 8104 '0x0066': { 8105 '0x0000': ['UL', '1', 'GenericGroupLength'], 8106 '0x0001': ['UL', '1', 'NumberOfSurfaces'], 8107 '0x0002': ['SQ', '1', 'SurfaceSequence'], 8108 '0x0003': ['UL', '1', 'SurfaceNumber'], 8109 '0x0004': ['LT', '1', 'SurfaceComments'], 8110 '0x0009': ['CS', '1', 'SurfaceProcessing'], 8111 '0x000A': ['FL', '1', 'SurfaceProcessingRatio'], 8112 '0x000B': ['LO', '1', 'SurfaceProcessingDescription'], 8113 '0x000C': ['FL', '1', 'RecommendedPresentationOpacity'], 8114 '0x000D': ['CS', '1', 'RecommendedPresentationType'], 8115 '0x000E': ['CS', '1', 'FiniteVolume'], 8116 '0x0010': ['CS', '1', 'Manifold'], 8117 '0x0011': ['SQ', '1', 'SurfacePointsSequence'], 8118 '0x0012': ['SQ', '1', 'SurfacePointsNormalsSequence'], 8119 '0x0013': ['SQ', '1', 'SurfaceMeshPrimitivesSequence'], 8120 '0x0015': ['UL', '1', 'NumberOfSurfacePoints'], 8121 '0x0016': ['OF', '1', 'PointCoordinatesData'], 8122 '0x0017': ['FL', '3', 'PointPositionAccuracy'], 8123 '0x0018': ['FL', '1', 'MeanPointDistance'], 8124 '0x0019': ['FL', '1', 'MaximumPointDistance'], 8125 '0x001A': ['FL', '6', 'PointsBoundingBoxCoordinates'], 8126 '0x001B': ['FL', '3', 'AxisOfRotation'], 8127 '0x001C': ['FL', '3', 'CenterOfRotation'], 8128 '0x001E': ['UL', '1', 'NumberOfVectors'], 8129 '0x001F': ['US', '1', 'VectorDimensionality'], 8130 '0x0020': ['FL', '1-n', 'VectorAccuracy'], 8131 '0x0021': ['OF', '1', 'VectorCoordinateData'], 8132 '0x0023': ['OW', '1', 'TrianglePointIndexList'], 8133 '0x0024': ['OW', '1', 'EdgePointIndexList'], 8134 '0x0025': ['OW', '1', 'VertexPointIndexList'], 8135 '0x0026': ['SQ', '1', 'TriangleStripSequence'], 8136 '0x0027': ['SQ', '1', 'TriangleFanSequence'], 8137 '0x0028': ['SQ', '1', 'LineSequence'], 8138 '0x0029': ['OW', '1', 'PrimitivePointIndexList'], 8139 '0x002A': ['UL', '1', 'SurfaceCount'], 8140 '0x002B': ['SQ', '1', 'ReferencedSurfaceSequence'], 8141 '0x002C': ['UL', '1', 'ReferencedSurfaceNumber'], 8142 '0x002D': ['SQ', '1', 'SegmentSurfaceGenerationAlgorithmIdentificationSequence'], 8143 '0x002E': ['SQ', '1', 'SegmentSurfaceSourceInstanceSequence'], 8144 '0x002F': ['SQ', '1', 'AlgorithmFamilyCodeSequence'], 8145 '0x0030': ['SQ', '1', 'AlgorithmNameCodeSequence'], 8146 '0x0031': ['LO', '1', 'AlgorithmVersion'], 8147 '0x0032': ['LT', '1', 'AlgorithmParameters'], 8148 '0x0034': ['SQ', '1', 'FacetSequence'], 8149 '0x0035': ['SQ', '1', 'SurfaceProcessingAlgorithmIdentificationSequence'], 8150 '0x0036': ['LO', '1', 'AlgorithmName'], 8151 '0x0037': ['FL', '1', 'RecommendedPointRadius'], 8152 '0x0038': ['FL', '1', 'RecommendedLineThickness'], 8153 '0x0040': ['UL', '1-n', 'LongPrimitivePointIndexList'], 8154 '0x0041': ['UL', '3-3n', 'LongTrianglePointIndexList'], 8155 '0x0042': ['UL', '2-2n', 'LongEdgePointIndexList'], 8156 '0x0043': ['UL', '1-n', 'LongVertexPointIndexList'] 8157 }, 8158 '0x0068': { 8159 '0x0000': ['UL', '1', 'GenericGroupLength'], 8160 '0x6210': ['LO', '1', 'ImplantSize'], 8161 '0x6221': ['LO', '1', 'ImplantTemplateVersion'], 8162 '0x6222': ['SQ', '1', 'ReplacedImplantTemplateSequence'], 8163 '0x6223': ['CS', '1', 'ImplantType'], 8164 '0x6224': ['SQ', '1', 'DerivationImplantTemplateSequence'], 8165 '0x6225': ['SQ', '1', 'OriginalImplantTemplateSequence'], 8166 '0x6226': ['DT', '1', 'EffectiveDateTime'], 8167 '0x6230': ['SQ', '1', 'ImplantTargetAnatomySequence'], 8168 '0x6260': ['SQ', '1', 'InformationFromManufacturerSequence'], 8169 '0x6265': ['SQ', '1', 'NotificationFromManufacturerSequence'], 8170 '0x6270': ['DT', '1', 'InformationIssueDateTime'], 8171 '0x6280': ['ST', '1', 'InformationSummary'], 8172 '0x62A0': ['SQ', '1', 'ImplantRegulatoryDisapprovalCodeSequence'], 8173 '0x62A5': ['FD', '1', 'OverallTemplateSpatialTolerance'], 8174 '0x62C0': ['SQ', '1', 'HPGLDocumentSequence'], 8175 '0x62D0': ['US', '1', 'HPGLDocumentID'], 8176 '0x62D5': ['LO', '1', 'HPGLDocumentLabel'], 8177 '0x62E0': ['SQ', '1', 'ViewOrientationCodeSequence'], 8178 '0x62F0': ['FD', '9', 'ViewOrientationModifier'], 8179 '0x62F2': ['FD', '1', 'HPGLDocumentScaling'], 8180 '0x6300': ['OB', '1', 'HPGLDocument'], 8181 '0x6310': ['US', '1', 'HPGLContourPenNumber'], 8182 '0x6320': ['SQ', '1', 'HPGLPenSequence'], 8183 '0x6330': ['US', '1', 'HPGLPenNumber'], 8184 '0x6340': ['LO', '1', 'HPGLPenLabel'], 8185 '0x6345': ['ST', '1', 'HPGLPenDescription'], 8186 '0x6346': ['FD', '2', 'RecommendedRotationPoint'], 8187 '0x6347': ['FD', '4', 'BoundingRectangle'], 8188 '0x6350': ['US', '1-n', 'ImplantTemplate3DModelSurfaceNumber'], 8189 '0x6360': ['SQ', '1', 'SurfaceModelDescriptionSequence'], 8190 '0x6380': ['LO', '1', 'SurfaceModelLabel'], 8191 '0x6390': ['FD', '1', 'SurfaceModelScalingFactor'], 8192 '0x63A0': ['SQ', '1', 'MaterialsCodeSequence'], 8193 '0x63A4': ['SQ', '1', 'CoatingMaterialsCodeSequence'], 8194 '0x63A8': ['SQ', '1', 'ImplantTypeCodeSequence'], 8195 '0x63AC': ['SQ', '1', 'FixationMethodCodeSequence'], 8196 '0x63B0': ['SQ', '1', 'MatingFeatureSetsSequence'], 8197 '0x63C0': ['US', '1', 'MatingFeatureSetID'], 8198 '0x63D0': ['LO', '1', 'MatingFeatureSetLabel'], 8199 '0x63E0': ['SQ', '1', 'MatingFeatureSequence'], 8200 '0x63F0': ['US', '1', 'MatingFeatureID'], 8201 '0x6400': ['SQ', '1', 'MatingFeatureDegreeOfFreedomSequence'], 8202 '0x6410': ['US', '1', 'DegreeOfFreedomID'], 8203 '0x6420': ['CS', '1', 'DegreeOfFreedomType'], 8204 '0x6430': ['SQ', '1', 'TwoDMatingFeatureCoordinatesSequence'], 8205 '0x6440': ['US', '1', 'ReferencedHPGLDocumentID'], 8206 '0x6450': ['FD', '2', 'TwoDMatingPoint'], 8207 '0x6460': ['FD', '4', 'TwoDMatingAxes'], 8208 '0x6470': ['SQ', '1', 'TwoDDegreeOfFreedomSequence'], 8209 '0x6490': ['FD', '3', 'ThreeDDegreeOfFreedomAxis'], 8210 '0x64A0': ['FD', '2', 'RangeOfFreedom'], 8211 '0x64C0': ['FD', '3', 'ThreeDMatingPoint'], 8212 '0x64D0': ['FD', '9', 'ThreeDMatingAxes'], 8213 '0x64F0': ['FD', '3', 'TwoDDegreeOfFreedomAxis'], 8214 '0x6500': ['SQ', '1', 'PlanningLandmarkPointSequence'], 8215 '0x6510': ['SQ', '1', 'PlanningLandmarkLineSequence'], 8216 '0x6520': ['SQ', '1', 'PlanningLandmarkPlaneSequence'], 8217 '0x6530': ['US', '1', 'PlanningLandmarkID'], 8218 '0x6540': ['LO', '1', 'PlanningLandmarkDescription'], 8219 '0x6545': ['SQ', '1', 'PlanningLandmarkIdentificationCodeSequence'], 8220 '0x6550': ['SQ', '1', 'TwoDPointCoordinatesSequence'], 8221 '0x6560': ['FD', '2', 'TwoDPointCoordinates'], 8222 '0x6590': ['FD', '3', 'ThreeDPointCoordinates'], 8223 '0x65A0': ['SQ', '1', 'TwoDLineCoordinatesSequence'], 8224 '0x65B0': ['FD', '4', 'TwoDLineCoordinates'],
8225 '0x65D0': ['FD', '6', 'ThreeDLineCoordinates'], 8226 '0x65E0': ['SQ', '1', 'TwoDPlaneCoordinatesSequence'], 8227 '0x65F0': ['FD', '4', 'TwoDPlaneIntersection'], 8228 '0x6610': ['FD', '3', 'ThreeDPlaneOrigin'], 8229 '0x6620': ['FD', '3', 'ThreeDPlaneNormal'] 8230 }, 8231 '0x0070': { 8232 '0x0000': ['UL', '1', 'GenericGroupLength'], 8233 '0x0001': ['SQ', '1', 'GraphicAnnotationSequence'], 8234 '0x0002': ['CS', '1', 'GraphicLayer'], 8235 '0x0003': ['CS', '1', 'BoundingBoxAnnotationUnits'], 8236 '0x0004': ['CS', '1', 'AnchorPointAnnotationUnits'], 8237 '0x0005': ['CS', '1', 'GraphicAnnotationUnits'], 8238 '0x0006': ['ST', '1', 'UnformattedTextValue'], 8239 '0x0008': ['SQ', '1', 'TextObjectSequence'], 8240 '0x0009': ['SQ', '1', 'GraphicObjectSequence'], 8241 '0x0010': ['FL', '2', 'BoundingBoxTopLeftHandCorner'], 8242 '0x0011': ['FL', '2', 'BoundingBoxBottomRightHandCorner'], 8243 '0x0012': ['CS', '1', 'BoundingBoxTextHorizontalJustification'], 8244 '0x0014': ['FL', '2', 'AnchorPoint'], 8245 '0x0015': ['CS', '1', 'AnchorPointVisibility'], 8246 '0x0020': ['US', '1', 'GraphicDimensions'], 8247 '0x0021': ['US', '1', 'NumberOfGraphicPoints'], 8248 '0x0022': ['FL', '2-n', 'GraphicData'], 8249 '0x0023': ['CS', '1', 'GraphicType'], 8250 '0x0024': ['CS', '1', 'GraphicFilled'], 8251 '0x0040': ['IS', '1', 'ImageRotationRetired'], 8252 '0x0041': ['CS', '1', 'ImageHorizontalFlip'], 8253 '0x0042': ['US', '1', 'ImageRotation'], 8254 '0x0050': ['US', '2', 'DisplayedAreaTopLeftHandCornerTrial'], 8255 '0x0051': ['US', '2', 'DisplayedAreaBottomRightHandCornerTrial'], 8256 '0x0052': ['SL', '2', 'DisplayedAreaTopLeftHandCorner'], 8257 '0x0053': ['SL', '2', 'DisplayedAreaBottomRightHandCorner'], 8258 '0x005A': ['SQ', '1', 'DisplayedAreaSelectionSequence'], 8259 '0x0060': ['SQ', '1', 'GraphicLayerSequence'], 8260 '0x0062': ['IS', '1', 'GraphicLayerOrder'], 8261 '0x0066': ['US', '1', 'GraphicLayerRecommendedDisplayGrayscaleValue'], 8262 '0x0067': ['US', '3', 'GraphicLayerRecommendedDisplayRGBValue'], 8263 '0x0068': ['LO', '1', 'GraphicLayerDescription'], 8264 '0x0080': ['CS', '1', 'ContentLabel'], 8265 '0x0081': ['LO', '1', 'ContentDescription'], 8266 '0x0082': ['DA', '1', 'PresentationCreationDate'], 8267 '0x0083': ['TM', '1', 'PresentationCreationTime'], 8268 '0x0084': ['PN', '1', 'ContentCreatorName'], 8269 '0x0086': ['SQ', '1', 'ContentCreatorIdentificationCodeSequence'], 8270 '0x0087': ['SQ', '1', 'AlternateContentDescriptionSequence'], 8271 '0x0100': ['CS', '1', 'PresentationSizeMode'], 8272 '0x0101': ['DS', '2', 'PresentationPixelSpacing'], 8273 '0x0102': ['IS', '2', 'PresentationPixelAspectRatio'], 8274 '0x0103': ['FL', '1', 'PresentationPixelMagnificationRatio'], 8275 '0x0207': ['LO', '1', 'GraphicGroupLabel'], 8276 '0x0208': ['ST', '1', 'GraphicGroupDescription'], 8277 '0x0209': ['SQ', '1', 'CompoundGraphicSequence'], 8278 '0x0226': ['UL', '1', 'CompoundGraphicInstanceID'], 8279 '0x0227': ['LO', '1', 'FontName'], 8280 '0x0228': ['CS', '1', 'FontNameType'], 8281 '0x0229': ['LO', '1', 'CSSFontName'], 8282 '0x0230': ['FD', '1', 'RotationAngle'], 8283 '0x0231': ['SQ', '1', 'TextStyleSequence'], 8284 '0x0232': ['SQ', '1', 'LineStyleSequence'], 8285 '0x0233': ['SQ', '1', 'FillStyleSequence'], 8286 '0x0234': ['SQ', '1', 'GraphicGroupSequence'], 8287 '0x0241': ['US', '3', 'TextColorCIELabValue'], 8288 '0x0242': ['CS', '1', 'HorizontalAlignment'], 8289 '0x0243': ['CS', '1', 'VerticalAlignment'], 8290 '0x0244': ['CS', '1', 'ShadowStyle'], 8291 '0x0245': ['FL', '1', 'ShadowOffsetX'], 8292 '0x0246': ['FL', '1', 'ShadowOffsetY'], 8293 '0x0247': ['US', '3', 'ShadowColorCIELabValue'], 8294 '0x0248': ['CS', '1', 'Underlined'], 8295 '0x0249': ['CS', '1', 'Bold'], 8296 '0x0250': ['CS', '1', 'Italic'], 8297 '0x0251': ['US', '3', 'PatternOnColorCIELabValue'], 8298 '0x0252': ['US', '3', 'PatternOffColorCIELabValue'], 8299 '0x0253': ['FL', '1', 'LineThickness'], 8300 '0x0254': ['CS', '1', 'LineDashingStyle'], 8301 '0x0255': ['UL', '1', 'LinePattern'], 8302 '0x0256': ['OB', '1', 'FillPattern'], 8303 '0x0257': ['CS', '1', 'FillMode'], 8304 '0x0258': ['FL', '1', 'ShadowOpacity'], 8305 '0x0261': ['FL', '1', 'GapLength'], 8306 '0x0262': ['FL', '1', 'DiameterOfVisibility'], 8307 '0x0273': ['FL', '2', 'RotationPoint'], 8308 '0x0274': ['CS', '1', 'TickAlignment'], 8309 '0x0278': ['CS', '1', 'ShowTickLabel'], 8310 '0x0279': ['CS', '1', 'TickLabelAlignment'],
8311 '0x0282': ['CS', '1', 'CompoundGraphicUnits'], 8312 '0x0284': ['FL', '1', 'PatternOnOpacity'], 8313 '0x0285': ['FL', '1', 'PatternOffOpacity'], 8314 '0x0287': ['SQ', '1', 'MajorTicksSequence'], 8315 '0x0288': ['FL', '1', 'TickPosition'], 8316 '0x0289': ['SH', '1', 'TickLabel'], 8317 '0x0294': ['CS', '1', 'CompoundGraphicType'], 8318 '0x0295': ['UL', '1', 'GraphicGroupID'], 8319 '0x0306': ['CS', '1', 'ShapeType'], 8320 '0x0308': ['SQ', '1', 'RegistrationSequence'], 8321 '0x0309': ['SQ', '1', 'MatrixRegistrationSequence'], 8322 '0x030A': ['SQ', '1', 'MatrixSequence'], 8323 '0x030C': ['CS', '1', 'FrameOfReferenceTransformationMatrixType'], 8324 '0x030D': ['SQ', '1', 'RegistrationTypeCodeSequence'], 8325 '0x030F': ['ST', '1', 'FiducialDescription'], 8326 '0x0310': ['SH', '1', 'FiducialIdentifier'], 8327 '0x0311': ['SQ', '1', 'FiducialIdentifierCodeSequence'], 8328 '0x0312': ['FD', '1', 'ContourUncertaintyRadius'], 8329 '0x0314': ['SQ', '1', 'UsedFiducialsSequence'], 8330 '0x0318': ['SQ', '1', 'GraphicCoordinatesDataSequence'], 8331 '0x031A': ['UI', '1', 'FiducialUID'], 8332 '0x031C': ['SQ', '1', 'FiducialSetSequence'], 8333 '0x031E': ['SQ', '1', 'FiducialSequence'], 8334 '0x0401': ['US', '3', 'GraphicLayerRecommendedDisplayCIELabValue'], 8335 '0x0402': ['SQ', '1', 'BlendingSequence'], 8336 '0x0403': ['FL', '1', 'RelativeOpacity'], 8337 '0x0404': ['SQ', '1', 'ReferencedSpatialRegistrationSequence'], 8338 '0x0405': ['CS', '1', 'BlendingPosition'] 8339 }, 8340 '0x0072': { 8341 '0x0000': ['UL', '1', 'GenericGroupLength'], 8342 '0x0002': ['SH', '1', 'HangingProtocolName'], 8343 '0x0004': ['LO', '1', 'HangingProtocolDescription'], 8344 '0x0006': ['CS', '1', 'HangingProtocolLevel'], 8345 '0x0008': ['LO', '1', 'HangingProtocolCreator'], 8346 '0x000A': ['DT', '1', 'HangingProtocolCreationDateTime'], 8347 '0x000C': ['SQ', '1', 'HangingProtocolDefinitionSequence'], 8348 '0x000E': ['SQ', '1', 'HangingProtocolUserIdentificationCodeSequence'], 8349 '0x0010': ['LO', '1', 'HangingProtocolUserGroupName'], 8350 '0x0012': ['SQ', '1', 'SourceHangingProtocolSequence'], 8351 '0x0014': ['US', '1', 'NumberOfPriorsReferenced'], 8352 '0x0020': ['SQ', '1', 'ImageSetsSequence'], 8353 '0x0022': ['SQ', '1', 'ImageSetSelectorSequence'], 8354 '0x0024': ['CS', '1', 'ImageSetSelectorUsageFlag'], 8355 '0x0026': ['AT', '1', 'SelectorAttribute'], 8356 '0x0028': ['US', '1', 'SelectorValueNumber'], 8357 '0x0030': ['SQ', '1', 'TimeBasedImageSetsSequence'], 8358 '0x0032': ['US', '1', 'ImageSetNumber'], 8359 '0x0034': ['CS', '1', 'ImageSetSelectorCategory'], 8360 '0x0038': ['US', '2', 'RelativeTime'], 8361 '0x003A': ['CS', '1', 'RelativeTimeUnits'], 8362 '0x003C': ['SS', '2', 'AbstractPriorValue'], 8363 '0x003E': ['SQ', '1', 'AbstractPriorCodeSequence'], 8364 '0x0040': ['LO', '1', 'ImageSetLabel'], 8365 '0x0050': ['CS', '1', 'SelectorAttributeVR'], 8366 '0x0052': ['AT', '1-n', 'SelectorSequencePointer'], 8367 '0x0054': ['LO', '1-n', 'SelectorSequencePointerPrivateCreator'], 8368 '0x0056': ['LO', '1', 'SelectorAttributePrivateCreator'], 8369 '0x0060': ['AT', '1-n', 'SelectorATValue'], 8370 '0x0062': ['CS', '1-n', 'SelectorCSValue'], 8371 '0x0064': ['IS', '1-n', 'SelectorISValue'], 8372 '0x0066': ['LO', '1-n', 'SelectorLOValue'], 8373 '0x0068': ['LT', '1', 'SelectorLTValue'], 8374 '0x006A': ['PN', '1-n', 'SelectorPNValue'], 8375 '0x006C': ['SH', '1-n', 'SelectorSHValue'], 8376 '0x006E': ['ST', '1', 'SelectorSTValue'], 8377 '0x0070': ['UT', '1', 'SelectorUTValue'], 8378 '0x0072': ['DS', '1-n', 'SelectorDSValue'], 8379 '0x0074': ['FD', '1-n', 'SelectorFDValue'], 8380 '0x0076': ['FL', '1-n', 'SelectorFLValue'], 8381 '0x0078': ['UL', '1-n', 'SelectorULValue'], 8382 '0x007A': ['US', '1-n', 'SelectorUSValue'], 8383 '0x007C': ['SL', '1-n', 'SelectorSLValue'], 8384 '0x007E': ['SS', '1-n', 'SelectorSSValue'], 8385 '0x007F': ['UI', '1-n', 'SelectorUIValue'], 8386 '0x0080': ['SQ', '1', 'SelectorCodeSequenceValue'], 8387 '0x0100': ['US', '1', 'NumberOfScreens'], 8388 '0x0102': ['SQ', '1', 'NominalScreenDefinitionSequence'], 8389 '0x0104': ['US', '1', 'NumberOfVerticalPixels'], 8390 '0x0106': ['US', '1', 'NumberOfHorizontalPixels'], 8391 '0x0108': ['FD', '4', 'DisplayEnvironmentSpatialPosition'], 8392 '0x010A': ['US', '1', 'ScreenMinimumGrayscaleBitDepth'], 8393 '0x010C': ['US', '1', 'ScreenMinimumColorBitDepth'], 8394 '0x010E': ['US', '1', 'ApplicationMaximumRepaintTime'], 8395 '0x0200': ['SQ', '1', 'DisplaySetsSequence'], 8396 '0x0202': ['US', '1', 'DisplaySetNumber'], 8397 '0x0203': ['LO', '1', 'DisplaySetLabel'], 8398 '0x0204': ['US', '1', 'DisplaySetPresentationGroup'], 8399 '0x0206': ['LO', '1', 'DisplaySetPresentationGroupDescription'], 8400 '0x0208': ['CS', '1', 'PartialDataDisplayHandling'], 8401 '0x0210': ['SQ', '1', 'SynchronizedScrollingSequence'], 8402 '0x0212': ['US', '2-n', 'DisplaySetScrollingGroup'], 8403 '0x0214': ['SQ', '1', 'NavigationIndicatorSequence'], 8404 '0x0216': ['US', '1', 'NavigationDisplaySet'], 8405 '0x0218': ['US', '1-n', 'ReferenceDisplaySets'], 8406 '0x0300': ['SQ', '1', 'ImageBoxesSequence'], 8407 '0x0302': ['US', '1', 'ImageBoxNumber'], 8408 '0x0304': ['CS', '1', 'ImageBoxLayoutType'], 8409 '0x0306': ['US', '1', 'ImageBoxTileHorizontalDimension'], 8410 '0x0308': ['US', '1', 'ImageBoxTileVerticalDimension'], 8411 '0x0310': ['CS', '1', 'ImageBoxScrollDirection'], 8412 '0x0312': ['CS', '1', 'ImageBoxSmallScrollType'], 8413 '0x0314': ['US', '1', 'ImageBoxSmallScrollAmount'], 8414 '0x0316': ['CS', '1', 'ImageBoxLargeScrollType'], 8415 '0x0318': ['US', '1', 'ImageBoxLargeScrollAmount'], 8416 '0x0320': ['US', '1', 'ImageBoxOverlapPriority'], 8417 '0x0330': ['FD', '1', 'CineRelativeToRealTime'], 8418 '0x0400': ['SQ', '1', 'FilterOperationsSequence'], 8419 '0x0402': ['CS', '1', 'FilterByCategory'], 8420 '0x0404': ['CS', '1', 'FilterByAttributePresence'], 8421 '0x0406': ['CS', '1', 'FilterByOperator'],
8422 '0x0420': ['US', '3', 'StructuredDisplayBackgroundCIELabValue'], 8423 '0x0421': ['US', '3', 'EmptyImageBoxCIELabValue'], 8424 '0x0422': ['SQ', '1', 'StructuredDisplayImageBoxSequence'], 8425 '0x0424': ['SQ', '1', 'StructuredDisplayTextBoxSequence'], 8426 '0x0427': ['SQ', '1', 'ReferencedFirstFrameSequence'], 8427 '0x0430': ['SQ', '1', 'ImageBoxSynchronizationSequence'], 8428 '0x0432': ['US', '2-n', 'SynchronizedImageBoxList'], 8429 '0x0434': ['CS', '1', 'TypeOfSynchronization'], 8430 '0x0500': ['CS', '1', 'BlendingOperationType'], 8431 '0x0510': ['CS', '1', 'ReformattingOperationType'], 8432 '0x0512': ['FD', '1', 'ReformattingThickness'], 8433 '0x0514': ['FD', '1', 'ReformattingInterval'], 8434 '0x0516': ['CS', '1', 'ReformattingOperationInitialViewDirection'], 8435 '0x0520': ['CS', '1-n', 'ThreeDRenderingType'], 8436 '0x0600': ['SQ', '1', 'SortingOperationsSequence'], 8437 '0x0602': ['CS', '1', 'SortByCategory'], 8438 '0x0604': ['CS', '1', 'SortingDirection'], 8439 '0x0700': ['CS', '2', 'DisplaySetPatientOrientation'], 8440 '0x0702': ['CS', '1', 'VOIType'], 8441 '0x0704': ['CS', '1', 'PseudoColorType'], 8442 '0x0705': ['SQ', '1', 'PseudoColorPaletteInstanceReferenceSequence'], 8443 '0x0706': ['CS', '1', 'ShowGrayscaleInverted'], 8444 '0x0710': ['CS', '1', 'ShowImageTrueSizeFlag'], 8445 '0x0712': ['CS', '1', 'ShowGraphicAnnotationFlag'], 8446 '0x0714': ['CS', '1', 'ShowPatientDemographicsFlag'], 8447 '0x0716': ['CS', '1', 'ShowAcquisitionTechniquesFlag'], 8448 '0x0717': ['CS', '1', 'DisplaySetHorizontalJustification'], 8449 '0x0718': ['CS', '1', 'DisplaySetVerticalJustification'] 8450 }, 8451 '0x0074': { 8452 '0x0000': ['UL', '1', 'GenericGroupLength'], 8453 '0x0120': ['FD', '1', 'ContinuationStartMeterset'], 8454 '0x0121': ['FD', '1', 'ContinuationEndMeterset'], 8455 '0x1000': ['CS', '1', 'ProcedureStepState'], 8456 '0x1002': ['SQ', '1', 'ProcedureStepProgressInformationSequence'], 8457 '0x1004': ['DS', '1', 'ProcedureStepProgress'], 8458 '0x1006': ['ST', '1', 'ProcedureStepProgressDescription'], 8459 '0x1008': ['SQ', '1', 'ProcedureStepCommunicationsURISequence'], 8460 '0x100A': ['UR', '1', 'ContactURI'], 8461 '0x100C': ['LO', '1', 'ContactDisplayName'], 8462 '0x100E': ['SQ', '1', 'ProcedureStepDiscontinuationReasonCodeSequence'], 8463 '0x1020': ['SQ', '1', 'BeamTaskSequence'], 8464 '0x1022': ['CS', '1', 'BeamTaskType'], 8465 '0x1024': ['IS', '1', 'BeamOrderIndexTrial'], 8466 '0x1025': ['CS', '1', 'AutosequenceFlag'], 8467 '0x1026': ['FD', '1', 'TableTopVerticalAdjustedPosition'], 8468 '0x1027': ['FD', '1', 'TableTopLongitudinalAdjustedPosition'], 8469 '0x1028': ['FD', '1', 'TableTopLateralAdjustedPosition'], 8470 '0x102A': ['FD', '1', 'PatientSupportAdjustedAngle'], 8471 '0x102B': ['FD', '1', 'TableTopEccentricAdjustedAngle'], 8472 '0x102C': ['FD', '1', 'TableTopPitchAdjustedAngle'], 8473 '0x102D': ['FD', '1', 'TableTopRollAdjustedAngle'], 8474 '0x1030': ['SQ', '1', 'DeliveryVerificationImageSequence'], 8475 '0x1032': ['CS', '1', 'VerificationImageTiming'], 8476 '0x1034': ['CS', '1', 'DoubleExposureFlag'], 8477 '0x1036': ['CS', '1', 'DoubleExposureOrdering'], 8478 '0x1038': ['DS', '1', 'DoubleExposureMetersetTrial'], 8479 '0x103A': ['DS', '4', 'DoubleExposureFieldDeltaTrial'], 8480 '0x1040': ['SQ', '1', 'RelatedReferenceRTImageSequence'], 8481 '0x1042': ['SQ', '1', 'GeneralMachineVerificationSequence'], 8482 '0x1044': ['SQ', '1', 'ConventionalMachineVerificationSequence'], 8483 '0x1046': ['SQ', '1', 'IonMachineVerificationSequence'], 8484 '0x1048': ['SQ', '1', 'FailedAttributesSequence'], 8485 '0x104A': ['SQ', '1', 'OverriddenAttributesSequence'], 8486 '0x104C': ['SQ', '1', 'ConventionalControlPointVerificationSequence'], 8487 '0x104E': ['SQ', '1', 'IonControlPointVerificationSequence'], 8488 '0x1050': ['SQ', '1', 'AttributeOccurrenceSequence'], 8489 '0x1052': ['AT', '1', 'AttributeOccurrencePointer'], 8490 '0x1054': ['UL', '1', 'AttributeItemSelector'], 8491 '0x1056': ['LO', '1', 'AttributeOccurrencePrivateCreator'], 8492 '0x1057': ['IS', '1-n', 'SelectorSequencePointerItems'], 8493 '0x1200': ['CS', '1', 'ScheduledProcedureStepPriority'], 8494 '0x1202': ['LO', '1', 'WorklistLabel'], 8495 '0x1204': ['LO', '1', 'ProcedureStepLabel'], 8496 '0x1210': ['SQ', '1', 'ScheduledProcessingParametersSequence'], 8497 '0x1212': ['SQ', '1', 'PerformedProcessingParametersSequence'], 8498 '0x1216': ['SQ', '1', 'UnifiedProcedureStepPerformedProcedureSequence'], 8499 '0x1220': ['SQ', '1', 'RelatedProcedureStepSequence'], 8500 '0x1222': ['LO', '1', 'ProcedureStepRelationshipType'], 8501 '0x1224': ['SQ', '1', 'ReplacedProcedureStepSequence'], 8502 '0x1230': ['LO', '1', 'DeletionLock'], 8503 '0x1234': ['AE', '1', 'ReceivingAE'], 8504 '0x1236': ['AE', '1', 'RequestingAE'],
8505 '0x1238': ['LT', '1', 'ReasonForCancellation'], 8506 '0x1242': ['CS', '1', 'SCPStatus'], 8507 '0x1244': ['CS', '1', 'SubscriptionListStatus'], 8508 '0x1246': ['CS', '1', 'UnifiedProcedureStepListStatus'], 8509 '0x1324': ['UL', '1', 'BeamOrderIndex'], 8510 '0x1338': ['FD', '1', 'DoubleExposureMeterset'], 8511 '0x133A': ['FD', '4', 'DoubleExposureFieldDelta'] 8512 }, 8513 '0x0076': { 8514 '0x0000': ['UL', '1', 'GenericGroupLength'], 8515 '0x0001': ['LO', '1', 'ImplantAssemblyTemplateName'], 8516 '0x0003': ['LO', '1', 'ImplantAssemblyTemplateIssuer'], 8517 '0x0006': ['LO', '1', 'ImplantAssemblyTemplateVersion'], 8518 '0x0008': ['SQ', '1', 'ReplacedImplantAssemblyTemplateSequence'], 8519 '0x000A': ['CS', '1', 'ImplantAssemblyTemplateType'], 8520 '0x000C': ['SQ', '1', 'OriginalImplantAssemblyTemplateSequence'], 8521 '0x000E': ['SQ', '1', 'DerivationImplantAssemblyTemplateSequence'], 8522 '0x0010': ['SQ', '1', 'ImplantAssemblyTemplateTargetAnatomySequence'], 8523 '0x0020': ['SQ', '1', 'ProcedureTypeCodeSequence'], 8524 '0x0030': ['LO', '1', 'SurgicalTechnique'], 8525 '0x0032': ['SQ', '1', 'ComponentTypesSequence'], 8526 '0x0034': ['CS', '1', 'ComponentTypeCodeSequence'], 8527 '0x0036': ['CS', '1', 'ExclusiveComponentType'], 8528 '0x0038': ['CS', '1', 'MandatoryComponentType'], 8529 '0x0040': ['SQ', '1', 'ComponentSequence'], 8530 '0x0055': ['US', '1', 'ComponentID'], 8531 '0x0060': ['SQ', '1', 'ComponentAssemblySequence'], 8532 '0x0070': ['US', '1', 'Component1ReferencedID'], 8533 '0x0080': ['US', '1', 'Component1ReferencedMatingFeatureSetID'], 8534 '0x0090': ['US', '1', 'Component1ReferencedMatingFeatureID'], 8535 '0x00A0': ['US', '1', 'Component2ReferencedID'], 8536 '0x00B0': ['US', '1', 'Component2ReferencedMatingFeatureSetID'], 8537 '0x00C0': ['US', '1', 'Component2ReferencedMatingFeatureID'] 8538 }, 8539 '0x0078': { 8540 '0x0000': ['UL', '1', 'GenericGroupLength'], 8541 '0x0001': ['LO', '1', 'ImplantTemplateGroupName'], 8542 '0x0010': ['ST', '1', 'ImplantTemplateGroupDescription'], 8543 '0x0020': ['LO', '1', 'ImplantTemplateGroupIssuer'], 8544 '0x0024': ['LO', '1', 'ImplantTemplateGroupVersion'], 8545 '0x0026': ['SQ', '1', 'ReplacedImplantTemplateGroupSequence'], 8546 '0x0028': ['SQ', '1', 'ImplantTemplateGroupTargetAnatomySequence'], 8547 '0x002A': ['SQ', '1', 'ImplantTemplateGroupMembersSequence'], 8548 '0x002E': ['US', '1', 'ImplantTemplateGroupMemberID'], 8549 '0x0050': ['FD', '3', 'ThreeDImplantTemplateGroupMemberMatchingPoint'], 8550 '0x0060': ['FD', '9', 'ThreeDImplantTemplateGroupMemberMatchingAxes'], 8551 '0x0070': ['SQ', '1', 'ImplantTemplateGroupMemberMatching2DCoordinatesSequence'], 8552 '0x0090': ['FD', '2', 'TwoDImplantTemplateGroupMemberMatchingPoint'], 8553 '0x00A0': ['FD', '4', 'TwoDImplantTemplateGroupMemberMatchingAxes'], 8554 '0x00B0': ['SQ', '1', 'ImplantTemplateGroupVariationDimensionSequence'], 8555 '0x00B2': ['LO', '1', 'ImplantTemplateGroupVariationDimensionName'], 8556 '0x00B4': ['SQ', '1', 'ImplantTemplateGroupVariationDimensionRankSequence'], 8557 '0x00B6': ['US', '1', 'ReferencedImplantTemplateGroupMemberID'], 8558 '0x00B8': ['US', '1', 'ImplantTemplateGroupVariationDimensionRank'] 8559 }, 8560 '0x0080': { 8561 '0x0000': ['UL', '1', 'GenericGroupLength'], 8562 '0x0001': ['SQ', '1', 'SurfaceScanAcquisitionTypeCodeSequence'], 8563 '0x0002': ['SQ', '1', 'SurfaceScanModeCodeSequence'], 8564 '0x0003': ['SQ', '1', 'RegistrationMethodCodeSequence'], 8565 '0x0004': ['FD', '1', 'ShotDurationTime'], 8566 '0x0005': ['FD', '1', 'ShotOffsetTime'], 8567 '0x0006': ['US', '1-n', 'SurfacePointPresentationValueData'], 8568 '0x0007': ['US', '3-3n', 'SurfacePointColorCIELabValueData'], 8569 '0x0008': ['SQ', '1', 'UVMappingSequence'], 8570 '0x0009': ['SH', '1', 'TextureLabel'], 8571 '0x0010': ['OF', '1-n', 'UValueData'], 8572 '0x0011': ['OF', '1-n', 'VValueData'], 8573 '0x0012': ['SQ', '1', 'ReferencedTextureSequence'], 8574 '0x0013': ['SQ', '1', 'ReferencedSurfaceDataSequence'] 8575 }, 8576 '0x0088': { 8577 '0x0000': ['UL', '1', 'GenericGroupLength'], 8578 '0x0130': ['SH', '1', 'StorageMediaFileSetID'], 8579 '0x0140': ['UI', '1', 'StorageMediaFileSetUID'], 8580 '0x0200': ['SQ', '1', 'IconImageSequence'], 8581 '0x0904': ['LO', '1', 'TopicTitle'], 8582 '0x0906': ['ST', '1', 'TopicSubject'], 8583 '0x0910': ['LO', '1', 'TopicAuthor'], 8584 '0x0912': ['LO', '1-32', 'TopicKeywords'] 8585 }, 8586 '0x0100': { 8587 '0x0000': ['UL', '1', 'GenericGroupLength'], 8588 '0x0410': ['CS', '1', 'SOPInstanceStatus'], 8589 '0x0420': ['DT', '1', 'SOPAuthorizationDateTime'], 8590 '0x0424': ['LT', '1', 'SOPAuthorizationComment'], 8591 '0x0426': ['LO', '1', 'AuthorizationEquipmentCertificationNumber'] 8592 }, 8593 '0x0400': { 8594 '0x0000': ['UL', '1', 'GenericGroupLength'], 8595 '0x0005': ['US', '1', 'MACIDNumber'], 8596 '0x0010': ['UI', '1', 'MACCalculationTransferSyntaxUID'], 8597 '0x0015': ['CS', '1', 'MACAlgorithm'], 8598 '0x0020': ['AT', '1-n', 'DataElementsSigned'], 8599 '0x0100': ['UI', '1', 'DigitalSignatureUID'], 8600 '0x0105': ['DT', '1', 'DigitalSignatureDateTime'], 8601 '0x0110': ['CS', '1', 'CertificateType'], 8602 '0x0115': ['OB', '1', 'CertificateOfSigner'], 8603 '0x0120': ['OB', '1', 'Signature'], 8604 '0x0305': ['CS', '1', 'CertifiedTimestampType'], 8605 '0x0310': ['OB', '1', 'CertifiedTimestamp'], 8606 '0x0401': ['SQ', '1', 'DigitalSignaturePurposeCodeSequence'], 8607 '0x0402': ['SQ', '1', 'ReferencedDigitalSignatureSequence'], 8608 '0x0403': ['SQ', '1', 'ReferencedSOPInstanceMACSequence'], 8609 '0x0404': ['OB', '1', 'MAC'], 8610 '0x0500': ['SQ', '1', 'EncryptedAttributesSequence'], 8611 '0x0510': ['UI', '1', 'EncryptedContentTransferSyntaxUID'], 8612 '0x0520': ['OB', '1', 'EncryptedContent'], 8613 '0x0550': ['SQ', '1', 'ModifiedAttributesSequence'], 8614 '0x0561': ['SQ', '1', 'OriginalAttributesSequence'], 8615 '0x0562': ['DT', '1', 'AttributeModificationDateTime'], 8616 '0x0563': ['LO', '1', 'ModifyingSystem'], 8617 '0x0564': ['LO', '1', 'SourceOfPreviousValues'], 8618 '0x0565': ['CS', '1', 'ReasonForTheAttributeModification'] 8619 }, 8620 '0x1000': { 8621 '0x0000': ['UL', '1', 'GenericGroupLength'], 8622 '0x0010': ['US', '3', 'EscapeTriplet'], 8623 '0x0011': ['US', '3', 'RunLengthTriplet'], 8624 '0x0012': ['US', '1', 'HuffmanTableSize'], 8625 '0x0013': ['US', '3', 'HuffmanTableTriplet'], 8626 '0x0014': ['US', '1', 'ShiftTableSize'], 8627 '0x0015': ['US', '3', 'ShiftTableTriplet'] 8628 }, 8629 '0x1010': { 8630 '0x0000': ['UL', '1', 'GenericGroupLength'], 8631 '0x0004': ['US', '1-n', 'ZonalMap'] 8632 }, 8633 '0x2000': { 8634 '0x0000': ['UL', '1', 'GenericGroupLength'], 8635 '0x0010': ['IS', '1', 'NumberOfCopies'], 8636 '0x001E': ['SQ', '1', 'PrinterConfigurationSequence'], 8637 '0x0020': ['CS', '1', 'PrintPriority'], 8638 '0x0030': ['CS', '1', 'MediumType'], 8639 '0x0040': ['CS', '1', 'FilmDestination'], 8640 '0x0050': ['LO', '1', 'FilmSessionLabel'], 8641 '0x0060': ['IS', '1', 'MemoryAllocation'], 8642 '0x0061': ['IS', '1', 'MaximumMemoryAllocation'], 8643 '0x0062': ['CS', '1', 'ColorImagePrintingFlag'], 8644 '0x0063': ['CS', '1', 'CollationFlag'], 8645 '0x0065': ['CS', '1', 'AnnotationFlag'], 8646 '0x0067': ['CS', '1', 'ImageOverlayFlag'], 8647 '0x0069': ['CS', '1', 'PresentationLUTFlag'], 8648 '0x006A': ['CS', '1', 'ImageBoxPresentationLUTFlag'], 8649 '0x00A0': ['US', '1', 'MemoryBitDepth'], 8650 '0x00A1': ['US', '1', 'PrintingBitDepth'], 8651 '0x00A2': ['SQ', '1', 'MediaInstalledSequence'], 8652 '0x00A4': ['SQ', '1', 'OtherMediaAvailableSequence'], 8653 '0x00A8': ['SQ', '1', 'SupportedImageDisplayFormatsSequence'], 8654 '0x0500': ['SQ', '1', 'ReferencedFilmBoxSequence'], 8655 '0x0510': ['SQ', '1', 'ReferencedStoredPrintSequence'] 8656 }, 8657 '0x2010': { 8658 '0x0000': ['UL', '1', 'GenericGroupLength'], 8659 '0x0010': ['ST', '1', 'ImageDisplayFormat'], 8660 '0x0030': ['CS', '1', 'AnnotationDisplayFormatID'], 8661 '0x0040': ['CS', '1', 'FilmOrientation'], 8662 '0x0050': ['CS', '1', 'FilmSizeID'], 8663 '0x0052': ['CS', '1', 'PrinterResolutionID'], 8664 '0x0054': ['CS', '1', 'DefaultPrinterResolutionID'], 8665 '0x0060': ['CS', '1', 'MagnificationType'], 8666 '0x0080': ['CS', '1', 'SmoothingType'], 8667 '0x00A6': ['CS', '1', 'DefaultMagnificationType'], 8668 '0x00A7': ['CS', '1-n', 'OtherMagnificationTypesAvailable'], 8669 '0x00A8': ['CS', '1', 'DefaultSmoothingType'], 8670 '0x00A9': ['CS', '1-n', 'OtherSmoothingTypesAvailable'], 8671 '0x0100': ['CS', '1', 'BorderDensity'], 8672 '0x0110': ['CS', '1', 'EmptyImageDensity'], 8673 '0x0120': ['US', '1', 'MinDensity'], 8674 '0x0130': ['US', '1', 'MaxDensity'], 8675 '0x0140': ['CS', '1', 'Trim'], 8676 '0x0150': ['ST', '1', 'ConfigurationInformation'], 8677 '0x0152': ['LT', '1', 'ConfigurationInformationDescription'], 8678 '0x0154': ['IS', '1', 'MaximumCollatedFilms'], 8679 '0x015E': ['US', '1', 'Illumination'], 8680 '0x0160': ['US', '1', 'ReflectedAmbientLight'], 8681 '0x0376': ['DS', '2', 'PrinterPixelSpacing'], 8682 '0x0500': ['SQ', '1', 'ReferencedFilmSessionSequence'], 8683 '0x0510': ['SQ', '1', 'ReferencedImageBoxSequence'], 8684 '0x0520': ['SQ', '1', 'ReferencedBasicAnnotationBoxSequence'] 8685 }, 8686 '0x2020': { 8687 '0x0000': ['UL', '1', 'GenericGroupLength'], 8688 '0x0010': ['US', '1', 'ImageBoxPosition'], 8689 '0x0020': ['CS', '1', 'Polarity'], 8690 '0x0030': ['DS', '1', 'RequestedImageSize'], 8691 '0x0040': ['CS', '1', 'RequestedDecimateCropBehavior'], 8692 '0x0050': ['CS', '1', 'RequestedResolutionID'], 8693 '0x00A0': ['CS', '1', 'RequestedImageSizeFlag'], 8694 '0x00A2': ['CS', '1', 'DecimateCropResult'], 8695 '0x0110': ['SQ', '1', 'BasicGrayscaleImageSequence'], 8696 '0x0111': ['SQ', '1', 'BasicColorImageSequence'], 8697 '0x0130': ['SQ', '1', 'ReferencedImageOverlayBoxSequence'], 8698 '0x0140': ['SQ', '1', 'ReferencedVOILUTBoxSequence'] 8699 }, 8700 '0x2030': { 8701 '0x0000': ['UL', '1', 'GenericGroupLength'], 8702 '0x0010': ['US', '1', 'AnnotationPosition'], 8703 '0x0020': ['LO', '1', 'TextString'] 8704 }, 8705 '0x2040': { 8706 '0x0000': ['UL', '1', 'GenericGroupLength'], 8707 '0x0010': ['SQ', '1', 'ReferencedOverlayPlaneSequence'], 8708 '0x0011': ['US', '1-99', 'ReferencedOverlayPlaneGroups'],
8709 '0x0020': ['SQ', '1', 'OverlayPixelDataSequence'], 8710 '0x0060': ['CS', '1', 'OverlayMagnificationType'], 8711 '0x0070': ['CS', '1', 'OverlaySmoothingType'], 8712 '0x0072': ['CS', '1', 'OverlayOrImageMagnification'], 8713 '0x0074': ['US', '1', 'MagnifyToNumberOfColumns'], 8714 '0x0080': ['CS', '1', 'OverlayForegroundDensity'], 8715 '0x0082': ['CS', '1', 'OverlayBackgroundDensity'], 8716 '0x0090': ['CS', '1', 'OverlayMode'], 8717 '0x0100': ['CS', '1', 'ThresholdDensity'], 8718 '0x0500': ['SQ', '1', 'ReferencedImageBoxSequenceRetired'] 8719 }, 8720 '0x2050': { 8721 '0x0000': ['UL', '1', 'GenericGroupLength'], 8722 '0x0010': ['SQ', '1', 'PresentationLUTSequence'], 8723 '0x0020': ['CS', '1', 'PresentationLUTShape'], 8724 '0x0500': ['SQ', '1', 'ReferencedPresentationLUTSequence'] 8725 }, 8726 '0x2100': { 8727 '0x0000': ['UL', '1', 'GenericGroupLength'], 8728 '0x0010': ['SH', '1', 'PrintJobID'], 8729 '0x0020': ['CS', '1', 'ExecutionStatus'], 8730 '0x0030': ['CS', '1', 'ExecutionStatusInfo'], 8731 '0x0040': ['DA', '1', 'CreationDate'], 8732 '0x0050': ['TM', '1', 'CreationTime'], 8733 '0x0070': ['AE', '1', 'Originator'], 8734 '0x0140': ['AE', '1', 'DestinationAE'], 8735 '0x0160': ['SH', '1', 'OwnerID'], 8736 '0x0170': ['IS', '1', 'NumberOfFilms'], 8737 '0x0500': ['SQ', '1', 'ReferencedPrintJobSequencePullStoredPrint'] 8738 }, 8739 '0x2110': { 8740 '0x0000': ['UL', '1', 'GenericGroupLength'], 8741 '0x0010': ['CS', '1', 'PrinterStatus'], 8742 '0x0020': ['CS', '1', 'PrinterStatusInfo'], 8743 '0x0030': ['LO', '1', 'PrinterName'], 8744 '0x0099': ['SH', '1', 'PrintQueueID'] 8745 }, 8746 '0x2120': { 8747 '0x0000': ['UL', '1', 'GenericGroupLength'], 8748 '0x0010': ['CS', '1', 'QueueStatus'], 8749 '0x0050': ['SQ', '1', 'PrintJobDescriptionSequence'], 8750 '0x0070': ['SQ', '1', 'ReferencedPrintJobSequence'] 8751 }, 8752 '0x2130': { 8753 '0x0000': ['UL', '1', 'GenericGroupLength'], 8754 '0x0010': ['SQ', '1', 'PrintManagementCapabilitiesSequence'], 8755 '0x0015': ['SQ', '1', 'PrinterCharacteristicsSequence'], 8756 '0x0030': ['SQ', '1', 'FilmBoxContentSequence'], 8757 '0x0040': ['SQ', '1', 'ImageBoxContentSequence'], 8758 '0x0050': ['SQ', '1', 'AnnotationContentSequence'], 8759 '0x0060': ['SQ', '1', 'ImageOverlayBoxContentSequence'], 8760 '0x0080': ['SQ', '1', 'PresentationLUTContentSequence'], 8761 '0x00A0': ['SQ', '1', 'ProposedStudySequence'], 8762 '0x00C0': ['SQ', '1', 'OriginalImageSequence'] 8763 }, 8764 '0x2200': { 8765 '0x0000': ['UL', '1', 'GenericGroupLength'], 8766 '0x0001': ['CS', '1', 'LabelUsingInformationExtractedFromInstances'], 8767 '0x0002': ['UT', '1', 'LabelText'], 8768 '0x0003': ['CS', '1', 'LabelStyleSelection'], 8769 '0x0004': ['LT', '1', 'MediaDisposition'], 8770 '0x0005': ['LT', '1', 'BarcodeValue'], 8771 '0x0006': ['CS', '1', 'BarcodeSymbology'], 8772 '0x0007': ['CS', '1', 'AllowMediaSplitting'], 8773 '0x0008': ['CS', '1', 'IncludeNonDICOMObjects'], 8774 '0x0009': ['CS', '1', 'IncludeDisplayApplication'], 8775 '0x000A': ['CS', '1', 'PreserveCompositeInstancesAfterMediaCreation'], 8776 '0x000B': ['US', '1', 'TotalNumberOfPiecesOfMediaCreated'], 8777 '0x000C': ['LO', '1', 'RequestedMediaApplicationProfile'], 8778 '0x000D': ['SQ', '1', 'ReferencedStorageMediaSequence'], 8779 '0x000E': ['AT', '1-n', 'FailureAttributes'], 8780 '0x000F': ['CS', '1', 'AllowLossyCompression'], 8781 '0x0020': ['CS', '1', 'RequestPriority'] 8782 }, 8783 '0x3002': { 8784 '0x0000': ['UL', '1', 'GenericGroupLength'], 8785 '0x0002': ['SH', '1', 'RTImageLabel'], 8786 '0x0003': ['LO', '1', 'RTImageName'], 8787 '0x0004': ['ST', '1', 'RTImageDescription'], 8788 '0x000A': ['CS', '1', 'ReportedValuesOrigin'], 8789 '0x000C': ['CS', '1', 'RTImagePlane'], 8790 '0x000D': ['DS', '3', 'XRayImageReceptorTranslation'], 8791 '0x000E': ['DS', '1', 'XRayImageReceptorAngle'], 8792 '0x0010': ['DS', '6', 'RTImageOrientation'], 8793 '0x0011': ['DS', '2', 'ImagePlanePixelSpacing'], 8794 '0x0012': ['DS', '2', 'RTImagePosition'], 8795 '0x0020': ['SH', '1', 'RadiationMachineName'], 8796 '0x0022': ['DS', '1', 'RadiationMachineSAD'], 8797 '0x0024': ['DS', '1', 'RadiationMachineSSD'], 8798 '0x0026': ['DS', '1', 'RTImageSID'], 8799 '0x0028': ['DS', '1', 'SourceToReferenceObjectDistance'], 8800 '0x0029': ['IS', '1', 'FractionNumber'], 8801 '0x0030': ['SQ', '1', 'ExposureSequence'], 8802 '0x0032': ['DS', '1', 'MetersetExposure'], 8803 '0x0034': ['DS', '4', 'DiaphragmPosition'], 8804 '0x0040': ['SQ', '1', 'FluenceMapSequence'], 8805 '0x0041': ['CS', '1', 'FluenceDataSource'], 8806 '0x0042': ['DS', '1', 'FluenceDataScale'], 8807 '0x0050': ['SQ', '1', 'PrimaryFluenceModeSequence'], 8808 '0x0051': ['CS', '1', 'FluenceMode'], 8809 '0x0052': ['SH', '1', 'FluenceModeID'] 8810 }, 8811 '0x3004': { 8812 '0x0000': ['UL', '1', 'GenericGroupLength'], 8813 '0x0001': ['CS', '1', 'DVHType'], 8814 '0x0002': ['CS', '1', 'DoseUnits'], 8815 '0x0004': ['CS', '1', 'DoseType'], 8816 '0x0005': ['CS', '1', 'SpatialTransformOfDose'], 8817 '0x0006': ['LO', '1', 'DoseComment'], 8818 '0x0008': ['DS', '3', 'NormalizationPoint'], 8819 '0x000A': ['CS', '1', 'DoseSummationType'], 8820 '0x000C': ['DS', '2-n', 'GridFrameOffsetVector'], 8821 '0x000E': ['DS', '1', 'DoseGridScaling'], 8822 '0x0010': ['SQ', '1', 'RTDoseROISequence'], 8823 '0x0012': ['DS', '1', 'DoseValue'], 8824 '0x0014': ['CS', '1-3', 'TissueHeterogeneityCorrection'], 8825 '0x0040': ['DS', '3', 'DVHNormalizationPoint'], 8826 '0x0042': ['DS', '1', 'DVHNormalizationDoseValue'], 8827 '0x0050': ['SQ', '1', 'DVHSequence'], 8828 '0x0052': ['DS', '1', 'DVHDoseScaling'], 8829 '0x0054': ['CS', '1', 'DVHVolumeUnits'], 8830 '0x0056': ['IS', '1', 'DVHNumberOfBins'],
8831 '0x0058': ['DS', '2-2n', 'DVHData'], 8832 '0x0060': ['SQ', '1', 'DVHReferencedROISequence'], 8833 '0x0062': ['CS', '1', 'DVHROIContributionType'], 8834 '0x0070': ['DS', '1', 'DVHMinimumDose'], 8835 '0x0072': ['DS', '1', 'DVHMaximumDose'], 8836 '0x0074': ['DS', '1', 'DVHMeanDose'] 8837 }, 8838 '0x3006': { 8839 '0x0000': ['UL', '1', 'GenericGroupLength'], 8840 '0x0002': ['SH', '1', 'StructureSetLabel'], 8841 '0x0004': ['LO', '1', 'StructureSetName'], 8842 '0x0006': ['ST', '1', 'StructureSetDescription'], 8843 '0x0008': ['DA', '1', 'StructureSetDate'], 8844 '0x0009': ['TM', '1', 'StructureSetTime'], 8845 '0x0010': ['SQ', '1', 'ReferencedFrameOfReferenceSequence'], 8846 '0x0012': ['SQ', '1', 'RTReferencedStudySequence'], 8847 '0x0014': ['SQ', '1', 'RTReferencedSeriesSequence'], 8848 '0x0016': ['SQ', '1', 'ContourImageSequence'], 8849 '0x0018': ['SQ', '1', 'PredecessorStructureSetSequence'], 8850 '0x0020': ['SQ', '1', 'StructureSetROISequence'], 8851 '0x0022': ['IS', '1', 'ROINumber'], 8852 '0x0024': ['UI', '1', 'ReferencedFrameOfReferenceUID'], 8853 '0x0026': ['LO', '1', 'ROIName'], 8854 '0x0028': ['ST', '1', 'ROIDescription'], 8855 '0x002A': ['IS', '3', 'ROIDisplayColor'], 8856 '0x002C': ['DS', '1', 'ROIVolume'], 8857 '0x0030': ['SQ', '1', 'RTRelatedROISequence'], 8858 '0x0033': ['CS', '1', 'RTROIRelationship'], 8859 '0x0036': ['CS', '1', 'ROIGenerationAlgorithm'], 8860 '0x0038': ['LO', '1', 'ROIGenerationDescription'], 8861 '0x0039': ['SQ', '1', 'ROIContourSequence'], 8862 '0x0040': ['SQ', '1', 'ContourSequence'], 8863 '0x0042': ['CS', '1', 'ContourGeometricType'], 8864 '0x0044': ['DS', '1', 'ContourSlabThickness'], 8865 '0x0045': ['DS', '3', 'ContourOffsetVector'], 8866 '0x0046': ['IS', '1', 'NumberOfContourPoints'], 8867 '0x0048': ['IS', '1', 'ContourNumber'], 8868 '0x0049': ['IS', '1-n', 'AttachedContours'], 8869 '0x0050': ['DS', '3-3n', 'ContourData'], 8870 '0x0080': ['SQ', '1', 'RTROIObservationsSequence'], 8871 '0x0082': ['IS', '1', 'ObservationNumber'], 8872 '0x0084': ['IS', '1', 'ReferencedROINumber'], 8873 '0x0085': ['SH', '1', 'ROIObservationLabel'], 8874 '0x0086': ['SQ', '1', 'RTROIIdentificationCodeSequence'], 8875 '0x0088': ['ST', '1', 'ROIObservationDescription'], 8876 '0x00A0': ['SQ', '1', 'RelatedRTROIObservationsSequence'], 8877 '0x00A4': ['CS', '1', 'RTROIInterpretedType'], 8878 '0x00A6': ['PN', '1', 'ROIInterpreter'], 8879 '0x00B0': ['SQ', '1', 'ROIPhysicalPropertiesSequence'], 8880 '0x00B2': ['CS', '1', 'ROIPhysicalProperty'], 8881 '0x00B4': ['DS', '1', 'ROIPhysicalPropertyValue'], 8882 '0x00B6': ['SQ', '1', 'ROIElementalCompositionSequence'], 8883 '0x00B7': ['US', '1', 'ROIElementalCompositionAtomicNumber'], 8884 '0x00B8': ['FL', '1', 'ROIElementalCompositionAtomicMassFraction'], 8885 '0x00B9': ['SQ', '1', 'AdditionalRTROIIdentificationCodeSequence'], 8886 '0x00C0': ['SQ', '1', 'FrameOfReferenceRelationshipSequence'], 8887 '0x00C2': ['UI', '1', 'RelatedFrameOfReferenceUID'], 8888 '0x00C4': ['CS', '1', 'FrameOfReferenceTransformationType'], 8889 '0x00C6': ['DS', '16', 'FrameOfReferenceTransformationMatrix'], 8890 '0x00C8': ['LO', '1', 'FrameOfReferenceTransformationComment'] 8891 }, 8892 '0x3008': { 8893 '0x0000': ['UL', '1', 'GenericGroupLength'], 8894 '0x0010': ['SQ', '1', 'MeasuredDoseReferenceSequence'], 8895 '0x0012': ['ST', '1', 'MeasuredDoseDescription'], 8896 '0x0014': ['CS', '1', 'MeasuredDoseType'], 8897 '0x0016': ['DS', '1', 'MeasuredDoseValue'], 8898 '0x0020': ['SQ', '1', 'TreatmentSessionBeamSequence'], 8899 '0x0021': ['SQ', '1', 'TreatmentSessionIonBeamSequence'], 8900 '0x0022': ['IS', '1', 'CurrentFractionNumber'], 8901 '0x0024': ['DA', '1', 'TreatmentControlPointDate'], 8902 '0x0025': ['TM', '1', 'TreatmentControlPointTime'], 8903 '0x002A': ['CS', '1', 'TreatmentTerminationStatus'], 8904 '0x002B': ['SH', '1', 'TreatmentTerminationCode'], 8905 '0x002C': ['CS', '1', 'TreatmentVerificationStatus'], 8906 '0x0030': ['SQ', '1', 'ReferencedTreatmentRecordSequence'], 8907 '0x0032': ['DS', '1', 'SpecifiedPrimaryMeterset'], 8908 '0x0033': ['DS', '1', 'SpecifiedSecondaryMeterset'], 8909 '0x0036': ['DS', '1', 'DeliveredPrimaryMeterset'], 8910 '0x0037': ['DS', '1', 'DeliveredSecondaryMeterset'], 8911 '0x003A': ['DS', '1', 'SpecifiedTreatmentTime'], 8912 '0x003B': ['DS', '1', 'DeliveredTreatmentTime'], 8913 '0x0040': ['SQ', '1', 'ControlPointDeliverySequence'], 8914 '0x0041': ['SQ', '1', 'IonControlPointDeliverySequence'], 8915 '0x0042': ['DS', '1', 'SpecifiedMeterset'], 8916 '0x0044': ['DS', '1', 'DeliveredMeterset'], 8917 '0x0045': ['FL', '1', 'MetersetRateSet'], 8918 '0x0046': ['FL', '1', 'MetersetRateDelivered'], 8919 '0x0047': ['FL', '1-n', 'ScanSpotMetersetsDelivered'], 8920 '0x0048': ['DS', '1', 'DoseRateDelivered'], 8921 '0x0050': ['SQ', '1', 'TreatmentSummaryCalculatedDoseReferenceSequence'], 8922 '0x0052': ['DS', '1', 'CumulativeDoseToDoseReference'], 8923 '0x0054': ['DA', '1', 'FirstTreatmentDate'], 8924 '0x0056': ['DA', '1', 'MostRecentTreatmentDate'], 8925 '0x005A': ['IS', '1', 'NumberOfFractionsDelivered'], 8926 '0x0060': ['SQ', '1', 'OverrideSequence'], 8927 '0x0061': ['AT', '1', 'ParameterSequencePointer'], 8928 '0x0062': ['AT', '1', 'OverrideParameterPointer'], 8929 '0x0063': ['IS', '1', 'ParameterItemIndex'], 8930 '0x0064': ['IS', '1', 'MeasuredDoseReferenceNumber'], 8931 '0x0065': ['AT', '1', 'ParameterPointer'], 8932 '0x0066': ['ST', '1', 'OverrideReason'], 8933 '0x0068': ['SQ', '1', 'CorrectedParameterSequence'], 8934 '0x006A': ['FL', '1', 'CorrectionValue'], 8935 '0x0070': ['SQ', '1', 'CalculatedDoseReferenceSequence'], 8936 '0x0072': ['IS', '1', 'CalculatedDoseReferenceNumber'], 8937 '0x0074': ['ST', '1', 'CalculatedDoseReferenceDescription'], 8938 '0x0076': ['DS', '1', 'CalculatedDoseReferenceDoseValue'], 8939 '0x0078': ['DS', '1', 'StartMeterset'], 8940 '0x007A': ['DS', '1', 'EndMeterset'], 8941 '0x0080': ['SQ', '1', 'ReferencedMeasuredDoseReferenceSequence'], 8942 '0x0082': ['IS', '1', 'ReferencedMeasuredDoseReferenceNumber'], 8943 '0x0090': ['SQ', '1', 'ReferencedCalculatedDoseReferenceSequence'], 8944 '0x0092': ['IS', '1', 'ReferencedCalculatedDoseReferenceNumber'], 8945 '0x00A0': ['SQ', '1', 'BeamLimitingDeviceLeafPairsSequence'], 8946 '0x00B0': ['SQ', '1', 'RecordedWedgeSequence'], 8947 '0x00C0': ['SQ', '1', 'RecordedCompensatorSequence'], 8948 '0x00D0': ['SQ', '1', 'RecordedBlockSequence'], 8949 '0x00E0': ['SQ', '1', 'TreatmentSummaryMeasuredDoseReferenceSequence'], 8950 '0x00F0': ['SQ', '1', 'RecordedSnoutSequence'], 8951 '0x00F2': ['SQ', '1', 'RecordedRangeShifterSequence'], 8952 '0x00F4': ['SQ', '1', 'RecordedLateralSpreadingDeviceSequence'], 8953 '0x00F6': ['SQ', '1', 'RecordedRangeModulatorSequence'], 8954 '0x0100': ['SQ', '1', 'RecordedSourceSequence'], 8955 '0x0105': ['LO', '1', 'SourceSerialNumber'], 8956 '0x0110': ['SQ', '1', 'TreatmentSessionApplicationSetupSequence'], 8957 '0x0116': ['CS', '1', 'ApplicationSetupCheck'], 8958 '0x0120': ['SQ', '1', 'RecordedBrachyAccessoryDeviceSequence'], 8959 '0x0122': ['IS', '1', 'ReferencedBrachyAccessoryDeviceNumber'],
8960 '0x0130': ['SQ', '1', 'RecordedChannelSequence'], 8961 '0x0132': ['DS', '1', 'SpecifiedChannelTotalTime'], 8962 '0x0134': ['DS', '1', 'DeliveredChannelTotalTime'], 8963 '0x0136': ['IS', '1', 'SpecifiedNumberOfPulses'], 8964 '0x0138': ['IS', '1', 'DeliveredNumberOfPulses'], 8965 '0x013A': ['DS', '1', 'SpecifiedPulseRepetitionInterval'], 8966 '0x013C': ['DS', '1', 'DeliveredPulseRepetitionInterval'], 8967 '0x0140': ['SQ', '1', 'RecordedSourceApplicatorSequence'], 8968 '0x0142': ['IS', '1', 'ReferencedSourceApplicatorNumber'], 8969 '0x0150': ['SQ', '1', 'RecordedChannelShieldSequence'], 8970 '0x0152': ['IS', '1', 'ReferencedChannelShieldNumber'], 8971 '0x0160': ['SQ', '1', 'BrachyControlPointDeliveredSequence'], 8972 '0x0162': ['DA', '1', 'SafePositionExitDate'], 8973 '0x0164': ['TM', '1', 'SafePositionExitTime'], 8974 '0x0166': ['DA', '1', 'SafePositionReturnDate'], 8975 '0x0168': ['TM', '1', 'SafePositionReturnTime'], 8976 '0x0171': ['SQ', '1', 'PulseSpecificBrachyControlPointDeliveredSequence'], 8977 '0x0172': ['US', '1', 'PulseNumber'], 8978 '0x0173': ['SQ', '1', 'BrachyPulseControlPointDeliveredSequence'], 8979 '0x0200': ['CS', '1', 'CurrentTreatmentStatus'], 8980 '0x0202': ['ST', '1', 'TreatmentStatusComment'], 8981 '0x0220': ['SQ', '1', 'FractionGroupSummarySequence'], 8982 '0x0223': ['IS', '1', 'ReferencedFractionNumber'], 8983 '0x0224': ['CS', '1', 'FractionGroupType'], 8984 '0x0230': ['CS', '1', 'BeamStopperPosition'], 8985 '0x0240': ['SQ', '1', 'FractionStatusSummarySequence'], 8986 '0x0250': ['DA', '1', 'TreatmentDate'], 8987 '0x0251': ['TM', '1', 'TreatmentTime'] 8988 }, 8989 '0x300A': { 8990 '0x0000': ['UL', '1', 'GenericGroupLength'], 8991 '0x0002': ['SH', '1', 'RTPlanLabel'], 8992 '0x0003': ['LO', '1', 'RTPlanName'], 8993 '0x0004': ['ST', '1', 'RTPlanDescription'], 8994 '0x0006': ['DA', '1', 'RTPlanDate'], 8995 '0x0007': ['TM', '1', 'RTPlanTime'], 8996 '0x0009': ['LO', '1-n', 'TreatmentProtocols'], 8997 '0x000A': ['CS', '1', 'PlanIntent'], 8998 '0x000B': ['LO', '1-n', 'TreatmentSites'], 8999 '0x000C': ['CS', '1', 'RTPlanGeometry'], 9000 '0x000E': ['ST', '1', 'PrescriptionDescription'], 9001 '0x0010': ['SQ', '1', 'DoseReferenceSequence'], 9002 '0x0012': ['IS', '1', 'DoseReferenceNumber'], 9003 '0x0013': ['UI', '1', 'DoseReferenceUID'], 9004 '0x0014': ['CS', '1', 'DoseReferenceStructureType'], 9005 '0x0015': ['CS', '1', 'NominalBeamEnergyUnit'], 9006 '0x0016': ['LO', '1', 'DoseReferenceDescription'], 9007 '0x0018': ['DS', '3', 'DoseReferencePointCoordinates'], 9008 '0x001A': ['DS', '1', 'NominalPriorDose'], 9009 '0x0020': ['CS', '1', 'DoseReferenceType'], 9010 '0x0021': ['DS', '1', 'ConstraintWeight'], 9011 '0x0022': ['DS', '1', 'DeliveryWarningDose'], 9012 '0x0023': ['DS', '1', 'DeliveryMaximumDose'], 9013 '0x0025': ['DS', '1', 'TargetMinimumDose'], 9014 '0x0026': ['DS', '1', 'TargetPrescriptionDose'], 9015 '0x0027': ['DS', '1', 'TargetMaximumDose'], 9016 '0x0028': ['DS', '1', 'TargetUnderdoseVolumeFraction'], 9017 '0x002A': ['DS', '1', 'OrganAtRiskFullVolumeDose'], 9018 '0x002B': ['DS', '1', 'OrganAtRiskLimitDose'], 9019 '0x002C': ['DS', '1', 'OrganAtRiskMaximumDose'], 9020 '0x002D': ['DS', '1', 'OrganAtRiskOverdoseVolumeFraction'], 9021 '0x0040': ['SQ', '1', 'ToleranceTableSequence'], 9022 '0x0042': ['IS', '1', 'ToleranceTableNumber'], 9023 '0x0043': ['SH', '1', 'ToleranceTableLabel'], 9024 '0x0044': ['DS', '1', 'GantryAngleTolerance'], 9025 '0x0046': ['DS', '1', 'BeamLimitingDeviceAngleTolerance'], 9026 '0x0048': ['SQ', '1', 'BeamLimitingDeviceToleranceSequence'], 9027 '0x004A': ['DS', '1', 'BeamLimitingDevicePositionTolerance'], 9028 '0x004B': ['FL', '1', 'SnoutPositionTolerance'], 9029 '0x004C': ['DS', '1', 'PatientSupportAngleTolerance'], 9030 '0x004E': ['DS', '1', 'TableTopEccentricAngleTolerance'], 9031 '0x004F': ['FL', '1', 'TableTopPitchAngleTolerance'], 9032 '0x0050': ['FL', '1', 'TableTopRollAngleTolerance'], 9033 '0x0051': ['DS', '1', 'TableTopVerticalPositionTolerance'], 9034 '0x0052': ['DS', '1', 'TableTopLongitudinalPositionTolerance'], 9035 '0x0053': ['DS', '1', 'TableTopLateralPositionTolerance'], 9036 '0x0055': ['CS', '1', 'RTPlanRelationship'], 9037 '0x0070': ['SQ', '1', 'FractionGroupSequence'], 9038 '0x0071': ['IS', '1', 'FractionGroupNumber'], 9039 '0x0072': ['LO', '1', 'FractionGroupDescription'], 9040 '0x0078': ['IS', '1', 'NumberOfFractionsPlanned'], 9041 '0x0079': ['IS', '1', 'NumberOfFractionPatternDigitsPerDay'], 9042 '0x007A': ['IS', '1', 'RepeatFractionCycleLength'], 9043 '0x007B': ['LT', '1', 'FractionPattern'], 9044 '0x0080': ['IS', '1', 'NumberOfBeams'], 9045 '0x0082': ['DS', '3', 'BeamDoseSpecificationPoint'], 9046 '0x0084': ['DS', '1', 'BeamDose'], 9047 '0x0086': ['DS', '1', 'BeamMeterset'], 9048 '0x0088': ['FL', '1', 'BeamDosePointDepth'], 9049 '0x0089': ['FL', '1', 'BeamDosePointEquivalentDepth'], 9050 '0x008A': ['FL', '1', 'BeamDosePointSSD'], 9051 '0x008B': ['CS', '1', 'BeamDoseMeaning'], 9052 '0x008C': ['SQ', '1', 'BeamDoseVerificationControlPointSequence'], 9053 '0x008D': ['FL', '1', 'AverageBeamDosePointDepth'], 9054 '0x008E': ['FL', '1', 'AverageBeamDosePointEquivalentDepth'], 9055 '0x008F': ['FL', '1', 'AverageBeamDosePointSSD'], 9056 '0x00A0': ['IS', '1', 'NumberOfBrachyApplicationSetups'], 9057 '0x00A2': ['DS', '3', 'BrachyApplicationSetupDoseSpecificationPoint'], 9058 '0x00A4': ['DS', '1', 'BrachyApplicationSetupDose'], 9059 '0x00B0': ['SQ', '1', 'BeamSequence'], 9060 '0x00B2': ['SH', '1', 'TreatmentMachineName'], 9061 '0x00B3': ['CS', '1', 'PrimaryDosimeterUnit'], 9062 '0x00B4': ['DS', '1', 'SourceAxisDistance'], 9063 '0x00B6': ['SQ', '1', 'BeamLimitingDeviceSequence'], 9064 '0x00B8': ['CS', '1', 'RTBeamLimitingDeviceType'], 9065 '0x00BA': ['DS', '1', 'SourceToBeamLimitingDeviceDistance'], 9066 '0x00BB': ['FL', '1', 'IsocenterToBeamLimitingDeviceDistance'], 9067 '0x00BC': ['IS', '1', 'NumberOfLeafJawPairs'], 9068 '0x00BE': ['DS', '3-n', 'LeafPositionBoundaries'], 9069 '0x00C0': ['IS', '1', 'BeamNumber'], 9070 '0x00C2': ['LO', '1', 'BeamName'], 9071 '0x00C3': ['ST', '1', 'BeamDescription'], 9072 '0x00C4': ['CS', '1', 'BeamType'], 9073 '0x00C5': ['FD', '1', 'BeamDeliveryDurationLimit'], 9074 '0x00C6': ['CS', '1', 'RadiationType'], 9075 '0x00C7': ['CS', '1', 'HighDoseTechniqueType'], 9076 '0x00C8': ['IS', '1', 'ReferenceImageNumber'], 9077 '0x00CA': ['SQ', '1', 'PlannedVerificationImageSequence'], 9078 '0x00CC': ['LO', '1-n', 'ImagingDeviceSpecificAcquisitionParameters'], 9079 '0x00CE': ['CS', '1', 'TreatmentDeliveryType'], 9080 '0x00D0': ['IS', '1', 'NumberOfWedges'], 9081 '0x00D1': ['SQ', '1', 'WedgeSequence'], 9082 '0x00D2': ['IS', '1', 'WedgeNumber'], 9083 '0x00D3': ['CS', '1', 'WedgeType'], 9084 '0x00D4': ['SH', '1', 'WedgeID'], 9085 '0x00D5': ['IS', '1', 'WedgeAngle'], 9086 '0x00D6': ['DS', '1', 'WedgeFactor'], 9087 '0x00D7': ['FL', '1', 'TotalWedgeTrayWaterEquivalentThickness'], 9088 '0x00D8': ['DS', '1', 'WedgeOrientation'], 9089 '0x00D9': ['FL', '1', 'IsocenterToWedgeTrayDistance'], 9090 '0x00DA': ['DS', '1', 'SourceToWedgeTrayDistance'], 9091 '0x00DB': ['FL', '1', 'WedgeThinEdgePosition'], 9092 '0x00DC': ['SH', '1', 'BolusID'], 9093 '0x00DD': ['ST', '1', 'BolusDescription'], 9094 '0x00DE': ['DS', '1', 'EffectiveWedgeAngle'], 9095 '0x00E0': ['IS', '1', 'NumberOfCompensators'], 9096 '0x00E1': ['SH', '1', 'MaterialID'], 9097 '0x00E2': ['DS', '1', 'TotalCompensatorTrayFactor'], 9098 '0x00E3': ['SQ', '1', 'CompensatorSequence'], 9099 '0x00E4': ['IS', '1', 'CompensatorNumber'], 9100 '0x00E5': ['SH', '1', 'CompensatorID'], 9101 '0x00E6': ['DS', '1', 'SourceToCompensatorTrayDistance'], 9102 '0x00E7': ['IS', '1', 'CompensatorRows'], 9103 '0x00E8': ['IS', '1', 'CompensatorColumns'], 9104 '0x00E9': ['DS', '2', 'CompensatorPixelSpacing'], 9105 '0x00EA': ['DS', '2', 'CompensatorPosition'], 9106 '0x00EB': ['DS', '1-n', 'CompensatorTransmissionData'], 9107 '0x00EC': ['DS', '1-n', 'CompensatorThicknessData'], 9108 '0x00ED': ['IS', '1', 'NumberOfBoli'], 9109 '0x00EE': ['CS', '1', 'CompensatorType'], 9110 '0x00EF': ['SH', '1', 'CompensatorTrayID'], 9111 '0x00F0': ['IS', '1', 'NumberOfBlocks'],
9112 '0x00F2': ['DS', '1', 'TotalBlockTrayFactor'], 9113 '0x00F3': ['FL', '1', 'TotalBlockTrayWaterEquivalentThickness'], 9114 '0x00F4': ['SQ', '1', 'BlockSequence'], 9115 '0x00F5': ['SH', '1', 'BlockTrayID'], 9116 '0x00F6': ['DS', '1', 'SourceToBlockTrayDistance'], 9117 '0x00F7': ['FL', '1', 'IsocenterToBlockTrayDistance'], 9118 '0x00F8': ['CS', '1', 'BlockType'], 9119 '0x00F9': ['LO', '1', 'AccessoryCode'], 9120 '0x00FA': ['CS', '1', 'BlockDivergence'], 9121 '0x00FB': ['CS', '1', 'BlockMountingPosition'], 9122 '0x00FC': ['IS', '1', 'BlockNumber'], 9123 '0x00FE': ['LO', '1', 'BlockName'], 9124 '0x0100': ['DS', '1', 'BlockThickness'], 9125 '0x0102': ['DS', '1', 'BlockTransmission'], 9126 '0x0104': ['IS', '1', 'BlockNumberOfPoints'], 9127 '0x0106': ['DS', '2-2n', 'BlockData'], 9128 '0x0107': ['SQ', '1', 'ApplicatorSequence'], 9129 '0x0108': ['SH', '1', 'ApplicatorID'], 9130 '0x0109': ['CS', '1', 'ApplicatorType'], 9131 '0x010A': ['LO', '1', 'ApplicatorDescription'], 9132 '0x010C': ['DS', '1', 'CumulativeDoseReferenceCoefficient'], 9133 '0x010E': ['DS', '1', 'FinalCumulativeMetersetWeight'], 9134 '0x0110': ['IS', '1', 'NumberOfControlPoints'], 9135 '0x0111': ['SQ', '1', 'ControlPointSequence'], 9136 '0x0112': ['IS', '1', 'ControlPointIndex'], 9137 '0x0114': ['DS', '1', 'NominalBeamEnergy'], 9138 '0x0115': ['DS', '1', 'DoseRateSet'], 9139 '0x0116': ['SQ', '1', 'WedgePositionSequence'], 9140 '0x0118': ['CS', '1', 'WedgePosition'], 9141 '0x011A': ['SQ', '1', 'BeamLimitingDevicePositionSequence'], 9142 '0x011C': ['DS', '2-2n', 'LeafJawPositions'], 9143 '0x011E': ['DS', '1', 'GantryAngle'], 9144 '0x011F': ['CS', '1', 'GantryRotationDirection'], 9145 '0x0120': ['DS', '1', 'BeamLimitingDeviceAngle'], 9146 '0x0121': ['CS', '1', 'BeamLimitingDeviceRotationDirection'], 9147 '0x0122': ['DS', '1', 'PatientSupportAngle'], 9148 '0x0123': ['CS', '1', 'PatientSupportRotationDirection'], 9149 '0x0124': ['DS', '1', 'TableTopEccentricAxisDistance'], 9150 '0x0125': ['DS', '1', 'TableTopEccentricAngle'], 9151 '0x0126': ['CS', '1', 'TableTopEccentricRotationDirection'], 9152 '0x0128': ['DS', '1', 'TableTopVerticalPosition'], 9153 '0x0129': ['DS', '1', 'TableTopLongitudinalPosition'], 9154 '0x012A': ['DS', '1', 'TableTopLateralPosition'], 9155 '0x012C': ['DS', '3', 'IsocenterPosition'], 9156 '0x012E': ['DS', '3', 'SurfaceEntryPoint'], 9157 '0x0130': ['DS', '1', 'SourceToSurfaceDistance'], 9158 '0x0131': ['FL', '1', 'AverageBeamDosePointSourceToExternalContourSurfaceDistance'], 9159 '0x0132': ['FL', '1', 'SourceToExternalContourDistance'], 9160 '0x0133': ['FL', '3', 'ExternalContourEntryPoint'], 9161 '0x0134': ['DS', '1', 'CumulativeMetersetWeight'], 9162 '0x0140': ['FL', '1', 'TableTopPitchAngle'], 9163 '0x0142': ['CS', '1', 'TableTopPitchRotationDirection'], 9164 '0x0144': ['FL', '1', 'TableTopRollAngle'], 9165 '0x0146': ['CS', '1', 'TableTopRollRotationDirection'], 9166 '0x0148': ['FL', '1', 'HeadFixationAngle'], 9167 '0x014A': ['FL', '1', 'GantryPitchAngle'], 9168 '0x014C': ['CS', '1', 'GantryPitchRotationDirection'], 9169 '0x014E': ['FL', '1', 'GantryPitchAngleTolerance'], 9170 '0x0180': ['SQ', '1', 'PatientSetupSequence'], 9171 '0x0182': ['IS', '1', 'PatientSetupNumber'], 9172 '0x0183': ['LO', '1', 'PatientSetupLabel'], 9173 '0x0184': ['LO', '1', 'PatientAdditionalPosition'], 9174 '0x0190': ['SQ', '1', 'FixationDeviceSequence'], 9175 '0x0192': ['CS', '1', 'FixationDeviceType'], 9176 '0x0194': ['SH', '1', 'FixationDeviceLabel'], 9177 '0x0196': ['ST', '1', 'FixationDeviceDescription'], 9178 '0x0198': ['SH', '1', 'FixationDevicePosition'], 9179 '0x0199': ['FL', '1', 'FixationDevicePitchAngle'], 9180 '0x019A': ['FL', '1', 'FixationDeviceRollAngle'], 9181 '0x01A0': ['SQ', '1', 'ShieldingDeviceSequence'], 9182 '0x01A2': ['CS', '1', 'ShieldingDeviceType'], 9183 '0x01A4': ['SH', '1', 'ShieldingDeviceLabel'], 9184 '0x01A6': ['ST', '1', 'ShieldingDeviceDescription'], 9185 '0x01A8': ['SH', '1', 'ShieldingDevicePosition'], 9186 '0x01B0': ['CS', '1', 'SetupTechnique'], 9187 '0x01B2': ['ST', '1', 'SetupTechniqueDescription'], 9188 '0x01B4': ['SQ', '1', 'SetupDeviceSequence'], 9189 '0x01B6': ['CS', '1', 'SetupDeviceType'],
9190 '0x01B8': ['SH', '1', 'SetupDeviceLabel'], 9191 '0x01BA': ['ST', '1', 'SetupDeviceDescription'], 9192 '0x01BC': ['DS', '1', 'SetupDeviceParameter'], 9193 '0x01D0': ['ST', '1', 'SetupReferenceDescription'], 9194 '0x01D2': ['DS', '1', 'TableTopVerticalSetupDisplacement'], 9195 '0x01D4': ['DS', '1', 'TableTopLongitudinalSetupDisplacement'], 9196 '0x01D6': ['DS', '1', 'TableTopLateralSetupDisplacement'], 9197 '0x0200': ['CS', '1', 'BrachyTreatmentTechnique'], 9198 '0x0202': ['CS', '1', 'BrachyTreatmentType'], 9199 '0x0206': ['SQ', '1', 'TreatmentMachineSequence'], 9200 '0x0210': ['SQ', '1', 'SourceSequence'], 9201 '0x0212': ['IS', '1', 'SourceNumber'], 9202 '0x0214': ['CS', '1', 'SourceType'], 9203 '0x0216': ['LO', '1', 'SourceManufacturer'], 9204 '0x0218': ['DS', '1', 'ActiveSourceDiameter'], 9205 '0x021A': ['DS', '1', 'ActiveSourceLength'], 9206 '0x021B': ['SH', '1', 'SourceModelID'], 9207 '0x021C': ['LO', '1', 'SourceDescription'], 9208 '0x0222': ['DS', '1', 'SourceEncapsulationNominalThickness'], 9209 '0x0224': ['DS', '1', 'SourceEncapsulationNominalTransmission'], 9210 '0x0226': ['LO', '1', 'SourceIsotopeName'], 9211 '0x0228': ['DS', '1', 'SourceIsotopeHalfLife'], 9212 '0x0229': ['CS', '1', 'SourceStrengthUnits'], 9213 '0x022A': ['DS', '1', 'ReferenceAirKermaRate'], 9214 '0x022B': ['DS', '1', 'SourceStrength'], 9215 '0x022C': ['DA', '1', 'SourceStrengthReferenceDate'], 9216 '0x022E': ['TM', '1', 'SourceStrengthReferenceTime'], 9217 '0x0230': ['SQ', '1', 'ApplicationSetupSequence'], 9218 '0x0232': ['CS', '1', 'ApplicationSetupType'], 9219 '0x0234': ['IS', '1', 'ApplicationSetupNumber'], 9220 '0x0236': ['LO', '1', 'ApplicationSetupName'], 9221 '0x0238': ['LO', '1', 'ApplicationSetupManufacturer'], 9222 '0x0240': ['IS', '1', 'TemplateNumber'], 9223 '0x0242': ['SH', '1', 'TemplateType'], 9224 '0x0244': ['LO', '1', 'TemplateName'], 9225 '0x0250': ['DS', '1', 'TotalReferenceAirKerma'], 9226 '0x0260': ['SQ', '1', 'BrachyAccessoryDeviceSequence'], 9227 '0x0262': ['IS', '1', 'BrachyAccessoryDeviceNumber'], 9228 '0x0263': ['SH', '1', 'BrachyAccessoryDeviceID'], 9229 '0x0264': ['CS', '1', 'BrachyAccessoryDeviceType'], 9230 '0x0266': ['LO', '1', 'BrachyAccessoryDeviceName'], 9231 '0x026A': ['DS', '1', 'BrachyAccessoryDeviceNominalThickness'], 9232 '0x026C': ['DS', '1', 'BrachyAccessoryDeviceNominalTransmission'], 9233 '0x0280': ['SQ', '1', 'ChannelSequence'], 9234 '0x0282': ['IS', '1', 'ChannelNumber'], 9235 '0x0284': ['DS', '1', 'ChannelLength'], 9236 '0x0286': ['DS', '1', 'ChannelTotalTime'], 9237 '0x0288': ['CS', '1', 'SourceMovementType'], 9238 '0x028A': ['IS', '1', 'NumberOfPulses'], 9239 '0x028C': ['DS', '1', 'PulseRepetitionInterval'], 9240 '0x0290': ['IS', '1', 'SourceApplicatorNumber'], 9241 '0x0291': ['SH', '1', 'SourceApplicatorID'], 9242 '0x0292': ['CS', '1', 'SourceApplicatorType'], 9243 '0x0294': ['LO', '1', 'SourceApplicatorName'], 9244 '0x0296': ['DS', '1', 'SourceApplicatorLength'], 9245 '0x0298': ['LO', '1', 'SourceApplicatorManufacturer'], 9246 '0x029C': ['DS', '1', 'SourceApplicatorWallNominalThickness'], 9247 '0x029E': ['DS', '1', 'SourceApplicatorWallNominalTransmission'], 9248 '0x02A0': ['DS', '1', 'SourceApplicatorStepSize'], 9249 '0x02A2': ['IS', '1', 'TransferTubeNumber'], 9250 '0x02A4': ['DS', '1', 'TransferTubeLength'], 9251 '0x02B0': ['SQ', '1', 'ChannelShieldSequence'], 9252 '0x02B2': ['IS', '1', 'ChannelShieldNumber'], 9253 '0x02B3': ['SH', '1', 'ChannelShieldID'], 9254 '0x02B4': ['LO', '1', 'ChannelShieldName'], 9255 '0x02B8': ['DS', '1', 'ChannelShieldNominalThickness'], 9256 '0x02BA': ['DS', '1', 'ChannelShieldNominalTransmission'], 9257 '0x02C8': ['DS', '1', 'FinalCumulativeTimeWeight'], 9258 '0x02D0': ['SQ', '1', 'BrachyControlPointSequence'], 9259 '0x02D2': ['DS', '1', 'ControlPointRelativePosition'], 9260 '0x02D4': ['DS', '3', 'ControlPoint3DPosition'], 9261 '0x02D6': ['DS', '1', 'CumulativeTimeWeight'], 9262 '0x02E0': ['CS', '1', 'CompensatorDivergence'], 9263 '0x02E1': ['CS', '1', 'CompensatorMountingPosition'], 9264 '0x02E2': ['DS', '1-n', 'SourceToCompensatorDistance'], 9265 '0x02E3': ['FL', '1', 'TotalCompensatorTrayWaterEquivalentThickness'], 9266 '0x02E4': ['FL', '1', 'IsocenterToCompensatorTrayDistance'], 9267 '0x02E5': ['FL', '1', 'CompensatorColumnOffset'], 9268 '0x02E6': ['FL', '1-n', 'IsocenterToCompensatorDistances'], 9269 '0x02E7': ['FL', '1', 'CompensatorRelativeStoppingPowerRatio'], 9270 '0x02E8': ['FL', '1', 'CompensatorMillingToolDiameter'], 9271 '0x02EA': ['SQ', '1', 'IonRangeCompensatorSequence'], 9272 '0x02EB': ['LT', '1', 'CompensatorDescription'], 9273 '0x0302': ['IS', '1', 'RadiationMassNumber'], 9274 '0x0304': ['IS', '1', 'RadiationAtomicNumber'], 9275 '0x0306': ['SS', '1', 'RadiationChargeState'], 9276 '0x0308': ['CS', '1', 'ScanMode'], 9277 '0x030A': ['FL', '2', 'VirtualSourceAxisDistances'], 9278 '0x030C': ['SQ', '1', 'SnoutSequence'], 9279 '0x030D': ['FL', '1', 'SnoutPosition'], 9280 '0x030F': ['SH', '1', 'SnoutID'], 9281 '0x0312': ['IS', '1', 'NumberOfRangeShifters'], 9282 '0x0314': ['SQ', '1', 'RangeShifterSequence'], 9283 '0x0316': ['IS', '1', 'RangeShifterNumber'], 9284 '0x0318': ['SH', '1', 'RangeShifterID'], 9285 '0x0320': ['CS', '1', 'RangeShifterType'], 9286 '0x0322': ['LO', '1', 'RangeShifterDescription'], 9287 '0x0330': ['IS', '1', 'NumberOfLateralSpreadingDevices'], 9288 '0x0332': ['SQ', '1', 'LateralSpreadingDeviceSequence'], 9289 '0x0334': ['IS', '1', 'LateralSpreadingDeviceNumber'], 9290 '0x0336': ['SH', '1', 'LateralSpreadingDeviceID'], 9291 '0x0338': ['CS', '1', 'LateralSpreadingDeviceType'], 9292 '0x033A': ['LO', '1', 'LateralSpreadingDeviceDescription'], 9293 '0x033C': ['FL', '1', 'LateralSpreadingDeviceWaterEquivalentThickness'], 9294 '0x0340': ['IS', '1', 'NumberOfRangeModulators'], 9295 '0x0342': ['SQ', '1', 'RangeModulatorSequence'], 9296 '0x0344': ['IS', '1', 'RangeModulatorNumber'], 9297 '0x0346': ['SH', '1', 'RangeModulatorID'], 9298 '0x0348': ['CS', '1', 'RangeModulatorType'], 9299 '0x034A': ['LO', '1', 'RangeModulatorDescription'], 9300 '0x034C': ['SH', '1', 'BeamCurrentModulationID'], 9301 '0x0350': ['CS', '1', 'PatientSupportType'], 9302 '0x0352': ['SH', '1', 'PatientSupportID'], 9303 '0x0354': ['LO', '1', 'PatientSupportAccessoryCode'], 9304 '0x0356': ['FL', '1', 'FixationLightAzimuthalAngle'], 9305 '0x0358': ['FL', '1', 'FixationLightPolarAngle'], 9306 '0x035A': ['FL', '1', 'MetersetRate'], 9307 '0x0360': ['SQ', '1', 'RangeShifterSettingsSequence'], 9308 '0x0362': ['LO', '1', 'RangeShifterSetting'], 9309 '0x0364': ['FL', '1', 'IsocenterToRangeShifterDistance'], 9310 '0x0366': ['FL', '1', 'RangeShifterWaterEquivalentThickness'], 9311 '0x0370': ['SQ', '1', 'LateralSpreadingDeviceSettingsSequence'], 9312 '0x0372': ['LO', '1', 'LateralSpreadingDeviceSetting'], 9313 '0x0374': ['FL', '1', 'IsocenterToLateralSpreadingDeviceDistance'], 9314 '0x0380': ['SQ', '1', 'RangeModulatorSettingsSequence'], 9315 '0x0382': ['FL', '1', 'RangeModulatorGatingStartValue'], 9316 '0x0384': ['FL', '1', 'RangeModulatorGatingStopValue'], 9317 '0x0386': ['FL', '1', 'RangeModulatorGatingStartWaterEquivalentThickness'], 9318 '0x0388': ['FL', '1', 'RangeModulatorGatingStopWaterEquivalentThickness'], 9319 '0x038A': ['FL', '1', 'IsocenterToRangeModulatorDistance'], 9320 '0x0390': ['SH', '1', 'ScanSpotTuneID'], 9321 '0x0392': ['IS', '1', 'NumberOfScanSpotPositions'], 9322 '0x0394': ['FL', '1-n', 'ScanSpotPositionMap'], 9323 '0x0396': ['FL', '1-n', 'ScanSpotMetersetWeights'], 9324 '0x0398': ['FL', '2', 'ScanningSpotSize'], 9325 '0x039A': ['IS', '1', 'NumberOfPaintings'], 9326 '0x03A0': ['SQ', '1', 'IonToleranceTableSequence'], 9327 '0x03A2': ['SQ', '1', 'IonBeamSequence'], 9328 '0x03A4': ['SQ', '1', 'IonBeamLimitingDeviceSequence'], 9329 '0x03A6': ['SQ', '1', 'IonBlockSequence'], 9330 '0x03A8': ['SQ', '1', 'IonControlPointSequence'], 9331 '0x03AA': ['SQ', '1', 'IonWedgeSequence'], 9332 '0x03AC': ['SQ', '1', 'IonWedgePositionSequence'], 9333 '0x0401': ['SQ', '1', 'ReferencedSetupImageSequence'], 9334 '0x0402': ['ST', '1', 'SetupImageComment'], 9335 '0x0410': ['SQ', '1', 'MotionSynchronizationSequence'], 9336 '0x0412': ['FL', '3', 'ControlPointOrientation'], 9337 '0x0420': ['SQ', '1', 'GeneralAccessorySequence'], 9338 '0x0421': ['SH', '1', 'GeneralAccessoryID'], 9339 '0x0422': ['ST', '1', 'GeneralAccessoryDescription'], 9340 '0x0423': ['CS', '1', 'GeneralAccessoryType'], 9341 '0x0424': ['IS', '1', 'GeneralAccessoryNumber'], 9342 '0x0425': ['FL', '1', 'SourceToGeneralAccessoryDistance'], 9343 '0x0431': ['SQ', '1', 'ApplicatorGeometrySequence'], 9344 '0x0432': ['CS', '1', 'ApplicatorApertureShape'], 9345 '0x0433': ['FL', '1', 'ApplicatorOpening'], 9346 '0x0434': ['FL', '1', 'ApplicatorOpeningX'], 9347 '0x0435': ['FL', '1', 'ApplicatorOpeningY'], 9348 '0x0436': ['FL', '1', 'SourceToApplicatorMountingPositionDistance'], 9349 '0x0440': ['IS', '1', 'NumberOfBlockSlabItems'], 9350 '0x0441': ['SQ', '1', 'BlockSlabSequence'], 9351 '0x0442': ['DS', '1', 'BlockSlabThickness'], 9352 '0x0443': ['US', '1', 'BlockSlabNumber'], 9353 '0x0450': ['SQ', '1', 'DeviceMotionControlSequence'], 9354 '0x0451': ['CS', '1', 'DeviceMotionExecutionMode'], 9355 '0x0452': ['CS', '1', 'DeviceMotionObservationMode'], 9356 '0x0453': ['SQ', '1', 'DeviceMotionParameterCodeSequence'] 9357 }, 9358 '0x300C': { 9359 '0x0000': ['UL', '1', 'GenericGroupLength'], 9360 '0x0002': ['SQ', '1', 'ReferencedRTPlanSequence'], 9361 '0x0004': ['SQ', '1', 'ReferencedBeamSequence'], 9362 '0x0006': ['IS', '1', 'ReferencedBeamNumber'], 9363 '0x0007': ['IS', '1', 'ReferencedReferenceImageNumber'], 9364 '0x0008': ['DS', '1', 'StartCumulativeMetersetWeight'], 9365 '0x0009': ['DS', '1', 'EndCumulativeMetersetWeight'], 9366 '0x000A': ['SQ', '1', 'ReferencedBrachyApplicationSetupSequence'], 9367 '0x000C': ['IS', '1', 'ReferencedBrachyApplicationSetupNumber'], 9368 '0x000E': ['IS', '1', 'ReferencedSourceNumber'], 9369 '0x0020': ['SQ', '1', 'ReferencedFractionGroupSequence'], 9370 '0x0022': ['IS', '1', 'ReferencedFractionGroupNumber'], 9371 '0x0040': ['SQ', '1', 'ReferencedVerificationImageSequence'], 9372 '0x0042': ['SQ', '1', 'ReferencedReferenceImageSequence'], 9373 '0x0050': ['SQ', '1', 'ReferencedDoseReferenceSequence'], 9374 '0x0051': ['IS', '1', 'ReferencedDoseReferenceNumber'], 9375 '0x0055': ['SQ', '1', 'BrachyReferencedDoseReferenceSequence'], 9376 '0x0060': ['SQ', '1', 'ReferencedStructureSetSequence'], 9377 '0x006A': ['IS', '1', 'ReferencedPatientSetupNumber'], 9378 '0x0080': ['SQ', '1', 'ReferencedDoseSequence'], 9379 '0x00A0': ['IS', '1', 'ReferencedToleranceTableNumber'], 9380 '0x00B0': ['SQ', '1', 'ReferencedBolusSequence'], 9381 '0x00C0': ['IS', '1', 'ReferencedWedgeNumber'], 9382 '0x00D0': ['IS', '1', 'ReferencedCompensatorNumber'], 9383 '0x00E0': ['IS', '1', 'ReferencedBlockNumber'], 9384 '0x00F0': ['IS', '1', 'ReferencedControlPointIndex'], 9385 '0x00F2': ['SQ', '1', 'ReferencedControlPointSequence'], 9386 '0x00F4': ['IS', '1', 'ReferencedStartControlPointIndex'], 9387 '0x00F6': ['IS', '1', 'ReferencedStopControlPointIndex'], 9388 '0x0100': ['IS', '1', 'ReferencedRangeShifterNumber'], 9389 '0x0102': ['IS', '1', 'ReferencedLateralSpreadingDeviceNumber'], 9390 '0x0104': ['IS', '1', 'ReferencedRangeModulatorNumber'], 9391 '0x0111': ['SQ', '1', 'OmittedBeamTaskSequence'], 9392 '0x0112': ['CS', '1', 'ReasonForOmission'], 9393 '0x0113': ['LO', '1', 'ReasonForOmissionDescription'] 9394 }, 9395 '0x300E': { 9396 '0x0000': ['UL', '1', 'GenericGroupLength'], 9397 '0x0002': ['CS', '1', 'ApprovalStatus'], 9398 '0x0004': ['DA', '1', 'ReviewDate'], 9399 '0x0005': ['TM', '1', 'ReviewTime'], 9400 '0x0008': ['PN', '1', 'ReviewerName'] 9401 }, 9402 '0x4000': { 9403 '0x0000': ['UL', '1', 'GenericGroupLength'], 9404 '0x0010': ['LT', '1', 'Arbitrary'], 9405 '0x4000': ['LT', '1', 'TextComments'] 9406 }, 9407 '0x4008': { 9408 '0x0000': ['UL', '1', 'GenericGroupLength'], 9409 '0x0040': ['SH', '1', 'ResultsID'], 9410 '0x0042': ['LO', '1', 'ResultsIDIssuer'], 9411 '0x0050': ['SQ', '1', 'ReferencedInterpretationSequence'], 9412 '0x00FF': ['CS', '1', 'ReportProductionStatusTrial'], 9413 '0x0100': ['DA', '1', 'InterpretationRecordedDate'], 9414 '0x0101': ['TM', '1', 'InterpretationRecordedTime'], 9415 '0x0102': ['PN', '1', 'InterpretationRecorder'], 9416 '0x0103': ['LO', '1', 'ReferenceToRecordedSound'], 9417 '0x0108': ['DA', '1', 'InterpretationTranscriptionDate'], 9418 '0x0109': ['TM', '1', 'InterpretationTranscriptionTime'], 9419 '0x010A': ['PN', '1', 'InterpretationTranscriber'], 9420 '0x010B': ['ST', '1', 'InterpretationText'], 9421 '0x010C': ['PN', '1', 'InterpretationAuthor'], 9422 '0x0111': ['SQ', '1', 'InterpretationApproverSequence'], 9423 '0x0112': ['DA', '1', 'InterpretationApprovalDate'], 9424 '0x0113': ['TM', '1', 'InterpretationApprovalTime'], 9425 '0x0114': ['PN', '1', 'PhysicianApprovingInterpretation'], 9426 '0x0115': ['LT', '1', 'InterpretationDiagnosisDescription'], 9427 '0x0117': ['SQ', '1', 'InterpretationDiagnosisCodeSequence'], 9428 '0x0118': ['SQ', '1', 'ResultsDistributionListSequence'], 9429 '0x0119': ['PN', '1', 'DistributionName'], 9430 '0x011A': ['LO', '1', 'DistributionAddress'], 9431 '0x0200': ['SH', '1', 'InterpretationID'], 9432 '0x0202': ['LO', '1', 'InterpretationIDIssuer'], 9433 '0x0210': ['CS', '1', 'InterpretationTypeID'], 9434 '0x0212': ['CS', '1', 'InterpretationStatusID'], 9435 '0x0300': ['ST', '1', 'Impressions'], 9436 '0x4000': ['ST', '1', 'ResultsComments'] 9437 }, 9438 '0x4010': { 9439 '0x0000': ['UL', '1', 'GenericGroupLength'], 9440 '0x0001': ['CS', '1', 'LowEnergyDetectors'], 9441 '0x0002': ['CS', '1', 'HighEnergyDetectors'], 9442 '0x0004': ['SQ', '1', 'DetectorGeometrySequence'], 9443 '0x1001': ['SQ', '1', 'ThreatROIVoxelSequence'], 9444 '0x1004': ['FL', '3', 'ThreatROIBase'], 9445 '0x1005': ['FL', '3', 'ThreatROIExtents'], 9446 '0x1006': ['OB', '1', 'ThreatROIBitmap'], 9447 '0x1007': ['SH', '1', 'RouteSegmentID'], 9448 '0x1008': ['CS', '1', 'GantryType'], 9449 '0x1009': ['CS', '1', 'OOIOwnerType'], 9450 '0x100A': ['SQ', '1', 'RouteSegmentSequence'], 9451 '0x1010': ['US', '1', 'PotentialThreatObjectID'], 9452 '0x1011': ['SQ', '1', 'ThreatSequence'], 9453 '0x1012': ['CS', '1', 'ThreatCategory'], 9454 '0x1013': ['LT', '1', 'ThreatCategoryDescription'], 9455 '0x1014': ['CS', '1', 'ATDAbilityAssessm
9455ent'], 9456 '0x1015': ['CS', '1', 'ATDAssessmentFlag'], 9457 '0x1016': ['FL', '1', 'ATDAssessmentProbability'], 9458 '0x1017': ['FL', '1', 'Mass'], 9459 '0x1018': ['FL', '1', 'Density'], 9460 '0x1019': ['FL', '1', 'ZEffective'], 9461 '0x101A': ['SH', '1', 'BoardingPassID'], 9462 '0x101B': ['FL', '3', 'CenterOfMass'], 9463 '0x101C': ['FL', '3', 'CenterOfPTO'], 9464 '0x101D': ['FL', '6-n', 'BoundingPolygon'], 9465 '0x101E': ['SH', '1', 'RouteSegmentStartLocationID'], 9466 '0x101F': ['SH', '1', 'RouteSegmentEndLocationID'], 9467 '0x1020': ['CS', '1', 'RouteSegmentLocationIDType'], 9468 '0x1021': ['CS', '1-n', 'AbortReason'], 9469 '0x1023': ['FL', '1', 'VolumeOfPTO'], 9470 '0x1024': ['CS', '1', 'AbortFlag'], 9471 '0x1025': ['DT', '1', 'RouteSegmentStartTime'], 9472 '0x1026': ['DT', '1', 'RouteSegmentEndTime'], 9473 '0x1027': ['CS', '1', 'TDRType'], 9474 '0x1028': ['CS', '1', 'InternationalRouteSegment'], 9475 '0x1029': ['LO', '1-n', 'ThreatDetectionAlgorithmandVersion'], 9476 '0x102A': ['SH', '1', 'AssignedLocation'], 9477 '0x102B': ['DT', '1', 'AlarmDecisionTime'], 9478 '0x1031': ['CS', '1', 'AlarmDecision'], 9479 '0x1033': ['US', '1', 'NumberOfTotalObjects'], 9480 '0x1034': ['US', '1', 'NumberOfAlarmObjects'], 9481 '0x1037': ['SQ', '1', 'PTORepresentationSequence'], 9482 '0x1038': ['SQ', '1', 'ATDAssessmentSequence'], 9483 '0x1039': ['CS', '1', 'TIPType'], 9484 '0x103A': ['CS', '1', 'DICOSVersion'], 9485 '0x1041': ['DT', '1', 'OOIOwnerCreationTime'], 9486 '0x1042': ['CS', '1', 'OOIType'], 9487 '0x1043': ['FL', '3', 'OOISize'], 9488 '0x1044': ['CS', '1', 'AcquisitionStatus'], 9489 '0x1045': ['SQ', '1', 'BasisMaterialsCodeSequence'], 9490 '0x1046': ['CS', '1', 'PhantomType'], 9491 '0x1047': ['SQ', '1', 'OOIOwnerSequence'], 9492 '0x1048': ['CS', '1', 'ScanType'], 9493 '0x1051': ['LO', '1', 'ItineraryID'], 9494 '0x1052': ['SH', '1', 'ItineraryIDType'], 9495 '0x1053': ['LO', '1', 'ItineraryIDAssigningAuthority'], 9496 '0x1054': ['SH', '1', 'RouteID'], 9497 '0x1055': ['SH', '1', 'RouteIDAssigningAuthority'], 9498 '0x1056': ['CS', '1', 'InboundArrivalType'], 9499 '0x1058': ['SH', '1', 'CarrierID'], 9500 '0x1059': ['CS', '1', 'CarrierIDAssigningAuthority'], 9501 '0x1060': ['FL', '3', 'SourceOrientation'], 9502 '0x1061': ['FL', '3', 'SourcePosition'], 9503 '0x1062': ['FL', '1', 'BeltHeight'], 9504 '0x1064': ['SQ', '1', 'AlgorithmRoutingCodeSequence'], 9505 '0x1067': ['CS', '1', 'TransportClassification'], 9506 '0x1068': ['LT', '1', 'OOITypeDescriptor'], 9507 '0x1069': ['FL', '1', 'TotalProcessingTime'], 9508 '0x106C': ['OB', '1', 'DetectorCalibrationData'], 9509 '0x106D': ['CS', '1', 'AdditionalScreeningPerformed'], 9510 '0x106E': ['CS', '1', 'AdditionalInspectionSelectionCriteria'], 9511 '0x106F': ['SQ', '1', 'AdditionalInspectionMethodSequence'], 9512 '0x1070': ['CS', '1', 'AITDeviceType'], 9513 '0x1071': ['SQ', '1', 'QRMeasurementsSequence'], 9514 '0x1072': ['SQ', '1', 'TargetMaterialSequence'], 9515 '0x1073': ['FD', '1', 'SNRThreshold'], 9516 '0x1075': ['DS', '1', 'ImageScaleRepresentation'], 9517 '0x1076': ['SQ', '1', 'ReferencedPTOSequence'], 9518 '0x1077': ['SQ', '1', 'ReferencedTDRInstanceSequence'], 9519 '0x1078': ['ST', '1', 'PTOLocationDescription'], 9520 '0x1079': ['SQ', '1', 'AnomalyLocatorIndicatorSequence'], 9521 '0x107A': ['FL', '3', 'AnomalyLocatorIndicator'], 9522 '0x107B': ['SQ', '1', 'PTORegionSequence'], 9523 '0x107C': ['CS', '1', 'InspectionSelectionCriteria'], 9524 '0x107D': ['SQ', '1', 'SecondaryInspectionMethodSequence'], 9525 '0x107E': ['DS', '6', 'PRCSToRCSOrientation'] 9526 }, 9527 '0x4FFE': { 9528 '0x0000': ['UL', '1', 'GenericGroupLength'], 9529 '0x0001': ['SQ', '1', 'MACParametersSequence'] 9530 }, 9531 '0x5000': { 9532 '0x0000': ['UL', '1', 'GenericGroupLength'], 9533 '0x0005': ['US', '1', 'CurveDimensions'], 9534 '0x0010': ['US', '1', 'NumberOfPoints'], 9535 '0x0020': ['CS', '1', 'TypeOfData'], 9536 '0x0022': ['LO', '1', 'CurveDescription'], 9537 '0x0030': ['SH', '1-n', 'AxisUnits'], 9538 '0x0040': ['SH', '1-n', 'AxisLabels'], 9539 '0x0103': ['US', '1', 'DataValueRepresentation'], 9540 '0x0104': ['US', '1-n', 'MinimumCoordinateValue'], 9541 '0x0105': ['US', '1-n', 'MaximumCoordinateValue'], 9542 '0x0106': ['SH', '1-n', 'CurveRange'], 9543 '0x0110': ['US', '1-n', 'CurveDataDescriptor'], 9544 '0x0112': ['US', '1-n', 'CoordinateStartValue'], 9545 '0x0114': ['US', '1-n', 'CoordinateStepValue'], 9546 '0x1001': ['CS', '1', 'CurveActivationLayer'], 9547 '0x2000': ['US', '1', 'AudioType'], 9548 '0x2002': ['US', '1', 'AudioSampleFormat'], 9549 '0x2004': ['US', '1', 'NumberOfChannels'], 9550 '0x2006': ['UL', '1', 'NumberOfSamples'], 9551 '0x2008': ['UL', '1', 'SampleRate'], 9552 '0x200A': ['UL', '1', 'TotalTime'], 9553 '0x200C': ['ox', '1', 'AudioSampleData'], 9554 '0x200E': ['LT', '1', 'AudioComments'], 9555 '0x2500': ['LO', '1', 'CurveLabel'], 9556 '0x2600': ['SQ', '1', 'CurveReferencedOverlaySequence'], 9557 '0x2610': ['US', '1', 'CurveReferencedOverlayGroup'], 9558 '0x3000': ['ox', '1', 'CurveData'] 9559 }, 9560 '0x5200': { 9561 '0x0000': ['UL', '1', 'GenericGroupLength'], 9562 '0x9229': ['SQ', '1', 'SharedFunctionalGroupsSequence'], 9563 '0x9230': ['SQ', '1', 'PerFrameFunctionalGroupsSequence'] 9564 }, 9565 '0x5400': { 9566 '0x0000': ['UL', '1', 'GenericGroupLength'], 9567 '0x0100': ['SQ', '1', 'WaveformSequence'], 9568 '0x0110': ['ox', '1', 'ChannelMinimumValue'], 9569 '0x0112': ['ox', '1', 'ChannelMaximumValue'], 9570 '0x1004': ['US', '1', 'WaveformBitsAllocated'], 9571 '0x1006': ['CS', '1', 'WaveformSampleInterpretation'], 9572 '0x100A': ['ox', '1', 'WaveformPaddingValue'], 9573 '0x1010': ['ox', '1', 'WaveformData'] 9574 }, 9575 '0x5600': { 9576 '0x0000': ['UL', '1', 'GenericGroupLength'], 9577 '0x0010': ['OF', '1', 'FirstOrderPhaseCorrectionAngle'], 9578 '0x0020': ['OF', '1', 'SpectroscopyData'] 9579 }, 9580 '0x6000': { 9581 '0x0000': ['UL', '1', 'GenericGroupLength'], 9582 '0x0010': ['US', '1', 'OverlayRows'], 9583 '0x0011': ['US', '1', 'OverlayColumns'], 9584 '0x0012': ['US', '1', 'OverlayPlanes'], 9585 '0x0015': ['IS', '1', 'NumberOfFramesInOverlay'], 9586 '0x0022': ['LO', '1', 'OverlayDescription'], 9587 '0x0040': ['CS', '1', 'OverlayType'], 9588 '0x0045': ['LO', '1', 'OverlaySubtype'], 9589 '0x0050': ['SS', '2', 'OverlayOrigin'], 9590 '0x0051': ['US', '1', 'ImageFrameOrigin'], 9591 '0x0052': ['US', '1', 'OverlayPlaneOrigin'], 9592 '0x0060': ['CS', '1', 'OverlayCompressionCode'], 9593 '0x0061': ['SH', '1', 'OverlayCompressionOriginator'], 9594 '0x0062': ['SH', '1', 'OverlayCompressionLabel'], 9595 '0x0063': ['CS', '1', 'OverlayCompressionDescription'], 9596 '0x0066': ['AT', '1-n', 'OverlayCompressionStepPointers'], 9597 '0x0068': ['US', '1', 'OverlayRepeatInterval'], 9598 '0x0069': ['US', '1', 'OverlayBitsGrouped'], 9599 '0x0100': ['US', '1', 'OverlayBitsAllocated'], 9600 '0x0102': ['US', '1', 'OverlayBitPosition'], 9601 '0x0110': ['CS', '1', 'OverlayFormat'], 9602 '0x0200': ['US', '1', 'OverlayLocation'],
9603 '0x0800': ['CS', '1-n', 'OverlayCodeLabel'], 9604 '0x0802': ['US', '1', 'OverlayNumberOfTables'], 9605 '0x0803': ['AT', '1-n', 'OverlayCodeTableLocation'], 9606 '0x0804': ['US', '1', 'OverlayBitsForCodeWord'], 9607 '0x1001': ['CS', '1', 'OverlayActivationLayer'], 9608 '0x1100': ['US', '1', 'OverlayDescriptorGray'], 9609 '0x1101': ['US', '1', 'OverlayDescriptorRed'], 9610 '0x1102': ['US', '1', 'OverlayDescriptorGreen'], 9611 '0x1103': ['US', '1', 'OverlayDescriptorBlue'], 9612 '0x1200': ['US', '1-n', 'OverlaysGray'], 9613 '0x1201': ['US', '1-n', 'OverlaysRed'], 9614 '0x1202': ['US', '1-n', 'OverlaysGreen'], 9615 '0x1203': ['US', '1-n', 'OverlaysBlue'], 9616 '0x1301': ['IS', '1', 'ROIArea'], 9617 '0x1302': ['DS', '1', 'ROIMean'], 9618 '0x1303': ['DS', '1', 'ROIStandardDeviation'], 9619 '0x1500': ['LO', '1', 'OverlayLabel'], 9620 '0x3000': ['ox', '1', 'OverlayData'], 9621 '0x4000': ['LT', '1', 'OverlayComments'] 9622 }, 9623 '0x7FE0': { 9624 '0x0000': ['UL', '1', 'GenericGroupLength'], 9625 '0x0008': ['OF', '1', 'FloatPixelData'], 9626 '0x0009': ['OD', '1', 'DoubleFloatPixelData'], 9627 '0x0010': ['ox', '1', 'PixelData'], 9628 '0x0020': ['OW', '1', 'CoefficientsSDVN'], 9629 '0x0030': ['OW', '1', 'CoefficientsSDHN'], 9630 '0x0040': ['OW', '1', 'CoefficientsSDDN'] 9631 }, 9632 '0x7F00': { 9633 '0x0000': ['UL', '1', 'GenericGroupLength'], 9634 '0x0010': ['ox', '1', 'VariablePixelData'], 9635 '0x0011': ['US', '1', 'VariableNextDataGroup'], 9636 '0x0020': ['OW', '1', 'VariableCoefficientsSDVN'], 9637 '0x0030': ['OW', '1', 'VariableCoefficientsSDHN'], 9638 '0x0040': ['OW', '1', 'VariableCoefficientsSDDN'] 9639 }, 9640 '0xFFFA': { 9641 '0x0000': ['UL', '1', 'GenericGroupLength'], 9642 '0xFFFA': ['SQ', '1', 'DigitalSignaturesSequence'] 9643 }, 9644 '0xFFFC': { 9645 '0x0000': ['UL', '1', 'GenericGroupLength'], 9646 '0xFFFC': ['OB', '1', 'DataSetTrailingPadding'] 9647 }, 9648 '0xFFFE': { 9649 '0x0000': ['UL', '1', 'GenericGroupLength'], 9650 '0xE000': ['NONE', '1', 'Item'], 9651 '0xE00D': ['NONE', '1', 'ItemDelimitationItem'], 9652 '0xE0DD': ['NONE', '1', 'SequenceDelimitationItem'] 9653 } 9654 }; // dwv.dicom.Dictionnary 9655 9656 // taken from gdcm-2.6.1\Source\DataDictionary\GroupName.dic 9657 // -> removed duplicates (commented) 9658 dwv.dicom.TagGroups = { 9659 'x0000': 'Command', 9660 'x0002': 'Meta Element', 9661 'x0004': 'File Set', 9662 //'x0004': 'Directory', 9663 'x0008': 'Identifying', 9664 'x0009': 'SPI Identifying', 9665 'x0010': 'Patient', 9666 'x0012': 'Clinical Trial', 9667 'x0018': 'Acquisition', 9668 'x0019': 'SPI Acquisition', 9669 'x0020': 'Image', 9670 'x0021': 'SPI Image', 9671 'x0022': 'Ophtalmology', 9672 'x0028': 'Image Presentation', 9673 'x0032': 'Study', 9674 'x0038': 'Visit', 9675 'x003A': 'Waveform', 9676 'x0040': 'Procedure', 9677 //'x0040': ''Modality Worklist', 9678 'x0042': 'Encapsulated Document', 9679 'x0050': 'Device Informations', 9680 //'x0050': 'XRay Angio Device', 9681 'x0054': 'Nuclear Medicine', 9682 'x0060': 'Histogram', 9683 'x0070': 'Presentation State', 9684 'x0072': 'Hanging Protocol', 9685 'x0088': 'Storage', 9686 //'x0088': 'Medicine', 9687 'x0100': 'Authorization', 9688 'x0400': 'Digital Signature', 9689 'x1000': 'Code Table', 9690 'x1010': 'Zonal Map', 9691 'x2000': 'Film Session', 9692 'x2010': 'Film Box', 9693 'x2020': 'Image Box', 9694 'x2030': 'Annotation', 9695 'x2040': 'Overlay Box', 9696 'x2050': 'Presentation LUT', 9697 'x2100': 'Print Job', 9698 'x2110': 'Printer', 9699 'x2120': 'Queue', 9700 'x2130': 'Print Content', 9701 'x2200': 'Media Creation', 9702 'x3002': 'RT Image', 9703 'x3004': 'RT Dose', 9704 'x3006': 'RT StructureSet', 9705 'x3008': 'RT Treatment', 9706 'x300A': 'RT Plan', 9707 'x300C': 'RT Relationship', 9708 'x300E': 'RT Approval', 9709 'x4000': 'Text',
9710 'x4008': 'Results', 9711 'x4FFE': 'MAC Parameters', 9712 'x5000': 'Curve', 9713 'x5002': 'Curve', 9714 'x5004': 'Curve', 9715 'x5006': 'Curve', 9716 'x5008': 'Curve', 9717 'x500A': 'Curve', 9718 'x500C': 'Curve', 9719 'x500E': 'Curve', 9720 'x5400': 'Waveform Data', 9721 'x6000': 'Overlays', 9722 'x6002': 'Overlays', 9723 'x6004': 'Overlays', 9724 'x6008': 'Overlays', 9725 'x600A': 'Overlays', 9726 'x600C': 'Overlays', 9727 'x600E': 'Overlays', 9728 'xFFFC': 'Generic', 9729 'x7FE0': 'Pixel Data', 9730 'xFFFF': 'Unknown' 9731 }; 9732 9733 // namespaces 9734 var dwv = dwv || {}; 9735 /** @namespace */ 9736 dwv.gui = dwv.gui || {}; 9737 /** @namespace */ 9738 dwv.gui.base = dwv.gui.base || {}; 9739 /** @namespace */ 9740 dwv.gui.filter = dwv.gui.filter || {}; 9741 /** @namespace */ 9742 dwv.gui.filter.base = dwv.gui.filter.base || {}; 9743 9744 /** 9745 * Filter tool base gui. 9746 * @constructor 9747 */ 9748 dwv.gui.base.Filter = function(app) { 9749 /** 9750 * Setup the filter tool HTML. 9751 */ 9752 this.setup = function(list) { 9753 // filter select 9754 var filterSelector = dwv.html.createHtmlSelect("filterSelect", list, "filter"); 9755 filterSelector.onchange = app.onChangeFilter; 9756 9757 // filter list element 9758 var filterLi = dwv.html.createHiddenElement("li", "filterLi"); 9759 filterLi.className += " ui-block-b"; 9760 filterLi.appendChild(filterSelector); 9761 9762 // append element 9763 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 9764 dwv.html.appendElement(node, filterLi); 9765 }; 9766 9767 /** 9768 * Display the tool HTML. 9769 * @param {Boolean} flag True to display, false to hide. 9770 */ 9771 this.display = function(flag) { 9772 var node = app.getElement("filterLi"); 9773 dwv.html.displayElement(node, flag); 9774 }; 9775 9776 /** 9777 * Initialise the tool HTML. 9778 */ 9779 this.initialise = function() { 9780 // filter select: reset selected options 9781 var filterSelector = app.getElement("filterSelect"); 9782 filterSelector.selectedIndex = 0; 9783 // refresh 9784 dwv.gui.refreshElement(filterSelector); 9785 }; 9786 9787 }; // class dwv.gui.base.Filter 9788 9789 /** 9790 * Threshold filter base gui. 9791 * @constructor 9792 */ 9793 dwv.gui.base.Threshold = function(app) { 9794 /** 9795 * Threshold slider. 9796 * @private 9797 * @type Object 9798 */ 9799 var slider = new dwv.gui.Slider(app); 9800 9801 /** 9802 * Setup the threshold filter HTML. 9803 */ 9804 this.setup = function() { 9805 // threshold list element 9806 var thresholdLi = dwv.html.createHiddenElement("li", "thresholdLi"); 9807 thresholdLi.className += " ui-block-c"; 9808 9809 // node 9810 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 9811 // append threshold 9812 node.appendChild(thresholdLi); 9813 // threshold slider 9814 slider.append(); 9815 // refresh 9816 dwv.gui.refreshElement(node); 9817 }; 9818 9819 /** 9820 * Clear the threshold filter HTML. 9821 * @param {Boolean} flag True to display, false to hide. 9822 */ 9823 this.display = function(flag) { 9824 // only initialise at display time 9825 // (avoids min/max calculation at startup) 9826 if (flag) { 9827 slider.initialise(); 9828 } 9829 9830 var node = app.getElement("thresholdLi"); 9831 dwv.html.displayElement(node, flag); 9832 }; 9833 9834 /** 9835 * Initialise the threshold filter HTML. 9836 */ 9837 this.initialise = function() { 9838 // nothing to do 9839 }; 9840 9841 }; // class dwv.gui.base.Threshold 9842 9843 /** 9844 * Create the apply filter button. 9845 */ 9846 dwv.gui.filter.base.createFilterApplyButton = function(app) { 9847 var button = document.createElement("button"); 9848 button.id = "runFilterButton"; 9849 button.onclick = app.onRunFilter; 9850 button.setAttribute("style", "width:100%; margin-top:0.5em;"); 9851 button.setAttribute("class", "ui-btn ui-btn-b"); 9852 button.appendChild(document.createTextNode(dwv.i18n("basics.apply"))); 9853 return button; 9854 }; 9855 9856 /** 9857 * Sharpen filter base gui. 9858 * @constructor 9859 */
9860 dwv.gui.base.Sharpen = function(app) { 9861 /** 9862 * Setup the sharpen filter HTML. 9863 */ 9864 this.setup = function() { 9865 // sharpen list element 9866 var sharpenLi = dwv.html.createHiddenElement("li", "sharpenLi"); 9867 sharpenLi.className += " ui-block-c"; 9868 sharpenLi.appendChild(dwv.gui.filter.base.createFilterApplyButton(app)); 9869 // append element 9870 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 9871 dwv.html.appendElement(node, sharpenLi); 9872 }; 9873 9874 /** 9875 * Display the sharpen filter HTML. 9876 * @param {Boolean} flag True to display, false to hide. 9877 */ 9878 this.display = function(flag) { 9879 var node = app.getElement("sharpenLi"); 9880 dwv.html.displayElement(node, flag); 9881 }; 9882 9883 }; // class dwv.gui.base.Sharpen 9884 9885 /** 9886 * Sobel filter base gui. 9887 * @constructor 9888 */ 9889 dwv.gui.base.Sobel = function(app) { 9890 /** 9891 * Setup the sobel filter HTML. 9892 */ 9893 this.setup = function() { 9894 // sobel list element 9895 var sobelLi = dwv.html.createHiddenElement("li", "sobelLi"); 9896 sobelLi.className += " ui-block-c"; 9897 sobelLi.appendChild(dwv.gui.filter.base.createFilterApplyButton(app)); 9898 // append element 9899 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 9900 dwv.html.appendElement(node, sobelLi); 9901 }; 9902 9903 /** 9904 * Display the sobel filter HTML. 9905 * @param {Boolean} flag True to display, false to hide. 9906 */ 9907 this.display = function(flag) { 9908 var node = app.getElement("sobelLi"); 9909 dwv.html.displayElement(node, flag); 9910 }; 9911 9912 }; // class dwv.gui.base.Sobel 9913 9914 // namespaces 9915 var dwv = dwv || {}; 9916 dwv.gui = dwv.gui || {}; 9917 dwv.gui.base = dwv.gui.base || {}; 9918 9919 /** 9920 * Get the size of the image display window. 9921 */ 9922 dwv.gui.base.getWindowSize = function() { 9923 return { 'width': window.innerWidth, 'height': window.innerHeight - 147 }; 9924 }; 9925 9926 /** 9927 * Ask some text to the user. 9928 * @param {String} message Text to display to the user. 9929 * @param {String} defaultText Default value displayed in the text input field. 9930 * @return {String} Text entered by the user. 9931 */ 9932 dwv.gui.base.prompt = function(message, defaultText) { 9933 return prompt(message, defaultText); 9934 }; 9935 9936 /** 9937 * Display a progress value. 9938 * @param {Number} percent The progress percentage. 9939 */ 9940 dwv.gui.base.displayProgress = function( /*percent*/ ) { 9941 // default does nothing... 9942 }; 9943 9944 /** 9945 * Focus the view on the image. 9946 */ 9947 dwv.gui.base.focusImage = function() { 9948 // default does nothing... 9949 }; 9950 9951 /** 9952 * Post process a HTML table. 9953 * @param {Object} table The HTML table to process. 9954 * @return The processed HTML table. 9955 */ 9956 dwv.gui.base.postProcessTable = function( /*table*/ ) { 9957 // default does nothing... 9958 }; 9959 9960 /** 9961 * Get a HTML element associated to a container div. 9962 * @param {Number} containerDivId The id of the container div. 9963 * @param {String} name The name or id to find. 9964 * @return {Object} The found element or null. 9965 */ 9966 dwv.gui.base.getElement = function(containerDivId, name) { 9967 // get by class in the container div 9968 var parent = document.getElementById(containerDivId); 9969 var elements = parent.getElementsByClassName(name); 9970 // getting the last element since some libraries (ie jquery-mobile) creates 9971 // span in front of regular tags (such as select)... 9972 var element = elements[elements.length - 1]; 9973 // if not found get by id with 'containerDivId-className' 9974 if (typeof element === "undefined") { 9975 element = document.getElementById(containerDivId + '-' + name); 9976 } 9977 return element; 9978 }; 9979 9980 /** 9981 * Refresh a HTML element. Mainly for jquery-mobile. 9982 * @param {String} element The HTML element to refresh. 9983 */ 9984 dwv.gui.base.refreshElement = function( /*element*/ ) { 9985 // base does nothing... 9986 }; 9987 9988 /** 9989 * Set the selected item of a HTML select. 9990 * @param {String} element The HTML select element. 9991 * @param {String}
9991 value The value of the option to mark as selected. 9992 */ 9993 dwv.gui.setSelected = function(element, value) { 9994 if (element) { 9995 var index = 0; 9996 for (index in element.options) { 9997 if (element.options[index].value === value) { 9998 break; 9999 } 10000 } 10001 element.selectedIndex = index; 10002 dwv.gui.refreshElement(element); 10003 } 10004 }; 10005 10006 /** 10007 * Slider base gui. 10008 * @constructor 10009 */ 10010 dwv.gui.base.Slider = function(app) { 10011 /** 10012 * Append the slider HTML. 10013 */ 10014 this.append = function() { 10015 // default values 10016 var min = 0; 10017 var max = 1; 10018 10019 // jquery-mobile range slider 10020 // minimum input 10021 var inputMin = document.createElement("input"); 10022 inputMin.id = "threshold-min"; 10023 inputMin.type = "range"; 10024 inputMin.max = max; 10025 inputMin.min = min; 10026 inputMin.value = min; 10027 // maximum input 10028 var inputMax = document.createElement("input"); 10029 inputMax.id = "threshold-max"; 10030 inputMax.type = "range"; 10031 inputMax.max = max; 10032 inputMax.min = min; 10033 inputMax.value = max; 10034 // slicer div 10035 var div = document.createElement("div"); 10036 div.id = "threshold-div"; 10037 div.setAttribute("data-role", "rangeslider"); 10038 div.appendChild(inputMin); 10039 div.appendChild(inputMax); 10040 div.setAttribute("data-mini", "true"); 10041 // append to document 10042 app.getElement("thresholdLi").appendChild(div); 10043 // bind change 10044 $("#threshold-div").on("change", 10045 function( /*event*/ ) { 10046 app.onChangeMinMax({ 10047 "min": $("#threshold-min").val(), 10048 "max": $("#threshold-max").val() 10049 }); 10050 } 10051 ); 10052 // refresh 10053 dwv.gui.refreshElement(app.getElement("toolList")); 10054 }; 10055 10056 /** 10057 * Initialise the slider HTML. 10058 */ 10059 this.initialise = function() { 10060 var min = app.getImage().getDataRange().min; 10061 var max = app.getImage().getDataRange().max; 10062 10063 // minimum input 10064 var inputMin = document.getElementById("threshold-min"); 10065 inputMin.max = max; 10066 inputMin.min = min; 10067 inputMin.value = min; 10068 // maximum input 10069 var inputMax = document.getElementById("threshold-max"); 10070 inputMax.max = max; 10071 inputMax.min = min; 10072 inputMax.value = max; 10073 // refresh 10074 dwv.gui.refreshElement(app.getElement("toolList")); 10075 }; 10076 10077 }; // class dwv.gui.base.Slider 10078 10079 /** 10080 * DICOM tags base gui. 10081 * @param {Object} app The associated application. 10082 * @constructor 10083 */ 10084 dwv.gui.base.DicomTags = function(app) { 10085 /** 10086 * Update the DICOM tags table with the input info. 10087 * @param {Object} dataInfo The data information. 10088 */ 10089 this.update = function(dataInfo) { 10090 // HTML node 10091 var node = app.getElement("tags"); 10092 if (node === null) { 10093 console.warn("Cannot find a node to append the DICOM tags."); 10094 return; 10095 } 10096 // remove possible previous 10097 while (node.hasChildNodes()) { 10098 node.removeChild(node.firstChild); 10099 } 10100 10101 // exit if no tags 10102 if (dataInfo.length === 0) { 10103 console.warn("No DICOM tags to show."); 10104 return; 10105 } 10106 10107 // tags HTML table 10108 var table = dwv.html.toTable(dataInfo); 10109 table.className = "tagsTable"; 10110 10111 // optional gui specific table post process 10112 dwv.gui.postProcessTable(table); 10113 10114 // check processed table 10115 if (table.rows.length === 0) { 10116 console.warn("The processed table does not contain data."); 10117 return; 10118 } 10119 10120 // translate first row 10121 dwv.html.translateTableRow(table.rows.item(0)); 10122 10123 // append search form 10124 node.appendChild(dwv.html.getHtmlSearchForm(table)); 10125 // append tags table 10126 node.appendChild(table); 10127 10128 // refresh 10129 dwv.gui.refreshElement(node); 10130 }; 10131 10132 }; // class dwv.gui.base.DicomTags 10133 10134 /** 10135 * Drawing list base gui. 10136 * @param {Object} app The associated application. 10137 * @constructor 10138 */ 10139 dwv.gui.base.DrawList = function(app) { 10140 /** 10141 * Closure to self. 10142 */ 10143 var self = this; 10144 10145 /** 10146 * Update the draw list html element 10147 * @param {Object} event A change event, decides if the table is editable or not. 10148 */ 10149 this.update = function(event) { 10150 var isEditable = false; 10151 if (typeof event.editable !== "undefined") { 10152 isEditable = event.editable; 10153 } 10154 10155 // HTML node 10156 var node = app.getElement("drawList"); 10157 if (node === null) {
10158 console.warn("Cannot find a node to append the drawing list."); 10159 return; 10160 } 10161 // remove possible previous 10162 while (node.hasChildNodes()) { 10163 node.removeChild(node.firstChild); 10164 } 10165 10166 // drawing details 10167 var drawDisplayDetails = app.getDrawDisplayDetails(); 10168 10169 // exit if no details 10170 if (drawDisplayDetails.length === 0) { 10171 return; 10172 } 10173 10174 // tags HTML table 10175 var table = dwv.html.toTable(drawDisplayDetails); 10176 table.className = "drawsTable"; 10177 10178 // optional gui specific table post process 10179 dwv.gui.postProcessTable(table); 10180 10181 // check processed table 10182 if (table.rows.length === 0) { 10183 console.warn("The processed table does not contain data."); 10184 return; 10185 } 10186 10187 // translate first row 10188 dwv.html.translateTableRow(table.rows.item(0)); 10189 10190 // translate shape names 10191 dwv.html.translateTableColumn(table, 3, "shape", "name"); 10192 10193 // create a color onkeyup handler 10194 var createColorOnKeyUp = function(details) { 10195 return function() { 10196 details.color = this.value; 10197 app.updateDraw(details); 10198 }; 10199 }; 10200 // create a text onkeyup handler 10201 var createTextOnKeyUp = function(details) { 10202 return function() { 10203 details.label = this.value; 10204 app.updateDraw(details); 10205 }; 10206 }; 10207 // create a long text onkeyup handler 10208 var createLongTextOnKeyUp = function(details) { 10209 return function() { 10210 details.description = this.value; 10211 app.updateDraw(details); 10212 }; 10213 }; 10214 // create a row onclick handler 10215 var createRowOnClick = function(slice, frame) { 10216 return function() { 10217 // update slice 10218 var pos = app.getViewController().getCurrentPosition(); 10219 pos.k = slice; 10220 app.getViewController().setCurrentPosition(pos); 10221 // update frame 10222 app.getViewController().setCurrentFrame(frame); 10223 // focus on the image 10224 dwv.gui.focusImage(); 10225 }; 10226 }; 10227 // create visibility handler 10228 var createVisibleOnClick = function(details) { 10229 return function() { 10230 app.toogleGroupVisibility(details); 10231 }; 10232 }; 10233 10234 // append visible column to the header row 10235 var row0 = table.rows.item(0); 10236 var cell00 = row0.insertCell(0); 10237 cell00.outerHTML = "<th>" + dwv.i18n("basics.visible") + "</th>"; 10238 10239 // loop through rows 10240 for (var r = 1; r < table.rows.length; ++r) { 10241 var drawId = r - 1; 10242 var drawDetails = drawDisplayDetails[drawId]; 10243 var row = table.rows.item(r); 10244 var cells = row.cells; 10245 10246 // loop through cells 10247 for (var c = 0; c < cells.length; ++c) { 10248 // show short ID 10249 if (c === 0) { 10250 cells[c].firstChild.data = cells[c].firstChild.data.substring(0, 5); 10251 } 10252 10253 if (isEditable) { 10254 // color 10255 if (c === 4) { 10256 dwv.html.makeCellEditable(cells[c], createColorOnKeyUp(drawDetails), "color"); 10257 } 10258 // text 10259 else if (c === 5) { 10260 dwv.html.makeCellEditable(cells[c], createTextOnKeyUp(drawDetails)); 10261 } 10262 // long text 10263 else if (c === 6) { 10264 dwv.html.makeCellEditable(cells[c], createLongTextOnKeyUp(drawDetails)); 10265 } 10266 } else { 10267 // id: link to image 10268 cells[0].onclick = createRowOnClick( 10269 cells[1].firstChild.data, 10270 cells[2].firstChild.data); 10271 cells[0].onmouseover = dwv.html.setCursorToPointer; 10272 cells[0].onmouseout = dwv.html.setCursorToDefault; 10273 // color: just display the input color with no callback 10274 if (c === 4) { 10275 dwv.html.makeCellEditable(cells[c], null, "color"); 10276 } 10277 } 10278 } 10279 10280 // append visible column 10281 var cell0 = row.insertCell(0); 10282 var input = document.createElement("input"); 10283 input.setAttribute("type", "checkbox"); 10284 input.checked = app.isGroupVisible(drawDetails); 10285 input.onclick = createVisibleOnClick(drawDetails); 10286 cell0.appendChild(input); 10287 } 10288 10289 // editable checkbox 10290 var tickBox = document.createElement("input"); 10291 tickBox.setAttribute("type", "checkbox"); 10292 tickBox.id = "checkbox-editable"; 10293 tickBox.checked = isEditable; 10294 tickBox.onclick = function() { self.update({ "editable": this.checked }); }; 10295 // checkbox label 10296 var tickLabel = document.createElement("label"); 10297 tickLabel.setAttribute("for", tickBox.id); 10298 tickLabel.setAttribute("class", "inline"); 10299 tickLabel.appendChild(document.createTextNode(dwv.i18n("basics.editMode"))); 10300 // checkbox div 10301 var tickDiv = document.createElement("div"); 10302 tickDiv.appendChild(tickLabel); 10303 tickDiv.appendChild(tickBox); 10304 10305 // search form 10306 node.appendChild(dwv.html.getHtmlSearchForm(table)); 10307 // tick form 10308 node.appendChild(tickDiv); 10309 10310 // draw list table 10311 node.appendChild(table); 10312 10313 // delete draw button 10314 var deleteButton = document.createElement("button"); 10315 deleteButton.onclick = function() { app.deleteDraws(); }; 10316 deleteButton.setAttribute("class", "ui-btn ui-btn-inline"); 10317 deleteButton.appendChild(document.createTextNode(dwv.i18n("basics.deleteDraws"))); 10318 if (!isEditable) { 10319 deleteButton.style.display = "none"; 10320 } 10321 node.appendChild(deleteButton); 10322 10323 // refresh 10324 dwv.gui.refreshElement(node); 10325 }; 10326 10327 }; // class dwv.gui.base.DrawList 10328 10329 // namespaces 10330 var dwv = dwv || {}; 10331 dwv.gui = dwv.gui || {}; 10332 dwv.gui.base = dwv.gui.base || {}; 10333 10334 /** 10335 * Append the version HTML. 10336 */ 10337 dwv.gui.base.appendVersionHtml = function(version) { 10338 var nodes = document.getElementsByClassName("dwv-version"); 10339 if (nodes) { 10340 for (var i = 0; i < nodes.length; ++i) { 10341 nodes[i].appendChild(document.createTextNode(version)); 10342 } 10343 } 10344 }; 10345 10346 /** 10347 * Build the help HTML. 10348 * @param {Boolean} mobile Flag for mobile or not environement. 10349 */ 10350 dwv.gui.base.appendHelpHtml = function(toolList, mobile, app) { 10351 var actionType = "mouse"; 10352 if (mobile) { 10353 actionType = "touch"; 10354 } 10355 10356 var toolHelpDiv = document.createElement("div"); 10357 10358 // current location 10359 var loc = window.location.pathname; 10360 var dir = loc.substring(0, loc.lastIndexOf('/')); 10361 10362 var tool = null; 10363 var tkeys = Object.keys(toolList); 10364 for (var t = 0; t < tkeys.length; ++t) { 10365 tool = toolList[tkeys[t]]; 10366 // title 10367 var title = document.createElement("h3"); 10368 title.appendChild(document.createTextNode(tool.getHelp().title)); 10369 // doc div 10370 var docDiv = document.createElement("div"); 10371 // brief 10372 var brief = document.createElement("p"); 10373 brief.appendChild(document.createTextNode(tool.getHelp().brief)); 10374 docDiv.appendChild(brief); 10375 // details 10376 if (tool.getHelp()[actionType]) { 10377 var keys = Object.keys(tool.getHelp()[actionType]); 10378 for (var i = 0; i < keys.length; ++i) { 10379 var action = tool.getHelp()[actionType][keys[i]]; 10380 10381 var img = document.createElement("img"); 10382 img.src = dir + "/../../resources/help/" + keys[i] + ".png"; 10383 img.style.float = "left"; 10384 img.style.margin = "0px 15px 15px 0px"; 10385 10386 var br = document.createElement("br"); 10387 br.style.clear = "both"; 10388 10389 var para = document.createElement("p"); 10390 para.appendChild(img); 10391 para.appendChild(document.createTextNode(action)); 10392 para.appendChild(br); 10393 docDiv.appendChild(para); 10394 } 10395 } 10396 10397 // different div structure for mobile or static 10398 if (mobile) { 10399 var toolDiv = document.createElement("div"); 10400 toolDiv.setAttribute("data-role", "collapsible"); 10401 toolDiv.appendChild(title); 10402 toolDiv.appendChild(docDiv); 10403 toolHelpDiv.appendChild(toolDiv); 10404 } else { 10405 toolHelpDiv.id = "accordion"; 10406 toolHelpDiv.appendChild(title); 10407 toolHelpDiv.appendChild(docDiv); 10408 } 10409 } 10410 10411 var helpNode = app.getElement("help"); 10412 10413 var headPara = document.createElement("p"); 10414 headPara.appendChild(document.createTextNode(dwv.i18n("help.intro.p0"))); 10415 helpNode.appendChild(headPara); 10416 10417 var secondPara = document.createElement("p"); 10418 secondPara.appendChild(document.createTextNode(dwv.i18n("help.intro.p1"))); 10419 helpNode.appendChild(secondPara); 10420 10421 var toolPara = document.createElement("p"); 10422 toolPara.appendChild(document.createTextNode(dwv.i18n("help.tool_intro"))); 10423 helpNode.appendChild(toolPara); 10424 helpNode.appendChild(toolHelpDiv); 10425 }; 10426 10427 // namespaces 10428 var dwv = dwv || {}; 10429 /** @namespace */ 10430 dwv.html = dwv.html || {}; 10431 10432 /** 10433 * Append a cell to a given row. 10434 * @param {Object} row The row to append the cell to. 10435 * @param {Object} content The content of the cell. 10436 */ 10437 dwv.html.appendCell = function(row, content) { 10438 var cell = row.insertCell(-1); 10439 var str = content; 10440 // special care for arrays 10441 if (content instanceof Array || 10442 content instanceof Uint8Array || content instanceof Int8Array || 10443 content instanceof Uint16Array || content instanceof Int16Array || 10444 content instanceof Uint32Array) { 10445 if (content.length > 10) { 10446 content = Array.prototype.slice.call(content, 0, 10); 10447 content[10] = "..."; 10448 } 10449 str = Array.prototype.join.call(content, ', '); 10450 } 10451 // append 10452 cell.appendChild(document.createTextNode(str)); 10453 }; 10454 10455 /** 10456 * Append a header cell to a given row. 10457 * @param {Object} row The row to append the header cell to. 10458 * @param {String} text The text of the header cell. 10459 */ 10460 dwv.html.appendHCell = function(row, text) { 10461 var cell = document.createElement("th"); 10462 cell.appendChild(document.createTextNode(text)); 10463 row.appendChild(cell); 10464 }; 10465 10466 /** 10467 * Append a row to an array. 10468 * @param {Object} table The HTML table to append a row to. 10469 * @param {Array} input The input row array. 10470 * @param {Number} level The depth level of the input array. 10471 * @param {Number} maxLevel The maximum depth level. 10472 * @param {String} rowHeader The content of the first cell of a row (mainly for objects). 10473 */
10474 dwv.html.appendRowForArray = function(table, input, level, maxLevel, rowHeader) { 10475 var row = null; 10476 // loop through 10477 for (var i = 0; i < input.length; ++i) { 10478 var value = input[i]; 10479 // last level 10480 if (typeof value === 'number' || 10481 typeof value === 'string' || 10482 value === null || 10483 value === undefined || 10484 level >= maxLevel) { 10485 if (!row) { 10486 row = table.insertRow(-1); 10487 } 10488 dwv.html.appendCell(row, value); 10489 } 10490 // more to come 10491 else { 10492 dwv.html.appendRow(table, value, level + i, maxLevel, rowHeader); 10493 } 10494 } 10495 }; 10496 10497 /** 10498 * Append a row to an object. 10499 * @param {Object} table The HTML table to append a row to. 10500 * @param {Array} input The input row array. 10501 * @param {Number} level The depth level of the input array. 10502 * @param {Number} maxLevel The maximum depth level. 10503 * @param {String} rowHeader The content of the first cell of a row (mainly for objects). 10504 */ 10505 dwv.html.appendRowForObject = function(table, input, level, maxLevel, rowHeader) { 10506 var keys = Object.keys(input); 10507 var row = null; 10508 for (var o = 0; o < keys.length; ++o) { 10509 var value = input[keys[o]]; 10510 // last level 10511 if (typeof value === 'number' || 10512 typeof value === 'string' || 10513 value === null || 10514 value === undefined || 10515 level >= maxLevel) { 10516 if (!row) { 10517 row = table.insertRow(-1); 10518 } 10519 if (o === 0 && rowHeader) { 10520 dwv.html.appendCell(row, rowHeader); 10521 } 10522 dwv.html.appendCell(row, value); 10523 } 10524 // more to come 10525 else { 10526 dwv.html.appendRow(table, value, level + o, maxLevel, keys[o]); 10527 } 10528 } 10529 // header row 10530 // warn: need to create the header after the rest 10531 // otherwise the data will inserted in the thead... 10532 if (level === 2) { 10533 var header = table.createTHead(); 10534 var th = header.insertRow(-1); 10535 if (rowHeader) { 10536 dwv.html.appendHCell(th, ""); 10537 } 10538 for (var k = 0; k < keys.length; ++k) { 10539 dwv.html.appendHCell(th, keys[k]); 10540 } 10541 } 10542 }; 10543 10544 /** 10545 * Append a row to an object or an array. 10546 * @param {Object} table The HTML table to append a row to. 10547 * @param {Array} input The input row array. 10548 * @param {Number} level The depth level of the input array. 10549 * @param {Number} maxLevel The maximum depth level. 10550 * @param {String} rowHeader The content of the first cell of a row (mainly for objects). 10551 */ 10552 dwv.html.appendRow = function(table, input, level, maxLevel, rowHeader) { 10553 // array 10554 if (input instanceof Array) { 10555 dwv.html.appendRowForArray(table, input, level + 1, maxLevel, rowHeader); 10556 } 10557 // object 10558 else if (typeof input === 'object') { 10559 dwv.html.appendRowForObject(table, input, level + 1, maxLevel, rowHeader); 10560 } else { 10561 throw new Error("Unsupported input data type."); 10562 } 10563 }; 10564 10565 /** 10566 * Converts the input to an HTML table. 10567 * @input {Mixed} input Allowed types are: array, array of object, object. 10568 * @return {Object} The created HTML table or null if the input is empty. 10569 * @warning Null is interpreted differently in browsers, firefox will not display it. 10570 */ 10571 dwv.html.toTable = function(input) { 10572 // check content 10573 if (input.length === 0) { 10574 return null; 10575 } 10576 10577 var table = document.createElement('table'); 10578 dwv.html.appendRow(table, input, 0, 2); 10579 return table; 10580 }; 10581 10582 /** 10583 * Get an HTML search form. 10584 * @param {Object} htmlTableToSearch The table to do the search on. 10585 * @return {Object} The HTML search form. 10586 */ 10587 dwv.html.getHtmlSearchForm = function(htmlTableToSearch) { 10588 // input 10589 var input = document.createElement("input"); 10590 input.id = "table-search"; 10591 // TODO Use new html5 search type 10592 //input.setAttribute("type", "search"); 10593 input.onkeyup = function() { 10594 dwv.html.filterTable(input, htmlTableToSearch); 10595 }; 10596 // label 10597 var label = document.createElement("label"); 10598 label.setAttribute("for", input.id); 10599 label.appendChild(document.createTextNode(dwv.i18n("basics.search") + ": ")); 10600 // form 10601 var form = document.createElement("form"); 10602 form.setAttribute("class", "filter"); 10603 form.onsubmit = function(event) { 10604 event.preventDefault(); 10605 }; 10606 form.appendChild(label); 10607 form.appendChild(input); 10608 // return 10609 return form; 10610 }; 10611 10612 /** 10613 * Filter a table with a given parameter: sets the display css of rows to 10614 * true or false if it contains the term. 10615 * @param {String} term The term to filter the table with. 10616 * @param {Object} table The table to filter. 10617 */ 10618 dwv.html.filterTable = function(term, table) { 10619 // de-highlight 10620 dwv.html.dehighlight(table); 10621 // split search terms 10622 var terms = term.value.toLowerCase().split(" "); 10623 10624 // search 10625 var text = 0; 10626 var display = 0; 10627 for (var r = 1; r < table.rows.length; ++r) { 10628 display = ''; 10629 for (var i = 0; i < terms.length; ++i) { 10630 text = table.rows[r].innerHTML.replace(/<[^>]+>/g, "").toLowerCase(); 10631 if (text.indexOf(terms[i]) < 0) { 10632 display = 'none'; 10633 } else { 10634 if (terms[i].length) { 10635 dwv.html.highlight(terms[i], table.rows[r]); 10636 } 10637 } 10638 table.rows[r].style.display = display; 10639 } 10640 } 10641 }; 10642 10643 /** 10644 * Transform back each 10645 * 'preText <span class="highlighted">term</span> postText' 10646 * into its original 'preText term postText'. 10647 * @param {Object} container The container to de-highlight. 10648 */ 10649 dwv.html.dehighlight = function(container) { 10650 for (var i = 0; i < container.childNodes.length; i++) { 10651 var node = container.childNodes[i];
10652 10653 if (node.attributes && 10654 node.attributes['class'] && 10655 node.attributes['class'].value === 'highlighted') { 10656 node.parentNode.parentNode.replaceChild( 10657 document.createTextNode( 10658 node.parentNode.innerHTML.replace(/<[^>]+>/g, "")), 10659 node.parentNode); 10660 // Stop here and process next parent 10661 return; 10662 } else if (node.nodeType !== 3) { 10663 // Keep going onto other elements 10664 dwv.html.dehighlight(node); 10665 } 10666 } 10667 }; 10668 10669 /** 10670 * Create a 10671 * 'preText <span class="highlighted">term</span> postText' 10672 * around each search term. 10673 * @param {String} term The term to highlight. 10674 * @param {Object} container The container where to highlight the term. 10675 */ 10676 dwv.html.highlight = function(term, container) { 10677 for (var i = 0; i < container.childNodes.length; i++) { 10678 var node = container.childNodes[i]; 10679 10680 if (node.nodeType === 3) { 10681 // Text node 10682 var data = node.data; 10683 var data_low = data.toLowerCase(); 10684 if (data_low.indexOf(term) >= 0) { 10685 //term found! 10686 var new_node = document.createElement('span'); 10687 node.parentNode.replaceChild(new_node, node); 10688 10689 var result; 10690 while ((result = data_low.indexOf(term)) !== -1) { 10691 // before term 10692 new_node.appendChild(document.createTextNode( 10693 data.substr(0, result))); 10694 // term 10695 new_node.appendChild(dwv.html.createHighlightNode( 10696 document.createTextNode(data.substr( 10697 result, term.length)))); 10698 // reduce search string 10699 data = data.substr(result + term.length); 10700 data_low = data_low.substr(result + term.length); 10701 } 10702 new_node.appendChild(document.createTextNode(data)); 10703 } 10704 } else { 10705 // Keep going onto other elements 10706 dwv.html.highlight(term, node); 10707 } 10708 } 10709 }; 10710 10711 /** 10712 * Highlight a HTML node. 10713 * @param {Object} child The child to highlight. 10714 * @return {Object} The created HTML node. 10715 */ 10716 dwv.html.createHighlightNode = function(child) { 10717 var node = document.createElement('span'); 10718 node.setAttribute('class', 'highlighted'); 10719 node.attributes['class'].value = 'highlighted'; 10720 node.appendChild(child); 10721 return node; 10722 }; 10723 10724 /** 10725 * Remove all children of a HTML node. 10726 * @param {Object} node The node to remove kids. 10727 */ 10728 dwv.html.cleanNode = function(node) { 10729 // remove its children if node exists 10730 if (!node) { 10731 return; 10732 } 10733 while (node.hasChildNodes()) { 10734 node.removeChild(node.firstChild); 10735 } 10736 }; 10737 10738 /** 10739 * Remove a HTML node and all its children. 10740 * @param {String} nodeId The string id of the node to delete. 10741 */ 10742 dwv.html.removeNode = function(node) { 10743 // check node 10744 if (!node) { 10745 return; 10746 } 10747 // remove its children 10748 dwv.html.cleanNode(node); 10749 // remove it from its parent 10750 var top = node.parentNode; 10751 top.removeChild(node); 10752 }; 10753 10754 /** 10755 * Remove a list of HTML nodes and all their children. 10756 * @param {Array} nodes The list of nodes to delete. 10757 */ 10758 dwv.html.removeNodes = function(nodes) { 10759 for (var i = 0; i < nodes.length; ++i) { 10760 dwv.html.removeNode(nodes[i]); 10761 } 10762 }; 10763 10764 /** 10765 * Translate the content of an HTML row. 10766 * @param {Object} row The HTML row to parse. 10767 * @param {String} i18nPrefix The i18n prefix to use to find the translation. 10768 */ 10769 dwv.html.translateTableRow = function(row, i18nPrefix) { 10770 var prefix = (typeof i18nPrefix === "undefined") ? "basics" : i18nPrefix; 10771 if (prefix.length !== 0) { 10772 prefix += "."; 10773 } 10774 var cells = row.cells; 10775 for (var c = 0; c < cells.length; ++c) { 10776 var text = cells[c].firstChild.data; 10777 cells[c].firstChild.data = dwv.i18n(prefix + text); 10778 } 10779 }; 10780 10781 /** 10782 * Translate the content of an HTML column. 10783 * @param {Object} table The HTML table to parse. 10784 * @param {Number} columnNumber The number of the column to translate. 10785 * @param {String} i18nPrefix The i18n prefix to use to find the translation. 10786 * @param {String} i18nSuffix The i18n suffix to use to find the translation. 10787 */ 10788 dwv.html.translateTableColumn = function(table, columnNumber, i18nPrefix, i18nSuffix) { 10789 var prefix = (typeof i18nPrefix === "undefined") ? "basics" : i18nPrefix; 10790 if (prefix.length !== 0) { 10791 prefix += "."; 10792 } 10793 var suffix = (typeof i18nSuffix === "undefined") ? "" : i18nSuffix; 10794 if (suffix.length !== 0) { 10795 suffix = "." + suffix; 10796 } 10797 if (table.rows.length !== 0) { 10798 for (var r = 1; r < table.rows.length; ++r) { 10799 var cells = table.rows.item(r).cells; 10800 if (cells.length >= columnNumber) { 10801 var text = cells[columnNumber].firstChild.data; 10802 cells[columnNumber].firstChild.data = dwv.i18n(prefix + text + suffix); 10803 } 10804 } 10805 } 10806 }; 10807 10808 /** 10809 * Make a HTML table cell editable by putting its content inside an input element. 10810 * @param {Object} cell The cell to make editable. 10811 * @param {Function} onchange The callback to call when cell's content is changed. 10812 * if set to null, the HTML input will be disabled. 10813 * @param {String} inputType The type of the HTML input, default to 'text'. 10814 */ 10815 dwv.html.makeCellEditable = function(cell, onchange, inputType) { 10816 // check event 10817 if (typeof cell === "undefined") { 10818 console.warn("Cannot create input for non existing cell."); 10819 return; 10820 } 10821 // HTML input 10822 var input = document.createElement("input"); 10823 // handle change 10824 if (onchange) { 10825 input.onchange = onchange; 10826 } else { 10827 input.disabled = true; 10828 } 10829 // set input value 10830 input.value = cell.firstChild.data; 10831 // input type 10832 if (typeof inputType === "undefined" || 10833 (inputType === "color" && !dwv.browser.hasInputColor())) { 10834 input.type = "text"; 10835 } else { 10836 input.type = inputType; 10837 } 10838 10839 // clean cell 10840 dwv.html.cleanNode(cell); 10841 10842 // HTML form 10843 var form = document.createElement("form"); 10844 form.onsubmit = function(event) { 10845 event.preventDefault(); 10846 }; 10847 form.appendChild(input); 10848 // add form to cell 10849 cell.appendChild(form); 10850 }; 10851 10852 /** 10853 * Set the document cursor to 'pointer'. 10854 */ 10855 dwv.html.setCursorToPointer = function() { 10856 document.body.style.cursor = 'pointer'; 10857 }; 10858 10859 /** 10860 * Set the document cursor to 'default'. 10861 */ 10862 dwv.html.setCursorToDefault = function() { 10863 document.body.style.cursor = 'default'; 10864 }; 10865 10866 10867 /** 10868 * Create a HTML select from an input array of options. 10869 * The values of the options are the name of the option made lower case. 10870 * It is left to the user to set the 'onchange' method of the select. 10871 * @param {String} name The name of the HTML select. 10872 * @param {Mixed} list The list of options of the HTML select. 10873 * @param {String} i18nPrefix An optional namespace prefix to find the translation values. 10874 * @param {Bool} i18nSafe An optional flag to check translation existence. 10875 * @return {Object} The created HTML select. 10876 */ 10877 dwv.html.createHtmlSelect = function(name, list, i18nPrefix, i18nSafe) { 10878 // select 10879 var select = document.createElement("select"); 10880 //select.name = name; 10881 select.className = name; 10882 var prefix = (typeof i18nPrefix === "undefined") ? "" : i18nPrefix + "."; 10883 var safe = (typeof i18nSafe === "undefined") ? false : true; 10884 var getText = function(value) { 10885 var key = prefix + value + ".name"; 10886 var text = ""; 10887 if (safe) { 10888 if (dwv.i18nExists(key)) { 10889 text = dwv.i18n(key); 10890 } else { 10891 text = value; 10892 } 10893 } else { 10894 text = dwv.i18n(key); 10895 } 10896 return text; 10897 }; 10898 // options 10899 var option; 10900 if (list instanceof Array) { 10901 for (var i in list) { 10902 if (list.hasOwnProperty(i)) { 10903 option = document.createElement("option"); 10904 option.value = list[i]; 10905 option.appendChild(document.createTextNode(getText(list[i]))); 10906 select.appendChild(option); 10907 } 10908 } 10909 } else if (typeof list === 'object') { 10910 for (var item in list) { 10911 option = document.createElement("option"); 10912 option.value = item; 10913 option.appendChild(document.createTextNode(getText(item))); 10914 select.appendChild(option); 10915 } 10916 } else { 10917 throw new Error("Unsupported input list type."); 10918 } 10919 return select; 10920 }; 10921 10922 /**
10923 * Display or not an element. 10924 * @param {Object} element The HTML element to display. 10925 * @param {Boolean} flag True to display the element. 10926 */ 10927 dwv.html.displayElement = function(element, flag) { 10928 element.style.display = flag ? "" : "none"; 10929 }; 10930 10931 /** 10932 * Toggle the display of an element. 10933 * @param {Object} element The HTML element to display. 10934 */ 10935 dwv.html.toggleDisplay = function(element) { 10936 if (element.style.display === "none") { 10937 element.style.display = ''; 10938 } else { 10939 element.style.display = "none"; 10940 } 10941 }; 10942 10943 /** 10944 * Append an element. 10945 * @param {Object} parent The HTML element to append to. 10946 * @param {Object} element The HTML element to append. 10947 */ 10948 dwv.html.appendElement = function(parent, element) { 10949 // append 10950 parent.appendChild(element); 10951 // refresh 10952 dwv.gui.refreshElement(parent); 10953 }; 10954 10955 /** 10956 * Create an element. 10957 * @param {String} type The type of the elemnt. 10958 * @param {String} className The className of the element. 10959 */ 10960 dwv.html.createHiddenElement = function(type, className) { 10961 var element = document.createElement(type); 10962 element.className = className; 10963 // hide by default 10964 element.style.display = "none"; 10965 // return 10966 return element; 10967 }; 10968 10969 // namespaces 10970 var dwv = dwv || {}; 10971 dwv.gui = dwv.gui || {}; 10972 dwv.gui.base = dwv.gui.base || {}; 10973 dwv.gui.info = dwv.gui.info || {}; 10974 10975 /** 10976 * Plot some data in a given div. 10977 * @param {Object} div The HTML element to add WindowLevel info to. 10978 * @param {Array} data The data array to plot. 10979 * @param {Object} options Plot options. 10980 */ 10981 dwv.gui.base.plot = function( /*div, data, options*/ ) { 10982 // default does nothing... 10983 }; 10984 10985 /** 10986 * MiniColourMap info layer. 10987 * @constructor 10988 * @param {Object} div The HTML element to add colourMap info to. 10989 * @param {Object} app The associated application. 10990 */ 10991 dwv.gui.info.MiniColourMap = function(div, app) { 10992 /** 10993 * Create the mini colour map info div. 10994 */ 10995 this.create = function() { 10996 // clean div 10997 var elems = div.getElementsByClassName("colour-map-info"); 10998 if (elems.length !== 0) { 10999 dwv.html.removeNodes(elems); 11000 } 11001 // colour map 11002 var canvas = document.createElement("canvas"); 11003 canvas.className = "colour-map-info"; 11004 canvas.width = 98; 11005 canvas.height = 10; 11006 // add canvas to div 11007 div.appendChild(canvas); 11008 }; 11009 11010 /** 11011 * Update the mini colour map info div. 11012 * @param {Object} event The windowing change event containing the new values. 11013 * Warning: expects the mini colour map div to exist (use after createMiniColourMap). 11014 */ 11015 this.update = function(event) { 11016 var windowCenter = event.wc; 11017 var windowWidth = event.ww; 11018 // retrieve canvas and context 11019 var canvas = div.getElementsByClassName("colour-map-info")[0]; 11020 var context = canvas.getContext('2d'); 11021 // fill in the image data 11022 var colourMap = app.getViewController().getColourMap(); 11023 var imageData = context.getImageData(0, 0, canvas.width, canvas.height); 11024 // histogram sampling 11025 var c = 0; 11026 var minInt = app.getImage().getRescaledDataRange().min; 11027 var range = app.getImage().getRescaledDataRange().max - minInt; 11028 var incrC = range / canvas.width; 11029 // Y scale 11030 var y = 0; 11031 var yMax = 255; 11032 var yMin = 0; 11033 // X scale 11034 var xMin = windowCenter - 0.5 - (windowWidth - 1) / 2; 11035 var xMax = windowCenter - 0.5 + (windowWidth - 1) / 2; 11036 // loop through values 11037 var index; 11038 for (var j = 0; j < canvas.height; ++j) { 11039 c = minInt; 11040 for (var i = 0; i < canvas.width; ++i) { 11041 if (c <= xMin) { 11042 y = yMin; 11043 } else if (c > xMax) { 11044 y = yMax; 11045 } else { 11046 y = ((c - (windowCenter - 0.5)) / (windowWidth - 1) + 0.5) * 11047 (yMax - yMin) + yMin; 11048 y = parseInt(y, 10); 11049 } 11050 index = (i + j * canvas.width) * 4; 11051 imageData.data[index] = colourMap.red[y]; 11052 imageData.data[index + 1] = colourMap.green[y]; 11053 imageData.data[index + 2] = colourMap.blue[y]; 11054 imageData.data[index + 3] = 0xff; 11055 c += incrC; 11056 } 11057 } 11058 // put the image data in the context 11059 context.putImageData(imageData, 0, 0); 11060 }; 11061 }; // class dwv.gui.info.MiniColourMap 11062 11063 11064 /** 11065 * Plot info layer. 11066 * @constructor 11067 * @param {Object} div The HTML element to add colourMap info to. 11068 * @param {Object} app The associated application. 11069 */ 11070 dwv.gui.info.Plot = function(div, app) { 11071 /** 11072 * Create the plot info. 11073 */ 11074 this.create = function() { 11075 // clean div 11076 if (div) { 11077 dwv.html.cleanNode(div); 11078 } 11079 // plot 11080 dwv.gui.plot(div, app.getImage().getHistogram()); 11081 }; 11082 11083 /** 11084 * Update plot. 11085 * @param {Object} event The windowing change event containing the new values. 11086 * Warning: expects the plot to exist (use after createPlot). 11087 */ 11088 this.update = function(event) { 11089 var wc = event.wc; 11090 var ww = event.ww; 11091 11092 var half = parseInt((ww - 1) / 2, 10); 11093 var center = parseInt((wc - 0.5), 10); 11094 var min = center - half; 11095 var max = center + half; 11096 11097 var markings = [ 11098 { "color": "#faa", "lineWidth": 1, "xaxis": { "from": min, "to": min } }, 11099 { "color": "#aaf", "lineWidth": 1, "xaxis": { "from": max, "to": max } } 11100 ]; 11101 11102 // plot 11103 dwv.gui.plot(div, app.getImage().getHistogram(), { markings: markings }); 11104 }; 11105 11106 }; // class dwv.gui.info.Plot 11107 11108 /** 11109 * DICOM Header overlay info layer. 11110 * @constructor 11111 * @param {Object} div The HTML element to add Header overlay info to. 11112 * @param {String} pos The string to specify the corner position. (tl,tc,tr,cl,cr,bl,bc,br) 11113 */ 11114 dwv.gui.info.Overlay = function(div, pos, app) { 11115 // closure to self 11116 var self = this; 11117 11118 /** 11119 * Get the overlay array of the current position. 11120 * @return {Array} The overlay information. 11121 */ 11122 this.getOverlays = function() { 11123 var image = app.getImage(); 11124 if (!image) { 11125 return; 11126 } 11127 var allOverlays = image.getOverlays(); 11128 if (!allOverlays) { 11129 return; 11130 } 11131 var position = app.getViewController().getCurrentPosition(); 11132 var sliceOverlays = allOverlays[position.k]; 11133 if (!sliceOverlays) { 11134 return; 11135 } 11136 return sliceOverlays[pos]; 11137 }; 11138 11139 /** 11140 * Create the overlay info div. 11141 */ 11142 this.create = function() { 11143 // remove all <ul> elements from div 11144 dwv.html.cleanNode(div); 11145 11146 // get overlay string array of the current position 11147 var overlays = self.getOverlays(); 11148 if (!overlays) { 11149 return; 11150 } 11151 11152 if (pos === "bc" || pos === "tc" || 11153 pos === "cr" || pos === "cl") { 11154 div.textContent = overlays[0].value; 11155 } else { 11156 // create <ul> element 11157 var ul = document.createElement("ul"); 11158 11159 for (var n = 0; overlays[n]; n++) { 11160 var li; 11161 if (overlays[n].value === "window-center") { 11162 li = document.createElement("li"); 11163 li.className = "info-" + pos + "-window-center"; 11164 ul.appendChild(li); 11165 } else if (overlays[n].value === "window-width") { 11166 li = document.createElement("li"); 11167 li.className = "info-" + pos + "-window-width"; 11168 ul.appendChild(li); 11169 } else if (overlays[n].value === "zoom") { 11170 li = document.createElement("li"); 11171 li.className = "info-" + pos + "-zoom"; 11172 ul.appendChild(li); 11173 } else if (overlays[n].value === "offset") { 11174 li = document.createElement("li"); 11175 li.className = "info-" + pos + "-offset"; 11176 ul.appendChild(li); 11177 } else if (overlays[n].value === "value") { 11178 li = document.createElement("li"); 11179 li.className = "info-" + pos + "-value"; 11180 ul.appendChild(li); 11181 } else if (overlays[n].value === "position") { 11182 li = document.createElement("li"); 11183 li.className = "info-" + pos + "-position"; 11184 ul.appendChild(li); 11185 } else if (overlays[n].value === "frame") { 11186 li = document.createElement("li"); 11187 li.className = "info-" + pos + "-frame"; 11188 ul.appendChild(li); 11189 } else { 11190 li = document.createElement("li"); 11191 li.className = "info-" + pos + "-" + n; 11192 li.appendChild(document.createTextNode(overlays[n].value)); 11193 ul.appendChild(li); 11194 } 11195 } 11196 11197 // append <ul> element before color map 11198 div.appendChild(ul); 11199 } 11200 }; 11201 11202 /** 11203 * Update the overlay info div. 11204 * @param {Object} event A change event. 11205 */ 11206 this.update = function(event) { 11207 // get overlay string array of the current position 11208 var overlays = self.getOverlays(); 11209 if (!overlays) { 11210 return; 11211 } 11212 11213 if (pos === "bc" || pos === "tc" || 11214 pos === "cr" || pos === "cl") { 11215 div.textContent = overlays[0].value; 11216 } else { 11217 var li; 11218 var n; 11219 11220 for (n = 0; overlays[n]; n++) { 11221 if (overlays[n].value === "window-center") {
11222 if (event.type === "wl-center-change") { 11223 li = div.getElementsByClassName("info-" + pos + "-window-center")[0]; 11224 dwv.html.cleanNode(li); 11225 var wcStr = dwv.utils.replaceFlags2(overlays[n].format, [Math.round(event.wc)]); 11226 if (li) { 11227 li.appendChild(document.createTextNode(wcStr)); 11228 } 11229 } 11230 } else if (overlays[n].value === "window-width") { 11231 if (event.type === "wl-width-change") { 11232 li = div.getElementsByClassName("info-" + pos + "-window-width")[0]; 11233 dwv.html.cleanNode(li); 11234 var wwStr = dwv.utils.replaceFlags2(overlays[n].format, [Math.round(event.ww)]); 11235 if (li) { 11236 li.appendChild(document.createTextNode(wwStr)); 11237 } 11238 } 11239 } else if (overlays[n].value === "zoom") { 11240 if (event.type === "zoom-change") { 11241 li = div.getElementsByClassName("info-" + pos + "-zoom")[0]; 11242 dwv.html.cleanNode(li); 11243 var zoom = Number(event.scale).toPrecision(3); 11244 var zoomStr = dwv.utils.replaceFlags2(overlays[n].format, [zoom]); 11245 if (li) { 11246 li.appendChild(document.createTextNode(zoomStr)); 11247 } 11248 } 11249 } else if (overlays[n].value === "offset") { 11250 if (event.type === "zoom-change") { 11251 li = div.getElementsByClassName("info-" + pos + "-offset")[0]; 11252 dwv.html.cleanNode(li); 11253 var offset = [Number(event.cx).toPrecision(3), 11254 Number(event.cy).toPrecision(3) 11255 ]; 11256 var offStr = dwv.utils.replaceFlags2(overlays[n].format, offset); 11257 if (li) { 11258 li.appendChild(document.createTextNode(offStr)); 11259 } 11260 } 11261 } else if (overlays[n].value === "value") { 11262 if (event.type === "position-change") { 11263 li = div.getElementsByClassName("info-" + pos + "-value")[0]; 11264 dwv.html.cleanNode(li); 11265 var valueStr = dwv.utils.replaceFlags2(overlays[n].format, [event.value]); 11266 if (li) { 11267 li.appendChild(document.createTextNode(valueStr)); 11268 } 11269 } 11270 } else if (overlays[n].value === "position") { 11271 if (event.type === "position-change") { 11272 li = div.getElementsByClassName("info-" + pos + "-position")[0]; 11273 dwv.html.cleanNode(li); 11274 var posStr = dwv.utils.replaceFlags2(overlays[n].format, [event.i, event.j, event.k]); 11275 if (li) { 11276 li.appendChild(document.createTextNode(posStr)); 11277 } 11278 } 11279 } else if (overlays[n].value === "frame") { 11280 if (event.type === "frame-change") { 11281 li = div.getElementsByClassName("info-" + pos + "-frame")[0]; 11282 dwv.html.cleanNode(li); 11283 var frameStr = dwv.utils.replaceFlags2(overlays[n].format, [event.frame]); 11284 if (li) { 11285 li.appendChild(document.createTextNode(frameStr)); 11286 } 11287 } 11288 } else { 11289 if (event.type === "position-change") { 11290 li = div.getElementsByClassName("info-"
11290+ pos + "-" + n)[0]; 11291 dwv.html.cleanNode(li); 11292 if (li) { 11293 li.appendChild(document.createTextNode(overlays[n].value)); 11294 } 11295 } 11296 } 11297 } 11298 } 11299 }; 11300 }; // class dwv.gui.info.Overlay 11301 11302 /** 11303 * Create overlay string array of the image in each corner 11304 * @param {Object} dicomElements DICOM elements of the image 11305 * @return {Array} Array of string to be shown in each corner 11306 */ 11307 dwv.gui.info.createOverlays = function(dicomElements) { 11308 var overlays = {}; 11309 var modality = dicomElements.getFromKey("x00080060"); 11310 if (!modality) { 11311 return overlays; 11312 } 11313 11314 var omaps = dwv.gui.info.overlayMaps; 11315 if (!omaps) { 11316 return overlays; 11317 } 11318 var omap = omaps[modality] || omaps['*']; 11319 11320 for (var n = 0; omap[n]; n++) { 11321 var value = omap[n].value; 11322 var tags = omap[n].tags; 11323 var format = omap[n].format; 11324 var pos = omap[n].pos; 11325 11326 if (typeof tags !== "undefined" && tags.length !== 0) { 11327 // get values 11328 var values = []; 11329 for (var i = 0; i < tags.length; ++i) { 11330 values.push(dicomElements.getElementValueAsStringFromKey(tags[i])); 11331 } 11332 // format 11333 if (typeof format === "undefined" || format === null) { 11334 format = dwv.utils.createDefaultReplaceFormat(values); 11335 } 11336 value = dwv.utils.replaceFlags2(format, values); 11337 } 11338 11339 if (!value || value.length === 0) { 11340 continue; 11341 } 11342 11343 // add value to overlays 11344 if (!overlays[pos]) { 11345 overlays[pos] = []; 11346 } 11347 overlays[pos].push({ 'value': value.trim(), 'format': format }); 11348 } 11349 11350 // (0020,0020) Patient Orientation 11351 var valuePO = dicomElements.getFromKey("x00200020"); 11352 if (typeof valuePO !== "undefined" && valuePO !== null && valuePO.length == 2) { 11353 var po0 = dwv.dicom.cleanString(valuePO[0]); 11354 var po1 = dwv.dicom.cleanString(valuePO[1]); 11355 overlays.cr = [{ 'value': po0 }]; 11356 overlays.cl = [{ 'value': dwv.dicom.getReverseOrientation(po0) }]; 11357 overlays.bc = [{ 'value': po1 }]; 11358 overlays.tc = [{ 'value': dwv.dicom.getReverseOrientation(po1) }]; 11359 } 11360 11361 return overlays; 11362 }; 11363 11364 /** 11365 * Create overlay string array of the image in each corner 11366 * @param {Object} dicomElements DICOM elements of the image 11367 * @return {Array} Array of string to be shown in each corner 11368 */ 11369 dwv.gui.info.createOverlaysForDom = function(info) { 11370 var overlays = {}; 11371 var omaps = dwv.gui.info.overlayMaps; 11372 if (!omaps) { 11373 return overlays; 11374 } 11375 var omap = omaps.DOM; 11376 if (!omap) { 11377 return overlays; 11378 } 11379 11380 for (var n = 0; omap[n]; n++) { 11381 var value = omap[n].value; 11382 var tags = omap[n].tags; 11383 var format = omap[n].format; 11384 var pos = omap[n].pos; 11385 11386 if (typeof tags !== "undefined" && tags.length !== 0) { 11387 // get values 11388 var values = []; 11389 for (var i = 0; i < tags.length; ++i) { 11390 for (var j = 0; j < info.length; ++j) { 11391 if (tags[i] === info[j].name) { 11392 values.push(info[j].value); 11393 } 11394 } 11395 } 11396 // format 11397 if (typeof format === "undefined" || format === null) { 11398 format = dwv.utils.createDefaultReplaceFormat(values); 11399 } 11400 value = dwv.utils.replaceFlags2(format, values); 11401 } 11402 11403 if (!value || value.length === 0) { 11404 continue; 11405 } 11406 11407 // add value to overlays 11408 if (!overlays[pos]) { 11409 overlays[pos] = []; 11410 } 11411 overlays[pos].push({ 'value': value.trim(), 'format': format }); 11412 } 11413 11414 return overlays; 11415 }; 11416 11417 // namespaces 11418 var dwv = dwv || {}; 11419 dwv.html = dwv.html || {}; 11420 11421 /** 11422 * Window layer. 11423 * @constructor 11424 * @param {String} name The name of the layer. 11425 */ 11426 dwv.html.Layer = function(canvas) { 11427 /** 11428 * A cache of the initial canvas. 11429 * @private 11430 * @type Object 11431 */ 11432 var cacheCanvas = null; 11433 /** 11434 * The associated CanvasRenderingContext2D. 11435 * @private 11436 * @type Object 11437 */ 11438 var context = null; 11439 11440 /** 11441 * Get the layer canvas. 11442 * @return {Object} The layer canvas. 11443 */ 11444 this.getCanvas = function() { return canvas; }; 11445 /** 11446 * Get the layer context. 11447 * @return {Object} The layer context. 11448 */ 11449 this.getContext = function() { return context; }; 11450 /** 11451 * Get the layer offset on page. 11452 * @return {Number} The layer offset on page. 11453 */ 11454 this.getOffset = function() { return canvas.offset(); }; 11455 11456 /** 11457 * The image data array. 11458 * @private 11459 * @type Array 11460 */ 11461 var imageData = null; 11462 11463 /** 11464 * The layer origin. 11465 * @private 11466 * @type {Object} 11467 */ 11468 var origin = { 'x': 0, 'y': 0 }; 11469 /** 11470 * Get the layer origin. 11471 * @return {Object} The layer origin as {'x','y'}. 11472 */ 11473 this.getOrigin = function() { 11474 return origin; 11475 }; 11476 /** 11477 * The layer zoom. 11478 * @private 11479 * @type {Object} 11480 */ 11481 var zoom = { 'x': 1, 'y': 1 }; 11482 /** 11483 * Get the layer zoom. 11484 * @return {Object} The layer zoom as {'x','y'}. 11485 */ 11486 this.getZoom = function() { 11487 return zoom; 11488 }; 11489 11490 /** 11491 * The layer translation. 11492 * @private 11493 * @type {Object} 11494 */ 11495 var trans = { 'x': 0, 'y': 0 }; 11496 /** 11497 * Get the layer translation. 11498 * @return {Object} The layer translation as {'x','y'}. 11499 */ 11500 this.getTrans = function() { 11501 return trans; 11502 }; 11503 11504 /** 11505 * Set the canvas width. 11506 * @param {Number} width The new width. 11507 */ 11508 this.setWidth = function(width) { 11509 canvas.width = width; 11510 }; 11511 /** 11512 * Set the canvas height. 11513 * @param {Number} height The new height. 11514 */ 11515 this.setHeight = function(height) { 11516 canvas.height = height; 11517 }; 11518 11519 /** 11520 * Set the layer zoom. 11521 * @param {Number} newZoomX The zoom in the X direction. 11522 * @param {Number} newZoomY The zoom in the Y direction. 11523 * @param {Number} centerX The zoom center in the X direction. 11524 * @param {Number} centerY The zoom center in the Y direction. 11525 */
11526 this.zoom = function(newZoomX, newZoomY, centerX, centerY) { 11527 // The zoom is the ratio between the differences from the center 11528 // to the origins: 11529 // centerX - originX = ( centerX - originX0 ) * zoomX 11530 // (center in ~world coordinate system) 11531 //originX = (centerX / zoomX) + originX - (centerX / newZoomX); 11532 //originY = (centerY / zoomY) + originY - (centerY / newZoomY); 11533 11534 // center in image coordinate system 11535 origin.x = centerX - (centerX - origin.x) * (newZoomX / zoom.x); 11536 origin.y = centerY - (centerY - origin.y) * (newZoomY / zoom.y); 11537 11538 // save zoom 11539 zoom.x = newZoomX; 11540 zoom.y = newZoomY; 11541 }; 11542 11543 /** 11544 * Set the layer translation. 11545 * Translation is according to the last one. 11546 * @param {Number} tx The translation in the X direction. 11547 * @param {Number} ty The translation in the Y direction. 11548 */ 11549 this.translate = function(tx, ty) { 11550 trans.x = tx; 11551 trans.y = ty; 11552 }; 11553 11554 /** 11555 * Set the image data array. 11556 * @param {Array} data The data array. 11557 */ 11558 this.setImageData = function(data) { 11559 imageData = data; 11560 // update the cached canvas 11561 cacheCanvas.getContext("2d").putImageData(imageData, 0, 0); 11562 }; 11563 11564 /** 11565 * Reset the layout. 11566 */ 11567 this.resetLayout = function(izoom) { 11568 origin.x = 0; 11569 origin.y = 0; 11570 zoom.x = izoom; 11571 zoom.y = izoom; 11572 trans.x = 0; 11573 trans.y = 0; 11574 }; 11575 11576 /** 11577 * Transform a display position to an index. 11578 */ 11579 this.displayToIndex = function(point2D) { 11580 return { 11581 'x': ((point2D.x - origin.x) / zoom.x) - trans.x, 11582 'y': ((point2D.y - origin.y) / zoom.y) - trans.y 11583 }; 11584 }; 11585 11586 /** 11587 * Draw the content (imageData) of the layer. 11588 * The imageData variable needs to be set 11589 */ 11590 this.draw = function() { 11591 // clear the context: reset the transform first 11592 // store the current transformation matrix 11593 context.save(); 11594 // use the identity matrix while clearing the canvas 11595 context.setTransform(1, 0, 0, 1, 0, 0); 11596 context.clearRect(0, 0, canvas.width, canvas.height); 11597 // restore the transform 11598 context.restore(); 11599 11600 // draw the cached canvas on the context 11601 // transform takes as input a, b, c, d, e, f to create 11602 // the transform matrix (column-major order): 11603 // [ a c e ] 11604 // [ b d f ] 11605 // [ 0 0 1 ] 11606 context.setTransform(zoom.x, 0, 0, zoom.y, 11607 origin.x + (trans.x * zoom.x), 11608 origin.y + (trans.y * zoom.y)); 11609 context.drawImage(cacheCanvas, 0, 0); 11610 }; 11611 11612 /** 11613 * Initialise the layer: set the canvas and context 11614 * @input {Number} inputWidth The width of the canvas. 11615 * @input {Number} inputHeight The height of the canvas. 11616 */ 11617 this.initialise = function(inputWidth, inputHeight) { 11618 // find the canvas element 11619 //canvas = document.getElementById(name); 11620 //if (!canvas) 11621 //{ 11622 // alert("Error: cannot find the canvas element for '" + name + "'."); 11623 // return; 11624 //} 11625 // check that the getContext method exists 11626 if (!canvas.getContext) { 11627 alert("Error: no canvas.getContext method."); 11628 return; 11629 } 11630 // get the 2D context 11631 context = canvas.getContext('2d'); 11632 if (!context) { 11633 alert("Error: failed to get the 2D context."); 11634 return; 11635 } 11636 // canvas sizes 11637 canvas.width = inputWidth; 11638 canvas.height = inputHeight; 11639 // original empty image data array 11640 context.clearRect(0, 0, canvas.width, canvas.height); 11641 imageData = context.getImageData(0, 0, canvas.width, canvas.height); 11642 // cached canvas 11643 cacheCanvas = document.createElement("canvas"); 11644 cacheCanvas.width = inputWidth; 11645 cacheCanvas.height = inputHeight; 11646 }; 11647 11648 /** 11649 * Fill the full context with the current style. 11650 */ 11651 this.fillContext = function() { 11652 context.fillRect(0, 0, canvas.width, canvas.height); 11653 }; 11654 11655 /** 11656 * Clear the context and reset the image data. 11657 */ 11658 this.clear = function() { 11659 context.clearRect(0, 0, canvas.width, canvas.height); 11660 imageData = context.getImageData(0, 0, canvas.width, canvas.height); 11661 this.resetLayout(); 11662 }; 11663 11664 /** 11665 * Merge two layers. 11666 * @input {Layer} layerToMerge The layer to merge. It will also be emptied. 11667 */ 11668 this.merge = function(layerToMerge) { 11669 // basic resampling of the merge data to put it at zoom 1:1 11670 var mergeImageData = layerToMerge.getContext().getImageData( 11671 0, 0, canvas.width, canvas.height); 11672 var offMerge = 0; 11673 var offMergeJ = 0; 11674 var offThis = 0; 11675 var offThisJ = 0; 11676 var alpha = 0; 11677 for (var j = 0; j < canvas.height; ++j) { 11678 offMergeJ = parseInt((origin.y + j * zoom.y), 10) * canvas.width; 11679 offThisJ = j * canvas.width; 11680 for (var i = 0; i < canvas.width; ++i) { 11681 // 4 component data: RGB + alpha 11682 offMerge = 4 * (parseInt((origin.x + i * zoom.x), 10) + offMergeJ); 11683 offThis = 4 * (i + offThisJ); 11684 // merge non transparent 11685 alpha = mergeImageData.data[offMerge + 3]; 11686 if (alpha !== 0) { 11687 imageData.data[offThis] = mergeImageData.data[offMerge]; 11688 imageData.data[offThis + 1] = mergeImageData.data[offMerge + 1]; 11689 imageData.data[offThis + 2] = mergeImageData.data[offMerge + 2]; 11690 imageData.data[offThis + 3] = alpha; 11691 } 11692 } 11693 } 11694 // empty and reset merged layer 11695 layerToMerge.clear(); 11696 // draw the layer 11697 this.draw(); 11698 }; 11699 11700 /** 11701 * Set the line colour for the layer. 11702 * @input {String} colour The line colour. 11703 */ 11704 this.setLineColour = function(colour) { 11705 context.fillStyle = colour; 11706 context.strokeStyle = colour; 11707 }; 11708 11709 /**
11710 * Display the layer. 11711 * @input {Boolean} val Whether to display the layer or not. 11712 */ 11713 this.setStyleDisplay = function(val) { 11714 if (val === true) { 11715 canvas.style.display = ''; 11716 } else { 11717 canvas.style.display = "none"; 11718 } 11719 }; 11720 11721 /** 11722 * Check if the layer is visible. 11723 * @return {Boolean} True if the layer is visible. 11724 */ 11725 this.isVisible = function() { 11726 if (canvas.style.display === "none") { 11727 return false; 11728 } else { 11729 return true; 11730 } 11731 }; 11732 11733 /** 11734 * Align on another layer. 11735 * @param {Layer} rhs The layer to align on. 11736 */ 11737 this.align = function(rhs) { 11738 canvas.style.top = rhs.getCanvas().offsetTop; 11739 canvas.style.left = rhs.getCanvas().offsetLeft; 11740 }; 11741 }; // Layer class 11742 11743 /** 11744 * Get the offset of an input event. 11745 * @param {Object} event The event to get the offset from. 11746 * @return {Array} The array of offsets. 11747 */ 11748 dwv.html.getEventOffset = function(event) { 11749 var positions = []; 11750 var ex = 0; 11751 var ey = 0; 11752 if (event.targetTouches) { 11753 // get the touch offset from all its parents 11754 var offsetLeft = 0; 11755 var offsetTop = 0; 11756 var offsetParent = event.targetTouches[0].target.offsetParent; 11757 while (offsetParent) { 11758 if (!isNaN(offsetParent.offsetLeft)) { 11759 offsetLeft += offsetParent.offsetLeft; 11760 } 11761 if (!isNaN(offsetParent.offsetTop)) { 11762 offsetTop += offsetParent.offsetTop; 11763 } 11764 offsetParent = offsetParent.offsetParent; 11765 } 11766 // set its position 11767 var touch = null; 11768 for (var i = 0; i < event.targetTouches.length; ++i) { 11769 touch = event.targetTouches[i]; 11770 ex = touch.pageX - offsetLeft; 11771 ey = touch.pageY - offsetTop; 11772 positions.push({ 'x': ex, 'y': ey }); 11773 } 11774 } else { 11775 // layerX is used by Firefox 11776 ex = event.offsetX === undefined ? event.layerX : event.offsetX; 11777 ey = event.offsetY === undefined ? event.layerY : event.offsetY; 11778 positions.push({ 'x': ex, 'y': ey }); 11779 } 11780 return positions; 11781 }; 11782 11783 // namespaces 11784 var dwv = dwv || {}; 11785 dwv.gui = dwv.gui || {}; 11786 dwv.gui.base = dwv.gui.base || {}; 11787 11788 /** 11789 * Loadbox base gui. 11790 * @constructor 11791 */ 11792 dwv.gui.base.Loadbox = function(app, loaders) { 11793 /** 11794 * Loader HTML select. 11795 * @private 11796 */ 11797 var loaderSelector = null; 11798 11799 /** 11800 * Setup the loadbox HTML. 11801 */ 11802 this.setup = function() { 11803 // loader select 11804 loaderSelector = dwv.html.createHtmlSelect("loaderSelect", loaders, "io"); 11805 loaderSelector.onchange = app.onChangeLoader; 11806 11807 // node 11808 var node = app.getElement("loaderlist"); 11809 // clear it 11810 while (node.hasChildNodes()) { 11811 node.removeChild(node.firstChild); 11812 } 11813 // append 11814 node.appendChild(loaderSelector); 11815 // refresh 11816 dwv.gui.refreshElement(node); 11817 }; 11818 11819 /** 11820 * Display a loader. 11821 * @param {String} name The name of the loader to show. 11822 */ 11823 this.displayLoader = function(name) { 11824 var keys = Object.keys(loaders); 11825 for (var i = 0; i < keys.length; ++i) { 11826 if (keys[i] === name) { 11827 loaders[keys[i]].display(true); 11828 } else { 11829 loaders[keys[i]].display(false); 11830 } 11831 } 11832 }; 11833 11834 /** 11835 * Reset to its original state. 11836 */ 11837 this.reset = function() { 11838 // display first loader 11839 var keys = Object.keys(loaders); 11840 this.displayLoader(keys[0]); 11841 // reset HTML select 11842 if (loaderSelector) { 11843 loaderSelector.selectedIndex = 0; 11844 } 11845 }; 11846 11847 }; // class dwv.gui.base.Loadbox 11848 11849 /** 11850 * FileLoad base gui. 11851 * @constructor 11852 */ 11853 dwv.gui.base.FileLoad = function(app) { 11854 // closure to self 11855 var self = this; 11856 11857 /** 11858 * Internal file input change handler. 11859 * @param {Object} event The change event. 11860 */ 11861 function onchangeinternal(event) { 11862 if (typeof self.onchange === "function") { 11863 self.onchange(event); 11864 } 11865 app.onChangeFiles(event); 11866 } 11867 11868 /** 11869 * Setup the file load HTML to the page. 11870 */ 11871 this.setup = function() { 11872 // input 11873 var fileLoadInput = document.createElement("input"); 11874 fileLoadInput.onchange = onchangeinternal; 11875 fileLoadInput.type = "file"; 11876 fileLoadInput.multiple = true; 11877 fileLoadInput.className = "imagefiles"; 11878 fileLoadInput.setAttribute("data-clear-btn", "true"); 11879 fileLoadInput.setAttribute("data-mini", "true"); 11880 11881 // associated div 11882 var fileLoadDiv = document.createElement("div"); 11883 fileLoadDiv.className = "imagefilesdiv"; 11884 fileLoadDiv.style.display = "none"; 11885 fileLoadDiv.appendChild(fileLoadInput); 11886 11887 // node 11888 var node = app.getElement("loaderlist"); 11889 // append 11890 node.appendChild(fileLoadDiv); 11891 // refresh 11892 dwv.gui.refreshElement(node); 11893 }; 11894 11895 /**
11896 * Display the file load HTML. 11897 * @param {Boolean} bool True to display, false to hide. 11898 */ 11899 this.display = function(bool) { 11900 // file div element 11901 var node = app.getElement("loaderlist"); 11902 var filediv = node.getElementsByClassName("imagefilesdiv")[0]; 11903 filediv.style.display = bool ? "" : "none"; 11904 }; 11905 11906 }; // class dwv.gui.base.FileLoad 11907 11908 /** 11909 * FolderLoad base gui. 11910 * @constructor 11911 */ 11912 dwv.gui.base.FolderLoad = function(app) { 11913 // closure to self 11914 var self = this; 11915 11916 /** 11917 * Internal file input change handler. 11918 * @param {Object} event The change event. 11919 */ 11920 function onchangeinternal(event) { 11921 if (typeof self.onchange === "function") { 11922 self.onchange(event); 11923 } 11924 app.onChangeFiles(event); 11925 } 11926 11927 /** 11928 * Setup the file load HTML to the page. 11929 */ 11930 this.setup = function() { 11931 // input 11932 var fileLoadInput = document.createElement("input"); 11933 fileLoadInput.onchange = onchangeinternal; 11934 fileLoadInput.type = "file"; 11935 fileLoadInput.multiple = true; 11936 fileLoadInput.webkitdirectory = true; 11937 fileLoadInput.className = "imagefolder"; 11938 fileLoadInput.setAttribute("data-clear-btn", "true"); 11939 fileLoadInput.setAttribute("data-mini", "true"); 11940 11941 // associated div 11942 var folderLoadDiv = document.createElement("div"); 11943 folderLoadDiv.className = "imagefolderdiv"; 11944 folderLoadDiv.style.display = "none"; 11945 folderLoadDiv.appendChild(fileLoadInput); 11946 11947 // node 11948 var node = app.getElement("loaderlist"); 11949 // append 11950 node.appendChild(folderLoadDiv); 11951 // refresh 11952 dwv.gui.refreshElement(node); 11953 }; 11954 11955 /** 11956 * Display the folder load HTML. 11957 * @param {Boolean} bool True to display, false to hide. 11958 */ 11959 this.display = function(bool) { 11960 // file div element 11961 var node = app.getElement("loaderlist"); 11962 var folderdiv = node.getElementsByClassName("imagefolderdiv")[0]; 11963 folderdiv.style.display = bool ? "" : "none"; 11964 }; 11965 11966 }; // class dwv.gui.base.FileLoad 11967 11968 /** 11969 * UrlLoad base gui. 11970 * @constructor 11971 */ 11972 dwv.gui.base.UrlLoad = function(app) { 11973 // closure to self 11974 var self = this; 11975 11976 /** 11977 * Internal url input change handler. 11978 * @param {Object} event The change event. 11979 */ 11980 function onchangeinternal(event) { 11981 if (typeof self.onchange === "function") { 11982 self.onchange(event); 11983 } 11984 app.onChangeURL(event); 11985 } 11986 11987 /** 11988 * Setup the url load HTML to the page. 11989 */ 11990 this.setup = function() { 11991 // input 11992 var urlLoadInput = document.createElement("input"); 11993 urlLoadInput.onchange = onchangeinternal; 11994 urlLoadInput.type = "url"; 11995 urlLoadInput.className = "imageurl"; 11996 urlLoadInput.setAttribute("data-clear-btn", "true"); 11997 urlLoadInput.setAttribute("data-mini", "true"); 11998 11999 // associated div 12000 var urlLoadDiv = document.createElement("div"); 12001 urlLoadDiv.className = "imageurldiv"; 12002 urlLoadDiv.style.display = "none"; 12003 urlLoadDiv.appendChild(urlLoadInput); 12004 12005 // node 12006 var node = app.getElement("loaderlist"); 12007 // append 12008 node.appendChild(urlLoadDiv); 12009 // refresh 12010 dwv.gui.refreshElement(node); 12011 }; 12012 12013 /** 12014 * Display the url load HTML. 12015 * @param {Boolean} bool True to display, false to hide. 12016 */ 12017 this.display = function(bool) { 12018 // url div element 12019 var node = app.getElement("loaderlist"); 12020 var urldiv = node.getElementsByClassName("imageurldiv")[0]; 12021 urldiv.style.display = bool ? "" : "none"; 12022 }; 12023 12024 }; // class dwv.gui.base.UrlLoad 12025 12026 // namespaces 12027 var dwv = dwv || {}; 12028 dwv.html = dwv.html || {}; 12029 12030 /** 12031 * Style class. 12032 * @constructor 12033 */ 12034 dwv.html.Style = function() { 12035 /** 12036 * Font size. 12037 * @private 12038 * @type Number 12039 */ 12040 var fontSize = 12; 12041 /** 12042 * Font family. 12043 * @private
12044 * @type String 12045 */ 12046 var fontFamily = "Verdana"; 12047 /** 12048 * Text colour. 12049 * @private 12050 * @type String 12051 */ 12052 var textColour = "#fff"; 12053 /** 12054 * Line colour. 12055 * @private 12056 * @type String 12057 */ 12058 var lineColour = "#ffff80"; 12059 /** 12060 * Display scale. 12061 * @private 12062 * @type Number 12063 */ 12064 var displayScale = 1; 12065 /** 12066 * Stroke width. 12067 * @private 12068 * @type Number 12069 */ 12070 var strokeWidth = 2; 12071 12072 /** 12073 * Get the font family. 12074 * @return {String} The font family. 12075 */ 12076 this.getFontFamily = function() { return fontFamily; }; 12077 12078 /** 12079 * Get the font size. 12080 * @return {Number} The font size. 12081 */ 12082 this.getFontSize = function() { return fontSize; }; 12083 12084 /** 12085 * Get the stroke width. 12086 * @return {Number} The stroke width. 12087 */
12088 this.getStrokeWidth = function() { return strokeWidth; }; 12089 12090 /** 12091 * Get the text colour. 12092 * @return {String} The text colour. 12093 */ 12094 this.getTextColour = function() { return textColour; }; 12095 12096 /** 12097 * Get the line colour. 12098 * @return {String} The line colour. 12099 */ 12100 this.getLineColour = function() { return lineColour; }; 12101 12102 /** 12103 * Set the line colour. 12104 * @param {String} colour The line colour. 12105 */ 12106 this.setLineColour = function(colour) { lineColour = colour; }; 12107 12108 /** 12109 * Set the display scale. 12110 * @param {String} scale The display scale. 12111 */ 12112 this.setScale = function(scale) { displayScale = scale; }; 12113 12114 /** 12115 * Scale an input value. 12116 * @param {Number} value The value to scale. 12117 */ 12118 this.scale = function(value) { return value / displayScale; }; 12119 }; 12120 12121 /** 12122 * Get the font definition string. 12123 * @return {String} The font definition string. 12124 */ 12125 dwv.html.Style.prototype.getFontStr = function() { 12126 return ("normal " + this.getFontSize() + "px sans-serif"); 12127 }; 12128 12129 /** 12130 * Get the line height. 12131 * @return {Number} The line height. 12132 */ 12133 dwv.html.Style.prototype.getLineHeight = function() { 12134 return (this.getFontSize() + this.getFontSize() / 5); 12135 }; 12136 12137 /** 12138 * Get the font size scaled to the display. 12139 * @return {Number} The scaled font size. 12140 */ 12141 dwv.html.Style.prototype.getScaledFontSize = function() { 12142 return this.scale(this.getFontSize()); 12143 }; 12144 12145 /** 12146 * Get the stroke width scaled to the display. 12147 * @return {Number} The scaled stroke width. 12148 */ 12149 dwv.html.Style.prototype.getScaledStrokeWidth = function() { 12150 return this.scale(this.getStrokeWidth()); 12151 }; 12152 12153 // namespaces 12154 var dwv = dwv || {}; 12155 dwv.gui = dwv.gui || {}; 12156 dwv.gui.base = dwv.gui.base || {}; 12157 12158 /** 12159 * Toolbox base gui. 12160 * @constructor 12161 */ 12162 dwv.gui.base.Toolbox = function(app) { 12163 /** 12164 * Setup the toolbox HTML. 12165 */ 12166 this.setup = function(list) { 12167 // tool select 12168 var toolSelector = dwv.html.createHtmlSelect("toolSelect", list, "tool"); 12169 toolSelector.onchange = app.onChangeTool; 12170 12171 // tool list element 12172 var toolLi = document.createElement("li"); 12173 toolLi.className = "toolLi ui-block-a"; 12174 toolLi.style.display = "none"; 12175 toolLi.appendChild(toolSelector); 12176 12177 // tool ul 12178 var toolUl = document.createElement("ul"); 12179 toolUl.appendChild(toolLi); 12180 toolUl.className = "ui-grid-b"; 12181 12182 // node 12183 var node = app.getElement("toolList"); 12184 // append 12185 node.appendChild(toolUl); 12186 // refresh 12187 dwv.gui.refreshElement(node); 12188 }; 12189 12190 /** 12191 * Display the toolbox HTML. 12192 * @param {Boolean} bool True to display, false to hide. 12193 */ 12194 this.display = function(bool) { 12195 // tool list element 12196 var node = app.getElement("toolLi"); 12197 dwv.html.displayElement(node, bool); 12198 }; 12199 12200 /** 12201 * Initialise the toolbox HTML. 12202 */ 12203 this.initialise = function(displays) { 12204 // tool select: reset selected option 12205 var toolSelector = app.getElement("toolSelect"); 12206 12207 // update list 12208 var options = toolSelector.options; 12209 var selectedIndex = -1; 12210 for (var i = 0; i < options.length; ++i) { 12211 if (!displays[i]) { 12212 options[i].style.display = "none"; 12213 } else { 12214 if (selectedIndex === -1) { 12215 selectedIndex = i; 12216 } 12217 options[i].style.display = ""; 12218 } 12219 } 12220 toolSelector.selectedIndex = selectedIndex; 12221 12222 // refresh 12223 dwv.gui.refreshElement(toolSelector); 12224 }; 12225 12226 }; // dwv.gui.base.Toolbox 12227 12228 /** 12229 * WindowLevel tool base gui. 12230 * @constructor 12231 */ 12232 dwv.gui.base.WindowLevel = function(app) { 12233 /** 12234 * Setup the tool HTML. 12235 */ 12236 this.setup = function() { 12237 // preset select 12238 var wlSelector = dwv.html.createHtmlSelect("presetSelect", []); 12239 wlSelector.onchange = app.onChangeWindowLevelPreset; 12240 // colour map select 12241 var cmSelector = dwv.html.createHtmlSelect("colourMapSelect", dwv.tool.colourMaps, "colourmap"); 12242 cmSelector.onchange = app.onChangeColourMap; 12243 12244 // preset list element 12245 var wlLi = document.createElement("li"); 12246 wlLi.className = "wlLi ui-block-b"; 12247 //wlLi.className = "wlLi"; 12248 wlLi.style.display = "none"; 12249 wlLi.appendChild(wlSelector); 12250 // colour map list element 12251 var cmLi = document.createElement("li"); 12252 cmLi.className = "cmLi ui-block-c"; 12253 //cmLi.className = "cmLi"; 12254 cmLi.style.display = "none"; 12255 cmLi.appendChild(cmSelector); 12256 12257 // node 12258 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 12259 // append preset 12260 node.appendChild(wlLi); 12261 // append colour map 12262 node.appendChild(cmLi); 12263 // refresh 12264 dwv.gui.refreshElement(node); 12265 }; 12266 12267 /**
12268 * Display the tool HTML. 12269 * @param {Boolean} bool True to display, false to hide. 12270 */ 12271 this.display = function(bool) { 12272 // presets list element 12273 var node = app.getElement("wlLi"); 12274 dwv.html.displayElement(node, bool); 12275 // colour map list element 12276 node = app.getElement("cmLi"); 12277 dwv.html.displayElement(node, bool); 12278 }; 12279 12280 /** 12281 * Initialise the tool HTML. 12282 */ 12283 this.initialise = function() { 12284 // create new preset select 12285 var wlSelector = dwv.html.createHtmlSelect("presetSelect", 12286 app.getViewController().getWindowLevelPresetsNames(), "wl.presets", true); 12287 wlSelector.onchange = app.onChangeWindowLevelPreset; 12288 wlSelector.title = "Select w/l preset."; 12289 12290 // copy html list 12291 var wlLi = app.getElement("wlLi"); 12292 // clear node 12293 dwv.html.cleanNode(wlLi); 12294 // add children 12295 wlLi.appendChild(wlSelector); 12296 // refresh 12297 dwv.gui.refreshElement(wlLi); 12298 12299 // colour map select 12300 var cmSelector = app.getElement("colourMapSelect"); 12301 cmSelector.selectedIndex = 0; 12302 // special monochrome1 case 12303 if (app.getImage().getPhotometricInterpretation() === "MONOCHROME1") { 12304 cmSelector.selectedIndex = 1; 12305 } 12306 // refresh 12307 dwv.gui.refreshElement(cmSelector); 12308 }; 12309 12310 }; // class dwv.gui.base.WindowLevel 12311 12312 /** 12313 * Draw tool base gui. 12314 * @constructor 12315 */ 12316 dwv.gui.base.Draw = function(app) { 12317 // default colours 12318 var colours = [ 12319 "Yellow", "Red", "White", "Green", "Blue", "Lime", "Fuchsia", "Black" 12320 ]; 12321 /** 12322 * Get the default colour. 12323 */ 12324 this.getDefaultColour = function() { 12325 if (dwv.browser.hasInputColor()) { 12326 return "#FFFF80"; 12327 } else { 12328 return colours[0]; 12329 } 12330 }; 12331 12332 /** 12333 * Setup the tool HTML. 12334 */ 12335 this.setup = function(shapeList) { 12336 // shape select 12337 var shapeSelector = dwv.html.createHtmlSelect("shapeSelect", shapeList, "shape"); 12338 shapeSelector.onchange = app.onChangeShape; 12339 // colour select 12340 var colourSelector = null; 12341 if (dwv.browser.hasInputColor()) { 12342 colourSelector = document.createElement("input"); 12343 colourSelector.className = "colourSelect"; 12344 colourSelector.type = "color"; 12345 colourSelector.value = "#FFFF80"; 12346 } else { 12347 colourSelector = dwv.html.createHtmlSelect("colourSelect", colours, "colour"); 12348 } 12349 colourSelector.onchange = app.onChangeLineColour; 12350 12351 // shape list element 12352 var shapeLi = document.createElement("li"); 12353 shapeLi.className = "shapeLi ui-block-c"; 12354 shapeLi.style.display = "none"; 12355 shapeLi.appendChild(shapeSelector); 12356 //shapeLi.setAttribute("class","ui-block-c"); 12357 // colour list element 12358 var colourLi = document.createElement("li"); 12359 colourLi.className = "colourLi ui-block-b"; 12360 colourLi.style.display = "none"; 12361 colourLi.appendChild(colourSelector); 12362 //colourLi.setAttribute("class","ui-block-b"); 12363 12364 // node 12365 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 12366 // apend shape 12367 node.appendChild(shapeLi); 12368 // append colour 12369 node.appendChild(colourLi); 12370 // refresh 12371 dwv.gui.refreshElement(node); 12372 }; 12373 12374 /** 12375 * Display the tool HTML. 12376 * @param {Boolean} bool True to display, false to hide. 12377 */ 12378 this.display = function(bool) { 12379 // colour list element 12380 var node = app.getElement("colourLi"); 12381 dwv.html.displayElement(node, bool); 12382 // shape list element 12383 node = app.getElement("shapeLi"); 12384 dwv.html.displayElement(node, bool); 12385 }; 12386 12387 /** 12388 * Initialise the tool HTML. 12389 */ 12390 this.initialise = function() { 12391 // shape select: reset selected option 12392 var shapeSelector = app.getElement("shapeSelect"); 12393 shapeSelector.selectedIndex = 0; 12394 // refresh 12395 dwv.gui.refreshElement(shapeSelector); 12396 12397 // colour select: reset selected option 12398 var colourSelector = app.getElement("colourSelect"); 12399 if (!dwv.browser.hasInputColor()) { 12400 colourSelector.selectedIndex = 0; 12401 } 12402 // refresh 12403 dwv.gui.refreshElement(colourSelector); 12404 }; 12405 12406 }; // class dwv.gui.base.Draw 12407 12408 /** 12409 * Base gui for a tool with a colour setting. 12410 * @constructor 12411 */ 12412 dwv.gui.base.ColourTool = function(app, prefix) { 12413 // default colours 12414 var colours = [ 12415 "Yellow", "Red", "White", "Green", "Blue", "Lime", "Fuchsia", "Black" 12416 ]; 12417 // colour selector class 12418 var colourSelectClassName = prefix + "ColourSelect"; 12419 // colour selector class 12420 var colourLiClassName = prefix + "ColourLi"; 12421 12422 /** 12423 * Get the default colour. 12424 */ 12425 this.getDefaultColour = function() { 12426 if (dwv.browser.hasInputColor()) { 12427 return "#FFFF80"; 12428 } else { 12429 return colours[0]; 12430 } 12431 }; 12432 12433 /** 12434 * Setup the tool HTML. 12435 */ 12436 this.setup = function() { 12437 // colour select 12438 var colourSelector = null; 12439 if (dwv.browser.hasInputColor()) { 12440 colourSelector = document.createElement("input"); 12441 colourSelector.className = colourSelectClassName; 12442 colourSelector.type = "color"; 12443 colourSelector.value = "#FFFF80"; 12444 } else { 12445 colourSelector = dwv.html.createHtmlSelect(colourSelectClassName, colours, "colour"); 12446 } 12447 colourSelector.onchange = app.onChangeLineColour; 12448 12449 // colour list element 12450 var colourLi = document.createElement("li"); 12451 colourLi.className = colourLiClassName + " ui-block-b"; 12452 colourLi.style.display = "none"; 12453 //colourLi.setAttribute("class","ui-block-b"); 12454 colourLi.appendChild(colourSelector); 12455 12456 // node 12457 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 12458 // apend colour 12459 node.appendChild(colourLi); 12460 // refresh 12461 dwv.gui.refreshElement(node); 12462 }; 12463 12464 /**
12465 * Display the tool HTML. 12466 * @param {Boolean} bool True to display, false to hide. 12467 */ 12468 this.display = function(bool) { 12469 // colour list 12470 var node = app.getElement(colourLiClassName); 12471 dwv.html.displayElement(node, bool); 12472 }; 12473 12474 /** 12475 * Initialise the tool HTML. 12476 */ 12477 this.initialise = function() { 12478 var colourSelector = app.getElement(colourSelectClassName); 12479 if (!dwv.browser.hasInputColor()) { 12480 colourSelector.selectedIndex = 0; 12481 } 12482 dwv.gui.refreshElement(colourSelector); 12483 }; 12484 12485 }; // class dwv.gui.base.ColourTool 12486 12487 /** 12488 * ZoomAndPan tool base gui. 12489 * @constructor 12490 */ 12491 dwv.gui.base.ZoomAndPan = function(app) { 12492 /** 12493 * Setup the tool HTML. 12494 */ 12495 this.setup = function() { 12496 // reset button 12497 var button = document.createElement("button"); 12498 button.className = "zoomResetButton"; 12499 button.name = "zoomResetButton"; 12500 button.onclick = app.onZoomReset; 12501 button.setAttribute("style", "width:100%; margin-top:0.5em;"); 12502 button.setAttribute("class", "ui-btn ui-btn-b"); 12503 var text = document.createTextNode(dwv.i18n("basics.reset")); 12504 button.appendChild(text); 12505 12506 // list element 12507 var liElement = document.createElement("li"); 12508 liElement.className = "zoomLi ui-block-c"; 12509 liElement.style.display = "none"; 12510 //liElement.setAttribute("class","ui-block-c"); 12511 liElement.appendChild(button); 12512 12513 // node 12514 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 12515 // append element 12516 node.appendChild(liElement); 12517 // refresh 12518 dwv.gui.refreshElement(node); 12519 }; 12520 12521 /** 12522 * Display the tool HTML. 12523 * @param {Boolean} bool True to display, false to hide. 12524 */ 12525 this.display = function(bool) { 12526 // display list element 12527 var node = app.getElement("zoomLi"); 12528 dwv.html.displayElement(node, bool); 12529 }; 12530 12531 }; // class dwv.gui.base.ZoomAndPan 12532 12533 /** 12534 * Scroll tool base gui. 12535 * @constructor 12536 */ 12537 dwv.gui.base.Scroll = function(app) { 12538 /** 12539 * Setup the tool HTML. 12540 */ 12541 this.setup = function() { 12542 // list element 12543 var liElement = document.createElement("li"); 12544 liElement.className = "scrollLi ui-block-c"; 12545 liElement.style.display = "none"; 12546 12547 // node 12548 var node = app.getElement("toolList").getElementsByTagName("ul")[0]; 12549 // append element 12550 node.appendChild(liElement); 12551 // refresh 12552 dwv.gui.refreshElement(node); 12553 }; 12554 12555 /** 12556 * Display the tool HTML. 12557 * @param {Boolean} bool True to display, false to hide. 12558 */ 12559 this.display = function(bool) { 12560 // display list element 12561 var node = app.getElement("scrollLi"); 12562 dwv.html.displayElement(node, bool); 12563 }; 12564 12565 }; // class dwv.gui.base.Scroll 12566 12567 // namespaces 12568 var dwv = dwv || {}; 12569 dwv.gui = dwv.gui || {}; 12570 dwv.gui.base = dwv.gui.base || {}; 12571 12572 /** 12573 * Undo base gui. 12574 * @constructor 12575 */ 12576 dwv.gui.base.Undo = function(app) { 12577 /** 12578 * Setup the undo HTML. 12579 */ 12580 this.setup = function() { 12581 var paragraph = document.createElement("p"); 12582 paragraph.appendChild(document.createTextNode("History:")); 12583 paragraph.appendChild(document.createElement("br")); 12584 12585 var select = document.createElement("select"); 12586 select.className = "history_list"; 12587 select.name = "history_list"; 12588 select.multiple = "multiple"; 12589 paragraph.appendChild(select); 12590 12591 // node 12592 var node = app.getElement("history"); 12593 // clear it 12594 while (node.hasChildNodes()) { 12595 node.removeChild(node.firstChild); 12596 } 12597 // append 12598 node.appendChild(paragraph); 12599 // refresh 12600 dwv.gui.refreshElement(node); 12601 }; 12602 12603 /** 12604 * Clear the command list of the undo HTML. 12605 */ 12606 this.initialise = function() { 12607 var select = app.getElement("history_list"); 12608 if (select && select.length !== 0) { 12609 for (var i = select.length - 1; i >= 0; --i) { 12610 select.remove(i); 12611 } 12612 } 12613 // refresh 12614 dwv.gui.refreshElement(select); 12615 }; 12616 12617 /** 12618 * Add a command to the undo HTML. 12619 * @param {String} commandName The name of the command to add. 12620 */ 12621 this.addCommandToUndoHtml = function(commandName) { 12622 var select = app.getElement("history_list"); 12623 // remove undone commands 12624 var count = select.length - (select.selectedIndex + 1); 12625 if (count > 0) { 12626 for (var i = 0; i < count; ++i) { 12627 select.remove(select.length - 1); 12628 } 12629 } 12630 // add new option 12631 var option = document.createElement("option"); 12632 option.text = commandName; 12633 option.value = commandName; 12634 select.add(option); 12635 // increment selected index 12636 select.selectedIndex++; 12637 // refresh 12638 dwv.gui.refreshElement(select); 12639 }; 12640 12641 /** 12642 * Enable the last command of the undo HTML. 12643 * @param {Boolean} enable Flag to enable or disable the command. 12644 */ 12645 this.enableInUndoHtml = function(enable) { 12646 var select = app.getElement("history_list"); 12647 // enable or not (order is important) 12648 var option; 12649 if (enable) { 12650 // increment selected index 12651 select.selectedIndex++; 12652 // enable option 12653 option = select.options[select.selectedIndex]; 12654 option.disabled = false; 12655 } else { 12656 // disable option 12657 option = select.options[select.selectedIndex]; 12658 option.disabled = true; 12659 // decrement selected index 12660 select.selectedIndex--; 12661 } 12662 // refresh 12663 dwv.gui.refreshElement(select); 12664 }; 12665 12666 }; // class dwv.gui.base.Undo 12667 12668 // namespaces 12669 var dwv = dwv || {}; 12670 dwv.image = dwv.image || {}; 12671 12672 // JPEG Baseline 12673 var hasJpegBaselineDecoder = (typeof JpegImage !== "undefined"); 12674 var JpegImage = JpegImage || {}; 12675 // JPEG Lossless 12676 var hasJpegLosslessDecoder = (typeof jpeg !== "undefined") && 12677 (typeof jpeg.lossless !== "undefined"); 12678 var jpeg = jpeg || {}; 12679 jpeg.lossless = jpeg.lossless || {}; 12680 // JPEG 2000 12681 var hasJpeg2000Decoder = (typeof JpxImage !== "undefined"); 12682 var JpxImage = JpxImage || {}; 12683 12684 /** 12685 * Asynchronous pixel buffer decoder. 12686 * @param {String} script The path to the decoder script to be used by the web worker. 12687 */ 12688 dwv.image.AsynchPixelBufferDecoder = function(script) { 12689 // initialise the thread pool 12690 var pool = new dwv.utils.ThreadPool(15); 12691 pool.init(); 12692 12693 /** 12694 * Decode a pixel buffer. 12695 * @param {Array} pixelBuffer The pixel buffer. 12696 * @param {Number} bitsAllocated The bits allocated per element in the buffer. 12697 * @param {Boolean} isSigned Is the data signed. 12698 * @param {Function} callback Callback function to handle decoded data. 12699 */ 12700 this.decode = function(pixelBuffer, bitsAllocated, isSigned, callback) { 12701 // (re)set event handler 12702 pool.onpoolworkend = this.ondecodeend; 12703 pool.onworkerend = this.ondecoded; 12704 // create worker task 12705 var workerTask = new dwv.utils.WorkerTask(script, callback, { 12706 'buffer': pixelBuffer, 12707 'bitsAllocated': bitsAllocated, 12708 'isSigned': isSigned 12709 }); 12710 // add it the queue and run it 12711 pool.addWorkerTask(workerTask); 12712 }; 12713 12714 /** 12715 * Abort decoding. 12716 */ 12717 this.abort = function() { 12718 // abort the thread pool 12719 pool.abort(); 12720 }; 12721 }; 12722 12723 /** 12724 * Handle a decode end event. 12725 */ 12726 dwv.image.AsynchPixelBufferDecoder.prototype.ondecodeend = function() { 12727 // default does nothing. 12728 }; 12729 12730 /** 12731 * Handle a decode event. 12732 */ 12733 dwv.image.AsynchPixelBufferDecoder.prototype.ondecoded = function() { 12734 // default does nothing. 12735 }; 12736 12737 /** 12738 * Synchronous pixel buffer decoder. 12739 * @param {String} algoName The decompression algorithm name. 12740 */ 12741 dwv.image.SynchPixelBufferDecoder = function(algoName) { 12742 /** 12743 * Decode a pixel buffer. 12744 * @param {Array} pixelBuffer The pixel buffer. 12745 * @param {Number} bitsAllocated The bits allocated per element in the buffer. 12746 * @param {Boolean} isSigned Is the data signed. 12747 * @param {Function} callback Callback function to handle decoded data. 12748 * @external jpeg 12749 * @external JpegImage 12750 * @external JpxImage 12751 */ 12752 this.decode = function(pixelBuffer, bitsAllocated, isSigned, callback) { 12753 var decoder = null; 12754 var decodedBuffer = null; 12755 if (algoName === "jpeg-lossless") { 12756 if (!hasJpegLosslessDecoder) { 12757 throw new Error("No JPEG Lossless decoder provided"); 12758 } 12759 // bytes per element 12760 var bpe = bitsAllocated / 8; 12761 var buf = new Uint8Array(pixelBuffer); 12762 decoder = new jpeg.lossless.Decoder(); 12763 var decoded = decoder.decode(buf.buffer, 0, buf.buffer.byteLength, bpe); 12764 if (bitsAllocated === 8) { 12765 if (isSigned) { 12766 decodedBuffer = new Int8Array(decoded.buffer); 12767 } else { 12768 decodedBuffer = new Uint8Array(decoded.buffer); 12769 } 12770 } else if (bitsAllocated === 16) { 12771 if (isSigned) { 12772 decodedBuffer = new Int16Array(decoded.buffer); 12773 } else { 12774 decodedBuffer = new Uint16Array(decoded.buffer); 12775 } 12776 } 12777 } else if (algoName === "jpeg-baseline") { 12778 if (!hasJpegBaselineDecoder) { 12779 throw new Error("No JPEG Baseline decoder provided"); 12780 } 12781 decoder = new JpegImage(); 12782 decoder.parse(pixelBuffer); 12783 decodedBuffer = decoder.getData(decoder.width, decoder.height); 12784 } else if (algoName === "jpeg2000") { 12785 if (!hasJpeg2000Decoder) { 12786 throw new Error("No JPEG 2000 decoder provided"); 12787 } 12788 // decompress pixel buffer into Int16 image 12789 decoder = new JpxImage(); 12790 decoder.parse(pixelBuffer); 12791 // set the pixel buffer 12792 decodedBuffer = decoder.tiles[0].items; 12793 } 12794 // send events 12795 this.ondecoded(); 12796 this.ondecodeend(); 12797 // call callback with decoded buffer as array 12798 callback({ data: [decodedBuffer] }); 12799 }; 12800 12801 /** 12802 * Abort decoding. 12803 */ 12804 this.abort = function() { 12805 // nothing to do in the synchronous case. 12806 }; 12807 }; 12808 12809 /** 12810 * Handle a decode end event. 12811 */ 12812 dwv.image.SynchPixelBufferDecoder.prototype.ondecodeend = function() { 12813 // default does nothing. 12814 }; 12815 12816 /** 12817 * Handle a decode event. 12818 */ 12819 dwv.image.SynchPixelBufferDecoder.prototype.ondecoded = function() { 12820 // default does nothing. 12821 }; 12822 12823 /** 12824 * Decode a pixel buffer. 12825 * @constructor 12826 * @param {String} algoName The decompression algorithm name. 12827 * If the 'dwv.image.decoderScripts' variable does not contain the desired algorythm, 12828 * the decoder will switch to the synchronous mode. 12829 */ 12830 dwv.image.PixelBufferDecoder = function(algoName) { 12831 /** 12832 * Pixel decoder. 12833 * Defined only once. 12834 * @private 12835 * @type Object 12836 */ 12837 var pixelDecoder = null; 12838 12839 // initialise the asynch decoder (if possible) 12840 if (typeof dwv.image.decoderScripts !== "undefined" && 12841 typeof dwv.image.decoderScripts[algoName] !== "undefined") { 12842 pixelDecoder = new dwv.image.AsynchPixelBufferDecoder(dwv.image.decoderScripts[algoName]); 12843 } else { 12844 pixelDecoder = new dwv.image.SynchPixelBufferDecoder(algoName); 12845 } 12846 12847 /** 12848 * Get data from an input buffer using a DICOM parser. 12849 * @param {Array} pixelBuffer The input data buffer. 12850 * @param {Number} bitsAllocated The bits allocated per element in the buffer. 12851 * @param {Boolean} isSigned Is the data signed. 12852 * @param {Object} callback The callback on the conversion. 12853 */ 12854 this.decode = function(pixelBuffer, bitsAllocated, isSigned, callback) { 12855 // set event handler 12856 pixelDecoder.ondecodeend = this.ondecodeend; 12857 pixelDecoder.ondecoded = this.ondecoded; 12858 // decode and call the callback 12859 pixelDecoder.decode(pixelBuffer, bitsAllocated, isSigned, callback); 12860 }; 12861 12862 /** 12863 * Abort decoding. 12864 */ 12865 this.abort = function() { 12866 // decoder classes should define an abort 12867 pixelDecoder.abort(); 12868 }; 12869 }; 12870 12871 /** 12872 * Handle a decode end event. 12873 */ 12874 dwv.image.PixelBufferDecoder.prototype.ondecodeend = function() { 12875 // default does nothing. 12876 }; 12877 12878 /** 12879 * Handle a decode end event. 12880 */ 12881 dwv.image.PixelBufferDecoder.prototype.ondecoded = function() { 12882 // default does nothing. 12883 }; 12884 12885 // namespaces 12886 var dwv = dwv || {}; 12887 dwv.image = dwv.image || {}; 12888 12889 /** 12890 * Create a dwv.image.View from a DICOM buffer. 12891 * @constructor 12892 */ 12893 dwv.image.DicomBufferToView = function() { 12894 // closure to self 12895 var self = this; 12896 12897 /** 12898 * The default character set (optional). 12899 * @private
12900 * @type String 12901 */ 12902 var defaultCharacterSet; 12903 12904 /** 12905 * Set the default character set. 12906 * param {String} The character set. 12907 */ 12908 this.setDefaultCharacterSet = function(characterSet) { 12909 defaultCharacterSet = characterSet; 12910 }; 12911 12912 /** 12913 * Pixel buffer decoder. 12914 * Define only once to allow optional asynchronous mode. 12915 * @private 12916 * @type Object 12917 */ 12918 var pixelDecoder = null; 12919 12920 /** 12921 * Get data from an input buffer using a DICOM parser. 12922 * @param {Array} buffer The input data buffer. 12923 * @param {Number} dataIndex The data index. 12924 */ 12925 this.convert = function(buffer, dataIndex) { 12926 // DICOM parser 12927 var dicomParser = new dwv.dicom.DicomParser(); 12928 dicomParser.setDefaultCharacterSet(defaultCharacterSet); 12929 // parse the buffer 12930 dicomParser.parse(buffer); 12931 12932 var pixelBuffer = dicomParser.getRawDicomElements().x7FE00010.value; 12933 var syntax = dwv.dicom.cleanString(dicomParser.getRawDicomElements().x00020010.value[0]); 12934 var algoName = dwv.dicom.getSyntaxDecompressionName(syntax); 12935 var needDecompression = (algoName !== null); 12936 12937 // worker callback 12938 var onDecodedFirstFrame = function( /*event*/ ) { 12939 // create the image 12940 var imageFactory = new dwv.image.ImageFactory(); 12941 var image = imageFactory.create(dicomParser.getDicomElements(), pixelBuffer); 12942 // create the view 12943 var viewFactory = new dwv.image.ViewFactory(); 12944 var view = viewFactory.create(dicomParser.getDicomElements(), image); 12945 // return 12946 self.onload({ "view": view, "info": dicomParser.getDicomElements().dumpToTable() }); 12947 }; 12948 12949 if (needDecompression) { 12950 var bitsAllocated = dicomParser.getRawDicomElements().x00280100.value[0]; 12951 var pixelRepresentation = dicomParser.getRawDicomElements().x00280103.value[0]; 12952 var isSigned = (pixelRepresentation === 1); 12953 var nFrames = pixelBuffer.length; 12954 12955 if (!pixelDecoder) { 12956 pixelDecoder = new dwv.image.PixelBufferDecoder(algoName); 12957 } 12958 12959 // loadend event 12960 pixelDecoder.ondecodeend = function() { 12961 self.onloadend(); 12962 }; 12963 12964 // send an onload event for mono frame 12965 if (nFrames === 1) { 12966 pixelDecoder.ondecoded = function() { 12967 self.onloadend(); 12968 }; 12969 } 12970 12971 // decoder callback 12972 var countDecodedFrames = 0; 12973 var onDecodedFrame = function(frame) { 12974 return function(event) { 12975 // send progress 12976 ++countDecodedFrames; 12977 var ev = { 12978 'type': "load-progress", 12979 'lengthComputable': true, 12980 'loaded': (countDecodedFrames * 100 / nFrames), 12981 'total': 100 12982 }; 12983 if (typeof dataIndex !== "undefined") { 12984 ev.index = dataIndex; 12985 } 12986 self.onprogress(ev); 12987 // store data 12988 pixelBuffer[frame] = event.data[0]; 12989 // create image for first frame 12990 if (frame === 0) { 12991 onDecodedFirstFrame(); 12992 } 12993 }; 12994 }; 12995 12996 // decompress synchronously the first frame to create the image 12997 pixelDecoder.decode(pixelBuffer[0], 12998 bitsAllocated, isSigned, onDecodedFrame(0), false); 12999 13000 // decompress the possible other frames 13001 if (nFrames !== 1) { 13002 // decode (asynchronously if possible) 13003 for (var f = 1; f < nFrames; ++f) { 13004 pixelDecoder.decode(pixelBuffer[f], 13005 bitsAllocated, isSigned, onDecodedFrame(f)); 13006 } 13007 } 13008 } 13009 // no decompression 13010 else { 13011 // send progress 13012 var evnodec = { 13013 'type': 'load-progress', 13014 'lengthComputable': true, 13015 'loaded': 100, 13016 'total': 100 13017 }; 13018 if (typeof dataIndex !== "undefined") { 13019 evnodec.index = dataIndex; 13020 } 13021 self.onprogress(evnodec); 13022 // create image 13023 onDecodedFirstFrame(); 13024 // send load events 13025 self.onloadend(); 13026 } 13027 }; 13028 13029 /** 13030 * Abort a conversion. 13031 */ 13032 this.abort = function() { 13033 if (pixelDecoder) { 13034 pixelDecoder.abort(); 13035 } 13036 }; 13037 }; 13038 13039 /** 13040 * Handle a load end event. 13041 * @param {Object} event The load end event. 13042 * Default does nothing. 13043 */ 13044 dwv.image.DicomBufferToView.prototype.onloadend = function( /*event*/ ) {}; 13045 /** 13046 * Handle a load event. 13047 * @param {Object} event The load event. 13048 * Default does nothing. 13049 */ 13050 dwv.image.DicomBufferToView.prototype.onload = function( /*event*/ ) {}; 13051 /** 13052 * Handle a load progress event. 13053 * @param {Object} event The progress event. 13054 * Default does nothing. 13055 */ 13056 dwv.image.DicomBufferToView.prototype.onprogress = function( /*event*/ ) {}; 13057 13058 // namespaces 13059 var dwv = dwv || {}; 13060 dwv.image = dwv.image || {}; 13061 13062 /** 13063 * Create a simple array buffer from an ImageData buffer. 13064 * @param {Object} imageData The ImageData taken from a context. 13065 * @return {Array} The image buffer. 13066 */ 13067 dwv.image.imageDataToBuffer = function(imageData) { 13068 // remove alpha 13069 // TODO support passing the full image data 13070 var dataLen = imageData.data.length; 13071 var buffer = new Uint8Array((dataLen / 4) * 3); 13072 var j = 0; 13073 for (var i = 0; i < dataLen; i += 4) { 13074 buffer[j] = imageData.data[i]; 13075 buffer[j + 1] = imageData.data[i + 1];
13076 buffer[j + 2] = imageData.data[i + 2]; 13077 j += 3; 13078 } 13079 return buffer; 13080 }; 13081 13082 /** 13083 * Get data from an input context imageData. 13084 * @param {Number} width The width of the coresponding image. 13085 * @param {Number} height The height of the coresponding image. 13086 * @param {Number} sliceIndex The slice index of the imageData. 13087 * @param {Object} imageBuffer The image buffer. 13088 * @param {Number} numberOfFrames The final number of frames. 13089 * @return {Object} The corresponding view. 13090 */ 13091 dwv.image.getDefaultView = function( 13092 width, height, sliceIndex, 13093 imageBuffer, numberOfFrames, info) { 13094 // image size 13095 var imageSize = new dwv.image.Size(width, height); 13096 // default spacing 13097 // TODO: misleading... 13098 var imageSpacing = new dwv.image.Spacing(1, 1); 13099 // default origin 13100 var origin = new dwv.math.Point3D(0, 0, sliceIndex); 13101 // create image 13102 var geometry = new dwv.image.Geometry(origin, imageSize, imageSpacing); 13103 var image = new dwv.image.Image(geometry, imageBuffer, numberOfFrames); 13104 image.setPhotometricInterpretation("RGB"); 13105 // meta information 13106 var meta = {}; 13107 meta.BitsStored = 8; 13108 image.setMeta(meta); 13109 // overlay 13110 image.setFirstOverlay(dwv.gui.info.createOverlaysForDom(info)); 13111 // view 13112 var view = new dwv.image.View(image); 13113 // defaut preset 13114 view.setWindowLevelMinMax(); 13115 // return 13116 return view; 13117 }; 13118 13119 /** 13120 * Get data from an input image using a canvas. 13121 * @param {Object} image The DOM Image. 13122 * @return {Mixed} The corresponding view and info. 13123 */ 13124 dwv.image.getViewFromDOMImage = function(image) { 13125 // image size 13126 var width = image.width; 13127 var height = image.height; 13128 13129 // draw the image in the canvas in order to get its data 13130 var canvas = document.createElement('canvas'); 13131 canvas.width = width; 13132 canvas.height = height; 13133 var ctx = canvas.getContext('2d'); 13134 ctx.drawImage(image, 0, 0); 13135 // get the image data 13136 var imageData = ctx.getImageData(0, 0, width, height); 13137 13138 // image properties 13139 var info = []; 13140 if (typeof image.origin === "string") { 13141 info.push({ "name": "origin", "value": image.origin }); 13142 } else { 13143 info.push({ "name": "fileName", "value": image.origin.name }); 13144 info.push({ "name": "fileType", "value": image.origin.type }); 13145 info.push({ "name": "fileLastModifiedDate", "value": image.origin.lastModifiedDate }); 13146 } 13147 info.push({ "name": "imageWidth", "value": width }); 13148 info.push({ "name": "imageHeight", "value": height }); 13149 13150 // create view 13151 var sliceIndex = image.index ? image.index : 0; 13152 var imageBuffer = dwv.image.imageDataToBuffer(imageData); 13153 var view = dwv.image.getDefaultView( 13154 width, height, sliceIndex, [imageBuffer], 1, info); 13155 13156 // return 13157 return { "view": view, "info": info }; 13158 }; 13159 13160 /** 13161 * Get data from an input image using a canvas. 13162 * @param {Object} video The DOM Video. 13163 * @param {Object} callback The function to call once the data is loaded. 13164 * @param {Object} cbprogress The function to call to report progress. 13165 * @param {Object} cbonloadend The function to call to report load end. 13166 * @param {Number} dataindex The data index. 13167 */ 13168 dwv.image.getViewFromDOMVideo = function(video, callback, cbprogress, cbonloadend, dataIndex) { 13169 // video size 13170 var width = video.videoWidth; 13171 var height = video.videoHeight; 13172 13173 // default frame rate... 13174 var frameRate = 30; 13175 // number of frames 13176 var numberOfFrames = Math.floor(video.duration * frameRate); 13177 13178 // video properties 13179 var info = []; 13180 if (video.file) { 13181 info.push({ "name": "fileName", "value": video.file.name });
13182 info.push({ "name": "fileType", "value": video.file.type }); 13183 info.push({ "name": "fileLastModifiedDate", "value": video.file.lastModifiedDate }); 13184 } 13185 info.push({ "name": "imageWidth", "value": width }); 13186 info.push({ "name": "imageHeight", "value": height }); 13187 info.push({ "name": "numberOfFrames", "value": numberOfFrames }); 13188 13189 // draw the image in the canvas in order to get its data 13190 var canvas = document.createElement('canvas'); 13191 canvas.width = width; 13192 canvas.height = height; 13193 var ctx = canvas.getContext('2d'); 13194 13195 // using seeked to loop through all video frames 13196 video.addEventListener('seeked', onseeked, false); 13197 13198 // current frame index 13199 var frameIndex = 0; 13200 // video view 13201 var view = null; 13202 13203 // draw the context and store it as a frame 13204 function storeFrame() { 13205 // send progress 13206 var evprog = { 13207 'type': 'load-progress', 13208 'lengthComputable': true, 13209 'loaded': frameIndex, 13210 'total': numberOfFrames 13211 }; 13212 if (typeof dataIndex !== "undefined") { 13213 evprog.index = dataIndex; 13214 } 13215 cbprogress(evprog); 13216 // draw image 13217 ctx.drawImage(video, 0, 0); 13218 // context to image buffer 13219 var imgBuffer = dwv.image.imageDataToBuffer( 13220 ctx.getImageData(0, 0, width, height)); 13221 if (frameIndex === 0) { 13222 // create view 13223 view = dwv.image.getDefaultView( 13224 width, height, 1, [imgBuffer], numberOfFrames, info); 13225 // call callback 13226 callback({ "view": view, "info": info }); 13227 } else { 13228 view.appendFrameBuffer(imgBuffer); 13229 } 13230 } 13231 13232 // handle seeked event 13233 function onseeked( /*event*/ ) { 13234 // store 13235 storeFrame(); 13236 // increment index 13237 ++frameIndex; 13238 // set the next time 13239 // (not using currentTime, it seems to get offseted) 13240 var nextTime = frameIndex / frameRate; 13241 if (nextTime <= this.duration) { 13242 this.currentTime = nextTime; 13243 } else { 13244 cbonloadend(); 13245 // stop listening 13246 video.removeEventListener('seeked', onseeked); 13247 } 13248 } 13249 13250 // trigger the first seeked 13251 video.currentTime = 0; 13252 }; 13253 13254 // namespaces 13255 var dwv = dwv || {}; 13256 dwv.image = dwv.image || {}; 13257 /** @namespace */ 13258 dwv.image.filter = dwv.image.filter || {}; 13259 13260 /** 13261 * Threshold an image between an input minimum and maximum. 13262 * @constructor 13263 */ 13264 dwv.image.filter.Threshold = function() { 13265 /** 13266 * Threshold minimum. 13267 * @private 13268 * @type Number 13269 */ 13270 var min = 0; 13271 /** 13272 * Threshold maximum. 13273 * @private 13274 * @type Number 13275 */ 13276 var max = 0; 13277 13278 /** 13279 * Get the threshold minimum. 13280 * @return {Number} The threshold minimum. 13281 */ 13282 this.getMin = function() { return min; }; 13283 /** 13284 * Set the threshold minimum. 13285 * @param {Number} val The threshold minimum. 13286 */ 13287 this.setMin = function(val) { min = val; }; 13288 /** 13289 * Get the threshold maximum. 13290 * @return {Number} The threshold maximum. 13291 */ 13292 this.getMax = function() { return max; }; 13293 /** 13294 * Set the threshold maximum. 13295 * @param {Number} val The threshold maximum. 13296 */ 13297 this.setMax = function(val) { max = val; }; 13298 /** 13299 * Get the name of the filter. 13300 * @return {String} The name of the filter. 13301 */ 13302 this.getName = function() { return "Threshold"; }; 13303 13304 /** 13305 * Original image. 13306 * @private 13307 * @type Object 13308 */ 13309 var originalImage = null; 13310 /** 13311 * Set the original image. 13312 * @param {Object} image The original image. 13313 */ 13314 this.setOriginalImage = function(image) { originalImage = image; }; 13315 /** 13316 * Get the original image. 13317 * @return {Object} image The original image. 13318 */ 13319 this.getOriginalImage = function() { return originalImage; }; 13320 }; 13321 13322 /** 13323 * Transform the main image using this filter. 13324 * @return {Object} The transformed image. 13325 */ 13326 dwv.image.filter.Threshold.prototype.update = function() { 13327 var image = this.getOriginalImage(); 13328 var imageMin = image.getDataRange().min; 13329 var self = this; 13330 var threshFunction = function(value) { 13331 if (value < self.getMin() || value > self.getMax()) { 13332 return imageMin; 13333 } else { 13334 return value; 13335 } 13336 }; 13337 return image.transform(threshFunction); 13338 }; 13339 13340 /** 13341 * Sharpen an image using a sharpen convolution matrix. 13342 * @constructor 13343 */ 13344 dwv.image.filter.Sharpen = function() { 13345 /** 13346 * Get the name of the filter. 13347 * @return {String} The name of the filter. 13348 */ 13349 this.getName = function() { return "Sharpen"; }; 13350 /** 13351 * Original image. 13352 * @private 13353 * @type Object 13354 */ 13355 var originalImage = null; 13356 /** 13357 * Set the original image. 13358 * @param {Object} image The original image. 13359 */ 13360 this.setOriginalImage = function(image) { originalImage = image; }; 13361 /** 13362 * Get the original image. 13363 * @return {Object} image The original image. 13364 */ 13365 this.getOriginalImage = function() { return originalImage; }; 13366 }; 13367 13368 /** 13369 * Transform the main image using this filter. 13370 * @return {Object} The transformed image. 13371 */ 13372 dwv.image.filter.Sharpen.prototype.update = function() { 13373 var image = this.getOriginalImage(); 13374 13375 return image.convolute2D( 13376 [0, -1, 0, -1, 5, -1, 13377 0, -1, 0 13378 ]); 13379 }; 13380 13381 /** 13382 * Apply a Sobel filter to an image. 13383 * @constructor 13384 */ 13385 dwv.image.filter.Sobel = function() { 13386 /** 13387 * Get the name of the filter. 13388 * @return {String} The name of the filter. 13389 */ 13390 this.getName = function() { return "Sobel"; }; 13391 /** 13392 * Original image. 13393 * @private 13394 * @type Object 13395 */ 13396 var originalImage = null; 13397 /** 13398 * Set the original image. 13399 * @param {Object} image The original image. 13400 */ 13401 this.setOriginalImage = function(image) { originalImage = image; }; 13402 /** 13403 * Get the original image. 13404 * @return {Object} image The original image. 13405 */ 13406 this.getOriginalImage = function() { return originalImage; }; 13407 }; 13408 13409 /** 13410 * Transform the main image using this filter. 13411 * @return {Object} The transformed image. 13412 */ 13413 dwv.image.filter.Sobel.prototype.update = function() { 13414 var image = this.getOriginalImage(); 13415 13416 var gradX = image.convolute2D( 13417 [1, 0, -1, 13418 2, 0, -2, 13419 1, 0, -1 13420 ]); 13421 13422 var gradY = image.convolute2D( 13423 [1, 2, 1, 13424 0, 0, 0, -1, -2, -1 13425 ]); 13426 13427 return gradX.compose(gradY, function(x, y) { return Math.sqrt(x * x + y * y); }); 13428 }; 13429 13430 // namespaces 13431 var dwv = dwv || {}; 13432 dwv.image = dwv.image || {}; 13433 13434 /** 13435 * 2D/3D Size class. 13436 * @constructor 13437 * @param {Number} numberOfColumns The number of columns. 13438 * @param {Number} numberOfRows The number of rows. 13439 * @param {Number} numberOfSlices The number of slices. 13440 */ 13441 dwv.image.Size = function(numberOfColumns, numberOfRows, numberOfSlices) { 13442 /** 13443 * Get the number of columns. 13444 * @return {Number} The number of columns. 13445 */ 13446 this.getNumberOfColumns = function() { return numberOfColumns; }; 13447 /** 13448 * Get the number of rows. 13449 * @return {Number} The number of rows. 13450 */ 13451 this.getNumberOfRows = function() { return numberOfRows; }; 13452 /** 13453 * Get the number of slices. 13454 * @return {Number} The number of slices. 13455 */ 13456 this.getNumberOfSlices = function() { return (numberOfSlices || 1.0); }; 13457 }; 13458 13459 /** 13460 * Get the size of a slice. 13461 * @return {Number} The size of a slice. 13462 */ 13463 dwv.image.Size.prototype.getSliceSize = function() { 13464 return this.getNumberOfColumns() * this.getNumberOfRows(); 13465 }; 13466 13467 /** 13468 * Get the total size. 13469 * @return {Number} The total size. 13470 */ 13471 dwv.image.Size.prototype.getTotalSize = function() {
13472 return this.getSliceSize() * this.getNumberOfSlices(); 13473 }; 13474 13475 /** 13476 * Check for equality. 13477 * @param {Size} rhs The object to compare to. 13478 * @return {Boolean} True if both objects are equal. 13479 */ 13480 dwv.image.Size.prototype.equals = function(rhs) { 13481 return rhs !== null && 13482 this.getNumberOfColumns() === rhs.getNumberOfColumns() && 13483 this.getNumberOfRows() === rhs.getNumberOfRows() && 13484 this.getNumberOfSlices() === rhs.getNumberOfSlices(); 13485 }; 13486 13487 /** 13488 * Check that coordinates are within bounds. 13489 * @param {Number} i The column coordinate. 13490 * @param {Number} j The row coordinate. 13491 * @param {Number} k The slice coordinate. 13492 * @return {Boolean} True if the given coordinates are within bounds. 13493 */ 13494 dwv.image.Size.prototype.isInBounds = function(i, j, k) { 13495 if (i < 0 || i > this.getNumberOfColumns() - 1 || 13496 j < 0 || j > this.getNumberOfRows() - 1 || 13497 k < 0 || k > this.getNumberOfSlices() - 1) { 13498 return false; 13499 } 13500 return true; 13501 }; 13502 13503 /** 13504 * Get a string representation of the Vector3D. 13505 * @return {String} The vector as a string. 13506 */ 13507 dwv.image.Size.prototype.toString = function() { 13508 return "(" + this.getNumberOfColumns() + 13509 ", " + this.getNumberOfRows() + 13510 ", " + this.getNumberOfSlices() + ")"; 13511 }; 13512 13513 /** 13514 * 2D/3D Spacing class. 13515 * @constructor 13516 * @param {Number} columnSpacing The column spacing. 13517 * @param {Number} rowSpacing The row spacing. 13518 * @param {Number} sliceSpacing The slice spacing. 13519 */ 13520 dwv.image.Spacing = function(columnSpacing, rowSpacing, sliceSpacing) { 13521 /** 13522 * Get the column spacing. 13523 * @return {Number} The column spacing. 13524 */ 13525 this.getColumnSpacing = function() { return columnSpacing; }; 13526 /** 13527 * Get the row spacing. 13528 * @return {Number} The row spacing. 13529 */ 13530 this.getRowSpacing = function() { return rowSpacing; }; 13531 /** 13532 * Get the slice spacing. 13533 * @return {Number} The slice spacing. 13534 */
13535 this.getSliceSpacing = function() { return (sliceSpacing || 1.0); }; 13536 }; 13537 13538 /** 13539 * Check for equality. 13540 * @param {Spacing} rhs The object to compare to. 13541 * @return {Boolean} True if both objects are equal. 13542 */ 13543 dwv.image.Spacing.prototype.equals = function(rhs) { 13544 return rhs !== null && 13545 this.getColumnSpacing() === rhs.getColumnSpacing() && 13546 this.getRowSpacing() === rhs.getRowSpacing() && 13547 this.getSliceSpacing() === rhs.getSliceSpacing(); 13548 }; 13549 13550 /** 13551 * Get a string representation of the Vector3D. 13552 * @return {String} The vector as a string. 13553 */ 13554 dwv.image.Spacing.prototype.toString = function() { 13555 return "(" + this.getColumnSpacing() + 13556 ", " + this.getRowSpacing() + 13557 ", " + this.getSliceSpacing() + ")"; 13558 }; 13559 13560 13561 /** 13562 * 2D/3D Geometry class. 13563 * @constructor 13564 * @param {Object} origin The object origin (a 3D point). 13565 * @param {Object} size The object size. 13566 * @param {Object} spacing The object spacing. 13567 * @param {Object} orientation The object orientation (3*3 matrix, default to 3*3 identity). 13568 */ 13569 dwv.image.Geometry = function(origin, size, spacing, orientation) { 13570 // check input origin 13571 if (typeof origin === 'undefined') { 13572 origin = new dwv.math.Point3D(0, 0, 0); 13573 } 13574 var origins = [origin]; 13575 // check input orientation 13576 if (typeof orientation === 'undefined') { 13577 orientation = new dwv.math.getIdentityMat33(); 13578 } 13579 13580 /** 13581 * Get the object first origin. 13582 * @return {Object} The object first origin. 13583 */ 13584 this.getOrigin = function() { return origin; }; 13585 /** 13586 * Get the object origins. 13587 * @return {Array} The object origins. 13588 */ 13589 this.getOrigins = function() { return origins; }; 13590 /** 13591 * Get the object size. 13592 * @return {Object} The object size. 13593 */
13594 this.getSize = function() { return size; }; 13595 /** 13596 * Get the object spacing. 13597 * @return {Object} The object spacing. 13598 */ 13599 this.getSpacing = function() { return spacing; }; 13600 /** 13601 * Get the object orientation. 13602 * @return {Object} The object orientation. 13603 */ 13604 this.getOrientation = function() { return orientation; }; 13605 13606 /** 13607 * Get the slice position of a point in the current slice layout. 13608 * @param {Object} point The point to evaluate. 13609 */ 13610 this.getSliceIndex = function(point) { 13611 // cannot use this.worldToIndex(point).getK() since 13612 // we cannot guaranty consecutive slices... 13613 13614 // find the closest index 13615 var closestSliceIndex = 0; 13616 var minDist = point.getDistance(origins[0]); 13617 var dist = 0; 13618 for (var i = 0; i < origins.length; ++i) { 13619 dist = point.getDistance(origins[i]); 13620 if (dist < minDist) { 13621 minDist = dist; 13622 closestSliceIndex = i; 13623 } 13624 } 13625 // we have the closest point, are we before or after 13626 var normal = new dwv.math.Vector3D( 13627 orientation.get(2, 0), orientation.get(2, 1), orientation.get(2, 2)); 13628 var dotProd = normal.dotProduct(point.minus(origins[closestSliceIndex])); 13629 var sliceIndex = (dotProd > 0) ? closestSliceIndex + 1 : closestSliceIndex; 13630 return sliceIndex; 13631 }; 13632 13633 /** 13634 * Append an origin to the geometry. 13635 * @param {Object} origin The origin to append. 13636 * @param {Number} index The index at which to append. 13637 */ 13638 this.appendOrigin = function(origin, index) { 13639 // add in origin array 13640 origins.splice(index, 0, origin); 13641 // increment slice number 13642 size = new dwv.image.Size( 13643 size.getNumberOfColumns(), 13644 size.getNumberOfRows(), 13645 size.getNumberOfSlices() + 1); 13646 }; 13647 13648 }; 13649 13650 /** 13651 * Check for equality. 13652 * @param {Geometry} rhs The object to compare to. 13653 * @return {Boolean} True if both objects are equal. 13654 */ 13655 dwv.image.Geometry.prototype.equals = function(rhs) { 13656 return rhs !== null && 13657 this.getOrigin() === rhs.getOrigin() &&
13658 this.getSize() === rhs.getSize() && 13659 this.getSpacing() === rhs.getSpacing(); 13660 }; 13661 13662 /** 13663 * Convert an index to an offset in memory. 13664 * @param {Object} index The index to convert. 13665 */ 13666 dwv.image.Geometry.prototype.indexToOffset = function(index) { 13667 var size = this.getSize(); 13668 return index.getI() + 13669 index.getJ() * size.getNumberOfColumns() + 13670 index.getK() * size.getSliceSize(); 13671 }; 13672 13673 /** 13674 * Convert an index into world coordinates. 13675 * @param {Object} index The index to convert. 13676 */ 13677 dwv.image.Geometry.prototype.indexToWorld = function(index) { 13678 var origin = this.getOrigin(); 13679 var spacing = this.getSpacing(); 13680 return new dwv.math.Point3D( 13681 origin.getX() + index.getI() * spacing.getColumnSpacing(), 13682 origin.getY() + index.getJ() * spacing.getRowSpacing(), 13683 origin.getZ() + index.getK() * spacing.getSliceSpacing()); 13684 }; 13685 13686 /** 13687 * Convert world coordinates into an index. 13688 * @param {Object} THe point to convert. 13689 */ 13690 dwv.image.Geometry.prototype.worldToIndex = function(point) { 13691 var origin = this.getOrigin(); 13692 var spacing = this.getSpacing(); 13693 return new dwv.math.Point3D( 13694 point.getX() / spacing.getColumnSpacing() - origin.getX(), 13695 point.getY() / spacing.getRowSpacing() - origin.getY(), 13696 point.getZ() / spacing.getSliceSpacing() - origin.getZ()); 13697 }; 13698 13699 // namespaces 13700 var dwv = dwv || {}; 13701 /** @namespace */ 13702 dwv.image = dwv.image || {}; 13703 13704 /** 13705 * Rescale Slope and Intercept 13706 * @constructor 13707 * @param slope 13708 * @param intercept 13709 */ 13710 dwv.image.RescaleSlopeAndIntercept = function(slope, intercept) { 13711 /*// Check the rescale slope. 13712 if(typeof(slope) === 'undefined') { 13713 slope = 1; 13714 } 13715 // Check the rescale intercept. 13716 if(typeof(intercept) === 'undefined') { 13717 intercept = 0; 13718 }*/ 13719 13720 /** 13721 * Get the slope of the RSI. 13722 * @return {Number} The slope of the RSI. 13723 */
13724 this.getSlope = function() { 13725 return slope; 13726 }; 13727 /** 13728 * Get the intercept of the RSI. 13729 * @return {Number} The intercept of the RSI. 13730 */ 13731 this.getIntercept = function() { 13732 return intercept; 13733 }; 13734 /** 13735 * Apply the RSI on an input value. 13736 * @return {Number} The value to rescale. 13737 */ 13738 this.apply = function(value) { 13739 return value * slope + intercept; 13740 }; 13741 }; 13742 13743 /** 13744 * Check for RSI equality. 13745 * @param {Object} rhs The other RSI to compare to. 13746 * @return {Boolean} True if both RSI are equal. 13747 */ 13748 dwv.image.RescaleSlopeAndIntercept.prototype.equals = function(rhs) { 13749 return rhs !== null && 13750 this.getSlope() === rhs.getSlope() && 13751 this.getIntercept() === rhs.getIntercept(); 13752 }; 13753 13754 /** 13755 * Get a string representation of the RSI. 13756 * @return {String} The RSI as a string. 13757 */ 13758 dwv.image.RescaleSlopeAndIntercept.prototype.toString = function() { 13759 return (this.getSlope() + ", " + this.getIntercept()); 13760 }; 13761 13762 /** 13763 * Is this RSI an ID RSI. 13764 * @return {Boolean} True if the RSI has a slope of 1 and no intercept. 13765 */ 13766 dwv.image.RescaleSlopeAndIntercept.prototype.isID = function() { 13767 return (this.getSlope() === 1 && this.getIntercept() === 0); 13768 }; 13769 13770 /** 13771 * Image class. 13772 * Usable once created, optional are: 13773 * - rescale slope and intercept (default 1:0), 13774 * - photometric interpretation (default MONOCHROME2), 13775 * - planar configuration (default RGBRGB...). 13776 * @constructor 13777 * @param {Object} geometry The geometry of the image. 13778 * @param {Array} buffer The image data as an array of frame buffers. 13779 * @param {Number} numberOfFrames The number of frames (optional, can be used 13780 to anticipate the final number after appends). 13781 */ 13782 dwv.image.Image = function(geometry, buffer, numberOfFrames) { 13783 // use buffer length in not specified 13784 if (typeof numberOfFrames === "undefined") { 13785 numberOfFrames = buffer.length; 13786 } 13787 13788 /** 13789 * Get the number of frames. 13790 * @returns {Number} The number of frames. 13791 */ 13792 this.getNumberOfFrames = function() { 13793 return numberOfFrames; 13794 }; 13795 13796 /** 13797 * Rescale slope and intercept. 13798 * @private 13799 * @type Number 13800 */ 13801 var rsis = []; 13802 // initialise RSIs 13803 for (var s = 0, nslices = geometry.getSize().getNumberOfSlices(); s < nslices; ++s) { 13804 rsis.push(new dwv.image.RescaleSlopeAndIntercept(1, 0)); 13805 } 13806 /** 13807 * Flag to know if the RSIs are all identity (1,0). 13808 * @private 13809 * @type Boolean 13810 */ 13811 var isIdentityRSI = true; 13812 /** 13813 * Flag to know if the RSIs are all equals. 13814 * @private 13815 * @type Boolean 13816 */ 13817 var isConstantRSI = true; 13818 /** 13819 * Photometric interpretation (MONOCHROME, RGB...). 13820 * @private
13821 * @type String 13822 */ 13823 var photometricInterpretation = "MONOCHROME2"; 13824 /** 13825 * Planar configuration for RGB data (0:RGBRGBRGBRGB... or 1:RRR...GGG...BBB...). 13826 * @private 13827 * @type Number 13828 */ 13829 var planarConfiguration = 0; 13830 /** 13831 * Number of components. 13832 * @private 13833 * @type Number 13834 */ 13835 var numberOfComponents = buffer[0].length / ( 13836 geometry.getSize().getTotalSize()); 13837 /** 13838 * Meta information. 13839 * @private 13840 * @type Object 13841 */ 13842 var meta = {}; 13843 13844 /** 13845 * Data range. 13846 * @private 13847 * @type Object 13848 */ 13849 var dataRange = null; 13850 /** 13851 * Rescaled data range. 13852 * @private 13853 * @type Object 13854 */ 13855 var rescaledDataRange = null; 13856 /** 13857 * Histogram. 13858 * @private 13859 * @type Array 13860 */ 13861 var histogram = null; 13862 13863 /** 13864 * Overlay. 13865 * @private 13866 * @type Array 13867 */ 13868 var overlays = []; 13869 13870 /** 13871 * Set the first overlay. 13872 * @param {Array} over The first overlay. 13873 */ 13874 this.setFirstOverlay = function(over) { overlays[0] = over; }; 13875 13876 /** 13877 * Get the overlays. 13878 * @return {Array} The overlays array. 13879 */ 13880 this.getOverlays = function() { return overlays; }; 13881 13882 /** 13883 * Get the geometry of the image. 13884 * @return {Object} The size of the image. 13885 */ 13886 this.getGeometry = function() { return geometry; }; 13887 13888 /** 13889 * Get the data buffer of the image. 13890 * @todo dangerous... 13891 * @return {Array} The data buffer of the image. 13892 */ 13893 this.getBuffer = function() { return buffer; }; 13894 /** 13895 * Get the data buffer of the image. 13896 * @todo dangerous... 13897 * @return {Array} The data buffer of the frame. 13898 */ 13899 this.getFrame = function(frame) { return buffer[frame]; }; 13900 13901 /** 13902 * Get the rescale slope and intercept. 13903 * @param {Number} k The slice index. 13904 * @return {Object} The rescale slope and intercept. 13905 */ 13906 this.getRescaleSlopeAndIntercept = function(k) { return rsis[k]; }; 13907 /** 13908 * Set the rescale slope and intercept. 13909 * @param {Array} inRsi The input rescale slope and intercept. 13910 * @param {Number} k The slice index (optional). 13911 */ 13912 this.setRescaleSlopeAndIntercept = function(inRsi, k) { 13913 if (typeof k === 'undefined') { 13914 k = 0; 13915 } 13916 rsis[k] = inRsi; 13917 13918 // update RSI flags 13919 isIdentityRSI = true; 13920 isConstantRSI = true; 13921 for (var s = 0, lens = rsis.length; s < lens; ++s) { 13922 if (!rsis[s].isID()) { 13923 isIdentityRSI = false; 13924 } 13925 if (s > 0 && !rsis[s].equals(rsis[s - 1])) { 13926 isConstantRSI = false; 13927 } 13928 } 13929 }; 13930 /** 13931 * Are all the RSIs identity (1,0). 13932 * @return {Boolean} True if they are. 13933 */ 13934 this.isIdentityRSI = function() { return isIdentityRSI; }; 13935 /** 13936 * Are all the RSIs equal. 13937 * @return {Boolean} True if they are. 13938 */ 13939 this.isConstantRSI = function() { return isConstantRSI; }; 13940 /** 13941 * Get the photometricInterpretation of the image. 13942 * @return {String} The photometricInterpretation of the image. 13943 */ 13944 this.getPhotometricInterpretation = function() { return photometricInterpretation; }; 13945 /** 13946 * Set the photometricInterpretation of the image. 13947 * @pqrqm {String} interp The photometricInterpretation of the image. 13948 */ 13949 this.setPhotometricInterpretation = function(interp) { photometricInterpretation = interp; }; 13950 /** 13951 * Get the planarConfiguration of the image. 13952 * @return {Number} The planarConfiguration of the image. 13953 */ 13954 this.getPlanarConfiguration = function() { return planarConfiguration; }; 13955 /** 13956 * Set the planarConfiguration of the image. 13957 * @param {Number} config The planarConfiguration of the image. 13958 */ 13959 this.setPlanarConfiguration = function(config) { planarConfiguration = config; }; 13960 /** 13961 * Get the numberOfComponents of the image. 13962 * @return {Number} The numberOfComponents of the image. 13963 */ 13964 this.getNumberOfComponents = function() { return numberOfComponents; }; 13965 13966 /** 13967 * Get the meta information of the image. 13968 * @return {Object} The meta information of the image. 13969 */ 13970 this.getMeta = function() { return meta; }; 13971 /** 13972 * Set the meta information of the image. 13973 * @param {Object} rhs The meta information of the image. 13974 */ 13975 this.setMeta = function(rhs) { meta = rhs; }; 13976 13977 /** 13978 * Get value at offset. Warning: No size check... 13979 * @param {Number} offset The desired offset. 13980 * @param {Number} frame The desired frame. 13981 * @return {Number} The value at offset. 13982 */ 13983 this.getValueAtOffset = function(offset, frame) { 13984 return buffer[frame][offset]; 13985 }; 13986 13987 /** 13988 * Clone the image. 13989 * @return {Image} A clone of this image. 13990 */ 13991 this.clone = function() { 13992 // clone the image buffer 13993 var clonedBuffer = []; 13994 for (var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f) { 13995 clonedBuffer[f] = buffer[f].slice(0); 13996 } 13997 // create the image copy 13998 var copy = new dwv.image.Image(this.getGeometry(), clonedBuffer); 13999 // copy the RSIs 14000 var nslices = this.getGeometry().getSize().getNumberOfSlices(); 14001 for (var k = 0; k < nslices; ++k) { 14002 copy.setRescaleSlopeAndIntercept(this.getRescaleSlopeAndIntercept(k), k); 14003 } 14004 // copy extras 14005 copy.setPhotometricInterpretation(this.getPhotometricInterpretation()); 14006 copy.setPlanarConfiguration(this.getPlanarConfiguration()); 14007 copy.setMeta(this.getMeta()); 14008 // return 14009 return copy; 14010 }; 14011 14012 /** 14013 * Append a slice to the image. 14014 * @param {Image} The slice to append. 14015 * @return {Number} The number of the inserted slice. 14016 */ 14017 this.appendSlice = function(rhs, frame) { 14018 // check input 14019 if (rhs === null) { 14020 throw new Error("Cannot append null slice"); 14021 } 14022 var rhsSize = rhs.getGeometry().getSize(); 14023 var size = geometry.getSize(); 14024 if (rhsSize.getNumberOfSlices() !== 1) { 14025 throw new Error("Cannot append more than one slice"); 14026 } 14027 if (size.getNumberOfColumns() !== rhsSize.getNumberOfColumns()) { 14028 throw new Error("Cannot append a slice with different number of columns"); 14029 } 14030 if (size.getNumberOfRows() !== rhsSize.getNumberOfRows()) { 14031 throw new Error("Cannot append a slice with different number of rows"); 14032 } 14033 if (photometricInterpretation !== rhs.getPhotometricInterpretation()) { 14034 throw new Error("Cannot append a slice with different photometric interpretation"); 14035 } 14036 // all meta should be equal 14037 for (var key in meta) { 14038 if (meta[key] !== rhs.getMeta()[key]) { 14039 throw new Error("Cannot append a slice with different " + key); 14040 } 14041 } 14042 14043 var f = (typeof frame === "undefined") ? 0 : frame; 14044 14045 // calculate slice size 14046 var mul = 1; 14047 if (photometricInterpretation === "RGB" || photometricInterpretation === "YBR_FULL_422") { 14048 mul = 3; 14049 } 14050 var sliceSize = mul * size.getSliceSize(); 14051 14052 // create the new buffer 14053 var newBuffer = dwv.dicom.getTypedArray( 14054 buffer[f].BYTES_PER_ELEMENT * 8, 14055 meta.IsSigned ? 1 : 0, 14056 sliceSize * (size.getNumberOfSlices() + 1)); 14057 14058 // append slice at new position 14059 var newSliceNb = geometry.getSliceIndex(rhs.getGeometry().getOrigin()); 14060 if (newSliceNb === 0) { 14061 newBuffer.set(rhs.getFrame(f)); 14062 newBuffer.set(buffer[f], sliceSize); 14063 } else if (newSliceNb === size.getNumberOfSlices()) { 14064 newBuffer.set(buffer[f]); 14065 newBuffer.set(rhs.getFrame(f), size.getNumberOfSlices() * sliceSize); 14066 } else { 14067 var offset = newSliceNb * sliceSize; 14068 newBuffer.set(buffer[f].subarray(0, offset - 1)); 14069 newBuffer.set(rhs.getFrame(f), offset); 14070 newBuffer.set(buffer[f].subarray(offset), offset + sliceSize); 14071 } 14072 14073 // update geometry 14074 geometry.appendOrigin(rhs.getGeometry().getOrigin(), newSliceNb); 14075 // update rsi
14076 rsis.splice(newSliceNb, 0, rhs.getRescaleSlopeAndIntercept(0)); 14077 14078 // copy to class variables 14079 buffer[f] = newBuffer; 14080 14081 // insert overlay information of the slice to the image 14082 overlays.splice(newSliceNb, 0, rhs.getOverlays()[0]); 14083 14084 // return the appended slice number 14085 return newSliceNb; 14086 }; 14087 14088 /** 14089 * Append a frame buffer to the image. 14090 * @param {Object} frameBuffer The frame buffer to append. 14091 */ 14092 this.appendFrameBuffer = function(frameBuffer) { 14093 buffer.push(frameBuffer); 14094 }; 14095 14096 /** 14097 * Get the data range. 14098 * @return {Object} The data range. 14099 */ 14100 this.getDataRange = function() { 14101 if (!dataRange) { 14102 dataRange = this.calculateDataRange(); 14103 } 14104 return dataRange; 14105 }; 14106 14107 /** 14108 * Get the rescaled data range. 14109 * @return {Object} The rescaled data range. 14110 */ 14111 this.getRescaledDataRange = function() { 14112 if (!rescaledDataRange) { 14113 rescaledDataRange = this.calculateRescaledDataRange(); 14114 } 14115 return rescaledDataRange; 14116 }; 14117 14118 /** 14119 * Get the histogram. 14120 * @return {Array} The histogram. 14121 */ 14122 this.getHistogram = function() { 14123 if (!histogram) { 14124 var res = this.calculateHistogram(); 14125 dataRange = res.dataRange; 14126 rescaledDataRange = res.rescaledDataRange; 14127 histogram = res.histogram; 14128 } 14129 return histogram; 14130 }; 14131 }; 14132 14133 /** 14134 * Get the value of the image at a specific coordinate. 14135 * @param {Number} i The X index. 14136 * @param {Number} j The Y index. 14137 * @param {Number} k The Z index. 14138 * @param {Number} f The frame number. 14139 * @return {Number} The value at the desired position. 14140 * Warning: No size check... 14141 */ 14142 dwv.image.Image.prototype.getValue = function(i, j, k, f) { 14143 var frame = (f || 0); 14144 var index = new dwv.math.Index3D(i, j, k); 14145 return this.getValueAtOffset(this.getGeometry().indexToOffset(index), frame); 14146 }; 14147 14148 /** 14149 * Get the rescaled value of the image at a specific coordinate. 14150 * @param {Number} i The X index. 14151 * @param {Number} j The Y index. 14152 * @param {Number} k The Z index. 14153 * @param {Number} f The frame number. 14154 * @return {Number} The rescaled value at the desired position. 14155 * Warning: No size check... 14156 */ 14157 dwv.image.Image.prototype.getRescaledValue = function(i, j, k, f) { 14158 var frame = (f || 0); 14159 var val = this.getValue(i, j, k, frame); 14160 if (!this.isIdentityRSI()) { 14161 val = this.getRescaleSlopeAndIntercept(k).apply(val); 14162 } 14163 return val; 14164 }; 14165 14166 /** 14167 * Calculate the data range of the image. 14168 * WARNING: for speed reasons, only calculated on the first frame... 14169 * @return {Object} The range {min, max}. 14170 */ 14171 dwv.image.Image.prototype.calculateDataRange = function() { 14172 var size = this.getGeometry().getSize().getTotalSize(); 14173 var nFrames = 1; //this.getNumberOfFrames(); 14174 var min = this.getValueAtOffset(0, 0); 14175 var max = min; 14176 var value = 0; 14177 for (var f = 0; f < nFrames; ++f) { 14178 for (var i = 0; i < size; ++i) { 14179 value = this.getValueAtOffset(i, f); 14180 if (value > max) { max = value; } 14181 if (value < min) { min = value; } 14182 } 14183 } 14184 // return 14185 return { "min": min, "max": max }; 14186 }; 14187 14188 /** 14189 * Calculate the rescaled data range of the image. 14190 * WARNING: for speed reasons, only calculated on the first frame... 14191 * @return {Object} The range {min, max}. 14192 */ 14193 dwv.image.Image.prototype.calculateRescaledDataRange = function() { 14194 if (this.isIdentityRSI()) { 14195 return this.getDataRange(); 14196 } else if (this.isConstantRSI()) { 14197 var range = this.getDataRange(); 14198 var resmin = this.getRescaleSlopeAndIntercept(0).apply(range.min); 14199 var resmax = this.getRescaleSlopeAndIntercept(0).apply(range.max); 14200 return { 14201 "min": ((resmin < resmax) ? resmin : resmax), 14202 "max": ((resmin > resmax) ? resmin : resmax) 14203 }; 14204 } else { 14205 var size = this.getGeometry().getSize(); 14206 var nFrames = 1; //this.getNumberOfFrames(); 14207 var rmin = this.getRescaledValue(0, 0, 0); 14208 var rmax = rmin; 14209 var rvalue = 0; 14210 for (var f = 0, nframes = nFrames; f < nframes; ++f) { 14211 for (var k = 0, nslices = size.getNumberOfSlices(); k < nslices; ++k) { 14212 for (var j = 0, nrows = size.getNumberOfRows(); j < nrows; ++j) { 14213 for (var i = 0, ncols = size.getNumberOfColumns(); i < ncols; ++i) { 14214 rvalue = this.getRescaledValue(i, j, k, f); 14215 if (rvalue > rmax) { rmax = rvalue; } 14216 if (rvalue < rmin) { rmin = rvalue; } 14217 } 14218 } 14219 } 14220 } 14221 // return 14222 return { "min": rmin, "max": rmax }; 14223 } 14224 }; 14225 14226 /** 14227 * Calculate the histogram of the image. 14228 * @return {Object} The histogram, data range and rescaled data range. 14229 */ 14230 dwv.image.Image.prototype.calculateHistogram = function() { 14231 var size = this.getGeometry().getSize(); 14232 var histo = []; 14233 var min = this.getValue(0, 0, 0); 14234 var max = min; 14235 var value = 0; 14236 var rmin = this.getRescaledValue(0, 0, 0); 14237 var rmax = rmin; 14238 var rvalue = 0; 14239 for (var f = 0, nframes = this.getNumberOfFrames(); f < nframes; ++f) { 14240 for (var k = 0, nslices = size.getNumberOfSlices(); k < nslices; ++k) { 14241 for (var j = 0, nrows = size.getNumberOfRows(); j < nrows; ++j) { 14242 for (var i = 0, ncols = size.getNumberOfColumns(); i < ncols; ++i) { 14243 value = this.getValue(i, j, k, f); 14244 if (value > max) { max = value; } 14245 if (value < min) { min = value; } 14246 rvalue = this.getRescaleSlopeAndIntercept(k).apply(value); 14247 if (rvalue > rmax) { rmax = rvalue; } 14248 if (rvalue < rmin) { rmin = rvalue; } 14249 histo[rvalue] = (histo[rvalue] || 0) + 1; 14250 } 14251 } 14252 } 14253 } 14254 // set data range 14255 var dataRange = { "min": min, "max": max }; 14256 var rescaledDataRange = { "min": rmin, "max": rmax }; 14257 // generate data for plotting 14258 var histogram = []; 14259 for (var b = rmin; b <= rmax; ++b) { 14260 histogram.push([b, (histo[b] || 0)]); 14261 } 14262 // return 14263 return { 14264 'dataRange': dataRange, 14265 'rescaledDataRange': rescaledDataRange, 14266 'histogram': histogram 14267 }; 14268 }; 14269 14270 /** 14271 * Convolute the image with a given 2D kernel. 14272 * @param {Array} weights The weights of the 2D kernel as a 3x3 matrix. 14273 * @return {Image} The convoluted image. 14274 * Note: Uses the raw buffer values. 14275 */ 14276 dwv.image.Image.prototype.convolute2D = function(weights) { 14277 if (weights.length !== 9) { 14278 throw new Error("The convolution matrix does not have a length of 9; it has " + weights.length); 14279 } 14280 14281 var newImage = this.clone(); 14282 var newBuffer = newImage.getBuffer(); 14283 14284 var imgSize = this.getGeometry().getSize(); 14285 var ncols = imgSize.getNumberOfColumns(); 14286 var nrows = imgSize.getNumberOfRows(); 14287 var nslices = imgSize.getNumberOfSlices(); 14288 var nframes = this.getNumberOfFrames(); 14289 var ncomp = this.getNumberOfComponents(); 14290 14291 // adapt to number of component and planar configuration 14292 var factor = 1;
14293 var componentOffset = 1; 14294 var frameOffset = imgSize.getTotalSize(); 14295 if (ncomp === 3) { 14296 frameOffset *= 3; 14297 if (this.getPlanarConfiguration() === 0) { 14298 factor = 3; 14299 } else { 14300 componentOffset = imgSize.getTotalSize(); 14301 } 14302 } 14303 14304 // allow special indent for matrices 14305 /*jshint indent:false */ 14306 14307 // default weight offset matrix 14308 var wOff = []; 14309 wOff[0] = (-ncols - 1) * factor; 14310 wOff[1] = (-ncols) * factor; 14311 wOff[2] = (-ncols + 1) * factor; 14312 wOff[3] = -factor; 14313 wOff[4] = 0; 14314 wOff[5] = 1 * factor; 14315 wOff[6] = (ncols - 1) * factor; 14316 wOff[7] = (ncols) * factor; 14317 wOff[8] = (ncols + 1) * factor; 14318 14319 // border weight offset matrices 14320 // borders are extended (see http://en.wikipedia.org/wiki/Kernel_%28image_processing%29) 14321 14322 // i=0, j=0 14323 var wOff00 = []; 14324 wOff00[0] = wOff[4]; 14325 wOff00[1] = wOff[4]; 14326 wOff00[2] = wOff[5]; 14327 wOff00[3] = wOff[4]; 14328 wOff00[4] = wOff[4]; 14329 wOff00[5] = wOff[5]; 14330 wOff00[6] = wOff[7]; 14331 wOff00[7] = wOff[7]; 14332 wOff00[8] = wOff[8]; 14333 // i=0, j=* 14334 var wOff0x = []; 14335 wOff0x[0] = wOff[1]; 14336 wOff0x[1] = wOff[1]; 14337 wOff0x[2] = wOff[2]; 14338 wOff0x[3] = wOff[4]; 14339 wOff0x[4] = wOff[4]; 14340 wOff0x[5] = wOff[5]; 14341 wOff0x[6] = wOff[7]; 14342 wOff0x[7] = wOff[7]; 14343 wOff0x[8] = wOff[8]; 14344 // i=0, j=nrows 14345 var wOff0n = []; 14346 wOff0n[0] = wOff[1]; 14347 wOff0n[1] = wOff[1]; 14348 wOff0n[2] = wOff[2]; 14349 wOff0n[3] = wOff[4]; 14350 wOff0n[4] = wOff[4]; 14351 wOff0n[5] = wOff[5]; 14352 wOff0n[6] = wOff[4]; 14353 wOff0n[7] = wOff[4]; 14354 wOff0n[8] = wOff[5]; 14355 14356 // i=*, j=0 14357 var wOffx0 = []; 14358 wOffx0[0] = wOff[3]; 14359 wOffx0[1] = wOff[4]; 14360 wOffx0[2] = wOff[5]; 14361 wOffx0[3] = wOff[3]; 14362 wOffx0[4] = wOff[4]; 14363 wOffx0[5] = wOff[5]; 14364 wOffx0[6] = wOff[6]; 14365 wOffx0[7] = wOff[7]; 14366 wOffx0[8] = wOff[8]; 14367 // i=*, j=* -> wOff 14368 // i=*, j=nrows 14369 var wOffxn = []; 14370 wOffxn[0] = wOff[0]; 14371 wOffxn[1] = wOff[1]; 14372 wOffxn[2] = wOff[2]; 14373 wOffxn[3] = wOff[3]; 14374 wOffxn[4] = wOff[4]; 14375 wOffxn[5] = wOff[5]; 14376 wOffxn[6] = wOff[3]; 14377 wOffxn[7] = wOff[4]; 14378 wOffxn[8] = wOff[5]; 14379 14380 // i=ncols, j=0 14381 var wOffn0 = []; 14382 wOffn0[0] = wOff[3]; 14383 wOffn0[1] = wOff[4]; 14384 wOffn0[2] = wOff[4]; 14385 wOffn0[3] = wOff[3]; 14386 wOffn0[4] = wOff[4]; 14387 wOffn0[5] = wOff[4]; 14388 wOffn0[6] = wOff[6]; 14389 wOffn0[7] = wOff[7]; 14390 wOffn0[8] = wOff[7]; 14391 // i=ncols, j=* 14392 var wOffnx = []; 14393 wOffnx[0] = wOff[0]; 14394 wOffnx[1] = wOff[1]; 14395 wOffnx[2] = wOff[1]; 14396 wOffnx[3] = wOff[3]; 14397 wOffnx[4] = wOff[4]; 14398 wOffnx[5] = wOff[4]; 14399 wOffnx[6] = wOff[6]; 14400 wOffnx[7] = wOff[7]; 14401 wOffnx[8] = wOff[7]; 14402 // i=ncols, j=nrows 14403 var wOffnn = []; 14404 wOffnn[0] = wOff[0]; 14405 wOffnn[1] = wOff[1]; 14406 wOffnn[2] = wOff[1]; 14407 wOffnn[3] = wOff[3]; 14408 wOffnn[4] = wOff[4]; 14409 wOffnn[5] = wOff[4]; 14410 wOffnn[6] = wOff[3]; 14411 wOffnn[7] = wOff[4]; 14412 wOffnn[8] = wOff[4]; 14413 14414 // restore indent for rest of method 14415 /*jshint indent:4 */ 14416 14417 // loop vars 14418 var pixelOffset = 0; 14419 var newValue = 0; 14420 var wOffFinal = []; 14421 // go through the destination image pixels 14422 for (var f = 0; f < nframes; f++) { 14423 pixelOffset = f * frameOffset; 14424 for (var c = 0; c < ncomp; c++) { 14425 // special component offset 14426 pixelOffset += c * componentOffset; 14427 for (var k = 0; k < nslices; k++) { 14428 for (var j = 0; j < nrows; j++) { 14429 for (var i = 0; i < ncols; i++) { 14430 wOffFinal = wOff; 14431 // special border cases 14432 if (i === 0 && j === 0) { 14433 wOffFinal = wOff00; 14434 } else if (i === 0 && j === (nrows - 1)) { 14435 wOffFinal = wOff0n; 14436 } else if (i === (ncols - 1) && j === 0) { 14437 wOffFinal = wOffn0; 14438 } else if (i === (ncols - 1) && j === (nrows - 1)) { 14439 wOffFinal = wOffnn; 14440 } else if (i === 0 && j !== (nrows - 1) && j !== 0) { 14441 wOffFinal = wOff0x; 14442 } else if (i === (ncols - 1) && j !== (nrows - 1) && j !== 0) { 14443 wOffFinal = wOffnx; 14444 } else if (i !== 0 && i !== (ncols - 1) && j === 0) { 14445 wOffFinal = wOffx0; 14446 } else if (i !== 0 && i !== (ncols - 1) && j === (nrows - 1)) { 14447 wOffFinal = wOffxn; 14448 } 14449 14450 // calculate the weighed sum of the source image pixels that 14451 // fall under the convolution matrix 14452 newValue = 0; 14453 for (var wi = 0; wi < 9; ++wi) { 14454 newValue += this.getValueAtOffset(pixelOffset + wOffFinal[wi], f) * weights[wi]; 14455 } 14456 newBuffer[f][pixelOffset] = newValue; 14457 // increment pixel offset 14458 pixelOffset += factor; 14459 } 14460 } 14461 } 14462 } 14463 } 14464 return newImage; 14465 }; 14466 14467 /** 14468 * Transform an image using a specific operator. 14469 * WARNING: no size check! 14470 * @param {Function} operator The operator to use when transforming. 14471 * @return {Image} The transformed image. 14472 * Note: Uses the raw buffer values. 14473 */ 14474 dwv.image.Image.prototype.transform = function(operator) { 14475 var newImage = this.clone(); 14476 var newBuffer = newImage.getBuffer(); 14477 for (var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f) { 14478 for (var i = 0, leni = newBuffer[f].length; i < leni; ++i) { 14479 newBuffer[f][i] = operator(newImage.getValueAtOffset(i, f)); 14480 } 14481 } 14482 return newImage; 14483 }; 14484 14485 /** 14486 * Compose this image with another one and using a specific operator. 14487 * WARNING: no size check! 14488 * @param {Image} rhs The image to compose with. 14489 * @param {Function} operator The operator to use when composing. 14490 * @return {Image} The composed image. 14491 * Note: Uses the raw buffer values. 14492 */ 14493 dwv.image.Image.prototype.compose = function(rhs, operator) { 14494 var newImage = this.clone(); 14495 var newBuffer = newImage.getBuffer(); 14496 for (var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f) { 14497 for (var i = 0, leni = newBuffer[f].length; i < leni; ++i) { 14498 // using the operator on the local buffer, i.e. the latest (not original) data 14499 newBuffer[f][i] = Math.floor(operator(this.getValueAtOffset(i, f), rhs.getValueAtOffset(i, f))); 14500 } 14501 } 14502 return newImage; 14503 }; 14504 14505 /** 14506 * Quantify a line according to image information. 14507 * @param {Object} line The line to quantify. 14508 * @return {Object} A quantification object. 14509 */ 14510 dwv.image.Image.prototype.quantifyLine = function(line) { 14511 var quant = {}; 14512 // length 14513 var spacing = this.getGeometry().getSpacing(); 14514 var length = line.getWorldLength(spacing.getColumnSpacing(), 14515 spacing.getRowSpacing()); 14516 if (length !== null) { 14517 quant.length = { "value": length, "unit": dwv.i18n("unit.mm") }; 14518 } 14519 // return 14520 return quant; 14521 }; 14522 14523 /** 14524 * Quantify a rectangle according to image information. 14525 * @param {Object} rect The rectangle to quantify. 14526 * @return {Object} A quantification object. 14527 */ 14528 dwv.image.Image.prototype.quantifyRect = function(rect) { 14529 var quant = {}; 14530 // surface 14531 var spacing = this.getGeometry().getSpacing(); 14532 var surface = rect.getWorldSurface(spacing.getColumnSpacing(), 14533 spacing.getRowSpacing()); 14534 if (surface !== null) { 14535 quant.surface = { "value": surface / 100, "unit": dwv.i18n("unit.cm2") }; 14536 } 14537 // stats 14538 var subBuffer = []; 14539 var minJ = parseInt(rect.getBegin().getY(), 10); 14540 var maxJ = parseInt(rect.getEnd().getY(), 10); 14541 var minI = parseInt(rect.getBegin().getX(), 10); 14542 var maxI = parseInt(rect.getEnd().getX(), 10); 14543 for (var j = minJ; j < maxJ; ++j) { 14544 for (var i = minI; i < maxI; ++i) { 14545 subBuffer.push(this.getValue(i, j, 0)); 14546 } 14547 } 14548 var quantif = dwv.math.getStats(subBuffer); 14549 quant.min = { "value": quantif.min, "unit": "" }; 14550 quant.max = { "value": quantif.max, "unit": "" }; 14551 quant.mean = { "value": quantif.mean, "unit": "" }; 14552 quant.stdDev = { "value": quantif.stdDev, "unit": "" }; 14553 // return 14554 return quant; 14555 }; 14556 14557 /** 14558 * Quantify an ellipse according to image information. 14559 * @param {Object} ellipse The ellipse to quantify. 14560 * @return {Object} A quantification object. 14561 */ 14562 dwv.image.Image.prototype.quantifyEllipse = function(ellipse) { 14563 var quant = {}; 14564 // surface 14565 var spacing = this.getGeometry().getSpacing(); 14566 var surface = ellipse.getWorldSurface(spacing.getColumnSpacing(), 14567 spacing.getRowSpacing()); 14568 if (surface !== null) { 14569 quant.surface = { "value": surface / 100, "unit": dwv.i18n("unit.cm2") }; 14570 } 14571 // return 14572 return quant; 14573 }; 14574 14575 /** 14576 * {@link dwv.image.Image} factory. 14577 * @constructor 14578 */ 14579 dwv.image.ImageFactory = function() {}; 14580 14581 /** 14582 * Get an {@link dwv.image.Image} object from the read DICOM file. 14583 * @param {Object} dicomElements The DICOM tags. 14584 * @param {Array} pixelBuffer The pixel buffer. 14585 * @return {View} A new Image. 14586 */ 14587 dwv.image.ImageFactory.prototype.create = function(dicomElements, pixelBuffer) { 14588 // columns 14589 var columns = dicomElements.getFromKey("x00280011"); 14590 if (!columns) { 14591 throw new Error("Missing or empty DICOM image number of columns"); 14592 } 14593 // rows 14594 var rows = dicomElements.getFromKey("x00280010"); 14595 if (!rows) { 14596 throw new Error("Missing or empty DICOM image number of rows"); 14597 } 14598 // image size 14599 var size = new dwv.image.Size(columns, rows); 14600 14601 // spacing 14602 var rowSpacing = null; 14603 var columnSpacing = null; 14604 // PixelSpacing 14605 var pixelSpacing = dicomElements.getFromKey("x00280030"); 14606 // ImagerPixelSpacing 14607 var imagerPixelSpacing = dicomElements.getFromKey("x00181164"); 14608 if (pixelSpacing && pixelSpacing[0] && pixelSpacing[1]) { 14609 rowSpacing = parseFloat(pixelSpacing[0]); 14610 columnSpacing = parseFloat(pixelSpacing[1]); 14611 } else if (imagerPixelSpacing && imagerPixelSpacing[0] && imagerPixelSpacing[1]) { 14612 rowSpacing = parseFloat(imagerPixelSpacing[0]); 14613 columnSpacing = parseFloat(imagerPixelSpacing[1]); 14614 } 14615 // image spacing 14616 var spacing = new dwv.image.Spacing(columnSpacing, rowSpacing); 14617 14618 // TransferSyntaxUID 14619 var transferSyntaxUID = dicomElements.getFromKey("x00020010"); 14620 var syntax = dwv.dicom.cleanString(transferSyntaxUID); 14621 var jpeg2000 = dwv.dicom.isJpeg2000TransferSyntax(syntax); 14622 var jpegBase = dwv.dicom.isJpegBaselineTransferSyntax(syntax); 14623 var jpegLoss = dwv.dicom.isJpegLosslessTransferSyntax(syntax); 14624 14625 // slice position 14626 var slicePosition = new Array(0, 0, 0); 14627 // ImagePositionPatient 14628 var imagePositionPatient = dicomElements.getFromKey("x00200032"); 14629 if (imagePositionPatient) { 14630 slicePosition = [parseFloat(imagePositionPatient[0]), 14631 parseFloat(imagePositionPatient[1]), 14632 parseFloat(imagePositionPatient[2]) 14633 ]; 14634 } 14635 14636 // slice orientation 14637 var imageOrientationPatient = dicomElements.getFromKey("x00200037"); 14638 var orientationMatrix; 14639 if (imageOrientationPatient) { 14640 var rowCosines = new dwv.math.Vector3D(parseFloat(imageOrientationPatient[0]), 14641 parseFloat(imageOrientationPatient[1]), 14642 parseFloat(imageOrientationPatient[2])); 14643 var colCosines = new dwv.math.Vector3D(parseFloat(imageOrientationPatient[3]), 14644 parseFloat(imageOrientationPatient[4]), 14645 parseFloat(imageOrientationPatient[5])); 14646 var normal = rowCosines.crossProduct(colCosines); 14647 orientationMatrix = new dwv.math.Matrix33( 14648 rowCosines.getX(), rowCosines.getY(), rowCosines.getZ(), 14649 colCosines.getX(), colCosines.getY(), colCosines.getZ(), 14650 normal.getX(), normal.getY(), normal.getZ()); 14651 } 14652 14653 // geometry 14654 var origin = new dwv.math.Point3D(slicePosition[0], slicePosition[1], slicePosition[2]); 14655 var geometry = new dwv.image.Geometry(origin, size, spacing, orientationMatrix); 14656 14657 // image 14658 var image = new dwv.image.Image(geometry, pixelBuffer); 14659 // PhotometricInterpretation 14660 var photometricInterpretation = dicomElements.getFromKey("x00280004"); 14661 if (photometricInterpretation) { 14662 var photo = dwv.dicom.cleanString(photometricInterpretation).toUpperCase(); 14663 // jpeg decoders output RGB data 14664 if ((jpeg2000 || jpegBase || jpegLoss) && 14665 (photo !== "MONOCHROME1" && photo !== "MONOCHROME2")) { 14666 photo = "RGB"; 14667 } 14668 image.setPhotometricInterpretation(photo); 14669 } 14670 // PlanarConfiguration 14671 var planarConfiguration = dicomElements.getFromKey("x00280006"); 14672 if (planarConfiguration) { 14673 image.setPlanarConfiguration(planarConfiguration); 14674 } 14675 14676 // rescale slope and intercept 14677 var slope = 1; 14678 // RescaleSlope 14679 var rescaleSlope = dicomElements.getFromKey("x00281053"); 14680 if (rescaleSlope) { 14681 slope = parseFloat(rescaleSlope); 14682 } 14683 var intercept = 0; 14684 // RescaleIntercept 14685 var rescaleIntercept = dicomElements.getFromKey("x00281052"); 14686 if (rescaleIntercept) { 14687 intercept = parseFloat(rescaleIntercept); 14688 } 14689 var rsi = new dwv.image.RescaleSlopeAndIntercept(slope, intercept); 14690 image.setRescaleSlopeAndIntercept(rsi); 14691 14692 // meta information 14693 var meta = {}; 14694 // Modality 14695 var modality = dicomElements.getFromKey("x00080060"); 14696 if (modality) { 14697 meta.Modality = modality; 14698 } 14699 // StudyInstanceUID 14700 var studyInstanceUID = dicomElements.getFromKey("x0020000D"); 14701 if (studyInstanceUID) { 14702 meta.StudyInstanceUID = studyInstanceUID; 14703 } 14704 // SeriesInstanceUID 14705 var seriesInstanceUID = dicomElements.getFromKey("x0020000E"); 14706 if (seriesInstanceUID) { 14707 meta.SeriesInstanceUID = seriesInstanceUID; 14708 } 14709 // BitsStored 14710 var bitsStored = dicomElements.getFromKey("x00280101"); 14711 if (bitsStored) { 14712 meta.BitsStored = parseInt(bitsStored, 10); 14713 } 14714 // PixelRepresentation -> is signed 14715 var pixelRepresentation = dicomElements.getFromKey("x00280103"); 14716 meta
14716.IsSigned = false; 14717 if (pixelRepresentation) { 14718 meta.IsSigned = (pixelRepresentation === 1); 14719 } 14720 image.setMeta(meta); 14721 14722 // overlay 14723 image.setFirstOverlay(dwv.gui.info.createOverlays(dicomElements)); 14724 14725 return image; 14726 }; 14727 14728 // namespaces 14729 var dwv = dwv || {}; 14730 dwv.image = dwv.image || {}; 14731 /** @namespace */ 14732 dwv.image.lut = dwv.image.lut || {}; 14733 14734 /** 14735 * Rescale LUT class. 14736 * @constructor 14737 * @param {Object} rsi The rescale slope and intercept. 14738 * @param {Number} bitsStored The number of bits used to store the data. 14739 */ 14740 dwv.image.lut.Rescale = function(rsi, bitsStored) { 14741 /** 14742 * The internal array. 14743 * @private 14744 * @type Array 14745 */ 14746 var lut = null; 14747 14748 /** 14749 * Flag to know if the lut is ready or not. 14750 * @private 14751 * @type Boolean 14752 */ 14753 var isReady = false; 14754 14755 /** 14756 * The size of the LUT array. 14757 * @private 14758 * @type Number 14759 */ 14760 var length = Math.pow(2, bitsStored); 14761 14762 /** 14763 * Get the Rescale Slope and Intercept (RSI). 14764 * @return {Object} The rescale slope and intercept. 14765 */ 14766 this.getRSI = function() { return rsi; }; 14767 14768 /** 14769 * Is the lut ready to use or not? If not, the user must 14770 * call 'initialise'. 14771 * @return {Boolean} True if the lut is ready to use. 14772 */ 14773 this.isReady = function() { return isReady; }; 14774 14775 /** 14776 * Initialise the LUT. 14777 */ 14778 this.initialise = function() { 14779 // check if already initialised 14780 if (isReady) { 14781 return; 14782 } 14783 // create lut and fill it 14784 lut = new Float32Array(length); 14785 for (var i = 0; i < length; ++i) { 14786 lut[i] = rsi.apply(i); 14787 } 14788 // update ready flag 14789 isReady = true; 14790 }; 14791 14792 /** 14793 * Get the length of the LUT array. 14794 * @return {Number} The length of the LUT array. 14795 */ 14796 this.getLength = function() { return length; }; 14797 14798 /** 14799 * Get the value of the LUT at the given offset. 14800 * @return {Number} The value of the LUT at the given offset. 14801 */ 14802 this.getValue = function(offset) { 14803 return lut[offset]; 14804 }; 14805 }; 14806 14807 /** 14808 * Window LUT class. 14809 * @constructor 14810 * @param {Number} rescaleLut The associated rescale LUT. 14811 * @param {Boolean} isSigned Flag to know if the data is signed or not. 14812 */ 14813 dwv.image.lut.Window = function(rescaleLut, isSigned) { 14814 /** 14815 * The internal array: Uint8ClampedArray clamps between 0 and 255. 14816 * @private 14817 * @type Array 14818 */ 14819 var lut = null; 14820 14821 /** 14822 * The window level. 14823 * @private 14824 * @type {Object} 14825 */ 14826 var windowLevel = null; 14827 14828 /** 14829 * Flag to know if the lut is ready or not. 14830 * @private 14831 * @type Boolean 14832 */ 14833 var isReady = false; 14834 14835 /** 14836 * Shift for signed data. 14837 * @private 14838 * @type Number 14839 */ 14840 var signedShift = 0; 14841 14842 /** 14843 * Get the window / level. 14844 * @return {Object} The window / level. 14845 */ 14846 this.getWindowLevel = function() { return windowLevel; }; 14847 /** 14848 * Get the signed flag. 14849 * @return {Boolean} The signed flag. 14850 */ 14851 this.isSigned = function() { return isSigned; }; 14852 /** 14853 * Get the rescale lut. 14854 * @return {Object} The rescale lut. 14855 */ 14856 this.getRescaleLut = function() { return rescaleLut; }; 14857 14858 /** 14859 * Is the lut ready to use or not? If not, the user must 14860 * call 'update'. 14861 * @return {Boolean} True if the lut is ready to use. 14862 */ 14863 this.isReady = function() { return isReady; }; 14864 14865 /** 14866 * Set the window center and width. 14867 * @param {Object} wl The window level. 14868 */ 14869 this.setWindowLevel = function(wl) { 14870 // store the window values 14871 windowLevel = wl; 14872 // possible signed shift 14873 signedShift = 0; 14874 windowLevel.setSignedOffset(0); 14875 if (isSigned) { 14876 var size = rescaleLut.getLength(); 14877 signedShift = size / 2; 14878 windowLevel.setSignedOffset(rescaleLut.getRSI().getSlope() * signedShift); 14879 } 14880 // update ready flag 14881 isReady = false; 14882 }; 14883 14884 /** 14885 * Update the lut if needed.. 14886 */ 14887 this.update = function() { 14888 // check if we need to update 14889 if (isReady) { 14890 return; 14891 } 14892 14893 // check rescale lut 14894 if (!rescaleLut.isReady()) { 14895 rescaleLut.initialise(); 14896 } 14897 // create window lut 14898 var size = rescaleLut.getLength(); 14899 if (!lut) { 14900 // use clamped array (polyfilled in browser.js) 14901 lut = new Uint8ClampedArray(size); 14902 } 14903 // by default WindowLevel returns a value in the [0,255] range 14904 // this is ok with regular Arrays and ClampedArray. 14905 for (var i = 0; i < size; ++i) { 14906 lut[i] = windowLevel.apply(rescaleLut.getValue(i)); 14907 } 14908 14909 // update ready flag 14910 isReady = true; 14911 }; 14912 14913 /** 14914 * Get the length of the LUT array. 14915 * @return {Number} The length of the LUT array. 14916 */ 14917 this.getLength = function() { return lut.length; }; 14918 14919 /** 14920 * Get the value of the LUT at the given offset. 14921 * @return {Number} The value of the LUT at the given offset. 14922 */ 14923 this.getValue = function(offset) { 14924 return lut[offset + signedShift]; 14925 }; 14926 }; 14927 14928 /** 14929 * Lookup tables for image colour display. 14930 */ 14931 14932 dwv.image.lut.range_max = 256; 14933 14934 dwv.image.lut.buildLut = function(func) { 14935 var lut = []; 14936 for (var i = 0; i < dwv.image.lut.range_max; ++i) { 14937 lut.push(func(i)); 14938 } 14939 return lut; 14940 }; 14941 14942 dwv.image.lut.max = function( /*i*/ ) { 14943 return dwv.image.lut.range_max - 1; 14944 }; 14945 14946 dwv.image.lut.maxFirstThird = function(i) { 14947 if (i < dwv.image.lut.range_max / 3) { 14948 return dwv.image.lut.range_max - 1; 14949 } 14950 return 0; 14951 }; 14952 14953 dwv.image.lut.maxSecondThird = function(i) { 14954 var third = dwv.image.lut.range_max / 3; 14955 if (i >= third && i < 2 * third) { 14956 return dwv.image.lut.range_max - 1; 14957 } 14958 return 0; 14959 }; 14960 14961 dwv.image.lut.maxThirdThird = function(i) { 14962 if (i >= 2 * dwv.image.lut.range_max / 3) { 14963 return dwv.image.lut.range_max - 1; 14964 } 14965 return 0; 14966 }; 14967 14968 dwv.image.lut.toMaxFirstThird = function(i) { 14969 var val = i * 3; 14970 if (val > dwv.image.lut.range_max - 1) { 14971 return dwv.image.lut.range_max - 1; 14972 } 14973 return val; 14974 }; 14975 14976 dwv.image.lut.toMaxSecondThird = function(i) { 14977 var third = dwv.image.lut.range_max / 3; 14978 var val = 0; 14979 if (i >= third) { 14980 val = (i - third) * 3; 14981 if (val > dwv.image.lut.range_max - 1) { 14982 return dwv.image.lut.range_max - 1; 14983 } 14984 } 14985 return val; 14986 }; 14987 14988 dwv.image.lut.toMaxThirdThird = function(i) { 14989 var third = dwv.image.lut.range_max / 3; 14990 var val = 0; 14991 if (i >= 2 * third) { 14992 val = (i - 2 * third) * 3; 14993 if (val > dwv.image.lut.range_max - 1) { 14994 return dwv.image.lut.range_max - 1; 14995 } 14996 } 14997 return val; 14998 }; 14999 15000 dwv.image.lut.zero = function( /*i*/ ) { 15001 return 0; 15002 }; 15003 15004 dwv.image.lut.id = function(i) { 15005 return i; 15006 }; 15007 15008 dwv.image.lut.invId = function(i) { 15009 return (dwv.image.lut.range_max - 1) - i; 15010 }; 15011 15012 // plain 15013 dwv.image.lut.plain = { 15014 "red": dwv.image.lut.buildLut(dwv.image.lut.id), 15015 "green": dwv.image.lut.buildLut(dwv.image.lut.id), 15016 "blue": dwv.image.lut.buildLut(dwv.image.lut.id) 15017 }; 15018 15019 // inverse plain 15020 dwv.image.lut.invPlain = { 15021 "red": dwv.image.lut.buildLut(dwv.image.lut.invId), 15022 "green": dwv.image.lut.buildLut(dwv.image.lut.invId), 15023 "blue": dwv.image.lut.buildLut(dwv.image.lut.invId) 15024 }; 15025 15026 //rainbow 15027 dwv.image.lut.rainbow = { 15028 "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,
15028128, 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], 15029 "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], 15030 "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] 15031 }; 15032 15033 // hot 15034 dwv.image.lut.hot = { 15035 "red": dwv.image.lut.buildLut(dwv.image.lut.toMaxFirstThird), 15036 "green": dwv.image.lut.buildLut(dwv.image.lut.toMaxSecondThird), 15037 "blue": dwv.image.lut.buildLut(dwv.image.lut.toMaxThirdThird) 15038 }; 15039 15040 // hot iron 15041 dwv.image.lut.hot_iron = { 15042 "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], 15043 "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,
15043134, 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], 15044 "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] 15045 }; 15046 15047 // pet 15048 dwv.image.lut.pet = { 15049 "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], 15050 "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], 15051 "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,
15051128, 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] 15052 }; 15053 15054 // hot metal blue 15055 dwv.image.lut.hot_metal_blue = { 15056 "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], 15057 "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], 15058 "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] 15059 }; 15060 15061 // pet 20 step 15062 dwv.image.lut.pet_20step = { 15063 "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], 15064 "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], 15065 "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] 15066 }; 15067 15068 // test 15069 dwv.image.lut.test = { 15070 "red": dwv.image.lut.buildLut(dwv.image.lut.id), 15071 "green": dwv.image.lut.buildLut(dwv.image.lut.zero), 15072 "blue": dwv.image.lut.buildLut(dwv.image.lut.zero) 15073 }; 15074 15075 //red 15076 /*dwv.image.lut.red = { 15077 "red": dwv.image.lut.buildLut(dwv.image.lut.max), 15078 "green": dwv.image.lut.buildLut(dwv.image.lut.id), 15079 "blue": dwv.image.lut.buildLut(dwv.image.lut.id) 15080 };*/ 15081 15082 // namespaces 15083 var dwv = dwv || {}; 15084 dwv.image = dwv.image || {}; 15085 15086 /** 15087 * WindowLevel class. 15088 * References: 15089 * - 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} 15090 * Pseudo-code: 15091 * if (x <= c - 0.5 - (w-1)/2), then y = ymin 15092 * else if (x > c - 0.5 + (w-1)/2), then y = ymax, 15093 * else y = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax - ymin) + ymin 15094 */ 15095 dwv.image.WindowLevel = function(center, width) { 15096 // avoid zero width 15097 if (width === 0) { 15098 throw new Error("A window level with a width of zero is not possible."); 15099 } 15100 15101 /** 15102 * Signed data offset. 15103 * @private 15104 * @type Number 15105 */ 15106 var signedOffset = 0; 15107 /** 15108 * Output value minimum. 15109 * @private 15110 * @type Number 15111 */ 15112 var ymin = 0; 15113 /** 15114 * Output value maximum. 15115 * @private 15116 * @type Number 15117 */ 15118 var ymax = 255; 15119 15120 /** 15121 * Input value minimum (calculated). 15122 * @private 15123 * @type Number 15124 */ 15125 var xmin = null; 15126 /** 15127 * Input value maximum (calculated). 15128 * @private 15129 * @type Number 15130 */ 15131 var xmax = null; 15132 /** 15133 * Window level equation slope (calculated). 15134 * @private 15135 * @type Number 15136 */ 15137 var slope = null; 15138 /** 15139 * Window level equation intercept (calculated). 15140 * @private 15141 * @type Number 15142 */ 15143 var inter = null; 15144 15145 /** 15146 * Initialise members. 15147 */
15148 function init() { 15149 var c = center + signedOffset; 15150 // from the standard 15151 xmin = c - 0.5 - ((width - 1) / 2); 15152 xmax = c - 0.5 + ((width - 1) / 2); 15153 // develop the equation: 15154 // y = ( ( x - (c - 0.5) ) / (w-1) + 0.5 ) * (ymax - ymin) + ymin 15155 // y = ( x / (w-1) ) * (ymax - ymin) + ( -(c - 0.5) / (w-1) + 0.5 ) * (ymax - ymin) + ymin 15156 slope = (ymax - ymin) / (width - 1); 15157 inter = (-(c - 0.5) / (width - 1) + 0.5) * (ymax - ymin) + ymin; 15158 } 15159 15160 // call init 15161 init(); 15162 15163 /** 15164 * Get the window center. 15165 * @return {Number} The window center. 15166 */ 15167 this.getCenter = function() { return center; }; 15168 /** 15169 * Get the window width. 15170 * @return {Number} The window width. 15171 */ 15172 this.getWidth = function() { return width; }; 15173 15174 /** 15175 * Set the output value range. 15176 * @param {Number} min The output value minimum. 15177 * @param {Number} max The output value maximum. 15178 */ 15179 this.setRange = function(min, max) { 15180 ymin = parseInt(min, 10); 15181 ymax = parseInt(max, 10); 15182 // re-initialise 15183 init(); 15184 }; 15185 /** 15186 * Set the signed offset. 15187 * @param {Number} The signed data offset, typically: slope * ( size / 2). 15188 */ 15189 this.setSignedOffset = function(offset) { 15190 signedOffset = offset; 15191 // re-initialise 15192 init(); 15193 }; 15194 15195 /** 15196 * Apply the window level on an input value. 15197 * @param {Number} The value to rescale as an integer. 15198 * @return {Number} The leveled value, in the 15199 * [ymin, ymax] range (default [0,255]). 15200 */ 15201 this.apply = function(value) { 15202 if (value <= xmin) { 15203 return ymin; 15204 } else if (value > xmax) { 15205 return ymax; 15206 } else { 15207 return parseInt(((value * slope) + inter), 10); 15208 } 15209 }; 15210 15211 }; 15212 15213 /** 15214 * Check for window level equality. 15215 * @param {Object} rhs The other window level to compare to. 15216 * @return {Boolean} True if both window level are equal. 15217 */ 15218 dwv.image.WindowLevel.prototype.equals = function(rhs) { 15219 return rhs !== null && 15220 this.getCenter() === rhs.getCenter() && 15221 this.getWidth() === rhs.getWidth(); 15222 }; 15223 15224 /** 15225 * Get a string representation of the window level. 15226 * @return {String} The window level as a string. 15227 */ 15228 dwv.image.WindowLevel.prototype.toString = function() { 15229 return (this.getCenter() + ", " + this.getWidth()); 15230 }; 15231 15232 /** 15233 * View class. 15234 * @constructor 15235 * @param {Image} image The associated image. 15236 * Need to set the window lookup table once created 15237 * (either directly or with helper methods). 15238 */ 15239 dwv.image.View = function(image) { 15240 /** 15241 * Window lookup tables, indexed per Rescale Slope and Intercept (RSI). 15242 * @private 15243 * @type Window 15244 */ 15245 var windowLuts = {}; 15246 15247 /** 15248 * Window presets. 15249 * Minmax will be filled at first use (see view.setWindowLevelPreset). 15250 * @private 15251 * @type Object 15252 */ 15253 var windowPresets = { "minmax": { "name": "minmax" } }; 15254 15255 /** 15256 * Current window preset name. 15257 * @private
15258 * @type String 15259 */ 15260 var currentPresetName = null; 15261 15262 /** 15263 * colour map. 15264 * @private 15265 * @type Object 15266 */ 15267 var colourMap = dwv.image.lut.plain; 15268 /** 15269 * Current position. 15270 * @private 15271 * @type Object 15272 */ 15273 var currentPosition = { "i": 0, "j": 0, "k": 0 }; 15274 /** 15275 * Current frame. Zero based. 15276 * @private 15277 * @type Number 15278 */ 15279 var currentFrame = null; 15280 15281 /** 15282 * Get the associated image. 15283 * @return {Image} The associated image. 15284 */ 15285 this.getImage = function() { return image; }; 15286 /** 15287 * Set the associated image. 15288 * @param {Image} inImage The associated image. 15289 */ 15290 this.setImage = function(inImage) { image = inImage; }; 15291 15292 /** 15293 * Get the window LUT of the image. 15294 * Warning: can be undefined in no window/level was set. 15295 * @param {Object} rsi Optional image rsi, will take the one of the current slice otherwise. 15296 * @return {Window} The window LUT of the image. 15297 */ 15298 this.getCurrentWindowLut = function(rsi) { 15299 var sliceNumber = this.getCurrentPosition().k; 15300 // use current rsi if not provided 15301 if (typeof rsi === "undefined") { 15302 rsi = image.getRescaleSlopeAndIntercept(sliceNumber); 15303 } 15304 // get the lut 15305 var wlut = windowLuts[rsi.toString()]; 15306 15307 // special case for 'perslice' presets 15308 if (currentPresetName && 15309 typeof windowPresets[currentPresetName] !== "undefined" && 15310 typeof windowPresets[currentPresetName].perslice !== "undefined" && 15311 windowPresets[currentPresetName].perslice === true) { 15312 // get the preset for this slice 15313 var wl = windowPresets[currentPresetName].wl[sliceNumber]; 15314 // apply it if different from previous 15315 if (!wlut.getWindowLevel().equals(wl)) { 15316 // previous values 15317 var previousWidth = wlut.getWindowLevel().getWidth(); 15318 var previousCenter = wlut.getWindowLevel().getCenter(); 15319 // set slice window level 15320 wlut.setWindowLevel(wl); 15321 // fire event 15322 if (previousWidth !== wl.getWidth()) { 15323 this.fireEvent({ 15324 "type": "wl-width-change", 15325 "wc": wl.getCenter(), 15326 "ww": wl.getWidth(), 15327 "skipGenerate": true 15328 }); 15329 } 15330 if (previousCenter !== wl.getCenter()) { 15331 this.fireEvent({ 15332 "type": "wl-center-change", 15333 "wc": wl.getCenter(), 15334 "ww": wl.getWidth(), 15335 "skipGenerate": true 15336 }); 15337 } 15338 } 15339 } 15340 15341 // update in case of wl change 15342 // TODO: should not be run in a getter... 15343 wlut.update(); 15344 15345 // return 15346 return wlut; 15347 }; 15348 /** 15349 * Add the window LUT to the list. 15350 * @param {Window} wlut The window LUT of the image. 15351 */ 15352 this.addWindowLut = function(wlut) { 15353 var rsi = wlut.getRescaleLut().getRSI(); 15354 windowLuts[rsi.toString()] = wlut; 15355 }; 15356 15357 /** 15358 * Get the window presets. 15359 * @return {Object} The window presets. 15360 */ 15361 this.getWindowPresets = function() { 15362 return windowPresets; 15363 }; 15364 15365 /** 15366 * Get the window presets names. 15367 * @return {Object} The list of window presets names. 15368 */ 15369 this.getWindowPresetsNames = function() { 15370 return Object.keys(windowPresets); 15371 }; 15372 15373 /** 15374 * Set the window presets. 15375 * @param {Object} presets The window presets. 15376 */ 15377 this.setWindowPresets = function(presets) { 15378 windowPresets = presets; 15379 }; 15380 15381 /** 15382 * Set the default colour map. 15383 * @param {Object} map The colour map. 15384 */ 15385 this.setDefaultColourMap = function(map) { 15386 colourMap = map; 15387 }; 15388 15389 /** 15390 * Add window presets to the existing ones. 15391 * @param {Object} presets The window presets. 15392 * @param {Number} k The slice the preset belong to. 15393 */ 15394 this.addWindowPresets = function(presets, k) { 15395 var keys = Object.keys(presets); 15396 var key = null; 15397 for (var i = 0; i < keys.length; ++i) { 15398 key = keys[i]; 15399 if (typeof windowPresets[key] !== "undefined") { 15400 if (typeof windowPresets[key].perslice !== "undefined" && 15401 windowPresets[key].perslice === true) { 15402 // use first new preset wl... 15403 windowPresets[key].wl.splice(k, 0, presets[key].wl[0]); 15404 } else { 15405 windowPresets[key] = presets[key]; 15406 } 15407 } else { 15408 // add new 15409 windowPresets[key] = presets[key]; 15410 } 15411 } 15412 }; 15413 15414 /** 15415 * Get the colour map of the image. 15416 * @return {Object} The colour map of the image. 15417 */ 15418 this.getColourMap = function() { return colourMap; }; 15419 /** 15420 * Set the colour map of the image. 15421 * @param {Object} map The colour map of the image. 15422 */ 15423 this.setColourMap = function(map) { 15424 colourMap = map; 15425 this.fireEvent({ 15426 "type": "colour-change", 15427 "wc": this.getCurrentWindowLut().getWindowLevel().getCenter(), 15428 "ww": this.getCurrentWindowLut().getWindowLevel().getWidth() 15429 }); 15430 }; 15431 15432 /** 15433 * Get the current position. 15434 * @return {Object} The current position. 15435 */ 15436 this.getCurrentPosition = function() { 15437 // return a clone to avoid reference problems 15438 return { "i": currentPosition.i, "j": currentPosition.j, "k": currentPosition.k }; 15439 }; 15440 /** 15441 * Set the current position. 15442 * @param {Object} pos The current position. 15443 * @param {Boolean} silent If true, does not fire a slice-change event. 15444 * @return {Boolean} False if not in bounds 15445 */ 15446 this.setCurrentPosition = function(pos, silent) { 15447 // default silent flag to false 15448 if (typeof silent === "undefined") { 15449 silent = false; 15450 } 15451 // check if possible 15452 if (!image.getGeometry().getSize().isInBounds(pos.i, pos.j, pos.k)) { 15453 return false; 15454 } 15455 var oldPosition = currentPosition; 15456 currentPosition = pos; 15457 15458 // fire a 'position-change' event 15459 if (image.getPhotometricInterpretation().match(/MONOCHROME/) !== null) { 15460 this.fireEvent({ 15461 "type": "position-change", 15462 "i": pos.i, 15463 "j": pos.j, 15464 "k": pos.k, 15465 "value": image.getRescaledValue(pos.i, pos.j, pos.k, this.getCurrentFrame()) 15466 }); 15467 } else { 15468 this.fireEvent({ 15469 "type": "position-change", 15470 "i": pos.i, 15471 "j": pos.j, 15472 "k": pos.k 15473 }); 15474 } 15475 15476 // fire a slice change event (used to trigger redraw) 15477 if (!silent) { 15478 if (oldPosition.k !== currentPosition.k) { 15479 this.fireEvent({ "type": "slice-change" }); 15480 } 15481 } 15482 15483 // all good 15484 return true; 15485 }; 15486 15487 /** 15488 * Get the current frame number. 15489 * @return {Number} The current frame number. 15490 */ 15491 this.getCurrentFrame = function() { 15492 return currentFrame; 15493 }; 15494 15495 /** 15496 * Set the current frame number. 15497 * @param {Number} The current frame number. 15498 * @return {Boolean} False if not in bounds 15499 */ 15500 this.setCurrentFrame = function(frame) { 15501 // check if possible 15502 if (frame < 0 || frame >= image.getNumberOfFrames()) { 15503 return false; 15504 } 15505 // assign 15506 var oldFrame = currentFrame; 15507 currentFrame = frame; 15508 // fire event 15509 if (oldFrame !== currentFrame && image.getNumberOfFrames() !== 1) { 15510 this.fireEvent({ "type": "frame-change", "frame": currentFrame }); 15511 // silent set current position to update info text 15512 this.setCurrentPosition(this.getCurrentPosition(), true); 15513 } 15514 // all good 15515 return true; 15516 }; 15517 15518 /** 15519 * Append another view to this one. 15520 * @param {Object} rhs The view to append. 15521 */ 15522 this.append = function(rhs) { 15523 // append images 15524 var newSliceNumber = this.getImage().appendSlice(rhs.getImage()); 15525 // update position if a slice was appended before 15526 if (newSliceNumber <= this.getCurrentPosition().k) { 15527 this.setCurrentPosition({ 15528 "i": this.getCurrentPosition().i, 15529 "j": this.getCurrentPosition().j, 15530 "k": this.getCurrentPosition().k + 1 15531 }, true); 15532 } 15533 // add window presets 15534 this.addWindowPresets(rhs.getWindowPresets(), newSliceNumber); 15535 }; 15536 15537 /** 15538 * Append a frame buffer to the included image. 15539 * @param {Object} frameBuffer The frame buffer to append. 15540 */ 15541 this.appendFrameBuffer = function(frameBuffer) { 15542 this.getImage().appendFrameBuffer(frameBuffer); 15543 }; 15544 15545 /** 15546 * Set the view window/level. 15547 * @param {Number} center The window center.
15548 * @param {Number} width The window width. 15549 * @param {String} name Associated preset name, defaults to 'manual'. 15550 * Warning: uses the latest set rescale LUT or the default linear one. 15551 */ 15552 this.setWindowLevel = function(center, width, name) { 15553 // window width shall be >= 1 (see https://www.dabsoft.ch/dicom/3/C.11.2.1.2/) 15554 if (width >= 1) { 15555 15556 // get current window/level (before updating name) 15557 var sliceNumber = this.getCurrentPosition().k; 15558 var currentWl = null; 15559 var rsi = image.getRescaleSlopeAndIntercept(sliceNumber); 15560 if (rsi && typeof rsi !== "undefined") { 15561 var currentLut = windowLuts[rsi.toString()]; 15562 if (currentLut && typeof currentLut !== "undefined") { 15563 currentWl = currentLut.getWindowLevel(); 15564 } 15565 } 15566 15567 if (typeof name === "undefined") { 15568 name = "manual"; 15569 } 15570 // update current preset name 15571 currentPresetName = name; 15572 15573 var wl = new dwv.image.WindowLevel(center, width); 15574 var keys = Object.keys(windowLuts); 15575 15576 // create the first lut if none exists 15577 if (keys.length === 0) { 15578 // create the rescale lookup table 15579 var rescaleLut = new dwv.image.lut.Rescale( 15580 image.getRescaleSlopeAndIntercept(0), image.getMeta().BitsStored); 15581 // create the window lookup table 15582 var windowLut = new dwv.image.lut.Window(rescaleLut, image.getMeta().IsSigned); 15583 this.addWindowLut(windowLut); 15584 } 15585 15586 // set window level on luts 15587 for (var key in windowLuts) { 15588 windowLuts[key].setWindowLevel(wl); 15589 } 15590 15591 // fire window level change event 15592 if (currentWl && typeof currentWl !== "undefined") { 15593 if (currentWl.getWidth() !== width) { 15594 this.fireEvent({ "type": "wl-width-change", "wc": center, "ww": width }); 15595 } 15596 if (currentWl.getCenter() !== center) { 15597 this.fireEvent({ "type": "wl-center-change", "wc": center, "ww": width }); 15598 } 15599 } else { 15600 this.fireEvent({ "type": "wl-width-change", "wc": center, "ww": width }); 15601 this.fireEvent({ "type": "wl-center-change", "wc": center, "ww": width }); 15602 } 15603 } 15604 }; 15605 15606 /** 15607 * Set the window level to the preset with the input name. 15608 * @param {String} name The name of the preset to activate. 15609 */ 15610 this.setWindowLevelPreset = function(name) { 15611 var preset = this.getWindowPresets()[name]; 15612 if (typeof preset === "undefined") { 15613 throw new Error("Unknown window level preset: '" + name + "'"); 15614 } 15615 // special min/max 15616 if (name === "minmax" && typeof preset.wl === "undefined") { 15617 preset.wl = this.getWindowLevelMinMax(); 15618 } 15619 // special 'perslice' case 15620 if (typeof preset.perslice !== "undefined" && 15621 preset.perslice === true) { 15622 preset = { "wl": preset.wl[this.getCurrentPosition().k] }; 15623 } 15624 // set w/l 15625 this.setWindowLevel(preset.wl.getCenter(), preset.wl.getWidth(), name); 15626 }; 15627 15628 /** 15629 * Set the window level to the preset with the input id. 15630 * @param {Number} id The id of the preset to activate. 15631 */ 15632 this.setWindowLevelPresetById = function(id) { 15633 var keys = Object.keys(this.getWindowPresets()); 15634 this.setWindowLevelPreset(keys[id]); 15635 }; 15636 15637 /** 15638 * Clone the image using all meta data and the original data buffer. 15639 * @return {View} A full copy of this {dwv.image.View}. 15640 */ 15641 this.clone = function() { 15642 var copy = new dwv.image.View(this.getImage()); 15643 for (var key in windowLuts) { 15644 copy.addWindowLut(windowLuts[key]); 15645 } 15646 copy.setListeners(this.getListeners()); 15647 return copy; 15648 }; 15649 15650 /** 15651 * View listeners 15652 * @private 15653 * @type Object 15654 */ 15655 var listeners = {}; 15656 /** 15657 * Get the view listeners. 15658 * @return {Object} The view listeners. 15659 */ 15660 this.getListeners = function() { return listeners; }; 15661 /** 15662 * Set the view listeners. 15663 * @param {Object} list The view listeners. 15664 */ 15665 this.setListeners = function(list) { listeners = list; }; 15666 }; 15667 15668 /** 15669 * Get the image window/level that covers the full data range. 15670 * Warning: uses the latest set rescale LUT or the default linear one. 15671 */ 15672 dwv.image.View.prototype.getWindowLevelMinMax = function() { 15673 var range = this.getImage().getRescaledDataRange(); 15674 var min = range.min; 15675 var max = range.max; 15676 var width = max - min; 15677 var center = min + width / 2; 15678 return new dwv.image.WindowLevel(center, width); 15679 }; 15680 15681 /** 15682 * Set the image window/level to cover the full data range. 15683 * Warning: uses the latest set rescale LUT or the default linear one. 15684 */ 15685 dwv.image.View.prototype.setWindowLevelMinMax = function() { 15686 // calculate center and width 15687 var wl = this.getWindowLevelMinMax(); 15688 // set window level 15689 this.setWindowLevel(wl.getCenter(), wl.getWidth(), "minmax"); 15690 }; 15691 15692 /** 15693 * Generate display image data to be given to a canvas. 15694 * @param {Array} array The array to fill in. 15695 */ 15696 dwv.image.View.prototype.generateImageData = function(array) { 15697 var windowLut = this.getCurrentWindowLut(); 15698 15699 var image = this.getImage(); 15700 var sliceSize = image.getGeometry().getSize().getSliceSize(); 15701 var sliceOffset = sliceSize * this.getCurrentPosition().k; 15702 var frame = (this.getCurrentFrame()) ? this.getCurrentFrame() : 0; 15703 15704 var index = 0; 15705 var pxValue = 0; 15706 var stepPos = 0; 15707 15708 var photoInterpretation = image.getPhotometricInterpretation(); 15709 switch (photoInterpretation) { 15710 case "MONOCHROME1": 15711 case "MONOCHROME2": 15712 var colourMap = this.getColourMap(); 15713 var iMax = sliceOffset + sliceSize; 15714 for (var i = sliceOffset; i < iMax; ++i) { 15715 pxValue = parseInt(windowLut.getValue( 15716 image.getValueAtOffset(i, frame)), 10); 15717 array.data[index] = colourMap.red[pxValue]; 15718 array.data[index + 1] = colourMap.green[pxValue]; 15719 array.data[index + 2] = colourMap.blue[pxValue]; 15720 array.data[index + 3] = 0xff; 15721 index += 4; 15722 } 15723 break; 15724 15725 case "RGB": 15726 // 3 times bigger... 15727 sliceOffset *= 3; 15728 // the planar configuration defines the memory layout 15729 var planarConfig = image.getPlanarConfiguration(); 15730 if (planarConfig !== 0 && planarConfig !== 1) { 15731 throw new Error("Unsupported planar configuration: " + planarConfig); 15732 } 15733 // default: RGBRGBRGBRGB... 15734 var posR = sliceOffset; 15735 var posG = sliceOffset + 1; 15736 var posB = sliceOffset + 2; 15737 stepPos = 3; 15738 // RRRR...GGGG...BBBB... 15739 if (planarConfig === 1) { 15740 posR = sliceOffset; 15741 posG = sliceOffset + sliceSize; 15742 posB = sliceOffset + 2 * sliceSize; 15743 stepPos = 1; 15744 } 15745 15746 for (var j = 0; j < sliceSize; ++j) { 15747 array.data[index] = parseInt(windowLut.getValue( 15748 image.getValueAtOffset(posR, frame)), 10); 15749 array.data[index + 1] = parseInt(windowLut.getValue( 15750 image.getValueAtOffset(posG, frame)), 10); 15751 array.data[index + 2] = parseInt(windowLut.getValue( 15752 image.getValueAtOffset(posB, frame)), 10); 15753 array.data[index + 3] = 0xff; 15754 index += 4; 15755 15756 posR += stepPos; 15757 posG += stepPos; 15758 posB += stepPos; 15759 } 15760 break; 15761 15762 case "YBR_FULL_422": 15763 // theory: 15764 // http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.7.html#sect_C.7.6.3.1.2 15765 // reverse equation: 15766 // https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion 15767 15768 // 3 times bigger... 15769 sliceOffset *= 3; 15770 // the planar configuration defines the memory layout 15771 var planarConfigYBR = image.getPlanarConfiguration(); 15772 if (planarConfigYBR !== 0 && planarConfigYBR !== 1) { 15773 throw new Error("Unsupported planar configuration: " + planarConfigYBR); 15774 } 15775 // default: YBRYBRYBR... 15776 var posY = sliceOffset; 15777 var posCB = sliceOffset + 1; 15778 var posCR = sliceOffset + 2; 15779 stepPos = 3; 15780 // YYYY...BBBB...RRRR... 15781 if (planarConfigYBR === 1) { 15782 posY = sliceOffset; 15783 posCB = sliceOffset + sliceSize; 15784 posCR = sliceOffset + 2 * sliceSize; 15785 stepPos = 1; 15786 } 15787 15788 var y, cb, cr; 15789 var r, g, b; 15790 for (var k = 0; k < sliceSize; ++k) { 15791 y = image.getValueAtOffset(posY, frame); 15792 cb = image.getValueAtOffset(posCB, frame); 15793 cr = image.getValueAtOffset(posCR, frame); 15794 15795 r = y + 1.402 * (cr - 128); 15796 g = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128); 15797 b = y + 1.772 * (cb - 128); 15798 15799 array.data[index] = parseInt(windowLut.getValue(r), 10); 15800 array.data[index + 1] = parseInt(windowLut.getValue(g), 10); 15801 array.data[index + 2] = parseInt(windowLut.getValue(b), 10); 15802 array.data[index + 3] = 0xff; 15803 index += 4; 15804 15805 posY += stepPos; 15806 posCB += stepPos; 15807 posCR += stepPos; 15808 } 15809 break; 15810 15811 default: 15812 throw new Error("Unsupported photometric interpretation: " + photoInterpretation); 15813 } 15814 }; 15815 15816 /** 15817 * Add an event listener on the view. 15818 * @param {String} type The event type. 15819 * @param {Object} listener The method associated with the provided event type. 15820 */ 15821 dwv.image.View.prototype.addEventListener = function(type, listener) { 15822 var listeners = this.getListeners(); 15823 if (!listeners[type]) { 15824 listeners[type] = []; 15825 } 15826 listeners[type].push(listener); 15827 }; 15828 15829 /** 15830 * Remove an event listener on the view. 15831 * @param {String} type The event type. 15832 * @param {Object} listener The method associated with the provided event type. 15833 */ 15834 dwv.image.View.prototype.removeEventListener = function(type, listener) { 15835 var listeners = this.getListeners(); 15836 if (!listeners[type]) { 15837 return; 15838 } 15839 for (var i = 0; i < listeners[type].length; ++i) { 15840 if (listeners[type][i] === listener) { 15841 listeners[type].splice(i, 1); 15842 } 15843 } 15844 }; 15845 15846 /** 15847 * Fire an event: call all associated listeners. 15848 * @param {Object} event The event to fire. 15849 */ 15850 dwv.image.View.prototype.fireEvent = function(event) { 15851 var listeners = this.getListeners(); 15852 if (!listeners[event.type]) { 15853 return; 15854 } 15855 for (var i = 0; i < listeners[event.type].length; ++i) { 15856 listeners[event.type][i](event); 15857 } 15858 }; 15859 15860 /** 15861 * View factory. 15862 * @constructor 15863 */ 15864 dwv.image.ViewFactory = function() {}; 15865 15866 /** 15867 * Get an View object from the read DICOM file. 15868 * @param {Object} dicomElements The DICOM tags. 15869 * @param {Object} image The associated image. 15870 * @return {View} The new View. 15871 */ 15872 dwv.image.ViewFactory.prototype.create = function(dicomElements, image) { 15873 // view 15874 var view = new dwv.image.View(image); 15875 15876 // default color map 15877 if (image.getPhotometricInterpretation() === "MONOCHROME1") { 15878 view.setDefaultColourMap(dwv.image.lut.invPlain); 15879 } 15880 15881 // presets 15882 var windowPresets = {}; 15883 15884 // DICOM presets 15885 var windowCenter = dicomElements.getFromKey("x00281050", true); 15886 var windowWidth = dicomElements.getFromKey("x00281051", true); 15887 var windowCWExplanation = dicomElements.getFromKey("x00281055", true); 15888 if (windowCenter && windowWidth) { 15889 var name; 15890 for (var j = 0; j < windowCenter.length; ++j) { 15891 var center = parseFloat(windowCenter[j], 10); 15892 var width = parseFloat(windowWidth[j], 10); 15893 if (center && width) { 15894 name = ""; 15895 if (windowCWExplanation) { 15896 name = dwv.dicom.cleanString(windowCWExplanation[j]); 15897 } 15898 if (name === "") { 15899 name = "Default" + j; 15900 } 15901 windowPresets[name] = { 15902 "wl": [new dwv.image.WindowLevel(center, width)], 15903 "name": name, 15904 "perslice": true 15905 }; 15906 } 15907 } 15908 } 15909 15910 // min/max 15911 // Not filled yet since it is stil too costly to calculate min/max 15912 // for each slice... It will be filled at first use (see view.setWindowLevelPreset). 15913 // Order is important, if no wl from DICOM, this will be the default. 15914 windowPresets.minmax = { "name": "minmax" }; 15915 15916 // optional modality presets 15917 if (typeof dwv.tool.defaultpresets !== "undefined") { 15918 var modality = image.getMeta().Modality; 15919 for (var key in dwv.tool.defaultpresets[modality]) { 15920 var preset = dwv.tool.defaultpresets[modality][key]; 15921 windowPresets[key] = { 15922 "wl": new dwv.image.WindowLevel(preset.center, preset.width), 15923 "name": key 15924 }; 15925 } 15926 } 15927 15928 // store 15929 view.setWindowPresets(windowPresets); 15930 15931 return view; 15932 }; 15933 15934 // namespaces 15935 var dwv = dwv || {}; 15936 dwv.io = dwv.io || {}; 15937 15938 /** 15939 * DICOM data loader. 15940 */ 15941 dwv.io.DicomDataLoader = function() { 15942 // closure to self 15943 var self = this; 15944 15945 /** 15946 * Loader options. 15947 * @private 15948 * @type Object 15949 */ 15950 var options = {}; 15951 15952 /** 15953 * Loading flag. 15954 * @private 15955 * @type Boolean 15956 */ 15957 var isLoading = false; 15958 15959 /** 15960 * Set the loader options. 15961 * @param {Object} opt The input options. 15962 */ 15963 this.setOptions = function(opt) { 15964 options = opt; 15965 }; 15966 15967 /** 15968 * Is the load ongoing? 15969 * @return {Boolean} True if loading. 15970 */ 15971 this.isLoading = function() { 15972 return isLoading; 15973 }; 15974 15975 /** 15976 * DICOM buffer to dwv.image.View (asynchronous) 15977 */ 15978 var db2v =
15978 new dwv.image.DicomBufferToView(); 15979 15980 /** 15981 * Load data. 15982 * @param {Object} buffer The DICOM buffer. 15983 * @param {String} origin The data origin. 15984 * @param {Number} index The data index. 15985 */ 15986 this.load = function(buffer, origin, index) { 15987 // set loading flag 15988 isLoading = true; 15989 // set character set 15990 if (typeof options.defaultCharacterSet !== "undefined") { 15991 db2v.setDefaultCharacterSet(options.defaultCharacterSet); 15992 } 15993 // connect handlers 15994 db2v.onload = self.onload; 15995 db2v.onloadend = function() { 15996 // reset loading flag 15997 isLoading = false; 15998 // call listeners 15999 self.onloadend(); 16000 }; 16001 db2v.onprogress = self.onprogress; 16002 // convert 16003 try { 16004 db2v.convert(buffer, index); 16005 } catch (error) { 16006 // TODO: error will be for individual file, isLoading is global... 16007 //isLoading = false; 16008 self.onerror(error); 16009 } 16010 }; 16011 16012 /** 16013 * Abort load. 16014 */ 16015 this.abort = function() { 16016 // abort conversion 16017 db2v.abort(); 16018 // reset loading flag 16019 isLoading = false; 16020 // call listeners 16021 self.onabort({ message: "Abort while loading DICOM data." }); 16022 }; 16023 16024 /** 16025 * Get a file load handler. 16026 * @param {Object} file The file to load. 16027 * @param {Number} index The index 'id' of the file. 16028 * @return {Function} A file load handler. 16029 */ 16030 this.getFileLoadHandler = function(file, index) { 16031 return function(event) { 16032 self.load(event.target.result, file, index); 16033 }; 16034 }; 16035 16036 /** 16037 * Get a url load handler. 16038 * @param {String} url The url to load. 16039 * @param {Number} index The index 'id' of the url. 16040 * @return {Function} A url load handler. 16041 */ 16042 this.getUrlLoadHandler = function(url, index) { 16043 return function( /*event*/ ) { 16044 // check response status 16045 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes 16046 // status 200: "OK"; status 0: "debug" 16047 if (this.status !== 200 && this.status !== 0) { 16048 self.onerror({ 16049 'name': "RequestError", 16050 'message': "Error status: " + this.status + 16051 " while loading '" + url + "' [DicomDataLoader]" 16052 }); 16053 return; 16054 } 16055 // load 16056 self.load(this.response, url, index); 16057 }; 16058 }; 16059 16060 }; // class DicomDataLoader 16061 16062 /** 16063 * Check if the loader can load the provided file. 16064 * @param {Object} file The file to check. 16065 * @return True if the file can be loaded. 16066 */ 16067 dwv.io.DicomDataLoader.prototype.canLoadFile = function(file) { 16068 var split = file.name.split('.'); 16069 var ext = ""; 16070 if (split.length !== 1) { 16071 ext = split.pop().toLowerCase(); 16072 } 16073 var hasExt = (ext.length !== 0); 16074 return !hasExt || (ext === "dcm"); 16075 }; 16076 16077 /** 16078 * Check if the loader can load the provided url. 16079 * @param {String} url The url to check. 16080 * @return True if the url can be loaded. 16081 */ 16082 dwv.io.DicomDataLoader.prototype.canLoadUrl = function(url) { 16083 var split = url.split('.'); 16084 var ext = ""; 16085 if (split.length !== 1) { 16086 ext = split.pop().toLowerCase(); 16087 } 16088 var hasExt = (ext.length !== 0) && (ext.length < 5); 16089 // wado url 16090 var isDicomContentType = (url.indexOf("contentType=application/dicom") !== -1); 16091 16092 return isDicomContentType || (ext === "dcm") || !hasExt; 16093 }; 16094 16095 /** 16096 * Get the file content type needed by the loader. 16097 * @return One of the 'dwv.io.fileContentTypes'. 16098 */ 16099 dwv.io.DicomDataLoader.prototype.loadFileAs = function() { 16100 return dwv.io.fileContentTypes.ArrayBuffer; 16101 }; 16102 16103 /** 16104 * Get the url content type needed by the loader. 16105 * @return One of the 'dwv.io.urlContentTypes'. 16106 */ 16107 dwv.io.DicomDataLoader.prototype.loadUrlAs = function() { 16108 return dwv.io.urlContentTypes.ArrayBuffer; 16109 }; 16110 16111 /** 16112 * Handle a load event. 16113 * @param {Object} event The load event, 'event.target' 16114 * should be the loaded data. 16115 * Default does nothing. 16116 */ 16117 dwv.io.DicomDataLoader.prototype.onload = function( /*event*/ ) {}; 16118 /** 16119 * Handle an load end event. 16120 * Default does nothing. 16121 */ 16122 dwv.io.DicomDataLoader.prototype.onloadend = function() {}; 16123 /** 16124 * Handle a progress event. 16125 * @param {Object} event The progress event. 16126 * Default does nothing. 16127 */ 16128 dwv.io.DicomDataLoader.prototype.onprogress = function( /*event*/ ) {}; 16129 /** 16130 * Handle an error event. 16131 * @param {Object} event The error event with an 16132 * optional 'event.message'. 16133 * Default does nothing. 16134 */ 16135 dwv.io.DicomDataLoader.prototype.onerror = function( /*event*/ ) {}; 16136 /** 16137 * Handle an abort event. 16138 * @param {Object} event The abort event with an 16139 * optional 'event.message'. 16140 * Default does nothing. 16141 */ 16142 dwv.io.DicomDataLoader.prototype.onabort = function( /*event*/ ) {}; 16143 16144 /**
16145 * Add to Loader list. 16146 */ 16147 dwv.io.loaderList = dwv.io.loaderList || []; 16148 dwv.io.loaderList.push("DicomDataLoader"); 16149 16150 // namespaces 16151 var dwv = dwv || {}; 16152 /** @namespace */ 16153 dwv.io = dwv.io || {}; 16154 16155 // file content types 16156 dwv.io.fileContentTypes = { 16157 'Text': 0, 16158 'ArrayBuffer': 1, 16159 'DataURL': 2 16160 }; 16161 16162 /** 16163 * Files loader. 16164 * @constructor 16165 */ 16166 dwv.io.FilesLoader = function() { 16167 /** 16168 * Closure to self. 16169 * @private 16170 * @type Object 16171 */ 16172 var self = this; 16173 16174 /** 16175 * Array of launched readers (used in abort). 16176 * @private 16177 * @type Array 16178 */ 16179 var readers = []; 16180 16181 /** 16182 * Launched loader (used in abort). 16183 * @private 16184 * @type Object 16185 */ 16186 var runningLoader = []; 16187 16188 /** 16189 * Number of data to load. 16190 * @private 16191 * @type Number 16192 */ 16193 var nToLoad = 0; 16194 /** 16195 * Number of loaded data. 16196 * @private 16197 * @type Number 16198 */ 16199 var nLoaded = 0; 16200 16201 /** 16202 * The default character set (optional). 16203 * @private
16204 * @type String 16205 */ 16206 var defaultCharacterSet; 16207 16208 /** 16209 * Get the default character set. 16210 * @return {String} The default character set. 16211 */ 16212 this.getDefaultCharacterSet = function() { 16213 return defaultCharacterSet; 16214 }; 16215 16216 /** 16217 * Set the default character set. 16218 * @param {String} characterSet The character set. 16219 */ 16220 this.setDefaultCharacterSet = function(characterSet) { 16221 defaultCharacterSet = characterSet; 16222 }; 16223 16224 /** 16225 * Store a launched reader. 16226 * @param {Object} request The launched reader. 16227 */ 16228 this.storeReader = function(reader) { 16229 readers.push(reader); 16230 }; 16231 16232 /** 16233 * Clear the stored readers. 16234 */ 16235 this.clearStoredReaders = function() { 16236 readers = []; 16237 }; 16238 16239 /** 16240 * Store the launched loader. 16241 * @param {Object} loader The launched loader. 16242 */ 16243 this.storeLoader = function(loader) { 16244 runningLoader = loader; 16245 }; 16246 16247 /** 16248 * Clear the stored loader. 16249 */ 16250 this.clearStoredLoader = function() { 16251 runningLoader = null; 16252 }; 16253 16254 /** 16255 * Abort a URLs load. 16256 */ 16257 this.abort = function() { 16258 // abort readers 16259 for (var i = 0; i < readers.length; ++i) { 16260 // 0: EMPTY, 1: LOADING, 2: DONE 16261 if (readers[i].readyState === 1) { 16262 readers[i].abort(); 16263 } 16264 } 16265 this.clearStoredReaders(); 16266 // abort loader 16267 if (runningLoader) { 16268 runningLoader.abort(); 16269 } 16270 this.clearStoredLoader(); 16271 }; 16272 16273 /** 16274 * Set the number of data to load. 16275 * @param {Number} n The number of data to load. 16276 */ 16277 this.setNToLoad = function(n) { 16278 nToLoad = n; 16279 }; 16280 16281 /** 16282 * Increment the number of loaded data 16283 * and call onloadend if loaded all data. 16284 */ 16285 this.addLoaded = function() { 16286 nLoaded++; 16287 if (nLoaded === nToLoad) { 16288 self.onloadend(); 16289 } 16290 }; 16291 16292 }; // class File 16293 16294 /** 16295 * Handle a load event. 16296 * @param {Object} event The load event, 'event.target' 16297 * should be the loaded data. 16298 * Default does nothing. 16299 */ 16300 dwv.io.FilesLoader.prototype.onload = function( /*event*/ ) {}; 16301 /** 16302 * Handle a load end event. 16303 * Default does nothing. 16304 */ 16305 dwv.io.FilesLoader.prototype.onloadend = function() {}; 16306 /** 16307 * Handle a progress event. 16308 * @param {Object} event The progress event. 16309 * Default does nothing. 16310 */ 16311 dwv.io.FilesLoader.prototype.onprogress = function( /*event*/ ) {}; 16312 /** 16313 * Handle an error event. 16314 * @param {Object} event The error event with an 16315 * optional 'event.message'. 16316 * Default does nothing. 16317 */ 16318 dwv.io.FilesLoader.prototype.onerror = function( /*event*/ ) {}; 16319 /** 16320 * Handle an abort event. 16321 * @param {Object} event The abort event with an 16322 * optional 'event.message'. 16323 * Default does nothing. 16324 */ 16325 dwv.io.FilesLoader.prototype.onabort = function( /*event*/ ) {}; 16326 16327 /** 16328 * Load a list of files. 16329 * @param {Array} ioArray The list of files to load. 16330 * @external FileReader 16331 */ 16332 dwv.io.FilesLoader.prototype.load = function(ioArray) { 16333 // clear storage 16334 this.clearStoredReaders(); 16335 this.clearStoredLoader(); 16336 16337 // closure to self for handlers 16338 var self = this; 16339 // set the number of data to load 16340 this.setNToLoad(ioArray.length); 16341 16342 var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress); 16343 mproghandler.setNToLoad(ioArray.length); 16344 16345 // get loaders 16346 var loaders = []; 16347 for (var m = 0; m < dwv.io.loaderList.length; ++m) { 16348 loaders.push(new dwv.io[dwv.io.loaderList[m]]()); 16349 } 16350 16351 // set loaders callbacks 16352 var loader = null; 16353 for (var k = 0; k < loaders.length; ++k) { 16354 loader = loaders[k]; 16355 loader.onload = self.onload; 16356 loader.onloadend = self.addLoaded; 16357 loader.onerror = self.onerror; 16358 loader.onabort = self.onabort; 16359 loader.setOptions({ 16360 'defaultCharacterSet': this.getDefaultCharacterSet() 16361 }); 16362 loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1); 16363 } 16364 16365 // request onerror handler 16366 var getReaderOnError = function(origin) { 16367 return function(event) { 16368 var message = "An error occurred while reading '" + origin + "'"; 16369 if (typeof event.getMessage !== "undefined") {
16370 message += " (" + event.getMessage() + ")"; 16371 } 16372 message += "."; 16373 self.onerror({ 'name': "FileReaderError", 'message': message }); 16374 }; 16375 }; 16376 16377 // request onabort handler 16378 var getReaderOnAbort = function(origin) { 16379 return function() { 16380 self.onabort({ 'message': "Abort while reading '" + origin + "'" }); 16381 }; 16382 }; 16383 16384 // loop on I/O elements 16385 for (var i = 0; i < ioArray.length; ++i) { 16386 var file = ioArray[i]; 16387 var reader = new FileReader(); 16388 16389 // store reader 16390 this.storeReader(reader); 16391 16392 // bind reader progress 16393 reader.onprogress = mproghandler.getMonoProgressHandler(i, 0); 16394 16395 // find a loader 16396 var foundLoader = false; 16397 for (var l = 0; l < loaders.length; ++l) { 16398 loader = loaders[l]; 16399 if (loader.canLoadFile(file)) { 16400 foundLoader = true; 16401 // store loader 16402 this.storeLoader(loader); 16403 // set reader callbacks 16404 reader.onload = loader.getFileLoadHandler(file, i); 16405 reader.onerror = getReaderOnError(file.name); 16406 reader.onabort = getReaderOnAbort(file.name); 16407 // read 16408 if (loader.loadFileAs() === dwv.io.fileContentTypes.Text) { 16409 reader.readAsText(file); 16410 } else if (loader.loadFileAs() === dwv.io.fileContentTypes.DataURL) { 16411 reader.readAsDataURL(file); 16412 } else if (loader.loadFileAs() === dwv.io.fileContentTypes.ArrayBuffer) { 16413 reader.readAsArrayBuffer(file); 16414 } 16415 // next file 16416 break; 16417 } 16418 } 16419 // TODO: throw? 16420 if (!foundLoader) { 16421 throw new Error("No loader found for file: " + file); 16422 } 16423 } 16424 }; 16425 16426 // namespaces 16427 var dwv = dwv || {}; 16428 dwv.io = dwv.io || {}; 16429 16430 /** 16431 * JSON text loader. 16432 */ 16433 dwv.io.JSONTextLoader = function() { 16434 // closure to self 16435 var self = this; 16436 16437 /** 16438 * Loading flag. 16439 * @private 16440 * @type Boolean 16441 */ 16442 var isLoading = false; 16443 16444 /** 16445 * Set the loader options. 16446 * @param {Object} opt The input options. 16447 */ 16448 this.setOptions = function() { 16449 // does nothing 16450 }; 16451 16452 /** 16453 * Is the load ongoing? 16454 * @return {Boolean} True if loading. 16455 */ 16456 this.isLoading = function() { 16457 return isLoading; 16458 }; 16459 16460 /** 16461 * Load data. 16462 * @param {Object} text The input text. 16463 * @param {String} origin The data origin. 16464 * @param {Number} index The data index. 16465 */ 16466 this.load = function(text, origin, index) { 16467 // set loading flag 16468 isLoading = true; 16469 try { 16470 self.onload(text); 16471 // reset loading flag 16472 isLoading = false; 16473 // call listeners 16474 self.onloadend(); 16475 } catch (error) { 16476 self.onerror(error); 16477 } 16478 self.onprogress({ 16479 'type': 'read-progress', 16480 'lengthComputable': true, 16481 'loaded': 100, 16482 'total': 100, 16483 'index': index 16484 }); 16485 }; 16486 16487 /** 16488 * Abort load: pass to listeners. 16489 */ 16490 this.abort = function() { 16491 // reset loading flag 16492 isLoading = false; 16493 // call listeners 16494 self.onabort(); 16495 }; 16496 16497 /** 16498 * Get a file load handler. 16499 * @param {Object} file The file to load. 16500 * @param {Number} index The index 'id' of the file. 16501 * @return {Function} A file load handler. 16502 */ 16503 this.getFileLoadHandler = function(file, index) { 16504 return function(event) { 16505 self.load(event.target.result, file, index); 16506 }; 16507 }; 16508 16509 /** 16510 * Get a url load handler. 16511 * @param {String} url The url to load. 16512 * @param {Number} index The index 'id' of the url. 16513 * @return {Function} A url load handler. 16514 */ 16515 this.getUrlLoadHandler = function(url, index) { 16516 return function( /*event*/ ) { 16517 // check response status 16518 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes 16519 // status 200: "OK"; status 0: "debug" 16520 if (this.status !== 200 && this.status !== 0) { 16521 self.onerror({ 16522 'name': "RequestError", 16523 'message': "Error status: " + this.status + 16524 " while loading '" + url + "' [JSONTextLoader]" 16525 }); 16526 return; 16527 } 16528 // load 16529 self.load(this.responseText, url, index); 16530 }; 16531 }; 16532 16533 }; // class JSONTextLoader 16534 16535 /** 16536 * Check if the loader can load the provided file. 16537 * @param {Object} file The file to check. 16538 * @return True if the file can be loaded. 16539 */ 16540 dwv.io.JSONTextLoader.prototype.canLoadFile = function(file) { 16541 var ext = file.name.split('.').pop().toLowerCase(); 16542 return (ext === "json"); 16543 }; 16544 16545 /** 16546 * Check if the loader can load the provided url. 16547 * @param {String} url The url to check. 16548 * @return True if the url can be loaded. 16549 */ 16550 dwv.io.JSONTextLoader.prototype.canLoadUrl = function(url) { 16551 var ext = url.split('.').pop().toLowerCase(); 16552 return (ext === "json"); 16553 }; 16554 16555 /** 16556 * Get the file content type needed by the loader. 16557 * @return One of the 'dwv.io.fileContentTypes'. 16558 */ 16559 dwv.io.JSONTextLoader.prototype.loadFileAs = function() { 16560 return dwv.io.fileContentTypes.Text; 16561 }; 16562 16563 /** 16564 * Get the url content type needed by the loader. 16565 * @return One of the 'dwv.io.urlContentTypes'. 16566 */ 16567 dwv.io.JSONTextLoader.prototype.loadUrlAs = function() { 16568 return dwv.io.urlContentTypes.Text; 16569 }; 16570 16571 /** 16572 * Handle a load event. 16573 * @param {Object} event The load event, 'event.target' 16574 * should be the loaded data. 16575 * Default does nothing. 16576 */ 16577 dwv.io.JSONTextLoader.prototype.onload = function( /*event*/ ) {}; 16578 /** 16579 * Handle an load end event. 16580 * Default does nothing. 16581 */ 16582 dwv.io.JSONTextLoader.prototype.onloadend = function() {}; 16583 /** 16584 * Handle a progress event. 16585 * @param {Object} event The progress event. 16586 * Default does nothing. 16587 */ 16588 dwv.io.JSONTextLoader.prototype.onprogress = function( /*event*/ ) {}; 16589 /** 16590 * Handle an error event. 16591 * @param {Object} event The error event with an 16592 * optional 'event.message'. 16593 * Default does nothing. 16594 */ 16595 dwv.io.JSONTextLoader.prototype.onerror = function( /*event*/ ) {}; 16596 /** 16597 * Handle an abort event. 16598 * @param {Object} event The abort event with an 16599 * optional 'event.message'. 16600 * Default does nothing. 16601 */ 16602 dwv.io.JSONTextLoader.prototype.onabort = function( /*event*/ ) {}; 16603 16604 /**
16605 * Add to Loader list. 16606 */ 16607 dwv.io.loaderList = dwv.io.loaderList || []; 16608 dwv.io.loaderList.push("JSONTextLoader"); 16609 16610 // namespaces 16611 var dwv = dwv || {}; 16612 dwv.io = dwv.io || {}; 16613 16614 /** 16615 * Memory loader. 16616 * @constructor 16617 */ 16618 dwv.io.MemoryLoader = function() { 16619 /** 16620 * Closure to self. 16621 * @private 16622 * @type Object 16623 */ 16624 var self = this; 16625 16626 /** 16627 * Launched loader (used in abort). 16628 * @private 16629 * @type Object 16630 */ 16631 var runningLoader = null; 16632 16633 /** 16634 * Number of data to load. 16635 * @private 16636 * @type Number 16637 */ 16638 var nToLoad = 0; 16639 /** 16640 * Number of loaded data. 16641 * @private 16642 * @type Number 16643 */ 16644 var nLoaded = 0; 16645 16646 /** 16647 * The default character set (optional). 16648 * @private 16649 * @type String 16650 */ 16651 var defaultCharacterSet; 16652 16653 /** 16654 * Get the default character set.
16655 * @return {String} The default character set. 16656 */ 16657 this.getDefaultCharacterSet = function() { 16658 return defaultCharacterSet; 16659 }; 16660 16661 /** 16662 * Set the default character set. 16663 * @param {String} characterSet The character set. 16664 */ 16665 this.setDefaultCharacterSet = function(characterSet) { 16666 defaultCharacterSet = characterSet; 16667 }; 16668 16669 /** 16670 * Store a launched loader. 16671 * @param {Object} loader The launched loader. 16672 */ 16673 this.storeLoader = function(loader) { 16674 runningLoader = loader; 16675 }; 16676 16677 /** 16678 * Clear the stored loader. 16679 */ 16680 this.clearStoredLoader = function() { 16681 runningLoader = null; 16682 }; 16683 16684 /** 16685 * Abort a memory load. 16686 */ 16687 this.abort = function() { 16688 // abort loader 16689 runningLoader.abort(); 16690 this.clearStoredLoaders(); 16691 }; 16692 16693 /** 16694 * Set the number of data to load. 16695 * @param {Number} n The number of data to load. 16696 */ 16697 this.setNToLoad = function(n) { 16698 nToLoad = n; 16699 }; 16700 16701 /** 16702 * Increment the number of loaded data 16703 * and call onloadend if loaded all data. 16704 */ 16705 this.addLoaded = function() { 16706 nLoaded++; 16707 if (nLoaded === nToLoad) { 16708 self.onloadend(); 16709 } 16710 }; 16711 16712 }; // class Memory 16713 16714 /** 16715 * Handle a load event. 16716 * @param {Object} event The load event, 'event.target' 16717 * should be the loaded data. 16718 * Default does nothing. 16719 */ 16720 dwv.io.MemoryLoader.prototype.onload = function( /*event*/ ) {}; 16721 /** 16722 * Handle a load end event. 16723 * Default does nothing. 16724 */ 16725 dwv.io.MemoryLoader.prototype.onloadend = function() {}; 16726 /** 16727 * Handle a progress event. 16728 * @param {Object} event The progress event. 16729 * Default does nothing. 16730 */ 16731 dwv.io.MemoryLoader.prototype.onprogress = function( /*event*/ ) {}; 16732 /** 16733 * Handle an error event. 16734 * @param {Object} event The error event with an 16735 * optional 'event.message'. 16736 * Default does nothing. 16737 */ 16738 dwv.io.MemoryLoader.prototype.onerror = function( /*event*/ ) {}; 16739 /** 16740 * Handle an abort event. 16741 * @param {Object} event The abort event with an 16742 * optional 'event.message'. 16743 * Default does nothing. 16744 */ 16745 dwv.io.MemoryLoader.prototype.onabort = function( /*event*/ ) {}; 16746 16747 /** 16748 * Load a list of buffers. 16749 * @param {Array} ioArray The list of buffers to load. 16750 */ 16751 dwv.io.MemoryLoader.prototype.load = function(ioArray) { 16752 // clear storage 16753 this.clearStoredLoader(); 16754 16755 // closure to self for handlers 16756 var self = this; 16757 // set the number of data to load 16758 this.setNToLoad(ioArray.length); 16759 16760 var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress); 16761 mproghandler.setNToLoad(ioArray.length); 16762 16763 // get loaders 16764 var loaders = []; 16765 for (var m = 0; m < dwv.io.loaderList.length; ++m) { 16766 loaders.push(new dwv.io[dwv.io.loaderList[m]]()); 16767 } 16768 16769 // set loaders callbacks 16770 var loader = null; 16771 for (var k = 0; k < loaders.length; ++k) { 16772 loader = loaders[k]; 16773 loader.onload = self.onload; 16774 loader.onloadend = self.addLoaded; 16775 loader.onerror = self.onerror; 16776 loader.onabort = self.onabort; 16777 loader.setOptions({ 16778 'defaultCharacterSet': this.getDefaultCharacterSet() 16779 }); 16780 loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1); 16781 } 16782 16783 // loop on I/O elements 16784 for (var i = 0; i < ioArray.length; ++i) { 16785 var iodata = ioArray[i]; 16786 16787 // find a loader 16788 var foundLoader = false; 16789 for (var l = 0; l < loaders.length; ++l) { 16790 loader = loaders[l]; 16791 if (loader.canLoadUrl(iodata.filename)) { 16792 foundLoader = true; 16793 // store loader 16794 this.storeLoader(loader); 16795 // read 16796 loader.load(iodata.data, iodata.filename, i); 16797 // next file 16798 break; 16799 } 16800 } 16801 // TODO: throw? 16802 if (!foundLoader) { 16803 throw new Error("No loader found for file: " + iodata.filename); 16804 } 16805 } 16806 }; 16807 16808 // namespaces 16809 var dwv = dwv || {}; 16810 dwv.io = dwv.io || {}; 16811 16812 /** 16813 * Raw image loader. 16814 */ 16815 dwv.io.RawImageLoader = function() { 16816 // closure to self 16817 var self = this; 16818 16819 /** 16820 * Set the loader options. 16821 * @param {Object} opt The input options. 16822 */ 16823 this.setOptions = function() { 16824 // does nothing 16825 }; 16826 16827 /** 16828 * Is the load ongoing? TODO... 16829 * @return {Boolean} True if loading. 16830 */ 16831 this.isLoading = function() { 16832 return true; 16833 }; 16834 16835 /** 16836 * Create a Data URI from an HTTP request response. 16837 * @param {Object} response The HTTP request response. 16838 * @param {String} dataType The data type. 16839 */ 16840 function createDataUri(response, dataType) { 16841 // image data as string 16842 var bytes = new Uint8Array(response); 16843 var imageDataStr = ''; 16844 for (var i = 0; i < bytes.byteLength; ++i) { 16845 imageDataStr += String.fromCharCode(bytes[i]); 16846 } 16847 // image type 16848 var imageType = dataType; 16849 if (imageType === "jpg") { 16850 imageType = "jpeg"; 16851 } 16852 // create uri 16853 var uri = "data:image/" + imageType + ";base64," + window.btoa(imageDataStr); 16854 return uri; 16855 } 16856 16857 /** 16858 * Load data. 16859 * @param {Object} dataUri The data URI. 16860 * @param {String} origin The data origin. 16861 * @param {Number} index The data index. 16862 */ 16863 this.load = function(dataUri, origin, index) { 16864 // create a DOM image 16865 var image = new Image(); 16866 image.src = dataUri; 16867 // storing values to pass them on 16868 image.origin = origin; 16869 image.index = index; 16870 // triggered by ctx.drawImage 16871 image.onload = function( /*event*/ ) { 16872 try { 16873 self.onload(dwv.image.getViewFromDOMImage(this)); 16874 self.onloadend(); 16875 } catch (error) { 16876 self.onerror(error); 16877 } 16878 self.onprogress({ 16879 'type': 'read-progress', 16880 'lengthComputable': true, 16881 'loaded': 100, 16882 'total': 100, 16883 'index': index 16884 }); 16885 }; 16886 }; 16887 16888 /** 16889 * Abort load. TODO... 16890 */ 16891 this.abort = function() { 16892 self.onabort(); 16893 }; 16894 16895 /** 16896 * Get a file load handler. 16897 * @param {Object} file The file to load. 16898 * @param {Number} index The index 'id' of the file. 16899 * @return {Function} A file load handler. 16900 */ 16901 this.getFileLoadHandler = function(file, index) { 16902 return function(event) { 16903 self.load(event.target.result, file, index); 16904 }; 16905 }; 16906 16907 /** 16908 * Get a url load handler. 16909 * @param {String} url The url to load. 16910 * @param {Number} index The index 'id' of the url. 16911 * @return {Function} A url load handler. 16912 */ 16913 this.getUrlLoadHandler = function(url, index) { 16914 return function( /*event*/ ) { 16915 // check response status 16916 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes 16917 // status 200: "OK"; status 0: "debug" 16918 if (this.status !== 200 && this.status !== 0) { 16919 self.onerror({ 16920 'name': "RequestError", 16921 'message': "Error status: " + this.status + 16922 " while loading '" + url + "' [RawImageLoader]" 16923 }); 16924 return; 16925 } 16926 // load 16927 var ext = url.split('.').pop().toLowerCase(); 16928 self.load(createDataUri(this.response, ext), url, index); 16929 }; 16930 }; 16931 16932 }; // class RawImageLoader 16933 16934 /** 16935 * Check if the loader can load the provided file. 16936 * @param {Object} file The file to check. 16937 * @return True if the file can be loaded. 16938 */ 16939 dwv.io.RawImageLoader.prototype.canLoadFile = function(file) { 16940 return file.type.match("image.*"); 16941 }; 16942 16943 /** 16944 * Check if the loader can load the provided url. 16945 * @param {String} url The url to check. 16946 * @return True if the url can be loaded. 16947 */ 16948 dwv.io.RawImageLoader.prototype.canLoadUrl = function(url) { 16949 var ext = url.split('.').pop().toLowerCase(); 16950 var hasImageExt = (ext === "jpeg") || (ext === "jpg") || 16951 (ext === "png") || (ext === "gif"); 16952 // wado url 16953 var isImageContentType = (url.indexOf("contentType=image/jpeg") !== -1) || 16954 (url.indexOf("contentType=image/png") !== -1) || 16955 (url.indexOf("contentType=image/gif") !== -1); 16956 16957 return isImageContentType || hasImageExt; 16958 }; 16959 16960 /** 16961 * Get the file content type needed by the loader. 16962 * @return One of the 'dwv.io.fileContentTypes'. 16963 */ 16964 dwv.io.RawImageLoader.prototype.loadFileAs = function() { 16965 return dwv.io.fileContentTypes.DataURL; 16966 }; 16967 16968 /** 16969 * Get the url content type needed by the loader. 16970 * @return One of the 'dwv.io.urlContentTypes'. 16971 */ 16972 dwv.io.RawImageLoader.prototype.loadUrlAs = function() { 16973 return dwv.io.urlContentTypes.ArrayBuffer; 16974 }; 16975 16976 /** 16977 * Handle a load event. 16978 * @param {Object} event The load event, 'event.target' 16979 * should be the loaded data. 16980 * Default does nothing. 16981 */ 16982 dwv.io.RawImageLoader.prototype.onload = function( /*event*/ ) {}; 16983 /** 16984 * Handle an load end event. 16985 * Default does nothing. 16986 */ 16987 dwv.io.RawImageLoader.prototype.onloadend = function() {}; 16988 /** 16989 * Handle a progress event. 16990 * @param {Object} event The progress event. 16991 * Default does nothing. 16992 */ 16993 dwv.io.RawImageLoader.prototype.onprogress = function( /*event*/ ) {}; 16994 /** 16995 * Handle an error event. 16996 * @param {Object} event The error event with an 16997 * optional 'event.message'. 16998 * Default does nothing. 16999 */ 17000 dwv.io.RawImageLoader.prototype.onerror = function( /*event*/ ) {}; 17001 /** 17002 * Handle an abort event. 17003 * @param {Object} event The abort event with an 17004 * optional 'event.message'. 17005 * Default does nothing. 17006 */ 17007 dwv.io.RawImageLoader.prototype.onabort = function( /*event*/ ) {}; 17008 17009 /**
17010 * Add to Loader list. 17011 */ 17012 dwv.io.loaderList = dwv.io.loaderList || []; 17013 dwv.io.loaderList.push("RawImageLoader"); 17014 17015 // namespaces 17016 var dwv = dwv || {}; 17017 dwv.io = dwv.io || {}; 17018 17019 /** 17020 * Raw video loader. 17021 * url example (cors enabled): 17022 * https://raw.githubusercontent.com/clappr/clappr/master/test/fixtures/SampleVideo_360x240_1mb.mp4 17023 */ 17024 dwv.io.RawVideoLoader = function() { 17025 // closure to self 17026 var self = this; 17027 17028 /** 17029 * Set the loader options. 17030 * @param {Object} opt The input options. 17031 */ 17032 this.setOptions = function() { 17033 // does nothing 17034 }; 17035 17036 /** 17037 * Is the load ongoing? TODO... 17038 * @return {Boolean} True if loading. 17039 */ 17040 this.isLoading = function() { 17041 return true; 17042 }; 17043 17044 /** 17045 * Create a Data URI from an HTTP request response. 17046 * @param {Object} response The HTTP request response. 17047 * @param {String} dataType The data type. 17048 */ 17049 function createDataUri(response, dataType) { 17050 // image data as string 17051 var bytes = new Uint8Array(response); 17052 var videoDataStr = ''; 17053 for (var i = 0; i < bytes.byteLength; ++i) { 17054 videoDataStr += String.fromCharCode(bytes[i]); 17055 } 17056 // create uri 17057 var uri = "data:video/" + dataType + ";base64," + window.btoa(videoDataStr); 17058 return uri; 17059 } 17060 17061 /** 17062 * Internal Data URI load. 17063 * @param {Object} dataUri The data URI. 17064 * @param {String} origin The data origin. 17065 * @param {Number} index The data index. 17066 */ 17067 this.load = function(dataUri, origin, index) { 17068 // create a DOM video 17069 var video = document.createElement('video'); 17070 video.src = dataUri; 17071 // storing values to pass them on 17072 video.file = origin; 17073 video.index = index; 17074 // onload handler 17075 video.onloadedmetadata = function( /*event*/ ) { 17076 try { 17077 dwv.image.getViewFromDOMVideo(this, 17078 self.onload, self.onprogress, self.onloadend, index); 17079 } catch (error) { 17080 self.onerror(error); 17081 } 17082 }; 17083 }; 17084 17085 /** 17086 * Abort load. TODO... 17087 */ 17088 this.abort = function() { 17089 self.onabort(); 17090 }; 17091 17092 /** 17093 * Get a file load handler. 17094 * @param {Object} file The file to load. 17095 * @param {Number} index The index 'id' of the file. 17096 * @return {Function} A file load handler. 17097 */ 17098 this.getFileLoadHandler = function(file, index) { 17099 return function(event) { 17100 self.load(event.target.result, file, index); 17101 }; 17102 }; 17103 17104 /** 17105 * Get a url load handler. 17106 * @param {String} url The url to load. 17107 * @param {Number} index The index 'id' of the url. 17108 * @return {Function} A url load handler. 17109 */ 17110 this.getUrlLoadHandler = function(url, index) { 17111 return function( /*event*/ ) { 17112 // check response status 17113 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes 17114 // status 200: "OK"; status 0: "debug" 17115 if (this.status !== 200 && this.status !== 0) { 17116 self.onerror({ 17117 'name': "RequestError", 17118 'message': "Error status: " + this.status + 17119 " while loading '" + url + "' [RawVideoLoader]" 17120 }); 17121 return; 17122 } 17123 // load 17124 var ext = url.split('.').pop().toLowerCase(); 17125 self.load(createDataUri(this.response, ext), url, index); 17126 }; 17127 }; 17128 17129 }; // class RawVideoLoader 17130 17131 /** 17132 * Check if the loader can load the provided file. 17133 * @param {Object} file The file to check. 17134 * @return True if the file can be loaded. 17135 */ 17136 dwv.io.RawVideoLoader.prototype.canLoadFile = function(file) { 17137 return file.type.match("video.*"); 17138 }; 17139 17140 /** 17141 * Check if the loader can load the provided url. 17142 * @param {String} url The url to check. 17143 * @return True if the url can be loaded. 17144 */ 17145 dwv.io.RawVideoLoader.prototype.canLoadUrl = function(url) { 17146 var ext = url.split('.').pop().toLowerCase(); 17147 return (ext === "mp4") || (ext === "ogg") || 17148 (ext === "webm"); 17149 }; 17150 17151 /** 17152 * Get the file content type needed by the loader. 17153 * @return One of the 'dwv.io.fileContentTypes'. 17154 */ 17155 dwv.io.RawVideoLoader.prototype.loadFileAs = function() { 17156 return dwv.io.fileContentTypes.DataURL; 17157 }; 17158 17159 /** 17160 * Get the url content type needed by the loader. 17161 * @return One of the 'dwv.io.urlContentTypes'. 17162 */ 17163 dwv.io.RawVideoLoader.prototype.loadUrlAs = function() { 17164 return dwv.io.urlContentTypes.ArrayBuffer; 17165 }; 17166 17167 /** 17168 * Handle a load event. 17169 * @param {Object} event The load event, 'event.target' 17170 * should be the loaded data. 17171 * Default does nothing. 17172 */ 17173 dwv.io.RawVideoLoader.prototype.onload = function( /*event*/ ) {}; 17174 /** 17175 * Handle an load end event. 17176 * Default does nothing. 17177 */ 17178 dwv.io.RawVideoLoader.prototype.onloadend = function() {}; 17179 /** 17180 * Handle a progress event. 17181 * @param {Object} event The progress event. 17182 * Default does nothing. 17183 */ 17184 dwv.io.RawVideoLoader.prototype.onprogress = function( /*event*/ ) {}; 17185 /** 17186 * Handle an error event. 17187 * @param {Object} event The error event with an 17188 * optional 'event.message'. 17189 * Default does nothing. 17190 */ 17191 dwv.io.RawVideoLoader.prototype.onerror = function( /*event*/ ) {}; 17192 /** 17193 * Handle an abort event. 17194 * @param {Object} event The abort event with an 17195 * optional 'event.message'. 17196 * Default does nothing. 17197 */ 17198 dwv.io.RawVideoLoader.prototype.onabort = function( /*event*/ ) {}; 17199 17200 /**
17201 * Add to Loader list. 17202 */ 17203 dwv.io.loaderList = dwv.io.loaderList || []; 17204 dwv.io.loaderList.push("RawVideoLoader"); 17205 17206 // namespaces 17207 var dwv = dwv || {}; 17208 dwv.io = dwv.io || {}; 17209 17210 // url content types 17211 dwv.io.urlContentTypes = { 17212 'Text': 0, 17213 'ArrayBuffer': 1, 17214 'oups': 2 17215 }; 17216 17217 /** 17218 * Urls loader. 17219 * @constructor 17220 */ 17221 dwv.io.UrlsLoader = function() { 17222 /** 17223 * Closure to self. 17224 * @private 17225 * @type Object 17226 */ 17227 var self = this; 17228 17229 /** 17230 * Array of launched requests used in abort. 17231 * @private 17232 * @type Array 17233 */ 17234 var requests = []; 17235 17236 /** 17237 * Launched loader used in abort. 17238 * @private 17239 * @type Object 17240 */ 17241 var runningLoader = null; 17242 17243 /** 17244 * Number of data to load. 17245 * @private 17246 * @type Number 17247 */ 17248 var nToLoad = 0; 17249 /** 17250 * Number of loaded data. 17251 * @private 17252 * @type Number 17253 */ 17254 var nLoaded = 0; 17255 17256 /** 17257 * The default character set (optional). 17258 * @private
17259 * @type String 17260 */ 17261 var defaultCharacterSet; 17262 17263 /** 17264 * Get the default character set. 17265 * @return {String} The default character set. 17266 */ 17267 this.getDefaultCharacterSet = function() { 17268 return defaultCharacterSet; 17269 }; 17270 17271 /** 17272 * Set the default character set. 17273 * @param {String} characterSet The character set. 17274 */ 17275 this.setDefaultCharacterSet = function(characterSet) { 17276 defaultCharacterSet = characterSet; 17277 }; 17278 17279 /** 17280 * Store a launched request. 17281 * @param {Object} request The launched request. 17282 */ 17283 this.storeRequest = function(request) { 17284 requests.push(request); 17285 }; 17286 17287 /** 17288 * Clear the stored requests. 17289 */ 17290 this.clearStoredRequests = function() { 17291 requests = []; 17292 }; 17293 17294 /** 17295 * Store a launched loader. 17296 * @param {Object} loader The launched loader. 17297 */ 17298 this.storeLoader = function(loader) { 17299 runningLoader = loader; 17300 }; 17301 17302 /** 17303 * Clear the stored loader. 17304 */ 17305 this.clearStoredLoader = function() { 17306 runningLoader = null; 17307 }; 17308 17309 /** 17310 * Abort a URLs load. 17311 */ 17312 this.abort = function() { 17313 // abort requests 17314 for (var i = 0; i < requests.length; ++i) { 17315 // 0: UNSENT, 1: OPENED, 2: HEADERS_RECEIVED (send()), 3: LOADING, 4: DONE 17316 if (requests[i].readyState === 2 || requests[i].readyState === 3) { 17317 requests[i].abort(); 17318 } 17319 } 17320 this.clearStoredRequests(); 17321 // abort loader 17322 if (runningLoader && runningLoader.isLoading()) { 17323 runningLoader.abort(); 17324 } 17325 this.clearStoredLoader(); 17326 }; 17327 17328 /** 17329 * Set the number of data to load. 17330 * @param {Number} n The number of data to load. 17331 */ 17332 this.setNToLoad = function(n) { 17333 nToLoad = n; 17334 }; 17335 17336 /** 17337 * Increment the number of loaded data 17338 * and call onloadend if loaded all data. 17339 */ 17340 this.addLoaded = function() { 17341 nLoaded++; 17342 if (nLoaded === nToLoad) { 17343 self.onloadend(); 17344 } 17345 }; 17346 17347 }; // class Url 17348 17349 /** 17350 * Handle a load event. 17351 * @param {Object} event The load event, 'event.target' 17352 * should be the loaded data. 17353 * Default does nothing. 17354 */ 17355 dwv.io.UrlsLoader.prototype.onload = function( /*event*/ ) {}; 17356 /** 17357 * Handle a load end event. 17358 * Default does nothing. 17359 */ 17360 dwv.io.UrlsLoader.prototype.onloadend = function() {}; 17361 /** 17362 * Handle a progress event. 17363 * @param {Object} event The progress event. 17364 * Default does nothing. 17365 */ 17366 dwv.io.UrlsLoader.prototype.onprogress = function( /*event*/ ) {}; 17367 /** 17368 * Handle an error event. 17369 * @param {Object} event The error event with an 17370 * optional 'event.message'. 17371 * Default does nothing. 17372 */ 17373 dwv.io.UrlsLoader.prototype.onerror = function( /*event*/ ) {}; 17374 /** 17375 * Handle an abort event. 17376 * @param {Object} event The abort event with an 17377 * optional 'event.message'. 17378 * Default does nothing. 17379 */ 17380 dwv.io.UrlsLoader.prototype.onabort = function( /*event*/ ) {}; 17381 17382 /** 17383 * Load a list of URLs. 17384 * @param {Array} ioArray The list of urls to load. 17385 * @param {Object} options Load options. 17386 * @external XMLHttpRequest 17387 */ 17388 dwv.io.UrlsLoader.prototype.load = function(ioArray, options) { 17389 // clear storage 17390 this.clearStoredRequests(); 17391 this.clearStoredLoader(); 17392 17393 // closure to self for handlers 17394 var self = this; 17395 // set the number of data to load 17396 this.setNToLoad(ioArray.length); 17397 17398 var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress); 17399 mproghandler.setNToLoad(ioArray.length); 17400 17401 // get loaders 17402 var loaders = []; 17403 for (var m = 0; m < dwv.io.loaderList.length; ++m) { 17404 loaders.push(new dwv.io[dwv.io.loaderList[m]]()); 17405 } 17406 17407 // set loaders callbacks 17408 var loader = null; 17409 for (var k = 0; k < loaders.length; ++k) { 17410 loader = loaders[k]; 17411 loader.onload = self.onload; 17412 loader.onloadend = self.addLoaded; 17413 loader.onerror = self.onerror; 17414 loader.onabort = self.onabort; 17415 loader.setOptions({ 17416 'defaultCharacterSet': this.getDefaultCharacterSet() 17417 }); 17418 loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1); 17419 } 17420 17421 // request onerror handler 17422 var getRequestOnError = function(origin) { 17423 return function( /*event*/ ) { 17424 var message = "An error occurred while downloading '" + origin + "'"; 17425 if (typeof this.status !== "undefined") {
17426 message += " (http status: " + this.status + ")"; 17427 } 17428 message += "."; 17429 self.onerror({ 'name': "RequestError", 'message': message }); 17430 }; 17431 }; 17432 17433 // request onabort handler 17434 var getRequestOnAbort = function(origin) { 17435 return function() { 17436 self.onabort({ 'message': "Abort while downloading '" + origin + "'." }); 17437 }; 17438 }; 17439 17440 // loop on I/O elements 17441 for (var i = 0; i < ioArray.length; ++i) { 17442 var url = ioArray[i]; 17443 var request = new XMLHttpRequest(); 17444 request.open('GET', url, true); 17445 17446 // store request 17447 this.storeRequest(request); 17448 17449 // optional request headers 17450 if (typeof options.requestHeaders !== "undefined") { 17451 var requestHeaders = options.requestHeaders; 17452 for (var j = 0; j < requestHeaders.length; ++j) { 17453 if (typeof requestHeaders[j].name !== "undefined" && 17454 typeof requestHeaders[j].value !== "undefined") { 17455 request.setRequestHeader(requestHeaders[j].name, requestHeaders[j].value); 17456 } 17457 } 17458 } 17459 17460 // bind reader progress 17461 request.onprogress = mproghandler.getMonoProgressHandler(i, 0); 17462 request.onloadend = mproghandler.getMonoOnLoadEndHandler(i, 0); 17463 17464 // find a loader 17465 var foundLoader = false; 17466 for (var l = 0; l < loaders.length; ++l) { 17467 loader = loaders[l]; 17468 if (loader.canLoadUrl(url)) { 17469 foundLoader = true; 17470 // store loader 17471 this.storeLoader(loader); 17472 // set reader callbacks 17473 request.onload = loader.getUrlLoadHandler(url, i); 17474 request.onerror = getRequestOnError(url); 17475 request.onabort = getRequestOnAbort(url); 17476 // response type (default is 'text') 17477 if (loader.loadUrlAs() === dwv.io.urlContentTypes.ArrayBuffer) { 17478 request.responseType = "arraybuffer"; 17479 } 17480 // read 17481 request.send(null); 17482 // next file 17483 break; 17484 } 17485 } 17486 // TODO: throw? 17487 if (!foundLoader) { 17488 throw new Error("No loader found for url: " + url); 17489 } 17490 } 17491 }; 17492 17493 // namespaces 17494 var dwv = dwv || {}; 17495 dwv.io = dwv.io || {}; 17496 // external 17497 var JSZip = JSZip || {}; 17498 17499 /** 17500 * ZIP data loader. 17501 */ 17502 dwv.io.ZipLoader = function() { 17503 // closure to self 17504 var self = this; 17505 17506 /** 17507 * Loader options. 17508 * @private 17509 * @type Object 17510 */ 17511 var options = {}; 17512 17513 /** 17514 * Loading flag. 17515 * @private 17516 * @type Boolean 17517 */ 17518 var isLoading = false; 17519 17520 /** 17521 * Set the loader options. 17522 * @param {Object} opt The input options. 17523 */ 17524 this.setOptions = function(opt) { 17525 options = opt; 17526 }; 17527 17528 /** 17529 * Is the load ongoing? 17530 * @return {Boolean} True if loading. 17531 */ 17532 this.isLoading = function() { 17533 return isLoading; 17534 }; 17535 17536 var filename = ""; 17537 var files = []; 17538 var zobjs = null; 17539 17540 /** 17541 * JSZip.async callback 17542 * @param {ArrayBuffer} content unzipped file image 17543 * @return {} 17544 */ 17545 function zipAsyncCallback(content) { 17546 files.push({ "filename": filename, "data": content }); 17547 17548 if (files.length < zobjs.length) { 17549 var num = files.length; 17550 filename = zobjs[num].name; 17551 zobjs[num].async("arrayBuffer").then(zipAsyncCallback); 17552 } else { 17553 var memoryIO = new dwv.io.MemoryLoader(); 17554 memoryIO.onload = self.onload; 17555 memoryIO.onloadend = function() { 17556 // reset loading flag 17557 isLoading = false; 17558 // call listeners 17559 self.onloadend(); 17560 }; 17561 memoryIO.onprogress = self.onprogress; 17562 memoryIO.onerror = self.onerror; 17563 memoryIO.onabort = self.onabort; 17564 17565 memoryIO.load(files); 17566 } 17567 } 17568 17569 /** 17570 * Load data. 17571 * @param {Object} buffer The DICOM buffer. 17572 * @param {String} origin The data origin. 17573 * @param {Number} index The data index. 17574 */ 17575 this.load = function(buffer /*, origin, index*/ ) { 17576 // set loading flag 17577 isLoading = true; 17578 17579 JSZip.loadAsync(buffer).then(function(zip) { 17580 files = []; 17581 zobjs = zip.file(/.*\.dcm/); 17582 // recursively load zip files into the files array 17583 var num = files.length; 17584 filename = zobjs[num].name; 17585 zobjs[num].async("arrayBuffer").then(zipAsyncCallback); 17586 }); 17587 }; 17588 17589 /** 17590 * Abort load: pass to listeners. 17591 */ 17592 this.abort = function() { 17593 // reset loading flag 17594 isLoading = false; 17595 // call listeners 17596 self.onabort(); 17597 }; 17598 17599 /** 17600 * Get a file load handler. 17601 * @param {Object} file The file to load. 17602 * @param {Number} index The index 'id' of the file. 17603 * @return {Function} A file load handler. 17604 */ 17605 this.getFileLoadHandler = function(file, index) { 17606 return function(event) { 17607 self.load(event.target.result, file, index); 17608 }; 17609 }; 17610 17611 /** 17612 * Get a url load handler. 17613 * @param {String} url The url to load. 17614 * @param {Number} index The index 'id' of the url. 17615 * @return {Function} A url load handler. 17616 */ 17617 this.getUrlLoadHandler = function(url, index) { 17618 return function( /*event*/ ) { 17619 // check response status 17620 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes 17621 // status 200: "OK"; status 0: "debug" 17622 if (this.status !== 200 && this.status !== 0) { 17623 self.onerror({ 17624 'name': "RequestError", 17625 'message': "Error status: " + this.status + 17626 " while loading '" + url + "' [ZipLoader]" 17627 }); 17628 return; 17629 } 17630 // load 17631 self.load(this.response, url, index); 17632 }; 17633 }; 17634 17635 }; // class DicomDataLoader 17636 17637 /** 17638 * Check if the loader can load the provided file. 17639 * @param {Object} file The file to check. 17640 * @return True if the file can be loaded. 17641 */ 17642 dwv.io.ZipLoader.prototype.canLoadFile = function(file) { 17643 var ext = file.name.split('.').pop().toLowerCase(); 17644 return (ext === "zip"); 17645 }; 17646 17647 /** 17648 * Check if the loader can load the provided url. 17649 * @param {String} url The url to check. 17650 * @return True if the url can be loaded. 17651 */ 17652 dwv.io.ZipLoader.prototype.canLoadUrl = function(url) { 17653 var ext = url.split('.').pop().toLowerCase(); 17654 return (ext === "zip"); 17655 }; 17656 17657 /** 17658 * Get the file content type needed by the loader. 17659 * @return One of the 'dwv.io.fileContentTypes'. 17660 */ 17661 dwv.io.ZipLoader.prototype.loadFileAs = function() { 17662 return dwv.io.fileContentTypes.ArrayBuffer; 17663 }; 17664 17665 /** 17666 * Get the url content type needed by the loader. 17667 * @return One of the 'dwv.io.urlContentTypes'. 17668 */ 17669 dwv.io.ZipLoader.prototype.loadUrlAs = function() { 17670 return dwv.io.urlContentTypes.ArrayBuffer; 17671 }; 17672 17673 /** 17674 * Handle a load event. 17675 * @param {Object} event The load event, 'event.target' 17676 * should be the loaded data. 17677 * Default does nothing. 17678 */ 17679 dwv.io.ZipLoader.prototype.onload = function( /*event*/ ) {}; 17680 /** 17681 * Handle an load end event. 17682 * Default does nothing. 17683 */ 17684 dwv.io.ZipLoader.prototype.onloadend = function() {}; 17685 /** 17686 * Handle a progress event. 17687 * @param {Object} event The progress event. 17688 * Default does nothing. 17689 */ 17690 dwv.io.ZipLoader.prototype.onprogress = function( /*event*/ ) {}; 17691 /** 17692 * Handle an error event. 17693 * @param {Object} event The error event with an 17694 * optional 'event.message'. 17695 * Default does nothing. 17696 */ 17697 dwv.io.ZipLoader.prototype.onerror = function( /*event*/ ) {}; 17698 /** 17699 * Handle an abort event. 17700 * @param {Object} event The abort event with an 17701 * optional 'event.message'. 17702 * Default does nothing. 17703 */ 17704 dwv.io.ZipLoader.prototype.onabort = function( /*event*/ ) {}; 17705 17706 /**
17707 * Add to Loader list. 17708 */ 17709 dwv.io.loaderList = dwv.io.loaderList || []; 17710 dwv.io.loaderList.push("ZipLoader"); 17711 17712 // namespaces 17713 var dwv = dwv || {}; 17714 /** @namespace */ 17715 dwv.math = dwv.math || {}; 17716 17717 /** 17718 * Circular Bucket Queue. 17719 * 17720 * Returns input'd points in sorted order. All operations run in roughly O(1) 17721 * time (for input with small cost values), but it has a strict requirement: 17722 * 17723 * If the most recent point had a cost of c, any points added should have a cost 17724 * c' in the range c <= c' <= c + (capacity - 1). 17725 * 17726 * @constructor 17727 * @input bits 17728 * @input cost_functor 17729 */ 17730 dwv.math.BucketQueue = function(bits, cost_functor) { 17731 this.bucketCount = 1 << bits; // # of buckets = 2^bits 17732 this.mask = this.bucketCount - 1; // 2^bits - 1 = index mask 17733 this.size = 0; 17734 17735 this.loc = 0; // Current index in bucket list 17736 17737 // Cost defaults to item value 17738 this.cost = (typeof(cost_functor) !== 'undefined') ? cost_functor : function(item) { 17739 return item; 17740 }; 17741 17742 this.buckets = this.buildArray(this.bucketCount); 17743 }; 17744 17745 dwv.math.BucketQueue.prototype.push = function(item) { 17746 // Prepend item to the list in the appropriate bucket 17747 var bucket = this.getBucket(item); 17748 item.next = this.buckets[bucket]; 17749 this.buckets[bucket] = item; 17750 17751 this.size++; 17752 }; 17753 17754 dwv.math.BucketQueue.prototype.pop = function() { 17755 if (this.size === 0) { 17756 throw new Error("Cannot pop, bucketQueue is empty."); 17757 } 17758 17759 // Find first empty bucket 17760 while (this.buckets[this.loc] === null) { 17761 this.loc = (this.loc + 1) % this.bucketCount; 17762 } 17763 17764 // All items in bucket have same cost, return the first one 17765 var ret = this.buckets[this.loc]; 17766 this.buckets[this.loc] = ret.next; 17767 ret.next = null; 17768 17769 this.size--; 17770 return ret; 17771 }; 17772 17773 dwv.math.BucketQueue.prototype.remove = function(item) { 17774 // Tries to remove item from queue. Returns true on success, false otherwise 17775 if (!item) { 17776 return false; 17777 } 17778 17779 // To find node, go to bucket and search through unsorted list. 17780 var bucket = this.getBucket(item); 17781 var node = this.buckets[bucket]; 17782 17783 while (node !== null && !item.equals(node.next)) { 17784 node = node.next; 17785 } 17786 17787 if (node === null) { 17788 // Item not in list, ergo item not in queue 17789 return false; 17790 } else { 17791 // Found item, do standard list node deletion 17792 node.next = node.next.next; 17793 17794 this.size--; 17795 return true; 17796 } 17797 }; 17798 17799 dwv.math.BucketQueue.prototype.isEmpty = function() { 17800 return this.size === 0; 17801 }; 17802 17803 dwv.math.BucketQueue.prototype.getBucket = function(item) { 17804 // Bucket index is the masked cost 17805 return this.cost(item) & this.mask; 17806 }; 17807 17808 dwv.math.BucketQueue.prototype.buildArray = function(newSize) { 17809 // Create array and initialze pointers to null 17810 var buckets = new Array(newSize); 17811 17812 for (var i = 0; i < buckets.length; i++) { 17813 buckets[i] = null; 17814 } 17815 17816 return buckets; 17817 }; 17818 17819 // namespaces 17820 var dwv = dwv || {}; 17821 dwv.math = dwv.math || {}; 17822 17823 /** 17824 * Immutable 3x3 Matrix. 17825 * @constructor 17826 */ 17827 dwv.math.Matrix33 = function( 17828 m00, m01, m02, 17829 m10, m11, m12, 17830 m20, m21, m22) { 17831 // row-major order 17832 var mat = new Float32Array(9); 17833 mat[0] = m00; 17834 mat[1] = m01; 17835 mat[2] = m02; 17836 mat[3] = m10; 17837 mat[4] = m11; 17838 mat[5] = m12; 17839 mat[6] = m20; 17840 mat[7] = m21; 17841 mat[8] = m22; 17842 17843 /** 17844 * Get a value of the matrix. 17845 * @param {Number} row The row at wich to get the value. 17846 * @param {Number} col The column at wich to get the value. 17847 */ 17848 this.get = function(row, col) { 17849 return mat[row * 3 + col]; 17850 }; 17851 }; // Matrix33 17852 17853 /** 17854 * Check for Matrix33 equality. 17855 * @param {Object} rhs The other matrix to compare to. 17856 * @return {Boolean} True if both matrices are equal. 17857 */ 17858 dwv.math.Matrix33.prototype.equals = function(rhs) { 17859 return this.get(0, 0) === rhs.get(0, 0) && this.get(0, 1) === rhs.get(0, 1) && 17860 this.get(0, 2) === rhs.get(0, 2) && this.get(1, 0) === rhs.get(1, 0) && 17861 this.get(1, 1) === rhs.get(1, 1) && this.get(1, 2) === rhs.get(1, 2) && 17862 this.get(2, 0) === rhs.get(2, 0) && this.get(2, 1) === rhs.get(2, 1) && 17863 this.get(2, 2) === rhs.get(2, 2); 17864 }; 17865 17866 /** 17867 * Get a string representation of the Matrix33. 17868 * @return {String}
17868 The matrix as a string. 17869 */ 17870 dwv.math.Matrix33.prototype.toString = function() { 17871 return "[" + this.get(0, 0) + ", " + this.get(0, 1) + ", " + this.get(0, 2) + 17872 "\n " + this.get(1, 0) + ", " + this.get(1, 1) + ", " + this.get(1, 2) + 17873 "\n " + this.get(2, 0) + ", " + this.get(2, 1) + ", " + this.get(2, 2) + "]"; 17874 }; 17875 17876 /** 17877 * Multiply this matrix by a 3D vector. 17878 * @param {Object} vector3D The input 3D vector 17879 * @return {Object} The result 3D vector 17880 */ 17881 dwv.math.Matrix33.multiplyVector3D = function(vector3D) { 17882 // cache matrix values 17883 var m00 = this.get(0, 0); 17884 var m01 = this.get(0, 1); 17885 var m02 = this.get(0, 2); 17886 var m10 = this.get(1, 0); 17887 var m11 = this.get(1, 1); 17888 var m12 = this.get(1, 2); 17889 var m20 = this.get(2, 0); 17890 var m21 = this.get(2, 1); 17891 var m22 = this.get(2, 2); 17892 // cache vector values 17893 var vx = vector3D.getX(); 17894 var vy = vector3D.getY(); 17895 var vz = vector3D.getZ(); 17896 // calculate 17897 return new dwv.math.Vector3D( 17898 (m00 * vx) + (m01 * vy) + (m02 * vz), 17899 (m10 * vx) + (m11 * vy) + (m12 * vz), 17900 (m20 * vx) + (m21 * vy) + (m22 * vz)); 17901 }; 17902 17903 /** 17904 * Create a 3x3 identity matrix. 17905 * @return {Object} The identity matrix. 17906 */ 17907 dwv.math.getIdentityMat33 = function() { 17908 return new dwv.math.Matrix33( 17909 1, 0, 0, 17910 0, 1, 0, 17911 0, 0, 1); 17912 }; 17913 17914 // namespaces 17915 var dwv = dwv || {}; 17916 dwv.math = dwv.math || {}; 17917 17918 /** 17919 * Immutable 2D point. 17920 * @constructor 17921 * @param {Number} x The X coordinate for the point. 17922 * @param {Number} y The Y coordinate for the point. 17923 */ 17924 dwv.math.Point2D = function(x, y) { 17925 /** 17926 * Get the X position of the point. 17927 * @return {Number} The X position of the point. 17928 */ 17929 this.getX = function() { return x; }; 17930 /** 17931 * Get the Y position of the point. 17932 * @return {Number} The Y position of the point. 17933 */ 17934 this.getY = function() { return y; }; 17935 }; // Point2D class 17936 17937 /** 17938 * Check for Point2D equality. 17939 * @param {Object} rhs The other point to compare to. 17940 * @return {Boolean} True if both points are equal. 17941 */ 17942 dwv.math.Point2D.prototype.equals = function(rhs) { 17943 return rhs !== null && 17944 this.getX() === rhs.getX() && 17945 this.getY() === rhs.getY(); 17946 }; 17947 17948 /** 17949 * Get a string representation of the Point2D. 17950 * @return {String} The point as a string. 17951 */ 17952 dwv.math.Point2D.prototype.toString = function() { 17953 return "(" + this.getX() + ", " + this.getY() + ")"; 17954 }; 17955 17956 /** 17957 * Get the distance to another Point2D. 17958 * @param {Object} point2D The input point. 17959 */ 17960 dwv.math.Point2D.prototype.getDistance = function(point2D) { 17961 return Math.sqrt( 17962 (this.getX() - point2D.getX()) * (this.getX() - point2D.getX()) + 17963 (this.getY() - point2D.getY()) * (this.getY() - point2D.getY())); 17964 }; 17965 17966 /** 17967 * Mutable 2D point. 17968 * @constructor 17969 * @param {Number} x The X coordinate for the point. 17970 * @param {Number} y The Y coordinate for the point. 17971 */ 17972 dwv.math.FastPoint2D = function(x, y) { 17973 this.x = x; 17974 this.y = y; 17975 }; // FastPoint2D class 17976 17977 /** 17978 * Check for FastPoint2D equality. 17979 * @param {Object} other The other point to compare to. 17980 * @return {Boolean} True if both points are equal. 17981 */ 17982 dwv.math.FastPoint2D.prototype.equals = function(rhs) { 17983 return rhs !== null && 17984 this.x === rhs.x && 17985 this.y === rhs.y; 17986 }; 17987 17988 /** 17989 * Get a string representation of the FastPoint2D. 17990 * @return {String} The point as a string. 17991 */ 17992 dwv.math.FastPoint2D.prototype.toString = function() { 17993 return "(" + this.x + ", " + this.y + ")"; 17994 }; 17995 17996 /** 17997 * Immutable 3D point. 17998 * @constructor 17999 * @param {Number} x The X coordinate for the point. 18000 * @param {Number} y The Y coordinate for the point. 18001 * @param {Number} z The Z coordinate for the point. 18002 */ 18003 dwv.math.Point3D = function(x, y, z) { 18004 /** 18005 * Get the X position of the point. 18006 * @return {Number} The X position of the point. 18007 */ 18008 this.getX = function() { return x; }; 18009 /** 18010 * Get the Y position of the point. 18011 * @return {Number} The Y position of the point. 18012 */ 18013 this.getY = function() { return y; }; 18014 /** 18015 * Get the Z position of the point. 18016 * @return {Number} The Z position of the point. 18017 */ 18018 this.getZ = function() { return z; }; 18019 }; // Point3D class 18020 18021 /** 18022 * Check for Point3D equality. 18023 * @param {Object} rhs The other point to compare to. 18024 * @return {Boolean} True if both points are equal. 18025 */ 18026 dwv.math.Point3D.prototype.equals = function(rhs) { 18027 return rhs !== null && 18028 this.getX() === rhs.getX() && 18029 this.getY() === rhs.getY() && 18030 this.getZ() === rhs.getZ(); 18031 }; 18032 18033 /** 18034 * Get a string representation of the Point3D. 18035 * @return {String} The point as a string. 18036 */ 18037 dwv.math.Point3D.prototype.toString = function() { 18038 return "(" + this.getX() + 18039 ", " + this.getY() + 18040 ", " + this.getZ() + ")"; 18041 }; 18042 18043 /** 18044 * Get the distance to another Point3D. 18045 * @param {Object} point3D The input point. 18046 */ 18047 dwv.math.Point3D.prototype.getDistance = function(point3D) { 18048 return Math.sqrt( 18049 (this.getX() - point3D.getX()) * (this.getX() - point3D.getX()) + 18050 (this.getY() - point3D.getY()) * (this.getY() - point3D.getY()) + 18051 (this.getZ() - point3D.getZ()) * (this.getZ() - point3D.getZ())); 18052 }; 18053 18054 /** 18055 * Get the difference to another Point3D. 18056 * @param {Object} point3D The input point. 18057 * @return {Object} The 3D vector from the input point to this one. 18058 */ 18059 dwv.math.Point3D.prototype.minus = function(point3D) { 18060 return new dwv.math.Vector3D( 18061 (this.getX() - point3D.getX()), 18062 (this.getY() - point3D.getY()), 18063 (this.getZ() - point3D.getZ())); 18064 }; 18065 18066 /** 18067 * Immutable 3D index. 18068 * @constructor 18069 * @param {Number} i The column index. 18070 * @param {Number} j The row index. 18071 * @param {Number} k The slice index. 18072 */ 18073 dwv.math.Index3D = function(i, j, k) { 18074 /** 18075 * Get the column index. 18076 * @return {Number} The column index. 18077 */ 18078 this.getI = function() { return i; }; 18079 /** 18080 * Get the row index. 18081 * @return {Number} The row index. 18082 */ 18083 this.getJ = function() { return j; }; 18084 /** 18085 * Get the slice index. 18086 * @return {Number} The slice index. 18087 */ 18088 this.getK = function() { return k; }; 18089 }; // Index3D class 18090 18091 /** 18092 * Check for Index3D equality. 18093 * @param {Object} rhs The other index to compare to. 18094 * @return {Boolean} True if both indices are equal. 18095 */ 18096 dwv.math.Index3D.prototype.equals = function(rhs) { 18097 return rhs !== null && 18098 this.getI() === rhs.getI() && 18099 this.getJ() === rhs.getJ() && 18100 this.getK() === rhs.getK(); 18101 }; 18102 18103 /** 18104 * Get a string representation of the Index3D. 18105 * @return {String} The Index3D as a string. 18106 */ 18107 dwv.math.Index3D.prototype.toString = function() { 18108 return "(" + this.getI() + 18109 ", " + this.getJ() + 18110 ", " + this.getK() + ")"; 18111 }; 18112 18113 // namespaces 18114 var dwv = dwv || {}; 18115 dwv.math = dwv.math || {}; 18116 18117 // Pre-created to reduce allocation in inner loops 18118 var __twothirdpi = (2 / (3 * Math.PI)); 18119 18120 /** 18121 * 18122 */ 18123 dwv.math.computeGreyscale = function(data, width, height) { 18124 // Returns 2D augmented array containing greyscale data 18125 // Greyscale values found by averaging colour channels
18126 // Input should be in a flat RGBA array, with values between 0 and 255 18127 var greyscale = []; 18128 18129 // Compute actual values 18130 for (var y = 0; y < height; y++) { 18131 greyscale[y] = []; 18132 18133 for (var x = 0; x < width; x++) { 18134 var p = (y * width + x) * 4; 18135 greyscale[y][x] = (data[p] + data[p + 1] + data[p + 2]) / (3 * 255); 18136 } 18137 } 18138 18139 // Augment with convenience functions 18140 greyscale.dx = function(x, y) { 18141 if (x + 1 === this[y].length) { 18142 // If we're at the end, back up one 18143 x--; 18144 } 18145 return this[y][x + 1] - this[y][x]; 18146 }; 18147 18148 greyscale.dy = function(x, y) { 18149 if (y + 1 === this.length) { 18150 // If we're at the end, back up one 18151 y--; 18152 } 18153 return this[y][x] - this[y + 1][x]; 18154 }; 18155 18156 greyscale.gradMagnitude = function(x, y) { 18157 var dx = this.dx(x, y); 18158 var dy = this.dy(x, y); 18159 return Math.sqrt(dx * dx + dy * dy); 18160 }; 18161 18162 greyscale.laplace = function(x, y) { 18163 // Laplacian of Gaussian 18164 var lap = -16 * this[y][x]; 18165 lap += this[y - 2][x]; 18166 lap += this[y - 1][x - 1] + 2 * this[y - 1][x] + this[y - 1][x + 1]; 18167 lap += this[y][x - 2] + 2 * this[y][x - 1] + 2 * this[y][x + 1] + this[y][x + 2]; 18168 lap += this[y + 1][x - 1] + 2 * this[y + 1][x] + this[y + 1][x + 1]; 18169 lap += this[y + 2][x]; 18170 18171 return lap; 18172 }; 18173 18174 return greyscale; 18175 }; 18176 18177 /** 18178 * 18179 */ 18180 dwv.math.computeGradient = function(greyscale) { 18181 // Returns a 2D array of gradient magnitude values for greyscale. The values 18182 // are scaled between 0 and 1, and then flipped, so that it works as a cost 18183 // function. 18184 var gradient = []; 18185 18186 var max = 0; // Maximum gradient found, for scaling purposes 18187 18188 var x = 0; 18189 var y = 0; 18190 18191 for (y = 0; y < greyscale.length - 1; y++) { 18192 gradient[y] = []; 18193 18194 for (x = 0; x < greyscale[y].length - 1; x++) { 18195 gradient[y][x] = greyscale.gradMagnitude(x, y); 18196 max = Math.max(gradient[y][x], max); 18197 } 18198 18199 gradient[y][greyscale[y].length - 1] = gradient[y][greyscale.length - 2]; 18200 } 18201 18202 gradient[greyscale.length - 1] = []; 18203 for (var i = 0; i < gradient[0].length; i++) { 18204 gradient[greyscale.length - 1][i] = gradient[greyscale.length - 2][i]; 18205 } 18206 18207 // Flip and scale. 18208 for (y = 0; y < gradient.length; y++) { 18209 for (x = 0; x < gradient[y].length; x++) { 18210 gradient[y][x] = 1 - (gradient[y][x] / max); 18211 } 18212 } 18213 18214 return gradient; 18215 }; 18216 18217 /** 18218 * 18219 */ 18220 dwv.math.computeLaplace = function(greyscale) { 18221 // Returns a 2D array of Laplacian of Gaussian values 18222 var laplace = []; 18223 18224 // Make the edges low cost here. 18225 18226 laplace[0] = []; 18227 laplace[1] = []; 18228 for (var i = 1; i < greyscale.length; i++) { 18229 // Pad top, since we can't compute Laplacian 18230 laplace[0][i] = 1; 18231 laplace[1][i] = 1; 18232 } 18233 18234 for (var y = 2; y < greyscale.length - 2; y++) { 18235 laplace[y] = []; 18236 // Pad left, ditto 18237 laplace[y][0] = 1; 18238 laplace[y][1] = 1; 18239 18240 for (var x = 2; x < greyscale[y].length - 2; x++) { 18241 // Threshold needed to get rid of clutter. 18242 laplace[y][x] = (greyscale.laplace(x, y) > 0.33) ? 0 : 1; 18243 } 18244 18245 // Pad right, ditto 18246 laplace[y][greyscale[y].length - 2] = 1; 18247 laplace[y][greyscale[y].length - 1] = 1; 18248 } 18249 18250 laplace[greyscale.length - 2] = []; 18251 laplace[greyscale.length - 1] = []; 18252 for (var j = 1; j < greyscale.length; j++) { 18253 // Pad bottom, ditto 18254 laplace[greyscale.length - 2][j] = 1; 18255 laplace[greyscale.length - 1][j] = 1; 18256 } 18257 18258 return laplace; 18259 }; 18260 18261 dwv.math.computeGradX = function(greyscale) { 18262 // Returns 2D array of x-gradient values for greyscale 18263 var gradX = []; 18264 18265 for (var y = 0; y < greyscale.length; y++) { 18266 gradX[y] = []; 18267 18268 for (var x = 0; x < greyscale[y].length - 1; x++) { 18269 gradX[y][x] = greyscale.dx(x, y); 18270 } 18271 18272 gradX[y][greyscale[y].length - 1] = gradX[y][greyscale[y].length - 2]; 18273 } 18274 18275 return gradX; 18276 }; 18277 18278 dwv.math.computeGradY = function(greyscale) { 18279 // Returns 2D array of y-gradient values for greyscale 18280 var gradY = []; 18281 18282 for (var y = 0; y < greyscale.length - 1; y++) { 18283 gradY[y] = []; 18284 18285 for (var x = 0; x < greyscale[y].length; x++) { 18286 gradY[y][x] = greyscale.dy(x, y); 18287 } 18288 } 18289 18290 gradY[greyscale.length - 1] = []; 18291 for (var i = 0; i < greyscale[0].length; i++) { 18292 gradY[greyscale.length - 1][i] = gradY[greyscale.length - 2][i]; 18293 } 18294 18295 return gradY; 18296 }; 18297 18298 dwv.math.gradUnitVector = function(gradX, gradY, px, py, out) { 18299 // Returns the gradient vector at (px,py), scaled to a magnitude of 1 18300 var ox = gradX[py][px]; 18301 var oy = gradY[py][px]; 18302 18303 var gvm = Math.sqrt(ox * ox + oy * oy); 18304 gvm = Math.max(gvm, 1e-100); // To avoid possible divide-by-0 errors 18305 18306 out.x = ox / gvm; 18307 out.y = oy / gvm; 18308 }; 18309 18310 dwv.math.gradDirection = function(gradX, gradY, px, py, qx, qy) { 18311 var __dgpuv = new dwv.math.FastPoint2D(-1, -1); 18312 var __gdquv = new dwv.math.FastPoint2D(-1, -1); 18313 // Compute the gradiant direction, in radians, between to points 18314 dwv.math.gradUnitVector(gradX, gradY, px, py, __dgpuv); 18315 dwv.math.gradUnitVector(gradX, gradY, qx, qy, __gdquv); 18316 18317 var dp = __dgpuv.y * (qx - px) - __dgpuv.x * (qy - py); 18318 var dq = __gdquv.y * (qx - px) - __gdquv.x * (qy - py); 18319 18320 // Make sure dp is positive, to keep things consistant 18321 if (dp < 0) { 18322 dp = -dp; 18323 dq = -dq; 18324 } 18325 18326 if (px !== qx && py !== qy) { 18327 // We're going diagonally between pixels 18328 dp *= Math.SQRT1_2; 18329 dq *= Math.SQRT1_2; 18330 } 18331
18332 return __twothirdpi * (Math.acos(dp) + Math.acos(dq)); 18333 }; 18334 18335 dwv.math.computeSides = function(dist, gradX, gradY, greyscale) { 18336 // Returns 2 2D arrays, containing inside and outside greyscale values. 18337 // These greyscale values are the intensity just a little bit along the 18338 // gradient vector, in either direction, from the supplied point. These 18339 // values are used when using active-learning Intelligent Scissors 18340 18341 var sides = {}; 18342 sides.inside = []; 18343 sides.outside = []; 18344 18345 var guv = new dwv.math.FastPoint2D(-1, -1); // Current gradient unit vector 18346 18347 for (var y = 0; y < gradX.length; y++) { 18348 sides.inside[y] = []; 18349 sides.outside[y] = []; 18350 18351 for (var x = 0; x < gradX[y].length; x++) { 18352 dwv.math.gradUnitVector(gradX, gradY, x, y, guv); 18353 18354 //(x, y) rotated 90 = (y, -x) 18355 18356 var ix = Math.round(x + dist * guv.y); 18357 var iy = Math.round(y - dist * guv.x); 18358 var ox = Math.round(x - dist * guv.y); 18359 var oy = Math.round(y + dist * guv.x); 18360 18361 ix = Math.max(Math.min(ix, gradX[y].length - 1), 0); 18362 ox = Math.max(Math.min(ox, gradX[y].length - 1), 0); 18363 iy = Math.max(Math.min(iy, gradX.length - 1), 0); 18364 oy = Math.max(Math.min(oy, gradX.length - 1), 0); 18365 18366 sides.inside[y][x] = greyscale[iy][ix]; 18367 sides.outside[y][x] = greyscale[oy][ox]; 18368 } 18369 } 18370 18371 return sides; 18372 }; 18373 18374 dwv.math.gaussianBlur = function(buffer, out) { 18375 // Smooth values over to fill in gaps in the mapping 18376 out[0] = 0.4 * buffer[0] + 0.5 * buffer[1] + 0.1 * buffer[1]; 18377 out[1] = 0.25 * buffer[0] + 0.4 * buffer[1] + 0.25 * buffer[2] + 0.1 * buffer[3]; 18378 18379 for (var i = 2; i < buffer.length - 2; i++) { 18380 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]; 18381 } 18382 18383 var len = buffer.length; 18384 out[len - 2] = 0.25 * buffer[len - 1] + 0.4 * buffer[len - 2] + 0.25 * buffer[len - 3] + 0.1 * buffer[len - 4]; 18385 out[len - 1] = 0.4 * buffer[len - 1] + 0.5 * buffer[len - 2] + 0.1 * buffer[len - 3]; 18386 }; 18387 18388 18389 /** 18390 * Scissors 18391 * 18392 * Ref: Eric N. Mortensen, William A. Barrett, Interactive Segmentation with 18393 * Intelligent Scissors, Graphical Models and Image Processing, Volume 60, 18394 * Issue 5, September 1998, Pages 349-384, ISSN 1077-3169, 18395 * DOI: 10.1006/gmip.1998.0480. 18396 * 18397 * {@link http://www.sciencedirect.com/science/article/B6WG4-45JB8WN-9/2/6fe59d8089fd1892c2bfb82283065579} 18398 * 18399 * Highly inspired from {@link http://code.google.com/p/livewire-javascript/} 18400 * @constructor 18401 */ 18402 dwv.math.Scissors = function() { 18403 this.width = -1; 18404 this.height = -1; 18405 18406 this.curPoint = null; // Corrent point we're searching on. 18407 this.searchGranBits = 8; // Bits of resolution for BucketQueue. 18408 this.searchGran = 1 << this.earchGranBits; //bits. 18409 this.pointsPerPost = 500; 18410 18411 // Precomputed image data. All in ranges 0 >= x >= 1 and all inverted (1 - x). 18412 this.greyscale = null; // Greyscale of image 18413 this.laplace = null; // Laplace zero-crossings (either 0 or 1). 18414 this.gradient = null; // Gradient magnitudes. 18415 this.gradX = null; // X-differences. 18416 this.gradY = null; // Y-differences. 18417 18418 this.parents = null; // Matrix mapping point => parent along shortest-path to root. 18419 18420 this.working = false; // Currently computing shortest paths? 18421 18422 // Begin Training: 18423 this.trained = false; 18424 this.trainingPoints = null; 18425 18426 this.edgeWidth = 2; 18427 this.trainingLength = 32; 18428 18429 this.edgeGran = 256; 18430 this.edgeTraining = null; 18431 18432 this.gradPointsNeeded = 32; 18433 this.gradGran = 1024; 18434 this.gradTraining = null; 18435 18436 this.insideGran = 256; 18437 this.insideTraining = null; 18438 18439 this.outsideGran = 256; 18440 this.outsideTraining = null; 18441 // End Training 18442 }; // Scissors class 18443 18444 // Begin training methods // 18445 dwv.math.Scissors.prototype.getTrainingIdx = function(granularity, value) { 18446 return Math.round((granularity - 1) * value); 18447 }; 18448 18449 dwv.math.Scissors.prototype.getTrainedEdge = function(edge) { 18450 return this.edgeTraining[this.getTrainingIdx(this.edgeGran, edge)]; 18451 }; 18452 18453 dwv.math.Scissors.prototype.getTrainedGrad = function(grad) { 18454 return this.gradTraining[this.getTrainingIdx(this.gradGran, grad)]; 18455 }; 18456 18457 dwv.math.Scissors.prototype.getTrainedInside = function(inside) { 18458 return this.insideTraining[this.getTrainingIdx(this.insideGran, inside)]; 18459 }; 18460 18461 dwv.math.Scissors.prototype.getTrainedOutside = function(outside) { 18462 return this.outsideTraining[this.getTrainingIdx(this.outsideGran, outside)]; 18463 }; 18464 // End training methods // 18465 18466 dwv.math.Scissors.prototype.setWorking = function(working) { 18467 // Sets working flag 18468 this.working = working; 18469 }; 18470 18471 dwv.math.Scissors.prototype.setDimensions = function(width, height) { 18472 this.width = width; 18473 this.height = height; 18474 }; 18475 18476 dwv.math.Scissors.prototype.setData = function(data) { 18477 if (this.width === -1 || this.height === -1) { 18478 // The width and height should have already been set 18479 throw new Error("Dimensions have not been set."); 18480 } 18481 18482 this.greyscale = dwv.math.computeGreyscale(data, this.width, this.height); 18483 this.laplace = dwv.math.computeLaplace(this.greyscale); 18484 this.gradient = dwv.math.computeGradient(this.greyscale); 18485 this.gradX = dwv.math.computeGradX(this.greyscale); 18486 this.gradY = dwv.math.computeGradY(this.greyscale); 18487 18488 var sides = dwv.math.computeSides(this.edgeWidth, this.gradX, this.gradY, this.greyscale); 18489 this.inside = sides.inside; 18490 this.outside = sides.outside; 18491 this.edgeTraining = []; 18492 this.gradTraining = []; 18493 this.insideTraining = []; 18494 this.outsideTraining = []; 18495 }; 18496 18497 dwv.math.Scissors.prototype.findTrainingPoints = function(p) { 18498 // Grab the last handful of points for training 18499 var points = []; 18500 18501 if (this.parents !== null) { 18502 for (var i = 0; i < this.trainingLength && p; i++) { 18503 points.push(p); 18504 p = this.parents[p.y][p.x]; 18505 } 18506 } 18507 18508 return points; 18509 }; 18510 18511 dwv.math.Scissors.prototype.resetTraining = function() { 18512 this.trained = false; // Training is ignored with this flag set 18513 }; 18514 18515 dwv.math.Scissors.prototype.doTraining = function(p) { 18516 // Compute training weights and measures 18517 this.trainingPoints = this.findTrainingPoints(p); 18518 18519 if (this.trainingPoints.length < 8) { 18520 return; // Not enough points, I think. It might crash if length = 0. 18521 } 18522 18523 var buffer = []; 18524 this.calculateTraining(buffer, this.edgeGran, this.greyscale, this.edgeTraining); 18525 this.calculateTraining(buffer, this.gradGran, this.gradient, this.gradTraining); 18526 this.calculateTraining(buffer, this.insideGran, this.inside, this.insideTraining); 18527 this.calculateTraining(buffer, this.outsideGran, this.outside, this.outsideTraining); 18528 18529 if (this.trainingPoints.length < this.gradPointsNeeded) { 18530 // If we have two few training points, the gradient weight map might not 18531 // be smooth enough, so average with normal weights. 18532 this.addInStaticGrad(this.trainingPoints.length, this.gradPointsNeeded); 18533 } 18534 18535 this.trained = true; 18536 }; 18537 18538 dwv.math.Scissors.prototype.calculateTraining = function(buffer, granularity, input, output) { 18539 var i = 0; 18540 // Build a map of raw-weights to trained-weights by favoring input values 18541 buffer.length = granularity; 18542 for (i = 0; i < granularity; i++) { 18543 buffer[i] = 0; 18544 } 18545 18546 var maxVal = 1; 18547 for (i = 0; i < this.trainingPoints.length; i++) { 18548 var p = this.trainingPoints[i]; 18549 var idx = this.getTrainingIdx(granularity, input[p.y][p.x]); 18550 buffer[idx] += 1; 18551 18552 maxVal = Math.max(maxVal, buffer[idx]); 18553 } 18554 18555 // Invert and scale. 18556 for (i = 0; i < granularity; i++) { 18557 buffer[i] = 1 - buffer[i] / maxVal; 18558 } 18559 18560 // Blur it, as suggested. Gets rid of static. 18561 dwv.math.gaussianBlur(buffer, output); 18562 }; 18563 18564 dwv.math.Scissors.prototype.addInStaticGrad = function(have, need) { 18565 // Average gradient raw-weights to trained-weights map with standard weight 18566 // map so that we don't end up with something to spiky 18567 for (var i = 0; i < this.gradGran; i++) { 18568 this.gradTraining[i] = Math.min(this.gradTraining[i], 1 - i * (need - have) / (need * this.gradGran)); 18569 } 18570 }; 18571 18572 dwv.math.Scissors.prototype.gradDirection = function(px, py, qx, qy) { 18573 return dwv.math.gradDirection(this.gradX, this.gradY, px, py, qx, qy); 18574 }; 18575 18576 dwv.math.Scissors.prototype.dist = function(px, py, qx, qy) { 18577 // The grand culmunation of most of the code: the weighted distance function 18578 var grad = this.gradient[qy][qx]; 18579 18580 if (px === qx || py === qy) { 18581 // The distance is Euclidean-ish; non-diagonal edges should be shorter 18582 grad *= Math.SQRT1_2; 18583 } 18584 18585 var lap = this.laplace[qy][qx]; 18586 var dir = this.gradDirection(px, py, qx, qy); 18587 18588 if (this.trained) { 18589 // Apply training magic 18590 var gradT = this.getTrainedGrad(grad); 18591 var edgeT = this.getTrainedEdge(this.greyscale[py][px]); 18592 var insideT = this.getTrainedInside(this.inside[py][px]); 18593 var outsideT = this.getTrainedOutside(this.outside[py][px]); 18594 18595 return 0.3 * gradT + 0.3 * lap + 0.1 * (dir + edgeT + insideT + outsideT); 18596 } else { 18597 // Normal weights 18598 return 0.43 * grad + 0.43 * lap + 0.11 * dir; 18599 } 18600 }; 18601 18602 dwv.math.Scissors.prototype.adj = function(p) { 18603 var list = []; 18604 18605 var sx = Math.max(p.x - 1, 0); 18606 var sy = Math.max(p.y - 1, 0); 18607 var ex = Math.min(p.x + 1, this.greyscale[0].length - 1); 18608 var ey = Math.min(p.y + 1, this.greyscale.length - 1); 18609 18610 var idx = 0; 18611 for (var y = sy; y <= ey; y++) { 18612 for (var x = sx; x <= ex; x++) { 18613 if (x !== p.x || y !== p.y) { 18614 list[idx++] = new dwv.math.FastPoint2D(x, y); 18615 } 18616 } 18617 } 18618 18619 return list; 18620 }; 18621 18622 dwv.math.Scissors.prototype.setPoint = function(sp) { 18623 this.setWorking(true); 18624 18625 this.curPoint = sp; 18626 18627 var x = 0; 18628 var y = 0; 18629 18630 this.visited = []; 18631 for (y = 0; y < this.height; y++) { 18632 this.visited[y] = []; 18633 for (x = 0; x < this.width; x++) { 18634 this.visited[y][x] = false; 18635 } 18636 } 18637 18638 this.parents = []; 18639 for (y = 0; y < this.height; y++) { 18640 this.parents[y] = []; 18641 } 18642 18643 this.cost = []; 18644 for (y = 0; y < this.height; y++) { 18645 this.cost[y] = []; 18646 for (x = 0; x < this.width; x++) { 18647 this.cost[y][x] = Number.MAX_VALUE; 18648 } 18649 } 18650 18651 this.pq = new dwv.math.BucketQueue(this.searchGranBits, function(p) { 18652 return Math.round(this.searchGran * this.costArr[p.y][p.x]); 18653 }); 18654 this.pq.searchGran = this.searchGran; 18655 this.pq.costArr = this.cost; 18656 18657 this.pq.push(sp); 18658 this.cost[sp.y][sp.x] = 0; 18659 }; 18660 18661 dwv.math.Scissors.prototype.doWork = function() { 18662 if (!this.working) { 18663 return; 18664 } 18665 18666 this.timeout = null; 18667 18668 var pointCount = 0; 18669 var newPoints = []; 18670 while (!this.pq.isEmpty() && pointCount < this.pointsPerPost) { 18671 var p = this.pq.pop();
18672 newPoints.push(p); 18673 newPoints.push(this.parents[p.y][p.x]); 18674 18675 this.visited[p.y][p.x] = true; 18676 18677 var adjList = this.adj(p); 18678 for (var i = 0; i < adjList.length; i++) { 18679 var q = adjList[i]; 18680 18681 var pqCost = this.cost[p.y][p.x] + this.dist(p.x, p.y, q.x, q.y); 18682 18683 if (pqCost < this.cost[q.y][q.x]) { 18684 if (this.cost[q.y][q.x] !== Number.MAX_VALUE) { 18685 // Already in PQ, must remove it so we can re-add it. 18686 this.pq.remove(q); 18687 } 18688 18689 this.cost[q.y][q.x] = pqCost; 18690 this.parents[q.y][q.x] = p; 18691 this.pq.push(q); 18692 } 18693 } 18694 18695 pointCount++; 18696 } 18697 18698 return newPoints; 18699 }; 18700 18701 // namespaces 18702 var dwv = dwv || {}; 18703 dwv.math = dwv.math || {}; 18704 18705 /** 18706 * Mulitply the three inputs if the last two are not null. 18707 * @param {Number} a The first input. 18708 * @param {Number} b The second input. 18709 * @param {Number} c The third input. 18710 * @return {Number} The multiplication of the three inputs or 18711 * null if one of the last two is null. 18712 */ 18713 function mulABC(a, b, c) { 18714 var res = null; 18715 if (b !== null && c !== null) { 18716 res = a * b * c; 18717 } 18718 return res; 18719 } 18720 18721 /** 18722 * Circle shape. 18723 * @constructor 18724 * @param {Object} centre A Point2D representing the centre of the circle. 18725 * @param {Number} radius The radius of the circle. 18726 */ 18727 dwv.math.Circle = function(centre, radius) { 18728 /** 18729 * Circle surface. 18730 * @private 18731 * @type Number 18732 */ 18733 var surface = Math.PI * radius * radius; 18734 18735 /** 18736 * Get the centre (point) of the circle. 18737 * @return {Object} The center (point) of the circle. 18738 */ 18739 this.getCenter = function() { return centre; }; 18740 /** 18741 * Get the radius of the circle. 18742 * @return {Number} The radius of the circle. 18743 */ 18744 this.getRadius = function() { return radius; }; 18745 /** 18746 * Get the surface of the circle. 18747 * @return {Number} The surface of the circle. 18748 */
18749 this.getSurface = function() { return surface; }; 18750 /** 18751 * Get the surface of the circle according to a spacing. 18752 * @param {Number} spacingX The X spacing. 18753 * @param {Number} spacingY The Y spacing. 18754 * @return {Number} The surface of the circle multiplied by the given 18755 * spacing or null for null spacings. 18756 */ 18757 this.getWorldSurface = function(spacingX, spacingY) { 18758 return mulABC(surface, spacingX, spacingY); 18759 }; 18760 }; // Circle class 18761 18762 /** 18763 * Ellipse shape. 18764 * @constructor 18765 * @param {Object} centre A Point2D representing the centre of the ellipse. 18766 * @param {Number} a The radius of the ellipse on the horizontal axe. 18767 * @param {Number} b The radius of the ellipse on the vertical axe. 18768 */ 18769 dwv.math.Ellipse = function(centre, a, b) { 18770 /** 18771 * Circle surface. 18772 * @private 18773 * @type Number 18774 */ 18775 var surface = Math.PI * a * b; 18776 18777 /** 18778 * Get the centre (point) of the ellipse. 18779 * @return {Object} The center (point) of the ellipse. 18780 */ 18781 this.getCenter = function() { return centre; }; 18782 /** 18783 * Get the radius of the ellipse on the horizontal axe. 18784 * @return {Number} The radius of the ellipse on the horizontal axe. 18785 */ 18786 this.getA = function() { return a; }; 18787 /** 18788 * Get the radius of the ellipse on the vertical axe. 18789 * @return {Number} The radius of the ellipse on the vertical axe. 18790 */ 18791 this.getB = function() { return b; }; 18792 /** 18793 * Get the surface of the ellipse. 18794 * @return {Number} The surface of the ellipse. 18795 */
18796 this.getSurface = function() { return surface; }; 18797 /** 18798 * Get the surface of the ellipse according to a spacing. 18799 * @param {Number} spacingX The X spacing. 18800 * @param {Number} spacingY The Y spacing. 18801 * @return {Number} The surface of the ellipse multiplied by the given 18802 * spacing or null for null spacings. 18803 */ 18804 this.getWorldSurface = function(spacingX, spacingY) { 18805 return mulABC(surface, spacingX, spacingY); 18806 }; 18807 }; // Circle class 18808 18809 /** 18810 * Line shape. 18811 * @constructor 18812 * @param {Object} begin A Point2D representing the beginning of the line. 18813 * @param {Object} end A Point2D representing the end of the line. 18814 */ 18815 dwv.math.Line = function(begin, end) { 18816 /** 18817 * Line delta in the X direction. 18818 * @private 18819 * @type Number 18820 */ 18821 var dx = end.getX() - begin.getX(); 18822 /** 18823 * Line delta in the Y direction. 18824 * @private 18825 * @type Number 18826 */ 18827 var dy = end.getY() - begin.getY(); 18828 /** 18829 * Line length. 18830 * @private 18831 * @type Number 18832 */ 18833 var length = Math.sqrt(dx * dx + dy * dy); 18834 18835 /** 18836 * Get the begin point of the line. 18837 * @return {Object} The beginning point of the line. 18838 */ 18839 this.getBegin = function() { return begin; }; 18840 /** 18841 * Get the end point of the line. 18842 * @return {Object} The ending point of the line. 18843 */ 18844 this.getEnd = function() { return end; }; 18845 /** 18846 * Get the line delta in the X direction. 18847 * @return {Number} The delta in the X direction. 18848 */ 18849 this.getDeltaX = function() { return dx; }; 18850 /** 18851 * Get the line delta in the Y direction. 18852 * @return {Number} The delta in the Y direction. 18853 */ 18854 this.getDeltaY = function() { return dy; }; 18855 /** 18856 * Get the length of the line. 18857 * @return {Number} The length of the line. 18858 */ 18859 this.getLength = function() { return length; }; 18860 /** 18861 * Get the length of the line according to a spacing. 18862 * @param {Number} spacingX The X spacing. 18863 * @param {Number} spacingY The Y spacing. 18864 * @return {Number} The length of the line with spacing 18865 * or null for null spacings. 18866 */ 18867 this.getWorldLength = function(spacingX, spacingY) { 18868 var wlen = null; 18869 if (spacingX !== null && spacingY !== null) { 18870 var dxs = dx * spacingX; 18871 var dys = dy * spacingY; 18872 wlen = Math.sqrt(dxs * dxs + dys * dys); 18873 } 18874 return wlen; 18875 }; 18876 /** 18877 * Get the mid point of the line. 18878 * @return {Object} The mid point of the line. 18879 */ 18880 this.getMidpoint = function() { 18881 return new dwv.math.Point2D( 18882 parseInt((begin.getX() + end.getX()) / 2, 10), 18883 parseInt((begin.getY() + end.getY()) / 2, 10)); 18884 }; 18885 /** 18886 * Get the slope of the line. 18887 * @return {Number} The slope of the line. 18888 */
18889 this.getSlope = function() { 18890 return dy / dx; 18891 }; 18892 /** 18893 * Get the intercept of the line. 18894 * @return {Number} The slope of the line. 18895 */ 18896 this.getIntercept = function() { 18897 return (end.getX() * begin.getY() - begin.getX() * end.getY()) / dx; 18898 }; 18899 /** 18900 * Get the inclination of the line. 18901 * @return {Number} The inclination of the line. 18902 */ 18903 this.getInclination = function() { 18904 // tan(theta) = slope 18905 var angle = Math.atan2(dy, dx) * 180 / Math.PI; 18906 // shift? 18907 return 180 - angle; 18908 }; 18909 }; // Line class 18910 18911 /** 18912 * Get the angle between two lines. 18913 * @param line0 The first line. 18914 * @param line1 The second line. 18915 */ 18916 dwv.math.getAngle = function(line0, line1) { 18917 var dx0 = line0.getDeltaX(); 18918 var dy0 = line0.getDeltaY(); 18919 var dx1 = line1.getDeltaX(); 18920 var dy1 = line1.getDeltaY(); 18921 // dot = ||a||*||b||*cos(theta) 18922 var dot = dx0 * dx1 + dy0 * dy1; 18923 // cross = ||a||*||b||*sin(theta) 18924 var det = dx0 * dy1 - dy0 * dx1; 18925 // tan = sin / cos 18926 var angle = Math.atan2(det, dot) * 180 / Math.PI; 18927 // complementary angle 18928 // shift? 18929 return 360 - (180 - angle); 18930 }; 18931 18932 /** 18933 * Get a perpendicular line to an input one. 18934 * @param {Object} line The line to be perpendicular to. 18935 * @param {Object} point The middle point of the perpendicular line. 18936 * @param {Number} length The length of the perpendicular line. 18937 */ 18938 dwv.math.getPerpendicularLine = function(line, point, length) { 18939 // begin point 18940 var beginX = 0; 18941 var beginY = 0; 18942 // end point 18943 var endX = 0; 18944 var endY = 0; 18945 18946 // check slope: 18947 // 0 -> horizontal 18948 // Infinite -> vertical (a/Infinite = 0) 18949 if (line.getSlope() !== 0) { 18950 // a0 * a1 = -1 18951 var slope = -1 / line.getSlope(); 18952 // y0 = a1*x0 + b1 -> b1 = y0 - a1*x0 18953 var intercept = point.getY() - slope * point.getX(); 18954
18955 // 1. (x - x0)^2 + (y - y0)^2 = d^2 18956 // 2. a = (y - y0) / (x - x0) -> y = a*(x - x0) + y0 18957 // -> (x - x0)^2 + m^2 * (x - x0)^2 = d^2 18958 // -> x = x0 +- d / sqrt(1+m^2) 18959 18960 // length is the distance between begin and end, 18961 // point is half way between both -> d = length / 2 18962 var dx = length / (2 * Math.sqrt(1 + slope * slope)); 18963 18964 // begin point 18965 beginX = point.getX() - dx; 18966 beginY = slope * beginX + intercept; 18967 // end point 18968 endX = point.getX() + dx; 18969 endY = slope * endX + intercept; 18970 } else { 18971 // horizontal input line -> perpendicular is vertical! 18972 // begin point 18973 beginX = point.getX(); 18974 beginY = point.getY() - length / 2; 18975 // end point 18976 endX = point.getX(); 18977 endY = point.getY() + length / 2; 18978 } 18979 // perpendicalar line 18980 return new dwv.math.Line( 18981 new dwv.math.Point2D(beginX, beginY), 18982 new dwv.math.Point2D(endX, endY)); 18983 }; 18984 18985 /** 18986 * Rectangle shape. 18987 * @constructor 18988 * @param {Object} begin A Point2D representing the beginning of the rectangle. 18989 * @param {Object} end A Point2D representing the end of the rectangle. 18990 */ 18991 dwv.math.Rectangle = function(begin, end) { 18992 if (end.getX() < begin.getX()) { 18993 var tmpX = begin.getX(); 18994 begin = new dwv.math.Point2D(end.getX(), begin.getY()); 18995 end = new dwv.math.Point2D(tmpX, end.getY()); 18996 } 18997 if (end.getY() < begin.getY()) { 18998 var tmpY = begin.getY(); 18999 begin = new dwv.math.Point2D(begin.getX(), end.getY()); 19000 end = new dwv.math.Point2D(end.getX(), tmpY); 19001 } 19002 19003 /** 19004 * Rectangle surface. 19005 * @private 19006 * @type Number 19007 */ 19008 var surface = Math.abs(end.getX() - begin.getX()) * Math.abs(end.getY() - begin.getY()); 19009 19010 /** 19011 * Get the begin point of the rectangle. 19012 * @return {Object} The begin point of the rectangle 19013 */ 19014 this.getBegin = function() { return begin; }; 19015 /** 19016 * Get the end point of the rectangle. 19017 * @return {Object} The end point of the rectangle 19018 */ 19019 this.getEnd = function() { return end; }; 19020 /** 19021 * Get the real width of the rectangle. 19022 * @return {Number} The real width of the rectangle. 19023 */ 19024 this.getRealWidth = function() { return end.getX() - begin.getX(); }; 19025 /** 19026 * Get the real height of the rectangle. 19027 * @return {Number} The real height of the rectangle. 19028 */ 19029 this.getRealHeight = function() { return end.getY() - begin.getY(); }; 19030 /** 19031 * Get the width of the rectangle. 19032 * @return {Number} The width of the rectangle. 19033 */ 19034 this.getWidth = function() { return Math.abs(this.getRealWidth()); }; 19035 /** 19036 * Get the height of the rectangle. 19037 * @return {Number} The height of the rectangle. 19038 */ 19039 this.getHeight = function() { return Math.abs(this.getRealHeight()); }; 19040 /** 19041 * Get the surface of the rectangle. 19042 * @return {Number} The surface of the rectangle. 19043 */
19044 this.getSurface = function() { return surface; }; 19045 /** 19046 * Get the surface of the circle according to a spacing. 19047 * @param {Number} spacingX The X spacing. 19048 * @param {Number} spacingY The Y spacing. 19049 * @return {Number} The surface of the rectangle multiplied by the given 19050 * spacing or null for null spacings. 19051 */ 19052 this.getWorldSurface = function(spacingX, spacingY) { 19053 return mulABC(surface, spacingX, spacingY); 19054 }; 19055 }; // Rectangle class 19056 19057 /** 19058 * Region Of Interest shape. 19059 * Note: should be a closed path. 19060 * @constructor 19061 */ 19062 dwv.math.ROI = function() { 19063 /** 19064 * List of points. 19065 * @private 19066 * @type Array 19067 */ 19068 var points = []; 19069 19070 /** 19071 * Get a point of the list at a given index. 19072 * @param {Number} index The index of the point to get (beware, no size check). 19073 * @return {Object} The Point2D at the given index. 19074 */ 19075 this.getPoint = function(index) { return points[index]; }; 19076 /** 19077 * Get the length of the point list. 19078 * @return {Number} The length of the point list. 19079 */ 19080 this.getLength = function() { return points.length; }; 19081 /** 19082 * Add a point to the ROI. 19083 * @param {Object} point The Point2D to add. 19084 */ 19085 this.addPoint = function(point) { points.push(point); }; 19086 /** 19087 * Add points to the ROI. 19088 * @param {Array} rhs The array of POints2D to add. 19089 */ 19090 this.addPoints = function(rhs) { points = points.concat(rhs); }; 19091 }; // ROI class 19092 19093 /** 19094 * Path shape. 19095 * @constructor 19096 * @param {Array} inputPointArray The list of Point2D that make the path (optional). 19097 * @param {Array} inputControlPointIndexArray The list of control point of path, 19098 * as indexes (optional). 19099 * Note: first and last point do not need to be equal. 19100 */ 19101 dwv.math.Path = function(inputPointArray, inputControlPointIndexArray) { 19102 /** 19103 * List of points. 19104 * @type Array 19105 */ 19106 this.pointArray = inputPointArray ? inputPointArray.slice() : []; 19107 /** 19108 * List of control points. 19109 * @type Array 19110 */ 19111 this.controlPointIndexArray = inputControlPointIndexArray ? 19112 inputControlPointIndexArray.slice() : []; 19113 }; // Path class 19114 19115 /** 19116 * Get a point of the list. 19117 * @param {Number} index The index of the point to get (beware, no size check). 19118 * @return {Object} The Point2D at the given index. 19119 */ 19120 dwv.math.Path.prototype.getPoint = function(index) { 19121 return this.pointArray[index]; 19122 }; 19123 19124 /** 19125 * Is the given point a control point. 19126 * @param {Object} point The Point2D to check. 19127 * @return {Boolean} True if a control point. 19128 */ 19129 dwv.math.Path.prototype.isControlPoint = function(point) { 19130 var index = this.pointArray.indexOf(point); 19131 if (index !== -1) { 19132 return this.controlPointIndexArray.indexOf(index) !== -1; 19133 } else { 19134 throw new Error("Error: isControlPoint called with not in list point."); 19135 } 19136 }; 19137 19138 /** 19139 * Get the length of the path. 19140 * @return {Number} The length of the path. 19141 */ 19142 dwv.math.Path.prototype.getLength = function() { 19143 return this.pointArray.length; 19144 }; 19145 19146 /** 19147 * Add a point to the path. 19148 * @param {Object} point The Point2D to add. 19149 */ 19150 dwv.math.Path.prototype.addPoint = function(point) { 19151 this.pointArray.push(point); 19152 }; 19153 19154 /** 19155 * Add a control point to the path. 19156 * @param {Object} point The Point2D to make a control point. 19157 */ 19158 dwv.math.Path.prototype.addControlPoint = function(point) { 19159 var index = this.pointArray.indexOf(point); 19160 if (index !== -1) { 19161 this.controlPointIndexArray.push(index); 19162 } else { 19163 throw new Error("Error: addControlPoint called with no point in list point."); 19164 } 19165 }; 19166 19167 /** 19168 * Add points to the path. 19169 * @param {Array} points The list of Point2D to add. 19170 */ 19171 dwv.math.Path.prototype.addPoints = function(newPointArray) { 19172 this.pointArray = this.pointArray.concat(newPointArray); 19173 }; 19174 19175 /** 19176 * Append a Path to this one. 19177 * @param {Path} other The Path to append. 19178 */ 19179 dwv.math.Path.prototype.appenPath = function(other) { 19180 var oldSize = this.pointArray.length; 19181 this.pointArray = this.pointArray.concat(other.pointArray); 19182 var indexArray = []; 19183 for (var i = 0; i < other.controlPointIndexArray.length; ++i) { 19184 indexArray[i] = other.controlPointIndexArray[i] + oldSize; 19185 } 19186 this.controlPointIndexArray = this.controlPointIndexArray.concat(indexArray);
19187 }; 19188 19189 // namespaces 19190 var dwv = dwv || {}; 19191 dwv.math = dwv.math || {}; 19192 19193 /** 19194 * Get the minimum, maximum, mean and standard deviation 19195 * of an array of values. 19196 * Note: could use {@link https://github.com/tmcw/simple-statistics}. 19197 */ 19198 dwv.math.getStats = function(array) { 19199 var min = array[0]; 19200 var max = min; 19201 var mean = 0; 19202 var sum = 0; 19203 var sumSqr = 0; 19204 var stdDev = 0; 19205 var variance = 0; 19206 19207 var val = 0; 19208 for (var i = 0; i < array.length; ++i) { 19209 val = array[i]; 19210 if (val < min) { 19211 min = val; 19212 } else if (val > max) { 19213 max = val; 19214 } 19215 sum += val; 19216 sumSqr += val * val; 19217 } 19218 19219 mean = sum / array.length; 19220 // see http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance 19221 variance = sumSqr / array.length - mean * mean; 19222 stdDev = Math.sqrt(variance); 19223 19224 return { 'min': min, 'max': max, 'mean': mean, 'stdDev': stdDev }; 19225 }; 19226 19227 /** 19228 * Unique ID generator. 19229 * See {@link http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript} 19230 * and this {@link http://stackoverflow.com/a/13403498 answer}. 19231 */ 19232 dwv.math.guid = function() { 19233 return Math.random().toString(36).substring(2, 15); 19234 }; 19235 19236 // namespaces 19237 var dwv = dwv || {}; 19238 dwv.math = dwv.math || {}; 19239 19240 /** 19241 * Immutable 3D vector. 19242 * @constructor 19243 * @param {Number} x The X component of the vector. 19244 * @param {Number} y The Y component of the vector. 19245 * @param {Number} z The Z component of the vector. 19246 */ 19247 dwv.math.Vector3D = function(x, y, z) { 19248 /** 19249 * Get the X component of the vector. 19250 * @return {Number} The X component of the vector. 19251 */ 19252 this.getX = function() { return x; }; 19253 /** 19254 * Get the Y component of the vector. 19255 * @return {Number} The Y component of the vector. 19256 */ 19257 this.getY = function() { return y; }; 19258 /** 19259 * Get the Z component of the vector. 19260 * @return {Number} The Z component of the vector. 19261 */ 19262 this.getZ = function() { return z; }; 19263 }; // Vector3D class 19264 19265 /** 19266 * Check for Vector3D equality. 19267 * @param {Object} rhs The other vector to compare to. 19268 * @return {Boolean} True if both vectors are equal. 19269 */ 19270 dwv.math.Vector3D.prototype.equals = function(rhs) { 19271 return rhs !== null && 19272 this.getX() === rhs.getX() && 19273 this.getY() === rhs.getY() && 19274 this.getZ() === rhs.getZ(); 19275 }; 19276 19277 /** 19278 * Get a string representation of the Vector3D. 19279 * @return {String} The vector as a string. 19280 */ 19281 dwv.math.Vector3D.prototype.toString = function() { 19282 return "(" + this.getX() + 19283 ", " + this.getY() + 19284 ", " + this.getZ() + ")"; 19285 }; 19286 19287 /** 19288 * Get the norm of the vector. 19289 * @return {Number} The norm. 19290 */ 19291 dwv.math.Vector3D.prototype.norm = function() { 19292 return Math.sqrt((this.getX() * this.getX()) + 19293 (this.getY() * this.getY()) + 19294 (this.getZ() * this.getZ())); 19295 }; 19296 19297 /** 19298 * Get the cross product with another Vector3D, ie the 19299 * vector that is perpendicular to both a and b. 19300 * If both vectors are parallel, the cross product is a zero vector. 19301 * @param {Object} vector3D The input vector. 19302 * @return {Object} The result vector. 19303 */ 19304 dwv.math.Vector3D.prototype.crossProduct = function(vector3D) { 19305 return new dwv.math.Vector3D( 19306 (this.getY() * vector3D.getZ()) - (vector3D.getY() * this.getZ()), 19307 (this.getZ() * vector3D.getX()) - (vector3D.getZ() * this.getX()), 19308 (this.getX() * vector3D.getY()) - (vector3D.getX() * this.getY())); 19309 }; 19310 19311 /** 19312 * Get the dot product with another Vector3D. 19313 * @param {Object} vector3D The input vector. 19314 * @return {Number} The dot product. 19315 */ 19316 dwv.math.Vector3D.prototype.dotProduct = function(vector3D) { 19317 return (this.getX() * vector3D.getX()) + 19318 (this.getY() * vector3D.getY()) + 19319 (this.getZ() * vector3D.getZ()); 19320 }; 19321 19322 // namespaces 19323 var dwv = dwv || {}; 19324 dwv.tool = dwv.tool || {}; 19325 // external 19326 var Konva = Konva || {}; 19327 19328 /** 19329 * Arrow factory. 19330 * @constructor 19331 * @external Konva 19332 */ 19333 dwv.tool.ArrowFactory = function() { 19334 /** 19335 * Get the number of points needed to build the shape. 19336 * @return {Number} The number of points. 19337 */ 19338 this.getNPoints = function() { return 2; }; 19339 /** 19340 * Get the timeout between point storage. 19341 * @return {Number} The timeout in milliseconds. 19342 */ 19343 this.getTimeout = function() { return 0; }; 19344 }; 19345 19346 /** 19347 * Create an arrow shape to be displayed. 19348 * @param {Array} points The points from which to extract the line. 19349 * @param {Object} style The drawing style. 19350 * @param {Object} image The associated image. 19351 */ 19352 dwv.tool.ArrowFactory.prototype.create = function(points, style /*, image*/ ) { 19353 // physical shape 19354 var line = new dwv.math.Line(points[0], points[1]); 19355 // draw shape 19356 var kshape = new Konva.Line({ 19357 points: [line.getBegin().getX(), line.getBegin().getY(), 19358 line.getEnd().getX(), line.getEnd().getY() 19359 ], 19360 stroke: style.getLineColour(), 19361 strokeWidth: style.getScaledStrokeWidth(), 19362 name: "shape" 19363 }); 19364 // larger hitfunc 19365 var linePerp0 = dwv.math.getPerpendicularLine(line, points[0], 10); 19366 var linePerp1 = dwv.math.getPerpendicularLine(line, points[1], 10); 19367 kshape.hitFunc(function(context) { 19368 context.beginPath(); 19369 context.moveTo(linePerp0.getBegin().getX(), linePerp0.getBegin().getY()); 19370 context.lineTo(linePerp0.getEnd().getX(), linePerp0.getEnd().getY()); 19371 context.lineTo(linePerp1.getEnd().getX(), linePerp1.getEnd().getY()); 19372 context.lineTo(linePerp1.getBegin().getX(), linePerp1.getBegin().getY()); 19373 context.closePath(); 19374 context.fillStrokeShape(this); 19375 }); 19376 // triangle 19377 var beginTy = new dwv.math.Point2D(line.getBegin().getX(), line.getBegin().getY() - 10); 19378 var verticalLine = new dwv.math.Line(line.getBegin(), beginTy); 19379 var angle = dwv.math.getAngle(line, verticalLine); 19380 var angleRad = angle * Math.PI / 180; 19381 var radius = 5; 19382 var kpoly = new Konva.RegularPolygon({ 19383 x: line.getBegin().getX() + radius * Math.sin(angleRad), 19384 y: line.getBegin().getY() + radius * Math.cos(angleRad), 19385 sides: 3, 19386 radius: radius, 19387 rotation: -angle, 19388 fill: style.getLineColour(), 19389 strokeWidth: style.getScaledStrokeWidth(), 19390 name: "shape-triangle" 19391 }); 19392 // quantification 19393 var ktext = new Konva.Text({ 19394 fontSize: style.getScaledFontSize(), 19395 fontFamily: style.getFontFamily(), 19396 fill: style.getLineColour(), 19397 name: "text" 19398 }); 19399 ktext.textExpr = ""; 19400 ktext.longText = ""; 19401 ktext.quant = null; 19402 ktext.setText(ktext.textExpr); 19403 // label 19404 var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1; 19405 var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5; 19406 var klabel = new Konva.Label({ 19407 x: line.getEnd().getX() + dX * 25, 19408 y: line.getEnd().getY() + dY * 15, 19409 name: "label" 19410 }); 19411 klabel.add(ktext); 19412 klabel.add(new Konva.Tag()); 19413 19414 // return group 19415 var group = new Konva.Group(); 19416 group.name("line-group"); 19417 group.add(kshape); 19418 group.add(kpoly); 19419 group.add(klabel); 19420 group.visible(true); // dont inherit 19421 return group; 19422 }; 19423 19424 /** 19425 * Update an arrow shape. 19426 * @param {Object} anchor The active anchor. 19427 * @param {Object} image The associated image. 19428 */ 19429 dwv.tool.UpdateArrow = function(anchor /*, image*/ ) { 19430 // parent group 19431 var group = anchor.getParent(); 19432 // associated shape 19433 var kline = group.getChildren(function(node) { 19434 return node.name() === 'shape'; 19435 })[0]; 19436 // associated triangle shape 19437 var ktriangle = group.getChildren(function(node) { 19438 return node.name() === 'shape-triangle'; 19439 })[0]; 19440 // associated label 19441 var klabel = group.getChildren(function(node) { 19442 return node.name() === 'label'; 19443 })[0]; 19444 // find special points 19445 var begin = group.getChildren(function(node) { 19446 return node.id() === 'begin'; 19447 })[0]; 19448 var end = group.getChildren(function(node) { 19449 return node.id() === 'end'; 19450 })[0]; 19451 // update special points 19452 switch (anchor.id()) { 19453 case 'begin': 19454 begin.x(anchor.x()); 19455 begin.y(anchor.y()); 19456 break; 19457 case 'end': 19458 end.x(anchor.x()); 19459 end.y(anchor.y()); 19460 break; 19461 } 19462 // update shape and compensate for possible drag 19463 // note: shape.position() and shape.size() won't work... 19464 var bx = begin.x() - kline.x(); 19465 var by = begin.y() - kline.y(); 19466 var ex = end.x() - kline.x(); 19467 var ey = end.y() - kline.y(); 19468 kline.points([bx, by, ex, ey]); 19469 // new line 19470 var p2d0 = new dwv.math.Point2D(begin.x(), begin.y()); 19471 var p2d1 = new dwv.math.Point2D(end.x(), end.y()); 19472 var line = new dwv.math.Line(p2d0, p2d1); 19473 // larger hitfunc 19474 var p2b = new dwv.math.Point2D(bx, by); 19475 var p2e = new dwv.math.Point2D(ex, ey); 19476 var linePerp0 = dwv.math.getPerpendicularLine(line, p2b, 10); 19477 var linePerp1 = dwv.math.getPerpendicularLine(line, p2e, 10); 19478 kline.hitFunc(function(context) { 19479 context.beginPath(); 19480 context.moveTo(linePerp0.getBegin().getX(), linePerp0.getBegin().getY()); 19481 context.lineTo(linePerp0.getEnd().getX(), linePerp0.getEnd().getY()); 19482 context.lineTo(linePerp1.getEnd().getX(), linePerp1.getEnd().getY()); 19483 context.lineTo(linePerp1.getBegin().getX(), linePerp1.getBegin().getY()); 19484 context.closePath(); 19485 context.fillStrokeShape(this); 19486 }); 19487 // udate triangle 19488 var beginTy = new dwv.math.Point2D(line.getBegin().getX(), line.getBegin().getY() - 10); 19489 var verticalLine = new dwv.math.Line(line.getBegin(), beginTy); 19490 var angle = dwv.math.getAngle(line, verticalLine); 19491 var angleRad = angle * Math.PI / 180; 19492 ktriangle.x(line.getBegin().getX() + ktriangle.radius() * Math.sin(angleRad)); 19493 ktriangle.y(line.getBegin().getY() + ktriangle.radius() * Math.cos(angleRad)); 19494 ktriangle.rotation(-angle); 19495 // update text 19496 var ktext = klabel.getText(); 19497 ktext.quant = null; 19498 ktext.setText(ktext.textExpr); 19499 // update position 19500 var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1; 19501 var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5; 19502 var textPos = { 19503 'x': line.getEnd().getX() + dX * 25, 19504 'y': line.getEnd().getY() + dY * 15 19505 }; 19506 klabel.position(textPos); 19507 }; 19508 19509 // namespaces 19510 var dwv = dwv || {}; 19511 /** @namespace */ 19512 dwv.tool = dwv.tool || {}; 19513 // external 19514 var Konva = Konva || {}; 19515 19516 /** 19517 * Drawing tool. 19518 * @constructor 19519 * @param {Object} app The associated application. 19520 * @external Konva 19521 */ 19522 dwv.tool.Draw = function(app, shapeFactoryList) { 19523 /** 19524 * Closure to self: to be used by event handlers. 19525 * @private 19526 * @type WindowLevel 19527 */ 19528 var self = this; 19529 /** 19530 * Draw GUI. 19531 * @type Object 19532 */ 19533 var gui = null; 19534 /** 19535 * Interaction start flag. 19536 * @private 19537 * @type Boolean 19538 */ 19539 var started = false; 19540 19541 /** 19542 * Shape factory list 19543 * @type Object 19544 */ 19545 this.shapeFactoryList = shapeFactoryList; 19546 /** 19547 * Draw command. 19548 * @private 19549 * @type Object 19550 */ 19551 var command = null; 19552 /** 19553 * Current shape group. 19554 * @private 19555 * @type Object 19556 */ 19557 var shapeGroup = null; 19558 19559 /** 19560 * Shape name.
19561 * @type String 19562 */ 19563 this.shapeName = 0; 19564 19565 /** 19566 * List of points. 19567 * @private 19568 * @type Array 19569 */ 19570 var points = []; 19571 19572 /** 19573 * Last selected point. 19574 * @private 19575 * @type Object 19576 */ 19577 var lastPoint = null; 19578 19579 /** 19580 * Shape editor. 19581 * @private 19582 * @type Object 19583 */ 19584 var shapeEditor = new dwv.tool.ShapeEditor(app); 19585 19586 // associate the event listeners of the editor 19587 // with those of the draw tool 19588 shapeEditor.setDrawEventCallback(fireEvent); 19589 19590 /** 19591 * Trash draw: a cross. 19592 * @private 19593 * @type Object 19594 */ 19595 var trash = new Konva.Group(); 19596 19597 // first line of the cross 19598 var trashLine1 = new Konva.Line({ 19599 points: [-10, -10, 10, 10], 19600 stroke: 'red' 19601 }); 19602 // second line of the cross 19603 var trashLine2 = new Konva.Line({ 19604 points: [10, -10, -10, 10], 19605 stroke: 'red' 19606 }); 19607 trash.add(trashLine1); 19608 trash.add(trashLine2); 19609 19610 /** 19611 * Drawing style. 19612 * @type Style 19613 */ 19614 this.style = new dwv.html.Style(); 19615 19616 /** 19617 * Event listeners. 19618 * @private 19619 */ 19620 var listeners = {}; 19621 19622 /** 19623 * The associated draw layer. 19624 * @private 19625 * @type Object 19626 */ 19627 var drawLayer = null; 19628 19629 /** 19630 * Handle mouse down event. 19631 * @param {Object} event The mouse down event. 19632 */ 19633 this.mousedown = function(event) { 19634 // determine if the click happened in an existing shape 19635 var stage = app.getDrawStage(); 19636 var kshape = stage.getIntersection({ 19637 x: event._xs, 19638 y: event._ys 19639 }); 19640 19641 if (kshape) { 19642 var group = kshape.getParent(); 19643 var selectedShape = group.find(".shape")[0]; 19644 // reset editor if click on other shape 19645 // (and avoid anchors mouse down) 19646 if (selectedShape && selectedShape !== shapeEditor.getShape()) { 19647 shapeEditor.disable(); 19648 shapeEditor.setShape(selectedShape); 19649 shapeEditor.setImage(app.getImage()); 19650 shapeEditor.enable(); 19651 } 19652 } else { 19653 // disable edition 19654 shapeEditor.disable(); 19655 shapeEditor.setShape(null); 19656 shapeEditor.setImage(null); 19657 // start storing points 19658 started = true; 19659 // clear array 19660 points = []; 19661 // store point 19662 lastPoint = new dwv.math.Point2D(event._x, event._y); 19663 points.push(lastPoint); 19664 } 19665 }; 19666 19667 /** 19668 * Handle mouse move event. 19669 * @param {Object} event The mouse move event. 19670 */ 19671 this.mousemove = function(event) { 19672 if (!started) { 19673 return; 19674 } 19675 if (Math.abs(event._x - lastPoint.getX()) > 0 || 19676 Math.abs(event._y - lastPoint.getY()) > 0) { 19677 // current point 19678 lastPoint = new dwv.math.Point2D(event._x, event._y); 19679 // clear last added point from the list (but not the first one) 19680 if (points.length != 1) { 19681 points.pop(); 19682 } 19683 // add current one to the list 19684 points.push(lastPoint); 19685 // allow for anchor points 19686 var factory = new self.shapeFactoryList[self.shapeName](); 19687 if (points.length < factory.getNPoints()) { 19688 clearTimeout(this.timer); 19689 this.timer = setTimeout(function() { 19690 points.push(lastPoint); 19691 }, factory.getTimeout()); 19692 } 19693 // remove previous draw 19694 if (shapeGroup) { 19695 shapeGroup.destroy(); 19696 } 19697 // create shape group 19698 shapeGroup = factory.create(points, self.style, app.getImage()); 19699 // do not listen during creation 19700 var shape = shapeGroup.getChildren(function(node) { 19701 return node.name() === 'shape'; 19702 })[0]; 19703 shape.listening(false); 19704 drawLayer.hitGraphEnabled(false); 19705 // draw shape command 19706 command = new dwv.tool.DrawGroupCommand(shapeGroup, self.shapeName, drawLayer, true); 19707 // draw 19708 command.execute(); 19709 } 19710 }; 19711 19712 /** 19713 * Handle mouse up event. 19714 * @param {Object} event The mouse up event. 19715 */ 19716 this.mouseup = function( /*event*/ ) { 19717 if (started && points.length > 1) { 19718 // reset shape group 19719 if (shapeGroup) { 19720 shapeGroup.destroy(); 19721 } 19722 // create final shape 19723 var factory = new self.shapeFactoryList[self.shapeName](); 19724 var group = factory.create(points, self.style, app.getImage()); 19725 group.id(dwv.math.guid()); 19726 // re-activate layer 19727 drawLayer.hitGraphEnabled(true); 19728 // draw shape command 19729 command = new dwv.tool.DrawGroupCommand(group, self.shapeName, drawLayer); 19730 command.onExecute = fireEvent; 19731 command.onUndo = fireEvent; 19732 // execute it 19733 command.execute(); 19734 // save it in undo stack 19735 app.addToUndoStack(command); 19736 19737 // set shape on 19738 var shape = group.getChildren(function(node) { 19739 return node.name() === 'shape'; 19740 })[0]; 19741 self.setShapeOn(shape); 19742 } 19743 // reset flag 19744 started = false; 19745 }; 19746 19747 /** 19748 * Handle mouse out event. 19749 * @param {Object} event The mouse out event. 19750 */ 19751 this.mouseout = function(event) { 19752 self.mouseup(event); 19753 }; 19754 19755 /** 19756 * Handle touch start event. 19757 * @param {Object} event The touch start event. 19758 */ 19759 this.touchstart = function(event) { 19760 self.mousedown(event); 19761 }; 19762 19763 /** 19764 * Handle touch move event. 19765 * @param {Object} event The touch move event. 19766 */ 19767 this.touchmove = function(event) { 19768 self.mousemove(event); 19769 }; 19770 19771 /** 19772 * Handle touch end event. 19773 * @param {Object} event The touch end event. 19774 */ 19775 this.touchend = function(event) { 19776 self.mouseup(event); 19777 }; 19778 19779 /** 19780 * Handle key down event. 19781 * @param {Object} event The key down event. 19782 */ 19783 this.keydown = function(event) { 19784 app.onKeydown(event); 19785 }; 19786 19787 /** 19788 * Setup the tool GUI. 19789 */ 19790 this.setup = function() { 19791 gui = new dwv.gui.Draw(app); 19792 gui.setup(this.shapeFactoryList); 19793 }; 19794 19795 /** 19796 * Enable the tool. 19797 * @param {Boolean} flag The flag to enable or not. 19798 */ 19799 this.display = function(flag) { 19800 if (gui) { 19801 gui.display(flag); 19802 } 19803 // reset shape display properties 19804 shapeEditor.disable(); 19805 shapeEditor.setShape(null); 19806 shapeEditor.setImage(null); 19807 document.body.style.cursor = 'default'; 19808 // make layer listen or not to events 19809 app.getDrawStage().listening(flag); 19810 // get the current draw layer 19811 drawLayer = app.getCurrentDrawLayer(); 19812 renderDrawLayer(flag); 19813 // listen to app change to update the draw layer 19814 if (flag) { 19815 app.addEventListener("slice-change", updateDrawLayer); 19816 app.addEventListener("frame-change", updateDrawLayer); 19817 } else { 19818 app.removeEventListener("slice-change", updateDrawLayer); 19819 app.removeEventListener("frame-change", updateDrawLayer); 19820 } 19821 }; 19822 19823 /** 19824 * Get the current app draw layer. 19825 */ 19826 function updateDrawLayer() { 19827 // deactivate the old draw layer 19828 renderDrawLayer(false); 19829 // get the current draw layer 19830 drawLayer = app.getCurrentDrawLayer(); 19831 // activate the new draw layer 19832 renderDrawLayer(true); 19833 } 19834 19835 /**
19836 * Render (or not) the draw layer. 19837 * @param {Boolean} visible Set the draw layer visible or not. 19838 */ 19839 function renderDrawLayer(visible) { 19840 drawLayer.listening(visible); 19841 drawLayer.hitGraphEnabled(visible); 19842 // get the list of shapes 19843 var groups = drawLayer.getChildren(); 19844 var shapes = []; 19845 var fshape = function(node) { 19846 return node.name() === 'shape'; 19847 }; 19848 for (var i = 0; i < groups.length; ++i) { 19849 // should only be one shape per group 19850 shapes.push(groups[i].getChildren(fshape)[0]); 19851 } 19852 // set shape display properties 19853 if (visible) { 19854 app.addToolCanvasListeners(app.getDrawStage().getContent()); 19855 shapes.forEach(function(shape) { self.setShapeOn(shape); }); 19856 } else { 19857 app.removeToolCanvasListeners(app.getDrawStage().getContent()); 19858 shapes.forEach(function(shape) { setShapeOff(shape); }); 19859 } 19860 // draw 19861 drawLayer.draw(); 19862 } 19863 19864 /** 19865 * Set shape off properties. 19866 * @param {Object} shape The shape to set off. 19867 */ 19868 function setShapeOff(shape) { 19869 // mouse styling 19870 shape.off('mouseover'); 19871 shape.off('mouseout'); 19872 // drag 19873 shape.draggable(false); 19874 shape.off('dragstart'); 19875 shape.off('dragmove'); 19876 shape.off('dragend'); 19877 shape.off('dblclick'); 19878 } 19879 19880 /** 19881 * Get the real position from an event. 19882 */ 19883 function getRealPosition(index) { 19884 var stage = app.getDrawStage(); 19885 return { 19886 'x': stage.offset().x + index.x / stage.scale().x, 19887 'y': stage.offset().y + index.y / stage.scale().y 19888 }; 19889 } 19890 19891 /** 19892 * Set shape on properties. 19893 * @param {Object} shape The shape to set on. 19894 */ 19895 this.setShapeOn = function(shape) { 19896 // mouse over styling 19897 shape.on('mouseover', function() { 19898 document.body.style.cursor = 'pointer'; 19899 }); 19900 // mouse out styling 19901 shape.on('mouseout', function() { 19902 document.body.style.cursor = 'default'; 19903 }); 19904 19905 // make it draggable 19906 shape.draggable(true); 19907 var dragStartPos = null; 19908 var dragLastPos = null; 19909 19910 // command name based on shape type 19911 var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape); 19912 19913 // store original colour 19914 var colour = null; 19915 19916 // save start position 19917 dragStartPos = { 'x': shape.x(), 'y': shape.y() }; 19918 19919 // drag start event handling 19920 shape.on('dragstart', function( /*event*/ ) { 19921 // colour 19922 colour = shape.stroke(); 19923 // display trash 19924 var stage = app.getDrawStage(); 19925 var scale = stage.scale(); 19926 var invscale = { 'x': 1 / scale.x, 'y': 1 / scale.y }; 19927 trash.x(stage.offset().x + (256 / scale.x)); 19928 trash.y(stage.offset().y + (20 / scale.y)); 19929 trash.scale(invscale); 19930 drawLayer.add(trash); 19931 // deactivate anchors to avoid events on null shape 19932 shapeEditor.setAnchorsActive(false); 19933 // draw 19934 drawLayer.draw(); 19935 }); 19936 // drag move event handling 19937 shape.on('dragmove', function(event) { 19938 var pos = { 'x': this.x(), 'y': this.y() }; 19939 var translation; 19940 if (dragLastPos) { 19941 translation = { 19942 'x': pos.x - dragLastPos.x, 19943 'y': pos.y - dragLastPos.y 19944 }; 19945 } else { 19946 translation = { 19947 'x': pos.x - dragStartPos.x, 19948 'y': pos.y - dragStartPos.y 19949 }; 19950 } 19951 dragLastPos = pos; 19952 // highlight trash when on it 19953 var offset = dwv.html.getEventOffset(event.evt)[0]; 19954 var eventPos = getRealPosition(offset); 19955 if (Math.abs(eventPos.x - trash.x()) < 10 && 19956 Math.abs(eventPos.y - trash.y()) < 10) { 19957 trash.getChildren().each(function(tshape) { tshape.stroke('orange'); }); 19958 shape.stroke('red'); 19959 } else { 19960 trash.getChildren().each(function(tshape) { tshape.stroke('red'); }); 19961 shape.stroke(colour); 19962 } 19963 // update group but not 'this' shape 19964 var group = this.getParent(); 19965 group.getChildren().each(function(ashape) { 19966 if (ashape === shape) { 19967 return; 19968 } 19969 ashape.x(ashape.x() + translation.x); 19970 ashape.y(ashape.y() + translation.y); 19971 }); 19972 // reset anchors 19973 shapeEditor.resetAnchors();
19974 // draw 19975 drawLayer.draw(); 19976 }); 19977 // drag end event handling 19978 shape.on('dragend', function(event) { 19979 var pos = { 'x': this.x(), 'y': this.y() }; 19980 dragLastPos = null; 19981 // remove trash 19982 trash.remove(); 19983 // delete case 19984 var offset = dwv.html.getEventOffset(event.evt)[0]; 19985 var eventPos = getRealPosition(offset); 19986 if (Math.abs(eventPos.x - trash.x()) < 10 && 19987 Math.abs(eventPos.y - trash.y()) < 10) { 19988 // compensate for the drag translation 19989 var delTranslation = { 19990 'x': eventPos.x - dragStartPos.x, 19991 'y': eventPos.y - dragStartPos.y 19992 }; 19993 var group = this.getParent(); 19994 group.getChildren().each(function(ashape) { 19995 ashape.x(ashape.x() - delTranslation.x); 19996 ashape.y(ashape.y() - delTranslation.y); 19997 }); 19998 // disable editor 19999 shapeEditor.disable(); 20000 shapeEditor.setShape(null); 20001 shapeEditor.setImage(null); 20002 // reset 20003 shape.stroke(colour); 20004 document.body.style.cursor = 'default'; 20005 // delete command 20006 var delcmd = new dwv.tool.DeleteGroupCommand(this.getParent(), 20007 shapeDisplayName, drawLayer); 20008 delcmd.onExecute = fireEvent; 20009 delcmd.onUndo = fireEvent; 20010 delcmd.execute(); 20011 app.addToUndoStack(delcmd); 20012 } else { 20013 // save drag move 20014 var translation = { 20015 'x': pos.x - dragStartPos.x, 20016 'y': pos.y - dragStartPos.y 20017 }; 20018 if (translation.x !== 0 || translation.y !== 0) { 20019 var mvcmd = new dwv.tool.MoveGroupCommand(this.getParent(), 20020 shapeDisplayName, translation, drawLayer); 20021 mvcmd.onExecute = fireEvent; 20022 mvcmd.onUndo = fireEvent; 20023 app.addToUndoStack(mvcmd); 20024 20025 // reset start position 20026 dragStartPos = { 'x': this.x(), 'y': this.y() }; 20027 // the move is handled by Konva, trigger an event manually 20028 fireEvent({ 'type': 'draw-move' }); 20029 } 20030 // reset anchors 20031 shapeEditor.setAnchorsActive(true); 20032 shapeEditor.resetAnchors(); 20033 } 20034 // draw 20035 drawLayer.draw(); 20036 }); 20037 // double click handling: update label 20038 shape.on('dblclick', function() { 20039 20040 // get the label object for this shape 20041 var group = this.getParent(); 20042 var labels = group.find('Label'); 20043 // should just be one 20044 if (labels.length !== 1) { 20045 throw new Error("Could not find the shape label."); 20046 } 20047 var ktext = labels[0].getText(); 20048 20049 // ask user for new label 20050 var labelText = dwv.gui.prompt("Shape label", ktext.textExpr); 20051 20052 // if press cancel do nothing 20053 if (labelText === null) { 20054 return; 20055 } else if (labelText === ktext.textExpr) { 20056 return; 20057 } 20058 // update text expression and set text 20059 ktext.textExpr = labelText; 20060 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 20061 20062 // trigger event 20063 fireEvent({ 'type': 'draw-change' }); 20064 20065 // draw 20066 drawLayer.draw(); 20067 }); 20068 }; 20069 20070 /** 20071 * Initialise the tool. 20072 */ 20073 this.init = function() { 20074 // set the default to the first in the list 20075 var shapeName = 0; 20076 for (var key in this.shapeFactoryList) { 20077 shapeName = key; 20078 break; 20079 } 20080 this.setShapeName(shapeName); 20081 // init gui 20082 if (gui) { 20083 // init with the app window scale 20084 this.style.setScale(app.getWindowScale()); 20085 // same for colour 20086 this.setLineColour(this.style.getLineColour()); 20087 // init html 20088 gui.initialise(); 20089 } 20090 return true; 20091 }; 20092 20093 /** 20094 * Add an event listener on the app. 20095 * @param {String} type The event type. 20096 * @param {Object} listener The method associated with the provided event type. 20097 */ 20098 this.addEventListener = function(type, listener) { 20099 if (typeof listeners[type] === "undefined") { 20100 listeners[type] = []; 20101 } 20102 listeners[type].push(listener); 20103 }; 20104 20105 /** 20106 * Remove an event listener from the app. 20107 * @param {String} type The event type. 20108 * @param {Object} listener The method associated with the provided event type. 20109 */ 20110 this.removeEventListener = function(type, listener) { 20111 if (typeof listeners[type] === "undefined") { 20112 return; 20113 } 20114 for (var i = 0; i < listeners[type].length; ++i) { 20115 if (listeners[type][i] === listener) { 20116 listeners[type].splice(i, 1); 20117 } 20118 } 20119 }; 20120 20121 /** 20122 * Set the line colour of the drawing. 20123 * @param {String} colour The colour to set. 20124 */ 20125 this.setLineColour = function(colour) { 20126 this.style.setLineColour(colour); 20127 }; 20128 20129 // Private Methods ----------------------------------------------------------- 20130 20131 /** 20132 * Fire an event: call all associated listeners. 20133 * @param {Object} event The event to fire. 20134 */ 20135 function fireEvent(event) { 20136 if (typeof listeners[event.type] === "undefined") { 20137 return; 20138 } 20139 for (var i = 0; i < listeners[event.type].length; ++i) { 20140 listeners[event.type][i](event); 20141 } 20142 } 20143 20144 }; // Draw class 20145 20146 /** 20147 * Help for this tool. 20148 * @return {Object} The help content. 20149 */ 20150 dwv.tool.Draw.prototype.getHelp = function() { 20151 return { 20152 "title": dwv.i18n("tool.Draw.name"), 20153 "brief": dwv.i18n("tool.Draw.brief"), 20154 "mouse": {
20155 "mouse_drag": dwv.i18n("tool.Draw.mouse_drag") 20156 }, 20157 "touch": { 20158 "touch_drag": dwv.i18n("tool.Draw.touch_drag") 20159 } 20160 }; 20161 }; 20162 20163 /** 20164 * Set the shape name of the drawing. 20165 * @param {String} name The name of the shape. 20166 */ 20167 dwv.tool.Draw.prototype.setShapeName = function(name) { 20168 // check if we have it 20169 if (!this.hasShape(name)) { 20170 throw new Error("Unknown shape: '" + name + "'"); 20171 } 20172 this.shapeName = name; 20173 }; 20174 20175 /** 20176 * Check if the shape is in the shape list. 20177 * @param {String} name The name of the shape. 20178 */ 20179 dwv.tool.Draw.prototype.hasShape = function(name) { 20180 return this.shapeFactoryList[name]; 20181 }; 20182 20183 // namespaces 20184 var dwv = dwv || {}; 20185 /** @namespace */ 20186 dwv.tool = dwv.tool || {}; 20187 // external 20188 var Konva = Konva || {}; 20189 20190 /** 20191 * Get the display name of the input shape. 20192 * @param {Object} shape The Konva shape. 20193 * @return {String} The display name. 20194 * @external Konva 20195 */ 20196 dwv.tool.GetShapeDisplayName = function(shape) { 20197 var displayName = "shape"; 20198 if (shape instanceof Konva.Line) { 20199 if (shape.points().length === 4) { 20200 displayName = "line"; 20201 } else if (shape.points().length === 6) { 20202 displayName = "protractor"; 20203 } else { 20204 displayName = "roi"; 20205 } 20206 } else if (shape instanceof Konva.Rect) { 20207 displayName = "rectangle"; 20208 } else if (shape instanceof Konva.Ellipse) { 20209 displayName = "ellipse"; 20210 } 20211 // return 20212 return displayName; 20213 }; 20214 20215 /** 20216 * Draw group command. 20217 * @param {Object} group The group draw. 20218 * @param {String} name The shape display name. 20219 * @param {Object} layer The layer where to draw the group. 20220 * @param {Object} silent Whether to send a creation event or not. 20221 * @constructor 20222 */ 20223 dwv.tool.DrawGroupCommand = function(group, name, layer, silent) { 20224 var isSilent = (typeof silent === "undefined") ? false : true; 20225 20226 /** 20227 * Get the command name. 20228 * @return {String} The command name. 20229 */ 20230 this.getName = function() { return "Draw-" + name; }; 20231 /** 20232 * Execute the command. 20233 */ 20234 this.execute = function() { 20235 // add the group to the layer 20236 layer.add(group); 20237 // draw 20238 layer.draw(); 20239 // callback 20240 if (!isSilent) { 20241 this.onExecute({ 'type': 'draw-create', 'id': group.id() }); 20242 } 20243 }; 20244 /** 20245 * Undo the command. 20246 */ 20247 this.undo = function() { 20248 // remove the group from the parent layer 20249 group.remove(); 20250 // draw 20251 layer.draw(); 20252 // callback 20253 this.onUndo({ 'type': 'draw-delete', 'id': group.id() }); 20254 }; 20255 }; // DrawGroupCommand class 20256 20257 /** 20258 * Handle an execute event. 20259 * @param {Object} event The execute event with type and id. 20260 */ 20261 dwv.tool.DrawGroupCommand.prototype.onExecute = function( /*event*/ ) { 20262 // default does nothing. 20263 }; 20264 /** 20265 * Handle an undo event. 20266 * @param {Object} event The undo event with type and id. 20267 */ 20268 dwv.tool.DrawGroupCommand.prototype.onUndo = function( /*event*/ ) { 20269 // default does nothing. 20270 }; 20271 20272 /** 20273 * Move group command. 20274 * @param {Object} group The group draw. 20275 * @param {String} name The shape display name. 20276 * @param {Object} translation A 2D translation to move the group by. 20277 * @param {Object} layer The layer where to move the group. 20278 * @constructor 20279 */ 20280 dwv.tool.MoveGroupCommand = function(group, name, translation, layer) { 20281 /** 20282 * Get the command name. 20283 * @return {String} The command name. 20284 */ 20285 this.getName = function() { return "Move-" + name; }; 20286 20287 /** 20288 * Execute the command. 20289 */ 20290 this.execute = function() { 20291 // translate all children of group 20292 group.getChildren().each(function(shape) { 20293 shape.x(shape.x() + translation.x); 20294 shape.y(shape.y() + translation.y); 20295 }); 20296 // draw 20297 layer.draw(); 20298 // callback 20299 this.onExecute({ 'type': 'draw-move', 'id': group.id() }); 20300 }; 20301 /** 20302 * Undo the command. 20303 */ 20304 this.undo = function() { 20305 // invert translate all children of group 20306 group.getChildren().each(function(shape) { 20307 shape.x(shape.x() - translation.x); 20308 shape.y(shape.y() - translation.y); 20309 }); 20310 // draw 20311 layer.draw(); 20312 // callback 20313 this.onUndo({ 'type': 'draw-move', 'id': group.id() }); 20314 }; 20315 }; // MoveGroupCommand class 20316 20317 /** 20318 * Handle an execute event. 20319 * @param {Object} event The execute event with type and id. 20320 */ 20321 dwv.tool.MoveGroupCommand.prototype.onExecute = function( /*event*/ ) { 20322 // default does nothing. 20323 }; 20324 /** 20325 * Handle an undo event. 20326 * @param {Object} event The undo event with type and id. 20327 */ 20328 dwv.tool.MoveGroupCommand.prototype.onUndo = function( /*event*/ ) { 20329 // default does nothing. 20330 }; 20331 20332 /** 20333 * Change group command. 20334 * @param {String} name The shape display name. 20335 * @param {Object} func The change function. 20336 * @param {Object} startAnchor The anchor that starts the change. 20337 * @param {Object} endAnchor The anchor that ends the change. 20338 * @param {Object} layer The layer where to change the group. 20339 * @param {Object} image The associated image. 20340 * @constructor 20341 */ 20342 dwv.tool.ChangeGroupCommand = function(name, func, startAnchor, endAnchor, layer, image) { 20343 /** 20344 * Get the command name. 20345 * @return {String} The command name. 20346 */ 20347 this.getName = function() { return "Change-" + name; }; 20348 20349 /** 20350 * Execute the command. 20351 */ 20352 this.execute = function() { 20353 // change shape
20354 func(endAnchor, image); 20355 // draw 20356 layer.draw(); 20357 // callback 20358 this.onExecute({ 'type': 'draw-change' }); 20359 }; 20360 /** 20361 * Undo the command. 20362 */ 20363 this.undo = function() { 20364 // invert change shape 20365 func(startAnchor, image); 20366 // draw 20367 layer.draw(); 20368 // callback 20369 this.onUndo({ 'type': 'draw-change' }); 20370 }; 20371 }; // ChangeGroupCommand class 20372 20373 /** 20374 * Handle an execute event. 20375 * @param {Object} event The execute event with type and id. 20376 */ 20377 dwv.tool.ChangeGroupCommand.prototype.onExecute = function( /*event*/ ) { 20378 // default does nothing. 20379 }; 20380 /** 20381 * Handle an undo event. 20382 * @param {Object} event The undo event with type and id. 20383 */ 20384 dwv.tool.ChangeGroupCommand.prototype.onUndo = function( /*event*/ ) { 20385 // default does nothing. 20386 }; 20387 20388 /** 20389 * Delete group command. 20390 * @param {Object} group The group draw. 20391 * @param {String} name The shape display name. 20392 * @param {Object} layer The layer where to delete the group. 20393 * @constructor 20394 */ 20395 dwv.tool.DeleteGroupCommand = function(group, name, layer) { 20396 /** 20397 * Get the command name. 20398 * @return {String} The command name. 20399 */ 20400 this.getName = function() { return "Delete-" + name; }; 20401 /** 20402 * Execute the command. 20403 */ 20404 this.execute = function() { 20405 // remove the group from the parent layer 20406 group.remove(); 20407 // draw 20408 layer.draw(); 20409 // callback 20410 this.onExecute({ 'type': 'draw-delete', 'id': group.id() }); 20411 }; 20412 /** 20413 * Undo the command. 20414 */ 20415 this.undo = function() { 20416 // add the group to the layer 20417 layer.add(group); 20418 // draw 20419 layer.draw(); 20420 // callback 20421 this.onUndo({ 'type': 'draw-create', 'id': group.id() }); 20422 }; 20423 }; // DeleteGroupCommand class 20424 20425 /** 20426 * Handle an execute event. 20427 * @param {Object} event The execute event with type and id. 20428 */ 20429 dwv.tool.DeleteGroupCommand.prototype.onExecute = function( /*event*/ ) { 20430 // default does nothing. 20431 }; 20432 /** 20433 * Handle an undo event. 20434 * @param {Object} event The undo event with type and id. 20435 */ 20436 dwv.tool.DeleteGroupCommand.prototype.onUndo = function( /*event*/ ) { 20437 // default does nothing. 20438 }; 20439 20440 // namespaces 20441 var dwv = dwv || {}; 20442 dwv.tool = dwv.tool || {}; 20443 // external 20444 var Konva = Konva || {}; 20445 20446 /** 20447 * Shape editor. 20448 * @constructor 20449 * @external Konva 20450 */ 20451 dwv.tool.ShapeEditor = function(app) { 20452 /** 20453 * Edited shape. 20454 * @private 20455 * @type Object 20456 */ 20457 var shape = null; 20458 /** 20459 * Edited image. Used for quantification update. 20460 * @private 20461 * @type Object 20462 */ 20463 var image = null; 20464 /** 20465 * Active flag. 20466 * @private 20467 * @type Boolean 20468 */ 20469 var isActive = false; 20470 /** 20471 * Update function used by anchors to update the shape. 20472 * @private 20473 * @type Function 20474 */ 20475 var updateFunction = null; 20476 /** 20477 * Draw event callback. 20478 * @private 20479 * @type Function 20480 */ 20481 var drawEventCallback = null; 20482 20483 /** 20484 * Set the shape to edit. 20485 * @param {Object} inshape The shape to edit. 20486 */ 20487 this.setShape = function(inshape) { 20488 shape = inshape; 20489 // reset anchors 20490 if (shape) { 20491 removeAnchors(); 20492 addAnchors(); 20493 } 20494 }; 20495 20496 /** 20497 * Set the associated image. 20498 * @param {Object} img The associated image. 20499 */ 20500 this.setImage = function(img) { 20501 image = img; 20502 }; 20503 20504 /** 20505 * Get the edited shape. 20506 * @return {Object} The edited shape. 20507 */
20508 this.getShape = function() { 20509 return shape; 20510 }; 20511 20512 /** 20513 * Get the active flag. 20514 * @return {Boolean} The active flag. 20515 */ 20516 this.isActive = function() { 20517 return isActive; 20518 }; 20519 20520 /** 20521 * Set the draw event callback. 20522 * @param {Object} callback The callback. 20523 */ 20524 this.setDrawEventCallback = function(callback) { 20525 drawEventCallback = callback; 20526 }; 20527 20528 /** 20529 * Enable the editor. Redraws the layer. 20530 */ 20531 this.enable = function() { 20532 isActive = true; 20533 if (shape) { 20534 setAnchorsVisible(true); 20535 if (shape.getLayer()) { 20536 shape.getLayer().draw(); 20537 } 20538 } 20539 }; 20540 20541 /** 20542 * Disable the editor. Redraws the layer. 20543 */ 20544 this.disable = function() { 20545 isActive = false; 20546 if (shape) { 20547 setAnchorsVisible(false); 20548 if (shape.getLayer()) { 20549 shape.getLayer().draw(); 20550 } 20551 } 20552 }; 20553 20554 /** 20555 * Reset the anchors. 20556 */ 20557 this.resetAnchors = function() { 20558 // remove previous controls 20559 removeAnchors(); 20560 // add anchors 20561 addAnchors(); 20562 // set them visible 20563 setAnchorsVisible(true); 20564 }; 20565 20566 /** 20567 * Apply a function on all anchors. 20568 * @param {Object} func A f(shape) function. 20569 */ 20570 function applyFuncToAnchors(func) { 20571 if (shape && shape.getParent()) { 20572 var anchors = shape.getParent().find('.anchor'); 20573 anchors.each(func); 20574 } 20575 } 20576 20577 /** 20578 * Set anchors visibility. 20579 * @param {Boolean} flag The visible flag. 20580 */ 20581 function setAnchorsVisible(flag) { 20582 applyFuncToAnchors(function(anchor) { 20583 anchor.visible(flag); 20584 }); 20585 } 20586 20587 /** 20588 * Set anchors active. 20589 * @param {Boolean} flag The active (on/off) flag. 20590 */ 20591 this.setAnchorsActive = function(flag) { 20592 var func = null; 20593 if (flag) { 20594 func = function(anchor) { 20595 setAnchorOn(anchor); 20596 }; 20597 } else { 20598 func = function(anchor) { 20599 setAnchorOff(anchor); 20600 }; 20601 } 20602 applyFuncToAnchors(func); 20603 }; 20604 20605 /** 20606 * Remove anchors. 20607 */ 20608 function removeAnchors() { 20609 applyFuncToAnchors(function(anchor) { 20610 anchor.remove(); 20611 }); 20612 } 20613 20614 /** 20615 * Add the shape anchors. 20616 */ 20617 function addAnchors() { 20618 // exit if no shape or no layer 20619 if (!shape || !shape.getLayer()) { 20620 return; 20621 } 20622 // get shape group 20623 var group = shape.getParent(); 20624 // add shape specific anchors to the shape group 20625 if (shape instanceof Konva.Line) { 20626 var points = shape.points(); 20627 if (points.length === 4 || points.length === 6) { 20628 // add shape offset 20629 var p0x = points[0] + shape.x(); 20630 var p0y = points[1] + shape.y(); 20631 var p1x = points[2] + shape.x(); 20632 var p1y = points[3] + shape.y(); 20633 addAnchor(group, p0x, p0y, 'begin'); 20634 if (points.length === 4) { 20635 var shapekids = group.getChildren(function(node) { 20636 return node.name().startsWith("shape-"); 20637 }); 20638 if (shapekids.length === 2) { 20639 updateFunction = dwv.tool.UpdateRuler; 20640 } else { 20641 updateFunction = dwv.tool.UpdateArrow; 20642 } 20643 addAnchor(group, p1x, p1y, 'end'); 20644 } else { 20645 updateFunction = dwv.tool.UpdateProtractor; 20646 addAnchor(group, p1x, p1y, 'mid'); 20647 var p2x = points[4] + shape.x(); 20648 var p2y = points[5] + shape.y(); 20649 addAnchor(group, p2x, p2y, 'end'); 20650 } 20651 } else { 20652 updateFunction = dwv.tool.UpdateRoi; 20653 var px = 0; 20654 var py = 0; 20655 for (var i = 0; i < points.length; i = i + 2) { 20656 px = points[i] + shape.x(); 20657 py = points[i + 1] + shape.y(); 20658 addAnchor(group, px, py, i); 20659 } 20660 } 20661 } else if (shape instanceof Konva.Rect) { 20662 updateFunction = dwv.tool.UpdateRect; 20663 var rectX = shape.x(); 20664 var rectY = shape.y(); 20665 var rectWidth = shape.width(); 20666 var rectHeight = shape.height(); 20667 addAnchor(group, rectX, rectY, 'topLeft'); 20668 addAnchor(group, rectX + rectWidth, rectY, 'topRight'); 20669 addAnchor(group, rectX + rectWidth, rectY + rectHeight, 'bottomRight'); 20670 addAnchor(group, rectX, rectY + rectHeight, 'bottomLeft'); 20671 } else if (shape instanceof Konva.Ellipse) { 20672 updateFunction = dwv.tool.UpdateEllipse; 20673 var ellipseX = shape.x(); 20674 var ellipseY = shape.y();
20675 var radius = shape.radius(); 20676 addAnchor(group, ellipseX - radius.x, ellipseY - radius.y, 'topLeft'); 20677 addAnchor(group, ellipseX + radius.x, ellipseY - radius.y, 'topRight'); 20678 addAnchor(group, ellipseX + radius.x, ellipseY + radius.y, 'bottomRight'); 20679 addAnchor(group, ellipseX - radius.x, ellipseY + radius.y, 'bottomLeft'); 20680 } 20681 // add group to layer 20682 shape.getLayer().add(group); 20683 } 20684 20685 /** 20686 * Create shape editor controls, i.e. the anchors. 20687 * @param {Object} group The group associated with this anchor. 20688 * @param {Number} x The X position of the anchor. 20689 * @param {Number} y The Y position of the anchor. 20690 * @param {Number} id The id of the anchor. 20691 */ 20692 function addAnchor(group, x, y, id) { 20693 // anchor shape 20694 var anchor = new Konva.Circle({ 20695 x: x, 20696 y: y, 20697 stroke: '#999', 20698 fill: 'rgba(100,100,100,0.7', 20699 strokeWidth: app.getStyle().getScaledStrokeWidth() / app.getScale(), 20700 radius: app.getStyle().scale(6) / app.getScale(), 20701 name: 'anchor', 20702 id: id, 20703 dragOnTop: false, 20704 draggable: true, 20705 visible: false 20706 }); 20707 // set anchor on 20708 setAnchorOn(anchor); 20709 // add the anchor to the group 20710 group.add(anchor); 20711 } 20712 20713 /** 20714 * Get a simple clone of the input anchor. 20715 * @param {Object} anchor The anchor to clone. 20716 */ 20717 function getClone(anchor) { 20718 // create closure to properties 20719 var parent = anchor.getParent(); 20720 var id = anchor.id(); 20721 var x = anchor.x(); 20722 var y = anchor.y(); 20723 // create clone object 20724 var clone = {}; 20725 clone.getParent = function() { 20726 return parent; 20727 }; 20728 clone.id = function() { 20729 return id; 20730 }; 20731 clone.x = function() { 20732 return x; 20733 }; 20734 clone.y = function() { 20735 return y; 20736 }; 20737 return clone; 20738 } 20739 20740 /** 20741 * Set the anchor on listeners. 20742 * @param {Object} anchor The anchor to set on. 20743 */ 20744 function setAnchorOn(anchor) { 20745 var startAnchor = null; 20746 20747 // command name based on shape type 20748 var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape); 20749 20750 // drag start listener 20751 anchor.on('dragstart', function() { 20752 startAnchor = getClone(this); 20753 }); 20754 // drag move listener 20755 anchor.on('dragmove', function() { 20756 if (updateFunction) { 20757 updateFunction(this, image); 20758 } 20759 if (this.getLayer()) { 20760 this.getLayer().draw(); 20761 } else { 20762 console.warn("No layer to draw the anchor!"); 20763 } 20764 }); 20765 // drag end listener 20766 anchor.on('dragend', function() { 20767 var endAnchor = getClone(this); 20768 // store the change command 20769 var chgcmd = new dwv.tool.ChangeGroupCommand( 20770 shapeDisplayName, updateFunction, startAnchor, endAnchor, this.getLayer(), image); 20771 chgcmd.onExecute = drawEventCallback; 20772 chgcmd.onUndo = drawEventCallback; 20773 chgcmd.execute(); 20774 app.addToUndoStack(chgcmd); 20775 // reset start anchor 20776 startAnchor = endAnchor; 20777 }); 20778 // mouse down listener 20779 anchor.on('mousedown touchstart', function() { 20780 this.moveToTop(); 20781 }); 20782 // mouse over styling 20783 anchor.on('mouseover', function() { 20784 document.body.style.cursor = 'pointer'; 20785 this.stroke('#ddd'); 20786 if (this.getLayer()) { 20787 this.getLayer().draw(); 20788 } else {
20789 console.warn("No layer to draw the anchor!"); 20790 } 20791 }); 20792 // mouse out styling 20793 anchor.on('mouseout', function() { 20794 document.body.style.cursor = 'default'; 20795 this.stroke('#999'); 20796 if (this.getLayer()) { 20797 this.getLayer().draw(); 20798 } else { 20799 console.warn("No layer to draw the anchor!"); 20800 } 20801 }); 20802 } 20803 20804 /** 20805 * Set the anchor off listeners. 20806 * @param {Object} anchor The anchor to set off. 20807 */ 20808 function setAnchorOff(anchor) { 20809 anchor.off('dragstart'); 20810 anchor.off('dragmove'); 20811 anchor.off('dragend'); 20812 anchor.off('mousedown touchstart'); 20813 anchor.off('mouseover'); 20814 anchor.off('mouseout'); 20815 } 20816 }; 20817 20818 // namespaces 20819 var dwv = dwv || {}; 20820 dwv.tool = dwv.tool || {}; 20821 // external 20822 var Konva = Konva || {}; 20823 20824 /** 20825 * Ellipse factory. 20826 * @constructor 20827 * @external Konva 20828 */ 20829 dwv.tool.EllipseFactory = function() { 20830 /** 20831 * Get the number of points needed to build the shape. 20832 * @return {Number} The number of points. 20833 */ 20834 this.getNPoints = function() { return 2; }; 20835 /** 20836 * Get the timeout between point storage. 20837 * @return {Number} The timeout in milliseconds. 20838 */ 20839 this.getTimeout = function() { return 0; }; 20840 }; 20841 20842 /** 20843 * Create an ellipse shape to be displayed. 20844 * @param {Array} points The points from which to extract the ellipse. 20845 * @param {Object} style The drawing style. 20846 * @param {Object} image The associated image. 20847 */ 20848 dwv.tool.EllipseFactory.prototype.create = function(points, style, image) { 20849 // calculate radius 20850 var a = Math.abs(points[0].getX() - points[1].getX()); 20851 var b = Math.abs(points[0].getY() - points[1].getY()); 20852 // physical shape 20853 var ellipse = new dwv.math.Ellipse(points[0], a, b); 20854 // draw shape 20855 var kshape = new Konva.Ellipse({ 20856 x: ellipse.getCenter().getX(), 20857 y: ellipse.getCenter().getY(), 20858 radius: { x: ellipse.getA(), y: ellipse.getB() }, 20859 stroke: style.getLineColour(), 20860 strokeWidth: style.getScaledStrokeWidth(), 20861 name: "shape" 20862 }); 20863 // quantification 20864 var quant = image.quantifyEllipse(ellipse); 20865 var ktext = new Konva.Text({ 20866 fontSize: style.getScaledFontSize(), 20867 fontFamily: style.getFontFamily(), 20868 fill: style.getLineColour(), 20869 name: "text" 20870 }); 20871 ktext.textExpr = "{surface}"; 20872 ktext.longText = ""; 20873 ktext.quant = quant; 20874 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 20875 // label 20876 var klabel = new Konva.Label({ 20877 x: ellipse.getCenter().getX(), 20878 y: ellipse.getCenter().getY(), 20879 name: "label" 20880 }); 20881 klabel.add(ktext); 20882 klabel.add(new Konva.Tag()); 20883 20884 // return group 20885 var group = new Konva.Group(); 20886 group.name("ellipse-group"); 20887 group.add(kshape); 20888 group.add(klabel); 20889 group.visible(true); // dont inherit 20890 return group; 20891 }; 20892 20893 /** 20894 * Update an ellipse shape. 20895 * @param {Object} anchor The active anchor. 20896 * @param {Object} image The associated image. 20897 */ 20898 dwv.tool.UpdateEllipse = function(anchor, image) { 20899 // parent group 20900 var group = anchor.getParent(); 20901 // associated shape 20902 var kellipse = group.getChildren(function(node) { 20903 return node.name() === 'shape'; 20904 })[0]; 20905 // associated label 20906 var klabel = group.getChildren(function(node) { 20907 return node.name() === 'label'; 20908 })[0]; 20909 // find special points 20910 var topLeft = group.getChildren(function(node) { 20911 return node.id() === 'topLeft'; 20912 })[0]; 20913 var topRight = group.getChildren(function(node) { 20914 return node.id() === 'topRight'; 20915 })[0]; 20916 var bottomRight = group.getChildren(function(node) { 20917 return node.id() === 'bottomRight'; 20918 })[0]; 20919 var bottomLeft = group.getChildren(function(node) { 20920 return node.id() === 'bottomLeft'; 20921 })[0]; 20922 // update 'self' (undo case) and special points 20923 switch (anchor.id()) { 20924 case 'topLeft': 20925 topLeft.x(anchor.x()); 20926 topLeft.y(anchor.y()); 20927 topRight.y(anchor.y()); 20928 bottomLeft.x(anchor.x()); 20929 break; 20930 case 'topRight': 20931 topRight.x(anchor.x()); 20932 topRight.y(anchor.y()); 20933 topLeft.y(anchor.y()); 20934 bottomRight.x(anchor.x()); 20935 break; 20936 case 'bottomRight': 20937 bottomRight.x(anchor.x()); 20938 bottomRight.y(anchor.y()); 20939 bottomLeft.y(anchor.y()); 20940 topRight.x(anchor.x()); 20941 break; 20942 case 'bottomLeft': 20943 bottomLeft.x(anchor.x()); 20944 bottomLeft.y(anchor.y()); 20945 bottomRight.y(anchor.y()); 20946 topLeft.x(anchor.x()); 20947 break; 20948 default: 20949 console.error('Unhandled anchor id: ' + anchor.id()); 20950 break; 20951 } 20952 // update shape 20953 var radiusX = (topRight.x() - topLeft.x()) / 2; 20954 var radiusY = (bottomRight.y() - topRight.y()) / 2; 20955 var center = { 'x': topLeft.x() + radiusX, 'y': topRight.y() + radiusY }; 20956 kellipse.position(center); 20957 var radiusAbs = { 'x': Math.abs(radiusX), 'y': Math.abs(radiusY) }; 20958 if (radiusAbs) { 20959 kellipse.radius(radiusAbs); 20960 } 20961 // new ellipse 20962 var ellipse = new dwv.math.Ellipse(center, radiusAbs.x, radiusAbs.y); 20963 // update text 20964 var quant = image.quantifyEllipse(ellipse); 20965 var ktext = klabel.getText(); 20966 ktext.quant = quant; 20967 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 20968 // update position 20969 var textPos = { 'x': center.x, 'y': center.y }; 20970 klabel.position(textPos); 20971 }; 20972 20973 // namespaces 20974 var dwv = dwv || {}; 20975 dwv.tool = dwv.tool || {}; 20976 /** @namespace */ 20977 dwv.tool.filter = dwv.tool.filter || {}; 20978 20979 /** 20980 * Filter tool. 20981 * @constructor 20982 * @param {Array} filterList The list of filter objects. 20983 * @param {Object} app The associated app. 20984 */ 20985 dwv.tool.Filter = function(filterList, app) { 20986 /** 20987 * Filter GUI. 20988 * @type Object 20989 */ 20990 var gui = null; 20991 /** 20992 * Filter list 20993 * @type Object 20994 */ 20995 this.filterList = filterList; 20996 /** 20997 * Selected filter. 20998 * @type Object 20999 */ 21000 this.selectedFilter = 0; 21001 /** 21002 * Default filter name.
21003 * @type String 21004 */ 21005 this.defaultFilterName = 0; 21006 /** 21007 * Display Flag. 21008 * @type Boolean 21009 */ 21010 this.displayed = false; 21011 /** 21012 * Listener handler. 21013 * @type Object 21014 */ 21015 var listenerHandler = new dwv.utils.ListenerHandler(); 21016 21017 /** 21018 * Setup the filter GUI. Called at app startup. 21019 */ 21020 this.setup = function() { 21021 if (Object.keys(this.filterList).length !== 0) { 21022 gui = new dwv.gui.Filter(app); 21023 gui.setup(this.filterList); 21024 for (var key in this.filterList) { 21025 this.filterList[key].setup(); 21026 this.filterList[key].addEventListener("filter-run", fireEvent); 21027 this.filterList[key].addEventListener("filter-undo", fireEvent); 21028 } 21029 } 21030 }; 21031 21032 /** 21033 * Display the tool. 21034 * @param {Boolean} bool Flag to enable or not. 21035 */ 21036 this.display = function(bool) { 21037 if (gui) { 21038 gui.display(bool); 21039 } 21040 this.displayed = bool; 21041 // display the selected filter 21042 this.selectedFilter.display(bool); 21043 }; 21044 21045 /** 21046 * Initialise the filter. Called once the image is loaded. 21047 */ 21048 this.init = function() { 21049 // set the default to the first in the list 21050 for (var key in this.filterList) { 21051 this.defaultFilterName = key; 21052 break; 21053 } 21054 this.setSelectedFilter(this.defaultFilterName); 21055 // init all filters 21056 for (key in this.filterList) { 21057 this.filterList[key].init(); 21058 } 21059 // init html 21060 if (gui) { 21061 gui.initialise(); 21062 } 21063 return true; 21064 }; 21065 21066 /** 21067 * Handle keydown event. 21068 * @param {Object} event The keydown event. 21069 */ 21070 this.keydown = function(event) { 21071 app.onKeydown(event); 21072 }; 21073 21074 /** 21075 * Add an event listener to this class. 21076 * @param {String} type The event type. 21077 * @param {Object} callback The method associated with the provided event type, 21078 * will be called with the fired event. 21079 */ 21080 this.addEventListener = function(type, callback) { 21081 listenerHandler.add(type, callback); 21082 }; 21083 /** 21084 * Remove an event listener from this class. 21085 * @param {String} type The event type. 21086 * @param {Object} callback The method associated with the provided event type. 21087 */ 21088 this.removeEventListener = function(type, callback) { 21089 listenerHandler.remove(type, callback); 21090 }; 21091 /** 21092 * Fire an event: call all associated listeners with the input event object. 21093 * @param {Object} event The event to fire. 21094 * @private 21095 */ 21096 function fireEvent(event) { 21097 listenerHandler.fireEvent(event); 21098 } 21099 21100 }; // class dwv.tool.Filter 21101 21102 /** 21103 * Help for this tool. 21104 * @return {Object} The help content. 21105 */ 21106 dwv.tool.Filter.prototype.getHelp = function() { 21107 return { 21108 "title": dwv.i18n("tool.Filter.name"), 21109 "brief": dwv.i18n("tool.Filter.brief") 21110 }; 21111 }; 21112 21113 /** 21114 * Get the selected filter. 21115 * @return {Object} The selected filter. 21116 */ 21117 dwv.tool.Filter.prototype.getSelectedFilter = function() { 21118 return this.selectedFilter; 21119 }; 21120 21121 /** 21122 * Set the selected filter. 21123 * @return {String} The name of the filter to select. 21124 */ 21125 dwv.tool.Filter.prototype.setSelectedFilter = function(name) { 21126 // check if we have it 21127 if (!this.hasFilter(name)) { 21128 throw new Error("Unknown filter: '" + name + "'"); 21129 } 21130 // hide last selected 21131 if (this.displayed) { 21132 this.selectedFilter.display(false); 21133 } 21134 // enable new one 21135 this.selectedFilter = this.filterList[name]; 21136 // display the selected filter 21137 if (this.displayed) { 21138 this.selectedFilter.display(true); 21139 } 21140 }; 21141 21142 /** 21143 * Get the list of filters. 21144 * @return {Array} The list of filter objects. 21145 */ 21146 dwv.tool.Filter.prototype.getFilterList = function() { 21147 return this.filterList; 21148 }; 21149 21150 /** 21151 * Check if a filter is in the filter list. 21152 * @param {String} name The name to check. 21153 * @return {String} The filter list element for the given name. 21154 */ 21155 dwv.tool.Filter.prototype.hasFilter = function(name) { 21156 return this.filterList[name]; 21157 }; 21158 21159 /** 21160 * Threshold filter tool. 21161 * @constructor 21162 * @param {Object} app The associated application. 21163 */ 21164 dwv.tool.filter.Threshold = function(app) { 21165 /** 21166 * Associated filter. 21167 * @type Object 21168 */ 21169 var filter = new dwv.image.filter.Threshold(); 21170 /** 21171 * Filter GUI. 21172 * @type Object 21173 */ 21174 var gui = new dwv.gui.Threshold(app); 21175 /** 21176 * Flag to know wether to reset the image or not. 21177 * @type Boolean 21178 */ 21179 var resetImage = true; 21180 /** 21181 * Listener handler. 21182 * @type Object 21183 */ 21184 var listenerHandler = new dwv.utils.ListenerHandler(); 21185 21186 /** 21187 * Setup the filter GUI. Called at app startup. 21188 */ 21189 this.setup = function() { 21190 gui.setup(); 21191 }; 21192 21193 /**
21194 * Display the filter. 21195 * @param {Boolean} bool Flag to display or not. 21196 */ 21197 this.display = function(bool) { 21198 gui.display(bool); 21199 // reset the image when the tool is displayed 21200 if (bool) { 21201 resetImage = true; 21202 } 21203 }; 21204 21205 /** 21206 * Initialise the filter. Called once the image is loaded. 21207 */ 21208 this.init = function() { 21209 gui.initialise(); 21210 }; 21211 21212 /** 21213 * Run the filter. 21214 * @param {Mixed} args The filter arguments. 21215 */ 21216 this.run = function(args) { 21217 filter.setMin(args.min); 21218 filter.setMax(args.max); 21219 // reset the image if asked 21220 if (resetImage) { 21221 filter.setOriginalImage(app.getImage()); 21222 resetImage = false; 21223 } 21224 var command = new dwv.tool.RunFilterCommand(filter, app); 21225 command.onExecute = fireEvent; 21226 command.onUndo = fireEvent; 21227 command.execute(); 21228 // save command in undo stack 21229 app.addToUndoStack(command); 21230 }; 21231 21232 /** 21233 * Add an event listener to this class. 21234 * @param {String} type The event type. 21235 * @param {Object} callback The method associated with the provided event type, 21236 * will be called with the fired event. 21237 */ 21238 this.addEventListener = function(type, callback) { 21239 listenerHandler.add(type, callback); 21240 }; 21241 /** 21242 * Remove an event listener from this class. 21243 * @param {String} type The event type. 21244 * @param {Object} callback The method associated with the provided event type. 21245 */ 21246 this.removeEventListener = function(type, callback) { 21247 listenerHandler.remove(type, callback); 21248 }; 21249 /** 21250 * Fire an event: call all associated listeners with the input event object. 21251 * @param {Object} event The event to fire. 21252 * @private 21253 */ 21254 function fireEvent(event) { 21255 listenerHandler.fireEvent(event); 21256 } 21257 21258 }; // class dwv.tool.filter.Threshold 21259 21260 21261 /** 21262 * Sharpen filter tool. 21263 * @constructor 21264 * @param {Object} app The associated application. 21265 */ 21266 dwv.tool.filter.Sharpen = function(app) { 21267 /** 21268 * Filter GUI. 21269 * @type Object 21270 */ 21271 var gui = new dwv.gui.Sharpen(app); 21272 /** 21273 * Listener handler. 21274 * @type Object 21275 */ 21276 var listenerHandler = new dwv.utils.ListenerHandler(); 21277 21278 /** 21279 * Setup the filter GUI. Called at app startup. 21280 */ 21281 this.setup = function() { 21282 gui.setup(); 21283 }; 21284 21285 /** 21286 * Display the filter. 21287 * @param {Boolean} bool Flag to enable or not. 21288 */ 21289 this.display = function(bool) { 21290 gui.display(bool); 21291 }; 21292 21293 /** 21294 * Initialise the filter. Called once the image is loaded. 21295 */ 21296 this.init = function() { 21297 // nothing to do... 21298 }; 21299 21300 /** 21301 * Run the filter. 21302 * @param {Mixed} args The filter arguments. 21303 */ 21304 this.run = function( /*args*/ ) { 21305 var filter = new dwv.image.filter.Sharpen(); 21306 filter.setOriginalImage(app.getImage()); 21307 var command = new dwv.tool.RunFilterCommand(filter, app); 21308 command.onExecute = fireEvent; 21309 command.onUndo = fireEvent; 21310 command.execute(); 21311 // save command in undo stack 21312 app.addToUndoStack(command); 21313 }; 21314 21315 /** 21316 * Add an event listener to this class. 21317 * @param {String} type The event type. 21318 * @param {Object} callback The method associated with the provided event type, 21319 * will be called with the fired event. 21320 */ 21321 this.addEventListener = function(type, callback) { 21322 listenerHandler.add(type, callback); 21323 }; 21324 /** 21325 * Remove an event listener from this class. 21326 * @param {String} type The event type. 21327 * @param {Object} callback The method associated with the provided event type. 21328 */ 21329 this.removeEventListener = function(type, callback) { 21330 listenerHandler.remove(type, callback); 21331 }; 21332 /** 21333 * Fire an event: call all associated listeners with the input event object. 21334 * @param {Object} event The event to fire. 21335 * @private 21336 */ 21337 function fireEvent(event) { 21338 listenerHandler.fireEvent(event); 21339 } 21340 21341 }; // dwv.tool.filter.Sharpen 21342 21343 /** 21344 * Sobel filter tool. 21345 * @constructor 21346 * @param {Object} app The associated application. 21347 */ 21348 dwv.tool.filter.Sobel = function(app) { 21349 /** 21350 * Filter GUI. 21351 * @type Object 21352 */ 21353 var gui = new dwv.gui.Sobel(app); 21354 /** 21355 * Listener handler. 21356 * @type Object 21357 */ 21358 var listenerHandler = new dwv.utils.ListenerHandler(); 21359 21360 /** 21361 * Setup the filter GUI. Called at app startup. 21362 */ 21363 this.setup = function() { 21364 gui.setup(); 21365 }; 21366 21367 /** 21368 * Enable the filter. 21369 * @param {Boolean} bool Flag to enable or not. 21370 */ 21371 this.display = function(bool) { 21372 gui.display(bool); 21373 }; 21374 21375 /** 21376 * Initialise the filter. Called once the image is loaded. 21377 */ 21378 this.init = function() { 21379 // nothing to do... 21380 }; 21381 21382 /** 21383 * Run the filter. 21384 * @param {Mixed} args The filter arguments. 21385 */ 21386 dwv.tool.filter.Sobel.prototype.run = function( /*args*/ ) { 21387 var filter = new dwv.image.filter.Sobel(); 21388 filter.setOriginalImage(app.getImage()); 21389 var command = new dwv.tool.RunFilterCommand(filter, app); 21390 command.onExecute = fireEvent; 21391 command.onUndo = fireEvent; 21392 command.execute(); 21393 // save command in undo stack 21394 app.addToUndoStack(command); 21395 }; 21396 21397 /** 21398 * Add an event listener to this class. 21399 * @param {String} type The event type. 21400 * @param {Object} callback The method associated with the provided event type, 21401 * will be called with the fired event. 21402 */ 21403 this.addEventListener = function(type, callback) { 21404 listenerHandler.add(type, callback); 21405 }; 21406 /** 21407 * Remove an event listener from this class. 21408 * @param {String} type The event type. 21409 * @param {Object} callback The method associated with the provided event type. 21410 */ 21411 this.removeEventListener = function(type, callback) { 21412 listenerHandler.remove(type, callback); 21413 }; 21414 /** 21415 * Fire an event: call all associated listeners with the input event object. 21416 * @param {Object} event The event to fire. 21417 * @private 21418 */ 21419 function fireEvent(event) { 21420 listenerHandler.fireEvent(event); 21421 } 21422 21423 }; // class dwv.tool.filter.Sobel 21424 21425 /** 21426 * Run filter command. 21427 * @constructor 21428 * @param {Object} filter The filter to run. 21429 * @param {Object} app The associated application. 21430 */ 21431 dwv.tool.RunFilterCommand = function(filter, app) { 21432 21433 /** 21434 * Get the command name. 21435 * @return {String} The command name. 21436 */ 21437 this.getName = function() { return "Filter-" + filter.getName(); }; 21438 21439 /** 21440 * Execute the command. 21441 */ 21442 this.execute = function() { 21443 // run filter and set app image
21444 app.setImage(filter.update()); 21445 // update display 21446 app.render(); 21447 // callback 21448 this.onExecute({ 'type': 'filter-run', 'id': this.getName() }); 21449 }; 21450 21451 /** 21452 * Undo the command. 21453 */ 21454 this.undo = function() { 21455 // reset the image 21456 app.setImage(filter.getOriginalImage()); 21457 // update display 21458 app.render(); 21459 // callback 21460 this.onUndo({ 'type': 'filter-undo', 'id': this.getName() }); 21461 }; 21462 21463 }; // RunFilterCommand class 21464 21465 /** 21466 * Handle an execute event. 21467 * @param {Object} event The execute event with type and id. 21468 */ 21469 dwv.tool.RunFilterCommand.prototype.onExecute = function( /*event*/ ) { 21470 // default does nothing. 21471 }; 21472 /** 21473 * Handle an undo event. 21474 * @param {Object} event The undo event with type and id. 21475 */ 21476 dwv.tool.RunFilterCommand.prototype.onUndo = function( /*event*/ ) { 21477 // default does nothing. 21478 }; 21479 21480 // namespaces 21481 var dwv = dwv || {}; 21482 dwv.tool = dwv.tool || {}; 21483 // external 21484 var MagicWand = MagicWand || {}; 21485 21486 /** 21487 * Floodfill painting tool. 21488 * @constructor 21489 * @param {Object} app The associated application. 21490 * @external MagicWand 21491 * @see {@link https://github.com/Tamersoul/magic-wand-js} 21492 */ 21493 dwv.tool.Floodfill = function(app) { 21494 /** 21495 * Original variables from external library. Used as in the lib example. 21496 * @private 21497 * @type Number 21498 */ 21499 var blurRadius = 5; 21500 /** 21501 * Original variables from external library. Used as in the lib example. 21502 * @private 21503 * @type Number 21504 */ 21505 var simplifyTolerant = 0; 21506 /** 21507 * Original variables from external library. Used as in the lib example. 21508 * @private 21509 * @type Number 21510 */ 21511 var simplifyCount = 2000; 21512 /** 21513 * Canvas info 21514 * @private 21515 * @type Object 21516 */ 21517 var imageInfo = null; 21518 /** 21519 * Object created by MagicWand lib containing border points 21520 * @private 21521 * @type Object 21522 */ 21523 var mask = null; 21524 /** 21525 * threshold default tolerance of the tool border 21526 * @private 21527 * @type Number 21528 */ 21529 var initialthreshold = 10; 21530 /** 21531 * threshold tolerance of the tool border 21532 * @private 21533 * @type Number 21534 */ 21535 var currentthreshold = null; 21536 /** 21537 * Closure to self: to be used by event handlers. 21538 * @private 21539 * @type WindowLevel 21540 */ 21541 var self = this; 21542 /** 21543 * Interaction start flag. 21544 * @type Boolean 21545 */ 21546 this.started = false; 21547 /** 21548 * Livewire GUI. 21549 * @type Object 21550 */ 21551 var gui = null; 21552 /** 21553 * Draw command. 21554 * @private 21555 * @type Object 21556 */ 21557 var command = null; 21558 /** 21559 * Current shape group. 21560 * @private 21561 * @type Object 21562 */ 21563 var shapeGroup = null; 21564 /** 21565 * Coordinates of the fist mousedown event. 21566 * @private 21567 * @type Object 21568 */ 21569 var initialpoint; 21570 /** 21571 * Floodfill border. 21572 * @private 21573 * @type Object 21574 */ 21575 var border = null; 21576 /** 21577 * List of parent points. 21578 * @private 21579 * @type Array 21580 */ 21581 var parentPoints = []; 21582 /** 21583 * Assistant variable to paint border on all slices. 21584 * @private 21585 * @type Boolean 21586 */ 21587 var extender = false; 21588 /** 21589 * Timeout for painting on mousemove. 21590 * @private 21591 */ 21592 var painterTimeout; 21593 /** 21594 * Drawing style. 21595 * @type Style 21596 */ 21597 this.style = new dwv.html.Style(); 21598 21599 /** 21600 * Event listeners. 21601 * @private 21602 */ 21603 var listeners = []; 21604 21605 /** 21606 * Set extend option for painting border on all slices. 21607 * @param {Boolean} The option to set 21608 */ 21609 this.setExtend = function(Bool) { 21610 extender = Bool; 21611 }; 21612 21613 /** 21614 * Get extend option for painting border on all slices. 21615 * @return {Boolean} The actual value of of the variable to use Floodfill on museup. 21616 */ 21617 this.getExtend = function() { 21618 return extender; 21619 }; 21620 21621 /**
21622 * Get (x, y) coordinates referenced to the canvas 21623 * @param {Object} event The original event. 21624 */ 21625 var getCoord = function(event) { 21626 return { x: event._x, y: event._y }; 21627 }; 21628 21629 /** 21630 * Calculate border. 21631 * @private 21632 * @param {Object} Start point. 21633 * @param {Number} Threshold tolerance. 21634 */ 21635 var calcBorder = function(points, threshold, simple) { 21636 21637 parentPoints = []; 21638 var image = { 21639 data: imageInfo.data, 21640 width: imageInfo.width, 21641 height: imageInfo.height, 21642 bytes: 4 21643 }; 21644 21645 // var p = new dwv.math.FastPoint2D(points.x, points.y); 21646 mask = MagicWand.floodFill(image, points.x, points.y, threshold); 21647 mask = MagicWand.gaussBlurOnlyBorder(mask, blurRadius); 21648 21649 var cs = MagicWand.traceContours(mask); 21650 cs = MagicWand.simplifyContours(cs, simplifyTolerant, simplifyCount); 21651 21652 if (cs.length > 0 && cs[0].points[0].x) { 21653 if (simple) { 21654 return cs[0].points; 21655 } 21656 for (var j = 0, icsl = cs[0].points.length; j < icsl; j++) { 21657 parentPoints.push(new dwv.math.Point2D(cs[0].points[j].x, cs[0].points[j].y)); 21658 } 21659 return parentPoints; 21660 } else { 21661 return false; 21662 } 21663 }; 21664 21665 /** 21666 * Paint Floodfill. 21667 * @private 21668 * @param {Object} Start point. 21669 * @param {Number} Threshold tolerance. 21670 */ 21671 var paintBorder = function(point, threshold) { 21672 // Calculate the border 21673 border = calcBorder(point, threshold); 21674 // Paint the border 21675 if (border) { 21676 var factory = new dwv.tool.RoiFactory(); 21677 shapeGroup = factory.create(border, self.style); 21678 shapeGroup.id(dwv.math.guid()); 21679 // draw shape command 21680 command = new dwv.tool.DrawGroupCommand(shapeGroup, "floodfill", app.getCurrentDrawLayer()); 21681 command.onExecute = fireEvent; 21682 command.onUndo = fireEvent; 21683 // // draw 21684 command.execute(); 21685 // save it in undo stack 21686 app.addToUndoStack(command); 21687 21688 return true; 21689 } else { 21690 return false; 21691 } 21692 }; 21693 21694 /** 21695 * Create Floodfill in all the prev and next slices while border is found 21696 */ 21697 this.extend = function(ini, end) { 21698 //avoid errors 21699 if (!initialpoint) { 21700 throw "'initialpoint' not found. User must click before use extend!"; 21701 } 21702 // remove previous draw 21703 if (shapeGroup) { 21704 shapeGroup.destroy(); 21705 } 21706 21707 var pos = app.getViewController().getCurrentPosition(); 21708 var threshold = currentthreshold || initialthreshold; 21709 21710 // Iterate over the next images and paint border on each slice. 21711 for (var i = pos.k, len = end ? end : app.getImage().getGeometry().getSize().getNumberOfSlices(); i < len; i++) { 21712 if (!paintBorder(initialpoint, threshold)) { 21713 break; 21714 } 21715 app.getViewController().incrementSliceNb(); 21716 } 21717 app.getViewController().setCurrentPosition(pos); 21718 21719 // Iterate over the prev images and paint border on each slice. 21720 for (var j = pos.k, jl = ini ? ini : 0; j > jl; j--) { 21721 if (!paintBorder(initialpoint, threshold)) { 21722 break; 21723 } 21724 app.getViewController().decrementSliceNb(); 21725 } 21726 app.getViewController().setCurrentPosition(pos); 21727 }; 21728 21729 /** 21730 * Modify tolerance threshold and redraw ROI. 21731 * @param {Number} New threshold. 21732 */ 21733 this.modifyThreshold = function(modifyThreshold, shape) { 21734 21735 if (!shape && shapeGroup) { 21736 shape = shapeGroup.getChildren(function(node) { 21737 return node.name() === 'shape'; 21738 })[0]; 21739 } else { 21740 throw 'No shape found'; 21741 } 21742 21743 clearTimeout(painterTimeout); 21744 painterTimeout = setTimeout(function() { 21745 border = calcBorder(initialpoint, modifyThreshold, true); 21746 if (!border) { 21747 return false; 21748 } 21749 var arr = []; 21750 for (var i = 0, bl = border.length; i < bl; ++i) { 21751 arr.push(border[i].x); 21752 arr.push(border[i].y); 21753 } 21754 shape.setPoints(arr); 21755 var shapeLayer = shape.getLayer(); 21756 shapeLayer.draw(); 21757 self.onThresholdChange(modifyThreshold); 21758 }, 100); 21759 }; 21760 21761 /** 21762 * Event fired when threshold change 21763 * @param {Number} Current threshold 21764 */ 21765 this.onThresholdChange = function( /*value*/ ) { 21766 // Defaults do nothing 21767 }; 21768 21769 /** 21770 * Handle mouse down event. 21771 * @param {Object} event The mouse down event. 21772 */ 21773 this.mousedown = function(event) { 21774 imageInfo = app.getImageData(); 21775 if (!imageInfo) { return console.error('No image found'); } 21776 21777 self.started = true; 21778 initialpoint = getCoord(event); 21779 paintBorder(initialpoint, initialthreshold); 21780 self.onThresholdChange(initialthreshold); 21781 }; 21782 21783 /** 21784 * Handle mouse move event. 21785 * @param {Object} event The mouse move event. 21786 */ 21787 this.mousemove = function(event) { 21788 if (!self.started) { 21789 return; 21790 } 21791 var movedpoint = getCoord(event); 21792 currentthreshold = Math.round(Math.sqrt(Math.pow((initialpoint.x - movedpoint.x), 2) + Math.pow((initialpoint.y - movedpoint.y), 2)) / 2); 21793 currentthreshold = currentthreshold < initialthreshold ? initialthreshold : currentthreshold - initialthreshold; 21794 self.modifyThreshold(currentthreshold); 21795 }; 21796 21797 /** 21798 * Handle mouse up event. 21799 * @param {Object} event The mouse up event. 21800 */ 21801 this.mouseup = function( /*event*/ ) { 21802 self.started = false; 21803 if (extender) { 21804 self.extend(); 21805 } 21806 }; 21807 21808 /** 21809 * Handle mouse out event. 21810 * @param {Object} event The mouse out event. 21811 */ 21812 this.mouseout = function( /*event*/ ) { 21813 self.mouseup( /*event*/ ); 21814 }; 21815 21816 /** 21817 * Handle touch start event. 21818 * @param {Object} event The touch start event. 21819 */ 21820 this.touchstart = function(event) { 21821 // treat as mouse down 21822 self.mousedown(event); 21823 }; 21824 21825 /** 21826 * Handle touch move event. 21827 * @param {Object} event The touch move event. 21828 */ 21829 this.touchmove = function(event) { 21830 // treat as mouse move 21831 self.mousemove(event); 21832 }; 21833 21834 /** 21835 * Handle touch end event. 21836 * @param {Object} event The touch end event. 21837 */ 21838 this.touchend = function( /*event*/ ) { 21839 // treat as mouse up 21840 self.mouseup( /*event*/ ); 21841 }; 21842 21843 /** 21844 * Handle key down event. 21845 * @param {Object} event The key down event. 21846 */ 21847 this.keydown = function(event) { 21848 app.onKeydown(event); 21849 }; 21850 21851 /** 21852 * Setup the tool GUI. 21853 */ 21854 this.setup = function() { 21855 gui = new dwv.gui.ColourTool(app, "ff"); 21856 gui.setup(); 21857 }; 21858 21859 /** 21860 * Enable the tool. 21861 * @param {Boolean} bool The flag to enable or not. 21862 */ 21863 this.display = function(bool) { 21864 if (gui) { 21865 gui.display(bool); 21866 } 21867 // TODO why twice? 21868 this.init(); 21869 }; 21870 21871 /** 21872 * Initialise the tool. 21873 */ 21874 this.init = function() { 21875 if (gui) { 21876 // init with the app window scale 21877 this.style.setScale(app.getWindowScale()); 21878 // set the default to the first in the list 21879 this.setLineColour(this.style.getLineColour()); 21880 // init html 21881 gui.initialise(); 21882 } 21883 21884 return true; 21885 }; 21886 21887 /** 21888 * Add an event listener on the app. 21889 * @param {Object} listener The method associated with the provided event type. 21890 */ 21891 this.addEventListener = function(listener) { 21892 listeners.push(listener); 21893 }; 21894 21895 /** 21896 * Remove an event listener from the app. 21897 * @param {Object} listener The method associated with the provided event type. 21898 */ 21899 this.removeEventListener = function(listener) { 21900 for (var i = 0; i < listeners.length; ++i) { 21901 if (listeners[i] === listener) { 21902 listeners.splice(i, 1); 21903 } 21904 } 21905 }; 21906 21907 // Private Methods ----------------------------------------------------------- 21908 21909 /** 21910 * Fire an event: call all associated listeners. 21911 * @param {Object} event The event to fire. 21912 */ 21913 function fireEvent(event) { 21914 for (var i = 0; i < listeners.length; ++i) { 21915 listeners[i](event); 21916 } 21917 } 21918 21919 }; // Floodfill class 21920 21921 /** 21922 * Help for this tool. 21923 * @return {Object} The help content. 21924 */ 21925 dwv.tool.Floodfill.prototype.getHelp = function() { 21926 return { 21927 'title': dwv.i18n("tool.Floodfill.name"), 21928 'brief': dwv.i18n("tool.Floodfill.brief"), 21929 "mouse": { 21930 "click": dwv.i18n("tool.Floodfill.click") 21931 }, 21932 "touch": { 21933 "tap": dwv.i18n("tool.Floodfill.tap") 21934 } 21935 }; 21936 }; 21937 21938 /** 21939 * Set the line colour of the drawing. 21940 * @param {String} colour The colour to set. 21941 */ 21942 dwv.tool.Floodfill.prototype.setLineColour = function(colour) { 21943 // set style var 21944 this.style.setLineColour(colour); 21945 }; 21946 21947 // namespaces 21948 var dwv = dwv || {}; 21949 dwv.tool = dwv.tool || {}; 21950 // external 21951 var Konva = Konva || {}; 21952 21953 /** 21954 * FreeHand factory. 21955 * @constructor 21956 * @external Konva 21957 */ 21958 dwv.tool.FreeHandFactory = function() { 21959 /** 21960 * Get the number of points needed to build the shape. 21961 * @return {Number} The number of points. 21962 */ 21963 this.getNPoints = function() { return 1000; }; 21964 /** 21965 * Get the timeout between point storage. 21966 * @return {Number} The timeout in milliseconds. 21967 */ 21968 this.getTimeout = function() { return 25; }; 21969 }; 21970 21971 /** 21972 * Create a roi shape to be displayed. 21973 * @param {Array} points The points from which to extract the line. 21974 * @param {Object} style The drawing style. 21975 * @param {Object} image The associated image. 21976 */ 21977 dwv.tool.FreeHandFactory.prototype.create = function(points, style /*, image*/ ) { 21978 // points stored the Konvajs way 21979 var arr = []; 21980 for (var i = 0; i < points.length; ++i) { 21981 arr.push(points[i].getX()); 21982 arr.push(points[i].getY()); 21983 } 21984 // draw shape 21985 var kshape = new Konva.Line({ 21986 points: arr, 21987 stroke: style.getLineColour(), 21988 strokeWidth: style.getScaledStrokeWidth(), 21989 name: "shape", 21990 tension: 0.5 21991 }); 21992 21993 // text 21994 var ktext = new Konva.Text({ 21995 fontSize: style.getScaledFontSize(), 21996 fontFamily: style.getFontFamily(), 21997 fill: style.getLineColour(), 21998 name: "text" 21999 }); 22000 ktext.textExpr = ""; 22001 ktext.longText = ""; 22002 ktext.quant = null; 22003 ktext.setText(ktext.textExpr); 22004 22005 // label 22006 var klabel = new Konva.Label({ 22007 x: points[0].getX(), 22008 y: points[0].getY() + 10, 22009 name: "label" 22010 }); 22011 klabel.add(ktext); 22012 klabel.add(new Konva.Tag()); 22013 22014 // return group 22015 var group = new Konva.Group(); 22016 group.name("freeHand-group"); 22017 group.add(kshape); 22018 group.add(klabel); 22019 group.visible(true); // dont inherit 22020 return group; 22021 }; 22022 22023 /** 22024 * Update a FreeHand shape. 22025 * @param {Object} anchor The active anchor. 22026 * @param {Object} image The associated image. 22027 */ 22028 dwv.tool.UpdateFreeHand = function(anchor /*, image*/ ) { 22029 // parent group 22030 var group = anchor.getParent(); 22031 // associated shape 22032 var kline = group.getChildren(function(node) { 22033 return node.name() === 'shape'; 22034 })[0]; 22035 // associated label 22036 var klabel = group.getChildren(function(node) { 22037 return node.name() === 'label'; 22038 })[0]; 22039 22040 // update self 22041 var point = group.getChildren(function(node) { 22042 return node.id() === anchor.id(); 22043 })[0]; 22044 point.x(anchor.x()); 22045 point.y(anchor.y()); 22046 // update the roi point and compensate for possible drag 22047 // (the anchor id is the index of the point in the list) 22048 var points = kline.points();
22049 points[anchor.id()] = anchor.x() - kline.x(); 22050 points[anchor.id() + 1] = anchor.y() - kline.y(); 22051 kline.points(points); 22052 22053 // update text 22054 var ktext = klabel.getText(); 22055 ktext.quant = null; 22056 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22057 // update position 22058 var textPos = { 'x': points[0] + kline.x(), 'y': points[1] + kline.y() + 10 }; 22059 klabel.position(textPos); 22060 }; 22061 22062 // namespaces 22063 var dwv = dwv || {}; 22064 dwv.tool = dwv.tool || {}; 22065 22066 /** 22067 * Livewire painting tool. 22068 * @constructor 22069 * @param {Object} app The associated application. 22070 */ 22071 dwv.tool.Livewire = function(app) { 22072 /** 22073 * Closure to self: to be used by event handlers. 22074 * @private 22075 * @type WindowLevel 22076 */ 22077 var self = this; 22078 /** 22079 * Livewire GUI. 22080 * @type Object 22081 */ 22082 var gui = null; 22083 /** 22084 * Interaction start flag. 22085 * @type Boolean 22086 */ 22087 this.started = false; 22088 22089 /** 22090 * Draw command. 22091 * @private 22092 * @type Object 22093 */ 22094 var command = null; 22095 /** 22096 * Current shape group. 22097 * @private 22098 * @type Object 22099 */ 22100 var shapeGroup = null; 22101 /** 22102 * Drawing style. 22103 * @type Style 22104 */ 22105 this.style = new dwv.html.Style(); 22106 // init with the app window scale 22107 this.style.setScale(app.getWindowScale()); 22108 22109 /** 22110 * Path storage. Paths are stored in reverse order. 22111 * @private 22112 * @type Path 22113 */ 22114 var path = new dwv.math.Path(); 22115 /** 22116 * Current path storage. Paths are stored in reverse order. 22117 * @private 22118 * @type Path 22119 */ 22120 var currentPath = new dwv.math.Path(); 22121 /** 22122 * List of parent points. 22123 * @private 22124 * @type Array 22125 */ 22126 var parentPoints = []; 22127 /** 22128 * Tolerance. 22129 * @private 22130 * @type Number 22131 */ 22132 var tolerance = 5; 22133 22134 /** 22135 * Event listeners. 22136 * @private 22137 */ 22138 var listeners = []; 22139 22140 /** 22141 * Clear the parent points list. 22142 * @private 22143 */ 22144 function clearParentPoints() { 22145 var nrows = app.getImage().getGeometry().getSize().getNumberOfRows(); 22146 for (var i = 0; i < nrows; ++i) { 22147 parentPoints[i] = []; 22148 } 22149 } 22150 22151 /** 22152 * Clear the stored paths. 22153 * @private 22154 */ 22155 function clearPaths() { 22156 path = new dwv.math.Path(); 22157 currentPath = new dwv.math.Path(); 22158 } 22159 22160 /** 22161 * Scissor representation. 22162 * @private 22163 * @type Scissors 22164 */ 22165 var scissors = new dwv.math.Scissors(); 22166 22167 /** 22168 * Handle mouse down event. 22169 * @param {Object} event The mouse down event. 22170 */ 22171 this.mousedown = function(event) { 22172 // first time 22173 if (!self.started) { 22174 self.started = true; 22175 self.x0 = event._x; 22176 self.y0 = event._y; 22177 // clear vars 22178 clearPaths(); 22179 clearParentPoints(); 22180 // do the training from the first point 22181 var p = new dwv.math.FastPoint2D(event._x, event._y); 22182 scissors.doTraining(p); 22183 // add the initial point to the path 22184 var p0 = new dwv.math.Point2D(event._x, event._y); 22185 path.addPoint(p0); 22186 path.addControlPoint(p0); 22187 } else { 22188 // final point: at 'tolerance' of the initial point 22189 if ((Math.abs(event._x - self.x0) < tolerance) && (Math.abs(event._y - self.y0) < tolerance)) { 22190 // draw 22191 self.mousemove(event); 22192 // listen 22193 command.onExecute = fireEvent; 22194 command.onUndo = fireEvent; 22195 // debug 22196 console.log("Done."); 22197 // save command in undo stack 22198 app.addToUndoStack(command); 22199 // set flag 22200 self.started = false; 22201 } 22202 // anchor point 22203 else { 22204 path = currentPath; 22205 clearParentPoints();
22206 var pn = new dwv.math.FastPoint2D(event._x, event._y); 22207 scissors.doTraining(pn); 22208 path.addControlPoint(currentPath.getPoint(0)); 22209 } 22210 } 22211 }; 22212 22213 /** 22214 * Handle mouse move event. 22215 * @param {Object} event The mouse move event. 22216 */ 22217 this.mousemove = function(event) { 22218 if (!self.started) { 22219 return; 22220 } 22221 // set the point to find the path to 22222 var p = new dwv.math.FastPoint2D(event._x, event._y); 22223 scissors.setPoint(p); 22224 // do the work 22225 var results = 0; 22226 var stop = false; 22227 while (!parentPoints[p.y][p.x] && !stop) { 22228 console.log("Getting ready..."); 22229 results = scissors.doWork(); 22230 22231 if (results.length === 0) { 22232 stop = true; 22233 } else { 22234 // fill parents 22235 for (var i = 0; i < results.length - 1; i += 2) { 22236 var _p = results[i]; 22237 var _q = results[i + 1]; 22238 parentPoints[_p.y][_p.x] = _q; 22239 } 22240 } 22241 } 22242 console.log("Ready!"); 22243 22244 // get the path 22245 currentPath = new dwv.math.Path(); 22246 stop = false; 22247 while (p && !stop) { 22248 currentPath.addPoint(new dwv.math.Point2D(p.x, p.y)); 22249 if (!parentPoints[p.y]) { 22250 stop = true; 22251 } else { 22252 if (!parentPoints[p.y][p.x]) { 22253 stop = true; 22254 } else { 22255 p = parentPoints[p.y][p.x]; 22256 } 22257 } 22258 } 22259 currentPath.appenPath(path); 22260 22261 // remove previous draw 22262 if (shapeGroup) { 22263 shapeGroup.destroy(); 22264 } 22265 // create shape 22266 var factory = new dwv.tool.RoiFactory(); 22267 shapeGroup = factory.create(currentPath.pointArray, self.style); 22268 shapeGroup.id(dwv.math.guid()); 22269 // draw shape command 22270 command = new dwv.tool.DrawGroupCommand(shapeGroup, "livewire", app.getCurrentDrawLayer()); 22271 // draw 22272 command.execute(); 22273 }; 22274 22275 /** 22276 * Handle mouse up event. 22277 * @param {Object} event The mouse up event. 22278 */ 22279 this.mouseup = function( /*event*/ ) { 22280 // nothing to do 22281 }; 22282 22283 /** 22284 * Handle mouse out event. 22285 * @param {Object} event The mouse out event. 22286 */ 22287 this.mouseout = function(event) { 22288 // treat as mouse up 22289 self.mouseup(event); 22290 }; 22291 22292 /** 22293 * Handle double click event. 22294 * @param {Object} event The double click event. 22295 */ 22296 this.dblclick = function( /*event*/ ) { 22297 console.log("dblclick"); 22298 // save command in undo stack 22299 app.addToUndoStack(command); 22300 // set flag 22301 self.started = false; 22302 }; 22303 22304 /** 22305 * Handle touch start event. 22306 * @param {Object} event The touch start event. 22307 */ 22308 this.touchstart = function(event) { 22309 // treat as mouse down 22310 self.mousedown(event); 22311 }; 22312 22313 /** 22314 * Handle touch move event. 22315 * @param {Object} event The touch move event. 22316 */ 22317 this.touchmove = function(event) { 22318 // treat as mouse move 22319 self.mousemove(event); 22320 }; 22321 22322 /** 22323 * Handle touch end event. 22324 * @param {Object} event The touch end event. 22325 */ 22326 this.touchend = function(event) { 22327 // treat as mouse up 22328 self.mouseup(event); 22329 }; 22330 22331 /** 22332 * Handle key down event. 22333 * @param {Object} event The key down event. 22334 */ 22335 this.keydown = function(event) { 22336 app.onKeydown(event); 22337 }; 22338 22339 /** 22340 * Setup the tool GUI. 22341 */ 22342 this.setup = function() { 22343 gui = new dwv.gui.ColourTool(app, "lw"); 22344 gui.setup(); 22345 }; 22346 22347 /** 22348 * Enable the tool. 22349 * @param {Boolean} bool The flag to enable or not. 22350 */ 22351 this.display = function(bool) { 22352 if (gui) { 22353 gui.display(bool); 22354 } 22355 // start scissors if displayed 22356 if (bool) { 22357 //scissors = new dwv.math.Scissors(); 22358 var size = app.getImage().getGeometry().getSize(); 22359 scissors.setDimensions( 22360 size.getNumberOfColumns(), 22361 size.getNumberOfRows()); 22362 scissors.setData(app.getImageData().data); 22363 } 22364 }; 22365 22366 /** 22367 * Initialise the tool. 22368 */ 22369 this.init = function() { 22370 if (gui) { 22371 // init with the app window scale 22372 this.style.setScale(app.getWindowScale()); 22373 // set the default to the first in the list 22374 this.setLineColour(this.style.getLineColour()); 22375 // init html 22376 gui.initialise(); 22377 } 22378 22379 return true; 22380 }; 22381 22382 /** 22383 * Add an event listener on the app. 22384 * @param {Object} listener The method associated with the provided event type. 22385 */ 22386 this.addEventListener = function(listener) { 22387 listeners.push(listener); 22388 }; 22389 22390 /** 22391 * Remove an event listener from the app. 22392 * @param {Object} listener The method associated with the provided event type. 22393 */ 22394 this.removeEventListener = function(listener) { 22395 for (var i = 0; i < listeners.length; ++i) { 22396 if (listeners[i] === listener) { 22397 listeners.splice(i, 1); 22398 } 22399 } 22400 }; 22401 22402 // Private Methods ----------------------------------------------------------- 22403 22404 /** 22405 * Fire an event: call all associated listeners. 22406 * @param {Object} event The event to fire. 22407 */ 22408 function fireEvent(event) { 22409 for (var i = 0; i < listeners.length; ++i) { 22410 listeners[i](event); 22411 } 22412 } 22413 22414 }; // Livewire class 22415 22416 /** 22417 * Help for this tool. 22418 * @return {Object} The help content. 22419 */ 22420 dwv.tool.Livewire.prototype.getHelp = function() { 22421 return { 22422 "title": dwv.i18n("tool.Livewire.name"), 22423 "brief": dwv.i18n("tool.Livewire.brief") 22424 }; 22425 }; 22426 22427 /** 22428 * Set the line colour of the drawing. 22429 * @param {String} colour The colour to set. 22430 */ 22431 dwv.tool.Livewire.prototype.setLineColour = function(colour) { 22432 // set style var 22433 this.style.setLineColour(colour); 22434 }; 22435 22436 // namespaces 22437 var dwv = dwv || {}; 22438 dwv.tool = dwv.tool || {}; 22439 // external 22440 var Konva = Konva || {}; 22441 22442 /** 22443 * Protractor factory. 22444 * @constructor 22445 * @external Konva 22446 */ 22447 dwv.tool.ProtractorFactory = function() { 22448 /** 22449 * Get the number of points needed to build the shape. 22450 * @return {Number} The number of points. 22451 */ 22452 this.getNPoints = function() { return 3; }; 22453 /** 22454 * Get the timeout between point storage. 22455 * @return {Number} The timeout in milliseconds. 22456 */ 22457 this.getTimeout = function() { return 500; }; 22458 }; 22459 22460 /** 22461 * Create a protractor shape to be displayed. 22462 * @param {Array} points The points from which to extract the protractor. 22463 * @param {Object} style The drawing style. 22464 * @param {Object} image The associated image. 22465 */ 22466 dwv.tool.ProtractorFactory.prototype.create = function(points, style /*, image*/ ) { 22467 // physical shape 22468 var line0 = new dwv.math.Line(points[0], points[1]); 22469 // points stored the Konvajs way 22470 var pointsArray = []; 22471 for (var i = 0; i < points.length; ++i) { 22472 pointsArray.push(points[i].getX()); 22473 pointsArray.push(points[i].getY()); 22474 } 22475 // draw shape 22476 var kshape = new Konva.Line({ 22477 points: pointsArray, 22478 stroke: style.getLineColour(), 22479 strokeWidth: style.getScaledStrokeWidth(), 22480 name: "shape" 22481 }); 22482 var group = new Konva.Group(); 22483 group.name("protractor-group"); 22484 group.add(kshape); 22485 group.visible(true); // dont inherit 22486 // text and decoration 22487 if (points.length === 3) { 22488 var line1 = new dwv.math.Line(points[1], points[2]); 22489 // larger hitfunc 22490 kshape.hitFunc(function(context) { 22491 context.beginPath(); 22492 context.moveTo(points[0].getX(), points[0].getY()); 22493 context.lineTo(points[1].getX(), points[1].getY()); 22494 context.lineTo(points[2].getX(), points[2].getY()); 22495 context.closePath(); 22496 context.fillStrokeShape(this); 22497 }); 22498 // quantification 22499 var angle = dwv.math.getAngle(line0, line1); 22500 var inclination = line0.getInclination(); 22501 if (angle > 180) { 22502 angle = 360 - angle; 22503 inclination += angle; 22504 } 22505 22506 // quantification 22507 var quant = { "angle": { "value": angle, "unit": dwv.i18n("unit.degree") } }; 22508 var ktext = new Konva.Text({ 22509 fontSize: style.getScaledFontSize(), 22510 fontFamily: style.getFontFamily(), 22511 fill: style.getLineColour(), 22512 name: "text" 22513 }); 22514 ktext.textExpr = "{angle}"; 22515 ktext.longText = ""; 22516 ktext.quant = quant; 22517 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22518 22519 // label 22520 var midX = (line0.getMidpoint().getX() + line1.getMidpoint().getX()) / 2; 22521 var midY = (line0.getMidpoint().getY() + line1.getMidpoint().getY()) / 2; 22522 var klabel = new Konva.Label({ 22523 x: midX, 22524 y: midY - 15, 22525 name: "label" 22526 }); 22527 klabel.add(ktext); 22528 klabel.add(new Konva.Tag()); 22529 22530 // arc 22531 var radius = Math.min(line0.getLength(), line1.getLength()) * 33 / 100; 22532 var karc = new Konva.Arc({ 22533 innerRadius: radius, 22534 outerRadius: radius, 22535 stroke: style.getLineColour(), 22536 strokeWidth: style.getScaledStrokeWidth(), 22537 angle: angle, 22538 rotation: -inclination, 22539 x: points[1].getX(), 22540 y: points[1].getY(), 22541 name: "shape-arc" 22542 }); 22543 // add to group 22544 group.add(klabel); 22545 group.add(karc); 22546 } 22547 // return group 22548 return group; 22549 }; 22550 22551 /** 22552 * Update a protractor shape. 22553 * @param {Object} anchor The active anchor. 22554 * @param {Object} image The associated image. 22555 */ 22556 dwv.tool.UpdateProtractor = function(anchor /*, image*/ ) { 22557 // parent group 22558 var group = anchor.getParent(); 22559 // associated shape 22560 var kline = group.getChildren(function(node) { 22561 return node.name() === 'shape'; 22562 })[0]; 22563 // associated label 22564 var klabel = group.getChildren(function(node) { 22565 return node.name() === 'label'; 22566 })[0]; 22567 // associated arc 22568 var karc = group.getChildren(function(node) { 22569 return node.name() === 'shape-arc'; 22570 })[0]; 22571 // find special points 22572 var begin = group.getChildren(function(node) { 22573 return node.id() === 'begin'; 22574 })[0]; 22575 var mid = group.getChildren(function(node) { 22576 return node.id() === 'mid'; 22577 })[0]; 22578 var end = group.getChildren(function(node) { 22579 return node.id() === 'end'; 22580 })[0]; 22581 // update special points 22582 switch (anchor.id()) { 22583 case 'begin': 22584 begin.x(anchor.x()); 22585 begin.y(anchor.y()); 22586 break; 22587 case 'mid': 22588 mid.x(anchor.x()); 22589 mid.y(anchor.y()); 22590 break; 22591 case 'end': 22592 end.x(anchor.x()); 22593 end.y(anchor.y()); 22594 break; 22595 } 22596 // update shape and compensate for possible drag 22597 // note: shape.position() and shape.size() won't work... 22598 var bx = begin.x() - kline.x(); 22599 var by = begin.y() - kline.y(); 22600 var mx = mid.x() - kline.x(); 22601 var my = mid.y() - kline.y(); 22602 var ex = end.x() - kline.x(); 22603 var ey = end.y() - kline.y(); 22604 kline.points([bx, by, mx, my, ex, ey]); 22605 // larger hitfunc 22606 kline.hitFunc(function(context) { 22607 context.beginPath(); 22608 context.moveTo(bx, by); 22609 context.lineTo(mx, my); 22610 context.lineTo(ex, ey); 22611 context.closePath(); 22612 context.fillStrokeShape(this); 22613 }); 22614 // update text 22615 var p2d0 = new dwv.math.Point2D(begin.x(), begin.y()); 22616 var p2d1 = new dwv.math.Point2D(mid.x(), mid.y()); 22617 var p2d2 = new dwv.math.Point2D(end.x(), end.y()); 22618 var line0 = new dwv.math.Line(p2d0, p2d1); 22619 var line1 = new dwv.math.Line(p2d1, p2d2); 22620 var angle = dwv.math.getAngle(line0, line1); 22621 var inclination = line0.getInclination(); 22622 if (angle > 180) { 22623 angle = 360 - angle; 22624 inclination += angle; 22625 } 22626 22627 // update text 22628 var quant = { "angle": { "value": angle, "unit": dwv.i18n("unit.degree") } }; 22629 var ktext = klabel.getText(); 22630 ktext.quant = quant; 22631 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22632 // update position 22633 var midX = (line0.getMidpoint().getX() + line1.getMidpoint().getX()) / 2; 22634 var midY = (line0.getMidpoint().getY() + line1.getMidpoint().getY()) / 2; 22635 var textPos = { 'x': midX, 'y': midY - 15 }; 22636 klabel.position(textPos); 22637 22638 // arc 22639 var radius = Math.min(line0.getLength(), line1.getLength()) * 33 / 100; 22640 karc.innerRadius(radius); 22641 karc.outerRadius(radius); 22642 karc.angle(angle); 22643 karc.rotation(-inclination); 22644 var arcPos = { 'x': mid.x(), 'y': mid.y() }; 22645 karc.position(arcPos); 22646 }; 22647 22648 // namespaces 22649 var dwv = dwv || {}; 22650 dwv.tool = dwv.tool || {}; 22651 // external 22652 var Konva = Konva || {}; 22653 22654 /** 22655 * Rectangle factory. 22656 * @constructor 22657 * @external Konva 22658 */ 22659 dwv.tool.RectangleFactory = function() { 22660 /** 22661 * Get the number of points needed to build the shape. 22662 * @return {Number} The number of points. 22663 */ 22664 this.getNPoints = function() { return 2; }; 22665 /** 22666 * Get the timeout between point storage. 22667 * @return {Number} The timeout in milliseconds. 22668 */ 22669 this.getTimeout = function() { return 0; }; 22670 }; 22671 22672 /** 22673 * Create a rectangle shape to be displayed. 22674 * @param {Array} points The points from which to extract the rectangle. 22675 * @param {Object} style The drawing style. 22676 * @param {Object} image The associated image. 22677 */ 22678 dwv.tool.RectangleFactory.prototype.create = function(points, style, image) { 22679 // physical shape 22680 var rectangle = new dwv.math.Rectangle(points[0], points[1]); 22681 // draw shape 22682 var kshape = new Konva.Rect({ 22683 x: rectangle.getBegin().getX(), 22684 y: rectangle.getBegin().getY(), 22685 width: rectangle.getWidth(), 22686 height: rectangle.getHeight(), 22687 s
22687troke: style.getLineColour(), 22688 strokeWidth: style.getScaledStrokeWidth(), 22689 name: "shape" 22690 }); 22691 // quantification 22692 var quant = image.quantifyRect(rectangle); 22693 var ktext = new Konva.Text({ 22694 fontSize: style.getScaledFontSize(), 22695 fontFamily: style.getFontFamily(), 22696 fill: style.getLineColour(), 22697 name: "text" 22698 }); 22699 ktext.textExpr = "{surface}"; 22700 ktext.longText = ""; 22701 ktext.quant = quant; 22702 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22703 22704 // label 22705 var klabel = new Konva.Label({ 22706 x: rectangle.getBegin().getX(), 22707 y: rectangle.getEnd().getY() + 10, 22708 name: "label" 22709 }); 22710 klabel.add(ktext); 22711 klabel.add(new Konva.Tag()); 22712 22713 // return group 22714 var group = new Konva.Group(); 22715 group.name("rectangle-group"); 22716 group.add(kshape); 22717 group.add(klabel); 22718 group.visible(true); // dont inherit 22719 return group; 22720 }; 22721 22722 /** 22723 * Update a rectangle shape. 22724 * @param {Object} anchor The active anchor. 22725 * @param {Object} image The associated image. 22726 */ 22727 dwv.tool.UpdateRect = function(anchor, image) { 22728 // parent group 22729 var group = anchor.getParent(); 22730 // associated shape 22731 var krect = group.getChildren(function(node) { 22732 return node.name() === 'shape'; 22733 })[0]; 22734 // associated label 22735 var klabel = group.getChildren(function(node) { 22736 return node.name() === 'label'; 22737 })[0]; 22738 // find special points 22739 var topLeft = group.getChildren(function(node) { 22740 return node.id() === 'topLeft'; 22741 })[0]; 22742 var topRight = group.getChildren(function(node) { 22743 return node.id() === 'topRight'; 22744 })[0]; 22745 var bottomRight = group.getChildren(function(node) { 22746 return node.id() === 'bottomRight'; 22747 })[0]; 22748 var bottomLeft = group.getChildren(function(node) { 22749 return node.id() === 'bottomLeft'; 22750 })[0]; 22751 // update 'self' (undo case) and special points 22752 switch (anchor.id()) { 22753 case 'topLeft': 22754 topLeft.x(anchor.x()); 22755 topLeft.y(anchor.y()); 22756 topRight.y(anchor.y()); 22757 bottomLeft.x(anchor.x()); 22758 break; 22759 case 'topRight': 22760 topRight.x(anchor.x()); 22761 topRight.y(anchor.y()); 22762 topLeft.y(anchor.y()); 22763 bottomRight.x(anchor.x()); 22764 break; 22765 case 'bottomRight': 22766 bottomRight.x(anchor.x()); 22767 bottomRight.y(anchor.y()); 22768 bottomLeft.y(anchor.y()); 22769 topRight.x(anchor.x()); 22770 break; 22771 case 'bottomLeft': 22772 bottomLeft.x(anchor.x()); 22773 bottomLeft.y(anchor.y()); 22774 bottomRight.y(anchor.y()); 22775 topLeft.x(anchor.x()); 22776 break; 22777 default: 22778 console.error('Unhandled anchor id: ' + anchor.id()); 22779 break; 22780 } 22781 // update shape 22782 krect.position(topLeft.position()); 22783 var width = topRight.x() - topLeft.x(); 22784 var height = bottomLeft.y() - topLeft.y(); 22785 if (width && height) { 22786 krect.size({ 'width': width, 'height': height }); 22787 } 22788 // new rect 22789 var p2d0 = new dwv.math.Point2D(topLeft.x(), topLeft.y()); 22790 var p2d1 = new dwv.math.Point2D(bottomRight.x(), bottomRight.y()); 22791 var rect = new dwv.math.Rectangle(p2d0, p2d1); 22792 // update text 22793 var quant = image.quantifyRect(rect); 22794 var ktext = klabel.getText(); 22795 ktext.quant = quant; 22796 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22797 // update position 22798 var textPos = { 'x': rect.getBegin().getX(), 'y': rect.getEnd().getY() + 10 }; 22799 klabel.position(textPos); 22800 }; 22801 22802 // namespaces 22803 var dwv = dwv || {}; 22804 dwv.tool = dwv.tool || {}; 22805 // external 22806 var Konva = Konva || {}; 22807 22808 /** 22809 * ROI factory. 22810 * @constructor 22811 * @external Konva 22812 */ 22813 dwv.tool.RoiFactory = function() { 22814 /** 22815 * Get the number of points needed to build the shape. 22816 * @return {Number} The number of points. 22817 */ 22818 this.getNPoints = function() { return 50; }; 22819 /** 22820 * Get the timeout between point storage. 22821 * @return {Number} The timeout in milliseconds. 22822 */ 22823 this.getTimeout = function() { return 100; }; 22824 }; 22825 22826 /** 22827 * Create a roi shape to be displayed. 22828 * @param {Array} points The points from which to extract the line. 22829 * @param {Object} style The drawing style. 22830 * @param {Object} image The associated image. 22831 */ 22832 dwv.tool.RoiFactory.prototype.create = function(points, style /*, image*/ ) { 22833 // physical shape
22834 var roi = new dwv.math.ROI(); 22835 // add input points to the ROI 22836 roi.addPoints(points); 22837 // points stored the Konvajs way 22838 var arr = []; 22839 for (var i = 0; i < roi.getLength(); ++i) { 22840 arr.push(roi.getPoint(i).getX()); 22841 arr.push(roi.getPoint(i).getY()); 22842 } 22843 // draw shape 22844 var kshape = new Konva.Line({ 22845 points: arr, 22846 stroke: style.getLineColour(), 22847 strokeWidth: style.getScaledStrokeWidth(), 22848 name: "shape", 22849 closed: true 22850 }); 22851 22852 // text 22853 var ktext = new Konva.Text({ 22854 fontSize: style.getScaledFontSize(), 22855 fontFamily: style.getFontFamily(), 22856 fill: style.getLineColour(), 22857 name: "text" 22858 }); 22859 ktext.textExpr = ""; 22860 ktext.longText = ""; 22861 ktext.quant = null; 22862 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22863 22864 // label 22865 var klabel = new Konva.Label({ 22866 x: roi.getPoint(0).getX(), 22867 y: roi.getPoint(0).getY() + 10, 22868 name: "label" 22869 }); 22870 klabel.add(ktext); 22871 klabel.add(new Konva.Tag()); 22872 22873 // return group 22874 var group = new Konva.Group(); 22875 group.name("roi-group"); 22876 group.add(kshape); 22877 group.add(klabel); 22878 group.visible(true); // dont inherit 22879 return group; 22880 }; 22881 22882 /** 22883 * Update a roi shape. 22884 * @param {Object} anchor The active anchor. 22885 * @param {Object} image The associated image. 22886 */ 22887 dwv.tool.UpdateRoi = function(anchor /*, image*/ ) { 22888 // parent group 22889 var group = anchor.getParent(); 22890 // associated shape 22891 var kroi = group.getChildren(function(node) { 22892 return node.name() === 'shape'; 22893 })[0]; 22894 // associated label 22895 var klabel = group.getChildren(function(node) { 22896 return node.name() === 'label'; 22897 })[0]; 22898 22899 // update self 22900 var point = group.getChildren(function(node) { 22901 return node.id() === anchor.id(); 22902 })[0]; 22903 point.x(anchor.x()); 22904 point.y(anchor.y()); 22905 // update the roi point and compensate for possible drag 22906 // (the anchor id is the index of the point in the list) 22907 var points = kroi.points(); 22908 points[anchor.id()] = anchor.x() - kroi.x(); 22909 points[anchor.id() + 1] = anchor.y() - kroi.y(); 22910 kroi.points(points); 22911 22912 // update text 22913 var ktext = klabel.getText(); 22914 ktext.quant = null; 22915 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 22916 // update position 22917 var textPos = { 'x': points[0] + kroi.x(), 'y': points[1] + kroi.y() + 10 }; 22918 klabel.position(textPos); 22919 22920 }; 22921 22922 // namespaces 22923 var dwv = dwv || {}; 22924 dwv.tool = dwv.tool || {}; 22925 // external 22926 var Konva = Konva || {}; 22927 22928 /** 22929 * Ruler factory. 22930 * @constructor 22931 * @external Konva 22932 */ 22933 dwv.tool.RulerFactory = function() { 22934 /** 22935 * Get the number of points needed to build the shape. 22936 * @return {Number} The number of points. 22937 */ 22938 this.getNPoints = function() { return 2; }; 22939 /** 22940 * Get the timeout between point storage. 22941 * @return {Number} The timeout in milliseconds. 22942 */ 22943 this.getTimeout = function() { return 0; }; 22944 }; 22945 22946 /** 22947 * Create a ruler shape to be displayed. 22948 * @param {Array} points The points from which to extract the line. 22949 * @param {Object} style The drawing style. 22950 * @param {Object} image The associated image. 22951 */ 22952 dwv.tool.RulerFactory.prototype.create = function(points, style, image) { 22953 // physical shape 22954 var line = new dwv.math.Line(points[0], points[1]); 22955 // draw shape 22956 var kshape = new Konva.Line({ 22957 points: [line.getBegin().getX(), line.getBegin().getY(), 22958 line.getEnd().getX(), line.getEnd().getY() 22959 ], 22960 stroke: style.getLineColour(), 22961 strokeWidth: style.getScaledStrokeWidth(), 22962 name: "shape" 22963 }); 22964 22965 // tick begin 22966 var linePerp0 = dwv.math.getPerpendicularLine(line, points[0], 10); 22967 var ktick0 = new Konva.Line({ 22968 points: [linePerp0.getBegin().getX(), linePerp0.getBegin().getY(), 22969 linePerp0.getEnd().getX(), linePerp0.getEnd().getY() 22970 ], 22971 stroke: style.getLineColour(), 22972 strokeWidth: style.getScaledStrokeWidth(), 22973 name: "shape-tick0" 22974 }); 22975 22976 // tick end 22977 var linePerp1 = dwv.math.getPerpendicularLine(line, points[1], 10); 22978 var ktick1 = new Konva.Line({ 22979 points: [linePerp1.getBegin().getX(), linePerp1.getBegin().getY(), 22980 linePerp1.getEnd().getX(), linePerp1.getEnd().getY() 22981 ], 22982 stroke: style.getLineColour(), 22983 strokeWidth: style.getScaledStrokeWidth(), 22984 name: "shape-tick1" 22985 }); 22986 22987 // larger hitfunc 22988 kshape.hitFunc(function(context) { 22989 context.beginPath(); 22990 context.moveTo(linePerp0.getBegin().getX(), linePerp0.getBegin().getY()); 22991 context.lineTo(linePerp0.getEnd().getX(), linePerp0.getEnd().getY()); 22992 context.lineTo(linePerp1.getEnd().getX(), linePerp1.getEnd().getY()); 22993 context.lineTo(linePerp1.getBegin().getX(), linePerp1.getBegin().getY()); 22994 context.closePath(); 22995 context.fillStrokeShape(this); 22996 }); 22997 22998 // quantification 22999 var quant = image.quantifyLine(line); 23000 var ktext = new Konva.Text({ 23001 fontSize: style.getScaledFontSize(), 23002 fontFamily: style.getFontFamily(), 23003 fill: style.getLineColour(), 23004 name: "text" 23005 }); 23006 ktext.textExpr = "{length}"; 23007 ktext.longText = ""; 23008 ktext.quant = quant; 23009 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 23010 // label 23011 var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1; 23012 var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5; 23013 var klabel = new Konva.Label({ 23014 x: line.getEnd().getX() + dX * 25, 23015 y: line.getEnd().getY() + dY * 15, 23016 name: "label" 23017 }); 23018 klabel.add(ktext); 23019 klabel.add(new Konva.Tag()); 23020 23021 // return group 23022 var group = new Konva.Group(); 23023 group.name("ruler-group"); 23024 group.add(kshape); 23025 group.add(ktick0); 23026 group.add(ktick1); 23027 group.add(klabel); 23028 group.visible(true); // dont inherit 23029 return group; 23030 }; 23031 23032 /** 23033 * Update a ruler shape. 23034 * @param {Object} anchor The active anchor. 23035 * @param {Object} image The associated image. 23036 */ 23037 dwv.tool.UpdateRuler = function(anchor, image) { 23038 // parent group 23039 var group = anchor.getParent(); 23040 // associated shape 23041 var kline = group.getChildren(function(node) { 23042 return node.name() === 'shape'; 23043 })[0]; 23044 // associated tick0 23045 var ktick0 = group.getChildren(function(node) { 23046 return node.name() === 'shape-tick0'; 23047 })[0]; 23048 // associated tick1 23049 var ktick1 = group.getChildren(function(node) { 23050 return node.name() === 'shape-tick1'; 23051 })[0]; 23052 // associated label 23053 var klabel = group.getChildren(function(node) { 23054 return node.name() === 'label'; 23055 })[0]; 23056 // find special points 23057 var begin = group.getChildren(function(node) { 23058 return node.id() === 'begin'; 23059 })[0]; 23060 var end = group.getChildren(function(node) { 23061 return node.id() === 'end'; 23062 })[0]; 23063 // update special points 23064 switch (anchor.id()) { 23065 case 'begin': 23066 begin.x(anchor.x()); 23067 begin.y(anchor.y()); 23068 break; 23069 case 'end': 23070 end.x(anchor.x()); 23071 end.y(anchor.y()); 23072 break; 23073 } 23074 // update shape and compensate for possible drag 23075 // note: shape.position() and shape.size() won't work... 23076 var bx = begin.x() - kline.x(); 23077 var by = begin.y() - kline.y(); 23078 var ex = end.x() - kline.x(); 23079 var ey = end.y() - kline.y(); 23080 kline.points([bx, by, ex, ey]); 23081 // new line 23082 var p2d0 = new dwv.math.Point2D(begin.x(), begin.y()); 23083 var p2d1 = new dwv.math.Point2D(end.x(), end.y()); 23084 var line = new dwv.math.Line(p2d0, p2d1); 23085 // tick 23086 var p2b = new dwv.math.Point2D(bx, by); 23087 var p2e = new dwv.math.Point2D(ex, ey); 23088 var linePerp0 = dwv.math.getPerpendicularLine(line, p2b, 10); 23089 ktick0.points([linePerp0.getBegin().getX(), linePerp0.getBegin().getY(), 23090 linePerp0.getEnd().getX(), linePerp0.getEnd().getY() 23091 ]); 23092 var linePerp1 = dwv.math.getPerpendicularLine(line, p2e, 10); 23093 ktick1.points([linePerp1.getBegin().getX(), linePerp1.getBegin().getY(), 23094 linePerp1.getEnd().getX(), linePerp1.getEnd().getY() 23095 ]); 23096 // larger hitfunc 23097 kline.hitFunc(function(context) { 23098 context.beginPath(); 23099 context.moveTo(linePerp0.getBegin().getX(), linePerp0.getBegin().getY()); 23100 context.lineTo(linePerp0.getEnd().getX(), linePerp0.getEnd().getY()); 23101 context.lineTo(linePerp1.getEnd().getX(), linePerp1.getEnd().getY()); 23102 context.lineTo(linePerp1.getBegin().getX(), linePerp1.getBegin().getY()); 23103 context.closePath(); 23104 context.fillStrokeShape(this); 23105 }); 23106 // update text 23107 var quant = image.quantifyLine(line); 23108 var ktext = klabel.getText(); 23109 ktext.quant = quant; 23110 ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant)); 23111 // update position 23112 var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1; 23113 var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5; 23114 var textPos = { 23115 'x': line.getEnd().getX() + dX * 25, 23116 'y': line.getEnd().getY() + dY * 15 23117 }; 23118 klabel.position(textPos); 23119 }; 23120 23121 // namespaces 23122 var dwv = dwv || {}; 23123 dwv.tool = dwv.tool || {}; 23124 23125 /** 23126 * Scroll class. 23127 * @constructor 23128 * @param {Object} app The associated application. 23129 */ 23130 dwv.tool.Scroll = function(app) { 23131 /** 23132 * Closure to self: to be used by event handlers. 23133 * @private 23134 * @type WindowLevel 23135 */ 23136 var self = this; 23137 /** 23138 * Scroll GUI. 23139 * @type Object 23140 */ 23141 var gui = null; 23142 /** 23143 * Interaction start flag. 23144 * @type Boolean 23145 */ 23146 this.started = false; 23147 // touch timer ID (created by setTimeout) 23148 var touchTimerID = null; 23149 23150 /** 23151 * Handle mouse down event. 23152 * @param {Object} event The mouse down event. 23153 */ 23154 this.mousedown = function(event) { 23155 // stop viewer if playing 23156 if (app.getViewController().isPlaying()) { 23157 app.getViewController().stop(); 23158 } 23159 // start flag 23160 self.started = true; 23161 // first position 23162 self.x0 = event._x; 23163 self.y0 = event._y; 23164 }; 23165 23166 /** 23167 * Handle mouse move event. 23168 * @param {Object} event The mouse move event. 23169 */ 23170 this.mousemove = function(event) { 23171 if (!self.started) { 23172 return; 23173 } 23174 23175 // difference to last Y position 23176 var diffY = event._y - self.y0; 23177 var yMove = (Math.abs(diffY) > 15); 23178 // do not trigger for small moves 23179 if (yMove) { 23180 // update GUI 23181 if (diffY > 0) { 23182 app.getViewController().decrementSliceNb(); 23183 } else { 23184 app.getViewController().incrementSliceNb(); 23185 } 23186 } 23187 23188 // difference to last X position 23189 var diffX = event._x - self.x0; 23190 var xMove = (Math.abs(diffX) > 15); 23191 // do not trigger for small moves 23192 if (xMove) { 23193 // update GUI 23194 if (diffX > 0) { 23195 app.getViewController().incrementFrameNb(); 23196 } else { 23197 app.getViewController().decrementFrameNb(); 23198 } 23199 } 23200 23201 // reset origin point 23202 if (xMove) { 23203 self.x0 = event._x; 23204 } 23205 if (yMove) { 23206 self.y0 = event._y; 23207 } 23208 }; 23209 23210 /** 23211 * Handle mouse up event. 23212 * @param {Object} event The mouse up event. 23213 */ 23214 this.mouseup = function( /*event*/ ) { 23215 if (self.started) { 23216 // stop recording 23217 self.started = false; 23218 } 23219 }; 23220 23221 /** 23222 * Handle mouse out event. 23223 * @param {Object} event The mouse out event. 23224 */ 23225 this.mouseout = function(event) { 23226 self.mouseup(event); 23227 }; 23228 23229 /** 23230 * Handle touch start event. 23231 * @param {Object} event The touch start event. 23232 */ 23233 this.touchstart = function(event) { 23234 // long touch triggers the dblclick 23235 touchTimerID = setTimeout(self.dblclick, 500); 23236 // call mouse equivalent 23237 self.mousedown(event); 23238 }; 23239 23240 /** 23241 * Handle touch move event. 23242 * @param {Object} event The touch move event. 23243 */ 23244 this.touchmove = function(event) { 23245 // abort timer if move 23246 if (touchTimerID !== null) { 23247 clearTimeout(touchTimerID); 23248 touchTimerID = null; 23249 } 23250 // call mouse equivalent 23251 self.mousemove(event); 23252 }; 23253 23254 /** 23255 * Handle touch end event. 23256 * @param {Object} event The touch end event. 23257 */ 23258 this.touchend = function(event) { 23259 // abort timer 23260 if (touchTimerID !== null) { 23261 clearTimeout(touchTimerID); 23262 touchTimerID = null; 23263 } 23264 // call mouse equivalent 23265 self.mouseup(event); 23266 }; 23267 23268 /** 23269 * Handle mouse scroll event (fired by Firefox). 23270 * @param {Object} event The mouse scroll event. 23271 */ 23272 this.DOMMouseScroll = function(event) { 23273 // ev.detail on firefox is 3 23274 if (event.detail < 0) { 23275 mouseScroll(true); 23276 } else { 23277 mouseScroll(false); 23278 } 23279 }; 23280 23281 /** 23282 * Handle mouse wheel event. 23283 * @param {Object} event The mouse wheel event. 23284 */ 23285 this.mousewheel = function(event) { 23286 // ev.wheelDelta on chrome is 120 23287 if (event.wheelDelta > 0) { 23288 mouseScroll(true); 23289 } else { 23290 mouseScroll(false); 23291 } 23292 }; 23293 23294 /** 23295 * Mouse scroll action. 23296 * @param {Boolean} up True to increment, false to decrement. 23297 */ 23298 function mouseScroll(up) { 23299 var hasSlices = (app.getImage().getGeometry().getSize().getNumberOfSlices() !== 1); 23300 var hasFrames = (app.getImage().getNumberOfFrames() !== 1); 23301 if (up) { 23302 if (hasSlices) { 23303 app.getViewController().incrementSliceNb(); 23304 } else if (hasFrames) { 23305 app.getViewController().incrementFrameNb(); 23306 } 23307 } else { 23308 if (hasSlices) { 23309 app.getViewController().decrementSliceNb(); 23310 } else if (hasFrames) { 23311
23311 app.getViewController().decrementFrameNb(); 23312 } 23313 } 23314 } 23315 23316 /** 23317 * Handle key down event. 23318 * @param {Object} event The key down event. 23319 */ 23320 this.keydown = function(event) { 23321 app.onKeydown(event); 23322 }; 23323 /** 23324 * Handle double click. 23325 * @param {Object} event The key down event. 23326 */ 23327 this.dblclick = function( /*event*/ ) { 23328 app.getViewController().play(); 23329 }; 23330 23331 /** 23332 * Setup the tool GUI. 23333 */ 23334 this.setup = function() { 23335 gui = new dwv.gui.Scroll(app); 23336 gui.setup(); 23337 }; 23338 23339 /** 23340 * Enable the tool. 23341 * @param {Boolean} bool The flag to enable or not. 23342 */ 23343 this.display = function(bool) { 23344 if (gui) { 23345 gui.display(bool); 23346 } 23347 }; 23348 23349 /** 23350 * Initialise the tool. 23351 */ 23352 this.init = function() { 23353 if (app.isMonoSliceData() && app.getImage().getNumberOfFrames() === 1) { 23354 return false; 23355 } 23356 return true; 23357 }; 23358 23359 }; // Scroll class 23360 23361 /** 23362 * Help for this tool. 23363 * @return {Object} The help content. 23364 */ 23365 dwv.tool.Scroll.prototype.getHelp = function() { 23366 return { 23367 "title": dwv.i18n("tool.Scroll.name"), 23368 "brief": dwv.i18n("tool.Scroll.brief"), 23369 "mouse": { 23370 "mouse_drag": dwv.i18n("tool.Scroll.mouse_drag"), 23371 "double_click": dwv.i18n("tool.Scroll.double_click") 23372 }, 23373 "touch": { 23374 'touch_drag': dwv.i18n("tool.Scroll.touch_drag"), 23375 'tap_and_hold': dwv.i18n("tool.Scroll.tap_and_hold") 23376 } 23377 }; 23378 }; 23379 23380 // namespaces 23381 var dwv = dwv || {}; 23382 dwv.tool = dwv.tool || {}; 23383 23384 /** 23385 * Tool box. 23386 * @constructor 23387 * @param {Array} toolList The list of tool objects. 23388 * @param {Object} gui The associated gui. 23389 */ 23390 dwv.tool.Toolbox = function(toolList, app) { 23391 /** 23392 * Toolbox GUI. 23393 * @type Object 23394 */ 23395 var gui = null; 23396 /** 23397 * Selected tool. 23398 * @type Object 23399 */ 23400 var selectedTool = null; 23401 /** 23402 * Default tool name.
23403 * @type String 23404 */ 23405 var defaultToolName = null; 23406 23407 /** 23408 * Get the list of tools. 23409 * @return {Array} The list of tool objects. 23410 */ 23411 this.getToolList = function() { 23412 return toolList; 23413 }; 23414 23415 /** 23416 * Get the selected tool. 23417 * @return {Object} The selected tool. 23418 */ 23419 this.getSelectedTool = function() { 23420 return selectedTool; 23421 }; 23422 23423 /** 23424 * Setup the toolbox GUI. 23425 */ 23426 this.setup = function() { 23427 if (Object.keys(toolList).length !== 0) { 23428 gui = new dwv.gui.Toolbox(app); 23429 gui.setup(toolList); 23430 23431 for (var key in toolList) { 23432 toolList[key].setup(); 23433 } 23434 } 23435 }; 23436 23437 /** 23438 * Display the toolbox. 23439 * @param {Boolean} bool Flag to display or not. 23440 */ 23441 this.display = function(bool) { 23442 if (Object.keys(toolList).length !== 0 && gui) { 23443 gui.display(bool); 23444 } 23445 }; 23446 23447 /** 23448 * Initialise the tool box. 23449 */ 23450 this.init = function() { 23451 var keys = Object.keys(toolList); 23452 // check if we have tools 23453 if (keys.length === 0) { 23454 return; 23455 } 23456 // init all tools 23457 defaultToolName = ""; 23458 var displays = []; 23459 var display = null; 23460 for (var key in toolList) { 23461 display = toolList[key].init(); 23462 if (display && defaultToolName === "") { 23463 defaultToolName = key; 23464 } 23465 displays.push(display); 23466 } 23467 this.setSelectedTool(defaultToolName); 23468 // init html 23469 if (gui) { 23470 gui.initialise(displays); 23471 } 23472 }; 23473 23474 /** 23475 * Set the selected tool. 23476 * @return {String} The name of the tool to select. 23477 */ 23478 this.setSelectedTool = function(name) { 23479 // check if we have it 23480 if (!this.hasTool(name)) { 23481 throw new Error("Unknown tool: '" + name + "'"); 23482 } 23483 // hide last selected 23484 if (selectedTool) { 23485 selectedTool.display(false); 23486 } 23487 // enable new one 23488 selectedTool = toolList[name]; 23489 // display it 23490 selectedTool.display(true); 23491 }; 23492 23493 /** 23494 * Reset the tool box. 23495 */ 23496 this.reset = function() { 23497 // hide last selected 23498 if (selectedTool) { 23499 selectedTool.display(false); 23500 } 23501 selectedTool = null; 23502 defaultToolName = null; 23503 }; 23504 }; 23505 23506 /** 23507 * Check if a tool is in the tool list. 23508 * @param {String} name The name to check. 23509 * @return {String} The tool list element for the given name. 23510 */ 23511 dwv.tool.Toolbox.prototype.hasTool = function(name) { 23512 return this.getToolList()[name]; 23513 }; 23514 23515 // namespaces 23516 var dwv = dwv || {}; 23517 dwv.tool = dwv.tool || {}; 23518 23519 /** 23520 * UndoStack class. 23521 * @constructor 23522 */ 23523 dwv.tool.UndoStack = function(app) { 23524 /** 23525 * Undo GUI. 23526 * @type Object 23527 */ 23528 var gui = new dwv.gui.Undo(app); 23529 /** 23530 * Array of commands. 23531 * @private 23532 * @type Array 23533 */ 23534 var stack = []; 23535 23536 /** 23537 * Get the stack. 23538 * @return {Array} The list of stored commands. 23539 */
23540 this.getStack = function() { return stack; }; 23541 23542 /** 23543 * Current command index. 23544 * @private 23545 * @type Number 23546 */ 23547 var curCmdIndex = 0; 23548 23549 /** 23550 * Add a command to the stack. 23551 * @param {Object} cmd The command to add. 23552 */ 23553 this.add = function(cmd) { 23554 // clear commands after current index 23555 stack = stack.slice(0, curCmdIndex); 23556 // store command 23557 stack.push(cmd); 23558 //stack[curCmdIndex] = cmd; 23559 // increment index 23560 ++curCmdIndex; 23561 // add command to display history 23562 gui.addCommandToUndoHtml(cmd.getName()); 23563 }; 23564 23565 /** 23566 * Undo the last command. 23567 */ 23568 this.undo = function() { 23569 // a bit inefficient... 23570 if (curCmdIndex > 0) { 23571 // decrement command index 23572 --curCmdIndex; 23573 // undo last command 23574 stack[curCmdIndex].undo(); 23575 // disable last in display history 23576 gui.enableInUndoHtml(false); 23577 } 23578 }; 23579 23580 /** 23581 * Redo the last command. 23582 */ 23583 this.redo = function() { 23584 if (curCmdIndex < stack.length) { 23585 // run last command 23586 stack[curCmdIndex].execute(); 23587 // increment command index 23588 ++curCmdIndex; 23589 // enable next in display history 23590 gui.enableInUndoHtml(true); 23591 } 23592 }; 23593 23594 /** 23595 * Setup the tool GUI. 23596 */ 23597 this.setup = function() { 23598 gui.setup(); 23599 }; 23600 23601 /** 23602 * Initialise the tool GUI. 23603 */ 23604 this.initialise = function() { 23605 gui.initialise(); 23606 }; 23607 23608 }; // UndoStack class 23609 23610 // namespaces 23611 var dwv = dwv || {}; 23612 dwv.tool = dwv.tool || {}; 23613 23614 /** 23615 * WindowLevel tool: handle window/level related events. 23616 * @constructor 23617 * @param {Object} app The associated application. 23618 */ 23619 dwv.tool.WindowLevel = function(app) { 23620 /** 23621 * Closure to self: to be used by event handlers. 23622 * @private 23623 * @type WindowLevel 23624 */ 23625 var self = this; 23626 /** 23627 * WindowLevel GUI. 23628 * @type Object 23629 */ 23630 var gui = null; 23631 /** 23632 * Interaction start flag. 23633 * @type Boolean 23634 */ 23635 this.started = false; 23636 23637 /** 23638 * Handle mouse down event. 23639 * @param {Object} event The mouse down event. 23640 */ 23641 this.mousedown = function(event) { 23642 // set start flag 23643 self.started = true; 23644 // store initial position 23645 self.x0 = event._x; 23646 self.y0 = event._y; 23647 // update GUI 23648 app.getViewController().setCurrentPosition2D(event._x, event._y); 23649 }; 23650 23651 /** 23652 * Handle mouse move event. 23653 * @param {Object} event The mouse move event. 23654 */ 23655 this.mousemove = function(event) { 23656 // check start flag 23657 if (!self.started) { 23658 return; 23659 } 23660 // difference to last position 23661 var diffX = event._x - self.x0; 23662 var diffY = self.y0 - event._y; 23663 // calculate new window level 23664 var windowCenter = parseInt(app.getViewController().getWindowLevel().center, 10) + diffY; 23665 var windowWidth = parseInt(app.getViewController().getWindowLevel().width, 10) + diffX; 23666 23667 // add the manual preset to the view 23668 app.getViewController().addWindowLevelPresets({ 23669 "manual": { 23670 "wl": new dwv.image.WindowLevel(windowCenter, windowWidth), 23671 "name": "manual" 23672 } 23673 }); 23674 app.getViewController().setWindowLevelPreset("manual"); 23675 23676 // update gui 23677 if (gui) { 23678 // initialise to add the manual preset 23679 gui.initialise(); 23680 // set selected preset 23681 dwv.gui.setSelected(app.getElement("presetSelect"), "manual"); 23682 } 23683 23684 // store position 23685 self.x0 = event._x; 23686 self.y0 = event._y; 23687 }; 23688 23689 /** 23690 * Handle mouse up event. 23691 * @param {Object} event The mouse up event. 23692 */ 23693 this.mouseup = function( /*event*/ ) { 23694 // set start flag 23695 if (self.started) { 23696 self.started = false; 23697 } 23698 }; 23699 23700 /** 23701 * Handle mouse out event. 23702 * @param {Object} event The mouse out event. 23703 */ 23704 this.mouseout = function(event) { 23705 // treat as mouse up 23706 self.mouseup(event); 23707 }; 23708 23709 /** 23710 * Handle touch start event. 23711 * @param {Object} event The touch start event. 23712 */ 23713 this.touchstart = function(event) { 23714 self.mousedown(event); 23715 }; 23716 23717 /** 23718 * Handle touch move event. 23719 * @param {Object} event The touch move event. 23720 */ 23721 this.touchmove = function(event) { 23722 self.mousemove(event); 23723 }; 23724 23725 /** 23726 * Handle touch end event. 23727 * @param {Object} event The touch end event. 23728 */ 23729 this.touchend = function(event) { 23730 self.mouseup(event); 23731 }; 23732 23733 /** 23734 * Handle double click event. 23735 * @param {Object} event The double click event. 23736 */ 23737 this.dblclick = function(event) { 23738 // update GUI 23739 app.getViewController().setWindowLevel( 23740 parseInt(app.getImage().getRescaledValue( 23741 event._x, event._y, app.getViewController().getCurrentPosition().k), 10), 23742 parseInt(app.getViewController().getWindowLevel().width, 10)); 23743 }; 23744 23745 /** 23746 * Handle key down event. 23747 * @param {Object} event The key down event. 23748 */ 23749 this.keydown = function(event) { 23750 // let the app handle it 23751 app.onKeydown(event); 23752 }; 23753 23754 /** 23755 * Setup the tool GUI. 23756 */ 23757 this.setup = function() { 23758 gui = new dwv.gui.WindowLevel(app); 23759 gui.setup(); 23760 }; 23761 23762 /**
23763 * Display the tool. 23764 * @param {Boolean} bool The flag to display or not. 23765 */ 23766 this.display = function(bool) { 23767 if (gui) { 23768 if (app.getImage().getPhotometricInterpretation().match(/MONOCHROME/) !== null) { 23769 gui.display(bool); 23770 } else { 23771 gui.display(false); 23772 } 23773 } 23774 }; 23775 23776 /** 23777 * Initialise the tool. 23778 */ 23779 this.init = function() { 23780 if (gui) { 23781 gui.initialise(); 23782 } 23783 return true; 23784 }; 23785 }; // WindowLevel class 23786 23787 /** 23788 * Help for this tool. 23789 * @return {Object} The help content. 23790 */ 23791 dwv.tool.WindowLevel.prototype.getHelp = function() { 23792 return { 23793 "title": dwv.i18n("tool.WindowLevel.name"), 23794 "brief": dwv.i18n("tool.WindowLevel.brief"), 23795 "mouse": { 23796 "mouse_drag": dwv.i18n("tool.WindowLevel.mouse_drag"), 23797 "double_click": dwv.i18n("tool.WindowLevel.double_click") 23798 }, 23799 "touch": { 23800 "touch_drag": dwv.i18n("tool.WindowLevel.touch_drag") 23801 } 23802 }; 23803 }; 23804 23805 // namespaces 23806 var dwv = dwv || {}; 23807 dwv.tool = dwv.tool || {}; 23808 23809 /** 23810 * ZoomAndPan class. 23811 * @constructor 23812 * @param {Object} app The associated application. 23813 */ 23814 dwv.tool.ZoomAndPan = function(app) { 23815 /** 23816 * Closure to self: to be used by event handlers. 23817 * @private 23818 * @type Object 23819 */ 23820 var self = this; 23821 /** 23822 * ZoomAndPan GUI. 23823 * @type Object 23824 */ 23825 var gui = null; 23826 /** 23827 * Interaction start flag. 23828 * @type Boolean 23829 */ 23830 this.started = false; 23831 23832 /** 23833 * Handle mouse down event. 23834 * @param {Object} event The mouse down event. 23835 */ 23836 this.mousedown = function(event) { 23837 self.started = true; 23838 // first position 23839 self.x0 = event._xs; 23840 self.y0 = event._ys; 23841 }; 23842 23843 /** 23844 * Handle two touch down event. 23845 * @param {Object} event The touch down event. 23846 */ 23847 this.twotouchdown = function(event) { 23848 self.started = true; 23849 // store first point 23850 self.x0 = event._x; 23851 self.y0 = event._y; 23852 // first line 23853 var point0 = new dwv.math.Point2D(event._x, event._y); 23854 var point1 = new dwv.math.Point2D(event._x1, event._y1); 23855 self.line0 = new dwv.math.Line(point0, point1); 23856 self.midPoint = self.line0.getMidpoint(); 23857 }; 23858 23859 /** 23860 * Handle mouse move event. 23861 * @param {Object} event The mouse move event. 23862 */ 23863 this.mousemove = function(event) { 23864 if (!self.started) { 23865 return; 23866 } 23867 // calculate translation 23868 var tx = event._xs - self.x0; 23869 var ty = event._ys - self.y0; 23870 // apply translation 23871 //app.translate(tx, ty); 23872 app.stepTranslate(tx, ty); 23873 // reset origin point 23874 self.x0 = event._xs; 23875 self.y0 = event._ys; 23876 }; 23877 23878 /** 23879 * Handle two touch move event. 23880 * @param {Object} event The touch move event. 23881 */ 23882 this.twotouchmove = function(event) { 23883 if (!self.started) { 23884 return; 23885 } 23886 var point0 = new dwv.math.Point2D(event._x, event._y); 23887 var point1 = new dwv.math.Point2D(event._x1, event._y1); 23888 var newLine = new dwv.math.Line(point0, point1); 23889 var lineRatio = newLine.getLength() / self.line0.getLength(); 23890 23891 if (lineRatio === 1) { 23892 // scroll mode 23893 // difference to last position 23894 var diffY = event._y - self.y0; 23895 // do not trigger for small moves 23896 if (Math.abs(diffY) < 15) { 23897 return; 23898 } 23899 // update GUI 23900 if (diffY > 0) { 23901 app.getViewController().incrementSliceNb(); 23902 } else { 23903 app.getViewController().decrementSliceNb(); 23904 } 23905 } else { 23906 // zoom mode 23907 var zoom = (lineRatio - 1) / 2; 23908 if (Math.abs(zoom) % 0.1 <= 0.05) { 23909 app.stepZoom(zoom, event._xs, event._ys); 23910 } 23911 } 23912 }; 23913 23914 /** 23915 * Handle mouse up event. 23916 * @param {Object} event The mouse up event. 23917 */ 23918 this.mouseup = function( /*event*/ ) { 23919 if (self.started) { 23920 // stop recording 23921 self.started = false; 23922 } 23923 }; 23924 23925 /** 23926 * Handle mouse out event. 23927 * @param {Object} event The mouse out event. 23928 */ 23929 this.mouseout = function(event) { 23930 self.mouseup(event); 23931 }; 23932 23933 /** 23934 * Handle touch start event. 23935 * @param {Object} event The touch start event. 23936 */ 23937 this.touchstart = function(event) { 23938 var touches = event.targetTouches; 23939 if (touches.length === 1) { 23940 self.mousedown(event); 23941 } else if (touches.length === 2) { 23942 self.twotouchdown(event); 23943 } 23944 }; 23945 23946 /** 23947 * Handle touch move event. 23948 * @param {Object} event The touch move event. 23949 */ 23950 this.touchmove = function(event) { 23951 var touches = event.targetTouches; 23952 if (touches.length === 1) { 23953 self.mousemove(event); 23954 } else if (touches.length === 2) { 23955 self.twotouchmove(event); 23956 } 23957 }; 23958 23959 /** 23960 * Handle touch end event. 23961 * @param {Object} event The touch end event. 23962 */ 23963 this.touchend = function(event) { 23964 self.mouseup(event); 23965 }; 23966 23967 /** 23968 * Handle mouse scroll event (fired by Firefox). 23969 * @param {Object} event The mouse scroll event. 23970 */ 23971 this.DOMMouseScroll = function(event) { 23972 // ev.detail on firefox is 3 23973 var step = -event.detail / 30; 23974 app.stepZoom(step, event._xs, event._ys); 23975 }; 23976 23977 /** 23978 * Handle mouse wheel event. 23979 * @param {Object} event The mouse wheel event. 23980 */ 23981 this.mousewheel = function(event) { 23982 // ev.wheelDelta on chrome is 120 23983 var step = event.wheelDelta / 1200; 23984 app.stepZoom(step, event._xs, event._ys); 23985 }; 23986 23987 /** 23988 * Handle key down event. 23989 * @param {Object} event The key down event. 23990 */ 23991 this.keydown = function(event) { 23992 app.onKeydown(event); 23993 }; 23994 23995 /** 23996 * Setup the tool GUI. 23997 */ 23998 this.setup = function() { 23999 gui = new dwv.gui.ZoomAndPan(app); 24000 gui.setup(); 24001 }; 24002 24003 /** 24004 * Enable the tool. 24005 * @param {Boolean} bool The flag to enable or not. 24006 */ 24007 this.display = function(bool) { 24008 if (gui) { 24009 gui.display(bool); 24010 } 24011 }; 24012 24013 }; // ZoomAndPan class 24014 24015 /** 24016 * Help for this tool. 24017 * @return {Object} The help content. 24018 */ 24019 dwv.tool.ZoomAndPan.prototype.getHelp = function() { 24020 return { 24021 "title": dwv.i18n("tool.ZoomAndPan.name"), 24022 "brief": dwv.i18n("tool.ZoomAndPan.brief"), 24023 "mouse": { 24024 "mouse_wheel": dwv.i18n("tool.ZoomAndPan.mouse_wheel"), 24025 "mouse_drag": dwv.i18n("tool.ZoomAndPan.mouse_drag") 24026 }, 24027 "touch": { 24028 'twotouch_pinch': dwv.i18n("tool.ZoomAndPan.twotouch_pinch"), 24029 'touch_drag': dwv.i18n("tool.ZoomAndPan.touch_drag") 24030 } 24031 }; 24032 }; 24033 24034 /** 24035 * Initialise the tool. 24036 */ 24037 dwv.tool.ZoomAndPan.prototype.init = function() { 24038 return true; 24039 }; 24040 24041 // namespaces 24042 var dwv = dwv || {}; 24043 /** @namespace */ 24044 dwv.browser = dwv.browser || {}; 24045 // external 24046 var Modernizr = Modernizr || {}; 24047 24048 /** 24049 * Browser check for the FileAPI. 24050 * Assume support for Safari5. 24051 */ 24052 dwv.browser.hasFileApi = function() { 24053 // regular test does not work on Safari 5 24054 var isSafari5 = (navigator.appVersion.indexOf("Safari") !== -1) && 24055 (navigator.appVersion.indexOf("Chrome") === -1) &&
24056 ((navigator.appVersion.indexOf("5.0.") !== -1) || 24057 (navigator.appVersion.indexOf("5.1.") !== -1)); 24058 if (isSafari5) { 24059 console.warn("Assuming FileAPI support for Safari5..."); 24060 return true; 24061 } 24062 // regular test 24063 return Modernizr.filereader; 24064 }; 24065 24066 /** 24067 * Browser check for the XMLHttpRequest. 24068 */ 24069 dwv.browser.hasXmlHttpRequest = function() { 24070 return Modernizr.xhrresponsetype && 24071 Modernizr.xhrresponsetypearraybuffer && Modernizr.xhrresponsetypetext && 24072 "XMLHttpRequest" in window && "withCredentials" in new XMLHttpRequest(); 24073 }; 24074 24075 /** 24076 * Browser check for typed array. 24077 */ 24078 dwv.browser.hasTypedArray = function() { 24079 return Modernizr.dataview && Modernizr.typedarrays; 24080 }; 24081 24082 /** 24083 * Browser check for input with type='color'. 24084 * Missing in IE and Safari. 24085 */ 24086 dwv.browser.hasInputColor = function() { 24087 return Modernizr.inputtypes.color; 24088 }; 24089 24090 /** 24091 * Browser check for input with type='files' and webkitdirectory flag. 24092 * Missing in IE and Safari. 24093 */ 24094 dwv.browser.hasInputDirectory = function() { 24095 return Modernizr.fileinputdirectory; 24096 }; 24097 24098 24099 //only check at startup (since we propose a replacement) 24100 dwv.browser._hasTypedArraySlice = (typeof Uint8Array.prototype.slice !== "undefined"); 24101 24102 /** 24103 * Browser check for typed array slice method. 24104 * Missing in Internet Explorer 11. 24105 */ 24106 dwv.browser.hasTypedArraySlice = function() { 24107 return dwv.browser._hasTypedArraySlice; 24108 }; 24109 24110 // only check at startup (since we propose a replacement) 24111 dwv.browser._hasFloat64Array = ("Float64Array" in window); 24112 24113 /** 24114 * Browser check for Float64Array array. 24115 * Missing in PhantomJS 1.9.20 (on Travis). 24116 */ 24117 dwv.browser.hasFloat64Array = function() { 24118 return dwv.browser._hasFloat64Array; 24119 }; 24120 24121 //only check at startup (since we propose a replacement) 24122 dwv.browser._hasClampedArray = ("Uint8ClampedArray" in window); 24123 24124 /** 24125 * Browser check for clamped array. 24126 * Missing in: 24127 * - Safari 5.1.7 for Windows 24128 * - PhantomJS 1.9.20 (on Travis). 24129 */ 24130 dwv.browser.hasClampedArray = function() { 24131 return dwv.browser._hasClampedArray; 24132 }; 24133 24134 /** 24135 * Browser checks to see if it can run dwv. Throws an error if not. 24136 * Silently replaces basic functions. 24137 */ 24138 dwv.browser.check = function() { 24139 24140 // Required -------------- 24141 24142 var appnorun = "The application cannot be run."; 24143 var message = ""; 24144 // Check for the File API support 24145 if (!dwv.browser.hasFileApi()) { 24146 message = "The File APIs are not supported in this browser. "; 24147 alert(message + appnorun); 24148 throw new Error(message); 24149 } 24150 // Check for XMLHttpRequest 24151 if (!dwv.browser.hasXmlHttpRequest()) { 24152 message = "The XMLHttpRequest is not supported in this browser. "; 24153 alert(message + appnorun); 24154 throw new Error(message); 24155 } 24156 // Check typed array 24157 if (!dwv.browser.hasTypedArray()) { 24158 message = "The Typed arrays are not supported in this browser. "; 24159 alert(message + appnorun); 24160 throw new Error(message); 24161 } 24162 24163 // Replaced if not present ------------ 24164 24165 // Check typed array slice 24166 if (!dwv.browser.hasTypedArraySlice()) { 24167 // silent fail with warning 24168 console.warn("The TypedArray.slice method is not supported in this browser. This may impair performance. "); 24169 // basic Uint16Array implementation 24170 Uint16Array.prototype.slice = function(begin, end) { 24171 var size = end - begin; 24172 var cloned = new Uint16Array(size); 24173 for (var i = 0; i < size; i++) { 24174 cloned[i] = this[begin + i]; 24175 } 24176 return cloned; 24177 }; 24178 // basic Int16Array implementation 24179 Int16Array.prototype.slice = function(begin, end) { 24180 var size = end - begin; 24181 var cloned = new Int16Array(size); 24182 for (var i = 0; i < size; i++) { 24183 cloned[i] = this[begin + i]; 24184 } 24185 return cloned; 24186 }; 24187 // basic Uint8Array implementation 24188 Uint8Array.prototype.slice = function(begin, end) { 24189 var size = end - begin; 24190 var cloned = new Uint8Array(size); 24191 for (var i = 0; i < size; i++) { 24192 cloned[i] = this[begin + i]; 24193 } 24194 return cloned; 24195 }; 24196 // basic Int8Array implementation 24197 Int8Array.prototype.slice = function(begin, end) { 24198 var size = end - begin; 24199 var cloned = new Int8Array(size); 24200 for (var i = 0; i < size; i++) { 24201 cloned[i] = this[begin + i]; 24202 } 24203 return cloned; 24204 }; 24205 } 24206 // check clamped array 24207 if (!dwv.browser.hasClampedArray()) { 24208 // silent fail with warning 24209 console.warn("The Uint8ClampedArray is not supported in this browser. This may impair performance. "); 24210 // Use Uint8Array instead... Not good 24211 // TODO Find better replacement! 24212 window.Uint8ClampedArray = window.Uint8Array; 24213 } 24214 // check Float64 array 24215 if (!dwv.browser.hasFloat64Array()) { 24216 // silent fail with warning 24217 console.warn("The Float64Array is not supported in this browser. This may impair performance. "); 24218 // Use Float32Array instead... Not good 24219 // TODO Find better replacement! 24220 window.Float64Array = window.Float32Array; 24221 } 24222 }; 24223 24224 // namespaces 24225 var dwv = dwv || {}; 24226 // external 24227 var i18next = i18next || {}; 24228 var i18nextXHRBackend = i18nextXHRBackend || {}; 24229 var i18nextBrowserLanguageDetector = i18nextBrowserLanguageDetector || {}; 24230 24231 // This is mainly a wrapper around the i18next object. 24232 // see its API: http://i18next.com/docs/api/ 24233 24234 // global locales path 24235 dwv.i18nLocalesPath = null; 24236 24237 /** 24238 * Initialise i18n. 24239 * @param {String} language The language to translate to. Defaults to 'auto' and 24240 * gets the language from the browser. 24241 * @param {String} localesPath Path to the locales directory. 24242 * @external i18next 24243 * @external i18nextXHRBackend 24244 * @external i18nextBrowserLanguageDetector 24245 */ 24246 dwv.i18nInitialise = function(language, localesPath) { 24247 var lng = (typeof language === "undefined") ? "auto" : language; 24248 var lpath = (typeof localesPath === "undefined") ? "../.." : localesPath; 24249 // store as global 24250 dwv.i18nLocalesPath = lpath; 24251 // i18n options: default 'en' language and 24252 // only load language, not specialised (for ex en-GB) 24253 var options = { 24254 fallbackLng: "en", 24255 load: "languageOnly", 24256 backend: { loadPath: lpath + "/locales/{{lng}}/{{ns}}.json" } 24257 }; 24258 // use the XHR backend to get translation files 24259 var i18n = i18next.use(i18nextXHRBackend); 24260 // use browser language or the specified one 24261 if (lng === "auto") { 24262 i18n.use(i18nextBrowserLanguageDetector); 24263 } else { 24264 options.lng = lng; 24265 } 24266 // init i18n: will be ready when the 'loaded' event is fired 24267 i18n.init(options); 24268 }; 24269 24270 /** 24271 * Initialise i18n with recources as input. 24272 * @param {String} language The language to translate to. Defaults to 'auto' and 24273 * gets the language from the browser. 24274 * @param {Object} resources Languages provided as object. 24275 * @external i18next 24276 * @external i18nextBrowserLanguageDetector 24277 */ 24278 dwv.i18nInitialiseWithResources = function(language, resources) { 24279 var lng = (typeof language === "undefined") ? "auto" : language; 24280 // i18n options: default 'en' language and 24281 // only load language, not specialised (for ex en-GB) 24282 var options = { 24283 fallbackLng: "en", 24284 load: "languageOnly", 24285 resources: resources 24286 }; 24287 // use browser language or the specified one 24288 // init i18n: will be ready when the 'loaded' event is fired 24289 if (lng === "auto") { 24290 var i18n = i18next.use(i18nextBrowserLanguageDetector); 24291 i18n.init(options); 24292 } else { 24293 options.lng = lng; 24294 i18next.init(options); 24295 } 24296 }; 24297 24298 /** 24299 * Handle i18n 'initialized' event. 24300 * @param {Object} callback The callback function to call when i18n is initialised. 24301 * It can take one argument that will be replaced with the i18n options. 24302 * @external i18next 24303 */
24304 dwv.i18nOnInitialised = function(callback) { 24305 i18next.on('initialized', callback); 24306 }; 24307 24308 /** 24309 * Stop handling i18n load event. 24310 * @external i18next 24311 */ 24312 dwv.i18nOffInitialised = function() { 24313 i18next.off('initialized'); 24314 }; 24315 24316 /** 24317 * Handle i18n failed load event. 24318 * @param {Object} callback The callback function to call when i18n is loaded. 24319 * It can take three arguments: lng, ns and msg. 24320 * @external i18next 24321 */ 24322 dwv.i18nOnFailedLoad = function(callback) { 24323 i18next.on('failedLoading', callback); 24324 }; 24325 24326 /** 24327 * Stop handling i18n failed load event. 24328 * @external i18next 24329 */ 24330 dwv.i18nOffFailedLoad = function() { 24331 i18next.off('failedLoading'); 24332 }; 24333 24334 /** 24335 * Get the translated text. 24336 * @param {String} key The key to the text entry. 24337 * @param {Object} options The translation options such as plural, context... 24338 * @external i18next 24339 */ 24340 dwv.i18n = function(key, options) { 24341 return i18next.t(key, options); 24342 }; 24343 24344 /** 24345 * Check the existence of a translation. 24346 * @param {String} key The key to the text entry. 24347 * @param {Object} options The translation options such as plural, context... 24348 * @external i18next 24349 */ 24350 dwv.i18nExists = function(key, options) { 24351 return i18next.exists(key, options); 24352 }; 24353 24354 /** 24355 * Translate all data-i18n tags in the current html page. If an html tag defines the 24356 * data-i18n attribute, its value will be used as key to find its corresponding text 24357 * and will replace the content of the html tag. 24358 */ 24359 dwv.i18nPage = function() { 24360 // get all elements 24361 var elements = document.getElementsByTagName("*"); 24362 // if the element defines data-i18n, replace its content with the tranlation 24363 for (var i = 0; i < elements.length; ++i) { 24364 if (typeof elements[i].dataset.i18n !== "undefined") { 24365 elements[i].innerHTML = dwv.i18n(elements[i].dataset.i18n); 24366 } 24367 } 24368 }; 24369 24370 /** 24371 * Get the current locale resource path. 24372 * Warning: to be used once i18next is initialised. 24373 * @return {String} The path to the locale resource. 24374 */ 24375 dwv.i18nGetLocalePath = function(filename) { 24376 var lng = i18next.language.substr(0, 2); 24377 return dwv.i18nLocalesPath + 24378 "/locales/" + lng + "/" + filename; 24379 }; 24380 24381 /** 24382 * Get the current locale resource path. 24383 * Warning: to be used once i18next is initialised. 24384 * @return {String} The path to the locale resource. 24385 */ 24386 dwv.i18nGetFallbackLocalePath = function(filename) { 24387 var lng = i18next.languages[i18next.languages.length - 1].substr(0, 2); 24388 return dwv.i18nLocalesPath + 24389 "/locales/" + lng + "/" + filename; 24390 }; 24391 24392 // namespaces 24393 var dwv = dwv || {}; 24394 dwv.utils = dwv.utils || {}; 24395 24396 /** 24397 * ListenerHandler class: handles add/removing and firing listeners. 24398 * @constructor 24399 */ 24400 dwv.utils.ListenerHandler = function() { 24401 /** 24402 * listeners. 24403 * @private 24404 */ 24405 var listeners = {}; 24406 24407 /** 24408 * Add an event listener. 24409 * @param {String} type The event type. 24410 * @param {Object} callback The method associated with the provided event type, 24411 * will be called with the fired event. 24412 */ 24413 this.add = function(type, callback) { 24414 // create array if not present 24415 if (typeof listeners[type] === "undefined") { 24416 listeners[type] = []; 24417 } 24418 // add callback to listeners array 24419 listeners[type].push(callback); 24420 }; 24421 24422 /** 24423 * Remove an event listener. 24424 * @param {String} type The event type. 24425 * @param {Object} callback The method associated with the provided event type. 24426 */ 24427 this.remove = function(type, callback) { 24428 // check if the type is present 24429 if (typeof listeners[type] === "undefined") { 24430 return; 24431 } 24432 // remove from listeners array 24433 for (var i = 0; i < listeners[type].length; ++i) { 24434 if (listeners[type][i] === callback) { 24435 listeners[type].splice(i, 1); 24436 } 24437 } 24438 }; 24439 24440 /** 24441 * Fire an event: call all associated listeners with the input event object. 24442 * @param {Object} event The event to fire. 24443 */ 24444 this.fireEvent = function(event) { 24445 // check if they are listeners for the event type 24446 if (typeof listeners[event.type] === "undefined") { 24447 return; 24448 } 24449 // fire events 24450 for (var i = 0; i < listeners[event.type].length; ++i) { 24451 listeners[event.type][i](event); 24452 } 24453 }; 24454 }; 24455 24456 // namespaces 24457 var dwv = dwv || {}; 24458 dwv.utils = dwv.utils || {}; 24459 24460 /** 24461 * Multiple progresses handler. 24462 * Stores a multi dimensional list of progresses to allow to 24463 * calculate a global progress. 24464 * @param {Function} callback The function to pass the global progress to. 24465 */ 24466 dwv.utils.MultiProgressHandler = function(callback) { 24467 // closure to self 24468 var self = this; 24469 24470 /** 24471 * List of progresses. 24472 * @private 24473 * @type Array 24474 */ 24475 var progresses = []; 24476 24477 /** 24478 * Number of dimensions. 24479 * @private 24480 * @type Number 24481 */ 24482 var numberOfDimensions = 2; 24483 24484 /** 24485 * Set the number of dimensions. 24486 * @param {Number} num The number. 24487 */ 24488 this.setNumberOfDimensions = function(num) { 24489 numberOfDimensions = num; 24490 }; 24491 24492 /** 24493 * Set the number of data to load. 24494 * @param {Number} n The number of data to load. 24495 */ 24496 this.setNToLoad = function(n) { 24497 for (var i = 0; i < n; ++i) { 24498 progresses[i] = []; 24499 for (var j = 0; j < numberOfDimensions; ++j) { 24500 progresses[i][j] = 0; 24501 } 24502 } 24503 }; 24504 24505 /** 24506 * Handle a load progress. 24507 * @param {Object} event The progress event. 24508 */ 24509 this.onprogress = function(event) { 24510 // check event 24511 if (!event.lengthComputable) { 24512 return; 24513 } 24514 if (typeof event.subindex === "undefined") { 24515 return; 24516 } 24517 if (typeof event.index === "undefined") { 24518 return; 24519 } 24520 // calculate percent 24521 var percent = (event.loaded * 100) / event.total; 24522 // set percent for index 24523 progresses[event.index][event.subindex] = percent; 24524 24525 // call callback 24526 callback({ 24527 'type': event.type, 24528 'lengthComputable': true, 24529 'loaded': getGlobalPercent(), 24530 'total': 100 24531 }); 24532 }; 24533 24534 /** 24535 * Get the global load percent including the provided one. 24536 * @return {Number} The accumulated percentage. 24537 */ 24538 function getGlobalPercent() { 24539 var sum = 0; 24540 var lenprog = progresses.length; 24541 for (var i = 0; i < lenprog; ++i) { 24542 for (var j = 0; j < numberOfDimensions; ++j) { 24543 sum += progresses[i][j];
24544 } 24545 } 24546 return Math.round(sum / (lenprog * numberOfDimensions)); 24547 } 24548 24549 /** 24550 * Create a mono progress event handler. 24551 * @param {Number} index The index of the data. 24552 * @param {Number} subindex The sub-index of the data. 24553 */ 24554 this.getMonoProgressHandler = function(index, subindex) { 24555 return function(event) { 24556 event.index = index; 24557 event.subindex = subindex; 24558 self.onprogress(event); 24559 }; 24560 }; 24561 24562 /** 24563 * Create a mono loadend event handler: sends a 100% progress. 24564 * @param {Number} index The index of the data. 24565 * @param {Number} subindex The sub-index of the data. 24566 */ 24567 this.getMonoOnLoadEndHandler = function(index, subindex) { 24568 return function() { 24569 self.onprogress({ 24570 'type': 'load-progress', 24571 'lengthComputable': true, 24572 'loaded': 100, 24573 'total': 100, 24574 'index': index, 24575 'subindex': subindex 24576 }); 24577 }; 24578 }; 24579 24580 /** 24581 * Create a mono progress event handler with an undefined index. 24582 * Warning: The caller handles the progress index. 24583 * @param {Number} subindex The sub-index of the data. 24584 */ 24585 this.getUndefinedMonoProgressHandler = function(subindex) { 24586 return function(event) { 24587 event.subindex = subindex; 24588 self.onprogress(event); 24589 }; 24590 }; 24591 }; 24592 24593 // namespaces 24594 var dwv = dwv || {}; 24595 /** @namespace */ 24596 dwv.utils = dwv.utils || {}; 24597 24598 /** 24599 * Capitalise the first letter of a string. 24600 * @param {String} string The string to capitalise the first letter. 24601 * @return {String} The new string. 24602 */ 24603 dwv.utils.capitaliseFirstLetter = function(string) { 24604 var res = string; 24605 if (string) { 24606 res = string.charAt(0).toUpperCase() + string.slice(1); 24607 } 24608 return res; 24609 }; 24610 24611 /** 24612 * Split key/value string: 24613 * key0=val00&key0=val01&key1=val10 returns 24614 * { key0 : [val00, val01], key1 : val1 } 24615 * @param {String} inputStr The string to split. 24616 * @return {Object} The split string. 24617 */ 24618 dwv.utils.splitKeyValueString = function(inputStr) { 24619 // result 24620 var result = {}; 24621 // check input string 24622 if (inputStr) { 24623 // split key/value pairs 24624 var pairs = inputStr.split('&'); 24625 for (var i = 0; i < pairs.length; ++i) { 24626 var pair = pairs[i].split('='); 24627 // if the key does not exist, create it 24628 if (!result[pair[0]]) { 24629 result[pair[0]] = pair[1]; 24630 } else { 24631 // make it an array 24632 if (!(result[pair[0]] instanceof Array)) { 24633 result[pair[0]] = [result[pair[0]]]; 24634 } 24635 result[pair[0]].push(pair[1]); 24636 } 24637 } 24638 } 24639 return result; 24640 }; 24641 24642 /** 24643 * Replace flags in a input string. Flags are keywords surrounded with curly 24644 * braces. 24645 * @param {String} inputStr The input string. 24646 * @param {Object} values A object of {value, unit}. 24647 * @example 24648 * var values = {"length": { "value": 33, "unit": "cm" } }; 24649 * var str = "The length is: {length}."; 24650 * var res = dwv.utils.replaceFlags(str, values); // "The length is: 33 cm." 24651 * @return {String} The result string. 24652 */ 24653 dwv.utils.replaceFlags = function(inputStr, values) { 24654 var res = ""; 24655 // check input string 24656 if (inputStr === null || typeof inputStr === "undefined") { 24657 return res; 24658 } 24659 res = inputStr; 24660 // check values 24661 if (values === null || typeof values === "undefined") { 24662 return res; 24663 } 24664 // loop through values keys 24665 var keys = Object.keys(values); 24666 for (var i = 0; i < keys.length; ++i) { 24667 var valueObj = values[keys[i]]; 24668 if (valueObj !== null && typeof valueObj !== "undefined" && 24669 valueObj.value !== null && typeof valueObj.value !== "undefined") { 24670 // value string 24671 var valueStr = valueObj.value.toPrecision(4); 24672 // add unit if available 24673 // space or no space? Yes apart from degree... 24674 // check: https://en.wikipedia.org/wiki/Space_(punctuation)#Spaces_and_unit_symbols 24675 if (valueObj.unit !== null && typeof valueObj.unit !== "undefined" && 24676 valueObj.unit.length !== 0) { 24677 if (valueObj.unit !== "degree") { 24678 valueStr += " "; 24679 } 24680 valueStr += valueObj.unit; 24681 } 24682 // flag to replace 24683 var flag = '{' + keys[i] + '}'; 24684 // replace 24685 res = res.replace(flag, valueStr); 24686 } 24687 } 24688 // return 24689 return res; 24690 }; 24691 24692 /** 24693 * Replace flags in a input string. Flags are keywords surrounded with curly 24694 * braces. 24695 * @param {String} inputStr The input string. 24696 * @param {Array}
24696 values An array of strings. 24697 * @example 24698 * var values = ["a", "b"]; 24699 * var str = "The length is: {v0}. The size is: {v1}"; 24700 * var res = dwv.utils.replaceFlags2(str, values); // "The length is: a. The size is: b" 24701 * @return {String} The result string. 24702 */ 24703 dwv.utils.replaceFlags2 = function(inputStr, values) { 24704 var res = inputStr; 24705 for (var j = 0; j < values.length; ++j) { 24706 res = res.replace("{v" + j + "}", values[j]); 24707 } 24708 return res; 24709 }; 24710 24711 dwv.utils.createDefaultReplaceFormat = function(values) { 24712 var res = ""; 24713 for (var j = 0; j < values.length; ++j) { 24714 if (j !== 0) { 24715 res += ", "; 24716 } 24717 res += "{v" + j + "}"; 24718 } 24719 return res; 24720 }; 24721 24722 // namespaces 24723 var dwv = dwv || {}; 24724 dwv.utils = dwv.utils || {}; 24725 24726 /** 24727 * Thread Pool. 24728 * Highly inspired from {@link http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool}. 24729 * @constructor 24730 * @param {Number} poolSize The size of the pool. 24731 */ 24732 dwv.utils.ThreadPool = function(poolSize) { 24733 24734 // task queue 24735 var taskQueue = []; 24736 // lsit of available threads 24737 var freeThreads = []; 24738 // list of running threads (unsed in abort) 24739 var runningThreads = []; 24740 24741 /** 24742 * Initialise. 24743 */ 24744 this.init = function() { 24745 // create 'poolSize' number of worker threads 24746 for (var i = 0; i < poolSize; ++i) { 24747 freeThreads.push(new dwv.utils.WorkerThread(this)); 24748 } 24749 }; 24750 24751 /** 24752 * Add a worker task to the queue. 24753 * Will be run when a thread is made available. 24754 * @return {Object} workerTask The task to add. 24755 */ 24756 this.addWorkerTask = function(workerTask) { 24757 if (freeThreads.length > 0) { 24758 // get the first free worker thread 24759 var workerThread = freeThreads.shift(); 24760 // run the input task 24761 workerThread.run(workerTask); 24762 // add the thread to the runnning list 24763 runningThreads.push(workerThread); 24764 } else { 24765 // no free thread, add task to queue 24766 taskQueue.push(workerTask); 24767 } 24768 }; 24769 24770 /** 24771 * Abort all threads. 24772 */ 24773 this.abort = function() { 24774 // clear tasks 24775 taskQueue = []; 24776 // cancel running workers 24777 for (var i = 0; i < runningThreads.length; ++i) { 24778 runningThreads[i].stop(); 24779 } 24780 runningThreads = []; 24781 // re-init 24782 this.init(); 24783 }; 24784 24785 /** 24786 * Free a worker thread. 24787 * @param {Object} workerThread The thread to free. 24788 */ 24789 this.freeWorkerThread = function(workerThread) { 24790 // send worker end 24791 this.onworkerend(); 24792 24793 if (taskQueue.length > 0) { 24794 // get waiting task 24795 var workerTask = taskQueue.shift(); 24796 // use input thread to run the waiting task 24797 workerThread.run(workerTask); 24798 } else { 24799 // no task to run, add to free list 24800 freeThreads.push(workerThread); 24801 // remove from running list 24802 for (var i = 0; i < runningThreads.length; ++i) { 24803 if (runningThreads[i].getId() === workerThread.getId()) { 24804 runningThreads.splice(i, 1); 24805 } 24806 } 24807 // the work is done when the queue is back to its initial size 24808 if (freeThreads.length === poolSize) { 24809 this.onpoolworkend(); 24810 } 24811 } 24812 }; 24813 }; 24814 24815 /** 24816 * Handle a pool work end event. 24817 * Default does nothing. 24818 */ 24819 dwv.utils.ThreadPool.prototype.onpoolworkend = function() {}; 24820 /** 24821 * Handle a pool worker end event. 24822 * Default does nothing. 24823 */ 24824 dwv.utils.ThreadPool.prototype.onworkerend = function() {}; 24825 24826 /** 24827 * Worker thread. 24828 * @external Worker 24829 * @constructor 24830 * @param {Object} parentPool The parent pool. 24831 */ 24832 dwv.utils.WorkerThread = function(parentPool) { 24833 // closure to self 24834 var self = this; 24835 24836 // thread ID 24837 var id = Math.random().toString(36).substring(2, 15); 24838 24839 // running task 24840 var runningTask = {}; 24841 // worker used to run task 24842 var worker; 24843 24844 /** 24845 * Get the thread ID. 24846 * @return {String} The thread ID (alphanumeric). 24847 */ 24848 this.getId = function() { 24849 return id; 24850 }; 24851 24852 /** 24853 * Run a worker task 24854 * @param {Object} workerTask The task to run. 24855 */ 24856 this.run = function(workerTask) { 24857 // store task 24858 runningTask = workerTask; 24859 // create a new web worker 24860 if (runningTask.script !== null) { 24861 worker = new Worker(runningTask.script); 24862 worker.addEventListener('message', ontaskend, false); 24863 // launch the worker 24864 worker.postMessage(runningTask.startMessage); 24865 } 24866 }; 24867 24868 /** 24869 * Stop a run and free the thread. 24870 */ 24871 this.stop = function() { 24872 // stop the worker 24873 worker.terminate(); 24874 // tell the parent pool this thread is free 24875 parentPool.freeWorkerThread(this); 24876 }; 24877 24878 /** 24879 * Handle once the task is done. 24880 * For now assume we only get a single callback from a worker 24881 * which also indicates the end of this worker. 24882 * @param {Object} event The callback event. 24883 */ 24884 function ontaskend(event) { 24885 // pass to original callback 24886 runningTask.callback(event); 24887 // stop the worker and free the thread 24888 self.stop(); 24889 } 24890 24891 }; 24892 24893 /** 24894 * Worker task. 24895 * @constructor 24896 * @param {String} script The worker script. 24897 * @param {Function} callback The worker callback. 24898 * @param {Object} message The data to pass to the worker. 24899 */ 24900 dwv.utils.WorkerTask = function(script, callback, message) { 24901 // worker script 24902 this.script = script; 24903 // worker callback 24904 this.callback = callback; 24905 // worker start message 24906 this.startMessage = message; 24907 }; 24908 24909 // namespaces 24910 var dwv = dwv || {}; 24911 dwv.utils = dwv.utils || {}; 24912 /** @namespace */ 24913 dwv.utils.base = dwv.utils.base || {}; 24914 24915 /** 24916 * Split an input URI: 24917 * 'root?key0=val00&key0=val01&key1=val10' returns 24918 * { base : root, query : [ key0 : [val00, val01], key1 : val1 ] } 24919 * Returns an empty object if the input string is not correct (null, empty...) 24920 * or if it is not a query string (no question mark). 24921 * @param {String} inputStr The string to split. 24922 * @return {Object} The split string. 24923 */ 24924 dwv.utils.splitUri = function(uri) { 24925 // result 24926 var result = {}; 24927 // check if query string 24928 var sepIndex = null; 24929 if (uri && (sepIndex = uri.indexOf('?')) !== -1) { 24930 // base: before the '?'
24931 result.base = uri.substr(0, sepIndex); 24932 // query : after the '?' and until possible '#' 24933 var hashIndex = uri.indexOf('#'); 24934 if (hashIndex === -1) { 24935 hashIndex = uri.length; 24936 } 24937 var query = uri.substr(sepIndex + 1, (hashIndex - 1 - sepIndex)); 24938 // split key/value pairs of the query 24939 result.query = dwv.utils.splitKeyValueString(query); 24940 } 24941 // return 24942 return result; 24943 }; 24944 24945 /** 24946 * Get the query part, split into an array, of an input URI. 24947 * The URI scheme is: 'base?query#fragment' 24948 * @param {String } uri The input URI. 24949 * @return {Object} The query part, split into an array, of the input URI. 24950 */ 24951 dwv.utils.getUriQuery = function(uri) { 24952 // split 24953 var parts = dwv.utils.splitUri(uri); 24954 // check not empty 24955 if (Object.keys(parts).length === 0) { 24956 return null; 24957 } 24958 // return query 24959 return parts.query; 24960 }; 24961 24962 /** 24963 * Generic URI query decoder. 24964 * Supports manifest: 24965 * [dwv root]?input=encodeURIComponent('[manifest file]')&type=manifest 24966 * or encoded URI with base and key value/pairs: 24967 * [dwv root]?input=encodeURIComponent([root]?key0=value0&key1=value1) 24968 * @param {String} query The query part to the input URI. 24969 * @param {Function} callback The function to call with the decoded file urls. 24970 */ 24971 dwv.utils.base.decodeQuery = function(query, callback) { 24972 // manifest 24973 if (query.type && query.type === "manifest") { 24974 dwv.utils.decodeManifestQuery(query, callback); 24975 } 24976 // default case: encoded URI with base and key/value pairs 24977 else { 24978 callback(dwv.utils.decodeKeyValueUri(query.input, query.dwvReplaceMode)); 24979 } 24980 }; 24981 24982 /** 24983 * Decode a Key/Value pair URI. If a key is repeated, the result 24984 * be an array of base + each key. 24985 * @param {String} uri The URI to decode. 24986 * @param {String} replaceMode The key replace more. 24987 * replaceMode can be: 24988 * - key (default): keep the key 24989 * - other than key: do not use the key 24990 * 'file' is a special case where the '?' of the query is not kept. 24991 * @return The list of input file urls. 24992 */ 24993 dwv.utils.decodeKeyValueUri = function(uri, replaceMode) { 24994 var result = []; 24995 24996 // repeat key replace mode (default to keep key) 24997 var repeatKeyReplaceMode = "key"; 24998 if (replaceMode) { 24999 repeatKeyReplaceMode = replaceMode; 25000 } 25001 25002 // decode input URI 25003 var queryUri = decodeURIComponent(uri); 25004 // get key/value pairs from input URI 25005 var inputQueryPairs = dwv.utils.splitUri(queryUri); 25006 if (Object.keys(inputQueryPairs).length === 0) { 25007 result.push(queryUri); 25008 } else { 25009 var keys = Object.keys(inputQueryPairs.query); 25010 // find repeat key 25011 var repeatKey = null; 25012 for (var i = 0; i < keys.length; ++i) { 25013 if (inputQueryPairs.query[keys[i]] instanceof Array) { 25014 repeatKey = keys[i]; 25015 break; 25016 } 25017 } 25018 25019 if (!repeatKey) { 25020 result.push(queryUri); 25021 } else { 25022 var repeatList = inputQueryPairs.query[repeatKey]; 25023 // build base uri 25024 var baseUrl = inputQueryPairs.base; 25025 // do not add '?' when the repeatKey is 'file' 25026 // root/path/to/?file=0.jpg&file=1.jpg 25027 if (repeatKey !== "file") { 25028 baseUrl += "?"; 25029 } 25030 var gotOneArg = false; 25031 for (var j = 0; j < keys.length; ++j) { 25032 if (keys[j] !== repeatKey) { 25033 if (gotOneArg) { 25034 baseUrl += "&"; 25035 } 25036 baseUrl += keys[j] + "=" + inputQueryPairs.query[keys[j]]; 25037 gotOneArg = true; 25038 } 25039 } 25040 // append built urls to result 25041 var url; 25042 for (var k = 0; k < repeatList.length; ++k) { 25043 url = baseUrl; 25044 if (gotOneArg) { 25045 url += "&"; 25046 } 25047 if (repeatKeyReplaceMode === "key") { 25048 url += repeatKey + "="; 25049 } 25050 // other than 'key' mode: do nothing 25051 url += repeatList[k]; 25052 result.push(url); 25053 } 25054 } 25055 } 25056 // return 25057 return result; 25058 }; 25059 25060 /** 25061 * Decode a manifest query. 25062 * @external XMLHttpRequest 25063 * @param {Object} query The manifest query: {input, nslices}, 25064 * with input the input URI and nslices the number of slices. 25065 * @param {Function} The function to call with the decoded urls. 25066 */ 25067 dwv.utils.decodeManifestQuery = function(query, callback) { 25068 var uri = ""; 25069 if (query.input[0] === '/') { 25070 uri = window.location.protocol + "//" + window.location.host; 25071 } 25072 // TODO: needs to be decoded (decodeURIComponent? 25073 uri += query.input; 25074 25075 // handle error 25076 var onError = function( /*event*/ ) { 25077 console.warn("RequestError while receiving manifest: " + this.status); 25078 }; 25079 25080 // handle load 25081 var onLoad = function( /*event*/ ) { 25082 callback(dwv.utils.decodeManifest(this.responseXML, query.nslices)); 25083 }; 25084 25085 var request = new XMLHttpRequest(); 25086 request.open('GET', decodeURIComponent(uri), true); 25087 request.responseType = "document"; 25088 request.onload = onLoad; 25089 request.onerror = onError; 25090 request.send(null); 25091 }; 25092 25093 /** 25094 * Decode an XML manifest. 25095 * @param {Object} manifest The manifest to decode. 25096 * @param {Number} nslices The number of slices to load. 25097 */
25098 dwv.utils.decodeManifest = function(manifest, nslices) { 25099 var result = []; 25100 // wado url 25101 var wadoElement = manifest.getElementsByTagName("wado_query"); 25102 var wadoURL = wadoElement[0].getAttribute("wadoURL"); 25103 var rootURL = wadoURL + "?requestType=WADO&contentType=application/dicom&"; 25104 // patient list 25105 var patientList = manifest.getElementsByTagName("Patient"); 25106 if (patientList.length > 1) { 25107 console.warn("More than one patient, loading first one."); 25108 } 25109 // study list 25110 var studyList = patientList[0].getElementsByTagName("Study"); 25111 if (studyList.length > 1) { 25112 console.warn("More than one study, loading first one."); 25113 } 25114 var studyUID = studyList[0].getAttribute("StudyInstanceUID"); 25115 // series list 25116 var seriesList = studyList[0].getElementsByTagName("Series"); 25117 if (seriesList.length > 1) { 25118 console.warn("More than one series, loading first one."); 25119 } 25120 var seriesUID = seriesList[0].getAttribute("SeriesInstanceUID"); 25121 // instance list 25122 var instanceList = seriesList[0].getElementsByTagName("Instance"); 25123 // loop on instances and push links 25124 var max = instanceList.length; 25125 if (nslices < max) { 25126 max = nslices; 25127 } 25128 for (var i = 0; i < max; ++i) { 25129 var sopInstanceUID = instanceList[i].getAttribute("SOPInstanceUID"); 25130 var link = rootURL + 25131 "&studyUID=" + studyUID + 25132 "&seriesUID=" + seriesUID + 25133 "&objectUID=" + sopInstanceUID; 25134 result.push(link); 25135 } 25136 // return 25137 return result; 25138 }; 25139 25140 return dwv; 25141}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.