packages feed

too-many-cells-0.1.12.3: src/TooManyCells/Matrix/AtacSeq.hs

{- TooManyCells.MakeTree.AtacSeq
Gregory W. Schwartz

Collects functions pertaining to converting a feature count matrix to an AtacSeq
matrix, where each feature is a bin range.
-}

{-# LANGUAGE BangPatterns #-}

module TooManyCells.Matrix.AtacSeq
    ( rangeToBinSc
    ) where

-- Remote
import BirchBeer.Types
import Data.Maybe (fromMaybe)
import Data.List (sort, foldl')
import TextShow (showt)
import qualified Control.Lens as L
import qualified Data.Attoparsec.Text as A
import qualified Data.Foldable as F
import qualified Data.Map as Map
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import qualified Data.Sparse.Common as S
import qualified Data.Text as T
import qualified Data.Vector as V

-- Local
import TooManyCells.Matrix.Types

-- | Read a range from a text of the form LABEL:LOWERBOUND-UPPERBOUND.
readRange :: RangeIdx -> T.Text -> Range
readRange idx x = either (\ a -> error $ "Error reading " <> a <> " : " <> T.unpack x <> " (did you follow the format LABEL:LOWERBOUND-UPPERBOUND ?)") id
                . A.eitherResult
                . A.parse (rangeParser idx)
                . T.filter (/= ',')
                $ x

-- | How to parse a range.
rangeParser :: RangeIdx -> A.Parser Range
rangeParser idx = do
  l <- A.many1 (A.notChar ':')
  A.char ':'
  lb <- A.decimal
  A.char '-'
  ub <- A.decimal
  return $ Range idx (T.pack l) lb ub

-- | Convert an input range to a bin.
rangeToBin :: BinSize -> Range -> Bin
rangeToBin (BinSize b) range@(Range _ l _ _) = Bin Nothing l lb (lb + b)
  where
    lb = (div (midpoint range) b) * b

-- | Find midpoint of a range.
midpoint :: Range -> Int
midpoint (Range _ _ lb ub) = lb + div (ub - lb) 2

-- | Get the map of ranges to their bins, keeping bin order.
binsToRangeBinMapWithOrder :: BinSize
                           -> V.Vector Range
                           -> (RangeBinMap, V.Vector Bin)
binsToRangeBinMapWithOrder b rs = (rangeBinMap, V.fromList bins)
  where
    rangeBinMap = RangeBinMap
                . Map.fromList
                . zip (fmap rangeIdx . V.toList $ rs)
                . fmap (fromMaybe (error "Bin missing index in binsToRangeBinMapWithOrder") . binIdx)
                $ bins
    bins = zipWith (\i v -> v { binIdx = Just i }) (fmap BinIdx [0..]) -- Insert correct idx
         . sort
         . fmap (rangeToBin b)
         . V.toList
         $ rs

-- | Convert a range matrix to a bin matrix.
rangeToBinMat :: RangeBinMap -> MatObsRow -> MatObsRow
rangeToBinMat (RangeBinMap bm) (MatObsRow mat) =
  MatObsRow . foldl' addToMat init . S.toListSM $ mat
  where
    addToMat m val = S.fromListSM (S.dimSM init) [val] S.^+^ m
    init = S.zeroSM (S.nrows mat) . Set.size . Set.fromList . Map.elems $ bm

-- | Convert a range SingleCells matrix to a bin SingleCells matrix.
rangeToBinSc :: BinSize -> SingleCells -> SingleCells
rangeToBinSc b sc =
  SingleCells { _matrix = rangeToBinMat rangeBinMap . L.view matrix $ sc
              , _rowNames = L.view rowNames sc
              , _colNames = fmap (Feature . showt) bins
              }
  where
    (rangeBinMap, bins) =
      binsToRangeBinMapWithOrder b
        . V.fromList
        . fmap (\(!i, !v) -> readRange (RangeIdx i) . unFeature $ v)
        . zip [0..]
        . V.toList
        . L.view colNames
        $ sc