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