mirror of
https://github.com/azaion/annotations.git
synced 2026-04-23 05:56:30 +00:00
fix id problems with day/winter switch
This commit is contained in:
@@ -281,7 +281,6 @@ public partial class Annotator
|
|||||||
return;
|
return;
|
||||||
Dispatcher.Invoke(async () =>
|
Dispatcher.Invoke(async () =>
|
||||||
{
|
{
|
||||||
var canvasSize = Editor.RenderSize;
|
|
||||||
var videoSize = _formState.CurrentVideoSize;
|
var videoSize = _formState.CurrentVideoSize;
|
||||||
if (showImage)
|
if (showImage)
|
||||||
{
|
{
|
||||||
@@ -292,13 +291,7 @@ public partial class Annotator
|
|||||||
videoSize = Editor.RenderSize;
|
videoSize = Editor.RenderSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var detection in annotation.Detections)
|
Editor.CreateDetections(annotation.Time, annotation.Detections, _appConfig.AnnotationConfig.DetectionClasses, videoSize);
|
||||||
{
|
|
||||||
var annClass = _appConfig.AnnotationConfig.DetectionClasses[detection.ClassNumber];
|
|
||||||
var canvasLabel = new CanvasLabel(detection, canvasSize, videoSize, detection.Probability);
|
|
||||||
Editor.CreateDetectionControl(annClass, annotation.Time, canvasLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,24 @@ public class CanvasEditor : Canvas
|
|||||||
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (SelectionState == SelectionState.NewAnnCreating)
|
if (SelectionState == SelectionState.NewAnnCreating)
|
||||||
CreateDetectionControl(e.GetPosition(this));
|
{
|
||||||
|
var endPos = e.GetPosition(this);
|
||||||
|
_newAnnotationRect.Width = 0;
|
||||||
|
_newAnnotationRect.Height = 0;
|
||||||
|
var width = Math.Abs(endPos.X - _newAnnotationStartPos.X);
|
||||||
|
var height = Math.Abs(endPos.Y - _newAnnotationStartPos.Y);
|
||||||
|
if (width < MIN_SIZE || height < MIN_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var time = GetTimeFunc();
|
||||||
|
CreateDetectionControl(CurrentAnnClass, time, new CanvasLabel
|
||||||
|
{
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
X = Math.Min(endPos.X, _newAnnotationStartPos.X),
|
||||||
|
Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
SelectionState = SelectionState.None;
|
SelectionState = SelectionState.None;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@@ -291,26 +308,17 @@ public class CanvasEditor : Canvas
|
|||||||
SetTop(_newAnnotationRect, currentPos.Y);
|
SetTop(_newAnnotationRect, currentPos.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateDetectionControl(Point endPos)
|
public void CreateDetections(TimeSpan time, IEnumerable<Detection> detections, List<DetectionClass> detectionClasses, Size videoSize)
|
||||||
{
|
{
|
||||||
_newAnnotationRect.Width = 0;
|
foreach (var detection in detections)
|
||||||
_newAnnotationRect.Height = 0;
|
|
||||||
var width = Math.Abs(endPos.X - _newAnnotationStartPos.X);
|
|
||||||
var height = Math.Abs(endPos.Y - _newAnnotationStartPos.Y);
|
|
||||||
if (width < MIN_SIZE || height < MIN_SIZE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var time = GetTimeFunc();
|
|
||||||
CreateDetectionControl(CurrentAnnClass, time, new CanvasLabel
|
|
||||||
{
|
{
|
||||||
Width = width,
|
var annClass = DetectionClass.FromYoloId(detection.ClassNumber, detectionClasses);
|
||||||
Height = height,
|
var canvasLabel = new CanvasLabel(detection, RenderSize, videoSize, detection.Probability);
|
||||||
X = Math.Min(endPos.X, _newAnnotationStartPos.X),
|
CreateDetectionControl(annClass, time, canvasLabel);
|
||||||
Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y)
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DetectionControl CreateDetectionControl(DetectionClass annClass, TimeSpan time, CanvasLabel canvasLabel)
|
private void CreateDetectionControl(DetectionClass annClass, TimeSpan time, CanvasLabel canvasLabel)
|
||||||
{
|
{
|
||||||
var detectionControl = new DetectionControl(annClass, time, AnnotationResizeStart, canvasLabel.Probability)
|
var detectionControl = new DetectionControl(annClass, time, AnnotationResizeStart, canvasLabel.Probability)
|
||||||
{
|
{
|
||||||
@@ -323,7 +331,6 @@ public class CanvasEditor : Canvas
|
|||||||
Children.Add(detectionControl);
|
Children.Add(detectionControl);
|
||||||
CurrentDetections.Add(detectionControl);
|
CurrentDetections.Add(detectionControl);
|
||||||
_newAnnotationRect.Fill = new SolidColorBrush(annClass.Color);
|
_newAnnotationRect.Fill = new SolidColorBrush(annClass.Color);
|
||||||
return detectionControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ public class DetectionClass
|
|||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public SolidColorBrush ColorBrush => new(Color);
|
public SolidColorBrush ColorBrush => new(Color);
|
||||||
|
|
||||||
|
public static DetectionClass FromYoloId(int yoloId, List<DetectionClass> detectionClasses)
|
||||||
|
{
|
||||||
|
var cls = yoloId % 20;
|
||||||
|
var photoMode = (PhotoMode)(yoloId - cls);
|
||||||
|
var detClass = detectionClasses[cls];
|
||||||
|
detClass.PhotoMode = photoMode;
|
||||||
|
return detClass;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PhotoMode
|
public enum PhotoMode
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public static class ThrottleExt
|
|||||||
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken);
|
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken);
|
||||||
await func();
|
await func();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_taskStates[actionId] = false;
|
_taskStates[actionId] = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
await SaveAnnotationInner(DateTime.UtcNow, annotation.OriginalMediaName, annotation.Time, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null,
|
await SaveAnnotationInner(DateTime.UtcNow, annotation.OriginalMediaName, annotation.Time, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null,
|
||||||
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: false, token);
|
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: false, token);
|
||||||
|
|
||||||
|
// Manual save from Validators -> Validated -> stream: azaion-annotations-confirm
|
||||||
|
// AI, Manual save from Operators -> Created -> stream: azaion-annotations
|
||||||
private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string originalMediaName, TimeSpan time, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream,
|
private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string originalMediaName, TimeSpan time, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream,
|
||||||
RoleEnum userRole,
|
RoleEnum userRole,
|
||||||
string createdEmail,
|
string createdEmail,
|
||||||
@@ -132,8 +134,6 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
|||||||
var annotation = await _dbFactory.Run(async db =>
|
var annotation = await _dbFactory.Run(async db =>
|
||||||
{
|
{
|
||||||
var ann = await db.Annotations.FirstOrDefaultAsync(x => x.Name == fName, token: token);
|
var ann = await db.Annotations.FirstOrDefaultAsync(x => x.Name == fName, token: token);
|
||||||
// Manual Save from Validators -> Validated
|
|
||||||
// otherwise Created
|
|
||||||
status = userRole.IsValidator() && source == SourceEnum.Manual
|
status = userRole.IsValidator() && source == SourceEnum.Manual
|
||||||
? AnnotationStatus.Validated
|
? AnnotationStatus.Validated
|
||||||
: AnnotationStatus.Created;
|
: AnnotationStatus.Created;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class HardwareService : IHardwareService
|
|||||||
: lines[2],
|
: lines[2],
|
||||||
MacAddress = macAddress
|
MacAddress = macAddress
|
||||||
};
|
};
|
||||||
hardwareInfo.Hash = ToHash($"Azaion_{macAddress}_{hardwareInfo.CPU}_{hardwareInfo.GPU}");
|
hardwareInfo.Hash = ToHash($"Az|{hardwareInfo.CPU}|{hardwareInfo.GPU}|{macAddress}");
|
||||||
return hardwareInfo;
|
return hardwareInfo;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class PythonResourceLoader : IResourceLoader, IAuthProvider
|
|||||||
|
|
||||||
public PythonResourceLoader(PythonConfig config)
|
public PythonResourceLoader(PythonConfig config)
|
||||||
{
|
{
|
||||||
StartPython();
|
//StartPython();
|
||||||
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
|
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
|
||||||
_dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}");
|
_dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}");
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ public class PythonResourceLoader : IResourceLoader, IAuthProvider
|
|||||||
{
|
{
|
||||||
_dealer.SendFrame(RemoteCommand.Serialize(CommandType.Load, new LoadFileData(fileName, folder)));
|
_dealer.SendFrame(RemoteCommand.Serialize(CommandType.Load, new LoadFileData(fileName, folder)));
|
||||||
|
|
||||||
if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(3), out var bytes))
|
if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(300), out var bytes))
|
||||||
throw new Exception($"Unable to receive {fileName}");
|
throw new Exception($"Unable to receive {fileName}");
|
||||||
|
|
||||||
return new MemoryStream(bytes);
|
return new MemoryStream(bytes);
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Azaion.CommonSecurity.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -223,13 +223,7 @@ public partial class DatasetExplorer
|
|||||||
|
|
||||||
var time = ann.Time;
|
var time = ann.Time;
|
||||||
ExplorerEditor.RemoveAllAnns();
|
ExplorerEditor.RemoveAllAnns();
|
||||||
foreach (var deetection in ann.Detections)
|
ExplorerEditor.CreateDetections(time, ann.Detections, _annotationConfig.DetectionClasses, ExplorerEditor.RenderSize);
|
||||||
{
|
|
||||||
var annClass = _annotationConfig.DetectionClassesDict[deetection.ClassNumber];
|
|
||||||
var canvasLabel = new CanvasLabel(deetection, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize);
|
|
||||||
ExplorerEditor.CreateDetectionControl(annClass, time, canvasLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
ThumbnailLoading = false;
|
ThumbnailLoading = false;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ from credentials cimport Credentials
|
|||||||
|
|
||||||
|
|
||||||
cdef class ApiClient:
|
cdef class ApiClient:
|
||||||
cdef public Credentials credentials
|
cdef Credentials credentials
|
||||||
cdef str token, folder, api_url
|
cdef str token, folder, api_url
|
||||||
cdef User user
|
cdef User user
|
||||||
|
|
||||||
cdef get_encryption_key(self, str hardware_hash)
|
cdef set_credentials(self, Credentials credentials)
|
||||||
cdef login(self)
|
cdef login(self)
|
||||||
cdef set_token(self, str token)
|
cdef set_token(self, str token)
|
||||||
cdef get_user(self)
|
cdef get_user(self)
|
||||||
|
|
||||||
cdef load_bytes(self, str filename, str folder=*)
|
cdef load_bytes(self, str filename, str folder=*)
|
||||||
|
cdef upload_file(self, str filename, str folder=*)
|
||||||
cdef load_ai_model(self)
|
cdef load_ai_model(self)
|
||||||
cdef load_queue_config(self)
|
cdef load_queue_config(self)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import time
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
import jwt
|
import jwt
|
||||||
@@ -10,7 +8,6 @@ from hardware_service cimport HardwareService, HardwareInfo
|
|||||||
from security cimport Security
|
from security cimport Security
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from user cimport User, RoleEnum
|
from user cimport User, RoleEnum
|
||||||
from file_data cimport FileData
|
|
||||||
|
|
||||||
cdef class ApiClient:
|
cdef class ApiClient:
|
||||||
"""Handles API authentication and downloading of the AI model."""
|
"""Handles API authentication and downloading of the AI model."""
|
||||||
@@ -19,9 +16,8 @@ cdef class ApiClient:
|
|||||||
self.user = None
|
self.user = None
|
||||||
self.token = None
|
self.token = None
|
||||||
|
|
||||||
cdef get_encryption_key(self, str hardware_hash):
|
cdef set_credentials(self, Credentials credentials):
|
||||||
cdef str key = f'{self.credentials.email}-{self.credentials.password}-{hardware_hash}-#%@AzaionKey@%#---'
|
self.credentials = credentials
|
||||||
return Security.calc_hash(key)
|
|
||||||
|
|
||||||
cdef login(self):
|
cdef login(self):
|
||||||
response = requests.post(f"{constants.API_URL}/login",
|
response = requests.post(f"{constants.API_URL}/login",
|
||||||
@@ -61,6 +57,20 @@ cdef class ApiClient:
|
|||||||
self.login()
|
self.login()
|
||||||
return self.user
|
return self.user
|
||||||
|
|
||||||
|
cdef upload_file(self, str filename, str folder=None):
|
||||||
|
folder = folder or self.credentials.folder
|
||||||
|
if self.token is None:
|
||||||
|
self.login()
|
||||||
|
url = f"{constants.API_URL}/resources/{folder}"
|
||||||
|
headers = { "Authorization": f"Bearer {self.token}" }
|
||||||
|
files = dict(data=open(<str>filename, 'rb'))
|
||||||
|
try:
|
||||||
|
r = requests.post(url, headers=headers, files=files, allow_redirects=True)
|
||||||
|
r.raise_for_status()
|
||||||
|
print(f"Upload success: {r.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Upload fail: {e}")
|
||||||
|
|
||||||
cdef load_bytes(self, str filename, str folder=None):
|
cdef load_bytes(self, str filename, str folder=None):
|
||||||
folder = folder or self.credentials.folder
|
folder = folder or self.credentials.folder
|
||||||
hardware_service = HardwareService()
|
hardware_service = HardwareService()
|
||||||
@@ -68,7 +78,6 @@ cdef class ApiClient:
|
|||||||
|
|
||||||
if self.token is None:
|
if self.token is None:
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
url = f"{constants.API_URL}/resources/get/{folder}"
|
url = f"{constants.API_URL}/resources/get/{folder}"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.token}",
|
"Authorization": f"Bearer {self.token}",
|
||||||
@@ -82,7 +91,6 @@ cdef class ApiClient:
|
|||||||
"fileName": filename
|
"fileName": filename
|
||||||
}, indent=4)
|
}, indent=4)
|
||||||
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.UNAUTHORIZED or response.status_code == HTTPStatus.FORBIDDEN:
|
if response.status_code == HTTPStatus.UNAUTHORIZED or response.status_code == HTTPStatus.FORBIDDEN:
|
||||||
self.login()
|
self.login()
|
||||||
headers = {
|
headers = {
|
||||||
@@ -94,7 +102,8 @@ cdef class ApiClient:
|
|||||||
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
|
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
|
||||||
print('500!')
|
print('500!')
|
||||||
|
|
||||||
key = self.get_encryption_key(hardware.hash)
|
hw_hash = Security.get_hw_hash(hardware)
|
||||||
|
key = Security.get_api_encryption_key(self.credentials, hw_hash)
|
||||||
|
|
||||||
stream = BytesIO(response.raw.read())
|
stream = BytesIO(response.raw.read())
|
||||||
data = Security.decrypt_to(stream, key)
|
data = Security.decrypt_to(stream, key)
|
||||||
@@ -102,7 +111,16 @@ cdef class ApiClient:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
cdef load_ai_model(self):
|
cdef load_ai_model(self):
|
||||||
return self.load_bytes(constants.AI_MODEL_FILE)
|
with open(<str>constants.AI_MODEL_FILE_BIG, 'rb') as binary_file:
|
||||||
|
encrypted_bytes_big = binary_file.read()
|
||||||
|
encrypted_bytes_small = self.load_bytes(constants.AI_MODEL_FILE_SMALL)
|
||||||
|
|
||||||
|
encrypted_model_bytes = encrypted_bytes_small + encrypted_bytes_big
|
||||||
|
key = Security.get_model_encryption_key()
|
||||||
|
|
||||||
|
model_bytes = Security.decrypt_to(BytesIO(encrypted_model_bytes), key)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cdef load_queue_config(self):
|
cdef load_queue_config(self):
|
||||||
return self.load_bytes(constants.QUEUE_CONFIG_FILENAME).decode(encoding='utf-8')
|
return self.load_bytes(constants.QUEUE_CONFIG_FILENAME).decode(encoding='utf-8')
|
||||||
@@ -6,7 +6,8 @@ cdef str ANNOTATIONS_QUEUE # Name of the annotations queue in rabbit
|
|||||||
|
|
||||||
cdef str API_URL # Base URL for the external API
|
cdef str API_URL # Base URL for the external API
|
||||||
cdef str QUEUE_CONFIG_FILENAME # queue config filename to load from api
|
cdef str QUEUE_CONFIG_FILENAME # queue config filename to load from api
|
||||||
cdef str AI_MODEL_FILE # AI Model file
|
cdef str AI_MODEL_FILE_BIG # AI Model file (BIG part)
|
||||||
|
cdef str AI_MODEL_FILE_SMALL # AI Model file (small part)
|
||||||
|
|
||||||
cdef bytes DONE_SIGNAL
|
cdef bytes DONE_SIGNAL
|
||||||
cdef int MODEL_BATCH_SIZE
|
cdef int MODEL_BATCH_SIZE
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ cdef str ANNOTATIONS_QUEUE = "azaion-annotations"
|
|||||||
|
|
||||||
cdef str API_URL = "https://api.azaion.com" # Base URL for the external API
|
cdef str API_URL = "https://api.azaion.com" # Base URL for the external API
|
||||||
cdef str QUEUE_CONFIG_FILENAME = "secured-config.json"
|
cdef str QUEUE_CONFIG_FILENAME = "secured-config.json"
|
||||||
cdef str AI_MODEL_FILE = "azaion.onnx"
|
cdef str AI_MODEL_FILE_BIG = "azaion.onnx.big"
|
||||||
|
cdef str AI_MODEL_FILE_SMALL = "azaion.onnx.small"
|
||||||
|
|
||||||
cdef bytes DONE_SIGNAL = b"DONE"
|
cdef bytes DONE_SIGNAL = b"DONE"
|
||||||
cdef int MODEL_BATCH_SIZE = 4
|
cdef int MODEL_BATCH_SIZE = 4
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
cdef class HardwareInfo:
|
cdef class HardwareInfo:
|
||||||
cdef str cpu, gpu, memory, mac_address, hash
|
cdef str cpu, gpu, memory, mac_address
|
||||||
cdef to_json_object(self)
|
cdef to_json_object(self)
|
||||||
|
|
||||||
cdef class HardwareService:
|
cdef class HardwareService:
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
from security cimport Security
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
cdef class HardwareInfo:
|
cdef class HardwareInfo:
|
||||||
def __init__(self, str cpu, str gpu, str memory, str mac_address, str hw_hash):
|
def __init__(self, str cpu, str gpu, str memory, str mac_address):
|
||||||
self.cpu = cpu
|
self.cpu = cpu
|
||||||
self.gpu = gpu
|
self.gpu = gpu
|
||||||
self.memory = memory
|
self.memory = memory
|
||||||
self.mac_address = mac_address
|
self.mac_address = mac_address
|
||||||
self.hash = hw_hash
|
|
||||||
|
|
||||||
cdef to_json_object(self):
|
cdef to_json_object(self):
|
||||||
return {
|
return {
|
||||||
"CPU": self.cpu,
|
"CPU": self.cpu,
|
||||||
"GPU": self.gpu,
|
"GPU": self.gpu,
|
||||||
"MacAddress": self.mac_address,
|
"MacAddress": self.mac_address,
|
||||||
"Memory": self.memory,
|
"Memory": self.memory
|
||||||
"Hash": self.hash,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -68,7 +65,5 @@ cdef class HardwareService:
|
|||||||
cdef str gpu = lines[1].replace("Name=", "").replace(" ", " ")
|
cdef str gpu = lines[1].replace("Name=", "").replace(" ", " ")
|
||||||
cdef str memory = lines[2].replace("TotalVisibleMemorySize=", "").replace(" ", " ")
|
cdef str memory = lines[2].replace("TotalVisibleMemorySize=", "").replace(" ", " ")
|
||||||
cdef str mac_address = self.get_mac_address()
|
cdef str mac_address = self.get_mac_address()
|
||||||
cdef str full_hw_str = f'Azaion_{mac_address}_{cpu}_{gpu}'
|
|
||||||
|
|
||||||
hw_hash = Security.calc_hash(full_hw_str)
|
return HardwareInfo(cpu, gpu, memory, mac_address)
|
||||||
return HardwareInfo(cpu, gpu, memory, mac_address, hw_hash)
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ cdef class CommandProcessor:
|
|||||||
|
|
||||||
cdef login(self, RemoteCommand command):
|
cdef login(self, RemoteCommand command):
|
||||||
cdef User user
|
cdef User user
|
||||||
self.api_client.credentials = Credentials.from_msgpack(command.data)
|
self.api_client.set_credentials(Credentials.from_msgpack(command.data))
|
||||||
user = self.api_client.get_user()
|
user = self.api_client.get_user()
|
||||||
self.remote_handler.send(command.client_id, user.serialize())
|
self.remote_handler.send(command.client_id, user.serialize())
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
cdef class SecureModelLoader:
|
|
||||||
cdef:
|
|
||||||
bytes _model_bytes
|
|
||||||
str _ramdisk_path
|
|
||||||
str _temp_file_path
|
|
||||||
int _disk_size_mb
|
|
||||||
|
|
||||||
cpdef str load_model(self, bytes model_bytes)
|
|
||||||
cdef str _get_ramdisk_path(self)
|
|
||||||
cdef void _create_ramdisk(self)
|
|
||||||
cdef void _store_model(self)
|
|
||||||
cdef void _cleanup(self)
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import os
|
|
||||||
import platform
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from libc.stdio cimport FILE, fopen, fclose, remove
|
|
||||||
from libc.stdlib cimport free
|
|
||||||
from libc.string cimport strdup
|
|
||||||
|
|
||||||
cdef class SecureModelLoader:
|
|
||||||
def __cinit__(self, int disk_size_mb=512):
|
|
||||||
self._disk_size_mb = disk_size_mb
|
|
||||||
self._ramdisk_path = None
|
|
||||||
self._temp_file_path = None
|
|
||||||
|
|
||||||
cpdef str load_model(self, bytes model_bytes):
|
|
||||||
"""Public method to load YOLO model securely."""
|
|
||||||
self._model_bytes = model_bytes
|
|
||||||
self._create_ramdisk()
|
|
||||||
self._store_model()
|
|
||||||
return self._temp_file_path
|
|
||||||
|
|
||||||
cdef str _get_ramdisk_path(self):
|
|
||||||
"""Determine the RAM disk path based on the OS."""
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
return "R:\\"
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
return "/mnt/ramdisk"
|
|
||||||
elif platform.system() == "Darwin":
|
|
||||||
return "/Volumes/RAMDisk"
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Unsupported OS for RAM disk")
|
|
||||||
|
|
||||||
cdef void _create_ramdisk(self):
|
|
||||||
"""Create a RAM disk securely based on the OS."""
|
|
||||||
system = platform.system()
|
|
||||||
|
|
||||||
if system == "Windows":
|
|
||||||
# Create RAM disk via PowerShell
|
|
||||||
command = f'powershell -Command "subst R: {tempfile.gettempdir()}"'
|
|
||||||
if os.system(command) != 0:
|
|
||||||
raise RuntimeError("Failed to create RAM disk on Windows")
|
|
||||||
self._ramdisk_path = "R:\\"
|
|
||||||
|
|
||||||
elif system == "Linux":
|
|
||||||
# Use tmpfs for RAM disk
|
|
||||||
self._ramdisk_path = "/mnt/ramdisk"
|
|
||||||
if not Path(self._ramdisk_path).exists():
|
|
||||||
os.mkdir(self._ramdisk_path)
|
|
||||||
if os.system(f"mount -t tmpfs -o size={self._disk_size_mb}M tmpfs {self._ramdisk_path}") != 0:
|
|
||||||
raise RuntimeError("Failed to create RAM disk on Linux")
|
|
||||||
|
|
||||||
elif system == "Darwin":
|
|
||||||
# Use hdiutil for macOS RAM disk
|
|
||||||
block_size = 2048 # 512-byte blocks * 2048 = 1MB
|
|
||||||
num_blocks = self._disk_size_mb * block_size
|
|
||||||
result = os.popen(f"hdiutil attach -nomount ram://{num_blocks}").read().strip()
|
|
||||||
if result:
|
|
||||||
self._ramdisk_path = "/Volumes/RAMDisk"
|
|
||||||
os.system(f"diskutil eraseVolume HFS+ RAMDisk {result}")
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Failed to create RAM disk on macOS")
|
|
||||||
|
|
||||||
cdef void _store_model(self):
|
|
||||||
"""Write model securely to the RAM disk."""
|
|
||||||
cdef char* temp_path
|
|
||||||
cdef FILE* cfile
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(
|
|
||||||
dir=self._ramdisk_path, suffix='.pt', delete=False
|
|
||||||
) as tmp_file:
|
|
||||||
tmp_file.write(self._model_bytes)
|
|
||||||
self._temp_file_path = tmp_file.name
|
|
||||||
|
|
||||||
encoded_path = self._temp_file_path.encode('utf-8')
|
|
||||||
temp_path = strdup(encoded_path)
|
|
||||||
with nogil:
|
|
||||||
cfile = fopen(temp_path, "rb")
|
|
||||||
if cfile == NULL:
|
|
||||||
raise IOError(f"Could not open {self._temp_file_path}")
|
|
||||||
fclose(cfile)
|
|
||||||
|
|
||||||
cdef void _cleanup(self):
|
|
||||||
"""Remove the model file and unmount RAM disk securely."""
|
|
||||||
cdef char* c_path
|
|
||||||
if self._temp_file_path:
|
|
||||||
c_path = strdup(os.fsencode(self._temp_file_path))
|
|
||||||
with nogil:
|
|
||||||
remove(c_path)
|
|
||||||
free(c_path)
|
|
||||||
self._temp_file_path = None
|
|
||||||
|
|
||||||
# Unmount RAM disk based on OS
|
|
||||||
if self._ramdisk_path:
|
|
||||||
if platform.system() == "Windows":
|
|
||||||
os.system("subst R: /D")
|
|
||||||
elif platform.system() == "Linux":
|
|
||||||
os.system(f"umount {self._ramdisk_path}")
|
|
||||||
elif platform.system() == "Darwin":
|
|
||||||
os.system("hdiutil detach /Volumes/RAMDisk")
|
|
||||||
self._ramdisk_path = None
|
|
||||||
|
|
||||||
def __dealloc__(self):
|
|
||||||
"""Ensure cleanup when the object is deleted."""
|
|
||||||
self._cleanup()
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
from credentials cimport Credentials
|
||||||
|
from hardware_service cimport HardwareInfo
|
||||||
|
|
||||||
cdef class Security:
|
cdef class Security:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef encrypt_to(input_stream, key)
|
cdef encrypt_to(input_stream, key)
|
||||||
@@ -5,5 +8,14 @@ cdef class Security:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
cdef decrypt_to(input_stream, key)
|
cdef decrypt_to(input_stream, key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef get_hw_hash(HardwareInfo hardware)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef get_api_encryption_key(Credentials credentials, str hardware_hash)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef get_model_encryption_key()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef calc_hash(str key)
|
cdef calc_hash(str key)
|
||||||
@@ -2,6 +2,8 @@ import base64
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
from hashlib import sha384
|
from hashlib import sha384
|
||||||
|
from credentials cimport Credentials
|
||||||
|
from hardware_service cimport HardwareInfo
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import padding
|
from cryptography.hazmat.primitives import padding
|
||||||
@@ -24,14 +26,14 @@ cdef class Security:
|
|||||||
encrypted_chunk = encryptor.update(chunk)
|
encrypted_chunk = encryptor.update(chunk)
|
||||||
res.extend(encrypted_chunk)
|
res.extend(encrypted_chunk)
|
||||||
res.extend(encryptor.finalize())
|
res.extend(encryptor.finalize())
|
||||||
return res
|
return bytes(res)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef decrypt_to(input_stream, key):
|
cdef decrypt_to(input_stream, key):
|
||||||
cdef bytes aes_key = hashlib.sha256(key.encode('utf-8')).digest()
|
cdef bytes aes_key = hashlib.sha256(key.encode('utf-8')).digest()
|
||||||
cdef bytes iv = input_stream.read(16)
|
cdef bytes iv = input_stream.read(16)
|
||||||
|
|
||||||
cdef cipher = Cipher(algorithms.AES(<bytes>aes_key), modes.CBC(<bytes>iv), backend=default_backend())
|
cdef cipher = Cipher(algorithms.AES(<bytes>aes_key), modes.CFB(<bytes>iv), backend=default_backend())
|
||||||
cdef decryptor = cipher.decryptor()
|
cdef decryptor = cipher.decryptor()
|
||||||
|
|
||||||
cdef bytearray res = bytearray()
|
cdef bytearray res = bytearray()
|
||||||
@@ -40,8 +42,22 @@ cdef class Security:
|
|||||||
res.extend(decrypted_chunk)
|
res.extend(decrypted_chunk)
|
||||||
res.extend(decryptor.finalize())
|
res.extend(decryptor.finalize())
|
||||||
|
|
||||||
unpadder = padding.PKCS7(128).unpadder() # AES block size is 128 bits (16 bytes)
|
return bytes(res)
|
||||||
return unpadder.update(res) + unpadder.finalize()
|
|
||||||
|
@staticmethod
|
||||||
|
cdef get_hw_hash(HardwareInfo hardware):
|
||||||
|
cdef str key = f'Azaion_{hardware.mac_address}_{hardware.cpu}_{hardware.gpu}'
|
||||||
|
return Security.calc_hash(key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef get_api_encryption_key(Credentials creds, str hardware_hash):
|
||||||
|
cdef str key = f'{creds.email}-{creds.password}-{hardware_hash}-#%@AzaionKey@%#---'
|
||||||
|
return Security.calc_hash(key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
cdef get_model_encryption_key():
|
||||||
|
cdef str key = '-#%@AzaionKey@%#---234sdfklgvhjbnn'
|
||||||
|
return Security.calc_hash(key)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
cdef calc_hash(str key):
|
cdef calc_hash(str key):
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ extensions = [
|
|||||||
Extension('annotation', ['annotation.pyx']),
|
Extension('annotation', ['annotation.pyx']),
|
||||||
Extension('credentials', ['credentials.pyx']),
|
Extension('credentials', ['credentials.pyx']),
|
||||||
Extension('file_data', ['file_data.pyx']),
|
Extension('file_data', ['file_data.pyx']),
|
||||||
Extension('security', ['security.pyx']),
|
|
||||||
Extension('hardware_service', ['hardware_service.pyx'], extra_compile_args=["-g"], extra_link_args=["-g"]),
|
Extension('hardware_service', ['hardware_service.pyx'], extra_compile_args=["-g"], extra_link_args=["-g"]),
|
||||||
|
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('user', ['user.pyx']),
|
||||||
Extension('api_client', ['api_client.pyx']),
|
Extension('api_client', ['api_client.pyx']),
|
||||||
Extension('secure_model', ['secure_model.pyx']),
|
|
||||||
Extension('ai_config', ['ai_config.pyx']),
|
Extension('ai_config', ['ai_config.pyx']),
|
||||||
Extension('inference', ['inference.pyx'], include_dirs=[np.get_include()]),
|
Extension('inference', ['inference.pyx'], include_dirs=[np.get_include()]),
|
||||||
Extension('main', ['main.pyx']),
|
Extension('main', ['main.pyx']),
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from PyInstaller.utils.hooks import collect_all
|
|||||||
datas = []
|
datas = []
|
||||||
binaries = []
|
binaries = []
|
||||||
hiddenimports = ['constants', 'annotation', 'credentials', 'file_data', 'user', 'security', 'secure_model', 'api_client', 'hardware_service', 'remote_command', 'ai_config', 'inference', 'remote_command_handler']
|
hiddenimports = ['constants', 'annotation', 'credentials', 'file_data', 'user', 'security', 'secure_model', 'api_client', 'hardware_service', 'remote_command', 'ai_config', 'inference', 'remote_command_handler']
|
||||||
|
tmp_ret = collect_all('pyyaml')
|
||||||
|
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||||
tmp_ret = collect_all('jwt')
|
tmp_ret = collect_all('jwt')
|
||||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||||
tmp_ret = collect_all('requests')
|
tmp_ret = collect_all('requests')
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import psutil
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import padding
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
import requests
|
||||||
|
from io import BytesIO
|
||||||
|
from http import HTTPStatus
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
BUFFER_SIZE = 64 * 1024 # 64 KB
|
||||||
|
API_URL = "https://api.azaion.com"
|
||||||
|
|
||||||
|
|
||||||
|
class HWInfo:
|
||||||
|
def __init__(self, cpu, gpu, memory, mac_address, hw_hash):
|
||||||
|
self.cpu = cpu
|
||||||
|
self.gpu = gpu
|
||||||
|
self.memory = memory
|
||||||
|
self.mac_address = mac_address
|
||||||
|
self.hash = hw_hash
|
||||||
|
|
||||||
|
def to_json_object(self):
|
||||||
|
return {
|
||||||
|
"CPU": self.cpu,
|
||||||
|
"GPU": self.gpu,
|
||||||
|
"MacAddress": self.mac_address,
|
||||||
|
"Memory": self.memory,
|
||||||
|
"Hash": self.hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'CPU: {self.cpu}. GPU: {self.gpu}. Memory: {self.memory}. MAC Address: {self.mac_address}'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_to(input_stream, key):
|
||||||
|
aes_key = hashlib.sha256(key.encode('utf-8')).digest()
|
||||||
|
iv = os.urandom(16)
|
||||||
|
|
||||||
|
cipher = Cipher(algorithms.AES(aes_key), modes.CFB(iv), backend=default_backend())
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
|
||||||
|
res = bytearray()
|
||||||
|
res.extend(iv)
|
||||||
|
while chunk := input_stream.read(BUFFER_SIZE):
|
||||||
|
encrypted_chunk = encryptor.update(chunk)
|
||||||
|
res.extend(encrypted_chunk)
|
||||||
|
res.extend(encryptor.finalize())
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decrypt_to(input_stream, key):
|
||||||
|
aes_key = hashlib.sha256(key.encode('utf-8')).digest()
|
||||||
|
iv = input_stream.read(16)
|
||||||
|
|
||||||
|
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
|
||||||
|
res = bytearray()
|
||||||
|
while chunk := input_stream.read(BUFFER_SIZE):
|
||||||
|
decrypted_chunk = decryptor.update(chunk)
|
||||||
|
res.extend(decrypted_chunk)
|
||||||
|
res.extend(decryptor.finalize())
|
||||||
|
|
||||||
|
unpadder = padding.PKCS7(128).unpadder() # AES block size is 128 bits (16 bytes)
|
||||||
|
return unpadder.update(res) + unpadder.finalize()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calc_hash(key):
|
||||||
|
str_bytes = key.encode('utf-8')
|
||||||
|
hash_bytes = hashlib.sha384(str_bytes).digest()
|
||||||
|
h = base64.b64encode(hash_bytes).decode('utf-8')
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
|
class HWService:
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
res = subprocess.check_output("ver", shell=True).decode('utf-8')
|
||||||
|
if "Microsoft Windows" in res:
|
||||||
|
self.is_windows = True
|
||||||
|
else:
|
||||||
|
self.is_windows = False
|
||||||
|
except Exception:
|
||||||
|
print('Error during os type checking')
|
||||||
|
self.is_windows = False
|
||||||
|
|
||||||
|
def get_mac_address(self, interface="Ethernet"):
|
||||||
|
addresses = psutil.net_if_addrs()
|
||||||
|
for interface_name, interface_info in addresses.items():
|
||||||
|
if interface_name == interface:
|
||||||
|
for addr in interface_info:
|
||||||
|
if addr.family == psutil.AF_LINK:
|
||||||
|
return addr.address.replace('-', '')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_hardware_info(self):
|
||||||
|
if self.is_windows:
|
||||||
|
os_command = (
|
||||||
|
"wmic CPU get Name /Value && "
|
||||||
|
"wmic path Win32_VideoController get Name /Value && "
|
||||||
|
"wmic OS get TotalVisibleMemorySize /Value"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
os_command = (
|
||||||
|
"/bin/bash -c \" lscpu | grep 'Model name:' | cut -d':' -f2 && "
|
||||||
|
"lspci | grep VGA | cut -d':' -f3 && "
|
||||||
|
"free -g | grep Mem: | awk '{print $2}' && \""
|
||||||
|
)
|
||||||
|
result = subprocess.check_output(os_command, shell=True).decode('utf-8')
|
||||||
|
lines = [line.strip() for line in result.splitlines() if line.strip()]
|
||||||
|
|
||||||
|
cpu = lines[0].replace("Name=", "").replace(" ", " ")
|
||||||
|
gpu = lines[1].replace("Name=", "").replace(" ", " ")
|
||||||
|
memory = lines[2].replace("TotalVisibleMemorySize=", "").replace(" ", " ")
|
||||||
|
mac_address = self.get_mac_address()
|
||||||
|
full_hw_str = f'Azaion_{mac_address}_{cpu}_{gpu}'
|
||||||
|
|
||||||
|
hw_hash = HWInfo.calc_hash(full_hw_str)
|
||||||
|
return HWInfo(cpu, gpu, memory, mac_address, hw_hash)
|
||||||
|
|
||||||
|
class Credentials:
|
||||||
|
|
||||||
|
def __init__(self, email, password, folder):
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
self.folder = folder
|
||||||
|
|
||||||
|
class Api:
|
||||||
|
|
||||||
|
def __init__(self, credentials):
|
||||||
|
self.token = None
|
||||||
|
self.credentials = credentials
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_file(filename, size_mb=1): # chunk_size_kb is now configurable
|
||||||
|
size_bytes = size_mb * 1024 * 1024
|
||||||
|
chunk_size = 1024 * 1024 # 1mb chunk size
|
||||||
|
bytes_written = 0
|
||||||
|
sha256_hash = hashlib.sha256() # init hash
|
||||||
|
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
while bytes_written < size_bytes:
|
||||||
|
write_size = min(chunk_size, size_bytes - bytes_written)
|
||||||
|
random_bytes = os.urandom(write_size)
|
||||||
|
f.write(random_bytes)
|
||||||
|
bytes_written += write_size
|
||||||
|
sha256_hash.update(random_bytes)
|
||||||
|
return sha256_hash.hexdigest()
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
response = requests.post(f"{API_URL}/login",
|
||||||
|
json={"email": self.credentials.email, "password": self.credentials.password})
|
||||||
|
response.raise_for_status()
|
||||||
|
token = response.json()["token"]
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_sha256(data_bytes):
|
||||||
|
sha256 = hashlib.sha256()
|
||||||
|
sha256.update(data_bytes)
|
||||||
|
return sha256.hexdigest()
|
||||||
|
|
||||||
|
def upload_file(self, filename, folder = None):
|
||||||
|
folder = folder or self.credentials.folder
|
||||||
|
if self.token is None:
|
||||||
|
self.login()
|
||||||
|
url = f"{API_URL}/resources/{folder}"
|
||||||
|
headers = {"Authorization": f"Bearer {self.token}"}
|
||||||
|
files = dict(data=open(filename, 'rb'))
|
||||||
|
try:
|
||||||
|
r = requests.post(url, headers=headers, files=files, allow_redirects=True)
|
||||||
|
r.raise_for_status()
|
||||||
|
print(f"Upload success: {r.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Upload fail: {e}")
|
||||||
|
|
||||||
|
def get_encryption_key(self, hardware_hash):
|
||||||
|
key = f'{self.credentials.email}-{self.credentials.password}-{hardware_hash}-#%@AzaionKey@%#---'
|
||||||
|
return HWInfo.calc_hash(key)
|
||||||
|
|
||||||
|
def load_bytes(self, filename, folder = None):
|
||||||
|
folder = folder or self.credentials.folder
|
||||||
|
hardware_service = HWService()
|
||||||
|
hardware = hardware_service.get_hardware_info()
|
||||||
|
|
||||||
|
if self.token is None:
|
||||||
|
self.login()
|
||||||
|
url = f"{API_URL}/resources/get/{folder}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = json.dumps(
|
||||||
|
{
|
||||||
|
"password": self.credentials.password,
|
||||||
|
"hardware": hardware.to_json_object(),
|
||||||
|
"fileName": filename
|
||||||
|
}, indent=4)
|
||||||
|
response = requests.post(url, data=payload, headers=headers, stream=True, timeout=20)
|
||||||
|
if response.status_code == HTTPStatus.UNAUTHORIZED or response.status_code == HTTPStatus.FORBIDDEN:
|
||||||
|
self.login()
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
response = requests.post(url, data=payload, headers=headers, stream=True)
|
||||||
|
|
||||||
|
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
|
||||||
|
print('500!')
|
||||||
|
|
||||||
|
key = self.get_encryption_key(hardware.hash)
|
||||||
|
|
||||||
|
stream = BytesIO(response.raw.read())
|
||||||
|
data = HWInfo.decrypt_to(stream, key)
|
||||||
|
print(f'Downloaded file: {filename}, {len(data)} bytes')
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
credentials = Credentials('admin@azaion.com', 'Az@1on1000Odm$n', 'stage')
|
||||||
|
api = Api(credentials)
|
||||||
|
file = 'file1'
|
||||||
|
sha256_init = Api.create_file(file, size_mb=100)
|
||||||
|
api.upload_file(file)
|
||||||
|
|
||||||
|
file_bytes = api.load_bytes(file)
|
||||||
|
print(f'received: {len(file_bytes)/1024} kb')
|
||||||
|
sha256_downloaded = Api.get_sha256(file_bytes)
|
||||||
|
print(f'{sha256_init}: sha256 initial file')
|
||||||
|
print(f'{sha256_downloaded}: sha256 downloaded file')
|
||||||
@@ -25,6 +25,9 @@ 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}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
build\publish.cmd = build\publish.cmd
|
build\publish.cmd = build\publish.cmd
|
||||||
|
build\download.py = build\download.py
|
||||||
|
build\requirements.txt = build\requirements.txt
|
||||||
|
build\build_downloader.cmd = build\build_downloader.cmd
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ public partial class App
|
|||||||
|
|
||||||
private readonly List<string> _encryptedResources =
|
private readonly List<string> _encryptedResources =
|
||||||
[
|
[
|
||||||
"Azaion.Annotator",
|
// "Azaion.Annotator",
|
||||||
"Azaion.Dataset"
|
// "Azaion.Dataset"
|
||||||
];
|
];
|
||||||
|
|
||||||
private static PythonConfig ReadPythonConfig()
|
private static PythonConfig ReadPythonConfig()
|
||||||
@@ -83,11 +83,11 @@ public partial class App
|
|||||||
{
|
{
|
||||||
new ConfigUpdater().CheckConfig();
|
new ConfigUpdater().CheckConfig();
|
||||||
var login = new Login();
|
var login = new Login();
|
||||||
var config = ReadPythonConfig();
|
var pythonConfig = ReadPythonConfig();
|
||||||
_resourceLoader = new PythonResourceLoader(config);
|
_resourceLoader = new PythonResourceLoader(pythonConfig);
|
||||||
login.CredentialsEntered += (_, credentials) =>
|
login.CredentialsEntered += (_, credentials) =>
|
||||||
{
|
{
|
||||||
credentials.Folder = config.ResourcesFolder;
|
credentials.Folder = pythonConfig.ResourcesFolder;
|
||||||
_resourceLoader.Login(credentials);
|
_resourceLoader.Login(credentials);
|
||||||
_securedConfig = _resourceLoader.LoadFileFromPython("secured-config.json");
|
_securedConfig = _resourceLoader.LoadFileFromPython("secured-config.json");
|
||||||
|
|
||||||
@@ -152,6 +152,7 @@ public partial class App
|
|||||||
services.AddSingleton<IInferenceService, PythonInferenceService>();
|
services.AddSingleton<IInferenceService, PythonInferenceService>();
|
||||||
|
|
||||||
services.Configure<AppConfig>(context.Configuration);
|
services.Configure<AppConfig>(context.Configuration);
|
||||||
|
services.ConfigureSection<PythonConfig>(context.Configuration);
|
||||||
services.ConfigureSection<QueueConfig>(context.Configuration);
|
services.ConfigureSection<QueueConfig>(context.Configuration);
|
||||||
services.ConfigureSection<DirectoriesConfig>(context.Configuration);
|
services.ConfigureSection<DirectoriesConfig>(context.Configuration);
|
||||||
services.ConfigureSection<AnnotationConfig>(context.Configuration);
|
services.ConfigureSection<AnnotationConfig>(context.Configuration);
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
||||||
<ProjectReference Include="..\Dummy\Azaion.Annotator\Azaion.Annotator.csproj" />
|
<ProjectReference Include="..\Azaion.Annotator\Azaion.Annotator.csproj" />
|
||||||
<ProjectReference Include="..\Dummy\Azaion.Dataset\Azaion.Dataset.csproj" />
|
<ProjectReference Include="..\Azaion.Dataset\Azaion.Dataset.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -55,8 +55,8 @@
|
|||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="Build">
|
<Target Name="PostBuild" AfterTargets="Build">
|
||||||
<MakeDir Directories="$(TargetDir)dummy" />
|
<MakeDir Directories="$(TargetDir)dummy" />
|
||||||
<Move SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" />
|
<Copy SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" />
|
||||||
<Move SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" />
|
<Copy SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" />
|
||||||
<Exec Command="upload.cmd $(ConfigurationName) stage" />
|
<Exec Command="upload.cmd $(ConfigurationName) stage" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
BorderBrush="DimGray"
|
BorderBrush="DimGray"
|
||||||
BorderThickness="0,0,0,1"
|
BorderThickness="0,0,0,1"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
|
Text="admin@azaion.com"
|
||||||
/>
|
/>
|
||||||
<TextBlock Text="Пароль"
|
<TextBlock Text="Пароль"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
@@ -87,7 +88,8 @@
|
|||||||
Padding="0,5"
|
Padding="0,5"
|
||||||
Width="300"
|
Width="300"
|
||||||
BorderThickness="0,0,0,1"
|
BorderThickness="0,0,0,1"
|
||||||
HorizontalAlignment="Left"/>
|
HorizontalAlignment="Left"
|
||||||
|
Password="Az@1on1000Odm$n"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Button x:Name="LoginBtn"
|
<Button x:Name="LoginBtn"
|
||||||
Content="Вхід"
|
Content="Вхід"
|
||||||
|
|||||||
+8
-2
@@ -1,8 +1,9 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
cd Azaion.Suite
|
|
||||||
echo Build .net app
|
echo Build .net app
|
||||||
dotnet build -c Release
|
dotnet build -c Release
|
||||||
|
|
||||||
|
cd Azaion.Suite
|
||||||
call upload.cmd Release
|
call upload.cmd Release
|
||||||
|
|
||||||
echo Publish .net app
|
echo Publish .net app
|
||||||
@@ -44,6 +45,11 @@ start.py
|
|||||||
move dist\start.exe ..\dist\azaion-inference.exe
|
move dist\start.exe ..\dist\azaion-inference.exe
|
||||||
copy config.yaml ..\dist
|
copy config.yaml ..\dist
|
||||||
|
|
||||||
echo Copy ico
|
echo Download onnx model
|
||||||
|
cd build
|
||||||
|
call onnx_download.exe
|
||||||
|
move azaion.onnx.big ..\dist\
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
echo Copy ico
|
||||||
copy logo.ico dist\
|
copy logo.ico dist\
|
||||||
Reference in New Issue
Block a user