ink_stroke_modeler_rs/
params.rs

1/// all parameters for the modeler
2#[derive(Debug, Clone, PartialEq, PartialOrd, Copy)]
3pub struct ModelerParams {
4    /// these parameters are used to apply smoothing to the input to reduce
5    /// wobble in the prediction
6    ///
7    /// The length of the window over which the moving average of speed and position is calculated
8    ///
9    /// Check if this can't be done with the rust time types as this probably comes from a
10    /// conversion to float (DURATION)
11    ///
12    /// A good starting point is
13    ///    <math>
14    ///    <mrow>
15    ///      <mn>2.5</mn>
16    ///      <mi>/</mi>
17    ///      <msub>
18    ///        <mi>f</mi>
19    ///        <mtext>input rate</mtext>
20    ///      </msub>
21    ///    </mrow>
22    ///  </math>
23    /// Should be positive
24    pub wobble_smoother_timeout: f64,
25    /// The range of speed considered for wobble smoothing.
26    /// At [ModelerParams::wobble_smoother_speed_floor] the maximum
27    /// amount of smoothing is applied. At [ModelerParams::wobble_smoother_speed_ceiling],
28    /// no smoothing is applied
29    ///
30    /// Good starting points are 2 - 3 % of the expected speed of the inputs
31    /// Should be positive and the speed floor smaller than the ceiling
32    pub wobble_smoother_speed_floor: f64,
33    pub wobble_smoother_speed_ceiling: f64,
34    /// The mass of the "weight" being pulled along the path, multiplied by the spring constant.
35    ///
36    /// Should be positive
37    pub position_modeler_spring_mass_constant: f64,
38    /// The ratio of the pen's velocity that is subtracted from the pen's acceleration per unit time, to simulate drag.
39    ///
40    /// Should be positive
41    pub position_modeler_drag_constant: f64,
42    /// The minimum number of modeled inputs to output per unit time. If inputs are received at a lower rate,
43    /// they will be upsampled to produce output of atleast [ModelerParams::sampling_min_output_rate].
44    /// If inputs are received at a higher rate, the output rate will match the input rate.
45    ///
46    /// Should be positive
47    pub sampling_min_output_rate: f64,
48    /// This determines the stop condition for the end-of-stroke modeling
49    /// If the position is within this distance of the final raw input, or
50    /// if the last update iteration moved less than this distance,
51    /// it stops iterating.
52    ///
53    /// this should be a small distance, good heuristic is
54    /// 2-3 orders of magnitude smaller than the expected distance
55    /// between input points
56    ///
57    /// Should be positive
58    pub sampling_end_of_stroke_stopping_distance: f64,
59    /// The maximum number of iterations to perform at the end of the stroke,
60    /// if it does not stop due to the constraint of the `sampling_end_of_stroke_stopping_distance`
61    ///
62    /// Should be positive and is capped at 1000 (to limit the memory requirements)
63    pub sampling_end_of_stroke_max_iterations: usize,
64    /// Maximum number of outputs to generate per call to Update or Predict.
65    /// related to issues if input events are received with too long of a delay
66    /// See what's done in the rnote call and on this end to limit things like this
67    ///
68    /// Should be strictly positive
69    pub sampling_max_outputs_per_call: usize,
70    /// the maximum number of raw inputs to look at when
71    /// searching for the nearest states when interpolating
72    ///
73    /// Should be strictly positive
74    pub stylus_state_modeler_max_input_samples: usize,
75}
76
77impl ModelerParams {
78    /// [ModelerParams::wobble_smoother_timeout] : 0.04,\
79    /// [ModelerParams::wobble_smoother_speed_floor] : 1.31,\
80    /// [ModelerParams::wobble_smoother_speed_ceiling] : 1.44,\
81    /// [ModelerParams::position_modeler_spring_mass_constant] : 11.0 / 32400.0,\
82    /// [ModelerParams::position_modeler_drag_constant] : 72.0,\
83    /// [ModelerParams::sampling_min_output_rate] : 180.0,\
84    /// [ModelerParams::sampling_end_of_stroke_stopping_distance] : 0.001,\
85    /// [ModelerParams::sampling_end_of_stroke_max_iterations] : 20,\
86    /// [ModelerParams::sampling_max_outputs_per_call] : 20,\
87    /// [ModelerParams::stylus_state_modeler_max_input_samples] : 10,
88    pub fn suggested() -> Self {
89        Self {
90            wobble_smoother_timeout: 0.04,
91            wobble_smoother_speed_floor: 1.31,
92            wobble_smoother_speed_ceiling: 1.44,
93            position_modeler_spring_mass_constant: 11.0 / 32400.0,
94            position_modeler_drag_constant: 72.0,
95            sampling_min_output_rate: 180.0,
96            sampling_end_of_stroke_stopping_distance: 0.001,
97            sampling_end_of_stroke_max_iterations: 20,
98            sampling_max_outputs_per_call: 20,
99            stylus_state_modeler_max_input_samples: 10,
100        }
101    }
102
103    /// validate the parameters as being correct, returns a error string with
104    /// the reasons otherwise
105    pub fn validate(self) -> Result<Self, String> {
106        let parameter_tests = [
107            self.position_modeler_spring_mass_constant > 0.0,
108            self.position_modeler_drag_constant > 0.0,
109            self.sampling_min_output_rate > 0.0,
110            self.sampling_end_of_stroke_stopping_distance > 0.0,
111            self.sampling_end_of_stroke_max_iterations > 0,
112            self.sampling_end_of_stroke_max_iterations < 1000,
113            self.sampling_max_outputs_per_call > 0,
114            self.wobble_smoother_timeout > 0.0,
115            self.wobble_smoother_speed_floor > 0.0,
116            self.wobble_smoother_speed_ceiling > 0.0,
117            self.wobble_smoother_speed_floor < self.wobble_smoother_speed_ceiling,
118        ];
119
120        let errors = vec![
121            "`position_modeler_spring_mass_constant` is not positive; ",
122            "`position_modeler_drag_constant` is not positive; ",
123            "`sampling_min_output_rate` is not positive; ",
124            "`sampling_end_of_stroke_stopping_distance` is not positive; ",
125            "`sampling_end_of_stroke_max_iterations` is not positive; ",
126            "`sampling_end_of_stroke_max_iterations` is too large (>1000); ",
127            "`sampling_max_outputs_per_call` is not positive; ",
128            "`wobble_smoother_timeout` is not positive; ",
129            "`wobble_smoother_speed_floor` is not positive; ",
130            "`wobble_smoother_speed_ceiling` is not positive; ",
131            "`wobble_smoother_speed_floor` should be strictly smaller than `wobble_smoother_speed_ceiling`",
132        ];
133
134        let tests_passed = parameter_tests.iter().fold(true, |acc, x| acc & x);
135
136        if tests_passed {
137            Ok(self)
138        } else {
139            //Collect errors
140            let error_acc = parameter_tests.iter().zip(errors).filter(|x| !*(x.0)).fold(
141                String::from("the following errors occurred : "),
142                |acc, x| acc + x.1,
143            );
144
145            Err(error_acc)
146        }
147    }
148}
149
150#[cfg(test)]
151mod test_params {
152    // import parent
153    use super::super::*;
154    #[test]
155    fn validation_modeler_params() {
156        let s = (ModelerParams {
157            wobble_smoother_timeout: -1.0,
158            wobble_smoother_speed_floor: -1.0,
159            wobble_smoother_speed_ceiling: -1.0,
160            position_modeler_spring_mass_constant: -1.0,
161            position_modeler_drag_constant: -1.0,
162            sampling_min_output_rate: -1.0,
163            sampling_end_of_stroke_stopping_distance: -1.0,
164            sampling_end_of_stroke_max_iterations: 0,
165            sampling_max_outputs_per_call: 0,
166            stylus_state_modeler_max_input_samples: 0,
167        })
168        .validate();
169        match s {
170            Ok(_) => assert!(false),
171            Err(_) => assert!(true),
172        }
173    }
174}