1 package org.broadinstitute.hellbender.utils.clipping;
2 
3 import htsjdk.samtools.CigarElement;
4 import htsjdk.samtools.CigarOperator;
5 import org.apache.commons.lang3.tuple.Pair;
6 import org.apache.logging.log4j.LogManager;
7 import org.apache.logging.log4j.Logger;
8 import org.broadinstitute.hellbender.exceptions.GATKException;
9 import org.broadinstitute.hellbender.utils.Utils;
10 import org.broadinstitute.hellbender.utils.read.CigarUtils;
11 import org.broadinstitute.hellbender.utils.read.GATKRead;
12 import org.broadinstitute.hellbender.utils.read.ReadUtils;
13 
14 import java.util.ArrayList;
15 import java.util.List;
16 
17 /**
18  * A comprehensive clipping tool.
19  *
20  * General Contract:
21  *  - All clipping operations return a new read with the clipped bases requested, it never modifies the original read.
22  *  - If a read is fully clipped, return an empty SAMRecord, never null.
23  *  - When hard clipping, add cigar operator H for every *reference base* removed (i.e. Matches, SoftClips and Deletions, but *not* insertions). See Hard Clipping notes for details.
24  *
25  *
26  * There are several types of clipping to use:
27  *
28  * Write N's:
29  *   Change the bases to N's in the desired region. This can be applied anywhere in the read.
30  *
31  * Write Q0's:
32  *   Change the quality of the bases in the desired region to Q0. This can be applied anywhere in the read.
33  *
34  * Write both N's and Q0's:
35  *   Same as the two independent operations, put together.
36  *
37  * Soft Clipping:
38  *   Do not change the read, just mark the reads as soft clipped in the Cigar String
39  *   and adjust the alignment start and end of the read.
40  *
41  * Hard Clipping:
42  *   Creates a new read without the hard clipped bases (and base qualities). The cigar string
43  *   will be updated with the cigar operator H for every reference base removed (i.e. Matches,
44  *   Soft clipped bases and deletions, but *not* insertions). This contract with the cigar
45  *   is necessary to allow read.getUnclippedStart() / End() to recover the original alignment
46  *   of the read (before clipping).
47  *
48  */
49 public class ReadClipper {
50     final static Logger logger = LogManager.getLogger(ReadClipper.class);
51     final GATKRead read;
52     boolean wasClipped;
53     List<ClippingOp> ops = null;
54 
55     /**
56      * Initializes a ReadClipper object.
57      *
58      * You can set up your clipping operations using the addOp method. When you're ready to
59      * generate a new read with all the clipping operations, use clipRead().
60      *
61      * Note: Use this if you want to set up multiple operations on the read using the ClippingOp
62      * class. If you just want to apply one of the typical modes of clipping, use the static
63      * clipping functions available in this class instead.
64      *
65      * @param read the read to clip
66      */
ReadClipper(final GATKRead read)67     public ReadClipper(final GATKRead read) {
68         Utils.nonNull(read);
69         this.read = read;
70         this.wasClipped = false;
71     }
72 
73     /**
74      * Add clipping operation to the read.
75      *
76      * You can add as many operations as necessary to this read before clipping. Beware that the
77      * order in which you add these operations matter. For example, if you hard clip the beginning
78      * of a read first then try to hard clip the end, the indices will have changed. Make sure you
79      * know what you're doing, otherwise just use the static functions below that take care of the
80      * ordering for you.
81      *
82      * Note: You only choose the clipping mode when you use clipRead()
83      *
84      * @param op a ClippingOp object describing the area you want to clip.
85      */
addOp(final ClippingOp op)86     public void addOp(final ClippingOp op) {
87         Utils.nonNull(op);
88         if (ops == null) {
89             ops = new ArrayList<>();
90         }
91         ops.add(op);
92     }
93 
94     /**
95      * Check the list of operations set up for this read.
96      *
97      * @return a list of the operations set up for this read.
98      */
getOps()99     public List<ClippingOp> getOps() {
100         return ops;
101     }
102 
103     /**
104      * Check whether or not this read has been clipped.
105      * @return true if this read has produced a clipped read, false otherwise.
106      */
wasClipped()107     public boolean wasClipped() {
108         return wasClipped;
109     }
110 
111     /**
112      * The original read.
113      *
114      * @return  returns the read to be clipped (original)
115      */
getRead()116     public GATKRead getRead() {
117         return read;
118     }
119 
120     /**
121      * Clips a read according to ops and the chosen algorithm.
122      *
123      * @param algorithm What mode of clipping do you want to apply for the stacked operations.
124      * @return the read with the clipping applied (Could be an empty, unmapped read if the clip removed all bases)
125      */
clipRead(final ClippingRepresentation algorithm)126     public GATKRead clipRead(final ClippingRepresentation algorithm) {
127         Utils.nonNull(algorithm);
128         if (ops == null) {
129             return getRead();
130         }
131 
132         GATKRead clippedRead = read;
133         for (final ClippingOp op : getOps()) {
134             final int readLength = clippedRead.getLength();
135             //check if the clipped read can still be clipped in the range requested
136             if (op.start < readLength) {
137                 ClippingOp fixedOperation = op;
138                 if (op.stop >= readLength) {
139                     fixedOperation = new ClippingOp(op.start, readLength - 1);
140                 }
141 
142                 clippedRead = fixedOperation.apply(algorithm, clippedRead);
143             }
144         }
145         wasClipped = true;
146         ops.clear();
147         if ( clippedRead.isEmpty() ) {
148             return ReadUtils.emptyRead(clippedRead);
149         }
150         return clippedRead;
151     }
152 
153 
154     /**
155      * Hard clips the left tail of a read up to (and including) refStop using reference
156      * coordinates.
157      *
158      * @param refStop the last base to be hard clipped in the left tail of the read.
159      * @return a new read, without the left tail (Could be an empty, unmapped read if the clip removed all bases).
160      */
hardClipByReferenceCoordinatesLeftTail(final int refStop)161     private GATKRead hardClipByReferenceCoordinatesLeftTail(final int refStop) {
162         return clipByReferenceCoordinates(-1, refStop, ClippingRepresentation.HARDCLIP_BASES);
163     }
hardClipByReferenceCoordinatesLeftTail(final GATKRead read, final int refStop)164     public static GATKRead hardClipByReferenceCoordinatesLeftTail(final GATKRead read, final int refStop) {
165         return (new ReadClipper(read)).clipByReferenceCoordinates(-1, refStop, ClippingRepresentation.HARDCLIP_BASES);
166     }
167 
168     /**
169      * Hard clips the right tail of a read starting at (and including) refStart using reference
170      * coordinates.
171      *
172      * @param refStart refStop the first base to be hard clipped in the right tail of the read.
173      * @return a new read, without the right tail (Could be an empty, unmapped read if the clip removed all bases).
174      */
hardClipByReferenceCoordinatesRightTail(final int refStart)175     private GATKRead hardClipByReferenceCoordinatesRightTail(final int refStart) {
176         return clipByReferenceCoordinates(refStart, -1, ClippingRepresentation.HARDCLIP_BASES);
177     }
178 
hardClipByReferenceCoordinatesRightTail(final GATKRead read, final int refStart)179     public static GATKRead hardClipByReferenceCoordinatesRightTail(final GATKRead read, final int refStart) {
180         return (new ReadClipper(read)).clipByReferenceCoordinates(refStart, -1, ClippingRepresentation.HARDCLIP_BASES);
181     }
182 
183     /**
184      * Hard clips both tails of a read.
185      *   Left tail goes from the beginning to the 'left' coordinate (inclusive)
186      *   Right tail goes from the 'right' coordinate (inclusive) until the end of the read
187      *
188      * @param left the coordinate of the last base to be clipped in the left tail (inclusive)
189      * @param right the coordinate of the first base to be clipped in the right tail (inclusive)
190      * @return a new read, without the clipped bases (Could return an empty, unmapped read)
191      */
hardClipBothEndsByReferenceCoordinates(final int left, final int right)192     private GATKRead hardClipBothEndsByReferenceCoordinates(final int left, final int right) {
193         if (read.isEmpty() || left == right) {
194             return ReadUtils.emptyRead(read);
195         }
196         final GATKRead leftTailRead = clipByReferenceCoordinates(right, -1, ClippingRepresentation.HARDCLIP_BASES);
197 
198         // after clipping one tail, it is possible that the consequent hard clipping of adjacent deletions
199         // make the left cut index no longer part of the read. In that case, clip the read entirely.
200         if (left > leftTailRead.getEnd()) {
201             return ReadUtils.emptyRead(read);
202         }
203 
204         final ReadClipper clipper = new ReadClipper(leftTailRead);
205         return clipper.hardClipByReferenceCoordinatesLeftTail(left);
206     }
207 
hardClipBothEndsByReferenceCoordinates(final GATKRead read, final int left, final int right)208     public static GATKRead hardClipBothEndsByReferenceCoordinates(final GATKRead read, final int left, final int right) {
209         return (new ReadClipper(read)).hardClipBothEndsByReferenceCoordinates(left, right);
210     }
211 
212 
213     /**
214      * Clips any contiguous tail (left, right or both) with base quality lower than lowQual using the desired algorithm.
215      *
216      * This function will look for low quality tails and hard clip them away. A low quality tail
217      * ends when a base has base quality greater than lowQual.
218      *
219      * @param algorithm the algorithm to use (HardClip, SoftClip, Write N's,...)
220      * @param lowQual every base quality lower than or equal to this in the tail of the read will be hard clipped
221      * @return a new read without low quality tails (Could be an empty, unmapped read if the clip removed all bases).
222      */
clipLowQualEnds(final ClippingRepresentation algorithm, final byte lowQual)223     private GATKRead clipLowQualEnds(final ClippingRepresentation algorithm, final byte lowQual) {
224         if (read.isEmpty()) {
225             return read;
226         }
227 
228         final int readLength = read.getLength();
229         int leftClipIndex = 0;
230         int rightClipIndex = readLength - 1;
231 
232         // check how far we can clip both sides
233         while (rightClipIndex >= 0 && read.getBaseQuality(rightClipIndex) <= lowQual) {
234             rightClipIndex--;
235         }
236         while (leftClipIndex < readLength && read.getBaseQuality(leftClipIndex) <= lowQual) {
237             leftClipIndex++;
238         }
239 
240         // if the entire read should be clipped, then return an empty read.
241         if (leftClipIndex > rightClipIndex) {
242             return ReadUtils.emptyRead(read);
243         }
244 
245         if (rightClipIndex < readLength - 1) {
246             this.addOp(new ClippingOp(rightClipIndex + 1, readLength - 1));
247         }
248         if (leftClipIndex > 0 ) {
249             this.addOp(new ClippingOp(0, leftClipIndex - 1));
250         }
251         return this.clipRead(algorithm);
252     }
253 
hardClipLowQualEnds(final byte lowQual)254     private GATKRead hardClipLowQualEnds(final byte lowQual) {
255         return this.clipLowQualEnds(ClippingRepresentation.HARDCLIP_BASES, lowQual);
256     }
257 
clipLowQualEnds(final GATKRead read, final byte lowQual, final ClippingRepresentation algorithm)258     public static GATKRead clipLowQualEnds(final GATKRead read, final byte lowQual, final ClippingRepresentation algorithm) {
259         return (new ReadClipper(read)).clipLowQualEnds(algorithm, lowQual);
260     }
261 
hardClipLowQualEnds(final GATKRead read, final byte lowQual)262     public static GATKRead hardClipLowQualEnds(final GATKRead read, final byte lowQual) {
263         return (new ReadClipper(read)).hardClipLowQualEnds(lowQual);
264     }
265 
softClipLowQualEnds(final GATKRead read, final byte lowQual)266     public static GATKRead softClipLowQualEnds(final GATKRead read, final byte lowQual) {
267         return (new ReadClipper(read)).clipLowQualEnds(ClippingRepresentation.SOFTCLIP_BASES, lowQual);
268     }
269 
270     /**
271      * Will hard clip every soft clipped bases in the read.
272      *
273      * @return a new read without the soft clipped bases (Could be an empty, unmapped read if it was all soft and hard clips).
274      */
hardClipSoftClippedBases()275     private GATKRead hardClipSoftClippedBases () {
276         if (read.isEmpty()) {
277             return read;
278         }
279 
280         int readIndex = 0;
281         int cutLeft = -1;            // first position to hard clip (inclusive)
282         int cutRight = -1;           // first position to hard clip (inclusive)
283         boolean rightTail = false;   // trigger to stop clipping the left tail and start cutting the right tail
284 
285         for (final CigarElement cigarElement : read.getCigarElements()) {
286             if (cigarElement.getOperator() == CigarOperator.SOFT_CLIP) {
287                 if (rightTail) {
288                     cutRight = readIndex;
289                 }
290                 else {
291                     cutLeft = readIndex + cigarElement.getLength() - 1;
292                 }
293             }
294             else if (cigarElement.getOperator() != CigarOperator.HARD_CLIP) {
295                 rightTail = true;
296             }
297 
298             if (cigarElement.getOperator().consumesReadBases()) {
299                 readIndex += cigarElement.getLength();
300             }
301         }
302 
303         // It is extremely important that we cut the end first otherwise the read coordinates change.
304         if (cutRight >= 0) {
305             this.addOp(new ClippingOp(cutRight, read.getLength() - 1));
306         }
307         if (cutLeft >= 0) {
308             this.addOp(new ClippingOp(0, cutLeft));
309         }
310 
311         return clipRead(ClippingRepresentation.HARDCLIP_BASES);
312     }
hardClipSoftClippedBases(final GATKRead read)313     public static GATKRead hardClipSoftClippedBases (final GATKRead read) {
314         return new ReadClipper(read).hardClipSoftClippedBases();
315     }
316 
317     /**
318      * Hard clip the read to the variable region (from refStart to refStop)
319      *
320      * @param read     the read to be clipped
321      * @param refStart the beginning of the variant region (inclusive)
322      * @param refStop  the end of the variant region (inclusive)
323      * @return the read hard clipped to the variant region (Could return an empty, unmapped read)
324      */
hardClipToRegion( final GATKRead read, final int refStart, final int refStop )325     public static GATKRead hardClipToRegion( final GATKRead read, final int refStart, final int refStop ) {
326         final int start = read.getStart();
327         final int stop = read.getEnd();
328         return hardClipToRegion(read, refStart, refStop, start, stop);
329     }
330 
hardClipToRegion( final GATKRead read, final int refStart, final int refStop, final int alignmentStart, final int alignmentStop)331     private static GATKRead hardClipToRegion( final GATKRead read, final int refStart, final int refStop, final int alignmentStart, final int alignmentStop){
332         // check if the read is contained in region
333         if (alignmentStart <= refStop && alignmentStop >= refStart) {
334             if (alignmentStart < refStart && alignmentStop > refStop) {
335                 return hardClipBothEndsByReferenceCoordinates(read, refStart - 1, refStop + 1);
336             } else if (alignmentStart < refStart) {
337                 return hardClipByReferenceCoordinatesLeftTail(read, refStart - 1);
338             } else if (alignmentStop > refStop) {
339                 return hardClipByReferenceCoordinatesRightTail(read, refStop + 1);
340             }
341             return read;
342         } else {
343             return ReadUtils.emptyRead(read);
344         }
345 
346     }
347 
348     /**
349      * Checks if a read contains adaptor sequences. If it does, hard clips them out.
350      *
351      * Note: To see how a read is checked for adaptor sequence see ReadUtils.getAdaptorBoundary()
352      *
353      * @return a new read without adaptor sequence (Could return an empty, unmapped read)
354      */
hardClipAdaptorSequence()355     private GATKRead hardClipAdaptorSequence () {
356         final int adaptorBoundary = read.getAdaptorBoundary();
357 
358         if (adaptorBoundary == ReadUtils.CANNOT_COMPUTE_ADAPTOR_BOUNDARY || !ReadUtils.isInsideRead(read, adaptorBoundary)) {
359             return read;
360         }
361 
362         return read.isReverseStrand() ? hardClipByReferenceCoordinatesLeftTail(adaptorBoundary) : hardClipByReferenceCoordinatesRightTail(adaptorBoundary);
363     }
hardClipAdaptorSequence(final GATKRead read)364     public static GATKRead hardClipAdaptorSequence (final GATKRead read) {
365         return new ReadClipper(read).hardClipAdaptorSequence();
366     }
367 
368     /**
369      * Turns soft clipped bases into matches
370      * @return a new read with every soft clip turned into a match, or the same read if no softclip bases were found
371      */
revertSoftClippedBases()372     private GATKRead revertSoftClippedBases() {
373         if (read.isEmpty()) {
374             return read;
375         }
376 
377         this.addOp(new ClippingOp(0, 0));
378         return this.clipRead(ClippingRepresentation.REVERT_SOFTCLIPPED_BASES);
379     }
380 
381     /**
382      * Reverts ALL soft-clipped bases
383      *
384      * @param read the read
385      * @return the read with all soft-clipped bases turned into matches (May return empty, unclipped reads close to the beginning of a contig)
386      */
revertSoftClippedBases(final GATKRead read)387     public static GATKRead revertSoftClippedBases(final GATKRead read) {
388         return new ReadClipper(read).revertSoftClippedBases();
389     }
390 
hardClipByReferenceCoordinates(final int refStart, final int refStop)391     protected GATKRead hardClipByReferenceCoordinates(final int refStart, final int refStop) {
392         return clipByReferenceCoordinates(refStart, refStop, ClippingRepresentation.HARDCLIP_BASES);
393     }
394 
395     /**
396      * Generic functionality to  clip a read, used internally by hardClipByReferenceCoordinatesLeftTail
397      * and hardClipByReferenceCoordinatesRightTail. Should not be used directly.
398      *
399      * Note, it REQUIRES you to give the directionality of your hard clip (i.e. whether you're clipping the
400      * left of right tail) by specifying either refStart < 0 or refStop < 0.
401      *
402      * @param refStart  first base to clip (inclusive)
403      * @param refStop last base to clip (inclusive)
404      * @param clippingOp clipping operation to be performed
405      * @return a new read, without the clipped bases (May return empty, unclipped reads)
406      */
clipByReferenceCoordinates(final int refStart, final int refStop, ClippingRepresentation clippingOp)407     protected GATKRead clipByReferenceCoordinates(final int refStart, final int refStop, ClippingRepresentation clippingOp) {
408         if (read.isEmpty()) {
409             return read;
410         }
411         if ((clippingOp == ClippingRepresentation.SOFTCLIP_BASES) && read.isUnmapped()) {
412             throw new GATKException("Cannot soft-clip read "+read.commonToString()+" by reference coordinates because it is unmapped");
413         }
414 
415         final int start;
416         final int stop;
417 
418         // Determine the read coordinate to start and stop hard clipping
419         if (refStart < 0) {
420             if (refStop < 0) {
421                 throw new GATKException("Only one of refStart or refStop must be < 0, not both (" + refStart + ", " + refStop + ")");
422             }
423             start = 0;
424 
425             final Pair<Integer, CigarOperator> stopPosAndOperator = ReadUtils.getReadIndexForReferenceCoordinate(read, refStop);
426 
427             // if the refStop falls in a deletion, the above method returns the position after the deletion.  Since the stop we return here
428             // is inclusive, we decrement the stop to avoid overclipping by one base.  As a result we do not clip the deletion, which is fine.
429             stop = stopPosAndOperator.getLeft() - (stopPosAndOperator.getRight().consumesReadBases() ? 0 : 1);
430         }
431         else {
432             if (refStop >= 0) {
433                 throw new GATKException("Either refStart or refStop must be < 0 (" + refStart + ", " + refStop + ")");
434             }
435             // unlike the above case where we clip the start fo the read, here we clip the end and returning the base to the right of a deletion avoids overclipping
436             start = ReadUtils.getReadIndexForReferenceCoordinate(read, refStart).getLeft();
437             stop = read.getLength() - 1;
438         }
439 
440         if (start == ReadUtils.READ_INDEX_NOT_FOUND || stop == ReadUtils.READ_INDEX_NOT_FOUND) {
441             return read;
442         }
443 
444         if (start < 0 || stop > read.getLength() - 1) {
445             throw new GATKException("Trying to clip before the start or after the end of a read");
446         }
447 
448         if ( start > stop ) {
449             throw new GATKException(String.format("START (%d) > (%d) STOP -- this should never happen, please check read: %s (CIGAR: %s)", start, stop, read, read.getCigar().toString()));
450         }
451 
452         if ( start > 0 && stop < read.getLength() - 1) {
453             throw new GATKException(String.format("Trying to clip the middle of the read: start %d, stop %d, cigar: %s", start, stop, read.getCigar().toString()));
454         }
455         this.addOp(new ClippingOp(start, stop));
456 
457         final GATKRead clippedRead = clipRead(clippingOp);
458         this.ops = null;
459         return clippedRead;
460     }
461 
462 
463 
464     /**
465      * Soft clip the read to the variable region (from refStart to refStop) processing also the clipped bases
466      *
467      * @param read     the read to be clipped
468      * @param refStart the beginning of the variant region (inclusive)
469      * @param refStop  the end of the variant region (inclusive)
470      * @return the read soft clipped to the variant region (May return empty, unclipped reads)
471      */
softClipToRegionIncludingClippedBases( final GATKRead read, final int refStart, final int refStop )472     public static GATKRead softClipToRegionIncludingClippedBases( final GATKRead read, final int refStart, final int refStop ) {
473         final int start = read.getUnclippedStart();
474         final int stop = start + CigarUtils.countRefBasesAndClips(read.getCigarElements(), 0, read.numCigarElements()) - 1;
475 
476         if (start <= refStop && stop >= refStart) {
477             if (start < refStart && stop > refStop) {
478                 return (new ReadClipper(read)).softClipBothEndsByReferenceCoordinates(refStart - 1, refStop + 1);
479             } else if (start < refStart) {
480                 return (new ReadClipper(read)).softClipByReferenceCoordinates(-1, refStart - 1);
481             } else if (stop > refStop) {
482                 return (new ReadClipper(read)).softClipByReferenceCoordinates(refStop + 1, -1);
483             }
484             return read;
485         } else {
486             logger.warn("Attempting to clip the entirety of a read by region: %s", read.toString());
487             return ReadUtils.emptyRead(read);
488         }
489     }
490 
491 
492     /**
493      * Soft clips both tails of a read.
494      *   Left tail goes from the beginning to the 'left' coordinate (inclusive)
495      *   Right tail goes from the 'right' coordinate (inclusive) until the end of the read
496      *
497      * @param left the coordinate of the last base to be clipped in the left tail (inclusive)
498      * @param right the coordinate of the first base to be clipped in the right tail (inclusive)
499      * @return a new read, without the clipped bases (May return empty, unclipped reads)
500      */
softClipBothEndsByReferenceCoordinates(final int left, final int right)501     private GATKRead softClipBothEndsByReferenceCoordinates(final int left, final int right) {
502         if (read.isEmpty()) {
503             return ReadUtils.emptyRead(read);
504         }
505         if (left == right) {
506             logger.warn("Attempting to clip the entirety of a read by by reference coordinates: %s", read.toString());
507             return ReadUtils.emptyRead(read);
508         }
509         final GATKRead leftTailRead = softClipByReferenceCoordinates(right, -1);
510 
511         // after clipping one tail, it is possible that the consequent hard clipping of adjacent deletions
512         // make the left cut index no longer part of the read. In that case, clip the read entirely.
513         if (left > leftTailRead.getEnd()) {
514             return ReadUtils.emptyRead(read);
515         }
516 
517         final ReadClipper clipper = new ReadClipper(leftTailRead);
518         return clipper.softClipByReferenceCoordinates(-1, left);
519     }
520 
softClipBothEndsByReferenceCoordinates(final GATKRead read, final int left, final int right)521     public static GATKRead softClipBothEndsByReferenceCoordinates(final GATKRead read, final int left, final int right) {
522         return (new ReadClipper(read)).softClipBothEndsByReferenceCoordinates(left, right);
523     }
524 
525     /**
526      * Generic functionality to soft clip a read (is analogous to hardClipByReferenceCoordinates())
527      *
528      * Note, it REQUIRES you to give the directionality of your soft clip (i.e. whether you're clipping the
529      * left of right tail) by specifying either refStart < 0 or refStop < 0.
530      *
531      * @param refStart  first base to clip (inclusive)
532      * @param refStop last base to clip (inclusive)
533      * @return a new read, with the soft clipped bases
534      */
softClipByReferenceCoordinates(final int refStart, final int refStop)535     protected GATKRead softClipByReferenceCoordinates(final int refStart, final int refStop) {
536         return clipByReferenceCoordinates(refStart, refStop, ClippingRepresentation.SOFTCLIP_BASES);
537     }
538 
539 
540     /**
541      * Soft clips a read using read coordinates.
542      *
543      * @param start the first base to clip (inclusive)
544      * @param stop the last base to clip (inclusive)
545      * @return a new read, without the clipped bases (May return empty, unclipped reads)
546      */
softClipByReadCoordinates(final int start, final int stop)547     private GATKRead softClipByReadCoordinates(final int start, final int stop) {
548         if (read.isEmpty()) {
549             return ReadUtils.emptyRead(read);
550         }
551         if ( (start == 0 && stop == read.getLength() - 1)) {
552             logger.warn("Attempting to clip the entirety of a read by by read coordinates: %s", read.toString());
553             return ReadUtils.emptyRead(read);
554         }
555 
556         this.addOp(new ClippingOp(start, stop));
557         return clipRead(ClippingRepresentation.SOFTCLIP_BASES);
558     }
559 
softClipByReadCoordinates(final GATKRead read, final int start, final int stop)560     public static GATKRead softClipByReadCoordinates(final GATKRead read, final int start, final int stop) {
561         return (new ReadClipper(read)).softClipByReadCoordinates(start, stop);
562     }
563 }