Kinect for Windows SDKで、アプリを挿抜に対応させる(F# + WPF)

KINECT SDK Advent Calendarのときに

KINECT SDK Beta2 で、アプリを挿抜に対応させる(F#+WPF+Rx) #kinectsdk_ac - pocketberserkerの爆走

という記事をかいたわけですが、Kinect for Windows SDKがリリースされたのにあわせて修正してみました。
よりF#っぽいコードにもしたつもりです。

必要なもの

XAML

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="577" Width="669">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="149*" />
            <RowDefinition Height="389*" />
        </Grid.RowDefinitions>
        <TextBox Name="kinectCount" Margin="0,0,0,97" FontSize="18" Text="Text" TextAlignment="Center" FontWeight="Bold" FontStretch="Normal" TextWrapping="NoWrap" VerticalContentAlignment="Center" />
        <Image Name="image1" Height="240" Width="320" Margin="0,50,332,248" Grid.RowSpan="2" Stretch="Uniform" />
        <Image Height="240" Margin="326,50,6,248" Name="image2" Width="320" Grid.RowSpan="2" Stretch="Uniform" />
        <Image Height="240" Margin="0,149,332,0" Name="image3" Width="320" Grid.Row="1" Stretch="Uniform" />
        <Image Height="240" Margin="326,149,6,0" Name="image4" Width="320" Grid.Row="1" Stretch="Uniform" />
    </Grid>
</Window>

前回からの変更点はありません。

コード

module FsSampleKinectApplication2

open System
open System.Threading
open System.Reactive.Linq
open System.Windows
open System.Windows.Controls
open System.Windows.Threading
open System.Windows.Media.Imaging
open Microsoft.Kinect
open Coding4Fun.Kinect.Wpf

let window =
  Application.LoadComponent(new System.Uri("/FsSampleKinectApplication2;component/MainWindow.xaml", System.UriKind.Relative)) :?> Window

let kinectCount = window.FindName "kinectCount" :?> TextBox

let findImage name = name |> window.FindName :?> Image

let image1 = "image1" |> findImage
let image2 = "image2" |> findImage
let image3 = "image3" |> findImage
let image4 = "image4" |> findImage

let images = [| image1;image2;image3;image4 |]
let mutable eventDictionary : (KinectSensor * IDisposable) list = []

let syncContext =
  if SynchronizationContext.Current = null then SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext())
  SynchronizationContext.Current

let showKinectCount () =
  kinectCount.Text <- (string KinectSensor.KinectSensors.Count) + "台のKINECTが有効です"

let createColorFrameReady (sensor:KinectSensor) =
  sensor.ColorFrameReady
  |> Observable.subscribe begin
    fun args ->
      KinectSensor.KinectSensors
      |> Seq.tryFindIndex ((=) sensor)
      |> Option.iter (fun index -> images.[index].Source <- args.OpenColorImageFrame().ToBitmapSource() )
  end

let initSensor (sensor:KinectSensor) =
  sensor.ColorStream.Enable()
  sensor.Start()
  eventDictionary <- (sensor, (sensor |> createColorFrameReady)) :: eventDictionary

let kinect_StatusChanged = syncContext |> KinectSensor.KinectSensors.StatusChanged.SubscribeOn

do kinect_StatusChanged
     .Subscribe begin
       fun (e:StatusChangedEventArgs) ->
         showKinectCount ()
         match e.Status with
         | KinectStatus.Connected -> e.Sensor |> initSensor
         | KinectStatus.Disconnected ->
           images
           |> Array.partition ( fun image -> images |> Array.findIndex ((=) image) >= KinectSensor.KinectSensors.Count)
           |> fst
           |> Array.iter (fun image -> image.Source <- null)
           eventDictionary
           |> List.find (fst >> ((=) e.Sensor))
           |> (fun (sensor,event) -> event.Dispose(); eventDictionary <- eventDictionary |> List.filter (fst >> ((<>) sensor)))
           e.Sensor.Stop()
         | _ -> ()
     end
   |> ignore

do window.Loaded
   |> Observable.subscribe (fun _ -> KinectSensor.KinectSensors |> Seq.iter initSensor )
   |> ignore

do window.Unloaded
   |> Observable.subscribe begin
       fun _ ->
         eventDictionary |> List.iter (fun (s,e) -> e.Dispose(); s.Stop() )
         eventDictionary <- []
     end
   |> ignore

[<STAThread>]
window |> (new Application()).Run |> ignore

Beta2の時には存在していたインスタンスのインデックスを取得できるRuntime.InstanceIndex相当のものが存在しない気がします。なので、ここはF#らしくSeq.tryFindIndexを使っておきました。通常ならKinectSensor.KinectSensors.IndexOfを使うのですかね?


ちなみに、今回は可能な限りif式やfor文を排除してみました。