< Summary - ReFlex - Library

Information
Class: ReFlex.Core.Interactivity.Components.InteractionSmoothingBehaviour
Assembly: ReFlex.Core.Interactivity
File(s): D:\a\reflex\reflex\library\src\Core\Interactivity\Components\InteractionSmoothingBehaviour.cs
Line coverage
76%
Covered lines: 167
Uncovered lines: 52
Coverable lines: 219
Total lines: 382
Line coverage: 76.2%
Branch coverage
74%
Covered branches: 47
Total branches: 63
Branch coverage: 74.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
get_TouchMergeDistance2D()100%11100%
get_NumFramesHistory()100%11100%
get_NumFramesSmoothing()100%11100%
get_MaxNumEmptyFramesBetween()100%11100%
get_CurrentFrameId()100%11100%
get_CurrentMaxId()100%11100%
get_MaxConfidence()100%11100%
get_DepthScale()100%210%
set_DepthScale(...)100%11100%
get_InteractionsFramesCache()100%11100%
Reset()100%11100%
UpdateFilterType(...)54.54%361140.9%
Update(...)90%1010100%
UpdateInteractionFramesList()100%2269.23%
UpdateCachedFrame(...)0%2040%
SmoothInteraction(...)100%1414100%
MapClosestInteraction(...)72.72%282277.5%
AssignMaxId(...)100%11100%
ComputeConfidence(...)100%11100%

File(s)

D:\a\reflex\reflex\library\src\Core\Interactivity\Components\InteractionSmoothingBehaviour.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using NLog;
 5using ReFlex.Core.Common.Components;
 6using ReFlex.Core.Filtering.Components;
 7using ReFlex.Core.Interactivity.Util;
 8using Math = System.Math;
 9
 10namespace ReFlex.Core.Interactivity.Components
 11{
 12    public class InteractionSmoothingBehaviour
 13    {
 214        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
 15
 16        #region Fields
 17
 6818        private List<InteractionFrame> _interactionFrames = new List<InteractionFrame>();
 19
 6820        private int _frameId = 0;
 6821        private int _maxTouchId = 0;
 6822        private FilterType _type = FilterType.None;
 23        private IPointFilter _filter;
 24
 6825        private float _depthScale = 500f;
 26
 27        #endregion
 28
 29        #region Properties
 30
 106031        public float TouchMergeDistance2D { get; set; } = 64f;
 32
 124433        public  int NumFramesHistory { get; set; }
 34
 98635        public  int NumFramesSmoothing { get; set; }
 36
 115637        public int MaxNumEmptyFramesBetween { get; set; }
 38
 50039        public int CurrentFrameId => _frameId;
 50040        public int CurrentMaxId => _maxTouchId;
 41
 106242        public int MaxConfidence { get; set; }
 43
 44        public float DepthScale
 45        {
 046            get => _depthScale;
 847            set => _depthScale = value;
 48        }
 49
 201450        public InteractionFrame[] InteractionsFramesCache => _interactionFrames.ToArray();
 51
 52        #endregion
 53
 54        #region Constructor
 55
 6856        public InteractionSmoothingBehaviour(int numFramesHistory)
 6857        {
 6858            NumFramesHistory = numFramesHistory;
 6859            _filter = new SimpleMovingAverageFilter((int) Math.Floor(NumFramesHistory / 2d));
 6860        }
 61
 62        #endregion
 63
 64        #region public Methods
 65
 66        public void Reset()
 267        {
 268            _frameId = 0;
 269            _maxTouchId = 0;
 270            _interactionFrames.Clear();
 271        }
 72
 73        public void UpdateFilterType(FilterType type)
 5074        {
 5075            _type = type;
 76
 5077            switch (type)
 78            {
 79                case FilterType.MovingAverage:
 1080                    _filter = new SimpleMovingAverageFilter((int) Math.Floor(NumFramesHistory / 2d));
 1081                    break;
 82                case FilterType.WeightedMovingAverage:
 083                    _filter = new WeightedMovingAverageFilter((int) Math.Floor(NumFramesHistory / 2d));
 084                    break;
 85                case FilterType.PolynomialFit:
 086                    _filter = new PolynomialFitFilter();
 087                    break;
 88                case FilterType.WeightedPolynomialFit:
 089                    _filter = new WeightedPolynomialFitFilter();
 090                    break;
 91                case FilterType.SavitzkyGolay:
 092                {
 093                    var sidePoints = NumFramesHistory / 2 - 1;
 094                    var order = Math.Min(sidePoints, 3);
 095                    _filter = new SavitzkyGolayFilter(sidePoints, order);
 096                    break;
 97                }
 98                case FilterType.Butterworth:
 099                    _filter = new ButterworthFilter();
 0100                    break;
 101                default:
 40102                    _filter = null;
 40103                    break;
 104            }
 105
 50106            Logger.Info($"switched Interaction Processing Smoothing Filter to {_filter?.GetType().FullName ?? "None\""}"
 50107        }
 108
 109        public InteractionFrame Update(IList<Interaction> rawInteractions)
 522110        {
 522111            _frameId++;
 112
 113            // reconstruct ids by mapping touches in history
 522114            var mappedInteractionIds = MapClosestInteraction(rawInteractions.Take(20).ToList());
 115
 116            // store raw interactions with associated ids
 522117            var newFrame = new InteractionFrame(_frameId, mappedInteractionIds);
 118
 522119            _interactionFrames.Add(newFrame);
 120
 121            // remove old entries / update when _numFrames has changed
 522122            UpdateInteractionFramesList();
 123
 124            // create new Frame for smoothed values
 522125            var result = new InteractionFrame(_frameId);
 126
 522127            var frames = _interactionFrames.ToArray();
 128
 522129            var allInteractionIds = frames
 11048130                .SelectMany(frame => frame.Interactions.Select(interaction => interaction.TouchId)).Distinct().ToList();
 131
 522132            allInteractionIds.ForEach(id =>
 1026133            {
 9818134                var lastFrameId = frames.OrderByDescending(frame => frame.FrameId).FirstOrDefault(frame =>
 4864135                        frame.Interactions.FirstOrDefault(interaction => Equals(interaction.TouchId, id)) != null)
 1026136                    ?.FrameId ?? -1;
 522137
 522138                // remove touch ids that are "too old" --> prevent touch from being still displayed after the finger lef
 522139                // if touch id "returns" after a while, do not filter it out
 1192140                if (_frameId - lastFrameId > MaxNumEmptyFramesBetween && newFrame.Interactions.FirstOrDefault(interactio
 176141                    return;
 522142
 850143                var smoothed = SmoothInteraction(id);
 850144                if (smoothed != null)
 850145                    result.Interactions.Add(smoothed);
 1548146            });
 147
 148
 522149            return result;
 522150        }
 151
 152        #endregion
 153
 154        /// <summary>
 155        /// order list of frames by frame id descending and remove old frames
 156        /// </summary>
 157        private void UpdateInteractionFramesList()
 522158        {
 522159            if (_interactionFrames.Count > NumFramesHistory)
 380160            {
 161                try
 380162                {
 4560163                    _interactionFrames = _interactionFrames.OrderByDescending(frame => frame.FrameId)
 380164                        .Take(NumFramesHistory).ToList();
 380165                }
 0166                catch (Exception e)
 0167                {
 0168                    Logger.Error(e, $"Exception catched in {GetType()}.{nameof(this.UpdateInteractionFramesList)}.");
 0169                }
 170
 380171            }
 522172        }
 173
 174        /// <summary>
 175        /// replaces the frame in the cache with the updated frame (if FrameId is existing, otherwise, nothing is change
 176        /// </summary>
 177        /// <param name="updatedFrame">Frame containing updated values</param>
 178        public void UpdateCachedFrame(InteractionFrame updatedFrame)
 0179        {
 0180          var frameIdxToBeReplaced = _interactionFrames.FindIndex((f) => f.FrameId == updatedFrame.FrameId);
 0181          if (frameIdxToBeReplaced < 0)
 0182            return;
 183
 184          // _interactionFrames[frameIdxToBeReplaced] = updatedFrame;
 0185          _interactionFrames[frameIdxToBeReplaced].Interactions.ForEach((interaction) =>
 0186          {
 0187            var updatedInteraction =
 0188              updatedFrame.Interactions.FirstOrDefault((update) => update.TouchId == interaction.TouchId);
 0189            if (updatedInteraction != null)
 0190            {
 0191              interaction.Confidence = updatedInteraction.Confidence;
 0192              interaction.ExtremumDescription = updatedInteraction.ExtremumDescription;
 0193
 0194            }
 0195          });
 0196        }
 197
 198        /// <summary>
 199        /// Apply smoothing according to chosen filter type.
 200        /// Extracts touches of a given id from frames history and  sends them to smoothing algorithm
 201        /// </summary>
 202        /// <param name="touchId">the id of the touch, which should be smoothed</param>
 203        /// <returns>Interaction (last entry in history) with smoothed position</returns>
 204        private Interaction SmoothInteraction(int touchId)
 850205        {
 850206            var interactionsHistory = new List<Interaction>();
 207
 8010208            var frames = new List<InteractionFrame>(_interactionFrames.OrderBy(frame => frame.FrameId));
 209
 850210            frames.ForEach(frame =>
 7160211            {
 7160212                var lastTouch =
 16208213                    frame.Interactions.FirstOrDefault(interaction => Equals(interaction.TouchId, touchId));
 7160214                if (lastTouch == null)
 1158215                    return;
 850216
 6002217                interactionsHistory.Add(new Interaction(lastTouch));
 8010218            });
 219
 850220            var smooth = interactionsHistory.LastOrDefault();
 221
 6852222            var raw = interactionsHistory.Select(interaction => interaction.Position).ToList();
 223
 850224            if (NumFramesSmoothing > 0 &&
 850225                smooth != null && raw.Count >= NumFramesSmoothing &&
 850226                _type != FilterType.None && _filter != null)
 8227            {
 8228                var framesForSmoothing = raw.Take(NumFramesSmoothing).ToList();
 24229                framesForSmoothing.ForEach(p => p.Z *= _depthScale);
 8230                smooth.Position = _filter.Process(framesForSmoothing).First();
 8231                smooth.Position.Z /= _depthScale;
 24232                framesForSmoothing.ForEach(p => p.Z /= _depthScale);
 8233            }
 234
 850235            return smooth;
 850236        }
 237
 238
 239
 240        /// <summary>
 241        /// Try to identify an interaction that can be associated with the given interaction (from another frame, or smo
 242        /// returns null, if no interaction can be found which is within the <see cref="TouchMergeDistance2D"/>.
 243        /// </summary>
 244        /// <param name="frameToSearch"></param>
 245        /// <param name="source"></param>
 246        /// <returns></returns>
 247        private List<Interaction> MapClosestInteraction(List<Interaction> rawInteractions)
 522248        {
 249            // no past interactions: assign Ids
 522250            if (_interactionFrames.Count == 0)
 36251            {
 36252                rawInteractions.ForEach(AssignMaxId);
 36253                return rawInteractions;
 254            }
 255
 256            // reset touch id to negative value
 1152257            rawInteractions.ForEach(Interaction => Interaction.TouchId = -1);
 258
 259            // step 1: look in past frames for
 260
 8776261            var pastFrames = _interactionFrames.Where(frame => frame.Interactions.Count > 0).OrderByDescending(frame => 
 262
 486263            var result = new List<Interaction>();
 264
 486265            var candidates = rawInteractions.ToArray();
 266
 486267            var i = pastFrames.Length - 1;
 268
 1210269            while (candidates.Length != 0 && i >= 0)
 724270            {
 271             // List : candidateIdx, Array<touchId, distances> (ordered by distance desc))
 724272                var distances =
 926273                    candidates.Select((interaction, idx) => Tuple.Create(idx,
 926274                        pastFrames[i].Interactions
 1330275                            .Select(otherInteraction => Tuple.Create(otherInteraction, Point3.Squared2DDistance(interact
 808276                            .OrderBy(tpl => tpl.Item2)
 926277                            .ToList()))
 724278                        .ToList();
 279
 724280                var duplicateCount = distances.Count;
 281
 1448282                while (duplicateCount != 0)
 724283                {
 284                    // // if a point has no corresponding point
 285                    // distances.Where(dist => dist.Item2.Count == 0).ToList().ForEach(tpl =>
 286                    //    {
 287                    //        var interaction = new Interaction(candidates[tpl.Item1]);
 288                    //         AssignMaxId(interaction);
 289                    //         result.Add(interaction);
 290                    //     }
 291                    // );
 292
 293                    // remove all points which have no next point
 1650294                    distances.RemoveAll(dist => dist.Item2.Count == 0);
 295
 3502296                    var duplicates = distances.Where(dist => dist.Item2.Count != 0).GroupBy(tpl => tpl.Item1).Where(grou
 297
 724298                    duplicateCount = duplicateCount > 0 ? duplicates.Count : duplicateCount;
 299
 724300                    duplicates.ForEach(
 724301                        duplicate =>
 0302                        {
 0303                            var ordered = duplicate.ToList().OrderBy(elem => elem.Item2[0].Item2).ToList();
 0304                            for (var n = 1; n < ordered.Count; n++)
 0305                            {
 724306                                // remove duplicate distance
 0307                                ordered[n].Item2
 0308                                    .RemoveAt(0);
 0309                            }
 724310                        });
 724311                }
 312
 724313                distances.ForEach(dist =>
 926314                {
 926315                    if (dist.Item2[0].Item2 < TouchMergeDistance2D)
 614316                    {
 614317                        var interaction = new Interaction(candidates[dist.Item1]);
 614318                        interaction.TouchId = dist.Item2[0].Item1.TouchId;
 724319
 614320                        interaction.Confidence = ComputeConfidence(interaction.TouchId, interaction.Confidence, pastFram
 614321                        candidates[dist.Item1].TouchId = dist.Item2[0].Item1.TouchId;
 724322
 724323                        // prevent adding duplicate id's
 816324                        var alreadyAdded = result.FirstOrDefault(inter => Equals(inter.TouchId, interaction.TouchId));
 724325
 724326                        // id does not exist - add point
 614327                        if (alreadyAdded == null)
 614328                        {
 614329                            result.Add(interaction);
 614330                        }
 724331                        else
 0332                        {
 724333                            // TODO: find better selection of associated point (distance and time)
 0334                            if (alreadyAdded.Confidence < interaction.Confidence)
 0335                            {
 0336                                result.Remove(alreadyAdded);
 0337                                interaction.Confidence++;
 0338                                result.Add(interaction);
 0339                            }
 724340                            else
 0341                            {
 0342                               alreadyAdded.Confidence = interaction.Confidence;
 0343                            }
 724344
 0345                        }
 614346                    }
 1650347                });
 348
 1650349                candidates = candidates.Where(interaction => interaction.TouchId < 0).ToArray();
 350
 724351                i--;
 724352            }
 353
 486354            var newInteractions =  candidates.ToList();
 486355            newInteractions.ForEach(AssignMaxId);
 356
 486357            result.AddRange(newInteractions);
 358
 890359            return result.OrderBy(interaction => interaction.TouchId).ToList();
 522360        }
 361
 362        /// <summary>
 363        /// Assigns a new id to the given point and increments current maximum touch id.
 364        /// </summary>
 365        /// <param name="interaction">the touch point which should get a new unique id</param>
 366        private void AssignMaxId(Interaction interaction)
 96367        {
 96368            interaction.TouchId = _maxTouchId;
 96369            _maxTouchId++;
 96370        }
 371
 372        private int ComputeConfidence(int touchId, float currentConfidence, InteractionFrame[] pastFrames)
 614373        {
 374            // find maximum confidence in history for touch id
 6310375            var maxExistingConfidence = pastFrames.SelectMany(frames => frames.Interactions)
 15790376                .Where(inter => Equals(inter.TouchId, touchId)).Max(inter => inter.Confidence);
 377
 378            // increment and clamp to max value
 614379            return (int) Math.Min(Math.Max(currentConfidence, maxExistingConfidence) + 1, MaxConfidence);
 614380        }
 381    }
 382}