packages feed

risk-weaver-0.1.0.0: src/RiskWeaver/Cmd/BDD.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

module RiskWeaver.Cmd.BDD where

import Control.Monad
import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
import RiskWeaver.DSL.BDD qualified as BDD
import Data.ByteString qualified as BS
import Data.FileEmbed (embedFile)
import Data.List (sortBy)
import Data.Map qualified as Map
import Data.Text qualified as T
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import RiskWeaver.Format.Coco
import RiskWeaver.Metric

import Options.Applicative
import System.Random
import Text.Printf
import RiskWeaver.Cmd.Core (RiskCommands(..))

cocoCategoryToClass :: CocoMap -> CategoryId -> BDD.Class
cocoCategoryToClass coco categoryId =
  let cocoCategory = (cocoMapCocoCategory coco) Map.! categoryId
   in case T.unpack (cocoCategoryName cocoCategory) of
        "pedestrian" -> BDD.Pedestrian
        "rider" -> BDD.Rider
        "car" -> BDD.Car
        "truck" -> BDD.Truck
        "bus" -> BDD.Bus
        "train" -> BDD.Train
        "motorcycle" -> BDD.Motorcycle
        "bicycle" -> BDD.Bicycle
        _ -> BDD.Background

cocoResultToVector :: CocoMap -> ImageId -> (Vector BDD.BoundingBoxGT, Vector BDD.BoundingBoxDT)
cocoResultToVector coco imageId = (groundTruth, detection)
  where
    groundTruth =
      Vector.fromList $
        maybe
          []
          ( map
              ( \CocoAnnotation {..} ->
                  let CoCoBoundingBox (cocox, cocoy, cocow, cocoh) = cocoAnnotationBbox
                   in BDD.BoundingBoxGT
                        { x = cocox,
                          y = cocoy,
                          w = cocow,
                          h = cocoh,
                          cls = cocoCategoryToClass coco cocoAnnotationCategory,
                          idx = cocoAnnotationId
                        }
              )
          )
          (Map.lookup imageId (cocoMapCocoAnnotation coco))
    detection =
      Vector.fromList $
        maybe
          []
          ( map
              ( \CocoResult {..} ->
                  let CoCoBoundingBox (cocox, cocoy, cocow, cocoh) = cocoResultBbox
                   in BDD.BoundingBoxDT
                        { x = cocox,
                          y = cocoy,
                          w = cocow,
                          h = cocoh,
                          cls = cocoCategoryToClass coco cocoResultCategory,
                          score = unScore cocoResultScore,
                          idx = unImageId cocoResultImageId
                        }
              )
          )
          (Map.lookup imageId (cocoMapCocoResult coco))

runRisk ::
  CocoMap ->
  IO [(ImageId, Double)]
runRisk cocoMap = do
  forM (cocoMapImageIds cocoMap) $ \imageId -> do
    let (groundTruth, detection) = cocoResultToVector cocoMap imageId
    let env =
          BDD.MyEnv
            { envGroundTruth = groundTruth,
              envDetection = detection,
              envConfidenceScoreThresh = 0.4,
              envIoUThresh = 0.5
            }
    risk <- flip runReaderT env (BDD.myRisk @BDD.BoundingBoxGT)
    return (imageId, risk)

showRisk :: Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()
showRisk coco cocoResults iouThreshold scoreThresh mImageId = do
  let cocoMap =
        let cocoMap' = toCocoMap coco cocoResults
         in case mImageId of
              Nothing -> cocoMap'
              Just imageId -> cocoMap' {cocoMapImageIds = [imageId]}
      iouThreshold' = case iouThreshold of
        Nothing -> IOU 0.5
        Just iouThreshold -> IOU iouThreshold
      scoreThresh' = case scoreThresh of
        Nothing -> Score 0.4
        Just scoreThresh -> Score scoreThresh
  risks <- runRisk cocoMap
  putStrLn $ printf "%-12s %-12s %s" "#ImageId" "Filename" "Risk"
  let sortedRisks = sortBy (\(_, risk1) (_, risk2) -> compare risk2 risk1) risks
  forM_ sortedRisks $ \(imageId, risk) -> do
    let cocoImage = (cocoMapCocoImage cocoMap) Map.! imageId
    putStrLn $ printf "%-12d %-12s %.3f" (unImageId imageId) (T.unpack (cocoImageFileName cocoImage)) risk

generateRiskWeightedDataset :: Coco -> [CocoResult] -> FilePath -> Maybe Double -> Maybe Double -> IO ()
generateRiskWeightedDataset coco cocoResults cocoOutputFile iouThreshold scoreThresh = do
  let cocoMap = toCocoMap coco cocoResults
      iouThreshold' = case iouThreshold of
        Nothing -> IOU 0.5
        Just iouThreshold -> IOU iouThreshold
      scoreThresh' = case scoreThresh of
        Nothing -> Score 0.4
        Just scoreThresh -> Score scoreThresh
  risks <- runRisk cocoMap
  let sumRisks = sum $ map snd risks
      probabilitis = map (\(_, risk) -> risk / sumRisks) risks
      accumulatedProbabilitis = scanl (+) 0 probabilitis
      numDatasets = length $ cocoMapImageIds cocoMap
      seed = mkStdGen 0

  -- Generate dataset by probability.
  -- The dataset's format is same as coco dataset.
  -- Accumurate probability
  -- Gen sorted list by accumulated probability with image id.
  -- Lottery by random number
  -- Get image id by lottery
  -- Generate random number between 0 and 1
  -- Find accumulated probability that is greater than random number
  -- Get image id by accumulated probability

  -- imageSets has accumulated probability and image id.
  -- It uses binary search to find image id by random number.
  let imageSets :: Vector (Double, ImageId)
      imageSets = Vector.fromList $ zip accumulatedProbabilitis (cocoMapImageIds cocoMap)
      findImageIdFromImageSets :: Vector (Double, ImageId) -> Double -> ImageId
      findImageIdFromImageSets imageSets randomNum =
        let (start, end) = (0, Vector.length imageSets - 1)
            findImageIdFromImageSets' :: Int -> Int -> ImageId
            findImageIdFromImageSets' start end =
              let mid = (start + end) `div` 2
                  (accumulatedProbability, imageId) = imageSets Vector.! mid
               in if start == end
                    then imageId
                    else
                      if accumulatedProbability > randomNum
                        then findImageIdFromImageSets' start mid
                        else findImageIdFromImageSets' (mid + 1) end
         in findImageIdFromImageSets' start end
      lotteryN :: Int -> StdGen -> Int -> [ImageId]
      lotteryN _ _ 0 = []
      lotteryN numDatasets seed n =
        let (randNum, seed') = randomR (0, 1) seed
            imageId = findImageIdFromImageSets imageSets randNum
         in imageId : lotteryN numDatasets seed' (n - 1)
      imageIds = lotteryN numDatasets seed numDatasets
      cocoImages' = map (\imageId -> (cocoMapCocoImage cocoMap) Map.! imageId) imageIds
      newCoco =
        Coco
          { cocoImages = cocoImages',
            cocoCategories = cocoCategories coco,
            cocoAnnotations = [],
            cocoLicenses = cocoLicenses coco,
            cocoInfo = cocoInfo coco
          }
  writeCoco cocoOutputFile newCoco


bddCommand :: RiskCommands
bddCommand = RiskCommands {
  showRisk = RiskWeaver.Cmd.BDD.showRisk,
  generateRiskWeightedDataset = RiskWeaver.Cmd.BDD.generateRiskWeightedDataset
}