package jalview.ws2.actions.annotation; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; import jalview.analysis.AlignSeq; import jalview.analysis.SeqsetUtils; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceCollectionI; import jalview.datamodel.SequenceI; import jalview.schemes.ResidueProperties; import jalview.util.Comparison; import jalview.ws2.actions.BaseJob; public class AnnotationJob extends BaseJob { final boolean[] gapMap; final Map seqNames; final int regionStart, regionEnd; final int minSize; public AnnotationJob(List inputSeqs, boolean[] gapMap, Map seqNames, int start, int end, int minSize) { super(inputSeqs); this.gapMap = gapMap; this.seqNames = seqNames; this.regionStart = start; this.regionEnd = end; this.minSize = minSize; } @Override public boolean isInputValid() { int nvalid = 0; for (SequenceI sq : getInputSequences()) if (sq.getStart() <= sq.getEnd()) nvalid++; return nvalid >= minSize; } public static AnnotationJob create(SequenceCollectionI inputSeqs, boolean bySequence, boolean submitGaps, boolean requireAligned, boolean filterNonStandardResidues, int minSize) { List seqences = new ArrayList<>(); int minlen = 10; int width = 0; Map namesMap = bySequence ? new HashMap<>() : null; BitSet residueMap = new BitSet(); int start = inputSeqs.getStartRes(); int end = inputSeqs.getEndRes(); // TODO: URGENT! unify with JPred / MSA code to handle hidden regions // correctly // TODO: push attributes into WsJob instance (so they can be safely // persisted/restored for (SequenceI sq : inputSeqs.getSequences()) { int sqLen = (bySequence) ? sq.findPosition(end + 1) - sq.findPosition(start + 1) : sq.getEnd() - sq.getStart(); if (sqLen < minlen) continue; String newName = SeqsetUtils.unique_name(seqences.size() + 1); if (namesMap != null) namesMap.put(newName, sq); Sequence seq; if (submitGaps) { seq = new Sequence(newName, sq.getSequenceAsString()); updateResidueMap(residueMap, seq, filterNonStandardResidues); } else { // TODO: add ability to exclude hidden regions seq = new Sequence(newName, AlignSeq.extractGaps(Comparison.GapChars, sq.getSequenceAsString(start, end + 1))); // for annotation need to also record map to sequence start/end // position in range // then transfer back to original sequence on return. } seqences.add(seq); width = Math.max(width, seq.getLength()); } if (requireAligned && submitGaps) { for (int i = 0; i < seqences.size(); i++) { SequenceI sq = seqences.get(i); char[] padded = fitSequenceToResidueMap(sq.getSequence(), residueMap); seqences.set(i, new Sequence(sq.getName(), padded)); } } boolean[] gapMapArray = null; if (submitGaps) { gapMapArray = new boolean[width]; for (int i = 0; i < width; i++) gapMapArray[i] = residueMap.get(i); } return new AnnotationJob(seqences, gapMapArray, namesMap, start, end, minSize); } private static void updateResidueMap(BitSet residueMap, SequenceI seq, boolean filterNonStandardResidues) { for (int pos : seq.gapMap()) { char sqchr = seq.getCharAt(pos); boolean include = !filterNonStandardResidues; include |= seq.isProtein() ? ResidueProperties.aaIndex[sqchr] < 20 : ResidueProperties.nucleotideIndex[sqchr] < 5; if (include) residueMap.set(pos); } } /** * Fits the sequence to the residue map removing empty columns where residue * map is unset and padding the sequence with gaps at the end if needed. */ private static char[] fitSequenceToResidueMap(char[] sequence, BitSet residueMap) { int width = residueMap.cardinality(); char[] padded = new char[width]; for (int op = 0, pp = 0; pp < width; op++) { if (residueMap.get(op)) { if (sequence.length > op) padded[pp++] = sequence[op]; else padded[pp++] = '-'; } } return padded; } }