packages feed

too-many-cells-2.1.0.1: 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 #-}
{-# LANGUAGE TupleSections #-}

module TooManyCells.Matrix.AtacSeq
    ( rangeToBin
    , rangeToBinSc
    , binarizeSc
    ) where

-- Remote
import BirchBeer.Types
import Data.Bool (bool)
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.IntMap as IMap
import qualified Data.IntervalMap.Interval as I
import qualified Data.Map.Strict 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

-- | Convert an input range to a bin.
rangeToBin :: BinWidth -> ChrRegion -> ChrRegionBin
rangeToBin (BinWidth !b) region@(ChrRegion (!l, _)) =
  ChrRegionBin $ ChrRegion (l, I.ClosedInterval lb (lb + b - 1))
  where
    lb = ((div (midpoint region) b) * b) + 1

-- | Find midpoint of a range.
midpoint :: ChrRegion -> Int
midpoint (ChrRegion (_, !i)) =
  I.lowerBound i + div (I.upperBound i - I.lowerBound i) 2

-- | Get the map of ranges to their bins, keeping bin order.
binsToRangeBinMapWithOrder :: BinWidth
                           -> [ChrRegion]
                           -> (RangeBinMap, [ChrRegionBin])
binsToRangeBinMapWithOrder b rs =
  (rangeBinMap, Set.toAscList . Set.fromList $ bins)
  where
    rangeBinMap = RangeBinMap . IMap.fromList . zip [0..] . fmap fst $ rangeBins
    rangeBins = fmap (\ !v -> (Map.findWithDefault (error "Bin missing index in binsToRangeBinMapWithOrder") v binMap, v)) bins  -- Insert correct idx
    binMap = Map.fromList
           . flip zip (fmap BinIdx [0..])
           . Set.toAscList
           . Set.fromList
           $ bins
    bins = fmap (rangeToBin b) 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 = m S.^+^ S.fromListSM (S.dimSM init) [rangeToBinVal val] -- Possible space leak with ^+^
    init = S.zeroSM (S.nrows mat) . Set.size . Set.fromList . IMap.elems $ bm
    rangeToBinVal all@(!i, !j, !v) = (i, unBinIdx $ rangeToBinCol all, v)
    rangeToBinCol all@(_, !j, _) = IMap.findWithDefault (error $ "Missing range index in rangeToBinMat for: " <> show all)
                                    j
                                    bm

-- | Convert a range SingleCells matrix to a bin SingleCells matrix.
rangeToBinSc :: BinWidth -> SingleCells -> SingleCells
rangeToBinSc b sc =
  SingleCells { _matrix = rangeToBinMat rangeBinMap . L.view matrix $ sc
              , _rowNames = L.view rowNames sc
              , _colNames =
                  V.fromList . fmap (Feature . showt . unChrRegionBin) $ bins
              }
  where
    (rangeBinMap, bins) =
      binsToRangeBinMapWithOrder b
        . fmap ( either (error . (<>) "Cannot parse region format `chrN:START-END` in: ") id
               . parseChrRegion
               . unFeature
               )
        . V.toList
        . L.view colNames
        $ sc

-- | Binarize a matrix.
binarizeSc :: SingleCells -> SingleCells
binarizeSc = L.over matrix (MatObsRow . fmap (bool 0 1 . (> 0)) . unMatObsRow)