rework to Azaion.Suite, show tabs with annotator and dataset explorer

This commit is contained in:
Alex Bezdieniezhnykh
2024-11-23 08:53:12 +02:00
parent 490e90f239
commit 3b40bd601e
40 changed files with 374 additions and 284 deletions
+1
View File
@@ -10,6 +10,7 @@
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
+39 -18
View File
@@ -5,9 +5,17 @@ namespace Azaion.Common;
public class Constants
{
#region DefaultConfig
public const string CONFIG_PATH = "config.json";
public const string DEFAULT_DLL_CACHE_DIR = "DllCache";
#region ApiConfig
public const string DEFAULT_API_URL = "https://api.azaion.com/";
public const int DEFAULT_API_RETRY_COUNT = 3;
public const int DEFAULT_API_TIMEOUT_SECONDS = 40;
#endregion ApiConfig
#region DirectoriesConfig
public const string DEFAULT_VIDEO_DIR = "video";
public const string DEFAULT_LABELS_DIR = "labels";
@@ -15,24 +23,9 @@ public class Constants
public const string DEFAULT_RESULTS_DIR = "results";
public const string DEFAULT_THUMBNAILS_DIR = "thumbnails";
public const int DEFAULT_THUMBNAIL_BORDER = 10;
public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2;
public const double TRACKING_DISTANCE_CONFIDENCE = 0.15;
public const double TRACKING_PROBABILITY_INCREASE = 15;
public const double TRACKING_INTERSECTION_THRESHOLD = 0.8;
public static readonly Size DefaultWindowSize = new(1280, 720);
public static readonly Point DefaultWindowLocation = new(100, 100);
public static readonly Size DefaultThumbnailSize = new(240, 135);
#endregion
#region Thumbnails
public const string THUMBNAIL_PREFIX = "_thumb";
public const string THUMBNAILS_CACHE_FILE = "thumbnails.cache";
#endregion
#region AnnotatorConfig
public static readonly List<AnnotationClass> DefaultAnnotationClasses =
[
@@ -52,6 +45,34 @@ public class Constants
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
public static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"];
# endregion AnnotatorConfig
# region AIRecognitionConfig
public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2;
public const double TRACKING_DISTANCE_CONFIDENCE = 0.15;
public const double TRACKING_PROBABILITY_INCREASE = 15;
public const double TRACKING_INTERSECTION_THRESHOLD = 0.8;
# endregion AIRecognitionConfig
# region WindowConfig
public static readonly Size DefaultWindowSize = new(1280, 720);
public static readonly Point DefaultWindowLocation = new(100, 100);
public static readonly Size DefaultThumbnailSize = new(240, 135);
#endregion
#region Thumbnails
public const int DEFAULT_THUMBNAIL_BORDER = 10;
public const string THUMBNAIL_PREFIX = "_thumb";
public const string THUMBNAILS_CACHE_FILE = "thumbnails.cache";
#endregion
public static TimeSpan? GetTime(string imagePath)
{
var timeStr = imagePath.Split("_").LastOrDefault();
+2 -2
View File
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
using System.Windows.Media;
using System.Windows.Media;
using Azaion.Common.Extensions;
using Newtonsoft.Json;
namespace Azaion.Common.DTO;
@@ -1,8 +1,7 @@
namespace Azaion.Annotator.DTO;
namespace Azaion.Common.DTO.Config;
public class AIRecognitionConfig
{
public string AIModelPath { get; set; } = null!;
public double FrameRecognitionSeconds { get; set; }
public double TrackingDistanceConfidence { get; set; }
public double TrackingProbabilityIncrease { get; set; }
+1 -6
View File
@@ -1,4 +1,4 @@
namespace Azaion.Suite.Services.DTO;
namespace Azaion.Common.DTO.Config;
public class ApiConfig
{
@@ -6,8 +6,3 @@ public class ApiConfig
public int RetryCount {get;set;}
public double TimeoutSeconds { get; set; }
}
public class LocalFilesConfig
{
public string DllPath { get; set; } = null!;
}
+9 -7
View File
@@ -1,8 +1,5 @@
using System.IO;
using System.Text;
using Azaion.Annotator.DTO;
using Azaion.Suite.Services.DTO;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
namespace Azaion.Common.DTO.Config;
@@ -11,14 +8,14 @@ public class AppConfig
{
public ApiConfig ApiConfig { get; set; } = null!;
public DirectoriesConfig DirectoriesConfig { get; set; } = null!;
public AnnotationConfig AnnotationConfig { get; set; } = null!;
public WindowConfig WindowConfig { get; set; } = null!;
public AIRecognitionConfig AIRecognitionConfig { get; set; } = null!;
public DirectoriesConfig DirectoriesConfig { get; set; } = null!;
public ThumbnailConfig ThumbnailConfig { get; set; } = null!;
}
@@ -40,6 +37,13 @@ public class ConfigUpdater : IConfigUpdater
var appConfig = new AppConfig
{
ApiConfig = new ApiConfig
{
Url = Constants.DEFAULT_API_URL,
RetryCount = Constants.DEFAULT_API_RETRY_COUNT,
TimeoutSeconds = Constants.DEFAULT_API_TIMEOUT_SECONDS
},
AnnotationConfig = new AnnotationConfig
{
AnnotationClasses = Constants.DefaultAnnotationClasses,
@@ -51,7 +55,6 @@ public class ConfigUpdater : IConfigUpdater
{
WindowSize = Constants.DefaultWindowSize,
WindowLocation = Constants.DefaultWindowLocation,
ShowHelpOnStart = true,
FullScreen = true,
LeftPanelWidth = 250,
RightPanelWidth = 250,
@@ -74,7 +77,6 @@ public class ConfigUpdater : IConfigUpdater
AIRecognitionConfig = new AIRecognitionConfig
{
AIModelPath = "azaion.onnx",
FrameRecognitionSeconds = Constants.DEFAULT_FRAME_RECOGNITION_SECONDS,
TrackingDistanceConfidence = Constants.TRACKING_DISTANCE_CONFIDENCE,
TrackingProbabilityIncrease = Constants.TRACKING_PROBABILITY_INCREASE,
@@ -1,4 +1,4 @@
namespace Azaion.Common.DTO;
namespace Azaion.Common.DTO.Config;
public class DirectoriesConfig
{
+11
View File
@@ -0,0 +1,11 @@
namespace Azaion.Common.DTO;
public class HardwareInfo
{
public string CPU { get; set; } = null!;
public string GPU { get; set; } = null!;
public string MacAddress { get; set; } = null!;
public string Memory { get; set; } = null!;
public string Hash { get; set; } = null!;
}
@@ -0,0 +1,10 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Azaion.Common.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection ConfigureSection<T>(this IServiceCollection services, IConfiguration config) where T: class =>
services.Configure<T>(config.GetSection(typeof(T).Name));
}
+91
View File
@@ -0,0 +1,91 @@
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Text;
using Azaion.Common.DTO;
using Newtonsoft.Json;
namespace Azaion.Common.Services;
public class AzaionApiClient(HttpClient httpClient) : IDisposable
{
const string JSON_MEDIA = "application/json";
private string Email { get; set; } = null!;
private SecureString Password { get; set; } = new();
private string JwtToken { get; set; } = null!;
public void Login(string email, string password)
{
if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password))
throw new Exception("Email or password is empty!");
Email = email;
Password = password.ToSecureString();
}
public async Task<Stream> GetResource(string fileName, string password, HardwareInfo hardware)
{
var response = await Send(httpClient, new HttpRequestMessage(HttpMethod.Post, "/resources/get")
{
Content = new StringContent(JsonConvert.SerializeObject(new { fileName, password, hardware }), Encoding.UTF8, JSON_MEDIA)
});
return await response.Content.ReadAsStreamAsync();
}
private async Task<string> Authorize()
{
if (string.IsNullOrEmpty(Email) || Password.Length == 0)
throw new Exception("Email or password is empty! Please do Login first!");
var payload = new
{
email = Email,
password = Password.ToRealString()
};
var response = await httpClient.PostAsync(
"login",
new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, JSON_MEDIA));
if (!response.IsSuccessStatusCode)
throw new Exception($"Login failed: {response.StatusCode}");
var responseData = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<LoginResponse>(responseData);
if (string.IsNullOrEmpty(result?.Token))
throw new Exception("JWT Token not found in response");
return result.Token;
}
private async Task<HttpResponseMessage> Send(HttpClient client, HttpRequestMessage request)
{
if (string.IsNullOrEmpty(JwtToken))
JwtToken = await Authorize();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken);
var response = await client.SendAsync(request);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
JwtToken = await Authorize();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken);
response = await client.SendAsync(request);
}
if (response.IsSuccessStatusCode)
return response;
var result = await response.Content.ReadAsStringAsync();
throw new Exception($"Failed: {response.StatusCode}! Result: {result}");
}
public void Dispose()
{
httpClient.Dispose();
Password.Dispose();
}
}
+108
View File
@@ -0,0 +1,108 @@
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using Azaion.Common.DTO;
namespace Azaion.Common.Services;
public interface IHardwareService
{
Task<HardwareInfo> GetHardware();
}
public class HardwareService : IHardwareService
{
private const string WIN32_GET_HARDWARE_COMMAND =
"wmic OS get TotalVisibleMemorySize /Value && " +
"wmic CPU get Name /Value && " +
"wmic path Win32_VideoController get Name /Value";
private const string UNIX_GET_HARDWARE_COMMAND =
"/bin/bash -c \"free -g | grep Mem: | awk '{print $2}' && " +
"lscpu | grep 'Model name:' | cut -d':' -f2 && " +
"lspci | grep VGA | cut -d':' -f3\"";
public async Task<HardwareInfo> GetHardware()
{
try
{
var output = await RunCommand(Environment.OSVersion.Platform == PlatformID.Win32NT
? WIN32_GET_HARDWARE_COMMAND
: UNIX_GET_HARDWARE_COMMAND);
var lines = output
.Replace("TotalVisibleMemorySize=", "")
.Replace("Name=", "")
.Replace(" ", " ")
.Trim()
.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
var memoryStr = "Unknown RAM";
if (lines.Length > 0)
{
memoryStr = lines[0];
if (int.TryParse(memoryStr, out var memKb))
memoryStr = $"{Math.Round(memKb / 1024.0 / 1024.0)} Gb";
}
var hardwareInfo = new HardwareInfo
{
Memory = memoryStr,
CPU = lines.Length > 1 && string.IsNullOrEmpty(lines[1])
? "Unknown RAM"
: lines[1],
GPU = lines.Length > 2 && string.IsNullOrEmpty(lines[2])
? "Unknown GPU"
: lines[2]
};
hardwareInfo.Hash = ToHash($"Azaion_{MacAddress()}_{hardwareInfo.CPU}_{hardwareInfo.GPU}");
return hardwareInfo;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
private string MacAddress()
{
var macAddress = NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up)
.Select(nic => nic.GetPhysicalAddress().ToString())
.FirstOrDefault();
return macAddress ?? string.Empty;
}
private async Task<string> RunCommand(string command)
{
try
{
using var process = new Process();
process.StartInfo.FileName = Environment.OSVersion.Platform == PlatformID.Unix ? "/bin/bash" : "cmd.exe";
process.StartInfo.Arguments = Environment.OSVersion.Platform == PlatformID.Unix
? $"-c \"{command}\""
: $"/c {command}";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.Start();
var result = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
return result.Trim();
}
catch
{
return string.Empty;
}
}
private static string ToHash(string str) =>
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
}
+22
View File
@@ -0,0 +1,22 @@
using System.IO;
namespace Azaion.Common.Services;
public interface IResourceLoader
{
Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default);
}
public class ResourceLoader(string email, string password, AzaionApiClient api, IHardwareService hardwareService) : IResourceLoader
{
public async Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default)
{
var hardwareInfo = await hardwareService.GetHardware();
var encryptedStream = await api.GetResource(fileName, password, hardwareInfo);
var key = Security.MakeEncryptionKey(email, password, hardwareInfo.Hash);
var stream = new MemoryStream();
await encryptedStream.DecryptTo(stream, key, cancellationToken);
return stream;
}
}
+83
View File
@@ -0,0 +1,83 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
namespace Azaion.Common.Services;
public static class Security
{
private const int BUFFER_SIZE = 524288; // 512 KB buffer size
public static string ToHash(this string str) =>
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
public static string MakeEncryptionKey(string email, string password, string? hardwareHash) =>
$"{email}-{password}-{hardwareHash}-#%@AzaionKey@%#---".ToHash();
public static SecureString ToSecureString(this string str)
{
var secureString = new SecureString();
foreach (var c in str.ToCharArray())
secureString.AppendChar(c);
return secureString;
}
public static string? ToRealString(this SecureString value)
{
var valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
return Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
public static async Task EncryptTo(this Stream stream, Stream toStream, string key, CancellationToken cancellationToken = default)
{
if (stream is { CanRead: false }) throw new ArgumentNullException(nameof(stream));
if (key is not { Length: > 0 }) throw new ArgumentNullException(nameof(key));
using var aes = Aes.Create();
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
aes.GenerateIV();
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
await using var cs = new CryptoStream(toStream, encryptor, CryptoStreamMode.Write, leaveOpen: true);
// Prepend IV to the encrypted data
await toStream.WriteAsync(aes.IV.AsMemory(0, aes.IV.Length), cancellationToken);
var buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, cancellationToken)) > 0)
await cs.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
}
public static async Task DecryptTo(this Stream encryptedStream, Stream toStream, string key, CancellationToken cancellationToken = default)
{
using var aes = Aes.Create();
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
// Read the IV from the start of the input stream
var iv = new byte[aes.BlockSize / 8];
_ = await encryptedStream.ReadAsync(iv, cancellationToken);
aes.IV = iv;
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
await using var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read, leaveOpen: true);
// Read and write in chunks
var buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = await cryptoStream.ReadAsync(buffer, cancellationToken)) > 0)
await toStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
}
}