|
@@ -13,205 +13,203 @@
|
|
|
* @param {ArrayBuffer} arrayBuffer The buffer with volume data
|
|
|
*/
|
|
|
|
|
|
- function Volume( xLength, yLength, zLength, type, arrayBuffer ) {
|
|
|
+ class Volume {
|
|
|
+
|
|
|
+ constructor( xLength, yLength, zLength, type, arrayBuffer ) {
|
|
|
+
|
|
|
+ if ( xLength !== undefined ) {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @member {number} xLength Width of the volume in the IJK coordinate system
|
|
|
+ */
|
|
|
+ this.xLength = Number( xLength ) || 1;
|
|
|
+ /**
|
|
|
+ * @member {number} yLength Height of the volume in the IJK coordinate system
|
|
|
+ */
|
|
|
+
|
|
|
+ this.yLength = Number( yLength ) || 1;
|
|
|
+ /**
|
|
|
+ * @member {number} zLength Depth of the volume in the IJK coordinate system
|
|
|
+ */
|
|
|
+
|
|
|
+ this.zLength = Number( zLength ) || 1;
|
|
|
+ /**
|
|
|
+ * @member {Array<string>} The order of the Axis dictated by the NRRD header
|
|
|
+ */
|
|
|
+
|
|
|
+ this.axisOrder = [ 'x', 'y', 'z' ];
|
|
|
+ /**
|
|
|
+ * @member {TypedArray} data Data of the volume
|
|
|
+ */
|
|
|
+
|
|
|
+ switch ( type ) {
|
|
|
+
|
|
|
+ case 'Uint8':
|
|
|
+ case 'uint8':
|
|
|
+ case 'uchar':
|
|
|
+ case 'unsigned char':
|
|
|
+ case 'uint8_t':
|
|
|
+ this.data = new Uint8Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Int8':
|
|
|
+ case 'int8':
|
|
|
+ case 'signed char':
|
|
|
+ case 'int8_t':
|
|
|
+ this.data = new Int8Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Int16':
|
|
|
+ case 'int16':
|
|
|
+ case 'short':
|
|
|
+ case 'short int':
|
|
|
+ case 'signed short':
|
|
|
+ case 'signed short int':
|
|
|
+ case 'int16_t':
|
|
|
+ this.data = new Int16Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Uint16':
|
|
|
+ case 'uint16':
|
|
|
+ case 'ushort':
|
|
|
+ case 'unsigned short':
|
|
|
+ case 'unsigned short int':
|
|
|
+ case 'uint16_t':
|
|
|
+ this.data = new Uint16Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Int32':
|
|
|
+ case 'int32':
|
|
|
+ case 'int':
|
|
|
+ case 'signed int':
|
|
|
+ case 'int32_t':
|
|
|
+ this.data = new Int32Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Uint32':
|
|
|
+ case 'uint32':
|
|
|
+ case 'uint':
|
|
|
+ case 'unsigned int':
|
|
|
+ case 'uint32_t':
|
|
|
+ this.data = new Uint32Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'longlong':
|
|
|
+ case 'long long':
|
|
|
+ case 'long long int':
|
|
|
+ case 'signed long long':
|
|
|
+ case 'signed long long int':
|
|
|
+ case 'int64':
|
|
|
+ case 'int64_t':
|
|
|
+ case 'ulonglong':
|
|
|
+ case 'unsigned long long':
|
|
|
+ case 'unsigned long long int':
|
|
|
+ case 'uint64':
|
|
|
+ case 'uint64_t':
|
|
|
+ throw new Error( 'Error in Volume constructor : this type is not supported in JavaScript' );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Float32':
|
|
|
+ case 'float32':
|
|
|
+ case 'float':
|
|
|
+ this.data = new Float32Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Float64':
|
|
|
+ case 'float64':
|
|
|
+ case 'double':
|
|
|
+ this.data = new Float64Array( arrayBuffer );
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ this.data = new Uint8Array( arrayBuffer );
|
|
|
|
|
|
- if ( arguments.length > 0 ) {
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( this.data.length !== this.xLength * this.yLength * this.zLength ) {
|
|
|
|
|
|
+ throw new Error( 'Error in Volume constructor, lengths are not matching arrayBuffer size' );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
/**
|
|
|
- * @member {number} xLength Width of the volume in the IJK coordinate system
|
|
|
+ * @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system
|
|
|
*/
|
|
|
- this.xLength = Number( xLength ) || 1;
|
|
|
+
|
|
|
+
|
|
|
+ this.spacing = [ 1, 1, 1 ];
|
|
|
/**
|
|
|
- * @member {number} yLength Height of the volume in the IJK coordinate system
|
|
|
+ * @member {Array} offset Offset of the volume in the RAS coordinate system
|
|
|
*/
|
|
|
|
|
|
- this.yLength = Number( yLength ) || 1;
|
|
|
+ this.offset = [ 0, 0, 0 ];
|
|
|
/**
|
|
|
- * @member {number} zLength Depth of the volume in the IJK coordinate system
|
|
|
+ * @member {Martrix3} matrix The IJK to RAS matrix
|
|
|
*/
|
|
|
|
|
|
- this.zLength = Number( zLength ) || 1;
|
|
|
+ this.matrix = new THREE.Matrix3();
|
|
|
+ this.matrix.identity();
|
|
|
/**
|
|
|
- * @member {Array<string>} The order of the Axis dictated by the NRRD header
|
|
|
+ * @member {Martrix3} inverseMatrix The RAS to IJK matrix
|
|
|
*/
|
|
|
|
|
|
- this.axisOrder = [ 'x', 'y', 'z' ];
|
|
|
/**
|
|
|
- * @member {TypedArray} data Data of the volume
|
|
|
+ * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices.
|
|
|
+ * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
|
|
|
*/
|
|
|
|
|
|
- switch ( type ) {
|
|
|
+ let lowerThreshold = - Infinity;
|
|
|
+ Object.defineProperty( this, 'lowerThreshold', {
|
|
|
+ get: function () {
|
|
|
|
|
|
- case 'Uint8':
|
|
|
- case 'uint8':
|
|
|
- case 'uchar':
|
|
|
- case 'unsigned char':
|
|
|
- case 'uint8_t':
|
|
|
- this.data = new Uint8Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ return lowerThreshold;
|
|
|
|
|
|
- case 'Int8':
|
|
|
- case 'int8':
|
|
|
- case 'signed char':
|
|
|
- case 'int8_t':
|
|
|
- this.data = new Int8Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ },
|
|
|
+ set: function ( value ) {
|
|
|
|
|
|
- case 'Int16':
|
|
|
- case 'int16':
|
|
|
- case 'short':
|
|
|
- case 'short int':
|
|
|
- case 'signed short':
|
|
|
- case 'signed short int':
|
|
|
- case 'int16_t':
|
|
|
- this.data = new Int16Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ lowerThreshold = value;
|
|
|
+ this.sliceList.forEach( function ( slice ) {
|
|
|
|
|
|
- case 'Uint16':
|
|
|
- case 'uint16':
|
|
|
- case 'ushort':
|
|
|
- case 'unsigned short':
|
|
|
- case 'unsigned short int':
|
|
|
- case 'uint16_t':
|
|
|
- this.data = new Uint16Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ slice.geometryNeedsUpdate = true;
|
|
|
|
|
|
- case 'Int32':
|
|
|
- case 'int32':
|
|
|
- case 'int':
|
|
|
- case 'signed int':
|
|
|
- case 'int32_t':
|
|
|
- this.data = new Int32Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ } );
|
|
|
|
|
|
- case 'Uint32':
|
|
|
- case 'uint32':
|
|
|
- case 'uint':
|
|
|
- case 'unsigned int':
|
|
|
- case 'uint32_t':
|
|
|
- this.data = new Uint32Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ }
|
|
|
+ } );
|
|
|
+ /**
|
|
|
+ * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices.
|
|
|
+ * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
|
|
|
+ */
|
|
|
|
|
|
- case 'longlong':
|
|
|
- case 'long long':
|
|
|
- case 'long long int':
|
|
|
- case 'signed long long':
|
|
|
- case 'signed long long int':
|
|
|
- case 'int64':
|
|
|
- case 'int64_t':
|
|
|
- case 'ulonglong':
|
|
|
- case 'unsigned long long':
|
|
|
- case 'unsigned long long int':
|
|
|
- case 'uint64':
|
|
|
- case 'uint64_t':
|
|
|
- throw new Error( 'Error in Volume constructor : this type is not supported in JavaScript' );
|
|
|
- break;
|
|
|
+ let upperThreshold = Infinity;
|
|
|
+ Object.defineProperty( this, 'upperThreshold', {
|
|
|
+ get: function () {
|
|
|
|
|
|
- case 'Float32':
|
|
|
- case 'float32':
|
|
|
- case 'float':
|
|
|
- this.data = new Float32Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ return upperThreshold;
|
|
|
|
|
|
- case 'Float64':
|
|
|
- case 'float64':
|
|
|
- case 'double':
|
|
|
- this.data = new Float64Array( arrayBuffer );
|
|
|
- break;
|
|
|
+ },
|
|
|
+ set: function ( value ) {
|
|
|
|
|
|
- default:
|
|
|
- this.data = new Uint8Array( arrayBuffer );
|
|
|
+ upperThreshold = value;
|
|
|
+ this.sliceList.forEach( function ( slice ) {
|
|
|
|
|
|
- }
|
|
|
+ slice.geometryNeedsUpdate = true;
|
|
|
|
|
|
- if ( this.data.length !== this.xLength * this.yLength * this.zLength ) {
|
|
|
+ } );
|
|
|
|
|
|
- throw new Error( 'Error in Volume constructor, lengths are not matching arrayBuffer size' );
|
|
|
+ }
|
|
|
+ } );
|
|
|
+ /**
|
|
|
+ * @member {Array} sliceList The list of all the slices associated to this volume
|
|
|
+ */
|
|
|
|
|
|
- }
|
|
|
+ this.sliceList = [];
|
|
|
+ /**
|
|
|
+ * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space
|
|
|
+ */
|
|
|
|
|
|
}
|
|
|
- /**
|
|
|
- * @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system
|
|
|
- */
|
|
|
-
|
|
|
-
|
|
|
- this.spacing = [ 1, 1, 1 ];
|
|
|
- /**
|
|
|
- * @member {Array} offset Offset of the volume in the RAS coordinate system
|
|
|
- */
|
|
|
-
|
|
|
- this.offset = [ 0, 0, 0 ];
|
|
|
- /**
|
|
|
- * @member {Martrix3} matrix The IJK to RAS matrix
|
|
|
- */
|
|
|
-
|
|
|
- this.matrix = new THREE.Matrix3();
|
|
|
- this.matrix.identity();
|
|
|
- /**
|
|
|
- * @member {Martrix3} inverseMatrix The RAS to IJK matrix
|
|
|
- */
|
|
|
-
|
|
|
- /**
|
|
|
- * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices.
|
|
|
- * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
|
|
|
- */
|
|
|
-
|
|
|
- let lowerThreshold = - Infinity;
|
|
|
- Object.defineProperty( this, 'lowerThreshold', {
|
|
|
- get: function () {
|
|
|
-
|
|
|
- return lowerThreshold;
|
|
|
-
|
|
|
- },
|
|
|
- set: function ( value ) {
|
|
|
-
|
|
|
- lowerThreshold = value;
|
|
|
- this.sliceList.forEach( function ( slice ) {
|
|
|
-
|
|
|
- slice.geometryNeedsUpdate = true;
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- }
|
|
|
- } );
|
|
|
- /**
|
|
|
- * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices.
|
|
|
- * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
|
|
|
- */
|
|
|
-
|
|
|
- let upperThreshold = Infinity;
|
|
|
- Object.defineProperty( this, 'upperThreshold', {
|
|
|
- get: function () {
|
|
|
-
|
|
|
- return upperThreshold;
|
|
|
-
|
|
|
- },
|
|
|
- set: function ( value ) {
|
|
|
-
|
|
|
- upperThreshold = value;
|
|
|
- this.sliceList.forEach( function ( slice ) {
|
|
|
-
|
|
|
- slice.geometryNeedsUpdate = true;
|
|
|
-
|
|
|
- } );
|
|
|
-
|
|
|
- }
|
|
|
- } );
|
|
|
- /**
|
|
|
- * @member {Array} sliceList The list of all the slices associated to this volume
|
|
|
- */
|
|
|
-
|
|
|
- this.sliceList = [];
|
|
|
- /**
|
|
|
- * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space
|
|
|
- */
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- Volume.prototype = {
|
|
|
- constructor: Volume,
|
|
|
-
|
|
|
/**
|
|
|
* @member {Function} getData Shortcut for data[access(i,j,k)]
|
|
|
* @memberof Volume
|
|
@@ -220,12 +218,13 @@
|
|
|
* @param {number} k Third coordinate
|
|
|
* @returns {number} value in the data array
|
|
|
*/
|
|
|
- getData: function ( i, j, k ) {
|
|
|
|
|
|
- return this.data[ k * this.xLength * this.yLength + j * this.xLength + i ];
|
|
|
|
|
|
- },
|
|
|
+ getData( i, j, k ) {
|
|
|
+
|
|
|
+ return this.data[ k * this.xLength * this.yLength + j * this.xLength + i ];
|
|
|
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system
|
|
|
* @memberof Volume
|
|
@@ -234,27 +233,29 @@
|
|
|
* @param {number} k Third coordinate
|
|
|
* @returns {number} index
|
|
|
*/
|
|
|
- access: function ( i, j, k ) {
|
|
|
|
|
|
- return k * this.xLength * this.yLength + j * this.xLength + i;
|
|
|
|
|
|
- },
|
|
|
+ access( i, j, k ) {
|
|
|
+
|
|
|
+ return k * this.xLength * this.yLength + j * this.xLength + i;
|
|
|
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data
|
|
|
* @memberof Volume
|
|
|
* @param {number} index index of the voxel
|
|
|
* @returns {Array} [x,y,z]
|
|
|
*/
|
|
|
- reverseAccess: function ( index ) {
|
|
|
+
|
|
|
+
|
|
|
+ reverseAccess( index ) {
|
|
|
|
|
|
const z = Math.floor( index / ( this.yLength * this.xLength ) );
|
|
|
const y = Math.floor( ( index - z * this.yLength * this.xLength ) / this.xLength );
|
|
|
const x = index - z * this.yLength * this.xLength - y * this.xLength;
|
|
|
return [ x, y, z ];
|
|
|
|
|
|
- },
|
|
|
-
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced
|
|
|
* @memberof Volume
|
|
@@ -265,7 +266,9 @@
|
|
|
* @param {Object} context You can specify a context in which call the function, default if this Volume
|
|
|
* @returns {Volume} this
|
|
|
*/
|
|
|
- map: function ( functionToMap, context ) {
|
|
|
+
|
|
|
+
|
|
|
+ map( functionToMap, context ) {
|
|
|
|
|
|
const length = this.data.length;
|
|
|
context = context || this;
|
|
@@ -278,8 +281,7 @@
|
|
|
|
|
|
return this;
|
|
|
|
|
|
- },
|
|
|
-
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} extractPerpendicularPlane Compute the orientation of the slice and returns all the information relative to the geometry such as sliceAccess, the plane matrix (orientation and position in RAS coordinate) and the dimensions of the plane in both coordinate system.
|
|
|
* @memberof Volume
|
|
@@ -287,7 +289,9 @@
|
|
|
* @param {number} index the index of the slice
|
|
|
* @returns {Object} an object containing all the usefull information on the geometry of the slice
|
|
|
*/
|
|
|
- extractPerpendicularPlane: function ( axis, RASIndex ) {
|
|
|
+
|
|
|
+
|
|
|
+ extractPerpendicularPlane( axis, RASIndex ) {
|
|
|
|
|
|
let firstSpacing, secondSpacing, positionOffset, IJKIndex;
|
|
|
const axisInIJK = new THREE.Vector3(),
|
|
@@ -386,8 +390,7 @@
|
|
|
planeHeight: planeHeight
|
|
|
};
|
|
|
|
|
|
- },
|
|
|
-
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} extractSlice Returns a slice corresponding to the given axis and index
|
|
|
* The coordinate are given in the Right Anterior Superior coordinate format
|
|
@@ -396,21 +399,24 @@
|
|
|
* @param {number} index the index of the slice
|
|
|
* @returns {VolumeSlice} the extracted slice
|
|
|
*/
|
|
|
- extractSlice: function ( axis, index ) {
|
|
|
+
|
|
|
+
|
|
|
+ extractSlice( axis, index ) {
|
|
|
|
|
|
const slice = new THREE.VolumeSlice( this, index, axis );
|
|
|
this.sliceList.push( slice );
|
|
|
return slice;
|
|
|
|
|
|
- },
|
|
|
-
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
|
|
|
* @see THREE.VolumeSlice.repaint
|
|
|
* @memberof Volume
|
|
|
* @returns {Volume} this
|
|
|
*/
|
|
|
- repaintAllSlices: function () {
|
|
|
+
|
|
|
+
|
|
|
+ repaintAllSlices() {
|
|
|
|
|
|
this.sliceList.forEach( function ( slice ) {
|
|
|
|
|
@@ -419,14 +425,15 @@
|
|
|
} );
|
|
|
return this;
|
|
|
|
|
|
- },
|
|
|
-
|
|
|
+ }
|
|
|
/**
|
|
|
* @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
|
|
|
* @memberof Volume
|
|
|
* @returns {Array} [min,max]
|
|
|
*/
|
|
|
- computeMinMax: function () {
|
|
|
+
|
|
|
+
|
|
|
+ computeMinMax() {
|
|
|
|
|
|
let min = Infinity;
|
|
|
let max = - Infinity; // buffer the length
|
|
@@ -451,7 +458,8 @@
|
|
|
return [ min, max ];
|
|
|
|
|
|
}
|
|
|
- };
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
THREE.Volume = Volume;
|
|
|
|