@Override
  protected void acceptRead(final SAMRecord rec, final ReferenceSequence ref) {
    // see if the whole read should be skipped
    if (recordFilter.filterOut(rec)) return;

    // check read group + library
    final String library =
        (rec.getReadGroup() == null)
            ? UNKNOWN_LIBRARY
            : getOrElse(rec.getReadGroup().getLibrary(), UNKNOWN_LIBRARY);
    if (!libraries.contains(library)) {
      // should never happen if SAM is valid
      throw new PicardException("Record contains library that is missing from header: " + library);
    }

    // set up some constants that don't change in the loop below
    final int contextFullLength = 2 * CONTEXT_SIZE + 1;
    final ArtifactCounter counter = artifactCounters.get(library);
    final byte[] readBases = rec.getReadBases();
    final byte[] readQuals;
    if (USE_OQ) {
      final byte[] tmp = rec.getOriginalBaseQualities();
      readQuals = tmp == null ? rec.getBaseQualities() : tmp;
    } else {
      readQuals = rec.getBaseQualities();
    }

    // iterate over aligned positions
    for (final AlignmentBlock block : rec.getAlignmentBlocks()) {
      for (int offset = 0; offset < block.getLength(); offset++) {
        // remember, these are 1-based!
        final int readPos = block.getReadStart() + offset;
        final int refPos = block.getReferenceStart() + offset;

        // skip low BQ sites
        final byte qual = readQuals[readPos - 1];
        if (qual < MINIMUM_QUALITY_SCORE) continue;

        // skip N bases in read
        final char readBase = Character.toUpperCase((char) readBases[readPos - 1]);
        if (readBase == 'N') continue;

        /**
         * Skip regions outside of intervals.
         *
         * <p>NB: IntervalListReferenceSequenceMask.get() has side-effects which assume that
         * successive ReferenceSequence's passed to this method will be in-order (e.g. it will break
         * if you call acceptRead() with chr1, then chr2, then chr1 again). So this only works if
         * the underlying iteration is coordinate-sorted.
         */
        if (intervalMask != null && !intervalMask.get(ref.getContigIndex(), refPos)) continue;

        // skip dbSNP sites
        if (dbSnpMask != null && dbSnpMask.isDbSnpSite(ref.getName(), refPos)) continue;

        // skip the ends of the reference
        final int contextStartIndex = refPos - CONTEXT_SIZE - 1;
        if (contextStartIndex < 0 || contextStartIndex + contextFullLength > ref.length()) continue;

        // skip contexts with N bases
        final String context = getRefContext(ref, contextStartIndex, contextFullLength);
        if (context.contains("N")) continue;

        // count the base!
        counter.countRecord(context, readBase, rec);
      }
    }
  }