model.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { StateAction } from '../../../../mol-state';
  7. import { PluginContext } from '../../../../mol-plugin/context';
  8. import { PluginStateObject as PSO } from '../../../../mol-plugin/state/objects';
  9. import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
  10. import { Ingredient, CellPacking } from './data';
  11. import { getFromPdb, getFromCellPackDB } from './util';
  12. import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext } from '../../../../mol-model/structure';
  13. import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif';
  14. import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
  15. import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
  16. import { SymmetryOperator } from '../../../../mol-math/geometry';
  17. import { Task } from '../../../../mol-task';
  18. import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
  19. import { StateTransforms } from '../../../../mol-plugin/state/transforms';
  20. import { distinctColors } from '../../../../mol-util/color/distinct';
  21. import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
  22. import { Hcl } from '../../../../mol-util/color/spaces/hcl';
  23. import { ParseCellPack, StructureFromCellpack } from './state';
  24. import { formatMolScript } from '../../../../mol-script/language/expression-formatter';
  25. import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
  26. import { getMatFromResamplePoints } from './curve';
  27. import { compile } from '../../../../mol-script/runtime/query/compiler';
  28. import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
  29. import { ThemeRegistryContext } from '../../../../mol-theme/theme';
  30. import { ColorTheme } from '../../../../mol-theme/color';
  31. function getCellPackModelUrl(fileName: string, baseUrl: string) {
  32. return `${baseUrl}/cellPACK_database_1.1.0/results/${fileName}`
  33. }
  34. async function getModel(id: string, baseUrl: string) {
  35. let model: Model;
  36. if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
  37. // return
  38. const cif = await getFromPdb(id)
  39. model = (await trajectoryFromMmCIF(cif).run())[0]
  40. } else {
  41. const pdb = await getFromCellPackDB(id, baseUrl)
  42. model = (await trajectoryFromPDB(pdb).run())[0]
  43. }
  44. return model
  45. }
  46. async function getStructure(model: Model, props: { assembly?: string } = {}) {
  47. let structure = Structure.ofModel(model)
  48. const { assembly } = props
  49. if (assembly) {
  50. structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
  51. }
  52. const query = MS.struct.modifier.union([
  53. MS.struct.generator.atomGroups({
  54. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
  55. })
  56. ])
  57. const compiled = compile<StructureSelection>(query)
  58. const result = compiled(new QueryContext(structure))
  59. structure = StructureSelection.unionStructure(result)
  60. return structure
  61. }
  62. function getTransform(trans: Vec3, rot: Quat) {
  63. const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
  64. const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
  65. Mat4.transpose(m, m)
  66. Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
  67. Mat4.setTranslation(m, trans)
  68. return m
  69. }
  70. function getResultTransforms(results: Ingredient['results']) {
  71. return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
  72. }
  73. function getCurveTransforms(ingredient: Ingredient) {
  74. const n = ingredient.nbCurve || 0
  75. const instances: Mat4[] = []
  76. for (let i = 0; i < n; ++i) {
  77. const cname = `curve${i}`
  78. if (!(cname in ingredient)) {
  79. // console.warn(`Expected '${cname}' in ingredient`)
  80. continue
  81. }
  82. const _points = ingredient[cname] as Vec3[]
  83. if (_points.length <= 2) {
  84. // TODO handle curve with 2 or less points
  85. continue
  86. }
  87. const points = new Float32Array(_points.length * 3)
  88. for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3)
  89. const newInstances = getMatFromResamplePoints(points)
  90. instances.push(...newInstances)
  91. }
  92. return instances
  93. }
  94. function getAssembly(transforms: Mat4[], structure: Structure) {
  95. const builder = Structure.Builder()
  96. const { units } = structure;
  97. for (let i = 0, il = transforms.length; i < il; ++i) {
  98. const id = `${i + 1}`
  99. const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
  100. for (const unit of units) {
  101. builder.addWithOperator(unit, op)
  102. }
  103. }
  104. return builder.getStructure();
  105. }
  106. async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
  107. const { name, source, results, nbCurve } = ingredient
  108. // TODO can these be added to the library?
  109. if (name === 'HIV1_CAhex_0_1_0') return
  110. if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
  111. if (name === 'iLDL') return
  112. if (name === 'peptides') return
  113. if (name === 'lypoglycane') return
  114. if (source.pdb === 'None') return
  115. const model = await getModel(source.pdb || name, baseUrl)
  116. if (!model) return
  117. const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
  118. const transforms = nbCurve ? getCurveTransforms(ingredient) : getResultTransforms(results)
  119. const assembly = getAssembly(transforms, structure)
  120. return assembly
  121. }
  122. export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
  123. return Task.create('Create Packing Structure', async ctx => {
  124. const { ingredients, name } = packing
  125. const structures: Structure[] = []
  126. for (const iName in ingredients) {
  127. if (ctx.shouldUpdate) ctx.update(iName)
  128. const s = await getIngredientStructure(ingredients[iName], baseUrl)
  129. if (s) structures.push(s)
  130. }
  131. const builder = Structure.Builder({ label: name })
  132. let offsetInvariantId = 0
  133. for (const s of structures) {
  134. let maxInvariantId = 0
  135. for (const u of s.units) {
  136. const invariantId = u.invariantId + offsetInvariantId
  137. if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
  138. builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, invariantId)
  139. }
  140. offsetInvariantId += maxInvariantId
  141. }
  142. const s = builder.getStructure()
  143. return s
  144. })
  145. }
  146. export const LoadCellPackModel = StateAction.build({
  147. display: { name: 'Load CellPack Model' },
  148. params: {
  149. id: PD.Select('influenza_model1.json', [
  150. ['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
  151. ['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
  152. ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
  153. ['influenza_model1.json', 'influenza_model1'],
  154. ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
  155. ]),
  156. baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master'),
  157. preset: PD.Group({
  158. traceOnly: PD.Boolean(false),
  159. representation: PD.Select('spacefill', [
  160. ['spacefill', 'Spacefill'],
  161. ['gaussian-surface', 'Gaussian Surface'],
  162. ['point', 'Point'],
  163. ])
  164. }, { isExpanded: true })
  165. },
  166. from: PSO.Root
  167. })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
  168. const url = getCellPackModelUrl(params.id, params.baseUrl)
  169. const root = state.build().toRoot();
  170. const cellPackBuilder = root
  171. .apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
  172. .apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
  173. .apply(ParseCellPack)
  174. const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
  175. const { packings } = cellPackObject.data
  176. let tree = state.build().to(cellPackBuilder.ref);
  177. const isHiv = (
  178. params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
  179. params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
  180. )
  181. if (isHiv) {
  182. for (let i = 0, il = packings.length; i < il; ++i) {
  183. if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
  184. const url = `${params.baseUrl}/cellPACK_database_1.1.0/extras/rna_allpoints.json`
  185. const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
  186. const { points } = await (new Response(data)).json() as { points: number[] }
  187. const curve0: Vec3[] = []
  188. for (let j = 0, jl = points.length; j < jl; j += 3) {
  189. curve0.push(Vec3.fromArray(Vec3(), points, j))
  190. }
  191. packings[i].ingredients['RNA'] = {
  192. source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
  193. results: [],
  194. name: 'RNA',
  195. nbCurve: 1,
  196. curve0
  197. }
  198. }
  199. }
  200. }
  201. const colors = distinctColors(packings.length)
  202. for (let i = 0, il = packings.length; i < il; ++i) {
  203. const hcl = Hcl.fromColor(Hcl(), colors[i])
  204. const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
  205. const p = { packing: i, baseUrl: params.baseUrl }
  206. const expression = params.preset.traceOnly
  207. ? MS.struct.generator.atomGroups({
  208. 'atom-test': MS.core.logic.or([
  209. MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
  210. MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
  211. ])
  212. })
  213. : MS.struct.generator.all()
  214. const query = { language: 'mol-script' as const, expression: formatMolScript(expression) }
  215. tree.apply(StructureFromCellpack, p)
  216. .apply(StateTransforms.Model.UserStructureSelection, { query })
  217. .apply(StateTransforms.Representation.StructureRepresentation3D,
  218. StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
  219. repr: getReprParams(ctx, params.preset),
  220. color: getColorParams(hue)
  221. })
  222. )
  223. }
  224. if (isHiv) {
  225. const url = `${params.baseUrl}/cellPACK_database_1.1.0/membranes/hiv_lipids.bcif`
  226. tree.apply(StateTransforms.Data.Download, { url }, { state: { isGhost: true } })
  227. .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
  228. .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
  229. .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
  230. .apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
  231. .apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
  232. .apply(StateTransforms.Representation.StructureRepresentation3D,
  233. StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
  234. repr: getReprParams(ctx, params.preset),
  235. color: UniformColorThemeProvider
  236. })
  237. )
  238. }
  239. await state.updateTree(tree).runInContext(taskCtx);
  240. }));
  241. function getReprParams(ctx: PluginContext, params: { representation: 'spacefill' | 'gaussian-surface' | 'point', traceOnly: boolean }) {
  242. const { representation, traceOnly } = params
  243. switch (representation) {
  244. case 'spacefill':
  245. return traceOnly
  246. ? [
  247. ctx.structureRepresentation.registry.get('spacefill'),
  248. () => ({ sizeFactor: 2, ignoreHydrogens: true })
  249. ] as [any, any]
  250. : [
  251. ctx.structureRepresentation.registry.get('spacefill'),
  252. () => ({ ignoreHydrogens: true })
  253. ] as [any, any]
  254. case 'gaussian-surface':
  255. return [
  256. ctx.structureRepresentation.registry.get('gaussian-surface'),
  257. () => ({
  258. quality: 'custom', resolution: 10, radiusOffset: 2,
  259. alpha: 1.0, flatShaded: false, doubleSided: false,
  260. ignoreHydrogens: true
  261. })
  262. ] as [any, any]
  263. case 'point':
  264. return [
  265. ctx.structureRepresentation.registry.get('point'),
  266. () => ({ ignoreHydrogens: true })
  267. ] as [any, any]
  268. }
  269. }
  270. function getColorParams(hue: [number, number]) {
  271. return [
  272. ModelIndexColorThemeProvider,
  273. (c: ColorTheme.Provider<any>, ctx: ThemeRegistryContext) => {
  274. return {
  275. palette: {
  276. name: 'generate',
  277. params: {
  278. hue, chroma: [30, 80], luminance: [15, 85],
  279. clusteringStepCount: 50, minSampleCount: 800,
  280. maxCount: 75
  281. }
  282. }
  283. }
  284. }
  285. ] as [any, any]
  286. }