using System.Windows; using System.Windows.Controls; using System.Windows.Media; using CommunityToolkit.Mvvm.ComponentModel; using System.IO; using System.Windows.Media.Imaging; using Emgu.CV.CvEnum; using Emgu.CV; using System.Drawing; using Image = System.Windows.Controls.Image; using Rectangle = System.Windows.Shapes.Rectangle; using Size = System.Drawing.Size; using Ookii.Dialogs.Wpf; using Rect = System.Windows.Rect; using System.ComponentModel; using Newtonsoft.Json; using Xceed.Wpf.Toolkit.PropertyGrid.Attributes; using System.ComponentModel; using ClosedXML.Excel; using Colors = System.Windows.Media.Colors; using CtrEditor.FuncionesBase; namespace CtrEditor.ObjetosSim.Extraccion_Datos { /// /// Interaction logic for ucBuscarCoincidencias.xaml /// /// public partial class osBuscarCoincidencias : osBase, IosBase { [JsonIgnore] public float offsetY; [JsonIgnore] public float offsetX; [ObservableProperty] List search_rectangles; public static string NombreClase() { return "Search Templates"; } private string nombre = NombreClase(); public override string Nombre { get => nombre; set => SetProperty(ref nombre, value); } [ObservableProperty] [property: Category("Tag Extraction:")] bool search_templates; partial void OnSearch_templatesChanged(bool oldValue, bool newValue) { if (Search_templates) BuscarCoincidencias(); Search_templates = false; } [ObservableProperty] [property: Category("Tag Extraction:")] bool export_ocr; [ObservableProperty] [property: Category("Tag Extraction:")] string text_export_ocr; partial void OnExport_ocrChanged(bool value) { if (Export_ocr) { Text_export_ocr = ""; if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null) { foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables) { if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre) { if (objetoSimulable is osExtraccionTag osExtraccionTag) { osExtraccionTag.CaptureImageAreaAndDoOCR(); Text_export_ocr += osExtraccionTag.Tag_extract; } } } } } Export_ocr = false; } public override void TopChanging(float oldValue, float newValue) { offsetY = newValue - oldValue; } public override void LeftChanging(float oldValue, float newValue) { offsetX = newValue - oldValue; } [ObservableProperty] [property: Category("Tag Extraction:")] string tag_extract; [ObservableProperty] [property: Category("Tag Extraction:")] string clase; [ObservableProperty] [property: Category("Tag Extraction:")] string tag_name; [ObservableProperty] float opacity_oculto; [ObservableProperty] [property: Category("Tag Extraction:")] bool show_debug_ocr; [ObservableProperty] [property: Category("Tag Extraction:")] float threshold; [ObservableProperty] [property: Category("Tag Extraction:")] [property: ReadOnly(true)] int coincidencias; public osBuscarCoincidencias() { Ancho = 1; Alto = 1; Angulo = 0; Opacity_oculto = 0.1f; Threshold = 0.6f; } private void ShowPreviewWindow(Stream imageStream) { // Create a new window for preview Window previewWindow = new Window { Title = "Preview Captured Image", Width = 500, Height = 500, Content = new Image { Source = BitmapFrame.Create(imageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad), Stretch = Stretch.Uniform } }; previewWindow.ShowDialog(); } public async void BuscarCoincidencias() { var progressDialog = new ProgressDialog { WindowTitle = "Procesando", Text = "Buscando coincidencias...", ShowTimeRemaining = true, ShowCancelButton = false }; progressDialog.DoWork += (sender, e) => BuscarCoincidenciasAsync(progressDialog); progressDialog.RunWorkerCompleted += (sender, e) => { if (e.Error != null) { MessageBox.Show(e.Error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); } }; progressDialog.Show(); } private void BuscarCoincidenciasAsync(ProgressDialog progressDialog) { // Reset the Canvas children Application.Current.Dispatcher.Invoke(() => { var clearShapes = _mainViewModel.MainCanvas.Children.OfType().Where(s => s.Tag as string == "BuscarCoincidencias").ToList(); foreach (var shape in clearShapes) { _mainViewModel.MainCanvas.Children.Remove(shape); } if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo) { // Asegurarse de que la imagen origen está disponible if (imagenDeFondo.Source is BitmapSource bitmapSource) { progressDialog.ReportProgress(10); // Obtener los DPI de la imagen original float originalDpiX = (float)bitmapSource.DpiX; float originalDpiY = (float)bitmapSource.DpiY; // Estándar DPI en el que el Canvas renderiza la imagen float canvasDpiX = 96; // WPF usually renders at 96 DPI float canvasDpiY = 96; // Calcular el ratio de escala entre el Canvas y la imagen original float scaleFactorX = originalDpiX / canvasDpiX; float scaleFactorY = originalDpiY / canvasDpiY; // Ajustar las coordenadas de recorte en función del ratio de escala int x = (int)MeterToPixels(Left * scaleFactorX); int y = (int)MeterToPixels(Top * scaleFactorY); int width = (int)MeterToPixels(Ancho * scaleFactorX); int height = (int)MeterToPixels(Alto * scaleFactorY); // Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen if (x < 0) x = 0; if (y < 0) y = 0; if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x; if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y; // Recortar el área deseada utilizando las coordenadas ajustadas CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height)); // Convertir CroppedBitmap a Mat directamente Mat templateMat = BitmapSourceToMat(croppedBitmap); int scale = 4; // Convertir la plantilla a escala de grises y redimensionarla Mat templateGray = new Mat(); CvInvoke.CvtColor(templateMat, templateGray, ColorConversion.Bgr2Gray); Mat templateGrayResized = new Mat(); CvInvoke.Resize(templateGray, templateGrayResized, new Size(templateGray.Width / scale, templateGray.Height / scale), 0, 0, Inter.Linear); progressDialog.ReportProgress(20); // Cargar la imagen principal completa en un Mat Mat mainImageMat = BitmapSourceToMat(bitmapSource); // Convertir la imagen principal a escala de grises y redimensionarla Mat mainImageGray = new Mat(); CvInvoke.CvtColor(mainImageMat, mainImageGray, ColorConversion.Bgr2Gray); Mat mainImageGrayResized = new Mat(); CvInvoke.Resize(mainImageGray, mainImageGrayResized, new Size(mainImageGray.Width / scale, mainImageGray.Height / scale), 0, 0, Inter.Linear); progressDialog.ReportProgress(50); // Realizar la coincidencia de plantillas Mat result = new Mat(); CvInvoke.MatchTemplate(mainImageGray, templateGray, result, TemplateMatchingType.CcoeffNormed); // Establecer un umbral de coincidencia if (Threshold < 0.4) Threshold = 0.4f; double threshold = Threshold; int ConteoPositivos = 0; // Lista para mantener áreas ya aceptadas if (search_rectangles != null) search_rectangles.Clear(); else search_rectangles = new List(); // Añadir el rectángulo usado por croppedBitmap a search_rectangles //search_rectangles.Add(new Rect(x / scaleFactorX, y / scaleFactorY, width / scaleFactorX, height / scaleFactorY)); // Obtener los puntos que superan el umbral float[] resultArray = result.GetData(false) as float[]; if (resultArray != null) { for (int i = 0; i < resultArray.Length; i++) { if (resultArray[i] >= threshold) { int row = i / result.Cols; int col = i % result.Cols; Rect newRect = new Rect(); newRect.X = col / scaleFactorX; newRect.Y = row / scaleFactorY; newRect.Width = width / scaleFactorX; newRect.Height = height / scaleFactorY; // Crear un rectángulo para la coincidencia actual Rectangle matchRect = new Rectangle { Stroke = new SolidColorBrush(Colors.Red), StrokeThickness = 2, Width = newRect.Width, Height = newRect.Height, Tag = "BuscarCoincidencias" }; Canvas.SetLeft(matchRect, newRect.X); Canvas.SetTop(matchRect, newRect.Y); // Verificar si la coincidencia actual está dentro de algún rectángulo aceptado bool isOverlap = search_rectangles.Any(r => r.IntersectsWith(newRect) ); // Si no hay superposición, agregar el rectángulo al Canvas y a la lista de aceptados if (!isOverlap) { Canvas.SetZIndex(matchRect, 40); _mainViewModel.MainCanvas.Children.Add(matchRect); search_rectangles.Add(newRect); ConteoPositivos++; Coincidencias = ConteoPositivos; progressDialog.ReportProgress(90); if (ConteoPositivos > 20) return; } } } PopularTagExtraction(); } } } }); } public void PopularTagExtraction() { var objetosSimulablesCopy = new List(_mainViewModel.ObjetosSimulables); foreach (var obj in objetosSimulablesCopy) if (obj is osExtraccionTag objExtraccionTag) if (objExtraccionTag.Id_Search_Templates == this.Nombre && objExtraccionTag.Cloned) _mainViewModel.RemoverObjetoSimulable(objExtraccionTag); var objetosSimulables2Copy = new List(_mainViewModel.ObjetosSimulables); // Saltar el primer rectángulo en el foreach int Row = 0; foreach (var rectangle in search_rectangles) //.Skip(1)) { float offsetX = PixelsToMeters((float)rectangle.X) - Left; float offsetY = PixelsToMeters((float)rectangle.Y) - Top; osExtraccionTag newObj = null; foreach (var eTag in objetosSimulables2Copy) { if (eTag is osExtraccionTag objExtraccionTag) { if (objExtraccionTag.Id_Search_Templates == this.Nombre) { newObj = (osExtraccionTag)_mainViewModel.DuplicarObjeto(objExtraccionTag, offsetX, offsetY); if (newObj != null) { newObj.Cloned = true; newObj.Cloned_from = objExtraccionTag.Id; newObj.Copy_Number = Row; newObj.Enable_On_All_Pages = false; if (newObj.Extraer) objExtraccionTag.CaptureImageAreaAndDoOCR(); } } } } Row++; if (newObj != null) newObj.New_Row = true; } } public static int FindFirstEmptyRow(IXLWorksheet worksheet) { var lastRowUsed = worksheet.LastRowUsed(); return lastRowUsed == null ? 1 : lastRowUsed.RowNumber() + 1; } // Método para convertir BitmapSource a Mat private Mat BitmapSourceToMat(BitmapSource bitmapSource) { Bitmap bitmap; using (MemoryStream outStream = new MemoryStream()) { BitmapEncoder enc = new BmpBitmapEncoder(); enc.Frames.Add(BitmapFrame.Create(bitmapSource)); enc.Save(outStream); bitmap = new Bitmap(outStream); } return bitmap.ToMat(); } public override void ucLoaded() { // El UserControl ya se ha cargado y podemos obtener las coordenadas para // crear el objeto de simulacion base.ucLoaded(); } } public partial class ucBuscarCoincidencias : UserControl, IDataContainer { public osBase? Datos { get; set; } public ucBuscarCoincidencias() { InitializeComponent(); this.Loaded += OnLoaded; this.Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { Datos?.ucLoaded(); } private void OnUnloaded(object sender, RoutedEventArgs e) { Datos?.ucUnLoaded(); } public void Highlight(bool State) { } public ZIndexEnum ZIndex() { return ZIndexEnum.Estaticos; } } }