Browse Source

Migate to new protobuf

Alexey Edelev 4 years ago
commit
4c16bb1e78

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+src
+bin
+pkg
+CMakeLists.txt.user
+*.pb.go

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "handwritingui/qtprotobuf"]
+	path = handwritingui/qtprotobuf
+	url = git@github.com:semlanik/qtprotobuf.git

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+MIT License
+Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>, Tatyana Borisova <tanusshhka@mail.ru>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 13 - 0
build.sh

@@ -0,0 +1,13 @@
+export GOPATH=$PWD
+export PATH=$PATH:$PWD/bin
+export GOBIN=$PWD/bin
+
+go get github.com/golang/protobuf/protoc-gen-go
+go install ./src/github.com/golang/protobuf/protoc-gen-go
+
+export HANDWRITING_RPC_PATH=$PWD
+mkdir -p $HANDWRITING_RPC_PATH
+rm -f $HANDWRITING_RPC_PATH/handwriting/*.pb.go
+protoc -I$HANDWRITING_RPC_PATH --go_out=plugins=grpc:$HANDWRITING_RPC_PATH/handwriting $HANDWRITING_RPC_PATH/handwriting.proto
+go get -v
+go build -o $GOBIN/handwriting

+ 49 - 0
handwriting.proto

@@ -0,0 +1,49 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+syntax="proto3";
+
+package handwriting;
+
+message Matrix {
+    repeated double data = 1;
+}
+
+message Result {
+    uint32 resultCharacter = 1;
+}
+
+message NeuralNetworkRaw {
+    bytes data = 1;
+}
+
+message None {}
+
+service Handwriting {
+    rpc recognize(Matrix) returns (Result) {}
+    rpc setNeuralNetworkData(NeuralNetworkRaw) returns (None) {}
+    rpc getNeuralNetworkData(None) returns (NeuralNetworkRaw) {}
+    rpc reTrain(None) returns (None) {}
+}

+ 122 - 0
handwriting/handwriting.go

@@ -0,0 +1,122 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package handwriting
+
+import (
+	"bytes"
+	context "context"
+	fmt "fmt"
+	"net"
+
+	neuralnetwork "git.semlanik.org/semlanik/NeuralNetwork/neuralnetwork"
+	gradients "git.semlanik.org/semlanik/NeuralNetwork/neuralnetwork/gradients"
+	training "git.semlanik.org/semlanik/NeuralNetwork/training"
+	"gonum.org/v1/gonum/mat"
+	grpc "google.golang.org/grpc"
+)
+
+type HandwritingService struct {
+	nn *neuralnetwork.NeuralNetwork
+}
+
+func NewHandwritingService() (hws *HandwritingService) {
+	hws = &HandwritingService{}
+	hws.nn, _ = neuralnetwork.NewNeuralNetwork([]int{784, 300, 10}, gradients.NewRPropInitializer(gradients.RPropConfig{
+		NuPlus:   1.2,
+		NuMinus:  0.5,
+		DeltaMax: 50.0,
+		DeltaMin: 0.000001,
+	}))
+	return
+}
+
+func (hws *HandwritingService) Recognize(ctx context.Context, matrix *Matrix) (*Result, error) {
+	fmt.Printf("Recognize %v size: %v\n", len(matrix.Data), hws.nn.Sizes[0])
+	dense := mat.NewDense(hws.nn.Sizes[0], 1, matrix.Data)
+	index, _ := hws.nn.Predict(dense)
+	fmt.Printf("Recognition result %v\n", index)
+	return &Result{ResultCharacter: uint32(index)}, nil
+}
+
+func (hws *HandwritingService) SetNeuralNetworkData(ctx context.Context, nnRaw *NeuralNetworkRaw) (*None, error) {
+	fmt.Println("SetNeuralNetworkData")
+	r := bytes.NewReader(nnRaw.Data)
+	hws.nn.LoadState(r)
+	return &None{}, nil
+}
+
+func (hws *HandwritingService) GetNeuralNetworkData(context.Context, *None) (*NeuralNetworkRaw, error) {
+	nnRaw := &NeuralNetworkRaw{}
+	fmt.Println("SetNeuralNetworkData")
+	r := bytes.NewReader(nnRaw.Data)
+	hws.nn.LoadState(r)
+	return nnRaw, nil
+}
+
+func (hws *HandwritingService) ReTrain(context.Context, *None) (*None, error) {
+	fmt.Println("ReTrain")
+
+	trainer := training.NewMNISTReader("./train-images-idx3-ubyte", "./train-labels-idx1-ubyte", "./t10k-images-idx3-ubyte", "./t10k-labels-idx1-ubyte")
+	failCount, total := hws.nn.Validate(trainer)
+	fmt.Printf("Fail count before: %v/%v\n\n", failCount, total)
+
+	hws.nn.Train(trainer, 100)
+
+	hws.nn.SaveStateToFile("./mnistnet.nnd")
+
+	failCount, total = hws.nn.Validate(trainer)
+	fmt.Printf("Fail count after: %v/%v\n\n", failCount, total)
+
+	fmt.Println("ReTrain finished")
+	return &None{}, nil
+}
+
+func (hws *HandwritingService) Run() {
+	grpcServer := grpc.NewServer()
+	RegisterHandwritingServer(grpcServer, hws)
+	lis, err := net.Listen("tcp", "localhost:65001")
+	if err != nil {
+		fmt.Printf("Failed to listen: %v\n", err)
+	}
+
+	fmt.Printf("Listen localhost:65001\n")
+	if err := grpcServer.Serve(lis); err != nil {
+		fmt.Printf("Failed to serve: %v\n", err)
+	}
+}
+
+func drawImage(dense *mat.Dense) {
+	for i := 0; i < 28; i++ {
+		for j := 0; j < 28; j++ {
+			val := 0
+			if dense.At(i*28+j, 0) > 0 {
+				val = 1
+			}
+			fmt.Printf("%v ", val)
+		}
+		fmt.Println()
+	}
+}

+ 23 - 0
handwritingui/CMakeLists.txt

@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(Handwriting LANGUAGES CXX)
+
+find_package(Qt5 COMPONENTS Quick Gui Core Qml REQUIRED)
+
+set(QTPROTOBUF_MAKE_TESTS false)
+set(QTPROTOBUF_MAKE_EXAMPLES false)
+add_subdirectory("qtprotobuf")
+find_package(QtProtobufProject CONFIG COMPONENTS QtProtobuf QtGrpc REQUIRED)
+if(Qt5_POSITION_INDEPENDENT_CODE)
+    set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+endif()
+
+file(GLOB PROTO_FILES ABSOLUTE "${CMAKE_CURRENT_SOURCE_DIR}/../handwriting.proto")
+
+generate_qtprotobuf(TARGET HandwritingUi PROTO_FILES ${PROTO_FILES} QML TRUE)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+add_executable(HandwritingUi main.cpp qml.qrc handwritingengine.cpp)
+target_link_libraries(HandwritingUi Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick QtProtobufProject::QtProtobuf QtProtobufProject::QtGrpc ${QtProtobuf_GENERATED})

+ 106 - 0
handwritingui/handwritingengine.cpp

@@ -0,0 +1,106 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "handwritingengine.h"
+
+#include <QFile>
+#include <QDebug>
+
+#include <QtProtobufTypes>
+#include <QGrpcHttp2Channel>
+#include <QGrpcInsecureCredentials>
+
+#include "handwriting.qpb.h"
+#include "handwriting_grpc.qpb.h"
+
+static const QtProtobuf::DoubleList emptyData{0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                              0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                                             };
+
+HandwritingEngine::HandwritingEngine(QObject *parent) : QObject(parent)
+  , m_client(new handwriting::HandwritingClient)
+  , m_result(0)
+{
+
+    auto chan = std::shared_ptr<QtProtobuf::QGrpcHttp2Channel>(new QtProtobuf::QGrpcHttp2Channel(QUrl("http://localhost:65001"), QtProtobuf::QGrpcInsecureCallCredentials()|QtProtobuf::QGrpcInsecureChannelCredentials()));
+    m_client->attachChannel(chan);
+
+    m_matrix.setData(emptyData);
+}
+
+void HandwritingEngine::retrain() {
+    m_client->reTrain({});
+}
+
+void HandwritingEngine::recognize()
+{
+    m_client->recognize(m_matrix, this, [this](QtProtobuf::QGrpcAsyncReply *reply) {
+        setResult(reply->read<handwriting::Result>().resultCharacter());
+    });
+}
+
+void HandwritingEngine::setNeuralNetworkData(const QString &networkDataPath)
+{
+    qDebug() << "HandwritingEngine::setNeuralNetworkData";
+    QFile dataFile(QUrl(networkDataPath).toLocalFile());
+    if (!dataFile.open(QFile::ReadOnly)) {
+        qCritical() << "Could not open" << QUrl(networkDataPath).toLocalFile();
+        return;
+    }
+
+    m_client->setNeuralNetworkData({dataFile.readAll()});
+}
+
+void HandwritingEngine::updateValue(int index, double value)
+{
+    m_matrix.data()[index] = value;
+}
+

+ 85 - 0
handwritingui/handwritingengine.h

@@ -0,0 +1,85 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef HANDWRITINGENGINE_H
+#define HANDWRITINGENGINE_H
+
+#include <QObject>
+#include <QUrl>
+#include <QByteArray>
+#include <QtProtobufTypes>
+
+#include "handwriting.qpb.h"
+
+#include <memory>
+
+namespace handwriting {
+    class HandwritingClient;
+}
+class HandwritingEngine final : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(int result READ result WRITE setResult NOTIFY resultChanged)
+    Q_PROPERTY(handwriting::Matrix *matrix READ matrix CONSTANT)
+public:
+    explicit HandwritingEngine(QObject *parent = nullptr);
+    virtual ~HandwritingEngine() = default;
+
+    Q_INVOKABLE void retrain();
+    Q_INVOKABLE void recognize();
+    Q_INVOKABLE void setNeuralNetworkData(const QString &networkDataPath);
+    Q_INVOKABLE void updateValue(int index, double value);
+
+    int result() const
+    {
+        return m_result;
+    }
+
+    handwriting::Matrix *matrix()
+    {
+        return &m_matrix;
+    }
+
+public slots:
+    void setResult(int result)
+    {
+        if (m_result == result)
+            return;
+
+        m_result = result;
+        emit resultChanged(m_result);
+    }
+
+signals:
+    void resultChanged(int result);
+
+private:
+    std::shared_ptr<handwriting::HandwritingClient> m_client;
+    int m_result;
+    QtProtobuf::DoubleList m_data;
+    handwriting::Matrix m_matrix;
+};
+
+#endif // HANDWRITINGENGINE_H

+ 51 - 0
handwritingui/main.cpp

@@ -0,0 +1,51 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QDebug>
+
+#include <QtProtobufTypes>
+
+#include "qtprotobuf_global.qpb.h"
+
+#include "handwritingengine.h"
+
+int main(int argc, char *argv[])
+{
+    QtProtobuf::qRegisterProtobufTypes();
+    handwriting::qRegisterProtobufTypes();
+
+    QGuiApplication app(argc, argv);
+    HandwritingEngine hwengine;
+    QQmlApplicationEngine engine;
+    engine.rootContext()->setContextProperty("hwengine", &hwengine);
+    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+    if (engine.rootObjects().isEmpty())
+        return -1;
+
+    return app.exec();
+}

+ 163 - 0
handwritingui/main.qml

@@ -0,0 +1,163 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import QtQuick 2.11
+import QtQuick.Window 2.11
+import QtQuick.Controls 1.4
+import QtQuick.Dialogs 1.2
+
+ApplicationWindow {
+    id: root
+    visible: true
+    width: 1120
+    height: 560
+    maximumWidth: 1120
+    maximumHeight: 560
+    minimumWidth: 1120
+    minimumHeight: 560
+
+    property int tileSize: 20
+    property int tileCount: 28
+    property color resetColor: "#333333"
+
+    MouseArea {
+        id: drawingArea
+        width: tileCount*tileSize
+        height: tileCount*tileSize
+        Repeater {
+            model: 784
+            Rectangle {
+                id: tile
+                width: tileSize
+                height: tileSize
+                color: resetColor
+                x: tileSize*(model.index%tileCount)
+                y: tileSize*Math.floor(model.index/tileCount)
+                Connections {
+                    target: drawingArea
+                    onPositionChanged: {
+                        var centerX = tile.x + tile.width/2
+                        var centerY = tile.y + tile.height/2
+                        var diffX = mouse.x - centerX
+                        var diffY = mouse.y - centerY
+                        var dense = Math.sqrt(diffX * diffX + diffY * diffY)
+                        if (dense < tileSize*1.5) {
+                            var newColor = color.r + (tileSize*1.5 - dense)/tileSize*1.5
+                            var newValue = newColor - 0.2
+                            if (newColor > 1.0) {
+                                newColor = 1.0
+                                newValue = 1.0
+                            }
+
+                            tile.color = Qt.rgba(newColor, newColor, newColor, 1.0)
+                            hwengine.updateValue(model.index, newValue)
+                        }
+                    }
+                }
+
+                Connections {
+                    target: clearButton
+                    onClicked: {
+                        tile.color = resetColor
+                        hwengine.updateValue(model.index, 0.0)
+                    }
+                }
+            }
+        }
+    }
+
+    Column {
+        id: controls
+        anchors.left: drawingArea.right
+        anchors.top: parent.top
+        anchors.margins: 10
+        spacing: 10
+
+        Text {
+            text: "Select neural network data"
+        }
+
+        Row {
+            TextField {
+                id: pathToNeuralNetwork
+                enabled: false
+                width: 300
+            }
+            Button {
+                id: browseButton
+                text: "Browse"
+                onClicked: {
+                    neuralNetworkImport.open()
+                }
+            }
+
+            Button {
+                id: uploadButton
+                text: "Upload"
+                onClicked: {
+                    hwengine.setNeuralNetworkData(pathToNeuralNetwork.text)
+                }
+            }
+        }
+        Button {
+            id: tainButton
+            text: "Re-run trainig"
+            onClicked: {
+                hwengine.retrain()
+            }
+        }
+        Button {
+            id: clearButton
+            text: "Clear"
+        }
+        Button {
+            id: recognizeButton
+            text: "Recognize"
+            onClicked: {
+                hwengine.recognize()
+            }
+        }
+    }
+
+    Text {
+        id: result
+        anchors.top: controls.bottom
+        anchors.topMargin: 100
+        anchors.horizontalCenter: controls.horizontalCenter
+        color: "#003b6f" //Tardis blue :)
+        font.pixelSize: 40
+        text: hwengine.result
+    }
+
+    FileDialog {
+        id: neuralNetworkImport
+        selectMultiple: false
+        selectFolder: false
+        nameFilters: ["*.nnd"]
+        onAccepted: {
+            pathToNeuralNetwork.text = neuralNetworkImport.fileUrl
+        }
+    }
+}

+ 5 - 0
handwritingui/qml.qrc

@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/">
+        <file>main.qml</file>
+    </qresource>
+</RCC>

+ 1 - 0
handwritingui/qtprotobuf

@@ -0,0 +1 @@
+Subproject commit 8cd7bdd128a64f9b60cc335306c49e6dab6ed1c7

+ 35 - 0
main.go

@@ -0,0 +1,35 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package main
+
+import (
+	handwriting "./handwriting"
+)
+
+func main() {
+	hws := handwriting.NewHandwritingService()
+	hws.Run()
+}