Clover icon

Coverage Report

  1. Project Clover database Wed Dec 3 2025 17:03:17 GMT
  2. Package jalview.analysis

File AlignmentAnnotationUtils.java

 

Coverage histogram

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

Code metrics

92
137
7
1
552
332
81
0.59
19.57
7
11.57

Classes

Class Line # Actions
AlignmentAnnotationUtils 40 137 81
0.8432203584.3%
 

Contributing tests

This file is covered by 267 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<>();
84   
85    // trackers for which calcId!label combinations we have dealt with
86  47 List<String> addedToShown = new ArrayList<>();
87  47 List<String> addedToHidden = new ArrayList<>();
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<>();
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    * - annotation row associated with a sequence to be propagated to
335    * its reference annotation
336    * @param typeName
337    * - label used to match existing row
338    * @param calcId
339    * - calcId for existing row
340    * @param aSeq
341    * - Alignment sequence which overrides newAnnot.sequenceRef for resolving dataset sequence
342    */
 
343  214 toggle public static void replaceAnnotationOnAlignmentWith(
344    AlignmentAnnotation newAnnot, String typeName, String calcId,
345    SequenceI aSeq)
346    {
347  214 if (newAnnot.sequenceRef==null && aSeq==null)
348    {
349    // throw new Error("Need a reference sequence to resolve dataset!")
350  0 return;
351    }
352    // JAL-4107 - adope 2.12's slightly safer resolution of destination dataset sequence
353  214 SequenceI dsseq = (aSeq!=null) ? aSeq : newAnnot.sequenceRef;
354  422 while (dsseq.getDatasetSequence() != null)
355    {
356  208 dsseq = dsseq.getDatasetSequence();
357    }
358    // look for same annotation on dataset and lift this one over
359  214 List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
360    typeName);
361  214 if (dsan != null && dsan.size() > 0)
362    {
363  8 for (AlignmentAnnotation dssan : dsan)
364    {
365  8 dsseq.removeAlignmentAnnotation(dssan);
366    }
367    }
368  214 AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
369  214 dsseq.addAlignmentAnnotation(dssan);
370  214 dssan.adjustForAlignment();
371    }
372   
373    /**
374    * Looks for (and materialises) metadata for the given secondary structure annotation
375    * - sets a property Constants.ANNOTATION_DETAILS
376    * -- giving structure ID:chain for 3D structure data
377    * --
378    * @param aa
379    * @return Secondary Structure 'provider'
380    */
 
381  163789 toggle public static String extractSSSourceFromAnnotationDescription(
382    AlignmentAnnotation aa)
383    {
384  163793 if (aa == null || aa.label == null)
385    {
386  0 return null;
387    }
388   
389  163796 String label = aa.label;
390   
391    // Proceed if the label exists in SECONDARY_STRUCTURE_LABELS
392  163795 if (!Constants.SECONDARY_STRUCTURE_LABELS.containsKey(label))
393    {
394  18695 return null;
395    }
396   
397    // Check if the annotation has a provider saved as a property
398  145099 String provider = aa.getProperty(Constants.SS_PROVIDER_PROPERTY);
399  145089 if (provider != null)
400    {
401   
402  144582 if( Constants.PDB.equals(aa.getProperty(Constants.SS_PROVIDER_PROPERTY))
403    && aa.getProperty(Constants.PDBID) != null
404    && !aa.hasAnnotationDetailsProperty() )
405    {
406  14 String annotDetails = aa.getProperty(Constants.PDBID);
407  14 if(aa.getProperty(Constants.CHAINID) != null)
408    {
409  14 annotDetails = annotDetails + ":" + aa.getProperty(Constants.CHAINID);
410    }
411   
412  14 aa.setAnnotationDetailsProperty(annotDetails);
413   
414    }
415   
416  144579 return provider;
417    }
418   
419    // JPred label
420  497 if (Constants.SS_ANNOTATION_FROM_JPRED_LABEL.equals(label))
421    {
422  5 provider = Constants.SECONDARY_STRUCTURE_LABELS.get(label);
423  5 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, provider);
424  5 return provider;
425    }
426   
427    // 3D structure label
428  492 if (aa.description != null
429    && Constants.SS_ANNOTATION_LABEL.equals(label)
430    && Constants.SS_ANNOTATION_LABEL.equals(aa.description))
431    {
432  62 provider = Constants.SECONDARY_STRUCTURE_LABELS.get(label);
433  62 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, provider);
434  62 return provider;
435    }
436   
437    // Identify provider from sequence reference, description and PDB entries
438  430 if (aa.sequenceRef == null
439    || aa.sequenceRef.getDatasetSequence() == null)
440    {
441  351 return null;
442    }
443   
444  79 Vector<PDBEntry> pdbEntries = aa.sequenceRef.getDatasetSequence()
445    .getAllPDBEntries();
446  79 if (pdbEntries == null || pdbEntries.isEmpty())
447    {
448  0 return null;
449    }
450   
451  79 for (PDBEntry entry : pdbEntries)
452    {
453  107 if (entry == null || entry.getId() == null)
454    {
455  0 continue;
456    }
457   
458  107 String entryProvider = entry.getProvider();
459   
460  107 if (entryProvider == null)
461    {
462    // No provider - so this is either an old Jalview project, or not
463    // retrieved from recognised source
464  97 entryProvider = Constants.PDB;
465    }
466   
467    // Should (re)use a standard mechanism for extracting the PDB ID as it
468    // is written 1QWXTUV:CHAIN
469  107 String entryId = entry.getId();
470  107 int colonIndex = entryId.indexOf(':');
471    // Check if colon exists
472  107 if (colonIndex != -1)
473    {
474    // Trim the string from first occurrence of colon
475  5 entryId = entryId.substring(0, colonIndex);
476    }
477   
478    // Annotation from PDB (description text match)
479  107 if (Constants.PDB.equals(entryProvider) && aa.description != null
480    && aa.description.toLowerCase().contains(
481    "secondary structure for " + entryId.toLowerCase()))
482    {
483   
484  69 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, Constants.PDB);
485   
486  69 String annotDetails = aa.getProperty(Constants.PDBID);
487  69 if (annotDetails != null
488    && aa.getProperty(Constants.CHAINID) != null)
489    {
490  65 annotDetails += ":" + aa.getProperty(Constants.CHAINID);
491    }
492   
493  69 if (annotDetails != null)
494    {
495  65 aa.setAnnotationDetailsProperty(annotDetails);
496    }
497   
498  69 return Constants.PDB;
499    }
500   
501    // Annotation from other providers (AlphaFold, SwissModel)
502  38 if (!Constants.PDB.equals(entryProvider)
503    && aa.description.toLowerCase().contains(entryId.toLowerCase()))
504    {
505  10 aa.setProperty(Constants.SS_PROVIDER_PROPERTY, entryProvider);
506  10 return entryProvider;
507    }
508   
509    }
510   
511  0 return null;
512   
513    }
514   
515    /**
516    * replace an existing sequence associated annotation with another, creating
517    * association as necessary.
518    *
519    * @param newAnnot
520    * - annotation row associated with a sequence to be propagated to
521    * its reference annotation
522    * @param typeName
523    * - label used to match existing row
524    * @param calcId
525    * - calcId for existing row
526    */
 
527  27 toggle public static void replaceAnnotationOnAlignmentWith(
528    AlignmentAnnotation newAnnot, String typeName, String calcId)
529    {
530  27 if (newAnnot.sequenceRef != null)
531    {
532  27 SequenceI dsseq = newAnnot.sequenceRef;
533  54 while (dsseq.getDatasetSequence() != null)
534    {
535  27 dsseq = dsseq.getDatasetSequence();
536    }
537    // look for same annotation on dataset and lift this one over
538  27 List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
539    typeName);
540  27 if (dsan != null && dsan.size() > 0)
541    {
542  0 for (AlignmentAnnotation dssan : dsan)
543    {
544  0 dsseq.removeAlignmentAnnotation(dssan);
545    }
546    }
547  27 AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
548  27 dsseq.addAlignmentAnnotation(dssan);
549  27 dssan.adjustForAlignment();
550    }
551    }
552    }