transformers.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { PluginStateObject as SO, PluginStateTransform } from '../../../state/objects';
  7. import { VolumeServerInfo, VolumeServerHeader } from './model';
  8. import { ParamDefinition as PD } from 'mol-util/param-definition';
  9. import { Task } from 'mol-task';
  10. import { PluginContext } from 'mol-plugin/context';
  11. import { urlCombine } from 'mol-util/url';
  12. import { createIsoValueParam } from 'mol-repr/volume/isosurface';
  13. import { VolumeIsoValue } from 'mol-model/volume';
  14. import { StateAction, StateObject, StateTransformer } from 'mol-state';
  15. import { getStreamingMethod, getEmdbIdAndContourLevel } from './util';
  16. import { VolumeStreaming } from './behavior';
  17. import { VolumeRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation';
  18. import { BuiltInVolumeRepresentations } from 'mol-repr/volume/registry';
  19. import { createTheme } from 'mol-theme/theme';
  20. import { Box3D } from 'mol-math/geometry';
  21. import { Vec3 } from 'mol-math/linear-algebra';
  22. // import { PluginContext } from 'mol-plugin/context';
  23. export const InitVolumeStreaming = StateAction.build({
  24. display: { name: 'Volume Streaming' },
  25. from: SO.Molecule.Structure,
  26. params(a) {
  27. return {
  28. method: PD.Select<VolumeServerInfo.Kind>(getStreamingMethod(a && a.data), [['em', 'EM'], ['x-ray', 'X-Ray']]),
  29. id: PD.Text((a && a.data.models.length > 0 && a.data.models[0].label) || ''),
  30. serverUrl: PD.Text('https://ds.litemol.org')
  31. };
  32. },
  33. isApplicable: (a) => a.data.models.length === 1
  34. })(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
  35. // TODO: custom react view for this and the VolumeStreamingBehavior transformer
  36. let dataId = params.id.toLowerCase(), emDefaultContourLevel: number | undefined;
  37. if (params.method === 'em') {
  38. await taskCtx.update('Getting EMDB info...');
  39. const emInfo = await getEmdbIdAndContourLevel(plugin, taskCtx, dataId);
  40. dataId = emInfo.emdbId;
  41. emDefaultContourLevel = emInfo.contour;
  42. }
  43. const infoTree = state.build().to(ref)
  44. .apply(CreateVolumeStreamingInfo, {
  45. serverUrl: params.serverUrl,
  46. source: params.method === 'em'
  47. ? { name: 'em', params: { isoValue: VolumeIsoValue.absolute(emDefaultContourLevel || 0) } }
  48. : { name: 'x-ray', params: { } },
  49. dataId
  50. });
  51. const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
  52. const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
  53. PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data)));
  54. if (params.method === 'em') {
  55. behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true } });
  56. } else {
  57. behTree.apply(VolumeStreamingVisual, { channel: '2fo-fc' }, { state: { isGhost: true } });
  58. behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(+ve)' }, { state: { isGhost: true } });
  59. behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(-ve)' }, { state: { isGhost: true } });
  60. }
  61. await state.updateTree(behTree).runInContext(taskCtx);
  62. }));
  63. export const BoxifyVolumeStreaming = StateAction.build({
  64. display: { name: 'Boxify Volume Streaming', description: 'Make the current box permanent.' },
  65. from: VolumeStreaming,
  66. isApplicable: (a) => a.data.params.view.name === 'selection-box'
  67. })(({ a, ref, state }, plugin: PluginContext) => {
  68. const params = a.data.params;
  69. if (params.view.name !== 'selection-box') return;
  70. const box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
  71. const r = params.view.params.radius;
  72. Box3D.expand(box, box, Vec3.create(r, r, r));
  73. const newParams: VolumeStreaming.Params = {
  74. ...params,
  75. view: {
  76. name: 'box' as 'box',
  77. params: {
  78. bottomLeft: box.min,
  79. topRight: box.max
  80. }
  81. }
  82. };
  83. return state.updateTree(state.build().to(ref).update(newParams));
  84. });
  85. export { CreateVolumeStreamingInfo }
  86. type CreateVolumeStreamingInfo = typeof CreateVolumeStreamingInfo
  87. const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
  88. name: 'create-volume-streaming-info',
  89. display: { name: 'Volume Streaming Info' },
  90. from: SO.Molecule.Structure,
  91. to: VolumeServerInfo,
  92. params(a) {
  93. return {
  94. serverUrl: PD.Text('https://ds.litemol.org'),
  95. source: PD.MappedStatic('x-ray', {
  96. 'em': PD.Group({
  97. isoValue: createIsoValueParam(VolumeIsoValue.relative(1))
  98. }),
  99. 'x-ray': PD.Group({ })
  100. }),
  101. dataId: PD.Text('')
  102. };
  103. }
  104. })({
  105. apply: ({ a, params }, plugin: PluginContext) => Task.create('', async taskCtx => {
  106. const dataId = params.dataId;
  107. const emDefaultContourLevel = params.source.name === 'em' ? params.source.params.isoValue : VolumeIsoValue.relative(1);
  108. await taskCtx.update('Getting server header...');
  109. const header = await plugin.fetch<VolumeServerHeader>({ url: urlCombine(params.serverUrl, `${params.source.name}/${dataId.toLocaleLowerCase()}`), type: 'json' }).runInContext(taskCtx);
  110. const data: VolumeServerInfo.Data = {
  111. serverUrl: params.serverUrl,
  112. dataId,
  113. kind: params.source.name,
  114. header,
  115. emDefaultContourLevel,
  116. structure: a.data
  117. };
  118. return new VolumeServerInfo(data, { label: `Volume Server: ${dataId}` });
  119. })
  120. });
  121. export { CreateVolumeStreamingBehavior }
  122. type CreateVolumeStreamingBehavior = typeof CreateVolumeStreamingBehavior
  123. const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
  124. name: 'create-volume-streaming-behavior',
  125. display: { name: 'Volume Streaming Behavior' },
  126. from: VolumeServerInfo,
  127. to: VolumeStreaming,
  128. params(a) {
  129. return VolumeStreaming.createParams(a && a.data);
  130. }
  131. })({
  132. canAutoUpdate: ({ oldParams, newParams }) => {
  133. return oldParams.view === newParams.view
  134. || (oldParams.view.name === newParams.view.name && oldParams.view.name === 'selection-box');
  135. },
  136. apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
  137. const behavior = new VolumeStreaming.Behavior(plugin, a.data);
  138. await behavior.update(params);
  139. return new VolumeStreaming(behavior, { label: 'Volume Streaming', description: behavior.getDescription() });
  140. }),
  141. update({ b, newParams }) {
  142. return Task.create('Update Volume Streaming', async _ => {
  143. const ret = await b.data.update(newParams) ? StateTransformer.UpdateResult.Updated : StateTransformer.UpdateResult.Unchanged;
  144. b.description = b.data.getDescription();
  145. return ret;
  146. });
  147. }
  148. });
  149. export { VolumeStreamingVisual }
  150. type VolumeStreamingVisual = typeof VolumeStreamingVisual
  151. const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
  152. name: 'create-volume-streaming-visual',
  153. display: { name: 'Volume Streaming Visual' },
  154. from: VolumeStreaming,
  155. to: SO.Volume.Representation3D,
  156. params: {
  157. channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true })
  158. }
  159. })({
  160. apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
  161. const channel = a.data.channels[srcParams.channel];
  162. if (!channel) return StateObject.Null;
  163. const params = createVolumeProps(a.data, srcParams.channel);
  164. const provider = BuiltInVolumeRepresentations.isosurface;
  165. const props = params.type.params || {}
  166. const repr = provider.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
  167. repr.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: channel.data }, params))
  168. await repr.createOrUpdate(props, channel.data).runInContext(ctx);
  169. return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
  170. }),
  171. update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
  172. // TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
  173. const channel = a.data.channels[newParams.channel];
  174. // TODO: is this correct behavior?
  175. if (!channel) return StateTransformer.UpdateResult.Unchanged;
  176. const params = createVolumeProps(a.data, newParams.channel);
  177. const props = { ...b.data.repr.props, ...params.type.params };
  178. b.data.repr.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: channel.data }, params))
  179. await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
  180. return StateTransformer.UpdateResult.Updated;
  181. })
  182. });
  183. function createVolumeProps(streaming: VolumeStreaming.Behavior, channelName: VolumeStreaming.ChannelType) {
  184. const channel = streaming.channels[channelName]!;
  185. return VolumeRepresentation3DHelpers.getDefaultParamsStatic(streaming.plugin,
  186. 'isosurface', { isoValue: channel.isoValue, alpha: channel.opacity, visuals: channel.wireframe ? ['wireframe'] : ['solid'] },
  187. 'uniform', { value: channel.color });
  188. }