From eec139fe537035db8fe765ceaab02449f5b34c59 Mon Sep 17 00:00:00 2001 From: ivmartel Date: Thu, 26 Feb 2015 00:17:42 +0100 Subject: [PATCH 1/5] Added Index3D, Point3D, Geometry and RescalaSlopeAndIntercept classes. Use them in Image. Updated test and html files. --- src/application.js | 5 +- src/image/geometry.js | 244 +++++++++++++++++++++++++++ src/image/image.js | 299 ++++++++------------------------- src/image/reader.js | 4 +- src/image/view.js | 11 +- src/math/point.js | 204 ++++++++++++++++++++++ src/math/shapes.js | 82 --------- src/tools/livewire.js | 8 +- tests/image/geometry.test.js | 41 +++++ tests/image/image.test.js | 112 ++++++------ tests/image/view.test.js | 18 +- tests/index.html | 4 + tests/math/point.test.js | 52 ++++++ tests/math/shapes.test.js | 46 ----- tests/pacs/data/index.html | 2 + tests/pacs/data/index2000.html | 4 +- viewers/mobile/index.html | 2 + viewers/simple/index.html | 2 + viewers/static/index.html | 2 + 19 files changed, 707 insertions(+), 435 deletions(-) create mode 100644 src/image/geometry.js create mode 100644 src/math/point.js create mode 100644 tests/image/geometry.test.js create mode 100644 tests/math/point.test.js diff --git a/src/application.js b/src/application.js index 944dd8b164..b4232cda19 100644 --- a/src/application.js +++ b/src/application.js @@ -1140,8 +1140,9 @@ dwv.App = function () image = originalImage; // layout - dataWidth = image.getSize().getNumberOfColumns(); - dataHeight = image.getSize().getNumberOfRows(); + var size = image.getGeometry().getSize(); + dataWidth = size.getNumberOfColumns(); + dataHeight = size.getNumberOfRows(); createLayers(dataWidth, dataHeight); // get the image data from the image layer diff --git a/src/image/geometry.js b/src/image/geometry.js new file mode 100644 index 0000000000..b53d16e47d --- /dev/null +++ b/src/image/geometry.js @@ -0,0 +1,244 @@ +/** + * Image module. + * @module image + */ +var dwv = dwv || {}; +dwv.image = dwv.image || {}; + +/** + * 2D/3D Size class. + * @class Size + * @namespace dwv.image + * @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. + * @method getNumberOfColumns + * @return {Number} The number of columns. + */ + this.getNumberOfColumns = function () { return numberOfColumns; }; + /** + * Get the number of rows. + * @method getNumberOfRows + * @return {Number} The number of rows. + */ + this.getNumberOfRows = function () { return numberOfRows; }; + /** + * Get the number of slices. + * @method getNumberOfSlices + * @return {Number} The number of slices. + */ + this.getNumberOfSlices = function () { return (numberOfSlices || 1.0); }; +}; + +/** + * Get the size of a slice. + * @method getSliceSize + * @return {Number} The size of a slice. + */ +dwv.image.Size.prototype.getSliceSize = function () { + return this.getNumberOfColumns() * this.getNumberOfRows(); +}; + +/** + * Get the total size. + * @method getTotalSize + * @return {Number} The total size. + */ +dwv.image.Size.prototype.getTotalSize = function () { + return this.getSliceSize() * this.getNumberOfSlices(); +}; + +/** + * Check for equality. + * @method equals + * @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. + * @method isInBounds + * @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; +}; + +/** + * 2D/3D Spacing class. + * @class Spacing + * @namespace dwv.image + * @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. + * @method getColumnSpacing + * @return {Number} The column spacing. + */ + this.getColumnSpacing = function () { return columnSpacing; }; + /** + * Get the row spacing. + * @method getRowSpacing + * @return {Number} The row spacing. + */ + this.getRowSpacing = function () { return rowSpacing; }; + /** + * Get the slice spacing. + * @method getSliceSpacing + * @return {Number} The slice spacing. + */ + this.getSliceSpacing = function () { return (sliceSpacing || 1.0); }; +}; + +/** + * Check for equality. + * @method equals + * @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(); +}; + +/** + * 2D/3D Geometry class. + * @class Geometry + * @namespace dwv.image + * @constructor + * @param {Object} origin The object origin. + * @param {Object} size The object size. + * @param {Object} spacing The object spacing. + */ +dwv.image.Geometry = function ( origin, size, spacing ) +{ + // check input origin. + if( typeof(origin) === 'undefined' ) { + origin = new dwv.math.Point3D(0,0,0); + } + var origins = [origin]; + + /** + * Get the object origin. + * @method getOrigin + * @return {Object} The object origin. + */ + this.getOrigin = function () { return origin; }; + this.getOrigins = function () { return origins; }; + /** + * Get the object size. + * @method getSize + * @return {Object} The object size. + */ + this.getSize = function () { return size; }; + /** + * Get the object spacing. + * @method getSpacing + * @return {Object} The object spacing. + */ + this.getSpacing = function () { return spacing; }; + + /** + * Get the slice position of a point in the current slice layout. + */ + this.getSliceIndex = function (point) { + // cannot use this.worldToIndex(point).getK() since + // we cannot guaranty consecutive slices... + + // find the closest index + var closestSliceIndex = 0; + var minDiff = Math.abs( origins[0].getZ() - point.getZ() ); + var diff = 0; + for( var i = 0; i < origins.length; ++i ) + { + diff = Math.abs( origins[i].getZ() - point.getZ() ); + if( diff < minDiff ) + { + minDiff = diff; + closestSliceIndex = i; + } + } + diff = origins[closestSliceIndex].getZ() - point.getZ(); + var sliceIndex = ( diff > 0 ) ? closestSliceIndex : closestSliceIndex + 1; + return sliceIndex; + }; + + this.appendOrigin = function (origin, index) { + origins.splice(index, 0, origin); + }; + +}; + +/** + * Check for equality. + * @method equals + * @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(); +}; + +/** +* +*/ +dwv.image.Geometry.prototype.indexToOffset = function (index) { + var size = this.getSize(); + return index.getI() + + index.getJ() * size.getNumberOfColumns() + + index.getK() * size.getSliceSize(); +}; + +/** + * + */ +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() ); +}; + +/** + * + */ +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() ); +}; diff --git a/src/image/image.js b/src/image/image.js index 06cdcb0bc0..75dd6c80b9 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -6,128 +6,20 @@ var dwv = dwv || {}; dwv.image = dwv.image || {}; /** - * Image Size class. - * Supports 2D and 3D images. - * @class Size - * @namespace dwv.image - * @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. - * @method getNumberOfColumns - * @return {Number} The number of columns. - */ - this.getNumberOfColumns = function() { return numberOfColumns; }; - /** - * Get the number of rows. - * @method getNumberOfRows - * @return {Number} The number of rows. - */ - this.getNumberOfRows = function() { return numberOfRows; }; - /** - * Get the number of slices. - * @method getNumberOfSlices - * @return {Number} The number of slices. - */ - this.getNumberOfSlices = function() { return (numberOfSlices || 1.0); }; -}; - -/** - * Get the size of a slice. - * @method getSliceSize - * @return {Number} The size of a slice. - */ -dwv.image.Size.prototype.getSliceSize = function() { - return this.getNumberOfColumns()*this.getNumberOfRows(); -}; - -/** - * Get the total size. - * @method getTotalSize - * @return {Number} The total size. - */ -dwv.image.Size.prototype.getTotalSize = function() { - return this.getSliceSize()*this.getNumberOfSlices(); -}; - -/** - * Check for equality. - * @method equals - * @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. - * @method isInBounds - * @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; -}; - -/** - * Image Spacing class. - * Supports 2D and 3D images. - * @class Spacing - * @namespace dwv.image - * @constructor - * @param {Number} columnSpacing The column spacing. - * @param {Number} rowSpacing The row spacing. - * @param {Number} sliceSpacing The slice spacing. + * @param slope + * @param intercept */ -dwv.image.Spacing = function( columnSpacing, rowSpacing, sliceSpacing ) +dwv.image.RescaleSlopeAndIntercept = function (slope, intercept) { - /** - * Get the column spacing. - * @method getColumnSpacing - * @return {Number} The column spacing. - */ - this.getColumnSpacing = function() { return columnSpacing; }; - /** - * Get the row spacing. - * @method getRowSpacing - * @return {Number} The row spacing. - */ - this.getRowSpacing = function() { return rowSpacing; }; - /** - * Get the slice spacing. - * @method getSliceSpacing - * @return {Number} The slice spacing. - */ - this.getSliceSpacing = function() { return (sliceSpacing || 1.0); }; -}; - -/** - * Check for equality. - * @method equals - * @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(); + this.getSlope = function () { + return slope; + }; + this.getIntercept = function () { + return intercept; + }; + this.apply = function (value) { + return value * slope + intercept; + }; }; /** @@ -139,27 +31,18 @@ dwv.image.Spacing.prototype.equals = function(rhs) { * @class Image * @namespace dwv.image * @constructor - * @param {Size} size The size of the image. - * @param {Spacing} spacing The spacing of the image. + * @param {Object} geometry The geometry of the image. * @param {Array} buffer The image data. - * @param {Array} slicePositions The slice positions. */ -dwv.image.Image = function(size, spacing, buffer, slicePositions) +dwv.image.Image = function(geometry, buffer) { /** - * Rescale slope. - * @property rescaleSlope + * Rescale slope and intercept. + * @property rsi * @private * @type Number */ - var rescaleSlope = 1; - /** - * Rescale intercept. - * @property rescaleIntercept - * @private - * @type Number - */ - var rescaleIntercept = 0; + var rsi = new dwv.image.RescaleSlopeAndIntercept( 1, 0 ); /** * Photometric interpretation (MONOCHROME, RGB...). * @property photometricInterpretation @@ -180,7 +63,7 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) * @private * @type Number */ - var numberOfComponents = buffer.length / size.getTotalSize(); + var numberOfComponents = buffer.length / geometry.getSize().getTotalSize(); /** * Meta information. * @property meta @@ -197,11 +80,6 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) */ var originalBuffer = new Int16Array(buffer); - // check slice positions. - if( typeof(slicePositions) === 'undefined' ) { - slicePositions = [[0,0,0]]; - } - /** * Data range. * @property dataRange @@ -218,54 +96,30 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) var histogram = null; /** - * Get the size of the image. - * @method getSize - * @return {Size} The size of the image. - */ - this.getSize = function() { return size; }; - /** - * Get the spacing of the image. - * @method getSpacing - * @return {Spacing} The spacing of the image. + * Get the geometry of the image. + * @method getGeometry + * @return {Object} The size of the image. */ - this.getSpacing = function() { return spacing; }; + this.getGeometry = function() { return geometry; }; /** * Get the data buffer of the image. TODO dangerous... * @method getBuffer * @return {Array} The data buffer of the image. */ this.getBuffer = function() { return buffer; }; - /** - * Get the slice positions. - * @method getSlicePositions - * @return {Array} The slice positions. - */ - this.getSlicePositions = function() { return slicePositions; }; /** - * Get the rescale slope. - * @method getRescaleSlope - * @return {Number} The rescale slope. - */ - this.getRescaleSlope = function() { return rescaleSlope; }; - /** - * Set the rescale slope. - * @method setRescaleSlope - * @param {Number} rs The rescale slope. + * Get the rescale slope and intercept. + * @method getRescaleSlopeAndIntercept + * @return {Object} The rescale slope and intercept. */ - this.setRescaleSlope = function(rs) { rescaleSlope = rs; }; + this.getRescaleSlopeAndIntercept = function() { return rsi; }; /** - * Get the rescale intercept. - * @method getRescaleIntercept - * @return {Number} The rescale intercept. + * Set the rescale slope and intercept. + * @method setRescaleSlopeAndIntercept + * @param {Object} rsi The rescale slope and intercept. */ - this.getRescaleIntercept = function() { return rescaleIntercept; }; - /** - * Set the rescale intercept. - * @method setRescaleIntercept - * @param {Number} ri The rescale intercept. - */ - this.setRescaleIntercept = function(ri) { rescaleIntercept = ri; }; + this.setRescaleSlopeAndIntercept = function(inRsi) { rsi = inRsi; }; /** * Get the photometricInterpretation of the image. * @method getPhotometricInterpretation @@ -327,9 +181,8 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) */ this.clone = function() { - var copy = new dwv.image.Image(this.getSize(), this.getSpacing(), originalBuffer, slicePositions); - copy.setRescaleSlope(this.getRescaleSlope()); - copy.setRescaleIntercept(this.getRescaleIntercept()); + var copy = new dwv.image.Image(this.getGeometry(), originalBuffer); + copy.setRescaleSlopeAndIntercept(this.getRescaleSlopeAndIntercept()); copy.setPhotometricInterpretation(this.getPhotometricInterpretation()); copy.setPlanarConfiguration(this.getPlanarConfiguration()); copy.setMeta(this.getMeta()); @@ -347,13 +200,15 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) if( rhs === null ) { throw new Error("Cannot append null slice"); } - if( rhs.getSize().getNumberOfSlices() !== 1 ) { + 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() !== rhs.getSize().getNumberOfColumns() ) { + if( size.getNumberOfColumns() !== rhsSize.getNumberOfColumns() ) { throw new Error("Cannot append a slice with different number of columns"); } - if( size.getNumberOfRows() !== rhs.getSize().getNumberOfRows() ) { + if( size.getNumberOfRows() !== rhsSize.getNumberOfRows() ) { throw new Error("Cannot append a slice with different number of rows"); } if( photometricInterpretation !== rhs.getPhotometricInterpretation() ) { @@ -366,23 +221,6 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) } } - // find index where to append slice - var closestSliceIndex = 0; - var slicePosition = rhs.getSlicePositions()[0]; - var minDiff = Math.abs( slicePositions[0][2] - slicePosition[2] ); - var diff = 0; - for( var i = 0; i < slicePositions.length; ++i ) - { - diff = Math.abs( slicePositions[i][2] - slicePosition[2] ); - if( diff < minDiff ) - { - minDiff = diff; - closestSliceIndex = i; - } - } - diff = slicePositions[closestSliceIndex][2] - slicePosition[2]; - var newSliceNb = ( diff > 0 ) ? closestSliceIndex : closestSliceIndex + 1; - // new size var newSize = new dwv.image.Size(size.getNumberOfColumns(), size.getNumberOfRows(), @@ -399,6 +237,7 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) var newBuffer = new Int16Array(sliceSize * newSize.getNumberOfSlices()); // append slice at new position + var newSliceNb = geometry.getSliceIndex( rhs.getGeometry().getOrigin() ); if( newSliceNb === 0 ) { newBuffer.set(rhs.getBuffer()); @@ -418,7 +257,7 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) } // update slice positions - slicePositions.splice(newSliceNb, 0, slicePosition); + geometry.appendOrigin( rhs.getGeometry().getOrigin(), newSliceNb ); // copy to class variables size = newSize; @@ -462,9 +301,8 @@ dwv.image.Image = function(size, spacing, buffer, slicePositions) */ dwv.image.Image.prototype.getValue = function( i, j, k ) { - return this.getValueAtOffset( i + - ( j * this.getSize().getNumberOfColumns() ) + - ( k * this.getSize().getSliceSize()) ); + var index = new dwv.math.Index3D(i,j,k); + return this.getValueAtOffset( this.getGeometry().indexToOffset(index) ); }; /** @@ -476,7 +314,7 @@ dwv.image.Image.prototype.getValue = function( i, j, k ) */ dwv.image.Image.prototype.getRescaledValueAtOffset = function( offset ) { - return (this.getValueAtOffset(offset)*this.getRescaleSlope())+this.getRescaleIntercept(); + return this.getRescaleSlopeAndIntercept().apply( this.getValueAtOffset(offset) ); }; /** @@ -490,7 +328,7 @@ dwv.image.Image.prototype.getRescaledValueAtOffset = function( offset ) */ dwv.image.Image.prototype.getRescaledValue = function( i, j, k ) { - return (this.getValue(i,j,k)*this.getRescaleSlope())+this.getRescaleIntercept(); + return this.getRescaleSlopeAndIntercept().apply( this.getValue(i,j,k) ); }; /** @@ -503,7 +341,7 @@ dwv.image.Image.prototype.calculateDataRange = function() var min = this.getValueAtOffset(0); var max = min; var value = 0; - for(var i=0; i < this.getSize().getTotalSize(); ++i) + for(var i=0; i < this.getGeometry().getSize().getTotalSize(); ++i) { value = this.getValueAtOffset(i); if( value > max ) { max = value; } @@ -520,8 +358,8 @@ dwv.image.Image.prototype.calculateDataRange = function() dwv.image.Image.prototype.getRescaledDataRange = function() { var rawRange = this.getDataRange(); - return { "min": rawRange.min*this.getRescaleSlope()+this.getRescaleIntercept(), - "max": rawRange.max*this.getRescaleSlope()+this.getRescaleIntercept()}; + return { "min": this.getRescaleSlopeAndIntercept().apply(rawRange.min), + "max": this.getRescaleSlopeAndIntercept().apply(rawRange.max)}; }; /** @@ -534,8 +372,8 @@ dwv.image.Image.prototype.calculateHistogram = function() var histo = []; var histoPlot = []; var value = 0; - var size = this.getSize().getTotalSize(); - for ( var i = 0; i < size; ++i ) { + var totalSize = this.getGeometry().getSize().getTotalSize(); + for ( var i = 0; i < totalSize; ++i ) { value = this.getRescaledValueAtOffset(i); histo[value] = ( histo[value] || 0 ) + 1; } @@ -564,9 +402,10 @@ dwv.image.Image.prototype.convolute2D = function(weights) var newImage = this.clone(); var newBuffer = newImage.getBuffer(); - var ncols = this.getSize().getNumberOfColumns(); - var nrows = this.getSize().getNumberOfRows(); - var nslices = this.getSize().getNumberOfSlices(); + var imgSize = this.getGeometry().getSize(); + var ncols = imgSize.getNumberOfColumns(); + var nrows = imgSize.getNumberOfRows(); + var nslices = imgSize.getNumberOfSlices(); var ncomp = this.getNumberOfComponents(); // adapt to number of component and planar configuration @@ -580,7 +419,7 @@ dwv.image.Image.prototype.convolute2D = function(weights) } else { - componentOffset = this.getSize().getTotalSize(); + componentOffset = imgSize.getTotalSize(); } } @@ -744,8 +583,9 @@ dwv.image.Image.prototype.compose = function(rhs, operator) */ dwv.image.Image.prototype.quantifyLine = function(line) { - var length = line.getWorldLength( this.getSpacing().getColumnSpacing(), - this.getSpacing().getRowSpacing()); + var spacing = this.getGeometry().getSpacing(); + var length = line.getWorldLength( spacing.getColumnSpacing(), + spacing.getRowSpacing() ); return {"length": length}; }; @@ -757,8 +597,9 @@ dwv.image.Image.prototype.quantifyLine = function(line) */ dwv.image.Image.prototype.quantifyRect = function(rect) { - var surface = rect.getWorldSurface( this.getSpacing().getColumnSpacing(), - this.getSpacing().getRowSpacing()); + var spacing = this.getGeometry().getSpacing(); + var surface = rect.getWorldSurface( spacing.getColumnSpacing(), + spacing.getRowSpacing()); var subBuffer = []; var minJ = parseInt(rect.getBegin().getY(), 10); var maxJ = parseInt(rect.getEnd().getY(), 10); @@ -782,8 +623,9 @@ dwv.image.Image.prototype.quantifyRect = function(rect) */ dwv.image.Image.prototype.quantifyEllipse = function(ellipse) { - var surface = ellipse.getWorldSurface( this.getSpacing().getColumnSpacing(), - this.getSpacing().getRowSpacing()); + var spacing = this.getGeometry().getSpacing(); + var surface = ellipse.getWorldSurface( spacing.getColumnSpacing(), + spacing.getRowSpacing()); return {"surface": surface}; }; @@ -872,8 +714,12 @@ dwv.image.ImageFactory.prototype.create = function (dicomElements, pixelBuffer) parseFloat(dicomElements.ImagePositionPatient.value[2]) ]; } + // geometry + var origin = new dwv.math.Point3D(slicePosition[0], slicePosition[1], slicePosition[2]); + var geometry = new dwv.image.Geometry( origin, size, spacing ); + // image - var image = new dwv.image.Image( size, spacing, buffer, [slicePosition] ); + var image = new dwv.image.Image( geometry, buffer ); // photometricInterpretation if ( dicomElements.PhotometricInterpretation ) { var photo = dwv.utils.cleanString( @@ -891,14 +737,17 @@ dwv.image.ImageFactory.prototype.create = function (dicomElements, pixelBuffer) } image.setPlanarConfiguration( planar ); } - // rescale slope + // rescale slope and intercept + var slope = 1; if ( dicomElements.RescaleSlope ) { - image.setRescaleSlope( parseFloat(dicomElements.RescaleSlope.value[0]) ); + slope = parseFloat(dicomElements.RescaleSlope.value[0]); } - // rescale intercept + var intercept = 0; if ( dicomElements.RescaleIntercept ) { - image.setRescaleIntercept( parseFloat(dicomElements.RescaleIntercept.value[0]) ); + intercept = parseFloat(dicomElements.RescaleIntercept.value[0]); } + var rsi = new dwv.image.RescaleSlopeAndIntercept(slope, intercept); + image.setRescaleSlopeAndIntercept( rsi ); // meta information var meta = {}; if ( dicomElements.Modality ) { diff --git a/src/image/reader.js b/src/image/reader.js index ab5a25bb19..3f071217db 100644 --- a/src/image/reader.js +++ b/src/image/reader.js @@ -43,7 +43,9 @@ dwv.image.getDataFromImage = function(image) // TODO: wrong info... var imageSpacing = new dwv.image.Spacing(1,1); var sliceIndex = image.index ? image.index : 0; - var dwvImage = new dwv.image.Image(imageSize, imageSpacing, buffer, [[0,0,sliceIndex]]); + var origin = new dwv.math.Point3D(0,0,sliceIndex); + var geometry = new dwv.image.Geometry(origin, imageSize, imageSpacing ); + var dwvImage = new dwv.image.Image( geometry, buffer ); dwvImage.setPhotometricInterpretation("RGB"); // meta information var meta = {}; diff --git a/src/image/view.js b/src/image/view.js index 6f23bb03d2..b85fe1e850 100644 --- a/src/image/view.js +++ b/src/image/view.js @@ -24,7 +24,8 @@ dwv.image.View = function(image, isSigned) * @type Rescale */ var rescaleLut = new dwv.image.lut.Rescale( - image.getRescaleSlope(), image.getRescaleIntercept() ); + image.getRescaleSlopeAndIntercept().getSlope(), + image.getRescaleSlopeAndIntercept().getIntercept() ); // initialise it rescaleLut.initialise(image.getMeta().BitsStored); @@ -154,7 +155,7 @@ dwv.image.View = function(image, isSigned) * @param {Object} pos The current position. */ this.setCurrentPosition = function(pos) { - if( !image.getSize().isInBounds(pos.i,pos.j,pos.k) ) { + if( !image.getGeometry().getSize().isInBounds(pos.i,pos.j,pos.k) ) { return false; } var oldPosition = currentPosition; @@ -302,13 +303,12 @@ dwv.image.View.prototype.generateImageData = function( array ) var windowLut = this.getWindowLut(); var colorMap = this.getColorMap(); var index = 0; - var sliceSize = 0; + var sliceSize = image.getGeometry().getSize().getSliceSize(); var sliceOffset = 0; switch (photoInterpretation) { case "MONOCHROME1": case "MONOCHROME2": - sliceSize = image.getSize().getSliceSize(); sliceOffset = (sliceNumber || 0) * sliceSize; var iMax = sliceOffset + sliceSize; for(var i=sliceOffset; i < iMax; ++i) @@ -328,7 +328,6 @@ dwv.image.View.prototype.generateImageData = function( array ) if( planarConfig !== 0 && planarConfig !== 1 ) { throw new Error("Unsupported planar configuration: "+planarConfig); } - sliceSize = image.getSize().getSliceSize(); sliceOffset = (sliceNumber || 0) * 3 * sliceSize; // default: RGBRGBRGBRGB... var posR = sliceOffset; @@ -346,7 +345,7 @@ dwv.image.View.prototype.generateImageData = function( array ) var redValue = 0; var greenValue = 0; var blueValue = 0; - for(var j=0; j < image.getSize().getSliceSize(); ++j) + for(var j=0; j < sliceSize; ++j) { redValue = parseInt( windowLut.getValue( image.getValueAtOffset(posR) ), 10 ); diff --git a/src/math/point.js b/src/math/point.js new file mode 100644 index 0000000000..62888e639b --- /dev/null +++ b/src/math/point.js @@ -0,0 +1,204 @@ +/** + * Math module. + * @module math + */ +var dwv = dwv || {}; +/** + * Namespace for math functions. + * @class math + * @namespace dwv + * @static + */ +dwv.math = dwv.math || {}; + +/** + * Immutable 2D point. + * @class Point2D + * @namespace dwv.math + * @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. + * @method getX + * @return {Number} The X position of the point. + */ + this.getX = function () { return x; }; + /** + * Get the Y position of the point. + * @method getY + * @return {Number} The Y position of the point. + */ + this.getY = function () { return y; }; +}; // Point2D class + +/** + * Check for Point2D equality. + * @method equals + * @param {Point2D} rhs The other Point2D 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. + * @method toString + * @return {String} The Point2D as a string. + */ +dwv.math.Point2D.prototype.toString = function () { + return "(" + this.getX() + ", " + this.getY() + ")"; +}; + +/** + * Mutable 2D point. + * @class FastPoint2D + * @namespace dwv.math + * @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. + * @method equals + * @param {FastPoint2D} other The other FastPoint2D 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. + * @method toString + * @return {String} The Point2D as a string. + */ +dwv.math.FastPoint2D.prototype.toString = function () { + return "(" + this.x + ", " + this.y + ")"; +}; + +/** + * Immutable 3D point. + * @class Point3D + * @namespace dwv.math + * @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. + * @method getX + * @return {Number} The X position of the point. + */ + this.getX = function () { return x; }; + /** + * Get the Y position of the point. + * @method getY + * @return {Number} The Y position of the point. + */ + this.getY = function () { return y; }; + /** + * Get the Z position of the point. + * @method getZ + * @return {Number} The Z position of the point. + */ + this.getZ = function () { return z; }; +}; // Point3D class + +/** + * Check for Point3D equality. + * @method equals + * @param {Point3D} rhs The other Point3D 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. + * @method toString + * @return {String} The Point3D as a string. + */ +dwv.math.Point3D.prototype.toString = function () { + return "(" + this.getX() + + ", " + this.getY() + + ", " + this.getZ() + ")"; +}; + +/** + * Immutable 3D index. + * @class Index3D + * @namespace dwv.math + * @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. + * @method getI + * @return {Number} The column index. + */ + this.getI = function () { return i; }; + /** + * Get the row index. + * @method getJ + * @return {Number} The row index. + */ + this.getJ = function () { return j; }; + /** + * Get the slice index. + * @method getK + * @return {Number} The slice index. + */ + this.getK = function () { return k; }; +}; // Index3D class + +/** + * Check for Index3D equality. + * @method equals + * @param {Index3D} rhs The other Index3D to compare to. + * @return {Boolean} True if both points 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. + * @method toString + * @return {String} The Index3D as a string. + */ +dwv.math.Index3D.prototype.toString = function () { + return "(" + this.getI() + + ", " + this.getJ() + + ", " + this.getK() + ")"; +}; + + diff --git a/src/math/shapes.js b/src/math/shapes.js index afe9bd7d32..91b307c233 100644 --- a/src/math/shapes.js +++ b/src/math/shapes.js @@ -11,88 +11,6 @@ var dwv = dwv || {}; */ dwv.math = dwv.math || {}; -/** - * 2D point. Immutable. - * @class Point2D - * @namespace dwv.math - * @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. - * @method getX - * @return {Number} The X position of the point. - */ - this.getX = function() { return x; }; - /** - * Get the Y position of the point. - * @method getY - * @return {Number} The Y position of the point. - */ - this.getY = function() { return y; }; -}; // Point2D class - -/** - * Check for Point2D equality. - * @method equals - * @param {Point2D} other The other Point2D to compare to. - * @return {Boolean} True if both points are equal. - */ -dwv.math.Point2D.prototype.equals = function(other) { - if( !other ) { - return false; - } - return ( this.getX() === other.getX() && this.getY() === other.getY() ); -}; - -/** - * Get a string representation of the Point2D. - * @method toString - * @return {String} The Point2D as a string. - */ -dwv.math.Point2D.prototype.toString = function() { - return "(" + this.getX() + ", " + this.getY() + ")"; -}; - -/** - * Fast 2D point since it's mutable! - * @class FastPoint2D - * @namespace dwv.math - * @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. - * @method equals - * @param {FastPoint2D} other The other FastPoint2D to compare to. - * @return {Boolean} True if both points are equal. - */ -dwv.math.FastPoint2D.prototype.equals = function(other) { - if( !other ) { - return false; - } - return ( this.x === other.x && this.y === other.y ); -}; - -/** - * Get a string representation of the FastPoint2D. - * @method toString - * @return {String} The Point2D as a string. - */ -dwv.math.FastPoint2D.prototype.toString = function() { - return "(" + this.x + ", " + this.y + ")"; -}; - /** * Circle shape. * @class Circle diff --git a/src/tools/livewire.js b/src/tools/livewire.js index 9b596ce75b..ed2e392734 100644 --- a/src/tools/livewire.js +++ b/src/tools/livewire.js @@ -91,7 +91,8 @@ dwv.tool.Livewire = function(app) * @private */ function clearParentPoints() { - for( var i = 0; i < app.getImage().getSize().getNumberOfRows(); ++i ) { + var nrows = app.getImage().getGeometry().getSize().getNumberOfRows(); + for( var i = 0; i < nrows; ++i ) { parentPoints[i] = []; } } @@ -320,9 +321,10 @@ dwv.tool.Livewire = function(app) } //scissors = new dwv.math.Scissors(); + var size = app.getImage().getGeometry().getSize(); scissors.setDimensions( - app.getImage().getSize().getNumberOfColumns(), - app.getImage().getSize().getNumberOfRows() ); + size.getNumberOfColumns(), + size.getNumberOfRows() ); scissors.setData(app.getImageData().data); }; diff --git a/tests/image/geometry.test.js b/tests/image/geometry.test.js new file mode 100644 index 0000000000..cc26de61fe --- /dev/null +++ b/tests/image/geometry.test.js @@ -0,0 +1,41 @@ +/** + * Tests for the 'image/geometry.js' file. + */ +// Do not warn if these variables were not defined before. +/* global module, test, equal */ +module("geometry"); + +test("Test Size.", function() { + var size0 = new dwv.image.Size(2, 3, 4); + // test its values + equal( size0.getNumberOfColumns(), 2, "getNumberOfColumns" ); + equal( size0.getNumberOfRows(), 3, "getNumberOfRows" ); + equal( size0.getNumberOfSlices(), 4, "getNumberOfSlices" ); + equal( size0.getSliceSize(), 6, "getSliceSize" ); + equal( size0.getTotalSize(), 24, "getTotalSize" ); + // equality + equal( size0.equals(size0), 1, "equals self true" ); + var size1 = new dwv.image.Size(2, 3, 4); + equal( size0.equals(size1), 1, "equals true" ); + var size2 = new dwv.image.Size(3, 3, 4); + equal( size0.equals(size2), 0, "equals false" ); + // is in bounds + equal( size0.isInBounds(0,0,0), 1, "isInBounds 0" ); + equal( size0.isInBounds(1,2,3), 1, "isInBounds max" ); + equal( size0.isInBounds(2,3,4), 0, "isInBounds too big" ); + equal( size0.isInBounds(-1,2,3), 0, "isInBounds too small" ); +}); + +test("Test Spacing.", function() { + var spacing0 = new dwv.image.Spacing(2, 3, 4); + // test its values + equal( spacing0.getColumnSpacing(), 2, "getColumnSpacing" ); + equal( spacing0.getRowSpacing(), 3, "getRowSpacing" ); + equal( spacing0.getSliceSpacing(), 4, "getSliceSpacing" ); + // equality + equal( spacing0.equals(spacing0), 1, "equals self true" ); + var spacing1 = new dwv.image.Spacing(2, 3, 4); + equal( spacing0.equals(spacing1), 1, "equals true" ); + var spacing2 = new dwv.image.Spacing(3, 3, 4); + equal( spacing0.equals(spacing2), 0, "equals false" ); +}); diff --git a/tests/image/image.test.js b/tests/image/image.test.js index 9cacf378cf..4dc4cc3207 100644 --- a/tests/image/image.test.js +++ b/tests/image/image.test.js @@ -5,51 +5,18 @@ /* global module, test, equal, deepEqual */ module("image"); -test("Test Size.", function() { - var size0 = new dwv.image.Size(2, 3, 4); - // test its values - equal( size0.getNumberOfColumns(), 2, "getNumberOfColumns" ); - equal( size0.getNumberOfRows(), 3, "getNumberOfRows" ); - equal( size0.getNumberOfSlices(), 4, "getNumberOfSlices" ); - equal( size0.getSliceSize(), 6, "getSliceSize" ); - equal( size0.getTotalSize(), 24, "getTotalSize" ); - // equality - equal( size0.equals(size0), 1, "equals self true" ); - var size1 = new dwv.image.Size(2, 3, 4); - equal( size0.equals(size1), 1, "equals true" ); - var size2 = new dwv.image.Size(3, 3, 4); - equal( size0.equals(size2), 0, "equals false" ); - // is in bounds - equal( size0.isInBounds(0,0,0), 1, "isInBounds 0" ); - equal( size0.isInBounds(1,2,3), 1, "isInBounds max" ); - equal( size0.isInBounds(2,3,4), 0, "isInBounds too big" ); - equal( size0.isInBounds(-1,2,3), 0, "isInBounds too small" ); -}); - -test("Test Spacing.", function() { - var spacing0 = new dwv.image.Spacing(2, 3, 4); - // test its values - equal( spacing0.getColumnSpacing(), 2, "getColumnSpacing" ); - equal( spacing0.getRowSpacing(), 3, "getRowSpacing" ); - equal( spacing0.getSliceSpacing(), 4, "getSliceSpacing" ); - // equality - equal( spacing0.equals(spacing0), 1, "equals self true" ); - var spacing1 = new dwv.image.Spacing(2, 3, 4); - equal( spacing0.equals(spacing1), 1, "equals true" ); - var spacing2 = new dwv.image.Spacing(3, 3, 4); - equal( spacing0.equals(spacing2), 0, "equals false" ); -}); - test("Test Image getValue.", function() { // create a simple image var size0 = 4; var imgSize0 = new dwv.image.Size(size0, size0, 1); var imgSpacing0 = new dwv.image.Spacing(1, 1, 1); + var imgOrigin0 = new dwv.math.Point3D(0,0,0); + var imgGeometry0 = new dwv.image.Geometry(imgOrigin0, imgSize0, imgSpacing0); var buffer0 = []; for(var i=0; i + + + + diff --git a/tests/math/point.test.js b/tests/math/point.test.js new file mode 100644 index 0000000000..272bdacdb7 --- /dev/null +++ b/tests/math/point.test.js @@ -0,0 +1,52 @@ +/** + * Tests for the 'math/point.js' file. + */ +// Do not warn if these variables were not defined before. +/* global module, test, equal */ +module("point"); + +test("Test Point2D.", function() { + var p0 = new dwv.math.Point2D(1,2); + // getX + equal(p0.getX(), 1, "getX"); + // getY + equal(p0.getY(), 2, "getY"); + // can't modify internal x + p0.x = 3; + equal(p0.getX(), 1, "getX after .x"); + // can't modify internal y + p0.y = 3; + equal(p0.getY(), 2, "getY after .y"); + // equals: true + var p1 = new dwv.math.Point2D(1,2); + equal(p0.equals(p1), true, "equals true"); + // equals: false + equal(p0.equals(null), false, "null equals false"); + var p2 = new dwv.math.Point2D(2,1); + equal(p0.equals(p2), false, "equals false"); + // to string + equal(p0.toString(), "(1, 2)", "toString"); +}); + +test("Test FastPoint2D.", function() { + var p0 = new dwv.math.FastPoint2D(1,2); + // x + equal(p0.x, 1, "x"); + // y + equal(p0.y, 2, "y"); + // can modify x + p0.x = 3; + equal(p0.x, 3, "modified x"); + // can modify y + p0.y = 4; + equal(p0.y, 4, "modified y"); + // equals: true + var p1 = new dwv.math.FastPoint2D(3,4); + equal(p0.equals(p1), true, "equals true"); + // equals: false + equal(p0.equals(null), false, "null equals false"); + var p2 = new dwv.math.FastPoint2D(4,3); + equal(p0.equals(p2), false, "equals false"); + // to string + equal(p0.toString(), "(3, 4)", "toString"); +}); diff --git a/tests/math/shapes.test.js b/tests/math/shapes.test.js index f5d9da940d..65c9aa71ae 100644 --- a/tests/math/shapes.test.js +++ b/tests/math/shapes.test.js @@ -5,52 +5,6 @@ /* global module, test, equal */ module("shapes"); -test("Test Point2D.", function() { - var p0 = new dwv.math.Point2D(1,2); - // getX - equal(p0.getX(), 1, "getX"); - // getY - equal(p0.getY(), 2, "getY"); - // can't modify internal x - p0.x = 3; - equal(p0.getX(), 1, "getX after .x"); - // can't modify internal y - p0.y = 3; - equal(p0.getY(), 2, "getY after .y"); - // equals: true - var p1 = new dwv.math.Point2D(1,2); - equal(p0.equals(p1), true, "equals true"); - // equals: false - equal(p0.equals(null), false, "null equals false"); - var p2 = new dwv.math.Point2D(2,1); - equal(p0.equals(p2), false, "equals false"); - // to string - equal(p0.toString(), "(1, 2)", "toString"); -}); - -test("Test FastPoint2D.", function() { - var p0 = new dwv.math.FastPoint2D(1,2); - // x - equal(p0.x, 1, "x"); - // y - equal(p0.y, 2, "y"); - // can modify x - p0.x = 3; - equal(p0.x, 3, "modified x"); - // can modify y - p0.y = 4; - equal(p0.y, 4, "modified y"); - // equals: true - var p1 = new dwv.math.FastPoint2D(3,4); - equal(p0.equals(p1), true, "equals true"); - // equals: false - equal(p0.equals(null), false, "null equals false"); - var p2 = new dwv.math.FastPoint2D(4,3); - equal(p0.equals(p2), false, "equals false"); - // to string - equal(p0.toString(), "(3, 4)", "toString"); -}); - test("Test Circle.", function() { var center = new dwv.math.Point2D(0,0); var c0 = new dwv.math.Circle(center,2); diff --git a/tests/pacs/data/index.html b/tests/pacs/data/index.html index 2b0f96e698..2e666c0241 100644 --- a/tests/pacs/data/index.html +++ b/tests/pacs/data/index.html @@ -49,6 +49,7 @@ + @@ -56,6 +57,7 @@ + diff --git a/tests/pacs/data/index2000.html b/tests/pacs/data/index2000.html index 18f1ae7de7..3dfde2b18a 100644 --- a/tests/pacs/data/index2000.html +++ b/tests/pacs/data/index2000.html @@ -2,7 +2,7 @@ -DWV DICOM Check +DWV DICOM jpg2000 Check @@ -49,6 +49,7 @@ + @@ -57,6 +58,7 @@ + diff --git a/viewers/mobile/index.html b/viewers/mobile/index.html index f7bb0afc9a..64b41689a6 100644 --- a/viewers/mobile/index.html +++ b/viewers/mobile/index.html @@ -48,6 +48,7 @@ + @@ -55,6 +56,7 @@ + diff --git a/viewers/simple/index.html b/viewers/simple/index.html index 606e6e2dd8..4775a9b135 100644 --- a/viewers/simple/index.html +++ b/viewers/simple/index.html @@ -39,6 +39,7 @@ + @@ -46,6 +47,7 @@ + diff --git a/viewers/static/index.html b/viewers/static/index.html index e7533d2b55..b309ca3f2a 100644 --- a/viewers/static/index.html +++ b/viewers/static/index.html @@ -42,6 +42,7 @@ + @@ -49,6 +50,7 @@ + From e69fc12ccf56e4e7a8a730281ea671d08128ec6c Mon Sep 17 00:00:00 2001 From: ivmartel Date: Thu, 26 Feb 2015 23:13:52 +0100 Subject: [PATCH 2/5] Fix slice append. Fix image tests. Append views in app and not just slices. --- src/application.js | 4 +- src/image/geometry.js | 41 +++++++++++---- src/image/image.js | 47 +++++++++++++---- src/image/luts.js | 105 ++++++++++++++++---------------------- src/image/view.js | 42 ++++++++++++--- tests/image/image.test.js | 7 +-- tests/index.html | 17 ++++++ 7 files changed, 170 insertions(+), 93 deletions(-) diff --git a/src/application.js b/src/application.js index b4232cda19..3416bdb352 100644 --- a/src/application.js +++ b/src/application.js @@ -390,7 +390,7 @@ dwv.App = function () fileIO.onload = function (data) { var isFirst = true; if ( image ) { - image.appendSlice( data.view.getImage() ); + view.append( data.view ); isFirst = false; } postLoadInit(data); @@ -426,7 +426,7 @@ dwv.App = function () urlIO.onload = function (data) { var isFirst = true; if ( image ) { - image.appendSlice( data.view.getImage() ); + view.append( data.view ); isFirst = false; } postLoadInit(data); diff --git a/src/image/geometry.js b/src/image/geometry.js index b53d16e47d..8dbd1791cb 100644 --- a/src/image/geometry.js +++ b/src/image/geometry.js @@ -146,11 +146,16 @@ dwv.image.Geometry = function ( origin, size, spacing ) var origins = [origin]; /** - * Get the object origin. + * Get the object first origin. * @method getOrigin - * @return {Object} The object origin. + * @return {Object} The object first origin. */ this.getOrigin = function () { return origin; }; + /** + * Get the object origins. + * @method getOrigins + * @return {Array} The object origins. + */ this.getOrigins = function () { return origins; }; /** * Get the object size. @@ -167,8 +172,11 @@ dwv.image.Geometry = function ( origin, size, spacing ) /** * Get the slice position of a point in the current slice layout. + * @method getSliceIndex + * @param {Object} point The point to evaluate. */ - this.getSliceIndex = function (point) { + this.getSliceIndex = function (point) + { // cannot use this.worldToIndex(point).getK() since // we cannot guaranty consecutive slices... @@ -190,8 +198,20 @@ dwv.image.Geometry = function ( origin, size, spacing ) return sliceIndex; }; - this.appendOrigin = function (origin, index) { + /** + * 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); }; }; @@ -210,17 +230,19 @@ dwv.image.Geometry.prototype.equals = function (rhs) { }; /** -* -*/ + * 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(); + 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(); @@ -232,7 +254,8 @@ dwv.image.Geometry.prototype.indexToWorld = function (index) { }; /** - * + * Convert world coordinates into an index. + * @param {Object} THe point to convert. */ dwv.image.Geometry.prototype.worldToIndex = function (point) { var origin = this.getOrigin(); diff --git a/src/image/image.js b/src/image/image.js index 75dd6c80b9..92bdaaead2 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -6,18 +6,49 @@ var dwv = dwv || {}; dwv.image = dwv.image || {}; /** + * Rescale Slope and Intercept + * @class RescaleSlopeAndIntercept + * @namespace dwv.image + * @constructor * @param slope * @param intercept */ dwv.image.RescaleSlopeAndIntercept = function (slope, intercept) { - this.getSlope = function () { + /*// 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. + * @method getSlope + * @return {Number} The slope of the RSI. + */ + this.getSlope = function () + { return slope; }; - this.getIntercept = function () { + /** + * Get the intercept of the RSI. + * @method getIntercept + * @return {Number} The intercept of the RSI. + */ + this.getIntercept = function () + { return intercept; }; - this.apply = function (value) { + /** + * Apply the RSI on an input value. + * @method apply + * @return {Number} The value to rescale. + */ + this.apply = function (value) + { return value * slope + intercept; }; }; @@ -221,11 +252,6 @@ dwv.image.Image = function(geometry, buffer) } } - // new size - var newSize = new dwv.image.Size(size.getNumberOfColumns(), - size.getNumberOfRows(), - size.getNumberOfSlices() + 1 ); - // calculate slice size var mul = 1; if( photometricInterpretation === "RGB" ) { @@ -234,7 +260,7 @@ dwv.image.Image = function(geometry, buffer) var sliceSize = mul * size.getSliceSize(); // create the new buffer - var newBuffer = new Int16Array(sliceSize * newSize.getNumberOfSlices()); + var newBuffer = new Int16Array(sliceSize * (size.getNumberOfSlices() + 1) ); // append slice at new position var newSliceNb = geometry.getSliceIndex( rhs.getGeometry().getOrigin() ); @@ -256,11 +282,10 @@ dwv.image.Image = function(geometry, buffer) newBuffer.set(buffer.subarray(offset), offset + sliceSize); } - // update slice positions + // update geometry geometry.appendOrigin( rhs.getGeometry().getOrigin(), newSliceNb ); // copy to class variables - size = newSize; buffer = newBuffer; originalBuffer = new Int16Array(newBuffer); }; diff --git a/src/image/luts.js b/src/image/luts.js index e1c7eac7e8..a90fdcb484 100644 --- a/src/image/luts.js +++ b/src/image/luts.js @@ -11,53 +11,36 @@ dwv.image.lut = dwv.image.lut || {}; * @class Rescale * @namespace dwv.image.lut * @constructor - * @param {Number} slope_ The rescale slope. - * @param {Number} intercept_ The rescale intercept. + * @param {Object} rsi The rescale slope and intercept. */ -dwv.image.lut.Rescale = function(slope_,intercept_) +dwv.image.lut.Rescale = function (rsi) { /** * The internal array. - * @property rescaleLut_ + * @property rescaleLut * @private * @type Array */ - var rescaleLut_ = null; + var rescaleLut = null; - // Check the rescale slope. - if(typeof(slope_) === 'undefined') { - slope_ = 1; - } - // Check the rescale intercept. - if(typeof(intercept_) === 'undefined') { - intercept_ = 0; - } - - /** - * Get the rescale slope. - * @method getSlope - * @return {Number} The rescale slope. - */ - this.getSlope = function() { return slope_; }; /** - * Get the rescale intercept. - * @method getIntercept - * @return {Number} The rescale intercept. + * Get the Rescale Slope and Intercept (RSI). + * @method getRSI + * @return {Object} The rescale slope and intercept. */ - this.getIntercept = function() { return intercept_; }; + this.getRSI = function () { return rsi; }; /** * Initialise the LUT. * @method initialise * @param {Number} bitsStored The number of bits used to store the data. */ - // Initialise the LUT. - this.initialise = function(bitsStored) + this.initialise = function (bitsStored) { var size = Math.pow(2, bitsStored); - rescaleLut_ = new Float32Array(size); - for(var i=0; i yMax ) { - windowLut_[j] = yMax; + windowLut[j] = yMax; } else { - windowLut_[j] = dispval; + windowLut[j] = dispval; } } } @@ -185,8 +168,8 @@ dwv.image.lut.Window = function(rescaleLut_, isSigned_) { // from the DICOM specification (https://www.dabsoft.ch/dicom/3/C.11.2.1.2/) // y = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax - ymin )+ ymin - dispval = ((rescaleLut_.getValue(i) - center0 ) / width0 + 0.5) * 255; - windowLut_[i]= parseInt(dispval, 10); + dispval = ((rescaleLut.getValue(i) - center0 ) / width0 + 0.5) * 255; + windowLut[i]= parseInt(dispval, 10); } } }; @@ -196,7 +179,7 @@ dwv.image.lut.Window = function(rescaleLut_, isSigned_) * @method getLength * @return {Number} The length of the LUT array. */ - this.getLength = function() { return windowLut_.length; }; + this.getLength = function() { return windowLut.length; }; /** * Get the value of the LUT at the given offset. @@ -205,8 +188,8 @@ dwv.image.lut.Window = function(rescaleLut_, isSigned_) */ this.getValue = function(offset) { - var shift = isSigned_ ? windowLut_.length / 2 : 0; - return windowLut_[offset+shift]; + var shift = isSigned ? windowLut.length / 2 : 0; + return windowLut[offset+shift]; }; }; diff --git a/src/image/view.js b/src/image/view.js index b85fe1e850..3f3459cff9 100644 --- a/src/image/view.js +++ b/src/image/view.js @@ -23,19 +23,14 @@ dwv.image.View = function(image, isSigned) * @private * @type Rescale */ - var rescaleLut = new dwv.image.lut.Rescale( - image.getRescaleSlopeAndIntercept().getSlope(), - image.getRescaleSlopeAndIntercept().getIntercept() ); - // initialise it - rescaleLut.initialise(image.getMeta().BitsStored); - + var rescaleLut = null; /** * Window lookup table. * @property windowLut * @private * @type Window */ - var windowLut = new dwv.image.lut.Window(rescaleLut, isSigned); + var windowLut = null; /** * Window presets. @@ -59,6 +54,26 @@ dwv.image.View = function(image, isSigned) */ var currentPosition = {"i":0,"j":0,"k":0}; + /** + * Initialise the view. + * @method initialise + */ + function initialise() + { + if ( !rescaleLut ) { + // create the rescale lookup table + rescaleLut = new dwv.image.lut.Rescale( + image.getRescaleSlopeAndIntercept() ); + // initialise the rescale lookup table + rescaleLut.initialise(image.getMeta().BitsStored); + // create the window lookup table + windowLut = new dwv.image.lut.Window(rescaleLut, isSigned); + } + } + + // default contructor + initialise(); + /** * Get the associated image. * @method getImage @@ -179,6 +194,19 @@ dwv.image.View = function(image, isSigned) return true; }; + /** + * Append another view to this one. + * @method append + * @param {Object} rhs The view to append. + */ + this.append = function( rhs ) + { + // append images + this.getImage().appendSlice( rhs.getImage() ); + // init to update self + initialise( rhs.getImage() ); + }; + /** * View listeners * @property listeners diff --git a/tests/image/image.test.js b/tests/image/image.test.js index 4dc4cc3207..b1150bf1f5 100644 --- a/tests/image/image.test.js +++ b/tests/image/image.test.js @@ -61,9 +61,10 @@ test("Test Image getValue.", function() { test("Test Image append slice.", function (assert) { var size = 4; var imgSize = new dwv.image.Size(size, size, 2); + var imgSizeMinusOne = new dwv.image.Size(size, size, 1); var imgSpacing = new dwv.image.Spacing(1, 1, 1); var imgOrigin = new dwv.math.Point3D(0,0,0); - var imgGeometry0 = new dwv.image.Geometry(imgOrigin, imgSize, imgSpacing); + var imgGeometry0 = new dwv.image.Geometry(imgOrigin, imgSizeMinusOne, imgSpacing); imgGeometry0.appendOrigin(new dwv.math.Point3D(0,0,1), 1); // slice to append @@ -109,7 +110,7 @@ test("Test Image append slice.", function (assert) { deepEqual( imgGeometry0.getOrigins(), sliceOrigins0, "Slice positions (append before)" ); // image 1 - var imgGeometry1 = new dwv.image.Geometry(imgOrigin, imgSize, imgSpacing); + var imgGeometry1 = new dwv.image.Geometry(imgOrigin, imgSizeMinusOne, imgSpacing); imgGeometry1.appendOrigin(new dwv.math.Point3D(0,0,1), 1); var image1 = new dwv.image.Image(imgGeometry1, buffer); var sliceOrigin1 = new dwv.math.Point3D(0,0,2); @@ -132,7 +133,7 @@ test("Test Image append slice.", function (assert) { deepEqual( imgGeometry1.getOrigins(), sliceOrigins1, "Slice positions (append after)" ); // image 2 - var imgGeometry2 = new dwv.image.Geometry(imgOrigin, imgSize, imgSpacing); + var imgGeometry2 = new dwv.image.Geometry(imgOrigin, imgSizeMinusOne, imgSpacing); imgGeometry2.appendOrigin(new dwv.math.Point3D(0,0,1), 1); var image2 = new dwv.image.Image(imgGeometry2, buffer); var sliceOrigin2 = new dwv.math.Point3D(0,0,0.4); diff --git a/tests/index.html b/tests/index.html index 4007314056..8a5efe7ee6 100644 --- a/tests/index.html +++ b/tests/index.html @@ -5,6 +5,11 @@ DWV Tests + @@ -37,6 +42,18 @@ + + + From d0f1fd2beb48184590e11a60eb8ea1a159f8ea01 Mon Sep 17 00:00:00 2001 From: ivmartel Date: Sat, 28 Feb 2015 01:51:14 +0100 Subject: [PATCH 3/5] Allow for a rsi per slice in image. --- src/image/image.js | 135 +++++++++++++++++++++++++++++++-------------- src/image/view.js | 4 +- 2 files changed, 96 insertions(+), 43 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index 92bdaaead2..834a58d421 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -73,7 +73,10 @@ dwv.image.Image = function(geometry, buffer) * @private * @type Number */ - var rsi = new dwv.image.RescaleSlopeAndIntercept( 1, 0 ); + var rsis = []; + for ( var s = 0; s < geometry.getSize().getNumberOfSlices(); ++s ) { + rsis.push( new dwv.image.RescaleSlopeAndIntercept( 1, 0 ) ); + } /** * Photometric interpretation (MONOCHROME, RGB...). * @property photometricInterpretation @@ -118,6 +121,13 @@ dwv.image.Image = function(geometry, buffer) * @type Object */ var dataRange = null; + /** + * Rescaled data range. + * @property rescaledDataRange + * @private + * @type Object + */ + var rescaledDataRange = null; /** * Histogram. * @property histogram @@ -144,13 +154,18 @@ dwv.image.Image = function(geometry, buffer) * @method getRescaleSlopeAndIntercept * @return {Object} The rescale slope and intercept. */ - this.getRescaleSlopeAndIntercept = function() { return rsi; }; + this.getRescaleSlopeAndIntercept = function(k) { return rsis[k]; }; /** * Set the rescale slope and intercept. * @method setRescaleSlopeAndIntercept * @param {Object} rsi The rescale slope and intercept. */ - this.setRescaleSlopeAndIntercept = function(inRsi) { rsi = inRsi; }; + this.setRescaleSlopeAndIntercept = function(inRsi, k) { + if ( typeof k === 'undefined' ) { + k = 0; + } + rsis[k] = inRsi; + }; /** * Get the photometricInterpretation of the image. * @method getPhotometricInterpretation @@ -213,7 +228,10 @@ dwv.image.Image = function(geometry, buffer) this.clone = function() { var copy = new dwv.image.Image(this.getGeometry(), originalBuffer); - copy.setRescaleSlopeAndIntercept(this.getRescaleSlopeAndIntercept()); + var nslices = this.getGeometry().getSize().getNumberOfSlices(); + for ( var k = 0; k < nslices; ++k ) { + copy.setRescaleSlopeAndIntercept(this.getRescaleSlopeAndIntercept(k), k); + } copy.setPhotometricInterpretation(this.getPhotometricInterpretation()); copy.setPlanarConfiguration(this.getPlanarConfiguration()); copy.setMeta(this.getMeta()); @@ -284,6 +302,8 @@ dwv.image.Image = function(geometry, buffer) // update geometry geometry.appendOrigin( rhs.getGeometry().getOrigin(), newSliceNb ); + // update rsi + rsis.splice(newSliceNb, 0, rhs.getRescaleSlopeAndIntercept(0)); // copy to class variables buffer = newBuffer; @@ -302,6 +322,18 @@ dwv.image.Image = function(geometry, buffer) return dataRange; }; + /** + * Get the rescaled data range. + * @method getRescaledDataRange + * @return {Object} The rescaled data range. + */ + this.getRescaledDataRange = function() { + if( !rescaledDataRange ) { + rescaledDataRange = this.calculateRescaledDataRange(); + } + return rescaledDataRange; + }; + /** * Get the histogram. * @method getHistogram @@ -309,7 +341,10 @@ dwv.image.Image = function(geometry, buffer) */ this.getHistogram = function() { if( !histogram ) { - histogram = this.calculateHistogram(); + var res = this.calculateHistogram(); + dataRange = res.dataRange; + rescaledDataRange = res.rescaledDataRange; + histogram = res.histogram; } return histogram; }; @@ -330,18 +365,6 @@ dwv.image.Image.prototype.getValue = function( i, j, k ) return this.getValueAtOffset( this.getGeometry().indexToOffset(index) ); }; -/** - * Get the rescaled value of the image at a specific offset. - * @method getRescaledValueAtOffset - * @param {Number} offset The offset in the buffer. - * @return {Number} The rescaled value at the desired offset. - * Warning: No size check... - */ -dwv.image.Image.prototype.getRescaledValueAtOffset = function( offset ) -{ - return this.getRescaleSlopeAndIntercept().apply( this.getValueAtOffset(offset) ); -}; - /** * Get the rescaled value of the image at a specific coordinate. * @method getRescaledValue @@ -353,62 +376,92 @@ dwv.image.Image.prototype.getRescaledValueAtOffset = function( offset ) */ dwv.image.Image.prototype.getRescaledValue = function( i, j, k ) { - return this.getRescaleSlopeAndIntercept().apply( this.getValue(i,j,k) ); + return this.getRescaleSlopeAndIntercept(k).apply( this.getValue(i,j,k) ); }; /** - * Calculate the raw image data range. + * Calculate the data range of the image. * @method calculateDataRange * @return {Object} The range {min, max}. */ -dwv.image.Image.prototype.calculateDataRange = function() +dwv.image.Image.prototype.calculateDataRange = function () { + var size = this.getGeometry().getSize().getTotalSize(); var min = this.getValueAtOffset(0); var max = min; var value = 0; - for(var i=0; i < this.getGeometry().getSize().getTotalSize(); ++i) - { + for ( var i = 0; i < size; ++i ) { value = this.getValueAtOffset(i); if( value > max ) { max = value; } if( value < min ) { min = value; } } + // return return { "min": min, "max": max }; }; /** - * Calculate the image data range after rescale. - * @method getRescaledDataRange - * @return {Object} The rescaled data range {min, max}. + * Calculate the rescaled data range of the image. + * @method calculateRescaledDataRange + * @return {Object} The range {min, max}. */ -dwv.image.Image.prototype.getRescaledDataRange = function() +dwv.image.Image.prototype.calculateRescaledDataRange = function () { - var rawRange = this.getDataRange(); - return { "min": this.getRescaleSlopeAndIntercept().apply(rawRange.min), - "max": this.getRescaleSlopeAndIntercept().apply(rawRange.max)}; + var size = this.getGeometry().getSize(); + var rmin = this.getRescaledValue(0,0,0); + var rmax = rmin; + var rvalue = 0; + for ( var k = 0; k < size.getNumberOfSlices(); ++k ) { + for ( var j = 0; j < size.getNumberOfRows(); ++j ) { + for ( var i = 0; i < size.getNumberOfColumns(); ++i ) { + rvalue = this.getRescaledValue(i,j,k); + if( rvalue > rmax ) { rmax = rvalue; } + if( rvalue < rmin ) { rmin = rvalue; } + } + } + } + // return + return { "min": rmin, "max": rmax }; }; /** * Calculate the histogram of the image. * @method calculateHistogram - * @return {Array} An array representing the histogram. + * @return {Object} The histogram, data range and rescaled data range. */ -dwv.image.Image.prototype.calculateHistogram = function() +dwv.image.Image.prototype.calculateHistogram = function () { + var size = this.getGeometry().getSize(); var histo = []; - var histoPlot = []; + var min = this.getValue(0,0,0); + var max = min; var value = 0; - var totalSize = this.getGeometry().getSize().getTotalSize(); - for ( var i = 0; i < totalSize; ++i ) { - value = this.getRescaledValueAtOffset(i); - histo[value] = ( histo[value] || 0 ) + 1; + var rmin = this.getRescaledValue(0,0,0); + var rmax = rmin; + var rvalue = 0; + for ( var k = 0; k < size.getNumberOfSlices(); ++k ) { + for ( var j = 0; j < size.getNumberOfRows(); ++j ) { + for ( var i = 0; i < size.getNumberOfColumns(); ++i ) { + value = this.getValue(i,j,k); + 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 min = this.getRescaledDataRange().min; - var max = this.getRescaledDataRange().max; - for ( var j = min; j <= max; ++j ) { - histoPlot.push([j, ( histo[j] || 0 ) ]); + var histogram = []; + for ( var b = rmin; b <= rmax; ++b ) { + histogram.push([b, ( histo[b] || 0 ) ]); } - return histoPlot; + // return + return { 'dataRange': dataRange, 'rescaledDataRange': rescaledDataRange, + 'histogram': histogram }; }; /** diff --git a/src/image/view.js b/src/image/view.js index 3f3459cff9..88d0376854 100644 --- a/src/image/view.js +++ b/src/image/view.js @@ -63,7 +63,7 @@ dwv.image.View = function(image, isSigned) if ( !rescaleLut ) { // create the rescale lookup table rescaleLut = new dwv.image.lut.Rescale( - image.getRescaleSlopeAndIntercept() ); + image.getRescaleSlopeAndIntercept(0) ); // initialise the rescale lookup table rescaleLut.initialise(image.getMeta().BitsStored); // create the window lookup table @@ -71,7 +71,7 @@ dwv.image.View = function(image, isSigned) } } - // default contructor + // default constructor initialise(); /** From f37c2bb7a54db720e81c6a28ec970ec019768a88 Mon Sep 17 00:00:00 2001 From: ivmartel Date: Sun, 1 Mar 2015 00:19:19 +0100 Subject: [PATCH 4/5] Removed unnecessary rescale lut member. --- src/image/view.js | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/image/view.js b/src/image/view.js index 88d0376854..213dc803f7 100644 --- a/src/image/view.js +++ b/src/image/view.js @@ -17,13 +17,6 @@ dwv.image = dwv.image || {}; */ dwv.image.View = function(image, isSigned) { - /** - * Rescale lookup table. - * @property rescaleLut - * @private - * @type Rescale - */ - var rescaleLut = null; /** * Window lookup table. * @property windowLut @@ -60,9 +53,9 @@ dwv.image.View = function(image, isSigned) */ function initialise() { - if ( !rescaleLut ) { + if ( !windowLut ) { // create the rescale lookup table - rescaleLut = new dwv.image.lut.Rescale( + var rescaleLut = new dwv.image.lut.Rescale( image.getRescaleSlopeAndIntercept(0) ); // initialise the rescale lookup table rescaleLut.initialise(image.getMeta().BitsStored); @@ -87,19 +80,6 @@ dwv.image.View = function(image, isSigned) */ this.setImage = function(inImage) { image = inImage; }; - /** - * Get the rescale LUT of the image. - * @method getRescaleLut - * @return {Rescale} The rescale LUT of the image. - */ - this.getRescaleLut = function() { return rescaleLut; }; - /** - * Set the rescale LUT of the image. - * @method setRescaleLut - * @param {Rescale} lut The rescale LUT of the image. - */ - this.setRescaleLut = function(lut) { rescaleLut = lut; }; - /** * Get the window LUT of the image. * @method getWindowLut @@ -309,7 +289,6 @@ dwv.image.View.prototype.decrementSliceNb = function() dwv.image.View.prototype.clone = function() { var copy = new dwv.image.View(this.getImage()); - copy.setRescaleLut(this.getRescaleLut()); copy.setWindowLut(this.getWindowLut()); copy.setListeners(this.getListeners()); return copy; From 4191c818e5624ac9a646300c9ac33e660ca257e4 Mon Sep 17 00:00:00 2001 From: ivmartel Date: Mon, 2 Mar 2015 01:05:58 +0100 Subject: [PATCH 5/5] Getting there... --- src/image/image.js | 21 ++++++++ src/image/luts.js | 27 ++++++++++ src/image/view.js | 129 +++++++++++++++++++++++++-------------------- 3 files changed, 121 insertions(+), 56 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index 834a58d421..a8ec3f74fa 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -53,6 +53,27 @@ dwv.image.RescaleSlopeAndIntercept = function (slope, intercept) }; }; +/** + * Check for RSI equality. + * @method equals + * @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. + * @method toString + * @return {String} The RSI as a string. + */ +dwv.image.RescaleSlopeAndIntercept.prototype.toString = function () { + return (this.getSlope() + ", " + this.getIntercept()); +}; + /** * Image class. * Usable once created, optional are: diff --git a/src/image/luts.js b/src/image/luts.js index a90fdcb484..52267817ff 100644 --- a/src/image/luts.js +++ b/src/image/luts.js @@ -101,6 +101,14 @@ dwv.image.lut.Window = function (rescaleLut, isSigned) */ var width = null; + /** + * Flag to know if the lut needs update or not. + * @property needsUpdate + * @private + * @type Boolean + */ + var needsUpdate = false; + /** * Get the window center. * @method getCenter @@ -119,6 +127,12 @@ dwv.image.lut.Window = function (rescaleLut, isSigned) * @return {Boolean} The signed flag. */ this.isSigned = function() { return isSigned; }; + /** + * Get the rescale lut. + * @method getRescaleLut + * @return {Object} The rescale lut. + */ + this.getRescaleLut = function() { return rescaleLut; }; /** * Set the window center and width. @@ -131,6 +145,18 @@ dwv.image.lut.Window = function (rescaleLut, isSigned) // store the window values center = inCenter; width = inWidth; + needsUpdate = true; + }; + + /** + * Update the lut if needed.. + * @method update + */ + this.update = function () + { + if ( !needsUpdate ) { + return; + } // pre calculate loop values var size = windowLut.length; var center0 = center - 0.5; @@ -172,6 +198,7 @@ dwv.image.lut.Window = function (rescaleLut, isSigned) windowLut[i]= parseInt(dispval, 10); } } + needsUpdate = false; }; /** diff --git a/src/image/view.js b/src/image/view.js index 213dc803f7..856f0aebb0 100644 --- a/src/image/view.js +++ b/src/image/view.js @@ -18,12 +18,12 @@ dwv.image = dwv.image || {}; dwv.image.View = function(image, isSigned) { /** - * Window lookup table. - * @property windowLut + * Window lookup tables, indexed per Rescale Slope and Intercept (RSI). + * @property windowLuts * @private * @type Window */ - var windowLut = null; + var windowLuts = {}; /** * Window presets. @@ -47,26 +47,6 @@ dwv.image.View = function(image, isSigned) */ var currentPosition = {"i":0,"j":0,"k":0}; - /** - * Initialise the view. - * @method initialise - */ - function initialise() - { - if ( !windowLut ) { - // create the rescale lookup table - var rescaleLut = new dwv.image.lut.Rescale( - image.getRescaleSlopeAndIntercept(0) ); - // initialise the rescale lookup table - rescaleLut.initialise(image.getMeta().BitsStored); - // create the window lookup table - windowLut = new dwv.image.lut.Window(rescaleLut, isSigned); - } - } - - // default constructor - initialise(); - /** * Get the associated image. * @method getImage @@ -85,14 +65,46 @@ dwv.image.View = function(image, isSigned) * @method getWindowLut * @return {Window} The window LUT of the image. */ - this.getWindowLut = function() { return windowLut; }; + this.getWindowLut = function (rsi) { + if ( typeof rsi === "undefined" ) { + var sliceNumber = this.getCurrentPosition().k; + rsi = image.getRescaleSlopeAndIntercept(sliceNumber); + } + return windowLuts[ rsi.toString() ]; + }; /** * Set the window LUT of the image. * @method setWindowLut - * @param {Window} lut The window LUT of the image. + * @param {Window} wlut The window LUT of the image. */ - this.setWindowLut = function(lut) { windowLut = lut; }; + this.setWindowLut = function (wlut) + { + var rsi = wlut.getRescaleLut().getRSI(); + windowLuts[rsi.toString()] = wlut; + }; + var self = this; + + /** + * Initialise the view. Only called at construction. + * @method initialise + * @private + */ + function initialise() + { + // create the rescale lookup table + var rescaleLut = new dwv.image.lut.Rescale( + image.getRescaleSlopeAndIntercept(0) ); + // initialise the rescale lookup table + rescaleLut.initialise(image.getMeta().BitsStored); + // create the window lookup table + var windowLut = new dwv.image.lut.Window(rescaleLut, isSigned); + self.setWindowLut(windowLut); + } + + // default constructor + initialise(); + /** * Get the window presets. * @method getWindowPresets @@ -184,9 +196,42 @@ dwv.image.View = function(image, isSigned) // append images this.getImage().appendSlice( rhs.getImage() ); // init to update self - initialise( rhs.getImage() ); + this.setWindowLut(rhs.getWindowLut()); }; + /** + * Set the view window/level. + * @method setWindowLevel + * @param {Number} center The window center. + * @param {Number} width The window width. + * Warning: uses the latest set rescale LUT or the default linear one. + */ + this.setWindowLevel = function ( center, width ) + { + // window width shall be >= 1 (see https://www.dabsoft.ch/dicom/3/C.11.2.1.2/) + if ( width >= 1 ) { + for ( var key in windowLuts ) { + windowLuts[key].setCenterAndWidth(center, width); + } + this.fireEvent({"type": "wlchange", "wc": center, "ww": width }); + } + }; + + /** + * Clone the image using all meta data and the original data buffer. + * @method clone + * @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.setWindowLut(windowLuts[key]); + } + copy.setListeners(this.getListeners()); + return copy; + }; + /** * View listeners * @property listeners @@ -208,22 +253,6 @@ dwv.image.View = function(image, isSigned) this.setListeners = function(list) { listeners = list; }; }; -/** - * Set the view window/level. - * @method setWindowLevel - * @param {Number} center The window center. - * @param {Number} width The window width. - * Warning: uses the latest set rescale LUT or the default linear one. - */ -dwv.image.View.prototype.setWindowLevel = function( center, width ) -{ - // window width shall be >= 1 (see https://www.dabsoft.ch/dicom/3/C.11.2.1.2/) - if ( width >= 1 ) { - this.getWindowLut().setCenterAndWidth(center, width); - this.fireEvent({"type": "wlchange", "wc": center, "ww": width }); - } -}; - /** * Set the image window/level to cover the full data range. * @method setWindowLevelMinMax @@ -281,19 +310,6 @@ dwv.image.View.prototype.decrementSliceNb = function() }); }; -/** - * Clone the image using all meta data and the original data buffer. - * @method clone - * @return {View} A full copy of this {dwv.image.Image}. - */ -dwv.image.View.prototype.clone = function() -{ - var copy = new dwv.image.View(this.getImage()); - copy.setWindowLut(this.getWindowLut()); - copy.setListeners(this.getListeners()); - return copy; -}; - /** * Generate display image data to be given to a canvas. * @method generateImageData @@ -308,6 +324,7 @@ dwv.image.View.prototype.generateImageData = function( array ) var photoInterpretation = image.getPhotometricInterpretation(); var planarConfig = image.getPlanarConfiguration(); var windowLut = this.getWindowLut(); + windowLut.update(); var colorMap = this.getColorMap(); var index = 0; var sliceSize = image.getGeometry().getSize().getSliceSize();