/// <summary> /// Shows all the information for a trial-level node highlighted in the treeview. /// </summary> /// <param name="node">The highlighted node in the left treeview, assumed to represent an individual trial.</param> private void ShowTrialInfo(TreeNode node) { // // Get the trial and its condition from the 'Tag' field from the selected node. // _tdata = (TrialData)node.Tag; _cdata = (ConditionData)node.Parent.Tag; // // Get the resampled and smoothed velocity, acceleration, and jerk profiles. // MovementData.Profiles resampled = _tdata.Movement.CreateResampledProfiles(); MovementData.Profiles smoothed = _tdata.Movement.CreateSmoothedProfiles(); // // Clear any series in the graphs. // grfDistance.ClearSeries(); grfVelocity.ClearSeries(); grfAcceleration.ClearSeries(); grfJerk.ClearSeries(); // // Graph the resampled distance to the target over time. // List <PointF> rdist = new List <PointF>(resampled.Position.Count); for (int i = 0; i < resampled.Position.Count; i++) { long t = resampled.Position[i].Time - resampled.Position[0].Time; double dx = GeotrigEx.Distance(resampled.Position[i], _tdata.TargetCenterFromStart); rdist.Add(new PointF(t, (float)dx)); } Graph.Series rd = new Graph.Series("resampled distance", Color.Salmon, Color.Salmon, false, true); rd.AddPoints(rdist); // // Graph the smoothed distance from the target over time. // List <PointF> sdist = new List <PointF>(smoothed.Position.Count); for (int i = 0; i < smoothed.Position.Count; i++) { long t = smoothed.Position[i].Time - smoothed.Position[0].Time; double dx = GeotrigEx.Distance(smoothed.Position[i], _tdata.TargetCenterFromStart); sdist.Add(new PointF(t, (float)dx)); } Graph.Series sd = new Graph.Series("smoothed distance", Color.MediumBlue, Color.MediumBlue, false, true); sd.AddPoints(sdist); grfDistance.AddSeries(rd); grfDistance.AddSeries(sd); // // Graph the velocity. // Graph.Series rv = new Graph.Series("resampled velocity", Color.Salmon, Color.Salmon, false, true); rv.AddPoints(resampled.Velocity); Graph.Series sv = new Graph.Series("smoothed velocity", Color.MediumBlue, Color.MediumBlue, false, true); sv.AddPoints(smoothed.Velocity); grfVelocity.AddSeries(rv); grfVelocity.AddSeries(sv); // // Graph the acceleration. // Graph.Series aaxis = new Graph.Series("zero line", Color.Gray, Color.Gray, false, true); aaxis.AddPoint(0f, 0f); aaxis.AddPoint(resampled.Acceleration[resampled.Acceleration.Count - 1].X, 0f); Graph.Series ra = new Graph.Series("resampled acceleration", Color.Salmon, Color.Salmon, false, true); ra.AddPoints(resampled.Acceleration); Graph.Series sa = new Graph.Series("smoothed acceleration", Color.MediumBlue, Color.MediumBlue, false, true); sa.AddPoints(smoothed.Acceleration); grfAcceleration.AddSeries(aaxis); grfAcceleration.AddSeries(ra); grfAcceleration.AddSeries(sa); // // Graph the jerk. // Graph.Series jaxis = new Graph.Series("zero line", Color.Gray, Color.Gray, false, true); jaxis.AddPoint(0f, 0f); jaxis.AddPoint(resampled.Jerk[resampled.Jerk.Count - 1].X, 0f); Graph.Series rj = new Graph.Series("resampled jerk", Color.Salmon, Color.Salmon, false, true); rj.AddPoints(resampled.Jerk); Graph.Series sj = new Graph.Series("smoothed jerk", Color.MediumBlue, Color.MediumBlue, false, true); sj.AddPoints(smoothed.Jerk); grfJerk.AddSeries(jaxis); grfJerk.AddSeries(rj); grfJerk.AddSeries(sj); // // Now show the trial-level XML in the web control. // XmlTextWriter writer = null; string tmpXml = String.Format("{0}{1}.xml", Path.GetTempPath(), Path.GetRandomFileName()); try { writer = new XmlTextWriter(tmpXml, Encoding.UTF8); writer.Formatting = Formatting.Indented; _tdata.WriteXmlHeader(writer); } catch (Exception ex) { Console.WriteLine(ex); tmpXml = String.Empty; } finally { if (writer != null) { writer.Close(); } if (tmpXml != String.Empty) { webXml.Navigate(tmpXml); rtxXml.LoadFile(tmpXml, RichTextBoxStreamType.PlainText); _tmpXml.Add(tmpXml); } } // // Invalidate everything that needs to be repainted based on the node just selected. // pnlTrial.Invalidate(); grfDistance.Invalidate(); grfVelocity.Invalidate(); grfAcceleration.Invalidate(); grfJerk.Invalidate(); }
/// <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); }
/// <summary> /// Build the tree view of the session, blocks, conditions, and trials on the left. Use icons /// to depict each, and show valuable information in each string label. /// </summary> /// <param name="sender">The sender of this event.</param> /// <param name="e">The arguments for this event.</param> private void GraphForm_Load(object sender, EventArgs e) { string s; trvTrials.SuspendLayout(); s = String.Format("Subject {0} - {1}D, {2}", _sdata.Subject, _sdata.Is2D ? 2 : 1, _sdata.UsedMetronome ? "with metronome" : "no metronome"); TreeNode sNode = new TreeNode(s); sNode.Tag = _sdata; sNode.ImageIndex = SessionIcon; sNode.SelectedImageIndex = SessionIcon; trvTrials.Nodes.Add(sNode); for (int b = 0; b < _sdata.NumBlocks; b++) { s = String.Format("Block {0}", b); TreeNode bNode = new TreeNode(s); bNode.ImageIndex = BlockIcon; bNode.SelectedImageIndex = BlockIcon; sNode.Nodes.Add(bNode); for (int i = 0; i < _sdata.NumConditionsPerBlock; i++) { ConditionData cdata = _sdata[b, i]; if (cdata.MTPct != -1.0) { s = String.Format("Condition {0} - {1}×{2}×{3}", cdata.Index, cdata.MTPct, cdata.A, cdata.W); } else { s = String.Format("Condition {0} - {1}×{2}", cdata.Index, cdata.A, cdata.W); } TreeNode cNode = new TreeNode(s); cNode.Tag = cdata; cNode.ImageIndex = ConditionIcon; cNode.SelectedImageIndex = ConditionIcon; bNode.Nodes.Add(cNode); for (int j = 1; j <= cdata.NumTrials; j++) { TrialData tdata = cdata[j]; s = String.Format("Trial {0}", tdata.Number); TreeNode tNode = new TreeNode(s); tNode.Tag = tdata; if (tdata.IsPractice) { tNode.ImageIndex = TrialPracticeIcon; tNode.SelectedImageIndex = TrialPracticeIcon; } else if (tdata.IsSpatialOutlier || tdata.IsTemporalOutlier) { tNode.ImageIndex = TrialOutlierIcon; tNode.SelectedImageIndex = TrialOutlierIcon; } else if (tdata.IsError) { tNode.ImageIndex = TrialErrorIcon; tNode.SelectedImageIndex = TrialErrorIcon; } else { tNode.ImageIndex = TrialGoodIcon; tNode.SelectedImageIndex = TrialGoodIcon; } cNode.Nodes.Add(tNode); } } } sNode.Nodes[0].Nodes[0].Nodes[0].EnsureVisible(); // ensure first trial node is visible trvTrials.SelectedNode = sNode.Nodes[0].Nodes[0].Nodes[0]; // select first trial trvTrials.ResumeLayout(true); }
/// <summary> /// Constructor for a DataMovement instance. /// </summary> /// <param name="owner">The trial to which this movement belongs.</param> public MovementData(TrialData owner) { _moves = new List <TimePointF>(128); _owner = owner; }