How to write data to .bmp file using an external program from unity? - c#

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.

Related

How to create 2D map in unity using single image?

I have to create 2D map in unity using single image. So, i have one .png file with 5 different pieces out of which I have to create a map & i am not allowed to crop the image. So, how do create this map using only one image.
I am bit new to unity, i tried searching but didn't find exactly what i am looking for. Also tried, tilemap using Pallet but couldn't figure out how to extract only one portion of the image.
You can create various Sprites from the given texture on the fly in code.
You can define which part of a given Texture2D shall be used for the Sprite using Sprite.Create providing the rect in pixel coordinates of the given image. Remember however that in unity texture coordinates start bottom left.
Example use the given pixel coordinate snippet of a texture for the attached UI.Image component:
[RequireComponent(typeof(Image))]
public class Example : MonoBehaviour
{
// your texture e.g. from a public field via the inspector
public Texture2D texture;
// define which pixel coordinates to use for this sprite also via the inspector
public Rect pixelCoordinates;
private void Start()
{
var newSprite = Sprite.Create(texture, pixelCoordinates, Vector2.one / 2f);
GetComponent<Image>().sprite = newSprite;
}
// called everytime something is changed in the Inspector
private void OnValidate()
{
if (!texture)
{
pixelCoordinates = new Rect();
return;
}
// reset to valid rect values
pixelCoordinates.x = Mathf.Clamp(pixelCoordinates.x, 0, texture.width);
pixelCoordinates.y = Mathf.Clamp(pixelCoordinates.y, 0, texture.height);
pixelCoordinates.width = Mathf.Clamp(pixelCoordinates.width, 0, pixelCoordinates.x + texture.width);
pixelCoordinates.height = Mathf.Clamp(pixelCoordinates.height, 0, pixelCoordinates.y + texture.height);
}
}
Or you get make a kind of manager class for generating all needed sprites once e.g. in a list like
public class Example : MonoBehaviour
{
// your texture e.g. from a public field via the inspector
public Texture2D texture;
// define which pixel coordinates to use for this sprite also via the inspector
public List<Rect> pixelCoordinates = new List<Rect>();
// OUTPUT
public List<Sprite> resultSprites = new List<Sprite>();
private void Start()
{
foreach(var coordinates in pixelCoordinates)
{
var newSprite = Sprite.Create(texture, coordinates, Vector2.one / 2f);
resultSprites.Add(newSprite);
}
}
// called everytime something is changed in the Inspector
private void OnValidate()
{
if (!texture)
{
for(var i = 0; i < pixelCoordinates.Count; i++)
{
pixelCoordinates[i] = new Rect();
}
return;
}
for (var i = 0; i < pixelCoordinates.Count; i++)
{
// reset to valid rect values
var rect = pixelCoordinates[i];
rect.x = Mathf.Clamp(pixelCoordinates[i].x, 0, texture.width);
rect.y = Mathf.Clamp(pixelCoordinates[i].y, 0, texture.height);
rect.width = Mathf.Clamp(pixelCoordinates[i].width, 0, pixelCoordinates[i].x + texture.width);
rect.height = Mathf.Clamp(pixelCoordinates[i].height, 0, pixelCoordinates[i].y + texture.height);
pixelCoordinates[i] = rect;
}
}
}
Example:
I have 4 Image instances and configured them so the pixelCoordinates are:
imageBottomLeft: X=0, Y=0, W=100, H=100
imageTopLeft: X=0, Y=100, W=100, H=100
imageBottomRight: X=100, Y=0, W=100, H=100
imageTopRight: X=100, Y=100, W=100, H=100
The texture I used is 386 x 395 so I'm not using all of it here (just added the frames the Sprites are going to use)
so when hitting Play the following sprites are created:

C# - Updating pictureBox.Image during Runtime causes Error

i am trying to change the pictureBox.Image during Runtime. I have several Model classes with a picture stored, whenever i click on a MenuStripItem i call the method "ChangePictureBoxImages". Till then there is no error (the pB is invisible!) but once i call the method to make the pB visible i get an Error. The Error code: "An unhandled exception of type 'System.ArgumentException' occurred in System.Drawing.dll".
Research said i should dispose the picturebox and set it to "null", however this does NOT help.
My Code:
using (Image current = BitmapManipulator.EvaluateMesurement(CSV_Name1, max_Rows, max_Col, var.TopImage, var.BitmapToManipulate, pB_ColourScale_Evaluation.Image, var.BitmapToManipulate, var.Filepath, var.FoldID))
{
var.LastEvaluationImage = current;
BitmapManipulator.CombineImagesAndSaveThem_Evaluation(var.TopImage, var.BitmapToManipulate, pB_ColourScale_Evaluation.Image, var.Filepath, var.FoldID); //saves the Files as jpg
if (var.CurrentlyShownToUser) //checks if the MenuStripItem is the active one
{
if (var.LastEvaluationImage == null) { MessageBox.Show("the image is null");} //only for debugging purpose -> does never call
ChangePictureBoxImages();
}
}
and the ChangePictureBoxImages():
public void ChangePictureBoxImages()
{
foreach (Fold fold in FoldExisting)
{
if (fold.FoldID == LastSelectedMenuStripItem_Name) //the clicked item is the last Selected MenuStripItem
{
if (fold.LastEvaluationImage != null)
{
Debug.WriteLine(pB_Evaluation_Bottom.Image.ToString() + " " + fold.LastEvaluationImage.ToString());
pB_Evaluation_Bottom.Image = fold.LastEvaluationImage;
}
pB_Evaluation_Top.Image = fold.TopImage;
}
}
}
There is no error till then, the error appears once i call "pB_Evaluation_Bottom.visible = true". (or if i called the visible method first first the error appears upon changing the Image!) The error also appears upon clicking 2 times on the MenuStripItem. I load the picture from the Class Fold as following:
This will set an image in the fold class, this image will then be manipulated and stored in LastEvaluationImage
private void setTheImages(string PictureToManipulate, string PathToTopImage)
{
try
{
this.BitmapToManipulate_intern = (Image)Image.FromFile(#PictureToManipulate, true);
this.TopImage_intern = (Image)Image.FromFile(#PathToTopImage, true);
}
catch (ArgumentNullException ex)
{
Debug.WriteLine("The BitMap for the manipulation process and the top image is not created.");
}
}
and the LastEvaluationImage where the last picture is stored -> this will be called to be the new pb.Image
private Image LastEvaluationImage_intern;
public Image LastEvaluationImage
{
get
{
return this.LastEvaluationImage_intern;
}
set
{
if (LastEvaluationImage_intern != null) { LastEvaluationImage_intern.Dispose(); LastEvaluationImage_intern = null; }
this.LastEvaluationImage_intern = value;
this.LastEvaluationTime_intern = DateTime.Now;
}
}
I know this is a little complex, but i hope someone can help me.
THANKS IN ADVANCE!
UPDATE: The Error must be in the following Code:
The BitmapManipulator.EvaluateMeasurement Code :
public Image EvaluateMesurement(double[][] MeasuredValues, int max_Rows, int max_Col, Image pB_Evaluation_Top, Image pB_Evaluation_Bottom, Image pB_EvaluationColourScale, Image ManipulatedBitmap, string PathMeasurementFiles, string Foldname)
{
using (Bitmap bitmap = new Bitmap(ManipulatedBitmap))
{
// the data array sizes:
int number_nio = 0;
int number_total = 0;
List<FileInfo> LastFiles;
int got_number_for_trends = Properties.Settings.Default.TrendNumber;
SolidBrush myBrush = new SolidBrush(red);
using (Graphics g = Graphics.FromImage(bitmap))
{
Random rnd = new Random(8);
int[,] data = new int[max_Col, max_Rows];
// scale the tile size:
float sx = 1f * bitmap.Width / data.GetLength(0);
float sy = 1f * bitmap.Height / data.GetLength(1);
LastFiles = FM.GetLastFiles_Trend(ref got_number_for_trends, PathMeasurementFiles);
double[][] CSV_Statistiken = FM.LastFilesToCSV(got_number_for_trends, true, LastFiles, PathMeasurementFiles);
for (int x = 0; x < max_Col; x++)
{
for (int y = max_Rows - 1; y >= 0; y--)
{
number_total++;
RectangleF r = new RectangleF(x * sx, y * sy, sx, sy);
if (MeasuredValues[y][x] < Properties.Settings.Default.Threshhold)
{
number_nio++;
if (CSV_Statistiken[y][x] == Properties.Settings.Default.TrendNumber)
{
myBrush.Color = Color.FromArgb(150, black);
g.FillRectangle(myBrush, r);
}
else
{
myBrush.Color = Color.FromArgb(150, red);
g.FillRectangle(myBrush, r);
}
}
else
{
myBrush.Color = Color.FromArgb(150, green);
g.FillRectangle(myBrush, r);
}
}
}
}
return bitmap;
}
}
This returned bitmap will be stored in fold.LastEvaluationImage as following:
using (Image current = BitmapManipulator.EvaluateMesurement(CSV_Name1, max_Rows, max_Col, var.TopImage, var.BitmapToManipulate, pB_ColourScale_Evaluation.Image, var.BitmapToManipulate, var.Filepath, var.FoldID))
{
var.LastEvaluationImage = current;
}
You're returning a disposed bitmap. It shouldn't be surprising you can't draw something that no longer exists :)
The using (bitmap) is the last thing you want in this case. The bitmap must survive longer than the scope of the using. And the using (current) in the caller has the same problem - you're again disposing the image way too early. You can only dispose it when it's clear that it isn't going to be used ever again - e.g. when you replace it with a new image.
To elaborate, using does nothing but call Dispose when you leave its scope. In the case of Bitmap (which is just a "thin" wrapper around a GDI bitmap), this releases the memory where the actual image data is stored. There isn't anything interesting left, so there's nothing to draw (and you'd basically be calling DrawBitmap(NULL) as far as GDI is concerned).

Update color of single point of GeometryModel3D Material rather than whole system of points

I'm having trouble getting my head around the colour/material system of C# WPF projects, currently I am updating the colour of an entire system of points on each update of the model when I would instead like to just update the colour of a single point (as it is added).
AggregateSystem Class
public class AggregateSystem {
// stack to store each particle in aggregate
private readonly Stack<AggregateParticle> particle_stack;
private readonly GeometryModel3D particle_model;
// positions, indices and texture co-ordinates for particles
private readonly Point3DCollection particle_positions;
private readonly Int32Collection triangle_indices;
private readonly PointCollection text_coords;
// brush to apply to particle_model.Material
private RadialGradientBrush rad_brush;
// ellipse for rendering
private Ellipse ellipse;
private RenderTargetBitmap render_bitmap;
public AggregateSystem() {
particle_stack = new Stack<AggregateParticle>();
particle_model = new GeometryModel3D { Geometry = new MeshGeometry3D() };
ellipse = new Ellipse {
Width = 32.0,
Height = 32.0
};
rad_brush = new RadialGradientBrush();
// fill ellipse interior using rad_brush
ellipse.Fill = rad_brush;
ellipse.Measure(new Size(32,32));
ellipse.Arrange(new Rect(0,0,32,32));
render_bitmap = new RenderTargetBitmap(32,32,96,96,PixelFormats.Pbgra32));
ImageBrush img_brush = new ImageBrush(render_bitmap);
DiffuseMaterial diff_mat = new DiffuseMaterial(img_brush);
particle_model.Material = diff_mat;
particle_positions = new Point3DCollection();
triangle_indices = new Int32Collection();
tex_coords = new PointCollection();
}
public Model3D AggregateModel => particle_model;
public void Update() {
// get the most recently added particle
AggregateParticle p = particle_stack.Peek();
// compute position index for triangle index generation
int position_index = particle_stack.Count * 4;
// create points associated with particle for circle generation
Point3D p1 = new Point3D(p.position.X, p.position.Y, p.position.Z);
Point3D p2 = new Point3D(p.position.X, p.position.Y + p.size, p.position.Z);
Point3D p3 = new Point3D(p.position.X + p.size, p.position.Y + p.size, p.position.Z);
Point3D p4 = new Point3D(p.position.X + p.size, p.position.Y, p.position.Z);
// add points to particle positions collection
particle_positions.Add(p1);
particle_positions.Add(p2);
particle_positions.Add(p3);
particle_positions.Add(p4);
// create points for texture co-ords
Point t1 = new Point(0.0, 0.0);
Point t2 = new Point(0.0, 1.0);
Point t3 = new Point(1.0, 1.0);
Point t4 = new Point(1.0, 0.0);
// add texture co-ords points to texcoords collection
tex_coords.Add(t1);
tex_coords.Add(t2);
tex_coords.Add(t3);
tex_coords.Add(t4);
// add position indices to indices collection
triangle_indices.Add(position_index);
triangle_indices.Add(position_index + 2);
triangle_indices.Add(position_index + 1);
triangle_indices.Add(position_index);
triangle_indices.Add(position_index + 3);
triangle_indices.Add(position_index + 2);
// update colour of points - **NOTE: UPDATES ENTIRE POINT SYSTEM**
// -> want to just apply colour to single particles added
rad_brush.GradientStops.Add(new GradientStop(p.colour, 0.0));
render_bitmap.Render(ellipse);
// set particle_model Geometry model properties
((MeshGeometry3D)particle_model.Geometry).Positions = particle_positions;
((MeshGeometry3D)particle_model.Geometry).TriangleIndices = triangle_indices;
((MeshGeometry3D)particle_model.Geometry).TextureCoordinates = tex_coords;
}
public void SpawnParticle(Point3D _pos, Color _col, double _size) {
AggregateParticle agg_particle = new AggregateParticle {
position = _pos, colour = _col, size = _size;
}
// push most-recently-added particle to stack
particle_stack.Push(agg_particle);
}
}
where AggregateParticle is a POD class consisting of Point3D position, Color color and double size fields which are self-explanatory.
Is there any simple and efficient method to update the colour of the single particle as it is added in the Update method rather than the entire system of particles? Or will I need to create a List (or similar data structure) of DiffuseMaterial instances for each and every particle in the system and apply brushes for the necessary colour to each?
[The latter is something I want to avoid at all costs, partly due to the fact it would require large structural changes to my code, and I am certain that there is a better way to approach this than that - i.e. there MUST be some simple way to apply colour to a set of texture co-ordinates, surely?!.]
Further Details
AggregateModel is a single Model3D instance corresponding to the field particle_model which is added to a Model3DGroup of the MainWindow.
I should note that what I am trying to achieve, specifically, here is a "gradient" of colours for each particle in an aggregate structure where a particle has a Color in a "temperature-gradient" (computed elsewhere in the program) which is dependent upon order in which it was generated - i.e. particles have a colder colour if generated earlier and a warmer colour if generated later. This colour list is pre-computed and passed to each particle in the Update method as can be seen above.
One solution I attempted involved creating a separate AggregateComponent instance for each particle where each of these objects has an associated Model3D and thus a corresponding brush. Then an AggregateComponentManager class was created which contained the List of each AggregateComponent. This solution works, however it is horrendously slow as each component has to be updated every time a particle is added so memory usage explodes - is there a way to adapt this where I can cache already rendered AggregateComponents without having to call their Update method each time a particle is added?
Full source code (C# code in the DLAProject directory) can be found on GitHub: https://github.com/SJR276/DLAProject
We create WPF 3D models for smallish point clouds (+/- 100 k points) where each point is added as an octahedron (8 triangles) to a MeshGeometry3D.
To allow different colors for different points (we use this for selecting one or a subset of points) in such a point cloud, we assign texture coordinates from a small Bitmap.
At a high level we have some code like this:
BitmapSource bm = GetColorsBitmap(new List<Color> { BaseColor, SelectedColor });
ImageBrush ib = new ImageBrush(bm)
{
ViewportUnits = BrushMappingMode.Absolute,
Viewport = new Rect(0, 0, 1, 1) // Matches the pixels in the bitmap.
};
GeometryModel3D model = new GeometryModel3D { Material = new DiffuseMaterial(ib) };
and now the texture coordinates are just
new Point(0, 0);
new Point(1, 0);
... etc.
The colors Bitmap comes from:
// Creates a bitmap that has a single row containing single pixels with the given colors.
// At most 256 colors.
public static BitmapSource GetColorsBitmap(IList<Color> colors)
{
if (colors == null) throw new ArgumentNullException("colors");
if (colors.Count > 256) throw new ArgumentOutOfRangeException("colors", "More than 256 colors");
int size = colors.Count;
for (int j = colors.Count; j < 256; j++)
{
colors.Add(Colors.White);
}
var palette = new BitmapPalette(colors);
byte[] pixels = new byte[size];
for (int i = 0; i < size; i++)
{
pixels[i] = (byte)i;
}
var bm = BitmapSource.Create(size, 1, 96, 96, PixelFormats.Indexed8, palette, pixels, 1 * size);
bm.Freeze();
return bm;
}
We also go to some effort to cache and reuse the internal Geometry structure when updating the point cloud.
Finally we display this with the awesome Helix Toolkit.

Color Point Mapping To Depth Point with Kinect v1 for XBOX

I'm working a project that about biometric measurement with kinect. I have a Kinect v1 for Xbox. I can get color image and depth image via this kinect. I want to mapping depth value of color image point. Kinect sdk doesnt support CoordinateMapper.MapColorPoint2DepthPoint or space.
All coordinate tranformation method is here.All Mapping Functions List
How can I mapping color point to depth point with kinect v1 for xbox.
Thanks your answers.
note : Language: C# , Platform: Windows Form
What you need is the CoordinateMapper.MapDepthFrameToColorFrame method.
The Coordinate Mapping Basics-WPF C# Sample shows how to use this method. You can find some significant parts of the code in the following:
// Intermediate storage for the depth data received from the sensor
private DepthImagePixel[] depthPixels;
// Intermediate storage for the color data received from the camera
private byte[] colorPixels;
// Intermediate storage for the depth to color mapping
private ColorImagePoint[] colorCoordinates;
// Inverse scaling factor between color and depth
private int colorToDepthDivisor;
// Format we will use for the depth stream
private const DepthImageFormat DepthFormat = DepthImageFormat.Resolution320x240Fps30;
// Format we will use for the color stream
private const ColorImageFormat ColorFormat = ColorImageFormat.RgbResolution640x480Fps30;
//...
// Initialization
this.colorCoordinates = new ColorImagePoint[this.sensor.DepthStream.FramePixelDataLength];
this.depthWidth = this.sensor.DepthStream.FrameWidth;
this.depthHeight = this.sensor.DepthStream.FrameHeight;
int colorWidth = this.sensor.ColorStream.FrameWidth;
int colorHeight = this.sensor.ColorStream.FrameHeight;
this.colorToDepthDivisor = colorWidth / this.depthWidth;
this.sensor.AllFramesReady += this.SensorAllFramesReady;
//...
private void SensorAllFramesReady(object sender, AllFramesReadyEventArgs e)
{
// in the middle of shutting down, so nothing to do
if (null == this.sensor)
{
return;
}
bool depthReceived = false;
bool colorReceived = false;
using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
{
if (null != depthFrame)
{
// Copy the pixel data from the image to a temporary array
depthFrame.CopyDepthImagePixelDataTo(this.depthPixels);
depthReceived = true;
}
}
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
if (null != colorFrame)
{
// Copy the pixel data from the image to a temporary array
colorFrame.CopyPixelDataTo(this.colorPixels);
colorReceived = true;
}
}
if (true == depthReceived)
{
this.sensor.CoordinateMapper.MapDepthFrameToColorFrame(
DepthFormat,
this.depthPixels,
ColorFormat,
this.colorCoordinates);
// ...
int depthIndex = x + (y * this.depthWidth);
DepthImagePixel depthPixel = this.depthPixels[depthIndex];
// scale color coordinates to depth resolution
int X = colorImagePoint.X / this.colorToDepthDivisor;
int Y = colorImagePoint.Y / this.colorToDepthDivisor;
// depthPixel is the depth for the (X,Y) pixel in the color frame
}
}
Thanks Vito your fast reply. I solved it Kinect sdk provide to me from depth frame to color frame. I find depth value of image point(x,y) from manuel inverse transaction of depth-color map.
This function return depth value of specific color image point.
public int GetDepthFromColorImagePoint(KinectSensor sensor, DepthImageFormat depthImageFormat, ColorImageFormat colorImageFormat, DepthImagePixel[] depthPixels, int depthWidth, int depthHeight, int colorImageX, int colorImageY)
{
ColorImagePoint[] color_points = new ColorImagePoint[depthHeight * depthWidth];
sensor.CoordinateMapper.MapDepthFrameToColorFrame(depthImageFormat, depthPixels, colorImageFormat, color_points);
int depthIndex = color_points.ToList().FindIndex(p => p.X == colorImageX && p.Y == colorImageY);
if (depthIndex < 0)
return -1;
short depthValue = depthPixels[depthIndex].Depth;
return depthValue;
}
if you see result of this function , view this gif

Matrix / coordinate transformation in C#

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.

Categories

Resources