mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 12:36:31 +00:00
rework to Azaion.Suite, show tabs with annotator and dataset explorer
This commit is contained in:
@@ -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
@@ -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();
|
||||
|
||||
@@ -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,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!;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user