direct-volume.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  7. import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
  8. import { Box3D } from '../../mol-math/geometry';
  9. import { VolumeData } from '../../mol-model/volume';
  10. import { RuntimeContext } from '../../mol-task';
  11. import { WebGLContext } from '../../mol-gl/webgl/context';
  12. import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
  13. import { VisualContext } from '../visual';
  14. import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
  15. import { BaseGeometry } from '../../mol-geo/geometry/base';
  16. import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
  17. import { LocationIterator } from '../../mol-geo/util/location-iterator';
  18. import { NullLocation } from '../../mol-model/location';
  19. import { EmptyLoci } from '../../mol-model/loci';
  20. import { VisualUpdateState } from '../util';
  21. import { RepresentationContext, RepresentationParamsGetter } from '../representation';
  22. function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
  23. const bbox = Box3D.empty()
  24. Box3D.add(bbox, gridDimension)
  25. Box3D.transform(bbox, bbox, transform)
  26. return bbox
  27. }
  28. // 2d volume texture
  29. function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize: number) {
  30. let width = 0
  31. let height = dim[1]
  32. let rows = 1
  33. let columns = dim[0]
  34. if (maxTextureSize < dim[0] * dim[2]) {
  35. columns = Math.floor(maxTextureSize / dim[0])
  36. rows = Math.ceil(dim[2] / columns)
  37. width = columns * dim[0]
  38. height *= rows
  39. } else {
  40. width = dim[0] * dim[2]
  41. }
  42. width += columns // horizontal padding
  43. height += rows // vertical padding
  44. return { width, height, columns, rows }
  45. }
  46. function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) {
  47. const { data: tensor, dataStats: stats } = volume
  48. const { space, data } = tensor
  49. const dim = space.dimensions as Vec3
  50. const { get } = space
  51. const { width, height, columns, rows } = getVolumeTexture2dLayout(dim, maxTextureSize)
  52. const array = new Uint8Array(width * height * 4)
  53. const textureImage = { array, width, height }
  54. const [ xl, yl, zl ] = dim
  55. const xlp = xl + 1 // horizontal padding
  56. const ylp = yl + 1 // vertical padding
  57. function setTex(value: number, x: number, y: number, z: number) {
  58. const column = Math.floor(((z * xlp) % width) / xlp)
  59. const row = Math.floor((z * xlp) / width)
  60. const px = column * xlp + x
  61. const index = 4 * ((row * ylp * width) + (y * width) + px)
  62. array[index + 3] = ((value - stats.min) / (stats.max - stats.min)) * 255
  63. }
  64. console.log('dim', dim)
  65. console.log('layout', { width, height, columns, rows })
  66. for (let z = 0; z < zl; ++z) {
  67. for (let y = 0; y < yl; ++y) {
  68. for (let x = 0; x < xl; ++x) {
  69. setTex(get(data, x, y, z), x, y, z)
  70. }
  71. }
  72. }
  73. return textureImage
  74. }
  75. export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: VolumeData, directVolume?: DirectVolume) {
  76. const gridDimension = volume.data.space.dimensions as Vec3
  77. const textureImage = createVolumeTexture2d(volume, webgl.maxTextureSize)
  78. // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
  79. const transform = VolumeData.getGridToCartesianTransform(volume)
  80. const bbox = getBoundingBox(gridDimension, transform)
  81. const dim = Vec3.create(gridDimension[0], gridDimension[1], gridDimension[2])
  82. dim[0] += 1 // horizontal padding
  83. dim[0] += 1 // vertical padding
  84. const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear')
  85. texture.load(textureImage)
  86. return DirectVolume.create(bbox, dim, transform, texture, directVolume)
  87. }
  88. // 3d volume texture
  89. function createVolumeTexture3d(volume: VolumeData) {
  90. const { data: tensor, dataStats: stats } = volume
  91. const { space, data } = tensor
  92. const [ width, height, depth ] = space.dimensions as Vec3
  93. const { get } = space
  94. const array = new Uint8Array(width * height * depth * 4)
  95. const textureVolume = { array, width, height, depth }
  96. let i = 0
  97. for (let z = 0; z < depth; ++z) {
  98. for (let y = 0; y < height; ++y) {
  99. for (let x = 0; x < width; ++x) {
  100. if (i < 100) {
  101. console.log(get(data, x, y, z), ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255)
  102. }
  103. array[i + 3] = ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255
  104. i += 4
  105. }
  106. }
  107. }
  108. return textureVolume
  109. }
  110. export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: VolumeData, directVolume?: DirectVolume) {
  111. const gridDimension = volume.data.space.dimensions as Vec3
  112. const textureVolume = createVolumeTexture3d(volume)
  113. const transform = VolumeData.getGridToCartesianTransform(volume)
  114. // Mat4.invert(transform, transform)
  115. const bbox = getBoundingBox(gridDimension, transform)
  116. const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear')
  117. texture.load(textureVolume)
  118. return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume)
  119. }
  120. //
  121. export async function createDirectVolume(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
  122. const { runtime, webgl } = ctx
  123. if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props')
  124. return webgl.isWebGL2 ?
  125. await createDirectVolume3d(runtime, webgl, volume, directVolume) :
  126. await createDirectVolume2d(runtime, webgl, volume, directVolume)
  127. }
  128. //
  129. export const DirectVolumeParams = {
  130. ...BaseGeometry.Params,
  131. ...DirectVolume.Params
  132. }
  133. export type DirectVolumeParams = typeof DirectVolumeParams
  134. export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: VolumeData) {
  135. return PD.clone(DirectVolumeParams)
  136. }
  137. export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolumeParams> {
  138. return VolumeVisual<DirectVolume, DirectVolumeParams>({
  139. defaultProps: PD.getDefaultValues(DirectVolumeParams),
  140. createGeometry: createDirectVolume,
  141. createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
  142. getLoci: () => EmptyLoci,
  143. eachLocation: () => false,
  144. setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
  145. },
  146. geometryUtils: DirectVolume.Utils
  147. }, materialId)
  148. }
  149. export function DirectVolumeRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, DirectVolumeParams>): VolumeRepresentation<DirectVolumeParams> {
  150. return VolumeRepresentation('Direct Volume', ctx, getParams, DirectVolumeVisual)
  151. }
  152. export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
  153. name: 'direct-volume',
  154. label: 'Direct Volume',
  155. description: 'Direct volume rendering of volumetric data.',
  156. factory: DirectVolumeRepresentation,
  157. getParams: getDirectVolumeParams,
  158. defaultValues: PD.getDefaultValues(DirectVolumeParams),
  159. defaultColorTheme: { name: 'uniform' },
  160. defaultSizeTheme: { name: 'uniform' },
  161. isApplicable: (volume: VolumeData) => volume.data.data.length > 0
  162. })