check point
This commit is contained in:
121
VisionBuilder.UI.Statistics/README.md
Normal file
121
VisionBuilder.UI.Statistics/README.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# VisionBuilder.UI.Statistics
|
||||
|
||||
A .NET 8 library for collecting and managing image processing statistics in VisionBuilder applications. This library tracks session data, defect counts, and exports statistics to CSV files for analysis.
|
||||
|
||||
## Overview
|
||||
|
||||
The VisionBuilder.UI.Statistics library provides comprehensive statistics collection for computer vision and image processing workflows. It automatically tracks session events, image processing results, and defect data, organizing them into structured CSV reports.
|
||||
|
||||
## Features
|
||||
|
||||
- **Session Tracking**: Monitors recognition sessions from start to end
|
||||
- **Defect Recording**: Tracks image processing errors and defect types
|
||||
- **CSV Export**: Generates detailed statistics files for analysis
|
||||
- **Configurable Paths**: Flexible output directory configuration with variable substitution
|
||||
- **Monthly Reports**: Aggregates data into monthly statistics files
|
||||
- **Thread-Safe**: Uses concurrent collections for reliable multi-threaded operation
|
||||
|
||||
## Core Components
|
||||
|
||||
### VisionBuilderStatistics
|
||||
|
||||
The main statistics collection class that implements `IInitializable`.
|
||||
|
||||
**Key Responsibilities:**
|
||||
- Monitors recognition control events
|
||||
- Records session start/end times
|
||||
- Tracks defects and error types
|
||||
- Exports data to CSV files
|
||||
|
||||
**Event Handlers:**
|
||||
- `SessionStarted`: Initializes new statistics session
|
||||
- `ImageProcessed`: Records defect data for failed images
|
||||
- `SessionEnded`: Finalizes and exports session statistics
|
||||
|
||||
### VisionBuilderStatisticsSettings
|
||||
|
||||
Configuration class for statistics collection settings.
|
||||
|
||||
**Properties:**
|
||||
- `CameraName`: Identifier for the camera/station
|
||||
- `PathTemplate`: Configurable output directory with variable substitution
|
||||
|
||||
**Supported Variables:**
|
||||
- `$RECIPE_NAME` - Current recipe name
|
||||
- `$DEFECT_NAME` - Defect type name
|
||||
- `$CAMERA_NAME` - Camera identifier
|
||||
- Time variables: `$HOUR`, `$MINUTE`, `$SECOND`, `$MS`
|
||||
- Date variables: `$DAY`, `$MONTH`, `$YEAR`
|
||||
- Session start variables: `$SS_HOUR`, `$SS_MINUTE`, `$SS_SECOND`, `$SS_DAY`, `$SS_MONTH`, `$SS_YEAR`
|
||||
|
||||
## Output Files
|
||||
|
||||
### statistics_by_events.csv
|
||||
Contains detailed event-by-event statistics with columns:
|
||||
- Start Date, Start Time
|
||||
- End Date, End Time
|
||||
- Error Time
|
||||
- Product (Recipe)
|
||||
- Error (Defect Type)
|
||||
|
||||
### statistics_month.csv
|
||||
Monthly aggregated statistics for trend analysis.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Serilog** (4.3.0): Logging framework
|
||||
- **Inspectron.Statistics**: Core statistics functionality
|
||||
- **VisionBuilder.UI.Common**: Common UI components and interfaces
|
||||
|
||||
## Usage
|
||||
|
||||
```csharp
|
||||
// Configure settings
|
||||
var settings = new VisionBuilderStatisticsSettings("Camera1")
|
||||
{
|
||||
PathTemplate = @"..\Data\Statistics\$SS_MONTH $SS_YEAR\$RECIPE_NAME\"
|
||||
};
|
||||
|
||||
// Initialize statistics collector
|
||||
var statistics = new VisionBuilderStatistics(
|
||||
settings,
|
||||
recognitionControl,
|
||||
loadingService
|
||||
);
|
||||
|
||||
// Initialize to process any pending statistics
|
||||
statistics.Initialize();
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The library follows an event-driven architecture:
|
||||
|
||||
1. **Session Start**: Creates temporary CSV file and begins tracking
|
||||
2. **Image Processing**: Records defects as they occur during processing
|
||||
3. **Session End**: Finalizes temporary file and merges into main statistics
|
||||
4. **Cleanup**: Removes temporary files and updates monthly aggregations
|
||||
|
||||
## File Management
|
||||
|
||||
- Temporary files use GUID naming: `tmp_{guid}.csv`
|
||||
- Files are sorted by creation time during consolidation
|
||||
- Automatic directory creation for output paths
|
||||
- Safe cleanup of temporary files after processing
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Comprehensive exception handling with Serilog logging
|
||||
- Graceful handling of invalid date/time data
|
||||
- Safe file operations with proper resource disposal
|
||||
- Loading service integration for user feedback
|
||||
|
||||
## Thread Safety
|
||||
|
||||
Uses `ConcurrentDictionary<string, int>` for defect counting to ensure thread-safe operations in multi-threaded image processing environments.
|
||||
|
||||
## Target Framework
|
||||
|
||||
- .NET 8.0
|
||||
- Nullable reference types enabled
|
||||
- Implicit usings enabled
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\framework\Inspectron.Statistics\Inspectron.Statistics.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
145
VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs
Normal file
145
VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Inspectron.Statistics;
|
||||
using Lindt.Colorballs.Duo.Utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using Ninject;
|
||||
using Serilog;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.Commands;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace VisionBuilder.UI.Statistics
|
||||
{
|
||||
public class VisionBuilderStatistics:IInitializable
|
||||
{
|
||||
private CsvStatistics _csvStatistics;
|
||||
private readonly VisionBuilderStatisticsSettings _settings;
|
||||
private string _filePath;
|
||||
private readonly IRecognitionControl _recognitionControl;
|
||||
private readonly ILoadingService _loadingService;
|
||||
private DateTime _startTime;
|
||||
private string _currentRecipe;
|
||||
private string _fileName;
|
||||
private string _directoryPath;
|
||||
|
||||
private const string BY_EVENTS_FILE_NAME = "statistics_by_events.csv";
|
||||
private const string MONTHLY_FILE_NAME = "statistics_month.csv";
|
||||
private const string EMPTY_DATE = "";
|
||||
private const string EMPTY_TIME = "";
|
||||
|
||||
|
||||
public VisionBuilderStatistics(VisionBuilderStatisticsSettings settings, IRecognitionControl recognitionControl, ILoadingService loadingService)
|
||||
{
|
||||
_settings = settings;
|
||||
_recognitionControl = recognitionControl;
|
||||
_loadingService = loadingService;
|
||||
|
||||
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
|
||||
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
|
||||
_recognitionControl.SessionEnded += _recognitionControl_SessionEnded;
|
||||
}
|
||||
|
||||
private void _recognitionControl_SessionEnded(SessionEndedEvent obj)
|
||||
{
|
||||
_loadingService.StartLoading("Writing statistics for " + _settings.CameraName);
|
||||
try
|
||||
{
|
||||
_csvStatistics.WriteLine([_startTime.ToString("d"), _startTime.ToString("T"), DateTime.Now.ToString("d"), DateTime.Now.ToString("T"), "", "", ""]);
|
||||
_csvStatistics.Dispose();
|
||||
WriteFinalStatistics();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loadingService.StopLoading("Writing statistics for " + _settings.CameraName);
|
||||
}
|
||||
}
|
||||
|
||||
private void _recognitionControl_ImageProcessed(ImageProcessedEvent obj)
|
||||
{
|
||||
if (obj.HasError)
|
||||
{
|
||||
RecordBadImage(obj.ErrorNames[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void _recognitionControl_SessionStarted(SessionStartedEvent obj)
|
||||
{
|
||||
_startTime = DateTime.Now;
|
||||
_currentRecipe = obj.RecipeName;
|
||||
_fileName = "tmp_"+Guid.NewGuid().ToString()+".csv";
|
||||
_directoryPath = PathSettingsUtils.ConvertVariables(_settings.PathTemplate, DateTime.Now, DateTime.Now,
|
||||
obj.RecipeName, cameraName: _settings.CameraName);
|
||||
_filePath = Path.Combine(_directoryPath, _fileName);
|
||||
var headers = new[] { _settings.StartDateColumnName, _settings.StartTimeColumnName, _settings.EndDateColumnName, _settings.EndTimeColumnName, _settings.ErrorTimeColumnName, _settings.ProductColumnName, _settings.ErrorColumnName };
|
||||
if (!Directory.Exists(Path.GetDirectoryName(_filePath)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_filePath));
|
||||
}
|
||||
_csvStatistics = new CsvStatistics(_filePath, headers);
|
||||
_csvStatistics.WriteLine([_startTime.ToString("d"), _startTime.ToString("T")]); // session started
|
||||
_defects = new ConcurrentDictionary<string, int>();
|
||||
}
|
||||
|
||||
ConcurrentDictionary<string, int> _defects = new();
|
||||
private void RecordBadImage(string defectName)
|
||||
{
|
||||
|
||||
_csvStatistics.WriteLine(
|
||||
|
||||
[_startTime.ToString("d"), _startTime.ToString("T"), EMPTY_DATE, EMPTY_TIME, DateTime.Now.ToString("T"), _currentRecipe, defectName]
|
||||
|
||||
);
|
||||
|
||||
_defects.AddOrUpdate(defectName, 1, (key, oldValue) => oldValue + 1);
|
||||
}
|
||||
|
||||
private void WriteMonthStatistics(string filename)
|
||||
{
|
||||
// read second line to get the start time
|
||||
var lines = File.ReadAllLines(filename);
|
||||
var lastLine = lines[^1];
|
||||
if (lines.Length < 2) return; // no data to write
|
||||
var startDate = DateTime.Parse(lastLine.Split(',')[0]);
|
||||
var startTime = DateTime.Parse(lastLine.Split(',')[1]);
|
||||
var endDate = DateTime.Parse(lastLine.Split(',')[2]);
|
||||
var endTime = DateTime.Parse(lastLine.Split(',')[3]);
|
||||
|
||||
// all must be non-empty
|
||||
if (startDate == DateTime.MinValue || startTime == DateTime.MinValue || endDate == DateTime.MinValue || endTime == DateTime.MinValue)
|
||||
{
|
||||
Log.Error("Invalid date or time in statistics file: " + filename);
|
||||
throw new InvalidOperationException("Invalid date or time in statistics file: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteFinalStatistics()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tmpFiles = Directory.GetFiles(_directoryPath, "tmp_*");
|
||||
// order by creation time
|
||||
Array.Sort(tmpFiles, (x, y) => File.GetCreationTime(x).CompareTo(File.GetCreationTime(y)));
|
||||
foreach (var file in tmpFiles)
|
||||
{
|
||||
var finalFileName = Path.Combine(_directoryPath, BY_EVENTS_FILE_NAME);
|
||||
var lines = File.ReadAllLines(file);
|
||||
File.AppendAllLines(finalFileName, lines.Skip(1)); // skip header
|
||||
WriteMonthStatistics(file);
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Error writing statistics for " + _settings.CameraName+ e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
WriteFinalStatistics();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Inspectron.Settings;
|
||||
using Inspectron.Settings.Attributes;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace VisionBuilder.UI.Statistics;
|
||||
|
||||
public class VisionBuilderStatisticsSettings: ISettings
|
||||
{
|
||||
public string CameraName { get; }
|
||||
|
||||
public VisionBuilderStatisticsSettings(string cameraName)
|
||||
{
|
||||
CameraName = cameraName;
|
||||
}
|
||||
|
||||
[SettingDescription(
|
||||
@"
|
||||
Specifies directory, in which MaxBad and MaxGood are applied.
|
||||
You can use the following system variables:
|
||||
|
||||
$RECIPE_NAME - name of the current recipe
|
||||
$DEFECT_NAME - name of the current defect(for bad images)
|
||||
$CAMERA_NAME - name of the camera
|
||||
|
||||
$HOUR - hour of the image
|
||||
$MINUTE - minute of the image
|
||||
$SECOND - second of the image
|
||||
$MS - milliseconds of the image
|
||||
|
||||
$DAY - day of the image
|
||||
$MONTH - month of the image
|
||||
$YEAR - year of the image
|
||||
|
||||
$SS_HOUR - hour of the session start
|
||||
$SS_MINUTE - minute of the session start
|
||||
$SS_SECOND - second of the session start
|
||||
|
||||
$SS_DAY - day of the session start
|
||||
$SS_MONTH - month of the session start
|
||||
$SS_YEAR - year of the session start
|
||||
"
|
||||
)]
|
||||
public string PathTemplate { get; set; }= @$"..\Data\Statistics\$SS_MONTH $SS_YEAR\$RECIPE_NAME\";
|
||||
|
||||
public string StartDateColumnName { get; set; } = "Start Date";
|
||||
public string StartTimeColumnName { get; set; } = "Start time";
|
||||
public string EndDateColumnName { get; set; } = "End Date";
|
||||
public string EndTimeColumnName { get; set; } = "End time";
|
||||
public string ErrorTimeColumnName { get; set; } = "Error time";
|
||||
public string ProductColumnName { get; set; } = "Product";
|
||||
public string ErrorColumnName { get; set; } = "Error";
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this,()=>this.PathTemplate,CameraName+"/"+"Statistics",nameof(PathTemplate));
|
||||
settings.RegisterSimple(this,()=>this.StartDateColumnName,CameraName+"/"+"Statistics",nameof(StartDateColumnName));
|
||||
settings.RegisterSimple(this,()=>this.StartTimeColumnName,CameraName+"/"+"Statistics",nameof(StartTimeColumnName));
|
||||
settings.RegisterSimple(this,()=>this.EndDateColumnName,CameraName+"/"+"Statistics",nameof(EndDateColumnName));
|
||||
settings.RegisterSimple(this,()=>this.EndTimeColumnName,CameraName+"/"+"Statistics",nameof(EndTimeColumnName));
|
||||
settings.RegisterSimple(this,()=>this.ErrorTimeColumnName,CameraName+"/"+"Statistics",nameof(ErrorTimeColumnName));
|
||||
settings.RegisterSimple(this,()=>this.ProductColumnName,CameraName+"/"+"Statistics",nameof(ProductColumnName));
|
||||
settings.RegisterSimple(this,()=>this.ErrorColumnName,CameraName+"/"+"Statistics",nameof(ErrorColumnName));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user