fei-datasets-1.0.0: src/MXNet/NN/DataIter/PascalVOC.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
module MXNet.NN.DataIter.PascalVOC (
module MXNet.NN.DataIter.Common,
Configuration(..), VOCConfig, conf_width,
classes, vocMainImages, loadImageAndBBoxes
) where
import Control.Exception (throw)
import Control.Lens (makeLenses)
import Data.Array.Repa ((:.) (..), Z (..), fromListUnboxed)
import qualified Data.Array.Repa as Repa
import Data.Conduit
import qualified Data.Conduit.Combinators as C (yieldMany)
import qualified Data.Conduit.List as C
import qualified Data.Random as RND (runRVar, shuffleN,
stdUniform)
import Data.Random.Source.StdGen (StdGen)
import qualified Data.Vector.Storable as SV (unsafeCast)
import GHC.Float (double2Float)
import qualified Graphics.Image as HIP
import qualified Graphics.Image.Interface as HIP
import RIO
import qualified RIO.ByteString as B
import RIO.FilePath
import qualified RIO.Text as T
import qualified RIO.Vector.Boxed as V
import qualified RIO.Vector.Storable as SV
import Text.XML.Expat.Proc
import Text.XML.Expat.Tree
import MXNet.Base.ParserUtils (parseR, rational)
import MXNet.NN.DataIter.Common
data Exc = FileNotFound String String
| CannotParseAnnotation String
deriving Show
instance Exception Exc
classes :: V.Vector Text
classes = V.fromList [
"__background__", -- always index 0
"aeroplane", "bicycle", "bird", "boat",
"bottle", "bus", "car", "cat", "chair",
"cow", "diningtable", "dog", "horse",
"motorbike", "person", "pottedplant",
"sheep", "sofa", "train", "tvmonitor"]
data instance Configuration "voc" = VOCConfig {
_conf_base_dir :: FilePath,
_conf_width :: Int,
_conf_mean :: (Float, Float, Float),
_conf_std :: (Float, Float, Float)
}
makeLenses 'VOCConfig
type VOCConfig = Configuration "voc"
instance HasDatasetConfig VOCConfig where
type DatasetTag VOCConfig = "voc"
datasetConfig = id
instance ImageDataset "voc" where
imagesMean = conf_mean
imagesStdDev = conf_std
vocMainImages :: (MonadReader env m, HasDatasetConfig env, DatasetTag env ~ "voc", MonadIO m) =>
String -> IORef StdGen -> ConduitT () String m ()
vocMainImages datasplit rand_gen = do
base <- view (datasetConfig . conf_base_dir)
let imageset = base </> "ImageSets" </> "Main" </> datasplit <.> "txt"
content <- readFileUtf8 imageset
let image_list = T.lines content
all_images <- liftIO $ RND.runRVar (RND.shuffleN (length image_list) image_list) rand_gen
C.yieldMany all_images .| C.map T.unpack
loadImageAndBBoxes :: (MonadReader env m, HasDatasetConfig env, DatasetTag env ~ "voc", MonadIO m) =>
String -> m (Maybe (String, ImageTensor, ImageInfo, GTBoxes))
loadImageAndBBoxes ident = do
width <- view (datasetConfig . conf_width)
base <- view (datasetConfig . conf_base_dir)
let imgFilePath = base </> "JPEGImages" </> ident <.> "jpg"
imgRGB <- liftIO $ raiseLeft (FileNotFound imgFilePath) $
(HIP.readImageExact HIP.JPG imgFilePath)
let (imgH, imgW) = HIP.dims (imgRGB :: HIP.Image HIP.VS HIP.RGB Double)
imgH_ = fromIntegral imgH
imgW_ = fromIntegral imgW
width_ = fromIntegral width
(scale, imgW', imgH') = if imgW >= imgH
then (width_ / imgW_, width, floor (imgH_ * width_ / imgW_))
else (width_ / imgH_, floor (imgW_ * width_ / imgH_), width)
imgInfo = fromListUnboxed (Z :. 3) [fromIntegral imgH', fromIntegral imgW', scale]
imgResized = HIP.resize HIP.Bilinear HIP.Edge (imgH', imgW') imgRGB
imgPadded = HIP.canvasSize (HIP.Fill $ HIP.PixelRGB 0.5 0.5 0.5) (width, width) imgResized
imgRepa = Repa.fromUnboxed (Z:.width:.width:.3) $
SV.convert $
SV.unsafeCast $
HIP.toVector imgPadded
let annoFilePath = base </> "Annotations" </> ident <.> "xml"
xml <- liftIO $ B.readFile annoFilePath
gtBoxes <- case parse' defaultParseOptions xml of
Left err -> throw (CannotParseAnnotation annoFilePath) err
Right root -> do
let objs = findElements "object" (root :: Node Text Text)
return $ V.fromList $ catMaybes $ map (makeGTBox scale) objs
if V.null gtBoxes
then return Nothing
else do
imgEval <- transform $ Repa.map double2Float imgRepa
-- deepSeq the array so that the workload are well parallelized.
return $!! Just (ident, Repa.computeUnboxedS imgEval, imgInfo, gtBoxes)
where
makeGTBox scale node = do
className <- textContent <$> findElement "name" node
bndbox <- findElement "bndbox" node
xmin <- textContent <$> findElement "xmin" bndbox
xmax <- textContent <$> findElement "xmax" bndbox
ymin <- textContent <$> findElement "ymin" bndbox
ymax <- textContent <$> findElement "ymax" bndbox
classId <- V.elemIndex className classes
let x0 = parseR rational xmin
x1 = parseR rational xmax
y0 = parseR rational ymin
y1 = parseR rational ymax
return $ fromListUnboxed (Z :. 5) [x0*scale, y0*scale, x1*scale, y1*scale, fromIntegral classId]