ink_stroke_modeler_rs

Struct StrokeModeler

source
pub struct StrokeModeler { /* private fields */ }
Expand description

This class models a stroke from a raw input stream. The modeling is performed in several stages

  • Wobble smoothing : dampens high-frequency noise from quantization error
  • Position modeling : models the pen tip as a mass, connected by a spring, to a moving anchor
  • Stylus state modeling : constructs stylus states for modeled positions by interpolating over the raw input

Additional, this class provides prediction of the modeled stroke

StrokeModeler is unit-agnostic

Implementations§

source§

impl StrokeModeler

Notations

We denote :

  • Pressure : ν [ 0 , 1 ] \nu \in \lbrack 0,1\rbrack

  • time : t 0 t \geq 0

  • point : defined by position x x and y y (or p = ( x , y ) 2 p = (x,y) \in {\mathbb{R}}^{2} )

  • Raw inputs are denoted by a tuple i [ k ] = ( ν [ k ] , t [ k ] , x [ k ] , y [ k ] ) i\lbrack k\rbrack = \left( \nu\lbrack k\rbrack,t\lbrack k\rbrack,x\lbrack k\rbrack,y\lbrack k\rbrack \right) with k k \in {\mathbb{N}} .

An input stream is a sequence of raw inputs i = ( ν , t , x , y ) i = (\nu,t,x,y)

  • with time t [ k ] t\lbrack k\rbrack \nearrow \nearrow strictly increasing

  • starts with a Down event

  • contains Move for k 1 k \geq 1

  • ends either with a Move or a Up . If it is a Up we say the input stream is complete

We addition define

  • v x , v y , a x , a y v_{x},v_{y},a_{x},a_{y} as the velocity and acceleration.

With the vector shorthand v = ( v x , v y ) 2 v = \left( v_{x},v_{y} \right) \in {\mathbb{R}}^{2} and a = ( a x , a y ) 2 a = \left( a_{x},a_{y} \right) \in {\mathbb{R}}^{2}

Resampling

Algorithm : Upsampling
Input : { v [ k ] = ( x [ k ] , y [ k ] , t [ k ] ) , 0 k n } \left\{ v\lbrack k\rbrack = \left( x\lbrack k\rbrack,y\lbrack k\rbrack,t\lbrack k\rbrack \right),0 \leq k \leq n \right\} and a target rate sampling_min_output_rate, Interpolate time and position between each k k by linearly adding interpolated values
This is done by adding linearly interpolated values so that the output stream is { v [ 0 ] , u 0 [ 1 ] , , u 0 [ n 0 1 ] interpolated , v [ 1 ] , u 1 [ 1 ] , , u 1 [ n 1 1 ] interpolated , v [ 2 ] , } \left\{ v\lbrack 0\rbrack,\underset{\text{ interpolated}}{\underbrace{u_{0}\lbrack 1\rbrack,\ldots,u_{0}\left\lbrack n_{0} - 1 \right\rbrack}},v\lbrack 1\rbrack,\underset{\text{ interpolated}}{\underbrace{u_{1}\lbrack 1\rbrack,\ldots,u_{1}\left\lbrack n_{1} - 1 \right\rbrack}},v\lbrack 2\rbrack,\ldots \right\} Each n i n_{i} is the minimum integer such that t [ i ] t [ i 1 ] n i < Δ target n i = t [ i + 1 ] t [ i ] Δ target \begin{aligned} & \frac{t\lbrack i\rbrack - t\lbrack i - 1\rbrack}{n_{i}} < \Delta_{\text{target}} \\ \Leftrightarrow & n_{i} = \left\lceil \frac{t\lbrack i + 1\rbrack - t\lbrack i\rbrack}{\Delta_{\text{target}}} \right\rceil \end{aligned} and the linear interpolation means u j [ k ] = ( 1 k n j ) v [ j ] + k n i v [ j + 1 ] u_{j}\lbrack k\rbrack = \left( 1 - \frac{k}{n_{j}} \right)v\lbrack j\rbrack + \frac{k}{n_{i}}v\lbrack j + 1\rbrack
Output : { ( x [ k ] , y [ k ] , t [ k ] ) , 0 k ; n } \left\{ \left( x\prime\lbrack k\prime\rbrack,y\prime\lbrack k\prime\rbrack,t\prime\lbrack k\prime\rbrack \right),0 \leq k; \leq n\prime \right\} the upsampled position and times. This verifies k , t [ k ] t [ k 1 ] < Δ target = 1 sampling_min_output_rate \forall k\prime,t\prime\lbrack k\prime\rbrack - t\prime\lbrack k\prime - 1\rbrack < \Delta_{\text{target }} = \frac{1}{\text{sampling_min_output_rate}}
Remark : As this is a streaming algorithm, we only calculate this interpolation with respect to the latest stroke position.

Position modeling

The raw input is processed as follow

raw input \rightarrow wobble smoother \rightarrow upsampled \rightarrow position modeling

The position of the pen is modeled as a weight connected by a spring to an anchor.

The anchor moves along the resampled dewobbled inputs, pulling the weight along with it across a surface, with some amount of friction. Euler integration is used to solve for the position of the pen.

The physical model that is used to model the stroke is the following d 2 s d t 2 = Φ ( t ) s ( t ) k spring k drag d s d t \frac{d^{2}s}{dt^{2}} = \frac{\Phi(t) - s(t)}{k_{\text{spring }}} - k_{\text{drag }}\frac{ds}{dt} where

  • t t is time

  • s ( t ) s(t) is the position of the pen

  • Φ ( t ) \Phi(t) is the position of the anchor

  • k spring k_{\text{spring}} and k drag k_{\text{drag}} are constants that sets how the spring and drag occurs

k spring k_{\text{spring}} is given by position_modeler_spring_mass_constant and k drag k_{\text{drag}} by position_modeler_drag_constant

We will thus have as input the upsampled dewobbled inputs taking the role of discretized Φ ( t ) \Phi(t) and s ( t ) s(t) will be the output

Modeling a stroke.

  • Input : input stream { ( p [ k ] , t [ k ] ) , 0 k n } \left\{ \left( p\lbrack k\rbrack,t\lbrack k\rbrack \right),0 \leq k \leq n \right\}

  • Output : smoothed stream { ( p f [ k ] , v f [ k ] , a f [ k ] ) , 0 k n } \left\{ \left( p_{f}\lbrack k\rbrack,v_{f}\lbrack k\rbrack,a_{f}\lbrack k\rbrack \right),0 \leq k \leq n \right\}

We define Φ [ k ] = p [ k ] \Phi\lbrack k\rbrack = p\lbrack k\rbrack . An euler scheme integration scheme is used with the initial conditions being v [ 0 ] = 0 v\lbrack 0\rbrack = 0 and p f [ 0 ] = p [ 0 ] p_{f}\lbrack 0\rbrack = p\lbrack 0\rbrack (same initial conditions)

Update rule is simply a f [ j ] = p [ j ] p f [ j 1 ] k spring k drag v f [ j 1 ] v f [ j ] = v f [ j 1 ] + ( t [ j ] t [ j 1 ] ) a f [ j ] p f [ j ] = p f [ j 1 ] + ( t [ j ] t [ j 1 ] ) v f [ j ] \begin{array}{r} a_{f}\lbrack j\rbrack = \frac{p\lbrack j\rbrack - p_{f}\lbrack j - 1\rbrack}{k_{\text{spring }}} - k_{\text{drag }}v_{f}\lbrack j - 1\rbrack \\ v_{f}\lbrack j\rbrack = v_{f}\lbrack j - 1\rbrack + \left( t\lbrack j\rbrack - t\lbrack j - 1\rbrack \right)a_{f}\lbrack j\rbrack \\ p_{f}\lbrack j\rbrack = p_{f}\lbrack j - 1\rbrack + \left( t\lbrack j\rbrack - t\lbrack j - 1\rbrack \right)v_{f}\lbrack j\rbrack \end{array} The position s [ j ] s\lbrack j\rbrack is the main thing to export but we can also export speed and acceleration if needed. We denote q [ j ] = ( p f [ j ] , v f [ j ] , a f [ j ] , t [ j ] ) q\lbrack j\rbrack = \left( p_{f}\lbrack j\rbrack,v_{f}\lbrack j\rbrack,a_{f}\lbrack j\rbrack,t\lbrack j\rbrack \right) and this will be our output with 0 j n 0 \leq j \leq n

Stylus state modeler

Up till now we have only used the raw input stream to create a new smoothed stream of positions, leaving behind the pressure attribute. This is what’s done here, to model the state of the stylus for these new position based on the pressure data of the raw input strokes.
Algorithm Stylus state modeler
Input :

  • input stream with pressure information { ( p [ k ] = ( x [ k ] , y [ k ] ) , ν [ k ] ) , 0 k n } \left\{ \left( p\lbrack k\rbrack = \left( x\lbrack k\rbrack,y\lbrack k\rbrack \right),\nu\lbrack k\rbrack \right),0 \leq k \leq n \right\}

  • query position q = ( x , y ) q = (x,y)

  • search window n search n_{\text{search}} (From stylus_state_modeler_max_input_samples),


initialize d = d = \infty , index = None \text{index} = \text{None} , interp = None \text{interp } = \text{ None}
for i = n n search i = n - n_{\text{search}} to n 1 n - 1 do
- Find q i q_{i} the position that’s closest to q q on the segment [ p [ i ] , p [ i + 1 ] ] \left\lbrack p\lbrack i\rbrack,p\lbrack i + 1\rbrack \right\rbrack and denote r [ 0 , 1 ] r \in \lbrack 0,1\rbrack the value such that q i = ( 1 r ) p [ i ] + r p [ i + 1 ] q_{i} = (1 - r)p\lbrack i\rbrack + rp\lbrack i + 1\rbrack
- if q q i < d \left. \parallel{q - q_{i}} \right.\parallel < d
- d q q i < d index = i interp = r \begin{array}{r} d \leftarrow \left. \parallel{q - q_{i}} \right.\parallel < d \\ \text{index } = i \\ \text{interp } = r \end{array}
- endif
endfor
calculate ν = ( 1 r ) ν [ index ] + r ν [ index + 1 ] \nu = (1 - r)\nu\left\lbrack \text{index} \right\rbrack + r\nu\left\lbrack \text{index } + 1 \right\rbrack
Output : interpolated pressure ν \nu

Stroke end

The position modeling algorithm will lag behind the raw input by some distance. This algorithm iterates the previous dynamical system a few additional time using the raw input position as the anchor to allow a catch up of the stroke (though this prediction is only given by predict, so is not part of the results and becomes obsolete on the next input).

Algorithm :Stroke end
Input:

  • Final anchor position p [ end ] = ( x [ end ] , y [ end ] ) p\left\lbrack \text{end} \right\rbrack = \left( x\left\lbrack \text{end} \right\rbrack,y\left\lbrack \text{end} \right\rbrack \right) (From the original input stream)

  • final tip state q f [ end ] = ( p f [ end ] = ( x f [ end ] , y f [ end ] ) , v f [ end ] , a f [ end ] ) q_{f}\left\lbrack \text{end} \right\rbrack = \left( p_{f}\left\lbrack \text{end} \right\rbrack = \left( x_{f}\left\lbrack \text{end} \right\rbrack,y_{f}\left\lbrack \text{end} \right\rbrack \right),v_{f}\left\lbrack \text{end} \right\rbrack,a_{f}\left\lbrack \text{end} \right\rbrack \right) (returned from the physical modeling from the last section, f \cdot_{f} signifies that we are looking at the filtered output)

  • K max K_{\text{max}} max number of iterations (sampling_end_of_stroke_max_iterations)

  • Δ target \Delta_{\text{target}} the target time delay between stroke (1/sampling_min_output_rate)

  • d stop d_{\text{stop}} stopping distance (sampling_end_of_stroke_stopping_distance)

  • k spring k_{\text{spring}} and k drag k_{\text{drag}} the modeling coefficients

initialize the vector q o q_{o} with q 0 [ 0 ] = ( p f [ end ] p o [ 0 ] , v f [ end ] v o [ 0 ] , a f [ end ] a 0 [ 0 ] ) q_{0}\lbrack 0\rbrack = \left. \left( \underset{p_{o}\lbrack 0\rbrack}{\underbrace{p_{f}\left\lbrack \text{end} \right\rbrack}},\underset{v_{o}\lbrack 0\rbrack}{\underbrace{v_{f}\left\lbrack \text{end} \right\rbrack}},\underset{a_{0}\lbrack 0\rbrack}{\underbrace{a_{f}\left\lbrack \text{end} \right\rbrack}} \right) \right.
initialize Δ t = Δ target \Delta t = \Delta_{\text{target}}
for 1 k K max 1 \leq k \leq K_{\text{max}}
- calculate the next candidate a c = p [ end ] p o [ end ] k spring k drag v 0 [ end ] v c = v o [ 0 ] + Δ t a c p c = p o [ 0 ] + Δ v c \begin{aligned} a_{c} & = \frac{p\left\lbrack \text{end} \right\rbrack - p_{o}\left\lbrack \text{end} \right\rbrack}{k_{\text{spring}}} - k_{\text{drag }}v_{0}\left\lbrack \text{end} \right\rbrack \\ v_{c} & = v_{o}\lbrack 0\rbrack + \Delta ta_{c} \\ p_{c} & = p_{o}\lbrack 0\rbrack + \Delta v_{c} \end{aligned}


- if p c p [ end ] < d stop \left. \parallel{p_{c} - p\left\lbrack \text{end} \right\rbrack} \right.\parallel < d_{\text{stop}} (further iterations won’t be able to catch up and won’t move closer to the anchor, we stop here), - return q 0 q_{0}
- endif

  • if p c p o [ end ] , p [ end ] p o [ end ] < p c p o [ end ] \langle p_{c} - p_{o}\left\lbrack \text{end} \right\rbrack,p\left\lbrack \text{end} \right\rbrack - p_{o}\left\lbrack \text{end} \right\rbrack\rangle < \left. \parallel{p_{c} - p_{o}\left\lbrack \text{end} \right\rbrack} \right.\parallel (we’ve overshot the anchor, we retry with a smaller step)


    • Δ t Δ t 2 \Delta t \leftarrow \frac{\Delta t}{2} ,

    • continue (this candidate will be discarded, try again with a smaller time step instead),

- else
- q 0 [ e n d + 1 ] = ( p c , v c , q c ) q_{0}\lbrack end\ +1\rbrack = \left( p_{c},v_{c},q_{c} \right) (We append the result to the end of the q 0 q_{0} vector)
- endif

  • if p c p [ end ] < d stop \left. \parallel{p_{c} - p\left\lbrack \text{end} \right\rbrack} \right.\parallel < d_{\text{stop}} ,

    • return (We are within tolerance of the anchor, we stop iterating),

  • endif,

Output : { q o [ k ] = ( s o [ k ] , v o [ k ] , a o [ k ] ) , 0 k n ( K max 1 ) } \left\{ q_{o}\lbrack k\rbrack = \left( s_{o}\lbrack k\rbrack,v_{o}\lbrack k\rbrack,a_{o}\lbrack k\rbrack \right),0 \leq k \leq n\left( \leq K_{\text{max }} - 1 \right) \right\}

source

pub fn new(params: ModelerParams) -> Result<Self, String>

source

pub fn reset(&mut self)

Clears any in-progress stroke, keeping the same model parameters

source

pub fn reset_w_params(&mut self, params: ModelerParams) -> Result<(), String>

Clears any in-progress stroke, and re initialize the model with the given parameters

source

pub fn update( &mut self, input: ModelerInput, ) -> Result<Vec<ModelerResult>, ModelerError>

Updates the model with a raw input, and appends newly generated Results to the results vector. Any previously generated Result values remain valid. (This does not require that any previous results returned remain in the results vector, as it is appended to without examining the existing contents)

If this does not return an error, results will contain at least one Result, and potentially more if the inputs are slower than the minimum output rate

source

pub fn predict(&mut self) -> Result<Vec<ModelerResult>, String>

Models the given input prediction without changing the internal model state

Returns an error if the model has not yet been initialized, if there is no stroke in progress

Trait Implementations§

source§

impl Default for StrokeModeler

source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

source§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.