A simple line class is define as two PointF members containing the start and end coordinates:
public class Line {
PointF s, e;
}
I have two lists containing all horizontal and vertical lines that appear on a drawing canvas and form one or more tables.
List<Line> AllHorizontalLines;
List<Line> AllVerticalLines;
I need to group these lines so that lines belonging to one table are captured in a single group, thus the grouping function would have a signature like this:
List<List<Line>> GroupLines(List<Line> hor, List<Line> ver)
{
}
For simplicity we are assuming that there are only "simple" tables on the page, i.e. no nested table are there. However there can be merged cells, so we have to ignore small horizontal and vertical lines that are smaller than the full height of the parent table. For further simplicity, assume that both input lists are sorted (horizontal lines w.r.t. Y-axis and vertical lines w.r.t. X-axis).
Is there any known algorithm to solve this problem? Or can anyone help me devise one?
Here is my suggested approach:
Clean both lists so there aren't any fully contained 'small' lines.
Pick any line.
Take all the lines that touch (intersect) this line.
For each of these lines take all the lines that touch them.
Continue until you can't find any more touching lines.
You now have a group.
Pick a line from those that remain and repeat until there are no more lines left.
Code:
public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
// Clean the input lists here. I'll leave the implementation up to you.
var ungroupedLines = new HashSet<Line>(horizontalLines.Concat(verticalLines));
var groupedLines = new List<List<Line>>();
while (ungroupedLines.Count > 0)
{
var group = new List<Line>();
var unprocessedLines = new HashSet<Line>();
unprocessedLines.Add(ungroupedLines.TakeFirst());
while (unprocessedLines.Count > 0)
{
var line = unprocessedLines.TakeFirst();
group.Add(line);
unprocessedLines.AddRange(ungroupedLines.TakeIntersectingLines(line));
}
groupedLines.Add(group);
}
return groupedLines;
}
public static class GroupingExtensions
{
public static T TakeFirst<T>(this HashSet<T> set)
{
var item = set.First();
set.Remove(item);
return item;
}
public static IEnumerable<Line> TakeIntersectingLines(this HashSet<Line> lines, Line line)
{
var intersectedLines = lines.Where(l => l.Intersects(line)).ToList();
lines.RemoveRange(intersectedLines);
return intersectedLines;
}
public static void RemoveRange<T>(this HashSet<T> set, IEnumerable<T> itemsToRemove)
{
foreach(var item in itemsToRemove)
{
set.Remove(item);
}
}
public static void AddRange<T>(this HashSet<T> set, IEnumerable<T> itemsToAdd)
{
foreach(var item in itemsToAdd)
{
set.Add(item);
}
}
}
Add this method to Line
public bool Intersects(Line other)
{
// Whether this line intersects the other line or not.
// I'll leave the implementation up to you.
}
Notes:
If this code runs too slowly you might need to scan horizontally, picking up connected lines as you go. Might also be worth looking at this.
Specialised:
public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
// Clean the input lists here. I'll leave the implementation up to you.
var ungroupedHorizontalLines = new HashSet<Line>(horizontalLines);
var ungroupedVerticalLines = new HashSet<Line>(verticalLines);
var groupedLines = new List<List<Line>>();
while (ungroupedHorizontalLines.Count + ungroupedVerticalLines.Count > 0)
{
var group = new List<Line>();
var unprocessedHorizontalLines = new HashSet<Line>();
var unprocessedVerticalLines = new HashSet<Line>();
if (ungroupedHorizontalLines.Count > 0)
{
unprocessedHorizontalLines.Add(ungroupedHorizontalLines.TakeFirst());
}
else
{
unprocessedVerticalLines.Add(ungroupedVerticalLines.TakeFirst());
}
while (unprocessedHorizontalLines.Count + unprocessedVerticalLines.Count > 0)
{
while (unprocessedHorizontalLines.Count > 0)
{
var line = unprocessedHorizontalLines.TakeFirst();
group.Add(line);
unprocessedVerticalLines.AddRange(ungroupedVerticalLines.TakeIntersectingLines(line));
}
while (unprocessedVerticalLines.Count > 0)
{
var line = unprocessedVerticalLines.TakeFirst();
group.Add(line);
unprocessedHorizontalLines.AddRange(ungroupedHorizontalLines.TakeIntersectingLines(line));
}
}
groupedLines.Add(group);
}
return groupedLines;
}
This assumes no lines overlap as it doesn't check if horizontal lines touch other horizontal lines (same for vertical).
You can probably remove the if-else. That's just there in case there are vertical lines not attached to horizontal lines.
The following seems to work:
Set up a dictionary mapping bounding rectangles to a list of lines in each rectangle.
For each line in both your input lists (we don't care about the direction)
Create a bounding rectangle out of the line
Check if the line crosses any existing bounding rectangle(s).
If so, merge the lines from those rectangle(s), add the current line, delete the touched rectangles, calculate a new bounding rectangle, and check again.
Otherwise, add this new rectangle to the dictionary and delete the old one(s).
Return the list of lines from each rectangle.
Here's the code I've got:
public static IEnumerable<IEnumerable<Line>> GroupLines(IEnumerable<Line> lines)
{
var grouped = new Dictionary<Rectangle, IEnumerable<Line>>();
foreach (var line in lines)
{
var boundedLines = new List<Line>(new[] { line });
IEnumerable<Rectangle> crossedRectangles;
var boundingRectangle = CalculateRectangle(boundedLines);
while (
(crossedRectangles = grouped.Keys
.Where(r => Crosses(boundingRectangle, r))
.ToList()
).Any())
{
foreach (var crossedRectangle in crossedRectangles)
{
boundedLines.AddRange(grouped[crossedRectangle]);
grouped.Remove(crossedRectangle);
}
boundingRectangle = CalculateRectangle(boundedLines);
}
grouped.Add(boundingRectangle, boundedLines);
}
return grouped.Values;
}
public static bool Crosses(Rectangle r1, Rectangle r2)
{
return !(r2.Left > r1.Right ||
r2.Right < r1.Left ||
r2.Top > r1.Bottom ||
r2.Bottom < r1.Top);
}
public static Rectangle CalculateRectangle(IEnumerable<Line> lines)
{
Rectangle rtn = new Rectangle
{
Left = int.MaxValue,
Right = int.MinValue,
Top = int.MaxValue,
Bottom = int.MinValue
};
foreach (var line in lines)
{
if (line.P1.X < rtn.Left) rtn.Left = line.P1.X;
if (line.P2.X < rtn.Left) rtn.Left = line.P2.X;
if (line.P1.X > rtn.Right) rtn.Right = line.P1.X;
if (line.P2.X > rtn.Right) rtn.Right = line.P2.X;
if (line.P1.Y < rtn.Top) rtn.Top = line.P1.Y;
if (line.P2.Y < rtn.Top) rtn.Top = line.P2.Y;
if (line.P1.Y > rtn.Bottom) rtn.Bottom = line.P1.Y;
if (line.P2.Y > rtn.Bottom) rtn.Bottom = line.P2.Y;
}
return rtn;
}
OK. I finally devised an efficient algorithm myself. Here are the steps for any future reader.
Define a grand collection of Tuple<List<Line>, List<Line>>. Each tuple in this collection will represent one table (all horizontal and vertical lines of that table).
Find all vertical lines that touch a horizontal line's starting point at one end and another horizontal line's starting point at the other end. These will be the left-most lines of each table.
Now for each left-line (call it LV):
a. locate all the vertical lines that have same starting and ending Y as LV and thus belong to the same table. Add these "sister" lines to a list called MyVSisters.
b. Locate the right-most vertical sister line's X-coordinate (call it RVX).
c. Now locate all the horizontal lines that stretch from the LV's X to RVX and have their Y between LV's Y1 and Y2. Add these lines to a collection called MyHSisters.
d. Add the tuple MyHSisters and MyVSisters to the grand collection.
Return grand collection.
I'm not marking it as answer yet. I'll wait for response from both of the guys and compare performance and accuracy of all 3 before deciding which one is the best answer.
Related
Suppose I have a simple class with properties of a vertical line on a 2D Cartesian coordinate plane such as this:
public class VerticalLine
{
int x, y1, y2;
...
}
Assume that Y1 can be smaller or larger than Y2, but Y1 and Y2 never contain the same value.
In runtime, I will gather a collection(List) of vertical lines in similar format:
List<VerticalLine> lines = new List<VerticalLine>
{
new VerticalLine() { X=12, Y1=3, Y2=15 },
new VerticalLine() { X=23, Y1=23, Y2=5 },
new VerticalLine() { X=32, Y1=12, Y2=10 },
new VerticalLine() { X=37, Y1=15, Y2=12 },
...
};
Note that there are no specific order of the lines in the list.
By drawing imaginary horizontal lines from one end of vertical line to an end of another line, I need to check that if the lines in the above list will be able to connect and form a loop. In other words, pick a line in list, connect one end to an end of another line using a horizontal line, and then to next line and so on... until the last line connect back to the original line. See image for example.
Note that:
ALL the lines in the list must be used.
The order of the lines list is not important in finding the loop. Any line can be the first line.
The directions of lines are not important in finding the loop, meaning Y2 of one line may try to connect to Y1 or Y2 of the next line, as long as they share the same Y value.
In simple words, my problem is how to write a method to check if lines can form a loop.
My first thought of doing so will be using a recursive method to exhaustively try connecting the next line. But this way will involve a large amount of list manipulations, which can be slow. I wonder if there are 'better' way to achieve the same goal.
This is a working solution I have so far. It is simple but probably not the most efficient way of doing so. It is just a recursive and exhaustive method.
public bool CheckForLoop (List<VerticalLine> lines)
{
if (lines == null || lines.Count < 2)
return false;
VerticalLine head = lines[0];
return CheckForLoop(head.Y1, head.Y2, lines.GetRange(1, lines.Count - 1));
}
// helper method for recursion
private bool CheckForLoop(int start, int last, List<VerticalLine> remaining)
{
if (remaining.Count == 1)
{
return remaining[0].Y1 == start && remaining[0].Y2 == last || remaining[0].Y2 == start && remaining[0].Y1 == last;
}
foreach (var next in remaining)
{
bool foundMatching = false;
int newLast = 0;
if (next.Y1 == last)
{
foundMatching = true;
newLast = next.Y2;
} else if (next.Y2 == last)
{
foundMatching = true;
newLast = next.Y1;
}
if (foundMatching)
{
List<VerticalLine> remaining2 = new List<VerticalLine>(remaining);
remaining2.Remove(next);
if (CheckForLoop(start, newLast, remaining2))
{
return true;
}
}
}
return false;
}
What I don't like about this is that it requires a separate helper method for the recursion. I would be best if everything is done within the one method itself.
I have a grid and get a direction to move. This direction can be
Vector2Int movementDirection = new Vector2Int(/* this can be
(0,1) // up
(0,-1) // down
(-1,0) // left
(1,0) // right
*/);
Vector2Int is a class from the Unity Framework!
https://docs.unity3d.com/ScriptReference/Vector2Int.html
When moving from bottom to top I want to check all the cells around my targetCell. But I don't want to check the bottom cell because this cell is where I come from.
When I move from the left to the right, I don't want to check the left cell.
So I went for this
private void CheckCells(Vector2Int movementDirection)
{
Vector2Int[] cellDirections = { Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right };
cellDirections.Where(direction => !direction.Equals(movementDirection)).ToArray();
for (int i = 0; i < cellDirections.Length; i++)
{
// check the other 3 cells
}
}
This array will still have a length of 4. It seems I can not compare a Vector2Int.up with (0,1)
I tried
!direction.Equals(movementDirection)
and
directon != movementDirection
How can I start a loop for just 3 of 4 directions? The given parameter should remove the fourth direction from the array.
Maybe I don't need an array?
This line :
cellDirections.Where(direction => !direction.Equals(movementDirection)).ToArray();
does nothing to your cellDirections variable, as you don't assign the result.
Either try :
cellDirections = cellDirections
.Where(direction => !direction.Equals(movementDirection)).ToArray();
or any other assignment :
var anyVar = cellDirections
.Where(direction => !direction.Equals(movementDirection)).ToArray();
Note that you could also just loop through your cellDirections and add an if inside your for :
foreach (var direction in cellDirections)
{
if(!direction.Equals(movementDirection))
{
// check the cells
}
}
I've been using Microsoft Computer Vision to read receipts, trying to find an alternative to Abby's OCR as there is a substantial price difference.
The results I get are always grouped by regions. This obviously makes it much harder to identify the corresponding fields with their amounts.
Is there a way through Microsoft Vision or anyway at all that I can achieve the same aligned output as Abby's?
Here's an image with both results and the receipt
Ocr Results
I realize this is not a complete solution but I think it's enough to get you started.
The Computer Vision API returns a JSON result with a lines property that is just an array of objects with a boundingBox property.
These boundingBoxes are the X,Y coordinates of the top-left and bottom-right coordinates of the "square" of each phrase.
You basically need to process this array and "sort" the items based on this property.
In this JSFiddle you'll see that I'm sorting the lines by Y coordinate and then grouping them.
What's left to do is be "smarter" about the grouping - if the Y coordinates are 201 and 202 you can assume that they are on the same line and just add them to one same line, sorted by ascending X coordinate.
Code:
if (jsonResponse.status == 'Succeeded') {
var result = '';
// Sort lines by Y coordinate
jsonResponse.recognitionResult.lines.sort(function(a, b) {
var topLeftYCoordA = a.boundingBox[1];
var topLeftYCoordB = b.boundingBox[1];
if (topLeftYCoordA > topLeftYCoordB) {
return 1;
}
if (topLeftYCoordA < topLeftYCoordB) {
return -1;
}
return 0;
})
// group lines by Y coordinate
var grouped = {};
jsonResponse.recognitionResult.lines.map(function(line) {
var topLeftYcoordinate = line.boundingBox[1];
if (!grouped[topLeftYcoordinate]) {
grouped[topLeftYcoordinate] = line;
} else {
grouped[topLeftYcoordinate] += line;
}
});
Object.keys(grouped).forEach(function(yCoordinate) {
result += yCoordinate + ' - ' + grouped[yCoordinate].text + '</br>';
})
$(".right").html(result);
}
Result:
I have a List structured as:
"Then a sentence woop", 340
"and another one", 256
"in order they appear", 700
"in a linked file", 304
The list contains the highest scored sentence from each paragraph of a text file. I need to output each sentence, but with a track bar reduce how many are shown.
So remove the sentence with the lowest score, the issue is the list is ordered by the sentences appearance in the original text, and the output needs to be in this order. So if I had a track bar for the above list, it would have 4 points. If I move it to point 3 sentence 2 would vanish, point 2 sentence 2 and 4 would vanish.
The code where the list is generated is:
public List<ScoredSentence> buildSummarySentenceList()
{
List<ScoredSentence> ultimateScoreslist = new List<ScoredSentence>();
scoreCoord2 = -1;
for (int x1 = 0; x1 < results.Length; x1++)
{
List<ScoredSentence> paragraphsScorelist = new List<ScoredSentence>();
for (int x2 = 0; x2 < results[x1].Length; x2++)
{
scoreCoord2++;
paragraphsScorelist.Add(new ScoredSentence(results[x1][x2], intersectionSentenceScores[scoreCoord2]));
}
var maxValue = paragraphsScorelist.Max(s => s.score);
string topSentence = paragraphsScorelist.First(s => s.score == maxValue).sentence;
int topScore = paragraphsScorelist.First(s => s.score == maxValue).score;
ultimateScoreslist.Add(new ScoredSentence(topSentence, topScore));
}
return ultimateScoreslist;
}
public class ScoredSentence
{
public string sentence { get; set; }
public int score { get; set; }
public ScoredSentence(string sentence, int score)
{
this.sentence = sentence;
this.score = score;
}
}
This code loops though a jagged array and a list of sentence to sentence scores, it results in a list as shown at the top.
Currently I output every sentence, and set the trackbar to be as long as there are sentences:
protected void summaryOutput()
{
List<ScoredSentence> ultimateScoreslist = buildSummarySentenceList();
trackBSummaryPercent.Maximum = ultimateScoreslist.Count;
lblNoOfLines.Text += trackBSummaryPercent.Maximum.ToString();
//make 2 lists for the reduction????
for (var x = 0; x < ultimateScoreslist.Count; x++)
{
TextboxSummary.Text += ultimateScoreslist[x].sentence + "\n";
}
}
I have thought of on every onchange tick of the trackbar to have a second clone list and remove the lowest value entry. Then when the bar is moved up to somehow move the missing entries back from the clone list. I don't like this method as it may cause program speed issues when for example my current test text is 100 paragraphs long, and moving the trackbar a lot may make it become slow.
Add a displayed property to your ScoredSentence object. Then whenever the list changes, or the track bar selection changes, run this method on it to update the set of displayed elements. The main list should always be sorted in the order you want it displayed. numberToDisplay would be calculated by whatever means you are using to go from your UI to the number of items.
public void OnUpdate()
{
var orderedEnumerable = ScoresList.OrderByDescending (s => s.Score);
foreach (var s in orderedEnumerable.Take (numberToDisplay))
{
s.Displayed = true;
}
foreach (var s in orderedEnumerable.Skip(numberToDisplay))
{
s.Displayed = false;
}
}
Then use the following instead of the list whenever you need to display it
ScoredSentences.Where(s=> s.Displayed);
I am having some trouble correctly implementing the Bentley-Ottmann algorithm in C#. I am trying to implement it according to the pseudocode here. I have posted my main code below. Assuming my BST and PriorityQueue classes are implemented correctly, do you see any problems with the code?
There are no errors, but not all intersection points are found, only some. My guess is that there's an error in the else part of the code (when the current event is an intersection point). I'm not sure what the pseudocode means by swapping the position of two segments in the BST. Is the way I do it fine? Because in the end, the two aren't really swapped in the BST. I can't just change their positions either, because that could break the BST properties.
Also, am I right in assuming that segments are ordered in the BST by the Y-coordinate of their left endpoint?
Another error I've noticed that I can't seem to be able to track down is that sometimes the point (0, 0) gets into eventList. (0, 0) is outputted by Geometry.Intersects in case there is no intersection, but in that case the if conditions should stop it from getting added. I have no idea how it gets in. If I print the contents of eventList after adding a point in, (0, 0) never shows up. If I print the contents after extracting and popping an element, (0, 0) sometimes shows up. Could this have anything to do with the Pop() method messing with the references, or is it definitely a problem in my PriorityQueue implementation?
If needed I can show my implementations for the BST and priority queue as well.
static class BentleyOttman
{
private static void AddIntersectionEvent(PriorityQueue eventList, Segment segEv, Segment segA, SegPoint i)
{
i.IntersectingSegments = new Tuple<Segment, Segment>(segEv, segA);
i.Type = SegmentPointType.IntersectionPoint;
eventList.Add(i);
}
public static void Solve(Panel surface, TextBox debug)
{
debug.Clear();
var segList = Generator.SegList;
PriorityQueue eventList = new PriorityQueue();
foreach (Segment s in segList)
{
eventList.Add(new SegPoint(s.A, s, SegmentPointType.LeftEndpoint));
eventList.Add(new SegPoint(s.B, s, SegmentPointType.RightEndpoint));
}
BST sweepLine = new BST();
while (!eventList.Empty)
{
SegPoint ev = eventList.Top();
eventList.Pop();
if (ev.Type == SegmentPointType.LeftEndpoint)
{
Segment segEv = ev.Segment;
sweepLine.Insert(segEv);
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(segEv, segA, out i.Point))
{
AddIntersectionEvent(eventList, segA, segEv, i);
}
if (segB != null && Geometry.Intersects(segEv, segB, out i.Point))
{
AddIntersectionEvent(eventList, segEv, segB, i);
}
}
else if (ev.Type == SegmentPointType.RightEndpoint)
{
Segment segEv = ev.Segment;
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
sweepLine.Remove(segEv);
SegPoint i = new SegPoint();
if (segA != null && segB != null && Geometry.Intersects(segA, segB, out i.Point))
{
AddIntersectionEvent(eventList, segA, segB, i);
}
}
else
{
Generator.DrawPoint(ev.Point, surface, Brushes.Red);
Segment seg1 = ev.IntersectingSegments.Item1;
Segment seg2 = ev.IntersectingSegments.Item2;
sweepLine.Remove(seg1);
sweepLine.Remove(seg2);
Segment t = new Segment(seg1);
seg1 = new Segment(seg2);
seg2 = new Segment(t);
sweepLine.Insert(seg1);
sweepLine.Insert(seg2);
Segment segA = sweepLine.InorderPre(seg2);
Segment segB = sweepLine.InorderSuc(seg1);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(seg2, segA, out i.Point))
AddIntersectionEvent(eventList, segA, seg2, i);
if (segB != null && Geometry.Intersects(seg1, segB, out i.Point))
AddIntersectionEvent(eventList, seg1, segB, i);
}
}
}
}
I really cannot understand your code without some idea of what exactly the other classes do, but I can answer some of your other questions.
The segments are ordered in the BST by the Y coordinate of their intersection with the sweep line. So when we encounter a left endpoint we add the segment to the tree using the y coordinate of the left endpoint of the entering segment (comparing it with the Y coordinate of the intersection of the other segment with the sweep line). When we encounter a right endpoint we remove the segment from the tree. When we encounter an intersection, then the order of the intersections of the two segments with the sweep line switches, so we swap the two segments in the tree. For example consider the two segments
A = {(-1,1),(1,-1)} and
B = {(-1,-1),(1,1)}
When the X coordinate of the sweep line is less than 0 then the intersection of segment A with the sweep line is greater than the intersection of segment B with the sweep line. and if the sweep line is greater than 0 the reverse is true. (Draw a picture.)
It is probably instructive to draw a simple example, and trace what is going on step by step, drawing the sweep line for each event and labeling the segments in columns between the events, then keeping track of the BST and verifying that the BST keeps the same order as the segments in the region where it is valid. (I'm sorry if that is not as clear as it could be.)
Note: This assumes that your segments are in "general position", i.e. that no segment is vertical, no more than two segments intersect at a given point, etc. Dealing with segments not in general position is outlined on the wikipedia page