How to implement a paint control in WPF

Fertiges PaintControl
Fertiges PaintControl

Wenn man den Pattern Recognizer mal genauer unter die Lupe nimmt stellt man fest, dass die Pixel durch einfache Check-Boxes dargestellt werden, die ledeglich ein bisschen anderes Aussehen verpasst bekommen haben. Das mag in dem Fall gehen, ist generell aber nicht so toll, weil man jedes Pixel einzeln anklicken muss. Auch wollte ich bei meiner RBM nicht noch mal so was machen, und hatte auch keine Lust eine riesen XAML Datei mit einem Grid mit 28 und mehr Spalten und Zeilen zu verwenden. Also muss ein Steuerelement her, was einfach so benutzt werden kann, mit einer Codezeile, Weite und Höhe angeben… Deshalb soll es jetzt mal darum gehen ein Control zu entwickeln, in dem man Zahlen, Formeln, … also Muster eben, einzeichnen kann, so ähnlich wie in Paint. Das von .NET mitgelieferte InkCanvas scheidet hier aus dem ganz einfachen Grund aus, dass es die Muster als Striche erkennt, die  neuronalen Netze brauchen zur Analyse aber die Pixel-Daten.

Also brauchen wir zuerst ein einfaches UserControl, das  PaintControl. Das bekommt jetzt als Content einfach ein durchsichtiges Rechteck, damit das was später noch draufgemalt werden soll auch schön sichtbar bleibt. Kein Content geht nicht, weil sonst an den leeren Stellen keine Click-Events und derartiges greifen.

Dann überschreiben wir in der  Code – Behind Datei die Methode OnRender, hier werden dann später die  einzelnen Punkte des Musters gezeichnet.

Jetzt sind noch ein paar Dependency Properties nötig, nämlich die Anzahl der logischen Pixel(Weite und Höhe), die nicht unbedingt mit den absoluten übereinstimmen müssen. Das heißt ein Punkt in meinem  Muster kann mehreren Pixeln in Wirklichkeit entsprechen. Als letztes brauchen wir noch die Pixel – Daten als bool[,] mit Index Weite und Höhe, und natürlich den Handler für das Mouse Move Event, weil ja gezeichnet werden soll, wenn der beutzer die Maus bewegt.

Hier nochmal alle Vorbereitungen auf einen Blick:

XAML Code von PaintControl.xaml(sehr simpel):

<UserControl x:Class="RBM.UIExtentions.PaintControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="400" MouseMove="PaintControl_OnPaint" MouseDown="PaintControl_OnPaint">
    <Rectangle Fill="Transparent"/>
</UserControl>
using
System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace UIExtentions
{
    /// <summary>
    /// Interaction logic for PaintControl.xaml
    /// </summary>
    public partial class PaintControl : UserControl
    {
        public PaintControl()
        {
            InitializeComponent();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
        }

        public int PixelWidth
        {
            get { return (int)GetValue(PixelWidthProperty); }
            set { SetValue(PixelWidthProperty, value); }
        }

        public static readonly DependencyProperty PixelWidthProperty =
            DependencyProperty.Register("PixelWidth", typeof(int), typeof(PaintControl));

        public int PixelHeight
        {
            get { return (int)GetValue(PixelHeightProperty); }
            set { SetValue(PixelHeightProperty, value); }
        }

        public static readonly DependencyProperty PixelHeightProperty =
            DependencyProperty.Register("PixelHeight", typeof(int), typeof(PaintControl));

        public bool[,] Data
        {
            get { return (bool[,])GetValue(DataProperty); }
            private set { this.SetValue(DataPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey DataPropertyKey =
            DependencyProperty.RegisterReadOnly("Data", typeof(bool[,]), typeof(PaintControl), new PropertyMetadata());

        public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty;

        public void Clear()
        {
            this.Data = new bool[this.PixelWidth, this.PixelHeight];
            this.InvalidateVisual();
        }

        private void PaintControl_MouseMove(object sender, MouseEventArgs e)
        {
        }
    }
}

C# Code von PaintControl.xaml.cs

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RBM.UIExtentions
{
    /// <summary>
    /// Interaction logic for PaintControl.xaml
    /// </summary>
    public partial class PaintControl : UserControl
    {
        public PaintControl()
        {
            InitializeComponent();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
        }

        public int PixelWidth
        {
            get { return (int)GetValue(PixelWidthProperty); }
            set { SetValue(PixelWidthProperty, value); }
        }

        public static readonly DependencyProperty PixelWidthProperty =
            DependencyProperty.Register("PixelWidth", typeof(int), typeof(PaintControl));

        public int PixelHeight
        {
            get { return (int)GetValue(PixelHeightProperty); }
            set { SetValue(PixelHeightProperty, value); }
        }

        public static readonly DependencyProperty PixelHeightProperty =
            DependencyProperty.Register("PixelHeight", typeof(int), typeof(PaintControl));

        public bool[,] Data
        {
            get { return (bool[,])GetValue(DataProperty); }
            private set { this.SetValue(DataPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey DataPropertyKey =
            DependencyProperty.RegisterReadOnly("Data", typeof(bool[,]), typeof(PaintControl), new PropertyMetadata());

        public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty;

        // Methode zum Zurücksetzen der Pixel Daten, manchmal ganz nützlich
        public void Clear()
        {
            this.Data = new bool[this.PixelWidth, this.PixelHeight];
            this.InvalidateVisual();
        }

        private void PaintControl_OnPaint(object sender, MouseEventArgs e)
        {
        }
    }
}

Die OnPaint Methode wird immer aufgerufen, wenn an der aktuellen Mausposition gezeichnet werden soll, vorrausgesetzt die Maustaste ist gedrückt, was dann noch überüprüft werden muss. Die Implementierung sieht also so aus(mit einer kleinen Hilfsmethode zum Umrechnen von absoluten und logischen Pixeln)

private void PaintControl_OnPaint(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed)
    {
        Point logicalCoord = this.GetLogicalCoordinates(e.GetPosition(this));
        if (logicalCoord.X < this.PixelWidth && logicalCoord.Y < this.PixelHeight)
        {
            this.Data[(int)logicalCoord.X, (int)logicalCoord.Y] = true;
            this.InvalidateVisual();
        }
    }
}

private Point GetLogicalCoordinates(Point mousePosition)
{
    return new Point(mousePosition.X / (this.ActualWidth / this.PixelWidth), mousePosition.Y / (this.ActualHeight / this.PixelHeight));
}

Jetzt noch in der Render Methode die Pixel hinzeichnen:

protected override void OnRender(DrawingContext drawingContext)
{
    if (this.Data == null || this.Data.Length != this.PixelWidth * this.PixelHeight)
    {
        this.Data = new bool[this.PixelWidth, this.PixelHeight];
    }

    SolidColorBrush brush = new SolidColorBrush(Colors.Black);

    for (int y = 0; y < this.PixelHeight; y++)
    {
        for (int x = 0; x < this.PixelWidth; x++)
        {
            if (this.Data[x, y])
            {
                Rect rect = new Rect(((double)x / (double)this.PixelWidth) * this.ActualWidth, ((double)y / (double)this.PixelHeight) * this.ActualHeight,
                    this.ActualWidth / this.PixelWidth, this.ActualHeight / this.PixelHeight);

                GuidelineSet guidelines = new GuidelineSet();
                guidelines.GuidelinesX.Add(rect.Left);
                guidelines.GuidelinesX.Add(rect.Right);
                guidelines.GuidelinesY.Add(rect.Top);
                guidelines.GuidelinesY.Add(rect.Bottom);

                drawingContext.PushGuidelineSet(guidelines);
                drawingContext.DrawRectangle(brush, null, rect);
                drawingContext.Pop();
            }
        }
    }
}

OK, das wars das Paint Control ist soweit fertig, ich hab es ja im vorigen Artikel auch schon eingesetzt.

Restricted Boltzmann Machine – einfacher Test

Die neue Generation der neuronalen Netzwerke ist angebrochen!
Ich hab mal versucht meine Infrastruktur auf die neuronalen Netze von hier anzuwenden. Natürlich erst mal mit dem ganz einfachen Beispiel am Anfang probiert. Meine Anwendung sieht jetzt so aus:

Dazu hab ich mir noch ein kleines Control geschrieben, was die Pixeleingabe ermöglicht. Man kann also jetzt ein Muster eingeben:

und dann mit Train trainieren. Ich hab das mal mit ein Paar Einsen ausprobiert, und jetzt kommt eine Eins, die er noch nie gesehen hat:

Ok, zugegeben ich bin nicht besonders gut im Einsen malen, aber er macht die noch schön gerade, und verändert sie dabei fast nicht:

Und dabei hab ich ihn nur mit handgemalten(hässlichen) Daten, und auch nicht mit besonders vielen trainiert. Find ich genial, vor allen Dingen, weil das ja eigentlich noch gar nicht fertig ist, es kommen ja noch sehr viele Schichten dazu(also höhere Genauigkeit). Ich werden demnächst mal meine Anwendung mit diesen Beispielen erweitern. Vorher sind aber erstmal die  nächsten Schichten angesagt.

Jabber Update – Part 3

Jabber der dritte. Wir haben uns letztes Mal Gedanken darüber gemacht, wie der Client funktioniert, jetzt ist der Service dran. Zunächst wird eine Datenbank gebraucht, in der alle verfügbaren Updates gelistet werden. Dazu kommt eine Anwendung, die der Datenbank mit Schlüssel und dem genauen Verzeichnis auf der lokalen Platte, wo sich die Dateien für die entsprechende Installation befinden einen Eintrag hinzufügt. Die Anwendung wird, wenn sich irgendwas geändert hat einfach nochmal aufgerufen, und dann gibt der Server beim nächsten Update die neuen Infos zurück.

Dann gibt es einfach eine kleine Konsolenanwendung, die den Server startet(mittels AppSpace). Jetzt passiert der erste Schritt vom Update:

  • Step 1 – Benutzer klickt auf Update

Ok, der Benutzer will also seine Anwendung aktualisieren, der UpdateManager geht ins Netz, sucht sich den Service und schickt ein Telegramm mit den Informationen über die Anwendung, die gerade läuft. Der Service sucht nach dem Schlüssel in der Datenbank, und gibt die aktuelle Version der Anwendung an den Client zurück.

  • Step 2 – Updatebeginn

Der Client empfängt die Antwort, wenn die Version vom Service neuer ist als die eigene, wird ein Dialog angezeigt, und der Benutzer kann sich entscheiden, ob er das Update installieren möchte. Wenn das so ist, wird sofort ein weiteres Telegramm an den Service geschickt, mit der Bitte das Update zu starten. Sobald der Service diese Information erhält startet er intern eine Klasse mit einer eindutigen ID, die beginnt die Dateien auszulesen, und zunächst ein Telegramm zum erstellen neuer Dateien an den Client zurücksendet, was die ID enthält.

  • Step 3 – Update

Der Client erstellt die Dateien und schickt die ID wieder zurück. Dann werden auf dem Service nach und nach alle Bytes der Dateien gelesen und verschickt(immer 50kB in einer Nachricht). Zwischen dem Versenden dieser Nutzdatentelegramme wird zwischendurch immer wieder auf die Antwort des Clients gewartet, der die Dateien nach und nach auf die Platte schreibt und immer ein OK mit ID zurückliefert, was verhindert, dass die Nachrichten in der verkehrten Reihenfolge eintreffen, aber vor allem dafür sorgt, dass die Leitung nicht überfüllt wird, und der Client erst gar nichts erhält, und dann alles auf einmal. Denn wenn beispielsweise der Service alle Telegramme sofort mit einer Reihenfolgenummer losschicken würde, müssten alle Bytes gleichzeitig durch die Leitung, was bei größeren Dateien also mehreren Megabyte bis Gigabyte entsprechen würde. Es gäbe dann wahrscheinlich irgendwann ein Timeout, aber auf jeden Fall würde der Client zum Schluss mit einer Flut aus Nachrichten überschwemmt werden, die er gar nicht alle auf einmal bearbeiten kann. Ein Abbruch des Updates seitens des Clients wäre dann auch nicht möglich.

  • Step 4 – Ready

Der Server vermeldet die Fertigstellung des Updates nach dem letzten erfolgreich versandten Bytepacket. Dann startet der Client die Installer-Datei und die Installation kann beginnen!

Hier nochmal die Kommunikation im Detail:

Jabber Update – Part 2

Nochmal zurück zu Jabber und Updates. Wir wollen jetzt einen Schritt weiter gehen, von der grauen Theorie weg jetzt einfach mal eine Bibliothek implementieren, die die Update Funktion kapselt. Dazu brauchen wir zuerst ein API, was die Anwendung intern aufruft, um das Update durchzuführen. Wir beschäftigen uns also erst einmal mit der Client-Seite der ganzen Geschichte, und gehen jetzt mal davon aus, dass die Dateien schon auf dem Server registriert sind, und einfach als Bytes verschickt werden können, wenn der Client sie mit den Informationen zur Anwendung(Anwendungsname), über die diese identifiziert werden anfordert. Wie dann konkret der Service aussieht, darum kümmern wir uns später. Also erstellen wir doch einfach mal eine Klasse AppUpdateManager mit den Methoden BeginUpdate() und EndUpdate(). Wie man sicherlich schnell festellt handelt es sich hier um eine asynchrone Ausführung, logisch, weil ich als Anwender keine Lust hab die ganze Zeit eine blockierende Anwendung anzuschauen, während das Update läuft, und untätig rumzusitzen. So kann die Anwendung weiter verwendet werden, und das Update läuft einfach im Hintergrund ab. Wenn der Benutzer die Anwendung beendet kommt dann eben eine Message-Box, und fragt ihn, ob er das Update, wenn es noch nicht fertig ist, wirklich abbrechen will. Nur Pech, wenn die Anwendung abstürtzt ;-) Der UpdateManager kommuniziert dann über Jabber mit dem Server und lädt sich das Update in das aktuelle Temp Verzeichnis(vom aktuell angemeldeten User) runter. Dann startet er die MSI Datei(wieder asynchron, um dann noch sofort anschließend bevor die Installation überhaupt richtig gestartet ist noch den nächsten Schritt auszuführen), und löst dann ein Ereignis aus, auf was sich die Anwendung vorher registriert, und was diese beendet, da es wenig Spaß macht eine Anwendung frisch zu installieren, die gerade noch läuft. Soweit die Client Seite, es braucht jetzt nur noch der Code:

// Noch die aktuelle Assembly mitgeben, damit der UpdateManager weiß, was genau er aktualisieren soll
AppUpdateManager uManager = new AppUpdateManager(Assembly.GetExecutingAssembly());
uManager.Restart += delegate
{
    // hier irgendwie die Anwendung beenden
};
uManager.BeginUpdate();

eingefügt zu werden, und dann läuft das Update los. Allerdings sollte man peinlichst darauf achten, wenn die Anwendung geschlossen wird uManager.EndUpdate() aufzurufen, da sonst das Update im Hintergrund weiterläuft, was (vor allen Dingen wenn es dann fertig ist, und auf die tote Oberfläche zugreifen will) größere Komplikationen nach sich ziehen kann.
Im nächsten Teil geht es dann um die Serverseite, aber soweit erstmal:

Happy Jabber

Activation Functions im XOR Sample

Heute mal ein bisschen mit den Aktivierungsfunktionen vom XOR Netz rumgespielt. Dabei herausgefunden, dass das Teil wesentlich besser funktioniert, wenn man in der letzten Matrix die Aktivierungsfunktion auf linear schaltet. Funktioniert allerdings wirklich nur bei XOR, sonst sollte man die Aktivierungsfunktionen wirklich alle(außer BIAS natürlich) auf sigmoid belassen(s. Slideshow zum XOR Sample). Allerdings habe ich jetzt ein nahezu perfekt trainiertes XOR Netz, man sollte mal darüber nachdenken das zu patentieren, oder so ;-)

Jabber my Updates

Mit dem Neuron Net Builder kommt auch die Update – Funktion über  das Internet.  Man könnte jetzt denken die laufen über den FTP Server, gänge wahrscheinlich auch schneller, aber es soll mal eine Lösung gefunden werden, bei der kein FTP, oder sonst ein Server aufgesetzt werden muss. Das heißt das Update basiert rein auf der Kommunikation von zwei Rechnern, nämlich  meinem Laptop, und dem Computer, von dem aus das Update durchgeführt wird.  Daraus ergibt sich allerdings das Problem, dass ich mit herkömmlichen Methoden(das heißt zum Beispiel  normales  http, oder tcp, oder  sonstwas) nur im lokalen Intranet kommunizieren kann, es sei denn ich registriere das Zeug im IIS, bezahl eine teure Domain, und so weiter. FTP Server aufsetzen ist also wesentlich billiger und  weniger aufwendig(falls man sich  mit einem freien zufrieden geben kann). Wir können unser Problem auf diese Art und Weise also nicht lösen. Wir brauchen also eine Kommunikation über das Internet, wobei sich der Computer, der das Update zieht mit irgend einem Server im Internet verbindet, den ein Williger ins Netz stellt. Dann muss ich  dort eine Session aufmachen, und alle Anfragen, die der Server vom Computer, der das Update zieht an meine Machine weiterleiten, und dann auf dem gleichen Umweg wieder zurückschicken. So brauch ich keinen Server aufzusetzen, sondern alles läuft über diese Zwischenstelle. OK, so was denk ich mir natürlich nicht aus, diese Lösung hat den Namen Jabber(oder XMPP = Extensible Messaging and Presence Protocol).

Das funktioniert also, bliebe noch zu klären, wie ich genau meine Anwendung jetzt verteile.

  1. Was wird verteilt
    Die  Installationsdateienen liegen als msi vor, die  noch  mit zusätzlichen Dateien übertragen werden müssen(normalerweise setup.exe)
  2. Wie wird das über die Leitung geschickt?
    Hier kommt die Open Source Bibliothek namens AppSpace von Xcoordination zum Einsatz, welche  das Versenden und Empfangen von Nachrichten via Jabber ermöglicht(sie selbst setzt auf der AgsXMPP  Bibliothek auf,  die die Jabber – Kommunikation allgemein kapselt). Die Frage ist nun, wie viele Bytes meiner Installationsdateien in solch einer Nachricht enthalten sein sollten. Es macht zum Beispiel keinen Sinn immer nur einige wenige Bytes zu versenden, da ja auch der Header der Nachricht, und alles zugehörige noch mitgeschickt werden muss. Ich habe einfach mal immer 50  kByte Nutzdaten  geschickt, der  Wert kann einfach als  Konstante im Programm geändert werden.
  3. Empfangen
    Dann werden die Bytes vom Empfänger empfangen, in irgend  ein temporäres Verzeichnis kopiert, und dann wird die  msi Datei ausgeführt.
  4. Fertige Updatefunktion ohne  FTP Server, eigene Website oder ähnliches

Neural Nets extendet — Alles vernetzt

Hi,

das erste Stable Release von meiner Anwendung zum Bauen neuronaler Netze ist einsatzbereit! Gleich mit dazu kommt eine Anwendung, die die Zahlen 1 bis 5 als Muster erkennt(Mehr über das Projekt und Download)


Pattern Recognizer
Neural Networks in action


Die Implementierung davon ist mit meiner schönen Infrastruktur ganz einfach


  • Step 1, Netz laden


Das Netz wird aus einer Datei mit der Endung .nnet geladen:

try
{
    using (FileStream fs = new FileStream(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "patrec.nnet"), FileMode.Open, FileAccess.Read))
    {
           this.LayerList = ((SerializationObject)new BinaryFormatter().Deserialize(fs)).LayerList.ToList();
    }
}
catch
{
    MessageBox.Show("Error, net file not found", "Error");
}

Dabei wird einfach der Binary Formatter genutzt um die Kontraktklasse SerializationObject, in der alle wichtigen Informationen enthalten sind, zu speichern, und zu laden.


  • Step 2, Execute Vorbereiten


Wenn die Ausführung beendet ist, wollen wir das Ergebnis in Variablen(strings) abspeichern, die an die Oberfläche gebunden sind(PropertyChanged aktualisiert die WPF Obberfläche). Dazu definieren wir für jede Ausgabeunit folgendes:

buildNet.First(el => el.Key.Layer == 2 && el.Key.Index == #).Value.OutputChanged += delegate
{
    this.Result# = buildNet.First(el => el.Key.Layer == 2 && el.Key.Index == #).Value.Output.ToString();
    this.OnPropertyChanged("Result#");
};

(Vorrausgesetzt es handelt sich um ein 3-Schichtiges Netz, da Layer 0 der kleinste Layer ist, ist dann 2 der größte, also der Ausgabelayer)

Das Delegate wird sofort bei Wertänderung aufgerufen, und die Ergebnisse werden weitergeleitet.

# ist durch den jew. Index der Augabeunit zu ersetzen, dann wird bei erfolgter asynchroner Ausführung der Wert der zuvor zu definierenden Variable gesetzt, also: alles ganz easy!


  • Step 3, Execute itself


Dann muss das Netz natürlich ausgeführt werden, wobei alle Eingabewerte durch Benutzereingabe gegeben sind. Es braucht nur noch folgendes für jede Eingabeunit an die Infrastruktur weitergeleitet zu werden:

buildNet.First(el => el.Key.Index == # && el.Key.Layer == 0).Value.Input(Value#);

# ist der Index der Eingabeunit, Value# der Wert, den der Benutzer für die Unit angegeben hat.

Dann wird das ganze asynchron ausgeführt, und die Ergebnisse erscheinen im Fenster.

Thats it!