I'm using v6.4.2 of the C# version of the ClipperLib.
I have a lot of squares making up a fishnet. I also have a rectangle.
I want to get a result where only the squares that are inside the rectangle are returned and partially overlapped get clipped.
The subjects are in green and the clip is in red:
The result I get is the brown/gray rectangle, which is only one polygon:
I would have expected to have 15 full squares and 13 clipped squares as a result.
This is the code I'm using:
var startX = 100;
var startY = 250;
const int numPolygons = 10;
var subj = new Polygons(numPolygons * numPolygons);
for (var i = 0; i < numPolygons; i++)
{
for (var j = 0; j < numPolygons; j++)
{
var square = new Polygon(4)
{
new IntPoint(startX, startY),
new IntPoint(startX + 10, startY),
new IntPoint(startX + 10, startY - 10),
new IntPoint(startX, startY - 10)
};
subj.Add(square);
// Moving to the right
startX = startX + 10;
}
// Moving down
startY = startY - 10;
startX = 100;
}
var clip = new Polygons(1);
clip.Add(new Polygon(4));
clip[0].Add(new IntPoint(165, 215));
clip[0].Add(new IntPoint(255, 215));
clip[0].Add(new IntPoint(255, 155));
clip[0].Add(new IntPoint(165, 155));
var solution = new Polygons();
var c = new Clipper.Clipper();
c.AddPaths(subj, PolyType.ptSubject, true);
c.AddPaths(clip, PolyType.ptClip, true);
c.Execute(ClipType.ctIntersection, solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd);
Debug.WriteLine("solution.Count: " + solution.Count);
When running the above code is takes about 0.5 seconds. Because the result looks like the clip and subject are switched I've switched them. The result is the same, but now it only takes 0.1 seconds. So something extra is done. I think it is the merging of the resulting squares.
I don't want the result to merge. How can I prevent that? Or perhaps my code is faulty?
According to Clipper documentation of the Execute method:
There are several things to note about the solution paths returned:
...
polygons may rarely share a common edge (though this is now very rare as of version 6)
which I think means that paths get merged when performing any clipping operations.
I've tried to do the same thing with different PolyFillType`s with no success either.
You might want to try running the Execute method on each square individually (subject) against the clipping area on each iteration which should do the job, though performance may suffer as a result.
In this precise case you can easily compute the result by hand, without clipper lib.
The fact that all rectangles are axis-aligned objects makes it possible to speed up computations, so it could even be faster performing the operation yourself.
Related
I have a constantly feeding point array with a length of 4, and want to filter certain "outliers" in the array.
I'm creating a VR/AR app with Opencvforunity and Unity.
Using a live feed from the webcam, I have an 4-length points array which updates and contains x, y 2d coordinates, representing the four corners of a tracked object. And I'm using them as source values to draw a Rect in unity.
Each slot in array contains data such as this:
{296.64151, 88.096649}
However, Unity throws errors and crashes when the a value in the array has
negative values (sometimes happens because of tracking error)
large values exceeding the canvas size (same reason, currently using 1280 x 720)
An example of a "bad value" will be like this :
{-1745.10614, 46.908913} <- negative / big value on X
{681.00519, 1234.15828} <- big value on Y
So I have to somehow create a filter for the array to make the app to work.
The order should not be altered and the data constantly updates so ignoring/skipping bad values will be optimal. I'm new to C# and I have searched but no good luck for "point array"
Here's my code:
Point[] ptsArray = patternTrackingInfo.points2d.toArray();
pt1 = ptsArray[0];
pt2 = ptsArray[2];
pt3 = new OpenCVForUnity.CoreModule.Point(ptsArray[2].x + 5, ptsArray[2].y + 5);
for (int i = 0; i < 4; i++)
{
cropRect = new OpenCVForUnity.CoreModule.Rect(pt1, pt3);
}
pt1 represents the left-top corner and pt2 for right-bottom.
I heard that the right bottom point is exclusive in OpenCV itself so I tried to add a new point to that(pt3), but still crashing - so I believe it is not related to that matter.
Any suggestions for creating a filter for a point array will be very much helpful. Thank you.
I would just create a new list of Points and loop through the existing list, adding only the valid points to the new list. Then that becomes the list that you convert to an array for your OpenCV calls.
List<Point> filteredList = new List<Point>();
for(int i = 0; i < patternTrackingInfo.points2d.Count; i++)
{
if(/*Do your check here*/)
continue;
filteredList.Add(patternTrackingInfo.points2d[i]);
}
Point[] ptsArray = filteredList.toArray();
pt1 = ptsArray[0];
pt2 = ptsArray[2];
pt3 = new OpenCVForUnity.CoreModule.Point(ptsArray[2].x + 5, ptsArray[2].y + 5);
for (int i = 0; i < 4; i++)
{
cropRect = new OpenCVForUnity.CoreModule.Rect(pt1, pt3);
}
is there a way to get the rectangles of the stackcolumn chart bar?
this code snippet is how it can be works but it's very ugly:
var points = new List<Point>();
for (int x = 0; x < chart.Size.Width; x++)
{
for (int y = 0; y < chart.Size.Height; y++)
{
var hp = chart.HitTest(x, y, false, ChartElementType.DataPoint);
var result = hp.Where(h => h.Series?.Name == "Cats");
if (result.Count() > 0)
{
points.Add(new Point(x, y));
}
}
}
var bottomright = points.First();
var topleft = points.Last();
I will try to describe my purpose:
I would like to create a chart from various testresults and make this available as a HTML file. This generated Chart is inserted as an image file in the HTML document. Now, I would like to link each part of a Bar area from the Chart to an external document. Since the graphics is static, I have only the possibility to use the "MAP Area" element to make any area as a link from HTML. The "map" element requires a "rectangle", or these coordinates. That's the reason why I need the coordinator of each part of a Bar.
I have to mention that I am not really familiar with the Chart control yet.
The graphics is generated testweise.
[SOLVED]
i got the solution:
var stackedColumns = new List<Tuple<string,string,Rectangle>>();
for (int p = 0; p < chart.Series.Select(sm => sm.Points.Count).Max(); p++)
{
var totalPoints = 0;
foreach (var series in chart.Series)
{
var width = int.Parse(series.GetCustomProperty("PixelPointWidth"));
var x = (int)area.AxisX.ValueToPixelPosition(p + 1) - (width / 2);
int y = (int)area.AxisY.ValueToPixelPosition(totalPoints);
totalPoints += series.Points.Count > p ? (int)series.Points[p].YValues[0] : 0;
int y_total = (int)area.AxisY.ValueToPixelPosition(totalPoints);
var rect = new Rectangle(x, y_total, width, Math.Abs(y - y_total));
stackedColumns.Add(new Tuple<string, string, Rectangle>(series.Name, series.Points.ElementAtOrDefault(p)?.AxisLabel, rect));
}
}
this workaround works for stackedcolumn and points starts at x-axis=0.
just the PixelPointWidth property has to be set manualy to get the right width. i have not yet found a way to get the default bar width..
This is extremely tricky and I really wish I knew how to get the bounds from some chart functionionality!
You code snippet is actulally a good start for a workaround. I agree though that it has issues:
It is ugly
It doesn't always work
It has terrible performance
Let's tackle these issues one by one:
Yes it is ugly, but then that's the way of workarounds. My solution is even uglier ;-)
There are two things I found don't work:
You can't call a HitTest during a Pre/PostPaint event or terrible things will happen, like some Series go missing, SO exceptions or other crashes..
The result for the widths of the last Series are off by 1-2 pixels.
The performance of testing each pixel in the chart will be terrible even for small charts, but gets worse and worse when you enlarge the chart. This is relatively easy to prevent, though..:
What we are searching are bounding rectangles for each DataPoint of each Series.
A rectangle is defined by left and right or width plus top and bottom or height.
We can get precise values for top and bottom by using the axis function ValueToPixelPosition feeding in the y-value and 0 for each point. This is simple and cheap.
With that out of the way we still need to find the left and right edges of the points. To do so all we need to do it test along the zero-line. (All points will either start or end there!)
This greatly reduces the number of tests.
I have decided to do the testing for each series separately, restaring at 0 each time. For even better performance one could do it all in one go.
Here is a function that returns a List<Rectangle> for a given Series:
List<Rectangle> GetColumnSeriesRectangles(Series s, Chart chart, ChartArea ca)
{
ca.RecalculateAxesScale();
List<Rectangle> rex = new List<Rectangle>();
int loff = s == chart.Series.Last() ? 2 : 0; ;
int y0 = (int)ca.AxisY.ValueToPixelPosition(0);
int left = -1;
int right = -1;
foreach (var dp in s.Points)
{
left = -1;
int delta = 0;
int off = dp.YValues[0] > 0 ? delta : -delta;
for (int x = 0; x < chart.Width; x++)
{
var hitt = chart.HitTest(x, y0 +off );
if (hitt.ChartElementType == ChartElementType.DataPoint &&
((DataPoint)hitt.Object) == dp)
{
if (left < 0) left = x;
right = x;
}
else if (left > 0 && right > left) break;
}
int y = (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]);
rex.Add(new Rectangle(left, Math.Min(y0, y),
right - left + 1 - loff, Math.Abs(y - y0)));
left = -1;
}
return rex;
}
A few notes:
I start by doing a RecalculateAxesScale because we can't Hittest before the current layout has been calculated.
I use a helper variable loff to hold the offset for the width in the last Series.
I start searching at the last x coordinate as the points should all lie in sequence. If they don't because you have used funny x-values or inserted points you may need to start at 0 instead..
I use y0 as the baseline of the zero values for both the hittesting y and also the points' base.
I use a little Math to get the bounds right for both positive and negative y-values.
Here is a structure to hold those rectangles for all Series and code to collect them:
Dictionary<string, List<Rectangle>> ChartColumnRectangles = null;
Dictionary<string, List<Rectangle>> GetChartColumnRectangles(Chart chart, ChartArea ca)
{
Dictionary<string, List<Rectangle>> allrex = new Dictionary<string, List<Rectangle>>();
foreach (var s in chart.Series)
{
allrex.Add(s.Name, GetColumnSeriesRectangles(s, chart, ca));
}
return allrex;
}
We need to re-calculate the rectangles whenever we add points or resize the chart; also whenever the axis view changes. The common code for AxisViewChanged, ClientSizeChanged, Resize and any spot you add or remove points could look like this:
Chart chart= sender as Chart;
GetChartColumnRectangles(chart, chart.ChartAreas[0]);
Let's test the result with a Paint event:
private void chart1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
chart1.ApplyPaletteColors();
foreach (var kv in ChartColumnRectangles)
{
{
foreach (var r in kv.Value)
g.DrawRectangle(Pens.Black, r);
}
}
}
Here it is in action:
Well, I've been down this path and the BIG issue for me is that the custom property of 'PixelPointWidth' is just that - it is custom. You cannot retrieve it unless you've set it. I needed the width of the item - had to scwag/calculate it myself. Keep in mind that many charts can be panned/zoomed, so once you go down this path, then you need to recalculate it and set it for the chart prepaint events.
Here is a crude little function I made (is more verbose than needed - for educational purposes and has no error handling :)):
private int CalculateChartPixelPointWidth(Chart chart, ChartArea chartArea, Series series)
{
// Get right side - takes some goofy stuff - as the pixel location isn't available
var areaRightX = Math.Round(GetChartAreaRightPositionX(chart, chartArea));
var xValue = series.Points[0].XValue;
var xPixelValue = chartArea.AxisX.ValueToPixelPosition(xValue);
var seriesLeftX = chart.Location.X + xPixelValue;
var viewPointWidth = Math.Round((areaRightX - seriesLeftX - (series.Points.Count * 2)) / series.Points.Count, 0);
return Convert.ToInt32(viewPointWidth);
}
And this as well:
private double GetChartAreaRightPositionX(Chart chart, ChartArea area)
{
var xLoc = chart.Location.X;
return xLoc + (area.Position.Width + area.Position.X) / 100 * chart.Size.Width;
}
The reason I'm calculating this is because I need to draw some graphical overlays on top of the normal chart item objects (my own rendering for my own purposes).
In the 'prepaint' event for the chart, I need to calculate the 'PixelPointWidth' that matches the current chart view (might be panned/zoomed). I then use that value to SET the chart custom property to match . . . such that the normal chart entities and MINE are correctly aligned/scaled (ensures we're in exactly the right 'x' axis position):
In my prepaint event, I do the following - just prior to drawing my graphical entities:
// Pretty close scwag . . .
var viewPointWidth = CalculateChartPixelPointWidth(e.Chart, e.Chart.ChartAreas[0], e.Chart.Series[0]);
// Set the custom property and use the same point width for my own entities . .
chart1.Series[0].SetCustomProperty("PixelPointWidth", viewPointWidth.ToString("D"));
// . . . now draw my entities below . . .
In my application I online process images (1920x400) with frame rate up to 350 fps. From these images I calculate continuously the area of black object situated in the middle of the picture. The object has often few white holes.
Sample image 1:
Sample image 2:
For calculation of area I'm currently using the emguCV function CvInvoke.CountNonZero(Mat) on the thresholded (B/W) image. This solution runs pretty OK, but my problem are the holes, which could mispresent the results rapidly.
I tried to use morphological erosion in order to fill the holes
if (erodeEnable)
{
Mat kernel = new Mat(5, 5, DepthType.Cv8U, 1);
kernel.SetTo(new MCvScalar(1));
CvInvoke.Erode(postProcessTempMat, postProcessTempMat, new Mat(5, 5, DepthType.Cv8U, 1), new System.Drawing.Point(0,0), erodeIterations, BorderType.Default, new MCvScalar(0, 0, 0));
}
but this operation deforms the object shape dramatically. I want to fill the holes, but keep the outer shape if poss unchanged.
I also tried to use my own method, which column by column searches first upper and lower black pixel and from the positions of these pixels calculate the area of the outer shape.
public int getBlackArea(Mat matImage)
{
Image<Gray, Byte> tempImg = matImage.ToImage<Gray, Byte>();
int result = 0;
int coll = 0;
int upperBorder, lowerBorder;
const int avFactor = 3;
result = 0;
for (coll = 0; coll < tempImg.Width; coll=coll+avFactor)
{
lowerBorder = 0;
while ((tempImg.Data[lowerBorder,coll, 0] > 0) && (lowerBorder < tempImg.Height))
{
lowerBorder++;
}
upperBorder = iMAGEhEIGHT - 1;
while ((tempImg.Data[upperBorder,coll, 0] > 0) && (upperBorder > 0))
{
upperBorder--;
}
result += ((upperBorder - lowerBorder) * avFactor);
//tempImg.Data[(upperBorder-5), coll, 0] = 120; // draw the outline for check of function
//tempImg.Data[(lowerBorder+5), coll, 0] = 120; // draw the outline for check of function
}
//tempImg.Save(#"C:\\pics\INFOoutlined.jpg");
return result;
}
This method returns very good results, but it is too time-demanding.
Don't you have any idea, how to improve my function in order to speed it up? Or don't you have any other idea, how could I reach the results?
Many thanks in advance :)
It all depends on what morphological operation you choose to perform.
I first binarized the images by selecting an optimal threshold.
Then I performed morphological open. Unlike stated by yvs, morphological close does not change the image at all. Since the holes to be filled are surrounded by black pixels morphological open solves the problem.
If however, the holes were surrounded by white pixels, morphological close would do the trick.
OUTPUT
Image 1:
Threshold:
Morphological Open:
Image 2:
Threshold:
Morphological Open:
As you can see, in both the cases the holes have been filled.
In my C# WinForms application I have a picturebox that hosts 2 curves (Resulted from a voltage/current measurement). The X axis is voltage and Y axis is current. The voltage axis is ranged from -5 to 5 but the current axis is a much smaller scale ranged from -10 uA to 10 uA. The task is to see if the second curve is within 10% of the first curve.
For visual inspection I am trying to draw an envelope around the first curve (Blue one). The curve is just a PointF array. At the moment since I have no idea how to draw a correct envelope around the blue curve, I just draw two other curves that are result of X points of the actual curve added and subtracted by 10% of the original curve. Of course this is a bad approach, but atleast for the section of the curve that is noticably vertical, it works. But as soon as the curve is on its non vertical section, this trick does not work anymore, as you can see in the picture below:
Here is the code that I am using to draw the envelope:
public Bitmap DrawEnvelope(double[,] pinData, float vLimit, float iLimit)
{
g = Graphics.FromImage(box);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
PointF[] u = new PointF[pinData.GetLength(0)]; //Up line
PointF[] d = new PointF[pinData.GetLength(0)]; //Down Line
List<PointF> joinedCurves = new List<PointF>();
float posX = xMaxValue * (vLimit / 100);
float minX = posX * -1;
for (int i = 0; i < pinData.GetLength(0); i++)
{
u[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + minX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
}
for (int i = 0; i < pinData.GetLength(0); i++)
{
d[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + posX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
}
Pen pengraph = new Pen(Color.FromArgb(50, 0 ,0 ,200), 1F);
pengraph.Alignment = PenAlignment.Center;
joinedCurves.AddRange(u);
joinedCurves.AddRange(d.Reverse());
PointF[] fillPoints = joinedCurves.ToArray();
SolidBrush fillBrush = new SolidBrush(Color.FromArgb(40, 0, 0, 250));
FillMode newFillMode = FillMode.Alternate;
g.FillClosedCurve(fillBrush, fillPoints, newFillMode, 0);
g.Dispose();
return box;
}
The green circles are added by myself, and they indicate the region that the second curve (Red one) is potentially has a difference bigger than 10% from the orginal curve.
Would be nice if someone put me in the right way, what should I look to to achive a nice envelope around original curve?
UPDATE
Because I am so noob I cant find a way to implement the answers given to this question until now, So put a bounty to see if somone can kindly show me atleast a coding approach to this problem.
You could try finding the gradient between each pair of points and calculating two points either side that are on the orthogonal that passes through the midpoint.
You would then have two more lines defined as a set of points that you could use to draw the envelope.
Your best bet is to iterate your point array and to calculate a perpendicular vector to two consecutive points each time (see Calculating a 2D Vector's Cross Product for implementation clues). Project in either direction along these perpendicular vectors to generate the two point arrays of your envelope.
This function generates them roughly using segment midpoints (as long as the point count is high and your offset is not too small it should look ok when plotted):
private void GetEnvelope(PointF[] curve, out PointF[] left, out PointF[] right, float offset)
{
left = new PointF[curve.Length - 1];
right = new PointF[curve.Length - 1];
for (int i = 1; i < curve.Length; i++)
{
PointF normal = new PointF(curve[i].Y - curve[i - 1].Y, curve[i - 1].X - curve[i].X);
float length = (float)Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y);
normal.X /= length;
normal.Y /= length;
PointF midpoint = new PointF((curve[i - 1].X + curve[i].X) / 2F, (curve[i - 1].Y + curve[i].Y) / 2F);
left[i - 1] = new PointF(midpoint.X - (normal.X * offset), midpoint.Y - (normal.Y * offset));
right[i - 1] = new PointF(midpoint.X + (normal.X * offset), midpoint.Y + (normal.Y * offset));
}
}
It all depends on the way you want the envelop to be sized.
You could calculate/guestimate the slope of the curve in each point by calculating the slope to the next point and the slope to the previous point, average these and then calculate a perpendicular vector to the slope.
Add this vector to the point of the curve; this gives you the right-hand edge of the envelop.
Subtract this vector from the point of the curve; this gives you the left-hand edge of the envelop.
This method will fail if the points are too far apart or very sudden changes in the points appear.
This is probably a dumb suggestion. Perhaps instead of drawing the envelope yourself, maybe you could let winforms do it for you. Try drawing the envelope as a line with a pen that has a larger width. Perhaps it might work.
If you look at this msdn example on varying the pen width, you might see what I mean.
http://msdn.microsoft.com/en-us/library/3bssbs7z.aspx
2 (probably incorrect) possibilities.
Do what you did originally to get the pale blue wide area, but also do it in the vertical direction (not just the horizontal)
Do what Dan suggested with a REALLY thick line (in pale blue) then draw it again, then draw the original (thin) line on top of it.
I'm working with ImageSharp to do some basic image editing in my UWP app, and one of the things I need to do is to crop the image to a circle (you can assume the image is already a square).
I couldn't find a Clip API that worked with anything else that rectangles, so I came up with the following snippet:
// Image is an Image<Argb32> instance
image.Mutate(context =>
{
context.Apply(target =>
{
double half = target.Height / 2.0;
unsafe
{
fixed (Argb32* p = target.GetPixelSpan())
for (int i = 0; i < target.Height; i++)
for (int j = 0; j < target.Width; j++)
if (Math.Sqrt((half - i).Square() + (half - j).Square()) > half)
p[i * target.Width + j] = default;
}
});
});
NOTE: that Square method is just an extension that takes a double and returns its squared value.
Now, this works fine, and it's reasonably fast as I'm working with small enough images (say, <= 250 pixels for each axis). This snippet simply sets each pixel that falls outside of the circle with radius height / 2, centered at the center of the image, to a transparent pixel.
I wonder though if there wasn't another more intuitive method to do the same thing, that I just missed.
Thank you for your help!