module Vision.Image.Contour (
Contours(..), ContourId, OneContour, ContourValue, Contour(..), RowContour
, contours
, allContourIds, lookupContour, rowContour, contourSize, contourPerimeter
, ContourDrawStyle(..), drawContour, drawContours
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Monad (when)
import Control.Monad.ST
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import Data.Maybe (catMaybes)
import Data.List (groupBy,sort)
import Data.Function (on)
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VM
import Foreign.Storable
import Vision.Image.Mutable (MutableManifest, new', write)
import qualified Vision.Image.Mutable as Mut
import Vision.Image.Grey (Grey,GreyPixel)
import Vision.Image.Type (Delayed)
import Vision.Image.Class (
MaskedImage (..), Image (..), FromFunction (..), index
)
import Vision.Primitive (
Z (..), (:.) (..), Point, ix2, Size
)
data Contours =
Contours { contourOutlines :: Map ContourId Contour
, contourSizes :: !(VU.Vector Int)
}
allContourIds :: Contours -> [ContourId]
allContourIds = Map.keys . contourOutlines
contourPerimeter :: Contours -> ContourId -> [Point]
contourPerimeter m i =
maybe [] (map fst . VU.toList . outerContour) (lookupContour m i)
contourSize :: Contours -> ContourId -> Int
contourSize (Contours _ s) i
| unCID i < 0 || unCID i >= VU.length s = 0
| otherwise = s VU.! unCID i
lookupContour :: Contours -> ContourId -> Maybe Contour
lookupContour m i = Map.lookup i (contourOutlines m)
newtype ContourId = CID { unCID :: Int } deriving (Eq, Ord, Storable, Num, Show)
type OneContour = VU.Vector ContourValue
type ContourValue = (Point,Bool)
data Contour = Contour { outerContour :: OneContour
, innerContours :: [OneContour]
}
insOuterContour :: ContourId -> OneContour -> Map ContourId Contour
-> Map ContourId Contour
insOuterContour cid o mp =
let c = Contour o []
in Map.insert cid c mp
insInnerContour :: ContourId -> OneContour -> Map ContourId Contour
-> Map ContourId Contour
insInnerContour cid i mp =
let c = Contour (error "Impossible: Inner contour with no outer!") [i]
f _ (Contour o is) = Contour o (i:is)
in Map.insertWith f cid c mp
type RowContour = VU.Vector (Point,Point)
data ContourDrawStyle = OuterOutline | AllOutlines | Fill | FillWithHoles
deriving (Eq, Ord, Show, Read, Enum, Bounded)
drawContour :: Contours -> Size -> ContourDrawStyle -> ContourId -> Grey
drawContour master sz sty c = drawContours master sz sty [c]
drawContours :: Contours -> Size -> ContourDrawStyle -> [ContourId] -> Grey
drawContours m sz AllOutlines ids = drawOutlines listOfUVec m ids sz
where listOfUVec (Contour o is) = o:is
drawContours m sz OuterOutline ids = drawOutlines listOfUVec m ids sz
where listOfUVec (Contour o _) = [o]
drawContours m sz sty ids = drawRows pnts sz
where lk = lookupContour m
pnts = case sty of
Fill -> map (VU.toList . outerContour) $ catMaybes $ map lk ids
FillWithHoles -> map (concatMap VU.toList . maybe [] (\x -> outerContour x : innerContours x) . lk) ids
_ -> error "Impossible: Style is not Fill, FillWithHoles"
drawOutlines :: (Contour -> [VU.Vector ContourValue]) -> Contours -> [ContourId] -> Size -> Grey
drawOutlines oper m ids sz = runST f
where
f = do
i <- new' sz 0 :: ST s (MutableManifest GreyPixel s)
let vs = map fst $ concatMap VU.toList $ concatMap oper $ catMaybes $ map (lookupContour m) ids
mapM_ (\p -> write i p 255) vs
Mut.unsafeFreeze i
drawRows :: [[ContourValue]] -> Size -> Grey
drawRows vs sz = runST $ do
i <- new' sz 0
mapM_ (drawMutable i) vs
Mut.unsafeFreeze i
drawMutable :: MutableManifest GreyPixel s -> [ContourValue] -> ST s ()
drawMutable i cs = VU.mapM_ (f i) rs
where
rs = rowContour cs
f img (start,stop) = go (start, stop)
where go (s@(Z:.row:.col),t) = do
write img s 255
when (s /= t) $ go (Z :. row :. (col+1),t)
rowContour :: [ContourValue] -> RowContour
rowContour cs =
let rows :: [[(Point,Bool)]]
rows = groupBy ((==) `on` ((\(Z:.r:._) -> r) . fst)) $ sort cs
in VU.fromList $ concatMap walkM rows
where
walkM :: [(Point,Bool)] -> [(Point,Point)]
walkM [x] = [(fst x,fst x)]
walkM x = maybe (error $ "Impossible: No terminal when walking contour: " ++ show (x,cs)) id $ walk x
walk :: [(Point,Bool)] -> Maybe [(Point,Point)]
walk [] = Just []
walk xs@(x:_) = case dropWhile (not . snd) xs of
[] -> Nothing
(t:ys) -> ((fst x,fst t) :) <$> walk ys
contours :: (Image src, Num (ImagePixel src), Eq (ImagePixel src)) => src -> Contours
contours src = runST $ do
let bsrc = fromFunction (Z :. y+2 :. x+2) mkBorder
mutImg <- new' (shape bsrc) zid
(outlines,sz) <- doLabeling bsrc mutImg
sizes <- freezeBlobSizes sz
return (Contours outlines sizes)
where
(Z :. y :. x) = shape src
mkBorder (Z :. j :. i)
| j == 0 || j == (y+1) || i == 0 || i == (x+1) = background
| otherwise = index src (Z :. j1 :. i1)
background :: Num a => a
background = 0
zid :: ContourId
zid = CID 0
data BlobSizes s = BS (VM.MVector s Int)
freezeBlobSizes :: BlobSizes s -> ST s (VU.Vector Int)
freezeBlobSizes (BS v) = VU.unsafeFreeze v
incBlobSizes :: ContourId -> BlobSizes s -> ST s (BlobSizes s)
incBlobSizes (CID i) s@(BS v)
| i > 0 =
if VM.length v <= i
then do nv <- VM.unsafeGrow v (i*2)
mapM_ (\ix -> VM.unsafeWrite nv ix 0) [i..i*21]
VM.unsafeWrite nv i 1
return (BS nv)
else do p <- VM.unsafeRead v i
VM.unsafeWrite v i (p+1)
return s
| otherwise = return s
zeroBlobSizes :: ST s (BlobSizes s)
zeroBlobSizes = BS <$> VM.replicate 1024 0
doLabeling :: forall s p. (Storable p, Num p, Eq p) => Delayed p -> MutableManifest ContourId s -> ST s (Map ContourId Contour,BlobSizes s)
doLabeling src mutImg = zeroBlobSizes >>= go (Just $ ix2 1 1) (CID 0) (CID 1) mempty
where
getCID :: Point -> ST s ContourId
getCID = Mut.read mutImg
setCID i c = write mutImg i c
getPixel :: Point -> ImagePixel (Delayed p)
getPixel = index src
incIx :: Point -> Maybe Point
incIx !(Z :. (!y) :. (!x))
| x < xMax1 = Just $ Z :. y :. (x+1)
| y < yMax1 = Just $ Z :. (y+1) :. 1
| otherwise = Nothing
(Z :. yMax :. xMax) = shape src
go Nothing _ _ !mp v = return (mp,v)
go (Just idx) leftCID !newCID !mp v =
do thisCID <- getCID idx
if | val == background -> skipForward
| thisCID == zid && above == background ->
do
newContour <- traceContour src mutImg ExternalContour idx newCID
go (Just idx) newCID (newCID + 1) (insOuterContour newCID newContour mp) v
| below == background ->
do belowCID <- getCID belowIdx
if | belowCID == zid ->
do
let innerCID = if zid == thisCID then leftCID else thisCID
inner <- traceContour src mutImg InternalContour idx innerCID
go (incIx idx) innerCID newCID (insInnerContour innerCID inner mp) v
| otherwise -> stepForward
| otherwise -> stepForward
where val = getPixel idx
above = getPixel (Z :. y1 :. x)
below = getPixel belowIdx
belowIdx = Z :. y+1 :. x
Z :. y :. x = idx
stepForward = do xId <- if leftCID <= zid
then getCID idx
else return leftCID
setCID idx xId
nv <- incBlobSizes xId v
go (incIx idx) xId newCID mp nv
skipForward = go (incIx idx) (2) newCID mp v
traceContour :: forall p s. (Storable p, Num p, Eq p) => Delayed p -> MutableManifest ContourId s -> ContourType -> Point -> ContourId -> ST s OneContour
traceContour src mutImg contourTy origPnt assignedCID =
do next <- tracer origPnt startPos
case next of
Nothing -> return (VU.fromList $ fixList [(origPnt,True)])
Just (sndPnt,sndPos) -> do
let f pnt pos = do (nPnt,nPos) <- maybe (error "Impossible: Nothing in inner") id <$> tracer pnt pos
if pnt == origPnt && nPnt == sndPnt
then return []
else ((pnt,terminal pnt) :) <$> f nPnt nPos
VU.fromList . fixList . ((origPnt, terminal origPnt):) <$> f sndPnt sndPos
where
terminal (Z :. row :. col) = 0 == getPixel (Z :. row :. (col+1))
fixList xs = let f (Z :. a :. b, t) = (Z :. a1 :. b1,t) in map f xs
startPos = case contourTy of { ExternalContour -> UR ; InternalContour -> LL }
setCID i c = write mutImg i c
getPixel :: Point -> ImagePixel (Delayed p)
getPixel = index src
tracer pnt pos =
let tracer' True p | p == pos = setCID pnt assignedCID >> return Nothing
tracer' _ p = do let rpnt = relPoint pnt p
v = getPixel rpnt
if | v == background -> do setCID rpnt (1)
tracer' True (incCP p)
| otherwise -> do setCID pnt assignedCID
return (Just (rpnt, decCP2 p))
in tracer' False pos
data ContourType = ExternalContour | InternalContour deriving (Eq)
data ContourPos = MR | LR | LC | LL
| ML | UL | UC | UR
deriving (Enum, Bounded, Eq, Show)
relPoint :: Point -> ContourPos -> Point
relPoint (Z :. row :. col) pos = Z :. row' :. col'
where !row' = row + y
!col' = col + x
x = colOffset VU.! fromEnum pos
y = rowOffset VU.! fromEnum pos
colOffset,rowOffset :: VU.Vector Int
rowOffset = VU.fromList [0,1,1,1,0,1,1,1]
colOffset = VU.fromList [1,1,0,1,1,1,0,1]
incCP :: ContourPos -> ContourPos
incCP = toEnum . ((`rem` 8) . (+ 1)) . fromEnum
decCP2 :: ContourPos -> ContourPos
decCP2 = toEnum . ((`rem` 8) . (+ 6)) . fromEnum