| /* |
| * Copyright 2022 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef INK_STROKE_MODELER_PARAMS_H_ |
| #define INK_STROKE_MODELER_PARAMS_H_ |
| |
| #include "absl/status/status.h" |
| #include "absl/types/variant.h" |
| #include "ink_stroke_modeler/types.h" |
| |
| namespace ink { |
| namespace stroke_model { |
| |
| // These structs contain parameters for tuning the behavior of the stroke |
| // modeler. |
| // |
| // The stroke modeler is unit-agnostic, in both time and space. That is, the |
| // stroke modeler does not know or care whether the inputs and parameters are |
| // specified in feet and minutes, meters and seconds, or millimeters and years. |
| // As such, instead of referring to specific units, we refer to "unit distance" |
| // and "unit time". |
| // |
| // These parameters will need to be "tuned" to your use case. Because of this, |
| // and because of the modeler's unit-agnosticism, it's impossible to define |
| // "reasonable" default values for many of the parameters -- these parameters |
| // instead default to -1, which will cause the validation functions to return an |
| // error. |
| // |
| // Where possible, we've indicated what a good starting point for tuning might |
| // be, but you'll likely need to adjust these for best results. |
| |
| // These parameters are used for modeling the position of the pen. |
| struct PositionModelerParams { |
| // The mass of the "weight" being pulled along the path, multiplied by the |
| // spring constant. |
| float spring_mass_constant = 11.f / 32400; |
| |
| // The ratio of the pen's velocity that is subtracted from the pen's |
| // acceleration, to simulate drag. |
| float drag_constant = 72.f; |
| }; |
| |
| // These parameters are used for sampling. |
| struct SamplingParams { |
| // The minimum number of modeled inputs to output per unit time. If inputs are |
| // received at a lower rate, they will be upsampled to produce output of at |
| // least min_output_rate. If inputs are received at a higher rate, the |
| // output rate will match the input rate. |
| double min_output_rate = -1; |
| |
| // This determines stop condition for end-of-stroke modeling; if the position |
| // is within this distance of the final raw input, or if the last update |
| // iteration moved less than this distance, it stop iterating. |
| // |
| // This should be a small distance; a good starting point is 2-3 orders of |
| // magnitude smaller than the expected distance between input points. |
| float end_of_stroke_stopping_distance = -1; |
| |
| // The maximum number of iterations to perform at the end of the stroke, if it |
| // does not stop due to the constraints of end_of_stroke_stopping_distance. |
| int end_of_stroke_max_iterations = 20; |
| }; |
| |
| // These parameters are used modeling the state of the stylus once the position |
| // has been modeled. |
| struct StylusStateModelerParams { |
| // The maximum number of raw inputs to look at when finding the nearest states |
| // for interpolation. |
| int max_input_samples = 10; |
| }; |
| |
| // These parameters are used for applying smoothing to the input to reduce |
| // wobble in the prediction. |
| struct WobbleSmootherParams { |
| // The length of the window over which the moving average of speed and |
| // position are calculated. |
| // |
| // A good starting point is 2.5 divided by the expected number of inputs per |
| // unit time. |
| Duration timeout{-1}; |
| |
| // The range of speeds considered for wobble smoothing. At speed_floor, the |
| // maximum amount of smoothing is applied. At speed_ceiling, no smoothing is |
| // applied. |
| // |
| // Good starting points are 2% and 3% of the expected speed of the inputs. |
| float speed_floor = -1; |
| float speed_ceiling = -1; |
| }; |
| |
| // This struct indicates the "stroke end" prediction strategy should be used, |
| // which models a prediction as though the last seen input was the |
| // end-of-stroke. There aren't actually any tunable parameters for this; it uses |
| // the same PositionModelerParams and SamplingParams as the overall model. Note |
| // that this "prediction" doesn't actually predict substantially into the |
| // future, it only allows for very quickly "catching up" to the position of the |
| // raw input. |
| struct StrokeEndPredictorParams {}; |
| |
| // This struct indicates that the Kalman filter-based prediction strategy should |
| // be used, and provides the parameters for tuning it. |
| // |
| // Unlike the "stroke end" predictor, this strategy can predict an extension |
| // of the stroke beyond the last Input position, in addition to the "catch up" |
| // step. |
| struct KalmanPredictorParams { |
| // The variance of the noise inherent to the stroke itself. |
| double process_noise = -1; |
| |
| // The variance of the noise that rises from errors in measurement of the |
| // stroke. |
| double measurement_noise = -1; |
| |
| // The minimum number of inputs received before the Kalman predictor is |
| // considered stable enough to make a prediction. |
| int min_stable_iteration = 4; |
| |
| // The Kalman filter assumes that input is received in uniform time steps, but |
| // this is not always the case. We hold on to the most recent input timestamps |
| // for use in calculating the correction for this. This determines the maximum |
| // number of timestamps to save. |
| int max_time_samples = 20; |
| |
| // The minimum allowed velocity of the "catch up" portion of the prediction, |
| // which covers the distance between the last Result (the last corrected |
| // position) and the |
| // |
| // A good starting point is 3 orders of magnitude smaller than the expected |
| // speed of the inputs. |
| float min_catchup_velocity = -1; |
| |
| // These weights are applied to the acceleration (x²) and jerk (x³) terms of |
| // the cubic prediction polynomial. The closer they are to zero, the more |
| // linear the prediction will be. |
| float acceleration_weight = .5; |
| float jerk_weight = .1; |
| |
| // This value is a hint to the predictor, indicating the desired duration of |
| // of the portion of the prediction extending beyond the position of the last |
| // input. The actual duration of that portion of the prediction may be less |
| // than this, based on the predictor's confidence, but it will never be |
| // greater. |
| Duration prediction_interval{-1}; |
| |
| // The Kalman predictor uses several heuristics to evaluate confidence in the |
| // prediction. Each heuristic produces a confidence value between 0 and 1, and |
| // then we take their product as the total confidence. |
| // These parameters may be used to tune those heuristics. |
| struct ConfidenceParams { |
| // The first heuristic simply increases confidence as we receive more sample |
| // (i.e. input points). It evaluates to 0 at no samples, and 1 at |
| // desired_number_of_samples. |
| int desired_number_of_samples = 20; |
| |
| // The second heuristic is based on the distance between the last sample |
| // and the current estimate. If the distance is 0, it evaluates to 1, and if |
| // the distance is greater than or equal to max_estimation_distance, it |
| // evaluates to 0. |
| // |
| // A good starting point is 1.5 times measurement_noise. |
| float max_estimation_distance = -1; |
| |
| // The third heuristic is based on the speed of the prediction, which is |
| // approximated by measuring the from the start of the prediction to the |
| // projected endpoint (if it were extended for the full |
| // prediction_interval). It evaluates to 0 at min_travel_speed, and 1 |
| // at max_travel_speed. |
| // |
| // Good starting points are 5% and 25% of the expected speed of the inputs. |
| float min_travel_speed = -1; |
| float max_travel_speed = -1; |
| |
| // The fourth heuristic is based on the linearity of the prediction, which |
| // is approximated by comparing the endpoint of the prediction with the |
| // endpoint of a linear prediction (again, extended for the full |
| // prediction_interval). It evaluates to 1 at zero distance, and |
| // baseline_linearity_confidence at a distance of max_linear_deviation. |
| // |
| // A good starting point is an 10 times the measurement_noise. |
| float max_linear_deviation = -1; |
| float baseline_linearity_confidence = .4; |
| }; |
| ConfidenceParams confidence_params; |
| }; |
| using PredictionParams = |
| absl::variant<StrokeEndPredictorParams, KalmanPredictorParams>; |
| |
| // This convenience struct is a collection of the parameters for the individual |
| // parameter structs. |
| struct StrokeModelParams { |
| WobbleSmootherParams wobble_smoother_params; |
| PositionModelerParams position_modeler_params; |
| SamplingParams sampling_params; |
| StylusStateModelerParams stylus_state_modeler_params; |
| PredictionParams prediction_params = StrokeEndPredictorParams{}; |
| }; |
| |
| // These validation functions will return an error if the given parameters are |
| // invalid. |
| absl::Status ValidatePositionModelerParams(const PositionModelerParams& params); |
| absl::Status ValidateSamplingParams(const SamplingParams& params); |
| absl::Status ValidateStylusStateModelerParams( |
| const StylusStateModelerParams& params); |
| absl::Status ValidateWobbleSmootherParams(const WobbleSmootherParams& params); |
| absl::Status ValidatePredictionParams(const PredictionParams& params); |
| absl::Status ValidateStrokeModelParams(const StrokeModelParams& params); |
| |
| } // namespace stroke_model |
| } // namespace ink |
| |
| #endif // INK_STROKE_MODELER_PARAMS_H_ |