slice.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /**
  2. * Copyright (c) 2020 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 { Image } from '../../mol-geo/geometry/image/image';
  8. import { ThemeRegistryContext, Theme } from '../../mol-theme/theme';
  9. import { Grid, Volume } from '../../mol-model/volume';
  10. import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
  11. import { LocationIterator } from '../../mol-geo/util/location-iterator';
  12. import { VisualUpdateState } from '../util';
  13. import { NullLocation } from '../../mol-model/location';
  14. import { RepresentationContext, RepresentationParamsGetter } from '../representation';
  15. import { VisualContext } from '../visual';
  16. import { PickingId } from '../../mol-geo/geometry/picking';
  17. import { EmptyLoci, Loci } from '../../mol-model/loci';
  18. import { Interval, SortedArray } from '../../mol-data/int';
  19. import { transformPositionArray } from '../../mol-geo/util';
  20. import { RenderableState } from '../../mol-gl/renderable';
  21. import { Color } from '../../mol-util/color';
  22. import { ColorTheme } from '../../mol-theme/color';
  23. import { packIntToRGBArray } from '../../mol-util/number-packing';
  24. import { eachVolumeLoci } from './util';
  25. export async function createImage(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
  26. const { dimension: { name: dim }, isoValue } = props;
  27. const { space, data } = volume.grid.cells;
  28. const { min, max } = volume.grid.stats;
  29. const isoVal = Volume.IsoValue.toAbsolute(isoValue, volume.grid.stats).absoluteValue;
  30. // TODO more color themes
  31. const color = 'color' in theme.color ? theme.color.color(NullLocation, false) : Color(0xffffff);
  32. const [r, g, b] = Color.toRgbNormalized(color);
  33. const {
  34. width, height,
  35. x, y, z,
  36. x0, y0, z0,
  37. nx, ny, nz
  38. } = getSliceInfo(volume.grid, props);
  39. const corners = new Float32Array(
  40. dim === 'x' ? [x, 0, 0, x, y, 0, x, 0, z, x, y, z] :
  41. dim === 'y' ? [0, y, 0, x, y, 0, 0, y, z, x, y, z] :
  42. [0, 0, z, 0, y, z, x, 0, z, x, y, z]
  43. );
  44. const imageArray = new Uint8Array(width * height * 4);
  45. const groupArray = getPackedGroupArray(volume.grid, props);
  46. let i = 0;
  47. for (let iy = y0; iy < ny; ++iy) {
  48. for (let ix = x0; ix < nx; ++ix) {
  49. for (let iz = z0; iz < nz; ++iz) {
  50. const val = space.get(data, ix, iy, iz);
  51. const normVal = (val - min) / (max - min);
  52. imageArray[i] = r * normVal * 2 * 255;
  53. imageArray[i + 1] = g * normVal * 2 * 255;
  54. imageArray[i + 2] = b * normVal * 2 * 255;
  55. imageArray[i + 3] = val >= isoVal ? 255 : 0;
  56. i += 4;
  57. }
  58. }
  59. }
  60. const imageTexture = { width, height, array: imageArray, flipY: true };
  61. const groupTexture = { width, height, array: groupArray, flipY: true };
  62. const transform = Grid.getGridToCartesianTransform(volume.grid);
  63. transformPositionArray(transform, corners, 0, 4);
  64. return Image.create(imageTexture, corners, groupTexture, image);
  65. }
  66. function getSliceInfo(grid: Grid, props: PD.Values<SliceParams>) {
  67. const { dimension: { name: dim, params: index } } = props;
  68. const { space } = grid.cells;
  69. let width, height;
  70. let x, y, z;
  71. let x0 = 0, y0 = 0, z0 = 0;
  72. let [nx, ny, nz] = space.dimensions;
  73. if (dim === 'x') {
  74. x = index, y = ny - 1, z = nz - 1;
  75. width = nz, height = ny;
  76. x0 = x, nx = x0 + 1;
  77. } else if (dim === 'y') {
  78. x = nx - 1, y = index, z = nz - 1;
  79. width = nz, height = nx;
  80. y0 = y, ny = y0 + 1;
  81. } else {
  82. x = nx - 1, y = ny - 1, z = index;
  83. width = nx, height = ny;
  84. z0 = z, nz = z0 + 1;
  85. }
  86. return {
  87. width, height,
  88. x, y, z,
  89. x0, y0, z0,
  90. nx, ny, nz
  91. };
  92. }
  93. function getPackedGroupArray(grid: Grid, props: PD.Values<SliceParams>) {
  94. const { space } = grid.cells;
  95. const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props);
  96. const groupArray = new Uint8Array(width * height * 4);
  97. let j = 0;
  98. for (let iy = y0; iy < ny; ++iy) {
  99. for (let ix = x0; ix < nx; ++ix) {
  100. for (let iz = z0; iz < nz; ++iz) {
  101. packIntToRGBArray(space.dataOffset(ix, iy, iz), groupArray, j);
  102. j += 4;
  103. }
  104. }
  105. }
  106. return groupArray;
  107. }
  108. function getGroupArray(grid: Grid, props: PD.Values<SliceParams>) {
  109. const { space } = grid.cells;
  110. const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props);
  111. const groupArray = new Uint32Array(width * height);
  112. let j = 0;
  113. for (let iy = y0; iy < ny; ++iy) {
  114. for (let ix = x0; ix < nx; ++ix) {
  115. for (let iz = z0; iz < nz; ++iz) {
  116. groupArray[j] = space.dataOffset(ix, iy, iz);
  117. j += 1;
  118. }
  119. }
  120. }
  121. return groupArray;
  122. }
  123. function getLoci(volume: Volume, props: PD.Values<SliceParams>) {
  124. // TODO cache somehow?
  125. const groupArray = getGroupArray(volume.grid, props);
  126. return Volume.Cell.Loci(volume, SortedArray.ofUnsortedArray(groupArray));
  127. }
  128. function getSliceLoci(pickingId: PickingId, volume: Volume, props: PD.Values<SliceParams>, id: number) {
  129. const { objectId, groupId } = pickingId;
  130. if (id === objectId) {
  131. const granularity = Volume.PickingGranularity.get(volume);
  132. if (granularity === 'volume') {
  133. return Volume.Loci(volume);
  134. } else {
  135. return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
  136. }
  137. }
  138. return EmptyLoci;
  139. }
  140. function eachSlice(loci: Loci, volume: Volume, props: PD.Values<SliceParams>, apply: (interval: Interval) => boolean) {
  141. return eachVolumeLoci(loci, volume, undefined, apply);
  142. }
  143. //
  144. export const SliceParams = {
  145. ...Image.Params,
  146. quality: { ...Image.Params.quality, isEssential: false },
  147. dimension: PD.MappedStatic('x', {
  148. x: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
  149. y: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
  150. z: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
  151. }, { isEssential: true }),
  152. isoValue: Volume.IsoValueParam,
  153. };
  154. export type SliceParams = typeof SliceParams
  155. export function getSliceParams(ctx: ThemeRegistryContext, volume: Volume) {
  156. const p = PD.clone(SliceParams);
  157. const dim = volume.grid.cells.space.dimensions;
  158. p.dimension = PD.MappedStatic('x', {
  159. x: PD.Numeric(0, { min: 0, max: dim[0] - 1, step: 1 }),
  160. y: PD.Numeric(0, { min: 0, max: dim[1] - 1, step: 1 }),
  161. z: PD.Numeric(0, { min: 0, max: dim[2] - 1, step: 1 }),
  162. }, { isEssential: true });
  163. p.isoValue = Volume.createIsoValueParam(Volume.IsoValue.absolute(volume.grid.stats.min), volume.grid.stats);
  164. return p;
  165. }
  166. export function SliceVisual(materialId: number): VolumeVisual<SliceParams> {
  167. return VolumeVisual<Image, SliceParams>({
  168. defaultProps: PD.getDefaultValues(SliceParams),
  169. createGeometry: createImage,
  170. createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
  171. getLoci: getSliceLoci,
  172. eachLocation: eachSlice,
  173. setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>, newTheme: Theme, currentTheme: Theme) => {
  174. state.createGeometry = (
  175. newProps.dimension.name !== currentProps.dimension.name ||
  176. newProps.dimension.params !== currentProps.dimension.params ||
  177. !Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
  178. !ColorTheme.areEqual(newTheme.color, currentTheme.color)
  179. );
  180. },
  181. geometryUtils: {
  182. ...Image.Utils,
  183. createRenderableState: (props: PD.Values<SliceParams>) => {
  184. const state = Image.Utils.createRenderableState(props);
  185. updateRenderableState(state, props);
  186. return state;
  187. },
  188. updateRenderableState
  189. }
  190. }, materialId);
  191. }
  192. function updateRenderableState(state: RenderableState, props: PD.Values<SliceParams>) {
  193. Image.Utils.updateRenderableState(state, props);
  194. state.opaque = false;
  195. state.writeDepth = true;
  196. }
  197. export function SliceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, SliceParams>): VolumeRepresentation<SliceParams> {
  198. return VolumeRepresentation('Slice', ctx, getParams, SliceVisual, getLoci);
  199. }
  200. export const SliceRepresentationProvider = VolumeRepresentationProvider({
  201. name: 'slice',
  202. label: 'Slice',
  203. description: 'Slice of volume rendered as image with interpolation.',
  204. factory: SliceRepresentation,
  205. getParams: getSliceParams,
  206. defaultValues: PD.getDefaultValues(SliceParams),
  207. defaultColorTheme: { name: 'uniform' },
  208. defaultSizeTheme: { name: 'uniform' },
  209. isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
  210. });