risk-weaver-0.1.0.0: src/RiskWeaver/Format/Coco.hs
{-
This module provides COCO format parser of object detection dataset.
Aeson is used for parsing JSON.
-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE Strict #-}
{-# LANGUAGE TypeFamilies #-}
module RiskWeaver.Format.Coco where
import Codec.Picture.Metadata (Value (Double))
import Control.Monad (ap)
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
newtype ImageId = ImageId {unImageId :: Int} deriving (Show, Ord, Eq, Generic)
newtype CategoryId = CategoryId {unCategoryId :: Int} deriving (Show, Ord, Eq, Generic)
newtype Score = Score {unScore :: Double} deriving (Show, Eq, Ord, Num, Fractional, Floating, Real, RealFrac, RealFloat, Generic)
instance FromJSON ImageId where
parseJSON = withScientific "image_id" $ \n -> do
return $ ImageId $ round n
instance ToJSON ImageId where
toJSON (ImageId n) = toJSON n
instance FromJSON CategoryId where
parseJSON = withScientific "category_id" $ \n -> do
return $ CategoryId $ round n
instance ToJSON CategoryId where
toJSON (CategoryId n) = toJSON n
instance FromJSON Score where
parseJSON = withScientific "score" $ \n -> do
return $ Score $ realToFrac n
instance ToJSON Score where
toJSON (Score n) = toJSON n
data CocoInfo = CocoInfo
{ cocoInfoYear :: Int,
cocoInfoVersion :: Text,
cocoInfoDescription :: Text,
cocoInfoContributor :: Text,
cocoInfoUrl :: Text,
cocoInfoDateCreated :: Text
}
deriving (Show, Eq, Generic)
instance FromJSON CocoInfo where
parseJSON = withObject "info" $ \o -> do
cocoInfoYear <- o .: "year"
cocoInfoVersion <- o .: "version"
cocoInfoDescription <- o .: "description"
cocoInfoContributor <- o .: "contributor"
cocoInfoUrl <- o .: "url"
cocoInfoDateCreated <- o .: "date_created"
return CocoInfo {..}
instance ToJSON CocoInfo where
toJSON CocoInfo {..} =
object
[ "year" .= cocoInfoYear,
"version" .= cocoInfoVersion,
"description" .= cocoInfoDescription,
"contributor" .= cocoInfoContributor,
"url" .= cocoInfoUrl,
"date_created" .= cocoInfoDateCreated
]
data CocoLicense = CocoLicense
{ cocoLicenseId :: Int,
cocoLicenseName :: Text,
cocoLicenseUrl :: Text
}
deriving (Show, Eq, Generic)
instance FromJSON CocoLicense where
parseJSON = withObject "license" $ \o -> do
cocoLicenseId <- o .: "id"
cocoLicenseName <- o .: "name"
cocoLicenseUrl <- o .: "url"
return CocoLicense {..}
instance ToJSON CocoLicense where
toJSON CocoLicense {..} =
object
[ "id" .= cocoLicenseId,
"name" .= cocoLicenseName,
"url" .= cocoLicenseUrl
]
data CocoImage = CocoImage
{ cocoImageId :: ImageId,
cocoImageWidth :: Int,
cocoImageHeight :: Int,
cocoImageFileName :: Text,
cocoImageLicense :: Maybe Int,
cocoImageDateCoco :: Maybe Text
}
deriving (Show, Eq, Generic)
instance FromJSON CocoImage where
parseJSON = withObject "image" $ \o -> do
cocoImageId <- o .: "id"
cocoImageWidth <- o .: "width"
cocoImageHeight <- o .: "height"
cocoImageFileName <- o .: "file_name"
cocoImageLicense <- o .:? "license"
cocoImageDateCoco <- o .:? "date_captured"
return CocoImage {..}
instance ToJSON CocoImage where
toJSON CocoImage {..} =
object
[ "id" .= cocoImageId,
"width" .= cocoImageWidth,
"height" .= cocoImageHeight,
"file_name" .= cocoImageFileName,
"license" .= cocoImageLicense,
"date_captured" .= cocoImageDateCoco
]
newtype CoCoBoundingBox
= CoCoBoundingBox (Double, Double, Double, Double)
deriving (Show, Eq, Generic)
-- (x, y, width, height)
data CocoAnnotation = CocoAnnotation
{ cocoAnnotationId :: Int,
cocoAnnotationImageId :: ImageId,
cocoAnnotationCategory :: CategoryId,
cocoAnnotationSegment :: Maybe [[Double]], -- [[x1, y1, x2, y2, ...]]
cocoAnnotationArea :: Double,
cocoAnnotationBbox :: CoCoBoundingBox,
cocoAnnotationIsCrowd :: Maybe Int
}
deriving (Show, Eq, Generic)
instance FromJSON CocoAnnotation where
parseJSON = withObject "annotation" $ \o -> do
cocoAnnotationId <- o .: "id"
cocoAnnotationImageId <- o .: "image_id"
cocoAnnotationCategory <- o .: "category_id"
cocoAnnotationSegment <- o .:? "segmentation"
cocoAnnotationArea <- o .: "area"
cocoAnnotationBbox <- fmap (\[x, y, w, h] -> CoCoBoundingBox (x, y, w, h)) $ o .: "bbox"
cocoAnnotationIsCrowd <- o .:? "iscrowd"
return CocoAnnotation {..}
instance ToJSON CocoAnnotation where
toJSON CocoAnnotation {..} =
object
[ "id" .= cocoAnnotationId,
"image_id" .= cocoAnnotationImageId,
"category_id" .= cocoAnnotationCategory,
"segmentation" .= cocoAnnotationSegment,
"area" .= cocoAnnotationArea,
"bbox" .= case cocoAnnotationBbox of CoCoBoundingBox (x, y, w, h) -> [x, y, w, h],
"iscrowd" .= cocoAnnotationIsCrowd
]
data CocoCategory = CocoCategory
{ cocoCategoryId :: CategoryId,
cocoCategoryName :: Text,
cocoCategorySupercategory :: Text
}
deriving (Show, Eq, Generic)
instance FromJSON CocoCategory where
parseJSON = withObject "category" $ \o -> do
cocoCategoryId <- o .: "id"
cocoCategoryName <- o .: "name"
cocoCategorySupercategory <- o .: "supercategory"
return CocoCategory {..}
instance ToJSON CocoCategory where
toJSON CocoCategory {..} =
object
[ "id" .= cocoCategoryId,
"name" .= cocoCategoryName,
"supercategory" .= cocoCategorySupercategory
]
data Coco = Coco
{ cocoInfo :: Maybe CocoInfo,
cocoLicenses :: Maybe [CocoLicense],
cocoImages :: [CocoImage],
cocoAnnotations :: [CocoAnnotation],
cocoCategories :: [CocoCategory]
}
deriving (Show, Eq, Generic)
instance FromJSON Coco where
parseJSON = withObject "coco" $ \o -> do
cocoInfo <- o .:? "info"
cocoLicenses <- o .:? "licenses"
cocoImages <- o .: "images"
cocoAnnotations <- o .: "annotations"
cocoCategories <- o .: "categories"
return Coco {..}
instance ToJSON Coco where
toJSON Coco {..} =
object
[ "info" .= cocoInfo,
"licenses" .= cocoLicenses,
"images" .= cocoImages,
"annotations" .= cocoAnnotations,
"categories" .= cocoCategories
]
-- Coco result format is shown in https://cocodataset.org/#format-results .
data CocoResult = CocoResult
{ cocoResultImageId :: ImageId,
cocoResultCategory :: CategoryId,
cocoResultScore :: Score,
cocoResultBbox :: CoCoBoundingBox
}
deriving (Show, Eq, Generic)
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"
return CocoResult {..}
instance ToJSON CocoResult where
toJSON CocoResult {..} =
object
[ "image_id" .= cocoResultImageId,
"category_id" .= cocoResultCategory,
"score" .= cocoResultScore,
"bbox" .= case cocoResultBbox of CoCoBoundingBox (x, y, w, h) -> [x, y, w, h]
]
readCoco :: FilePath -> IO Coco
readCoco path = do
json <- BS.readFile path
case eitherDecode json of
Left err -> error err
Right coco -> return coco
writeCoco :: FilePath -> Coco -> IO ()
writeCoco path coco = BS.writeFile path $ encode coco
readCocoResult :: FilePath -> IO [CocoResult]
readCocoResult path = do
json <- BS.readFile path
case eitherDecode json of
Left err -> error err
Right coco -> return coco
writeCocoResult :: FilePath -> [CocoResult] -> IO ()
writeCocoResult path coco = BS.writeFile path $ encode coco
getCocoImageByFileName :: Coco -> FilePath -> Maybe (CocoImage, [CocoAnnotation])
getCocoImageByFileName coco fileName =
case filter (\CocoImage {..} -> T.unpack cocoImageFileName == fileName) $ cocoImages coco of
[] -> Nothing
(x : _) ->
let annotations = filter (\CocoAnnotation {..} -> cocoAnnotationImageId == cocoImageId x) $ cocoAnnotations coco
in Just (x, annotations)
getCocoResultByFileName :: Coco -> [CocoResult] -> FilePath -> Maybe (CocoImage, [CocoResult])
getCocoResultByFileName coco cocoResult fileName =
case filter (\CocoImage {..} -> T.unpack cocoImageFileName == fileName) $ cocoImages coco of
[] -> Nothing
(x : _) ->
let results = filter (\CocoResult {..} -> cocoResultImageId == cocoImageId x) cocoResult
in Just (x, results)
toCocoImageMap :: Coco -> Map.Map ImageId CocoImage
toCocoImageMap coco = Map.fromList $ map (\image -> (cocoImageId image, image)) $ cocoImages coco
toCocoAnnotationMap :: Coco -> Map.Map ImageId [CocoAnnotation]
toCocoAnnotationMap coco = Map.fromListWith (++) $ map (\annotation -> (cocoAnnotationImageId annotation, [annotation])) $ cocoAnnotations coco
toCategoryMap :: Coco -> Map.Map CategoryId CocoCategory
toCategoryMap coco = Map.fromList $ map (\category -> (cocoCategoryId category, category)) $ cocoCategories coco
toFilepathMap :: Coco -> Map.Map ImageId FilePath
toFilepathMap coco = Map.fromList $ map (\image -> (cocoImageId image, T.unpack $ cocoImageFileName image)) $ cocoImages coco
-- | Convert coco to image id map
-- | Key is image file name, and value is a list of image id
toImageId :: Coco -> Map.Map FilePath [ImageId]
toImageId coco = Map.fromListWith (++) $ map (\image -> (T.unpack $ cocoImageFileName image, [cocoImageId image])) $ cocoImages coco
toCocoResultMap :: [CocoResult] -> Map.Map ImageId [CocoResult]
toCocoResultMap cocoResult = Map.fromListWith (++) $ map (\result -> (cocoResultImageId result, [result])) cocoResult
data CocoMap = CocoMap
{ cocoMapImageId :: Map.Map FilePath [ImageId],
cocoMapCocoImage :: Map.Map ImageId CocoImage,
cocoMapCocoAnnotation :: Map.Map ImageId [CocoAnnotation],
cocoMapCocoCategory :: Map.Map CategoryId CocoCategory,
cocoMapCocoResult :: Map.Map ImageId [CocoResult],
cocoMapFilepath :: Map.Map ImageId FilePath,
cocoMapImageIds :: [ImageId],
cocoMapCategoryIds :: [CategoryId]
}
deriving (Show, Eq, Generic)
toCocoMap :: Coco -> [CocoResult] -> CocoMap
toCocoMap coco cocoResult =
let cocoMapImageId = toImageId coco
cocoMapCocoImage = toCocoImageMap coco
cocoMapCocoAnnotation = toCocoAnnotationMap coco
cocoMapCocoCategory = toCategoryMap coco
cocoMapCocoResult = toCocoResultMap cocoResult
cocoMapFilepath = toFilepathMap coco
cocoMapImageIds = map (\CocoImage {..} -> cocoImageId) $ cocoImages coco
cocoMapCategoryIds = map (\CocoCategory {..} -> cocoCategoryId) $ cocoCategories coco
in CocoMap {..}