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