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 }