imj-base-0.1.0.2: src/Imj/Geo/Discrete/Resample.hs
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Imj.Geo.Discrete.Resample
( resampleWithExtremities
) where
import Imj.Prelude
import Data.List( length )
import Imj.Util( replicateElements )
{- | Resamples a list, using the analogy where a list
is seen as a uniform sampling of a geometrical segment.
With a uniform sampling strategy, for an input of length \( n \), and a desired
output of length \( m \):
* /Regular/ samples are repeated \( r = \lfloor {m \over n} \rfloor \) times.
* /Over-represented/ samples are repeated \( r + 1 \) times.
If \( m' \) is the number of over-represented samples,
\[
\begin{alignedat}{2}
m &= r*n + m' \\
\implies \quad m' &= m - r*n
\end{alignedat}
\]
We can chose over-represented samples in at least two different ways:
* __Even spread__ :
* Given a partition of the input continuous interval \( [\,0, length]\, \)
in \( m' \) equal-length intervals, the over-represented samples are located at
the (floored) centers of these intervals.
* More precisely, over-represented samples indexes are:
\[ \biggl\{ a + \Bigl\lfloor {1 \over 2} + { n-1-a \over m-1 } * s \Bigl\rfloor \mid s \in [\,0\,..\,m'-1] \;,\; a = {1 \over 2} * {n \over m'} \biggl\} \]
* Example : for a length 5 input, and 2 over-represented samples:
@
input samples: -----
over-represented samples: - -
@
* __"Even with extremities" spread__:
* The first and last over-represented samples match
with an input extremity. The rest of the over-represented samples are positionned
"regularly" in-between the first and last. An exception is made when there is only one
over-represented sample : in that case it is placed in the middle.
* More precisely, over-represented samples indexes are:
\[ if \; m' == 1 : \biggl\{ \Bigl\lfloor {n-1 \over 2} \Bigl\rfloor \biggl\} \]
\[ otherwise : \biggl\{ \Bigl\lfloor {1 \over 2} + {n-1 \over m'-1}*s \Bigl\rfloor \mid s \in [\,0,m'-1]\, \biggl\} \]
* Example : for a length 5 input, and 2 over-represented samples:
@
input samples: -----
over-represented samples: - -
@
/As its name suggests, this function uses the "even with extremities" spread./
/For clarity, the variable names used in the code match the ones in the documentation./
-}
resampleWithExtremities :: [a]
-- ^ Input
-> Int
-- ^ \( n \) : input length. It is expected that \( 0 <= n <= \) @length input@
-> Int
-- ^ \( m \) : output length. It is expected that \( 0 <= m \).
-> [a]
-- ^ Output :
--
-- * when \( m < n \), it is a /downsampled/ version of the input,
-- * when \( m > n \), it is an /upsampled/ version of the input.
resampleWithExtremities input n m
| assert (m >= 0) m == n = input
| otherwise =
let r = quot m n
m' = m - (r * n)
res
| m' == 0 = replicateElements r input
| otherwise = let overRepIdx = getOverRepIdx (assert (m' > 0) m') n 0
in resampleRec m' n 0 (overRepIdx, 0) input r
in assert (verifyResample input m res) res
resampleRec :: Int
-- ^ over-represented samples count
-> Int
-- ^ \( n \) : input length.
-> Int
-- ^ current index
-> (Int, Int)
-- ^ (next overrepresentation index, count of over-represented samples sofar)
-> [a]
-- ^ the list to be resampled
-> Int
-- ^ \( r = floor(m/n) \) : every sample will be replicated
-- \( r \) times, or \( r + 1 \) times if distance to next overrepresentation == 0
-> [a]
resampleRec _ _ _ _ [] _ = []
resampleRec m' n curIdx (overRepIdx, s) l@(_:_) r =
let (nCopies, nextState)
-- This commented guard was used to debug cases where the assert on the line after would fail
-- | overIdx < curIdx = error ("\noverIdx " ++ show overIdx ++ "\ncurIdx " ++ show curIdx ++ "\nm' " ++ show m' ++ "\nn " ++ show n ++ "\ns " ++ show s)
| assert (overRepIdx >= curIdx) overRepIdx == curIdx
= let nextS = succ s
nextOverRepIdx = getOverRepIdx m' n nextS
in (succ r, (nextOverRepIdx, nextS))
| otherwise = (r , (overRepIdx , s))
in replicate nCopies (head l) ++ resampleRec m' n (succ curIdx) nextState (tail l) r
-- | Returns maxBound when there is no over-representation
getOverRepIdx :: Int -> Int -> Int -> Int
getOverRepIdx m' n s
| m' > 1 = floor( 0.5 + (fromIntegral ((n - 1) * s) :: Float) / fromIntegral (m'-1))
| m' == 1 = if s == 0
then
quot n 2
else
maxBound
| otherwise = assert (m' == 0) maxBound
verifyResample :: [a]
-- ^ the input
-> Int
-- ^ the number of samples
-> [a]
-- ^ the output
-> Bool
verifyResample input nSamples resampled
| nSamples == length resampled = True
| otherwise = error $ "\ninput " ++ show (length input) ++
"\nnSamples " ++ show nSamples ++
"\nactual " ++ show (length resampled)