Clover icon

Coverage Report

  1. Project Clover database Thu Nov 27 2025 17:21:41 GMT
  2. Package jalview.ws.dbsources

File EBIAlfaFold.java

 

Coverage histogram

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

Code metrics

74
171
25
1
691
469
83
0.49
6.84
25
3.32

Classes

Class Line # Actions
EBIAlfaFold 68 171 83
0.314814831.5%
 

Contributing tests

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