packages feed

bbi 0.1.0 → 0.1.1

raw patch · 6 files changed

+293/−152 lines, 6 filesdep +bbidep +bioinformatics-toolkitdep +randomdep ~basedep ~bytestringdep ~conduitPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: bbi, bioinformatics-toolkit, random, tasty, tasty-golden, tasty-hunit, vector

Dependency ranges changed: base, bytestring, conduit

API changes (from Hackage documentation)

- Data.BBI.BigBed: toBedRecords :: Monad m => Endianness -> Conduit ByteString m (Int, Int, Int, ByteString)
+ Data.BBI.BigBed: closeBBedFile :: BBedFile -> IO ()
+ Data.BBI.BigBed: data BBedFile
+ Data.BBI.BigBed: openBBedFile :: FilePath -> IO BBedFile
+ Data.BBI.BigBed: query :: (ByteString, Int, Int) -> BBedFile -> ConduitT () (ByteString, Int, Int, ByteString) IO ()
- Data.BBI: BbiFile :: Handle -> !BbiFileHeader -> !(Map ByteString (Int, Int)) -> !RTreeHeader -> BbiFile
+ Data.BBI: BbiFile :: Handle -> !BbiFileHeader -> !Map Chromosome (ChromID, ChromSize) -> !RTreeHeader -> BbiFile
- Data.BBI: [_chromTree] :: BbiFile -> !(Map ByteString (Int, Int))
+ Data.BBI: [_chromTree] :: BbiFile -> !Map Chromosome (ChromID, ChromSize)
- Data.BBI: getBbiFileHeader :: Handle -> IO (Either String BbiFileHeader)
+ Data.BBI: getBbiFileHeader :: Handle -> IO BbiFileHeader
- Data.BBI: getChromId :: BbiFile -> ByteString -> Maybe Int
+ Data.BBI: getChromId :: BbiFile -> ByteString -> Maybe ChromID
- Data.BBI: overlappingBlocks :: BbiFile -> (Int, Int, Int) -> IO [(Int, Int)]
+ Data.BBI: overlappingBlocks :: BbiFile -> (ChromID, Int, Int) -> IO [Block]
- Data.BBI: readBlocks :: BbiFile -> [(Int, Int)] -> Source IO ByteString
+ Data.BBI: readBlocks :: BbiFile -> [Block] -> Source IO ByteString

Files

bbi.cabal view
@@ -1,18 +1,14 @@--- Initial bbi.cabal generated by cabal init.  For further documentation, --- see http://haskell.org/cabal/users-guide/- name:                bbi-version:             0.1.0+version:             0.1.1 synopsis:            Tools for reading Big Binary Indexed files, e.g., bigBed, bigWig--- description:         +-- description: license:             BSD3 license-file:        LICENSE author:              Kai Zhang maintainer:          kai@kzhang.org-copyright:           (c) 2014 Kai Zhang+copyright:           (c) 2014-2019 Kai Zhang category:            Bio build-type:          Simple--- extra-source-files:   cabal-version:       >=1.10  library@@ -21,13 +17,12 @@     , Data.BBI.BigBed     , Data.BBI.BigWig     , Data.BBI.Utils-  -- other-modules:           build-depends:       base >=4.7 && <5.0     , bytestring >=0.10.0.0     , cereal >=0.4.0-    , conduit >=1.2.0.0+    , conduit     , mtl     , containers     , zlib@@ -35,3 +30,28 @@   hs-source-dirs:      src    default-language:    Haskell2010++test-suite tests+  type: exitcode-stdio-1.0+  hs-source-dirs: tests+  main-is: tests.hs+  other-modules:+      Tests.BigBed++  default-language:    Haskell2010+  build-depends:+      base+    , bytestring+    , conduit+    , random+    , vector+    , tasty+    , tasty-golden+    , tasty-hunit+    , mtl+    , bioinformatics-toolkit+    , bbi++source-repository  head+  type: git+  location: https://github.com/kaizhang/bbi.git
src/Data/BBI.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-}+{-# LANGUAGE LambdaCase #-}  module Data.BBI     ( BbiFile(..)@@ -18,7 +19,7 @@ import Control.Monad.State import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL-import Data.Conduit+import Conduit import Data.Maybe import qualified Data.Map.Strict as M import System.IO@@ -28,13 +29,45 @@               | BigWig     deriving (Show) -data BbiFile = BbiFile-    { _filehandle :: Handle-    , _header :: !BbiFileHeader-    , _chromTree :: !(M.Map B.ByteString (Int, Int))-    , _rtree :: !RTreeHeader-    } deriving (Show)+-- | Get the file type.+getFileType :: Handle -> IO (FileType, Endianness)+getFileType h = do+    hSeek h AbsoluteSeek 0+    magic <- B.hGet h 4+    let magicBE = readInt32 BE magic+        magicLE = readInt32 LE magic+    return $ case () of+        _ | magicBE == bigBedMagic -> (BigBed, BE)+          | magicBE == bigWigMagic -> (BigWig, BE)+          | magicLE == bigBedMagic -> (BigBed, LE)+          | magicLE == bigWigMagic -> (BigWig, LE)+          | otherwise -> error "Not a bigBed or bigWig file!"+  where+    bigWigMagic = 0x888FFC26+    bigBedMagic = 0x8789F2EB+{-# INLINE getFileType #-} ++-- | The header for big index file. Here are the Byte-by-byte details:+-- --------------------  ----  ----  -------------------------------------------+-- Name                  Size  Type	 Description+-- --------------------  ----  ----  -------------------------------------------+-- magic                 4     uint  0x888FFC26 for BigWig, 0x8789F2EB for+--                                   BigBed.  If byte-swapped, all+--                                   numbers in file are byte-swapped.+-- version               2     uint	 Currently 3. +-- zoomLevels            2     uint	 Number of different zoom summary resolutions.+-- chromosomeTreeOffset	 8     uint	 Offset in file to chromosome B+ tree index.+-- fullDataOffset        8     uint	 Offset to main (unzoomed) data. Points+--                                   specifically to the dataCount.+-- fullIndexOffset       8     uint	 Offset to R tree index of items.+-- fieldCount            2     uint	 Number of fields in BED file. (0 for BigWig)+-- definedFieldCount     2     uint	 Number of fields that are predefined BED fields.+-- autoSqlOffset         8     uint	 Offset to zero-terminated string with .as spec.+-- totalSummaryOffset    8     uint	 Offset to overall file summary data block.+-- uncompressBufSize     4     uint	 Maximum size of decompression buffer+--                                   needed (nonzero on compressed files).+-- reserved              8     uint	 Reserved for future expansion. Currently 0. data BbiFileHeader = BbiFileHeader     { _filetype :: !FileType     , _endian :: !Endianness@@ -51,96 +84,30 @@     , _reserved :: !Int     } deriving (Show) -openBbiFile :: FilePath -> IO BbiFile-openBbiFile fl = do-    h <- openFile fl ReadMode-    header <- fromRight <$> getBbiFileHeader h-    t <- fromRight <$> getChromTreeAsList (_endian header) h (fromIntegral $ _chrTreeOffset header)-    rtree <- fromRight <$> getRTreeHeader (_endian header) h (fromIntegral $ _fullIndexOffset header)-    return $ BbiFile h header (M.fromList t) rtree--closeBbiFile :: BbiFile -> IO ()-closeBbiFile = hClose . _filehandle--getBbiFileHeader :: Handle -> IO (Either String BbiFileHeader)+getBbiFileHeader :: Handle -> IO BbiFileHeader getBbiFileHeader h = do-    ft <- getFileType h-    case ft of-        Right (t, e) -> do-            hSeek h AbsoluteSeek 4-            v <- hReadInt16 e h-            zl <- hReadInt16 e h-            ct <- hReadInt64 e h-            fd <- hReadInt64 e h-            fi <- hReadInt64 e h-            fc <- hReadInt16 e h-            df <- hReadInt16 e h-            as <- hReadInt64 e h-            ts <- hReadInt64 e h-            ub <- hReadInt32 e h-            r <- hReadInt64 e h-            return . Right $ BbiFileHeader t e v zl ct fd fi fc df as ts ub r-        Left e -> return $ Left e+    (ft, endi) <- getFileType h +    hSeek h AbsoluteSeek 4+    BbiFileHeader <$> return ft <*> return endi <*> hReadInt16 endi h <*>+        hReadInt16 endi h <*> hReadInt64 endi h <*> hReadInt64 endi h <*>+        hReadInt64 endi h <*> hReadInt16 endi h <*> hReadInt16 endi h <*>+        hReadInt64 endi h <*> hReadInt64 endi h <*> hReadInt32 endi h <*>+        hReadInt64 endi h+{-# INLINE getBbiFileHeader #-} -getFileType :: Handle -> IO (Either String (FileType, Endianness))-getFileType h = do-    hSeek h AbsoluteSeek 0-    magic <- B.hGet h 4-    let magicBE = readInt32 BE magic-        magicLE = readInt32 LE magic-    return $ case () of-        _ | magicBE == bigBedMagic -> Right (BigBed, BE)-          | magicBE == bigWigMagic -> Right (BigWig, BE)-          | magicLE == bigBedMagic -> Right (BigBed, LE)-          | magicLE == bigWigMagic -> Right (BigWig, LE)-          | otherwise -> Left "not a bigBed/bigWig file"-  where-    bigWigMagic = 0x888FFC26-    bigBedMagic = 0x8789F2EB-{-# INLINE getFileType #-} -getChromTreeAsList :: Endianness -> Handle -> Integer -> IO (Either String [(B.ByteString, (Int, Int))])-getChromTreeAsList e h offset = do-    hSeek h AbsoluteSeek offset-    magic <- hReadInt32 e h-    if magic /= chromTreeMagic-       then return $ Left "wrong chromosome tree header"-       else do-           _ <- hReadInt32 e h -- blockSize, not used-           keySize <- hReadInt32 e h-           _ <- hReadInt32 e h -- valSize, not used-           _ <- hReadInt64 e h -- itemCount, not used-           _ <- hReadInt64 e h -- reserved, not used-           Right <$> traverseTree keySize-  where-    traverseTree ks = go-      where-        go = do (isLeaf, n) <- readNode-                if isLeaf-                   then replicateM n $ readLeafItem ks-                   else do-                       xs <- replicateM n $ readNonLeafItem ks-                       rs <- forM xs $ \(_, off) -> do-                           hSeek h AbsoluteSeek $ fromIntegral off-                           go-                       return $ concat rs--    chromTreeMagic = 0x78CA8C91--    readNode = do isLeaf <- hReadBool h-                  _ <- hReadBool h-                  count <- hReadInt16 e h-                  return (isLeaf, count)--    readLeafItem n = do key <- B.filter (/=0) <$> B.hGet h n-                        chromId <- hReadInt32 e h-                        chromSize <- hReadInt32 e h-                        return (key, (chromId, chromSize))--    readNonLeafItem n = do key <- B.filter (/=0) <$> B.hGet h n-                           childOffset <- hReadInt64 e h-                           return (key, childOffset)-+-- | R tree index header.+-- Name Size	Type	Description+-- magic	4	uint	0x2468ACE0.  If byte-swapped all numbers in index are byte-swapped.+-- blockSize	4	uint	Number of children per block (not byte size of block). +-- itemCount	8	uint	The number of chromosomes/contigs.+-- startChromIx	4	uint	ID of first chromosome in index.+-- startBase	4	uint	Position of first base in index.+-- endChromIx	4	uint	ID of last chromosome in index.+-- endBase	4	uint	Position of last base in index.+-- endFileOffset	8	uint	Position in file where data being indexed ends.+-- itemsPerSlot	4	uint	Number of items pointed to by leaves of index.+-- Reserved	4	uint	Reserved for future expansion. Currently 0. data RTreeHeader = RTreeHeader     { _blockSize :: Int     , _itemCount :: Int@@ -153,36 +120,110 @@     , _rTreeReserved :: Int     } deriving (Show) -getRTreeHeader :: Endianness -> Handle -> Integer -> IO (Either String RTreeHeader)+getRTreeHeader :: Endianness+               -> Handle+               -> Integer   -- ^ Location of R Tree header in the file.+               -> IO RTreeHeader getRTreeHeader e h i = do-    hSeek h AbsoluteSeek i-    magic <- hReadInt32 e h-    if magic /= rtmagic-       then return $ Left "incorrect RTree magic"+    magic <- hSeek h AbsoluteSeek i >> hReadInt32 e h+    if magic /= 0x2468ACE0+       then error "incorrect RTree magic"+       else RTreeHeader <$> hReadInt32 e h <*> hReadInt64 e h <*>+           hReadInt32 e h <*> hReadInt32 e h <*> hReadInt32 e h <*>+           hReadInt32 e h <*> hReadInt64 e h <*> hReadInt32 e h <*>+           hReadInt32 e h+++-- | Big index file format. Here are the Byte-by-byte details:+-- --------------  ------  -----------------------------------------------------+-- Name            Size    Description+-- --------------  ------  -----------------------------------------------------+-- bbiHeader       64      Contains high-level information about file and+--                         offsets to various parts of file.+-- zoomHeaders     N*24    One for each level of zoom built into file.+-- autoSql         Varies  Zero-terminated string in autoSql format describing+--                         formats.  Optional, not used in BigWig.+-- totalSummary    40      Statistical summary of entire file.+-- chromosomeTree  Varies  B+ tree-formatted index of chromosomes, their sizes,+--                         and a unique ID for each.+-- dataCount       4       Number of records in data. For BigWig this+--                         corresponds to the number of sections, not the+--                         number of floating point values.+-- data            Varies  Possibly compressed data in format specific for+--                         file type.+-- index           Varies  R tree index of data.+-- zoomInfo        Varies  One for each zoom level.+data BbiFile = BbiFile+    { _filehandle :: Handle+    , _header :: !BbiFileHeader+    , _chromTree :: !(M.Map Chromosome (ChromID, ChromSize))+    , _rtree :: !RTreeHeader+    } deriving (Show)++type Chromosome = B.ByteString+type ChromSize = Int+type ChromID = Int++openBbiFile :: FilePath -> IO BbiFile+openBbiFile fl = do+    h <- openFile fl ReadMode+    header <- getBbiFileHeader h+    t <- getChromTreeAsList (_endian header) h (fromIntegral $ _chrTreeOffset header)+    rtree <- getRTreeHeader (_endian header) h (fromIntegral $ _fullIndexOffset header)+    return $ BbiFile h header (M.fromList t) rtree++closeBbiFile :: BbiFile -> IO ()+closeBbiFile = hClose . _filehandle++getChromTreeAsList :: Endianness+                   -> Handle+                   -> Integer   -- ^ Location+                   -> IO [(B.ByteString, (Int, Int))]+getChromTreeAsList e h offset = do+    magic <- hSeek h AbsoluteSeek offset >> hReadInt32 e h+    if magic /= 0x78CA8C91+       then error "wrong chromosome tree header"        else do-           bs <- hReadInt32 e h-           ic <- hReadInt64 e h-           sc <- hReadInt32 e h-           sb <- hReadInt32 e h-           ec <- hReadInt32 e h-           eb <- hReadInt32 e h-           eo <- hReadInt64 e h-           ips <- hReadInt32 e h-           r <- hReadInt32 e h-           return $ Right $ RTreeHeader bs ic sc sb ec eb eo ips r+           _ <- hReadInt32 e h -- blockSize, not used+           keySize <- hReadInt32 e h+           _ <- hReadInt32 e h -- valSize, not used+           _ <- hReadInt64 e h -- itemCount, not used+           _ <- hReadInt64 e h -- reserved, not used+           traverseTree keySize   where-    rtmagic = 0x2468ACE0+    traverseTree ks = go+      where+        go = readNode >>= \case+            (True, n) -> replicateM n $ readLeafItem ks+            (False, n) -> fmap concat $+                replicateM n (readNonLeafItem ks) >>= mapM ( \(_, off) -> do+                    hSeek h AbsoluteSeek $ fromIntegral off+                    go )+    readNode = do+        isLeaf <- hReadBool h+        _ <- hReadBool h+        count <- hReadInt16 e h+        return (isLeaf, count)+    readLeafItem n = do+        key <- B.filter (/=0) <$> B.hGet h n+        chromId <- hReadInt32 e h+        chromSize <- hReadInt32 e h+        return (key, (chromId, chromSize))+    readNonLeafItem n = do+        key <- B.filter (/=0) <$> B.hGet h n+        childOffset <- hReadInt64 e h+        return (key, childOffset)  overlap :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Bool-overlap qChr qStart qEnd rStartChr rStart rEndChr rEnd = +overlap qChr qStart qEnd rStartChr rStart rEndChr rEnd =     (qChr, qStart) < (rEndChr, rEnd) && (qChr, qEnd) > (rStartChr, rStart) {-# INLINE overlap #-} -getChromId :: BbiFile -> B.ByteString -> Maybe Int+getChromId :: BbiFile -> B.ByteString -> Maybe ChromID getChromId fl chr = fmap fst . M.lookup chr . _chromTree $ fl {-# INLINE getChromId #-} -overlappingBlocks :: BbiFile -> (Int, Int, Int) -> IO [(Int, Int)]+overlappingBlocks :: BbiFile -> (ChromID, Int, Int) -> IO [Block] overlappingBlocks fl (cid, start, end) =     if overlap cid start end (_startChromIx $ _rtree fl)                              (_startBase $ _rtree fl)@@ -195,10 +236,10 @@     e = _endian . _header $ fl     go i = do         hSeek h AbsoluteSeek i-        (isLeaf, n) <- readNode +        (isLeaf, n) <- readNode         if isLeaf            then replicateM n readLeafItem-           else do +           else do                pos <- fromIntegral <$> hTell h                results <- mapM (readNonLeafItem . (\x -> pos + x*24)) [0..n-1]                return $ concat results@@ -219,7 +260,7 @@                     then Just (offset, size)                     else Nothing -    readNonLeafItem p = do +    readNonLeafItem p = do         hSeek h AbsoluteSeek $ fromIntegral p         stCIx <- hReadInt32 e h         st <- hReadInt32 e h@@ -231,10 +272,15 @@            else return [] {-# INLINE overlappingBlocks #-} -readBlocks :: BbiFile -> [(Int, Int)] -> Source IO B.ByteString+type Block = (Offset, BlockSize)+type Offset = Int+type BlockSize = Int++-- | Read Blocks from the file. +readBlocks :: BbiFile -> [Block] -> Source IO B.ByteString readBlocks fl blks = forM_ blks $ \(offset, size) -> do-    bs <- lift $ do hSeek handle AbsoluteSeek $ fromIntegral offset-                    BL.hGet handle size+    bs <- lift $ hSeek handle AbsoluteSeek (fromIntegral offset) >>+        BL.hGet handle size     yield . BL.toStrict . decompress $ bs   where     handle = _filehandle fl
src/Data/BBI/BigBed.hs view
@@ -1,24 +1,60 @@-module Data.BBI.BigBed where+module Data.BBI.BigBed+    ( BBedFile+    , openBBedFile+    , closeBBedFile+    , query+    ) where  import qualified Data.ByteString as B-import Data.Conduit-import qualified Data.Conduit.List as CL+import qualified Data.ByteString.Char8 as BC+import qualified Data.Map as M+import Conduit+ import Data.BBI import Data.BBI.Utils -toBedRecords :: Monad m => Endianness -> Conduit B.ByteString m (Int, Int, Int, B.ByteString)-toBedRecords e = do-    d <- await-    case d of-        Nothing -> return ()-        Just bs -> go bs+newtype BBedFile = BBedFile BbiFile++openBBedFile :: FilePath -> IO BBedFile+openBBedFile fl = do+    bbi <- openBbiFile fl+    case (_filetype . _header) bbi of+        BigBed -> return $ BBedFile bbi+        _ -> error "not a bigbed file"++closeBBedFile :: BBedFile -> IO ()+closeBBedFile (BBedFile fl) = closeBbiFile fl++-- | Find all regions that are overlapped with the query in the bigbed file.+query :: (B.ByteString, Int, Int)   -- ^ (Chr, start, end)+      -> BBedFile+      -> ConduitT () (B.ByteString, Int, Int, B.ByteString) IO ()+query (chr, s, e) (BBedFile fl) = case getChromId fl chr of+    Just i -> do+        blks <- lift $ overlappingBlocks fl (i, s, e)+        readBlocks fl blks .| concatMapC (map f . decodeBlock endi) .| filterC+          (\(_,s',e',_) -> isOverlapped (s',e') (s,e))+    Nothing -> return ()   where-    go s | B.null s = return ()-         | otherwise = do-             let chr = readInt32 e . B.take 4 $ s-                 start = readInt32 e . B.take 4 . B.drop 4 $ s-                 end = readInt32 e . B.take 4 . B.drop 8 $ s-                 (rest, remain) = B.break (==0) . B.drop 12 $ s-             yield (chr, start, end, rest)-             go $ B.tail remain-{-# INLINE toBedRecords #-}+    endi = _endian $ _header fl+    id2Chr = M.fromList $ map (\(a,(b,_)) -> (b,a) ) $ M.toList $ _chromTree fl+    f (a,b,c,d) = (M.findWithDefault undefined a id2Chr, b, c, d)++-- | Whether two intervals are overlapped.+isOverlapped :: (Int, Int) -> (Int, Int) -> Bool+isOverlapped (lo1, hi1) (lo2, hi2) = not (lo2 > hi1 || lo1 > hi2)+{-# INLINE isOverlapped #-}++-- | Decode blocks in BigBed.+decodeBlock :: Endianness+            -> B.ByteString +            -> [(Int, Int, Int, B.ByteString)]+decodeBlock e blk+    | B.null blk = []+    | otherwise = (chr, start, end, rest) : decodeBlock e (B.tail remain)+  where+    chr = readInt32 e $ B.take 4 blk+    start = readInt32 e $ B.take 4 $ B.drop 4 blk+    end = readInt32 e $ B.take 4 $ B.drop 8 blk+    (rest, remain) = BC.break (=='\0') $ B.drop 12 blk+{-# INLINE decodeBlock #-}
src/Data/BBI/BigWig.hs view
@@ -8,8 +8,7 @@ import Control.Monad (unless) import Control.Monad.Trans (lift) import qualified Data.ByteString as B-import Data.Conduit-import qualified Data.Conduit.List as CL+import Conduit import Data.Maybe import Data.BBI import Data.BBI.Utils@@ -48,7 +47,7 @@     blks <- lift $ overlappingBlocks fl (fromJust cid, start, end)     readBlocks fl blks $= toWigRecords endi $= filter'   where-    filter' = CL.mapMaybe $ \(c, s, e, v) ->+    filter' = concatMapC $ \(c, s, e, v) ->                  let s' = max start s                      e' = min end e                  in if c == fromJust cid && s' < e'@@ -60,7 +59,7 @@  -- | convert bytestring to wig style record, but the output is 0-indexed toWigRecords :: Monad m => Endianness -> Conduit B.ByteString m (Int, Int, Int, Float)-toWigRecords endi = CL.concatMap $ \bs ->+toWigRecords endi = concatMapC $ \bs ->     let header = readWigHeader endi bs     in case _type header of         VarStep -> readVarStep header $ B.drop 24 bs@@ -82,7 +81,7 @@       where         loop st i x | i >= n = []                     | otherwise = let v = readFloat32 endi . B.take 4 $ x-                                  in (cid, st, st+sp, v) : loop (st+step) (i+1) (B.drop 4 x) +                                  in (cid, st, st+sp, v) : loop (st+step) (i+1) (B.drop 4 x)         n = _itemCount h         sp = _itemSpan h         step = _itemStep h@@ -113,5 +112,5 @@     f x | x == 1 = BedGraph         | x == 2 = VarStep         | x == 3 = FixedStep-        | otherwise = error "Unknown Wig seciton type" +        | otherwise = error "Unknown Wig seciton type" {-# INLINE readWigHeader #-}
+ tests/Tests/BigBed.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}++module Tests.BigBed+    ( tests ) where++import Bio.Data.Bed+import Bio.Data.Bed.Types+import Conduit++import Test.Tasty+import Test.Tasty.HUnit++import Data.BBI.BigBed++tests :: TestTree+tests = testGroup "Test: BigBed"+    [ testCase "intersect" intersectTest+    ]++intersectTest :: Assertion+intersectTest = do+    bed <- readBed "tests/data/sample.bed"+    bbed <- openBBedFile "tests/data/sample.bigbed"+    compareIntersection (BED3 "chr1" 1 10000000) bed bbed++compareIntersection :: BED3 -> [BED3] -> BBedFile -> Assertion+compareIntersection x@(BED3 chr s e) bed bbed = do+    observed <- runConduit $ query (chr, s, e) bbed .| mapC f .| sinkList+    fromSorted (sortBed observed) @?= fromSorted (sortBed expected)+  where+    expected = runIdentity $ runConduit $+        yieldMany bed .| intersectBed [x] .| sinkList+    f (a,b,c,_) = BED3 a b c
+ tests/tests.hs view
@@ -0,0 +1,7 @@+import qualified Tests.BigBed as BB+import Test.Tasty++main :: IO ()+main = defaultMain $ testGroup "Main"+    [ BB.tests+    ]