< 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
71%
Covered lines: 157
Uncovered lines: 62
Coverable lines: 219
Total lines: 382
Line coverage: 71.6%
Branch coverage
63%
Covered branches: 40
Total branches: 63
Branch coverage: 63.4%
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%210%
get_InteractionsFramesCache()100%11100%
Reset()100%11100%
UpdateFilterType(...)36.36%491131.81%
Update(...)90%1010100%
UpdateInteractionFramesList()100%2269.23%
UpdateCachedFrame(...)0%2040%
SmoothInteraction(...)64.28%181473.07%
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    {
 114        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
 15
 16        #region Fields
 17
 1118        private List<InteractionFrame> _interactionFrames = new List<InteractionFrame>();
 19
 1120        private int _frameId = 0;
 1121        private int _maxTouchId = 0;
 1122        private FilterType _type = FilterType.None;
 23        private IPointFilter _filter;
 24
 1125        private float _depthScale = 500f;
 26
 27        #endregion
 28
 29        #region Properties
 30
 48231        public float TouchMergeDistance2D { get; set; } = 64f;
 32
 46133        public  int NumFramesHistory { get; set; }
 34
 41935        public  int NumFramesSmoothing { get; set; }
 36
 53937        public int MaxNumEmptyFramesBetween { get; set; }
 38
 25039        public int CurrentFrameId => _frameId;
 25040        public int CurrentMaxId => _maxTouchId;
 41
 50742        public int MaxConfidence { get; set; }
 43
 44        public float DepthScale
 45        {
 046            get => _depthScale;
 047            set => _depthScale = value;
 48        }
 49
 100050        public InteractionFrame[] InteractionsFramesCache => _interactionFrames.ToArray();
 51
 52        #endregion
 53
 54        #region Constructor
 55
 1156        public InteractionSmoothingBehaviour(int numFramesHistory)
 1157        {
 1158            NumFramesHistory = numFramesHistory;
 1159            _filter = new SimpleMovingAverageFilter((int) Math.Floor(NumFramesHistory / 2d));
 1160        }
 61
 62        #endregion
 63
 64        #region public Methods
 65
 66        public void Reset()
 167        {
 168            _frameId = 0;
 169            _maxTouchId = 0;
 170            _interactionFrames.Clear();
 171        }
 72
 73        public void UpdateFilterType(FilterType type)
 1174        {
 1175            _type = type;
 76
 1177            switch (type)
 78            {
 79                case FilterType.MovingAverage:
 080                    _filter = new SimpleMovingAverageFilter((int) Math.Floor(NumFramesHistory / 2d));
 081                    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:
 11102                    _filter = null;
 11103                    break;
 104            }
 105
 11106            Logger.Info($"switched Interaction Processing Smoothing Filter to {_filter?.GetType().FullName ?? "None\""}"
 11107        }
 108
 109        public InteractionFrame Update(IList<Interaction> rawInteractions)
 249110        {
 249111            _frameId++;
 112
 113            // reconstruct ids by mapping touches in history
 249114            var mappedInteractionIds = MapClosestInteraction(rawInteractions.Take(20).ToList());
 115
 116            // store raw interactions with associated ids
 249117            var newFrame = new InteractionFrame(_frameId, mappedInteractionIds);
 118
 249119            _interactionFrames.Add(newFrame);
 120
 121            // remove old entries / update when _numFrames has changed
 249122            UpdateInteractionFramesList();
 123
 124            // create new Frame for smoothed values
 249125            var result = new InteractionFrame(_frameId);
 126
 249127            var frames = _interactionFrames.ToArray();
 128
 249129            var allInteractionIds = frames
 5470130                .SelectMany(frame => frame.Interactions.Select(interaction => interaction.TouchId)).Distinct().ToList();
 131
 249132            allInteractionIds.ForEach(id =>
 496133            {
 4866134                var lastFrameId = frames.OrderByDescending(frame => frame.FrameId).FirstOrDefault(frame =>
 2392135                        frame.Interactions.FirstOrDefault(interaction => Equals(interaction.TouchId, id)) != null)
 496136                    ?.FrameId ?? -1;
 249137
 249138                // remove touch ids that are "too old" --> prevent touch from being still displayed after the finger lef
 249139                // if touch id "returns" after a while, do not filter it out
 579140                if (_frameId - lastFrameId > MaxNumEmptyFramesBetween && newFrame.Interactions.FirstOrDefault(interactio
 88141                    return;
 249142
 408143                var smoothed = SmoothInteraction(id);
 408144                if (smoothed != null)
 408145                    result.Interactions.Add(smoothed);
 745146            });
 147
 148
 249149            return result;
 249150        }
 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()
 249158        {
 249159            if (_interactionFrames.Count > NumFramesHistory)
 190160            {
 161                try
 190162                {
 2280163                    _interactionFrames = _interactionFrames.OrderByDescending(frame => frame.FrameId)
 190164                        .Take(NumFramesHistory).ToList();
 190165                }
 0166                catch (Exception e)
 0167                {
 0168                    Logger.Error(e, $"Exception catched in {GetType()}.{nameof(this.UpdateInteractionFramesList)}.");
 0169                }
 170
 190171            }
 249172        }
 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)
 408205        {
 408206            var interactionsHistory = new List<Interaction>();
 207
 3962208            var frames = new List<InteractionFrame>(_interactionFrames.OrderBy(frame => frame.FrameId));
 209
 408210            frames.ForEach(frame =>
 3554211            {
 3554212                var lastTouch =
 8046213                    frame.Interactions.FirstOrDefault(interaction => Equals(interaction.TouchId, touchId));
 3554214                if (lastTouch == null)
 577215                    return;
 408216
 2977217                interactionsHistory.Add(new Interaction(lastTouch));
 3962218            });
 219
 408220            var smooth = interactionsHistory.LastOrDefault();
 221
 3385222            var raw = interactionsHistory.Select(interaction => interaction.Position).ToList();
 223
 408224            if (NumFramesSmoothing > 0 &&
 408225                smooth != null && raw.Count >= NumFramesSmoothing &&
 408226                _type != FilterType.None && _filter != null)
 0227            {
 0228                var framesForSmoothing = raw.Take(NumFramesSmoothing).ToList();
 0229                framesForSmoothing.ForEach(p => p.Z *= _depthScale);
 0230                smooth.Position = _filter.Process(framesForSmoothing).First();
 0231                smooth.Position.Z /= _depthScale;
 0232                framesForSmoothing.ForEach(p => p.Z /= _depthScale);
 0233            }
 234
 408235            return smooth;
 408236        }
 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)
 249248        {
 249            // no past interactions: assign Ids
 249250            if (_interactionFrames.Count == 0)
 11251            {
 11252                rawInteractions.ForEach(AssignMaxId);
 11253                return rawInteractions;
 254            }
 255
 256            // reset touch id to negative value
 564257            rawInteractions.ForEach(Interaction => Interaction.TouchId = -1);
 258
 259            // step 1: look in past frames for
 260
 4395261            var pastFrames = _interactionFrames.Where(frame => frame.Interactions.Count > 0).OrderByDescending(frame => 
 262
 238263            var result = new List<Interaction>();
 264
 238265            var candidates = rawInteractions.ToArray();
 266
 238267            var i = pastFrames.Length - 1;
 268
 595269            while (candidates.Length != 0 && i >= 0)
 357270            {
 271             // List : candidateIdx, Array<touchId, distances> (ordered by distance desc))
 357272                var distances =
 456273                    candidates.Select((interaction, idx) => Tuple.Create(idx,
 456274                        pastFrames[i].Interactions
 654275                            .Select(otherInteraction => Tuple.Create(otherInteraction, Point3.Squared2DDistance(interact
 654276                            .OrderBy(tpl => tpl.Item2)
 456277                            .ToList()))
 357278                        .ToList();
 279
 357280                var duplicateCount = distances.Count;
 281
 714282                while (duplicateCount != 0)
 357283                {
 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
 813294                    distances.RemoveAll(dist => dist.Item2.Count == 0);
 295
 1725296                    var duplicates = distances.Where(dist => dist.Item2.Count != 0).GroupBy(tpl => tpl.Item1).Where(grou
 297
 357298                    duplicateCount = duplicateCount > 0 ? duplicates.Count : duplicateCount;
 299
 357300                    duplicates.ForEach(
 357301                        duplicate =>
 0302                        {
 0303                            var ordered = duplicate.ToList().OrderBy(elem => elem.Item2[0].Item2).ToList();
 0304                            for (var n = 1; n < ordered.Count; n++)
 0305                            {
 357306                                // remove duplicate distance
 0307                                ordered[n].Item2
 0308                                    .RemoveAt(0);
 0309                            }
 357310                        });
 357311                }
 312
 357313                distances.ForEach(dist =>
 456314                {
 456315                    if (dist.Item2[0].Item2 < TouchMergeDistance2D)
 301316                    {
 301317                        var interaction = new Interaction(candidates[dist.Item1]);
 301318                        interaction.TouchId = dist.Item2[0].Item1.TouchId;
 357319
 301320                        interaction.Confidence = ComputeConfidence(interaction.TouchId, interaction.Confidence, pastFram
 301321                        candidates[dist.Item1].TouchId = dist.Item2[0].Item1.TouchId;
 357322
 357323                        // prevent adding duplicate id's
 400324                        var alreadyAdded = result.FirstOrDefault(inter => Equals(inter.TouchId, interaction.TouchId));
 357325
 357326                        // id does not exist - add point
 301327                        if (alreadyAdded == null)
 301328                        {
 301329                            result.Add(interaction);
 301330                        }
 357331                        else
 0332                        {
 357333                            // 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                            }
 357340                            else
 0341                            {
 0342                               alreadyAdded.Confidence = interaction.Confidence;
 0343                            }
 357344
 0345                        }
 301346                    }
 813347                });
 348
 813349                candidates = candidates.Where(interaction => interaction.TouchId < 0).ToArray();
 350
 357351                i--;
 357352            }
 353
 238354            var newInteractions =  candidates.ToList();
 238355            newInteractions.ForEach(AssignMaxId);
 356
 238357            result.AddRange(newInteractions);
 358
 564359            return result.OrderBy(interaction => interaction.TouchId).ToList();
 249360        }
 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)
 38367        {
 38368            interaction.TouchId = _maxTouchId;
 38369            _maxTouchId++;
 38370        }
 371
 372        private int ComputeConfidence(int touchId, float currentConfidence, InteractionFrame[] pastFrames)
 301373        {
 374            // find maximum confidence in history for touch id
 3142375            var maxExistingConfidence = pastFrames.SelectMany(frames => frames.Interactions)
 7871376                .Where(inter => Equals(inter.TouchId, touchId)).Max(inter => inter.Confidence);
 377
 378            // increment and clamp to max value
 301379            return (int) Math.Min(Math.Max(currentConfidence, maxExistingConfidence) + 1, MaxConfidence);
 301380        }
 381    }
 382}