friday-0.1: src/Vision/Image/Threshold.hs
{-# LANGUAGE BangPatterns, GADTs #-}
module Vision.Image.Threshold (
ThresholdType (..)
, threshold
, AdaptiveThresholdKernel (..), adaptiveThreshold
) where
import Foreign.Storable (Storable)
import Vision.Image.Filter (Filter (..), SeparableFilter, blur, gaussianBlur)
import Vision.Image.Type (ImagePixel, FunctorImage)
import qualified Vision.Image.Type as I
-- | Specifies what to do with pixels matching the threshold predicate.
--
-- @'BinaryThreshold' a b@ will replace matching pixels by @a@ and non-matchings
-- pixels by @b@.
--
-- @'Truncate' a@ will replace matching pixels by @a@.
data ThresholdType src res where
BinaryThreshold :: res -> res -> ThresholdType src res
Truncate :: src -> ThresholdType src src
-- | Applies the given predicate and threshold policy on the image.
threshold :: FunctorImage src res
=> (ImagePixel src -> Bool)
-> ThresholdType (ImagePixel src) (ImagePixel res) -> src -> res
threshold !cond !(BinaryThreshold ifTrue ifFalse) !img =
I.map (\pix -> if cond pix then ifTrue else ifFalse) img
threshold !cond !(Truncate ifTrue) !img =
I.map (\pix -> if cond pix then ifTrue else pix) img
{-# INLINE threshold #-}
-- | Defines how pixels of the kernel of the adaptive threshold will be
-- weighted.
--
-- With 'MeanKernel', pixels of the kernel have the same weight.
--
-- With @'GaussianKernel' sigma@, pixels are weighted according to their distance
-- from the thresholded pixel using a Gaussian function parametred by @sigma@.
-- See 'gaussianBlur' for details.
data AdaptiveThresholdKernel acc where
MeanKernel :: Integral acc => AdaptiveThresholdKernel acc
GaussianKernel :: (Floating acc, RealFrac acc)
=> Maybe acc -> AdaptiveThresholdKernel acc
-- | Applies a thresholding adaptively.
-- Compares every pixel to its surrounding ones in the kernel of the given
-- radius.
adaptiveThreshold :: (Integral src, Num src, Ord src, Storable acc)
=> AdaptiveThresholdKernel acc
-> Int -- ^ Kernel radius.
-> src -- ^ Minimum difference between the pixel and the
-- kernel average. The pixel is thresholded if
-- @pixel_value - kernel_mean > difference@ where
-- difference if this number. Can be negative.
-> ThresholdType src res -> SeparableFilter src acc res
adaptiveThreshold !kernelType !radius !thres !thresType =
kernelFilter { fPost = post }
where
!kernelFilter =
case kernelType of MeanKernel -> blur radius
GaussianKernel sig -> gaussianBlur radius sig
post !pix !acc =
let !acc' = (fPost kernelFilter) pix acc
!cond = (pix - acc') > thres
in case thresType of
BinaryThreshold ifTrue ifFalse -> if cond then ifTrue
else ifFalse
Truncate ifTrue -> if cond then ifTrue else pix
{-# INLINE adaptiveThreshold #-}