public TimeSeriesCollection BuildKinematics(FilteredTrajectory traj, float intervalMilliseconds, bool enableSecondPassFiltering)
        {
            TimeSeriesCollection tsc = new TimeSeriesCollection(traj.Length);

            if (traj.Length == 0)
            {
                return(tsc);
            }

            tsc.AddTimes(traj.Times);
            tsc.AddComponent(Kinematics.XRaw, traj.RawXs);
            tsc.AddComponent(Kinematics.X, traj.Xs);

            Func <int, PointF> getCoord;

            if (traj.CanFilter)
            {
                getCoord = traj.Coordinates;
            }
            else
            {
                getCoord = traj.RawCoordinates;
            }

            tsc.InitializeKinematicComponents(new List <Kinematics>()
            {
                Kinematics.LinearHorizontalVelocity,
                Kinematics.LinearHorizontalAcceleration,
            });

            ComputeVelocities(tsc, getCoord, intervalMilliseconds, enableSecondPassFiltering);
            ComputeAccelerations(tsc, getCoord, intervalMilliseconds, enableSecondPassFiltering);

            return(tsc);
        }
        private void ComputeVelocities(TimeSeriesCollection tsc, Func <int, PointF> getCoord, float intervalMilliseconds, bool enableSecondPassFiltering)
        {
            if (tsc.Length <= 2)
            {
                TimeSeriesPadder.Pad(tsc[Kinematics.LinearHorizontalVelocity], 1);
                return;
            }

            for (int i = 1; i < tsc.Length - 1; i++)
            {
                PointF a = getCoord(i - 1);
                PointF b = getCoord(i + 1);
                float  t = intervalMilliseconds * 2;
                tsc[Kinematics.LinearHorizontalVelocity][i] = (b.X - a.X) / t;
            }

            TimeSeriesPadder.Pad(tsc[Kinematics.LinearHorizontalVelocity], 1);

            // Second pass: apply extra smoothing to the derivatives.
            // This is only applied for high speed videos where the digitization is very noisy
            // due to the combination of increased time resolution and decreased spatial resolution.
            if (enableSecondPassFiltering)
            {
                double        constantVelocitySpan = 40;
                MovingAverage filter    = new MovingAverage();
                double        framerate = 1000 / intervalMilliseconds;
                double[]      averagedHorizontalVelocity = filter.FilterSamples(tsc[Kinematics.LinearHorizontalVelocity], framerate, constantVelocitySpan, 1);

                for (int i = 0; i < tsc.Length; i++)
                {
                    tsc[Kinematics.LinearHorizontalVelocity][i] = averagedHorizontalVelocity[i];
                }
            }
        }
        private void ComputeAccelerations(TimeSeriesCollection tsc, Func <int, PointF> getCoord, float intervalMilliseconds, bool enableSecondPassFiltering)
        {
            if (tsc.Length <= 4)
            {
                TimeSeriesPadder.Pad(tsc[Kinematics.LinearHorizontalAcceleration], 2);
                return;
            }

            // First pass: average speed over 2t centered on each data point.
            for (int i = 2; i < tsc.Length - 2; i++)
            {
                float  t = intervalMilliseconds * 2;
                double horizontalAcceleration = (tsc[Kinematics.LinearHorizontalVelocity][i + 1] - tsc[Kinematics.LinearHorizontalVelocity][i - 1]) / t;
                tsc[Kinematics.LinearHorizontalAcceleration][i] = horizontalAcceleration;
            }

            TimeSeriesPadder.Pad(tsc[Kinematics.LinearHorizontalAcceleration], 2);

            // Second pass: extra smoothing derivatives.
            // This is only applied for high speed videos where the digitization is very noisy
            // due to the combination of increased time resolution and decreased spatial resolution.
            if (enableSecondPassFiltering)
            {
                double        constantAccelerationSpan = 50;
                MovingAverage filter    = new MovingAverage();
                double        framerate = 1000 / intervalMilliseconds;
                double[]      averagedHorizontalAcceleration = filter.FilterSamples(tsc[Kinematics.LinearHorizontalAcceleration], framerate, constantAccelerationSpan, 2);

                for (int i = 0; i < tsc.Length; i++)
                {
                    tsc[Kinematics.LinearHorizontalAcceleration][i] = averagedHorizontalAcceleration[i];
                }
            }
        }