JAL-4114 only rank model qualities that are computed with the same method, and uprank...
[jalview.git] / src / jalview / gui / structurechooser / TDBResultAnalyser.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 jalview.gui.structurechooser;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.BitSet;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.List;
30 import java.util.Locale;
31
32 import jalview.datamodel.SequenceI;
33 import jalview.fts.api.FTSData;
34 import jalview.fts.core.FTSRestRequest;
35
36 public class TDBResultAnalyser
37 {
38
39   /**
40    * model categories - update as needed. warnings output if unknown types
41    * encountered.
42    * 
43    * Order denotes 'trust'
44    */
45   private static List<String> EXP_CATEGORIES = Arrays
46           .asList(new String[]
47           { "EXPERIMENTALLY DETERMINED", "DEEP-LEARNING", "AB-INITIO",
48               "TEMPLATE-BASED", "CONFORMATIONAL ENSEMBLE" });
49
50   private SequenceI seq;
51
52   private Collection<FTSData> collectedResults;
53
54   private FTSRestRequest lastTdbRequest;
55
56   private int idx_ups;
57
58   private int idx_upe;
59
60   private int idx_mcat;
61
62   private int idx_mqual;
63
64   private int idx_mqualtype;
65
66   private int idx_resol;
67
68   /**
69    * selection model
70    */
71   private String filter = null;
72
73   /**
74    * limit to particular source
75    */
76   private String sourceFilter = null;
77
78   private int idx_mprov;
79
80   public TDBResultAnalyser(SequenceI seq,
81           Collection<FTSData> collectedResults,
82           FTSRestRequest lastTdbRequest, String fieldToFilterBy,
83           String string)
84   {
85     this.seq = seq;
86     this.collectedResults = collectedResults;
87     this.lastTdbRequest = lastTdbRequest;
88     this.filter = fieldToFilterBy;
89     this.sourceFilter = string;
90     idx_ups = lastTdbRequest.getFieldIndex("Uniprot Start");
91     idx_upe = lastTdbRequest.getFieldIndex("Uniprot End");
92     idx_mcat = lastTdbRequest.getFieldIndex("Model Category");
93     idx_mprov = lastTdbRequest.getFieldIndex("Provider");
94     idx_mqual = lastTdbRequest.getFieldIndex("Confidence");
95     idx_resol = lastTdbRequest.getFieldIndex("Resolution");
96     idx_mqualtype = lastTdbRequest.getFieldIndex("Confidence Score Type");
97   }
98
99   /**
100    * maintain and resolve categories to 'trust order' TODO: change the trust
101    * scheme to something comprehensible.
102    * 
103    * @param cat
104    * @return 0 for null cat, less than zero for others
105    */
106   public final int scoreCategory(String cat)
107   {
108     if (cat == null)
109     {
110       return 0;
111     }
112     String upper_cat = cat.toUpperCase(Locale.ROOT);
113     int idx = EXP_CATEGORIES.indexOf(upper_cat);
114     if (idx == -1)
115     {
116       System.out.println("Unknown category: '" + cat + "'");
117       EXP_CATEGORIES.add(upper_cat);
118       idx = EXP_CATEGORIES.size() - 1;
119     }
120     return -EXP_CATEGORIES.size() - idx;
121   }
122
123   /**
124    * sorts records discovered by 3D beacons and excludes any that don't
125    * intersect with the sequence's start/end rage
126    * 
127    * @return
128    */
129   public List<FTSData> getFilteredResponse()
130   {
131     List<FTSData> filteredResponse = new ArrayList<FTSData>();
132
133     // ignore anything outside the sequence region
134     for (FTSData row : collectedResults)
135     {
136       if (row.getSummaryData() != null
137               && row.getSummaryData()[idx_ups] != null)
138       {
139         int up_s = (Integer) row.getSummaryData()[idx_ups];
140         int up_e = (Integer) row.getSummaryData()[idx_upe];
141         String provider = (String) row.getSummaryData()[idx_mprov];
142         String mcat = (String) row.getSummaryData()[idx_mcat];
143         // this makes sure all new categories are in the score array.
144         int scorecat = scoreCategory(mcat);
145         if (sourceFilter == null || sourceFilter.equals(provider))
146         {
147           if (seq == row.getSummaryData()[0] && up_e > seq.getStart()
148                   && up_s < seq.getEnd())
149           {
150             filteredResponse.add(row);
151           }
152         }
153       }
154     }
155     // sort according to decreasing length,
156     // increasing start
157     Collections.sort(filteredResponse, new Comparator<FTSData>()
158     {
159       @Override
160       public int compare(FTSData o1, FTSData o2)
161       {
162         Object[] o1data = o1.getSummaryData();
163         Object[] o2data = o2.getSummaryData();
164         int o1_s = (Integer) o1data[idx_ups];
165         int o1_e = (Integer) o1data[idx_upe];
166         int o1_cat = scoreCategory((String) o1data[idx_mcat]);
167         String o1_prov = ((String) o1data[idx_mprov])
168                 .toUpperCase(Locale.ROOT);
169         int o2_s = (Integer) o2data[idx_ups];
170         int o2_e = (Integer) o2data[idx_upe];
171         int o2_cat = scoreCategory((String) o2data[idx_mcat]);
172         String o2_prov = ((String) o2data[idx_mprov])
173                 .toUpperCase(Locale.ROOT);
174         String o1_qualtype = (String) o1data[idx_mqualtype],
175                 o2_qualtype = (String) o2data[idx_mqualtype];
176
177         if (o1_cat == o2_cat)
178         {
179           if (o1_s == o2_s)
180           {
181             int o1_xtent = o1_e - o1_s;
182             int o2_xtent = o2_e - o2_s;
183             if (o1_xtent == o2_xtent)
184             {
185               // EXPERIMENTAL DATA ALWAYS TRUMPS MODELS
186               if (o1_cat == scoreCategory(EXP_CATEGORIES.get(0)))
187               {
188                 if (o1_prov.equals(o2_prov))
189                 {
190                   if ("PDBE".equals(o1_prov))
191                   {
192                     if (eitherNull(idx_resol, o1data, o2data))
193                     {
194                       return nonNullFirst(idx_resol, o1data, o2data);
195                     }
196                     // experimental structures, so rank on quality
197                     double o1_res = (Double) o1data[idx_resol];
198                     double o2_res = (Double) o2data[idx_resol];
199                     return (o2_res < o1_res) ? 1
200                             : (o2_res == o1_res) ? 0 : -1;
201                   }
202                   else
203                   {
204                     return 0; // no change in order
205                   }
206                 }
207                 else
208                 {
209                   // PDBe always ranked above all other experimentally
210                   // determined categories
211                   return "PDBE".equals(o1_prov) ? -1
212                           : "PDBE".equals(o2_prov) ? 1 : 0;
213                 }
214               }
215               else
216               {
217                 // RANK ON QUALITY - DOWNRANK THOSE WITH NO QUALITY MEASURE
218                 if (eitherNull(idx_mqualtype, o1data, o2data))
219                 {
220                   return nonNullFirst(idx_mqualtype, o1data, o2data);
221                 }
222                 // ONLY COMPARE LIKE QUALITY SCORES
223                 if (!o1_qualtype.equals(o2_qualtype))
224                 {
225                   // prefer LDDT measure over others
226                   return "pLDDT".equals(o1_prov) ? -1
227                           : "pLDDT".equals(o2_prov) ? 1 : 0;
228                 }
229                 // OR NO VALUE FOR THE QUALITY
230                 if (eitherNull(idx_mqual, o1data, o2data))
231                 {
232                   return nonNullFirst(idx_mqual, o1data, o2data);
233                 }
234                 // models, so rank on qmean - b
235                 double o1_mq = (Double) o1data[idx_mqual];
236                 double o2_mq = (Double) o2data[idx_mqual];
237                 return (o2_mq < o1_mq) ? 1 : (o2_mq == o1_mq) ? 0 : -1;
238               }
239             }
240             else
241             {
242               return o1_xtent - o2_xtent;
243             }
244           }
245           else
246           {
247             return o1_s - o2_s;
248           }
249         }
250         else
251         {
252           // if both are not experimental, then favour alphafold
253           if (o2_cat > 0 && o1_cat > 0)
254           {
255             return "ALPHAFOLD DB".equals(o1_prov) ? -1
256                     : "ALPHAFOLD DB".equals(o2_prov) ? 1 : 0;
257           }
258           return o2_cat - o1_cat;
259         }
260       }
261
262       private int nonNullFirst(int idx_resol, Object[] o1data,
263               Object[] o2data)
264       {
265         return o1data[idx_resol] == o2data[idx_resol] ? 0
266                 : o1data[idx_resol] != null ? -1 : 1;
267       }
268
269       private boolean eitherNull(int idx_resol, Object[] o1data,
270               Object[] o2data)
271       {
272         return (o1data[idx_resol] == null || o2data[idx_resol] == null);
273       }
274
275       @Override
276       public boolean equals(Object obj)
277       {
278         return super.equals(obj);
279       }
280     });
281     return filteredResponse;
282   }
283
284   /**
285    * return list of structures to be marked as selected for this sequence
286    * according to given criteria
287    * 
288    * @param filteredStructures
289    *          - sorted, filtered structures from getFilteredResponse
290    * 
291    */
292   public List<FTSData> selectStructures(List<FTSData> filteredStructures)
293   {
294     List<FTSData> selected = new ArrayList<FTSData>();
295     BitSet cover = new BitSet();
296     cover.set(seq.getStart(), seq.getEnd());
297     // walk down the list of structures, selecting some to add to selected
298     // TODO: could do simple DP - double loop to select largest number of
299     // structures covering largest number of sites
300     for (FTSData structure : filteredStructures)
301     {
302       Object[] odata = structure.getSummaryData();
303       int o1_s = (Integer) odata[idx_ups];
304       int o1_e = (Integer) odata[idx_upe];
305       int o1_cat = scoreCategory((String) odata[idx_mcat]);
306       BitSet scover = new BitSet();
307       // measure intersection
308       scover.set(o1_s, o1_e);
309       scover.and(cover);
310       if (scover.cardinality() > 4)
311       {
312         selected.add(structure);
313         // clear the range covered by this structure
314         cover.andNot(scover);
315       }
316     }
317     if (selected.size() == 0)
318     {
319       return selected;
320     }
321     // final step is to sort on length - this might help the superposition
322     // process
323     Collections.sort(selected, new Comparator<FTSData>()
324     {
325       @Override
326       public int compare(FTSData o1, FTSData o2)
327       {
328         Object[] o1data = o1.getSummaryData();
329         Object[] o2data = o2.getSummaryData();
330         int o1_xt = ((Integer) o1data[idx_upe])
331                 - ((Integer) o1data[idx_ups]);
332         int o1_cat = scoreCategory((String) o1data[idx_mcat]);
333         int o2_xt = ((Integer) o2data[idx_upe] - (Integer) o2data[idx_ups]);
334         int o2_cat = scoreCategory((String) o2data[idx_mcat]);
335         return o2_xt - o1_xt;
336       }
337     });
338     if (filter.equals(
339             ThreeDBStructureChooserQuerySource.FILTER_FIRST_BEST_COVERAGE))
340     {
341       return selected.subList(0, 1);
342     }
343     return selected;
344   }
345
346 }