mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 23:46:30 +00:00
make thumbnail generating multithread
fix the bug with open video add class distribution chart
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Azaion.Annotator.Extensions;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.Test;
|
||||||
|
|
||||||
|
public class ParallelExtTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ParallelExtWorksOkTest()
|
||||||
|
{
|
||||||
|
var list = Enumerable.Range(0, 10).ToList();
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
await ParallelExt.ForEachAsync(list, async (i, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(i), cancellationToken);
|
||||||
|
}, new ParallelOptions
|
||||||
|
{
|
||||||
|
CpuUtilPercent = 100,
|
||||||
|
ProgressUpdateInterval = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
var elapsed = sw.Elapsed;
|
||||||
|
elapsed.Should().BeLessThan(TimeSpan.FromSeconds(11));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParallelLibWorksOkTest()
|
||||||
|
{
|
||||||
|
var list = Enumerable.Range(0, 10).ToList();
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
await Parallel.ForEachAsync(list, new System.Threading.Tasks.ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = Environment.ProcessorCount,
|
||||||
|
}, async (i, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(i), cancellationToken);
|
||||||
|
});
|
||||||
|
var elapsed = sw.Elapsed;
|
||||||
|
elapsed.Should().BeLessThan(TimeSpan.FromSeconds(11));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="RangeTree" Version="3.0.1" />
|
<PackageReference Include="RangeTree" Version="3.0.1" />
|
||||||
|
<PackageReference Include="ScottPlot.WPF" Version="5.0.39" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ public class FormState
|
|||||||
public ObservableCollection<AnnotationResult> AnnotationResults { get; set; } = [];
|
public ObservableCollection<AnnotationResult> AnnotationResults { get; set; } = [];
|
||||||
public WindowsEnum ActiveWindow { get; set; }
|
public WindowsEnum ActiveWindow { get; set; }
|
||||||
|
|
||||||
public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}";
|
public string GetTimeName(TimeSpan? ts) => $"{VideoName}_{ts:hmmssf}";
|
||||||
|
|
||||||
public TimeSpan? GetTime(string name)
|
public TimeSpan? GetTime(string name)
|
||||||
{
|
{
|
||||||
var timeStr = name.Split("_").LastOrDefault();
|
var timeStr = name.Split("_").LastOrDefault();
|
||||||
if (string.IsNullOrEmpty(timeStr) || timeStr.Length < 7)
|
if (string.IsNullOrEmpty(timeStr) || timeStr.Length < 6)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
|
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
|
||||||
|
|||||||
@@ -148,9 +148,9 @@ public class YoloLabel : Label
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<List<YoloLabel>> ReadFromFile(string filename)
|
public static async Task<List<YoloLabel>> ReadFromFile(string filename, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var str = await File.ReadAllTextAsync(filename);
|
var str = await File.ReadAllTextAsync(filename, cancellationToken);
|
||||||
|
|
||||||
return str.Split('\n')
|
return str.Split('\n')
|
||||||
.Select(Parse)
|
.Select(Parse)
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
||||||
xmlns:local="clr-namespace:Azaion.Annotator"
|
|
||||||
xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
|
xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
|
||||||
xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
|
xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
|
||||||
|
xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Браузер анотацій" Height="900" Width="1200">
|
Title="Переглядач анотацій" Height="900" Width="1200">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<DataTemplate x:Key="ThumbnailTemplate" DataType="{x:Type dto:ThumbnailDto}">
|
<DataTemplate x:Key="ThumbnailTemplate" DataType="{x:Type dto:ThumbnailDto}">
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Background="Black">
|
Background="Black">
|
||||||
<TabItem Header="Браузер">
|
<TabItem Header="Анотації">
|
||||||
<vwp:GridView
|
<vwp:GridView
|
||||||
Name="ThumbnailsView"
|
Name="ThumbnailsView"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
@@ -70,12 +70,15 @@
|
|||||||
>
|
>
|
||||||
</vwp:GridView>
|
</vwp:GridView>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Перегляд">
|
<TabItem Header="Редактор">
|
||||||
<controls:CanvasEditor x:Name="ExplorerEditor"
|
<controls:CanvasEditor x:Name="ExplorerEditor"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
HorizontalAlignment="Stretch" >
|
HorizontalAlignment="Stretch" >
|
||||||
</controls:CanvasEditor>
|
</controls:CanvasEditor>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="Розподіл класів">
|
||||||
|
<ScottPlot:WpfPlot x:Name="ClassDistribution" />
|
||||||
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using Azaion.Annotator.DTO;
|
|||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using ScottPlot;
|
||||||
|
using Color = ScottPlot.Color;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
@@ -48,7 +50,6 @@ public partial class DatasetExplorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = this;
|
|
||||||
Loaded += async (_, _) =>
|
Loaded += async (_, _) =>
|
||||||
{
|
{
|
||||||
AllAnnotationClasses = new ObservableCollection<AnnotationClass>(
|
AllAnnotationClasses = new ObservableCollection<AnnotationClass>(
|
||||||
@@ -88,12 +89,14 @@ public partial class DatasetExplorer
|
|||||||
LvClasses.SelectedIndex = config.LastSelectedExplorerClass ?? 0;
|
LvClasses.SelectedIndex = config.LastSelectedExplorerClass ?? 0;
|
||||||
ExplorerEditor.CurrentAnnClass = (AnnotationClass)LvClasses.SelectedItem;
|
ExplorerEditor.CurrentAnnClass = (AnnotationClass)LvClasses.SelectedItem;
|
||||||
await ReloadThumbnails();
|
await ReloadThumbnails();
|
||||||
|
LoadClassDistribution();
|
||||||
|
|
||||||
SizeChanged += async (_, _) => await SaveUserSettings();
|
SizeChanged += async (_, _) => await SaveUserSettings();
|
||||||
LocationChanged += async (_, _) => await SaveUserSettings();
|
LocationChanged += async (_, _) => await SaveUserSettings();
|
||||||
StateChanged += async (_, _) => await SaveUserSettings();
|
StateChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
|
||||||
RefreshThumbBar.Value = galleryManager.ThumbnailsPercentage;
|
RefreshThumbBar.Value = galleryManager.ThumbnailsPercentage;
|
||||||
|
DataContext = this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Closing += (sender, args) =>
|
Closing += (sender, args) =>
|
||||||
@@ -125,18 +128,17 @@ public partial class DatasetExplorer
|
|||||||
|
|
||||||
Switcher.SelectionChanged += (sender, args) =>
|
Switcher.SelectionChanged += (sender, args) =>
|
||||||
{
|
{
|
||||||
if (Switcher.SelectedIndex == 1)
|
switch (Switcher.SelectedIndex)
|
||||||
{
|
{
|
||||||
//Editor
|
case 0: //ListView
|
||||||
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
|
||||||
LvClasses.ItemsSource = _config.AnnotationClasses;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Explorer
|
|
||||||
LvClasses.ItemsSource = AllAnnotationClasses;
|
LvClasses.ItemsSource = AllAnnotationClasses;
|
||||||
LvClasses.SelectedIndex = _tempSelectedClassIdx;
|
LvClasses.SelectedIndex = _tempSelectedClassIdx;
|
||||||
ExplorerEditor.Background = null;
|
ExplorerEditor.Background = null;
|
||||||
|
break;
|
||||||
|
case 1: //Editor
|
||||||
|
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
||||||
|
LvClasses.ItemsSource = _config.AnnotationClasses;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,6 +150,41 @@ public partial class DatasetExplorer
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadClassDistribution()
|
||||||
|
{
|
||||||
|
var data = LabelsCache.SelectMany(x => x.Value)
|
||||||
|
.GroupBy(x => x)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Key,
|
||||||
|
_config.AnnotationClassesDict[x.Key].Name,
|
||||||
|
_config.AnnotationClassesDict[x.Key].Color,
|
||||||
|
ClassCount = x.Count()
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var plot = ClassDistribution.Plot;
|
||||||
|
plot.Add.Bars(data.Select(x => new Bar
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Position = -1.5 * x.Key + 1,
|
||||||
|
Label = x.ClassCount > 200 ? x.ClassCount.ToString() : "",
|
||||||
|
FillColor = new Color(x.Color.R, x.Color.G, x.Color.B, x.Color.A),
|
||||||
|
Value = x.ClassCount,
|
||||||
|
CenterLabel = true
|
||||||
|
}));
|
||||||
|
foreach (var x in data)
|
||||||
|
{
|
||||||
|
var label = plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1);
|
||||||
|
label.LabelFontSize = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
plot.Axes.AutoScale();
|
||||||
|
//plot.Axes.SetLimits(-200, data.Max(x => x.ClassCount + 3000), -2 * data.Count + 5, 5);
|
||||||
|
ClassDistribution.Background = new SolidColorBrush(System.Windows.Media.Colors.Black);
|
||||||
|
ClassDistribution.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task EditAnnotation()
|
private async Task EditAnnotation()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -171,7 +208,7 @@ public partial class DatasetExplorer
|
|||||||
ExplorerEditor.RemoveAllAnns();
|
ExplorerEditor.RemoveAllAnns();
|
||||||
foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath))
|
foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath))
|
||||||
{
|
{
|
||||||
var annClass = _config.AnnotationClasses[ann.ClassNumber];
|
var annClass = _config.AnnotationClassesDict[ann.ClassNumber];
|
||||||
var annInfo = new CanvasLabel(ann, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize);
|
var annInfo = new CanvasLabel(ann, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize);
|
||||||
ExplorerEditor.CreateAnnotation(annClass, time, annInfo);
|
ExplorerEditor.CreateAnnotation(annClass, time, annInfo);
|
||||||
}
|
}
|
||||||
@@ -248,7 +285,7 @@ public partial class DatasetExplorer
|
|||||||
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddThumbnail(string thumbnail)
|
private async Task AddThumbnail(string thumbnail, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -284,7 +321,7 @@ public partial class DatasetExplorer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var labels = await YoloLabel.ReadFromFile(labelPath);
|
var labels = await YoloLabel.ReadFromFile(labelPath, cancellationToken);
|
||||||
classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
||||||
LabelsCache.Add(name, classes);
|
LabelsCache.Add(name, classes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
namespace Azaion.Annotator.Extensions;
|
||||||
|
|
||||||
|
public class ParallelOptions
|
||||||
|
{
|
||||||
|
public int ProgressUpdateInterval { get; set; } = 100;
|
||||||
|
public Func<int, Task> ProgressFn { get; set; } = null!;
|
||||||
|
public double CpuUtilPercent { get; set; } = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ParallelExt
|
||||||
|
{
|
||||||
|
public static async Task ForEachAsync<T>(ICollection<T> source,
|
||||||
|
Func<T, CancellationToken, ValueTask> processFn,
|
||||||
|
ParallelOptions? parallelOptions = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
parallelOptions ??= new ParallelOptions
|
||||||
|
{
|
||||||
|
CpuUtilPercent = 100,
|
||||||
|
ProgressUpdateInterval = 100,
|
||||||
|
ProgressFn = i =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Processed {i} item by Task {Task.CurrentId}%");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var threadsCount = (int)(Environment.ProcessorCount * parallelOptions.CpuUtilPercent / 100.0);
|
||||||
|
|
||||||
|
var processedCount = 0;
|
||||||
|
var chunkSize = Math.Max(1, (int)(source.Count / (decimal)threadsCount));
|
||||||
|
var chunks = source.Chunk(chunkSize).ToList();
|
||||||
|
if (chunks.Count > threadsCount)
|
||||||
|
{
|
||||||
|
chunks[^2] = chunks[^2].Concat(chunks.Last()).ToArray();
|
||||||
|
chunks.RemoveAt(chunks.Count - 1);
|
||||||
|
}
|
||||||
|
var progressUpdateLock = new SemaphoreSlim(1);
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
foreach (var chunk in chunks)
|
||||||
|
{
|
||||||
|
tasks.Add(await Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
foreach (var item in chunk)
|
||||||
|
{
|
||||||
|
await processFn(item, cancellationToken);
|
||||||
|
Interlocked.Increment(ref processedCount);
|
||||||
|
if (processedCount % parallelOptions.ProgressUpdateInterval == 0 && parallelOptions.ProgressFn != null)
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await progressUpdateLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await parallelOptions.ProgressFn(processedCount);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
progressUpdateLock.Release();
|
||||||
|
}
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, TaskCreationOptions.LongRunning));
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@ using System.Drawing.Drawing2D;
|
|||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Annotator.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Color = System.Drawing.Color;
|
using Color = System.Drawing.Color;
|
||||||
|
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
@@ -14,7 +16,7 @@ public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
|
|||||||
public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGalleryManager
|
public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGalleryManager
|
||||||
{
|
{
|
||||||
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||||
private const int UPDATE_STEP = 20;
|
|
||||||
private readonly SemaphoreSlim _updateLock = new(1);
|
private readonly SemaphoreSlim _updateLock = new(1);
|
||||||
|
|
||||||
public double ThumbnailsPercentage { get; set; }
|
public double ThumbnailsPercentage { get; set; }
|
||||||
@@ -53,32 +55,35 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
.GroupBy(x => x)
|
.GroupBy(x => x)
|
||||||
.Select(gr => gr.Key)
|
.Select(gr => gr.Key)
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
var thumbnailsCount = thumbnails.Count;
|
|
||||||
|
|
||||||
var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
|
var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
|
||||||
var imagesCount = files.Length;
|
var imagesCount = files.Length;
|
||||||
|
|
||||||
for (int i = 0; i < files.Length; i++)
|
await ParallelExt.ForEachAsync(files, async (file, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var img = files[i];
|
var imgName = Path.GetFileNameWithoutExtension(file.Name);
|
||||||
ThumbnailsPercentage = imagesCount == 0 ? 0 : Math.Min(100, i * 100 / (double)imagesCount);
|
|
||||||
var imgName = Path.GetFileNameWithoutExtension(img.Name);
|
|
||||||
if (i % UPDATE_STEP == 0)
|
|
||||||
ThumbnailsUpdate?.Invoke(ThumbnailsPercentage);
|
|
||||||
if (thumbnails.Contains(imgName))
|
if (thumbnails.Contains(imgName))
|
||||||
continue;
|
return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await CreateThumbnail(img.FullName);
|
await CreateThumbnail(file.FullName, cancellationToken);
|
||||||
thumbnailsCount++;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.LogError(e, $"Failed to generate thumbnail for {img.Name}! Error: {e.Message}");
|
logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}, new ParallelOptions
|
||||||
|
{
|
||||||
await Task.Delay(10000);
|
ProgressFn = async num =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Processed {num} item by Thread {Environment.CurrentManagedThreadId}");
|
||||||
|
ThumbnailsPercentage = imagesCount == 0 ? 0 : Math.Min(100, num * 100 / (double)imagesCount);
|
||||||
|
ThumbnailsUpdate?.Invoke(ThumbnailsPercentage);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
},
|
||||||
|
CpuUtilPercent = 100,
|
||||||
|
ProgressUpdateInterval = 200
|
||||||
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -86,7 +91,7 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateThumbnail(string imgPath)
|
public async Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var bitmap = await GenerateThumbnail(imgPath);
|
var bitmap = await GenerateThumbnail(imgPath);
|
||||||
if (bitmap != null)
|
if (bitmap != null)
|
||||||
@@ -178,7 +183,7 @@ public interface IGalleryManager
|
|||||||
{
|
{
|
||||||
event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||||
double ThumbnailsPercentage { get; set; }
|
double ThumbnailsPercentage { get; set; }
|
||||||
Task CreateThumbnail(string imgPath);
|
Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default);
|
||||||
Task RefreshThumbnails();
|
Task RefreshThumbnails();
|
||||||
void ClearThumbnails();
|
void ClearThumbnails();
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
IsEnabled="True" Header="Відкрити папку..." Click="OpenFolderItemClick"/>
|
IsEnabled="True" Header="Відкрити папку..." Click="OpenFolderItemClick"/>
|
||||||
<MenuItem x:Name="OpenDataExplorerItem"
|
<MenuItem x:Name="OpenDataExplorerItem"
|
||||||
Foreground="Black"
|
Foreground="Black"
|
||||||
IsEnabled="True" Header="Відкрити браузер анотацій..." Click="OpenDataExplorerItemClick"/>
|
IsEnabled="True" Header="Відкрити переглядач анотацій..." Click="OpenDataExplorerItemClick"/>
|
||||||
<MenuItem x:Name="ReloadThumbnailsItem"
|
<MenuItem x:Name="ReloadThumbnailsItem"
|
||||||
Foreground="Black"
|
Foreground="Black"
|
||||||
IsEnabled="True" Header="Оновити базу іконок" Click="ReloadThumbnailsItemClick"/>
|
IsEnabled="True" Header="Оновити базу іконок" Click="ReloadThumbnailsItemClick"/>
|
||||||
|
|||||||
@@ -248,25 +248,25 @@ public partial class MainWindow
|
|||||||
foreach (var file in labelFiles)
|
foreach (var file in labelFiles)
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(file.Name);
|
var name = Path.GetFileNameWithoutExtension(file.Name);
|
||||||
var time = _formState.GetTime(name)!.Value;
|
var time = _formState.GetTime(name);
|
||||||
|
|
||||||
await AddAnnotation(time, await YoloLabel.ReadFromFile(file.FullName));
|
await AddAnnotation(time, await YoloLabel.ReadFromFile(file.FullName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAnnotation(TimeSpan time, List<YoloLabel> annotations)
|
public async Task AddAnnotation(TimeSpan? time, List<YoloLabel> annotations)
|
||||||
{
|
{
|
||||||
var fName = _formState.GetTimeName(time);
|
var fName = _formState.GetTimeName(time);
|
||||||
|
|
||||||
var previousAnnotations = Annotations.Query(time);
|
var timeValue = time ?? TimeSpan.FromMinutes(0);
|
||||||
|
var previousAnnotations = Annotations.Query(timeValue);
|
||||||
Annotations.Remove(previousAnnotations);
|
Annotations.Remove(previousAnnotations);
|
||||||
Annotations.Add(time.Subtract(_thresholdBefore), time.Add(_thresholdAfter), annotations);
|
Annotations.Add(timeValue.Subtract(_thresholdBefore), timeValue.Add(_thresholdAfter), annotations);
|
||||||
|
|
||||||
var existingResult = _formState.AnnotationResults.FirstOrDefault(x => x.Time == time);
|
var existingResult = _formState.AnnotationResults.FirstOrDefault(x => x.Time == time);
|
||||||
if (existingResult != null)
|
if (existingResult != null)
|
||||||
_formState.AnnotationResults.Remove(existingResult);
|
_formState.AnnotationResults.Remove(existingResult);
|
||||||
|
|
||||||
_formState.AnnotationResults.Add(new AnnotationResult(time, fName, annotations, _config));
|
_formState.AnnotationResults.Add(new AnnotationResult(timeValue, fName, annotations, _config));
|
||||||
await File.WriteAllTextAsync($"{_config.ResultsDirectory}/{fName}.json", JsonConvert.SerializeObject(_formState.AnnotationResults));
|
await File.WriteAllTextAsync($"{_config.ResultsDirectory}/{fName}.json", JsonConvert.SerializeObject(_formState.AnnotationResults));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +277,13 @@ public partial class MainWindow
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var labelNames = new DirectoryInfo(_config.LabelsDirectory).GetFiles()
|
var labelNames = new DirectoryInfo(_config.LabelsDirectory).GetFiles()
|
||||||
.Select(x => x.Name[..^11])
|
.Select(x =>
|
||||||
|
{
|
||||||
|
var name = Path.GetFileNameWithoutExtension(x.Name);
|
||||||
|
return name.Length > 8
|
||||||
|
? name[..^7]
|
||||||
|
: name;
|
||||||
|
})
|
||||||
.GroupBy(x => x)
|
.GroupBy(x => x)
|
||||||
.Select(gr => gr.Key)
|
.Select(gr => gr.Key)
|
||||||
.ToDictionary(x => x);
|
.ToDictionary(x => x);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
"VideosDirectory": "E:\\Azaion3\\Videos",
|
"VideosDirectory": "E:\\Azaion3\\Videos",
|
||||||
"LabelsDirectory": "E:\\labels",
|
"LabelsDirectory": "E:\\labels",
|
||||||
"ImagesDirectory": "E:\\images",
|
"ImagesDirectory": "E:\\images",
|
||||||
|
"ThumbnailsDirectory": "E:\\thumbnails",
|
||||||
"ResultsDirectory": "E:\\results",
|
"ResultsDirectory": "E:\\results",
|
||||||
"UnknownImages": "E:\\unknown",
|
"UnknownImages": "E:\\unknown",
|
||||||
"ThumbnailsDirectory": "E:\\thumbnails",
|
|
||||||
"AnnotationClasses": [
|
"AnnotationClasses": [
|
||||||
{ "Id": 0, "Name": "Броньована техніка", "Color": "#40FF0000" },
|
{ "Id": 0, "Name": "Броньована техніка", "Color": "#40FF0000" },
|
||||||
{ "Id": 1, "Name": "Вантажівка", "Color": "#4000FF00" },
|
{ "Id": 1, "Name": "Вантажівка", "Color": "#4000FF00" },
|
||||||
|
|||||||
Reference in New Issue
Block a user