JAL-629 refactoring TFType. Remove i18n identification of annotation.
[jalview.git] / src / jalview / ws / dbsources / EBIAlfaFold.java
1
2 /*
3  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
4  * Copyright (C) $$Year-Rel$$ The Jalview Authors
5  * 
6  * This file is part of Jalview.
7  * 
8  * Jalview is free software: you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License 
10  * as published by the Free Software Foundation, either version 3
11  * of the License, or (at your option) any later version.
12  *  
13  * Jalview is distributed in the hope that it will be useful, but 
14  * WITHOUT ANY WARRANTY; without even the implied warranty 
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
16  * PURPOSE.  See the GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
20  * The Jalview Authors are detailed in the 'AUTHORS' file.
21  */
22 package jalview.ws.dbsources;
23
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32
33 import org.json.simple.parser.ParseException;
34
35 import com.stevesoft.pat.Regex;
36
37 import jalview.api.FeatureSettingsModelI;
38 import jalview.bin.Console;
39 import jalview.datamodel.AlignmentAnnotation;
40 import jalview.datamodel.AlignmentI;
41 import jalview.datamodel.ContactMatrixI;
42 import jalview.datamodel.DBRefEntry;
43 import jalview.datamodel.PDBEntry;
44 import jalview.datamodel.SequenceFeature;
45 import jalview.datamodel.SequenceI;
46 import jalview.gui.Desktop;
47 import jalview.io.DataSourceType;
48 import jalview.io.FileFormat;
49 import jalview.io.FileFormatI;
50 import jalview.io.FormatAdapter;
51 import jalview.io.PDBFeatureSettings;
52 import jalview.structure.StructureSelectionManager;
53 import jalview.util.MessageManager;
54 import jalview.util.Platform;
55 import jalview.ws.datamodel.alphafold.PAEContactMatrix;
56 import jalview.ws.utils.UrlDownloadClient;
57
58 /**
59  * @author JimP
60  * 
61  */
62 public class EBIAlfaFold extends EbiFileRetrievedProxy
63 {
64   private static final String SEPARATOR = "|";
65
66   private static final String COLON = ":";
67
68   private static final int PDB_ID_LENGTH = 4;
69
70   private static String AF_VERSION = "3";
71
72   public EBIAlfaFold()
73   {
74     super();
75   }
76
77   /*
78    * (non-Javadoc)
79    * 
80    * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
81    */
82   @Override
83   public String getAccessionSeparator()
84   {
85     return null;
86   }
87
88   /*
89    * (non-Javadoc)
90    * 
91    * @see jalview.ws.DbSourceProxy#getAccessionValidator()
92    */
93   @Override
94   public Regex getAccessionValidator()
95   {
96     Regex validator = new Regex("(AF-[A-Z]+[0-9]+[A-Z0-9]+-F1)");
97     validator.setIgnoreCase(true);
98     return validator;
99   }
100
101   /*
102    * (non-Javadoc)
103    * 
104    * @see jalview.ws.DbSourceProxy#getDbSource()
105    */
106   @Override
107   public String getDbSource()
108   {
109     return "ALPHAFOLD";
110   }
111
112   /*
113    * (non-Javadoc)
114    * 
115    * @see jalview.ws.DbSourceProxy#getDbVersion()
116    */
117   @Override
118   public String getDbVersion()
119   {
120     return "1";
121   }
122
123   public static String getAlphaFoldCifDownloadUrl(String id, String vnum)
124   {
125     if (vnum == null || vnum.length() == 0)
126     {
127       vnum = AF_VERSION;
128     }
129     return "https://alphafold.ebi.ac.uk/files/" + id + "-model_v" + vnum
130             + ".cif";
131   }
132
133   public static String getAlphaFoldPaeDownloadUrl(String id, String vnum)
134   {
135     if (vnum == null || vnum.length() == 0)
136     {
137       vnum = AF_VERSION;
138     }
139     return "https://alphafold.ebi.ac.uk/files/" + id
140             + "-predicted_aligned_error_v" + vnum + ".json";
141   }
142
143   /*
144    * (non-Javadoc)
145    * 
146    * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
147    */
148   @Override
149   public AlignmentI getSequenceRecords(String queries) throws Exception
150   {
151     return getSequenceRecords(queries, null);
152   }
153
154   public AlignmentI getSequenceRecords(String queries, String retrievalUrl)
155           throws Exception
156   {
157     AlignmentI pdbAlignment = null;
158     String chain = null;
159     String id = null;
160     if (queries.indexOf(COLON) > -1)
161     {
162       chain = queries.substring(queries.indexOf(COLON) + 1);
163       id = queries.substring(0, queries.indexOf(COLON));
164     }
165     else
166     {
167       id = queries;
168     }
169
170     if (!isValidReference(id))
171     {
172       System.err.println(
173               "(AFClient) Ignoring invalid alphafold query: '" + id + "'");
174       stopQuery();
175       return null;
176     }
177     String alphaFoldCif = getAlphaFoldCifDownloadUrl(id, AF_VERSION);
178     if (retrievalUrl != null)
179     {
180       alphaFoldCif = retrievalUrl;
181     }
182
183     try
184     {
185       File tmpFile = File.createTempFile(id, ".cif");
186       Console.debug("Retrieving structure file for " + id + " from "
187               + alphaFoldCif);
188       UrlDownloadClient.download(alphaFoldCif, tmpFile);
189
190       // may not need this check ?
191       file = tmpFile.getAbsolutePath();
192       if (file == null)
193       {
194         return null;
195       }
196
197       pdbAlignment = importDownloadedStructureFromUrl(alphaFoldCif, tmpFile,
198               id, chain, getDbSource(), getDbVersion());
199
200       if (pdbAlignment == null || pdbAlignment.getHeight() < 1)
201       {
202         throw new Exception(MessageManager.formatMessage(
203                 "exception.no_pdb_records_for_chain", new String[]
204                 { id, ((chain == null) ? "' '" : chain) }));
205       }
206       // done during structure retrieval
207       // retrieve_AlphaFold_pAE(id, pdbAlignment, retrievalUrl);
208
209     } catch (Exception ex) // Problem parsing PDB file
210     {
211       stopQuery();
212       throw (ex);
213     }
214     return pdbAlignment;
215   }
216
217   /**
218    * get an alphafold pAE for the given id, and add it to sequence 0 in
219    * pdbAlignment (assuming it came from structurefile parser).
220    * 
221    * @param id
222    * @param pdbAlignment
223    * @param retrievalUrl
224    *          - URL of .mmcif from EBI-AlphaFold - will be used to generate the
225    *          pAE URL automatically
226    * @throws IOException
227    * @throws Exception
228    */
229   public static void retrieve_AlphaFold_pAE(String id,
230           AlignmentI pdbAlignment, String retrievalUrl) throws IOException
231   {
232     // import PAE as contact matrix - assume this will work if there was a
233     // model
234     String paeURL = getAlphaFoldPaeDownloadUrl(id, AF_VERSION);
235
236     if (retrievalUrl != null)
237     {
238       // manufacture the PAE url from a url like ...-model-vN.cif
239       paeURL = retrievalUrl.replace("model", "predicted_aligned_error")
240               .replace(".cif", ".json");
241     }
242
243     File pae = null;
244     try
245     {
246       pae = File.createTempFile(id == null ? "af_pae" : id, "pae_json");
247     } catch (IOException e)
248     {
249       e.printStackTrace();
250     }
251     Console.debug("Downloading pae from " + paeURL + " to " + pae.toString()
252             + "");
253     UrlDownloadClient.download(paeURL, pae);
254     addAlphaFoldPAEToSequence(pdbAlignment, pae, 0, null);
255   }
256
257   public static void addAlphaFoldPAEToSequence(AlignmentI pdbAlignment,
258           File pae, int index, String seqId)
259   {
260     FileInputStream pae_input = null;
261     try
262     {
263       pae_input = new FileInputStream(pae);
264     } catch (FileNotFoundException e)
265     {
266       Console.error(
267               "Could not find pAE file '" + pae.getAbsolutePath() + "'", e);
268     }
269
270     try
271     {
272       if (!importPaeJSONAsContactMatrix(pdbAlignment, pae_input, index,
273               seqId))
274       {
275         Console.warn("Could not import contact matrix from '"
276                 + pae.getAbsolutePath() + "'");
277       }
278     } catch (IOException e1)
279     {
280       Console.error("Error when importing pAE file '"
281               + pae.getAbsolutePath() + "'", e1);
282     } catch (ParseException e2)
283     {
284       Console.error(
285               "Error when parsing pAE file '" + pae.getAbsolutePath() + "'",
286               e2);
287     }
288
289   }
290
291   public static void addAlphaFoldPAEToStructure(AlignmentI pdbAlignment,
292           File pae, int index, String structId)
293   {
294     StructureSelectionManager ssm = StructureSelectionManager
295             .getStructureSelectionManager(Desktop.instance);
296     if (ssm != null)
297     {
298       /*
299       ssm.setAddTempFacAnnot(showTemperatureFactor);
300       ssm.setProcessSecondaryStructure(showSecondaryStructure);
301       */
302     }
303
304   }
305
306   /**
307    * parses the given pAE matrix and adds it to sequence 0 in the given
308    * alignment
309    * 
310    * @param pdbAlignment
311    * @param pae_input
312    * @return true if there was a pAE matrix added
313    * @throws ParseException
314    * @throws IOException
315    * @throws Exception
316    */
317   public static boolean importPaeJSONAsContactMatrix(
318           AlignmentI pdbAlignment, InputStream pae_input)
319           throws IOException, ParseException
320   {
321     return importPaeJSONAsContactMatrix(pdbAlignment, pae_input, 0, null);
322   }
323
324   public static boolean importPaeJSONAsContactMatrix(
325           AlignmentI pdbAlignment, InputStream pae_input, int index,
326           String seqId) throws IOException, ParseException
327   {
328
329     List<Object> pae_obj = (List<Object>) Platform.parseJSON(pae_input);
330     if (pae_obj == null)
331     {
332       Console.debug("JSON file did not parse properly.");
333       return false;
334     }
335     SequenceI sequence = null;
336     /* debugging */
337     SequenceI[] seqs = pdbAlignment.getSequencesArray();
338     if (seqs == null)
339       Console.debug("******* sequences is null");
340     else
341     {
342       for (int i = 0; i < seqs.length; i++)
343       {
344         SequenceI s = seqs[i];
345         Console.debug("******* sequences[" + i + "]='" + s.getName() + "'");
346       }
347     }
348     /* end debug */
349     if (seqId == null)
350     {
351       int seqToGet = index > 0 ? index : 0;
352       sequence = pdbAlignment.getSequenceAt(seqToGet);
353       Console.debug("***** Got sequence at index " + seqToGet + ": "
354               + (sequence == null ? null : sequence.getName()));
355     }
356     if (sequence == null)
357     {
358       Console.debug("***** Looking for sequence with id '" + seqId + "'");
359
360       SequenceI[] sequences = pdbAlignment.findSequenceMatch(seqId);
361       if (sequences == null || sequences.length < 1)
362       {
363         Console.warn("Could not find sequence with id '" + seqId
364                 + "' to attach pAE matrix to. Ignoring matrix.");
365         return false;
366       }
367       else
368       {
369         sequence = sequences[0]; // just use the first sequence with this seqId
370       }
371     }
372     ContactMatrixI matrix = new PAEContactMatrix(sequence,
373             (Map<String, Object>) pae_obj.get(0));
374
375     AlignmentAnnotation cmannot = sequence.addContactList(matrix);
376     pdbAlignment.addAnnotation(cmannot);
377     return true;
378   }
379
380   /**
381    * general purpose structure importer - designed to yield alignment useful for
382    * transfer of annotation to associated sequences
383    * 
384    * @param alphaFoldCif
385    * @param tmpFile
386    * @param id
387    * @param chain
388    * @param dbSource
389    * @param dbVersion
390    * @return
391    * @throws Exception
392    */
393   public static AlignmentI importDownloadedStructureFromUrl(
394           String alphaFoldCif, File tmpFile, String id, String chain,
395           String dbSource, String dbVersion) throws Exception
396   {
397     String file = tmpFile.getAbsolutePath();
398     // todo get rid of Type and use FileFormatI instead?
399     FileFormatI fileFormat = FileFormat.MMCif;
400     AlignmentI pdbAlignment = new FormatAdapter().readFile(tmpFile,
401             DataSourceType.FILE, fileFormat);
402     if (pdbAlignment != null)
403     {
404       List<SequenceI> toremove = new ArrayList<SequenceI>();
405       for (SequenceI pdbcs : pdbAlignment.getSequences())
406       {
407         String chid = null;
408         // Mapping map=null;
409         for (PDBEntry pid : pdbcs.getAllPDBEntries())
410         {
411           if (pid.getFile() == file)
412           {
413             chid = pid.getChainCode();
414
415           }
416         }
417         if (chain == null || (chid != null && (chid.equals(chain)
418                 || chid.trim().equals(chain.trim())
419                 || (chain.trim().length() == 0 && chid.equals("_")))))
420         {
421           // FIXME seems to result in 'PDB|1QIP|1qip|A' - 1QIP is redundant.
422           // TODO: suggest simplify naming to 1qip|A as default name defined
423           pdbcs.setName(id + SEPARATOR + pdbcs.getName());
424           // Might need to add more metadata to the PDBEntry object
425           // like below
426           /*
427            * PDBEntry entry = new PDBEntry(); // Construct the PDBEntry
428            * entry.setId(id); if (entry.getProperty() == null)
429            * entry.setProperty(new Hashtable());
430            * entry.getProperty().put("chains", pdbchain.id + "=" +
431            * sq.getStart() + "-" + sq.getEnd());
432            * sq.getDatasetSequence().addPDBId(entry);
433            */
434           // Add PDB DB Refs
435           // We make a DBRefEtntry because we have obtained the PDB file from
436           // a
437           // verifiable source
438           // JBPNote - PDB DBRefEntry should also carry the chain and mapping
439           // information
440           if (dbSource != null)
441           {
442             DBRefEntry dbentry = new DBRefEntry(dbSource,
443
444                     dbVersion, (chid == null ? id : id + chid));
445             // dbentry.setMap()
446             pdbcs.addDBRef(dbentry);
447             // update any feature groups
448             List<SequenceFeature> allsf = pdbcs.getFeatures()
449                     .getAllFeatures();
450             List<SequenceFeature> newsf = new ArrayList<SequenceFeature>();
451             if (allsf != null && allsf.size() > 0)
452             {
453               for (SequenceFeature f : allsf)
454               {
455                 if (file.equals(f.getFeatureGroup()))
456                 {
457                   f = new SequenceFeature(f, f.type, f.begin, f.end, id,
458                           f.score);
459                 }
460                 newsf.add(f);
461               }
462               pdbcs.setSequenceFeatures(newsf);
463             }
464           }
465         }
466         else
467         {
468           // mark this sequence to be removed from the alignment
469           // - since it's not from the right chain
470           toremove.add(pdbcs);
471         }
472       }
473       // now remove marked sequences
474       for (SequenceI pdbcs : toremove)
475       {
476         pdbAlignment.deleteSequence(pdbcs);
477         if (pdbcs.getAnnotation() != null)
478         {
479           for (AlignmentAnnotation aa : pdbcs.getAnnotation())
480           {
481             pdbAlignment.deleteAnnotation(aa);
482           }
483         }
484       }
485     }
486     return pdbAlignment;
487   }
488
489   /*
490    * (non-Javadoc)
491    * 
492    * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
493    */
494   @Override
495   public boolean isValidReference(String accession)
496   {
497     Regex r = getAccessionValidator();
498     return r.search(accession.trim());
499   }
500
501   /**
502    * human glyoxalase
503    */
504   @Override
505   public String getTestQuery()
506   {
507     return "AF-O15552-F1";
508   }
509
510   @Override
511   public String getDbName()
512   {
513     return "ALPHAFOLD"; // getDbSource();
514   }
515
516   @Override
517   public int getTier()
518   {
519     return 0;
520   }
521
522   /**
523    * Returns a descriptor for suitable feature display settings with
524    * <ul>
525    * <li>ResNums or insertions features visible</li>
526    * <li>insertions features coloured red</li>
527    * <li>ResNum features coloured by label</li>
528    * <li>Insertions displayed above (on top of) ResNums</li>
529    * </ul>
530    */
531   @Override
532   public FeatureSettingsModelI getFeatureColourScheme()
533   {
534     return new PDBFeatureSettings();
535   }
536
537 }