model.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { StateAction, StateBuilder, StateTransformer, State } 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, IngredientFiles, parseCif, parsePDBfile } from './util';
  12. import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
  13. import { trajectoryFromMmCIF, MmcifFormat } 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, RuntimeContext } from '../../../../mol-task';
  18. import { StateTransforms } from '../../../../mol-plugin-state/transforms';
  19. import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
  20. import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
  21. import { getMatFromResamplePoints } from './curve';
  22. import { compile } from '../../../../mol-script/runtime/query/compiler';
  23. import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
  24. import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
  25. import { Column } from '../../../../mol-data/db';
  26. import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
  27. import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
  28. import { AjaxTask } from '../../../../mol-util/data-source';
  29. import { CellPackInfoProvider } from './property';
  30. import { CellPackColorThemeProvider } from './color';
  31. function getCellPackModelUrl(fileName: string, baseUrl: string) {
  32. return `${baseUrl}/results/${fileName}`
  33. }
  34. async function getModel(id: string, baseUrl: string, file?: File) {
  35. let model: Model;
  36. if (file) {
  37. const text = await file.text()
  38. if (file.name.endsWith('.cif')) {
  39. const cif = (await parseCif(text)).blocks[0]
  40. model = (await trajectoryFromMmCIF(cif).run())[0]
  41. } else if (file.name.endsWith('.pdb')) {
  42. const pdb = await parsePDBfile(text, id)
  43. model = (await trajectoryFromPDB(pdb).run())[0]
  44. } else {
  45. throw new Error(`unsupported file type '${file.name}'`)
  46. }
  47. } else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
  48. // return
  49. const cif = await getFromPdb(id)
  50. model = (await trajectoryFromMmCIF(cif).run())[0]
  51. } else {
  52. const pdb = await getFromCellPackDB(id, baseUrl)
  53. model = (await trajectoryFromPDB(pdb).run())[0]
  54. }
  55. return model
  56. }
  57. async function getStructure(model: Model, props: { assembly?: string } = {}) {
  58. let structure = Structure.ofModel(model)
  59. const { assembly } = props
  60. if (assembly) {
  61. structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
  62. }
  63. const query = MS.struct.modifier.union([
  64. MS.struct.generator.atomGroups({
  65. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
  66. })
  67. ])
  68. const compiled = compile<StructureSelection>(query)
  69. const result = compiled(new QueryContext(structure))
  70. structure = StructureSelection.unionStructure(result)
  71. return structure
  72. }
  73. function getTransform(trans: Vec3, rot: Quat) {
  74. const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
  75. const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
  76. Mat4.transpose(m, m)
  77. Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
  78. Mat4.setTranslation(m, trans)
  79. return m
  80. }
  81. function getResultTransforms(results: Ingredient['results']) {
  82. return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
  83. }
  84. function getCurveTransforms(ingredient: Ingredient) {
  85. const n = ingredient.nbCurve || 0
  86. const instances: Mat4[] = []
  87. for (let i = 0; i < n; ++i) {
  88. const cname = `curve${i}`
  89. if (!(cname in ingredient)) {
  90. // console.warn(`Expected '${cname}' in ingredient`)
  91. continue
  92. }
  93. const _points = ingredient[cname] as Vec3[]
  94. if (_points.length <= 2) {
  95. // TODO handle curve with 2 or less points
  96. continue
  97. }
  98. const points = new Float32Array(_points.length * 3)
  99. for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3)
  100. const newInstances = getMatFromResamplePoints(points)
  101. instances.push(...newInstances)
  102. }
  103. return instances
  104. }
  105. function getAssembly(transforms: Mat4[], structure: Structure) {
  106. const builder = Structure.Builder()
  107. const { units } = structure;
  108. for (let i = 0, il = transforms.length; i < il; ++i) {
  109. const id = `${i + 1}`
  110. const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } })
  111. for (const unit of units) {
  112. builder.addWithOperator(unit, op)
  113. }
  114. }
  115. return builder.getStructure();
  116. }
  117. function getCifCurve(name: string, transforms: Mat4[], model: Model) {
  118. if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed')
  119. const { db } = model.sourceData.data
  120. const d = db.atom_site
  121. const n = d._rowCount
  122. const rowCount = n * transforms.length
  123. const { offsets, count } = model.atomicHierarchy.chainAtomSegments
  124. const x = d.Cartn_x.toArray()
  125. const y = d.Cartn_y.toArray()
  126. const z = d.Cartn_z.toArray()
  127. const Cartn_x = new Float32Array(rowCount)
  128. const Cartn_y = new Float32Array(rowCount)
  129. const Cartn_z = new Float32Array(rowCount)
  130. const map = new Uint32Array(rowCount)
  131. const seq = new Int32Array(rowCount)
  132. let offset = 0
  133. for (let c = 0; c < count; ++c) {
  134. const cStart = offsets[c]
  135. const cEnd = offsets[c + 1]
  136. const cLength = cEnd - cStart
  137. for (let t = 0, tl = transforms.length; t < tl; ++t) {
  138. const m = transforms[t]
  139. for (let j = cStart; j < cEnd; ++j) {
  140. const i = offset + j - cStart
  141. const xj = x[j], yj = y[j], zj = z[j]
  142. Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12]
  143. Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13]
  144. Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14]
  145. map[i] = j
  146. seq[i] = t + 1
  147. }
  148. offset += cLength
  149. }
  150. }
  151. function multColumn<T>(column: Column<T>) {
  152. const array = column.toArray()
  153. return Column.ofLambda({
  154. value: row => array[map[row]],
  155. areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
  156. rowCount, schema: column.schema
  157. })
  158. }
  159. const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
  160. auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
  161. auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
  162. auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
  163. auth_seq_id: CifField.ofNumbers(seq),
  164. B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
  165. Cartn_x: CifField.ofNumbers(Cartn_x),
  166. Cartn_y: CifField.ofNumbers(Cartn_y),
  167. Cartn_z: CifField.ofNumbers(Cartn_z),
  168. group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
  169. id: CifField.ofColumn(Column.ofLambda({
  170. value: row => row,
  171. areValuesEqual: (rowA, rowB) => rowA === rowB,
  172. rowCount, schema: d.id.schema,
  173. })),
  174. label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
  175. label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
  176. label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
  177. label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
  178. label_seq_id: CifField.ofNumbers(seq),
  179. label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
  180. occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
  181. type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
  182. pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
  183. pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
  184. }
  185. const categories = {
  186. entity: CifCategory.ofTable('entity', db.entity),
  187. chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
  188. atom_site: CifCategory.ofFields('atom_site', _atom_site)
  189. }
  190. return {
  191. header: name,
  192. categoryNames: Object.keys(categories),
  193. categories
  194. };
  195. }
  196. async function getCurve(name: string, transforms: Mat4[], model: Model) {
  197. const cif = getCifCurve(name, transforms, model)
  198. const curveModelTask = Task.create('Curve Model', async ctx => {
  199. const format = MmcifFormat.fromFrame(cif)
  200. const models = await createModels(format.data.db, format, ctx)
  201. return models[0]
  202. })
  203. const curveModel = await curveModelTask.run()
  204. return getStructure(curveModel)
  205. }
  206. async function getIngredientStructure(ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) {
  207. const { name, source, results, nbCurve } = ingredient
  208. if (source.pdb === 'None') return
  209. const file = ingredientFiles[source.pdb]
  210. if (!file) {
  211. // TODO can these be added to the library?
  212. if (name === 'HIV1_CAhex_0_1_0') return
  213. if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
  214. if (name === 'iLDL') return
  215. if (name === 'peptides') return
  216. if (name === 'lypoglycane') return
  217. }
  218. const model = await getModel(source.pdb || name, baseUrl, file)
  219. if (!model) return
  220. if (nbCurve) {
  221. return getCurve(name, getCurveTransforms(ingredient), model)
  222. } else {
  223. const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
  224. return getAssembly(getResultTransforms(results), structure)
  225. }
  226. }
  227. export function createStructureFromCellPack(packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
  228. return Task.create('Create Packing Structure', async ctx => {
  229. const { ingredients, name } = packing
  230. const structures: Structure[] = []
  231. for (const iName in ingredients) {
  232. if (ctx.shouldUpdate) await ctx.update(iName)
  233. const s = await getIngredientStructure(ingredients[iName], baseUrl, ingredientFiles)
  234. if (s) structures.push(s)
  235. }
  236. if (ctx.shouldUpdate) await ctx.update(`${name} - units`)
  237. const builder = Structure.Builder({ label: name })
  238. let offsetInvariantId = 0
  239. for (const s of structures) {
  240. if (ctx.shouldUpdate) await ctx.update(`${s.label}`)
  241. let maxInvariantId = 0
  242. for (const u of s.units) {
  243. const invariantId = u.invariantId + offsetInvariantId
  244. if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
  245. builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
  246. }
  247. offsetInvariantId += maxInvariantId + 1
  248. }
  249. if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
  250. const s = builder.getStructure()
  251. for( let i = 0, il = s.models.length; i < il; ++i) {
  252. const { trajectoryInfo } = s.models[i]
  253. trajectoryInfo.size = il
  254. trajectoryInfo.index = i
  255. }
  256. return s
  257. })
  258. }
  259. async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, packings: CellPacking[], baseUrl: string) {
  260. for (let i = 0, il = packings.length; i < il; ++i) {
  261. if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
  262. const url = `${baseUrl}/extras/rna_allpoints.json`
  263. const data = await ctx.fetch({ url, type: 'string' }).runInContext(ctx.runtime);
  264. const { points } = await (new Response(data)).json() as { points: number[] }
  265. const curve0: Vec3[] = []
  266. for (let j = 0, jl = points.length; j < jl; j += 3) {
  267. curve0.push(Vec3.fromArray(Vec3(), points, j))
  268. }
  269. packings[i].ingredients['RNA'] = {
  270. source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
  271. results: [],
  272. name: 'RNA',
  273. nbCurve: 1,
  274. curve0
  275. }
  276. }
  277. }
  278. }
  279. async function loadHivMembrane(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
  280. const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
  281. const membraneBuilder = state.build().toRoot()
  282. .apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
  283. .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
  284. .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
  285. .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
  286. .apply(StateTransforms.Model.StructureFromModel)
  287. await state.updateTree(membraneBuilder).runInContext(runtime)
  288. const membraneParams = {
  289. representation: params.preset.representation,
  290. }
  291. const membrane = state.build().to(membraneBuilder.ref)
  292. await plugin.updateDataState(membrane, { revertOnError: true });
  293. await CellpackMembranePreset.apply(membrane.selector, membraneParams, plugin)
  294. }
  295. async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
  296. let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>
  297. if (params.source.name === 'id') {
  298. const url = getCellPackModelUrl(params.source.params, params.baseUrl)
  299. cellPackJson = state.build().toRoot()
  300. .apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } })
  301. } else {
  302. const file = params.source.params
  303. cellPackJson = state.build().toRoot()
  304. .apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } })
  305. }
  306. const cellPackBuilder = cellPackJson
  307. .apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
  308. .apply(ParseCellPack)
  309. const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime)
  310. const { packings } = cellPackObject.data
  311. await handleHivRna({ runtime, fetch: plugin.fetch }, packings, params.baseUrl)
  312. for (let i = 0, il = packings.length; i < il; ++i) {
  313. const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles: params.ingredients.files }
  314. const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p)
  315. await plugin.updateDataState(packing, { revertOnError: true });
  316. const structure = packing.selector.obj?.data
  317. if (structure) {
  318. await CellPackInfoProvider.attach({ fetch: plugin.fetch, runtime }, structure, {
  319. info: { packingsCount: packings.length, packingIndex: i }
  320. })
  321. }
  322. const packingParams = {
  323. traceOnly: params.preset.traceOnly,
  324. representation: params.preset.representation,
  325. }
  326. await CellpackPackingPreset.apply(packing.selector, packingParams, plugin)
  327. }
  328. }
  329. const LoadCellPackModelParams = {
  330. source: PD.MappedStatic('id', {
  331. 'id': PD.Select('influenza_model1.json', [
  332. ['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
  333. ['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
  334. ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
  335. ['hiv_lipids.bcif', 'hiv_lipids'],
  336. ['influenza_model1.json', 'influenza_model1'],
  337. ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
  338. ] as const),
  339. 'file': PD.File({ accept: 'id' }),
  340. }, { options: [['id', 'Id'], ['file', 'File']] }),
  341. baseUrl: PD.Text(DefaultCellPackBaseUrl),
  342. ingredients : PD.Group({
  343. files: PD.FileList({ accept: '.cif,.pdb' })
  344. }, { isExpanded: true }),
  345. preset: PD.Group({
  346. traceOnly: PD.Boolean(false),
  347. representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid']))
  348. }, { isExpanded: true })
  349. }
  350. type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
  351. export const LoadCellPackModel = StateAction.build({
  352. display: { name: 'Load CellPack', description: 'Open or download a model' },
  353. params: LoadCellPackModelParams,
  354. from: PSO.Root
  355. })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
  356. if (!ctx.representation.structure.themes.colorThemeRegistry.has(CellPackColorThemeProvider)) {
  357. ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider)
  358. }
  359. if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
  360. await loadHivMembrane(ctx, taskCtx, state, params)
  361. } else {
  362. await loadPackings(ctx, taskCtx, state, params)
  363. }
  364. }));