Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing an envelope around a curve

Tags:

c#

math

winforms

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:

enter image description here

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.

like image 490
Saeid Yazdani Avatar asked Jun 05 '12 11:06

Saeid Yazdani


4 Answers

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.

like image 167
Nick Butler Avatar answered Oct 22 '22 06:10

Nick Butler


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));
            }
        }
like image 39
zeFrenchy Avatar answered Oct 22 '22 08:10

zeFrenchy


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.

like image 26
Emond Avatar answered Oct 22 '22 07:10

Emond


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

like image 42
gorengpisang Avatar answered Oct 22 '22 07:10

gorengpisang