/// <summary> /// Calculate answer step distance. /// DP matching /// </summary> /// <param name="group1"></param> /// <param name="group2"></param> /// <returns></returns> private DPMatchingResult CalcAnswerStepDistance(AnswerStep group1, AnswerStep group2) { // parameters --------------------- // Gap cost for DP matching double gapCost = 100.0; // -------------------------------- List<AnalysisPenStroke> strokes1 = group1.GetNormalizedStrokes(AnswerSheetAnalyzer.NormarizingHeight, true); List<AnalysisPenStroke> strokes2 = group2.GetNormalizedStrokes(AnswerSheetAnalyzer.NormarizingHeight, true); if (group1.Strokes.Count == 0 && group2.Strokes.Count == 0) { return new DPMatchingResult(0.0, new List<int[]>()); } if (group1.Strokes.Count == 0) { return new DPMatchingResult(1.0 / ((double)strokes2.Count * gapCost), new List<int[]>()); } if (group2.Strokes.Count == 0) { return new DPMatchingResult(1.0 / ((double)strokes1.Count * gapCost), new List<int[]>()); } double[,] m = new double[strokes1.Count, strokes2.Count]; // from which direction? // 0->match, -1->fromStroke1, -2->fromStroke2 int[,] from = new int[strokes1.Count, strokes2.Count]; for (int i = 0, ilen = strokes1.Count; i < ilen; i++) { m[i, 0] = (double)i * gapCost; from[i, 0] = -1; } for (int i = 0, ilen = strokes2.Count; i < ilen; i++) { m[0, i] = (double)i * gapCost; from[0, i] = -2; } from[0, 0] = 0; for (int i = 1, ilen = strokes1.Count; i < ilen; i++) { for (int k = 1, klen = strokes2.Count; k < klen; k++) { double d = 0.0; d = CalcStrokeDistance(strokes1[i], strokes2[k]).Distance; //Console.WriteLine("Stroke Sequence Distance: " + d); //double[] scores = new double[] { m[i - 1, k] + gapCost, m[i - 1, k - 1] + d, m[i, k - 1] + gapCost }; //Console.WriteLine("Stroke Sequence Distance Candidates: " + scores[0] + "," + scores[1] + "," + scores[2]); //m[i, k] = scores.Min(); double from_grp1 = m[i - 1, k] + gapCost; double from_grp2 = m[i, k - 1] + gapCost; double from_match = m[i - 1, k - 1] + d; if (from_match <= from_grp1 && from_match <= from_grp2) { m[i, k] = from_match; } else if (from_grp1 <= from_grp2 && from_grp1 <= from_match) { from[i, k] = -1; m[i, k] = from_grp1; } else if (from_grp2 <= from_grp1 && from_grp2 <= from_match) { from[i, k] = -2; m[i, k] = from_grp2; } } } // generate matching list // {group1Pos, group2Pos, distance, joinCount} List<int[]> matchingList = new List<int[]>(); int g1Pos = strokes1.Count - 1; int g2Pos = strokes2.Count - 1; for (int i = strokes1.Count - 1, k = strokes2.Count - 1; i != 0 || k != 0; ) { int[] match = null; switch (from[i, k]) { case -1: match = new int[4] { g1Pos, -1, -1, 0 }; i--; g1Pos--; break; case -2: match = new int[4] { -1, g2Pos, -1, 0 }; k--; g2Pos--; break; default: match = new int[4] { g1Pos, g2Pos, -1, from[i, k] }; i--; k--; g1Pos--; g2Pos--; break; } matchingList.Add(match); } matchingList.Reverse(); DPMatchingResult res = new DPMatchingResult(m[strokes1.Count - 1, strokes2.Count - 1] / (Math.Min(strokes1.Count, strokes2.Count)), matchingList); return res; //return m[strokes1.Count - 1, strokes2.Count - 1] / (Math.Min(strokes1.Count, strokes2.Count)); }
/// <summary> /// Group answer step. Result is set to this.answerGroupList. /// Weighted manhattan distance method /// </summary> /// <param name="strokes"></param> /// <returns>Result of grouping</returns> public List<AnswerStep> GroupAnswerStep(List<AnalysisPenStroke> strokes) { List<AnswerStep> ansGroupList = new List<AnswerStep>(); // grouping by using weighted manhattan distance (WMD) // AnsGrp:0 - AnsGrp:N AnswerStep ansGroup = new AnswerStep(0); for (int i = 0, ilen = strokes.Count - 1; i < ilen; i++) { ansGroup.Strokes.Add(strokes[i]); double d = CommonFunction.WeightedManhattanDistance(strokes[i].CenterPoint, strokes[i + 1].CenterPoint, 1.0 - GROUP_WM_YWEIGHT, GROUP_WM_YWEIGHT); //Console.WriteLine("WMDistance: " + d); if (d > GROUP_WM_THRES) { ansGroupList.Add(ansGroup); ansGroup = new AnswerStep(ansGroupList.Count); } } ansGroup.Strokes.Add(strokes[strokes.Count - 1]); ansGroupList.Add(ansGroup); // for reworking bool combined = true; while (combined) { combined = false; for (int i = 0, ilen = ansGroupList.Count; i < ilen; i++) { for (int k = i + 1; k < ilen; k++) { Rect ri = ansGroupList[i].GetBounds(); Rect rk = ansGroupList[k].GetBounds(); // Overlap condition if (rk.Top < ri.Bottom && ri.Top < rk.Bottom) { // calculate y-axis overlap amount double overlap = 0.0; if (ri.Top <= rk.Top && ri.Bottom <= rk.Bottom) { overlap = (ri.Bottom - rk.Top) / ((ri.Height < rk.Height) ? ri.Height : rk.Height); } else if (rk.Top <= ri.Top && rk.Bottom <= ri.Bottom) { overlap = (rk.Bottom - ri.Top) / ((ri.Height < rk.Height) ? ri.Height : rk.Height); } else { overlap = 1.0; } if (GROUP_WM_OVERLAP_Y < overlap) { // calculate x-axis distance double dx = GROUP_WM_THRES / (1.0 - GROUP_WM_YWEIGHT); if (rk.Left < ri.Right + dx && ri.Left - dx < rk.Right) { // combine answer step group ansGroupList[i].Strokes.AddRange(ansGroupList[k].Strokes); ansGroupList.RemoveAt(k); combined = true; break; } } } } if (combined) break; } } // reassign group ID for (int i = 0, ilen = ansGroupList.Count; i < ilen; i++) { ansGroupList[i].GroupID = i; } return ansGroupList; }
/// <summary> /// Calculate answer process similarity /// </summary> /// <param name="group1"></param> /// <param name="group2"></param> /// <returns>Matching result</returns> public DPMatchingResult CalcAnswerProcessSimilarity(List<AnswerStep> group1, List<AnswerStep> group2) { // parameters---------------- // dynamic programming matching double gapCost = 40.0; // weight of join cost. 1: without JOIN penalty, 0: with JOIN penalty same as gapCost double joinCostWeight = 1.0; // --------------------------- if (group1.Count == 0 && group2.Count == 0) { DPMatchingResult result = new DPMatchingResult(0.0, new List<int[]>()); return result; } if (group1.Count == 0) { DPMatchingResult result = new DPMatchingResult(1.0 / ((double)group2.Count * gapCost), new List<int[]>()); return result; } if (group2.Count == 0) { DPMatchingResult result = new DPMatchingResult(1.0 / ((double)group1.Count * gapCost), new List<int[]>()); return result; } // DP matching double[,] m = new double[group1.Count + 1, group2.Count + 1]; // from which direction? // 0-n->Match, -1->fromGRP1Skip, -2->fromGRP2Skip int[,] from = new int[group1.Count + 1, group2.Count + 1]; double[,] dm = new double[group1.Count, group2.Count]; m[0, 0] = 0.0; // implememtation of corner case // entry point for part matching for (int i = 1, ilen = group1.Count + 1; i < ilen; i++) { m[i, 0] = (double)i * gapCost; from[i, 0] = -1; } for (int i = 0, ilen = group2.Count + 1; i < ilen; i++) { m[0, i] = (double)i * gapCost; from[0, i] = -2; } from[0, 0] = 0; for (int i = 1, ilen = group1.Count + 1; i < ilen; i++) { for (int k = 1, klen = group2.Count + 1; k < klen; k++) { double distance_match = double.MaxValue; if (from[i - 1, k - 1] >= 0) { // cannot join distance_match = CalcAnswerStepDistance(group1[i - 1], group2[k - 1]).Distance; from[i, k] = 0; } else if (from[i - 1, k - 1] == -1) { // can join at group 1 for (int n = 0; from[i - 1 - n, k - 1] == -1; n++) { // n-> number of joins AnswerStep join_group = new AnswerStep(i - 1, group1[i - 1].Strokes); for (int o = 1; o <= n; o++) { join_group.JoinStrokes(group1[i - 1 - o]); } double distance_tmp = CalcAnswerStepDistance(join_group, group2[k - 1]).Distance; if (distance_tmp < distance_match) { distance_match = distance_tmp; from[i, k] = n; } } } else if (from[i - 1, k - 1] == -2) { // can join at group 2 for (int n = 0; from[i - 1, k - 1 - n] == -2; n++) { // n-> number of joins AnswerStep join_group = new AnswerStep(k - 1, group2[k - 1].Strokes); for (int o = 1; o <= n; o++) { join_group.JoinStrokes(group2[k - 1 - o]); } double distance_tmp = CalcAnswerStepDistance(group1[i - 1], join_group).Distance; if (distance_tmp < distance_match) { distance_match = distance_tmp; from[i, k] = n; } } } dm[i - 1, k - 1] = distance_match; //Console.WriteLine("AnswerStep Distance: " + distance_match.ToString()); double from_grp1 = m[i - 1, k] + gapCost; double from_grp2 = m[i, k - 1] + gapCost; double from_match = m[i - 1, k - 1] + distance_match - ((double)from[i, k] * gapCost * joinCostWeight); if (1 < i && 1 < k) { // can match, skip ; } else if (i == 1 && k == 1) { // can only match from_grp1 = double.MaxValue; from_grp2 = double.MaxValue; } else if (k == 1) { // can match and group 1 skip from_grp2 = double.MaxValue; } else if (i == 1) { // can match and group 2 skip from_grp1 = double.MaxValue; } if (from_match <= from_grp1 && from_match <= from_grp2) { m[i, k] = from_match; } else if (from_grp1 <= from_grp2 && from_grp1 <= from_match) { from[i, k] = -1; m[i, k] = from_grp1; } else if (from_grp2 <= from_grp1 && from_grp2 <= from_match) { from[i, k] = -2; m[i, k] = from_grp2; } } } // generate matching list // {group1Pos, group2Pos, distance, joinCount} List<int[]> matchingList = new List<int[]>(); int g1pos = group1.Count - 1; int g2pos = group2.Count - 1; //int matchCnt = 0; for (int i = group1.Count, k = group2.Count; i != 0 || k != 0; ) { int[] match = null; switch (from[i, k]) { case -1: match = new int[4] { g1pos, -1, -1, 0 }; i--; g1pos--; break; case -2: match = new int[4] { -1, g2pos, -1, 0 }; k--; g2pos--; break; default: // match match = new int[4] { g1pos, g2pos, (int)(dm[g1pos, g2pos] * 100.0), from[i,k] }; i--; k--; g1pos--; g2pos--; //matchCnt++; break; } matchingList.Add(match); } matchingList.Reverse(); //DPMatchingResult res = new DPMatchingResult(m[group1.Count, group2.Count] / (group1.Count + group2.Count + 1.0), matchingList); DPMatchingResult res = new DPMatchingResult(m[group1.Count, group2.Count] / (Math.Min(group1.Count, group2.Count) + 1.0), matchingList); return res; }
/// <summary> /// Join an answer step after the answer step /// </summary> /// <param name="step"></param> public void JoinStrokes(AnswerStep step) { // Calculate offset value for coordinate transformation Rect orgBounds = this.GetBounds(); Rect addBounds = step.GetBounds(); Point endPoint = new Point(orgBounds.Left, (orgBounds.Top + orgBounds.Bottom) / 2.0); Point startPoint = new Point(addBounds.Left, (addBounds.Top + addBounds.Bottom) / 2.0); Point offsetPoint = new Point(endPoint.X - startPoint.X, endPoint.Y - startPoint.Y); // transform coordinates to join anter the origin strokes foreach (AnalysisPenStroke addStroke in step.Strokes) { AnalysisPenStroke s = new AnalysisPenStroke(); foreach (AnalysisPenPoint addPoint in addStroke.Points) { AnalysisPenPoint p = new AnalysisPenPoint(addPoint.Time, addPoint.X + offsetPoint.X, addPoint.Y + offsetPoint.Y); s.Points.Add(p); } this.Strokes.Add(s); } }