packages feed

too-many-cells-0.2.2.1: src/TooManyCells/Diversity/Load.hs

{- TooManyCells.Diversity.Load
Gregory W. Schwartz

Collects the functions pertaining to the loading of data.
-}

{-# LANGUAGE PackageImports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE BangPatterns #-}

module TooManyCells.Diversity.Load
    ( loadPopulation
    ) where

-- Remote
import BirchBeer.Types
import Control.Monad.Except (runExceptT, ExceptT (..))
import Control.Monad.Managed (with, liftIO, Managed (..))
import Data.Matrix.MatrixMarket (readMatrix)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Data.Tuple (swap)
import Math.Clustering.Hierarchical.Spectral.Sparse (B (..))
import Safe (headMay)
import TextShow (showt)
import qualified Control.Lens as L
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy.Char8 as B
import qualified Data.ByteString.Streaming.Char8 as BS
import qualified Data.Foldable as F
import qualified Data.Map.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Data.Text.Read as T
import qualified Streaming as S
import qualified Streaming.Cassava as S
import qualified Streaming.Prelude as S
import qualified Streaming.With.Lifted as SW
import qualified System.Directory as FP
import qualified System.FilePath as FP

-- Local
import TooManyCells.Diversity.Types
import TooManyCells.File.Types
import TooManyCells.MakeTree.Types
import TooManyCells.Matrix.Utility
import TooManyCells.Matrix.Types

-- | Load a population representation from a cluster file
loadPopulationCsv :: PriorPath -> IO (Either String Population)
loadPopulationCsv file = do
    let getCols :: Map.Map T.Text T.Text -> Either String (Species, Seq.Seq Cell)
        getCols m = do
            x <- do
                    maybe (Left "\nNo cluster column.") (Right . Species)
                    . Map.lookup "cluster"
                    $ m
            y <- do
                    maybe
                        (Left "\nNo cell column.")
                        (Right . Seq.singleton . Cell)
                    . Map.lookup "cell"
                    $ m

            return (x, y)

    flip with return $ do
        contents   <- SW.withBinaryFileContents . unPriorPath $ file
        population <- fmap (\x -> do
                                x' <- either (Left . show) Right x
                                fmap (Population . Map.fromListWith (Seq.><))
                                    . sequence
                                    $ x'
                           )
                    . runExceptT
                    . S.toList_
                    . S.map getCols
                    . S.decodeByName
                    $ (contents :: BS.ByteString (ExceptT S.CsvParseException Managed) ())

        return population

-- | Convert previous cluster results to a population representation.
priorToPopulation :: [(CellInfo, [Cluster])] -> Either String Population
priorToPopulation =
    fmap (Population . Map.fromListWith (Seq.><))
        . mapM (\(!x, !y) -> do
                    let x' = Seq.singleton . _barcode $ x
                    y' <- maybe
                            (Left "\nNo cluster for cell.")
                            (Right . Species . showt . unCluster)
                        . headMay
                        $ y
                    return (y', x')
               )

-- | Convert a Population to a custom population with a label map.
popToLabelPop :: LabelMap -> Population -> Either String Population
popToLabelPop (LabelMap lm) = fmap (Population . Map.fromListWith (Seq.><))
                            . mapM getPopEntry
                            . F.toList
                            . mconcat
                            . Map.elems
                            . unPopulation
  where
    getPopEntry (Cell x) =
      ( maybe
            (Left $ "\nMissing label for " <> (show x))
            (Right . Species . unLabel)
            (Map.lookup (Id x) lm)
      )
        >>= Right . (, Seq.singleton . Cell $ x)

-- | Load the populations from a list of files. If a label map is provided, the
-- species are the labels, otherwise they are the clusters.
loadPopulation :: Maybe LabelMap
               -> PriorPath
               -> IO (Either String Population)
loadPopulation lm (PriorPath path) = do
    dirExist <- FP.doesDirectoryExist path
    fileExist <- FP.doesFileExist path
    res <- case (dirExist, fileExist) of
        (False, False) -> return $ Left "\nInput does not exist."
        (_, True)      -> runExceptT $ do
            pop <- ExceptT . loadPopulationCsv . PriorPath $ path
            return pop
        otherwise      -> runExceptT $ do
            let crInput = path FP.</> "cluster_list.json"

            pop <- ExceptT
                 . fmap ((=<<) priorToPopulation . A.eitherDecode)
                 . B.readFile
                 $ crInput

            return pop

    return . maybe res (\x -> res >>= popToLabelPop x) $ lm