Public Access
123 lines
3.9 KiB
C#
123 lines
3.9 KiB
C#
using MeetingAssistant;
|
|
using MeetingAssistant.Speakers;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace MeetingAssistant.Tests;
|
|
|
|
public sealed class ResemblyzerVoiceClusterMatcherTests
|
|
{
|
|
[Fact]
|
|
public void CoherentSimilarClusterSelectsUnambiguousIdentity()
|
|
{
|
|
var matcher = CreateMatcher();
|
|
var query = Enumerable.Range(0, 5)
|
|
.Select(index => UnitVector(0, secondaryDimension: index + 2, secondaryValue: 0.05f))
|
|
.ToList();
|
|
var candidates = new[]
|
|
{
|
|
new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0), UnitVector(0, 10, 0.02f)]),
|
|
new ResemblyzerVoiceVectorCandidate(77, [UnitVector(1), UnitVector(1, 11, 0.02f)])
|
|
};
|
|
|
|
var result = matcher.Match(query, candidates);
|
|
|
|
Assert.True(result.Accepted);
|
|
Assert.Equal(42, result.IdentityId);
|
|
Assert.True(result.Cohesion >= 0.99);
|
|
Assert.True(result.BestSimilarity >= 0.99);
|
|
Assert.True(result.RunnerUpSimilarity < 0.1);
|
|
}
|
|
|
|
[Fact]
|
|
public void FourVectorsDoNotTriggerAutomaticMatching()
|
|
{
|
|
var result = CreateMatcher().Match(
|
|
Enumerable.Repeat(UnitVector(0), 4).ToList(),
|
|
[new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])]);
|
|
|
|
Assert.False(result.Accepted);
|
|
Assert.Contains("4/5", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncoherentClusterIsRejectedBeforeIdentityScoring()
|
|
{
|
|
var result = CreateMatcher().Match(
|
|
Enumerable.Range(0, 5).Select(index => UnitVector(index)).ToList(),
|
|
[new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])]);
|
|
|
|
Assert.False(result.Accepted);
|
|
Assert.Contains("cohesion", result.Reason);
|
|
Assert.Null(result.BestSimilarity);
|
|
}
|
|
|
|
[Fact]
|
|
public void SimilarityBelowThresholdIsRejected()
|
|
{
|
|
var result = CreateMatcher().Match(
|
|
Enumerable.Repeat(UnitVector(0), 5).ToList(),
|
|
[new ResemblyzerVoiceVectorCandidate(42, [UnitVector(1)])]);
|
|
|
|
Assert.False(result.Accepted);
|
|
Assert.Contains("best similarity", result.Reason);
|
|
Assert.Equal(0, result.BestSimilarity);
|
|
}
|
|
|
|
[Fact]
|
|
public void SimilarCandidatesWithinMarginAreRejected()
|
|
{
|
|
var result = CreateMatcher().Match(
|
|
Enumerable.Repeat(UnitVector(0), 5).ToList(),
|
|
[
|
|
new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)]),
|
|
new ResemblyzerVoiceVectorCandidate(77, [UnitVector(0, 1, 0.01f)])
|
|
]);
|
|
|
|
Assert.False(result.Accepted);
|
|
Assert.Contains("margin", result.Reason);
|
|
Assert.NotNull(result.RunnerUpSimilarity);
|
|
}
|
|
|
|
[Fact]
|
|
public void InvalidStoredCandidateDoesNotAbortScoringOtherIdentities()
|
|
{
|
|
var result = CreateMatcher().Match(
|
|
Enumerable.Repeat(UnitVector(0), 5).ToList(),
|
|
[
|
|
new ResemblyzerVoiceVectorCandidate(13, [new float[256]]),
|
|
new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])
|
|
]);
|
|
|
|
Assert.True(result.Accepted);
|
|
Assert.Equal(42, result.IdentityId);
|
|
}
|
|
|
|
private static ResemblyzerVoiceClusterMatcher CreateMatcher()
|
|
{
|
|
return new ResemblyzerVoiceClusterMatcher(
|
|
new ResemblyzerSpeakerRecognitionOptions
|
|
{
|
|
RequiredVectorsPerSpeaker = 5,
|
|
MinimumClusterCohesion = 0.75,
|
|
MinimumIdentitySimilarity = 0.75,
|
|
MinimumSimilarityMargin = 0.05
|
|
},
|
|
NullLogger<ResemblyzerVoiceClusterMatcher>.Instance);
|
|
}
|
|
|
|
private static float[] UnitVector(
|
|
int primaryDimension,
|
|
int? secondaryDimension = null,
|
|
float secondaryValue = 0)
|
|
{
|
|
var vector = new float[256];
|
|
vector[primaryDimension] = 1;
|
|
if (secondaryDimension is { } dimension)
|
|
{
|
|
vector[dimension] = secondaryValue;
|
|
}
|
|
|
|
return vector;
|
|
}
|
|
}
|