sort thumbnails by date in DatasetExplorer

This commit is contained in:
Alex Bezdieniezhnykh
2024-09-29 16:24:31 +03:00
parent 22d4493d86
commit d2186eb326
8 changed files with 182 additions and 108 deletions
+10
View File
@@ -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; }
}
+1
View File
@@ -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
+5 -3
View File
@@ -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>
+78 -76
View File
@@ -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";
}
}
+63 -26
View File
@@ -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();
+10 -1
View File
@@ -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));
} }