private void OnPointsAdded(SeriesEx series) { if (PointsAdded != null) { PointsAdded(series); } }
/// <summary> /// Retrieves absolute range from all points. /// </summary> private int[] GetVisiblePointsRange(SeriesEx series) { // two shortcuts ChartArea area = WrappedChart.ChartAreas[0]; // these are the X-Values of the zoomed portion: double minX = area.AxisX.ScaleView.ViewMinimum; double maxX = area.AxisX.ScaleView.ViewMaximum; double minPointsX = series.Points.Items.Min((point) => point.X); double maxPointX = series.Points.Items.Max((point) => point.X); double size = maxPointX - minPointsX; if (size <= minX || size <= maxX) { return new int[] { -1, -1 } } ; else { return new int[] { (int)Math.Ceiling((minX / size) * series.Points.Items.Count), (int)Math.Ceiling((maxX / size) * series.Points.Items.Count) } }; } }
/// <summary> /// Writes all or part of this data object to XML. If this data object owns other /// data objects that will also be written, this method may leave some XML elements /// open, which will be closed with a later call to <i>WriteXmlFooter</i>. /// </summary> /// <param name="writer">An open XML writer. The writer will be left open by this method /// after writing.</param> /// <returns>Returns <b>true</b> if successful; <b>false</b> otherwise.</returns> public bool WriteXmlHeader(XmlTextWriter writer) { writer.WriteStartElement("Movement"); writer.WriteAttributeString("count", XmlConvert.ToString(_moves.Count)); writer.WriteAttributeString("travel", XmlConvert.ToString(Math.Round(this.Travel, 4))); writer.WriteAttributeString("duration", XmlConvert.ToString(this.Duration)); // write out the submovement information Profiles resampled = CreateResampledProfiles(); if (resampled.IsEmpty) // this can happen if the trial is terminated prematurely { writer.WriteAttributeString("submovements", XmlConvert.ToString(0)); writer.WriteAttributeString("maxVelocity", PointF.Empty.ToString()); writer.WriteAttributeString("maxAcceleration", PointF.Empty.ToString()); writer.WriteAttributeString("maxJerk", PointF.Empty.ToString()); } else { int nsub = GetNumSubmovements(); // from smoothed velocity profile int vidx = SeriesEx.Max(resampled.Velocity, 0, -1); int aidx = SeriesEx.Max(resampled.Acceleration, 0, -1); int jidx = SeriesEx.Max(resampled.Jerk, 0, -1); writer.WriteAttributeString("submovements", XmlConvert.ToString(nsub)); writer.WriteAttributeString("maxVelocity", resampled.Velocity[vidx].ToString()); writer.WriteAttributeString("maxAcceleration", resampled.Acceleration[aidx].ToString()); writer.WriteAttributeString("maxJerk", resampled.Jerk[jidx].ToString()); } // write out all of MacKenzie's path analysis measures PathAnalyses analyses = DoPathAnalyses(_owner.Start, _owner.TargetCenterFromStart); writer.WriteAttributeString("taskAxisCrossings", XmlConvert.ToString(analyses.TaskAxisCrossings)); writer.WriteAttributeString("movementDirectionChanges", XmlConvert.ToString(analyses.MovementDirectionChanges)); writer.WriteAttributeString("orthogonalDirectionChanges", XmlConvert.ToString(analyses.OrthogonalDirectionChanges)); writer.WriteAttributeString("movementVariability", XmlConvert.ToString(Math.Round(analyses.MovementVariability, 4))); writer.WriteAttributeString("movementError", XmlConvert.ToString(Math.Round(analyses.MovementError, 4))); writer.WriteAttributeString("movementOffset", XmlConvert.ToString(Math.Round(analyses.MovementOffset, 4))); // write out all the mouse move points that make up this trial int i = 0; foreach (TimePointF pt in _moves) { writer.WriteStartElement("move"); writer.WriteAttributeString("index", XmlConvert.ToString(i++)); writer.WriteAttributeString("point", pt.ToString()); writer.WriteEndElement(); // </move> } writer.WriteEndElement(); // </Movement> return(true); }
public SeriesEx Add(String name) { SeriesEx s = collection.FirstOrDefault(x => name.Equals(x.WrappedSeries.Name)); if (s == null) { s = new SeriesEx(WrappedCollection.Add(name)); s.PointsAdded += PointsAddedHandler; collection.Add(s); } return(s); }
public void Add(SeriesEx series) { SeriesEx s = collection.FirstOrDefault(x => series.WrappedSeries.Name.Equals(x.WrappedSeries.Name)); if (s == null) { collection.Add(series); WrappedCollection.Add(series.WrappedSeries); series.PointsAdded += PointsAddedHandler; OnPointsAdded(series); // force update } }
public List<double> Vector; // vector representation -- for Protractor /// <summary> /// Constructor of a unistroke gesture. A unistroke is comprised of a set of points drawn /// out over time in a sequence. /// </summary> /// <param name="name">The name of the unistroke gesture.</param> /// <param name="timepoints">The array of points supplied for this unistroke.</param> public Unistroke(string name, List<TimePointF> timepoints) { this.Name = name; this.RawPoints = new List<TimePointF>(timepoints); // copy (saved for drawing) double I = GeotrigEx.PathLength(timepoints) / (Recognizer.NumPoints - 1); // interval distance between points this.Points = TimePointF.ConvertList(SeriesEx.ResampleInSpace(timepoints, I)); double radians = GeotrigEx.Angle(GeotrigEx.Centroid(this.Points), this.Points[0], false); this.Points = GeotrigEx.RotatePoints(this.Points, -radians); this.Points = GeotrigEx.ScaleTo(this.Points, Recognizer.SquareSize); this.Points = GeotrigEx.TranslateTo(this.Points, Recognizer.Origin, true); this.Vector = Vectorize(this.Points); // vectorize resampled points (for Protractor) }
/// <summary> /// Calculates the number of submovements in this movement. The number of submovements is defined /// by the number of velocity peaks in the smoothed velocity profile. The profile is obtained after /// resampling at 100 Hz and smoothing using a Gaussian convolution filter with a standard deviation /// of 3. /// </summary> /// <returns>The number of submovements defined by peaks in the smoothed velocity profile.</returns> public int GetNumSubmovements() { Profiles smoothed = CreateSmoothedProfiles(); if (smoothed.IsEmpty) { return(-1); } int[] maxima = SeriesEx.Maxima(smoothed.Velocity, 0, -1); return(maxima.Length); }
/// <summary> /// Temporally resamples the movement at 100 Hz, and then produces the position, velocity, acceleration, /// and jerk profiles that accompany it. Note that the position profile are TimePoints, and the derivative /// profiles are PointF time series. /// </summary> /// <returns>The velocity, acceleration, and jerk submovement profiles from the resampled movement.</returns> public Profiles CreateResampledProfiles() { if (_moves.Count == 0) { return(Profiles.Empty); } // Resampled position, velocity, acceleration, jerk Profiles resampled; resampled.Position = SeriesEx.ResampleInTime(_moves, Hertz); resampled.Velocity = SeriesEx.Derivative(resampled.Position); resampled.Acceleration = SeriesEx.Derivative(resampled.Velocity); resampled.Jerk = SeriesEx.Derivative(resampled.Acceleration); return(resampled); }
private void Generate() { PointF[][] pts = new PointF[12][]; chartEx.Series.Clear(); for (int i = 0; i < seriesAmount; i++) { SeriesEx s = new SeriesEx(i.ToString()); s.WrappedSeries.ChartType = SeriesChartType.Line; chartEx.Series.Add(s); s.WrappedSeries.Enabled = enabledSeries[i]; if (enabledSeries[i]) { pts[i] = GetFunc(i).CreatePoints(pointsAmount, pointsRange, 0, (i + 1) * 10, time); s.Points.AddRange(pts[i]); } } }
/// <summary> /// /// </summary> /// <param name="timepoints"></param> /// <param name="protractor">false</param> /// <returns></returns> public NBestList Recognize(List <TimePointF> timepoints, bool protractor) // candidate points { double I = GeotrigEx.PathLength(timepoints) / (NumPoints - 1); // interval distance between points List <PointF> points = TimePointF.ConvertList(SeriesEx.ResampleInSpace(timepoints, I)); double radians = GeotrigEx.Angle(GeotrigEx.Centroid(points), points[0], false); points = GeotrigEx.RotatePoints(points, -radians); points = GeotrigEx.ScaleTo(points, SquareSize); points = GeotrigEx.TranslateTo(points, Origin, true); List <double> vector = Unistroke.Vectorize(points); // candidate's vector representation NBestList nbest = new NBestList(); foreach (Unistroke u in _gestures.Values) { if (protractor) // Protractor extension by Yang Li (CHI 2010) { double[] best = OptimalCosineDistance(u.Vector, vector); double score = 1.0 / best[0]; nbest.AddResult(u.Name, score, best[0], best[1]); // name, score, distance, angle } else // original $1 angular invariance search -- Golden Section Search (GSS) { double[] best = GoldenSectionSearch( points, // to rotate u.Points, // to match GeotrigEx.Degrees2Radians(-45.0), // lbound GeotrigEx.Degrees2Radians(+45.0), // ubound GeotrigEx.Degrees2Radians(2.0) // threshold ); double score = 1.0 - best[0] / HalfDiagonal; nbest.AddResult(u.Name, score, best[0], best[1]); // name, score, distance, angle } } nbest.SortDescending(); // sort descending by score so that nbest[0] is best result return(nbest); }
// Main approximation methods private void ZoomApproximation(SeriesEx s) { if (!ApproximationEnabled) { return; } if (!s.WrappedSeries.Enabled) { return; } List <PointF> storedPoints = s.Points.Items; if (storedPoints.Count > MaxRenderedPoints) { int[] actualIndexes = GetVisiblePointsRange(s); if (actualIndexes[0] >= 0 && actualIndexes[1] >= 0) { int actualPointsCount = actualIndexes[1] - actualIndexes[0]; List <PointF> res = ApproximateSeries(s, storedPoints, actualIndexes); s.WrappedSeries.Points.DataBindXY(res, "X", res, "Y"); } } }
private List <PointF> ApproximateSeries(SeriesEx series, List <PointF> candidatePoints, int[] detailedRange = null) { List <PointF> resultPoints = new List <PointF>(); int detailedRangeWidth = ((detailedRange != null && detailedRange.Length == 2) ? detailedRange[1] - detailedRange[0] : 0); if (ApproximationEnabled && candidatePoints.Count > MaxRenderedPoints) { if (detailedRangeWidth == 0) { resultPoints = SelectionMethod.Select(candidatePoints, MaxRenderedPoints); if (resultPoints.Count > MaxRenderedPoints) // ensure the Selector gives us allowed number of points. { resultPoints = resultPoints.Take(MaxRenderedPoints).ToList(); } if (UseDashLines) { series.WrappedSeries.BorderDashStyle = ChartDashStyle.Dash; } } else { this.detailedRange = detailedRange; resultPoints.Add(candidatePoints.First()); resultPoints.AddRange(ApproximateSeries(series, candidatePoints.GetRange(detailedRange[0], detailedRangeWidth))); resultPoints.Add(candidatePoints.Last()); } } else { resultPoints.AddRange(candidatePoints); if (UseDashLines) { series.WrappedSeries.BorderDashStyle = ChartDashStyle.Solid; } } return(resultPoints); }
private List <PointF> ApproximateSeries(SeriesEx series, int[] detailedRanges = null) { return(ApproximateSeries(series, series.Points.Items, detailedRanges)); }
/// <summary> /// Performs any analyses on this data object and writes the results to a space-delimitted /// file for subsequent copy-and-pasting into a spreadsheet like Microsoft Excel or SAS JMP. /// </summary> /// <param name="writer">An open stream writer pointed to a text file. The writer will be closed by /// this method after writing.</param> /// <returns>True if writing is successful; false otherwise.</returns> public bool WriteResultsToTxt(StreamWriter writer) { bool success = true; try { // write an identifying title for this file. writer.WriteLine("FittsStudy log analysis results for '{0}' on {1} at {2}. FittsStudy.exe version: {3}.\r\n", this.FilenameBase + ".xml", // Note: ((FileStream) writer.BaseStream).Name holds the file path. DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString(), Assembly.GetExecutingAssembly().GetName().Version); // write the column headers with comma separation -- see Columns.txt writer.WriteLine("Subject,Circular?,Block,Condition,Trial,Practice?,Metronome?,MT%,MTPred,MT,a(given),b(given),A,W,ID,axis,angle,ae(1d),dx(1d),ae(2d),dx(2d),Ae(1d),SD(1d),We(1d),IDe(1d),TP(1d),Ae(2d),SD(2d),We(2d),IDe(2d),TP(2d),MTe,MTRatio,MeanMTe,MeanMTe(sx),MeanMTe(tx),Entries,Overshoots,Error?,Errors,Errors(sx),Errors(tx),Error%,Error%(sx),Error%(tx),SpatialOutlier?,TemporalOutlier?,SpatialOutliers,TemporalOutliers,StartX,StartY,EndX,EndY,TargetX,TargetY,Travel,Duration,Submovements,MaxVelocity,MaxAcceleration,MaxJerk,tMaxVelocity,tMaxAcceleration,tMaxJerk,TAC,MDC,ODC,MV,ME,MO,N,Fitts_TP_avg(1d),Fitts_TP_inv(1d),Fitts_a(1d),Fitts_b(1d),Fitts_r(1d),Fitts_TP_avg(2d),Fitts_TP_inv(2d),Fitts_a(2d),Fitts_b(2d),Fitts_r(2d),Error_m(1d),Error_b(1d),Error_r(1d),Error_m(2d),Error_b(2d),Error_r(2d)"); // pre-compute session-level values here Model fm = this.BuildModel(); fm.RoundTerms(4); // now iterate through all of the conditions for (int i = 0; i < _conditions.Count; i++) { // get the condition and pre-compute any condition-level values here. we could // compute them again and again while writing each trial, but that is inefficient. ConditionData cd = _conditions[i]; double cAe_1d = Math.Round(cd.GetAe(false), 4); double cSD_1d = Math.Round(cd.GetSD(false), 4); double cWe_1d = Math.Round(cd.GetWe(false), 4); double cIDe_1d = Math.Round(cd.GetIDe(false), 4); double cTP_1d = Math.Round(cd.GetTP(false), 4); double cAe_2d = Math.Round(cd.GetAe(true), 4); double cSD_2d = Math.Round(cd.GetSD(true), 4); double cWe_2d = Math.Round(cd.GetWe(true), 4); double cIDe_2d = Math.Round(cd.GetIDe(true), 4); double cTP_2d = Math.Round(cd.GetTP(true), 4); long meanMTe = cd.GetMTe(ExcludeOutliersType.None); long meanMTe_sx = cd.GetMTe(ExcludeOutliersType.Spatial); long meanMTe_tx = cd.GetMTe(ExcludeOutliersType.Temporal); int errors = cd.GetNumErrors(ExcludeOutliersType.None); int errors_sx = cd.GetNumErrors(ExcludeOutliersType.Spatial); int errors_tx = cd.GetNumErrors(ExcludeOutliersType.Temporal); double errorPct = Math.Round(cd.GetErrorRate(ExcludeOutliersType.None), 4); double errorPct_sx = Math.Round(cd.GetErrorRate(ExcludeOutliersType.Spatial), 4); double errorPct_tx = Math.Round(cd.GetErrorRate(ExcludeOutliersType.Temporal), 4); int nSpatialOutliers = cd.NumSpatialOutliers; int nTemporalOutliers = cd.NumTemporalOutliers; // within each condition, iterate through the trials. start at index 1 because // the trial at index 0 is the special start-area trial, and should be ignored. for (int j = 1; j <= cd.NumTrials; j++) { TrialData td = cd[j]; MovementData md = td.Movement; // the movement path itself // calculate the resampled submovement profiles MovementData.Profiles profiles = md.CreateResampledProfiles(); int vidx = SeriesEx.Max(profiles.Velocity, 0, -1); // max velocity int aidx = SeriesEx.Max(profiles.Acceleration, 0, -1); // max acceleration int jidx = SeriesEx.Max(profiles.Jerk, 0, -1); // max jerk // calculate the MacKenzie et al. (2001) path analysis measures MovementData.PathAnalyses analyses = md.DoPathAnalyses(td.Start, td.TargetCenterFromStart); // write the spreadsheet row here writer.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20},{21},{22},{23},{24},{25},{26},{27},{28},{29},{30},{31},{32},{33},{34},{35},{36},{37},{38},{39},{40},{41},{42},{43},{44},{45},{46},{47},{48},{49},{50},{51},{52},{53},{54},{55},{56},{57},{58},{59},{60},{61},{62},{63},{64},{65},{66},{67},{68},{69},{70},{71},{72},{73},{74},{75},{76},{77},{78},{79},{80},{81},{82},{83},{84},{85},{86}", _subject, // Subject, _circular ? 1 : 0, // Circular? cd.Block, // Block, cd.Index, // Condition, td.Number, // Trial, (== j) td.IsPractice ? 1 : 0, // Practice?, cd.UsedMetronome ? 1 : 0, // Metronome?, (== td.UsedMetronome) cd.MTPct, // MT%, cd.MTPred, // MTPred, cd.MT, // MT, (== td.MT) _intercept, // a(given), _slope, // b(given), cd.A, // A, (== td.A) cd.W, // W, (== td.W) Math.Round(cd.ID, 4), // ID, (== td.ID) Math.Round(GeotrigEx.Radians2Degrees(td.Axis), 4), // axis, Math.Round(GeotrigEx.Radians2Degrees(td.Angle), 4), // angle, Math.Round(td.GetAe(false), 4), // ae(1d), Math.Round(td.GetDx(false), 4), // dx(1d), Math.Round(td.GetAe(true), 4), // ae(2d), Math.Round(td.GetDx(true), 4), // dx(2d), cAe_1d, // Ae(1d), cSD_1d, // SD(1d), cWe_1d, // We(1d), cIDe_1d, // IDe(1d), cTP_1d, // TP(1d), cAe_2d, // Ae(2d), cSD_2d, // SD(2d), cWe_2d, // We(2d), cIDe_2d, // IDe(2d), cTP_2d, // TP(2d), td.MTe, // MTe, Math.Round(td.MTRatio, 4), // MTRatio, meanMTe, // MeanMTe, meanMTe_sx, // MeanMTe(sx), meanMTe_tx, // MeanMTe(tx), td.TargetEntries, // Entries, td.TargetOvershoots, // Overshoots, td.IsError ? 1 : 0, // Error?, errors, // Errors, errors_sx, // Errors(sx), errors_tx, // Errors(tx), errorPct, // Error%, errorPct_sx, // Error%(sx), errorPct_tx, // Error%(tx), td.IsSpatialOutlier ? 1 : 0, // SpatialOutlier?, td.IsTemporalOutlier ? 1 : 0, // TemporalOutlier?, nSpatialOutliers, // SpatialOutliers, nTemporalOutliers, // TemporalOutliers, td.Start.X, // StartX td.Start.Y, // StartY td.End.X, // EndX td.End.Y, // EndY td.TargetCenterFromStart.X, // TargetX td.TargetCenterFromStart.Y, // TargetY Math.Round(md.Travel, 4), // Travel, md.Duration, // Duration, md.GetNumSubmovements(), // Submovements, Math.Round(profiles.Velocity[vidx].Y, 4), // MaxVelocity, Math.Round(profiles.Acceleration[aidx].Y, 4), // MaxAcceleration, Math.Round(profiles.Jerk[jidx].Y, 4), // MaxJerk, Math.Round(profiles.Velocity[vidx].X / md.Duration, 4), // tMaxVelocity, Math.Round(profiles.Acceleration[aidx].X / md.Duration, 4), // tMaxAcceleration, Math.Round(profiles.Jerk[jidx].X / md.Duration, 4), // tMaxJerk, analyses.TaskAxisCrossings, // TAC, analyses.MovementDirectionChanges, // MDC, analyses.OrthogonalDirectionChanges, // ODC, Math.Round(analyses.MovementVariability, 4), // MV, Math.Round(analyses.MovementError, 4), // ME, Math.Round(analyses.MovementOffset, 4), // MO, fm.N, // N, fm.Fitts_TP_avg_1d, // Fitts_TP_avg(1d), fm.Fitts_TP_inv_1d, // Fitts_TP_inv(1d), fm.Fitts_a_1d, // Fitts_a(1d), fm.Fitts_b_1d, // Fitts_b(1d), fm.Fitts_r_1d, // Fitts_r(1d), fm.Fitts_TP_avg_2d, // Fitts_TP_avg(2d), fm.Fitts_TP_inv_2d, // Fitts_TP_inv(2d), fm.Fitts_a_2d, // Fitts_a(2d), fm.Fitts_b_2d, // Fitts_b(2d), fm.Fitts_r_2d, // Fitts_r(2d), fm.Error_m_1d, // Error_m(1d), fm.Error_b_1d, // Error_b(1d), fm.Error_r_1d, // Error_r(1d), fm.Error_m_2d, // Error_m(2d), fm.Error_b_2d, // Error_b(2d), fm.Error_r_2d // Error_r(2d) ); } } } catch (IOException ioex) { Console.WriteLine(ioex); success = false; } catch (Exception ex) { Console.WriteLine(ex); success = false; } finally { if (writer != null) { writer.Close(); } } return(success); }
private void PointsAddedHandler(SeriesEx series) { OnPointsAdded(); }
/// <summary> /// First, temporally resample the movement at 100 Hz. Second, compute the submovement profiles /// for velocity, acceleration, and jerk. Third, smooth these profiles using a 1D Gaussian convolution /// filter. /// </summary> /// <returns>This movement smoothed over time.</returns> /// <remarks>Smoothing velocity, acceleration, and jerk amounts to smoothing 1D time series. But /// position is a 2D time series, and although its x-coords and y-coords can be smoothed independently, /// bad artifacts occur at the beginnings and ends. These can be ameliorated by extending the first /// and last values position values in the series so that the kernel overlaps them during smoothing. /// </remarks> public Profiles CreateSmoothedProfiles() { // // Resample to get the submovement profiles with temporally-evenly spaced points. This // is a necessary step before applying the Gaussian convolution filter, since intervals // should be evenly spaced. // Profiles resampled = CreateResampledProfiles(); if (resampled.IsEmpty) { return(Profiles.Empty); } // // Now smooth the resampled submovement profiles. // Profiles smoothed; int halfLen = Kernel.Length / 2; // // To smooth position with 1D filter, we have to smooth the (x,y) coordinates independently. // This works fine except at either end of the profile, where it causes major departures. So // we must extend the profile at the head and tail to reduce this problem. // List <PointF> posx = new List <PointF>(resampled.Position.Count + Kernel.Length); List <PointF> posy = new List <PointF>(resampled.Position.Count + Kernel.Length); for (int i = 0; i < halfLen; i++) // extend first value { posx.Add(new PointF(0, resampled.Position[0].X)); posy.Add(new PointF(0, resampled.Position[0].Y)); } for (int i = 0; i < resampled.Position.Count; i++) // add actual values { posx.Add(new PointF(resampled.Position[i].Time, resampled.Position[i].X)); posy.Add(new PointF(resampled.Position[i].Time, resampled.Position[i].Y)); } for (int i = 0; i < halfLen; i++) // extend last value { posx.Add(new PointF(0, resampled.Position[resampled.Position.Count - 1].X)); posy.Add(new PointF(0, resampled.Position[resampled.Position.Count - 1].Y)); } posx = SeriesEx.Filter(posx, Kernel); // smooth x-values posy = SeriesEx.Filter(posy, Kernel); // smooth y-values smoothed.Position = new List <TimePointF>(resampled.Position.Count); // reassemble the (x,y) points for (int i = halfLen; i < resampled.Position.Count + halfLen; i++) { smoothed.Position.Add(new TimePointF(posx[i].Y, posy[i].Y, resampled.Position[i - halfLen].Time)); } // // Smooth the derivative resampled time series to create the smoothed velocity, acceleration, and jerk profiles. // smoothed.Velocity = SeriesEx.Filter(resampled.Velocity, Kernel); smoothed.Acceleration = SeriesEx.Filter(resampled.Acceleration, Kernel); smoothed.Jerk = SeriesEx.Filter(resampled.Jerk, Kernel); return(smoothed); }
private void PointsAdded(SeriesEx series) { List <PointF> res = ApproximateSeries(series, detailedRange); series.WrappedSeries.Points.DataBindXY(res, "X", res, "Y"); }
/// <summary> /// Private static constructor for the entire class. /// </summary> static MovementData() { Kernel = SeriesEx.GaussianKernel(GaussianStdDev); }