packages feed

too-many-cells-3.0.1.0: src/TooManyCells/Program/Spatial.hs

{- TooManyCells.Program.Spatial
Gregory W. Schwartz

Spatial entry point for command line program.
-}

{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ViewPatterns #-}

module TooManyCells.Program.Spatial where

-- Remote
import BirchBeer.Load (loadLabelData)
import BirchBeer.Types (LabelFile (..), Delimiter (..), LabelMap (..), Sample (..), Feature (..), Label (..))
import Control.Concurrent.Async.Pool (withTaskGroup, mapTasks)
import Control.Monad (join, forM_, guard, unless, when, void)
import Data.Bool (bool)
import Data.Maybe (isJust, fromMaybe)
import GHC.Conc (getNumCapabilities)
import Language.R.Instance as R
import Language.R.QQ
import System.Directory (getTemporaryDirectory)
import System.IO (hPutStrLn, stderr)
import Text.Read (readMaybe)
import qualified Control.Foldl as Fold
import qualified Control.Lens as L
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified H.Prelude as H
import qualified System.FilePath as FP
import qualified Turtle as TU

-- Local
import TooManyCells.Matrix.Load (loadProjectionMap)
import TooManyCells.Program.LoadMatrix (loadAllSSM)
import TooManyCells.Program.Options
import TooManyCells.Spatial.AnnoSpat (scToAnnoSpatFile, runAnnoSpat)
import TooManyCells.Spatial.ProjectionPlot (plotSpatialProjection)
import TooManyCells.Spatial.SummaryPlot (plotSummary)
import TooManyCells.Spatial.Relationships (spatialRelationshipsR)
import TooManyCells.Spatial.Utility (subsampleProjectionMap, markToText)
import qualified TooManyCells.File.Types as Too
import qualified TooManyCells.Matrix.Types as Too
import qualified TooManyCells.Program.Utility as Too
import qualified TooManyCells.Spatial.Types as Too

-- | General call for spatialRelationshipsR.
spatialRelationshipsCall :: Too.OutputDirectory
                         -> Too.SkipFinishedFlag
                         -> Too.PCFCrossFlag
                         -> Too.ProjectionMap
                         -> Maybe LabelMap
                         -> Too.SingleCells
                         -> Maybe Sample
                         -> Maybe Too.TopDistances
                         -> [Too.Mark]
                         -> IO ()
spatialRelationshipsCall (Too.OutputDirectory outDir) skipFlag' pcfCrossFlag' pm lm sc sample td marks = do
  let markString = T.unpack . T.intercalate "_" . fmap markToText $ marks
      folderName (Just (Sample x)) = T.unpack x <> "_" <> markString
      folderName Nothing = markString
      outDir' = Too.OutputDirectory
              $ outDir
         FP.</> folderName sample
      finalFile = TU.fromText
                . T.pack
                $ Too.unOutputDirectory outDir' FP.</> "stats.csv"

  -- If stats.csv exists and is not empty, then run everything
  !skip <- (&&)
       <$> (pure $ Too.unSkipFinishedFlag skipFlag')
       <*> (TU.testfile finalFile)
  when (not skip)
    $ spatialRelationshipsR outDir' pcfCrossFlag' pm lm sc td marks

-- | Helper for spatial relationships function.
spatialRelationshipsHelper :: Too.OutputDirectory
                           -> Too.SkipFinishedFlag
                           -> Too.PCFCrossFlag
                           -> Too.ProjectionMap
                           -> Maybe LabelMap
                           -> Too.SingleCells
                           -> Maybe Sample
                           -> Maybe Too.TopDistances
                           -> [Too.Mark]
                           -> IO ()
spatialRelationshipsHelper _ _ _ _ _ _ _ _ [] = pure ()
spatialRelationshipsHelper outDir skipFlag' pcfCrossFlag' pm lm sc sample td (fmap markToText -> ["ALL"]) = do
  cores <- getNumCapabilities
  withTaskGroup cores $ \workers ->
    void
      . mapTasks workers  -- mapTasks_ not working
      . fmap (\(!x, !y) -> spatialRelationshipsCall outDir skipFlag' pcfCrossFlag' pm lm sc sample td [x, y])
      $ (,) <$> getMarks lm <*> getMarks lm
  where
    getMarks Nothing = fmap Too.MarkFeature . V.toList . L.view Too.colNames $ sc
    getMarks (Just lm) =
      fmap Too.MarkLabel . Set.toList . Set.fromList . Map.elems . unLabelMap $ lm
spatialRelationshipsHelper outDir skipFlag' pcfCrossFlag' pm lm sc sample td marks =
  spatialRelationshipsCall outDir skipFlag' pcfCrossFlag' pm lm sc sample td marks

-- | Helper for plotting spatial summary.
spatialSummary :: Too.OutputDirectory
               -> Delimiter
               -> Too.IncludeOthersFlag
               -> Maybe Too.StateLabelsFile
               -> IO ()
spatialSummary outputDir' delimiter' iof stateLabelsFile' = do
  stateLabelMap <-
    mapM
      ( fmap (Too.StateLabelMap . unLabelMap)
      . loadLabelData delimiter'
      . LabelFile
      . Too.unStateLabelsFile
      )
      stateLabelsFile'
  let featureList = fmap
                      Feature
                      [ "meanCorr"
                      , "maxCorr"
                      , "minCorr"
                      , "topMaxCorr"
                      , "topMinCorr"
                      , "topMeanCorr"
                      , "negSwap"
                      , "posSwap"
                      , "longestPosLength"
                      , "longestNegLength"
                      , "maxPosWithVal"
                      , "logMaxPosWithVal"
                      , "maxPos"
                      , "minPos"
                      ]
  mapM_ (plotSummary outputDir' iof stateLabelMap) featureList

-- | Spatial path.
spatialMain :: Subcommand -> IO ()
spatialMain sub@(SpatialCommand opts) = H.withEmbeddedR R.defaultConfig $ do
  let onlySummaryFlag' = onlySummaryFlag opts
      outputDir' = Too.OutputDirectory . (output :: Spatial -> String) $ opts
      stateLabelsFile' = fmap Too.StateLabelsFile . stateLabelsFile $ opts
      delimiter' = Delimiter
                 . (delimiter :: LoadMatrixOptions -> Char)
                 . (loadMatrixOptions :: Spatial -> LoadMatrixOptions)
                 $ opts
      skipFlag' = Too.SkipFinishedFlag $ skipFinishedFlag opts
      iof' = Too.IncludeOthersFlag $ includeOthersFlag opts
      td' = topDistances opts

  unless onlySummaryFlag' $ do

    let readOrErr err = fromMaybe (error err) . readMaybe
        delimiter'        = Delimiter
                          . (delimiter :: LoadMatrixOptions -> Char)
                          . (loadMatrixOptions :: Spatial -> LoadMatrixOptions)
                          $ opts
        projectionFile' =
            maybe (error "--projection-file required") Too.ProjectionFile
              . (projectionFile :: Spatial -> Maybe String)
              $ opts
        annoSpatMarkerFile' = fmap Too.AnnoSpatMarkerFile . annoSpatMarkerFile $ opts
        annoSpatCommand' = Too.AnnoSpatCommand . annoSpatCommand $ opts

    scRes <- fmap (fromMaybe (error "Requires --matrix-path"))
          . loadAllSSM sub
          $ (loadMatrixOptions :: Spatial -> LoadMatrixOptions) opts
    let processedSc = fst scRes
        customLabelMap = snd scRes

    projectionMap <- loadProjectionMap projectionFile'

    let labelsFileError = hPutStrLn stderr "Warning: Problem in AnnoSpat, skipping label generation ..."
    labelsFile' <-
      case annoSpatMarkerFile' of
        Nothing ->
          pure . fmap LabelFile . (labelsFile :: Spatial -> Maybe String) $ opts
        (Just mf) -> (=<<) (maybe (labelsFileError >> pure Nothing) pure) . TU.reduce Fold.head $ do
          let annoOutDir = Too.OutputDirectory
                        $ Too.unOutputDirectory outputDir' FP.</> "AnnoSpat_out"
          tmpDir <- TU.liftIO $ fmap (TU.fromText . T.pack) getTemporaryDirectory
          tmpOut <- fmap Too.TempPath $ TU.mktempfile tmpDir "AnnoSpat_input.csv"
          startEndCols <- TU.liftIO $ scToAnnoSpatFile projectionMap processedSc tmpOut

          case startEndCols of
            Nothing -> pure Nothing
            (Just (startCol, endCol)) ->
              TU.liftIO
                $ runAnnoSpat
                    annoSpatCommand'
                    tmpOut
                    mf
                    annoOutDir
                    startCol
                    endCol

    labelMap <- if isJust labelsFile'
                  then mapM (loadLabelData delimiter') $ labelsFile'
                  else return customLabelMap

    let marks' = fmap
                  ( bool (Too.MarkFeature . Feature) (Too.MarkLabel . Label)
                  . isJust
                  $ labelMap
                  )
              . mark
              $ opts

    pcfCrossFlag' <- case (isJust labelMap, pcfCrossFlag opts) of
                      (False, True) -> do
                        hPutStrLn stderr "Warning: Continuous feature marks detected but pcfcross requested, ignoring pcfcross request ..."
                        return $ Too.PCFCrossFlag False
                      _ ->
                        return . Too.PCFCrossFlag . pcfCrossFlag $ opts

    let samples = Set.toList
                . Set.fromList
                . fmap fst
                . Map.elems
                . Too.unProjectionMap
                $ projectionMap

    forM_ samples $ \s -> do
      let s' = fromMaybe (Sample "total") s
          sOutLabel = T.unpack $ unSample s'
          projectionOutput = Too.OutputDirectory
                            . (FP.</> sOutLabel FP.</> "projections")
                            . Too.unOutputDirectory
                            $ outputDir'
          relationshipsOutput = Too.OutputDirectory
                              . (FP.</> sOutLabel FP.</> "relationships")
                              . Too.unOutputDirectory
                              $ outputDir'
          subPm = subsampleProjectionMap s projectionMap
      plotSpatialProjection projectionOutput labelMap subPm processedSc s'

      case marks' of
        [] -> pure ()
        m -> spatialRelationshipsHelper relationshipsOutput skipFlag' pcfCrossFlag' subPm labelMap processedSc s td' m

  spatialSummary outputDir' delimiter' iof' stateLabelsFile'
spatialMain _ = error "Wrong path in spatial, contact Gregory Schwartz for this error."