Volume.js 12.7 KB
Newer Older
M
r119  
Mr.doob 已提交
1 2 3 4 5 6
import {
	Matrix3,
	Matrix4,
	Vector3
} from "../../../build/three.module.js";
import { VolumeSlice } from "../misc/VolumeSlice.js";
M
r75  
Mr.doob 已提交
7 8 9 10 11 12 13 14 15 16 17 18
/**
 * This class had been written to handle the output of the NRRD loader.
 * It contains a volume of data and informations about it.
 * For now it only handles 3 dimensional data.
 * See the webgl_loader_nrrd.html example and the loaderNRRD.js file to see how to use this class.
 * @class
 * @param   {number}        xLength         Width of the volume
 * @param   {number}        yLength         Length of the volume
 * @param   {number}        zLength         Depth of the volume
 * @param   {string}        type            The type of data (uint8, uint16, ...)
 * @param   {ArrayBuffer}   arrayBuffer     The buffer with volume data
 */
M
r106  
Mr.doob 已提交
19
var Volume = function ( xLength, yLength, zLength, type, arrayBuffer ) {
M
r75  
Mr.doob 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

	if ( arguments.length > 0 ) {

		/**
		 * @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 {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' :
M
r106  
Mr.doob 已提交
98
				throw 'Error in Volume constructor : this type is not supported in JavaScript';
M
r75  
Mr.doob 已提交
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
				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 ( this.data.length !== this.xLength * this.yLength * this.zLength ) {

M
r106  
Mr.doob 已提交
117
			throw 'Error in Volume constructor, lengths are not matching arrayBuffer size';
M
r75  
Mr.doob 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131

		}

	}

	/**
	 * @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 ];
	/**
M
r106  
Mr.doob 已提交
132
	 * @member {Martrix3} matrix The IJK to RAS matrix
M
r75  
Mr.doob 已提交
133
	 */
M
r106  
Mr.doob 已提交
134
	this.matrix = new Matrix3();
M
r75  
Mr.doob 已提交
135 136
	this.matrix.identity();
	/**
M
r106  
Mr.doob 已提交
137
	 * @member {Martrix3} inverseMatrix The RAS to IJK matrix
M
r75  
Mr.doob 已提交
138 139 140 141 142 143 144
	 */
	/**
	 * @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
	 */
	var lowerThreshold = - Infinity;
	Object.defineProperty( this, 'lowerThreshold', {
M
r97  
Mr.doob 已提交
145
		get: function () {
M
r75  
Mr.doob 已提交
146 147 148 149

			return lowerThreshold;

		},
M
r97  
Mr.doob 已提交
150
		set: function ( value ) {
M
r75  
Mr.doob 已提交
151 152

			lowerThreshold = value;
M
r97  
Mr.doob 已提交
153
			this.sliceList.forEach( function ( slice ) {
M
r75  
Mr.doob 已提交
154

M
r97  
Mr.doob 已提交
155
				slice.geometryNeedsUpdate = true;
M
r75  
Mr.doob 已提交
156 157 158 159 160 161 162 163 164 165 166

			} );

		}
	} );
	/**
	 * @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
	 */
	var upperThreshold = Infinity;
	Object.defineProperty( this, 'upperThreshold', {
M
r97  
Mr.doob 已提交
167
		get: function () {
M
r75  
Mr.doob 已提交
168 169 170 171

			return upperThreshold;

		},
M
r97  
Mr.doob 已提交
172
		set: function ( value ) {
M
r75  
Mr.doob 已提交
173 174

			upperThreshold = value;
M
r97  
Mr.doob 已提交
175
			this.sliceList.forEach( function ( slice ) {
M
r75  
Mr.doob 已提交
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

				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
	 */

M
r83  
Mr.doob 已提交
195
};
M
r75  
Mr.doob 已提交
196

M
r106  
Mr.doob 已提交
197
Volume.prototype = {
M
r75  
Mr.doob 已提交
198

M
r106  
Mr.doob 已提交
199
	constructor: Volume,
M
r75  
Mr.doob 已提交
200 201 202

	/**
	 * @member {Function} getData Shortcut for data[access(i,j,k)]
M
r106  
Mr.doob 已提交
203
	 * @memberof Volume
M
r75  
Mr.doob 已提交
204 205 206 207 208
	 * @param {number} i    First coordinate
	 * @param {number} j    Second coordinate
	 * @param {number} k    Third coordinate
	 * @returns {number}  value in the data array
	 */
M
r97  
Mr.doob 已提交
209
	getData: function ( i, j, k ) {
M
r75  
Mr.doob 已提交
210 211 212 213 214 215 216

		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
M
r106  
Mr.doob 已提交
217
	 * @memberof Volume
M
r75  
Mr.doob 已提交
218 219 220 221 222
	 * @param {number} i    First coordinate
	 * @param {number} j    Second coordinate
	 * @param {number} k    Third coordinate
	 * @returns {number}  index
	 */
M
r97  
Mr.doob 已提交
223
	access: function ( i, j, k ) {
M
r75  
Mr.doob 已提交
224 225 226 227 228 229 230

		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
M
r106  
Mr.doob 已提交
231
	 * @memberof Volume
M
r75  
Mr.doob 已提交
232 233 234
	 * @param {number} index index of the voxel
	 * @returns {Array}  [x,y,z]
	 */
M
r97  
Mr.doob 已提交
235
	reverseAccess: function ( index ) {
M
r75  
Mr.doob 已提交
236 237 238 239 240 241 242 243 244 245

		var z = Math.floor( index / ( this.yLength * this.xLength ) );
		var y = Math.floor( ( index - z * this.yLength * this.xLength ) / this.xLength );
		var 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
M
r106  
Mr.doob 已提交
246
	 * @memberof Volume
M
r75  
Mr.doob 已提交
247 248 249 250 251
	 * @param {Function} functionToMap A function to apply to every voxel, will be called with the following parameters :
	 *                                 value of the voxel
	 *                                 index of the voxel
	 *                                 the data (TypedArray)
	 * @param {Object}   context    You can specify a context in which call the function, default if this Volume
M
r106  
Mr.doob 已提交
252
	 * @returns {Volume}   this
M
r75  
Mr.doob 已提交
253
	 */
M
r97  
Mr.doob 已提交
254
	map: function ( functionToMap, context ) {
M
r75  
Mr.doob 已提交
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270

		var length = this.data.length;
		context = context || this;

		for ( var i = 0; i < length; i ++ ) {

			this.data[ i ] = functionToMap.call( context, this.data[ i ], i, this.data );

		}

		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.
M
r106  
Mr.doob 已提交
271
	 * @memberof Volume
M
r75  
Mr.doob 已提交
272 273 274 275
	 * @param {string}            axis  the normal axis to the slice 'x' 'y' or 'z'
	 * @param {number}            index the index of the slice
	 * @returns {Object} an object containing all the usefull information on the geometry of the slice
	 */
M
r97  
Mr.doob 已提交
276
	extractPerpendicularPlane: function ( axis, RASIndex ) {
M
r75  
Mr.doob 已提交
277 278

		var iLength,
M
r97  
Mr.doob 已提交
279 280
			jLength,
			sliceAccess,
M
r106  
Mr.doob 已提交
281
			planeMatrix = ( new Matrix4() ).identity(),
M
r97  
Mr.doob 已提交
282 283 284 285 286 287 288
			volume = this,
			planeWidth,
			planeHeight,
			firstSpacing,
			secondSpacing,
			positionOffset,
			IJKIndex;
M
r75  
Mr.doob 已提交
289

M
r106  
Mr.doob 已提交
290 291 292
		var axisInIJK = new Vector3(),
			firstDirection = new Vector3(),
			secondDirection = new Vector3();
M
r75  
Mr.doob 已提交
293

M
r106  
Mr.doob 已提交
294
		var dimensions = new Vector3( this.xLength, this.yLength, this.zLength );
M
r75  
Mr.doob 已提交
295 296 297 298 299 300 301 302 303 304


		switch ( axis ) {

			case 'x' :
				axisInIJK.set( 1, 0, 0 );
				firstDirection.set( 0, 0, - 1 );
				secondDirection.set( 0, - 1, 0 );
				firstSpacing = this.spacing[ 2 ];
				secondSpacing = this.spacing[ 1 ];
M
r106  
Mr.doob 已提交
305
				IJKIndex = new Vector3( RASIndex, 0, 0 );
M
r75  
Mr.doob 已提交
306

M
r106  
Mr.doob 已提交
307
				planeMatrix.multiply( ( new Matrix4() ).makeRotationY( Math.PI / 2 ) );
M
r75  
Mr.doob 已提交
308
				positionOffset = ( volume.RASDimensions[ 0 ] - 1 ) / 2;
M
r106  
Mr.doob 已提交
309
				planeMatrix.setPosition( new Vector3( RASIndex - positionOffset, 0, 0 ) );
M
r75  
Mr.doob 已提交
310 311 312 313 314 315 316
				break;
			case 'y' :
				axisInIJK.set( 0, 1, 0 );
				firstDirection.set( 1, 0, 0 );
				secondDirection.set( 0, 0, 1 );
				firstSpacing = this.spacing[ 0 ];
				secondSpacing = this.spacing[ 2 ];
M
r106  
Mr.doob 已提交
317
				IJKIndex = new Vector3( 0, RASIndex, 0 );
M
r75  
Mr.doob 已提交
318

M
r106  
Mr.doob 已提交
319
				planeMatrix.multiply( ( new Matrix4() ).makeRotationX( - Math.PI / 2 ) );
M
r75  
Mr.doob 已提交
320
				positionOffset = ( volume.RASDimensions[ 1 ] - 1 ) / 2;
M
r106  
Mr.doob 已提交
321
				planeMatrix.setPosition( new Vector3( 0, RASIndex - positionOffset, 0 ) );
M
r75  
Mr.doob 已提交
322 323 324 325 326 327 328 329
				break;
			case 'z' :
			default :
				axisInIJK.set( 0, 0, 1 );
				firstDirection.set( 1, 0, 0 );
				secondDirection.set( 0, - 1, 0 );
				firstSpacing = this.spacing[ 0 ];
				secondSpacing = this.spacing[ 1 ];
M
r106  
Mr.doob 已提交
330
				IJKIndex = new Vector3( 0, 0, RASIndex );
M
r75  
Mr.doob 已提交
331 332

				positionOffset = ( volume.RASDimensions[ 2 ] - 1 ) / 2;
M
r106  
Mr.doob 已提交
333
				planeMatrix.setPosition( new Vector3( 0, 0, RASIndex - positionOffset ) );
M
r75  
Mr.doob 已提交
334
				break;
M
r97  
Mr.doob 已提交
335

M
r75  
Mr.doob 已提交
336 337 338 339 340 341 342 343 344 345 346 347 348
		}

		firstDirection.applyMatrix4( volume.inverseMatrix ).normalize();
		firstDirection.argVar = 'i';
		secondDirection.applyMatrix4( volume.inverseMatrix ).normalize();
		secondDirection.argVar = 'j';
		axisInIJK.applyMatrix4( volume.inverseMatrix ).normalize();
		iLength = Math.floor( Math.abs( firstDirection.dot( dimensions ) ) );
		jLength = Math.floor( Math.abs( secondDirection.dot( dimensions ) ) );
		planeWidth = Math.abs( iLength * firstSpacing );
		planeHeight = Math.abs( jLength * secondSpacing );

		IJKIndex = Math.abs( Math.round( IJKIndex.applyMatrix4( volume.inverseMatrix ).dot( axisInIJK ) ) );
M
r106  
Mr.doob 已提交
349
		var base = [ new Vector3( 1, 0, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ) ];
M
r97  
Mr.doob 已提交
350
		var iDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
M
r75  
Mr.doob 已提交
351 352 353 354

			return Math.abs( x.dot( base[ 0 ] ) ) > 0.9;

		} );
M
r97  
Mr.doob 已提交
355
		var jDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
M
r75  
Mr.doob 已提交
356 357 358 359

			return Math.abs( x.dot( base[ 1 ] ) ) > 0.9;

		} );
M
r97  
Mr.doob 已提交
360
		var kDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
M
r75  
Mr.doob 已提交
361 362 363 364 365

			return Math.abs( x.dot( base[ 2 ] ) ) > 0.9;

		} );

M
r116  
Mr.doob 已提交
366
		sliceAccess = function ( i, j ) {
M
r75  
Mr.doob 已提交
367

M
r116  
Mr.doob 已提交
368 369 370 371 372 373 374 375 376 377 378
			var accessI, accessJ, accessK;

			var si = ( iDirection === axisInIJK ) ? IJKIndex : ( iDirection.argVar === 'i' ? i : j );
			var sj = ( jDirection === axisInIJK ) ? IJKIndex : ( jDirection.argVar === 'i' ? i : j );
			var sk = ( kDirection === axisInIJK ) ? IJKIndex : ( kDirection.argVar === 'i' ? i : j );

			// invert indices if necessary

			var accessI = ( iDirection.dot( base[ 0 ] ) > 0 ) ? si : ( volume.xLength - 1 ) - si;
			var accessJ = ( jDirection.dot( base[ 1 ] ) > 0 ) ? sj : ( volume.yLength - 1 ) - sj;
			var accessK = ( kDirection.dot( base[ 2 ] ) > 0 ) ? sk : ( volume.zLength - 1 ) - sk;
M
r75  
Mr.doob 已提交
379

M
r116  
Mr.doob 已提交
380 381 382
			return volume.access( accessI, accessJ, accessK );

		};
M
r75  
Mr.doob 已提交
383 384

		return {
M
r97  
Mr.doob 已提交
385 386 387 388 389 390
			iLength: iLength,
			jLength: jLength,
			sliceAccess: sliceAccess,
			matrix: planeMatrix,
			planeWidth: planeWidth,
			planeHeight: planeHeight
M
r96  
Mr.doob 已提交
391
		};
M
r75  
Mr.doob 已提交
392 393 394 395 396 397

	},

	/**
	 * @member {Function} extractSlice Returns a slice corresponding to the given axis and index
	 *                        The coordinate are given in the Right Anterior Superior coordinate format
M
r106  
Mr.doob 已提交
398
	 * @memberof Volume
M
r75  
Mr.doob 已提交
399 400
	 * @param {string}            axis  the normal axis to the slice 'x' 'y' or 'z'
	 * @param {number}            index the index of the slice
M
r106  
Mr.doob 已提交
401
	 * @returns {VolumeSlice} the extracted slice
M
r75  
Mr.doob 已提交
402
	 */
M
r97  
Mr.doob 已提交
403
	extractSlice: function ( axis, index ) {
M
r75  
Mr.doob 已提交
404

M
r106  
Mr.doob 已提交
405
		var slice = new VolumeSlice( this, index, axis );
M
r75  
Mr.doob 已提交
406 407 408 409 410 411 412
		this.sliceList.push( slice );
		return slice;

	},

	/**
	 * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
M
r106  
Mr.doob 已提交
413 414 415
	 * @see VolumeSlice.repaint
	 * @memberof Volume
	 * @returns {Volume} this
M
r75  
Mr.doob 已提交
416
	 */
M
r97  
Mr.doob 已提交
417
	repaintAllSlices: function () {
M
r75  
Mr.doob 已提交
418

M
r97  
Mr.doob 已提交
419
		this.sliceList.forEach( function ( slice ) {
M
r75  
Mr.doob 已提交
420 421 422 423 424 425 426 427 428 429 430

			slice.repaint();

		} );

		return this;

	},

	/**
	 * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
M
r106  
Mr.doob 已提交
431
	 * @memberof Volume
M
r75  
Mr.doob 已提交
432 433
	 * @returns {Array} [min,max]
	 */
M
r97  
Mr.doob 已提交
434
	computeMinMax: function () {
M
r75  
Mr.doob 已提交
435 436 437 438 439 440 441 442

		var min = Infinity;
		var max = - Infinity;

		// buffer the length
		var datasize = this.data.length;

		var i = 0;
M
r116  
Mr.doob 已提交
443

M
r75  
Mr.doob 已提交
444 445 446 447 448 449 450 451 452 453 454
		for ( i = 0; i < datasize; i ++ ) {

			if ( ! isNaN( this.data[ i ] ) ) {

				var value = this.data[ i ];
				min = Math.min( min, value );
				max = Math.max( max, value );

			}

		}
M
r116  
Mr.doob 已提交
455

M
r75  
Mr.doob 已提交
456 457 458 459 460 461 462
		this.min = min;
		this.max = max;

		return [ min, max ];

	}

M
r83  
Mr.doob 已提交
463
};
M
r106  
Mr.doob 已提交
464 465

export { Volume };