birch-beer-0.2.2.0: app/Main.hs
{- birch-beer
Gregory W. Schwartz
Displays a hierarchical tree of clusters with colors, scaling, and more.
-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
module Main where
-- Remote
import Control.Monad (when)
import Data.Char (ord)
import Data.Colour.SRGB (sRGB24read)
import Data.List (isSuffixOf)
import Data.Maybe (fromMaybe)
import Math.Clustering.Hierarchical.Spectral.Load (readSparseAdjMatrix)
import Options.Generic
import System.IO (openFile, hClose, IOMode (..))
import Text.Read (readMaybe, readEither)
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.Csv as CSV
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
import qualified Diagrams.Backend.Cairo as D
import qualified Diagrams.Prelude as D
-- Local
import BirchBeer.ColorMap
import BirchBeer.Interactive
import BirchBeer.LeafGraph
import BirchBeer.Load
import BirchBeer.MainDiagram
import BirchBeer.Options
import BirchBeer.Types
import BirchBeer.Utility
main :: IO ()
main = do
opts <- getRecord "birch-beer, Gregory W. Schwartz.\
\ Displays a hierarchical tree of clusters with colors,\
\ scaling, and more."
let input' = unHelpful . input $ opts
inputMatrix' = unHelpful . inputMatrix $ opts
delimiter' =
Delimiter . fromMaybe ',' . unHelpful . delimiter $ opts
labelsFile' = fmap LabelFile . unHelpful . labelsFile $ opts
minSize' = fmap MinClusterSize . unHelpful . minSize $ opts
maxStep' = fmap MaxStep . unHelpful . maxStep $ opts
maxProportion' = fmap MaxProportion . unHelpful . maxProportion $ opts
minDistance' = fmap MinDistance . unHelpful . minDistance $ opts
minDistanceSearch' = fmap MinDistanceSearch . unHelpful . minDistanceSearch $ opts
smartCutoff' = fmap SmartCutoff . unHelpful . smartCutoff $ opts
customCut' = CustomCut . Set.fromList . unHelpful . customCut $ opts
rootCut' = fmap RootCut . unHelpful . rootCut $ opts
order' = fmap Order . unHelpful . order $ opts
drawLeaf' =
maybe
(maybe DrawText (const (DrawItem DrawLabel)) labelsFile')
(fromMaybe (error "Cannot read draw-leaf.") . readMaybe)
. unHelpful
. drawLeaf
$ opts
drawCollection' = maybe
PieChart
(fromMaybe (error "Cannot read draw-collection.") . readMaybe)
. unHelpful
. drawCollection
$ opts
drawMark' = maybe
MarkNone
(fromMaybe (error "Cannot read draw-mark.") . readMaybe)
. unHelpful
. drawMark
$ opts
drawNodeNumber' = DrawNodeNumber . unHelpful . drawNodeNumber $ opts
drawMaxNodeSize' =
DrawMaxNodeSize . fromMaybe 72 . unHelpful . drawMaxNodeSize $ opts
drawMaxLeafNodeSize' = DrawMaxLeafNodeSize
. fromMaybe (unDrawMaxNodeSize drawMaxNodeSize')
. unHelpful
. drawMaxLeafNodeSize
$ opts
drawNoScaleNodes' =
DrawNoScaleNodesFlag . unHelpful . drawNoScaleNodes $ opts
drawLegendSep' = DrawLegendSep
. fromMaybe 1
. unHelpful
. drawLegendSep
$ opts
drawLegendAllLabels' =
DrawLegendAllLabels . unHelpful . drawLegendAllLabels $ opts
drawPalette' = maybe
Set1
(fromMaybe (error "Cannot read palette") . readMaybe)
. unHelpful
. drawPalette
$ opts
drawColors' = fmap ( CustomColors
. fmap sRGB24read
. (\x -> read x :: [String])
)
. unHelpful
. drawColors
$ opts
drawDiscretize' = (=<<) (\x -> either error Just
. either
(\ err -> either
(\y -> Left $ finalError err y)
(Right . SegmentColorMap)
(readEither x :: Either String Int)
)
(Right . CustomColorMap . fmap sRGB24read)
$ (readEither x :: Either String [String])
)
. unHelpful
. drawDiscretize
$ opts
where
finalError err x = "Error in draw-discretize: " <> err <> " " <> x
drawScaleSaturation' =
fmap DrawScaleSaturation . unHelpful . drawScaleSaturation $ opts
drawFont' = fmap DrawFont . unHelpful . drawFont $ opts
output' =
fromMaybe "dendrogram.svg" . unHelpful . output $ opts
tree <- loadTreeOrDendFromFile input'
-- Get the label map from either a file or from expression thresholds.
labelMap <- case drawLeaf' of
(DrawItem (DrawThresholdContinuous gs)) ->
error "Threshold not supported here."
-- fmap
-- ( Just
-- . getLabelMapThresholdContinuous
-- (fmap (L.over L._1 Feature) gs)
-- . fromMaybe (error "Requires matrix.")
-- )
-- mat
_ -> sequence . fmap (loadLabelData delimiter') $ labelsFile'
let readMat file =
if isSuffixOf ".mtx" file
then do
mat <- loadMatrix file
let items = V.fromList . fmap showt $ [1..S.nrows mat]
return . SimilarityMatrix $ NamedMatrix mat items items
else do
let decodeOpt = CSV.defaultDecodeOptions
{ CSV.decDelimiter =
fromIntegral (ord . unDelimiter $ delimiter')
}
h <- openFile file ReadMode
(items, mat) <- readSparseAdjMatrix decodeOpt h
hClose h
return . SimilarityMatrix $ NamedMatrix mat items items
simMat <- sequence . fmap readMat $ inputMatrix'
let config :: Config T.Text NamedMatrix
config = Config { _birchLabelMap = labelMap
, _birchMinSize = minSize'
, _birchMaxStep = maxStep'
, _birchMaxProportion = maxProportion'
, _birchMinDistance = minDistance'
, _birchMinDistanceSearch = minDistanceSearch'
, _birchSmartCutoff = smartCutoff'
, _birchCustomCut = customCut'
, _birchRootCut = rootCut'
, _birchOrder = order'
, _birchDrawLeaf = drawLeaf'
, _birchDrawCollection = drawCollection'
, _birchDrawMark = drawMark'
, _birchDrawNodeNumber = drawNodeNumber'
, _birchDrawMaxNodeSize = drawMaxNodeSize'
, _birchDrawMaxLeafNodeSize = drawMaxLeafNodeSize'
, _birchDrawNoScaleNodes = drawNoScaleNodes'
, _birchDrawLegendSep = drawLegendSep'
, _birchDrawLegendAllLabels = drawLegendAllLabels'
, _birchDrawPalette = drawPalette'
, _birchDrawColors = drawColors'
, _birchDrawDiscretize = drawDiscretize'
, _birchDrawScaleSaturation = drawScaleSaturation'
, _birchDrawFont = drawFont'
, _birchTree = tree
, _birchMat = Nothing
, _birchSimMat = simMat
}
(plot, _, _, _, tree', _) <- mainDiagram config
-- Plot tree.
D.renderCairo
output'
(D.mkHeight 1000)
plot
-- Write new tree if necessary.
mapM_ (\x -> B.writeFile x . A.encode $ tree') . unHelpful . jsonOutput $ opts
when (unHelpful . interactive $ opts) $ interactiveDiagram tree labelMap (Nothing :: Maybe NamedMatrix) simMat
return ()