Clover icon

Coverage Report

  1. Project Clover database Mon Dec 1 2025 15:35:32 GMT
  2. Package jalview.analysis

File AlignmentAnnotationUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart9.png
13% of files have more coverage

Code metrics

82
124
6
1
503
304
73
0.59
20.67
6
12.17

Classes

Class Line # Actions
AlignmentAnnotationUtils 40 124 73
0.849056684.9%
 

Contributing tests

This file is covered by 253 tests. .

Source view

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.analysis;
22   
23    import java.util.ArrayList;
24    import java.util.Arrays;
25    import java.util.BitSet;
26    import java.util.Collections;
27    import java.util.HashMap;
28    import java.util.List;
29    import java.util.Map;
30    import java.util.Optional;
31    import java.util.Vector;
32   
33    import jalview.datamodel.AlignmentAnnotation;
34    import jalview.datamodel.PDBEntry;
35    import jalview.datamodel.SequenceGroup;
36    import jalview.datamodel.SequenceI;
37    import jalview.renderer.AnnotationRenderer;
38    import jalview.util.Constants;
39   
 
40    public class AlignmentAnnotationUtils
41    {
42   
43    /**
44    * Helper method to populate lists of annotation types for the Show/Hide
45    * Annotations menus. If sequenceGroup is not null, this is restricted to
46    * annotations which are associated with sequences in the selection group.
47    * <p/>
48    * If an annotation row is currently visible, its type (label) is added (once
49    * only per type), to the shownTypes list. If it is currently hidden, it is
50    * added to the hiddenTypesList.
51    * <p/>
52    * For rows that belong to a line graph group, so are always rendered
53    * together:
54    * <ul>
55    * <li>Treat all rows in the group as visible, if at least one of them is</li>
56    * <li>Build a list of all the annotation types that belong to the group</li>
57    * </ul>
58    *
59    * @param shownTypes
60    * a map, keyed by calcId (annotation source), whose entries are the
61    * lists of annotation types found for the calcId; each annotation
62    * type in turn may be a list (in the case of grouped annotations)
63    * @param hiddenTypes
64    * a map, similar to shownTypes, but for hidden annotation types
65    * @param annotations
66    * the annotations on the alignment to scan
67    * @param forSequences
68    * the sequences to restrict search to
69    */
 
70  47 toggle public static void getShownHiddenTypes(
71    Map<String, List<List<String>>> shownTypes,
72    Map<String, List<List<String>>> hiddenTypes,
73    List<AlignmentAnnotation> annotations,
74    List<SequenceI> forSequences)
75    {
76  47 BitSet visibleGraphGroups = AlignmentAnnotationUtils
77    .getVisibleLineGraphGroups(annotations);
78   
79    /*
80    * Build a lookup, by calcId (annotation source), of all annotation types in
81    * each graph group.
82    */
83  47 Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
84   
85    // trackers for which calcId!label combinations we have dealt with
86  47 List<String> addedToShown = new ArrayList<String>();
87  47 List<String> addedToHidden = new ArrayList<String>();
88   
89  47 for (AlignmentAnnotation aa : annotations)
90    {
91    /*
92    * Ignore non-positional annotations, can't render these against an
93    * alignment
94    */
95  224 if (aa.annotations == null)
96    {
97  0 continue;
98    }
99  224 if (forSequences != null && (aa.sequenceRef != null
100    && forSequences.contains(aa.sequenceRef)))
101    {
102  22 String calcId = aa.getCalcId();
103   
104    /*
105    * Build a 'composite label' for types in line graph groups.
106    */
107  22 final List<String> labelAsList = new ArrayList<String>();
108  22 final String displayLabel = aa.label;
109  22 labelAsList.add(displayLabel);
110  22 if (aa.graph == AlignmentAnnotation.LINE_GRAPH
111    && aa.graphGroup > -1)
112    {
113  8 if (!groupLabels.containsKey(calcId))
114    {
115  2 groupLabels.put(calcId, new HashMap<Integer, List<String>>());
116    }
117  8 Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
118    .get(calcId);
119  8 if (groupLabelsForCalcId.containsKey(aa.graphGroup))
120    {
121  4 if (!groupLabelsForCalcId.get(aa.graphGroup)
122    .contains(displayLabel))
123    {
124  4 groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
125    }
126    }
127    else
128    {
129  4 groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
130    }
131    }
132    else
133    /*
134    * 'Simple case' - not a grouped annotation type - list of one label
135    * only
136    */
137    {
138  14 String rememberAs = calcId + "!" + displayLabel;
139  14 if (aa.isForDisplay() && !addedToShown.contains(rememberAs)) // exclude noData annotations
140    {
141  6 if (!shownTypes.containsKey(calcId))
142    {
143  4 shownTypes.put(calcId, new ArrayList<List<String>>());
144    }
145  6 shownTypes.get(calcId).add(labelAsList);
146  6 addedToShown.add(rememberAs);
147    }
148    else
149    {
150  8 if (!aa.visible && !addedToHidden.contains(rememberAs))
151    {
152  8 if (!hiddenTypes.containsKey(calcId))
153    {
154  7 hiddenTypes.put(calcId, new ArrayList<List<String>>());
155    }
156  8 hiddenTypes.get(calcId).add(labelAsList);
157  8 addedToHidden.add(rememberAs);
158    }
159    }
160    }
161    }
162    }
163    /*
164    * Finally add the 'composite group labels' to the appropriate lists,
165    * depending on whether the group is identified as visible or hidden. Don't
166    * add the same label more than once (there may be many graph groups that
167    * generate it).
168    */
169  47 for (String calcId : groupLabels.keySet())
170    {
171  2 for (int group : groupLabels.get(calcId).keySet())
172    {
173  4 final List<String> groupLabel = groupLabels.get(calcId).get(group);
174    // don't want to duplicate 'same types in different order'
175  4 Collections.sort(groupLabel);
176  4 if (visibleGraphGroups.get(group))
177    {
178  2 if (!shownTypes.containsKey(calcId))
179    {
180  1 shownTypes.put(calcId, new ArrayList<List<String>>());
181    }
182  2 if (!shownTypes.get(calcId).contains(groupLabel))
183    {
184  1 shownTypes.get(calcId).add(groupLabel);
185    }
186    }
187    else
188    {
189  2 if (!hiddenTypes.containsKey(calcId))
190    {
191  1 hiddenTypes.put(calcId, new ArrayList<List<String>>());
192    }
193  2 if (!hiddenTypes.get(calcId).contains(groupLabel))
194    {
195  1 hiddenTypes.get(calcId).add(groupLabel);
196    }
197    }
198    }
199    }
200    }
201   
202    /**
203    * Updates the lists of shown and hidden secondary structure types based on
204    * the selected sequence group.
205    *
206    * @param shownTypes
207    * A list that will be populated with the providers of secondary
208    * structures that are shown.
209    * @param hiddenTypes
210    * A list that will be populated with the providers of secondary
211    * structures that are hidden.
212    * @param annotations
213    * A list of AlignmentAnnotation objects.
214    * @param selectedSequenceGroup
215    * The sequence group selected by the user.
216    */
 
217  4 toggle public static void getShownHiddenSecondaryStructureProvidersForGroup(
218    List<String> shownTypes, List<String> hiddenTypes,
219    List<AlignmentAnnotation> annotations,
220    SequenceGroup selectedSequenceGroup)
221    {
222    // Return if the selected sequence group or annotations are null
223  4 if (selectedSequenceGroup == null || annotations == null)
224    {
225  0 return;
226    }
227   
228    // Get the secondary structure sources of the selected sequence group
229  4 List<String> ssSourcesForSelectedGroup = selectedSequenceGroup
230    .getSecondaryStructureSources();
231   
232    // Return if there are no secondary structure sources for the selected group
233  4 if (ssSourcesForSelectedGroup == null
234    || ssSourcesForSelectedGroup.isEmpty())
235    {
236  4 return;
237    }
238   
239    // Iterate through each annotation
240  0 for (AlignmentAnnotation aa : annotations)
241    {
242    /* Skip to the next annotation if the annotation, the annotation's group
243    * reference is null, or the annotation's group reference does not match
244    * the selected group
245    */
246  0 if (aa.annotations == null || aa.groupRef == null
247    || selectedSequenceGroup != aa.groupRef
248    || !aa.label.startsWith(
249    Constants.SECONDARY_STRUCTURE_CONSENSUS_LABEL))
250    {
251  0 continue;
252    }
253   
254    /* Find a provider from the secondary structure sources that matches
255    * the annotation's label. This is to exclude secondary structure
256    * providers which has no secondary structure data for the selected group.
257    */
258  0 Optional<String> provider = ssSourcesForSelectedGroup.stream()
259    .filter(aa.label::contains).findFirst()
260    .map(substring -> aa.label.substring(0,
261    aa.label.indexOf(substring) + substring.length()));
262   
263    // If a matching provider is found and the annotation is visible, add
264    // the provider to the shown types list (if not already in shownTypes).
265    // If the annotation is not visible, add it to hiddenTypes list.
266  0 provider.ifPresent(p -> {
267  0 if (aa.visible && !shownTypes.contains(p))
268    {
269  0 shownTypes.add(p);
270    }
271  0 else if (!aa.visible && !shownTypes.contains(p))
272    {
273  0 hiddenTypes.add(p);
274    }
275    });
276    }
277    }
278   
279    /**
280    * Returns a BitSet (possibly empty) of those graphGroups for line graph
281    * annotations, which have at least one member annotation row marked visible.
282    * <p/>
283    * Only one row in each visible group is marked visible, but when it is drawn,
284    * so are all the other rows in the same group.
285    * <p/>
286    * This lookup set allows us to check whether rows apparently marked not
287    * visible are in fact shown.
288    *
289    * @see AnnotationRenderer#drawComponent
290    * @param annotations
291    * @return
292    */
 
293  48 toggle public static BitSet getVisibleLineGraphGroups(
294    List<AlignmentAnnotation> annotations)
295    {
296  48 BitSet result = new BitSet();
297  48 for (AlignmentAnnotation ann : annotations)
298    {
299  236 if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
300    {
301  6 int gg = ann.graphGroup;
302  6 if (gg > -1)
303    {
304  5 result.set(gg);
305    }
306    }
307    }
308  48 return result;
309    }
310   
311    /**
312    * Converts an array of AlignmentAnnotation into a List of
313    * AlignmentAnnotation. A null array is converted to an empty list.
314    *
315    * @param anns
316    * @return
317    */
 
318  55 toggle public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
319    {
320    // TODO use AlignmentAnnotationI instead when it exists
321  55 return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
322    : Arrays.asList(anns));
323    }
324   
325    /**
326    * Pulls sequence associated annotation on an alignment sequence onto its
327    * dataset sequence and leaves the alignment seuqence's annotation as 'added
328    * reference annotation'
329    *
330    * NB. This will overwrite/delete any existing annotation on the dataset
331    * sequence with matching calcId and label
332    *
333    * @param newAnnot
334    * @param typeName
335    * @param calcId
336    * @param aSeq
337    */
 
338  211 toggle public static void replaceAnnotationOnAlignmentWith(
339    AlignmentAnnotation newAnnot, String typeName, String calcId,
340    SequenceI aSeq)
341    {
342  211 SequenceI dsseq = aSeq.getDatasetSequence();
343  208 while (dsseq.getDatasetSequence() != null)
344    {
345  0 dsseq = dsseq.getDatasetSequence();
346    }
347    // look for same annotation on dataset and lift this one over
348  208 List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
349    typeName);
350  208 if (dsan != null && dsan.size() > 0)
351    {
352  2 for (AlignmentAnnotation dssan : dsan)
353    {
354  2 dsseq.removeAlignmentAnnotation(dssan);
355    }
356    }
357  208 AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
358  208 dsseq.addAlignmentAnnotation(dssan);
359  208 dssan.adjustForAlignment();
360    }
361   
362    /**
363    * Looks for (and materialises) metadata for the given secondary structure annotation
364    * - sets a property Constants.ANNOTATION_DETAILS
365    * -- giving structure ID:chain for 3D structure data
366    * --
367    * @param aa
368    * @return Secondary Structure 'provider'
369    */
 
370  292972 toggle public static String extractSSSourceFromAnnotationDescription(
371    AlignmentAnnotation aa)
372    {
373  292965 if (aa == null || aa.label == null)
374    {
375  0 return null;
376    }
377   
378  292978 String label = aa.label;
379   
380    // Proceed if the label exists in SECONDARY_STRUCTURE_LABELS
381  293024 if (!Constants.SECONDARY_STRUCTURE_LABELS.containsKey(label))
382    {
383  24259 return null;
384    }
385   
386    // Check if the annotation has a provider saved as a property
387  268777 String provider = aa.getProperty(Constants.SS_PROVIDER_PROPERTY);
388  268722 if (provider != null)
389    {
390   
391  268073 if( Constants.PDB.equals(aa.getProperty(Constants.SS_PROVIDER_PROPERTY))
392    && aa.getProperty(Constants.PDBID) != null
393    && !aa.hasAnnotationDetailsProperty() )
394    {
395  7 String annotDetails = aa.getProperty(Constants.PDBID);
396  7 if(aa.getProperty(Constants.CHAINID) != null)
397    {
398  7 annotDetails = annotDetails + ":" + aa.getProperty(Constants.CHAINID);
399    }
400   
401  7 aa.setAnnotationDetailsProperty(annotDetails);
402   
403    }
404   
405  268048 return provider;
406    }
407   
408    // JPred label
409  603 if (Constants.SS_ANNOTATION_FROM_JPRED_LABEL.equals(label))
410    {
411  5 provider = Constants.SECONDARY_STRUCTURE_LABELS.get(label);
412  5 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, provider);
413  5 return provider;
414    }
415   
416    // 3D structure label
417  598 if (aa.description != null
418    && Constants.SS_ANNOTATION_LABEL.equals(label)
419    && Constants.SS_ANNOTATION_LABEL.equals(aa.description))
420    {
421  62 provider = Constants.SECONDARY_STRUCTURE_LABELS.get(label);
422  62 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, provider);
423  62 return provider;
424    }
425   
426    // Identify provider from sequence reference, description and PDB entries
427  536 if (aa.sequenceRef == null
428    || aa.sequenceRef.getDatasetSequence() == null)
429    {
430  431 return null;
431    }
432   
433  105 Vector<PDBEntry> pdbEntries = aa.sequenceRef.getDatasetSequence()
434    .getAllPDBEntries();
435  105 if (pdbEntries == null || pdbEntries.isEmpty())
436    {
437  0 return null;
438    }
439   
440  105 for (PDBEntry entry : pdbEntries)
441    {
442  136 if (entry == null || entry.getId() == null)
443    {
444  0 continue;
445    }
446   
447  136 String entryProvider = entry.getProvider();
448   
449  136 if (entryProvider == null)
450    {
451    // No provider - so this is either an old Jalview project, or not
452    // retrieved from recognised source
453  126 entryProvider = Constants.PDB;
454    }
455   
456    // Should (re)use a standard mechanism for extracting the PDB ID as it
457    // is written 1QWXTUV:CHAIN
458  136 String entryId = entry.getId();
459  136 int colonIndex = entryId.indexOf(':');
460    // Check if colon exists
461  136 if (colonIndex != -1)
462    {
463    // Trim the string from first occurrence of colon
464  5 entryId = entryId.substring(0, colonIndex);
465    }
466   
467    // Annotation from PDB (description text match)
468  136 if (Constants.PDB.equals(entryProvider) && aa.description != null
469    && aa.description.toLowerCase().contains(
470    "secondary structure for " + entryId.toLowerCase()))
471    {
472   
473  95 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, Constants.PDB);
474   
475  95 String annotDetails = aa.getProperty(Constants.PDBID);
476  95 if (annotDetails != null
477    && aa.getProperty(Constants.CHAINID) != null)
478    {
479  91 annotDetails += ":" + aa.getProperty(Constants.CHAINID);
480    }
481   
482  95 if (annotDetails != null)
483    {
484  91 aa.setAnnotationDetailsProperty(annotDetails);
485    }
486   
487  95 return Constants.PDB;
488    }
489   
490    // Annotation from other providers (AlphaFold, SwissModel)
491  41 if (!Constants.PDB.equals(entryProvider)
492    && aa.description.toLowerCase().contains(entryId.toLowerCase()))
493    {
494  10 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, entryProvider);
495  10 return entryProvider;
496    }
497   
498    }
499   
500  0 return null;
501   
502    }
503    }