packages feed

hyraxAbif-0.2.3.2: test/AbifTests.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

module AbifTests (tests) where


import           Protolude
import qualified Data.List as Lst
import qualified Data.Text as Txt
import qualified Data.Text.Encoding as TxtE
import qualified Data.Binary as B
import qualified Data.Binary.Get as B
import qualified Data.ByteString.Lazy as BSL
import           Hedgehog

import qualified Hyrax.Abif as H
import qualified Hyrax.Abif.Read as H
import qualified Hyrax.Abif.Write as H
import qualified Hyrax.Abif.Generate as H
import           Generators

-- | Test that an ab1 (write, read, write, read) results in the original data
prop_roundtrip :: Property
prop_roundtrip = property $ do 
  fdata <- forAll $ genFastaData 2
  let fasta = TxtE.encodeUtf8 $ toFastaTxt False fdata

  wfasta <- evalEither $ H.readWeightedFasta fasta
  let ab1Written1 = H.generateAb1 ("test", wfasta)
  ab1Read1 <- evalEither $ H.getAbif ab1Written1

  let ab1Written2 = H.createAbifBytes ab1Read1
  ab1Read2 <- evalEither $ H.getAbif ab1Written2

  ab1Read1 === ab1Read2
  
  
-- | Generate an ab1 from a fasta and then confirm that the generated peaks of the
--    chromatogram match the original fasta.
--    Note that we are only testing the simple/single fasta case (i.e. no mixes)
prop_readPeaks :: Property
prop_readPeaks = property $ do
  nucs <- nucsNoIupacGen
  let fasta = TxtE.encodeUtf8 $ "> 1\n" <> nucs

  wfasta <- evalEither $ H.readWeightedFasta fasta
  let ab1Written = H.generateAb1 ("test", wfasta)
  ab1 <- evalEither $ H.getAbif ab1Written

  -- Get the peak locations
  peaks <- evalEither $ readShorts <$> getDirEntry ab1 "PLOC"  1

  -- Get the val at the peak per channel
  chanG <- evalEither $ readDataPeaks ab1  9 peaks
  chanA <- evalEither $ readDataPeaks ab1 10 peaks
  chanT <- evalEither $ readDataPeaks ab1 11 peaks
  chanC <- evalEither $ readDataPeaks ab1 12 peaks

  -- Check that all the data is available
  length chanA === length peaks
  length chanC === length peaks
  length chanG === length peaks
  length chanT === length peaks

  -- Call the peaks
  let called = Txt.pack $ Lst.zipWith4 callPeaks chanA chanC chanG chanT

  -- Compare original fasta vs called
  called === nucs

  -- Compare to the sequence in the AB1
  pbas <- evalEither $ getDirEntry ab1 "PBAS" 1
  (TxtE.decodeUtf8 . BSL.toStrict $ pbas) === nucs

  where
    -- Highest peak wins, no iupac
    callPeaks :: Int -> Int -> Int -> Int -> Char
    callPeaks a c g t =
      case reverse $ Lst.sortOn snd [('A', a), ('C', c), ('G', g), ('T', t)] of
        ((n,_) : _) -> n
        _ -> '?'
    
    readDataPeaks :: H.Abif -> Int -> [Int] -> Either Text [Int]
    readDataPeaks ab1 dirNum peaks =
      case readShorts <$> getDirEntry ab1 "DATA" dirNum of
        Left e -> Left e
        Right vs ->
          let valsAtPeaks = atMay vs <$> peaks in
          maybeToRight "peaks" $ sequenceA valsAtPeaks
  

getDirEntry :: H.Abif -> Text -> Int -> Either Text BSL.ByteString
getDirEntry ab1 dirName dirNum =
  let r = filter (\d -> H.dTagNum d == dirNum && H.dTagName d == dirName) $ H.aDirs ab1 in
  case r of
    (a:_) -> Right $ H.dData a
    _ -> Left $ "No entry found for '" <> dirName <> "' " <> show dirNum


readShorts :: BSL.ByteString -> [Int]
readShorts =
  let r = B.runGet $ readArray B.getWord16be in
  fromIntegral <<$>> r


readArray :: B.Get n -> B.Get [n]
readArray getFn = do
  e <- B.isEmpty
  if e then return []
  else do
    c <- getFn
    cs <- readArray getFn
    pure (c:cs)


tests :: IO Bool
tests =
  checkParallel $$discover