/// <summary> /// Visualize answer process matching result /// </summary> /// <param name="ansGroupList1"></param> /// <param name="ansGroupList2"></param> /// <param name="matchingResult"></param> /// <param name="graphCanvas"></param> /// <param name="g2Origin">Offset point of origin</param> private void VisualizeMatchingResult(List<AnswerStep> ansGroupList1, List<AnswerStep> ansGroupList2, DPMatchingResult matchingResult, Canvas graphCanvas, Point g2Origin) { for (int i = 0, ilen = matchingResult.MatchingList.Count; i < ilen; i++) { List<int[]> matchings = matchingResult.MatchingList; if (matchings[i][0] != -1 && matchings[i][1] != -1) { // draw joined group Rect rect1 = ansGroupList1[matchings[i][0]].GetBounds(Config.OutputCanvasWidth, Config.OutputCanvasHeight); Rect rect2 = ansGroupList2[matchings[i][1]].GetBounds(Config.OutputCanvasWidth, Config.OutputCanvasHeight); if (matchings[i][3] > 0) { // join List<AnswerStep> joinSteps = new List<AnswerStep>(); Rect jr; List<int> joinedId = new List<int>(); double canvasLeftOffset = 0.0; double canvasTopOffset = 0.0; if (matchings[i - 1][1] == -1) { // group1 joined for (int k = 0; k <= matchings[i][3]; k++) { joinSteps.Add(ansGroupList1[matchings[i][0] - k]); joinedId.Add(matchings[i][0] - k); } jr = AnswerStep.GetJoinedBounds(joinSteps, Config.OutputCanvasWidth, Config.OutputCanvasHeight); rect1 = jr; } else { // group2 joined for (int k = 0; k <= matchings[i][3]; k++) { joinSteps.Add(ansGroupList2[matchings[i][1] - k]); joinedId.Add(matchings[i][1] - k); } canvasLeftOffset = g2Origin.X; canvasTopOffset = g2Origin.Y; jr = AnswerStep.GetJoinedBounds(joinSteps, Config.OutputCanvasWidth, Config.OutputCanvasHeight); rect2 = jr; } joinedId.Sort(); Rectangle drawRect = new Rectangle(); drawRect.Stroke = Brushes.Red; drawRect.StrokeThickness = 3; drawRect.SetValue(Canvas.LeftProperty, jr.Left + canvasLeftOffset); drawRect.SetValue(Canvas.TopProperty, jr.Top + canvasTopOffset); drawRect.Width = jr.Width; drawRect.Height = jr.Height; graphCanvas.Children.Add(drawRect); // draw joined IDs StringBuilder idtxt = new StringBuilder("Joined: "); foreach (int id in joinedId) { idtxt.Append(id.ToString("D2") + ", "); } idtxt.Length -= 2; ContentControl idtxtContent = new ContentControl(); Canvas.SetLeft(idtxtContent, jr.Left + canvasLeftOffset); Canvas.SetTop(idtxtContent, jr.Bottom + canvasTopOffset); TextBlock idtxtBlock = new TextBlock(); idtxtBlock.Text = idtxt.ToString(); idtxtBlock.Foreground = Brushes.Red; idtxtBlock.FontSize = 16; idtxtContent.Content = idtxtBlock; graphCanvas.Children.Add(idtxtContent); } // draw matching connector Line l = new Line(); l.Stroke = Brushes.Red; l.StrokeThickness = 2; l.X1 = (rect1.TopRight.X + rect1.BottomRight.X) / 2.0; l.Y1 = (rect1.TopRight.Y + rect1.BottomRight.Y) / 2.0; l.X2 = (rect2.TopLeft.X + rect2.BottomLeft.X) / 2.0 + g2Origin.X; l.Y2 = (rect2.TopLeft.Y + rect2.BottomLeft.Y) / 2.0 + g2Origin.Y; l.HorizontalAlignment = HorizontalAlignment.Left; l.VerticalAlignment = VerticalAlignment.Center; graphCanvas.Children.Add(l); // draw distance score ContentControl txtContent = new ContentControl(); Canvas.SetLeft(txtContent, (l.X1 + l.X2) / 2.0); Canvas.SetTop(txtContent, (l.Y1 + l.Y2) / 2.0); TextBlock tb = new TextBlock(); tb.Text = matchings[i][2].ToString(); tb.Foreground = Brushes.Red; tb.FontSize = 16; txtContent.Content = tb; graphCanvas.Children.Add(txtContent); } } // similarity score ContentControl totalDistanceContent = new ContentControl(); Canvas.SetLeft(totalDistanceContent, 0.0); Canvas.SetTop(totalDistanceContent, 0.0); TextBlock txtBlock = new TextBlock(); txtBlock.Text = ((int)(matchingResult.Distance * 1000.0)).ToString(); txtBlock.Foreground = Brushes.Red; txtBlock.FontSize = 18; totalDistanceContent.Content = txtBlock; graphCanvas.Children.Add(totalDistanceContent); }
/// <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> /// Calculate distance betwee strokes. /// DP matching /// </summary> /// <param name="stroke1"></param> /// <param name="stroke2"></param> /// <returns></returns> private DPMatchingResult CalcStrokeDistance(AnalysisPenStroke stroke1, AnalysisPenStroke stroke2) { //double distance = 0.0; // parameters ---------------------------- // gap cost for DP matching double gapCost = 40.0; // --------------------------------------- Point[] points1 = stroke1.GetRamerSampledPoints_NonRecursive(AnswerSheetAnalyzer.RamerSamplingDistanceThres); Point[] points2 = stroke2.GetRamerSampledPoints_NonRecursive(AnswerSheetAnalyzer.RamerSamplingDistanceThres); if (points1.Length == 0 && points2.Length == 0) { return new DPMatchingResult(0.0, new List<int[]>()); } if (points1.Length == 0) { return new DPMatchingResult(1.0 / ((double)points2.Length * gapCost), new List<int[]>()); //return points2.Length * gapCost; } if (points2.Length == 0) { return new DPMatchingResult(1.0 / ((double)points1.Length * gapCost), new List<int[]>()); //return points1.Length * gapCost; } double[,] m = new double[points1.Length, points2.Length]; // from which direction? // 0->match, -1->fromPoints1, -2->fromPoints2 int[,] from = new int[points1.Length, points2.Length]; for (int i = 0, ilen = points1.Length; i < ilen; i++) { m[i, 0] = (double)i * gapCost; from[i, 0] = -1; } for (int i = 0, ilen = points2.Length; i < ilen; i++) { m[0, i] = (double)i * gapCost; from[0, i] = -2; } from[0, 0] = 0; for (int i = 1, ilen = points1.Length; i < ilen; i++) { for (int k = 1, klen = points2.Length; k < klen; k++) { double d = 0.0; d = CommonFunction.Distance(points1[i], points2[k]); //Console.WriteLine("Stroke distance: " + d); //double[] scores = new double[] { m[i - 1, k] + gapCost, m[i - 1, k - 1] + d, m[i, k - 1] + gapCost }; //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 = points1.Length - 1; int g2pos = points2.Length - 1; //int matchCnt = 0; for (int i = points1.Length - 1, k = points2.Length - 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--; //matchCnt++; break; } matchingList.Add(match); } matchingList.Reverse(); DPMatchingResult res = new DPMatchingResult(m[points1.Length - 1, points2.Length - 1] / (Math.Min(points1.Length, points2.Length)), matchingList); //distance = m[points1.Length - 1, points2.Length - 1] / (Math.Min(points1.Length, points2.Length)); return res; }
/// <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; }