JAL-2349 JAL-3855 formatting
[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.util.ArrayList;
27 import java.util.List;
28 import java.util.Map;
29
30 import com.stevesoft.pat.Regex;
31
32 import jalview.api.FeatureSettingsModelI;
33 import jalview.bin.Console;
34 import jalview.datamodel.AlignmentAnnotation;
35 import jalview.datamodel.AlignmentI;
36 import jalview.datamodel.ContactMatrixI;
37 import jalview.datamodel.DBRefEntry;
38 import jalview.datamodel.PDBEntry;
39 import jalview.datamodel.SequenceFeature;
40 import jalview.datamodel.SequenceI;
41 import jalview.io.DataSourceType;
42 import jalview.io.FileFormat;
43 import jalview.io.FileFormatI;
44 import jalview.io.FormatAdapter;
45 import jalview.io.PDBFeatureSettings;
46 import jalview.util.MessageManager;
47 import jalview.util.Platform;
48 import jalview.ws.datamodel.alphafold.PAEContactMatrix;
49 import jalview.ws.utils.UrlDownloadClient;
50
51 /**
52  * @author JimP
53  * 
54  */
55 public class EBIAlfaFold extends EbiFileRetrievedProxy
56 {
57   private static final String SEPARATOR = "|";
58
59   private static final String COLON = ":";
60
61   private static final int PDB_ID_LENGTH = 4;
62
63   private static String AF_VERSION = "2";
64
65   public EBIAlfaFold()
66   {
67     super();
68   }
69
70   /*
71    * (non-Javadoc)
72    * 
73    * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
74    */
75   @Override
76   public String getAccessionSeparator()
77   {
78     return null;
79   }
80
81   /*
82    * (non-Javadoc)
83    * 
84    * @see jalview.ws.DbSourceProxy#getAccessionValidator()
85    */
86   @Override
87   public Regex getAccessionValidator()
88   {
89     Regex validator = new Regex("(AF-[A-Z]+[0-9]+[A-Z0-9]+-F1)");
90     validator.setIgnoreCase(true);
91     return validator;
92   }
93
94   /*
95    * (non-Javadoc)
96    * 
97    * @see jalview.ws.DbSourceProxy#getDbSource()
98    */
99   @Override
100   public String getDbSource()
101   {
102     return "ALPHAFOLD";
103   }
104
105   /*
106    * (non-Javadoc)
107    * 
108    * @see jalview.ws.DbSourceProxy#getDbVersion()
109    */
110   @Override
111   public String getDbVersion()
112   {
113     return "1";
114   }
115
116   public static String getAlphaFoldCifDownloadUrl(String id, String vnum)
117   {
118     if (vnum == null || vnum.length() == 0)
119     {
120       vnum = AF_VERSION;
121     }
122     return "https://alphafold.ebi.ac.uk/files/" + id + "-model_v" + vnum
123             + ".cif";
124   }
125
126   public static String getAlphaFoldPaeDownloadUrl(String id, String vnum)
127   {
128     if (vnum == null || vnum.length() == 0)
129     {
130       vnum = AF_VERSION;
131     }
132     return "https://alphafold.ebi.ac.uk/files/" + id
133             + "-predicted_aligned_error_v" + vnum + ".json";
134   }
135
136   /*
137    * (non-Javadoc)
138    * 
139    * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
140    */
141   @Override
142   public AlignmentI getSequenceRecords(String queries) throws Exception
143   {
144     return getSequenceRecords(queries, null);
145   }
146
147   public AlignmentI getSequenceRecords(String queries, String retrievalUrl)
148           throws Exception
149   {
150     AlignmentI pdbAlignment = null;
151     String chain = null;
152     String id = null;
153     if (queries.indexOf(COLON) > -1)
154     {
155       chain = queries.substring(queries.indexOf(COLON) + 1);
156       id = queries.substring(0, queries.indexOf(COLON));
157     }
158     else
159     {
160       id = queries;
161     }
162
163     if (!isValidReference(id))
164     {
165       System.err.println(
166               "(AFClient) Ignoring invalid alphafold query: '" + id + "'");
167       stopQuery();
168       return null;
169     }
170     String alphaFoldCif = getAlphaFoldCifDownloadUrl(id, AF_VERSION);
171     if (retrievalUrl != null)
172     {
173       alphaFoldCif = retrievalUrl;
174     }
175
176     try
177     {
178       File tmpFile = File.createTempFile(id, ".cif");
179       Console.debug("Retrieving structure file for " + id + " from "
180               + alphaFoldCif);
181       UrlDownloadClient.download(alphaFoldCif, tmpFile);
182
183       // may not need this check ?
184       file = tmpFile.getAbsolutePath();
185       if (file == null)
186       {
187         return null;
188       }
189
190       pdbAlignment = importDownloadedStructureFromUrl(alphaFoldCif, tmpFile,
191               id, chain, getDbSource(), getDbVersion());
192
193       if (pdbAlignment == null || pdbAlignment.getHeight() < 1)
194       {
195         throw new Exception(MessageManager.formatMessage(
196                 "exception.no_pdb_records_for_chain", new String[]
197                 { id, ((chain == null) ? "' '" : chain) }));
198       }
199
200       // import PAE as contact matrix - assume this will work if there was a
201       // model
202       File pae = File.createTempFile(id, "pae_json");
203       String paeURL = getAlphaFoldPaeDownloadUrl(id, AF_VERSION);
204
205       if (retrievalUrl != null)
206       {
207         // manufacture the PAE url from a url like ...-model-vN.cif
208         paeURL = retrievalUrl.replace("model", "predicted_aligned_error")
209                 .replace(".cif", ".json");
210       }
211       Console.debug("Downloading pae from " + paeURL + " to "
212               + pae.toString() + "");
213
214       try
215       {
216         UrlDownloadClient.download(paeURL, pae);
217         if (!importPaeJSONAsContactMatrix(pdbAlignment, pae))
218         {
219           Console.warn("Couln't import contact matrix from " + paeURL
220                   + " (stored in " + pae.toString() + ")");
221         }
222       } catch (Exception pae_ex)
223       {
224         Console.debug("Couldn't download PAE", pae_ex);
225       }
226
227     } catch (Exception ex) // Problem parsing PDB file
228     {
229       stopQuery();
230       throw (ex);
231     }
232     return pdbAlignment;
233   }
234
235   private boolean importPaeJSONAsContactMatrix(AlignmentI pdbAlignment,
236           File pae) throws Exception
237   {
238     FileInputStream pae_input = new FileInputStream(pae);
239
240     List<Object> pae_obj = (List<Object>) Platform.parseJSON(pae_input);
241     if (pae_obj == null)
242     {
243       return false;
244     }
245     ContactMatrixI matrix = new PAEContactMatrix(
246             pdbAlignment.getSequenceAt(0),
247             (Map<String, Object>) pae_obj.get(0));
248
249     pdbAlignment.getSequenceAt(0)
250             .addAlignmentAnnotation(pdbAlignment.addContactList(matrix));
251     return true;
252   }
253
254   /**
255    * general purpose structure importer - designed to yield alignment useful for
256    * transfer of annotation to associated sequences
257    * 
258    * @param alphaFoldCif
259    * @param tmpFile
260    * @param id
261    * @param chain
262    * @param dbSource
263    * @param dbVersion
264    * @return
265    * @throws Exception
266    */
267   public static AlignmentI importDownloadedStructureFromUrl(
268           String alphaFoldCif, File tmpFile, String id, String chain,
269           String dbSource, String dbVersion) throws Exception
270   {
271     String file = tmpFile.getAbsolutePath();
272     // todo get rid of Type and use FileFormatI instead?
273     FileFormatI fileFormat = FileFormat.MMCif;
274     AlignmentI pdbAlignment = new FormatAdapter().readFile(tmpFile,
275             DataSourceType.FILE, fileFormat);
276     if (pdbAlignment != null)
277     {
278       List<SequenceI> toremove = new ArrayList<SequenceI>();
279       for (SequenceI pdbcs : pdbAlignment.getSequences())
280       {
281         String chid = null;
282         // Mapping map=null;
283         for (PDBEntry pid : pdbcs.getAllPDBEntries())
284         {
285           if (pid.getFile() == file)
286           {
287             chid = pid.getChainCode();
288
289           }
290         }
291         if (chain == null || (chid != null && (chid.equals(chain)
292                 || chid.trim().equals(chain.trim())
293                 || (chain.trim().length() == 0 && chid.equals("_")))))
294         {
295           // FIXME seems to result in 'PDB|1QIP|1qip|A' - 1QIP is redundant.
296           // TODO: suggest simplify naming to 1qip|A as default name defined
297           pdbcs.setName(id + SEPARATOR + pdbcs.getName());
298           // Might need to add more metadata to the PDBEntry object
299           // like below
300           /*
301            * PDBEntry entry = new PDBEntry(); // Construct the PDBEntry
302            * entry.setId(id); if (entry.getProperty() == null)
303            * entry.setProperty(new Hashtable());
304            * entry.getProperty().put("chains", pdbchain.id + "=" +
305            * sq.getStart() + "-" + sq.getEnd());
306            * sq.getDatasetSequence().addPDBId(entry);
307            */
308           // Add PDB DB Refs
309           // We make a DBRefEtntry because we have obtained the PDB file from
310           // a
311           // verifiable source
312           // JBPNote - PDB DBRefEntry should also carry the chain and mapping
313           // information
314           if (dbSource != null)
315           {
316             DBRefEntry dbentry = new DBRefEntry(dbSource,
317
318                     dbVersion, (chid == null ? id : id + chid));
319             // dbentry.setMap()
320             pdbcs.addDBRef(dbentry);
321             // update any feature groups
322             List<SequenceFeature> allsf = pdbcs.getFeatures()
323                     .getAllFeatures();
324             List<SequenceFeature> newsf = new ArrayList<SequenceFeature>();
325             if (allsf != null && allsf.size() > 0)
326             {
327               for (SequenceFeature f : allsf)
328               {
329                 if (file.equals(f.getFeatureGroup()))
330                 {
331                   f = new SequenceFeature(f, f.type, f.begin, f.end, id,
332                           f.score);
333                 }
334                 newsf.add(f);
335               }
336               pdbcs.setSequenceFeatures(newsf);
337             }
338           }
339         }
340         else
341         {
342           // mark this sequence to be removed from the alignment
343           // - since it's not from the right chain
344           toremove.add(pdbcs);
345         }
346       }
347       // now remove marked sequences
348       for (SequenceI pdbcs : toremove)
349       {
350         pdbAlignment.deleteSequence(pdbcs);
351         if (pdbcs.getAnnotation() != null)
352         {
353           for (AlignmentAnnotation aa : pdbcs.getAnnotation())
354           {
355             pdbAlignment.deleteAnnotation(aa);
356           }
357         }
358       }
359     }
360     return pdbAlignment;
361   }
362
363   /*
364    * (non-Javadoc)
365    * 
366    * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
367    */
368   @Override
369   public boolean isValidReference(String accession)
370   {
371     Regex r = getAccessionValidator();
372     return r.search(accession.trim());
373   }
374
375   /**
376    * human glyoxalase
377    */
378   @Override
379   public String getTestQuery()
380   {
381     return "AF-O15552-F1";
382   }
383
384   @Override
385   public String getDbName()
386   {
387     return "ALPHAFOLD"; // getDbSource();
388   }
389
390   @Override
391   public int getTier()
392   {
393     return 0;
394   }
395
396   /**
397    * Returns a descriptor for suitable feature display settings with
398    * <ul>
399    * <li>ResNums or insertions features visible</li>
400    * <li>insertions features coloured red</li>
401    * <li>ResNum features coloured by label</li>
402    * <li>Insertions displayed above (on top of) ResNums</li>
403    * </ul>
404    */
405   @Override
406   public FeatureSettingsModelI getFeatureColourScheme()
407   {
408     return new PDBFeatureSettings();
409   }
410
411 }