model.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { PluginStateTransform } from '../objects';
  8. import { PluginStateObject as SO } from '../objects';
  9. import { Task, RuntimeContext } from 'mol-task';
  10. import { Model, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection as Sel, StructureQuery, Queries } from 'mol-model/structure';
  11. import { ParamDefinition as PD } from 'mol-util/param-definition';
  12. import Expression from 'mol-script/language/expression';
  13. import { compile } from 'mol-script/runtime/query/compiler';
  14. import { MolScriptBuilder } from 'mol-script/language/builder';
  15. import { StateObject } from 'mol-state';
  16. import { PluginContext } from 'mol-plugin/context';
  17. import { stringToWords } from 'mol-util/string';
  18. import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4';
  19. import { Vec3 } from 'mol-math/linear-algebra';
  20. import { volumeFromDsn6 } from 'mol-model/volume/formats/dsn6';
  21. import { parse_mmCIF } from 'mol-model-parsers/structure/mmcif';
  22. import { ModelFormat } from 'mol-model-parsers/structure/format';
  23. export { TrajectoryFromMmCif }
  24. type TrajectoryFromMmCif = typeof TrajectoryFromMmCif
  25. const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
  26. name: 'trajectory-from-mmcif',
  27. display: { name: 'Trajectory from mmCIF', description: 'Identify and create all separate models in the specified CIF data block' },
  28. from: SO.Format.Cif,
  29. to: SO.Molecule.Trajectory,
  30. params(a) {
  31. if (!a) {
  32. return {
  33. blockHeader: PD.makeOptional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.' }))
  34. };
  35. }
  36. const { blocks } = a.data;
  37. return {
  38. blockHeader: PD.makeOptional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' }))
  39. };
  40. }
  41. })({
  42. isApplicable: a => a.data.blocks.length > 0,
  43. apply({ a, params }) {
  44. return Task.create('Parse mmCIF', async ctx => {
  45. const header = params.blockHeader || a.data.blocks[0].header;
  46. const block = a.data.blocks.find(b => b.header === header);
  47. if (!block) throw new Error(`Data block '${[header]}' not found.`);
  48. const models = await parse_mmCIF(ModelFormat.mmCIF(block)).runInContext(ctx);
  49. if (models.length === 0) throw new Error('No models found.');
  50. const props = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
  51. return new SO.Molecule.Trajectory(models, props);
  52. });
  53. }
  54. });
  55. export { ModelFromTrajectory }
  56. const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
  57. type ModelFromTrajectory = typeof ModelFromTrajectory
  58. const ModelFromTrajectory = PluginStateTransform.BuiltIn({
  59. name: 'model-from-trajectory',
  60. display: { name: 'Model from Trajectory', description: 'Create a molecular structure from the specified model.' },
  61. from: SO.Molecule.Trajectory,
  62. to: SO.Molecule.Model,
  63. params: a => {
  64. if (!a) {
  65. return { modelIndex: PD.Numeric(0, {}, { description: 'Zero-based index of the model' }) };
  66. }
  67. return { modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.length, step: 1 }, { description: 'Model Index' })) }
  68. }
  69. })({
  70. isApplicable: a => a.data.length > 0,
  71. apply({ a, params }) {
  72. if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`);
  73. const model = a.data[params.modelIndex];
  74. const props = { label: `Model ${model.modelNum}` };
  75. return new SO.Molecule.Model(model, props);
  76. }
  77. });
  78. export { StructureFromModel }
  79. type StructureFromModel = typeof StructureFromModel
  80. const StructureFromModel = PluginStateTransform.BuiltIn({
  81. name: 'structure-from-model',
  82. display: { name: 'Structure from Model', description: 'Create a molecular structure from the specified model.' },
  83. from: SO.Molecule.Model,
  84. to: SO.Molecule.Structure
  85. })({
  86. apply({ a }) {
  87. let s = Structure.ofModel(a.data);
  88. const props = { label: a.data.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
  89. return new SO.Molecule.Structure(s, props);
  90. }
  91. });
  92. function structureDesc(s: Structure) {
  93. return s.elementCount === 1 ? '1 element' : `${s.elementCount} elements`;
  94. }
  95. export { StructureAssemblyFromModel }
  96. type StructureAssemblyFromModel = typeof StructureAssemblyFromModel
  97. const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
  98. name: 'structure-assembly-from-model',
  99. display: { name: 'Structure Assembly', description: 'Create a molecular structure assembly.' },
  100. from: SO.Molecule.Model,
  101. to: SO.Molecule.Structure,
  102. params(a) {
  103. if (!a) {
  104. return { id: PD.makeOptional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. If none specified (undefined or empty string), the asymmetric unit is used.' })) };
  105. }
  106. const model = a.data;
  107. const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]);
  108. if (!ids.length) ids.push(['deposited', 'Deposited'])
  109. return { id: PD.makeOptional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' })) };
  110. }
  111. })({
  112. apply({ a, params }, plugin: PluginContext) {
  113. return Task.create('Build Assembly', async ctx => {
  114. const model = a.data;
  115. const id = params.id;
  116. const asm = ModelSymmetry.findAssembly(model, id || '');
  117. if (!!id && id !== 'deposited' && !asm) throw new Error(`Assembly '${id}' not found`);
  118. const base = Structure.ofModel(model);
  119. if (!asm) {
  120. plugin.log.warn(`Model '${a.label}' has no assembly, returning deposited structure.`);
  121. const label = { label: a.data.label, description: structureDesc(base) };
  122. return new SO.Molecule.Structure(base, label);
  123. }
  124. const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
  125. const props = { label: `Assembly ${id}`, description: structureDesc(s) };
  126. return new SO.Molecule.Structure(s, props);
  127. })
  128. }
  129. });
  130. export { StructureSelection }
  131. type StructureSelection = typeof StructureSelection
  132. const StructureSelection = PluginStateTransform.BuiltIn({
  133. name: 'structure-selection',
  134. display: { name: 'Structure Selection', description: 'Create a molecular structure from the specified query expression.' },
  135. from: SO.Molecule.Structure,
  136. to: SO.Molecule.Structure,
  137. params: {
  138. query: PD.Value<Expression>(MolScriptBuilder.struct.generator.all, { isHidden: true }),
  139. label: PD.makeOptional(PD.Text('', { isHidden: true }))
  140. }
  141. })({
  142. apply({ a, params }) {
  143. // TODO: use cache, add "update"
  144. const compiled = compile<Sel>(params.query);
  145. const result = compiled(new QueryContext(a.data));
  146. const s = Sel.unionStructure(result);
  147. const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
  148. return new SO.Molecule.Structure(s, props);
  149. }
  150. });
  151. export { StructureComplexElement }
  152. namespace StructureComplexElement {
  153. export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres'
  154. }
  155. const StructureComplexElementTypes: [StructureComplexElement.Types, StructureComplexElement.Types][] = ['atomic-sequence', 'water', 'atomic-het', 'spheres'].map(t => [t, t] as any);
  156. type StructureComplexElement = typeof StructureComplexElement
  157. const StructureComplexElement = PluginStateTransform.BuiltIn({
  158. name: 'structure-complex-element',
  159. display: { name: 'Complex Element', description: 'Create a molecular structure from the specified model.' },
  160. from: SO.Molecule.Structure,
  161. to: SO.Molecule.Structure,
  162. params: { type: PD.Select<StructureComplexElement.Types>('atomic-sequence', StructureComplexElementTypes, { isHidden: true }) }
  163. })({
  164. apply({ a, params }) {
  165. // TODO: update function.
  166. let query: StructureQuery, label: string;
  167. switch (params.type) {
  168. case 'atomic-sequence': query = Queries.internal.atomicSequence(); label = 'Sequence'; break;
  169. case 'water': query = Queries.internal.water(); label = 'Water'; break;
  170. case 'atomic-het': query = Queries.internal.atomicHet(); label = 'HET Groups/Ligands'; break;
  171. case 'spheres': query = Queries.internal.spheres(); label = 'Coarse Spheres'; break;
  172. default: throw new Error(`${params.type} is a not valid complex element.`);
  173. }
  174. const result = query(new QueryContext(a.data));
  175. const s = Sel.unionStructure(result);
  176. if (s.elementCount === 0) return StateObject.Null;
  177. return new SO.Molecule.Structure(s, { label, description: structureDesc(s) });
  178. }
  179. });
  180. export { CustomModelProperties }
  181. type CustomModelProperties = typeof CustomModelProperties
  182. const CustomModelProperties = PluginStateTransform.BuiltIn({
  183. name: 'custom-model-properties',
  184. display: { name: 'Custom Model Properties' },
  185. from: SO.Molecule.Model,
  186. to: SO.Molecule.Model,
  187. params: (a, ctx: PluginContext) => {
  188. if (!a) return { properties: PD.MultiSelect([], [], { description: 'A list of property descriptor ids.' }) };
  189. return { properties: ctx.customModelProperties.getSelect(a.data) };
  190. }
  191. })({
  192. apply({ a, params }, ctx: PluginContext) {
  193. return Task.create('Custom Props', async taskCtx => {
  194. await attachProps(a.data, ctx, taskCtx, params.properties);
  195. return new SO.Molecule.Model(a.data, { label: 'Props', description: `${params.properties.length} Selected` });
  196. });
  197. }
  198. });
  199. async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, names: string[]) {
  200. for (const name of names) {
  201. const p = ctx.customModelProperties.get(name);
  202. await p.attach(model).runInContext(taskCtx);
  203. }
  204. }
  205. //
  206. export { VolumeFromCcp4 }
  207. type VolumeFromCcp4 = typeof VolumeFromCcp4
  208. const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
  209. name: 'volume-from-ccp4',
  210. display: { name: 'Volume from CCP4/MRC/MAP', description: 'Create Volume from CCP4/MRC/MAP data' },
  211. from: SO.Format.Ccp4,
  212. to: SO.Volume.Data,
  213. params(a) {
  214. return {
  215. voxelSize: PD.Vec3(Vec3.create(1, 1, 1))
  216. };
  217. }
  218. })({
  219. apply({ a, params }) {
  220. return Task.create('Create volume from CCP4/MRC/MAP', async ctx => {
  221. const volume = await volumeFromCcp4(a.data, params).runInContext(ctx)
  222. const props = { label: 'Volume' };
  223. return new SO.Volume.Data(volume, props);
  224. });
  225. }
  226. });
  227. export { VolumeFromDsn6 }
  228. type VolumeFromDsn6 = typeof VolumeFromDsn6
  229. const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
  230. name: 'volume-from-dsn6',
  231. display: { name: 'Volume from DSN6/BRIX', description: 'Create Volume from DSN6/BRIX data' },
  232. from: SO.Format.Dsn6,
  233. to: SO.Volume.Data,
  234. params(a) {
  235. return {
  236. voxelSize: PD.Vec3(Vec3.create(1, 1, 1))
  237. };
  238. }
  239. })({
  240. apply({ a, params }) {
  241. return Task.create('Create volume from DSN6/BRIX', async ctx => {
  242. const volume = await volumeFromDsn6(a.data, params).runInContext(ctx)
  243. const props = { label: 'Volume' };
  244. return new SO.Volume.Data(volume, props);
  245. });
  246. }
  247. });