packages feed

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

{- TooManyCells.Spatial.Relationships
Gregory W. Schwartz

Collects the functions pertaining to quantifying relationships between points.
-}

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}

module TooManyCells.Spatial.Relationships
    ( spatialRelationshipsR
    ) where

-- Remote
import BirchBeer.Types (LabelMap (..))
import Data.Bool (bool)
import Data.Maybe (isJust)
import Language.R as R
import Language.R.QQ (r)

-- Local
import TooManyCells.File.Types (OutputDirectory (..))
import TooManyCells.Matrix.Types (SingleCells (..), ProjectionMap (..), X (..), Y (..))
import TooManyCells.Matrix.Utility (sparseMatToSparseRMat)
import TooManyCells.Spatial.Utility (scToRPat)
import qualified TooManyCells.Spatial.Types as Too

spatialRelationshipsR :: OutputDirectory
                      -> Too.PCFCrossFlag
                      -> ProjectionMap
                      -> Maybe LabelMap
                      -> SingleCells
                      -> Maybe Too.TopDistances
                      -> [Too.Mark]
                      -> IO ()
spatialRelationshipsR (OutputDirectory outDir) pcfCrossFlag' pm lm sc td marks = R.runRegion $ do
  let isLabelMarks = bool 0 1 . isJust $ lm :: Double
      pcfCrossFlagDouble = bool 0 1 $ Too.unPCFCrossFlag pcfCrossFlag' :: Double
      (tVal, tQFlag) = case td of
                        Nothing -> (10, 1 :: Double)
                        Just (Too.TopQuantile x) -> (x :: Double, 1)
                        Just (Too.TopDistance x) -> (x :: Double, 0)

  pat <- scToRPat lm pm sc marks

  [r|
    suppressMessages(library(spatstat))
    suppressMessages(library(reshape2))
    suppressMessages(library(plyr))

    # Find index of first switch from above to below or equal value.
    findNegSwap = function(x, xs) {
      if(all(xs$obs > x)) {
        return(xs$r[length(xs$r)])
      } else if(all(xs$obs <= x)) {
        return(xs$r[1])
      } else {
        for (i in c(1:length(xs$r))) {
          if (i == length(xs$r)) {
            return(xs$r[i])
          } else if ((xs$obs[i] > x) && (xs$obs[i + 1] <= x)) {
            return(xs$r[i + 1])
          }
        }
      }
    }

    # Find index of first switch from below or equal value to above.
    findPosSwap = function(x, xs) {
      if (all(xs$obs <= x)) {
        return(xs$r[length(xs$r)])
      } else if (all(xs$obs > x)) {
        return(xs$r[1])
      } else {
        for (i in c(1:length(xs$r))) {
          if (i == length(xs$r)) {
            return(xs$r[i])
          } else if (xs$obs[i] <= x && xs$obs[i + 1] > x) {
            return(xs$r[i + 1])
          }
        }
      }
    }

    # Find the longest length of a continuously positive (or negative / equal to) stretch.
    findLongestLength = function(posFlag, x, xs) {
      if(posFlag) {
        lengthInfo = rle(xs$obs > x)
      } else {
        lengthInfo = rle(xs$obs <= x)
      }
      stretches = lengthInfo$lengths[which(lengthInfo$values)]
      if(length(stretches) == 0) {
        return(0)
      } else {
        return(xs$r[max(stretches)] - xs$r[1])
      }
    }

    # Find the first r value of the maximum value.
    findMaxPos = function(xs) {
      return(xs$r[min(which(xs$obs == max(xs$obs)))])
    }

    # Find the first r value of the maximum value with maximum value.
    findMaxPosWithVal = function(as) {
      # Do not use 0 values
      xs = as[-1,]
      r = findMaxPos(xs)
      val = max(xs$obs)

      return(val / r)
    }

    # Find the first r value of the minimum value.
    findMinPos = function(xs) {
      return(xs$r[min(which(xs$obs == min(xs$obs)))])
    }

    topDistances = function(xs) {
      if(tQFlag_hs == 1) {
        return(xs$obs[1:round(length(xs$obs) / tVal_hs)])
      } else {
        return(xs$obs[1:max(which(xs$r <= tVal_hs))])
      }
    }

    # Find statistics for a single curve
    getCurveStats = function (outFolder, df) {
      statsDf = data.frame(meanCorr = mean(df$obs))
      statsDf$maxCorr = max(df$obs)
      statsDf$minCorr = min(df$obs)
      statsDf$meanCorr = mean(df$obs)
      statsDf$topMaxCorr = max(topDistances(df))
      statsDf$topMeanCorr = mean(topDistances(df))
      statsDf$topMinCorr = min(topDistances(df))
      statsDf$negSwap = findNegSwap(1, df)
      statsDf$posSwap = findPosSwap(1, df)
      statsDf$longestPosLength = findLongestLength(TRUE, 1, df)
      statsDf$longestNegLength = findLongestLength(FALSE, 1, df)
      statsDf$maxPosWithVal = findMaxPosWithVal(df)
      statsDf$logMaxPosWithVal = log(findMaxPosWithVal(df))
      statsDf$maxPos = findMaxPos(df)
      statsDf$minPos = findMinPos(df)
      statsDf$label = basename(outFolder)
      statsDf$topVal = tVal_hs
      statsDf$topQuantile = tQFlag_hs

      return(statsDf)
    }

    crossCorrCurveStats = function (outFolder) {

      # Cross correlation function (several columns of marks).
      if(pcfCrossFlagDouble_hs == 1) {
        crossFn = envelope(pat_hs, pcfcross, funargs=marks)
        crossFn$obs = crossFn$iso
      } else {
        crossFn = markcrosscorr(pat_hs)
        for(i in c(1:length(crossFn$fns))) {
          crossFn$fns[[i]]$obs = crossFn$fns[[i]]$iso
        }
      }
      plot(crossFn)

      # Object
      outObject = crossFn
      outObject$label = basename(outFolder)
      saveRDS(outObject, file = file.path(outFolder,"crosscorr.rds"))

      # Statistics
      if(pcfCrossFlagDouble_hs == 1) {
        statsDf = getCurveStats(outFolder, crossFn)
      } else {
        varDf = melt(crossFn$which)
        varDf = varDf[order(varDf$value),]
        statsDf = cbind(varDf, do.call(rbind, lapply(crossFn$fns, function (x) getCurveStats(outFolder, x))))
      }

      # To get the sample size, different for quantitative vs. nominal marks
      markVars = marks(pat_hs)
      if (is.data.frame(markVars)) {
        statsDf$n = nrow(markVars)
      } else {
        statsDf$n = length(markVars[markVars != "Other"])
      }

      # Output statistics
      write.csv(statsDf, file.path(outFolder, "stats.csv"), quote = FALSE, row.names = FALSE)

      # Curve
      if(pcfCrossFlagDouble_hs == 1) {
        curveDf = data.frame(x = crossFn$r, y = crossFn$obs, label = basename(outFolder))
      } else {
        curveDf = adply(melt(crossFn$which), 1, function(xs) {
          data.frame(x = crossFn$fns[[xs$value]]$r, y = crossFn$fns[[xs$value]]$obs, label = basename(outFolder))
        })
      }
      write.csv(curveDf, file.path(outFolder, "curve.csv"), quote = FALSE, row.names = FALSE)

      return ()
    }

    mainRelationships = function(isLabelMarks, outFolder) {
      message(paste("Processing", outFolder))
      dir.create(outFolder, showWarnings = FALSE, recursive = TRUE)

      message("Plotting point process")
      pdf(file = file.path(outFolder, "basic_plot.pdf"))
      tryCatch(plot(pat_hs), error = function (e) message(paste("Could not plot basic_plot.pdf:", e)))
      dev.off()

      message("Plotting mark correlation function")
      # Mark correlation function.
      pdf(file = file.path(outFolder, "mark_correlation_function.pdf"))
      tryCatch(plot(markcorr(pat_hs)), error = function (e) message(paste("Could not plot mark_correlation_function.pdf:", e)))
      dev.off()

      # Mark variogram (lower value, more similar mark values at a distance).
      if (!isLabelMarks) {
        message("Plotting variogram")
        pdf(file = file.path(outFolder, "mark_variogram.pdf"))
        tryCatch(plot(markvario(pat_hs)), error = function (e) message(paste("Could not plot mark_variogram:", e)))
        dev.off()
      }

      # Envelope.
      message("Plotting envelope")
      pdf(file = file.path(outFolder, "envelope.pdf"))
      tryCatch(plot(envelope(pat_hs)), error = function (e) message(paste("Could not plot envelope", e)))
      dev.off()

      # Mark cross correlation
      message("Plotting mark cross correlation")
      pdf(file = file.path(outFolder, "cross_correlation_function.pdf"))
      tryCatch(crossCorrCurveStats(outFolder), error = function (e) message(paste("Could not compute mark cross correlation statistics:", e)))
      dev.off()

      return()

    }

    mainRelationships(isLabelMarks_hs, outDir_hs)

  |]

  pure ()