mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 11:06:30 +00:00
add manual Tile Processor
zoom on video on pause (temp image)
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Azaion.Annotator.Controls;
|
||||
using Azaion.Annotator.DTO;
|
||||
using Azaion.Common;
|
||||
@@ -47,7 +48,8 @@ public class AnnotatorEventHandler(
|
||||
private const int STEP = 20;
|
||||
private const int LARGE_STEP = 5000;
|
||||
private const int RESULT_WIDTH = 1280;
|
||||
|
||||
private readonly string tempImgPath = Path.Combine(dirConfig.Value.ImagesDirectory, "___temp___.jpg");
|
||||
|
||||
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
|
||||
{
|
||||
{ Key.Space, PlaybackControlEnum.Pause },
|
||||
@@ -139,12 +141,21 @@ public class AnnotatorEventHandler(
|
||||
await Play(cancellationToken);
|
||||
break;
|
||||
case PlaybackControlEnum.Pause:
|
||||
mediaPlayer.Pause();
|
||||
|
||||
if (formState.BackgroundTime.HasValue)
|
||||
if (mediaPlayer.IsPlaying)
|
||||
{
|
||||
mainWindow.Editor.SetBackground(null);
|
||||
formState.BackgroundTime = null;
|
||||
mediaPlayer.Pause();
|
||||
mediaPlayer.TakeSnapshot(0, tempImgPath, 0, 0);
|
||||
mainWindow.Editor.SetBackground(await tempImgPath.OpenImage());
|
||||
formState.BackgroundTime = TimeSpan.FromMilliseconds(mediaPlayer.Time);
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaPlayer.Play();
|
||||
if (formState.BackgroundTime.HasValue)
|
||||
{
|
||||
mainWindow.Editor.SetBackground(null);
|
||||
formState.BackgroundTime = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PlaybackControlEnum.Stop:
|
||||
@@ -159,7 +170,7 @@ public class AnnotatorEventHandler(
|
||||
mainWindow.SeekTo(mediaPlayer.Time + step);
|
||||
break;
|
||||
case PlaybackControlEnum.SaveAnnotations:
|
||||
await SaveAnnotations(cancellationToken);
|
||||
await SaveAnnotation(cancellationToken);
|
||||
break;
|
||||
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||
|
||||
@@ -226,63 +237,125 @@ public class AnnotatorEventHandler(
|
||||
if (mainWindow.LvFiles.SelectedItem == null)
|
||||
return;
|
||||
var mediaInfo = (MediaFileInfo)mainWindow.LvFiles.SelectedItem;
|
||||
mainWindow.Editor.SetBackground(null);
|
||||
|
||||
|
||||
formState.CurrentMedia = mediaInfo;
|
||||
mainWindow.Title = $"Azaion Annotator - {mediaInfo.Name}";
|
||||
|
||||
|
||||
//need to wait a bit for correct VLC playback event handling
|
||||
await Task.Delay(100, ct);
|
||||
mediaPlayer.Stop();
|
||||
mediaPlayer.Play(new Media(libVLC, mediaInfo.Path));
|
||||
mainWindow.Title = $"{mainWindow.MainTitle} - {mediaInfo.Name}";
|
||||
|
||||
if (mediaInfo.MediaType == MediaTypes.Video)
|
||||
{
|
||||
mainWindow.Editor.SetBackground(null);
|
||||
//need to wait a bit for correct VLC playback event handling
|
||||
await Task.Delay(100, ct);
|
||||
mediaPlayer.Stop();
|
||||
mediaPlayer.Play(new Media(libVLC, mediaInfo.Path));
|
||||
}
|
||||
else
|
||||
{
|
||||
formState.BackgroundTime = TimeSpan.Zero;
|
||||
var image = await mediaInfo.Path.OpenImage();
|
||||
formState.CurrentMediaSize = new Size(image.PixelWidth, image.PixelHeight);
|
||||
mainWindow.Editor.SetBackground(image);
|
||||
mediaPlayer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
//SAVE: MANUAL
|
||||
private async Task SaveAnnotations(CancellationToken cancellationToken = default)
|
||||
private async Task SaveAnnotation(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (formState.CurrentMedia == null)
|
||||
return;
|
||||
|
||||
var time = formState.BackgroundTime ?? TimeSpan.FromMilliseconds(mediaPlayer.Time);
|
||||
var originalMediaName = formState.VideoName;
|
||||
var fName = originalMediaName.ToTimeName(time);
|
||||
|
||||
var currentDetections = mainWindow.Editor.CurrentDetections
|
||||
.Select(x => new Detection(fName, x.GetLabel(mainWindow.Editor.RenderSize, formState.BackgroundTime.HasValue ? mainWindow.Editor.RenderSize : formState.CurrentVideoSize)))
|
||||
.ToList();
|
||||
|
||||
formState.CurrentMedia.HasAnnotations = currentDetections.Count != 0;
|
||||
mainWindow.LvFiles.Items.Refresh();
|
||||
mainWindow.Editor.RemoveAllAnns();
|
||||
|
||||
var timeName = formState.MediaName.ToTimeName(time);
|
||||
var isVideo = formState.CurrentMedia.MediaType == MediaTypes.Video;
|
||||
var imgPath = Path.Combine(dirConfig.Value.ImagesDirectory, $"{fName}{Constants.JPG_EXT}");
|
||||
var imgPath = Path.Combine(dirConfig.Value.ImagesDirectory, $"{timeName}{Constants.JPG_EXT}");
|
||||
|
||||
if (formState.BackgroundTime.HasValue)
|
||||
formState.CurrentMedia.HasAnnotations = mainWindow.Editor.CurrentDetections.Count != 0;
|
||||
var annotations = await SaveAnnotationInner(imgPath, cancellationToken);
|
||||
if (isVideo)
|
||||
{
|
||||
//no need to save image, it's already there, just remove background
|
||||
mainWindow.Editor.SetBackground(null);
|
||||
formState.BackgroundTime = null;
|
||||
|
||||
//next item
|
||||
var annGrid = mainWindow.DgAnnotations;
|
||||
annGrid.SelectedIndex = Math.Min(annGrid.Items.Count, annGrid.SelectedIndex + 1);
|
||||
mainWindow.OpenAnnotationResult((AnnotationResult)annGrid.SelectedItem);
|
||||
foreach (var annotation in annotations)
|
||||
mainWindow.AddAnnotation(annotation);
|
||||
mediaPlayer.Play();
|
||||
|
||||
// next item. Probably not needed
|
||||
// var annGrid = mainWindow.DgAnnotations;
|
||||
// annGrid.SelectedIndex = Math.Min(annGrid.Items.Count, annGrid.SelectedIndex + 1);
|
||||
// mainWindow.OpenAnnotationResult((AnnotationResult)annGrid.SelectedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
var resultHeight = (uint)Math.Round(RESULT_WIDTH / formState.CurrentVideoSize.Width * formState.CurrentVideoSize.Height);
|
||||
mediaPlayer.TakeSnapshot(0, imgPath, RESULT_WIDTH, resultHeight);
|
||||
if (isVideo)
|
||||
mediaPlayer.Play();
|
||||
else
|
||||
await NextMedia(ct: cancellationToken);
|
||||
await NextMedia(ct: cancellationToken);
|
||||
}
|
||||
mainWindow.Editor.SetBackground(null);
|
||||
formState.BackgroundTime = null;
|
||||
|
||||
mainWindow.LvFiles.Items.Refresh();
|
||||
mainWindow.Editor.RemoveAllAnns();
|
||||
}
|
||||
|
||||
var annotation = await annotationService.SaveAnnotation(originalMediaName, time, currentDetections, token: cancellationToken);
|
||||
if (isVideo)
|
||||
mainWindow.AddAnnotation(annotation);
|
||||
private async Task<List<Annotation>> SaveAnnotationInner(string imgPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var canvasDetections = mainWindow.Editor.CurrentDetections.Select(x => x.ToCanvasLabel()).ToList();
|
||||
var annotationsResult = new List<Annotation>();
|
||||
if (!File.Exists(imgPath))
|
||||
{
|
||||
var source = (mainWindow.Editor.BackgroundImage.Source as BitmapSource)!;
|
||||
if (source.PixelWidth <= RESULT_WIDTH * 2 && source.PixelHeight <= RESULT_WIDTH * 2) // Allow to be up to 2560*2560 to save to 1280*1280
|
||||
{
|
||||
//Save image
|
||||
await using var stream = new FileStream(imgPath, FileMode.Create);
|
||||
var encoder = new JpegBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(source));
|
||||
encoder.Save(stream);
|
||||
await stream.FlushAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Tiling
|
||||
|
||||
//1. Restore original picture coordinates
|
||||
var pictureCoordinatesDetections = canvasDetections.Select(x => new CanvasLabel(
|
||||
new YoloLabel(x, mainWindow.Editor.RenderSize, formState.CurrentMediaSize), formState.CurrentMediaSize, null, x.Confidence))
|
||||
.ToList();
|
||||
|
||||
//2. Split to 1280*1280 frames
|
||||
var results = TileProcessor.Split(formState.CurrentMediaSize, pictureCoordinatesDetections, cancellationToken);
|
||||
|
||||
//3. Save each frame as a separate annotation
|
||||
BitmapEncoder tileEncoder = new JpegBitmapEncoder();
|
||||
foreach (var res in results)
|
||||
{
|
||||
var mediaName = $"{formState.MediaName}!split!{res.Tile.X}_{res.Tile.Y}!";
|
||||
var time = TimeSpan.Zero;
|
||||
var annotationName = mediaName.ToTimeName(time);
|
||||
|
||||
var tileImgPath = Path.Combine(dirConfig.Value.ImagesDirectory, $"{annotationName}{Constants.JPG_EXT}");
|
||||
await using var tileStream = new FileStream(tileImgPath, FileMode.Create);
|
||||
var bitmap = new CroppedBitmap(source, new Int32Rect((int)res.Tile.X, (int)res.Tile.Y, (int)res.Tile.Width, (int)res.Tile.Height));
|
||||
tileEncoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||
tileEncoder.Save(tileStream);
|
||||
await tileStream.FlushAsync(cancellationToken);
|
||||
|
||||
var frameSize = new Size(res.Tile.Width, res.Tile.Height);
|
||||
var detections = res.Detections
|
||||
.Select(det => det.ReframeToSmall(res.Tile))
|
||||
.Select(x => new Detection(annotationName, new YoloLabel(x, frameSize)))
|
||||
.ToList();
|
||||
|
||||
annotationsResult.Add(await annotationService.SaveAnnotation(mediaName, time, detections, token: cancellationToken));
|
||||
}
|
||||
return annotationsResult;
|
||||
}
|
||||
}
|
||||
|
||||
var timeImg = formState.BackgroundTime ?? TimeSpan.FromMilliseconds(mediaPlayer.Time);
|
||||
var timeName = formState.MediaName.ToTimeName(timeImg);
|
||||
var currentDetections = canvasDetections.Select(x =>
|
||||
new Detection(timeName, new YoloLabel(x, mainWindow.Editor.RenderSize)))
|
||||
.ToList();
|
||||
var annotation = await annotationService.SaveAnnotation(formState.MediaName, timeImg, currentDetections, token: cancellationToken);
|
||||
return [annotation];
|
||||
}
|
||||
|
||||
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken ct)
|
||||
@@ -316,21 +389,20 @@ public class AnnotatorEventHandler(
|
||||
});
|
||||
|
||||
await dbFactory.DeleteAnnotations(notification.AnnotationNames, ct);
|
||||
|
||||
try
|
||||
|
||||
foreach (var name in notification.AnnotationNames)
|
||||
{
|
||||
foreach (var name in notification.AnnotationNames)
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine(dirConfig.Value.ImagesDirectory, $"{name}{Constants.JPG_EXT}"));
|
||||
File.Delete(Path.Combine(dirConfig.Value.LabelsDirectory, $"{name}{Constants.TXT_EXT}"));
|
||||
File.Delete(Path.Combine(dirConfig.Value.ThumbnailsDirectory, $"{name}{Constants.THUMBNAIL_PREFIX}{Constants.JPG_EXT}"));
|
||||
File.Delete(Path.Combine(dirConfig.Value.ResultsDirectory, $"{name}{Constants.RESULT_PREFIX}{Constants.JPG_EXT}"));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, e.Message);
|
||||
throw;
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
//Only validators can send Delete to the queue
|
||||
@@ -403,4 +475,4 @@ public class AnnotatorEventHandler(
|
||||
map.SatelliteMap.Position = pointLatLon;
|
||||
map.SatelliteMap.ZoomAndCenterMarkers(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user