packages feed

risk-weaver-0.1.0.2: src/RiskWeaver/DSL/Core.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DefaultSignatures #-}

module RiskWeaver.DSL.Core where

import Control.Monad.Trans.Reader (ReaderT, ask, runReader)
import Control.Parallel.Strategies
import Data.Kind (Type)
import Data.Map (Map)
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Data.List qualified as List

class Rectangle a where
  rX :: a -> Double
  rY :: a -> Double
  rW :: a -> Double
  rH :: a -> Double

-- | Bounding box type class of ground truth
class (Eq (ClassG a), Eq (ClassD a)) => BoundingBox a where
  -- | Detection type
  data Detection a :: Type

  -- | Ground truth class type
  type ClassG a :: Type

  -- | Detection class type
  type ClassD a :: Type

  -- | Error type
  data ErrorType a :: Type

  -- | Interest area type
  type InterestArea a :: Type

  -- | Interest object type
  type InterestObject a :: Type

  -- | Environment type of the image
  data Env a :: Type

  -- | Index type of bounding box annotations
  type Idx a :: Type

  -- | Image index type of bounding box annotations
  type ImgIdx a :: Type

  -- | Risk type
  data Risk a :: Type

  -- | Risk of the environment
  riskE :: Env a -> [Risk a]
  riskE env = runReader myRisk env
    where
      myRisk = do
        !riskG <- riskForGroundTruth
        !riskD <- riskForDetection
        return $ riskG <> riskD

  -- | Risk of groundtruth
  riskForGroundTruth :: Monad m => ReaderT (Env a) m [Risk a]

  -- | Risk of detection
  riskForDetection :: Monad m => ReaderT (Env a) m [Risk a]

  -- | Interest area of the environment
  interestArea :: Env a -> InterestArea a

  -- | Interest object of the environment
  interestObject :: Env a -> InterestObject a

  -- | Ground truth of the environment
  groundTruth :: Env a -> Vector a

  -- | Detection of the environment
  detection :: Env a -> Vector (Detection a)

  -- | Confidence score threshold
  confidenceScoreThresh :: Env a -> Double

  -- | IoU threshold
  ioUThresh :: Env a -> Double

  -- | Confidence score of the detection
  scoreD :: Detection a -> Double

  -- | Size of the detection
  sizeD :: Detection a -> Double
  default sizeD :: (Rectangle (Detection a)) => Detection a -> Double
  sizeD v = rW v * rH v

  -- | Class of the detection
  classD :: Detection a -> ClassG a

  -- | Index of the detection
  idD :: Detection a -> Idx a

  -- | Index of the image
  imageId :: Env a -> ImgIdx a

  -- | True if the detection is in front of the other detection
  isFrontD :: Detection a -> Detection a -> Bool
  default isFrontD :: (Rectangle (Detection a)) => Detection a -> Detection a -> Bool
  isFrontD dtBack dtFront =
    let intersection =
          (min (rX dtBack + rW dtBack) (rX dtFront + rW dtFront) - max (rX dtBack) (rX dtFront))
            * (min (rY dtBack + rH dtBack) (rY dtFront + rH dtFront) - max (rY dtBack) (rY dtFront))
     in (intersection / (rW dtFront * rH dtFront)) >= 0.99

  -- | True if the detection is in back of the other detection
  isBackD :: Detection a -> Detection a -> Bool

  -- | True if the detection is in left of the other detection
  isLeftD :: Detection a -> Detection a -> Bool

  -- | True if the detection is in right of the other detection
  isRightD :: Detection a -> Detection a -> Bool

  -- | True if the detection is in top of the other detection
  isTopD :: Detection a -> Detection a -> Bool

  -- | True if the detection is in bottom of the other detection
  isBottomD :: Detection a -> Detection a -> Bool

  -- | True if the detection is background
  isBackGroundD :: ClassD a -> Bool

  -- | Detect the ground truth of the detection
  detectD :: Env a -> Detection a -> Maybe a
  detectD env dt =
    let gts = groundTruth env
        gts'' = filter (\gt -> classD @a dt == classG @a gt) $ Vector.toList gts
        -- Get max IOU detection with ioUThresh
        gts''' = filter (\(iou', _) -> iou' > ioUThresh env) $ map (\gt -> (ioU gt dt, gt)) gts''
     in case gts''' of
          [] -> Nothing
          gts_ -> Just $ snd $ List.maximumBy (\(iou1, _) (iou2, _) -> compare iou1 iou2) gts_

  -- | Get error type from risk
  toErrorType :: Risk a -> ErrorType a

  -- | Get a score from risk
  toRiskScore :: Risk a -> Double

  -- | Size of the ground truth
  sizeG :: a -> Double
  default sizeG :: (Rectangle a) => a -> Double
  sizeG v = rW v * rH v

  -- | Class of the ground truth
  classG :: a -> ClassG a

  -- | Angle of detection to the ground truth
  angle :: a -> Detection a -> Double

  -- | Index of the ground truth
  idG :: a -> Idx a

  -- | IoU(Intersection Over Union) of the ground truth and the detection
  ioU :: a -> Detection a -> Double
  default ioU :: (Rectangle a, Rectangle (Detection a)) => a -> Detection a -> Double
  ioU g d =
    let intersection =
          (min (rX g + rW g) (rX d + rW d) - max (rX g) (rX d))
            * (min (rY g + rH g) (rY d + rH d) - max (rY g) (rY d))
     in intersection / (rW g * rH g + rW d * rH d - intersection)

  -- | IoG(Intersection Over Ground truth) of the ground truth and the detection
  ioG :: a -> Detection a -> Double
  default ioG :: (Rectangle a, Rectangle (Detection a)) => a -> Detection a -> Double
  ioG g d =
    let intersection =
          (min (rX g + rW g) (rX d + rW d) - max (rX g) (rX d))
            * (min (rY g + rH g) (rY d + rH d) - max (rY g) (rY d))
     in intersection / (rW g * rH g)

  -- | IoD(Intersection Over Detection) of the ground truth and the detection
  ioD :: a -> Detection a -> Double
  default ioD :: (Rectangle a, Rectangle (Detection a)) => a -> Detection a -> Double
  ioD g d =
    let intersection =
          (min (rX g + rW g) (rX d + rW d) - max (rX g) (rX d))
            * (min (rY g + rH g) (rY d + rH d) - max (rY g) (rY d))
     in intersection / (rW d * rH d)

  -- | Detect the detection of the ground truth
  detectG :: Env a -> a -> Maybe (Detection a)
  detectG env gt =
    let dts = detection env
        dts' = filter (\dt -> scoreD @a dt > confidenceScoreThresh env) $ Vector.toList dts
        dts'' = filter (\dt -> classD @a dt == classG @a gt) dts'
        -- Get max IOU detection with ioUThresh
        dts''' = filter (\(iou', _) -> iou' > ioUThresh env) $ map (\dt -> (ioU gt dt, dt)) dts''
     in case dts''' of
          [] -> Nothing
          dts_ -> Just $ snd $ List.maximumBy (\(iou1, _) (iou2, _) -> compare iou1 iou2) dts_

  -- | True if the detection is in the interest area
  isInIeterestAreaD :: InterestArea a -> Detection a -> Bool

  -- | True if the ground truth is in the interest area
  isInIeterestAreaG :: InterestArea a -> a -> Bool

  -- | True if the detection is in the interest object
  isInterestObjectD :: InterestObject a -> Detection a -> Bool

  -- | True if the ground truth is in the interest object
  isInterestObjectG :: InterestObject a -> a -> Bool

-- | b includes ground-truth images and detection images.
class (NFData (ImgIdx a), NFData (Risk a), BoundingBox a) => World b a where
  -- | Environments of the image
  envs :: b -> [Env a]
  envs context =
    map
      (\imageId' -> toEnv @b @a context imageId')
      (toImageIds @b @a context)

  -- | An environment of the image
  toEnv :: b -> ImgIdx a -> Env a

  -- | An environment of the image
  toImageIds :: b -> [ImgIdx a]

  -- | mAP of the images
  mAP :: b -> Double

  -- | AP of the images for each class
  ap :: b -> Map (ClassG a) Double

  -- | mF1 of the images
  mF1 :: b -> Double

  -- | F1 of the images for each class
  f1 :: b -> Map (ClassG a) Double

  -- | Risk of the images
  risk :: b -> [Risk a]
  risk context = concat $ map snd $ runRiskWithError context

  -- | Confusion matrix of recall
  confusionMatrixRecall :: b -> Map (ClassG a, ClassD a) [Risk a]

  -- | Confusion matrix of precision
  confusionMatrixPrecision :: b -> Map (ClassD a, ClassG a) [Risk a]

-- | Loop for ground truth
loopG :: forall a m b. (BoundingBox a, Monad m) => (b -> b -> b) -> b -> (a -> ReaderT (Env a) m b) -> ReaderT (Env a) m b
loopG add init' fn = do
  env <- ask
  foldl add init' <$> mapM fn (groundTruth @a env)
{-# INLINEABLE loopG #-}

-- | Loop for detection
loopD :: forall a m b. (BoundingBox a, Monad m) => (b -> b -> b) -> b -> (Detection a -> ReaderT (Env a) m b) -> ReaderT (Env a) m b
loopD add init' fn = do
  env <- ask
  foldl add init' <$> mapM fn (detection @a env)
{-# INLINEABLE loopD #-}

detectMaxIouG :: BoundingBox a => Env a -> a -> Maybe (Detection a)
detectMaxIouG env gt =
  let dts = detection env
      dts' = map (\dt -> (ioU gt dt, dt)) $ Vector.toList dts
   in case dts' of
        [] -> Nothing
        dts_ -> Just $ snd $ List.maximumBy (\(iou1, _) (iou2, _) -> compare iou1 iou2) dts_

detectMaxIouD :: BoundingBox a => Env a -> (Detection a) -> Maybe a
detectMaxIouD env dt =
  let gts = groundTruth env
      gts' = map (\gt -> (ioU gt dt, gt)) $ Vector.toList gts
   in case gts' of
        [] -> Nothing
        gts_ -> Just $ snd $ List.maximumBy (\(iou1, _) (iou2, _) -> compare iou1 iou2) gts_

whenInterestAreaD :: forall m a b. (Monad m, BoundingBox a) => Bool -> Detection a -> ReaderT (Env a) m [b] -> ReaderT (Env a) m [b]
whenInterestAreaD cond dt func = do
  env <- ask
  if cond
  then do
    if isInIeterestAreaD (interestArea env) dt && isInterestObjectD (interestObject env) dt
      then func
      else return []
  else func

whenInterestAreaG :: forall m a b. (Monad m, BoundingBox a) => Bool -> a -> ReaderT (Env a) m [b] -> ReaderT (Env a) m [b]
whenInterestAreaG cond gt func = do
  env <- ask
  if cond
  then do
    if isInIeterestAreaG (interestArea env) gt && isInterestObjectG (interestObject env) gt
      then func
      else return []
  else func

runRisk :: forall context a. (World context a) => context -> [(ImgIdx a, Double)]
runRisk context =
  map (\(imageId', risks) -> (imageId', sum $ map (\r -> toRiskScore r) risks)) (runRiskWithError @context @a context)
    `using` parList rdeepseq

runRiskWithError :: forall context a. (World context a) => context -> [(ImgIdx a, [Risk a])]
runRiskWithError context =
  map (\imageId' -> (imageId', riskE (toEnv context imageId'))) (toImageIds @context @a context)
    `using` parList rdeepseq

generateRiskWeightedImages :: forall b a. World b a => b -> [ImgIdx a]
generateRiskWeightedImages context =
  let risks = runRisk @b @a context
      sumRisks = sum $ map snd risks
      probs = map (\(_, risk') -> risk' / sumRisks) risks
      acc_probs =
        let loop [] _ = []
            loop (x : xs) s = (s, s + x) : loop xs (s + x)
         in loop probs 0
      numDatasets = length $ toImageIds @b @a context
      imageSets :: [((Double, Double), ImgIdx a)]
      imageSets = zip acc_probs $ map fst risks
      resample [] _ _ = []
      resample s@(((x, y), img) : xs) n end =
        if n == end
          then []
          else
            let p = (fromIntegral n :: Double) / (fromIntegral numDatasets :: Double)
             in if x <= p && p < y
                  then img : resample s (n + 1) end
                  else resample xs n end
  in resample imageSets 0 numDatasets