Browse Source

Merge pull request #369 from molstar/pdbx_sifts-export

Better support for atom_site.pdbx_sifts_xref
David Sehnal 3 years ago
parent
commit
bc2e8d8ac4

+ 4 - 0
CHANGELOG.md

@@ -6,6 +6,10 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Rename "best database mapping" to "SIFTS Mapping"
+- Add schema and export support for ``atom_site.pdbx_sifts_xref_*`` fields
+- Add schema export support for ``atom_site.pdbx_label_index`` field
+
 ## [v3.1.0] - 2022-02-06
 
 - Fix ``xrayShaded`` & ``ignoreLight`` params not working at the same time

+ 5 - 0
data/cif-field-names/mmcif-field-names.csv

@@ -24,6 +24,11 @@ atom_site.auth_asym_id
 atom_site.auth_seq_id
 atom_site.pdbx_PDB_model_num
 atom_site.ihm_model_id
+atom_site.pdbx_label_index
+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

+ 22 - 0
src/mol-io/reader/cif/schema/mmcif.ts

@@ -215,6 +215,28 @@ export const mmCIF_Schema = {
          * formal charge assignment normally found in chemical diagrams.
          */
         pdbx_formal_charge: int,
+        /**
+         * This data item is an ordinal which identifies distinct chemical components in the atom_site category, both
+         * polymeric and non-polymeric.
+         */
+        pdbx_label_index: 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

+ 35 - 21
src/mol-model-props/sequence/best-database-mapping.ts → 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 };

+ 19 - 19
src/mol-model-props/sequence/themes/best-database-mapping.ts → src/mol-model-props/sequence/themes/sifts-mapping.ts

@@ -12,27 +12,27 @@ 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.';
+const Description = 'Assigns a color based on SIFTS mapping.';
 
 // same colors for same accessions
 const globalAccessionMap = new Map<string, number>();
 
-export const BestDatabaseSequenceMappingColorThemeParams = {
+export const SIFTSMappingColorThemeParams = {
     ...getPaletteParams({ type: 'colors', colorList: 'set-1' }),
 };
-export type BestDatabaseSequenceMappingColorThemeParams = typeof BestDatabaseSequenceMappingColorThemeParams
-export function getBestDatabaseSequenceMappingColorThemeParams(ctx: ThemeDataContext) {
-    return BestDatabaseSequenceMappingColorThemeParams; // TODO return copy
+export type SIFTSMappingColorThemeParams = typeof SIFTSMappingColorThemeParams
+export function getSIFTSMappingColorThemeParams(ctx: ThemeDataContext) {
+    return SIFTSMappingColorThemeParams; // TODO return copy
 }
-export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, props: PD.Values<BestDatabaseSequenceMappingColorThemeParams>): ColorTheme<BestDatabaseSequenceMappingColorThemeParams> {
+export function SIFTSMappingColorTheme(ctx: ThemeDataContext, props: PD.Values<SIFTSMappingColorThemeParams>): ColorTheme<SIFTSMappingColorThemeParams> {
     let color: LocationColor;
 
     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)!;
@@ -70,7 +70,7 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro
     }
 
     return {
-        factory: BestDatabaseSequenceMappingColorTheme,
+        factory: SIFTSMappingColorTheme,
         granularity: 'group',
         preferSmoothing: true,
         color,
@@ -79,26 +79,26 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro
     };
 }
 
-export const BestDatabaseSequenceMappingColorThemeProvider: ColorTheme.Provider<BestDatabaseSequenceMappingColorThemeParams, 'best-sequence-database-mapping'> = {
-    name: 'best-sequence-database-mapping',
-    label: 'Best Database Sequence Mapping',
+export const SIFTSMappingColorThemeProvider: ColorTheme.Provider<SIFTSMappingColorThemeParams, 'sifts-mapping'> = {
+    name: 'sifts-mapping',
+    label: 'SIFTS Mapping',
     category: ColorTheme.Category.Residue,
-    factory: BestDatabaseSequenceMappingColorTheme,
-    getParams: getBestDatabaseSequenceMappingColorThemeParams,
-    defaultValues: PD.getDefaultValues(BestDatabaseSequenceMappingColorThemeParams),
-    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => BestDatabaseSequenceMapping.Provider.isApplicable(m)),
+    factory: SIFTSMappingColorTheme,
+    getParams: getSIFTSMappingColorThemeParams,
+    defaultValues: PD.getDefaultValues(SIFTSMappingColorThemeParams),
+    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);
             }
         }
     }

+ 82 - 2
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,64 @@ function atom_site_auth_asym_id(e: StructureElement.Location) {
     return l + suffix;
 }
 
-const atom_site_fields = () => CifWriter.fields<StructureElement.Location, Structure>()
+
+const atom_site_pdbx_label_index = {
+    shouldInclude(s: AtomSiteData) {
+        return !!s.atom_site?.pdbx_label_index.isDefined;
+    },
+    value(e: StructureElement.Location, d: AtomSiteData) {
+        const srcIndex = d.sourceIndex.value(e.element);
+        return d.atom_site!.pdbx_label_index.value(srcIndex);
+    },
+};
+
+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 +123,37 @@ 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 })
+
+    .int('pdbx_label_index', atom_site_pdbx_label_index.value, { shouldInclude: atom_site_pdbx_label_index.shouldInclude })
+
+    // 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()
             }))

+ 27 - 13
src/mol-model/structure/structure/util/superposition-db-mapping.ts → src/mol-model/structure/structure/util/superposition-sifts-mapping.ts

@@ -5,20 +5,25 @@
  */
 
 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';
 
-export interface AlignmentResult {
+export interface AlignmentResultEntry {
     transform: MinimizeRmsd.Result,
     pivot: number,
     other: number
 }
 
-export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]): AlignmentResult[] {
+export interface AlignmentResult {
+    entries: AlignmentResultEntry[],
+    zeroOverlapPairs: [number, number][],
+    failedPairs: [number, number][]
+}
+
+export function alignAndSuperposeWithSIFTSMapping(structures: Structure[]): AlignmentResult {
     const indexMap = new Map<string, IndexEntry>();
 
     for (let i = 0; i < structures.length; i++) {
@@ -30,15 +35,26 @@ export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]
     // TODO: support non-first structure pivots
     const pairs = findPairs(structures.length, index);
 
-    const ret: AlignmentResult[] = [];
+    const zeroOverlapPairs: AlignmentResult['zeroOverlapPairs'] = [];
+    const failedPairs: AlignmentResult['failedPairs'] = [];
+
+
+    const entries: AlignmentResultEntry[] = [];
     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 });
+        if (p.count === 0) {
+            zeroOverlapPairs.push([p.i, p.j]);
+        } else {
+            const [a, b] = getPositionTables(index, p.i, p.j, p.count);
+            const transform = MinimizeRmsd.compute({ a, b });
+            if (Number.isNaN(transform.rmsd)) {
+                failedPairs.push([p.i, p.j]);
+            } else {
+                entries.push({ transform, pivot: p.i, other: p.j });
+            }
+        }
     }
 
-    return ret;
+    return { entries, zeroOverlapPairs, failedPairs };
 }
 
 function getPositionTables(index: IndexEntry[], pivot: number, other: number, N: number) {
@@ -127,7 +143,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;
@@ -161,6 +177,4 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
             }
         }
     }
-
-    console.log(index);
 }

+ 27 - 12
src/mol-plugin-ui/structure/superposition.tsx

@@ -20,9 +20,9 @@ import { ParameterControls } from '../controls/parameters';
 import { stripTags } from '../../mol-util/string';
 import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
 import { ToggleSelectionModeButton } from './selection';
-import { alignAndSuperposeWithBestDatabaseMapping } from '../../mol-model/structure/structure/util/superposition-db-mapping';
+import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-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() {
@@ -48,7 +48,7 @@ export class StructureSuperpositionControls extends CollapsableControls {
 }
 
 export const StructureSuperpositionParams = {
-    alignSequences: PD.Boolean(true, { isEssential: true, description: 'Perform a sequence alignment and use the aligned residue pairs to guide the 3D superposition.' }),
+    alignSequences: PD.Boolean(true, { isEssential: true, description: 'For Chain-based 3D superposition, perform a sequence alignment and use the aligned residue pairs to guide the 3D superposition.' }),
 };
 const DefaultStructureSuperpositionOptions = PD.getDefaultValues(StructureSuperpositionParams);
 export type StructureSuperpositionOptions = PD.ValuesFor<typeof StructureSuperpositionParams>
@@ -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))) });
         });
     }
 
@@ -176,20 +176,35 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
     superposeDb = async () => {
         const input = this.plugin.managers.structure.hierarchy.behaviors.selection.value.structures;
 
-        const transforms = alignAndSuperposeWithBestDatabaseMapping(input.map(s => s.cell.obj?.data!));
+        const structures = input.map(s => s.cell.obj?.data!);
+        const { entries, failedPairs, zeroOverlapPairs } = alignAndSuperposeWithSIFTSMapping(structures);
 
         let rmsd = 0;
 
-        for (const xform of transforms) {
+        for (const xform of entries) {
             await this.transform(input[xform.other].cell, xform.transform.bTransform);
             rmsd += xform.transform.rmsd;
         }
 
-        rmsd /= Math.max(transforms.length - 1, 1);
+        rmsd /= Math.max(entries.length - 1, 1);
 
-        this.plugin.log.info(`Superposed ${input.length} structures with avg. RMSD ${rmsd.toFixed(2)} Å.`);
-        await new Promise(res => requestAnimationFrame(res));
-        PluginCommands.Camera.Reset(this.plugin);
+        const formatPairs = (pairs: [number, number][]) => {
+            return `[${pairs.map(([i, j]) => `(${structures[i].models[0].entryId}, ${structures[j].models[0].entryId})`).join(', ')}]`;
+        };
+
+        if (zeroOverlapPairs.length) {
+            this.plugin.log.warn(`Superposition: No UNIPROT mapping overlap between structures ${formatPairs(zeroOverlapPairs)}.`);
+        }
+
+        if (failedPairs.length) {
+            this.plugin.log.error(`Superposition: Failed to superpose structures ${formatPairs(failedPairs)}.`);
+        }
+
+        if (entries.length) {
+            this.plugin.log.info(`Superposed ${entries.length + 1} structures with avg. RMSD ${rmsd.toFixed(2)} Å.`);
+            await new Promise(res => requestAnimationFrame(res));
+            PluginCommands.Camera.Reset(this.plugin);
+        }
     };
 
     toggleByChains = () => this.setState({ action: this.state.action === 'byChains' ? void 0 : 'byChains' });
@@ -323,8 +338,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 intersection of residues from SIFTS UNIPROT mapping.' className='msp-btn msp-btn-block' onClick={this.superposeDb} style={{ marginTop: '1px' }} disabled={this.state.isBusy}>
+                Uniprot
             </Button>
         </>;
     }

+ 1 - 1
src/mol-plugin/behavior/dynamic/custom-props.ts

@@ -11,6 +11,6 @@ export { AccessibleSurfaceArea } from './custom-props/computed/accessible-surfac
 export { Interactions } from './custom-props/computed/interactions';
 export { SecondaryStructure } from './custom-props/computed/secondary-structure';
 export { ValenceModel } from './custom-props/computed/valence-model';
-export { BestDatabaseSequenceMapping } from './custom-props/sequence/best-database-mapping';
+export { SIFTSMapping as BestDatabaseSequenceMapping } from './custom-props/sequence/sifts-mapping';
 
 export { CrossLinkRestraint } from './custom-props/integrative/cross-link-restraint';

+ 7 - 7
src/mol-plugin/behavior/dynamic/custom-props/sequence/best-database-mapping.ts → src/mol-plugin/behavior/dynamic/custom-props/sequence/sifts-mapping.ts

@@ -5,17 +5,17 @@
  */
 
 import { OrderedSet } from '../../../../../mol-data/int';
-import { BestDatabaseSequenceMapping as BestDatabaseSequenceMappingProp } from '../../../../../mol-model-props/sequence/best-database-mapping';
-import { BestDatabaseSequenceMappingColorThemeProvider } from '../../../../../mol-model-props/sequence/themes/best-database-mapping';
+import { SIFTSMapping as BestDatabaseSequenceMappingProp } from '../../../../../mol-model-props/sequence/sifts-mapping';
+import { SIFTSMappingColorThemeProvider } from '../../../../../mol-model-props/sequence/themes/sifts-mapping';
 import { Loci } from '../../../../../mol-model/loci';
 import { StructureElement } from '../../../../../mol-model/structure';
 import { ParamDefinition as PD } from '../../../../../mol-util/param-definition';
 import { PluginBehavior } from '../../../behavior';
 
-export const BestDatabaseSequenceMapping = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
-    name: 'best-sequence-database-mapping-prop',
+export const SIFTSMapping = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
+    name: 'sifts-mapping-prop',
     category: 'custom-props',
-    display: { name: 'Best Database Sequence Mapping' },
+    display: { name: 'SIFTS Mapping' },
     ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
         private provider = BestDatabaseSequenceMappingProp.Provider;
 
@@ -39,13 +39,13 @@ export const BestDatabaseSequenceMapping = PluginBehavior.create<{ autoAttach: b
 
         register(): void {
             this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
-            this.ctx.representation.structure.themes.colorThemeRegistry.add(BestDatabaseSequenceMappingColorThemeProvider);
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(SIFTSMappingColorThemeProvider);
             this.ctx.managers.lociLabels.addProvider(this.labelProvider);
         }
 
         unregister() {
             this.ctx.customModelProperties.unregister(this.provider.descriptor.name);
-            this.ctx.representation.structure.themes.colorThemeRegistry.remove(BestDatabaseSequenceMappingColorThemeProvider);
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(SIFTSMappingColorThemeProvider);
             this.ctx.managers.lociLabels.removeProvider(this.labelProvider);
         }
     },