diff --git a/Bonsai.Vision.Design/ImageEllipseRoiPicker.cs b/Bonsai.Vision.Design/ImageEllipsePicker.cs similarity index 92% rename from Bonsai.Vision.Design/ImageEllipseRoiPicker.cs rename to Bonsai.Vision.Design/ImageEllipsePicker.cs index 84faa0a30..5e162c865 100644 --- a/Bonsai.Vision.Design/ImageEllipseRoiPicker.cs +++ b/Bonsai.Vision.Design/ImageEllipsePicker.cs @@ -1,375 +1,388 @@ -using System; -using System.Linq; -using OpenCV.Net; -using System.Reactive.Linq; -using System.Windows.Forms; -using System.Collections.ObjectModel; -using System.Drawing; -using OpenTK.Graphics.OpenGL; -using Bonsai.Design; -using System.Globalization; -using System.Drawing.Text; -using System.Drawing.Drawing2D; -using System.Collections.Specialized; -using Point = OpenCV.Net.Point; -using Font = System.Drawing.Font; - -namespace Bonsai.Vision.Design -{ - class ImageEllipseRoiPicker : ImageBox - { - bool disposed; - int? selectedRoi; - const int FillOpacity = 85; - const float LabelFontScale = 0.1f; - const double ScaleIncrement = 0.1; - readonly ObservableCollection regions = new ObservableCollection(); - CommandExecutor commandExecutor = new CommandExecutor(); - IplImage labelImage; - IplImageTexture labelTexture; - bool refreshLabels; - Font labelFont; - bool dragging; - - public ImageEllipseRoiPicker() - { - Canvas.KeyDown += Canvas_KeyDown; - commandExecutor.StatusChanged += commandExecutor_StatusChanged; - regions.CollectionChanged += regions_CollectionChanged; - var mouseDoubleClick = Observable.FromEventPattern(Canvas, nameof(MouseDoubleClick)).Select(e => e.EventArgs); - var mouseMove = Observable.FromEventPattern(Canvas, nameof(MouseMove)).Select(e => e.EventArgs); - var mouseDown = Observable.FromEventPattern(Canvas, nameof(MouseDown)).Select(e => e.EventArgs); - var mouseUp = Observable.FromEventPattern(Canvas, nameof(MouseUp)).Select(e => e.EventArgs); - mouseDown = mouseDown.Do(x => dragging = true); - mouseUp = mouseUp.Do(x => dragging = false); - - var roiSelected = from downEvt in mouseDown - where Image != null - let location = NormalizedLocation(downEvt.X, downEvt.Y) - let selection = (from region in regions.Select((rect, i) => new { rect, i = (int?)i }) - let distance = TestIntersection(region.rect, location) - where distance < 1 - orderby distance - select region.i) - .FirstOrDefault() - select new Action(() => SelectedRegion = selection); - - var roiMoveScale = (from downEvt in mouseDown - where Image != null && selectedRoi.HasValue - let location = NormalizedLocation(downEvt.X, downEvt.Y) - let selection = selectedRoi.Value - let region = regions[selection] - select (from moveEvt in mouseMove.TakeUntil(mouseUp) - let target = NormalizedLocation(moveEvt.X, moveEvt.Y) - let modifiedRegion = downEvt.Button == MouseButtons.Right - ? ScaleRegion(region, target, ModifierKeys.HasFlag(Keys.Control)) - : MoveRegion(region, target - location) - let modifiedRectangle = RegionRectangle(modifiedRegion) - where modifiedRectangle.Width > 0 && modifiedRectangle.Height > 0 && - modifiedRectangle.Left >= 0 && modifiedRectangle.Top >= 0 && - modifiedRectangle.Right < Image.Width && modifiedRectangle.Bottom < Image.Height - select modifiedRegion) - .Publish(ps => - ps.TakeLast(1).Do(modifiedRegion => - commandExecutor.Execute( - () => regions[selection] = modifiedRegion, - () => regions[selection] = region)) - .Merge(ps)) - .Select(displacedRegion => new Action(() => regions[selection] = displacedRegion))) - .Switch(); - - var regionInsertion = (from downEvt in mouseDown - where Image != null && downEvt.Button == MouseButtons.Left && !selectedRoi.HasValue - let count = regions.Count - let origin = NormalizedLocation(downEvt.X, downEvt.Y) - select (from moveEvt in mouseMove.TakeUntil(mouseUp) - let location = EnsureSizeRatio(origin, NormalizedLocation(moveEvt.X, moveEvt.Y), ModifierKeys.HasFlag(Keys.Control)) - where location.X - origin.X != 0 && location.Y - origin.Y != 0 - select CreateEllipseRegion(origin, location)) - .Publish(ps => - ps.TakeLast(1).Do(region => - commandExecutor.Execute( - () => { if (count == regions.Count) AddRegion(region); }, - () => { regions.Remove(region); SelectedRegion = null; })) - .Merge(ps)) - .Select(region => new Action(() => - { - if (selectedRoi.HasValue) regions[selectedRoi.Value] = region; - else AddRegion(region); - }))) - .Switch(); - - var roiActions = Observable.Merge(roiSelected, roiMoveScale, regionInsertion); - roiActions.Subscribe(action => - { - action(); - }); - } - - void regions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - refreshLabels = true; - } - - void commandExecutor_StatusChanged(object sender, EventArgs e) - { - Canvas.Invalidate(); - } - - static RotatedRect CreateEllipseRegion(Point origin, Point location) - { - RotatedRect region; - region.Size = new Size2f(Math.Abs(location.X - origin.X), Math.Abs(location.Y - origin.Y)); - region.Center = new Point2f((location.X + origin.X) / 2f, (location.Y + origin.Y) / 2f); - region.Angle = 0; - return region; - } - - static RotatedRect MoveRegion(RotatedRect region, Point displacement) - { - region.Center += new Point2f(displacement); - return region; - } - - static RotatedRect ScaleRegion(RotatedRect region, Point target, bool uniformScaling) - { - var size = new Size2f( - 2 * Math.Abs(target.X - region.Center.X), - 2 * Math.Abs(target.Y - region.Center.Y)); - if (uniformScaling) - { - var sizeNorm = (float)Math.Sqrt(size.Width * size.Width + size.Height * size.Height); - region.Size.Width = sizeNorm; - region.Size.Height = sizeNorm; - } - else region.Size = size; - return region; - } - - void Canvas_KeyDown(object sender, KeyEventArgs e) - { - if (dragging) return; - if (e.KeyCode == Keys.PageUp) ImageScale += ScaleIncrement; - if (e.KeyCode == Keys.PageDown) ImageScale -= ScaleIncrement; - if (e.Control && e.KeyCode == Keys.Z) commandExecutor.Undo(); - if (e.Control && e.KeyCode == Keys.Y) commandExecutor.Redo(); - if (e.Control && e.KeyCode == Keys.V) - { - var roiText = (string)Clipboard.GetData(DataFormats.Text); - try - { - var mousePosition = PointToClient(MousePosition); - var offset = NormalizedLocation(mousePosition.X, mousePosition.Y); - var roiData = (float[])ArrayConvert.ToArray(roiText, 1, typeof(float)); - var center = new Point2f(offset.X, offset.Y); - var size = new Size2f(roiData[0], roiData[1]); - var roi = new RotatedRect(center, size, 0); - - var selection = selectedRoi; - commandExecutor.Execute( - () => AddRegion(roi), - () => { regions.Remove(roi); SelectedRegion = selection; }); - } - catch (ArgumentException) { } - catch (InvalidCastException) { } - catch (FormatException) { } - } - - if (selectedRoi.HasValue) - { - if (e.Control && e.KeyCode == Keys.C) - { - var roi = regions[selectedRoi.Value]; - var roiData = new[] { roi.Size.Width, roi.Size.Height }; - Clipboard.SetData(DataFormats.Text, ArrayConvert.ToString(roiData)); - } - - if (e.KeyCode == Keys.Delete) - { - var selection = selectedRoi.Value; - var region = regions[selection]; - commandExecutor.Execute( - () => { regions.RemoveAt(selection); SelectedRegion = null; }, - () => { regions.Insert(selection, region); SelectedRegion = selection; }); - } - } - } - - protected override bool ProcessCmdKey(ref Message msg, Keys keyData) - { - if (keyData == Keys.Tab && regions.Count > 0) - { - SelectedRegion = ((selectedRoi ?? 0) + 1) % regions.Count; - Canvas.Invalidate(); - } - - return base.ProcessCmdKey(ref msg, keyData); - } - - void AddRegion(RotatedRect region) - { - regions.Add(region); - SelectedRegion = regions.Count - 1; - } - - float TestIntersection(RotatedRect region, Point point) - { - var dx = point.X - region.Center.X; - var dy = point.Y - region.Center.Y; - var a = region.Size.Width * region.Size.Width / 4; - var b = region.Size.Height * region.Size.Height / 4; - return dx * dx / a + dy * dy / b; - } - - Point NormalizedLocation(int x, int y) - { - return new Point( - Math.Max(0, Math.Min((int)(x * Image.Width / (float)Canvas.Width), Image.Width - 1)), - Math.Max(0, Math.Min((int)(y * Image.Height / (float)Canvas.Height), Image.Height - 1))); - } - - Point EnsureSizeRatio(Point origin, Point location, bool square) - { - if (square) - { - var dx = location.X - origin.X; - var dy = location.Y - origin.Y; - var width = Math.Abs(dx); - var height = Math.Abs(dy); - if (width < height) location.Y -= Math.Sign(dy) * (height - width); - else location.X -= Math.Sign(dx) * (width - height); - } - return location; - } - - RectangleF RegionRectangle(RotatedRect region) - { - var x = region.Center.X - region.Size.Width / 2f; - var y = region.Center.Y - region.Size.Height / 2f; - var width = region.Size.Width; - var height = region.Size.Height; - return new RectangleF(x, y, width, height); - } - - public int? SelectedRegion - { - get { return selectedRoi; } - set - { - selectedRoi = value; - refreshLabels = true; - } - } - - public Collection Regions - { - get { return regions; } - } - - public event EventHandler RegionsChanged - { - add { regions.CollectionChanged += new NotifyCollectionChangedEventHandler(value); } - remove { regions.CollectionChanged -= new NotifyCollectionChangedEventHandler(value); } - } - - protected override void OnLoad(EventArgs e) - { - if (DesignMode) return; - GL.Enable(EnableCap.Blend); - GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); - labelTexture = new IplImageTexture(); - base.OnLoad(e); - } - - private void UpdateLabelTexture() - { - if (labelImage != null) - { - if (labelFont == null) - { - var emSize = Font.SizeInPoints * (labelImage.Height * LabelFontScale) / Font.Height; - labelFont = new Font(Font.FontFamily, emSize); - } - - labelImage.SetZero(); - using (var labelBitmap = new Bitmap(labelImage.Width, labelImage.Height, labelImage.WidthStep, System.Drawing.Imaging.PixelFormat.Format32bppArgb, labelImage.ImageData)) - using (var graphics = Graphics.FromImage(labelBitmap)) - using (var regionBrush = new SolidBrush(Color.FromArgb(FillOpacity, Color.Red))) - using (var selectedBrush = new SolidBrush(Color.FromArgb(FillOpacity, Color.LimeGreen))) - using (var format = new StringFormat()) - { - graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - format.Alignment = StringAlignment.Center; - format.LineAlignment = StringAlignment.Center; - for (int i = 0; i < regions.Count; i++) - { - var rect = RegionRectangle(regions[i]); - var brush = i == selectedRoi ? selectedBrush : regionBrush; - graphics.FillEllipse(brush, rect); - graphics.DrawString(i.ToString(CultureInfo.InvariantCulture), labelFont, Brushes.White, rect, format); - } - } - - labelTexture.Update(labelImage); - } - } - - protected override void SetImage(IplImage image) - { - if (image == null) labelImage = null; - else if (labelImage == null || labelImage.Width != image.Width || labelImage.Height != image.Height) - { - labelImage = new IplImage(image.Size, IplDepth.U8, 4); - refreshLabels = true; - } - base.SetImage(image); - } - - protected override void OnRenderFrame(EventArgs e) - { - base.OnRenderFrame(e); - var image = Image; - if (image != null) - { - GL.Disable(EnableCap.Texture2D); - GL.Color3(Color.White); - GL.Enable(EnableCap.Texture2D); - if (labelImage != null) - { - if (refreshLabels) - { - UpdateLabelTexture(); - refreshLabels = false; - } - labelTexture.Draw(); - } - } - } - - protected override void Dispose(bool disposing) - { - if (!disposed) - { - if (disposing) - { - MakeCurrent(); - if (labelTexture != null) - { - labelTexture.Dispose(); - labelTexture = null; - } - - if (labelFont != null) - { - labelFont.Dispose(); - labelFont = null; - } - disposed = true; - } - } - - base.Dispose(disposing); - } - } -} +using System; +using System.Linq; +using OpenCV.Net; +using System.Reactive.Linq; +using System.Windows.Forms; +using System.Collections.ObjectModel; +using System.Drawing; +using OpenTK.Graphics.OpenGL; +using Bonsai.Design; +using System.Globalization; +using System.Drawing.Text; +using System.Drawing.Drawing2D; +using System.Collections.Specialized; +using Point = OpenCV.Net.Point; +using Font = System.Drawing.Font; + +namespace Bonsai.Vision.Design +{ + class ImageEllipsePicker : ImageBox + { + bool disposed; + int? selectedRoi; + const int FillOpacity = 85; + const float LabelFontScale = 0.1f; + const double ScaleIncrement = 0.1; + readonly ObservableCollection regions = new ObservableCollection(); + readonly CommandExecutor commandExecutor = new CommandExecutor(); + IplImage labelImage; + IplImageTexture labelTexture; + bool refreshLabels; + Font labelFont; + bool dragging; + + public ImageEllipsePicker() + { + LabelRegions = true; + Canvas.KeyDown += Canvas_KeyDown; + commandExecutor.StatusChanged += commandExecutor_StatusChanged; + regions.CollectionChanged += regions_CollectionChanged; + var mouseDoubleClick = Observable.FromEventPattern(Canvas, nameof(MouseDoubleClick)).Select(e => e.EventArgs); + var mouseMove = Observable.FromEventPattern(Canvas, nameof(MouseMove)).Select(e => e.EventArgs); + var mouseDown = Observable.FromEventPattern(Canvas, nameof(MouseDown)).Select(e => e.EventArgs); + var mouseUp = Observable.FromEventPattern(Canvas, nameof(MouseUp)).Select(e => e.EventArgs); + var dragStart = mouseDown.Select(x => new Action(() => dragging = true)); + var dragEnd = mouseUp.Select(x => new Action(() => dragging = false)); + + var roiSelected = from downEvt in mouseDown + where Image != null + let location = NormalizedLocation(downEvt.X, downEvt.Y) + let selection = (from region in regions.Select((rect, i) => new { rect, i = (int?)i }) + let distance = TestIntersection(region.rect, location) + where distance < 1 + orderby distance + select region.i) + .FirstOrDefault() + select new Action(() => SelectedRegion = selection); + + var roiMoveScale = (from downEvt in mouseDown + where Image != null && selectedRoi.HasValue + let location = NormalizedLocation(downEvt.X, downEvt.Y) + let selection = selectedRoi.Value + let region = regions[selection] + select (from moveEvt in mouseMove.TakeUntil(mouseUp) + let target = NormalizedLocation(moveEvt.X, moveEvt.Y) + let modifiedRegion = downEvt.Button == MouseButtons.Right + ? ScaleRegion(region, target, ModifierKeys.HasFlag(Keys.Control)) + : MoveRegion(region, target - location) + let modifiedRectangle = RegionRectangle(modifiedRegion) + where modifiedRectangle.Width > 0 && modifiedRectangle.Height > 0 && + modifiedRectangle.Left >= 0 && modifiedRectangle.Top >= 0 && + modifiedRectangle.Right < Image.Width && modifiedRectangle.Bottom < Image.Height + select modifiedRegion) + .Publish(ps => + ps.TakeLast(1).Do(modifiedRegion => + commandExecutor.Execute( + () => regions[selection] = modifiedRegion, + () => regions[selection] = region)) + .Merge(ps)) + .Select(displacedRegion => new Action(() => regions[selection] = displacedRegion))) + .Switch(); + + var regionInsertion = (from downEvt in mouseDown + where Image != null && downEvt.Button == MouseButtons.Left && !selectedRoi.HasValue && !(regions.Count >= MaxRegions) + let count = regions.Count + let origin = NormalizedLocation(downEvt.X, downEvt.Y) + select (from moveEvt in mouseMove.TakeUntil(mouseUp) + let location = EnsureSizeRatio( + origin, + NormalizedLocation(moveEvt.X, moveEvt.Y), + IsCirclePicker || ModifierKeys.HasFlag(Keys.Control)) + where location.X - origin.X != 0 && location.Y - origin.Y != 0 + select CreateEllipseRegion(origin, location)) + .Publish(ps => + ps.TakeLast(1).Do(region => + commandExecutor.Execute( + () => { if (count == regions.Count) AddRegion(region); }, + () => { regions.Remove(region); SelectedRegion = null; })) + .Merge(ps)) + .Select(region => new Action(() => + { + if (selectedRoi.HasValue) regions[selectedRoi.Value] = region; + else AddRegion(region); + }))) + .Switch(); + + var roiActions = Observable.Merge(dragStart, dragEnd, roiSelected, roiMoveScale, regionInsertion); + roiActions.Subscribe(action => + { + action(); + }); + } + + void regions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + refreshLabels = true; + } + + void commandExecutor_StatusChanged(object sender, EventArgs e) + { + Canvas.Invalidate(); + } + + static RotatedRect CreateEllipseRegion(Point origin, Point location) + { + RotatedRect region; + region.Size = new Size2f(Math.Abs(location.X - origin.X), Math.Abs(location.Y - origin.Y)); + region.Center = new Point2f((location.X + origin.X) / 2f, (location.Y + origin.Y) / 2f); + region.Angle = 0; + return region; + } + + static RotatedRect MoveRegion(RotatedRect region, Point displacement) + { + region.Center += new Point2f(displacement); + return region; + } + + static RotatedRect ScaleRegion(RotatedRect region, Point target, bool uniformScaling) + { + var size = new Size2f( + 2 * Math.Abs(target.X - region.Center.X), + 2 * Math.Abs(target.Y - region.Center.Y)); + if (uniformScaling) + { + var sizeNorm = (float)Math.Sqrt(size.Width * size.Width + size.Height * size.Height); + region.Size.Width = sizeNorm; + region.Size.Height = sizeNorm; + } + else region.Size = size; + return region; + } + + void Canvas_KeyDown(object sender, KeyEventArgs e) + { + if (dragging) return; + if (e.KeyCode == Keys.PageUp) ImageScale += ScaleIncrement; + if (e.KeyCode == Keys.PageDown) ImageScale -= ScaleIncrement; + if (e.Control && e.KeyCode == Keys.Z) commandExecutor.Undo(); + if (e.Control && e.KeyCode == Keys.Y) commandExecutor.Redo(); + if (e.Control && e.KeyCode == Keys.V && !(regions.Count >= MaxRegions)) + { + var roiText = (string)Clipboard.GetData(DataFormats.Text); + try + { + var mousePosition = PointToClient(MousePosition); + var offset = NormalizedLocation(mousePosition.X, mousePosition.Y); + var roiData = (float[])ArrayConvert.ToArray(roiText, 1, typeof(float)); + var center = new Point2f(offset.X, offset.Y); + var size = new Size2f(roiData[0], roiData[1]); + var roi = new RotatedRect(center, size, 0); + + var selection = selectedRoi; + commandExecutor.Execute( + () => AddRegion(roi), + () => { regions.Remove(roi); SelectedRegion = selection; }); + } + catch (ArgumentException) { } + catch (InvalidCastException) { } + catch (FormatException) { } + } + + if (selectedRoi.HasValue) + { + if (e.Control && e.KeyCode == Keys.C) + { + var roi = regions[selectedRoi.Value]; + var roiData = new[] { roi.Size.Width, roi.Size.Height }; + Clipboard.SetData(DataFormats.Text, ArrayConvert.ToString(roiData)); + } + + if (e.KeyCode == Keys.Delete) + { + var selection = selectedRoi.Value; + var region = regions[selection]; + commandExecutor.Execute( + () => { regions.RemoveAt(selection); SelectedRegion = null; }, + () => { regions.Insert(selection, region); SelectedRegion = selection; }); + } + } + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData == Keys.Tab && regions.Count > 0) + { + SelectedRegion = ((selectedRoi ?? 0) + 1) % regions.Count; + Canvas.Invalidate(); + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + void AddRegion(RotatedRect region) + { + regions.Add(region); + SelectedRegion = regions.Count - 1; + } + + float TestIntersection(RotatedRect region, Point point) + { + var dx = point.X - region.Center.X; + var dy = point.Y - region.Center.Y; + var a = region.Size.Width * region.Size.Width / 4; + var b = region.Size.Height * region.Size.Height / 4; + return dx * dx / a + dy * dy / b; + } + + Point NormalizedLocation(int x, int y) + { + return new Point( + Math.Max(0, Math.Min((int)(x * Image.Width / (float)Canvas.Width), Image.Width - 1)), + Math.Max(0, Math.Min((int)(y * Image.Height / (float)Canvas.Height), Image.Height - 1))); + } + + Point EnsureSizeRatio(Point origin, Point location, bool square) + { + if (square) + { + var dx = location.X - origin.X; + var dy = location.Y - origin.Y; + var width = Math.Abs(dx); + var height = Math.Abs(dy); + if (width < height) location.Y -= Math.Sign(dy) * (height - width); + else location.X -= Math.Sign(dx) * (width - height); + } + return location; + } + + RectangleF RegionRectangle(RotatedRect region) + { + var x = region.Center.X - region.Size.Width / 2f; + var y = region.Center.Y - region.Size.Height / 2f; + var width = region.Size.Width; + var height = region.Size.Height; + return new RectangleF(x, y, width, height); + } + + public bool LabelRegions { get; set; } + + public bool IsCirclePicker { get; set; } + + public int? MaxRegions { get; set; } + + public int? SelectedRegion + { + get { return selectedRoi; } + set + { + selectedRoi = value; + refreshLabels = true; + } + } + + public Collection Regions + { + get { return regions; } + } + + public event EventHandler RegionsChanged + { + add { regions.CollectionChanged += new NotifyCollectionChangedEventHandler(value); } + remove { regions.CollectionChanged -= new NotifyCollectionChangedEventHandler(value); } + } + + protected override void OnLoad(EventArgs e) + { + if (DesignMode) return; + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + labelTexture = new IplImageTexture(); + base.OnLoad(e); + } + + private void UpdateLabelTexture() + { + if (labelImage != null) + { + if (labelFont == null) + { + var emSize = Font.SizeInPoints * (labelImage.Height * LabelFontScale) / Font.Height; + labelFont = new Font(Font.FontFamily, emSize); + } + + labelImage.SetZero(); + using (var labelBitmap = new Bitmap(labelImage.Width, labelImage.Height, labelImage.WidthStep, System.Drawing.Imaging.PixelFormat.Format32bppArgb, labelImage.ImageData)) + using (var graphics = Graphics.FromImage(labelBitmap)) + using (var regionBrush = new SolidBrush(Color.FromArgb(FillOpacity, Color.Red))) + using (var selectedBrush = new SolidBrush(Color.FromArgb(FillOpacity, Color.LimeGreen))) + using (var format = new StringFormat()) + { + graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + format.Alignment = StringAlignment.Center; + format.LineAlignment = StringAlignment.Center; + for (int i = 0; i < regions.Count; i++) + { + var rect = RegionRectangle(regions[i]); + var brush = i == selectedRoi ? selectedBrush : regionBrush; + graphics.FillEllipse(brush, rect); + if (LabelRegions) + { + graphics.DrawString(i.ToString(CultureInfo.InvariantCulture), labelFont, Brushes.White, rect, format); + } + } + } + + labelTexture.Update(labelImage); + } + } + + protected override void SetImage(IplImage image) + { + if (image == null) labelImage = null; + else if (labelImage == null || labelImage.Width != image.Width || labelImage.Height != image.Height) + { + labelImage = new IplImage(image.Size, IplDepth.U8, 4); + refreshLabels = true; + } + base.SetImage(image); + } + + protected override void OnRenderFrame(EventArgs e) + { + base.OnRenderFrame(e); + var image = Image; + if (image != null) + { + GL.Disable(EnableCap.Texture2D); + GL.Color3(Color.White); + GL.Enable(EnableCap.Texture2D); + if (labelImage != null) + { + if (refreshLabels) + { + UpdateLabelTexture(); + refreshLabels = false; + } + labelTexture.Draw(); + } + } + } + + protected override void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + MakeCurrent(); + if (labelTexture != null) + { + labelTexture.Dispose(); + labelTexture = null; + } + + if (labelFont != null) + { + labelFont.Dispose(); + labelFont = null; + } + disposed = true; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/Bonsai.Vision.Design/ImageRoiPicker.cs b/Bonsai.Vision.Design/ImageRoiPicker.cs index 907c903b2..864560376 100644 --- a/Bonsai.Vision.Design/ImageRoiPicker.cs +++ b/Bonsai.Vision.Design/ImageRoiPicker.cs @@ -45,8 +45,8 @@ public ImageRoiPicker() var mouseMove = Observable.FromEventPattern(Canvas, nameof(MouseMove)).Select(e => e.EventArgs); var mouseDown = Observable.FromEventPattern(Canvas, nameof(MouseDown)).Select(e => e.EventArgs); var mouseUp = Observable.FromEventPattern(Canvas, nameof(MouseUp)).Select(e => e.EventArgs); - mouseDown = mouseDown.Do(x => dragging = true); - mouseUp = mouseUp.Do(x => dragging = false); + var dragStart = mouseDown.Select(x => new Action(() => dragging = true)); + var dragEnd = mouseUp.Select(x => new Action(() => dragging = false)); var roiSelected = from downEvt in mouseDown where Image != null @@ -164,7 +164,15 @@ where region.Length > 3 () => regions[selection] = region); }); - var roiActions = Observable.Merge(roiSelected, pointMove, roiMoveScale, pointInsertion, pointDeletion, regionInsertion); + var roiActions = Observable.Merge( + dragStart, + dragEnd, + roiSelected, + pointMove, + roiMoveScale, + pointInsertion, + pointDeletion, + regionInsertion); roiActions.Subscribe(action => { action(); diff --git a/Bonsai.Vision.Design/IplImageCircleEditor.cs b/Bonsai.Vision.Design/IplImageCircleEditor.cs new file mode 100644 index 000000000..4ba7fa7c6 --- /dev/null +++ b/Bonsai.Vision.Design/IplImageCircleEditor.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using System.Drawing.Design; +using System.ComponentModel; +using System.Windows.Forms.Design; +using Bonsai.Design; +using OpenCV.Net; +using System.Reactive.Linq; +using System.Windows.Forms; +using System.Collections.Generic; + +namespace Bonsai.Vision.Design +{ + /// + /// Provides a user interface for visually editing circular regions on top of + /// the input image sequence. + /// + public class IplImageCircleEditor : DataSourceTypeEditor + { + /// + /// Initializes a new instance of the class + /// using the input image data source. + /// + public IplImageCircleEditor() + : this(DataSource.Input) + { + } + + /// + /// Initializes a new instance of the class + /// using the specified image data source. + /// + /// + /// Specifies the source of image notifications to the property editor. + /// + protected IplImageCircleEditor(DataSource source) + : base(source, typeof(IplImage)) + { + } + + /// + /// Gets the sequence of images arriving to or from the operator. + /// + /// + /// An observable sequence that multicasts notifications from all the active + /// subscriptions to the workflow operator. + /// + protected virtual IObservable GetImageSource(IObservable> source) + { + return source.Merge().Select(image => image as IplImage); + } + + /// + public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) + { + return UITypeEditorEditStyle.Modal; + } + + /// + public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) + { + var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); + if (context != null && editorService != null) + { + using (var visualizerDialog = new TypeVisualizerDialog()) + { + RotatedRect[] regions = default; + var imageControl = new ImageEllipsePicker { IsCirclePicker = true }; + var propertyDescriptor = context.PropertyDescriptor; + var singleRegion = propertyDescriptor.PropertyType == typeof(Circle); + if (singleRegion) + { + if (value is Circle circle && circle.Radius > 0) + { + regions = new[] { FromCircle(circle) }; + } + imageControl.MaxRegions = 1; + imageControl.LabelRegions = false; + } + else if (value != null) + { + regions = Array.ConvertAll((Circle[])value, FromCircle); + } + + imageControl.Dock = DockStyle.Fill; + visualizerDialog.Text = propertyDescriptor.Name; + if (regions != null) + { + foreach (var region in regions) + { + imageControl.Regions.Add(region); + } + } + + imageControl.RegionsChanged += (sender, e) => + { + propertyDescriptor.SetValue(context.Instance, GetResult(imageControl.Regions, singleRegion)); + }; + + visualizerDialog.AddControl(imageControl); + imageControl.Canvas.MouseDoubleClick += (sender, e) => + { + if (e.Button == MouseButtons.Left && imageControl.Image != null && !imageControl.SelectedRegion.HasValue) + { + visualizerDialog.ClientSize = new System.Drawing.Size( + imageControl.Image.Width, + imageControl.Image.Height); + } + }; + + IDisposable subscription = null; + var source = GetDataSource(context, provider); + var imageSource = GetImageSource(source.Output); + imageControl.Load += delegate { subscription = imageSource.Subscribe(image => imageControl.Image = image); }; + imageControl.HandleDestroyed += delegate { subscription.Dispose(); }; + editorService.ShowDialog(visualizerDialog); + return GetResult(imageControl.Regions, singleRegion); + } + } + + return base.EditValue(context, provider, value); + } + + static RotatedRect FromCircle(Circle circle) + { + RotatedRect ellipse; + ellipse.Angle = 0; + ellipse.Center = circle.Center; + ellipse.Size = new Size2f(circle.Radius, circle.Radius); + return ellipse; + } + + static Circle ToCircle(RotatedRect ellipse) + { + Circle circle; + circle.Center = ellipse.Center; + circle.Radius = ellipse.Size.Width; + return circle; + } + + static object GetResult(IList regions, bool singleRegion) + { + if (regions.Count == 0) return null; + else if (singleRegion) return ToCircle(regions[0]); + else return regions.Select(ToCircle).ToArray(); + } + } + + /// + /// Provides a user interface for visually editing circular regions on top of + /// the output image sequence. + /// + public class IplImageOutputCircleEditor : IplImageCircleEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageOutputCircleEditor() + : base(DataSource.Output) + { + } + } +} diff --git a/Bonsai.Vision.Design/IplImageEllipseRoiEditor.cs b/Bonsai.Vision.Design/IplImageEllipseEditor.cs similarity index 54% rename from Bonsai.Vision.Design/IplImageEllipseRoiEditor.cs rename to Bonsai.Vision.Design/IplImageEllipseEditor.cs index 2308adf07..5934863ea 100644 --- a/Bonsai.Vision.Design/IplImageEllipseRoiEditor.cs +++ b/Bonsai.Vision.Design/IplImageEllipseEditor.cs @@ -1,94 +1,166 @@ -using System; -using System.Linq; -using System.Drawing.Design; -using System.ComponentModel; -using System.Windows.Forms.Design; -using Bonsai.Design; -using OpenCV.Net; -using System.Reactive.Linq; -using System.Windows.Forms; - -namespace Bonsai.Vision.Design -{ - /// - /// Provides an abstract base class for user interface editors that allow - /// visually editing a collection of elliptical regions on top of the active - /// image source. - /// - public abstract class IplImageEllipseRoiEditor : DataSourceTypeEditor - { - /// - /// Initializes a new instance of the class - /// using the specified image data source. - /// - /// - /// Specifies the source of image notifications to the property editor. - /// - protected IplImageEllipseRoiEditor(DataSource source) - : base(source, typeof(IplImage)) - { - } - - /// - /// Gets the sequence of images arriving to or from the operator. - /// - /// - /// An observable sequence that multicasts notifications from all the active - /// subscriptions to the workflow operator. - /// - protected virtual IObservable GetImageSource(IObservable> source) - { - return source.Merge().Select(image => image as IplImage); - } - - /// - public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) - { - return UITypeEditorEditStyle.Modal; - } - - /// - public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) - { - var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); - if (context != null && editorService != null) - { - using (var visualizerDialog = new TypeVisualizerDialog()) - { - var imageControl = new ImageEllipseRoiPicker(); - var propertyDescriptor = context.PropertyDescriptor; - var regions = (RotatedRect[])value; - - imageControl.Dock = DockStyle.Fill; - visualizerDialog.Text = propertyDescriptor.Name; - if (regions != null) - { - foreach (var region in regions) imageControl.Regions.Add(region); - } - - imageControl.RegionsChanged += (sender, e) => propertyDescriptor.SetValue(context.Instance, imageControl.Regions.ToArray()); - visualizerDialog.AddControl(imageControl); - imageControl.Canvas.MouseDoubleClick += (sender, e) => - { - if (e.Button == MouseButtons.Left && imageControl.Image != null && !imageControl.SelectedRegion.HasValue) - { - visualizerDialog.ClientSize = new System.Drawing.Size( - imageControl.Image.Width, - imageControl.Image.Height); - } - }; - - IDisposable subscription = null; - var source = GetDataSource(context, provider); - var imageSource = GetImageSource(source.Output); - imageControl.Load += delegate { subscription = imageSource.Subscribe(image => imageControl.Image = image); }; - imageControl.HandleDestroyed += delegate { subscription.Dispose(); }; - editorService.ShowDialog(visualizerDialog); - return imageControl.Regions.ToArray(); - } - } - - return base.EditValue(context, provider, value); - } - } -} +using System; +using System.Linq; +using System.Drawing.Design; +using System.ComponentModel; +using System.Windows.Forms.Design; +using Bonsai.Design; +using OpenCV.Net; +using System.Reactive.Linq; +using System.Windows.Forms; +using System.Collections.Generic; + +namespace Bonsai.Vision.Design +{ + /// + /// Provides a user interface for visually editing elliptical regions on top of + /// the input image sequence. + /// + public class IplImageEllipseEditor : DataSourceTypeEditor + { + /// + /// Initializes a new instance of the class + /// using the input image data source. + /// + public IplImageEllipseEditor() + : this(DataSource.Input) + { + } + + /// + /// Initializes a new instance of the class + /// using the specified image data source. + /// + /// + /// Specifies the source of image notifications to the property editor. + /// + protected IplImageEllipseEditor(DataSource source) + : base(source, typeof(IplImage)) + { + } + + /// + /// Gets the sequence of images arriving to or from the operator. + /// + /// + /// An observable sequence that multicasts notifications from all the active + /// subscriptions to the workflow operator. + /// + protected virtual IObservable GetImageSource(IObservable> source) + { + return source.Merge().Select(image => image as IplImage); + } + + /// + public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) + { + return UITypeEditorEditStyle.Modal; + } + + /// + public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) + { + var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); + if (context != null && editorService != null) + { + using (var visualizerDialog = new TypeVisualizerDialog()) + { + RotatedRect[] regions = default; + var imageControl = new ImageEllipsePicker(); + var propertyDescriptor = context.PropertyDescriptor; + var singleRegion = propertyDescriptor.PropertyType == typeof(RotatedRect); + if (singleRegion) + { + if (value is RotatedRect ellipse && + ellipse.Size.Width > 0 && + ellipse.Size.Height > 0) + { + regions = new[] { ellipse }; + } + imageControl.MaxRegions = 1; + imageControl.LabelRegions = false; + } + else regions = (RotatedRect[])value; + + imageControl.Dock = DockStyle.Fill; + visualizerDialog.Text = propertyDescriptor.Name; + if (regions != null) + { + foreach (var region in regions) + { + imageControl.Regions.Add(region); + } + } + + imageControl.RegionsChanged += (sender, e) => + { + propertyDescriptor.SetValue(context.Instance, GetResult(imageControl.Regions, singleRegion)); + }; + + visualizerDialog.AddControl(imageControl); + imageControl.Canvas.MouseDoubleClick += (sender, e) => + { + if (e.Button == MouseButtons.Left && imageControl.Image != null && !imageControl.SelectedRegion.HasValue) + { + visualizerDialog.ClientSize = new System.Drawing.Size( + imageControl.Image.Width, + imageControl.Image.Height); + } + }; + + IDisposable subscription = null; + var source = GetDataSource(context, provider); + var imageSource = GetImageSource(source.Output); + imageControl.Load += delegate { subscription = imageSource.Subscribe(image => imageControl.Image = image); }; + imageControl.HandleDestroyed += delegate { subscription.Dispose(); }; + editorService.ShowDialog(visualizerDialog); + return GetResult(imageControl.Regions, singleRegion); + } + } + + return base.EditValue(context, provider, value); + } + + static object GetResult(IList regions, bool singleRegion) + { + if (regions.Count == 0) return null; + else if (singleRegion) return regions[0]; + else return regions.ToArray(); + } + } + + /// + /// Provides a user interface for visually editing elliptical regions on top of + /// the output image sequence. + /// + public class IplImageOutputEllipseEditor : IplImageEllipseEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageOutputEllipseEditor() + : base(DataSource.Output) + { + } + } + + /// + /// Provides an abstract base class for user interface editors that allow + /// visually editing a collection of elliptical regions on top of the active + /// image source. + /// + [Obsolete] + public abstract class IplImageEllipseRoiEditor : IplImageEllipseEditor + { + /// + /// Initializes a new instance of the class + /// using the specified image data source. + /// + /// + /// Specifies the source of image notifications to the property editor. + /// + protected IplImageEllipseRoiEditor(DataSource source) + : base(source) + { + } + } +} diff --git a/Bonsai.Vision.Design/IplImageInputLabeledRoiEditor.cs b/Bonsai.Vision.Design/IplImageInputLabeledRoiEditor.cs deleted file mode 100644 index d5436a46f..000000000 --- a/Bonsai.Vision.Design/IplImageInputLabeledRoiEditor.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Bonsai.Vision.Design -{ - /// - /// Provides a user interface for visually editing a collection of labeled - /// polygonal regions on top of the active image source. - /// - public class IplImageInputLabeledRoiEditor : IplImageRoiEditor - { - /// - /// Initializes a new instance of the class. - /// - public IplImageInputLabeledRoiEditor() - : base(DataSource.Input) - { - LabelRegions = true; - } - } -} diff --git a/Bonsai.Vision.Design/IplImageInputQuadrangleEditor.cs b/Bonsai.Vision.Design/IplImageInputQuadrangleEditor.cs deleted file mode 100644 index 8aade8a1e..000000000 --- a/Bonsai.Vision.Design/IplImageInputQuadrangleEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bonsai.Vision.Design -{ - /// - /// Provides a user interface for visually editing a quadrangular region on top - /// of the input image sequence. - /// - public class IplImageInputQuadrangleEditor : IplImageQuadrangleEditor - { - /// - /// Initializes a new instance of the class. - /// - public IplImageInputQuadrangleEditor() - : base(DataSource.Input) - { - } - } -} diff --git a/Bonsai.Vision.Design/IplImageInputRectangleEditor.cs b/Bonsai.Vision.Design/IplImageInputRectangleEditor.cs deleted file mode 100644 index d5bba91e7..000000000 --- a/Bonsai.Vision.Design/IplImageInputRectangleEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bonsai.Vision.Design -{ - /// - /// Provides a user interface for visually editing a rectangular region on top - /// of the input image sequence. - /// - public class IplImageInputRectangleEditor : IplImageRectangleEditor - { - /// - /// Initializes a new instance of the class. - /// - public IplImageInputRectangleEditor() - : base(DataSource.Input) - { - } - } -} diff --git a/Bonsai.Vision.Design/IplImageInputRoiEditor.cs b/Bonsai.Vision.Design/IplImageInputRoiEditor.cs deleted file mode 100644 index ee0a9445c..000000000 --- a/Bonsai.Vision.Design/IplImageInputRoiEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bonsai.Vision.Design -{ - /// - /// Provides a user interface for visually editing a collection of polygonal - /// regions on top of the input image sequence. - /// - public class IplImageInputRoiEditor : IplImageRoiEditor - { - /// - /// Initializes a new instance of the class. - /// - public IplImageInputRoiEditor() - : base(DataSource.Input) - { - } - } -} diff --git a/Bonsai.Vision.Design/IplImageOutputQuadrangleEditor.cs b/Bonsai.Vision.Design/IplImageOutputQuadrangleEditor.cs deleted file mode 100644 index 319bcc227..000000000 --- a/Bonsai.Vision.Design/IplImageOutputQuadrangleEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bonsai.Vision.Design -{ - /// - /// Provides a user interface for visually editing a quadrangular region on top - /// of the output image sequence. - /// - public class IplImageOutputQuadrangleEditor : IplImageQuadrangleEditor - { - /// - /// Initializes a new instance of the class. - /// - public IplImageOutputQuadrangleEditor() - : base(DataSource.Output) - { - } - } -} diff --git a/Bonsai.Vision.Design/IplImageOutputRectangleEditor.cs b/Bonsai.Vision.Design/IplImageOutputRectangleEditor.cs deleted file mode 100644 index 98bebfd1e..000000000 --- a/Bonsai.Vision.Design/IplImageOutputRectangleEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bonsai.Vision.Design -{ - /// - /// Provides a user interface for visually editing a rectangular region on top - /// of the output image sequence. - /// - public class IplImageOutputRectangleEditor : IplImageRectangleEditor - { - /// - /// Initializes a new instance of the class. - /// - public IplImageOutputRectangleEditor() - : base(DataSource.Output) - { - } - } -} diff --git a/Bonsai.Vision.Design/IplImageQuadrangleEditor.cs b/Bonsai.Vision.Design/IplImageQuadrangleEditor.cs index ede4b6369..66368b4e5 100644 --- a/Bonsai.Vision.Design/IplImageQuadrangleEditor.cs +++ b/Bonsai.Vision.Design/IplImageQuadrangleEditor.cs @@ -11,11 +11,19 @@ namespace Bonsai.Vision.Design { /// - /// Provides an abstract base class for user interface editors that allow - /// visually editing a quadrangular region on top of the active image source. + /// Provides a user interface for visually editing a quadrangular region on top + /// of the input image sequence. /// - public abstract class IplImageQuadrangleEditor : DataSourceTypeEditor + public class IplImageQuadrangleEditor : DataSourceTypeEditor { + /// + /// Initializes a new instance of the class. + /// + public IplImageQuadrangleEditor() + : this(DataSource.Input) + { + } + /// /// Initializes a new instance of the class /// using the specified image data source. @@ -88,4 +96,35 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide return base.EditValue(context, provider, value); } } + + /// + /// Provides a user interface for visually editing a quadrangular region on top + /// of the output image sequence. + /// + public class IplImageOutputQuadrangleEditor : IplImageQuadrangleEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageOutputQuadrangleEditor() + : base(DataSource.Output) + { + } + } + + /// + /// Provides a user interface for visually editing a quadrangular region on top + /// of the input image sequence. + /// + [Obsolete] + public class IplImageInputQuadrangleEditor : IplImageQuadrangleEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageInputQuadrangleEditor() + : base(DataSource.Input) + { + } + } } diff --git a/Bonsai.Vision.Design/IplImageRectangleEditor.cs b/Bonsai.Vision.Design/IplImageRectangleEditor.cs index a21e4e5d0..a0e9a77f8 100644 --- a/Bonsai.Vision.Design/IplImageRectangleEditor.cs +++ b/Bonsai.Vision.Design/IplImageRectangleEditor.cs @@ -11,11 +11,19 @@ namespace Bonsai.Vision.Design { /// - /// Provides an abstract base class for user interface editors that allow - /// visually editing a rectangular region on top of the active image source. + /// Provides a user interface for visually editing a rectangular region on top + /// of the input image sequence. /// - public abstract class IplImageRectangleEditor : DataSourceTypeEditor + public class IplImageRectangleEditor : DataSourceTypeEditor { + /// + /// Initializes a new instance of the class. + /// + public IplImageRectangleEditor() + : this(DataSource.Input) + { + } + /// /// Initializes a new instance of the class /// using the specified image data source. @@ -86,4 +94,35 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide return base.EditValue(context, provider, value); } } + + /// + /// Provides a user interface for visually editing a rectangular region on top + /// of the output image sequence. + /// + public class IplImageOutputRectangleEditor : IplImageRectangleEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageOutputRectangleEditor() + : base(DataSource.Output) + { + } + } + + /// + /// Provides a user interface for visually editing a rectangular region on top + /// of the input image sequence. + /// + [Obsolete] + public class IplImageInputRectangleEditor : IplImageRectangleEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageInputRectangleEditor() + : base(DataSource.Input) + { + } + } } diff --git a/Bonsai.Vision.Design/IplImageRoiEditor.cs b/Bonsai.Vision.Design/IplImageRoiEditor.cs index d4fc92b95..4c35a6f42 100644 --- a/Bonsai.Vision.Design/IplImageRoiEditor.cs +++ b/Bonsai.Vision.Design/IplImageRoiEditor.cs @@ -12,12 +12,19 @@ namespace Bonsai.Vision.Design { /// - /// Provides an abstract base class for user interface editors that allow - /// visually editing a collection of polygonal regions on top of the active - /// image source. + /// Provides a user interface for visually editing polygonal regions on top of + /// the input image sequence. /// - public abstract class IplImageRoiEditor : DataSourceTypeEditor + public class IplImageRoiEditor : DataSourceTypeEditor { + /// + /// Initializes a new instance of the class. + /// + public IplImageRoiEditor() + : this(DataSource.Input) + { + } + /// /// Initializes a new instance of the class /// using the specified image data source. @@ -116,4 +123,68 @@ static object GetResult(IList regions, bool singleRegion) else return regions.ToArray(); } } + + /// + /// Provides a user interface for visually editing polygonal regions on top of + /// the output image sequence. + /// + public class IplImageOutputRoiEditor : IplImageRoiEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageOutputRoiEditor() + : base(DataSource.Output) + { + } + } + + /// + /// Provides a user interface for visually editing labeled polygonal regions on + /// top of the input image sequence. + /// + public class IplImageLabeledRoiEditor : IplImageRoiEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageLabeledRoiEditor() + : base(DataSource.Input) + { + LabelRegions = true; + } + } + + /// + /// Provides a user interface for visually editing a collection of polygonal + /// regions on top of the input image sequence. + /// + [Obsolete] + public class IplImageInputRoiEditor : IplImageRoiEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageInputRoiEditor() + : base(DataSource.Input) + { + } + } + + /// + /// Provides a user interface for visually editing a collection of labeled + /// polygonal regions on top of the active image source. + /// + [Obsolete] + public class IplImageInputLabeledRoiEditor : IplImageRoiEditor + { + /// + /// Initializes a new instance of the class. + /// + public IplImageInputLabeledRoiEditor() + : base(DataSource.Input) + { + LabelRegions = true; + } + } } diff --git a/Bonsai.Vision/Crop.cs b/Bonsai.Vision/Crop.cs index a8a9ae787..e287842af 100644 --- a/Bonsai.Vision/Crop.cs +++ b/Bonsai.Vision/Crop.cs @@ -18,7 +18,7 @@ public class Crop : Transform /// Gets or sets a rectangle specifying the region of interest inside the image. /// [Description("Specifies the region of interest inside the image.")] - [Editor("Bonsai.Vision.Design.IplImageInputRectangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] + [Editor("Bonsai.Vision.Design.IplImageRectangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] public Rect RegionOfInterest { get; set; } /// diff --git a/Bonsai.Vision/CropPolygon.cs b/Bonsai.Vision/CropPolygon.cs index 720bb6600..e6c1ad1ca 100644 --- a/Bonsai.Vision/CropPolygon.cs +++ b/Bonsai.Vision/CropPolygon.cs @@ -34,7 +34,7 @@ internal CropPolygon(bool crop) /// Gets or sets the array of vertices specifying each polygonal region of interest. /// [Description("The array of vertices specifying each polygonal region of interest.")] - [Editor("Bonsai.Vision.Design.IplImageInputRoiEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] + [Editor("Bonsai.Vision.Design.IplImageRoiEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] public Point[][] Regions { get; set; } /// diff --git a/Bonsai.Vision/Drawing/CropCanvas.cs b/Bonsai.Vision/Drawing/CropCanvas.cs index 7b7916e92..39be5b0d8 100644 --- a/Bonsai.Vision/Drawing/CropCanvas.cs +++ b/Bonsai.Vision/Drawing/CropCanvas.cs @@ -17,7 +17,7 @@ public class CropCanvas : Transform /// Gets or sets a rectangle specifying the region of interest inside the canvas. /// [Description("Specifies the region of interest inside the canvas.")] - [Editor("Bonsai.Vision.Design.IplImageInputRectangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] + [Editor("Bonsai.Vision.Design.IplImageRectangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] public Rect RegionOfInterest { get; set; } /// diff --git a/Bonsai.Vision/GoodFeaturesToTrack.cs b/Bonsai.Vision/GoodFeaturesToTrack.cs index 388081832..1f98d603f 100644 --- a/Bonsai.Vision/GoodFeaturesToTrack.cs +++ b/Bonsai.Vision/GoodFeaturesToTrack.cs @@ -36,7 +36,7 @@ public class GoodFeaturesToTrack : Transform /// If the rectangle is empty, the whole image is used. /// [Description("The region of interest used to find image corners.")] - [Editor("Bonsai.Vision.Design.IplImageInputRectangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] + [Editor("Bonsai.Vision.Design.IplImageRectangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] public Rect RegionOfInterest { get; set; } /// diff --git a/Bonsai.Vision/RoiActivity.cs b/Bonsai.Vision/RoiActivity.cs index feae39692..d9bd7c972 100644 --- a/Bonsai.Vision/RoiActivity.cs +++ b/Bonsai.Vision/RoiActivity.cs @@ -20,7 +20,7 @@ public class RoiActivity : Transform /// activation intensity. /// [Description("The regions of interest for which to calculate activation intensity.")] - [Editor("Bonsai.Vision.Design.IplImageInputLabeledRoiEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] + [Editor("Bonsai.Vision.Design.IplImageLabeledRoiEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] public Point[][] Regions { get; set; } /// diff --git a/Bonsai.Vision/WarpPerspective.cs b/Bonsai.Vision/WarpPerspective.cs index f4b9423c7..994231b97 100644 --- a/Bonsai.Vision/WarpPerspective.cs +++ b/Bonsai.Vision/WarpPerspective.cs @@ -19,7 +19,7 @@ public class WarpPerspective : Transform /// in the input image. /// [Description("The coordinates of the four source quadrangle vertices in the input image.")] - [Editor("Bonsai.Vision.Design.IplImageInputQuadrangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] + [Editor("Bonsai.Vision.Design.IplImageQuadrangleEditor, Bonsai.Vision.Design", DesignTypes.UITypeEditor)] public Point2f[] Source { get; set; } ///