mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 09:46:30 +00:00
fix bug with annotation result gradient stops
add tensorrt engine
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
xmlns:controls1="clr-namespace:Azaion.Common.Controls;assembly=Azaion.Common"
|
||||
xmlns:controls2="clr-namespace:Azaion.Annotator.Controls;assembly=Azaion.Common"
|
||||
mc:Ignorable="d"
|
||||
xmlns:local="clr-namespace:Azaion.Annotator"
|
||||
Title="Azaion Annotator" Height="800" Width="1100"
|
||||
WindowState="Maximized"
|
||||
>
|
||||
@@ -49,6 +50,8 @@
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<local:GradientStyleSelector x:Key="GradientStyleSelector"/>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Name="GlobalGrid"
|
||||
@@ -232,7 +235,6 @@
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="4"
|
||||
Background="Black"
|
||||
RowBackground="#252525"
|
||||
Foreground="White"
|
||||
RowHeaderWidth="0"
|
||||
Padding="2 0 0 0"
|
||||
@@ -241,7 +243,8 @@
|
||||
CellStyle="{DynamicResource DataGridCellStyle1}"
|
||||
IsReadOnly="True"
|
||||
CanUserResizeRows="False"
|
||||
CanUserResizeColumns="False">
|
||||
CanUserResizeColumns="False"
|
||||
RowStyleSelector="{StaticResource GradientStyleSelector}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="60"
|
||||
@@ -264,20 +267,6 @@
|
||||
<Setter Property="Background" Value="#252525"></Setter>
|
||||
</Style>
|
||||
</DataGridTextColumn.HeaderStyle>
|
||||
<DataGridTextColumn.CellStyle>
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<LinearGradientBrush StartPoint="0 0 " EndPoint="1 0">
|
||||
<GradientStop Offset="0.3" Color="{Binding Path=ClassColor0}" />
|
||||
<GradientStop Offset="0.5" Color="{Binding Path=ClassColor1}" />
|
||||
<GradientStop Offset="0.8" Color="{Binding Path=ClassColor2}" />
|
||||
<GradientStop Offset="0.99" Color="{Binding Path=ClassColor3}" />
|
||||
</LinearGradientBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -328,7 +328,11 @@ public partial class Annotator
|
||||
|
||||
var existingResult = _formState.AnnotationResults.FirstOrDefault(x => x.Annotation.Time == time);
|
||||
if (existingResult != null)
|
||||
{
|
||||
_logger.LogInformation($"remove annotation {existingResult.TimeStr} {existingResult.ClassName}!");
|
||||
_formState.AnnotationResults.Remove(existingResult);
|
||||
}
|
||||
|
||||
|
||||
var dict = _formState.AnnotationResults
|
||||
.Select((x, i) => new { x.Annotation.Time, Index = i })
|
||||
@@ -339,7 +343,8 @@ public partial class Annotator
|
||||
.Select(x => x.Value + 1)
|
||||
.FirstOrDefault();
|
||||
|
||||
_formState.AnnotationResults.Insert(index, new AnnotationResult(_appConfig.AnnotationConfig.DetectionClassesDict, annotation));
|
||||
var annRes = new AnnotationResult(_appConfig.AnnotationConfig.DetectionClassesDict, annotation);
|
||||
_formState.AnnotationResults.Insert(index, annRes);
|
||||
}
|
||||
|
||||
private async Task ReloadFiles()
|
||||
@@ -565,6 +570,7 @@ public partial class Annotator
|
||||
await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play), ct);
|
||||
}
|
||||
}
|
||||
|
||||
AddAnnotation(annotation);
|
||||
|
||||
if (FollowAI)
|
||||
@@ -574,7 +580,7 @@ public partial class Annotator
|
||||
$"{_appConfig.AnnotationConfig.DetectionClassesDict[det.ClassNumber].Name}: " +
|
||||
$"xy=({det.CenterX:F2},{det.CenterY:F2}), " +
|
||||
$"size=({det.Width:F2}, {det.Height:F2}), " +
|
||||
$"prob: {det.Probability*100:F0}%"));
|
||||
$"conf: {det.Confidence*100:F0}%"));
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
@@ -618,3 +624,42 @@ public partial class Annotator
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class GradientStyleSelector : StyleSelector
|
||||
{
|
||||
public override Style? SelectStyle(object item, DependencyObject container)
|
||||
{
|
||||
if (container is not DataGridRow row || row.DataContext is not AnnotationResult result)
|
||||
return null;
|
||||
|
||||
var style = new Style(typeof(DataGridRow));
|
||||
var brush = new LinearGradientBrush
|
||||
{
|
||||
StartPoint = new Point(0, 0),
|
||||
EndPoint = new Point(1, 0)
|
||||
};
|
||||
|
||||
var gradients = new List<GradientStop>();
|
||||
if (result.Colors.Count != 0)
|
||||
{
|
||||
var color = (Color)ColorConverter.ConvertFromString("#40DDDDDD");
|
||||
gradients = [new GradientStop(color, 0.99)];
|
||||
}
|
||||
else
|
||||
{
|
||||
var increment = 1.0 / result.Colors.Count;
|
||||
var currentStop = increment;
|
||||
foreach (var c in result.Colors)
|
||||
{
|
||||
var resultColor = c.Color.ToConfidenceColor(c.Confidence);
|
||||
brush.GradientStops.Add(new GradientStop(resultColor, currentStop));
|
||||
currentStop += increment;
|
||||
}
|
||||
}
|
||||
foreach (var gradientStop in gradients)
|
||||
brush.GradientStops.Add(gradientStop);
|
||||
|
||||
style.Setters.Add(new Setter(DataGridRow.BackgroundProperty, brush));
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +38,19 @@ public partial class MapMatcher : UserControl
|
||||
SatelliteMap.Position = new PointLatLng(48.295985271707664, 37.14477539062501);
|
||||
SatelliteMap.MultiTouchEnabled = true;
|
||||
|
||||
GpsFiles.MouseDoubleClick += async (sender, args) =>
|
||||
GpsFiles.MouseDoubleClick += async (sender, args) => { await OpenGpsLocation(GpsFiles.SelectedIndex); };
|
||||
}
|
||||
|
||||
private async Task OpenGpsLocation(int gpsFilesIndex)
|
||||
{
|
||||
var media = GpsFiles.Items[gpsFilesIndex] as MediaFileInfo;
|
||||
var ann = _annotations.GetValueOrDefault(Path.GetFileNameWithoutExtension(media.Name));
|
||||
GpsImageEditor.Background = new ImageBrush
|
||||
{
|
||||
var media = (GpsFiles.SelectedItem as MediaFileInfo)!;
|
||||
var ann = _annotations.GetValueOrDefault(Path.GetFileNameWithoutExtension(media.Name));
|
||||
GpsImageEditor.Background = new ImageBrush
|
||||
{
|
||||
ImageSource = await Path.Combine(_currentDir, ann.Name).OpenImage()
|
||||
};
|
||||
SatelliteMap.Position = new PointLatLng(ann.Lat, ann.Lon);
|
||||
ImageSource = await Path.Combine(_currentDir, ann.Name).OpenImage()
|
||||
};
|
||||
if (ann.Lat != 0 && ann.Lon != 0)
|
||||
SatelliteMap.Position = new PointLatLng(ann.Lat, ann.Lon);
|
||||
}
|
||||
|
||||
private void GpsFilesContextOpening(object sender, ContextMenuEventArgs e)
|
||||
@@ -104,8 +107,11 @@ public partial class MapMatcher : UserControl
|
||||
_allMediaFiles = mediaFiles;
|
||||
GpsFiles.ItemsSource = new ObservableCollection<MediaFileInfo>(_allMediaFiles);
|
||||
var annotations = SetFromCsv(mediaFiles);
|
||||
Cursor = Cursors.Wait;
|
||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||
SetMarkers(annotations);
|
||||
Cursor = Cursors.Arrow;
|
||||
await OpenGpsLocation(0);
|
||||
}
|
||||
|
||||
private Dictionary<string, Annotation> SetFromCsv(List<MediaFileInfo> mediaFiles)
|
||||
@@ -117,7 +123,9 @@ public partial class MapMatcher : UserControl
|
||||
}).ToDictionary(x => Path.GetFileNameWithoutExtension(x.OriginalMediaName));
|
||||
|
||||
var csvResults = GpsCsvResult.ReadFromCsv(Constants.CSV_PATH);
|
||||
var csvDict = csvResults.ToDictionary(x => x.Image);
|
||||
var csvDict = csvResults
|
||||
.Where(x => x.MatchType == "stitched")
|
||||
.ToDictionary(x => x.Image);
|
||||
foreach (var ann in _annotations)
|
||||
{
|
||||
var csvRes = csvDict.GetValueOrDefault(ann.Key);
|
||||
@@ -137,7 +145,7 @@ public partial class MapMatcher : UserControl
|
||||
|
||||
var firstAnnotation = annotations.FirstOrDefault();
|
||||
SatelliteMap.Position = new PointLatLng(firstAnnotation.Value.Lat, firstAnnotation.Value.Lon);
|
||||
foreach (var ann in annotations)
|
||||
foreach (var ann in annotations.Where(x => x.Value.Lat != 0 && x.Value.Lon != 0))
|
||||
{
|
||||
var marker = new GMapMarker(new PointLatLng(ann.Value.Lat, ann.Value.Lon));
|
||||
var circle = new CircleVisual(marker, System.Windows.Media.Brushes.Blue)
|
||||
@@ -147,6 +155,6 @@ public partial class MapMatcher : UserControl
|
||||
marker.Shape = circle;
|
||||
SatelliteMap.Markers.Add(marker);
|
||||
}
|
||||
|
||||
SatelliteMap.ZoomAndCenterMarkers(null);
|
||||
}
|
||||
}
|
||||
@@ -90,5 +90,5 @@ public class Constants
|
||||
|
||||
#endregion
|
||||
|
||||
public const string CSV_PATH = "D:\\matches.csv";
|
||||
public const string CSV_PATH = "matches.csv";
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public class CanvasEditor : Canvas
|
||||
_verticalLine.Fill = value.ColorBrush;
|
||||
_horizontalLine.Stroke = value.ColorBrush;
|
||||
_horizontalLine.Fill = value.ColorBrush;
|
||||
_classNameHint.Text = value.Name;
|
||||
_classNameHint.Text = value.ShortName;
|
||||
|
||||
_newAnnotationRect.Stroke = value.ColorBrush;
|
||||
_newAnnotationRect.Fill = value.ColorBrush;
|
||||
@@ -84,7 +84,7 @@ public class CanvasEditor : Canvas
|
||||
};
|
||||
_classNameHint = new TextBlock
|
||||
{
|
||||
Text = CurrentAnnClass?.Name ?? "asd",
|
||||
Text = CurrentAnnClass?.ShortName ?? "",
|
||||
Foreground = new SolidColorBrush(Colors.Black),
|
||||
Cursor = Cursors.Arrow,
|
||||
FontSize = 16,
|
||||
@@ -313,14 +313,14 @@ public class CanvasEditor : Canvas
|
||||
foreach (var detection in detections)
|
||||
{
|
||||
var annClass = DetectionClass.FromYoloId(detection.ClassNumber, detectionClasses);
|
||||
var canvasLabel = new CanvasLabel(detection, RenderSize, videoSize, detection.Probability);
|
||||
var canvasLabel = new CanvasLabel(detection, RenderSize, videoSize, detection.Confidence);
|
||||
CreateDetectionControl(annClass, time, 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.Confidence)
|
||||
{
|
||||
Width = canvasLabel.Width,
|
||||
Height = canvasLabel.Height
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Width="*" Header="Назва" Binding="{Binding Path=Name}" CanUserSort="False">
|
||||
<DataGridTextColumn Width="*" Header="Назва" Binding="{Binding Path=ShortName}" CanUserSort="False">
|
||||
<DataGridTextColumn.HeaderStyle>
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#252525"/>
|
||||
|
||||
@@ -1,52 +1,33 @@
|
||||
using System.Windows.Media;
|
||||
using Azaion.Common.Database;
|
||||
using Azaion.Common.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public class AnnotationResult
|
||||
{
|
||||
public Annotation Annotation { get; set; }
|
||||
public List<(Color Color, double Confidence)> Colors { get; private set; }
|
||||
|
||||
public string ImagePath { get; set; }
|
||||
public string TimeStr { get; set; }
|
||||
|
||||
public string ClassName { get; set; }
|
||||
|
||||
public Color ClassColor0 { get; set; }
|
||||
public Color ClassColor1 { get; set; }
|
||||
public Color ClassColor2 { get; set; }
|
||||
public Color ClassColor3 { get; set; }
|
||||
|
||||
|
||||
public AnnotationResult(Dictionary<int, DetectionClass> allDetectionClasses, Annotation annotation)
|
||||
{
|
||||
|
||||
Annotation = annotation;
|
||||
var detections = annotation.Detections.ToList();
|
||||
|
||||
Color GetAnnotationClass(List<int> detectionClasses, int colorNumber)
|
||||
{
|
||||
if (detections.Count == 0)
|
||||
return (-1).ToColor();
|
||||
|
||||
return colorNumber >= detectionClasses.Count
|
||||
? allDetectionClasses[detectionClasses.LastOrDefault()].Color
|
||||
: allDetectionClasses[detectionClasses[colorNumber]].Color;
|
||||
}
|
||||
|
||||
TimeStr = $"{annotation.Time:h\\:mm\\:ss}";
|
||||
ImagePath = annotation.ImagePath;
|
||||
|
||||
var detectionClasses = detections.Select(x => x.ClassNumber).Distinct().ToList();
|
||||
var detectionClasses = annotation.Detections.Select(x => x.ClassNumber).Distinct().ToList();
|
||||
|
||||
Colors = annotation.Detections
|
||||
.Select(d => (allDetectionClasses[d.ClassNumber].Color, d.Confidence))
|
||||
.ToList();
|
||||
|
||||
ClassName = detectionClasses.Count > 1
|
||||
? string.Join(", ", detectionClasses.Select(x => allDetectionClasses[x].UIName))
|
||||
: allDetectionClasses[detectionClasses.FirstOrDefault()].UIName;
|
||||
|
||||
ClassColor0 = GetAnnotationClass(detectionClasses, 0);
|
||||
ClassColor1 = GetAnnotationClass(detectionClasses, 1);
|
||||
ClassColor2 = GetAnnotationClass(detectionClasses, 2);
|
||||
ClassColor3 = GetAnnotationClass(detectionClasses, 3);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class AnnotationConfig
|
||||
{
|
||||
Id = cls.Id,
|
||||
Name = cls.Name,
|
||||
Color = cls.Color,
|
||||
ShortName = cls.ShortName,
|
||||
PhotoMode = mode
|
||||
}))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Windows.Media;
|
||||
using Azaion.Common.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
@@ -11,6 +10,8 @@ public class DetectionClass
|
||||
public string Name { get; set; } = null!;
|
||||
public string ShortName { get; set; } = null!;
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string UIName
|
||||
{
|
||||
@@ -31,9 +32,6 @@ public class DetectionClass
|
||||
[JsonIgnore]
|
||||
public PhotoMode PhotoMode { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Color Color => Id.ToColor();
|
||||
|
||||
[JsonIgnore] //For UI
|
||||
public int ClassNumber => Id + 1;
|
||||
|
||||
|
||||
@@ -26,22 +26,22 @@ public class CanvasLabel : Label
|
||||
public double Y { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public double? Probability { get; }
|
||||
public double? Confidence { get; }
|
||||
|
||||
public CanvasLabel()
|
||||
{
|
||||
}
|
||||
|
||||
public CanvasLabel(int classNumber, double x, double y, double width, double height, double? probability = null) : base(classNumber)
|
||||
public CanvasLabel(int classNumber, double x, double y, double width, double height, double? confidence = null) : base(classNumber)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Probability = probability;
|
||||
Confidence = confidence;
|
||||
}
|
||||
|
||||
public CanvasLabel(YoloLabel label, Size canvasSize, Size? videoSize = null, double? probability = null)
|
||||
public CanvasLabel(YoloLabel label, Size canvasSize, Size? videoSize = null, double confidence = 1)
|
||||
{
|
||||
var cw = canvasSize.Width;
|
||||
var ch = canvasSize.Height;
|
||||
@@ -75,7 +75,7 @@ public class CanvasLabel : Label
|
||||
Width = label.Width * realWidth;
|
||||
Height = label.Height * ch;
|
||||
}
|
||||
Probability = probability;
|
||||
Confidence = confidence;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,12 +189,12 @@ public class YoloLabel : Label
|
||||
public class Detection : YoloLabel
|
||||
{
|
||||
[JsonProperty(PropertyName = "an")][Key("an")] public string AnnotationName { get; set; } = null!;
|
||||
[JsonProperty(PropertyName = "p")][Key("p")] public double? Probability { get; set; }
|
||||
[JsonProperty(PropertyName = "p")][Key("p")] public double Confidence { get; set; }
|
||||
|
||||
//For db & serialization
|
||||
public Detection(){}
|
||||
|
||||
public Detection(string annotationName, YoloLabel label, double? probability = null)
|
||||
public Detection(string annotationName, YoloLabel label, double confidence = 1)
|
||||
{
|
||||
AnnotationName = annotationName;
|
||||
ClassNumber = label.ClassNumber;
|
||||
@@ -202,6 +202,6 @@ public class Detection : YoloLabel
|
||||
CenterY = label.CenterY;
|
||||
Height = label.Height;
|
||||
Width = label.Width;
|
||||
Probability = probability;
|
||||
Confidence = confidence;
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,12 @@ namespace Azaion.Common.Extensions;
|
||||
|
||||
public static class ColorExtensions
|
||||
{
|
||||
public static Color ToColor(this int id)
|
||||
private const int MIN_ALPHA = 20;
|
||||
private const int MAX_ALPHA = 100;
|
||||
|
||||
public static Color ToConfidenceColor(this Color color, double confidence )
|
||||
{
|
||||
var index = id % ColorValues.Length;
|
||||
var hex = index == -1
|
||||
? "#40DDDDDD"
|
||||
: $"#40{ColorValues[index]}";
|
||||
var color =(Color)ColorConverter.ConvertFromString(hex);
|
||||
color.A = (byte)(MIN_ALPHA + (int)Math.Round(confidence * (MAX_ALPHA - MIN_ALPHA)));
|
||||
return color;
|
||||
}
|
||||
|
||||
private static readonly string[] ColorValues =
|
||||
[
|
||||
"FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "000000",
|
||||
"800000", "008000", "000080", "808000", "800080", "008080", "808080",
|
||||
"C00000", "00C000", "0000C0", "C0C000", "C000C0", "00C0C0", "C0C0C0",
|
||||
"400000", "004000", "000040", "404000", "400040", "004040", "404040",
|
||||
"200000", "002000", "000020", "202000", "200020", "002020", "202020",
|
||||
"600000", "006000", "000060", "606000", "600060", "006060", "606060",
|
||||
"A00000", "00A000", "0000A0", "A0A000", "A000A0", "00A0A0", "A0A0A0",
|
||||
"E00000", "00E000", "0000E0", "E0E000", "E000E0", "00E0E0", "E0E0E0"
|
||||
];
|
||||
}
|
||||
@@ -13,4 +13,7 @@ public class InferenceClientConfig : ExternalClientConfig
|
||||
public string ResourcesFolder { get; set; } = "";
|
||||
}
|
||||
|
||||
public class GpsDeniedClientConfig : ExternalClientConfig;
|
||||
public class GpsDeniedClientConfig : ExternalClientConfig
|
||||
{
|
||||
public int ZeroMqReceiverPort { get; set; }
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public abstract class BaseZeroMqExternalClient : IExternalClient
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = ClientPath
|
||||
FileName = ClientPath,
|
||||
//Arguments = $"-e {credentials.Email} -p {credentials.Password} -f {apiConfig.ResourcesFolder}",
|
||||
//RedirectStandardOutput = true,
|
||||
//RedirectStandardError = true,
|
||||
|
||||
@@ -14,9 +14,11 @@ public interface IHardwareService
|
||||
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";
|
||||
"powershell -Command \"" +
|
||||
"Get-CimInstance -ClassName Win32_Processor | Select-Object -ExpandProperty Name | Write-Output; " +
|
||||
"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty Name | Write-Output; " +
|
||||
"Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -ExpandProperty TotalVisibleMemorySize | Write-Output" +
|
||||
"\"";
|
||||
|
||||
private const string UNIX_GET_HARDWARE_COMMAND =
|
||||
"/bin/bash -c \"free -g | grep Mem: | awk '{print $2}' && " +
|
||||
|
||||
@@ -77,11 +77,11 @@ public partial class DatasetExplorer
|
||||
await DeleteAnnotations();
|
||||
break;
|
||||
case Key.Enter:
|
||||
await EditAnnotation();
|
||||
await EditAnnotation(ThumbnailsView.SelectedIndex);
|
||||
break;
|
||||
}
|
||||
};
|
||||
ThumbnailsView.MouseDoubleClick += async (_, _) => await EditAnnotation();
|
||||
ThumbnailsView.MouseDoubleClick += async (_, _) => await EditAnnotation(ThumbnailsView.SelectedIndex);
|
||||
|
||||
ThumbnailsView.SelectionChanged += (_, _) =>
|
||||
{
|
||||
@@ -152,7 +152,7 @@ public partial class DatasetExplorer
|
||||
.Select(gr => new
|
||||
{
|
||||
gr.Key,
|
||||
_annotationConfig.DetectionClassesDict[gr.Key].Name,
|
||||
_annotationConfig.DetectionClassesDict[gr.Key].ShortName,
|
||||
_annotationConfig.DetectionClassesDict[gr.Key].Color,
|
||||
ClassCount = gr.Value.Count
|
||||
})
|
||||
@@ -175,7 +175,7 @@ public partial class DatasetExplorer
|
||||
|
||||
foreach (var x in data)
|
||||
{
|
||||
var label = ClassDistribution.Plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1);
|
||||
var label = ClassDistribution.Plot.Add.Text(x.ShortName, 50, -1.5 * x.Key + 1.1);
|
||||
label.LabelFontColor = foregroundColor;
|
||||
label.LabelFontSize = 18;
|
||||
}
|
||||
@@ -204,16 +204,17 @@ public partial class DatasetExplorer
|
||||
RefreshThumbnailsButtonItem.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private async Task EditAnnotation()
|
||||
public async Task EditAnnotation(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
ThumbnailLoading = true;
|
||||
|
||||
if (ThumbnailsView.SelectedItem == null)
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
CurrentAnnotation = (ThumbnailsView.SelectedItem as AnnotationThumbnail)!;
|
||||
CurrentAnnotation = (ThumbnailsView.Items[index] as AnnotationThumbnail)!;
|
||||
ThumbnailsView.SelectedIndex = index;
|
||||
|
||||
var ann = CurrentAnnotation.Annotation;
|
||||
ExplorerEditor.Background = new ImageBrush
|
||||
{
|
||||
@@ -224,7 +225,6 @@ public partial class DatasetExplorer
|
||||
var time = ann.Time;
|
||||
ExplorerEditor.RemoveAllAnns();
|
||||
ExplorerEditor.CreateDetections(time, ann.Detections, _annotationConfig.DetectionClasses, ExplorerEditor.RenderSize);
|
||||
ThumbnailLoading = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -233,7 +233,11 @@ public partial class DatasetExplorer
|
||||
}
|
||||
finally
|
||||
{
|
||||
ThumbnailLoading = false;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(100);
|
||||
ThumbnailLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -260,7 +264,7 @@ public partial class DatasetExplorer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAnnotations()
|
||||
public async Task DeleteAnnotations()
|
||||
{
|
||||
var tempSelected = ThumbnailsView.SelectedIndex;
|
||||
var result = MessageBox.Show("Чи дійсно видалити аннотації?","Підтвердження видалення", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Queue;
|
||||
@@ -22,7 +23,9 @@ public class DatasetExplorerEventHandler(
|
||||
{ Key.Delete, PlaybackControlEnum.RemoveSelectedAnns },
|
||||
{ Key.X, PlaybackControlEnum.RemoveAllAnns },
|
||||
{ Key.Escape, PlaybackControlEnum.Close },
|
||||
{ Key.V, PlaybackControlEnum.ValidateAnnotations}
|
||||
{ Key.Down, PlaybackControlEnum.Next },
|
||||
{ Key.Up, PlaybackControlEnum.Previous },
|
||||
{ Key.V, PlaybackControlEnum.ValidateAnnotations},
|
||||
};
|
||||
|
||||
public async Task Handle(DatasetExplorerControlEvent notification, CancellationToken cancellationToken)
|
||||
@@ -63,15 +66,28 @@ public class DatasetExplorerEventHandler(
|
||||
var detections = datasetExplorer.ExplorerEditor.CurrentDetections
|
||||
.Select(x => new Detection(a.Name, x.GetLabel(datasetExplorer.ExplorerEditor.RenderSize)))
|
||||
.ToList();
|
||||
var index = datasetExplorer.ThumbnailsView.SelectedIndex;
|
||||
await annotationService.SaveAnnotation(a.OriginalMediaName, a.Time, detections, SourceEnum.Manual, token: cancellationToken);
|
||||
datasetExplorer.SwitchTab(toEditor: false);
|
||||
await datasetExplorer.EditAnnotation(index + 1);
|
||||
break;
|
||||
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||
datasetExplorer.ExplorerEditor.RemoveSelectedAnns();
|
||||
if (datasetExplorer.ExplorerEditor.CurrentDetections.Any(x => x.IsSelected))
|
||||
datasetExplorer.ExplorerEditor.RemoveSelectedAnns();
|
||||
else
|
||||
{
|
||||
await datasetExplorer.DeleteAnnotations();
|
||||
await datasetExplorer.EditAnnotation(datasetExplorer.ThumbnailsView.SelectedIndex);
|
||||
}
|
||||
break;
|
||||
case PlaybackControlEnum.RemoveAllAnns:
|
||||
datasetExplorer.ExplorerEditor.RemoveAllAnns();
|
||||
break;
|
||||
case PlaybackControlEnum.Next:
|
||||
await datasetExplorer.EditAnnotation(datasetExplorer.ThumbnailsView.SelectedIndex + 1);
|
||||
break;
|
||||
case PlaybackControlEnum.Previous:
|
||||
await datasetExplorer.EditAnnotation(datasetExplorer.ThumbnailsView.SelectedIndex - 1);
|
||||
break;
|
||||
case PlaybackControlEnum.Close:
|
||||
datasetExplorer.SwitchTab(toEditor: false);
|
||||
break;
|
||||
|
||||
@@ -72,5 +72,5 @@ In case of fbgemm.dll error (Windows specific):
|
||||
|
||||
<h3>Build exe</h3>
|
||||
```
|
||||
.\build.exe
|
||||
.\build.cmd
|
||||
```
|
||||
|
||||
@@ -14,4 +14,4 @@ cdef class ApiClient:
|
||||
|
||||
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, bint is_tensor=*)
|
||||
|
||||
@@ -73,6 +73,7 @@ cdef class ApiClient:
|
||||
|
||||
cdef load_bytes(self, str filename, str folder=None):
|
||||
folder = folder or self.credentials.folder
|
||||
|
||||
hardware_service = HardwareService()
|
||||
cdef HardwareInfo hardware = hardware_service.get_hardware_info()
|
||||
|
||||
@@ -110,11 +111,20 @@ cdef class ApiClient:
|
||||
constants.log(<str>f'Downloaded file: {filename}, {len(data)} bytes')
|
||||
return data
|
||||
|
||||
cdef load_ai_model(self):
|
||||
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)
|
||||
cdef load_ai_model(self, bint is_tensor=False):
|
||||
if is_tensor:
|
||||
big_file = <str> constants.AI_TENSOR_MODEL_FILE_BIG
|
||||
small_file = <str> constants.AI_TENSOR_MODEL_FILE_SMALL
|
||||
else:
|
||||
big_file = <str>constants.AI_ONNX_MODEL_FILE_BIG
|
||||
small_file = <str> constants.AI_ONNX_MODEL_FILE_SMALL
|
||||
|
||||
with open(big_file, 'rb') as binary_file:
|
||||
encrypted_bytes_big = binary_file.read()
|
||||
print('read encrypted big file')
|
||||
print(f'small file: {small_file}')
|
||||
encrypted_bytes_small = self.load_bytes(small_file)
|
||||
print('read encrypted small file')
|
||||
encrypted_model_bytes = encrypted_bytes_small + encrypted_bytes_big
|
||||
key = Security.get_model_encryption_key()
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
datas = []
|
||||
binaries = []
|
||||
hiddenimports = ['constants', 'annotation', 'credentials', 'file_data', 'user', 'security', 'secure_model', 'api_client', 'hardware_service', 'remote_command', 'ai_config', 'inference_engine', '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')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('requests')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('psutil')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('msgpack')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('zmq')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('cryptography')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('cv2')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('onnxruntime')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('tensorrt')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('pycuda')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
tmp_ret = collect_all('re')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['start.py'],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='azaion-inference',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='azaion-inference',
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
pyinstaller --onefile ^
|
||||
pyinstaller --name=azaion-inference ^
|
||||
--collect-all pyyaml ^
|
||||
--collect-all jwt ^
|
||||
--collect-all requests ^
|
||||
@@ -8,6 +8,9 @@ pyinstaller --onefile ^
|
||||
--collect-all cryptography ^
|
||||
--collect-all cv2 ^
|
||||
--collect-all onnxruntime ^
|
||||
--collect-all tensorrt ^
|
||||
--collect-all pycuda ^
|
||||
--collect-all re ^
|
||||
--hidden-import constants ^
|
||||
--hidden-import annotation ^
|
||||
--hidden-import credentials ^
|
||||
@@ -19,6 +22,7 @@ pyinstaller --onefile ^
|
||||
--hidden-import hardware_service ^
|
||||
--hidden-import remote_command ^
|
||||
--hidden-import ai_config ^
|
||||
--hidden-import inference_engine ^
|
||||
--hidden-import inference ^
|
||||
--hidden-import remote_command_handler ^
|
||||
start.py
|
||||
@@ -6,8 +6,13 @@ cdef str ANNOTATIONS_QUEUE # Name of the annotations queue in rabbit
|
||||
|
||||
cdef str API_URL # Base URL for the external API
|
||||
cdef str QUEUE_CONFIG_FILENAME # queue config filename to load from api
|
||||
cdef str AI_MODEL_FILE_BIG # AI Model file (BIG part)
|
||||
cdef str AI_MODEL_FILE_SMALL # AI Model file (small part)
|
||||
|
||||
cdef str AI_ONNX_MODEL_FILE_BIG
|
||||
cdef str AI_ONNX_MODEL_FILE_SMALL
|
||||
|
||||
cdef str AI_TENSOR_MODEL_FILE_BIG
|
||||
cdef str AI_TENSOR_MODEL_FILE_SMALL
|
||||
|
||||
|
||||
cdef bytes DONE_SIGNAL
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@ cdef str ANNOTATIONS_QUEUE = "azaion-annotations"
|
||||
|
||||
cdef str API_URL = "https://api.azaion.com" # Base URL for the external API
|
||||
cdef str QUEUE_CONFIG_FILENAME = "secured-config.json"
|
||||
cdef str AI_MODEL_FILE_BIG = "azaion.onnx.big"
|
||||
cdef str AI_MODEL_FILE_SMALL = "azaion.onnx.small"
|
||||
|
||||
cdef str AI_ONNX_MODEL_FILE_BIG = "azaion.onnx.big"
|
||||
cdef str AI_ONNX_MODEL_FILE_SMALL = "azaion.onnx.small"
|
||||
|
||||
cdef str AI_TENSOR_MODEL_FILE_BIG = "azaion.engine.big"
|
||||
cdef str AI_TENSOR_MODEL_FILE_SMALL = "azaion.engine.small"
|
||||
|
||||
cdef log(str log_message, bytes client_id=None):
|
||||
local_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
||||
|
||||
@@ -5,4 +5,7 @@ cdef class HardwareInfo:
|
||||
cdef class HardwareService:
|
||||
cdef bint is_windows
|
||||
cdef get_mac_address(self, interface=*)
|
||||
|
||||
@staticmethod
|
||||
cdef has_nvidia_gpu()
|
||||
cdef HardwareInfo get_hardware_info(self)
|
||||
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
import subprocess
|
||||
import psutil
|
||||
|
||||
@@ -42,6 +43,18 @@ cdef class HardwareService:
|
||||
return addr.address.replace('-', '')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
cdef has_nvidia_gpu():
|
||||
try:
|
||||
output = subprocess.check_output(['nvidia-smi']).decode()
|
||||
match = re.search(r'CUDA Version:\s*([\d.]+)', output)
|
||||
if match:
|
||||
return float(match.group(1)) > 11
|
||||
return False
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
cdef HardwareInfo get_hardware_info(self):
|
||||
if self.is_windows:
|
||||
os_command = (
|
||||
|
||||
@@ -2,10 +2,11 @@ from remote_command cimport RemoteCommand
|
||||
from annotation cimport Annotation, Detection
|
||||
from ai_config cimport AIRecognitionConfig
|
||||
from api_client cimport ApiClient
|
||||
from inference_engine cimport InferenceEngine
|
||||
|
||||
cdef class Inference:
|
||||
cdef ApiClient api_client
|
||||
cdef object session
|
||||
cdef InferenceEngine engine
|
||||
cdef object on_annotation
|
||||
cdef Annotation _previous_annotation
|
||||
cdef AIRecognitionConfig ai_config
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import json
|
||||
import mimetypes
|
||||
import subprocess
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as onnx
|
||||
|
||||
cimport constants
|
||||
from remote_command cimport RemoteCommand
|
||||
from annotation cimport Detection, Annotation
|
||||
from ai_config cimport AIRecognitionConfig
|
||||
from inference_engine cimport OnnxEngine, TensorRTEngine
|
||||
from hardware_service cimport HardwareService
|
||||
|
||||
cdef class Inference:
|
||||
def __init__(self, api_client, on_annotation):
|
||||
self.api_client = api_client
|
||||
self.on_annotation = on_annotation
|
||||
self.stop_signal = False
|
||||
self.session = None
|
||||
self.model_input = None
|
||||
self.model_width = 0
|
||||
self.model_height = 0
|
||||
self.engine = None
|
||||
self.class_names = None
|
||||
|
||||
def init_ai(self):
|
||||
model_bytes = self.api_client.load_ai_model()
|
||||
self.session = onnx.InferenceSession(
|
||||
model_bytes, providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
model_inputs = self.session.get_inputs()
|
||||
self.model_input = model_inputs[0].name
|
||||
input_shape = model_inputs[0].shape
|
||||
self.model_width = input_shape[2]
|
||||
self.model_height = input_shape[3]
|
||||
print(f'AI detection model input: {self.model_input} ({self.model_width}, {self.model_height})')
|
||||
model_meta = self.session.get_modelmeta()
|
||||
print("Metadata:", model_meta.custom_metadata_map)
|
||||
self.class_names = eval(model_meta.custom_metadata_map["names"])
|
||||
if self.engine is not None:
|
||||
return
|
||||
|
||||
is_nvidia = HardwareService.has_nvidia_gpu()
|
||||
if is_nvidia:
|
||||
model_bytes = self.api_client.load_ai_model(is_tensor=True)
|
||||
self.engine = TensorRTEngine(model_bytes, batch_size=4)
|
||||
else:
|
||||
model_bytes = self.api_client.load_ai_model()
|
||||
self.engine = OnnxEngine(model_bytes, batch_size=4)
|
||||
|
||||
self.model_height, self.model_width = self.engine.get_input_shape()
|
||||
self.class_names = self.engine.get_class_names()
|
||||
|
||||
cdef preprocess(self, frames):
|
||||
blobs = [cv2.dnn.blobFromImage(frame,
|
||||
@@ -47,33 +47,37 @@ cdef class Inference:
|
||||
return np.vstack(blobs)
|
||||
|
||||
cdef postprocess(self, output, ai_config):
|
||||
print('enter postprocess')
|
||||
cdef list[Detection] detections = []
|
||||
cdef int ann_index
|
||||
cdef float x1, y1, x2, y2, conf, cx, cy, w, h
|
||||
cdef int class_id
|
||||
cdef list[list[Detection]] results = []
|
||||
print('start try: code')
|
||||
try:
|
||||
for ann_index in range(len(output[0])):
|
||||
detections.clear()
|
||||
for det in output[0][ann_index]:
|
||||
if det[4] == 0: # if confidence is 0 then valid points are over.
|
||||
break
|
||||
x1 = det[0] / self.model_width
|
||||
y1 = det[1] / self.model_height
|
||||
x2 = det[2] / self.model_width
|
||||
y2 = det[3] / self.model_height
|
||||
conf = round(det[4], 2)
|
||||
class_id = int(det[5])
|
||||
|
||||
for ann_index in range(len(output[0])):
|
||||
detections.clear()
|
||||
for det in output[0][ann_index]:
|
||||
if det[4] == 0: # if confidence is 0 then valid points are over.
|
||||
break
|
||||
x1 = det[0] / self.model_width
|
||||
y1 = det[1] / self.model_height
|
||||
x2 = det[2] / self.model_width
|
||||
y2 = det[3] / self.model_height
|
||||
conf = round(det[4], 2)
|
||||
class_id = int(det[5])
|
||||
|
||||
x = (x1 + x2) / 2
|
||||
y = (y1 + y2) / 2
|
||||
w = x2 - x1
|
||||
h = y2 - y1
|
||||
if conf >= ai_config.probability_threshold:
|
||||
detections.append(Detection(x, y, w, h, class_id, conf))
|
||||
filtered_detections = self.remove_overlapping_detections(detections)
|
||||
results.append(filtered_detections)
|
||||
return results
|
||||
x = (x1 + x2) / 2
|
||||
y = (y1 + y2) / 2
|
||||
w = x2 - x1
|
||||
h = y2 - y1
|
||||
if conf >= ai_config.probability_threshold:
|
||||
detections.append(Detection(x, y, w, h, class_id, conf))
|
||||
filtered_detections = self.remove_overlapping_detections(detections)
|
||||
results.append(filtered_detections)
|
||||
return results
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to postprocess: {str(e)}")
|
||||
|
||||
cdef remove_overlapping_detections(self, list[Detection] detections):
|
||||
cdef Detection det1, det2
|
||||
@@ -121,8 +125,7 @@ cdef class Inference:
|
||||
raise Exception('ai recognition config is empty')
|
||||
|
||||
self.stop_signal = False
|
||||
if self.session is None:
|
||||
self.init_ai()
|
||||
self.init_ai()
|
||||
|
||||
print(ai_config.paths)
|
||||
for m in ai_config.paths:
|
||||
@@ -160,7 +163,9 @@ cdef class Inference:
|
||||
|
||||
if len(batch_frames) == ai_config.model_batch_size:
|
||||
input_blob = self.preprocess(batch_frames)
|
||||
outputs = self.session.run(None, {self.model_input: input_blob})
|
||||
|
||||
outputs = self.engine.run(input_blob)
|
||||
|
||||
list_detections = self.postprocess(outputs, ai_config)
|
||||
for i in range(len(list_detections)):
|
||||
detections = list_detections[i]
|
||||
@@ -189,7 +194,9 @@ cdef class Inference:
|
||||
timestamps.append(0)
|
||||
|
||||
input_blob = self.preprocess(frames)
|
||||
outputs = self.session.run(None, {self.model_input: input_blob})
|
||||
|
||||
outputs = self.engine.run(input_blob)
|
||||
|
||||
list_detections = self.postprocess(outputs, ai_config)
|
||||
for i in range(len(list_detections)):
|
||||
detections = list_detections[i]
|
||||
@@ -199,6 +206,7 @@ cdef class Inference:
|
||||
print(annotation.to_str(self.class_names))
|
||||
self.on_annotation(cmd, annotation)
|
||||
|
||||
|
||||
cdef stop(self):
|
||||
self.stop_signal = True
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from typing import List, Tuple
|
||||
import numpy as np
|
||||
|
||||
|
||||
cdef class InferenceEngine:
|
||||
cdef public int batch_size
|
||||
cdef tuple get_input_shape(self)
|
||||
cdef int get_batch_size(self)
|
||||
cdef get_class_names(self)
|
||||
cpdef run(self, input_data)
|
||||
|
||||
cdef class OnnxEngine(InferenceEngine):
|
||||
cdef object session
|
||||
cdef list model_inputs
|
||||
cdef str input_name
|
||||
cdef object input_shape
|
||||
cdef object class_names
|
||||
|
||||
cdef class TensorRTEngine(InferenceEngine):
|
||||
cdef object stream
|
||||
cdef object context
|
||||
cdef str input_name
|
||||
cdef str output_name
|
||||
cdef object d_input
|
||||
cdef object d_output
|
||||
cdef object input_shape
|
||||
cdef object output_shape
|
||||
cdef object h_output
|
||||
cdef object class_names
|
||||
@@ -0,0 +1,140 @@
|
||||
import json
|
||||
import struct
|
||||
from typing import List, Tuple
|
||||
import numpy as np
|
||||
import onnxruntime as onnx
|
||||
import tensorrt as trt
|
||||
import pycuda.driver as cuda
|
||||
import pycuda.autoinit # required for automatically initialize CUDA, do not remove.
|
||||
|
||||
|
||||
cdef class InferenceEngine:
|
||||
def __init__(self, model_bytes: bytes, batch_size: int = 1, **kwargs):
|
||||
self.batch_size = batch_size
|
||||
|
||||
cdef tuple get_input_shape(self):
|
||||
raise NotImplementedError("Subclass must implement get_input_shape")
|
||||
|
||||
cdef int get_batch_size(self):
|
||||
return self.batch_size
|
||||
|
||||
cpdef run(self, input_data):
|
||||
raise NotImplementedError("Subclass must implement run")
|
||||
|
||||
cdef get_class_names(self):
|
||||
raise NotImplementedError("Subclass must implement get_class_names")
|
||||
|
||||
|
||||
cdef class OnnxEngine(InferenceEngine):
|
||||
def __init__(self, model_bytes: bytes, batch_size: int = 1, **kwargs):
|
||||
super().__init__(model_bytes, batch_size)
|
||||
self.batch_size = batch_size
|
||||
self.session = onnx.InferenceSession(model_bytes, providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
||||
self.model_inputs = self.session.get_inputs()
|
||||
self.input_name = self.model_inputs[0].name
|
||||
self.input_shape = self.model_inputs[0].shape
|
||||
if self.input_shape[0] != -1:
|
||||
self.batch_size = self.input_shape[0]
|
||||
print(f'AI detection model input: {self.model_inputs} {self.input_shape}')
|
||||
model_meta = self.session.get_modelmeta()
|
||||
print("Metadata:", model_meta.custom_metadata_map)
|
||||
self.class_names = eval(model_meta.custom_metadata_map["names"])
|
||||
|
||||
cdef tuple get_input_shape(self):
|
||||
shape = self.input_shape
|
||||
return shape[2], shape[3]
|
||||
|
||||
cdef int get_batch_size(self):
|
||||
return self.batch_size
|
||||
|
||||
cdef get_class_names(self):
|
||||
return self.class_names
|
||||
|
||||
cpdef run(self, input_data):
|
||||
return self.session.run(None, {self.input_name: input_data})
|
||||
|
||||
|
||||
cdef class TensorRTEngine(InferenceEngine):
|
||||
def __init__(self, model_bytes: bytes, batch_size: int = 4, **kwargs):
|
||||
super().__init__(model_bytes, batch_size)
|
||||
self.batch_size = batch_size
|
||||
print('Enter init TensorRT')
|
||||
try:
|
||||
logger = trt.Logger(trt.Logger.WARNING)
|
||||
|
||||
metadata_len = struct.unpack("<I", model_bytes[:4])[0]
|
||||
try:
|
||||
metadata = json.loads(model_bytes[4:4 + metadata_len])
|
||||
print(f"Model metadata: {json.dumps(metadata, indent=2)}")
|
||||
string_dict = metadata['names']
|
||||
self.class_names = {int(k): v for k, v in string_dict.items()}
|
||||
except json.JSONDecodeError:
|
||||
print(f"Failed to parse metadata")
|
||||
return
|
||||
engine_data = model_bytes[4 + metadata_len:]
|
||||
|
||||
|
||||
runtime = trt.Runtime(logger)
|
||||
engine = runtime.deserialize_cuda_engine(engine_data)
|
||||
|
||||
if engine is None:
|
||||
raise RuntimeError(f"Failed to load TensorRT engine from bytes")
|
||||
|
||||
self.context = engine.create_execution_context()
|
||||
# input
|
||||
self.input_name = engine.get_tensor_name(0)
|
||||
engine_input_shape = engine.get_tensor_shape(self.input_name)
|
||||
if engine_input_shape[0] != -1:
|
||||
self.batch_size = engine_input_shape[0]
|
||||
|
||||
self.input_shape = [
|
||||
self.batch_size,
|
||||
engine_input_shape[1], # Channels (usually fixed at 3 for RGB)
|
||||
1280 if engine_input_shape[2] == -1 else engine_input_shape[2], # Height
|
||||
1280 if engine_input_shape[3] == -1 else engine_input_shape[3] # Width
|
||||
]
|
||||
self.context.set_input_shape(self.input_name, self.input_shape)
|
||||
input_size = trt.volume(self.input_shape) * np.dtype(np.float32).itemsize
|
||||
self.d_input = cuda.mem_alloc(input_size)
|
||||
|
||||
# output
|
||||
self.output_name = engine.get_tensor_name(1)
|
||||
engine_output_shape = tuple(engine.get_tensor_shape(self.output_name))
|
||||
self.output_shape = [
|
||||
batch_size if self.input_shape[0] == -1 else self.input_shape[0],
|
||||
300 if engine_output_shape[1] == -1 else engine_output_shape[1], # max detections number
|
||||
6 if engine_output_shape[2] == -1 else engine_output_shape[2] # x1 y1 x2 y2 conf cls
|
||||
]
|
||||
self.h_output = cuda.pagelocked_empty(tuple(self.output_shape), dtype=np.float32)
|
||||
self.d_output = cuda.mem_alloc(self.h_output.nbytes)
|
||||
|
||||
self.stream = cuda.Stream()
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to initialize TensorRT engine: {str(e)}")
|
||||
|
||||
cdef tuple get_input_shape(self):
|
||||
return self.input_shape[2], self.input_shape[3]
|
||||
|
||||
cdef int get_batch_size(self):
|
||||
return self.batch_size
|
||||
|
||||
cdef get_class_names(self):
|
||||
return self.class_names
|
||||
|
||||
cpdef run(self, input_data):
|
||||
try:
|
||||
cuda.memcpy_htod_async(self.d_input, input_data, self.stream)
|
||||
self.context.set_tensor_address(self.input_name, int(self.d_input)) # input buffer
|
||||
self.context.set_tensor_address(self.output_name, int(self.d_output)) # output buffer
|
||||
|
||||
self.context.execute_async_v3(stream_handle=self.stream.handle)
|
||||
self.stream.synchronize()
|
||||
|
||||
# Fix: Remove the stream parameter from memcpy_dtoh
|
||||
cuda.memcpy_dtoh(self.h_output, self.d_output)
|
||||
output = self.h_output.reshape(self.output_shape)
|
||||
return [output]
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to run TensorRT inference: {str(e)}")
|
||||
@@ -10,3 +10,5 @@ pyjwt
|
||||
zmq
|
||||
requests
|
||||
pyyaml
|
||||
pycuda
|
||||
tensorrt
|
||||
@@ -14,6 +14,7 @@ extensions = [
|
||||
Extension('user', ['user.pyx']),
|
||||
Extension('api_client', ['api_client.pyx']),
|
||||
Extension('ai_config', ['ai_config.pyx']),
|
||||
Extension('inference_engine', ['inference_engine.pyx'], include_dirs=[np.get_include()]),
|
||||
Extension('inference', ['inference.pyx'], include_dirs=[np.get_include()]),
|
||||
Extension('main', ['main.pyx']),
|
||||
]
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
||||
<ProjectReference Include="..\dummy\Azaion.Annotator\Azaion.Annotator.csproj" />
|
||||
<ProjectReference Include="..\dummy\Azaion.Dataset\Azaion.Dataset.csproj" />
|
||||
<ProjectReference Include="..\Azaion.Annotator\Azaion.Annotator.csproj" />
|
||||
<ProjectReference Include="..\Azaion.Dataset\Azaion.Dataset.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -61,8 +61,8 @@
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="Build">
|
||||
<MakeDir Directories="$(TargetDir)dummy" />
|
||||
<Move SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" />
|
||||
<Move SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" />
|
||||
<Copy SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" />
|
||||
<Copy SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" />
|
||||
<Exec Command="upload.cmd $(ConfigurationName) stage" />
|
||||
</Target>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
BorderBrush="DimGray"
|
||||
BorderThickness="0,0,0,1"
|
||||
HorizontalAlignment="Left"
|
||||
Text=""
|
||||
Text="admin@azaion.com"
|
||||
/>
|
||||
<TextBlock Text="Пароль"
|
||||
Grid.Row="2"
|
||||
@@ -89,7 +89,7 @@
|
||||
Width="300"
|
||||
BorderThickness="0,0,0,1"
|
||||
HorizontalAlignment="Left"
|
||||
Password=""/>
|
||||
Password="Az@1on1000Odm$n"/>
|
||||
</Grid>
|
||||
<Button x:Name="LoginBtn"
|
||||
Content="Вхід"
|
||||
|
||||
+19
-16
@@ -8,7 +8,8 @@
|
||||
},
|
||||
"GpsDeniedClientConfig": {
|
||||
"ZeroMqHost": "127.0.0.1",
|
||||
"ZeroMqPort": 5227,
|
||||
"ZeroMqPort": 5555,
|
||||
"ZeroMqReceiverPort": 5556,
|
||||
"RetryCount": 25,
|
||||
"TimeoutSeconds": 5
|
||||
},
|
||||
@@ -21,20 +22,22 @@
|
||||
},
|
||||
"AnnotationConfig": {
|
||||
"DetectionClasses": [
|
||||
{ "Id": 0, "Name": "Броньована техніка", "ShortName": "Броня" },
|
||||
{ "Id": 1, "Name": "Вантажівка", "ShortName": "Вантаж." },
|
||||
{ "Id": 2, "Name": "Машина легкова", "ShortName": "Машина" },
|
||||
{ "Id": 3, "Name": "Артилерія", "ShortName": "Арта" },
|
||||
{ "Id": 4, "Name": "Тінь від техніки", "ShortName": "Тінь" },
|
||||
{ "Id": 5, "Name": "Окопи", "ShortName": "Окопи" },
|
||||
{ "Id": 6, "Name": "Військовий", "ShortName": "Військов" },
|
||||
{ "Id": 7, "Name": "Накати", "ShortName": "Накати" },
|
||||
{ "Id": 8, "Name": "Танк з захистом", "ShortName": "Танк.захист" },
|
||||
{ "Id": 9, "Name": "Дим", "ShortName": "Дим" },
|
||||
{ "Id": 10, "Name": "Літак", "ShortName": "Літак" },
|
||||
{ "Id": 11, "Name": "Мотоцикл", "ShortName": "Мото" },
|
||||
{ "Id": 12, "Name": "Маскування сіткою", "ShortName": "Сітка" },
|
||||
{ "Id": 13, "Name": "Маскування гілками", "ShortName": "Гілки" }
|
||||
{ "Id": 0, "Name": "ArmorVehicle", "ShortName": "Броня", "Color": "#FF0000" },
|
||||
{ "Id": 1, "Name": "Truck", "ShortName": "Вантаж.", "Color": "#00FF00" },
|
||||
{ "Id": 2, "Name": "Vehicle", "ShortName": "Машина", "Color": "#0000FF" },
|
||||
{ "Id": 3, "Name": "Atillery", "ShortName": "Арта", "Color": "#FFFF00" },
|
||||
{ "Id": 4, "Name": "Shadow", "ShortName": "Тінь", "Color": "#FF00FF" },
|
||||
{ "Id": 5, "Name": "Trenches", "ShortName": "Окопи", "Color": "#00FFFF" },
|
||||
{ "Id": 6, "Name": "MilitaryMan", "ShortName": "Військов", "Color": "#188021" },
|
||||
{ "Id": 7, "Name": "TyreTracks", "ShortName": "Накати", "Color": "#800000" },
|
||||
{ "Id": 8, "Name": "AdditArmoredTank", "ShortName": "Танк.захист", "Color": "#008000" },
|
||||
{ "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#000080" },
|
||||
{ "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#000080" },
|
||||
{ "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#808000" },
|
||||
{ "Id": 12, "Name": "CamouflageNnet", "ShortName": "Сітка", "Color": "#800080" },
|
||||
{ "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#008080" },
|
||||
{ "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#0050A0" },
|
||||
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#008080" }
|
||||
],
|
||||
"LastSelectedExplorerClass": null,
|
||||
"VideoFormats": [ ".mp4", ".mov", ".avi" ],
|
||||
@@ -52,7 +55,7 @@
|
||||
"TrackingProbabilityIncrease": 15.0,
|
||||
"TrackingIntersectionThreshold": 0.8,
|
||||
|
||||
"ModelBatchSize": 2
|
||||
"ModelBatchSize": 4
|
||||
},
|
||||
"ThumbnailConfig": { "Size": "240,135", "Border": 10 },
|
||||
"MapConfig":
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"ZeroMqHost": "127.0.0.1",
|
||||
"ZeroMqPort": 5131,
|
||||
"RetryCount": 25,
|
||||
"TimeoutSeconds": 5,
|
||||
"ResourcesFolder": "stage"
|
||||
"TimeoutSeconds": 5
|
||||
},
|
||||
"GpsDeniedClientConfig": {
|
||||
"ZeroMqHost": "127.0.0.1",
|
||||
@@ -21,20 +20,22 @@
|
||||
},
|
||||
"AnnotationConfig": {
|
||||
"DetectionClasses": [
|
||||
{ "Id": 0, "Name": "Броньована техніка", "ShortName": "Броня" },
|
||||
{ "Id": 1, "Name": "Вантажівка", "ShortName": "Вантаж." },
|
||||
{ "Id": 2, "Name": "Машина легкова", "ShortName": "Машина" },
|
||||
{ "Id": 3, "Name": "Артилерія", "ShortName": "Арта" },
|
||||
{ "Id": 4, "Name": "Тінь від техніки", "ShortName": "Тінь" },
|
||||
{ "Id": 5, "Name": "Окопи", "ShortName": "Окопи" },
|
||||
{ "Id": 6, "Name": "Військовий", "ShortName": "Військов" },
|
||||
{ "Id": 7, "Name": "Накати", "ShortName": "Накати" },
|
||||
{ "Id": 8, "Name": "Танк з захистом", "ShortName": "Танк.захист" },
|
||||
{ "Id": 9, "Name": "Дим", "ShortName": "Дим" },
|
||||
{ "Id": 10, "Name": "Літак", "ShortName": "Літак" },
|
||||
{ "Id": 11, "Name": "Мотоцикл", "ShortName": "Мото" },
|
||||
{ "Id": 12, "Name": "Маскування сіткою", "ShortName": "Сітка" },
|
||||
{ "Id": 13, "Name": "Маскування гілками", "ShortName": "Гілки" }
|
||||
{ "Id": 0, "Name": "ArmorVehicle", "ShortName": "Броня", "Color": "#80FF0000" },
|
||||
{ "Id": 1, "Name": "Truck", "ShortName": "Вантаж.", "Color": "#8000FF00" },
|
||||
{ "Id": 2, "Name": "Vehicle", "ShortName": "Машина", "Color": "#800000FF" },
|
||||
{ "Id": 3, "Name": "Atillery", "ShortName": "Арта", "Color": "#80FFFF00" },
|
||||
{ "Id": 4, "Name": "Shadow", "ShortName": "Тінь", "Color": "#80FF00FF" },
|
||||
{ "Id": 5, "Name": "Trenches", "ShortName": "Окопи", "Color": "#8000FFFF" },
|
||||
{ "Id": 6, "Name": "MilitaryMan", "ShortName": "Військов", "Color": "#80188021" },
|
||||
{ "Id": 7, "Name": "TyreTracks", "ShortName": "Накати", "Color": "#80800000" },
|
||||
{ "Id": 8, "Name": "AdditArmoredTank", "ShortName": "Танк.захист", "Color": "#80008000" },
|
||||
{ "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#8080000080" },
|
||||
{ "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#80000080" },
|
||||
{ "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#80808000" },
|
||||
{ "Id": 12, "Name": "CamouflageNnet", "ShortName": "Сітка", "Color": "#80800080" },
|
||||
{ "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#80008080" },
|
||||
{ "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#800050A0" },
|
||||
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#80008080" }
|
||||
],
|
||||
"LastSelectedExplorerClass": null,
|
||||
"VideoFormats": [ ".mp4", ".mov", ".avi" ],
|
||||
|
||||
+10
-3
@@ -21,7 +21,7 @@ move dist\Azaion.Dataset.dll dist\dummy\
|
||||
|
||||
echo Build Cython app
|
||||
cd Azaion.Inference
|
||||
.\venv\Scripts\pyinstaller --onefile ^
|
||||
.\venv\Scripts\pyinstaller --name=azaion-inference ^
|
||||
--collect-all jwt ^
|
||||
--collect-all requests ^
|
||||
--collect-all psutil ^
|
||||
@@ -30,6 +30,9 @@ cd Azaion.Inference
|
||||
--collect-all cryptography ^
|
||||
--collect-all cv2 ^
|
||||
--collect-all onnxruntime ^
|
||||
--collect-all tensorrt ^
|
||||
--collect-all pycuda ^
|
||||
--collect-all re ^
|
||||
--hidden-import constants ^
|
||||
--hidden-import annotation ^
|
||||
--hidden-import credentials ^
|
||||
@@ -41,6 +44,7 @@ cd Azaion.Inference
|
||||
--hidden-import hardware_service ^
|
||||
--hidden-import remote_command ^
|
||||
--hidden-import ai_config ^
|
||||
--hidden-import inference_engine ^
|
||||
--hidden-import inference ^
|
||||
--hidden-import remote_command_handler ^
|
||||
start.py
|
||||
@@ -51,7 +55,9 @@ cd..
|
||||
echo Download onnx model
|
||||
cd build
|
||||
call cdn_manager.exe download models azaion.onnx.big
|
||||
move azaion.onnx.big ..\dist\
|
||||
call cdn_manager.exe download models azaion.engine.big
|
||||
|
||||
move azaion.* ..\dist\
|
||||
cd..
|
||||
|
||||
echo Copy ico
|
||||
@@ -64,8 +70,9 @@ copy %cudnn-folder%\* dist\*
|
||||
del dist\cudnn_adv64_9.dll
|
||||
|
||||
|
||||
|
||||
echo building installer...
|
||||
iscc build\installer.iss
|
||||
cd build\
|
||||
echo uploading installer...
|
||||
call .\cdn_manager.exe upload suite AzaionSuiteInstaller.exe ..\AzaionSuiteInstaller.exe
|
||||
call cdn_manager.exe upload suite AzaionSuiteInstaller.exe ..\AzaionSuiteInstaller.exe
|
||||
Reference in New Issue
Block a user