Waarom F# prima met C# combineert

Op mijn huidige project moet ontzettend veel met getallen gegoocheld worden. De klant heeft bakken vol met berekeningen en formules om daar fantastische dingen mee te doen. Ik vind dit zeer intrigerend. Zo’n bedrijf bouwt al jaren zijn producten en heeft hierbij een hele afdeling voor softwareontwikkeling opgetuigd. Binnen het bedrijf komen en gaan de ontwikkeltools, de operating systemen, de nieuwste ideeën over software-architectuur maar er is één constante. Juist, die berekeningen?

Komt dit bekend voor?

Ik durf te stellen dat voor eigenlijk alle bedrijven hun formules (ik denk aan financiële instellingen zoals banken en verzekeraars en aan productiebedrijven met krachten, uitvloeiing van kunststof of draaicirkels) gerust als de kroonjuwelen kunne beschouwen. Iedere investering hierin om ze nog beter, sneller en betrouwbaarder te maken is een juiste keuze.

Dit betekent dus dat er een goed versiebeheer moet zijn voor de formules, een goede distributie van de gereedschapskisten waar ze inzitten (dll’s of services) en ik stel me voor dat er ook een complete geautomatiseerde teststraat (lees buildserver met minimaal unittests en een code coverage van 100 procent) voor draait. Waarom 100 procent terwijl dat een hele opgave is? De kosten tegen verkeerde resultaten uit formules wegen niet op tegen de kosten van de enorme stap van 80 procent naar 100 procent. Er kunnen bedrijven failliet gaan of zelfs mensenlevens op het spel staan.

Dus…

Formules schrijven lijkt simpel maar het kan al snel ingewikkeld worden J

Nu heb ik zelf regelmatig formules mogen implementeren en dan ga je aan de gang met static methodes om stateless te gaan rekenen en je wordt overweldigd met reeksen en complexe structuren om door te werken. En dat wordt dan dus het ijdele handwerk in C# code.

Maar de laatste weken heb ik mijn tijd doorgebracht met de PluralSight presentaties over F#. En ik vind het kijken van inhoudelijke filmpjes over een nieuw stuk technologie zowiezo heel nuttig (Het kost veel tijd om zoiets op te zetten en daarom rolt er meestal uiteindelijk echt de essentie van een onderwerp uit) maar ik ben nu heel enthousiast over F#.

F# is gewoon een extra ontwikkeltaal voor .Net en wat het speciaal maakt, is dat het een functionele taal is. Dit soort talen is juist geschikt voor zaken als recursiviteit, reeksen, enz. Alles dus waar je óf van houdt óf wat je haat 🙂

F# is vooral interessant omdat eigenlijk alles types immutable zijn. En hierdoor kan er tijdens parallelle uitvoering geen conflict optreden waarbij de ene thread een gezamenlijke waarde overschrijft.

Helaas heeft Microsoft voorheen F# vooral willen postioneren als een programmeertaal waarin je ook Winforms apps kunt maken en waarmee je ook een WPF app in elkaar kunt draaien. Persoonlijk had ik hier geen trek in, dat kon ik al met C# en ik zag niet het nut in om een nieuwe taal te leren om hetzelfde te kunnen doen wat ik al kon.

Maar F# is superieur als aanvulling op C# als het om berekeningen gaat. Kijk maar eens naar de volgende vier F# functies:


module Functions

let Func1 (x) = 2*x*x + 5*x + 3
let Func2 (x) = x*x + x
let Func3 (x) =  x*x*x - x*x + x
let Func4 (x) =  x*x*x*x + x*x*x - x*x + x

Dit is de hele inhoud van een F# file in een F# library project. En ja, dit zijn gewoon vier functies. Sterker nog, dit zijn vier methodes. Om dit te bewijzen, heb ik deze aangeroepen vanuit C#.

Het enige wat ik moest doen was een reference leggen vanuit mijn C# console app naar de F# library:

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("CSharp Host");

    for (int i = -5; i <= 5; i++)
    {
      Console.WriteLine("Result of Func1 {0} = {1}", i, Functions.Func1(i));
    }

    for (int i = -5; i <= 5; i++)
    {
      Console.WriteLine("Result of Func2 {0} = {1}", i, Functions.Func2(i));
    }

    for (int i = -5; i <= 5; i++)
    {
      Console.WriteLine("Result of Func3 {0} = {1}", i, Functions.Func3(i));
    }

    for (int i = -5; i <= 5; i++)
    {
      Console.WriteLine("Result of Func4 {0} = {1}", i, Functions.Func4(i));
    }

    Console.WriteLine("Press a key...");

    Console.ReadKey();
  }
}

De uitkomst is dan:
F#01
Dit kan natuurlijk ook met een unit test gecontroleerd worden. F# is tenslotte een volwaardige .Net taal dus MSUnit is al voldoende:

[TestClass]
public class UnitTest1
{
  [TestMethod]
  public void TestMethod1()
  {
    // ARRANGE
    var x = 5;
    var expected = 78;

    // ACT
    var actual = Functions.Func1(x);

    // ASSERT
    Assert.AreEqual<int>(expected, actual);
}

[TestMethod]
public void TestMethod2()
{
  // ARRANGE
  var x = 5;
  var expected = 30;

  // ACT
  var actual = Functions.Func2(x);

  // ASSERT
  Assert.AreEqual<int>(expected, actual);
}

[TestMethod]
public void TestMethod3()
{
  // ARRANGE
  var x = 5;
  var expected = 105;

  // ACT
  var actual = Functions.Func3(x);

  // ASSERT
  Assert.AreEqual<int>(expected, actual);
}

[TestMethod]
public void TestMethod4()
{
  // ARRANGE
  var x = 5;
  var expected = 730;

  // ACT
  var actual = Functions.Func4(x);

  // ASSERT
  Assert.AreEqual<int>(expected, actual);
}
}

Dit is wederom simpel en werkt direct:
F#02

En de code coverage is ook 100% 🙂

F#03

Zoals je ziet wordt alles als types ook goed ingevuld. F# kent dus geen variants maar alles wordt inferred, afgeleid. Dit maakt de functies compact en flexibel.

De F# functie blijkt met integers te willen omgaan:

F#04

 

En C# snapt dit:

 

F#05

 

En:

F#06

Dus doe eens gek en ga eens naar http://www.tryfsharp.org/ en speciaal naar http://www.tryfsharp.org/Learn/getting-started . Hier kun je online in de browser kennis maken met F#.

F#07

En kijk anders eens de hilarische video op Channel 9 van Luca Bolognese

Prima entertainment en je steekt er ook nog wat van op. Al is het maar om je behoefte te vervullen om jaarlijks een programmeertaal te leren.

Meer inhoudelijke informatie over F# is te vinden op: http://fsharp.org/

 

 

 

3D illusie met XBox 360 Kinect

Het is al weer een enige tijd geleden dat Johnny Chung Lee  opzien baarde door met een eenvoudige Wii Remote hele leuke toepassingen wist te verzinnen die Nintendo nog niet verkocht.

Voor diegene die het gemist heeft, er staat een leuke demonstratie op het web van zijn TED presentatie. Hierin wordt oa. een 3D illusie geïllustreerd door met een bril te bewegen die via infrarood de positie van het hoofd verraden. En die positie wordt door de remote opgevangen via de interne camera. Meer informatie is ook te vinden op zijn website.

Toen Microsoft de eerste Kinect los verkocht werd deze met een adapter geleverd om op een reguliere USB poort aan te sluiten en al snel werd ook de Kinect ‘gehacked’. De opnames van de Kinect worden niet versleuteld dus al snel waren er open-source bibliotheken om de ruwe informatie uit Kinect te kunnen benutten voor de PC. Oa. mijn gewaardeerde collega Jan Saris heeft hier heel wat uurtjes aan gesleuteld met leuke projecten tot gevolg.

Ik heb destijds ook met verbazing gekeken. Ik vond vooral de ‘stadion’ illusie erg leuk. Sindsdien heb ik dit altijd nog eens over willen doen met de Kinect. Deze kan dus de positie van meerdere personen bepalen en dus ook van ook die van een hoofd. En je hoeft geen brilletje op te zetten.

Nu is dit al vaker gedaan :-). Zo is de  Kinect gecombineerd met 3D TV.

Wellicht was het geldingsdrang maar ook ik wilde dit nog eens uitzoeken. De afgelopen week heb ik er eindelijk vaart achter gezet en tada, hier is het resultaat.

Kinect SDK plus Kinect Toolkit

Om met de Kinect te kunnen knutselen moet je beschikken over Visual Studio en je hebt Kinect for Windows SDK v1.6 nodig. Volgens de EULA mag dit alleen i.c.m. de “Kinect for PC” (een duurder broertje met een verbeterd blikveld) toegepast worden. Het is niet toegestaan de Xbox360 Kinect te promoten bij je eigen software. Gelukkig  mag je wel testen met de Xbox360 Kinect en dat gaan we hier dus doen…

Ook is het handig om dan direct ook de Kinect for Windows Developer Toolkit v1.6 te downloaden. Dit is echt goud! Hierin zitten voorbeelden hoe je de Kinect kunt gebruiken voor spraak besturing (inclusief richtinggevoeligheid) of hoe je door een applicatie kunt navigeren met die typische Kinect handbesturing.

En natuurlijk zijn er in de Kinect ook voorbeelden van applicaties die skelet tracking uitvoeren, zoals de Skeletons basic –WPF app.

Deze laatste WPF applicatie gaan wij ombouwen.

Wat is het idee? Ik toon een mooi vergezicht overmaats op het scherm, dit geeft de illusie als dat je door een raam kijkt. Als je naar links loopt, dan gaat de foto ook naar links, en naar rechts als naar rechts loopt. Hierdoor is het net alsof je om de hoek kijkt van een raam. Of zo J. En dat kan herhaald worden door boven, onder, dichterbij en verder weg.

Absoluut werd relatief

Ik heb enkele manieren onderzocht en bleef een beetje steken in een ViewModel. Ik wilde graag MVVM toepassen. Nu kan ik de absolute posities achterhalen en die doorgeven naar de X, Y, Z en zoom factor maar dit wordt erg schokkerig. Ik kreeg het niet voor elkaar om een storyboard hier aan te koppelen. Een trigger gaat pas af als een bepaalde waarde gehaald is, niet als een waarde verandert?

Dus ik heb geen storyboard toegepast. Wel heb ik de positiedoorgifte van een gevoeligheid voorzien. Als een gemeten positie  flink afwijkt vanaf een vorige geaccepteerde positie dan wordt de foto verschoven en dan wordt dit de nieuwe te onthouden positie.

Daarom heb ik voor de verschillende richtingen in totaal zes storyboard gemaakt die vanuit de code behind worden aangeroepen. En ik heb een extra code-behind geschreven om de foto op de start positie te brengen.

De Xaml ziet er dan als volgt uit; zeven storyboard en een image op een grid:

<Window x:Class="MainWindow"
  xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
  xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
  Title="PictureZoom"
  Loaded="WindowLoaded"
  Closing="WindowClosing"
  WindowState="Maximized"
  WindowStyle="None"
  WindowStartupLocation="CenterScreen"
  mc:Ignorable="d"
  xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008">http://schemas.microsoft.com/expression/blend/2008</a>"
  xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006">http://schemas.openxmlformats.org/markup-compatibility/2006</a>"
  d:DesignHeight="710"
  d:DesignWidth="1300"
  SizeToContent="WidthAndHeight">
<Window.Resources>
      <Storyboard x:Key="Start">
        <DoubleAnimation Storyboard.TargetName="Scale"
                         Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
                         To="1.6"
                         Duration="0:0:0.0" />
        <DoubleAnimation Storyboard.TargetName="Scale"
                         Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
                         To="1.6"
                         Duration="0:0:0.0" />
      </Storyboard>
      <Storyboard x:Key="ZoomIn">
        <DoubleAnimation Storyboard.TargetName="Scale"
                         Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
                         By="0.010"
                         Duration="0:0:0.20" />
        <DoubleAnimation Storyboard.TargetName="Scale"
                         Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
                         By="0.010"
                         Duration="0:0:0.20">
          <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
          </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
      </Storyboard>
      <Storyboard x:Key="ZoomOut">
        <DoubleAnimation Storyboard.TargetName="Scale"
                         Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
                         By="-0.010"
                         Duration="0:0:0.25" />
        <DoubleAnimation Storyboard.TargetName="Scale"
                         Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
                         By="-0.010"
                         Duration="0:0:0.25">
          <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
          </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
      </Storyboard>
      <Storyboard x:Key="PanLeft">
        <DoubleAnimation Storyboard.TargetName="Pan"
                         Storyboard.TargetProperty="(TranslateTransform.X)"
                         By="-10"
                         Duration="0:0:0.25">
          <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
          </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
      </Storyboard>
      <Storyboard x:Key="PanRight">
        <DoubleAnimation Storyboard.TargetName="Pan"
                         Storyboard.TargetProperty="(TranslateTransform.X)"
                         By="10"
                         Duration="0:0:0.25">
          <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
          </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
      </Storyboard>
      <Storyboard x:Key="PanUp">
        <DoubleAnimation Storyboard.TargetName="Pan"
                         Storyboard.TargetProperty="(TranslateTransform.Y)"
                         By="-10"
                         Duration="0:0:0.25">
          <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
          </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
      </Storyboard>
      <Storyboard x:Key="PanDown">
        <DoubleAnimation Storyboard.TargetName="Pan"
                         Storyboard.TargetProperty="(TranslateTransform.Y)"
                         By="10"
                         Duration="0:0:0.25">
          <DoubleAnimation.EasingFunction>
            <SineEase EasingMode="EaseInOut" />
          </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
      </Storyboard>
    </Window.Resources>
  <Grid>
    <Border Grid.Row="1"
            Name="border"
            ClipToBounds="True">
      <Image Name="PictureToScale"
             RenderTransformOrigin="0.5, 0.5"
             Source="XYZ.JPG">
        <Image.RenderTransform>
          <TransformGroup>
            <ScaleTransform x:Name="Scale"
                            ScaleX="1"
                            ScaleY="1" />
            <TranslateTransform x:Name="Pan" />
          </TransformGroup>
        </Image.RenderTransform>
      </Image>
    </Border>
  </Grid>
</Window>

De code-behind is een afgeslankte variant op het demo project van Microsoft. Ik onthoud de laatst positie gemeten positie en hou referenties vast naar de storyboards. De precisie en de Kinect Sensor blijven onveranderd.

Kinect3d

Bij het starten van de applicatie laat ik de Kinect initialiseren en reageer ik alleen op de positie van de nek. Want het hoofd kan ook schuin gehouden worden en dat wil ik negeren. De magie zit in het “SensorSkeletonFrameReady” event waarbij de positie van dit nekgewricht wordt gemeten.

De code behind is dus:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
  private Storyboard storyboardZoomIn;
  private Storyboard storyboardZoomOut;
  private Storyboard storyboardPanLeft;
  private Storyboard storyboardPanRight;
  private Storyboard storyboardPanUp;
  private Storyboard storyboardPanDown;

  private KinectSensor sensor;

  // Kinect precision: less is more responsive
  private double _precision = 0.025;

  private double _lastPositionX;
  private double _lastPositionY;
  private double _lastPositionZ;

  /// <summary>
  /// Initializes a new instance of the MainWindow class.
  /// </summary>
  public MainWindow()
  {
    InitializeComponent();

    // wait some time to get to the starting position...
    Thread.Sleep(4000);

    Mouse.OverrideCursor = Cursors.None;

    Storyboard storyboardStart = (Storyboard)TryFindResource("Start");
    storyboardStart.Begin(this);

    storyboardZoomIn = (Storyboard)TryFindResource("ZoomIn");
    storyboardZoomOut = (Storyboard)TryFindResource("ZoomOut");
    storyboardPanLeft = (Storyboard)TryFindResource("PanLeft");
    storyboardPanRight = (Storyboard)TryFindResource("PanRight");
    storyboardPanUp = (Storyboard)TryFindResource("PanUp");
    storyboardPanDown = (Storyboard)TryFindResource("PanDown");
  }

  /// <summary>
  /// Execute startup tasks
  /// </summary>
  /// <param name="sender">object sending the event</param>
  /// <param name="e">event arguments</param>
  private void WindowLoaded(object sender, RoutedEventArgs e)
  {
    // Look through all sensors and start the first connected one.
    // This requires that a Kinect is connected at the time of app startup.
    // To make your app robust against plug/unplug,
    // it is recommended to use KinectSensorChooser provided in Microsoft.Kinect.Toolkit
    foreach (var potentialSensor in KinectSensor.KinectSensors)
    {
      if (potentialSensor.Status == KinectStatus.Connected)
      {
        this.sensor = potentialSensor;
        break;
      }
    }

    if (null != this.sensor)
    {
      // Turn on the skeleton stream to receive skeleton frames
      this.sensor.SkeletonStream.Enable();

      // Add an event handler to be called whenever there is new color frame data
      this.sensor.SkeletonFrameReady += this.SensorSkeletonFrameReady;

      // Start the sensor!
      try
      {
        this.sensor.Start();
      }
      catch (IOException)
      {
        this.sensor = null;
      }
    }
  }

  /// <summary>
  /// Execute shutdown tasks
  /// </summary>
  /// <param name="sender">object sending the event</param>
  /// <param name="e">event arguments</param>
  private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
  {
    if (null != this.sensor)
    {
      this.sensor.Stop();
    }
  }

  /// <summary>
  /// Event handler for Kinect sensor's SkeletonFrameReady event
  /// </summary>
  /// <param name="sender">object sending the event</param>
  /// <param name="e">event arguments</param>
  private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
  {
    Skeleton[] skeletons = new Skeleton[0];

    using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
    {
      if (skeletonFrame != null)
      {
        skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
        skeletonFrame.CopySkeletonDataTo(skeletons);
      }
    }

    if (skeletons.Length != 0)
    {
      foreach (Skeleton skel in skeletons)
      {
        if (skel.TrackingState == SkeletonTrackingState.Tracked)
        {
          // Otherwise use JointType.ShoulderCenter
          this.MoveImage(skel, JointType.Head);

          break;
        }
      }
    }
  }

  private bool PersonMovedLeftRight(SkeletonPoint skeletonPoint)
  {
    return (Math.Abs(skeletonPoint.X - _lastPositionX) > _precision);
  }

  private bool PersonMovedUpDown(SkeletonPoint skeletonPoint)
  {
    return (Math.Abs(skeletonPoint.Y - _lastPositionY) > (_precision));
  }

  private bool PersonMovedInOut(SkeletonPoint skeletonPoint)
  {
    return (Math.Abs(skeletonPoint.Z - _lastPositionZ) > _precision);
  }

  /// <summary>
  /// Move the image
  /// </summary>
  /// <param name="skeleton">skeleton to draw bones from</param>
  /// <param name="jointType">joint to start drawing from</param>
  private void MoveImage(Skeleton skeleton, JointType jointType)
  {
    Joint joint = skeleton.Joints[jointType];

    if (joint.TrackingState == JointTrackingState.NotTracked
          || joint.TrackingState == JointTrackingState.Inferred)
    {
      return;
    }

    if ((_lastPositionX == 0)
          && (_lastPositionY == 0)
          && (_lastPositionZ == 0))
    {
      _lastPositionX = joint.Position.X;
      _lastPositionY = joint.Position.Y;
      _lastPositionZ = joint.Position.Z;
    }

    if (PersonMovedLeftRight(joint.Position))
    {
      if (joint.Position.X < _lastPositionX)
      {
        //left
        storyboardPanLeft.Begin(this);
      }
      else
      {
        if (joint.Position.X > _lastPositionX)
        {
          //right
          storyboardPanRight.Begin(this);
        }
      }

      _lastPositionX = joint.Position.X;
    }

    if (PersonMovedUpDown(joint.Position))
    {
      if (joint.Position.Y < _lastPositionY)
      {
        //down
        storyboardPanDown.Begin(this);
      }
      else
      {
        if (joint.Position.Y > _lastPositionY)
        {
          //up
          storyboardPanUp.Begin(this);
        }
      }

      _lastPositionY = joint.Position.Y;
    }

    if (PersonMovedInOut(joint.Position))
    {
      if (joint.Position.Z < _lastPositionZ)
      {
        //in
        storyboardZoomOut.Begin(this);
      }
      else
      {
        if (joint.Position.Z > _lastPositionZ)
        {
          //out
          storyboardZoomIn.Begin(this);
        }
      }

      _lastPositionZ = joint.Position.Z;
    }
  }
}

Is dit voldoende voor een 3D illusie? Bekijk het op http://youtu.be/5dupe4_UIQo en http://youtu.be/nMk5oSMXZmI .

Ik vind het zelf een heel aardig effect hebben. Maar er valt wel wat op aan te merken. Er gaat na enige tijd een afwijking optreden in het aansturen met relatieve stappen. Een stuk naar links bewegen en een stuk naar rechts bewegen wilt niet zeggen dat de foto weer op het zelfde punt terug is. Hoewel de gevoeligheid fantastisch is (je hoofd/nek een paar centimeter bewegen wordt al geregistreerd) kan ik het verloop niet goed compenseren. Ik ken zelfs over de rand van de foto ‘vallen’ en dan is de illusie in één keer verdwenen. Daarom heb ik ook een sleep van een paar seconden er in staan. Dit geeft mij de tijd om in het midden van de kamer te gaan staan.

Maar zoals ik al gezegd heb, het is een leuke illusie en het kostte alleen wat prutsen in de avond. En mijn zonen vinden het ook gaaf, prima toch?