Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added SkeletonTracking - a helper class for dealing with the Skeleton…

…s produced by the Kinect API. I would really appreciate a code review by anyone who knows Rx well to make sure I'm not doing something stupid somewhere.
  • Loading branch information...
commit 3dd1b11424d6460b2b1364d198cf54201e703390 1 parent 12ed366
@Aesir authored
View
2  ObservableKinect/ObservableKinect.csproj
@@ -63,6 +63,8 @@
<ItemGroup>
<Compile Include="KinectSensor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="SkeletonDispatcher.cs" />
+ <Compile Include="SkeletonTracking.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
View
89 ObservableKinect/SkeletonDispatcher.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Microsoft.Research.Kinect.Nui;
+
+namespace ObservableKinect
+{
+ /// <summary>
+ /// Takes an IObservable over a collection of SkeletonData and parses it to show new
+ /// skeletons and provide an IObservable for each skeleton tracked
+ /// </summary>
+ internal class SkeletonDispatcher
+ {
+ private readonly Dictionary<int, ISubject<SkeletonData>> myLivingingSkeletons = new Dictionary<int, ISubject<SkeletonData>>();
+ private readonly IDisposable mySkeletonSubscription;
+
+ [ContractInvariantMethod]
+ private void ObjectInvariant()
+ {
+ Contract.Invariant(myLivingingSkeletons != null);
+ Contract.Invariant(mySkeletonSubscription != null);
+ Contract.Invariant(_NewSkeletons != null);
+ }
+
+ public SkeletonDispatcher(IObservable<IEnumerable<SkeletonData>> skeletons)
+ {
+ Contract.Requires(skeletons != null);
+
+ mySkeletonSubscription =
+ skeletons
+ .Select(sds => sds.Where(sd => sd.TrackingState != SkeletonTrackingState.NotTracked))
+ .Subscribe(OnSkeletonsProduced);
+ }
+
+ private void OnSkeletonsProduced(IEnumerable<SkeletonData> skeletons)
+ {
+ Contract.Requires(skeletons != null);
+
+ var newSkeltons = new List<IObservable<SkeletonData>>();
+ var deadSkeletonIds = myLivingingSkeletons.Keys.ToList();
+
+ //Publish for living skeletons and create new living skeletons
+ foreach (var skeleton in skeletons)
+ {
+ ISubject<SkeletonData> skeletonSubject;
+ if (myLivingingSkeletons.TryGetValue(skeleton.TrackingID, out skeletonSubject))
+ {
+ deadSkeletonIds.Remove(skeleton.TrackingID);
+ }
+ else
+ {
+ skeletonSubject = new Subject<SkeletonData>();
+ myLivingingSkeletons.Add(skeleton.TrackingID, skeletonSubject);
+
+ newSkeltons.Add(skeletonSubject);
+ }
+ skeletonSubject.OnNext(skeleton);
+ }
+
+ //Send OnCompleted for any Skeletons in myLivingSkeletons that didn't get updated
+ foreach (var deadId in deadSkeletonIds)
+ {
+ var newlyDeadSkeleton = myLivingingSkeletons[deadId];
+ newlyDeadSkeleton.OnCompleted();
+ myLivingingSkeletons.Remove(deadId);
+ }
+
+ //Push out any new skeletons so people can subscribe to them
+ if (newSkeltons.Any())
+ {
+ _NewSkeletons.OnNext(newSkeltons);
+ }
+ }
+
+ public IObservable<IEnumerable<IObservable<SkeletonData>>> NewSkeletons
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<IObservable<IEnumerable<IObservable<SkeletonData>>>>() != null);
+
+ return _NewSkeletons;
+ }
+ }
+ private readonly ISubject<IEnumerable<IObservable<SkeletonData>>> _NewSkeletons = new Subject<IEnumerable<IObservable<SkeletonData>>>();
+ }
+}
View
112 ObservableKinect/SkeletonTracking.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Reactive.Linq;
+using Microsoft.Research.Kinect.Nui;
+
+namespace ObservableKinect
+{
+ /// <summary>
+ /// A helper that provides convenient IObservables for skeleton tracking
+ /// </summary>
+ public class SkeletonTracking
+ {
+ private readonly KinectSensor mySensor;
+ private readonly SkeletonDispatcher mySkeletonTracker;
+
+ [ContractInvariantMethod]
+ private void ObjectInvariant()
+ {
+ Contract.Invariant(mySensor != null);
+ Contract.Invariant(mySkeletonTracker != null);
+ Contract.Invariant(_SkeletonPresent != null);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SkeletonTracking"/> class using the default Kinect sensor.
+ /// </summary>
+ public SkeletonTracking()
+ : this(KinectSensor.Start(RuntimeOptions.UseSkeletalTracking))
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SkeletonTracking"/> class using the Kinect sensor at the specified index.
+ /// </summary>
+ /// <param name="sensorIndex">Index of the Kinect sensor to use.</param>
+ public SkeletonTracking(int sensorIndex)
+ : this(KinectSensor.Start(RuntimeOptions.UseSkeletalTracking, sensorIndex))
+ {
+ Contract.Requires(sensorIndex >= 0 && sensorIndex < Runtime.Kinects.Count);
+ }
+
+ private SkeletonTracking(KinectSensor sensor)
+ {
+ Contract.Requires(sensor != null);
+
+ mySensor = sensor;
+ var skeletonFrames =
+ mySensor
+ .SkeletonFrames
+ .Select(sf => sf.SkeletonFrame);
+
+ //skeletonFrames
+ // .Sample(TimeSpan.FromSeconds(5.0))
+ // .Subscribe(PrintSkeletonFrames);
+
+ _SkeletonPresent = skeletonFrames
+ .Select(sf => sf.Skeletons.Any(skel => skel.TrackingState != SkeletonTrackingState.PositionOnly))
+ .DistinctUntilChanged();
@Aesir Owner
Aesir added a note

I'm concerned because this won't produce a value for quite some time if someone connects to it after SkeletonPresent produces its first value. I have a question up on Stackoverflow (http://stackoverflow.com/questions/8460717/how-do-i-provide-the-latest-value-of-a-hot-observable-on-subscribe) to find out what I should add make it so the most recent value produced is sent to any subscribers when they connect. The obvious choice (to me) PublishLatest doesn't seem to allow any values to be produced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ mySkeletonTracker = new SkeletonDispatcher(skeletonFrames.Select(sf => sf.Skeletons));
+ }
+
+ /// <summary>
+ /// Gets an Observable that says whether at least one skeleton is present and being tracked by the Kinect sensor
+ /// </summary>
+ public IObservable<bool> SkeletonPresent
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<IObservable<bool>>() != null);
+
+ return _SkeletonPresent;
+ }
+ }
+ private readonly IObservable<bool> _SkeletonPresent;
+
+ /// <summary>
+ /// Gets an observable that pushes a new value whenever there is/are new skeleton(s) being tracked. The values
+ /// consist of an observable sequence for each new skeleton, allowing you to subscribe and track the skeleton.
+ /// </summary>
+ public IObservable<IEnumerable<IObservable<SkeletonData>>> FreshSkeletons
+ {
+ get
+ {
+ Contract.Ensures(Contract.Result<IObservable<IEnumerable<IObservable<SkeletonData>>>>() != null);
+
+ return mySkeletonTracker.NewSkeletons;
+ }
+ }
+
+ private static void PrintSkeletonFrames(SkeletonFrame frame)
+ {
+ Console.WriteLine("Frame Number: {0}", frame.FrameNumber);
+ Console.WriteLine("Quality: {0}", frame.Quality);
+ Console.WriteLine("TimeStamp: {0}", frame.TimeStamp);
+ foreach (var skeleton in frame.Skeletons.Where(skel => skel.TrackingState == SkeletonTrackingState.Tracked))
+ {
+ Console.WriteLine();
+ Console.WriteLine("\tUserIndex: {0}", skeleton.UserIndex);
+ Console.WriteLine("\tTracking Id: {0}", skeleton.TrackingID);
+ Console.WriteLine("\tTracking State: {0}", skeleton.TrackingState);
+ Console.WriteLine("\tQuality: {0}", skeleton.Quality);
+ Console.WriteLine("\tPosition: [W={0}, X={1}, Y={2}, Z={3}]", skeleton.Position.W, skeleton.Position.X, skeleton.Position.Y, skeleton.Position.Z);
+ }
+ Console.WriteLine();
+ Console.WriteLine("===============================================");
+ Console.WriteLine();
+ }
+ }
+}

1 comment on commit 3dd1b11

@Aesir
Owner

I put up a gist with a short example of how to use these changes here: https://gist.github.com/1458553

Please sign in to comment.
Something went wrong with that request. Please try again.