// -----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// -----------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.Webserver.Sensor
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.Kinect;
using Microsoft.Kinect.Toolkit.BackgroundRemoval;
using Microsoft.Samples.Kinect.Webserver.Properties;
using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
///
/// Implementation of ISensorStreamHandler that exposes background removal streams.
///
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Disposable background removal stream is disposed when sensor is set to null")]
public class BackgroundRemovalStreamHandler : SensorStreamHandlerBase
{
///
/// JSON name of background removal stream.
///
internal const string BackgroundRemovalStreamName = "backgroundRemoval";
///
/// JSON name for property representing the tracking user id.
///
internal const string TrackingIdPropertyName = "trackingId";
///
/// JSON name for property representing the background removed color image resolution.
///
internal const string ResolutionPropertyName = "resolution";
///
/// Regular expression that matches the background removed frame resolution property.
///
private static readonly Regex BackgroundRemovalResolutionRegex = new Regex(@"^(?i)(\d+)x(\d+)$");
private static readonly KeyValuePair[] BackgroundRemovalResolutions =
{
new KeyValuePair(ColorImageFormat.RgbResolution640x480Fps30, new Size(640, 480)),
new KeyValuePair(ColorImageFormat.RgbResolution1280x960Fps12, new Size(1280, 960))
};
///
/// Context that allows this stream handler to communicate with its owner.
///
private readonly SensorStreamHandlerContext ownerContext;
///
/// Serializable background removal stream message, reused as background removed color frames arrive.
///
private readonly BackgroundRemovalStreamMessage backgroundRemovalStreamMessage = new BackgroundRemovalStreamMessage { stream = BackgroundRemovalStreamName };
///
/// Sensor providing data to background removal stream.
///
private KinectSensor sensor;
///
/// Entry point for background removal stream functionality.
///
private BackgroundRemovedColorStream backgroundRemovalStream;
///
/// Id of the user we choose to track.
///
private int trackingId;
///
/// True if background removal stream is enabled. Background removal stream is disabled by default.
///
private bool backgroundRemovalStreamIsEnabled;
///
/// The background removed color image format.
///
private ColorImageFormat colorImageFormat = ColorImageFormat.RgbResolution640x480Fps30;
///
/// Keep track if we're in the middle of processing a background removed color frame.
///
private bool isProcessingBackgroundRemovedFrame;
///
/// Initializes a new instance of the class
/// and associates it with a context that allows it to communicate with its owner.
///
///
/// An instance of class.
///
internal BackgroundRemovalStreamHandler(SensorStreamHandlerContext ownerContext)
{
this.ownerContext = ownerContext;
this.AddStreamConfiguration(BackgroundRemovalStreamName, new StreamConfiguration(this.GetProperties, this.SetProperty));
}
///
/// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
/// handler has changed.
///
///
/// New KinectSensor.
///
public override void OnSensorChanged(KinectSensor newSensor)
{
if (this.sensor != null)
{
try
{
this.backgroundRemovalStream.BackgroundRemovedFrameReady -= this.BackgroundRemovedFrameReadyAsync;
this.backgroundRemovalStream.Dispose();
this.backgroundRemovalStream = null;
this.sensor.ColorStream.Disable();
}
catch (InvalidOperationException)
{
// KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
// E.g.: sensor might be abruptly unplugged.
}
}
this.sensor = newSensor;
if (newSensor != null)
{
this.backgroundRemovalStream = new BackgroundRemovedColorStream(newSensor);
this.backgroundRemovalStream.BackgroundRemovedFrameReady += this.BackgroundRemovedFrameReadyAsync;
// Force enabling the background removal stream because it hasn't been enabled before.
this.UpdateBackgroundRemovalFrameFormat(this.colorImageFormat, true);
}
}
///
/// Process data from one Kinect color frame.
///
///
/// Kinect color data.
///
///
/// from which we obtained color data.
///
public override void ProcessColor(byte[] colorData, ColorImageFrame colorFrame)
{
if (colorData == null)
{
throw new ArgumentNullException("colorData");
}
if (colorFrame == null)
{
throw new ArgumentNullException("colorFrame");
}
if (this.backgroundRemovalStreamIsEnabled)
{
this.backgroundRemovalStream.ProcessColor(colorData, colorFrame.Timestamp);
}
}
///
/// Process data from one Kinect depth frame.
///
///
/// Kinect depth data.
///
///
/// from which we obtained depth data.
///
public override void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
{
if (depthData == null)
{
throw new ArgumentNullException("depthData");
}
if (depthFrame == null)
{
throw new ArgumentNullException("depthFrame");
}
if (this.backgroundRemovalStreamIsEnabled)
{
this.backgroundRemovalStream.ProcessDepth(depthData, depthFrame.Timestamp);
}
}
///
/// Process data from one Kinect skeleton frame.
///
///
/// Kinect skeleton data.
///
///
/// from which we obtained skeleton data.
///
public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
{
if (skeletons == null)
{
throw new ArgumentNullException("skeletons");
}
if (skeletonFrame == null)
{
throw new ArgumentNullException("skeletonFrame");
}
if (this.backgroundRemovalStreamIsEnabled)
{
this.backgroundRemovalStream.ProcessSkeleton(skeletons, skeletonFrame.Timestamp);
}
}
///
/// Event handler for BackgroundRemovedColorStream's BackgroundRemovedFrameReady event
///
/// object sending the event
/// event arguments
internal async void BackgroundRemovedFrameReadyAsync(object sender, BackgroundRemovedColorFrameReadyEventArgs e)
{
if (!this.backgroundRemovalStreamIsEnabled)
{
// Directly return if the stream is not enabled.
return;
}
if (this.isProcessingBackgroundRemovedFrame)
{
// Re-entered BackgroundRemovedFrameReadyAsync while a previous frame is already being processed.
// Just ignore new frames until the current one finishes processing.
return;
}
this.isProcessingBackgroundRemovedFrame = true;
try
{
bool haveFrameData = false;
using (var backgroundRemovedFrame = e.OpenBackgroundRemovedColorFrame())
{
if (backgroundRemovedFrame != null)
{
this.backgroundRemovalStreamMessage.UpdateBackgroundRemovedColorFrame(backgroundRemovedFrame);
haveFrameData = true;
}
}
if (haveFrameData)
{
await this.ownerContext.SendTwoPartStreamMessageAsync(this.backgroundRemovalStreamMessage, this.backgroundRemovalStreamMessage.Buffer);
}
}
finally
{
this.isProcessingBackgroundRemovedFrame = false;
}
}
///
/// Get the size for the given color image format.
///
/// The color image format.
/// The width and height of the given image.
private static Size GetColorImageSize(ColorImageFormat format)
{
try
{
var q = from item in BackgroundRemovalResolutions
where item.Key == format
select item.Value;
return q.Single();
}
catch (InvalidOperationException)
{
throw new ArgumentException(Resources.UnsupportedColorFormat, "format");
}
}
///
/// Get the color image format for the specified width and height.
///
///
/// Image width.
///
///
/// Image height.
///
///
/// The color image format enumeration value.
///
private static ColorImageFormat GetColorImageFormat(int width, int height)
{
try
{
var q = from item in BackgroundRemovalResolutions
where (int)item.Value.Width == width && (int)item.Value.Height == height
select item.Key;
return q.Single();
}
catch (InvalidOperationException)
{
throw new ArgumentException(Resources.UnsupportedColorFormat);
}
}
///
/// Set the background removed color frame format.
///
///
/// The given color image format.
///
///
/// Streams should be enabled even if new color image format is the same as the old one.
/// This is useful for the initial enabling of the stream.
///
private void UpdateBackgroundRemovalFrameFormat(ColorImageFormat format, bool forceEnable)
{
if (!forceEnable && (format == this.colorImageFormat))
{
// No work to do
return;
}
if (this.sensor != null)
{
try
{
this.sensor.ColorStream.Enable(format);
this.backgroundRemovalStream.Enable(format, DepthImageFormat.Resolution640x480Fps30);
}
catch (InvalidOperationException)
{
// KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
// E.g.: sensor might be abruptly unplugged.
}
}
// Update the image format property if the action succeeded.
this.colorImageFormat = format;
}
///
/// Gets a background removal stream property value.
///
///
/// Property name->value map where property values should be set.
///
private void GetProperties(Dictionary propertyMap)
{
propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.backgroundRemovalStreamIsEnabled);
propertyMap.Add(TrackingIdPropertyName, this.trackingId);
var size = GetColorImageSize(this.colorImageFormat);
propertyMap.Add(ResolutionPropertyName, string.Format(CultureInfo.InvariantCulture, @"{0}x{1}", (int)size.Width, (int)size.Height));
}
///
/// Set a background removal stream property value.
///
///
/// Name of property to set.
///
///
/// Property value to set.
///
///
/// null if property setting was successful, error message otherwise.
///
private string SetProperty(string propertyName, object propertyValue)
{
bool recognized = true;
if (propertyValue == null)
{
return Resources.PropertyValueInvalidFormat;
}
try
{
switch (propertyName)
{
case KinectRequestHandler.EnabledPropertyName:
this.backgroundRemovalStreamIsEnabled = (bool)propertyValue;
break;
case TrackingIdPropertyName:
{
var oldTrackingId = this.trackingId;
this.trackingId = (int)propertyValue;
if (this.trackingId != oldTrackingId)
{
this.backgroundRemovalStream.SetTrackedPlayer(this.trackingId);
}
}
break;
case ResolutionPropertyName:
var match = BackgroundRemovalResolutionRegex.Match((string)propertyValue);
if (!match.Success || (match.Groups.Count != 3))
{
return Resources.PropertyValueInvalidFormat;
}
int width = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
int height = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
try
{
var format = GetColorImageFormat(width, height);
this.UpdateBackgroundRemovalFrameFormat(format, false);
}
catch (ArgumentException)
{
return Resources.PropertyValueUnsupportedResolution;
}
break;
default:
recognized = false;
break;
}
if (!recognized)
{
return Resources.PropertyNameUnrecognized;
}
}
catch (InvalidCastException)
{
return Resources.PropertyValueInvalidFormat;
}
return null;
}
}
}