Files
annotations/Azaion.Annotator/GalleryManager.cs
T
Alex Bezdieniezhnykh 22d4493d86 make thumbnail generating multithread
fix the bug with open video
add class distribution chart
2024-09-25 21:46:07 +03:00

189 lines
7.0 KiB
C#

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using Azaion.Annotator.DTO;
using Azaion.Annotator.Extensions;
using Microsoft.Extensions.Logging;
using Color = System.Drawing.Color;
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
using Size = System.Windows.Size;
namespace Azaion.Annotator;
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGalleryManager
{
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
private readonly SemaphoreSlim _updateLock = new(1);
public double ThumbnailsPercentage { get; set; }
private DirectoryInfo? _thumbnailsDirectory;
private DirectoryInfo ThumbnailsDirectory
{
get
{
if (_thumbnailsDirectory != null)
return _thumbnailsDirectory;
var dir = new DirectoryInfo(config.ThumbnailsDirectory);
if (!dir.Exists)
Directory.CreateDirectory(config.ThumbnailsDirectory);
_thumbnailsDirectory = new DirectoryInfo(config.ThumbnailsDirectory);
return _thumbnailsDirectory;
}
}
public void ClearThumbnails()
{
foreach(var file in new DirectoryInfo(config.ThumbnailsDirectory).GetFiles())
file.Delete();
}
public async Task RefreshThumbnails()
{
await _updateLock.WaitAsync();
try
{
var prefixLen = Config.ThumbnailPrefix.Length;
var thumbnails = ThumbnailsDirectory.GetFiles()
.Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen])
.GroupBy(x => x)
.Select(gr => gr.Key)
.ToHashSet();
var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
var imagesCount = files.Length;
await ParallelExt.ForEachAsync(files, async (file, cancellationToken) =>
{
var imgName = Path.GetFileNameWithoutExtension(file.Name);
if (thumbnails.Contains(imgName))
return;
try
{
await CreateThumbnail(file.FullName, cancellationToken);
}
catch (Exception e)
{
logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
}
}, new ParallelOptions
{
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
{
_updateLock.Release();
}
}
public async Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default)
{
var bitmap = await GenerateThumbnail(imgPath);
if (bitmap != null)
{
var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Config.ThumbnailPrefix}.jpg");
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
}
}
private async Task<Bitmap?> GenerateThumbnail(string imgPath)
{
var width = (int)config.ThumbnailConfig.Size.Width;
var height = (int)config.ThumbnailConfig.Size.Height;
var imgName = Path.GetFileName(imgPath);
var labelName = Path.Combine(config.LabelsDirectory, $"{Path.GetFileNameWithoutExtension(imgPath)}.txt");
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(imgPath)));
var bitmap = new Bitmap(width, height);
using var g = Graphics.FromImage(bitmap);
g.CompositingQuality = CompositingQuality.HighSpeed;
g.SmoothingMode = SmoothingMode.HighSpeed;
g.InterpolationMode = InterpolationMode.Default;
var size = new Size(originalImage.Width, originalImage.Height);
if (!File.Exists(labelName))
{
File.Move(imgPath, Path.Combine(config.UnknownImages, imgName));
logger.LogInformation($"No labels found for image {imgName}! Moved image to the {config.UnknownImages} folder.");
return null;
}
var labels = (await YoloLabel.ReadFromFile(labelName))
.Select(x => new CanvasLabel(x, size, size))
.ToList();
var thumbWhRatio = width / (float)height;
var border = config.ThumbnailConfig.Border;
var frameX = 0.0;
var frameY = 0.0;
var frameHeight = size.Height;
var frameWidth = size.Width;
if (labels.Any())
{
var labelsMinX = labels.Min(x => x.X);
var labelsMaxX = labels.Max(x => x.X + x.Width);
var labelsMinY = labels.Min(x => x.Y);
var labelsMaxY = labels.Max(x => x.Y + x.Height);
var labelsHeight = labelsMaxY - labelsMinY + 2 * border;
var labelsWidth = labelsMaxX - labelsMinX + 2 * border;
if (labelsWidth / labelsHeight > thumbWhRatio)
{
frameWidth = labelsWidth;
frameHeight = Math.Min(labelsWidth / thumbWhRatio, size.Height);
frameX = Math.Max(0, labelsMinX - border);
frameY = Math.Max(0, 0.5 * (labelsMinY + labelsMaxY - frameHeight) - border);
}
else
{
frameHeight = labelsHeight;
frameWidth = Math.Min(labelsHeight * thumbWhRatio, size.Width);
frameY = Math.Max(0, labelsMinY - border);
frameX = Math.Max(0, 0.5 * (labelsMinX + labelsMaxX - frameWidth) - border);
}
}
var scale = frameHeight / height;
g.DrawImage(originalImage, new Rectangle(0, 0, width, height), new RectangleF((float)frameX, (float)frameY, (float)frameWidth, (float)frameHeight), GraphicsUnit.Pixel);
foreach (var label in labels)
{
var color = config.AnnotationClassesDict[label.ClassNumber].Color;
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));
g.FillRectangle(brush, rectangle);
}
return bitmap;
}
}
public interface IGalleryManager
{
event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
double ThumbnailsPercentage { get; set; }
Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default);
Task RefreshThumbnails();
void ClearThumbnails();
}