{-# LANGUAGE RebindableSyntax #-} module Spreadsheet.Format ( Results, Measurements, AllPaths, TableFormats(..), Flags(..), defaultFlags, RecordingFlags(..), writeOverviewHead, writeOverviewFoot, appendOverview, appendTreatmentOverview, formatTables, -- * utility (<->), ) where import qualified LabelTrack import qualified LabelChain import qualified Durations as Durs import qualified ClassRecord import qualified Class import qualified Time import qualified SpectralDistribution as SD import qualified Signal import qualified Rate import Measurement (SpectralParameters(SpectralParameters, spectralFlatness), ClassFeatures, ) import qualified Spreadsheet.Formula as CalcForm import qualified Spreadsheet.Palisade as Palisade import qualified Spreadsheet.Row as CalcRow import qualified Text.CSV.Lazy.String as CSV import Spreadsheet.Row (FieldTracked, Precision(Prec0, Prec3, Prec6)) import qualified Data.Text as Text import Data.Text (Text, ) import qualified Control.Monad.Trans.Writer as MW import Control.DeepSeq (NFData, rnf, force, ) import Control.Monad (void, liftM2, liftM3, when, ) import Control.Applicative (Applicative, liftA2, liftA3, pure, (<*>), ) import Data.Biapplicative (bipure, (<<*>>), ) import qualified Data.Traversable as Trav import qualified Data.Foldable as Fold import qualified Data.NonEmpty as NonEmpty import qualified Data.List.Match as Match import qualified Data.List.HT as ListHT import qualified Data.List as List import qualified Data.Map as Map import qualified Data.Monoid.HT as Mn import qualified Data.Semigroup as Sg import qualified Data.Char as Char import Data.IORef (IORef, newIORef, readIORef, modifyIORef, ) import Data.Foldable (foldMap, fold, ) import Data.Monoid (Monoid, mempty, mappend, (<>), ) import Data.Tuple.HT (mapPair, mapSnd, fst3, snd3, thd3, ) import Data.Maybe.HT (toMaybe, ) import Data.Maybe (mapMaybe, ) import Data.Map (Map, ) import qualified System.FilePath.Find as Find import qualified System.Path.PartClass as PathClass import qualified System.Path.Directory as Dir import qualified System.Path.IO as PathIO import qualified System.Path as Path import System.FilePath.Find ((~~?), (==?), (&&?), (||?), ) import System.Path ((>), (<.>), ) import Text.Printf (printf, ) import qualified Algebra.RealRing as Real import qualified Algebra.Transcendental as Trans import qualified Algebra.Field as Field import qualified Algebra.Ring as Ring import Algebra.ToRational (realToField) import NumericPrelude.Numeric import NumericPrelude.Base import Prelude (Num) spectralParametersReal :: (Trans.C a) => Formula a -> SpectralParameters Float -> SpectralParameters (Formula a) spectralParametersReal rate (SpectralParameters flatness maxFreq (centroid,deviation) distr) = SpectralParameters (realToField flatness) (rate * realToField maxFreq) (realToField centroid, realToField deviation) (spectralDistributionReal rate distr) spectralDistributionReal :: (Trans.C a) => Formula a -> SD.T Float -> SD.T (Formula a) spectralDistributionReal rate (SD.Cons centroid spread) = let angularRate = rate / (Ring.fromInteger 2 * CalcForm.pi) in SD.Cons (realToField centroid * angularRate) (realToField (SD.signedSqrt spread) * angularRate) type Cell = CalcForm.CellTracked type Formula = CalcForm.FormulaTracked palBounds :: (CalcRow.Fraction a, Real.C a) => Palisade.T (Cell a) a palBounds = Palisade.colNoSum "Start/s" `Palisade.right` Palisade.colNoSum "Stop/s" `Palisade.right` Palisade.colSum Prec6 "Duration/s" rowBounds :: (CalcRow.Fraction a) => Formula a -> (Int,Int) -> CalcRow.M (Cell a) rowBounds rate bounds = do start <- CalcRow.putFractionFormula Prec6 $ fromIntegral (fst bounds) / rate stop <- CalcRow.putFractionFormula Prec6 $ fromIntegral (snd bounds) / rate CalcRow.putFraction Prec6 $ stop - start precSpectral :: SpectralParameters Precision precSpectral = (pure Prec0) {spectralFlatness = Prec3} palSpectral :: (CalcRow.Fraction a, Real.C a) => Palisade.T (SpectralParameters (Cell a)) (SpectralParameters a) palSpectral = Palisade.Cons (map (flip (,) True) $ Fold.toList $ SpectralParameters "WienerEntropy" "MaxFreq/Hz" ("BandCentroid/Hz", "BandDeviation/Hz") (SD.Cons "SpecCentroid/Hz" "SpecSpread/Hz")) (fold . liftA2 Palisade.summarySum5 precSpectral . Trav.sequenceA) (fmap CalcForm.trackedNumber) (fold . liftA2 Palisade.summary5String precSpectral . Trav.sequenceA) rowSpectral :: (CalcRow.Fraction a) => SpectralParameters (Formula a) -> CalcRow.M (SpectralParameters (Cell a)) rowSpectral = Trav.sequence . liftA2 CalcRow.putFraction precSpectral _palClickDur :: (CalcRow.Fraction a, Real.C a) => Palisade.T (Cell a) a _palClickDur = Palisade.colSum5 Prec6 "ClickDur/s" _rowClickDur :: (CalcRow.Fraction a) => Formula a -> [LabelChain.ClickAbs Int] -> CalcRow.M (Cell a) _rowClickDur rate clicks = CalcRow.putFraction Prec6 $ fromIntegral (sum (map (\(LabelChain.ClickAbs t0 t1 _) -> t1-t0) clicks)) / (rate * fromIntegral (length clicks)) type Advertisement a = (BoundRasping a, Maybe (BoundChirping a)) type BoundAdvertisement a = (a, Advertisement a) palBoundAdvertisement :: (CalcRow.Fraction a, Real.C a) => Palisade.T (BoundAdvertisement (Cell a)) (BoundAdvertisement a) palBoundAdvertisement = Palisade.pair palBounds $ Palisade.pair (Palisade.mapHeaders ("Slow " ++) $ Palisade.pair (Palisade.colSum5 Prec6 "Dur/s") palRasping) (Palisade.maybe $ Palisade.mapHeaders ("Fast " ++) $ Palisade.pair (Palisade.colSum5 Prec6 "Dur/s") palChirping) type Rasping a = ((a, a), (a, a, a), SpectralParameters a) palRasping :: (CalcRow.Fraction a, Real.C a) => Palisade.T (Rasping (Cell a)) (Rasping a) palRasping = Palisade.triple (Palisade.pair (Palisade.colSum Prec3 "NumClicks") (Palisade.colSum Prec3 "NumEmphasized")) (Palisade.triple (Palisade.colSum5 Prec3 "ClickRate/Hz") (Palisade.colSum5 Prec6 "ClickHalfLife/s") (Palisade.colSum5 Prec6 "ClickPause/s")) palSpectral rowRasping :: (CalcRow.Fraction a) => Formula a -> Formula a -> ((Int, Int, Int), SpectralParameters (Formula a)) -> CalcRow.M (Rasping (Cell a)) rowRasping rate dur ((numClicksInt, sumHalfLifes, numEmphasizedInt), spectralDistr) = do numClicksCell <- CalcRow.putInt numClicksInt let numClicks = CalcForm.trackedVar numClicksCell numEmphasized <- CalcRow.putInt numEmphasizedInt clickRate <- CalcRow.putFraction Prec3 $ numClicks / dur hlife <- CalcRow.putFraction Prec6 $ fromIntegral sumHalfLifes / (numClicks * rate) clickPause <- CalcRow.putFraction Prec6 $ dur / numClicks - CalcForm.trackedVar hlife spectral <- rowSpectral spectralDistr return ((numClicksCell, numEmphasized), (clickRate, hlife, clickPause), spectral) type BoundRasping a = (a, Rasping a) palBoundRasping :: (CalcRow.Fraction a, Real.C a) => Palisade.T (BoundRasping (Cell a)) (BoundRasping a) palBoundRasping = Palisade.pair palBounds palRasping type Chirping a = (a, SpectralParameters a) palChirping :: (CalcRow.Fraction a, Real.C a) => Palisade.T (Chirping (Cell a)) (Chirping a) palChirping = Palisade.pair (Palisade.colSum5 Prec6 "MainDur/s") palSpectral rowChirping :: (CalcRow.Fraction a) => Formula a -> (Int, SpectralParameters (Formula a)) -> CalcRow.M (Chirping (Cell a)) rowChirping rate (brk, spectralDistr) = do mainDur <- CalcRow.putFraction Prec6 $ fromIntegral brk / rate spectral <- rowSpectral spectralDistr return (mainDur, spectral) rowNoChirping :: CalcRow.M () rowNoChirping = case palChirping :: Palisade.T (Chirping (Cell Double)) (Chirping Double) of Palisade.Cons tabHeadChirping _ _ _ -> sequence_ $ Match.replicate tabHeadChirping CalcRow.putEmpty type BoundChirping a = (a, Chirping a) palBoundChirping :: (CalcRow.Fraction a, Real.C a) => Palisade.T (BoundChirping (Cell a)) (BoundChirping a) palBoundChirping = Palisade.pair palBounds palChirping type Ticking a = (a, a, SpectralParameters a) palTicking :: (CalcRow.Fraction a, Real.C a) => Palisade.T (Ticking (Cell a)) (Ticking a) palTicking = Palisade.triple (Palisade.colSum5 Prec3 "NumClicks") (Palisade.colSum Prec3 "ClickRate/Hz") palSpectral rowTicking :: (CalcRow.Fraction a) => Formula a -> (Int, SpectralParameters (Formula a)) -> CalcRow.M (Ticking (Cell a)) rowTicking dur (numClicksInt, spectralDistr) = do numClicks <- CalcRow.putInt numClicksInt clickRate <- CalcRow.putFraction Prec3 $ CalcForm.trackedVar numClicks / dur spectral <- rowSpectral spectralDistr return (numClicks, clickRate, spectral) type BoundTicking a = (a, Ticking a) palBoundTicking :: (CalcRow.Fraction a, Real.C a) => Palisade.T (BoundTicking (Cell a)) (BoundTicking a) palBoundTicking = Palisade.pair palBounds palTicking type Growling a = Rasping a palGrowling :: (CalcRow.Fraction a, Real.C a) => Palisade.T (Growling (Cell a)) (Growling a) palGrowling = palRasping rowGrowling :: (CalcRow.Fraction a) => Formula a -> Formula a -> ((Int, Int, Int), SpectralParameters (Formula a)) -> CalcRow.M (Growling (Cell a)) rowGrowling = rowRasping type BoundGrowling a = (a, Growling a) palBoundGrowling :: (CalcRow.Fraction a, Real.C a) => Palisade.T (BoundGrowling (Cell a)) (BoundGrowling a) palBoundGrowling = Palisade.pair palBounds palGrowling data EvalType = Number | Formula deriving (Eq, Ord, Show, Enum) typeName :: EvalType -> String typeName typ = case typ of Formula -> "formula" Number -> "number" typeSelect :: EvalType -> CalcForm.Tracked a a -> a typeSelect typ = case typ of Formula -> CalcForm.trackedFormula Number -> CalcForm.trackedNumber infixl 7 <-> (<->) :: Path.File ar -> String -> Path.File ar path <-> var = Path.mapFileName (++ "-" ++ var) path formatTable :: (PathClass.AbsRel ar) => Path.File ar -> EvalType -> String -> [[FieldTracked]] -> TableFormats (Path.File ar, Text) formatTable outputStem typ cls content = liftA2 (,) (fmap (\ext -> outputStem <-> map Char.toLower cls <-> typeName typ <.> ext) formatExtensions) (fmap (\fmt -> Text.pack $ formatOpen fmt cls ++ formatRows fmt typ content ++ formatClose fmt) $ formats) formatMultiTable :: (PathClass.AbsRel ar) => Path.File ar -> EvalType -> ClassRecord.T (String, [[FieldTracked]]) -> TableFormats (Path.File ar, Text) formatMultiTable outputStem typ tables = liftA2 (,) (fmap (\ext -> outputStem <-> typeName typ <.> ext) formatExtensions) (fmap (\fmt -> Text.pack $ formatFileOpen fmt ++ foldMap (uncurry (formatSheet fmt typ)) tables ++ formatFileClose fmt) formats) renderTable2 :: Palisade.T a b -> [CalcRow.M a] -> ([[FieldTracked]], [b]) renderTable2 (Palisade.Cons tabHead aggregate select _) table = let (meass, content) = unzip $ zipWith CalcRow.run [1..] table in ((map tableHead $ "Source" : map fst tabHead) : content ++ [] : List.transpose (map tableHead (Fold.toList Palisade.summary5Names ++ ["Sum"]) : flip map (aggregate meass) (\(sum5, sumRes) -> Fold.toList sum5 ++ [sumRes])) , map select meass) data TableFormats a = TableFormats {tableCSV, tableHTML, tableXML2003 :: a} type TablePaths ar = TableFormats (Maybe (Path.File ar)) data Resolutions a = Resolutions {resTreatment, resAnimal, resRecording :: a} type ResPaths ar = Resolutions (TablePaths ar) type ClassPaths ar = ClassRecord.T (ResPaths ar) type DurPaths ar = ((TablePaths ar, TablePaths ar), IORef Int) type AllPaths ar = (DurPaths ar, ClassPaths ar, (Path.Dir ar, String, String)) instance Functor TableFormats where fmap = Trav.fmapDefault instance Fold.Foldable TableFormats where foldMap = Trav.foldMapDefault instance Trav.Traversable TableFormats where traverse f (TableFormats csv html xml2003) = liftA3 TableFormats (f csv) (f html) (f xml2003) instance Applicative TableFormats where pure a = TableFormats a a a TableFormats fCSV fHTML fXML2003 <*> TableFormats xCSV xHTML xXML2003 = TableFormats (fCSV xCSV) (fHTML xHTML) (fXML2003 xXML2003) formatExtensions :: TableFormats String formatExtensions = TableFormats "csv" "html" "xml" instance Fold.Foldable Resolutions where foldMap f (Resolutions treatment animal recording) = f treatment <> f animal <> f recording tableHead :: String -> FieldTracked tableHead content = (tableField content) { CalcRow.fieldHead = True } tableSpanHead :: Int -> String -> FieldTracked tableSpanHead colSpan content = (tableHead content) { CalcRow.fieldSpan = colSpan } tableField :: String -> FieldTracked tableField content = CalcRow.TableField { CalcRow.fieldType = CalcRow.TypeString, CalcRow.fieldQuoted = False, CalcRow.fieldAnchor = "", CalcRow.fieldHead = False, CalcRow.fieldSpan = 1, CalcRow.fieldPrecision = Nothing, CalcRow.fieldContent = CalcForm.untracked content } tableFieldInt :: Int -> FieldTracked tableFieldInt number = (tableField $ show number) { CalcRow.fieldType = CalcRow.TypeNumber } tableFieldAnchor :: (PathClass.FileDir fd) => Path.AbsDir -> Path.Rel fd -> FieldTracked tableFieldAnchor path file = (tableField $ Path.toString file) { CalcRow.fieldAnchor = Path.toString $ path > file } initFile :: (PathClass.AbsRel ar) => Path.File ar -> String -> IO (Path.File ar) initFile path str = do PathIO.writeFile path str return path data Format = Format { formatOpen :: String -> String, formatClose :: String, formatRows :: EvalType -> [[FieldTracked]] -> String, formatFileOpen :: String, formatFileClose :: String, formatSheet :: EvalType -> String -> [[FieldTracked]] -> String } formats :: TableFormats Format formats = TableFormats { tableCSV = formatCSV, tableHTML = formatHTML, tableXML2003 = formatXML2003 } formatCSV, formatHTML, formatXML2003 :: Format formatCSV = Format { formatOpen = const "", formatClose = "", formatRows = formatCSVRows, formatFileOpen = "", formatFileClose = "", formatSheet = \typ name content -> (prettyCSVTable [[name]]) ++ formatCSVRows typ content ++ (prettyCSVTable [[]]) } formatHTML = let fileOpenNamed name = "" : printf "