JAL-2349 JAL-3855 Integrate pAE retrieval in StructureFile so it can be transferred...
[jalview.git] / src / mc_view / PDBChain.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package mc_view;
22
23 import java.awt.Color;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Vector;
27
28 import jalview.analysis.AlignSeq;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.Annotation;
31 import jalview.datamodel.ContactMatrixI;
32 import jalview.datamodel.Mapping;
33 import jalview.datamodel.Sequence;
34 import jalview.datamodel.SequenceFeature;
35 import jalview.datamodel.SequenceI;
36 import jalview.datamodel.annotations.AnnotationRowBuilder;
37 import jalview.schemes.ColourSchemeI;
38 import jalview.schemes.ResidueProperties;
39 import jalview.structure.StructureImportSettings;
40 import jalview.structure.StructureMapping;
41 import jalview.util.Comparison;
42
43 public class PDBChain
44 {
45   public static final String RESNUM_FEATURE = "RESNUM";
46
47   private static final String IEASTATUS = "IEA:jalview";
48
49   public String id;
50
51   public Vector<Bond> bonds = new Vector<>();
52
53   public Vector<Atom> atoms = new Vector<>();
54
55   public Vector<Residue> residues = new Vector<>();
56
57   public int offset;
58
59   /**
60    * sequence is the sequence extracted by the chain parsing code
61    */
62   public SequenceI sequence;
63
64   /**
65    * shadow is the sequence created by any other parsing processes (e.g. Jmol,
66    * RNAview)
67    */
68   public SequenceI shadow = null;
69
70   public boolean isNa = false;
71
72   public boolean isVisible = true;
73
74   public int pdbstart = 0;
75
76   public int pdbend = 0;
77
78   public int seqstart = 0;
79
80   public int seqend = 0;
81
82   public String pdbid = "";
83
84   AnnotationRowBuilder tfacTemplate = new AnnotationRowBuilder(
85           "TemperatureFactor");
86
87   public PDBChain(String thePdbid, String theId,
88           AnnotationRowBuilder template)
89   {
90     this(thePdbid, theId);
91     if (template != null)
92     {
93       tfacTemplate = template;
94     }
95   }
96
97   /**
98    * import chain data assuming Temperature Factor is in the Temperature Factor
99    * column
100    * 
101    * @param thePdbid
102    * @param theId
103    */
104   public PDBChain(String thePdbid, String theId)
105   {
106     this.pdbid = thePdbid == null ? thePdbid
107             : thePdbid.toLowerCase(Locale.ROOT);
108     this.id = theId;
109   }
110
111   /**
112    * character used to write newlines
113    */
114   protected String newline = System.getProperty("line.separator");
115
116   public Mapping shadowMap;
117
118   public void setNewlineString(String nl)
119   {
120     newline = nl;
121   }
122
123   public String getNewlineString()
124   {
125     return newline;
126   }
127
128   public String print()
129   {
130     StringBuilder tmp = new StringBuilder(256);
131
132     for (Bond b : bonds)
133     {
134       tmp.append(b.at1.resName).append(" ").append(b.at1.resNumber)
135               .append(" ").append(offset).append(newline);
136     }
137
138     return tmp.toString();
139   }
140
141   /**
142    * Annotate the residues with their corresponding positions in s1 using the
143    * alignment in as NOTE: This clears all atom.alignmentMapping values on the
144    * structure.
145    * 
146    * @param as
147    * @param s1
148    */
149   public void makeExactMapping(AlignSeq as, SequenceI s1)
150   {
151     int pdbpos = as.getSeq2Start() - 2;
152     int alignpos = s1.getStart() + as.getSeq1Start() - 3;
153     // first clear out any old alignmentMapping values:
154     for (Atom atom : atoms)
155     {
156       atom.alignmentMapping = -1;
157     }
158     // and now trace the alignment onto the atom set.
159     for (int i = 0; i < as.astr1.length(); i++)
160     {
161       if (as.astr1.charAt(i) != '-')
162       {
163         alignpos++;
164       }
165
166       if (as.astr2.charAt(i) != '-')
167       {
168         pdbpos++;
169       }
170
171       boolean sameResidue = Comparison.isSameResidue(as.astr1.charAt(i),
172               as.astr2.charAt(i), false);
173       if (sameResidue)
174       {
175         if (pdbpos >= residues.size())
176         {
177           continue;
178         }
179         Residue res = residues.elementAt(pdbpos);
180         for (Atom atom : res.atoms)
181         {
182           atom.alignmentMapping = alignpos;
183         }
184       }
185     }
186   }
187
188   /**
189    * Annotate the residues with their corresponding positions in s1 using the
190    * alignment in as NOTE: This clears all atom.alignmentMapping values on the
191    * structure.
192    * 
193    * @param as
194    * @param s1
195    */
196   public void makeExactMapping(StructureMapping mapping, SequenceI s1)
197   {
198     // first clear out any old alignmentMapping values:
199     for (Atom atom : atoms)
200     {
201       atom.alignmentMapping = -1;
202     }
203     SequenceI ds = s1;
204     while (ds.getDatasetSequence() != null)
205     {
206       ds = ds.getDatasetSequence();
207     }
208     int pdboffset = 0;
209     for (Residue res : residues)
210     {
211       // res.number isn't set correctly for discontinuous/mismapped residues
212       int seqpos = mapping.getSeqPos(res.atoms.get(0).resNumber);
213       char strchar = sequence.getCharAt(pdboffset++);
214       if (seqpos == StructureMapping.UNASSIGNED_VALUE)
215       {
216         continue;
217       }
218       char seqchar = ds.getCharAt(seqpos - ds.getStart());
219
220       boolean sameResidue = Comparison.isSameResidue(seqchar, strchar,
221               false);
222       if (sameResidue)
223       {
224         for (Atom atom : res.atoms)
225         {
226           atom.alignmentMapping = seqpos - 1;
227         }
228       }
229     }
230   }
231
232   /**
233    * Copies over the RESNUM seqfeatures from the internal chain sequence to the
234    * mapped sequence
235    * 
236    * @param seq
237    * @param status
238    *          The Status of the transferred annotation
239    * 
240    * @param altPDBID
241    *          the group id for the features on the destination sequence (e.g.
242    *          the official accession ID)
243    */
244   public void transferRESNUMFeatures(SequenceI seq, String status,
245           String altPDBID)
246   {
247     if (altPDBID == null)
248     {
249       altPDBID = pdbid;
250     }
251     SequenceI sq = seq;
252     while (sq != null && sq.getDatasetSequence() != null)
253     {
254       sq = sq.getDatasetSequence();
255       if (sq == sequence)
256       {
257         return;
258       }
259     }
260
261     /*
262      * Remove any existing features for this chain if they exist ?
263      * SequenceFeature[] seqsfeatures=seq.getSequenceFeatures(); int
264      * totfeat=seqsfeatures.length; // Remove any features for this exact chain
265      * ? for (int i=0; i<seqsfeatures.length; i++) { }
266      */
267     if (status == null)
268     {
269       status = PDBChain.IEASTATUS;
270     }
271
272     List<SequenceFeature> features = sequence.getSequenceFeatures();
273     for (SequenceFeature feature : features)
274     {
275       if (feature.getFeatureGroup() != null
276               && feature.getFeatureGroup().equals(pdbid))
277       {
278         int newBegin = 1
279                 + residues.elementAt(feature.getBegin() - offset).atoms
280                         .elementAt(0).alignmentMapping;
281         int newEnd = 1 + residues.elementAt(feature.getEnd() - offset).atoms
282                 .elementAt(0).alignmentMapping;
283         SequenceFeature tx = new SequenceFeature(feature, newBegin, newEnd,
284                 altPDBID, feature.getScore());
285         tx.setStatus(status
286                 + ((tx.getStatus() == null || tx.getStatus().length() == 0)
287                         ? ""
288                         : ":" + tx.getStatus()));
289         if (tx.begin != 0 && tx.end != 0)
290         {
291           sq.addSequenceFeature(tx);
292         }
293       }
294     }
295   }
296
297   /**
298    * Traverses the list of residues and constructs bonds where CA-to-CA atoms or
299    * P-to-P atoms are found. Also sets the 'isNa' flag if more than 99% of
300    * residues contain a P not a CA.
301    */
302   public void makeCaBondList()
303   {
304     boolean na = false;
305     int numNa = 0;
306     for (int i = 0; i < (residues.size() - 1); i++)
307     {
308       Residue tmpres = residues.elementAt(i);
309       Residue tmpres2 = residues.elementAt(i + 1);
310       Atom at1 = tmpres.findAtom("CA");
311       Atom at2 = tmpres2.findAtom("CA");
312       na = false;
313       if ((at1 == null) && (at2 == null))
314       {
315         na = true;
316         at1 = tmpres.findAtom("P");
317         at2 = tmpres2.findAtom("P");
318       }
319       if ((at1 != null) && (at2 != null))
320       {
321         if (at1.chain.equals(at2.chain))
322         {
323           if (na)
324           {
325             numNa++;
326           }
327           makeBond(at1, at2);
328         }
329       }
330       else
331       {
332         System.out.println("not found " + i);
333       }
334     }
335
336     /*
337      * If > 99% 'P', flag as nucleotide; note the count doesn't include the last
338      * residue
339      */
340     if (residues.size() > 1 && (numNa / (residues.size() - 1) > 0.99))
341     {
342       isNa = true;
343     }
344   }
345
346   /**
347    * Construct a bond from atom1 to atom2 and add it to the list of bonds for
348    * this chain
349    * 
350    * @param at1
351    * @param at2
352    */
353   public void makeBond(Atom at1, Atom at2)
354   {
355     bonds.addElement(new Bond(at1, at2));
356   }
357
358   /**
359    * Traverses the list of atoms and
360    * <ul>
361    * <li>constructs a list of Residues, each containing all the atoms that share
362    * the same residue number</li>
363    * <li>adds a RESNUM sequence feature for each position</li>
364    * <li>creates the sequence string</li>
365    * <li>determines if nucleotide</li>
366    * <li>saves the residue number of the first atom as 'offset'</li>
367    * <li>adds temp factor annotation if the flag is set to do so</li>
368    * </ul>
369    * 
370    * @param visibleChainAnnotation
371    */
372   public void makeResidueList(boolean visibleChainAnnotation)
373   {
374     int count = 0;
375     Object symbol;
376     boolean deoxyn = false;
377     boolean nucleotide = false;
378     StringBuilder seq = new StringBuilder(256);
379     Vector<SequenceFeature> resFeatures = new Vector<>();
380     Vector<Annotation> resAnnotation = new Vector<>();
381     int iSize = atoms.size() - 1;
382     int resNumber = -1;
383     char insCode = ' ';
384
385     for (int i = 0; i <= iSize; i++)
386     {
387       Atom tmp = atoms.elementAt(i);
388       resNumber = tmp.resNumber;
389       insCode = tmp.insCode;
390
391       int res = resNumber;
392       char ins = insCode;
393
394       if (i == 0)
395       {
396         offset = resNumber;
397       }
398
399       Vector<Atom> resAtoms = new Vector<>();
400       // Add atoms to a vector while the residue number
401       // remains the same as the first atom's resNumber (res)
402       while ((resNumber == res) && (ins == insCode) && (i < atoms.size()))
403       {
404         resAtoms.add(atoms.elementAt(i));
405         i++;
406
407         if (i < atoms.size())
408         {
409           resNumber = atoms.elementAt(i).resNumber;
410           insCode = atoms.elementAt(i).insCode;
411         }
412         else
413         {
414           resNumber++;
415         }
416       }
417
418       // We need this to keep in step with the outer for i = loop
419       i--;
420
421       // Add inserted residues as features to the base residue
422       Atom currAtom = resAtoms.get(0);
423       if (currAtom.insCode != ' ' && !residues.isEmpty()
424               && residues.lastElement().atoms
425                       .get(0).resNumber == currAtom.resNumber)
426       {
427         String desc = currAtom.resName + ":" + currAtom.resNumIns + " "
428                 + pdbid + id;
429         SequenceFeature sf = new SequenceFeature("INSERTION", desc,
430                 offset + count - 1, offset + count - 1, "PDB_INS");
431         resFeatures.addElement(sf);
432         residues.lastElement().atoms.addAll(resAtoms);
433       }
434       else
435       {
436         // Make a new Residue object with the new atoms vector
437         residues.addElement(new Residue(resAtoms, resNumber - 1, count));
438
439         Residue tmpres = residues.lastElement();
440         Atom tmpat = tmpres.atoms.get(0);
441         // Make A new SequenceFeature for the current residue numbering
442         String desc = tmpat.resName + ":" + tmpat.resNumIns + " " + pdbid
443                 + id;
444         SequenceFeature sf = new SequenceFeature(RESNUM_FEATURE, desc,
445                 offset + count, offset + count, pdbid);
446         resFeatures.addElement(sf);
447         resAnnotation.addElement(new Annotation(tmpat.tfactor));
448         // Keep totting up the sequence
449
450         if ((symbol = ResidueProperties.getAA3Hash()
451                 .get(tmpat.resName)) == null)
452         {
453           String nucname = tmpat.resName.trim();
454           // use the aaIndex rather than call 'toLower' - which would take a bit
455           // more time.
456           deoxyn = nucname.length() == 2
457                   && ResidueProperties.aaIndex[nucname
458                           .charAt(0)] == ResidueProperties.aaIndex['D'];
459           if (tmpat.name.equalsIgnoreCase("CA")
460                   || ResidueProperties.nucleotideIndex[nucname
461                           .charAt((deoxyn ? 1 : 0))] == -1)
462           {
463             char r = ResidueProperties.getSingleCharacterCode(
464                     ResidueProperties.getCanonicalAminoAcid(tmpat.resName));
465             seq.append(r == '0' ? 'X' : r);
466             // System.err.println("PDBReader:Null aa3Hash for " +
467             // tmpat.resName);
468           }
469           else
470           {
471             // nucleotide flag
472             nucleotide = true;
473             seq.append(nucname.charAt((deoxyn ? 1 : 0)));
474           }
475         }
476         else
477         {
478           if (nucleotide)
479           {
480             System.err.println(
481                     "Warning: mixed nucleotide and amino acid chain.. its gonna do bad things to you!");
482           }
483           seq.append(ResidueProperties.aa[((Integer) symbol).intValue()]);
484         }
485         count++;
486       }
487     }
488
489     if (id.length() < 1)
490     {
491       id = " ";
492     }
493     isNa = nucleotide;
494     sequence = new Sequence(id, seq.toString(), offset, resNumber - 1); // Note:
495     // resNumber-offset
496     // ~=
497     // seq.size()
498     // Add normalised feature scores to RESNUM indicating start/end of sequence
499     // sf.setScore(offset+count);
500
501     // System.out.println("PDB Sequence is :\nSequence = " + seq);
502     // System.out.println("No of residues = " + residues.size());
503
504     if (StructureImportSettings.isShowSeqFeatures())
505     {
506       iSize = resFeatures.size();
507       for (int i = 0; i < iSize; i++)
508       {
509         sequence.addSequenceFeature(resFeatures.elementAt(i));
510         resFeatures.setElementAt(null, i);
511       }
512     }
513     if (visibleChainAnnotation)
514     {
515       Annotation[] annots = new Annotation[resAnnotation.size()];
516       float max = 0f;
517       float min = 0f;
518       iSize = annots.length;
519       for (int i = 0; i < iSize; i++)
520       {
521         annots[i] = resAnnotation.elementAt(i);
522         tfacTemplate.processAnnotation(annots[i]);
523         max = Math.max(max, annots[i].value);
524         min = Math.min(min, annots[i].value);
525         resAnnotation.setElementAt(null, i);
526       }
527       if (tfacTemplate.isHasMinMax())
528       {
529         max = tfacTemplate.getMax();
530         min = tfacTemplate.getMin();
531       }
532
533       AlignmentAnnotation tfactorann = new AlignmentAnnotation(
534               tfacTemplate.getName(),
535               (tfacTemplate.isHasDescription()
536                       ? tfacTemplate.getDescription()
537                       : tfacTemplate.getName()) + " for " + pdbid + id,
538               annots, min, max, AlignmentAnnotation.LINE_GRAPH);
539       tfactorann.setCalcId(getClass().getName());
540
541       tfactorann.setSequenceRef(sequence);
542       sequence.addAlignmentAnnotation(tfactorann);
543     }
544   }
545
546   /**
547    * Colour start/end of bonds by charge
548    * <ul>
549    * <li>ASP and GLU red</li>
550    * <li>LYS and ARG blue</li>
551    * <li>CYS yellow</li>
552    * <li>others light gray</li>
553    * </ul>
554    */
555   public void setChargeColours()
556   {
557     for (Bond b : bonds)
558     {
559       if (b.at1 != null && b.at2 != null)
560       {
561         b.startCol = getChargeColour(b.at1.resName);
562         b.endCol = getChargeColour(b.at2.resName);
563       }
564       else
565       {
566         b.startCol = Color.gray;
567         b.endCol = Color.gray;
568       }
569     }
570   }
571
572   public static Color getChargeColour(String resName)
573   {
574     Color result = Color.lightGray;
575     if ("ASP".equals(resName) || "GLU".equals(resName))
576     {
577       result = Color.red;
578     }
579     else if ("LYS".equals(resName) || "ARG".equals(resName))
580     {
581       result = Color.blue;
582     }
583     else if ("CYS".equals(resName))
584     {
585       result = Color.yellow;
586     }
587     return result;
588   }
589
590   /**
591    * Sets the start/end colours of bonds to those of the start/end atoms
592    * according to the specified colour scheme. Note: currently only works for
593    * peptide residues.
594    * 
595    * @param cs
596    */
597   public void setChainColours(ColourSchemeI cs)
598   {
599     int index;
600     for (Bond b : bonds)
601     {
602       try
603       {
604         index = ResidueProperties.aa3Hash.get(b.at1.resName).intValue();
605         b.startCol = cs.findColour(ResidueProperties.aa[index].charAt(0), 0,
606                 null, null, 0f);
607
608         index = ResidueProperties.aa3Hash.get(b.at2.resName).intValue();
609         b.endCol = cs.findColour(ResidueProperties.aa[index].charAt(0), 0,
610                 null, null, 0f);
611
612       } catch (Exception e)
613       {
614         b.startCol = Color.gray;
615         b.endCol = Color.gray;
616       }
617     }
618   }
619
620   public void setChainColours(Color col)
621   {
622     for (Bond b : bonds)
623     {
624       b.startCol = col;
625       b.endCol = col;
626     }
627   }
628
629   /**
630    * copy any sequence annotation onto the sequence mapped using the provided
631    * StructureMapping
632    * 
633    * @param mapping
634    *          - positional mapping between destination sequence and pdb resnum
635    * @param sqmpping
636    *          - mapping between destination sequence and local chain
637    */
638   public void transferResidueAnnotation(StructureMapping mapping,
639           jalview.datamodel.Mapping sqmpping)
640   {
641     SequenceI sq = mapping.getSequence();
642     SequenceI dsq = sq;
643     if (sqmpping == null)
644     {
645       // SIFTS mappings are recorded in the StructureMapping object...
646
647       sqmpping = mapping.getSeqToPdbMapping();
648     }
649     if (sq != null)
650     {
651       while (dsq.getDatasetSequence() != null)
652       {
653         dsq = dsq.getDatasetSequence();
654       }
655       // any annotation will be transferred onto the dataset sequence
656
657       if (shadow != null && shadow.getAnnotation() != null)
658       {
659
660         for (AlignmentAnnotation ana : shadow.getAnnotation())
661         {
662           // match on calcId, label and description so annotations from
663           // different structures are preserved
664           List<AlignmentAnnotation> transfer = sq.getAlignmentAnnotations(
665                   ana.getCalcId(), ana.label, ana.description);
666           if (transfer == null || transfer.size() == 0)
667           {
668             ContactMatrixI cm = shadow.getContactMatrixFor(ana);
669             ana = new AlignmentAnnotation(ana);
670             // TODO map contact matrix under mapping
671             ana.liftOver(sequence, shadowMap);
672             ana.liftOver(dsq, sqmpping);
673             dsq.addAlignmentAnnotation(ana);
674             if (cm != null)
675             {
676               dsq.addContactListFor(ana, cm);
677             }
678           }
679           else
680           {
681             continue;
682           }
683         }
684       }
685       else
686       {
687         if (sequence != null && sequence.getAnnotation() != null)
688         {
689           for (AlignmentAnnotation ana : sequence.getAnnotation())
690           {
691             // match on calcId, label and description so annotations from
692             // different structures are preserved
693             List<AlignmentAnnotation> transfer = dsq
694                     .getAlignmentAnnotations(ana.getCalcId(), ana.label,
695                             ana.description);
696             if (transfer == null || transfer.size() == 0)
697             {
698               ContactMatrixI cm = sequence.getContactMatrixFor(ana);
699               ana = new AlignmentAnnotation(ana);
700               ana.liftOver(dsq, sqmpping);
701               dsq.addAlignmentAnnotation(ana);
702               if (cm != null)
703               {
704                 dsq.addContactListFor(ana, cm);
705               }
706             }
707             else
708             {
709               continue;
710             }
711           }
712         }
713       }
714       if (false)
715       {
716         // Useful for debugging mappings - adds annotation for mapped position
717         float min = -1, max = 0;
718         Annotation[] an = new Annotation[sq.getEnd() - sq.getStart() + 1];
719         for (int i = sq.getStart(), j = sq.getEnd(),
720                 k = 0; i <= j; i++, k++)
721         {
722           int prn = mapping.getPDBResNum(k + 1);
723
724           an[k] = new Annotation(prn);
725           if (min == -1)
726           {
727             min = k;
728             max = k;
729           }
730           else
731           {
732             if (min > k)
733             {
734               min = k;
735             }
736             else if (max < k)
737             {
738               max = k;
739             }
740           }
741         }
742         sq.addAlignmentAnnotation(new AlignmentAnnotation("PDB.RESNUM",
743                 "PDB Residue Numbering for " + this.pdbid + ":" + this.id,
744                 an, min, max, AlignmentAnnotation.LINE_GRAPH));
745       }
746     }
747   }
748 }