volume.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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 { Grid } from './grid';
  7. import { OrderedSet } from '../../mol-data/int';
  8. import { Sphere3D } from '../../mol-math/geometry';
  9. import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
  10. import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
  11. import { CubeFormat } from '../../mol-model-formats/volume/cube';
  12. import { equalEps } from '../../mol-math/linear-algebra/3d/common';
  13. import { ModelFormat } from '../../mol-model-formats/format';
  14. import { CustomProperties } from '../custom-property';
  15. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  16. import { toPrecision } from '../../mol-util/number';
  17. import { DscifFormat } from '../../mol-model-formats/volume/density-server';
  18. export interface Volume {
  19. readonly label?: string
  20. readonly entryId?: string,
  21. readonly grid: Grid
  22. readonly sourceData: ModelFormat
  23. // TODO use...
  24. customProperties: CustomProperties
  25. /**
  26. * Not to be accessed directly, each custom property descriptor
  27. * defines property accessors that use this field to store the data.
  28. */
  29. _propertyData: { [name: string]: any }
  30. // TODO add as customProperty?
  31. readonly colorVolume?: Volume
  32. }
  33. export namespace Volume {
  34. export function is(x: any): x is Volume {
  35. // TODO: improve
  36. return (
  37. x?.grid?.cells?.space?.dimensions?.length &&
  38. x?.sourceData &&
  39. x?.customProperties &&
  40. x?._propertyData
  41. );
  42. }
  43. export type CellIndex = { readonly '@type': 'cell-index' } & number
  44. export type IsoValue = IsoValue.Absolute | IsoValue.Relative
  45. export namespace IsoValue {
  46. export type Relative = Readonly<{ kind: 'relative', relativeValue: number }>
  47. export type Absolute = Readonly<{ kind: 'absolute', absoluteValue: number }>
  48. export function areSame(a: IsoValue, b: IsoValue, stats: Grid['stats']) {
  49. return equalEps(toAbsolute(a, stats).absoluteValue, toAbsolute(b, stats).absoluteValue, stats.sigma / 100);
  50. }
  51. export function absolute(value: number): Absolute { return { kind: 'absolute', absoluteValue: value }; }
  52. export function relative(value: number): Relative { return { kind: 'relative', relativeValue: value }; }
  53. export function calcAbsolute(stats: Grid['stats'], relativeValue: number): number {
  54. return relativeValue * stats.sigma + stats.mean;
  55. }
  56. export function calcRelative(stats: Grid['stats'], absoluteValue: number): number {
  57. return stats.sigma === 0 ? 0 : ((absoluteValue - stats.mean) / stats.sigma);
  58. }
  59. export function toAbsolute(value: IsoValue, stats: Grid['stats']): Absolute {
  60. return value.kind === 'absolute' ? value : { kind: 'absolute', absoluteValue: IsoValue.calcAbsolute(stats, value.relativeValue) };
  61. }
  62. export function toRelative(value: IsoValue, stats: Grid['stats']): Relative {
  63. return value.kind === 'relative' ? value : { kind: 'relative', relativeValue: IsoValue.calcRelative(stats, value.absoluteValue) };
  64. }
  65. export function toString(value: IsoValue) {
  66. return value.kind === 'relative'
  67. ? `${value.relativeValue.toFixed(2)} σ`
  68. : `${value.absoluteValue.toPrecision(4)}`;
  69. }
  70. }
  71. // Converts iso value to relative if using downsample VolumeServer data
  72. export function adjustedIsoValue(volume: Volume, value: number, kind: 'absolute' | 'relative') {
  73. if (kind === 'relative') return IsoValue.relative(value);
  74. const absolute = IsoValue.absolute(value);
  75. if (DscifFormat.is(volume.sourceData)) {
  76. const stats = {
  77. min: volume.sourceData.data.volume_data_3d_info.min_source.value(0),
  78. max: volume.sourceData.data.volume_data_3d_info.max_source.value(0),
  79. mean: volume.sourceData.data.volume_data_3d_info.mean_source.value(0),
  80. sigma: volume.sourceData.data.volume_data_3d_info.sigma_source.value(0),
  81. };
  82. return Volume.IsoValue.toRelative(absolute, stats);
  83. }
  84. return absolute;
  85. }
  86. const defaultStats: Grid['stats'] = { min: -1, max: 1, mean: 0, sigma: 0.1 };
  87. export function createIsoValueParam(defaultValue: Volume.IsoValue, stats?: Grid['stats']) {
  88. const sts = stats || defaultStats;
  89. const { min, max, mean, sigma } = sts;
  90. // using ceil/floor could lead to "ouf of bounds" when converting
  91. const relMin = (min - mean) / sigma;
  92. const relMax = (max - mean) / sigma;
  93. let def = defaultValue;
  94. if (defaultValue.kind === 'absolute') {
  95. if (defaultValue.absoluteValue < min) def = Volume.IsoValue.absolute(min);
  96. else if (defaultValue.absoluteValue > max) def = Volume.IsoValue.absolute(max);
  97. } else {
  98. if (defaultValue.relativeValue < relMin) def = Volume.IsoValue.relative(relMin);
  99. else if (defaultValue.relativeValue > relMax) def = Volume.IsoValue.relative(relMax);
  100. }
  101. return PD.Conditioned(
  102. def,
  103. {
  104. 'absolute': PD.Converted(
  105. (v: Volume.IsoValue) => Volume.IsoValue.toAbsolute(v, Grid.One.stats).absoluteValue,
  106. (v: number) => Volume.IsoValue.absolute(v),
  107. PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) }, { immediateUpdate: true })
  108. ),
  109. 'relative': PD.Converted(
  110. (v: Volume.IsoValue) => Volume.IsoValue.toRelative(v, Grid.One.stats).relativeValue,
  111. (v: number) => Volume.IsoValue.relative(v),
  112. PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) }, { immediateUpdate: true })
  113. )
  114. },
  115. (v: Volume.IsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
  116. (v: Volume.IsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? Volume.IsoValue.toAbsolute(v, sts) : Volume.IsoValue.toRelative(v, sts),
  117. { isEssential: true }
  118. );
  119. }
  120. export const IsoValueParam = createIsoValueParam(Volume.IsoValue.relative(2));
  121. export type IsoValueParam = typeof IsoValueParam
  122. export const One: Volume = {
  123. label: '',
  124. grid: Grid.One,
  125. sourceData: { kind: '', name: '', data: {} },
  126. customProperties: new CustomProperties(),
  127. _propertyData: Object.create(null),
  128. };
  129. export function areEquivalent(volA: Volume, volB: Volume) {
  130. return Grid.areEquivalent(volA.grid, volB.grid);
  131. }
  132. export function isEmpty(vol: Volume) {
  133. return Grid.isEmpty(vol.grid);
  134. }
  135. export function isOrbitals(volume: Volume) {
  136. if (!CubeFormat.is(volume.sourceData)) return false;
  137. return volume.sourceData.data.header.orbitals;
  138. }
  139. export interface Loci { readonly kind: 'volume-loci', readonly volume: Volume }
  140. export function Loci(volume: Volume): Loci { return { kind: 'volume-loci', volume }; }
  141. export function isLoci(x: any): x is Loci { return !!x && x.kind === 'volume-loci'; }
  142. export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume; }
  143. export function isLociEmpty(loci: Loci) { return Grid.isEmpty(loci.volume.grid); }
  144. export function getBoundingSphere(volume: Volume, boundingSphere?: Sphere3D) {
  145. return Grid.getBoundingSphere(volume.grid, boundingSphere);
  146. }
  147. export namespace Isosurface {
  148. export interface Loci { readonly kind: 'isosurface-loci', readonly volume: Volume, readonly isoValue: Volume.IsoValue }
  149. export function Loci(volume: Volume, isoValue: Volume.IsoValue): Loci { return { kind: 'isosurface-loci', volume, isoValue }; }
  150. export function isLoci(x: any): x is Loci { return !!x && x.kind === 'isosurface-loci'; }
  151. export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && Volume.IsoValue.areSame(a.isoValue, b.isoValue, a.volume.grid.stats); }
  152. export function isLociEmpty(loci: Loci) { return loci.volume.grid.cells.data.length === 0; }
  153. export function getBoundingSphere(volume: Volume, isoValue: Volume.IsoValue, boundingSphere?: Sphere3D) {
  154. // TODO get bounding sphere for subgrid with values >= isoValue
  155. return Volume.getBoundingSphere(volume, boundingSphere);
  156. }
  157. }
  158. export namespace Cell {
  159. export interface Loci { readonly kind: 'cell-loci', readonly volume: Volume, readonly indices: OrderedSet<CellIndex> }
  160. export function Loci(volume: Volume, indices: OrderedSet<CellIndex>): Loci { return { kind: 'cell-loci', volume, indices }; }
  161. export function isLoci(x: any): x is Loci { return !!x && x.kind === 'cell-loci'; }
  162. export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
  163. export function isLociEmpty(loci: Loci) { return OrderedSet.size(loci.indices) === 0; }
  164. const boundaryHelper = new BoundaryHelper('98');
  165. const tmpBoundaryPos = Vec3();
  166. export function getBoundingSphere(volume: Volume, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {
  167. boundaryHelper.reset();
  168. const transform = Grid.getGridToCartesianTransform(volume.grid);
  169. const { getCoords } = volume.grid.cells.space;
  170. for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
  171. const o = OrderedSet.getAt(indices, i);
  172. getCoords(o, tmpBoundaryPos);
  173. Vec3.transformMat4(tmpBoundaryPos, tmpBoundaryPos, transform);
  174. boundaryHelper.includePosition(tmpBoundaryPos);
  175. }
  176. boundaryHelper.finishedIncludeStep();
  177. for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
  178. const o = OrderedSet.getAt(indices, i);
  179. getCoords(o, tmpBoundaryPos);
  180. Vec3.transformMat4(tmpBoundaryPos, tmpBoundaryPos, transform);
  181. boundaryHelper.radiusPosition(tmpBoundaryPos);
  182. }
  183. const bs = boundaryHelper.getSphere(boundingSphere);
  184. return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10);
  185. }
  186. }
  187. export type PickingGranularity = 'volume' | 'object' | 'voxel';
  188. export const PickingGranularity = {
  189. set(volume: Volume, granularity: PickingGranularity) {
  190. volume._propertyData['__picking_granularity__'] = granularity;
  191. },
  192. get(volume: Volume): PickingGranularity {
  193. return volume._propertyData['__picking_granularity__'] ?? 'voxel';
  194. }
  195. };
  196. }