HsHTSLib-1.9.2: src/Bio/HTS/Utils.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE PartialTypeSignatures #-}
module Bio.HTS.Utils
( markDupBy
, makeKey
, BAMKey
, Orientation
) where
import Conduit
import qualified Data.Map.Strict as M
import Data.List
import Data.Maybe
import qualified Data.Sequence as S
import qualified Data.ByteString.Char8 as B
import Bio.HTS.BAM
import Bio.HTS.Types
data Orientation = F | R | FF | FR | RR | RF deriving (Eq, Ord, Show)
data BAMKey = Single { _ref_id1 :: Int
, _loc1 :: Int
, _orientation :: Orientation
, _barcode :: Maybe B.ByteString }
| Pair { _ref_id1 :: Int
, _ref_id2 :: Int
, _loc1 :: Int
, _loc2 :: Int
, _orientation :: Orientation
, _leftmost :: Bool
, _barcode :: Maybe B.ByteString }
deriving (Eq, Ord, Show)
makeKey :: (BAM -> Maybe B.ByteString) -- ^ Get Barcode
-> BAM
-> (BAMKey, BAMKey)
makeKey fn bam = (single, pair)
where
single = Single ref1 (if isFwd1 then lloc1 else rloc1)
(if isFwd1 then F else R) bc
pair = Pair ref1 ref2 loc1 loc2 orientation isLeftMost bc
bc = fn bam
ref1 = refId bam
lloc1 = startLoc bam - fst clipped1 + 1
rloc1 = endLoc bam + snd clipped1
clipped1 = getClipped $ fromJust $ cigar bam
isFwd1 = not $ isRC flg
ref2 = mateRefId bam
lloc2 = mateStartLoc bam - fst clipped2 + 1
rloc2 = mateStartLoc bam + ciglen cig + snd clipped2
clipped2 = getClipped cig
isFwd2 = not $ isNextRC flg
cig = case queryAuxData ('M', 'C') bam of
Just (AuxString x) -> string2Cigar x
_ -> error "No MC tag. Please run samtools fixmate on file first."
isLeftMost
| ref1 /= ref2 = ref1 < ref2
| otherwise = if isFwd1 == isFwd2
then if isFwd1 then lloc1 <= lloc2 else rloc1 <= rloc2
else if isFwd1 then lloc1 <= rloc2 else rloc1 <= lloc2
orientation
| isLeftMost = if isFwd1 == isFwd2
then if isFwd1
then if isFirstSegment flg then FF else RR
else if isFirstSegment flg then RR else FF
else if isFwd1 then FR else RF
| otherwise = if isFwd1 == isFwd2
then if isFwd1
then if isFirstSegment flg then RR else FF
else if isFirstSegment flg then FF else RR
else if isFwd1 then RF else FR
loc1 | isFwd1 == isFwd2 = if isLeftMost then lloc1 else rloc1
| otherwise = if isFwd1 then lloc1 else rloc1
loc2 | isFwd1 == isFwd2 = if isLeftMost then rloc2 else lloc2
| otherwise = if isFwd1 then rloc2 else lloc2
flg = flag bam
ciglen (CIGAR c) = foldl' f 0 c
where f acc (n,x) = if x `elem` "MDN=X" then n + acc else acc
-- | Get the number of clips at both ends.
getClipped :: CIGAR -> (Int, Int)
getClipped (CIGAR c) | length c <= 1 = (0,0)
| otherwise = (getC $ head c, getC $ last c)
where
getC x = case x of
(n, 'S') -> n
(n, 'H') -> n
_ -> 0
{-# INLINE getClipped #-}
-- | Remove duplicated reads. Duplicates are determined by
-- checking for matching keys. The Key is comprised of:
-- 1. Chromosome
-- 2. Orientation (forward/reverse)
-- 3. Unclipped Start(forward)/End(reverse)
-- 4. Barcode
--
-- Keep the read that has a higher base quality sum (sum of all
-- base qualities in the record above 15).
markDupBy :: MonadIO m
=> (BAM -> Maybe B.ByteString) -- ^ Get Barcode
-> ConduitT BAM BAM m ()
markDupBy bcFn = go (-1,-1) M.empty S.empty
where
go prev keyMap readBuf = await >>= \case
Nothing -> mapM_ (\(_,_,x) -> yield x) readBuf
Just bam -> markDup prev keyMap readBuf bam >>=
(\(a,b,c) -> go a b c)
markDup prev keyMap readBuf bam
| isUnmapped flg = yield bam >> return (prev, keyMap, readBuf)
| not (isSorted prev cur) = error $
"bad coordinate order: " ++ show (prev, cur)
| isDup flg = return (cur, keyMap, readBuf')
| otherwise = do
keyMap' <- addAndMark key bam keyMap
update keyMap' readBuf' cur
where
readBuf' = readBuf S.|> (key, _loc1 singleKey, bam)
cur = (refId bam, startLoc bam)
key | not (hasMultiSegments flg) || isNextUnmapped flg = singleKey
| otherwise = pairKey
(singleKey, pairKey) = makeKey bcFn bam
flg = flag bam
update keyMap readBuf (chr, loc) = mapM_ (\(_,_,b) -> yield b) exclude >>
return ((chr, loc), keyMap', kept)
where
keyMap' = foldl' (\m (k,_,_) -> M.delete k m) keyMap exclude
(exclude, kept) = S.breakl toKeep readBuf
toKeep (key, pos,_) = _ref_id1 key == chr && pos + max_len > loc
addAndMark key bam keyMap = liftIO $ case M.lookup key keyMap of
Nothing -> return $ M.insert key (bam, sc) keyMap
Just (old, old_sc) -> if sc > old_sc -- || (sc == old_sc && queryName bam < queryName old)
then setDup old >> return (M.insert key (bam, sc) keyMap)
else setDup bam >> return keyMap
where
sc = case queryAuxData ('m', 's') bam of
Just (AuxInt x) -> x + fromJust (sumQual 15 bam)
_ -> fromJust $ sumQual 15 bam
max_len = 500
isSorted (chr1, loc1) (chr2, loc2) = chr1 < chr2 ||
(chr1 == chr2 && loc1 <= loc2)
{-# INLINE markDupBy #-}