packages feed

risk-weaver 0.1.0.1 → 0.1.0.2

raw patch · 12 files changed

+1880/−522 lines, 12 filesdep +asyncdep +deepseqdep +paralleldep ~JuicyPixelsdep ~containersdep ~filepathnew-component:exe:sample-riskPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: async, deepseq, parallel

Dependency ranges changed: JuicyPixels, containers, filepath, text, transformers, vector

API changes (from Hackage documentation)

- RiskWeaver.Cmd.BDD: cocoCategoryToClass :: CocoMap -> CategoryId -> Class
- RiskWeaver.Cmd.BDD: cocoResultToVector :: CocoMap -> ImageId -> (Vector BoundingBoxGT, Vector BoundingBoxDT)
- RiskWeaver.Cmd.BDD: runRisk :: CocoMap -> IO [(ImageId, Double)]
- RiskWeaver.Cmd.Core: ShowFalseNegative :: FilePath -> FilePath -> Maybe Double -> Maybe Double -> Maybe Int -> CocoCommand
- RiskWeaver.Cmd.Core: [imageId] :: CocoCommand -> Maybe Int
- RiskWeaver.Cmd.Core: evaluate :: Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()
- RiskWeaver.DSL.BDD: BoundingBoxDT :: Double -> Double -> Double -> Double -> Class -> Double -> Int -> BoundingBoxDT
- RiskWeaver.DSL.BDD: [$sel:cls:BoundingBoxDT] :: BoundingBoxDT -> Class
- RiskWeaver.DSL.BDD: [$sel:h:BoundingBoxDT] :: BoundingBoxDT -> Double
- RiskWeaver.DSL.BDD: [$sel:idx:BoundingBoxDT] :: BoundingBoxDT -> Int
- RiskWeaver.DSL.BDD: [$sel:score:BoundingBoxDT] :: BoundingBoxDT -> Double
- RiskWeaver.DSL.BDD: [$sel:w:BoundingBoxDT] :: BoundingBoxDT -> Double
- RiskWeaver.DSL.BDD: [$sel:x:BoundingBoxDT] :: BoundingBoxDT -> Double
- RiskWeaver.DSL.BDD: [$sel:y:BoundingBoxDT] :: BoundingBoxDT -> Double
- RiskWeaver.DSL.BDD: data BoundingBoxDT
- RiskWeaver.DSL.BDD: data FNError
- RiskWeaver.DSL.BDD: instance GHC.Classes.Eq RiskWeaver.DSL.BDD.BoundingBoxDT
- RiskWeaver.DSL.BDD: instance GHC.Classes.Eq RiskWeaver.DSL.BDD.FNError
- RiskWeaver.DSL.BDD: instance GHC.Show.Show RiskWeaver.DSL.BDD.BoundingBoxDT
- RiskWeaver.DSL.BDD: instance GHC.Show.Show RiskWeaver.DSL.BDD.FNError
- RiskWeaver.DSL.BDD: myRisk :: forall a m. (Fractional (Risk a), Num (Risk a), BoundingBox a, Monad m) => ReaderT (Env a) m (Risk a)
- RiskWeaver.DSL.BDD: myRiskWithError :: forall a m. (Monoid [(Risk a, ErrorType a)], BoundingBox a, Monad m, a ~ BoundingBoxGT) => ReaderT (Env a) m [(Risk a, ErrorType a)]
- RiskWeaver.DSL.Core: confusionMatrixAccuracy :: World a => Vector (Image a) -> Map (ClassD a, ClassG a) Double
- RiskWeaver.DSL.Core: confusionMatrixAccuracy' :: World a => Vector (Image a) -> Map (ClassD a, ClassG a) [Idx a]
- RiskWeaver.DSL.Core: confusionMatrixAccuracyBB :: BoundingBox a => Env a -> Map (ClassD a, ClassG a) Double
- RiskWeaver.DSL.Core: confusionMatrixAccuracyBB' :: BoundingBox a => Env a -> Map (ClassD a, ClassG a) [Idx a]
- RiskWeaver.DSL.Core: confusionMatrixRecall' :: World a => Vector (Image a) -> Map (ClassG a, ClassD a) [Idx a]
- RiskWeaver.DSL.Core: confusionMatrixRecallBB :: BoundingBox a => Env a -> Map (ClassG a, ClassD a) Double
- RiskWeaver.DSL.Core: confusionMatrixRecallBB' :: BoundingBox a => Env a -> Map (ClassG a, ClassD a) [Idx a]
- RiskWeaver.DSL.Core: env :: World a => Image a -> Env a
- RiskWeaver.DSL.Core: errorGroups :: World a => Vector (Image a) -> Map (ClassG a) (Map (ErrorType a) [Idx a])
- RiskWeaver.DSL.Core: errorGroupsBB :: BoundingBox a => Env a -> Map (ClassG a) (Map (ErrorType a) [Idx a])
- RiskWeaver.DSL.Core: errorType :: BoundingBox a => Env a -> Detection a -> Maybe (ErrorType a)
- RiskWeaver.DSL.Core: idI :: World a => Image a -> Int
- RiskWeaver.DSL.Core: riskBB :: BoundingBox a => Env a -> Risk a
- RiskWeaver.DSL.Core: riskD :: BoundingBox a => Env a -> Detection a -> Risk a
- RiskWeaver.DSL.Core: type Detection a :: Type;
- RiskWeaver.DSL.Core: type Image a :: Type;
- RiskWeaver.DSL.Core: type Risk a :: Type;
- RiskWeaver.Draw: inline_c_ffi_6989586621679060425 :: Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
- RiskWeaver.Draw: inline_c_ffi_6989586621679060476 :: Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
- RiskWeaver.Draw: inline_c_ffi_6989586621679060843 :: Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
- RiskWeaver.Draw: inline_c_ffi_6989586621679060893 :: Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
- RiskWeaver.Metric: ConfusionMatrix :: Map (Gt CategoryId) (Map (Dt CategoryId) Int) -> Map (Dt CategoryId) (Map (Gt CategoryId) Int) -> [CategoryId] -> ConfusionMatrix
- RiskWeaver.Metric: [confusionMatrixCategoryIds] :: ConfusionMatrix -> [CategoryId]
- RiskWeaver.Metric: [confusionMatrixPrecision] :: ConfusionMatrix -> Map (Dt CategoryId) (Map (Gt CategoryId) Int)
- RiskWeaver.Metric: [confusionMatrixRecall] :: ConfusionMatrix -> Map (Gt CategoryId) (Map (Dt CategoryId) Int)
- RiskWeaver.Metric: confusionMatrix :: CocoMap -> IOU -> Score -> ConfusionMatrix
- RiskWeaver.Metric: confusionMatrixForImage :: CocoMap -> IOU -> Score -> ImageId -> ConfusionMatrix
- RiskWeaver.Metric: data ConfusionMatrix
- RiskWeaver.Metric: instance GHC.Base.Semigroup RiskWeaver.Metric.ConfusionMatrix
- RiskWeaver.Metric: instance GHC.Classes.Eq RiskWeaver.Metric.ConfusionMatrix
- RiskWeaver.Metric: instance GHC.Classes.Ord RiskWeaver.Metric.ConfusionMatrix
- RiskWeaver.Metric: instance GHC.Show.Show RiskWeaver.Metric.ConfusionMatrix
+ RiskWeaver.Cmd.BDD: (!!!) :: forall a b. Ord b => Map b [a] -> b -> [a]
+ RiskWeaver.Cmd.BDD: black :: (Int, Int, Int)
+ RiskWeaver.Cmd.BDD: evaluate :: CocoMap -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.BDD: green :: (Int, Int, Int)
+ RiskWeaver.Cmd.BDD: red :: (Int, Int, Int)
+ RiskWeaver.Cmd.BDD: showDetectionImage :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.BDD: showRiskWithError :: CocoMap -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.BDD: toBddContext :: CocoMap -> Maybe Double -> Maybe Double -> BddContext
+ RiskWeaver.Cmd.Core: GenerateTemplate :: CocoCommand
+ RiskWeaver.Cmd.Core: ShowRiskWithError :: FilePath -> FilePath -> Maybe Double -> Maybe Double -> CocoCommand
+ RiskWeaver.Cmd.Core: [evaluate] :: RiskCommands -> CocoMap -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.Core: [showDetectionImage] :: RiskCommands -> CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.Core: [showRiskWithError] :: RiskCommands -> CocoMap -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.Core: generateTemplate :: IO ()
+ RiskWeaver.DSL.BDD: BddContext :: CocoMap -> Double -> Double -> Bool -> BddContext
+ RiskWeaver.DSL.BDD: [$sel:bddContextDataset:BddContext] :: BddContext -> CocoMap
+ RiskWeaver.DSL.BDD: [$sel:bddContextIouThresh:BddContext] :: BddContext -> Double
+ RiskWeaver.DSL.BDD: [$sel:bddContextScoreThresh:BddContext] :: BddContext -> Double
+ RiskWeaver.DSL.BDD: [$sel:bddContextUseInterestArea:BddContext] :: BddContext -> Bool
+ RiskWeaver.DSL.BDD: cocoCategoryToClass :: CocoMap -> CategoryId -> Class
+ RiskWeaver.DSL.BDD: cocoResultToVector :: CocoMap -> ImageId -> (Vector BoundingBoxGT, Vector BoundingBoxDT)
+ RiskWeaver.DSL.BDD: data BddContext
+ RiskWeaver.DSL.BDD: data SubErrorType
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData (RiskWeaver.DSL.Core.Detection RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData (RiskWeaver.DSL.Core.Env RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData (RiskWeaver.DSL.Core.ErrorType RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData (RiskWeaver.DSL.Core.Risk RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData RiskWeaver.DSL.BDD.BoundingBoxGT
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData RiskWeaver.DSL.BDD.Class
+ RiskWeaver.DSL.BDD: instance Control.DeepSeq.NFData RiskWeaver.DSL.BDD.SubErrorType
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Eq (RiskWeaver.DSL.Core.Detection RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Eq (RiskWeaver.DSL.Core.Env RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Eq (RiskWeaver.DSL.Core.Risk RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Eq RiskWeaver.DSL.BDD.BddContext
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Eq RiskWeaver.DSL.BDD.SubErrorType
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord (RiskWeaver.DSL.Core.Detection RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord (RiskWeaver.DSL.Core.Env RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord (RiskWeaver.DSL.Core.ErrorType RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord (RiskWeaver.DSL.Core.Risk RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord RiskWeaver.DSL.BDD.BoundingBoxGT
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord RiskWeaver.DSL.BDD.Class
+ RiskWeaver.DSL.BDD: instance GHC.Classes.Ord RiskWeaver.DSL.BDD.SubErrorType
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic (RiskWeaver.DSL.Core.Detection RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic (RiskWeaver.DSL.Core.Env RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic (RiskWeaver.DSL.Core.ErrorType RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic (RiskWeaver.DSL.Core.Risk RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic RiskWeaver.DSL.BDD.BoundingBoxGT
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic RiskWeaver.DSL.BDD.Class
+ RiskWeaver.DSL.BDD: instance GHC.Generics.Generic RiskWeaver.DSL.BDD.SubErrorType
+ RiskWeaver.DSL.BDD: instance GHC.Show.Show (RiskWeaver.DSL.Core.Detection RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Show.Show (RiskWeaver.DSL.Core.Env RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Show.Show (RiskWeaver.DSL.Core.Risk RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance GHC.Show.Show RiskWeaver.DSL.BDD.BddContext
+ RiskWeaver.DSL.BDD: instance GHC.Show.Show RiskWeaver.DSL.BDD.SubErrorType
+ RiskWeaver.DSL.BDD: instance RiskWeaver.DSL.Core.Rectangle (RiskWeaver.DSL.Core.Detection RiskWeaver.DSL.BDD.BoundingBoxGT)
+ RiskWeaver.DSL.BDD: instance RiskWeaver.DSL.Core.Rectangle RiskWeaver.DSL.BDD.BoundingBoxGT
+ RiskWeaver.DSL.BDD: instance RiskWeaver.DSL.Core.World RiskWeaver.DSL.BDD.BddContext RiskWeaver.DSL.BDD.BoundingBoxGT
+ RiskWeaver.DSL.BDD: type BddRisk = Risk BoundingBoxGT
+ RiskWeaver.DSL.BDD: type BoundingBoxDT = Detection BoundingBoxGT
+ RiskWeaver.DSL.Core: -- | Risk type
+ RiskWeaver.DSL.Core: class Rectangle a
+ RiskWeaver.DSL.Core: confusionMatrixPrecision :: World b a => b -> Map (ClassD a, ClassG a) [Risk a]
+ RiskWeaver.DSL.Core: data Detection a :: Type;
+ RiskWeaver.DSL.Core: data Risk a :: Type;
+ RiskWeaver.DSL.Core: detectMaxIouD :: BoundingBox a => Env a -> Detection a -> Maybe a
+ RiskWeaver.DSL.Core: detectMaxIouG :: BoundingBox a => Env a -> a -> Maybe (Detection a)
+ RiskWeaver.DSL.Core: envs :: World b a => b -> [Env a]
+ RiskWeaver.DSL.Core: f1 :: World b a => b -> Map (ClassG a) Double
+ RiskWeaver.DSL.Core: generateRiskWeightedImages :: forall b a. World b a => b -> [ImgIdx a]
+ RiskWeaver.DSL.Core: imageId :: BoundingBox a => Env a -> ImgIdx a
+ RiskWeaver.DSL.Core: isInterestObjectD :: BoundingBox a => InterestObject a -> Detection a -> Bool
+ RiskWeaver.DSL.Core: isInterestObjectG :: BoundingBox a => InterestObject a -> a -> Bool
+ RiskWeaver.DSL.Core: mF1 :: World b a => b -> Double
+ RiskWeaver.DSL.Core: rH :: Rectangle a => a -> Double
+ RiskWeaver.DSL.Core: rW :: Rectangle a => a -> Double
+ RiskWeaver.DSL.Core: rX :: Rectangle a => a -> Double
+ RiskWeaver.DSL.Core: rY :: Rectangle a => a -> Double
+ RiskWeaver.DSL.Core: riskForDetection :: (BoundingBox a, Monad m) => ReaderT (Env a) m [Risk a]
+ RiskWeaver.DSL.Core: riskForGroundTruth :: (BoundingBox a, Monad m) => ReaderT (Env a) m [Risk a]
+ RiskWeaver.DSL.Core: runRisk :: forall context a. World context a => context -> [(ImgIdx a, Double)]
+ RiskWeaver.DSL.Core: runRiskWithError :: forall context a. World context a => context -> [(ImgIdx a, [Risk a])]
+ RiskWeaver.DSL.Core: toEnv :: World b a => b -> ImgIdx a -> Env a
+ RiskWeaver.DSL.Core: toErrorType :: BoundingBox a => Risk a -> ErrorType a
+ RiskWeaver.DSL.Core: toImageIds :: World b a => b -> [ImgIdx a]
+ RiskWeaver.DSL.Core: toRiskScore :: BoundingBox a => Risk a -> Double
+ RiskWeaver.DSL.Core: type ImgIdx a :: Type;
+ RiskWeaver.DSL.Core: whenInterestAreaD :: forall m a b. (Monad m, BoundingBox a) => Bool -> Detection a -> ReaderT (Env a) m [b] -> ReaderT (Env a) m [b]
+ RiskWeaver.DSL.Core: whenInterestAreaG :: forall m a b. (Monad m, BoundingBox a) => Bool -> a -> ReaderT (Env a) m [b] -> ReaderT (Env a) m [b]
+ RiskWeaver.Draw: cloneImage :: Image PixelRGB8 -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: concatImageByHorizontal :: Image PixelRGB8 -> Image PixelRGB8 -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: concatImageByVertical :: Image PixelRGB8 -> Image PixelRGB8 -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: concatImages2x2 :: Image PixelRGB8 -> Image PixelRGB8 -> Image PixelRGB8 -> Image PixelRGB8 -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: concatImagesH :: [Image PixelRGB8] -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: concatImagesV :: [Image PixelRGB8] -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: createImage :: Int -> Int -> IO (Image PixelRGB8)
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071550 :: Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071599 :: Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071676 :: Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071722 :: Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071763 :: Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071810 :: Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071874 :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: inline_c_ffi_6989586621679071928 :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> CInt -> IO ()
+ RiskWeaver.Draw: pasteImage :: Image PixelRGB8 -> Int -> Int -> Image PixelRGB8 -> IO ()
+ RiskWeaver.Format.Coco: [cocoMapCocoFile] :: CocoMap -> FilePath
+ RiskWeaver.Format.Coco: [cocoMapCocoResultFile] :: CocoMap -> FilePath
+ RiskWeaver.Format.Coco: [cocoMapCoco] :: CocoMap -> Coco
+ RiskWeaver.Format.Coco: class CocoMapable a
+ RiskWeaver.Format.Coco: getCocoResult :: CocoMapable a => CocoMap -> a -> Maybe (CocoImage, [CocoResult])
+ RiskWeaver.Format.Coco: getImageDir :: CocoMap -> FilePath
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CategoryId
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CoCoBoundingBox
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.Coco
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoAnnotation
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoCategory
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoImage
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoInfo
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoLicense
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoMap
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.CocoResult
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.ImageId
+ RiskWeaver.Format.Coco: instance Control.DeepSeq.NFData RiskWeaver.Format.Coco.Score
+ RiskWeaver.Format.Coco: instance RiskWeaver.Format.Coco.CocoMapable GHC.IO.FilePath
+ RiskWeaver.Format.Coco: instance RiskWeaver.Format.Coco.CocoMapable RiskWeaver.Format.Coco.ImageId
+ RiskWeaver.Format.Coco: readCocoMap :: FilePath -> FilePath -> IO CocoMap
+ RiskWeaver.Format.Coco: resampleCocoMapWithImageIds :: CocoMap -> [ImageId] -> (Coco, [CocoResult])
+ RiskWeaver.Metric: average :: forall a f. (Num a, Foldable f, Fractional a) => f a -> a
+ RiskWeaver.Metric: f1ForCategory :: CocoMap -> CategoryId -> IOU -> Score -> Double
+ RiskWeaver.Metric: mF1 :: CocoMap -> IOU -> Score -> (Double, [(CategoryId, Double)])
+ RiskWeaver.Metric: sortAndGroup :: Ord k => [(k, v)] -> Map k [v]
+ RiskWeaver.Pip: Point :: (Double, Double) -> Point
+ RiskWeaver.Pip: Polygon :: [(Double, Double)] -> Polygon
+ RiskWeaver.Pip: instance GHC.Classes.Eq RiskWeaver.Pip.Point
+ RiskWeaver.Pip: instance GHC.Classes.Eq RiskWeaver.Pip.Polygon
+ RiskWeaver.Pip: instance GHC.Show.Show RiskWeaver.Pip.Point
+ RiskWeaver.Pip: instance GHC.Show.Show RiskWeaver.Pip.Polygon
+ RiskWeaver.Pip: newtype Point
+ RiskWeaver.Pip: newtype Polygon
+ RiskWeaver.Pip: pointInPolygon :: Polygon -> Point -> Bool
- RiskWeaver.Cmd.BDD: generateRiskWeightedDataset :: Coco -> [CocoResult] -> FilePath -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.BDD: generateRiskWeightedDataset :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()
- RiskWeaver.Cmd.BDD: showRisk :: Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()
+ RiskWeaver.Cmd.BDD: showRisk :: CocoMap -> Maybe Double -> Maybe Double -> IO ()
- RiskWeaver.Cmd.Core: Evaluate :: FilePath -> FilePath -> Maybe Double -> Maybe Double -> Maybe Int -> CocoCommand
+ RiskWeaver.Cmd.Core: Evaluate :: FilePath -> FilePath -> Maybe Double -> Maybe Double -> CocoCommand
- RiskWeaver.Cmd.Core: RiskCommands :: (Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()) -> (Coco -> [CocoResult] -> FilePath -> Maybe Double -> Maybe Double -> IO ()) -> RiskCommands
+ RiskWeaver.Cmd.Core: RiskCommands :: (CocoMap -> Maybe Double -> Maybe Double -> IO ()) -> (CocoMap -> Maybe Double -> Maybe Double -> IO ()) -> (CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()) -> (CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()) -> (CocoMap -> Maybe Double -> Maybe Double -> IO ()) -> RiskCommands
- RiskWeaver.Cmd.Core: ShowDetectionImage :: FilePath -> FilePath -> FilePath -> Maybe Double -> CocoCommand
+ RiskWeaver.Cmd.Core: ShowDetectionImage :: FilePath -> FilePath -> FilePath -> Maybe Double -> Maybe Double -> CocoCommand
- RiskWeaver.Cmd.Core: ShowRisk :: FilePath -> FilePath -> Maybe Double -> Maybe Double -> Maybe Int -> CocoCommand
+ RiskWeaver.Cmd.Core: ShowRisk :: FilePath -> FilePath -> Maybe Double -> Maybe Double -> CocoCommand
- RiskWeaver.Cmd.Core: [generateRiskWeightedDataset] :: RiskCommands -> Coco -> [CocoResult] -> FilePath -> Maybe Double -> Maybe Double -> IO ()
+ RiskWeaver.Cmd.Core: [generateRiskWeightedDataset] :: RiskCommands -> CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()
- RiskWeaver.Cmd.Core: [showRisk] :: RiskCommands -> Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()
+ RiskWeaver.Cmd.Core: [showRisk] :: RiskCommands -> CocoMap -> Maybe Double -> Maybe Double -> IO ()
- RiskWeaver.DSL.BDD: Boundary :: FNError
+ RiskWeaver.DSL.BDD: Boundary :: SubErrorType
- RiskWeaver.DSL.BDD: LowScore :: FNError
+ RiskWeaver.DSL.BDD: LowScore :: SubErrorType
- RiskWeaver.DSL.BDD: MissClass :: FNError
+ RiskWeaver.DSL.BDD: MissClass :: SubErrorType
- RiskWeaver.DSL.BDD: Occulusion :: FNError
+ RiskWeaver.DSL.BDD: Occulusion :: SubErrorType
- RiskWeaver.DSL.Core: ap :: World a => Vector (Image a) -> Map (ClassG a) Double
+ RiskWeaver.DSL.Core: ap :: World b a => b -> Map (ClassG a) Double
- RiskWeaver.DSL.Core: class BoundingBox a where {
+ RiskWeaver.DSL.Core: class (Eq (ClassG a), Eq (ClassD a)) => BoundingBox a where {
- RiskWeaver.DSL.Core: class (BoundingBox a) => World a where {
+ RiskWeaver.DSL.Core: class (NFData (ImgIdx a), NFData (Risk a), BoundingBox a) => World b a
- RiskWeaver.DSL.Core: confusionMatrixRecall :: World a => Vector (Image a) -> Map (ClassG a, ClassD a) Double
+ RiskWeaver.DSL.Core: confusionMatrixRecall :: World b a => b -> Map (ClassG a, ClassD a) [Risk a]
- RiskWeaver.DSL.Core: ioD :: BoundingBox a => a -> Detection a -> Double
+ RiskWeaver.DSL.Core: ioD :: (BoundingBox a, Rectangle a, Rectangle (Detection a)) => a -> Detection a -> Double
- RiskWeaver.DSL.Core: ioG :: BoundingBox a => a -> Detection a -> Double
+ RiskWeaver.DSL.Core: ioG :: (BoundingBox a, Rectangle a, Rectangle (Detection a)) => a -> Detection a -> Double
- RiskWeaver.DSL.Core: ioU :: BoundingBox a => a -> Detection a -> Double
+ RiskWeaver.DSL.Core: ioU :: (BoundingBox a, Rectangle a, Rectangle (Detection a)) => a -> Detection a -> Double
- RiskWeaver.DSL.Core: isFrontD :: BoundingBox a => Detection a -> Detection a -> Bool
+ RiskWeaver.DSL.Core: isFrontD :: (BoundingBox a, Rectangle (Detection a)) => Detection a -> Detection a -> Bool
- RiskWeaver.DSL.Core: mAP :: World a => Vector (Image a) -> Double
+ RiskWeaver.DSL.Core: mAP :: World b a => b -> Double
- RiskWeaver.DSL.Core: risk :: World a => Vector (Image a) -> Double
+ RiskWeaver.DSL.Core: risk :: World b a => b -> [Risk a]
- RiskWeaver.DSL.Core: riskE :: BoundingBox a => Env a -> Risk a
+ RiskWeaver.DSL.Core: riskE :: BoundingBox a => Env a -> [Risk a]
- RiskWeaver.DSL.Core: sizeD :: BoundingBox a => Detection a -> Double
+ RiskWeaver.DSL.Core: sizeD :: (BoundingBox a, Rectangle (Detection a)) => Detection a -> Double
- RiskWeaver.DSL.Core: sizeG :: BoundingBox a => a -> Double
+ RiskWeaver.DSL.Core: sizeG :: (BoundingBox a, Rectangle a) => a -> Double
- RiskWeaver.Display: drawDetectionBoundingBox :: DynamicImage -> [CocoResult] -> Map CategoryId CocoCategory -> Maybe Double -> IO (Image PixelRGB8)
+ RiskWeaver.Display: drawDetectionBoundingBox :: Show a => DynamicImage -> [CocoResult] -> [a] -> Map CategoryId CocoCategory -> Maybe Double -> Maybe (Image PixelRGB8 -> a -> IO (Image PixelRGB8)) -> IO (Image PixelRGB8)
- RiskWeaver.Display: showDetectionImage :: Coco -> FilePath -> FilePath -> FilePath -> Maybe Double -> IO ()
+ RiskWeaver.Display: showDetectionImage :: Show a => CocoMap -> FilePath -> Maybe Double -> [a] -> Maybe (Image PixelRGB8 -> a -> IO (Image PixelRGB8)) -> IO ()
- RiskWeaver.Format.Coco: CocoMap :: Map FilePath [ImageId] -> Map ImageId CocoImage -> Map ImageId [CocoAnnotation] -> Map CategoryId CocoCategory -> Map ImageId [CocoResult] -> Map ImageId FilePath -> [ImageId] -> [CategoryId] -> CocoMap
+ RiskWeaver.Format.Coco: CocoMap :: Map FilePath [ImageId] -> Map ImageId CocoImage -> Map ImageId [CocoAnnotation] -> Map CategoryId CocoCategory -> Map ImageId [CocoResult] -> Map ImageId FilePath -> [ImageId] -> [CategoryId] -> Coco -> FilePath -> FilePath -> CocoMap
- RiskWeaver.Format.Coco: toCocoMap :: Coco -> [CocoResult] -> CocoMap
+ RiskWeaver.Format.Coco: toCocoMap :: Coco -> [CocoResult] -> FilePath -> FilePath -> CocoMap

Files

app/Main.hs view
@@ -3,7 +3,8 @@  module Main where -import RiskWeaver.Cmd.Core qualified as Core import RiskWeaver.Cmd.BDD qualified as BDD+import RiskWeaver.Cmd.Core qualified as Core +main :: IO () main = Core.baseMain BDD.bddCommand
+ bin/sample-risk.hs view
@@ -0,0 +1,596 @@+#!/usr/bin/env cabal+{- cabal:+build-depends: base+             , risk-weaver == 0.1.0.2+	     , containers+	     , vector+	     , text+	     , random+	     , transformers+       , parallel+       , filepath+       , JuicyPixels+-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++import Codec.Picture+import Control.Monad+import Control.Monad.Trans.Reader (ReaderT, ask, runReader)+import Control.Parallel.Strategies+import Data.List (sortBy)+import Data.Map qualified as Map+import Data.Maybe (fromMaybe)+import Data.Set (Set)+import Data.Set qualified as Set+import Data.Text qualified as T+import Data.Vector (Vector)+import Data.Vector qualified as Vector+import GHC.Generics+import RiskWeaver.Cmd.Core (RiskCommands (..), baseMain)+import RiskWeaver.DSL.Core+import RiskWeaver.DSL.Core qualified as Core+import RiskWeaver.Display (putImage)+import RiskWeaver.Draw+import RiskWeaver.Format.Coco+import RiskWeaver.Metric+import RiskWeaver.Metric qualified as M+import RiskWeaver.Pip+import System.FilePath ((</>))+import Text.Printf+++++data BoundingBoxGT = BoundingBoxGT+  { x :: Double,+    y :: Double,+    w :: Double,+    h :: Double,+    cls :: Class,+    idx :: Int+  }+  deriving (Show, Eq, Ord, Generic, NFData)++data Class+  = Background+  | Pedestrian+  | Rider+  | Car+  | Truck+  | Bus+  | Train+  | Motorcycle+  | Bicycle+  deriving (Show, Eq, Ord, Generic, NFData)++data SubErrorType+  = Boundary+  | LowScore+  | MissClass+  | Occulusion+  deriving (Show, Ord, Eq, Generic, NFData)++type BoundingBoxDT = Detection BoundingBoxGT++instance Rectangle BoundingBoxGT where+  rX b = b.x+  rY b = b.y+  rW b = b.w+  rH b = b.h+  +instance Rectangle (Detection BoundingBoxGT) where+  rX b = b.x+  rY b = b.y+  rW b = b.w+  rH b = b.h+  +instance BoundingBox BoundingBoxGT where+  data Detection _ = BoundingBoxDT+    { x :: Double,+      y :: Double,+      w :: Double,+      h :: Double,+      cls :: Class,+      score :: Double,+      idx :: Int+    }+    deriving (Show, Eq, Ord, Generic, NFData)+  type ClassG _ = Class+  type ClassD _ = Class+  data ErrorType _+    = FalsePositive (Set SubErrorType)+    | FalseNegative (Set SubErrorType)+    | TruePositive+    | TrueNegative+    deriving (Ord, Eq, Generic, NFData)+  type InterestArea _ = [(Double, Double)]+  type InterestObject _ = Either BoundingBoxGT BoundingBoxDT -> Bool+  data Env _ = MyEnv+    { envGroundTruth :: Vector BoundingBoxGT,+      envDetection :: Vector BoundingBoxDT,+      envConfidenceScoreThresh :: Double,+      envIoUThresh :: Double,+      envUseInterestArea :: Bool,+      envImageId :: ImageId+    }+    deriving (Show, Ord, Eq, Generic, NFData)+  type Idx _ = Int+  type ImgIdx _ = ImageId+  data Risk _ = BddRisk+    { riskType :: ErrorType BoundingBoxGT,+      risk :: Double,+      riskGt :: Maybe BoundingBoxGT,+      riskDt :: Maybe (Detection BoundingBoxGT)+    } deriving (Show, Ord, Eq, Generic, NFData)++  interestArea :: Env BoundingBoxGT -> InterestArea BoundingBoxGT+  interestArea _ = [(0, 1), (0.3, 0.6), (0.7, 0.6), (1, 1), (1, 2), (0, 2)]+  interestObject _ = \case+    Left gt -> gt.w * gt.h > 1000+    Right dt -> dt.w * dt.h > 1000+  groundTruth env = envGroundTruth env+  detection env = envDetection env+  confidenceScoreThresh env = envConfidenceScoreThresh env+  ioUThresh env = envIoUThresh env+  scoreD v = v.score+  classD v = v.cls+  idD v = v.idx+  imageId env = envImageId env++  isBackD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isBackD _ _ = undefined++  isLeftD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isLeftD _ _ = undefined+  isRightD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isRightD _ _ = undefined+  isTopD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isTopD _ _ = undefined+  isBottomD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isBottomD _ _ = undefined+  isBackGroundD :: ClassD BoundingBoxGT -> Bool+  isBackGroundD Background = True+  isBackGroundD _ = False+  toErrorType = riskType+  toRiskScore = Main.risk++  classG v = v.cls+  angle _ _ = undefined+  idG v = v.idx++  isInIeterestAreaD :: InterestArea BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isInIeterestAreaD polygon dt = pointInPolygon (Polygon polygon) (Point (dt.x, dt.y))+  isInIeterestAreaG :: InterestArea BoundingBoxGT -> BoundingBoxGT -> Bool+  isInIeterestAreaG polygon gt = pointInPolygon (Polygon polygon) (Point (gt.x, gt.y))+  isInterestObjectD :: InterestObject BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isInterestObjectD fn dt = fn $ Right dt+  isInterestObjectG :: InterestObject BoundingBoxGT -> BoundingBoxGT -> Bool+  isInterestObjectG fn gt = fn $ Left gt++  riskForGroundTruth :: forall m. (Monad m) => ReaderT (Env BoundingBoxGT) m [Risk BoundingBoxGT]+  riskForGroundTruth = do+    env <- ask+    loopG (++) [] $ \(gt :: a) ->+      whenInterestAreaG (envUseInterestArea env) gt $ do+        let riskBias = if classG @BoundingBoxGT gt == Pedestrian then 10 else 1+        case detectG env gt of+          Just (dt :: Detection a) ->+            return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = 0.0001, riskType = TruePositive}]+          Nothing -> do+            case detectMaxIouG env gt of+              Nothing -> return [BddRisk {riskGt = Just gt, riskDt = Nothing, risk = riskBias * 30, riskType = FalseNegative []}]+              Just (dt :: Detection a) -> do+                case ( classD @BoundingBoxGT dt == classG @BoundingBoxGT gt,+                       scoreD @BoundingBoxGT dt > confidenceScoreThresh env,+                       ioU gt dt > ioUThresh env,+                       ioG gt dt > ioUThresh env+                     ) of+                  (False, False, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5.1, riskType = FalseNegative [MissClass, LowScore, Occulusion]}]+                  (False, False, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5, riskType = FalseNegative [MissClass, LowScore]}]+                  (False, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5.1, riskType = FalseNegative [MissClass, Occulusion]}]+                  (False, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 2, riskType = FalseNegative [MissClass]}]+                  (True, False, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5.1, riskType = FalseNegative [LowScore, Occulusion]}]+                  (True, False, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5, riskType = FalseNegative [LowScore]}]+                  (True, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.1, riskType = FalseNegative [Occulusion]}]+                  (True, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.0001, riskType = TruePositive}]+                  (_, _, False, False) -> return [BddRisk {riskGt = Just gt, riskDt = Nothing, risk = riskBias * 30, riskType = FalseNegative []}]+  {-# INLINEABLE riskForGroundTruth #-}+  +  riskForDetection :: forall m. (Monad m) => ReaderT (Env BoundingBoxGT) m [Risk BoundingBoxGT]+  riskForDetection = do+    env <- ask+    loopD (++) [] $ \(dt :: Detection a) ->+      whenInterestAreaD (envUseInterestArea env) dt $ do+        let riskBias = if classD @BoundingBoxGT dt == Pedestrian then 10 else 1+        case detectD env dt of+          Just (gt :: a) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = 0.0001, riskType = TruePositive}]+          Nothing -> do+            case detectMaxIouD env dt of+              Nothing -> return [BddRisk {riskGt = Nothing, riskDt = Just dt, risk = riskBias * 5, riskType = FalsePositive []}]+              Just (gt :: a) -> do+                case ( classD @BoundingBoxGT dt == classG @BoundingBoxGT gt,+                       scoreD @BoundingBoxGT dt > confidenceScoreThresh env,+                       ioU gt dt > ioUThresh env,+                       ioG gt dt > ioUThresh env+                     ) of+                  (False, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 2.1, riskType = FalsePositive [MissClass, Occulusion]}]+                  (False, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 2, riskType = FalsePositive [MissClass]}]+                  (True, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.1, riskType = FalsePositive [Occulusion]}]+                  (True, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.0001, riskType = TruePositive}]+                  (_, True, False, False) -> return [BddRisk {riskGt = Nothing, riskDt = Just dt, risk = riskBias * 5, riskType = FalsePositive []}]+                  (_, False, _, _) -> return []+  {-# INLINEABLE riskForDetection #-}++instance Show (ErrorType BoundingBoxGT) where+  show (FalsePositive suberrors) = "FP: " ++ foldl (\acc suberror -> acc ++ show suberror ++ ", ") "" (Set.toList suberrors)+  show (FalseNegative suberrors) = "FN: " ++ foldl (\acc suberror -> acc ++ show suberror ++ ", ") "" (Set.toList suberrors)+  show TruePositive = "TP"+  show TrueNegative = "TN"++type BddRisk = Risk BoundingBoxGT++cocoCategoryToClass :: CocoMap -> CategoryId -> Class+cocoCategoryToClass coco categoryId =+  let cocoCategory = (cocoMapCocoCategory coco) Map.! categoryId+   in case T.unpack (cocoCategoryName cocoCategory) of+        "pedestrian" -> Pedestrian+        "rider" -> Rider+        "car" -> Car+        "truck" -> Truck+        "bus" -> Bus+        "train" -> Train+        "motorcycle" -> Motorcycle+        "bicycle" -> Bicycle+        _ -> Background++cocoResultToVector :: CocoMap -> ImageId -> (Vector BoundingBoxGT, Vector BoundingBoxDT)+cocoResultToVector coco imageId' = (groundTruth', detection')+  where+    groundTruth' =+      Vector.fromList $+        maybe+          []+          ( map+              ( \(index, CocoAnnotation {..}) ->+                  let CoCoBoundingBox (cocox, cocoy, cocow, cocoh) = cocoAnnotationBbox+                   in BoundingBoxGT+                        { x = cocox,+                          y = cocoy,+                          w = cocow,+                          h = cocoh,+                          cls = cocoCategoryToClass coco cocoAnnotationCategory,+                          idx = index -- cocoAnnotationId+                        }+              )+              . zip [0 ..]+          )+          (Map.lookup imageId' (cocoMapCocoAnnotation coco))+    detection' =+      Vector.fromList $+        maybe+          []+          ( map+              ( \(index, CocoResult {..}) ->+                  let CoCoBoundingBox (cocox, cocoy, cocow, cocoh) = cocoResultBbox+                   in BoundingBoxDT+                        { x = cocox,+                          y = cocoy,+                          w = cocow,+                          h = cocoh,+                          cls = cocoCategoryToClass coco cocoResultCategory,+                          score = unScore cocoResultScore,+                          idx = index+                        }+              )+              . zip [0 ..]+          )+          (Map.lookup imageId' (cocoMapCocoResult coco))++data BddContext = BddContext+  { bddContextDataset :: CocoMap,+    bddContextIouThresh :: Double,+    bddContextScoreThresh :: Double,+    bddContextUseInterestArea :: Bool+  }+  deriving (Show, Eq)++instance World BddContext BoundingBoxGT where+  mAP BddContext {..} = fst $ RiskWeaver.Metric.mAP bddContextDataset (IOU bddContextIouThresh)+  ap BddContext {..} = Map.fromList $ map (\(key, value) -> (cocoCategoryToClass bddContextDataset key, value)) $ snd $ RiskWeaver.Metric.mAP bddContextDataset (IOU bddContextIouThresh)+  mF1 BddContext {..} = fst $ RiskWeaver.Metric.mF1 bddContextDataset (IOU bddContextIouThresh) (Score bddContextScoreThresh)+  f1 BddContext {..} = Map.fromList $ map (\(key, value) -> (cocoCategoryToClass bddContextDataset key, value)) $ snd $ RiskWeaver.Metric.mF1 bddContextDataset (IOU bddContextIouThresh) (Score bddContextScoreThresh)+  confusionMatrixRecall context@BddContext {..} = sortAndGroup risks+    where+      risks :: [((Class, Class), BddRisk)]+      risks = concat $ flip map (cocoMapImageIds bddContextDataset) $ \imageId' -> map getKeyValue (runReader riskForGroundTruth (toEnv context imageId'))+      getKeyValue :: BddRisk -> ((Class, Class), BddRisk)+      getKeyValue bddRisk = ((maybe Background classG bddRisk.riskGt, maybe Background classD bddRisk.riskDt), bddRisk)+  confusionMatrixPrecision context@BddContext {..} = sortAndGroup risks+    where+      risks :: [((Class, Class), BddRisk)]+      risks = concat $ flip map (cocoMapImageIds bddContextDataset) $ \imageId' -> map getKeyValue (runReader riskForDetection (toEnv context imageId'))+      getKeyValue :: BddRisk -> ((Class, Class), BddRisk)+      getKeyValue bddRisk = ((maybe Background classD bddRisk.riskDt, maybe Background classG bddRisk.riskGt), bddRisk)++  toEnv BddContext {..} imageId' =+    let (groundTruth', detection') = cocoResultToVector bddContextDataset imageId'+     in MyEnv+          { envGroundTruth = groundTruth',+            envDetection = detection',+            envConfidenceScoreThresh = bddContextScoreThresh,+            envIoUThresh = bddContextIouThresh,+            envUseInterestArea = bddContextUseInterestArea,+            envImageId = imageId'+          }++  toImageIds BddContext {..} = cocoMapImageIds bddContextDataset+++++toBddContext :: CocoMap -> Maybe Double -> Maybe Double -> Main.BddContext+toBddContext cocoMap iouThreshold scoreThresh =+  let iouThreshold'' = case iouThreshold of+        Nothing -> 0.5+        Just iouThreshold' -> iouThreshold'+      scoreThresh'' = case scoreThresh of+        Nothing -> 0.4+        Just scoreThresh' -> scoreThresh'+      context =+        Main.BddContext+          { bddContextDataset = cocoMap,+            bddContextIouThresh = iouThreshold'',+            bddContextScoreThresh = scoreThresh'',+            bddContextUseInterestArea = False+          }+   in context++showRisk :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+showRisk cocoMap iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      risks = Core.runRisk @Main.BddContext @Main.BoundingBoxGT context+  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++showRiskWithError :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+showRiskWithError cocoMap iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      risks = Core.runRiskWithError @Main.BddContext @Main.BoundingBoxGT context :: [(ImageId, [Main.BddRisk])]+  putStrLn $ printf "%-12s %-12s %-12s %-12s" "#ImageId" "Filename" "Risk" "ErrorType"+  let sum' riskWithErrors = sum $ map (\r -> r.risk) riskWithErrors+      sortedRisks = sortBy (\(_, risk1) (_, risk2) -> compare (sum' risk2) (sum' risk1)) risks+  forM_ sortedRisks $ \(imageId, risks') -> do+    let cocoImage = (cocoMapCocoImage cocoMap) Map.! imageId+    forM_ risks' $ \bddRisk -> do+      putStrLn $ printf "%-12d %-12s %.3f %-12s" (unImageId imageId) (T.unpack (cocoImageFileName cocoImage)) bddRisk.risk (show bddRisk.riskType)+++generateRiskWeightedDataset :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()+generateRiskWeightedDataset cocoMap cocoOutputFile iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      imageIds = Core.generateRiskWeightedImages @Main.BddContext @Main.BoundingBoxGT context+      (newCoco, newCocoResult) = resampleCocoMapWithImageIds cocoMap imageIds+  writeCoco cocoOutputFile newCoco+  let newCocoMap = toCocoMap newCoco newCocoResult cocoOutputFile ""+  Main.evaluate newCocoMap iouThreshold scoreThresh++green :: (Int, Int, Int)+green = (0, 255, 0)++red :: (Int, Int, Int)+red = (255, 0, 0)++black :: (Int, Int, Int)+black = (0, 0, 0)++showDetectionImage :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()+showDetectionImage cocoMap imageFile iouThreshold scoreThreshold = do+  let imageDir = getImageDir cocoMap+      imagePath = imageDir </> imageFile+  let image' = getCocoResult cocoMap imageFile+      context = toBddContext cocoMap iouThreshold scoreThreshold+  case image' of+    Nothing -> putStrLn $ "Image file " ++ imageFile ++ " is not found."+    Just (image, _) -> do+      imageBin' <- readImage imagePath+      let env = Core.toEnv @Main.BddContext @Main.BoundingBoxGT context (cocoImageId image)+          riskG = runReader Core.riskForGroundTruth env+          riskD = runReader Core.riskForDetection env+      forM_ riskG $ \riskg -> do+        putStrLn $ show riskg+      forM_ riskD $ \riskd -> do+        putStrLn $ show riskd+      case imageBin' of+        Left err -> putStrLn $ "Image file " ++ imagePath ++ " can not be read. : " ++ show err+        Right imageBin -> do+          let imageRGB8 = convertRGB8 imageBin+          groundTruthImage <- cloneImage imageRGB8+          detectionImage <- cloneImage imageRGB8+          forM_ riskG $ \Main.BddRisk {..} -> do+            case riskGt of+              Nothing -> return ()+              Just riskGt' -> do+                let annotation = env.envGroundTruth Vector.! (Core.idG riskGt')+                    (bx, by, bw, bh) = (annotation.x, annotation.y, annotation.w, annotation.h)+                    category = annotation.cls+                    x = round bx+                    y = round by+                    width = round bw+                    height = round bh+                    draw = do+                      let color = case riskType of+                            Main.TruePositive -> green+                            _ -> red+                      drawRect x y (x + width) (y + height) color groundTruthImage+                      drawString (show category) x y color black groundTruthImage+                      drawString (printf "%.2f" risk) x (y + 10) color black groundTruthImage+                      drawString (show riskType) x (y + 20) color black groundTruthImage+                -- Use printf format to show score+                -- drawString (printf "%.2f" (unScore $ riskGt.score)) x (y + 10) green black imageRGB8+                -- drawString (show $ cocoResultScore annotation)  x (y + 10) (255,0,0) (0,0,0) imageRGB8+                draw+          forM_ riskD $ \Main.BddRisk {..} -> do+            case riskDt of+              Nothing -> return ()+              Just riskDt' -> do+                let annotation = env.envDetection Vector.! Core.idD riskDt'+                    (bx, by, bw, bh) = (annotation.x, annotation.y, annotation.w, annotation.h)+                    category = annotation.cls+                    x = round bx+                    y = round by+                    width = round bw+                    height = round bh+                    draw = do+                      let color = case riskType of+                            Main.TruePositive -> green+                            _ -> red+                      drawRect x y (x + width) (y + height) color detectionImage+                      drawString (show category) x y color black detectionImage+                      drawString (printf "%.2f" (annotation.score)) x (y + 10) color black detectionImage+                      drawString (printf "%.2f" risk) x (y + 20) color black detectionImage+                      drawString (show riskType) x (y + 30) color black detectionImage+                if annotation.score >= context.bddContextScoreThresh+                  then draw+                  else return ()+          concatImage <- concatImageByHorizontal groundTruthImage detectionImage+          -- let resizedImage = resizeRGB8 groundTruthImage.imageWidth groundTruthImage.imageHeight True concatImage+          putImage (Right concatImage)++(!!!) :: forall a b. Ord b => Map.Map b [a] -> b -> [a]+(!!!) dat key = fromMaybe [] (Map.lookup key dat)++evaluate :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+evaluate cocoMap iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      mAP = Core.mAP @Main.BddContext @Main.BoundingBoxGT context+      ap' = Core.ap @Main.BddContext @Main.BoundingBoxGT context+      f1 = Core.f1 @Main.BddContext @Main.BoundingBoxGT context+      mF1 = Core.mF1 @Main.BddContext @Main.BoundingBoxGT context+      confusionMatrixR :: Map.Map (Main.Class, Main.Class) [Main.BddRisk]+      confusionMatrixR = Core.confusionMatrixRecall @Main.BddContext @Main.BoundingBoxGT context -- Metric.confusionMatrix @(Sum Int) cocoMap iouThreshold' scoreThresh'+      confusionMatrixP :: Map.Map (Main.Class, Main.Class) [Main.BddRisk]+      confusionMatrixP = Core.confusionMatrixPrecision @Main.BddContext @Main.BoundingBoxGT context -- Metric.confusionMatrix @(Sum Int) cocoMap iouThreshold' scoreThresh'+      confusionMatrixR_cnt :: Map.Map (Main.Class, Main.Class) Int+      confusionMatrixR_cnt = Map.fromList $ concat $+        flip map (cocoMapCategoryIds cocoMap) $ \categoryId ->+          let classG = Main.cocoCategoryToClass cocoMap categoryId+              keyBG = (classG, Main.Background)+              toBG = (keyBG, length $ confusionMatrixR !!! keyBG)+              toClasses =+                flip map (cocoMapCategoryIds cocoMap) $ \categoryId' ->+                  let classD = Main.cocoCategoryToClass cocoMap categoryId'+                      keyCl = (classG, classD)+                  in (keyCl, length $ confusionMatrixR !!! keyCl)+          in toBG: toClasses+      confusionMatrixP_cnt :: Map.Map (Main.Class, Main.Class) Int+      confusionMatrixP_cnt = Map.fromList $ concat $+        flip map (cocoMapCategoryIds cocoMap) $ \categoryId ->+          let classD = Main.cocoCategoryToClass cocoMap categoryId+              keyBG = (classD, Main.Background)+              toBG = (keyBG, length $ confusionMatrixP !!! keyBG)+              toClasses =+                flip map (cocoMapCategoryIds cocoMap) $ \categoryId' ->+                  let classG = Main.cocoCategoryToClass cocoMap categoryId'+                      keyCl = (classD, classG)+                  in (keyCl, length $ confusionMatrixP !!! keyCl)+          in toBG: toClasses+        +  putStrLn $ printf "#%-12s, %s" "CocoFile" cocoMap.cocoMapCocoFile+  putStrLn $ printf "#%-12s, %s" "CocoResultFile" cocoMap.cocoMapCocoResultFile++  putStrLn $ printf "%-12s, %s" "#Category" "AP"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let class' = Main.cocoCategoryToClass cocoMap categoryId+    putStrLn $ printf "%-12s, %.3f" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId))) (ap' Map.! class')+  putStrLn $ printf "%-12s, %.3f" "mAP" mAP+  putStrLn ""++  -- Print risk scores statistically+  let risks = Core.runRisk @Main.BddContext @Main.BoundingBoxGT context+  putStrLn $ printf "%-12s" "#Risk"+  let num_of_images = (length $ map snd risks)+      max_risks = (maximum $ map snd risks)+      sorted_risks = sortBy (\r1 r2 -> compare r2 r1) $ map snd risks+      percentile_90 = take (num_of_images * 10 `div` 100) sorted_risks+  putStrLn $ printf "%-12s, %.2f" "total" (sum $ map snd risks)+  putStrLn $ printf "%-12s, %.2f" "maximum" max_risks+  putStrLn $ printf "%-12s, %.2f" "average" (M.average $ map snd risks)+  putStrLn $ printf "%-12s, %.2f" "minimum" (minimum $ map snd risks)+  putStrLn $ printf "%-12s, %.2f" "90percentile" $ head $ reverse percentile_90+  putStrLn $ printf "%-12s, %d" "num_of_images" num_of_images+  putStrLn ""++  -- Print confusion matrix+  putStrLn "#confusion matrix of recall: row is ground truth, column is prediction."+  putStr $ printf "%-12s," "#GT \\ DT"+  putStr $ printf "%-12s," "Backgroud"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+  putStrLn ""+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let classG = Main.cocoCategoryToClass cocoMap categoryId+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+    putStr $ printf "%-12d," $ confusionMatrixR_cnt Map.! (classG, Main.Background)+    forM_ (cocoMapCategoryIds cocoMap) $ \categoryId' -> do+      let classD = Main.cocoCategoryToClass cocoMap categoryId'+      putStr $ printf "%-12d," $ confusionMatrixR_cnt Map.! (classG, classD)+    putStrLn ""+  putStrLn ""++  putStrLn "#confusion matrix of precision: row is prediction, column is ground truth."+  putStr $ printf "#%-11s," "DT \\ GT"+  putStr $ printf "%-12s," "Backgroud"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+  putStrLn ""+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let classD = Main.cocoCategoryToClass cocoMap categoryId+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+    putStr $ printf "%-12d," $ confusionMatrixP_cnt Map.! (classD, Main.Background)+    forM_ (cocoMapCategoryIds cocoMap) $ \categoryId' -> do+      let classG = Main.cocoCategoryToClass cocoMap categoryId'+      putStr $ printf "%-12d," $ confusionMatrixP_cnt Map.! (classD, classG)+    putStrLn ""+  putStrLn ""++  -- Print F1 scores+  putStrLn "#F1 Scores"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let class' = Main.cocoCategoryToClass cocoMap categoryId+    putStrLn $ printf "%-12s, %.3f" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId))) (f1 Map.! class')+  putStrLn $ printf "%-12s, %.3f" "mF1" mF1+  putStrLn ""+  putStrLn ""++bddCommand :: RiskCommands+bddCommand =+  RiskCommands+    { showRisk = Main.showRisk,+      showRiskWithError = Main.showRiskWithError,+      generateRiskWeightedDataset = Main.generateRiskWeightedDataset,+      showDetectionImage = Main.showDetectionImage,+      evaluate = Main.evaluate+    }+main = baseMain bddCommand
risk-weaver.cabal view
@@ -8,7 +8,7 @@ -- PVP summary:     +-+------- breaking API changes --                  | | +----- non-breaking API additions --                  | | | +--- code changes with no API change-version:            0.1.0.1+version:            0.1.0.2  synopsis: A DSL for Risk-oriented Object Detection Requirements description: Please see the README on GitHub at <https://github.com/jst-qaml/risk-weaver#readme>@@ -38,6 +38,7 @@                     , RiskWeaver.Draw                     , RiskWeaver.Display                     , RiskWeaver.Metric+                    , RiskWeaver.Pip                     , RiskWeaver.Cmd.Core                     , RiskWeaver.Cmd.BDD     build-depends:    base == 4.*@@ -55,9 +56,12 @@                     , text >= 2.0.2 && < 2.1                     , transformers >= 0.6.1 && < 0.7                     , vector >= 0.13.1 && < 0.14+                    , parallel >= 3.2.1 && < 3.3+                    , deepseq >= 1.4.8 && < 1.5+                    , async >= 2.2.1 && < 2.3     hs-source-dirs:   src     default-language: GHC2021-+    default-extensions:  StrictData executable risk-weaver-exe     import:           warnings     main-is:          Main.hs@@ -65,7 +69,26 @@                     , risk-weaver     hs-source-dirs:   app     default-language: GHC2021+    ghc-options:      -rtsopts -threaded "-with-rtsopts=-A1g -N2"+    default-extensions:  StrictData +executable sample-risk+    import:           warnings+    main-is:          sample-risk.hs+    build-depends:    base == 4.*+                    , risk-weaver+                    , vector+                    , JuicyPixels+                    , text+                    , transformers+                    , containers+                    , filepath+                    , parallel+    hs-source-dirs:   bin+    default-language: GHC2021+    ghc-options:      -rtsopts -threaded "-with-rtsopts=-A1g -N2"+    default-extensions:  StrictData+        test-suite risk-weaver-test     import:           warnings
src/RiskWeaver/Cmd/BDD.hs view
@@ -1,182 +1,273 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-}  module RiskWeaver.Cmd.BDD where +import Codec.Picture 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 Control.Monad.Trans.Reader (runReader) import Data.List (sortBy) import Data.Map qualified as Map+import Data.Maybe (fromMaybe) import Data.Text qualified as T-import Data.Vector (Vector) import Data.Vector qualified as Vector+import RiskWeaver.Cmd.Core (RiskCommands (..))+import RiskWeaver.DSL.BDD qualified as BDD+import RiskWeaver.DSL.Core qualified as Core+import RiskWeaver.Display (putImage)+import RiskWeaver.Draw+import RiskWeaver.Metric qualified as M import RiskWeaver.Format.Coco-import RiskWeaver.Metric--import Options.Applicative-import System.Random+import System.FilePath ((</>)) 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)+toBddContext :: CocoMap -> Maybe Double -> Maybe Double -> BDD.BddContext+toBddContext cocoMap iouThreshold scoreThresh =+  let iouThreshold'' = case iouThreshold of+        Nothing -> 0.5+        Just iouThreshold' -> iouThreshold'+      scoreThresh'' = case scoreThresh of+        Nothing -> 0.4+        Just scoreThresh' -> scoreThresh'+      context =+        BDD.BddContext+          { bddContextDataset = cocoMap,+            bddContextIouThresh = iouThreshold'',+            bddContextScoreThresh = scoreThresh'',+            bddContextUseInterestArea = False+          }+   in context -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+showRisk :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+showRisk cocoMap iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      risks = Core.runRisk @BDD.BddContext @BDD.BoundingBoxGT context   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+showRiskWithError :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+showRiskWithError cocoMap iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      risks = Core.runRiskWithError @BDD.BddContext @BDD.BoundingBoxGT context :: [(ImageId, [BDD.BddRisk])]+  putStrLn $ printf "%-12s %-12s %-12s %-12s" "#ImageId" "Filename" "Risk" "ErrorType"+  let sum' riskWithErrors = sum $ map (\r -> r.risk) riskWithErrors+      sortedRisks = sortBy (\(_, risk1) (_, risk2) -> compare (sum' risk2) (sum' risk1)) risks+  forM_ sortedRisks $ \(imageId, risks') -> do+    let cocoImage = (cocoMapCocoImage cocoMap) Map.! imageId+    forM_ risks' $ \bddRisk -> do+      putStrLn $ printf "%-12d %-12s %.3f %-12s" (unImageId imageId) (T.unpack (cocoImageFileName cocoImage)) bddRisk.risk (show bddRisk.riskType) -  -- 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-          }+generateRiskWeightedDataset :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()+generateRiskWeightedDataset cocoMap cocoOutputFile iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      imageIds = Core.generateRiskWeightedImages @BDD.BddContext @BDD.BoundingBoxGT context+      (newCoco, newCocoResult) = resampleCocoMapWithImageIds cocoMap imageIds   writeCoco cocoOutputFile newCoco+  let newCocoMap = toCocoMap newCoco newCocoResult cocoOutputFile ""+  RiskWeaver.Cmd.BDD.evaluate newCocoMap iouThreshold scoreThresh +green :: (Int, Int, Int)+green = (0, 255, 0) +red :: (Int, Int, Int)+red = (255, 0, 0)++black :: (Int, Int, Int)+black = (0, 0, 0)++showDetectionImage :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO ()+showDetectionImage cocoMap imageFile iouThreshold scoreThreshold = do+  let imageDir = getImageDir cocoMap+      imagePath = imageDir </> imageFile+  let image' = getCocoResult cocoMap imageFile+      context = toBddContext cocoMap iouThreshold scoreThreshold+  case image' of+    Nothing -> putStrLn $ "Image file " ++ imageFile ++ " is not found."+    Just (image, _) -> do+      imageBin' <- readImage imagePath+      let env = Core.toEnv @BDD.BddContext @BDD.BoundingBoxGT context (cocoImageId image)+          riskG = runReader Core.riskForGroundTruth env+          riskD = runReader Core.riskForDetection env+      forM_ riskG $ \riskg -> do+        putStrLn $ show riskg+      forM_ riskD $ \riskd -> do+        putStrLn $ show riskd+      case imageBin' of+        Left err -> putStrLn $ "Image file " ++ imagePath ++ " can not be read. : " ++ show err+        Right imageBin -> do+          let imageRGB8 = convertRGB8 imageBin+          groundTruthImage <- cloneImage imageRGB8+          detectionImage <- cloneImage imageRGB8+          forM_ riskG $ \BDD.BddRisk {..} -> do+            case riskGt of+              Nothing -> return ()+              Just riskGt' -> do+                let annotation = env.envGroundTruth Vector.! (Core.idG riskGt')+                    (bx, by, bw, bh) = (annotation.x, annotation.y, annotation.w, annotation.h)+                    category = annotation.cls+                    x = round bx+                    y = round by+                    width = round bw+                    height = round bh+                    draw = do+                      let color = case riskType of+                            BDD.TruePositive -> green+                            _ -> red+                      drawRect x y (x + width) (y + height) color groundTruthImage+                      drawString (show category) x y color black groundTruthImage+                      drawString (printf "%.2f" risk) x (y + 10) color black groundTruthImage+                      drawString (show riskType) x (y + 20) color black groundTruthImage+                -- Use printf format to show score+                -- drawString (printf "%.2f" (unScore $ riskGt.score)) x (y + 10) green black imageRGB8+                -- drawString (show $ cocoResultScore annotation)  x (y + 10) (255,0,0) (0,0,0) imageRGB8+                draw+          forM_ riskD $ \BDD.BddRisk {..} -> do+            case riskDt of+              Nothing -> return ()+              Just riskDt' -> do+                let annotation = env.envDetection Vector.! Core.idD riskDt'+                    (bx, by, bw, bh) = (annotation.x, annotation.y, annotation.w, annotation.h)+                    category = annotation.cls+                    x = round bx+                    y = round by+                    width = round bw+                    height = round bh+                    draw = do+                      let color = case riskType of+                            BDD.TruePositive -> green+                            _ -> red+                      drawRect x y (x + width) (y + height) color detectionImage+                      drawString (show category) x y color black detectionImage+                      drawString (printf "%.2f" (annotation.score)) x (y + 10) color black detectionImage+                      drawString (printf "%.2f" risk) x (y + 20) color black detectionImage+                      drawString (show riskType) x (y + 30) color black detectionImage+                if annotation.score >= context.bddContextScoreThresh+                  then draw+                  else return ()+          concatImage <- concatImageByHorizontal groundTruthImage detectionImage+          -- let resizedImage = resizeRGB8 groundTruthImage.imageWidth groundTruthImage.imageHeight True concatImage+          putImage (Right concatImage)++(!!!) :: forall a b. Ord b => Map.Map b [a] -> b -> [a]+(!!!) dat key = fromMaybe [] (Map.lookup key dat)++evaluate :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+evaluate cocoMap iouThreshold scoreThresh = do+  let context = toBddContext cocoMap iouThreshold scoreThresh+      mAP = Core.mAP @BDD.BddContext @BDD.BoundingBoxGT context+      ap' = Core.ap @BDD.BddContext @BDD.BoundingBoxGT context+      f1 = Core.f1 @BDD.BddContext @BDD.BoundingBoxGT context+      mF1 = Core.mF1 @BDD.BddContext @BDD.BoundingBoxGT context+      confusionMatrixR :: Map.Map (BDD.Class, BDD.Class) [BDD.BddRisk]+      confusionMatrixR = Core.confusionMatrixRecall @BDD.BddContext @BDD.BoundingBoxGT context -- Metric.confusionMatrix @(Sum Int) cocoMap iouThreshold' scoreThresh'+      confusionMatrixP :: Map.Map (BDD.Class, BDD.Class) [BDD.BddRisk]+      confusionMatrixP = Core.confusionMatrixPrecision @BDD.BddContext @BDD.BoundingBoxGT context -- Metric.confusionMatrix @(Sum Int) cocoMap iouThreshold' scoreThresh'+      confusionMatrixR_cnt :: Map.Map (BDD.Class, BDD.Class) Int+      confusionMatrixR_cnt = Map.fromList $ concat $+        flip map (cocoMapCategoryIds cocoMap) $ \categoryId ->+          let classG = BDD.cocoCategoryToClass cocoMap categoryId+              keyBG = (classG, BDD.Background)+              toBG = (keyBG, length $ confusionMatrixR !!! keyBG)+              toClasses =+                flip map (cocoMapCategoryIds cocoMap) $ \categoryId' ->+                  let classD = BDD.cocoCategoryToClass cocoMap categoryId'+                      keyCl = (classG, classD)+                  in (keyCl, length $ confusionMatrixR !!! keyCl)+          in toBG: toClasses+      confusionMatrixP_cnt :: Map.Map (BDD.Class, BDD.Class) Int+      confusionMatrixP_cnt = Map.fromList $ concat $+        flip map (cocoMapCategoryIds cocoMap) $ \categoryId ->+          let classD = BDD.cocoCategoryToClass cocoMap categoryId+              keyBG = (classD, BDD.Background)+              toBG = (keyBG, length $ confusionMatrixP !!! keyBG)+              toClasses =+                flip map (cocoMapCategoryIds cocoMap) $ \categoryId' ->+                  let classG = BDD.cocoCategoryToClass cocoMap categoryId'+                      keyCl = (classD, classG)+                  in (keyCl, length $ confusionMatrixP !!! keyCl)+          in toBG: toClasses+        +  putStrLn $ printf "#%-12s, %s" "CocoFile" cocoMap.cocoMapCocoFile+  putStrLn $ printf "#%-12s, %s" "CocoResultFile" cocoMap.cocoMapCocoResultFile++  putStrLn $ printf "%-12s, %s" "#Category" "AP"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let class' = BDD.cocoCategoryToClass cocoMap categoryId+    putStrLn $ printf "%-12s, %.3f" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId))) (ap' Map.! class')+  putStrLn $ printf "%-12s, %.3f" "mAP" mAP+  putStrLn ""++  -- Print risk scores statistically+  let risks = Core.runRisk @BDD.BddContext @BDD.BoundingBoxGT context+  putStrLn $ printf "%-12s" "#Risk"+  let num_of_images = (length $ map snd risks)+      max_risks = (maximum $ map snd risks)+      sorted_risks = sortBy (\r1 r2 -> compare r2 r1) $ map snd risks+      percentile_90 = take (num_of_images * 10 `div` 100) sorted_risks+  putStrLn $ printf "%-12s, %.2f" "total" (sum $ map snd risks)+  putStrLn $ printf "%-12s, %.2f" "maximum" max_risks+  putStrLn $ printf "%-12s, %.2f" "average" (M.average $ map snd risks)+  putStrLn $ printf "%-12s, %.2f" "minimum" (minimum $ map snd risks)+  putStrLn $ printf "%-12s, %.2f" "90percentile" $ head $ reverse percentile_90+  putStrLn $ printf "%-12s, %d" "num_of_images" num_of_images+  putStrLn ""++  -- Print confusion matrix+  putStrLn "#confusion matrix of recall: row is ground truth, column is prediction."+  putStr $ printf "%-12s," "#GT \\ DT"+  putStr $ printf "%-12s," "Backgroud"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+  putStrLn ""+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let classG = BDD.cocoCategoryToClass cocoMap categoryId+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+    putStr $ printf "%-12d," $ confusionMatrixR_cnt Map.! (classG, BDD.Background)+    forM_ (cocoMapCategoryIds cocoMap) $ \categoryId' -> do+      let classD = BDD.cocoCategoryToClass cocoMap categoryId'+      putStr $ printf "%-12d," $ confusionMatrixR_cnt Map.! (classG, classD)+    putStrLn ""+  putStrLn ""++  putStrLn "#confusion matrix of precision: row is prediction, column is ground truth."+  putStr $ printf "#%-11s," "DT \\ GT"+  putStr $ printf "%-12s," "Backgroud"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+  putStrLn ""+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let classD = BDD.cocoCategoryToClass cocoMap categoryId+    putStr $ printf "%-12s," (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))+    putStr $ printf "%-12d," $ confusionMatrixP_cnt Map.! (classD, BDD.Background)+    forM_ (cocoMapCategoryIds cocoMap) $ \categoryId' -> do+      let classG = BDD.cocoCategoryToClass cocoMap categoryId'+      putStr $ printf "%-12d," $ confusionMatrixP_cnt Map.! (classD, classG)+    putStrLn ""+  putStrLn ""++  -- Print F1 scores+  putStrLn "#F1 Scores"+  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do+    let class' = BDD.cocoCategoryToClass cocoMap categoryId+    putStrLn $ printf "%-12s, %.3f" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId))) (f1 Map.! class')+  putStrLn $ printf "%-12s, %.3f" "mF1" mF1+  putStrLn ""+  putStrLn ""+ bddCommand :: RiskCommands-bddCommand = RiskCommands {-  showRisk = RiskWeaver.Cmd.BDD.showRisk,-  generateRiskWeightedDataset = RiskWeaver.Cmd.BDD.generateRiskWeightedDataset-}+bddCommand =+  RiskCommands+    { showRisk = RiskWeaver.Cmd.BDD.showRisk,+      showRiskWithError = RiskWeaver.Cmd.BDD.showRiskWithError,+      generateRiskWeightedDataset = RiskWeaver.Cmd.BDD.generateRiskWeightedDataset,+      showDetectionImage = RiskWeaver.Cmd.BDD.showDetectionImage,+      evaluate = RiskWeaver.Cmd.BDD.evaluate+    }
src/RiskWeaver/Cmd/Core.hs view
@@ -1,27 +1,19 @@+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}  module RiskWeaver.Cmd.Core where  import Control.Monad-import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)-import RiskWeaver.DSL.Core qualified as DSL import Data.ByteString qualified as BS import Data.FileEmbed (embedFile)-import Data.List (sortBy)-import Data.Map qualified as Map-import Data.Maybe import Data.Text qualified as T-import Data.Vector (Vector)-import Data.Vector qualified as Vector+import Options.Applicative import RiskWeaver.Display import RiskWeaver.Format.Coco-import qualified RiskWeaver.Metric as Metric-import RiskWeaver.Metric--import Options.Applicative-import System.Random-import Text.Printf+import Data.Text qualified as T+import Data.Text.Encoding qualified as T  data CocoCommand   = ListImages {cocoFile :: FilePath}@@ -37,28 +29,26 @@       { cocoFile :: FilePath,         cocoResultFile :: FilePath,         imageFile :: FilePath,+        iouThreshold :: Maybe Double,         scoreThreshold :: Maybe Double       }   | Evaluate       { cocoFile :: FilePath,         cocoResultFile :: FilePath,         iouThreshold :: Maybe Double,-        scoreThreshold :: Maybe Double,-        imageId :: Maybe Int+        scoreThreshold :: Maybe Double       }-  | ShowFalseNegative+  | ShowRisk       { cocoFile :: FilePath,         cocoResultFile :: FilePath,         iouThreshold :: Maybe Double,-        scoreThreshold :: Maybe Double,-        imageId :: Maybe Int+        scoreThreshold :: Maybe Double       }-  | ShowRisk+  | ShowRiskWithError       { cocoFile :: FilePath,         cocoResultFile :: FilePath,         iouThreshold :: Maybe Double,-        scoreThreshold :: Maybe Double,-        imageId :: Maybe Int+        scoreThreshold :: Maybe Double       }   | GenerateRiskWeightedDataset       { cocoFile :: FilePath,@@ -68,13 +58,16 @@         scoreThreshold :: Maybe Double       }   | BashCompletion+  | GenerateTemplate   deriving (Show, Eq) -data RiskCommands = -  RiskCommands-    { showRisk :: Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()-    , generateRiskWeightedDataset :: Coco -> [CocoResult] -> FilePath -> Maybe Double -> Maybe Double -> IO ()-    }+data RiskCommands = RiskCommands+  { showRisk :: CocoMap -> Maybe Double -> Maybe Double -> IO (),+    showRiskWithError :: CocoMap -> Maybe Double -> Maybe Double -> IO (),+    generateRiskWeightedDataset :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO (),+    showDetectionImage :: CocoMap -> FilePath -> Maybe Double -> Maybe Double -> IO (),+    evaluate :: CocoMap -> Maybe Double -> Maybe Double -> IO ()+  }  listImages :: Coco -> IO () listImages coco = do@@ -124,58 +117,6 @@   forM_ cocoResults $ \cocoResult -> do     putStrLn $ show (cocoResultImageId cocoResult) ++ "\t" ++ show (cocoResultCategory cocoResult) ++ "\t" ++ show (cocoResultScore cocoResult) ++ "\t" ++ show (cocoResultBbox cocoResult) -evaluate :: Coco -> [CocoResult] -> Maybe Double -> Maybe Double -> Maybe ImageId -> IO ()-evaluate coco cocoResults iouThreshold scoreThresh mImageId = do-  -- Print mAP-  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.1-        Just scoreThresh -> Score scoreThresh-      mAP = Metric.mAP cocoMap iouThreshold'-      confusionMatrix = Metric.confusionMatrix cocoMap iouThreshold' scoreThresh'-  putStrLn $ printf "%-12s %s" "#Category" "AP"-  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do-    putStrLn $ printf "%-12s %.3f" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId))) ((Map.fromList (snd mAP)) Map.! categoryId)-  putStrLn $ printf "%-12s %.3f" "mAP" (fst mAP)-  putStrLn ""--  -- Print confusion matrix-  putStrLn "#confusion matrix of recall: row is ground truth, column is prediction."-  putStr $ printf "%-12s" "#GT \\ DT"-  putStr $ printf "%-12s" "Backgroud"-  let (!!) dat key = fromMaybe 0 (Map.lookup key dat)-      (!!!) dat key = fromMaybe Map.empty (Map.lookup key dat)-  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do-    putStr $ printf "%-12s" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))-  putStrLn ""-  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do-    putStr $ printf "%-12s" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))-    putStr $ printf "%-12d" (((confusionMatrixRecall confusionMatrix) !!! Gt categoryId) !! DtBackground)-    forM_ (cocoMapCategoryIds cocoMap) $ \categoryId' -> do-      putStr $ printf "%-12d" (((confusionMatrixRecall confusionMatrix) !!! Gt categoryId) !! (Dt categoryId'))-    putStrLn ""-  putStrLn ""--  putStrLn "#confusion matrix of precision: row is prediction, column is ground truth."-  putStr $ printf "#%-11s" "DT \\ GT"-  putStr $ printf "%-12s" "Backgroud"-  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do-    putStr $ printf "%-12s" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))-  putStrLn ""-  forM_ (cocoMapCategoryIds cocoMap) $ \categoryId -> do-    putStr $ printf "%-12s" (T.unpack (cocoCategoryName ((cocoMapCocoCategory cocoMap) Map.! categoryId)))-    putStr $ printf "%-12d" (((confusionMatrixPrecision confusionMatrix) !!! (Dt categoryId)) !! GtBackground)-    forM_ (cocoMapCategoryIds cocoMap) $ \categoryId' -> do-      putStr $ printf "%-12d" (((confusionMatrixPrecision confusionMatrix) !!! (Dt categoryId)) !! (Gt categoryId'))-    putStrLn ""- bashCompletion :: IO () bashCompletion = do   -- Read from bash_completion.d/risk-weaver-exe and write to stdout@@ -183,6 +124,31 @@   let file = $(embedFile "bash_completion.d/risk-weaver-exe")   BS.putStr file +-- | Generate template codes to define own risk environment from BDD.+generateTemplate :: IO ()+generateTemplate = do+  -- Read from bash_completion.d/risk-weaver-exe and write to stdout+  -- Inline the file content by tepmlate haskell+  let orgDslFile = $(embedFile "src/RiskWeaver/DSL/BDD.hs")+      orgCmdFile = $(embedFile "src/RiskWeaver/Cmd/BDD.hs")+      mainLine = "main = baseMain bddCommand\n"+      mergedFile = orgDslFile <> orgCmdFile <> mainLine+      extraceLangExtFromMergedFile = -- Extract all '{-# LANGUAGE .. #-}' lines+        let langExts = T.unlines $ filter (T.isPrefixOf "{-# LANGUAGE") $ T.lines $ T.decodeUtf8 mergedFile+         in T.encodeUtf8 langExts+      extractImportLines = -- Extract all 'import ..' lines+        let importLines = T.unlines $ filter (T.isPrefixOf "import") $ T.lines $ T.decodeUtf8 mergedFile+         in T.encodeUtf8 importLines+      removeModuleAndLangExtAndImport = -- Remove module and language extension and import lines+        let removedModule = T.unlines $ filter (not . T.isPrefixOf "module") $ T.lines $ T.decodeUtf8 mergedFile+            removedLangExt = T.unlines $ filter (not . T.isPrefixOf "{-# LANGUAGE") $ T.lines removedModule+            removedImport = T.unlines $ filter (not . T.isPrefixOf "import") $ T.lines removedLangExt+         in T.encodeUtf8 removedImport+      output = -- Concat all of them(extraceLangExtFromMergedFile, extractImportLines, removeModuleAndLangExtAndImport)+        extraceLangExtFromMergedFile <> "\n" <> extractImportLines <> "\n" <> removeModuleAndLangExtAndImport+  BS.putStr output++ opts :: Parser CocoCommand opts =   subparser@@ -191,18 +157,20 @@         <> command "list-annotations" (info (ListAnnotations <$> argument str (metavar "FILE")) (progDesc "list all annotations of coco file"))         <> command "list-coco-result" (info (ListCocoResult <$> argument str (metavar "FILE")) (progDesc "list all coco result"))         <> command "show-image" (info (ShowImage <$> argument str (metavar "FILE") <*> argument str (metavar "IMAGE_FILE") <*> switch (long "enable-bounding-box" <> short 'b' <> help "enable bounding box")) (progDesc "show image by sixel"))-        <> command "show-detection-image" (info (ShowDetectionImage <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> argument str (metavar "IMAGE_FILE") <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold"))) (progDesc "show detection image by sixel"))-        <> command "evaluate" (info (Evaluate <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold")) <*> optional (option auto (long "filter" <> short 'e' <> help "filter with regex"))) (progDesc "evaluate coco result"))-        <> command "show-risk" (info (ShowRisk <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold")) <*> optional (option auto (long "filter" <> short 'e' <> help "filter with regex"))) (progDesc "show risk"))+        <> command "show-detection-image" (info (ShowDetectionImage <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> argument str (metavar "IMAGE_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold"))) (progDesc "show detection image by sixel"))+        <> command "evaluate" (info (Evaluate <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold"))) (progDesc "evaluate coco result"))+        <> command "show-risk" (info (ShowRisk <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold"))) (progDesc "show risk"))+        <> command "show-risk-with-error" (info (ShowRiskWithError <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold"))) (progDesc "show risk with error"))         <> command "generate-risk-weighted-dataset" (info (GenerateRiskWeightedDataset <$> argument str (metavar "FILE") <*> argument str (metavar "RESULT_FILE") <*> argument str (metavar "OUTPUT_FILE") <*> optional (option auto (long "iou-threshold" <> short 'i' <> help "iou threshold")) <*> optional (option auto (long "score-threshold" <> short 's' <> help "score threshold"))) (progDesc "generate risk weighted dataset"))         <> command "bash-completion" (info (pure BashCompletion) (progDesc "bash completion"))+        <> command "generate-template" (info (pure GenerateTemplate) (progDesc "generate template"))     )  baseMain :: RiskCommands -> IO ()-baseMain RiskCommands{..} = do-  cmd <- customExecParser (prefs showHelpOnEmpty) (info (helper <*> opts) (fullDesc <> progDesc "coco command line tool"))+baseMain hook = do+  parsedCommand <- customExecParser (prefs showHelpOnEmpty) (info (helper <*> opts) (fullDesc <> progDesc "coco command line tool")) -  case cmd of+  case parsedCommand of     BashCompletion -> bashCompletion     ListImages cocoFile -> do       coco <- readCoco cocoFile@@ -219,19 +187,19 @@     ShowImage cocoFile imageFile enableBoundingBox -> do       coco <- readCoco cocoFile       showImage coco cocoFile imageFile enableBoundingBox-    ShowDetectionImage cocoFile cocoResultFile imageFile scoreThreshold -> do-      coco <- readCoco cocoFile-      showDetectionImage coco cocoFile cocoResultFile imageFile scoreThreshold-    Evaluate cocoFile cocoResultFile iouThreshold scoreThreshold imageId -> do-      coco <- readCoco cocoFile-      cocoResult <- readCocoResult cocoResultFile-      evaluate coco cocoResult iouThreshold scoreThreshold (fmap ImageId imageId)-    ShowRisk cocoFile cocoResultFile iouThreshold scoreThreshold imageId -> do-      coco <- readCoco cocoFile-      cocoResult <- readCocoResult cocoResultFile-      showRisk coco cocoResult iouThreshold scoreThreshold (fmap ImageId imageId)+    ShowDetectionImage cocoFile cocoResultFile imageFile iouThreshold scoreThreshold -> do+      cocoMap <- readCocoMap cocoFile cocoResultFile+      hook.showDetectionImage cocoMap imageFile iouThreshold scoreThreshold+    Evaluate cocoFile cocoResultFile iouThreshold scoreThreshold -> do+      cocoMap <- readCocoMap cocoFile cocoResultFile+      hook.evaluate cocoMap iouThreshold scoreThreshold+    ShowRisk cocoFile cocoResultFile iouThreshold scoreThreshold -> do+      cocoMap <- readCocoMap cocoFile cocoResultFile+      hook.showRisk cocoMap iouThreshold scoreThreshold+    ShowRiskWithError cocoFile cocoResultFile iouThreshold scoreThreshold -> do+      cocoMap <- readCocoMap cocoFile cocoResultFile+      hook.showRiskWithError cocoMap iouThreshold scoreThreshold     GenerateRiskWeightedDataset cocoFile cocoResultFile cocoOutputFile iouThreshold scoreThreshold -> do-      coco <- readCoco cocoFile-      cocoResult <- readCocoResult cocoResultFile-      generateRiskWeightedDataset coco cocoResult cocoOutputFile iouThreshold scoreThreshold-    _ -> return ()+      cocoMap <- readCocoMap cocoFile cocoResultFile+      hook.generateRiskWeightedDataset cocoMap cocoOutputFile iouThreshold scoreThreshold+    GenerateTemplate -> generateTemplate
src/RiskWeaver/DSL/BDD.hs view
@@ -1,5 +1,10 @@ {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -8,18 +13,19 @@  module RiskWeaver.DSL.BDD where -import Control.Monad (mapM)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Control.Monad.Trans.Reader (ReaderT, ask, runReader, runReaderT)-import RiskWeaver.DSL.Core-import Data.List qualified as List-import Data.Map (Map)-import Data.Maybe (Maybe)+import Control.Monad.Trans.Reader (ReaderT, ask, runReader)+import Control.Parallel.Strategies+import Data.Map qualified as Map import Data.Set (Set) import Data.Set qualified as Set+import Data.Text qualified as T import Data.Vector (Vector) import Data.Vector qualified as Vector+import GHC.Generics+import RiskWeaver.DSL.Core import RiskWeaver.Format.Coco+import RiskWeaver.Metric+import RiskWeaver.Pip  data BoundingBoxGT = BoundingBoxGT   { x :: Double,@@ -29,18 +35,7 @@     cls :: Class,     idx :: Int   }-  deriving (Show, Eq)--data BoundingBoxDT = BoundingBoxDT-  { x :: Double,-    y :: Double,-    w :: Double,-    h :: Double,-    cls :: Class,-    score :: Double,-    idx :: Int-  }-  deriving (Show, Eq)+  deriving (Show, Eq, Ord, Generic, NFData)  data Class   = Background@@ -52,55 +47,81 @@   | Train   | Motorcycle   | Bicycle-  deriving (Show, Eq)+  deriving (Show, Eq, Ord, Generic, NFData) -data FNError+data SubErrorType   = Boundary   | LowScore   | MissClass   | Occulusion-  deriving (Show, Eq)+  deriving (Show, Ord, Eq, Generic, NFData) +type BoundingBoxDT = Detection BoundingBoxGT++instance Rectangle BoundingBoxGT where+  rX b = b.x+  rY b = b.y+  rW b = b.w+  rH b = b.h+  +instance Rectangle (Detection BoundingBoxGT) where+  rX b = b.x+  rY b = b.y+  rW b = b.w+  rH b = b.h+   instance BoundingBox BoundingBoxGT where-  type Detection _ = BoundingBoxDT+  data Detection _ = BoundingBoxDT+    { x :: Double,+      y :: Double,+      w :: Double,+      h :: Double,+      cls :: Class,+      score :: Double,+      idx :: Int+    }+    deriving (Show, Eq, Ord, Generic, NFData)   type ClassG _ = Class   type ClassD _ = Class   data ErrorType _-    = FalsePositive-    | FalseNegative (Set FNError)+    = FalsePositive (Set SubErrorType)+    | FalseNegative (Set SubErrorType)     | TruePositive     | TrueNegative-    deriving (Show, Eq)+    deriving (Ord, Eq, Generic, NFData)   type InterestArea _ = [(Double, Double)]-  type InterestObject _ = BoundingBoxGT+  type InterestObject _ = Either BoundingBoxGT BoundingBoxDT -> Bool   data Env _ = MyEnv     { envGroundTruth :: Vector BoundingBoxGT,       envDetection :: Vector BoundingBoxDT,       envConfidenceScoreThresh :: Double,-      envIoUThresh :: Double+      envIoUThresh :: Double,+      envUseInterestArea :: Bool,+      envImageId :: ImageId     }+    deriving (Show, Ord, Eq, Generic, NFData)   type Idx _ = Int-  type Risk _ = Double+  type ImgIdx _ = ImageId+  data Risk _ = BddRisk+    { riskType :: ErrorType BoundingBoxGT,+      risk :: Double,+      riskGt :: Maybe BoundingBoxGT,+      riskDt :: Maybe (Detection BoundingBoxGT)+    } deriving (Show, Ord, Eq, Generic, NFData) -  riskE env = runReader (myRisk @BoundingBoxGT) env   interestArea :: Env BoundingBoxGT -> InterestArea BoundingBoxGT   interestArea _ = [(0, 1), (0.3, 0.6), (0.7, 0.6), (1, 1), (1, 2), (0, 2)]-  interestObject _ = undefined+  interestObject _ = \case+    Left gt -> gt.w * gt.h > 1000+    Right dt -> dt.w * dt.h > 1000   groundTruth env = envGroundTruth env   detection env = envDetection env   confidenceScoreThresh env = envConfidenceScoreThresh env   ioUThresh env = envIoUThresh env   scoreD v = v.score-  sizeD v = v.w * v.h   classD v = v.cls   idD v = v.idx--  isFrontD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool-  isFrontD dtBack dtFront =-    let intersection =-          (min (dtBack.x + dtBack.w) (dtFront.x + dtFront.w) - max dtBack.x dtFront.x)-            * (min (dtBack.y + dtBack.h) (dtFront.y + dtFront.h) - max dtBack.y dtFront.y)-     in (intersection / (dtFront.w * dtFront.h)) >= 0.99+  imageId env = envImageId env    isBackD :: Detection BoundingBoxGT -> Detection BoundingBoxGT -> Bool   isBackD _ _ = undefined@@ -116,78 +137,177 @@   isBackGroundD :: ClassD BoundingBoxGT -> Bool   isBackGroundD Background = True   isBackGroundD _ = False-  detectD :: Env BoundingBoxGT -> Detection BoundingBoxGT -> Maybe BoundingBoxGT-  detectD _ _ = undefined-  errorType :: Env BoundingBoxGT -> Detection BoundingBoxGT -> Maybe (ErrorType BoundingBoxGT)-  errorType _ _ = undefined+  toErrorType = riskType+  toRiskScore = RiskWeaver.DSL.BDD.risk -  sizeG v = v.w * v.h   classG v = v.cls   angle _ _ = undefined   idG v = v.idx-  ioU g d =-    let intersection =-          (min (g.x + g.w) (d.x + d.w) - max g.x d.x)-            * (min (g.y + g.h) (d.y + d.h) - max g.y d.y)-     in intersection / (g.w * g.h + d.w * d.h - intersection)-  ioG g d =-    let intersection =-          (min (g.x + g.w) (d.x + d.w) - max g.x d.x)-            * (min (g.y + g.h) (d.y + d.h) - max g.y d.y)-     in intersection / (g.w * g.h)-  ioD g d =-    let intersection =-          (min (g.x + g.w) (d.x + d.w) - max g.x d.x)-            * (min (g.y + g.h) (d.y + d.h) - max g.y d.y)-     in intersection / (d.w * d.h)-  detectG :: Env BoundingBoxGT -> BoundingBoxGT -> Maybe (Detection BoundingBoxGT)-  detectG env gt =-    let dts = detection env-        dts' = filter (\dt -> scoreD @BoundingBoxGT dt > confidenceScoreThresh env) $ Vector.toList dts-        dts'' = filter (\dt -> classD @BoundingBoxGT dt == classG @BoundingBoxGT 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    isInIeterestAreaD :: InterestArea BoundingBoxGT -> Detection BoundingBoxGT -> Bool-  isInIeterestAreaD _ _ = undefined-  isInIeterestAreaG _ _ = undefined+  isInIeterestAreaD polygon dt = pointInPolygon (Polygon polygon) (Point (dt.x, dt.y))+  isInIeterestAreaG :: InterestArea BoundingBoxGT -> BoundingBoxGT -> Bool+  isInIeterestAreaG polygon gt = pointInPolygon (Polygon polygon) (Point (gt.x, gt.y))+  isInterestObjectD :: InterestObject BoundingBoxGT -> Detection BoundingBoxGT -> Bool+  isInterestObjectD fn dt = fn $ Right dt+  isInterestObjectG :: InterestObject BoundingBoxGT -> BoundingBoxGT -> Bool+  isInterestObjectG fn gt = fn $ Left gt -  riskD _ _ = undefined-  riskBB _ = undefined+  riskForGroundTruth :: forall m. (Monad m) => ReaderT (Env BoundingBoxGT) m [Risk BoundingBoxGT]+  riskForGroundTruth = do+    env <- ask+    loopG (++) [] $ \(gt :: a) ->+      whenInterestAreaG (envUseInterestArea env) gt $ do+        let riskBias = if classG @BoundingBoxGT gt == Pedestrian then 10 else 1+        case detectG env gt of+          Just (dt :: Detection a) ->+            return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = 0.0001, riskType = TruePositive}]+          Nothing -> do+            case detectMaxIouG env gt of+              Nothing -> return [BddRisk {riskGt = Just gt, riskDt = Nothing, risk = riskBias * 30, riskType = FalseNegative []}]+              Just (dt :: Detection a) -> do+                case ( classD @BoundingBoxGT dt == classG @BoundingBoxGT gt,+                       scoreD @BoundingBoxGT dt > confidenceScoreThresh env,+                       ioU gt dt > ioUThresh env,+                       ioG gt dt > ioUThresh env+                     ) of+                  (False, False, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5.1, riskType = FalseNegative [MissClass, LowScore, Occulusion]}]+                  (False, False, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5, riskType = FalseNegative [MissClass, LowScore]}]+                  (False, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5.1, riskType = FalseNegative [MissClass, Occulusion]}]+                  (False, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 2, riskType = FalseNegative [MissClass]}]+                  (True, False, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5.1, riskType = FalseNegative [LowScore, Occulusion]}]+                  (True, False, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 5, riskType = FalseNegative [LowScore]}]+                  (True, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.1, riskType = FalseNegative [Occulusion]}]+                  (True, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.0001, riskType = TruePositive}]+                  (_, _, False, False) -> return [BddRisk {riskGt = Just gt, riskDt = Nothing, risk = riskBias * 30, riskType = FalseNegative []}]+  {-# INLINEABLE riskForGroundTruth #-}+  +  riskForDetection :: forall m. (Monad m) => ReaderT (Env BoundingBoxGT) m [Risk BoundingBoxGT]+  riskForDetection = do+    env <- ask+    loopD (++) [] $ \(dt :: Detection a) ->+      whenInterestAreaD (envUseInterestArea env) dt $ do+        let riskBias = if classD @BoundingBoxGT dt == Pedestrian then 10 else 1+        case detectD env dt of+          Just (gt :: a) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = 0.0001, riskType = TruePositive}]+          Nothing -> do+            case detectMaxIouD env dt of+              Nothing -> return [BddRisk {riskGt = Nothing, riskDt = Just dt, risk = riskBias * 5, riskType = FalsePositive []}]+              Just (gt :: a) -> do+                case ( classD @BoundingBoxGT dt == classG @BoundingBoxGT gt,+                       scoreD @BoundingBoxGT dt > confidenceScoreThresh env,+                       ioU gt dt > ioUThresh env,+                       ioG gt dt > ioUThresh env+                     ) of+                  (False, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 2.1, riskType = FalsePositive [MissClass, Occulusion]}]+                  (False, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 2, riskType = FalsePositive [MissClass]}]+                  (True, True, False, True) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.1, riskType = FalsePositive [Occulusion]}]+                  (True, True, True, _) -> return [BddRisk {riskGt = Just gt, riskDt = Just dt, risk = riskBias * 0.0001, riskType = TruePositive}]+                  (_, True, False, False) -> return [BddRisk {riskGt = Nothing, riskDt = Just dt, risk = riskBias * 5, riskType = FalsePositive []}]+                  (_, False, _, _) -> return []+  {-# INLINEABLE riskForDetection #-} -  confusionMatrixRecallBB _ = undefined-  confusionMatrixAccuracyBB _ = undefined-  confusionMatrixRecallBB' _ = undefined-  confusionMatrixAccuracyBB' _ = undefined-  errorGroupsBB _ = undefined+instance Show (ErrorType BoundingBoxGT) where+  show (FalsePositive suberrors) = "FP: " ++ foldl (\acc suberror -> acc ++ show suberror ++ ", ") "" (Set.toList suberrors)+  show (FalseNegative suberrors) = "FN: " ++ foldl (\acc suberror -> acc ++ show suberror ++ ", ") "" (Set.toList suberrors)+  show TruePositive = "TP"+  show TrueNegative = "TN" -myRisk :: forall a m. (Fractional (Risk a), Num (Risk a), BoundingBox a, Monad m) => ReaderT (Env a) m (Risk a)-myRisk = do-  env <- ask-  loopG (+) 0 $ \(gt :: a) ->-    case detectG env gt of-      Nothing -> return 10-      Just (obj :: Detection a) -> do-        if ioU gt obj > ioUThresh env-          then return 0.001-          else-            if ioG gt obj > ioUThresh env-              then return 1-              else return 5+type BddRisk = Risk BoundingBoxGT -myRiskWithError :: forall a m. (Monoid [(Risk a, ErrorType a)], BoundingBox a, Monad m, a ~ BoundingBoxGT) => ReaderT (Env a) m [(Risk a, ErrorType a)]-myRiskWithError = do-  env <- ask-  loopG (++) [] $ \(gt :: a) ->-    case detectG env gt of-      Nothing -> return [(10, FalseNegative Set.empty)]-      Just (obj :: Detection a) -> do-        if ioU gt obj > ioUThresh env-          then return [(0, TruePositive)]-          else-            if ioG gt obj > ioUThresh env-              then return [(1, FalseNegative Set.empty)]-              else return [(5, FalseNegative Set.empty)]+cocoCategoryToClass :: CocoMap -> CategoryId -> Class+cocoCategoryToClass coco categoryId =+  let cocoCategory = (cocoMapCocoCategory coco) Map.! categoryId+   in case T.unpack (cocoCategoryName cocoCategory) of+        "pedestrian" -> Pedestrian+        "rider" -> Rider+        "car" -> Car+        "truck" -> Truck+        "bus" -> Bus+        "train" -> Train+        "motorcycle" -> Motorcycle+        "bicycle" -> Bicycle+        _ -> Background++cocoResultToVector :: CocoMap -> ImageId -> (Vector BoundingBoxGT, Vector BoundingBoxDT)+cocoResultToVector coco imageId' = (groundTruth', detection')+  where+    groundTruth' =+      Vector.fromList $+        maybe+          []+          ( map+              ( \(index, CocoAnnotation {..}) ->+                  let CoCoBoundingBox (cocox, cocoy, cocow, cocoh) = cocoAnnotationBbox+                   in BoundingBoxGT+                        { x = cocox,+                          y = cocoy,+                          w = cocow,+                          h = cocoh,+                          cls = cocoCategoryToClass coco cocoAnnotationCategory,+                          idx = index -- cocoAnnotationId+                        }+              )+              . zip [0 ..]+          )+          (Map.lookup imageId' (cocoMapCocoAnnotation coco))+    detection' =+      Vector.fromList $+        maybe+          []+          ( map+              ( \(index, CocoResult {..}) ->+                  let CoCoBoundingBox (cocox, cocoy, cocow, cocoh) = cocoResultBbox+                   in BoundingBoxDT+                        { x = cocox,+                          y = cocoy,+                          w = cocow,+                          h = cocoh,+                          cls = cocoCategoryToClass coco cocoResultCategory,+                          score = unScore cocoResultScore,+                          idx = index+                        }+              )+              . zip [0 ..]+          )+          (Map.lookup imageId' (cocoMapCocoResult coco))++data BddContext = BddContext+  { bddContextDataset :: CocoMap,+    bddContextIouThresh :: Double,+    bddContextScoreThresh :: Double,+    bddContextUseInterestArea :: Bool+  }+  deriving (Show, Eq)++instance World BddContext BoundingBoxGT where+  mAP BddContext {..} = fst $ RiskWeaver.Metric.mAP bddContextDataset (IOU bddContextIouThresh)+  ap BddContext {..} = Map.fromList $ map (\(key, value) -> (cocoCategoryToClass bddContextDataset key, value)) $ snd $ RiskWeaver.Metric.mAP bddContextDataset (IOU bddContextIouThresh)+  mF1 BddContext {..} = fst $ RiskWeaver.Metric.mF1 bddContextDataset (IOU bddContextIouThresh) (Score bddContextScoreThresh)+  f1 BddContext {..} = Map.fromList $ map (\(key, value) -> (cocoCategoryToClass bddContextDataset key, value)) $ snd $ RiskWeaver.Metric.mF1 bddContextDataset (IOU bddContextIouThresh) (Score bddContextScoreThresh)+  confusionMatrixRecall context@BddContext {..} = sortAndGroup risks+    where+      risks :: [((Class, Class), BddRisk)]+      risks = concat $ flip map (cocoMapImageIds bddContextDataset) $ \imageId' -> map getKeyValue (runReader riskForGroundTruth (toEnv context imageId'))+      getKeyValue :: BddRisk -> ((Class, Class), BddRisk)+      getKeyValue bddRisk = ((maybe Background classG bddRisk.riskGt, maybe Background classD bddRisk.riskDt), bddRisk)+  confusionMatrixPrecision context@BddContext {..} = sortAndGroup risks+    where+      risks :: [((Class, Class), BddRisk)]+      risks = concat $ flip map (cocoMapImageIds bddContextDataset) $ \imageId' -> map getKeyValue (runReader riskForDetection (toEnv context imageId'))+      getKeyValue :: BddRisk -> ((Class, Class), BddRisk)+      getKeyValue bddRisk = ((maybe Background classD bddRisk.riskDt, maybe Background classG bddRisk.riskGt), bddRisk)++  toEnv BddContext {..} imageId' =+    let (groundTruth', detection') = cocoResultToVector bddContextDataset imageId'+     in MyEnv+          { envGroundTruth = groundTruth',+            envDetection = detection',+            envConfidenceScoreThresh = bddContextScoreThresh,+            envIoUThresh = bddContextIouThresh,+            envUseInterestArea = bddContextUseInterestArea,+            envImageId = imageId'+          }++  toImageIds BddContext {..} = cocoMapImageIds bddContextDataset+
src/RiskWeaver/DSL/Core.hs view
@@ -1,91 +1,337 @@ {-# 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)+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 BoundingBox a where-  type Detection a :: Type+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-  type Risk a :: Type -  riskE :: Env a -> Risk a+  -- | 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-  errorType :: Env a -> Detection a -> Maybe (ErrorType 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 -  riskD :: Env a -> Detection a -> Risk a-  riskBB :: Env a -> Risk a+  -- | True if the detection is in the interest object+  isInterestObjectD :: InterestObject a -> Detection a -> Bool -  confusionMatrixRecallBB :: Env a -> Map (ClassG a, ClassD a) Double-  confusionMatrixAccuracyBB :: Env a -> Map (ClassD a, ClassG a) Double-  confusionMatrixRecallBB' :: Env a -> Map (ClassG a, ClassD a) [Idx a]-  confusionMatrixAccuracyBB' :: Env a -> Map (ClassD a, ClassG a) [Idx a]-  errorGroupsBB :: Env a -> Map (ClassG a) (Map (ErrorType a) [Idx a])+  -- | True if the ground truth is in the interest object+  isInterestObjectG :: InterestObject a -> a -> Bool -class (BoundingBox a) => World a where-  type Image a :: Type-  idI :: Image a -> Int-  env :: Image a -> Env a-  mAP :: Vector (Image a) -> Double-  ap :: Vector (Image a) -> Map (ClassG a) Double-  risk :: Vector (Image a) -> Double-  confusionMatrixRecall :: Vector (Image a) -> Map (ClassG a, ClassD a) Double-  confusionMatrixAccuracy :: Vector (Image a) -> Map (ClassD a, ClassG a) Double-  confusionMatrixRecall' :: Vector (Image a) -> Map (ClassG a, ClassD a) [Idx a]-  confusionMatrixAccuracy' :: Vector (Image a) -> Map (ClassD a, ClassG a) [Idx a]-  errorGroups :: Vector (Image a) -> Map (ClassG a) (Map (ErrorType a) [Idx a])+-- | 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+loopG add init' fn = do   env <- ask-  foldl add init <$> mapM fn (groundTruth @a env)+  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+loopD add init' fn = do   env <- ask-  foldl add init <$> mapM fn (detection @a env)+  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+
src/RiskWeaver/Display.hs view
@@ -19,8 +19,8 @@     Left imagePath -> do       imageBin <- readImage imagePath       case imageBin of-        Left err -> fail $ "Image file " ++ imagePath ++ " can not be read."-        Right imageBin -> return (convertRGB8 imageBin)+        Left err -> fail $ "Image file " ++ imagePath ++ " can not be read. : " ++ show err+        Right imageBin' -> return (convertRGB8 imageBin')     Right image -> return image   case termProgram of     Just "iTerm.app" -> do@@ -46,10 +46,28 @@     drawString (T.unpack (cocoCategoryName (categories Map.! cocoAnnotationCategory annotation))) x y (255, 0, 0) (0, 0, 0) imageRGB8   return imageRGB8 -drawDetectionBoundingBox :: DynamicImage -> [CocoResult] -> Map.Map CategoryId CocoCategory -> Maybe Double -> IO (Image PixelRGB8)-drawDetectionBoundingBox imageBin annotations categories scoreThreshold = do+drawDetectionBoundingBox ::+  (Show a) =>+  -- | Image+  DynamicImage ->+  -- | A list of Coco result+  [CocoResult] ->+  -- | A list of object property+  [a] ->+  -- | A map of category+  Map.Map CategoryId CocoCategory ->+  -- | Score threshold+  Maybe Double ->+  -- | Overlay function to draw object property+  Maybe (Image PixelRGB8 -> a -> IO (Image PixelRGB8)) ->+  IO (Image PixelRGB8)+drawDetectionBoundingBox imageBin annotations properties categories scoreThreshold overlay = do   let imageRGB8 = convertRGB8 imageBin-  forM_ annotations $ \annotation -> do+      zipedAnnotations = zip annotations $+        case properties of+          [] -> repeat Nothing+          _ -> map Just properties+  forM_ zipedAnnotations $ \(annotation, property) -> do     let (CoCoBoundingBox (bx, by, bw, bh)) = cocoResultBbox annotation         x = round bx         y = round by@@ -60,11 +78,16 @@           drawString (T.unpack (cocoCategoryName (categories Map.! cocoResultCategory annotation))) x y (255, 0, 0) (0, 0, 0) imageRGB8           -- Use printf format to show score           drawString (printf "%.2f" (unScore $ cocoResultScore annotation)) x (y + 10) (255, 0, 0) (0, 0, 0) imageRGB8+          case (property, overlay) of+            (Just property', Just overlay') -> do+              _ <- overlay' imageRGB8 property'+              return ()+            _ -> return ()     -- drawString (show $ cocoResultScore annotation)  x (y + 10) (255,0,0) (0,0,0) imageRGB8     case scoreThreshold of       Nothing -> draw-      Just scoreThreshold -> do-        if cocoResultScore annotation >= Score scoreThreshold+      Just scoreThreshold' -> do+        if cocoResultScore annotation >= Score scoreThreshold'           then draw           else return ()   return imageRGB8@@ -84,31 +107,27 @@       let image' = getCocoImageByFileName coco imageFile       case image' of         Nothing -> putStrLn $ "Image file " ++ imageFile ++ " is not found."-        Just (image, annotations) -> do+        Just (_, annotations) -> do           let categories = toCategoryMap coco           imageBin' <- readImage imagePath           case imageBin' of-            Left err -> putStrLn $ "Image file " ++ imagePath ++ " can not be read."+            Left err -> putStrLn $ "Image file " ++ imagePath ++ " can not be read. : " ++ show err             Right imageBin -> do               imageRGB8 <- drawBoundingBox imageBin annotations categories               putImage (Right imageRGB8)     else do       putImage (Left imagePath) -showDetectionImage :: Coco -> FilePath -> FilePath -> FilePath -> Maybe Double -> IO ()-showDetectionImage coco cocoFile cocoResultFile imageFile scoreThreshold = do-  let cocoFileNameWithoutExtension = takeBaseName cocoFile-      imageDir = takeDirectory (takeDirectory cocoFile) </> cocoFileNameWithoutExtension </> "images"-      imagePath = imageDir </> imageFile-  cocoResult <- readCocoResult cocoResultFile-  let image' = getCocoResultByFileName coco cocoResult imageFile+showDetectionImage :: (Show a) => CocoMap -> FilePath -> Maybe Double -> [a] -> Maybe (Image PixelRGB8 -> a -> IO (Image PixelRGB8)) -> IO ()+showDetectionImage cocoMap imageFile scoreThreshold properties overlay = do+  let imagePath = getImageDir cocoMap </> imageFile+  let image' = getCocoResult cocoMap imageFile   case image' of     Nothing -> putStrLn $ "Image file " ++ imageFile ++ " is not found."-    Just (image, annotations) -> do+    Just (_, annotations) -> do       imageBin' <- readImage imagePath       case imageBin' of-        Left err -> putStrLn $ "Image file " ++ imagePath ++ " can not be read."+        Left err -> putStrLn $ "Image file " ++ imagePath ++ " can not be read. : " ++ show err         Right imageBin -> do-          let categories = toCategoryMap coco-          imageRGB8 <- drawDetectionBoundingBox imageBin (filter (\res -> cocoResultImageId res == cocoImageId image) cocoResult) categories scoreThreshold-          putImage (Right imageRGB8)+          drawDetectionBoundingBox imageBin annotations properties (cocoMapCocoCategory cocoMap) scoreThreshold overlay+            >>= putImage . Right
src/RiskWeaver/Draw.hs view
@@ -11,24 +11,15 @@ module RiskWeaver.Draw where  import Codec.Picture qualified as I-import Control.Exception.Safe-  ( SomeException (..),-    throwIO,-    try,-  ) import Control.Monad-  ( MonadPlus,-    forM_,+  ( forM_,     when,   )-import Data.ByteString qualified as BS-import Data.ByteString.Internal qualified as BSI import Data.Int import Data.Vector.Storable qualified as V-import Data.Word import Foreign.ForeignPtr qualified as F import Foreign.Ptr qualified as F-import GHC.Exts (IsList (fromList))+import GHC.ForeignPtr qualified as GF import Language.C.Inline qualified as C import System.IO.Unsafe import Prelude hiding (max, min)@@ -58,10 +49,8 @@   let channel = 3 :: Int       (I.Image org_w org_h org_vec) = input       img@(I.Image w h vec) = I.generateImage (\_ _ -> (I.PixelRGB8 0 0 0)) width height :: I.Image I.PixelRGB8-      (org_fptr, org_len) = V.unsafeToForeignPtr0 org_vec-      org_whc = fromIntegral $ org_w * org_h * channel-      (fptr, len) = V.unsafeToForeignPtr0 vec-      whc = fromIntegral $ w * h * channel+      (org_fptr, _) = V.unsafeToForeignPtr0 org_vec+      (fptr, _) = V.unsafeToForeignPtr0 vec   F.withForeignPtr org_fptr $ \ptr1 -> F.withForeignPtr fptr $ \ptr2 -> do     let src = F.castPtr ptr1         dst = F.castPtr ptr2@@ -97,8 +86,8 @@  drawLine :: Int -> Int -> Int -> Int -> (Int, Int, Int) -> I.Image I.PixelRGB8 -> IO () drawLine x0 y0 x1 y1 (r, g, b) input = do-  let img@(I.Image w h vec) = input-      (fptr, len) = V.unsafeToForeignPtr0 vec+  let (I.Image w h vec) = input+      (fptr, _) = V.unsafeToForeignPtr0 vec   F.withForeignPtr fptr $ \ptr2 -> do     let iw = fromIntegral w         ih = fromIntegral h@@ -163,8 +152,8 @@  drawChar :: Int -> Int -> Int -> (Int, Int, Int) -> (Int, Int, Int) -> I.Image I.PixelRGB8 -> IO () drawChar ascii_code x0 y0 (r, g, b) (br, bg, bb) input = do-  let img@(I.Image w h vec) = input-      (fptr, len) = V.unsafeToForeignPtr0 vec+  let (I.Image w h vec) = input+      (fptr, _) = V.unsafeToForeignPtr0 vec   F.withForeignPtr fptr $ \ptr2 -> do     let iw = fromIntegral w         ih = fromIntegral h@@ -320,10 +309,8 @@   let channel = 3 :: Int       (I.Image org_w org_h org_vec) = input       img@(I.Image w h vec) = I.generateImage (\_ _ -> (I.PixelRGB8 0 0 0)) width height :: I.Image I.PixelRGB8-      (org_fptr, org_len) = V.unsafeToForeignPtr0 org_vec-      org_whc = fromIntegral $ org_w * org_h * channel-      (fptr, len) = V.unsafeToForeignPtr0 vec-      whc = fromIntegral $ w * h * channel+      (org_fptr, _) = V.unsafeToForeignPtr0 org_vec+      (fptr, _) = V.unsafeToForeignPtr0 vec   F.withForeignPtr org_fptr $ \ptr1 -> F.withForeignPtr fptr $ \ptr2 -> do     let src = F.castPtr ptr1         dst = F.castPtr ptr2@@ -404,3 +391,192 @@   I.ImageY16 _ -> Y16   I.ImageYA16 _ -> YA16   I.ImageY32 _ -> Y32++-- allocates memory for a new image+createImage :: Int -> Int -> IO (I.Image I.PixelRGB8)+createImage w h = do+  when (w < 0) $ error ("trying to createImage of negative dim: " ++ show w)+  when (h < 0) $ error ("trying to createImage of negative dim: " ++ show h)+  fp <- GF.mallocPlainForeignPtrBytes size+  return $ I.Image w h (V.unsafeFromForeignPtr fp 0 size)+  where+    size = w * h * 3++cloneImage :: I.Image I.PixelRGB8 -> IO (I.Image I.PixelRGB8)+cloneImage input = do+  let (I.Image w h vec) = input+      (org_fptr, _) = V.unsafeToForeignPtr0 vec+  newImage <- createImage w h+  let (I.Image _ _ dst_vec) = newImage+      (dst_fptr, _) = V.unsafeToForeignPtr0 dst_vec+  F.withForeignPtr org_fptr $ \ptr1 -> F.withForeignPtr dst_fptr $ \ptr2 -> do+    let src = F.castPtr ptr1+        dst = F.castPtr ptr2+        iw = fromIntegral w+        ih = fromIntegral h+        ichannel = 3+    [C.block| void {+        uint8_t* src = $(uint8_t* src);+        uint8_t* dst = $(uint8_t* dst);+        int w = $(int iw);+        int h = $(int ih);+        int channel = $(int ichannel);+        for(int y=0;y<h;y++){+          for(int x=0;x<w;x++){+            for(int c=0;c<channel;c++){+              dst[(y*w+x)*channel+c] = src[(y*w+x)*channel+c];+            }+          }+        }+    } |]+    return newImage++pasteImage :: I.Image I.PixelRGB8 -> Int -> Int -> I.Image I.PixelRGB8 -> IO ()+pasteImage input offsetx offsety destination = do+  let (I.Image w h vec) = input+      (org_fptr, _) = V.unsafeToForeignPtr0 vec+      (I.Image dst_w dst_h dst_vec) = destination+      (dst_fptr, _) = V.unsafeToForeignPtr0 dst_vec+  F.withForeignPtr org_fptr $ \ptr1 -> F.withForeignPtr dst_fptr $ \ptr2 -> do+    let src = F.castPtr ptr1+        dst = F.castPtr ptr2+        iw = fromIntegral w+        ih = fromIntegral h+        iorg_w = fromIntegral dst_w+        iorg_h = fromIntegral dst_h+        ichannel = 3+        ioffsetx = fromIntegral offsetx+        ioffsety = fromIntegral offsety+    [C.block| void {+        uint8_t* src = $(uint8_t* src);+        uint8_t* dst = $(uint8_t* dst);+        int w = $(int iw);+        int h = $(int ih);+        int ow = $(int iorg_w);+        int oh = $(int iorg_h);+        int channel = $(int ichannel);+        int offsetx = $(int ioffsetx);+        int offsety = $(int ioffsety);+        for(int y=0;y<h;y++){+          for(int x=0;x<w;x++){+            for(int c=0;c<channel;c++){+              int sy = y + offsety;+              int sx = x + offsetx;+              if(sx >= 0 && sx < ow &&+                 sy >= 0 && sy < oh){+                 dst[(sy*ow+sx)*channel+c] = src[(y*w+x)*channel+c];+              }+            }+          }+        }+    } |]++concatImagesH :: [I.Image I.PixelRGB8] -> IO (I.Image I.PixelRGB8)+concatImagesH [] = error "concatImagesH: empty list"+concatImagesH (x : []) = return x+concatImagesH (x : y : xs) = do+  newImage <- concatImageByHorizontal x y+  concatImagesH (newImage : xs)++concatImagesV :: [I.Image I.PixelRGB8] -> IO (I.Image I.PixelRGB8)+concatImagesV [] = error "concatImagesH: empty list"+concatImagesV (x : []) = return x+concatImagesV (x : y : xs) = do+  newImage <- concatImageByVertical x y+  concatImagesH (newImage : xs)++concatImageByHorizontal :: I.Image I.PixelRGB8 -> I.Image I.PixelRGB8 -> IO (I.Image I.PixelRGB8)+concatImageByHorizontal left right = do+  let (I.Image lw lh lvec) = left+      (lfptr, _) = V.unsafeToForeignPtr0 lvec+      (I.Image rw rh rvec) = right+      (rfptr, _) = V.unsafeToForeignPtr0 rvec+  newImage <- createImage (lw + rw) (P.max lh rh)+  let (I.Image w h dst_vec) = newImage+      (dst_fptr, _) = V.unsafeToForeignPtr0 dst_vec+  F.withForeignPtr lfptr $ \lptr -> F.withForeignPtr rfptr $ \rptr -> F.withForeignPtr dst_fptr $ \dptr -> do+    let lsrc = F.castPtr lptr+        rsrc = F.castPtr rptr+        dst = F.castPtr dptr+        iw = fromIntegral w+        ih = fromIntegral h+        ilw = fromIntegral lw+        ilh = fromIntegral lh+        irw = fromIntegral rw+        irh = fromIntegral rh+        ichannel = 3+    [C.block| void {+        uint8_t* lsrc = $(uint8_t* lsrc);+        uint8_t* rsrc = $(uint8_t* rsrc);+        uint8_t* dst = $(uint8_t* dst);+        int w = $(int iw);+        int h = $(int ih);+        int lw = $(int ilw);+        int lh = $(int ilh);+        int rw = $(int irw);+        int rh = $(int irh);+        int channel = $(int ichannel);+        for(int y=0;y<h;y++){+          for(int x=0;x<w;x++){+            for(int c=0;c<channel;c++){+              if(x < lw){+                dst[(y*w+x)*channel+c] = lsrc[(y*lw+x)*channel+c];+              } else {+                dst[(y*w+x)*channel+c] = rsrc[(y*rw+(x-lw))*channel+c];+              }+            }+          }+        }+    } |]+    return newImage++concatImageByVertical :: I.Image I.PixelRGB8 -> I.Image I.PixelRGB8 -> IO (I.Image I.PixelRGB8)+concatImageByVertical top bottom = do+  let (I.Image tw th tvec) = top+      (tfptr, _) = V.unsafeToForeignPtr0 tvec+      (I.Image bw bh bvec) = bottom+      (bfptr, _) = V.unsafeToForeignPtr0 bvec+  newImage <- createImage (P.max tw bw) (th + bh)+  let (I.Image w h dst_vec) = newImage+      (dst_fptr, _) = V.unsafeToForeignPtr0 dst_vec+  F.withForeignPtr tfptr $ \tptr -> F.withForeignPtr bfptr $ \bptr -> F.withForeignPtr dst_fptr $ \dptr -> do+    let tsrc = F.castPtr tptr+        bsrc = F.castPtr bptr+        dst = F.castPtr dptr+        iw = fromIntegral w+        ih = fromIntegral h+        itw = fromIntegral tw+        ith = fromIntegral th+        ibw = fromIntegral bw+        ibh = fromIntegral bh+        ichannel = 3+    [C.block| void {+        uint8_t* tsrc = $(uint8_t* tsrc);+        uint8_t* bsrc = $(uint8_t* bsrc);+        uint8_t* dst = $(uint8_t* dst);+        int w = $(int iw);+        int h = $(int ih);+        int tw = $(int itw);+        int th = $(int ith);+        int bw = $(int ibw);+        int bh = $(int ibh);+        int channel = $(int ichannel);+        for(int y=0;y<h;y++){+          for(int x=0;x<w;x++){+            for(int c=0;c<channel;c++){+              if(y < th){+                dst[(y*w+x)*channel+c] = tsrc[(y*tw+x)*channel+c];+              } else {+                dst[(y*w+x)*channel+c] = bsrc[((y-th)*bw+x)*channel+c];+              }+            }+          }+        }+    } |]+    return newImage++concatImages2x2 :: I.Image I.PixelRGB8 -> I.Image I.PixelRGB8 -> I.Image I.PixelRGB8 -> I.Image I.PixelRGB8 -> IO (I.Image I.PixelRGB8)+concatImages2x2 topLeft topRight bottomLeft bottomRight = do+  top <- concatImageByHorizontal topLeft topRight+  bottom <- concatImageByHorizontal bottomLeft bottomRight+  concatImageByVertical top bottom
src/RiskWeaver/Format/Coco.hs view
@@ -1,8 +1,10 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-} {- This module provides COCO format parser of object detection dataset. Aeson is used for parsing JSON. -}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE Strict #-}@@ -10,26 +12,21 @@  module RiskWeaver.Format.Coco where -import Codec.Picture.Metadata (Value (Double))-import Control.Monad (ap)+import Control.Concurrent.Async+import Control.DeepSeq import Data.Aeson import Data.ByteString.Lazy qualified as BS-import Data.List (maximumBy, sort, sortBy) import Data.Map qualified as Map-import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T import GHC.Generics---- import Debug.Trace (trace)--- myTrace :: Show a => String -> a -> a--- myTrace s a = trace (s ++ ": " ++ show a) a+import System.FilePath (takeBaseName, takeDirectory, (</>)) -newtype ImageId = ImageId {unImageId :: Int} deriving (Show, Ord, Eq, Generic)+newtype ImageId = ImageId {unImageId :: Int} deriving (Show, Ord, Eq, Generic, NFData) -newtype CategoryId = CategoryId {unCategoryId :: Int} deriving (Show, Ord, Eq, Generic)+newtype CategoryId = CategoryId {unCategoryId :: Int} deriving (Show, Ord, Eq, Generic, NFData) -newtype Score = Score {unScore :: Double} deriving (Show, Eq, Ord, Num, Fractional, Floating, Real, RealFrac, RealFloat, Generic)+newtype Score = Score {unScore :: Double} deriving (Show, Eq, Ord, Num, Fractional, Floating, Real, RealFrac, RealFloat, Generic, NFData)  instance FromJSON ImageId where   parseJSON = withScientific "image_id" $ \n -> do@@ -62,6 +59,8 @@   }   deriving (Show, Eq, Generic) +instance NFData CocoInfo+ instance FromJSON CocoInfo where   parseJSON = withObject "info" $ \o -> do     cocoInfoYear <- o .: "year"@@ -90,6 +89,8 @@   }   deriving (Show, Eq, Generic) +instance NFData CocoLicense+ instance FromJSON CocoLicense where   parseJSON = withObject "license" $ \o -> do     cocoLicenseId <- o .: "id"@@ -115,6 +116,8 @@   }   deriving (Show, Eq, Generic) +instance NFData CocoImage+ instance FromJSON CocoImage where   parseJSON = withObject "image" $ \o -> do     cocoImageId <- o .: "id"@@ -140,6 +143,8 @@   = CoCoBoundingBox (Double, Double, Double, Double)   deriving (Show, Eq, Generic) +instance NFData CoCoBoundingBox+ -- (x, y, width, height)  data CocoAnnotation = CocoAnnotation@@ -153,6 +158,8 @@   }   deriving (Show, Eq, Generic) +instance NFData CocoAnnotation+ instance FromJSON CocoAnnotation where   parseJSON = withObject "annotation" $ \o -> do     cocoAnnotationId <- o .: "id"@@ -160,7 +167,13 @@     cocoAnnotationCategory <- o .: "category_id"     cocoAnnotationSegment <- o .:? "segmentation"     cocoAnnotationArea <- o .: "area"-    cocoAnnotationBbox <- fmap (\[x, y, w, h] -> CoCoBoundingBox (x, y, w, h)) $ o .: "bbox"+    cocoAnnotationBbox <-+      fmap+        ( \case+            x : y : w : h : _ -> CoCoBoundingBox (x, y, w, h)+            v -> error $ "Annotation's bounding box needs 4 numbers. : " ++ show v+        )+        $ o .: "bbox"     cocoAnnotationIsCrowd <- o .:? "iscrowd"     return CocoAnnotation {..} @@ -183,6 +196,8 @@   }   deriving (Show, Eq, Generic) +instance NFData CocoCategory+ instance FromJSON CocoCategory where   parseJSON = withObject "category" $ \o -> do     cocoCategoryId <- o .: "id"@@ -207,6 +222,8 @@   }   deriving (Show, Eq, Generic) +instance NFData Coco+ instance FromJSON Coco where   parseJSON = withObject "coco" $ \o -> do     cocoInfo <- o .:? "info"@@ -236,12 +253,20 @@   }   deriving (Show, Eq, Generic) +instance NFData CocoResult+ instance FromJSON CocoResult where   parseJSON = withObject "result" $ \o -> do     cocoResultImageId <- o .: "image_id"     cocoResultCategory <- o .: "category_id"     cocoResultScore <- o .: "score"-    cocoResultBbox <- fmap (\[x, y, w, h] -> CoCoBoundingBox (x, y, w, h)) $ o .: "bbox"+    cocoResultBbox <-+      fmap+        ( \case+            x : y : w : h : _ -> CoCoBoundingBox (x, y, w, h)+            v -> error $ "Annotation's bounding box needs 4 numbers. : " ++ show v+        )+        $ o .: "bbox"     return CocoResult {..}  instance ToJSON CocoResult where@@ -317,12 +342,40 @@     cocoMapCocoResult :: Map.Map ImageId [CocoResult],     cocoMapFilepath :: Map.Map ImageId FilePath,     cocoMapImageIds :: [ImageId],-    cocoMapCategoryIds :: [CategoryId]+    cocoMapCategoryIds :: [CategoryId],+    cocoMapCoco :: Coco,+    cocoMapCocoFile :: FilePath,+    cocoMapCocoResultFile :: FilePath   }   deriving (Show, Eq, Generic) -toCocoMap :: Coco -> [CocoResult] -> CocoMap-toCocoMap coco cocoResult =+instance NFData CocoMap++getImageDir :: CocoMap -> FilePath+getImageDir cocoMap =+  let cocoFileNameWithoutExtension = takeBaseName $ cocoMapCocoFile cocoMap+      imageDir = takeDirectory (takeDirectory $ cocoMapCocoFile cocoMap) </> cocoFileNameWithoutExtension </> "images"+   in imageDir++class CocoMapable a where+  getCocoResult :: CocoMap -> a -> Maybe (CocoImage, [CocoResult])++instance CocoMapable FilePath where+  getCocoResult cocoMap filePath = do+    imageIds <- Map.lookup filePath $ cocoMapImageId cocoMap+    let imageId = head imageIds+    image <- Map.lookup imageId $ cocoMapCocoImage cocoMap+    results <- Map.lookup imageId $ cocoMapCocoResult cocoMap+    return (image, results)++instance CocoMapable ImageId where+  getCocoResult cocoMap imageId = do+    image <- Map.lookup imageId $ cocoMapCocoImage cocoMap+    results <- Map.lookup imageId $ cocoMapCocoResult cocoMap+    return (image, results)++toCocoMap :: Coco -> [CocoResult] -> FilePath -> FilePath -> CocoMap+toCocoMap coco cocoResult cocoFile cocoResultFile =   let cocoMapImageId = toImageId coco       cocoMapCocoImage = toCocoImageMap coco       cocoMapCocoAnnotation = toCocoAnnotationMap coco@@ -331,4 +384,51 @@       cocoMapFilepath = toFilepathMap coco       cocoMapImageIds = map (\CocoImage {..} -> cocoImageId) $ cocoImages coco       cocoMapCategoryIds = map (\CocoCategory {..} -> cocoCategoryId) $ cocoCategories coco+      cocoMapCoco = coco+      cocoMapCocoFile = cocoFile+      cocoMapCocoResultFile = cocoResultFile    in CocoMap {..}++readCocoMap :: FilePath -> FilePath -> IO CocoMap+readCocoMap cocoFile cocoResultFile =+  withAsync (readCoco cocoFile) $ \coco' -> do+    withAsync (readCocoResult cocoResultFile) $ \cocoResult' -> do+      coco <- wait coco'+      cocoResult <- wait cocoResult'+      return $ toCocoMap coco cocoResult cocoFile cocoResultFile++resampleCocoMapWithImageIds :: CocoMap -> [ImageId] -> (Coco, [CocoResult])+resampleCocoMapWithImageIds cocoMap imageIds =+  let zipedImageIds = zip [1 ..] imageIds+      newImageIds = (ImageId . fst) <$> zipedImageIds+      imageIdsMap = Map.fromList zipedImageIds+      cocoImages' =+        map+          ( \imageId ->+              let orgImageId = imageIdsMap Map.! (unImageId imageId)+                  img = (cocoMapCocoImage cocoMap) Map.! orgImageId+               in img {cocoImageId = imageId}+          )+          newImageIds+      cocoAnnotations' =+        let annotations' = concat $ flip map newImageIds $ \imageId ->+              let orgImageId = imageIdsMap Map.! (unImageId imageId)+                  annotations = Map.findWithDefault [] orgImageId (cocoMapCocoAnnotation cocoMap)+                  newAnnotations = map (\annotation -> annotation {cocoAnnotationImageId = imageId}) annotations+               in newAnnotations+            zippedAnnotations = zip [1 ..] annotations'+            alignedAnnotations = map (\(newId, annotation) -> annotation {cocoAnnotationId = newId}) zippedAnnotations+         in alignedAnnotations+      newCoco =+        (cocoMapCoco cocoMap)+          { cocoImages = cocoImages',+            cocoAnnotations = cocoAnnotations'+          }+      newCocoResult = concat $ flip map newImageIds $ \imageId ->+        let orgImageId = imageIdsMap Map.! (unImageId imageId)+            cocoResult = Map.findWithDefault [] orgImageId (cocoMapCocoResult cocoMap)+            newCocoResult' = map (\cocoResult' -> cocoResult' {cocoResultImageId = imageId}) cocoResult+         in newCocoResult'+   in (newCoco, newCocoResult)++
src/RiskWeaver/Metric.hs view
@@ -6,11 +6,13 @@  module RiskWeaver.Metric where -import Data.List (maximumBy, sort, sortBy)+import Control.Parallel.Strategies+import Data.List (maximumBy, sortBy)+import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe (fromMaybe)-import RiskWeaver.Format.Coco import GHC.Generics+import RiskWeaver.Format.Coco  newtype IOU = IOU Double deriving (Show, Eq, Ord, Num, Fractional, Floating, Real, RealFrac, RealFloat, Generic) @@ -39,6 +41,7 @@       intersection = max 0 (x' - x) * max 0 (y' - y)       union = w1 * h1 + w2 * h2 - intersection    in IOU $ intersection / union+{-# INLINEABLE iou #-}  iog :: CoCoBoundingBox -> CoCoBoundingBox -> IOG iog (CoCoBoundingBox (x1, y1, w1, h1)) (CoCoBoundingBox (x2, y2, w2, h2)) =@@ -53,6 +56,7 @@       intersection = max 0 (x' - x) * max 0 (y' - y)       groundTruth = w1 * h1    in IOG $ intersection / groundTruth+{-# INLINEABLE iog #-}  -- | Calculate TP or FP -- | TP = true positive@@ -60,9 +64,8 @@ -- | When the value is True, TP is calculated. -- | When the value is False, FP is calculated. toTPorFP :: CocoMap -> ImageId -> CategoryId -> IOU -> ([(CocoResult, Bool)], Int)-toTPorFP cocoMap@CocoMap {..} imageId categoryId iouThresh =-  let cocoImage = cocoMapCocoImage Map.! imageId-      -- detections is sorted by score in descending order.+toTPorFP CocoMap {..} imageId categoryId iouThresh =+  let -- detections is sorted by score in descending order.       detections :: [CocoResult] =         case Map.lookup imageId cocoMapCocoResult of           Nothing -> []@@ -81,26 +84,27 @@         if Map.size gts == 0           then Nothing           else-            let ious = map (\(i, gt) -> (i, gt, iou (cocoAnnotationBbox gt) (cocoResultBbox cocoResult))) $ Map.toList gts+            let ious = map (\(i', gt') -> (i', gt', iou (cocoAnnotationBbox gt') (cocoResultBbox cocoResult))) $ Map.toList gts                 (i, gt, iou') = maximumBy (\(_, _, iou1) (_, _, iou2) -> compare iou1 iou2) ious              in if iou' >= iouThresh                   then Just (i, gt, iou')                   else Nothing       loop :: [CocoResult] -> Map.Map Int CocoAnnotation -> [(CocoResult, Bool)]       loop [] _ = []-      loop (result : results) groundTruths =-        case getGTWithMaxScore result groundTruths of-          Nothing -> (result, False) : loop results groundTruths-          Just (i, gt, iou') ->-            let groundTruths' = Map.delete i groundTruths-             in (result, True) : loop results groundTruths'+      loop (result : results) groundTruths' =+        case getGTWithMaxScore result groundTruths' of+          Nothing -> (result, False) : loop results groundTruths'+          Just (i, _, _) ->+            let groundTruths'' = Map.delete i groundTruths'+             in (result, True) : loop results groundTruths''    in (loop detections groundTruths, numOfGroundTruths)  apForCategory :: CocoMap -> CategoryId -> IOU -> Double apForCategory cocoMap@CocoMap {..} categoryId iouThresh =-  let for = flip map-      imageIds = cocoMapImageIds-      tpAndFps' = for imageIds $ \imageId -> toTPorFP cocoMap imageId categoryId iouThresh+  let imageIds = cocoMapImageIds+      tpAndFps' =+        map (\imageId -> toTPorFP cocoMap imageId categoryId iouThresh) imageIds+          `using` parList rdeepseq       numOfGroundTruths = sum $ map snd tpAndFps'       tpAndFps = sortBy (\res0 res1 -> compare (cocoResultScore (fst res1)) (cocoResultScore (fst res0))) $ concat $ map fst tpAndFps'       precisionRecallCurve :: [(CocoResult, Bool)] -> Int -> Int -> [(Double, Double)]@@ -127,62 +131,50 @@ mAP cocoMap@CocoMap {..} iouThresh =   let categoryIds = cocoMapCategoryIds       aps = map (\categoryId -> apForCategory cocoMap categoryId iouThresh) categoryIds-   in (sum aps / fromIntegral (length aps), zip categoryIds aps)+      aps' = aps `using` parList rdeepseq+   in (sum aps' / fromIntegral (length aps'), zip categoryIds aps') -data ConfusionMatrix = ConfusionMatrix-  { confusionMatrixRecall :: Map.Map (Gt CategoryId) (Map.Map (Dt CategoryId) Int),-    confusionMatrixPrecision :: Map.Map (Dt CategoryId) (Map.Map (Gt CategoryId) Int),-    confusionMatrixCategoryIds :: [CategoryId]-  }-  deriving (Show, Eq, Ord) -instance Semigroup ConfusionMatrix where-  ConfusionMatrix recall1 precision1 categoryIds1 <> ConfusionMatrix recall2 precision2 categoryIds2 =-    ConfusionMatrix-      { confusionMatrixRecall = recall,-        confusionMatrixPrecision = precision,-        confusionMatrixCategoryIds = categoryIds-      }-    where-      recall = Map.unionWith (Map.unionWith (+)) recall1 recall2-      precision = Map.unionWith (Map.unionWith (+)) precision1 precision2-      categoryIds = categoryIds1+f1ForCategory :: CocoMap -> CategoryId -> IOU -> Score -> Double+f1ForCategory cocoMap@CocoMap {..} categoryId iouThresh scoreThresh =+  let imageIds = cocoMapImageIds+      tpAndFps' =+        map (\imageId -> toTPorFP cocoMap imageId categoryId iouThresh) imageIds+          `using` parList rdeepseq+      numOfGroundTruths = sum $ map snd tpAndFps'+      tpAndFps = sortBy (\res0 res1 -> compare (cocoResultScore (fst res1)) (cocoResultScore (fst res0))) $ concat $ map fst tpAndFps'+      precisionRecallCurve :: [(CocoResult, Bool)] -> Int -> Int -> [(Double, Double, Score)]+      precisionRecallCurve [] _ _ = []+      precisionRecallCurve (x : xs) accTps accNum =+        (precision, recall, cocoResultScore (fst x)) : precisionRecallCurve xs accTps' accNum'+        where+          accTps' = if snd x then accTps + 1 else accTps+          accNum' = accNum + 1+          precision = fromIntegral accTps' / fromIntegral accNum'+          recall = fromIntegral accTps' / fromIntegral numOfGroundTruths+      precisionRecallCurve' = reverse $ precisionRecallCurve tpAndFps 0 0+      f1 :: [(Double, Double, Score)] -> Double+      f1 [] = 0+      f1 ((precision, recall, score) : xs) = +        if score >= scoreThresh+          then 2 * (precision * recall) / (precision + recall)+          else f1 xs+   in f1 precisionRecallCurve' -confusionMatrix :: CocoMap -> IOU -> Score -> ConfusionMatrix-confusionMatrix cocoMap@CocoMap {..} iouThresh scoreThresh =-  foldl (<>) (ConfusionMatrix Map.empty Map.empty cocoMapCategoryIds) $-    map (confusionMatrixForImage cocoMap iouThresh scoreThresh) cocoMapImageIds+mF1 :: CocoMap -> IOU -> Score -> (Double, [(CategoryId, Double)])+mF1 cocoMap@CocoMap {..} iouThresh scoreThresh =+  let categoryIds = cocoMapCategoryIds+      f1s = map (\categoryId -> f1ForCategory cocoMap categoryId iouThresh scoreThresh) categoryIds+      f1s' = f1s `using` parList rdeepseq+   in (sum f1s' / fromIntegral (length f1s'), zip categoryIds f1s') -confusionMatrixForImage :: CocoMap -> IOU -> Score -> ImageId -> ConfusionMatrix-confusionMatrixForImage cocoMap@CocoMap {..} iouThresh scoreThresh imageId =-  ConfusionMatrix-    { confusionMatrixRecall = recall,-      confusionMatrixPrecision = precision,-      confusionMatrixCategoryIds = cocoMapCategoryIds-    }-  where-    gts = fromMaybe [] $ Map.lookup imageId cocoMapCocoAnnotation-    dts = fromMaybe [] $ Map.lookup imageId cocoMapCocoResult-    for = flip map-    recall = foldl (Map.unionWith (Map.unionWith (+))) Map.empty $-      for gts $ \gt ->-        let iousWithDt = sortBy (\(iou0, _) (iou1, _) -> compare iou1 iou0) $ flip map dts $ \dt -> (iou (cocoAnnotationBbox gt) (cocoResultBbox dt), dt)-            filteredDt = filter (\(iou, dt) -> iou >= iouThresh && cocoResultScore dt >= scoreThresh) iousWithDt-            categoryFilter = filter (\(iou, dt) -> cocoAnnotationCategory gt == cocoResultCategory dt) filteredDt-         in case categoryFilter of-              [] ->-                case filteredDt of-                  [] -> Map.singleton (Gt $ cocoAnnotationCategory gt) $ Map.singleton DtBackground 1-                  (iou, dt) : _ -> Map.singleton (Gt $ cocoAnnotationCategory gt) $ Map.singleton (Dt $ cocoResultCategory dt) 1-              (iou, dt) : _ -> Map.singleton (Gt $ cocoAnnotationCategory gt) $ Map.singleton (Dt $ cocoResultCategory dt) 1-    precision = foldl (Map.unionWith (Map.unionWith (+))) Map.empty $-      for dts $ \dt ->-        let iousWithGt = sortBy (\(iou0, _) (iou1, _) -> compare iou1 iou0) $ flip map gts $ \gt -> (iou (cocoAnnotationBbox gt) (cocoResultBbox dt), gt)-            filteredGt = filter (\(iou, gt) -> iou >= iouThresh) iousWithGt-            categoryFilter = filter (\(iou, gt) -> cocoAnnotationCategory gt == cocoResultCategory dt) filteredGt-         in case categoryFilter of-              [] ->-                case filteredGt of-                  [] -> Map.singleton (Dt $ cocoResultCategory dt) $ Map.singleton GtBackground 1-                  (iou, gt) : _ -> Map.singleton (Dt $ cocoResultCategory dt) $ Map.singleton (Gt $ cocoAnnotationCategory gt) 1-              (iou, gt) : _ -> Map.singleton (Dt $ cocoResultCategory dt) $ Map.singleton (Gt $ cocoAnnotationCategory gt) 1+sortAndGroup :: (Ord k) => [(k, v)] -> Map k [v]+sortAndGroup assocs = Map.fromListWith (++) [(k, [v]) | (k, v) <- assocs]++average :: forall a f. (Num a, Foldable f, Fractional a) => f a -> a+average xs+  | null xs = 0+  | otherwise =+      uncurry (/)+        . foldl (\(!total, !count) x -> (total + x, count + 1)) (0, 0)+        $ xs
+ src/RiskWeaver/Pip.hs view
@@ -0,0 +1,26 @@+module RiskWeaver.Pip where++newtype Polygon = Polygon [(Double, Double)] deriving (Show, Eq)++newtype Point = Point (Double, Double) deriving (Show, Eq)++-- | When given a polygon and a point, returns True if the point is inside the polygon.+pointInPolygon :: Polygon -> Point -> Bool+pointInPolygon (Polygon polygon) (Point point) =+  let points = zip polygon (tail polygon)+      wn = flip map points $ \(vi, vii) ->+        if ((snd vi <= snd point) && (snd vii > snd point))+          then+            let vt = (snd point - snd vi) / (snd vii - snd vi)+             in if (fst point < (fst vi + (vt * (fst vii - fst vi))))+                  then 1+                  else 0+          else+            if ((snd vi > snd point) && (snd vii <= snd point))+              then+                let vt = (snd point - snd vi) / (snd vii - snd vi)+                 in if (fst point < (fst vi + (vt * (fst vii - fst vi))))+                      then -1+                      else 0+              else 0+   in (foldl (+) 0 wn) /= (0 :: Int)