Skip to content

Commit 685093f

Browse files
committed
Get single frames and tests
1 parent 5b9ab79 commit 685093f

File tree

18 files changed

+262
-155
lines changed

18 files changed

+262
-155
lines changed

.editorconfig

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# Keep comments human: explain the “why”, not just the “what”.
21
root = true
32

43
[*.{cs,csx}]
@@ -20,11 +19,18 @@ dotnet_analyzer_diagnostic.category-Design.severity = warning
2019
dotnet_analyzer_diagnostic.category-Reliability.severity = warning
2120
dotnet_analyzer_diagnostic.category-Security.severity = warning
2221

23-
# Naming (basic, we’ll refine later)
22+
# Naming
2423
dotnet_naming_rule.private_fields_underscore.symbols = private_fields
2524
dotnet_naming_rule.private_fields_underscore.style = underscore_prefix
2625
dotnet_naming_rule.private_fields_underscore.severity = suggestion
2726
dotnet_naming_symbols.private_fields.applicable_kinds = field
2827
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
2928
dotnet_naming_style.underscore_prefix.required_prefix = _
3029
dotnet_naming_style.underscore_prefix.capitalization = camel_case
30+
31+
# StyleCop rules (mostly disabled for initial simplicity)
32+
dotnet_diagnostic.SA1633.severity = none # file header
33+
dotnet_diagnostic.SA1200.severity = none # usings inside namespace
34+
dotnet_diagnostic.SA1518.severity = suggestion # newline at EOF
35+
dotnet_diagnostic.SA1502.severity = suggestion # one-line elements
36+
dotnet_diagnostic.SA1503.severity = suggestion # omitted braces

Directory.Packages.props

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
<!-- Test & tooling -->
1212
<PackageVersion Include="xunit" Version="2.9.0" />
1313
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
14-
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
1514
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
16-
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
1715

1816
<!-- App & Vision deps -->
1917
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
namespace ScreenAutomation.Core.Abstractions;
2-
3-
public interface IDetectionPipeline
1+
namespace ScreenAutomation.Core.Abstractions
42
{
5-
IEnumerable<object> Run(ImageBuffer image);
3+
using System.Collections.Generic;
4+
5+
public interface IDetectionPipeline<TAspect>
6+
{
7+
IReadOnlyList<Detection<TAspect>> Run(ImageBuffer image);
8+
}
69
}
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
namespace ScreenAutomation.Core.Abstractions;
2-
3-
public interface IScreenCapture
1+
namespace ScreenAutomation.Core.Abstractions
42
{
5-
ImageBuffer Capture();
3+
using System.Collections.Generic;
4+
5+
// Orchestrates one capture → run the detection pipeline → return detections.
6+
public interface ICaptureRunner<TAspect>
7+
{
8+
IReadOnlyList<Detection<TAspect>> RunOnce();
9+
}
610
}
Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,50 @@
1-
using OpenCvSharp;
2-
using ScreenAutomation.Core;
3-
using ScreenAutomation.Core.Abstractions;
4-
using ScreenAutomation.Vision.Extensions;
5-
6-
namespace ScreenAutomation.Vision.Detectors;
7-
8-
9-
// Extremely simple template matcher using OpenCV's MatchTemplate (CCoeffNormed).
10-
// This is a baseline to prove the pipeline & test rig; we will harden it over time.
11-
public sealed class TemplateMatchDetector : IAspectDetector<BoundingBox>, IDisposable
1+
namespace ScreenAutomation.Vision.Detectors
122
{
13-
private readonly Mat _template; // CV_8UC1
14-
private readonly int _tw;
15-
private readonly int _th;
16-
17-
/// <param name="template">Grayscale template pixels (Width*Height == Pixels.Length).</param>
18-
public TemplateMatchDetector(ImageBuffer template)
3+
using System;
4+
using OpenCvSharp;
5+
using ScreenAutomation.Core;
6+
using ScreenAutomation.Core.Abstractions;
7+
using ScreenAutomation.Vision.Extensions;
8+
9+
// Minimal template matcher (CCoeffNormed). Baseline slice to exercise the pipeline.
10+
public sealed class TemplateMatchDetector : IAspectDetector<BoundingBox>, IDisposable
1911
{
20-
_template = template.ToGrayMat(); // wraps the array; we keep it alive in this class
21-
_tw = template.Width;
22-
_th = template.Height;
23-
}
24-
25-
26-
// Perform normalized template matching against the given scene (grayscale).
27-
public Detection<BoundingBox> Detect(ImageBuffer image)
28-
{
29-
using var scene = image.ToGrayMat();
30-
31-
// result map size = (scene - template + 1)
32-
var resW = scene.Cols - _tw + 1;
33-
var resH = scene.Rows - _th + 1;
12+
private readonly Mat templateMat; // CV_8UC1
13+
private readonly int tw;
14+
private readonly int th;
3415

35-
if (resW <= 0 || resH <= 0)
16+
public TemplateMatchDetector(ImageBuffer template)
3617
{
37-
// Template can't fit → trivially "no match"
38-
var degenerate = new BoundingBox(0, 0, _tw, _th);
39-
return new Detection<BoundingBox>(degenerate, degenerate, 0f);
18+
this.templateMat = template.ToGrayMat();
19+
this.tw = template.Width;
20+
this.th = template.Height;
4021
}
4122

42-
using var result = new Mat(resH, resW, MatType.CV_32FC1);
23+
public Detection<BoundingBox> Detect(ImageBuffer image)
24+
{
25+
using var scene = image.ToGrayMat();
4326

44-
// Use CCoeffNormed which tends to be robust for simple luminance changes.
45-
Cv2.MatchTemplate(scene, _template, result, TemplateMatchModes.CCoeffNormed);
27+
var resW = scene.Cols - this.tw + 1;
28+
var resH = scene.Rows - this.th + 1;
4629

47-
Cv2.MinMaxLoc(result, out _, out double maxVal, out _, out Point maxLoc);
30+
if (resW <= 0 || resH <= 0)
31+
{
32+
var box = new BoundingBox(0, 0, this.tw, this.th);
33+
return new Detection<BoundingBox>(box, box, 0f);
34+
}
4835

49-
var box = new BoundingBox(maxLoc.X, maxLoc.Y, _tw, _th);
50-
return new Detection<BoundingBox>(box, box, (float)maxVal);
51-
}
36+
using var result = new Mat(resH, resW, MatType.CV_32FC1);
5237

53-
public void Dispose()
54-
{
55-
_template?.Dispose();
38+
Cv2.MatchTemplate(scene, this.templateMat, result, TemplateMatchModes.CCoeffNormed);
39+
Cv2.MinMaxLoc(result, out _, out double maxVal, out _, out Point maxLoc);
40+
41+
var detected = new BoundingBox(maxLoc.X, maxLoc.Y, this.tw, this.th);
42+
return new Detection<BoundingBox>(detected, detected, (float)maxVal);
43+
}
44+
45+
public void Dispose()
46+
{
47+
this.templateMat?.Dispose();
48+
}
5649
}
5750
}
Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,41 @@
1-
using OpenCvSharp;
2-
using ScreenAutomation.Core;
3-
4-
namespace ScreenAutomation.Vision.Extensions;
5-
6-
7-
// Helpers to convert between Core's ImageBuffer (library-agnostic) and OpenCvSharp Mat.
8-
// We keep things grayscale-only for now to keep the minimal slice simple.
9-
public static class ImageBufferExtensions
1+
namespace ScreenAutomation.Vision.Extensions
102
{
11-
// Wrap an ImageBuffer (grayscale) in an OpenCV Mat (CV_8UC1). No deep copy is performed.
3+
using System;
4+
using System.Runtime.InteropServices;
5+
using OpenCvSharp;
6+
using ScreenAutomation.Core;
127

13-
public static Mat ToGrayMat(this ImageBuffer buffer)
8+
// Converts between Core's ImageBuffer and OpenCvSharp Mat (grayscale only for now).
9+
public static class ImageBufferExtensions
1410
{
15-
// Mat uses the provided memory; ensure the buffer stays alive for the Mat's lifetime.
16-
return new Mat(buffer.Height, buffer.Width, MatType.CV_8UC1, buffer.Pixels);
17-
}
11+
// Copy ImageBuffer (8-bit grayscale) into a new Mat (CV_8UC1).
12+
public static Mat ToGrayMat(this ImageBuffer buffer)
13+
{
14+
if (buffer.Pixels is null)
15+
throw new ArgumentNullException(nameof(buffer.Pixels));
1816

17+
var mat = new Mat(buffer.Height, buffer.Width, MatType.CV_8UC1);
18+
var length = buffer.Pixels.Length;
1919

20-
// Copy an OpenCV grayscale Mat (CV_8UC1) into an ImageBuffer (managed).
21-
public static ImageBuffer FromGrayMat(Mat mat)
22-
{
23-
if (mat.Type() != MatType.CV_8UC1)
24-
{
25-
throw new ArgumentException($"Expected CV_8UC1 mat, got {mat.Type()}");
20+
// Copy managed bytes -> unmanaged Mat buffer
21+
Marshal.Copy(buffer.Pixels, 0, mat.Data, length);
22+
return mat;
2623
}
2724

28-
var w = mat.Cols;
29-
var h = mat.Rows;
30-
var total = w * h;
31-
var pixels = new byte[total];
25+
// Copy an OpenCV grayscale Mat (CV_8UC1) into an ImageBuffer (managed).
26+
public static ImageBuffer FromGrayMat(Mat mat)
27+
{
28+
if (mat.Type() != MatType.CV_8UC1)
29+
throw new ArgumentException($"Expected CV_8UC1 mat, got {mat.Type()}");
3230

33-
// GetArray performs a copy from Mat into managed memory.
34-
mat.GetArray(0, 0, pixels);
31+
var width = mat.Cols;
32+
var height = mat.Rows;
33+
var pixels = new byte[width * height];
3534

36-
return new ImageBuffer(w, h, pixels);
35+
// Copy unmanaged Mat buffer -> managed bytes
36+
Marshal.Copy(mat.Data, pixels, 0, pixels.Length);
37+
38+
return new ImageBuffer(width, height, pixels);
39+
}
3740
}
3841
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace ScreenAutomation.Vision.Pipeline
2+
{
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using ScreenAutomation.Core;
6+
using ScreenAutomation.Core.Abstractions;
7+
8+
// Simple pipeline: runs each detector and returns all detections.
9+
public sealed class DetectionPipeline<TAspect> : IDetectionPipeline<TAspect>
10+
{
11+
private readonly IReadOnlyList<IAspectDetector<TAspect>> detectors;
12+
13+
public DetectionPipeline(IEnumerable<IAspectDetector<TAspect>> detectors)
14+
{
15+
this.detectors = detectors?.ToList() ?? new List<IAspectDetector<TAspect>>();
16+
}
17+
18+
public IReadOnlyList<Detection<TAspect>> Run(ImageBuffer image)
19+
{
20+
var results = new List<Detection<TAspect>>(this.detectors.Count);
21+
foreach (var d in this.detectors)
22+
{
23+
results.Add(d.Detect(image));
24+
}
25+
26+
return results;
27+
}
28+
}
29+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
namespace ScreenAutomation.Vision { internal static class Placeholder { } }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace ScreenAutomation.Vision.Runtime
2+
{
3+
using System.Collections.Generic;
4+
using ScreenAutomation.Core;
5+
using ScreenAutomation.Core.Abstractions;
6+
7+
// Single-responsibility: grab one frame from IScreenCapture and pass it to the pipeline.
8+
public sealed class CaptureRunner<TAspect> : ICaptureRunner<TAspect>
9+
{
10+
private readonly IScreenCapture capture;
11+
private readonly IDetectionPipeline<TAspect> pipeline;
12+
13+
public CaptureRunner(IScreenCapture capture, IDetectionPipeline<TAspect> pipeline)
14+
{
15+
this.capture = capture;
16+
this.pipeline = pipeline;
17+
}
18+
19+
public IReadOnlyList<Detection<TAspect>> RunOnce()
20+
{
21+
var frame = this.capture.Capture();
22+
return this.pipeline.Run(frame);
23+
}
24+
}
25+
}

src/ScreenAutomation.Vision/_Placeholder.cs

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)