diff --git a/data/cif-field-names/mmcif-field-names.csv b/data/cif-field-names/mmcif-field-names.csv index add635b14b6eb41a87392180d37f885f59bf3fd0..860ac4780936985d84b882a71ae6b96c3d0327d4 100644 --- a/data/cif-field-names/mmcif-field-names.csv +++ b/data/cif-field-names/mmcif-field-names.csv @@ -24,6 +24,10 @@ atom_site.auth_asym_id atom_site.auth_seq_id atom_site.pdbx_PDB_model_num atom_site.ihm_model_id +atom_site.pdbx_sifts_xref_db_name +atom_site.pdbx_sifts_xref_db_acc +atom_site.pdbx_sifts_xref_db_num +atom_site.pdbx_sifts_xref_db_res atom_site_anisotrop.id atom_site_anisotrop.U diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts index 1f0a8358bf1af14c83c5c76371785360194976d4..94e387d625415e3f6e9f212026754833f757c771 100644 --- a/src/mol-io/reader/cif/schema/mmcif.ts +++ b/src/mol-io/reader/cif/schema/mmcif.ts @@ -215,6 +215,23 @@ export const mmCIF_Schema = { * formal charge assignment normally found in chemical diagrams. */ pdbx_formal_charge: int, + /** + * The name of additional external databases with residue level mapping. + */ + pdbx_sifts_xref_db_name: str, + /** + * The accession code related to the additional external database entry. + */ + pdbx_sifts_xref_db_acc: str, + /** + * The sequence position of the external database entry that corresponds + * to the residue mapping defined by the SIFTS process. + */ + pdbx_sifts_xref_db_num: str, + /** + * Describes the residue type of the given UniProt match + */ + pdbx_sifts_xref_db_res: str, /** * The model id corresponding to the atom site. * This data item is a pointer to _ihm_model_list.model_id diff --git a/src/mol-model-props/sequence/best-database-mapping.ts b/src/mol-model-props/sequence/sifts-mapping.ts similarity index 62% rename from src/mol-model-props/sequence/best-database-mapping.ts rename to src/mol-model-props/sequence/sifts-mapping.ts index a0ebeb91bb95362bb74b5a555e36671118feab43..1c28307f8db05ec3ed8a2bf37d1dd618a5e7061c 100644 --- a/src/mol-model-props/sequence/best-database-mapping.ts +++ b/src/mol-model-props/sequence/sifts-mapping.ts @@ -11,30 +11,43 @@ import { Model } from '../../mol-model/structure'; import { StructureElement } from '../../mol-model/structure/structure'; import { CustomModelProperty } from '../common/custom-model-property'; -export { BestDatabaseSequenceMapping }; +export { SIFTSMapping as SIFTSMapping }; -interface BestDatabaseSequenceMapping { +interface SIFTSMappingMapping { readonly dbName: string[], readonly accession: string[], - readonly num: number[], + readonly num: string[], readonly residue: string[] } -namespace BestDatabaseSequenceMapping { - export const Provider: CustomModelProperty.Provider<{}, BestDatabaseSequenceMapping> = CustomModelProperty.createProvider({ - label: 'Best Database Sequence Mapping', +namespace SIFTSMapping { + export const Provider: CustomModelProperty.Provider<{}, SIFTSMappingMapping> = CustomModelProperty.createProvider({ + label: 'SIFTS Mapping', descriptor: CustomPropertyDescriptor({ - name: 'molstar_best_database_sequence_mapping' + name: 'sifts_sequence_mapping' }), type: 'static', defaultParams: {}, getParams: () => ({}), - isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site?.fieldNames.indexOf('pdbx_sifts_xref_db_name') >= 0, + isApplicable: (data: Model) => isAvailable(data), obtain: async (ctx, data) => { return { value: fromCif(data) }; } }); + export function isAvailable(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return false; + + const { + pdbx_sifts_xref_db_name: db_name, + pdbx_sifts_xref_db_acc: db_acc, + pdbx_sifts_xref_db_num: db_num, + pdbx_sifts_xref_db_res: db_res + } = model.sourceData.data.db.atom_site; + + return db_name.isDefined && db_acc.isDefined && db_num.isDefined && db_res.isDefined; + } + export function getKey(loc: StructureElement.Location) { const model = loc.unit.model; const data = Provider.get(model).value; @@ -55,22 +68,23 @@ namespace BestDatabaseSequenceMapping { return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`; } - function fromCif(model: Model): BestDatabaseSequenceMapping | undefined { + function fromCif(model: Model): SIFTSMappingMapping | undefined { if (!MmcifFormat.is(model.sourceData)) return; - const { atom_site } = model.sourceData.data.frame.categories; - const db_name = atom_site.getField('pdbx_sifts_xref_db_name'); - const db_acc = atom_site.getField('pdbx_sifts_xref_db_acc'); - const db_num = atom_site.getField('pdbx_sifts_xref_db_num'); - const db_res = atom_site.getField('pdbx_sifts_xref_db_res'); + const { + pdbx_sifts_xref_db_name: db_name, + pdbx_sifts_xref_db_acc: db_acc, + pdbx_sifts_xref_db_num: db_num, + pdbx_sifts_xref_db_res: db_res + } = model.sourceData.data.db.atom_site; - if (!db_name || !db_acc || !db_num || !db_res) return; + if (!db_name.isDefined || !db_acc.isDefined || !db_num.isDefined || !db_res.isDefined) return; const { atomSourceIndex } = model.atomicHierarchy; const { count, offsets: residueOffsets } = model.atomicHierarchy.residueAtomSegments; const dbName = new Array<string>(count); const accession = new Array<string>(count); - const num = new Array<number>(count); + const num = new Array<string>(count); const residue = new Array<string>(count); for (let i = 0; i < count; i++) { @@ -79,15 +93,15 @@ namespace BestDatabaseSequenceMapping { if (db_name.valueKind(row) !== Column.ValueKind.Present) { dbName[i] = ''; accession[i] = ''; - num[i] = 0; + num[i] = ''; residue[i] = ''; continue; } - dbName[i] = db_name.str(row); - accession[i] = db_acc.str(row); - num[i] = db_num.int(row); - residue[i] = db_res.str(row); + dbName[i] = db_name.value(row); + accession[i] = db_acc.value(row); + num[i] = db_num.value(row); + residue[i] = db_res.value(row); } return { dbName, accession, num, residue }; diff --git a/src/mol-model-props/sequence/themes/best-database-mapping.ts b/src/mol-model-props/sequence/themes/best-database-mapping.ts index c4588f43155e17ce0bf17ef8a112c5a35e6d1c51..9e6bbcb49f73cb2cc72dfd2769a97ed67e025b41 100644 --- a/src/mol-model-props/sequence/themes/best-database-mapping.ts +++ b/src/mol-model-props/sequence/themes/best-database-mapping.ts @@ -12,7 +12,7 @@ import { Color } from '../../../mol-util/color'; import { getPalette, getPaletteParams } from '../../../mol-util/color/palette'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { CustomProperty } from '../../common/custom-property'; -import { BestDatabaseSequenceMapping } from '../best-database-mapping'; +import { SIFTSMapping } from '../sifts-mapping'; const DefaultColor = Color(0xFAFAFA); const Description = 'Assigns a color based on best dababase sequence mapping.'; @@ -32,7 +32,7 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro if (ctx.structure) { for (const m of ctx.structure.models) { - const mapping = BestDatabaseSequenceMapping.Provider.get(m).value; + const mapping = SIFTSMapping.Provider.get(m).value; if (!mapping) continue; for (const acc of mapping.accession) { if (!acc || globalAccessionMap.has(acc)) continue; @@ -45,7 +45,7 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro const colorMap = new Map<string, Color>(); const getColor = (location: StructureElement.Location) => { - const key = BestDatabaseSequenceMapping.getKey(location); + const key = SIFTSMapping.getKey(location); if (!key) return DefaultColor; if (colorMap.has(key)) return colorMap.get(key)!; @@ -86,19 +86,19 @@ export const BestDatabaseSequenceMappingColorThemeProvider: ColorTheme.Provider< factory: BestDatabaseSequenceMappingColorTheme, getParams: getBestDatabaseSequenceMappingColorThemeParams, defaultValues: PD.getDefaultValues(BestDatabaseSequenceMappingColorThemeParams), - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => BestDatabaseSequenceMapping.Provider.isApplicable(m)), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => SIFTSMapping.Provider.isApplicable(m)), ensureCustomProperties: { attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => { if (!data.structure) return; for (const m of data.structure.models) { - await BestDatabaseSequenceMapping.Provider.attach(ctx, m, void 0, true); + await SIFTSMapping.Provider.attach(ctx, m, void 0, true); } }, detach: (data) => { if (!data.structure) return; for (const m of data.structure.models) { - BestDatabaseSequenceMapping.Provider.ref(m, false); + SIFTSMapping.Provider.ref(m, false); } } } diff --git a/src/mol-model/structure/export/categories/atom_site.ts b/src/mol-model/structure/export/categories/atom_site.ts index daf85b13cc02f0699bc5e8d895b6cbee7fc08a82..77729c73e6a52e901f2d41909449bbb364847066 100644 --- a/src/mol-model/structure/export/categories/atom_site.ts +++ b/src/mol-model/structure/export/categories/atom_site.ts @@ -5,7 +5,11 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { Column } from '../../../../mol-data/db'; +import { mmCIF_Database } from '../../../../mol-io/reader/cif/schema/mmcif'; import { CifWriter } from '../../../../mol-io/writer/cif'; +import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif'; +import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping'; import { StructureElement, Structure, StructureProperties as P } from '../../structure'; import { CifExportContext } from '../mmcif'; import CifField = CifWriter.Field @@ -26,7 +30,53 @@ function atom_site_auth_asym_id(e: StructureElement.Location) { return l + suffix; } -const atom_site_fields = () => CifWriter.fields<StructureElement.Location, Structure>() +const SIFTS = { + shouldInclude(s: AtomSiteData) { + return SIFTSMapping.isAvailable(s.structure.models[0]); + }, + pdbx_sifts_xref_db_name: { + value(e: StructureElement.Location, d: AtomSiteData) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_name.value(srcIndex); + }, + valueKind(e: StructureElement.Location, d: any) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_name.valueKind(srcIndex); + }, + }, + pdbx_sifts_xref_db_acc: { + value(e: StructureElement.Location, d: AtomSiteData) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_acc.value(srcIndex); + }, + valueKind(e: StructureElement.Location, d: any) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_acc.valueKind(srcIndex); + }, + }, + pdbx_sifts_xref_db_num: { + value(e: StructureElement.Location, d: AtomSiteData) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_num.value(srcIndex); + }, + valueKind(e: StructureElement.Location, d: any) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_num.valueKind(srcIndex); + }, + }, + pdbx_sifts_xref_db_res: { + value(e: StructureElement.Location, d: AtomSiteData) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_res.value(srcIndex); + }, + valueKind(e: StructureElement.Location, d: any) { + const srcIndex = d.sourceIndex.value(e.element); + return d.atom_site!.pdbx_sifts_xref_db_res.valueKind(srcIndex); + }, + } +}; + +const atom_site_fields = () => CifWriter.fields<StructureElement.Location, AtomSiteData>() .str('group_PDB', P.residue.group_PDB) .index('id') .str('type_symbol', P.atom.type_symbol as any) @@ -62,18 +112,35 @@ const atom_site_fields = () => CifWriter.fields<StructureElement.Location, Struc .str('auth_asym_id', atom_site_auth_asym_id) .int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE }) + + // SIFTS + .str('pdbx_sifts_xref_db_name', SIFTS.pdbx_sifts_xref_db_name.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_name.valueKind }) + .str('pdbx_sifts_xref_db_acc', SIFTS.pdbx_sifts_xref_db_acc.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_acc.valueKind }) + .str('pdbx_sifts_xref_db_num', SIFTS.pdbx_sifts_xref_db_num.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_num.valueKind }) + .str('pdbx_sifts_xref_db_res', SIFTS.pdbx_sifts_xref_db_res.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_res.valueKind }) + // .str('operator_name', P.unit.operator_name, { // shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity) // }) .getFields(); +interface AtomSiteData { + structure: Structure, + sourceIndex: Column<number>, + atom_site?: mmCIF_Database['atom_site'] +} + export const _atom_site: CifCategory<CifExportContext> = { name: 'atom_site', instance({ structures }: CifExportContext) { return { fields: atom_site_fields(), source: structures.map(s => ({ - data: s, + data: { + structure: s, + sourceIndex: s.model.atomicHierarchy.atomSourceIndex, + atom_site: MmcifFormat.is(s.model.sourceData) ? s.model.sourceData.data.db.atom_site : void 0 + } as AtomSiteData, rowCount: s.elementCount, keys: () => s.elementLocations() })) diff --git a/src/mol-model/structure/structure/util/superposition-db-mapping.ts b/src/mol-model/structure/structure/util/superposition-db-mapping.ts index 90d2df380fb9c436c451104e9aef04495d6a4226..725826622095f1c9286de1e8e057489aadda3a35 100644 --- a/src/mol-model/structure/structure/util/superposition-db-mapping.ts +++ b/src/mol-model/structure/structure/util/superposition-db-mapping.ts @@ -7,7 +7,7 @@ import { Segmentation } from '../../../../mol-data/int'; import { Mat4 } from '../../../../mol-math/linear-algebra'; import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd'; -import { BestDatabaseSequenceMapping } from '../../../../mol-model-props/sequence/best-database-mapping'; +import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping'; import { ElementIndex } from '../../model/indexing'; import { Structure } from '../structure'; import { Unit } from '../unit'; @@ -34,7 +34,6 @@ export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[] for (const p of pairs) { const [a, b] = getPositionTables(index, p.i, p.j, p.count); const transform = MinimizeRmsd.compute({ a, b }); - console.log(Mat4.makeTable(transform.bTransform), transform.rmsd); ret.push({ transform, pivot: p.i, other: p.j }); } @@ -127,7 +126,7 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu const { elements, model } = unit; - const map = BestDatabaseSequenceMapping.Provider.get(model).value; + const map = SIFTSMapping.Provider.get(model).value; if (!map) return; const { dbName, accession, num } = map; diff --git a/src/mol-plugin-ui/structure/superposition.tsx b/src/mol-plugin-ui/structure/superposition.tsx index 040050ca395cee916ca7a2a2b0bd8d4b2bd0d560..418a774db86a15a37b53425b2a7015c2fca43069 100644 --- a/src/mol-plugin-ui/structure/superposition.tsx +++ b/src/mol-plugin-ui/structure/superposition.tsx @@ -22,7 +22,7 @@ import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/s import { ToggleSelectionModeButton } from './selection'; import { alignAndSuperposeWithBestDatabaseMapping } from '../../mol-model/structure/structure/util/superposition-db-mapping'; import { PluginCommands } from '../../mol-plugin/commands'; -import { BestDatabaseSequenceMapping } from '../../mol-model-props/sequence/best-database-mapping'; +import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping'; export class StructureSuperpositionControls extends CollapsableControls { defaultState() { @@ -94,7 +94,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit }); this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, sel => { - this.setState({ canUseDb: sel.structures.every(s => !!s.cell.obj?.data && s.cell.obj.data.models.some(m => BestDatabaseSequenceMapping.Provider.isApplicable(m))) }); + this.setState({ canUseDb: sel.structures.every(s => !!s.cell.obj?.data && s.cell.obj.data.models.some(m => SIFTSMapping.Provider.isApplicable(m))) }); }); } @@ -323,8 +323,8 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit superposeByDbMapping() { return <> - <Button icon={SuperposeChainsSvg} title='Superpose structures using database mapping.' className='msp-btn msp-btn-block' onClick={this.superposeDb} style={{ marginTop: '1px' }} disabled={this.state.isBusy}> - DB + <Button icon={SuperposeChainsSvg} title='Superpose structures using UNIPROT mapping.' className='msp-btn msp-btn-block' onClick={this.superposeDb} style={{ marginTop: '1px' }} disabled={this.state.isBusy}> + Uniprot </Button> </>; } diff --git a/src/mol-plugin/behavior/dynamic/custom-props/sequence/best-database-mapping.ts b/src/mol-plugin/behavior/dynamic/custom-props/sequence/best-database-mapping.ts index d5e4c1e615c7810aa31984dfa41a9d13888378fb..80563357a3bb9216f719762e9ad3ef70f1aaa415 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/sequence/best-database-mapping.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/sequence/best-database-mapping.ts @@ -5,7 +5,7 @@ */ import { OrderedSet } from '../../../../../mol-data/int'; -import { BestDatabaseSequenceMapping as BestDatabaseSequenceMappingProp } from '../../../../../mol-model-props/sequence/best-database-mapping'; +import { SIFTSMapping as BestDatabaseSequenceMappingProp } from '../../../../../mol-model-props/sequence/sifts-mapping'; import { BestDatabaseSequenceMappingColorThemeProvider } from '../../../../../mol-model-props/sequence/themes/best-database-mapping'; import { Loci } from '../../../../../mol-model/loci'; import { StructureElement } from '../../../../../mol-model/structure';