Volume.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. console.warn( "THREE.Volume: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/index.html#manual/en/introduction/Import-via-modules." );
  2. /**
  3. * This class had been written to handle the output of the NRRD loader.
  4. * It contains a volume of data and informations about it.
  5. * For now it only handles 3 dimensional data.
  6. * See the webgl_loader_nrrd.html example and the loaderNRRD.js file to see how to use this class.
  7. * @class
  8. * @author Valentin Demeusy / https://github.com/stity
  9. * @param {number} xLength Width of the volume
  10. * @param {number} yLength Length of the volume
  11. * @param {number} zLength Depth of the volume
  12. * @param {string} type The type of data (uint8, uint16, ...)
  13. * @param {ArrayBuffer} arrayBuffer The buffer with volume data
  14. */
  15. THREE.Volume = function ( xLength, yLength, zLength, type, arrayBuffer ) {
  16. if ( arguments.length > 0 ) {
  17. /**
  18. * @member {number} xLength Width of the volume in the IJK coordinate system
  19. */
  20. this.xLength = Number( xLength ) || 1;
  21. /**
  22. * @member {number} yLength Height of the volume in the IJK coordinate system
  23. */
  24. this.yLength = Number( yLength ) || 1;
  25. /**
  26. * @member {number} zLength Depth of the volume in the IJK coordinate system
  27. */
  28. this.zLength = Number( zLength ) || 1;
  29. /**
  30. * @member {TypedArray} data Data of the volume
  31. */
  32. switch ( type ) {
  33. case 'Uint8' :
  34. case 'uint8' :
  35. case 'uchar' :
  36. case 'unsigned char' :
  37. case 'uint8_t' :
  38. this.data = new Uint8Array( arrayBuffer );
  39. break;
  40. case 'Int8' :
  41. case 'int8' :
  42. case 'signed char' :
  43. case 'int8_t' :
  44. this.data = new Int8Array( arrayBuffer );
  45. break;
  46. case 'Int16' :
  47. case 'int16' :
  48. case 'short' :
  49. case 'short int' :
  50. case 'signed short' :
  51. case 'signed short int' :
  52. case 'int16_t' :
  53. this.data = new Int16Array( arrayBuffer );
  54. break;
  55. case 'Uint16' :
  56. case 'uint16' :
  57. case 'ushort' :
  58. case 'unsigned short' :
  59. case 'unsigned short int' :
  60. case 'uint16_t' :
  61. this.data = new Uint16Array( arrayBuffer );
  62. break;
  63. case 'Int32' :
  64. case 'int32' :
  65. case 'int' :
  66. case 'signed int' :
  67. case 'int32_t' :
  68. this.data = new Int32Array( arrayBuffer );
  69. break;
  70. case 'Uint32' :
  71. case 'uint32' :
  72. case 'uint' :
  73. case 'unsigned int' :
  74. case 'uint32_t' :
  75. this.data = new Uint32Array( arrayBuffer );
  76. break;
  77. case 'longlong' :
  78. case 'long long' :
  79. case 'long long int' :
  80. case 'signed long long' :
  81. case 'signed long long int' :
  82. case 'int64' :
  83. case 'int64_t' :
  84. case 'ulonglong' :
  85. case 'unsigned long long' :
  86. case 'unsigned long long int' :
  87. case 'uint64' :
  88. case 'uint64_t' :
  89. throw 'Error in THREE.Volume constructor : this type is not supported in JavaScript';
  90. break;
  91. case 'Float32' :
  92. case 'float32' :
  93. case 'float' :
  94. this.data = new Float32Array( arrayBuffer );
  95. break;
  96. case 'Float64' :
  97. case 'float64' :
  98. case 'double' :
  99. this.data = new Float64Array( arrayBuffer );
  100. break;
  101. default :
  102. this.data = new Uint8Array( arrayBuffer );
  103. }
  104. if ( this.data.length !== this.xLength * this.yLength * this.zLength ) {
  105. throw 'Error in THREE.Volume constructor, lengths are not matching arrayBuffer size';
  106. }
  107. }
  108. /**
  109. * @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system
  110. */
  111. this.spacing = [ 1, 1, 1 ];
  112. /**
  113. * @member {Array} offset Offset of the volume in the RAS coordinate system
  114. */
  115. this.offset = [ 0, 0, 0 ];
  116. /**
  117. * @member {THREE.Martrix3} matrix The IJK to RAS matrix
  118. */
  119. this.matrix = new THREE.Matrix3();
  120. this.matrix.identity();
  121. /**
  122. * @member {THREE.Martrix3} inverseMatrix The RAS to IJK matrix
  123. */
  124. /**
  125. * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices.
  126. * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
  127. */
  128. var lowerThreshold = - Infinity;
  129. Object.defineProperty( this, 'lowerThreshold', {
  130. get: function () {
  131. return lowerThreshold;
  132. },
  133. set: function ( value ) {
  134. lowerThreshold = value;
  135. this.sliceList.forEach( function ( slice ) {
  136. slice.geometryNeedsUpdate = true;
  137. } );
  138. }
  139. } );
  140. /**
  141. * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices.
  142. * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
  143. */
  144. var upperThreshold = Infinity;
  145. Object.defineProperty( this, 'upperThreshold', {
  146. get: function () {
  147. return upperThreshold;
  148. },
  149. set: function ( value ) {
  150. upperThreshold = value;
  151. this.sliceList.forEach( function ( slice ) {
  152. slice.geometryNeedsUpdate = true;
  153. } );
  154. }
  155. } );
  156. /**
  157. * @member {Array} sliceList The list of all the slices associated to this volume
  158. */
  159. this.sliceList = [];
  160. /**
  161. * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space
  162. */
  163. };
  164. THREE.Volume.prototype = {
  165. constructor: THREE.Volume,
  166. /**
  167. * @member {Function} getData Shortcut for data[access(i,j,k)]
  168. * @memberof THREE.Volume
  169. * @param {number} i First coordinate
  170. * @param {number} j Second coordinate
  171. * @param {number} k Third coordinate
  172. * @returns {number} value in the data array
  173. */
  174. getData: function ( i, j, k ) {
  175. return this.data[ k * this.xLength * this.yLength + j * this.xLength + i ];
  176. },
  177. /**
  178. * @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system
  179. * @memberof THREE.Volume
  180. * @param {number} i First coordinate
  181. * @param {number} j Second coordinate
  182. * @param {number} k Third coordinate
  183. * @returns {number} index
  184. */
  185. access: function ( i, j, k ) {
  186. return k * this.xLength * this.yLength + j * this.xLength + i;
  187. },
  188. /**
  189. * @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data
  190. * @memberof THREE.Volume
  191. * @param {number} index index of the voxel
  192. * @returns {Array} [x,y,z]
  193. */
  194. reverseAccess: function ( index ) {
  195. var z = Math.floor( index / ( this.yLength * this.xLength ) );
  196. var y = Math.floor( ( index - z * this.yLength * this.xLength ) / this.xLength );
  197. var x = index - z * this.yLength * this.xLength - y * this.xLength;
  198. return [ x, y, z ];
  199. },
  200. /**
  201. * @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced
  202. * @memberof THREE.Volume
  203. * @param {Function} functionToMap A function to apply to every voxel, will be called with the following parameters :
  204. * value of the voxel
  205. * index of the voxel
  206. * the data (TypedArray)
  207. * @param {Object} context You can specify a context in which call the function, default if this Volume
  208. * @returns {THREE.Volume} this
  209. */
  210. map: function ( functionToMap, context ) {
  211. var length = this.data.length;
  212. context = context || this;
  213. for ( var i = 0; i < length; i ++ ) {
  214. this.data[ i ] = functionToMap.call( context, this.data[ i ], i, this.data );
  215. }
  216. return this;
  217. },
  218. /**
  219. * @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.
  220. * @memberof THREE.Volume
  221. * @param {string} axis the normal axis to the slice 'x' 'y' or 'z'
  222. * @param {number} index the index of the slice
  223. * @returns {Object} an object containing all the usefull information on the geometry of the slice
  224. */
  225. extractPerpendicularPlane: function ( axis, RASIndex ) {
  226. var iLength,
  227. jLength,
  228. sliceAccess,
  229. planeMatrix = ( new THREE.Matrix4() ).identity(),
  230. volume = this,
  231. planeWidth,
  232. planeHeight,
  233. firstSpacing,
  234. secondSpacing,
  235. positionOffset,
  236. IJKIndex;
  237. var axisInIJK = new THREE.Vector3(),
  238. firstDirection = new THREE.Vector3(),
  239. secondDirection = new THREE.Vector3();
  240. var dimensions = new THREE.Vector3( this.xLength, this.yLength, this.zLength );
  241. switch ( axis ) {
  242. case 'x' :
  243. axisInIJK.set( 1, 0, 0 );
  244. firstDirection.set( 0, 0, - 1 );
  245. secondDirection.set( 0, - 1, 0 );
  246. firstSpacing = this.spacing[ 2 ];
  247. secondSpacing = this.spacing[ 1 ];
  248. IJKIndex = new THREE.Vector3( RASIndex, 0, 0 );
  249. planeMatrix.multiply( ( new THREE.Matrix4() ).makeRotationY( Math.PI / 2 ) );
  250. positionOffset = ( volume.RASDimensions[ 0 ] - 1 ) / 2;
  251. planeMatrix.setPosition( new THREE.Vector3( RASIndex - positionOffset, 0, 0 ) );
  252. break;
  253. case 'y' :
  254. axisInIJK.set( 0, 1, 0 );
  255. firstDirection.set( 1, 0, 0 );
  256. secondDirection.set( 0, 0, 1 );
  257. firstSpacing = this.spacing[ 0 ];
  258. secondSpacing = this.spacing[ 2 ];
  259. IJKIndex = new THREE.Vector3( 0, RASIndex, 0 );
  260. planeMatrix.multiply( ( new THREE.Matrix4() ).makeRotationX( - Math.PI / 2 ) );
  261. positionOffset = ( volume.RASDimensions[ 1 ] - 1 ) / 2;
  262. planeMatrix.setPosition( new THREE.Vector3( 0, RASIndex - positionOffset, 0 ) );
  263. break;
  264. case 'z' :
  265. default :
  266. axisInIJK.set( 0, 0, 1 );
  267. firstDirection.set( 1, 0, 0 );
  268. secondDirection.set( 0, - 1, 0 );
  269. firstSpacing = this.spacing[ 0 ];
  270. secondSpacing = this.spacing[ 1 ];
  271. IJKIndex = new THREE.Vector3( 0, 0, RASIndex );
  272. positionOffset = ( volume.RASDimensions[ 2 ] - 1 ) / 2;
  273. planeMatrix.setPosition( new THREE.Vector3( 0, 0, RASIndex - positionOffset ) );
  274. break;
  275. }
  276. firstDirection.applyMatrix4( volume.inverseMatrix ).normalize();
  277. firstDirection.argVar = 'i';
  278. secondDirection.applyMatrix4( volume.inverseMatrix ).normalize();
  279. secondDirection.argVar = 'j';
  280. axisInIJK.applyMatrix4( volume.inverseMatrix ).normalize();
  281. iLength = Math.floor( Math.abs( firstDirection.dot( dimensions ) ) );
  282. jLength = Math.floor( Math.abs( secondDirection.dot( dimensions ) ) );
  283. planeWidth = Math.abs( iLength * firstSpacing );
  284. planeHeight = Math.abs( jLength * secondSpacing );
  285. IJKIndex = Math.abs( Math.round( IJKIndex.applyMatrix4( volume.inverseMatrix ).dot( axisInIJK ) ) );
  286. var base = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
  287. var iDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
  288. return Math.abs( x.dot( base[ 0 ] ) ) > 0.9;
  289. } );
  290. var jDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
  291. return Math.abs( x.dot( base[ 1 ] ) ) > 0.9;
  292. } );
  293. var kDirection = [ firstDirection, secondDirection, axisInIJK ].find( function ( x ) {
  294. return Math.abs( x.dot( base[ 2 ] ) ) > 0.9;
  295. } );
  296. sliceAccess = function ( i, j ) {
  297. var accessI, accessJ, accessK;
  298. var si = ( iDirection === axisInIJK ) ? IJKIndex : ( iDirection.argVar === 'i' ? i : j );
  299. var sj = ( jDirection === axisInIJK ) ? IJKIndex : ( jDirection.argVar === 'i' ? i : j );
  300. var sk = ( kDirection === axisInIJK ) ? IJKIndex : ( kDirection.argVar === 'i' ? i : j );
  301. // invert indices if necessary
  302. var accessI = ( iDirection.dot( base[ 0 ] ) > 0 ) ? si : ( volume.xLength - 1 ) - si;
  303. var accessJ = ( jDirection.dot( base[ 1 ] ) > 0 ) ? sj : ( volume.yLength - 1 ) - sj;
  304. var accessK = ( kDirection.dot( base[ 2 ] ) > 0 ) ? sk : ( volume.zLength - 1 ) - sk;
  305. return volume.access( accessI, accessJ, accessK );
  306. };
  307. return {
  308. iLength: iLength,
  309. jLength: jLength,
  310. sliceAccess: sliceAccess,
  311. matrix: planeMatrix,
  312. planeWidth: planeWidth,
  313. planeHeight: planeHeight
  314. };
  315. },
  316. /**
  317. * @member {Function} extractSlice Returns a slice corresponding to the given axis and index
  318. * The coordinate are given in the Right Anterior Superior coordinate format
  319. * @memberof THREE.Volume
  320. * @param {string} axis the normal axis to the slice 'x' 'y' or 'z'
  321. * @param {number} index the index of the slice
  322. * @returns {THREE.VolumeSlice} the extracted slice
  323. */
  324. extractSlice: function ( axis, index ) {
  325. var slice = new THREE.VolumeSlice( this, index, axis );
  326. this.sliceList.push( slice );
  327. return slice;
  328. },
  329. /**
  330. * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
  331. * @see THREE.VolumeSlice.repaint
  332. * @memberof THREE.Volume
  333. * @returns {THREE.Volume} this
  334. */
  335. repaintAllSlices: function () {
  336. this.sliceList.forEach( function ( slice ) {
  337. slice.repaint();
  338. } );
  339. return this;
  340. },
  341. /**
  342. * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
  343. * @memberof THREE.Volume
  344. * @returns {Array} [min,max]
  345. */
  346. computeMinMax: function () {
  347. var min = Infinity;
  348. var max = - Infinity;
  349. // buffer the length
  350. var datasize = this.data.length;
  351. var i = 0;
  352. for ( i = 0; i < datasize; i ++ ) {
  353. if ( ! isNaN( this.data[ i ] ) ) {
  354. var value = this.data[ i ];
  355. min = Math.min( min, value );
  356. max = Math.max( max, value );
  357. }
  358. }
  359. this.min = min;
  360. this.max = max;
  361. return [ min, max ];
  362. }
  363. };