I have an array of coordinates that reflect known positions on an image. Let's call this the template image. It has a unique barcode and orientation markers (which are also in the coordinate array).
The image is printed, scanned and fed back into my application to be detected. During printing and scanning, the image could be transformed in three ways; translation, rotation and scale.
Assuming that I can find the orientation markers on the distorted image, how can I use matrix transformation to get the relative positions of the remaining coordinates?
I posted this question on SO before but made it too complicated to understand what I wanted.
EDIT
namespace MatrixTest
{
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
public static class Program
{
public static void Main ()
{
Template template = new Template(); // Original template image.
Document document = new Document(); // Printed and scanned distorted image.
template.CreateTemplateImage();
// The template image is printed and scanned. This method generates an example scan or this question.
document.CreateDistortedImageFromTemplateImage();
// Stuck here.
document.Transform();
// Draw transformed points on the image to verify that transformation is successful.
document.DrawPoints();
System.Diagnostics.Process.Start(new System.IO.FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).Directory.FullName);
}
}
public class Page
{
public Bitmap Image { get; set; }
public Point[] Markers = new Point[3]; // Orientation markers: 1=TopLeft, 2=TopRight, 3=BottomRight.
public Point[] Points = new Point[100]; // Coordinates to transform in the TemplateScanned derived class!
}
// This class represents the originalk template image.
public class Template: Page
{
public Template ()
{
this.Image = new Bitmap(300, 400);
// Known dimentions for marker rectangles.
this.Markers[0] = new Point(10, 10);
this.Markers[1] = new Point(this.Image.Width - 20 - 10, 10);
this.Markers[2] = new Point(this.Image.Width - 20 - 10, this.Image.Height - 20 - 10);
// Known points of interest. Consider them hardcoded.
int index = 0;
for (int y = 0; y < 10; y++)
for (int x = 0; x < 10; x++)
this.Points[index++] = new Point((this.Image.Width / 10) + (x * 20), (this.Image.Height / 10) + (y * 20));
}
public void CreateTemplateImage ()
{
using (Graphics graphics = Graphics.FromImage(this.Image))
{
graphics.Clear(Color.White);
for (int i = 0; i < this.Markers.Length; i++)
graphics.FillRectangle(Brushes.Black, this.Markers[i].X, this.Markers[i].Y, 20, 20);
for (int i = 0; i < this.Points.Length; i++)
graphics.DrawRectangle(Pens.Red, this.Points[i].X, this.Points[i].Y, 5, 5);
}
this.Image.Save("Document Original.png");
}
}
// This class represents the scanned image.
public class Document: Page
{
public struct StructTransformation
{
public float AngleOfRotation;
public SizeF ScaleRatio;
public SizeF TranslationOffset;
}
private Template Template = new Template();
private StructTransformation Transformation = new StructTransformation();
public Document ()
{
this.Template = new Template();
this.Transformation = new StructTransformation { AngleOfRotation = 5f, ScaleRatio = new SizeF(.8f, .7f), TranslationOffset = new SizeF(100f, 30f) };
this.Template.CreateTemplateImage();
// Copy points from template.
for (int i = 0; i < this.Template.Markers.Length; i++)
this.Markers[i] = this.Template.Markers[i];
for (int i = 0; i < this.Points.Length; i++)
this.Points[i] = this.Template.Points[i];
}
// Just distorts the original template image as if it had been read from a scanner.
public void CreateDistortedImageFromTemplateImage ()
{
// Distort coordinates.
Matrix matrix = new Matrix();
matrix.Rotate(this.Transformation.AngleOfRotation);
matrix.Scale(this.Transformation.ScaleRatio.Width, this.Transformation.ScaleRatio.Height);
matrix.Translate(this.Transformation.TranslationOffset.Width, this.Transformation.TranslationOffset.Height);
matrix.TransformPoints(this.Markers);
matrix.TransformPoints(this.Points);
// Distort and save image for visual reference.
this.Image = new Bitmap(this.Template.Image.Width, this.Template.Image.Height);
using (Graphics graphics = Graphics.FromImage(this.Image))
{
graphics.Clear(Color.White);
graphics.RotateTransform(this.Transformation.AngleOfRotation);
graphics.ScaleTransform(this.Transformation.ScaleRatio.Width, this.Transformation.ScaleRatio.Height);
graphics.TranslateTransform(this.Transformation.TranslationOffset.Width, this.Transformation.TranslationOffset.Height);
graphics.DrawImage(this.Template.Image, 0, 0);
}
this.Image.Save("Document Scanned.png");
}
public void Transform ()
{
// The rectangles of the ScannedDcoument are not known at this time. They would obviously be relative to the three orientation markers.
// I can't figure out how to use the following code properly i.e. using Matrix to apply all three transformations.
Matrix matrix = new Matrix();
matrix.Rotate(-this.Transformation.AngleOfRotation);
matrix.Scale(1f/this.Transformation.ScaleRatio.Width, 1f/this.Transformation.ScaleRatio.Height);
matrix.Translate(-this.Transformation.TranslationOffset.Width, -this.Transformation.TranslationOffset.Height);
matrix.TransformPoints(this.Markers);
matrix.TransformPoints(this.Points);
}
public void DrawPoints ()
{
using (Graphics graphics = Graphics.FromImage(this.Image))
{
graphics.Clear(Color.White);
for (int i = 0; i < this.Markers.Length; i++)
graphics.FillRectangle(Brushes.Blue, this.Markers[i].X, this.Markers[i].Y, 20, 20);
for (int i = 0; i < this.Points.Length; i++)
graphics.DrawRectangle(Pens.Purple, this.Points[i].X, this.Points[i].Y, 5, 5);
}
this.Image.Save("Document Fixed.png");
}
}
}
I'm assuming you want to transform the image to the unit square ( (0, 0) - (1.0, 1.0))
You need three points, one is the origin, the other will be transformed to the x-axis (1.0, 0) and the other to the y-axis (0, 1.0).
In the original coordinate system:
The origin is (Ox, Oy)
The X-axis is (X1, Y2)
The Y-Axis is (X2, Y2)
X-Axis relative to the origin (X1-Ox, Y1-Oy) will be shortened to (RX1, RY1)
Y-Axis relative to the origin (X2-ox, Y2-Oy) will be shortened to (RX2, RY2)
First we will shift the origin to (0,0) in homogeneous coordinates the transform matrix will be
(1 0 -Ox)
(0 1 -Oy)
(0 0 1)
The transform from the new space to the old one is represented by the following matrix:
(RX1 RX2 0)
(RY1 RY2 0)
( 0 0 1)
Because you want the inverse transformation, from old space to the new one, we need the invert this matrix:
Let's shorten (RX1*RY2-RX2*RY1) as D
(RY2/D -RX2/D 0)
(-RY1/D RX1/D 0)
( 0 0 1)
Now you can multiply both matrices first you do the translation and then use the second matrix to transform the basis.
Related
Using CvInvoke.Canny and CvInvoke.FindContours I'm trying to find the rectangle containing the item name (Schematic: Maple Desk). This rectangle is shown in the image below:
I'm able to find a lot of rectangles but I'm not able to get this one. Tried a lot of different thresholds for Canny but to no effect. The following image shows all rectangles I currently get:
Any ideas how to tackle this? Do I need to use other thresholds or another approach? I already experimented using grayscale and blurring but that didn't give better result. I added my current source below and the original image I'm using is this:
public Mat ProcessImage(Mat img)
{
UMat filter = new UMat();
UMat cannyEdges = new UMat();
Mat rectangleImage = new Mat(img.Size, DepthType.Cv8U, 3);
//Convert the image to grayscale and filter out the noise
//CvInvoke.CvtColor(img, filter, ColorConversion.Bgr2Gray);
//Remove noise
//CvInvoke.GaussianBlur(filter, filter, new System.Drawing.Size(3, 3), 1);
// Canny and edge detection
double cannyThreshold = 1.0; //180.0
double cannyThresholdLinking = 1.0; //120.0
//CvInvoke.Canny(filter, cannyEdges, cannyThreshold, cannyThresholdLinking);
CvInvoke.Canny(img, cannyEdges, cannyThreshold, cannyThresholdLinking);
// Find rectangles
List<RotatedRect> rectangleList = new List<RotatedRect>();
using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
{
CvInvoke.FindContours(cannyEdges, contours, null, RetrType.List, ChainApproxMethod.ChainApproxSimple);
int count = contours.Size;
for (int i = 0; i < count; i++)
{
using (VectorOfPoint contour = contours[i])
using (VectorOfPoint approxContour = new VectorOfPoint())
{
CvInvoke.ApproxPolyDP(contour, approxContour, CvInvoke.ArcLength(contour, true) * 0.05, true);
// Only consider contours with area greater than 250
if (CvInvoke.ContourArea(approxContour, false) > 250)
{
// The contour has 4 vertices.
if (approxContour.Size == 4)
{
// Determine if all the angles in the contour are within [80, 100] degree
bool isRectangle = true;
System.Drawing.Point[] pts = approxContour.ToArray();
LineSegment2D[] edges = Emgu.CV.PointCollection.PolyLine(pts, true);
for (int j = 0; j < edges.Length; j++)
{
double angle = Math.Abs(edges[(j + 1) % edges.Length].GetExteriorAngleDegree(edges[j]));
if (angle < 80 || angle > 100)
{
isRectangle = false;
break;
}
}
if (isRectangle) rectangleList.Add(CvInvoke.MinAreaRect(approxContour));
}
}
}
}
}
// Draw rectangles
foreach (RotatedRect rectangle in rectangleList)
{
CvInvoke.Polylines(rectangleImage, Array.ConvertAll(rectangle.GetVertices(), System.Drawing.Point.Round), true,
new Bgr(Color.DarkOrange).MCvScalar, 2);
}
//Drawing a light gray frame around the image
CvInvoke.Rectangle(rectangleImage,
new Rectangle(System.Drawing.Point.Empty,
new System.Drawing.Size(rectangleImage.Width - 1, rectangleImage.Height - 1)),
new MCvScalar(120, 120, 120));
//Draw the labels
CvInvoke.PutText(rectangleImage, "Rectangles", new System.Drawing.Point(20, 20),
FontFace.HersheyDuplex, 0.5, new MCvScalar(120, 120, 120));
Mat result = new Mat();
CvInvoke.VConcat(new Mat[] { img, rectangleImage }, result);
return result;
}
Edit 1:
After some more fine tuning with the following thresholds for Canny
cannyThreshold 100
cannyThresholdLinking 400
And using a minimum size for all ContourAreas of 10000 I can get the following result:
Edit 2:
For those interested, solved using the current detection, no changes were needed. Used the 2 detected rectangles in the screenshot above to get the location of the missing rectangle containing the item name.
Result can be found here:
https://github.com/josdemmers/NewWorldCompanion
For those interested, solved using the current detection, no changes were needed. Used the 2 detected rectangles in the screenshot above to get the location of the missing rectangle containing the item name.
Result can be found here: https://github.com/josdemmers/NewWorldCompanion
I'm adding a feature to a project that will allow users to see a heat map representation of their mouse movements on the screen in real time. My goal is to make this API as dynamic as possible.
By dynamic, I mean I would like users to be able to use this API to generate the heat map in real time and then plug that heat map into their third party graphics software to view that heat map. (i.e Unity, React, Mobile, etc.. )
For testing purposes the third party graphics software that I am using is Unity.
I have created a .cs unity script that does the following per frame:
Start recording mouse locations.
Have ExternalProgram.exe generate a bitmap image using the
points that unity just recorded.
Then have Unity display the updated .bmp image on the screen.
Right now the problem that I am having is that the .bmp file is not being created when I use ProcessStartInfo in my unity script to run the .exe that is in charge of creating the .bmp image.
I've been debugging this code for the past week trying to figure out what is wrong with it. I know for a fact that Unity is successfully recording the mouse's location and passing those values to the .exe after calling ProcessStartInfo.
But for some reason it doesn't actually create the .bmp file. This is weird because if I independently run the ExternalProject in Visual Studio then it works just fine and it creates the .bmp file and shows the correct heat map representation on the image.
I figured that maybe starting a program and passing it tons of data and having that program create a file would be a lot of work for unity to do every single frame. (I am open to suggestions on ways to get around having to do that)
So I decided to just have the script record points for the first 15 seconds and then try to write that data to the .bmp file but that didn't work either.
Main program file for ExternalProject.exe
class Program
{
public static void Main(string[] args)
{
string arguments = "";
foreach (string arg in args)
{
arguments += arg;
}
Console.WriteLine("My Args: " + arguments + "--EOF");
bool noArguments = String.IsNullOrEmpty(arguments);
if (noArguments)
{
// Test input data
arguments = "(111,222)|(333,444)|(555,777)|(888,999)|(1000,1000)|(1000,1000)|(1000,1000)";
}
// The method ConvertStringToSignalsList() has already been tested and it works.
List<MouseMovementSignal> signals = ConvertStringToSignalsList(arguments);
CreateMouseHeatmap(signals);
Console.WriteLine("Press Enter to close...");
Console.ReadLine();
}
private static void CreateMouseHeatmap(List<MouseMovementSignal> argumentSignals)
{
int Height = 2100;
int Width = 3800;
List<MouseMovementSignal> mouseSignals
= argumentSignals; // Program perameters
//= getRecordedMouseData(); // DB
//= createFakeMouseSignals(Height, Width); // Generates fake signals
try
{
HeatmapStructure<MouseMovementSignal> mapper =
new HeatmapStructure<MouseMovementSignal>(Height, Width);
mapper.LoadSignalsFromObjects(mouseSignals);
// .BMP Image is created inside of this method !!
mapper.IdentifyHeatRegions();
//MouseMovementSignal mms = argumentSignals[argumentSignals.Count - 1];
//Console.WriteLine("Last: " + mms.Channels[0].Name + ": " + mms.Channels[0].Values[0] + ", "
// + mms.Channels[1].Name + ": " + mms.Channels[1].Values[0]);
//finalHeatmap.Save("MyFirstBitmap.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
Console.WriteLine("Image actually Complete!!!!!!!!!!!!!!");
}
catch (Exception e)
{
Console.WriteLine("Error: " + e);
}
}
}
My Unity Script
public class HeatmapScript : MonoBehaviour {
private List<string> points;
private Camera cam;
private RawImage ri;
private string heatmapBmpPath = #"C:\Users\not-showing-you-my-file-structure-LOL\MyFirstBitmap.bmp";
private int frameCount = 0;
string pointsAsString = "";
Stopwatch sw;
// Use this for initialization
void Start() {
// Initialize the raw image.
cam = Camera.main;
GameObject imageObject = GameObject.FindGameObjectWithTag("imageView");
ri = imageObject.GetComponent<RawImage>();
// Initialize points list.
points = new List<string>();
sw = new Stopwatch();
sw.Start();
}
bool stop = false;
// Update is called once per frame.
void Update() {
float xValue = Input.mousePosition.x;
float yValue = Input.mousePosition.y;
Vector2 newPoint = new Vector2(xValue, yValue);
points.Add(newPoint.ToString());
int tSecs = 15000;// 15 seconds
// After 15 seconds of recording points pass them to the program that creates the heat map.
if (stop == false && sw.ElapsedMilliseconds > tSecs)
{
StartCoroutine("UpdateBMP");
UnityEngine.Debug.Log(points.Count);
stop = true;
}
//Update the raw image on the screen.
UnityEngine.Texture2D newTexture = CreateTextureFromBitmap(heatmapBmpPath);
//Set the RawImage to the size of the scren.
ri.texture = newTexture;
ri.rectTransform.sizeDelta = new Vector2(Screen.width, Screen.height);
frameCount++;
}
IEnumerator UpdateBMP()
{
// Show mouse position in unity environment
float xValue = Input.mousePosition.x;
float yValue = Input.mousePosition.y;
Vector2 newPoint = new Vector2(xValue, yValue);
points.Add(newPoint.ToString());
// EX:
// (123,123)|(123,123)|(123,123)|(123,123)|(123,123)|(123,123)
// display list contents without loop
pointsAsString = string.Join("|", points.ToArray());
// Every frame call Behavior's Program.cs that calls HeatmapStructure.cs to update .bmp file
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.FileName = #"C:\Users\not-showing-you-my-file-structure-LOL\ExternalProgram.exe";
processInfo.UseShellExecute = false;
processInfo.Arguments = pointsAsString;
Process process = Process.Start(processInfo);
yield return null;
}
private UnityEngine.Texture2D CreateTextureFromBitmap(string completeFilePath)
{
BMPLoader loader = new BMPLoader();
BMPImage img = loader.LoadBMP(completeFilePath);
UnityEngine.Texture2D myTexture = img.ToTexture2D();
UnityEngine.Debug.Log("File Size: " + img.header.filesize);
return myTexture;
}
}
HeatmapStructure.cs class
public class HeatmapStructure<T> where T : ISignal
{
public class COGPoint
{
public double X, Y, Z;
//public Color Color;
public byte Intensity;
public bool isD3Point = false; // 3D Point check
public const double DEFAULT_AXIS_LOC = 0.0001;
public COGPoint()
{
//Color = Color.Blue;
Intensity = 0;
}
// NOTE: double z has a default value therefore it is optional
public COGPoint(byte intensity, double x, double y, double z = DEFAULT_AXIS_LOC)
{
this.X = x;
this.Y = y;
this.Z = z; // Optional
//Color = Color.Blue; // Cold: Blue / Hot: Red
this.Intensity = intensity;
if (z != DEFAULT_AXIS_LOC)
{
isD3Point = true;
}
}
public override string ToString()
{
string output = (isD3Point == true) ?
("(x,y,z) " + X + "," + Y + "," + Z) : ("(x,y) " + X + "," + Y); // 3D : 2D
output += //" Color: " + Color.ToString() +
" Intensity: " + Intensity;
return output;
}
}
private List<COGPoint> points;
private int Height;
private int Width;
public HeatmapStructure(int Height, int Width)
{
this.Height = Height;
this.Width = Width;
points = new List<COGPoint>();
}
private Bitmap CreateIntensityMask(Bitmap bSurface, List<COGPoint> aHeatPoints)
{
// Create new graphics surface from memory bitmap
Graphics DrawSurface = Graphics.FromImage(bSurface);
// Set background color to white so that pixels can be correctly colorized
DrawSurface.Clear(Color.White);
// Traverse heat point data and draw masks for each heat point
foreach (COGPoint DataPoint in aHeatPoints)
{
// Render current heat point on draw surface
DrawHeatPoint(DrawSurface, DataPoint, 45);
}
return bSurface;
}
// TODO: How to draw updating Bitmap in unity in real time ??
private void DrawHeatPoint(Graphics Canvas, COGPoint HeatPoint, int Radius)
{
// Create points generic list of points to hold circumference points
List<Point> CircumferencePointsList = new List<Point>();
// Create an empty point to predefine the point struct used in the circumference loop
Point CircumferencePoint;
// Create an empty array that will be populated with points from the generic list
Point[] CircumferencePointsArray;
// Calculate ratio to scale byte intensity range from 0-255 to 0-1
float fRatio = 1F / Byte.MaxValue;
// Precalulate half of byte max value
byte bHalf = Byte.MaxValue / 2;
// Flip intensity on it's center value from low-high to high-low
int iIntensity = (byte)(HeatPoint.Intensity - ((HeatPoint.Intensity - bHalf) * 2));
// Store scaled and flipped intensity value for use with gradient center location
float fIntensity = iIntensity * fRatio;
// Loop through all angles of a circle
// Define loop variable as a double to prevent casting in each iteration
// Iterate through loop on 10 degree deltas, this can change to improve performance
for (double i = 0; i <= 360; i += 10)
{
// Replace last iteration point with new empty point struct
CircumferencePoint = new Point();
// Plot new point on the circumference of a circle of the defined radius
// Using the point coordinates, radius, and angle
// Calculate the position of this iterations point on the circle
CircumferencePoint.X = Convert.ToInt32(HeatPoint.X + Radius * Math.Cos(ConvertDegreesToRadians(i)));
CircumferencePoint.Y = Convert.ToInt32(HeatPoint.Y + Radius * Math.Sin(ConvertDegreesToRadians(i)));
// Add newly plotted circumference point to generic point list
CircumferencePointsList.Add(CircumferencePoint);
}
// Populate empty points system array from generic points array list
// Do this to satisfy the datatype of the PathGradientBrush and FillPolygon methods
CircumferencePointsArray = CircumferencePointsList.ToArray();
// Create new PathGradientBrush to create a radial gradient using the circumference points
PathGradientBrush GradientShaper = new PathGradientBrush(CircumferencePointsArray);
// Create new color blend to tell the PathGradientBrush what colors to use and where to put them
ColorBlend GradientSpecifications = new ColorBlend(3);
// Define positions of gradient colors, use intesity to adjust the middle color to
// show more mask or less mask
GradientSpecifications.Positions = new float[3] { 0, fIntensity, 1 };
// Define gradient colors and their alpha values, adjust alpha of gradient colors to match intensity
GradientSpecifications.Colors = new Color[3]
{
Color.FromArgb(0, Color.White),
Color.FromArgb(HeatPoint.Intensity, Color.Black),
Color.FromArgb(HeatPoint.Intensity, Color.Black)
};
// Pass off color blend to PathGradientBrush to instruct it how to generate the gradient
GradientShaper.InterpolationColors = GradientSpecifications;
// Draw polygon (circle) using our point array and gradient brush
Canvas.FillPolygon(GradientShaper, CircumferencePointsArray);
}
private double ConvertDegreesToRadians(double degrees)
{
double radians = (Math.PI / 180) * degrees;
return (radians);
}
// old name : button1_Click
public Bitmap IdentifyHeatRegions()
{
// Create new memory bitmap the same size as the picture box
Bitmap bMap = new Bitmap(Width, Height);
// Call CreateIntensityMask, give it the memory bitmap, and use it's output to set the picture box image
Bitmap bm = CreateIntensityMask(bMap, points);
Bitmap coloredBitmap = Colorize(bm, 243); // <-- NOTE: should be 255. But my palette.bmp is 243x5
coloredBitmap.Save("MyFirstBitmap.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
return coloredBitmap;
}
public static Bitmap Colorize(Bitmap Mask, byte Alpha)
{
// Create new bitmap to act as a work surface for the colorization process
Bitmap Output = new Bitmap(Mask.Width, Mask.Height, PixelFormat.Format32bppArgb);
// Create a graphics object from our memory bitmap so we can draw on it and clear it's drawing surface
Graphics Surface = Graphics.FromImage(Output);
Surface.Clear(Color.Transparent);
// Build an array of color mappings to remap our greyscale mask to full color
// Accept an alpha byte to specify the transparancy of the output image
ColorMap[] Colors = CreatePaletteIndex(Alpha);
// Create new image attributes class to handle the color remappings
// Inject our color map array to instruct the image attributes class how to do the colorization
ImageAttributes Remapper = new ImageAttributes();
try
{
Remapper.SetRemapTable(Colors);
}
catch (Exception e)
{
Console.WriteLine(e);
}
// Draw our mask onto our memory bitmap work surface using the new color mapping scheme
Surface.DrawImage(Mask, new Rectangle(0, 0, Mask.Width, Mask.Height), 0, 0, Mask.Width, Mask.Height, GraphicsUnit.Pixel, Remapper);
// Send back newly colorized memory bitmap
return Output;
}
private static ColorMap[] CreatePaletteIndex(byte Alpha)
{
ColorMap[] OutputMap = new ColorMap[Alpha + 1];
// Change this path to wherever you saved the palette image.
Bitmap Palette = (Bitmap)Bitmap.FromFile(#"C:\Users\cdowns\Desktop\palette.bmp");
// Loop through each pixel and create a new color mapping
try
{
for (int X = 0; X <= Alpha; X++)
{
OutputMap[X] = new ColorMap();
OutputMap[X].OldColor = Color.FromArgb(X, X, X);
OutputMap[X].NewColor = Color.FromArgb(Alpha, Palette.GetPixel(X, 0));
}
}
catch (Exception e) {
Console.WriteLine(e);
}
return OutputMap;
}
public void LoadSignalsFromObjects(List<T> allSignals) // ISignal Object
{
Random random = new Random();
foreach (T signal in allSignals)
{
COGPoint newPoint = new COGPoint();
if (allSignals[0].GetType() == typeof(MouseMovementSignal))
{
string axis1 = signal.Channels[0].Name;
List<double> value1 = signal.Channels[0].Values;
string axis2 = signal.Channels[1].Name;
List<double> value2 = signal.Channels[1].Values;
// Make sure to enter signals into database in this format
//Console.WriteLine(axis1 + " " + axis2);
newPoint.X = value1[0];
newPoint.Y = value2[0];
// TOOD: Implement
newPoint.Intensity = (byte)random.Next(0, 120);
// Display newPoint values
//Console.WriteLine("COGPoint Numbers: X: " + newPoint.X + " , Y: " + newPoint.Y
// + /*" Color: " + newPoint.Color + */" Intensity: " + newPoint.Intensity);
}
else if (allSignals[0].GetType() == typeof(EyePosition))
{
}
// Add to main list of heat points
points.Add(newPoint);
}
}
}
Expected result is that the .bmp image is created after the first 15 seconds.
(P.S. I am very new to both Unity and C# to I am probably doing this completely wrong. I am open to an entirely new idea for going about making this work. Thanks)
After doing a little more browsing on here for similar problems that other people have had I found this post pictureBox.Image.Save() in c# doesn't work. It answered my question regarding why my .bmp wasn't being generated.
It turns out that my program was working correctly after all. It was correctly generating the .bmp file. However, when I called ProcessStartInfo from within unity to run the ExternalProgram.exe that called Bitmap.save("filename.bmp") the working directory changed. Therefore, the image was not being saved in the location that I was expecting to find it in.
I have a chart on which I want to plot a heat map; the only data I have is humidity and temperature, which represent a point in the chart.
How do I get the rectangular type of heat map on the chart in c#?
What I want is similar to picture below :
What I really want is a rectangular region in the chart which is plotted in different color based on the point that i get from the list of points and form the colorful section in the chart.
You have a choice of at least three ways to create a chart with colored rectangles that make up a heat map.
Here is one example
that uses/abuses a DataGridView. While I would not suggest this, the post contains a useful function that creates nice color lists to use in your task.
Then there is the option to draw the chart using GDI+ methods, namely Graphics.FillRectangle. This not hard at all but once you want to get those nice extras a Chart control offers, like scaling, axes, tooltips etc the work adds up.. See below!
So let's have a look at option three: Using the Chart control from the DataVisualization namespace.
Let's first assume that you have created a list of colors:
List<Color> colorList = new List<Color>();
And that you have managed to project your data onto a 2D array of int indices that point into the color list:
int[,] coloredData = null;
Next you have to pick a ChartType for your Series S1 There really is only one I can think of that will help:
S1.ChartType = SeriesChartType.Point;
Points are displayed by Markers. We want the DataPoints not really displayed as one of the standard MarkerTypes.
Square would be ok, if we wanted to display squares; but for rectangles it will not work well: Even if we let them overlap there will still be points at the borders that have a different size because they don't fully overlap..
So we use a custom marker by setting the MarkerImage of each point to a bitmap of a suitable size and color.
Here is a loop that adds the DataPoints to our Series and sets each to have a MarkerImage:
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
This takes some explaining: To set a MarkerImage that is not at a path on the disk, it has to reside in the Chart's Images collection. This means is needs to be of type NamedImage. Any image will do, but it has to have a unique name string added to identify it in the NamedImagesCollection . I chose the names to be 'NI1', 'NI2'..
Obviously we need to create all those images; here is a function to do that:
void createMarkers(Chart chart, int count)
{
// rough calculation:
int sw = chart.ClientSize.Width / coloredData.GetLength(0);
int sh = chart.ClientSize.Height / coloredData.GetLength(1);
// clean up previous images:
foreach(NamedImage ni in chart1.Images) ni.Dispose();
chart.Images.Clear();
// now create count images:
for (int i = 0; i < count; i++)
{
Bitmap bmp = new Bitmap(sw, sh);
using (Graphics G = Graphics.FromImage(bmp))
G.Clear(colorList[i]);
chart.Images.Add(new NamedImage("NI" + i, bmp));
}
}
We want all markers to have at least roughly the right size; so whenever that size changes we set it again:
void setMarkerSize(Chart chart)
{
int sx = chart1.ClientSize.Width / coloredData.GetLength(0);
int sy = chart1.ClientSize.Height / coloredData.GetLength(1);
chart1.Series["S1"].MarkerSize = (int)Math.Max(sx, sy);
}
This doesn't care much about details like the InnerPlotPosition, i.e. the actual area to draw to; so here is some room for refinement..!
We call this when we set up the chart but also upon resizing:
private void chart1_Resize(object sender, EventArgs e)
{
setMarkerSize(chart1);
createMarkers(chart1, 100);
}
Let's have a look at the result using some cheap testdata:
As you can see resizing works ok..
Here is the full code that set up my example:
private void button6_Click(object sender, EventArgs e)
{
List<Color> stopColors = new List<Color>()
{ Color.Blue, Color.Cyan, Color.YellowGreen, Color.Orange, Color.Red };
colorList = interpolateColors(stopColors, 100);
coloredData = getCData(32, 24);
// basic setup..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA");
chart1.Series.Clear();
Series S1 = chart1.Series.Add("S1");
chart1.Legends.Clear();
// we choose a charttype that lets us add points freely:
S1.ChartType = SeriesChartType.Point;
Size sz = chart1.ClientSize;
// we need to make the markers large enough to fill the area completely:
setMarkerSize(chart1);
createMarkers(chart1, 100);
// now we fill in the datapoints
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
// S1.Points[pt].Color = coloredData[x, y];
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
}
A few notes on limitations:
The point will always sit on top of any gridlines. If you really needs those you will have to draw them on top in one of the the Paint events.
The labels as shown are referring to the integers indices of the data array. If you want to show the original data, one way would be to add CustomLabels to the axes.. See here for an example!
This should give you an idea of what you can do with a Chart control; to complete your confusion here is how to draw those rectangles in GDI+ using the same colors and data:
Bitmap getChartImg(float[,] data, Size sz, Padding pad)
{
Bitmap bmp = new Bitmap(sz.Width , sz.Height);
using (Graphics G = Graphics.FromImage(bmp))
{
float w = 1f * (sz.Width - pad.Left - pad.Right) / coloredData.GetLength(0);
float h = 1f * (sz.Height - pad.Top - pad.Bottom) / coloredData.GetLength(1);
for (int x = 0; x < coloredData.GetLength(0); x++)
for (int y = 0; y < coloredData.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colorList[coloredData[x,y]]))
G.FillRectangle(brush, pad.Left + x * w, y * h - pad.Bottom, w, h);
}
}
return bmp;
}
The resulting Bitmap looks familiar:
That was simple; but to add all the extras into the space reserved by the padding will not be so easy..
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions concerning problems with code you've written must describe the specific problem — and include valid code to reproduce it — in the question itself. See SSCCE.org for guidance.
Closed 9 years ago.
Improve this question
I have to demonstrate a use of Lambdas/Actions/Delegates within my program for my A-Level to make it more complex.
I know how to make them all and (kind of) what they do, but not when to use them.
Can anybody let me know where in my code I can use one, two or all of these? I wasn't sure if this was the right place for this, let me know if not.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
//Command-Line Arguements for SizeX and SizeY
namespace prjT02L08_Predator_Prey
{
public partial class frmSim : Form
{
Point GridSize; //Creates a pair of X and Y co-ordinates
Random r = new Random(); //Used for any random number needed in the program
float FoodSpawnChance = 50; //The chance that food has of spawning
int SimTick = 0; //How long the program has been running for
int ImageScale = 7; //How much to scale the bitmap grid up for displaying
int SizeX = 60; //The number of pixels on the X axis
int SizeY = 60; //The number of pixels on the Y axis
bool StepDone = true; //Has one update of the simulation happened?
public frmSim() //Main constructor method
{
InitializeComponent();
GridSize = new Point(SizeX, SizeY); //Sets the size of the grid using the size of the X and Y axis
AStar.Grid = Node.MakeGrid(GridSize.X, GridSize.Y, 10);
for (int i = 0; i < 10; i++)
new Mitosis(r.Next(0, SizeX) /*Produces a random number between 0 and the size of the X axis*/,
r.Next(0, SizeY)/*Random number between 0 and the size of the Y axis*/); //Adds new Mitosis bacteria for the first time
for (int i = 0; i < 8; i++)
new Meiosis(r.Next(0, SizeX), r.Next(0, SizeY)); //Adds new Meiosis bacteria for the first time
chkNaturalSpawn.Checked = true; //Sets the food's natural spawn to true, so it randomly spawns
}
private void UpdateSim() //Updates the whole simulation
{
UpdateVars(); //Updates all the variables in the simulation
if (SimTick == 20) //If the simulation has run for 20 ticks,
for (int i = 0; i < 10; i++)
new VirusP(1, 1); //Creates a new virus at the X position 1, and Y position 1
if (chkNaturalSpawn.Checked == true) //If natural spawning has been selected
SpawnFood(); //then try to spawn food
Entity.UpdateAll(SimTick); //Updates all entities
}
private void UpdateVars() //Updates all the variables in the simulation
{
SimTick++; //Each timer tick, this variable is incremented
tmrSimClock.Interval = trcInterval.Value; //The gap between ticks is set based of the trackbar (Which has been reversed right-to-left)
if (chkNaturalSpawn.Checked == true) //Checks if natural food spawning is enabled
FoodSpawnChance = trcFoodSpawn.Value; //then if true, sets the chance of food spawning to the value of the trackbar
VirusP.DoubleStepChance = trcPred2Step.Value; //The chance of the Virus moving two places instead of one is set of the trackbar
}
private void SpawnFood() //Attempts to spawn food at a random location
{
//Chance to spawn based on FoodSpawnChance variable
if (r.Next(0, 1000) < (FoodSpawnChance * 100)) //Uses a random number to determine whether food can spawn or not
{
int x = r.Next(0, GridSize.X); //Sets the value of x to a random number between 0 and the value of the Gridsize.X
int y = r.Next(0, GridSize.Y); //Sets the value of y to a random number between 0 and the value of the Gridsize.Y
if (!AStar.Grid[x, y].IsWall) //Checks if the random position chosen isn't a wall
new Food(x, y); //then if true, food is spawned at that position
}
}
private void frmSim_Load(object sender, EventArgs e)
{
}
private void btnStep_Click(object sender, EventArgs e)
{
if (StepDone == true) //Checks if the previous update from this button has already been completed or not - Prevents slow down
{
StepDone = false;
UpdateSim(); //Updates the simulation once
DrawSim(); //Redraws the bitmap image to show a visual update
StepDone = true;
}
}
private void DrawSim() //Creates the bitmap of the grid which is dispalyed on the screen and scales it up
{
Bitmap bmp = new Bitmap(GridSize.X, GridSize.Y); //Creates the bitmap specifying the width and height of it
//These two for loops loop through every part of the grid:
for (int x = 0; x < GridSize.X; x++) //For every value in the height of the grid
{
for (int y = 0; y < GridSize.Y; y++)//and every value in the width of the grid
{
Color Colour = Color.Black; //Creates a new color used to set the pixel colour on the bitmap (Empty space is black)
foreach (Entity e in Entity.GetEntitiesAt(x, y)) //For every entity a the current location...
{
if ((e as Food) != null) //If it is Food, set the colour to green
Colour = Color.FromArgb(Colour.R, 255, Colour.B);
else if ((e as Mitosis) != null) //If it is bacteria Mitosis, set the colour to blue
Colour = Color.FromArgb(Colour.R, Colour.G, 255);
else if ((e as Meiosis) != null) //If it is bacteria Meiosis, set the colour to gold
Colour = Color.Gold;
else //If it's none of these, the only entity left is the Virus, set the colour to red
Colour = Color.FromArgb(255, Colour.G, Colour.B);
}
if (AStar.Grid[x, y].IsWall) //If that location is a wall, set the colour to white
Colour = Color.White;
bmp.SetPixel(x, y, Colour); //Set the pixel at position x and y to the colour chosen above
}
}
//Scales up the bitmap into a new bitmap
Bitmap bmpscale = new Bitmap(GridSize.X * ImageScale, GridSize.Y * ImageScale);
for (int x = 0; x < GridSize.X; x++)
{
for (int y = 0; y < GridSize.Y; y++)
{
for (int sx = 0; sx < ImageScale; sx++)
{
for (int sy = 0; sy < ImageScale; sy++)
{
bmpscale.SetPixel(((x * ImageScale) + sx), ((y * ImageScale) + sy), bmp.GetPixel(x, y));
}
}
}
}
this.CreateGraphics().DrawImage(bmpscale, new Point(10, 10)); //Draws the bitmap image at set co-ordinates on the form
}
private void tmrSimClock_Tick(object sender, EventArgs e) //Every time the timer updates
{
UpdateSim(); //Updates the simulation
DrawSim(); //Redraws the simulation
}
private void btnRun_Click(object sender, EventArgs e)
{
tmrSimClock.Enabled = !tmrSimClock.Enabled; //Put timer in opposite state
btnStep.Enabled = !btnStep.Enabled; //Put button in opposite state
if (tmrSimClock.Enabled)
btnRun.Text = "Running...";
else
btnRun.Text = "Run";
}
private void btnReset_Click(object sender, EventArgs e)
{
Entity.Entities = new List<Entity>(); //Recreates the list of entitites
SimTick = 0; //Restarts the simulation timer
tmrSimClock.Enabled = false; //Turns the timer off
for (int i = 0; i < 10; i++)//Recreates entities Mitosis and Meiosis
{
new Mitosis(r.Next(0, SizeX), r.Next(0, SizeY));
new Meiosis(r.Next(0, SizeX), r.Next(0, SizeY));
}
btnRun.Text = "Run";
}
private void chkNaturalSpawn_CheckedChanged(object sender, EventArgs e)
{
lblFood.Enabled = chkNaturalSpawn.Checked == true ? true : false; //Turnery Statement
trcFoodSpawn.Enabled = chkNaturalSpawn.Checked == true ? true : false; //If checked is true, return true, else return false
}
}
}
First, I've got to agree with Jon: make it tidy first. This is an example of lambda use by using an action as an anonymous delegate/lambda function. You may have to fiddle to get it to work
private void traverseBmp(Action<int, int> doIt)
{
for (int x = 0; x < GridSize.X; x++) //For every value in the height of the grid
{
for (int y = 0; y < GridSize.Y; y++)//and every value in the width of the grid
{
doIt(x, y);
}
}
private void DrawSim() //Creates the bitmap of the grid which is dispalyed on the screen and scales it up
{
Bitmap bmp = new Bitmap(GridSize.X, GridSize.Y); //Creates the bitmap specifying the width and height of it
//These two for loops loop through every part of the grid:
traverseBmp((x, y) =>
{
Color Colour = Color.Black;
//Creates a new color used to set the pixel colour on the bitmap (Empty space is black)
foreach (Entity e in Entity.GetEntitiesAt(x, y)) //For every entity a the current location...
{
if ((e as Food) != null) //If it is Food, set the colour to green
Colour = Color.FromArgb(Colour.R, 255, Colour.B);
else if ((e as Mitosis) != null) //If it is bacteria Mitosis, set the colour to blue
Colour = Color.FromArgb(Colour.R, Colour.G, 255);
else if ((e as Meiosis) != null) //If it is bacteria Meiosis, set the colour to gold
Colour = Color.Gold;
else //If it's none of these, the only entity left is the Virus, set the colour to red
Colour = Color.FromArgb(255, Colour.G, Colour.B);
}
if (AStar.Grid[x, y].IsWall) //If that location is a wall, set the colour to white
Colour = Color.White;
bmp.SetPixel(x, y, Colour); //Set the pixel at position x and y to the colour chosen above
});
//Scales up the bitmap into a new bitmap
Bitmap bmpscale = new Bitmap(GridSize.X * ImageScale, GridSize.Y * ImageScale);
traverseBmp((x,y) =>
{
for (int sx = 0; sx < ImageScale; sx++)
{
for (int sy = 0; sy < ImageScale; sy++)
{
bmpscale.SetPixel(((x*ImageScale) + sx), ((y*ImageScale) + sy), bmp.GetPixel(x, y));
}
}
});
this.CreateGraphics().DrawImage(bmpscale, new Point(10, 10)); //Draws the bitmap image at set co-ordinates on the form
}
I'm trying to draw 10 rectangles, but when I use g.DrawRectangle() it is drawing a cross as shown below:
I'm creating Vertex objects that contain a getRectangle() function which returns a Rectangle object for that vertex.
I was hoping to create these objects and show them as Rectangles on the pictureBox.
Here's my code
private System.Drawing.Graphics g;
private System.Drawing.Pen pen1 = new System.Drawing.Pen(Color.Blue, 2F);
public Form1()
{
InitializeComponent();
pictureBox.Dock = DockStyle.Fill;
pictureBox.BackColor = Color.White;
}
private void paintPictureBox(object sender, PaintEventArgs e)
{
// Draw the vertex on the screen
g = e.Graphics;
// Create new graph object
Graph newGraph = new Graph();
for (int i = 0; i <= 10; i++)
{
// Tried this code too, but it still shows the cross
//g.DrawRectangle(pen1, Rectangle(10,10,10,10);
g.DrawRectangle(pen1, newGraph.verteces[0,i].getRectangle());
}
}
Code for Vertex class
class Vertex
{
public int locationX;
public int locationY;
public int height = 10;
public int width = 10;
// Empty overload constructor
public Vertex()
{
}
// Constructor for Vertex
public Vertex(int locX, int locY)
{
// Set the variables
this.locationX = locX;
this.locationY = locY;
}
public Rectangle getRectangle()
{
// Create a rectangle out of the vertex information
return new Rectangle(locationX, locationY, width, height);
}
}
Code for Graph class
class Graph
{
//verteces;
public Vertex[,] verteces = new Vertex[10, 10];
public Graph()
{
// Generate the graph, create the vertexs
for (int i = 0; i <= 10; i++)
{
// Create 10 Vertexes with different coordinates
verteces[0, i] = new Vertex(0, i);
}
}
}
Looks like an exception in your draw loop
last call to:
newGraph.verteces[0,i]
fails with OutOfRangeException
you shoul iterate not to i <= 10, but to i < 10
Red Cross Indicates that an Exception has been thrown, you are not seeing it because it's being handled. Configure Visual Studio to break on exception throw to catch it.
An exception has been thrown. At first look your code:
for (int i = 0; i <= 10; i++)
will generate an IndexOutOfRangeException because verteces has 10 items but it will cycle from 0 to 10 (included so it'll search for 11 elements). It depends on what you want to do but you have to change the cycle to (removing the = from <=):
for (int i = 0; i < 10; i++)
or to increment the size of verteces to 11.