separate load functionality from inference client to loader client. Call loader client from inference to get the model.

remove dummy dlls, remove resource loader from c#.

TODO: Load dlls separately by Loader UI and loader client

WIP
This commit is contained in:
Alex Bezdieniezhnykh
2025-06-06 20:04:03 +03:00
parent 500db31142
commit 7750025631
54 changed files with 353 additions and 571 deletions
+3 -1
View File
@@ -19,4 +19,6 @@ dist-azaion
Azaion*.exe Azaion*.exe
Azaion*.bin Azaion*.bin
azaion\.*\.big azaion\.*\.big
_internal
*.spec
+3
View File
@@ -8,6 +8,8 @@ namespace Azaion.Common.DTO.Config;
public class AppConfig public class AppConfig
{ {
public LoaderClientConfig LoaderClientConfig { get; set; } = null!;
public InferenceClientConfig InferenceClientConfig { get; set; } = null!; public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!; public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
@@ -76,6 +78,7 @@ public class ConfigUpdater : IConfigUpdater
//Save only user's config //Save only user's config
var publicConfig = new var publicConfig = new
{ {
config.LoaderClientConfig,
config.InferenceClientConfig, config.InferenceClientConfig,
config.GpsDeniedClientConfig, config.GpsDeniedClientConfig,
config.DirectoriesConfig, config.DirectoriesConfig,
+10 -50
View File
@@ -17,17 +17,14 @@ namespace Azaion.Common.Services;
public interface IInferenceClient : IDisposable public interface IInferenceClient : IDisposable
{ {
event EventHandler<RemoteCommand> BytesReceived;
event EventHandler<RemoteCommand>? InferenceDataReceived; event EventHandler<RemoteCommand>? InferenceDataReceived;
event EventHandler<RemoteCommand>? AIAvailabilityReceived; event EventHandler<RemoteCommand>? AIAvailabilityReceived;
void Send(RemoteCommand create); void Send(RemoteCommand create);
void Stop(); void Stop();
} }
public class InferenceClient : IInferenceClient, IResourceLoader public class InferenceClient(IOptions<InferenceClientConfig> inferenceConfig, IOptions<LoaderClientConfig> loaderConfig) : IInferenceClient
{ {
private CancellationTokenSource _waitFileCancelSource = new();
public event EventHandler<RemoteCommand>? BytesReceived; public event EventHandler<RemoteCommand>? BytesReceived;
public event EventHandler<RemoteCommand>? InferenceDataReceived; public event EventHandler<RemoteCommand>? InferenceDataReceived;
public event EventHandler<RemoteCommand>? AIAvailabilityReceived; public event EventHandler<RemoteCommand>? AIAvailabilityReceived;
@@ -35,15 +32,10 @@ public class InferenceClient : IInferenceClient, IResourceLoader
private readonly DealerSocket _dealer = new(); private readonly DealerSocket _dealer = new();
private readonly NetMQPoller _poller = new(); private readonly NetMQPoller _poller = new();
private readonly Guid _clientId = Guid.NewGuid(); private readonly Guid _clientId = Guid.NewGuid();
private readonly InferenceClientConfig _inferenceClientConfig; private readonly InferenceClientConfig _inferenceClientConfig = inferenceConfig.Value;
private readonly LoaderClientConfig _loaderClientConfig = loaderConfig.Value;
public InferenceClient(IOptions<InferenceClientConfig> config, CancellationToken ct) private void Start()
{
_inferenceClientConfig = config.Value;
Start(ct);
}
private void Start(CancellationToken ct = default)
{ {
try try
{ {
@@ -51,7 +43,7 @@ public class InferenceClient : IInferenceClient, IResourceLoader
process.StartInfo = new ProcessStartInfo process.StartInfo = new ProcessStartInfo
{ {
FileName = SecurityConstants.EXTERNAL_INFERENCE_PATH, FileName = SecurityConstants.EXTERNAL_INFERENCE_PATH,
Arguments = $"--port {_inferenceClientConfig.ZeroMqPort} --api {_inferenceClientConfig.ApiUrl}", Arguments = $"--port {_inferenceClientConfig.ZeroMqPort} --loader-port {_loaderClientConfig.ZeroMqPort} --api {_inferenceClientConfig.ApiUrl}",
//RedirectStandardOutput = true, //RedirectStandardOutput = true,
//RedirectStandardError = true, //RedirectStandardError = true,
//CreateNoWindow = true //CreateNoWindow = true
@@ -70,9 +62,9 @@ public class InferenceClient : IInferenceClient, IResourceLoader
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N")); _dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
_dealer.Connect($"tcp://{_inferenceClientConfig.ZeroMqHost}:{_inferenceClientConfig.ZeroMqPort}"); _dealer.Connect($"tcp://{_inferenceClientConfig.ZeroMqHost}:{_inferenceClientConfig.ZeroMqPort}");
_dealer.ReceiveReady += (_, e) => ProcessClientCommand(e.Socket, ct); _dealer.ReceiveReady += (_, e) => ProcessClientCommand(e.Socket);
_poller.Add(_dealer); _poller.Add(_dealer);
_ = Task.Run(() => _poller.RunAsync(), ct); _ = Task.Run(() => _poller.RunAsync());
} }
private void ProcessClientCommand(NetMQSocket socket, CancellationToken ct = default) private void ProcessClientCommand(NetMQSocket socket, CancellationToken ct = default)
@@ -99,46 +91,14 @@ public class InferenceClient : IInferenceClient, IResourceLoader
} }
} }
public void Stop() public void Stop() =>
{ Send(RemoteCommand.Create(CommandType.StopInference));
} public void Send(RemoteCommand command) =>
public void Send(RemoteCommand command)
{
_dealer.SendFrame(MessagePackSerializer.Serialize(command)); _dealer.SendFrame(MessagePackSerializer.Serialize(command));
}
public MemoryStream LoadFile(string fileName, string? folder = null, TimeSpan? timeout = null)
{
//TODO: Bad solution, look for better implementation
byte[] bytes = [];
Exception? exception = null;
_waitFileCancelSource = new CancellationTokenSource();
Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(fileName, folder)));
BytesReceived += OnBytesReceived;
void OnBytesReceived(object? sender, RemoteCommand command)
{
if (command.Data is null)
{
exception = new BusinessException(command.Message ?? "File is empty");
_waitFileCancelSource.Cancel();
}
bytes = command.Data!;
_waitFileCancelSource.Cancel();
}
_waitFileCancelSource.Token.WaitForCancel(timeout ?? TimeSpan.FromSeconds(15));
BytesReceived -= OnBytesReceived;
if (exception != null)
throw exception;
return new MemoryStream(bytes);
}
public void Dispose() public void Dispose()
{ {
_waitFileCancelSource.Dispose();
_poller.Stop(); _poller.Stop();
_poller.Dispose(); _poller.Dispose();
+1 -4
View File
@@ -79,8 +79,5 @@ public class InferenceService : IInferenceService
await combinedTokenSource.Token.AsTask(); await combinedTokenSource.Token.AsTask();
} }
public void StopInference() public void StopInference() => _client.Stop();
{
_client.Send(RemoteCommand.Create(CommandType.StopInference));
}
} }
@@ -19,6 +19,8 @@ public class RemoteCommand(CommandType commandType, byte[]? data = null, string?
public static RemoteCommand Create<T>(CommandType commandType, T data, string? message = null) where T : class => public static RemoteCommand Create<T>(CommandType commandType, T data, string? message = null) where T : class =>
new(commandType, MessagePackSerializer.Serialize(data), message); new(commandType, MessagePackSerializer.Serialize(data), message);
public override string ToString() => $"({CommandType.ToString().ToUpper()}: Data: {Data?.Length ?? 0} bytes. Message: {Message})";
} }
[MessagePackObject] [MessagePackObject]
@@ -35,6 +37,7 @@ public class LoadFileData(string filename, string? folder = null )
public enum CommandType public enum CommandType
{ {
None = 0, None = 0,
Ok = 3,
Login = 10, Login = 10,
Load = 20, Load = 20,
DataBytes = 25, DataBytes = 25,
@@ -6,8 +6,6 @@ public class SecurityConstants
{ {
public const string CONFIG_PATH = "config.json"; public const string CONFIG_PATH = "config.json";
public const string DUMMY_DIR = "dummy";
private const string DEFAULT_API_URL = "https://api.azaion.com"; private const string DEFAULT_API_URL = "https://api.azaion.com";
#region ExternalClientsConfig #region ExternalClientsConfig
+30 -13
View File
@@ -27,8 +27,14 @@ public class LoaderClient(LoaderClientConfig config, ILogger logger, Cancellatio
CreateNoWindow = true CreateNoWindow = true
}; };
process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; process.OutputDataReceived += (_, e) =>
process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; {
if (e.Data != null) Console.WriteLine(e.Data);
};
process.ErrorDataReceived += (_, e) =>
{
if (e.Data != null) Console.WriteLine(e.Data);
};
process.Start(); process.Start();
} }
catch (Exception e) catch (Exception e)
@@ -44,28 +50,39 @@ public class LoaderClient(LoaderClientConfig config, ILogger logger, Cancellatio
_dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}"); _dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}");
} }
public void Send(RemoteCommand command) => public void Login(ApiCredentials credentials)
_dealer.SendFrame(MessagePackSerializer.Serialize(command)); {
var result = SendCommand(RemoteCommand.Create(CommandType.Login, credentials));
if (result.CommandType != CommandType.Ok)
throw new Exception(result.Message);
}
public MemoryStream LoadFile(string filename, string folder) public MemoryStream LoadFile(string filename, string folder)
{
var result = SendCommand(RemoteCommand.Create(CommandType.Load, new LoadFileData(filename, folder)));
if (result.Data?.Length == 0)
throw new Exception($"Can't load {filename}. Returns 0 bytes");
return new MemoryStream(result.Data!);
}
private RemoteCommand SendCommand(RemoteCommand command, int retryCount = 15, int retryDelayMs = 400)
{ {
try try
{ {
Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(filename, folder))); _dealer.SendFrame(MessagePackSerializer.Serialize(command));
var retries = 10;
var tryNum = 0; var tryNum = 0;
while (!ct.IsCancellationRequested && tryNum++ < retries) while (!ct.IsCancellationRequested && tryNum++ < retryCount)
{ {
if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(150), out var bytes)) if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(retryDelayMs), out var bytes))
continue; continue;
var resultCommand = MessagePackSerializer.Deserialize<RemoteCommand>(bytes, cancellationToken: ct); var res = MessagePackSerializer.Deserialize<RemoteCommand>(bytes, cancellationToken: ct);
if (resultCommand.Data?.Length == 0) if (res.CommandType == CommandType.Error)
throw new Exception($"Can't load {filename}. Returns 0 bytes"); throw new Exception(res.Message);
return new MemoryStream(resultCommand.Data!); return res;
} }
throw new Exception($"Can't load file {filename} after {retries} retries"); throw new Exception($"Sent {command} {retryCount} times. No response from client.");
} }
catch (Exception e) catch (Exception e)
{ {
@@ -1,8 +0,0 @@
using Azaion.CommonSecurity.DTO.Commands;
namespace Azaion.CommonSecurity.Services;
public interface IResourceLoader
{
MemoryStream LoadFile(string fileName, string? folder, TimeSpan? timeout = null);
}
-9
View File
@@ -23,7 +23,6 @@ echo install azaion-inference
venv\Scripts\pyinstaller --name=azaion-inference ^ venv\Scripts\pyinstaller --name=azaion-inference ^
--collect-submodules cv2 ^ --collect-submodules cv2 ^
--add-data "venv\Lib\site-packages\cv2;cv2" ^ --add-data "venv\Lib\site-packages\cv2;cv2" ^
--collect-all requests ^
--collect-all psutil ^ --collect-all psutil ^
--collect-all msgpack ^ --collect-all msgpack ^
--collect-all zmq ^ --collect-all zmq ^
@@ -36,14 +35,6 @@ venv\Scripts\pyinstaller --name=azaion-inference ^
--collect-all jwt ^ --collect-all jwt ^
--hidden-import constants ^ --hidden-import constants ^
--hidden-import annotation ^ --hidden-import annotation ^
--hidden-import credentials ^
--hidden-import file_data ^
--hidden-import user ^
--hidden-import security ^
--hidden-import secure_model ^
--hidden-import cdn_manager ^
--hidden-import api_client ^
--hidden-import hardware_service ^
--hidden-import remote_command ^ --hidden-import remote_command ^
--hidden-import ai_config ^ --hidden-import ai_config ^
--hidden-import tensorrt_engine ^ --hidden-import tensorrt_engine ^
+16
View File
@@ -0,0 +1,16 @@
cdef class FileData:
cdef public str folder
cdef public str filename
@staticmethod
cdef from_msgpack(bytes data)
cdef bytes serialize(self)
cdef class UploadFileData(FileData):
cdef public bytes resource
@staticmethod
cdef from_msgpack(bytes data)
cdef bytes serialize(self)
+40
View File
@@ -0,0 +1,40 @@
from msgpack import unpackb, packb
cdef class FileData:
def __init__(self, str folder, str filename):
self.folder = folder
self.filename = filename
@staticmethod
cdef from_msgpack(bytes data):
unpacked = unpackb(data, strict_map_key=False)
return FileData(
unpacked.get("Folder"),
unpacked.get("Filename"))
cdef bytes serialize(self):
return packb({
"Folder": self.folder,
"Filename": self.filename
})
cdef class UploadFileData(FileData):
def __init__(self, bytes resource, str folder, str filename):
super().__init__(folder, filename)
self.resource = resource
@staticmethod
cdef from_msgpack(bytes data):
unpacked = unpackb(data, strict_map_key=False)
return UploadFileData(
unpacked.get("Resource"),
unpacked.get("Folder"),
unpacked.get("Filename"))
cdef bytes serialize(self):
return packb({
"Resource": self.resource,
"Folder": self.folder,
"Filename": self.filename
})
+2 -2
View File
@@ -1,11 +1,11 @@
from remote_command cimport RemoteCommand from remote_command cimport RemoteCommand
from annotation cimport Annotation, Detection from annotation cimport Annotation, Detection
from ai_config cimport AIRecognitionConfig from ai_config cimport AIRecognitionConfig
from api_client cimport ApiClient from loader_client cimport LoaderClient
from inference_engine cimport InferenceEngine from inference_engine cimport InferenceEngine
cdef class Inference: cdef class Inference:
cdef ApiClient api_client cdef LoaderClient loader_client
cdef InferenceEngine engine cdef InferenceEngine engine
cdef object on_annotation cdef object on_annotation
cdef Annotation _previous_annotation cdef Annotation _previous_annotation
+45 -22
View File
@@ -1,29 +1,54 @@
import json
import mimetypes import mimetypes
import os
import subprocess
import sys
import time import time
import cv2 import cv2
import numpy as np import numpy as np
cimport constants cimport constants
from remote_command cimport RemoteCommand from remote_command cimport RemoteCommand
from annotation cimport Detection, Annotation from annotation cimport Detection, Annotation
from ai_config cimport AIRecognitionConfig from ai_config cimport AIRecognitionConfig
from hardware_service cimport HardwareService import pynvml
from security cimport Security
if HardwareService.has_nvidia_gpu(): cdef int tensor_gpu_index
cdef int check_tensor_gpu_index():
try:
pynvml.nvmlInit()
deviceCount = pynvml.nvmlDeviceGetCount()
if deviceCount == 0:
print('No NVIDIA GPUs found.')
return -1
for i in range(deviceCount):
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
major, minor = pynvml.nvmlDeviceGetCudaComputeCapability(handle)
if major > 6 or (major == 6 and minor >= 1):
print('found NVIDIA GPU!')
return i
print('NVIDIA GPU doesnt support TensorRT!')
return -1
except pynvml.NVMLError:
return -1
finally:
try:
pynvml.nvmlShutdown()
except:
print('Failed to shutdown pynvml cause probably no NVidia GPU')
pass
tensor_gpu_index = check_tensor_gpu_index()
if tensor_gpu_index > -1:
from tensorrt_engine import TensorRTEngine from tensorrt_engine import TensorRTEngine
else: else:
from onnx_engine import OnnxEngine from onnx_engine import OnnxEngine
cdef class Inference: cdef class Inference:
def __init__(self, api_client, on_annotation): def __init__(self, loader_client, on_annotation):
self.api_client = api_client self.loader_client = loader_client
self.on_annotation = on_annotation self.on_annotation = on_annotation
self.stop_signal = False self.stop_signal = False
self.model_input = None self.model_input = None
@@ -33,27 +58,26 @@ cdef class Inference:
self.is_building_engine = False self.is_building_engine = False
cdef build_tensor_engine(self, object updater_callback): cdef build_tensor_engine(self, object updater_callback):
is_nvidia = HardwareService.has_nvidia_gpu() if not tensor_gpu_index == -1:
if not is_nvidia:
return return
engine_filename = TensorRTEngine.get_engine_filename(0) engine_filename = TensorRTEngine.get_engine_filename(0)
key = Security.get_model_encryption_key()
models_dir = constants.MODELS_FOLDER models_dir = constants.MODELS_FOLDER
self.is_building_engine = True self.is_building_engine = True
updater_callback('downloading') updater_callback('downloading')
if self.api_client.load_big_small_resource(engine_filename, models_dir, key):
if self.loader_client.load_big_small_resource(engine_filename, models_dir):
print('tensor rt engine is here, no need to build') print('tensor rt engine is here, no need to build')
self.is_building_engine = False self.is_building_engine = False
return return
# time.sleep(8) # prevent simultaneously loading dll and models # time.sleep(8) # prevent simultaneously loading dll and models
updater_callback('converting') updater_callback('converting')
onnx_model = self.api_client.load_big_small_resource(constants.AI_ONNX_MODEL_FILE, models_dir, key) onnx_model = self.loader_client.load_big_small_resource(constants.AI_ONNX_MODEL_FILE, models_dir)
model_bytes = TensorRTEngine.convert_from_onnx(onnx_model) model_bytes = TensorRTEngine.convert_from_onnx(onnx_model)
updater_callback('uploading') updater_callback('uploading')
self.api_client.upload_big_small_resource(model_bytes, <str> engine_filename, models_dir, key) self.loader_client.upload_big_small_resource(model_bytes, <str> engine_filename, models_dir)
print(f'uploaded {engine_filename} to CDN and API') print(f'uploaded {engine_filename} to CDN and API')
self.is_building_engine = False self.is_building_engine = False
@@ -61,17 +85,16 @@ cdef class Inference:
if self.engine is not None: if self.engine is not None:
return return
is_nvidia = HardwareService.has_nvidia_gpu()
key = Security.get_model_encryption_key()
models_dir = constants.MODELS_FOLDER models_dir = constants.MODELS_FOLDER
if is_nvidia: if tensor_gpu_index > -1:
while self.is_building_engine: while self.is_building_engine:
time.sleep(1) time.sleep(1)
engine_filename = TensorRTEngine.get_engine_filename(0) engine_filename = TensorRTEngine.get_engine_filename(0)
model_bytes = self.api_client.load_big_small_resource(engine_filename, models_dir, key)
model_bytes = self.loader_client.load_big_small_resource(engine_filename, models_dir)
self.engine = TensorRTEngine(model_bytes) self.engine = TensorRTEngine(model_bytes)
else: else:
model_bytes = self.api_client.load_big_small_resource(constants.AI_ONNX_MODEL_FILE, models_dir, key) model_bytes = self.loader_client.load_big_small_resource(constants.AI_ONNX_MODEL_FILE, models_dir)
self.engine = OnnxEngine(model_bytes) self.engine = OnnxEngine(model_bytes)
self.model_height, self.model_width = self.engine.get_input_shape() self.model_height, self.model_width = self.engine.get_input_shape()
+13
View File
@@ -0,0 +1,13 @@
from remote_command cimport RemoteCommand
cdef class LoaderClient:
cdef object _context
cdef object _socket
cdef RemoteCommand _send_receive_command(self, RemoteCommand command)
cdef load_big_small_resource(self, str filename, str directory)
cdef upload_big_small_resource(self, bytes content, str filename, str directory)
cdef close(self)
+39
View File
@@ -0,0 +1,39 @@
import zmq
from remote_command cimport RemoteCommand, CommandType
from file_data cimport FileData, UploadFileData
cdef class LoaderClient:
def __init__(self, str zmq_host, int zmq_port):
self._context = zmq.Context().instance()
self._socket = self._context.socket(zmq.DEALER)
self._socket.connect(f'tcp://{zmq_host}:{zmq_port}')
cdef RemoteCommand _send_receive_command(self, RemoteCommand command):
self._socket.send(command.serialize())
return RemoteCommand.from_msgpack(self._socket.recv())
cdef load_big_small_resource(self, str filename, str directory):
cdef FileData file_data = FileData(folder=directory, filename=filename)
cdef RemoteCommand response = self._send_receive_command(RemoteCommand(CommandType.LOAD_BIG_SMALL, data=file_data.serialize()))
if response.command_type == CommandType.DATA_BYTES:
return response.data
elif response.command_type == CommandType.ERROR:
raise Exception(f"Error from server: {response.message}")
else:
raise Exception(f"Unexpected response command type: {response.command_type}")
cdef upload_big_small_resource(self, bytes content, str filename, str directory):
cdef UploadFileData upload_file_data = UploadFileData(content, filename, directory)
cdef RemoteCommand upload_resp = self._send_receive_command(RemoteCommand(CommandType.UPLOAD_BIG_SMALL, data=upload_file_data.serialize()))
if upload_resp.command_type == CommandType.OK:
return
elif upload_resp.command_type == CommandType.ERROR:
raise Exception(f"Error from server: {upload_resp.message}")
else:
raise Exception(f"Unexpected response command type: {upload_resp.command_type}")
cdef close(self):
if self._socket and not self._socket.closed:
self._socket.close()
if self._context and not self._context.closed:
self._context.term()
+9 -29
View File
@@ -4,10 +4,9 @@ from queue import Queue
cimport constants cimport constants
from threading import Thread from threading import Thread
import yaml
from annotation cimport Annotation from annotation cimport Annotation
from inference cimport Inference from inference cimport Inference
from loader_client cimport LoaderClient
from remote_command cimport RemoteCommand, CommandType from remote_command cimport RemoteCommand, CommandType
from remote_command_handler cimport RemoteCommandHandler from remote_command_handler cimport RemoteCommandHandler
@@ -17,14 +16,16 @@ cdef class CommandProcessor:
cdef object inference_queue cdef object inference_queue
cdef bint running cdef bint running
cdef Inference inference cdef Inference inference
cdef LoaderClient loader_client
def __init__(self, int zmq_port, str api_url): def __init__(self, int zmq_port, str loader_zmq_host, int loader_zmq_port, str api_url):
self.remote_handler = RemoteCommandHandler(zmq_port, self.on_command) self.remote_handler = RemoteCommandHandler(zmq_port, self.on_command)
self.inference_queue = Queue(maxsize=constants.QUEUE_MAXSIZE) self.inference_queue = Queue(maxsize=constants.QUEUE_MAXSIZE)
self.remote_handler.start() self.remote_handler.start()
self.running = True self.running = True
self.loader_client = LoaderClient(loader_zmq_host, loader_zmq_port)
#TODO: replace api_client to azaion_loader.exe call #TODO: replace api_client to azaion_loader.exe call
self.inference = Inference(self.api_client, self.on_annotation) self.inference = Inference(self.loader_client, self.on_annotation)
def start(self): def start(self):
while self.running: while self.running:
@@ -41,14 +42,12 @@ cdef class CommandProcessor:
cdef on_command(self, RemoteCommand command): cdef on_command(self, RemoteCommand command):
try: try:
if command.command_type == CommandType.LOGIN: if command.command_type == CommandType.INFERENCE:
self.api_client.set_credentials(Credentials.from_msgpack(command.data))
elif command.command_type == CommandType.LOAD:
self.load_file(command)
elif command.command_type == CommandType.INFERENCE:
self.inference_queue.put(command) self.inference_queue.put(command)
elif command.command_type == CommandType.AI_AVAILABILITY_CHECK: elif command.command_type == CommandType.AI_AVAILABILITY_CHECK:
self.build_tensor_engine(command.client_id) self.inference.build_tensor_engine(lambda status: self.remote_handler.send(
command.client_id, RemoteCommand(CommandType.AI_AVAILABILITY_RESULT, None, status).serialize()))
self.remote_handler.send(command.client_id, RemoteCommand(CommandType.AI_AVAILABILITY_RESULT, None, 'enabled').serialize())
elif command.command_type == CommandType.STOP_INFERENCE: elif command.command_type == CommandType.STOP_INFERENCE:
self.inference.stop() self.inference.stop()
elif command.command_type == CommandType.EXIT: elif command.command_type == CommandType.EXIT:
@@ -59,25 +58,6 @@ cdef class CommandProcessor:
except Exception as e: except Exception as e:
print(f"Error handling client: {e}") print(f"Error handling client: {e}")
cdef build_tensor_engine(self, client_id):
self.inference.build_tensor_engine(lambda status: self.build_tensor_status_updater(client_id, status))
self.remote_handler.send(client_id, RemoteCommand(CommandType.AI_AVAILABILITY_RESULT, None, 'enabled').serialize())
cdef build_tensor_status_updater(self, bytes client_id, str status):
self.remote_handler.send(client_id, RemoteCommand(CommandType.AI_AVAILABILITY_RESULT, None, status).serialize())
cdef load_file(self, RemoteCommand command):
cdef RemoteCommand response
cdef FileData file_data
cdef bytes file_bytes
try:
file_data = FileData.from_msgpack(command.data)
file_bytes = self.api_client.load_bytes(file_data.filename, file_data.folder)
response = RemoteCommand(CommandType.DATA_BYTES, file_bytes)
except Exception as e:
response = RemoteCommand(CommandType.DATA_BYTES, None, str(e))
self.remote_handler.send(command.client_id, response.serialize())
cdef on_annotation(self, RemoteCommand cmd, Annotation annotation): cdef on_annotation(self, RemoteCommand cmd, Annotation annotation):
cdef RemoteCommand response = RemoteCommand(CommandType.INFERENCE_DATA, annotation.serialize()) cdef RemoteCommand response = RemoteCommand(CommandType.INFERENCE_DATA, annotation.serialize())
self.remote_handler.send(cmd.client_id, response.serialize()) self.remote_handler.send(cmd.client_id, response.serialize())
+3
View File
@@ -1,6 +1,9 @@
cdef enum CommandType: cdef enum CommandType:
OK = 3
LOGIN = 10 LOGIN = 10
LOAD = 20 LOAD = 20
LOAD_BIG_SMALL = 22
UPLOAD_BIG_SMALL = 24
DATA_BYTES = 25 DATA_BYTES = 25
INFERENCE = 30 INFERENCE = 30
INFERENCE_DATA = 35 INFERENCE_DATA = 35
+3
View File
@@ -8,8 +8,11 @@ cdef class RemoteCommand:
def __str__(self): def __str__(self):
command_type_names = { command_type_names = {
3: "OK",
10: "LOGIN", 10: "LOGIN",
20: "LOAD", 20: "LOAD",
22: "LOAD_BIG_SMALL",
24: "UPLOAD_BIG_SMALL",
25: "DATA_BYTES", 25: "DATA_BYTES",
30: "INFERENCE", 30: "INFERENCE",
35: "INFERENCE_DATA", 35: "INFERENCE_DATA",
+1 -6
View File
@@ -5,15 +5,10 @@ import numpy as np
extensions = [ extensions = [
Extension('constants', ['constants.pyx']), Extension('constants', ['constants.pyx']),
Extension('annotation', ['annotation.pyx']), Extension('annotation', ['annotation.pyx']),
Extension('credentials', ['credentials.pyx']),
Extension('file_data', ['file_data.pyx']), Extension('file_data', ['file_data.pyx']),
Extension('hardware_service', ['hardware_service.pyx'], extra_compile_args=["-g"], extra_link_args=["-g"]), Extension('loader_client', ['loader_client.pyx']),
Extension('security', ['security.pyx']),
Extension('remote_command', ['remote_command.pyx']), Extension('remote_command', ['remote_command.pyx']),
Extension('remote_command_handler', ['remote_command_handler.pyx']), Extension('remote_command_handler', ['remote_command_handler.pyx']),
Extension('user', ['user.pyx']),
Extension('cdn_manager', ['cdn_manager.pyx']),
Extension('api_client', ['api_client.pyx']),
Extension('ai_config', ['ai_config.pyx']), Extension('ai_config', ['ai_config.pyx']),
Extension('tensorrt_engine', ['tensorrt_engine.pyx'], include_dirs=[np.get_include()]), Extension('tensorrt_engine', ['tensorrt_engine.pyx'], include_dirs=[np.get_include()]),
Extension('onnx_engine', ['onnx_engine.pyx'], include_dirs=[np.get_include()]), Extension('onnx_engine', ['onnx_engine.pyx'], include_dirs=[np.get_include()]),
+9 -11
View File
@@ -2,18 +2,16 @@ from main import CommandProcessor
import argparse import argparse
def start(zmq_port, api_url): if __name__ == '__main__':
processor = CommandProcessor(zmq_port, api_url) parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", type=int, default=5127, help="zero mq port")
parser.add_argument("-lh", "--loader-host", type=str, default="127.0.0.1", help="zero mq loader app port")
parser.add_argument("-lp", "--loader-port", type=int, default=5025, help="zero mq loader app port")
parser.add_argument("-a", "--api", type=str, default="https://api.azaion.com", help="api url")
args = parser.parse_args()
processor = CommandProcessor(args.port, args.loader_host, args.loader_port, args.api)
try: try:
processor.start() processor.start()
except KeyboardInterrupt: except KeyboardInterrupt:
processor.stop() processor.stop()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", type=str, default="5127", help="zero mq port")
parser.add_argument("-a", "--api", type=str, default="https://api.azaion.com", help="api url")
args = parser.parse_args()
start(int(args.port), args.api)
+2 -2
View File
@@ -17,5 +17,5 @@ cdef class ApiClient:
cdef load_bytes(self, str filename, str folder) cdef load_bytes(self, str filename, str folder)
cdef upload_file(self, str filename, bytes resource, str folder) cdef upload_file(self, str filename, bytes resource, str folder)
cdef load_big_file_cdn(self, str folder, str big_part) cdef load_big_file_cdn(self, str folder, str big_part)
cdef load_big_small_resource(self, str resource_name, str folder, str key) cdef load_big_small_resource(self, str resource_name, str folder)
cdef upload_big_small_resource(self, bytes resource, str resource_name, str folder, str key) cdef upload_big_small_resource(self, bytes resource, str resource_name, str folder)
+18 -10
View File
@@ -8,7 +8,7 @@ import requests
cimport constants cimport constants
import yaml import yaml
from requests import HTTPError from requests import HTTPError
from credentials cimport Credentials
from cdn_manager cimport CDNManager, CDNCredentials from cdn_manager cimport CDNManager, CDNCredentials
from hardware_service cimport HardwareService from hardware_service cimport HardwareService
from security cimport Security from security cimport Security
@@ -98,8 +98,10 @@ cdef class ApiClient:
constants.log(f"Upload fail: {e}") constants.log(f"Upload fail: {e}")
cdef load_bytes(self, str filename, str folder): cdef load_bytes(self, str filename, str folder):
hardware_service = HardwareService() cdef str hardware = HardwareService.get_hardware_info()
cdef str hardware = hardware_service.get_hardware_info() hw_hash = Security.get_hw_hash(hardware)
key = Security.get_api_encryption_key(self.credentials, hw_hash)
if self.token is None: if self.token is None:
self.login() self.login()
url = f"{self.api_url}/resources/get/{folder}" url = f"{self.api_url}/resources/get/{folder}"
@@ -124,10 +126,12 @@ cdef class ApiClient:
response = requests.post(url, data=payload, headers=headers, stream=True) response = requests.post(url, data=payload, headers=headers, stream=True)
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
print('500!') raise Exception(f'Internal API error! {response.text}')
if response.status_code == HTTPStatus.CONFLICT:
hw_hash = Security.get_hw_hash(hardware) res = response.json()
key = Security.get_api_encryption_key(self.credentials, hw_hash) err_code = res['ErrorCode']
err_msg = res['Message']
raise Exception(f"Error {err_code}: {err_msg}")
resp_bytes = response.raw.read() resp_bytes = response.raw.read()
data = Security.decrypt_to(resp_bytes, key) data = Security.decrypt_to(resp_bytes, key)
@@ -142,28 +146,32 @@ cdef class ApiClient:
else: else:
return None return None
cdef load_big_small_resource(self, str resource_name, str folder, str key): cdef load_big_small_resource(self, str resource_name, str folder):
cdef str big_part = f'{resource_name}.big' cdef str big_part = f'{resource_name}.big'
cdef str small_part = f'{resource_name}.small' cdef str small_part = f'{resource_name}.small'
encrypted_bytes_small = self.load_bytes(small_part, folder) encrypted_bytes_small = self.load_bytes(small_part, folder)
key = Security.get_resource_encryption_key()
print(f'checking on existence for {folder}\\{big_part}') print(f'checking on existence for {folder}\\{big_part}')
if os.path.exists(os.path.join(<str> folder, big_part)): if os.path.exists(os.path.join(<str> folder, big_part)):
with open(path.join(<str> folder, big_part), 'rb') as binary_file: with open(path.join(<str> folder, big_part), 'rb') as binary_file:
local_bytes_big = binary_file.read() local_bytes_big = binary_file.read()
print(f'local file {folder}\\{big_part} is found!') print(f'local file {folder}\\{big_part} is found!')
try: try:
return Security.decrypt_to(encrypted_bytes_small + local_bytes_big, key) resource = Security.decrypt_to(encrypted_bytes_small + local_bytes_big, key)
return resource
except Exception as ex: except Exception as ex:
print('Local file doesnt match with api file, old version') print('Local file doesnt match with api file, old version')
remote_bytes_big = self.load_big_file_cdn(folder, big_part) remote_bytes_big = self.load_big_file_cdn(folder, big_part)
return Security.decrypt_to(encrypted_bytes_small + remote_bytes_big, key) return Security.decrypt_to(encrypted_bytes_small + remote_bytes_big, key)
cdef upload_big_small_resource(self, bytes resource, str resource_name, str folder, str key): cdef upload_big_small_resource(self, bytes resource, str resource_name, str folder):
cdef str big_part_name = f'{resource_name}.big' cdef str big_part_name = f'{resource_name}.big'
cdef str small_part_name = f'{resource_name}.small' cdef str small_part_name = f'{resource_name}.small'
key = Security.get_resource_encryption_key()
resource_encrypted = Security.encrypt_to(<bytes>resource, key) resource_encrypted = Security.encrypt_to(<bytes>resource, key)
part_small_size = min(constants.SMALL_SIZE_KB * 1024, int(0.3 * len(resource_encrypted))) part_small_size = min(constants.SMALL_SIZE_KB * 1024, int(0.3 * len(resource_encrypted)))
+19 -1
View File
@@ -21,4 +21,22 @@ venv\Scripts\python setup.py build_ext --inplace
echo install azaion-loader echo install azaion-loader
venv\Scripts\pyinstaller --name=azaion-loader ^ venv\Scripts\pyinstaller --name=azaion-loader ^
--collect-all boto3 ^ --collect-all requests ^
--collect-all boto3 ^
--collect-all msgpack ^
--collect-all zmq ^
--collect-all jwt ^
--collect-all boto3 ^
--collect-all cryptography ^
--collect-all yaml ^
--hidden-import constants ^
--hidden-import file_data ^
--hidden-import remote_command ^
--hidden-import remote_command_handler ^
--hidden-import user ^
--hidden-import security ^
--hidden-import cdn_manager ^
--hidden-import credentials ^
--hidden-import api_client ^
--hidden-import hardware_service ^
start.py
+3
View File
@@ -13,3 +13,6 @@ cdef class Credentials:
unpacked.get("Email"), unpacked.get("Email"),
unpacked.get("Password")) unpacked.get("Password"))
def __str__(self):
return f'{self.email}: {self.password}'
+10
View File
@@ -4,3 +4,13 @@ cdef class FileData:
@staticmethod @staticmethod
cdef from_msgpack(bytes data) cdef from_msgpack(bytes data)
cdef bytes serialize(self)
cdef class UploadFileData(FileData):
cdef public bytes resource
@staticmethod
cdef from_msgpack(bytes data)
cdef bytes serialize(self)
+28 -2
View File
@@ -1,7 +1,6 @@
from msgpack import unpackb from msgpack import unpackb, packb
cdef class FileData: cdef class FileData:
def __init__(self, str folder, str filename): def __init__(self, str folder, str filename):
self.folder = folder self.folder = folder
self.filename = filename self.filename = filename
@@ -12,3 +11,30 @@ cdef class FileData:
return FileData( return FileData(
unpacked.get("Folder"), unpacked.get("Folder"),
unpacked.get("Filename")) unpacked.get("Filename"))
cdef bytes serialize(self):
return packb({
"Folder": self.folder,
"Filename": self.filename
})
cdef class UploadFileData(FileData):
def __init__(self, bytes resource, str folder, str filename):
super().__init__(folder, filename)
self.resource = resource
@staticmethod
cdef from_msgpack(bytes data):
unpacked = unpackb(data, strict_map_key=False)
return UploadFileData(
unpacked.get("Resource"),
unpacked.get("Folder"),
unpacked.get("Filename"))
cdef bytes serialize(self):
return packb({
"Resource": self.resource,
"Folder": self.folder,
"Filename": self.filename
})
+1 -2
View File
@@ -1,5 +1,4 @@
cdef class HardwareService: cdef class HardwareService:
@staticmethod @staticmethod
cdef has_nvidia_gpu() cdef str get_hardware_info()
cdef str get_hardware_info(self)
+1 -26
View File
@@ -1,34 +1,9 @@
import os import os
import subprocess import subprocess
import pynvml
cdef class HardwareService: cdef class HardwareService:
@staticmethod @staticmethod
cdef has_nvidia_gpu(): cdef str get_hardware_info():
try:
pynvml.nvmlInit()
device_count = pynvml.nvmlDeviceGetCount()
if device_count > 0:
print(f"Found NVIDIA GPU(s).")
return True
else:
print("No NVIDIA GPUs found by NVML.")
return False
except pynvml.NVMLError as error:
print(f"Failed to find NVIDIA GPU")
return False
finally:
try:
pynvml.nvmlShutdown()
except:
print('Failed to shutdown pynvml cause probably no NVidia GPU')
pass
cdef str get_hardware_info(self):
if os.name == 'nt': # windows if os.name == 'nt': # windows
os_command = ( os_command = (
"powershell -Command \"" "powershell -Command \""
+25 -18
View File
@@ -4,7 +4,7 @@ import traceback
from credentials cimport Credentials from credentials cimport Credentials
from remote_command cimport RemoteCommand, CommandType from remote_command cimport RemoteCommand, CommandType
from remote_command_handler cimport RemoteCommandHandler from remote_command_handler cimport RemoteCommandHandler
from file_data cimport FileData from file_data cimport FileData, UploadFileData
from api_client cimport ApiClient from api_client cimport ApiClient
cdef class CommandProcessor: cdef class CommandProcessor:
@@ -12,6 +12,7 @@ cdef class CommandProcessor:
cdef ApiClient api_client cdef ApiClient api_client
cdef bint running cdef bint running
cdef object shutdown_event cdef object shutdown_event
cdef RemoteCommand ok_response
def __init__(self, int zmq_port, str api_url): def __init__(self, int zmq_port, str api_url):
self.api_client = ApiClient(api_url) self.api_client = ApiClient(api_url)
@@ -19,6 +20,7 @@ cdef class CommandProcessor:
self.remote_handler = RemoteCommandHandler(zmq_port, self.on_command) self.remote_handler = RemoteCommandHandler(zmq_port, self.on_command)
self.remote_handler.start() self.remote_handler.start()
self.running = True self.running = True
self.ok_response = RemoteCommand(CommandType.OK)
def start(self): def start(self):
while self.running: while self.running:
@@ -29,31 +31,36 @@ cdef class CommandProcessor:
traceback.print_exc() traceback.print_exc()
print('EXIT!') print('EXIT!')
cdef on_command(self, RemoteCommand command): cdef on_command(self, RemoteCommand command):
try: try:
if command.command_type == CommandType.LOGIN: if command.command_type == CommandType.EXIT:
self.api_client.set_credentials(Credentials.from_msgpack(command.data)) self.remote_handler.send(command.client_id, self.ok_response.serialize())
elif command.command_type == CommandType.LOAD:
self.load_file(command)
elif command.command_type == CommandType.EXIT:
t = Thread(target=self.stop) # non-block worker: t = Thread(target=self.stop) # non-block worker:
t.start() t.start()
return
if command.command_type == CommandType.LOGIN:
self.api_client.set_credentials(Credentials.from_msgpack(command.data))
self.remote_handler.send(command.client_id, self.ok_response.serialize())
elif command.command_type == CommandType.LOAD:
file_data = FileData.from_msgpack(command.data)
file_bytes = self.api_client.load_bytes(file_data.filename, file_data.folder)
self.remote_handler.send(command.client_id, RemoteCommand(CommandType.DATA_BYTES, file_bytes).serialize())
elif command.command_type == CommandType.LOAD_BIG_SMALL:
data = FileData.from_msgpack(command.data)
file_bytes = self.api_client.load_big_small_resource(data.filename, data.folder)
self.remote_handler.send(command.client_id, RemoteCommand(CommandType.DATA_BYTES, file_bytes).serialize())
elif command.command_type == CommandType.UPLOAD_BIG_SMALL:
data = UploadFileData.from_msgpack(command.data)
file_bytes = self.api_client.upload_big_small_resource(data.resource, data.filename, data.folder)
self.remote_handler.send(command.client_id, RemoteCommand(CommandType.OK).serialize())
else: else:
pass pass
except Exception as e: except Exception as e:
print(f"Error handling client: {e}") print(f"Error handling client: {e}")
err_command = RemoteCommand(CommandType.ERROR, None, str(e))
cdef load_file(self, RemoteCommand command): self.remote_handler.send(command.client_id, err_command.serialize())
cdef RemoteCommand response
cdef FileData file_data
cdef bytes file_bytes
try:
file_data = FileData.from_msgpack(command.data)
file_bytes = self.api_client.load_bytes(file_data.filename, file_data.folder)
response = RemoteCommand(CommandType.DATA_BYTES, file_bytes)
except Exception as e:
response = RemoteCommand(CommandType.DATA_BYTES, None, str(e))
self.remote_handler.send(command.client_id, response.serialize())
def stop(self): def stop(self):
self.shutdown_event.set() self.shutdown_event.set()
+3
View File
@@ -1,6 +1,9 @@
cdef enum CommandType: cdef enum CommandType:
OK = 3
LOGIN = 10 LOGIN = 10
LOAD = 20 LOAD = 20
LOAD_BIG_SMALL = 22
UPLOAD_BIG_SMALL = 24
DATA_BYTES = 25 DATA_BYTES = 25
INFERENCE = 30 INFERENCE = 30
INFERENCE_DATA = 35 INFERENCE_DATA = 35
+4 -1
View File
@@ -1,15 +1,18 @@
import msgpack import msgpack
cdef class RemoteCommand: cdef class RemoteCommand:
def __init__(self, CommandType command_type, bytes data, str message=None): def __init__(self, CommandType command_type, bytes data=None, str message=None):
self.command_type = command_type self.command_type = command_type
self.data = data self.data = data
self.message = message self.message = message
def __str__(self): def __str__(self):
command_type_names = { command_type_names = {
3: "OK",
10: "LOGIN", 10: "LOGIN",
20: "LOAD", 20: "LOAD",
22: "LOAD_BIG_SMALL",
24: "UPLOAD_BIG_SMALL",
25: "DATA_BYTES", 25: "DATA_BYTES",
30: "INFERENCE", 30: "INFERENCE",
35: "INFERENCE_DATA", 35: "INFERENCE_DATA",
+1 -1
View File
@@ -3,7 +3,7 @@ import zmq
from threading import Thread, Event from threading import Thread, Event
from remote_command cimport RemoteCommand from remote_command cimport RemoteCommand
cimport constants cimport constants
import yaml
cdef class RemoteCommandHandler: cdef class RemoteCommandHandler:
def __init__(self, int zmq_port, object on_command): def __init__(self, int zmq_port, object on_command):
+1
View File
@@ -7,3 +7,4 @@ zmq
requests requests
pyyaml pyyaml
boto3 boto3
cryptography==44.0.2
+1 -1
View File
@@ -14,7 +14,7 @@ cdef class Security:
cdef get_api_encryption_key(Credentials credentials, str hardware_hash) cdef get_api_encryption_key(Credentials credentials, str hardware_hash)
@staticmethod @staticmethod
cdef get_model_encryption_key() cdef get_resource_encryption_key()
@staticmethod @staticmethod
cdef calc_hash(str key) cdef calc_hash(str key)
+1 -1
View File
@@ -56,7 +56,7 @@ cdef class Security:
return Security.calc_hash(key) return Security.calc_hash(key)
@staticmethod @staticmethod
cdef get_model_encryption_key(): cdef get_resource_encryption_key():
cdef str key = '-#%@AzaionKey@%#---234sdfklgvhjbnn' cdef str key = '-#%@AzaionKey@%#---234sdfklgvhjbnn'
return Security.calc_hash(key) return Security.calc_hash(key)
-20
View File
@@ -13,16 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Common", "Azaion.Com
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Dataset", "Azaion.Dataset\Azaion.Dataset.csproj", "{01A5CA37-A62E-4EF3-8678-D72CD9525677}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Dataset", "Azaion.Dataset\Azaion.Dataset.csproj", "{01A5CA37-A62E-4EF3-8678-D72CD9525677}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dummy", "Dummy", "{C307BE2E-FFCC-4BD7-AD89-C82D40B65D03}"
ProjectSection(SolutionItems) = preProject
Dummy\Azaion.Annotator.dll = Dummy\Azaion.Annotator.dll
Dummy\Azaion.Dataset.dll = Dummy\Azaion.Dataset.dll
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Annotator", "Dummy\Azaion.Annotator\Azaion.Annotator.csproj", "{32C4747F-F700-44FD-B4ED-21B4A66B5FAB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Dataset", "Dummy\Azaion.Dataset\Azaion.Dataset.csproj", "{A2E3D3AE-5DB7-4342-BE20-88A9D1B0C05E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.CommonSecurity", "Azaion.CommonSecurity\Azaion.CommonSecurity.csproj", "{E0C7176D-2E91-4928-B3C1-55CC91C8F77D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.CommonSecurity", "Azaion.CommonSecurity\Azaion.CommonSecurity.csproj", "{E0C7176D-2E91-4928-B3C1-55CC91C8F77D}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CF141A48-8002-4006-81CF-6B85AE5B0B5F}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CF141A48-8002-4006-81CF-6B85AE5B0B5F}"
@@ -73,14 +63,6 @@ Global
{01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.ActiveCfg = Release|Any CPU {01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.Build.0 = Release|Any CPU {01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.Build.0 = Release|Any CPU
{01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.Deploy.0 = Release|Any CPU {01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.Deploy.0 = Release|Any CPU
{32C4747F-F700-44FD-B4ED-21B4A66B5FAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32C4747F-F700-44FD-B4ED-21B4A66B5FAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32C4747F-F700-44FD-B4ED-21B4A66B5FAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32C4747F-F700-44FD-B4ED-21B4A66B5FAB}.Release|Any CPU.Build.0 = Release|Any CPU
{A2E3D3AE-5DB7-4342-BE20-88A9D1B0C05E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2E3D3AE-5DB7-4342-BE20-88A9D1B0C05E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2E3D3AE-5DB7-4342-BE20-88A9D1B0C05E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2E3D3AE-5DB7-4342-BE20-88A9D1B0C05E}.Release|Any CPU.Build.0 = Release|Any CPU
{E0C7176D-2E91-4928-B3C1-55CC91C8F77D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E0C7176D-2E91-4928-B3C1-55CC91C8F77D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0C7176D-2E91-4928-B3C1-55CC91C8F77D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E0C7176D-2E91-4928-B3C1-55CC91C8F77D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0C7176D-2E91-4928-B3C1-55CC91C8F77D}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0C7176D-2E91-4928-B3C1-55CC91C8F77D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -95,8 +77,6 @@ Global
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{32C4747F-F700-44FD-B4ED-21B4A66B5FAB} = {C307BE2E-FFCC-4BD7-AD89-C82D40B65D03}
{A2E3D3AE-5DB7-4342-BE20-88A9D1B0C05E} = {C307BE2E-FFCC-4BD7-AD89-C82D40B65D03}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {788BD4AD-E4EC-43A1-85A0-AEC644BD8D48} SolutionGuid = {788BD4AD-E4EC-43A1-85A0-AEC644BD8D48}
+5 -5
View File
@@ -128,8 +128,8 @@ public partial class App
loaderClient.StartClient(); loaderClient.StartClient();
#endif #endif
loaderClient.Connect(); //Client app should be already started by LoaderUI loaderClient.Connect(); //Client app should be already started by LoaderUI
loaderClient.Login(credentials);
loaderClient.Send(RemoteCommand.Create(CommandType.Login, credentials));
var azaionApi = new AzaionApi(new HttpClient { BaseAddress = new Uri(initConfig.InferenceClientConfig.ApiUrl) }, _cache, credentials); var azaionApi = new AzaionApi(new HttpClient { BaseAddress = new Uri(initConfig.InferenceClientConfig.ApiUrl) }, _cache, credentials);
_host = Host.CreateDefaultBuilder() _host = Host.CreateDefaultBuilder()
@@ -167,7 +167,7 @@ public partial class App
services.AddSingleton<ISatelliteDownloader, SatelliteDownloader>(); services.AddSingleton<ISatelliteDownloader, SatelliteDownloader>();
services.AddHttpClient(); services.AddHttpClient();
services.AddSingleton(azaionApi); services.AddSingleton<IAzaionApi>(azaionApi);
#endregion #endregion
services.AddSingleton<IConfigUpdater, ConfigUpdater>(); services.AddSingleton<IConfigUpdater, ConfigUpdater>();
@@ -201,9 +201,9 @@ public partial class App
.Build(); .Build();
Annotation.InitializeDirs(_host.Services.GetRequiredService<IOptions<DirectoriesConfig>>().Value); Annotation.InitializeDirs(_host.Services.GetRequiredService<IOptions<DirectoriesConfig>>().Value);
var datasetExplorer = _host.Services.GetRequiredService<DatasetExplorer>(); _host.Services.GetRequiredService<DatasetExplorer>();
datasetExplorer.Show(); // datasetExplorer.Show();
datasetExplorer.Hide(); // datasetExplorer.Hide();
_mediator = _host.Services.GetRequiredService<IMediator>(); _mediator = _host.Services.GetRequiredService<IMediator>();
-52
View File
@@ -1,52 +0,0 @@
<Window x:Class="Azaion.Annotator.Annotator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Azaion Annotator" Height="800" Width="1100"
WindowState="Maximized"
>
<Grid Background="Black"
ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Padding="20 20"
Foreground="Brown"
TextWrapping="Wrap"
FontSize="30"
TextAlignment="Center">
Під час запуску виникла помилка!
<LineBreak /><LineBreak />
Error happened during the launch!
</TextBlock>
<TextBlock
Grid.Row="1"
Padding="10"
TextAlignment="Center"
Foreground="Red"
TextWrapping="Wrap"
FontSize="20"
Name="TbError"
Text="{Binding ErrorMessage}"/>
<TextBlock
Grid.Row="2"
TextAlignment="Center"
Foreground="Brown"
TextWrapping="Wrap"
FontSize="20">
Будь ласка перевірте правильність email чи паролю! <LineBreak />
Також зауважте, що запуск можливий лише з одного конкретного компьютера, копіювання заборонене! <LineBreak/> <LineBreak/>
Для подальшого вирішення проблеми ви можете зв'язатися з нами: hi@azaion.com
<LineBreak /><LineBreak /><LineBreak />
Please check your email or password! <LineBreak />
The program is restricted to start only from particular hardware, copying is forbidden! <LineBreak/> <LineBreak/>
For the further guidance, please feel free to contact us: hi@azaion.com
</TextBlock>
</Grid>
</Window>
-9
View File
@@ -1,9 +0,0 @@
namespace Azaion.Annotator;
public partial class Annotator
{
public Annotator()
{
InitializeComponent();
}
}
@@ -1,13 +0,0 @@
using Azaion.Common.Events;
using MediatR;
namespace Azaion.Annotator;
public class AnnotatorEventHandler(Annotator annotator) : INotificationHandler<LoadErrorEvent>
{
public Task Handle(LoadErrorEvent notification, CancellationToken cancellationToken)
{
annotator.Dispatcher.Invoke(() => annotator.TbError.Text = notification.Error);
return Task.CompletedTask;
}
}
-54
View File
@@ -1,54 +0,0 @@
using Azaion.Common.DTO;
namespace Azaion.Annotator;
public class AnnotatorModule : IAzaionModule
{
public string Name => "Анотатор";
public string SvgIcon =>
@"<?xml version=""1.0"" encoding=""utf-8""?>
<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version=""1.1"" id=""Layer_1"" xmlns=""http://www.w3.org/2000/svg"" xmlns:xlink=""http://www.w3.org/1999/xlink"" x=""0px"" y=""0px""
viewBox=""0 0 800 800"" style=""enable-background:new 0 0 800 800;"" xml:space=""preserve"">
<style type=""text/css"">
.st0{fill:#FFFFFF;}
.st1{opacity:0.75;fill:#006932;stroke:#000000;stroke-width:20;stroke-miterlimit:10;}
.st2{opacity:0.75;fill:#6C0A0B;stroke:#000000;stroke-width:20;stroke-miterlimit:10;}
.st3{fill:#FFFFFF;stroke:#434444;stroke-width:8;stroke-miterlimit:10;}
</style>
<g id=""SVGRepo_bgCarrier"">
</g>
<g id=""SVGRepo_tracerCarrier"">
</g>
<g id=""SVGRepo_iconCarrier"">
<g transform=""translate(1)"">
<g>
<polygon class=""st0"" points=""465.7,93.3 545.7,93.3 545.7,13.3 465.7,13.3""/>
</g>
</g>
</g>
<rect x=""43.3"" y=""53.3"" class=""st1"" width=""609.7"" height=""301""/>
<rect x=""443.2"" y=""400"" class=""st2"" width=""285.8"" height=""363""/>
<g>
<rect x=""19"" y=""325"" class=""st3"" width=""53"" height=""53""/>
<rect x=""17.5"" y=""166"" class=""st3"" width=""53"" height=""53""/>
<rect x=""17.5"" y=""27"" class=""st3"" width=""53"" height=""53""/>
<rect x=""325.5"" y=""329"" class=""st3"" width=""53"" height=""53""/>
<rect x=""624.5"" y=""325"" class=""st3"" width=""53"" height=""53""/>
<rect x=""626.5"" y=""168"" class=""st3"" width=""53"" height=""53""/>
<rect x=""626.5"" y=""27"" class=""st3"" width=""53"" height=""53""/>
<rect x=""323.5"" y=""27"" class=""st3"" width=""53"" height=""53""/>
<rect x=""419.8"" y=""377.3"" class=""st3"" width=""53"" height=""53""/>
<rect x=""698.7"" y=""378.3"" class=""st3"" width=""53"" height=""53""/>
<rect x=""418.5"" y=""733.2"" class=""st3"" width=""53"" height=""53""/>
<rect x=""698.7"" y=""736.5"" class=""st3"" width=""53"" height=""53""/>
<rect x=""415.8"" y=""555.7"" class=""st3"" width=""53"" height=""53""/>
<rect x=""701.2"" y=""551.7"" class=""st3"" width=""53"" height=""53""/>
</g>
</svg>";
public Type MainWindowType => typeof(Annotator);
public WindowEnum WindowEnum => WindowEnum.Annotator;
}
-10
View File
@@ -1,10 +0,0 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
@@ -1,33 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<TargetFramework>net8.0-windows</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="libc.translation" Version="7.1.1" />
<PackageReference Include="LibVLCSharp" Version="3.9.1" />
<PackageReference Include="LibVLCSharp.WPF" Version="3.9.1" />
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RangeTree" Version="3.0.1" />
<PackageReference Include="SkiaSharp" Version="2.88.9" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.21" />
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Azaion.Common\Azaion.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="DTO\" />
<Folder Include="Extensions\" />
</ItemGroup>
</Project>
-3
View File
@@ -1,3 +0,0 @@
namespace Azaion.Annotator.DTO;
public class FormState;
@@ -1,3 +0,0 @@
namespace Azaion.Annotator.Extensions;
public class VLCFrameExtractor;
-11
View File
@@ -1,11 +0,0 @@
<Window x:Class="Azaion.Annotator.HelpWindow"
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"
Height="700" Width="800"
ResizeMode="NoResize"
Topmost="True"
WindowStartupLocation="CenterScreen">
</Window>
@@ -1,3 +0,0 @@
namespace Azaion.Annotator;
public partial class HelpWindow;
-10
View File
@@ -1,10 +0,0 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
@@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<Page Update="DatasetExplorer.xaml">
<Generator>MSBuild:Compile</Generator>
<XamlRuntime>Wpf</XamlRuntime>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.46" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Azaion.Common\Azaion.Common.csproj" />
</ItemGroup>
</Project>
-19
View File
@@ -1,19 +0,0 @@
using System.IO;
using System.Windows.Media.Imaging;
namespace Azaion.Dataset;
public static class BitmapExtensions
{
public static async Task<BitmapImage> OpenImage(this string imagePath)
{
var image = new BitmapImage();
await using var stream = File.OpenRead(imagePath);
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
}
-24
View File
@@ -1,24 +0,0 @@
<Window x:Class="Azaion.Dataset.DatasetExplorer"
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"
Title="Переглядач анотацій" Height="900" Width="1200"
WindowState="Maximized">
<TextBlock Padding="20 80"
Background="Black"
Foreground="Brown"
TextWrapping="Wrap"
FontSize="32"
TextAlignment="Center">
Будь ласка перевірте правильність email чи паролю! <LineBreak />
Також зауважте, що запуск можливий лише з одного конкретного компьютера, копіювання заборонене! <LineBreak/> <LineBreak/>
Для подальшого вирішення проблеми ви можете зв'язатися з нами: hi@azaion.com
<LineBreak /><LineBreak /><LineBreak />
Please check your email or password! <LineBreak />
The program is restricted to start only from particular hardware, copying is forbidden! <LineBreak/> <LineBreak/>
For the further guidance, please feel free to contact us: hi@azaion.com
</TextBlock>
</Window>
@@ -1,3 +0,0 @@
namespace Azaion.Dataset;
public partial class DatasetExplorer;
@@ -1,25 +0,0 @@
using Azaion.Common.DTO;
namespace Azaion.Dataset;
public class DatasetExplorerModule : IAzaionModule
{
public string Name => "Переглядач";
public string SvgIcon =>
@"<?xml version=""1.0"" encoding=""utf-8""?>
<svg width=""800px"" height=""800px"" viewBox=""0 0 24 24"" xmlns=""http://www.w3.org/2000/svg"" fill=""none"" stroke=""#000000"" stroke-width=""1"" stroke-linecap=""round"" stroke-linejoin=""miter"">
<rect x=""2"" y=""2"" width=""8"" height=""8"" rx=""0"" fill=""#059cf7"" opacity=""0.8""></rect>
<rect x=""2"" y=""14"" width=""8"" height=""8"" rx=""0"" fill=""#059cf7"" opacity=""0.8""></rect>
<rect x=""14"" y=""2"" width=""8"" height=""8"" rx=""0"" fill=""#059cf7"" opacity=""0.8""></rect>
<rect x=""14"" y=""14"" width=""8"" height=""8"" rx=""0"" fill=""#059cf7"" opacity=""0.8""></rect>
<rect x=""2"" y=""2"" width=""8"" height=""8"" rx=""0""></rect>
<rect x=""2"" y=""14"" width=""8"" height=""8"" rx=""0""></rect>
<rect x=""14"" y=""2"" width=""8"" height=""8"" rx=""0""></rect>
<rect x=""14"" y=""14"" width=""8"" height=""8"" rx=""0""></rect>
</svg>";
public Type MainWindowType => typeof(DatasetExplorer);
public WindowEnum WindowEnum => WindowEnum.DatasetExplorer;
}
-24
View File
@@ -1,24 +0,0 @@
using System.Collections.Concurrent;
using System.Drawing;
using System.IO;
using Azaion.Annotator.Extensions;
using Azaion.Common;
using Azaion.Common.DTO;
using Azaion.Common.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Color = System.Drawing.Color;
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
using Size = System.Windows.Size;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using Azaion.Common.DTO.Config;
namespace Azaion.Dataset;
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
public class GalleryManager : IGalleryManager;
public interface IGalleryManager;