Ver Fonte

AlignmentMapper tools && molstar superpose

bioinsilico há 2 anos atrás
pai
commit
efc67de493

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "@rcsb/rcsb-saguaro-3d",
-  "version": "2.1.0",
+  "version": "2.2.0-uniprot-msa.7",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@rcsb/rcsb-saguaro-3d",
-      "version": "2.1.0",
+      "version": "2.2.0-uniprot-msa.7",
       "license": "MIT",
       "dependencies": {
         "@rcsb/rcsb-api-tools": "^4.1.0",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@rcsb/rcsb-saguaro-3d",
-  "version": "2.2.0-uniprot-msa.7",
+  "version": "2.2.0-uniprot-msa.8",
   "description": "RCSB Molstar/Saguaro Web App",
   "main": "build/dist/app.js",
   "files": [

+ 30 - 54
src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/UniprotCallbackManager.ts

@@ -7,10 +7,11 @@ import {RcsbFvTrackDataElementInterface} from "@rcsb/rcsb-saguaro";
 import {
     UniprotSequenceOnchangeInterface
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvBuilder/RcsbFvUniprotBuilder";
-import {AlignmentResponse} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
+import {AlignedRegion, AlignmentResponse} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
 import {RegionSelectionInterface} from "../../../../RcsbFvState/RcsbFvSelectorManager";
 import {ChainInfo, SaguaroRegionList} from "../../../../RcsbFvStructure/StructureViewerInterface";
 import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
+import {AlignmentMapper as AM} from "../../../../Utils/AlignmentMapper";
 
 export class UniprotCallbackManagerFactory<R> implements CallbackManagerFactoryInterface<R,{context: UniprotSequenceOnchangeInterface;}> {
 
@@ -58,23 +59,21 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
 
     protected async innerStructureViewerSelectionChange(mode: "select" | "hover"): Promise<void> {
         const allSel: Array<SaguaroRegionList> | undefined = this.stateManager.selectionState.getSelection(mode);
-        if(allSel == null || allSel.length ===0) {
-            this.rcsbFvContainer.get()?.getFv().clearSelection(mode);
-        }else{
-            const alignment: AlignmentResponse|undefined = await this.rcsbFvContainer.get()?.getAlignmentResponse();
-            if(alignment) {
-                allSel.forEach(sel => {
-                    const chain: ChainInfo | undefined = this.stateManager.assemblyModelSate.getModelChainInfo(sel.modelId)?.chains.find(ch => ch.entityId == TagDelimiter.parseEntity(sel.modelId).entityId && ch.label == sel.labelAsymId);
-                    if (chain) {
-                        const regions = this.getModelRegions(sel.regions.map(r => ({
-                            begin: r.begin,
-                            end: r.end
-                        })), alignment, [sel.modelId], "target");
-                        this.rcsbFvContainer.get()?.getFv().addSelection({mode, elements: regions.map(r => r.region)})
-                    }
-                });
-            }
+        const alignment: AlignmentResponse|undefined = await this.rcsbFvContainer.get()?.getAlignmentResponse();
+        let regions: SelectedRegion[] = [];
+        if(alignment) {
+            allSel.forEach(sel => {
+                const chain: ChainInfo | undefined = this.stateManager.assemblyModelSate.getModelChainInfo(sel.modelId)?.chains.find(ch => ch.entityId == TagDelimiter.parseEntity(sel.modelId).entityId && ch.label == sel.labelAsymId);
+                if (chain) {
+                    sel.regions.forEach(r=>console.log(r));
+                    regions = regions.concat(this.getModelRegions(sel.regions.map(r => ({
+                        begin: r.begin,
+                        end: r.end
+                    })), alignment, [sel.modelId], "target"));
+                }
+            });
         }
+        this.rcsbFvContainer.get()?.getFv().setSelection({mode, elements: regions.map(r => r.region)})
     }
 
     protected async innerPfvSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): Promise<void> {
@@ -93,7 +92,6 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
     }
 
     private getModelRegions(selection: Array<RcsbFvTrackDataElementInterface>, alignment: AlignmentResponse, modelList: string[], pointer:"query"|"target"): SelectedRegion[] {
-        const cPointer: "query"|"target" = pointer == "query" ? "target" : "query";
         const regions: SelectedRegion[] = [];
         modelList.forEach(modelId=>{
             const chain: ChainInfo|undefined = this.stateManager.assemblyModelSate.getModelChainInfo(modelId)?.chains.find(ch=>ch.entityId==TagDelimiter.parseEntity(modelId).entityId);
@@ -104,44 +102,22 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
             if(!labelAsymId || ! operatorName)
                 return;
             selection.forEach(s=>{
-                const rangeBegin = alignment.target_alignment?.find(ta=>ta?.target_id === modelId)?.aligned_regions?.find(ar=>((ar?.[alignmentPointer[pointer].begin] ?? -1) <= s.begin) && (s.begin <= (ar?.[alignmentPointer[pointer].end] ?? -1)));
-                const rangeEnd = alignment.target_alignment?.find(ta=>ta?.target_id === modelId)?.aligned_regions?.find(ar=>((ar?.[alignmentPointer[pointer].begin] ?? -1) <= (s.end ?? s.begin) ) && ((s.end ?? s.begin) <= (ar?.[alignmentPointer[pointer].end] ?? -1)));
-                const begin = s.begin - (rangeBegin?.[alignmentPointer[pointer].begin] ?? 0) + (rangeBegin?.[alignmentPointer[cPointer].begin] ?? 0);
-                const end = (s.end ?? s.begin) - (rangeEnd?.[alignmentPointer[pointer].begin] ?? 0) + (rangeEnd?.[alignmentPointer[cPointer].begin] ?? 0);
-                regions.push({
-                    modelId,
-                    labelAsymId,
-                    operatorName,
-                    region:{
-                        begin,
-                        end,
-                        source:"sequence"
-                    }
+                const alignedRegions = alignment.target_alignment?.find(ta=>ta?.target_id === modelId)?.aligned_regions!.filter((o): o is AlignedRegion => o!=null);
+                if(!alignedRegions)
+                    return;
+                AM.mapRangeToRegionList({begin:s.begin, end: s.end ?? s.begin}, alignedRegions, pointer)?.forEach(region=>{
+                    regions.push({
+                        modelId,
+                        labelAsymId,
+                        operatorName,
+                        region:{
+                            ...region,
+                            source:"sequence"
+                        }
+                    });
                 });
-            })
+            });
         });
         return regions;
     }
-}
-
-interface AlignmentPointerInterface {
-    query: {
-        begin: "query_begin",
-        end: "query_end"
-    },
-    target: {
-        begin: "target_begin",
-        end: "target_end"
-    }
-}
-
-const alignmentPointer: AlignmentPointerInterface = {
-    query: {
-        begin: "query_begin",
-        end: "query_end"
-    },
-    target: {
-        begin: "target_begin",
-        end: "target_end"
-    }
 }

+ 10 - 2
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvManagerFactory.ts

@@ -30,6 +30,13 @@ export class UniprotPfvManagerFactory<R> implements PfvManagerFactoryInterface<{
 
 }
 
+type AlignmentDataType = {
+    pdb:{
+        entryId:string;
+        entityId:string;
+    },
+    targetAlignment: TargetAlignment;
+};
 class UniprotPfvManager<R> extends AbstractPfvManager<{upAcc:string},R,{context: UniprotSequenceOnchangeInterface;}>{
 
     private readonly upAcc:string;
@@ -89,11 +96,12 @@ class UniprotPfvManager<R> extends AbstractPfvManager<{upAcc:string},R,{context:
 
     private loadAlignment(alignmentContext: AlignmentRequestContextType, targetAlignment: TargetAlignment):void {
         if(typeof targetAlignment.target_id === "string") {
-            this.stateManager.next<"model-change", {pdb:{entryId:string;entityId:string;}}>({
+            this.stateManager.next<"model-change",AlignmentDataType>({
                 type:"model-change",
                 view:"1d-view",
                 data:{
-                    pdb:TagDelimiter.parseEntity(targetAlignment.target_id)
+                    pdb:TagDelimiter.parseEntity(targetAlignment.target_id),
+                    targetAlignment
                 }
             });
         }

+ 5 - 3
src/RcsbFvStructure/StructureUtils/MolstarAlignmentLoader.ts

@@ -22,12 +22,13 @@ import {TrajectoryHierarchyPresetProvider} from "molstar/lib/mol-plugin-state/bu
 import {
     AlignmentTrajectoryPresetProvider, TrajectoryParamsType
 } from "../StructureViewers/MolstarViewer/TrajectoryPresetProvider/AlignmentTrajectoryPresetProvider";
+import {TargetAlignment} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
 
-export class MolstarAlignmentLoader implements StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <LoadMolstarInterface<TrajectoryParamsType>>,{entryId:string;entityId:string;}]> {
+export class MolstarAlignmentLoader implements StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <LoadMolstarInterface<TrajectoryParamsType>>,{entryId:string;entityId:string;},TargetAlignment]> {
 
     private readonly structureMap: Set<string> = new Set<string>();
 
-    async load(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface <LoadMolstarInterface<TrajectoryParamsType>>, pdb:{entryId:string;entityId:string;}): Promise<void> {
+    async load(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface <LoadMolstarInterface<TrajectoryParamsType>>, pdb:{entryId:string;entityId:string;}, targetAlignment: TargetAlignment): Promise<void> {
         const structureId: string = `${pdb.entryId}${TagDelimiter.entity}${pdb.entityId}`;
         if(!this.structureMap.has(structureId)){
             await structureViewer.load({
@@ -39,7 +40,8 @@ export class MolstarAlignmentLoader implements StructureLoaderInterface<[ViewerC
                     params:{
                         assemblyId: "1",
                         modelIndex: 0,
-                        pdb: pdb
+                        pdb,
+                        targetAlignment
                     }
                 }
             });

+ 15 - 7
src/RcsbFvStructure/StructureViewerBehaviour/UniprotBehaviour.ts

@@ -21,13 +21,14 @@ import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
 import {createSelectionExpressions} from "@rcsb/rcsb-molstar/build/src/viewer/helpers/selection";
 import {RegionSelectionInterface} from "../../RcsbFvState/RcsbFvSelectorManager";
 import {defaultInputTarget} from "concurrently/dist/src/defaults";
+import {TargetAlignment} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
 
 export class UniprotBehaviourObserver<R> implements StructureViewerBehaviourObserverInterface<R> {
 
     private structureBehaviour: StructureViewerBehaviourInterface;
-    private readonly structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;}]>;
+    private readonly structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;},TargetAlignment]>;
 
-    constructor(structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;}]>) {
+    constructor(structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;},TargetAlignment]>) {
         this.structureLoader = structureLoader
     }
     public observe(
@@ -44,12 +45,19 @@ export class UniprotBehaviourObserver<R> implements StructureViewerBehaviourObse
 }
 
 type SelectedRegion = {modelId: string, labelAsymId: string, region: RegionSelectionInterface, operatorName?: string};
+type AlignmentDataType = {
+    pdb:{
+        entryId:string;
+        entityId:string;
+    },
+    targetAlignment: TargetAlignment;
+};
 class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
 
     private readonly structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>;
     private readonly stateManager: RcsbFvStateInterface;
     private readonly subscription: Subscription;
-    private readonly structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;}]>;
+    private readonly structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;},TargetAlignment]>;
     private readonly componentList: string[] = [];
 
     private readonly CREATE_COMPONENT_THR: number = 5;
@@ -57,7 +65,7 @@ class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
     constructor(
         structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>,
         stateManager: RcsbFvStateInterface,
-        structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;}]>
+        structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;},TargetAlignment]>
     ) {
         this.structureViewer = structureViewer;
         this.stateManager = stateManager;
@@ -66,7 +74,7 @@ class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
     }
 
     private subscribe(): Subscription {
-        return this.stateManager.subscribe<"model-change"|"representation-change"|"feature-click",{pdb:{entryId:string;entityId:string;}} & {tag:"polymer"|"non-polymer";isHidden:boolean;} & SelectedRegion[]>(async o=>{
+        return this.stateManager.subscribe<"model-change"|"representation-change"|"feature-click",AlignmentDataType & {tag:"polymer"|"non-polymer";isHidden:boolean;} & SelectedRegion[]>(async o=>{
             if(o.type == "model-change" && o.view == "1d-view" && o.data)
                 await this.modelChange(o.data);
             if(o.type == "representation-change" && o.view == "1d-view" && o.data)
@@ -162,9 +170,9 @@ class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
         }
     }
 
-    async modelChange(data?:{pdb:{entryId:string;entityId:string;}}): Promise<void> {
+    async modelChange(data?:AlignmentDataType): Promise<void> {
         if(data)
-            await this.structureLoader.load(this.structureViewer, data.pdb);
+            await this.structureLoader.load(this.structureViewer, data.pdb, data.targetAlignment);
     }
 
     private select(mode:"select"|"hover"): void{

+ 97 - 23
src/RcsbFvStructure/StructureViewers/MolstarViewer/TrajectoryPresetProvider/AlignmentRepresentationPresetProvider.ts

@@ -9,43 +9,49 @@ import {
 import {PluginContext} from "molstar/lib/mol-plugin/context";
 import {PluginStateObject} from "molstar/lib/mol-plugin-state/objects";
 import {StateObjectRef} from "molstar/lib/mol-state";
-import {Structure, StructureElement, StructureProperties as SP, Unit} from "molstar/lib/mol-model/structure";
+import {
+    QueryContext,
+    Structure,
+    StructureElement,
+    StructureProperties as SP,
+    StructureSelection,
+    Unit
+} from "molstar/lib/mol-model/structure";
 import {MolScriptBuilder as MS} from "molstar/lib/mol-script/language/builder";
-import reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
 import uniqid from "uniqid";
 import {PLDDTConfidenceColorThemeProvider} from "molstar/lib/extensions/model-archive/quality-assessment/color/plddt";
 import {ColorTheme} from "molstar/lib/mol-theme/color";
 import {createSelectionExpressions} from "@rcsb/rcsb-molstar/build/src/viewer/helpers/selection";
 import {ParamDefinition as PD} from "molstar/lib/mol-util/param-definition";
 import {Loci} from "molstar/lib/mol-model/loci";
-import {alignAndSuperpose} from "molstar/lib/mol-model/structure/structure/util/superposition";
+import {alignAndSuperpose, superpose} from "molstar/lib/mol-model/structure/structure/util/superposition";
 import {Mat4} from "molstar/lib/mol-math/linear-algebra";
 import {SymmetryOperator} from "molstar/lib/mol-math/geometry/symmetry-operator";
 import {StateTransforms} from "molstar/lib/mol-plugin-state/transforms";
 import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
-import {type} from "typedoc/dist/lib/output/themes/default/partials/type";
+import {AlignedRegion, TargetAlignment} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
+import {AlignmentMapper as AM} from "../../../../Utils/AlignmentMapper";
+import {compile} from 'molstar/lib/mol-script/runtime/query/compiler';
+import reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
 
 let refData: Structure|undefined = undefined;
-let refId: {entryId:string;entityId:string;}|undefined = undefined;
-export const AlignmentRepresentationPresetProvider = StructureRepresentationPresetProvider<{pdb?:{entryId:string;entityId:string;};},any>({
+let refParams: StructureAlignmentParamsType|undefined = undefined;
+export const AlignmentRepresentationPresetProvider = StructureRepresentationPresetProvider<{pdb?:{entryId:string;entityId:string;};targetAlignment?:TargetAlignment;},any>({
         id: 'alignment-to-reference',
         display: {
             name: 'Alignemnt to Reference'
         },
         isApplicable: (structureRef: PluginStateObject.Molecule.Structure, plugin: PluginContext): boolean => true,
         params: (structureRef: PluginStateObject.Molecule.Structure | undefined, plugin: PluginContext) => ({
-            pdb: PD.Value<{entryId:string;entityId:string;}|undefined>(undefined)
+            pdb: PD.Value<{entryId:string;entityId:string;}|undefined>(undefined),
+            targetAlignment: PD.Value<TargetAlignment|undefined>(undefined)
         }),
-        apply: async (structureRef: StateObjectRef<PluginStateObject.Molecule.Structure>, params: {pdb?:{entryId:string;entityId:string;};}, plugin: PluginContext) => {
+        apply: async (structureRef: StateObjectRef<PluginStateObject.Molecule.Structure>, params: {pdb?:{entryId:string;entityId:string;};targetAlignment?: TargetAlignment;}, plugin: PluginContext) => {
             const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, structureRef);
             if(!structureCell) return;
             const structure = structureCell.obj!.data;
-            if(plugin.managers.structure.hierarchy.current.structures.length == 1){
-                refId = params.pdb
-            }
-            if(refId && params.pdb){
-                await structuralAlignment(plugin, refId, params.pdb, structure);
-            }
+
+
             const entryId = params.pdb?.entryId!;
             const entityId = params.pdb?.entityId!;
             const l = StructureElement.Location.create(structure);
@@ -62,6 +68,22 @@ export const AlignmentRepresentationPresetProvider = StructureRepresentationPres
                     const alignedOperators = SP.unit.pdbx_struct_oper_list_ids(l);
                     if(alignedType != "polymer")
                         return;
+                    if(plugin.managers.structure.hierarchy.current.structures.length == 1){
+                        refParams = {
+                            entryId: entryId,
+                            labelAsymId: alignedAsymId,
+                            operatorName:alignedOperatorName,
+                            targetAlignment:params.targetAlignment!
+                        };
+                    }
+                    if(refParams && params.pdb){
+                        await structuralAlignment(plugin, refParams, {
+                            entryId: entryId,
+                            labelAsymId: alignedAsymId,
+                            operatorName:alignedOperatorName,
+                            targetAlignment:params.targetAlignment!
+                        }, structure);
+                    }
                     const comp = await plugin.builders.structure.tryCreateComponentFromExpression(
                         structureCell,
                         MS.struct.generator.atomGroups({
@@ -167,20 +189,45 @@ export const AlignmentRepresentationPresetProvider = StructureRepresentationPres
         }
     });
 
-async function structuralAlignment(plugin: PluginContext, ref:{entryId:string;entityId:string;}, pdb:{entryId:string;entityId:string;}, structure: Structure): Promise<void> {
+
+type StructureAlignmentParamsType = {
+    entryId:string;
+    labelAsymId:string;
+    operatorName:string;
+    targetAlignment:TargetAlignment;
+};
+async function structuralAlignment(plugin: PluginContext, ref:StructureAlignmentParamsType, pdb:StructureAlignmentParamsType, structure: Structure): Promise<void> {
     if(ref.entryId == pdb.entryId){
         refData = structure;
     }else{
+        const pdbResIndexes: number[] = [];
+        const refResIndexes: number[] = [];
+        if(ref.targetAlignment?.aligned_regions && pdb.targetAlignment?.aligned_regions){
+            const alignmentList = AM.getAllTargetIntersections( ref.targetAlignment.aligned_regions as AlignedRegion[], pdb.targetAlignment.aligned_regions as AlignedRegion[])
+            alignmentList.forEach(alignment=>{
+                const refRange = AM.range(alignment[0].target_begin, alignment[0].target_end);
+                const pdbRange = AM.range(alignment[1].target_begin, alignment[1].target_end);
+                refRange.forEach((refIndex,n)=>{
+                    const pdbIndex = pdbRange[n];
+                    const pdbLoci =  residueToLoci(pdb, pdbIndex, structure);
+                    const refLoci =  residueToLoci(refParams!, refIndex, refData!);
+                    if(!Loci.isEmpty(pdbLoci) && !Loci.isEmpty(refLoci)){
+                        pdbResIndexes.push(pdbIndex)
+                        refResIndexes.push(refIndex)
+                    }
+                });
+            })
+        }
         const pdbData: Structure = structure;
-        const pdbUnit:Unit|undefined = findFirstEntityUnit(pdbData,pdb.entityId);
-        const refUnit:Unit|undefined =  refData ? findFirstEntityUnit(refData, ref.entityId) : undefined;
+        const pdbUnit:Unit|undefined = findFirstInstanceUnit(pdbData,pdb.labelAsymId);
+        const refUnit:Unit|undefined =  refData ? findFirstInstanceUnit(refData, ref.labelAsymId) : undefined;
         if(pdbData && pdbUnit && refData && refUnit){
-            const refLoci: Loci = Structure.toStructureElementLoci(Structure.create([refUnit]));
-            const pdbLoci: Loci = Structure.toStructureElementLoci(Structure.create([pdbUnit]));
+            const refLoci: Loci = residueListToLoci(refParams!, refResIndexes, refData);
+            const pdbLoci: Loci = residueListToLoci(pdb, pdbResIndexes, structure);
             if(StructureElement.Loci.is(refLoci) && StructureElement.Loci.is(pdbLoci)) {
                 const pivot = plugin.managers.structure.hierarchy.findStructure(refLoci.structure);
                 const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem;
-                const transforms = alignAndSuperpose([refLoci, pdbLoci]);
+                const transforms = superpose([refLoci, pdbLoci]);
                 const { bTransform } = transforms[0];
                 await transform(plugin, plugin.helpers.substructureParent.get(pdbData)!, bTransform, coordinateSystem);
             }
@@ -188,12 +235,11 @@ async function structuralAlignment(plugin: PluginContext, ref:{entryId:string;en
     }
 }
 
-function findFirstEntityUnit(structure: Structure, entityId: string): Unit|undefined {
+function findFirstInstanceUnit(structure: Structure, labelAsymId: string): Unit|undefined {
     const l = StructureElement.Location.create(structure);
     for(const unit of structure.units) {
         StructureElement.Location.set(l, structure, unit, unit.elements[0]);
-        const unitEntityId = SP.chain.label_entity_id(l);
-        if (unitEntityId == entityId) {
+        if (SP.chain.label_asym_id(l) == labelAsymId) {
             return unit;
         }
     }
@@ -220,4 +266,32 @@ async function transform(plugin:PluginContext, s: StateObjectRef<PluginStateObje
         : plugin.state.data.build().to(s)
             .insert(StateTransforms.Model.TransformStructureConformation, params, { tags: SuperpositionTag });
     await plugin.runTask(plugin.state.data.updateTree(b));
+}
+
+function residueToLoci(pdb:StructureAlignmentParamsType, pdbIndex:number, structure: Structure): Loci {
+    const expression = MS.struct.generator.atomGroups({
+        'chain-test': MS.core.logic.and([
+            MS.core.rel.eq([MS.ammp('label_asym_id'), pdb.labelAsymId]),
+            MS.core.rel.eq([MS.acp('operatorName'), pdb.operatorName])
+        ]),
+        'residue-test':MS.core.rel.eq([MS.ammp('label_seq_id'), pdbIndex])
+    });
+    const query = compile<StructureSelection>(expression);
+    const selection = query(new QueryContext(structure));
+    return StructureSelection.toLociWithSourceUnits(selection);
+}
+
+function residueListToLoci(pdb:StructureAlignmentParamsType, indexList:number[], structure: Structure): Loci {
+    const expression = MS.struct.generator.atomGroups({
+        'chain-test': MS.core.logic.and([
+            MS.core.rel.eq([MS.ammp('label_asym_id'), pdb.labelAsymId]),
+            MS.core.rel.eq([MS.acp('operatorName'), pdb.operatorName])
+        ]),
+        'residue-test':MS.core.logic.or(
+            indexList.map(index=>MS.core.rel.eq([MS.ammp('label_seq_id'), index]))
+        )
+    });
+    const query = compile<StructureSelection>(expression);
+    const selection = query(new QueryContext(structure));
+    return StructureSelection.toLociWithSourceUnits(selection);
 }

+ 5 - 1
src/RcsbFvStructure/StructureViewers/MolstarViewer/TrajectoryPresetProvider/AlignmentTrajectoryPresetProvider.ts

@@ -16,9 +16,11 @@ import {
 } from "molstar/lib/mol-plugin-state/builder/structure/representation-preset";
 import {PLDDTConfidenceColorThemeProvider} from "molstar/lib/extensions/model-archive/quality-assessment/color/plddt";
 import {AlignmentRepresentationPresetProvider} from "./AlignmentRepresentationPresetProvider";
+import {TargetAlignment} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
 
 export type TrajectoryParamsType = {
     pdb?: {entryId:string;entityId:string;};
+    targetAlignment?: TargetAlignment;
     assemblyId?: string;
     modelIndex?: number;
     plddt?: 'off' | 'single-chain' | 'on';
@@ -34,6 +36,7 @@ export const AlignmentTrajectoryPresetProvider = TrajectoryHierarchyPresetProvid
     isApplicable: (trajectory: PluginStateObject.Molecule.Trajectory, plugin: PluginContext): boolean => true,
     params: (trajectory: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext):ParamDefinition.For<TrajectoryParamsType> => ({
         pdb:PD.Value<{entryId:string;entityId:string;}|undefined>(undefined),
+        targetAlignment: PD.Value<TargetAlignment|undefined>(undefined),
         assemblyId:PD.Value<string|undefined>(undefined),
         modelIndex:PD.Value<number|undefined>(undefined),
         plddt:PD.Value<'off' | 'single-chain' | 'on' | undefined>(undefined)
@@ -60,7 +63,8 @@ export const AlignmentTrajectoryPresetProvider = TrajectoryHierarchyPresetProvid
             structureProperties,
             AlignmentRepresentationPresetProvider,
             {
-                pdb:params.pdb
+                pdb:params.pdb,
+                targetAlignment:params.targetAlignment
             }
         );
 

+ 142 - 0
src/Utils/AlignmentMapper.ts

@@ -0,0 +1,142 @@
+/*
+* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
+* @author Joan Segura Mora <joan.segura@rcsb.org>
+*/
+
+import {AlignedRegion} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
+
+export namespace AlignmentMapper {
+
+    export function mapRangeToRegionList(range:{begin:number,end:number}, regionList:AlignedRegion[], pointer:"query"|"target"): {begin:number,end:number}[]|undefined {
+        return regionList.map(region=>mapRangeToRegion(range,region,pointer)).filter((o): o is typeof range => o!=null);
+    }
+
+    export function mapRangeToRegion(range:{begin:number,end:number}, region:AlignedRegion, pointer:"query"|"target"): {begin:number,end:number}|undefined {
+        if(!areIntersectingRegions({query_begin:range.begin, query_end: range.end, target_begin:range.begin, target_end:range.end}, region, pointer))
+            return;
+        const cPointer: "query"|"target" = pointer == "query" ? "target" : "query";
+        return {
+            begin: mapPointToRegion(range.begin, region, pointer) ?? region[ALIGNMENT_POINTER[cPointer].begin],
+            end: mapPointToRegion(range.end, region, pointer) ?? region[ALIGNMENT_POINTER[cPointer].end]
+        }
+    }
+
+    export function mapPointToRegion(p:number, region:AlignedRegion, pointer:"query"|"target"): number|undefined {
+        if(region[ALIGNMENT_POINTER[pointer].begin]<=p && p<=region[ALIGNMENT_POINTER[pointer].end]) {
+            const cPointer: "query"|"target" = pointer == "query" ? "target" : "query";
+            return region[ALIGNMENT_POINTER[cPointer].begin] + (p-region[ALIGNMENT_POINTER[pointer].begin]);
+        }
+        return;
+    }
+
+    export function areIntersectingRegions(regionA:AlignedRegion,regionB:AlignedRegion, pointer:"query"|"target"): boolean {
+        return !(regionA[ALIGNMENT_POINTER[pointer].begin] > regionB[ALIGNMENT_POINTER[pointer].end] || regionA[ALIGNMENT_POINTER[pointer].end] < regionB[ALIGNMENT_POINTER[pointer].begin]);
+    }
+
+    export function getAllQueryIntersections(regionListA:AlignedRegion[],regionListB:AlignedRegion[]): [{query_begin:number;query_end:number;},{query_begin:number;query_end:number;}][]  {
+        return regionListA.map(
+            regionA=>regionListB.filter(
+                regionB=>areIntersectingRegions(regionA,regionB,"target")
+            ).map(
+                regionB=>getQueryIntersection(regionA,regionB)
+            )
+        ).flat();
+    }
+
+    export function getAllTargetIntersections(regionListA:AlignedRegion[],regionListB:AlignedRegion[]): [{target_begin:number;target_end:number;},{target_begin:number;target_end:number;}][]  {
+        return regionListA.map(
+            regionA=>regionListB.filter(
+                regionB=>areIntersectingRegions(regionA,regionB,"query")
+            ).map(
+                regionB=>getTargetIntersection(regionA,regionB)
+            )
+        ).flat();
+    }
+
+    export function getQueryIntersection(regionA:AlignedRegion,regionB:AlignedRegion): [{query_begin:number;query_end:number;},{query_begin:number;query_end:number;}] {
+        const [targetRegionA, targetRegionB] = getTargetIntersection(swapQueryAndTarget(regionA), swapQueryAndTarget(regionB));
+        return [{
+            query_begin:targetRegionA.target_begin,
+            query_end:targetRegionA.target_end
+        },{
+            query_begin:targetRegionB.target_begin,
+            query_end:targetRegionB.target_end
+        }];
+    }
+
+    export function getTargetIntersection(regionA:AlignedRegion,regionB:AlignedRegion): [{target_begin:number;target_end:number;},{target_begin:number;target_end:number;}]  {
+        const out = {
+            target_begin_A: 0,
+            target_end_A: 0,
+            target_begin_B: 0,
+            target_end_B: 0
+        }
+        if( typeof mapPointToRegion( regionA.query_begin, regionB, "query" ) === "number"){
+            out.target_begin_A = regionA.target_begin;
+            out.target_begin_B = mapPointToRegion( regionA.query_begin, regionB, "query" )!;
+        }else if( typeof mapPointToRegion( regionB.query_begin, regionA, "query" ) === "number" ){
+            out.target_begin_A = mapPointToRegion( regionB.query_begin, regionA, "query" )!;
+            out.target_begin_B = regionB.target_begin;
+        }else{
+            throw "Intersection Error: No intersection was found";
+        }
+
+        if( typeof mapPointToRegion( regionA.query_end, regionB, "query" ) === "number"){
+            out.target_end_A = regionA.target_end;
+            out.target_end_B = mapPointToRegion( regionA.query_end, regionB, "query" )!;
+        }else if( typeof mapPointToRegion( regionB.query_end, regionA, "query" ) === "number" ){
+            out.target_end_A = mapPointToRegion( regionB.query_end, regionA, "query" )!;
+            out.target_end_B = regionB.target_end;
+        }else{
+            throw "Intersection Error: No intersection was found";
+        }
+
+        if(out.target_end_A-out.target_begin_A != out.target_end_B-out.target_begin_B)
+            throw `Intersection Error: Inconsistent intersection range [${out.target_begin_A},${out.target_end_A}] [${out.target_begin_B},${out.target_end_B}]`;
+
+        return [{
+            target_begin:out.target_begin_A,
+            target_end:out.target_end_A
+        },{
+            target_begin:out.target_begin_B,
+            target_end:out.target_end_B
+        }];
+    }
+
+    export function  range(start:number, stop:number, step:number =1): number[] {
+        const length = Math.ceil((stop+1 - start) / step);
+        return Array.from({length}, (_, i) => (i * step) + start);
+    }
+
+    export function swapQueryAndTarget(region:AlignedRegion): AlignedRegion{
+        return {
+            query_begin: region.target_begin,
+            query_end: region.target_end,
+            target_begin: region.query_begin,
+            target_end: region.query_end
+        };
+    }
+
+}
+
+interface AlignmentPointerInterface {
+    query: {
+        begin: "query_begin",
+        end: "query_end"
+    },
+    target: {
+        begin: "target_begin",
+        end: "target_end"
+    }
+}
+
+const ALIGNMENT_POINTER: AlignmentPointerInterface = {
+    query: {
+        begin: "query_begin",
+        end: "query_end"
+    },
+    target: {
+        begin: "target_begin",
+        end: "target_end"
+    }
+}