25790 lines
868 KiB
JavaScript
25790 lines
868 KiB
JavaScript
// Inspired from umdjs
|
|
// See https://github.com/umdjs/umd/blob/master/templates/returnExports.js
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define([
|
|
'modernizr',
|
|
'i18next',
|
|
'i18nextXHRBackend',
|
|
'i18nextBrowserLanguageDetector',
|
|
'jszip',
|
|
'konva',
|
|
''
|
|
], factory);
|
|
} else if (typeof module === 'object' && module.exports) {
|
|
// Node. Does not work with strict CommonJS, but
|
|
// only CommonJS-like environments that support module.exports,
|
|
// like Node.
|
|
|
|
// i18next-xhr-backend: requires XMlHttpRequest
|
|
// Konva: requires 'canvas' -> deactivated for now...
|
|
// MagicWand: no package -> deactivated
|
|
|
|
module.exports = factory(
|
|
require('modernizr'),
|
|
require('i18next'),
|
|
require('i18next-xhr-backend'),
|
|
require('i18next-browser-languagedetector'),
|
|
require('jszip'),
|
|
null,
|
|
null
|
|
);
|
|
} else {
|
|
// Browser globals (root is window)
|
|
root.dwv = factory(
|
|
root.Modernizr,
|
|
root.i18next,
|
|
root.i18nextXHRBackend,
|
|
root.i18nextBrowserLanguageDetector,
|
|
root.JSZip,
|
|
root.Konva,
|
|
root.MagicWand
|
|
);
|
|
}
|
|
}(this, function (
|
|
Modernizr,
|
|
i18next,
|
|
i18nextXHRBackend,
|
|
i18nextBrowserLanguageDetector,
|
|
JSZip,
|
|
Konva,
|
|
MagicWand) {
|
|
|
|
// similar to what browserify does but reversed
|
|
//https://www.contentful.com/blog/2017/01/17/the-global-object-in-javascript/
|
|
var window = typeof window !== 'undefined' ?
|
|
window : typeof self !== 'undefined' ?
|
|
self : typeof global !== 'undefined' ?
|
|
global : {};
|
|
|
|
/** @namespace */
|
|
var dwv = dwv || {};
|
|
|
|
/**
|
|
* Main application class.
|
|
* @constructor
|
|
*/
|
|
dwv.App = function ()
|
|
{
|
|
// Local object
|
|
var self = this;
|
|
|
|
// Image
|
|
var image = null;
|
|
// Original image
|
|
var originalImage = null;
|
|
// Image data array
|
|
var imageData = null;
|
|
// Image data width
|
|
var dataWidth = 0;
|
|
// Image data height
|
|
var dataHeight = 0;
|
|
// Is the data mono-slice?
|
|
var isMonoSliceData = 0;
|
|
|
|
// Default character set
|
|
var defaultCharacterSet;
|
|
|
|
// Container div id
|
|
var containerDivId = null;
|
|
// Display window scale
|
|
var windowScale = 1;
|
|
// Fit display to window flag
|
|
var fitToWindow = false;
|
|
// main scale
|
|
var scale = 1;
|
|
// zoom center
|
|
var scaleCenter = {"x": 0, "y": 0};
|
|
// translation
|
|
var translation = {"x": 0, "y": 0};
|
|
|
|
// View
|
|
var view = null;
|
|
// View controller
|
|
var viewController = null;
|
|
|
|
// Info layer controller
|
|
var infoController = null;
|
|
|
|
// Dicom tags gui
|
|
var tagsGui = null;
|
|
|
|
// Drawing list gui
|
|
var drawListGui = null;
|
|
|
|
// Image layer
|
|
var imageLayer = null;
|
|
|
|
// Draw controller
|
|
var drawController = null;
|
|
|
|
// Generic style
|
|
var style = new dwv.html.Style();
|
|
|
|
// Toolbox controller
|
|
var toolboxController = null;
|
|
|
|
// Loadbox
|
|
var loadbox = null;
|
|
// Current loader
|
|
var currentLoader = null;
|
|
|
|
// UndoStack
|
|
var undoStack = null;
|
|
|
|
// listeners
|
|
var listeners = {};
|
|
|
|
/**
|
|
* Get the image.
|
|
* @return {Image} The associated image.
|
|
*/
|
|
this.getImage = function () { return image; };
|
|
/**
|
|
* Set the view.
|
|
* @param {Image} img The associated image.
|
|
*/
|
|
this.setImage = function (img)
|
|
{
|
|
image = img;
|
|
view.setImage(img);
|
|
};
|
|
/**
|
|
* Restore the original image.
|
|
*/
|
|
this.restoreOriginalImage = function ()
|
|
{
|
|
image = originalImage;
|
|
view.setImage(originalImage);
|
|
};
|
|
/**
|
|
* Get the image data array.
|
|
* @return {Array} The image data array.
|
|
*/
|
|
this.getImageData = function () { return imageData; };
|
|
/**
|
|
* Is the data mono-slice?
|
|
* @return {Boolean} True if the data is mono-slice.
|
|
*/
|
|
this.isMonoSliceData = function () { return isMonoSliceData; };
|
|
|
|
/**
|
|
* Get the main scale.
|
|
* @return {Number} The main scale.
|
|
*/
|
|
this.getScale = function () { return scale / windowScale; };
|
|
|
|
/**
|
|
* Get the window scale.
|
|
* @return {Number} The window scale.
|
|
*/
|
|
this.getWindowScale = function () { return windowScale; };
|
|
|
|
/**
|
|
* Get the scale center.
|
|
* @return {Object} The coordinates of the scale center.
|
|
*/
|
|
this.getScaleCenter = function () { return scaleCenter; };
|
|
|
|
/**
|
|
* Get the translation.
|
|
* @return {Object} The translation.
|
|
*/
|
|
this.getTranslation = function () { return translation; };
|
|
|
|
/**
|
|
* Get the view controller.
|
|
* @return {Object} The controller.
|
|
*/
|
|
this.getViewController = function () { return viewController; };
|
|
|
|
/**
|
|
* Get the image layer.
|
|
* @return {Object} The image layer.
|
|
*/
|
|
this.getImageLayer = function () { return imageLayer; };
|
|
/**
|
|
* Get the current draw layer.
|
|
* @return {Object} The draw layer.
|
|
*/
|
|
this.getCurrentDrawLayer = function () {
|
|
return drawController.getCurrentDrawLayer();
|
|
};
|
|
/**
|
|
* Get the draw stage.
|
|
* @return {Object} The draw stage.
|
|
*/
|
|
this.getDrawStage = function () {
|
|
return drawController.getDrawStage();
|
|
};
|
|
|
|
/**
|
|
* Get the app style.
|
|
* @return {Object} The app style.
|
|
*/
|
|
this.getStyle = function () { return style; };
|
|
|
|
/**
|
|
* Add a command to the undo stack.
|
|
* @param {Object} The command to add.
|
|
*/
|
|
this.addToUndoStack = function (cmd) {
|
|
if ( undoStack !== null ) {
|
|
undoStack.add(cmd);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialise the HTML for the application.
|
|
*/
|
|
this.init = function ( config ) {
|
|
containerDivId = config.containerDivId;
|
|
// tools
|
|
if ( config.tools && config.tools.length !== 0 ) {
|
|
// setup the tool list
|
|
var toolList = {};
|
|
for ( var t = 0; t < config.tools.length; ++t ) {
|
|
var toolName = config.tools[t];
|
|
if ( toolName === "Draw" ) {
|
|
if ( config.shapes !== 0 ) {
|
|
// setup the shape list
|
|
var shapeList = {};
|
|
for ( var s = 0; s < config.shapes.length; ++s ) {
|
|
var shapeName = config.shapes[s];
|
|
var shapeFactoryClass = shapeName+"Factory";
|
|
if (typeof dwv.tool[shapeFactoryClass] !== "undefined") {
|
|
shapeList[shapeName] = dwv.tool[shapeFactoryClass];
|
|
}
|
|
else {
|
|
console.warn("Could not initialise unknown shape: "+shapeName);
|
|
}
|
|
}
|
|
toolList.Draw = new dwv.tool.Draw(this, shapeList);
|
|
toolList.Draw.addEventListener("draw-create", fireEvent);
|
|
toolList.Draw.addEventListener("draw-change", fireEvent);
|
|
toolList.Draw.addEventListener("draw-move", fireEvent);
|
|
toolList.Draw.addEventListener("draw-delete", fireEvent);
|
|
}
|
|
}
|
|
else if ( toolName === "Filter" ) {
|
|
if ( config.filters.length !== 0 ) {
|
|
// setup the filter list
|
|
var filterList = {};
|
|
for ( var f = 0; f < config.filters.length; ++f ) {
|
|
var filterName = config.filters[f];
|
|
if (typeof dwv.tool.filter[filterName] !== "undefined") {
|
|
filterList[filterName] = new dwv.tool.filter[filterName](this);
|
|
}
|
|
else {
|
|
console.warn("Could not initialise unknown filter: "+filterName);
|
|
}
|
|
}
|
|
toolList.Filter = new dwv.tool.Filter(filterList, this);
|
|
toolList.Filter.addEventListener("filter-run", fireEvent);
|
|
toolList.Filter.addEventListener("filter-undo", fireEvent);
|
|
}
|
|
}
|
|
else {
|
|
// default: find the tool in the dwv.tool namespace
|
|
var toolClass = toolName;
|
|
if (typeof dwv.tool[toolClass] !== "undefined") {
|
|
toolList[toolClass] = new dwv.tool[toolClass](this);
|
|
if (typeof toolList[toolClass].addEventListener !== "undefined") {
|
|
toolList[toolClass].addEventListener(fireEvent);
|
|
}
|
|
}
|
|
else {
|
|
console.warn("Could not initialise unknown tool: "+toolName);
|
|
}
|
|
}
|
|
}
|
|
toolboxController = new dwv.ToolboxController();
|
|
toolboxController.create(toolList, this);
|
|
}
|
|
// gui
|
|
if ( config.gui ) {
|
|
// tools
|
|
if ( config.gui.indexOf("tool") !== -1 && toolboxController) {
|
|
toolboxController.setup();
|
|
}
|
|
// load
|
|
if ( config.gui.indexOf("load") !== -1 ) {
|
|
var loaderList = {};
|
|
for ( var l = 0; l < config.loaders.length; ++l ) {
|
|
var loaderName = config.loaders[l];
|
|
var loaderClass = loaderName + "Load";
|
|
// default: find the loader in the dwv.gui namespace
|
|
if (typeof dwv.gui[loaderClass] !== "undefined") {
|
|
loaderList[loaderName] = new dwv.gui[loaderClass](this);
|
|
}
|
|
else {
|
|
console.warn("Could not initialise unknown loader: "+loaderName);
|
|
}
|
|
}
|
|
loadbox = new dwv.gui.Loadbox(this, loaderList);
|
|
loadbox.setup();
|
|
var loaderKeys = Object.keys(loaderList);
|
|
for ( var lk = 0; lk < loaderKeys.length; ++lk ) {
|
|
loaderList[loaderKeys[lk]].setup();
|
|
}
|
|
loadbox.displayLoader(loaderKeys[0]);
|
|
}
|
|
// undo
|
|
if ( config.gui.indexOf("undo") !== -1 ) {
|
|
undoStack = new dwv.tool.UndoStack(this);
|
|
undoStack.setup();
|
|
}
|
|
// DICOM Tags
|
|
if ( config.gui.indexOf("tags") !== -1 ) {
|
|
tagsGui = new dwv.gui.DicomTags(this);
|
|
}
|
|
// Draw list
|
|
if ( config.gui.indexOf("drawList") !== -1 ) {
|
|
drawListGui = new dwv.gui.DrawList(this);
|
|
// update list on draw events
|
|
this.addEventListener("draw-create", drawListGui.update);
|
|
this.addEventListener("draw-change", drawListGui.update);
|
|
this.addEventListener("draw-delete", drawListGui.update);
|
|
}
|
|
// version number
|
|
if ( config.gui.indexOf("version") !== -1 ) {
|
|
dwv.gui.appendVersionHtml(dwv.getVersion());
|
|
}
|
|
// help
|
|
if ( config.gui.indexOf("help") !== -1 ) {
|
|
var isMobile = true;
|
|
if ( config.isMobile !== "undefined" ) {
|
|
isMobile = config.isMobile;
|
|
}
|
|
dwv.gui.appendHelpHtml( toolboxController.getToolList(), isMobile, this );
|
|
}
|
|
}
|
|
|
|
// listen to drag&drop
|
|
var box = this.getElement("dropBox");
|
|
if ( box ) {
|
|
box.addEventListener("dragover", onDragOver);
|
|
box.addEventListener("dragleave", onDragLeave);
|
|
box.addEventListener("drop", onDrop);
|
|
// initial size
|
|
var size = dwv.gui.getWindowSize();
|
|
var dropBoxSize = 2 * size.height / 3;
|
|
box.setAttribute("style","width:"+dropBoxSize+"px;height:"+dropBoxSize+"px");
|
|
}
|
|
|
|
// possible load from URL
|
|
if ( typeof config.skipLoadUrl === "undefined" ) {
|
|
var query = dwv.utils.getUriQuery(window.location.href);
|
|
// check query
|
|
if ( query && typeof query.input !== "undefined" ) {
|
|
dwv.utils.decodeQuery(query, this.onInputURLs);
|
|
// optional display state
|
|
if ( typeof query.state !== "undefined" ) {
|
|
var onLoadEnd = function (/*event*/) {
|
|
loadStateUrl(query.state);
|
|
};
|
|
this.addEventListener( "load-end", onLoadEnd );
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
console.log("Not loading url from address since skipLoadUrl is defined.");
|
|
}
|
|
|
|
// align layers when the window is resized
|
|
if ( config.fitToWindow ) {
|
|
fitToWindow = true;
|
|
window.onresize = this.onResize;
|
|
}
|
|
|
|
// default character set
|
|
if ( typeof config.defaultCharacterSet !== "undefined" ) {
|
|
defaultCharacterSet = config.defaultCharacterSet;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a HTML element associated to the application.
|
|
* @param name The name or id to find.
|
|
* @return The found element or null.
|
|
*/
|
|
this.getElement = function (name)
|
|
{
|
|
return dwv.gui.getElement(containerDivId, name);
|
|
};
|
|
|
|
/**
|
|
* Reset the application.
|
|
*/
|
|
this.reset = function ()
|
|
{
|
|
// clear tools
|
|
if ( toolboxController ) {
|
|
toolboxController.reset();
|
|
}
|
|
// clear draw
|
|
if ( drawController ) {
|
|
drawController.reset();
|
|
}
|
|
// clear objects
|
|
image = null;
|
|
view = null;
|
|
isMonoSliceData = false;
|
|
// reset undo/redo
|
|
if ( undoStack ) {
|
|
undoStack = new dwv.tool.UndoStack(this);
|
|
undoStack.initialise();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset the layout of the application.
|
|
*/
|
|
this.resetLayout = function () {
|
|
var previousScale = scale;
|
|
var previousSC = scaleCenter;
|
|
var previousTrans = translation;
|
|
// reset values
|
|
scale = windowScale;
|
|
scaleCenter = {"x": 0, "y": 0};
|
|
translation = {"x": 0, "y": 0};
|
|
// apply new values
|
|
if ( imageLayer ) {
|
|
imageLayer.resetLayout(windowScale);
|
|
imageLayer.draw();
|
|
}
|
|
if ( drawController ) {
|
|
drawController.resetStage(windowScale);
|
|
}
|
|
// fire events
|
|
if (previousScale != scale) {
|
|
fireEvent({"type": "zoom-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y });
|
|
}
|
|
if ( (previousSC.x !== scaleCenter.x || previousSC.y !== scaleCenter.y) ||
|
|
(previousTrans.x !== translation.x || previousTrans.y !== translation.y)) {
|
|
fireEvent({"type": "offset-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add an event listener on the app.
|
|
* @param {String} type The event type.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.addEventListener = function (type, listener)
|
|
{
|
|
if ( typeof listeners[type] === "undefined" ) {
|
|
listeners[type] = [];
|
|
}
|
|
listeners[type].push(listener);
|
|
};
|
|
|
|
/**
|
|
* Remove an event listener from the app.
|
|
* @param {String} type The event type.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (type, listener)
|
|
{
|
|
if( typeof listeners[type] === "undefined" ) {
|
|
return;
|
|
}
|
|
for ( var i = 0; i < listeners[type].length; ++i )
|
|
{
|
|
if ( listeners[type][i] === listener ) {
|
|
listeners[type].splice(i,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load a list of files. Can be image files or a state file.
|
|
* @param {Array} files The list of files to load.
|
|
*/
|
|
this.loadFiles = function (files)
|
|
{
|
|
// has been checked for emptiness.
|
|
var ext = files[0].name.split('.').pop().toLowerCase();
|
|
if ( ext === "json" ) {
|
|
loadStateFile(files[0]);
|
|
}
|
|
else {
|
|
loadImageFiles(files);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load a list of image files.
|
|
* @private
|
|
* @param {Array} files The list of image files to load.
|
|
*/
|
|
function loadImageFiles(files)
|
|
{
|
|
// create IO
|
|
var fileIO = new dwv.io.FilesLoader();
|
|
// load data
|
|
loadImageData(files, fileIO);
|
|
}
|
|
|
|
/**
|
|
* Load a State file.
|
|
* @private
|
|
* @param {String} file The state file to load.
|
|
*/
|
|
function loadStateFile(file)
|
|
{
|
|
// create IO
|
|
var fileIO = new dwv.io.FilesLoader();
|
|
// load data
|
|
loadStateData([file], fileIO);
|
|
}
|
|
|
|
/**
|
|
* Load a list of URLs. Can be image files or a state file.
|
|
* @param {Array} urls The list of urls to load.
|
|
* @param {Array} requestHeaders An array of {name, value} to use as request headers.
|
|
*/
|
|
this.loadURLs = function (urls, requestHeaders)
|
|
{
|
|
// has been checked for emptiness.
|
|
var ext = urls[0].split('.').pop().toLowerCase();
|
|
if ( ext === "json" ) {
|
|
loadStateUrl(urls[0], requestHeaders);
|
|
}
|
|
else {
|
|
loadImageUrls(urls, requestHeaders);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Abort the current load.
|
|
*/
|
|
this.abortLoad = function ()
|
|
{
|
|
if ( currentLoader ) {
|
|
currentLoader.abort();
|
|
currentLoader = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load a list of ArrayBuffers.
|
|
* @param {Array} data The list of ArrayBuffers to load
|
|
* in the form of [{name: "", filename: "", data: data}].
|
|
*/
|
|
this.loadImageObject = function (data)
|
|
{
|
|
// create IO
|
|
var memoryIO = new dwv.io.MemoryLoader();
|
|
// create options
|
|
var options = {};
|
|
// load data
|
|
loadImageData(data, memoryIO, options);
|
|
};
|
|
|
|
/**
|
|
* Load a list of image URLs.
|
|
* @private
|
|
* @param {Array} urls The list of urls to load.
|
|
* @param {Array} requestHeaders An array of {name, value} to use as request headers.
|
|
*/
|
|
function loadImageUrls(urls, requestHeaders)
|
|
{
|
|
// create IO
|
|
var urlIO = new dwv.io.UrlsLoader();
|
|
// create options
|
|
var options = {'requestHeaders': requestHeaders};
|
|
// load data
|
|
loadImageData(urls, urlIO, options);
|
|
}
|
|
|
|
/**
|
|
* Load a State url.
|
|
* @private
|
|
* @param {String} url The state url to load.
|
|
* @param {Array} requestHeaders An array of {name, value} to use as request headers.
|
|
*/
|
|
function loadStateUrl(url, requestHeaders)
|
|
{
|
|
// create IO
|
|
var urlIO = new dwv.io.UrlsLoader();
|
|
// create options
|
|
var options = {'requestHeaders': requestHeaders};
|
|
// load data
|
|
loadStateData([url], urlIO, options);
|
|
}
|
|
|
|
/**
|
|
* Load a list of image data.
|
|
* @private
|
|
* @param {Array} data Array of data to load.
|
|
* @param {Object} loader The data loader.
|
|
* @param {Object} options Options passed to the final loader.
|
|
*/
|
|
function loadImageData(data, loader, options)
|
|
{
|
|
// store loader
|
|
currentLoader = loader;
|
|
|
|
// allow to cancel
|
|
var previousOnKeyDown = window.onkeydown;
|
|
window.onkeydown = function (event) {
|
|
if (event.ctrlKey && event.keyCode === 88 ) // crtl-x
|
|
{
|
|
console.log("crtl-x pressed!");
|
|
self.abortLoad();
|
|
}
|
|
};
|
|
|
|
// clear variables
|
|
self.reset();
|
|
// first data name
|
|
var firstName = "";
|
|
if (typeof data[0].name !== "undefined") {
|
|
firstName = data[0].name;
|
|
} else {
|
|
firstName = data[0];
|
|
}
|
|
// flag used by scroll to decide wether to activate or not
|
|
// TODO: supposing multi-slice for zip files, could not be...
|
|
isMonoSliceData = (data.length === 1 &&
|
|
firstName.split('.').pop().toLowerCase() !== "zip");
|
|
// set IO
|
|
loader.setDefaultCharacterSet(defaultCharacterSet);
|
|
loader.onload = function (data) {
|
|
if ( image ) {
|
|
view.append( data.view );
|
|
if ( drawController ) {
|
|
drawController.appendDrawLayer(image.getNumberOfFrames());
|
|
}
|
|
}
|
|
postLoadInit(data);
|
|
};
|
|
loader.onerror = function (error) { handleError(error); };
|
|
loader.onabort = function (error) { handleAbort(error); };
|
|
loader.onloadend = function (/*event*/) {
|
|
window.onkeydown = previousOnKeyDown;
|
|
if ( drawController ) {
|
|
drawController.activateDrawLayer(viewController);
|
|
}
|
|
fireEvent({type: "load-progress", lengthComputable: true,
|
|
loaded: 100, total: 100});
|
|
fireEvent({ 'type': 'load-end' });
|
|
// reset member
|
|
currentLoader = null;
|
|
};
|
|
loader.onprogress = onLoadProgress;
|
|
// main load (asynchronous)
|
|
fireEvent({ 'type': 'load-start' });
|
|
loader.load(data, options);
|
|
}
|
|
|
|
/**
|
|
* Load a State data.
|
|
* @private
|
|
* @param {Array} data Array of data to load.
|
|
* @param {Object} loader The data loader.
|
|
* @param {Object} options Options passed to the final loader.
|
|
*/
|
|
function loadStateData(data, loader, options)
|
|
{
|
|
// set IO
|
|
loader.onload = function (data) {
|
|
// load state
|
|
var state = new dwv.State(self);
|
|
state.fromJSON(data);
|
|
};
|
|
loader.onerror = function (error) { handleError(error); };
|
|
// main load (asynchronous)
|
|
loader.load(data, options);
|
|
}
|
|
|
|
/**
|
|
* Fit the display to the given size. To be called once the image is loaded.
|
|
*/
|
|
this.fitToSize = function (size)
|
|
{
|
|
// previous width
|
|
var oldWidth = parseInt(windowScale*dataWidth, 10);
|
|
// find new best fit
|
|
windowScale = Math.min( (size.width / dataWidth), (size.height / dataHeight) );
|
|
// new sizes
|
|
var newWidth = parseInt(windowScale*dataWidth, 10);
|
|
var newHeight = parseInt(windowScale*dataHeight, 10);
|
|
// ratio previous/new to add to zoom
|
|
var mul = newWidth / oldWidth;
|
|
scale *= mul;
|
|
|
|
// update style
|
|
style.setScale(windowScale);
|
|
|
|
// resize container
|
|
var container = this.getElement("layerContainer");
|
|
container.setAttribute("style","width:"+newWidth+"px;height:"+newHeight+"px");
|
|
// resize image layer
|
|
if ( imageLayer ) {
|
|
imageLayer.setWidth(newWidth);
|
|
imageLayer.setHeight(newHeight);
|
|
imageLayer.zoom(scale, scale, 0, 0);
|
|
imageLayer.draw();
|
|
}
|
|
// resize draw stage
|
|
if ( drawController ) {
|
|
drawController.resizeStage(newWidth, newHeight, scale);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Toggle the display of the information layer.
|
|
*/
|
|
this.toggleInfoLayerDisplay = function ()
|
|
{
|
|
// toggle html
|
|
var infoLayer = self.getElement("infoLayer");
|
|
dwv.html.toggleDisplay(infoLayer);
|
|
// toggle listeners
|
|
infoController.toggleListeners(self, view);
|
|
};
|
|
|
|
/**
|
|
* Init the Window/Level display
|
|
*/
|
|
this.initWLDisplay = function ()
|
|
{
|
|
// set window/level to first preset
|
|
viewController.setWindowLevelPresetById(0);
|
|
// default position
|
|
viewController.setCurrentPosition2D(0,0);
|
|
// default frame
|
|
viewController.setCurrentFrame(0);
|
|
};
|
|
|
|
/**
|
|
* Add canvas mouse and touch listeners.
|
|
* @param {Object} canvas The canvas to listen to.
|
|
*/
|
|
this.addToolCanvasListeners = function (layer)
|
|
{
|
|
toolboxController.addCanvasListeners(layer);
|
|
};
|
|
|
|
/**
|
|
* Remove layer mouse and touch listeners.
|
|
* @param {Object} canvas The canvas to stop listening to.
|
|
*/
|
|
this.removeToolCanvasListeners = function (layer)
|
|
{
|
|
toolboxController.removeCanvasListeners(layer);
|
|
};
|
|
|
|
/**
|
|
* Render the current image.
|
|
*/
|
|
this.render = function ()
|
|
{
|
|
generateAndDrawImage();
|
|
};
|
|
|
|
/**
|
|
* Zoom to the layers.
|
|
* @param {Number} zoom The zoom to apply.
|
|
* @param {Number} cx The zoom center X coordinate.
|
|
* @param {Number} cy The zoom center Y coordinate.
|
|
*/
|
|
this.zoom = function (zoom, cx, cy) {
|
|
scale = zoom * windowScale;
|
|
if ( scale <= 0.1 ) {
|
|
scale = 0.1;
|
|
}
|
|
scaleCenter = {"x": cx, "y": cy};
|
|
zoomLayers();
|
|
};
|
|
|
|
/**
|
|
* Add a step to the layers zoom.
|
|
* @param {Number} step The zoom step increment. A good step is of 0.1.
|
|
* @param {Number} cx The zoom center X coordinate.
|
|
* @param {Number} cy The zoom center Y coordinate.
|
|
*/
|
|
this.stepZoom = function (step, cx, cy) {
|
|
scale += step;
|
|
if ( scale <= 0.1 ) {
|
|
scale = 0.1;
|
|
}
|
|
scaleCenter = {"x": cx, "y": cy};
|
|
zoomLayers();
|
|
};
|
|
|
|
/**
|
|
* Apply a translation to the layers.
|
|
* @param {Number} tx The translation along X.
|
|
* @param {Number} ty The translation along Y.
|
|
*/
|
|
this.translate = function (tx, ty)
|
|
{
|
|
translation = {"x": tx, "y": ty};
|
|
translateLayers();
|
|
};
|
|
|
|
/**
|
|
* Add a translation to the layers.
|
|
* @param {Number} tx The step translation along X.
|
|
* @param {Number} ty The step translation along Y.
|
|
*/
|
|
this.stepTranslate = function (tx, ty)
|
|
{
|
|
var txx = translation.x + tx / scale;
|
|
var tyy = translation.y + ty / scale;
|
|
translation = {"x": txx, "y": tyy};
|
|
translateLayers();
|
|
};
|
|
|
|
/**
|
|
* Get the list of drawing display details.
|
|
* @return {Object} The list of draw details including id, slice, frame...
|
|
*/
|
|
this.getDrawDisplayDetails = function ()
|
|
{
|
|
return drawController.getDrawDisplayDetails();
|
|
};
|
|
/**
|
|
* Get the list of drawings.
|
|
* @return {Object} The list of drawings.
|
|
*/
|
|
this.getDraws = function ()
|
|
{
|
|
return drawController.getDraws();
|
|
};
|
|
/**
|
|
* Get a list of drawing store details.
|
|
* @return {Object} A list of draw details including id, text, quant...
|
|
*/
|
|
this.getDrawStoreDetails = function ()
|
|
{
|
|
return drawController.getDrawStoreDetails();
|
|
};
|
|
/**
|
|
* Set the drawings on the current stage.
|
|
* @param {Array} drawings An array of drawings.
|
|
* @param {Array} drawingsDetails An array of drawings details.
|
|
*/
|
|
this.setDrawings = function (drawings, drawingsDetails)
|
|
{
|
|
drawController.setDrawings(drawings, drawingsDetails, fireEvent, this.addToUndoStack);
|
|
};
|
|
/**
|
|
* Update a drawing from its details.
|
|
* @param {Object} drawDetails Details of the drawing to update.
|
|
*/
|
|
this.updateDraw = function (drawDetails)
|
|
{
|
|
drawController.updateDraw(drawDetails);
|
|
};
|
|
/**
|
|
* Delete all Draws from all layers.
|
|
*/
|
|
this.deleteDraws = function () {
|
|
drawController.deleteDraws(fireEvent, this.addToUndoStack);
|
|
};
|
|
/**
|
|
* Check the visibility of a given group.
|
|
* @param {Object} drawDetails Details of the drawing to check.
|
|
*/
|
|
this.isGroupVisible = function (drawDetails)
|
|
{
|
|
return drawController.isGroupVisible(drawDetails);
|
|
};
|
|
/**
|
|
* Toggle group visibility.
|
|
* @param {Object} drawDetails Details of the drawing to update.
|
|
*/
|
|
this.toogleGroupVisibility = function (drawDetails)
|
|
{
|
|
drawController.toogleGroupVisibility(drawDetails);
|
|
};
|
|
|
|
// Handler Methods -----------------------------------------------------------
|
|
|
|
/**
|
|
* Handle window/level change.
|
|
* @param {Object} event The event fired when changing the window/level.
|
|
*/
|
|
this.onWLChange = function (event)
|
|
{
|
|
// generate and draw if no skip flag
|
|
if (typeof event.skipGenerate === "undefined" ||
|
|
event.skipGenerate === false) {
|
|
generateAndDrawImage();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle colour map change.
|
|
* @param {Object} event The event fired when changing the colour map.
|
|
*/
|
|
this.onColourChange = function (/*event*/)
|
|
{
|
|
generateAndDrawImage();
|
|
};
|
|
|
|
/**
|
|
* Handle frame change.
|
|
* @param {Object} event The event fired when changing the frame.
|
|
*/
|
|
this.onFrameChange = function (/*event*/)
|
|
{
|
|
generateAndDrawImage();
|
|
if ( drawController ) {
|
|
drawController.activateDrawLayer(viewController);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle slice change.
|
|
* @param {Object} event The event fired when changing the slice.
|
|
*/
|
|
this.onSliceChange = function (/*event*/)
|
|
{
|
|
generateAndDrawImage();
|
|
if ( drawController ) {
|
|
drawController.activateDrawLayer(viewController);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* - CRTL-Z: undo
|
|
* - CRTL-Y: redo
|
|
* - CRTL-ARROW_LEFT: next frame
|
|
* - CRTL-ARROW_UP: next slice
|
|
* - CRTL-ARROW_RIGHT: previous frame
|
|
* - CRTL-ARROW_DOWN: previous slice
|
|
* Default behavior. Usually used in tools.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.onKeydown = function (event)
|
|
{
|
|
if (event.ctrlKey) {
|
|
if ( event.keyCode === 37 ) // crtl-arrow-left
|
|
{
|
|
event.preventDefault();
|
|
self.getViewController().decrementFrameNb();
|
|
}
|
|
else if ( event.keyCode === 38 ) // crtl-arrow-up
|
|
{
|
|
event.preventDefault();
|
|
self.getViewController().incrementSliceNb();
|
|
}
|
|
else if ( event.keyCode === 39 ) // crtl-arrow-right
|
|
{
|
|
event.preventDefault();
|
|
self.getViewController().incrementFrameNb();
|
|
}
|
|
else if ( event.keyCode === 40 ) // crtl-arrow-down
|
|
{
|
|
event.preventDefault();
|
|
self.getViewController().decrementSliceNb();
|
|
}
|
|
else if ( event.keyCode === 89 ) // crtl-y
|
|
{
|
|
undoStack.redo();
|
|
}
|
|
else if ( event.keyCode === 90 ) // crtl-z
|
|
{
|
|
undoStack.undo();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle resize.
|
|
* Fit the display to the window. To be called once the image is loaded.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onResize = function (/*event*/)
|
|
{
|
|
self.fitToSize(dwv.gui.getWindowSize());
|
|
};
|
|
|
|
/**
|
|
* Handle zoom reset.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onZoomReset = function (/*event*/)
|
|
{
|
|
self.resetLayout();
|
|
};
|
|
|
|
/**
|
|
* Handle loader change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeLoader = function (/*event*/)
|
|
{
|
|
// called from an HTML select, use its value
|
|
loadbox.displayLoader( this.value );
|
|
};
|
|
|
|
/**
|
|
* Reset the load box to its original state.
|
|
*/
|
|
this.resetLoadbox = function ()
|
|
{
|
|
loadbox.reset();
|
|
};
|
|
|
|
/**
|
|
* Handle change url event.
|
|
* @param {Object} event The event fired when changing the url field.
|
|
*/
|
|
this.onChangeURL = function (event)
|
|
{
|
|
self.loadURLs([event.target.value]);
|
|
};
|
|
|
|
/**
|
|
* Handle input urls.
|
|
* @param {Array} urls The list of input urls.
|
|
* @param {Array} requestHeaders An array of {name, value} to use as request headers.
|
|
*/
|
|
this.onInputURLs = function (urls, requestHeaders)
|
|
{
|
|
self.loadURLs(urls, requestHeaders);
|
|
};
|
|
|
|
/**
|
|
* Handle change files event.
|
|
* @param {Object} event The event fired when changing the file field.
|
|
*/
|
|
this.onChangeFiles = function (event)
|
|
{
|
|
var files = event.target.files;
|
|
if ( files.length !== 0 ) {
|
|
self.loadFiles(files);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle state save event.
|
|
* @param {Object} event The event fired when changing the state save field.
|
|
*/
|
|
this.onStateSave = function (/*event*/)
|
|
{
|
|
var state = new dwv.State(self);
|
|
// add href to link (html5)
|
|
var element = self.getElement("download-state");
|
|
element.href = "data:application/json," + state.toJSON();
|
|
};
|
|
|
|
/**
|
|
* Handle colour map change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeColourMap = function (/*event*/)
|
|
{
|
|
// called from an HTML select, use its value
|
|
viewController.setColourMapFromName(this.value);
|
|
};
|
|
|
|
/**
|
|
* Handle window/level preset change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeWindowLevelPreset = function (/*event*/)
|
|
{
|
|
// value should be the name of the preset
|
|
viewController.setWindowLevelPreset( this.value );
|
|
};
|
|
|
|
/**
|
|
* Handle tool change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeTool = function (/*event*/)
|
|
{
|
|
// called from an HTML select, use its value
|
|
toolboxController.setSelectedTool(this.value);
|
|
};
|
|
|
|
/**
|
|
* Handle shape change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeShape = function (/*event*/)
|
|
{
|
|
// called from an HTML select, use its value
|
|
toolboxController.setSelectedShape(this.value);
|
|
};
|
|
|
|
/**
|
|
* Handle filter change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeFilter = function (/*event*/)
|
|
{
|
|
// called from an HTML select, use its value
|
|
toolboxController.setSelectedFilter(this.value);
|
|
};
|
|
|
|
/**
|
|
* Handle filter run.
|
|
* @param {Object} event The run event.
|
|
*/
|
|
this.onRunFilter = function (/*event*/)
|
|
{
|
|
toolboxController.runSelectedFilter();
|
|
};
|
|
|
|
/**
|
|
* Handle line colour change.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onChangeLineColour = function (/*event*/)
|
|
{
|
|
// called from an HTML select, use its value
|
|
toolboxController.setLineColour(this.value);
|
|
};
|
|
|
|
/**
|
|
* Handle min/max slider change.
|
|
* @param {Object} range The new range of the data.
|
|
*/
|
|
this.onChangeMinMax = function (range)
|
|
{
|
|
toolboxController.setRange(range);
|
|
};
|
|
|
|
/**
|
|
* Handle undo.
|
|
* @param {Object} event The associated event.
|
|
*/
|
|
this.onUndo = function (/*event*/)
|
|
{
|
|
undoStack.undo();
|
|
};
|
|
|
|
/**
|
|
* Handle redo.
|
|
* @param {Object} event The associated event.
|
|
*/
|
|
this.onRedo = function (/*event*/)
|
|
{
|
|
undoStack.redo();
|
|
};
|
|
|
|
/**
|
|
* Handle toggle of info layer.
|
|
* @param {Object} event The associated event.
|
|
*/
|
|
this.onToggleInfoLayer = function (/*event*/)
|
|
{
|
|
self.toggleInfoLayerDisplay();
|
|
};
|
|
|
|
/**
|
|
* Handle display reset.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
this.onDisplayReset = function (/*event*/)
|
|
{
|
|
self.resetLayout();
|
|
self.initWLDisplay();
|
|
// update preset select
|
|
var select = self.getElement("presetSelect");
|
|
if (select) {
|
|
select.selectedIndex = 0;
|
|
dwv.gui.refreshElement(select);
|
|
}
|
|
};
|
|
|
|
|
|
// Private Methods -----------------------------------------------------------
|
|
|
|
/**
|
|
* Fire an event: call all associated listeners.
|
|
* @param {Object} event The event to fire.
|
|
*/
|
|
function fireEvent (event)
|
|
{
|
|
if ( typeof listeners[event.type] === "undefined" ) {
|
|
return;
|
|
}
|
|
for ( var i = 0; i < listeners[event.type].length; ++i )
|
|
{
|
|
listeners[event.type][i](event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate the image data and draw it.
|
|
*/
|
|
function generateAndDrawImage()
|
|
{
|
|
// generate image data from DICOM
|
|
view.generateImageData(imageData);
|
|
// set the image data of the layer
|
|
imageLayer.setImageData(imageData);
|
|
// draw the image
|
|
imageLayer.draw();
|
|
}
|
|
|
|
/**
|
|
* Apply the stored zoom to the layers.
|
|
*/
|
|
function zoomLayers()
|
|
{
|
|
// image layer
|
|
if( imageLayer ) {
|
|
imageLayer.zoom(scale, scale, scaleCenter.x, scaleCenter.y);
|
|
imageLayer.draw();
|
|
}
|
|
// draw layer
|
|
if( drawController ) {
|
|
drawController.zoomStage(scale, scaleCenter);
|
|
}
|
|
// fire event
|
|
fireEvent({"type": "zoom-change", "scale": scale, "cx": scaleCenter.x, "cy": scaleCenter.y });
|
|
}
|
|
|
|
/**
|
|
* Apply the stored translation to the layers.
|
|
*/
|
|
function translateLayers()
|
|
{
|
|
// image layer
|
|
if( imageLayer ) {
|
|
imageLayer.translate(translation.x, translation.y);
|
|
imageLayer.draw();
|
|
// draw layer
|
|
if( drawController ) {
|
|
var ox = - imageLayer.getOrigin().x / scale - translation.x;
|
|
var oy = - imageLayer.getOrigin().y / scale - translation.y;
|
|
drawController.translateStage(ox, oy);
|
|
}
|
|
// fire event
|
|
fireEvent({"type": "offset-change", "scale": scale,
|
|
"cx": imageLayer.getTrans().x, "cy": imageLayer.getTrans().y });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a drag over.
|
|
* @private
|
|
* @param {Object} event The event to handle.
|
|
*/
|
|
function onDragOver(event)
|
|
{
|
|
// prevent default handling
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
// update box
|
|
var box = self.getElement("dropBox");
|
|
if ( box ) {
|
|
box.className = 'dropBox hover';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a drag leave.
|
|
* @private
|
|
* @param {Object} event The event to handle.
|
|
*/
|
|
function onDragLeave(event)
|
|
{
|
|
// prevent default handling
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
// update box
|
|
var box = self.getElement("dropBox hover");
|
|
if ( box ) {
|
|
box.className = 'dropBox';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a drop event.
|
|
* @private
|
|
* @param {Object} event The event to handle.
|
|
*/
|
|
function onDrop(event)
|
|
{
|
|
// prevent default handling
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
// load files
|
|
self.loadFiles(event.dataTransfer.files);
|
|
}
|
|
|
|
/**
|
|
* Handle an error: display it to the user.
|
|
* @private
|
|
* @param {Object} error The error to handle.
|
|
*/
|
|
function handleError(error)
|
|
{
|
|
// alert window
|
|
if ( error.name && error.message) {
|
|
alert(error.name+": "+error.message);
|
|
}
|
|
else {
|
|
alert("Error: "+error+".");
|
|
}
|
|
// log
|
|
if ( error.stack ) {
|
|
console.error(error.stack);
|
|
}
|
|
// stop progress
|
|
dwv.gui.displayProgress(100);
|
|
}
|
|
|
|
/**
|
|
* Handle an abort: display it to the user.
|
|
* @param {Object} error The error to handle.
|
|
* @private
|
|
*/
|
|
function handleAbort(error)
|
|
{
|
|
// log
|
|
if ( error.message ) {
|
|
console.warn(error.message);
|
|
}
|
|
else {
|
|
console.warn("Abort called.");
|
|
}
|
|
// stop progress
|
|
dwv.gui.displayProgress(100);
|
|
}
|
|
|
|
/**
|
|
* Handle a load progress.
|
|
* @private
|
|
* @param {Object} event The event to handle.
|
|
*/
|
|
function onLoadProgress(event)
|
|
{
|
|
fireEvent(event);
|
|
if( event.lengthComputable )
|
|
{
|
|
var percent = Math.ceil((event.loaded / event.total) * 100);
|
|
dwv.gui.displayProgress(percent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create the application layers.
|
|
* @private
|
|
* @param {Number} dataWidth The width of the input data.
|
|
* @param {Number} dataHeight The height of the input data.
|
|
*/
|
|
function createLayers(dataWidth, dataHeight)
|
|
{
|
|
// image layer
|
|
var canImgLay = self.getElement("imageLayer");
|
|
imageLayer = new dwv.html.Layer(canImgLay);
|
|
imageLayer.initialise(dataWidth, dataHeight);
|
|
imageLayer.fillContext();
|
|
imageLayer.setStyleDisplay(true);
|
|
// draw layer
|
|
var drawDiv = self.getElement("drawDiv");
|
|
if ( drawDiv ) {
|
|
drawController = new dwv.DrawController(drawDiv);
|
|
drawController.create(dataWidth, dataHeight);
|
|
}
|
|
// resize app
|
|
if ( fitToWindow ) {
|
|
self.fitToSize( dwv.gui.getWindowSize() );
|
|
}
|
|
else {
|
|
self.fitToSize( {
|
|
'width': self.getElement("layerContainer").offsetWidth,
|
|
'height': self.getElement("layerContainer").offsetHeight } );
|
|
}
|
|
self.resetLayout();
|
|
}
|
|
|
|
/**
|
|
* Post load application initialisation. To be called once the DICOM has been parsed.
|
|
* @private
|
|
* @param {Object} data The data to display.
|
|
*/
|
|
function postLoadInit(data)
|
|
{
|
|
// only initialise the first time
|
|
if ( view ) {
|
|
return;
|
|
}
|
|
|
|
// get the view from the loaded data
|
|
view = data.view;
|
|
viewController = new dwv.ViewController(view);
|
|
|
|
// append the DICOM tags table
|
|
if ( tagsGui ) {
|
|
tagsGui.update(data.info);
|
|
}
|
|
// store image
|
|
originalImage = view.getImage();
|
|
image = originalImage;
|
|
|
|
// layout
|
|
var size = image.getGeometry().getSize();
|
|
dataWidth = size.getNumberOfColumns();
|
|
dataHeight = size.getNumberOfRows();
|
|
createLayers(dataWidth, dataHeight);
|
|
|
|
// get the image data from the image layer
|
|
imageData = imageLayer.getContext().createImageData(
|
|
dataWidth, dataHeight);
|
|
|
|
// image listeners
|
|
view.addEventListener("wl-width-change", self.onWLChange);
|
|
view.addEventListener("wl-center-change", self.onWLChange);
|
|
view.addEventListener("colour-change", self.onColourChange);
|
|
view.addEventListener("slice-change", self.onSliceChange);
|
|
view.addEventListener("frame-change", self.onFrameChange);
|
|
|
|
// connect with local listeners
|
|
view.addEventListener("wl-width-change", fireEvent);
|
|
view.addEventListener("wl-center-change", fireEvent);
|
|
view.addEventListener("colour-change", fireEvent);
|
|
view.addEventListener("position-change", fireEvent);
|
|
view.addEventListener("slice-change", fireEvent);
|
|
view.addEventListener("frame-change", fireEvent);
|
|
|
|
// append draw layers (before initialising the toolbox)
|
|
if ( drawController ) {
|
|
drawController.appendDrawLayer(image.getNumberOfFrames());
|
|
}
|
|
|
|
// initialise the toolbox
|
|
if ( toolboxController ) {
|
|
toolboxController.initAndDisplay( imageLayer );
|
|
}
|
|
|
|
// stop box listening to drag (after first drag)
|
|
var box = self.getElement("dropBox");
|
|
if ( box ) {
|
|
box.removeEventListener("dragover", onDragOver);
|
|
box.removeEventListener("dragleave", onDragLeave);
|
|
box.removeEventListener("drop", onDrop);
|
|
dwv.html.removeNode(box);
|
|
// switch listening to layerContainer
|
|
var div = self.getElement("layerContainer");
|
|
div.addEventListener("dragover", onDragOver);
|
|
div.addEventListener("dragleave", onDragLeave);
|
|
div.addEventListener("drop", onDrop);
|
|
}
|
|
|
|
// info layer
|
|
var infoLayer = self.getElement("infoLayer");
|
|
if ( infoLayer ) {
|
|
infoController = new dwv.InfoController(containerDivId);
|
|
infoController.create(self);
|
|
infoController.toggleListeners(self, view);
|
|
}
|
|
|
|
// init W/L display
|
|
self.initWLDisplay();
|
|
// generate first image
|
|
generateAndDrawImage();
|
|
}
|
|
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Draw controller.
|
|
* @constructor
|
|
* @param {Object} drawDiv The HTML div used to store the drawings.
|
|
* @external Konva
|
|
*/
|
|
dwv.DrawController = function (drawDiv)
|
|
{
|
|
|
|
// Draw stage
|
|
var drawStage = null;
|
|
// Draw layers: 2 dimension array: [slice][frame]
|
|
var drawLayers = [];
|
|
|
|
// current slice position
|
|
var currentSlice = 0;
|
|
// current frame position
|
|
var currentFrame = 0;
|
|
|
|
/**
|
|
* Create the controller: sets up the draw stage.
|
|
* @param {Number} width The width of the stage.
|
|
* @param {Number} height The height of the stage.
|
|
*/
|
|
this.create = function (width, height) {
|
|
// create stage
|
|
drawStage = new Konva.Stage({
|
|
'container': drawDiv,
|
|
'width': width,
|
|
'height': height,
|
|
'listening': false
|
|
});
|
|
// reset style
|
|
// (avoids a not needed vertical scrollbar)
|
|
drawStage.getContent().setAttribute("style", "");
|
|
};
|
|
|
|
/**
|
|
* Get the current draw layer.
|
|
* @return {Object} The draw layer.
|
|
*/
|
|
this.getCurrentDrawLayer = function () {
|
|
//return this.getDrawLayer(currentSlice, currentFrame);
|
|
return drawLayers[currentSlice][currentFrame];
|
|
};
|
|
|
|
/**
|
|
* Reset: clear the layers array.
|
|
*/
|
|
this.reset = function () {
|
|
drawLayers = [];
|
|
};
|
|
|
|
/**
|
|
* Get the draw stage.
|
|
* @return {Object} The draw layer.
|
|
*/
|
|
this.getDrawStage = function () {
|
|
return drawStage;
|
|
};
|
|
|
|
/**
|
|
* Activate the current draw layer.
|
|
* @param {Object} viewController The associated view controller.
|
|
*/
|
|
this.activateDrawLayer = function (viewController)
|
|
{
|
|
// hide all draw layers
|
|
for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) {
|
|
for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) {
|
|
drawLayers[k][f].visible( false );
|
|
}
|
|
}
|
|
// set current position
|
|
currentSlice = viewController.getCurrentPosition().k;
|
|
currentFrame = viewController.getCurrentFrame();
|
|
// show current draw layer
|
|
var currentLayer = this.getCurrentDrawLayer();
|
|
currentLayer.visible( true );
|
|
currentLayer.draw();
|
|
};
|
|
|
|
/**
|
|
* Reset the stage with a new window scale.
|
|
* @param {Number} windowScale The window scale.
|
|
*/
|
|
this.resetStage = function (windowScale) {
|
|
drawStage.offset( {'x': 0, 'y': 0} );
|
|
drawStage.scale( {'x': windowScale, 'y': windowScale} );
|
|
drawStage.draw();
|
|
};
|
|
|
|
/**
|
|
* Resize the current stage.
|
|
* @param {Number} width the stage width.
|
|
* @param {Number} height the stage height.
|
|
* @param {Number} scale the stage scale.
|
|
*/
|
|
this.resizeStage = function (width, height, scale) {
|
|
// resize div
|
|
drawDiv.setAttribute("style","width:"+width+"px;height:"+height+"px");
|
|
// resize stage
|
|
drawStage.setWidth(width);
|
|
drawStage.setHeight(height);
|
|
drawStage.scale( {'x': scale, 'y': scale} );
|
|
drawStage.draw();
|
|
};
|
|
|
|
/**
|
|
* Zoom the stage.
|
|
* @param {Number} scale The scale factor.
|
|
* @param {Object} scaleCenter The scale center point.
|
|
*/
|
|
this.zoomStage = function (scale, scaleCenter) {
|
|
// zoom
|
|
var newScale = {'x': scale, 'y': scale};
|
|
// offset
|
|
// TODO different from the imageLayer offset?
|
|
var oldScale = drawStage.scale();
|
|
var oldOffset = drawStage.offset();
|
|
var newOffsetX = (scaleCenter.x / oldScale.x) +
|
|
oldOffset.x - (scaleCenter.x / newScale.x);
|
|
var newOffsetY = (scaleCenter.y / oldScale.y) +
|
|
oldOffset.y - (scaleCenter.y / newScale.y);
|
|
var newOffset = {'x': newOffsetX, 'y': newOffsetY};
|
|
// store
|
|
drawStage.offset( newOffset );
|
|
drawStage.scale( newScale );
|
|
drawStage.draw();
|
|
};
|
|
|
|
/**
|
|
* Translate the stage.
|
|
* @param {Number} tx The X translation.
|
|
* @param {Number} ty The Y translation.
|
|
*/
|
|
this.translateStage = function (tx, ty) {
|
|
drawStage.offset( {'x': tx, 'y': ty} );
|
|
drawStage.draw();
|
|
};
|
|
|
|
/**
|
|
* Append a new draw layer list to the list.
|
|
* @param {Number} nLayers The size of the layers array to append to the current one.
|
|
*/
|
|
this.appendDrawLayer = function (nLayers) {
|
|
// add a new dimension
|
|
drawLayers.push([]);
|
|
// fill it
|
|
for (var i = 0; i < nLayers; ++i) {
|
|
// create draw layer
|
|
var drawLayer = new Konva.Layer({
|
|
'listening': false,
|
|
'hitGraphEnabled': false,
|
|
'visible': false
|
|
});
|
|
drawLayers[drawLayers.length - 1].push(drawLayer);
|
|
// add the layer to the stage
|
|
drawStage.add(drawLayer);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a list of drawing display details.
|
|
* @return {Object} A list of draw details including id, slice, frame...
|
|
*/
|
|
this.getDrawDisplayDetails = function ()
|
|
{
|
|
var list = [];
|
|
for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) {
|
|
for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) {
|
|
var collec = drawLayers[k][f].getChildren();
|
|
for ( var i = 0, leni = collec.length; i < leni; ++i ) {
|
|
var shape = collec[i].getChildren( isNodeNameShape )[0];
|
|
var label = collec[i].getChildren( isNodeNameLabel )[0];
|
|
var text = label.getChildren()[0];
|
|
var type = shape.className;
|
|
if (type === "Line") {
|
|
var shapeExtrakids = collec[i].getChildren( isNodeNameShapeExtra );
|
|
if (shape.closed()) {
|
|
type = "Roi";
|
|
} else if (shapeExtrakids.length !== 0) {
|
|
if ( shapeExtrakids[0].name().indexOf("triangle") !== -1 ) {
|
|
type = "Arrow";
|
|
}
|
|
else {
|
|
type = "Ruler";
|
|
}
|
|
}
|
|
}
|
|
if (type === "Rect") {
|
|
type = "Rectangle";
|
|
}
|
|
list.push( {
|
|
"id": collec[i].id(),
|
|
"slice": k,
|
|
"frame": f,
|
|
"type": type,
|
|
"color": shape.stroke(),
|
|
"label": text.textExpr,
|
|
"description": text.longText
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// return
|
|
return list;
|
|
};
|
|
|
|
/**
|
|
* Get all the draws of the stage.
|
|
*/
|
|
this.getDraws = function ()
|
|
{
|
|
var drawGroups = [];
|
|
for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) {
|
|
drawGroups[k] = [];
|
|
for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) {
|
|
// getChildren always return, so drawings will have the good size
|
|
var groups = drawLayers[k][f].getChildren();
|
|
drawGroups[k].push(groups);
|
|
}
|
|
}
|
|
return drawGroups;
|
|
};
|
|
|
|
/**
|
|
* Get a list of drawing store details.
|
|
* @return {Object} A list of draw details including id, text, quant...
|
|
* TODO Unify with getDrawDisplayDetails?
|
|
*/
|
|
this.getDrawStoreDetails = function ()
|
|
{
|
|
var drawingsDetails = [];
|
|
for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) {
|
|
drawingsDetails[k] = [];
|
|
for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) {
|
|
// getChildren always return, so drawings will have the good size
|
|
var groups = drawLayers[k][f].getChildren();
|
|
var details = [];
|
|
for ( var i = 0, leni = groups.length; i < leni; ++i ) {
|
|
// remove anchors
|
|
var anchors = groups[i].find(".anchor");
|
|
for ( var a = 0; a < anchors.length; ++a ) {
|
|
anchors[a].remove();
|
|
}
|
|
// get text
|
|
var texts = groups[i].find(".text");
|
|
if ( texts.length !== 1 ) {
|
|
console.warn("There should not be more than one text per shape.");
|
|
}
|
|
// get details (non konva vars)
|
|
details.push({
|
|
"id": groups[i].id(),
|
|
"textExpr": encodeURIComponent(texts[0].textExpr),
|
|
"longText": encodeURIComponent(texts[0].longText),
|
|
"quant": texts[0].quant
|
|
});
|
|
}
|
|
drawingsDetails[k].push(details);
|
|
}
|
|
}
|
|
return drawingsDetails;
|
|
};
|
|
|
|
/**
|
|
* Set the drawings on the current stage.
|
|
* @param {Array} drawings An array of drawings.
|
|
* @param {Array} drawingsDetails An array of drawings details.
|
|
* @param {Object} cmdCallback The DrawCommand callback.
|
|
* @param {Object} exeCallback The callback to call once the DrawCommand has been executed.
|
|
*/
|
|
this.setDrawings = function (drawings, drawingsDetails, cmdCallback, exeCallback)
|
|
{
|
|
// loop through layers
|
|
for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) {
|
|
for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) {
|
|
for ( var i = 0, leni = drawings[k][f].length; i < leni; ++i ) {
|
|
// create the group
|
|
var group = Konva.Node.create(drawings[k][f][i]);
|
|
var shape = group.getChildren( isNodeNameShape )[0];
|
|
// create the draw command
|
|
var cmd = new dwv.tool.DrawGroupCommand(
|
|
group, shape.className,
|
|
drawLayers[k][f] );
|
|
// draw command callbacks
|
|
cmd.onExecute = cmdCallback;
|
|
cmd.onUndo = cmdCallback;
|
|
// text (new in v0.2)
|
|
// TODO Verify ID?
|
|
if (drawingsDetails) {
|
|
var details = drawingsDetails[k][f][i];
|
|
var label = group.getChildren( isNodeNameLabel )[0];
|
|
var text = label.getText();
|
|
// store details
|
|
text.textExpr = details.textExpr;
|
|
text.longText = details.longText;
|
|
text.quant = details.quant;
|
|
// reset text (it was not encoded)
|
|
text.setText(dwv.utils.replaceFlags(text.textExpr, text.quant));
|
|
}
|
|
// execute
|
|
cmd.execute();
|
|
exeCallback(cmd);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update a drawing from its details.
|
|
* @param {Object} drawDetails Details of the drawing to update.
|
|
*/
|
|
this.updateDraw = function (drawDetails)
|
|
{
|
|
// get the group
|
|
var group = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id);
|
|
// shape
|
|
var shapes = group.getChildren( isNodeNameShape );
|
|
for (var i = 0; i < shapes.length; ++i ) {
|
|
shapes[i].stroke(drawDetails.color);
|
|
}
|
|
// shape extra
|
|
var shapesExtra = group.getChildren( isNodeNameShapeExtra );
|
|
for (var j = 0; j < shapesExtra.length; ++j ) {
|
|
if (typeof shapesExtra[j].stroke() !== "undefined") {
|
|
shapesExtra[j].stroke(drawDetails.color);
|
|
}
|
|
else if (typeof shapesExtra[j].fill() !== "undefined") {
|
|
shapesExtra[j].fill(drawDetails.color);
|
|
}
|
|
}
|
|
// label
|
|
var label = group.getChildren( isNodeNameLabel )[0];
|
|
var text = label.getChildren()[0];
|
|
text.fill(drawDetails.color);
|
|
text.textExpr = drawDetails.label;
|
|
text.longText = drawDetails.description;
|
|
text.setText(dwv.utils.replaceFlags(text.textExpr, text.quant));
|
|
|
|
// udpate current layer
|
|
this.getCurrentDrawLayer().draw();
|
|
};
|
|
|
|
/**
|
|
* Check the visibility of a given group.
|
|
* @param {Object} drawDetails Details of the group to check.
|
|
*/
|
|
this.isGroupVisible = function (drawDetails) {
|
|
// get the group
|
|
var group = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id);
|
|
// get visibility
|
|
return group.isVisible();
|
|
};
|
|
|
|
/**
|
|
* Toggle the visibility of a given group.
|
|
* @param {Object} drawDetails Details of the group to update.
|
|
*/
|
|
this.toogleGroupVisibility = function (drawDetails) {
|
|
// get the group
|
|
var group = getDrawGroup(drawDetails.slice, drawDetails.frame, drawDetails.id);
|
|
// toggle visible
|
|
group.visible(!group.isVisible());
|
|
|
|
// udpate current layer
|
|
this.getCurrentDrawLayer().draw();
|
|
};
|
|
|
|
/**
|
|
* Delete all Draws from the stage.
|
|
* @param {Object} cmdCallback The DeleteCommand callback.
|
|
* @param {Object} exeCallback The callback to call once the DeleteCommand has been executed.
|
|
*/
|
|
this.deleteDraws = function (cmdCallback, exeCallback) {
|
|
var delcmd, layer, groups;
|
|
for ( var k = 0, lenk = drawLayers.length; k < lenk; ++k ) {
|
|
for ( var f = 0, lenf = drawLayers[k].length; f < lenf; ++f ) {
|
|
layer = drawLayers[k][f];
|
|
groups = layer.getChildren();
|
|
while (groups.length) {
|
|
var shape = groups[0].getChildren( isNodeNameShape )[0];
|
|
delcmd = new dwv.tool.DeleteGroupCommand( groups[0],
|
|
dwv.tool.GetShapeDisplayName(shape), layer);
|
|
delcmd.onExecute = cmdCallback;
|
|
delcmd.onUndo = cmdCallback;
|
|
delcmd.execute();
|
|
exeCallback(delcmd);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a draw group.
|
|
* @param {Number} slice The slice position.
|
|
* @param {Number} frame The frame position.
|
|
* @param {Number} id The group id.
|
|
*/
|
|
function getDrawGroup(slice, frame, id) {
|
|
var layer = drawLayers[slice][frame];
|
|
//var collec = layer.getChildren()[drawDetails.id];
|
|
var collec = layer.getChildren( function (node) {
|
|
return node.id() === id;
|
|
});
|
|
|
|
var res = null;
|
|
if (collec.length !== 0) {
|
|
res = collec[0];
|
|
}
|
|
else {
|
|
console.warn("Could not find draw group for slice='" +
|
|
slice + "', frame='" + frame + "', id='" + id + "'.");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Is an input node's name 'shape'.
|
|
* @param {Object} node A Konva node.
|
|
*/
|
|
function isNodeNameShape( node ) {
|
|
return node.name() === "shape";
|
|
}
|
|
|
|
/**
|
|
* Is a node an extra shape associated with a main one.
|
|
* @param {Object} node A Konva node.
|
|
*/
|
|
function isNodeNameShapeExtra( node ) {
|
|
return node.name().startsWith("shape-");
|
|
}
|
|
|
|
/**
|
|
* Is an input node's name 'label'.
|
|
* @param {Object} node A Konva node.
|
|
*/
|
|
function isNodeNameLabel( node ) {
|
|
return node.name() === "label";
|
|
}
|
|
|
|
}; // class dwv.DrawController
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
|
|
/**
|
|
* Info controller.
|
|
* @constructor
|
|
*/
|
|
dwv.InfoController = function (containerDivId)
|
|
{
|
|
|
|
// Info layer plot gui
|
|
var plotInfo = null;
|
|
// Info layer colour map gui
|
|
var miniColourMap = null;
|
|
// Info layer overlay
|
|
var overlayInfos = [];
|
|
// flag to know if the info layer is listening on the image.
|
|
var isInfoLayerListening = false;
|
|
|
|
/**
|
|
* Create the different info elements.
|
|
* TODO Get rid of the app input arg...
|
|
*/
|
|
this.create = function (app)
|
|
{
|
|
var infocm = getElement("infocm");
|
|
if (infocm) {
|
|
miniColourMap = new dwv.gui.info.MiniColourMap(infocm, app);
|
|
miniColourMap.create();
|
|
}
|
|
|
|
// create overlay info at each corner
|
|
var pos_list = [
|
|
"tl", "tc", "tr",
|
|
"cl", "cr",
|
|
"bl", "bc", "br" ];
|
|
|
|
var num = 0;
|
|
for (var n=0; n<pos_list.length; n++){
|
|
var pos = pos_list[n];
|
|
var info = getElement("info" + pos);
|
|
if (info) {
|
|
overlayInfos[num] = new dwv.gui.info.Overlay(info, pos, app);
|
|
overlayInfos[num].create();
|
|
num++;
|
|
}
|
|
}
|
|
|
|
var plot = getElement("plot");
|
|
if (plot) {
|
|
plotInfo = new dwv.gui.info.Plot(plot, app);
|
|
plotInfo.create();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Toggle info listeners to the app and the view.
|
|
* @param {Object} app The app to listen or not to.
|
|
* @param {Object} view The view to listen or not to.
|
|
*/
|
|
this.toggleListeners = function (app, view)
|
|
{
|
|
if (isInfoLayerListening) {
|
|
removeListeners(app, view);
|
|
}
|
|
else {
|
|
addListeners(app, view);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a HTML element associated to the application.
|
|
* @param name The name or id to find.
|
|
* @return The found element or null.
|
|
*/
|
|
function getElement(name)
|
|
{
|
|
return dwv.gui.getElement(containerDivId, name);
|
|
}
|
|
|
|
/**
|
|
* Add info listeners to the view.
|
|
* @param {Object} app The app to listen to.
|
|
* @param {Object} view The view to listen to.
|
|
*/
|
|
function addListeners(app, view)
|
|
{
|
|
if (plotInfo) {
|
|
view.addEventListener("wl-width-change", plotInfo.update);
|
|
view.addEventListener("wl-center-change", plotInfo.update);
|
|
}
|
|
if (miniColourMap) {
|
|
view.addEventListener("wl-width-change", miniColourMap.update);
|
|
view.addEventListener("wl-center-change", miniColourMap.update);
|
|
view.addEventListener("colour-change", miniColourMap.update);
|
|
}
|
|
if (overlayInfos.length > 0){
|
|
for (var n=0; n<overlayInfos.length; n++){
|
|
app.addEventListener("zoom-change", overlayInfos[n].update);
|
|
view.addEventListener("wl-width-change", overlayInfos[n].update);
|
|
view.addEventListener("wl-center-change", overlayInfos[n].update);
|
|
view.addEventListener("position-change", overlayInfos[n].update);
|
|
view.addEventListener("frame-change", overlayInfos[n].update);
|
|
}
|
|
}
|
|
// udpate listening flag
|
|
isInfoLayerListening = true;
|
|
}
|
|
|
|
/**
|
|
* Remove info listeners to the view.
|
|
* @param {Object} app The app to stop listening to.
|
|
* @param {Object} view The view to stop listening to.
|
|
*/
|
|
function removeListeners(app, view)
|
|
{
|
|
if (plotInfo) {
|
|
view.removeEventListener("wl-width-change", plotInfo.update);
|
|
view.removeEventListener("wl-center-change", plotInfo.update);
|
|
}
|
|
if (miniColourMap) {
|
|
view.removeEventListener("wl-width-change", miniColourMap.update);
|
|
view.removeEventListener("wl-center-change", miniColourMap.update);
|
|
view.removeEventListener("colour-change", miniColourMap.update);
|
|
}
|
|
if (overlayInfos.length > 0){
|
|
for (var n=0; n<overlayInfos.length; n++){
|
|
app.removeEventListener("zoom-change", overlayInfos[n].update);
|
|
view.removeEventListener("wl-width-change", overlayInfos[n].update);
|
|
view.removeEventListener("wl-center-change", overlayInfos[n].update);
|
|
view.removeEventListener("position-change", overlayInfos[n].update);
|
|
view.removeEventListener("frame-change", overlayInfos[n].update);
|
|
}
|
|
}
|
|
// udpate listening flag
|
|
isInfoLayerListening = false;
|
|
}
|
|
|
|
}; // class dwv.InfoController
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
|
|
/**
|
|
* State class.
|
|
* Saves: data url/path, display info.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.State = function (app)
|
|
{
|
|
/**
|
|
* Save the application state as JSON.
|
|
*/
|
|
this.toJSON = function () {
|
|
// store each slice drawings group
|
|
var drawings = app.getDraws();
|
|
var drawingsDetails = app.getDrawStoreDetails();
|
|
// return a JSON string
|
|
return JSON.stringify( {
|
|
"version": "0.2",
|
|
"window-center": app.getViewController().getWindowLevel().center,
|
|
"window-width": app.getViewController().getWindowLevel().width,
|
|
"position": app.getViewController().getCurrentPosition(),
|
|
"scale": app.getScale(),
|
|
"scaleCenter": app.getScaleCenter(),
|
|
"translation": app.getTranslation(),
|
|
"drawings": drawings,
|
|
// new in v0.2
|
|
"drawingsDetails": drawingsDetails
|
|
} );
|
|
};
|
|
/**
|
|
* Load an application state from JSON.
|
|
* @param {String} json The JSON representation of the state.
|
|
*/
|
|
this.fromJSON = function (json) {
|
|
var data = JSON.parse(json);
|
|
if (data.version === "0.1") {
|
|
readV01(data);
|
|
}
|
|
else if (data.version === "0.2") {
|
|
readV02(data);
|
|
}
|
|
else {
|
|
throw new Error("Unknown state file format version: '"+data.version+"'.");
|
|
}
|
|
};
|
|
/**
|
|
* Read an application state from an Object in v0.1 format.
|
|
* @param {Object} data The Object representation of the state.
|
|
*/
|
|
function readV01(data) {
|
|
// display
|
|
app.getViewController().setWindowLevel( data["window-center"], data["window-width"] );
|
|
app.getViewController().setCurrentPosition( data.position );
|
|
app.zoom( data.scale, data.scaleCenter.x, data.scaleCenter.y );
|
|
app.translate( data.translation.x, data.translation.y );
|
|
// drawings
|
|
app.setDrawings( data.drawings, null );
|
|
}
|
|
/**
|
|
* Read an application state from an Object in v0.2 format.
|
|
* @param {Object} data The Object representation of the state.
|
|
*/
|
|
function readV02(data) {
|
|
// display
|
|
app.getViewController().setWindowLevel( data["window-center"], data["window-width"] );
|
|
app.getViewController().setCurrentPosition( data.position );
|
|
app.zoom( data.scale, data.scaleCenter.x, data.scaleCenter.y );
|
|
app.translate( data.translation.x, data.translation.y );
|
|
// drawings
|
|
app.setDrawings( data.drawings, data.drawingsDetails );
|
|
}
|
|
}; // State class
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
|
|
/**
|
|
* Toolbox controller.
|
|
* @constructor
|
|
*/
|
|
dwv.ToolboxController = function ()
|
|
{
|
|
// internal toolbox
|
|
var toolbox = null;
|
|
// point converter function
|
|
var displayToIndexConverter = null;
|
|
|
|
/**
|
|
* Create the internal toolbox.
|
|
* @param {Array} toolList The list of tools instances.
|
|
* @param {Object} app The associated app.
|
|
*/
|
|
this.create = function (toolList, app) {
|
|
toolbox = new dwv.tool.Toolbox(toolList, app);
|
|
};
|
|
|
|
/**
|
|
* Setup the internal toolbox.
|
|
*/
|
|
this.setup = function () {
|
|
toolbox.setup();
|
|
};
|
|
|
|
/**
|
|
* Reset the internal toolbox.
|
|
*/
|
|
this.reset = function () {
|
|
toolbox.reset();
|
|
};
|
|
|
|
/**
|
|
* Initialise and display the internal toolbox.
|
|
*/
|
|
this.initAndDisplay = function (layer) {
|
|
// initialise
|
|
toolbox.init();
|
|
// display
|
|
toolbox.display(true);
|
|
// TODO Would prefer to have this done in the addLayerListeners
|
|
displayToIndexConverter = layer.displayToIndex;
|
|
// add layer listeners
|
|
this.addCanvasListeners(layer.getCanvas());
|
|
// keydown listener
|
|
window.addEventListener("keydown", onMouch, true);
|
|
};
|
|
|
|
/**
|
|
* Get the tool list.
|
|
*/
|
|
this.getToolList = function () {
|
|
return toolbox.getToolList();
|
|
};
|
|
|
|
/**
|
|
* Get the selected tool event handler.
|
|
* @param {String} eventType The event type, for example mousedown, touchstart...
|
|
*/
|
|
this.getSelectedToolEventHandler = function (eventType)
|
|
{
|
|
return toolbox.getSelectedTool()[eventType];
|
|
};
|
|
|
|
/**
|
|
* Set the selected tool.
|
|
* @param {String} name The name of the tool.
|
|
*/
|
|
this.setSelectedTool = function (name)
|
|
{
|
|
toolbox.setSelectedTool(name);
|
|
};
|
|
|
|
/**
|
|
* Set the selected shape.
|
|
* @param {String} name The name of the shape.
|
|
*/
|
|
this.setSelectedShape = function (name)
|
|
{
|
|
toolbox.getSelectedTool().setShapeName(name);
|
|
};
|
|
|
|
/**
|
|
* Set the selected filter.
|
|
* @param {String} name The name of the filter.
|
|
*/
|
|
this.setSelectedFilter = function (name)
|
|
{
|
|
toolbox.getSelectedTool().setSelectedFilter(name);
|
|
};
|
|
|
|
/**
|
|
* Run the selected filter.
|
|
*/
|
|
this.runSelectedFilter = function ()
|
|
{
|
|
toolbox.getSelectedTool().getSelectedFilter().run();
|
|
};
|
|
|
|
/**
|
|
* Set the tool line colour.
|
|
* @param {String} colour The colour.
|
|
*/
|
|
this.setLineColour = function (colour)
|
|
{
|
|
toolbox.getSelectedTool().setLineColour(colour);
|
|
};
|
|
|
|
/**
|
|
* Set the tool range.
|
|
* @param {Object} range The new range of the data.
|
|
*/
|
|
this.setRange = function (range)
|
|
{
|
|
// seems like jquery is checking if the method exists before it
|
|
// is used...
|
|
if ( toolbox && toolbox.getSelectedTool() &&
|
|
toolbox.getSelectedTool().getSelectedFilter() ) {
|
|
toolbox.getSelectedTool().getSelectedFilter().run(range);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add canvas mouse and touch listeners.
|
|
* @param {Object} canvas The canvas to listen to.
|
|
*/
|
|
this.addCanvasListeners = function (canvas)
|
|
{
|
|
// allow pointer events
|
|
canvas.setAttribute("style", "pointer-events: auto;");
|
|
// mouse listeners
|
|
canvas.addEventListener("mousedown", onMouch);
|
|
canvas.addEventListener("mousemove", onMouch);
|
|
canvas.addEventListener("mouseup", onMouch);
|
|
canvas.addEventListener("mouseout", onMouch);
|
|
canvas.addEventListener("mousewheel", onMouch);
|
|
canvas.addEventListener("DOMMouseScroll", onMouch);
|
|
canvas.addEventListener("dblclick", onMouch);
|
|
// touch listeners
|
|
canvas.addEventListener("touchstart", onMouch);
|
|
canvas.addEventListener("touchmove", onMouch);
|
|
canvas.addEventListener("touchend", onMouch);
|
|
};
|
|
|
|
/**
|
|
* Remove canvas mouse and touch listeners.
|
|
* @param {Object} canvas The canvas to stop listening to.
|
|
*/
|
|
this.removeCanvasListeners = function (canvas)
|
|
{
|
|
// disable pointer events
|
|
canvas.setAttribute("style", "pointer-events: none;");
|
|
// mouse listeners
|
|
canvas.removeEventListener("mousedown", onMouch);
|
|
canvas.removeEventListener("mousemove", onMouch);
|
|
canvas.removeEventListener("mouseup", onMouch);
|
|
canvas.removeEventListener("mouseout", onMouch);
|
|
canvas.removeEventListener("mousewheel", onMouch);
|
|
canvas.removeEventListener("DOMMouseScroll", onMouch);
|
|
canvas.removeEventListener("dblclick", onMouch);
|
|
// touch listeners
|
|
canvas.removeEventListener("touchstart", onMouch);
|
|
canvas.removeEventListener("touchmove", onMouch);
|
|
canvas.removeEventListener("touchend", onMouch);
|
|
};
|
|
|
|
/**
|
|
* Mou(se) and (T)ouch event handler. This function just determines the mouse/touch
|
|
* position relative to the canvas element. It then passes it to the current tool.
|
|
* @private
|
|
* @param {Object} event The event to handle.
|
|
*/
|
|
function onMouch(event)
|
|
{
|
|
// flag not to get confused between touch and mouse
|
|
var handled = false;
|
|
// Store the event position relative to the image canvas
|
|
// in an extra member of the event:
|
|
// event._x and event._y.
|
|
var offsets = null;
|
|
var position = null;
|
|
if ( event.type === "touchstart" ||
|
|
event.type === "touchmove")
|
|
{
|
|
// event offset(s)
|
|
offsets = dwv.html.getEventOffset(event);
|
|
// should have at least one offset
|
|
event._xs = offsets[0].x;
|
|
event._ys = offsets[0].y;
|
|
position = displayToIndexConverter( offsets[0] );
|
|
event._x = parseInt( position.x, 10 );
|
|
event._y = parseInt( position.y, 10 );
|
|
// possible second
|
|
if ( offsets.length === 2 ) {
|
|
event._x1s = offsets[1].x;
|
|
event._y1s = offsets[1].y;
|
|
position = displayToIndexConverter( offsets[1] );
|
|
event._x1 = parseInt( position.x, 10 );
|
|
event._y1 = parseInt( position.y, 10 );
|
|
}
|
|
// set handle event flag
|
|
handled = true;
|
|
}
|
|
else if ( event.type === "mousemove" ||
|
|
event.type === "mousedown" ||
|
|
event.type === "mouseup" ||
|
|
event.type === "mouseout" ||
|
|
event.type === "mousewheel" ||
|
|
event.type === "dblclick" ||
|
|
event.type === "DOMMouseScroll" )
|
|
{
|
|
offsets = dwv.html.getEventOffset(event);
|
|
event._xs = offsets[0].x;
|
|
event._ys = offsets[0].y;
|
|
position = displayToIndexConverter( offsets[0] );
|
|
event._x = parseInt( position.x, 10 );
|
|
event._y = parseInt( position.y, 10 );
|
|
// set handle event flag
|
|
handled = true;
|
|
}
|
|
else if ( event.type === "keydown" ||
|
|
event.type === "touchend")
|
|
{
|
|
handled = true;
|
|
}
|
|
|
|
// Call the event handler of the curently selected tool.
|
|
if ( handled )
|
|
{
|
|
if ( event.type !== "keydown" ) {
|
|
event.preventDefault();
|
|
}
|
|
var func = toolbox.getSelectedTool()[event.type];
|
|
if ( func )
|
|
{
|
|
func(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
}; // class dwv.ToolboxController
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
|
|
/**
|
|
* View controller.
|
|
* @constructor
|
|
*/
|
|
dwv.ViewController = function ( view )
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
// Slice/frame player ID (created by setInterval)
|
|
var playerID = null;
|
|
|
|
/**
|
|
* Get the window/level presets names.
|
|
* @return {Array} The presets names.
|
|
*/
|
|
this.getWindowLevelPresetsNames = function ()
|
|
{
|
|
return view.getWindowPresetsNames();
|
|
};
|
|
|
|
/**
|
|
* Add window/level presets to the view.
|
|
* @return {Object} The list of presets.
|
|
*/
|
|
this.addWindowLevelPresets = function (presets)
|
|
{
|
|
return view.addWindowPresets(presets);
|
|
};
|
|
|
|
/**
|
|
* Set the window level to the preset with the input name.
|
|
* @param {String} name The name of the preset to activate.
|
|
*/
|
|
this.setWindowLevelPreset = function (name)
|
|
{
|
|
view.setWindowLevelPreset(name);
|
|
};
|
|
|
|
/**
|
|
* Set the window level to the preset with the input id.
|
|
* @param {Number} id The id of the preset to activate.
|
|
*/
|
|
this.setWindowLevelPresetById = function (id)
|
|
{
|
|
view.setWindowLevelPresetById(id);
|
|
};
|
|
|
|
/**
|
|
* Check if the controller is playing.
|
|
* @return {Boolean} True is the controler is playing slices/frames.
|
|
*/
|
|
this.isPlaying = function () { return (playerID !== null); };
|
|
|
|
/**
|
|
* Get the current position.
|
|
* @return {Object} The position.
|
|
*/
|
|
this.getCurrentPosition = function ()
|
|
{
|
|
return view.getCurrentPosition();
|
|
};
|
|
|
|
/**
|
|
* Set the current position.
|
|
* @param {Object} pos The position.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.setCurrentPosition = function (pos)
|
|
{
|
|
return view.setCurrentPosition(pos);
|
|
};
|
|
|
|
/**
|
|
* Set the current 2D (i,j) position.
|
|
* @param {Number} i The column index.
|
|
* @param {Number} j The row index.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.setCurrentPosition2D = function (i, j)
|
|
{
|
|
return view.setCurrentPosition({
|
|
"i": i,
|
|
"j": j,
|
|
"k": view.getCurrentPosition().k
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Set the current slice position.
|
|
* @param {Number} k The slice index.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.setCurrentSlice = function (k)
|
|
{
|
|
return view.setCurrentPosition({
|
|
"i": view.getCurrentPosition().i,
|
|
"j": view.getCurrentPosition().j,
|
|
"k": k
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Increment the current slice number.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.incrementSliceNb = function ()
|
|
{
|
|
return self.setCurrentSlice( view.getCurrentPosition().k + 1 );
|
|
};
|
|
|
|
/**
|
|
* Decrement the current slice number.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.decrementSliceNb = function ()
|
|
{
|
|
return self.setCurrentSlice( view.getCurrentPosition().k - 1 );
|
|
};
|
|
|
|
/**
|
|
* Get the current frame.
|
|
* @return {Number} The frame number.
|
|
*/
|
|
this.getCurrentFrame = function ()
|
|
{
|
|
return view.getCurrentFrame();
|
|
};
|
|
|
|
/**
|
|
* Set the current frame.
|
|
* @param {Number} number The frame number.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.setCurrentFrame = function (number)
|
|
{
|
|
return view.setCurrentFrame(number);
|
|
};
|
|
|
|
/**
|
|
* Increment the current frame.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.incrementFrameNb = function ()
|
|
{
|
|
return view.setCurrentFrame( view.getCurrentFrame() + 1 );
|
|
};
|
|
|
|
/**
|
|
* Decrement the current frame.
|
|
* @return {Boolean} False if not in bounds.
|
|
*/
|
|
this.decrementFrameNb = function ()
|
|
{
|
|
return view.setCurrentFrame( view.getCurrentFrame() - 1 );
|
|
};
|
|
|
|
/**
|
|
* Go to first slice .
|
|
* @return {Boolean} False if not in bounds.
|
|
* @deprecated Use the setCurrentSlice function.
|
|
*/
|
|
this.goFirstSlice = function()
|
|
{
|
|
return view.setCurrentPosition({
|
|
"i": view.getCurrentPosition().i,
|
|
"j": view.getCurrentPosition().j,
|
|
"k": 0
|
|
});
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
this.play = function ()
|
|
{
|
|
if ( playerID === null ) {
|
|
var nSlices = view.getImage().getGeometry().getSize().getNumberOfSlices();
|
|
var nFrames = view.getImage().getNumberOfFrames();
|
|
|
|
playerID = setInterval( function () {
|
|
if ( nSlices !== 1 ) {
|
|
if ( !self.incrementSliceNb() ) {
|
|
self.setCurrentSlice(0);
|
|
}
|
|
} else if ( nFrames !== 1 ) {
|
|
if ( !self.incrementFrameNb() ) {
|
|
self.setCurrentFrame(0);
|
|
}
|
|
}
|
|
|
|
}, 300);
|
|
} else {
|
|
this.stop();
|
|
}
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
this.stop = function ()
|
|
{
|
|
if ( playerID !== null ) {
|
|
clearInterval(playerID);
|
|
playerID = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the window/level.
|
|
* @return {Object} The window center and width.
|
|
*/
|
|
this.getWindowLevel = function ()
|
|
{
|
|
return {
|
|
"width": view.getCurrentWindowLut().getWindowLevel().getWidth(),
|
|
"center": view.getCurrentWindowLut().getWindowLevel().getCenter()
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Set the window/level.
|
|
* @param {Number} wc The window center.
|
|
* @param {Number} ww The window width.
|
|
*/
|
|
this.setWindowLevel = function (wc, ww)
|
|
{
|
|
view.setWindowLevel(wc,ww);
|
|
};
|
|
|
|
/**
|
|
* Get the colour map.
|
|
* @return {Object} The colour map.
|
|
*/
|
|
this.getColourMap = function ()
|
|
{
|
|
return view.getColourMap();
|
|
};
|
|
|
|
/**
|
|
* Set the colour map.
|
|
* @param {Object} colourMap The colour map.
|
|
*/
|
|
this.setColourMap = function (colourMap)
|
|
{
|
|
view.setColourMap(colourMap);
|
|
};
|
|
|
|
/**
|
|
* Set the colour map from a name.
|
|
* @param {String} name The name of the colour map to set.
|
|
*/
|
|
this.setColourMapFromName = function (name)
|
|
{
|
|
// check if we have it
|
|
if ( !dwv.tool.colourMaps[name] ) {
|
|
throw new Error("Unknown colour map: '" + name + "'");
|
|
}
|
|
// enable it
|
|
this.setColourMap( dwv.tool.colourMaps[name] );
|
|
};
|
|
|
|
}; // class dwv.ViewController
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.dicom = dwv.dicom || {};
|
|
|
|
/**
|
|
* Get the version of the library.
|
|
* @return {String} The version of the library.
|
|
*/
|
|
dwv.getVersion = function () { return "0.22.1"; };
|
|
|
|
/**
|
|
* Clean string: trim and remove ending.
|
|
* @param {String} inputStr The string to clean.
|
|
* @return {String} The cleaned string.
|
|
*/
|
|
dwv.dicom.cleanString = function (inputStr)
|
|
{
|
|
var res = inputStr;
|
|
if ( inputStr ) {
|
|
// trim spaces
|
|
res = inputStr.trim();
|
|
// get rid of ending zero-width space (u200B)
|
|
if ( res[res.length-1] === String.fromCharCode("u200B") ) {
|
|
res = res.substring(0, res.length-1);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* Is the Native endianness Little Endian.
|
|
* @type Boolean
|
|
*/
|
|
dwv.dicom.isNativeLittleEndian = function ()
|
|
{
|
|
return new Int8Array(new Int16Array([1]).buffer)[0] > 0;
|
|
};
|
|
|
|
/**
|
|
* Get the utfLabel (used by the TextDecoder) from a character set term
|
|
* References:
|
|
* - DICOM [Value Encoding]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_6.html}
|
|
* - DICOM [Specific Character Set]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2}
|
|
* - [TextDecoder#Parameters]{@link https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/TextDecoder#Parameters}
|
|
*/
|
|
dwv.dicom.getUtfLabel = function (charSetTerm)
|
|
{
|
|
var label = "utf-8";
|
|
if (charSetTerm === "ISO_IR 100" ) {
|
|
label = "iso-8859-1";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 101" ) {
|
|
label = "iso-8859-2";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 109" ) {
|
|
label = "iso-8859-3";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 110" ) {
|
|
label = "iso-8859-4";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 144" ) {
|
|
label = "iso-8859-5";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 127" ) {
|
|
label = "iso-8859-6";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 126" ) {
|
|
label = "iso-8859-7";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 138" ) {
|
|
label = "iso-8859-8";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 148" ) {
|
|
label = "iso-8859-9";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 13" ) {
|
|
label = "shift-jis";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 166" ) {
|
|
label = "iso-8859-11";
|
|
}
|
|
else if (charSetTerm === "ISO 2022 IR 87" ) {
|
|
label = "iso-2022-jp";
|
|
}
|
|
else if (charSetTerm === "ISO 2022 IR 149" ) {
|
|
// not supported by TextDecoder when it says it should...
|
|
//label = "iso-2022-kr";
|
|
}
|
|
else if (charSetTerm === "ISO 2022 IR 58") {
|
|
// not supported by TextDecoder...
|
|
//label = "iso-2022-cn";
|
|
}
|
|
else if (charSetTerm === "ISO_IR 192" ) {
|
|
label = "utf-8";
|
|
}
|
|
else if (charSetTerm === "GB18030" ) {
|
|
label = "gb18030";
|
|
}
|
|
else if (charSetTerm === "GB2312" ) {
|
|
label = "gb2312";
|
|
}
|
|
else if (charSetTerm === "GBK" ) {
|
|
label = "chinese";
|
|
}
|
|
return label;
|
|
};
|
|
|
|
/**
|
|
* Data reader.
|
|
* @constructor
|
|
* @param {Array} buffer The input array buffer.
|
|
* @param {Boolean} isLittleEndian Flag to tell if the data is little or big endian.
|
|
*/
|
|
dwv.dicom.DataReader = function (buffer, isLittleEndian)
|
|
{
|
|
// Set endian flag if not defined.
|
|
if ( typeof isLittleEndian === 'undefined' ) {
|
|
isLittleEndian = true;
|
|
}
|
|
|
|
// Default text decoder
|
|
var defaultTextDecoder = {};
|
|
defaultTextDecoder.decode = function (buffer) {
|
|
var result = "";
|
|
for ( var i = 0, leni = buffer.length; i < leni; ++i ) {
|
|
result += String.fromCharCode( buffer[ i ] );
|
|
}
|
|
return result;
|
|
};
|
|
// Text decoder
|
|
var textDecoder = defaultTextDecoder;
|
|
if (typeof window.TextDecoder !== "undefined") {
|
|
textDecoder = new TextDecoder("iso-8859-1");
|
|
}
|
|
|
|
/**
|
|
* Set the utfLabel used to construct the TextDecoder.
|
|
* @param {String} label The encoding label.
|
|
*/
|
|
this.setUtfLabel = function (label) {
|
|
if (typeof window.TextDecoder !== "undefined") {
|
|
textDecoder = new TextDecoder(label);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Is the Native endianness Little Endian.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isNativeLittleEndian = dwv.dicom.isNativeLittleEndian();
|
|
|
|
/**
|
|
* Flag to know if the TypedArray data needs flipping.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var needFlip = (isLittleEndian !== isNativeLittleEndian);
|
|
|
|
/**
|
|
* The main data view.
|
|
* @private
|
|
* @type DataView
|
|
*/
|
|
var view = new DataView(buffer);
|
|
|
|
/**
|
|
* Flip an array's endianness.
|
|
* Inspired from [DataStream.js]{@link https://github.com/kig/DataStream.js}.
|
|
* @param {Object} array The array to flip (modified).
|
|
*/
|
|
this.flipArrayEndianness = function (array) {
|
|
var blen = array.byteLength;
|
|
var u8 = new Uint8Array(array.buffer, array.byteOffset, blen);
|
|
var bpel = array.BYTES_PER_ELEMENT;
|
|
var tmp;
|
|
for ( var i = 0; i < blen; i += bpel ) {
|
|
for ( var j = i + bpel - 1, k = i; j > k; j--, k++ ) {
|
|
tmp = u8[k];
|
|
u8[k] = u8[j];
|
|
u8[j] = tmp;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Read Uint16 (2 bytes) data.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @return {Number} The read data.
|
|
*/
|
|
this.readUint16 = function (byteOffset) {
|
|
return view.getUint16(byteOffset, isLittleEndian);
|
|
};
|
|
/**
|
|
* Read Uint32 (4 bytes) data.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @return {Number} The read data.
|
|
*/
|
|
this.readUint32 = function (byteOffset) {
|
|
return view.getUint32(byteOffset, isLittleEndian);
|
|
};
|
|
/**
|
|
* Read Int32 (4 bytes) data.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @return {Number} The read data.
|
|
*/
|
|
this.readInt32 = function (byteOffset) {
|
|
return view.getInt32(byteOffset, isLittleEndian);
|
|
};
|
|
/**
|
|
* Read Uint8 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readUint8Array = function (byteOffset, size) {
|
|
return new Uint8Array(buffer, byteOffset, size);
|
|
};
|
|
/**
|
|
* Read Int8 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readInt8Array = function (byteOffset, size) {
|
|
return new Int8Array(buffer, byteOffset, size);
|
|
};
|
|
/**
|
|
* Read Uint16 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readUint16Array = function (byteOffset, size) {
|
|
var arraySize = size / Uint16Array.BYTES_PER_ELEMENT;
|
|
var data = null;
|
|
// byteOffset should be a multiple of Uint16Array.BYTES_PER_ELEMENT (=2)
|
|
if ( (byteOffset % Uint16Array.BYTES_PER_ELEMENT) === 0 ) {
|
|
data = new Uint16Array(buffer, byteOffset, arraySize);
|
|
if ( needFlip ) {
|
|
this.flipArrayEndianness(data);
|
|
}
|
|
}
|
|
else {
|
|
data = new Uint16Array(arraySize);
|
|
for ( var i = 0; i < arraySize; ++i ) {
|
|
data[i] = view.getInt16( (byteOffset +
|
|
Uint16Array.BYTES_PER_ELEMENT * i),
|
|
isLittleEndian);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
/**
|
|
* Read Int16 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readInt16Array = function (byteOffset, size) {
|
|
var arraySize = size / Int16Array.BYTES_PER_ELEMENT;
|
|
var data = null;
|
|
// byteOffset should be a multiple of Int16Array.BYTES_PER_ELEMENT (=2)
|
|
if ( (byteOffset % Int16Array.BYTES_PER_ELEMENT) === 0 ) {
|
|
data = new Int16Array(buffer, byteOffset, arraySize);
|
|
if ( needFlip ) {
|
|
this.flipArrayEndianness(data);
|
|
}
|
|
}
|
|
else {
|
|
data = new Int16Array(arraySize);
|
|
for ( var i = 0; i < arraySize; ++i ) {
|
|
data[i] = view.getInt16( (byteOffset +
|
|
Int16Array.BYTES_PER_ELEMENT * i),
|
|
isLittleEndian);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
/**
|
|
* Read Uint32 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readUint32Array = function (byteOffset, size) {
|
|
var arraySize = size / Uint32Array.BYTES_PER_ELEMENT;
|
|
var data = null;
|
|
// byteOffset should be a multiple of Uint32Array.BYTES_PER_ELEMENT (=4)
|
|
if ( (byteOffset % Uint32Array.BYTES_PER_ELEMENT) === 0 ) {
|
|
data = new Uint32Array(buffer, byteOffset, arraySize);
|
|
if ( needFlip ) {
|
|
this.flipArrayEndianness(data);
|
|
}
|
|
}
|
|
else {
|
|
data = new Uint32Array(arraySize);
|
|
for ( var i = 0; i < arraySize; ++i ) {
|
|
data[i] = view.getUint32( (byteOffset +
|
|
Uint32Array.BYTES_PER_ELEMENT * i),
|
|
isLittleEndian);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
/**
|
|
* Read Int32 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readInt32Array = function (byteOffset, size) {
|
|
var arraySize = size / Int32Array.BYTES_PER_ELEMENT;
|
|
var data = null;
|
|
// byteOffset should be a multiple of Int32Array.BYTES_PER_ELEMENT (=4)
|
|
if ( (byteOffset % Int32Array.BYTES_PER_ELEMENT) === 0 ) {
|
|
data = new Int32Array(buffer, byteOffset, arraySize);
|
|
if ( needFlip ) {
|
|
this.flipArrayEndianness(data);
|
|
}
|
|
}
|
|
else {
|
|
data = new Int32Array(arraySize);
|
|
for ( var i = 0; i < arraySize; ++i ) {
|
|
data[i] = view.getInt32( (byteOffset +
|
|
Int32Array.BYTES_PER_ELEMENT * i),
|
|
isLittleEndian);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
/**
|
|
* Read Float32 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readFloat32Array = function (byteOffset, size) {
|
|
var arraySize = size / Float32Array.BYTES_PER_ELEMENT;
|
|
var data = null;
|
|
// byteOffset should be a multiple of Float32Array.BYTES_PER_ELEMENT (=4)
|
|
if ( (byteOffset % Float32Array.BYTES_PER_ELEMENT) === 0 ) {
|
|
data = new Float32Array(buffer, byteOffset, arraySize);
|
|
if ( needFlip ) {
|
|
this.flipArrayEndianness(data);
|
|
}
|
|
}
|
|
else {
|
|
data = new Float32Array(arraySize);
|
|
for ( var i = 0; i < arraySize; ++i ) {
|
|
data[i] = view.getFloat32( (byteOffset +
|
|
Float32Array.BYTES_PER_ELEMENT * i),
|
|
isLittleEndian);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
/**
|
|
* Read Float64 array.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} size The size of the array.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readFloat64Array = function (byteOffset, size) {
|
|
var arraySize = size / Float64Array.BYTES_PER_ELEMENT;
|
|
var data = null;
|
|
// byteOffset should be a multiple of Float64Array.BYTES_PER_ELEMENT (=8)
|
|
if ( (byteOffset % Float64Array.BYTES_PER_ELEMENT) === 0 ) {
|
|
data = new Float64Array(buffer, byteOffset, arraySize);
|
|
if ( needFlip ) {
|
|
this.flipArrayEndianness(data);
|
|
}
|
|
}
|
|
else {
|
|
data = new Float64Array(arraySize);
|
|
for ( var i = 0; i < arraySize; ++i ) {
|
|
data[i] = view.getFloat64( (byteOffset +
|
|
Float64Array.BYTES_PER_ELEMENT*i),
|
|
isLittleEndian);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
/**
|
|
* Read data as an hexadecimal string.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @return {Array} The read data.
|
|
*/
|
|
this.readHex = function (byteOffset) {
|
|
// read and convert to hex string
|
|
var str = this.readUint16(byteOffset).toString(16);
|
|
// return padded
|
|
return "0x0000".substr(0, 6 - str.length) + str.toUpperCase();
|
|
};
|
|
|
|
/**
|
|
* Read data as a string.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} nChars The number of characters to read.
|
|
* @return {String} The read data.
|
|
*/
|
|
this.readString = function (byteOffset, nChars) {
|
|
var data = this.readUint8Array(byteOffset, nChars);
|
|
return defaultTextDecoder.decode(data);
|
|
};
|
|
|
|
/**
|
|
* Read data as a 'special' string, decoding it if the TextDecoder is available.
|
|
* @param {Number} byteOffset The offset to start reading from.
|
|
* @param {Number} nChars The number of characters to read.
|
|
* @return {String} The read data.
|
|
*/
|
|
this.readSpecialString = function (byteOffset, nChars) {
|
|
var data = this.readUint8Array(byteOffset, nChars);
|
|
return textDecoder.decode(data);
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* Get the group-element pair from a tag string name.
|
|
* @param {String} tagName The tag string name.
|
|
* @return {Object} group-element pair.
|
|
*/
|
|
dwv.dicom.getGroupElementFromName = function (tagName)
|
|
{
|
|
var group = null;
|
|
var element = null;
|
|
var dict = dwv.dicom.dictionary;
|
|
var keys0 = Object.keys(dict);
|
|
var keys1 = null;
|
|
// label for nested loop break
|
|
outLabel:
|
|
// search through dictionary
|
|
for ( var k0 = 0, lenK0 = keys0.length; k0 < lenK0; ++k0 ) {
|
|
group = keys0[k0];
|
|
keys1 = Object.keys( dict[group] );
|
|
for ( var k1 = 0, lenK1 = keys1.length; k1 < lenK1; ++k1 ) {
|
|
element = keys1[k1];
|
|
if ( dict[group][element][2] === tagName ) {
|
|
break outLabel;
|
|
}
|
|
}
|
|
}
|
|
return { 'group': group, 'element': element };
|
|
};
|
|
|
|
/**
|
|
* Immutable tag.
|
|
* @constructor
|
|
* @param {String} group The tag group.
|
|
* @param {String} element The tag element.
|
|
*/
|
|
dwv.dicom.Tag = function (group, element)
|
|
{
|
|
/**
|
|
* Get the tag group.
|
|
* @return {String} The tag group.
|
|
*/
|
|
this.getGroup = function () { return group; };
|
|
/**
|
|
* Get the tag element.
|
|
* @return {String} The tag element.
|
|
*/
|
|
this.getElement = function () { return element; };
|
|
}; // Tag class
|
|
|
|
/**
|
|
* Check for Tag equality.
|
|
* @param {Object} rhs The other tag to compare to.
|
|
* @return {Boolean} True if both tags are equal.
|
|
*/
|
|
dwv.dicom.Tag.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getGroup() === rhs.getGroup() &&
|
|
this.getElement() === rhs.getElement();
|
|
};
|
|
|
|
/**
|
|
* Check for Tag equality.
|
|
* @param {Object} rhs The other tag to compare to provided as a simple object.
|
|
* @return {Boolean} True if both tags are equal.
|
|
*/
|
|
dwv.dicom.Tag.prototype.equals2 = function (rhs) {
|
|
if (rhs === null ||
|
|
typeof rhs.group === "undefined" ||
|
|
typeof rhs.element === "undefined" ) {
|
|
return false;
|
|
}
|
|
return this.equals(new dwv.dicom.Tag(rhs.group, rhs.element));
|
|
};
|
|
|
|
// Get the FileMetaInformationGroupLength Tag.
|
|
dwv.dicom.getFileMetaInformationGroupLengthTag = function () {
|
|
return new dwv.dicom.Tag("0x0002", "0x0000");
|
|
};
|
|
// Get the Item Tag.
|
|
dwv.dicom.getItemTag = function () {
|
|
return new dwv.dicom.Tag("0xFFFE", "0xE000");
|
|
};
|
|
// Get the ItemDelimitationItem Tag.
|
|
dwv.dicom.getItemDelimitationItemTag = function () {
|
|
return new dwv.dicom.Tag("0xFFFE", "0xE00D");
|
|
};
|
|
// Get the SequenceDelimitationItem Tag.
|
|
dwv.dicom.getSequenceDelimitationItemTag = function () {
|
|
return new dwv.dicom.Tag("0xFFFE", "0xE0DD");
|
|
};
|
|
// Get the PixelData Tag.
|
|
dwv.dicom.getPixelDataTag = function () {
|
|
return new dwv.dicom.Tag("0x7FE0", "0x0010");
|
|
};
|
|
|
|
/**
|
|
* Get the group-element key used to store DICOM elements.
|
|
* @param {Number} group The DICOM group.
|
|
* @param {Number} element The DICOM element.
|
|
* @return {String} The key.
|
|
*/
|
|
dwv.dicom.getGroupElementKey = function (group, element)
|
|
{
|
|
return 'x' + group.substr(2,6) + element.substr(2,6);
|
|
};
|
|
|
|
/**
|
|
* Split a group-element key used to store DICOM elements.
|
|
* @param {String} key The key in form "x00280102.
|
|
* @return {Object} The DICOM group and element.
|
|
*/
|
|
dwv.dicom.splitGroupElementKey = function (key)
|
|
{
|
|
return {'group': key.substr(1,4), 'element': key.substr(5,8) };
|
|
};
|
|
|
|
/**
|
|
* Get patient orientation label in the reverse direction.
|
|
* @param {String} ori Patient Orientation value.
|
|
* @return {String} Reverse Orientation Label.
|
|
*/
|
|
dwv.dicom.getReverseOrientation = function (ori)
|
|
{
|
|
if (!ori) {
|
|
return null;
|
|
}
|
|
// reverse labels
|
|
var rlabels = {
|
|
"L": "R",
|
|
"R": "L",
|
|
"A": "P",
|
|
"P": "A",
|
|
"H": "F",
|
|
"F": "H"
|
|
};
|
|
|
|
var rori = "";
|
|
for (var n=0; n<ori.length; n++) {
|
|
var o = ori.substr(n,1);
|
|
var r = rlabels[o];
|
|
if (r){
|
|
rori += r;
|
|
}
|
|
}
|
|
// return
|
|
return rori;
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is an implicit one (element with no VR).
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if an implicit syntax.
|
|
*/
|
|
dwv.dicom.isImplicitTransferSyntax = function (syntax)
|
|
{
|
|
return syntax === "1.2.840.10008.1.2";
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is a big endian syntax.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a big endian syntax.
|
|
*/
|
|
dwv.dicom.isBigEndianTransferSyntax = function (syntax)
|
|
{
|
|
return syntax === "1.2.840.10008.1.2.2";
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is a JPEG baseline one.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a jpeg baseline syntax.
|
|
*/
|
|
dwv.dicom.isJpegBaselineTransferSyntax = function (syntax)
|
|
{
|
|
return syntax === "1.2.840.10008.1.2.4.50" ||
|
|
syntax === "1.2.840.10008.1.2.4.51";
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is a retired JPEG one.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a retired jpeg syntax.
|
|
*/
|
|
dwv.dicom.isJpegRetiredTransferSyntax = function (syntax)
|
|
{
|
|
return ( syntax.match(/1.2.840.10008.1.2.4.5/) !== null &&
|
|
!dwv.dicom.isJpegBaselineTransferSyntax() &&
|
|
!dwv.dicom.isJpegLosslessTransferSyntax() ) ||
|
|
syntax.match(/1.2.840.10008.1.2.4.6/) !== null;
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is a JPEG Lossless one.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a jpeg lossless syntax.
|
|
*/
|
|
dwv.dicom.isJpegLosslessTransferSyntax = function (syntax)
|
|
{
|
|
return syntax === "1.2.840.10008.1.2.4.57" ||
|
|
syntax === "1.2.840.10008.1.2.4.70";
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is a JPEG-LS one.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a jpeg-ls syntax.
|
|
*/
|
|
dwv.dicom.isJpeglsTransferSyntax = function (syntax)
|
|
{
|
|
return syntax.match(/1.2.840.10008.1.2.4.8/) !== null;
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is a JPEG 2000 one.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a jpeg 2000 syntax.
|
|
*/
|
|
dwv.dicom.isJpeg2000TransferSyntax = function (syntax)
|
|
{
|
|
return syntax.match(/1.2.840.10008.1.2.4.9/) !== null;
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax needs decompression.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {String} The name of the decompression algorithm.
|
|
*/
|
|
dwv.dicom.getSyntaxDecompressionName = function (syntax)
|
|
{
|
|
var algo = null;
|
|
if ( dwv.dicom.isJpeg2000TransferSyntax(syntax) ) {
|
|
algo = "jpeg2000";
|
|
}
|
|
else if ( dwv.dicom.isJpegBaselineTransferSyntax(syntax) ) {
|
|
algo = "jpeg-baseline";
|
|
}
|
|
else if ( dwv.dicom.isJpegLosslessTransferSyntax(syntax) ) {
|
|
algo = "jpeg-lossless";
|
|
}
|
|
return algo;
|
|
};
|
|
|
|
/**
|
|
* Tell if a given syntax is supported for reading.
|
|
* @param {String} syntax The transfer syntax to test.
|
|
* @return {Boolean} True if a supported syntax.
|
|
*/
|
|
dwv.dicom.isReadSupportedTransferSyntax = function (syntax) {
|
|
|
|
// Unsupported:
|
|
// "1.2.840.10008.1.2.1.99": Deflated Explicit VR - Little Endian
|
|
// "1.2.840.10008.1.2.4.100": MPEG2 Image Compression
|
|
// dwv.dicom.isJpegRetiredTransferSyntax(syntax): non supported JPEG
|
|
// dwv.dicom.isJpeglsTransferSyntax(syntax): JPEG-LS
|
|
// "1.2.840.10008.1.2.5": RLE (lossless)
|
|
|
|
return( syntax === "1.2.840.10008.1.2" || // Implicit VR - Little Endian
|
|
syntax === "1.2.840.10008.1.2.1" || // Explicit VR - Little Endian
|
|
syntax === "1.2.840.10008.1.2.2" || // Explicit VR - Big Endian
|
|
dwv.dicom.isJpegBaselineTransferSyntax(syntax) || // JPEG baseline
|
|
dwv.dicom.isJpegLosslessTransferSyntax(syntax) || // JPEG Lossless
|
|
dwv.dicom.isJpeg2000TransferSyntax(syntax) ); // JPEG 2000
|
|
};
|
|
|
|
/**
|
|
* Get the transfer syntax name.
|
|
* Reference: [UID Values]{@link http://dicom.nema.org/dicom/2013/output/chtml/part06/chapter_A.html}.
|
|
* @param {String} syntax The transfer syntax.
|
|
* @return {String} The name of the transfer syntax.
|
|
*/
|
|
dwv.dicom.getTransferSyntaxName = function (syntax)
|
|
{
|
|
var name = "Unknown";
|
|
// Implicit VR - Little Endian
|
|
if( syntax === "1.2.840.10008.1.2" ) {
|
|
name = "Little Endian Implicit";
|
|
}
|
|
// Explicit VR - Little Endian
|
|
else if( syntax === "1.2.840.10008.1.2.1" ) {
|
|
name = "Little Endian Explicit";
|
|
}
|
|
// Deflated Explicit VR - Little Endian
|
|
else if( syntax === "1.2.840.10008.1.2.1.99" ) {
|
|
name = "Little Endian Deflated Explicit";
|
|
}
|
|
// Explicit VR - Big Endian
|
|
else if( syntax === "1.2.840.10008.1.2.2" ) {
|
|
name = "Big Endian Explicit";
|
|
}
|
|
// JPEG baseline
|
|
else if( dwv.dicom.isJpegBaselineTransferSyntax(syntax) ) {
|
|
if ( syntax === "1.2.840.10008.1.2.4.50" ) {
|
|
name = "JPEG Baseline";
|
|
}
|
|
else { // *.51
|
|
name = "JPEG Extended, Process 2+4";
|
|
}
|
|
}
|
|
// JPEG Lossless
|
|
else if( dwv.dicom.isJpegLosslessTransferSyntax(syntax) ) {
|
|
if ( syntax === "1.2.840.10008.1.2.4.57" ) {
|
|
name = "JPEG Lossless, Nonhierarchical (Processes 14)";
|
|
}
|
|
else { // *.70
|
|
name = "JPEG Lossless, Non-hierarchical, 1st Order Prediction";
|
|
}
|
|
}
|
|
// Retired JPEG
|
|
else if( dwv.dicom.isJpegRetiredTransferSyntax(syntax) ) {
|
|
name = "Retired JPEG";
|
|
}
|
|
// JPEG-LS
|
|
else if( dwv.dicom.isJpeglsTransferSyntax(syntax) ) {
|
|
name = "JPEG-LS";
|
|
}
|
|
// JPEG 2000
|
|
else if( dwv.dicom.isJpeg2000TransferSyntax(syntax) ) {
|
|
if ( syntax === "1.2.840.10008.1.2.4.91" ) {
|
|
name = "JPEG 2000 (Lossless or Lossy)";
|
|
}
|
|
else { // *.90
|
|
name = "JPEG 2000 (Lossless only)";
|
|
}
|
|
}
|
|
// MPEG2 Image Compression
|
|
else if( syntax === "1.2.840.10008.1.2.4.100" ) {
|
|
name = "MPEG2";
|
|
}
|
|
// RLE (lossless)
|
|
else if( syntax === "1.2.840.10008.1.2.5" ) {
|
|
name = "RLE";
|
|
}
|
|
// return
|
|
return name;
|
|
};
|
|
|
|
/**
|
|
* Get the appropriate TypedArray in function of arguments.
|
|
* @param {Number} bitsAllocated The number of bites used to store the data: [8, 16, 32].
|
|
* @param {Number} pixelRepresentation The pixel representation, 0:unsigned;1:signed.
|
|
* @param {Size} size The size of the new array.
|
|
* @return The good typed array.
|
|
*/
|
|
dwv.dicom.getTypedArray = function (bitsAllocated, pixelRepresentation, size)
|
|
{
|
|
var res = null;
|
|
if (bitsAllocated === 8) {
|
|
if (pixelRepresentation === 0) {
|
|
res = new Uint8Array(size);
|
|
}
|
|
else {
|
|
res = new Int8Array(size);
|
|
}
|
|
}
|
|
else if (bitsAllocated === 16) {
|
|
if (pixelRepresentation === 0) {
|
|
res = new Uint16Array(size);
|
|
}
|
|
else {
|
|
res = new Int16Array(size);
|
|
}
|
|
}
|
|
else if (bitsAllocated === 32) {
|
|
if (pixelRepresentation === 0) {
|
|
res = new Uint32Array(size);
|
|
}
|
|
else {
|
|
res = new Int32Array(size);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* Does this Value Representation (VR) have a 32bit Value Length (VL).
|
|
* Ref: [Data Element explicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_7.html#table_7.1-1}.
|
|
* @param {String} vr The data Value Representation (VR).
|
|
* @returns {Boolean} True if this VR has a 32-bit VL.
|
|
*/
|
|
dwv.dicom.is32bitVLVR = function (vr)
|
|
{
|
|
// added locally used 'ox'
|
|
return ( vr === "OB" || vr === "OW" || vr === "OF" || vr === "ox" || vr === "UT" ||
|
|
vr === "SQ" || vr === "UN" );
|
|
};
|
|
|
|
/**
|
|
* Does this tag have a VR.
|
|
* Basically the Item, ItemDelimitationItem and SequenceDelimitationItem tags.
|
|
* @param {String} group The tag group.
|
|
* @param {String} element The tag element.
|
|
* @returns {Boolean} True if this tar has a VR.
|
|
*/
|
|
dwv.dicom.isTagWithVR = function (group, element) {
|
|
return !(group === "0xFFFE" &&
|
|
(element === "0xE000" || element === "0xE00D" || element === "0xE0DD" ));
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the number of bytes occupied by a data element prefix, i.e. without its value.
|
|
* @param {String} vr The Value Representation of the element.
|
|
* @param {Boolean} isImplicit Does the data use implicit VR?
|
|
* WARNING: this is valid for tags with a VR, if not sure use the 'isTagWithVR' function first.
|
|
* Reference:
|
|
* - [Data Element explicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_7.html#table_7.1-1},
|
|
* - [Data Element implicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html#table_7.5-1}.
|
|
*
|
|
* | Tag | VR | VL | Value |
|
|
* | 4 | 2 | 2 | X | -> regular explicit: 8 + X
|
|
* | 4 | 2+2 | 4 | X | -> 32bit VL: 12 + X
|
|
*
|
|
* | Tag | VL | Value |
|
|
* | 4 | 4 | X | -> implicit (32bit VL): 8 + X
|
|
*
|
|
* | Tag | Len | Value |
|
|
* | 4 | 4 | X | -> item: 8 + X
|
|
*/
|
|
dwv.dicom.getDataElementPrefixByteSize = function (vr, isImplicit) {
|
|
return isImplicit ? 8 : dwv.dicom.is32bitVLVR(vr) ? 12 : 8;
|
|
};
|
|
|
|
/**
|
|
* DicomParser class.
|
|
* @constructor
|
|
*/
|
|
dwv.dicom.DicomParser = function ()
|
|
{
|
|
/**
|
|
* The list of DICOM elements.
|
|
* @type Array
|
|
*/
|
|
this.dicomElements = {};
|
|
|
|
/**
|
|
* Default character set (optional).
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var defaultCharacterSet;
|
|
/**
|
|
* Get the default character set.
|
|
* @return {String} The default character set.
|
|
*/
|
|
this.getDefaultCharacterSet = function () {
|
|
return defaultCharacterSet;
|
|
};
|
|
/**
|
|
* Set the default character set.
|
|
* param {String} The character set.
|
|
*/
|
|
this.setDefaultCharacterSet = function (characterSet) {
|
|
defaultCharacterSet = characterSet;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get the raw DICOM data elements.
|
|
* @return {Object} The raw DICOM elements.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.getRawDicomElements = function ()
|
|
{
|
|
return this.dicomElements;
|
|
};
|
|
|
|
/**
|
|
* Get the DICOM data elements.
|
|
* @return {Object} The DICOM elements.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.getDicomElements = function ()
|
|
{
|
|
return new dwv.dicom.DicomElementsWrapper(this.dicomElements);
|
|
};
|
|
|
|
/**
|
|
* Read a DICOM tag.
|
|
* @param reader The raw data reader.
|
|
* @param offset The offset where to start to read.
|
|
* @return An object containing the tags 'group', 'element' and 'name'.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.readTag = function (reader, offset)
|
|
{
|
|
// group
|
|
var group = reader.readHex(offset);
|
|
offset += Uint16Array.BYTES_PER_ELEMENT;
|
|
// element
|
|
var element = reader.readHex(offset);
|
|
offset += Uint16Array.BYTES_PER_ELEMENT;
|
|
// name
|
|
var name = dwv.dicom.getGroupElementKey(group, element);
|
|
// return
|
|
return {
|
|
'group': group,
|
|
'element': element,
|
|
'name': name,
|
|
'endOffset': offset };
|
|
};
|
|
|
|
/**
|
|
* Read an item data element.
|
|
* @param {Object} reader The raw data reader.
|
|
* @param {Number} offset The offset where to start to read.
|
|
* @param {Boolean} implicit Is the DICOM VR implicit?
|
|
* @returns {Object} The item data as a list of data elements.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.readItemDataElement = function (reader, offset, implicit)
|
|
{
|
|
var itemData = {};
|
|
|
|
// read the first item
|
|
var item = this.readDataElement(reader, offset, implicit);
|
|
offset = item.endOffset;
|
|
|
|
// exit if it is a sequence delimitation item
|
|
var isSeqDelim = ( item.tag.name === "xFFFEE0DD" );
|
|
if (isSeqDelim) {
|
|
return {
|
|
data: itemData,
|
|
endOffset: item.endOffset,
|
|
isSeqDelim: isSeqDelim };
|
|
}
|
|
|
|
// store it
|
|
itemData[item.tag.name] = item;
|
|
|
|
// explicit VR items
|
|
if (item.vl !== "u/l") {
|
|
// not empty
|
|
if (item.vl !== 0) {
|
|
// read until the end offset
|
|
var endOffset = offset;
|
|
offset -= item.vl;
|
|
while (offset < endOffset) {
|
|
item = this.readDataElement(reader, offset, implicit);
|
|
offset = item.endOffset;
|
|
itemData[item.tag.name] = item;
|
|
}
|
|
}
|
|
}
|
|
// implicit VR items
|
|
else {
|
|
// read until the item delimitation item
|
|
var isItemDelim = false;
|
|
while (!isItemDelim) {
|
|
item = this.readDataElement(reader, offset, implicit);
|
|
offset = item.endOffset;
|
|
isItemDelim = ( item.tag.name === "xFFFEE00D" );
|
|
if (!isItemDelim) {
|
|
itemData[item.tag.name] = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
'data': itemData,
|
|
'endOffset': offset,
|
|
'isSeqDelim': false };
|
|
};
|
|
|
|
/**
|
|
* Read the pixel item data element.
|
|
* Ref: [Single frame fragments]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_A.4.html#table_A.4-1}.
|
|
* @param {Object} reader The raw data reader.
|
|
* @param {Number} offset The offset where to start to read.
|
|
* @param {Boolean} implicit Is the DICOM VR implicit?
|
|
* @returns {Array} The item data as an array of data elements.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.readPixelItemDataElement = function (reader, offset, implicit)
|
|
{
|
|
var itemData = [];
|
|
|
|
// first item: basic offset table
|
|
var item = this.readDataElement(reader, offset, implicit);
|
|
var offsetTableVl = item.vl;
|
|
offset = item.endOffset;
|
|
|
|
// read until the sequence delimitation item
|
|
var isSeqDelim = false;
|
|
while (!isSeqDelim) {
|
|
item = this.readDataElement(reader, offset, implicit);
|
|
offset = item.endOffset;
|
|
isSeqDelim = ( item.tag.name === "xFFFEE0DD" );
|
|
if (!isSeqDelim) {
|
|
itemData.push(item.value);
|
|
}
|
|
}
|
|
|
|
return {
|
|
'data': itemData,
|
|
'endOffset': offset,
|
|
'offsetTableVl': offsetTableVl };
|
|
};
|
|
|
|
/**
|
|
* Read a DICOM data element.
|
|
* Reference: [DICOM VRs]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#table_6.2-1}.
|
|
* @param {Object} reader The raw data reader.
|
|
* @param {Number} offset The offset where to start to read.
|
|
* @param {Boolean} implicit Is the DICOM VR implicit?
|
|
* @return {Object} An object containing the element 'tag', 'vl', 'vr', 'data' and 'endOffset'.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.readDataElement = function (reader, offset, implicit)
|
|
{
|
|
// Tag: group, element
|
|
var tag = this.readTag(reader, offset);
|
|
offset = tag.endOffset;
|
|
|
|
// Value Representation (VR)
|
|
var vr = null;
|
|
var is32bitVLVR = false;
|
|
if (dwv.dicom.isTagWithVR(tag.group, tag.element)) {
|
|
// implicit VR
|
|
if (implicit) {
|
|
vr = "UN";
|
|
var dict = dwv.dicom.dictionary;
|
|
if ( typeof dict[tag.group] !== "undefined" &&
|
|
typeof dict[tag.group][tag.element] !== "undefined" ) {
|
|
vr = dwv.dicom.dictionary[tag.group][tag.element][0];
|
|
}
|
|
is32bitVLVR = true;
|
|
}
|
|
else {
|
|
vr = reader.readString( offset, 2 );
|
|
offset += 2 * Uint8Array.BYTES_PER_ELEMENT;
|
|
is32bitVLVR = dwv.dicom.is32bitVLVR(vr);
|
|
// reserved 2 bytes
|
|
if ( is32bitVLVR ) {
|
|
offset += 2 * Uint8Array.BYTES_PER_ELEMENT;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
vr = "UN";
|
|
is32bitVLVR = true;
|
|
}
|
|
|
|
// Value Length (VL)
|
|
var vl = 0;
|
|
if ( is32bitVLVR ) {
|
|
vl = reader.readUint32( offset );
|
|
offset += Uint32Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else {
|
|
vl = reader.readUint16( offset );
|
|
offset += Uint16Array.BYTES_PER_ELEMENT;
|
|
}
|
|
|
|
// check the value of VL
|
|
var vlString = vl;
|
|
if( vl === 0xffffffff ) {
|
|
vlString = "u/l";
|
|
vl = 0;
|
|
}
|
|
|
|
var startOffset = offset;
|
|
|
|
// data
|
|
var data = null;
|
|
var isPixelData = (tag.name === "x7FE00010");
|
|
// pixel data sequence (implicit)
|
|
if (isPixelData && vlString === "u/l")
|
|
{
|
|
var pixItemData = this.readPixelItemDataElement(reader, offset, implicit);
|
|
offset = pixItemData.endOffset;
|
|
startOffset += pixItemData.offsetTableVl;
|
|
data = pixItemData.data;
|
|
}
|
|
else if (isPixelData && (vr === "OB" || vr === "OW" || vr === "OF" || vr === "ox")) {
|
|
// BitsAllocated
|
|
var bitsAllocated = 16;
|
|
if ( typeof this.dicomElements.x00280100 !== 'undefined' ) {
|
|
bitsAllocated = this.dicomElements.x00280100.value[0];
|
|
} else {
|
|
console.warn("Reading DICOM pixel data with default bitsAllocated.");
|
|
}
|
|
if (bitsAllocated === 8 && vr === "OW") {
|
|
console.warn("Reading DICOM pixel data with vr=OW and bitsAllocated=8 (should be 16).");
|
|
}
|
|
if (bitsAllocated === 16 && vr === "OB") {
|
|
console.warn("Reading DICOM pixel data with vr=OB and bitsAllocated=16 (should be 8).");
|
|
}
|
|
// PixelRepresentation 0->unsigned, 1->signed
|
|
var pixelRepresentation = 0;
|
|
if ( typeof this.dicomElements.x00280103 !== 'undefined' ) {
|
|
pixelRepresentation = this.dicomElements.x00280103.value[0];
|
|
}
|
|
// read
|
|
if ( bitsAllocated === 8 ) {
|
|
if (pixelRepresentation === 0) {
|
|
data = reader.readUint8Array( offset, vl );
|
|
}
|
|
else {
|
|
data = reader.readInt8Array( offset, vl );
|
|
}
|
|
}
|
|
else if ( bitsAllocated === 16 ) {
|
|
if (pixelRepresentation === 0) {
|
|
data = reader.readUint16Array( offset, vl );
|
|
}
|
|
else {
|
|
data = reader.readInt16Array( offset, vl );
|
|
}
|
|
}
|
|
else if ( bitsAllocated === 32 ) {
|
|
if (pixelRepresentation === 0) {
|
|
data = reader.readUint32Array( offset, vl );
|
|
}
|
|
else {
|
|
data = reader.readInt32Array( offset, vl );
|
|
}
|
|
}
|
|
else if ( bitsAllocated === 64 ) {
|
|
if (pixelRepresentation === 0) {
|
|
data = reader.readUint64Array( offset, vl );
|
|
}
|
|
else {
|
|
data = reader.readInt64Array( offset, vl );
|
|
}
|
|
}
|
|
offset += vl;
|
|
}
|
|
// others
|
|
else if ( vr === "OB" )
|
|
{
|
|
data = reader.readInt8Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if ( vr === "OW" )
|
|
{
|
|
data = reader.readInt16Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if ( vr === "OF" )
|
|
{
|
|
data = reader.readInt32Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if ( vr === "OD" )
|
|
{
|
|
data = reader.readInt64Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
// numbers
|
|
else if( vr === "US")
|
|
{
|
|
data = reader.readUint16Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if( vr === "UL")
|
|
{
|
|
data = reader.readUint32Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if( vr === "SS")
|
|
{
|
|
data = reader.readInt16Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if( vr === "SL")
|
|
{
|
|
data = reader.readInt32Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if( vr === "FL")
|
|
{
|
|
data = reader.readFloat32Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
else if( vr === "FD")
|
|
{
|
|
data = reader.readFloat64Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
// attribute
|
|
else if( vr === "AT")
|
|
{
|
|
var raw = reader.readUint16Array( offset, vl );
|
|
offset += vl;
|
|
data = [];
|
|
for ( var i = 0, leni = raw.length; i < leni; i+=2 ) {
|
|
var stri = raw[i].toString(16);
|
|
var stri1 = raw[i+1].toString(16);
|
|
var str = "(";
|
|
str += "0000".substr(0, 4 - stri.length) + stri.toUpperCase();
|
|
str += ",";
|
|
str += "0000".substr(0, 4 - stri1.length) + stri1.toUpperCase();
|
|
str += ")";
|
|
data.push(str);
|
|
}
|
|
}
|
|
// not available
|
|
else if( vr === "UN")
|
|
{
|
|
data = reader.readUint8Array( offset, vl );
|
|
offset += vl;
|
|
}
|
|
// sequence
|
|
else if (vr === "SQ")
|
|
{
|
|
data = [];
|
|
var itemData;
|
|
// explicit VR sequence
|
|
if (vlString !== "u/l") {
|
|
// not empty
|
|
if (vl !== 0) {
|
|
var sqEndOffset = offset + vl;
|
|
while (offset < sqEndOffset) {
|
|
itemData = this.readItemDataElement(reader, offset, implicit);
|
|
data.push( itemData.data );
|
|
offset = itemData.endOffset;
|
|
}
|
|
}
|
|
}
|
|
// implicit VR sequence
|
|
else {
|
|
// read until the sequence delimitation item
|
|
var isSeqDelim = false;
|
|
while (!isSeqDelim) {
|
|
itemData = this.readItemDataElement(reader, offset, implicit);
|
|
isSeqDelim = itemData.isSeqDelim;
|
|
offset = itemData.endOffset;
|
|
// do not store the delimitation item
|
|
if (!isSeqDelim) {
|
|
data.push( itemData.data );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// raw
|
|
else
|
|
{
|
|
if ( vr === "SH" || vr === "LO" || vr === "ST" ||
|
|
vr === "PN" || vr === "LT" || vr === "UT" ) {
|
|
data = reader.readSpecialString( offset, vl );
|
|
} else {
|
|
data = reader.readString( offset, vl );
|
|
}
|
|
offset += vl;
|
|
data = data.split("\\");
|
|
}
|
|
|
|
// return
|
|
return {
|
|
'tag': tag,
|
|
'vr': vr,
|
|
'vl': vlString,
|
|
'value': data,
|
|
'startOffset': startOffset,
|
|
'endOffset': offset
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Parse the complete DICOM file (given as input to the class).
|
|
* Fills in the member object 'dicomElements'.
|
|
* @param buffer The input array buffer.
|
|
*/
|
|
dwv.dicom.DicomParser.prototype.parse = function (buffer)
|
|
{
|
|
var offset = 0;
|
|
var implicit = false;
|
|
// default readers
|
|
var metaReader = new dwv.dicom.DataReader(buffer);
|
|
var dataReader = new dwv.dicom.DataReader(buffer);
|
|
|
|
// 128 -> 132: magic word
|
|
offset = 128;
|
|
var magicword = metaReader.readString( offset, 4 );
|
|
offset += 4 * Uint8Array.BYTES_PER_ELEMENT;
|
|
if(magicword !== "DICM")
|
|
{
|
|
throw new Error("Not a valid DICOM file (no magic DICM word found)");
|
|
}
|
|
|
|
// 0x0002, 0x0000: FileMetaInformationGroupLength
|
|
var dataElement = this.readDataElement(metaReader, offset, false);
|
|
offset = dataElement.endOffset;
|
|
// store the data element
|
|
this.dicomElements[dataElement.tag.name] = dataElement;
|
|
// get meta length
|
|
var metaLength = parseInt(dataElement.value[0], 10);
|
|
|
|
// meta elements
|
|
var metaEnd = offset + metaLength;
|
|
while( offset < metaEnd )
|
|
{
|
|
// get the data element
|
|
dataElement = this.readDataElement(metaReader, offset, false);
|
|
offset = dataElement.endOffset;
|
|
// store the data element
|
|
this.dicomElements[dataElement.tag.name] = dataElement;
|
|
}
|
|
|
|
// check the TransferSyntaxUID (has to be there!)
|
|
if (typeof this.dicomElements.x00020010 === "undefined")
|
|
{
|
|
throw new Error("Not a valid DICOM file (no TransferSyntaxUID found)");
|
|
}
|
|
var syntax = dwv.dicom.cleanString(this.dicomElements.x00020010.value[0]);
|
|
|
|
// check support
|
|
if (!dwv.dicom.isReadSupportedTransferSyntax(syntax)) {
|
|
throw new Error("Unsupported DICOM transfer syntax: '"+syntax+
|
|
"' ("+dwv.dicom.getTransferSyntaxName(syntax)+")");
|
|
}
|
|
|
|
// Implicit VR
|
|
if (dwv.dicom.isImplicitTransferSyntax(syntax)) {
|
|
implicit = true;
|
|
}
|
|
|
|
// Big Endian
|
|
if (dwv.dicom.isBigEndianTransferSyntax(syntax)) {
|
|
dataReader = new dwv.dicom.DataReader(buffer,false);
|
|
}
|
|
|
|
// default character set
|
|
if (typeof this.getDefaultCharacterSet() !== "undefined") {
|
|
dataReader.setUtfLabel(this.getDefaultCharacterSet());
|
|
}
|
|
|
|
// DICOM data elements
|
|
while ( offset < buffer.byteLength )
|
|
{
|
|
// get the data element
|
|
dataElement = this.readDataElement(dataReader, offset, implicit);
|
|
// check character set
|
|
if (dataElement.tag.name === "x00080005") {
|
|
var charSetTerm;
|
|
if (dataElement.value.length === 1) {
|
|
charSetTerm = dwv.dicom.cleanString(dataElement.value[0]);
|
|
}
|
|
else {
|
|
charSetTerm = dwv.dicom.cleanString(dataElement.value[1]);
|
|
console.warn("Unsupported character set with code extensions: '"+charSetTerm+"'.");
|
|
}
|
|
dataReader.setUtfLabel(dwv.dicom.getUtfLabel(charSetTerm));
|
|
}
|
|
// increment offset
|
|
offset = dataElement.endOffset;
|
|
// store the data element
|
|
this.dicomElements[dataElement.tag.name] = dataElement;
|
|
}
|
|
|
|
// safety check...
|
|
if (buffer.byteLength !== offset) {
|
|
console.warn("Did not reach the end of the buffer: "+
|
|
offset+" != "+buffer.byteLength);
|
|
}
|
|
|
|
// pixel buffer
|
|
if (typeof this.dicomElements.x7FE00010 !== "undefined") {
|
|
|
|
var numberOfFrames = 1;
|
|
if (typeof this.dicomElements.x00280008 !== "undefined") {
|
|
numberOfFrames = this.dicomElements.x00280008.value[0];
|
|
}
|
|
|
|
if (this.dicomElements.x7FE00010.vl !== "u/l") {
|
|
// compressed should be encapsulated...
|
|
if (dwv.dicom.isJpeg2000TransferSyntax( syntax ) ||
|
|
dwv.dicom.isJpegBaselineTransferSyntax( syntax ) ||
|
|
dwv.dicom.isJpegLosslessTransferSyntax( syntax ) ) {
|
|
console.warn("Compressed but no items...");
|
|
}
|
|
|
|
// calculate the slice size
|
|
var pixData = this.dicomElements.x7FE00010.value;
|
|
var columns = this.dicomElements.x00280011.value[0];
|
|
var rows = this.dicomElements.x00280010.value[0];
|
|
var samplesPerPixel = this.dicomElements.x00280002.value[0];
|
|
var sliceSize = columns * rows * samplesPerPixel;
|
|
// slice data in an array of frames
|
|
var newPixData = [];
|
|
var frameOffset = 0;
|
|
for (var g = 0; g < numberOfFrames; ++g) {
|
|
newPixData[g] = pixData.slice(frameOffset, frameOffset+sliceSize);
|
|
frameOffset += sliceSize;
|
|
}
|
|
// store as pixel data
|
|
this.dicomElements.x7FE00010.value = newPixData;
|
|
}
|
|
else {
|
|
// handle fragmented pixel buffer
|
|
// Reference: http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_8.2.html
|
|
// (third note, "Depending on the transfer syntax...")
|
|
var pixItems = this.dicomElements.x7FE00010.value;
|
|
if (pixItems.length > 1 && pixItems.length > numberOfFrames ) {
|
|
|
|
// concatenate pixel data items
|
|
// concat does not work on typed arrays
|
|
//this.pixelBuffer = this.pixelBuffer.concat( dataElement.data );
|
|
// manual concat...
|
|
var nItemPerFrame = pixItems.length / numberOfFrames;
|
|
var newPixItems = [];
|
|
var index = 0;
|
|
for (var f = 0; f < numberOfFrames; ++f) {
|
|
index = f * nItemPerFrame;
|
|
// calculate the size of a frame
|
|
var size = 0;
|
|
for (var i = 0; i < nItemPerFrame; ++i) {
|
|
size += pixItems[index + i].length;
|
|
}
|
|
// create new buffer
|
|
var newBuffer = new pixItems[0].constructor(size);
|
|
// fill new buffer
|
|
var fragOffset = 0;
|
|
for (var j = 0; j < nItemPerFrame; ++j) {
|
|
newBuffer.set( pixItems[index + j], fragOffset );
|
|
fragOffset += pixItems[index + j].length;
|
|
}
|
|
newPixItems[f] = newBuffer;
|
|
}
|
|
// store as pixel data
|
|
this.dicomElements.x7FE00010.value = newPixItems;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* DicomElements wrapper.
|
|
* @constructor
|
|
* @param {Array} dicomElements The elements to wrap.
|
|
*/
|
|
dwv.dicom.DicomElementsWrapper = function (dicomElements) {
|
|
|
|
/**
|
|
* Get a DICOM Element value from a group/element key.
|
|
* @param {String} groupElementKey The key to retrieve.
|
|
* @return {Object} The DICOM element.
|
|
*/
|
|
this.getDEFromKey = function ( groupElementKey ) {
|
|
return dicomElements[groupElementKey];
|
|
};
|
|
|
|
/**
|
|
* Get a DICOM Element value from a group/element key.
|
|
* @param {String} groupElementKey The key to retrieve.
|
|
* @param {Boolean} asArray Get the value as an Array.
|
|
* @return {Object} The DICOM element value.
|
|
*/
|
|
this.getFromKey = function ( groupElementKey, asArray ) {
|
|
// default
|
|
if ( typeof asArray === "undefined" ) {
|
|
asArray = false;
|
|
}
|
|
var value = null;
|
|
var dElement = dicomElements[groupElementKey];
|
|
if ( typeof dElement !== "undefined" ) {
|
|
// raw value if only one
|
|
if ( dElement.value.length === 1 && asArray === false) {
|
|
value = dElement.value[0];
|
|
}
|
|
else {
|
|
value = dElement.value;
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Dump the DICOM tags to an array.
|
|
* @return {Array}
|
|
*/
|
|
this.dumpToTable = function () {
|
|
var keys = Object.keys(dicomElements);
|
|
var dict = dwv.dicom.dictionary;
|
|
var table = [];
|
|
var dicomElement = null;
|
|
var dictElement = null;
|
|
var row = null;
|
|
for ( var i = 0, leni = keys.length; i < leni; ++i ) {
|
|
dicomElement = dicomElements[keys[i]];
|
|
row = {};
|
|
// dictionnary entry (to get name)
|
|
dictElement = null;
|
|
if ( typeof dict[dicomElement.tag.group] !== "undefined" &&
|
|
typeof dict[dicomElement.tag.group][dicomElement.tag.element] !== "undefined") {
|
|
dictElement = dict[dicomElement.tag.group][dicomElement.tag.element];
|
|
}
|
|
// name
|
|
if ( dictElement !== null ) {
|
|
row.name = dictElement[2];
|
|
}
|
|
else {
|
|
row.name = "Unknown Tag & Data";
|
|
}
|
|
// value
|
|
row.value = this.getElementValueAsString(dicomElement);
|
|
// others
|
|
row.group = dicomElement.tag.group;
|
|
row.element = dicomElement.tag.element;
|
|
row.vr = dicomElement.vr;
|
|
row.vl = dicomElement.vl;
|
|
|
|
table.push( row );
|
|
}
|
|
return table;
|
|
};
|
|
|
|
/**
|
|
* Dump the DICOM tags to a string.
|
|
* @return {String} The dumped file.
|
|
*/
|
|
this.dump = function () {
|
|
var keys = Object.keys(dicomElements);
|
|
var result = "\n";
|
|
result += "# Dicom-File-Format\n";
|
|
result += "\n";
|
|
result += "# Dicom-Meta-Information-Header\n";
|
|
result += "# Used TransferSyntax: ";
|
|
if ( dwv.dicom.isNativeLittleEndian() ) {
|
|
result += "Little Endian Explicit\n";
|
|
}
|
|
else {
|
|
result += "NOT Little Endian Explicit\n";
|
|
}
|
|
var dicomElement = null;
|
|
var checkHeader = true;
|
|
for ( var i = 0, leni = keys.length; i < leni; ++i ) {
|
|
dicomElement = dicomElements[keys[i]];
|
|
if ( checkHeader && dicomElement.tag.group !== "0x0002" ) {
|
|
result += "\n";
|
|
result += "# Dicom-Data-Set\n";
|
|
result += "# Used TransferSyntax: ";
|
|
var syntax = dwv.dicom.cleanString(dicomElements.x00020010.value[0]);
|
|
result += dwv.dicom.getTransferSyntaxName(syntax);
|
|
result += "\n";
|
|
checkHeader = false;
|
|
}
|
|
result += this.getElementAsString(dicomElement) + "\n";
|
|
}
|
|
return result;
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* Get a data element value as a string.
|
|
* @param {Object} dicomElement The DICOM element.
|
|
* @param {Boolean} pretty When set to true, returns a 'pretified' content.
|
|
* @return {String} A string representation of the DICOM element.
|
|
*/
|
|
dwv.dicom.DicomElementsWrapper.prototype.getElementValueAsString = function ( dicomElement, pretty )
|
|
{
|
|
var str = "";
|
|
var strLenLimit = 65;
|
|
|
|
// dafault to pretty output
|
|
if ( typeof pretty === "undefined" ) {
|
|
pretty = true;
|
|
}
|
|
// check dicom element input
|
|
if ( typeof dicomElement === "undefined" || dicomElement === null ) {
|
|
return str;
|
|
}
|
|
|
|
// Polyfill for Number.isInteger.
|
|
var isInteger = Number.isInteger || function (value) {
|
|
return typeof value === 'number' &&
|
|
isFinite(value) &&
|
|
Math.floor(value) === value;
|
|
};
|
|
|
|
// TODO Support sequences.
|
|
|
|
if ( dicomElement.vr !== "SQ" &&
|
|
dicomElement.value.length === 1 && dicomElement.value[0] === "" ) {
|
|
str += "(no value available)";
|
|
} else if ( dicomElement.tag.group === '0x7FE0' &&
|
|
dicomElement.tag.element === '0x0010' &&
|
|
dicomElement.vl === 'u/l' ) {
|
|
str = "(PixelSequence)";
|
|
} else if ( dicomElement.vr === "DA" && pretty ) {
|
|
var daValue = dicomElement.value[0];
|
|
var daYear = parseInt( daValue.substr(0,4), 10 );
|
|
var daMonth = parseInt( daValue.substr(4,2), 10 ) - 1; // 0-11
|
|
var daDay = parseInt( daValue.substr(6,2), 10 );
|
|
var da = new Date(daYear, daMonth, daDay);
|
|
str = da.toLocaleDateString();
|
|
} else if ( dicomElement.vr === "TM" && pretty ) {
|
|
var tmValue = dicomElement.value[0];
|
|
var tmHour = tmValue.substr(0,2);
|
|
var tmMinute = tmValue.length >= 4 ? tmValue.substr(2,2) : "00";
|
|
var tmSeconds = tmValue.length >= 6 ? tmValue.substr(4,2) : "00";
|
|
str = tmHour + ':' + tmMinute + ':' + tmSeconds;
|
|
} else {
|
|
var isOtherVR = ( dicomElement.vr[0].toUpperCase() === "O" );
|
|
var isFloatNumberVR = ( dicomElement.vr === "FL" ||
|
|
dicomElement.vr === "FD" ||
|
|
dicomElement.vr === "DS");
|
|
var valueStr = "";
|
|
for ( var k = 0, lenk = dicomElement.value.length; k < lenk; ++k ) {
|
|
valueStr = "";
|
|
if ( k !== 0 ) {
|
|
valueStr += "\\";
|
|
}
|
|
if ( isFloatNumberVR ) {
|
|
var val = dicomElement.value[k];
|
|
if (typeof val === "string") {
|
|
val = dwv.dicom.cleanString(val);
|
|
}
|
|
var num = Number( val );
|
|
if ( !isInteger( num ) && pretty ) {
|
|
valueStr += num.toPrecision(4);
|
|
} else {
|
|
valueStr += num.toString();
|
|
}
|
|
} else if ( isOtherVR ) {
|
|
var tmp = dicomElement.value[k].toString(16);
|
|
if ( dicomElement.vr === "OB" ) {
|
|
tmp = "00".substr(0, 2 - tmp.length) + tmp;
|
|
}
|
|
else {
|
|
tmp = "0000".substr(0, 4 - tmp.length) + tmp;
|
|
}
|
|
valueStr += tmp;
|
|
} else if ( typeof dicomElement.value[k] === "string" ) {
|
|
valueStr += dwv.dicom.cleanString(dicomElement.value[k]);
|
|
} else {
|
|
valueStr += dicomElement.value[k];
|
|
}
|
|
// check length
|
|
if ( str.length + valueStr.length <= strLenLimit ) {
|
|
str += valueStr;
|
|
} else {
|
|
str += "...";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return str;
|
|
};
|
|
|
|
/**
|
|
* Get a data element value as a string.
|
|
* @param {String} groupElementKey The key to retrieve.
|
|
*/
|
|
dwv.dicom.DicomElementsWrapper.prototype.getElementValueAsStringFromKey = function ( groupElementKey )
|
|
{
|
|
return this.getElementValueAsString( this.getDEFromKey(groupElementKey) );
|
|
};
|
|
|
|
/**
|
|
* Get a data element as a string.
|
|
* @param {Object} dicomElement The DICOM element.
|
|
* @param {String} prefix A string to prepend this one.
|
|
*/
|
|
dwv.dicom.DicomElementsWrapper.prototype.getElementAsString = function ( dicomElement, prefix )
|
|
{
|
|
// default prefix
|
|
prefix = prefix || "";
|
|
|
|
// get element from dictionary
|
|
var dict = dwv.dicom.dictionary;
|
|
var dictElement = null;
|
|
if ( typeof dict[dicomElement.tag.group] !== "undefined" &&
|
|
typeof dict[dicomElement.tag.group][dicomElement.tag.element] !== "undefined") {
|
|
dictElement = dict[dicomElement.tag.group][dicomElement.tag.element];
|
|
}
|
|
|
|
var deSize = dicomElement.value.length;
|
|
var isOtherVR = ( dicomElement.vr[0].toUpperCase() === "O" );
|
|
|
|
// no size for delimitations
|
|
if ( dicomElement.tag.group === "0xFFFE" && (
|
|
dicomElement.tag.element === "0xE00D" ||
|
|
dicomElement.tag.element === "0xE0DD" ) ) {
|
|
deSize = 0;
|
|
}
|
|
else if ( isOtherVR ) {
|
|
deSize = 1;
|
|
}
|
|
|
|
var isPixSequence = (dicomElement.tag.group === '0x7FE0' &&
|
|
dicomElement.tag.element === '0x0010' &&
|
|
dicomElement.vl === 'u/l');
|
|
|
|
var line = null;
|
|
|
|
// (group,element)
|
|
line = "(";
|
|
line += dicomElement.tag.group.substr(2,5).toLowerCase();
|
|
line += ",";
|
|
line += dicomElement.tag.element.substr(2,5).toLowerCase();
|
|
line += ") ";
|
|
// value representation
|
|
line += dicomElement.vr;
|
|
// value
|
|
if ( dicomElement.vr !== "SQ" && dicomElement.value.length === 1 && dicomElement.value[0] === "" ) {
|
|
line += " (no value available)";
|
|
deSize = 0;
|
|
}
|
|
else {
|
|
// simple number display
|
|
if ( dicomElement.vr === "na" ) {
|
|
line += " ";
|
|
line += dicomElement.value[0];
|
|
}
|
|
// pixel sequence
|
|
else if ( isPixSequence ) {
|
|
line += " (PixelSequence #=" + deSize + ")";
|
|
}
|
|
else if ( dicomElement.vr === 'SQ' ) {
|
|
line += " (Sequence with";
|
|
if ( dicomElement.vl === "u/l" ) {
|
|
line += " undefined";
|
|
}
|
|
else {
|
|
line += " explicit";
|
|
}
|
|
line += " length #=";
|
|
line += dicomElement.value.length;
|
|
line += ")";
|
|
}
|
|
// 'O'ther array, limited display length
|
|
else if ( isOtherVR ||
|
|
dicomElement.vr === 'pi' ||
|
|
dicomElement.vr === "UL" ||
|
|
dicomElement.vr === "US" ||
|
|
dicomElement.vr === "SL" ||
|
|
dicomElement.vr === "SS" ||
|
|
dicomElement.vr === "FL" ||
|
|
dicomElement.vr === "FD" ||
|
|
dicomElement.vr === "AT" ) {
|
|
line += " ";
|
|
line += this.getElementValueAsString(dicomElement, false);
|
|
}
|
|
// default
|
|
else {
|
|
line += " [";
|
|
line += this.getElementValueAsString(dicomElement, false);
|
|
line += "]";
|
|
}
|
|
}
|
|
|
|
// align #
|
|
var nSpaces = 55 - line.length;
|
|
if ( nSpaces > 0 ) {
|
|
for ( var s = 0; s < nSpaces; ++s ) {
|
|
line += " ";
|
|
}
|
|
}
|
|
line += " # ";
|
|
if ( dicomElement.vl < 100 ) {
|
|
line += " ";
|
|
}
|
|
if ( dicomElement.vl < 10 ) {
|
|
line += " ";
|
|
}
|
|
line += dicomElement.vl;
|
|
line += ", ";
|
|
line += deSize; //dictElement[1];
|
|
line += " ";
|
|
if ( dictElement !== null ) {
|
|
line += dictElement[2];
|
|
}
|
|
else {
|
|
line += "Unknown Tag & Data";
|
|
}
|
|
|
|
var message = null;
|
|
|
|
// continue for sequence
|
|
if ( dicomElement.vr === 'SQ' ) {
|
|
var item = null;
|
|
for ( var l = 0, lenl = dicomElement.value.length; l < lenl; ++l ) {
|
|
item = dicomElement.value[l];
|
|
var itemKeys = Object.keys(item);
|
|
if ( itemKeys.length === 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// get the item element
|
|
var itemElement = item.xFFFEE000;
|
|
message = "(Item with";
|
|
if ( itemElement.vl === "u/l" ) {
|
|
message += " undefined";
|
|
}
|
|
else {
|
|
message += " explicit";
|
|
}
|
|
message += " length #="+(itemKeys.length - 1)+")";
|
|
itemElement.value = [message];
|
|
itemElement.vr = "na";
|
|
|
|
line += "\n";
|
|
line += this.getElementAsString(itemElement, prefix + " ");
|
|
|
|
for ( var m = 0, lenm = itemKeys.length; m < lenm; ++m ) {
|
|
if ( itemKeys[m] !== "xFFFEE000" ) {
|
|
line += "\n";
|
|
line += this.getElementAsString(item[itemKeys[m]], prefix + " ");
|
|
}
|
|
}
|
|
|
|
message = "(ItemDelimitationItem";
|
|
if ( itemElement.vl !== "u/l" ) {
|
|
message += " for re-encoding";
|
|
}
|
|
message += ")";
|
|
var itemDelimElement = {
|
|
"tag": { "group": "0xFFFE", "element": "0xE00D" },
|
|
"vr": "na",
|
|
"vl": "0",
|
|
"value": [message]
|
|
};
|
|
line += "\n";
|
|
line += this.getElementAsString(itemDelimElement, prefix + " ");
|
|
|
|
}
|
|
|
|
message = "(SequenceDelimitationItem";
|
|
if ( dicomElement.vl !== "u/l" ) {
|
|
message += " for re-encod.";
|
|
}
|
|
message += ")";
|
|
var sqDelimElement = {
|
|
"tag": { "group": "0xFFFE", "element": "0xE0DD" },
|
|
"vr": "na",
|
|
"vl": "0",
|
|
"value": [message]
|
|
};
|
|
line += "\n";
|
|
line += this.getElementAsString(sqDelimElement, prefix);
|
|
}
|
|
// pixel sequence
|
|
else if ( isPixSequence ) {
|
|
var pixItem = null;
|
|
for ( var n = 0, lenn = dicomElement.value.length; n < lenn; ++n ) {
|
|
pixItem = dicomElement.value[n];
|
|
line += "\n";
|
|
pixItem.vr = 'pi';
|
|
line += this.getElementAsString(pixItem, prefix + " ");
|
|
}
|
|
|
|
var pixDelimElement = {
|
|
"tag": { "group": "0xFFFE", "element": "0xE0DD" },
|
|
"vr": "na",
|
|
"vl": "0",
|
|
"value": ["(SequenceDelimitationItem)"]
|
|
};
|
|
line += "\n";
|
|
line += this.getElementAsString(pixDelimElement, prefix);
|
|
}
|
|
|
|
return prefix + line;
|
|
};
|
|
|
|
/**
|
|
* Get a DICOM Element value from a group and an element.
|
|
* @param {Number} group The group.
|
|
* @param {Number} element The element.
|
|
* @return {Object} The DICOM element value.
|
|
*/
|
|
dwv.dicom.DicomElementsWrapper.prototype.getFromGroupElement = function (
|
|
group, element )
|
|
{
|
|
return this.getFromKey(
|
|
dwv.dicom.getGroupElementKey(group, element) );
|
|
};
|
|
|
|
/**
|
|
* Get a DICOM Element value from a tag name.
|
|
* Uses the DICOM dictionary.
|
|
* @param {String} name The tag name.
|
|
* @return {Object} The DICOM element value.
|
|
*/
|
|
dwv.dicom.DicomElementsWrapper.prototype.getFromName = function ( name )
|
|
{
|
|
var value = null;
|
|
var tagGE = dwv.dicom.getGroupElementFromName(name);
|
|
// check that we are not at the end of the dictionary
|
|
if ( tagGE.group !== null && tagGE.element !== null ) {
|
|
value = this.getFromKey(dwv.dicom.getGroupElementKey(tagGE.group, tagGE.element));
|
|
}
|
|
return value;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.dicom = dwv.dicom || {};
|
|
|
|
/**
|
|
* Data writer.
|
|
*
|
|
* Example usage:
|
|
* var parser = new dwv.dicom.DicomParser();
|
|
* parser.parse(this.response);
|
|
*
|
|
* var writer = new dwv.dicom.DicomWriter(parser.getRawDicomElements());
|
|
* var blob = new Blob([writer.getBuffer()], {type: 'application/dicom'});
|
|
*
|
|
* var element = document.getElementById("download");
|
|
* element.href = URL.createObjectURL(blob);
|
|
* element.download = "anonym.dcm";
|
|
*
|
|
* @constructor
|
|
* @param {Array} buffer The input array buffer.
|
|
* @param {Boolean} isLittleEndian Flag to tell if the data is little or big endian.
|
|
*/
|
|
dwv.dicom.DataWriter = function (buffer, isLittleEndian)
|
|
{
|
|
// Set endian flag if not defined.
|
|
if ( typeof isLittleEndian === 'undefined' ) {
|
|
isLittleEndian = true;
|
|
}
|
|
|
|
// private DataView
|
|
var view = new DataView(buffer);
|
|
|
|
/**
|
|
* Write Uint8 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeUint8 = function (byteOffset, value) {
|
|
view.setUint8(byteOffset, value);
|
|
return byteOffset + Uint8Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Int8 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeInt8 = function (byteOffset, value) {
|
|
view.setInt8(byteOffset, value);
|
|
return byteOffset + Int8Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Uint16 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeUint16 = function (byteOffset, value) {
|
|
view.setUint16(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Uint16Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Int16 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeInt16 = function (byteOffset, value) {
|
|
view.setInt16(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Int16Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Uint32 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeUint32 = function (byteOffset, value) {
|
|
view.setUint32(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Uint32Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Int32 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeInt32 = function (byteOffset, value) {
|
|
view.setInt32(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Int32Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Float32 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeFloat32 = function (byteOffset, value) {
|
|
view.setFloat32(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Float32Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write Float64 data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} value The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeFloat64 = function (byteOffset, value) {
|
|
view.setFloat64(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Float64Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write string data as hexadecimal.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} str The padded hexadecimal string to write ('0x####').
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeHex = function (byteOffset, str) {
|
|
// remove first two chars and parse
|
|
var value = parseInt(str.substr(2), 16);
|
|
view.setUint16(byteOffset, value, isLittleEndian);
|
|
return byteOffset + Uint16Array.BYTES_PER_ELEMENT;
|
|
};
|
|
|
|
/**
|
|
* Write string data.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Number} str The data to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
this.writeString = function (byteOffset, str) {
|
|
for ( var i = 0, len = str.length; i < len; ++i ) {
|
|
view.setUint8(byteOffset, str.charCodeAt(i));
|
|
byteOffset += Uint8Array.BYTES_PER_ELEMENT;
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* Write Uint8 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeUint8Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeUint8(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Int8 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeInt8Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeInt8(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Uint16 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeUint16Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeUint16(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Int16 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeInt16Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeInt16(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Uint32 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeUint32Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeUint32(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Int32 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeInt32Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeInt32(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Float32 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeFloat32Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeFloat32(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write Float64 array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeFloat64Array = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
byteOffset = this.writeFloat64(byteOffset, array[i]);
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write string array.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} array The array to write.
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeStringArray = function (byteOffset, array) {
|
|
for ( var i = 0, len = array.length; i < len; ++i ) {
|
|
// separator
|
|
if ( i !== 0 ) {
|
|
byteOffset = this.writeString(byteOffset, "\\");
|
|
}
|
|
// value
|
|
byteOffset = this.writeString(byteOffset, array[i].toString());
|
|
}
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write a list of items.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} items The list of items to write.
|
|
* @param {Boolean} isImplicit Is the DICOM VR implicit?
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeDataElementItems = function (byteOffset, items, isImplicit) {
|
|
var item = null;
|
|
for ( var i = 0; i < items.length; ++i ) {
|
|
item = items[i];
|
|
var itemKeys = Object.keys(item);
|
|
if ( itemKeys.length === 0 ) {
|
|
continue;
|
|
}
|
|
// write item
|
|
var itemElement = item.xFFFEE000;
|
|
itemElement.value = [];
|
|
var implicitLength = (itemElement.vl === "u/l");
|
|
if (implicitLength) {
|
|
itemElement.vl = 0xffffffff;
|
|
}
|
|
byteOffset = this.writeDataElement(itemElement, byteOffset, isImplicit);
|
|
// write rest
|
|
for ( var m = 0; m < itemKeys.length; ++m ) {
|
|
if ( itemKeys[m] !== "xFFFEE000" && itemKeys[m] !== "xFFFEE00D") {
|
|
byteOffset = this.writeDataElement(item[itemKeys[m]], byteOffset, isImplicit);
|
|
}
|
|
}
|
|
// item delimitation
|
|
if (implicitLength) {
|
|
var itemDelimElement = {
|
|
'tag': { group: "0xFFFE",
|
|
element: "0xE00D",
|
|
name: "ItemDelimitationItem" },
|
|
'vr': "NONE",
|
|
'vl': 0,
|
|
'value': []
|
|
};
|
|
byteOffset = this.writeDataElement(itemDelimElement, byteOffset, isImplicit);
|
|
}
|
|
}
|
|
|
|
// return new offset
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write data with a specific Value Representation (VR).
|
|
* @param {String} vr The data Value Representation (VR).
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} value The array to write.
|
|
* @param {Boolean} isImplicit Is the DICOM VR implicit?
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeDataElementValue = function (vr, byteOffset, value, isImplicit) {
|
|
// first check input type to know how to write
|
|
if (value instanceof Uint8Array) {
|
|
byteOffset = this.writeUint8Array(byteOffset, value);
|
|
} else if (value instanceof Int8Array) {
|
|
byteOffset = this.writeInt8Array(byteOffset, value);
|
|
} else if (value instanceof Uint16Array) {
|
|
byteOffset = this.writeUint16Array(byteOffset, value);
|
|
} else if (value instanceof Int16Array) {
|
|
byteOffset = this.writeInt16Array(byteOffset, value);
|
|
} else if (value instanceof Uint32Array) {
|
|
byteOffset = this.writeUint32Array(byteOffset, value);
|
|
} else if (value instanceof Int32Array) {
|
|
byteOffset = this.writeInt32Array(byteOffset, value);
|
|
} else {
|
|
// switch according to VR if input type is undefined
|
|
if ( vr === "UN" ) {
|
|
byteOffset = this.writeUint8Array(byteOffset, value);
|
|
} else if ( vr === "OB" ) {
|
|
byteOffset = this.writeInt8Array(byteOffset, value);
|
|
} else if ( vr === "OW" ) {
|
|
byteOffset = this.writeInt16Array(byteOffset, value);
|
|
} else if ( vr === "OF" ) {
|
|
byteOffset = this.writeInt32Array(byteOffset, value);
|
|
} else if ( vr === "OD" ) {
|
|
byteOffset = this.writeInt64Array(byteOffset, value);
|
|
} else if ( vr === "US") {
|
|
byteOffset = this.writeUint16Array(byteOffset, value);
|
|
} else if ( vr === "SS") {
|
|
byteOffset = this.writeInt16Array(byteOffset, value);
|
|
} else if ( vr === "UL") {
|
|
byteOffset = this.writeUint32Array(byteOffset, value);
|
|
} else if ( vr === "SL") {
|
|
byteOffset = this.writeInt32Array(byteOffset, value);
|
|
} else if ( vr === "FL") {
|
|
byteOffset = this.writeFloat32Array(byteOffset, value);
|
|
} else if ( vr === "FD") {
|
|
byteOffset = this.writeFloat64Array(byteOffset, value);
|
|
} else if ( vr === "SQ") {
|
|
byteOffset = this.writeDataElementItems(byteOffset, value, isImplicit);
|
|
} else if ( vr === "AT") {
|
|
for ( var i = 0; i < value.length; ++i ) {
|
|
var hexString = value[i] + '';
|
|
var hexString1 = hexString.substring(1, 5);
|
|
var hexString2 = hexString.substring(6, 10);
|
|
var dec1 = parseInt(hexString1, 16);
|
|
var dec2 = parseInt(hexString2, 16);
|
|
var atValue = new Uint16Array([dec1, dec2]);
|
|
byteOffset = this.writeUint16Array(byteOffset, atValue);
|
|
}
|
|
} else {
|
|
byteOffset = this.writeStringArray(byteOffset, value);
|
|
}
|
|
}
|
|
// return new offset
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write a pixel data element.
|
|
* @param {String} vr The data Value Representation (VR).
|
|
* @param {String} vl The data Value Length (VL).
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Array} value The array to write.
|
|
* @param {Boolean} isImplicit Is the DICOM VR implicit?
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writePixelDataElementValue = function (vr, vl, byteOffset, value, isImplicit) {
|
|
// explicit length
|
|
if (vl !== "u/l") {
|
|
var finalValue = value[0];
|
|
// flatten multi frame
|
|
if (value.length > 1) {
|
|
finalValue = dwv.dicom.flattenArrayOfTypedArrays(value);
|
|
}
|
|
// write
|
|
byteOffset = this.writeDataElementValue(vr, byteOffset, finalValue, isImplicit);
|
|
} else {
|
|
// pixel data as sequence
|
|
var item = {};
|
|
// first item: basic offset table
|
|
item.xFFFEE000 = {
|
|
'tag': { group: "0xFFFE",
|
|
element: "0xE000",
|
|
name: "xFFFEE000" },
|
|
'vr': "UN",
|
|
'vl': 0,
|
|
'value': []
|
|
};
|
|
// data
|
|
for (var i = 0; i < value.length; ++i) {
|
|
item[i] = {
|
|
'tag': { group: "0xFFFE",
|
|
element: "0xE000",
|
|
name: "xFFFEE000" },
|
|
'vr': vr,
|
|
'vl': value[i].length,
|
|
'value': value[i]
|
|
};
|
|
}
|
|
// write
|
|
byteOffset = this.writeDataElementItems(byteOffset, [item], isImplicit);
|
|
}
|
|
|
|
// return new offset
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Write a data element.
|
|
* @param {Object} element The DICOM data element to write.
|
|
* @param {Number} byteOffset The offset to start writing from.
|
|
* @param {Boolean} isImplicit Is the DICOM VR implicit?
|
|
* @returns {Number} The new offset position.
|
|
*/
|
|
dwv.dicom.DataWriter.prototype.writeDataElement = function (element, byteOffset, isImplicit) {
|
|
var isTagWithVR = dwv.dicom.isTagWithVR(element.tag.group, element.tag.element);
|
|
var is32bitVLVR = (isImplicit || !isTagWithVR) ? true : dwv.dicom.is32bitVLVR(element.vr);
|
|
// group
|
|
byteOffset = this.writeHex(byteOffset, element.tag.group);
|
|
// element
|
|
byteOffset = this.writeHex(byteOffset, element.tag.element);
|
|
// VR
|
|
if ( isTagWithVR && !isImplicit ) {
|
|
byteOffset = this.writeString(byteOffset, element.vr);
|
|
// reserved 2 bytes for 32bit VL
|
|
if ( is32bitVLVR ) {
|
|
byteOffset += 2;
|
|
}
|
|
}
|
|
|
|
// update vl for sequence or item with implicit length
|
|
var vl = element.vl;
|
|
if ( dwv.dicom.isImplicitLengthSequence(element) ||
|
|
dwv.dicom.isImplicitLengthItem(element) ||
|
|
dwv.dicom.isImplicitLengthPixels(element) ) {
|
|
vl = 0xffffffff;
|
|
}
|
|
// VL
|
|
if ( is32bitVLVR ) {
|
|
byteOffset = this.writeUint32(byteOffset, vl);
|
|
}
|
|
else {
|
|
byteOffset = this.writeUint16(byteOffset, vl);
|
|
}
|
|
|
|
// value
|
|
var value = element.value;
|
|
// check value
|
|
if (typeof value === 'undefined') {
|
|
value = [];
|
|
}
|
|
// write
|
|
if (element.tag.name === "x7FE00010") {
|
|
byteOffset = this.writePixelDataElementValue(element.vr, element.vl, byteOffset, value, isImplicit);
|
|
} else {
|
|
byteOffset = this.writeDataElementValue(element.vr, byteOffset, value, isImplicit);
|
|
}
|
|
|
|
// sequence delimitation item for sequence with implicit length
|
|
if ( dwv.dicom.isImplicitLengthSequence(element) ||
|
|
dwv.dicom.isImplicitLengthPixels(element) ) {
|
|
var seqDelimElement = {
|
|
'tag': { group: "0xFFFE",
|
|
element: "0xE0DD",
|
|
name: "SequenceDelimitationItem" },
|
|
'vr': "NONE",
|
|
'vl': 0,
|
|
'value': []
|
|
};
|
|
byteOffset = this.writeDataElement(seqDelimElement, byteOffset, isImplicit);
|
|
}
|
|
|
|
// return new offset
|
|
return byteOffset;
|
|
};
|
|
|
|
/**
|
|
* Is this element an implicit length sequence?
|
|
* @param {Object} element The element to check.
|
|
* @returns {Boolean} True if it is.
|
|
*/
|
|
dwv.dicom.isImplicitLengthSequence = function (element) {
|
|
// sequence with no length
|
|
return (element.vr === "SQ") &&
|
|
(element.vl === "u/l");
|
|
};
|
|
|
|
/**
|
|
* Is this element an implicit length item?
|
|
* @param {Object} element The element to check.
|
|
* @returns {Boolean} True if it is.
|
|
*/
|
|
dwv.dicom.isImplicitLengthItem = function (element) {
|
|
// item with no length
|
|
return (element.tag.name === "xFFFEE000") &&
|
|
(element.vl === "u/l");
|
|
};
|
|
|
|
/**
|
|
* Is this element an implicit length pixel data?
|
|
* @param {Object} element The element to check.
|
|
* @returns {Boolean} True if it is.
|
|
*/
|
|
dwv.dicom.isImplicitLengthPixels = function (element) {
|
|
// pixel data with no length
|
|
return (element.tag.name === "x7FE00010") &&
|
|
(element.vl === "u/l");
|
|
};
|
|
|
|
/**
|
|
* Helper method to flatten an array of typed arrays to 2D typed array
|
|
* @param {Array} array of typed arrays
|
|
* @returns {Object} a typed array containing all values
|
|
*/
|
|
dwv.dicom.flattenArrayOfTypedArrays = function(initialArray) {
|
|
var initialArrayLength = initialArray.length;
|
|
var arrayLength = initialArray[0].length;
|
|
var flattenendArrayLength = initialArrayLength * arrayLength;
|
|
|
|
var flattenedArray = new initialArray[0].constructor(flattenendArrayLength);
|
|
|
|
for (var i = 0; i < initialArrayLength; i++) {
|
|
var indexFlattenedArray = i * arrayLength;
|
|
flattenedArray.set(initialArray[i], indexFlattenedArray);
|
|
}
|
|
return flattenedArray;
|
|
};
|
|
|
|
/**
|
|
* DICOM writer.
|
|
* @constructor
|
|
*/
|
|
dwv.dicom.DicomWriter = function () {
|
|
|
|
// possible tag actions
|
|
var actions = {
|
|
'copy': function (item) { return item; },
|
|
'remove': function () { return null; },
|
|
'clear': function (item) {
|
|
item.value[0] = "";
|
|
item.vl = 0;
|
|
item.endOffset = item.startOffset;
|
|
return item;
|
|
},
|
|
'replace': function (item, value) {
|
|
item.value[0] = value;
|
|
item.vl = value.length;
|
|
item.endOffset = item.startOffset + value.length;
|
|
return item;
|
|
}
|
|
};
|
|
|
|
// default rules: just copy
|
|
var defaultRules = {
|
|
'default': {action: 'copy', value: null }
|
|
};
|
|
|
|
/**
|
|
* Public (modifiable) rules.
|
|
* Set of objects as:
|
|
* name : { action: 'actionName', value: 'optionalValue }
|
|
* The names are either 'default', tagName or groupName.
|
|
* Each DICOM element will be checked to see if a rule is applicable.
|
|
* First checked by tagName and then by groupName,
|
|
* if nothing is found the default rule is applied.
|
|
*/
|
|
this.rules = defaultRules;
|
|
|
|
/**
|
|
* Example anonymisation rules.
|
|
*/
|
|
this.anonymisationRules = {
|
|
'default': {action: 'remove', value: null },
|
|
'PatientName': {action: 'replace', value: 'Anonymized'}, // tag
|
|
'Meta Element' : {action: 'copy', value: null }, // group 'x0002'
|
|
'Acquisition' : {action: 'copy', value: null }, // group 'x0018'
|
|
'Image Presentation' : {action: 'copy', value: null }, // group 'x0028'
|
|
'Procedure' : {action: 'copy', value: null }, // group 'x0040'
|
|
'Pixel Data' : {action: 'copy', value: null } // group 'x7fe0'
|
|
};
|
|
|
|
/**
|
|
* Get the element to write according to the class rules.
|
|
* Priority order: tagName, groupName, default.
|
|
* @param {Object} element The element to check
|
|
* @return {Object} The element to write, can be null.
|
|
*/
|
|
this.getElementToWrite = function (element) {
|
|
// get group and tag string name
|
|
var tagName = null;
|
|
var dict = dwv.dicom.dictionary;
|
|
var group = element.tag.group;
|
|
var groupName = dwv.dicom.TagGroups[group.substr(1)]; // remove first 0
|
|
|
|
if ( typeof dict[group] !== 'undefined' && typeof dict[group][element.tag.element] !== 'undefined') {
|
|
tagName = dict[group][element.tag.element][2];
|
|
}
|
|
// apply rules:
|
|
var rule;
|
|
// 1. tag itself
|
|
if (typeof this.rules[element.tag.name] !== 'undefined') {
|
|
rule = this.rules[element.tag.name];
|
|
}
|
|
// 2. tag name
|
|
else if ( tagName !== null && typeof this.rules[tagName] !== 'undefined' ) {
|
|
rule = this.rules[tagName];
|
|
}
|
|
// 3. group name
|
|
else if ( typeof this.rules[groupName] !== 'undefined' ) {
|
|
rule = this.rules[groupName];
|
|
}
|
|
// 4. default
|
|
else {
|
|
rule = this.rules['default'];
|
|
}
|
|
// apply action on element and return
|
|
return actions[rule.action](element, rule.value);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get the ArrayBuffer corresponding to input DICOM elements.
|
|
* @param {Array} dicomElements The wrapped elements to write.
|
|
* @returns {ArrayBuffer} The elements as a buffer.
|
|
*/
|
|
dwv.dicom.DicomWriter.prototype.getBuffer = function (dicomElements) {
|
|
// array keys
|
|
var keys = Object.keys(dicomElements);
|
|
|
|
// transfer syntax
|
|
var syntax = dwv.dicom.cleanString(dicomElements.x00020010.value[0]);
|
|
var isImplicit = dwv.dicom.isImplicitTransferSyntax(syntax);
|
|
var isBigEndian = dwv.dicom.isBigEndianTransferSyntax(syntax);
|
|
|
|
// calculate buffer size and split elements (meta and non meta)
|
|
var totalSize = 128 + 4; // DICM
|
|
var localSize = 0;
|
|
var metaElements = [];
|
|
var rawElements = [];
|
|
var element;
|
|
var groupName;
|
|
var metaLength = 0;
|
|
var fmiglTag = dwv.dicom.getFileMetaInformationGroupLengthTag();
|
|
var icUIDTag = new dwv.dicom.Tag("0x0002", "0x0012"); // ImplementationClassUID
|
|
var ivnTag = new dwv.dicom.Tag("0x0002", "0x0013"); // ImplementationVersionName
|
|
for ( var i = 0, leni = keys.length; i < leni; ++i ) {
|
|
element = this.getElementToWrite(dicomElements[keys[i]]);
|
|
if ( element !== null &&
|
|
!fmiglTag.equals2(element.tag) &&
|
|
!icUIDTag.equals2(element.tag) &&
|
|
!ivnTag.equals2(element.tag) ) {
|
|
localSize = 0;
|
|
// tag group name
|
|
groupName = dwv.dicom.TagGroups[element.tag.group.substr(1)]; // remove first 0
|
|
|
|
// prefix
|
|
if ( groupName === 'Meta Element' ) {
|
|
localSize += dwv.dicom.getDataElementPrefixByteSize(element.vr, false);
|
|
} else {
|
|
localSize += dwv.dicom.getDataElementPrefixByteSize(element.vr, isImplicit);
|
|
}
|
|
|
|
// value
|
|
var realVl = element.endOffset - element.startOffset;
|
|
localSize += parseInt(realVl, 10);
|
|
|
|
// add size of sequence delimitation item
|
|
if ( dwv.dicom.isImplicitLengthSequence(element) ||
|
|
dwv.dicom.isImplicitLengthPixels(element) ) {
|
|
localSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
|
|
}
|
|
|
|
// sort elements
|
|
if ( groupName === 'Meta Element' ) {
|
|
metaElements.push(element);
|
|
metaLength += localSize;
|
|
}
|
|
else {
|
|
rawElements.push(element);
|
|
}
|
|
|
|
// add to total size
|
|
totalSize += localSize;
|
|
}
|
|
}
|
|
|
|
// ImplementationClassUID
|
|
var icUID = dwv.dicom.getDicomElement("ImplementationClassUID");
|
|
var icUIDSize = dwv.dicom.getDataElementPrefixByteSize(icUID.vr, isImplicit);
|
|
icUIDSize += dwv.dicom.setElementValue(icUID, "1.2.826.0.1.3680043.9.7278.1."+dwv.getVersion(), false);
|
|
metaElements.push(icUID);
|
|
metaLength += icUIDSize;
|
|
totalSize += icUIDSize;
|
|
// ImplementationVersionName
|
|
var ivn = dwv.dicom.getDicomElement("ImplementationVersionName");
|
|
var ivnSize = dwv.dicom.getDataElementPrefixByteSize(ivn.vr, isImplicit);
|
|
ivnSize += dwv.dicom.setElementValue(ivn, "DWV_"+dwv.getVersion(), false);
|
|
metaElements.push(ivn);
|
|
metaLength += ivnSize;
|
|
totalSize += ivnSize;
|
|
|
|
// create the FileMetaInformationGroupLength element
|
|
var fmigl = dwv.dicom.getDicomElement("FileMetaInformationGroupLength");
|
|
var fmiglSize = dwv.dicom.getDataElementPrefixByteSize(fmigl.vr, isImplicit);
|
|
fmiglSize += dwv.dicom.setElementValue(fmigl, metaLength, false);
|
|
|
|
// add its size to the total one
|
|
totalSize += fmiglSize;
|
|
|
|
// create buffer
|
|
var buffer = new ArrayBuffer(totalSize);
|
|
var metaWriter = new dwv.dicom.DataWriter(buffer);
|
|
var dataWriter = new dwv.dicom.DataWriter(buffer, !isBigEndian);
|
|
var offset = 128;
|
|
// DICM
|
|
offset = metaWriter.writeString(offset, "DICM");
|
|
// FileMetaInformationGroupLength
|
|
offset = metaWriter.writeDataElement(fmigl, offset, false);
|
|
// write meta
|
|
for ( var j = 0, lenj = metaElements.length; j < lenj; ++j ) {
|
|
offset = metaWriter.writeDataElement(metaElements[j], offset, false);
|
|
}
|
|
// write non meta
|
|
for ( var k = 0, lenk = rawElements.length; k < lenk; ++k ) {
|
|
offset = dataWriter.writeDataElement(rawElements[k], offset, isImplicit);
|
|
}
|
|
|
|
// return
|
|
return buffer;
|
|
};
|
|
|
|
/**
|
|
* Get a DICOM element from its tag name (value set separatly).
|
|
* @param {String} tagName The string tag name.
|
|
* @return {Object} The DICOM element.
|
|
*/
|
|
dwv.dicom.getDicomElement = function (tagName)
|
|
{
|
|
var tagGE = dwv.dicom.getGroupElementFromName(tagName);
|
|
var dict = dwv.dicom.dictionary;
|
|
// return element definition
|
|
return {
|
|
'tag': { 'group': tagGE.group, 'element': tagGE.element },
|
|
'vr': dict[tagGE.group][tagGE.element][0],
|
|
'vl': dict[tagGE.group][tagGE.element][1]
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Set a DICOM element value according to its VR (Value Representation).
|
|
* @param {Object} element The DICOM element to set the value.
|
|
* @param {Object} value The value to set.
|
|
* @param {Boolean} isImplicit Does the data use implicit VR?
|
|
* @return {Number} The total element size.
|
|
*/
|
|
dwv.dicom.setElementValue = function (element, value, isImplicit) {
|
|
// byte size of the element
|
|
var size = 0;
|
|
// special sequence case
|
|
if ( element.vr === "SQ") {
|
|
|
|
// set the value
|
|
element.value = value;
|
|
element.vl = 0;
|
|
|
|
if ( value !== null && value !== 0 ) {
|
|
var sqItems = [];
|
|
var name;
|
|
|
|
// explicit or implicit length
|
|
var explicitLength = true;
|
|
if ( typeof value.explicitLength !== "undefined" ) {
|
|
explicitLength = value.explicitLength;
|
|
delete value.explicitLength;
|
|
}
|
|
|
|
// items
|
|
var itemData;
|
|
var itemKeys = Object.keys(value);
|
|
for ( var i = 0, leni = itemKeys.length; i < leni; ++i )
|
|
{
|
|
var itemElements = {};
|
|
var subSize = 0;
|
|
itemData = value[itemKeys[i]];
|
|
|
|
// check data
|
|
if ( itemData === null || itemData === 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// elements
|
|
var subElement;
|
|
var elemKeys = Object.keys(itemData);
|
|
for ( var j = 0, lenj = elemKeys.length; j < lenj; ++j )
|
|
{
|
|
subElement = dwv.dicom.getDicomElement(elemKeys[j]);
|
|
subSize += dwv.dicom.setElementValue(subElement, itemData[elemKeys[j]]);
|
|
|
|
// add sequence delimitation size for sub sequences
|
|
if (dwv.dicom.isImplicitLengthSequence(subElement)) {
|
|
subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
|
|
}
|
|
|
|
name = dwv.dicom.getGroupElementKey(subElement.tag.group, subElement.tag.element);
|
|
itemElements[name] = subElement;
|
|
subSize += dwv.dicom.getDataElementPrefixByteSize(subElement.vr, isImplicit);
|
|
}
|
|
|
|
// item (after elements to get the size)
|
|
var itemElement = {
|
|
'tag': { 'group': "0xFFFE", 'element': "0xE000" },
|
|
'vr': "NONE",
|
|
'vl': (explicitLength ? subSize : "u/l"),
|
|
'value': []
|
|
};
|
|
name = dwv.dicom.getGroupElementKey(itemElement.tag.group, itemElement.tag.element);
|
|
itemElements[name] = itemElement;
|
|
subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
|
|
|
|
// item delimitation
|
|
if (!explicitLength) {
|
|
var itemDelimElement = {
|
|
'tag': { 'group': "0xFFFE", 'element': "0xE00D" },
|
|
'vr': "NONE",
|
|
'vl': 0,
|
|
'value': []
|
|
};
|
|
name = dwv.dicom.getGroupElementKey(itemDelimElement.tag.group, itemDelimElement.tag.element);
|
|
itemElements[name] = itemDelimElement;
|
|
subSize += dwv.dicom.getDataElementPrefixByteSize("NONE", isImplicit);
|
|
}
|
|
|
|
size += subSize;
|
|
sqItems.push(itemElements);
|
|
}
|
|
|
|
element.value = sqItems;
|
|
if (explicitLength) {
|
|
element.vl = size;
|
|
} else {
|
|
element.vl = "u/l";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// set the value and calculate size
|
|
size = 0;
|
|
if (value instanceof Array) {
|
|
element.value = value;
|
|
for (var k = 0; k < value.length; ++k) {
|
|
// spearator
|
|
if (k !== 0) {
|
|
size += 1;
|
|
}
|
|
// value
|
|
size += value[k].toString().length;
|
|
}
|
|
}
|
|
else {
|
|
element.value = [value];
|
|
if (typeof value !== "undefined" && typeof value.length !== "undefined") {
|
|
size = value.length;
|
|
}
|
|
else {
|
|
// numbers
|
|
size = 1;
|
|
}
|
|
}
|
|
|
|
// convert size to bytes
|
|
if ( element.vr === "US" || element.vr === "OW") {
|
|
size *= Uint16Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else if ( element.vr === "SS") {
|
|
size *= Int16Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else if ( element.vr === "UL") {
|
|
size *= Uint32Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else if ( element.vr === "SL") {
|
|
size *= Int32Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else if ( element.vr === "FL") {
|
|
size *= Float32Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else if ( element.vr === "FD") {
|
|
size *= Float64Array.BYTES_PER_ELEMENT;
|
|
}
|
|
else {
|
|
size *= Uint8Array.BYTES_PER_ELEMENT;
|
|
}
|
|
element.vl = size;
|
|
}
|
|
|
|
// return the size of that data
|
|
return size;
|
|
};
|
|
|
|
/**
|
|
* Get the DICOM element from a DICOM tags object.
|
|
* @param {Object} tags The DICOM tags object.
|
|
* @return {Object} The DICOM elements and the end offset.
|
|
*/
|
|
dwv.dicom.getElementsFromJSONTags = function (tags) {
|
|
// transfer syntax
|
|
var isImplicit = dwv.dicom.isImplicitTransferSyntax(tags.TransferSyntaxUID);
|
|
// convert JSON to DICOM element object
|
|
var keys = Object.keys(tags);
|
|
var dicomElements = {};
|
|
var dicomElement;
|
|
var name;
|
|
var offset = 128 + 4; // preamble
|
|
var size;
|
|
for ( var k = 0, len = keys.length; k < len; ++k )
|
|
{
|
|
// get the DICOM element definition from its name
|
|
dicomElement = dwv.dicom.getDicomElement(keys[k]);
|
|
// set its value
|
|
size = dwv.dicom.setElementValue(dicomElement, tags[keys[k]], isImplicit);
|
|
// set offsets
|
|
offset += dwv.dicom.getDataElementPrefixByteSize(dicomElement.vr, isImplicit);
|
|
dicomElement.startOffset = offset;
|
|
offset += size;
|
|
dicomElement.endOffset = offset;
|
|
// create the tag group/element key
|
|
name = dwv.dicom.getGroupElementKey(dicomElement.tag.group, dicomElement.tag.element);
|
|
// store
|
|
dicomElements[name] = dicomElement;
|
|
}
|
|
// return
|
|
return {'elements': dicomElements, 'offset': offset };
|
|
};
|
|
|
|
/**
|
|
* GradSquarePixGenerator
|
|
* Generates a small gradient square.
|
|
* @param {Number} numberOfColumns The image number of columns.
|
|
* @param {Number} numberOfRows The image number of rows.
|
|
* @constructor
|
|
*/
|
|
var GradSquarePixGenerator = function (numberOfColumns, numberOfRows) {
|
|
|
|
var halfCols = numberOfColumns * 0.5;
|
|
var halfRows = numberOfRows * 0.5;
|
|
var maxNoBounds = (numberOfColumns/2 + halfCols/2) * (numberOfRows/2 + halfRows/2);
|
|
var max = 100;
|
|
|
|
/**
|
|
* Get a grey value.
|
|
* @param {Number} i The column index.
|
|
* @param {Number} j The row index.
|
|
* @return {Array} The grey value.
|
|
*/
|
|
this.getGrey = function (i,j) {
|
|
var value = max;
|
|
var jc = Math.abs( j - halfRows );
|
|
var ic = Math.abs( i - halfCols );
|
|
if ( jc < halfRows/2 && ic < halfCols/2 ) {
|
|
value += (i * j) * (max / maxNoBounds);
|
|
}
|
|
return [value];
|
|
};
|
|
|
|
/**
|
|
* Get RGB values.
|
|
* @param {Number} i The column index.
|
|
* @param {Number} j The row index.
|
|
* @return {Array} The [R,G,B] values.
|
|
*/
|
|
this.getRGB = function (i,j) {
|
|
var value = 0;
|
|
var jc = Math.abs( j - halfRows );
|
|
var ic = Math.abs( i - halfCols );
|
|
if ( jc < halfRows/2 && ic < halfCols/2 ) {
|
|
value += (i * j) * (max / maxNoBounds);
|
|
}
|
|
if (value > 255 ) {
|
|
value = 200;
|
|
}
|
|
return [0, value, value];
|
|
};
|
|
};
|
|
|
|
// List of pixel generators.
|
|
dwv.dicom.pixelGenerators = {
|
|
'gradSquare': GradSquarePixGenerator
|
|
};
|
|
|
|
/**
|
|
* Get the DICOM pixel data from a DICOM tags object.
|
|
* @param {Object} tags The DICOM tags object.
|
|
* @param {Object} startOffset The start offset of the pixel data.
|
|
* @param {String} pixGeneratorName The name of a pixel generator.
|
|
* @return {Object} The DICOM pixel data element.
|
|
*/
|
|
dwv.dicom.generatePixelDataFromJSONTags = function (tags, startOffset, pixGeneratorName) {
|
|
|
|
// default generator
|
|
if ( typeof pixGeneratorName === "undefined" ) {
|
|
pixGeneratorName = "gradSquare";
|
|
}
|
|
|
|
// check tags
|
|
if ( typeof tags.TransferSyntaxUID === "undefined" ) {
|
|
throw new Error("Missing transfer syntax for pixel generation.");
|
|
} else if ( typeof tags.Rows === "undefined" ) {
|
|
throw new Error("Missing number of rows for pixel generation.");
|
|
} else if ( typeof tags.Columns === "undefined" ) {
|
|
throw new Error("Missing number of columns for pixel generation.");
|
|
} else if ( typeof tags.BitsAllocated === "undefined" ) {
|
|
throw new Error("Missing BitsAllocated for pixel generation.");
|
|
} else if ( typeof tags.PixelRepresentation === "undefined" ) {
|
|
throw new Error("Missing PixelRepresentation for pixel generation.");
|
|
} else if ( typeof tags.SamplesPerPixel === "undefined" ) {
|
|
throw new Error("Missing SamplesPerPixel for pixel generation.");
|
|
} else if ( typeof tags.PhotometricInterpretation === "undefined" ) {
|
|
throw new Error("Missing PhotometricInterpretation for pixel generation.");
|
|
}
|
|
|
|
// extract info from tags
|
|
var isImplicit = dwv.dicom.isImplicitTransferSyntax(tags.TransferSyntaxUID);
|
|
var numberOfRows = tags.Rows;
|
|
var numberOfColumns = tags.Columns;
|
|
var bitsAllocated = tags.BitsAllocated;
|
|
var pixelRepresentation = tags.PixelRepresentation;
|
|
var samplesPerPixel = tags.SamplesPerPixel;
|
|
var photometricInterpretation = tags.PhotometricInterpretation;
|
|
|
|
var sliceLength = numberOfRows * numberOfColumns;
|
|
var dataLength = sliceLength * samplesPerPixel;
|
|
|
|
// check values
|
|
if (samplesPerPixel !== 1 && samplesPerPixel !== 3) {
|
|
throw new Error("Unsupported SamplesPerPixel for pixel generation: "+samplesPerPixel);
|
|
}
|
|
if ( (samplesPerPixel === 1 && !( photometricInterpretation === "MONOCHROME1" ||
|
|
photometricInterpretation === "MONOCHROME2" ) ) ||
|
|
( samplesPerPixel === 3 && photometricInterpretation !== "RGB" ) ) {
|
|
throw new Error("Unsupported PhotometricInterpretation for pixel generation: " +
|
|
photometricInterpretation + " with SamplesPerPixel: " + samplesPerPixel);
|
|
}
|
|
|
|
var nSamples = 1;
|
|
var nColourPlanes = 1;
|
|
if ( samplesPerPixel === 3 ) {
|
|
if ( typeof tags.PlanarConfiguration === "undefined" ) {
|
|
throw new Error("Missing PlanarConfiguration for pixel generation.");
|
|
}
|
|
var planarConfiguration = tags.PlanarConfiguration;
|
|
if ( planarConfiguration !== 0 && planarConfiguration !== 1 ) {
|
|
throw new Error("Unsupported PlanarConfiguration for pixel generation: "+planarConfiguration);
|
|
}
|
|
if ( planarConfiguration === 0 ) {
|
|
nSamples = 3;
|
|
} else {
|
|
nColourPlanes = 3;
|
|
}
|
|
}
|
|
|
|
// create pixel array
|
|
var pixels = dwv.dicom.getTypedArray(
|
|
bitsAllocated, pixelRepresentation, dataLength );
|
|
|
|
// pixels generator
|
|
if (typeof dwv.dicom.pixelGenerators[pixGeneratorName] === "undefined" ) {
|
|
throw new Error("Unknown PixelData generator: "+pixGeneratorName);
|
|
}
|
|
var generator = new dwv.dicom.pixelGenerators[pixGeneratorName](numberOfColumns, numberOfRows);
|
|
var generate = generator.getGrey;
|
|
if (photometricInterpretation === "RGB") {
|
|
generate = generator.getRGB;
|
|
}
|
|
|
|
// main loop
|
|
var offset = 0;
|
|
for ( var c = 0; c < nColourPlanes; ++c ) {
|
|
for ( var j = 0; j < numberOfRows; ++j ) {
|
|
for ( var i = 0; i < numberOfColumns; ++i ) {
|
|
for ( var s = 0; s < nSamples; ++s ) {
|
|
if ( nColourPlanes !== 1 ) {
|
|
pixels[offset] = generate(i,j)[c];
|
|
} else {
|
|
pixels[offset] = generate(i,j)[s];
|
|
}
|
|
++offset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create and return the DICOM element
|
|
var vr = "OW";
|
|
if ( bitsAllocated === 8 ) {
|
|
vr = "OB";
|
|
}
|
|
var pixVL = dwv.dicom.getDataElementPrefixByteSize(vr, isImplicit) +
|
|
(pixels.BYTES_PER_ELEMENT * dataLength);
|
|
return {
|
|
'tag': { 'group': "0x7FE0", 'element': "0x0010" },
|
|
'vr': vr,
|
|
'vl': pixVL,
|
|
'value': pixels,
|
|
'startOffset': startOffset,
|
|
'endOffset': startOffset + pixVL
|
|
};
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.dicom = dwv.dicom || {};
|
|
|
|
/**
|
|
* DICOM tag dictionary.
|
|
* Generated using xml standard conversion
|
|
* from {@link https://github.com/ivmartel/dcmbench/tree/master/view/part06}
|
|
* with {@link http://medical.nema.org/medical/dicom/current/source/docbook/part06/part06.xml}
|
|
* Conversion changes:
|
|
* - (vr) "See Note" -> "NONE", "OB or OW" -> "ox", "US or SS" -> "xs"
|
|
* - added "GenericGroupLength" element to each group
|
|
* Local changes:
|
|
* - tag numbers with 'xx' were replaced with '00', 'xxx' with '001' and 'xxxx' with '0004'
|
|
*/
|
|
dwv.dicom.dictionary = {
|
|
'0x0000': {
|
|
'0x0000': ['UL', '1', 'GroupLength'],
|
|
'0x0001': ['UL', '1', 'CommandLengthToEnd'],
|
|
'0x0002': ['UI', '1', 'AffectedSOPClassUID'],
|
|
'0x0003': ['UI', '1', 'RequestedSOPClassUID'],
|
|
'0x0010': ['CS', '1', 'CommandRecognitionCode'],
|
|
'0x0100': ['US', '1', 'CommandField'],
|
|
'0x0110': ['US', '1', 'MessageID'],
|
|
'0x0120': ['US', '1', 'MessageIDBeingRespondedTo'],
|
|
'0x0200': ['AE', '1', 'Initiator'],
|
|
'0x0300': ['AE', '1', 'Receiver'],
|
|
'0x0400': ['AE', '1', 'FindLocation'],
|
|
'0x0600': ['AE', '1', 'MoveDestination'],
|
|
'0x0700': ['US', '1', 'Priority'],
|
|
'0x0800': ['US', '1', 'DataSetType'],
|
|
'0x0850': ['US', '1', 'NumberOfMatches'],
|
|
'0x0860': ['US', '1', 'ResponseSequenceNumber'],
|
|
'0x0900': ['US', '1', 'Status'],
|
|
'0x0901': ['AT', '1-n', 'OffendingElement'],
|
|
'0x0902': ['LO', '1', 'ErrorComment'],
|
|
'0x0903': ['US', '1', 'ErrorID'],
|
|
'0x0904': ['OT', '1-n', 'ErrorInformation'],
|
|
'0x1000': ['UI', '1', 'AffectedSOPInstanceUID'],
|
|
'0x1001': ['UI', '1', 'RequestedSOPInstanceUID'],
|
|
'0x1002': ['US', '1', 'EventTypeID'],
|
|
'0x1003': ['OT', '1-n', 'EventInformation'],
|
|
'0x1005': ['AT', '1-n', 'AttributeIdentifierList'],
|
|
'0x1007': ['AT', '1-n', 'ModificationList'],
|
|
'0x1008': ['US', '1', 'ActionTypeID'],
|
|
'0x1009': ['OT', '1-n', 'ActionInformation'],
|
|
'0x1013': ['UI', '1-n', 'SuccessfulSOPInstanceUIDList'],
|
|
'0x1014': ['UI', '1-n', 'FailedSOPInstanceUIDList'],
|
|
'0x1015': ['UI', '1-n', 'WarningSOPInstanceUIDList'],
|
|
'0x1020': ['US', '1', 'NumberOfRemainingSuboperations'],
|
|
'0x1021': ['US', '1', 'NumberOfCompletedSuboperations'],
|
|
'0x1022': ['US', '1', 'NumberOfFailedSuboperations'],
|
|
'0x1023': ['US', '1', 'NumberOfWarningSuboperations'],
|
|
'0x1030': ['AE', '1', 'MoveOriginatorApplicationEntityTitle'],
|
|
'0x1031': ['US', '1', 'MoveOriginatorMessageID'],
|
|
'0x4000': ['AT', '1', 'DialogReceiver'],
|
|
'0x4010': ['AT', '1', 'TerminalType'],
|
|
'0x5010': ['SH', '1', 'MessageSetID'],
|
|
'0x5020': ['SH', '1', 'EndMessageSet'],
|
|
'0x5110': ['AT', '1', 'DisplayFormat'],
|
|
'0x5120': ['AT', '1', 'PagePositionID'],
|
|
'0x5130': ['CS', '1', 'TextFormatID'],
|
|
'0x5140': ['CS', '1', 'NormalReverse'],
|
|
'0x5150': ['CS', '1', 'AddGrayScale'],
|
|
'0x5160': ['CS', '1', 'Borders'],
|
|
'0x5170': ['IS', '1', 'Copies'],
|
|
'0x5180': ['CS', '1', 'OldMagnificationType'],
|
|
'0x5190': ['CS', '1', 'Erase'],
|
|
'0x51A0': ['CS', '1', 'Print'],
|
|
'0x51B0': ['US', '1-n', 'Overlays']
|
|
},
|
|
'0x0002': {
|
|
'0x0000': ['UL', '1', 'FileMetaInformationGroupLength'],
|
|
'0x0001': ['OB', '1', 'FileMetaInformationVersion'],
|
|
'0x0002': ['UI', '1', 'MediaStorageSOPClassUID'],
|
|
'0x0003': ['UI', '1', 'MediaStorageSOPInstanceUID'],
|
|
'0x0010': ['UI', '1', 'TransferSyntaxUID'],
|
|
'0x0012': ['UI', '1', 'ImplementationClassUID'],
|
|
'0x0013': ['SH', '1', 'ImplementationVersionName'],
|
|
'0x0016': ['AE', '1', 'SourceApplicationEntityTitle'],
|
|
'0x0017': ['AE', '1', 'SendingApplicationEntityTitle'],
|
|
'0x0018': ['AE', '1', 'ReceivingApplicationEntityTitle'],
|
|
'0x0100': ['UI', '1', 'PrivateInformationCreatorUID'],
|
|
'0x0102': ['OB', '1', 'PrivateInformation']
|
|
},
|
|
'0x0004': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x1130': ['CS', '1', 'FileSetID'],
|
|
'0x1141': ['CS', '1-8', 'FileSetDescriptorFileID'],
|
|
'0x1142': ['CS', '1', 'SpecificCharacterSetOfFileSetDescriptorFile'],
|
|
'0x1200': ['UL', '1', 'OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity'],
|
|
'0x1202': ['UL', '1', 'OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity'],
|
|
'0x1212': ['US', '1', 'FileSetConsistencyFlag'],
|
|
'0x1220': ['SQ', '1', 'DirectoryRecordSequence'],
|
|
'0x1400': ['UL', '1', 'OffsetOfTheNextDirectoryRecord'],
|
|
'0x1410': ['US', '1', 'RecordInUseFlag'],
|
|
'0x1420': ['UL', '1', 'OffsetOfReferencedLowerLevelDirectoryEntity'],
|
|
'0x1430': ['CS', '1', 'DirectoryRecordType'],
|
|
'0x1432': ['UI', '1', 'PrivateRecordUID'],
|
|
'0x1500': ['CS', '1-8', 'ReferencedFileID'],
|
|
'0x1504': ['UL', '1', 'MRDRDirectoryRecordOffset'],
|
|
'0x1510': ['UI', '1', 'ReferencedSOPClassUIDInFile'],
|
|
'0x1511': ['UI', '1', 'ReferencedSOPInstanceUIDInFile'],
|
|
'0x1512': ['UI', '1', 'ReferencedTransferSyntaxUIDInFile'],
|
|
'0x151A': ['UI', '1-n', 'ReferencedRelatedGeneralSOPClassUIDInFile'],
|
|
'0x1600': ['UL', '1', 'NumberOfReferences']
|
|
},
|
|
'0x0008': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['UL', '1', 'LengthToEnd'],
|
|
'0x0005': ['CS', '1-n', 'SpecificCharacterSet'],
|
|
'0x0006': ['SQ', '1', 'LanguageCodeSequence'],
|
|
'0x0008': ['CS', '2-n', 'ImageType'],
|
|
'0x0010': ['SH', '1', 'RecognitionCode'],
|
|
'0x0012': ['DA', '1', 'InstanceCreationDate'],
|
|
'0x0013': ['TM', '1', 'InstanceCreationTime'],
|
|
'0x0014': ['UI', '1', 'InstanceCreatorUID'],
|
|
'0x0015': ['DT', '1', 'InstanceCoercionDateTime'],
|
|
'0x0016': ['UI', '1', 'SOPClassUID'],
|
|
'0x0018': ['UI', '1', 'SOPInstanceUID'],
|
|
'0x001A': ['UI', '1-n', 'RelatedGeneralSOPClassUID'],
|
|
'0x001B': ['UI', '1', 'OriginalSpecializedSOPClassUID'],
|
|
'0x0020': ['DA', '1', 'StudyDate'],
|
|
'0x0021': ['DA', '1', 'SeriesDate'],
|
|
'0x0022': ['DA', '1', 'AcquisitionDate'],
|
|
'0x0023': ['DA', '1', 'ContentDate'],
|
|
'0x0024': ['DA', '1', 'OverlayDate'],
|
|
'0x0025': ['DA', '1', 'CurveDate'],
|
|
'0x002A': ['DT', '1', 'AcquisitionDateTime'],
|
|
'0x0030': ['TM', '1', 'StudyTime'],
|
|
'0x0031': ['TM', '1', 'SeriesTime'],
|
|
'0x0032': ['TM', '1', 'AcquisitionTime'],
|
|
'0x0033': ['TM', '1', 'ContentTime'],
|
|
'0x0034': ['TM', '1', 'OverlayTime'],
|
|
'0x0035': ['TM', '1', 'CurveTime'],
|
|
'0x0040': ['US', '1', 'DataSetType'],
|
|
'0x0041': ['LO', '1', 'DataSetSubtype'],
|
|
'0x0042': ['CS', '1', 'NuclearMedicineSeriesType'],
|
|
'0x0050': ['SH', '1', 'AccessionNumber'],
|
|
'0x0051': ['SQ', '1', 'IssuerOfAccessionNumberSequence'],
|
|
'0x0052': ['CS', '1', 'QueryRetrieveLevel'],
|
|
'0x0053': ['CS', '1', 'QueryRetrieveView'],
|
|
'0x0054': ['AE', '1-n', 'RetrieveAETitle'],
|
|
'0x0056': ['CS', '1', 'InstanceAvailability'],
|
|
'0x0058': ['UI', '1-n', 'FailedSOPInstanceUIDList'],
|
|
'0x0060': ['CS', '1', 'Modality'],
|
|
'0x0061': ['CS', '1-n', 'ModalitiesInStudy'],
|
|
'0x0062': ['UI', '1-n', 'SOPClassesInStudy'],
|
|
'0x0064': ['CS', '1', 'ConversionType'],
|
|
'0x0068': ['CS', '1', 'PresentationIntentType'],
|
|
'0x0070': ['LO', '1', 'Manufacturer'],
|
|
'0x0080': ['LO', '1', 'InstitutionName'],
|
|
'0x0081': ['ST', '1', 'InstitutionAddress'],
|
|
'0x0082': ['SQ', '1', 'InstitutionCodeSequence'],
|
|
'0x0090': ['PN', '1', 'ReferringPhysicianName'],
|
|
'0x0092': ['ST', '1', 'ReferringPhysicianAddress'],
|
|
'0x0094': ['SH', '1-n', 'ReferringPhysicianTelephoneNumbers'],
|
|
'0x0096': ['SQ', '1', 'ReferringPhysicianIdentificationSequence'],
|
|
'0x009C': ['PN', '1-n', 'ConsultingPhysicianName'],
|
|
'0x009D': ['SQ', '1', 'ConsultingPhysicianIdentificationSequence'],
|
|
'0x0100': ['SH', '1', 'CodeValue'],
|
|
'0x0101': ['LO', '1', 'ExtendedCodeValue'],
|
|
'0x0102': ['SH', '1', 'CodingSchemeDesignator'],
|
|
'0x0103': ['SH', '1', 'CodingSchemeVersion'],
|
|
'0x0104': ['LO', '1', 'CodeMeaning'],
|
|
'0x0105': ['CS', '1', 'MappingResource'],
|
|
'0x0106': ['DT', '1', 'ContextGroupVersion'],
|
|
'0x0107': ['DT', '1', 'ContextGroupLocalVersion'],
|
|
'0x0108': ['LT', '1', 'ExtendedCodeMeaning'],
|
|
'0x010B': ['CS', '1', 'ContextGroupExtensionFlag'],
|
|
'0x010C': ['UI', '1', 'CodingSchemeUID'],
|
|
'0x010D': ['UI', '1', 'ContextGroupExtensionCreatorUID'],
|
|
'0x010F': ['CS', '1', 'ContextIdentifier'],
|
|
'0x0110': ['SQ', '1', 'CodingSchemeIdentificationSequence'],
|
|
'0x0112': ['LO', '1', 'CodingSchemeRegistry'],
|
|
'0x0114': ['ST', '1', 'CodingSchemeExternalID'],
|
|
'0x0115': ['ST', '1', 'CodingSchemeName'],
|
|
'0x0116': ['ST', '1', 'CodingSchemeResponsibleOrganization'],
|
|
'0x0117': ['UI', '1', 'ContextUID'],
|
|
'0x0118': ['UI', '1', 'MappingResourceUID'],
|
|
'0x0119': ['UC', '1', 'LongCodeValue'],
|
|
'0x0120': ['UR', '1', 'URNCodeValue'],
|
|
'0x0121': ['SQ', '1', 'EquivalentCodeSequence'],
|
|
'0x0201': ['SH', '1', 'TimezoneOffsetFromUTC'],
|
|
'0x0300': ['SQ', '1', 'PrivateDataElementCharacteristicsSequence'],
|
|
'0x0301': ['US', '1', 'PrivateGroupReference'],
|
|
'0x0302': ['LO', '1', 'PrivateCreatorReference'],
|
|
'0x0303': ['CS', '1', 'BlockIdentifyingInformationStatus'],
|
|
'0x0304': ['US', '1-n', 'NonidentifyingPrivateElements'],
|
|
'0x0306': ['US', '1-n', 'IdentifyingPrivateElements'],
|
|
'0x0305': ['SQ', '1', 'DeidentificationActionSequence'],
|
|
'0x0307': ['CS', '1', 'DeidentificationAction'],
|
|
'0x1000': ['AE', '1', 'NetworkID'],
|
|
'0x1010': ['SH', '1', 'StationName'],
|
|
'0x1030': ['LO', '1', 'StudyDescription'],
|
|
'0x1032': ['SQ', '1', 'ProcedureCodeSequence'],
|
|
'0x103E': ['LO', '1', 'SeriesDescription'],
|
|
'0x103F': ['SQ', '1', 'SeriesDescriptionCodeSequence'],
|
|
'0x1040': ['LO', '1', 'InstitutionalDepartmentName'],
|
|
'0x1048': ['PN', '1-n', 'PhysiciansOfRecord'],
|
|
'0x1049': ['SQ', '1', 'PhysiciansOfRecordIdentificationSequence'],
|
|
'0x1050': ['PN', '1-n', 'PerformingPhysicianName'],
|
|
'0x1052': ['SQ', '1', 'PerformingPhysicianIdentificationSequence'],
|
|
'0x1060': ['PN', '1-n', 'NameOfPhysiciansReadingStudy'],
|
|
'0x1062': ['SQ', '1', 'PhysiciansReadingStudyIdentificationSequence'],
|
|
'0x1070': ['PN', '1-n', 'OperatorsName'],
|
|
'0x1072': ['SQ', '1', 'OperatorIdentificationSequence'],
|
|
'0x1080': ['LO', '1-n', 'AdmittingDiagnosesDescription'],
|
|
'0x1084': ['SQ', '1', 'AdmittingDiagnosesCodeSequence'],
|
|
'0x1090': ['LO', '1', 'ManufacturerModelName'],
|
|
'0x1100': ['SQ', '1', 'ReferencedResultsSequence'],
|
|
'0x1110': ['SQ', '1', 'ReferencedStudySequence'],
|
|
'0x1111': ['SQ', '1', 'ReferencedPerformedProcedureStepSequence'],
|
|
'0x1115': ['SQ', '1', 'ReferencedSeriesSequence'],
|
|
'0x1120': ['SQ', '1', 'ReferencedPatientSequence'],
|
|
'0x1125': ['SQ', '1', 'ReferencedVisitSequence'],
|
|
'0x1130': ['SQ', '1', 'ReferencedOverlaySequence'],
|
|
'0x1134': ['SQ', '1', 'ReferencedStereometricInstanceSequence'],
|
|
'0x113A': ['SQ', '1', 'ReferencedWaveformSequence'],
|
|
'0x1140': ['SQ', '1', 'ReferencedImageSequence'],
|
|
'0x1145': ['SQ', '1', 'ReferencedCurveSequence'],
|
|
'0x114A': ['SQ', '1', 'ReferencedInstanceSequence'],
|
|
'0x114B': ['SQ', '1', 'ReferencedRealWorldValueMappingInstanceSequence'],
|
|
'0x1150': ['UI', '1', 'ReferencedSOPClassUID'],
|
|
'0x1155': ['UI', '1', 'ReferencedSOPInstanceUID'],
|
|
'0x115A': ['UI', '1-n', 'SOPClassesSupported'],
|
|
'0x1160': ['IS', '1-n', 'ReferencedFrameNumber'],
|
|
'0x1161': ['UL', '1-n', 'SimpleFrameList'],
|
|
'0x1162': ['UL', '3-3n', 'CalculatedFrameList'],
|
|
'0x1163': ['FD', '2', 'TimeRange'],
|
|
'0x1164': ['SQ', '1', 'FrameExtractionSequence'],
|
|
'0x1167': ['UI', '1', 'MultiFrameSourceSOPInstanceUID'],
|
|
'0x1190': ['UR', '1', 'RetrieveURL'],
|
|
'0x1195': ['UI', '1', 'TransactionUID'],
|
|
'0x1196': ['US', '1', 'WarningReason'],
|
|
'0x1197': ['US', '1', 'FailureReason'],
|
|
'0x1198': ['SQ', '1', 'FailedSOPSequence'],
|
|
'0x1199': ['SQ', '1', 'ReferencedSOPSequence'],
|
|
'0x1200': ['SQ', '1', 'StudiesContainingOtherReferencedInstancesSequence'],
|
|
'0x1250': ['SQ', '1', 'RelatedSeriesSequence'],
|
|
'0x2110': ['CS', '1', 'LossyImageCompressionRetired'],
|
|
'0x2111': ['ST', '1', 'DerivationDescription'],
|
|
'0x2112': ['SQ', '1', 'SourceImageSequence'],
|
|
'0x2120': ['SH', '1', 'StageName'],
|
|
'0x2122': ['IS', '1', 'StageNumber'],
|
|
'0x2124': ['IS', '1', 'NumberOfStages'],
|
|
'0x2127': ['SH', '1', 'ViewName'],
|
|
'0x2128': ['IS', '1', 'ViewNumber'],
|
|
'0x2129': ['IS', '1', 'NumberOfEventTimers'],
|
|
'0x212A': ['IS', '1', 'NumberOfViewsInStage'],
|
|
'0x2130': ['DS', '1-n', 'EventElapsedTimes'],
|
|
'0x2132': ['LO', '1-n', 'EventTimerNames'],
|
|
'0x2133': ['SQ', '1', 'EventTimerSequence'],
|
|
'0x2134': ['FD', '1', 'EventTimeOffset'],
|
|
'0x2135': ['SQ', '1', 'EventCodeSequence'],
|
|
'0x2142': ['IS', '1', 'StartTrim'],
|
|
'0x2143': ['IS', '1', 'StopTrim'],
|
|
'0x2144': ['IS', '1', 'RecommendedDisplayFrameRate'],
|
|
'0x2200': ['CS', '1', 'TransducerPosition'],
|
|
'0x2204': ['CS', '1', 'TransducerOrientation'],
|
|
'0x2208': ['CS', '1', 'AnatomicStructure'],
|
|
'0x2218': ['SQ', '1', 'AnatomicRegionSequence'],
|
|
'0x2220': ['SQ', '1', 'AnatomicRegionModifierSequence'],
|
|
'0x2228': ['SQ', '1', 'PrimaryAnatomicStructureSequence'],
|
|
'0x2229': ['SQ', '1', 'AnatomicStructureSpaceOrRegionSequence'],
|
|
'0x2230': ['SQ', '1', 'PrimaryAnatomicStructureModifierSequence'],
|
|
'0x2240': ['SQ', '1', 'TransducerPositionSequence'],
|
|
'0x2242': ['SQ', '1', 'TransducerPositionModifierSequence'],
|
|
'0x2244': ['SQ', '1', 'TransducerOrientationSequence'],
|
|
'0x2246': ['SQ', '1', 'TransducerOrientationModifierSequence'],
|
|
'0x2251': ['SQ', '1', 'AnatomicStructureSpaceOrRegionCodeSequenceTrial'],
|
|
'0x2253': ['SQ', '1', 'AnatomicPortalOfEntranceCodeSequenceTrial'],
|
|
'0x2255': ['SQ', '1', 'AnatomicApproachDirectionCodeSequenceTrial'],
|
|
'0x2256': ['ST', '1', 'AnatomicPerspectiveDescriptionTrial'],
|
|
'0x2257': ['SQ', '1', 'AnatomicPerspectiveCodeSequenceTrial'],
|
|
'0x2258': ['ST', '1', 'AnatomicLocationOfExaminingInstrumentDescriptionTrial'],
|
|
'0x2259': ['SQ', '1', 'AnatomicLocationOfExaminingInstrumentCodeSequenceTrial'],
|
|
'0x225A': ['SQ', '1', 'AnatomicStructureSpaceOrRegionModifierCodeSequenceTrial'],
|
|
'0x225C': ['SQ', '1', 'OnAxisBackgroundAnatomicStructureCodeSequenceTrial'],
|
|
'0x3001': ['SQ', '1', 'AlternateRepresentationSequence'],
|
|
'0x3010': ['UI', '1-n', 'IrradiationEventUID'],
|
|
'0x3011': ['SQ', '1', 'SourceIrradiationEventSequence'],
|
|
'0x3012': ['UI', '1', 'RadiopharmaceuticalAdministrationEventUID'],
|
|
'0x4000': ['LT', '1', 'IdentifyingComments'],
|
|
'0x9007': ['CS', '4', 'FrameType'],
|
|
'0x9092': ['SQ', '1', 'ReferencedImageEvidenceSequence'],
|
|
'0x9121': ['SQ', '1', 'ReferencedRawDataSequence'],
|
|
'0x9123': ['UI', '1', 'CreatorVersionUID'],
|
|
'0x9124': ['SQ', '1', 'DerivationImageSequence'],
|
|
'0x9154': ['SQ', '1', 'SourceImageEvidenceSequence'],
|
|
'0x9205': ['CS', '1', 'PixelPresentation'],
|
|
'0x9206': ['CS', '1', 'VolumetricProperties'],
|
|
'0x9207': ['CS', '1', 'VolumeBasedCalculationTechnique'],
|
|
'0x9208': ['CS', '1', 'ComplexImageComponent'],
|
|
'0x9209': ['CS', '1', 'AcquisitionContrast'],
|
|
'0x9215': ['SQ', '1', 'DerivationCodeSequence'],
|
|
'0x9237': ['SQ', '1', 'ReferencedPresentationStateSequence'],
|
|
'0x9410': ['SQ', '1', 'ReferencedOtherPlaneSequence'],
|
|
'0x9458': ['SQ', '1', 'FrameDisplaySequence'],
|
|
'0x9459': ['FL', '1', 'RecommendedDisplayFrameRateInFloat'],
|
|
'0x9460': ['CS', '1', 'SkipFrameRangeFlag']
|
|
},
|
|
'0x0010': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['PN', '1', 'PatientName'],
|
|
'0x0020': ['LO', '1', 'PatientID'],
|
|
'0x0021': ['LO', '1', 'IssuerOfPatientID'],
|
|
'0x0022': ['CS', '1', 'TypeOfPatientID'],
|
|
'0x0024': ['SQ', '1', 'IssuerOfPatientIDQualifiersSequence'],
|
|
'0x0030': ['DA', '1', 'PatientBirthDate'],
|
|
'0x0032': ['TM', '1', 'PatientBirthTime'],
|
|
'0x0040': ['CS', '1', 'PatientSex'],
|
|
'0x0050': ['SQ', '1', 'PatientInsurancePlanCodeSequence'],
|
|
'0x0101': ['SQ', '1', 'PatientPrimaryLanguageCodeSequence'],
|
|
'0x0102': ['SQ', '1', 'PatientPrimaryLanguageModifierCodeSequence'],
|
|
'0x0200': ['CS', '1', 'QualityControlSubject'],
|
|
'0x0201': ['SQ', '1', 'QualityControlSubjectTypeCodeSequence'],
|
|
'0x1000': ['LO', '1-n', 'OtherPatientIDs'],
|
|
'0x1001': ['PN', '1-n', 'OtherPatientNames'],
|
|
'0x1002': ['SQ', '1', 'OtherPatientIDsSequence'],
|
|
'0x1005': ['PN', '1', 'PatientBirthName'],
|
|
'0x1010': ['AS', '1', 'PatientAge'],
|
|
'0x1020': ['DS', '1', 'PatientSize'],
|
|
'0x1021': ['SQ', '1', 'PatientSizeCodeSequence'],
|
|
'0x1030': ['DS', '1', 'PatientWeight'],
|
|
'0x1040': ['LO', '1', 'PatientAddress'],
|
|
'0x1050': ['LO', '1-n', 'InsurancePlanIdentification'],
|
|
'0x1060': ['PN', '1', 'PatientMotherBirthName'],
|
|
'0x1080': ['LO', '1', 'MilitaryRank'],
|
|
'0x1081': ['LO', '1', 'BranchOfService'],
|
|
'0x1090': ['LO', '1', 'MedicalRecordLocator'],
|
|
'0x1100': ['SQ', '1', 'ReferencedPatientPhotoSequence'],
|
|
'0x2000': ['LO', '1-n', 'MedicalAlerts'],
|
|
'0x2110': ['LO', '1-n', 'Allergies'],
|
|
'0x2150': ['LO', '1', 'CountryOfResidence'],
|
|
'0x2152': ['LO', '1', 'RegionOfResidence'],
|
|
'0x2154': ['SH', '1-n', 'PatientTelephoneNumbers'],
|
|
'0x2155': ['LT', '1', 'PatientTelecomInformation'],
|
|
'0x2160': ['SH', '1', 'EthnicGroup'],
|
|
'0x2180': ['SH', '1', 'Occupation'],
|
|
'0x21A0': ['CS', '1', 'SmokingStatus'],
|
|
'0x21B0': ['LT', '1', 'AdditionalPatientHistory'],
|
|
'0x21C0': ['US', '1', 'PregnancyStatus'],
|
|
'0x21D0': ['DA', '1', 'LastMenstrualDate'],
|
|
'0x21F0': ['LO', '1', 'PatientReligiousPreference'],
|
|
'0x2201': ['LO', '1', 'PatientSpeciesDescription'],
|
|
'0x2202': ['SQ', '1', 'PatientSpeciesCodeSequence'],
|
|
'0x2203': ['CS', '1', 'PatientSexNeutered'],
|
|
'0x2210': ['CS', '1', 'AnatomicalOrientationType'],
|
|
'0x2292': ['LO', '1', 'PatientBreedDescription'],
|
|
'0x2293': ['SQ', '1', 'PatientBreedCodeSequence'],
|
|
'0x2294': ['SQ', '1', 'BreedRegistrationSequence'],
|
|
'0x2295': ['LO', '1', 'BreedRegistrationNumber'],
|
|
'0x2296': ['SQ', '1', 'BreedRegistryCodeSequence'],
|
|
'0x2297': ['PN', '1', 'ResponsiblePerson'],
|
|
'0x2298': ['CS', '1', 'ResponsiblePersonRole'],
|
|
'0x2299': ['LO', '1', 'ResponsibleOrganization'],
|
|
'0x4000': ['LT', '1', 'PatientComments'],
|
|
'0x9431': ['FL', '1', 'ExaminedBodyThickness']
|
|
},
|
|
'0x0012': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['LO', '1', 'ClinicalTrialSponsorName'],
|
|
'0x0020': ['LO', '1', 'ClinicalTrialProtocolID'],
|
|
'0x0021': ['LO', '1', 'ClinicalTrialProtocolName'],
|
|
'0x0030': ['LO', '1', 'ClinicalTrialSiteID'],
|
|
'0x0031': ['LO', '1', 'ClinicalTrialSiteName'],
|
|
'0x0040': ['LO', '1', 'ClinicalTrialSubjectID'],
|
|
'0x0042': ['LO', '1', 'ClinicalTrialSubjectReadingID'],
|
|
'0x0050': ['LO', '1', 'ClinicalTrialTimePointID'],
|
|
'0x0051': ['ST', '1', 'ClinicalTrialTimePointDescription'],
|
|
'0x0060': ['LO', '1', 'ClinicalTrialCoordinatingCenterName'],
|
|
'0x0062': ['CS', '1', 'PatientIdentityRemoved'],
|
|
'0x0063': ['LO', '1-n', 'DeidentificationMethod'],
|
|
'0x0064': ['SQ', '1', 'DeidentificationMethodCodeSequence'],
|
|
'0x0071': ['LO', '1', 'ClinicalTrialSeriesID'],
|
|
'0x0072': ['LO', '1', 'ClinicalTrialSeriesDescription'],
|
|
'0x0081': ['LO', '1', 'ClinicalTrialProtocolEthicsCommitteeName'],
|
|
'0x0082': ['LO', '1', 'ClinicalTrialProtocolEthicsCommitteeApprovalNumber'],
|
|
'0x0083': ['SQ', '1', 'ConsentForClinicalTrialUseSequence'],
|
|
'0x0084': ['CS', '1', 'DistributionType'],
|
|
'0x0085': ['CS', '1', 'ConsentForDistributionFlag']
|
|
},
|
|
'0x0014': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0023': ['ST', '1-n', 'CADFileFormat'],
|
|
'0x0024': ['ST', '1-n', 'ComponentReferenceSystem'],
|
|
'0x0025': ['ST', '1-n', 'ComponentManufacturingProcedure'],
|
|
'0x0028': ['ST', '1-n', 'ComponentManufacturer'],
|
|
'0x0030': ['DS', '1-n', 'MaterialThickness'],
|
|
'0x0032': ['DS', '1-n', 'MaterialPipeDiameter'],
|
|
'0x0034': ['DS', '1-n', 'MaterialIsolationDiameter'],
|
|
'0x0042': ['ST', '1-n', 'MaterialGrade'],
|
|
'0x0044': ['ST', '1-n', 'MaterialPropertiesDescription'],
|
|
'0x0045': ['ST', '1-n', 'MaterialPropertiesFileFormatRetired'],
|
|
'0x0046': ['LT', '1', 'MaterialNotes'],
|
|
'0x0050': ['CS', '1', 'ComponentShape'],
|
|
'0x0052': ['CS', '1', 'CurvatureType'],
|
|
'0x0054': ['DS', '1', 'OuterDiameter'],
|
|
'0x0056': ['DS', '1', 'InnerDiameter'],
|
|
'0x1010': ['ST', '1', 'ActualEnvironmentalConditions'],
|
|
'0x1020': ['DA', '1', 'ExpiryDate'],
|
|
'0x1040': ['ST', '1', 'EnvironmentalConditions'],
|
|
'0x2002': ['SQ', '1', 'EvaluatorSequence'],
|
|
'0x2004': ['IS', '1', 'EvaluatorNumber'],
|
|
'0x2006': ['PN', '1', 'EvaluatorName'],
|
|
'0x2008': ['IS', '1', 'EvaluationAttempt'],
|
|
'0x2012': ['SQ', '1', 'IndicationSequence'],
|
|
'0x2014': ['IS', '1', 'IndicationNumber'],
|
|
'0x2016': ['SH', '1', 'IndicationLabel'],
|
|
'0x2018': ['ST', '1', 'IndicationDescription'],
|
|
'0x201A': ['CS', '1-n', 'IndicationType'],
|
|
'0x201C': ['CS', '1', 'IndicationDisposition'],
|
|
'0x201E': ['SQ', '1', 'IndicationROISequence'],
|
|
'0x2030': ['SQ', '1', 'IndicationPhysicalPropertySequence'],
|
|
'0x2032': ['SH', '1', 'PropertyLabel'],
|
|
'0x2202': ['IS', '1', 'CoordinateSystemNumberOfAxes'],
|
|
'0x2204': ['SQ', '1', 'CoordinateSystemAxesSequence'],
|
|
'0x2206': ['ST', '1', 'CoordinateSystemAxisDescription'],
|
|
'0x2208': ['CS', '1', 'CoordinateSystemDataSetMapping'],
|
|
'0x220A': ['IS', '1', 'CoordinateSystemAxisNumber'],
|
|
'0x220C': ['CS', '1', 'CoordinateSystemAxisType'],
|
|
'0x220E': ['CS', '1', 'CoordinateSystemAxisUnits'],
|
|
'0x2210': ['OB', '1', 'CoordinateSystemAxisValues'],
|
|
'0x2220': ['SQ', '1', 'CoordinateSystemTransformSequence'],
|
|
'0x2222': ['ST', '1', 'TransformDescription'],
|
|
'0x2224': ['IS', '1', 'TransformNumberOfAxes'],
|
|
'0x2226': ['IS', '1-n', 'TransformOrderOfAxes'],
|
|
'0x2228': ['CS', '1', 'TransformedAxisUnits'],
|
|
'0x222A': ['DS', '1-n', 'CoordinateSystemTransformRotationAndScaleMatrix'],
|
|
'0x222C': ['DS', '1-n', 'CoordinateSystemTransformTranslationMatrix'],
|
|
'0x3011': ['DS', '1', 'InternalDetectorFrameTime'],
|
|
'0x3012': ['DS', '1', 'NumberOfFramesIntegrated'],
|
|
'0x3020': ['SQ', '1', 'DetectorTemperatureSequence'],
|
|
'0x3022': ['ST', '1', 'SensorName'],
|
|
'0x3024': ['DS', '1', 'HorizontalOffsetOfSensor'],
|
|
'0x3026': ['DS', '1', 'VerticalOffsetOfSensor'],
|
|
'0x3028': ['DS', '1', 'SensorTemperature'],
|
|
'0x3040': ['SQ', '1', 'DarkCurrentSequence'],
|
|
'0x3050': ['ox', '1', 'DarkCurrentCounts'],
|
|
'0x3060': ['SQ', '1', 'GainCorrectionReferenceSequence'],
|
|
'0x3070': ['ox', '1', 'AirCounts'],
|
|
'0x3071': ['DS', '1', 'KVUsedInGainCalibration'],
|
|
'0x3072': ['DS', '1', 'MAUsedInGainCalibration'],
|
|
'0x3073': ['DS', '1', 'NumberOfFramesUsedForIntegration'],
|
|
'0x3074': ['LO', '1', 'FilterMaterialUsedInGainCalibration'],
|
|
'0x3075': ['DS', '1', 'FilterThicknessUsedInGainCalibration'],
|
|
'0x3076': ['DA', '1', 'DateOfGainCalibration'],
|
|
'0x3077': ['TM', '1', 'TimeOfGainCalibration'],
|
|
'0x3080': ['OB', '1', 'BadPixelImage'],
|
|
'0x3099': ['LT', '1', 'CalibrationNotes'],
|
|
'0x4002': ['SQ', '1', 'PulserEquipmentSequence'],
|
|
'0x4004': ['CS', '1', 'PulserType'],
|
|
'0x4006': ['LT', '1', 'PulserNotes'],
|
|
'0x4008': ['SQ', '1', 'ReceiverEquipmentSequence'],
|
|
'0x400A': ['CS', '1', 'AmplifierType'],
|
|
'0x400C': ['LT', '1', 'ReceiverNotes'],
|
|
'0x400E': ['SQ', '1', 'PreAmplifierEquipmentSequence'],
|
|
'0x400F': ['LT', '1', 'PreAmplifierNotes'],
|
|
'0x4010': ['SQ', '1', 'TransmitTransducerSequence'],
|
|
'0x4011': ['SQ', '1', 'ReceiveTransducerSequence'],
|
|
'0x4012': ['US', '1', 'NumberOfElements'],
|
|
'0x4013': ['CS', '1', 'ElementShape'],
|
|
'0x4014': ['DS', '1', 'ElementDimensionA'],
|
|
'0x4015': ['DS', '1', 'ElementDimensionB'],
|
|
'0x4016': ['DS', '1', 'ElementPitchA'],
|
|
'0x4017': ['DS', '1', 'MeasuredBeamDimensionA'],
|
|
'0x4018': ['DS', '1', 'MeasuredBeamDimensionB'],
|
|
'0x4019': ['DS', '1', 'LocationOfMeasuredBeamDiameter'],
|
|
'0x401A': ['DS', '1', 'NominalFrequency'],
|
|
'0x401B': ['DS', '1', 'MeasuredCenterFrequency'],
|
|
'0x401C': ['DS', '1', 'MeasuredBandwidth'],
|
|
'0x401D': ['DS', '1', 'ElementPitchB'],
|
|
'0x4020': ['SQ', '1', 'PulserSettingsSequence'],
|
|
'0x4022': ['DS', '1', 'PulseWidth'],
|
|
'0x4024': ['DS', '1', 'ExcitationFrequency'],
|
|
'0x4026': ['CS', '1', 'ModulationType'],
|
|
'0x4028': ['DS', '1', 'Damping'],
|
|
'0x4030': ['SQ', '1', 'ReceiverSettingsSequence'],
|
|
'0x4031': ['DS', '1', 'AcquiredSoundpathLength'],
|
|
'0x4032': ['CS', '1', 'AcquisitionCompressionType'],
|
|
'0x4033': ['IS', '1', 'AcquisitionSampleSize'],
|
|
'0x4034': ['DS', '1', 'RectifierSmoothing'],
|
|
'0x4035': ['SQ', '1', 'DACSequence'],
|
|
'0x4036': ['CS', '1', 'DACType'],
|
|
'0x4038': ['DS', '1-n', 'DACGainPoints'],
|
|
'0x403A': ['DS', '1-n', 'DACTimePoints'],
|
|
'0x403C': ['DS', '1-n', 'DACAmplitude'],
|
|
'0x4040': ['SQ', '1', 'PreAmplifierSettingsSequence'],
|
|
'0x4050': ['SQ', '1', 'TransmitTransducerSettingsSequence'],
|
|
'0x4051': ['SQ', '1', 'ReceiveTransducerSettingsSequence'],
|
|
'0x4052': ['DS', '1', 'IncidentAngle'],
|
|
'0x4054': ['ST', '1', 'CouplingTechnique'],
|
|
'0x4056': ['ST', '1', 'CouplingMedium'],
|
|
'0x4057': ['DS', '1', 'CouplingVelocity'],
|
|
'0x4058': ['DS', '1', 'ProbeCenterLocationX'],
|
|
'0x4059': ['DS', '1', 'ProbeCenterLocationZ'],
|
|
'0x405A': ['DS', '1', 'SoundPathLength'],
|
|
'0x405C': ['ST', '1', 'DelayLawIdentifier'],
|
|
'0x4060': ['SQ', '1', 'GateSettingsSequence'],
|
|
'0x4062': ['DS', '1', 'GateThreshold'],
|
|
'0x4064': ['DS', '1', 'VelocityOfSound'],
|
|
'0x4070': ['SQ', '1', 'CalibrationSettingsSequence'],
|
|
'0x4072': ['ST', '1', 'CalibrationProcedure'],
|
|
'0x4074': ['SH', '1', 'ProcedureVersion'],
|
|
'0x4076': ['DA', '1', 'ProcedureCreationDate'],
|
|
'0x4078': ['DA', '1', 'ProcedureExpirationDate'],
|
|
'0x407A': ['DA', '1', 'ProcedureLastModifiedDate'],
|
|
'0x407C': ['TM', '1-n', 'CalibrationTime'],
|
|
'0x407E': ['DA', '1-n', 'CalibrationDate'],
|
|
'0x4080': ['SQ', '1', 'ProbeDriveEquipmentSequence'],
|
|
'0x4081': ['CS', '1', 'DriveType'],
|
|
'0x4082': ['LT', '1', 'ProbeDriveNotes'],
|
|
'0x4083': ['SQ', '1', 'DriveProbeSequence'],
|
|
'0x4084': ['DS', '1', 'ProbeInductance'],
|
|
'0x4085': ['DS', '1', 'ProbeResistance'],
|
|
'0x4086': ['SQ', '1', 'ReceiveProbeSequence'],
|
|
'0x4087': ['SQ', '1', 'ProbeDriveSettingsSequence'],
|
|
'0x4088': ['DS', '1', 'BridgeResistors'],
|
|
'0x4089': ['DS', '1', 'ProbeOrientationAngle'],
|
|
'0x408B': ['DS', '1', 'UserSelectedGainY'],
|
|
'0x408C': ['DS', '1', 'UserSelectedPhase'],
|
|
'0x408D': ['DS', '1', 'UserSelectedOffsetX'],
|
|
'0x408E': ['DS', '1', 'UserSelectedOffsetY'],
|
|
'0x4091': ['SQ', '1', 'ChannelSettingsSequence'],
|
|
'0x4092': ['DS', '1', 'ChannelThreshold'],
|
|
'0x409A': ['SQ', '1', 'ScannerSettingsSequence'],
|
|
'0x409B': ['ST', '1', 'ScanProcedure'],
|
|
'0x409C': ['DS', '1', 'TranslationRateX'],
|
|
'0x409D': ['DS', '1', 'TranslationRateY'],
|
|
'0x409F': ['DS', '1', 'ChannelOverlap'],
|
|
'0x40A0': ['LO', '1', 'ImageQualityIndicatorType'],
|
|
'0x40A1': ['LO', '1', 'ImageQualityIndicatorMaterial'],
|
|
'0x40A2': ['LO', '1', 'ImageQualityIndicatorSize'],
|
|
'0x5002': ['IS', '1', 'LINACEnergy'],
|
|
'0x5004': ['IS', '1', 'LINACOutput'],
|
|
'0x5100': ['US', '1', 'ActiveAperture'],
|
|
'0x5101': ['DS', '1', 'TotalAperture'],
|
|
'0x5102': ['DS', '1', 'ApertureElevation'],
|
|
'0x5103': ['DS', '1', 'MainLobeAngle'],
|
|
'0x5104': ['DS', '1', 'MainRoofAngle'],
|
|
'0x5105': ['CS', '1', 'ConnectorType'],
|
|
'0x5106': ['SH', '1', 'WedgeModelNumber'],
|
|
'0x5107': ['DS', '1', 'WedgeAngleFloat'],
|
|
'0x5108': ['DS', '1', 'WedgeRoofAngle'],
|
|
'0x5109': ['CS', '1', 'WedgeElement1Position'],
|
|
'0x510A': ['DS', '1', 'WedgeMaterialVelocity'],
|
|
'0x510B': ['SH', '1', 'WedgeMaterial'],
|
|
'0x510C': ['DS', '1', 'WedgeOffsetZ'],
|
|
'0x510D': ['DS', '1', 'WedgeOriginOffsetX'],
|
|
'0x510E': ['DS', '1', 'WedgeTimeDelay'],
|
|
'0x510F': ['SH', '1', 'WedgeName'],
|
|
'0x5110': ['SH', '1', 'WedgeManufacturerName'],
|
|
'0x5111': ['LO', '1', 'WedgeDescription'],
|
|
'0x5112': ['DS', '1', 'NominalBeamAngle'],
|
|
'0x5113': ['DS', '1', 'WedgeOffsetX'],
|
|
'0x5114': ['DS', '1', 'WedgeOffsetY'],
|
|
'0x5115': ['DS', '1', 'WedgeTotalLength'],
|
|
'0x5116': ['DS', '1', 'WedgeInContactLength'],
|
|
'0x5117': ['DS', '1', 'WedgeFrontGap'],
|
|
'0x5118': ['DS', '1', 'WedgeTotalHeight'],
|
|
'0x5119': ['DS', '1', 'WedgeFrontHeight'],
|
|
'0x511A': ['DS', '1', 'WedgeRearHeight'],
|
|
'0x511B': ['DS', '1', 'WedgeTotalWidth'],
|
|
'0x511C': ['DS', '1', 'WedgeInContactWidth'],
|
|
'0x511D': ['DS', '1', 'WedgeChamferHeight'],
|
|
'0x511E': ['CS', '1', 'WedgeCurve'],
|
|
'0x511F': ['DS', '1', 'RadiusAlongWedge']
|
|
},
|
|
'0x0018': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['LO', '1', 'ContrastBolusAgent'],
|
|
'0x0012': ['SQ', '1', 'ContrastBolusAgentSequence'],
|
|
'0x0013': ['FL', '1', 'ContrastBolusT1Relaxivity'],
|
|
'0x0014': ['SQ', '1', 'ContrastBolusAdministrationRouteSequence'],
|
|
'0x0015': ['CS', '1', 'BodyPartExamined'],
|
|
'0x0020': ['CS', '1-n', 'ScanningSequence'],
|
|
'0x0021': ['CS', '1-n', 'SequenceVariant'],
|
|
'0x0022': ['CS', '1-n', 'ScanOptions'],
|
|
'0x0023': ['CS', '1', 'MRAcquisitionType'],
|
|
'0x0024': ['SH', '1', 'SequenceName'],
|
|
'0x0025': ['CS', '1', 'AngioFlag'],
|
|
'0x0026': ['SQ', '1', 'InterventionDrugInformationSequence'],
|
|
'0x0027': ['TM', '1', 'InterventionDrugStopTime'],
|
|
'0x0028': ['DS', '1', 'InterventionDrugDose'],
|
|
'0x0029': ['SQ', '1', 'InterventionDrugCodeSequence'],
|
|
'0x002A': ['SQ', '1', 'AdditionalDrugSequence'],
|
|
'0x0030': ['LO', '1-n', 'Radionuclide'],
|
|
'0x0031': ['LO', '1', 'Radiopharmaceutical'],
|
|
'0x0032': ['DS', '1', 'EnergyWindowCenterline'],
|
|
'0x0033': ['DS', '1-n', 'EnergyWindowTotalWidth'],
|
|
'0x0034': ['LO', '1', 'InterventionDrugName'],
|
|
'0x0035': ['TM', '1', 'InterventionDrugStartTime'],
|
|
'0x0036': ['SQ', '1', 'InterventionSequence'],
|
|
'0x0037': ['CS', '1', 'TherapyType'],
|
|
'0x0038': ['CS', '1', 'InterventionStatus'],
|
|
'0x0039': ['CS', '1', 'TherapyDescription'],
|
|
'0x003A': ['ST', '1', 'InterventionDescription'],
|
|
'0x0040': ['IS', '1', 'CineRate'],
|
|
'0x0042': ['CS', '1', 'InitialCineRunState'],
|
|
'0x0050': ['DS', '1', 'SliceThickness'],
|
|
'0x0060': ['DS', '1', 'KVP'],
|
|
'0x0070': ['IS', '1', 'CountsAccumulated'],
|
|
'0x0071': ['CS', '1', 'AcquisitionTerminationCondition'],
|
|
'0x0072': ['DS', '1', 'EffectiveDuration'],
|
|
'0x0073': ['CS', '1', 'AcquisitionStartCondition'],
|
|
'0x0074': ['IS', '1', 'AcquisitionStartConditionData'],
|
|
'0x0075': ['IS', '1', 'AcquisitionTerminationConditionData'],
|
|
'0x0080': ['DS', '1', 'RepetitionTime'],
|
|
'0x0081': ['DS', '1', 'EchoTime'],
|
|
'0x0082': ['DS', '1', 'InversionTime'],
|
|
'0x0083': ['DS', '1', 'NumberOfAverages'],
|
|
'0x0084': ['DS', '1', 'ImagingFrequency'],
|
|
'0x0085': ['SH', '1', 'ImagedNucleus'],
|
|
'0x0086': ['IS', '1-n', 'EchoNumbers'],
|
|
'0x0087': ['DS', '1', 'MagneticFieldStrength'],
|
|
'0x0088': ['DS', '1', 'SpacingBetweenSlices'],
|
|
'0x0089': ['IS', '1', 'NumberOfPhaseEncodingSteps'],
|
|
'0x0090': ['DS', '1', 'DataCollectionDiameter'],
|
|
'0x0091': ['IS', '1', 'EchoTrainLength'],
|
|
'0x0093': ['DS', '1', 'PercentSampling'],
|
|
'0x0094': ['DS', '1', 'PercentPhaseFieldOfView'],
|
|
'0x0095': ['DS', '1', 'PixelBandwidth'],
|
|
'0x1000': ['LO', '1', 'DeviceSerialNumber'],
|
|
'0x1002': ['UI', '1', 'DeviceUID'],
|
|
'0x1003': ['LO', '1', 'DeviceID'],
|
|
'0x1004': ['LO', '1', 'PlateID'],
|
|
'0x1005': ['LO', '1', 'GeneratorID'],
|
|
'0x1006': ['LO', '1', 'GridID'],
|
|
'0x1007': ['LO', '1', 'CassetteID'],
|
|
'0x1008': ['LO', '1', 'GantryID'],
|
|
'0x1010': ['LO', '1', 'SecondaryCaptureDeviceID'],
|
|
'0x1011': ['LO', '1', 'HardcopyCreationDeviceID'],
|
|
'0x1012': ['DA', '1', 'DateOfSecondaryCapture'],
|
|
'0x1014': ['TM', '1', 'TimeOfSecondaryCapture'],
|
|
'0x1016': ['LO', '1', 'SecondaryCaptureDeviceManufacturer'],
|
|
'0x1017': ['LO', '1', 'HardcopyDeviceManufacturer'],
|
|
'0x1018': ['LO', '1', 'SecondaryCaptureDeviceManufacturerModelName'],
|
|
'0x1019': ['LO', '1-n', 'SecondaryCaptureDeviceSoftwareVersions'],
|
|
'0x101A': ['LO', '1-n', 'HardcopyDeviceSoftwareVersion'],
|
|
'0x101B': ['LO', '1', 'HardcopyDeviceManufacturerModelName'],
|
|
'0x1020': ['LO', '1-n', 'SoftwareVersions'],
|
|
'0x1022': ['SH', '1', 'VideoImageFormatAcquired'],
|
|
'0x1023': ['LO', '1', 'DigitalImageFormatAcquired'],
|
|
'0x1030': ['LO', '1', 'ProtocolName'],
|
|
'0x1040': ['LO', '1', 'ContrastBolusRoute'],
|
|
'0x1041': ['DS', '1', 'ContrastBolusVolume'],
|
|
'0x1042': ['TM', '1', 'ContrastBolusStartTime'],
|
|
'0x1043': ['TM', '1', 'ContrastBolusStopTime'],
|
|
'0x1044': ['DS', '1', 'ContrastBolusTotalDose'],
|
|
'0x1045': ['IS', '1', 'SyringeCounts'],
|
|
'0x1046': ['DS', '1-n', 'ContrastFlowRate'],
|
|
'0x1047': ['DS', '1-n', 'ContrastFlowDuration'],
|
|
'0x1048': ['CS', '1', 'ContrastBolusIngredient'],
|
|
'0x1049': ['DS', '1', 'ContrastBolusIngredientConcentration'],
|
|
'0x1050': ['DS', '1', 'SpatialResolution'],
|
|
'0x1060': ['DS', '1', 'TriggerTime'],
|
|
'0x1061': ['LO', '1', 'TriggerSourceOrType'],
|
|
'0x1062': ['IS', '1', 'NominalInterval'],
|
|
'0x1063': ['DS', '1', 'FrameTime'],
|
|
'0x1064': ['LO', '1', 'CardiacFramingType'],
|
|
'0x1065': ['DS', '1-n', 'FrameTimeVector'],
|
|
'0x1066': ['DS', '1', 'FrameDelay'],
|
|
'0x1067': ['DS', '1', 'ImageTriggerDelay'],
|
|
'0x1068': ['DS', '1', 'MultiplexGroupTimeOffset'],
|
|
'0x1069': ['DS', '1', 'TriggerTimeOffset'],
|
|
'0x106A': ['CS', '1', 'SynchronizationTrigger'],
|
|
'0x106C': ['US', '2', 'SynchronizationChannel'],
|
|
'0x106E': ['UL', '1', 'TriggerSamplePosition'],
|
|
'0x1070': ['LO', '1', 'RadiopharmaceuticalRoute'],
|
|
'0x1071': ['DS', '1', 'RadiopharmaceuticalVolume'],
|
|
'0x1072': ['TM', '1', 'RadiopharmaceuticalStartTime'],
|
|
'0x1073': ['TM', '1', 'RadiopharmaceuticalStopTime'],
|
|
'0x1074': ['DS', '1', 'RadionuclideTotalDose'],
|
|
'0x1075': ['DS', '1', 'RadionuclideHalfLife'],
|
|
'0x1076': ['DS', '1', 'RadionuclidePositronFraction'],
|
|
'0x1077': ['DS', '1', 'RadiopharmaceuticalSpecificActivity'],
|
|
'0x1078': ['DT', '1', 'RadiopharmaceuticalStartDateTime'],
|
|
'0x1079': ['DT', '1', 'RadiopharmaceuticalStopDateTime'],
|
|
'0x1080': ['CS', '1', 'BeatRejectionFlag'],
|
|
'0x1081': ['IS', '1', 'LowRRValue'],
|
|
'0x1082': ['IS', '1', 'HighRRValue'],
|
|
'0x1083': ['IS', '1', 'IntervalsAcquired'],
|
|
'0x1084': ['IS', '1', 'IntervalsRejected'],
|
|
'0x1085': ['LO', '1', 'PVCRejection'],
|
|
'0x1086': ['IS', '1', 'SkipBeats'],
|
|
'0x1088': ['IS', '1', 'HeartRate'],
|
|
'0x1090': ['IS', '1', 'CardiacNumberOfImages'],
|
|
'0x1094': ['IS', '1', 'TriggerWindow'],
|
|
'0x1100': ['DS', '1', 'ReconstructionDiameter'],
|
|
'0x1110': ['DS', '1', 'DistanceSourceToDetector'],
|
|
'0x1111': ['DS', '1', 'DistanceSourceToPatient'],
|
|
'0x1114': ['DS', '1', 'EstimatedRadiographicMagnificationFactor'],
|
|
'0x1120': ['DS', '1', 'GantryDetectorTilt'],
|
|
'0x1121': ['DS', '1', 'GantryDetectorSlew'],
|
|
'0x1130': ['DS', '1', 'TableHeight'],
|
|
'0x1131': ['DS', '1', 'TableTraverse'],
|
|
'0x1134': ['CS', '1', 'TableMotion'],
|
|
'0x1135': ['DS', '1-n', 'TableVerticalIncrement'],
|
|
'0x1136': ['DS', '1-n', 'TableLateralIncrement'],
|
|
'0x1137': ['DS', '1-n', 'TableLongitudinalIncrement'],
|
|
'0x1138': ['DS', '1', 'TableAngle'],
|
|
'0x113A': ['CS', '1', 'TableType'],
|
|
'0x1140': ['CS', '1', 'RotationDirection'],
|
|
'0x1141': ['DS', '1', 'AngularPosition'],
|
|
'0x1142': ['DS', '1-n', 'RadialPosition'],
|
|
'0x1143': ['DS', '1', 'ScanArc'],
|
|
'0x1144': ['DS', '1', 'AngularStep'],
|
|
'0x1145': ['DS', '1', 'CenterOfRotationOffset'],
|
|
'0x1146': ['DS', '1-n', 'RotationOffset'],
|
|
'0x1147': ['CS', '1', 'FieldOfViewShape'],
|
|
'0x1149': ['IS', '1-2', 'FieldOfViewDimensions'],
|
|
'0x1150': ['IS', '1', 'ExposureTime'],
|
|
'0x1151': ['IS', '1', 'XRayTubeCurrent'],
|
|
'0x1152': ['IS', '1', 'Exposure'],
|
|
'0x1153': ['IS', '1', 'ExposureInuAs'],
|
|
'0x1154': ['DS', '1', 'AveragePulseWidth'],
|
|
'0x1155': ['CS', '1', 'RadiationSetting'],
|
|
'0x1156': ['CS', '1', 'RectificationType'],
|
|
'0x115A': ['CS', '1', 'RadiationMode'],
|
|
'0x115E': ['DS', '1', 'ImageAndFluoroscopyAreaDoseProduct'],
|
|
'0x1160': ['SH', '1', 'FilterType'],
|
|
'0x1161': ['LO', '1-n', 'TypeOfFilters'],
|
|
'0x1162': ['DS', '1', 'IntensifierSize'],
|
|
'0x1164': ['DS', '2', 'ImagerPixelSpacing'],
|
|
'0x1166': ['CS', '1-n', 'Grid'],
|
|
'0x1170': ['IS', '1', 'GeneratorPower'],
|
|
'0x1180': ['SH', '1', 'CollimatorGridName'],
|
|
'0x1181': ['CS', '1', 'CollimatorType'],
|
|
'0x1182': ['IS', '1-2', 'FocalDistance'],
|
|
'0x1183': ['DS', '1-2', 'XFocusCenter'],
|
|
'0x1184': ['DS', '1-2', 'YFocusCenter'],
|
|
'0x1190': ['DS', '1-n', 'FocalSpots'],
|
|
'0x1191': ['CS', '1', 'AnodeTargetMaterial'],
|
|
'0x11A0': ['DS', '1', 'BodyPartThickness'],
|
|
'0x11A2': ['DS', '1', 'CompressionForce'],
|
|
'0x11A4': ['LO', '1', 'PaddleDescription'],
|
|
'0x1200': ['DA', '1-n', 'DateOfLastCalibration'],
|
|
'0x1201': ['TM', '1-n', 'TimeOfLastCalibration'],
|
|
'0x1202': ['DT', '1', 'DateTimeOfLastCalibration'],
|
|
'0x1210': ['SH', '1-n', 'ConvolutionKernel'],
|
|
'0x1240': ['IS', '1-n', 'UpperLowerPixelValues'],
|
|
'0x1242': ['IS', '1', 'ActualFrameDuration'],
|
|
'0x1243': ['IS', '1', 'CountRate'],
|
|
'0x1244': ['US', '1', 'PreferredPlaybackSequencing'],
|
|
'0x1250': ['SH', '1', 'ReceiveCoilName'],
|
|
'0x1251': ['SH', '1', 'TransmitCoilName'],
|
|
'0x1260': ['SH', '1', 'PlateType'],
|
|
'0x1261': ['LO', '1', 'PhosphorType'],
|
|
'0x1300': ['DS', '1', 'ScanVelocity'],
|
|
'0x1301': ['CS', '1-n', 'WholeBodyTechnique'],
|
|
'0x1302': ['IS', '1', 'ScanLength'],
|
|
'0x1310': ['US', '4', 'AcquisitionMatrix'],
|
|
'0x1312': ['CS', '1', 'InPlanePhaseEncodingDirection'],
|
|
'0x1314': ['DS', '1', 'FlipAngle'],
|
|
'0x1315': ['CS', '1', 'VariableFlipAngleFlag'],
|
|
'0x1316': ['DS', '1', 'SAR'],
|
|
'0x1318': ['DS', '1', 'dBdt'],
|
|
'0x1400': ['LO', '1', 'AcquisitionDeviceProcessingDescription'],
|
|
'0x1401': ['LO', '1', 'AcquisitionDeviceProcessingCode'],
|
|
'0x1402': ['CS', '1', 'CassetteOrientation'],
|
|
'0x1403': ['CS', '1', 'CassetteSize'],
|
|
'0x1404': ['US', '1', 'ExposuresOnPlate'],
|
|
'0x1405': ['IS', '1', 'RelativeXRayExposure'],
|
|
'0x1411': ['DS', '1', 'ExposureIndex'],
|
|
'0x1412': ['DS', '1', 'TargetExposureIndex'],
|
|
'0x1413': ['DS', '1', 'DeviationIndex'],
|
|
'0x1450': ['DS', '1', 'ColumnAngulation'],
|
|
'0x1460': ['DS', '1', 'TomoLayerHeight'],
|
|
'0x1470': ['DS', '1', 'TomoAngle'],
|
|
'0x1480': ['DS', '1', 'TomoTime'],
|
|
'0x1490': ['CS', '1', 'TomoType'],
|
|
'0x1491': ['CS', '1', 'TomoClass'],
|
|
'0x1495': ['IS', '1', 'NumberOfTomosynthesisSourceImages'],
|
|
'0x1500': ['CS', '1', 'PositionerMotion'],
|
|
'0x1508': ['CS', '1', 'PositionerType'],
|
|
'0x1510': ['DS', '1', 'PositionerPrimaryAngle'],
|
|
'0x1511': ['DS', '1', 'PositionerSecondaryAngle'],
|
|
'0x1520': ['DS', '1-n', 'PositionerPrimaryAngleIncrement'],
|
|
'0x1521': ['DS', '1-n', 'PositionerSecondaryAngleIncrement'],
|
|
'0x1530': ['DS', '1', 'DetectorPrimaryAngle'],
|
|
'0x1531': ['DS', '1', 'DetectorSecondaryAngle'],
|
|
'0x1600': ['CS', '1-3', 'ShutterShape'],
|
|
'0x1602': ['IS', '1', 'ShutterLeftVerticalEdge'],
|
|
'0x1604': ['IS', '1', 'ShutterRightVerticalEdge'],
|
|
'0x1606': ['IS', '1', 'ShutterUpperHorizontalEdge'],
|
|
'0x1608': ['IS', '1', 'ShutterLowerHorizontalEdge'],
|
|
'0x1610': ['IS', '2', 'CenterOfCircularShutter'],
|
|
'0x1612': ['IS', '1', 'RadiusOfCircularShutter'],
|
|
'0x1620': ['IS', '2-2n', 'VerticesOfThePolygonalShutter'],
|
|
'0x1622': ['US', '1', 'ShutterPresentationValue'],
|
|
'0x1623': ['US', '1', 'ShutterOverlayGroup'],
|
|
'0x1624': ['US', '3', 'ShutterPresentationColorCIELabValue'],
|
|
'0x1700': ['CS', '1-3', 'CollimatorShape'],
|
|
'0x1702': ['IS', '1', 'CollimatorLeftVerticalEdge'],
|
|
'0x1704': ['IS', '1', 'CollimatorRightVerticalEdge'],
|
|
'0x1706': ['IS', '1', 'CollimatorUpperHorizontalEdge'],
|
|
'0x1708': ['IS', '1', 'CollimatorLowerHorizontalEdge'],
|
|
'0x1710': ['IS', '2', 'CenterOfCircularCollimator'],
|
|
'0x1712': ['IS', '1', 'RadiusOfCircularCollimator'],
|
|
'0x1720': ['IS', '2-2n', 'VerticesOfThePolygonalCollimator'],
|
|
'0x1800': ['CS', '1', 'AcquisitionTimeSynchronized'],
|
|
'0x1801': ['SH', '1', 'TimeSource'],
|
|
'0x1802': ['CS', '1', 'TimeDistributionProtocol'],
|
|
'0x1803': ['LO', '1', 'NTPSourceAddress'],
|
|
'0x2001': ['IS', '1-n', 'PageNumberVector'],
|
|
'0x2002': ['SH', '1-n', 'FrameLabelVector'],
|
|
'0x2003': ['DS', '1-n', 'FramePrimaryAngleVector'],
|
|
'0x2004': ['DS', '1-n', 'FrameSecondaryAngleVector'],
|
|
'0x2005': ['DS', '1-n', 'SliceLocationVector'],
|
|
'0x2006': ['SH', '1-n', 'DisplayWindowLabelVector'],
|
|
'0x2010': ['DS', '2', 'NominalScannedPixelSpacing'],
|
|
'0x2020': ['CS', '1', 'DigitizingDeviceTransportDirection'],
|
|
'0x2030': ['DS', '1', 'RotationOfScannedFilm'],
|
|
'0x2041': ['SQ', '1', 'BiopsyTargetSequence'],
|
|
'0x2042': ['UI', '1', 'TargetUID'],
|
|
'0x2043': ['FL', '2', 'LocalizingCursorPosition'],
|
|
'0x2044': ['FL', '3', 'CalculatedTargetPosition'],
|
|
'0x2045': ['SH', '1', 'TargetLabel'],
|
|
'0x2046': ['FL', '1', 'DisplayedZValue'],
|
|
'0x3100': ['CS', '1', 'IVUSAcquisition'],
|
|
'0x3101': ['DS', '1', 'IVUSPullbackRate'],
|
|
'0x3102': ['DS', '1', 'IVUSGatedRate'],
|
|
'0x3103': ['IS', '1', 'IVUSPullbackStartFrameNumber'],
|
|
'0x3104': ['IS', '1', 'IVUSPullbackStopFrameNumber'],
|
|
'0x3105': ['IS', '1-n', 'LesionNumber'],
|
|
'0x4000': ['LT', '1', 'AcquisitionComments'],
|
|
'0x5000': ['SH', '1-n', 'OutputPower'],
|
|
'0x5010': ['LO', '1-n', 'TransducerData'],
|
|
'0x5012': ['DS', '1', 'FocusDepth'],
|
|
'0x5020': ['LO', '1', 'ProcessingFunction'],
|
|
'0x5021': ['LO', '1', 'PostprocessingFunction'],
|
|
'0x5022': ['DS', '1', 'MechanicalIndex'],
|
|
'0x5024': ['DS', '1', 'BoneThermalIndex'],
|
|
'0x5026': ['DS', '1', 'CranialThermalIndex'],
|
|
'0x5027': ['DS', '1', 'SoftTissueThermalIndex'],
|
|
'0x5028': ['DS', '1', 'SoftTissueFocusThermalIndex'],
|
|
'0x5029': ['DS', '1', 'SoftTissueSurfaceThermalIndex'],
|
|
'0x5030': ['DS', '1', 'DynamicRange'],
|
|
'0x5040': ['DS', '1', 'TotalGain'],
|
|
'0x5050': ['IS', '1', 'DepthOfScanField'],
|
|
'0x5100': ['CS', '1', 'PatientPosition'],
|
|
'0x5101': ['CS', '1', 'ViewPosition'],
|
|
'0x5104': ['SQ', '1', 'ProjectionEponymousNameCodeSequence'],
|
|
'0x5210': ['DS', '6', 'ImageTransformationMatrix'],
|
|
'0x5212': ['DS', '3', 'ImageTranslationVector'],
|
|
'0x6000': ['DS', '1', 'Sensitivity'],
|
|
'0x6011': ['SQ', '1', 'SequenceOfUltrasoundRegions'],
|
|
'0x6012': ['US', '1', 'RegionSpatialFormat'],
|
|
'0x6014': ['US', '1', 'RegionDataType'],
|
|
'0x6016': ['UL', '1', 'RegionFlags'],
|
|
'0x6018': ['UL', '1', 'RegionLocationMinX0'],
|
|
'0x601A': ['UL', '1', 'RegionLocationMinY0'],
|
|
'0x601C': ['UL', '1', 'RegionLocationMaxX1'],
|
|
'0x601E': ['UL', '1', 'RegionLocationMaxY1'],
|
|
'0x6020': ['SL', '1', 'ReferencePixelX0'],
|
|
'0x6022': ['SL', '1', 'ReferencePixelY0'],
|
|
'0x6024': ['US', '1', 'PhysicalUnitsXDirection'],
|
|
'0x6026': ['US', '1', 'PhysicalUnitsYDirection'],
|
|
'0x6028': ['FD', '1', 'ReferencePixelPhysicalValueX'],
|
|
'0x602A': ['FD', '1', 'ReferencePixelPhysicalValueY'],
|
|
'0x602C': ['FD', '1', 'PhysicalDeltaX'],
|
|
'0x602E': ['FD', '1', 'PhysicalDeltaY'],
|
|
'0x6030': ['UL', '1', 'TransducerFrequency'],
|
|
'0x6031': ['CS', '1', 'TransducerType'],
|
|
'0x6032': ['UL', '1', 'PulseRepetitionFrequency'],
|
|
'0x6034': ['FD', '1', 'DopplerCorrectionAngle'],
|
|
'0x6036': ['FD', '1', 'SteeringAngle'],
|
|
'0x6038': ['UL', '1', 'DopplerSampleVolumeXPositionRetired'],
|
|
'0x6039': ['SL', '1', 'DopplerSampleVolumeXPosition'],
|
|
'0x603A': ['UL', '1', 'DopplerSampleVolumeYPositionRetired'],
|
|
'0x603B': ['SL', '1', 'DopplerSampleVolumeYPosition'],
|
|
'0x603C': ['UL', '1', 'TMLinePositionX0Retired'],
|
|
'0x603D': ['SL', '1', 'TMLinePositionX0'],
|
|
'0x603E': ['UL', '1', 'TMLinePositionY0Retired'],
|
|
'0x603F': ['SL', '1', 'TMLinePositionY0'],
|
|
'0x6040': ['UL', '1', 'TMLinePositionX1Retired'],
|
|
'0x6041': ['SL', '1', 'TMLinePositionX1'],
|
|
'0x6042': ['UL', '1', 'TMLinePositionY1Retired'],
|
|
'0x6043': ['SL', '1', 'TMLinePositionY1'],
|
|
'0x6044': ['US', '1', 'PixelComponentOrganization'],
|
|
'0x6046': ['UL', '1', 'PixelComponentMask'],
|
|
'0x6048': ['UL', '1', 'PixelComponentRangeStart'],
|
|
'0x604A': ['UL', '1', 'PixelComponentRangeStop'],
|
|
'0x604C': ['US', '1', 'PixelComponentPhysicalUnits'],
|
|
'0x604E': ['US', '1', 'PixelComponentDataType'],
|
|
'0x6050': ['UL', '1', 'NumberOfTableBreakPoints'],
|
|
'0x6052': ['UL', '1-n', 'TableOfXBreakPoints'],
|
|
'0x6054': ['FD', '1-n', 'TableOfYBreakPoints'],
|
|
'0x6056': ['UL', '1', 'NumberOfTableEntries'],
|
|
'0x6058': ['UL', '1-n', 'TableOfPixelValues'],
|
|
'0x605A': ['FL', '1-n', 'TableOfParameterValues'],
|
|
'0x6060': ['FL', '1-n', 'RWaveTimeVector'],
|
|
'0x7000': ['CS', '1', 'DetectorConditionsNominalFlag'],
|
|
'0x7001': ['DS', '1', 'DetectorTemperature'],
|
|
'0x7004': ['CS', '1', 'DetectorType'],
|
|
'0x7005': ['CS', '1', 'DetectorConfiguration'],
|
|
'0x7006': ['LT', '1', 'DetectorDescription'],
|
|
'0x7008': ['LT', '1', 'DetectorMode'],
|
|
'0x700A': ['SH', '1', 'DetectorID'],
|
|
'0x700C': ['DA', '1', 'DateOfLastDetectorCalibration'],
|
|
'0x700E': ['TM', '1', 'TimeOfLastDetectorCalibration'],
|
|
'0x7010': ['IS', '1', 'ExposuresOnDetectorSinceLastCalibration'],
|
|
'0x7011': ['IS', '1', 'ExposuresOnDetectorSinceManufactured'],
|
|
'0x7012': ['DS', '1', 'DetectorTimeSinceLastExposure'],
|
|
'0x7014': ['DS', '1', 'DetectorActiveTime'],
|
|
'0x7016': ['DS', '1', 'DetectorActivationOffsetFromExposure'],
|
|
'0x701A': ['DS', '2', 'DetectorBinning'],
|
|
'0x7020': ['DS', '2', 'DetectorElementPhysicalSize'],
|
|
'0x7022': ['DS', '2', 'DetectorElementSpacing'],
|
|
'0x7024': ['CS', '1', 'DetectorActiveShape'],
|
|
'0x7026': ['DS', '1-2', 'DetectorActiveDimensions'],
|
|
'0x7028': ['DS', '2', 'DetectorActiveOrigin'],
|
|
'0x702A': ['LO', '1', 'DetectorManufacturerName'],
|
|
'0x702B': ['LO', '1', 'DetectorManufacturerModelName'],
|
|
'0x7030': ['DS', '2', 'FieldOfViewOrigin'],
|
|
'0x7032': ['DS', '1', 'FieldOfViewRotation'],
|
|
'0x7034': ['CS', '1', 'FieldOfViewHorizontalFlip'],
|
|
'0x7036': ['FL', '2', 'PixelDataAreaOriginRelativeToFOV'],
|
|
'0x7038': ['FL', '1', 'PixelDataAreaRotationAngleRelativeToFOV'],
|
|
'0x7040': ['LT', '1', 'GridAbsorbingMaterial'],
|
|
'0x7041': ['LT', '1', 'GridSpacingMaterial'],
|
|
'0x7042': ['DS', '1', 'GridThickness'],
|
|
'0x7044': ['DS', '1', 'GridPitch'],
|
|
'0x7046': ['IS', '2', 'GridAspectRatio'],
|
|
'0x7048': ['DS', '1', 'GridPeriod'],
|
|
'0x704C': ['DS', '1', 'GridFocalDistance'],
|
|
'0x7050': ['CS', '1-n', 'FilterMaterial'],
|
|
'0x7052': ['DS', '1-n', 'FilterThicknessMinimum'],
|
|
'0x7054': ['DS', '1-n', 'FilterThicknessMaximum'],
|
|
'0x7056': ['FL', '1-n', 'FilterBeamPathLengthMinimum'],
|
|
'0x7058': ['FL', '1-n', 'FilterBeamPathLengthMaximum'],
|
|
'0x7060': ['CS', '1', 'ExposureControlMode'],
|
|
'0x7062': ['LT', '1', 'ExposureControlModeDescription'],
|
|
'0x7064': ['CS', '1', 'ExposureStatus'],
|
|
'0x7065': ['DS', '1', 'PhototimerSetting'],
|
|
'0x8150': ['DS', '1', 'ExposureTimeInuS'],
|
|
'0x8151': ['DS', '1', 'XRayTubeCurrentInuA'],
|
|
'0x9004': ['CS', '1', 'ContentQualification'],
|
|
'0x9005': ['SH', '1', 'PulseSequenceName'],
|
|
'0x9006': ['SQ', '1', 'MRImagingModifierSequence'],
|
|
'0x9008': ['CS', '1', 'EchoPulseSequence'],
|
|
'0x9009': ['CS', '1', 'InversionRecovery'],
|
|
'0x9010': ['CS', '1', 'FlowCompensation'],
|
|
'0x9011': ['CS', '1', 'MultipleSpinEcho'],
|
|
'0x9012': ['CS', '1', 'MultiPlanarExcitation'],
|
|
'0x9014': ['CS', '1', 'PhaseContrast'],
|
|
'0x9015': ['CS', '1', 'TimeOfFlightContrast'],
|
|
'0x9016': ['CS', '1', 'Spoiling'],
|
|
'0x9017': ['CS', '1', 'SteadyStatePulseSequence'],
|
|
'0x9018': ['CS', '1', 'EchoPlanarPulseSequence'],
|
|
'0x9019': ['FD', '1', 'TagAngleFirstAxis'],
|
|
'0x9020': ['CS', '1', 'MagnetizationTransfer'],
|
|
'0x9021': ['CS', '1', 'T2Preparation'],
|
|
'0x9022': ['CS', '1', 'BloodSignalNulling'],
|
|
'0x9024': ['CS', '1', 'SaturationRecovery'],
|
|
'0x9025': ['CS', '1', 'SpectrallySelectedSuppression'],
|
|
'0x9026': ['CS', '1', 'SpectrallySelectedExcitation'],
|
|
'0x9027': ['CS', '1', 'SpatialPresaturation'],
|
|
'0x9028': ['CS', '1', 'Tagging'],
|
|
'0x9029': ['CS', '1', 'OversamplingPhase'],
|
|
'0x9030': ['FD', '1', 'TagSpacingFirstDimension'],
|
|
'0x9032': ['CS', '1', 'GeometryOfKSpaceTraversal'],
|
|
'0x9033': ['CS', '1', 'SegmentedKSpaceTraversal'],
|
|
'0x9034': ['CS', '1', 'RectilinearPhaseEncodeReordering'],
|
|
'0x9035': ['FD', '1', 'TagThickness'],
|
|
'0x9036': ['CS', '1', 'PartialFourierDirection'],
|
|
'0x9037': ['CS', '1', 'CardiacSynchronizationTechnique'],
|
|
'0x9041': ['LO', '1', 'ReceiveCoilManufacturerName'],
|
|
'0x9042': ['SQ', '1', 'MRReceiveCoilSequence'],
|
|
'0x9043': ['CS', '1', 'ReceiveCoilType'],
|
|
'0x9044': ['CS', '1', 'QuadratureReceiveCoil'],
|
|
'0x9045': ['SQ', '1', 'MultiCoilDefinitionSequence'],
|
|
'0x9046': ['LO', '1', 'MultiCoilConfiguration'],
|
|
'0x9047': ['SH', '1', 'MultiCoilElementName'],
|
|
'0x9048': ['CS', '1', 'MultiCoilElementUsed'],
|
|
'0x9049': ['SQ', '1', 'MRTransmitCoilSequence'],
|
|
'0x9050': ['LO', '1', 'TransmitCoilManufacturerName'],
|
|
'0x9051': ['CS', '1', 'TransmitCoilType'],
|
|
'0x9052': ['FD', '1-2', 'SpectralWidth'],
|
|
'0x9053': ['FD', '1-2', 'ChemicalShiftReference'],
|
|
'0x9054': ['CS', '1', 'VolumeLocalizationTechnique'],
|
|
'0x9058': ['US', '1', 'MRAcquisitionFrequencyEncodingSteps'],
|
|
'0x9059': ['CS', '1', 'Decoupling'],
|
|
'0x9060': ['CS', '1-2', 'DecoupledNucleus'],
|
|
'0x9061': ['FD', '1-2', 'DecouplingFrequency'],
|
|
'0x9062': ['CS', '1', 'DecouplingMethod'],
|
|
'0x9063': ['FD', '1-2', 'DecouplingChemicalShiftReference'],
|
|
'0x9064': ['CS', '1', 'KSpaceFiltering'],
|
|
'0x9065': ['CS', '1-2', 'TimeDomainFiltering'],
|
|
'0x9066': ['US', '1-2', 'NumberOfZeroFills'],
|
|
'0x9067': ['CS', '1', 'BaselineCorrection'],
|
|
'0x9069': ['FD', '1', 'ParallelReductionFactorInPlane'],
|
|
'0x9070': ['FD', '1', 'CardiacRRIntervalSpecified'],
|
|
'0x9073': ['FD', '1', 'AcquisitionDuration'],
|
|
'0x9074': ['DT', '1', 'FrameAcquisitionDateTime'],
|
|
'0x9075': ['CS', '1', 'DiffusionDirectionality'],
|
|
'0x9076': ['SQ', '1', 'DiffusionGradientDirectionSequence'],
|
|
'0x9077': ['CS', '1', 'ParallelAcquisition'],
|
|
'0x9078': ['CS', '1', 'ParallelAcquisitionTechnique'],
|
|
'0x9079': ['FD', '1-n', 'InversionTimes'],
|
|
'0x9080': ['ST', '1', 'MetaboliteMapDescription'],
|
|
'0x9081': ['CS', '1', 'PartialFourier'],
|
|
'0x9082': ['FD', '1', 'EffectiveEchoTime'],
|
|
'0x9083': ['SQ', '1', 'MetaboliteMapCodeSequence'],
|
|
'0x9084': ['SQ', '1', 'ChemicalShiftSequence'],
|
|
'0x9085': ['CS', '1', 'CardiacSignalSource'],
|
|
'0x9087': ['FD', '1', 'DiffusionBValue'],
|
|
'0x9089': ['FD', '3', 'DiffusionGradientOrientation'],
|
|
'0x9090': ['FD', '3', 'VelocityEncodingDirection'],
|
|
'0x9091': ['FD', '1', 'VelocityEncodingMinimumValue'],
|
|
'0x9092': ['SQ', '1', 'VelocityEncodingAcquisitionSequence'],
|
|
'0x9093': ['US', '1', 'NumberOfKSpaceTrajectories'],
|
|
'0x9094': ['CS', '1', 'CoverageOfKSpace'],
|
|
'0x9095': ['UL', '1', 'SpectroscopyAcquisitionPhaseRows'],
|
|
'0x9096': ['FD', '1', 'ParallelReductionFactorInPlaneRetired'],
|
|
'0x9098': ['FD', '1-2', 'TransmitterFrequency'],
|
|
'0x9100': ['CS', '1-2', 'ResonantNucleus'],
|
|
'0x9101': ['CS', '1', 'FrequencyCorrection'],
|
|
'0x9103': ['SQ', '1', 'MRSpectroscopyFOVGeometrySequence'],
|
|
'0x9104': ['FD', '1', 'SlabThickness'],
|
|
'0x9105': ['FD', '3', 'SlabOrientation'],
|
|
'0x9106': ['FD', '3', 'MidSlabPosition'],
|
|
'0x9107': ['SQ', '1', 'MRSpatialSaturationSequence'],
|
|
'0x9112': ['SQ', '1', 'MRTimingAndRelatedParametersSequence'],
|
|
'0x9114': ['SQ', '1', 'MREchoSequence'],
|
|
'0x9115': ['SQ', '1', 'MRModifierSequence'],
|
|
'0x9117': ['SQ', '1', 'MRDiffusionSequence'],
|
|
'0x9118': ['SQ', '1', 'CardiacSynchronizationSequence'],
|
|
'0x9119': ['SQ', '1', 'MRAveragesSequence'],
|
|
'0x9125': ['SQ', '1', 'MRFOVGeometrySequence'],
|
|
'0x9126': ['SQ', '1', 'VolumeLocalizationSequence'],
|
|
'0x9127': ['UL', '1', 'SpectroscopyAcquisitionDataColumns'],
|
|
'0x9147': ['CS', '1', 'DiffusionAnisotropyType'],
|
|
'0x9151': ['DT', '1', 'FrameReferenceDateTime'],
|
|
'0x9152': ['SQ', '1', 'MRMetaboliteMapSequence'],
|
|
'0x9155': ['FD', '1', 'ParallelReductionFactorOutOfPlane'],
|
|
'0x9159': ['UL', '1', 'SpectroscopyAcquisitionOutOfPlanePhaseSteps'],
|
|
'0x9166': ['CS', '1', 'BulkMotionStatus'],
|
|
'0x9168': ['FD', '1', 'ParallelReductionFactorSecondInPlane'],
|
|
'0x9169': ['CS', '1', 'CardiacBeatRejectionTechnique'],
|
|
'0x9170': ['CS', '1', 'RespiratoryMotionCompensationTechnique'],
|
|
'0x9171': ['CS', '1', 'RespiratorySignalSource'],
|
|
'0x9172': ['CS', '1', 'BulkMotionCompensationTechnique'],
|
|
'0x9173': ['CS', '1', 'BulkMotionSignalSource'],
|
|
'0x9174': ['CS', '1', 'ApplicableSafetyStandardAgency'],
|
|
'0x9175': ['LO', '1', 'ApplicableSafetyStandardDescription'],
|
|
'0x9176': ['SQ', '1', 'OperatingModeSequence'],
|
|
'0x9177': ['CS', '1', 'OperatingModeType'],
|
|
'0x9178': ['CS', '1', 'OperatingMode'],
|
|
'0x9179': ['CS', '1', 'SpecificAbsorptionRateDefinition'],
|
|
'0x9180': ['CS', '1', 'GradientOutputType'],
|
|
'0x9181': ['FD', '1', 'SpecificAbsorptionRateValue'],
|
|
'0x9182': ['FD', '1', 'GradientOutput'],
|
|
'0x9183': ['CS', '1', 'FlowCompensationDirection'],
|
|
'0x9184': ['FD', '1', 'TaggingDelay'],
|
|
'0x9185': ['ST', '1', 'RespiratoryMotionCompensationTechniqueDescription'],
|
|
'0x9186': ['SH', '1', 'RespiratorySignalSourceID'],
|
|
'0x9195': ['FD', '1', 'ChemicalShiftMinimumIntegrationLimitInHz'],
|
|
'0x9196': ['FD', '1', 'ChemicalShiftMaximumIntegrationLimitInHz'],
|
|
'0x9197': ['SQ', '1', 'MRVelocityEncodingSequence'],
|
|
'0x9198': ['CS', '1', 'FirstOrderPhaseCorrection'],
|
|
'0x9199': ['CS', '1', 'WaterReferencedPhaseCorrection'],
|
|
'0x9200': ['CS', '1', 'MRSpectroscopyAcquisitionType'],
|
|
'0x9214': ['CS', '1', 'RespiratoryCyclePosition'],
|
|
'0x9217': ['FD', '1', 'VelocityEncodingMaximumValue'],
|
|
'0x9218': ['FD', '1', 'TagSpacingSecondDimension'],
|
|
'0x9219': ['SS', '1', 'TagAngleSecondAxis'],
|
|
'0x9220': ['FD', '1', 'FrameAcquisitionDuration'],
|
|
'0x9226': ['SQ', '1', 'MRImageFrameTypeSequence'],
|
|
'0x9227': ['SQ', '1', 'MRSpectroscopyFrameTypeSequence'],
|
|
'0x9231': ['US', '1', 'MRAcquisitionPhaseEncodingStepsInPlane'],
|
|
'0x9232': ['US', '1', 'MRAcquisitionPhaseEncodingStepsOutOfPlane'],
|
|
'0x9234': ['UL', '1', 'SpectroscopyAcquisitionPhaseColumns'],
|
|
'0x9236': ['CS', '1', 'CardiacCyclePosition'],
|
|
'0x9239': ['SQ', '1', 'SpecificAbsorptionRateSequence'],
|
|
'0x9240': ['US', '1', 'RFEchoTrainLength'],
|
|
'0x9241': ['US', '1', 'GradientEchoTrainLength'],
|
|
'0x9250': ['CS', '1', 'ArterialSpinLabelingContrast'],
|
|
'0x9251': ['SQ', '1', 'MRArterialSpinLabelingSequence'],
|
|
'0x9252': ['LO', '1', 'ASLTechniqueDescription'],
|
|
'0x9253': ['US', '1', 'ASLSlabNumber'],
|
|
'0x9254': ['FD', '1', 'ASLSlabThickness'],
|
|
'0x9255': ['FD', '3', 'ASLSlabOrientation'],
|
|
'0x9256': ['FD', '3', 'ASLMidSlabPosition'],
|
|
'0x9257': ['CS', '1', 'ASLContext'],
|
|
'0x9258': ['UL', '1', 'ASLPulseTrainDuration'],
|
|
'0x9259': ['CS', '1', 'ASLCrusherFlag'],
|
|
'0x925A': ['FD', '1', 'ASLCrusherFlowLimit'],
|
|
'0x925B': ['LO', '1', 'ASLCrusherDescription'],
|
|
'0x925C': ['CS', '1', 'ASLBolusCutoffFlag'],
|
|
'0x925D': ['SQ', '1', 'ASLBolusCutoffTimingSequence'],
|
|
'0x925E': ['LO', '1', 'ASLBolusCutoffTechnique'],
|
|
'0x925F': ['UL', '1', 'ASLBolusCutoffDelayTime'],
|
|
'0x9260': ['SQ', '1', 'ASLSlabSequence'],
|
|
'0x9295': ['FD', '1', 'ChemicalShiftMinimumIntegrationLimitInppm'],
|
|
'0x9296': ['FD', '1', 'ChemicalShiftMaximumIntegrationLimitInppm'],
|
|
'0x9297': ['CS', '1', 'WaterReferenceAcquisition'],
|
|
'0x9298': ['IS', '1', 'EchoPeakPosition'],
|
|
'0x9301': ['SQ', '1', 'CTAcquisitionTypeSequence'],
|
|
'0x9302': ['CS', '1', 'AcquisitionType'],
|
|
'0x9303': ['FD', '1', 'TubeAngle'],
|
|
'0x9304': ['SQ', '1', 'CTAcquisitionDetailsSequence'],
|
|
'0x9305': ['FD', '1', 'RevolutionTime'],
|
|
'0x9306': ['FD', '1', 'SingleCollimationWidth'],
|
|
'0x9307': ['FD', '1', 'TotalCollimationWidth'],
|
|
'0x9308': ['SQ', '1', 'CTTableDynamicsSequence'],
|
|
'0x9309': ['FD', '1', 'TableSpeed'],
|
|
'0x9310': ['FD', '1', 'TableFeedPerRotation'],
|
|
'0x9311': ['FD', '1', 'SpiralPitchFactor'],
|
|
'0x9312': ['SQ', '1', 'CTGeometrySequence'],
|
|
'0x9313': ['FD', '3', 'DataCollectionCenterPatient'],
|
|
'0x9314': ['SQ', '1', 'CTReconstructionSequence'],
|
|
'0x9315': ['CS', '1', 'ReconstructionAlgorithm'],
|
|
'0x9316': ['CS', '1', 'ConvolutionKernelGroup'],
|
|
'0x9317': ['FD', '2', 'ReconstructionFieldOfView'],
|
|
'0x9318': ['FD', '3', 'ReconstructionTargetCenterPatient'],
|
|
'0x9319': ['FD', '1', 'ReconstructionAngle'],
|
|
'0x9320': ['SH', '1', 'ImageFilter'],
|
|
'0x9321': ['SQ', '1', 'CTExposureSequence'],
|
|
'0x9322': ['FD', '2', 'ReconstructionPixelSpacing'],
|
|
'0x9323': ['CS', '1', 'ExposureModulationType'],
|
|
'0x9324': ['FD', '1', 'EstimatedDoseSaving'],
|
|
'0x9325': ['SQ', '1', 'CTXRayDetailsSequence'],
|
|
'0x9326': ['SQ', '1', 'CTPositionSequence'],
|
|
'0x9327': ['FD', '1', 'TablePosition'],
|
|
'0x9328': ['FD', '1', 'ExposureTimeInms'],
|
|
'0x9329': ['SQ', '1', 'CTImageFrameTypeSequence'],
|
|
'0x9330': ['FD', '1', 'XRayTubeCurrentInmA'],
|
|
'0x9332': ['FD', '1', 'ExposureInmAs'],
|
|
'0x9333': ['CS', '1', 'ConstantVolumeFlag'],
|
|
'0x9334': ['CS', '1', 'FluoroscopyFlag'],
|
|
'0x9335': ['FD', '1', 'DistanceSourceToDataCollectionCenter'],
|
|
'0x9337': ['US', '1', 'ContrastBolusAgentNumber'],
|
|
'0x9338': ['SQ', '1', 'ContrastBolusIngredientCodeSequence'],
|
|
'0x9340': ['SQ', '1', 'ContrastAdministrationProfileSequence'],
|
|
'0x9341': ['SQ', '1', 'ContrastBolusUsageSequence'],
|
|
'0x9342': ['CS', '1', 'ContrastBolusAgentAdministered'],
|
|
'0x9343': ['CS', '1', 'ContrastBolusAgentDetected'],
|
|
'0x9344': ['CS', '1', 'ContrastBolusAgentPhase'],
|
|
'0x9345': ['FD', '1', 'CTDIvol'],
|
|
'0x9346': ['SQ', '1', 'CTDIPhantomTypeCodeSequence'],
|
|
'0x9351': ['FL', '1', 'CalciumScoringMassFactorPatient'],
|
|
'0x9352': ['FL', '3', 'CalciumScoringMassFactorDevice'],
|
|
'0x9353': ['FL', '1', 'EnergyWeightingFactor'],
|
|
'0x9360': ['SQ', '1', 'CTAdditionalXRaySourceSequence'],
|
|
'0x9401': ['SQ', '1', 'ProjectionPixelCalibrationSequence'],
|
|
'0x9402': ['FL', '1', 'DistanceSourceToIsocenter'],
|
|
'0x9403': ['FL', '1', 'DistanceObjectToTableTop'],
|
|
'0x9404': ['FL', '2', 'ObjectPixelSpacingInCenterOfBeam'],
|
|
'0x9405': ['SQ', '1', 'PositionerPositionSequence'],
|
|
'0x9406': ['SQ', '1', 'TablePositionSequence'],
|
|
'0x9407': ['SQ', '1', 'CollimatorShapeSequence'],
|
|
'0x9410': ['CS', '1', 'PlanesInAcquisition'],
|
|
'0x9412': ['SQ', '1', 'XAXRFFrameCharacteristicsSequence'],
|
|
'0x9417': ['SQ', '1', 'FrameAcquisitionSequence'],
|
|
'0x9420': ['CS', '1', 'XRayReceptorType'],
|
|
'0x9423': ['LO', '1', 'AcquisitionProtocolName'],
|
|
'0x9424': ['LT', '1', 'AcquisitionProtocolDescription'],
|
|
'0x9425': ['CS', '1', 'ContrastBolusIngredientOpaque'],
|
|
'0x9426': ['FL', '1', 'DistanceReceptorPlaneToDetectorHousing'],
|
|
'0x9427': ['CS', '1', 'IntensifierActiveShape'],
|
|
'0x9428': ['FL', '1-2', 'IntensifierActiveDimensions'],
|
|
'0x9429': ['FL', '2', 'PhysicalDetectorSize'],
|
|
'0x9430': ['FL', '2', 'PositionOfIsocenterProjection'],
|
|
'0x9432': ['SQ', '1', 'FieldOfViewSequence'],
|
|
'0x9433': ['LO', '1', 'FieldOfViewDescription'],
|
|
'0x9434': ['SQ', '1', 'ExposureControlSensingRegionsSequence'],
|
|
'0x9435': ['CS', '1', 'ExposureControlSensingRegionShape'],
|
|
'0x9436': ['SS', '1', 'ExposureControlSensingRegionLeftVerticalEdge'],
|
|
'0x9437': ['SS', '1', 'ExposureControlSensingRegionRightVerticalEdge'],
|
|
'0x9438': ['SS', '1', 'ExposureControlSensingRegionUpperHorizontalEdge'],
|
|
'0x9439': ['SS', '1', 'ExposureControlSensingRegionLowerHorizontalEdge'],
|
|
'0x9440': ['SS', '2', 'CenterOfCircularExposureControlSensingRegion'],
|
|
'0x9441': ['US', '1', 'RadiusOfCircularExposureControlSensingRegion'],
|
|
'0x9442': ['SS', '2-n', 'VerticesOfThePolygonalExposureControlSensingRegion'],
|
|
'0x9445': ['', '', ''],
|
|
'0x9447': ['FL', '1', 'ColumnAngulationPatient'],
|
|
'0x9449': ['FL', '1', 'BeamAngle'],
|
|
'0x9451': ['SQ', '1', 'FrameDetectorParametersSequence'],
|
|
'0x9452': ['FL', '1', 'CalculatedAnatomyThickness'],
|
|
'0x9455': ['SQ', '1', 'CalibrationSequence'],
|
|
'0x9456': ['SQ', '1', 'ObjectThicknessSequence'],
|
|
'0x9457': ['CS', '1', 'PlaneIdentification'],
|
|
'0x9461': ['FL', '1-2', 'FieldOfViewDimensionsInFloat'],
|
|
'0x9462': ['SQ', '1', 'IsocenterReferenceSystemSequence'],
|
|
'0x9463': ['FL', '1', 'PositionerIsocenterPrimaryAngle'],
|
|
'0x9464': ['FL', '1', 'PositionerIsocenterSecondaryAngle'],
|
|
'0x9465': ['FL', '1', 'PositionerIsocenterDetectorRotationAngle'],
|
|
'0x9466': ['FL', '1', 'TableXPositionToIsocenter'],
|
|
'0x9467': ['FL', '1', 'TableYPositionToIsocenter'],
|
|
'0x9468': ['FL', '1', 'TableZPositionToIsocenter'],
|
|
'0x9469': ['FL', '1', 'TableHorizontalRotationAngle'],
|
|
'0x9470': ['FL', '1', 'TableHeadTiltAngle'],
|
|
'0x9471': ['FL', '1', 'TableCradleTiltAngle'],
|
|
'0x9472': ['SQ', '1', 'FrameDisplayShutterSequence'],
|
|
'0x9473': ['FL', '1', 'AcquiredImageAreaDoseProduct'],
|
|
'0x9474': ['CS', '1', 'CArmPositionerTabletopRelationship'],
|
|
'0x9476': ['SQ', '1', 'XRayGeometrySequence'],
|
|
'0x9477': ['SQ', '1', 'IrradiationEventIdentificationSequence'],
|
|
'0x9504': ['SQ', '1', 'XRay3DFrameTypeSequence'],
|
|
'0x9506': ['SQ', '1', 'ContributingSourcesSequence'],
|
|
'0x9507': ['SQ', '1', 'XRay3DAcquisitionSequence'],
|
|
'0x9508': ['FL', '1', 'PrimaryPositionerScanArc'],
|
|
'0x9509': ['FL', '1', 'SecondaryPositionerScanArc'],
|
|
'0x9510': ['FL', '1', 'PrimaryPositionerScanStartAngle'],
|
|
'0x9511': ['FL', '1', 'SecondaryPositionerScanStartAngle'],
|
|
'0x9514': ['FL', '1', 'PrimaryPositionerIncrement'],
|
|
'0x9515': ['FL', '1', 'SecondaryPositionerIncrement'],
|
|
'0x9516': ['DT', '1', 'StartAcquisitionDateTime'],
|
|
'0x9517': ['DT', '1', 'EndAcquisitionDateTime'],
|
|
'0x9518': ['SS', '1', 'PrimaryPositionerIncrementSign'],
|
|
'0x9519': ['SS', '1', 'SecondaryPositionerIncrementSign'],
|
|
'0x9524': ['LO', '1', 'ApplicationName'],
|
|
'0x9525': ['LO', '1', 'ApplicationVersion'],
|
|
'0x9526': ['LO', '1', 'ApplicationManufacturer'],
|
|
'0x9527': ['CS', '1', 'AlgorithmType'],
|
|
'0x9528': ['LO', '1', 'AlgorithmDescription'],
|
|
'0x9530': ['SQ', '1', 'XRay3DReconstructionSequence'],
|
|
'0x9531': ['LO', '1', 'ReconstructionDescription'],
|
|
'0x9538': ['SQ', '1', 'PerProjectionAcquisitionSequence'],
|
|
'0x9541': ['SQ', '1', 'DetectorPositionSequence'],
|
|
'0x9542': ['SQ', '1', 'XRayAcquisitionDoseSequence'],
|
|
'0x9543': ['FD', '1', 'XRaySourceIsocenterPrimaryAngle'],
|
|
'0x9544': ['FD', '1', 'XRaySourceIsocenterSecondaryAngle'],
|
|
'0x9545': ['FD', '1', 'BreastSupportIsocenterPrimaryAngle'],
|
|
'0x9546': ['FD', '1', 'BreastSupportIsocenterSecondaryAngle'],
|
|
'0x9547': ['FD', '1', 'BreastSupportXPositionToIsocenter'],
|
|
'0x9548': ['FD', '1', 'BreastSupportYPositionToIsocenter'],
|
|
'0x9549': ['FD', '1', 'BreastSupportZPositionToIsocenter'],
|
|
'0x9550': ['FD', '1', 'DetectorIsocenterPrimaryAngle'],
|
|
'0x9551': ['FD', '1', 'DetectorIsocenterSecondaryAngle'],
|
|
'0x9552': ['FD', '1', 'DetectorXPositionToIsocenter'],
|
|
'0x9553': ['FD', '1', 'DetectorYPositionToIsocenter'],
|
|
'0x9554': ['FD', '1', 'DetectorZPositionToIsocenter'],
|
|
'0x9555': ['SQ', '1', 'XRayGridSequence'],
|
|
'0x9556': ['SQ', '1', 'XRayFilterSequence'],
|
|
'0x9557': ['FD', '3', 'DetectorActiveAreaTLHCPosition'],
|
|
'0x9558': ['FD', '6', 'DetectorActiveAreaOrientation'],
|
|
'0x9559': ['CS', '1', 'PositionerPrimaryAngleDirection'],
|
|
'0x9601': ['SQ', '1', 'DiffusionBMatrixSequence'],
|
|
'0x9602': ['FD', '1', 'DiffusionBValueXX'],
|
|
'0x9603': ['FD', '1', 'DiffusionBValueXY'],
|
|
'0x9604': ['FD', '1', 'DiffusionBValueXZ'],
|
|
'0x9605': ['FD', '1', 'DiffusionBValueYY'],
|
|
'0x9606': ['FD', '1', 'DiffusionBValueYZ'],
|
|
'0x9607': ['FD', '1', 'DiffusionBValueZZ'],
|
|
'0x9701': ['DT', '1', 'DecayCorrectionDateTime'],
|
|
'0x9715': ['FD', '1', 'StartDensityThreshold'],
|
|
'0x9716': ['FD', '1', 'StartRelativeDensityDifferenceThreshold'],
|
|
'0x9717': ['FD', '1', 'StartCardiacTriggerCountThreshold'],
|
|
'0x9718': ['FD', '1', 'StartRespiratoryTriggerCountThreshold'],
|
|
'0x9719': ['FD', '1', 'TerminationCountsThreshold'],
|
|
'0x9720': ['FD', '1', 'TerminationDensityThreshold'],
|
|
'0x9721': ['FD', '1', 'TerminationRelativeDensityThreshold'],
|
|
'0x9722': ['FD', '1', 'TerminationTimeThreshold'],
|
|
'0x9723': ['FD', '1', 'TerminationCardiacTriggerCountThreshold'],
|
|
'0x9724': ['FD', '1', 'TerminationRespiratoryTriggerCountThreshold'],
|
|
'0x9725': ['CS', '1', 'DetectorGeometry'],
|
|
'0x9726': ['FD', '1', 'TransverseDetectorSeparation'],
|
|
'0x9727': ['FD', '1', 'AxialDetectorDimension'],
|
|
'0x9729': ['US', '1', 'RadiopharmaceuticalAgentNumber'],
|
|
'0x9732': ['SQ', '1', 'PETFrameAcquisitionSequence'],
|
|
'0x9733': ['SQ', '1', 'PETDetectorMotionDetailsSequence'],
|
|
'0x9734': ['SQ', '1', 'PETTableDynamicsSequence'],
|
|
'0x9735': ['SQ', '1', 'PETPositionSequence'],
|
|
'0x9736': ['SQ', '1', 'PETFrameCorrectionFactorsSequence'],
|
|
'0x9737': ['SQ', '1', 'RadiopharmaceuticalUsageSequence'],
|
|
'0x9738': ['CS', '1', 'AttenuationCorrectionSource'],
|
|
'0x9739': ['US', '1', 'NumberOfIterations'],
|
|
'0x9740': ['US', '1', 'NumberOfSubsets'],
|
|
'0x9749': ['SQ', '1', 'PETReconstructionSequence'],
|
|
'0x9751': ['SQ', '1', 'PETFrameTypeSequence'],
|
|
'0x9755': ['CS', '1', 'TimeOfFlightInformationUsed'],
|
|
'0x9756': ['CS', '1', 'ReconstructionType'],
|
|
'0x9758': ['CS', '1', 'DecayCorrected'],
|
|
'0x9759': ['CS', '1', 'AttenuationCorrected'],
|
|
'0x9760': ['CS', '1', 'ScatterCorrected'],
|
|
'0x9761': ['CS', '1', 'DeadTimeCorrected'],
|
|
'0x9762': ['CS', '1', 'GantryMotionCorrected'],
|
|
'0x9763': ['CS', '1', 'PatientMotionCorrected'],
|
|
'0x9764': ['CS', '1', 'CountLossNormalizationCorrected'],
|
|
'0x9765': ['CS', '1', 'RandomsCorrected'],
|
|
'0x9766': ['CS', '1', 'NonUniformRadialSamplingCorrected'],
|
|
'0x9767': ['CS', '1', 'SensitivityCalibrated'],
|
|
'0x9768': ['CS', '1', 'DetectorNormalizationCorrection'],
|
|
'0x9769': ['CS', '1', 'IterativeReconstructionMethod'],
|
|
'0x9770': ['CS', '1', 'AttenuationCorrectionTemporalRelationship'],
|
|
'0x9771': ['SQ', '1', 'PatientPhysiologicalStateSequence'],
|
|
'0x9772': ['SQ', '1', 'PatientPhysiologicalStateCodeSequence'],
|
|
'0x9801': ['FD', '1-n', 'DepthsOfFocus'],
|
|
'0x9803': ['SQ', '1', 'ExcludedIntervalsSequence'],
|
|
'0x9804': ['DT', '1', 'ExclusionStartDateTime'],
|
|
'0x9805': ['FD', '1', 'ExclusionDuration'],
|
|
'0x9806': ['SQ', '1', 'USImageDescriptionSequence'],
|
|
'0x9807': ['SQ', '1', 'ImageDataTypeSequence'],
|
|
'0x9808': ['CS', '1', 'DataType'],
|
|
'0x9809': ['SQ', '1', 'TransducerScanPatternCodeSequence'],
|
|
'0x980B': ['CS', '1', 'AliasedDataType'],
|
|
'0x980C': ['CS', '1', 'PositionMeasuringDeviceUsed'],
|
|
'0x980D': ['SQ', '1', 'TransducerGeometryCodeSequence'],
|
|
'0x980E': ['SQ', '1', 'TransducerBeamSteeringCodeSequence'],
|
|
'0x980F': ['SQ', '1', 'TransducerApplicationCodeSequence'],
|
|
'0x9810': ['xs', '1', 'ZeroVelocityPixelValue'],
|
|
'0xA001': ['SQ', '1', 'ContributingEquipmentSequence'],
|
|
'0xA002': ['DT', '1', 'ContributionDateTime'],
|
|
'0xA003': ['ST', '1', 'ContributionDescription']
|
|
},
|
|
'0x0020': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x000D': ['UI', '1', 'StudyInstanceUID'],
|
|
'0x000E': ['UI', '1', 'SeriesInstanceUID'],
|
|
'0x0010': ['SH', '1', 'StudyID'],
|
|
'0x0011': ['IS', '1', 'SeriesNumber'],
|
|
'0x0012': ['IS', '1', 'AcquisitionNumber'],
|
|
'0x0013': ['IS', '1', 'InstanceNumber'],
|
|
'0x0014': ['IS', '1', 'IsotopeNumber'],
|
|
'0x0015': ['IS', '1', 'PhaseNumber'],
|
|
'0x0016': ['IS', '1', 'IntervalNumber'],
|
|
'0x0017': ['IS', '1', 'TimeSlotNumber'],
|
|
'0x0018': ['IS', '1', 'AngleNumber'],
|
|
'0x0019': ['IS', '1', 'ItemNumber'],
|
|
'0x0020': ['CS', '2', 'PatientOrientation'],
|
|
'0x0022': ['IS', '1', 'OverlayNumber'],
|
|
'0x0024': ['IS', '1', 'CurveNumber'],
|
|
'0x0026': ['IS', '1', 'LUTNumber'],
|
|
'0x0030': ['DS', '3', 'ImagePosition'],
|
|
'0x0032': ['DS', '3', 'ImagePositionPatient'],
|
|
'0x0035': ['DS', '6', 'ImageOrientation'],
|
|
'0x0037': ['DS', '6', 'ImageOrientationPatient'],
|
|
'0x0050': ['DS', '1', 'Location'],
|
|
'0x0052': ['UI', '1', 'FrameOfReferenceUID'],
|
|
'0x0060': ['CS', '1', 'Laterality'],
|
|
'0x0062': ['CS', '1', 'ImageLaterality'],
|
|
'0x0070': ['LO', '1', 'ImageGeometryType'],
|
|
'0x0080': ['CS', '1-n', 'MaskingImage'],
|
|
'0x00AA': ['IS', '1', 'ReportNumber'],
|
|
'0x0100': ['IS', '1', 'TemporalPositionIdentifier'],
|
|
'0x0105': ['IS', '1', 'NumberOfTemporalPositions'],
|
|
'0x0110': ['DS', '1', 'TemporalResolution'],
|
|
'0x0200': ['UI', '1', 'SynchronizationFrameOfReferenceUID'],
|
|
'0x0242': ['UI', '1', 'SOPInstanceUIDOfConcatenationSource'],
|
|
'0x1000': ['IS', '1', 'SeriesInStudy'],
|
|
'0x1001': ['IS', '1', 'AcquisitionsInSeries'],
|
|
'0x1002': ['IS', '1', 'ImagesInAcquisition'],
|
|
'0x1003': ['IS', '1', 'ImagesInSeries'],
|
|
'0x1004': ['IS', '1', 'AcquisitionsInStudy'],
|
|
'0x1005': ['IS', '1', 'ImagesInStudy'],
|
|
'0x1020': ['LO', '1-n', 'Reference'],
|
|
'0x1040': ['LO', '1', 'PositionReferenceIndicator'],
|
|
'0x1041': ['DS', '1', 'SliceLocation'],
|
|
'0x1070': ['IS', '1-n', 'OtherStudyNumbers'],
|
|
'0x1200': ['IS', '1', 'NumberOfPatientRelatedStudies'],
|
|
'0x1202': ['IS', '1', 'NumberOfPatientRelatedSeries'],
|
|
'0x1204': ['IS', '1', 'NumberOfPatientRelatedInstances'],
|
|
'0x1206': ['IS', '1', 'NumberOfStudyRelatedSeries'],
|
|
'0x1208': ['IS', '1', 'NumberOfStudyRelatedInstances'],
|
|
'0x1209': ['IS', '1', 'NumberOfSeriesRelatedInstances'],
|
|
'0x3100': ['CS', '1-n', 'SourceImageIDs'],
|
|
'0x3401': ['CS', '1', 'ModifyingDeviceID'],
|
|
'0x3402': ['CS', '1', 'ModifiedImageID'],
|
|
'0x3403': ['DA', '1', 'ModifiedImageDate'],
|
|
'0x3404': ['LO', '1', 'ModifyingDeviceManufacturer'],
|
|
'0x3405': ['TM', '1', 'ModifiedImageTime'],
|
|
'0x3406': ['LO', '1', 'ModifiedImageDescription'],
|
|
'0x4000': ['LT', '1', 'ImageComments'],
|
|
'0x5000': ['AT', '1-n', 'OriginalImageIdentification'],
|
|
'0x5002': ['LO', '1-n', 'OriginalImageIdentificationNomenclature'],
|
|
'0x9056': ['SH', '1', 'StackID'],
|
|
'0x9057': ['UL', '1', 'InStackPositionNumber'],
|
|
'0x9071': ['SQ', '1', 'FrameAnatomySequence'],
|
|
'0x9072': ['CS', '1', 'FrameLaterality'],
|
|
'0x9111': ['SQ', '1', 'FrameContentSequence'],
|
|
'0x9113': ['SQ', '1', 'PlanePositionSequence'],
|
|
'0x9116': ['SQ', '1', 'PlaneOrientationSequence'],
|
|
'0x9128': ['UL', '1', 'TemporalPositionIndex'],
|
|
'0x9153': ['FD', '1', 'NominalCardiacTriggerDelayTime'],
|
|
'0x9154': ['FL', '1', 'NominalCardiacTriggerTimePriorToRPeak'],
|
|
'0x9155': ['FL', '1', 'ActualCardiacTriggerTimePriorToRPeak'],
|
|
'0x9156': ['US', '1', 'FrameAcquisitionNumber'],
|
|
'0x9157': ['UL', '1-n', 'DimensionIndexValues'],
|
|
'0x9158': ['LT', '1', 'FrameComments'],
|
|
'0x9161': ['UI', '1', 'ConcatenationUID'],
|
|
'0x9162': ['US', '1', 'InConcatenationNumber'],
|
|
'0x9163': ['US', '1', 'InConcatenationTotalNumber'],
|
|
'0x9164': ['UI', '1', 'DimensionOrganizationUID'],
|
|
'0x9165': ['AT', '1', 'DimensionIndexPointer'],
|
|
'0x9167': ['AT', '1', 'FunctionalGroupPointer'],
|
|
'0x9170': ['SQ', '1', 'UnassignedSharedConvertedAttributesSequence'],
|
|
'0x9171': ['SQ', '1', 'UnassignedPerFrameConvertedAttributesSequence'],
|
|
'0x9172': ['SQ', '1', 'ConversionSourceAttributesSequence'],
|
|
'0x9213': ['LO', '1', 'DimensionIndexPrivateCreator'],
|
|
'0x9221': ['SQ', '1', 'DimensionOrganizationSequence'],
|
|
'0x9222': ['SQ', '1', 'DimensionIndexSequence'],
|
|
'0x9228': ['UL', '1', 'ConcatenationFrameOffsetNumber'],
|
|
'0x9238': ['LO', '1', 'FunctionalGroupPrivateCreator'],
|
|
'0x9241': ['FL', '1', 'NominalPercentageOfCardiacPhase'],
|
|
'0x9245': ['FL', '1', 'NominalPercentageOfRespiratoryPhase'],
|
|
'0x9246': ['FL', '1', 'StartingRespiratoryAmplitude'],
|
|
'0x9247': ['CS', '1', 'StartingRespiratoryPhase'],
|
|
'0x9248': ['FL', '1', 'EndingRespiratoryAmplitude'],
|
|
'0x9249': ['CS', '1', 'EndingRespiratoryPhase'],
|
|
'0x9250': ['CS', '1', 'RespiratoryTriggerType'],
|
|
'0x9251': ['FD', '1', 'RRIntervalTimeNominal'],
|
|
'0x9252': ['FD', '1', 'ActualCardiacTriggerDelayTime'],
|
|
'0x9253': ['SQ', '1', 'RespiratorySynchronizationSequence'],
|
|
'0x9254': ['FD', '1', 'RespiratoryIntervalTime'],
|
|
'0x9255': ['FD', '1', 'NominalRespiratoryTriggerDelayTime'],
|
|
'0x9256': ['FD', '1', 'RespiratoryTriggerDelayThreshold'],
|
|
'0x9257': ['FD', '1', 'ActualRespiratoryTriggerDelayTime'],
|
|
'0x9301': ['FD', '3', 'ImagePositionVolume'],
|
|
'0x9302': ['FD', '6', 'ImageOrientationVolume'],
|
|
'0x9307': ['CS', '1', 'UltrasoundAcquisitionGeometry'],
|
|
'0x9308': ['FD', '3', 'ApexPosition'],
|
|
'0x9309': ['FD', '16', 'VolumeToTransducerMappingMatrix'],
|
|
'0x930A': ['FD', '16', 'VolumeToTableMappingMatrix'],
|
|
'0x930B': ['CS', '1', 'VolumeToTransducerRelationship'],
|
|
'0x930C': ['CS', '1', 'PatientFrameOfReferenceSource'],
|
|
'0x930D': ['FD', '1', 'TemporalPositionTimeOffset'],
|
|
'0x930E': ['SQ', '1', 'PlanePositionVolumeSequence'],
|
|
'0x930F': ['SQ', '1', 'PlaneOrientationVolumeSequence'],
|
|
'0x9310': ['SQ', '1', 'TemporalPositionSequence'],
|
|
'0x9311': ['CS', '1', 'DimensionOrganizationType'],
|
|
'0x9312': ['UI', '1', 'VolumeFrameOfReferenceUID'],
|
|
'0x9313': ['UI', '1', 'TableFrameOfReferenceUID'],
|
|
'0x9421': ['LO', '1', 'DimensionDescriptionLabel'],
|
|
'0x9450': ['SQ', '1', 'PatientOrientationInFrameSequence'],
|
|
'0x9453': ['LO', '1', 'FrameLabel'],
|
|
'0x9518': ['US', '1-n', 'AcquisitionIndex'],
|
|
'0x9529': ['SQ', '1', 'ContributingSOPInstancesReferenceSequence'],
|
|
'0x9536': ['US', '1', 'ReconstructionIndex']
|
|
},
|
|
'0x0022': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['US', '1', 'LightPathFilterPassThroughWavelength'],
|
|
'0x0002': ['US', '2', 'LightPathFilterPassBand'],
|
|
'0x0003': ['US', '1', 'ImagePathFilterPassThroughWavelength'],
|
|
'0x0004': ['US', '2', 'ImagePathFilterPassBand'],
|
|
'0x0005': ['CS', '1', 'PatientEyeMovementCommanded'],
|
|
'0x0006': ['SQ', '1', 'PatientEyeMovementCommandCodeSequence'],
|
|
'0x0007': ['FL', '1', 'SphericalLensPower'],
|
|
'0x0008': ['FL', '1', 'CylinderLensPower'],
|
|
'0x0009': ['FL', '1', 'CylinderAxis'],
|
|
'0x000A': ['FL', '1', 'EmmetropicMagnification'],
|
|
'0x000B': ['FL', '1', 'IntraOcularPressure'],
|
|
'0x000C': ['FL', '1', 'HorizontalFieldOfView'],
|
|
'0x000D': ['CS', '1', 'PupilDilated'],
|
|
'0x000E': ['FL', '1', 'DegreeOfDilation'],
|
|
'0x0010': ['FL', '1', 'StereoBaselineAngle'],
|
|
'0x0011': ['FL', '1', 'StereoBaselineDisplacement'],
|
|
'0x0012': ['FL', '1', 'StereoHorizontalPixelOffset'],
|
|
'0x0013': ['FL', '1', 'StereoVerticalPixelOffset'],
|
|
'0x0014': ['FL', '1', 'StereoRotation'],
|
|
'0x0015': ['SQ', '1', 'AcquisitionDeviceTypeCodeSequence'],
|
|
'0x0016': ['SQ', '1', 'IlluminationTypeCodeSequence'],
|
|
'0x0017': ['SQ', '1', 'LightPathFilterTypeStackCodeSequence'],
|
|
'0x0018': ['SQ', '1', 'ImagePathFilterTypeStackCodeSequence'],
|
|
'0x0019': ['SQ', '1', 'LensesCodeSequence'],
|
|
'0x001A': ['SQ', '1', 'ChannelDescriptionCodeSequence'],
|
|
'0x001B': ['SQ', '1', 'RefractiveStateSequence'],
|
|
'0x001C': ['SQ', '1', 'MydriaticAgentCodeSequence'],
|
|
'0x001D': ['SQ', '1', 'RelativeImagePositionCodeSequence'],
|
|
'0x001E': ['FL', '1', 'CameraAngleOfView'],
|
|
'0x0020': ['SQ', '1', 'StereoPairsSequence'],
|
|
'0x0021': ['SQ', '1', 'LeftImageSequence'],
|
|
'0x0022': ['SQ', '1', 'RightImageSequence'],
|
|
'0x0028': ['CS', '1', 'StereoPairsPresent'],
|
|
'0x0030': ['FL', '1', 'AxialLengthOfTheEye'],
|
|
'0x0031': ['SQ', '1', 'OphthalmicFrameLocationSequence'],
|
|
'0x0032': ['FL', '2-2n', 'ReferenceCoordinates'],
|
|
'0x0035': ['FL', '1', 'DepthSpatialResolution'],
|
|
'0x0036': ['FL', '1', 'MaximumDepthDistortion'],
|
|
'0x0037': ['FL', '1', 'AlongScanSpatialResolution'],
|
|
'0x0038': ['FL', '1', 'MaximumAlongScanDistortion'],
|
|
'0x0039': ['CS', '1', 'OphthalmicImageOrientation'],
|
|
'0x0041': ['FL', '1', 'DepthOfTransverseImage'],
|
|
'0x0042': ['SQ', '1', 'MydriaticAgentConcentrationUnitsSequence'],
|
|
'0x0048': ['FL', '1', 'AcrossScanSpatialResolution'],
|
|
'0x0049': ['FL', '1', 'MaximumAcrossScanDistortion'],
|
|
'0x004E': ['DS', '1', 'MydriaticAgentConcentration'],
|
|
'0x0055': ['FL', '1', 'IlluminationWaveLength'],
|
|
'0x0056': ['FL', '1', 'IlluminationPower'],
|
|
'0x0057': ['FL', '1', 'IlluminationBandwidth'],
|
|
'0x0058': ['SQ', '1', 'MydriaticAgentSequence'],
|
|
'0x1007': ['SQ', '1', 'OphthalmicAxialMeasurementsRightEyeSequence'],
|
|
'0x1008': ['SQ', '1', 'OphthalmicAxialMeasurementsLeftEyeSequence'],
|
|
'0x1009': ['CS', '1', 'OphthalmicAxialMeasurementsDeviceType'],
|
|
'0x1010': ['CS', '1', 'OphthalmicAxialLengthMeasurementsType'],
|
|
'0x1012': ['SQ', '1', 'OphthalmicAxialLengthSequence'],
|
|
'0x1019': ['FL', '1', 'OphthalmicAxialLength'],
|
|
'0x1024': ['SQ', '1', 'LensStatusCodeSequence'],
|
|
'0x1025': ['SQ', '1', 'VitreousStatusCodeSequence'],
|
|
'0x1028': ['SQ', '1', 'IOLFormulaCodeSequence'],
|
|
'0x1029': ['LO', '1', 'IOLFormulaDetail'],
|
|
'0x1033': ['FL', '1', 'KeratometerIndex'],
|
|
'0x1035': ['SQ', '1', 'SourceOfOphthalmicAxialLengthCodeSequence'],
|
|
'0x1037': ['FL', '1', 'TargetRefraction'],
|
|
'0x1039': ['CS', '1', 'RefractiveProcedureOccurred'],
|
|
'0x1040': ['SQ', '1', 'RefractiveSurgeryTypeCodeSequence'],
|
|
'0x1044': ['SQ', '1', 'OphthalmicUltrasoundMethodCodeSequence'],
|
|
'0x1050': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSequence'],
|
|
'0x1053': ['FL', '1', 'IOLPower'],
|
|
'0x1054': ['FL', '1', 'PredictedRefractiveError'],
|
|
'0x1059': ['FL', '1', 'OphthalmicAxialLengthVelocity'],
|
|
'0x1065': ['LO', '1', 'LensStatusDescription'],
|
|
'0x1066': ['LO', '1', 'VitreousStatusDescription'],
|
|
'0x1090': ['SQ', '1', 'IOLPowerSequence'],
|
|
'0x1092': ['SQ', '1', 'LensConstantSequence'],
|
|
'0x1093': ['LO', '1', 'IOLManufacturer'],
|
|
'0x1094': ['LO', '1', 'LensConstantDescription'],
|
|
'0x1095': ['LO', '1', 'ImplantName'],
|
|
'0x1096': ['SQ', '1', 'KeratometryMeasurementTypeCodeSequence'],
|
|
'0x1097': ['LO', '1', 'ImplantPartNumber'],
|
|
'0x1100': ['SQ', '1', 'ReferencedOphthalmicAxialMeasurementsSequence'],
|
|
'0x1101': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSegmentNameCodeSequence'],
|
|
'0x1103': ['SQ', '1', 'RefractiveErrorBeforeRefractiveSurgeryCodeSequence'],
|
|
'0x1121': ['FL', '1', 'IOLPowerForExactEmmetropia'],
|
|
'0x1122': ['FL', '1', 'IOLPowerForExactTargetRefraction'],
|
|
'0x1125': ['SQ', '1', 'AnteriorChamberDepthDefinitionCodeSequence'],
|
|
'0x1127': ['SQ', '1', 'LensThicknessSequence'],
|
|
'0x1128': ['SQ', '1', 'AnteriorChamberDepthSequence'],
|
|
'0x1130': ['FL', '1', 'LensThickness'],
|
|
'0x1131': ['FL', '1', 'AnteriorChamberDepth'],
|
|
'0x1132': ['SQ', '1', 'SourceOfLensThicknessDataCodeSequence'],
|
|
'0x1133': ['SQ', '1', 'SourceOfAnteriorChamberDepthDataCodeSequence'],
|
|
'0x1134': ['SQ', '1', 'SourceOfRefractiveMeasurementsSequence'],
|
|
'0x1135': ['SQ', '1', 'SourceOfRefractiveMeasurementsCodeSequence'],
|
|
'0x1140': ['CS', '1', 'OphthalmicAxialLengthMeasurementModified'],
|
|
'0x1150': ['SQ', '1', 'OphthalmicAxialLengthDataSourceCodeSequence'],
|
|
'0x1153': ['SQ', '1', 'OphthalmicAxialLengthAcquisitionMethodCodeSequence'],
|
|
'0x1155': ['FL', '1', 'SignalToNoiseRatio'],
|
|
'0x1159': ['LO', '1', 'OphthalmicAxialLengthDataSourceDescription'],
|
|
'0x1210': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsTotalLengthSequence'],
|
|
'0x1211': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsSegmentalLengthSequence'],
|
|
'0x1212': ['SQ', '1', 'OphthalmicAxialLengthMeasurementsLengthSummationSequence'],
|
|
'0x1220': ['SQ', '1', 'UltrasoundOphthalmicAxialLengthMeasurementsSequence'],
|
|
'0x1225': ['SQ', '1', 'OpticalOphthalmicAxialLengthMeasurementsSequence'],
|
|
'0x1230': ['SQ', '1', 'UltrasoundSelectedOphthalmicAxialLengthSequence'],
|
|
'0x1250': ['SQ', '1', 'OphthalmicAxialLengthSelectionMethodCodeSequence'],
|
|
'0x1255': ['SQ', '1', 'OpticalSelectedOphthalmicAxialLengthSequence'],
|
|
'0x1257': ['SQ', '1', 'SelectedSegmentalOphthalmicAxialLengthSequence'],
|
|
'0x1260': ['SQ', '1', 'SelectedTotalOphthalmicAxialLengthSequence'],
|
|
'0x1262': ['SQ', '1', 'OphthalmicAxialLengthQualityMetricSequence'],
|
|
'0x1265': ['SQ', '1', 'OphthalmicAxialLengthQualityMetricTypeCodeSequence'],
|
|
'0x1273': ['LO', '1', 'OphthalmicAxialLengthQualityMetricTypeDescription'],
|
|
'0x1300': ['SQ', '1', 'IntraocularLensCalculationsRightEyeSequence'],
|
|
'0x1310': ['SQ', '1', 'IntraocularLensCalculationsLeftEyeSequence'],
|
|
'0x1330': ['SQ', '1', 'ReferencedOphthalmicAxialLengthMeasurementQCImageSequence'],
|
|
'0x1415': ['CS', '1', 'OphthalmicMappingDeviceType'],
|
|
'0x1420': ['SQ', '1', 'AcquisitionMethodCodeSequence'],
|
|
'0x1423': ['SQ', '1', 'AcquisitionMethodAlgorithmSequence'],
|
|
'0x1436': ['SQ', '1', 'OphthalmicThicknessMapTypeCodeSequence'],
|
|
'0x1443': ['SQ', '1', 'OphthalmicThicknessMappingNormalsSequence'],
|
|
'0x1445': ['SQ', '1', 'RetinalThicknessDefinitionCodeSequence'],
|
|
'0x1450': ['SQ', '1', 'PixelValueMappingToCodedConceptSequence'],
|
|
'0x1452': ['xs', '1', 'MappedPixelValue'],
|
|
'0x1454': ['LO', '1', 'PixelValueMappingExplanation'],
|
|
'0x1458': ['SQ', '1', 'OphthalmicThicknessMapQualityThresholdSequence'],
|
|
'0x1460': ['FL', '1', 'OphthalmicThicknessMapThresholdQualityRating'],
|
|
'0x1463': ['FL', '2', 'AnatomicStructureReferencePoint'],
|
|
'0x1465': ['SQ', '1', 'RegistrationToLocalizerSequence'],
|
|
'0x1466': ['CS', '1', 'RegisteredLocalizerUnits'],
|
|
'0x1467': ['FL', '2', 'RegisteredLocalizerTopLeftHandCorner'],
|
|
'0x1468': ['FL', '2', 'RegisteredLocalizerBottomRightHandCorner'],
|
|
'0x1470': ['SQ', '1', 'OphthalmicThicknessMapQualityRatingSequence'],
|
|
'0x1472': ['SQ', '1', 'RelevantOPTAttributesSequence'],
|
|
'0x1512': ['SQ', '1', 'TransformationMethodCodeSequence'],
|
|
'0x1513': ['SQ', '1', 'TransformationAlgorithmSequence'],
|
|
'0x1515': ['CS', '1', 'OphthalmicAxialLengthMethod'],
|
|
'0x1517': ['FL', '1', 'OphthalmicFOV'],
|
|
'0x1518': ['SQ', '1', 'TwoDimensionalToThreeDimensionalMapSequence'],
|
|
'0x1525': ['SQ', '1', 'WideFieldOphthalmicPhotographyQualityRatingSequence'],
|
|
'0x1526': ['SQ', '1', 'WideFieldOphthalmicPhotographyQualityThresholdSequence'],
|
|
'0x1527': ['FL', '1', 'WideFieldOphthalmicPhotographyThresholdQualityRating'],
|
|
'0x1528': ['FL', '1', 'XCoordinatesCenterPixelViewAngle'],
|
|
'0x1529': ['FL', '1', 'YCoordinatesCenterPixelViewAngle'],
|
|
'0x1530': ['UL', '1', 'NumberOfMapPoints'],
|
|
'0x1531': ['OF', '1', 'TwoDimensionalToThreeDimensionalMapData']
|
|
},
|
|
'0x0024': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['FL', '1', 'VisualFieldHorizontalExtent'],
|
|
'0x0011': ['FL', '1', 'VisualFieldVerticalExtent'],
|
|
'0x0012': ['CS', '1', 'VisualFieldShape'],
|
|
'0x0016': ['SQ', '1', 'ScreeningTestModeCodeSequence'],
|
|
'0x0018': ['FL', '1', 'MaximumStimulusLuminance'],
|
|
'0x0020': ['FL', '1', 'BackgroundLuminance'],
|
|
'0x0021': ['SQ', '1', 'StimulusColorCodeSequence'],
|
|
'0x0024': ['SQ', '1', 'BackgroundIlluminationColorCodeSequence'],
|
|
'0x0025': ['FL', '1', 'StimulusArea'],
|
|
'0x0028': ['FL', '1', 'StimulusPresentationTime'],
|
|
'0x0032': ['SQ', '1', 'FixationSequence'],
|
|
'0x0033': ['SQ', '1', 'FixationMonitoringCodeSequence'],
|
|
'0x0034': ['SQ', '1', 'VisualFieldCatchTrialSequence'],
|
|
'0x0035': ['US', '1', 'FixationCheckedQuantity'],
|
|
'0x0036': ['US', '1', 'PatientNotProperlyFixatedQuantity'],
|
|
'0x0037': ['CS', '1', 'PresentedVisualStimuliDataFlag'],
|
|
'0x0038': ['US', '1', 'NumberOfVisualStimuli'],
|
|
'0x0039': ['CS', '1', 'ExcessiveFixationLossesDataFlag'],
|
|
'0x0040': ['CS', '1', 'ExcessiveFixationLosses'],
|
|
'0x0042': ['US', '1', 'StimuliRetestingQuantity'],
|
|
'0x0044': ['LT', '1', 'CommentsOnPatientPerformanceOfVisualField'],
|
|
'0x0045': ['CS', '1', 'FalseNegativesEstimateFlag'],
|
|
'0x0046': ['FL', '1', 'FalseNegativesEstimate'],
|
|
'0x0048': ['US', '1', 'NegativeCatchTrialsQuantity'],
|
|
'0x0050': ['US', '1', 'FalseNegativesQuantity'],
|
|
'0x0051': ['CS', '1', 'ExcessiveFalseNegativesDataFlag'],
|
|
'0x0052': ['CS', '1', 'ExcessiveFalseNegatives'],
|
|
'0x0053': ['CS', '1', 'FalsePositivesEstimateFlag'],
|
|
'0x0054': ['FL', '1', 'FalsePositivesEstimate'],
|
|
'0x0055': ['CS', '1', 'CatchTrialsDataFlag'],
|
|
'0x0056': ['US', '1', 'PositiveCatchTrialsQuantity'],
|
|
'0x0057': ['CS', '1', 'TestPointNormalsDataFlag'],
|
|
'0x0058': ['SQ', '1', 'TestPointNormalsSequence'],
|
|
'0x0059': ['CS', '1', 'GlobalDeviationProbabilityNormalsFlag'],
|
|
'0x0060': ['US', '1', 'FalsePositivesQuantity'],
|
|
'0x0061': ['CS', '1', 'ExcessiveFalsePositivesDataFlag'],
|
|
'0x0062': ['CS', '1', 'ExcessiveFalsePositives'],
|
|
'0x0063': ['CS', '1', 'VisualFieldTestNormalsFlag'],
|
|
'0x0064': ['SQ', '1', 'ResultsNormalsSequence'],
|
|
'0x0065': ['SQ', '1', 'AgeCorrectedSensitivityDeviationAlgorithmSequence'],
|
|
'0x0066': ['FL', '1', 'GlobalDeviationFromNormal'],
|
|
'0x0067': ['SQ', '1', 'GeneralizedDefectSensitivityDeviationAlgorithmSequence'],
|
|
'0x0068': ['FL', '1', 'LocalizedDeviationFromNormal'],
|
|
'0x0069': ['LO', '1', 'PatientReliabilityIndicator'],
|
|
'0x0070': ['FL', '1', 'VisualFieldMeanSensitivity'],
|
|
'0x0071': ['FL', '1', 'GlobalDeviationProbability'],
|
|
'0x0072': ['CS', '1', 'LocalDeviationProbabilityNormalsFlag'],
|
|
'0x0073': ['FL', '1', 'LocalizedDeviationProbability'],
|
|
'0x0074': ['CS', '1', 'ShortTermFluctuationCalculated'],
|
|
'0x0075': ['FL', '1', 'ShortTermFluctuation'],
|
|
'0x0076': ['CS', '1', 'ShortTermFluctuationProbabilityCalculated'],
|
|
'0x0077': ['FL', '1', 'ShortTermFluctuationProbability'],
|
|
'0x0078': ['CS', '1', 'CorrectedLocalizedDeviationFromNormalCalculated'],
|
|
'0x0079': ['FL', '1', 'CorrectedLocalizedDeviationFromNormal'],
|
|
'0x0080': ['CS', '1', 'CorrectedLocalizedDeviationFromNormalProbabilityCalculated'],
|
|
'0x0081': ['FL', '1', 'CorrectedLocalizedDeviationFromNormalProbability'],
|
|
'0x0083': ['SQ', '1', 'GlobalDeviationProbabilitySequence'],
|
|
'0x0085': ['SQ', '1', 'LocalizedDeviationProbabilitySequence'],
|
|
'0x0086': ['CS', '1', 'FovealSensitivityMeasured'],
|
|
'0x0087': ['FL', '1', 'FovealSensitivity'],
|
|
'0x0088': ['FL', '1', 'VisualFieldTestDuration'],
|
|
'0x0089': ['SQ', '1', 'VisualFieldTestPointSequence'],
|
|
'0x0090': ['FL', '1', 'VisualFieldTestPointXCoordinate'],
|
|
'0x0091': ['FL', '1', 'VisualFieldTestPointYCoordinate'],
|
|
'0x0092': ['FL', '1', 'AgeCorrectedSensitivityDeviationValue'],
|
|
'0x0093': ['CS', '1', 'StimulusResults'],
|
|
'0x0094': ['FL', '1', 'SensitivityValue'],
|
|
'0x0095': ['CS', '1', 'RetestStimulusSeen'],
|
|
'0x0096': ['FL', '1', 'RetestSensitivityValue'],
|
|
'0x0097': ['SQ', '1', 'VisualFieldTestPointNormalsSequence'],
|
|
'0x0098': ['FL', '1', 'QuantifiedDefect'],
|
|
'0x0100': ['FL', '1', 'AgeCorrectedSensitivityDeviationProbabilityValue'],
|
|
'0x0102': ['CS', '1', 'GeneralizedDefectCorrectedSensitivityDeviationFlag'],
|
|
'0x0103': ['FL', '1', 'GeneralizedDefectCorrectedSensitivityDeviationValue'],
|
|
'0x0104': ['FL', '1', 'GeneralizedDefectCorrectedSensitivityDeviationProbabilityValue'],
|
|
'0x0105': ['FL', '1', 'MinimumSensitivityValue'],
|
|
'0x0106': ['CS', '1', 'BlindSpotLocalized'],
|
|
'0x0107': ['FL', '1', 'BlindSpotXCoordinate'],
|
|
'0x0108': ['FL', '1', 'BlindSpotYCoordinate'],
|
|
'0x0110': ['SQ', '1', 'VisualAcuityMeasurementSequence'],
|
|
'0x0112': ['SQ', '1', 'RefractiveParametersUsedOnPatientSequence'],
|
|
'0x0113': ['CS', '1', 'MeasurementLaterality'],
|
|
'0x0114': ['SQ', '1', 'OphthalmicPatientClinicalInformationLeftEyeSequence'],
|
|
'0x0115': ['SQ', '1', 'OphthalmicPatientClinicalInformationRightEyeSequence'],
|
|
'0x0117': ['CS', '1', 'FovealPointNormativeDataFlag'],
|
|
'0x0118': ['FL', '1', 'FovealPointProbabilityValue'],
|
|
'0x0120': ['CS', '1', 'ScreeningBaselineMeasured'],
|
|
'0x0122': ['SQ', '1', 'ScreeningBaselineMeasuredSequence'],
|
|
'0x0124': ['CS', '1', 'ScreeningBaselineType'],
|
|
'0x0126': ['FL', '1', 'ScreeningBaselineValue'],
|
|
'0x0202': ['LO', '1', 'AlgorithmSource'],
|
|
'0x0306': ['LO', '1', 'DataSetName'],
|
|
'0x0307': ['LO', '1', 'DataSetVersion'],
|
|
'0x0308': ['LO', '1', 'DataSetSource'],
|
|
'0x0309': ['LO', '1', 'DataSetDescription'],
|
|
'0x0317': ['SQ', '1', 'VisualFieldTestReliabilityGlobalIndexSequence'],
|
|
'0x0320': ['SQ', '1', 'VisualFieldGlobalResultsIndexSequence'],
|
|
'0x0325': ['SQ', '1', 'DataObservationSequence'],
|
|
'0x0338': ['CS', '1', 'IndexNormalsFlag'],
|
|
'0x0341': ['FL', '1', 'IndexProbability'],
|
|
'0x0344': ['SQ', '1', 'IndexProbabilitySequence']
|
|
},
|
|
'0x0028': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['US', '1', 'SamplesPerPixel'],
|
|
'0x0003': ['US', '1', 'SamplesPerPixelUsed'],
|
|
'0x0004': ['CS', '1', 'PhotometricInterpretation'],
|
|
'0x0005': ['US', '1', 'ImageDimensions'],
|
|
'0x0006': ['US', '1', 'PlanarConfiguration'],
|
|
'0x0008': ['IS', '1', 'NumberOfFrames'],
|
|
'0x0009': ['AT', '1-n', 'FrameIncrementPointer'],
|
|
'0x000A': ['AT', '1-n', 'FrameDimensionPointer'],
|
|
'0x0010': ['US', '1', 'Rows'],
|
|
'0x0011': ['US', '1', 'Columns'],
|
|
'0x0012': ['US', '1', 'Planes'],
|
|
'0x0014': ['US', '1', 'UltrasoundColorDataPresent'],
|
|
'0x0020': ['', '', ''],
|
|
'0x0030': ['DS', '2', 'PixelSpacing'],
|
|
'0x0031': ['DS', '2', 'ZoomFactor'],
|
|
'0x0032': ['DS', '2', 'ZoomCenter'],
|
|
'0x0034': ['IS', '2', 'PixelAspectRatio'],
|
|
'0x0040': ['CS', '1', 'ImageFormat'],
|
|
'0x0050': ['LO', '1-n', 'ManipulatedImage'],
|
|
'0x0051': ['CS', '1-n', 'CorrectedImage'],
|
|
'0x005F': ['LO', '1', 'CompressionRecognitionCode'],
|
|
'0x0060': ['CS', '1', 'CompressionCode'],
|
|
'0x0061': ['SH', '1', 'CompressionOriginator'],
|
|
'0x0062': ['LO', '1', 'CompressionLabel'],
|
|
'0x0063': ['SH', '1', 'CompressionDescription'],
|
|
'0x0065': ['CS', '1-n', 'CompressionSequence'],
|
|
'0x0066': ['AT', '1-n', 'CompressionStepPointers'],
|
|
'0x0068': ['US', '1', 'RepeatInterval'],
|
|
'0x0069': ['US', '1', 'BitsGrouped'],
|
|
'0x0070': ['US', '1-n', 'PerimeterTable'],
|
|
'0x0071': ['xs', '1', 'PerimeterValue'],
|
|
'0x0080': ['US', '1', 'PredictorRows'],
|
|
'0x0081': ['US', '1', 'PredictorColumns'],
|
|
'0x0082': ['US', '1-n', 'PredictorConstants'],
|
|
'0x0090': ['CS', '1', 'BlockedPixels'],
|
|
'0x0091': ['US', '1', 'BlockRows'],
|
|
'0x0092': ['US', '1', 'BlockColumns'],
|
|
'0x0093': ['US', '1', 'RowOverlap'],
|
|
'0x0094': ['US', '1', 'ColumnOverlap'],
|
|
'0x0100': ['US', '1', 'BitsAllocated'],
|
|
'0x0101': ['US', '1', 'BitsStored'],
|
|
'0x0102': ['US', '1', 'HighBit'],
|
|
'0x0103': ['US', '1', 'PixelRepresentation'],
|
|
'0x0104': ['xs', '1', 'SmallestValidPixelValue'],
|
|
'0x0105': ['xs', '1', 'LargestValidPixelValue'],
|
|
'0x0106': ['xs', '1', 'SmallestImagePixelValue'],
|
|
'0x0107': ['xs', '1', 'LargestImagePixelValue'],
|
|
'0x0108': ['xs', '1', 'SmallestPixelValueInSeries'],
|
|
'0x0109': ['xs', '1', 'LargestPixelValueInSeries'],
|
|
'0x0110': ['xs', '1', 'SmallestImagePixelValueInPlane'],
|
|
'0x0111': ['xs', '1', 'LargestImagePixelValueInPlane'],
|
|
'0x0120': ['xs', '1', 'PixelPaddingValue'],
|
|
'0x0121': ['xs', '1', 'PixelPaddingRangeLimit'],
|
|
'0x0122': ['FL', '1', 'FloatPixelPaddingValue'],
|
|
'0x0123': ['FD', '1', 'DoubleFloatPixelPaddingValue'],
|
|
'0x0124': ['FL', '1', 'FloatPixelPaddingRangeLimit'],
|
|
'0x0125': ['FD', '1', 'DoubleFloatPixelPaddingRangeLimit'],
|
|
'0x0200': ['US', '1', 'ImageLocation'],
|
|
'0x0300': ['CS', '1', 'QualityControlImage'],
|
|
'0x0301': ['CS', '1', 'BurnedInAnnotation'],
|
|
'0x0302': ['CS', '1', 'RecognizableVisualFeatures'],
|
|
'0x0303': ['CS', '1', 'LongitudinalTemporalInformationModified'],
|
|
'0x0304': ['UI', '1', 'ReferencedColorPaletteInstanceUID'],
|
|
'0x0400': ['LO', '1', 'TransformLabel'],
|
|
'0x0401': ['LO', '1', 'TransformVersionNumber'],
|
|
'0x0402': ['US', '1', 'NumberOfTransformSteps'],
|
|
'0x0403': ['LO', '1-n', 'SequenceOfCompressedData'],
|
|
'0x0404': ['AT', '1-n', 'DetailsOfCoefficients'],
|
|
'0x04x0': ['US', '1', 'RowsForNthOrderCoefficients'],
|
|
'0x04x1': ['US', '1', 'ColumnsForNthOrderCoefficients'],
|
|
'0x04x2': ['LO', '1-n', 'CoefficientCoding'],
|
|
'0x04x3': ['AT', '1-n', 'CoefficientCodingPointers'],
|
|
'0x0700': ['LO', '1', 'DCTLabel'],
|
|
'0x0701': ['CS', '1-n', 'DataBlockDescription'],
|
|
'0x0702': ['AT', '1-n', 'DataBlock'],
|
|
'0x0710': ['US', '1', 'NormalizationFactorFormat'],
|
|
'0x0720': ['US', '1', 'ZonalMapNumberFormat'],
|
|
'0x0721': ['AT', '1-n', 'ZonalMapLocation'],
|
|
'0x0722': ['US', '1', 'ZonalMapFormat'],
|
|
'0x0730': ['US', '1', 'AdaptiveMapFormat'],
|
|
'0x0740': ['US', '1', 'CodeNumberFormat'],
|
|
'0x08x0': ['CS', '1-n', 'CodeLabel'],
|
|
'0x08x2': ['US', '1', 'NumberOfTables'],
|
|
'0x08x3': ['AT', '1-n', 'CodeTableLocation'],
|
|
'0x08x4': ['US', '1', 'BitsForCodeWord'],
|
|
'0x08x8': ['AT', '1-n', 'ImageDataLocation'],
|
|
'0x0A02': ['CS', '1', 'PixelSpacingCalibrationType'],
|
|
'0x0A04': ['LO', '1', 'PixelSpacingCalibrationDescription'],
|
|
'0x1040': ['CS', '1', 'PixelIntensityRelationship'],
|
|
'0x1041': ['SS', '1', 'PixelIntensityRelationshipSign'],
|
|
'0x1050': ['DS', '1-n', 'WindowCenter'],
|
|
'0x1051': ['DS', '1-n', 'WindowWidth'],
|
|
'0x1052': ['DS', '1', 'RescaleIntercept'],
|
|
'0x1053': ['DS', '1', 'RescaleSlope'],
|
|
'0x1054': ['LO', '1', 'RescaleType'],
|
|
'0x1055': ['LO', '1-n', 'WindowCenterWidthExplanation'],
|
|
'0x1056': ['CS', '1', 'VOILUTFunction'],
|
|
'0x1080': ['CS', '1', 'GrayScale'],
|
|
'0x1090': ['CS', '1', 'RecommendedViewingMode'],
|
|
'0x1100': ['xs', '3', 'GrayLookupTableDescriptor'],
|
|
'0x1101': ['xs', '3', 'RedPaletteColorLookupTableDescriptor'],
|
|
'0x1102': ['xs', '3', 'GreenPaletteColorLookupTableDescriptor'],
|
|
'0x1103': ['xs', '3', 'BluePaletteColorLookupTableDescriptor'],
|
|
'0x1104': ['US', '3', 'AlphaPaletteColorLookupTableDescriptor'],
|
|
'0x1111': ['xs', '4', 'LargeRedPaletteColorLookupTableDescriptor'],
|
|
'0x1112': ['xs', '4', 'LargeGreenPaletteColorLookupTableDescriptor'],
|
|
'0x1113': ['xs', '4', 'LargeBluePaletteColorLookupTableDescriptor'],
|
|
'0x1199': ['UI', '1', 'PaletteColorLookupTableUID'],
|
|
'0x1200': ['US or SS or OW', '1-n or 1', 'GrayLookupTableData'],
|
|
'0x1201': ['OW', '1', 'RedPaletteColorLookupTableData'],
|
|
'0x1202': ['OW', '1', 'GreenPaletteColorLookupTableData'],
|
|
'0x1203': ['OW', '1', 'BluePaletteColorLookupTableData'],
|
|
'0x1204': ['OW', '1', 'AlphaPaletteColorLookupTableData'],
|
|
'0x1211': ['OW', '1', 'LargeRedPaletteColorLookupTableData'],
|
|
'0x1212': ['OW', '1', 'LargeGreenPaletteColorLookupTableData'],
|
|
'0x1213': ['OW', '1', 'LargeBluePaletteColorLookupTableData'],
|
|
'0x1214': ['UI', '1', 'LargePaletteColorLookupTableUID'],
|
|
'0x1221': ['OW', '1', 'SegmentedRedPaletteColorLookupTableData'],
|
|
'0x1222': ['OW', '1', 'SegmentedGreenPaletteColorLookupTableData'],
|
|
'0x1223': ['OW', '1', 'SegmentedBluePaletteColorLookupTableData'],
|
|
'0x1300': ['CS', '1', 'BreastImplantPresent'],
|
|
'0x1350': ['CS', '1', 'PartialView'],
|
|
'0x1351': ['ST', '1', 'PartialViewDescription'],
|
|
'0x1352': ['SQ', '1', 'PartialViewCodeSequence'],
|
|
'0x135A': ['CS', '1', 'SpatialLocationsPreserved'],
|
|
'0x1401': ['SQ', '1', 'DataFrameAssignmentSequence'],
|
|
'0x1402': ['CS', '1', 'DataPathAssignment'],
|
|
'0x1403': ['US', '1', 'BitsMappedToColorLookupTable'],
|
|
'0x1404': ['SQ', '1', 'BlendingLUT1Sequence'],
|
|
'0x1405': ['CS', '1', 'BlendingLUT1TransferFunction'],
|
|
'0x1406': ['FD', '1', 'BlendingWeightConstant'],
|
|
'0x1407': ['US', '3', 'BlendingLookupTableDescriptor'],
|
|
'0x1408': ['OW', '1', 'BlendingLookupTableData'],
|
|
'0x140B': ['SQ', '1', 'EnhancedPaletteColorLookupTableSequence'],
|
|
'0x140C': ['SQ', '1', 'BlendingLUT2Sequence'],
|
|
'0x140D': ['CS', '1', 'BlendingLUT2TransferFunction'],
|
|
'0x140E': ['CS', '1', 'DataPathID'],
|
|
'0x140F': ['CS', '1', 'RGBLUTTransferFunction'],
|
|
'0x1410': ['CS', '1', 'AlphaLUTTransferFunction'],
|
|
'0x2000': ['OB', '1', 'ICCProfile'],
|
|
'0x2110': ['CS', '1', 'LossyImageCompression'],
|
|
'0x2112': ['DS', '1-n', 'LossyImageCompressionRatio'],
|
|
'0x2114': ['CS', '1-n', 'LossyImageCompressionMethod'],
|
|
'0x3000': ['SQ', '1', 'ModalityLUTSequence'],
|
|
'0x3002': ['xs', '3', 'LUTDescriptor'],
|
|
'0x3003': ['LO', '1', 'LUTExplanation'],
|
|
'0x3004': ['LO', '1', 'ModalityLUTType'],
|
|
'0x3006': ['US or OW', '1-n or 1', 'LUTData'],
|
|
'0x3010': ['SQ', '1', 'VOILUTSequence'],
|
|
'0x3110': ['SQ', '1', 'SoftcopyVOILUTSequence'],
|
|
'0x4000': ['LT', '1', 'ImagePresentationComments'],
|
|
'0x5000': ['SQ', '1', 'BiPlaneAcquisitionSequence'],
|
|
'0x6010': ['US', '1', 'RepresentativeFrameNumber'],
|
|
'0x6020': ['US', '1-n', 'FrameNumbersOfInterest'],
|
|
'0x6022': ['LO', '1-n', 'FrameOfInterestDescription'],
|
|
'0x6023': ['CS', '1-n', 'FrameOfInterestType'],
|
|
'0x6030': ['US', '1-n', 'MaskPointers'],
|
|
'0x6040': ['US', '1-n', 'RWavePointer'],
|
|
'0x6100': ['SQ', '1', 'MaskSubtractionSequence'],
|
|
'0x6101': ['CS', '1', 'MaskOperation'],
|
|
'0x6102': ['US', '2-2n', 'ApplicableFrameRange'],
|
|
'0x6110': ['US', '1-n', 'MaskFrameNumbers'],
|
|
'0x6112': ['US', '1', 'ContrastFrameAveraging'],
|
|
'0x6114': ['FL', '2', 'MaskSubPixelShift'],
|
|
'0x6120': ['SS', '1', 'TIDOffset'],
|
|
'0x6190': ['ST', '1', 'MaskOperationExplanation'],
|
|
'0x7000': ['SQ', '1', 'EquipmentAdministratorSequence'],
|
|
'0x7001': ['US', '1', 'NumberOfDisplaySubsystems'],
|
|
'0x7002': ['US', '1', 'CurrentConfigurationID'],
|
|
'0x7003': ['US', '1', 'DisplaySubsystemID'],
|
|
'0x7004': ['SH', '1', 'DisplaySubsystemName'],
|
|
'0x7005': ['LO', '1', 'DisplaySubsystemDescription'],
|
|
'0x7006': ['CS', '1', 'SystemStatus'],
|
|
'0x7007': ['LO', '1', 'SystemStatusComment'],
|
|
'0x7008': ['SQ', '1', 'TargetLuminanceCharacteristicsSequence'],
|
|
'0x7009': ['US', '1', 'LuminanceCharacteristicsID'],
|
|
'0x700A': ['SQ', '1', 'DisplaySubsystemConfigurationSequence'],
|
|
'0x700B': ['US', '1', 'ConfigurationID'],
|
|
'0x700C': ['SH', '1', 'ConfigurationName'],
|
|
'0x700D': ['LO', '1', 'ConfigurationDescription'],
|
|
'0x700E': ['US', '1', 'ReferencedTargetLuminanceCharacteristicsID'],
|
|
'0x700F': ['SQ', '1', 'QAResultsSequence'],
|
|
'0x7010': ['SQ', '1', 'DisplaySubsystemQAResultsSequence'],
|
|
'0x7011': ['SQ', '1', 'ConfigurationQAResultsSequence'],
|
|
'0x7012': ['SQ', '1', 'MeasurementEquipmentSequence'],
|
|
'0x7013': ['CS', '1-n', 'MeasurementFunctions'],
|
|
'0x7014': ['CS', '1', 'MeasurementEquipmentType'],
|
|
'0x7015': ['SQ', '1', 'VisualEvaluationResultSequence'],
|
|
'0x7016': ['SQ', '1', 'DisplayCalibrationResultSequence'],
|
|
'0x7017': ['US', '1', 'DDLValue'],
|
|
'0x7018': ['FL', '2', 'CIExyWhitePoint'],
|
|
'0x7019': ['CS', '1', 'DisplayFunctionType'],
|
|
'0x701A': ['FL', '1', 'GammaValue'],
|
|
'0x701B': ['US', '1', 'NumberOfLuminancePoints'],
|
|
'0x701C': ['SQ', '1', 'LuminanceResponseSequence'],
|
|
'0x701D': ['FL', '1', 'TargetMinimumLuminance'],
|
|
'0x701E': ['FL', '1', 'TargetMaximumLuminance'],
|
|
'0x701F': ['FL', '1', 'LuminanceValue'],
|
|
'0x7020': ['LO', '1', 'LuminanceResponseDescription'],
|
|
'0x7021': ['CS', '1', 'WhitePointFlag'],
|
|
'0x7022': ['SQ', '1', 'DisplayDeviceTypeCodeSequence'],
|
|
'0x7023': ['SQ', '1', 'DisplaySubsystemSequence'],
|
|
'0x7024': ['SQ', '1', 'LuminanceResultSequence'],
|
|
'0x7025': ['CS', '1', 'AmbientLightValueSource'],
|
|
'0x7026': ['CS', '1-n', 'MeasuredCharacteristics'],
|
|
'0x7027': ['SQ', '1', 'LuminanceUniformityResultSequence'],
|
|
'0x7028': ['SQ', '1', 'VisualEvaluationTestSequence'],
|
|
'0x7029': ['CS', '1', 'TestResult'],
|
|
'0x702A': ['LO', '1', 'TestResultComment'],
|
|
'0x702B': ['CS', '1', 'TestImageValidation'],
|
|
'0x702C': ['SQ', '1', 'TestPatternCodeSequence'],
|
|
'0x702D': ['SQ', '1', 'MeasurementPatternCodeSequence'],
|
|
'0x702E': ['SQ', '1', 'VisualEvaluationMethodCodeSequence'],
|
|
'0x7FE0': ['UR', '1', 'PixelDataProviderURL'],
|
|
'0x9001': ['UL', '1', 'DataPointRows'],
|
|
'0x9002': ['UL', '1', 'DataPointColumns'],
|
|
'0x9003': ['CS', '1', 'SignalDomainColumns'],
|
|
'0x9099': ['US', '1', 'LargestMonochromePixelValue'],
|
|
'0x9108': ['CS', '1', 'DataRepresentation'],
|
|
'0x9110': ['SQ', '1', 'PixelMeasuresSequence'],
|
|
'0x9132': ['SQ', '1', 'FrameVOILUTSequence'],
|
|
'0x9145': ['SQ', '1', 'PixelValueTransformationSequence'],
|
|
'0x9235': ['CS', '1', 'SignalDomainRows'],
|
|
'0x9411': ['FL', '1', 'DisplayFilterPercentage'],
|
|
'0x9415': ['SQ', '1', 'FramePixelShiftSequence'],
|
|
'0x9416': ['US', '1', 'SubtractionItemID'],
|
|
'0x9422': ['SQ', '1', 'PixelIntensityRelationshipLUTSequence'],
|
|
'0x9443': ['SQ', '1', 'FramePixelDataPropertiesSequence'],
|
|
'0x9444': ['CS', '1', 'GeometricalProperties'],
|
|
'0x9445': ['FL', '1', 'GeometricMaximumDistortion'],
|
|
'0x9446': ['CS', '1-n', 'ImageProcessingApplied'],
|
|
'0x9454': ['CS', '1', 'MaskSelectionMode'],
|
|
'0x9474': ['CS', '1', 'LUTFunction'],
|
|
'0x9478': ['FL', '1', 'MaskVisibilityPercentage'],
|
|
'0x9501': ['SQ', '1', 'PixelShiftSequence'],
|
|
'0x9502': ['SQ', '1', 'RegionPixelShiftSequence'],
|
|
'0x9503': ['SS', '2-2n', 'VerticesOfTheRegion'],
|
|
'0x9505': ['SQ', '1', 'MultiFramePresentationSequence'],
|
|
'0x9506': ['US', '2-2n', 'PixelShiftFrameRange'],
|
|
'0x9507': ['US', '2-2n', 'LUTFrameRange'],
|
|
'0x9520': ['DS', '16', 'ImageToEquipmentMappingMatrix'],
|
|
'0x9537': ['CS', '1', 'EquipmentCoordinateSystemIdentification']
|
|
},
|
|
'0x0032': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x000A': ['CS', '1', 'StudyStatusID'],
|
|
'0x000C': ['CS', '1', 'StudyPriorityID'],
|
|
'0x0012': ['LO', '1', 'StudyIDIssuer'],
|
|
'0x0032': ['DA', '1', 'StudyVerifiedDate'],
|
|
'0x0033': ['TM', '1', 'StudyVerifiedTime'],
|
|
'0x0034': ['DA', '1', 'StudyReadDate'],
|
|
'0x0035': ['TM', '1', 'StudyReadTime'],
|
|
'0x1000': ['DA', '1', 'ScheduledStudyStartDate'],
|
|
'0x1001': ['TM', '1', 'ScheduledStudyStartTime'],
|
|
'0x1010': ['DA', '1', 'ScheduledStudyStopDate'],
|
|
'0x1011': ['TM', '1', 'ScheduledStudyStopTime'],
|
|
'0x1020': ['LO', '1', 'ScheduledStudyLocation'],
|
|
'0x1021': ['AE', '1-n', 'ScheduledStudyLocationAETitle'],
|
|
'0x1030': ['LO', '1', 'ReasonForStudy'],
|
|
'0x1031': ['SQ', '1', 'RequestingPhysicianIdentificationSequence'],
|
|
'0x1032': ['PN', '1', 'RequestingPhysician'],
|
|
'0x1033': ['LO', '1', 'RequestingService'],
|
|
'0x1034': ['SQ', '1', 'RequestingServiceCodeSequence'],
|
|
'0x1040': ['DA', '1', 'StudyArrivalDate'],
|
|
'0x1041': ['TM', '1', 'StudyArrivalTime'],
|
|
'0x1050': ['DA', '1', 'StudyCompletionDate'],
|
|
'0x1051': ['TM', '1', 'StudyCompletionTime'],
|
|
'0x1055': ['CS', '1', 'StudyComponentStatusID'],
|
|
'0x1060': ['LO', '1', 'RequestedProcedureDescription'],
|
|
'0x1064': ['SQ', '1', 'RequestedProcedureCodeSequence'],
|
|
'0x1070': ['LO', '1', 'RequestedContrastAgent'],
|
|
'0x4000': ['LT', '1', 'StudyComments']
|
|
},
|
|
'0x0038': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0004': ['SQ', '1', 'ReferencedPatientAliasSequence'],
|
|
'0x0008': ['CS', '1', 'VisitStatusID'],
|
|
'0x0010': ['LO', '1', 'AdmissionID'],
|
|
'0x0011': ['LO', '1', 'IssuerOfAdmissionID'],
|
|
'0x0014': ['SQ', '1', 'IssuerOfAdmissionIDSequence'],
|
|
'0x0016': ['LO', '1', 'RouteOfAdmissions'],
|
|
'0x001A': ['DA', '1', 'ScheduledAdmissionDate'],
|
|
'0x001B': ['TM', '1', 'ScheduledAdmissionTime'],
|
|
'0x001C': ['DA', '1', 'ScheduledDischargeDate'],
|
|
'0x001D': ['TM', '1', 'ScheduledDischargeTime'],
|
|
'0x001E': ['LO', '1', 'ScheduledPatientInstitutionResidence'],
|
|
'0x0020': ['DA', '1', 'AdmittingDate'],
|
|
'0x0021': ['TM', '1', 'AdmittingTime'],
|
|
'0x0030': ['DA', '1', 'DischargeDate'],
|
|
'0x0032': ['TM', '1', 'DischargeTime'],
|
|
'0x0040': ['LO', '1', 'DischargeDiagnosisDescription'],
|
|
'0x0044': ['SQ', '1', 'DischargeDiagnosisCodeSequence'],
|
|
'0x0050': ['LO', '1', 'SpecialNeeds'],
|
|
'0x0060': ['LO', '1', 'ServiceEpisodeID'],
|
|
'0x0061': ['LO', '1', 'IssuerOfServiceEpisodeID'],
|
|
'0x0062': ['LO', '1', 'ServiceEpisodeDescription'],
|
|
'0x0064': ['SQ', '1', 'IssuerOfServiceEpisodeIDSequence'],
|
|
'0x0100': ['SQ', '1', 'PertinentDocumentsSequence'],
|
|
'0x0101': ['SQ', '1', 'PertinentResourcesSequence'],
|
|
'0x0102': ['LO', '1', 'ResourceDescription'],
|
|
'0x0300': ['LO', '1', 'CurrentPatientLocation'],
|
|
'0x0400': ['LO', '1', 'PatientInstitutionResidence'],
|
|
'0x0500': ['LO', '1', 'PatientState'],
|
|
'0x0502': ['SQ', '1', 'PatientClinicalTrialParticipationSequence'],
|
|
'0x4000': ['LT', '1', 'VisitComments']
|
|
},
|
|
'0x003A': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0004': ['CS', '1', 'WaveformOriginality'],
|
|
'0x0005': ['US', '1', 'NumberOfWaveformChannels'],
|
|
'0x0010': ['UL', '1', 'NumberOfWaveformSamples'],
|
|
'0x001A': ['DS', '1', 'SamplingFrequency'],
|
|
'0x0020': ['SH', '1', 'MultiplexGroupLabel'],
|
|
'0x0200': ['SQ', '1', 'ChannelDefinitionSequence'],
|
|
'0x0202': ['IS', '1', 'WaveformChannelNumber'],
|
|
'0x0203': ['SH', '1', 'ChannelLabel'],
|
|
'0x0205': ['CS', '1-n', 'ChannelStatus'],
|
|
'0x0208': ['SQ', '1', 'ChannelSourceSequence'],
|
|
'0x0209': ['SQ', '1', 'ChannelSourceModifiersSequence'],
|
|
'0x020A': ['SQ', '1', 'SourceWaveformSequence'],
|
|
'0x020C': ['LO', '1', 'ChannelDerivationDescription'],
|
|
'0x0210': ['DS', '1', 'ChannelSensitivity'],
|
|
'0x0211': ['SQ', '1', 'ChannelSensitivityUnitsSequence'],
|
|
'0x0212': ['DS', '1', 'ChannelSensitivityCorrectionFactor'],
|
|
'0x0213': ['DS', '1', 'ChannelBaseline'],
|
|
'0x0214': ['DS', '1', 'ChannelTimeSkew'],
|
|
'0x0215': ['DS', '1', 'ChannelSampleSkew'],
|
|
'0x0218': ['DS', '1', 'ChannelOffset'],
|
|
'0x021A': ['US', '1', 'WaveformBitsStored'],
|
|
'0x0220': ['DS', '1', 'FilterLowFrequency'],
|
|
'0x0221': ['DS', '1', 'FilterHighFrequency'],
|
|
'0x0222': ['DS', '1', 'NotchFilterFrequency'],
|
|
'0x0223': ['DS', '1', 'NotchFilterBandwidth'],
|
|
'0x0230': ['FL', '1', 'WaveformDataDisplayScale'],
|
|
'0x0231': ['US', '3', 'WaveformDisplayBackgroundCIELabValue'],
|
|
'0x0240': ['SQ', '1', 'WaveformPresentationGroupSequence'],
|
|
'0x0241': ['US', '1', 'PresentationGroupNumber'],
|
|
'0x0242': ['SQ', '1', 'ChannelDisplaySequence'],
|
|
'0x0244': ['US', '3', 'ChannelRecommendedDisplayCIELabValue'],
|
|
'0x0245': ['FL', '1', 'ChannelPosition'],
|
|
'0x0246': ['CS', '1', 'DisplayShadingFlag'],
|
|
'0x0247': ['FL', '1', 'FractionalChannelDisplayScale'],
|
|
'0x0248': ['FL', '1', 'AbsoluteChannelDisplayScale'],
|
|
'0x0300': ['SQ', '1', 'MultiplexedAudioChannelsDescriptionCodeSequence'],
|
|
'0x0301': ['IS', '1', 'ChannelIdentificationCode'],
|
|
'0x0302': ['CS', '1', 'ChannelMode']
|
|
},
|
|
'0x0040': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['AE', '1-n', 'ScheduledStationAETitle'],
|
|
'0x0002': ['DA', '1', 'ScheduledProcedureStepStartDate'],
|
|
'0x0003': ['TM', '1', 'ScheduledProcedureStepStartTime'],
|
|
'0x0004': ['DA', '1', 'ScheduledProcedureStepEndDate'],
|
|
'0x0005': ['TM', '1', 'ScheduledProcedureStepEndTime'],
|
|
'0x0006': ['PN', '1', 'ScheduledPerformingPhysicianName'],
|
|
'0x0007': ['LO', '1', 'ScheduledProcedureStepDescription'],
|
|
'0x0008': ['SQ', '1', 'ScheduledProtocolCodeSequence'],
|
|
'0x0009': ['SH', '1', 'ScheduledProcedureStepID'],
|
|
'0x000A': ['SQ', '1', 'StageCodeSequence'],
|
|
'0x000B': ['SQ', '1', 'ScheduledPerformingPhysicianIdentificationSequence'],
|
|
'0x0010': ['SH', '1-n', 'ScheduledStationName'],
|
|
'0x0011': ['SH', '1', 'ScheduledProcedureStepLocation'],
|
|
'0x0012': ['LO', '1', 'PreMedication'],
|
|
'0x0020': ['CS', '1', 'ScheduledProcedureStepStatus'],
|
|
'0x0026': ['SQ', '1', 'OrderPlacerIdentifierSequence'],
|
|
'0x0027': ['SQ', '1', 'OrderFillerIdentifierSequence'],
|
|
'0x0031': ['UT', '1', 'LocalNamespaceEntityID'],
|
|
'0x0032': ['UT', '1', 'UniversalEntityID'],
|
|
'0x0033': ['CS', '1', 'UniversalEntityIDType'],
|
|
'0x0035': ['CS', '1', 'IdentifierTypeCode'],
|
|
'0x0036': ['SQ', '1', 'AssigningFacilitySequence'],
|
|
'0x0039': ['SQ', '1', 'AssigningJurisdictionCodeSequence'],
|
|
'0x003A': ['SQ', '1', 'AssigningAgencyOrDepartmentCodeSequence'],
|
|
'0x0100': ['SQ', '1', 'ScheduledProcedureStepSequence'],
|
|
'0x0220': ['SQ', '1', 'ReferencedNonImageCompositeSOPInstanceSequence'],
|
|
'0x0241': ['AE', '1', 'PerformedStationAETitle'],
|
|
'0x0242': ['SH', '1', 'PerformedStationName'],
|
|
'0x0243': ['SH', '1', 'PerformedLocation'],
|
|
'0x0244': ['DA', '1', 'PerformedProcedureStepStartDate'],
|
|
'0x0245': ['TM', '1', 'PerformedProcedureStepStartTime'],
|
|
'0x0250': ['DA', '1', 'PerformedProcedureStepEndDate'],
|
|
'0x0251': ['TM', '1', 'PerformedProcedureStepEndTime'],
|
|
'0x0252': ['CS', '1', 'PerformedProcedureStepStatus'],
|
|
'0x0253': ['SH', '1', 'PerformedProcedureStepID'],
|
|
'0x0254': ['LO', '1', 'PerformedProcedureStepDescription'],
|
|
'0x0255': ['LO', '1', 'PerformedProcedureTypeDescription'],
|
|
'0x0260': ['SQ', '1', 'PerformedProtocolCodeSequence'],
|
|
'0x0261': ['CS', '1', 'PerformedProtocolType'],
|
|
'0x0270': ['SQ', '1', 'ScheduledStepAttributesSequence'],
|
|
'0x0275': ['SQ', '1', 'RequestAttributesSequence'],
|
|
'0x0280': ['ST', '1', 'CommentsOnThePerformedProcedureStep'],
|
|
'0x0281': ['SQ', '1', 'PerformedProcedureStepDiscontinuationReasonCodeSequence'],
|
|
'0x0293': ['SQ', '1', 'QuantitySequence'],
|
|
'0x0294': ['DS', '1', 'Quantity'],
|
|
'0x0295': ['SQ', '1', 'MeasuringUnitsSequence'],
|
|
'0x0296': ['SQ', '1', 'BillingItemSequence'],
|
|
'0x0300': ['US', '1', 'TotalTimeOfFluoroscopy'],
|
|
'0x0301': ['US', '1', 'TotalNumberOfExposures'],
|
|
'0x0302': ['US', '1', 'EntranceDose'],
|
|
'0x0303': ['US', '1-2', 'ExposedArea'],
|
|
'0x0306': ['DS', '1', 'DistanceSourceToEntrance'],
|
|
'0x0307': ['DS', '1', 'DistanceSourceToSupport'],
|
|
'0x030E': ['SQ', '1', 'ExposureDoseSequence'],
|
|
'0x0310': ['ST', '1', 'CommentsOnRadiationDose'],
|
|
'0x0312': ['DS', '1', 'XRayOutput'],
|
|
'0x0314': ['DS', '1', 'HalfValueLayer'],
|
|
'0x0316': ['DS', '1', 'OrganDose'],
|
|
'0x0318': ['CS', '1', 'OrganExposed'],
|
|
'0x0320': ['SQ', '1', 'BillingProcedureStepSequence'],
|
|
'0x0321': ['SQ', '1', 'FilmConsumptionSequence'],
|
|
'0x0324': ['SQ', '1', 'BillingSuppliesAndDevicesSequence'],
|
|
'0x0330': ['SQ', '1', 'ReferencedProcedureStepSequence'],
|
|
'0x0340': ['SQ', '1', 'PerformedSeriesSequence'],
|
|
'0x0400': ['LT', '1', 'CommentsOnTheScheduledProcedureStep'],
|
|
'0x0440': ['SQ', '1', 'ProtocolContextSequence'],
|
|
'0x0441': ['SQ', '1', 'ContentItemModifierSequence'],
|
|
'0x0500': ['SQ', '1', 'ScheduledSpecimenSequence'],
|
|
'0x050A': ['LO', '1', 'SpecimenAccessionNumber'],
|
|
'0x0512': ['LO', '1', 'ContainerIdentifier'],
|
|
'0x0513': ['SQ', '1', 'IssuerOfTheContainerIdentifierSequence'],
|
|
'0x0515': ['SQ', '1', 'AlternateContainerIdentifierSequence'],
|
|
'0x0518': ['SQ', '1', 'ContainerTypeCodeSequence'],
|
|
'0x051A': ['LO', '1', 'ContainerDescription'],
|
|
'0x0520': ['SQ', '1', 'ContainerComponentSequence'],
|
|
'0x0550': ['SQ', '1', 'SpecimenSequence'],
|
|
'0x0551': ['LO', '1', 'SpecimenIdentifier'],
|
|
'0x0552': ['SQ', '1', 'SpecimenDescriptionSequenceTrial'],
|
|
'0x0553': ['ST', '1', 'SpecimenDescriptionTrial'],
|
|
'0x0554': ['UI', '1', 'SpecimenUID'],
|
|
'0x0555': ['SQ', '1', 'AcquisitionContextSequence'],
|
|
'0x0556': ['ST', '1', 'AcquisitionContextDescription'],
|
|
'0x059A': ['SQ', '1', 'SpecimenTypeCodeSequence'],
|
|
'0x0560': ['SQ', '1', 'SpecimenDescriptionSequence'],
|
|
'0x0562': ['SQ', '1', 'IssuerOfTheSpecimenIdentifierSequence'],
|
|
'0x0600': ['LO', '1', 'SpecimenShortDescription'],
|
|
'0x0602': ['UT', '1', 'SpecimenDetailedDescription'],
|
|
'0x0610': ['SQ', '1', 'SpecimenPreparationSequence'],
|
|
'0x0612': ['SQ', '1', 'SpecimenPreparationStepContentItemSequence'],
|
|
'0x0620': ['SQ', '1', 'SpecimenLocalizationContentItemSequence'],
|
|
'0x06FA': ['LO', '1', 'SlideIdentifier'],
|
|
'0x071A': ['SQ', '1', 'ImageCenterPointCoordinatesSequence'],
|
|
'0x072A': ['DS', '1', 'XOffsetInSlideCoordinateSystem'],
|
|
'0x073A': ['DS', '1', 'YOffsetInSlideCoordinateSystem'],
|
|
'0x074A': ['DS', '1', 'ZOffsetInSlideCoordinateSystem'],
|
|
'0x08D8': ['SQ', '1', 'PixelSpacingSequence'],
|
|
'0x08DA': ['SQ', '1', 'CoordinateSystemAxisCodeSequence'],
|
|
'0x08EA': ['SQ', '1', 'MeasurementUnitsCodeSequence'],
|
|
'0x09F8': ['SQ', '1', 'VitalStainCodeSequenceTrial'],
|
|
'0x1001': ['SH', '1', 'RequestedProcedureID'],
|
|
'0x1002': ['LO', '1', 'ReasonForTheRequestedProcedure'],
|
|
'0x1003': ['SH', '1', 'RequestedProcedurePriority'],
|
|
'0x1004': ['LO', '1', 'PatientTransportArrangements'],
|
|
'0x1005': ['LO', '1', 'RequestedProcedureLocation'],
|
|
'0x1006': ['SH', '1', 'PlacerOrderNumberProcedure'],
|
|
'0x1007': ['SH', '1', 'FillerOrderNumberProcedure'],
|
|
'0x1008': ['LO', '1', 'ConfidentialityCode'],
|
|
'0x1009': ['SH', '1', 'ReportingPriority'],
|
|
'0x100A': ['SQ', '1', 'ReasonForRequestedProcedureCodeSequence'],
|
|
'0x1010': ['PN', '1-n', 'NamesOfIntendedRecipientsOfResults'],
|
|
'0x1011': ['SQ', '1', 'IntendedRecipientsOfResultsIdentificationSequence'],
|
|
'0x1012': ['SQ', '1', 'ReasonForPerformedProcedureCodeSequence'],
|
|
'0x1060': ['LO', '1', 'RequestedProcedureDescriptionTrial'],
|
|
'0x1101': ['SQ', '1', 'PersonIdentificationCodeSequence'],
|
|
'0x1102': ['ST', '1', 'PersonAddress'],
|
|
'0x1103': ['LO', '1-n', 'PersonTelephoneNumbers'],
|
|
'0x1104': ['LT', '1', 'PersonTelecomInformation'],
|
|
'0x1400': ['LT', '1', 'RequestedProcedureComments'],
|
|
'0x2001': ['LO', '1', 'ReasonForTheImagingServiceRequest'],
|
|
'0x2004': ['DA', '1', 'IssueDateOfImagingServiceRequest'],
|
|
'0x2005': ['TM', '1', 'IssueTimeOfImagingServiceRequest'],
|
|
'0x2006': ['SH', '1', 'PlacerOrderNumberImagingServiceRequestRetired'],
|
|
'0x2007': ['SH', '1', 'FillerOrderNumberImagingServiceRequestRetired'],
|
|
'0x2008': ['PN', '1', 'OrderEnteredBy'],
|
|
'0x2009': ['SH', '1', 'OrderEntererLocation'],
|
|
'0x2010': ['SH', '1', 'OrderCallbackPhoneNumber'],
|
|
'0x2011': ['LT', '1', 'OrderCallbackTelecomInformation'],
|
|
'0x2016': ['LO', '1', 'PlacerOrderNumberImagingServiceRequest'],
|
|
'0x2017': ['LO', '1', 'FillerOrderNumberImagingServiceRequest'],
|
|
'0x2400': ['LT', '1', 'ImagingServiceRequestComments'],
|
|
'0x3001': ['LO', '1', 'ConfidentialityConstraintOnPatientDataDescription'],
|
|
'0x4001': ['CS', '1', 'GeneralPurposeScheduledProcedureStepStatus'],
|
|
'0x4002': ['CS', '1', 'GeneralPurposePerformedProcedureStepStatus'],
|
|
'0x4003': ['CS', '1', 'GeneralPurposeScheduledProcedureStepPriority'],
|
|
'0x4004': ['SQ', '1', 'ScheduledProcessingApplicationsCodeSequence'],
|
|
'0x4005': ['DT', '1', 'ScheduledProcedureStepStartDateTime'],
|
|
'0x4006': ['CS', '1', 'MultipleCopiesFlag'],
|
|
'0x4007': ['SQ', '1', 'PerformedProcessingApplicationsCodeSequence'],
|
|
'0x4009': ['SQ', '1', 'HumanPerformerCodeSequence'],
|
|
'0x4010': ['DT', '1', 'ScheduledProcedureStepModificationDateTime'],
|
|
'0x4011': ['DT', '1', 'ExpectedCompletionDateTime'],
|
|
'0x4015': ['SQ', '1', 'ResultingGeneralPurposePerformedProcedureStepsSequence'],
|
|
'0x4016': ['SQ', '1', 'ReferencedGeneralPurposeScheduledProcedureStepSequence'],
|
|
'0x4018': ['SQ', '1', 'ScheduledWorkitemCodeSequence'],
|
|
'0x4019': ['SQ', '1', 'PerformedWorkitemCodeSequence'],
|
|
'0x4020': ['CS', '1', 'InputAvailabilityFlag'],
|
|
'0x4021': ['SQ', '1', 'InputInformationSequence'],
|
|
'0x4022': ['SQ', '1', 'RelevantInformationSequence'],
|
|
'0x4023': ['UI', '1', 'ReferencedGeneralPurposeScheduledProcedureStepTransactionUID'],
|
|
'0x4025': ['SQ', '1', 'ScheduledStationNameCodeSequence'],
|
|
'0x4026': ['SQ', '1', 'ScheduledStationClassCodeSequence'],
|
|
'0x4027': ['SQ', '1', 'ScheduledStationGeographicLocationCodeSequence'],
|
|
'0x4028': ['SQ', '1', 'PerformedStationNameCodeSequence'],
|
|
'0x4029': ['SQ', '1', 'PerformedStationClassCodeSequence'],
|
|
'0x4030': ['SQ', '1', 'PerformedStationGeographicLocationCodeSequence'],
|
|
'0x4031': ['SQ', '1', 'RequestedSubsequentWorkitemCodeSequence'],
|
|
'0x4032': ['SQ', '1', 'NonDICOMOutputCodeSequence'],
|
|
'0x4033': ['SQ', '1', 'OutputInformationSequence'],
|
|
'0x4034': ['SQ', '1', 'ScheduledHumanPerformersSequence'],
|
|
'0x4035': ['SQ', '1', 'ActualHumanPerformersSequence'],
|
|
'0x4036': ['LO', '1', 'HumanPerformerOrganization'],
|
|
'0x4037': ['PN', '1', 'HumanPerformerName'],
|
|
'0x4040': ['CS', '1', 'RawDataHandling'],
|
|
'0x4041': ['CS', '1', 'InputReadinessState'],
|
|
'0x4050': ['DT', '1', 'PerformedProcedureStepStartDateTime'],
|
|
'0x4051': ['DT', '1', 'PerformedProcedureStepEndDateTime'],
|
|
'0x4052': ['DT', '1', 'ProcedureStepCancellationDateTime'],
|
|
'0x8302': ['DS', '1', 'EntranceDoseInmGy'],
|
|
'0x9092': ['SQ', '1', 'ParametricMapFrameTypeSequence'],
|
|
'0x9094': ['SQ', '1', 'ReferencedImageRealWorldValueMappingSequence'],
|
|
'0x9096': ['SQ', '1', 'RealWorldValueMappingSequence'],
|
|
'0x9098': ['SQ', '1', 'PixelValueMappingCodeSequence'],
|
|
'0x9210': ['SH', '1', 'LUTLabel'],
|
|
'0x9211': ['xs', '1', 'RealWorldValueLastValueMapped'],
|
|
'0x9212': ['FD', '1-n', 'RealWorldValueLUTData'],
|
|
'0x9216': ['xs', '1', 'RealWorldValueFirstValueMapped'],
|
|
'0x9220': ['SQ', '1', 'QuantityDefinitionSequence'],
|
|
'0x9224': ['FD', '1', 'RealWorldValueIntercept'],
|
|
'0x9225': ['FD', '1', 'RealWorldValueSlope'],
|
|
'0xA007': ['CS', '1', 'FindingsFlagTrial'],
|
|
'0xA010': ['CS', '1', 'RelationshipType'],
|
|
'0xA020': ['SQ', '1', 'FindingsSequenceTrial'],
|
|
'0xA021': ['UI', '1', 'FindingsGroupUIDTrial'],
|
|
'0xA022': ['UI', '1', 'ReferencedFindingsGroupUIDTrial'],
|
|
'0xA023': ['DA', '1', 'FindingsGroupRecordingDateTrial'],
|
|
'0xA024': ['TM', '1', 'FindingsGroupRecordingTimeTrial'],
|
|
'0xA026': ['SQ', '1', 'FindingsSourceCategoryCodeSequenceTrial'],
|
|
'0xA027': ['LO', '1', 'VerifyingOrganization'],
|
|
'0xA028': ['SQ', '1', 'DocumentingOrganizationIdentifierCodeSequenceTrial'],
|
|
'0xA030': ['DT', '1', 'VerificationDateTime'],
|
|
'0xA032': ['DT', '1', 'ObservationDateTime'],
|
|
'0xA040': ['CS', '1', 'ValueType'],
|
|
'0xA043': ['SQ', '1', 'ConceptNameCodeSequence'],
|
|
'0xA047': ['LO', '1', 'MeasurementPrecisionDescriptionTrial'],
|
|
'0xA050': ['CS', '1', 'ContinuityOfContent'],
|
|
'0xA057': ['CS', '1-n', 'UrgencyOrPriorityAlertsTrial'],
|
|
'0xA060': ['LO', '1', 'SequencingIndicatorTrial'],
|
|
'0xA066': ['SQ', '1', 'DocumentIdentifierCodeSequenceTrial'],
|
|
'0xA067': ['PN', '1', 'DocumentAuthorTrial'],
|
|
'0xA068': ['SQ', '1', 'DocumentAuthorIdentifierCodeSequenceTrial'],
|
|
'0xA070': ['SQ', '1', 'IdentifierCodeSequenceTrial'],
|
|
'0xA073': ['SQ', '1', 'VerifyingObserverSequence'],
|
|
'0xA074': ['OB', '1', 'ObjectBinaryIdentifierTrial'],
|
|
'0xA075': ['PN', '1', 'VerifyingObserverName'],
|
|
'0xA076': ['SQ', '1', 'DocumentingObserverIdentifierCodeSequenceTrial'],
|
|
'0xA078': ['SQ', '1', 'AuthorObserverSequence'],
|
|
'0xA07A': ['SQ', '1', 'ParticipantSequence'],
|
|
'0xA07C': ['SQ', '1', 'CustodialOrganizationSequence'],
|
|
'0xA080': ['CS', '1', 'ParticipationType'],
|
|
'0xA082': ['DT', '1', 'ParticipationDateTime'],
|
|
'0xA084': ['CS', '1', 'ObserverType'],
|
|
'0xA085': ['SQ', '1', 'ProcedureIdentifierCodeSequenceTrial'],
|
|
'0xA088': ['SQ', '1', 'VerifyingObserverIdentificationCodeSequence'],
|
|
'0xA089': ['OB', '1', 'ObjectDirectoryBinaryIdentifierTrial'],
|
|
'0xA090': ['SQ', '1', 'EquivalentCDADocumentSequence'],
|
|
'0xA0B0': ['US', '2-2n', 'ReferencedWaveformChannels'],
|
|
'0xA110': ['DA', '1', 'DateOfDocumentOrVerbalTransactionTrial'],
|
|
'0xA112': ['TM', '1', 'TimeOfDocumentCreationOrVerbalTransactionTrial'],
|
|
'0xA120': ['DT', '1', 'DateTime'],
|
|
'0xA121': ['DA', '1', 'Date'],
|
|
'0xA122': ['TM', '1', 'Time'],
|
|
'0xA123': ['PN', '1', 'PersonName'],
|
|
'0xA124': ['UI', '1', 'UID'],
|
|
'0xA125': ['CS', '2', 'ReportStatusIDTrial'],
|
|
'0xA130': ['CS', '1', 'TemporalRangeType'],
|
|
'0xA132': ['UL', '1-n', 'ReferencedSamplePositions'],
|
|
'0xA136': ['US', '1-n', 'ReferencedFrameNumbers'],
|
|
'0xA138': ['DS', '1-n', 'ReferencedTimeOffsets'],
|
|
'0xA13A': ['DT', '1-n', 'ReferencedDateTime'],
|
|
'0xA160': ['UT', '1', 'TextValue'],
|
|
'0xA161': ['FD', '1-n', 'FloatingPointValue'],
|
|
'0xA162': ['SL', '1-n', 'RationalNumeratorValue'],
|
|
'0xA163': ['UL', '1-n', 'RationalDenominatorValue'],
|
|
'0xA167': ['SQ', '1', 'ObservationCategoryCodeSequenceTrial'],
|
|
'0xA168': ['SQ', '1', 'ConceptCodeSequence'],
|
|
'0xA16A': ['ST', '1', 'BibliographicCitationTrial'],
|
|
'0xA170': ['SQ', '1', 'PurposeOfReferenceCodeSequence'],
|
|
'0xA171': ['UI', '1', 'ObservationUID'],
|
|
'0xA172': ['UI', '1', 'ReferencedObservationUIDTrial'],
|
|
'0xA173': ['CS', '1', 'ReferencedObservationClassTrial'],
|
|
'0xA174': ['CS', '1', 'ReferencedObjectObservationClassTrial'],
|
|
'0xA180': ['US', '1', 'AnnotationGroupNumber'],
|
|
'0xA192': ['DA', '1', 'ObservationDateTrial'],
|
|
'0xA193': ['TM', '1', 'ObservationTimeTrial'],
|
|
'0xA194': ['CS', '1', 'MeasurementAutomationTrial'],
|
|
'0xA195': ['SQ', '1', 'ModifierCodeSequence'],
|
|
'0xA224': ['ST', '1', 'IdentificationDescriptionTrial'],
|
|
'0xA290': ['CS', '1', 'CoordinatesSetGeometricTypeTrial'],
|
|
'0xA296': ['SQ', '1', 'AlgorithmCodeSequenceTrial'],
|
|
'0xA297': ['ST', '1', 'AlgorithmDescriptionTrial'],
|
|
'0xA29A': ['SL', '2-2n', 'PixelCoordinatesSetTrial'],
|
|
'0xA300': ['SQ', '1', 'MeasuredValueSequence'],
|
|
'0xA301': ['SQ', '1', 'NumericValueQualifierCodeSequence'],
|
|
'0xA307': ['PN', '1', 'CurrentObserverTrial'],
|
|
'0xA30A': ['DS', '1-n', 'NumericValue'],
|
|
'0xA313': ['SQ', '1', 'ReferencedAccessionSequenceTrial'],
|
|
'0xA33A': ['ST', '1', 'ReportStatusCommentTrial'],
|
|
'0xA340': ['SQ', '1', 'ProcedureContextSequenceTrial'],
|
|
'0xA352': ['PN', '1', 'VerbalSourceTrial'],
|
|
'0xA353': ['ST', '1', 'AddressTrial'],
|
|
'0xA354': ['LO', '1', 'TelephoneNumberTrial'],
|
|
'0xA358': ['SQ', '1', 'VerbalSourceIdentifierCodeSequenceTrial'],
|
|
'0xA360': ['SQ', '1', 'PredecessorDocumentsSequence'],
|
|
'0xA370': ['SQ', '1', 'ReferencedRequestSequence'],
|
|
'0xA372': ['SQ', '1', 'PerformedProcedureCodeSequence'],
|
|
'0xA375': ['SQ', '1', 'CurrentRequestedProcedureEvidenceSequence'],
|
|
'0xA380': ['SQ', '1', 'ReportDetailSequenceTrial'],
|
|
'0xA385': ['SQ', '1', 'PertinentOtherEvidenceSequence'],
|
|
'0xA390': ['SQ', '1', 'HL7StructuredDocumentReferenceSequence'],
|
|
'0xA402': ['UI', '1', 'ObservationSubjectUIDTrial'],
|
|
'0xA403': ['CS', '1', 'ObservationSubjectClassTrial'],
|
|
'0xA404': ['SQ', '1', 'ObservationSubjectTypeCodeSequenceTrial'],
|
|
'0xA491': ['CS', '1', 'CompletionFlag'],
|
|
'0xA492': ['LO', '1', 'CompletionFlagDescription'],
|
|
'0xA493': ['CS', '1', 'VerificationFlag'],
|
|
'0xA494': ['CS', '1', 'ArchiveRequested'],
|
|
'0xA496': ['CS', '1', 'PreliminaryFlag'],
|
|
'0xA504': ['SQ', '1', 'ContentTemplateSequence'],
|
|
'0xA525': ['SQ', '1', 'IdenticalDocumentsSequence'],
|
|
'0xA600': ['CS', '1', 'ObservationSubjectContextFlagTrial'],
|
|
'0xA601': ['CS', '1', 'ObserverContextFlagTrial'],
|
|
'0xA603': ['CS', '1', 'ProcedureContextFlagTrial'],
|
|
'0xA730': ['SQ', '1', 'ContentSequence'],
|
|
'0xA731': ['SQ', '1', 'RelationshipSequenceTrial'],
|
|
'0xA732': ['SQ', '1', 'RelationshipTypeCodeSequenceTrial'],
|
|
'0xA744': ['SQ', '1', 'LanguageCodeSequenceTrial'],
|
|
'0xA992': ['ST', '1', 'UniformResourceLocatorTrial'],
|
|
'0xB020': ['SQ', '1', 'WaveformAnnotationSequence'],
|
|
'0xDB00': ['CS', '1', 'TemplateIdentifier'],
|
|
'0xDB06': ['DT', '1', 'TemplateVersion'],
|
|
'0xDB07': ['DT', '1', 'TemplateLocalVersion'],
|
|
'0xDB0B': ['CS', '1', 'TemplateExtensionFlag'],
|
|
'0xDB0C': ['UI', '1', 'TemplateExtensionOrganizationUID'],
|
|
'0xDB0D': ['UI', '1', 'TemplateExtensionCreatorUID'],
|
|
'0xDB73': ['UL', '1-n', 'ReferencedContentItemIdentifier'],
|
|
'0xE001': ['ST', '1', 'HL7InstanceIdentifier'],
|
|
'0xE004': ['DT', '1', 'HL7DocumentEffectiveTime'],
|
|
'0xE006': ['SQ', '1', 'HL7DocumentTypeCodeSequence'],
|
|
'0xE008': ['SQ', '1', 'DocumentClassCodeSequence'],
|
|
'0xE010': ['UR', '1', 'RetrieveURI'],
|
|
'0xE011': ['UI', '1', 'RetrieveLocationUID'],
|
|
'0xE020': ['CS', '1', 'TypeOfInstances'],
|
|
'0xE021': ['SQ', '1', 'DICOMRetrievalSequence'],
|
|
'0xE022': ['SQ', '1', 'DICOMMediaRetrievalSequence'],
|
|
'0xE023': ['SQ', '1', 'WADORetrievalSequence'],
|
|
'0xE024': ['SQ', '1', 'XDSRetrievalSequence'],
|
|
'0xE025': ['SQ', '1', 'WADORSRetrievalSequence'],
|
|
'0xE030': ['UI', '1', 'RepositoryUniqueID'],
|
|
'0xE031': ['UI', '1', 'HomeCommunityID']
|
|
},
|
|
'0x0042': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['ST', '1', 'DocumentTitle'],
|
|
'0x0011': ['OB', '1', 'EncapsulatedDocument'],
|
|
'0x0012': ['LO', '1', 'MIMETypeOfEncapsulatedDocument'],
|
|
'0x0013': ['SQ', '1', 'SourceInstanceSequence'],
|
|
'0x0014': ['LO', '1-n', 'ListOfMIMETypes']
|
|
},
|
|
'0x0044': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['ST', '1', 'ProductPackageIdentifier'],
|
|
'0x0002': ['CS', '1', 'SubstanceAdministrationApproval'],
|
|
'0x0003': ['LT', '1', 'ApprovalStatusFurtherDescription'],
|
|
'0x0004': ['DT', '1', 'ApprovalStatusDateTime'],
|
|
'0x0007': ['SQ', '1', 'ProductTypeCodeSequence'],
|
|
'0x0008': ['LO', '1-n', 'ProductName'],
|
|
'0x0009': ['LT', '1', 'ProductDescription'],
|
|
'0x000A': ['LO', '1', 'ProductLotIdentifier'],
|
|
'0x000B': ['DT', '1', 'ProductExpirationDateTime'],
|
|
'0x0010': ['DT', '1', 'SubstanceAdministrationDateTime'],
|
|
'0x0011': ['LO', '1', 'SubstanceAdministrationNotes'],
|
|
'0x0012': ['LO', '1', 'SubstanceAdministrationDeviceID'],
|
|
'0x0013': ['SQ', '1', 'ProductParameterSequence'],
|
|
'0x0019': ['SQ', '1', 'SubstanceAdministrationParameterSequence']
|
|
},
|
|
'0x0046': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0012': ['LO', '1', 'LensDescription'],
|
|
'0x0014': ['SQ', '1', 'RightLensSequence'],
|
|
'0x0015': ['SQ', '1', 'LeftLensSequence'],
|
|
'0x0016': ['SQ', '1', 'UnspecifiedLateralityLensSequence'],
|
|
'0x0018': ['SQ', '1', 'CylinderSequence'],
|
|
'0x0028': ['SQ', '1', 'PrismSequence'],
|
|
'0x0030': ['FD', '1', 'HorizontalPrismPower'],
|
|
'0x0032': ['CS', '1', 'HorizontalPrismBase'],
|
|
'0x0034': ['FD', '1', 'VerticalPrismPower'],
|
|
'0x0036': ['CS', '1', 'VerticalPrismBase'],
|
|
'0x0038': ['CS', '1', 'LensSegmentType'],
|
|
'0x0040': ['FD', '1', 'OpticalTransmittance'],
|
|
'0x0042': ['FD', '1', 'ChannelWidth'],
|
|
'0x0044': ['FD', '1', 'PupilSize'],
|
|
'0x0046': ['FD', '1', 'CornealSize'],
|
|
'0x0050': ['SQ', '1', 'AutorefractionRightEyeSequence'],
|
|
'0x0052': ['SQ', '1', 'AutorefractionLeftEyeSequence'],
|
|
'0x0060': ['FD', '1', 'DistancePupillaryDistance'],
|
|
'0x0062': ['FD', '1', 'NearPupillaryDistance'],
|
|
'0x0063': ['FD', '1', 'IntermediatePupillaryDistance'],
|
|
'0x0064': ['FD', '1', 'OtherPupillaryDistance'],
|
|
'0x0070': ['SQ', '1', 'KeratometryRightEyeSequence'],
|
|
'0x0071': ['SQ', '1', 'KeratometryLeftEyeSequence'],
|
|
'0x0074': ['SQ', '1', 'SteepKeratometricAxisSequence'],
|
|
'0x0075': ['FD', '1', 'RadiusOfCurvature'],
|
|
'0x0076': ['FD', '1', 'KeratometricPower'],
|
|
'0x0077': ['FD', '1', 'KeratometricAxis'],
|
|
'0x0080': ['SQ', '1', 'FlatKeratometricAxisSequence'],
|
|
'0x0092': ['CS', '1', 'BackgroundColor'],
|
|
'0x0094': ['CS', '1', 'Optotype'],
|
|
'0x0095': ['CS', '1', 'OptotypePresentation'],
|
|
'0x0097': ['SQ', '1', 'SubjectiveRefractionRightEyeSequence'],
|
|
'0x0098': ['SQ', '1', 'SubjectiveRefractionLeftEyeSequence'],
|
|
'0x0100': ['SQ', '1', 'AddNearSequence'],
|
|
'0x0101': ['SQ', '1', 'AddIntermediateSequence'],
|
|
'0x0102': ['SQ', '1', 'AddOtherSequence'],
|
|
'0x0104': ['FD', '1', 'AddPower'],
|
|
'0x0106': ['FD', '1', 'ViewingDistance'],
|
|
'0x0121': ['SQ', '1', 'VisualAcuityTypeCodeSequence'],
|
|
'0x0122': ['SQ', '1', 'VisualAcuityRightEyeSequence'],
|
|
'0x0123': ['SQ', '1', 'VisualAcuityLeftEyeSequence'],
|
|
'0x0124': ['SQ', '1', 'VisualAcuityBothEyesOpenSequence'],
|
|
'0x0125': ['CS', '1', 'ViewingDistanceType'],
|
|
'0x0135': ['SS', '2', 'VisualAcuityModifiers'],
|
|
'0x0137': ['FD', '1', 'DecimalVisualAcuity'],
|
|
'0x0139': ['LO', '1', 'OptotypeDetailedDefinition'],
|
|
'0x0145': ['SQ', '1', 'ReferencedRefractiveMeasurementsSequence'],
|
|
'0x0146': ['FD', '1', 'SpherePower'],
|
|
'0x0147': ['FD', '1', 'CylinderPower'],
|
|
'0x0201': ['CS', '1', 'CornealTopographySurface'],
|
|
'0x0202': ['FL', '2', 'CornealVertexLocation'],
|
|
'0x0203': ['FL', '1', 'PupilCentroidXCoordinate'],
|
|
'0x0204': ['FL', '1', 'PupilCentroidYCoordinate'],
|
|
'0x0205': ['FL', '1', 'EquivalentPupilRadius'],
|
|
'0x0207': ['SQ', '1', 'CornealTopographyMapTypeCodeSequence'],
|
|
'0x0208': ['IS', '2-2n', 'VerticesOfTheOutlineOfPupil'],
|
|
'0x0210': ['SQ', '1', 'CornealTopographyMappingNormalsSequence'],
|
|
'0x0211': ['SQ', '1', 'MaximumCornealCurvatureSequence'],
|
|
'0x0212': ['FL', '1', 'MaximumCornealCurvature'],
|
|
'0x0213': ['FL', '2', 'MaximumCornealCurvatureLocation'],
|
|
'0x0215': ['SQ', '1', 'MinimumKeratometricSequence'],
|
|
'0x0218': ['SQ', '1', 'SimulatedKeratometricCylinderSequence'],
|
|
'0x0220': ['FL', '1', 'AverageCornealPower'],
|
|
'0x0224': ['FL', '1', 'CornealISValue'],
|
|
'0x0227': ['FL', '1', 'AnalyzedArea'],
|
|
'0x0230': ['FL', '1', 'SurfaceRegularityIndex'],
|
|
'0x0232': ['FL', '1', 'SurfaceAsymmetryIndex'],
|
|
'0x0234': ['FL', '1', 'CornealEccentricityIndex'],
|
|
'0x0236': ['FL', '1', 'KeratoconusPredictionIndex'],
|
|
'0x0238': ['FL', '1', 'DecimalPotentialVisualAcuity'],
|
|
'0x0242': ['CS', '1', 'CornealTopographyMapQualityEvaluation'],
|
|
'0x0244': ['SQ', '1', 'SourceImageCornealProcessedDataSequence'],
|
|
'0x0247': ['FL', '3', 'CornealPointLocation'],
|
|
'0x0248': ['CS', '1', 'CornealPointEstimated'],
|
|
'0x0249': ['FL', '1', 'AxialPower'],
|
|
'0x0250': ['FL', '1', 'TangentialPower'],
|
|
'0x0251': ['FL', '1', 'RefractivePower'],
|
|
'0x0252': ['FL', '1', 'RelativeElevation'],
|
|
'0x0253': ['FL', '1', 'CornealWavefront']
|
|
},
|
|
'0x0048': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['FL', '1', 'ImagedVolumeWidth'],
|
|
'0x0002': ['FL', '1', 'ImagedVolumeHeight'],
|
|
'0x0003': ['FL', '1', 'ImagedVolumeDepth'],
|
|
'0x0006': ['UL', '1', 'TotalPixelMatrixColumns'],
|
|
'0x0007': ['UL', '1', 'TotalPixelMatrixRows'],
|
|
'0x0008': ['SQ', '1', 'TotalPixelMatrixOriginSequence'],
|
|
'0x0010': ['CS', '1', 'SpecimenLabelInImage'],
|
|
'0x0011': ['CS', '1', 'FocusMethod'],
|
|
'0x0012': ['CS', '1', 'ExtendedDepthOfField'],
|
|
'0x0013': ['US', '1', 'NumberOfFocalPlanes'],
|
|
'0x0014': ['FL', '1', 'DistanceBetweenFocalPlanes'],
|
|
'0x0015': ['US', '3', 'RecommendedAbsentPixelCIELabValue'],
|
|
'0x0100': ['SQ', '1', 'IlluminatorTypeCodeSequence'],
|
|
'0x0102': ['DS', '6', 'ImageOrientationSlide'],
|
|
'0x0105': ['SQ', '1', 'OpticalPathSequence'],
|
|
'0x0106': ['SH', '1', 'OpticalPathIdentifier'],
|
|
'0x0107': ['ST', '1', 'OpticalPathDescription'],
|
|
'0x0108': ['SQ', '1', 'IlluminationColorCodeSequence'],
|
|
'0x0110': ['SQ', '1', 'SpecimenReferenceSequence'],
|
|
'0x0111': ['DS', '1', 'CondenserLensPower'],
|
|
'0x0112': ['DS', '1', 'ObjectiveLensPower'],
|
|
'0x0113': ['DS', '1', 'ObjectiveLensNumericalAperture'],
|
|
'0x0120': ['SQ', '1', 'PaletteColorLookupTableSequence'],
|
|
'0x0200': ['SQ', '1', 'ReferencedImageNavigationSequence'],
|
|
'0x0201': ['US', '2', 'TopLeftHandCornerOfLocalizerArea'],
|
|
'0x0202': ['US', '2', 'BottomRightHandCornerOfLocalizerArea'],
|
|
'0x0207': ['SQ', '1', 'OpticalPathIdentificationSequence'],
|
|
'0x021A': ['SQ', '1', 'PlanePositionSlideSequence'],
|
|
'0x021E': ['SL', '1', 'ColumnPositionInTotalImagePixelMatrix'],
|
|
'0x021F': ['SL', '1', 'RowPositionInTotalImagePixelMatrix'],
|
|
'0x0301': ['CS', '1', 'PixelOriginInterpretation']
|
|
},
|
|
'0x0050': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0004': ['CS', '1', 'CalibrationImage'],
|
|
'0x0010': ['SQ', '1', 'DeviceSequence'],
|
|
'0x0012': ['SQ', '1', 'ContainerComponentTypeCodeSequence'],
|
|
'0x0013': ['FD', '1', 'ContainerComponentThickness'],
|
|
'0x0014': ['DS', '1', 'DeviceLength'],
|
|
'0x0015': ['FD', '1', 'ContainerComponentWidth'],
|
|
'0x0016': ['DS', '1', 'DeviceDiameter'],
|
|
'0x0017': ['CS', '1', 'DeviceDiameterUnits'],
|
|
'0x0018': ['DS', '1', 'DeviceVolume'],
|
|
'0x0019': ['DS', '1', 'InterMarkerDistance'],
|
|
'0x001A': ['CS', '1', 'ContainerComponentMaterial'],
|
|
'0x001B': ['LO', '1', 'ContainerComponentID'],
|
|
'0x001C': ['FD', '1', 'ContainerComponentLength'],
|
|
'0x001D': ['FD', '1', 'ContainerComponentDiameter'],
|
|
'0x001E': ['LO', '1', 'ContainerComponentDescription'],
|
|
'0x0020': ['LO', '1', 'DeviceDescription']
|
|
},
|
|
'0x0052': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['FL', '1', 'ContrastBolusIngredientPercentByVolume'],
|
|
'0x0002': ['FD', '1', 'OCTFocalDistance'],
|
|
'0x0003': ['FD', '1', 'BeamSpotSize'],
|
|
'0x0004': ['FD', '1', 'EffectiveRefractiveIndex'],
|
|
'0x0006': ['CS', '1', 'OCTAcquisitionDomain'],
|
|
'0x0007': ['FD', '1', 'OCTOpticalCenterWavelength'],
|
|
'0x0008': ['FD', '1', 'AxialResolution'],
|
|
'0x0009': ['FD', '1', 'RangingDepth'],
|
|
'0x0011': ['FD', '1', 'ALineRate'],
|
|
'0x0012': ['US', '1', 'ALinesPerFrame'],
|
|
'0x0013': ['FD', '1', 'CatheterRotationalRate'],
|
|
'0x0014': ['FD', '1', 'ALinePixelSpacing'],
|
|
'0x0016': ['SQ', '1', 'ModeOfPercutaneousAccessSequence'],
|
|
'0x0025': ['SQ', '1', 'IntravascularOCTFrameTypeSequence'],
|
|
'0x0026': ['CS', '1', 'OCTZOffsetApplied'],
|
|
'0x0027': ['SQ', '1', 'IntravascularFrameContentSequence'],
|
|
'0x0028': ['FD', '1', 'IntravascularLongitudinalDistance'],
|
|
'0x0029': ['SQ', '1', 'IntravascularOCTFrameContentSequence'],
|
|
'0x0030': ['SS', '1', 'OCTZOffsetCorrection'],
|
|
'0x0031': ['CS', '1', 'CatheterDirectionOfRotation'],
|
|
'0x0033': ['FD', '1', 'SeamLineLocation'],
|
|
'0x0034': ['FD', '1', 'FirstALineLocation'],
|
|
'0x0036': ['US', '1', 'SeamLineIndex'],
|
|
'0x0038': ['US', '1', 'NumberOfPaddedALines'],
|
|
'0x0039': ['CS', '1', 'InterpolationType'],
|
|
'0x003A': ['CS', '1', 'RefractiveIndexApplied']
|
|
},
|
|
'0x0054': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['US', '1-n', 'EnergyWindowVector'],
|
|
'0x0011': ['US', '1', 'NumberOfEnergyWindows'],
|
|
'0x0012': ['SQ', '1', 'EnergyWindowInformationSequence'],
|
|
'0x0013': ['SQ', '1', 'EnergyWindowRangeSequence'],
|
|
'0x0014': ['DS', '1', 'EnergyWindowLowerLimit'],
|
|
'0x0015': ['DS', '1', 'EnergyWindowUpperLimit'],
|
|
'0x0016': ['SQ', '1', 'RadiopharmaceuticalInformationSequence'],
|
|
'0x0017': ['IS', '1', 'ResidualSyringeCounts'],
|
|
'0x0018': ['SH', '1', 'EnergyWindowName'],
|
|
'0x0020': ['US', '1-n', 'DetectorVector'],
|
|
'0x0021': ['US', '1', 'NumberOfDetectors'],
|
|
'0x0022': ['SQ', '1', 'DetectorInformationSequence'],
|
|
'0x0030': ['US', '1-n', 'PhaseVector'],
|
|
'0x0031': ['US', '1', 'NumberOfPhases'],
|
|
'0x0032': ['SQ', '1', 'PhaseInformationSequence'],
|
|
'0x0033': ['US', '1', 'NumberOfFramesInPhase'],
|
|
'0x0036': ['IS', '1', 'PhaseDelay'],
|
|
'0x0038': ['IS', '1', 'PauseBetweenFrames'],
|
|
'0x0039': ['CS', '1', 'PhaseDescription'],
|
|
'0x0050': ['US', '1-n', 'RotationVector'],
|
|
'0x0051': ['US', '1', 'NumberOfRotations'],
|
|
'0x0052': ['SQ', '1', 'RotationInformationSequence'],
|
|
'0x0053': ['US', '1', 'NumberOfFramesInRotation'],
|
|
'0x0060': ['US', '1-n', 'RRIntervalVector'],
|
|
'0x0061': ['US', '1', 'NumberOfRRIntervals'],
|
|
'0x0062': ['SQ', '1', 'GatedInformationSequence'],
|
|
'0x0063': ['SQ', '1', 'DataInformationSequence'],
|
|
'0x0070': ['US', '1-n', 'TimeSlotVector'],
|
|
'0x0071': ['US', '1', 'NumberOfTimeSlots'],
|
|
'0x0072': ['SQ', '1', 'TimeSlotInformationSequence'],
|
|
'0x0073': ['DS', '1', 'TimeSlotTime'],
|
|
'0x0080': ['US', '1-n', 'SliceVector'],
|
|
'0x0081': ['US', '1', 'NumberOfSlices'],
|
|
'0x0090': ['US', '1-n', 'AngularViewVector'],
|
|
'0x0100': ['US', '1-n', 'TimeSliceVector'],
|
|
'0x0101': ['US', '1', 'NumberOfTimeSlices'],
|
|
'0x0200': ['DS', '1', 'StartAngle'],
|
|
'0x0202': ['CS', '1', 'TypeOfDetectorMotion'],
|
|
'0x0210': ['IS', '1-n', 'TriggerVector'],
|
|
'0x0211': ['US', '1', 'NumberOfTriggersInPhase'],
|
|
'0x0220': ['SQ', '1', 'ViewCodeSequence'],
|
|
'0x0222': ['SQ', '1', 'ViewModifierCodeSequence'],
|
|
'0x0300': ['SQ', '1', 'RadionuclideCodeSequence'],
|
|
'0x0302': ['SQ', '1', 'AdministrationRouteCodeSequence'],
|
|
'0x0304': ['SQ', '1', 'RadiopharmaceuticalCodeSequence'],
|
|
'0x0306': ['SQ', '1', 'CalibrationDataSequence'],
|
|
'0x0308': ['US', '1', 'EnergyWindowNumber'],
|
|
'0x0400': ['SH', '1', 'ImageID'],
|
|
'0x0410': ['SQ', '1', 'PatientOrientationCodeSequence'],
|
|
'0x0412': ['SQ', '1', 'PatientOrientationModifierCodeSequence'],
|
|
'0x0414': ['SQ', '1', 'PatientGantryRelationshipCodeSequence'],
|
|
'0x0500': ['CS', '1', 'SliceProgressionDirection'],
|
|
'0x0501': ['CS', '1', 'ScanProgressionDirection'],
|
|
'0x1000': ['CS', '2', 'SeriesType'],
|
|
'0x1001': ['CS', '1', 'Units'],
|
|
'0x1002': ['CS', '1', 'CountsSource'],
|
|
'0x1004': ['CS', '1', 'ReprojectionMethod'],
|
|
'0x1006': ['CS', '1', 'SUVType'],
|
|
'0x1100': ['CS', '1', 'RandomsCorrectionMethod'],
|
|
'0x1101': ['LO', '1', 'AttenuationCorrectionMethod'],
|
|
'0x1102': ['CS', '1', 'DecayCorrection'],
|
|
'0x1103': ['LO', '1', 'ReconstructionMethod'],
|
|
'0x1104': ['LO', '1', 'DetectorLinesOfResponseUsed'],
|
|
'0x1105': ['LO', '1', 'ScatterCorrectionMethod'],
|
|
'0x1200': ['DS', '1', 'AxialAcceptance'],
|
|
'0x1201': ['IS', '2', 'AxialMash'],
|
|
'0x1202': ['IS', '1', 'TransverseMash'],
|
|
'0x1203': ['DS', '2', 'DetectorElementSize'],
|
|
'0x1210': ['DS', '1', 'CoincidenceWindowWidth'],
|
|
'0x1220': ['CS', '1-n', 'SecondaryCountsType'],
|
|
'0x1300': ['DS', '1', 'FrameReferenceTime'],
|
|
'0x1310': ['IS', '1', 'PrimaryPromptsCountsAccumulated'],
|
|
'0x1311': ['IS', '1-n', 'SecondaryCountsAccumulated'],
|
|
'0x1320': ['DS', '1', 'SliceSensitivityFactor'],
|
|
'0x1321': ['DS', '1', 'DecayFactor'],
|
|
'0x1322': ['DS', '1', 'DoseCalibrationFactor'],
|
|
'0x1323': ['DS', '1', 'ScatterFractionFactor'],
|
|
'0x1324': ['DS', '1', 'DeadTimeFactor'],
|
|
'0x1330': ['US', '1', 'ImageIndex'],
|
|
'0x1400': ['CS', '1-n', 'CountsIncluded'],
|
|
'0x1401': ['CS', '1', 'DeadTimeCorrectionFlag']
|
|
},
|
|
'0x0060': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x3000': ['SQ', '1', 'HistogramSequence'],
|
|
'0x3002': ['US', '1', 'HistogramNumberOfBins'],
|
|
'0x3004': ['xs', '1', 'HistogramFirstBinValue'],
|
|
'0x3006': ['xs', '1', 'HistogramLastBinValue'],
|
|
'0x3008': ['US', '1', 'HistogramBinWidth'],
|
|
'0x3010': ['LO', '1', 'HistogramExplanation'],
|
|
'0x3020': ['UL', '1-n', 'HistogramData']
|
|
},
|
|
'0x0062': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['CS', '1', 'SegmentationType'],
|
|
'0x0002': ['SQ', '1', 'SegmentSequence'],
|
|
'0x0003': ['SQ', '1', 'SegmentedPropertyCategoryCodeSequence'],
|
|
'0x0004': ['US', '1', 'SegmentNumber'],
|
|
'0x0005': ['LO', '1', 'SegmentLabel'],
|
|
'0x0006': ['ST', '1', 'SegmentDescription'],
|
|
'0x0008': ['CS', '1', 'SegmentAlgorithmType'],
|
|
'0x0009': ['LO', '1', 'SegmentAlgorithmName'],
|
|
'0x000A': ['SQ', '1', 'SegmentIdentificationSequence'],
|
|
'0x000B': ['US', '1-n', 'ReferencedSegmentNumber'],
|
|
'0x000C': ['US', '1', 'RecommendedDisplayGrayscaleValue'],
|
|
'0x000D': ['US', '3', 'RecommendedDisplayCIELabValue'],
|
|
'0x000E': ['US', '1', 'MaximumFractionalValue'],
|
|
'0x000F': ['SQ', '1', 'SegmentedPropertyTypeCodeSequence'],
|
|
'0x0010': ['CS', '1', 'SegmentationFractionalType'],
|
|
'0x0011': ['SQ', '1', 'SegmentedPropertyTypeModifierCodeSequence'],
|
|
'0x0012': ['SQ', '1', 'UsedSegmentsSequence']
|
|
},
|
|
'0x0064': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['SQ', '1', 'DeformableRegistrationSequence'],
|
|
'0x0003': ['UI', '1', 'SourceFrameOfReferenceUID'],
|
|
'0x0005': ['SQ', '1', 'DeformableRegistrationGridSequence'],
|
|
'0x0007': ['UL', '3', 'GridDimensions'],
|
|
'0x0008': ['FD', '3', 'GridResolution'],
|
|
'0x0009': ['OF', '1', 'VectorGridData'],
|
|
'0x000F': ['SQ', '1', 'PreDeformationMatrixRegistrationSequence'],
|
|
'0x0010': ['SQ', '1', 'PostDeformationMatrixRegistrationSequence']
|
|
},
|
|
'0x0066': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['UL', '1', 'NumberOfSurfaces'],
|
|
'0x0002': ['SQ', '1', 'SurfaceSequence'],
|
|
'0x0003': ['UL', '1', 'SurfaceNumber'],
|
|
'0x0004': ['LT', '1', 'SurfaceComments'],
|
|
'0x0009': ['CS', '1', 'SurfaceProcessing'],
|
|
'0x000A': ['FL', '1', 'SurfaceProcessingRatio'],
|
|
'0x000B': ['LO', '1', 'SurfaceProcessingDescription'],
|
|
'0x000C': ['FL', '1', 'RecommendedPresentationOpacity'],
|
|
'0x000D': ['CS', '1', 'RecommendedPresentationType'],
|
|
'0x000E': ['CS', '1', 'FiniteVolume'],
|
|
'0x0010': ['CS', '1', 'Manifold'],
|
|
'0x0011': ['SQ', '1', 'SurfacePointsSequence'],
|
|
'0x0012': ['SQ', '1', 'SurfacePointsNormalsSequence'],
|
|
'0x0013': ['SQ', '1', 'SurfaceMeshPrimitivesSequence'],
|
|
'0x0015': ['UL', '1', 'NumberOfSurfacePoints'],
|
|
'0x0016': ['OF', '1', 'PointCoordinatesData'],
|
|
'0x0017': ['FL', '3', 'PointPositionAccuracy'],
|
|
'0x0018': ['FL', '1', 'MeanPointDistance'],
|
|
'0x0019': ['FL', '1', 'MaximumPointDistance'],
|
|
'0x001A': ['FL', '6', 'PointsBoundingBoxCoordinates'],
|
|
'0x001B': ['FL', '3', 'AxisOfRotation'],
|
|
'0x001C': ['FL', '3', 'CenterOfRotation'],
|
|
'0x001E': ['UL', '1', 'NumberOfVectors'],
|
|
'0x001F': ['US', '1', 'VectorDimensionality'],
|
|
'0x0020': ['FL', '1-n', 'VectorAccuracy'],
|
|
'0x0021': ['OF', '1', 'VectorCoordinateData'],
|
|
'0x0023': ['OW', '1', 'TrianglePointIndexList'],
|
|
'0x0024': ['OW', '1', 'EdgePointIndexList'],
|
|
'0x0025': ['OW', '1', 'VertexPointIndexList'],
|
|
'0x0026': ['SQ', '1', 'TriangleStripSequence'],
|
|
'0x0027': ['SQ', '1', 'TriangleFanSequence'],
|
|
'0x0028': ['SQ', '1', 'LineSequence'],
|
|
'0x0029': ['OW', '1', 'PrimitivePointIndexList'],
|
|
'0x002A': ['UL', '1', 'SurfaceCount'],
|
|
'0x002B': ['SQ', '1', 'ReferencedSurfaceSequence'],
|
|
'0x002C': ['UL', '1', 'ReferencedSurfaceNumber'],
|
|
'0x002D': ['SQ', '1', 'SegmentSurfaceGenerationAlgorithmIdentificationSequence'],
|
|
'0x002E': ['SQ', '1', 'SegmentSurfaceSourceInstanceSequence'],
|
|
'0x002F': ['SQ', '1', 'AlgorithmFamilyCodeSequence'],
|
|
'0x0030': ['SQ', '1', 'AlgorithmNameCodeSequence'],
|
|
'0x0031': ['LO', '1', 'AlgorithmVersion'],
|
|
'0x0032': ['LT', '1', 'AlgorithmParameters'],
|
|
'0x0034': ['SQ', '1', 'FacetSequence'],
|
|
'0x0035': ['SQ', '1', 'SurfaceProcessingAlgorithmIdentificationSequence'],
|
|
'0x0036': ['LO', '1', 'AlgorithmName'],
|
|
'0x0037': ['FL', '1', 'RecommendedPointRadius'],
|
|
'0x0038': ['FL', '1', 'RecommendedLineThickness'],
|
|
'0x0040': ['UL', '1-n', 'LongPrimitivePointIndexList'],
|
|
'0x0041': ['UL', '3-3n', 'LongTrianglePointIndexList'],
|
|
'0x0042': ['UL', '2-2n', 'LongEdgePointIndexList'],
|
|
'0x0043': ['UL', '1-n', 'LongVertexPointIndexList']
|
|
},
|
|
'0x0068': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x6210': ['LO', '1', 'ImplantSize'],
|
|
'0x6221': ['LO', '1', 'ImplantTemplateVersion'],
|
|
'0x6222': ['SQ', '1', 'ReplacedImplantTemplateSequence'],
|
|
'0x6223': ['CS', '1', 'ImplantType'],
|
|
'0x6224': ['SQ', '1', 'DerivationImplantTemplateSequence'],
|
|
'0x6225': ['SQ', '1', 'OriginalImplantTemplateSequence'],
|
|
'0x6226': ['DT', '1', 'EffectiveDateTime'],
|
|
'0x6230': ['SQ', '1', 'ImplantTargetAnatomySequence'],
|
|
'0x6260': ['SQ', '1', 'InformationFromManufacturerSequence'],
|
|
'0x6265': ['SQ', '1', 'NotificationFromManufacturerSequence'],
|
|
'0x6270': ['DT', '1', 'InformationIssueDateTime'],
|
|
'0x6280': ['ST', '1', 'InformationSummary'],
|
|
'0x62A0': ['SQ', '1', 'ImplantRegulatoryDisapprovalCodeSequence'],
|
|
'0x62A5': ['FD', '1', 'OverallTemplateSpatialTolerance'],
|
|
'0x62C0': ['SQ', '1', 'HPGLDocumentSequence'],
|
|
'0x62D0': ['US', '1', 'HPGLDocumentID'],
|
|
'0x62D5': ['LO', '1', 'HPGLDocumentLabel'],
|
|
'0x62E0': ['SQ', '1', 'ViewOrientationCodeSequence'],
|
|
'0x62F0': ['FD', '9', 'ViewOrientationModifier'],
|
|
'0x62F2': ['FD', '1', 'HPGLDocumentScaling'],
|
|
'0x6300': ['OB', '1', 'HPGLDocument'],
|
|
'0x6310': ['US', '1', 'HPGLContourPenNumber'],
|
|
'0x6320': ['SQ', '1', 'HPGLPenSequence'],
|
|
'0x6330': ['US', '1', 'HPGLPenNumber'],
|
|
'0x6340': ['LO', '1', 'HPGLPenLabel'],
|
|
'0x6345': ['ST', '1', 'HPGLPenDescription'],
|
|
'0x6346': ['FD', '2', 'RecommendedRotationPoint'],
|
|
'0x6347': ['FD', '4', 'BoundingRectangle'],
|
|
'0x6350': ['US', '1-n', 'ImplantTemplate3DModelSurfaceNumber'],
|
|
'0x6360': ['SQ', '1', 'SurfaceModelDescriptionSequence'],
|
|
'0x6380': ['LO', '1', 'SurfaceModelLabel'],
|
|
'0x6390': ['FD', '1', 'SurfaceModelScalingFactor'],
|
|
'0x63A0': ['SQ', '1', 'MaterialsCodeSequence'],
|
|
'0x63A4': ['SQ', '1', 'CoatingMaterialsCodeSequence'],
|
|
'0x63A8': ['SQ', '1', 'ImplantTypeCodeSequence'],
|
|
'0x63AC': ['SQ', '1', 'FixationMethodCodeSequence'],
|
|
'0x63B0': ['SQ', '1', 'MatingFeatureSetsSequence'],
|
|
'0x63C0': ['US', '1', 'MatingFeatureSetID'],
|
|
'0x63D0': ['LO', '1', 'MatingFeatureSetLabel'],
|
|
'0x63E0': ['SQ', '1', 'MatingFeatureSequence'],
|
|
'0x63F0': ['US', '1', 'MatingFeatureID'],
|
|
'0x6400': ['SQ', '1', 'MatingFeatureDegreeOfFreedomSequence'],
|
|
'0x6410': ['US', '1', 'DegreeOfFreedomID'],
|
|
'0x6420': ['CS', '1', 'DegreeOfFreedomType'],
|
|
'0x6430': ['SQ', '1', 'TwoDMatingFeatureCoordinatesSequence'],
|
|
'0x6440': ['US', '1', 'ReferencedHPGLDocumentID'],
|
|
'0x6450': ['FD', '2', 'TwoDMatingPoint'],
|
|
'0x6460': ['FD', '4', 'TwoDMatingAxes'],
|
|
'0x6470': ['SQ', '1', 'TwoDDegreeOfFreedomSequence'],
|
|
'0x6490': ['FD', '3', 'ThreeDDegreeOfFreedomAxis'],
|
|
'0x64A0': ['FD', '2', 'RangeOfFreedom'],
|
|
'0x64C0': ['FD', '3', 'ThreeDMatingPoint'],
|
|
'0x64D0': ['FD', '9', 'ThreeDMatingAxes'],
|
|
'0x64F0': ['FD', '3', 'TwoDDegreeOfFreedomAxis'],
|
|
'0x6500': ['SQ', '1', 'PlanningLandmarkPointSequence'],
|
|
'0x6510': ['SQ', '1', 'PlanningLandmarkLineSequence'],
|
|
'0x6520': ['SQ', '1', 'PlanningLandmarkPlaneSequence'],
|
|
'0x6530': ['US', '1', 'PlanningLandmarkID'],
|
|
'0x6540': ['LO', '1', 'PlanningLandmarkDescription'],
|
|
'0x6545': ['SQ', '1', 'PlanningLandmarkIdentificationCodeSequence'],
|
|
'0x6550': ['SQ', '1', 'TwoDPointCoordinatesSequence'],
|
|
'0x6560': ['FD', '2', 'TwoDPointCoordinates'],
|
|
'0x6590': ['FD', '3', 'ThreeDPointCoordinates'],
|
|
'0x65A0': ['SQ', '1', 'TwoDLineCoordinatesSequence'],
|
|
'0x65B0': ['FD', '4', 'TwoDLineCoordinates'],
|
|
'0x65D0': ['FD', '6', 'ThreeDLineCoordinates'],
|
|
'0x65E0': ['SQ', '1', 'TwoDPlaneCoordinatesSequence'],
|
|
'0x65F0': ['FD', '4', 'TwoDPlaneIntersection'],
|
|
'0x6610': ['FD', '3', 'ThreeDPlaneOrigin'],
|
|
'0x6620': ['FD', '3', 'ThreeDPlaneNormal']
|
|
},
|
|
'0x0070': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['SQ', '1', 'GraphicAnnotationSequence'],
|
|
'0x0002': ['CS', '1', 'GraphicLayer'],
|
|
'0x0003': ['CS', '1', 'BoundingBoxAnnotationUnits'],
|
|
'0x0004': ['CS', '1', 'AnchorPointAnnotationUnits'],
|
|
'0x0005': ['CS', '1', 'GraphicAnnotationUnits'],
|
|
'0x0006': ['ST', '1', 'UnformattedTextValue'],
|
|
'0x0008': ['SQ', '1', 'TextObjectSequence'],
|
|
'0x0009': ['SQ', '1', 'GraphicObjectSequence'],
|
|
'0x0010': ['FL', '2', 'BoundingBoxTopLeftHandCorner'],
|
|
'0x0011': ['FL', '2', 'BoundingBoxBottomRightHandCorner'],
|
|
'0x0012': ['CS', '1', 'BoundingBoxTextHorizontalJustification'],
|
|
'0x0014': ['FL', '2', 'AnchorPoint'],
|
|
'0x0015': ['CS', '1', 'AnchorPointVisibility'],
|
|
'0x0020': ['US', '1', 'GraphicDimensions'],
|
|
'0x0021': ['US', '1', 'NumberOfGraphicPoints'],
|
|
'0x0022': ['FL', '2-n', 'GraphicData'],
|
|
'0x0023': ['CS', '1', 'GraphicType'],
|
|
'0x0024': ['CS', '1', 'GraphicFilled'],
|
|
'0x0040': ['IS', '1', 'ImageRotationRetired'],
|
|
'0x0041': ['CS', '1', 'ImageHorizontalFlip'],
|
|
'0x0042': ['US', '1', 'ImageRotation'],
|
|
'0x0050': ['US', '2', 'DisplayedAreaTopLeftHandCornerTrial'],
|
|
'0x0051': ['US', '2', 'DisplayedAreaBottomRightHandCornerTrial'],
|
|
'0x0052': ['SL', '2', 'DisplayedAreaTopLeftHandCorner'],
|
|
'0x0053': ['SL', '2', 'DisplayedAreaBottomRightHandCorner'],
|
|
'0x005A': ['SQ', '1', 'DisplayedAreaSelectionSequence'],
|
|
'0x0060': ['SQ', '1', 'GraphicLayerSequence'],
|
|
'0x0062': ['IS', '1', 'GraphicLayerOrder'],
|
|
'0x0066': ['US', '1', 'GraphicLayerRecommendedDisplayGrayscaleValue'],
|
|
'0x0067': ['US', '3', 'GraphicLayerRecommendedDisplayRGBValue'],
|
|
'0x0068': ['LO', '1', 'GraphicLayerDescription'],
|
|
'0x0080': ['CS', '1', 'ContentLabel'],
|
|
'0x0081': ['LO', '1', 'ContentDescription'],
|
|
'0x0082': ['DA', '1', 'PresentationCreationDate'],
|
|
'0x0083': ['TM', '1', 'PresentationCreationTime'],
|
|
'0x0084': ['PN', '1', 'ContentCreatorName'],
|
|
'0x0086': ['SQ', '1', 'ContentCreatorIdentificationCodeSequence'],
|
|
'0x0087': ['SQ', '1', 'AlternateContentDescriptionSequence'],
|
|
'0x0100': ['CS', '1', 'PresentationSizeMode'],
|
|
'0x0101': ['DS', '2', 'PresentationPixelSpacing'],
|
|
'0x0102': ['IS', '2', 'PresentationPixelAspectRatio'],
|
|
'0x0103': ['FL', '1', 'PresentationPixelMagnificationRatio'],
|
|
'0x0207': ['LO', '1', 'GraphicGroupLabel'],
|
|
'0x0208': ['ST', '1', 'GraphicGroupDescription'],
|
|
'0x0209': ['SQ', '1', 'CompoundGraphicSequence'],
|
|
'0x0226': ['UL', '1', 'CompoundGraphicInstanceID'],
|
|
'0x0227': ['LO', '1', 'FontName'],
|
|
'0x0228': ['CS', '1', 'FontNameType'],
|
|
'0x0229': ['LO', '1', 'CSSFontName'],
|
|
'0x0230': ['FD', '1', 'RotationAngle'],
|
|
'0x0231': ['SQ', '1', 'TextStyleSequence'],
|
|
'0x0232': ['SQ', '1', 'LineStyleSequence'],
|
|
'0x0233': ['SQ', '1', 'FillStyleSequence'],
|
|
'0x0234': ['SQ', '1', 'GraphicGroupSequence'],
|
|
'0x0241': ['US', '3', 'TextColorCIELabValue'],
|
|
'0x0242': ['CS', '1', 'HorizontalAlignment'],
|
|
'0x0243': ['CS', '1', 'VerticalAlignment'],
|
|
'0x0244': ['CS', '1', 'ShadowStyle'],
|
|
'0x0245': ['FL', '1', 'ShadowOffsetX'],
|
|
'0x0246': ['FL', '1', 'ShadowOffsetY'],
|
|
'0x0247': ['US', '3', 'ShadowColorCIELabValue'],
|
|
'0x0248': ['CS', '1', 'Underlined'],
|
|
'0x0249': ['CS', '1', 'Bold'],
|
|
'0x0250': ['CS', '1', 'Italic'],
|
|
'0x0251': ['US', '3', 'PatternOnColorCIELabValue'],
|
|
'0x0252': ['US', '3', 'PatternOffColorCIELabValue'],
|
|
'0x0253': ['FL', '1', 'LineThickness'],
|
|
'0x0254': ['CS', '1', 'LineDashingStyle'],
|
|
'0x0255': ['UL', '1', 'LinePattern'],
|
|
'0x0256': ['OB', '1', 'FillPattern'],
|
|
'0x0257': ['CS', '1', 'FillMode'],
|
|
'0x0258': ['FL', '1', 'ShadowOpacity'],
|
|
'0x0261': ['FL', '1', 'GapLength'],
|
|
'0x0262': ['FL', '1', 'DiameterOfVisibility'],
|
|
'0x0273': ['FL', '2', 'RotationPoint'],
|
|
'0x0274': ['CS', '1', 'TickAlignment'],
|
|
'0x0278': ['CS', '1', 'ShowTickLabel'],
|
|
'0x0279': ['CS', '1', 'TickLabelAlignment'],
|
|
'0x0282': ['CS', '1', 'CompoundGraphicUnits'],
|
|
'0x0284': ['FL', '1', 'PatternOnOpacity'],
|
|
'0x0285': ['FL', '1', 'PatternOffOpacity'],
|
|
'0x0287': ['SQ', '1', 'MajorTicksSequence'],
|
|
'0x0288': ['FL', '1', 'TickPosition'],
|
|
'0x0289': ['SH', '1', 'TickLabel'],
|
|
'0x0294': ['CS', '1', 'CompoundGraphicType'],
|
|
'0x0295': ['UL', '1', 'GraphicGroupID'],
|
|
'0x0306': ['CS', '1', 'ShapeType'],
|
|
'0x0308': ['SQ', '1', 'RegistrationSequence'],
|
|
'0x0309': ['SQ', '1', 'MatrixRegistrationSequence'],
|
|
'0x030A': ['SQ', '1', 'MatrixSequence'],
|
|
'0x030C': ['CS', '1', 'FrameOfReferenceTransformationMatrixType'],
|
|
'0x030D': ['SQ', '1', 'RegistrationTypeCodeSequence'],
|
|
'0x030F': ['ST', '1', 'FiducialDescription'],
|
|
'0x0310': ['SH', '1', 'FiducialIdentifier'],
|
|
'0x0311': ['SQ', '1', 'FiducialIdentifierCodeSequence'],
|
|
'0x0312': ['FD', '1', 'ContourUncertaintyRadius'],
|
|
'0x0314': ['SQ', '1', 'UsedFiducialsSequence'],
|
|
'0x0318': ['SQ', '1', 'GraphicCoordinatesDataSequence'],
|
|
'0x031A': ['UI', '1', 'FiducialUID'],
|
|
'0x031C': ['SQ', '1', 'FiducialSetSequence'],
|
|
'0x031E': ['SQ', '1', 'FiducialSequence'],
|
|
'0x0401': ['US', '3', 'GraphicLayerRecommendedDisplayCIELabValue'],
|
|
'0x0402': ['SQ', '1', 'BlendingSequence'],
|
|
'0x0403': ['FL', '1', 'RelativeOpacity'],
|
|
'0x0404': ['SQ', '1', 'ReferencedSpatialRegistrationSequence'],
|
|
'0x0405': ['CS', '1', 'BlendingPosition']
|
|
},
|
|
'0x0072': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['SH', '1', 'HangingProtocolName'],
|
|
'0x0004': ['LO', '1', 'HangingProtocolDescription'],
|
|
'0x0006': ['CS', '1', 'HangingProtocolLevel'],
|
|
'0x0008': ['LO', '1', 'HangingProtocolCreator'],
|
|
'0x000A': ['DT', '1', 'HangingProtocolCreationDateTime'],
|
|
'0x000C': ['SQ', '1', 'HangingProtocolDefinitionSequence'],
|
|
'0x000E': ['SQ', '1', 'HangingProtocolUserIdentificationCodeSequence'],
|
|
'0x0010': ['LO', '1', 'HangingProtocolUserGroupName'],
|
|
'0x0012': ['SQ', '1', 'SourceHangingProtocolSequence'],
|
|
'0x0014': ['US', '1', 'NumberOfPriorsReferenced'],
|
|
'0x0020': ['SQ', '1', 'ImageSetsSequence'],
|
|
'0x0022': ['SQ', '1', 'ImageSetSelectorSequence'],
|
|
'0x0024': ['CS', '1', 'ImageSetSelectorUsageFlag'],
|
|
'0x0026': ['AT', '1', 'SelectorAttribute'],
|
|
'0x0028': ['US', '1', 'SelectorValueNumber'],
|
|
'0x0030': ['SQ', '1', 'TimeBasedImageSetsSequence'],
|
|
'0x0032': ['US', '1', 'ImageSetNumber'],
|
|
'0x0034': ['CS', '1', 'ImageSetSelectorCategory'],
|
|
'0x0038': ['US', '2', 'RelativeTime'],
|
|
'0x003A': ['CS', '1', 'RelativeTimeUnits'],
|
|
'0x003C': ['SS', '2', 'AbstractPriorValue'],
|
|
'0x003E': ['SQ', '1', 'AbstractPriorCodeSequence'],
|
|
'0x0040': ['LO', '1', 'ImageSetLabel'],
|
|
'0x0050': ['CS', '1', 'SelectorAttributeVR'],
|
|
'0x0052': ['AT', '1-n', 'SelectorSequencePointer'],
|
|
'0x0054': ['LO', '1-n', 'SelectorSequencePointerPrivateCreator'],
|
|
'0x0056': ['LO', '1', 'SelectorAttributePrivateCreator'],
|
|
'0x0060': ['AT', '1-n', 'SelectorATValue'],
|
|
'0x0062': ['CS', '1-n', 'SelectorCSValue'],
|
|
'0x0064': ['IS', '1-n', 'SelectorISValue'],
|
|
'0x0066': ['LO', '1-n', 'SelectorLOValue'],
|
|
'0x0068': ['LT', '1', 'SelectorLTValue'],
|
|
'0x006A': ['PN', '1-n', 'SelectorPNValue'],
|
|
'0x006C': ['SH', '1-n', 'SelectorSHValue'],
|
|
'0x006E': ['ST', '1', 'SelectorSTValue'],
|
|
'0x0070': ['UT', '1', 'SelectorUTValue'],
|
|
'0x0072': ['DS', '1-n', 'SelectorDSValue'],
|
|
'0x0074': ['FD', '1-n', 'SelectorFDValue'],
|
|
'0x0076': ['FL', '1-n', 'SelectorFLValue'],
|
|
'0x0078': ['UL', '1-n', 'SelectorULValue'],
|
|
'0x007A': ['US', '1-n', 'SelectorUSValue'],
|
|
'0x007C': ['SL', '1-n', 'SelectorSLValue'],
|
|
'0x007E': ['SS', '1-n', 'SelectorSSValue'],
|
|
'0x007F': ['UI', '1-n', 'SelectorUIValue'],
|
|
'0x0080': ['SQ', '1', 'SelectorCodeSequenceValue'],
|
|
'0x0100': ['US', '1', 'NumberOfScreens'],
|
|
'0x0102': ['SQ', '1', 'NominalScreenDefinitionSequence'],
|
|
'0x0104': ['US', '1', 'NumberOfVerticalPixels'],
|
|
'0x0106': ['US', '1', 'NumberOfHorizontalPixels'],
|
|
'0x0108': ['FD', '4', 'DisplayEnvironmentSpatialPosition'],
|
|
'0x010A': ['US', '1', 'ScreenMinimumGrayscaleBitDepth'],
|
|
'0x010C': ['US', '1', 'ScreenMinimumColorBitDepth'],
|
|
'0x010E': ['US', '1', 'ApplicationMaximumRepaintTime'],
|
|
'0x0200': ['SQ', '1', 'DisplaySetsSequence'],
|
|
'0x0202': ['US', '1', 'DisplaySetNumber'],
|
|
'0x0203': ['LO', '1', 'DisplaySetLabel'],
|
|
'0x0204': ['US', '1', 'DisplaySetPresentationGroup'],
|
|
'0x0206': ['LO', '1', 'DisplaySetPresentationGroupDescription'],
|
|
'0x0208': ['CS', '1', 'PartialDataDisplayHandling'],
|
|
'0x0210': ['SQ', '1', 'SynchronizedScrollingSequence'],
|
|
'0x0212': ['US', '2-n', 'DisplaySetScrollingGroup'],
|
|
'0x0214': ['SQ', '1', 'NavigationIndicatorSequence'],
|
|
'0x0216': ['US', '1', 'NavigationDisplaySet'],
|
|
'0x0218': ['US', '1-n', 'ReferenceDisplaySets'],
|
|
'0x0300': ['SQ', '1', 'ImageBoxesSequence'],
|
|
'0x0302': ['US', '1', 'ImageBoxNumber'],
|
|
'0x0304': ['CS', '1', 'ImageBoxLayoutType'],
|
|
'0x0306': ['US', '1', 'ImageBoxTileHorizontalDimension'],
|
|
'0x0308': ['US', '1', 'ImageBoxTileVerticalDimension'],
|
|
'0x0310': ['CS', '1', 'ImageBoxScrollDirection'],
|
|
'0x0312': ['CS', '1', 'ImageBoxSmallScrollType'],
|
|
'0x0314': ['US', '1', 'ImageBoxSmallScrollAmount'],
|
|
'0x0316': ['CS', '1', 'ImageBoxLargeScrollType'],
|
|
'0x0318': ['US', '1', 'ImageBoxLargeScrollAmount'],
|
|
'0x0320': ['US', '1', 'ImageBoxOverlapPriority'],
|
|
'0x0330': ['FD', '1', 'CineRelativeToRealTime'],
|
|
'0x0400': ['SQ', '1', 'FilterOperationsSequence'],
|
|
'0x0402': ['CS', '1', 'FilterByCategory'],
|
|
'0x0404': ['CS', '1', 'FilterByAttributePresence'],
|
|
'0x0406': ['CS', '1', 'FilterByOperator'],
|
|
'0x0420': ['US', '3', 'StructuredDisplayBackgroundCIELabValue'],
|
|
'0x0421': ['US', '3', 'EmptyImageBoxCIELabValue'],
|
|
'0x0422': ['SQ', '1', 'StructuredDisplayImageBoxSequence'],
|
|
'0x0424': ['SQ', '1', 'StructuredDisplayTextBoxSequence'],
|
|
'0x0427': ['SQ', '1', 'ReferencedFirstFrameSequence'],
|
|
'0x0430': ['SQ', '1', 'ImageBoxSynchronizationSequence'],
|
|
'0x0432': ['US', '2-n', 'SynchronizedImageBoxList'],
|
|
'0x0434': ['CS', '1', 'TypeOfSynchronization'],
|
|
'0x0500': ['CS', '1', 'BlendingOperationType'],
|
|
'0x0510': ['CS', '1', 'ReformattingOperationType'],
|
|
'0x0512': ['FD', '1', 'ReformattingThickness'],
|
|
'0x0514': ['FD', '1', 'ReformattingInterval'],
|
|
'0x0516': ['CS', '1', 'ReformattingOperationInitialViewDirection'],
|
|
'0x0520': ['CS', '1-n', 'ThreeDRenderingType'],
|
|
'0x0600': ['SQ', '1', 'SortingOperationsSequence'],
|
|
'0x0602': ['CS', '1', 'SortByCategory'],
|
|
'0x0604': ['CS', '1', 'SortingDirection'],
|
|
'0x0700': ['CS', '2', 'DisplaySetPatientOrientation'],
|
|
'0x0702': ['CS', '1', 'VOIType'],
|
|
'0x0704': ['CS', '1', 'PseudoColorType'],
|
|
'0x0705': ['SQ', '1', 'PseudoColorPaletteInstanceReferenceSequence'],
|
|
'0x0706': ['CS', '1', 'ShowGrayscaleInverted'],
|
|
'0x0710': ['CS', '1', 'ShowImageTrueSizeFlag'],
|
|
'0x0712': ['CS', '1', 'ShowGraphicAnnotationFlag'],
|
|
'0x0714': ['CS', '1', 'ShowPatientDemographicsFlag'],
|
|
'0x0716': ['CS', '1', 'ShowAcquisitionTechniquesFlag'],
|
|
'0x0717': ['CS', '1', 'DisplaySetHorizontalJustification'],
|
|
'0x0718': ['CS', '1', 'DisplaySetVerticalJustification']
|
|
},
|
|
'0x0074': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0120': ['FD', '1', 'ContinuationStartMeterset'],
|
|
'0x0121': ['FD', '1', 'ContinuationEndMeterset'],
|
|
'0x1000': ['CS', '1', 'ProcedureStepState'],
|
|
'0x1002': ['SQ', '1', 'ProcedureStepProgressInformationSequence'],
|
|
'0x1004': ['DS', '1', 'ProcedureStepProgress'],
|
|
'0x1006': ['ST', '1', 'ProcedureStepProgressDescription'],
|
|
'0x1008': ['SQ', '1', 'ProcedureStepCommunicationsURISequence'],
|
|
'0x100A': ['UR', '1', 'ContactURI'],
|
|
'0x100C': ['LO', '1', 'ContactDisplayName'],
|
|
'0x100E': ['SQ', '1', 'ProcedureStepDiscontinuationReasonCodeSequence'],
|
|
'0x1020': ['SQ', '1', 'BeamTaskSequence'],
|
|
'0x1022': ['CS', '1', 'BeamTaskType'],
|
|
'0x1024': ['IS', '1', 'BeamOrderIndexTrial'],
|
|
'0x1025': ['CS', '1', 'AutosequenceFlag'],
|
|
'0x1026': ['FD', '1', 'TableTopVerticalAdjustedPosition'],
|
|
'0x1027': ['FD', '1', 'TableTopLongitudinalAdjustedPosition'],
|
|
'0x1028': ['FD', '1', 'TableTopLateralAdjustedPosition'],
|
|
'0x102A': ['FD', '1', 'PatientSupportAdjustedAngle'],
|
|
'0x102B': ['FD', '1', 'TableTopEccentricAdjustedAngle'],
|
|
'0x102C': ['FD', '1', 'TableTopPitchAdjustedAngle'],
|
|
'0x102D': ['FD', '1', 'TableTopRollAdjustedAngle'],
|
|
'0x1030': ['SQ', '1', 'DeliveryVerificationImageSequence'],
|
|
'0x1032': ['CS', '1', 'VerificationImageTiming'],
|
|
'0x1034': ['CS', '1', 'DoubleExposureFlag'],
|
|
'0x1036': ['CS', '1', 'DoubleExposureOrdering'],
|
|
'0x1038': ['DS', '1', 'DoubleExposureMetersetTrial'],
|
|
'0x103A': ['DS', '4', 'DoubleExposureFieldDeltaTrial'],
|
|
'0x1040': ['SQ', '1', 'RelatedReferenceRTImageSequence'],
|
|
'0x1042': ['SQ', '1', 'GeneralMachineVerificationSequence'],
|
|
'0x1044': ['SQ', '1', 'ConventionalMachineVerificationSequence'],
|
|
'0x1046': ['SQ', '1', 'IonMachineVerificationSequence'],
|
|
'0x1048': ['SQ', '1', 'FailedAttributesSequence'],
|
|
'0x104A': ['SQ', '1', 'OverriddenAttributesSequence'],
|
|
'0x104C': ['SQ', '1', 'ConventionalControlPointVerificationSequence'],
|
|
'0x104E': ['SQ', '1', 'IonControlPointVerificationSequence'],
|
|
'0x1050': ['SQ', '1', 'AttributeOccurrenceSequence'],
|
|
'0x1052': ['AT', '1', 'AttributeOccurrencePointer'],
|
|
'0x1054': ['UL', '1', 'AttributeItemSelector'],
|
|
'0x1056': ['LO', '1', 'AttributeOccurrencePrivateCreator'],
|
|
'0x1057': ['IS', '1-n', 'SelectorSequencePointerItems'],
|
|
'0x1200': ['CS', '1', 'ScheduledProcedureStepPriority'],
|
|
'0x1202': ['LO', '1', 'WorklistLabel'],
|
|
'0x1204': ['LO', '1', 'ProcedureStepLabel'],
|
|
'0x1210': ['SQ', '1', 'ScheduledProcessingParametersSequence'],
|
|
'0x1212': ['SQ', '1', 'PerformedProcessingParametersSequence'],
|
|
'0x1216': ['SQ', '1', 'UnifiedProcedureStepPerformedProcedureSequence'],
|
|
'0x1220': ['SQ', '1', 'RelatedProcedureStepSequence'],
|
|
'0x1222': ['LO', '1', 'ProcedureStepRelationshipType'],
|
|
'0x1224': ['SQ', '1', 'ReplacedProcedureStepSequence'],
|
|
'0x1230': ['LO', '1', 'DeletionLock'],
|
|
'0x1234': ['AE', '1', 'ReceivingAE'],
|
|
'0x1236': ['AE', '1', 'RequestingAE'],
|
|
'0x1238': ['LT', '1', 'ReasonForCancellation'],
|
|
'0x1242': ['CS', '1', 'SCPStatus'],
|
|
'0x1244': ['CS', '1', 'SubscriptionListStatus'],
|
|
'0x1246': ['CS', '1', 'UnifiedProcedureStepListStatus'],
|
|
'0x1324': ['UL', '1', 'BeamOrderIndex'],
|
|
'0x1338': ['FD', '1', 'DoubleExposureMeterset'],
|
|
'0x133A': ['FD', '4', 'DoubleExposureFieldDelta']
|
|
},
|
|
'0x0076': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['LO', '1', 'ImplantAssemblyTemplateName'],
|
|
'0x0003': ['LO', '1', 'ImplantAssemblyTemplateIssuer'],
|
|
'0x0006': ['LO', '1', 'ImplantAssemblyTemplateVersion'],
|
|
'0x0008': ['SQ', '1', 'ReplacedImplantAssemblyTemplateSequence'],
|
|
'0x000A': ['CS', '1', 'ImplantAssemblyTemplateType'],
|
|
'0x000C': ['SQ', '1', 'OriginalImplantAssemblyTemplateSequence'],
|
|
'0x000E': ['SQ', '1', 'DerivationImplantAssemblyTemplateSequence'],
|
|
'0x0010': ['SQ', '1', 'ImplantAssemblyTemplateTargetAnatomySequence'],
|
|
'0x0020': ['SQ', '1', 'ProcedureTypeCodeSequence'],
|
|
'0x0030': ['LO', '1', 'SurgicalTechnique'],
|
|
'0x0032': ['SQ', '1', 'ComponentTypesSequence'],
|
|
'0x0034': ['CS', '1', 'ComponentTypeCodeSequence'],
|
|
'0x0036': ['CS', '1', 'ExclusiveComponentType'],
|
|
'0x0038': ['CS', '1', 'MandatoryComponentType'],
|
|
'0x0040': ['SQ', '1', 'ComponentSequence'],
|
|
'0x0055': ['US', '1', 'ComponentID'],
|
|
'0x0060': ['SQ', '1', 'ComponentAssemblySequence'],
|
|
'0x0070': ['US', '1', 'Component1ReferencedID'],
|
|
'0x0080': ['US', '1', 'Component1ReferencedMatingFeatureSetID'],
|
|
'0x0090': ['US', '1', 'Component1ReferencedMatingFeatureID'],
|
|
'0x00A0': ['US', '1', 'Component2ReferencedID'],
|
|
'0x00B0': ['US', '1', 'Component2ReferencedMatingFeatureSetID'],
|
|
'0x00C0': ['US', '1', 'Component2ReferencedMatingFeatureID']
|
|
},
|
|
'0x0078': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['LO', '1', 'ImplantTemplateGroupName'],
|
|
'0x0010': ['ST', '1', 'ImplantTemplateGroupDescription'],
|
|
'0x0020': ['LO', '1', 'ImplantTemplateGroupIssuer'],
|
|
'0x0024': ['LO', '1', 'ImplantTemplateGroupVersion'],
|
|
'0x0026': ['SQ', '1', 'ReplacedImplantTemplateGroupSequence'],
|
|
'0x0028': ['SQ', '1', 'ImplantTemplateGroupTargetAnatomySequence'],
|
|
'0x002A': ['SQ', '1', 'ImplantTemplateGroupMembersSequence'],
|
|
'0x002E': ['US', '1', 'ImplantTemplateGroupMemberID'],
|
|
'0x0050': ['FD', '3', 'ThreeDImplantTemplateGroupMemberMatchingPoint'],
|
|
'0x0060': ['FD', '9', 'ThreeDImplantTemplateGroupMemberMatchingAxes'],
|
|
'0x0070': ['SQ', '1', 'ImplantTemplateGroupMemberMatching2DCoordinatesSequence'],
|
|
'0x0090': ['FD', '2', 'TwoDImplantTemplateGroupMemberMatchingPoint'],
|
|
'0x00A0': ['FD', '4', 'TwoDImplantTemplateGroupMemberMatchingAxes'],
|
|
'0x00B0': ['SQ', '1', 'ImplantTemplateGroupVariationDimensionSequence'],
|
|
'0x00B2': ['LO', '1', 'ImplantTemplateGroupVariationDimensionName'],
|
|
'0x00B4': ['SQ', '1', 'ImplantTemplateGroupVariationDimensionRankSequence'],
|
|
'0x00B6': ['US', '1', 'ReferencedImplantTemplateGroupMemberID'],
|
|
'0x00B8': ['US', '1', 'ImplantTemplateGroupVariationDimensionRank']
|
|
},
|
|
'0x0080': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['SQ', '1', 'SurfaceScanAcquisitionTypeCodeSequence'],
|
|
'0x0002': ['SQ', '1', 'SurfaceScanModeCodeSequence'],
|
|
'0x0003': ['SQ', '1', 'RegistrationMethodCodeSequence'],
|
|
'0x0004': ['FD', '1', 'ShotDurationTime'],
|
|
'0x0005': ['FD', '1', 'ShotOffsetTime'],
|
|
'0x0006': ['US', '1-n', 'SurfacePointPresentationValueData'],
|
|
'0x0007': ['US', '3-3n', 'SurfacePointColorCIELabValueData'],
|
|
'0x0008': ['SQ', '1', 'UVMappingSequence'],
|
|
'0x0009': ['SH', '1', 'TextureLabel'],
|
|
'0x0010': ['OF', '1-n', 'UValueData'],
|
|
'0x0011': ['OF', '1-n', 'VValueData'],
|
|
'0x0012': ['SQ', '1', 'ReferencedTextureSequence'],
|
|
'0x0013': ['SQ', '1', 'ReferencedSurfaceDataSequence']
|
|
},
|
|
'0x0088': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0130': ['SH', '1', 'StorageMediaFileSetID'],
|
|
'0x0140': ['UI', '1', 'StorageMediaFileSetUID'],
|
|
'0x0200': ['SQ', '1', 'IconImageSequence'],
|
|
'0x0904': ['LO', '1', 'TopicTitle'],
|
|
'0x0906': ['ST', '1', 'TopicSubject'],
|
|
'0x0910': ['LO', '1', 'TopicAuthor'],
|
|
'0x0912': ['LO', '1-32', 'TopicKeywords']
|
|
},
|
|
'0x0100': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0410': ['CS', '1', 'SOPInstanceStatus'],
|
|
'0x0420': ['DT', '1', 'SOPAuthorizationDateTime'],
|
|
'0x0424': ['LT', '1', 'SOPAuthorizationComment'],
|
|
'0x0426': ['LO', '1', 'AuthorizationEquipmentCertificationNumber']
|
|
},
|
|
'0x0400': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0005': ['US', '1', 'MACIDNumber'],
|
|
'0x0010': ['UI', '1', 'MACCalculationTransferSyntaxUID'],
|
|
'0x0015': ['CS', '1', 'MACAlgorithm'],
|
|
'0x0020': ['AT', '1-n', 'DataElementsSigned'],
|
|
'0x0100': ['UI', '1', 'DigitalSignatureUID'],
|
|
'0x0105': ['DT', '1', 'DigitalSignatureDateTime'],
|
|
'0x0110': ['CS', '1', 'CertificateType'],
|
|
'0x0115': ['OB', '1', 'CertificateOfSigner'],
|
|
'0x0120': ['OB', '1', 'Signature'],
|
|
'0x0305': ['CS', '1', 'CertifiedTimestampType'],
|
|
'0x0310': ['OB', '1', 'CertifiedTimestamp'],
|
|
'0x0401': ['SQ', '1', 'DigitalSignaturePurposeCodeSequence'],
|
|
'0x0402': ['SQ', '1', 'ReferencedDigitalSignatureSequence'],
|
|
'0x0403': ['SQ', '1', 'ReferencedSOPInstanceMACSequence'],
|
|
'0x0404': ['OB', '1', 'MAC'],
|
|
'0x0500': ['SQ', '1', 'EncryptedAttributesSequence'],
|
|
'0x0510': ['UI', '1', 'EncryptedContentTransferSyntaxUID'],
|
|
'0x0520': ['OB', '1', 'EncryptedContent'],
|
|
'0x0550': ['SQ', '1', 'ModifiedAttributesSequence'],
|
|
'0x0561': ['SQ', '1', 'OriginalAttributesSequence'],
|
|
'0x0562': ['DT', '1', 'AttributeModificationDateTime'],
|
|
'0x0563': ['LO', '1', 'ModifyingSystem'],
|
|
'0x0564': ['LO', '1', 'SourceOfPreviousValues'],
|
|
'0x0565': ['CS', '1', 'ReasonForTheAttributeModification']
|
|
},
|
|
'0x1000': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['US', '3', 'EscapeTriplet'],
|
|
'0x0011': ['US', '3', 'RunLengthTriplet'],
|
|
'0x0012': ['US', '1', 'HuffmanTableSize'],
|
|
'0x0013': ['US', '3', 'HuffmanTableTriplet'],
|
|
'0x0014': ['US', '1', 'ShiftTableSize'],
|
|
'0x0015': ['US', '3', 'ShiftTableTriplet']
|
|
},
|
|
'0x1010': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0004': ['US', '1-n', 'ZonalMap']
|
|
},
|
|
'0x2000': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['IS', '1', 'NumberOfCopies'],
|
|
'0x001E': ['SQ', '1', 'PrinterConfigurationSequence'],
|
|
'0x0020': ['CS', '1', 'PrintPriority'],
|
|
'0x0030': ['CS', '1', 'MediumType'],
|
|
'0x0040': ['CS', '1', 'FilmDestination'],
|
|
'0x0050': ['LO', '1', 'FilmSessionLabel'],
|
|
'0x0060': ['IS', '1', 'MemoryAllocation'],
|
|
'0x0061': ['IS', '1', 'MaximumMemoryAllocation'],
|
|
'0x0062': ['CS', '1', 'ColorImagePrintingFlag'],
|
|
'0x0063': ['CS', '1', 'CollationFlag'],
|
|
'0x0065': ['CS', '1', 'AnnotationFlag'],
|
|
'0x0067': ['CS', '1', 'ImageOverlayFlag'],
|
|
'0x0069': ['CS', '1', 'PresentationLUTFlag'],
|
|
'0x006A': ['CS', '1', 'ImageBoxPresentationLUTFlag'],
|
|
'0x00A0': ['US', '1', 'MemoryBitDepth'],
|
|
'0x00A1': ['US', '1', 'PrintingBitDepth'],
|
|
'0x00A2': ['SQ', '1', 'MediaInstalledSequence'],
|
|
'0x00A4': ['SQ', '1', 'OtherMediaAvailableSequence'],
|
|
'0x00A8': ['SQ', '1', 'SupportedImageDisplayFormatsSequence'],
|
|
'0x0500': ['SQ', '1', 'ReferencedFilmBoxSequence'],
|
|
'0x0510': ['SQ', '1', 'ReferencedStoredPrintSequence']
|
|
},
|
|
'0x2010': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['ST', '1', 'ImageDisplayFormat'],
|
|
'0x0030': ['CS', '1', 'AnnotationDisplayFormatID'],
|
|
'0x0040': ['CS', '1', 'FilmOrientation'],
|
|
'0x0050': ['CS', '1', 'FilmSizeID'],
|
|
'0x0052': ['CS', '1', 'PrinterResolutionID'],
|
|
'0x0054': ['CS', '1', 'DefaultPrinterResolutionID'],
|
|
'0x0060': ['CS', '1', 'MagnificationType'],
|
|
'0x0080': ['CS', '1', 'SmoothingType'],
|
|
'0x00A6': ['CS', '1', 'DefaultMagnificationType'],
|
|
'0x00A7': ['CS', '1-n', 'OtherMagnificationTypesAvailable'],
|
|
'0x00A8': ['CS', '1', 'DefaultSmoothingType'],
|
|
'0x00A9': ['CS', '1-n', 'OtherSmoothingTypesAvailable'],
|
|
'0x0100': ['CS', '1', 'BorderDensity'],
|
|
'0x0110': ['CS', '1', 'EmptyImageDensity'],
|
|
'0x0120': ['US', '1', 'MinDensity'],
|
|
'0x0130': ['US', '1', 'MaxDensity'],
|
|
'0x0140': ['CS', '1', 'Trim'],
|
|
'0x0150': ['ST', '1', 'ConfigurationInformation'],
|
|
'0x0152': ['LT', '1', 'ConfigurationInformationDescription'],
|
|
'0x0154': ['IS', '1', 'MaximumCollatedFilms'],
|
|
'0x015E': ['US', '1', 'Illumination'],
|
|
'0x0160': ['US', '1', 'ReflectedAmbientLight'],
|
|
'0x0376': ['DS', '2', 'PrinterPixelSpacing'],
|
|
'0x0500': ['SQ', '1', 'ReferencedFilmSessionSequence'],
|
|
'0x0510': ['SQ', '1', 'ReferencedImageBoxSequence'],
|
|
'0x0520': ['SQ', '1', 'ReferencedBasicAnnotationBoxSequence']
|
|
},
|
|
'0x2020': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['US', '1', 'ImageBoxPosition'],
|
|
'0x0020': ['CS', '1', 'Polarity'],
|
|
'0x0030': ['DS', '1', 'RequestedImageSize'],
|
|
'0x0040': ['CS', '1', 'RequestedDecimateCropBehavior'],
|
|
'0x0050': ['CS', '1', 'RequestedResolutionID'],
|
|
'0x00A0': ['CS', '1', 'RequestedImageSizeFlag'],
|
|
'0x00A2': ['CS', '1', 'DecimateCropResult'],
|
|
'0x0110': ['SQ', '1', 'BasicGrayscaleImageSequence'],
|
|
'0x0111': ['SQ', '1', 'BasicColorImageSequence'],
|
|
'0x0130': ['SQ', '1', 'ReferencedImageOverlayBoxSequence'],
|
|
'0x0140': ['SQ', '1', 'ReferencedVOILUTBoxSequence']
|
|
},
|
|
'0x2030': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['US', '1', 'AnnotationPosition'],
|
|
'0x0020': ['LO', '1', 'TextString']
|
|
},
|
|
'0x2040': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['SQ', '1', 'ReferencedOverlayPlaneSequence'],
|
|
'0x0011': ['US', '1-99', 'ReferencedOverlayPlaneGroups'],
|
|
'0x0020': ['SQ', '1', 'OverlayPixelDataSequence'],
|
|
'0x0060': ['CS', '1', 'OverlayMagnificationType'],
|
|
'0x0070': ['CS', '1', 'OverlaySmoothingType'],
|
|
'0x0072': ['CS', '1', 'OverlayOrImageMagnification'],
|
|
'0x0074': ['US', '1', 'MagnifyToNumberOfColumns'],
|
|
'0x0080': ['CS', '1', 'OverlayForegroundDensity'],
|
|
'0x0082': ['CS', '1', 'OverlayBackgroundDensity'],
|
|
'0x0090': ['CS', '1', 'OverlayMode'],
|
|
'0x0100': ['CS', '1', 'ThresholdDensity'],
|
|
'0x0500': ['SQ', '1', 'ReferencedImageBoxSequenceRetired']
|
|
},
|
|
'0x2050': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['SQ', '1', 'PresentationLUTSequence'],
|
|
'0x0020': ['CS', '1', 'PresentationLUTShape'],
|
|
'0x0500': ['SQ', '1', 'ReferencedPresentationLUTSequence']
|
|
},
|
|
'0x2100': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['SH', '1', 'PrintJobID'],
|
|
'0x0020': ['CS', '1', 'ExecutionStatus'],
|
|
'0x0030': ['CS', '1', 'ExecutionStatusInfo'],
|
|
'0x0040': ['DA', '1', 'CreationDate'],
|
|
'0x0050': ['TM', '1', 'CreationTime'],
|
|
'0x0070': ['AE', '1', 'Originator'],
|
|
'0x0140': ['AE', '1', 'DestinationAE'],
|
|
'0x0160': ['SH', '1', 'OwnerID'],
|
|
'0x0170': ['IS', '1', 'NumberOfFilms'],
|
|
'0x0500': ['SQ', '1', 'ReferencedPrintJobSequencePullStoredPrint']
|
|
},
|
|
'0x2110': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['CS', '1', 'PrinterStatus'],
|
|
'0x0020': ['CS', '1', 'PrinterStatusInfo'],
|
|
'0x0030': ['LO', '1', 'PrinterName'],
|
|
'0x0099': ['SH', '1', 'PrintQueueID']
|
|
},
|
|
'0x2120': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['CS', '1', 'QueueStatus'],
|
|
'0x0050': ['SQ', '1', 'PrintJobDescriptionSequence'],
|
|
'0x0070': ['SQ', '1', 'ReferencedPrintJobSequence']
|
|
},
|
|
'0x2130': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['SQ', '1', 'PrintManagementCapabilitiesSequence'],
|
|
'0x0015': ['SQ', '1', 'PrinterCharacteristicsSequence'],
|
|
'0x0030': ['SQ', '1', 'FilmBoxContentSequence'],
|
|
'0x0040': ['SQ', '1', 'ImageBoxContentSequence'],
|
|
'0x0050': ['SQ', '1', 'AnnotationContentSequence'],
|
|
'0x0060': ['SQ', '1', 'ImageOverlayBoxContentSequence'],
|
|
'0x0080': ['SQ', '1', 'PresentationLUTContentSequence'],
|
|
'0x00A0': ['SQ', '1', 'ProposedStudySequence'],
|
|
'0x00C0': ['SQ', '1', 'OriginalImageSequence']
|
|
},
|
|
'0x2200': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['CS', '1', 'LabelUsingInformationExtractedFromInstances'],
|
|
'0x0002': ['UT', '1', 'LabelText'],
|
|
'0x0003': ['CS', '1', 'LabelStyleSelection'],
|
|
'0x0004': ['LT', '1', 'MediaDisposition'],
|
|
'0x0005': ['LT', '1', 'BarcodeValue'],
|
|
'0x0006': ['CS', '1', 'BarcodeSymbology'],
|
|
'0x0007': ['CS', '1', 'AllowMediaSplitting'],
|
|
'0x0008': ['CS', '1', 'IncludeNonDICOMObjects'],
|
|
'0x0009': ['CS', '1', 'IncludeDisplayApplication'],
|
|
'0x000A': ['CS', '1', 'PreserveCompositeInstancesAfterMediaCreation'],
|
|
'0x000B': ['US', '1', 'TotalNumberOfPiecesOfMediaCreated'],
|
|
'0x000C': ['LO', '1', 'RequestedMediaApplicationProfile'],
|
|
'0x000D': ['SQ', '1', 'ReferencedStorageMediaSequence'],
|
|
'0x000E': ['AT', '1-n', 'FailureAttributes'],
|
|
'0x000F': ['CS', '1', 'AllowLossyCompression'],
|
|
'0x0020': ['CS', '1', 'RequestPriority']
|
|
},
|
|
'0x3002': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['SH', '1', 'RTImageLabel'],
|
|
'0x0003': ['LO', '1', 'RTImageName'],
|
|
'0x0004': ['ST', '1', 'RTImageDescription'],
|
|
'0x000A': ['CS', '1', 'ReportedValuesOrigin'],
|
|
'0x000C': ['CS', '1', 'RTImagePlane'],
|
|
'0x000D': ['DS', '3', 'XRayImageReceptorTranslation'],
|
|
'0x000E': ['DS', '1', 'XRayImageReceptorAngle'],
|
|
'0x0010': ['DS', '6', 'RTImageOrientation'],
|
|
'0x0011': ['DS', '2', 'ImagePlanePixelSpacing'],
|
|
'0x0012': ['DS', '2', 'RTImagePosition'],
|
|
'0x0020': ['SH', '1', 'RadiationMachineName'],
|
|
'0x0022': ['DS', '1', 'RadiationMachineSAD'],
|
|
'0x0024': ['DS', '1', 'RadiationMachineSSD'],
|
|
'0x0026': ['DS', '1', 'RTImageSID'],
|
|
'0x0028': ['DS', '1', 'SourceToReferenceObjectDistance'],
|
|
'0x0029': ['IS', '1', 'FractionNumber'],
|
|
'0x0030': ['SQ', '1', 'ExposureSequence'],
|
|
'0x0032': ['DS', '1', 'MetersetExposure'],
|
|
'0x0034': ['DS', '4', 'DiaphragmPosition'],
|
|
'0x0040': ['SQ', '1', 'FluenceMapSequence'],
|
|
'0x0041': ['CS', '1', 'FluenceDataSource'],
|
|
'0x0042': ['DS', '1', 'FluenceDataScale'],
|
|
'0x0050': ['SQ', '1', 'PrimaryFluenceModeSequence'],
|
|
'0x0051': ['CS', '1', 'FluenceMode'],
|
|
'0x0052': ['SH', '1', 'FluenceModeID']
|
|
},
|
|
'0x3004': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['CS', '1', 'DVHType'],
|
|
'0x0002': ['CS', '1', 'DoseUnits'],
|
|
'0x0004': ['CS', '1', 'DoseType'],
|
|
'0x0005': ['CS', '1', 'SpatialTransformOfDose'],
|
|
'0x0006': ['LO', '1', 'DoseComment'],
|
|
'0x0008': ['DS', '3', 'NormalizationPoint'],
|
|
'0x000A': ['CS', '1', 'DoseSummationType'],
|
|
'0x000C': ['DS', '2-n', 'GridFrameOffsetVector'],
|
|
'0x000E': ['DS', '1', 'DoseGridScaling'],
|
|
'0x0010': ['SQ', '1', 'RTDoseROISequence'],
|
|
'0x0012': ['DS', '1', 'DoseValue'],
|
|
'0x0014': ['CS', '1-3', 'TissueHeterogeneityCorrection'],
|
|
'0x0040': ['DS', '3', 'DVHNormalizationPoint'],
|
|
'0x0042': ['DS', '1', 'DVHNormalizationDoseValue'],
|
|
'0x0050': ['SQ', '1', 'DVHSequence'],
|
|
'0x0052': ['DS', '1', 'DVHDoseScaling'],
|
|
'0x0054': ['CS', '1', 'DVHVolumeUnits'],
|
|
'0x0056': ['IS', '1', 'DVHNumberOfBins'],
|
|
'0x0058': ['DS', '2-2n', 'DVHData'],
|
|
'0x0060': ['SQ', '1', 'DVHReferencedROISequence'],
|
|
'0x0062': ['CS', '1', 'DVHROIContributionType'],
|
|
'0x0070': ['DS', '1', 'DVHMinimumDose'],
|
|
'0x0072': ['DS', '1', 'DVHMaximumDose'],
|
|
'0x0074': ['DS', '1', 'DVHMeanDose']
|
|
},
|
|
'0x3006': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['SH', '1', 'StructureSetLabel'],
|
|
'0x0004': ['LO', '1', 'StructureSetName'],
|
|
'0x0006': ['ST', '1', 'StructureSetDescription'],
|
|
'0x0008': ['DA', '1', 'StructureSetDate'],
|
|
'0x0009': ['TM', '1', 'StructureSetTime'],
|
|
'0x0010': ['SQ', '1', 'ReferencedFrameOfReferenceSequence'],
|
|
'0x0012': ['SQ', '1', 'RTReferencedStudySequence'],
|
|
'0x0014': ['SQ', '1', 'RTReferencedSeriesSequence'],
|
|
'0x0016': ['SQ', '1', 'ContourImageSequence'],
|
|
'0x0018': ['SQ', '1', 'PredecessorStructureSetSequence'],
|
|
'0x0020': ['SQ', '1', 'StructureSetROISequence'],
|
|
'0x0022': ['IS', '1', 'ROINumber'],
|
|
'0x0024': ['UI', '1', 'ReferencedFrameOfReferenceUID'],
|
|
'0x0026': ['LO', '1', 'ROIName'],
|
|
'0x0028': ['ST', '1', 'ROIDescription'],
|
|
'0x002A': ['IS', '3', 'ROIDisplayColor'],
|
|
'0x002C': ['DS', '1', 'ROIVolume'],
|
|
'0x0030': ['SQ', '1', 'RTRelatedROISequence'],
|
|
'0x0033': ['CS', '1', 'RTROIRelationship'],
|
|
'0x0036': ['CS', '1', 'ROIGenerationAlgorithm'],
|
|
'0x0038': ['LO', '1', 'ROIGenerationDescription'],
|
|
'0x0039': ['SQ', '1', 'ROIContourSequence'],
|
|
'0x0040': ['SQ', '1', 'ContourSequence'],
|
|
'0x0042': ['CS', '1', 'ContourGeometricType'],
|
|
'0x0044': ['DS', '1', 'ContourSlabThickness'],
|
|
'0x0045': ['DS', '3', 'ContourOffsetVector'],
|
|
'0x0046': ['IS', '1', 'NumberOfContourPoints'],
|
|
'0x0048': ['IS', '1', 'ContourNumber'],
|
|
'0x0049': ['IS', '1-n', 'AttachedContours'],
|
|
'0x0050': ['DS', '3-3n', 'ContourData'],
|
|
'0x0080': ['SQ', '1', 'RTROIObservationsSequence'],
|
|
'0x0082': ['IS', '1', 'ObservationNumber'],
|
|
'0x0084': ['IS', '1', 'ReferencedROINumber'],
|
|
'0x0085': ['SH', '1', 'ROIObservationLabel'],
|
|
'0x0086': ['SQ', '1', 'RTROIIdentificationCodeSequence'],
|
|
'0x0088': ['ST', '1', 'ROIObservationDescription'],
|
|
'0x00A0': ['SQ', '1', 'RelatedRTROIObservationsSequence'],
|
|
'0x00A4': ['CS', '1', 'RTROIInterpretedType'],
|
|
'0x00A6': ['PN', '1', 'ROIInterpreter'],
|
|
'0x00B0': ['SQ', '1', 'ROIPhysicalPropertiesSequence'],
|
|
'0x00B2': ['CS', '1', 'ROIPhysicalProperty'],
|
|
'0x00B4': ['DS', '1', 'ROIPhysicalPropertyValue'],
|
|
'0x00B6': ['SQ', '1', 'ROIElementalCompositionSequence'],
|
|
'0x00B7': ['US', '1', 'ROIElementalCompositionAtomicNumber'],
|
|
'0x00B8': ['FL', '1', 'ROIElementalCompositionAtomicMassFraction'],
|
|
'0x00B9': ['SQ', '1', 'AdditionalRTROIIdentificationCodeSequence'],
|
|
'0x00C0': ['SQ', '1', 'FrameOfReferenceRelationshipSequence'],
|
|
'0x00C2': ['UI', '1', 'RelatedFrameOfReferenceUID'],
|
|
'0x00C4': ['CS', '1', 'FrameOfReferenceTransformationType'],
|
|
'0x00C6': ['DS', '16', 'FrameOfReferenceTransformationMatrix'],
|
|
'0x00C8': ['LO', '1', 'FrameOfReferenceTransformationComment']
|
|
},
|
|
'0x3008': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['SQ', '1', 'MeasuredDoseReferenceSequence'],
|
|
'0x0012': ['ST', '1', 'MeasuredDoseDescription'],
|
|
'0x0014': ['CS', '1', 'MeasuredDoseType'],
|
|
'0x0016': ['DS', '1', 'MeasuredDoseValue'],
|
|
'0x0020': ['SQ', '1', 'TreatmentSessionBeamSequence'],
|
|
'0x0021': ['SQ', '1', 'TreatmentSessionIonBeamSequence'],
|
|
'0x0022': ['IS', '1', 'CurrentFractionNumber'],
|
|
'0x0024': ['DA', '1', 'TreatmentControlPointDate'],
|
|
'0x0025': ['TM', '1', 'TreatmentControlPointTime'],
|
|
'0x002A': ['CS', '1', 'TreatmentTerminationStatus'],
|
|
'0x002B': ['SH', '1', 'TreatmentTerminationCode'],
|
|
'0x002C': ['CS', '1', 'TreatmentVerificationStatus'],
|
|
'0x0030': ['SQ', '1', 'ReferencedTreatmentRecordSequence'],
|
|
'0x0032': ['DS', '1', 'SpecifiedPrimaryMeterset'],
|
|
'0x0033': ['DS', '1', 'SpecifiedSecondaryMeterset'],
|
|
'0x0036': ['DS', '1', 'DeliveredPrimaryMeterset'],
|
|
'0x0037': ['DS', '1', 'DeliveredSecondaryMeterset'],
|
|
'0x003A': ['DS', '1', 'SpecifiedTreatmentTime'],
|
|
'0x003B': ['DS', '1', 'DeliveredTreatmentTime'],
|
|
'0x0040': ['SQ', '1', 'ControlPointDeliverySequence'],
|
|
'0x0041': ['SQ', '1', 'IonControlPointDeliverySequence'],
|
|
'0x0042': ['DS', '1', 'SpecifiedMeterset'],
|
|
'0x0044': ['DS', '1', 'DeliveredMeterset'],
|
|
'0x0045': ['FL', '1', 'MetersetRateSet'],
|
|
'0x0046': ['FL', '1', 'MetersetRateDelivered'],
|
|
'0x0047': ['FL', '1-n', 'ScanSpotMetersetsDelivered'],
|
|
'0x0048': ['DS', '1', 'DoseRateDelivered'],
|
|
'0x0050': ['SQ', '1', 'TreatmentSummaryCalculatedDoseReferenceSequence'],
|
|
'0x0052': ['DS', '1', 'CumulativeDoseToDoseReference'],
|
|
'0x0054': ['DA', '1', 'FirstTreatmentDate'],
|
|
'0x0056': ['DA', '1', 'MostRecentTreatmentDate'],
|
|
'0x005A': ['IS', '1', 'NumberOfFractionsDelivered'],
|
|
'0x0060': ['SQ', '1', 'OverrideSequence'],
|
|
'0x0061': ['AT', '1', 'ParameterSequencePointer'],
|
|
'0x0062': ['AT', '1', 'OverrideParameterPointer'],
|
|
'0x0063': ['IS', '1', 'ParameterItemIndex'],
|
|
'0x0064': ['IS', '1', 'MeasuredDoseReferenceNumber'],
|
|
'0x0065': ['AT', '1', 'ParameterPointer'],
|
|
'0x0066': ['ST', '1', 'OverrideReason'],
|
|
'0x0068': ['SQ', '1', 'CorrectedParameterSequence'],
|
|
'0x006A': ['FL', '1', 'CorrectionValue'],
|
|
'0x0070': ['SQ', '1', 'CalculatedDoseReferenceSequence'],
|
|
'0x0072': ['IS', '1', 'CalculatedDoseReferenceNumber'],
|
|
'0x0074': ['ST', '1', 'CalculatedDoseReferenceDescription'],
|
|
'0x0076': ['DS', '1', 'CalculatedDoseReferenceDoseValue'],
|
|
'0x0078': ['DS', '1', 'StartMeterset'],
|
|
'0x007A': ['DS', '1', 'EndMeterset'],
|
|
'0x0080': ['SQ', '1', 'ReferencedMeasuredDoseReferenceSequence'],
|
|
'0x0082': ['IS', '1', 'ReferencedMeasuredDoseReferenceNumber'],
|
|
'0x0090': ['SQ', '1', 'ReferencedCalculatedDoseReferenceSequence'],
|
|
'0x0092': ['IS', '1', 'ReferencedCalculatedDoseReferenceNumber'],
|
|
'0x00A0': ['SQ', '1', 'BeamLimitingDeviceLeafPairsSequence'],
|
|
'0x00B0': ['SQ', '1', 'RecordedWedgeSequence'],
|
|
'0x00C0': ['SQ', '1', 'RecordedCompensatorSequence'],
|
|
'0x00D0': ['SQ', '1', 'RecordedBlockSequence'],
|
|
'0x00E0': ['SQ', '1', 'TreatmentSummaryMeasuredDoseReferenceSequence'],
|
|
'0x00F0': ['SQ', '1', 'RecordedSnoutSequence'],
|
|
'0x00F2': ['SQ', '1', 'RecordedRangeShifterSequence'],
|
|
'0x00F4': ['SQ', '1', 'RecordedLateralSpreadingDeviceSequence'],
|
|
'0x00F6': ['SQ', '1', 'RecordedRangeModulatorSequence'],
|
|
'0x0100': ['SQ', '1', 'RecordedSourceSequence'],
|
|
'0x0105': ['LO', '1', 'SourceSerialNumber'],
|
|
'0x0110': ['SQ', '1', 'TreatmentSessionApplicationSetupSequence'],
|
|
'0x0116': ['CS', '1', 'ApplicationSetupCheck'],
|
|
'0x0120': ['SQ', '1', 'RecordedBrachyAccessoryDeviceSequence'],
|
|
'0x0122': ['IS', '1', 'ReferencedBrachyAccessoryDeviceNumber'],
|
|
'0x0130': ['SQ', '1', 'RecordedChannelSequence'],
|
|
'0x0132': ['DS', '1', 'SpecifiedChannelTotalTime'],
|
|
'0x0134': ['DS', '1', 'DeliveredChannelTotalTime'],
|
|
'0x0136': ['IS', '1', 'SpecifiedNumberOfPulses'],
|
|
'0x0138': ['IS', '1', 'DeliveredNumberOfPulses'],
|
|
'0x013A': ['DS', '1', 'SpecifiedPulseRepetitionInterval'],
|
|
'0x013C': ['DS', '1', 'DeliveredPulseRepetitionInterval'],
|
|
'0x0140': ['SQ', '1', 'RecordedSourceApplicatorSequence'],
|
|
'0x0142': ['IS', '1', 'ReferencedSourceApplicatorNumber'],
|
|
'0x0150': ['SQ', '1', 'RecordedChannelShieldSequence'],
|
|
'0x0152': ['IS', '1', 'ReferencedChannelShieldNumber'],
|
|
'0x0160': ['SQ', '1', 'BrachyControlPointDeliveredSequence'],
|
|
'0x0162': ['DA', '1', 'SafePositionExitDate'],
|
|
'0x0164': ['TM', '1', 'SafePositionExitTime'],
|
|
'0x0166': ['DA', '1', 'SafePositionReturnDate'],
|
|
'0x0168': ['TM', '1', 'SafePositionReturnTime'],
|
|
'0x0171': ['SQ', '1', 'PulseSpecificBrachyControlPointDeliveredSequence'],
|
|
'0x0172': ['US', '1', 'PulseNumber'],
|
|
'0x0173': ['SQ', '1', 'BrachyPulseControlPointDeliveredSequence'],
|
|
'0x0200': ['CS', '1', 'CurrentTreatmentStatus'],
|
|
'0x0202': ['ST', '1', 'TreatmentStatusComment'],
|
|
'0x0220': ['SQ', '1', 'FractionGroupSummarySequence'],
|
|
'0x0223': ['IS', '1', 'ReferencedFractionNumber'],
|
|
'0x0224': ['CS', '1', 'FractionGroupType'],
|
|
'0x0230': ['CS', '1', 'BeamStopperPosition'],
|
|
'0x0240': ['SQ', '1', 'FractionStatusSummarySequence'],
|
|
'0x0250': ['DA', '1', 'TreatmentDate'],
|
|
'0x0251': ['TM', '1', 'TreatmentTime']
|
|
},
|
|
'0x300A': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['SH', '1', 'RTPlanLabel'],
|
|
'0x0003': ['LO', '1', 'RTPlanName'],
|
|
'0x0004': ['ST', '1', 'RTPlanDescription'],
|
|
'0x0006': ['DA', '1', 'RTPlanDate'],
|
|
'0x0007': ['TM', '1', 'RTPlanTime'],
|
|
'0x0009': ['LO', '1-n', 'TreatmentProtocols'],
|
|
'0x000A': ['CS', '1', 'PlanIntent'],
|
|
'0x000B': ['LO', '1-n', 'TreatmentSites'],
|
|
'0x000C': ['CS', '1', 'RTPlanGeometry'],
|
|
'0x000E': ['ST', '1', 'PrescriptionDescription'],
|
|
'0x0010': ['SQ', '1', 'DoseReferenceSequence'],
|
|
'0x0012': ['IS', '1', 'DoseReferenceNumber'],
|
|
'0x0013': ['UI', '1', 'DoseReferenceUID'],
|
|
'0x0014': ['CS', '1', 'DoseReferenceStructureType'],
|
|
'0x0015': ['CS', '1', 'NominalBeamEnergyUnit'],
|
|
'0x0016': ['LO', '1', 'DoseReferenceDescription'],
|
|
'0x0018': ['DS', '3', 'DoseReferencePointCoordinates'],
|
|
'0x001A': ['DS', '1', 'NominalPriorDose'],
|
|
'0x0020': ['CS', '1', 'DoseReferenceType'],
|
|
'0x0021': ['DS', '1', 'ConstraintWeight'],
|
|
'0x0022': ['DS', '1', 'DeliveryWarningDose'],
|
|
'0x0023': ['DS', '1', 'DeliveryMaximumDose'],
|
|
'0x0025': ['DS', '1', 'TargetMinimumDose'],
|
|
'0x0026': ['DS', '1', 'TargetPrescriptionDose'],
|
|
'0x0027': ['DS', '1', 'TargetMaximumDose'],
|
|
'0x0028': ['DS', '1', 'TargetUnderdoseVolumeFraction'],
|
|
'0x002A': ['DS', '1', 'OrganAtRiskFullVolumeDose'],
|
|
'0x002B': ['DS', '1', 'OrganAtRiskLimitDose'],
|
|
'0x002C': ['DS', '1', 'OrganAtRiskMaximumDose'],
|
|
'0x002D': ['DS', '1', 'OrganAtRiskOverdoseVolumeFraction'],
|
|
'0x0040': ['SQ', '1', 'ToleranceTableSequence'],
|
|
'0x0042': ['IS', '1', 'ToleranceTableNumber'],
|
|
'0x0043': ['SH', '1', 'ToleranceTableLabel'],
|
|
'0x0044': ['DS', '1', 'GantryAngleTolerance'],
|
|
'0x0046': ['DS', '1', 'BeamLimitingDeviceAngleTolerance'],
|
|
'0x0048': ['SQ', '1', 'BeamLimitingDeviceToleranceSequence'],
|
|
'0x004A': ['DS', '1', 'BeamLimitingDevicePositionTolerance'],
|
|
'0x004B': ['FL', '1', 'SnoutPositionTolerance'],
|
|
'0x004C': ['DS', '1', 'PatientSupportAngleTolerance'],
|
|
'0x004E': ['DS', '1', 'TableTopEccentricAngleTolerance'],
|
|
'0x004F': ['FL', '1', 'TableTopPitchAngleTolerance'],
|
|
'0x0050': ['FL', '1', 'TableTopRollAngleTolerance'],
|
|
'0x0051': ['DS', '1', 'TableTopVerticalPositionTolerance'],
|
|
'0x0052': ['DS', '1', 'TableTopLongitudinalPositionTolerance'],
|
|
'0x0053': ['DS', '1', 'TableTopLateralPositionTolerance'],
|
|
'0x0055': ['CS', '1', 'RTPlanRelationship'],
|
|
'0x0070': ['SQ', '1', 'FractionGroupSequence'],
|
|
'0x0071': ['IS', '1', 'FractionGroupNumber'],
|
|
'0x0072': ['LO', '1', 'FractionGroupDescription'],
|
|
'0x0078': ['IS', '1', 'NumberOfFractionsPlanned'],
|
|
'0x0079': ['IS', '1', 'NumberOfFractionPatternDigitsPerDay'],
|
|
'0x007A': ['IS', '1', 'RepeatFractionCycleLength'],
|
|
'0x007B': ['LT', '1', 'FractionPattern'],
|
|
'0x0080': ['IS', '1', 'NumberOfBeams'],
|
|
'0x0082': ['DS', '3', 'BeamDoseSpecificationPoint'],
|
|
'0x0084': ['DS', '1', 'BeamDose'],
|
|
'0x0086': ['DS', '1', 'BeamMeterset'],
|
|
'0x0088': ['FL', '1', 'BeamDosePointDepth'],
|
|
'0x0089': ['FL', '1', 'BeamDosePointEquivalentDepth'],
|
|
'0x008A': ['FL', '1', 'BeamDosePointSSD'],
|
|
'0x008B': ['CS', '1', 'BeamDoseMeaning'],
|
|
'0x008C': ['SQ', '1', 'BeamDoseVerificationControlPointSequence'],
|
|
'0x008D': ['FL', '1', 'AverageBeamDosePointDepth'],
|
|
'0x008E': ['FL', '1', 'AverageBeamDosePointEquivalentDepth'],
|
|
'0x008F': ['FL', '1', 'AverageBeamDosePointSSD'],
|
|
'0x00A0': ['IS', '1', 'NumberOfBrachyApplicationSetups'],
|
|
'0x00A2': ['DS', '3', 'BrachyApplicationSetupDoseSpecificationPoint'],
|
|
'0x00A4': ['DS', '1', 'BrachyApplicationSetupDose'],
|
|
'0x00B0': ['SQ', '1', 'BeamSequence'],
|
|
'0x00B2': ['SH', '1', 'TreatmentMachineName'],
|
|
'0x00B3': ['CS', '1', 'PrimaryDosimeterUnit'],
|
|
'0x00B4': ['DS', '1', 'SourceAxisDistance'],
|
|
'0x00B6': ['SQ', '1', 'BeamLimitingDeviceSequence'],
|
|
'0x00B8': ['CS', '1', 'RTBeamLimitingDeviceType'],
|
|
'0x00BA': ['DS', '1', 'SourceToBeamLimitingDeviceDistance'],
|
|
'0x00BB': ['FL', '1', 'IsocenterToBeamLimitingDeviceDistance'],
|
|
'0x00BC': ['IS', '1', 'NumberOfLeafJawPairs'],
|
|
'0x00BE': ['DS', '3-n', 'LeafPositionBoundaries'],
|
|
'0x00C0': ['IS', '1', 'BeamNumber'],
|
|
'0x00C2': ['LO', '1', 'BeamName'],
|
|
'0x00C3': ['ST', '1', 'BeamDescription'],
|
|
'0x00C4': ['CS', '1', 'BeamType'],
|
|
'0x00C5': ['FD', '1', 'BeamDeliveryDurationLimit'],
|
|
'0x00C6': ['CS', '1', 'RadiationType'],
|
|
'0x00C7': ['CS', '1', 'HighDoseTechniqueType'],
|
|
'0x00C8': ['IS', '1', 'ReferenceImageNumber'],
|
|
'0x00CA': ['SQ', '1', 'PlannedVerificationImageSequence'],
|
|
'0x00CC': ['LO', '1-n', 'ImagingDeviceSpecificAcquisitionParameters'],
|
|
'0x00CE': ['CS', '1', 'TreatmentDeliveryType'],
|
|
'0x00D0': ['IS', '1', 'NumberOfWedges'],
|
|
'0x00D1': ['SQ', '1', 'WedgeSequence'],
|
|
'0x00D2': ['IS', '1', 'WedgeNumber'],
|
|
'0x00D3': ['CS', '1', 'WedgeType'],
|
|
'0x00D4': ['SH', '1', 'WedgeID'],
|
|
'0x00D5': ['IS', '1', 'WedgeAngle'],
|
|
'0x00D6': ['DS', '1', 'WedgeFactor'],
|
|
'0x00D7': ['FL', '1', 'TotalWedgeTrayWaterEquivalentThickness'],
|
|
'0x00D8': ['DS', '1', 'WedgeOrientation'],
|
|
'0x00D9': ['FL', '1', 'IsocenterToWedgeTrayDistance'],
|
|
'0x00DA': ['DS', '1', 'SourceToWedgeTrayDistance'],
|
|
'0x00DB': ['FL', '1', 'WedgeThinEdgePosition'],
|
|
'0x00DC': ['SH', '1', 'BolusID'],
|
|
'0x00DD': ['ST', '1', 'BolusDescription'],
|
|
'0x00DE': ['DS', '1', 'EffectiveWedgeAngle'],
|
|
'0x00E0': ['IS', '1', 'NumberOfCompensators'],
|
|
'0x00E1': ['SH', '1', 'MaterialID'],
|
|
'0x00E2': ['DS', '1', 'TotalCompensatorTrayFactor'],
|
|
'0x00E3': ['SQ', '1', 'CompensatorSequence'],
|
|
'0x00E4': ['IS', '1', 'CompensatorNumber'],
|
|
'0x00E5': ['SH', '1', 'CompensatorID'],
|
|
'0x00E6': ['DS', '1', 'SourceToCompensatorTrayDistance'],
|
|
'0x00E7': ['IS', '1', 'CompensatorRows'],
|
|
'0x00E8': ['IS', '1', 'CompensatorColumns'],
|
|
'0x00E9': ['DS', '2', 'CompensatorPixelSpacing'],
|
|
'0x00EA': ['DS', '2', 'CompensatorPosition'],
|
|
'0x00EB': ['DS', '1-n', 'CompensatorTransmissionData'],
|
|
'0x00EC': ['DS', '1-n', 'CompensatorThicknessData'],
|
|
'0x00ED': ['IS', '1', 'NumberOfBoli'],
|
|
'0x00EE': ['CS', '1', 'CompensatorType'],
|
|
'0x00EF': ['SH', '1', 'CompensatorTrayID'],
|
|
'0x00F0': ['IS', '1', 'NumberOfBlocks'],
|
|
'0x00F2': ['DS', '1', 'TotalBlockTrayFactor'],
|
|
'0x00F3': ['FL', '1', 'TotalBlockTrayWaterEquivalentThickness'],
|
|
'0x00F4': ['SQ', '1', 'BlockSequence'],
|
|
'0x00F5': ['SH', '1', 'BlockTrayID'],
|
|
'0x00F6': ['DS', '1', 'SourceToBlockTrayDistance'],
|
|
'0x00F7': ['FL', '1', 'IsocenterToBlockTrayDistance'],
|
|
'0x00F8': ['CS', '1', 'BlockType'],
|
|
'0x00F9': ['LO', '1', 'AccessoryCode'],
|
|
'0x00FA': ['CS', '1', 'BlockDivergence'],
|
|
'0x00FB': ['CS', '1', 'BlockMountingPosition'],
|
|
'0x00FC': ['IS', '1', 'BlockNumber'],
|
|
'0x00FE': ['LO', '1', 'BlockName'],
|
|
'0x0100': ['DS', '1', 'BlockThickness'],
|
|
'0x0102': ['DS', '1', 'BlockTransmission'],
|
|
'0x0104': ['IS', '1', 'BlockNumberOfPoints'],
|
|
'0x0106': ['DS', '2-2n', 'BlockData'],
|
|
'0x0107': ['SQ', '1', 'ApplicatorSequence'],
|
|
'0x0108': ['SH', '1', 'ApplicatorID'],
|
|
'0x0109': ['CS', '1', 'ApplicatorType'],
|
|
'0x010A': ['LO', '1', 'ApplicatorDescription'],
|
|
'0x010C': ['DS', '1', 'CumulativeDoseReferenceCoefficient'],
|
|
'0x010E': ['DS', '1', 'FinalCumulativeMetersetWeight'],
|
|
'0x0110': ['IS', '1', 'NumberOfControlPoints'],
|
|
'0x0111': ['SQ', '1', 'ControlPointSequence'],
|
|
'0x0112': ['IS', '1', 'ControlPointIndex'],
|
|
'0x0114': ['DS', '1', 'NominalBeamEnergy'],
|
|
'0x0115': ['DS', '1', 'DoseRateSet'],
|
|
'0x0116': ['SQ', '1', 'WedgePositionSequence'],
|
|
'0x0118': ['CS', '1', 'WedgePosition'],
|
|
'0x011A': ['SQ', '1', 'BeamLimitingDevicePositionSequence'],
|
|
'0x011C': ['DS', '2-2n', 'LeafJawPositions'],
|
|
'0x011E': ['DS', '1', 'GantryAngle'],
|
|
'0x011F': ['CS', '1', 'GantryRotationDirection'],
|
|
'0x0120': ['DS', '1', 'BeamLimitingDeviceAngle'],
|
|
'0x0121': ['CS', '1', 'BeamLimitingDeviceRotationDirection'],
|
|
'0x0122': ['DS', '1', 'PatientSupportAngle'],
|
|
'0x0123': ['CS', '1', 'PatientSupportRotationDirection'],
|
|
'0x0124': ['DS', '1', 'TableTopEccentricAxisDistance'],
|
|
'0x0125': ['DS', '1', 'TableTopEccentricAngle'],
|
|
'0x0126': ['CS', '1', 'TableTopEccentricRotationDirection'],
|
|
'0x0128': ['DS', '1', 'TableTopVerticalPosition'],
|
|
'0x0129': ['DS', '1', 'TableTopLongitudinalPosition'],
|
|
'0x012A': ['DS', '1', 'TableTopLateralPosition'],
|
|
'0x012C': ['DS', '3', 'IsocenterPosition'],
|
|
'0x012E': ['DS', '3', 'SurfaceEntryPoint'],
|
|
'0x0130': ['DS', '1', 'SourceToSurfaceDistance'],
|
|
'0x0131': ['FL', '1', 'AverageBeamDosePointSourceToExternalContourSurfaceDistance'],
|
|
'0x0132': ['FL', '1', 'SourceToExternalContourDistance'],
|
|
'0x0133': ['FL', '3', 'ExternalContourEntryPoint'],
|
|
'0x0134': ['DS', '1', 'CumulativeMetersetWeight'],
|
|
'0x0140': ['FL', '1', 'TableTopPitchAngle'],
|
|
'0x0142': ['CS', '1', 'TableTopPitchRotationDirection'],
|
|
'0x0144': ['FL', '1', 'TableTopRollAngle'],
|
|
'0x0146': ['CS', '1', 'TableTopRollRotationDirection'],
|
|
'0x0148': ['FL', '1', 'HeadFixationAngle'],
|
|
'0x014A': ['FL', '1', 'GantryPitchAngle'],
|
|
'0x014C': ['CS', '1', 'GantryPitchRotationDirection'],
|
|
'0x014E': ['FL', '1', 'GantryPitchAngleTolerance'],
|
|
'0x0180': ['SQ', '1', 'PatientSetupSequence'],
|
|
'0x0182': ['IS', '1', 'PatientSetupNumber'],
|
|
'0x0183': ['LO', '1', 'PatientSetupLabel'],
|
|
'0x0184': ['LO', '1', 'PatientAdditionalPosition'],
|
|
'0x0190': ['SQ', '1', 'FixationDeviceSequence'],
|
|
'0x0192': ['CS', '1', 'FixationDeviceType'],
|
|
'0x0194': ['SH', '1', 'FixationDeviceLabel'],
|
|
'0x0196': ['ST', '1', 'FixationDeviceDescription'],
|
|
'0x0198': ['SH', '1', 'FixationDevicePosition'],
|
|
'0x0199': ['FL', '1', 'FixationDevicePitchAngle'],
|
|
'0x019A': ['FL', '1', 'FixationDeviceRollAngle'],
|
|
'0x01A0': ['SQ', '1', 'ShieldingDeviceSequence'],
|
|
'0x01A2': ['CS', '1', 'ShieldingDeviceType'],
|
|
'0x01A4': ['SH', '1', 'ShieldingDeviceLabel'],
|
|
'0x01A6': ['ST', '1', 'ShieldingDeviceDescription'],
|
|
'0x01A8': ['SH', '1', 'ShieldingDevicePosition'],
|
|
'0x01B0': ['CS', '1', 'SetupTechnique'],
|
|
'0x01B2': ['ST', '1', 'SetupTechniqueDescription'],
|
|
'0x01B4': ['SQ', '1', 'SetupDeviceSequence'],
|
|
'0x01B6': ['CS', '1', 'SetupDeviceType'],
|
|
'0x01B8': ['SH', '1', 'SetupDeviceLabel'],
|
|
'0x01BA': ['ST', '1', 'SetupDeviceDescription'],
|
|
'0x01BC': ['DS', '1', 'SetupDeviceParameter'],
|
|
'0x01D0': ['ST', '1', 'SetupReferenceDescription'],
|
|
'0x01D2': ['DS', '1', 'TableTopVerticalSetupDisplacement'],
|
|
'0x01D4': ['DS', '1', 'TableTopLongitudinalSetupDisplacement'],
|
|
'0x01D6': ['DS', '1', 'TableTopLateralSetupDisplacement'],
|
|
'0x0200': ['CS', '1', 'BrachyTreatmentTechnique'],
|
|
'0x0202': ['CS', '1', 'BrachyTreatmentType'],
|
|
'0x0206': ['SQ', '1', 'TreatmentMachineSequence'],
|
|
'0x0210': ['SQ', '1', 'SourceSequence'],
|
|
'0x0212': ['IS', '1', 'SourceNumber'],
|
|
'0x0214': ['CS', '1', 'SourceType'],
|
|
'0x0216': ['LO', '1', 'SourceManufacturer'],
|
|
'0x0218': ['DS', '1', 'ActiveSourceDiameter'],
|
|
'0x021A': ['DS', '1', 'ActiveSourceLength'],
|
|
'0x021B': ['SH', '1', 'SourceModelID'],
|
|
'0x021C': ['LO', '1', 'SourceDescription'],
|
|
'0x0222': ['DS', '1', 'SourceEncapsulationNominalThickness'],
|
|
'0x0224': ['DS', '1', 'SourceEncapsulationNominalTransmission'],
|
|
'0x0226': ['LO', '1', 'SourceIsotopeName'],
|
|
'0x0228': ['DS', '1', 'SourceIsotopeHalfLife'],
|
|
'0x0229': ['CS', '1', 'SourceStrengthUnits'],
|
|
'0x022A': ['DS', '1', 'ReferenceAirKermaRate'],
|
|
'0x022B': ['DS', '1', 'SourceStrength'],
|
|
'0x022C': ['DA', '1', 'SourceStrengthReferenceDate'],
|
|
'0x022E': ['TM', '1', 'SourceStrengthReferenceTime'],
|
|
'0x0230': ['SQ', '1', 'ApplicationSetupSequence'],
|
|
'0x0232': ['CS', '1', 'ApplicationSetupType'],
|
|
'0x0234': ['IS', '1', 'ApplicationSetupNumber'],
|
|
'0x0236': ['LO', '1', 'ApplicationSetupName'],
|
|
'0x0238': ['LO', '1', 'ApplicationSetupManufacturer'],
|
|
'0x0240': ['IS', '1', 'TemplateNumber'],
|
|
'0x0242': ['SH', '1', 'TemplateType'],
|
|
'0x0244': ['LO', '1', 'TemplateName'],
|
|
'0x0250': ['DS', '1', 'TotalReferenceAirKerma'],
|
|
'0x0260': ['SQ', '1', 'BrachyAccessoryDeviceSequence'],
|
|
'0x0262': ['IS', '1', 'BrachyAccessoryDeviceNumber'],
|
|
'0x0263': ['SH', '1', 'BrachyAccessoryDeviceID'],
|
|
'0x0264': ['CS', '1', 'BrachyAccessoryDeviceType'],
|
|
'0x0266': ['LO', '1', 'BrachyAccessoryDeviceName'],
|
|
'0x026A': ['DS', '1', 'BrachyAccessoryDeviceNominalThickness'],
|
|
'0x026C': ['DS', '1', 'BrachyAccessoryDeviceNominalTransmission'],
|
|
'0x0280': ['SQ', '1', 'ChannelSequence'],
|
|
'0x0282': ['IS', '1', 'ChannelNumber'],
|
|
'0x0284': ['DS', '1', 'ChannelLength'],
|
|
'0x0286': ['DS', '1', 'ChannelTotalTime'],
|
|
'0x0288': ['CS', '1', 'SourceMovementType'],
|
|
'0x028A': ['IS', '1', 'NumberOfPulses'],
|
|
'0x028C': ['DS', '1', 'PulseRepetitionInterval'],
|
|
'0x0290': ['IS', '1', 'SourceApplicatorNumber'],
|
|
'0x0291': ['SH', '1', 'SourceApplicatorID'],
|
|
'0x0292': ['CS', '1', 'SourceApplicatorType'],
|
|
'0x0294': ['LO', '1', 'SourceApplicatorName'],
|
|
'0x0296': ['DS', '1', 'SourceApplicatorLength'],
|
|
'0x0298': ['LO', '1', 'SourceApplicatorManufacturer'],
|
|
'0x029C': ['DS', '1', 'SourceApplicatorWallNominalThickness'],
|
|
'0x029E': ['DS', '1', 'SourceApplicatorWallNominalTransmission'],
|
|
'0x02A0': ['DS', '1', 'SourceApplicatorStepSize'],
|
|
'0x02A2': ['IS', '1', 'TransferTubeNumber'],
|
|
'0x02A4': ['DS', '1', 'TransferTubeLength'],
|
|
'0x02B0': ['SQ', '1', 'ChannelShieldSequence'],
|
|
'0x02B2': ['IS', '1', 'ChannelShieldNumber'],
|
|
'0x02B3': ['SH', '1', 'ChannelShieldID'],
|
|
'0x02B4': ['LO', '1', 'ChannelShieldName'],
|
|
'0x02B8': ['DS', '1', 'ChannelShieldNominalThickness'],
|
|
'0x02BA': ['DS', '1', 'ChannelShieldNominalTransmission'],
|
|
'0x02C8': ['DS', '1', 'FinalCumulativeTimeWeight'],
|
|
'0x02D0': ['SQ', '1', 'BrachyControlPointSequence'],
|
|
'0x02D2': ['DS', '1', 'ControlPointRelativePosition'],
|
|
'0x02D4': ['DS', '3', 'ControlPoint3DPosition'],
|
|
'0x02D6': ['DS', '1', 'CumulativeTimeWeight'],
|
|
'0x02E0': ['CS', '1', 'CompensatorDivergence'],
|
|
'0x02E1': ['CS', '1', 'CompensatorMountingPosition'],
|
|
'0x02E2': ['DS', '1-n', 'SourceToCompensatorDistance'],
|
|
'0x02E3': ['FL', '1', 'TotalCompensatorTrayWaterEquivalentThickness'],
|
|
'0x02E4': ['FL', '1', 'IsocenterToCompensatorTrayDistance'],
|
|
'0x02E5': ['FL', '1', 'CompensatorColumnOffset'],
|
|
'0x02E6': ['FL', '1-n', 'IsocenterToCompensatorDistances'],
|
|
'0x02E7': ['FL', '1', 'CompensatorRelativeStoppingPowerRatio'],
|
|
'0x02E8': ['FL', '1', 'CompensatorMillingToolDiameter'],
|
|
'0x02EA': ['SQ', '1', 'IonRangeCompensatorSequence'],
|
|
'0x02EB': ['LT', '1', 'CompensatorDescription'],
|
|
'0x0302': ['IS', '1', 'RadiationMassNumber'],
|
|
'0x0304': ['IS', '1', 'RadiationAtomicNumber'],
|
|
'0x0306': ['SS', '1', 'RadiationChargeState'],
|
|
'0x0308': ['CS', '1', 'ScanMode'],
|
|
'0x030A': ['FL', '2', 'VirtualSourceAxisDistances'],
|
|
'0x030C': ['SQ', '1', 'SnoutSequence'],
|
|
'0x030D': ['FL', '1', 'SnoutPosition'],
|
|
'0x030F': ['SH', '1', 'SnoutID'],
|
|
'0x0312': ['IS', '1', 'NumberOfRangeShifters'],
|
|
'0x0314': ['SQ', '1', 'RangeShifterSequence'],
|
|
'0x0316': ['IS', '1', 'RangeShifterNumber'],
|
|
'0x0318': ['SH', '1', 'RangeShifterID'],
|
|
'0x0320': ['CS', '1', 'RangeShifterType'],
|
|
'0x0322': ['LO', '1', 'RangeShifterDescription'],
|
|
'0x0330': ['IS', '1', 'NumberOfLateralSpreadingDevices'],
|
|
'0x0332': ['SQ', '1', 'LateralSpreadingDeviceSequence'],
|
|
'0x0334': ['IS', '1', 'LateralSpreadingDeviceNumber'],
|
|
'0x0336': ['SH', '1', 'LateralSpreadingDeviceID'],
|
|
'0x0338': ['CS', '1', 'LateralSpreadingDeviceType'],
|
|
'0x033A': ['LO', '1', 'LateralSpreadingDeviceDescription'],
|
|
'0x033C': ['FL', '1', 'LateralSpreadingDeviceWaterEquivalentThickness'],
|
|
'0x0340': ['IS', '1', 'NumberOfRangeModulators'],
|
|
'0x0342': ['SQ', '1', 'RangeModulatorSequence'],
|
|
'0x0344': ['IS', '1', 'RangeModulatorNumber'],
|
|
'0x0346': ['SH', '1', 'RangeModulatorID'],
|
|
'0x0348': ['CS', '1', 'RangeModulatorType'],
|
|
'0x034A': ['LO', '1', 'RangeModulatorDescription'],
|
|
'0x034C': ['SH', '1', 'BeamCurrentModulationID'],
|
|
'0x0350': ['CS', '1', 'PatientSupportType'],
|
|
'0x0352': ['SH', '1', 'PatientSupportID'],
|
|
'0x0354': ['LO', '1', 'PatientSupportAccessoryCode'],
|
|
'0x0356': ['FL', '1', 'FixationLightAzimuthalAngle'],
|
|
'0x0358': ['FL', '1', 'FixationLightPolarAngle'],
|
|
'0x035A': ['FL', '1', 'MetersetRate'],
|
|
'0x0360': ['SQ', '1', 'RangeShifterSettingsSequence'],
|
|
'0x0362': ['LO', '1', 'RangeShifterSetting'],
|
|
'0x0364': ['FL', '1', 'IsocenterToRangeShifterDistance'],
|
|
'0x0366': ['FL', '1', 'RangeShifterWaterEquivalentThickness'],
|
|
'0x0370': ['SQ', '1', 'LateralSpreadingDeviceSettingsSequence'],
|
|
'0x0372': ['LO', '1', 'LateralSpreadingDeviceSetting'],
|
|
'0x0374': ['FL', '1', 'IsocenterToLateralSpreadingDeviceDistance'],
|
|
'0x0380': ['SQ', '1', 'RangeModulatorSettingsSequence'],
|
|
'0x0382': ['FL', '1', 'RangeModulatorGatingStartValue'],
|
|
'0x0384': ['FL', '1', 'RangeModulatorGatingStopValue'],
|
|
'0x0386': ['FL', '1', 'RangeModulatorGatingStartWaterEquivalentThickness'],
|
|
'0x0388': ['FL', '1', 'RangeModulatorGatingStopWaterEquivalentThickness'],
|
|
'0x038A': ['FL', '1', 'IsocenterToRangeModulatorDistance'],
|
|
'0x0390': ['SH', '1', 'ScanSpotTuneID'],
|
|
'0x0392': ['IS', '1', 'NumberOfScanSpotPositions'],
|
|
'0x0394': ['FL', '1-n', 'ScanSpotPositionMap'],
|
|
'0x0396': ['FL', '1-n', 'ScanSpotMetersetWeights'],
|
|
'0x0398': ['FL', '2', 'ScanningSpotSize'],
|
|
'0x039A': ['IS', '1', 'NumberOfPaintings'],
|
|
'0x03A0': ['SQ', '1', 'IonToleranceTableSequence'],
|
|
'0x03A2': ['SQ', '1', 'IonBeamSequence'],
|
|
'0x03A4': ['SQ', '1', 'IonBeamLimitingDeviceSequence'],
|
|
'0x03A6': ['SQ', '1', 'IonBlockSequence'],
|
|
'0x03A8': ['SQ', '1', 'IonControlPointSequence'],
|
|
'0x03AA': ['SQ', '1', 'IonWedgeSequence'],
|
|
'0x03AC': ['SQ', '1', 'IonWedgePositionSequence'],
|
|
'0x0401': ['SQ', '1', 'ReferencedSetupImageSequence'],
|
|
'0x0402': ['ST', '1', 'SetupImageComment'],
|
|
'0x0410': ['SQ', '1', 'MotionSynchronizationSequence'],
|
|
'0x0412': ['FL', '3', 'ControlPointOrientation'],
|
|
'0x0420': ['SQ', '1', 'GeneralAccessorySequence'],
|
|
'0x0421': ['SH', '1', 'GeneralAccessoryID'],
|
|
'0x0422': ['ST', '1', 'GeneralAccessoryDescription'],
|
|
'0x0423': ['CS', '1', 'GeneralAccessoryType'],
|
|
'0x0424': ['IS', '1', 'GeneralAccessoryNumber'],
|
|
'0x0425': ['FL', '1', 'SourceToGeneralAccessoryDistance'],
|
|
'0x0431': ['SQ', '1', 'ApplicatorGeometrySequence'],
|
|
'0x0432': ['CS', '1', 'ApplicatorApertureShape'],
|
|
'0x0433': ['FL', '1', 'ApplicatorOpening'],
|
|
'0x0434': ['FL', '1', 'ApplicatorOpeningX'],
|
|
'0x0435': ['FL', '1', 'ApplicatorOpeningY'],
|
|
'0x0436': ['FL', '1', 'SourceToApplicatorMountingPositionDistance'],
|
|
'0x0440': ['IS', '1', 'NumberOfBlockSlabItems'],
|
|
'0x0441': ['SQ', '1', 'BlockSlabSequence'],
|
|
'0x0442': ['DS', '1', 'BlockSlabThickness'],
|
|
'0x0443': ['US', '1', 'BlockSlabNumber'],
|
|
'0x0450': ['SQ', '1', 'DeviceMotionControlSequence'],
|
|
'0x0451': ['CS', '1', 'DeviceMotionExecutionMode'],
|
|
'0x0452': ['CS', '1', 'DeviceMotionObservationMode'],
|
|
'0x0453': ['SQ', '1', 'DeviceMotionParameterCodeSequence']
|
|
},
|
|
'0x300C': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['SQ', '1', 'ReferencedRTPlanSequence'],
|
|
'0x0004': ['SQ', '1', 'ReferencedBeamSequence'],
|
|
'0x0006': ['IS', '1', 'ReferencedBeamNumber'],
|
|
'0x0007': ['IS', '1', 'ReferencedReferenceImageNumber'],
|
|
'0x0008': ['DS', '1', 'StartCumulativeMetersetWeight'],
|
|
'0x0009': ['DS', '1', 'EndCumulativeMetersetWeight'],
|
|
'0x000A': ['SQ', '1', 'ReferencedBrachyApplicationSetupSequence'],
|
|
'0x000C': ['IS', '1', 'ReferencedBrachyApplicationSetupNumber'],
|
|
'0x000E': ['IS', '1', 'ReferencedSourceNumber'],
|
|
'0x0020': ['SQ', '1', 'ReferencedFractionGroupSequence'],
|
|
'0x0022': ['IS', '1', 'ReferencedFractionGroupNumber'],
|
|
'0x0040': ['SQ', '1', 'ReferencedVerificationImageSequence'],
|
|
'0x0042': ['SQ', '1', 'ReferencedReferenceImageSequence'],
|
|
'0x0050': ['SQ', '1', 'ReferencedDoseReferenceSequence'],
|
|
'0x0051': ['IS', '1', 'ReferencedDoseReferenceNumber'],
|
|
'0x0055': ['SQ', '1', 'BrachyReferencedDoseReferenceSequence'],
|
|
'0x0060': ['SQ', '1', 'ReferencedStructureSetSequence'],
|
|
'0x006A': ['IS', '1', 'ReferencedPatientSetupNumber'],
|
|
'0x0080': ['SQ', '1', 'ReferencedDoseSequence'],
|
|
'0x00A0': ['IS', '1', 'ReferencedToleranceTableNumber'],
|
|
'0x00B0': ['SQ', '1', 'ReferencedBolusSequence'],
|
|
'0x00C0': ['IS', '1', 'ReferencedWedgeNumber'],
|
|
'0x00D0': ['IS', '1', 'ReferencedCompensatorNumber'],
|
|
'0x00E0': ['IS', '1', 'ReferencedBlockNumber'],
|
|
'0x00F0': ['IS', '1', 'ReferencedControlPointIndex'],
|
|
'0x00F2': ['SQ', '1', 'ReferencedControlPointSequence'],
|
|
'0x00F4': ['IS', '1', 'ReferencedStartControlPointIndex'],
|
|
'0x00F6': ['IS', '1', 'ReferencedStopControlPointIndex'],
|
|
'0x0100': ['IS', '1', 'ReferencedRangeShifterNumber'],
|
|
'0x0102': ['IS', '1', 'ReferencedLateralSpreadingDeviceNumber'],
|
|
'0x0104': ['IS', '1', 'ReferencedRangeModulatorNumber'],
|
|
'0x0111': ['SQ', '1', 'OmittedBeamTaskSequence'],
|
|
'0x0112': ['CS', '1', 'ReasonForOmission'],
|
|
'0x0113': ['LO', '1', 'ReasonForOmissionDescription']
|
|
},
|
|
'0x300E': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0002': ['CS', '1', 'ApprovalStatus'],
|
|
'0x0004': ['DA', '1', 'ReviewDate'],
|
|
'0x0005': ['TM', '1', 'ReviewTime'],
|
|
'0x0008': ['PN', '1', 'ReviewerName']
|
|
},
|
|
'0x4000': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['LT', '1', 'Arbitrary'],
|
|
'0x4000': ['LT', '1', 'TextComments']
|
|
},
|
|
'0x4008': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0040': ['SH', '1', 'ResultsID'],
|
|
'0x0042': ['LO', '1', 'ResultsIDIssuer'],
|
|
'0x0050': ['SQ', '1', 'ReferencedInterpretationSequence'],
|
|
'0x00FF': ['CS', '1', 'ReportProductionStatusTrial'],
|
|
'0x0100': ['DA', '1', 'InterpretationRecordedDate'],
|
|
'0x0101': ['TM', '1', 'InterpretationRecordedTime'],
|
|
'0x0102': ['PN', '1', 'InterpretationRecorder'],
|
|
'0x0103': ['LO', '1', 'ReferenceToRecordedSound'],
|
|
'0x0108': ['DA', '1', 'InterpretationTranscriptionDate'],
|
|
'0x0109': ['TM', '1', 'InterpretationTranscriptionTime'],
|
|
'0x010A': ['PN', '1', 'InterpretationTranscriber'],
|
|
'0x010B': ['ST', '1', 'InterpretationText'],
|
|
'0x010C': ['PN', '1', 'InterpretationAuthor'],
|
|
'0x0111': ['SQ', '1', 'InterpretationApproverSequence'],
|
|
'0x0112': ['DA', '1', 'InterpretationApprovalDate'],
|
|
'0x0113': ['TM', '1', 'InterpretationApprovalTime'],
|
|
'0x0114': ['PN', '1', 'PhysicianApprovingInterpretation'],
|
|
'0x0115': ['LT', '1', 'InterpretationDiagnosisDescription'],
|
|
'0x0117': ['SQ', '1', 'InterpretationDiagnosisCodeSequence'],
|
|
'0x0118': ['SQ', '1', 'ResultsDistributionListSequence'],
|
|
'0x0119': ['PN', '1', 'DistributionName'],
|
|
'0x011A': ['LO', '1', 'DistributionAddress'],
|
|
'0x0200': ['SH', '1', 'InterpretationID'],
|
|
'0x0202': ['LO', '1', 'InterpretationIDIssuer'],
|
|
'0x0210': ['CS', '1', 'InterpretationTypeID'],
|
|
'0x0212': ['CS', '1', 'InterpretationStatusID'],
|
|
'0x0300': ['ST', '1', 'Impressions'],
|
|
'0x4000': ['ST', '1', 'ResultsComments']
|
|
},
|
|
'0x4010': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['CS', '1', 'LowEnergyDetectors'],
|
|
'0x0002': ['CS', '1', 'HighEnergyDetectors'],
|
|
'0x0004': ['SQ', '1', 'DetectorGeometrySequence'],
|
|
'0x1001': ['SQ', '1', 'ThreatROIVoxelSequence'],
|
|
'0x1004': ['FL', '3', 'ThreatROIBase'],
|
|
'0x1005': ['FL', '3', 'ThreatROIExtents'],
|
|
'0x1006': ['OB', '1', 'ThreatROIBitmap'],
|
|
'0x1007': ['SH', '1', 'RouteSegmentID'],
|
|
'0x1008': ['CS', '1', 'GantryType'],
|
|
'0x1009': ['CS', '1', 'OOIOwnerType'],
|
|
'0x100A': ['SQ', '1', 'RouteSegmentSequence'],
|
|
'0x1010': ['US', '1', 'PotentialThreatObjectID'],
|
|
'0x1011': ['SQ', '1', 'ThreatSequence'],
|
|
'0x1012': ['CS', '1', 'ThreatCategory'],
|
|
'0x1013': ['LT', '1', 'ThreatCategoryDescription'],
|
|
'0x1014': ['CS', '1', 'ATDAbilityAssessment'],
|
|
'0x1015': ['CS', '1', 'ATDAssessmentFlag'],
|
|
'0x1016': ['FL', '1', 'ATDAssessmentProbability'],
|
|
'0x1017': ['FL', '1', 'Mass'],
|
|
'0x1018': ['FL', '1', 'Density'],
|
|
'0x1019': ['FL', '1', 'ZEffective'],
|
|
'0x101A': ['SH', '1', 'BoardingPassID'],
|
|
'0x101B': ['FL', '3', 'CenterOfMass'],
|
|
'0x101C': ['FL', '3', 'CenterOfPTO'],
|
|
'0x101D': ['FL', '6-n', 'BoundingPolygon'],
|
|
'0x101E': ['SH', '1', 'RouteSegmentStartLocationID'],
|
|
'0x101F': ['SH', '1', 'RouteSegmentEndLocationID'],
|
|
'0x1020': ['CS', '1', 'RouteSegmentLocationIDType'],
|
|
'0x1021': ['CS', '1-n', 'AbortReason'],
|
|
'0x1023': ['FL', '1', 'VolumeOfPTO'],
|
|
'0x1024': ['CS', '1', 'AbortFlag'],
|
|
'0x1025': ['DT', '1', 'RouteSegmentStartTime'],
|
|
'0x1026': ['DT', '1', 'RouteSegmentEndTime'],
|
|
'0x1027': ['CS', '1', 'TDRType'],
|
|
'0x1028': ['CS', '1', 'InternationalRouteSegment'],
|
|
'0x1029': ['LO', '1-n', 'ThreatDetectionAlgorithmandVersion'],
|
|
'0x102A': ['SH', '1', 'AssignedLocation'],
|
|
'0x102B': ['DT', '1', 'AlarmDecisionTime'],
|
|
'0x1031': ['CS', '1', 'AlarmDecision'],
|
|
'0x1033': ['US', '1', 'NumberOfTotalObjects'],
|
|
'0x1034': ['US', '1', 'NumberOfAlarmObjects'],
|
|
'0x1037': ['SQ', '1', 'PTORepresentationSequence'],
|
|
'0x1038': ['SQ', '1', 'ATDAssessmentSequence'],
|
|
'0x1039': ['CS', '1', 'TIPType'],
|
|
'0x103A': ['CS', '1', 'DICOSVersion'],
|
|
'0x1041': ['DT', '1', 'OOIOwnerCreationTime'],
|
|
'0x1042': ['CS', '1', 'OOIType'],
|
|
'0x1043': ['FL', '3', 'OOISize'],
|
|
'0x1044': ['CS', '1', 'AcquisitionStatus'],
|
|
'0x1045': ['SQ', '1', 'BasisMaterialsCodeSequence'],
|
|
'0x1046': ['CS', '1', 'PhantomType'],
|
|
'0x1047': ['SQ', '1', 'OOIOwnerSequence'],
|
|
'0x1048': ['CS', '1', 'ScanType'],
|
|
'0x1051': ['LO', '1', 'ItineraryID'],
|
|
'0x1052': ['SH', '1', 'ItineraryIDType'],
|
|
'0x1053': ['LO', '1', 'ItineraryIDAssigningAuthority'],
|
|
'0x1054': ['SH', '1', 'RouteID'],
|
|
'0x1055': ['SH', '1', 'RouteIDAssigningAuthority'],
|
|
'0x1056': ['CS', '1', 'InboundArrivalType'],
|
|
'0x1058': ['SH', '1', 'CarrierID'],
|
|
'0x1059': ['CS', '1', 'CarrierIDAssigningAuthority'],
|
|
'0x1060': ['FL', '3', 'SourceOrientation'],
|
|
'0x1061': ['FL', '3', 'SourcePosition'],
|
|
'0x1062': ['FL', '1', 'BeltHeight'],
|
|
'0x1064': ['SQ', '1', 'AlgorithmRoutingCodeSequence'],
|
|
'0x1067': ['CS', '1', 'TransportClassification'],
|
|
'0x1068': ['LT', '1', 'OOITypeDescriptor'],
|
|
'0x1069': ['FL', '1', 'TotalProcessingTime'],
|
|
'0x106C': ['OB', '1', 'DetectorCalibrationData'],
|
|
'0x106D': ['CS', '1', 'AdditionalScreeningPerformed'],
|
|
'0x106E': ['CS', '1', 'AdditionalInspectionSelectionCriteria'],
|
|
'0x106F': ['SQ', '1', 'AdditionalInspectionMethodSequence'],
|
|
'0x1070': ['CS', '1', 'AITDeviceType'],
|
|
'0x1071': ['SQ', '1', 'QRMeasurementsSequence'],
|
|
'0x1072': ['SQ', '1', 'TargetMaterialSequence'],
|
|
'0x1073': ['FD', '1', 'SNRThreshold'],
|
|
'0x1075': ['DS', '1', 'ImageScaleRepresentation'],
|
|
'0x1076': ['SQ', '1', 'ReferencedPTOSequence'],
|
|
'0x1077': ['SQ', '1', 'ReferencedTDRInstanceSequence'],
|
|
'0x1078': ['ST', '1', 'PTOLocationDescription'],
|
|
'0x1079': ['SQ', '1', 'AnomalyLocatorIndicatorSequence'],
|
|
'0x107A': ['FL', '3', 'AnomalyLocatorIndicator'],
|
|
'0x107B': ['SQ', '1', 'PTORegionSequence'],
|
|
'0x107C': ['CS', '1', 'InspectionSelectionCriteria'],
|
|
'0x107D': ['SQ', '1', 'SecondaryInspectionMethodSequence'],
|
|
'0x107E': ['DS', '6', 'PRCSToRCSOrientation']
|
|
},
|
|
'0x4FFE': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0001': ['SQ', '1', 'MACParametersSequence']
|
|
},
|
|
'0x5000': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0005': ['US', '1', 'CurveDimensions'],
|
|
'0x0010': ['US', '1', 'NumberOfPoints'],
|
|
'0x0020': ['CS', '1', 'TypeOfData'],
|
|
'0x0022': ['LO', '1', 'CurveDescription'],
|
|
'0x0030': ['SH', '1-n', 'AxisUnits'],
|
|
'0x0040': ['SH', '1-n', 'AxisLabels'],
|
|
'0x0103': ['US', '1', 'DataValueRepresentation'],
|
|
'0x0104': ['US', '1-n', 'MinimumCoordinateValue'],
|
|
'0x0105': ['US', '1-n', 'MaximumCoordinateValue'],
|
|
'0x0106': ['SH', '1-n', 'CurveRange'],
|
|
'0x0110': ['US', '1-n', 'CurveDataDescriptor'],
|
|
'0x0112': ['US', '1-n', 'CoordinateStartValue'],
|
|
'0x0114': ['US', '1-n', 'CoordinateStepValue'],
|
|
'0x1001': ['CS', '1', 'CurveActivationLayer'],
|
|
'0x2000': ['US', '1', 'AudioType'],
|
|
'0x2002': ['US', '1', 'AudioSampleFormat'],
|
|
'0x2004': ['US', '1', 'NumberOfChannels'],
|
|
'0x2006': ['UL', '1', 'NumberOfSamples'],
|
|
'0x2008': ['UL', '1', 'SampleRate'],
|
|
'0x200A': ['UL', '1', 'TotalTime'],
|
|
'0x200C': ['ox', '1', 'AudioSampleData'],
|
|
'0x200E': ['LT', '1', 'AudioComments'],
|
|
'0x2500': ['LO', '1', 'CurveLabel'],
|
|
'0x2600': ['SQ', '1', 'CurveReferencedOverlaySequence'],
|
|
'0x2610': ['US', '1', 'CurveReferencedOverlayGroup'],
|
|
'0x3000': ['ox', '1', 'CurveData']
|
|
},
|
|
'0x5200': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x9229': ['SQ', '1', 'SharedFunctionalGroupsSequence'],
|
|
'0x9230': ['SQ', '1', 'PerFrameFunctionalGroupsSequence']
|
|
},
|
|
'0x5400': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0100': ['SQ', '1', 'WaveformSequence'],
|
|
'0x0110': ['ox', '1', 'ChannelMinimumValue'],
|
|
'0x0112': ['ox', '1', 'ChannelMaximumValue'],
|
|
'0x1004': ['US', '1', 'WaveformBitsAllocated'],
|
|
'0x1006': ['CS', '1', 'WaveformSampleInterpretation'],
|
|
'0x100A': ['ox', '1', 'WaveformPaddingValue'],
|
|
'0x1010': ['ox', '1', 'WaveformData']
|
|
},
|
|
'0x5600': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['OF', '1', 'FirstOrderPhaseCorrectionAngle'],
|
|
'0x0020': ['OF', '1', 'SpectroscopyData']
|
|
},
|
|
'0x6000': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['US', '1', 'OverlayRows'],
|
|
'0x0011': ['US', '1', 'OverlayColumns'],
|
|
'0x0012': ['US', '1', 'OverlayPlanes'],
|
|
'0x0015': ['IS', '1', 'NumberOfFramesInOverlay'],
|
|
'0x0022': ['LO', '1', 'OverlayDescription'],
|
|
'0x0040': ['CS', '1', 'OverlayType'],
|
|
'0x0045': ['LO', '1', 'OverlaySubtype'],
|
|
'0x0050': ['SS', '2', 'OverlayOrigin'],
|
|
'0x0051': ['US', '1', 'ImageFrameOrigin'],
|
|
'0x0052': ['US', '1', 'OverlayPlaneOrigin'],
|
|
'0x0060': ['CS', '1', 'OverlayCompressionCode'],
|
|
'0x0061': ['SH', '1', 'OverlayCompressionOriginator'],
|
|
'0x0062': ['SH', '1', 'OverlayCompressionLabel'],
|
|
'0x0063': ['CS', '1', 'OverlayCompressionDescription'],
|
|
'0x0066': ['AT', '1-n', 'OverlayCompressionStepPointers'],
|
|
'0x0068': ['US', '1', 'OverlayRepeatInterval'],
|
|
'0x0069': ['US', '1', 'OverlayBitsGrouped'],
|
|
'0x0100': ['US', '1', 'OverlayBitsAllocated'],
|
|
'0x0102': ['US', '1', 'OverlayBitPosition'],
|
|
'0x0110': ['CS', '1', 'OverlayFormat'],
|
|
'0x0200': ['US', '1', 'OverlayLocation'],
|
|
'0x0800': ['CS', '1-n', 'OverlayCodeLabel'],
|
|
'0x0802': ['US', '1', 'OverlayNumberOfTables'],
|
|
'0x0803': ['AT', '1-n', 'OverlayCodeTableLocation'],
|
|
'0x0804': ['US', '1', 'OverlayBitsForCodeWord'],
|
|
'0x1001': ['CS', '1', 'OverlayActivationLayer'],
|
|
'0x1100': ['US', '1', 'OverlayDescriptorGray'],
|
|
'0x1101': ['US', '1', 'OverlayDescriptorRed'],
|
|
'0x1102': ['US', '1', 'OverlayDescriptorGreen'],
|
|
'0x1103': ['US', '1', 'OverlayDescriptorBlue'],
|
|
'0x1200': ['US', '1-n', 'OverlaysGray'],
|
|
'0x1201': ['US', '1-n', 'OverlaysRed'],
|
|
'0x1202': ['US', '1-n', 'OverlaysGreen'],
|
|
'0x1203': ['US', '1-n', 'OverlaysBlue'],
|
|
'0x1301': ['IS', '1', 'ROIArea'],
|
|
'0x1302': ['DS', '1', 'ROIMean'],
|
|
'0x1303': ['DS', '1', 'ROIStandardDeviation'],
|
|
'0x1500': ['LO', '1', 'OverlayLabel'],
|
|
'0x3000': ['ox', '1', 'OverlayData'],
|
|
'0x4000': ['LT', '1', 'OverlayComments']
|
|
},
|
|
'0x7FE0': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0008': ['OF', '1', 'FloatPixelData'],
|
|
'0x0009': ['OD', '1', 'DoubleFloatPixelData'],
|
|
'0x0010': ['ox', '1', 'PixelData'],
|
|
'0x0020': ['OW', '1', 'CoefficientsSDVN'],
|
|
'0x0030': ['OW', '1', 'CoefficientsSDHN'],
|
|
'0x0040': ['OW', '1', 'CoefficientsSDDN']
|
|
},
|
|
'0x7F00': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0x0010': ['ox', '1', 'VariablePixelData'],
|
|
'0x0011': ['US', '1', 'VariableNextDataGroup'],
|
|
'0x0020': ['OW', '1', 'VariableCoefficientsSDVN'],
|
|
'0x0030': ['OW', '1', 'VariableCoefficientsSDHN'],
|
|
'0x0040': ['OW', '1', 'VariableCoefficientsSDDN']
|
|
},
|
|
'0xFFFA': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0xFFFA': ['SQ', '1', 'DigitalSignaturesSequence']
|
|
},
|
|
'0xFFFC': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0xFFFC': ['OB', '1', 'DataSetTrailingPadding']
|
|
},
|
|
'0xFFFE': {
|
|
'0x0000': ['UL', '1', 'GenericGroupLength'],
|
|
'0xE000': ['NONE', '1', 'Item'],
|
|
'0xE00D': ['NONE', '1', 'ItemDelimitationItem'],
|
|
'0xE0DD': ['NONE', '1', 'SequenceDelimitationItem']
|
|
}
|
|
}; // dwv.dicom.Dictionnary
|
|
|
|
// taken from gdcm-2.6.1\Source\DataDictionary\GroupName.dic
|
|
// -> removed duplicates (commented)
|
|
dwv.dicom.TagGroups = {
|
|
'x0000' :'Command',
|
|
'x0002': 'Meta Element',
|
|
'x0004': 'File Set',
|
|
//'x0004': 'Directory',
|
|
'x0008': 'Identifying',
|
|
'x0009': 'SPI Identifying',
|
|
'x0010': 'Patient',
|
|
'x0012': 'Clinical Trial',
|
|
'x0018': 'Acquisition',
|
|
'x0019': 'SPI Acquisition',
|
|
'x0020': 'Image',
|
|
'x0021': 'SPI Image',
|
|
'x0022': 'Ophtalmology',
|
|
'x0028': 'Image Presentation',
|
|
'x0032': 'Study',
|
|
'x0038': 'Visit',
|
|
'x003A': 'Waveform',
|
|
'x0040': 'Procedure',
|
|
//'x0040': ''Modality Worklist',
|
|
'x0042': 'Encapsulated Document',
|
|
'x0050': 'Device Informations',
|
|
//'x0050': 'XRay Angio Device',
|
|
'x0054': 'Nuclear Medicine',
|
|
'x0060': 'Histogram',
|
|
'x0070': 'Presentation State',
|
|
'x0072': 'Hanging Protocol',
|
|
'x0088': 'Storage',
|
|
//'x0088': 'Medicine',
|
|
'x0100': 'Authorization',
|
|
'x0400': 'Digital Signature',
|
|
'x1000': 'Code Table',
|
|
'x1010': 'Zonal Map',
|
|
'x2000': 'Film Session',
|
|
'x2010': 'Film Box',
|
|
'x2020': 'Image Box',
|
|
'x2030': 'Annotation',
|
|
'x2040': 'Overlay Box',
|
|
'x2050': 'Presentation LUT',
|
|
'x2100': 'Print Job',
|
|
'x2110': 'Printer',
|
|
'x2120': 'Queue',
|
|
'x2130': 'Print Content',
|
|
'x2200': 'Media Creation',
|
|
'x3002': 'RT Image',
|
|
'x3004': 'RT Dose',
|
|
'x3006': 'RT StructureSet',
|
|
'x3008': 'RT Treatment',
|
|
'x300A': 'RT Plan',
|
|
'x300C': 'RT Relationship',
|
|
'x300E': 'RT Approval',
|
|
'x4000': 'Text',
|
|
'x4008': 'Results',
|
|
'x4FFE': 'MAC Parameters',
|
|
'x5000': 'Curve',
|
|
'x5002': 'Curve',
|
|
'x5004': 'Curve',
|
|
'x5006': 'Curve',
|
|
'x5008': 'Curve',
|
|
'x500A': 'Curve',
|
|
'x500C': 'Curve',
|
|
'x500E': 'Curve',
|
|
'x5400': 'Waveform Data',
|
|
'x6000': 'Overlays',
|
|
'x6002': 'Overlays',
|
|
'x6004': 'Overlays',
|
|
'x6008': 'Overlays',
|
|
'x600A': 'Overlays',
|
|
'x600C': 'Overlays',
|
|
'x600E': 'Overlays',
|
|
'xFFFC': 'Generic',
|
|
'x7FE0': 'Pixel Data',
|
|
'xFFFF': 'Unknown'
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.gui = dwv.gui || {};
|
|
/** @namespace */
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
/** @namespace */
|
|
dwv.gui.filter = dwv.gui.filter || {};
|
|
/** @namespace */
|
|
dwv.gui.filter.base = dwv.gui.filter.base || {};
|
|
|
|
/**
|
|
* Filter tool base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Filter = function (app)
|
|
{
|
|
/**
|
|
* Setup the filter tool HTML.
|
|
*/
|
|
this.setup = function (list)
|
|
{
|
|
// filter select
|
|
var filterSelector = dwv.html.createHtmlSelect("filterSelect", list, "filter");
|
|
filterSelector.onchange = app.onChangeFilter;
|
|
|
|
// filter list element
|
|
var filterLi = dwv.html.createHiddenElement("li", "filterLi");
|
|
filterLi.className += " ui-block-b";
|
|
filterLi.appendChild(filterSelector);
|
|
|
|
// append element
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
dwv.html.appendElement(node, filterLi);
|
|
};
|
|
|
|
/**
|
|
* Display the tool HTML.
|
|
* @param {Boolean} flag True to display, false to hide.
|
|
*/
|
|
this.display = function (flag)
|
|
{
|
|
var node = app.getElement("filterLi");
|
|
dwv.html.displayElement(node, flag);
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
// filter select: reset selected options
|
|
var filterSelector = app.getElement("filterSelect");
|
|
filterSelector.selectedIndex = 0;
|
|
// refresh
|
|
dwv.gui.refreshElement(filterSelector);
|
|
};
|
|
|
|
}; // class dwv.gui.base.Filter
|
|
|
|
/**
|
|
* Threshold filter base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Threshold = function (app)
|
|
{
|
|
/**
|
|
* Threshold slider.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var slider = new dwv.gui.Slider(app);
|
|
|
|
/**
|
|
* Setup the threshold filter HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// threshold list element
|
|
var thresholdLi = dwv.html.createHiddenElement("li", "thresholdLi");
|
|
thresholdLi.className += " ui-block-c";
|
|
|
|
// node
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
// append threshold
|
|
node.appendChild(thresholdLi);
|
|
// threshold slider
|
|
slider.append();
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Clear the threshold filter HTML.
|
|
* @param {Boolean} flag True to display, false to hide.
|
|
*/
|
|
this.display = function (flag)
|
|
{
|
|
// only initialise at display time
|
|
// (avoids min/max calculation at startup)
|
|
if (flag) {
|
|
slider.initialise();
|
|
}
|
|
|
|
var node = app.getElement("thresholdLi");
|
|
dwv.html.displayElement(node, flag);
|
|
};
|
|
|
|
/**
|
|
* Initialise the threshold filter HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
// nothing to do
|
|
};
|
|
|
|
}; // class dwv.gui.base.Threshold
|
|
|
|
/**
|
|
* Create the apply filter button.
|
|
*/
|
|
dwv.gui.filter.base.createFilterApplyButton = function (app)
|
|
{
|
|
var button = document.createElement("button");
|
|
button.id = "runFilterButton";
|
|
button.onclick = app.onRunFilter;
|
|
button.setAttribute("style","width:100%; margin-top:0.5em;");
|
|
button.setAttribute("class","ui-btn ui-btn-b");
|
|
button.appendChild(document.createTextNode(dwv.i18n("basics.apply")));
|
|
return button;
|
|
};
|
|
|
|
/**
|
|
* Sharpen filter base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Sharpen = function (app)
|
|
{
|
|
/**
|
|
* Setup the sharpen filter HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// sharpen list element
|
|
var sharpenLi = dwv.html.createHiddenElement("li", "sharpenLi");
|
|
sharpenLi.className += " ui-block-c";
|
|
sharpenLi.appendChild( dwv.gui.filter.base.createFilterApplyButton(app) );
|
|
// append element
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
dwv.html.appendElement(node, sharpenLi);
|
|
};
|
|
|
|
/**
|
|
* Display the sharpen filter HTML.
|
|
* @param {Boolean} flag True to display, false to hide.
|
|
*/
|
|
this.display = function (flag)
|
|
{
|
|
var node = app.getElement("sharpenLi");
|
|
dwv.html.displayElement(node, flag);
|
|
};
|
|
|
|
}; // class dwv.gui.base.Sharpen
|
|
|
|
/**
|
|
* Sobel filter base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Sobel = function (app)
|
|
{
|
|
/**
|
|
* Setup the sobel filter HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// sobel list element
|
|
var sobelLi = dwv.html.createHiddenElement("li", "sobelLi");
|
|
sobelLi.className += " ui-block-c";
|
|
sobelLi.appendChild( dwv.gui.filter.base.createFilterApplyButton(app) );
|
|
// append element
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
dwv.html.appendElement(node, sobelLi);
|
|
};
|
|
|
|
/**
|
|
* Display the sobel filter HTML.
|
|
* @param {Boolean} flag True to display, false to hide.
|
|
*/
|
|
this.display = function (flag)
|
|
{
|
|
var node = app.getElement("sobelLi");
|
|
dwv.html.displayElement(node, flag);
|
|
};
|
|
|
|
}; // class dwv.gui.base.Sobel
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.gui = dwv.gui || {};
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
|
|
/**
|
|
* Get the size of the image display window.
|
|
*/
|
|
dwv.gui.base.getWindowSize = function ()
|
|
{
|
|
return { 'width': window.innerWidth, 'height': window.innerHeight - 147 };
|
|
};
|
|
|
|
/**
|
|
* Ask some text to the user.
|
|
* @param {String} message Text to display to the user.
|
|
* @param {String} defaultText Default value displayed in the text input field.
|
|
* @return {String} Text entered by the user.
|
|
*/
|
|
dwv.gui.base.prompt = function (message, defaultText)
|
|
{
|
|
return prompt(message, defaultText);
|
|
};
|
|
|
|
/**
|
|
* Display a progress value.
|
|
* @param {Number} percent The progress percentage.
|
|
*/
|
|
dwv.gui.base.displayProgress = function (/*percent*/)
|
|
{
|
|
// default does nothing...
|
|
};
|
|
|
|
/**
|
|
* Focus the view on the image.
|
|
*/
|
|
dwv.gui.base.focusImage = function ()
|
|
{
|
|
// default does nothing...
|
|
};
|
|
|
|
/**
|
|
* Post process a HTML table.
|
|
* @param {Object} table The HTML table to process.
|
|
* @return The processed HTML table.
|
|
*/
|
|
dwv.gui.base.postProcessTable = function (/*table*/)
|
|
{
|
|
// default does nothing...
|
|
};
|
|
|
|
/**
|
|
* Get a HTML element associated to a container div.
|
|
* @param {Number} containerDivId The id of the container div.
|
|
* @param {String} name The name or id to find.
|
|
* @return {Object} The found element or null.
|
|
*/
|
|
dwv.gui.base.getElement = function (containerDivId, name)
|
|
{
|
|
// get by class in the container div
|
|
var parent = document.getElementById(containerDivId);
|
|
var elements = parent.getElementsByClassName(name);
|
|
// getting the last element since some libraries (ie jquery-mobile) creates
|
|
// span in front of regular tags (such as select)...
|
|
var element = elements[elements.length-1];
|
|
// if not found get by id with 'containerDivId-className'
|
|
if ( typeof element === "undefined" ) {
|
|
element = document.getElementById(containerDivId + '-' + name);
|
|
}
|
|
return element;
|
|
};
|
|
|
|
/**
|
|
* Refresh a HTML element. Mainly for jquery-mobile.
|
|
* @param {String} element The HTML element to refresh.
|
|
*/
|
|
dwv.gui.base.refreshElement = function (/*element*/)
|
|
{
|
|
// base does nothing...
|
|
};
|
|
|
|
/**
|
|
* Set the selected item of a HTML select.
|
|
* @param {String} element The HTML select element.
|
|
* @param {String} value The value of the option to mark as selected.
|
|
*/
|
|
dwv.gui.setSelected = function (element, value)
|
|
{
|
|
if ( element ) {
|
|
var index = 0;
|
|
for( index in element.options){
|
|
if( element.options[index].value === value ) {
|
|
break;
|
|
}
|
|
}
|
|
element.selectedIndex = index;
|
|
dwv.gui.refreshElement(element);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Slider base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Slider = function (app)
|
|
{
|
|
/**
|
|
* Append the slider HTML.
|
|
*/
|
|
this.append = function ()
|
|
{
|
|
// default values
|
|
var min = 0;
|
|
var max = 1;
|
|
|
|
// jquery-mobile range slider
|
|
// minimum input
|
|
var inputMin = document.createElement("input");
|
|
inputMin.id = "threshold-min";
|
|
inputMin.type = "range";
|
|
inputMin.max = max;
|
|
inputMin.min = min;
|
|
inputMin.value = min;
|
|
// maximum input
|
|
var inputMax = document.createElement("input");
|
|
inputMax.id = "threshold-max";
|
|
inputMax.type = "range";
|
|
inputMax.max = max;
|
|
inputMax.min = min;
|
|
inputMax.value = max;
|
|
// slicer div
|
|
var div = document.createElement("div");
|
|
div.id = "threshold-div";
|
|
div.setAttribute("data-role", "rangeslider");
|
|
div.appendChild(inputMin);
|
|
div.appendChild(inputMax);
|
|
div.setAttribute("data-mini", "true");
|
|
// append to document
|
|
app.getElement("thresholdLi").appendChild(div);
|
|
// bind change
|
|
$("#threshold-div").on("change",
|
|
function(/*event*/) {
|
|
app.onChangeMinMax(
|
|
{ "min":$("#threshold-min").val(),
|
|
"max":$("#threshold-max").val() } );
|
|
}
|
|
);
|
|
// refresh
|
|
dwv.gui.refreshElement(app.getElement("toolList"));
|
|
};
|
|
|
|
/**
|
|
* Initialise the slider HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
var min = app.getImage().getDataRange().min;
|
|
var max = app.getImage().getDataRange().max;
|
|
|
|
// minimum input
|
|
var inputMin = document.getElementById("threshold-min");
|
|
inputMin.max = max;
|
|
inputMin.min = min;
|
|
inputMin.value = min;
|
|
// maximum input
|
|
var inputMax = document.getElementById("threshold-max");
|
|
inputMax.max = max;
|
|
inputMax.min = min;
|
|
inputMax.value = max;
|
|
// refresh
|
|
dwv.gui.refreshElement(app.getElement("toolList"));
|
|
};
|
|
|
|
}; // class dwv.gui.base.Slider
|
|
|
|
/**
|
|
* DICOM tags base gui.
|
|
* @param {Object} app The associated application.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.DicomTags = function (app)
|
|
{
|
|
/**
|
|
* Update the DICOM tags table with the input info.
|
|
* @param {Object} dataInfo The data information.
|
|
*/
|
|
this.update = function (dataInfo)
|
|
{
|
|
// HTML node
|
|
var node = app.getElement("tags");
|
|
if( node === null ) {
|
|
console.warn("Cannot find a node to append the DICOM tags.");
|
|
return;
|
|
}
|
|
// remove possible previous
|
|
while (node.hasChildNodes()) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
|
|
// exit if no tags
|
|
if (dataInfo.length === 0) {
|
|
console.warn("No DICOM tags to show.");
|
|
return;
|
|
}
|
|
|
|
// tags HTML table
|
|
var table = dwv.html.toTable(dataInfo);
|
|
table.className = "tagsTable";
|
|
|
|
// optional gui specific table post process
|
|
dwv.gui.postProcessTable(table);
|
|
|
|
// check processed table
|
|
if (table.rows.length === 0) {
|
|
console.warn("The processed table does not contain data.");
|
|
return;
|
|
}
|
|
|
|
// translate first row
|
|
dwv.html.translateTableRow(table.rows.item(0));
|
|
|
|
// append search form
|
|
node.appendChild(dwv.html.getHtmlSearchForm(table));
|
|
// append tags table
|
|
node.appendChild(table);
|
|
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
}; // class dwv.gui.base.DicomTags
|
|
|
|
/**
|
|
* Drawing list base gui.
|
|
* @param {Object} app The associated application.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.DrawList = function (app)
|
|
{
|
|
/**
|
|
* Closure to self.
|
|
*/
|
|
var self = this;
|
|
|
|
/**
|
|
* Update the draw list html element
|
|
* @param {Object} event A change event, decides if the table is editable or not.
|
|
*/
|
|
this.update = function (event)
|
|
{
|
|
var isEditable = false;
|
|
if (typeof event.editable !== "undefined") {
|
|
isEditable = event.editable;
|
|
}
|
|
|
|
// HTML node
|
|
var node = app.getElement("drawList");
|
|
if( node === null ) {
|
|
console.warn("Cannot find a node to append the drawing list.");
|
|
return;
|
|
}
|
|
// remove possible previous
|
|
while (node.hasChildNodes()) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
|
|
// drawing details
|
|
var drawDisplayDetails = app.getDrawDisplayDetails();
|
|
|
|
// exit if no details
|
|
if (drawDisplayDetails.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// tags HTML table
|
|
var table = dwv.html.toTable(drawDisplayDetails);
|
|
table.className = "drawsTable";
|
|
|
|
// optional gui specific table post process
|
|
dwv.gui.postProcessTable(table);
|
|
|
|
// check processed table
|
|
if (table.rows.length === 0) {
|
|
console.warn("The processed table does not contain data.");
|
|
return;
|
|
}
|
|
|
|
// translate first row
|
|
dwv.html.translateTableRow(table.rows.item(0));
|
|
|
|
// translate shape names
|
|
dwv.html.translateTableColumn(table, 3, "shape", "name");
|
|
|
|
// create a color onkeyup handler
|
|
var createColorOnKeyUp = function (details) {
|
|
return function () {
|
|
details.color = this.value;
|
|
app.updateDraw(details);
|
|
};
|
|
};
|
|
// create a text onkeyup handler
|
|
var createTextOnKeyUp = function (details) {
|
|
return function () {
|
|
details.label = this.value;
|
|
app.updateDraw(details);
|
|
};
|
|
};
|
|
// create a long text onkeyup handler
|
|
var createLongTextOnKeyUp = function (details) {
|
|
return function () {
|
|
details.description = this.value;
|
|
app.updateDraw(details);
|
|
};
|
|
};
|
|
// create a row onclick handler
|
|
var createRowOnClick = function (slice, frame) {
|
|
return function () {
|
|
// update slice
|
|
var pos = app.getViewController().getCurrentPosition();
|
|
pos.k = slice;
|
|
app.getViewController().setCurrentPosition(pos);
|
|
// update frame
|
|
app.getViewController().setCurrentFrame(frame);
|
|
// focus on the image
|
|
dwv.gui.focusImage();
|
|
};
|
|
};
|
|
// create visibility handler
|
|
var createVisibleOnClick = function (details) {
|
|
return function () {
|
|
app.toogleGroupVisibility(details);
|
|
};
|
|
};
|
|
|
|
// append visible column to the header row
|
|
var row0 = table.rows.item(0);
|
|
var cell00 = row0.insertCell(0);
|
|
cell00.outerHTML = "<th>" + dwv.i18n("basics.visible") + "</th>";
|
|
|
|
// loop through rows
|
|
for (var r = 1; r < table.rows.length; ++r) {
|
|
var drawId = r - 1;
|
|
var drawDetails = drawDisplayDetails[drawId];
|
|
var row = table.rows.item(r);
|
|
var cells = row.cells;
|
|
|
|
// loop through cells
|
|
for (var c = 0; c < cells.length; ++c) {
|
|
// show short ID
|
|
if (c === 0) {
|
|
cells[c].firstChild.data = cells[c].firstChild.data.substring(0, 5);
|
|
}
|
|
|
|
if (isEditable) {
|
|
// color
|
|
if (c === 4) {
|
|
dwv.html.makeCellEditable(cells[c], createColorOnKeyUp(drawDetails), "color");
|
|
}
|
|
// text
|
|
else if (c === 5) {
|
|
dwv.html.makeCellEditable(cells[c], createTextOnKeyUp(drawDetails));
|
|
}
|
|
// long text
|
|
else if (c === 6) {
|
|
dwv.html.makeCellEditable(cells[c], createLongTextOnKeyUp(drawDetails));
|
|
}
|
|
}
|
|
else {
|
|
// id: link to image
|
|
cells[0].onclick = createRowOnClick(
|
|
cells[1].firstChild.data,
|
|
cells[2].firstChild.data);
|
|
cells[0].onmouseover = dwv.html.setCursorToPointer;
|
|
cells[0].onmouseout = dwv.html.setCursorToDefault;
|
|
// color: just display the input color with no callback
|
|
if (c === 4) {
|
|
dwv.html.makeCellEditable(cells[c], null, "color");
|
|
}
|
|
}
|
|
}
|
|
|
|
// append visible column
|
|
var cell0 = row.insertCell(0);
|
|
var input = document.createElement("input");
|
|
input.setAttribute("type", "checkbox");
|
|
input.checked = app.isGroupVisible(drawDetails);
|
|
input.onclick = createVisibleOnClick(drawDetails);
|
|
cell0.appendChild(input);
|
|
}
|
|
|
|
// editable checkbox
|
|
var tickBox = document.createElement("input");
|
|
tickBox.setAttribute("type", "checkbox");
|
|
tickBox.id = "checkbox-editable";
|
|
tickBox.checked = isEditable;
|
|
tickBox.onclick = function () { self.update({"editable": this.checked}); };
|
|
// checkbox label
|
|
var tickLabel = document.createElement("label");
|
|
tickLabel.setAttribute( "for", tickBox.id );
|
|
tickLabel.setAttribute( "class", "inline" );
|
|
tickLabel.appendChild( document.createTextNode( dwv.i18n("basics.editMode") ) );
|
|
// checkbox div
|
|
var tickDiv = document.createElement("div");
|
|
tickDiv.appendChild(tickLabel);
|
|
tickDiv.appendChild(tickBox);
|
|
|
|
// search form
|
|
node.appendChild(dwv.html.getHtmlSearchForm(table));
|
|
// tick form
|
|
node.appendChild(tickDiv);
|
|
|
|
// draw list table
|
|
node.appendChild(table);
|
|
|
|
// delete draw button
|
|
var deleteButton = document.createElement("button");
|
|
deleteButton.onclick = function () { app.deleteDraws(); };
|
|
deleteButton.setAttribute( "class", "ui-btn ui-btn-inline" );
|
|
deleteButton.appendChild( document.createTextNode( dwv.i18n("basics.deleteDraws") ) );
|
|
if (!isEditable) {
|
|
deleteButton.style.display = "none";
|
|
}
|
|
node.appendChild(deleteButton);
|
|
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
}; // class dwv.gui.base.DrawList
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.gui = dwv.gui || {};
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
|
|
/**
|
|
* Append the version HTML.
|
|
*/
|
|
dwv.gui.base.appendVersionHtml = function (version)
|
|
{
|
|
var nodes = document.getElementsByClassName("dwv-version");
|
|
if ( nodes ) {
|
|
for( var i = 0; i < nodes.length; ++i ){
|
|
nodes[i].appendChild( document.createTextNode(version) );
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Build the help HTML.
|
|
* @param {Boolean} mobile Flag for mobile or not environement.
|
|
*/
|
|
dwv.gui.base.appendHelpHtml = function(toolList, mobile, app)
|
|
{
|
|
var actionType = "mouse";
|
|
if( mobile ) {
|
|
actionType = "touch";
|
|
}
|
|
|
|
var toolHelpDiv = document.createElement("div");
|
|
|
|
// current location
|
|
var loc = window.location.pathname;
|
|
var dir = loc.substring(0, loc.lastIndexOf('/'));
|
|
|
|
var tool = null;
|
|
var tkeys = Object.keys(toolList);
|
|
for ( var t=0; t < tkeys.length; ++t )
|
|
{
|
|
tool = toolList[tkeys[t]];
|
|
// title
|
|
var title = document.createElement("h3");
|
|
title.appendChild(document.createTextNode(tool.getHelp().title));
|
|
// doc div
|
|
var docDiv = document.createElement("div");
|
|
// brief
|
|
var brief = document.createElement("p");
|
|
brief.appendChild(document.createTextNode(tool.getHelp().brief));
|
|
docDiv.appendChild(brief);
|
|
// details
|
|
if( tool.getHelp()[actionType] ) {
|
|
var keys = Object.keys(tool.getHelp()[actionType]);
|
|
for( var i=0; i<keys.length; ++i )
|
|
{
|
|
var action = tool.getHelp()[actionType][keys[i]];
|
|
|
|
var img = document.createElement("img");
|
|
img.src = dir + "/../../resources/help/"+keys[i]+".png";
|
|
img.style.float = "left";
|
|
img.style.margin = "0px 15px 15px 0px";
|
|
|
|
var br = document.createElement("br");
|
|
br.style.clear = "both";
|
|
|
|
var para = document.createElement("p");
|
|
para.appendChild(img);
|
|
para.appendChild(document.createTextNode(action));
|
|
para.appendChild(br);
|
|
docDiv.appendChild(para);
|
|
}
|
|
}
|
|
|
|
// different div structure for mobile or static
|
|
if( mobile )
|
|
{
|
|
var toolDiv = document.createElement("div");
|
|
toolDiv.setAttribute("data-role", "collapsible");
|
|
toolDiv.appendChild(title);
|
|
toolDiv.appendChild(docDiv);
|
|
toolHelpDiv.appendChild(toolDiv);
|
|
}
|
|
else
|
|
{
|
|
toolHelpDiv.id = "accordion";
|
|
toolHelpDiv.appendChild(title);
|
|
toolHelpDiv.appendChild(docDiv);
|
|
}
|
|
}
|
|
|
|
var helpNode = app.getElement("help");
|
|
|
|
var headPara = document.createElement("p");
|
|
headPara.appendChild(document.createTextNode(dwv.i18n("help.intro.p0")));
|
|
helpNode.appendChild(headPara);
|
|
|
|
var secondPara = document.createElement("p");
|
|
secondPara.appendChild(document.createTextNode(dwv.i18n("help.intro.p1")));
|
|
helpNode.appendChild(secondPara);
|
|
|
|
var toolPara = document.createElement("p");
|
|
toolPara.appendChild(document.createTextNode(dwv.i18n("help.tool_intro")));
|
|
helpNode.appendChild(toolPara);
|
|
helpNode.appendChild(toolHelpDiv);
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.html = dwv.html || {};
|
|
|
|
/**
|
|
* Append a cell to a given row.
|
|
* @param {Object} row The row to append the cell to.
|
|
* @param {Object} content The content of the cell.
|
|
*/
|
|
dwv.html.appendCell = function (row, content)
|
|
{
|
|
var cell = row.insertCell(-1);
|
|
var str = content;
|
|
// special care for arrays
|
|
if ( content instanceof Array ||
|
|
content instanceof Uint8Array || content instanceof Int8Array ||
|
|
content instanceof Uint16Array || content instanceof Int16Array ||
|
|
content instanceof Uint32Array ) {
|
|
if ( content.length > 10 ) {
|
|
content = Array.prototype.slice.call( content, 0, 10 );
|
|
content[10] = "...";
|
|
}
|
|
str = Array.prototype.join.call( content, ', ' );
|
|
}
|
|
// append
|
|
cell.appendChild(document.createTextNode(str));
|
|
};
|
|
|
|
/**
|
|
* Append a header cell to a given row.
|
|
* @param {Object} row The row to append the header cell to.
|
|
* @param {String} text The text of the header cell.
|
|
*/
|
|
dwv.html.appendHCell = function (row, text)
|
|
{
|
|
var cell = document.createElement("th");
|
|
cell.appendChild(document.createTextNode(text));
|
|
row.appendChild(cell);
|
|
};
|
|
|
|
/**
|
|
* Append a row to an array.
|
|
* @param {Object} table The HTML table to append a row to.
|
|
* @param {Array} input The input row array.
|
|
* @param {Number} level The depth level of the input array.
|
|
* @param {Number} maxLevel The maximum depth level.
|
|
* @param {String} rowHeader The content of the first cell of a row (mainly for objects).
|
|
*/
|
|
dwv.html.appendRowForArray = function (table, input, level, maxLevel, rowHeader)
|
|
{
|
|
var row = null;
|
|
// loop through
|
|
for ( var i=0; i<input.length; ++i ) {
|
|
var value = input[i];
|
|
// last level
|
|
if ( typeof value === 'number' ||
|
|
typeof value === 'string' ||
|
|
value === null ||
|
|
value === undefined ||
|
|
level >= maxLevel ) {
|
|
if ( !row ) {
|
|
row = table.insertRow(-1);
|
|
}
|
|
dwv.html.appendCell(row, value);
|
|
}
|
|
// more to come
|
|
else {
|
|
dwv.html.appendRow(table, value, level+i, maxLevel, rowHeader);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Append a row to an object.
|
|
* @param {Object} table The HTML table to append a row to.
|
|
* @param {Array} input The input row array.
|
|
* @param {Number} level The depth level of the input array.
|
|
* @param {Number} maxLevel The maximum depth level.
|
|
* @param {String} rowHeader The content of the first cell of a row (mainly for objects).
|
|
*/
|
|
dwv.html.appendRowForObject = function (table, input, level, maxLevel, rowHeader)
|
|
{
|
|
var keys = Object.keys(input);
|
|
var row = null;
|
|
for ( var o=0; o<keys.length; ++o ) {
|
|
var value = input[keys[o]];
|
|
// last level
|
|
if ( typeof value === 'number' ||
|
|
typeof value === 'string' ||
|
|
value === null ||
|
|
value === undefined ||
|
|
level >= maxLevel ) {
|
|
if ( !row ) {
|
|
row = table.insertRow(-1);
|
|
}
|
|
if ( o === 0 && rowHeader) {
|
|
dwv.html.appendCell(row, rowHeader);
|
|
}
|
|
dwv.html.appendCell(row, value);
|
|
}
|
|
// more to come
|
|
else {
|
|
dwv.html.appendRow(table, value, level+o, maxLevel, keys[o]);
|
|
}
|
|
}
|
|
// header row
|
|
// warn: need to create the header after the rest
|
|
// otherwise the data will inserted in the thead...
|
|
if ( level === 2 ) {
|
|
var header = table.createTHead();
|
|
var th = header.insertRow(-1);
|
|
if ( rowHeader ) {
|
|
dwv.html.appendHCell(th, "");
|
|
}
|
|
for ( var k=0; k<keys.length; ++k ) {
|
|
dwv.html.appendHCell(th, keys[k]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Append a row to an object or an array.
|
|
* @param {Object} table The HTML table to append a row to.
|
|
* @param {Array} input The input row array.
|
|
* @param {Number} level The depth level of the input array.
|
|
* @param {Number} maxLevel The maximum depth level.
|
|
* @param {String} rowHeader The content of the first cell of a row (mainly for objects).
|
|
*/
|
|
dwv.html.appendRow = function (table, input, level, maxLevel, rowHeader)
|
|
{
|
|
// array
|
|
if ( input instanceof Array ) {
|
|
dwv.html.appendRowForArray(table, input, level+1, maxLevel, rowHeader);
|
|
}
|
|
// object
|
|
else if ( typeof input === 'object') {
|
|
dwv.html.appendRowForObject(table, input, level+1, maxLevel, rowHeader);
|
|
}
|
|
else {
|
|
throw new Error("Unsupported input data type.");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Converts the input to an HTML table.
|
|
* @input {Mixed} input Allowed types are: array, array of object, object.
|
|
* @return {Object} The created HTML table or null if the input is empty.
|
|
* @warning Null is interpreted differently in browsers, firefox will not display it.
|
|
*/
|
|
dwv.html.toTable = function (input)
|
|
{
|
|
// check content
|
|
if (input.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
var table = document.createElement('table');
|
|
dwv.html.appendRow(table, input, 0, 2);
|
|
return table;
|
|
};
|
|
|
|
/**
|
|
* Get an HTML search form.
|
|
* @param {Object} htmlTableToSearch The table to do the search on.
|
|
* @return {Object} The HTML search form.
|
|
*/
|
|
dwv.html.getHtmlSearchForm = function (htmlTableToSearch)
|
|
{
|
|
// input
|
|
var input = document.createElement("input");
|
|
input.id = "table-search";
|
|
// TODO Use new html5 search type
|
|
//input.setAttribute("type", "search");
|
|
input.onkeyup = function () {
|
|
dwv.html.filterTable(input, htmlTableToSearch);
|
|
};
|
|
// label
|
|
var label = document.createElement("label");
|
|
label.setAttribute("for", input.id);
|
|
label.appendChild(document.createTextNode(dwv.i18n("basics.search") + ": "));
|
|
// form
|
|
var form = document.createElement("form");
|
|
form.setAttribute("class", "filter");
|
|
form.onsubmit = function (event) {
|
|
event.preventDefault();
|
|
};
|
|
form.appendChild(label);
|
|
form.appendChild(input);
|
|
// return
|
|
return form;
|
|
};
|
|
|
|
/**
|
|
* Filter a table with a given parameter: sets the display css of rows to
|
|
* true or false if it contains the term.
|
|
* @param {String} term The term to filter the table with.
|
|
* @param {Object} table The table to filter.
|
|
*/
|
|
dwv.html.filterTable = function (term, table) {
|
|
// de-highlight
|
|
dwv.html.dehighlight(table);
|
|
// split search terms
|
|
var terms = term.value.toLowerCase().split(" ");
|
|
|
|
// search
|
|
var text = 0;
|
|
var display = 0;
|
|
for (var r = 1; r < table.rows.length; ++r) {
|
|
display = '';
|
|
for (var i = 0; i < terms.length; ++i) {
|
|
text = table.rows[r].innerHTML.replace(/<[^>]+>/g, "").toLowerCase();
|
|
if (text.indexOf(terms[i]) < 0) {
|
|
display = 'none';
|
|
} else {
|
|
if (terms[i].length) {
|
|
dwv.html.highlight(terms[i], table.rows[r]);
|
|
}
|
|
}
|
|
table.rows[r].style.display = display;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Transform back each
|
|
* 'preText <span class="highlighted">term</span> postText'
|
|
* into its original 'preText term postText'.
|
|
* @param {Object} container The container to de-highlight.
|
|
*/
|
|
dwv.html.dehighlight = function (container) {
|
|
for (var i = 0; i < container.childNodes.length; i++) {
|
|
var node = container.childNodes[i];
|
|
|
|
if (node.attributes &&
|
|
node.attributes['class'] &&
|
|
node.attributes['class'].value === 'highlighted') {
|
|
node.parentNode.parentNode.replaceChild(
|
|
document.createTextNode(
|
|
node.parentNode.innerHTML.replace(/<[^>]+>/g, "")),
|
|
node.parentNode);
|
|
// Stop here and process next parent
|
|
return;
|
|
} else if (node.nodeType !== 3) {
|
|
// Keep going onto other elements
|
|
dwv.html.dehighlight(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a
|
|
* 'preText <span class="highlighted">term</span> postText'
|
|
* around each search term.
|
|
* @param {String} term The term to highlight.
|
|
* @param {Object} container The container where to highlight the term.
|
|
*/
|
|
dwv.html.highlight = function (term, container) {
|
|
for (var i = 0; i < container.childNodes.length; i++) {
|
|
var node = container.childNodes[i];
|
|
|
|
if (node.nodeType === 3) {
|
|
// Text node
|
|
var data = node.data;
|
|
var data_low = data.toLowerCase();
|
|
if (data_low.indexOf(term) >= 0) {
|
|
//term found!
|
|
var new_node = document.createElement('span');
|
|
node.parentNode.replaceChild(new_node, node);
|
|
|
|
var result;
|
|
while ((result = data_low.indexOf(term)) !== -1) {
|
|
// before term
|
|
new_node.appendChild(document.createTextNode(
|
|
data.substr(0, result)));
|
|
// term
|
|
new_node.appendChild(dwv.html.createHighlightNode(
|
|
document.createTextNode(data.substr(
|
|
result, term.length))));
|
|
// reduce search string
|
|
data = data.substr(result + term.length);
|
|
data_low = data_low.substr(result + term.length);
|
|
}
|
|
new_node.appendChild(document.createTextNode(data));
|
|
}
|
|
} else {
|
|
// Keep going onto other elements
|
|
dwv.html.highlight(term, node);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Highlight a HTML node.
|
|
* @param {Object} child The child to highlight.
|
|
* @return {Object} The created HTML node.
|
|
*/
|
|
dwv.html.createHighlightNode = function (child) {
|
|
var node = document.createElement('span');
|
|
node.setAttribute('class', 'highlighted');
|
|
node.attributes['class'].value = 'highlighted';
|
|
node.appendChild(child);
|
|
return node;
|
|
};
|
|
|
|
/**
|
|
* Remove all children of a HTML node.
|
|
* @param {Object} node The node to remove kids.
|
|
*/
|
|
dwv.html.cleanNode = function (node) {
|
|
// remove its children if node exists
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
while (node.hasChildNodes()) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove a HTML node and all its children.
|
|
* @param {String} nodeId The string id of the node to delete.
|
|
*/
|
|
dwv.html.removeNode = function (node) {
|
|
// check node
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
// remove its children
|
|
dwv.html.cleanNode(node);
|
|
// remove it from its parent
|
|
var top = node.parentNode;
|
|
top.removeChild(node);
|
|
};
|
|
|
|
/**
|
|
* Remove a list of HTML nodes and all their children.
|
|
* @param {Array} nodes The list of nodes to delete.
|
|
*/
|
|
dwv.html.removeNodes = function (nodes) {
|
|
for ( var i = 0; i < nodes.length; ++i ) {
|
|
dwv.html.removeNode(nodes[i]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Translate the content of an HTML row.
|
|
* @param {Object} row The HTML row to parse.
|
|
* @param {String} i18nPrefix The i18n prefix to use to find the translation.
|
|
*/
|
|
dwv.html.translateTableRow = function (row, i18nPrefix) {
|
|
var prefix = (typeof i18nPrefix === "undefined") ? "basics" : i18nPrefix;
|
|
if (prefix.length !== 0) {
|
|
prefix += ".";
|
|
}
|
|
var cells = row.cells;
|
|
for (var c = 0; c < cells.length; ++c) {
|
|
var text = cells[c].firstChild.data;
|
|
cells[c].firstChild.data = dwv.i18n( prefix + text );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Translate the content of an HTML column.
|
|
* @param {Object} table The HTML table to parse.
|
|
* @param {Number} columnNumber The number of the column to translate.
|
|
* @param {String} i18nPrefix The i18n prefix to use to find the translation.
|
|
* @param {String} i18nSuffix The i18n suffix to use to find the translation.
|
|
*/
|
|
dwv.html.translateTableColumn = function (table, columnNumber, i18nPrefix, i18nSuffix) {
|
|
var prefix = (typeof i18nPrefix === "undefined") ? "basics" : i18nPrefix;
|
|
if (prefix.length !== 0) {
|
|
prefix += ".";
|
|
}
|
|
var suffix = (typeof i18nSuffix === "undefined") ? "" : i18nSuffix;
|
|
if (suffix.length !== 0) {
|
|
suffix = "." + suffix;
|
|
}
|
|
if (table.rows.length !== 0) {
|
|
for (var r = 1; r < table.rows.length; ++r) {
|
|
var cells = table.rows.item(r).cells;
|
|
if (cells.length >= columnNumber) {
|
|
var text = cells[columnNumber].firstChild.data;
|
|
cells[columnNumber].firstChild.data = dwv.i18n( prefix + text + suffix );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Make a HTML table cell editable by putting its content inside an input element.
|
|
* @param {Object} cell The cell to make editable.
|
|
* @param {Function} onchange The callback to call when cell's content is changed.
|
|
* if set to null, the HTML input will be disabled.
|
|
* @param {String} inputType The type of the HTML input, default to 'text'.
|
|
*/
|
|
dwv.html.makeCellEditable = function (cell, onchange, inputType) {
|
|
// check event
|
|
if (typeof cell === "undefined" ) {
|
|
console.warn("Cannot create input for non existing cell.");
|
|
return;
|
|
}
|
|
// HTML input
|
|
var input = document.createElement("input");
|
|
// handle change
|
|
if (onchange) {
|
|
input.onchange = onchange;
|
|
}
|
|
else {
|
|
input.disabled = true;
|
|
}
|
|
// set input value
|
|
input.value = cell.firstChild.data;
|
|
// input type
|
|
if (typeof inputType === "undefined" ||
|
|
(inputType === "color" && !dwv.browser.hasInputColor() ) ) {
|
|
input.type = "text";
|
|
}
|
|
else {
|
|
input.type = inputType;
|
|
}
|
|
|
|
// clean cell
|
|
dwv.html.cleanNode(cell);
|
|
|
|
// HTML form
|
|
var form = document.createElement("form");
|
|
form.onsubmit = function (event) {
|
|
event.preventDefault();
|
|
};
|
|
form.appendChild(input);
|
|
// add form to cell
|
|
cell.appendChild(form);
|
|
};
|
|
|
|
/**
|
|
* Set the document cursor to 'pointer'.
|
|
*/
|
|
dwv.html.setCursorToPointer = function () {
|
|
document.body.style.cursor = 'pointer';
|
|
};
|
|
|
|
/**
|
|
* Set the document cursor to 'default'.
|
|
*/
|
|
dwv.html.setCursorToDefault = function () {
|
|
document.body.style.cursor = 'default';
|
|
};
|
|
|
|
|
|
/**
|
|
* Create a HTML select from an input array of options.
|
|
* The values of the options are the name of the option made lower case.
|
|
* It is left to the user to set the 'onchange' method of the select.
|
|
* @param {String} name The name of the HTML select.
|
|
* @param {Mixed} list The list of options of the HTML select.
|
|
* @param {String} i18nPrefix An optional namespace prefix to find the translation values.
|
|
* @param {Bool} i18nSafe An optional flag to check translation existence.
|
|
* @return {Object} The created HTML select.
|
|
*/
|
|
dwv.html.createHtmlSelect = function (name, list, i18nPrefix, i18nSafe) {
|
|
// select
|
|
var select = document.createElement("select");
|
|
//select.name = name;
|
|
select.className = name;
|
|
var prefix = (typeof i18nPrefix === "undefined") ? "" : i18nPrefix + ".";
|
|
var safe = (typeof i18nSafe === "undefined") ? false : true;
|
|
var getText = function(value) {
|
|
var key = prefix + value + ".name";
|
|
var text = "";
|
|
if (safe) {
|
|
if (dwv.i18nExists(key)) {
|
|
text = dwv.i18n(key);
|
|
}
|
|
else {
|
|
text = value;
|
|
}
|
|
}
|
|
else {
|
|
text = dwv.i18n(key);
|
|
}
|
|
return text;
|
|
};
|
|
// options
|
|
var option;
|
|
if ( list instanceof Array )
|
|
{
|
|
for ( var i in list ) {
|
|
if ( list.hasOwnProperty(i) ) {
|
|
option = document.createElement("option");
|
|
option.value = list[i];
|
|
option.appendChild(document.createTextNode(getText(list[i])));
|
|
select.appendChild(option);
|
|
}
|
|
}
|
|
}
|
|
else if ( typeof list === 'object')
|
|
{
|
|
for ( var item in list )
|
|
{
|
|
option = document.createElement("option");
|
|
option.value = item;
|
|
option.appendChild(document.createTextNode(getText(item)));
|
|
select.appendChild(option);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Error("Unsupported input list type.");
|
|
}
|
|
return select;
|
|
};
|
|
|
|
/**
|
|
* Display or not an element.
|
|
* @param {Object} element The HTML element to display.
|
|
* @param {Boolean} flag True to display the element.
|
|
*/
|
|
dwv.html.displayElement = function (element, flag)
|
|
{
|
|
element.style.display = flag ? "" : "none";
|
|
};
|
|
|
|
/**
|
|
* Toggle the display of an element.
|
|
* @param {Object} element The HTML element to display.
|
|
*/
|
|
dwv.html.toggleDisplay = function (element)
|
|
{
|
|
if ( element.style.display === "none" ) {
|
|
element.style.display = '';
|
|
}
|
|
else {
|
|
element.style.display = "none";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Append an element.
|
|
* @param {Object} parent The HTML element to append to.
|
|
* @param {Object} element The HTML element to append.
|
|
*/
|
|
dwv.html.appendElement = function (parent, element)
|
|
{
|
|
// append
|
|
parent.appendChild(element);
|
|
// refresh
|
|
dwv.gui.refreshElement(parent);
|
|
};
|
|
|
|
/**
|
|
* Create an element.
|
|
* @param {String} type The type of the elemnt.
|
|
* @param {String} className The className of the element.
|
|
*/
|
|
dwv.html.createHiddenElement = function (type, className)
|
|
{
|
|
var element = document.createElement(type);
|
|
element.className = className;
|
|
// hide by default
|
|
element.style.display = "none";
|
|
// return
|
|
return element;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.gui = dwv.gui || {};
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
dwv.gui.info = dwv.gui.info || {};
|
|
|
|
/**
|
|
* Plot some data in a given div.
|
|
* @param {Object} div The HTML element to add WindowLevel info to.
|
|
* @param {Array} data The data array to plot.
|
|
* @param {Object} options Plot options.
|
|
*/
|
|
dwv.gui.base.plot = function (/*div, data, options*/)
|
|
{
|
|
// default does nothing...
|
|
};
|
|
|
|
/**
|
|
* MiniColourMap info layer.
|
|
* @constructor
|
|
* @param {Object} div The HTML element to add colourMap info to.
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.gui.info.MiniColourMap = function ( div, app )
|
|
{
|
|
/**
|
|
* Create the mini colour map info div.
|
|
*/
|
|
this.create = function ()
|
|
{
|
|
// clean div
|
|
var elems = div.getElementsByClassName("colour-map-info");
|
|
if ( elems.length !== 0 ) {
|
|
dwv.html.removeNodes(elems);
|
|
}
|
|
// colour map
|
|
var canvas = document.createElement("canvas");
|
|
canvas.className = "colour-map-info";
|
|
canvas.width = 98;
|
|
canvas.height = 10;
|
|
// add canvas to div
|
|
div.appendChild(canvas);
|
|
};
|
|
|
|
/**
|
|
* Update the mini colour map info div.
|
|
* @param {Object} event The windowing change event containing the new values.
|
|
* Warning: expects the mini colour map div to exist (use after createMiniColourMap).
|
|
*/
|
|
this.update = function (event)
|
|
{
|
|
var windowCenter = event.wc;
|
|
var windowWidth = event.ww;
|
|
// retrieve canvas and context
|
|
var canvas = div.getElementsByClassName("colour-map-info")[0];
|
|
var context = canvas.getContext('2d');
|
|
// fill in the image data
|
|
var colourMap = app.getViewController().getColourMap();
|
|
var imageData = context.getImageData(0,0,canvas.width, canvas.height);
|
|
// histogram sampling
|
|
var c = 0;
|
|
var minInt = app.getImage().getRescaledDataRange().min;
|
|
var range = app.getImage().getRescaledDataRange().max - minInt;
|
|
var incrC = range / canvas.width;
|
|
// Y scale
|
|
var y = 0;
|
|
var yMax = 255;
|
|
var yMin = 0;
|
|
// X scale
|
|
var xMin = windowCenter - 0.5 - (windowWidth-1) / 2;
|
|
var xMax = windowCenter - 0.5 + (windowWidth-1) / 2;
|
|
// loop through values
|
|
var index;
|
|
for ( var j = 0; j < canvas.height; ++j ) {
|
|
c = minInt;
|
|
for ( var i = 0; i < canvas.width; ++i ) {
|
|
if ( c <= xMin ) {
|
|
y = yMin;
|
|
}
|
|
else if ( c > xMax ) {
|
|
y = yMax;
|
|
}
|
|
else {
|
|
y = ( (c - (windowCenter-0.5) ) / (windowWidth-1) + 0.5 ) *
|
|
(yMax-yMin) + yMin;
|
|
y = parseInt(y,10);
|
|
}
|
|
index = (i + j * canvas.width) * 4;
|
|
imageData.data[index] = colourMap.red[y];
|
|
imageData.data[index+1] = colourMap.green[y];
|
|
imageData.data[index+2] = colourMap.blue[y];
|
|
imageData.data[index+3] = 0xff;
|
|
c += incrC;
|
|
}
|
|
}
|
|
// put the image data in the context
|
|
context.putImageData(imageData, 0, 0);
|
|
};
|
|
}; // class dwv.gui.info.MiniColourMap
|
|
|
|
|
|
/**
|
|
* Plot info layer.
|
|
* @constructor
|
|
* @param {Object} div The HTML element to add colourMap info to.
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.gui.info.Plot = function (div, app)
|
|
{
|
|
/**
|
|
* Create the plot info.
|
|
*/
|
|
this.create = function()
|
|
{
|
|
// clean div
|
|
if ( div ) {
|
|
dwv.html.cleanNode(div);
|
|
}
|
|
// plot
|
|
dwv.gui.plot(div, app.getImage().getHistogram());
|
|
};
|
|
|
|
/**
|
|
* Update plot.
|
|
* @param {Object} event The windowing change event containing the new values.
|
|
* Warning: expects the plot to exist (use after createPlot).
|
|
*/
|
|
this.update = function (event)
|
|
{
|
|
var wc = event.wc;
|
|
var ww = event.ww;
|
|
|
|
var half = parseInt( (ww-1) / 2, 10 );
|
|
var center = parseInt( (wc-0.5), 10 );
|
|
var min = center - half;
|
|
var max = center + half;
|
|
|
|
var markings = [
|
|
{ "color": "#faa", "lineWidth": 1, "xaxis": { "from": min, "to": min } },
|
|
{ "color": "#aaf", "lineWidth": 1, "xaxis": { "from": max, "to": max } }
|
|
];
|
|
|
|
// plot
|
|
dwv.gui.plot(div, app.getImage().getHistogram(), {markings: markings});
|
|
};
|
|
|
|
}; // class dwv.gui.info.Plot
|
|
|
|
/**
|
|
* DICOM Header overlay info layer.
|
|
* @constructor
|
|
* @param {Object} div The HTML element to add Header overlay info to.
|
|
* @param {String} pos The string to specify the corner position. (tl,tc,tr,cl,cr,bl,bc,br)
|
|
*/
|
|
dwv.gui.info.Overlay = function ( div, pos, app )
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Get the overlay array of the current position.
|
|
* @return {Array} The overlay information.
|
|
*/
|
|
this.getOverlays = function ()
|
|
{
|
|
var image = app.getImage();
|
|
if (!image) {
|
|
return;
|
|
}
|
|
var allOverlays = image.getOverlays();
|
|
if (!allOverlays) {
|
|
return;
|
|
}
|
|
var position = app.getViewController().getCurrentPosition();
|
|
var sliceOverlays = allOverlays[position.k];
|
|
if (!sliceOverlays) {
|
|
return;
|
|
}
|
|
return sliceOverlays[pos];
|
|
};
|
|
|
|
/**
|
|
* Create the overlay info div.
|
|
*/
|
|
this.create = function ()
|
|
{
|
|
// remove all <ul> elements from div
|
|
dwv.html.cleanNode(div);
|
|
|
|
// get overlay string array of the current position
|
|
var overlays = self.getOverlays();
|
|
if (!overlays) {
|
|
return;
|
|
}
|
|
|
|
if (pos === "bc" || pos === "tc" ||
|
|
pos === "cr" || pos === "cl") {
|
|
div.textContent = overlays[0].value;
|
|
} else {
|
|
// create <ul> element
|
|
var ul = document.createElement("ul");
|
|
|
|
for (var n=0; overlays[n]; n++){
|
|
var li;
|
|
if (overlays[n].value === "window-center") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-window-center";
|
|
ul.appendChild(li);
|
|
} else if (overlays[n].value === "window-width") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-window-width";
|
|
ul.appendChild(li);
|
|
} else if (overlays[n].value === "zoom") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-zoom";
|
|
ul.appendChild(li);
|
|
} else if (overlays[n].value === "offset") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-offset";
|
|
ul.appendChild(li);
|
|
} else if (overlays[n].value === "value") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-value";
|
|
ul.appendChild(li);
|
|
} else if (overlays[n].value === "position") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-position";
|
|
ul.appendChild(li);
|
|
} else if (overlays[n].value === "frame") {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-frame";
|
|
ul.appendChild(li);
|
|
} else {
|
|
li = document.createElement("li");
|
|
li.className = "info-" + pos + "-" + n;
|
|
li.appendChild( document.createTextNode( overlays[n].value ) );
|
|
ul.appendChild(li);
|
|
}
|
|
}
|
|
|
|
// append <ul> element before color map
|
|
div.appendChild(ul);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update the overlay info div.
|
|
* @param {Object} event A change event.
|
|
*/
|
|
this.update = function ( event )
|
|
{
|
|
// get overlay string array of the current position
|
|
var overlays = self.getOverlays();
|
|
if (!overlays) {
|
|
return;
|
|
}
|
|
|
|
if (pos === "bc" || pos === "tc" ||
|
|
pos === "cr" || pos === "cl") {
|
|
div.textContent = overlays[0].value;
|
|
} else {
|
|
var li;
|
|
var n;
|
|
|
|
for (n=0; overlays[n]; n++) {
|
|
if (overlays[n].value === "window-center") {
|
|
if (event.type === "wl-center-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-window-center")[0];
|
|
dwv.html.cleanNode(li);
|
|
var wcStr = dwv.utils.replaceFlags2( overlays[n].format, [Math.round(event.wc)] );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode(wcStr) );
|
|
}
|
|
}
|
|
} else if (overlays[n].value === "window-width") {
|
|
if (event.type === "wl-width-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-window-width")[0];
|
|
dwv.html.cleanNode(li);
|
|
var wwStr = dwv.utils.replaceFlags2( overlays[n].format, [Math.round(event.ww)] );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode(wwStr) );
|
|
}
|
|
}
|
|
} else if (overlays[n].value === "zoom") {
|
|
if (event.type === "zoom-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-zoom")[0];
|
|
dwv.html.cleanNode(li);
|
|
var zoom = Number(event.scale).toPrecision(3);
|
|
var zoomStr = dwv.utils.replaceFlags2( overlays[n].format, [zoom] );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode( zoomStr ) );
|
|
}
|
|
}
|
|
} else if (overlays[n].value === "offset") {
|
|
if (event.type === "zoom-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-offset")[0];
|
|
dwv.html.cleanNode(li);
|
|
var offset = [ Number(event.cx).toPrecision(3),
|
|
Number(event.cy).toPrecision(3)];
|
|
var offStr = dwv.utils.replaceFlags2( overlays[n].format, offset );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode( offStr ) );
|
|
}
|
|
}
|
|
} else if (overlays[n].value === "value") {
|
|
if (event.type === "position-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-value")[0];
|
|
dwv.html.cleanNode(li);
|
|
var valueStr = dwv.utils.replaceFlags2( overlays[n].format, [event.value] );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode( valueStr ) );
|
|
}
|
|
}
|
|
} else if (overlays[n].value === "position") {
|
|
if (event.type === "position-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-position")[0];
|
|
dwv.html.cleanNode(li);
|
|
var posStr = dwv.utils.replaceFlags2( overlays[n].format, [event.i, event.j, event.k] );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode( posStr ) );
|
|
}
|
|
}
|
|
} else if (overlays[n].value === "frame") {
|
|
if (event.type === "frame-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-frame")[0];
|
|
dwv.html.cleanNode(li);
|
|
var frameStr = dwv.utils.replaceFlags2( overlays[n].format, [event.frame] );
|
|
if (li) {
|
|
li.appendChild( document.createTextNode( frameStr ) );
|
|
}
|
|
}
|
|
} else {
|
|
if (event.type === "position-change") {
|
|
li = div.getElementsByClassName("info-" + pos + "-" + n)[0];
|
|
dwv.html.cleanNode(li);
|
|
if (li) {
|
|
li.appendChild( document.createTextNode( overlays[n].value ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}; // class dwv.gui.info.Overlay
|
|
|
|
/**
|
|
* Create overlay string array of the image in each corner
|
|
* @param {Object} dicomElements DICOM elements of the image
|
|
* @return {Array} Array of string to be shown in each corner
|
|
*/
|
|
dwv.gui.info.createOverlays = function (dicomElements)
|
|
{
|
|
var overlays = {};
|
|
var modality = dicomElements.getFromKey("x00080060");
|
|
if (!modality){
|
|
return overlays;
|
|
}
|
|
|
|
var omaps = dwv.gui.info.overlayMaps;
|
|
if (!omaps){
|
|
return overlays;
|
|
}
|
|
var omap = omaps[modality] || omaps['*'];
|
|
|
|
for (var n=0; omap[n]; n++){
|
|
var value = omap[n].value;
|
|
var tags = omap[n].tags;
|
|
var format = omap[n].format;
|
|
var pos = omap[n].pos;
|
|
|
|
if (typeof tags !== "undefined" && tags.length !== 0) {
|
|
// get values
|
|
var values = [];
|
|
for ( var i = 0; i < tags.length; ++i ) {
|
|
values.push( dicomElements.getElementValueAsStringFromKey( tags[i] ) );
|
|
}
|
|
// format
|
|
if (typeof format === "undefined" || format === null) {
|
|
format = dwv.utils.createDefaultReplaceFormat( values );
|
|
}
|
|
value = dwv.utils.replaceFlags2( format, values );
|
|
}
|
|
|
|
if (!value || value.length === 0){
|
|
continue;
|
|
}
|
|
|
|
// add value to overlays
|
|
if (!overlays[pos]) {
|
|
overlays[pos] = [];
|
|
}
|
|
overlays[pos].push({'value': value.trim(), 'format': format});
|
|
}
|
|
|
|
// (0020,0020) Patient Orientation
|
|
var valuePO = dicomElements.getFromKey("x00200020");
|
|
if (typeof valuePO !== "undefined" && valuePO !== null && valuePO.length == 2){
|
|
var po0 = dwv.dicom.cleanString(valuePO[0]);
|
|
var po1 = dwv.dicom.cleanString(valuePO[1]);
|
|
overlays.cr = [{'value': po0}];
|
|
overlays.cl = [{'value': dwv.dicom.getReverseOrientation(po0)}];
|
|
overlays.bc = [{'value': po1}];
|
|
overlays.tc = [{'value': dwv.dicom.getReverseOrientation(po1)}];
|
|
}
|
|
|
|
return overlays;
|
|
};
|
|
|
|
/**
|
|
* Create overlay string array of the image in each corner
|
|
* @param {Object} dicomElements DICOM elements of the image
|
|
* @return {Array} Array of string to be shown in each corner
|
|
*/
|
|
dwv.gui.info.createOverlaysForDom = function (info)
|
|
{
|
|
var overlays = {};
|
|
var omaps = dwv.gui.info.overlayMaps;
|
|
if (!omaps){
|
|
return overlays;
|
|
}
|
|
var omap = omaps.DOM;
|
|
if (!omap){
|
|
return overlays;
|
|
}
|
|
|
|
for (var n=0; omap[n]; n++){
|
|
var value = omap[n].value;
|
|
var tags = omap[n].tags;
|
|
var format = omap[n].format;
|
|
var pos = omap[n].pos;
|
|
|
|
if (typeof tags !== "undefined" && tags.length !== 0) {
|
|
// get values
|
|
var values = [];
|
|
for ( var i = 0; i < tags.length; ++i ) {
|
|
for ( var j = 0; j < info.length; ++j ) {
|
|
if (tags[i] === info[j].name) {
|
|
values.push( info[j].value );
|
|
}
|
|
}
|
|
}
|
|
// format
|
|
if (typeof format === "undefined" || format === null) {
|
|
format = dwv.utils.createDefaultReplaceFormat( values );
|
|
}
|
|
value = dwv.utils.replaceFlags2( format, values );
|
|
}
|
|
|
|
if (!value || value.length === 0){
|
|
continue;
|
|
}
|
|
|
|
// add value to overlays
|
|
if (!overlays[pos]) {
|
|
overlays[pos] = [];
|
|
}
|
|
overlays[pos].push({'value': value.trim(), 'format': format});
|
|
}
|
|
|
|
return overlays;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.html = dwv.html || {};
|
|
|
|
/**
|
|
* Window layer.
|
|
* @constructor
|
|
* @param {String} name The name of the layer.
|
|
*/
|
|
dwv.html.Layer = function(canvas)
|
|
{
|
|
/**
|
|
* A cache of the initial canvas.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var cacheCanvas = null;
|
|
/**
|
|
* The associated CanvasRenderingContext2D.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var context = null;
|
|
|
|
/**
|
|
* Get the layer canvas.
|
|
* @return {Object} The layer canvas.
|
|
*/
|
|
this.getCanvas = function() { return canvas; };
|
|
/**
|
|
* Get the layer context.
|
|
* @return {Object} The layer context.
|
|
*/
|
|
this.getContext = function() { return context; };
|
|
/**
|
|
* Get the layer offset on page.
|
|
* @return {Number} The layer offset on page.
|
|
*/
|
|
this.getOffset = function() { return canvas.offset(); };
|
|
|
|
/**
|
|
* The image data array.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var imageData = null;
|
|
|
|
/**
|
|
* The layer origin.
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
var origin = {'x': 0, 'y': 0};
|
|
/**
|
|
* Get the layer origin.
|
|
* @return {Object} The layer origin as {'x','y'}.
|
|
*/
|
|
this.getOrigin = function () {
|
|
return origin;
|
|
};
|
|
/**
|
|
* The layer zoom.
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
var zoom = {'x': 1, 'y': 1};
|
|
/**
|
|
* Get the layer zoom.
|
|
* @return {Object} The layer zoom as {'x','y'}.
|
|
*/
|
|
this.getZoom = function () {
|
|
return zoom;
|
|
};
|
|
|
|
/**
|
|
* The layer translation.
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
var trans = {'x': 0, 'y': 0};
|
|
/**
|
|
* Get the layer translation.
|
|
* @return {Object} The layer translation as {'x','y'}.
|
|
*/
|
|
this.getTrans = function () {
|
|
return trans;
|
|
};
|
|
|
|
/**
|
|
* Set the canvas width.
|
|
* @param {Number} width The new width.
|
|
*/
|
|
this.setWidth = function ( width ) {
|
|
canvas.width = width;
|
|
};
|
|
/**
|
|
* Set the canvas height.
|
|
* @param {Number} height The new height.
|
|
*/
|
|
this.setHeight = function ( height ) {
|
|
canvas.height = height;
|
|
};
|
|
|
|
/**
|
|
* Set the layer zoom.
|
|
* @param {Number} newZoomX The zoom in the X direction.
|
|
* @param {Number} newZoomY The zoom in the Y direction.
|
|
* @param {Number} centerX The zoom center in the X direction.
|
|
* @param {Number} centerY The zoom center in the Y direction.
|
|
*/
|
|
this.zoom = function(newZoomX,newZoomY,centerX,centerY)
|
|
{
|
|
// The zoom is the ratio between the differences from the center
|
|
// to the origins:
|
|
// centerX - originX = ( centerX - originX0 ) * zoomX
|
|
// (center in ~world coordinate system)
|
|
//originX = (centerX / zoomX) + originX - (centerX / newZoomX);
|
|
//originY = (centerY / zoomY) + originY - (centerY / newZoomY);
|
|
|
|
// center in image coordinate system
|
|
origin.x = centerX - (centerX - origin.x) * (newZoomX / zoom.x);
|
|
origin.y = centerY - (centerY - origin.y) * (newZoomY / zoom.y);
|
|
|
|
// save zoom
|
|
zoom.x = newZoomX;
|
|
zoom.y = newZoomY;
|
|
};
|
|
|
|
/**
|
|
* Set the layer translation.
|
|
* Translation is according to the last one.
|
|
* @param {Number} tx The translation in the X direction.
|
|
* @param {Number} ty The translation in the Y direction.
|
|
*/
|
|
this.translate = function(tx,ty)
|
|
{
|
|
trans.x = tx;
|
|
trans.y = ty;
|
|
};
|
|
|
|
/**
|
|
* Set the image data array.
|
|
* @param {Array} data The data array.
|
|
*/
|
|
this.setImageData = function(data)
|
|
{
|
|
imageData = data;
|
|
// update the cached canvas
|
|
cacheCanvas.getContext("2d").putImageData(imageData, 0, 0);
|
|
};
|
|
|
|
/**
|
|
* Reset the layout.
|
|
*/
|
|
this.resetLayout = function(izoom)
|
|
{
|
|
origin.x = 0;
|
|
origin.y = 0;
|
|
zoom.x = izoom;
|
|
zoom.y = izoom;
|
|
trans.x = 0;
|
|
trans.y = 0;
|
|
};
|
|
|
|
/**
|
|
* Transform a display position to an index.
|
|
*/
|
|
this.displayToIndex = function ( point2D ) {
|
|
return {'x': ( (point2D.x - origin.x) / zoom.x ) - trans.x,
|
|
'y': ( (point2D.y - origin.y) / zoom.y ) - trans.y};
|
|
};
|
|
|
|
/**
|
|
* Draw the content (imageData) of the layer.
|
|
* The imageData variable needs to be set
|
|
*/
|
|
this.draw = function ()
|
|
{
|
|
// clear the context: reset the transform first
|
|
// store the current transformation matrix
|
|
context.save();
|
|
// use the identity matrix while clearing the canvas
|
|
context.setTransform( 1, 0, 0, 1, 0, 0 );
|
|
context.clearRect( 0, 0, canvas.width, canvas.height );
|
|
// restore the transform
|
|
context.restore();
|
|
|
|
// draw the cached canvas on the context
|
|
// transform takes as input a, b, c, d, e, f to create
|
|
// the transform matrix (column-major order):
|
|
// [ a c e ]
|
|
// [ b d f ]
|
|
// [ 0 0 1 ]
|
|
context.setTransform( zoom.x, 0, 0, zoom.y,
|
|
origin.x + (trans.x * zoom.x),
|
|
origin.y + (trans.y * zoom.y) );
|
|
context.drawImage( cacheCanvas, 0, 0 );
|
|
};
|
|
|
|
/**
|
|
* Initialise the layer: set the canvas and context
|
|
* @input {Number} inputWidth The width of the canvas.
|
|
* @input {Number} inputHeight The height of the canvas.
|
|
*/
|
|
this.initialise = function(inputWidth, inputHeight)
|
|
{
|
|
// find the canvas element
|
|
//canvas = document.getElementById(name);
|
|
//if (!canvas)
|
|
//{
|
|
// alert("Error: cannot find the canvas element for '" + name + "'.");
|
|
// return;
|
|
//}
|
|
// check that the getContext method exists
|
|
if (!canvas.getContext)
|
|
{
|
|
alert("Error: no canvas.getContext method.");
|
|
return;
|
|
}
|
|
// get the 2D context
|
|
context = canvas.getContext('2d');
|
|
if (!context)
|
|
{
|
|
alert("Error: failed to get the 2D context.");
|
|
return;
|
|
}
|
|
// canvas sizes
|
|
canvas.width = inputWidth;
|
|
canvas.height = inputHeight;
|
|
// original empty image data array
|
|
context.clearRect (0, 0, canvas.width, canvas.height);
|
|
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
// cached canvas
|
|
cacheCanvas = document.createElement("canvas");
|
|
cacheCanvas.width = inputWidth;
|
|
cacheCanvas.height = inputHeight;
|
|
};
|
|
|
|
/**
|
|
* Fill the full context with the current style.
|
|
*/
|
|
this.fillContext = function()
|
|
{
|
|
context.fillRect( 0, 0, canvas.width, canvas.height );
|
|
};
|
|
|
|
/**
|
|
* Clear the context and reset the image data.
|
|
*/
|
|
this.clear = function()
|
|
{
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
this.resetLayout();
|
|
};
|
|
|
|
/**
|
|
* Merge two layers.
|
|
* @input {Layer} layerToMerge The layer to merge. It will also be emptied.
|
|
*/
|
|
this.merge = function(layerToMerge)
|
|
{
|
|
// basic resampling of the merge data to put it at zoom 1:1
|
|
var mergeImageData = layerToMerge.getContext().getImageData(
|
|
0, 0, canvas.width, canvas.height);
|
|
var offMerge = 0;
|
|
var offMergeJ = 0;
|
|
var offThis = 0;
|
|
var offThisJ = 0;
|
|
var alpha = 0;
|
|
for( var j=0; j < canvas.height; ++j ) {
|
|
offMergeJ = parseInt( (origin.y + j * zoom.y), 10 ) * canvas.width;
|
|
offThisJ = j * canvas.width;
|
|
for( var i=0; i < canvas.width; ++i ) {
|
|
// 4 component data: RGB + alpha
|
|
offMerge = 4 * ( parseInt( (origin.x + i * zoom.x), 10 ) + offMergeJ );
|
|
offThis = 4 * ( i + offThisJ );
|
|
// merge non transparent
|
|
alpha = mergeImageData.data[offMerge+3];
|
|
if( alpha !== 0 ) {
|
|
imageData.data[offThis] = mergeImageData.data[offMerge];
|
|
imageData.data[offThis+1] = mergeImageData.data[offMerge+1];
|
|
imageData.data[offThis+2] = mergeImageData.data[offMerge+2];
|
|
imageData.data[offThis+3] = alpha;
|
|
}
|
|
}
|
|
}
|
|
// empty and reset merged layer
|
|
layerToMerge.clear();
|
|
// draw the layer
|
|
this.draw();
|
|
};
|
|
|
|
/**
|
|
* Set the line colour for the layer.
|
|
* @input {String} colour The line colour.
|
|
*/
|
|
this.setLineColour = function(colour)
|
|
{
|
|
context.fillStyle = colour;
|
|
context.strokeStyle = colour;
|
|
};
|
|
|
|
/**
|
|
* Display the layer.
|
|
* @input {Boolean} val Whether to display the layer or not.
|
|
*/
|
|
this.setStyleDisplay = function(val)
|
|
{
|
|
if( val === true )
|
|
{
|
|
canvas.style.display = '';
|
|
}
|
|
else
|
|
{
|
|
canvas.style.display = "none";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if the layer is visible.
|
|
* @return {Boolean} True if the layer is visible.
|
|
*/
|
|
this.isVisible = function()
|
|
{
|
|
if( canvas.style.display === "none" ) {
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Align on another layer.
|
|
* @param {Layer} rhs The layer to align on.
|
|
*/
|
|
this.align = function(rhs)
|
|
{
|
|
canvas.style.top = rhs.getCanvas().offsetTop;
|
|
canvas.style.left = rhs.getCanvas().offsetLeft;
|
|
};
|
|
}; // Layer class
|
|
|
|
/**
|
|
* Get the offset of an input event.
|
|
* @param {Object} event The event to get the offset from.
|
|
* @return {Array} The array of offsets.
|
|
*/
|
|
dwv.html.getEventOffset = function (event) {
|
|
var positions = [];
|
|
var ex = 0;
|
|
var ey = 0;
|
|
if ( event.targetTouches ) {
|
|
// get the touch offset from all its parents
|
|
var offsetLeft = 0;
|
|
var offsetTop = 0;
|
|
var offsetParent = event.targetTouches[0].target.offsetParent;
|
|
while ( offsetParent ) {
|
|
if (!isNaN(offsetParent.offsetLeft)) {
|
|
offsetLeft += offsetParent.offsetLeft;
|
|
}
|
|
if (!isNaN(offsetParent.offsetTop)) {
|
|
offsetTop += offsetParent.offsetTop;
|
|
}
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
// set its position
|
|
var touch = null;
|
|
for ( var i = 0 ; i < event.targetTouches.length; ++i ) {
|
|
touch = event.targetTouches[i];
|
|
ex = touch.pageX - offsetLeft;
|
|
ey = touch.pageY - offsetTop;
|
|
positions.push({'x': ex, 'y': ey});
|
|
}
|
|
}
|
|
else {
|
|
// layerX is used by Firefox
|
|
ex = event.offsetX === undefined ? event.layerX : event.offsetX;
|
|
ey = event.offsetY === undefined ? event.layerY : event.offsetY;
|
|
positions.push({'x': ex, 'y': ey});
|
|
}
|
|
return positions;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.gui = dwv.gui || {};
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
|
|
/**
|
|
* Loadbox base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Loadbox = function (app, loaders)
|
|
{
|
|
/**
|
|
* Loader HTML select.
|
|
* @private
|
|
*/
|
|
var loaderSelector = null;
|
|
|
|
/**
|
|
* Setup the loadbox HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// loader select
|
|
loaderSelector = dwv.html.createHtmlSelect("loaderSelect", loaders, "io");
|
|
loaderSelector.onchange = app.onChangeLoader;
|
|
|
|
// node
|
|
var node = app.getElement("loaderlist");
|
|
// clear it
|
|
while(node.hasChildNodes()) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
// append
|
|
node.appendChild(loaderSelector);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display a loader.
|
|
* @param {String} name The name of the loader to show.
|
|
*/
|
|
this.displayLoader = function (name)
|
|
{
|
|
var keys = Object.keys(loaders);
|
|
for ( var i = 0; i < keys.length; ++i ) {
|
|
if ( keys[i] === name ) {
|
|
loaders[keys[i]].display(true);
|
|
}
|
|
else {
|
|
loaders[keys[i]].display(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset to its original state.
|
|
*/
|
|
this.reset = function ()
|
|
{
|
|
// display first loader
|
|
var keys = Object.keys(loaders);
|
|
this.displayLoader(keys[0]);
|
|
// reset HTML select
|
|
if (loaderSelector) {
|
|
loaderSelector.selectedIndex = 0;
|
|
}
|
|
};
|
|
|
|
}; // class dwv.gui.base.Loadbox
|
|
|
|
/**
|
|
* FileLoad base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.FileLoad = function (app)
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Internal file input change handler.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
function onchangeinternal(event) {
|
|
if (typeof self.onchange === "function") {
|
|
self.onchange(event);
|
|
}
|
|
app.onChangeFiles(event);
|
|
}
|
|
|
|
/**
|
|
* Setup the file load HTML to the page.
|
|
*/
|
|
this.setup = function()
|
|
{
|
|
// input
|
|
var fileLoadInput = document.createElement("input");
|
|
fileLoadInput.onchange = onchangeinternal;
|
|
fileLoadInput.type = "file";
|
|
fileLoadInput.multiple = true;
|
|
fileLoadInput.className = "imagefiles";
|
|
fileLoadInput.setAttribute("data-clear-btn","true");
|
|
fileLoadInput.setAttribute("data-mini","true");
|
|
|
|
// associated div
|
|
var fileLoadDiv = document.createElement("div");
|
|
fileLoadDiv.className = "imagefilesdiv";
|
|
fileLoadDiv.style.display = "none";
|
|
fileLoadDiv.appendChild(fileLoadInput);
|
|
|
|
// node
|
|
var node = app.getElement("loaderlist");
|
|
// append
|
|
node.appendChild(fileLoadDiv);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the file load HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// file div element
|
|
var node = app.getElement("loaderlist");
|
|
var filediv = node.getElementsByClassName("imagefilesdiv")[0];
|
|
filediv.style.display = bool ? "" : "none";
|
|
};
|
|
|
|
}; // class dwv.gui.base.FileLoad
|
|
|
|
/**
|
|
* FolderLoad base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.FolderLoad = function (app)
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Internal file input change handler.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
function onchangeinternal(event) {
|
|
if (typeof self.onchange === "function") {
|
|
self.onchange(event);
|
|
}
|
|
app.onChangeFiles(event);
|
|
}
|
|
|
|
/**
|
|
* Setup the file load HTML to the page.
|
|
*/
|
|
this.setup = function()
|
|
{
|
|
// input
|
|
var fileLoadInput = document.createElement("input");
|
|
fileLoadInput.onchange = onchangeinternal;
|
|
fileLoadInput.type = "file";
|
|
fileLoadInput.multiple = true;
|
|
fileLoadInput.webkitdirectory = true;
|
|
fileLoadInput.className = "imagefolder";
|
|
fileLoadInput.setAttribute("data-clear-btn","true");
|
|
fileLoadInput.setAttribute("data-mini","true");
|
|
|
|
// associated div
|
|
var folderLoadDiv = document.createElement("div");
|
|
folderLoadDiv.className = "imagefolderdiv";
|
|
folderLoadDiv.style.display = "none";
|
|
folderLoadDiv.appendChild(fileLoadInput);
|
|
|
|
// node
|
|
var node = app.getElement("loaderlist");
|
|
// append
|
|
node.appendChild(folderLoadDiv);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the folder load HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// file div element
|
|
var node = app.getElement("loaderlist");
|
|
var folderdiv = node.getElementsByClassName("imagefolderdiv")[0];
|
|
folderdiv.style.display = bool ? "" : "none";
|
|
};
|
|
|
|
}; // class dwv.gui.base.FileLoad
|
|
|
|
/**
|
|
* UrlLoad base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.UrlLoad = function (app)
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Internal url input change handler.
|
|
* @param {Object} event The change event.
|
|
*/
|
|
function onchangeinternal(event) {
|
|
if (typeof self.onchange === "function") {
|
|
self.onchange(event);
|
|
}
|
|
app.onChangeURL(event);
|
|
}
|
|
|
|
/**
|
|
* Setup the url load HTML to the page.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// input
|
|
var urlLoadInput = document.createElement("input");
|
|
urlLoadInput.onchange = onchangeinternal;
|
|
urlLoadInput.type = "url";
|
|
urlLoadInput.className = "imageurl";
|
|
urlLoadInput.setAttribute("data-clear-btn","true");
|
|
urlLoadInput.setAttribute("data-mini","true");
|
|
|
|
// associated div
|
|
var urlLoadDiv = document.createElement("div");
|
|
urlLoadDiv.className = "imageurldiv";
|
|
urlLoadDiv.style.display = "none";
|
|
urlLoadDiv.appendChild(urlLoadInput);
|
|
|
|
// node
|
|
var node = app.getElement("loaderlist");
|
|
// append
|
|
node.appendChild(urlLoadDiv);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the url load HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// url div element
|
|
var node = app.getElement("loaderlist");
|
|
var urldiv = node.getElementsByClassName("imageurldiv")[0];
|
|
urldiv.style.display = bool ? "" : "none";
|
|
};
|
|
|
|
}; // class dwv.gui.base.UrlLoad
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.html = dwv.html || {};
|
|
|
|
/**
|
|
* Style class.
|
|
* @constructor
|
|
*/
|
|
dwv.html.Style = function ()
|
|
{
|
|
/**
|
|
* Font size.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var fontSize = 12;
|
|
/**
|
|
* Font family.
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var fontFamily = "Verdana";
|
|
/**
|
|
* Text colour.
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var textColour = "#fff";
|
|
/**
|
|
* Line colour.
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var lineColour = "#ffff80";
|
|
/**
|
|
* Display scale.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var displayScale = 1;
|
|
/**
|
|
* Stroke width.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var strokeWidth = 2;
|
|
|
|
/**
|
|
* Get the font family.
|
|
* @return {String} The font family.
|
|
*/
|
|
this.getFontFamily = function () { return fontFamily; };
|
|
|
|
/**
|
|
* Get the font size.
|
|
* @return {Number} The font size.
|
|
*/
|
|
this.getFontSize = function () { return fontSize; };
|
|
|
|
/**
|
|
* Get the stroke width.
|
|
* @return {Number} The stroke width.
|
|
*/
|
|
this.getStrokeWidth = function () { return strokeWidth; };
|
|
|
|
/**
|
|
* Get the text colour.
|
|
* @return {String} The text colour.
|
|
*/
|
|
this.getTextColour = function () { return textColour; };
|
|
|
|
/**
|
|
* Get the line colour.
|
|
* @return {String} The line colour.
|
|
*/
|
|
this.getLineColour = function () { return lineColour; };
|
|
|
|
/**
|
|
* Set the line colour.
|
|
* @param {String} colour The line colour.
|
|
*/
|
|
this.setLineColour = function (colour) { lineColour = colour; };
|
|
|
|
/**
|
|
* Set the display scale.
|
|
* @param {String} scale The display scale.
|
|
*/
|
|
this.setScale = function (scale) { displayScale = scale; };
|
|
|
|
/**
|
|
* Scale an input value.
|
|
* @param {Number} value The value to scale.
|
|
*/
|
|
this.scale = function (value) { return value / displayScale; };
|
|
};
|
|
|
|
/**
|
|
* Get the font definition string.
|
|
* @return {String} The font definition string.
|
|
*/
|
|
dwv.html.Style.prototype.getFontStr = function ()
|
|
{
|
|
return ("normal " + this.getFontSize() + "px sans-serif");
|
|
};
|
|
|
|
/**
|
|
* Get the line height.
|
|
* @return {Number} The line height.
|
|
*/
|
|
dwv.html.Style.prototype.getLineHeight = function ()
|
|
{
|
|
return ( this.getFontSize() + this.getFontSize() / 5 );
|
|
};
|
|
|
|
/**
|
|
* Get the font size scaled to the display.
|
|
* @return {Number} The scaled font size.
|
|
*/
|
|
dwv.html.Style.prototype.getScaledFontSize = function ()
|
|
{
|
|
return this.scale( this.getFontSize() );
|
|
};
|
|
|
|
/**
|
|
* Get the stroke width scaled to the display.
|
|
* @return {Number} The scaled stroke width.
|
|
*/
|
|
dwv.html.Style.prototype.getScaledStrokeWidth = function ()
|
|
{
|
|
return this.scale( this.getStrokeWidth() );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.gui = dwv.gui || {};
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
|
|
/**
|
|
* Toolbox base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Toolbox = function (app)
|
|
{
|
|
/**
|
|
* Setup the toolbox HTML.
|
|
*/
|
|
this.setup = function (list)
|
|
{
|
|
// tool select
|
|
var toolSelector = dwv.html.createHtmlSelect("toolSelect", list, "tool");
|
|
toolSelector.onchange = app.onChangeTool;
|
|
|
|
// tool list element
|
|
var toolLi = document.createElement("li");
|
|
toolLi.className = "toolLi ui-block-a";
|
|
toolLi.style.display = "none";
|
|
toolLi.appendChild(toolSelector);
|
|
|
|
// tool ul
|
|
var toolUl = document.createElement("ul");
|
|
toolUl.appendChild(toolLi);
|
|
toolUl.className = "ui-grid-b";
|
|
|
|
// node
|
|
var node = app.getElement("toolList");
|
|
// append
|
|
node.appendChild(toolUl);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the toolbox HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// tool list element
|
|
var node = app.getElement("toolLi");
|
|
dwv.html.displayElement(node, bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the toolbox HTML.
|
|
*/
|
|
this.initialise = function (displays)
|
|
{
|
|
// tool select: reset selected option
|
|
var toolSelector = app.getElement("toolSelect");
|
|
|
|
// update list
|
|
var options = toolSelector.options;
|
|
var selectedIndex = -1;
|
|
for ( var i = 0; i < options.length; ++i ) {
|
|
if ( !displays[i] ) {
|
|
options[i].style.display = "none";
|
|
}
|
|
else {
|
|
if ( selectedIndex === -1 ) {
|
|
selectedIndex = i;
|
|
}
|
|
options[i].style.display = "";
|
|
}
|
|
}
|
|
toolSelector.selectedIndex = selectedIndex;
|
|
|
|
// refresh
|
|
dwv.gui.refreshElement(toolSelector);
|
|
};
|
|
|
|
}; // dwv.gui.base.Toolbox
|
|
|
|
/**
|
|
* WindowLevel tool base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.WindowLevel = function (app)
|
|
{
|
|
/**
|
|
* Setup the tool HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// preset select
|
|
var wlSelector = dwv.html.createHtmlSelect("presetSelect", []);
|
|
wlSelector.onchange = app.onChangeWindowLevelPreset;
|
|
// colour map select
|
|
var cmSelector = dwv.html.createHtmlSelect("colourMapSelect", dwv.tool.colourMaps, "colourmap");
|
|
cmSelector.onchange = app.onChangeColourMap;
|
|
|
|
// preset list element
|
|
var wlLi = document.createElement("li");
|
|
wlLi.className = "wlLi ui-block-b";
|
|
//wlLi.className = "wlLi";
|
|
wlLi.style.display = "none";
|
|
wlLi.appendChild(wlSelector);
|
|
// colour map list element
|
|
var cmLi = document.createElement("li");
|
|
cmLi.className = "cmLi ui-block-c";
|
|
//cmLi.className = "cmLi";
|
|
cmLi.style.display = "none";
|
|
cmLi.appendChild(cmSelector);
|
|
|
|
// node
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
// append preset
|
|
node.appendChild(wlLi);
|
|
// append colour map
|
|
node.appendChild(cmLi);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the tool HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// presets list element
|
|
var node = app.getElement("wlLi");
|
|
dwv.html.displayElement(node, bool);
|
|
// colour map list element
|
|
node = app.getElement("cmLi");
|
|
dwv.html.displayElement(node, bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
// create new preset select
|
|
var wlSelector = dwv.html.createHtmlSelect("presetSelect",
|
|
app.getViewController().getWindowLevelPresetsNames(), "wl.presets", true);
|
|
wlSelector.onchange = app.onChangeWindowLevelPreset;
|
|
wlSelector.title = "Select w/l preset.";
|
|
|
|
// copy html list
|
|
var wlLi = app.getElement("wlLi");
|
|
// clear node
|
|
dwv.html.cleanNode(wlLi);
|
|
// add children
|
|
wlLi.appendChild(wlSelector);
|
|
// refresh
|
|
dwv.gui.refreshElement(wlLi);
|
|
|
|
// colour map select
|
|
var cmSelector = app.getElement("colourMapSelect");
|
|
cmSelector.selectedIndex = 0;
|
|
// special monochrome1 case
|
|
if( app.getImage().getPhotometricInterpretation() === "MONOCHROME1" )
|
|
{
|
|
cmSelector.selectedIndex = 1;
|
|
}
|
|
// refresh
|
|
dwv.gui.refreshElement(cmSelector);
|
|
};
|
|
|
|
}; // class dwv.gui.base.WindowLevel
|
|
|
|
/**
|
|
* Draw tool base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Draw = function (app)
|
|
{
|
|
// default colours
|
|
var colours = [
|
|
"Yellow", "Red", "White", "Green", "Blue", "Lime", "Fuchsia", "Black"
|
|
];
|
|
/**
|
|
* Get the default colour.
|
|
*/
|
|
this.getDefaultColour = function () {
|
|
if ( dwv.browser.hasInputColor() ) {
|
|
return "#FFFF80";
|
|
}
|
|
else {
|
|
return colours[0];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Setup the tool HTML.
|
|
*/
|
|
this.setup = function (shapeList)
|
|
{
|
|
// shape select
|
|
var shapeSelector = dwv.html.createHtmlSelect("shapeSelect", shapeList, "shape");
|
|
shapeSelector.onchange = app.onChangeShape;
|
|
// colour select
|
|
var colourSelector = null;
|
|
if ( dwv.browser.hasInputColor() ) {
|
|
colourSelector = document.createElement("input");
|
|
colourSelector.className = "colourSelect";
|
|
colourSelector.type = "color";
|
|
colourSelector.value = "#FFFF80";
|
|
}
|
|
else {
|
|
colourSelector = dwv.html.createHtmlSelect("colourSelect", colours, "colour");
|
|
}
|
|
colourSelector.onchange = app.onChangeLineColour;
|
|
|
|
// shape list element
|
|
var shapeLi = document.createElement("li");
|
|
shapeLi.className = "shapeLi ui-block-c";
|
|
shapeLi.style.display = "none";
|
|
shapeLi.appendChild(shapeSelector);
|
|
//shapeLi.setAttribute("class","ui-block-c");
|
|
// colour list element
|
|
var colourLi = document.createElement("li");
|
|
colourLi.className = "colourLi ui-block-b";
|
|
colourLi.style.display = "none";
|
|
colourLi.appendChild(colourSelector);
|
|
//colourLi.setAttribute("class","ui-block-b");
|
|
|
|
// node
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
// apend shape
|
|
node.appendChild(shapeLi);
|
|
// append colour
|
|
node.appendChild(colourLi);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the tool HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// colour list element
|
|
var node = app.getElement("colourLi");
|
|
dwv.html.displayElement(node, bool);
|
|
// shape list element
|
|
node = app.getElement("shapeLi");
|
|
dwv.html.displayElement(node, bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
// shape select: reset selected option
|
|
var shapeSelector = app.getElement("shapeSelect");
|
|
shapeSelector.selectedIndex = 0;
|
|
// refresh
|
|
dwv.gui.refreshElement(shapeSelector);
|
|
|
|
// colour select: reset selected option
|
|
var colourSelector = app.getElement("colourSelect");
|
|
if ( !dwv.browser.hasInputColor() ) {
|
|
colourSelector.selectedIndex = 0;
|
|
}
|
|
// refresh
|
|
dwv.gui.refreshElement(colourSelector);
|
|
};
|
|
|
|
}; // class dwv.gui.base.Draw
|
|
|
|
/**
|
|
* Base gui for a tool with a colour setting.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.ColourTool = function (app, prefix)
|
|
{
|
|
// default colours
|
|
var colours = [
|
|
"Yellow", "Red", "White", "Green", "Blue", "Lime", "Fuchsia", "Black"
|
|
];
|
|
// colour selector class
|
|
var colourSelectClassName = prefix + "ColourSelect";
|
|
// colour selector class
|
|
var colourLiClassName = prefix + "ColourLi";
|
|
|
|
/**
|
|
* Get the default colour.
|
|
*/
|
|
this.getDefaultColour = function () {
|
|
if ( dwv.browser.hasInputColor() ) {
|
|
return "#FFFF80";
|
|
}
|
|
else {
|
|
return colours[0];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Setup the tool HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
// colour select
|
|
var colourSelector = null;
|
|
if ( dwv.browser.hasInputColor() ) {
|
|
colourSelector = document.createElement("input");
|
|
colourSelector.className = colourSelectClassName;
|
|
colourSelector.type = "color";
|
|
colourSelector.value = "#FFFF80";
|
|
}
|
|
else {
|
|
colourSelector = dwv.html.createHtmlSelect(colourSelectClassName, colours, "colour");
|
|
}
|
|
colourSelector.onchange = app.onChangeLineColour;
|
|
|
|
// colour list element
|
|
var colourLi = document.createElement("li");
|
|
colourLi.className = colourLiClassName + " ui-block-b";
|
|
colourLi.style.display = "none";
|
|
//colourLi.setAttribute("class","ui-block-b");
|
|
colourLi.appendChild(colourSelector);
|
|
|
|
// node
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
// apend colour
|
|
node.appendChild(colourLi);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the tool HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
// colour list
|
|
var node = app.getElement(colourLiClassName);
|
|
dwv.html.displayElement(node, bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
var colourSelector = app.getElement(colourSelectClassName);
|
|
if ( !dwv.browser.hasInputColor() ) {
|
|
colourSelector.selectedIndex = 0;
|
|
}
|
|
dwv.gui.refreshElement(colourSelector);
|
|
};
|
|
|
|
}; // class dwv.gui.base.ColourTool
|
|
|
|
/**
|
|
* ZoomAndPan tool base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.ZoomAndPan = function (app)
|
|
{
|
|
/**
|
|
* Setup the tool HTML.
|
|
*/
|
|
this.setup = function()
|
|
{
|
|
// reset button
|
|
var button = document.createElement("button");
|
|
button.className = "zoomResetButton";
|
|
button.name = "zoomResetButton";
|
|
button.onclick = app.onZoomReset;
|
|
button.setAttribute("style","width:100%; margin-top:0.5em;");
|
|
button.setAttribute("class","ui-btn ui-btn-b");
|
|
var text = document.createTextNode(dwv.i18n("basics.reset"));
|
|
button.appendChild(text);
|
|
|
|
// list element
|
|
var liElement = document.createElement("li");
|
|
liElement.className = "zoomLi ui-block-c";
|
|
liElement.style.display = "none";
|
|
//liElement.setAttribute("class","ui-block-c");
|
|
liElement.appendChild(button);
|
|
|
|
// node
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
// append element
|
|
node.appendChild(liElement);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the tool HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function(bool)
|
|
{
|
|
// display list element
|
|
var node = app.getElement("zoomLi");
|
|
dwv.html.displayElement(node, bool);
|
|
};
|
|
|
|
}; // class dwv.gui.base.ZoomAndPan
|
|
|
|
/**
|
|
* Scroll tool base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Scroll = function (app)
|
|
{
|
|
/**
|
|
* Setup the tool HTML.
|
|
*/
|
|
this.setup = function()
|
|
{
|
|
// list element
|
|
var liElement = document.createElement("li");
|
|
liElement.className = "scrollLi ui-block-c";
|
|
liElement.style.display = "none";
|
|
|
|
// node
|
|
var node = app.getElement("toolList").getElementsByTagName("ul")[0];
|
|
// append element
|
|
node.appendChild(liElement);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Display the tool HTML.
|
|
* @param {Boolean} bool True to display, false to hide.
|
|
*/
|
|
this.display = function(bool)
|
|
{
|
|
// display list element
|
|
var node = app.getElement("scrollLi");
|
|
dwv.html.displayElement(node, bool);
|
|
};
|
|
|
|
}; // class dwv.gui.base.Scroll
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.gui = dwv.gui || {};
|
|
dwv.gui.base = dwv.gui.base || {};
|
|
|
|
/**
|
|
* Undo base gui.
|
|
* @constructor
|
|
*/
|
|
dwv.gui.base.Undo = function (app)
|
|
{
|
|
/**
|
|
* Setup the undo HTML.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
var paragraph = document.createElement("p");
|
|
paragraph.appendChild(document.createTextNode("History:"));
|
|
paragraph.appendChild(document.createElement("br"));
|
|
|
|
var select = document.createElement("select");
|
|
select.className = "history_list";
|
|
select.name = "history_list";
|
|
select.multiple = "multiple";
|
|
paragraph.appendChild(select);
|
|
|
|
// node
|
|
var node = app.getElement("history");
|
|
// clear it
|
|
while(node.hasChildNodes()) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
// append
|
|
node.appendChild(paragraph);
|
|
// refresh
|
|
dwv.gui.refreshElement(node);
|
|
};
|
|
|
|
/**
|
|
* Clear the command list of the undo HTML.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
var select = app.getElement("history_list");
|
|
if ( select && select.length !== 0 ) {
|
|
for( var i = select.length - 1; i >= 0; --i)
|
|
{
|
|
select.remove(i);
|
|
}
|
|
}
|
|
// refresh
|
|
dwv.gui.refreshElement(select);
|
|
};
|
|
|
|
/**
|
|
* Add a command to the undo HTML.
|
|
* @param {String} commandName The name of the command to add.
|
|
*/
|
|
this.addCommandToUndoHtml = function (commandName)
|
|
{
|
|
var select = app.getElement("history_list");
|
|
// remove undone commands
|
|
var count = select.length - (select.selectedIndex+1);
|
|
if( count > 0 )
|
|
{
|
|
for( var i = 0; i < count; ++i)
|
|
{
|
|
select.remove(select.length-1);
|
|
}
|
|
}
|
|
// add new option
|
|
var option = document.createElement("option");
|
|
option.text = commandName;
|
|
option.value = commandName;
|
|
select.add(option);
|
|
// increment selected index
|
|
select.selectedIndex++;
|
|
// refresh
|
|
dwv.gui.refreshElement(select);
|
|
};
|
|
|
|
/**
|
|
* Enable the last command of the undo HTML.
|
|
* @param {Boolean} enable Flag to enable or disable the command.
|
|
*/
|
|
this.enableInUndoHtml = function (enable)
|
|
{
|
|
var select = app.getElement("history_list");
|
|
// enable or not (order is important)
|
|
var option;
|
|
if( enable )
|
|
{
|
|
// increment selected index
|
|
select.selectedIndex++;
|
|
// enable option
|
|
option = select.options[select.selectedIndex];
|
|
option.disabled = false;
|
|
}
|
|
else
|
|
{
|
|
// disable option
|
|
option = select.options[select.selectedIndex];
|
|
option.disabled = true;
|
|
// decrement selected index
|
|
select.selectedIndex--;
|
|
}
|
|
// refresh
|
|
dwv.gui.refreshElement(select);
|
|
};
|
|
|
|
}; // class dwv.gui.base.Undo
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
|
|
// JPEG Baseline
|
|
var hasJpegBaselineDecoder = (typeof JpegImage !== "undefined");
|
|
var JpegImage = JpegImage || {};
|
|
// JPEG Lossless
|
|
var hasJpegLosslessDecoder = (typeof jpeg !== "undefined") &&
|
|
(typeof jpeg.lossless !== "undefined");
|
|
var jpeg = jpeg || {};
|
|
jpeg.lossless = jpeg.lossless || {};
|
|
// JPEG 2000
|
|
var hasJpeg2000Decoder = (typeof JpxImage !== "undefined");
|
|
var JpxImage = JpxImage || {};
|
|
|
|
/**
|
|
* Asynchronous pixel buffer decoder.
|
|
* @param {String} script The path to the decoder script to be used by the web worker.
|
|
*/
|
|
dwv.image.AsynchPixelBufferDecoder = function (script)
|
|
{
|
|
// initialise the thread pool
|
|
var pool = new dwv.utils.ThreadPool(15);
|
|
pool.init();
|
|
|
|
/**
|
|
* Decode a pixel buffer.
|
|
* @param {Array} pixelBuffer The pixel buffer.
|
|
* @param {Number} bitsAllocated The bits allocated per element in the buffer.
|
|
* @param {Boolean} isSigned Is the data signed.
|
|
* @param {Function} callback Callback function to handle decoded data.
|
|
*/
|
|
this.decode = function (pixelBuffer, bitsAllocated, isSigned, callback) {
|
|
// (re)set event handler
|
|
pool.onpoolworkend = this.ondecodeend;
|
|
pool.onworkerend = this.ondecoded;
|
|
// create worker task
|
|
var workerTask = new dwv.utils.WorkerTask(script, callback, {
|
|
'buffer': pixelBuffer,
|
|
'bitsAllocated': bitsAllocated,
|
|
'isSigned': isSigned } );
|
|
// add it the queue and run it
|
|
pool.addWorkerTask(workerTask);
|
|
};
|
|
|
|
/**
|
|
* Abort decoding.
|
|
*/
|
|
this.abort = function () {
|
|
// abort the thread pool
|
|
pool.abort();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handle a decode end event.
|
|
*/
|
|
dwv.image.AsynchPixelBufferDecoder.prototype.ondecodeend = function ()
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Handle a decode event.
|
|
*/
|
|
dwv.image.AsynchPixelBufferDecoder.prototype.ondecoded = function ()
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Synchronous pixel buffer decoder.
|
|
* @param {String} algoName The decompression algorithm name.
|
|
*/
|
|
dwv.image.SynchPixelBufferDecoder = function (algoName)
|
|
{
|
|
/**
|
|
* Decode a pixel buffer.
|
|
* @param {Array} pixelBuffer The pixel buffer.
|
|
* @param {Number} bitsAllocated The bits allocated per element in the buffer.
|
|
* @param {Boolean} isSigned Is the data signed.
|
|
* @param {Function} callback Callback function to handle decoded data.
|
|
* @external jpeg
|
|
* @external JpegImage
|
|
* @external JpxImage
|
|
*/
|
|
this.decode = function (pixelBuffer, bitsAllocated, isSigned, callback) {
|
|
var decoder = null;
|
|
var decodedBuffer = null;
|
|
if( algoName === "jpeg-lossless" ) {
|
|
if ( !hasJpegLosslessDecoder ) {
|
|
throw new Error("No JPEG Lossless decoder provided");
|
|
}
|
|
// bytes per element
|
|
var bpe = bitsAllocated / 8;
|
|
var buf = new Uint8Array( pixelBuffer );
|
|
decoder = new jpeg.lossless.Decoder();
|
|
var decoded = decoder.decode(buf.buffer, 0, buf.buffer.byteLength, bpe);
|
|
if (bitsAllocated === 8) {
|
|
if (isSigned) {
|
|
decodedBuffer = new Int8Array(decoded.buffer);
|
|
}
|
|
else {
|
|
decodedBuffer = new Uint8Array(decoded.buffer);
|
|
}
|
|
}
|
|
else if (bitsAllocated === 16) {
|
|
if (isSigned) {
|
|
decodedBuffer = new Int16Array(decoded.buffer);
|
|
}
|
|
else {
|
|
decodedBuffer = new Uint16Array(decoded.buffer);
|
|
}
|
|
}
|
|
}
|
|
else if ( algoName === "jpeg-baseline" ) {
|
|
if ( !hasJpegBaselineDecoder ) {
|
|
throw new Error("No JPEG Baseline decoder provided");
|
|
}
|
|
decoder = new JpegImage();
|
|
decoder.parse( pixelBuffer );
|
|
decodedBuffer = decoder.getData(decoder.width,decoder.height);
|
|
}
|
|
else if( algoName === "jpeg2000" ) {
|
|
if ( !hasJpeg2000Decoder ) {
|
|
throw new Error("No JPEG 2000 decoder provided");
|
|
}
|
|
// decompress pixel buffer into Int16 image
|
|
decoder = new JpxImage();
|
|
decoder.parse( pixelBuffer );
|
|
// set the pixel buffer
|
|
decodedBuffer = decoder.tiles[0].items;
|
|
}
|
|
// send events
|
|
this.ondecoded();
|
|
this.ondecodeend();
|
|
// call callback with decoded buffer as array
|
|
callback({data: [decodedBuffer]});
|
|
};
|
|
|
|
/**
|
|
* Abort decoding.
|
|
*/
|
|
this.abort = function () {
|
|
// nothing to do in the synchronous case.
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handle a decode end event.
|
|
*/
|
|
dwv.image.SynchPixelBufferDecoder.prototype.ondecodeend = function ()
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Handle a decode event.
|
|
*/
|
|
dwv.image.SynchPixelBufferDecoder.prototype.ondecoded = function ()
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Decode a pixel buffer.
|
|
* @constructor
|
|
* @param {String} algoName The decompression algorithm name.
|
|
* If the 'dwv.image.decoderScripts' variable does not contain the desired algorythm,
|
|
* the decoder will switch to the synchronous mode.
|
|
*/
|
|
dwv.image.PixelBufferDecoder = function (algoName)
|
|
{
|
|
/**
|
|
* Pixel decoder.
|
|
* Defined only once.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var pixelDecoder = null;
|
|
|
|
// initialise the asynch decoder (if possible)
|
|
if (typeof dwv.image.decoderScripts !== "undefined" &&
|
|
typeof dwv.image.decoderScripts[algoName] !== "undefined") {
|
|
pixelDecoder = new dwv.image.AsynchPixelBufferDecoder(dwv.image.decoderScripts[algoName]);
|
|
} else {
|
|
pixelDecoder = new dwv.image.SynchPixelBufferDecoder(algoName);
|
|
}
|
|
|
|
/**
|
|
* Get data from an input buffer using a DICOM parser.
|
|
* @param {Array} pixelBuffer The input data buffer.
|
|
* @param {Number} bitsAllocated The bits allocated per element in the buffer.
|
|
* @param {Boolean} isSigned Is the data signed.
|
|
* @param {Object} callback The callback on the conversion.
|
|
*/
|
|
this.decode = function (pixelBuffer, bitsAllocated, isSigned, callback)
|
|
{
|
|
// set event handler
|
|
pixelDecoder.ondecodeend = this.ondecodeend;
|
|
pixelDecoder.ondecoded = this.ondecoded;
|
|
// decode and call the callback
|
|
pixelDecoder.decode(pixelBuffer, bitsAllocated, isSigned, callback);
|
|
};
|
|
|
|
/**
|
|
* Abort decoding.
|
|
*/
|
|
this.abort = function ()
|
|
{
|
|
// decoder classes should define an abort
|
|
pixelDecoder.abort();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handle a decode end event.
|
|
*/
|
|
dwv.image.PixelBufferDecoder.prototype.ondecodeend = function ()
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Handle a decode end event.
|
|
*/
|
|
dwv.image.PixelBufferDecoder.prototype.ondecoded = function ()
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
|
|
/**
|
|
* Create a dwv.image.View from a DICOM buffer.
|
|
* @constructor
|
|
*/
|
|
dwv.image.DicomBufferToView = function ()
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* The default character set (optional).
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var defaultCharacterSet;
|
|
|
|
/**
|
|
* Set the default character set.
|
|
* param {String} The character set.
|
|
*/
|
|
this.setDefaultCharacterSet = function (characterSet) {
|
|
defaultCharacterSet = characterSet;
|
|
};
|
|
|
|
/**
|
|
* Pixel buffer decoder.
|
|
* Define only once to allow optional asynchronous mode.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var pixelDecoder = null;
|
|
|
|
/**
|
|
* Get data from an input buffer using a DICOM parser.
|
|
* @param {Array} buffer The input data buffer.
|
|
* @param {Number} dataIndex The data index.
|
|
*/
|
|
this.convert = function (buffer, dataIndex)
|
|
{
|
|
// DICOM parser
|
|
var dicomParser = new dwv.dicom.DicomParser();
|
|
dicomParser.setDefaultCharacterSet(defaultCharacterSet);
|
|
// parse the buffer
|
|
dicomParser.parse(buffer);
|
|
|
|
var pixelBuffer = dicomParser.getRawDicomElements().x7FE00010.value;
|
|
var syntax = dwv.dicom.cleanString(dicomParser.getRawDicomElements().x00020010.value[0]);
|
|
var algoName = dwv.dicom.getSyntaxDecompressionName(syntax);
|
|
var needDecompression = (algoName !== null);
|
|
|
|
// worker callback
|
|
var onDecodedFirstFrame = function (/*event*/) {
|
|
// create the image
|
|
var imageFactory = new dwv.image.ImageFactory();
|
|
var image = imageFactory.create( dicomParser.getDicomElements(), pixelBuffer );
|
|
// create the view
|
|
var viewFactory = new dwv.image.ViewFactory();
|
|
var view = viewFactory.create( dicomParser.getDicomElements(), image );
|
|
// return
|
|
self.onload({"view": view, "info": dicomParser.getDicomElements().dumpToTable()});
|
|
};
|
|
|
|
if ( needDecompression ) {
|
|
var bitsAllocated = dicomParser.getRawDicomElements().x00280100.value[0];
|
|
var pixelRepresentation = dicomParser.getRawDicomElements().x00280103.value[0];
|
|
var isSigned = (pixelRepresentation === 1);
|
|
var nFrames = pixelBuffer.length;
|
|
|
|
if (!pixelDecoder){
|
|
pixelDecoder = new dwv.image.PixelBufferDecoder(algoName);
|
|
}
|
|
|
|
// loadend event
|
|
pixelDecoder.ondecodeend = function () {
|
|
self.onloadend();
|
|
};
|
|
|
|
// send an onload event for mono frame
|
|
if ( nFrames === 1 ) {
|
|
pixelDecoder.ondecoded = function () {
|
|
self.onloadend();
|
|
};
|
|
}
|
|
|
|
// decoder callback
|
|
var countDecodedFrames = 0;
|
|
var onDecodedFrame = function (frame) {
|
|
return function (event) {
|
|
// send progress
|
|
++countDecodedFrames;
|
|
var ev = {'type': "load-progress", 'lengthComputable': true,
|
|
'loaded': (countDecodedFrames * 100 / nFrames), 'total': 100};
|
|
if ( typeof dataIndex !== "undefined") {
|
|
ev.index = dataIndex;
|
|
}
|
|
self.onprogress(ev);
|
|
// store data
|
|
pixelBuffer[frame] = event.data[0];
|
|
// create image for first frame
|
|
if ( frame === 0 ) {
|
|
onDecodedFirstFrame();
|
|
}
|
|
};
|
|
};
|
|
|
|
// decompress synchronously the first frame to create the image
|
|
pixelDecoder.decode(pixelBuffer[0],
|
|
bitsAllocated, isSigned, onDecodedFrame(0), false);
|
|
|
|
// decompress the possible other frames
|
|
if ( nFrames !== 1 ) {
|
|
// decode (asynchronously if possible)
|
|
for (var f = 1; f < nFrames; ++f) {
|
|
pixelDecoder.decode(pixelBuffer[f],
|
|
bitsAllocated, isSigned, onDecodedFrame(f));
|
|
}
|
|
}
|
|
}
|
|
// no decompression
|
|
else {
|
|
// send progress
|
|
var evnodec = {'type': 'load-progress', 'lengthComputable': true,
|
|
'loaded': 100, 'total': 100};
|
|
if ( typeof dataIndex !== "undefined") {
|
|
evnodec.index = dataIndex;
|
|
}
|
|
self.onprogress(evnodec);
|
|
// create image
|
|
onDecodedFirstFrame();
|
|
// send load events
|
|
self.onloadend();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Abort a conversion.
|
|
*/
|
|
this.abort = function () {
|
|
if ( pixelDecoder ) {
|
|
pixelDecoder.abort();
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handle a load end event.
|
|
* @param {Object} event The load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.image.DicomBufferToView.prototype.onloadend = function (/*event*/) {};
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.image.DicomBufferToView.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle a load progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.image.DicomBufferToView.prototype.onprogress = function (/*event*/) {};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
|
|
/**
|
|
* Create a simple array buffer from an ImageData buffer.
|
|
* @param {Object} imageData The ImageData taken from a context.
|
|
* @return {Array} The image buffer.
|
|
*/
|
|
dwv.image.imageDataToBuffer = function (imageData) {
|
|
// remove alpha
|
|
// TODO support passing the full image data
|
|
var dataLen = imageData.data.length;
|
|
var buffer = new Uint8Array( (dataLen / 4) * 3);
|
|
var j = 0;
|
|
for( var i = 0; i < dataLen; i+=4 ) {
|
|
buffer[j] = imageData.data[i];
|
|
buffer[j+1] = imageData.data[i+1];
|
|
buffer[j+2] = imageData.data[i+2];
|
|
j+=3;
|
|
}
|
|
return buffer;
|
|
};
|
|
|
|
/**
|
|
* Get data from an input context imageData.
|
|
* @param {Number} width The width of the coresponding image.
|
|
* @param {Number} height The height of the coresponding image.
|
|
* @param {Number} sliceIndex The slice index of the imageData.
|
|
* @param {Object} imageBuffer The image buffer.
|
|
* @param {Number} numberOfFrames The final number of frames.
|
|
* @return {Object} The corresponding view.
|
|
*/
|
|
dwv.image.getDefaultView = function (
|
|
width, height, sliceIndex,
|
|
imageBuffer, numberOfFrames, info) {
|
|
// image size
|
|
var imageSize = new dwv.image.Size(width, height);
|
|
// default spacing
|
|
// TODO: misleading...
|
|
var imageSpacing = new dwv.image.Spacing(1,1);
|
|
// default origin
|
|
var origin = new dwv.math.Point3D(0,0,sliceIndex);
|
|
// create image
|
|
var geometry = new dwv.image.Geometry(origin, imageSize, imageSpacing );
|
|
var image = new dwv.image.Image( geometry, imageBuffer, numberOfFrames );
|
|
image.setPhotometricInterpretation("RGB");
|
|
// meta information
|
|
var meta = {};
|
|
meta.BitsStored = 8;
|
|
image.setMeta(meta);
|
|
// overlay
|
|
image.setFirstOverlay( dwv.gui.info.createOverlaysForDom(info) );
|
|
// view
|
|
var view = new dwv.image.View(image);
|
|
// defaut preset
|
|
view.setWindowLevelMinMax();
|
|
// return
|
|
return view;
|
|
};
|
|
|
|
/**
|
|
* Get data from an input image using a canvas.
|
|
* @param {Object} image The DOM Image.
|
|
* @return {Mixed} The corresponding view and info.
|
|
*/
|
|
dwv.image.getViewFromDOMImage = function (image)
|
|
{
|
|
// image size
|
|
var width = image.width;
|
|
var height = image.height;
|
|
|
|
// draw the image in the canvas in order to get its data
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.drawImage(image, 0, 0);
|
|
// get the image data
|
|
var imageData = ctx.getImageData(0, 0, width, height);
|
|
|
|
// image properties
|
|
var info = [];
|
|
if ( typeof image.origin === "string" ) {
|
|
info.push({ "name": "origin", "value": image.origin });
|
|
} else {
|
|
info.push({ "name": "fileName", "value": image.origin.name });
|
|
info.push({ "name": "fileType", "value": image.origin.type });
|
|
info.push({ "name": "fileLastModifiedDate", "value": image.origin.lastModifiedDate });
|
|
}
|
|
info.push({ "name": "imageWidth", "value": width });
|
|
info.push({ "name": "imageHeight", "value": height });
|
|
|
|
// create view
|
|
var sliceIndex = image.index ? image.index : 0;
|
|
var imageBuffer = dwv.image.imageDataToBuffer(imageData);
|
|
var view = dwv.image.getDefaultView(
|
|
width, height, sliceIndex, [imageBuffer], 1, info);
|
|
|
|
// return
|
|
return {"view": view, "info": info};
|
|
};
|
|
|
|
/**
|
|
* Get data from an input image using a canvas.
|
|
* @param {Object} video The DOM Video.
|
|
* @param {Object} callback The function to call once the data is loaded.
|
|
* @param {Object} cbprogress The function to call to report progress.
|
|
* @param {Object} cbonloadend The function to call to report load end.
|
|
* @param {Number} dataindex The data index.
|
|
*/
|
|
dwv.image.getViewFromDOMVideo = function (video, callback, cbprogress, cbonloadend, dataIndex)
|
|
{
|
|
// video size
|
|
var width = video.videoWidth;
|
|
var height = video.videoHeight;
|
|
|
|
// default frame rate...
|
|
var frameRate = 30;
|
|
// number of frames
|
|
var numberOfFrames = Math.floor(video.duration * frameRate);
|
|
|
|
// video properties
|
|
var info = [];
|
|
if( video.file )
|
|
{
|
|
info.push({ "name": "fileName", "value": video.file.name });
|
|
info.push({ "name": "fileType", "value": video.file.type });
|
|
info.push({ "name": "fileLastModifiedDate", "value": video.file.lastModifiedDate });
|
|
}
|
|
info.push({ "name": "imageWidth", "value": width });
|
|
info.push({ "name": "imageHeight", "value": height });
|
|
info.push({ "name": "numberOfFrames", "value": numberOfFrames });
|
|
|
|
// draw the image in the canvas in order to get its data
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
// using seeked to loop through all video frames
|
|
video.addEventListener('seeked', onseeked, false);
|
|
|
|
// current frame index
|
|
var frameIndex = 0;
|
|
// video view
|
|
var view = null;
|
|
|
|
// draw the context and store it as a frame
|
|
function storeFrame() {
|
|
// send progress
|
|
var evprog = {'type': 'load-progress', 'lengthComputable': true,
|
|
'loaded': frameIndex, 'total': numberOfFrames};
|
|
if (typeof dataIndex !== "undefined") {
|
|
evprog.index = dataIndex;
|
|
}
|
|
cbprogress(evprog);
|
|
// draw image
|
|
ctx.drawImage(video, 0, 0);
|
|
// context to image buffer
|
|
var imgBuffer = dwv.image.imageDataToBuffer(
|
|
ctx.getImageData(0, 0, width, height) );
|
|
if (frameIndex === 0) {
|
|
// create view
|
|
view = dwv.image.getDefaultView(
|
|
width, height, 1, [imgBuffer], numberOfFrames, info);
|
|
// call callback
|
|
callback( {"view": view, "info": info } );
|
|
} else {
|
|
view.appendFrameBuffer(imgBuffer);
|
|
}
|
|
}
|
|
|
|
// handle seeked event
|
|
function onseeked(/*event*/) {
|
|
// store
|
|
storeFrame();
|
|
// increment index
|
|
++frameIndex;
|
|
// set the next time
|
|
// (not using currentTime, it seems to get offseted)
|
|
var nextTime = frameIndex / frameRate;
|
|
if (nextTime <= this.duration) {
|
|
this.currentTime = nextTime;
|
|
} else {
|
|
cbonloadend();
|
|
// stop listening
|
|
video.removeEventListener('seeked', onseeked);
|
|
}
|
|
}
|
|
|
|
// trigger the first seeked
|
|
video.currentTime = 0;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
/** @namespace */
|
|
dwv.image.filter = dwv.image.filter || {};
|
|
|
|
/**
|
|
* Threshold an image between an input minimum and maximum.
|
|
* @constructor
|
|
*/
|
|
dwv.image.filter.Threshold = function()
|
|
{
|
|
/**
|
|
* Threshold minimum.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var min = 0;
|
|
/**
|
|
* Threshold maximum.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var max = 0;
|
|
|
|
/**
|
|
* Get the threshold minimum.
|
|
* @return {Number} The threshold minimum.
|
|
*/
|
|
this.getMin = function() { return min; };
|
|
/**
|
|
* Set the threshold minimum.
|
|
* @param {Number} val The threshold minimum.
|
|
*/
|
|
this.setMin = function(val) { min = val; };
|
|
/**
|
|
* Get the threshold maximum.
|
|
* @return {Number} The threshold maximum.
|
|
*/
|
|
this.getMax = function() { return max; };
|
|
/**
|
|
* Set the threshold maximum.
|
|
* @param {Number} val The threshold maximum.
|
|
*/
|
|
this.setMax = function(val) { max = val; };
|
|
/**
|
|
* Get the name of the filter.
|
|
* @return {String} The name of the filter.
|
|
*/
|
|
this.getName = function() { return "Threshold"; };
|
|
|
|
/**
|
|
* Original image.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var originalImage = null;
|
|
/**
|
|
* Set the original image.
|
|
* @param {Object} image The original image.
|
|
*/
|
|
this.setOriginalImage = function (image) { originalImage = image; };
|
|
/**
|
|
* Get the original image.
|
|
* @return {Object} image The original image.
|
|
*/
|
|
this.getOriginalImage = function () { return originalImage; };
|
|
};
|
|
|
|
/**
|
|
* Transform the main image using this filter.
|
|
* @return {Object} The transformed image.
|
|
*/
|
|
dwv.image.filter.Threshold.prototype.update = function ()
|
|
{
|
|
var image = this.getOriginalImage();
|
|
var imageMin = image.getDataRange().min;
|
|
var self = this;
|
|
var threshFunction = function (value) {
|
|
if ( value < self.getMin() || value > self.getMax() ) {
|
|
return imageMin;
|
|
}
|
|
else {
|
|
return value;
|
|
}
|
|
};
|
|
return image.transform( threshFunction );
|
|
};
|
|
|
|
/**
|
|
* Sharpen an image using a sharpen convolution matrix.
|
|
* @constructor
|
|
*/
|
|
dwv.image.filter.Sharpen = function()
|
|
{
|
|
/**
|
|
* Get the name of the filter.
|
|
* @return {String} The name of the filter.
|
|
*/
|
|
this.getName = function() { return "Sharpen"; };
|
|
/**
|
|
* Original image.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var originalImage = null;
|
|
/**
|
|
* Set the original image.
|
|
* @param {Object} image The original image.
|
|
*/
|
|
this.setOriginalImage = function (image) { originalImage = image; };
|
|
/**
|
|
* Get the original image.
|
|
* @return {Object} image The original image.
|
|
*/
|
|
this.getOriginalImage = function () { return originalImage; };
|
|
};
|
|
|
|
/**
|
|
* Transform the main image using this filter.
|
|
* @return {Object} The transformed image.
|
|
*/
|
|
dwv.image.filter.Sharpen.prototype.update = function()
|
|
{
|
|
var image = this.getOriginalImage();
|
|
|
|
return image.convolute2D(
|
|
[ 0, -1, 0,
|
|
-1, 5, -1,
|
|
0, -1, 0 ] );
|
|
};
|
|
|
|
/**
|
|
* Apply a Sobel filter to an image.
|
|
* @constructor
|
|
*/
|
|
dwv.image.filter.Sobel = function()
|
|
{
|
|
/**
|
|
* Get the name of the filter.
|
|
* @return {String} The name of the filter.
|
|
*/
|
|
this.getName = function() { return "Sobel"; };
|
|
/**
|
|
* Original image.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var originalImage = null;
|
|
/**
|
|
* Set the original image.
|
|
* @param {Object} image The original image.
|
|
*/
|
|
this.setOriginalImage = function (image) { originalImage = image; };
|
|
/**
|
|
* Get the original image.
|
|
* @return {Object} image The original image.
|
|
*/
|
|
this.getOriginalImage = function () { return originalImage; };
|
|
};
|
|
|
|
/**
|
|
* Transform the main image using this filter.
|
|
* @return {Object} The transformed image.
|
|
*/
|
|
dwv.image.filter.Sobel.prototype.update = function()
|
|
{
|
|
var image = this.getOriginalImage();
|
|
|
|
var gradX = image.convolute2D(
|
|
[ 1, 0, -1,
|
|
2, 0, -2,
|
|
1, 0, -1 ] );
|
|
|
|
var gradY = image.convolute2D(
|
|
[ 1, 2, 1,
|
|
0, 0, 0,
|
|
-1, -2, -1 ] );
|
|
|
|
return gradX.compose( gradY, function (x,y) { return Math.sqrt(x*x+y*y); } );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
|
|
/**
|
|
* 2D/3D Size class.
|
|
* @constructor
|
|
* @param {Number} numberOfColumns The number of columns.
|
|
* @param {Number} numberOfRows The number of rows.
|
|
* @param {Number} numberOfSlices The number of slices.
|
|
*/
|
|
dwv.image.Size = function ( numberOfColumns, numberOfRows, numberOfSlices )
|
|
{
|
|
/**
|
|
* Get the number of columns.
|
|
* @return {Number} The number of columns.
|
|
*/
|
|
this.getNumberOfColumns = function () { return numberOfColumns; };
|
|
/**
|
|
* Get the number of rows.
|
|
* @return {Number} The number of rows.
|
|
*/
|
|
this.getNumberOfRows = function () { return numberOfRows; };
|
|
/**
|
|
* Get the number of slices.
|
|
* @return {Number} The number of slices.
|
|
*/
|
|
this.getNumberOfSlices = function () { return (numberOfSlices || 1.0); };
|
|
};
|
|
|
|
/**
|
|
* Get the size of a slice.
|
|
* @return {Number} The size of a slice.
|
|
*/
|
|
dwv.image.Size.prototype.getSliceSize = function () {
|
|
return this.getNumberOfColumns() * this.getNumberOfRows();
|
|
};
|
|
|
|
/**
|
|
* Get the total size.
|
|
* @return {Number} The total size.
|
|
*/
|
|
dwv.image.Size.prototype.getTotalSize = function () {
|
|
return this.getSliceSize() * this.getNumberOfSlices();
|
|
};
|
|
|
|
/**
|
|
* Check for equality.
|
|
* @param {Size} rhs The object to compare to.
|
|
* @return {Boolean} True if both objects are equal.
|
|
*/
|
|
dwv.image.Size.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getNumberOfColumns() === rhs.getNumberOfColumns() &&
|
|
this.getNumberOfRows() === rhs.getNumberOfRows() &&
|
|
this.getNumberOfSlices() === rhs.getNumberOfSlices();
|
|
};
|
|
|
|
/**
|
|
* Check that coordinates are within bounds.
|
|
* @param {Number} i The column coordinate.
|
|
* @param {Number} j The row coordinate.
|
|
* @param {Number} k The slice coordinate.
|
|
* @return {Boolean} True if the given coordinates are within bounds.
|
|
*/
|
|
dwv.image.Size.prototype.isInBounds = function ( i, j, k ) {
|
|
if( i < 0 || i > this.getNumberOfColumns() - 1 ||
|
|
j < 0 || j > this.getNumberOfRows() - 1 ||
|
|
k < 0 || k > this.getNumberOfSlices() - 1 ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Vector3D.
|
|
* @return {String} The vector as a string.
|
|
*/
|
|
dwv.image.Size.prototype.toString = function () {
|
|
return "(" + this.getNumberOfColumns() +
|
|
", " + this.getNumberOfRows() +
|
|
", " + this.getNumberOfSlices() + ")";
|
|
};
|
|
|
|
/**
|
|
* 2D/3D Spacing class.
|
|
* @constructor
|
|
* @param {Number} columnSpacing The column spacing.
|
|
* @param {Number} rowSpacing The row spacing.
|
|
* @param {Number} sliceSpacing The slice spacing.
|
|
*/
|
|
dwv.image.Spacing = function ( columnSpacing, rowSpacing, sliceSpacing )
|
|
{
|
|
/**
|
|
* Get the column spacing.
|
|
* @return {Number} The column spacing.
|
|
*/
|
|
this.getColumnSpacing = function () { return columnSpacing; };
|
|
/**
|
|
* Get the row spacing.
|
|
* @return {Number} The row spacing.
|
|
*/
|
|
this.getRowSpacing = function () { return rowSpacing; };
|
|
/**
|
|
* Get the slice spacing.
|
|
* @return {Number} The slice spacing.
|
|
*/
|
|
this.getSliceSpacing = function () { return (sliceSpacing || 1.0); };
|
|
};
|
|
|
|
/**
|
|
* Check for equality.
|
|
* @param {Spacing} rhs The object to compare to.
|
|
* @return {Boolean} True if both objects are equal.
|
|
*/
|
|
dwv.image.Spacing.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getColumnSpacing() === rhs.getColumnSpacing() &&
|
|
this.getRowSpacing() === rhs.getRowSpacing() &&
|
|
this.getSliceSpacing() === rhs.getSliceSpacing();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Vector3D.
|
|
* @return {String} The vector as a string.
|
|
*/
|
|
dwv.image.Spacing.prototype.toString = function () {
|
|
return "(" + this.getColumnSpacing() +
|
|
", " + this.getRowSpacing() +
|
|
", " + this.getSliceSpacing() + ")";
|
|
};
|
|
|
|
|
|
/**
|
|
* 2D/3D Geometry class.
|
|
* @constructor
|
|
* @param {Object} origin The object origin (a 3D point).
|
|
* @param {Object} size The object size.
|
|
* @param {Object} spacing The object spacing.
|
|
* @param {Object} orientation The object orientation (3*3 matrix, default to 3*3 identity).
|
|
*/
|
|
dwv.image.Geometry = function ( origin, size, spacing, orientation )
|
|
{
|
|
// check input origin
|
|
if( typeof origin === 'undefined' ) {
|
|
origin = new dwv.math.Point3D(0,0,0);
|
|
}
|
|
var origins = [origin];
|
|
// check input orientation
|
|
if( typeof orientation === 'undefined' ) {
|
|
orientation = new dwv.math.getIdentityMat33();
|
|
}
|
|
|
|
/**
|
|
* Get the object first origin.
|
|
* @return {Object} The object first origin.
|
|
*/
|
|
this.getOrigin = function () { return origin; };
|
|
/**
|
|
* Get the object origins.
|
|
* @return {Array} The object origins.
|
|
*/
|
|
this.getOrigins = function () { return origins; };
|
|
/**
|
|
* Get the object size.
|
|
* @return {Object} The object size.
|
|
*/
|
|
this.getSize = function () { return size; };
|
|
/**
|
|
* Get the object spacing.
|
|
* @return {Object} The object spacing.
|
|
*/
|
|
this.getSpacing = function () { return spacing; };
|
|
/**
|
|
* Get the object orientation.
|
|
* @return {Object} The object orientation.
|
|
*/
|
|
this.getOrientation = function () { return orientation; };
|
|
|
|
/**
|
|
* Get the slice position of a point in the current slice layout.
|
|
* @param {Object} point The point to evaluate.
|
|
*/
|
|
this.getSliceIndex = function (point)
|
|
{
|
|
// cannot use this.worldToIndex(point).getK() since
|
|
// we cannot guaranty consecutive slices...
|
|
|
|
// find the closest index
|
|
var closestSliceIndex = 0;
|
|
var minDist = point.getDistance(origins[0]);
|
|
var dist = 0;
|
|
for( var i = 0; i < origins.length; ++i )
|
|
{
|
|
dist = point.getDistance(origins[i]);
|
|
if( dist < minDist )
|
|
{
|
|
minDist = dist;
|
|
closestSliceIndex = i;
|
|
}
|
|
}
|
|
// we have the closest point, are we before or after
|
|
var normal = new dwv.math.Vector3D(
|
|
orientation.get(2,0), orientation.get(2,1), orientation.get(2,2));
|
|
var dotProd = normal.dotProduct( point.minus(origins[closestSliceIndex]) );
|
|
var sliceIndex = ( dotProd > 0 ) ? closestSliceIndex + 1 : closestSliceIndex;
|
|
return sliceIndex;
|
|
};
|
|
|
|
/**
|
|
* Append an origin to the geometry.
|
|
* @param {Object} origin The origin to append.
|
|
* @param {Number} index The index at which to append.
|
|
*/
|
|
this.appendOrigin = function (origin, index)
|
|
{
|
|
// add in origin array
|
|
origins.splice(index, 0, origin);
|
|
// increment slice number
|
|
size = new dwv.image.Size(
|
|
size.getNumberOfColumns(),
|
|
size.getNumberOfRows(),
|
|
size.getNumberOfSlices() + 1);
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* Check for equality.
|
|
* @param {Geometry} rhs The object to compare to.
|
|
* @return {Boolean} True if both objects are equal.
|
|
*/
|
|
dwv.image.Geometry.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getOrigin() === rhs.getOrigin() &&
|
|
this.getSize() === rhs.getSize() &&
|
|
this.getSpacing() === rhs.getSpacing();
|
|
};
|
|
|
|
/**
|
|
* Convert an index to an offset in memory.
|
|
* @param {Object} index The index to convert.
|
|
*/
|
|
dwv.image.Geometry.prototype.indexToOffset = function (index) {
|
|
var size = this.getSize();
|
|
return index.getI() +
|
|
index.getJ() * size.getNumberOfColumns() +
|
|
index.getK() * size.getSliceSize();
|
|
};
|
|
|
|
/**
|
|
* Convert an index into world coordinates.
|
|
* @param {Object} index The index to convert.
|
|
*/
|
|
dwv.image.Geometry.prototype.indexToWorld = function (index) {
|
|
var origin = this.getOrigin();
|
|
var spacing = this.getSpacing();
|
|
return new dwv.math.Point3D(
|
|
origin.getX() + index.getI() * spacing.getColumnSpacing(),
|
|
origin.getY() + index.getJ() * spacing.getRowSpacing(),
|
|
origin.getZ() + index.getK() * spacing.getSliceSpacing() );
|
|
};
|
|
|
|
/**
|
|
* Convert world coordinates into an index.
|
|
* @param {Object} THe point to convert.
|
|
*/
|
|
dwv.image.Geometry.prototype.worldToIndex = function (point) {
|
|
var origin = this.getOrigin();
|
|
var spacing = this.getSpacing();
|
|
return new dwv.math.Point3D(
|
|
point.getX() / spacing.getColumnSpacing() - origin.getX(),
|
|
point.getY() / spacing.getRowSpacing() - origin.getY(),
|
|
point.getZ() / spacing.getSliceSpacing() - origin.getZ() );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.image = dwv.image || {};
|
|
|
|
/**
|
|
* Rescale Slope and Intercept
|
|
* @constructor
|
|
* @param slope
|
|
* @param intercept
|
|
*/
|
|
dwv.image.RescaleSlopeAndIntercept = function (slope, intercept)
|
|
{
|
|
/*// Check the rescale slope.
|
|
if(typeof(slope) === 'undefined') {
|
|
slope = 1;
|
|
}
|
|
// Check the rescale intercept.
|
|
if(typeof(intercept) === 'undefined') {
|
|
intercept = 0;
|
|
}*/
|
|
|
|
/**
|
|
* Get the slope of the RSI.
|
|
* @return {Number} The slope of the RSI.
|
|
*/
|
|
this.getSlope = function ()
|
|
{
|
|
return slope;
|
|
};
|
|
/**
|
|
* Get the intercept of the RSI.
|
|
* @return {Number} The intercept of the RSI.
|
|
*/
|
|
this.getIntercept = function ()
|
|
{
|
|
return intercept;
|
|
};
|
|
/**
|
|
* Apply the RSI on an input value.
|
|
* @return {Number} The value to rescale.
|
|
*/
|
|
this.apply = function (value)
|
|
{
|
|
return value * slope + intercept;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Check for RSI equality.
|
|
* @param {Object} rhs The other RSI to compare to.
|
|
* @return {Boolean} True if both RSI are equal.
|
|
*/
|
|
dwv.image.RescaleSlopeAndIntercept.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getSlope() === rhs.getSlope() &&
|
|
this.getIntercept() === rhs.getIntercept();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the RSI.
|
|
* @return {String} The RSI as a string.
|
|
*/
|
|
dwv.image.RescaleSlopeAndIntercept.prototype.toString = function () {
|
|
return (this.getSlope() + ", " + this.getIntercept());
|
|
};
|
|
|
|
/**
|
|
* Is this RSI an ID RSI.
|
|
* @return {Boolean} True if the RSI has a slope of 1 and no intercept.
|
|
*/
|
|
dwv.image.RescaleSlopeAndIntercept.prototype.isID = function () {
|
|
return (this.getSlope() === 1 && this.getIntercept() === 0);
|
|
};
|
|
|
|
/**
|
|
* Image class.
|
|
* Usable once created, optional are:
|
|
* - rescale slope and intercept (default 1:0),
|
|
* - photometric interpretation (default MONOCHROME2),
|
|
* - planar configuration (default RGBRGB...).
|
|
* @constructor
|
|
* @param {Object} geometry The geometry of the image.
|
|
* @param {Array} buffer The image data as an array of frame buffers.
|
|
* @param {Number} numberOfFrames The number of frames (optional, can be used
|
|
to anticipate the final number after appends).
|
|
*/
|
|
dwv.image.Image = function(geometry, buffer, numberOfFrames)
|
|
{
|
|
// use buffer length in not specified
|
|
if (typeof numberOfFrames === "undefined") {
|
|
numberOfFrames = buffer.length;
|
|
}
|
|
|
|
/**
|
|
* Get the number of frames.
|
|
* @returns {Number} The number of frames.
|
|
*/
|
|
this.getNumberOfFrames = function () {
|
|
return numberOfFrames;
|
|
};
|
|
|
|
/**
|
|
* Rescale slope and intercept.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var rsis = [];
|
|
// initialise RSIs
|
|
for ( var s = 0, nslices = geometry.getSize().getNumberOfSlices(); s < nslices; ++s ) {
|
|
rsis.push( new dwv.image.RescaleSlopeAndIntercept( 1, 0 ) );
|
|
}
|
|
/**
|
|
* Flag to know if the RSIs are all identity (1,0).
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isIdentityRSI = true;
|
|
/**
|
|
* Flag to know if the RSIs are all equals.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isConstantRSI = true;
|
|
/**
|
|
* Photometric interpretation (MONOCHROME, RGB...).
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var photometricInterpretation = "MONOCHROME2";
|
|
/**
|
|
* Planar configuration for RGB data (0:RGBRGBRGBRGB... or 1:RRR...GGG...BBB...).
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var planarConfiguration = 0;
|
|
/**
|
|
* Number of components.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var numberOfComponents = buffer[0].length / (
|
|
geometry.getSize().getTotalSize() );
|
|
/**
|
|
* Meta information.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var meta = {};
|
|
|
|
/**
|
|
* Data range.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var dataRange = null;
|
|
/**
|
|
* Rescaled data range.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var rescaledDataRange = null;
|
|
/**
|
|
* Histogram.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var histogram = null;
|
|
|
|
/**
|
|
* Overlay.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var overlays = [];
|
|
|
|
/**
|
|
* Set the first overlay.
|
|
* @param {Array} over The first overlay.
|
|
*/
|
|
this.setFirstOverlay = function (over) { overlays[0] = over; };
|
|
|
|
/**
|
|
* Get the overlays.
|
|
* @return {Array} The overlays array.
|
|
*/
|
|
this.getOverlays = function () { return overlays; };
|
|
|
|
/**
|
|
* Get the geometry of the image.
|
|
* @return {Object} The size of the image.
|
|
*/
|
|
this.getGeometry = function() { return geometry; };
|
|
|
|
/**
|
|
* Get the data buffer of the image.
|
|
* @todo dangerous...
|
|
* @return {Array} The data buffer of the image.
|
|
*/
|
|
this.getBuffer = function() { return buffer; };
|
|
/**
|
|
* Get the data buffer of the image.
|
|
* @todo dangerous...
|
|
* @return {Array} The data buffer of the frame.
|
|
*/
|
|
this.getFrame = function (frame) { return buffer[frame]; };
|
|
|
|
/**
|
|
* Get the rescale slope and intercept.
|
|
* @param {Number} k The slice index.
|
|
* @return {Object} The rescale slope and intercept.
|
|
*/
|
|
this.getRescaleSlopeAndIntercept = function(k) { return rsis[k]; };
|
|
/**
|
|
* Set the rescale slope and intercept.
|
|
* @param {Array} inRsi The input rescale slope and intercept.
|
|
* @param {Number} k The slice index (optional).
|
|
*/
|
|
this.setRescaleSlopeAndIntercept = function(inRsi, k) {
|
|
if ( typeof k === 'undefined' ) {
|
|
k = 0;
|
|
}
|
|
rsis[k] = inRsi;
|
|
|
|
// update RSI flags
|
|
isIdentityRSI = true;
|
|
isConstantRSI = true;
|
|
for ( var s = 0, lens = rsis.length; s < lens; ++s ) {
|
|
if (!rsis[s].isID()) {
|
|
isIdentityRSI = false;
|
|
}
|
|
if (s > 0 && !rsis[s].equals(rsis[s-1])) {
|
|
isConstantRSI = false;
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Are all the RSIs identity (1,0).
|
|
* @return {Boolean} True if they are.
|
|
*/
|
|
this.isIdentityRSI = function () { return isIdentityRSI; };
|
|
/**
|
|
* Are all the RSIs equal.
|
|
* @return {Boolean} True if they are.
|
|
*/
|
|
this.isConstantRSI = function () { return isConstantRSI; };
|
|
/**
|
|
* Get the photometricInterpretation of the image.
|
|
* @return {String} The photometricInterpretation of the image.
|
|
*/
|
|
this.getPhotometricInterpretation = function() { return photometricInterpretation; };
|
|
/**
|
|
* Set the photometricInterpretation of the image.
|
|
* @pqrqm {String} interp The photometricInterpretation of the image.
|
|
*/
|
|
this.setPhotometricInterpretation = function(interp) { photometricInterpretation = interp; };
|
|
/**
|
|
* Get the planarConfiguration of the image.
|
|
* @return {Number} The planarConfiguration of the image.
|
|
*/
|
|
this.getPlanarConfiguration = function() { return planarConfiguration; };
|
|
/**
|
|
* Set the planarConfiguration of the image.
|
|
* @param {Number} config The planarConfiguration of the image.
|
|
*/
|
|
this.setPlanarConfiguration = function(config) { planarConfiguration = config; };
|
|
/**
|
|
* Get the numberOfComponents of the image.
|
|
* @return {Number} The numberOfComponents of the image.
|
|
*/
|
|
this.getNumberOfComponents = function() { return numberOfComponents; };
|
|
|
|
/**
|
|
* Get the meta information of the image.
|
|
* @return {Object} The meta information of the image.
|
|
*/
|
|
this.getMeta = function() { return meta; };
|
|
/**
|
|
* Set the meta information of the image.
|
|
* @param {Object} rhs The meta information of the image.
|
|
*/
|
|
this.setMeta = function(rhs) { meta = rhs; };
|
|
|
|
/**
|
|
* Get value at offset. Warning: No size check...
|
|
* @param {Number} offset The desired offset.
|
|
* @param {Number} frame The desired frame.
|
|
* @return {Number} The value at offset.
|
|
*/
|
|
this.getValueAtOffset = function (offset, frame) {
|
|
return buffer[frame][offset];
|
|
};
|
|
|
|
/**
|
|
* Clone the image.
|
|
* @return {Image} A clone of this image.
|
|
*/
|
|
this.clone = function()
|
|
{
|
|
// clone the image buffer
|
|
var clonedBuffer = [];
|
|
for (var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f) {
|
|
clonedBuffer[f] = buffer[f].slice(0);
|
|
}
|
|
// create the image copy
|
|
var copy = new dwv.image.Image(this.getGeometry(), clonedBuffer);
|
|
// copy the RSIs
|
|
var nslices = this.getGeometry().getSize().getNumberOfSlices();
|
|
for ( var k = 0; k < nslices; ++k ) {
|
|
copy.setRescaleSlopeAndIntercept(this.getRescaleSlopeAndIntercept(k), k);
|
|
}
|
|
// copy extras
|
|
copy.setPhotometricInterpretation(this.getPhotometricInterpretation());
|
|
copy.setPlanarConfiguration(this.getPlanarConfiguration());
|
|
copy.setMeta(this.getMeta());
|
|
// return
|
|
return copy;
|
|
};
|
|
|
|
/**
|
|
* Append a slice to the image.
|
|
* @param {Image} The slice to append.
|
|
* @return {Number} The number of the inserted slice.
|
|
*/
|
|
this.appendSlice = function (rhs, frame)
|
|
{
|
|
// check input
|
|
if( rhs === null ) {
|
|
throw new Error("Cannot append null slice");
|
|
}
|
|
var rhsSize = rhs.getGeometry().getSize();
|
|
var size = geometry.getSize();
|
|
if( rhsSize.getNumberOfSlices() !== 1 ) {
|
|
throw new Error("Cannot append more than one slice");
|
|
}
|
|
if( size.getNumberOfColumns() !== rhsSize.getNumberOfColumns() ) {
|
|
throw new Error("Cannot append a slice with different number of columns");
|
|
}
|
|
if( size.getNumberOfRows() !== rhsSize.getNumberOfRows() ) {
|
|
throw new Error("Cannot append a slice with different number of rows");
|
|
}
|
|
if( photometricInterpretation !== rhs.getPhotometricInterpretation() ) {
|
|
throw new Error("Cannot append a slice with different photometric interpretation");
|
|
}
|
|
// all meta should be equal
|
|
for( var key in meta ) {
|
|
if( meta[key] !== rhs.getMeta()[key] ) {
|
|
throw new Error("Cannot append a slice with different "+key);
|
|
}
|
|
}
|
|
|
|
var f = (typeof frame === "undefined") ? 0 : frame;
|
|
|
|
// calculate slice size
|
|
var mul = 1;
|
|
if( photometricInterpretation === "RGB" || photometricInterpretation === "YBR_FULL_422") {
|
|
mul = 3;
|
|
}
|
|
var sliceSize = mul * size.getSliceSize();
|
|
|
|
// create the new buffer
|
|
var newBuffer = dwv.dicom.getTypedArray(
|
|
buffer[f].BYTES_PER_ELEMENT * 8,
|
|
meta.IsSigned ? 1 : 0,
|
|
sliceSize * (size.getNumberOfSlices() + 1) );
|
|
|
|
// append slice at new position
|
|
var newSliceNb = geometry.getSliceIndex( rhs.getGeometry().getOrigin() );
|
|
if( newSliceNb === 0 )
|
|
{
|
|
newBuffer.set(rhs.getFrame(f));
|
|
newBuffer.set(buffer[f], sliceSize);
|
|
}
|
|
else if( newSliceNb === size.getNumberOfSlices() )
|
|
{
|
|
newBuffer.set(buffer[f]);
|
|
newBuffer.set(rhs.getFrame(f), size.getNumberOfSlices() * sliceSize);
|
|
}
|
|
else
|
|
{
|
|
var offset = newSliceNb * sliceSize;
|
|
newBuffer.set(buffer[f].subarray(0, offset - 1));
|
|
newBuffer.set(rhs.getFrame(f), offset);
|
|
newBuffer.set(buffer[f].subarray(offset), offset + sliceSize);
|
|
}
|
|
|
|
// update geometry
|
|
geometry.appendOrigin( rhs.getGeometry().getOrigin(), newSliceNb );
|
|
// update rsi
|
|
rsis.splice(newSliceNb, 0, rhs.getRescaleSlopeAndIntercept(0));
|
|
|
|
// copy to class variables
|
|
buffer[f] = newBuffer;
|
|
|
|
// insert overlay information of the slice to the image
|
|
overlays.splice(newSliceNb, 0, rhs.getOverlays()[0]);
|
|
|
|
// return the appended slice number
|
|
return newSliceNb;
|
|
};
|
|
|
|
/**
|
|
* Append a frame buffer to the image.
|
|
* @param {Object} frameBuffer The frame buffer to append.
|
|
*/
|
|
this.appendFrameBuffer = function (frameBuffer)
|
|
{
|
|
buffer.push(frameBuffer);
|
|
};
|
|
|
|
/**
|
|
* Get the data range.
|
|
* @return {Object} The data range.
|
|
*/
|
|
this.getDataRange = function() {
|
|
if( !dataRange ) {
|
|
dataRange = this.calculateDataRange();
|
|
}
|
|
return dataRange;
|
|
};
|
|
|
|
/**
|
|
* Get the rescaled data range.
|
|
* @return {Object} The rescaled data range.
|
|
*/
|
|
this.getRescaledDataRange = function() {
|
|
if( !rescaledDataRange ) {
|
|
rescaledDataRange = this.calculateRescaledDataRange();
|
|
}
|
|
return rescaledDataRange;
|
|
};
|
|
|
|
/**
|
|
* Get the histogram.
|
|
* @return {Array} The histogram.
|
|
*/
|
|
this.getHistogram = function() {
|
|
if( !histogram ) {
|
|
var res = this.calculateHistogram();
|
|
dataRange = res.dataRange;
|
|
rescaledDataRange = res.rescaledDataRange;
|
|
histogram = res.histogram;
|
|
}
|
|
return histogram;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get the value of the image at a specific coordinate.
|
|
* @param {Number} i The X index.
|
|
* @param {Number} j The Y index.
|
|
* @param {Number} k The Z index.
|
|
* @param {Number} f The frame number.
|
|
* @return {Number} The value at the desired position.
|
|
* Warning: No size check...
|
|
*/
|
|
dwv.image.Image.prototype.getValue = function( i, j, k, f )
|
|
{
|
|
var frame = (f || 0);
|
|
var index = new dwv.math.Index3D(i,j,k);
|
|
return this.getValueAtOffset( this.getGeometry().indexToOffset(index), frame );
|
|
};
|
|
|
|
/**
|
|
* Get the rescaled value of the image at a specific coordinate.
|
|
* @param {Number} i The X index.
|
|
* @param {Number} j The Y index.
|
|
* @param {Number} k The Z index.
|
|
* @param {Number} f The frame number.
|
|
* @return {Number} The rescaled value at the desired position.
|
|
* Warning: No size check...
|
|
*/
|
|
dwv.image.Image.prototype.getRescaledValue = function( i, j, k, f )
|
|
{
|
|
var frame = (f || 0);
|
|
var val = this.getValue(i,j,k,frame);
|
|
if (!this.isIdentityRSI()) {
|
|
val = this.getRescaleSlopeAndIntercept(k).apply(val);
|
|
}
|
|
return val;
|
|
};
|
|
|
|
/**
|
|
* Calculate the data range of the image.
|
|
* WARNING: for speed reasons, only calculated on the first frame...
|
|
* @return {Object} The range {min, max}.
|
|
*/
|
|
dwv.image.Image.prototype.calculateDataRange = function ()
|
|
{
|
|
var size = this.getGeometry().getSize().getTotalSize();
|
|
var nFrames = 1; //this.getNumberOfFrames();
|
|
var min = this.getValueAtOffset(0,0);
|
|
var max = min;
|
|
var value = 0;
|
|
for ( var f = 0; f < nFrames; ++f ) {
|
|
for ( var i = 0; i < size; ++i ) {
|
|
value = this.getValueAtOffset(i,f);
|
|
if( value > max ) { max = value; }
|
|
if( value < min ) { min = value; }
|
|
}
|
|
}
|
|
// return
|
|
return { "min": min, "max": max };
|
|
};
|
|
|
|
/**
|
|
* Calculate the rescaled data range of the image.
|
|
* WARNING: for speed reasons, only calculated on the first frame...
|
|
* @return {Object} The range {min, max}.
|
|
*/
|
|
dwv.image.Image.prototype.calculateRescaledDataRange = function ()
|
|
{
|
|
if (this.isIdentityRSI()) {
|
|
return this.getDataRange();
|
|
}
|
|
else if (this.isConstantRSI()) {
|
|
var range = this.getDataRange();
|
|
var resmin = this.getRescaleSlopeAndIntercept(0).apply(range.min);
|
|
var resmax = this.getRescaleSlopeAndIntercept(0).apply(range.max);
|
|
return {
|
|
"min": ((resmin < resmax) ? resmin : resmax),
|
|
"max": ((resmin > resmax) ? resmin : resmax)
|
|
};
|
|
}
|
|
else {
|
|
var size = this.getGeometry().getSize();
|
|
var nFrames = 1; //this.getNumberOfFrames();
|
|
var rmin = this.getRescaledValue(0,0,0);
|
|
var rmax = rmin;
|
|
var rvalue = 0;
|
|
for ( var f = 0, nframes = nFrames; f < nframes; ++f ) {
|
|
for ( var k = 0, nslices = size.getNumberOfSlices(); k < nslices; ++k ) {
|
|
for ( var j = 0, nrows = size.getNumberOfRows(); j < nrows; ++j ) {
|
|
for ( var i = 0, ncols = size.getNumberOfColumns(); i < ncols; ++i ) {
|
|
rvalue = this.getRescaledValue(i,j,k,f);
|
|
if( rvalue > rmax ) { rmax = rvalue; }
|
|
if( rvalue < rmin ) { rmin = rvalue; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// return
|
|
return { "min": rmin, "max": rmax };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Calculate the histogram of the image.
|
|
* @return {Object} The histogram, data range and rescaled data range.
|
|
*/
|
|
dwv.image.Image.prototype.calculateHistogram = function ()
|
|
{
|
|
var size = this.getGeometry().getSize();
|
|
var histo = [];
|
|
var min = this.getValue(0,0,0);
|
|
var max = min;
|
|
var value = 0;
|
|
var rmin = this.getRescaledValue(0,0,0);
|
|
var rmax = rmin;
|
|
var rvalue = 0;
|
|
for ( var f = 0, nframes = this.getNumberOfFrames(); f < nframes; ++f ) {
|
|
for ( var k = 0, nslices = size.getNumberOfSlices(); k < nslices; ++k ) {
|
|
for ( var j = 0, nrows = size.getNumberOfRows(); j < nrows; ++j ) {
|
|
for ( var i = 0, ncols = size.getNumberOfColumns(); i < ncols; ++i ) {
|
|
value = this.getValue(i,j,k,f);
|
|
if( value > max ) { max = value; }
|
|
if( value < min ) { min = value; }
|
|
rvalue = this.getRescaleSlopeAndIntercept(k).apply(value);
|
|
if( rvalue > rmax ) { rmax = rvalue; }
|
|
if( rvalue < rmin ) { rmin = rvalue; }
|
|
histo[rvalue] = ( histo[rvalue] || 0 ) + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// set data range
|
|
var dataRange = { "min": min, "max": max };
|
|
var rescaledDataRange = { "min": rmin, "max": rmax };
|
|
// generate data for plotting
|
|
var histogram = [];
|
|
for ( var b = rmin; b <= rmax; ++b ) {
|
|
histogram.push([b, ( histo[b] || 0 ) ]);
|
|
}
|
|
// return
|
|
return { 'dataRange': dataRange, 'rescaledDataRange': rescaledDataRange,
|
|
'histogram': histogram };
|
|
};
|
|
|
|
/**
|
|
* Convolute the image with a given 2D kernel.
|
|
* @param {Array} weights The weights of the 2D kernel as a 3x3 matrix.
|
|
* @return {Image} The convoluted image.
|
|
* Note: Uses the raw buffer values.
|
|
*/
|
|
dwv.image.Image.prototype.convolute2D = function(weights)
|
|
{
|
|
if(weights.length !== 9) {
|
|
throw new Error("The convolution matrix does not have a length of 9; it has "+weights.length);
|
|
}
|
|
|
|
var newImage = this.clone();
|
|
var newBuffer = newImage.getBuffer();
|
|
|
|
var imgSize = this.getGeometry().getSize();
|
|
var ncols = imgSize.getNumberOfColumns();
|
|
var nrows = imgSize.getNumberOfRows();
|
|
var nslices = imgSize.getNumberOfSlices();
|
|
var nframes = this.getNumberOfFrames();
|
|
var ncomp = this.getNumberOfComponents();
|
|
|
|
// adapt to number of component and planar configuration
|
|
var factor = 1;
|
|
var componentOffset = 1;
|
|
var frameOffset = imgSize.getTotalSize();
|
|
if( ncomp === 3 )
|
|
{
|
|
frameOffset *= 3;
|
|
if( this.getPlanarConfiguration() === 0 )
|
|
{
|
|
factor = 3;
|
|
}
|
|
else
|
|
{
|
|
componentOffset = imgSize.getTotalSize();
|
|
}
|
|
}
|
|
|
|
// allow special indent for matrices
|
|
/*jshint indent:false */
|
|
|
|
// default weight offset matrix
|
|
var wOff = [];
|
|
wOff[0] = (-ncols-1) * factor; wOff[1] = (-ncols) * factor; wOff[2] = (-ncols+1) * factor;
|
|
wOff[3] = -factor; wOff[4] = 0; wOff[5] = 1 * factor;
|
|
wOff[6] = (ncols-1) * factor; wOff[7] = (ncols) * factor; wOff[8] = (ncols+1) * factor;
|
|
|
|
// border weight offset matrices
|
|
// borders are extended (see http://en.wikipedia.org/wiki/Kernel_%28image_processing%29)
|
|
|
|
// i=0, j=0
|
|
var wOff00 = [];
|
|
wOff00[0] = wOff[4]; wOff00[1] = wOff[4]; wOff00[2] = wOff[5];
|
|
wOff00[3] = wOff[4]; wOff00[4] = wOff[4]; wOff00[5] = wOff[5];
|
|
wOff00[6] = wOff[7]; wOff00[7] = wOff[7]; wOff00[8] = wOff[8];
|
|
// i=0, j=*
|
|
var wOff0x = [];
|
|
wOff0x[0] = wOff[1]; wOff0x[1] = wOff[1]; wOff0x[2] = wOff[2];
|
|
wOff0x[3] = wOff[4]; wOff0x[4] = wOff[4]; wOff0x[5] = wOff[5];
|
|
wOff0x[6] = wOff[7]; wOff0x[7] = wOff[7]; wOff0x[8] = wOff[8];
|
|
// i=0, j=nrows
|
|
var wOff0n = [];
|
|
wOff0n[0] = wOff[1]; wOff0n[1] = wOff[1]; wOff0n[2] = wOff[2];
|
|
wOff0n[3] = wOff[4]; wOff0n[4] = wOff[4]; wOff0n[5] = wOff[5];
|
|
wOff0n[6] = wOff[4]; wOff0n[7] = wOff[4]; wOff0n[8] = wOff[5];
|
|
|
|
// i=*, j=0
|
|
var wOffx0 = [];
|
|
wOffx0[0] = wOff[3]; wOffx0[1] = wOff[4]; wOffx0[2] = wOff[5];
|
|
wOffx0[3] = wOff[3]; wOffx0[4] = wOff[4]; wOffx0[5] = wOff[5];
|
|
wOffx0[6] = wOff[6]; wOffx0[7] = wOff[7]; wOffx0[8] = wOff[8];
|
|
// i=*, j=* -> wOff
|
|
// i=*, j=nrows
|
|
var wOffxn = [];
|
|
wOffxn[0] = wOff[0]; wOffxn[1] = wOff[1]; wOffxn[2] = wOff[2];
|
|
wOffxn[3] = wOff[3]; wOffxn[4] = wOff[4]; wOffxn[5] = wOff[5];
|
|
wOffxn[6] = wOff[3]; wOffxn[7] = wOff[4]; wOffxn[8] = wOff[5];
|
|
|
|
// i=ncols, j=0
|
|
var wOffn0 = [];
|
|
wOffn0[0] = wOff[3]; wOffn0[1] = wOff[4]; wOffn0[2] = wOff[4];
|
|
wOffn0[3] = wOff[3]; wOffn0[4] = wOff[4]; wOffn0[5] = wOff[4];
|
|
wOffn0[6] = wOff[6]; wOffn0[7] = wOff[7]; wOffn0[8] = wOff[7];
|
|
// i=ncols, j=*
|
|
var wOffnx = [];
|
|
wOffnx[0] = wOff[0]; wOffnx[1] = wOff[1]; wOffnx[2] = wOff[1];
|
|
wOffnx[3] = wOff[3]; wOffnx[4] = wOff[4]; wOffnx[5] = wOff[4];
|
|
wOffnx[6] = wOff[6]; wOffnx[7] = wOff[7]; wOffnx[8] = wOff[7];
|
|
// i=ncols, j=nrows
|
|
var wOffnn = [];
|
|
wOffnn[0] = wOff[0]; wOffnn[1] = wOff[1]; wOffnn[2] = wOff[1];
|
|
wOffnn[3] = wOff[3]; wOffnn[4] = wOff[4]; wOffnn[5] = wOff[4];
|
|
wOffnn[6] = wOff[3]; wOffnn[7] = wOff[4]; wOffnn[8] = wOff[4];
|
|
|
|
// restore indent for rest of method
|
|
/*jshint indent:4 */
|
|
|
|
// loop vars
|
|
var pixelOffset = 0;
|
|
var newValue = 0;
|
|
var wOffFinal = [];
|
|
// go through the destination image pixels
|
|
for (var f=0; f<nframes; f++) {
|
|
pixelOffset = f * frameOffset;
|
|
for (var c=0; c<ncomp; c++) {
|
|
// special component offset
|
|
pixelOffset += c * componentOffset;
|
|
for (var k=0; k<nslices; k++) {
|
|
for (var j=0; j<nrows; j++) {
|
|
for (var i=0; i<ncols; i++) {
|
|
wOffFinal = wOff;
|
|
// special border cases
|
|
if( i === 0 && j === 0 ) {
|
|
wOffFinal = wOff00;
|
|
}
|
|
else if( i === 0 && j === (nrows-1) ) {
|
|
wOffFinal = wOff0n;
|
|
}
|
|
else if( i === (ncols-1) && j === 0 ) {
|
|
wOffFinal = wOffn0;
|
|
}
|
|
else if( i === (ncols-1) && j === (nrows-1) ) {
|
|
wOffFinal = wOffnn;
|
|
}
|
|
else if( i === 0 && j !== (nrows-1) && j !== 0 ) {
|
|
wOffFinal = wOff0x;
|
|
}
|
|
else if( i === (ncols-1) && j !== (nrows-1) && j !== 0 ) {
|
|
wOffFinal = wOffnx;
|
|
}
|
|
else if( i !== 0 && i !== (ncols-1) && j === 0 ) {
|
|
wOffFinal = wOffx0;
|
|
}
|
|
else if( i !== 0 && i !== (ncols-1) && j === (nrows-1) ) {
|
|
wOffFinal = wOffxn;
|
|
}
|
|
|
|
// calculate the weighed sum of the source image pixels that
|
|
// fall under the convolution matrix
|
|
newValue = 0;
|
|
for( var wi=0; wi<9; ++wi )
|
|
{
|
|
newValue += this.getValueAtOffset(pixelOffset + wOffFinal[wi], f) * weights[wi];
|
|
}
|
|
newBuffer[f][pixelOffset] = newValue;
|
|
// increment pixel offset
|
|
pixelOffset += factor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return newImage;
|
|
};
|
|
|
|
/**
|
|
* Transform an image using a specific operator.
|
|
* WARNING: no size check!
|
|
* @param {Function} operator The operator to use when transforming.
|
|
* @return {Image} The transformed image.
|
|
* Note: Uses the raw buffer values.
|
|
*/
|
|
dwv.image.Image.prototype.transform = function(operator)
|
|
{
|
|
var newImage = this.clone();
|
|
var newBuffer = newImage.getBuffer();
|
|
for ( var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f )
|
|
{
|
|
for( var i = 0, leni = newBuffer[f].length; i < leni; ++i )
|
|
{
|
|
newBuffer[f][i] = operator( newImage.getValueAtOffset(i,f) );
|
|
}
|
|
}
|
|
return newImage;
|
|
};
|
|
|
|
/**
|
|
* Compose this image with another one and using a specific operator.
|
|
* WARNING: no size check!
|
|
* @param {Image} rhs The image to compose with.
|
|
* @param {Function} operator The operator to use when composing.
|
|
* @return {Image} The composed image.
|
|
* Note: Uses the raw buffer values.
|
|
*/
|
|
dwv.image.Image.prototype.compose = function(rhs, operator)
|
|
{
|
|
var newImage = this.clone();
|
|
var newBuffer = newImage.getBuffer();
|
|
for ( var f = 0, lenf = this.getNumberOfFrames(); f < lenf; ++f )
|
|
{
|
|
for( var i = 0, leni = newBuffer[f].length; i < leni; ++i )
|
|
{
|
|
// using the operator on the local buffer, i.e. the latest (not original) data
|
|
newBuffer[f][i] = Math.floor( operator( this.getValueAtOffset(i,f), rhs.getValueAtOffset(i,f) ) );
|
|
}
|
|
}
|
|
return newImage;
|
|
};
|
|
|
|
/**
|
|
* Quantify a line according to image information.
|
|
* @param {Object} line The line to quantify.
|
|
* @return {Object} A quantification object.
|
|
*/
|
|
dwv.image.Image.prototype.quantifyLine = function(line)
|
|
{
|
|
var quant = {};
|
|
// length
|
|
var spacing = this.getGeometry().getSpacing();
|
|
var length = line.getWorldLength( spacing.getColumnSpacing(),
|
|
spacing.getRowSpacing() );
|
|
if (length !== null) {
|
|
quant.length = {"value": length, "unit": dwv.i18n("unit.mm")};
|
|
}
|
|
// return
|
|
return quant;
|
|
};
|
|
|
|
/**
|
|
* Quantify a rectangle according to image information.
|
|
* @param {Object} rect The rectangle to quantify.
|
|
* @return {Object} A quantification object.
|
|
*/
|
|
dwv.image.Image.prototype.quantifyRect = function(rect)
|
|
{
|
|
var quant = {};
|
|
// surface
|
|
var spacing = this.getGeometry().getSpacing();
|
|
var surface = rect.getWorldSurface( spacing.getColumnSpacing(),
|
|
spacing.getRowSpacing());
|
|
if (surface !== null) {
|
|
quant.surface = {"value": surface/100, "unit": dwv.i18n("unit.cm2")};
|
|
}
|
|
// stats
|
|
var subBuffer = [];
|
|
var minJ = parseInt(rect.getBegin().getY(), 10);
|
|
var maxJ = parseInt(rect.getEnd().getY(), 10);
|
|
var minI = parseInt(rect.getBegin().getX(), 10);
|
|
var maxI = parseInt(rect.getEnd().getX(), 10);
|
|
for ( var j = minJ; j < maxJ; ++j ) {
|
|
for ( var i = minI; i < maxI; ++i ) {
|
|
subBuffer.push( this.getValue(i,j,0) );
|
|
}
|
|
}
|
|
var quantif = dwv.math.getStats( subBuffer );
|
|
quant.min = {"value": quantif.min, "unit": ""};
|
|
quant.max = {"value": quantif.max, "unit": ""};
|
|
quant.mean = {"value": quantif.mean, "unit": ""};
|
|
quant.stdDev = {"value": quantif.stdDev, "unit": ""};
|
|
// return
|
|
return quant;
|
|
};
|
|
|
|
/**
|
|
* Quantify an ellipse according to image information.
|
|
* @param {Object} ellipse The ellipse to quantify.
|
|
* @return {Object} A quantification object.
|
|
*/
|
|
dwv.image.Image.prototype.quantifyEllipse = function(ellipse)
|
|
{
|
|
var quant = {};
|
|
// surface
|
|
var spacing = this.getGeometry().getSpacing();
|
|
var surface = ellipse.getWorldSurface( spacing.getColumnSpacing(),
|
|
spacing.getRowSpacing());
|
|
if (surface !== null) {
|
|
quant.surface = {"value": surface/100, "unit": dwv.i18n("unit.cm2")};
|
|
}
|
|
// return
|
|
return quant;
|
|
};
|
|
|
|
/**
|
|
* {@link dwv.image.Image} factory.
|
|
* @constructor
|
|
*/
|
|
dwv.image.ImageFactory = function () {};
|
|
|
|
/**
|
|
* Get an {@link dwv.image.Image} object from the read DICOM file.
|
|
* @param {Object} dicomElements The DICOM tags.
|
|
* @param {Array} pixelBuffer The pixel buffer.
|
|
* @return {View} A new Image.
|
|
*/
|
|
dwv.image.ImageFactory.prototype.create = function (dicomElements, pixelBuffer)
|
|
{
|
|
// columns
|
|
var columns = dicomElements.getFromKey("x00280011");
|
|
if ( !columns ) {
|
|
throw new Error("Missing or empty DICOM image number of columns");
|
|
}
|
|
// rows
|
|
var rows = dicomElements.getFromKey("x00280010");
|
|
if ( !rows ) {
|
|
throw new Error("Missing or empty DICOM image number of rows");
|
|
}
|
|
// image size
|
|
var size = new dwv.image.Size( columns, rows );
|
|
|
|
// spacing
|
|
var rowSpacing = null;
|
|
var columnSpacing = null;
|
|
// PixelSpacing
|
|
var pixelSpacing = dicomElements.getFromKey("x00280030");
|
|
// ImagerPixelSpacing
|
|
var imagerPixelSpacing = dicomElements.getFromKey("x00181164");
|
|
if ( pixelSpacing && pixelSpacing[0] && pixelSpacing[1] ) {
|
|
rowSpacing = parseFloat( pixelSpacing[0] );
|
|
columnSpacing = parseFloat( pixelSpacing[1] );
|
|
}
|
|
else if ( imagerPixelSpacing && imagerPixelSpacing[0] && imagerPixelSpacing[1] ) {
|
|
rowSpacing = parseFloat( imagerPixelSpacing[0] );
|
|
columnSpacing = parseFloat( imagerPixelSpacing[1] );
|
|
}
|
|
// image spacing
|
|
var spacing = new dwv.image.Spacing( columnSpacing, rowSpacing );
|
|
|
|
// TransferSyntaxUID
|
|
var transferSyntaxUID = dicomElements.getFromKey("x00020010");
|
|
var syntax = dwv.dicom.cleanString( transferSyntaxUID );
|
|
var jpeg2000 = dwv.dicom.isJpeg2000TransferSyntax( syntax );
|
|
var jpegBase = dwv.dicom.isJpegBaselineTransferSyntax( syntax );
|
|
var jpegLoss = dwv.dicom.isJpegLosslessTransferSyntax( syntax );
|
|
|
|
// slice position
|
|
var slicePosition = new Array(0,0,0);
|
|
// ImagePositionPatient
|
|
var imagePositionPatient = dicomElements.getFromKey("x00200032");
|
|
if ( imagePositionPatient ) {
|
|
slicePosition = [ parseFloat( imagePositionPatient[0] ),
|
|
parseFloat( imagePositionPatient[1] ),
|
|
parseFloat( imagePositionPatient[2] ) ];
|
|
}
|
|
|
|
// slice orientation
|
|
var imageOrientationPatient = dicomElements.getFromKey("x00200037");
|
|
var orientationMatrix;
|
|
if ( imageOrientationPatient ) {
|
|
var rowCosines = new dwv.math.Vector3D( parseFloat( imageOrientationPatient[0] ),
|
|
parseFloat( imageOrientationPatient[1] ),
|
|
parseFloat( imageOrientationPatient[2] ) );
|
|
var colCosines = new dwv.math.Vector3D( parseFloat( imageOrientationPatient[3] ),
|
|
parseFloat( imageOrientationPatient[4] ),
|
|
parseFloat( imageOrientationPatient[5] ) );
|
|
var normal = rowCosines.crossProduct(colCosines);
|
|
orientationMatrix = new dwv.math.Matrix33(
|
|
rowCosines.getX(), rowCosines.getY(), rowCosines.getZ(),
|
|
colCosines.getX(), colCosines.getY(), colCosines.getZ(),
|
|
normal.getX(), normal.getY(), normal.getZ() );
|
|
}
|
|
|
|
// geometry
|
|
var origin = new dwv.math.Point3D(slicePosition[0], slicePosition[1], slicePosition[2]);
|
|
var geometry = new dwv.image.Geometry( origin, size, spacing, orientationMatrix );
|
|
|
|
// image
|
|
var image = new dwv.image.Image( geometry, pixelBuffer );
|
|
// PhotometricInterpretation
|
|
var photometricInterpretation = dicomElements.getFromKey("x00280004");
|
|
if ( photometricInterpretation ) {
|
|
var photo = dwv.dicom.cleanString(photometricInterpretation).toUpperCase();
|
|
// jpeg decoders output RGB data
|
|
if ( (jpeg2000 || jpegBase || jpegLoss) &&
|
|
(photo !== "MONOCHROME1" && photo !== "MONOCHROME2") ) {
|
|
photo = "RGB";
|
|
}
|
|
image.setPhotometricInterpretation( photo );
|
|
}
|
|
// PlanarConfiguration
|
|
var planarConfiguration = dicomElements.getFromKey("x00280006");
|
|
if ( planarConfiguration ) {
|
|
image.setPlanarConfiguration( planarConfiguration );
|
|
}
|
|
|
|
// rescale slope and intercept
|
|
var slope = 1;
|
|
// RescaleSlope
|
|
var rescaleSlope = dicomElements.getFromKey("x00281053");
|
|
if ( rescaleSlope ) {
|
|
slope = parseFloat(rescaleSlope);
|
|
}
|
|
var intercept = 0;
|
|
// RescaleIntercept
|
|
var rescaleIntercept = dicomElements.getFromKey("x00281052");
|
|
if ( rescaleIntercept ) {
|
|
intercept = parseFloat(rescaleIntercept);
|
|
}
|
|
var rsi = new dwv.image.RescaleSlopeAndIntercept(slope, intercept);
|
|
image.setRescaleSlopeAndIntercept( rsi );
|
|
|
|
// meta information
|
|
var meta = {};
|
|
// Modality
|
|
var modality = dicomElements.getFromKey("x00080060");
|
|
if ( modality ) {
|
|
meta.Modality = modality;
|
|
}
|
|
// StudyInstanceUID
|
|
var studyInstanceUID = dicomElements.getFromKey("x0020000D");
|
|
if ( studyInstanceUID ) {
|
|
meta.StudyInstanceUID = studyInstanceUID;
|
|
}
|
|
// SeriesInstanceUID
|
|
var seriesInstanceUID = dicomElements.getFromKey("x0020000E");
|
|
if ( seriesInstanceUID ) {
|
|
meta.SeriesInstanceUID = seriesInstanceUID;
|
|
}
|
|
// BitsStored
|
|
var bitsStored = dicomElements.getFromKey("x00280101");
|
|
if ( bitsStored ) {
|
|
meta.BitsStored = parseInt(bitsStored, 10);
|
|
}
|
|
// PixelRepresentation -> is signed
|
|
var pixelRepresentation = dicomElements.getFromKey("x00280103");
|
|
meta.IsSigned = false;
|
|
if ( pixelRepresentation ) {
|
|
meta.IsSigned = (pixelRepresentation === 1);
|
|
}
|
|
image.setMeta(meta);
|
|
|
|
// overlay
|
|
image.setFirstOverlay( dwv.gui.info.createOverlays(dicomElements) );
|
|
|
|
return image;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
/** @namespace */
|
|
dwv.image.lut = dwv.image.lut || {};
|
|
|
|
/**
|
|
* Rescale LUT class.
|
|
* @constructor
|
|
* @param {Object} rsi The rescale slope and intercept.
|
|
* @param {Number} bitsStored The number of bits used to store the data.
|
|
*/
|
|
dwv.image.lut.Rescale = function (rsi, bitsStored)
|
|
{
|
|
/**
|
|
* The internal array.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var lut = null;
|
|
|
|
/**
|
|
* Flag to know if the lut is ready or not.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isReady = false;
|
|
|
|
/**
|
|
* The size of the LUT array.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var length = Math.pow(2, bitsStored);
|
|
|
|
/**
|
|
* Get the Rescale Slope and Intercept (RSI).
|
|
* @return {Object} The rescale slope and intercept.
|
|
*/
|
|
this.getRSI = function () { return rsi; };
|
|
|
|
/**
|
|
* Is the lut ready to use or not? If not, the user must
|
|
* call 'initialise'.
|
|
* @return {Boolean} True if the lut is ready to use.
|
|
*/
|
|
this.isReady = function () { return isReady; };
|
|
|
|
/**
|
|
* Initialise the LUT.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
// check if already initialised
|
|
if (isReady) {
|
|
return;
|
|
}
|
|
// create lut and fill it
|
|
lut = new Float32Array(length);
|
|
for ( var i = 0; i < length; ++i ) {
|
|
lut[i] = rsi.apply(i);
|
|
}
|
|
// update ready flag
|
|
isReady = true;
|
|
};
|
|
|
|
/**
|
|
* Get the length of the LUT array.
|
|
* @return {Number} The length of the LUT array.
|
|
*/
|
|
this.getLength = function () { return length; };
|
|
|
|
/**
|
|
* Get the value of the LUT at the given offset.
|
|
* @return {Number} The value of the LUT at the given offset.
|
|
*/
|
|
this.getValue = function (offset)
|
|
{
|
|
return lut[ offset ];
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Window LUT class.
|
|
* @constructor
|
|
* @param {Number} rescaleLut The associated rescale LUT.
|
|
* @param {Boolean} isSigned Flag to know if the data is signed or not.
|
|
*/
|
|
dwv.image.lut.Window = function (rescaleLut, isSigned)
|
|
{
|
|
/**
|
|
* The internal array: Uint8ClampedArray clamps between 0 and 255.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var lut = null;
|
|
|
|
/**
|
|
* The window level.
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
var windowLevel = null;
|
|
|
|
/**
|
|
* Flag to know if the lut is ready or not.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isReady = false;
|
|
|
|
/**
|
|
* Shift for signed data.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var signedShift = 0;
|
|
|
|
/**
|
|
* Get the window / level.
|
|
* @return {Object} The window / level.
|
|
*/
|
|
this.getWindowLevel = function () { return windowLevel; };
|
|
/**
|
|
* Get the signed flag.
|
|
* @return {Boolean} The signed flag.
|
|
*/
|
|
this.isSigned = function () { return isSigned; };
|
|
/**
|
|
* Get the rescale lut.
|
|
* @return {Object} The rescale lut.
|
|
*/
|
|
this.getRescaleLut = function () { return rescaleLut; };
|
|
|
|
/**
|
|
* Is the lut ready to use or not? If not, the user must
|
|
* call 'update'.
|
|
* @return {Boolean} True if the lut is ready to use.
|
|
*/
|
|
this.isReady = function () { return isReady; };
|
|
|
|
/**
|
|
* Set the window center and width.
|
|
* @param {Object} wl The window level.
|
|
*/
|
|
this.setWindowLevel = function (wl)
|
|
{
|
|
// store the window values
|
|
windowLevel = wl;
|
|
// possible signed shift
|
|
signedShift = 0;
|
|
windowLevel.setSignedOffset(0);
|
|
if ( isSigned ) {
|
|
var size = rescaleLut.getLength();
|
|
signedShift = size / 2;
|
|
windowLevel.setSignedOffset(rescaleLut.getRSI().getSlope() * signedShift);
|
|
}
|
|
// update ready flag
|
|
isReady = false;
|
|
};
|
|
|
|
/**
|
|
* Update the lut if needed..
|
|
*/
|
|
this.update = function ()
|
|
{
|
|
// check if we need to update
|
|
if ( isReady ) {
|
|
return;
|
|
}
|
|
|
|
// check rescale lut
|
|
if (!rescaleLut.isReady()) {
|
|
rescaleLut.initialise();
|
|
}
|
|
// create window lut
|
|
var size = rescaleLut.getLength();
|
|
if (!lut) {
|
|
// use clamped array (polyfilled in browser.js)
|
|
lut = new Uint8ClampedArray(size);
|
|
}
|
|
// by default WindowLevel returns a value in the [0,255] range
|
|
// this is ok with regular Arrays and ClampedArray.
|
|
for ( var i = 0; i < size; ++i )
|
|
{
|
|
lut[i] = windowLevel.apply( rescaleLut.getValue(i) );
|
|
}
|
|
|
|
// update ready flag
|
|
isReady = true;
|
|
};
|
|
|
|
/**
|
|
* Get the length of the LUT array.
|
|
* @return {Number} The length of the LUT array.
|
|
*/
|
|
this.getLength = function () { return lut.length; };
|
|
|
|
/**
|
|
* Get the value of the LUT at the given offset.
|
|
* @return {Number} The value of the LUT at the given offset.
|
|
*/
|
|
this.getValue = function (offset)
|
|
{
|
|
return lut[ offset + signedShift ];
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Lookup tables for image colour display.
|
|
*/
|
|
|
|
dwv.image.lut.range_max = 256;
|
|
|
|
dwv.image.lut.buildLut = function(func)
|
|
{
|
|
var lut = [];
|
|
for( var i=0; i<dwv.image.lut.range_max; ++i ) {
|
|
lut.push(func(i));
|
|
}
|
|
return lut;
|
|
};
|
|
|
|
dwv.image.lut.max = function(/*i*/)
|
|
{
|
|
return dwv.image.lut.range_max-1;
|
|
};
|
|
|
|
dwv.image.lut.maxFirstThird = function(i)
|
|
{
|
|
if( i < dwv.image.lut.range_max/3 ) {
|
|
return dwv.image.lut.range_max-1;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
dwv.image.lut.maxSecondThird = function(i)
|
|
{
|
|
var third = dwv.image.lut.range_max/3;
|
|
if( i >= third && i < 2*third ) {
|
|
return dwv.image.lut.range_max-1;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
dwv.image.lut.maxThirdThird = function(i)
|
|
{
|
|
if( i >= 2*dwv.image.lut.range_max/3 ) {
|
|
return dwv.image.lut.range_max-1;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
dwv.image.lut.toMaxFirstThird = function(i)
|
|
{
|
|
var val = i * 3;
|
|
if( val > dwv.image.lut.range_max-1 ) {
|
|
return dwv.image.lut.range_max-1;
|
|
}
|
|
return val;
|
|
};
|
|
|
|
dwv.image.lut.toMaxSecondThird = function(i)
|
|
{
|
|
var third = dwv.image.lut.range_max/3;
|
|
var val = 0;
|
|
if( i >= third ) {
|
|
val = (i-third) * 3;
|
|
if( val > dwv.image.lut.range_max-1 ) {
|
|
return dwv.image.lut.range_max-1;
|
|
}
|
|
}
|
|
return val;
|
|
};
|
|
|
|
dwv.image.lut.toMaxThirdThird = function(i)
|
|
{
|
|
var third = dwv.image.lut.range_max/3;
|
|
var val = 0;
|
|
if( i >= 2*third ) {
|
|
val = (i-2*third) * 3;
|
|
if( val > dwv.image.lut.range_max-1 ) {
|
|
return dwv.image.lut.range_max-1;
|
|
}
|
|
}
|
|
return val;
|
|
};
|
|
|
|
dwv.image.lut.zero = function(/*i*/)
|
|
{
|
|
return 0;
|
|
};
|
|
|
|
dwv.image.lut.id = function(i)
|
|
{
|
|
return i;
|
|
};
|
|
|
|
dwv.image.lut.invId = function(i)
|
|
{
|
|
return (dwv.image.lut.range_max-1)-i;
|
|
};
|
|
|
|
// plain
|
|
dwv.image.lut.plain = {
|
|
"red": dwv.image.lut.buildLut(dwv.image.lut.id),
|
|
"green": dwv.image.lut.buildLut(dwv.image.lut.id),
|
|
"blue": dwv.image.lut.buildLut(dwv.image.lut.id)
|
|
};
|
|
|
|
// inverse plain
|
|
dwv.image.lut.invPlain = {
|
|
"red": dwv.image.lut.buildLut(dwv.image.lut.invId),
|
|
"green": dwv.image.lut.buildLut(dwv.image.lut.invId),
|
|
"blue": dwv.image.lut.buildLut(dwv.image.lut.invId)
|
|
};
|
|
|
|
//rainbow
|
|
dwv.image.lut.rainbow = {
|
|
"blue": [0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252, 255, 247, 239, 231, 223, 215, 207, 199, 191, 183, 175, 167, 159, 151, 143, 135, 127, 119, 111, 103, 95, 87, 79, 71, 63, 55, 47, 39, 31, 23, 15, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
"green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 251, 249, 247, 245, 243, 241, 239, 237, 235, 233, 231, 229, 227, 225, 223, 221, 219, 217, 215, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 192, 189, 186, 183, 180, 177, 174, 171, 168, 165, 162, 159, 156, 153, 150, 147, 144, 141, 138, 135, 132, 129, 126, 123, 120, 117, 114, 111, 108, 105, 102, 99, 96, 93, 90, 87, 84, 81, 78, 75, 72, 69, 66, 63, 60, 57, 54, 51, 48, 45, 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9, 6, 3],
|
|
"red": [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
|
|
};
|
|
|
|
// hot
|
|
dwv.image.lut.hot = {
|
|
"red": dwv.image.lut.buildLut(dwv.image.lut.toMaxFirstThird),
|
|
"green": dwv.image.lut.buildLut(dwv.image.lut.toMaxSecondThird),
|
|
"blue": dwv.image.lut.buildLut(dwv.image.lut.toMaxThirdThird)
|
|
};
|
|
|
|
// hot iron
|
|
dwv.image.lut.hot_iron = {
|
|
"red": [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
|
"green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 255],
|
|
"blue": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, 244, 248, 252, 255]
|
|
};
|
|
|
|
// pet
|
|
dwv.image.lut.pet = {
|
|
"red": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
|
"green": [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 128, 126, 124, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 255],
|
|
"blue": [0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 252, 248, 244, 240, 236, 232, 228, 224, 220, 216, 212, 208, 204, 200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 156, 152, 148, 144, 140, 136, 132, 128, 124, 120, 116, 112, 108, 104, 100, 96, 92, 88, 84, 80, 76, 72, 68, 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157, 161, 165, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 214, 218, 222, 226, 230, 234, 238, 242, 246, 250, 255]
|
|
};
|
|
|
|
// hot metal blue
|
|
dwv.image.lut.hot_metal_blue = {
|
|
"red": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 6, 9, 12, 15, 18, 21, 24, 26, 29, 32, 35, 38, 41, 44, 47, 50, 52, 55, 57, 59, 62, 64, 66, 69, 71, 74, 76, 78, 81, 83, 85, 88, 90, 93, 96, 99, 102, 105, 108, 111, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 166, 169, 172, 175, 178, 181, 184, 187, 190, 194, 198, 201, 205, 209, 213, 217, 221, 224, 228, 232, 236, 240, 244, 247, 251, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
|
"green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 6, 8, 9, 11, 13, 15, 17, 19, 21, 23, 24, 26, 28, 30, 32, 34, 36, 38, 40, 41, 43, 45, 47, 49, 51, 53, 55, 56, 58, 60, 62, 64, 66, 68, 70, 72, 73, 75, 77, 79, 81, 83, 85, 87, 88, 90, 92, 94, 96, 98, 100, 102, 104, 105, 107, 109, 111, 113, 115, 117, 119, 120, 122, 124, 126, 128, 130, 132, 134, 136, 137, 139, 141, 143, 145, 147, 149, 151, 152, 154, 156, 158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 201, 203, 205, 207, 209, 211, 213, 215, 216, 218, 220, 222, 224, 226, 228, 229, 231, 233, 235, 237, 239, 240, 242, 244, 246, 248, 250, 251, 253, 255],
|
|
"blue": [0, 2, 4, 6, 8, 10, 12, 14, 16, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 197, 194, 191, 188, 185, 182, 179, 176, 174, 171, 168, 165, 162, 159, 156, 153, 150, 144, 138, 132, 126, 121, 115, 109, 103, 97, 91, 85, 79, 74, 68, 62, 56, 50, 47, 44, 41, 38, 35, 32, 29, 26, 24, 21, 18, 15, 12, 9, 6, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 6, 9, 12, 15, 18, 21, 24, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 126, 129, 132, 135, 138, 141, 144, 147, 150, 153, 156, 159, 162, 165, 168, 171, 174, 176, 179, 182, 185, 188, 191, 194, 197, 200, 203, 206, 210, 213, 216, 219, 223, 226, 229, 232, 236, 239, 242, 245, 249, 252, 255]
|
|
};
|
|
|
|
// pet 20 step
|
|
dwv.image.lut.pet_20step = {
|
|
"red": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
|
"green": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
|
"blue": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
|
|
};
|
|
|
|
// test
|
|
dwv.image.lut.test = {
|
|
"red": dwv.image.lut.buildLut(dwv.image.lut.id),
|
|
"green": dwv.image.lut.buildLut(dwv.image.lut.zero),
|
|
"blue": dwv.image.lut.buildLut(dwv.image.lut.zero)
|
|
};
|
|
|
|
//red
|
|
/*dwv.image.lut.red = {
|
|
"red": dwv.image.lut.buildLut(dwv.image.lut.max),
|
|
"green": dwv.image.lut.buildLut(dwv.image.lut.id),
|
|
"blue": dwv.image.lut.buildLut(dwv.image.lut.id)
|
|
};*/
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.image = dwv.image || {};
|
|
|
|
/**
|
|
* WindowLevel class.
|
|
* References:
|
|
* - DICOM [Window Center and Window Width]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.11.html#sect_C.11.2.1.2}
|
|
* Pseudo-code:
|
|
* if (x <= c - 0.5 - (w-1)/2), then y = ymin
|
|
* else if (x > c - 0.5 + (w-1)/2), then y = ymax,
|
|
* else y = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax - ymin) + ymin
|
|
*/
|
|
dwv.image.WindowLevel = function (center, width)
|
|
{
|
|
// avoid zero width
|
|
if ( width === 0 ) {
|
|
throw new Error("A window level with a width of zero is not possible.");
|
|
}
|
|
|
|
/**
|
|
* Signed data offset.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var signedOffset = 0;
|
|
/**
|
|
* Output value minimum.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var ymin = 0;
|
|
/**
|
|
* Output value maximum.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var ymax = 255;
|
|
|
|
/**
|
|
* Input value minimum (calculated).
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var xmin = null;
|
|
/**
|
|
* Input value maximum (calculated).
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var xmax = null;
|
|
/**
|
|
* Window level equation slope (calculated).
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var slope = null;
|
|
/**
|
|
* Window level equation intercept (calculated).
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var inter = null;
|
|
|
|
/**
|
|
* Initialise members.
|
|
*/
|
|
function init() {
|
|
var c = center + signedOffset;
|
|
// from the standard
|
|
xmin = c - 0.5 - ( (width-1) / 2 );
|
|
xmax = c - 0.5 + ( (width-1) / 2 );
|
|
// develop the equation:
|
|
// y = ( ( x - (c - 0.5) ) / (w-1) + 0.5 ) * (ymax - ymin) + ymin
|
|
// y = ( x / (w-1) ) * (ymax - ymin) + ( -(c - 0.5) / (w-1) + 0.5 ) * (ymax - ymin) + ymin
|
|
slope = (ymax - ymin) / (width-1);
|
|
inter = ( -(c - 0.5) / (width-1) + 0.5 ) * (ymax - ymin) + ymin;
|
|
}
|
|
|
|
// call init
|
|
init();
|
|
|
|
/**
|
|
* Get the window center.
|
|
* @return {Number} The window center.
|
|
*/
|
|
this.getCenter = function () { return center; };
|
|
/**
|
|
* Get the window width.
|
|
* @return {Number} The window width.
|
|
*/
|
|
this.getWidth = function () { return width; };
|
|
|
|
/**
|
|
* Set the output value range.
|
|
* @param {Number} min The output value minimum.
|
|
* @param {Number} max The output value maximum.
|
|
*/
|
|
this.setRange = function (min, max) {
|
|
ymin = parseInt( min, 10 );
|
|
ymax = parseInt( max, 10 ) ;
|
|
// re-initialise
|
|
init();
|
|
};
|
|
/**
|
|
* Set the signed offset.
|
|
* @param {Number} The signed data offset, typically: slope * ( size / 2).
|
|
*/
|
|
this.setSignedOffset = function (offset) {
|
|
signedOffset = offset;
|
|
// re-initialise
|
|
init();
|
|
};
|
|
|
|
/**
|
|
* Apply the window level on an input value.
|
|
* @param {Number} The value to rescale as an integer.
|
|
* @return {Number} The leveled value, in the
|
|
* [ymin, ymax] range (default [0,255]).
|
|
*/
|
|
this.apply = function (value)
|
|
{
|
|
if ( value <= xmin ) {
|
|
return ymin;
|
|
} else if ( value > xmax ) {
|
|
return ymax;
|
|
} else {
|
|
return parseInt( ((value * slope) + inter), 10);
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* Check for window level equality.
|
|
* @param {Object} rhs The other window level to compare to.
|
|
* @return {Boolean} True if both window level are equal.
|
|
*/
|
|
dwv.image.WindowLevel.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getCenter() === rhs.getCenter() &&
|
|
this.getWidth() === rhs.getWidth();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the window level.
|
|
* @return {String} The window level as a string.
|
|
*/
|
|
dwv.image.WindowLevel.prototype.toString = function () {
|
|
return (this.getCenter() + ", " + this.getWidth());
|
|
};
|
|
|
|
/**
|
|
* View class.
|
|
* @constructor
|
|
* @param {Image} image The associated image.
|
|
* Need to set the window lookup table once created
|
|
* (either directly or with helper methods).
|
|
*/
|
|
dwv.image.View = function (image)
|
|
{
|
|
/**
|
|
* Window lookup tables, indexed per Rescale Slope and Intercept (RSI).
|
|
* @private
|
|
* @type Window
|
|
*/
|
|
var windowLuts = {};
|
|
|
|
/**
|
|
* Window presets.
|
|
* Minmax will be filled at first use (see view.setWindowLevelPreset).
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var windowPresets = { "minmax": {"name": "minmax"} };
|
|
|
|
/**
|
|
* Current window preset name.
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var currentPresetName = null;
|
|
|
|
/**
|
|
* colour map.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var colourMap = dwv.image.lut.plain;
|
|
/**
|
|
* Current position.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var currentPosition = {"i":0,"j":0,"k":0};
|
|
/**
|
|
* Current frame. Zero based.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var currentFrame = null;
|
|
|
|
/**
|
|
* Get the associated image.
|
|
* @return {Image} The associated image.
|
|
*/
|
|
this.getImage = function() { return image; };
|
|
/**
|
|
* Set the associated image.
|
|
* @param {Image} inImage The associated image.
|
|
*/
|
|
this.setImage = function(inImage) { image = inImage; };
|
|
|
|
/**
|
|
* Get the window LUT of the image.
|
|
* Warning: can be undefined in no window/level was set.
|
|
* @param {Object} rsi Optional image rsi, will take the one of the current slice otherwise.
|
|
* @return {Window} The window LUT of the image.
|
|
*/
|
|
this.getCurrentWindowLut = function (rsi) {
|
|
var sliceNumber = this.getCurrentPosition().k;
|
|
// use current rsi if not provided
|
|
if ( typeof rsi === "undefined" ) {
|
|
rsi = image.getRescaleSlopeAndIntercept(sliceNumber);
|
|
}
|
|
// get the lut
|
|
var wlut = windowLuts[ rsi.toString() ];
|
|
|
|
// special case for 'perslice' presets
|
|
if (currentPresetName &&
|
|
typeof windowPresets[currentPresetName] !== "undefined" &&
|
|
typeof windowPresets[currentPresetName].perslice !== "undefined" &&
|
|
windowPresets[currentPresetName].perslice === true ) {
|
|
// get the preset for this slice
|
|
var wl = windowPresets[currentPresetName].wl[sliceNumber];
|
|
// apply it if different from previous
|
|
if (!wlut.getWindowLevel().equals(wl)) {
|
|
// previous values
|
|
var previousWidth = wlut.getWindowLevel().getWidth();
|
|
var previousCenter = wlut.getWindowLevel().getCenter();
|
|
// set slice window level
|
|
wlut.setWindowLevel(wl);
|
|
// fire event
|
|
if ( previousWidth !== wl.getWidth() ) {
|
|
this.fireEvent({"type": "wl-width-change",
|
|
"wc": wl.getCenter(), "ww": wl.getWidth(),
|
|
"skipGenerate": true});
|
|
}
|
|
if ( previousCenter !== wl.getCenter() ) {
|
|
this.fireEvent({"type": "wl-center-change",
|
|
"wc": wl.getCenter(), "ww": wl.getWidth(),
|
|
"skipGenerate": true});
|
|
}
|
|
}
|
|
}
|
|
|
|
// update in case of wl change
|
|
// TODO: should not be run in a getter...
|
|
wlut.update();
|
|
|
|
// return
|
|
return wlut;
|
|
};
|
|
/**
|
|
* Add the window LUT to the list.
|
|
* @param {Window} wlut The window LUT of the image.
|
|
*/
|
|
this.addWindowLut = function (wlut)
|
|
{
|
|
var rsi = wlut.getRescaleLut().getRSI();
|
|
windowLuts[rsi.toString()] = wlut;
|
|
};
|
|
|
|
/**
|
|
* Get the window presets.
|
|
* @return {Object} The window presets.
|
|
*/
|
|
this.getWindowPresets = function () {
|
|
return windowPresets;
|
|
};
|
|
|
|
/**
|
|
* Get the window presets names.
|
|
* @return {Object} The list of window presets names.
|
|
*/
|
|
this.getWindowPresetsNames = function () {
|
|
return Object.keys(windowPresets);
|
|
};
|
|
|
|
/**
|
|
* Set the window presets.
|
|
* @param {Object} presets The window presets.
|
|
*/
|
|
this.setWindowPresets = function (presets) {
|
|
windowPresets = presets;
|
|
};
|
|
|
|
/**
|
|
* Set the default colour map.
|
|
* @param {Object} map The colour map.
|
|
*/
|
|
this.setDefaultColourMap = function (map) {
|
|
colourMap = map;
|
|
};
|
|
|
|
/**
|
|
* Add window presets to the existing ones.
|
|
* @param {Object} presets The window presets.
|
|
* @param {Number} k The slice the preset belong to.
|
|
*/
|
|
this.addWindowPresets = function (presets, k) {
|
|
var keys = Object.keys(presets);
|
|
var key = null;
|
|
for (var i = 0; i < keys.length; ++i) {
|
|
key = keys[i];
|
|
if (typeof windowPresets[key] !== "undefined") {
|
|
if (typeof windowPresets[key].perslice !== "undefined" &&
|
|
windowPresets[key].perslice === true) {
|
|
// use first new preset wl...
|
|
windowPresets[key].wl.splice(k, 0, presets[key].wl[0]);
|
|
} else {
|
|
windowPresets[key] = presets[key];
|
|
}
|
|
} else {
|
|
// add new
|
|
windowPresets[key] = presets[key];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the colour map of the image.
|
|
* @return {Object} The colour map of the image.
|
|
*/
|
|
this.getColourMap = function() { return colourMap; };
|
|
/**
|
|
* Set the colour map of the image.
|
|
* @param {Object} map The colour map of the image.
|
|
*/
|
|
this.setColourMap = function(map) {
|
|
colourMap = map;
|
|
this.fireEvent({"type": "colour-change",
|
|
"wc": this.getCurrentWindowLut().getWindowLevel().getCenter(),
|
|
"ww": this.getCurrentWindowLut().getWindowLevel().getWidth() });
|
|
};
|
|
|
|
/**
|
|
* Get the current position.
|
|
* @return {Object} The current position.
|
|
*/
|
|
this.getCurrentPosition = function() {
|
|
// return a clone to avoid reference problems
|
|
return {"i": currentPosition.i, "j": currentPosition.j, "k": currentPosition.k};
|
|
};
|
|
/**
|
|
* Set the current position.
|
|
* @param {Object} pos The current position.
|
|
* @param {Boolean} silent If true, does not fire a slice-change event.
|
|
* @return {Boolean} False if not in bounds
|
|
*/
|
|
this.setCurrentPosition = function(pos, silent) {
|
|
// default silent flag to false
|
|
if ( typeof silent === "undefined" ) {
|
|
silent = false;
|
|
}
|
|
// check if possible
|
|
if( !image.getGeometry().getSize().isInBounds(pos.i,pos.j,pos.k) ) {
|
|
return false;
|
|
}
|
|
var oldPosition = currentPosition;
|
|
currentPosition = pos;
|
|
|
|
// fire a 'position-change' event
|
|
if( image.getPhotometricInterpretation().match(/MONOCHROME/) !== null )
|
|
{
|
|
this.fireEvent({"type": "position-change",
|
|
"i": pos.i, "j": pos.j, "k": pos.k,
|
|
"value": image.getRescaledValue(pos.i,pos.j,pos.k, this.getCurrentFrame())});
|
|
}
|
|
else
|
|
{
|
|
this.fireEvent({"type": "position-change",
|
|
"i": pos.i, "j": pos.j, "k": pos.k});
|
|
}
|
|
|
|
// fire a slice change event (used to trigger redraw)
|
|
if ( !silent ) {
|
|
if( oldPosition.k !== currentPosition.k ) {
|
|
this.fireEvent({"type": "slice-change"});
|
|
}
|
|
}
|
|
|
|
// all good
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Get the current frame number.
|
|
* @return {Number} The current frame number.
|
|
*/
|
|
this.getCurrentFrame = function() {
|
|
return currentFrame;
|
|
};
|
|
|
|
/**
|
|
* Set the current frame number.
|
|
* @param {Number} The current frame number.
|
|
* @return {Boolean} False if not in bounds
|
|
*/
|
|
this.setCurrentFrame = function (frame) {
|
|
// check if possible
|
|
if( frame < 0 || frame >= image.getNumberOfFrames() ) {
|
|
return false;
|
|
}
|
|
// assign
|
|
var oldFrame = currentFrame;
|
|
currentFrame = frame;
|
|
// fire event
|
|
if( oldFrame !== currentFrame && image.getNumberOfFrames() !== 1 ) {
|
|
this.fireEvent({"type": "frame-change", "frame": currentFrame});
|
|
// silent set current position to update info text
|
|
this.setCurrentPosition(this.getCurrentPosition(),true);
|
|
}
|
|
// all good
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Append another view to this one.
|
|
* @param {Object} rhs The view to append.
|
|
*/
|
|
this.append = function( rhs )
|
|
{
|
|
// append images
|
|
var newSliceNumber = this.getImage().appendSlice( rhs.getImage() );
|
|
// update position if a slice was appended before
|
|
if ( newSliceNumber <= this.getCurrentPosition().k ) {
|
|
this.setCurrentPosition(
|
|
{"i": this.getCurrentPosition().i,
|
|
"j": this.getCurrentPosition().j,
|
|
"k": this.getCurrentPosition().k + 1}, true );
|
|
}
|
|
// add window presets
|
|
this.addWindowPresets( rhs.getWindowPresets(), newSliceNumber );
|
|
};
|
|
|
|
/**
|
|
* Append a frame buffer to the included image.
|
|
* @param {Object} frameBuffer The frame buffer to append.
|
|
*/
|
|
this.appendFrameBuffer = function (frameBuffer)
|
|
{
|
|
this.getImage().appendFrameBuffer(frameBuffer);
|
|
};
|
|
|
|
/**
|
|
* Set the view window/level.
|
|
* @param {Number} center The window center.
|
|
* @param {Number} width The window width.
|
|
* @param {String} name Associated preset name, defaults to 'manual'.
|
|
* Warning: uses the latest set rescale LUT or the default linear one.
|
|
*/
|
|
this.setWindowLevel = function ( center, width, name )
|
|
{
|
|
// window width shall be >= 1 (see https://www.dabsoft.ch/dicom/3/C.11.2.1.2/)
|
|
if ( width >= 1 ) {
|
|
|
|
// get current window/level (before updating name)
|
|
var sliceNumber = this.getCurrentPosition().k;
|
|
var currentWl = null;
|
|
var rsi = image.getRescaleSlopeAndIntercept(sliceNumber);
|
|
if ( rsi && typeof rsi !== "undefined" ) {
|
|
var currentLut = windowLuts[ rsi.toString() ];
|
|
if ( currentLut && typeof currentLut !== "undefined") {
|
|
currentWl = currentLut.getWindowLevel();
|
|
}
|
|
}
|
|
|
|
if ( typeof name === "undefined" ) {
|
|
name = "manual";
|
|
}
|
|
// update current preset name
|
|
currentPresetName = name;
|
|
|
|
var wl = new dwv.image.WindowLevel(center, width);
|
|
var keys = Object.keys(windowLuts);
|
|
|
|
// create the first lut if none exists
|
|
if (keys.length === 0) {
|
|
// create the rescale lookup table
|
|
var rescaleLut = new dwv.image.lut.Rescale(
|
|
image.getRescaleSlopeAndIntercept(0), image.getMeta().BitsStored );
|
|
// create the window lookup table
|
|
var windowLut = new dwv.image.lut.Window(rescaleLut, image.getMeta().IsSigned);
|
|
this.addWindowLut(windowLut);
|
|
}
|
|
|
|
// set window level on luts
|
|
for ( var key in windowLuts ) {
|
|
windowLuts[key].setWindowLevel(wl);
|
|
}
|
|
|
|
// fire window level change event
|
|
if (currentWl && typeof currentWl !== "undefined") {
|
|
if (currentWl.getWidth() !== width) {
|
|
this.fireEvent({"type": "wl-width-change", "wc": center, "ww": width });
|
|
}
|
|
if (currentWl.getCenter() !== center) {
|
|
this.fireEvent({"type": "wl-center-change", "wc": center, "ww": width });
|
|
}
|
|
} else {
|
|
this.fireEvent({"type": "wl-width-change", "wc": center, "ww": width });
|
|
this.fireEvent({"type": "wl-center-change", "wc": center, "ww": width });
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the window level to the preset with the input name.
|
|
* @param {String} name The name of the preset to activate.
|
|
*/
|
|
this.setWindowLevelPreset = function (name) {
|
|
var preset = this.getWindowPresets()[name];
|
|
if ( typeof preset === "undefined" ) {
|
|
throw new Error("Unknown window level preset: '" + name + "'");
|
|
}
|
|
// special min/max
|
|
if (name === "minmax" && typeof preset.wl === "undefined") {
|
|
preset.wl = this.getWindowLevelMinMax();
|
|
}
|
|
// special 'perslice' case
|
|
if (typeof preset.perslice !== "undefined" &&
|
|
preset.perslice === true) {
|
|
preset = { "wl": preset.wl[this.getCurrentPosition().k] };
|
|
}
|
|
// set w/l
|
|
this.setWindowLevel( preset.wl.getCenter(), preset.wl.getWidth(), name );
|
|
};
|
|
|
|
/**
|
|
* Set the window level to the preset with the input id.
|
|
* @param {Number} id The id of the preset to activate.
|
|
*/
|
|
this.setWindowLevelPresetById = function (id) {
|
|
var keys = Object.keys(this.getWindowPresets());
|
|
this.setWindowLevelPreset( keys[id] );
|
|
};
|
|
|
|
/**
|
|
* Clone the image using all meta data and the original data buffer.
|
|
* @return {View} A full copy of this {dwv.image.View}.
|
|
*/
|
|
this.clone = function ()
|
|
{
|
|
var copy = new dwv.image.View(this.getImage());
|
|
for ( var key in windowLuts ) {
|
|
copy.addWindowLut(windowLuts[key]);
|
|
}
|
|
copy.setListeners(this.getListeners());
|
|
return copy;
|
|
};
|
|
|
|
/**
|
|
* View listeners
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var listeners = {};
|
|
/**
|
|
* Get the view listeners.
|
|
* @return {Object} The view listeners.
|
|
*/
|
|
this.getListeners = function() { return listeners; };
|
|
/**
|
|
* Set the view listeners.
|
|
* @param {Object} list The view listeners.
|
|
*/
|
|
this.setListeners = function(list) { listeners = list; };
|
|
};
|
|
|
|
/**
|
|
* Get the image window/level that covers the full data range.
|
|
* Warning: uses the latest set rescale LUT or the default linear one.
|
|
*/
|
|
dwv.image.View.prototype.getWindowLevelMinMax = function ()
|
|
{
|
|
var range = this.getImage().getRescaledDataRange();
|
|
var min = range.min;
|
|
var max = range.max;
|
|
var width = max - min;
|
|
var center = min + width/2;
|
|
return new dwv.image.WindowLevel(center, width);
|
|
};
|
|
|
|
/**
|
|
* Set the image window/level to cover the full data range.
|
|
* Warning: uses the latest set rescale LUT or the default linear one.
|
|
*/
|
|
dwv.image.View.prototype.setWindowLevelMinMax = function()
|
|
{
|
|
// calculate center and width
|
|
var wl = this.getWindowLevelMinMax();
|
|
// set window level
|
|
this.setWindowLevel(wl.getCenter(), wl.getWidth(), "minmax");
|
|
};
|
|
|
|
/**
|
|
* Generate display image data to be given to a canvas.
|
|
* @param {Array} array The array to fill in.
|
|
*/
|
|
dwv.image.View.prototype.generateImageData = function( array )
|
|
{
|
|
var windowLut = this.getCurrentWindowLut();
|
|
|
|
var image = this.getImage();
|
|
var sliceSize = image.getGeometry().getSize().getSliceSize();
|
|
var sliceOffset = sliceSize * this.getCurrentPosition().k;
|
|
var frame = (this.getCurrentFrame()) ? this.getCurrentFrame() : 0;
|
|
|
|
var index = 0;
|
|
var pxValue = 0;
|
|
var stepPos = 0;
|
|
|
|
var photoInterpretation = image.getPhotometricInterpretation();
|
|
switch (photoInterpretation)
|
|
{
|
|
case "MONOCHROME1":
|
|
case "MONOCHROME2":
|
|
var colourMap = this.getColourMap();
|
|
var iMax = sliceOffset + sliceSize;
|
|
for(var i=sliceOffset; i < iMax; ++i)
|
|
{
|
|
pxValue = parseInt( windowLut.getValue(
|
|
image.getValueAtOffset(i, frame) ), 10 );
|
|
array.data[index] = colourMap.red[pxValue];
|
|
array.data[index+1] = colourMap.green[pxValue];
|
|
array.data[index+2] = colourMap.blue[pxValue];
|
|
array.data[index+3] = 0xff;
|
|
index += 4;
|
|
}
|
|
break;
|
|
|
|
case "RGB":
|
|
// 3 times bigger...
|
|
sliceOffset *= 3;
|
|
// the planar configuration defines the memory layout
|
|
var planarConfig = image.getPlanarConfiguration();
|
|
if( planarConfig !== 0 && planarConfig !== 1 ) {
|
|
throw new Error("Unsupported planar configuration: "+planarConfig);
|
|
}
|
|
// default: RGBRGBRGBRGB...
|
|
var posR = sliceOffset;
|
|
var posG = sliceOffset + 1;
|
|
var posB = sliceOffset + 2;
|
|
stepPos = 3;
|
|
// RRRR...GGGG...BBBB...
|
|
if (planarConfig === 1) {
|
|
posR = sliceOffset;
|
|
posG = sliceOffset + sliceSize;
|
|
posB = sliceOffset + 2 * sliceSize;
|
|
stepPos = 1;
|
|
}
|
|
|
|
for(var j=0; j < sliceSize; ++j)
|
|
{
|
|
array.data[index] = parseInt( windowLut.getValue(
|
|
image.getValueAtOffset(posR, frame) ), 10 );
|
|
array.data[index+1] = parseInt( windowLut.getValue(
|
|
image.getValueAtOffset(posG, frame) ), 10 );
|
|
array.data[index+2] = parseInt( windowLut.getValue(
|
|
image.getValueAtOffset(posB, frame) ), 10 );
|
|
array.data[index+3] = 0xff;
|
|
index += 4;
|
|
|
|
posR += stepPos;
|
|
posG += stepPos;
|
|
posB += stepPos;
|
|
}
|
|
break;
|
|
|
|
case "YBR_FULL_422":
|
|
// theory:
|
|
// http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.7.html#sect_C.7.6.3.1.2
|
|
// reverse equation:
|
|
// https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
|
|
|
|
// 3 times bigger...
|
|
sliceOffset *= 3;
|
|
// the planar configuration defines the memory layout
|
|
var planarConfigYBR = image.getPlanarConfiguration();
|
|
if( planarConfigYBR !== 0 && planarConfigYBR !== 1 ) {
|
|
throw new Error("Unsupported planar configuration: "+planarConfigYBR);
|
|
}
|
|
// default: YBRYBRYBR...
|
|
var posY = sliceOffset;
|
|
var posCB = sliceOffset + 1;
|
|
var posCR = sliceOffset + 2;
|
|
stepPos = 3;
|
|
// YYYY...BBBB...RRRR...
|
|
if (planarConfigYBR === 1) {
|
|
posY = sliceOffset;
|
|
posCB = sliceOffset + sliceSize;
|
|
posCR = sliceOffset + 2 * sliceSize;
|
|
stepPos = 1;
|
|
}
|
|
|
|
var y, cb, cr;
|
|
var r, g, b;
|
|
for (var k=0; k < sliceSize; ++k)
|
|
{
|
|
y = image.getValueAtOffset(posY, frame);
|
|
cb = image.getValueAtOffset(posCB, frame);
|
|
cr = image.getValueAtOffset(posCR, frame);
|
|
|
|
r = y + 1.402 * (cr - 128);
|
|
g = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128);
|
|
b = y + 1.772 * (cb - 128);
|
|
|
|
array.data[index] = parseInt( windowLut.getValue(r), 10 );
|
|
array.data[index+1] = parseInt( windowLut.getValue(g), 10 );
|
|
array.data[index+2] = parseInt( windowLut.getValue(b), 10 );
|
|
array.data[index+3] = 0xff;
|
|
index += 4;
|
|
|
|
posY += stepPos;
|
|
posCB += stepPos;
|
|
posCR += stepPos;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new Error("Unsupported photometric interpretation: "+photoInterpretation);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add an event listener on the view.
|
|
* @param {String} type The event type.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
dwv.image.View.prototype.addEventListener = function(type, listener)
|
|
{
|
|
var listeners = this.getListeners();
|
|
if( !listeners[type] ) {
|
|
listeners[type] = [];
|
|
}
|
|
listeners[type].push(listener);
|
|
};
|
|
|
|
/**
|
|
* Remove an event listener on the view.
|
|
* @param {String} type The event type.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
dwv.image.View.prototype.removeEventListener = function(type, listener)
|
|
{
|
|
var listeners = this.getListeners();
|
|
if( !listeners[type] ) {
|
|
return;
|
|
}
|
|
for(var i=0; i < listeners[type].length; ++i)
|
|
{
|
|
if( listeners[type][i] === listener ) {
|
|
listeners[type].splice(i,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Fire an event: call all associated listeners.
|
|
* @param {Object} event The event to fire.
|
|
*/
|
|
dwv.image.View.prototype.fireEvent = function(event)
|
|
{
|
|
var listeners = this.getListeners();
|
|
if( !listeners[event.type] ) {
|
|
return;
|
|
}
|
|
for(var i=0; i < listeners[event.type].length; ++i)
|
|
{
|
|
listeners[event.type][i](event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* View factory.
|
|
* @constructor
|
|
*/
|
|
dwv.image.ViewFactory = function () {};
|
|
|
|
/**
|
|
* Get an View object from the read DICOM file.
|
|
* @param {Object} dicomElements The DICOM tags.
|
|
* @param {Object} image The associated image.
|
|
* @return {View} The new View.
|
|
*/
|
|
dwv.image.ViewFactory.prototype.create = function (dicomElements, image)
|
|
{
|
|
// view
|
|
var view = new dwv.image.View(image);
|
|
|
|
// default color map
|
|
if( image.getPhotometricInterpretation() === "MONOCHROME1") {
|
|
view.setDefaultColourMap(dwv.image.lut.invPlain);
|
|
}
|
|
|
|
// presets
|
|
var windowPresets = {};
|
|
|
|
// DICOM presets
|
|
var windowCenter = dicomElements.getFromKey("x00281050", true);
|
|
var windowWidth = dicomElements.getFromKey("x00281051", true);
|
|
var windowCWExplanation = dicomElements.getFromKey("x00281055", true);
|
|
if ( windowCenter && windowWidth ) {
|
|
var name;
|
|
for ( var j = 0; j < windowCenter.length; ++j) {
|
|
var center = parseFloat( windowCenter[j], 10 );
|
|
var width = parseFloat( windowWidth[j], 10 );
|
|
if ( center && width ) {
|
|
name = "";
|
|
if ( windowCWExplanation ) {
|
|
name = dwv.dicom.cleanString(windowCWExplanation[j]);
|
|
}
|
|
if (name === "") {
|
|
name = "Default"+j;
|
|
}
|
|
windowPresets[name] = {
|
|
"wl": [new dwv.image.WindowLevel(center, width)],
|
|
"name": name,
|
|
"perslice": true};
|
|
}
|
|
}
|
|
}
|
|
|
|
// min/max
|
|
// Not filled yet since it is stil too costly to calculate min/max
|
|
// for each slice... It will be filled at first use (see view.setWindowLevelPreset).
|
|
// Order is important, if no wl from DICOM, this will be the default.
|
|
windowPresets.minmax = { "name": "minmax" };
|
|
|
|
// optional modality presets
|
|
if ( typeof dwv.tool.defaultpresets !== "undefined" ) {
|
|
var modality = image.getMeta().Modality;
|
|
for( var key in dwv.tool.defaultpresets[modality] ) {
|
|
var preset = dwv.tool.defaultpresets[modality][key];
|
|
windowPresets[key] = {
|
|
"wl": new dwv.image.WindowLevel(preset.center, preset.width),
|
|
"name": key};
|
|
}
|
|
}
|
|
|
|
// store
|
|
view.setWindowPresets( windowPresets );
|
|
|
|
return view;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
|
|
/**
|
|
* DICOM data loader.
|
|
*/
|
|
dwv.io.DicomDataLoader = function ()
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Loader options.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var options = {};
|
|
|
|
/**
|
|
* Loading flag.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isLoading = false;
|
|
|
|
/**
|
|
* Set the loader options.
|
|
* @param {Object} opt The input options.
|
|
*/
|
|
this.setOptions = function (opt) {
|
|
options = opt;
|
|
};
|
|
|
|
/**
|
|
* Is the load ongoing?
|
|
* @return {Boolean} True if loading.
|
|
*/
|
|
this.isLoading = function () {
|
|
return isLoading;
|
|
};
|
|
|
|
/**
|
|
* DICOM buffer to dwv.image.View (asynchronous)
|
|
*/
|
|
var db2v = new dwv.image.DicomBufferToView();
|
|
|
|
/**
|
|
* Load data.
|
|
* @param {Object} buffer The DICOM buffer.
|
|
* @param {String} origin The data origin.
|
|
* @param {Number} index The data index.
|
|
*/
|
|
this.load = function (buffer, origin, index) {
|
|
// set loading flag
|
|
isLoading = true;
|
|
// set character set
|
|
if (typeof options.defaultCharacterSet !== "undefined") {
|
|
db2v.setDefaultCharacterSet(options.defaultCharacterSet);
|
|
}
|
|
// connect handlers
|
|
db2v.onload = self.onload;
|
|
db2v.onloadend = function () {
|
|
// reset loading flag
|
|
isLoading = false;
|
|
// call listeners
|
|
self.onloadend();
|
|
};
|
|
db2v.onprogress = self.onprogress;
|
|
// convert
|
|
try {
|
|
db2v.convert( buffer, index );
|
|
} catch (error) {
|
|
// TODO: error will be for individual file, isLoading is global...
|
|
//isLoading = false;
|
|
self.onerror(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Abort load.
|
|
*/
|
|
this.abort = function () {
|
|
// abort conversion
|
|
db2v.abort();
|
|
// reset loading flag
|
|
isLoading = false;
|
|
// call listeners
|
|
self.onabort({message: "Abort while loading DICOM data."});
|
|
};
|
|
|
|
/**
|
|
* Get a file load handler.
|
|
* @param {Object} file The file to load.
|
|
* @param {Number} index The index 'id' of the file.
|
|
* @return {Function} A file load handler.
|
|
*/
|
|
this.getFileLoadHandler = function (file, index) {
|
|
return function (event) {
|
|
self.load(event.target.result, file, index);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get a url load handler.
|
|
* @param {String} url The url to load.
|
|
* @param {Number} index The index 'id' of the url.
|
|
* @return {Function} A url load handler.
|
|
*/
|
|
this.getUrlLoadHandler = function (url, index) {
|
|
return function (/*event*/) {
|
|
// check response status
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
|
|
// status 200: "OK"; status 0: "debug"
|
|
if (this.status !== 200 && this.status !== 0) {
|
|
self.onerror({'name': "RequestError",
|
|
'message': "Error status: " + this.status +
|
|
" while loading '" + url + "' [DicomDataLoader]" });
|
|
return;
|
|
}
|
|
// load
|
|
self.load(this.response, url, index);
|
|
};
|
|
};
|
|
|
|
}; // class DicomDataLoader
|
|
|
|
/**
|
|
* Check if the loader can load the provided file.
|
|
* @param {Object} file The file to check.
|
|
* @return True if the file can be loaded.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.canLoadFile = function (file) {
|
|
var split = file.name.split('.');
|
|
var ext = "";
|
|
if (split.length !== 1) {
|
|
ext = split.pop().toLowerCase();
|
|
}
|
|
var hasExt = (ext.length !== 0);
|
|
return !hasExt || (ext === "dcm");
|
|
};
|
|
|
|
/**
|
|
* Check if the loader can load the provided url.
|
|
* @param {String} url The url to check.
|
|
* @return True if the url can be loaded.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.canLoadUrl = function (url) {
|
|
var split = url.split('.');
|
|
var ext = "";
|
|
if (split.length !== 1) {
|
|
ext = split.pop().toLowerCase();
|
|
}
|
|
var hasExt = (ext.length !== 0) && (ext.length < 5);
|
|
// wado url
|
|
var isDicomContentType = (url.indexOf("contentType=application/dicom") !== -1);
|
|
|
|
return isDicomContentType || (ext === "dcm") || !hasExt;
|
|
};
|
|
|
|
/**
|
|
* Get the file content type needed by the loader.
|
|
* @return One of the 'dwv.io.fileContentTypes'.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.loadFileAs = function () {
|
|
return dwv.io.fileContentTypes.ArrayBuffer;
|
|
};
|
|
|
|
/**
|
|
* Get the url content type needed by the loader.
|
|
* @return One of the 'dwv.io.urlContentTypes'.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.loadUrlAs = function () {
|
|
return dwv.io.urlContentTypes.ArrayBuffer;
|
|
};
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle an load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.DicomDataLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Add to Loader list.
|
|
*/
|
|
dwv.io.loaderList = dwv.io.loaderList || [];
|
|
dwv.io.loaderList.push( "DicomDataLoader" );
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.io = dwv.io || {};
|
|
|
|
// file content types
|
|
dwv.io.fileContentTypes = {
|
|
'Text': 0,
|
|
'ArrayBuffer': 1,
|
|
'DataURL': 2
|
|
};
|
|
|
|
/**
|
|
* Files loader.
|
|
* @constructor
|
|
*/
|
|
dwv.io.FilesLoader = function ()
|
|
{
|
|
/**
|
|
* Closure to self.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var self = this;
|
|
|
|
/**
|
|
* Array of launched readers (used in abort).
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var readers = [];
|
|
|
|
/**
|
|
* Launched loader (used in abort).
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var runningLoader = [];
|
|
|
|
/**
|
|
* Number of data to load.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var nToLoad = 0;
|
|
/**
|
|
* Number of loaded data.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var nLoaded = 0;
|
|
|
|
/**
|
|
* The default character set (optional).
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var defaultCharacterSet;
|
|
|
|
/**
|
|
* Get the default character set.
|
|
* @return {String} The default character set.
|
|
*/
|
|
this.getDefaultCharacterSet = function () {
|
|
return defaultCharacterSet;
|
|
};
|
|
|
|
/**
|
|
* Set the default character set.
|
|
* @param {String} characterSet The character set.
|
|
*/
|
|
this.setDefaultCharacterSet = function (characterSet) {
|
|
defaultCharacterSet = characterSet;
|
|
};
|
|
|
|
/**
|
|
* Store a launched reader.
|
|
* @param {Object} request The launched reader.
|
|
*/
|
|
this.storeReader = function (reader) {
|
|
readers.push(reader);
|
|
};
|
|
|
|
/**
|
|
* Clear the stored readers.
|
|
*/
|
|
this.clearStoredReaders = function () {
|
|
readers = [];
|
|
};
|
|
|
|
/**
|
|
* Store the launched loader.
|
|
* @param {Object} loader The launched loader.
|
|
*/
|
|
this.storeLoader = function (loader) {
|
|
runningLoader = loader;
|
|
};
|
|
|
|
/**
|
|
* Clear the stored loader.
|
|
*/
|
|
this.clearStoredLoader = function () {
|
|
runningLoader = null;
|
|
};
|
|
|
|
/**
|
|
* Abort a URLs load.
|
|
*/
|
|
this.abort = function () {
|
|
// abort readers
|
|
for ( var i = 0; i < readers.length; ++i ) {
|
|
// 0: EMPTY, 1: LOADING, 2: DONE
|
|
if ( readers[i].readyState === 1 ) {
|
|
readers[i].abort();
|
|
}
|
|
}
|
|
this.clearStoredReaders();
|
|
// abort loader
|
|
if ( runningLoader ) {
|
|
runningLoader.abort();
|
|
}
|
|
this.clearStoredLoader();
|
|
};
|
|
|
|
/**
|
|
* Set the number of data to load.
|
|
* @param {Number} n The number of data to load.
|
|
*/
|
|
this.setNToLoad = function (n) {
|
|
nToLoad = n;
|
|
};
|
|
|
|
/**
|
|
* Increment the number of loaded data
|
|
* and call onloadend if loaded all data.
|
|
*/
|
|
this.addLoaded = function () {
|
|
nLoaded++;
|
|
if ( nLoaded === nToLoad ) {
|
|
self.onloadend();
|
|
}
|
|
};
|
|
|
|
}; // class File
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.FilesLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle a load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.FilesLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.FilesLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.FilesLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.FilesLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Load a list of files.
|
|
* @param {Array} ioArray The list of files to load.
|
|
* @external FileReader
|
|
*/
|
|
dwv.io.FilesLoader.prototype.load = function (ioArray)
|
|
{
|
|
// clear storage
|
|
this.clearStoredReaders();
|
|
this.clearStoredLoader();
|
|
|
|
// closure to self for handlers
|
|
var self = this;
|
|
// set the number of data to load
|
|
this.setNToLoad( ioArray.length );
|
|
|
|
var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress);
|
|
mproghandler.setNToLoad( ioArray.length );
|
|
|
|
// get loaders
|
|
var loaders = [];
|
|
for (var m = 0; m < dwv.io.loaderList.length; ++m) {
|
|
loaders.push( new dwv.io[dwv.io.loaderList[m]]() );
|
|
}
|
|
|
|
// set loaders callbacks
|
|
var loader = null;
|
|
for (var k = 0; k < loaders.length; ++k) {
|
|
loader = loaders[k];
|
|
loader.onload = self.onload;
|
|
loader.onloadend = self.addLoaded;
|
|
loader.onerror = self.onerror;
|
|
loader.onabort = self.onabort;
|
|
loader.setOptions({
|
|
'defaultCharacterSet': this.getDefaultCharacterSet()
|
|
});
|
|
loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1);
|
|
}
|
|
|
|
// request onerror handler
|
|
var getReaderOnError = function (origin) {
|
|
return function (event) {
|
|
var message = "An error occurred while reading '" + origin + "'";
|
|
if (typeof event.getMessage !== "undefined") {
|
|
message += " (" + event.getMessage() + ")";
|
|
}
|
|
message += ".";
|
|
self.onerror( {'name': "FileReaderError", 'message': message } );
|
|
};
|
|
};
|
|
|
|
// request onabort handler
|
|
var getReaderOnAbort = function (origin) {
|
|
return function () {
|
|
self.onabort( {'message': "Abort while reading '" + origin + "'" } );
|
|
};
|
|
};
|
|
|
|
// loop on I/O elements
|
|
for (var i = 0; i < ioArray.length; ++i)
|
|
{
|
|
var file = ioArray[i];
|
|
var reader = new FileReader();
|
|
|
|
// store reader
|
|
this.storeReader(reader);
|
|
|
|
// bind reader progress
|
|
reader.onprogress = mproghandler.getMonoProgressHandler(i, 0);
|
|
|
|
// find a loader
|
|
var foundLoader = false;
|
|
for (var l = 0; l < loaders.length; ++l) {
|
|
loader = loaders[l];
|
|
if (loader.canLoadFile(file)) {
|
|
foundLoader = true;
|
|
// store loader
|
|
this.storeLoader(loader);
|
|
// set reader callbacks
|
|
reader.onload = loader.getFileLoadHandler(file, i);
|
|
reader.onerror = getReaderOnError(file.name);
|
|
reader.onabort = getReaderOnAbort(file.name);
|
|
// read
|
|
if (loader.loadFileAs() === dwv.io.fileContentTypes.Text) {
|
|
reader.readAsText(file);
|
|
} else if (loader.loadFileAs() === dwv.io.fileContentTypes.DataURL) {
|
|
reader.readAsDataURL(file);
|
|
} else if (loader.loadFileAs() === dwv.io.fileContentTypes.ArrayBuffer) {
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
// next file
|
|
break;
|
|
}
|
|
}
|
|
// TODO: throw?
|
|
if (!foundLoader) {
|
|
throw new Error("No loader found for file: "+file);
|
|
}
|
|
}
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
|
|
/**
|
|
* JSON text loader.
|
|
*/
|
|
dwv.io.JSONTextLoader = function ()
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Loading flag.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isLoading = false;
|
|
|
|
/**
|
|
* Set the loader options.
|
|
* @param {Object} opt The input options.
|
|
*/
|
|
this.setOptions = function () {
|
|
// does nothing
|
|
};
|
|
|
|
/**
|
|
* Is the load ongoing?
|
|
* @return {Boolean} True if loading.
|
|
*/
|
|
this.isLoading = function () {
|
|
return isLoading;
|
|
};
|
|
|
|
/**
|
|
* Load data.
|
|
* @param {Object} text The input text.
|
|
* @param {String} origin The data origin.
|
|
* @param {Number} index The data index.
|
|
*/
|
|
this.load = function (text, origin, index) {
|
|
// set loading flag
|
|
isLoading = true;
|
|
try {
|
|
self.onload( text );
|
|
// reset loading flag
|
|
isLoading = false;
|
|
// call listeners
|
|
self.onloadend();
|
|
} catch (error) {
|
|
self.onerror(error);
|
|
}
|
|
self.onprogress({'type': 'read-progress', 'lengthComputable': true,
|
|
'loaded': 100, 'total': 100, 'index': index});
|
|
};
|
|
|
|
/**
|
|
* Abort load: pass to listeners.
|
|
*/
|
|
this.abort = function () {
|
|
// reset loading flag
|
|
isLoading = false;
|
|
// call listeners
|
|
self.onabort();
|
|
};
|
|
|
|
/**
|
|
* Get a file load handler.
|
|
* @param {Object} file The file to load.
|
|
* @param {Number} index The index 'id' of the file.
|
|
* @return {Function} A file load handler.
|
|
*/
|
|
this.getFileLoadHandler = function (file, index) {
|
|
return function (event) {
|
|
self.load(event.target.result, file, index);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get a url load handler.
|
|
* @param {String} url The url to load.
|
|
* @param {Number} index The index 'id' of the url.
|
|
* @return {Function} A url load handler.
|
|
*/
|
|
this.getUrlLoadHandler = function (url, index) {
|
|
return function (/*event*/) {
|
|
// check response status
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
|
|
// status 200: "OK"; status 0: "debug"
|
|
if (this.status !== 200 && this.status !== 0) {
|
|
self.onerror({'name': "RequestError",
|
|
'message': "Error status: " + this.status +
|
|
" while loading '" + url + "' [JSONTextLoader]" });
|
|
return;
|
|
}
|
|
// load
|
|
self.load(this.responseText, url, index);
|
|
};
|
|
};
|
|
|
|
}; // class JSONTextLoader
|
|
|
|
/**
|
|
* Check if the loader can load the provided file.
|
|
* @param {Object} file The file to check.
|
|
* @return True if the file can be loaded.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.canLoadFile = function (file) {
|
|
var ext = file.name.split('.').pop().toLowerCase();
|
|
return (ext === "json");
|
|
};
|
|
|
|
/**
|
|
* Check if the loader can load the provided url.
|
|
* @param {String} url The url to check.
|
|
* @return True if the url can be loaded.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.canLoadUrl = function (url) {
|
|
var ext = url.split('.').pop().toLowerCase();
|
|
return (ext === "json");
|
|
};
|
|
|
|
/**
|
|
* Get the file content type needed by the loader.
|
|
* @return One of the 'dwv.io.fileContentTypes'.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.loadFileAs = function () {
|
|
return dwv.io.fileContentTypes.Text;
|
|
};
|
|
|
|
/**
|
|
* Get the url content type needed by the loader.
|
|
* @return One of the 'dwv.io.urlContentTypes'.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.loadUrlAs = function () {
|
|
return dwv.io.urlContentTypes.Text;
|
|
};
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle an load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.JSONTextLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Add to Loader list.
|
|
*/
|
|
dwv.io.loaderList = dwv.io.loaderList || [];
|
|
dwv.io.loaderList.push( "JSONTextLoader" );
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
|
|
/**
|
|
* Memory loader.
|
|
* @constructor
|
|
*/
|
|
dwv.io.MemoryLoader = function ()
|
|
{
|
|
/**
|
|
* Closure to self.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var self = this;
|
|
|
|
/**
|
|
* Launched loader (used in abort).
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var runningLoader = null;
|
|
|
|
/**
|
|
* Number of data to load.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var nToLoad = 0;
|
|
/**
|
|
* Number of loaded data.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var nLoaded = 0;
|
|
|
|
/**
|
|
* The default character set (optional).
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var defaultCharacterSet;
|
|
|
|
/**
|
|
* Get the default character set.
|
|
* @return {String} The default character set.
|
|
*/
|
|
this.getDefaultCharacterSet = function () {
|
|
return defaultCharacterSet;
|
|
};
|
|
|
|
/**
|
|
* Set the default character set.
|
|
* @param {String} characterSet The character set.
|
|
*/
|
|
this.setDefaultCharacterSet = function (characterSet) {
|
|
defaultCharacterSet = characterSet;
|
|
};
|
|
|
|
/**
|
|
* Store a launched loader.
|
|
* @param {Object} loader The launched loader.
|
|
*/
|
|
this.storeLoader = function (loader) {
|
|
runningLoader = loader;
|
|
};
|
|
|
|
/**
|
|
* Clear the stored loader.
|
|
*/
|
|
this.clearStoredLoader = function () {
|
|
runningLoader = null;
|
|
};
|
|
|
|
/**
|
|
* Abort a memory load.
|
|
*/
|
|
this.abort = function () {
|
|
// abort loader
|
|
runningLoader.abort();
|
|
this.clearStoredLoaders();
|
|
};
|
|
|
|
/**
|
|
* Set the number of data to load.
|
|
* @param {Number} n The number of data to load.
|
|
*/
|
|
this.setNToLoad = function (n) {
|
|
nToLoad = n;
|
|
};
|
|
|
|
/**
|
|
* Increment the number of loaded data
|
|
* and call onloadend if loaded all data.
|
|
*/
|
|
this.addLoaded = function () {
|
|
nLoaded++;
|
|
if ( nLoaded === nToLoad ) {
|
|
self.onloadend();
|
|
}
|
|
};
|
|
|
|
}; // class Memory
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.MemoryLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle a load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.MemoryLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.MemoryLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.MemoryLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.MemoryLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Load a list of buffers.
|
|
* @param {Array} ioArray The list of buffers to load.
|
|
*/
|
|
dwv.io.MemoryLoader.prototype.load = function (ioArray)
|
|
{
|
|
// clear storage
|
|
this.clearStoredLoader();
|
|
|
|
// closure to self for handlers
|
|
var self = this;
|
|
// set the number of data to load
|
|
this.setNToLoad( ioArray.length );
|
|
|
|
var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress);
|
|
mproghandler.setNToLoad( ioArray.length );
|
|
|
|
// get loaders
|
|
var loaders = [];
|
|
for (var m = 0; m < dwv.io.loaderList.length; ++m) {
|
|
loaders.push( new dwv.io[dwv.io.loaderList[m]]() );
|
|
}
|
|
|
|
// set loaders callbacks
|
|
var loader = null;
|
|
for (var k = 0; k < loaders.length; ++k) {
|
|
loader = loaders[k];
|
|
loader.onload = self.onload;
|
|
loader.onloadend = self.addLoaded;
|
|
loader.onerror = self.onerror;
|
|
loader.onabort = self.onabort;
|
|
loader.setOptions({
|
|
'defaultCharacterSet': this.getDefaultCharacterSet()
|
|
});
|
|
loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1);
|
|
}
|
|
|
|
// loop on I/O elements
|
|
for (var i = 0; i < ioArray.length; ++i)
|
|
{
|
|
var iodata = ioArray[i];
|
|
|
|
// find a loader
|
|
var foundLoader = false;
|
|
for (var l = 0; l < loaders.length; ++l) {
|
|
loader = loaders[l];
|
|
if (loader.canLoadUrl(iodata.filename)) {
|
|
foundLoader = true;
|
|
// store loader
|
|
this.storeLoader(loader);
|
|
// read
|
|
loader.load(iodata.data, iodata.filename, i);
|
|
// next file
|
|
break;
|
|
}
|
|
}
|
|
// TODO: throw?
|
|
if (!foundLoader) {
|
|
throw new Error("No loader found for file: "+iodata.filename);
|
|
}
|
|
}
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
|
|
/**
|
|
* Raw image loader.
|
|
*/
|
|
dwv.io.RawImageLoader = function ()
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Set the loader options.
|
|
* @param {Object} opt The input options.
|
|
*/
|
|
this.setOptions = function () {
|
|
// does nothing
|
|
};
|
|
|
|
/**
|
|
* Is the load ongoing? TODO...
|
|
* @return {Boolean} True if loading.
|
|
*/
|
|
this.isLoading = function () {
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Create a Data URI from an HTTP request response.
|
|
* @param {Object} response The HTTP request response.
|
|
* @param {String} dataType The data type.
|
|
*/
|
|
function createDataUri(response, dataType) {
|
|
// image data as string
|
|
var bytes = new Uint8Array(response);
|
|
var imageDataStr = '';
|
|
for( var i = 0; i < bytes.byteLength; ++i ) {
|
|
imageDataStr += String.fromCharCode(bytes[i]);
|
|
}
|
|
// image type
|
|
var imageType = dataType;
|
|
if (imageType === "jpg") {
|
|
imageType = "jpeg";
|
|
}
|
|
// create uri
|
|
var uri = "data:image/" + imageType + ";base64," + window.btoa(imageDataStr);
|
|
return uri;
|
|
}
|
|
|
|
/**
|
|
* Load data.
|
|
* @param {Object} dataUri The data URI.
|
|
* @param {String} origin The data origin.
|
|
* @param {Number} index The data index.
|
|
*/
|
|
this.load = function ( dataUri, origin, index ) {
|
|
// create a DOM image
|
|
var image = new Image();
|
|
image.src = dataUri;
|
|
// storing values to pass them on
|
|
image.origin = origin;
|
|
image.index = index;
|
|
// triggered by ctx.drawImage
|
|
image.onload = function (/*event*/) {
|
|
try {
|
|
self.onload( dwv.image.getViewFromDOMImage(this) );
|
|
self.onloadend();
|
|
} catch (error) {
|
|
self.onerror(error);
|
|
}
|
|
self.onprogress({'type': 'read-progress', 'lengthComputable': true,
|
|
'loaded': 100, 'total': 100, 'index': index});
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Abort load. TODO...
|
|
*/
|
|
this.abort = function () {
|
|
self.onabort();
|
|
};
|
|
|
|
/**
|
|
* Get a file load handler.
|
|
* @param {Object} file The file to load.
|
|
* @param {Number} index The index 'id' of the file.
|
|
* @return {Function} A file load handler.
|
|
*/
|
|
this.getFileLoadHandler = function (file, index) {
|
|
return function (event) {
|
|
self.load(event.target.result, file, index);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get a url load handler.
|
|
* @param {String} url The url to load.
|
|
* @param {Number} index The index 'id' of the url.
|
|
* @return {Function} A url load handler.
|
|
*/
|
|
this.getUrlLoadHandler = function (url, index) {
|
|
return function (/*event*/) {
|
|
// check response status
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
|
|
// status 200: "OK"; status 0: "debug"
|
|
if (this.status !== 200 && this.status !== 0) {
|
|
self.onerror({'name': "RequestError",
|
|
'message': "Error status: " + this.status +
|
|
" while loading '" + url + "' [RawImageLoader]" });
|
|
return;
|
|
}
|
|
// load
|
|
var ext = url.split('.').pop().toLowerCase();
|
|
self.load(createDataUri(this.response, ext), url, index);
|
|
};
|
|
};
|
|
|
|
}; // class RawImageLoader
|
|
|
|
/**
|
|
* Check if the loader can load the provided file.
|
|
* @param {Object} file The file to check.
|
|
* @return True if the file can be loaded.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.canLoadFile = function (file) {
|
|
return file.type.match("image.*");
|
|
};
|
|
|
|
/**
|
|
* Check if the loader can load the provided url.
|
|
* @param {String} url The url to check.
|
|
* @return True if the url can be loaded.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.canLoadUrl = function (url) {
|
|
var ext = url.split('.').pop().toLowerCase();
|
|
var hasImageExt = (ext === "jpeg") || (ext === "jpg") ||
|
|
(ext === "png") || (ext === "gif");
|
|
// wado url
|
|
var isImageContentType = (url.indexOf("contentType=image/jpeg") !== -1) ||
|
|
(url.indexOf("contentType=image/png") !== -1) ||
|
|
(url.indexOf("contentType=image/gif") !== -1);
|
|
|
|
return isImageContentType || hasImageExt;
|
|
};
|
|
|
|
/**
|
|
* Get the file content type needed by the loader.
|
|
* @return One of the 'dwv.io.fileContentTypes'.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.loadFileAs = function () {
|
|
return dwv.io.fileContentTypes.DataURL;
|
|
};
|
|
|
|
/**
|
|
* Get the url content type needed by the loader.
|
|
* @return One of the 'dwv.io.urlContentTypes'.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.loadUrlAs = function () {
|
|
return dwv.io.urlContentTypes.ArrayBuffer;
|
|
};
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle an load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawImageLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Add to Loader list.
|
|
*/
|
|
dwv.io.loaderList = dwv.io.loaderList || [];
|
|
dwv.io.loaderList.push( "RawImageLoader" );
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
|
|
/**
|
|
* Raw video loader.
|
|
* url example (cors enabled):
|
|
* https://raw.githubusercontent.com/clappr/clappr/master/test/fixtures/SampleVideo_360x240_1mb.mp4
|
|
*/
|
|
dwv.io.RawVideoLoader = function ()
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Set the loader options.
|
|
* @param {Object} opt The input options.
|
|
*/
|
|
this.setOptions = function () {
|
|
// does nothing
|
|
};
|
|
|
|
/**
|
|
* Is the load ongoing? TODO...
|
|
* @return {Boolean} True if loading.
|
|
*/
|
|
this.isLoading = function () {
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Create a Data URI from an HTTP request response.
|
|
* @param {Object} response The HTTP request response.
|
|
* @param {String} dataType The data type.
|
|
*/
|
|
function createDataUri(response, dataType) {
|
|
// image data as string
|
|
var bytes = new Uint8Array(response);
|
|
var videoDataStr = '';
|
|
for( var i = 0; i < bytes.byteLength; ++i ) {
|
|
videoDataStr += String.fromCharCode(bytes[i]);
|
|
}
|
|
// create uri
|
|
var uri = "data:video/" + dataType + ";base64," + window.btoa(videoDataStr);
|
|
return uri;
|
|
}
|
|
|
|
/**
|
|
* Internal Data URI load.
|
|
* @param {Object} dataUri The data URI.
|
|
* @param {String} origin The data origin.
|
|
* @param {Number} index The data index.
|
|
*/
|
|
this.load = function ( dataUri, origin, index ) {
|
|
// create a DOM video
|
|
var video = document.createElement('video');
|
|
video.src = dataUri;
|
|
// storing values to pass them on
|
|
video.file = origin;
|
|
video.index = index;
|
|
// onload handler
|
|
video.onloadedmetadata = function (/*event*/) {
|
|
try {
|
|
dwv.image.getViewFromDOMVideo(this,
|
|
self.onload, self.onprogress, self.onloadend, index);
|
|
} catch (error) {
|
|
self.onerror(error);
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Abort load. TODO...
|
|
*/
|
|
this.abort = function () {
|
|
self.onabort();
|
|
};
|
|
|
|
/**
|
|
* Get a file load handler.
|
|
* @param {Object} file The file to load.
|
|
* @param {Number} index The index 'id' of the file.
|
|
* @return {Function} A file load handler.
|
|
*/
|
|
this.getFileLoadHandler = function (file, index) {
|
|
return function (event) {
|
|
self.load(event.target.result, file, index);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get a url load handler.
|
|
* @param {String} url The url to load.
|
|
* @param {Number} index The index 'id' of the url.
|
|
* @return {Function} A url load handler.
|
|
*/
|
|
this.getUrlLoadHandler = function (url, index) {
|
|
return function (/*event*/) {
|
|
// check response status
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
|
|
// status 200: "OK"; status 0: "debug"
|
|
if (this.status !== 200 && this.status !== 0) {
|
|
self.onerror({'name': "RequestError",
|
|
'message': "Error status: " + this.status +
|
|
" while loading '" + url + "' [RawVideoLoader]" });
|
|
return;
|
|
}
|
|
// load
|
|
var ext = url.split('.').pop().toLowerCase();
|
|
self.load(createDataUri(this.response, ext), url, index);
|
|
};
|
|
};
|
|
|
|
}; // class RawVideoLoader
|
|
|
|
/**
|
|
* Check if the loader can load the provided file.
|
|
* @param {Object} file The file to check.
|
|
* @return True if the file can be loaded.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.canLoadFile = function (file) {
|
|
return file.type.match("video.*");
|
|
};
|
|
|
|
/**
|
|
* Check if the loader can load the provided url.
|
|
* @param {String} url The url to check.
|
|
* @return True if the url can be loaded.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.canLoadUrl = function (url) {
|
|
var ext = url.split('.').pop().toLowerCase();
|
|
return (ext === "mp4") || (ext === "ogg") ||
|
|
(ext === "webm");
|
|
};
|
|
|
|
/**
|
|
* Get the file content type needed by the loader.
|
|
* @return One of the 'dwv.io.fileContentTypes'.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.loadFileAs = function () {
|
|
return dwv.io.fileContentTypes.DataURL;
|
|
};
|
|
|
|
/**
|
|
* Get the url content type needed by the loader.
|
|
* @return One of the 'dwv.io.urlContentTypes'.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.loadUrlAs = function () {
|
|
return dwv.io.urlContentTypes.ArrayBuffer;
|
|
};
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle an load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.RawVideoLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Add to Loader list.
|
|
*/
|
|
dwv.io.loaderList = dwv.io.loaderList || [];
|
|
dwv.io.loaderList.push( "RawVideoLoader" );
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
|
|
// url content types
|
|
dwv.io.urlContentTypes = {
|
|
'Text': 0,
|
|
'ArrayBuffer': 1,
|
|
'oups': 2
|
|
};
|
|
|
|
/**
|
|
* Urls loader.
|
|
* @constructor
|
|
*/
|
|
dwv.io.UrlsLoader = function ()
|
|
{
|
|
/**
|
|
* Closure to self.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var self = this;
|
|
|
|
/**
|
|
* Array of launched requests used in abort.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var requests = [];
|
|
|
|
/**
|
|
* Launched loader used in abort.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var runningLoader = null;
|
|
|
|
/**
|
|
* Number of data to load.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var nToLoad = 0;
|
|
/**
|
|
* Number of loaded data.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var nLoaded = 0;
|
|
|
|
/**
|
|
* The default character set (optional).
|
|
* @private
|
|
* @type String
|
|
*/
|
|
var defaultCharacterSet;
|
|
|
|
/**
|
|
* Get the default character set.
|
|
* @return {String} The default character set.
|
|
*/
|
|
this.getDefaultCharacterSet = function () {
|
|
return defaultCharacterSet;
|
|
};
|
|
|
|
/**
|
|
* Set the default character set.
|
|
* @param {String} characterSet The character set.
|
|
*/
|
|
this.setDefaultCharacterSet = function (characterSet) {
|
|
defaultCharacterSet = characterSet;
|
|
};
|
|
|
|
/**
|
|
* Store a launched request.
|
|
* @param {Object} request The launched request.
|
|
*/
|
|
this.storeRequest = function (request) {
|
|
requests.push(request);
|
|
};
|
|
|
|
/**
|
|
* Clear the stored requests.
|
|
*/
|
|
this.clearStoredRequests = function () {
|
|
requests = [];
|
|
};
|
|
|
|
/**
|
|
* Store a launched loader.
|
|
* @param {Object} loader The launched loader.
|
|
*/
|
|
this.storeLoader = function (loader) {
|
|
runningLoader = loader;
|
|
};
|
|
|
|
/**
|
|
* Clear the stored loader.
|
|
*/
|
|
this.clearStoredLoader = function () {
|
|
runningLoader = null;
|
|
};
|
|
|
|
/**
|
|
* Abort a URLs load.
|
|
*/
|
|
this.abort = function () {
|
|
// abort requests
|
|
for ( var i = 0; i < requests.length; ++i ) {
|
|
// 0: UNSENT, 1: OPENED, 2: HEADERS_RECEIVED (send()), 3: LOADING, 4: DONE
|
|
if ( requests[i].readyState === 2 || requests[i].readyState === 3 ) {
|
|
requests[i].abort();
|
|
}
|
|
}
|
|
this.clearStoredRequests();
|
|
// abort loader
|
|
if ( runningLoader && runningLoader.isLoading() ) {
|
|
runningLoader.abort();
|
|
}
|
|
this.clearStoredLoader();
|
|
};
|
|
|
|
/**
|
|
* Set the number of data to load.
|
|
* @param {Number} n The number of data to load.
|
|
*/
|
|
this.setNToLoad = function (n) {
|
|
nToLoad = n;
|
|
};
|
|
|
|
/**
|
|
* Increment the number of loaded data
|
|
* and call onloadend if loaded all data.
|
|
*/
|
|
this.addLoaded = function () {
|
|
nLoaded++;
|
|
if ( nLoaded === nToLoad ) {
|
|
self.onloadend();
|
|
}
|
|
};
|
|
|
|
}; // class Url
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.UrlsLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle a load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.UrlsLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.UrlsLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.UrlsLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.UrlsLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Load a list of URLs.
|
|
* @param {Array} ioArray The list of urls to load.
|
|
* @param {Object} options Load options.
|
|
* @external XMLHttpRequest
|
|
*/
|
|
dwv.io.UrlsLoader.prototype.load = function (ioArray, options)
|
|
{
|
|
// clear storage
|
|
this.clearStoredRequests();
|
|
this.clearStoredLoader();
|
|
|
|
// closure to self for handlers
|
|
var self = this;
|
|
// set the number of data to load
|
|
this.setNToLoad( ioArray.length );
|
|
|
|
var mproghandler = new dwv.utils.MultiProgressHandler(self.onprogress);
|
|
mproghandler.setNToLoad( ioArray.length );
|
|
|
|
// get loaders
|
|
var loaders = [];
|
|
for (var m = 0; m < dwv.io.loaderList.length; ++m) {
|
|
loaders.push( new dwv.io[dwv.io.loaderList[m]]() );
|
|
}
|
|
|
|
// set loaders callbacks
|
|
var loader = null;
|
|
for (var k = 0; k < loaders.length; ++k) {
|
|
loader = loaders[k];
|
|
loader.onload = self.onload;
|
|
loader.onloadend = self.addLoaded;
|
|
loader.onerror = self.onerror;
|
|
loader.onabort = self.onabort;
|
|
loader.setOptions({
|
|
'defaultCharacterSet': this.getDefaultCharacterSet()
|
|
});
|
|
loader.onprogress = mproghandler.getUndefinedMonoProgressHandler(1);
|
|
}
|
|
|
|
// request onerror handler
|
|
var getRequestOnError = function (origin) {
|
|
return function (/*event*/) {
|
|
var message = "An error occurred while downloading '" + origin + "'";
|
|
if (typeof this.status !== "undefined") {
|
|
message += " (http status: " + this.status + ")";
|
|
}
|
|
message += ".";
|
|
self.onerror( {'name': "RequestError", 'message': message } );
|
|
};
|
|
};
|
|
|
|
// request onabort handler
|
|
var getRequestOnAbort = function (origin) {
|
|
return function () {
|
|
self.onabort( {'message': "Abort while downloading '" + origin + "'." } );
|
|
};
|
|
};
|
|
|
|
// loop on I/O elements
|
|
for (var i = 0; i < ioArray.length; ++i)
|
|
{
|
|
var url = ioArray[i];
|
|
var request = new XMLHttpRequest();
|
|
request.open('GET', url, true);
|
|
|
|
// store request
|
|
this.storeRequest(request);
|
|
|
|
// optional request headers
|
|
if ( typeof options.requestHeaders !== "undefined" ) {
|
|
var requestHeaders = options.requestHeaders;
|
|
for (var j = 0; j < requestHeaders.length; ++j) {
|
|
if ( typeof requestHeaders[j].name !== "undefined" &&
|
|
typeof requestHeaders[j].value !== "undefined" ) {
|
|
request.setRequestHeader(requestHeaders[j].name, requestHeaders[j].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// bind reader progress
|
|
request.onprogress = mproghandler.getMonoProgressHandler(i, 0);
|
|
request.onloadend = mproghandler.getMonoOnLoadEndHandler(i, 0);
|
|
|
|
// find a loader
|
|
var foundLoader = false;
|
|
for (var l = 0; l < loaders.length; ++l) {
|
|
loader = loaders[l];
|
|
if (loader.canLoadUrl(url)) {
|
|
foundLoader = true;
|
|
// store loader
|
|
this.storeLoader(loader);
|
|
// set reader callbacks
|
|
request.onload = loader.getUrlLoadHandler(url, i);
|
|
request.onerror = getRequestOnError(url);
|
|
request.onabort = getRequestOnAbort(url);
|
|
// response type (default is 'text')
|
|
if (loader.loadUrlAs() === dwv.io.urlContentTypes.ArrayBuffer) {
|
|
request.responseType = "arraybuffer";
|
|
}
|
|
// read
|
|
request.send(null);
|
|
// next file
|
|
break;
|
|
}
|
|
}
|
|
// TODO: throw?
|
|
if (!foundLoader) {
|
|
throw new Error("No loader found for url: "+url);
|
|
}
|
|
}
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.io = dwv.io || {};
|
|
// external
|
|
var JSZip = JSZip || {};
|
|
|
|
/**
|
|
* ZIP data loader.
|
|
*/
|
|
dwv.io.ZipLoader = function ()
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* Loader options.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var options = {};
|
|
|
|
/**
|
|
* Loading flag.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isLoading = false;
|
|
|
|
/**
|
|
* Set the loader options.
|
|
* @param {Object} opt The input options.
|
|
*/
|
|
this.setOptions = function (opt) {
|
|
options = opt;
|
|
};
|
|
|
|
/**
|
|
* Is the load ongoing?
|
|
* @return {Boolean} True if loading.
|
|
*/
|
|
this.isLoading = function () {
|
|
return isLoading;
|
|
};
|
|
|
|
var filename = "";
|
|
var files = [];
|
|
var zobjs = null;
|
|
|
|
/**
|
|
* JSZip.async callback
|
|
* @param {ArrayBuffer} content unzipped file image
|
|
* @return {}
|
|
*/
|
|
function zipAsyncCallback(content)
|
|
{
|
|
files.push({"filename": filename, "data": content});
|
|
|
|
if (files.length < zobjs.length){
|
|
var num = files.length;
|
|
filename = zobjs[num].name;
|
|
zobjs[num].async("arrayBuffer").then(zipAsyncCallback);
|
|
}
|
|
else {
|
|
var memoryIO = new dwv.io.MemoryLoader();
|
|
memoryIO.onload = self.onload;
|
|
memoryIO.onloadend = function () {
|
|
// reset loading flag
|
|
isLoading = false;
|
|
// call listeners
|
|
self.onloadend();
|
|
};
|
|
memoryIO.onprogress = self.onprogress;
|
|
memoryIO.onerror = self.onerror;
|
|
memoryIO.onabort = self.onabort;
|
|
|
|
memoryIO.load(files);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load data.
|
|
* @param {Object} buffer The DICOM buffer.
|
|
* @param {String} origin The data origin.
|
|
* @param {Number} index The data index.
|
|
*/
|
|
this.load = function (buffer/*, origin, index*/) {
|
|
// set loading flag
|
|
isLoading = true;
|
|
|
|
JSZip.loadAsync(buffer).then( function(zip) {
|
|
files = [];
|
|
zobjs = zip.file(/.*\.dcm/);
|
|
// recursively load zip files into the files array
|
|
var num = files.length;
|
|
filename = zobjs[num].name;
|
|
zobjs[num].async("arrayBuffer").then(zipAsyncCallback);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Abort load: pass to listeners.
|
|
*/
|
|
this.abort = function () {
|
|
// reset loading flag
|
|
isLoading = false;
|
|
// call listeners
|
|
self.onabort();
|
|
};
|
|
|
|
/**
|
|
* Get a file load handler.
|
|
* @param {Object} file The file to load.
|
|
* @param {Number} index The index 'id' of the file.
|
|
* @return {Function} A file load handler.
|
|
*/
|
|
this.getFileLoadHandler = function (file, index) {
|
|
return function (event) {
|
|
self.load(event.target.result, file, index);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get a url load handler.
|
|
* @param {String} url The url to load.
|
|
* @param {Number} index The index 'id' of the url.
|
|
* @return {Function} A url load handler.
|
|
*/
|
|
this.getUrlLoadHandler = function (url, index) {
|
|
return function (/*event*/) {
|
|
// check response status
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes
|
|
// status 200: "OK"; status 0: "debug"
|
|
if (this.status !== 200 && this.status !== 0) {
|
|
self.onerror({'name': "RequestError",
|
|
'message': "Error status: " + this.status +
|
|
" while loading '" + url + "' [ZipLoader]" });
|
|
return;
|
|
}
|
|
// load
|
|
self.load(this.response, url, index);
|
|
};
|
|
};
|
|
|
|
}; // class DicomDataLoader
|
|
|
|
/**
|
|
* Check if the loader can load the provided file.
|
|
* @param {Object} file The file to check.
|
|
* @return True if the file can be loaded.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.canLoadFile = function (file) {
|
|
var ext = file.name.split('.').pop().toLowerCase();
|
|
return (ext === "zip");
|
|
};
|
|
|
|
/**
|
|
* Check if the loader can load the provided url.
|
|
* @param {String} url The url to check.
|
|
* @return True if the url can be loaded.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.canLoadUrl = function (url) {
|
|
var ext = url.split('.').pop().toLowerCase();
|
|
return (ext === "zip");
|
|
};
|
|
|
|
/**
|
|
* Get the file content type needed by the loader.
|
|
* @return One of the 'dwv.io.fileContentTypes'.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.loadFileAs = function () {
|
|
return dwv.io.fileContentTypes.ArrayBuffer;
|
|
};
|
|
|
|
/**
|
|
* Get the url content type needed by the loader.
|
|
* @return One of the 'dwv.io.urlContentTypes'.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.loadUrlAs = function () {
|
|
return dwv.io.urlContentTypes.ArrayBuffer;
|
|
};
|
|
|
|
/**
|
|
* Handle a load event.
|
|
* @param {Object} event The load event, 'event.target'
|
|
* should be the loaded data.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.onload = function (/*event*/) {};
|
|
/**
|
|
* Handle an load end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.onloadend = function () {};
|
|
/**
|
|
* Handle a progress event.
|
|
* @param {Object} event The progress event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.onprogress = function (/*event*/) {};
|
|
/**
|
|
* Handle an error event.
|
|
* @param {Object} event The error event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.onerror = function (/*event*/) {};
|
|
/**
|
|
* Handle an abort event.
|
|
* @param {Object} event The abort event with an
|
|
* optional 'event.message'.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.io.ZipLoader.prototype.onabort = function (/*event*/) {};
|
|
|
|
/**
|
|
* Add to Loader list.
|
|
*/
|
|
dwv.io.loaderList = dwv.io.loaderList || [];
|
|
dwv.io.loaderList.push( "ZipLoader" );
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.math = dwv.math || {};
|
|
|
|
/**
|
|
* Circular Bucket Queue.
|
|
*
|
|
* Returns input'd points in sorted order. All operations run in roughly O(1)
|
|
* time (for input with small cost values), but it has a strict requirement:
|
|
*
|
|
* If the most recent point had a cost of c, any points added should have a cost
|
|
* c' in the range c <= c' <= c + (capacity - 1).
|
|
*
|
|
* @constructor
|
|
* @input bits
|
|
* @input cost_functor
|
|
*/
|
|
dwv.math.BucketQueue = function(bits, cost_functor)
|
|
{
|
|
this.bucketCount = 1 << bits; // # of buckets = 2^bits
|
|
this.mask = this.bucketCount - 1; // 2^bits - 1 = index mask
|
|
this.size = 0;
|
|
|
|
this.loc = 0; // Current index in bucket list
|
|
|
|
// Cost defaults to item value
|
|
this.cost = (typeof(cost_functor) !== 'undefined') ? cost_functor : function(item) {
|
|
return item;
|
|
};
|
|
|
|
this.buckets = this.buildArray(this.bucketCount);
|
|
};
|
|
|
|
dwv.math.BucketQueue.prototype.push = function(item) {
|
|
// Prepend item to the list in the appropriate bucket
|
|
var bucket = this.getBucket(item);
|
|
item.next = this.buckets[bucket];
|
|
this.buckets[bucket] = item;
|
|
|
|
this.size++;
|
|
};
|
|
|
|
dwv.math.BucketQueue.prototype.pop = function() {
|
|
if ( this.size === 0 ) {
|
|
throw new Error("Cannot pop, bucketQueue is empty.");
|
|
}
|
|
|
|
// Find first empty bucket
|
|
while ( this.buckets[this.loc] === null ) {
|
|
this.loc = (this.loc + 1) % this.bucketCount;
|
|
}
|
|
|
|
// All items in bucket have same cost, return the first one
|
|
var ret = this.buckets[this.loc];
|
|
this.buckets[this.loc] = ret.next;
|
|
ret.next = null;
|
|
|
|
this.size--;
|
|
return ret;
|
|
};
|
|
|
|
dwv.math.BucketQueue.prototype.remove = function(item) {
|
|
// Tries to remove item from queue. Returns true on success, false otherwise
|
|
if ( !item ) {
|
|
return false;
|
|
}
|
|
|
|
// To find node, go to bucket and search through unsorted list.
|
|
var bucket = this.getBucket(item);
|
|
var node = this.buckets[bucket];
|
|
|
|
while ( node !== null && !item.equals(node.next) ) {
|
|
node = node.next;
|
|
}
|
|
|
|
if ( node === null ) {
|
|
// Item not in list, ergo item not in queue
|
|
return false;
|
|
}
|
|
else {
|
|
// Found item, do standard list node deletion
|
|
node.next = node.next.next;
|
|
|
|
this.size--;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
dwv.math.BucketQueue.prototype.isEmpty = function() {
|
|
return this.size === 0;
|
|
};
|
|
|
|
dwv.math.BucketQueue.prototype.getBucket = function(item) {
|
|
// Bucket index is the masked cost
|
|
return this.cost(item) & this.mask;
|
|
};
|
|
|
|
dwv.math.BucketQueue.prototype.buildArray = function(newSize) {
|
|
// Create array and initialze pointers to null
|
|
var buckets = new Array(newSize);
|
|
|
|
for ( var i = 0; i < buckets.length; i++ ) {
|
|
buckets[i] = null;
|
|
}
|
|
|
|
return buckets;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.math = dwv.math || {};
|
|
|
|
/**
|
|
* Immutable 3x3 Matrix.
|
|
* @constructor
|
|
*/
|
|
dwv.math.Matrix33 = function (
|
|
m00, m01, m02,
|
|
m10, m11, m12,
|
|
m20, m21, m22 )
|
|
{
|
|
// row-major order
|
|
var mat = new Float32Array(9);
|
|
mat[0] = m00; mat[1] = m01; mat[2] = m02;
|
|
mat[3] = m10; mat[4] = m11; mat[5] = m12;
|
|
mat[6] = m20; mat[7] = m21; mat[8] = m22;
|
|
|
|
/**
|
|
* Get a value of the matrix.
|
|
* @param {Number} row The row at wich to get the value.
|
|
* @param {Number} col The column at wich to get the value.
|
|
*/
|
|
this.get = function (row, col) {
|
|
return mat[row*3 + col];
|
|
};
|
|
}; // Matrix33
|
|
|
|
/**
|
|
* Check for Matrix33 equality.
|
|
* @param {Object} rhs The other matrix to compare to.
|
|
* @return {Boolean} True if both matrices are equal.
|
|
*/
|
|
dwv.math.Matrix33.prototype.equals = function (rhs) {
|
|
return this.get(0,0) === rhs.get(0,0) && this.get(0,1) === rhs.get(0,1) &&
|
|
this.get(0,2) === rhs.get(0,2) && this.get(1,0) === rhs.get(1,0) &&
|
|
this.get(1,1) === rhs.get(1,1) && this.get(1,2) === rhs.get(1,2) &&
|
|
this.get(2,0) === rhs.get(2,0) && this.get(2,1) === rhs.get(2,1) &&
|
|
this.get(2,2) === rhs.get(2,2);
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Matrix33.
|
|
* @return {String} The matrix as a string.
|
|
*/
|
|
dwv.math.Matrix33.prototype.toString = function () {
|
|
return "[" + this.get(0,0) + ", " + this.get(0,1) + ", " + this.get(0,2) +
|
|
"\n " + this.get(1,0) + ", " + this.get(1,1) + ", " + this.get(1,2) +
|
|
"\n " + this.get(2,0) + ", " + this.get(2,1) + ", " + this.get(2,2) + "]";
|
|
};
|
|
|
|
/**
|
|
* Multiply this matrix by a 3D vector.
|
|
* @param {Object} vector3D The input 3D vector
|
|
* @return {Object} The result 3D vector
|
|
*/
|
|
dwv.math.Matrix33.multiplyVector3D = function (vector3D) {
|
|
// cache matrix values
|
|
var m00 = this.get(0,0); var m01 = this.get(0,1); var m02 = this.get(0,2);
|
|
var m10 = this.get(1,0); var m11 = this.get(1,1); var m12 = this.get(1,2);
|
|
var m20 = this.get(2,0); var m21 = this.get(2,1); var m22 = this.get(2,2);
|
|
// cache vector values
|
|
var vx = vector3D.getX();
|
|
var vy = vector3D.getY();
|
|
var vz = vector3D.getZ();
|
|
// calculate
|
|
return new dwv.math.Vector3D(
|
|
(m00 * vx) + (m01 * vy) + (m02 * vz),
|
|
(m10 * vx) + (m11 * vy) + (m12 * vz),
|
|
(m20 * vx) + (m21 * vy) + (m22 * vz) );
|
|
};
|
|
|
|
/**
|
|
* Create a 3x3 identity matrix.
|
|
* @return {Object} The identity matrix.
|
|
*/
|
|
dwv.math.getIdentityMat33= function () {
|
|
return new dwv.math.Matrix33(
|
|
1, 0, 0,
|
|
0, 1, 0,
|
|
0, 0, 1 );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.math = dwv.math || {};
|
|
|
|
/**
|
|
* Immutable 2D point.
|
|
* @constructor
|
|
* @param {Number} x The X coordinate for the point.
|
|
* @param {Number} y The Y coordinate for the point.
|
|
*/
|
|
dwv.math.Point2D = function (x,y)
|
|
{
|
|
/**
|
|
* Get the X position of the point.
|
|
* @return {Number} The X position of the point.
|
|
*/
|
|
this.getX = function () { return x; };
|
|
/**
|
|
* Get the Y position of the point.
|
|
* @return {Number} The Y position of the point.
|
|
*/
|
|
this.getY = function () { return y; };
|
|
}; // Point2D class
|
|
|
|
/**
|
|
* Check for Point2D equality.
|
|
* @param {Object} rhs The other point to compare to.
|
|
* @return {Boolean} True if both points are equal.
|
|
*/
|
|
dwv.math.Point2D.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getX() === rhs.getX() &&
|
|
this.getY() === rhs.getY();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Point2D.
|
|
* @return {String} The point as a string.
|
|
*/
|
|
dwv.math.Point2D.prototype.toString = function () {
|
|
return "(" + this.getX() + ", " + this.getY() + ")";
|
|
};
|
|
|
|
/**
|
|
* Get the distance to another Point2D.
|
|
* @param {Object} point2D The input point.
|
|
*/
|
|
dwv.math.Point2D.prototype.getDistance = function (point2D) {
|
|
return Math.sqrt(
|
|
(this.getX() - point2D.getX()) * (this.getX() - point2D.getX()) +
|
|
(this.getY() - point2D.getY()) * (this.getY() - point2D.getY()) );
|
|
};
|
|
|
|
/**
|
|
* Mutable 2D point.
|
|
* @constructor
|
|
* @param {Number} x The X coordinate for the point.
|
|
* @param {Number} y The Y coordinate for the point.
|
|
*/
|
|
dwv.math.FastPoint2D = function (x,y)
|
|
{
|
|
this.x = x;
|
|
this.y = y;
|
|
}; // FastPoint2D class
|
|
|
|
/**
|
|
* Check for FastPoint2D equality.
|
|
* @param {Object} other The other point to compare to.
|
|
* @return {Boolean} True if both points are equal.
|
|
*/
|
|
dwv.math.FastPoint2D.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.x === rhs.x &&
|
|
this.y === rhs.y;
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the FastPoint2D.
|
|
* @return {String} The point as a string.
|
|
*/
|
|
dwv.math.FastPoint2D.prototype.toString = function () {
|
|
return "(" + this.x + ", " + this.y + ")";
|
|
};
|
|
|
|
/**
|
|
* Immutable 3D point.
|
|
* @constructor
|
|
* @param {Number} x The X coordinate for the point.
|
|
* @param {Number} y The Y coordinate for the point.
|
|
* @param {Number} z The Z coordinate for the point.
|
|
*/
|
|
dwv.math.Point3D = function (x,y,z)
|
|
{
|
|
/**
|
|
* Get the X position of the point.
|
|
* @return {Number} The X position of the point.
|
|
*/
|
|
this.getX = function () { return x; };
|
|
/**
|
|
* Get the Y position of the point.
|
|
* @return {Number} The Y position of the point.
|
|
*/
|
|
this.getY = function () { return y; };
|
|
/**
|
|
* Get the Z position of the point.
|
|
* @return {Number} The Z position of the point.
|
|
*/
|
|
this.getZ = function () { return z; };
|
|
}; // Point3D class
|
|
|
|
/**
|
|
* Check for Point3D equality.
|
|
* @param {Object} rhs The other point to compare to.
|
|
* @return {Boolean} True if both points are equal.
|
|
*/
|
|
dwv.math.Point3D.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getX() === rhs.getX() &&
|
|
this.getY() === rhs.getY() &&
|
|
this.getZ() === rhs.getZ();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Point3D.
|
|
* @return {String} The point as a string.
|
|
*/
|
|
dwv.math.Point3D.prototype.toString = function () {
|
|
return "(" + this.getX() +
|
|
", " + this.getY() +
|
|
", " + this.getZ() + ")";
|
|
};
|
|
|
|
/**
|
|
* Get the distance to another Point3D.
|
|
* @param {Object} point3D The input point.
|
|
*/
|
|
dwv.math.Point3D.prototype.getDistance = function (point3D) {
|
|
return Math.sqrt(
|
|
(this.getX() - point3D.getX()) * (this.getX() - point3D.getX()) +
|
|
(this.getY() - point3D.getY()) * (this.getY() - point3D.getY()) +
|
|
(this.getZ() - point3D.getZ()) * (this.getZ() - point3D.getZ()) );
|
|
};
|
|
|
|
/**
|
|
* Get the difference to another Point3D.
|
|
* @param {Object} point3D The input point.
|
|
* @return {Object} The 3D vector from the input point to this one.
|
|
*/
|
|
dwv.math.Point3D.prototype.minus = function (point3D) {
|
|
return new dwv.math.Vector3D(
|
|
(this.getX() - point3D.getX()),
|
|
(this.getY() - point3D.getY()),
|
|
(this.getZ() - point3D.getZ()) );
|
|
};
|
|
|
|
/**
|
|
* Immutable 3D index.
|
|
* @constructor
|
|
* @param {Number} i The column index.
|
|
* @param {Number} j The row index.
|
|
* @param {Number} k The slice index.
|
|
*/
|
|
dwv.math.Index3D = function (i,j,k)
|
|
{
|
|
/**
|
|
* Get the column index.
|
|
* @return {Number} The column index.
|
|
*/
|
|
this.getI = function () { return i; };
|
|
/**
|
|
* Get the row index.
|
|
* @return {Number} The row index.
|
|
*/
|
|
this.getJ = function () { return j; };
|
|
/**
|
|
* Get the slice index.
|
|
* @return {Number} The slice index.
|
|
*/
|
|
this.getK = function () { return k; };
|
|
}; // Index3D class
|
|
|
|
/**
|
|
* Check for Index3D equality.
|
|
* @param {Object} rhs The other index to compare to.
|
|
* @return {Boolean} True if both indices are equal.
|
|
*/
|
|
dwv.math.Index3D.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getI() === rhs.getI() &&
|
|
this.getJ() === rhs.getJ() &&
|
|
this.getK() === rhs.getK();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Index3D.
|
|
* @return {String} The Index3D as a string.
|
|
*/
|
|
dwv.math.Index3D.prototype.toString = function () {
|
|
return "(" + this.getI() +
|
|
", " + this.getJ() +
|
|
", " + this.getK() + ")";
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.math = dwv.math || {};
|
|
|
|
// Pre-created to reduce allocation in inner loops
|
|
var __twothirdpi = ( 2 / (3 * Math.PI) );
|
|
|
|
/**
|
|
*
|
|
*/
|
|
dwv.math.computeGreyscale = function(data, width, height) {
|
|
// Returns 2D augmented array containing greyscale data
|
|
// Greyscale values found by averaging colour channels
|
|
// Input should be in a flat RGBA array, with values between 0 and 255
|
|
var greyscale = [];
|
|
|
|
// Compute actual values
|
|
for (var y = 0; y < height; y++) {
|
|
greyscale[y] = [];
|
|
|
|
for (var x = 0; x < width; x++) {
|
|
var p = (y*width + x)*4;
|
|
greyscale[y][x] = (data[p] + data[p+1] + data[p+2]) / (3*255);
|
|
}
|
|
}
|
|
|
|
// Augment with convenience functions
|
|
greyscale.dx = function(x,y) {
|
|
if ( x+1 === this[y].length ) {
|
|
// If we're at the end, back up one
|
|
x--;
|
|
}
|
|
return this[y][x+1] - this[y][x];
|
|
};
|
|
|
|
greyscale.dy = function(x,y) {
|
|
if ( y+1 === this.length ) {
|
|
// If we're at the end, back up one
|
|
y--;
|
|
}
|
|
return this[y][x] - this[y+1][x];
|
|
};
|
|
|
|
greyscale.gradMagnitude = function(x,y) {
|
|
var dx = this.dx(x,y);
|
|
var dy = this.dy(x,y);
|
|
return Math.sqrt(dx*dx + dy*dy);
|
|
};
|
|
|
|
greyscale.laplace = function(x,y) {
|
|
// Laplacian of Gaussian
|
|
var lap = -16 * this[y][x];
|
|
lap += this[y-2][x];
|
|
lap += this[y-1][x-1] + 2*this[y-1][x] + this[y-1][x+1];
|
|
lap += this[y][x-2] + 2*this[y][x-1] + 2*this[y][x+1] + this[y][x+2];
|
|
lap += this[y+1][x-1] + 2*this[y+1][x] + this[y+1][x+1];
|
|
lap += this[y+2][x];
|
|
|
|
return lap;
|
|
};
|
|
|
|
return greyscale;
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
dwv.math.computeGradient = function(greyscale) {
|
|
// Returns a 2D array of gradient magnitude values for greyscale. The values
|
|
// are scaled between 0 and 1, and then flipped, so that it works as a cost
|
|
// function.
|
|
var gradient = [];
|
|
|
|
var max = 0; // Maximum gradient found, for scaling purposes
|
|
|
|
var x = 0;
|
|
var y = 0;
|
|
|
|
for (y = 0; y < greyscale.length-1; y++) {
|
|
gradient[y] = [];
|
|
|
|
for (x = 0; x < greyscale[y].length-1; x++) {
|
|
gradient[y][x] = greyscale.gradMagnitude(x,y);
|
|
max = Math.max(gradient[y][x], max);
|
|
}
|
|
|
|
gradient[y][greyscale[y].length-1] = gradient[y][greyscale.length-2];
|
|
}
|
|
|
|
gradient[greyscale.length-1] = [];
|
|
for (var i = 0; i < gradient[0].length; i++) {
|
|
gradient[greyscale.length-1][i] = gradient[greyscale.length-2][i];
|
|
}
|
|
|
|
// Flip and scale.
|
|
for (y = 0; y < gradient.length; y++) {
|
|
for (x = 0; x < gradient[y].length; x++) {
|
|
gradient[y][x] = 1 - (gradient[y][x] / max);
|
|
}
|
|
}
|
|
|
|
return gradient;
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
dwv.math.computeLaplace = function(greyscale) {
|
|
// Returns a 2D array of Laplacian of Gaussian values
|
|
var laplace = [];
|
|
|
|
// Make the edges low cost here.
|
|
|
|
laplace[0] = [];
|
|
laplace[1] = [];
|
|
for (var i = 1; i < greyscale.length; i++) {
|
|
// Pad top, since we can't compute Laplacian
|
|
laplace[0][i] = 1;
|
|
laplace[1][i] = 1;
|
|
}
|
|
|
|
for (var y = 2; y < greyscale.length-2; y++) {
|
|
laplace[y] = [];
|
|
// Pad left, ditto
|
|
laplace[y][0] = 1;
|
|
laplace[y][1] = 1;
|
|
|
|
for (var x = 2; x < greyscale[y].length-2; x++) {
|
|
// Threshold needed to get rid of clutter.
|
|
laplace[y][x] = (greyscale.laplace(x,y) > 0.33) ? 0 : 1;
|
|
}
|
|
|
|
// Pad right, ditto
|
|
laplace[y][greyscale[y].length-2] = 1;
|
|
laplace[y][greyscale[y].length-1] = 1;
|
|
}
|
|
|
|
laplace[greyscale.length-2] = [];
|
|
laplace[greyscale.length-1] = [];
|
|
for (var j = 1; j < greyscale.length; j++) {
|
|
// Pad bottom, ditto
|
|
laplace[greyscale.length-2][j] = 1;
|
|
laplace[greyscale.length-1][j] = 1;
|
|
}
|
|
|
|
return laplace;
|
|
};
|
|
|
|
dwv.math.computeGradX = function(greyscale) {
|
|
// Returns 2D array of x-gradient values for greyscale
|
|
var gradX = [];
|
|
|
|
for ( var y = 0; y < greyscale.length; y++ ) {
|
|
gradX[y] = [];
|
|
|
|
for ( var x = 0; x < greyscale[y].length-1; x++ ) {
|
|
gradX[y][x] = greyscale.dx(x,y);
|
|
}
|
|
|
|
gradX[y][greyscale[y].length-1] = gradX[y][greyscale[y].length-2];
|
|
}
|
|
|
|
return gradX;
|
|
};
|
|
|
|
dwv.math.computeGradY = function(greyscale) {
|
|
// Returns 2D array of y-gradient values for greyscale
|
|
var gradY = [];
|
|
|
|
for (var y = 0; y < greyscale.length-1; y++) {
|
|
gradY[y] = [];
|
|
|
|
for ( var x = 0; x < greyscale[y].length; x++ ) {
|
|
gradY[y][x] = greyscale.dy(x,y);
|
|
}
|
|
}
|
|
|
|
gradY[greyscale.length-1] = [];
|
|
for ( var i = 0; i < greyscale[0].length; i++ ) {
|
|
gradY[greyscale.length-1][i] = gradY[greyscale.length-2][i];
|
|
}
|
|
|
|
return gradY;
|
|
};
|
|
|
|
dwv.math.gradUnitVector = function(gradX, gradY, px, py, out) {
|
|
// Returns the gradient vector at (px,py), scaled to a magnitude of 1
|
|
var ox = gradX[py][px];
|
|
var oy = gradY[py][px];
|
|
|
|
var gvm = Math.sqrt(ox*ox + oy*oy);
|
|
gvm = Math.max(gvm, 1e-100); // To avoid possible divide-by-0 errors
|
|
|
|
out.x = ox / gvm;
|
|
out.y = oy / gvm;
|
|
};
|
|
|
|
dwv.math.gradDirection = function(gradX, gradY, px, py, qx, qy) {
|
|
var __dgpuv = new dwv.math.FastPoint2D(-1, -1);
|
|
var __gdquv = new dwv.math.FastPoint2D(-1, -1);
|
|
// Compute the gradiant direction, in radians, between to points
|
|
dwv.math.gradUnitVector(gradX, gradY, px, py, __dgpuv);
|
|
dwv.math.gradUnitVector(gradX, gradY, qx, qy, __gdquv);
|
|
|
|
var dp = __dgpuv.y * (qx - px) - __dgpuv.x * (qy - py);
|
|
var dq = __gdquv.y * (qx - px) - __gdquv.x * (qy - py);
|
|
|
|
// Make sure dp is positive, to keep things consistant
|
|
if (dp < 0) {
|
|
dp = -dp;
|
|
dq = -dq;
|
|
}
|
|
|
|
if ( px !== qx && py !== qy ) {
|
|
// We're going diagonally between pixels
|
|
dp *= Math.SQRT1_2;
|
|
dq *= Math.SQRT1_2;
|
|
}
|
|
|
|
return __twothirdpi * (Math.acos(dp) + Math.acos(dq));
|
|
};
|
|
|
|
dwv.math.computeSides = function(dist, gradX, gradY, greyscale) {
|
|
// Returns 2 2D arrays, containing inside and outside greyscale values.
|
|
// These greyscale values are the intensity just a little bit along the
|
|
// gradient vector, in either direction, from the supplied point. These
|
|
// values are used when using active-learning Intelligent Scissors
|
|
|
|
var sides = {};
|
|
sides.inside = [];
|
|
sides.outside = [];
|
|
|
|
var guv = new dwv.math.FastPoint2D(-1, -1); // Current gradient unit vector
|
|
|
|
for ( var y = 0; y < gradX.length; y++ ) {
|
|
sides.inside[y] = [];
|
|
sides.outside[y] = [];
|
|
|
|
for ( var x = 0; x < gradX[y].length; x++ ) {
|
|
dwv.math.gradUnitVector(gradX, gradY, x, y, guv);
|
|
|
|
//(x, y) rotated 90 = (y, -x)
|
|
|
|
var ix = Math.round(x + dist*guv.y);
|
|
var iy = Math.round(y - dist*guv.x);
|
|
var ox = Math.round(x - dist*guv.y);
|
|
var oy = Math.round(y + dist*guv.x);
|
|
|
|
ix = Math.max(Math.min(ix, gradX[y].length-1), 0);
|
|
ox = Math.max(Math.min(ox, gradX[y].length-1), 0);
|
|
iy = Math.max(Math.min(iy, gradX.length-1), 0);
|
|
oy = Math.max(Math.min(oy, gradX.length-1), 0);
|
|
|
|
sides.inside[y][x] = greyscale[iy][ix];
|
|
sides.outside[y][x] = greyscale[oy][ox];
|
|
}
|
|
}
|
|
|
|
return sides;
|
|
};
|
|
|
|
dwv.math.gaussianBlur = function(buffer, out) {
|
|
// Smooth values over to fill in gaps in the mapping
|
|
out[0] = 0.4*buffer[0] + 0.5*buffer[1] + 0.1*buffer[1];
|
|
out[1] = 0.25*buffer[0] + 0.4*buffer[1] + 0.25*buffer[2] + 0.1*buffer[3];
|
|
|
|
for ( var i = 2; i < buffer.length-2; i++ ) {
|
|
out[i] = 0.05*buffer[i-2] + 0.25*buffer[i-1] + 0.4*buffer[i] + 0.25*buffer[i+1] + 0.05*buffer[i+2];
|
|
}
|
|
|
|
var len = buffer.length;
|
|
out[len-2] = 0.25*buffer[len-1] + 0.4*buffer[len-2] + 0.25*buffer[len-3] + 0.1*buffer[len-4];
|
|
out[len-1] = 0.4*buffer[len-1] + 0.5*buffer[len-2] + 0.1*buffer[len-3];
|
|
};
|
|
|
|
|
|
/**
|
|
* Scissors
|
|
*
|
|
* Ref: Eric N. Mortensen, William A. Barrett, Interactive Segmentation with
|
|
* Intelligent Scissors, Graphical Models and Image Processing, Volume 60,
|
|
* Issue 5, September 1998, Pages 349-384, ISSN 1077-3169,
|
|
* DOI: 10.1006/gmip.1998.0480.
|
|
*
|
|
* {@link http://www.sciencedirect.com/science/article/B6WG4-45JB8WN-9/2/6fe59d8089fd1892c2bfb82283065579}
|
|
*
|
|
* Highly inspired from {@link http://code.google.com/p/livewire-javascript/}
|
|
* @constructor
|
|
*/
|
|
dwv.math.Scissors = function()
|
|
{
|
|
this.width = -1;
|
|
this.height = -1;
|
|
|
|
this.curPoint = null; // Corrent point we're searching on.
|
|
this.searchGranBits = 8; // Bits of resolution for BucketQueue.
|
|
this.searchGran = 1 << this.earchGranBits; //bits.
|
|
this.pointsPerPost = 500;
|
|
|
|
// Precomputed image data. All in ranges 0 >= x >= 1 and all inverted (1 - x).
|
|
this.greyscale = null; // Greyscale of image
|
|
this.laplace = null; // Laplace zero-crossings (either 0 or 1).
|
|
this.gradient = null; // Gradient magnitudes.
|
|
this.gradX = null; // X-differences.
|
|
this.gradY = null; // Y-differences.
|
|
|
|
this.parents = null; // Matrix mapping point => parent along shortest-path to root.
|
|
|
|
this.working = false; // Currently computing shortest paths?
|
|
|
|
// Begin Training:
|
|
this.trained = false;
|
|
this.trainingPoints = null;
|
|
|
|
this.edgeWidth = 2;
|
|
this.trainingLength = 32;
|
|
|
|
this.edgeGran = 256;
|
|
this.edgeTraining = null;
|
|
|
|
this.gradPointsNeeded = 32;
|
|
this.gradGran = 1024;
|
|
this.gradTraining = null;
|
|
|
|
this.insideGran = 256;
|
|
this.insideTraining = null;
|
|
|
|
this.outsideGran = 256;
|
|
this.outsideTraining = null;
|
|
// End Training
|
|
}; // Scissors class
|
|
|
|
// Begin training methods //
|
|
dwv.math.Scissors.prototype.getTrainingIdx = function(granularity, value) {
|
|
return Math.round((granularity - 1) * value);
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.getTrainedEdge = function(edge) {
|
|
return this.edgeTraining[this.getTrainingIdx(this.edgeGran, edge)];
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.getTrainedGrad = function(grad) {
|
|
return this.gradTraining[this.getTrainingIdx(this.gradGran, grad)];
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.getTrainedInside = function(inside) {
|
|
return this.insideTraining[this.getTrainingIdx(this.insideGran, inside)];
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.getTrainedOutside = function(outside) {
|
|
return this.outsideTraining[this.getTrainingIdx(this.outsideGran, outside)];
|
|
};
|
|
// End training methods //
|
|
|
|
dwv.math.Scissors.prototype.setWorking = function(working) {
|
|
// Sets working flag
|
|
this.working = working;
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.setDimensions = function(width, height) {
|
|
this.width = width;
|
|
this.height = height;
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.setData = function(data) {
|
|
if ( this.width === -1 || this.height === -1 ) {
|
|
// The width and height should have already been set
|
|
throw new Error("Dimensions have not been set.");
|
|
}
|
|
|
|
this.greyscale = dwv.math.computeGreyscale(data, this.width, this.height);
|
|
this.laplace = dwv.math.computeLaplace(this.greyscale);
|
|
this.gradient = dwv.math.computeGradient(this.greyscale);
|
|
this.gradX = dwv.math.computeGradX(this.greyscale);
|
|
this.gradY = dwv.math.computeGradY(this.greyscale);
|
|
|
|
var sides = dwv.math.computeSides(this.edgeWidth, this.gradX, this.gradY, this.greyscale);
|
|
this.inside = sides.inside;
|
|
this.outside = sides.outside;
|
|
this.edgeTraining = [];
|
|
this.gradTraining = [];
|
|
this.insideTraining = [];
|
|
this.outsideTraining = [];
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.findTrainingPoints = function(p) {
|
|
// Grab the last handful of points for training
|
|
var points = [];
|
|
|
|
if ( this.parents !== null ) {
|
|
for ( var i = 0; i < this.trainingLength && p; i++ ) {
|
|
points.push(p);
|
|
p = this.parents[p.y][p.x];
|
|
}
|
|
}
|
|
|
|
return points;
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.resetTraining = function() {
|
|
this.trained = false; // Training is ignored with this flag set
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.doTraining = function(p) {
|
|
// Compute training weights and measures
|
|
this.trainingPoints = this.findTrainingPoints(p);
|
|
|
|
if ( this.trainingPoints.length < 8 ) {
|
|
return; // Not enough points, I think. It might crash if length = 0.
|
|
}
|
|
|
|
var buffer = [];
|
|
this.calculateTraining(buffer, this.edgeGran, this.greyscale, this.edgeTraining);
|
|
this.calculateTraining(buffer, this.gradGran, this.gradient, this.gradTraining);
|
|
this.calculateTraining(buffer, this.insideGran, this.inside, this.insideTraining);
|
|
this.calculateTraining(buffer, this.outsideGran, this.outside, this.outsideTraining);
|
|
|
|
if ( this.trainingPoints.length < this.gradPointsNeeded ) {
|
|
// If we have two few training points, the gradient weight map might not
|
|
// be smooth enough, so average with normal weights.
|
|
this.addInStaticGrad(this.trainingPoints.length, this.gradPointsNeeded);
|
|
}
|
|
|
|
this.trained = true;
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.calculateTraining = function(buffer, granularity, input, output) {
|
|
var i = 0;
|
|
// Build a map of raw-weights to trained-weights by favoring input values
|
|
buffer.length = granularity;
|
|
for ( i = 0; i < granularity; i++ ) {
|
|
buffer[i] = 0;
|
|
}
|
|
|
|
var maxVal = 1;
|
|
for ( i = 0; i < this.trainingPoints.length; i++ ) {
|
|
var p = this.trainingPoints[i];
|
|
var idx = this.getTrainingIdx(granularity, input[p.y][p.x]);
|
|
buffer[idx] += 1;
|
|
|
|
maxVal = Math.max(maxVal, buffer[idx]);
|
|
}
|
|
|
|
// Invert and scale.
|
|
for ( i = 0; i < granularity; i++ ) {
|
|
buffer[i] = 1 - buffer[i] / maxVal;
|
|
}
|
|
|
|
// Blur it, as suggested. Gets rid of static.
|
|
dwv.math.gaussianBlur(buffer, output);
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.addInStaticGrad = function(have, need) {
|
|
// Average gradient raw-weights to trained-weights map with standard weight
|
|
// map so that we don't end up with something to spiky
|
|
for ( var i = 0; i < this.gradGran; i++ ) {
|
|
this.gradTraining[i] = Math.min(this.gradTraining[i], 1 - i*(need - have)/(need*this.gradGran));
|
|
}
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.gradDirection = function(px, py, qx, qy) {
|
|
return dwv.math.gradDirection(this.gradX, this.gradY, px, py, qx, qy);
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.dist = function(px, py, qx, qy) {
|
|
// The grand culmunation of most of the code: the weighted distance function
|
|
var grad = this.gradient[qy][qx];
|
|
|
|
if ( px === qx || py === qy ) {
|
|
// The distance is Euclidean-ish; non-diagonal edges should be shorter
|
|
grad *= Math.SQRT1_2;
|
|
}
|
|
|
|
var lap = this.laplace[qy][qx];
|
|
var dir = this.gradDirection(px, py, qx, qy);
|
|
|
|
if ( this.trained ) {
|
|
// Apply training magic
|
|
var gradT = this.getTrainedGrad(grad);
|
|
var edgeT = this.getTrainedEdge(this.greyscale[py][px]);
|
|
var insideT = this.getTrainedInside(this.inside[py][px]);
|
|
var outsideT = this.getTrainedOutside(this.outside[py][px]);
|
|
|
|
return 0.3*gradT + 0.3*lap + 0.1*(dir + edgeT + insideT + outsideT);
|
|
} else {
|
|
// Normal weights
|
|
return 0.43*grad + 0.43*lap + 0.11*dir;
|
|
}
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.adj = function(p) {
|
|
var list = [];
|
|
|
|
var sx = Math.max(p.x-1, 0);
|
|
var sy = Math.max(p.y-1, 0);
|
|
var ex = Math.min(p.x+1, this.greyscale[0].length-1);
|
|
var ey = Math.min(p.y+1, this.greyscale.length-1);
|
|
|
|
var idx = 0;
|
|
for ( var y = sy; y <= ey; y++ ) {
|
|
for ( var x = sx; x <= ex; x++ ) {
|
|
if ( x !== p.x || y !== p.y ) {
|
|
list[idx++] = new dwv.math.FastPoint2D(x,y);
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.setPoint = function(sp) {
|
|
this.setWorking(true);
|
|
|
|
this.curPoint = sp;
|
|
|
|
var x = 0;
|
|
var y = 0;
|
|
|
|
this.visited = [];
|
|
for ( y = 0; y < this.height; y++ ) {
|
|
this.visited[y] = [];
|
|
for ( x = 0; x < this.width; x++ ) {
|
|
this.visited[y][x] = false;
|
|
}
|
|
}
|
|
|
|
this.parents = [];
|
|
for ( y = 0; y < this.height; y++ ) {
|
|
this.parents[y] = [];
|
|
}
|
|
|
|
this.cost = [];
|
|
for ( y = 0; y < this.height; y++ ) {
|
|
this.cost[y] = [];
|
|
for ( x = 0; x < this.width; x++ ) {
|
|
this.cost[y][x] = Number.MAX_VALUE;
|
|
}
|
|
}
|
|
|
|
this.pq = new dwv.math.BucketQueue(this.searchGranBits, function(p) {
|
|
return Math.round(this.searchGran * this.costArr[p.y][p.x]);
|
|
});
|
|
this.pq.searchGran = this.searchGran;
|
|
this.pq.costArr = this.cost;
|
|
|
|
this.pq.push(sp);
|
|
this.cost[sp.y][sp.x] = 0;
|
|
};
|
|
|
|
dwv.math.Scissors.prototype.doWork = function() {
|
|
if ( !this.working ) {
|
|
return;
|
|
}
|
|
|
|
this.timeout = null;
|
|
|
|
var pointCount = 0;
|
|
var newPoints = [];
|
|
while ( !this.pq.isEmpty() && pointCount < this.pointsPerPost ) {
|
|
var p = this.pq.pop();
|
|
newPoints.push(p);
|
|
newPoints.push(this.parents[p.y][p.x]);
|
|
|
|
this.visited[p.y][p.x] = true;
|
|
|
|
var adjList = this.adj(p);
|
|
for ( var i = 0; i < adjList.length; i++) {
|
|
var q = adjList[i];
|
|
|
|
var pqCost = this.cost[p.y][p.x] + this.dist(p.x, p.y, q.x, q.y);
|
|
|
|
if ( pqCost < this.cost[q.y][q.x] ) {
|
|
if ( this.cost[q.y][q.x] !== Number.MAX_VALUE ) {
|
|
// Already in PQ, must remove it so we can re-add it.
|
|
this.pq.remove(q);
|
|
}
|
|
|
|
this.cost[q.y][q.x] = pqCost;
|
|
this.parents[q.y][q.x] = p;
|
|
this.pq.push(q);
|
|
}
|
|
}
|
|
|
|
pointCount++;
|
|
}
|
|
|
|
return newPoints;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.math = dwv.math || {};
|
|
|
|
/**
|
|
* Mulitply the three inputs if the last two are not null.
|
|
* @param {Number} a The first input.
|
|
* @param {Number} b The second input.
|
|
* @param {Number} c The third input.
|
|
* @return {Number} The multiplication of the three inputs or
|
|
* null if one of the last two is null.
|
|
*/
|
|
function mulABC( a, b, c) {
|
|
var res = null;
|
|
if (b !== null && c !== null) {
|
|
res = a * b * c;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Circle shape.
|
|
* @constructor
|
|
* @param {Object} centre A Point2D representing the centre of the circle.
|
|
* @param {Number} radius The radius of the circle.
|
|
*/
|
|
dwv.math.Circle = function(centre, radius)
|
|
{
|
|
/**
|
|
* Circle surface.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var surface = Math.PI*radius*radius;
|
|
|
|
/**
|
|
* Get the centre (point) of the circle.
|
|
* @return {Object} The center (point) of the circle.
|
|
*/
|
|
this.getCenter = function() { return centre; };
|
|
/**
|
|
* Get the radius of the circle.
|
|
* @return {Number} The radius of the circle.
|
|
*/
|
|
this.getRadius = function() { return radius; };
|
|
/**
|
|
* Get the surface of the circle.
|
|
* @return {Number} The surface of the circle.
|
|
*/
|
|
this.getSurface = function() { return surface; };
|
|
/**
|
|
* Get the surface of the circle according to a spacing.
|
|
* @param {Number} spacingX The X spacing.
|
|
* @param {Number} spacingY The Y spacing.
|
|
* @return {Number} The surface of the circle multiplied by the given
|
|
* spacing or null for null spacings.
|
|
*/
|
|
this.getWorldSurface = function(spacingX, spacingY)
|
|
{
|
|
return mulABC(surface, spacingX, spacingY);
|
|
};
|
|
}; // Circle class
|
|
|
|
/**
|
|
* Ellipse shape.
|
|
* @constructor
|
|
* @param {Object} centre A Point2D representing the centre of the ellipse.
|
|
* @param {Number} a The radius of the ellipse on the horizontal axe.
|
|
* @param {Number} b The radius of the ellipse on the vertical axe.
|
|
*/
|
|
dwv.math.Ellipse = function(centre, a, b)
|
|
{
|
|
/**
|
|
* Circle surface.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var surface = Math.PI*a*b;
|
|
|
|
/**
|
|
* Get the centre (point) of the ellipse.
|
|
* @return {Object} The center (point) of the ellipse.
|
|
*/
|
|
this.getCenter = function() { return centre; };
|
|
/**
|
|
* Get the radius of the ellipse on the horizontal axe.
|
|
* @return {Number} The radius of the ellipse on the horizontal axe.
|
|
*/
|
|
this.getA = function() { return a; };
|
|
/**
|
|
* Get the radius of the ellipse on the vertical axe.
|
|
* @return {Number} The radius of the ellipse on the vertical axe.
|
|
*/
|
|
this.getB = function() { return b; };
|
|
/**
|
|
* Get the surface of the ellipse.
|
|
* @return {Number} The surface of the ellipse.
|
|
*/
|
|
this.getSurface = function() { return surface; };
|
|
/**
|
|
* Get the surface of the ellipse according to a spacing.
|
|
* @param {Number} spacingX The X spacing.
|
|
* @param {Number} spacingY The Y spacing.
|
|
* @return {Number} The surface of the ellipse multiplied by the given
|
|
* spacing or null for null spacings.
|
|
*/
|
|
this.getWorldSurface = function(spacingX, spacingY)
|
|
{
|
|
return mulABC(surface, spacingX, spacingY);
|
|
};
|
|
}; // Circle class
|
|
|
|
/**
|
|
* Line shape.
|
|
* @constructor
|
|
* @param {Object} begin A Point2D representing the beginning of the line.
|
|
* @param {Object} end A Point2D representing the end of the line.
|
|
*/
|
|
dwv.math.Line = function(begin, end)
|
|
{
|
|
/**
|
|
* Line delta in the X direction.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var dx = end.getX() - begin.getX();
|
|
/**
|
|
* Line delta in the Y direction.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var dy = end.getY() - begin.getY();
|
|
/**
|
|
* Line length.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var length = Math.sqrt( dx * dx + dy * dy );
|
|
|
|
/**
|
|
* Get the begin point of the line.
|
|
* @return {Object} The beginning point of the line.
|
|
*/
|
|
this.getBegin = function() { return begin; };
|
|
/**
|
|
* Get the end point of the line.
|
|
* @return {Object} The ending point of the line.
|
|
*/
|
|
this.getEnd = function() { return end; };
|
|
/**
|
|
* Get the line delta in the X direction.
|
|
* @return {Number} The delta in the X direction.
|
|
*/
|
|
this.getDeltaX = function() { return dx; };
|
|
/**
|
|
* Get the line delta in the Y direction.
|
|
* @return {Number} The delta in the Y direction.
|
|
*/
|
|
this.getDeltaY = function() { return dy; };
|
|
/**
|
|
* Get the length of the line.
|
|
* @return {Number} The length of the line.
|
|
*/
|
|
this.getLength = function() { return length; };
|
|
/**
|
|
* Get the length of the line according to a spacing.
|
|
* @param {Number} spacingX The X spacing.
|
|
* @param {Number} spacingY The Y spacing.
|
|
* @return {Number} The length of the line with spacing
|
|
* or null for null spacings.
|
|
*/
|
|
this.getWorldLength = function(spacingX, spacingY)
|
|
{
|
|
var wlen = null;
|
|
if (spacingX !== null && spacingY !== null) {
|
|
var dxs = dx * spacingX;
|
|
var dys = dy * spacingY;
|
|
wlen = Math.sqrt( dxs * dxs + dys * dys );
|
|
}
|
|
return wlen;
|
|
};
|
|
/**
|
|
* Get the mid point of the line.
|
|
* @return {Object} The mid point of the line.
|
|
*/
|
|
this.getMidpoint = function()
|
|
{
|
|
return new dwv.math.Point2D(
|
|
parseInt( (begin.getX()+end.getX()) / 2, 10 ),
|
|
parseInt( (begin.getY()+end.getY()) / 2, 10 ) );
|
|
};
|
|
/**
|
|
* Get the slope of the line.
|
|
* @return {Number} The slope of the line.
|
|
*/
|
|
this.getSlope = function()
|
|
{
|
|
return dy / dx;
|
|
};
|
|
/**
|
|
* Get the intercept of the line.
|
|
* @return {Number} The slope of the line.
|
|
*/
|
|
this.getIntercept = function()
|
|
{
|
|
return (end.getX() * begin.getY() - begin.getX() * end.getY()) / dx;
|
|
};
|
|
/**
|
|
* Get the inclination of the line.
|
|
* @return {Number} The inclination of the line.
|
|
*/
|
|
this.getInclination = function()
|
|
{
|
|
// tan(theta) = slope
|
|
var angle = Math.atan2( dy, dx ) * 180 / Math.PI;
|
|
// shift?
|
|
return 180 - angle;
|
|
};
|
|
}; // Line class
|
|
|
|
/**
|
|
* Get the angle between two lines.
|
|
* @param line0 The first line.
|
|
* @param line1 The second line.
|
|
*/
|
|
dwv.math.getAngle = function (line0, line1)
|
|
{
|
|
var dx0 = line0.getDeltaX();
|
|
var dy0 = line0.getDeltaY();
|
|
var dx1 = line1.getDeltaX();
|
|
var dy1 = line1.getDeltaY();
|
|
// dot = ||a||*||b||*cos(theta)
|
|
var dot = dx0 * dx1 + dy0 * dy1;
|
|
// cross = ||a||*||b||*sin(theta)
|
|
var det = dx0 * dy1 - dy0 * dx1;
|
|
// tan = sin / cos
|
|
var angle = Math.atan2( det, dot ) * 180 / Math.PI;
|
|
// complementary angle
|
|
// shift?
|
|
return 360 - (180 - angle);
|
|
};
|
|
|
|
/**
|
|
* Get a perpendicular line to an input one.
|
|
* @param {Object} line The line to be perpendicular to.
|
|
* @param {Object} point The middle point of the perpendicular line.
|
|
* @param {Number} length The length of the perpendicular line.
|
|
*/
|
|
dwv.math.getPerpendicularLine = function (line, point, length)
|
|
{
|
|
// begin point
|
|
var beginX = 0;
|
|
var beginY = 0;
|
|
// end point
|
|
var endX = 0;
|
|
var endY = 0;
|
|
|
|
// check slope:
|
|
// 0 -> horizontal
|
|
// Infinite -> vertical (a/Infinite = 0)
|
|
if ( line.getSlope() !== 0 ) {
|
|
// a0 * a1 = -1
|
|
var slope = -1 / line.getSlope();
|
|
// y0 = a1*x0 + b1 -> b1 = y0 - a1*x0
|
|
var intercept = point.getY() - slope * point.getX();
|
|
|
|
// 1. (x - x0)^2 + (y - y0)^2 = d^2
|
|
// 2. a = (y - y0) / (x - x0) -> y = a*(x - x0) + y0
|
|
// -> (x - x0)^2 + m^2 * (x - x0)^2 = d^2
|
|
// -> x = x0 +- d / sqrt(1+m^2)
|
|
|
|
// length is the distance between begin and end,
|
|
// point is half way between both -> d = length / 2
|
|
var dx = length / ( 2 * Math.sqrt( 1 + slope * slope ) );
|
|
|
|
// begin point
|
|
beginX = point.getX() - dx;
|
|
beginY = slope * beginX + intercept;
|
|
// end point
|
|
endX = point.getX() + dx;
|
|
endY = slope * endX + intercept;
|
|
}
|
|
else {
|
|
// horizontal input line -> perpendicular is vertical!
|
|
// begin point
|
|
beginX = point.getX();
|
|
beginY = point.getY() - length / 2;
|
|
// end point
|
|
endX = point.getX();
|
|
endY = point.getY() + length / 2;
|
|
}
|
|
// perpendicalar line
|
|
return new dwv.math.Line(
|
|
new dwv.math.Point2D(beginX, beginY),
|
|
new dwv.math.Point2D(endX, endY) );
|
|
};
|
|
|
|
/**
|
|
* Rectangle shape.
|
|
* @constructor
|
|
* @param {Object} begin A Point2D representing the beginning of the rectangle.
|
|
* @param {Object} end A Point2D representing the end of the rectangle.
|
|
*/
|
|
dwv.math.Rectangle = function(begin, end)
|
|
{
|
|
if ( end.getX() < begin.getX() ) {
|
|
var tmpX = begin.getX();
|
|
begin = new dwv.math.Point2D( end.getX(), begin.getY() );
|
|
end = new dwv.math.Point2D( tmpX, end.getY() );
|
|
}
|
|
if ( end.getY() < begin.getY() ) {
|
|
var tmpY = begin.getY();
|
|
begin = new dwv.math.Point2D( begin.getX(), end.getY() );
|
|
end = new dwv.math.Point2D( end.getX(), tmpY );
|
|
}
|
|
|
|
/**
|
|
* Rectangle surface.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var surface = Math.abs(end.getX() - begin.getX()) * Math.abs(end.getY() - begin.getY() );
|
|
|
|
/**
|
|
* Get the begin point of the rectangle.
|
|
* @return {Object} The begin point of the rectangle
|
|
*/
|
|
this.getBegin = function() { return begin; };
|
|
/**
|
|
* Get the end point of the rectangle.
|
|
* @return {Object} The end point of the rectangle
|
|
*/
|
|
this.getEnd = function() { return end; };
|
|
/**
|
|
* Get the real width of the rectangle.
|
|
* @return {Number} The real width of the rectangle.
|
|
*/
|
|
this.getRealWidth = function() { return end.getX() - begin.getX(); };
|
|
/**
|
|
* Get the real height of the rectangle.
|
|
* @return {Number} The real height of the rectangle.
|
|
*/
|
|
this.getRealHeight = function() { return end.getY() - begin.getY(); };
|
|
/**
|
|
* Get the width of the rectangle.
|
|
* @return {Number} The width of the rectangle.
|
|
*/
|
|
this.getWidth = function() { return Math.abs(this.getRealWidth()); };
|
|
/**
|
|
* Get the height of the rectangle.
|
|
* @return {Number} The height of the rectangle.
|
|
*/
|
|
this.getHeight = function() { return Math.abs(this.getRealHeight()); };
|
|
/**
|
|
* Get the surface of the rectangle.
|
|
* @return {Number} The surface of the rectangle.
|
|
*/
|
|
this.getSurface = function() { return surface; };
|
|
/**
|
|
* Get the surface of the circle according to a spacing.
|
|
* @param {Number} spacingX The X spacing.
|
|
* @param {Number} spacingY The Y spacing.
|
|
* @return {Number} The surface of the rectangle multiplied by the given
|
|
* spacing or null for null spacings.
|
|
*/
|
|
this.getWorldSurface = function(spacingX, spacingY)
|
|
{
|
|
return mulABC(surface, spacingX, spacingY);
|
|
};
|
|
}; // Rectangle class
|
|
|
|
/**
|
|
* Region Of Interest shape.
|
|
* Note: should be a closed path.
|
|
* @constructor
|
|
*/
|
|
dwv.math.ROI = function()
|
|
{
|
|
/**
|
|
* List of points.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var points = [];
|
|
|
|
/**
|
|
* Get a point of the list at a given index.
|
|
* @param {Number} index The index of the point to get (beware, no size check).
|
|
* @return {Object} The Point2D at the given index.
|
|
*/
|
|
this.getPoint = function(index) { return points[index]; };
|
|
/**
|
|
* Get the length of the point list.
|
|
* @return {Number} The length of the point list.
|
|
*/
|
|
this.getLength = function() { return points.length; };
|
|
/**
|
|
* Add a point to the ROI.
|
|
* @param {Object} point The Point2D to add.
|
|
*/
|
|
this.addPoint = function(point) { points.push(point); };
|
|
/**
|
|
* Add points to the ROI.
|
|
* @param {Array} rhs The array of POints2D to add.
|
|
*/
|
|
this.addPoints = function(rhs) { points=points.concat(rhs);};
|
|
}; // ROI class
|
|
|
|
/**
|
|
* Path shape.
|
|
* @constructor
|
|
* @param {Array} inputPointArray The list of Point2D that make the path (optional).
|
|
* @param {Array} inputControlPointIndexArray The list of control point of path,
|
|
* as indexes (optional).
|
|
* Note: first and last point do not need to be equal.
|
|
*/
|
|
dwv.math.Path = function(inputPointArray, inputControlPointIndexArray)
|
|
{
|
|
/**
|
|
* List of points.
|
|
* @type Array
|
|
*/
|
|
this.pointArray = inputPointArray ? inputPointArray.slice() : [];
|
|
/**
|
|
* List of control points.
|
|
* @type Array
|
|
*/
|
|
this.controlPointIndexArray = inputControlPointIndexArray ?
|
|
inputControlPointIndexArray.slice() : [];
|
|
}; // Path class
|
|
|
|
/**
|
|
* Get a point of the list.
|
|
* @param {Number} index The index of the point to get (beware, no size check).
|
|
* @return {Object} The Point2D at the given index.
|
|
*/
|
|
dwv.math.Path.prototype.getPoint = function(index) {
|
|
return this.pointArray[index];
|
|
};
|
|
|
|
/**
|
|
* Is the given point a control point.
|
|
* @param {Object} point The Point2D to check.
|
|
* @return {Boolean} True if a control point.
|
|
*/
|
|
dwv.math.Path.prototype.isControlPoint = function(point) {
|
|
var index = this.pointArray.indexOf(point);
|
|
if( index !== -1 ) {
|
|
return this.controlPointIndexArray.indexOf(index) !== -1;
|
|
}
|
|
else {
|
|
throw new Error("Error: isControlPoint called with not in list point.");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the length of the path.
|
|
* @return {Number} The length of the path.
|
|
*/
|
|
dwv.math.Path.prototype.getLength = function() {
|
|
return this.pointArray.length;
|
|
};
|
|
|
|
/**
|
|
* Add a point to the path.
|
|
* @param {Object} point The Point2D to add.
|
|
*/
|
|
dwv.math.Path.prototype.addPoint = function(point) {
|
|
this.pointArray.push(point);
|
|
};
|
|
|
|
/**
|
|
* Add a control point to the path.
|
|
* @param {Object} point The Point2D to make a control point.
|
|
*/
|
|
dwv.math.Path.prototype.addControlPoint = function(point) {
|
|
var index = this.pointArray.indexOf(point);
|
|
if( index !== -1 ) {
|
|
this.controlPointIndexArray.push(index);
|
|
}
|
|
else {
|
|
throw new Error("Error: addControlPoint called with no point in list point.");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add points to the path.
|
|
* @param {Array} points The list of Point2D to add.
|
|
*/
|
|
dwv.math.Path.prototype.addPoints = function(newPointArray) {
|
|
this.pointArray = this.pointArray.concat(newPointArray);
|
|
};
|
|
|
|
/**
|
|
* Append a Path to this one.
|
|
* @param {Path} other The Path to append.
|
|
*/
|
|
dwv.math.Path.prototype.appenPath = function(other) {
|
|
var oldSize = this.pointArray.length;
|
|
this.pointArray = this.pointArray.concat(other.pointArray);
|
|
var indexArray = [];
|
|
for( var i=0; i < other.controlPointIndexArray.length; ++i ) {
|
|
indexArray[i] = other.controlPointIndexArray[i] + oldSize;
|
|
}
|
|
this.controlPointIndexArray = this.controlPointIndexArray.concat(indexArray);
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.math = dwv.math || {};
|
|
|
|
/**
|
|
* Get the minimum, maximum, mean and standard deviation
|
|
* of an array of values.
|
|
* Note: could use {@link https://github.com/tmcw/simple-statistics}.
|
|
*/
|
|
dwv.math.getStats = function (array)
|
|
{
|
|
var min = array[0];
|
|
var max = min;
|
|
var mean = 0;
|
|
var sum = 0;
|
|
var sumSqr = 0;
|
|
var stdDev = 0;
|
|
var variance = 0;
|
|
|
|
var val = 0;
|
|
for ( var i = 0; i < array.length; ++i ) {
|
|
val = array[i];
|
|
if ( val < min ) {
|
|
min = val;
|
|
}
|
|
else if ( val > max ) {
|
|
max = val;
|
|
}
|
|
sum += val;
|
|
sumSqr += val * val;
|
|
}
|
|
|
|
mean = sum / array.length;
|
|
// see http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
|
|
variance = sumSqr / array.length - mean * mean;
|
|
stdDev = Math.sqrt(variance);
|
|
|
|
return { 'min': min, 'max': max, 'mean': mean, 'stdDev': stdDev };
|
|
};
|
|
|
|
/**
|
|
* Unique ID generator.
|
|
* See {@link http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript}
|
|
* and this {@link http://stackoverflow.com/a/13403498 answer}.
|
|
*/
|
|
dwv.math.guid = function ()
|
|
{
|
|
return Math.random().toString(36).substring(2, 15);
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.math = dwv.math || {};
|
|
|
|
/**
|
|
* Immutable 3D vector.
|
|
* @constructor
|
|
* @param {Number} x The X component of the vector.
|
|
* @param {Number} y The Y component of the vector.
|
|
* @param {Number} z The Z component of the vector.
|
|
*/
|
|
dwv.math.Vector3D = function (x,y,z)
|
|
{
|
|
/**
|
|
* Get the X component of the vector.
|
|
* @return {Number} The X component of the vector.
|
|
*/
|
|
this.getX = function () { return x; };
|
|
/**
|
|
* Get the Y component of the vector.
|
|
* @return {Number} The Y component of the vector.
|
|
*/
|
|
this.getY = function () { return y; };
|
|
/**
|
|
* Get the Z component of the vector.
|
|
* @return {Number} The Z component of the vector.
|
|
*/
|
|
this.getZ = function () { return z; };
|
|
}; // Vector3D class
|
|
|
|
/**
|
|
* Check for Vector3D equality.
|
|
* @param {Object} rhs The other vector to compare to.
|
|
* @return {Boolean} True if both vectors are equal.
|
|
*/
|
|
dwv.math.Vector3D.prototype.equals = function (rhs) {
|
|
return rhs !== null &&
|
|
this.getX() === rhs.getX() &&
|
|
this.getY() === rhs.getY() &&
|
|
this.getZ() === rhs.getZ();
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the Vector3D.
|
|
* @return {String} The vector as a string.
|
|
*/
|
|
dwv.math.Vector3D.prototype.toString = function () {
|
|
return "(" + this.getX() +
|
|
", " + this.getY() +
|
|
", " + this.getZ() + ")";
|
|
};
|
|
|
|
/**
|
|
* Get the norm of the vector.
|
|
* @return {Number} The norm.
|
|
*/
|
|
dwv.math.Vector3D.prototype.norm = function () {
|
|
return Math.sqrt( (this.getX() * this.getX()) +
|
|
(this.getY() * this.getY()) +
|
|
(this.getZ() * this.getZ()) );
|
|
};
|
|
|
|
/**
|
|
* Get the cross product with another Vector3D, ie the
|
|
* vector that is perpendicular to both a and b.
|
|
* If both vectors are parallel, the cross product is a zero vector.
|
|
* @param {Object} vector3D The input vector.
|
|
* @return {Object} The result vector.
|
|
*/
|
|
dwv.math.Vector3D.prototype.crossProduct = function (vector3D) {
|
|
return new dwv.math.Vector3D(
|
|
(this.getY() * vector3D.getZ()) - (vector3D.getY() * this.getZ()),
|
|
(this.getZ() * vector3D.getX()) - (vector3D.getZ() * this.getX()),
|
|
(this.getX() * vector3D.getY()) - (vector3D.getX() * this.getY()) );
|
|
};
|
|
|
|
/**
|
|
* Get the dot product with another Vector3D.
|
|
* @param {Object} vector3D The input vector.
|
|
* @return {Number} The dot product.
|
|
*/
|
|
dwv.math.Vector3D.prototype.dotProduct = function (vector3D) {
|
|
return (this.getX() * vector3D.getX()) +
|
|
(this.getY() * vector3D.getY()) +
|
|
(this.getZ() * vector3D.getZ());
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Arrow factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.ArrowFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 2; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 0; };
|
|
};
|
|
|
|
/**
|
|
* Create an arrow shape to be displayed.
|
|
* @param {Array} points The points from which to extract the line.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.ArrowFactory.prototype.create = function (points, style/*, image*/)
|
|
{
|
|
// physical shape
|
|
var line = new dwv.math.Line(points[0], points[1]);
|
|
// draw shape
|
|
var kshape = new Konva.Line({
|
|
points: [line.getBegin().getX(), line.getBegin().getY(),
|
|
line.getEnd().getX(), line.getEnd().getY() ],
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape"
|
|
});
|
|
// larger hitfunc
|
|
var linePerp0 = dwv.math.getPerpendicularLine( line, points[0], 10 );
|
|
var linePerp1 = dwv.math.getPerpendicularLine( line, points[1], 10 );
|
|
kshape.hitFunc( function (context) {
|
|
context.beginPath();
|
|
context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
|
|
context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
|
|
context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
|
|
context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
|
|
context.closePath();
|
|
context.fillStrokeShape(this);
|
|
});
|
|
// triangle
|
|
var beginTy = new dwv.math.Point2D(line.getBegin().getX(), line.getBegin().getY() - 10);
|
|
var verticalLine = new dwv.math.Line(line.getBegin(), beginTy);
|
|
var angle = dwv.math.getAngle(line, verticalLine);
|
|
var angleRad = angle * Math.PI / 180;
|
|
var radius = 5;
|
|
var kpoly = new Konva.RegularPolygon({
|
|
x: line.getBegin().getX() + radius * Math.sin(angleRad),
|
|
y: line.getBegin().getY() + radius * Math.cos(angleRad),
|
|
sides: 3,
|
|
radius: radius,
|
|
rotation: -angle,
|
|
fill: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape-triangle"
|
|
});
|
|
// quantification
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "";
|
|
ktext.longText = "";
|
|
ktext.quant = null;
|
|
ktext.setText(ktext.textExpr);
|
|
// label
|
|
var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
|
|
var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
|
|
var klabel = new Konva.Label({
|
|
x: line.getEnd().getX() + dX * 25,
|
|
y: line.getEnd().getY() + dY * 15,
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// return group
|
|
var group = new Konva.Group();
|
|
group.name("line-group");
|
|
group.add(kshape);
|
|
group.add(kpoly);
|
|
group.add(klabel);
|
|
group.visible(true); // dont inherit
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update an arrow shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateArrow = function (anchor/*, image*/)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var kline = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated triangle shape
|
|
var ktriangle = group.getChildren( function (node) {
|
|
return node.name() === 'shape-triangle';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
// find special points
|
|
var begin = group.getChildren( function (node) {
|
|
return node.id() === 'begin';
|
|
})[0];
|
|
var end = group.getChildren( function (node) {
|
|
return node.id() === 'end';
|
|
})[0];
|
|
// update special points
|
|
switch ( anchor.id() ) {
|
|
case 'begin':
|
|
begin.x( anchor.x() );
|
|
begin.y( anchor.y() );
|
|
break;
|
|
case 'end':
|
|
end.x( anchor.x() );
|
|
end.y( anchor.y() );
|
|
break;
|
|
}
|
|
// update shape and compensate for possible drag
|
|
// note: shape.position() and shape.size() won't work...
|
|
var bx = begin.x() - kline.x();
|
|
var by = begin.y() - kline.y();
|
|
var ex = end.x() - kline.x();
|
|
var ey = end.y() - kline.y();
|
|
kline.points( [bx,by,ex,ey] );
|
|
// new line
|
|
var p2d0 = new dwv.math.Point2D(begin.x(), begin.y());
|
|
var p2d1 = new dwv.math.Point2D(end.x(), end.y());
|
|
var line = new dwv.math.Line(p2d0, p2d1);
|
|
// larger hitfunc
|
|
var p2b = new dwv.math.Point2D(bx, by);
|
|
var p2e = new dwv.math.Point2D(ex, ey);
|
|
var linePerp0 = dwv.math.getPerpendicularLine( line, p2b, 10 );
|
|
var linePerp1 = dwv.math.getPerpendicularLine( line, p2e, 10 );
|
|
kline.hitFunc( function (context) {
|
|
context.beginPath();
|
|
context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
|
|
context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
|
|
context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
|
|
context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
|
|
context.closePath();
|
|
context.fillStrokeShape(this);
|
|
});
|
|
// udate triangle
|
|
var beginTy = new dwv.math.Point2D(line.getBegin().getX(), line.getBegin().getY() - 10);
|
|
var verticalLine = new dwv.math.Line(line.getBegin(), beginTy);
|
|
var angle = dwv.math.getAngle(line, verticalLine);
|
|
var angleRad = angle * Math.PI / 180;
|
|
ktriangle.x(line.getBegin().getX() + ktriangle.radius() * Math.sin(angleRad));
|
|
ktriangle.y(line.getBegin().getY() + ktriangle.radius() * Math.cos(angleRad));
|
|
ktriangle.rotation(-angle);
|
|
// update text
|
|
var ktext = klabel.getText();
|
|
ktext.quant = null;
|
|
ktext.setText(ktext.textExpr);
|
|
// update position
|
|
var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
|
|
var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
|
|
var textPos = {
|
|
'x': line.getEnd().getX() + dX * 25,
|
|
'y': line.getEnd().getY() + dY * 15 };
|
|
klabel.position( textPos );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Drawing tool.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.Draw = function (app, shapeFactoryList)
|
|
{
|
|
/**
|
|
* Closure to self: to be used by event handlers.
|
|
* @private
|
|
* @type WindowLevel
|
|
*/
|
|
var self = this;
|
|
/**
|
|
* Draw GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Interaction start flag.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var started = false;
|
|
|
|
/**
|
|
* Shape factory list
|
|
* @type Object
|
|
*/
|
|
this.shapeFactoryList = shapeFactoryList;
|
|
/**
|
|
* Draw command.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var command = null;
|
|
/**
|
|
* Current shape group.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var shapeGroup = null;
|
|
|
|
/**
|
|
* Shape name.
|
|
* @type String
|
|
*/
|
|
this.shapeName = 0;
|
|
|
|
/**
|
|
* List of points.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var points = [];
|
|
|
|
/**
|
|
* Last selected point.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var lastPoint = null;
|
|
|
|
/**
|
|
* Shape editor.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var shapeEditor = new dwv.tool.ShapeEditor(app);
|
|
|
|
// associate the event listeners of the editor
|
|
// with those of the draw tool
|
|
shapeEditor.setDrawEventCallback(fireEvent);
|
|
|
|
/**
|
|
* Trash draw: a cross.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var trash = new Konva.Group();
|
|
|
|
// first line of the cross
|
|
var trashLine1 = new Konva.Line({
|
|
points: [-10, -10, 10, 10 ],
|
|
stroke: 'red'
|
|
});
|
|
// second line of the cross
|
|
var trashLine2 = new Konva.Line({
|
|
points: [10, -10, -10, 10 ],
|
|
stroke: 'red'
|
|
});
|
|
trash.add(trashLine1);
|
|
trash.add(trashLine2);
|
|
|
|
/**
|
|
* Drawing style.
|
|
* @type Style
|
|
*/
|
|
this.style = new dwv.html.Style();
|
|
|
|
/**
|
|
* Event listeners.
|
|
* @private
|
|
*/
|
|
var listeners = {};
|
|
|
|
/**
|
|
* The associated draw layer.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var drawLayer = null;
|
|
|
|
/**
|
|
* Handle mouse down event.
|
|
* @param {Object} event The mouse down event.
|
|
*/
|
|
this.mousedown = function(event){
|
|
// determine if the click happened in an existing shape
|
|
var stage = app.getDrawStage();
|
|
var kshape = stage.getIntersection({
|
|
x: event._xs,
|
|
y: event._ys
|
|
});
|
|
|
|
if ( kshape ) {
|
|
var group = kshape.getParent();
|
|
var selectedShape = group.find(".shape")[0];
|
|
// reset editor if click on other shape
|
|
// (and avoid anchors mouse down)
|
|
if ( selectedShape && selectedShape !== shapeEditor.getShape() ) {
|
|
shapeEditor.disable();
|
|
shapeEditor.setShape(selectedShape);
|
|
shapeEditor.setImage(app.getImage());
|
|
shapeEditor.enable();
|
|
}
|
|
}
|
|
else {
|
|
// disable edition
|
|
shapeEditor.disable();
|
|
shapeEditor.setShape(null);
|
|
shapeEditor.setImage(null);
|
|
// start storing points
|
|
started = true;
|
|
// clear array
|
|
points = [];
|
|
// store point
|
|
lastPoint = new dwv.math.Point2D(event._x, event._y);
|
|
points.push(lastPoint);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {Object} event The mouse move event.
|
|
*/
|
|
this.mousemove = function(event){
|
|
if (!started)
|
|
{
|
|
return;
|
|
}
|
|
if ( Math.abs( event._x - lastPoint.getX() ) > 0 ||
|
|
Math.abs( event._y - lastPoint.getY() ) > 0 )
|
|
{
|
|
// current point
|
|
lastPoint = new dwv.math.Point2D(event._x, event._y);
|
|
// clear last added point from the list (but not the first one)
|
|
if ( points.length != 1 ) {
|
|
points.pop();
|
|
}
|
|
// add current one to the list
|
|
points.push( lastPoint );
|
|
// allow for anchor points
|
|
var factory = new self.shapeFactoryList[self.shapeName]();
|
|
if( points.length < factory.getNPoints() ) {
|
|
clearTimeout(this.timer);
|
|
this.timer = setTimeout( function () {
|
|
points.push( lastPoint );
|
|
}, factory.getTimeout() );
|
|
}
|
|
// remove previous draw
|
|
if ( shapeGroup ) {
|
|
shapeGroup.destroy();
|
|
}
|
|
// create shape group
|
|
shapeGroup = factory.create(points, self.style, app.getImage());
|
|
// do not listen during creation
|
|
var shape = shapeGroup.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
shape.listening(false);
|
|
drawLayer.hitGraphEnabled(false);
|
|
// draw shape command
|
|
command = new dwv.tool.DrawGroupCommand(shapeGroup, self.shapeName, drawLayer, true);
|
|
// draw
|
|
command.execute();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse up event.
|
|
* @param {Object} event The mouse up event.
|
|
*/
|
|
this.mouseup = function (/*event*/){
|
|
if (started && points.length > 1 )
|
|
{
|
|
// reset shape group
|
|
if ( shapeGroup ) {
|
|
shapeGroup.destroy();
|
|
}
|
|
// create final shape
|
|
var factory = new self.shapeFactoryList[self.shapeName]();
|
|
var group = factory.create(points, self.style, app.getImage());
|
|
group.id( dwv.math.guid() );
|
|
// re-activate layer
|
|
drawLayer.hitGraphEnabled(true);
|
|
// draw shape command
|
|
command = new dwv.tool.DrawGroupCommand(group, self.shapeName, drawLayer);
|
|
command.onExecute = fireEvent;
|
|
command.onUndo = fireEvent;
|
|
// execute it
|
|
command.execute();
|
|
// save it in undo stack
|
|
app.addToUndoStack(command);
|
|
|
|
// set shape on
|
|
var shape = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
self.setShapeOn( shape );
|
|
}
|
|
// reset flag
|
|
started = false;
|
|
};
|
|
|
|
/**
|
|
* Handle mouse out event.
|
|
* @param {Object} event The mouse out event.
|
|
*/
|
|
this.mouseout = function(event){
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch start event.
|
|
* @param {Object} event The touch start event.
|
|
*/
|
|
this.touchstart = function(event){
|
|
self.mousedown(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.touchmove = function(event){
|
|
self.mousemove(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch end event.
|
|
* @param {Object} event The touch end event.
|
|
*/
|
|
this.touchend = function(event){
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.keydown = function(event){
|
|
app.onKeydown(event);
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui = new dwv.gui.Draw(app);
|
|
gui.setup(this.shapeFactoryList);
|
|
};
|
|
|
|
/**
|
|
* Enable the tool.
|
|
* @param {Boolean} flag The flag to enable or not.
|
|
*/
|
|
this.display = function ( flag ){
|
|
if ( gui ) {
|
|
gui.display( flag );
|
|
}
|
|
// reset shape display properties
|
|
shapeEditor.disable();
|
|
shapeEditor.setShape(null);
|
|
shapeEditor.setImage(null);
|
|
document.body.style.cursor = 'default';
|
|
// make layer listen or not to events
|
|
app.getDrawStage().listening( flag );
|
|
// get the current draw layer
|
|
drawLayer = app.getCurrentDrawLayer();
|
|
renderDrawLayer(flag);
|
|
// listen to app change to update the draw layer
|
|
if (flag) {
|
|
app.addEventListener("slice-change", updateDrawLayer);
|
|
app.addEventListener("frame-change", updateDrawLayer);
|
|
}
|
|
else {
|
|
app.removeEventListener("slice-change", updateDrawLayer);
|
|
app.removeEventListener("frame-change", updateDrawLayer);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the current app draw layer.
|
|
*/
|
|
function updateDrawLayer() {
|
|
// deactivate the old draw layer
|
|
renderDrawLayer(false);
|
|
// get the current draw layer
|
|
drawLayer = app.getCurrentDrawLayer();
|
|
// activate the new draw layer
|
|
renderDrawLayer(true);
|
|
}
|
|
|
|
/**
|
|
* Render (or not) the draw layer.
|
|
* @param {Boolean} visible Set the draw layer visible or not.
|
|
*/
|
|
function renderDrawLayer(visible) {
|
|
drawLayer.listening( visible );
|
|
drawLayer.hitGraphEnabled( visible );
|
|
// get the list of shapes
|
|
var groups = drawLayer.getChildren();
|
|
var shapes = [];
|
|
var fshape = function (node) {
|
|
return node.name() === 'shape';
|
|
};
|
|
for ( var i = 0; i < groups.length; ++i ) {
|
|
// should only be one shape per group
|
|
shapes.push( groups[i].getChildren(fshape)[0] );
|
|
}
|
|
// set shape display properties
|
|
if ( visible ) {
|
|
app.addToolCanvasListeners( app.getDrawStage().getContent() );
|
|
shapes.forEach( function (shape){ self.setShapeOn( shape ); });
|
|
}
|
|
else {
|
|
app.removeToolCanvasListeners( app.getDrawStage().getContent() );
|
|
shapes.forEach( function (shape){ setShapeOff( shape ); });
|
|
}
|
|
// draw
|
|
drawLayer.draw();
|
|
}
|
|
|
|
/**
|
|
* Set shape off properties.
|
|
* @param {Object} shape The shape to set off.
|
|
*/
|
|
function setShapeOff( shape ) {
|
|
// mouse styling
|
|
shape.off('mouseover');
|
|
shape.off('mouseout');
|
|
// drag
|
|
shape.draggable(false);
|
|
shape.off('dragstart');
|
|
shape.off('dragmove');
|
|
shape.off('dragend');
|
|
shape.off('dblclick');
|
|
}
|
|
|
|
/**
|
|
* Get the real position from an event.
|
|
*/
|
|
function getRealPosition( index ) {
|
|
var stage = app.getDrawStage();
|
|
return { 'x': stage.offset().x + index.x / stage.scale().x,
|
|
'y': stage.offset().y + index.y / stage.scale().y };
|
|
}
|
|
|
|
/**
|
|
* Set shape on properties.
|
|
* @param {Object} shape The shape to set on.
|
|
*/
|
|
this.setShapeOn = function ( shape ) {
|
|
// mouse over styling
|
|
shape.on('mouseover', function () {
|
|
document.body.style.cursor = 'pointer';
|
|
});
|
|
// mouse out styling
|
|
shape.on('mouseout', function () {
|
|
document.body.style.cursor = 'default';
|
|
});
|
|
|
|
// make it draggable
|
|
shape.draggable(true);
|
|
var dragStartPos = null;
|
|
var dragLastPos = null;
|
|
|
|
// command name based on shape type
|
|
var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape);
|
|
|
|
// store original colour
|
|
var colour = null;
|
|
|
|
// save start position
|
|
dragStartPos = {'x': shape.x(), 'y': shape.y()};
|
|
|
|
// drag start event handling
|
|
shape.on('dragstart', function (/*event*/) {
|
|
// colour
|
|
colour = shape.stroke();
|
|
// display trash
|
|
var stage = app.getDrawStage();
|
|
var scale = stage.scale();
|
|
var invscale = {'x': 1/scale.x, 'y': 1/scale.y};
|
|
trash.x( stage.offset().x + ( 256 / scale.x ) );
|
|
trash.y( stage.offset().y + ( 20 / scale.y ) );
|
|
trash.scale( invscale );
|
|
drawLayer.add( trash );
|
|
// deactivate anchors to avoid events on null shape
|
|
shapeEditor.setAnchorsActive(false);
|
|
// draw
|
|
drawLayer.draw();
|
|
});
|
|
// drag move event handling
|
|
shape.on('dragmove', function (event) {
|
|
var pos = {'x': this.x(), 'y': this.y()};
|
|
var translation;
|
|
if ( dragLastPos ) {
|
|
translation = {'x': pos.x - dragLastPos.x,
|
|
'y': pos.y - dragLastPos.y};
|
|
} else {
|
|
translation = {'x': pos.x - dragStartPos.x,
|
|
'y': pos.y - dragStartPos.y};
|
|
}
|
|
dragLastPos = pos;
|
|
// highlight trash when on it
|
|
var offset = dwv.html.getEventOffset( event.evt )[0];
|
|
var eventPos = getRealPosition( offset );
|
|
if ( Math.abs( eventPos.x - trash.x() ) < 10 &&
|
|
Math.abs( eventPos.y - trash.y() ) < 10 ) {
|
|
trash.getChildren().each( function (tshape){ tshape.stroke('orange'); });
|
|
shape.stroke('red');
|
|
}
|
|
else {
|
|
trash.getChildren().each( function (tshape){ tshape.stroke('red'); });
|
|
shape.stroke(colour);
|
|
}
|
|
// update group but not 'this' shape
|
|
var group = this.getParent();
|
|
group.getChildren().each( function (ashape) {
|
|
if ( ashape === shape ) {
|
|
return;
|
|
}
|
|
ashape.x( ashape.x() + translation.x );
|
|
ashape.y( ashape.y() + translation.y );
|
|
});
|
|
// reset anchors
|
|
shapeEditor.resetAnchors();
|
|
// draw
|
|
drawLayer.draw();
|
|
});
|
|
// drag end event handling
|
|
shape.on('dragend', function (event) {
|
|
var pos = {'x': this.x(), 'y': this.y()};
|
|
dragLastPos = null;
|
|
// remove trash
|
|
trash.remove();
|
|
// delete case
|
|
var offset = dwv.html.getEventOffset( event.evt )[0];
|
|
var eventPos = getRealPosition( offset );
|
|
if ( Math.abs( eventPos.x - trash.x() ) < 10 &&
|
|
Math.abs( eventPos.y - trash.y() ) < 10 ) {
|
|
// compensate for the drag translation
|
|
var delTranslation = {'x': eventPos.x - dragStartPos.x,
|
|
'y': eventPos.y - dragStartPos.y};
|
|
var group = this.getParent();
|
|
group.getChildren().each( function (ashape) {
|
|
ashape.x( ashape.x() - delTranslation.x );
|
|
ashape.y( ashape.y() - delTranslation.y );
|
|
});
|
|
// disable editor
|
|
shapeEditor.disable();
|
|
shapeEditor.setShape(null);
|
|
shapeEditor.setImage(null);
|
|
// reset
|
|
shape.stroke(colour);
|
|
document.body.style.cursor = 'default';
|
|
// delete command
|
|
var delcmd = new dwv.tool.DeleteGroupCommand(this.getParent(),
|
|
shapeDisplayName, drawLayer);
|
|
delcmd.onExecute = fireEvent;
|
|
delcmd.onUndo = fireEvent;
|
|
delcmd.execute();
|
|
app.addToUndoStack(delcmd);
|
|
}
|
|
else {
|
|
// save drag move
|
|
var translation = {'x': pos.x - dragStartPos.x,
|
|
'y': pos.y - dragStartPos.y};
|
|
if ( translation.x !== 0 || translation.y !== 0 ) {
|
|
var mvcmd = new dwv.tool.MoveGroupCommand(this.getParent(),
|
|
shapeDisplayName, translation, drawLayer);
|
|
mvcmd.onExecute = fireEvent;
|
|
mvcmd.onUndo = fireEvent;
|
|
app.addToUndoStack(mvcmd);
|
|
|
|
// reset start position
|
|
dragStartPos = {'x': this.x(), 'y': this.y()};
|
|
// the move is handled by Konva, trigger an event manually
|
|
fireEvent({'type': 'draw-move'});
|
|
}
|
|
// reset anchors
|
|
shapeEditor.setAnchorsActive(true);
|
|
shapeEditor.resetAnchors();
|
|
}
|
|
// draw
|
|
drawLayer.draw();
|
|
});
|
|
// double click handling: update label
|
|
shape.on('dblclick', function () {
|
|
|
|
// get the label object for this shape
|
|
var group = this.getParent();
|
|
var labels = group.find('Label');
|
|
// should just be one
|
|
if (labels.length !== 1) {
|
|
throw new Error("Could not find the shape label.");
|
|
}
|
|
var ktext = labels[0].getText();
|
|
|
|
// ask user for new label
|
|
var labelText = dwv.gui.prompt("Shape label", ktext.textExpr);
|
|
|
|
// if press cancel do nothing
|
|
if (labelText === null) {
|
|
return;
|
|
}
|
|
else if (labelText === ktext.textExpr) {
|
|
return;
|
|
}
|
|
// update text expression and set text
|
|
ktext.textExpr = labelText;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
|
|
// trigger event
|
|
fireEvent({'type': 'draw-change'});
|
|
|
|
// draw
|
|
drawLayer.draw();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool.
|
|
*/
|
|
this.init = function() {
|
|
// set the default to the first in the list
|
|
var shapeName = 0;
|
|
for( var key in this.shapeFactoryList ){
|
|
shapeName = key;
|
|
break;
|
|
}
|
|
this.setShapeName(shapeName);
|
|
// init gui
|
|
if ( gui ) {
|
|
// init with the app window scale
|
|
this.style.setScale(app.getWindowScale());
|
|
// same for colour
|
|
this.setLineColour(this.style.getLineColour());
|
|
// init html
|
|
gui.initialise();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Add an event listener on the app.
|
|
* @param {String} type The event type.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.addEventListener = function (type, listener)
|
|
{
|
|
if ( typeof listeners[type] === "undefined" ) {
|
|
listeners[type] = [];
|
|
}
|
|
listeners[type].push(listener);
|
|
};
|
|
|
|
/**
|
|
* Remove an event listener from the app.
|
|
* @param {String} type The event type.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (type, listener)
|
|
{
|
|
if( typeof listeners[type] === "undefined" ) {
|
|
return;
|
|
}
|
|
for ( var i = 0; i < listeners[type].length; ++i )
|
|
{
|
|
if ( listeners[type][i] === listener ) {
|
|
listeners[type].splice(i,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the line colour of the drawing.
|
|
* @param {String} colour The colour to set.
|
|
*/
|
|
this.setLineColour = function (colour)
|
|
{
|
|
this.style.setLineColour(colour);
|
|
};
|
|
|
|
// Private Methods -----------------------------------------------------------
|
|
|
|
/**
|
|
* Fire an event: call all associated listeners.
|
|
* @param {Object} event The event to fire.
|
|
*/
|
|
function fireEvent (event)
|
|
{
|
|
if ( typeof listeners[event.type] === "undefined" ) {
|
|
return;
|
|
}
|
|
for ( var i=0; i < listeners[event.type].length; ++i )
|
|
{
|
|
listeners[event.type][i](event);
|
|
}
|
|
}
|
|
|
|
}; // Draw class
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.Draw.prototype.getHelp = function()
|
|
{
|
|
return {
|
|
"title": dwv.i18n("tool.Draw.name"),
|
|
"brief": dwv.i18n("tool.Draw.brief"),
|
|
"mouse": {
|
|
"mouse_drag": dwv.i18n("tool.Draw.mouse_drag")
|
|
},
|
|
"touch": {
|
|
"touch_drag": dwv.i18n("tool.Draw.touch_drag")
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Set the shape name of the drawing.
|
|
* @param {String} name The name of the shape.
|
|
*/
|
|
dwv.tool.Draw.prototype.setShapeName = function(name)
|
|
{
|
|
// check if we have it
|
|
if( !this.hasShape(name) )
|
|
{
|
|
throw new Error("Unknown shape: '" + name + "'");
|
|
}
|
|
this.shapeName = name;
|
|
};
|
|
|
|
/**
|
|
* Check if the shape is in the shape list.
|
|
* @param {String} name The name of the shape.
|
|
*/
|
|
dwv.tool.Draw.prototype.hasShape = function(name) {
|
|
return this.shapeFactoryList[name];
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Get the display name of the input shape.
|
|
* @param {Object} shape The Konva shape.
|
|
* @return {String} The display name.
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.GetShapeDisplayName = function (shape)
|
|
{
|
|
var displayName = "shape";
|
|
if ( shape instanceof Konva.Line ) {
|
|
if ( shape.points().length === 4 ) {
|
|
displayName = "line";
|
|
}
|
|
else if ( shape.points().length === 6 ) {
|
|
displayName = "protractor";
|
|
}
|
|
else {
|
|
displayName = "roi";
|
|
}
|
|
}
|
|
else if ( shape instanceof Konva.Rect ) {
|
|
displayName = "rectangle";
|
|
}
|
|
else if ( shape instanceof Konva.Ellipse ) {
|
|
displayName = "ellipse";
|
|
}
|
|
// return
|
|
return displayName;
|
|
};
|
|
|
|
/**
|
|
* Draw group command.
|
|
* @param {Object} group The group draw.
|
|
* @param {String} name The shape display name.
|
|
* @param {Object} layer The layer where to draw the group.
|
|
* @param {Object} silent Whether to send a creation event or not.
|
|
* @constructor
|
|
*/
|
|
dwv.tool.DrawGroupCommand = function (group, name, layer, silent)
|
|
{
|
|
var isSilent = (typeof silent === "undefined") ? false : true;
|
|
|
|
/**
|
|
* Get the command name.
|
|
* @return {String} The command name.
|
|
*/
|
|
this.getName = function () { return "Draw-"+name; };
|
|
/**
|
|
* Execute the command.
|
|
*/
|
|
this.execute = function () {
|
|
// add the group to the layer
|
|
layer.add(group);
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
if (!isSilent) {
|
|
this.onExecute({'type': 'draw-create', 'id': group.id()});
|
|
}
|
|
};
|
|
/**
|
|
* Undo the command.
|
|
*/
|
|
this.undo = function () {
|
|
// remove the group from the parent layer
|
|
group.remove();
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onUndo({'type': 'draw-delete', 'id': group.id()});
|
|
};
|
|
}; // DrawGroupCommand class
|
|
|
|
/**
|
|
* Handle an execute event.
|
|
* @param {Object} event The execute event with type and id.
|
|
*/
|
|
dwv.tool.DrawGroupCommand.prototype.onExecute = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
/**
|
|
* Handle an undo event.
|
|
* @param {Object} event The undo event with type and id.
|
|
*/
|
|
dwv.tool.DrawGroupCommand.prototype.onUndo = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Move group command.
|
|
* @param {Object} group The group draw.
|
|
* @param {String} name The shape display name.
|
|
* @param {Object} translation A 2D translation to move the group by.
|
|
* @param {Object} layer The layer where to move the group.
|
|
* @constructor
|
|
*/
|
|
dwv.tool.MoveGroupCommand = function (group, name, translation, layer)
|
|
{
|
|
/**
|
|
* Get the command name.
|
|
* @return {String} The command name.
|
|
*/
|
|
this.getName = function () { return "Move-"+name; };
|
|
|
|
/**
|
|
* Execute the command.
|
|
*/
|
|
this.execute = function () {
|
|
// translate all children of group
|
|
group.getChildren().each( function (shape) {
|
|
shape.x( shape.x() + translation.x );
|
|
shape.y( shape.y() + translation.y );
|
|
});
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onExecute({'type': 'draw-move', 'id': group.id()});
|
|
};
|
|
/**
|
|
* Undo the command.
|
|
*/
|
|
this.undo = function () {
|
|
// invert translate all children of group
|
|
group.getChildren().each( function (shape) {
|
|
shape.x( shape.x() - translation.x );
|
|
shape.y( shape.y() - translation.y );
|
|
});
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onUndo({'type': 'draw-move', 'id': group.id()});
|
|
};
|
|
}; // MoveGroupCommand class
|
|
|
|
/**
|
|
* Handle an execute event.
|
|
* @param {Object} event The execute event with type and id.
|
|
*/
|
|
dwv.tool.MoveGroupCommand.prototype.onExecute = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
/**
|
|
* Handle an undo event.
|
|
* @param {Object} event The undo event with type and id.
|
|
*/
|
|
dwv.tool.MoveGroupCommand.prototype.onUndo = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Change group command.
|
|
* @param {String} name The shape display name.
|
|
* @param {Object} func The change function.
|
|
* @param {Object} startAnchor The anchor that starts the change.
|
|
* @param {Object} endAnchor The anchor that ends the change.
|
|
* @param {Object} layer The layer where to change the group.
|
|
* @param {Object} image The associated image.
|
|
* @constructor
|
|
*/
|
|
dwv.tool.ChangeGroupCommand = function (name, func, startAnchor, endAnchor, layer, image)
|
|
{
|
|
/**
|
|
* Get the command name.
|
|
* @return {String} The command name.
|
|
*/
|
|
this.getName = function () { return "Change-"+name; };
|
|
|
|
/**
|
|
* Execute the command.
|
|
*/
|
|
this.execute = function () {
|
|
// change shape
|
|
func( endAnchor, image );
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onExecute({'type': 'draw-change'});
|
|
};
|
|
/**
|
|
* Undo the command.
|
|
*/
|
|
this.undo = function () {
|
|
// invert change shape
|
|
func( startAnchor, image );
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onUndo({'type': 'draw-change'});
|
|
};
|
|
}; // ChangeGroupCommand class
|
|
|
|
/**
|
|
* Handle an execute event.
|
|
* @param {Object} event The execute event with type and id.
|
|
*/
|
|
dwv.tool.ChangeGroupCommand.prototype.onExecute = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
/**
|
|
* Handle an undo event.
|
|
* @param {Object} event The undo event with type and id.
|
|
*/
|
|
dwv.tool.ChangeGroupCommand.prototype.onUndo = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
/**
|
|
* Delete group command.
|
|
* @param {Object} group The group draw.
|
|
* @param {String} name The shape display name.
|
|
* @param {Object} layer The layer where to delete the group.
|
|
* @constructor
|
|
*/
|
|
dwv.tool.DeleteGroupCommand = function (group, name, layer)
|
|
{
|
|
/**
|
|
* Get the command name.
|
|
* @return {String} The command name.
|
|
*/
|
|
this.getName = function () { return "Delete-"+name; };
|
|
/**
|
|
* Execute the command.
|
|
*/
|
|
this.execute = function () {
|
|
// remove the group from the parent layer
|
|
group.remove();
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onExecute({'type': 'draw-delete', 'id': group.id()});
|
|
};
|
|
/**
|
|
* Undo the command.
|
|
*/
|
|
this.undo = function () {
|
|
// add the group to the layer
|
|
layer.add(group);
|
|
// draw
|
|
layer.draw();
|
|
// callback
|
|
this.onUndo({'type': 'draw-create', 'id': group.id()});
|
|
};
|
|
}; // DeleteGroupCommand class
|
|
|
|
/**
|
|
* Handle an execute event.
|
|
* @param {Object} event The execute event with type and id.
|
|
*/
|
|
dwv.tool.DeleteGroupCommand.prototype.onExecute = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
/**
|
|
* Handle an undo event.
|
|
* @param {Object} event The undo event with type and id.
|
|
*/
|
|
dwv.tool.DeleteGroupCommand.prototype.onUndo = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Shape editor.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.ShapeEditor = function (app)
|
|
{
|
|
/**
|
|
* Edited shape.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var shape = null;
|
|
/**
|
|
* Edited image. Used for quantification update.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var image = null;
|
|
/**
|
|
* Active flag.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var isActive = false;
|
|
/**
|
|
* Update function used by anchors to update the shape.
|
|
* @private
|
|
* @type Function
|
|
*/
|
|
var updateFunction = null;
|
|
/**
|
|
* Draw event callback.
|
|
* @private
|
|
* @type Function
|
|
*/
|
|
var drawEventCallback = null;
|
|
|
|
/**
|
|
* Set the shape to edit.
|
|
* @param {Object} inshape The shape to edit.
|
|
*/
|
|
this.setShape = function ( inshape ) {
|
|
shape = inshape;
|
|
// reset anchors
|
|
if ( shape ) {
|
|
removeAnchors();
|
|
addAnchors();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the associated image.
|
|
* @param {Object} img The associated image.
|
|
*/
|
|
this.setImage = function ( img ) {
|
|
image = img;
|
|
};
|
|
|
|
/**
|
|
* Get the edited shape.
|
|
* @return {Object} The edited shape.
|
|
*/
|
|
this.getShape = function () {
|
|
return shape;
|
|
};
|
|
|
|
/**
|
|
* Get the active flag.
|
|
* @return {Boolean} The active flag.
|
|
*/
|
|
this.isActive = function () {
|
|
return isActive;
|
|
};
|
|
|
|
/**
|
|
* Set the draw event callback.
|
|
* @param {Object} callback The callback.
|
|
*/
|
|
this.setDrawEventCallback = function ( callback ) {
|
|
drawEventCallback = callback;
|
|
};
|
|
|
|
/**
|
|
* Enable the editor. Redraws the layer.
|
|
*/
|
|
this.enable = function () {
|
|
isActive = true;
|
|
if ( shape ) {
|
|
setAnchorsVisible( true );
|
|
if ( shape.getLayer() ) {
|
|
shape.getLayer().draw();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Disable the editor. Redraws the layer.
|
|
*/
|
|
this.disable = function () {
|
|
isActive = false;
|
|
if ( shape ) {
|
|
setAnchorsVisible( false );
|
|
if ( shape.getLayer() ) {
|
|
shape.getLayer().draw();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset the anchors.
|
|
*/
|
|
this.resetAnchors = function () {
|
|
// remove previous controls
|
|
removeAnchors();
|
|
// add anchors
|
|
addAnchors();
|
|
// set them visible
|
|
setAnchorsVisible( true );
|
|
};
|
|
|
|
/**
|
|
* Apply a function on all anchors.
|
|
* @param {Object} func A f(shape) function.
|
|
*/
|
|
function applyFuncToAnchors( func ) {
|
|
if ( shape && shape.getParent() ) {
|
|
var anchors = shape.getParent().find('.anchor');
|
|
anchors.each( func );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set anchors visibility.
|
|
* @param {Boolean} flag The visible flag.
|
|
*/
|
|
function setAnchorsVisible( flag ) {
|
|
applyFuncToAnchors( function (anchor) {
|
|
anchor.visible( flag );
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set anchors active.
|
|
* @param {Boolean} flag The active (on/off) flag.
|
|
*/
|
|
this.setAnchorsActive = function ( flag ) {
|
|
var func = null;
|
|
if ( flag ) {
|
|
func = function (anchor) {
|
|
setAnchorOn( anchor );
|
|
};
|
|
}
|
|
else {
|
|
func = function (anchor) {
|
|
setAnchorOff( anchor );
|
|
};
|
|
}
|
|
applyFuncToAnchors( func );
|
|
};
|
|
|
|
/**
|
|
* Remove anchors.
|
|
*/
|
|
function removeAnchors() {
|
|
applyFuncToAnchors( function (anchor) {
|
|
anchor.remove();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add the shape anchors.
|
|
*/
|
|
function addAnchors() {
|
|
// exit if no shape or no layer
|
|
if ( !shape || !shape.getLayer() ) {
|
|
return;
|
|
}
|
|
// get shape group
|
|
var group = shape.getParent();
|
|
// add shape specific anchors to the shape group
|
|
if ( shape instanceof Konva.Line ) {
|
|
var points = shape.points();
|
|
if ( points.length === 4 || points.length === 6) {
|
|
// add shape offset
|
|
var p0x = points[0] + shape.x();
|
|
var p0y = points[1] + shape.y();
|
|
var p1x = points[2] + shape.x();
|
|
var p1y = points[3] + shape.y();
|
|
addAnchor(group, p0x, p0y, 'begin');
|
|
if ( points.length === 4 ) {
|
|
var shapekids = group.getChildren( function ( node ) {
|
|
return node.name().startsWith("shape-");
|
|
});
|
|
if (shapekids.length === 2) {
|
|
updateFunction = dwv.tool.UpdateRuler;
|
|
} else {
|
|
updateFunction = dwv.tool.UpdateArrow;
|
|
}
|
|
addAnchor(group, p1x, p1y, 'end');
|
|
}
|
|
else {
|
|
updateFunction = dwv.tool.UpdateProtractor;
|
|
addAnchor(group, p1x, p1y, 'mid');
|
|
var p2x = points[4] + shape.x();
|
|
var p2y = points[5] + shape.y();
|
|
addAnchor(group, p2x, p2y, 'end');
|
|
}
|
|
}
|
|
else {
|
|
updateFunction = dwv.tool.UpdateRoi;
|
|
var px = 0;
|
|
var py = 0;
|
|
for ( var i = 0; i < points.length; i=i+2 ) {
|
|
px = points[i] + shape.x();
|
|
py = points[i+1] + shape.y();
|
|
addAnchor(group, px, py, i);
|
|
}
|
|
}
|
|
}
|
|
else if ( shape instanceof Konva.Rect ) {
|
|
updateFunction = dwv.tool.UpdateRect;
|
|
var rectX = shape.x();
|
|
var rectY = shape.y();
|
|
var rectWidth = shape.width();
|
|
var rectHeight = shape.height();
|
|
addAnchor(group, rectX, rectY, 'topLeft');
|
|
addAnchor(group, rectX+rectWidth, rectY, 'topRight');
|
|
addAnchor(group, rectX+rectWidth, rectY+rectHeight, 'bottomRight');
|
|
addAnchor(group, rectX, rectY+rectHeight, 'bottomLeft');
|
|
}
|
|
else if ( shape instanceof Konva.Ellipse ) {
|
|
updateFunction = dwv.tool.UpdateEllipse;
|
|
var ellipseX = shape.x();
|
|
var ellipseY = shape.y();
|
|
var radius = shape.radius();
|
|
addAnchor(group, ellipseX-radius.x, ellipseY-radius.y, 'topLeft');
|
|
addAnchor(group, ellipseX+radius.x, ellipseY-radius.y, 'topRight');
|
|
addAnchor(group, ellipseX+radius.x, ellipseY+radius.y, 'bottomRight');
|
|
addAnchor(group, ellipseX-radius.x, ellipseY+radius.y, 'bottomLeft');
|
|
}
|
|
// add group to layer
|
|
shape.getLayer().add( group );
|
|
}
|
|
|
|
/**
|
|
* Create shape editor controls, i.e. the anchors.
|
|
* @param {Object} group The group associated with this anchor.
|
|
* @param {Number} x The X position of the anchor.
|
|
* @param {Number} y The Y position of the anchor.
|
|
* @param {Number} id The id of the anchor.
|
|
*/
|
|
function addAnchor(group, x, y, id) {
|
|
// anchor shape
|
|
var anchor = new Konva.Circle({
|
|
x: x,
|
|
y: y,
|
|
stroke: '#999',
|
|
fill: 'rgba(100,100,100,0.7',
|
|
strokeWidth: app.getStyle().getScaledStrokeWidth() / app.getScale(),
|
|
radius: app.getStyle().scale(6) / app.getScale(),
|
|
name: 'anchor',
|
|
id: id,
|
|
dragOnTop: false,
|
|
draggable: true,
|
|
visible: false
|
|
});
|
|
// set anchor on
|
|
setAnchorOn( anchor );
|
|
// add the anchor to the group
|
|
group.add(anchor);
|
|
}
|
|
|
|
/**
|
|
* Get a simple clone of the input anchor.
|
|
* @param {Object} anchor The anchor to clone.
|
|
*/
|
|
function getClone( anchor ) {
|
|
// create closure to properties
|
|
var parent = anchor.getParent();
|
|
var id = anchor.id();
|
|
var x = anchor.x();
|
|
var y = anchor.y();
|
|
// create clone object
|
|
var clone = {};
|
|
clone.getParent = function () {
|
|
return parent;
|
|
};
|
|
clone.id = function () {
|
|
return id;
|
|
};
|
|
clone.x = function () {
|
|
return x;
|
|
};
|
|
clone.y = function () {
|
|
return y;
|
|
};
|
|
return clone;
|
|
}
|
|
|
|
/**
|
|
* Set the anchor on listeners.
|
|
* @param {Object} anchor The anchor to set on.
|
|
*/
|
|
function setAnchorOn( anchor ) {
|
|
var startAnchor = null;
|
|
|
|
// command name based on shape type
|
|
var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape);
|
|
|
|
// drag start listener
|
|
anchor.on('dragstart', function () {
|
|
startAnchor = getClone(this);
|
|
});
|
|
// drag move listener
|
|
anchor.on('dragmove', function () {
|
|
if ( updateFunction ) {
|
|
updateFunction(this, image);
|
|
}
|
|
if ( this.getLayer() ) {
|
|
this.getLayer().draw();
|
|
}
|
|
else {
|
|
console.warn("No layer to draw the anchor!");
|
|
}
|
|
});
|
|
// drag end listener
|
|
anchor.on('dragend', function () {
|
|
var endAnchor = getClone(this);
|
|
// store the change command
|
|
var chgcmd = new dwv.tool.ChangeGroupCommand(
|
|
shapeDisplayName, updateFunction, startAnchor, endAnchor, this.getLayer(), image);
|
|
chgcmd.onExecute = drawEventCallback;
|
|
chgcmd.onUndo = drawEventCallback;
|
|
chgcmd.execute();
|
|
app.addToUndoStack(chgcmd);
|
|
// reset start anchor
|
|
startAnchor = endAnchor;
|
|
});
|
|
// mouse down listener
|
|
anchor.on('mousedown touchstart', function () {
|
|
this.moveToTop();
|
|
});
|
|
// mouse over styling
|
|
anchor.on('mouseover', function () {
|
|
document.body.style.cursor = 'pointer';
|
|
this.stroke('#ddd');
|
|
if ( this.getLayer() ) {
|
|
this.getLayer().draw();
|
|
}
|
|
else {
|
|
console.warn("No layer to draw the anchor!");
|
|
}
|
|
});
|
|
// mouse out styling
|
|
anchor.on('mouseout', function () {
|
|
document.body.style.cursor = 'default';
|
|
this.stroke('#999');
|
|
if ( this.getLayer() ) {
|
|
this.getLayer().draw();
|
|
}
|
|
else {
|
|
console.warn("No layer to draw the anchor!");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the anchor off listeners.
|
|
* @param {Object} anchor The anchor to set off.
|
|
*/
|
|
function setAnchorOff( anchor ) {
|
|
anchor.off('dragstart');
|
|
anchor.off('dragmove');
|
|
anchor.off('dragend');
|
|
anchor.off('mousedown touchstart');
|
|
anchor.off('mouseover');
|
|
anchor.off('mouseout');
|
|
}
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Ellipse factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.EllipseFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 2; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 0; };
|
|
};
|
|
|
|
/**
|
|
* Create an ellipse shape to be displayed.
|
|
* @param {Array} points The points from which to extract the ellipse.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.EllipseFactory.prototype.create = function (points, style, image)
|
|
{
|
|
// calculate radius
|
|
var a = Math.abs(points[0].getX() - points[1].getX());
|
|
var b = Math.abs(points[0].getY() - points[1].getY());
|
|
// physical shape
|
|
var ellipse = new dwv.math.Ellipse(points[0], a, b);
|
|
// draw shape
|
|
var kshape = new Konva.Ellipse({
|
|
x: ellipse.getCenter().getX(),
|
|
y: ellipse.getCenter().getY(),
|
|
radius: { x: ellipse.getA(), y: ellipse.getB() },
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape"
|
|
});
|
|
// quantification
|
|
var quant = image.quantifyEllipse( ellipse );
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "{surface}";
|
|
ktext.longText = "";
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// label
|
|
var klabel = new Konva.Label({
|
|
x: ellipse.getCenter().getX(),
|
|
y: ellipse.getCenter().getY(),
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// return group
|
|
var group = new Konva.Group();
|
|
group.name("ellipse-group");
|
|
group.add(kshape);
|
|
group.add(klabel);
|
|
group.visible(true); // dont inherit
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update an ellipse shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateEllipse = function (anchor, image)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var kellipse = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
// find special points
|
|
var topLeft = group.getChildren( function (node) {
|
|
return node.id() === 'topLeft';
|
|
})[0];
|
|
var topRight = group.getChildren( function (node) {
|
|
return node.id() === 'topRight';
|
|
})[0];
|
|
var bottomRight = group.getChildren( function (node) {
|
|
return node.id() === 'bottomRight';
|
|
})[0];
|
|
var bottomLeft = group.getChildren( function (node) {
|
|
return node.id() === 'bottomLeft';
|
|
})[0];
|
|
// update 'self' (undo case) and special points
|
|
switch ( anchor.id() ) {
|
|
case 'topLeft':
|
|
topLeft.x( anchor.x() );
|
|
topLeft.y( anchor.y() );
|
|
topRight.y( anchor.y() );
|
|
bottomLeft.x( anchor.x() );
|
|
break;
|
|
case 'topRight':
|
|
topRight.x( anchor.x() );
|
|
topRight.y( anchor.y() );
|
|
topLeft.y( anchor.y() );
|
|
bottomRight.x( anchor.x() );
|
|
break;
|
|
case 'bottomRight':
|
|
bottomRight.x( anchor.x() );
|
|
bottomRight.y( anchor.y() );
|
|
bottomLeft.y( anchor.y() );
|
|
topRight.x( anchor.x() );
|
|
break;
|
|
case 'bottomLeft':
|
|
bottomLeft.x( anchor.x() );
|
|
bottomLeft.y( anchor.y() );
|
|
bottomRight.y( anchor.y() );
|
|
topLeft.x( anchor.x() );
|
|
break;
|
|
default :
|
|
console.error('Unhandled anchor id: '+anchor.id());
|
|
break;
|
|
}
|
|
// update shape
|
|
var radiusX = ( topRight.x() - topLeft.x() ) / 2;
|
|
var radiusY = ( bottomRight.y() - topRight.y() ) / 2;
|
|
var center = { 'x': topLeft.x() + radiusX, 'y': topRight.y() + radiusY };
|
|
kellipse.position( center );
|
|
var radiusAbs = { 'x': Math.abs(radiusX), 'y': Math.abs(radiusY) };
|
|
if ( radiusAbs ) {
|
|
kellipse.radius( radiusAbs );
|
|
}
|
|
// new ellipse
|
|
var ellipse = new dwv.math.Ellipse(center, radiusAbs.x, radiusAbs.y);
|
|
// update text
|
|
var quant = image.quantifyEllipse( ellipse );
|
|
var ktext = klabel.getText();
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// update position
|
|
var textPos = { 'x': center.x, 'y': center.y };
|
|
klabel.position( textPos );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
/** @namespace */
|
|
dwv.tool.filter = dwv.tool.filter || {};
|
|
|
|
/**
|
|
* Filter tool.
|
|
* @constructor
|
|
* @param {Array} filterList The list of filter objects.
|
|
* @param {Object} app The associated app.
|
|
*/
|
|
dwv.tool.Filter = function ( filterList, app )
|
|
{
|
|
/**
|
|
* Filter GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Filter list
|
|
* @type Object
|
|
*/
|
|
this.filterList = filterList;
|
|
/**
|
|
* Selected filter.
|
|
* @type Object
|
|
*/
|
|
this.selectedFilter = 0;
|
|
/**
|
|
* Default filter name.
|
|
* @type String
|
|
*/
|
|
this.defaultFilterName = 0;
|
|
/**
|
|
* Display Flag.
|
|
* @type Boolean
|
|
*/
|
|
this.displayed = false;
|
|
/**
|
|
* Listener handler.
|
|
* @type Object
|
|
*/
|
|
var listenerHandler = new dwv.utils.ListenerHandler();
|
|
|
|
/**
|
|
* Setup the filter GUI. Called at app startup.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
if ( Object.keys(this.filterList).length !== 0 ) {
|
|
gui = new dwv.gui.Filter(app);
|
|
gui.setup(this.filterList);
|
|
for( var key in this.filterList ){
|
|
this.filterList[key].setup();
|
|
this.filterList[key].addEventListener("filter-run", fireEvent);
|
|
this.filterList[key].addEventListener("filter-undo", fireEvent);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Display the tool.
|
|
* @param {Boolean} bool Flag to enable or not.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
if ( gui ) {
|
|
gui.display(bool);
|
|
}
|
|
this.displayed = bool;
|
|
// display the selected filter
|
|
this.selectedFilter.display(bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the filter. Called once the image is loaded.
|
|
*/
|
|
this.init = function ()
|
|
{
|
|
// set the default to the first in the list
|
|
for( var key in this.filterList ){
|
|
this.defaultFilterName = key;
|
|
break;
|
|
}
|
|
this.setSelectedFilter(this.defaultFilterName);
|
|
// init all filters
|
|
for( key in this.filterList ) {
|
|
this.filterList[key].init();
|
|
}
|
|
// init html
|
|
if ( gui ) {
|
|
gui.initialise();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Handle keydown event.
|
|
* @param {Object} event The keydown event.
|
|
*/
|
|
this.keydown = function (event)
|
|
{
|
|
app.onKeydown(event);
|
|
};
|
|
|
|
/**
|
|
* Add an event listener to this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type,
|
|
* will be called with the fired event.
|
|
*/
|
|
this.addEventListener = function (type, callback) {
|
|
listenerHandler.add(type, callback);
|
|
};
|
|
/**
|
|
* Remove an event listener from this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (type, callback) {
|
|
listenerHandler.remove(type, callback);
|
|
};
|
|
/**
|
|
* Fire an event: call all associated listeners with the input event object.
|
|
* @param {Object} event The event to fire.
|
|
* @private
|
|
*/
|
|
function fireEvent (event) {
|
|
listenerHandler.fireEvent(event);
|
|
}
|
|
|
|
}; // class dwv.tool.Filter
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.Filter.prototype.getHelp = function ()
|
|
{
|
|
return {
|
|
"title": dwv.i18n("tool.Filter.name"),
|
|
"brief": dwv.i18n("tool.Filter.brief")
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get the selected filter.
|
|
* @return {Object} The selected filter.
|
|
*/
|
|
dwv.tool.Filter.prototype.getSelectedFilter = function ()
|
|
{
|
|
return this.selectedFilter;
|
|
};
|
|
|
|
/**
|
|
* Set the selected filter.
|
|
* @return {String} The name of the filter to select.
|
|
*/
|
|
dwv.tool.Filter.prototype.setSelectedFilter = function (name)
|
|
{
|
|
// check if we have it
|
|
if ( !this.hasFilter(name) )
|
|
{
|
|
throw new Error("Unknown filter: '" + name + "'");
|
|
}
|
|
// hide last selected
|
|
if ( this.displayed )
|
|
{
|
|
this.selectedFilter.display(false);
|
|
}
|
|
// enable new one
|
|
this.selectedFilter = this.filterList[name];
|
|
// display the selected filter
|
|
if ( this.displayed )
|
|
{
|
|
this.selectedFilter.display(true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the list of filters.
|
|
* @return {Array} The list of filter objects.
|
|
*/
|
|
dwv.tool.Filter.prototype.getFilterList = function ()
|
|
{
|
|
return this.filterList;
|
|
};
|
|
|
|
/**
|
|
* Check if a filter is in the filter list.
|
|
* @param {String} name The name to check.
|
|
* @return {String} The filter list element for the given name.
|
|
*/
|
|
dwv.tool.Filter.prototype.hasFilter = function (name)
|
|
{
|
|
return this.filterList[name];
|
|
};
|
|
|
|
/**
|
|
* Threshold filter tool.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.filter.Threshold = function ( app )
|
|
{
|
|
/**
|
|
* Associated filter.
|
|
* @type Object
|
|
*/
|
|
var filter = new dwv.image.filter.Threshold();
|
|
/**
|
|
* Filter GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = new dwv.gui.Threshold(app);
|
|
/**
|
|
* Flag to know wether to reset the image or not.
|
|
* @type Boolean
|
|
*/
|
|
var resetImage = true;
|
|
/**
|
|
* Listener handler.
|
|
* @type Object
|
|
*/
|
|
var listenerHandler = new dwv.utils.ListenerHandler();
|
|
|
|
/**
|
|
* Setup the filter GUI. Called at app startup.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Display the filter.
|
|
* @param {Boolean} bool Flag to display or not.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
gui.display(bool);
|
|
// reset the image when the tool is displayed
|
|
if ( bool ) {
|
|
resetImage = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialise the filter. Called once the image is loaded.
|
|
*/
|
|
this.init = function ()
|
|
{
|
|
gui.initialise();
|
|
};
|
|
|
|
/**
|
|
* Run the filter.
|
|
* @param {Mixed} args The filter arguments.
|
|
*/
|
|
this.run = function (args)
|
|
{
|
|
filter.setMin(args.min);
|
|
filter.setMax(args.max);
|
|
// reset the image if asked
|
|
if ( resetImage ) {
|
|
filter.setOriginalImage(app.getImage());
|
|
resetImage = false;
|
|
}
|
|
var command = new dwv.tool.RunFilterCommand(filter, app);
|
|
command.onExecute = fireEvent;
|
|
command.onUndo = fireEvent;
|
|
command.execute();
|
|
// save command in undo stack
|
|
app.addToUndoStack(command);
|
|
};
|
|
|
|
/**
|
|
* Add an event listener to this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type,
|
|
* will be called with the fired event.
|
|
*/
|
|
this.addEventListener = function (type, callback) {
|
|
listenerHandler.add(type, callback);
|
|
};
|
|
/**
|
|
* Remove an event listener from this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (type, callback) {
|
|
listenerHandler.remove(type, callback);
|
|
};
|
|
/**
|
|
* Fire an event: call all associated listeners with the input event object.
|
|
* @param {Object} event The event to fire.
|
|
* @private
|
|
*/
|
|
function fireEvent (event) {
|
|
listenerHandler.fireEvent(event);
|
|
}
|
|
|
|
}; // class dwv.tool.filter.Threshold
|
|
|
|
|
|
/**
|
|
* Sharpen filter tool.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.filter.Sharpen = function ( app )
|
|
{
|
|
/**
|
|
* Filter GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = new dwv.gui.Sharpen(app);
|
|
/**
|
|
* Listener handler.
|
|
* @type Object
|
|
*/
|
|
var listenerHandler = new dwv.utils.ListenerHandler();
|
|
|
|
/**
|
|
* Setup the filter GUI. Called at app startup.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Display the filter.
|
|
* @param {Boolean} bool Flag to enable or not.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
gui.display(bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the filter. Called once the image is loaded.
|
|
*/
|
|
this.init = function ()
|
|
{
|
|
// nothing to do...
|
|
};
|
|
|
|
/**
|
|
* Run the filter.
|
|
* @param {Mixed} args The filter arguments.
|
|
*/
|
|
this.run = function (/*args*/)
|
|
{
|
|
var filter = new dwv.image.filter.Sharpen();
|
|
filter.setOriginalImage(app.getImage());
|
|
var command = new dwv.tool.RunFilterCommand(filter, app);
|
|
command.onExecute = fireEvent;
|
|
command.onUndo = fireEvent;
|
|
command.execute();
|
|
// save command in undo stack
|
|
app.addToUndoStack(command);
|
|
};
|
|
|
|
/**
|
|
* Add an event listener to this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type,
|
|
* will be called with the fired event.
|
|
*/
|
|
this.addEventListener = function (type, callback) {
|
|
listenerHandler.add(type, callback);
|
|
};
|
|
/**
|
|
* Remove an event listener from this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (type, callback) {
|
|
listenerHandler.remove(type, callback);
|
|
};
|
|
/**
|
|
* Fire an event: call all associated listeners with the input event object.
|
|
* @param {Object} event The event to fire.
|
|
* @private
|
|
*/
|
|
function fireEvent (event) {
|
|
listenerHandler.fireEvent(event);
|
|
}
|
|
|
|
}; // dwv.tool.filter.Sharpen
|
|
|
|
/**
|
|
* Sobel filter tool.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.filter.Sobel = function ( app )
|
|
{
|
|
/**
|
|
* Filter GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = new dwv.gui.Sobel(app);
|
|
/**
|
|
* Listener handler.
|
|
* @type Object
|
|
*/
|
|
var listenerHandler = new dwv.utils.ListenerHandler();
|
|
|
|
/**
|
|
* Setup the filter GUI. Called at app startup.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Enable the filter.
|
|
* @param {Boolean} bool Flag to enable or not.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
gui.display(bool);
|
|
};
|
|
|
|
/**
|
|
* Initialise the filter. Called once the image is loaded.
|
|
*/
|
|
this.init = function ()
|
|
{
|
|
// nothing to do...
|
|
};
|
|
|
|
/**
|
|
* Run the filter.
|
|
* @param {Mixed} args The filter arguments.
|
|
*/
|
|
dwv.tool.filter.Sobel.prototype.run = function (/*args*/)
|
|
{
|
|
var filter = new dwv.image.filter.Sobel();
|
|
filter.setOriginalImage(app.getImage());
|
|
var command = new dwv.tool.RunFilterCommand(filter, app);
|
|
command.onExecute = fireEvent;
|
|
command.onUndo = fireEvent;
|
|
command.execute();
|
|
// save command in undo stack
|
|
app.addToUndoStack(command);
|
|
};
|
|
|
|
/**
|
|
* Add an event listener to this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type,
|
|
* will be called with the fired event.
|
|
*/
|
|
this.addEventListener = function (type, callback) {
|
|
listenerHandler.add(type, callback);
|
|
};
|
|
/**
|
|
* Remove an event listener from this class.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (type, callback) {
|
|
listenerHandler.remove(type, callback);
|
|
};
|
|
/**
|
|
* Fire an event: call all associated listeners with the input event object.
|
|
* @param {Object} event The event to fire.
|
|
* @private
|
|
*/
|
|
function fireEvent (event) {
|
|
listenerHandler.fireEvent(event);
|
|
}
|
|
|
|
}; // class dwv.tool.filter.Sobel
|
|
|
|
/**
|
|
* Run filter command.
|
|
* @constructor
|
|
* @param {Object} filter The filter to run.
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.RunFilterCommand = function (filter, app) {
|
|
|
|
/**
|
|
* Get the command name.
|
|
* @return {String} The command name.
|
|
*/
|
|
this.getName = function () { return "Filter-" + filter.getName(); };
|
|
|
|
/**
|
|
* Execute the command.
|
|
*/
|
|
this.execute = function ()
|
|
{
|
|
// run filter and set app image
|
|
app.setImage(filter.update());
|
|
// update display
|
|
app.render();
|
|
// callback
|
|
this.onExecute({'type': 'filter-run', 'id': this.getName()});
|
|
};
|
|
|
|
/**
|
|
* Undo the command.
|
|
*/
|
|
this.undo = function () {
|
|
// reset the image
|
|
app.setImage(filter.getOriginalImage());
|
|
// update display
|
|
app.render();
|
|
// callback
|
|
this.onUndo({'type': 'filter-undo', 'id': this.getName()});
|
|
};
|
|
|
|
}; // RunFilterCommand class
|
|
|
|
/**
|
|
* Handle an execute event.
|
|
* @param {Object} event The execute event with type and id.
|
|
*/
|
|
dwv.tool.RunFilterCommand.prototype.onExecute = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
/**
|
|
* Handle an undo event.
|
|
* @param {Object} event The undo event with type and id.
|
|
*/
|
|
dwv.tool.RunFilterCommand.prototype.onUndo = function (/*event*/)
|
|
{
|
|
// default does nothing.
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var MagicWand = MagicWand || {};
|
|
|
|
/**
|
|
* Floodfill painting tool.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
* @external MagicWand
|
|
* @see {@link https://github.com/Tamersoul/magic-wand-js}
|
|
*/
|
|
dwv.tool.Floodfill = function(app)
|
|
{
|
|
/**
|
|
* Original variables from external library. Used as in the lib example.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var blurRadius = 5;
|
|
/**
|
|
* Original variables from external library. Used as in the lib example.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var simplifyTolerant = 0;
|
|
/**
|
|
* Original variables from external library. Used as in the lib example.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var simplifyCount = 2000;
|
|
/**
|
|
* Canvas info
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var imageInfo = null;
|
|
/**
|
|
* Object created by MagicWand lib containing border points
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var mask = null;
|
|
/**
|
|
* threshold default tolerance of the tool border
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var initialthreshold = 10;
|
|
/**
|
|
* threshold tolerance of the tool border
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var currentthreshold = null;
|
|
/**
|
|
* Closure to self: to be used by event handlers.
|
|
* @private
|
|
* @type WindowLevel
|
|
*/
|
|
var self = this;
|
|
/**
|
|
* Interaction start flag.
|
|
* @type Boolean
|
|
*/
|
|
this.started = false;
|
|
/**
|
|
* Livewire GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Draw command.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var command = null;
|
|
/**
|
|
* Current shape group.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var shapeGroup = null;
|
|
/**
|
|
* Coordinates of the fist mousedown event.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var initialpoint;
|
|
/**
|
|
* Floodfill border.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var border = null;
|
|
/**
|
|
* List of parent points.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var parentPoints = [];
|
|
/**
|
|
* Assistant variable to paint border on all slices.
|
|
* @private
|
|
* @type Boolean
|
|
*/
|
|
var extender = false;
|
|
/**
|
|
* Timeout for painting on mousemove.
|
|
* @private
|
|
*/
|
|
var painterTimeout;
|
|
/**
|
|
* Drawing style.
|
|
* @type Style
|
|
*/
|
|
this.style = new dwv.html.Style();
|
|
|
|
/**
|
|
* Event listeners.
|
|
* @private
|
|
*/
|
|
var listeners = [];
|
|
|
|
/**
|
|
* Set extend option for painting border on all slices.
|
|
* @param {Boolean} The option to set
|
|
*/
|
|
this.setExtend = function(Bool){
|
|
extender = Bool;
|
|
};
|
|
|
|
/**
|
|
* Get extend option for painting border on all slices.
|
|
* @return {Boolean} The actual value of of the variable to use Floodfill on museup.
|
|
*/
|
|
this.getExtend = function(){
|
|
return extender;
|
|
};
|
|
|
|
/**
|
|
* Get (x, y) coordinates referenced to the canvas
|
|
* @param {Object} event The original event.
|
|
*/
|
|
var getCoord = function(event){
|
|
return { x: event._x, y: event._y };
|
|
};
|
|
|
|
/**
|
|
* Calculate border.
|
|
* @private
|
|
* @param {Object} Start point.
|
|
* @param {Number} Threshold tolerance.
|
|
*/
|
|
var calcBorder = function(points, threshold, simple){
|
|
|
|
parentPoints = [];
|
|
var image = {
|
|
data: imageInfo.data,
|
|
width: imageInfo.width,
|
|
height: imageInfo.height,
|
|
bytes: 4
|
|
};
|
|
|
|
// var p = new dwv.math.FastPoint2D(points.x, points.y);
|
|
mask = MagicWand.floodFill(image, points.x, points.y, threshold);
|
|
mask = MagicWand.gaussBlurOnlyBorder(mask, blurRadius);
|
|
|
|
var cs = MagicWand.traceContours(mask);
|
|
cs = MagicWand.simplifyContours(cs, simplifyTolerant, simplifyCount);
|
|
|
|
if(cs.length > 0 && cs[0].points[0].x){
|
|
if(simple){
|
|
return cs[0].points;
|
|
}
|
|
for(var j=0, icsl=cs[0].points.length; j<icsl; j++){
|
|
parentPoints.push(new dwv.math.Point2D(cs[0].points[j].x, cs[0].points[j].y));
|
|
}
|
|
return parentPoints;
|
|
}
|
|
else{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Paint Floodfill.
|
|
* @private
|
|
* @param {Object} Start point.
|
|
* @param {Number} Threshold tolerance.
|
|
*/
|
|
var paintBorder = function(point, threshold){
|
|
// Calculate the border
|
|
border = calcBorder(point, threshold);
|
|
// Paint the border
|
|
if(border){
|
|
var factory = new dwv.tool.RoiFactory();
|
|
shapeGroup = factory.create(border, self.style);
|
|
shapeGroup.id( dwv.math.guid() );
|
|
// draw shape command
|
|
command = new dwv.tool.DrawGroupCommand(shapeGroup, "floodfill", app.getCurrentDrawLayer());
|
|
command.onExecute = fireEvent;
|
|
command.onUndo = fireEvent;
|
|
// // draw
|
|
command.execute();
|
|
// save it in undo stack
|
|
app.addToUndoStack(command);
|
|
|
|
return true;
|
|
}
|
|
else{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create Floodfill in all the prev and next slices while border is found
|
|
*/
|
|
this.extend = function(ini, end){
|
|
//avoid errors
|
|
if(!initialpoint){
|
|
throw "'initialpoint' not found. User must click before use extend!";
|
|
}
|
|
// remove previous draw
|
|
if ( shapeGroup ) {
|
|
shapeGroup.destroy();
|
|
}
|
|
|
|
var pos = app.getViewController().getCurrentPosition();
|
|
var threshold = currentthreshold || initialthreshold;
|
|
|
|
// Iterate over the next images and paint border on each slice.
|
|
for(var i=pos.k, len = end ? end : app.getImage().getGeometry().getSize().getNumberOfSlices(); i<len ; i++){
|
|
if(!paintBorder(initialpoint, threshold)){
|
|
break;
|
|
}
|
|
app.getViewController().incrementSliceNb();
|
|
}
|
|
app.getViewController().setCurrentPosition(pos);
|
|
|
|
// Iterate over the prev images and paint border on each slice.
|
|
for(var j=pos.k, jl = ini ? ini : 0 ; j>jl ; j--){
|
|
if(!paintBorder(initialpoint, threshold)){
|
|
break;
|
|
}
|
|
app.getViewController().decrementSliceNb();
|
|
}
|
|
app.getViewController().setCurrentPosition(pos);
|
|
};
|
|
|
|
/**
|
|
* Modify tolerance threshold and redraw ROI.
|
|
* @param {Number} New threshold.
|
|
*/
|
|
this.modifyThreshold = function(modifyThreshold, shape){
|
|
|
|
if(!shape && shapeGroup){
|
|
shape = shapeGroup.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
}
|
|
else{
|
|
throw 'No shape found';
|
|
}
|
|
|
|
clearTimeout(painterTimeout);
|
|
painterTimeout = setTimeout( function () {
|
|
border = calcBorder(initialpoint, modifyThreshold, true);
|
|
if(!border){
|
|
return false;
|
|
}
|
|
var arr = [];
|
|
for( var i = 0, bl = border.length; i < bl ; ++i )
|
|
{
|
|
arr.push( border[i].x );
|
|
arr.push( border[i].y );
|
|
}
|
|
shape.setPoints(arr);
|
|
var shapeLayer = shape.getLayer();
|
|
shapeLayer.draw();
|
|
self.onThresholdChange(modifyThreshold);
|
|
}, 100);
|
|
};
|
|
|
|
/**
|
|
* Event fired when threshold change
|
|
* @param {Number} Current threshold
|
|
*/
|
|
this.onThresholdChange = function(/*value*/){
|
|
// Defaults do nothing
|
|
};
|
|
|
|
/**
|
|
* Handle mouse down event.
|
|
* @param {Object} event The mouse down event.
|
|
*/
|
|
this.mousedown = function(event){
|
|
imageInfo = app.getImageData();
|
|
if (!imageInfo){ return console.error('No image found');}
|
|
|
|
self.started = true;
|
|
initialpoint = getCoord(event);
|
|
paintBorder(initialpoint, initialthreshold);
|
|
self.onThresholdChange(initialthreshold);
|
|
};
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {Object} event The mouse move event.
|
|
*/
|
|
this.mousemove = function(event){
|
|
if (!self.started)
|
|
{
|
|
return;
|
|
}
|
|
var movedpoint = getCoord(event);
|
|
currentthreshold = Math.round(Math.sqrt( Math.pow((initialpoint.x-movedpoint.x), 2) + Math.pow((initialpoint.y-movedpoint.y), 2) )/2);
|
|
currentthreshold = currentthreshold < initialthreshold ? initialthreshold : currentthreshold - initialthreshold;
|
|
self.modifyThreshold(currentthreshold);
|
|
};
|
|
|
|
/**
|
|
* Handle mouse up event.
|
|
* @param {Object} event The mouse up event.
|
|
*/
|
|
this.mouseup = function(/*event*/){
|
|
self.started = false;
|
|
if(extender){
|
|
self.extend();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse out event.
|
|
* @param {Object} event The mouse out event.
|
|
*/
|
|
this.mouseout = function(/*event*/){
|
|
self.mouseup(/*event*/);
|
|
};
|
|
|
|
/**
|
|
* Handle touch start event.
|
|
* @param {Object} event The touch start event.
|
|
*/
|
|
this.touchstart = function(event){
|
|
// treat as mouse down
|
|
self.mousedown(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.touchmove = function(event){
|
|
// treat as mouse move
|
|
self.mousemove(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch end event.
|
|
* @param {Object} event The touch end event.
|
|
*/
|
|
this.touchend = function(/*event*/){
|
|
// treat as mouse up
|
|
self.mouseup(/*event*/);
|
|
};
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.keydown = function(event){
|
|
app.onKeydown(event);
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui = new dwv.gui.ColourTool(app, "ff");
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Enable the tool.
|
|
* @param {Boolean} bool The flag to enable or not.
|
|
*/
|
|
this.display = function(bool){
|
|
if ( gui ) {
|
|
gui.display(bool);
|
|
}
|
|
// TODO why twice?
|
|
this.init();
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool.
|
|
*/
|
|
this.init = function()
|
|
{
|
|
if ( gui ) {
|
|
// init with the app window scale
|
|
this.style.setScale(app.getWindowScale());
|
|
// set the default to the first in the list
|
|
this.setLineColour(this.style.getLineColour());
|
|
// init html
|
|
gui.initialise();
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Add an event listener on the app.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.addEventListener = function (listener)
|
|
{
|
|
listeners.push(listener);
|
|
};
|
|
|
|
/**
|
|
* Remove an event listener from the app.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (listener)
|
|
{
|
|
for ( var i = 0; i < listeners.length; ++i )
|
|
{
|
|
if ( listeners[i] === listener ) {
|
|
listeners.splice(i,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Private Methods -----------------------------------------------------------
|
|
|
|
/**
|
|
* Fire an event: call all associated listeners.
|
|
* @param {Object} event The event to fire.
|
|
*/
|
|
function fireEvent (event)
|
|
{
|
|
for ( var i=0; i < listeners.length; ++i )
|
|
{
|
|
listeners[i](event);
|
|
}
|
|
}
|
|
|
|
}; // Floodfill class
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.Floodfill.prototype.getHelp = function()
|
|
{
|
|
return {
|
|
'title': dwv.i18n("tool.Floodfill.name"),
|
|
'brief': dwv.i18n("tool.Floodfill.brief"),
|
|
"mouse": {
|
|
"click": dwv.i18n("tool.Floodfill.click")
|
|
},
|
|
"touch": {
|
|
"tap": dwv.i18n("tool.Floodfill.tap")
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Set the line colour of the drawing.
|
|
* @param {String} colour The colour to set.
|
|
*/
|
|
dwv.tool.Floodfill.prototype.setLineColour = function(colour)
|
|
{
|
|
// set style var
|
|
this.style.setLineColour(colour);
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* FreeHand factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.FreeHandFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 1000; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 25; };
|
|
};
|
|
|
|
/**
|
|
* Create a roi shape to be displayed.
|
|
* @param {Array} points The points from which to extract the line.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.FreeHandFactory.prototype.create = function (points, style /*, image*/)
|
|
{
|
|
// points stored the Konvajs way
|
|
var arr = [];
|
|
for( var i = 0; i < points.length; ++i )
|
|
{
|
|
arr.push( points[i].getX() );
|
|
arr.push( points[i].getY() );
|
|
}
|
|
// draw shape
|
|
var kshape = new Konva.Line({
|
|
points: arr,
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape",
|
|
tension: 0.5
|
|
});
|
|
|
|
// text
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "";
|
|
ktext.longText = "";
|
|
ktext.quant = null;
|
|
ktext.setText(ktext.textExpr);
|
|
|
|
// label
|
|
var klabel = new Konva.Label({
|
|
x: points[0].getX(),
|
|
y: points[0].getY() + 10,
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// return group
|
|
var group = new Konva.Group();
|
|
group.name("freeHand-group");
|
|
group.add(kshape);
|
|
group.add(klabel);
|
|
group.visible(true); // dont inherit
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update a FreeHand shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateFreeHand = function (anchor /*, image*/)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var kline = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
|
|
// update self
|
|
var point = group.getChildren( function (node) {
|
|
return node.id() === anchor.id();
|
|
})[0];
|
|
point.x( anchor.x() );
|
|
point.y( anchor.y() );
|
|
// update the roi point and compensate for possible drag
|
|
// (the anchor id is the index of the point in the list)
|
|
var points = kline.points();
|
|
points[anchor.id()] = anchor.x() - kline.x();
|
|
points[anchor.id()+1] = anchor.y() - kline.y();
|
|
kline.points( points );
|
|
|
|
// update text
|
|
var ktext = klabel.getText();
|
|
ktext.quant = null;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// update position
|
|
var textPos = { 'x': points[0] + kline.x(), 'y': points[1] + kline.y() + 10 };
|
|
klabel.position( textPos );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
|
|
/**
|
|
* Livewire painting tool.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.Livewire = function(app)
|
|
{
|
|
/**
|
|
* Closure to self: to be used by event handlers.
|
|
* @private
|
|
* @type WindowLevel
|
|
*/
|
|
var self = this;
|
|
/**
|
|
* Livewire GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Interaction start flag.
|
|
* @type Boolean
|
|
*/
|
|
this.started = false;
|
|
|
|
/**
|
|
* Draw command.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var command = null;
|
|
/**
|
|
* Current shape group.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var shapeGroup = null;
|
|
/**
|
|
* Drawing style.
|
|
* @type Style
|
|
*/
|
|
this.style = new dwv.html.Style();
|
|
// init with the app window scale
|
|
this.style.setScale(app.getWindowScale());
|
|
|
|
/**
|
|
* Path storage. Paths are stored in reverse order.
|
|
* @private
|
|
* @type Path
|
|
*/
|
|
var path = new dwv.math.Path();
|
|
/**
|
|
* Current path storage. Paths are stored in reverse order.
|
|
* @private
|
|
* @type Path
|
|
*/
|
|
var currentPath = new dwv.math.Path();
|
|
/**
|
|
* List of parent points.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var parentPoints = [];
|
|
/**
|
|
* Tolerance.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var tolerance = 5;
|
|
|
|
/**
|
|
* Event listeners.
|
|
* @private
|
|
*/
|
|
var listeners = [];
|
|
|
|
/**
|
|
* Clear the parent points list.
|
|
* @private
|
|
*/
|
|
function clearParentPoints() {
|
|
var nrows = app.getImage().getGeometry().getSize().getNumberOfRows();
|
|
for( var i = 0; i < nrows; ++i ) {
|
|
parentPoints[i] = [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear the stored paths.
|
|
* @private
|
|
*/
|
|
function clearPaths() {
|
|
path = new dwv.math.Path();
|
|
currentPath = new dwv.math.Path();
|
|
}
|
|
|
|
/**
|
|
* Scissor representation.
|
|
* @private
|
|
* @type Scissors
|
|
*/
|
|
var scissors = new dwv.math.Scissors();
|
|
|
|
/**
|
|
* Handle mouse down event.
|
|
* @param {Object} event The mouse down event.
|
|
*/
|
|
this.mousedown = function(event){
|
|
// first time
|
|
if( !self.started ) {
|
|
self.started = true;
|
|
self.x0 = event._x;
|
|
self.y0 = event._y;
|
|
// clear vars
|
|
clearPaths();
|
|
clearParentPoints();
|
|
// do the training from the first point
|
|
var p = new dwv.math.FastPoint2D(event._x, event._y);
|
|
scissors.doTraining(p);
|
|
// add the initial point to the path
|
|
var p0 = new dwv.math.Point2D(event._x, event._y);
|
|
path.addPoint(p0);
|
|
path.addControlPoint(p0);
|
|
}
|
|
else {
|
|
// final point: at 'tolerance' of the initial point
|
|
if( (Math.abs(event._x - self.x0) < tolerance) && (Math.abs(event._y - self.y0) < tolerance) ) {
|
|
// draw
|
|
self.mousemove(event);
|
|
// listen
|
|
command.onExecute = fireEvent;
|
|
command.onUndo = fireEvent;
|
|
// debug
|
|
console.log("Done.");
|
|
// save command in undo stack
|
|
app.addToUndoStack(command);
|
|
// set flag
|
|
self.started = false;
|
|
}
|
|
// anchor point
|
|
else {
|
|
path = currentPath;
|
|
clearParentPoints();
|
|
var pn = new dwv.math.FastPoint2D(event._x, event._y);
|
|
scissors.doTraining(pn);
|
|
path.addControlPoint(currentPath.getPoint(0));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {Object} event The mouse move event.
|
|
*/
|
|
this.mousemove = function(event){
|
|
if (!self.started)
|
|
{
|
|
return;
|
|
}
|
|
// set the point to find the path to
|
|
var p = new dwv.math.FastPoint2D(event._x, event._y);
|
|
scissors.setPoint(p);
|
|
// do the work
|
|
var results = 0;
|
|
var stop = false;
|
|
while( !parentPoints[p.y][p.x] && !stop)
|
|
{
|
|
console.log("Getting ready...");
|
|
results = scissors.doWork();
|
|
|
|
if( results.length === 0 ) {
|
|
stop = true;
|
|
}
|
|
else {
|
|
// fill parents
|
|
for( var i = 0; i < results.length-1; i+=2 ) {
|
|
var _p = results[i];
|
|
var _q = results[i+1];
|
|
parentPoints[_p.y][_p.x] = _q;
|
|
}
|
|
}
|
|
}
|
|
console.log("Ready!");
|
|
|
|
// get the path
|
|
currentPath = new dwv.math.Path();
|
|
stop = false;
|
|
while (p && !stop) {
|
|
currentPath.addPoint(new dwv.math.Point2D(p.x, p.y));
|
|
if(!parentPoints[p.y]) {
|
|
stop = true;
|
|
}
|
|
else {
|
|
if(!parentPoints[p.y][p.x]) {
|
|
stop = true;
|
|
}
|
|
else {
|
|
p = parentPoints[p.y][p.x];
|
|
}
|
|
}
|
|
}
|
|
currentPath.appenPath(path);
|
|
|
|
// remove previous draw
|
|
if ( shapeGroup ) {
|
|
shapeGroup.destroy();
|
|
}
|
|
// create shape
|
|
var factory = new dwv.tool.RoiFactory();
|
|
shapeGroup = factory.create(currentPath.pointArray, self.style);
|
|
shapeGroup.id( dwv.math.guid() );
|
|
// draw shape command
|
|
command = new dwv.tool.DrawGroupCommand(shapeGroup, "livewire", app.getCurrentDrawLayer());
|
|
// draw
|
|
command.execute();
|
|
};
|
|
|
|
/**
|
|
* Handle mouse up event.
|
|
* @param {Object} event The mouse up event.
|
|
*/
|
|
this.mouseup = function(/*event*/){
|
|
// nothing to do
|
|
};
|
|
|
|
/**
|
|
* Handle mouse out event.
|
|
* @param {Object} event The mouse out event.
|
|
*/
|
|
this.mouseout = function(event){
|
|
// treat as mouse up
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle double click event.
|
|
* @param {Object} event The double click event.
|
|
*/
|
|
this.dblclick = function(/*event*/){
|
|
console.log("dblclick");
|
|
// save command in undo stack
|
|
app.addToUndoStack(command);
|
|
// set flag
|
|
self.started = false;
|
|
};
|
|
|
|
/**
|
|
* Handle touch start event.
|
|
* @param {Object} event The touch start event.
|
|
*/
|
|
this.touchstart = function(event){
|
|
// treat as mouse down
|
|
self.mousedown(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.touchmove = function(event){
|
|
// treat as mouse move
|
|
self.mousemove(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch end event.
|
|
* @param {Object} event The touch end event.
|
|
*/
|
|
this.touchend = function(event){
|
|
// treat as mouse up
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.keydown = function(event){
|
|
app.onKeydown(event);
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui = new dwv.gui.ColourTool(app, "lw");
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Enable the tool.
|
|
* @param {Boolean} bool The flag to enable or not.
|
|
*/
|
|
this.display = function(bool){
|
|
if ( gui ) {
|
|
gui.display(bool);
|
|
}
|
|
// start scissors if displayed
|
|
if (bool) {
|
|
//scissors = new dwv.math.Scissors();
|
|
var size = app.getImage().getGeometry().getSize();
|
|
scissors.setDimensions(
|
|
size.getNumberOfColumns(),
|
|
size.getNumberOfRows() );
|
|
scissors.setData(app.getImageData().data);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool.
|
|
*/
|
|
this.init = function()
|
|
{
|
|
if ( gui ) {
|
|
// init with the app window scale
|
|
this.style.setScale(app.getWindowScale());
|
|
// set the default to the first in the list
|
|
this.setLineColour(this.style.getLineColour());
|
|
// init html
|
|
gui.initialise();
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Add an event listener on the app.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.addEventListener = function (listener)
|
|
{
|
|
listeners.push(listener);
|
|
};
|
|
|
|
/**
|
|
* Remove an event listener from the app.
|
|
* @param {Object} listener The method associated with the provided event type.
|
|
*/
|
|
this.removeEventListener = function (listener)
|
|
{
|
|
for ( var i = 0; i < listeners.length; ++i )
|
|
{
|
|
if ( listeners[i] === listener ) {
|
|
listeners.splice(i,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Private Methods -----------------------------------------------------------
|
|
|
|
/**
|
|
* Fire an event: call all associated listeners.
|
|
* @param {Object} event The event to fire.
|
|
*/
|
|
function fireEvent (event)
|
|
{
|
|
for ( var i=0; i < listeners.length; ++i )
|
|
{
|
|
listeners[i](event);
|
|
}
|
|
}
|
|
|
|
}; // Livewire class
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.Livewire.prototype.getHelp = function()
|
|
{
|
|
return {
|
|
"title": dwv.i18n("tool.Livewire.name"),
|
|
"brief": dwv.i18n("tool.Livewire.brief")
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Set the line colour of the drawing.
|
|
* @param {String} colour The colour to set.
|
|
*/
|
|
dwv.tool.Livewire.prototype.setLineColour = function(colour)
|
|
{
|
|
// set style var
|
|
this.style.setLineColour(colour);
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Protractor factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.ProtractorFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 3; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 500; };
|
|
};
|
|
|
|
/**
|
|
* Create a protractor shape to be displayed.
|
|
* @param {Array} points The points from which to extract the protractor.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.ProtractorFactory.prototype.create = function (points, style/*, image*/)
|
|
{
|
|
// physical shape
|
|
var line0 = new dwv.math.Line(points[0], points[1]);
|
|
// points stored the Konvajs way
|
|
var pointsArray = [];
|
|
for( var i = 0; i < points.length; ++i )
|
|
{
|
|
pointsArray.push( points[i].getX() );
|
|
pointsArray.push( points[i].getY() );
|
|
}
|
|
// draw shape
|
|
var kshape = new Konva.Line({
|
|
points: pointsArray,
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape"
|
|
});
|
|
var group = new Konva.Group();
|
|
group.name("protractor-group");
|
|
group.add(kshape);
|
|
group.visible(true); // dont inherit
|
|
// text and decoration
|
|
if ( points.length === 3 ) {
|
|
var line1 = new dwv.math.Line(points[1], points[2]);
|
|
// larger hitfunc
|
|
kshape.hitFunc( function (context) {
|
|
context.beginPath();
|
|
context.moveTo( points[0].getX(), points[0].getY() );
|
|
context.lineTo( points[1].getX(), points[1].getY() );
|
|
context.lineTo( points[2].getX(), points[2].getY() );
|
|
context.closePath();
|
|
context.fillStrokeShape(this);
|
|
});
|
|
// quantification
|
|
var angle = dwv.math.getAngle(line0, line1);
|
|
var inclination = line0.getInclination();
|
|
if ( angle > 180 ) {
|
|
angle = 360 - angle;
|
|
inclination += angle;
|
|
}
|
|
|
|
// quantification
|
|
var quant = { "angle": { "value": angle, "unit": dwv.i18n("unit.degree")} };
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "{angle}";
|
|
ktext.longText = "";
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
|
|
// label
|
|
var midX = ( line0.getMidpoint().getX() + line1.getMidpoint().getX() ) / 2;
|
|
var midY = ( line0.getMidpoint().getY() + line1.getMidpoint().getY() ) / 2;
|
|
var klabel = new Konva.Label({
|
|
x: midX,
|
|
y: midY - 15,
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// arc
|
|
var radius = Math.min(line0.getLength(), line1.getLength()) * 33 / 100;
|
|
var karc = new Konva.Arc({
|
|
innerRadius: radius,
|
|
outerRadius: radius,
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
angle: angle,
|
|
rotation: -inclination,
|
|
x: points[1].getX(),
|
|
y: points[1].getY(),
|
|
name: "shape-arc"
|
|
});
|
|
// add to group
|
|
group.add(klabel);
|
|
group.add(karc);
|
|
}
|
|
// return group
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update a protractor shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateProtractor = function (anchor/*, image*/)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var kline = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
// associated arc
|
|
var karc = group.getChildren( function (node) {
|
|
return node.name() === 'shape-arc';
|
|
})[0];
|
|
// find special points
|
|
var begin = group.getChildren( function (node) {
|
|
return node.id() === 'begin';
|
|
})[0];
|
|
var mid = group.getChildren( function (node) {
|
|
return node.id() === 'mid';
|
|
})[0];
|
|
var end = group.getChildren( function (node) {
|
|
return node.id() === 'end';
|
|
})[0];
|
|
// update special points
|
|
switch ( anchor.id() ) {
|
|
case 'begin':
|
|
begin.x( anchor.x() );
|
|
begin.y( anchor.y() );
|
|
break;
|
|
case 'mid':
|
|
mid.x( anchor.x() );
|
|
mid.y( anchor.y() );
|
|
break;
|
|
case 'end':
|
|
end.x( anchor.x() );
|
|
end.y( anchor.y() );
|
|
break;
|
|
}
|
|
// update shape and compensate for possible drag
|
|
// note: shape.position() and shape.size() won't work...
|
|
var bx = begin.x() - kline.x();
|
|
var by = begin.y() - kline.y();
|
|
var mx = mid.x() - kline.x();
|
|
var my = mid.y() - kline.y();
|
|
var ex = end.x() - kline.x();
|
|
var ey = end.y() - kline.y();
|
|
kline.points( [bx,by,mx,my,ex,ey] );
|
|
// larger hitfunc
|
|
kline.hitFunc( function (context) {
|
|
context.beginPath();
|
|
context.moveTo( bx, by );
|
|
context.lineTo( mx, my );
|
|
context.lineTo( ex, ey );
|
|
context.closePath();
|
|
context.fillStrokeShape(this);
|
|
});
|
|
// update text
|
|
var p2d0 = new dwv.math.Point2D(begin.x(), begin.y());
|
|
var p2d1 = new dwv.math.Point2D(mid.x(), mid.y());
|
|
var p2d2 = new dwv.math.Point2D(end.x(), end.y());
|
|
var line0 = new dwv.math.Line(p2d0, p2d1);
|
|
var line1 = new dwv.math.Line(p2d1, p2d2);
|
|
var angle = dwv.math.getAngle(line0, line1);
|
|
var inclination = line0.getInclination();
|
|
if ( angle > 180 ) {
|
|
angle = 360 - angle;
|
|
inclination += angle;
|
|
}
|
|
|
|
// update text
|
|
var quant = { "angle": { "value": angle, "unit": dwv.i18n("unit.degree")} };
|
|
var ktext = klabel.getText();
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// update position
|
|
var midX = ( line0.getMidpoint().getX() + line1.getMidpoint().getX() ) / 2;
|
|
var midY = ( line0.getMidpoint().getY() + line1.getMidpoint().getY() ) / 2;
|
|
var textPos = { 'x': midX, 'y': midY - 15 };
|
|
klabel.position( textPos );
|
|
|
|
// arc
|
|
var radius = Math.min(line0.getLength(), line1.getLength()) * 33 / 100;
|
|
karc.innerRadius(radius);
|
|
karc.outerRadius(radius);
|
|
karc.angle(angle);
|
|
karc.rotation(-inclination);
|
|
var arcPos = { 'x': mid.x(), 'y': mid.y() };
|
|
karc.position(arcPos);
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Rectangle factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.RectangleFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 2; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 0; };
|
|
};
|
|
|
|
/**
|
|
* Create a rectangle shape to be displayed.
|
|
* @param {Array} points The points from which to extract the rectangle.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.RectangleFactory.prototype.create = function (points, style, image)
|
|
{
|
|
// physical shape
|
|
var rectangle = new dwv.math.Rectangle(points[0], points[1]);
|
|
// draw shape
|
|
var kshape = new Konva.Rect({
|
|
x: rectangle.getBegin().getX(),
|
|
y: rectangle.getBegin().getY(),
|
|
width: rectangle.getWidth(),
|
|
height: rectangle.getHeight(),
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape"
|
|
});
|
|
// quantification
|
|
var quant = image.quantifyRect( rectangle );
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "{surface}";
|
|
ktext.longText = "";
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
|
|
// label
|
|
var klabel = new Konva.Label({
|
|
x: rectangle.getBegin().getX(),
|
|
y: rectangle.getEnd().getY() + 10,
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// return group
|
|
var group = new Konva.Group();
|
|
group.name("rectangle-group");
|
|
group.add(kshape);
|
|
group.add(klabel);
|
|
group.visible(true); // dont inherit
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update a rectangle shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateRect = function (anchor, image)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var krect = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
// find special points
|
|
var topLeft = group.getChildren( function (node) {
|
|
return node.id() === 'topLeft';
|
|
})[0];
|
|
var topRight = group.getChildren( function (node) {
|
|
return node.id() === 'topRight';
|
|
})[0];
|
|
var bottomRight = group.getChildren( function (node) {
|
|
return node.id() === 'bottomRight';
|
|
})[0];
|
|
var bottomLeft = group.getChildren( function (node) {
|
|
return node.id() === 'bottomLeft';
|
|
})[0];
|
|
// update 'self' (undo case) and special points
|
|
switch ( anchor.id() ) {
|
|
case 'topLeft':
|
|
topLeft.x( anchor.x() );
|
|
topLeft.y( anchor.y() );
|
|
topRight.y( anchor.y() );
|
|
bottomLeft.x( anchor.x() );
|
|
break;
|
|
case 'topRight':
|
|
topRight.x( anchor.x() );
|
|
topRight.y( anchor.y() );
|
|
topLeft.y( anchor.y() );
|
|
bottomRight.x( anchor.x() );
|
|
break;
|
|
case 'bottomRight':
|
|
bottomRight.x( anchor.x() );
|
|
bottomRight.y( anchor.y() );
|
|
bottomLeft.y( anchor.y() );
|
|
topRight.x( anchor.x() );
|
|
break;
|
|
case 'bottomLeft':
|
|
bottomLeft.x( anchor.x() );
|
|
bottomLeft.y( anchor.y() );
|
|
bottomRight.y( anchor.y() );
|
|
topLeft.x( anchor.x() );
|
|
break;
|
|
default :
|
|
console.error('Unhandled anchor id: '+anchor.id());
|
|
break;
|
|
}
|
|
// update shape
|
|
krect.position(topLeft.position());
|
|
var width = topRight.x() - topLeft.x();
|
|
var height = bottomLeft.y() - topLeft.y();
|
|
if ( width && height ) {
|
|
krect.size({'width': width, 'height': height});
|
|
}
|
|
// new rect
|
|
var p2d0 = new dwv.math.Point2D(topLeft.x(), topLeft.y());
|
|
var p2d1 = new dwv.math.Point2D(bottomRight.x(), bottomRight.y());
|
|
var rect = new dwv.math.Rectangle(p2d0, p2d1);
|
|
// update text
|
|
var quant = image.quantifyRect( rect );
|
|
var ktext = klabel.getText();
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// update position
|
|
var textPos = { 'x': rect.getBegin().getX(), 'y': rect.getEnd().getY() + 10 };
|
|
klabel.position( textPos );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* ROI factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.RoiFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 50; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 100; };
|
|
};
|
|
|
|
/**
|
|
* Create a roi shape to be displayed.
|
|
* @param {Array} points The points from which to extract the line.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.RoiFactory.prototype.create = function (points, style /*, image*/)
|
|
{
|
|
// physical shape
|
|
var roi = new dwv.math.ROI();
|
|
// add input points to the ROI
|
|
roi.addPoints(points);
|
|
// points stored the Konvajs way
|
|
var arr = [];
|
|
for( var i = 0; i < roi.getLength(); ++i )
|
|
{
|
|
arr.push( roi.getPoint(i).getX() );
|
|
arr.push( roi.getPoint(i).getY() );
|
|
}
|
|
// draw shape
|
|
var kshape = new Konva.Line({
|
|
points: arr,
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape",
|
|
closed: true
|
|
});
|
|
|
|
// text
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "";
|
|
ktext.longText = "";
|
|
ktext.quant = null;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
|
|
// label
|
|
var klabel = new Konva.Label({
|
|
x: roi.getPoint(0).getX(),
|
|
y: roi.getPoint(0).getY() + 10,
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// return group
|
|
var group = new Konva.Group();
|
|
group.name("roi-group");
|
|
group.add(kshape);
|
|
group.add(klabel);
|
|
group.visible(true); // dont inherit
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update a roi shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateRoi = function (anchor /*, image*/)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var kroi = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
|
|
// update self
|
|
var point = group.getChildren( function (node) {
|
|
return node.id() === anchor.id();
|
|
})[0];
|
|
point.x( anchor.x() );
|
|
point.y( anchor.y() );
|
|
// update the roi point and compensate for possible drag
|
|
// (the anchor id is the index of the point in the list)
|
|
var points = kroi.points();
|
|
points[anchor.id()] = anchor.x() - kroi.x();
|
|
points[anchor.id()+1] = anchor.y() - kroi.y();
|
|
kroi.points( points );
|
|
|
|
// update text
|
|
var ktext = klabel.getText();
|
|
ktext.quant = null;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// update position
|
|
var textPos = { 'x': points[0] + kroi.x(), 'y': points[1] + kroi.y() + 10 };
|
|
klabel.position( textPos );
|
|
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
// external
|
|
var Konva = Konva || {};
|
|
|
|
/**
|
|
* Ruler factory.
|
|
* @constructor
|
|
* @external Konva
|
|
*/
|
|
dwv.tool.RulerFactory = function ()
|
|
{
|
|
/**
|
|
* Get the number of points needed to build the shape.
|
|
* @return {Number} The number of points.
|
|
*/
|
|
this.getNPoints = function () { return 2; };
|
|
/**
|
|
* Get the timeout between point storage.
|
|
* @return {Number} The timeout in milliseconds.
|
|
*/
|
|
this.getTimeout = function () { return 0; };
|
|
};
|
|
|
|
/**
|
|
* Create a ruler shape to be displayed.
|
|
* @param {Array} points The points from which to extract the line.
|
|
* @param {Object} style The drawing style.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.RulerFactory.prototype.create = function (points, style, image)
|
|
{
|
|
// physical shape
|
|
var line = new dwv.math.Line(points[0], points[1]);
|
|
// draw shape
|
|
var kshape = new Konva.Line({
|
|
points: [line.getBegin().getX(), line.getBegin().getY(),
|
|
line.getEnd().getX(), line.getEnd().getY() ],
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape"
|
|
});
|
|
|
|
// tick begin
|
|
var linePerp0 = dwv.math.getPerpendicularLine( line, points[0], 10 );
|
|
var ktick0 = new Konva.Line({
|
|
points: [linePerp0.getBegin().getX(), linePerp0.getBegin().getY(),
|
|
linePerp0.getEnd().getX(), linePerp0.getEnd().getY() ],
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape-tick0"
|
|
});
|
|
|
|
// tick end
|
|
var linePerp1 = dwv.math.getPerpendicularLine( line, points[1], 10 );
|
|
var ktick1 = new Konva.Line({
|
|
points: [linePerp1.getBegin().getX(), linePerp1.getBegin().getY(),
|
|
linePerp1.getEnd().getX(), linePerp1.getEnd().getY() ],
|
|
stroke: style.getLineColour(),
|
|
strokeWidth: style.getScaledStrokeWidth(),
|
|
name: "shape-tick1"
|
|
});
|
|
|
|
// larger hitfunc
|
|
kshape.hitFunc( function (context) {
|
|
context.beginPath();
|
|
context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
|
|
context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
|
|
context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
|
|
context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
|
|
context.closePath();
|
|
context.fillStrokeShape(this);
|
|
});
|
|
|
|
// quantification
|
|
var quant = image.quantifyLine( line );
|
|
var ktext = new Konva.Text({
|
|
fontSize: style.getScaledFontSize(),
|
|
fontFamily: style.getFontFamily(),
|
|
fill: style.getLineColour(),
|
|
name: "text"
|
|
});
|
|
ktext.textExpr = "{length}";
|
|
ktext.longText = "";
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// label
|
|
var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
|
|
var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
|
|
var klabel = new Konva.Label({
|
|
x: line.getEnd().getX() + dX * 25,
|
|
y: line.getEnd().getY() + dY * 15,
|
|
name: "label"
|
|
});
|
|
klabel.add(ktext);
|
|
klabel.add(new Konva.Tag());
|
|
|
|
// return group
|
|
var group = new Konva.Group();
|
|
group.name("ruler-group");
|
|
group.add(kshape);
|
|
group.add(ktick0);
|
|
group.add(ktick1);
|
|
group.add(klabel);
|
|
group.visible(true); // dont inherit
|
|
return group;
|
|
};
|
|
|
|
/**
|
|
* Update a ruler shape.
|
|
* @param {Object} anchor The active anchor.
|
|
* @param {Object} image The associated image.
|
|
*/
|
|
dwv.tool.UpdateRuler = function (anchor, image)
|
|
{
|
|
// parent group
|
|
var group = anchor.getParent();
|
|
// associated shape
|
|
var kline = group.getChildren( function (node) {
|
|
return node.name() === 'shape';
|
|
})[0];
|
|
// associated tick0
|
|
var ktick0 = group.getChildren( function (node) {
|
|
return node.name() === 'shape-tick0';
|
|
})[0];
|
|
// associated tick1
|
|
var ktick1 = group.getChildren( function (node) {
|
|
return node.name() === 'shape-tick1';
|
|
})[0];
|
|
// associated label
|
|
var klabel = group.getChildren( function (node) {
|
|
return node.name() === 'label';
|
|
})[0];
|
|
// find special points
|
|
var begin = group.getChildren( function (node) {
|
|
return node.id() === 'begin';
|
|
})[0];
|
|
var end = group.getChildren( function (node) {
|
|
return node.id() === 'end';
|
|
})[0];
|
|
// update special points
|
|
switch ( anchor.id() ) {
|
|
case 'begin':
|
|
begin.x( anchor.x() );
|
|
begin.y( anchor.y() );
|
|
break;
|
|
case 'end':
|
|
end.x( anchor.x() );
|
|
end.y( anchor.y() );
|
|
break;
|
|
}
|
|
// update shape and compensate for possible drag
|
|
// note: shape.position() and shape.size() won't work...
|
|
var bx = begin.x() - kline.x();
|
|
var by = begin.y() - kline.y();
|
|
var ex = end.x() - kline.x();
|
|
var ey = end.y() - kline.y();
|
|
kline.points( [bx,by,ex,ey] );
|
|
// new line
|
|
var p2d0 = new dwv.math.Point2D(begin.x(), begin.y());
|
|
var p2d1 = new dwv.math.Point2D(end.x(), end.y());
|
|
var line = new dwv.math.Line(p2d0, p2d1);
|
|
// tick
|
|
var p2b = new dwv.math.Point2D(bx, by);
|
|
var p2e = new dwv.math.Point2D(ex, ey);
|
|
var linePerp0 = dwv.math.getPerpendicularLine( line, p2b, 10 );
|
|
ktick0.points( [linePerp0.getBegin().getX(), linePerp0.getBegin().getY(),
|
|
linePerp0.getEnd().getX(), linePerp0.getEnd().getY()] );
|
|
var linePerp1 = dwv.math.getPerpendicularLine( line, p2e, 10 );
|
|
ktick1.points( [linePerp1.getBegin().getX(), linePerp1.getBegin().getY(),
|
|
linePerp1.getEnd().getX(), linePerp1.getEnd().getY()] );
|
|
// larger hitfunc
|
|
kline.hitFunc( function (context) {
|
|
context.beginPath();
|
|
context.moveTo( linePerp0.getBegin().getX(), linePerp0.getBegin().getY() );
|
|
context.lineTo( linePerp0.getEnd().getX(), linePerp0.getEnd().getY() );
|
|
context.lineTo( linePerp1.getEnd().getX(), linePerp1.getEnd().getY() );
|
|
context.lineTo( linePerp1.getBegin().getX(), linePerp1.getBegin().getY() );
|
|
context.closePath();
|
|
context.fillStrokeShape(this);
|
|
});
|
|
// update text
|
|
var quant = image.quantifyLine( line );
|
|
var ktext = klabel.getText();
|
|
ktext.quant = quant;
|
|
ktext.setText(dwv.utils.replaceFlags(ktext.textExpr, ktext.quant));
|
|
// update position
|
|
var dX = line.getBegin().getX() > line.getEnd().getX() ? 0 : -1;
|
|
var dY = line.getBegin().getY() > line.getEnd().getY() ? -1 : 0.5;
|
|
var textPos = {
|
|
'x': line.getEnd().getX() + dX * 25,
|
|
'y': line.getEnd().getY() + dY * 15 };
|
|
klabel.position( textPos );
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
|
|
/**
|
|
* Scroll class.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.Scroll = function(app)
|
|
{
|
|
/**
|
|
* Closure to self: to be used by event handlers.
|
|
* @private
|
|
* @type WindowLevel
|
|
*/
|
|
var self = this;
|
|
/**
|
|
* Scroll GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Interaction start flag.
|
|
* @type Boolean
|
|
*/
|
|
this.started = false;
|
|
// touch timer ID (created by setTimeout)
|
|
var touchTimerID = null;
|
|
|
|
/**
|
|
* Handle mouse down event.
|
|
* @param {Object} event The mouse down event.
|
|
*/
|
|
this.mousedown = function(event){
|
|
// stop viewer if playing
|
|
if ( app.getViewController().isPlaying() ) {
|
|
app.getViewController().stop();
|
|
}
|
|
// start flag
|
|
self.started = true;
|
|
// first position
|
|
self.x0 = event._x;
|
|
self.y0 = event._y;
|
|
};
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {Object} event The mouse move event.
|
|
*/
|
|
this.mousemove = function(event){
|
|
if (!self.started) {
|
|
return;
|
|
}
|
|
|
|
// difference to last Y position
|
|
var diffY = event._y - self.y0;
|
|
var yMove = (Math.abs(diffY) > 15);
|
|
// do not trigger for small moves
|
|
if( yMove ) {
|
|
// update GUI
|
|
if( diffY > 0 ) {
|
|
app.getViewController().decrementSliceNb();
|
|
}
|
|
else {
|
|
app.getViewController().incrementSliceNb();
|
|
}
|
|
}
|
|
|
|
// difference to last X position
|
|
var diffX = event._x - self.x0;
|
|
var xMove = (Math.abs(diffX) > 15);
|
|
// do not trigger for small moves
|
|
if( xMove ) {
|
|
// update GUI
|
|
if( diffX > 0 ) {
|
|
app.getViewController().incrementFrameNb();
|
|
}
|
|
else {
|
|
app.getViewController().decrementFrameNb();
|
|
}
|
|
}
|
|
|
|
// reset origin point
|
|
if (xMove) {
|
|
self.x0 = event._x;
|
|
}
|
|
if (yMove) {
|
|
self.y0 = event._y;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse up event.
|
|
* @param {Object} event The mouse up event.
|
|
*/
|
|
this.mouseup = function(/*event*/){
|
|
if (self.started)
|
|
{
|
|
// stop recording
|
|
self.started = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse out event.
|
|
* @param {Object} event The mouse out event.
|
|
*/
|
|
this.mouseout = function(event){
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch start event.
|
|
* @param {Object} event The touch start event.
|
|
*/
|
|
this.touchstart = function(event){
|
|
// long touch triggers the dblclick
|
|
touchTimerID = setTimeout(self.dblclick, 500);
|
|
// call mouse equivalent
|
|
self.mousedown(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.touchmove = function(event){
|
|
// abort timer if move
|
|
if (touchTimerID !== null) {
|
|
clearTimeout(touchTimerID);
|
|
touchTimerID = null;
|
|
}
|
|
// call mouse equivalent
|
|
self.mousemove(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch end event.
|
|
* @param {Object} event The touch end event.
|
|
*/
|
|
this.touchend = function(event){
|
|
// abort timer
|
|
if (touchTimerID !== null) {
|
|
clearTimeout(touchTimerID);
|
|
touchTimerID = null;
|
|
}
|
|
// call mouse equivalent
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle mouse scroll event (fired by Firefox).
|
|
* @param {Object} event The mouse scroll event.
|
|
*/
|
|
this.DOMMouseScroll = function (event) {
|
|
// ev.detail on firefox is 3
|
|
if ( event.detail < 0 ) {
|
|
mouseScroll(true);
|
|
} else {
|
|
mouseScroll(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse wheel event.
|
|
* @param {Object} event The mouse wheel event.
|
|
*/
|
|
this.mousewheel = function (event) {
|
|
// ev.wheelDelta on chrome is 120
|
|
if ( event.wheelDelta > 0 ) {
|
|
mouseScroll(true);
|
|
} else {
|
|
mouseScroll(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Mouse scroll action.
|
|
* @param {Boolean} up True to increment, false to decrement.
|
|
*/
|
|
function mouseScroll (up) {
|
|
var hasSlices = (app.getImage().getGeometry().getSize().getNumberOfSlices() !== 1);
|
|
var hasFrames = (app.getImage().getNumberOfFrames() !== 1);
|
|
if ( up ) {
|
|
if (hasSlices) {
|
|
app.getViewController().incrementSliceNb();
|
|
} else if (hasFrames) {
|
|
app.getViewController().incrementFrameNb();
|
|
}
|
|
} else {
|
|
if (hasSlices) {
|
|
app.getViewController().decrementSliceNb();
|
|
} else if (hasFrames) {
|
|
app.getViewController().decrementFrameNb();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.keydown = function(event){
|
|
app.onKeydown(event);
|
|
};
|
|
/**
|
|
* Handle double click.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.dblclick = function (/*event*/) {
|
|
app.getViewController().play();
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui = new dwv.gui.Scroll(app);
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Enable the tool.
|
|
* @param {Boolean} bool The flag to enable or not.
|
|
*/
|
|
this.display = function(bool){
|
|
if ( gui ) {
|
|
gui.display(bool);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool.
|
|
*/
|
|
this.init = function() {
|
|
if ( app.isMonoSliceData() && app.getImage().getNumberOfFrames() === 1 ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
}; // Scroll class
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.Scroll.prototype.getHelp = function()
|
|
{
|
|
return {
|
|
"title": dwv.i18n("tool.Scroll.name"),
|
|
"brief": dwv.i18n("tool.Scroll.brief"),
|
|
"mouse": {
|
|
"mouse_drag": dwv.i18n("tool.Scroll.mouse_drag"),
|
|
"double_click": dwv.i18n("tool.Scroll.double_click")
|
|
},
|
|
"touch": {
|
|
'touch_drag': dwv.i18n("tool.Scroll.touch_drag"),
|
|
'tap_and_hold': dwv.i18n("tool.Scroll.tap_and_hold")
|
|
}
|
|
};
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
|
|
/**
|
|
* Tool box.
|
|
* @constructor
|
|
* @param {Array} toolList The list of tool objects.
|
|
* @param {Object} gui The associated gui.
|
|
*/
|
|
dwv.tool.Toolbox = function( toolList, app )
|
|
{
|
|
/**
|
|
* Toolbox GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Selected tool.
|
|
* @type Object
|
|
*/
|
|
var selectedTool = null;
|
|
/**
|
|
* Default tool name.
|
|
* @type String
|
|
*/
|
|
var defaultToolName = null;
|
|
|
|
/**
|
|
* Get the list of tools.
|
|
* @return {Array} The list of tool objects.
|
|
*/
|
|
this.getToolList = function ()
|
|
{
|
|
return toolList;
|
|
};
|
|
|
|
/**
|
|
* Get the selected tool.
|
|
* @return {Object} The selected tool.
|
|
*/
|
|
this.getSelectedTool = function ()
|
|
{
|
|
return selectedTool;
|
|
};
|
|
|
|
/**
|
|
* Setup the toolbox GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
if ( Object.keys(toolList).length !== 0 ) {
|
|
gui = new dwv.gui.Toolbox(app);
|
|
gui.setup(toolList);
|
|
|
|
for( var key in toolList ) {
|
|
toolList[key].setup();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Display the toolbox.
|
|
* @param {Boolean} bool Flag to display or not.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
if ( Object.keys(toolList).length !== 0 && gui ) {
|
|
gui.display(bool);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool box.
|
|
*/
|
|
this.init = function ()
|
|
{
|
|
var keys = Object.keys(toolList);
|
|
// check if we have tools
|
|
if ( keys.length === 0 ) {
|
|
return;
|
|
}
|
|
// init all tools
|
|
defaultToolName = "";
|
|
var displays = [];
|
|
var display = null;
|
|
for( var key in toolList ) {
|
|
display = toolList[key].init();
|
|
if ( display && defaultToolName === "" ) {
|
|
defaultToolName = key;
|
|
}
|
|
displays.push(display);
|
|
}
|
|
this.setSelectedTool(defaultToolName);
|
|
// init html
|
|
if ( gui ) {
|
|
gui.initialise(displays);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the selected tool.
|
|
* @return {String} The name of the tool to select.
|
|
*/
|
|
this.setSelectedTool = function (name)
|
|
{
|
|
// check if we have it
|
|
if( !this.hasTool(name) )
|
|
{
|
|
throw new Error("Unknown tool: '" + name + "'");
|
|
}
|
|
// hide last selected
|
|
if( selectedTool )
|
|
{
|
|
selectedTool.display(false);
|
|
}
|
|
// enable new one
|
|
selectedTool = toolList[name];
|
|
// display it
|
|
selectedTool.display(true);
|
|
};
|
|
|
|
/**
|
|
* Reset the tool box.
|
|
*/
|
|
this.reset = function ()
|
|
{
|
|
// hide last selected
|
|
if ( selectedTool ) {
|
|
selectedTool.display(false);
|
|
}
|
|
selectedTool = null;
|
|
defaultToolName = null;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Check if a tool is in the tool list.
|
|
* @param {String} name The name to check.
|
|
* @return {String} The tool list element for the given name.
|
|
*/
|
|
dwv.tool.Toolbox.prototype.hasTool = function (name)
|
|
{
|
|
return this.getToolList()[name];
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
|
|
/**
|
|
* UndoStack class.
|
|
* @constructor
|
|
*/
|
|
dwv.tool.UndoStack = function (app)
|
|
{
|
|
/**
|
|
* Undo GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = new dwv.gui.Undo(app);
|
|
/**
|
|
* Array of commands.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var stack = [];
|
|
|
|
/**
|
|
* Get the stack.
|
|
* @return {Array} The list of stored commands.
|
|
*/
|
|
this.getStack = function () { return stack; };
|
|
|
|
/**
|
|
* Current command index.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var curCmdIndex = 0;
|
|
|
|
/**
|
|
* Add a command to the stack.
|
|
* @param {Object} cmd The command to add.
|
|
*/
|
|
this.add = function(cmd)
|
|
{
|
|
// clear commands after current index
|
|
stack = stack.slice(0,curCmdIndex);
|
|
// store command
|
|
stack.push(cmd);
|
|
//stack[curCmdIndex] = cmd;
|
|
// increment index
|
|
++curCmdIndex;
|
|
// add command to display history
|
|
gui.addCommandToUndoHtml(cmd.getName());
|
|
};
|
|
|
|
/**
|
|
* Undo the last command.
|
|
*/
|
|
this.undo = function()
|
|
{
|
|
// a bit inefficient...
|
|
if( curCmdIndex > 0 )
|
|
{
|
|
// decrement command index
|
|
--curCmdIndex;
|
|
// undo last command
|
|
stack[curCmdIndex].undo();
|
|
// disable last in display history
|
|
gui.enableInUndoHtml(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Redo the last command.
|
|
*/
|
|
this.redo = function()
|
|
{
|
|
if( curCmdIndex < stack.length )
|
|
{
|
|
// run last command
|
|
stack[curCmdIndex].execute();
|
|
// increment command index
|
|
++curCmdIndex;
|
|
// enable next in display history
|
|
gui.enableInUndoHtml(true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool GUI.
|
|
*/
|
|
this.initialise = function ()
|
|
{
|
|
gui.initialise();
|
|
};
|
|
|
|
}; // UndoStack class
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
|
|
/**
|
|
* WindowLevel tool: handle window/level related events.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.WindowLevel = function(app)
|
|
{
|
|
/**
|
|
* Closure to self: to be used by event handlers.
|
|
* @private
|
|
* @type WindowLevel
|
|
*/
|
|
var self = this;
|
|
/**
|
|
* WindowLevel GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Interaction start flag.
|
|
* @type Boolean
|
|
*/
|
|
this.started = false;
|
|
|
|
/**
|
|
* Handle mouse down event.
|
|
* @param {Object} event The mouse down event.
|
|
*/
|
|
this.mousedown = function(event){
|
|
// set start flag
|
|
self.started = true;
|
|
// store initial position
|
|
self.x0 = event._x;
|
|
self.y0 = event._y;
|
|
// update GUI
|
|
app.getViewController().setCurrentPosition2D(event._x, event._y);
|
|
};
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {Object} event The mouse move event.
|
|
*/
|
|
this.mousemove = function(event){
|
|
// check start flag
|
|
if( !self.started ) {
|
|
return;
|
|
}
|
|
// difference to last position
|
|
var diffX = event._x - self.x0;
|
|
var diffY = self.y0 - event._y;
|
|
// calculate new window level
|
|
var windowCenter = parseInt(app.getViewController().getWindowLevel().center, 10) + diffY;
|
|
var windowWidth = parseInt(app.getViewController().getWindowLevel().width, 10) + diffX;
|
|
|
|
// add the manual preset to the view
|
|
app.getViewController().addWindowLevelPresets( { "manual": {
|
|
"wl": new dwv.image.WindowLevel(windowCenter, windowWidth),
|
|
"name": "manual"} } );
|
|
app.getViewController().setWindowLevelPreset("manual");
|
|
|
|
// update gui
|
|
if ( gui ) {
|
|
// initialise to add the manual preset
|
|
gui.initialise();
|
|
// set selected preset
|
|
dwv.gui.setSelected(app.getElement("presetSelect"), "manual");
|
|
}
|
|
|
|
// store position
|
|
self.x0 = event._x;
|
|
self.y0 = event._y;
|
|
};
|
|
|
|
/**
|
|
* Handle mouse up event.
|
|
* @param {Object} event The mouse up event.
|
|
*/
|
|
this.mouseup = function(/*event*/){
|
|
// set start flag
|
|
if( self.started ) {
|
|
self.started = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse out event.
|
|
* @param {Object} event The mouse out event.
|
|
*/
|
|
this.mouseout = function(event){
|
|
// treat as mouse up
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch start event.
|
|
* @param {Object} event The touch start event.
|
|
*/
|
|
this.touchstart = function(event){
|
|
self.mousedown(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.touchmove = function(event){
|
|
self.mousemove(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch end event.
|
|
* @param {Object} event The touch end event.
|
|
*/
|
|
this.touchend = function(event){
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle double click event.
|
|
* @param {Object} event The double click event.
|
|
*/
|
|
this.dblclick = function(event){
|
|
// update GUI
|
|
app.getViewController().setWindowLevel(
|
|
parseInt(app.getImage().getRescaledValue(
|
|
event._x, event._y, app.getViewController().getCurrentPosition().k), 10),
|
|
parseInt(app.getViewController().getWindowLevel().width, 10) );
|
|
};
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.keydown = function(event){
|
|
// let the app handle it
|
|
app.onKeydown(event);
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui = new dwv.gui.WindowLevel(app);
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Display the tool.
|
|
* @param {Boolean} bool The flag to display or not.
|
|
*/
|
|
this.display = function (bool)
|
|
{
|
|
if ( gui )
|
|
{
|
|
if( app.getImage().getPhotometricInterpretation().match(/MONOCHROME/) !== null ) {
|
|
gui.display(bool);
|
|
}
|
|
else {
|
|
gui.display(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool.
|
|
*/
|
|
this.init = function() {
|
|
if ( gui ) {
|
|
gui.initialise();
|
|
}
|
|
return true;
|
|
};
|
|
}; // WindowLevel class
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.WindowLevel.prototype.getHelp = function()
|
|
{
|
|
return {
|
|
"title": dwv.i18n("tool.WindowLevel.name"),
|
|
"brief": dwv.i18n("tool.WindowLevel.brief"),
|
|
"mouse": {
|
|
"mouse_drag": dwv.i18n("tool.WindowLevel.mouse_drag"),
|
|
"double_click": dwv.i18n("tool.WindowLevel.double_click")
|
|
},
|
|
"touch": {
|
|
"touch_drag": dwv.i18n("tool.WindowLevel.touch_drag")
|
|
}
|
|
};
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.tool = dwv.tool || {};
|
|
|
|
/**
|
|
* ZoomAndPan class.
|
|
* @constructor
|
|
* @param {Object} app The associated application.
|
|
*/
|
|
dwv.tool.ZoomAndPan = function(app)
|
|
{
|
|
/**
|
|
* Closure to self: to be used by event handlers.
|
|
* @private
|
|
* @type Object
|
|
*/
|
|
var self = this;
|
|
/**
|
|
* ZoomAndPan GUI.
|
|
* @type Object
|
|
*/
|
|
var gui = null;
|
|
/**
|
|
* Interaction start flag.
|
|
* @type Boolean
|
|
*/
|
|
this.started = false;
|
|
|
|
/**
|
|
* Handle mouse down event.
|
|
* @param {Object} event The mouse down event.
|
|
*/
|
|
this.mousedown = function(event){
|
|
self.started = true;
|
|
// first position
|
|
self.x0 = event._xs;
|
|
self.y0 = event._ys;
|
|
};
|
|
|
|
/**
|
|
* Handle two touch down event.
|
|
* @param {Object} event The touch down event.
|
|
*/
|
|
this.twotouchdown = function(event){
|
|
self.started = true;
|
|
// store first point
|
|
self.x0 = event._x;
|
|
self.y0 = event._y;
|
|
// first line
|
|
var point0 = new dwv.math.Point2D(event._x, event._y);
|
|
var point1 = new dwv.math.Point2D(event._x1, event._y1);
|
|
self.line0 = new dwv.math.Line(point0, point1);
|
|
self.midPoint = self.line0.getMidpoint();
|
|
};
|
|
|
|
/**
|
|
* Handle mouse move event.
|
|
* @param {Object} event The mouse move event.
|
|
*/
|
|
this.mousemove = function(event){
|
|
if (!self.started)
|
|
{
|
|
return;
|
|
}
|
|
// calculate translation
|
|
var tx = event._xs - self.x0;
|
|
var ty = event._ys - self.y0;
|
|
// apply translation
|
|
//app.translate(tx, ty);
|
|
app.stepTranslate(tx, ty);
|
|
// reset origin point
|
|
self.x0 = event._xs;
|
|
self.y0 = event._ys;
|
|
};
|
|
|
|
/**
|
|
* Handle two touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.twotouchmove = function(event){
|
|
if (!self.started)
|
|
{
|
|
return;
|
|
}
|
|
var point0 = new dwv.math.Point2D(event._x, event._y);
|
|
var point1 = new dwv.math.Point2D(event._x1, event._y1);
|
|
var newLine = new dwv.math.Line(point0, point1);
|
|
var lineRatio = newLine.getLength() / self.line0.getLength();
|
|
|
|
if( lineRatio === 1 )
|
|
{
|
|
// scroll mode
|
|
// difference to last position
|
|
var diffY = event._y - self.y0;
|
|
// do not trigger for small moves
|
|
if( Math.abs(diffY) < 15 ) {
|
|
return;
|
|
}
|
|
// update GUI
|
|
if( diffY > 0 ) {
|
|
app.getViewController().incrementSliceNb();
|
|
}
|
|
else {
|
|
app.getViewController().decrementSliceNb();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// zoom mode
|
|
var zoom = (lineRatio - 1) / 2;
|
|
if( Math.abs(zoom) % 0.1 <= 0.05 ) {
|
|
app.stepZoom(zoom, event._xs, event._ys);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse up event.
|
|
* @param {Object} event The mouse up event.
|
|
*/
|
|
this.mouseup = function(/*event*/){
|
|
if (self.started)
|
|
{
|
|
// stop recording
|
|
self.started = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle mouse out event.
|
|
* @param {Object} event The mouse out event.
|
|
*/
|
|
this.mouseout = function(event){
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle touch start event.
|
|
* @param {Object} event The touch start event.
|
|
*/
|
|
this.touchstart = function(event){
|
|
var touches = event.targetTouches;
|
|
if( touches.length === 1 ){
|
|
self.mousedown(event);
|
|
}
|
|
else if( touches.length === 2 ){
|
|
self.twotouchdown(event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle touch move event.
|
|
* @param {Object} event The touch move event.
|
|
*/
|
|
this.touchmove = function(event){
|
|
var touches = event.targetTouches;
|
|
if( touches.length === 1 ){
|
|
self.mousemove(event);
|
|
}
|
|
else if( touches.length === 2 ){
|
|
self.twotouchmove(event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle touch end event.
|
|
* @param {Object} event The touch end event.
|
|
*/
|
|
this.touchend = function(event){
|
|
self.mouseup(event);
|
|
};
|
|
|
|
/**
|
|
* Handle mouse scroll event (fired by Firefox).
|
|
* @param {Object} event The mouse scroll event.
|
|
*/
|
|
this.DOMMouseScroll = function(event){
|
|
// ev.detail on firefox is 3
|
|
var step = - event.detail / 30;
|
|
app.stepZoom(step, event._xs, event._ys);
|
|
};
|
|
|
|
/**
|
|
* Handle mouse wheel event.
|
|
* @param {Object} event The mouse wheel event.
|
|
*/
|
|
this.mousewheel = function(event){
|
|
// ev.wheelDelta on chrome is 120
|
|
var step = event.wheelDelta / 1200;
|
|
app.stepZoom(step, event._xs, event._ys);
|
|
};
|
|
|
|
/**
|
|
* Handle key down event.
|
|
* @param {Object} event The key down event.
|
|
*/
|
|
this.keydown = function(event){
|
|
app.onKeydown(event);
|
|
};
|
|
|
|
/**
|
|
* Setup the tool GUI.
|
|
*/
|
|
this.setup = function ()
|
|
{
|
|
gui = new dwv.gui.ZoomAndPan(app);
|
|
gui.setup();
|
|
};
|
|
|
|
/**
|
|
* Enable the tool.
|
|
* @param {Boolean} bool The flag to enable or not.
|
|
*/
|
|
this.display = function(bool){
|
|
if ( gui ) {
|
|
gui.display(bool);
|
|
}
|
|
};
|
|
|
|
}; // ZoomAndPan class
|
|
|
|
/**
|
|
* Help for this tool.
|
|
* @return {Object} The help content.
|
|
*/
|
|
dwv.tool.ZoomAndPan.prototype.getHelp = function()
|
|
{
|
|
return {
|
|
"title": dwv.i18n("tool.ZoomAndPan.name"),
|
|
"brief": dwv.i18n("tool.ZoomAndPan.brief"),
|
|
"mouse": {
|
|
"mouse_wheel": dwv.i18n("tool.ZoomAndPan.mouse_wheel"),
|
|
"mouse_drag": dwv.i18n("tool.ZoomAndPan.mouse_drag")
|
|
},
|
|
"touch": {
|
|
'twotouch_pinch': dwv.i18n("tool.ZoomAndPan.twotouch_pinch"),
|
|
'touch_drag': dwv.i18n("tool.ZoomAndPan.touch_drag")
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Initialise the tool.
|
|
*/
|
|
dwv.tool.ZoomAndPan.prototype.init = function() {
|
|
return true;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.browser = dwv.browser || {};
|
|
// external
|
|
var Modernizr = Modernizr || {};
|
|
|
|
/**
|
|
* Browser check for the FileAPI.
|
|
* Assume support for Safari5.
|
|
*/
|
|
dwv.browser.hasFileApi = function()
|
|
{
|
|
// regular test does not work on Safari 5
|
|
var isSafari5 = (navigator.appVersion.indexOf("Safari") !== -1) &&
|
|
(navigator.appVersion.indexOf("Chrome") === -1) &&
|
|
( (navigator.appVersion.indexOf("5.0.") !== -1) ||
|
|
(navigator.appVersion.indexOf("5.1.") !== -1) );
|
|
if( isSafari5 )
|
|
{
|
|
console.warn("Assuming FileAPI support for Safari5...");
|
|
return true;
|
|
}
|
|
// regular test
|
|
return Modernizr.filereader;
|
|
};
|
|
|
|
/**
|
|
* Browser check for the XMLHttpRequest.
|
|
*/
|
|
dwv.browser.hasXmlHttpRequest = function()
|
|
{
|
|
return Modernizr.xhrresponsetype &&
|
|
Modernizr.xhrresponsetypearraybuffer && Modernizr.xhrresponsetypetext &&
|
|
"XMLHttpRequest" in window && "withCredentials" in new XMLHttpRequest();
|
|
};
|
|
|
|
/**
|
|
* Browser check for typed array.
|
|
*/
|
|
dwv.browser.hasTypedArray = function()
|
|
{
|
|
return Modernizr.dataview && Modernizr.typedarrays;
|
|
};
|
|
|
|
/**
|
|
* Browser check for input with type='color'.
|
|
* Missing in IE and Safari.
|
|
*/
|
|
dwv.browser.hasInputColor = function()
|
|
{
|
|
return Modernizr.inputtypes.color;
|
|
};
|
|
|
|
/**
|
|
* Browser check for input with type='files' and webkitdirectory flag.
|
|
* Missing in IE and Safari.
|
|
*/
|
|
dwv.browser.hasInputDirectory = function()
|
|
{
|
|
return Modernizr.fileinputdirectory;
|
|
};
|
|
|
|
|
|
//only check at startup (since we propose a replacement)
|
|
dwv.browser._hasTypedArraySlice = (typeof Uint8Array.prototype.slice !== "undefined");
|
|
|
|
/**
|
|
* Browser check for typed array slice method.
|
|
* Missing in Internet Explorer 11.
|
|
*/
|
|
dwv.browser.hasTypedArraySlice = function()
|
|
{
|
|
return dwv.browser._hasTypedArraySlice;
|
|
};
|
|
|
|
// only check at startup (since we propose a replacement)
|
|
dwv.browser._hasFloat64Array = ("Float64Array" in window);
|
|
|
|
/**
|
|
* Browser check for Float64Array array.
|
|
* Missing in PhantomJS 1.9.20 (on Travis).
|
|
*/
|
|
dwv.browser.hasFloat64Array = function()
|
|
{
|
|
return dwv.browser._hasFloat64Array;
|
|
};
|
|
|
|
//only check at startup (since we propose a replacement)
|
|
dwv.browser._hasClampedArray = ("Uint8ClampedArray" in window);
|
|
|
|
/**
|
|
* Browser check for clamped array.
|
|
* Missing in:
|
|
* - Safari 5.1.7 for Windows
|
|
* - PhantomJS 1.9.20 (on Travis).
|
|
*/
|
|
dwv.browser.hasClampedArray = function()
|
|
{
|
|
return dwv.browser._hasClampedArray;
|
|
};
|
|
|
|
/**
|
|
* Browser checks to see if it can run dwv. Throws an error if not.
|
|
* Silently replaces basic functions.
|
|
*/
|
|
dwv.browser.check = function()
|
|
{
|
|
|
|
// Required --------------
|
|
|
|
var appnorun = "The application cannot be run.";
|
|
var message = "";
|
|
// Check for the File API support
|
|
if( !dwv.browser.hasFileApi() ) {
|
|
message = "The File APIs are not supported in this browser. ";
|
|
alert(message+appnorun);
|
|
throw new Error(message);
|
|
}
|
|
// Check for XMLHttpRequest
|
|
if( !dwv.browser.hasXmlHttpRequest() ) {
|
|
message = "The XMLHttpRequest is not supported in this browser. ";
|
|
alert(message+appnorun);
|
|
throw new Error(message);
|
|
}
|
|
// Check typed array
|
|
if( !dwv.browser.hasTypedArray() ) {
|
|
message = "The Typed arrays are not supported in this browser. ";
|
|
alert(message+appnorun);
|
|
throw new Error(message);
|
|
}
|
|
|
|
// Replaced if not present ------------
|
|
|
|
// Check typed array slice
|
|
if( !dwv.browser.hasTypedArraySlice() ) {
|
|
// silent fail with warning
|
|
console.warn("The TypedArray.slice method is not supported in this browser. This may impair performance. ");
|
|
// basic Uint16Array implementation
|
|
Uint16Array.prototype.slice = function (begin, end) {
|
|
var size = end - begin;
|
|
var cloned = new Uint16Array(size);
|
|
for (var i = 0; i < size; i++) {
|
|
cloned[i] = this[begin + i];
|
|
}
|
|
return cloned;
|
|
};
|
|
// basic Int16Array implementation
|
|
Int16Array.prototype.slice = function (begin, end) {
|
|
var size = end - begin;
|
|
var cloned = new Int16Array(size);
|
|
for (var i = 0; i < size; i++) {
|
|
cloned[i] = this[begin + i];
|
|
}
|
|
return cloned;
|
|
};
|
|
// basic Uint8Array implementation
|
|
Uint8Array.prototype.slice = function (begin, end) {
|
|
var size = end - begin;
|
|
var cloned = new Uint8Array(size);
|
|
for (var i = 0; i < size; i++) {
|
|
cloned[i] = this[begin + i];
|
|
}
|
|
return cloned;
|
|
};
|
|
// basic Int8Array implementation
|
|
Int8Array.prototype.slice = function (begin, end) {
|
|
var size = end - begin;
|
|
var cloned = new Int8Array(size);
|
|
for (var i = 0; i < size; i++) {
|
|
cloned[i] = this[begin + i];
|
|
}
|
|
return cloned;
|
|
};
|
|
}
|
|
// check clamped array
|
|
if( !dwv.browser.hasClampedArray() ) {
|
|
// silent fail with warning
|
|
console.warn("The Uint8ClampedArray is not supported in this browser. This may impair performance. ");
|
|
// Use Uint8Array instead... Not good
|
|
// TODO Find better replacement!
|
|
window.Uint8ClampedArray = window.Uint8Array;
|
|
}
|
|
// check Float64 array
|
|
if( !dwv.browser.hasFloat64Array() ) {
|
|
// silent fail with warning
|
|
console.warn("The Float64Array is not supported in this browser. This may impair performance. ");
|
|
// Use Float32Array instead... Not good
|
|
// TODO Find better replacement!
|
|
window.Float64Array = window.Float32Array;
|
|
}
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
// external
|
|
var i18next = i18next || {};
|
|
var i18nextXHRBackend = i18nextXHRBackend || {};
|
|
var i18nextBrowserLanguageDetector = i18nextBrowserLanguageDetector || {};
|
|
|
|
// This is mainly a wrapper around the i18next object.
|
|
// see its API: http://i18next.com/docs/api/
|
|
|
|
// global locales path
|
|
dwv.i18nLocalesPath = null;
|
|
|
|
/**
|
|
* Initialise i18n.
|
|
* @param {String} language The language to translate to. Defaults to 'auto' and
|
|
* gets the language from the browser.
|
|
* @param {String} localesPath Path to the locales directory.
|
|
* @external i18next
|
|
* @external i18nextXHRBackend
|
|
* @external i18nextBrowserLanguageDetector
|
|
*/
|
|
dwv.i18nInitialise = function (language, localesPath)
|
|
{
|
|
var lng = (typeof language === "undefined") ? "auto" : language;
|
|
var lpath = (typeof localesPath === "undefined") ? "../.." : localesPath;
|
|
// store as global
|
|
dwv.i18nLocalesPath = lpath;
|
|
// i18n options: default 'en' language and
|
|
// only load language, not specialised (for ex en-GB)
|
|
var options = {
|
|
fallbackLng: "en",
|
|
load: "languageOnly",
|
|
backend: { loadPath: lpath + "/locales/{{lng}}/{{ns}}.json" }
|
|
};
|
|
// use the XHR backend to get translation files
|
|
var i18n = i18next.use(i18nextXHRBackend);
|
|
// use browser language or the specified one
|
|
if (lng === "auto") {
|
|
i18n.use(i18nextBrowserLanguageDetector);
|
|
}
|
|
else {
|
|
options.lng = lng;
|
|
}
|
|
// init i18n: will be ready when the 'loaded' event is fired
|
|
i18n.init(options);
|
|
};
|
|
|
|
/**
|
|
* Initialise i18n with recources as input.
|
|
* @param {String} language The language to translate to. Defaults to 'auto' and
|
|
* gets the language from the browser.
|
|
* @param {Object} resources Languages provided as object.
|
|
* @external i18next
|
|
* @external i18nextBrowserLanguageDetector
|
|
*/
|
|
dwv.i18nInitialiseWithResources = function (language, resources)
|
|
{
|
|
var lng = (typeof language === "undefined") ? "auto" : language;
|
|
// i18n options: default 'en' language and
|
|
// only load language, not specialised (for ex en-GB)
|
|
var options = {
|
|
fallbackLng: "en",
|
|
load: "languageOnly",
|
|
resources: resources
|
|
};
|
|
// use browser language or the specified one
|
|
// init i18n: will be ready when the 'loaded' event is fired
|
|
if (lng === "auto") {
|
|
var i18n = i18next.use(i18nextBrowserLanguageDetector);
|
|
i18n.init(options);
|
|
}
|
|
else {
|
|
options.lng = lng;
|
|
i18next.init(options);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle i18n 'initialized' event.
|
|
* @param {Object} callback The callback function to call when i18n is initialised.
|
|
* It can take one argument that will be replaced with the i18n options.
|
|
* @external i18next
|
|
*/
|
|
dwv.i18nOnInitialised = function (callback) {
|
|
i18next.on('initialized', callback);
|
|
};
|
|
|
|
/**
|
|
* Stop handling i18n load event.
|
|
* @external i18next
|
|
*/
|
|
dwv.i18nOffInitialised = function () {
|
|
i18next.off('initialized');
|
|
};
|
|
|
|
/**
|
|
* Handle i18n failed load event.
|
|
* @param {Object} callback The callback function to call when i18n is loaded.
|
|
* It can take three arguments: lng, ns and msg.
|
|
* @external i18next
|
|
*/
|
|
dwv.i18nOnFailedLoad = function (callback) {
|
|
i18next.on('failedLoading', callback);
|
|
};
|
|
|
|
/**
|
|
* Stop handling i18n failed load event.
|
|
* @external i18next
|
|
*/
|
|
dwv.i18nOffFailedLoad = function () {
|
|
i18next.off('failedLoading');
|
|
};
|
|
|
|
/**
|
|
* Get the translated text.
|
|
* @param {String} key The key to the text entry.
|
|
* @param {Object} options The translation options such as plural, context...
|
|
* @external i18next
|
|
*/
|
|
dwv.i18n = function (key, options) {
|
|
return i18next.t(key, options);
|
|
};
|
|
|
|
/**
|
|
* Check the existence of a translation.
|
|
* @param {String} key The key to the text entry.
|
|
* @param {Object} options The translation options such as plural, context...
|
|
* @external i18next
|
|
*/
|
|
dwv.i18nExists = function (key, options) {
|
|
return i18next.exists(key, options);
|
|
};
|
|
|
|
/**
|
|
* Translate all data-i18n tags in the current html page. If an html tag defines the
|
|
* data-i18n attribute, its value will be used as key to find its corresponding text
|
|
* and will replace the content of the html tag.
|
|
*/
|
|
dwv.i18nPage = function () {
|
|
// get all elements
|
|
var elements = document.getElementsByTagName("*");
|
|
// if the element defines data-i18n, replace its content with the tranlation
|
|
for (var i = 0; i < elements.length; ++i) {
|
|
if (typeof elements[i].dataset.i18n !== "undefined") {
|
|
elements[i].innerHTML = dwv.i18n(elements[i].dataset.i18n);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the current locale resource path.
|
|
* Warning: to be used once i18next is initialised.
|
|
* @return {String} The path to the locale resource.
|
|
*/
|
|
dwv.i18nGetLocalePath = function (filename) {
|
|
var lng = i18next.language.substr(0, 2);
|
|
return dwv.i18nLocalesPath +
|
|
"/locales/" + lng + "/" + filename;
|
|
};
|
|
|
|
/**
|
|
* Get the current locale resource path.
|
|
* Warning: to be used once i18next is initialised.
|
|
* @return {String} The path to the locale resource.
|
|
*/
|
|
dwv.i18nGetFallbackLocalePath = function (filename) {
|
|
var lng = i18next.languages[i18next.languages.length-1].substr(0, 2);
|
|
return dwv.i18nLocalesPath +
|
|
"/locales/" + lng + "/" + filename;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.utils = dwv.utils || {};
|
|
|
|
/**
|
|
* ListenerHandler class: handles add/removing and firing listeners.
|
|
* @constructor
|
|
*/
|
|
dwv.utils.ListenerHandler = function ()
|
|
{
|
|
/**
|
|
* listeners.
|
|
* @private
|
|
*/
|
|
var listeners = {};
|
|
|
|
/**
|
|
* Add an event listener.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type,
|
|
* will be called with the fired event.
|
|
*/
|
|
this.add = function (type, callback)
|
|
{
|
|
// create array if not present
|
|
if ( typeof listeners[type] === "undefined" ) {
|
|
listeners[type] = [];
|
|
}
|
|
// add callback to listeners array
|
|
listeners[type].push(callback);
|
|
};
|
|
|
|
/**
|
|
* Remove an event listener.
|
|
* @param {String} type The event type.
|
|
* @param {Object} callback The method associated with the provided event type.
|
|
*/
|
|
this.remove = function (type, callback)
|
|
{
|
|
// check if the type is present
|
|
if( typeof listeners[type] === "undefined" ) {
|
|
return;
|
|
}
|
|
// remove from listeners array
|
|
for ( var i = 0; i < listeners[type].length; ++i ) {
|
|
if ( listeners[type][i] === callback ) {
|
|
listeners[type].splice(i,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Fire an event: call all associated listeners with the input event object.
|
|
* @param {Object} event The event to fire.
|
|
*/
|
|
this.fireEvent = function (event)
|
|
{
|
|
// check if they are listeners for the event type
|
|
if ( typeof listeners[event.type] === "undefined" ) {
|
|
return;
|
|
}
|
|
// fire events
|
|
for ( var i=0; i < listeners[event.type].length; ++i ) {
|
|
listeners[event.type][i](event);
|
|
}
|
|
};
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.utils = dwv.utils || {};
|
|
|
|
/**
|
|
* Multiple progresses handler.
|
|
* Stores a multi dimensional list of progresses to allow to
|
|
* calculate a global progress.
|
|
* @param {Function} callback The function to pass the global progress to.
|
|
*/
|
|
dwv.utils.MultiProgressHandler = function (callback)
|
|
{
|
|
// closure to self
|
|
var self = this;
|
|
|
|
/**
|
|
* List of progresses.
|
|
* @private
|
|
* @type Array
|
|
*/
|
|
var progresses = [];
|
|
|
|
/**
|
|
* Number of dimensions.
|
|
* @private
|
|
* @type Number
|
|
*/
|
|
var numberOfDimensions = 2;
|
|
|
|
/**
|
|
* Set the number of dimensions.
|
|
* @param {Number} num The number.
|
|
*/
|
|
this.setNumberOfDimensions = function (num) {
|
|
numberOfDimensions = num;
|
|
};
|
|
|
|
/**
|
|
* Set the number of data to load.
|
|
* @param {Number} n The number of data to load.
|
|
*/
|
|
this.setNToLoad = function (n) {
|
|
for ( var i = 0; i < n; ++i ) {
|
|
progresses[i] = [];
|
|
for ( var j = 0; j < numberOfDimensions; ++j ) {
|
|
progresses[i][j] = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle a load progress.
|
|
* @param {Object} event The progress event.
|
|
*/
|
|
this.onprogress = function (event) {
|
|
// check event
|
|
if ( !event.lengthComputable ) {
|
|
return;
|
|
}
|
|
if ( typeof event.subindex === "undefined" ) {
|
|
return;
|
|
}
|
|
if ( typeof event.index === "undefined" ) {
|
|
return;
|
|
}
|
|
// calculate percent
|
|
var percent = (event.loaded * 100) / event.total;
|
|
// set percent for index
|
|
progresses[event.index][event.subindex] = percent;
|
|
|
|
// call callback
|
|
callback({'type': event.type, 'lengthComputable': true,
|
|
'loaded': getGlobalPercent(), 'total': 100});
|
|
};
|
|
|
|
/**
|
|
* Get the global load percent including the provided one.
|
|
* @return {Number} The accumulated percentage.
|
|
*/
|
|
function getGlobalPercent() {
|
|
var sum = 0;
|
|
var lenprog = progresses.length;
|
|
for ( var i = 0; i < lenprog; ++i ) {
|
|
for ( var j = 0; j < numberOfDimensions; ++j ) {
|
|
sum += progresses[i][j];
|
|
}
|
|
}
|
|
return Math.round( sum / (lenprog * numberOfDimensions) );
|
|
}
|
|
|
|
/**
|
|
* Create a mono progress event handler.
|
|
* @param {Number} index The index of the data.
|
|
* @param {Number} subindex The sub-index of the data.
|
|
*/
|
|
this.getMonoProgressHandler = function (index, subindex) {
|
|
return function (event) {
|
|
event.index = index;
|
|
event.subindex = subindex;
|
|
self.onprogress(event);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Create a mono loadend event handler: sends a 100% progress.
|
|
* @param {Number} index The index of the data.
|
|
* @param {Number} subindex The sub-index of the data.
|
|
*/
|
|
this.getMonoOnLoadEndHandler = function (index, subindex) {
|
|
return function () {
|
|
self.onprogress({'type': 'load-progress', 'lengthComputable': true,
|
|
'loaded': 100, 'total': 100,
|
|
'index': index, 'subindex': subindex});
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Create a mono progress event handler with an undefined index.
|
|
* Warning: The caller handles the progress index.
|
|
* @param {Number} subindex The sub-index of the data.
|
|
*/
|
|
this.getUndefinedMonoProgressHandler = function (subindex) {
|
|
return function (event) {
|
|
event.subindex = subindex;
|
|
self.onprogress(event);
|
|
};
|
|
};
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
/** @namespace */
|
|
dwv.utils = dwv.utils || {};
|
|
|
|
/**
|
|
* Capitalise the first letter of a string.
|
|
* @param {String} string The string to capitalise the first letter.
|
|
* @return {String} The new string.
|
|
*/
|
|
dwv.utils.capitaliseFirstLetter = function (string)
|
|
{
|
|
var res = string;
|
|
if ( string ) {
|
|
res = string.charAt(0).toUpperCase() + string.slice(1);
|
|
}
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* Split key/value string:
|
|
* key0=val00&key0=val01&key1=val10 returns
|
|
* { key0 : [val00, val01], key1 : val1 }
|
|
* @param {String} inputStr The string to split.
|
|
* @return {Object} The split string.
|
|
*/
|
|
dwv.utils.splitKeyValueString = function (inputStr)
|
|
{
|
|
// result
|
|
var result = {};
|
|
// check input string
|
|
if ( inputStr ) {
|
|
// split key/value pairs
|
|
var pairs = inputStr.split('&');
|
|
for ( var i = 0; i < pairs.length; ++i )
|
|
{
|
|
var pair = pairs[i].split('=');
|
|
// if the key does not exist, create it
|
|
if ( !result[pair[0]] )
|
|
{
|
|
result[pair[0]] = pair[1];
|
|
}
|
|
else
|
|
{
|
|
// make it an array
|
|
if ( !( result[pair[0]] instanceof Array ) ) {
|
|
result[pair[0]] = [result[pair[0]]];
|
|
}
|
|
result[pair[0]].push(pair[1]);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Replace flags in a input string. Flags are keywords surrounded with curly
|
|
* braces.
|
|
* @param {String} inputStr The input string.
|
|
* @param {Object} values A object of {value, unit}.
|
|
* @example
|
|
* var values = {"length": { "value": 33, "unit": "cm" } };
|
|
* var str = "The length is: {length}.";
|
|
* var res = dwv.utils.replaceFlags(str, values); // "The length is: 33 cm."
|
|
* @return {String} The result string.
|
|
*/
|
|
dwv.utils.replaceFlags = function (inputStr, values)
|
|
{
|
|
var res = "";
|
|
// check input string
|
|
if (inputStr === null || typeof inputStr === "undefined") {
|
|
return res;
|
|
}
|
|
res = inputStr;
|
|
// check values
|
|
if (values === null || typeof values === "undefined") {
|
|
return res;
|
|
}
|
|
// loop through values keys
|
|
var keys = Object.keys(values);
|
|
for (var i = 0; i < keys.length; ++i) {
|
|
var valueObj = values[keys[i]];
|
|
if ( valueObj !== null && typeof valueObj !== "undefined" &&
|
|
valueObj.value !== null && typeof valueObj.value !== "undefined") {
|
|
// value string
|
|
var valueStr = valueObj.value.toPrecision(4);
|
|
// add unit if available
|
|
// space or no space? Yes apart from degree...
|
|
// check: https://en.wikipedia.org/wiki/Space_(punctuation)#Spaces_and_unit_symbols
|
|
if (valueObj.unit !== null && typeof valueObj.unit !== "undefined" &&
|
|
valueObj.unit.length !== 0) {
|
|
if (valueObj.unit !== "degree") {
|
|
valueStr += " ";
|
|
}
|
|
valueStr += valueObj.unit;
|
|
}
|
|
// flag to replace
|
|
var flag = '{' + keys[i] + '}';
|
|
// replace
|
|
res = res.replace(flag, valueStr);
|
|
}
|
|
}
|
|
// return
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* Replace flags in a input string. Flags are keywords surrounded with curly
|
|
* braces.
|
|
* @param {String} inputStr The input string.
|
|
* @param {Array} values An array of strings.
|
|
* @example
|
|
* var values = ["a", "b"];
|
|
* var str = "The length is: {v0}. The size is: {v1}";
|
|
* var res = dwv.utils.replaceFlags2(str, values); // "The length is: a. The size is: b"
|
|
* @return {String} The result string.
|
|
*/
|
|
dwv.utils.replaceFlags2 = function (inputStr, values)
|
|
{
|
|
var res = inputStr;
|
|
for ( var j = 0; j < values.length; ++j ) {
|
|
res = res.replace("{v"+j+"}", values[j]);
|
|
}
|
|
return res;
|
|
};
|
|
|
|
dwv.utils.createDefaultReplaceFormat = function (values)
|
|
{
|
|
var res = "";
|
|
for ( var j = 0; j < values.length; ++j ) {
|
|
if ( j !== 0 ) {
|
|
res += ", ";
|
|
}
|
|
res += "{v"+j+"}";
|
|
}
|
|
return res;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.utils = dwv.utils || {};
|
|
|
|
/**
|
|
* Thread Pool.
|
|
* Highly inspired from {@link http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool}.
|
|
* @constructor
|
|
* @param {Number} poolSize The size of the pool.
|
|
*/
|
|
dwv.utils.ThreadPool = function (poolSize) {
|
|
|
|
// task queue
|
|
var taskQueue = [];
|
|
// lsit of available threads
|
|
var freeThreads = [];
|
|
// list of running threads (unsed in abort)
|
|
var runningThreads = [];
|
|
|
|
/**
|
|
* Initialise.
|
|
*/
|
|
this.init = function () {
|
|
// create 'poolSize' number of worker threads
|
|
for (var i = 0; i < poolSize; ++i) {
|
|
freeThreads.push(new dwv.utils.WorkerThread(this));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add a worker task to the queue.
|
|
* Will be run when a thread is made available.
|
|
* @return {Object} workerTask The task to add.
|
|
*/
|
|
this.addWorkerTask = function (workerTask) {
|
|
if (freeThreads.length > 0) {
|
|
// get the first free worker thread
|
|
var workerThread = freeThreads.shift();
|
|
// run the input task
|
|
workerThread.run(workerTask);
|
|
// add the thread to the runnning list
|
|
runningThreads.push(workerThread);
|
|
} else {
|
|
// no free thread, add task to queue
|
|
taskQueue.push(workerTask);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Abort all threads.
|
|
*/
|
|
this.abort = function () {
|
|
// clear tasks
|
|
taskQueue = [];
|
|
// cancel running workers
|
|
for (var i = 0; i < runningThreads.length; ++i) {
|
|
runningThreads[i].stop();
|
|
}
|
|
runningThreads = [];
|
|
// re-init
|
|
this.init();
|
|
};
|
|
|
|
/**
|
|
* Free a worker thread.
|
|
* @param {Object} workerThread The thread to free.
|
|
*/
|
|
this.freeWorkerThread = function (workerThread) {
|
|
// send worker end
|
|
this.onworkerend();
|
|
|
|
if (taskQueue.length > 0) {
|
|
// get waiting task
|
|
var workerTask = taskQueue.shift();
|
|
// use input thread to run the waiting task
|
|
workerThread.run(workerTask);
|
|
} else {
|
|
// no task to run, add to free list
|
|
freeThreads.push(workerThread);
|
|
// remove from running list
|
|
for ( var i = 0; i < runningThreads.length; ++i ) {
|
|
if ( runningThreads[i].getId() === workerThread.getId() ) {
|
|
runningThreads.splice(i, 1);
|
|
}
|
|
}
|
|
// the work is done when the queue is back to its initial size
|
|
if ( freeThreads.length === poolSize ) {
|
|
this.onpoolworkend();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Handle a pool work end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.utils.ThreadPool.prototype.onpoolworkend = function () {};
|
|
/**
|
|
* Handle a pool worker end event.
|
|
* Default does nothing.
|
|
*/
|
|
dwv.utils.ThreadPool.prototype.onworkerend = function () {};
|
|
|
|
/**
|
|
* Worker thread.
|
|
* @external Worker
|
|
* @constructor
|
|
* @param {Object} parentPool The parent pool.
|
|
*/
|
|
dwv.utils.WorkerThread = function (parentPool) {
|
|
// closure to self
|
|
var self = this;
|
|
|
|
// thread ID
|
|
var id = Math.random().toString(36).substring(2, 15);
|
|
|
|
// running task
|
|
var runningTask = {};
|
|
// worker used to run task
|
|
var worker;
|
|
|
|
/**
|
|
* Get the thread ID.
|
|
* @return {String} The thread ID (alphanumeric).
|
|
*/
|
|
this.getId = function () {
|
|
return id;
|
|
};
|
|
|
|
/**
|
|
* Run a worker task
|
|
* @param {Object} workerTask The task to run.
|
|
*/
|
|
this.run = function (workerTask) {
|
|
// store task
|
|
runningTask = workerTask;
|
|
// create a new web worker
|
|
if (runningTask.script !== null) {
|
|
worker = new Worker(runningTask.script);
|
|
worker.addEventListener('message', ontaskend, false);
|
|
// launch the worker
|
|
worker.postMessage(runningTask.startMessage);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Stop a run and free the thread.
|
|
*/
|
|
this.stop = function () {
|
|
// stop the worker
|
|
worker.terminate();
|
|
// tell the parent pool this thread is free
|
|
parentPool.freeWorkerThread(this);
|
|
};
|
|
|
|
/**
|
|
* Handle once the task is done.
|
|
* For now assume we only get a single callback from a worker
|
|
* which also indicates the end of this worker.
|
|
* @param {Object} event The callback event.
|
|
*/
|
|
function ontaskend(event) {
|
|
// pass to original callback
|
|
runningTask.callback(event);
|
|
// stop the worker and free the thread
|
|
self.stop();
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Worker task.
|
|
* @constructor
|
|
* @param {String} script The worker script.
|
|
* @param {Function} callback The worker callback.
|
|
* @param {Object} message The data to pass to the worker.
|
|
*/
|
|
dwv.utils.WorkerTask = function (script, callback, message) {
|
|
// worker script
|
|
this.script = script;
|
|
// worker callback
|
|
this.callback = callback;
|
|
// worker start message
|
|
this.startMessage = message;
|
|
};
|
|
|
|
// namespaces
|
|
var dwv = dwv || {};
|
|
dwv.utils = dwv.utils || {};
|
|
/** @namespace */
|
|
dwv.utils.base = dwv.utils.base || {};
|
|
|
|
/**
|
|
* Split an input URI:
|
|
* 'root?key0=val00&key0=val01&key1=val10' returns
|
|
* { base : root, query : [ key0 : [val00, val01], key1 : val1 ] }
|
|
* Returns an empty object if the input string is not correct (null, empty...)
|
|
* or if it is not a query string (no question mark).
|
|
* @param {String} inputStr The string to split.
|
|
* @return {Object} The split string.
|
|
*/
|
|
dwv.utils.splitUri = function (uri)
|
|
{
|
|
// result
|
|
var result = {};
|
|
// check if query string
|
|
var sepIndex = null;
|
|
if ( uri && (sepIndex = uri.indexOf('?')) !== -1 ) {
|
|
// base: before the '?'
|
|
result.base = uri.substr(0, sepIndex);
|
|
// query : after the '?' and until possible '#'
|
|
var hashIndex = uri.indexOf('#');
|
|
if ( hashIndex === -1 ) {
|
|
hashIndex = uri.length;
|
|
}
|
|
var query = uri.substr(sepIndex + 1, (hashIndex - 1 - sepIndex));
|
|
// split key/value pairs of the query
|
|
result.query = dwv.utils.splitKeyValueString(query);
|
|
}
|
|
// return
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Get the query part, split into an array, of an input URI.
|
|
* The URI scheme is: 'base?query#fragment'
|
|
* @param {String } uri The input URI.
|
|
* @return {Object} The query part, split into an array, of the input URI.
|
|
*/
|
|
dwv.utils.getUriQuery = function (uri)
|
|
{
|
|
// split
|
|
var parts = dwv.utils.splitUri(uri);
|
|
// check not empty
|
|
if ( Object.keys(parts).length === 0 ) {
|
|
return null;
|
|
}
|
|
// return query
|
|
return parts.query;
|
|
};
|
|
|
|
/**
|
|
* Generic URI query decoder.
|
|
* Supports manifest:
|
|
* [dwv root]?input=encodeURIComponent('[manifest file]')&type=manifest
|
|
* or encoded URI with base and key value/pairs:
|
|
* [dwv root]?input=encodeURIComponent([root]?key0=value0&key1=value1)
|
|
* @param {String} query The query part to the input URI.
|
|
* @param {Function} callback The function to call with the decoded file urls.
|
|
*/
|
|
dwv.utils.base.decodeQuery = function (query, callback)
|
|
{
|
|
// manifest
|
|
if ( query.type && query.type === "manifest" ) {
|
|
dwv.utils.decodeManifestQuery( query, callback );
|
|
}
|
|
// default case: encoded URI with base and key/value pairs
|
|
else {
|
|
callback( dwv.utils.decodeKeyValueUri( query.input, query.dwvReplaceMode ) );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Decode a Key/Value pair URI. If a key is repeated, the result
|
|
* be an array of base + each key.
|
|
* @param {String} uri The URI to decode.
|
|
* @param {String} replaceMode The key replace more.
|
|
* replaceMode can be:
|
|
* - key (default): keep the key
|
|
* - other than key: do not use the key
|
|
* 'file' is a special case where the '?' of the query is not kept.
|
|
* @return The list of input file urls.
|
|
*/
|
|
dwv.utils.decodeKeyValueUri = function (uri, replaceMode)
|
|
{
|
|
var result = [];
|
|
|
|
// repeat key replace mode (default to keep key)
|
|
var repeatKeyReplaceMode = "key";
|
|
if ( replaceMode ) {
|
|
repeatKeyReplaceMode = replaceMode;
|
|
}
|
|
|
|
// decode input URI
|
|
var queryUri = decodeURIComponent(uri);
|
|
// get key/value pairs from input URI
|
|
var inputQueryPairs = dwv.utils.splitUri(queryUri);
|
|
if ( Object.keys(inputQueryPairs).length === 0 )
|
|
{
|
|
result.push(queryUri);
|
|
}
|
|
else
|
|
{
|
|
var keys = Object.keys(inputQueryPairs.query);
|
|
// find repeat key
|
|
var repeatKey = null;
|
|
for ( var i = 0; i < keys.length; ++i )
|
|
{
|
|
if ( inputQueryPairs.query[keys[i]] instanceof Array )
|
|
{
|
|
repeatKey = keys[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !repeatKey )
|
|
{
|
|
result.push(queryUri);
|
|
}
|
|
else
|
|
{
|
|
var repeatList = inputQueryPairs.query[repeatKey];
|
|
// build base uri
|
|
var baseUrl = inputQueryPairs.base;
|
|
// do not add '?' when the repeatKey is 'file'
|
|
// root/path/to/?file=0.jpg&file=1.jpg
|
|
if ( repeatKey !== "file" ) {
|
|
baseUrl += "?";
|
|
}
|
|
var gotOneArg = false;
|
|
for ( var j = 0; j < keys.length; ++j )
|
|
{
|
|
if ( keys[j] !== repeatKey ) {
|
|
if ( gotOneArg ) {
|
|
baseUrl += "&";
|
|
}
|
|
baseUrl += keys[j] + "=" + inputQueryPairs.query[keys[j]];
|
|
gotOneArg = true;
|
|
}
|
|
}
|
|
// append built urls to result
|
|
var url;
|
|
for ( var k = 0; k < repeatList.length; ++k )
|
|
{
|
|
url = baseUrl;
|
|
if ( gotOneArg ) {
|
|
url += "&";
|
|
}
|
|
if ( repeatKeyReplaceMode === "key" ) {
|
|
url += repeatKey + "=";
|
|
}
|
|
// other than 'key' mode: do nothing
|
|
url += repeatList[k];
|
|
result.push(url);
|
|
}
|
|
}
|
|
}
|
|
// return
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Decode a manifest query.
|
|
* @external XMLHttpRequest
|
|
* @param {Object} query The manifest query: {input, nslices},
|
|
* with input the input URI and nslices the number of slices.
|
|
* @param {Function} The function to call with the decoded urls.
|
|
*/
|
|
dwv.utils.decodeManifestQuery = function (query, callback)
|
|
{
|
|
var uri = "";
|
|
if ( query.input[0] === '/' ) {
|
|
uri = window.location.protocol + "//" + window.location.host;
|
|
}
|
|
// TODO: needs to be decoded (decodeURIComponent?
|
|
uri += query.input;
|
|
|
|
// handle error
|
|
var onError = function (/*event*/)
|
|
{
|
|
console.warn( "RequestError while receiving manifest: "+this.status );
|
|
};
|
|
|
|
// handle load
|
|
var onLoad = function (/*event*/)
|
|
{
|
|
callback( dwv.utils.decodeManifest(this.responseXML, query.nslices) );
|
|
};
|
|
|
|
var request = new XMLHttpRequest();
|
|
request.open('GET', decodeURIComponent(uri), true);
|
|
request.responseType = "document";
|
|
request.onload = onLoad;
|
|
request.onerror = onError;
|
|
request.send(null);
|
|
};
|
|
|
|
/**
|
|
* Decode an XML manifest.
|
|
* @param {Object} manifest The manifest to decode.
|
|
* @param {Number} nslices The number of slices to load.
|
|
*/
|
|
dwv.utils.decodeManifest = function (manifest, nslices)
|
|
{
|
|
var result = [];
|
|
// wado url
|
|
var wadoElement = manifest.getElementsByTagName("wado_query");
|
|
var wadoURL = wadoElement[0].getAttribute("wadoURL");
|
|
var rootURL = wadoURL + "?requestType=WADO&contentType=application/dicom&";
|
|
// patient list
|
|
var patientList = manifest.getElementsByTagName("Patient");
|
|
if ( patientList.length > 1 ) {
|
|
console.warn("More than one patient, loading first one.");
|
|
}
|
|
// study list
|
|
var studyList = patientList[0].getElementsByTagName("Study");
|
|
if ( studyList.length > 1 ) {
|
|
console.warn("More than one study, loading first one.");
|
|
}
|
|
var studyUID = studyList[0].getAttribute("StudyInstanceUID");
|
|
// series list
|
|
var seriesList = studyList[0].getElementsByTagName("Series");
|
|
if ( seriesList.length > 1 ) {
|
|
console.warn("More than one series, loading first one.");
|
|
}
|
|
var seriesUID = seriesList[0].getAttribute("SeriesInstanceUID");
|
|
// instance list
|
|
var instanceList = seriesList[0].getElementsByTagName("Instance");
|
|
// loop on instances and push links
|
|
var max = instanceList.length;
|
|
if ( nslices < max ) {
|
|
max = nslices;
|
|
}
|
|
for ( var i = 0; i < max; ++i ) {
|
|
var sopInstanceUID = instanceList[i].getAttribute("SOPInstanceUID");
|
|
var link = rootURL +
|
|
"&studyUID=" + studyUID +
|
|
"&seriesUID=" + seriesUID +
|
|
"&objectUID=" + sopInstanceUID;
|
|
result.push( link );
|
|
}
|
|
// return
|
|
return result;
|
|
};
|
|
|
|
return dwv;
|
|
}));
|