mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 09:56:31 +00:00
sort thumbnails by date in DatasetExplorer
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
|
public class LabelInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("c")] public List<int> Classes { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonProperty("d")] public DateTime ImageDateTime { get; set; }
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ public class ThumbnailDto : INotifyPropertyChanged
|
|||||||
public string ThumbnailPath { get; set; }
|
public string ThumbnailPath { get; set; }
|
||||||
public string ImagePath { get; set; }
|
public string ImagePath { get; set; }
|
||||||
public string LabelPath { get; set; }
|
public string LabelPath { get; set; }
|
||||||
|
public DateTime ImageDate { get; set; }
|
||||||
|
|
||||||
private BitmapImage? _image;
|
private BitmapImage? _image;
|
||||||
public BitmapImage? Image
|
public BitmapImage? Image
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Background="Black">
|
Background="Black">
|
||||||
<TabItem Header="Анотації">
|
<TabItem Name="AnnotationsTab" Header="Анотації">
|
||||||
<vwp:GridView
|
<vwp:GridView
|
||||||
Name="ThumbnailsView"
|
Name="ThumbnailsView"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
@@ -70,13 +70,15 @@
|
|||||||
>
|
>
|
||||||
</vwp:GridView>
|
</vwp:GridView>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Редактор">
|
<TabItem Name="EditorTab"
|
||||||
|
Header="Редактор"
|
||||||
|
Visibility="Collapsed">
|
||||||
<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="Розподіл класів">
|
<TabItem Name="ClassDistributionTab" Header="Розподіл класів">
|
||||||
<ScottPlot:WpfPlot x:Name="ClassDistribution" />
|
<ScottPlot:WpfPlot x:Name="ClassDistribution" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ using System.Windows.Media;
|
|||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using ScottPlot;
|
using ScottPlot;
|
||||||
using Color = ScottPlot.Color;
|
using Color = ScottPlot.Color;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
using Orientation = ScottPlot.Orientation;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
@@ -22,10 +22,9 @@ public partial class DatasetExplorer
|
|||||||
private ObservableCollection<AnnotationClass> AllAnnotationClasses { get; set; } = new();
|
private ObservableCollection<AnnotationClass> AllAnnotationClasses { get; set; } = new();
|
||||||
|
|
||||||
private int _tempSelectedClassIdx = 0;
|
private int _tempSelectedClassIdx = 0;
|
||||||
private readonly string _thumbnailsCacheFile;
|
|
||||||
private readonly IConfigRepository _configRepository;
|
private readonly IConfigRepository _configRepository;
|
||||||
private readonly FormState _formState;
|
private readonly FormState _formState;
|
||||||
private static Dictionary<string, List<int>> LabelsCache { get; set; } = new();
|
private readonly IGalleryManager _galleryManager;
|
||||||
|
|
||||||
public bool ThumbnailLoading { get; set; }
|
public bool ThumbnailLoading { get; set; }
|
||||||
|
|
||||||
@@ -42,12 +41,7 @@ public partial class DatasetExplorer
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configRepository = configRepository;
|
_configRepository = configRepository;
|
||||||
_formState = formState;
|
_formState = formState;
|
||||||
_thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.ThumbnailsCacheFile);
|
_galleryManager = galleryManager;
|
||||||
if (File.Exists(_thumbnailsCacheFile))
|
|
||||||
{
|
|
||||||
var cache = JsonConvert.DeserializeObject<Dictionary<string, List<int>>>(File.ReadAllText(_thumbnailsCacheFile));
|
|
||||||
LabelsCache = cache ?? new Dictionary<string, List<int>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Loaded += async (_, _) =>
|
Loaded += async (_, _) =>
|
||||||
@@ -126,22 +120,6 @@ public partial class DatasetExplorer
|
|||||||
|
|
||||||
Activated += (_, _) => { _formState.ActiveWindow = WindowsEnum.DatasetExplorer; };
|
Activated += (_, _) => { _formState.ActiveWindow = WindowsEnum.DatasetExplorer; };
|
||||||
|
|
||||||
Switcher.SelectionChanged += (sender, args) =>
|
|
||||||
{
|
|
||||||
switch (Switcher.SelectedIndex)
|
|
||||||
{
|
|
||||||
case 0: //ListView
|
|
||||||
LvClasses.ItemsSource = AllAnnotationClasses;
|
|
||||||
LvClasses.SelectedIndex = _tempSelectedClassIdx;
|
|
||||||
ExplorerEditor.Background = null;
|
|
||||||
break;
|
|
||||||
case 1: //Editor
|
|
||||||
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
|
||||||
LvClasses.ItemsSource = _config.AnnotationClasses;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentThumbnail!.ImagePath);
|
ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentThumbnail!.ImagePath);
|
||||||
galleryManager.ThumbnailsUpdate += thumbnailsPercentage =>
|
galleryManager.ThumbnailsUpdate += thumbnailsPercentage =>
|
||||||
{
|
{
|
||||||
@@ -152,7 +130,8 @@ public partial class DatasetExplorer
|
|||||||
|
|
||||||
private void LoadClassDistribution()
|
private void LoadClassDistribution()
|
||||||
{
|
{
|
||||||
var data = LabelsCache.SelectMany(x => x.Value)
|
var data = _galleryManager.LabelsCache
|
||||||
|
.SelectMany(x => x.Value.Classes)
|
||||||
.GroupBy(x => x)
|
.GroupBy(x => x)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
@@ -163,7 +142,9 @@ public partial class DatasetExplorer
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var foregroundColor = Color.FromColor(System.Drawing.Color.Black);
|
||||||
var plot = ClassDistribution.Plot;
|
var plot = ClassDistribution.Plot;
|
||||||
|
|
||||||
plot.Add.Bars(data.Select(x => new Bar
|
plot.Add.Bars(data.Select(x => new Bar
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
Orientation = Orientation.Horizontal,
|
||||||
@@ -171,17 +152,21 @@ public partial class DatasetExplorer
|
|||||||
Label = x.ClassCount > 200 ? x.ClassCount.ToString() : "",
|
Label = x.ClassCount > 200 ? x.ClassCount.ToString() : "",
|
||||||
FillColor = new Color(x.Color.R, x.Color.G, x.Color.B, x.Color.A),
|
FillColor = new Color(x.Color.R, x.Color.G, x.Color.B, x.Color.A),
|
||||||
Value = x.ClassCount,
|
Value = x.ClassCount,
|
||||||
CenterLabel = true
|
CenterLabel = true,
|
||||||
|
LabelOffset = 10
|
||||||
}));
|
}));
|
||||||
|
|
||||||
foreach (var x in data)
|
foreach (var x in data)
|
||||||
{
|
{
|
||||||
var label = plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1);
|
var label = plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1);
|
||||||
label.LabelFontSize = 16;
|
label.LabelFontColor = foregroundColor;
|
||||||
|
label.LabelFontSize = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
plot.Axes.AutoScale();
|
plot.Axes.AutoScale();
|
||||||
//plot.Axes.SetLimits(-200, data.Max(x => x.ClassCount + 3000), -2 * data.Count + 5, 5);
|
plot.HideAxesAndGrid();
|
||||||
ClassDistribution.Background = new SolidColorBrush(System.Windows.Media.Colors.Black);
|
plot.FigureBackground.Color = new("#888888");
|
||||||
|
|
||||||
ClassDistribution.Refresh();
|
ClassDistribution.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,9 +185,7 @@ public partial class DatasetExplorer
|
|||||||
{
|
{
|
||||||
ImageSource = await dto.ImagePath.OpenImage()
|
ImageSource = await dto.ImagePath.OpenImage()
|
||||||
};
|
};
|
||||||
|
SwitchTab(toEditor: true);
|
||||||
Switcher.SelectedIndex = 1;
|
|
||||||
LvClasses.SelectedIndex = 1;
|
|
||||||
|
|
||||||
var time = _formState.GetTime(dto.ImagePath);
|
var time = _formState.GetTime(dto.ImagePath);
|
||||||
ExplorerEditor.RemoveAllAnns();
|
ExplorerEditor.RemoveAllAnns();
|
||||||
@@ -227,6 +210,28 @@ public partial class DatasetExplorer
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SwitchTab(bool toEditor)
|
||||||
|
{
|
||||||
|
if (toEditor)
|
||||||
|
{
|
||||||
|
AnnotationsTab.Visibility = Visibility.Collapsed;
|
||||||
|
EditorTab.Visibility = Visibility.Visible;
|
||||||
|
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
||||||
|
LvClasses.ItemsSource = _config.AnnotationClasses;
|
||||||
|
|
||||||
|
Switcher.SelectedIndex = 1;
|
||||||
|
LvClasses.SelectedIndex = Math.Max(0, _tempSelectedClassIdx - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnnotationsTab.Visibility = Visibility.Visible;
|
||||||
|
EditorTab.Visibility = Visibility.Collapsed;
|
||||||
|
LvClasses.ItemsSource = AllAnnotationClasses;
|
||||||
|
LvClasses.SelectedIndex = _tempSelectedClassIdx;
|
||||||
|
Switcher.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveUserSettings()
|
private async Task SaveUserSettings()
|
||||||
{
|
{
|
||||||
_config.DatasetExplorerConfig = this.GetConfig();
|
_config.DatasetExplorerConfig = this.GetConfig();
|
||||||
@@ -264,81 +269,78 @@ public partial class DatasetExplorer
|
|||||||
if (!Directory.Exists(_config.ThumbnailsDirectory))
|
if (!Directory.Exists(_config.ThumbnailsDirectory))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ThumbnailsDtos.Clear();
|
|
||||||
var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
|
var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
|
||||||
|
var thumbnailDtos = new List<ThumbnailDto>();
|
||||||
var thumbNum = 0;
|
for (int i = 0; i < thumbnails.Length; i++)
|
||||||
foreach (var thumbnail in thumbnails)
|
|
||||||
{
|
{
|
||||||
await AddThumbnail(thumbnail);
|
var thumbnailDto = GetThumbnail(thumbnails[i]);
|
||||||
|
if (thumbnailDto != null)
|
||||||
|
thumbnailDtos.Add(thumbnailDto);
|
||||||
|
|
||||||
if (thumbNum % 1000 == 0)
|
if (i % 1000 == 0)
|
||||||
{
|
LoadingAnnsBar.Value = i * 100.0 / thumbnails.Length;
|
||||||
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
|
||||||
LoadingAnnsBar.Value = thumbNum * 100.0 / thumbnails.Length;
|
|
||||||
}
|
|
||||||
thumbNum++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThumbnailsDtos.Clear();
|
||||||
|
foreach (var th in thumbnailDtos.OrderByDescending(x => x.ImageDate))
|
||||||
|
ThumbnailsDtos.Add(th);
|
||||||
|
|
||||||
LoadingAnnsCaption.Visibility = Visibility.Collapsed;
|
LoadingAnnsCaption.Visibility = Visibility.Collapsed;
|
||||||
LoadingAnnsBar.Visibility = Visibility.Collapsed;
|
LoadingAnnsBar.Visibility = Visibility.Collapsed;
|
||||||
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddThumbnail(string thumbnail, CancellationToken cancellationToken = default)
|
private ThumbnailDto? GetThumbnail(string thumbnail)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
|
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
|
||||||
var imageName = Path.Combine(_config.ImagesDirectory, name);
|
var imagePath = Path.Combine(_config.ImagesDirectory, name);
|
||||||
foreach (var f in _config.ImageFormats)
|
foreach (var f in _config.ImageFormats)
|
||||||
{
|
{
|
||||||
var curName = $"{imageName}.{f}";
|
var curName = $"{imagePath}.{f}";
|
||||||
if (File.Exists(curName))
|
if (File.Exists(curName))
|
||||||
{
|
{
|
||||||
imageName = curName;
|
imagePath = curName;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
|
var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
|
||||||
|
|
||||||
if (!LabelsCache.TryGetValue(name, out var classes))
|
if (!_galleryManager.LabelsCache.TryGetValue(Path.GetFileName(imagePath), out var info))
|
||||||
{
|
{
|
||||||
if (!File.Exists(labelPath))
|
if (File.Exists(labelPath))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var imageExists = File.Exists(imagePath);
|
||||||
|
if (!imageExists)
|
||||||
{
|
{
|
||||||
var imageExists = File.Exists(imageName);
|
File.Delete(thumbnail);
|
||||||
if (!imageExists)
|
_logger.LogError($"No label {labelPath} found ! Image {imagePath} not found, thumbnail {thumbnail} was removed");
|
||||||
{
|
|
||||||
_logger.LogError($"No label {labelPath} found ! Image {imageName} not found, removing thumbnail {thumbnail}");
|
|
||||||
File.Delete(thumbnail);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogError($"No label {labelPath} found! But Image {imageName} exists! Image moved to {_config.UnknownImages} directory!");
|
|
||||||
File.Move(imageName, Path.Combine(_config.UnknownImages, imageName));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var labels = await YoloLabel.ReadFromFile(labelPath, cancellationToken);
|
|
||||||
classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
|
||||||
LabelsCache.Add(name, classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (classes.Contains(ExplorerEditor.CurrentAnnClass.Id) || ExplorerEditor.CurrentAnnClass.Id == -1)
|
|
||||||
{
|
|
||||||
ThumbnailsDtos.Add(new ThumbnailDto
|
|
||||||
{
|
{
|
||||||
ThumbnailPath = thumbnail,
|
File.Move(imagePath, Path.Combine(_config.UnknownImages, imagePath));
|
||||||
ImagePath = imageName,
|
_logger.LogError($"No label {labelPath} found! But Image {imagePath} exists! Image moved to {_config.UnknownImages} directory!");
|
||||||
LabelPath = labelPath
|
}
|
||||||
});
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!info.Classes.Contains(ExplorerEditor.CurrentAnnClass.Id) && ExplorerEditor.CurrentAnnClass.Id != -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new ThumbnailDto
|
||||||
|
{
|
||||||
|
ThumbnailPath = thumbnail,
|
||||||
|
ImagePath = imagePath,
|
||||||
|
LabelPath = labelPath,
|
||||||
|
ImageDate = info.ImageDateTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, e.Message);
|
_logger.LogError(e, e.Message);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +52,9 @@ public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer,
|
|||||||
|
|
||||||
await YoloLabel.WriteToFile(currentAnns, Path.Combine(config.LabelsDirectory, datasetExplorer.CurrentThumbnail!.LabelPath));
|
await YoloLabel.WriteToFile(currentAnns, Path.Combine(config.LabelsDirectory, datasetExplorer.CurrentThumbnail!.LabelPath));
|
||||||
await galleryManager.CreateThumbnail(datasetExplorer.CurrentThumbnail.ImagePath);
|
await galleryManager.CreateThumbnail(datasetExplorer.CurrentThumbnail.ImagePath);
|
||||||
|
await galleryManager.SaveLabelsCache();
|
||||||
datasetExplorer.CurrentThumbnail.UpdateImage();
|
datasetExplorer.CurrentThumbnail.UpdateImage();
|
||||||
datasetExplorer.Switcher.SelectedIndex = 0;
|
datasetExplorer.SwitchTab(toEditor: false);
|
||||||
break;
|
break;
|
||||||
case PlaybackControlEnum.RemoveSelectedAnns:
|
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||||
datasetExplorer.ExplorerEditor.RemoveSelectedAnns();
|
datasetExplorer.ExplorerEditor.RemoveSelectedAnns();
|
||||||
@@ -62,7 +63,7 @@ public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer,
|
|||||||
datasetExplorer.ExplorerEditor.RemoveAllAnns();
|
datasetExplorer.ExplorerEditor.RemoveAllAnns();
|
||||||
break;
|
break;
|
||||||
case PlaybackControlEnum.Close:
|
case PlaybackControlEnum.Close:
|
||||||
datasetExplorer.Switcher.SelectedIndex = 0;
|
datasetExplorer.SwitchTab(toEditor: false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.Extensions;
|
||||||
|
|
||||||
|
public class DenseDateTimeConverter : IsoDateTimeConverter
|
||||||
|
{
|
||||||
|
public DenseDateTimeConverter()
|
||||||
|
{
|
||||||
|
DateTimeFormat = "yy-MM-dd HH:mm:ss";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Drawing;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
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 Azaion.Annotator.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Color = System.Drawing.Color;
|
using Color = System.Drawing.Color;
|
||||||
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
|
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
@@ -13,15 +15,21 @@ namespace Azaion.Annotator;
|
|||||||
|
|
||||||
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
|
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
|
||||||
|
|
||||||
public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGalleryManager
|
public class GalleryManager : IGalleryManager
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<GalleryManager> _logger;
|
||||||
|
|
||||||
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||||
|
private readonly string _thumbnailsCacheFile;
|
||||||
|
|
||||||
private readonly SemaphoreSlim _updateLock = new(1);
|
private readonly SemaphoreSlim _updateLock = new(1);
|
||||||
|
|
||||||
public double ThumbnailsPercentage { get; set; }
|
public double ThumbnailsPercentage { get; set; }
|
||||||
|
public ConcurrentDictionary<string, LabelInfo> LabelsCache { get; set; } = new();
|
||||||
|
|
||||||
private DirectoryInfo? _thumbnailsDirectory;
|
private DirectoryInfo? _thumbnailsDirectory;
|
||||||
|
private readonly Config _config;
|
||||||
|
|
||||||
private DirectoryInfo ThumbnailsDirectory
|
private DirectoryInfo ThumbnailsDirectory
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -29,17 +37,24 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
if (_thumbnailsDirectory != null)
|
if (_thumbnailsDirectory != null)
|
||||||
return _thumbnailsDirectory;
|
return _thumbnailsDirectory;
|
||||||
|
|
||||||
var dir = new DirectoryInfo(config.ThumbnailsDirectory);
|
var dir = new DirectoryInfo(_config.ThumbnailsDirectory);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
Directory.CreateDirectory(config.ThumbnailsDirectory);
|
Directory.CreateDirectory(_config.ThumbnailsDirectory);
|
||||||
_thumbnailsDirectory = new DirectoryInfo(config.ThumbnailsDirectory);
|
_thumbnailsDirectory = new DirectoryInfo(_config.ThumbnailsDirectory);
|
||||||
return _thumbnailsDirectory;
|
return _thumbnailsDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GalleryManager(Config config, ILogger<GalleryManager> logger)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_logger = logger;
|
||||||
|
_thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.ThumbnailsCacheFile);
|
||||||
|
}
|
||||||
|
|
||||||
public void ClearThumbnails()
|
public void ClearThumbnails()
|
||||||
{
|
{
|
||||||
foreach(var file in new DirectoryInfo(config.ThumbnailsDirectory).GetFiles())
|
foreach(var file in new DirectoryInfo(_config.ThumbnailsDirectory).GetFiles())
|
||||||
file.Delete();
|
file.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +71,16 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
.Select(gr => gr.Key)
|
.Select(gr => gr.Key)
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
|
||||||
var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
|
if (File.Exists(_thumbnailsCacheFile))
|
||||||
|
{
|
||||||
|
var cache = JsonConvert.DeserializeObject<ConcurrentDictionary<string, LabelInfo>>(
|
||||||
|
await File.ReadAllTextAsync(_thumbnailsCacheFile), new DenseDateTimeConverter());
|
||||||
|
LabelsCache = cache ?? new ConcurrentDictionary<string, LabelInfo>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LabelsCache = new ConcurrentDictionary<string, LabelInfo>();
|
||||||
|
|
||||||
|
var files = new DirectoryInfo(_config.ImagesDirectory).GetFiles();
|
||||||
var imagesCount = files.Length;
|
var imagesCount = files.Length;
|
||||||
|
|
||||||
await ParallelExt.ForEachAsync(files, async (file, cancellationToken) =>
|
await ParallelExt.ForEachAsync(files, async (file, cancellationToken) =>
|
||||||
@@ -70,7 +94,7 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
|
_logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
|
||||||
}
|
}
|
||||||
}, new ParallelOptions
|
}, new ParallelOptions
|
||||||
{
|
{
|
||||||
@@ -87,29 +111,26 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
await SaveLabelsCache();
|
||||||
_updateLock.Release();
|
_updateLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default)
|
public async Task SaveLabelsCache()
|
||||||
{
|
{
|
||||||
var bitmap = await GenerateThumbnail(imgPath);
|
var labelsCacheStr = JsonConvert.SerializeObject(LabelsCache, new DenseDateTimeConverter());
|
||||||
if (bitmap != null)
|
await File.WriteAllTextAsync(_thumbnailsCacheFile, labelsCacheStr);
|
||||||
{
|
|
||||||
var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Config.ThumbnailPrefix}.jpg");
|
|
||||||
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Bitmap?> GenerateThumbnail(string imgPath)
|
public async Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var width = (int)config.ThumbnailConfig.Size.Width;
|
var width = (int)_config.ThumbnailConfig.Size.Width;
|
||||||
var height = (int)config.ThumbnailConfig.Size.Height;
|
var height = (int)_config.ThumbnailConfig.Size.Height;
|
||||||
|
|
||||||
var imgName = Path.GetFileName(imgPath);
|
var imgName = Path.GetFileName(imgPath);
|
||||||
var labelName = Path.Combine(config.LabelsDirectory, $"{Path.GetFileNameWithoutExtension(imgPath)}.txt");
|
var labelName = Path.Combine(_config.LabelsDirectory, $"{Path.GetFileNameWithoutExtension(imgPath)}.txt");
|
||||||
|
|
||||||
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(imgPath)));
|
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(imgPath, cancellationToken)));
|
||||||
|
|
||||||
var bitmap = new Bitmap(width, height);
|
var bitmap = new Bitmap(width, height);
|
||||||
|
|
||||||
@@ -121,16 +142,23 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
var size = new Size(originalImage.Width, originalImage.Height);
|
var size = new Size(originalImage.Width, originalImage.Height);
|
||||||
if (!File.Exists(labelName))
|
if (!File.Exists(labelName))
|
||||||
{
|
{
|
||||||
File.Move(imgPath, Path.Combine(config.UnknownImages, imgName));
|
File.Move(imgPath, Path.Combine(_config.UnknownImages, imgName));
|
||||||
logger.LogInformation($"No labels found for image {imgName}! Moved image to the {config.UnknownImages} folder.");
|
_logger.LogInformation($"No labels found for image {imgName}! Moved image to the {_config.UnknownImages} folder.");
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
var labels = (await YoloLabel.ReadFromFile(labelName))
|
var labels = (await YoloLabel.ReadFromFile(labelName))
|
||||||
.Select(x => new CanvasLabel(x, size, size))
|
.Select(x => new CanvasLabel(x, size, size))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var thumbWhRatio = width / (float)height;
|
var thumbWhRatio = width / (float)height;
|
||||||
var border = config.ThumbnailConfig.Border;
|
var border = _config.ThumbnailConfig.Border;
|
||||||
|
|
||||||
|
var classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
||||||
|
LabelsCache.TryAdd(imgName, new LabelInfo
|
||||||
|
{
|
||||||
|
Classes = classes,
|
||||||
|
ImageDateTime = File.GetCreationTimeUtc(imgPath)
|
||||||
|
});
|
||||||
|
|
||||||
var frameX = 0.0;
|
var frameX = 0.0;
|
||||||
var frameY = 0.0;
|
var frameY = 0.0;
|
||||||
@@ -169,13 +197,20 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
|
|
||||||
foreach (var label in labels)
|
foreach (var label in labels)
|
||||||
{
|
{
|
||||||
var color = config.AnnotationClassesDict[label.ClassNumber].Color;
|
var color = _config.AnnotationClassesDict[label.ClassNumber].Color;
|
||||||
var brush = new SolidBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
|
var brush = new SolidBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
|
||||||
|
|
||||||
var rectangle = new RectangleF((float)((label.X - frameX) / scale), (float)((label.Y - frameY) / scale), (float)(label.Width / scale), (float)(label.Height / scale));
|
var rectangle = new RectangleF((float)((label.X - frameX) / scale), (float)((label.Y - frameY) / scale), (float)(label.Width / scale), (float)(label.Height / scale));
|
||||||
g.FillRectangle(brush, rectangle);
|
g.FillRectangle(brush, rectangle);
|
||||||
}
|
}
|
||||||
return bitmap;
|
|
||||||
|
|
||||||
|
|
||||||
|
if (bitmap != null)
|
||||||
|
{
|
||||||
|
var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Config.ThumbnailPrefix}.jpg");
|
||||||
|
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +218,8 @@ public interface IGalleryManager
|
|||||||
{
|
{
|
||||||
event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||||
double ThumbnailsPercentage { get; set; }
|
double ThumbnailsPercentage { get; set; }
|
||||||
|
Task SaveLabelsCache();
|
||||||
|
ConcurrentDictionary<string, LabelInfo> LabelsCache { get; set; }
|
||||||
Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default);
|
Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default);
|
||||||
Task RefreshThumbnails();
|
Task RefreshThumbnails();
|
||||||
void ClearThumbnails();
|
void ClearThumbnails();
|
||||||
|
|||||||
@@ -266,7 +266,16 @@ public partial class MainWindow
|
|||||||
if (existingResult != null)
|
if (existingResult != null)
|
||||||
_formState.AnnotationResults.Remove(existingResult);
|
_formState.AnnotationResults.Remove(existingResult);
|
||||||
|
|
||||||
_formState.AnnotationResults.Add(new AnnotationResult(timeValue, fName, annotations, _config));
|
var dict = _formState.AnnotationResults
|
||||||
|
.Select((x,i) => new { x.Time, Index = i })
|
||||||
|
.ToDictionary(x => x.Time, x => x.Index);
|
||||||
|
|
||||||
|
var index = dict.Where(x => x.Key < timeValue)
|
||||||
|
.OrderBy(x => x.Key - timeValue)
|
||||||
|
.Select(x => x.Value + 1)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
_formState.AnnotationResults.Insert(index, 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user