repa-algorithms-2.0.0.1: Data/Array/Repa/Algorithms/Convolve.hs
{-# LANGUAGE BangPatterns, PackageImports #-}
{-# OPTIONS -Wall -fno-warn-missing-signatures -fno-warn-incomplete-patterns #-}
-- | Old support for stencil based convolutions.
--
-- NOTE: This is slated to be merged with the new Stencil support in the next version
-- of Repa. We'll still expose the `convolve` function though.
--
module Data.Array.Repa.Algorithms.Convolve
( convolve
, GetOut
, outAs
, outClamp
, convolveOut )
where
import Data.Array.Repa as A
import qualified Data.Vector.Unboxed as V
import qualified Data.Array.Repa.Shape as S
import Prelude as P
-- Plain Convolve ---------------------------------------------------------------------------------
-- | Image-kernel convolution,
-- which takes a function specifying what value to return when the kernel doesn't apply.
convolve
:: (Elt a, Num a)
=> (DIM2 -> a) -- ^ Use this function to get border elements where the kernel apply.
-> Array DIM2 a -- ^ Kernel to use in the convolution.
-> Array DIM2 a -- ^ Input image.
-> Array DIM2 a
{-# INLINE convolve #-}
convolve makeOut
kernel@(Array (_ :. krnHeight :. krnWidth) [Region RangeAll (GenManifest krnVec)])
image@(Array imgSh@(_ :. imgHeight :. imgWidth) [Region RangeAll (GenManifest imgVec)])
= kernel `deepSeqArray` image `deepSeqArray`
force $ unsafeTraverse image id update
where
!krnHeight2 = krnHeight `div` 2
!krnWidth2 = krnWidth `div` 2
-- If we're too close to the edge of the input image then
-- we can't apply the stencil because we don't have enough data.
!borderLeft = krnWidth2
!borderRight = imgWidth - krnWidth2 - 1
!borderUp = krnHeight2
!borderDown = imgHeight - krnHeight2 - 1
{-# INLINE update #-}
update _ ix@(_ :. j :. i)
| i < borderLeft = makeOut ix
| i > borderRight = makeOut ix
| j < borderUp = makeOut ix
| j > borderDown = makeOut ix
| otherwise = stencil j i
-- The actual stencil function.
{-# INLINE stencil #-}
stencil j i
= let imgStart = S.toIndex imgSh (Z :. j - krnHeight2 :. i - krnWidth2)
in integrate 0 0 0 imgStart 0
{-# INLINE integrate #-}
integrate !acc !x !y !imgCur !krnCur
| y >= krnHeight
= acc
| x >= krnWidth
= integrate acc 0 (y + 1) (imgCur + imgWidth - krnWidth) krnCur
| otherwise
= let imgZ = imgVec `V.unsafeIndex` imgCur
krnZ = krnVec `V.unsafeIndex` krnCur
here = imgZ * krnZ
in integrate (acc + here) (x + 1) y (imgCur + 1) (krnCur + 1)
-- Convolve Out -----------------------------------------------------------------------------------
-- | A function that gets out of range elements from an image.
type GetOut a
= (DIM2 -> a) -- ^ The original get function.
-> DIM2 -- ^ The shape of the image.
-> DIM2 -- ^ Index of element we were trying to get.
-> a
-- | Use the provided value for every out-of-range element.
outAs :: a -> GetOut a
{-# INLINE outAs #-}
outAs x _ _ _ = x
-- | If the requested element is out of range use
-- the closest one from the real image.
outClamp :: GetOut a
{-# INLINE outClamp #-}
outClamp get (_ :. yLen :. xLen) (sh :. j :. i)
= clampX j i
where {-# INLINE clampX #-}
clampX !y !x
| x < 0 = clampY y 0
| x >= xLen = clampY y (xLen - 1)
| otherwise = clampY y x
{-# INLINE clampY #-}
clampY !y !x
| y < 0 = get (sh :. 0 :. x)
| y >= yLen = get (sh :. (yLen - 1) :. x)
| otherwise = get (sh :. y :. x)
-- | Image-kernel convolution,
-- which takes a function specifying what value to use for out-of-range elements.
convolveOut
:: (Elt a, Num a)
=> GetOut a -- ^ Use this fn to get out of range elements.
-> Array DIM2 a -- ^ Kernel
-> Array DIM2 a -- ^ Image
-> Array DIM2 a
{-# INLINE convolveOut #-}
convolveOut getOut
kernel@(Array krnSh@(_ :. krnHeight :. krnWidth) _)
image@(Array imgSh@(_ :. imgHeight :. imgWidth) _)
= kernel `deepSeqArray` image `deepSeqArray`
force $ unsafeTraverse image id stencil
where
!krnHeight2 = krnHeight `div` 2
!krnWidth2 = krnWidth `div` 2
!krnSize = S.size krnSh
-- If we're too close to the edge of the input image then
-- we can't apply the stencil because we don't have enough data.
!borderLeft = krnWidth2
!borderRight = imgWidth - krnWidth2 - 1
!borderUp = krnHeight2
!borderDown = imgHeight - krnHeight2 - 1
-- The actual stencil function.
{-# INLINE stencil #-}
stencil get (_ :. j :. i)
= let
{-# INLINE get' #-}
get' ix@(_ :. y :. x)
| x < borderLeft = getOut get imgSh ix
| x > borderRight = getOut get imgSh ix
| y < borderUp = getOut get imgSh ix
| y > borderDown = getOut get imgSh ix
| otherwise = get ix
!ikrnWidth' = i - krnWidth2
!jkrnHeight' = j - krnHeight2
{-# INLINE integrate #-}
integrate !count !acc
| count == krnSize = acc
| otherwise
= let !ix@(sh :. y :. x) = S.fromIndex krnSh count
!ix' = sh :. y + jkrnHeight' :. x + ikrnWidth'
!here = kernel `unsafeIndex` ix * (get' ix')
in integrate (count + 1) (acc + here)
in integrate 0 0