pijul nest
guest [sign in]

Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

Dashboard.hs
module Frontend.Component.Dashboard where

import Prelude

import CSS (color)
import Color (toHexString)
import Data.Array (length)
import Data.DateTime (diff)
import Data.Foldable (for_)
import Data.Formatter.Number (formatOrShowNumber)
import Data.Int (floor, toNumber)
import Data.Maybe (Maybe(..))
import Data.Monoid (guard)
import Data.Newtype (unwrap)
import Data.Symbol (SProxy(..))
import Data.Time.Duration (class Duration, Days(..), Hours(..), Minutes(..), Seconds(..), convertDuration)
import Data.Tuple (Tuple(..))
import Effect.Aff.Class (class MonadAff, liftAff)
import Effect.Class.Console (log)
import Frontend.Api (fetchInstantInterval)
import Frontend.Component.Plot as Plot
import Frontend.Dom (card)
import Frontend.Draw (canvasShape, colorToCss, hashedColor, imageShape, metersToPixels)
import Frontend.Route (Route(..), routeCodec)
import Frontend.Types (Box(..), Config(..), Info(..), Vec2(..))
import Frontend.UPlot (defaultOpts, defaultSeries, defaultXAxis, noAxis)
import Frontend.Util (classes, datetimeToSeconds, enumerate, formatDateTime')
import Graphics.Canvas (CanvasImageSource)
import Graphics.Canvas as Canvas
import Halogen (liftEffect)
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.CSS as CSS
import Halogen.HTML.Properties as HP
import Routing.Duplex (print)


type State =
  { info :: Info
  , config :: Config
  , mapImage :: CanvasImageSource
  }

type Slots =
  ( plot :: H.Slot Plot.Query Void Unit
  )

data Action
  = Initialize
  | SetInput Input

type Input = { info :: Info, config :: Config, mapImage :: CanvasImageSource }

_plot = (SProxy :: SProxy "plot")

component :: forall query output m. MonadAff m => H.Component HH.HTML query Input output m
component = H.mkComponent
  { initialState: \{info, config, mapImage} ->
    { info
    , config
    , mapImage
    }
  , render
  , eval: H.mkEval H.defaultEval
    { handleAction = handleAction
    , initialize = Just Initialize
    , receive = Just <<< SetInput
    }
  }

tableRow :: forall a s m. String -> String -> H.ComponentHTML a s m
tableRow label value = HH.tr_
  [ HH.td_ [ HH.text label ]
  , HH.td_ [ HH.text value ]
  ]

formatDuration :: forall d. Duration d => d -> String
formatDuration duration = let
  Days d = convertDuration duration
  Hours h = convertDuration duration
  ds = show $ floor d
  hs = show $ floor h - (floor d * 24)
  in guard (floor d > 0) (ds <> " days ") <> hs <> " hours"

basicInformation :: forall a s m. MonadAff m => String -> Info -> Config -> H.ComponentHTML a s m
basicInformation cls (Info info) (Config config) =
  card [cls]
    [ HH.text "Basic Information" ]
    [ HH.table [ classes ["info-table"] ]
      [ tableRow "Recording Start" $
        formatDateTime' info.first_dt
      , tableRow "Recording End" $
        formatDateTime' info.last_dt
      , tableRow "Time Span" $
        formatDuration (info.last_dt `diff` info.first_dt :: Days)
      , tableRow "Number of Data Points" $
        formatOrShowNumber "0,0" (toNumber info.n_entries)
      , tableRow "Recording Length" $
        formatDuration (Seconds $ toNumber info.n_entries * config.predict_stride)
      ]
    ]

sceneConfiguration :: forall a s m. MonadAff m => String -> Config -> H.ComponentHTML a s m
sceneConfiguration cls (Config config) =
  card [cls]
    [ HH.text "Scene Configuration" ]
    [ HH.table [ classes ["info-table"] ]
      [ tableRow "Number of Cameras" $
          show (length config.cameras)
      , tableRow "Frames per Second" $
          formatOrShowNumber "0.0" (1.0 / config.predict_stride)
      , tableRow "Maximum Number of Objects" $
          show config.n_objects
      , tableRow "Minimum Tracklet Length" $
          show config.min_tracklet_len <> " frames"
      , tableRow "Warmup Period" $
          show config.toss_first <> " frames"
      , tableRow "Quantization" $
          show config.grid_px_size <> " m"
      , tableRow "Bounding Box" $
          show config.bbox
      , tableRow "GPU ID" $
          show config.gpu
      ]
    , HH.div [ classes [] ]
      [ HH.text "Zones:"
      , HH.br_
      , HH.ul [] $ enumerate config.zones <#> \(Tuple i zone) ->
        HH.li [CSS.style (color $ colorToCss (hashedColor i))] [HH.text zone.name]
      ]
    , HH.div [ classes ["mt-4"] ]
      [ HH.i_ [ HH.a [ HP.href ("/web/#" <> print routeCodec Cameras) ] [ HH.text "show cameras" ] ]
      , HH.br_
      , HH.i_
        [ HH.text "show raw config:"
          , HH.a [ classes ["mx-1"], HP.href "/info/tracker.yaml" ] [ HH.text "yaml" ]
          , HH.text "|"
          , HH.a [ classes ["mx-1"], HP.href "/config" ] [ HH.text "json" ]
          ]
      ]
    ]

sceneMap :: forall a s m. MonadAff m => CanvasImageSource -> H.ComponentHTML a s m
sceneMap mapImage = let
  {width, height} = imageShape mapImage
  in card []
    [ HH.text "Scene Map" ]
    [ HH.canvas [ HP.id_ "map-canvas", HP.width width, HP.height height ]
    ]

render :: forall a m. MonadAff m => State -> H.ComponentHTML a Slots m
render state = let
  plotOpts = defaultOpts
    { height = 100.0
    , axes = [defaultXAxis, noAxis]
    , series = [defaultSeries, defaultSeries { fill="#4e73df" }]
    }
  in
  HH.div [ classes ["row"] ]
    [ HH.div [ classes ["col-lg-8"]]
      [ card []
        [ HH.text "Recording History" ]
        [ HH.slot _plot unit Plot.component plotOpts (const Nothing)
        ]
      , HH.div [ classes ["row"] ]
        [ basicInformation "col-xl-6" state.info state.config
        , sceneConfiguration "col-xl-6" state.config
        ]
      ]
    , HH.div [ classes ["col-lg-4"] ]
      [ sceneMap state.mapImage
      ]
    ]

handleAction :: forall output m. MonadAff m => Action -> H.HalogenM State Action Slots output m Unit
handleAction = case _ of
  Initialize -> do
    {info: Info info, config: Config config, mapImage} <- H.get

    -- Draw recording timeline
    instantRange <- liftAff $ fetchInstantInterval info.first_dt info.last_dt (Minutes 10.0)
    for_ instantRange \instants -> do
      let x = instants <#> (unwrap >>> _.timestamp >>> datetimeToSeconds >>> Just)
          y = instants <#> (unwrap >>> _.objects >>> map (const 1.0))
      void $ H.query _plot unit $ H.tell (\a -> Plot.SetData [x, y] a)

    -- draw the map
    mCanvas <- liftEffect $ Canvas.getCanvasElementById "map-canvas"
    liftEffect case mCanvas of
      Nothing -> log "Can't locate canvas."
      Just canvas -> do
        ctx <- Canvas.getContext2D canvas
        Canvas.setGlobalAlpha ctx 1.0
        Canvas.drawImage ctx mapImage 0.0 0.0
        Canvas.setGlobalAlpha ctx 0.1
        cshape <- canvasShape canvas
        for_ (enumerate config.zones) \(Tuple i zone) -> do
          let Box {x0, x1, y0, y1} = zone.box
              V2 x y = metersToPixels cshape config.bbox (V2 x0 y0)
              V2 xx yy = metersToPixels cshape config.bbox (V2 x1 y1)
          Canvas.setFillStyle ctx (toHexString $ hashedColor i)
          Canvas.beginPath ctx
          Canvas.rect ctx {x, y, width: xx - x, height: yy - y}
          Canvas.fill ctx

  SetInput { info, config } -> do
    H.modify_ _ { info = info, config = config }
    handleAction Initialize