/// <summary> /// Shows all the information for a session-level node highlighted in the treeview. /// </summary> /// <param name="node">The highlighted node in the left treeview, assumed to represent a single session.</param> private void ShowSessionInfo(TreeNode node) { // // Get the session data. Note that sdata should be the same instance as _sdata. // For consistency, we pull it from the node's tag. // SessionData sdata = (SessionData)node.Tag; // // Set up a reusable XML writer. // 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; sdata.WriteXmlHeader(writer); // // Iterate through the conditions within the session. // foreach (TreeNode bnode in node.Nodes) { foreach (TreeNode cnode in bnode.Nodes) { ConditionData cd = (ConditionData)cnode.Tag; cd.WriteXmlHeader(writer); // writes out trials as well } } sdata.WriteXmlFooter(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); } } }
/// <summary> /// Shows all the information for a block-level node highlighted in the treeview. /// </summary> /// <param name="node">The highlighted node in the left treeview, assumed to represent a single block.</param> private void ShowBlockInfo(TreeNode node) { // // Set up a reusable XML writer. // 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; writer.WriteStartElement("Block"); writer.WriteAttributeString("index", XmlConvert.ToString(((ConditionData)node.Nodes[0].Tag).Block)); // // Blocks themselves are not data objects, they're just identifiers on conditions. // So iterate through the block's children nodes and write them out, to be later merged. // foreach (TreeNode cnode in node.Nodes) { ConditionData cd = (ConditionData)cnode.Tag; cd.WriteXmlHeader(writer); } writer.WriteEndElement(); // </Block> } 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); } } }
/// <summary> /// Shows all the information for a condition-level node highlighted in the treeview. /// </summary> /// <param name="node">The highlighted node in the left treeview, assumed to represent a single condition.</param> private void ShowConditionInfo(TreeNode node) { // // Get the condition from the 'Tag' field from the selected node. We don't // set the member variable because we're leaving the most recent trial depicted // on the graphs, etc., and just loading the XML for the condition. // ConditionData cd = (ConditionData)node.Tag; // // Set up a reusable XML writer. // 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; cd.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); } } }
/// <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> /// 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> /// Constructs a FittsSession instance. A FittsSession contains conditions for a Fitts' law study, /// which, in turn, contain a set of trials. A constructed instance contains a list of conditions /// in sequence, which themselves contain a list of trials. /// </summary> /// <param name="o">The options that configure this session obtained from the OptionsForm dialog.</param> public SessionData(OptionsForm.Options o) { // // Set the condition variables that define this test. // _subject = o.Subject; _circular = o.Is2D; _a = o.A; _w = o.W; _mtpct = o.MTPct; _intercept = o.Intercept; _slope = o.Slope; _screen = Screen.PrimaryScreen.Bounds; _conditions = new List <ConditionData>(); // // Create the order of conditions. Nesting is mt%[a[w]]]. // int[] a_order, w_order, mt_order; if (o.Randomize) // randomize the order of conditions { a_order = RandomEx.Array(0, o.A.Length - 1, o.A.Length, true); w_order = RandomEx.Array(0, o.W.Length - 1, o.W.Length, true); if (o.MTPct != null) { mt_order = RandomEx.Array(0, o.MTPct.Length - 1, o.MTPct.Length, true); while (o.MTPct[mt_order[0]] < StatsEx.Mean(o.MTPct)) // enforce that the first MT% condition is >avg(MT%) { mt_order = RandomEx.Array(0, o.MTPct.Length - 1, o.MTPct.Length, true); } } else { mt_order = null; } } else // in-order arrays { a_order = new int[o.A.Length]; for (int i = 0; i < o.A.Length; i++) { a_order[i] = i; } w_order = new int[o.W.Length]; for (int i = 0; i < o.W.Length; i++) { w_order[i] = i; } mt_order = (o.MTPct != null) ? new int[o.MTPct.Length] : null; for (int i = 0; o.MTPct != null && i < o.MTPct.Length; i++) { mt_order[i] = i; } } // // Create the ordered condition list for the first block of all conditions (i.e., one time through). // int n = 0; for (int i = 0; o.MTPct == null || i < o.MTPct.Length; i++) { for (int j = 0; j < o.A.Length; j++) { for (int k = 0; k < o.W.Length; k++) { double pct = (o.MTPct != null) ? o.MTPct[mt_order[i]] : -1.0; // MT% double fitts = o.Intercept + o.Slope * Math.Log((double)o.A[a_order[j]] / o.W[w_order[k]] + 1.0, 2.0); long mt = (o.MTPct != null) ? (long)fitts : -1L; // Fitts' law predicted movement time ConditionData cd = new ConditionData(0, n++, o.A[a_order[j]], o.W[w_order[k]], pct, mt, o.Trials, o.Practice, o.Is2D); _conditions.Add(cd); } } if (o.MTPct == null) { break; } } // // Now possibly replicate the created order multiple times. // int nConditions = _conditions.Count; // number of conditions in one block for (int b = 1; b < o.Blocks; b++) { for (int c = 0; c < nConditions; c++) { ConditionData fx = _conditions[c]; ConditionData fc = new ConditionData(b, c, fx.A, fx.W, fx.MTPct, fx.MTPred, o.Trials, o.Practice, o.Is2D); _conditions.Add(fc); } } }
/// <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> /// Reads a data object from XML and returns an instance of the object. /// </summary> /// <param name="reader">An open XML reader. The reader will be closed by this /// method after reading.</param> /// <returns>Returns <b>true</b> if successful; <b>false</b> otherwise.</returns> /// <remarks>Clients should first create a new instance using a default constructor, and then /// call this method to populate the data fields of the default instance.</remarks> public bool ReadFromXml(XmlTextReader reader) { bool success = true; try { // start up the xml text reader and move to the xml header reader.WhitespaceHandling = WhitespaceHandling.None; if (!reader.IsStartElement("Fitts_Study")) // moves to content and tests top tag { throw new XmlException("XML format error: Expected the <Fitts_Study> tag."); } // read the session-level information from the header _subject = XmlConvert.ToInt32(reader.GetAttribute("subject")); _circular = XmlConvert.ToBoolean(reader.GetAttribute("circular")); int conditions = XmlConvert.ToInt32(reader.GetAttribute("conditions")); _mtpct = StringEx.String2DoubleArray(reader.GetAttribute("MTPct")); _a = StringEx.String2IntArray(reader.GetAttribute("A")); _w = StringEx.String2IntArray(reader.GetAttribute("W")); _intercept = XmlConvert.ToDouble(reader.GetAttribute("intercept")); _slope = XmlConvert.ToDouble(reader.GetAttribute("slope")); _screen = StringEx.String2RectangleF(reader.GetAttribute("screen")); // saved for zoom feature // read the conditions and add condition objects to the session. the // conditions will be responsible for reading their individual trials. _conditions = new List <ConditionData>(conditions); for (int i = 0; i < conditions; i++) { ConditionData fc = new ConditionData(); if (!fc.ReadFromXml(reader)) { throw new XmlException("Failed to read the ConditionData."); } else { _conditions.Add(fc); } } // here would be the place to read the Fitts_Model parameters, but the model // can be recreated from the session data anyway, so there's really no point. } catch (XmlException xex) { Console.WriteLine(xex); success = false; } catch (Exception ex) { Console.WriteLine(ex); success = false; } finally { if (reader != null) { reader.Close(); } } return(success); }
/// <summary> /// Performs linear regression on the entire session's worth of data to fit a Fitts' law model /// of the form MTe = a + b * log2(Ae/We + 1). /// </summary> /// <returns>Model and fitting parameters.</returns> public Model BuildModel() { Model model = Model.Empty; model.FittsPts_1d = new List <PointF>(_conditions.Count); model.FittsPts_2d = new List <PointF>(_conditions.Count); double[] ide = new double[_conditions.Count]; // observed index of difficulties for each condition double[] mte = new double[_conditions.Count]; // observed mean movement times for each condition double[] tp = new double[_conditions.Count]; // observed throughputs for each condition for (int i = 0; i <= 1; i++) // first loop is univariate, second loop is bivariate { for (int j = 0; j < _conditions.Count; j++) // each A x W or MT% x A x W condition (blocks kept separate) { ConditionData cdata = _conditions[j]; ide[j] = cdata.GetIDe(i == 1); // bits mte[j] = cdata.GetMTe(ExcludeOutliersType.Spatial); // ms tp[j] = cdata.GetTP(i == 1); // bits/s if (i == 0) { model.FittsPts_1d.Add(new PointF((float)ide[j], (float)mte[j])); // for graphing later } else { model.FittsPts_2d.Add(new PointF((float)ide[j], (float)mte[j])); // for graphing later } } if (i == 0) // univariate { model.N = _conditions.Count; // == model.FittsPts.Count model.MTe = StatsEx.Mean(mte); // ms model.Fitts_TP_avg_1d = StatsEx.Mean(tp); // bits/s model.Fitts_a_1d = StatsEx.Intercept(ide, mte); // ms model.Fitts_b_1d = StatsEx.Slope(ide, mte); // ms/bit model.Fitts_TP_inv_1d = 1.0 / model.Fitts_b_1d * 1000.0; // bits/s model.Fitts_r_1d = StatsEx.Pearson(ide, mte); // correlation } else // bivariate { model.Fitts_TP_avg_2d = _circular ? StatsEx.Mean(tp) : 0.0; // bits/s model.Fitts_a_2d = _circular ? StatsEx.Intercept(ide, mte) : 0.0; // ms model.Fitts_b_2d = _circular ? StatsEx.Slope(ide, mte) : 0.0; // ms/bit model.Fitts_TP_inv_2d = _circular ? 1.0 / model.Fitts_b_2d * 1000.0 : 0.0; // bits/s model.Fitts_r_2d = _circular ? StatsEx.Pearson(ide, mte) : 0.0; // correlation } } // // Now compute the predicted error rates relevant to metronome experiments. // model.ErrorPts_1d = new List <PointF>(_conditions.Count); model.ErrorPts_2d = new List <PointF>(_conditions.Count); double[] errPred = new double[_conditions.Count]; // x, predicted errors based on the error model for pointing double[] errObs = new double[_conditions.Count]; // y, observed error rates for each condition (spatial outliers included) for (int i = 0; i <= 1; i++) // first loop is univariate, second loop is bivariate { for (int j = 0; j < _conditions.Count; j++) // each MT% x A x W condition, duplicates left separate { ConditionData cdata = _conditions[j]; errObs[j] = cdata.GetErrorRate(ExcludeOutliersType.Temporal); // observed error rates -- temporal outliers excluded double mt = cdata.GetMTe(ExcludeOutliersType.Temporal); // observed movement times -- temporal outliers excluded double z = (i == 0) ? 2.066 * ((double)cdata.W / cdata.A) * (Math.Pow(2.0, (mt - model.Fitts_a_1d) / model.Fitts_b_1d) - 1.0) : 2.066 * ((double)cdata.W / cdata.A) * (Math.Pow(2.0, (mt - model.Fitts_a_2d) / model.Fitts_b_2d) - 1.0); errPred[j] = GeotrigEx.PinValue(2.0 * (1.0 - StatsEx.CDF(0.0, 1.0, z)), 0.0, 1.0); // predicted error rates == 1 - erf(z / sqrt(2)) //double shah = 1.0 - (2.0 * StatsEx.ShahNormal(z)); // Shah approximation of the standard normal distribution if (i == 0) { model.ErrorPts_1d.Add(new PointF((float)errPred[j], (float)errObs[j])); // for graphing later } else { model.ErrorPts_2d.Add(new PointF((float)errPred[j], (float)errObs[j])); // for graphing later } } if (i == 0) // univariate { model.ErrorPct = StatsEx.Mean(errObs); // percent model.Error_m_1d = StatsEx.Slope(errPred, errObs); model.Error_b_1d = StatsEx.Intercept(errPred, errObs); model.Error_r_1d = StatsEx.Pearson(errPred, errObs); } else // bivariate { model.Error_m_2d = _circular ? StatsEx.Slope(errPred, errObs) : 0.0; model.Error_b_2d = _circular ? StatsEx.Intercept(errPred, errObs) : 0.0; model.Error_r_2d = _circular ? StatsEx.Pearson(errPred, errObs) : 0.0; } } return(model); }