265 lines
6.4 KiB
C#
265 lines
6.4 KiB
C#
using Ocr.Report.Models;
|
|
using ReportGeneration.Abstract.Model;
|
|
using ReportGeneration.Interface;
|
|
|
|
namespace Ocr.Report;
|
|
|
|
public class ReportGenerator : IDisposable
|
|
{
|
|
private IDocumentGenerator Document { get; }
|
|
|
|
private ICollection<ImageStats> Images { get; }
|
|
|
|
public ReportGenerator(
|
|
IDocumentGenerator document,
|
|
IEnumerable<ImageStats> data
|
|
)
|
|
{
|
|
Images = data.ToArray();
|
|
|
|
Document = document;
|
|
document.Open();
|
|
}
|
|
|
|
public ReportGenerator(
|
|
string title,
|
|
IDocumentGenerator document,
|
|
IEnumerable<ImageStats> data
|
|
) : this(document, data) => AddTitle(title);
|
|
|
|
#region Writing
|
|
|
|
public ReportGenerator AddTitle(string text)
|
|
{
|
|
Document.AppendHeading(1, text);
|
|
return this;
|
|
}
|
|
|
|
public ReportGenerator AddProcessorStats(string title)
|
|
{
|
|
Document.AppendHeading(2, title);
|
|
|
|
var processors = new Dictionary<string, ICollection<ImageStats>>();
|
|
foreach (var image in Images)
|
|
{
|
|
foreach (var processor in image.Processors)
|
|
{
|
|
if (processors.TryGetValue(processor.Name, out var images))
|
|
{
|
|
images.Add(image);
|
|
}
|
|
else
|
|
{
|
|
processors.Add(processor.Name, new List<ImageStats> { image });
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var (processor, images) in processors)
|
|
{
|
|
var ordered = images
|
|
.Select(i => (Stats: i, Distance: i
|
|
.Processors
|
|
.Where(p => p.Name.Equals(processor))
|
|
.Select(p => p.Distance)
|
|
.Average()
|
|
))
|
|
.OrderBy(i => i.Distance)
|
|
.ToArray();
|
|
|
|
Document
|
|
.AppendHeading(3, processor)
|
|
.AppendTable(2, table =>
|
|
{
|
|
table.AppendHeader(new[] { "Image", "Preview", "Distance" });
|
|
|
|
foreach (var (stats, distance) in ordered)
|
|
{
|
|
var imgPath = Path.Combine("results", $"{processor}.00.{stats.ImageName}.png");
|
|
|
|
table.AppendRow(new[]
|
|
{
|
|
$"<a href=\"#{stats.ImageName}\">{stats.ImageName}</a>",
|
|
Document.FormatImage(imgPath, new Bounds(0, 150)),
|
|
distance.ToString("F2")
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public ReportGenerator AddImageStatsFull(string title)
|
|
{
|
|
Document.AppendHeading(2, title);
|
|
|
|
foreach (var stat in Images)
|
|
{
|
|
Document
|
|
.AppendHeading(3, stat.ImageName)
|
|
.AppendParagraph(
|
|
Document.FormatImage(Path.Combine("img", stat.ImageName), new Bounds(0, 350))
|
|
)
|
|
.AppendTable(
|
|
stat.Reference.Count + 5,
|
|
table =>
|
|
{
|
|
table.AppendHeader(stat
|
|
.Reference
|
|
.Prepend("Image")
|
|
.Prepend("Perfect matches")
|
|
.Prepend("CER (avg)")
|
|
.Prepend("WER")
|
|
.Prepend("Elapsed")
|
|
.Prepend("Processor")
|
|
);
|
|
|
|
var processors = stat.Processors
|
|
.OrderBy(s => s.Distance)
|
|
.ThenBy(s => s.ProcessingTime);
|
|
|
|
foreach (var processor in processors)
|
|
{
|
|
var imgPath = Path.Combine("results", $"{processor.Name}.00.{stat.ImageName}.png");
|
|
|
|
table.AppendRow(processor.Words
|
|
.Select(s => s.ToString() ?? string.Empty)
|
|
.Prepend(Document.FormatImage(imgPath, new Bounds(0, 150)))
|
|
.Prepend($"{processor.Words.Count(w => w.Distance == 0)} / {processor.Words.Count}")
|
|
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
|
|
.Prepend($"{processor.Distance * 100:F1}%")
|
|
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
|
|
.Prepend(processor.Name)
|
|
);
|
|
}
|
|
})
|
|
.AppendParagraph(
|
|
$"Comparison data generated based on {stat.Reference.Count} tagged words."
|
|
);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public ReportGenerator AddComparison(
|
|
string title,
|
|
Func<IEnumerable<double>, (double, double)> evaluationFunc
|
|
)
|
|
{
|
|
var lookup = Images
|
|
.SelectMany(s => s.Processors)
|
|
.ToLookup(p => p.Name);
|
|
|
|
Document.AppendHeading(2, title);
|
|
|
|
var byWer = lookup
|
|
.Select(g =>
|
|
{
|
|
var (value, deviation) = evaluationFunc(g.Select(p => p.Distance * 100));
|
|
return (
|
|
Name: g.Key,
|
|
Value: value,
|
|
Deviation: deviation
|
|
);
|
|
})
|
|
.OrderBy(g => g.Value);
|
|
Document.AppendHeading(3, "WER");
|
|
AppendComparison(("Error", "%"), byWer);
|
|
|
|
var byCer = lookup
|
|
.Select(g =>
|
|
{
|
|
var (value, deviation) =
|
|
evaluationFunc(g.SelectMany(p => p.Words, (_, word) => word.Distance));
|
|
return (
|
|
Name: g.Key,
|
|
Value: value,
|
|
Deviation: deviation
|
|
);
|
|
})
|
|
.OrderBy(g => g.Value);
|
|
Document.AppendHeading(3, "CER");
|
|
AppendComparison(("Changes", string.Empty), byCer);
|
|
|
|
var byTime = lookup
|
|
.Select(g =>
|
|
{
|
|
var (value, deviation) = evaluationFunc(g.Select(p => p.ProcessingTime * 1000));
|
|
return (
|
|
Name: g.Key,
|
|
Value: value,
|
|
Deviation: deviation
|
|
);
|
|
})
|
|
.OrderBy(g => g.Value);
|
|
Document.AppendHeading(3, "Time");
|
|
AppendComparison(("Time", "ms"), byTime);
|
|
|
|
return this;
|
|
}
|
|
|
|
private void AppendComparison(
|
|
(string name, string unit)? valueInfo,
|
|
IEnumerable<(string, double, double)> values
|
|
)
|
|
{
|
|
const int context = 5;
|
|
|
|
var tValues = values.ToArray();
|
|
var tContext = Math.Min(tValues.Length / 2, context);
|
|
|
|
Document.AppendTable(3, table =>
|
|
{
|
|
table
|
|
.AppendHeader(new[]
|
|
{
|
|
"Processor",
|
|
valueInfo?.name ?? "Value",
|
|
"Deviation"
|
|
})
|
|
.AppendRows(tValues
|
|
.Take(tContext)
|
|
.Select(MakeRow))
|
|
.AppendRow("...")
|
|
.AppendRows(
|
|
tValues
|
|
.TakeLast(tContext)
|
|
.Select(MakeRow)
|
|
);
|
|
|
|
return;
|
|
|
|
string[] MakeRow((string, double, double) v) =>
|
|
new[]
|
|
{
|
|
v.Item1,
|
|
v.Item2.ToString("F2") + valueInfo?.unit,
|
|
v.Item3.ToString("F2") + valueInfo?.unit
|
|
};
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
Document.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
}
|