467dafd774e2f5c119a6a0b204d494611eda1189
[jalview.git] / src / jalview / ws2 / actions / annotation / AnnotationJob.java
1 package jalview.ws2.actions.annotation;
2
3 import java.util.ArrayList;
4 import java.util.BitSet;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8
9 import jalview.analysis.AlignSeq;
10 import jalview.analysis.SeqsetUtils;
11 import jalview.datamodel.Sequence;
12 import jalview.datamodel.SequenceCollectionI;
13 import jalview.datamodel.SequenceI;
14 import jalview.schemes.ResidueProperties;
15 import jalview.util.Comparison;
16 import jalview.ws2.actions.BaseJob;
17
18 public class AnnotationJob extends BaseJob
19 {
20   final boolean[] gapMap;
21
22   final Map<String, SequenceI> seqNames;
23
24   final int regionStart, regionEnd;
25
26   final int minSize;
27
28   public AnnotationJob(List<SequenceI> inputSeqs, boolean[] gapMap,
29           Map<String, SequenceI> seqNames, int start, int end, int minSize)
30   {
31     super(inputSeqs);
32     this.gapMap = gapMap;
33     this.seqNames = seqNames;
34     this.regionStart = start;
35     this.regionEnd = end;
36     this.minSize = minSize;
37   }
38
39   @Override
40   public boolean isInputValid()
41   {
42     int nvalid = 0;
43     for (SequenceI sq : getInputSequences())
44       if (sq.getStart() <= sq.getEnd())
45         nvalid++;
46     return nvalid >= minSize;
47   }
48
49   public static AnnotationJob create(SequenceCollectionI inputSeqs,
50           boolean bySequence, boolean submitGaps, boolean requireAligned,
51           boolean filterNonStandardResidues, int minSize)
52   {
53     List<SequenceI> seqences = new ArrayList<>();
54     int minlen = 10;
55     int width = 0;
56     Map<String, SequenceI> namesMap = bySequence ? new HashMap<>() : null;
57     BitSet residueMap = new BitSet();
58     int start = inputSeqs.getStartRes();
59     int end = inputSeqs.getEndRes();
60     // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
61     // correctly
62     // TODO: push attributes into WsJob instance (so they can be safely
63     // persisted/restored
64     for (SequenceI sq : inputSeqs.getSequences())
65     {
66       int sqLen = (bySequence)
67               ? sq.findPosition(end + 1) - sq.findPosition(start + 1)
68               : sq.getEnd() - sq.getStart();
69       if (sqLen < minlen)
70         continue;
71       String newName = SeqsetUtils.unique_name(seqences.size() + 1);
72       if (namesMap != null)
73         namesMap.put(newName, sq);
74       Sequence seq;
75       if (submitGaps)
76       {
77         seq = new Sequence(newName, sq.getSequenceAsString());
78         updateResidueMap(residueMap, seq, filterNonStandardResidues);
79       }
80       else
81       {
82         // TODO: add ability to exclude hidden regions
83         seq = new Sequence(newName,
84                 AlignSeq.extractGaps(Comparison.GapChars,
85                         sq.getSequenceAsString(start, end + 1)));
86         // for annotation need to also record map to sequence start/end
87         // position in range
88         // then transfer back to original sequence on return.
89       }
90       seqences.add(seq);
91       width = Math.max(width, seq.getLength());
92     }
93
94     if (requireAligned && submitGaps)
95     {
96       for (int i = 0; i < seqences.size(); i++)
97       {
98         SequenceI sq = seqences.get(i);
99         char[] padded = fitSequenceToResidueMap(sq.getSequence(),
100                 residueMap);
101         seqences.set(i, new Sequence(sq.getName(), padded));
102       }
103     }
104     boolean[] gapMapArray = null;
105     if (submitGaps)
106     {
107       gapMapArray = new boolean[width];
108       for (int i = 0; i < width; i++)
109         gapMapArray[i] = residueMap.get(i);
110     }
111     return new AnnotationJob(seqences, gapMapArray, namesMap, start, end,
112             minSize);
113   }
114
115   private static void updateResidueMap(BitSet residueMap, SequenceI seq,
116           boolean filterNonStandardResidues)
117   {
118     for (int pos : seq.gapMap())
119     {
120       char sqchr = seq.getCharAt(pos);
121       boolean include = !filterNonStandardResidues;
122       include |= seq.isProtein() ? ResidueProperties.aaIndex[sqchr] < 20
123               : ResidueProperties.nucleotideIndex[sqchr] < 5;
124       if (include)
125         residueMap.set(pos);
126     }
127   }
128
129   /**
130    * Fits the sequence to the residue map removing empty columns where residue
131    * map is unset and padding the sequence with gaps at the end if needed.
132    */
133   private static char[] fitSequenceToResidueMap(char[] sequence,
134           BitSet residueMap)
135   {
136     int width = residueMap.cardinality();
137     char[] padded = new char[width];
138     for (int op = 0, pp = 0; pp < width; op++)
139     {
140       if (residueMap.get(op))
141       {
142         if (sequence.length > op)
143           padded[pp++] = sequence[op];
144         else
145           padded[pp++] = '-';
146       }
147     }
148     return padded;
149   }
150 }