gosha20777

Rescuer-la: Невероятный C# или avalonia UI в действии

May 24, 2019 | 8 Minute Read

Вот уже пол года я в свободное время занимаюсь некоммерческим open source проектом rescuer-la для не коммерческой организации Лиза Алерт. Суть этого проекта – использовать нейроные сети и ML для распознавания потерявшихся в лесу людей по фотографиям получаемым с дронов. О самом проекте в общем я уже рассказывал на конференции Data Fest 6. Вероятно я ещё напишу статью о нем позже.

Как уже говорилось проект open source и по этому в рамках этого проекта были применены несколько необычных и смелых подходов. Об одном из них сегодня и пойдёт речь, а именно о там как было создано кросс платформенное desktop приложение.

Почему C#?

Передо мной встала задача разработать полностью платформонезависимое приложение для настольных пк, притом сделать это быстро. В свое время я довольно плотно работал с C# и в частности с wpf. По этому мне хотелось получить нечто такое же. Я знал что существует Net Core (я уже писал о нем в предыдущих статьях). Но Net core подходит для разработки Web сервисов. Как быть? Я стал искать варианты и нашёл один – avalonia ui! Посмотрев на фреймворк я увидел что он находится в beta версии 0.7. Интересно – подумал я и выбор в пользу C# стал для меня очевиден.

AvaloniaUI

Для полного понимания этой концепции стоит посмотреть выступление Никиты Цуканова или @kekekeks. Он является разработчиком этого фреймверка, отлично в разбирается в нем и в dotnet в общем рекомендую!

От себя же скажу что AvaloniaUIэто кросс платформенный фреймверк для построения интерфейсов.

По своей концепции он очень(!) похож на WPF (я перенёс на него уже 3 своих WPF приложения не особо изменяя ккод!). Он быстр и эффективен, 2d графика в нем рисуется быстрее и потребляет меньше ресурсов, чем у WPF. Также тут есть некоторые плюшки, улучшающие оригинальный WPF. Помните эту войну со стилями и кастомными контролами? Так вот тут можно сделать все немного иначе. Стили могут работать схожим с css способом, что удобно.

Что касается внутреннего устройства то тут применяется библиотека SkeaSharp для отрисовки графики и GTK (для Unix систем). Также ведётся разработка X11 рендера. Всё это позволяет рисовать интерфейс везде где угодно, даже в буфере консоли. Если бы dotnet core можно было бы запустить в Bios е то avalonia ui отрисовала бы там модный геймерский интерфейс, как на крутых материнских платах (ну вы меня поняли).

AvaloniaUI набирает популярность и является открытым фреймверком. Также хочу сказать что у проекта довольно отзывчивая поддержка и разработчики быстро отвечают на ваши issue.

Ниже я приведу несколько проектов написанных на этом фреймверке.

С какими сложностями столкнулся я?

У меня были некоторые сложности с корректной загрузкой картинок в avalon ui. Это произошло из за того что Bitmap в wpf и Bitmap в avalonia ui немного отличаются. Но порывшись в документации я понял как это делать.

Вот фрагмент этого кода (вдруг пригодится)

public void Load(string imgFileName, Enums.ImageLoadMode loadMode = Enums.ImageLoadMode.Full)
{
    ThreadPool.QueueUserWorkItem(o =>
    {
        _name = imgFileName;
        switch (loadMode)
        {
            case Enums.ImageLoadMode.Full:
                _bitmap = new Bitmap(imgFileName);
                break;
            case Enums.ImageLoadMode.Miniature:
                using (SKStream stream = new SKFileStream(imgFileName))
                using (SKBitmap src = SKBitmap.Decode(stream))
                {
                    float scale = 100f / src.Width;
                    SKBitmap resized = new SKBitmap(
                            (int)(src.Width * scale),
                            (int)(src.Height * scale), 
                            src.ColorType, 
                            src.AlphaType);
                    SKBitmap.Resize(resized, src, SKBitmapResizeMethod.Hamming);
                    _bitmap = new Bitmap(
                            resized.ColorType.ToPixelFormat(),
                            resized.GetPixels(),
                            new PixelSize(resized.Width, resized.Height), 
                            SkiaPlatform.DefaultDpi, 
                            resized.RowBytes);
                }
                break;
            default:
                throw new Exception($"invalid ImageLoadMode:{loadMode.ToString()}");
        }
        Dispatcher.UIThread.InvokeAsync(() =>
        {
            _imageBrush.Source = _bitmap;
            onLoad?.Invoke();
        });
    });
}

Также есть некоторые отличия в том как работают биндинги. Вместо INotifyPropertyChanged используется чуть-чуть другая конструкция: this.RaiseAndSetIfChanged(ref _someVar, value);.

И наконец мне пришлось написать свой класс RelayCommand, так как в Net Core нет встроенного:

using System;
using System.Windows.Input;
using RescuerLaApp.Models;

namespace RescuerLaApp.ViewModels
{
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;

        public RelayCommand(Action execute, Func<bool> canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
            _raiseCanExecuteChangedAction = RaiseCanExecuteChanged;
            SimpleCommandManager.AddRaiseCanExecuteChangedAction(ref _raiseCanExecuteChangedAction);
        }

        ~RelayCommand()
        {
            RemoveCommand();
        }

        public void RemoveCommand()
        {
            SimpleCommandManager.RemoveRaiseCanExecuteChangedAction(_raiseCanExecuteChangedAction);
        }

        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute;
        }

        public void Execute(object parameter)
        {
            _execute();
            SimpleCommandManager.RefreshCommandStates();
        }

        public bool CanExecute => _canExecute == null || _canExecute();

        public void RaiseCanExecuteChanged()
        {
            var handler = CanExecuteChanged;
            handler?.Invoke(this, new EventArgs());
        }

        private readonly Action _raiseCanExecuteChangedAction;

        public event EventHandler CanExecuteChanged;
    }
}

На этом все, остальные не вызвало трудностей и работало также как в wpf.

Погружение в контейнер

Так как моё приложение использует нейроные сети, то для этого как правило нужно установить на компьютер много драйверов и библиотек python, tensorflow, cuda, cudnn… Все это может быть сложным для конечного пользователя (и у поытного). Чтобы избавиться от этих зависимостей было приятно решение использовать Docker контейнер.

Docker контейнер это – программа, позволяющая операционной системе запускать процессы в изолированном окружении на базе специально созданных образов. Несмотря на то, что технологии, лежащие в основа Докера появились до него, именно Докер произвел революцию в том, как сегодня создается инфраструктура проектов, собираются и запускаются сервисы.

К тому же недалеко у github появилась новая функция GitHub Package Registry. Она позволяет скачивать образы docker напрямую, что делает установку и обновление программы ещё проще (вообще про эту функцию и про docker хотелось бы сделать отдельную статью, как и про синтез речи, как и про датафест, но время почему в сутках 24 часа?).

Изначально Docker разрабатывался для его применения на серверах, чтобы у dev-ops было меньше геморроя с зависимости и настройкой сервера. Но умельцы научились запускать в нем и gui приложения. Достаточно только перенаправить вывод X11. Работает как для Linux так и для Windows. По этому если вы хотите запускать Linux программы в Windows – докер вам поможет!

Nvidia-docker.

После появления docker в свет компания nvidia доработала его для использования в докере своих графических карт. При этом на хосоовой машине вовсе не обязательно иметь Cuda и Cudnn. Достаточно обычного драйвера. Это не может не радовать (особенно в моем случае).

К сожалению nvidia docker доступен только для Linux. Буду надеяться что с внедрением подсистемы Linux, Windows возможно тоже получит его поддержку.

Результаты

В результате у меня получилось рабочее приложение которое можно поставить на любую ос. А благодаря docker ещё и избавить пользователя от настройки окружения.

reacuer-la

Если вам потнарилось и вы бы хотели поучавствовать в этом проекте то приходите к нам! Мы будем очень рады! Будем двигать open source вместе!

Дополтиельная литература