Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dotnet bump #63

33 changes: 33 additions & 0 deletions SandWorm/Components/BaseComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Diagnostics;
using Grasshopper.Kernel;

namespace SandWorm.Components
{
// Provides common functions across the various Sandworm components
public abstract class BaseComponent : GH_Component
{
public static List<string> output; // Debugging
protected Stopwatch timer;

// Pass the constructor parameters up to the main GH_Component abstract class
protected BaseComponent(string name, string nickname, string description, string subCategory)
: base(name, nickname, description, "Sandworm", subCategory)
{
}

// Components must implement a solve instance using SandwormSolveInstance()
protected abstract void SandwormSolveInstance(IGH_DataAccess DA);

protected override void SolveInstance(IGH_DataAccess DA)
{
SandwormSolveInstance(DA);
}

protected void SetupLogging()
{
timer = Stopwatch.StartNew(); // Setup timer used for debugging
output = new List<string>(); // For the debugging log lines
}
}
}
196 changes: 196 additions & 0 deletions SandWorm/Components/BaseKinectComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Grasshopper.Kernel;
using Microsoft.Kinect;
using Rhino;
using Rhino.Geometry;

namespace SandWorm.Components
{
// Provides common functions across the components that read from the Kinect stream
public abstract class BaseKinectComponent : BaseComponent
{
// Common Input Parameters
public int averageFrames = 1;
public int blurRadius = 1;
public int keepFrames = 1;
// Sandworm Options
public SetupOptions options; // List of options coming from the SetupComponent
public int topRows = 0;
public int rightColumns = 0;
public int bottomRows = 0;
public int leftColumns = 0;
public double sensorElevation = 1000; // Arbitrary default value (must be >0)
public double[] elevationArray;
public int tickRate = 33; // In ms
// Derived
protected Core.PixelSize depthPixelSize;
public static double unitsMultiplier;
protected KinectSensor kinectSensor;
protected Point3f[] allPoints;
protected int trimmedHeight;
protected int trimmedWidth;
protected readonly LinkedList<int[]> renderBuffer = new LinkedList<int[]>();
public int[] runningSum = Enumerable.Range(1, 217088).Select(i => new int()).ToArray();

public BaseKinectComponent(string name, string nickname, string description)
: base(name, nickname, description, "Kinect Visualisation")
{
}

protected void GetSandwormOptions(IGH_DataAccess DA, int optionsIndex, int framesIndex, int blurIndex)
{
// Loads standard options provided by the setup component
options = new SetupOptions();
DA.GetData<SetupOptions>(optionsIndex, ref options);

if (options.SensorElevation != 0) sensorElevation = options.SensorElevation;
if (options.LeftColumns != 0) leftColumns = options.LeftColumns;
if (options.RightColumns != 0) rightColumns = options.RightColumns;
if (options.TopRows != 0) topRows = options.TopRows;
if (options.BottomRows != 0) bottomRows = options.BottomRows;
if (options.TickRate != 0) tickRate = options.TickRate;
if (options.KeepFrames != 0) keepFrames = options.KeepFrames;
if (options.ElevationArray.Length != 0) elevationArray = options.ElevationArray;
else elevationArray = new double[0];

// Pick the correct multiplier based on the drawing units. Shouldn't be a class variable; gets 'stuck'.
unitsMultiplier = Core.ConvertDrawingUnits(RhinoDoc.ActiveDoc.ModelUnitSystem);
sensorElevation /= unitsMultiplier; // Standardise to mm to match sensor units

// Technically not provided by setup; but common to all Kinect-accessing components
if (framesIndex > 0)
{
DA.GetData(framesIndex, ref averageFrames);
// Make sure there is at least one frame in the render buffer
averageFrames = averageFrames < 1 ? 1 : averageFrames;
}
if (blurIndex > 0)
DA.GetData(blurIndex, ref blurRadius);

depthPixelSize = Core.GetDepthPixelSpacing(sensorElevation);
}

protected void SetupKinect()
{
if (kinectSensor == null)
{
KinectController.AddRef();
kinectSensor = KinectController.sensor;
}

if (KinectController.depthFrameData == null)
{
ShowComponentError("No depth frame data provided by the Kinect.");
return;
}

// Initialize all arrays
trimmedWidth = KinectController.depthWidth - leftColumns - rightColumns;
trimmedHeight = KinectController.depthHeight - topRows - bottomRows;
}

protected void SetupRenderBuffer(int[] depthFrameDataInt, Mesh quadMesh)
{
// Trim the depth array and cast ushort values to int
Core.CopyAsIntArray(KinectController.depthFrameData, depthFrameDataInt,
leftColumns, rightColumns, topRows, bottomRows,
KinectController.depthHeight, KinectController.depthWidth);

// Reset everything when resizing Kinect's field of view or changing the amounts of frame to average across
if (renderBuffer.Count > averageFrames || (quadMesh != null && quadMesh.Faces.Count != (trimmedWidth - 2) * (trimmedHeight - 2)))
{
renderBuffer.Clear();
Array.Clear(runningSum, 0, runningSum.Length);
renderBuffer.AddLast(depthFrameDataInt);
}
else
{
renderBuffer.AddLast(depthFrameDataInt);
}
}

protected void AverageAndBlurPixels(int[] depthFrameDataInt, ref double[] averagedDepthFrameData)
{
// Average across multiple frames
for (var pixel = 0; pixel < depthFrameDataInt.Length; pixel++)
{
if (depthFrameDataInt[pixel] > 200) // We have a valid pixel.
{
runningSum[pixel] += depthFrameDataInt[pixel];
}
else
{
if (pixel > 0) // Pixel is invalid and we have a neighbor to steal information from
{
runningSum[pixel] += depthFrameDataInt[pixel - 1];
// Replace the zero value from the depth array with the one from the neighboring pixel
renderBuffer.Last.Value[pixel] = depthFrameDataInt[pixel - 1];
}
else // Pixel is invalid and it is the first one in the list. (No neighbor on the left hand side, so we set it to the lowest point on the table)
{
runningSum[pixel] += (int)sensorElevation;
renderBuffer.Last.Value[pixel] = (int)sensorElevation;
}
}

averagedDepthFrameData[pixel] = runningSum[pixel] / renderBuffer.Count; // Calculate average values
if (elevationArray.Length > 0) averagedDepthFrameData[pixel] -= elevationArray[pixel]; // Correct for Kinect's inacurracies using input from the calibration component

if (renderBuffer.Count >= averageFrames)
runningSum[pixel] -= renderBuffer.First.Value[pixel]; // Subtract the oldest value from the sum
}

Core.LogTiming(ref output, timer, "Frames averaging"); // Debug Info

if (blurRadius > 1) // Apply gaussian blur
{
var gaussianBlurProcessor = new GaussianBlurProcessor(blurRadius, trimmedWidth, trimmedHeight);
gaussianBlurProcessor.Apply(averagedDepthFrameData);
Core.LogTiming(ref output, timer, "Gaussian blurring"); // Debug Info
}
}

protected void GeneratePointCloud(double[] averagedDepthFrameData)
{
double depthPoint;
// Setup variables for per-pixel loop
allPoints = new Point3f[trimmedWidth * trimmedHeight];
var tempPoint = new Point3f();
var arrayIndex = 0;
for (var rows = 0; rows < trimmedHeight; rows++)
for (var columns = 0; columns < trimmedWidth; columns++)
{
depthPoint = averagedDepthFrameData[arrayIndex];
tempPoint.X = (float)(columns * -unitsMultiplier * depthPixelSize.x);
tempPoint.Y = (float)(rows * -unitsMultiplier * depthPixelSize.y);
tempPoint.Z = (float)((depthPoint - sensorElevation) * -unitsMultiplier);
allPoints[arrayIndex] = tempPoint; // Add new point to point cloud itself
arrayIndex++;
}

// Keep only the desired amount of frames in the buffer
while (renderBuffer.Count >= averageFrames) renderBuffer.RemoveFirst();

Core.LogTiming(ref output, timer, "Point cloud generation"); // Debug Info
}

protected void ShowComponentError(string errorMessage)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, errorMessage);
ScheduleSolve(); // Ensure a future solve is scheduled despite an early return to SolveInstance()
}

protected void ScheduleDelegate(GH_Document doc)
{
ExpireSolution(false);
}

protected void ScheduleSolve()
{
if (tickRate > 0) // Allow users to force manual recalculation
OnPingDocument().ScheduleSolution(tickRate, ScheduleDelegate);
}
}
}
52 changes: 52 additions & 0 deletions SandWorm/Components/BaseMarkerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Grasshopper.Kernel;
using Microsoft.Kinect;
using Rhino;
using Rhino.Geometry;
using OpenCvSharp;

namespace SandWorm.Components
{
// Provides common functions across the components that classify markers within the Kinect stream
public abstract class BaseMarkerComponent : BaseKinectComponent
{
protected List<Color> markerColors;
protected double colorFuzz;
protected Color[] allPixels;

public BaseMarkerComponent(string name, string nickname, string description)
: base(name, nickname, description)
{
}

protected Mat GenerateColorImage()
{
if (KinectController.colorFrameData == null)
{
ShowComponentError("No color frame data provided by the Kinect.");
return null;
}

// Convert the kinect pixel byte array to a opencv mat file for processing
var height = KinectController.colorHeight;
var width = KinectController.colorWidth;
var mat = new Mat(height, width, MatType.CV_8UC4, KinectController.colorFrameData);
Core.LogTiming(ref output, timer, "Color image generation"); // Debug Info
return mat;
}

protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddColourParameter("MarkerColor", "Color", "The color, or colors, of the markers this component should track", GH_ParamAccess.list);
pManager.AddNumberParameter("ColorFuzz", "Fuzz", "The amount of leeway to use when matching the color. A higher value will identify more similar colors",
GH_ParamAccess.item);
pManager.AddGenericParameter("SandwormOptions", "SWO", "Setup & Calibration options", GH_ParamAccess.item);
pManager[0].Optional = false;
pManager[1].Optional = true;
pManager[2].Optional = true;
}
}
}
64 changes: 64 additions & 0 deletions SandWorm/Components/MarkerAreaComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Microsoft.Kinect;
using Rhino.Geometry;
using SandWorm.Components;

namespace SandWorm
{
public class MarkerAreaComponent : BaseMarkerComponent
{
private List<Curve> markerAreas;
public MarkerAreaComponent() : base("Sandworm Area Markers", "SW PMarks",
"Track color markers from the Kinect camera stream and output them as areas")
{
}

protected override Bitmap Icon => null;

public override Guid ComponentGuid => new Guid("41b279b6-643e-4d22-bc45-b47efa264ffb");

protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddCurveParameter("Marker Areas", "A", "The areas of different color; as a Grasshopper curve", GH_ParamAccess.list);
pManager.AddTextParameter("Output", "O", "Output", GH_ParamAccess.list); //debugging
}

protected override void SandwormSolveInstance(IGH_DataAccess DA)
{
SetupLogging();
markerColors = new List<Color>();
DA.GetDataList(0, markerColors);
DA.GetData(1, ref colorFuzz);
GetSandwormOptions(DA, 2, 0, 0);
SetupKinect();
Core.LogTiming(ref output, timer, "Initial setup"); // Debug Info

var binaryImage = GenerateColorImage();
Core.LogTiming(ref output, timer, "Image generation"); // Debug Info
if (binaryImage != null)
{

// Translate identified areas back into Grasshopper geometry
markerAreas = new List<Curve>();

// TODO: translation

DA.SetDataList(0, markerAreas);
}
else
{
// TODO: add warning?
}
binaryImage.Dispose();

Core.LogTiming(ref output, timer, "Image processing"); // Debug Info
DA.SetDataList(1, output); // For logging/debugging
ScheduleSolve();
}
}
}
Loading