Updating vertices on separate thread in Unity - c#

I have a fluids simulation running in Unity, calculating a heightfield for the fluid. I then have a mesh/vertex grid for which I set the height. I'm now trying to make it threaded. I only update the fluids every several frames (this is also threaded and works perfectly so far.) I also only update the mesh when the simulation got updated.
I have the following (non-threaded) code that works as it should:
public override void ApplyVisuals(float[][] lowerLayersHeight) {
UpdateVisuals(1, N, lowerLayersHeight, _height, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors);
_mesh.vertices = _tempVertices;
_mesh.colors32 = _tempColors;
}
So I update the visuals with some parameters, and then store the new vertices and new colors for the mesh in _tempVertices en _tempColors respectively. Though I tried to run this code on a separate thread:
public override void ApplyVisuals(float[][] lowerLayersHeight) {
if (_mainVisualsThread != null && _mainVisualsThread.IsAlive) {
_mainVisualsThread.Join();
}
Helpers.Swap(ref _vertices, ref _tempVertices); //swap the onces calculated by the _mainVisualsThread (temp) with the current array
Helpers.Swap(ref _colors, ref _tempColors);
_mesh.vertices = _tempVertices; //set the current as visual, the temps will be updated with the new vertices for next update
_mesh.colors32 = _tempColors;
_mainVisualsThread = new Thread(()=>
UpdateVisuals(1, N, lowerLayersHeight, _height, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors)
);
_mainVisualsThread.Start();
}
So instead of updating the visuals this call, I let a separate thread calculate the visuals for the next time ApplyVisuals and apply the vertices and colors calculated by the previous call to ApplyVisuals. This of course makes the visuals lag behind one update, but that's an acceptable trade-of. Though for some reason this doesn't work. My mesh starts to flicker, one frame being the proper one, the next nothing. The picture shows the bad frame first, and then the good one.
The weirdness doesn't stop there however. In my first non-threaded solution, the simulation works perfectly. Though when I use threads the visualisation somehow fucks it up. Even though I don't alter any variables used in the simulation. The only thing I change is my buffered vertex and colors array, but those aren't used in the simulation.
The UpdateVisuals method I use:
static void UpdateVisuals(int xFrom, int xTo,
float[][] lowerLayersHeight,
float[][] heightField,
float opaqueHeight,
float nonZeroHeightOffset,
Vector3[] vertices, Color32[] colors
) {
// Set the heights of the vertices of the mesh and apply colors
int x, y, index;
float height, relHeight;
for (x = xFrom; x <= xTo; ++x) {
for (y = 0; y < N+2; ++y) {
index = CalculateIndex(x,y);
height = heightField[x][y];
relHeight = height / opaqueHeight;
vertices[index].y = height + lowerLayersHeight[x][y]
+ (height > 0 ? nonZeroHeightOffset : 0);
colors[index].a = (byte)Mathf.Lerp(0, 200, relHeight);
}
}
}
In my simulation I never alter the _height array, what I do is I alter a _tempHeight array, and then swap them when the update is done. I do however read from the _height array.
Some more things I tried:
Another ApplyVisuals method:
_mainVisualsThread = new Thread(()=>
UpdateVisuals(1, N, lowerLayersHeight, _height, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors)
);
_mainVisualsThread.Start();
_mainVisualsThread.Join();
_mesh.vertices = _tempVertices;
_mesh.colors32 = _tempColors;
This also works as it should. So there appears to go something wrong when actually doing other stuff while the visualsThread is working.
I also tried to copy both the _height and lowerLayersHeight with the copy from this question: link Like this:
var bufferedHeight = Helpers.CopyArrayBuiltIn(_height);
var bufferedLowerLayersHeight = Helpers.CopyArrayBuiltIn(lowerLayersHeight);
_mainVisualsThread = new Thread(()=>
UpdateVisuals(1, N, bufferedLowerLayersHeight, bufferedHeight, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors)
);
This didn't help, and both the simulation and visualisation go wrong again :(
And then I was out of ideas. I'm pretty new to threading, so I'm hoping there's somthing simple I'm missing. But I can't figure out what.
Here's my simulation update code:
public override void DoUpdate(float dt, float dx, float[][] lowerLayersHeight) {
// Wait for update to be done
if (_mainUpdateThread != null && _mainUpdateThread.IsAlive) {
_mainUpdateThread.Join();
}
Helpers.Swap(ref _tempHeight, ref _height);
// Start the next update already on different threads. These will then be swapped next time an update is wanted.
_mainUpdateThread = new Thread(()=> {
// Height
RunPartsThreaded(_threadCount, (int from, int to)=>UpdateHeight(from, to, dt, dx, _tempFlux, _height, _tempHeight));
});
_mainUpdateThread.Start();
}
static void UpdateHeight(int xFrom, int xTo, float dt, float dx,
OutflowFlux[][] tempFlux,
float[][] height,
float[][] tempHeight
) {
int x, y;
float dV;
for (x=xFrom ; x <= xTo ; x++ ) {
for (y=1 ; y <= N ; y++ ) {
//
// 3.2.2 Water Surface (and Velocity Field Update)
// ----------------------------------------------------------------------------------------
dV = dt * (
//sum in
tempFlux[x-1][y].right + tempFlux[x][y-1].top + tempFlux[x+1][y].left + tempFlux[x][y+1].bottom
//minus sum out
- tempFlux[x][y].right - tempFlux[x][y].top - tempFlux[x][y].left - tempFlux[x][y].bottom
); //(6)
tempHeight[x][y] = height[x][y] + dV / (dx*dx); //(7)
//swap temp and the real one later
}
}
}
The code gets called every _dt seconds (usually 0.02-0.05) like this:
_timeSinceLastUpdate += Time.deltaTime;
if (_timeSinceLastUpdate >= _dt) {
_timeSinceLastUpdate -= _dt;
//
// Update each layer
// ----------------------------------------------------------------------
ResetTotalHeight(); //The first layer just sits on a plane
for (int i = 0; i < _layers.Count; ++i) {
_layers[i].DoUpdate(_dt, _dx, _tempTotalHeight);
_layers[i].ApplyVisuals(_tempTotalHeight);
AddHeightToTotal(_layers[i].HeightField);
}
}
There are multiple layers, though only one uses the fluid simulation code, the others are static. The AddHeightToTotal is just a method to keep track of the total height of all previous layers, that's the lowerLayersHeight parameter my simulation methods get.
Lastly, the RunPartsThreaded method I use:
static void RunPartsThreaded(int threadCount, Action<int, int> partUpdateWrapper) {
int extraThreadCount = threadCount - 1;
Thread[] _threads = new Thread[extraThreadCount];
int step = N / threadCount;
int i;
for (i = 0; i < extraThreadCount; ++i) {
int index = i; //https://stackoverflow.com/questions/7352757/threadstart-in-a-loop-in-a-timer-only-executing-last-thread-in-said-loop
_threads[index] = new Thread(()=>partUpdateWrapper(step * index + 1, step * (index+1)));
_threads[index].Start();
}
partUpdateWrapper( step * extraThreadCount + 1, N );
for (i = 0; i < extraThreadCount; ++i) {
_threads[i].Join();
}
}
The simulation can be run on only strips of my grid, and I make multiple threads each updating a different part of the whole grid.

Preamble
I'm not very optimistic about your multi-threading design overall... IMO, it would be nice at least to have threads reused.
Problem
Ok, take a look on this:
public override void DoUpdate(float dt, float dx, float[][] lowerLayersHeight) {
....
Helpers.Swap(ref _tempHeight, ref _height);
Ooops! UpdateVisuals thread can be runnning right now (and it's highly likely that it is), which uses _height array.

Related

Custom virtualizing panel freezes UI thread when user scrolls

I found a VirtualizingWrapPanel(VWP) project here which, I thought, could help me with optimizing my ListView scrolling performance. The listView have to has four columns and multiple rows to display the source items. So I tried to use this VWP, but scrolling(I made it as DoubleAnimation) smoothness still sucks, the framerate drops down to about 30 fps and it's very noticeable. I copied the source code from the link above to try to debug it and understand what is the reason of such a bad performance.
After several hours a found out that ArrangeOverride method of VWP and MeasureOverride method of its "inheritance parent" can execute about 20-30ms (1000ms / 30ms = ~30fps) and that's it, I found the reason why it's so slow... but why?
MeasureOverride method calls two potentially heavy methods: RealizeItems and VirtualizeItems
protected virtual void RealizeItems()
{
var startPosition = ItemContainerGenerator.GeneratorPositionFromIndex(ItemRange.StartIndex);
int childIndex = startPosition.Offset == 0 ? startPosition.Index : startPosition.Index + 1;
using (ItemContainerGenerator.StartAt(startPosition, GeneratorDirection.Forward, true))
{
for (int i = ItemRange.StartIndex; i <= ItemRange.EndIndex; i++, childIndex++)
{
UIElement child = (UIElement)ItemContainerGenerator.GenerateNext(out bool isNewlyRealized);
if (isNewlyRealized || /*recycled*/!InternalChildren.Contains(child))
{
if (childIndex >= InternalChildren.Count)
{
AddInternalChild(child);
}
else
{
InsertInternalChild(childIndex, child);
}
ItemContainerGenerator.PrepareItemContainer(child);
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
}
if (child is IHierarchicalVirtualizationAndScrollInfo groupItem)
{
groupItem.Constraints = new HierarchicalVirtualizationConstraints(
new VirtualizationCacheLength(0),
VirtualizationCacheLengthUnit.Item,
new Rect(0, 0, ViewportWidth, ViewportHeight));
child.Measure(new Size(ViewportWidth, ViewportHeight));
}
}
}
}
protected virtual void VirtualizeItems()
{
for (int childIndex = InternalChildren.Count - 1; childIndex >= 0; childIndex--)
{
var generatorPosition = GetGeneratorPositionFromChildIndex(childIndex);
int itemIndex = ItemContainerGenerator.IndexFromGeneratorPosition(generatorPosition);
if (!ItemRange.Contains(itemIndex))
{
if (VirtualizationMode == VirtualizationMode.Recycling)
{
ItemContainerGenerator.Recycle(generatorPosition, 1);
}
else
{
ItemContainerGenerator.Remove(generatorPosition, 1);
}
RemoveInternalChildRange(childIndex, 1);
}
}
}
So I understand that developers could make a mistake in code, I also understand that there may not be a single thing that can be optimized, but I really cannot understand why it is so slow.. The loops iterates throught about 40-50 UIElement objects, every of which has two child objects(TextBlock and ImageSource) and that's not such a huge number of objects..
I want to use parallel tasks in these loops, but UIElement and others is not thread-safe and can only be accessed in UI Thread... or can?
How can I increase performance of these methods? Can there be implemented efficient multithreading?
Thanks!
I found this method, which calls InvalidateMeasure() every time vertical scroll offset changes. Because of DoubleAnimation I use to animate vertical scroll offset this is code really often called, but if there is no one thing to change measure and arrange methods executes for about 1-2ms in total, else - a lot slowlier.
public void SetVerticalOffset(double offset)
{
if (offset < 0 || Viewport.Height >= Extent.Height)
{
offset = 0;
}
else if (offset + Viewport.Height >= Extent.Height)
{
offset = Extent.Height - Viewport.Height;
}
Offset = new Point(Offset.X, offset);
ScrollOwner?.InvalidateScrollInfo();
InvalidateMeasure();
}
UPDATE: I had opened Profiler and found out, that a lot of time(7-10 and more ms) is taken by layout operations.
UPDATE 2: The code bellow offsets(arranges) all the visible items
protected override Size ArrangeOverride(Size finalSize)
{
double offsetX = GetX(Offset);
double offsetY = GetY(Offset);
/* When the items owner is a group item offset is handled by the parent panel. */
if (ItemsOwner is IHierarchicalVirtualizationAndScrollInfo groupItem)
{
offsetY = 0;
}
Size childSize = CalculateChildArrangeSize(finalSize);
CalculateSpacing(finalSize, out double innerSpacing, out double outerSpacing);
for (int childIndex = 0; childIndex < InternalChildren.Count; childIndex++)
{
UIElement child = InternalChildren[childIndex];
int itemIndex = GetItemIndexFromChildIndex(childIndex);
int columnIndex = itemIndex % itemsPerRowCount;
int rowIndex = itemIndex / itemsPerRowCount;
double x = outerSpacing + columnIndex * (GetWidth(childSize) + innerSpacing);
double y = rowIndex * GetHeight(childSize);
if (GetHeight(finalSize) == 0.0)
{
/* When the parent panel is grouping and a cached group item is not
* in the viewport it has no valid arrangement. That means that the
* height/width is 0. Therefore the items should not be visible so
* that they are not falsely displayed. */
child.Arrange(new Rect(0, 0, 0, 0));
}
else
{
child.Arrange(CreateRect(x - offsetX, y - offsetY, childSize.Width, childSize.Height));
}
}
return finalSize;
}

Zoomable, printable, scrollable train movement graphs in c#

I need to build a graphic train schedule visualisation tool in C#. Actually I have to rebuild this perfect tool in C#.
Marey's Trains
The graphs have to be zoomable, scrollable and printable/exportable to PDF with vector graphical elements.
Could you give me some tips? How should I start it? What sort of libraries should I use?
Is it worth to try using graphing libraries like OxyPlot? Maybe it's not the best because of the special axes and irregular grids - as I think. What's your opinion?
No matter which chart tool you use, once you need special types of display you will always have to add some extra coding.
Here is an example of using the MSChart control.
Do note that it has a limitation wrt to exporting vector formats:
It can export to various formats, including 3 EMF types; however only some application can actually use those. Not sure about the PDF libary you use..!
If you can't use the Emf formats you can get nice results by making the Chart control really big, exporting to Png and then making the Dpi resolution much larger the the default screen resolution it has after saving.. Setting it to 600 or 1200dpi should do for most pdf uses..
Now lets look at an example:
A few notes:
I have made my life easier in a number of ways. I have only coded for one direction and I have not reversed the rooster, so it goes only bottom to top.
I have not used real data but made them up.
I have not created one or more classes to hold the station data; instead I use a very simple Tuple.
I have not created a DataTable to hold the train data. Instead I make them up and add them to the chart on the fly..
I didn't test, but zooming and scrolling should work as well..
Here is the List<Tuple> that holds my station data:
// station name, distance, type: 0=terminal, 1=normal, 2=main station
List<Tuple<string, double, int>> TrainStops = null;
Here is how I set up the chart:
Setup24HoursAxis(chart1, DateTime.Today);
TrainStops = SetupTrainStops(17);
SetupTrainStopAxis(chart1);
for (int i = 0; i < 23 * 3; i++)
{
AddTrainStopSeries(chart1, DateTime.Today.Date.AddMinutes(i * 20),
17 - rnd.Next(4), i% 5 == 0 ? 1 : 0);
}
// this exports the image above:
chart1.SaveImage("D:\\trains.png", ChartImageFormat.Png);
This creates one train every 20 minutes with 14-17 stops and every 5th train a fast one.
Here are the routines I call:
Setting up the x-axis for hold one day's worth of data is straightforward.
public static void Setup24HoursAxis(Chart chart, DateTime dt)
{
chart.Legends[0].Enabled = false;
Axis ax = chart.ChartAreas[0].AxisX;
ax.IntervalType = DateTimeIntervalType.Hours;
ax.Interval = 1;
ax.Minimum = dt.ToOADate();
ax.Maximum = (dt.AddHours(24)).ToOADate();
ax.LabelStyle.Format = "H:mm";
}
Creating a List of stations with random distances is also very simple. I made the 1st and last ones terminals and every 5th a main station.
public List<Tuple<string, double, int>> SetupTrainStops(int count)
{
var stops = new List<Tuple<string, double, int>>();
Random rnd = new Random(count);
for (int i = 0; i < count; i++)
{
string n = (char)(i+(byte)'A') + "-Street";
double d = 1 + rnd.Next(3) + rnd.Next(4) + rnd.Next(5) / 10d;
if (d < 3) d = 3; // a minimum distance so the label won't touch
int t = (i == 0 | i == count-1) ? 0 : rnd.Next(5)==0 ? 2 : 1;
var ts = new Tuple<string, double, int>(n, d, t);
stops.Add(ts);
}
return stops;
}
Now that we have the train stops we can set up the y-axis:
public void SetupTrainStopAxis(Chart chart)
{
Axis ay = chart.ChartAreas[0].AxisY;
ay.LabelStyle.Font = new Font("Consolas", 8f);
double totalDist = 0;
for (int i = 0; i < TrainStops.Count; i++)
{
CustomLabel cl = new CustomLabel();
cl.Text = TrainStops[i].Item1;
cl.FromPosition = totalDist - 0.1d;
cl.ToPosition = totalDist + 0.1d;
totalDist += TrainStops[i].Item2;
cl.ForeColor = TrainStops[i].Item3 == 1 ? Color.DimGray : Color.Black;
ay.CustomLabels.Add(cl);
}
ay.Minimum = 0;
ay.Maximum = totalDist;
ay.MajorGrid.Enabled = false;
ay.MajorTickMark.Enabled = false;
}
A few notes are called for here:
As the values are quite dynamic we can't use normal Labels which would come with the fixed Interval spacing.
So we create CustomLabels instead.
For these we need two values to determine the space into which they shall be centered. So we create a small span by adding/subtracting 0.1d.
We have calculated the total distance and use it to set up the Maximum of the y-axis. Again: To mimick the schedule you show you will have to do some reversing here and there..
By adding CustomLabels the normal ones are turned off automatically. As we need MajorGridlines at the irregular intervals we also turn the normal ones off. Hence we must draw them ourselves. Not really hard as you can see..:
For this we code one of the xxxPaint events:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ay = chart1.ChartAreas[0].AxisY;
Axis ax = chart1.ChartAreas[0].AxisX;
int x0 = (int) ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int) ax.ValueToPixelPosition(ax.Maximum);
double totalDist = 0;
foreach (var ts in TrainStops)
{
int y = (int)ay.ValueToPixelPosition(totalDist);
totalDist += ts.Item2;
using (Pen p = new Pen(ts.Item3 == 1 ? Color.DarkGray : Color.Black,
ts.Item3 == 1 ? 0.5f : 1f))
e.ChartGraphics.Graphics.DrawLine(p, x0 + 1, y, x1, y);
}
// ** Insert marker drawing code (from update below) here !
}
Note the use of the ValueToPixelPosition conversion functions of the axes!
Now for the last part: How to add a Series of train data..:
public void AddTrainStopSeries(Chart chart, DateTime start, int count, int speed)
{
Series s = chart.Series.Add(start.ToShortTimeString());
s.ChartType = SeriesChartType.Line;
s.Color = speed == 0 ? Color.Black : Color.Brown;
s.MarkerStyle = MarkerStyle.Circle;
s.MarkerSize = 4;
double totalDist = 0;
DateTime ct = start;
for (int i = 0; i < count; i++)
{
var ts = TrainStops[i];
ct = ct.AddMinutes(ts.Item2 * (speed == 0 ? 1 : 1.1d));
DataPoint dp = new DataPoint( ct.ToOADate(), totalDist );
totalDist += TrainStops[i].Item2;
s.Points.Add(dp);
}
}
Note that since my data don't contain real arrival/departure times I calculated them from the distance and some speed factor. You, of course, would use your data!
Also note that I have used a Line chart with extra Marker circles.
Also note that each train series can easily be disabled/hidden or brought back again.
Let's show only the fast trains:
private void cbx_ShowOnlyFastTrains_CheckedChanged(object sender, EventArgs e)
{
foreach (Series s in chart1.Series)
s.Enabled = !cbx_ShowOnlyFastTrains.Checked || s.Color == Color.Brown;
}
Of course for a robust application you will not rely ona magic color ;-)
Instead you could add a Tag object to the Series to hold all sorts of train info.
Update: As you noticed the drawn GridLines cover the Markers. You can insert this piece of code here (**); it will owner-draw the Markers at the end of the xxxPaint event.
int w = chart1.Series[0].MarkerSize;
foreach(Series s in chart1.Series)
foreach(DataPoint dp in s.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue) - w / 2;
int y = (int) ay.ValueToPixelPosition(dp.YValues[0])- w / 2;
using (SolidBrush b = new SolidBrush(dp.Color))
e.ChartGraphics.Graphics.FillEllipse(b, x, y, w, w);
}
Close-up:

Assigning value to an array item, Game of life

Im new to c# and trying to code Conways Game of Life, but I have this problem. I have a class Gameboard:
public class GameBoard
{
int height = 32;
int width = 32;
public void Create()
{
for (int x = 1; x < width; x++)
{
for (int y = 1; y < height; y++)
{
Tile[,] tile = new Tile[1024, 2];
tile[x, y]= tile[,].value; // not working
Console.Write( "");
}
Console.WriteLine("");
}
}
}
and Tile class:
public class Tile
{
public int value = 0;
}
I want to be able to assign a value to every tile in Create method and I need that value to be from Tile class, I also have some methods changing the value in Tile class so I need a reference to it. My plan is to set all the values of tile[x,y] to zeros and then change accordingly to the rules to ones. How do I assign the Tile value propertz to tile[x,y] array item?
A couple things to note:
The way it is now, the Tile array is initialized (setting = new Tile[1024, 2];) every time you iterate through the inner loop. This will remove any value you've stored in it. Your Tile array should probably be a field of Gameboard since you'll most likely want to access this outside of the class. What this means is that you'll need to move the declaration of your Tile array to underneath the height and width values.
You'll also want to check the size that you're setting the array to. It seems that your board should be the dimensions set by height and width. So when you initialize the array, you'd want to use width and height.
Another change you might consider is creating a constructor and initializing everything there. A constructor acts similarly to a method in the sense that you have parameters and can execute code within the body. For the most part, the parameters are just used to initialize the fields within the class. In your case, this would allow you to easily create GameBoards of different sizes.
I'm a little confused as to why you are writing lines in your foreach. Debugging?
The iterators on your for-loops (x and y) should start at 0. If you declare an array of size 32 then it will have indexes from 0 to 31. So if your array was named tiles and was initialized Tile[] tiles = new Tile[32]; You could access values tiles[0] to tiles[31].
Here are the changes I mentioned above.
public class GameBoard
{
private int _height;
private int _width;
public Tile[,] Tiles; // Tile array is now a field
public GameBoard(int height, int width)
{
_height = height;
_width = width;
Tiles = new Tile[width, height];
}
// I'm fairly certain the default value for c# of an integer is 0
// so you may not need the following.
public void SetGameBoardValues()
{
Random rand = new Random(); //only add if you want to randomly generate the board
for (int x = 0; x < width; x++)//arrays start at 0
{
for (int y = 0; y < height; y++)//arrays start at 0
{
Tiles[x, y] = 0;
// If you'd like to randomly assign the value you can do:
Tile[x,y] = rand.Next(0,2)
}
}
}
}
You can now access this from a different class through this manner:
public class Main
{
public static int main(string [] args) //if you're using the console
{
GameBoard gameBoard = new GameBoard(32, 32); // gameboard is size 32x32
gameBoard.SetGameBoardValues();
gameBoard.Tiles[0, 0] = 1; //You can access values this way.
}
}

ZedGraph RealTime Data Plotting C#

public void DrawingPulseData(byte[] data)
{
// Make sure that the curvelist has at least one curve
if (PulseControl.GraphPane.CurveList.Count <= 0)
return;
// Get the first CurveItem in the graph
LineItem curve = PulseControl.GraphPane.CurveList[0] as LineItem;
if (curve == null)
return;
// Get the PointPairList
IPointListEdit list = curve.Points as IPointListEdit;
// If this is null, it means the reference at curve.Points does not
// support IPointListEdit, so we won't be able to modify it
if (list == null)
return;
double time = (Environment.TickCount - tickStart) / 1000.0;
for (int i = 0; i < count; i++)
{
list.Add(time, (double)data[i]);
}
Scale xScale = PulseControl.GraphPane.XAxis.Scale;
if (time > xScale.Max - xScale.MajorStep)
{
xScale.Max = time + xScale.MajorStep;
xScale.Min = xScale.Max - 30.0;
}
// Make sure the Y axis is rescaled to accommodate actual data
PulseControl.AxisChange();
// Force a redraw
PulseControl.Invalidate();
count = 0;
}
Hi. I am using this method to draw real time data in zedgraph. count is length of incoming serial port data. This code works fine in timer(20ms) and draws data at each tick. However if i move this method into a class it does not work correctly. It draws too fast and wrong data.
public static void DrawingPulseData(byte[] data,ZedGraphControl zgc,int count, int TickStart)
I changed parameters like this after moving it into class. I changed PulseControl as zgc, tickstart as TickStart. I could not understand why it is not work same as the first code.
At the first picture,using code provided by #discomurray, i wrote this code statement out of the if's scopes. It gives me data like this.
Scale xScale = zgc.GraphPane.XAxis.Scale;
xScale.Max = now;
xScale.Min = now - 30.0;
If i write the same code statement into if's scopes data looks like picture above. It is a 10 seconds record. I don't have such a data with my method.
I assume that tickCount is the start time of the data buffer.
When adding the data to the list you need to change the x value (time) for each point in the list.
public static void DrawPulseData(byte[] data, ZedGraphControl zgc, int count, int tickStart)
{
double now = Environment.TickCount / 1000.0;
if (count != 0)
{
double span = (now - tickStart);
double inverseRate = span / count;
for (int i = 0; i < count; i++)
{
list.add(tickStart + ((i+1) * inverseRate), data[i]);
}
}
Scale xScale = zgc.GraphPane.XAxis.Scale;
xScale.Max = now;
xScale.Min = now - 30.0;
PulseControl.AxisChange();
PulseControl.Invalidate();
}
as for drawing to fast, it will only go as fast as you tell it go.

Algorithm for finding a painted region on a canvas

Update: I am attempting to pull a little clutter out of this post and sum it up more concisely. Please see the original edit if needed.
I am currently attempting to trace a series of single colored blobs on a Bitmap canvas.
e.g. An example of the bitmap I am attempting to trace would look like the following:
alt text http://www.refuctored.com/polygons.bmp
After successfully tracing the outlines of the 3 blobs on the image, I would have a class that held the color of a blob tied to a point list representing the outline of the blob (not all the pixels inside of the blobs).
The problem I am running into is logic in instances where a neighboring pixel has no surrounding pixels other than the previous pixel.
e.g The top example would trace fine, but the second would fail because the pixel has no where to go since the previous pixels have already been used.
alt text http://www.refuctored.com/error.jpg
I am tracing left-to-right, top-to-bottom, favoring diagonal angles over right angles. I must be able to redraw an exact copy of the region based off the data I extract, so the pixels in the list must be in the right order for the copy to work.
Thus far, my attempt has been riddled with failure, and a couple days of pulling my hair out trying to rewrite the algorithms a little different each time to solve the issue. Thus far I have been unsuccessful. Has anyone else had a similar issue like mine who has a good algorithm to find the edges?
One simple trick to avoiding these cul-de-sacs is to double the size of the image you want to trace using a nearest neighbor scaling algorithm before tracing it. Like that you will never get single strips.
The alternative is to use a marching squares algorithm - but it seems to still have one or two cases where it fails: http://www.sakri.net/blog/2009/05/28/detecting-edge-pixels-with-marching-squares-algorithm/
Have you looked at blob detection algorithms? For example, http://opencv.willowgarage.com/wiki/cvBlobsLib if you can integrate OpenCV into your application. Coupled with thresholding to create binary images for each color (or color range) in your image, you could easily find the blobs that are the same color. Repeat for each color in the image, and you have a list of blobs sorted by color.
If you cannot use OpenCV directly, perhaps the paper referenced by that library ("A linear-time component labeling algorithm using contour tracing technique", F.Chang et al.) would provide a good method for finding blobs.
Rather than using recursion, use a stack.
Pseudo-code:
Add initial pixel to polygon
Add initial pixel to stack
while(stack is not empty) {
pop pixel off the stack
foreach (neighbor n of popped pixel) {
if (n is close enough in color to initial pixel) {
Add n to polygon
Add n to stack
}
}
}
This will use a lot less memory than the same solution using recursion.
Just send your 'Image' to BuildPixelArray function and then call the FindRegions.
After that the 'colors' variable will be holding your colors list and pixel coordinates in every list member.
I've copied the source from one of my projects, there may be some undefined variables or syntax erors.
public class ImageProcessing{
private int[,] pixelArray;
private int imageWidth;
private int imageHeight;
List<MyColor> colors;
public void BuildPixelArray(ref Image myImage)
{
imageHeight = myImage.Height;
imageWidth = myImage.Width;
pixelArray = new int[imageWidth, imageHeight];
Rectangle rect = new Rectangle(0, 0, myImage.Width, myImage.Height);
Bitmap temp = new Bitmap(myImage);
BitmapData bmpData = temp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int remain = bmpData.Stride - bmpData.Width * 3;
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int j = 15; j < bmpData.Height; j++)
{
for (int i = 0; i < bmpData.Width; i++)
{
pixelArray[i, j] = ptr[0] + ptr[1] * 256 + ptr[2] * 256 * 256;
ptr += 3;
}
ptr += remain;
}
}
temp.UnlockBits(bmpData);
}
public void FindRegions()
{
colors = new List<MyColor>();
for (int i = 0; i < imageWidth; i++)
{
for (int j = 0; j < imageHeight; j++)
{
int tmpColorValue = pixelArray[i, j];
MyColor tmp = new MyColor(tmpColorValue);
if (colors.Contains(tmp))
{
MyColor tmpColor = (from p in colors
where p.colorValue == tmpColorValue
select p).First();
tmpColor.pointList.Add(new MyPoint(i, j));
}
else
{
tmp.pointList.Add(new MyPoint(i, j));
colors.Add(tmp);
}
}
}
}
}
public class MyColor : IEquatable<MyColor>
{
public int colorValue { get; set; }
public List<MyPoint> pointList = new List<MyPoint>();
public MyColor(int _colorValue)
{
colorValue = _colorValue;
}
public bool Equals(MyColor other)
{
if (this.colorValue == other.colorValue)
{
return true;
}
return false;
}
}
public class MyPoint
{
public int xCoord { get; set; }
public int yCoord { get; set; }
public MyPoint(int _xCoord, int _yCoord)
{
xCoord = _xCoord;
yCoord = _yCoord;
}
}
If you're getting a stack overflow I would guess that you're not excluding already-checked pixels. The first check on visiting a square should be whether you've been here before.
Also, I was working on a related problem not too long ago and I came up with a different approach that uses a lot less memory:
A queue:
AddPointToQueue(x, y);
repeat
x, y = HeadItem;
AddMaybe(x - 1, y); x + 1, y; x, y - 1; x, y + 1;
until QueueIsEmpty;
AddMaybe(x, y):
if Visited[x, y] return;
Visited[x, y] = true;
AddPointToQueue(x, y);
The point of this approach is that you end up with your queue basically holding a line wrapped around the mapped area. This limits memory usage better than a stack can.
If relevant it also can be trivially modified to yield the travel distance to any square.
Try using AForge.net. I would go for Filter by colors, Threshold and then you could do some Morphology to decrement the black/White zones to lose contact between the objects. Then you could go for the Blobs.

Categories

Resources