Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.ws.dbsources

File EBIAlfaFold.java

 

Coverage histogram

../../../img/srcFileCovDistChart4.png
49% of files have more coverage

Code metrics

78
185
26
1
719
497
88
0.48
7.12
26
3.38

Classes

Class Line # Actions
EBIAlfaFold 69 185 88
0.3425605634.3%
 

Contributing tests

This file is covered by 34 tests. .

Source view

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.net.URL;
30    import java.util.ArrayList;
31    import java.util.Date;
32    import java.util.HashMap;
33    import java.util.List;
34    import java.util.Map;
35   
36    import org.json.simple.JSONArray;
37    import org.json.simple.JSONObject;
38    import org.json.simple.parser.ParseException;
39   
40    import com.stevesoft.pat.Regex;
41   
42    import jalview.api.FeatureSettingsModelI;
43    import jalview.bin.Console;
44    import jalview.datamodel.AlignmentAnnotation;
45    import jalview.datamodel.AlignmentI;
46    import jalview.datamodel.ContactMatrixI;
47    import jalview.datamodel.DBRefEntry;
48    import jalview.datamodel.PDBEntry;
49    import jalview.datamodel.SequenceFeature;
50    import jalview.datamodel.SequenceI;
51    import jalview.gui.Desktop;
52    import jalview.io.DataSourceType;
53    import jalview.io.FileFormat;
54    import jalview.io.FileFormatI;
55    import jalview.io.FormatAdapter;
56    import jalview.io.PDBFeatureSettings;
57    import jalview.structure.StructureImportSettings.TFType;
58    import jalview.structure.StructureMapping;
59    import jalview.structure.StructureSelectionManager;
60    import jalview.util.MessageManager;
61    import jalview.util.Platform;
62    import jalview.ws.datamodel.alphafold.PAEContactMatrix;
63    import jalview.ws.utils.UrlDownloadClient;
64   
65    /**
66    * @author JimP
67    *
68    */
 
69    public class EBIAlfaFold extends EbiFileRetrievedProxy
70    {
71    private static final String SEPARATOR = "|";
72   
73    private static final String COLON = ":";
74   
75    private static String AF_VERSION = null;
76   
 
77  0 toggle public EBIAlfaFold()
78    {
79  0 super();
80    }
81   
82    /*
83    * (non-Javadoc)
84    *
85    * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
86    */
 
87  0 toggle @Override
88    public String getAccessionSeparator()
89    {
90  0 return null;
91    }
92   
93    /*
94    * (non-Javadoc)
95    *
96    * @see jalview.ws.DbSourceProxy#getAccessionValidator()
97    */
 
98  0 toggle @Override
99    public Regex getAccessionValidator()
100    {
101  0 Regex validator = new Regex("(AF-[A-Z]+[0-9]+[A-Z0-9]+-F1)");
102  0 validator.setIgnoreCase(true);
103  0 return validator;
104    }
105   
106    /*
107    * (non-Javadoc)
108    *
109    * @see jalview.ws.DbSourceProxy#getDbSource()
110    */
 
111  0 toggle @Override
112    public String getDbSource()
113    {
114  0 return "ALPHAFOLD";
115    }
116   
117    /*
118    * (non-Javadoc)
119    *
120    * @see jalview.ws.DbSourceProxy#getDbVersion()
121    */
 
122  0 toggle @Override
123    public String getDbVersion()
124    {
125  0 return "1";
126    }
127   
 
128  1 toggle public static String pingAPIVersion()
129    {
130  1 if (AF_VERSION != null)
131    {
132  0 return AF_VERSION;
133    }
134  1 String Version = null;
135  1 try
136    {
137  1 URL ping = new URL(
138    "https://alphafold.ebi.ac.uk/api/prediction/Q5VSL9");
139  1 Object resp = Platform.parseJSON(ping.openStream());
140  1 if (resp != null && resp instanceof JSONArray)
141    {
142  1 Version = (((JSONObject) ((JSONArray) resp).get(0))
143    .get("latestVersion")).toString();
144  1 AF_VERSION = Version;
145    }
146    } catch (Throwable x)
147    {
148  0 jalview.bin.Console
149    .errPrintln("Couldn't get EBI AlphaFold DB latest version!");
150  0 jalview.bin.Console.errPrintln(x);
151    }
152  1 return Version;
153    }
154   
 
155  0 toggle public static String getAlphaFoldCifDownloadUrl(String id, String vnum)
156    {
157  0 if (vnum == null || vnum.length() == 0)
158    {
159  0 pingAPIVersion();
160  0 vnum = AF_VERSION;
161    }
162  0 return "https://alphafold.ebi.ac.uk/files/" + id + "-model_v" + vnum
163    + ".cif";
164    }
165   
 
166  32 toggle public static String getAlphaFoldPaeDownloadUrl(String id, String vnum)
167    {
168  32 if (vnum == null || vnum.length() == 0)
169    {
170  1 pingAPIVersion();
171  1 vnum = AF_VERSION;
172    }
173  32 return "https://alphafold.ebi.ac.uk/files/" + id
174    + "-predicted_aligned_error_v" + vnum + ".json";
175    }
176   
177    /*
178    * (non-Javadoc)
179    *
180    * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
181    */
 
182  0 toggle @Override
183    public AlignmentI getSequenceRecords(String queries) throws Exception
184    {
185  0 return getSequenceRecords(queries, null);
186    }
187   
 
188  0 toggle public AlignmentI getSequenceRecords(String queries, String retrievalUrl)
189    throws Exception
190    {
191  0 AlignmentI pdbAlignment = null;
192  0 String chain = null;
193  0 String id = null;
194  0 if (queries.indexOf(COLON) > -1)
195    {
196  0 chain = queries.substring(queries.indexOf(COLON) + 1);
197  0 id = queries.substring(0, queries.indexOf(COLON));
198    }
199    else
200    {
201  0 id = queries;
202    }
203   
204  0 if (!isValidReference(id))
205    {
206  0 jalview.bin.Console.errPrintln(
207    "(AFClient) Ignoring invalid alphafold query: '" + id + "'");
208  0 stopQuery();
209  0 return null;
210    }
211  0 String alphaFoldCif = getAlphaFoldCifDownloadUrl(id, AF_VERSION);
212  0 if (retrievalUrl != null)
213    {
214  0 alphaFoldCif = retrievalUrl;
215    }
216   
217  0 try
218    {
219  0 File tmpFile = File.createTempFile(id, ".cif");
220  0 Console.debug("Retrieving structure file for " + id + " from "
221    + alphaFoldCif);
222  0 UrlDownloadClient.download(alphaFoldCif, tmpFile);
223   
224    // may not need this check ?
225  0 file = tmpFile.getAbsolutePath();
226  0 if (file == null)
227    {
228  0 return null;
229    }
230    // TODO Get the PAE file somewhere around here and remove from JmolParser
231   
232  0 pdbAlignment = importDownloadedStructureFromUrl(alphaFoldCif, tmpFile,
233    id, chain, getDbSource(), getDbVersion());
234   
235  0 if (pdbAlignment == null || pdbAlignment.getHeight() < 1)
236    {
237  0 throw new Exception(MessageManager.formatMessage(
238    "exception.no_pdb_records_for_chain", new String[]
239  0 { id, ((chain == null) ? "' '" : chain) }));
240    }
241    // done during structure retrieval
242    // retrieve_AlphaFold_pAE(id, pdbAlignment, retrievalUrl);
243   
244    } catch (Exception ex) // Problem parsing PDB file
245    {
246  0 stopQuery();
247  0 throw (ex);
248    }
249  0 return pdbAlignment;
250    }
251   
252    /**
253    * get an alphafold pAE for the given id and return the File object of the
254    * downloaded (temp) file
255    *
256    * @param id
257    * @param retrievalUrl
258    * - URL of .mmcif from EBI-AlphaFold - will be used to generate the
259    * pAE URL automatically
260    * @throws IOException
261    * @throws Exception
262    */
 
263  32 toggle public static File fetchAlphaFoldPAE(String id, String retrievalUrl)
264    throws IOException
265    {
266    // import PAE as contact matrix - assume this will work if there was a
267    // model
268  32 String paeURL = getAlphaFoldPaeDownloadUrl(id, AF_VERSION);
269   
270  32 if (retrievalUrl != null)
271    {
272    // manufacture the PAE url from a url like ...-model-vN.cif
273  0 paeURL = retrievalUrl.replace("model", "predicted_aligned_error")
274    .replace(".cif", ".json");
275    }
276  32 return fetchAPAE_from(id, paeURL);
277    }
278   
279    /**
280    * get a PAE file or reuse existing file
281    *
282    * @param id
283    * - null or an alphafold ID if this is an alphafold model - used
284    * only to construct tempfile name
285    * @param retrievalUrl
286    * - URL of .mmcif from EBI-AlphaFold - will be used to generate the
287    * pAE URL automatically
288    * @throws IOException
289    * @throws Exception
290    */
 
291  35 toggle public static File fetchAPAE_from(String id, String paeURL)
292    throws IOException
293    {
294    // check the cache
295  35 File pae = paeDownloadCache.get(paeURL);
296  35 if (pae != null && pae.exists() && (new Date().getTime()
297    - pae.lastModified()) < PAE_CACHE_STALE_TIME)
298    {
299  32 Console.debug(
300    "Using existing file in PAE cache for '" + paeURL + "'");
301  32 return pae;
302    }
303   
304  3 try
305    {
306  3 pae = File.createTempFile(id == null ? "af_pae" : id, "pae_json");
307    } catch (IOException e)
308    {
309  0 e.printStackTrace();
310    }
311  3 Console.debug("Downloading pae from " + paeURL + " to " + pae.toString()
312    + "");
313  3 try
314    {
315  3 UrlDownloadClient.download(paeURL, pae);
316    } catch (IOException e)
317    {
318  0 throw e;
319    }
320    // cache and it if successful
321  3 paeDownloadCache.put(paeURL, pae);
322  3 return pae;
323    }
324   
325    /**
326    * get an alphafold pAE for the given id, and add it to sequence 0 in
327    * pdbAlignment (assuming it came from structurefile parser).
328    *
329    * @param id
330    * @param pdbAlignment
331    * @param retrievalUrl
332    * - URL of .mmcif from EBI-AlphaFold - will be used to generate the
333    * pAE URL automatically
334    * @throws IOException
335    * @throws Exception
336    */
 
337  0 toggle public static void retrieve_AlphaFold_pAE(String id,
338    AlignmentI pdbAlignment, String retrievalUrl) throws IOException
339    {
340  0 File pae = fetchAlphaFoldPAE(id, retrievalUrl);
341  0 addAlphaFoldPAE(pdbAlignment, pae, 0, null, false, false, null);
342    }
343   
 
344  81 toggle public static void addAlphaFoldPAE(AlignmentI pdbAlignment, File pae,
345    int index, String id, boolean isStruct, boolean isStructId,
346    String label)
347    {
348  81 FileInputStream paeInput = null;
349  81 try
350    {
351  81 paeInput = new FileInputStream(pae);
352    } catch (FileNotFoundException e)
353    {
354  0 Console.error(
355    "Could not find pAE file '" + pae.getAbsolutePath() + "'", e);
356  0 return;
357    }
358   
359  81 if (isStruct)
360    {
361    // ###### WRITE A TEST for this bit of the logic addAlphaFoldPAE with
362    // different params.
363  0 StructureSelectionManager ssm = StructureSelectionManager
364    .getStructureSelectionManager(Desktop.getInstance());
365  0 if (ssm != null)
366    {
367  0 String structFilename = isStructId ? ssm.findFileForPDBId(id) : id;
368  0 addPAEToStructure(ssm, structFilename, pae, label);
369    }
370   
371    }
372    else
373    {
374    // attach to sequence?!
375  81 try
376    {
377  81 if (!importPaeJSONAsContactMatrixToSequence(pdbAlignment, paeInput,
378    index, id, label))
379    {
380  0 Console.warn("Could not import contact matrix from '"
381    + pae.getAbsolutePath() + "' to sequence.");
382    }
383    } catch (IOException e1)
384    {
385  0 Console.error("Error when importing pAE file '"
386    + pae.getAbsolutePath() + "'", e1);
387    } catch (ParseException e2)
388    {
389  0 Console.error("Error when parsing pAE file '"
390    + pae.getAbsolutePath() + "'", e2);
391    }
392    }
393   
394    }
395   
 
396  0 toggle public static void addPAEToStructure(StructureSelectionManager ssm,
397    String structFilename, File pae, String label)
398    {
399  0 FileInputStream paeInput = null;
400  0 try
401    {
402  0 paeInput = new FileInputStream(pae);
403    } catch (FileNotFoundException e)
404    {
405  0 Console.error(
406    "Could not find pAE file '" + pae.getAbsolutePath() + "'", e);
407  0 return;
408    }
409  0 if (ssm == null)
410    {
411  0 ssm = StructureSelectionManager
412    .getStructureSelectionManager(Desktop.getInstance());
413    }
414  0 if (ssm != null)
415    {
416  0 StructureMapping[] smArray = ssm.getMapping(structFilename);
417   
418  0 try
419    {
420  0 if (!importPaeJSONAsContactMatrixToStructure(smArray, paeInput,
421    label))
422    {
423  0 Console.warn("Could not import contact matrix from '"
424    + pae.getAbsolutePath() + "' to structure.");
425    }
426    } catch (IOException e1)
427    {
428  0 Console.error("Error when importing pAE file '"
429    + pae.getAbsolutePath() + "'", e1);
430    } catch (ParseException e2)
431    {
432  0 Console.error("Error when parsing pAE file '"
433    + pae.getAbsolutePath() + "'", e2);
434    }
435    }
436    }
437   
438    /**
439    * parses the given pAE matrix and adds it to sequence 0 in the given
440    * alignment
441    *
442    * @param pdbAlignment
443    * @param pae_input
444    * @return true if there was a pAE matrix added
445    * @throws ParseException
446    * @throws IOException
447    * @throws Exception
448    */
 
449  81 toggle public static boolean importPaeJSONAsContactMatrixToSequence(
450    AlignmentI pdbAlignment, InputStream pae_input, int index,
451    String seqId, String label) throws IOException, ParseException
452    {
453  81 SequenceI sequence = null;
454  81 if (seqId == null)
455    {
456  81 int seqToGet = index > 0 ? index : 0;
457  81 sequence = pdbAlignment.getSequenceAt(seqToGet);
458    }
459  81 if (sequence == null)
460    {
461  0 SequenceI[] sequences = pdbAlignment.findSequenceMatch(seqId);
462  0 if (sequences == null || sequences.length < 1)
463    {
464  0 Console.warn("Could not find sequence with id '" + seqId
465    + "' to attach pAE matrix to. Ignoring matrix.");
466  0 return false;
467    }
468    else
469    {
470  0 sequence = sequences[0]; // just use the first sequence with this seqId
471    }
472    }
473  81 if (sequence == null)
474    {
475  0 return false;
476    }
477  81 return importPaeJSONAsContactMatrixToSequence(pdbAlignment, pae_input,
478    sequence, label);
479    }
480   
 
481  81 toggle public static boolean importPaeJSONAsContactMatrixToSequence(
482    AlignmentI pdbAlignment, InputStream pae_input,
483    SequenceI sequence, String label)
484    throws IOException, ParseException
485    {
486  81 JSONObject paeDict = parseJSONtoPAEContactMatrix(pae_input);
487  81 if (paeDict == null)
488    {
489  0 Console.debug("JSON file did not parse properly.");
490  0 return false;
491    }
492  81 ContactMatrixI matrix = new PAEContactMatrix(sequence, paeDict);
493   
494  81 AlignmentAnnotation cmannot = sequence.addContactList(matrix);
495  81 if (label != null)
496  0 cmannot.label = label;
497  81 pdbAlignment.addAnnotation(cmannot);
498   
499  81 return true;
500    }
501   
 
502  87 toggle public static JSONObject parseJSONtoPAEContactMatrix(
503    InputStream pae_input) throws IOException, ParseException
504    {
505  87 Object paeJson = Platform.parseJSON(pae_input);
506  87 JSONObject paeDict = null;
507  87 if (paeJson instanceof JSONObject)
508    {
509  20 paeDict = (JSONObject) paeJson;
510    }
511  67 else if (paeJson instanceof JSONArray)
512    {
513  67 JSONArray jsonArray = (JSONArray) paeJson;
514  67 if (jsonArray.size() > 0)
515  67 paeDict = (JSONObject) jsonArray.get(0);
516    }
517   
518  87 return paeDict;
519    }
520   
521    // ###### TEST THIS
 
522  3 toggle public static boolean importPaeJSONAsContactMatrixToStructure(
523    StructureMapping[] smArray, InputStream paeInput, String label)
524    throws IOException, ParseException
525    {
526  3 boolean someDone = false;
527  3 for (StructureMapping sm : smArray)
528    {
529  3 boolean thisDone = importPaeJSONAsContactMatrixToStructure(sm,
530    paeInput, label);
531  3 someDone |= thisDone;
532    }
533  3 return someDone;
534    }
535   
 
536  3 toggle public static boolean importPaeJSONAsContactMatrixToStructure(
537    StructureMapping sm, InputStream paeInput, String label)
538    throws IOException, ParseException
539    {
540  3 JSONObject pae_obj = parseJSONtoPAEContactMatrix(paeInput);
541  3 if (pae_obj == null)
542    {
543  0 Console.debug("JSON file did not parse properly.");
544  0 return false;
545    }
546   
547  3 SequenceI seq = sm.getSequence();
548  3 ContactMatrixI matrix = new PAEContactMatrix(seq, pae_obj);
549  3 AlignmentAnnotation cmannot = sm.getSequence().addContactList(matrix);
550    /* this already happens in Sequence.addContactList()
551    seq.addAlignmentAnnotation(cmannot);
552    */
553  3 return true;
554    }
555   
556    /**
557    * general purpose structure importer - designed to yield alignment useful for
558    * transfer of annotation to associated sequences
559    *
560    * @param alphaFoldCif
561    * @param tmpFile
562    * @param id
563    * @param chain
564    * @param dbSource
565    * @param dbVersion
566    * @return
567    * @throws Exception
568    */
 
569  0 toggle public static AlignmentI importDownloadedStructureFromUrl(
570    String alphaFoldCif, File tmpFile, String id, String chain,
571    String dbSource, String dbVersion) throws Exception
572    {
573  0 String file = tmpFile.getAbsolutePath();
574    // todo get rid of Type and use FileFormatI instead?
575  0 FileFormatI fileFormat = FileFormat.MMCif;
576  0 TFType tempfacType = TFType.PLDDT;
577  0 AlignmentI pdbAlignment = new FormatAdapter().readFile(tmpFile, file,
578    DataSourceType.FILE, fileFormat, tempfacType);
579   
580  0 if (pdbAlignment != null)
581    {
582  0 List<SequenceI> toremove = new ArrayList<SequenceI>();
583  0 for (SequenceI pdbcs : pdbAlignment.getSequences())
584    {
585  0 String chid = null;
586    // Mapping map=null;
587  0 for (PDBEntry pid : pdbcs.getAllPDBEntries())
588    {
589  0 if (pid.getFile() == file)
590    {
591  0 chid = pid.getChainCode();
592    }
593    }
594  0 if (chain == null || (chid != null && (chid.equals(chain)
595    || chid.trim().equals(chain.trim())
596    || (chain.trim().length() == 0 && chid.equals("_")))))
597    {
598    // FIXME seems to result in 'PDB|1QIP|1qip|A' - 1QIP is redundant.
599    // TODO: suggest simplify naming to 1qip|A as default name defined
600  0 pdbcs.setName(id + SEPARATOR + pdbcs.getName());
601    // Might need to add more metadata to the PDBEntry object
602    // like below
603    /*
604    * PDBEntry entry = new PDBEntry(); // Construct the PDBEntry
605    * entry.setId(id); if (entry.getProperty() == null)
606    * entry.setProperty(new Hashtable());
607    * entry.getProperty().put("chains", pdbchain.id + "=" +
608    * sq.getStart() + "-" + sq.getEnd());
609    * sq.getDatasetSequence().addPDBId(entry);
610    */
611    // Add PDB DB Refs
612    // We make a DBRefEtntry because we have obtained the PDB file from
613    // a
614    // verifiable source
615    // JBPNote - PDB DBRefEntry should also carry the chain and mapping
616    // information
617  0 if (dbSource != null)
618    {
619  0 DBRefEntry dbentry = new DBRefEntry(dbSource,
620   
621  0 dbVersion, (chid == null ? id : id + chid));
622    // dbentry.setMap()
623  0 pdbcs.addDBRef(dbentry);
624    // update any feature groups
625  0 List<SequenceFeature> allsf = pdbcs.getFeatures()
626    .getAllFeatures();
627  0 List<SequenceFeature> newsf = new ArrayList<SequenceFeature>();
628  0 if (allsf != null && allsf.size() > 0)
629    {
630  0 for (SequenceFeature f : allsf)
631    {
632  0 if (file.equals(f.getFeatureGroup()))
633    {
634  0 f = new SequenceFeature(f, f.type, f.begin, f.end, id,
635    f.score);
636    }
637  0 newsf.add(f);
638    }
639  0 pdbcs.setSequenceFeatures(newsf);
640    }
641    }
642    }
643    else
644    {
645    // mark this sequence to be removed from the alignment
646    // - since it's not from the right chain
647  0 toremove.add(pdbcs);
648    }
649    }
650    // now remove marked sequences
651  0 for (SequenceI pdbcs : toremove)
652    {
653  0 pdbAlignment.deleteSequence(pdbcs);
654  0 if (pdbcs.getAnnotation() != null)
655    {
656  0 for (AlignmentAnnotation aa : pdbcs.getAnnotation())
657    {
658  0 pdbAlignment.deleteAnnotation(aa);
659    }
660    }
661    }
662    }
663  0 return pdbAlignment;
664    }
665   
666    /*
667    * (non-Javadoc)
668    *
669    * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
670    */
 
671  0 toggle @Override
672    public boolean isValidReference(String accession)
673    {
674  0 Regex r = getAccessionValidator();
675  0 return r.search(accession.trim());
676    }
677   
678    /**
679    * human glyoxalase
680    */
 
681  0 toggle @Override
682    public String getTestQuery()
683    {
684  0 return "AF-O15552-F1";
685    }
686   
 
687  0 toggle @Override
688    public String getDbName()
689    {
690  0 return "ALPHAFOLD"; // getDbSource();
691    }
692   
 
693  0 toggle @Override
694    public int getTier()
695    {
696  0 return 0;
697    }
698   
699    /**
700    * Returns a descriptor for suitable feature display settings with
701    * <ul>
702    * <li>ResNums or insertions features visible</li>
703    * <li>insertions features coloured red</li>
704    * <li>ResNum features coloured by label</li>
705    * <li>Insertions displayed above (on top of) ResNums</li>
706    * </ul>
707    */
 
708  0 toggle @Override
709    public FeatureSettingsModelI getFeatureColourScheme()
710    {
711  0 return new PDBFeatureSettings();
712    }
713   
714    // days * 86400000
715    private static final long PAE_CACHE_STALE_TIME = 1 * 86400000;
716   
717    private static Map<String, File> paeDownloadCache = new HashMap<>();
718   
719    }