slice.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * Copyright (c) 2020-2022 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, key: number, 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, key: number, 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. } if (granularity === 'object') {
  135. return getLoci(volume, props);
  136. } else {
  137. return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
  138. }
  139. }
  140. return EmptyLoci;
  141. }
  142. function eachSlice(loci: Loci, volume: Volume, key: number, props: PD.Values<SliceParams>, apply: (interval: Interval) => boolean) {
  143. return eachVolumeLoci(loci, volume, undefined, apply);
  144. }
  145. //
  146. export const SliceParams = {
  147. ...Image.Params,
  148. quality: { ...Image.Params.quality, isEssential: false },
  149. dimension: PD.MappedStatic('x', {
  150. x: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
  151. y: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
  152. z: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
  153. }, { isEssential: true }),
  154. isoValue: Volume.IsoValueParam,
  155. };
  156. export type SliceParams = typeof SliceParams
  157. export function getSliceParams(ctx: ThemeRegistryContext, volume: Volume) {
  158. const p = PD.clone(SliceParams);
  159. const dim = volume.grid.cells.space.dimensions;
  160. p.dimension = PD.MappedStatic('x', {
  161. x: PD.Numeric(0, { min: 0, max: dim[0] - 1, step: 1 }),
  162. y: PD.Numeric(0, { min: 0, max: dim[1] - 1, step: 1 }),
  163. z: PD.Numeric(0, { min: 0, max: dim[2] - 1, step: 1 }),
  164. }, { isEssential: true });
  165. p.isoValue = Volume.createIsoValueParam(Volume.IsoValue.absolute(volume.grid.stats.min), volume.grid.stats);
  166. return p;
  167. }
  168. export function SliceVisual(materialId: number): VolumeVisual<SliceParams> {
  169. return VolumeVisual<Image, SliceParams>({
  170. defaultProps: PD.getDefaultValues(SliceParams),
  171. createGeometry: createImage,
  172. createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
  173. getLoci: getSliceLoci,
  174. eachLocation: eachSlice,
  175. setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>, newTheme: Theme, currentTheme: Theme) => {
  176. state.createGeometry = (
  177. newProps.dimension.name !== currentProps.dimension.name ||
  178. newProps.dimension.params !== currentProps.dimension.params ||
  179. !Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
  180. !ColorTheme.areEqual(newTheme.color, currentTheme.color)
  181. );
  182. },
  183. geometryUtils: {
  184. ...Image.Utils,
  185. createRenderableState: (props: PD.Values<SliceParams>) => {
  186. const state = Image.Utils.createRenderableState(props);
  187. updateRenderableState(state, props);
  188. return state;
  189. },
  190. updateRenderableState
  191. }
  192. }, materialId);
  193. }
  194. function updateRenderableState(state: RenderableState, props: PD.Values<SliceParams>) {
  195. Image.Utils.updateRenderableState(state, props);
  196. state.opaque = false;
  197. state.writeDepth = true;
  198. }
  199. export function SliceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, SliceParams>): VolumeRepresentation<SliceParams> {
  200. return VolumeRepresentation('Slice', ctx, getParams, SliceVisual, getLoci);
  201. }
  202. export const SliceRepresentationProvider = VolumeRepresentationProvider({
  203. name: 'slice',
  204. label: 'Slice',
  205. description: 'Slice of volume rendered as image with interpolation.',
  206. factory: SliceRepresentation,
  207. getParams: getSliceParams,
  208. defaultValues: PD.getDefaultValues(SliceParams),
  209. defaultColorTheme: { name: 'uniform' },
  210. defaultSizeTheme: { name: 'uniform' },
  211. isApplicable: (volume: Volume) => !Volume.isEmpty(volume) && !Volume.Segmentation.get(volume)
  212. });