packages feed

repa-examples-3.2.5.1: examples/Canny/src-repa/Main.hs

{-# LANGUAGE PackageImports, BangPatterns, QuasiQuotes, PatternGuards, 
             MagicHash, ScopedTypeVariables #-}
{-# OPTIONS -Wall -fno-warn-missing-signatures -fno-warn-incomplete-patterns #-}

-- | Canny edge detector.
--
--   NOTE: for best performance this needs to be compiled with the following GHC options:
--         -fllvm -optlo-O3 -Odph -fno-liberate-case
--         -funfolding-use-threshold100 -funfolding-keeness-factor100
--
import Data.List
import Data.Word
import Data.Int
import Control.Monad
import System.Environment
import Data.Array.Repa 				as R
import Data.Array.Repa.Repr.Unboxed             as U
import Data.Array.Repa.Repr.Cursored            as C
import Data.Array.Repa.Stencil
import Data.Array.Repa.Stencil.Dim2
import Data.Array.Repa.Specialised.Dim2
import Data.Array.Repa.Algorithms.Pixel
import Data.Array.Repa.IO.BMP
import Data.Array.Repa.IO.Timing
import Debug.Trace
import GHC.Exts
import qualified Data.Vector.Unboxed.Mutable	as VM
import qualified Data.Vector.Unboxed		as V
import qualified Prelude			as P
import Prelude                                  hiding (compare)


type Image a	= Array U DIM2 a

-- Constants ------------------------------------------------------------------
orientUndef	= 0	:: Word8
orientPosDiag	= 64	:: Word8
orientVert	= 128	:: Word8
orientNegDiag	= 192	:: Word8
orientHoriz	= 255	:: Word8

data Edge	= None | Weak | Strong
edge None	= 0 	:: Word8
edge Weak	= 128 	:: Word8
edge Strong	= 255	:: Word8


-- Main routine ---------------------------------------------------------------
main 
 = do	args	<- getArgs
	case args of
	 [fileIn, fileOut]		
	   -> run 0 50 100 fileIn fileOut

	 [loops, threshLow, threshHigh, fileIn, fileOut]
	   -> run (read loops) (read threshLow) (read threshHigh) fileIn fileOut

	 _ -> putStrLn 
           $ concat [ "repa-canny [<loops::Int> <threshLow::Int> <threshHigh::Int>]"
                    , " <fileIn.bmp> <fileOut.bmp>" ]


run loops threshLow threshHigh fileIn fileOut
 = do	arrInput <- liftM (either (error . show) id) 
		 $  readImageFromBMP fileIn

	(arrResult, tTotal)
	 <- time $ process loops threshLow threshHigh arrInput

	when (loops >= 1)
	 $ putStrLn $ "\nTOTAL\n"
	
	putStr $ prettyTime tTotal
	
	writeImageToBMP fileOut (U.zip3 arrResult arrResult arrResult)


process loops threshLow threshHigh arrInput
 = do   arrGrey         <- timeStage loops "toGreyScale"
                        $  toGreyScale    arrInput

        arrBluredX      <- timeStage loops "blurX"
                        $  blurSepX arrGrey

        arrBlured       <- timeStage loops "blurY"
                        $  blurSepY arrBluredX


        arrDX           <- timeStage loops "diffX"        
                        $  gradientX arrBlured

        arrDY           <- timeStage loops "diffY"
                        $  gradientY arrBlured
                
        arrMagOrient    <- timeStage loops "magOrient"   
                        $  gradientMagOrient threshLow arrDX arrDY

        arrSuppress     <- timeStage loops "suppress"     
                        $  suppress threshLow threshHigh arrMagOrient

        arrStrong       <- timeStage loops "select"
                        $  selectStrong arrSuppress   

        arrEdges        <- timeStage loops "wildfire"
                        $  wildfire arrSuppress arrStrong     

        return arrEdges


-- | Wrapper to time each stage of the algorithm.
timeStage
	:: (Shape sh, Unbox a)
	=> Int
	-> String 
	-> IO (Array U sh a)
	-> IO (Array U sh a)

timeStage loops name fn
 = do	
        let burn !n
	     = do !arr	<- fn
		  if n <= 1 then return arr
		            else burn (n - 1)

	traceEventIO $ "**** Stage " P.++ name P.++ " begin."
		
	(arrResult, t)
	 <- time $ do  !arrResult' <- burn loops
	               return arrResult'

        traceEventIO $ "**** Stage " P.++ name P.++ " end."

	when (loops >= 1) 
	 $ putStr 	$  name P.++ "\n"
			P.++ unlines [ "  " P.++ l | l <- lines $ prettyTime t ]

	return arrResult
{-# NOINLINE timeStage #-}


-------------------------------------------------------------------------------
-- | RGB to greyscale conversion.
toGreyScale :: Image (Word8, Word8, Word8) -> IO (Image Float)
toGreyScale arr
        = computeP
        $ R.map (* 255)
        $ R.map floatLuminanceOfRGB8 arr 
{-# NOINLINE toGreyScale #-}


-- | Separable Gaussian blur in the X direction.
blurSepX :: Image Float -> IO (Image Float)
blurSepX arr
        = computeP
        $ forStencil2  BoundClamp arr
          [stencil2|	1 4 6 4 1 |]	
{-# NOINLINE blurSepX #-}


-- | Separable Gaussian blur in the Y direction.
blurSepY :: Image Float -> IO (Image Float)
blurSepY arr
	= computeP
	$ R.smap (/ 256)
	$ forStencil2  BoundClamp arr
	  [stencil2|	1
	 		4
			6
			4
			1 |]
{-# NOINLINE blurSepY #-}


-- | Compute gradient in the X direction.
gradientX :: Image Float -> IO (Image Float)
gradientX img
 	= computeP
    	$ forStencil2 BoundClamp img
	  [stencil2|	-1  0  1
			-2  0  2
			-1  0  1 |]
{-# NOINLINE gradientX #-}


-- | Compute gradient in the Y direction.
gradientY :: Image Float -> IO (Image Float)
gradientY img
	= computeP
	$ forStencil2 BoundClamp img
	  [stencil2|	 1  2  1
			 0  0  0
			-1 -2 -1 |] 
{-# NOINLINE gradientY #-}


-- | Classify the magnitude and orientation of the vector gradient.
gradientMagOrient 
        :: Float -> Image Float -> Image Float -> IO (Image (Float, Word8))

gradientMagOrient !threshLow dX dY
        = computeP
        $ R.zipWith magOrient dX dY

 where	magOrient :: Float -> Float -> (Float, Word8)
	magOrient !x !y
		= (magnitude x y, orientation x y)
	{-# INLINE magOrient #-}
        
	magnitude :: Float -> Float -> Float
	magnitude !x !y
		= sqrt (x * x + y * y)
        {-# INLINE magnitude #-}
	
        {-# INLINE orientation #-}
	orientation :: Float -> Float -> Word8
	orientation !x !y

	 -- Don't bother computing orientation if vector is below threshold.
 	 | x >= negate threshLow, x < threshLow
 	 , y >= negate threshLow, y < threshLow
 	 = orientUndef 

	 | otherwise
	 = let	-- Determine the angle of the vector and rotate it around a bit
		-- to make the segments easier to classify.
		!d	= atan2 y x 
		!dRot	= (d - (pi/8)) * (4/pi)
	
		-- Normalise angle to beween 0..8
		!dNorm	= if dRot < 0 then dRot + 8 else dRot

		-- Doing explicit tests seems to be faster than using the FP floor function.
	   in fromIntegral 
               $ I# (if dNorm >= 4
		     then if dNorm >= 6
	   		  then if dNorm >= 7
			  	then 255#               -- 7
				else 192#               -- 6

			  else if dNorm >= 5
				then 128#               -- 5
				else 64#                -- 4

		     else if dNorm >= 2
			then if dNorm >= 3
				then 255#               -- 3
				else 192#               -- 2

			else if dNorm >= 1
				then 128#               -- 1
				else 64#)               -- 0
{-# NOINLINE gradientMagOrient #-}


-- | Suppress pixels that are not local maxima, and use the magnitude to classify maxima
--   into strong and weak (potential) edges.
suppress :: Float -> Float -> Image (Float, Word8) -> IO (Image Word8)
suppress !threshLow !threshHigh !dMagOrient
 = computeP
 $ makeBordered2 
        (extent dMagOrient) 1 
 	(makeCursored (extent dMagOrient) id addDim comparePts)
 	(fromFunction (extent dMagOrient) (const 0))

 where	{-# INLINE comparePts #-}
	comparePts d@(sh :. i :. j)
	 | o == orientUndef     = edge None
         | o == orientHoriz	= isMax (getMag (sh :. i   :. j-1)) (getMag (sh :. i   :. j+1)) 
         | o == orientVert	= isMax (getMag (sh :. i-1 :. j))   (getMag (sh :. i+1 :. j)) 
         | o == orientNegDiag   = isMax (getMag (sh :. i-1 :. j-1)) (getMag (sh :. i+1 :. j+1)) 
         | o == orientPosDiag   = isMax (getMag (sh :. i-1 :. j+1)) (getMag (sh :. i+1 :. j-1)) 
         | otherwise            = edge None
      
         where
          !o 		= getOrient d  
          !m		= getMag    (Z :. i :. j)

	  getMag 	= fst . (R.unsafeIndex dMagOrient)
	  getOrient	= snd . (R.unsafeIndex dMagOrient)

	  {-# INLINE isMax #-}
          isMax !intensity1 !intensity2
            | m < threshLow 	= edge None
            | m < intensity1 	= edge None
            | m < intensity2 	= edge None
	    | m < threshHigh	= edge Weak
	    | otherwise		= edge Strong
{-# NOINLINE suppress #-}


-- | Select indices of strong edges.
--   TODO: If would better if we could medge this into the above stage, and
--         record the strong edge during non-maximum suppression, but Repa
--         doesn't provide a fused mapFilter primitive yet.
selectStrong :: Image Word8 -> IO (Array U DIM1 Int)
selectStrong img
 = let 	vec             = toUnboxed img

	match ix	= vec `V.unsafeIndex` ix == edge Strong
        {-# INLINE match #-}
        
	process' ix	= ix
        {-# INLINE process' #-}
	
   in	selectP match process' (size $ extent img)
{-# NOINLINE selectStrong #-}


-- | Trace out strong edges in the final image. 
--   Also trace out weak edges that are connected to strong edges.
wildfire 
        :: Image Word8	     -- ^ Image with strong and weak edges set.
	-> Array U DIM1 Int  -- ^ Array containing flat indices of strong edges.
	-> IO (Image Word8)

wildfire img arrStrong
 = do	(sh, vec)	<- wildfireIO 
	return	$ sh `seq` vec `seq` fromUnboxed sh vec

 where	lenImg		= R.size $ R.extent img
	lenStrong	= R.size $ R.extent arrStrong
	vStrong		= toUnboxed arrStrong
	
	wildfireIO
  	 = do	-- Stack of image indices we still need to consider.
		vStrong' <- V.thaw vStrong
		vStack	 <- VM.grow vStrong' (lenImg - lenStrong)
	
		-- Burn in new edges.
		vImg	<- VM.unsafeNew lenImg
		VM.set vImg 0
		burn vImg vStack lenStrong
		vImg'	<- V.unsafeFreeze vImg
		return	(extent img, vImg')

	
	burn :: VM.IOVector Word8 -> VM.IOVector Int -> Int -> IO ()
	burn !vImg !vStack !top
	 | top == 0
	 = return ()
	
	 | otherwise
	 = do	let !top'		=  top - 1
		n			<- VM.unsafeRead vStack top'
		let (Z :. y :. x)	= fromIndex (R.extent img) n

		let {-# INLINE push #-}
		    push t		= pushWeak vImg vStack t
				
		VM.write vImg n (edge Strong)
	    	 >>  push (Z :. y - 1 :. x - 1) top'
	    	 >>= push (Z :. y - 1 :. x    )
	    	 >>= push (Z :. y - 1 :. x + 1)

	    	 >>= push (Z :. y     :. x - 1)
	    	 >>= push (Z :. y     :. x + 1)

	    	 >>= push (Z :. y + 1 :. x - 1)
	    	 >>= push (Z :. y + 1 :. x    )
	    	 >>= push (Z :. y + 1 :. x + 1)

	    	 >>= burn vImg vStack

	-- If this ix is weak in the source then set it to strong in the
	-- result and push the ix onto the stack.
	{-# INLINE pushWeak #-}
	pushWeak vImg vStack ix top
	 = do	let n		= toIndex (extent img) ix
		xDst		<- VM.unsafeRead vImg n
		let xSrc	= img `R.unsafeIndex` ix

		if   xDst == edge None 
		  && xSrc == edge Weak
		 then do
			VM.unsafeWrite vStack top (toIndex (extent img) ix)
			return (top + 1)
			
		 else	return top
{-# NOINLINE wildfire #-}