单侧
This commit is contained in:
@@ -172,6 +172,203 @@ int BuildSineRawValue(int sample_index, int sample_rate, double frequency_hz, do
|
|||||||
return static_cast<int>(std::lround(std::sin(angle) * amplitude));
|
return static_cast<int>(std::lround(std::sin(angle) * amplitude));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int BuildCombinedSineRawValue(int sample_index,
|
||||||
|
int sample_rate,
|
||||||
|
double primary_frequency_hz,
|
||||||
|
double primary_amplitude,
|
||||||
|
double secondary_frequency_hz = 0.0,
|
||||||
|
double secondary_amplitude = 0.0)
|
||||||
|
{
|
||||||
|
return BuildSineRawValue(sample_index, sample_rate, primary_frequency_hz, primary_amplitude) +
|
||||||
|
BuildSineRawValue(sample_index, sample_rate, secondary_frequency_hz, secondary_amplitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> BuildAlgorithmDataForSingleChannel(int sample_rate,
|
||||||
|
double primary_frequency_hz,
|
||||||
|
double primary_amplitude,
|
||||||
|
double secondary_frequency_hz = 0.0,
|
||||||
|
double secondary_amplitude = 0.0)
|
||||||
|
{
|
||||||
|
std::vector<double> algorithm_data(
|
||||||
|
static_cast<std::size_t>(sample_rate * XYPARSER_FRAME_DATA_COLUMN_COUNT), 0.0);
|
||||||
|
for (int sample = 0; sample < sample_rate; ++sample) {
|
||||||
|
const std::size_t sample_offset = static_cast<std::size_t>(sample) * XYPARSER_FRAME_DATA_COLUMN_COUNT;
|
||||||
|
algorithm_data[sample_offset] = static_cast<double>(BuildCombinedSineRawValue(sample,
|
||||||
|
sample_rate,
|
||||||
|
primary_frequency_hz,
|
||||||
|
primary_amplitude,
|
||||||
|
secondary_frequency_hz,
|
||||||
|
secondary_amplitude));
|
||||||
|
}
|
||||||
|
return algorithm_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> BuildAlgorithmDataForTwoChannels(int sample_rate,
|
||||||
|
double channel0_frequency_hz,
|
||||||
|
double channel0_amplitude,
|
||||||
|
double channel1_frequency_hz,
|
||||||
|
double channel1_amplitude)
|
||||||
|
{
|
||||||
|
std::vector<double> algorithm_data(
|
||||||
|
static_cast<std::size_t>(sample_rate * XYPARSER_FRAME_DATA_COLUMN_COUNT), 0.0);
|
||||||
|
for (int sample = 0; sample < sample_rate; ++sample) {
|
||||||
|
const std::size_t sample_offset = static_cast<std::size_t>(sample) * XYPARSER_FRAME_DATA_COLUMN_COUNT;
|
||||||
|
algorithm_data[sample_offset] = static_cast<double>(
|
||||||
|
BuildSineRawValue(sample, sample_rate, channel0_frequency_hz, channel0_amplitude));
|
||||||
|
algorithm_data[sample_offset + 1] = static_cast<double>(
|
||||||
|
BuildSineRawValue(sample, sample_rate, channel1_frequency_hz, channel1_amplitude));
|
||||||
|
}
|
||||||
|
return algorithm_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<std::uint8_t>> BuildFrameSequenceForSingleChannel(std::uint8_t channel_count,
|
||||||
|
int sample_rate,
|
||||||
|
double primary_frequency_hz,
|
||||||
|
double primary_amplitude,
|
||||||
|
double secondary_frequency_hz = 0.0,
|
||||||
|
double secondary_amplitude = 0.0)
|
||||||
|
{
|
||||||
|
const int frame_count = sample_rate / static_cast<int>(XYPARSER_SAMPLES_PER_FRAME);
|
||||||
|
std::vector<std::vector<std::uint8_t>> frames;
|
||||||
|
frames.reserve(static_cast<std::size_t>(frame_count));
|
||||||
|
|
||||||
|
for (int frame_index = 0; frame_index < frame_count; ++frame_index) {
|
||||||
|
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples{};
|
||||||
|
for (int sample_offset = 0; sample_offset < static_cast<int>(XYPARSER_SAMPLES_PER_FRAME); ++sample_offset) {
|
||||||
|
const int sample_index = frame_index * static_cast<int>(XYPARSER_SAMPLES_PER_FRAME) + sample_offset;
|
||||||
|
raw_samples[static_cast<std::size_t>(sample_offset)][0] =
|
||||||
|
BuildCombinedSineRawValue(sample_index,
|
||||||
|
sample_rate,
|
||||||
|
primary_frequency_hz,
|
||||||
|
primary_amplitude,
|
||||||
|
secondary_frequency_hz,
|
||||||
|
secondary_amplitude);
|
||||||
|
}
|
||||||
|
frames.push_back(BuildFrameWithRawSamples(
|
||||||
|
channel_count, static_cast<std::uint32_t>(frame_index + 1), raw_samples));
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<std::uint8_t>> BuildFrameSequenceForTwoChannels(std::uint8_t channel_count,
|
||||||
|
int sample_rate,
|
||||||
|
double channel0_frequency_hz,
|
||||||
|
double channel0_amplitude,
|
||||||
|
double channel1_frequency_hz,
|
||||||
|
double channel1_amplitude)
|
||||||
|
{
|
||||||
|
const int frame_count = sample_rate / static_cast<int>(XYPARSER_SAMPLES_PER_FRAME);
|
||||||
|
std::vector<std::vector<std::uint8_t>> frames;
|
||||||
|
frames.reserve(static_cast<std::size_t>(frame_count));
|
||||||
|
|
||||||
|
for (int frame_index = 0; frame_index < frame_count; ++frame_index) {
|
||||||
|
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples{};
|
||||||
|
for (int sample_offset = 0; sample_offset < static_cast<int>(XYPARSER_SAMPLES_PER_FRAME); ++sample_offset) {
|
||||||
|
const int sample_index = frame_index * static_cast<int>(XYPARSER_SAMPLES_PER_FRAME) + sample_offset;
|
||||||
|
raw_samples[static_cast<std::size_t>(sample_offset)][0] =
|
||||||
|
BuildSineRawValue(sample_index, sample_rate, channel0_frequency_hz, channel0_amplitude);
|
||||||
|
raw_samples[static_cast<std::size_t>(sample_offset)][1] =
|
||||||
|
BuildSineRawValue(sample_index, sample_rate, channel1_frequency_hz, channel1_amplitude);
|
||||||
|
}
|
||||||
|
frames.push_back(BuildFrameWithRawSamples(
|
||||||
|
channel_count, static_cast<std::uint32_t>(frame_index + 1), raw_samples));
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FindFrequencyIndex(const XYParserWelchSummary& summary, double target_frequency_hz)
|
||||||
|
{
|
||||||
|
for (std::uint32_t index = 0; index < summary.frequency_count; ++index) {
|
||||||
|
if (std::abs(summary.frequencies[index] - target_frequency_hz) < 1e-9) {
|
||||||
|
return static_cast<int>(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FindPeakPsdIndex(const XYParserWelchSummary& summary, XYParserLeadChannelNumber lead)
|
||||||
|
{
|
||||||
|
if (summary.frequency_count == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t lead_index = static_cast<std::size_t>(lead);
|
||||||
|
std::uint32_t peak_index = 0;
|
||||||
|
double peak_value = summary.psd_values[lead_index][0];
|
||||||
|
for (std::uint32_t index = 1; index < summary.frequency_count; ++index) {
|
||||||
|
if (summary.psd_values[lead_index][index] > peak_value) {
|
||||||
|
peak_value = summary.psd_values[lead_index][index];
|
||||||
|
peak_index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return static_cast<int>(peak_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint16_t MeasureLeadImpedanceForMixedSine(std::uint8_t channel_count,
|
||||||
|
XYParserLeadChannelNumber lead,
|
||||||
|
int sample_rate,
|
||||||
|
double primary_frequency_hz,
|
||||||
|
double primary_amplitude,
|
||||||
|
double secondary_frequency_hz,
|
||||||
|
double secondary_amplitude);
|
||||||
|
|
||||||
|
std::uint16_t MeasureLeadImpedanceForSine(std::uint8_t channel_count,
|
||||||
|
XYParserLeadChannelNumber lead,
|
||||||
|
int sample_rate,
|
||||||
|
double frequency_hz,
|
||||||
|
double amplitude)
|
||||||
|
{
|
||||||
|
return MeasureLeadImpedanceForMixedSine(
|
||||||
|
channel_count, lead, sample_rate, frequency_hz, amplitude, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint16_t MeasureLeadImpedanceForMixedSine(std::uint8_t channel_count,
|
||||||
|
XYParserLeadChannelNumber lead,
|
||||||
|
int sample_rate,
|
||||||
|
double primary_frequency_hz,
|
||||||
|
double primary_amplitude,
|
||||||
|
double secondary_frequency_hz,
|
||||||
|
double secondary_amplitude)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(channel_count));
|
||||||
|
if (parser.get() == nullptr) {
|
||||||
|
ADD_FAILURE() << "failed to create parser";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
XYParser_SetBypassChecksum(parser.get(), 1);
|
||||||
|
XYParser_SetSampleRate(parser.get(), sample_rate);
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const auto frames = BuildFrameSequenceForSingleChannel(channel_count,
|
||||||
|
sample_rate,
|
||||||
|
primary_frequency_hz,
|
||||||
|
primary_amplitude,
|
||||||
|
secondary_frequency_hz,
|
||||||
|
secondary_amplitude);
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries{};
|
||||||
|
for (const auto& frame : frames) {
|
||||||
|
if (XYParser_Feed(parser.get(),
|
||||||
|
frame.data(),
|
||||||
|
frame.size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())) != 1) {
|
||||||
|
ADD_FAILURE() << "failed to feed frame for impedance measurement";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<XYParserImpedanceSummary, 1> impedance{};
|
||||||
|
if (XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())) != 1) {
|
||||||
|
ADD_FAILURE() << "failed to read impedance summary";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return impedance[0].impedance_values[static_cast<std::size_t>(lead)];
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/// 测试:创建解析器时拒绝不支持的通道数
|
/// 测试:创建解析器时拒绝不支持的通道数
|
||||||
@@ -236,6 +433,19 @@ TEST(XYParserApiTests, SerializeTriggerCommandMatchesWirelessEegPacket)
|
|||||||
EXPECT_TRUE(std::equal(expected.begin(), expected.end(), command.begin()));
|
EXPECT_TRUE(std::equal(expected.begin(), expected.end(), command.begin()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, SerializeTrain1TriggerCommandMatchesWirelessEegPacket)
|
||||||
|
{
|
||||||
|
std::array<std::uint8_t, 8> command{};
|
||||||
|
const int command_size = XYParser_SerializeTriggerCommand(
|
||||||
|
XYPARSER_TRIGGER_TRAIN_1,
|
||||||
|
command.data(),
|
||||||
|
command.size());
|
||||||
|
|
||||||
|
ASSERT_EQ(command_size, static_cast<int>(XYParser_GetTriggerCommandSize()));
|
||||||
|
const std::array<std::uint8_t, 3> expected = {0x00, 0x00, 0xBC};
|
||||||
|
EXPECT_TRUE(std::equal(expected.begin(), expected.end(), command.begin()));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(XYParserApiTests, SerializeTriggerCommandRejectsUnsupportedTriggerType)
|
TEST(XYParserApiTests, SerializeTriggerCommandRejectsUnsupportedTriggerType)
|
||||||
{
|
{
|
||||||
std::array<std::uint8_t, 8> command{};
|
std::array<std::uint8_t, 8> command{};
|
||||||
@@ -592,6 +802,39 @@ TEST(XYParserApiTests, ImpedanceReturnsOneResultAfterOneSecondFor8Channels)
|
|||||||
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_FP1], 0);
|
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_FP1], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ReadImpedanceConsumesQueuedResults)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(8));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetBypassChecksum(parser.get(), 1);
|
||||||
|
XYParser_SetSampleRate(parser.get(), 10);
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples1{};
|
||||||
|
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples2{};
|
||||||
|
for (int sample = 0; sample < 10; ++sample) {
|
||||||
|
const int raw_value = BuildSineRawValue(sample, 10, 2.0, 1000000.0);
|
||||||
|
if (sample < static_cast<int>(XYPARSER_SAMPLES_PER_FRAME)) {
|
||||||
|
raw_samples1[static_cast<std::size_t>(sample)][0] = raw_value;
|
||||||
|
} else {
|
||||||
|
raw_samples2[static_cast<std::size_t>(sample - XYPARSER_SAMPLES_PER_FRAME)][0] = raw_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::uint8_t> frame1 = BuildFrameWithRawSamples(8, 1U, raw_samples1);
|
||||||
|
const std::vector<std::uint8_t> frame2 = BuildFrameWithRawSamples(8, 2U, raw_samples2);
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries{};
|
||||||
|
std::array<XYParserImpedanceSummary, 1> impedance{};
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(), frame1.data(), frame1.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(), frame2.data(), frame2.size(), summaries.data(), static_cast<int>(summaries.size())), 1);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 1);
|
||||||
|
EXPECT_GT(impedance[0].impedance_values[LeadChannel_PO5], 0);
|
||||||
|
EXPECT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(XYParserApiTests, ImpedanceUsesUnifiedLeadIndexesFor64Channels)
|
TEST(XYParserApiTests, ImpedanceUsesUnifiedLeadIndexesFor64Channels)
|
||||||
{
|
{
|
||||||
ParserGuard parser(XYParser_CreateParser(64));
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
@@ -625,6 +868,212 @@ TEST(XYParserApiTests, ImpedanceUsesUnifiedLeadIndexesFor64Channels)
|
|||||||
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_FP2], 0);
|
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_FP2], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedanceSeparatesDifferentChannelLeadsFor64Channels)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetBypassChecksum(parser.get(), 1);
|
||||||
|
XYParser_SetSampleRate(parser.get(), 10);
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const auto frames = BuildFrameSequenceForTwoChannels(64, 10, 3.0, 1000000.0, 2.0, 250000.0);
|
||||||
|
ASSERT_EQ(frames.size(), 2U);
|
||||||
|
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries{};
|
||||||
|
std::array<XYParserImpedanceSummary, 1> impedance{};
|
||||||
|
for (const auto& frame : frames) {
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(),
|
||||||
|
frame.data(),
|
||||||
|
frame.size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())),
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 1);
|
||||||
|
EXPECT_GT(impedance[0].impedance_values[LeadChannel_FP1], 0);
|
||||||
|
EXPECT_GT(impedance[0].impedance_values[LeadChannel_FP2], 0);
|
||||||
|
EXPECT_GT(impedance[0].impedance_values[LeadChannel_FP1], impedance[0].impedance_values[LeadChannel_FP2]);
|
||||||
|
EXPECT_EQ(impedance[0].impedance_values[LeadChannel_PO6], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedanceDisableClearsHalfWindow)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetBypassChecksum(parser.get(), 1);
|
||||||
|
XYParser_SetSampleRate(parser.get(), 10);
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const auto full_frames = BuildFrameSequenceForSingleChannel(64, 10, 3.0, 1000000.0);
|
||||||
|
ASSERT_EQ(full_frames.size(), 2U);
|
||||||
|
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries{};
|
||||||
|
std::array<XYParserImpedanceSummary, 1> impedance{};
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(),
|
||||||
|
full_frames[0].data(),
|
||||||
|
full_frames[0].size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())),
|
||||||
|
1);
|
||||||
|
EXPECT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 0);
|
||||||
|
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 0);
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(),
|
||||||
|
full_frames[0].data(),
|
||||||
|
full_frames[0].size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())),
|
||||||
|
1);
|
||||||
|
EXPECT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(),
|
||||||
|
full_frames[1].data(),
|
||||||
|
full_frames[1].size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())),
|
||||||
|
1);
|
||||||
|
ASSERT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 1);
|
||||||
|
EXPECT_GT(impedance[0].impedance_values[LeadChannel_FP1], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedanceSampleRateChangeClearsHalfWindow)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetBypassChecksum(parser.get(), 1);
|
||||||
|
XYParser_SetSampleRate(parser.get(), 10);
|
||||||
|
XYParser_SetImpedanceDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const auto frames_10hz_window = BuildFrameSequenceForSingleChannel(64, 10, 3.0, 1000000.0);
|
||||||
|
ASSERT_EQ(frames_10hz_window.size(), 2U);
|
||||||
|
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries{};
|
||||||
|
std::array<XYParserImpedanceSummary, 1> impedance{};
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(),
|
||||||
|
frames_10hz_window[0].data(),
|
||||||
|
frames_10hz_window[0].size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())),
|
||||||
|
1);
|
||||||
|
EXPECT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 0);
|
||||||
|
|
||||||
|
XYParser_SetSampleRate(parser.get(), 20);
|
||||||
|
|
||||||
|
const auto frames_20hz_window = BuildFrameSequenceForSingleChannel(64, 20, 3.0, 1000000.0);
|
||||||
|
ASSERT_EQ(frames_20hz_window.size(), 4U);
|
||||||
|
|
||||||
|
for (const auto& frame : frames_20hz_window) {
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser.get(),
|
||||||
|
frame.data(),
|
||||||
|
frame.size(),
|
||||||
|
summaries.data(),
|
||||||
|
static_cast<int>(summaries.size())),
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadImpedance(parser.get(), impedance.data(), static_cast<int>(impedance.size())), 1);
|
||||||
|
EXPECT_EQ(impedance[0].sample_rate, 20U);
|
||||||
|
EXPECT_EQ(impedance[0].window_sample_count, 20U);
|
||||||
|
EXPECT_GT(impedance[0].impedance_values[LeadChannel_FP1], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedanceSuppresses50HzLineNoiseFor64Channels)
|
||||||
|
{
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
constexpr double kAmplitude = 1000000.0;
|
||||||
|
|
||||||
|
const std::uint16_t signal_10hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 10.0, kAmplitude);
|
||||||
|
const std::uint16_t line_noise_50hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 50.0, kAmplitude);
|
||||||
|
|
||||||
|
EXPECT_GT(signal_10hz, 0);
|
||||||
|
EXPECT_LT(line_noise_50hz, signal_10hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedanceSuppressesFrequenciesAround50HzAnd100HzFor64Channels)
|
||||||
|
{
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
constexpr double kAmplitude = 1000000.0;
|
||||||
|
|
||||||
|
const std::uint16_t signal_10hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 10.0, kAmplitude);
|
||||||
|
const std::uint16_t signal_49hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 49.0, kAmplitude);
|
||||||
|
const std::uint16_t signal_50hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 50.0, kAmplitude);
|
||||||
|
const std::uint16_t signal_51hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 51.0, kAmplitude);
|
||||||
|
const std::uint16_t signal_100hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 100.0, kAmplitude);
|
||||||
|
|
||||||
|
ASSERT_GT(signal_10hz, 0);
|
||||||
|
EXPECT_LT(signal_49hz, signal_10hz);
|
||||||
|
EXPECT_LT(signal_50hz, signal_10hz);
|
||||||
|
EXPECT_LT(signal_51hz, signal_10hz);
|
||||||
|
EXPECT_LT(signal_100hz, signal_10hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedancePreserves10HzSignalWhenMixedWithStrong50HzLineNoise)
|
||||||
|
{
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
constexpr double kSignalAmplitude = 1000000.0;
|
||||||
|
constexpr double kLineNoiseAmplitude = 3000000.0;
|
||||||
|
|
||||||
|
const std::uint16_t signal_10hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 10.0, kSignalAmplitude);
|
||||||
|
const std::uint16_t line_noise_50hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 50.0, kLineNoiseAmplitude);
|
||||||
|
const std::uint16_t mixed_signal = MeasureLeadImpedanceForMixedSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 10.0, kSignalAmplitude, 50.0, kLineNoiseAmplitude);
|
||||||
|
|
||||||
|
ASSERT_GT(signal_10hz, 0);
|
||||||
|
EXPECT_LT(line_noise_50hz, signal_10hz);
|
||||||
|
EXPECT_GT(mixed_signal, line_noise_50hz);
|
||||||
|
EXPECT_NEAR(static_cast<double>(mixed_signal),
|
||||||
|
static_cast<double>(signal_10hz),
|
||||||
|
std::max(1.0, static_cast<double>(signal_10hz) * 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ImpedancePreserves10HzSignalWhenMixedWithStrong49To51HzLineNoise)
|
||||||
|
{
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
constexpr double kSignalAmplitude = 1000000.0;
|
||||||
|
constexpr double kLineNoiseAmplitude = 3000000.0;
|
||||||
|
|
||||||
|
const std::uint16_t signal_10hz = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, 10.0, kSignalAmplitude);
|
||||||
|
ASSERT_GT(signal_10hz, 0);
|
||||||
|
|
||||||
|
for (double line_noise_hz : {49.0, 50.0, 51.0}) {
|
||||||
|
const std::uint16_t pure_line_noise = MeasureLeadImpedanceForSine(
|
||||||
|
64, LeadChannel_FP1, kSampleRate, line_noise_hz, kLineNoiseAmplitude);
|
||||||
|
const std::uint16_t mixed_signal = MeasureLeadImpedanceForMixedSine(
|
||||||
|
64,
|
||||||
|
LeadChannel_FP1,
|
||||||
|
kSampleRate,
|
||||||
|
10.0,
|
||||||
|
kSignalAmplitude,
|
||||||
|
line_noise_hz,
|
||||||
|
kLineNoiseAmplitude);
|
||||||
|
|
||||||
|
EXPECT_LT(pure_line_noise, signal_10hz) << "line noise hz=" << line_noise_hz;
|
||||||
|
EXPECT_GT(mixed_signal, pure_line_noise) << "line noise hz=" << line_noise_hz;
|
||||||
|
EXPECT_NEAR(static_cast<double>(mixed_signal),
|
||||||
|
static_cast<double>(signal_10hz),
|
||||||
|
std::max(1.0, static_cast<double>(signal_10hz) * 0.2))
|
||||||
|
<< "line noise hz=" << line_noise_hz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(XYParserApiTests, WelchReturnsOneResultAfterOneSecondFromAlgorithmData)
|
TEST(XYParserApiTests, WelchReturnsOneResultAfterOneSecondFromAlgorithmData)
|
||||||
{
|
{
|
||||||
ParserGuard parser(XYParser_CreateParser(64));
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
@@ -660,6 +1109,447 @@ TEST(XYParserApiTests, WelchReturnsOneResultAfterOneSecondFromAlgorithmData)
|
|||||||
EXPECT_GT(welch[0].band_values[0][LeadChannel_FP1], 0.0);
|
EXPECT_GT(welch[0].band_values[0][LeadChannel_FP1], 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchDetects50HzPeakFromAlgorithmData)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 50.0, 1000000.0, 10.0, 100000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
|
||||||
|
const int frequency_10hz_index = FindFrequencyIndex(welch[0], 10.0);
|
||||||
|
const int frequency_50hz_index = FindFrequencyIndex(welch[0], 50.0);
|
||||||
|
const int peak_index = FindPeakPsdIndex(welch[0], LeadChannel_FP1);
|
||||||
|
|
||||||
|
ASSERT_GE(frequency_10hz_index, 0);
|
||||||
|
ASSERT_GE(frequency_50hz_index, 0);
|
||||||
|
ASSERT_GE(peak_index, 0);
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(welch[0].frequencies[static_cast<std::size_t>(peak_index)], 50.0);
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_50hz_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)]);
|
||||||
|
EXPECT_GT(welch[0].band_values[4][LeadChannel_FP1], welch[0].band_values[2][LeadChannel_FP1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchDetects49HzPeakFromAlgorithmData)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 49.0, 1000000.0, 10.0, 100000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
|
||||||
|
const int frequency_10hz_index = FindFrequencyIndex(welch[0], 10.0);
|
||||||
|
const int frequency_49hz_index = FindFrequencyIndex(welch[0], 49.0);
|
||||||
|
const int peak_index = FindPeakPsdIndex(welch[0], LeadChannel_FP1);
|
||||||
|
|
||||||
|
ASSERT_GE(frequency_10hz_index, 0);
|
||||||
|
ASSERT_GE(frequency_49hz_index, 0);
|
||||||
|
ASSERT_GE(peak_index, 0);
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(welch[0].frequencies[static_cast<std::size_t>(peak_index)], 49.0);
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_49hz_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)]);
|
||||||
|
EXPECT_GT(welch[0].band_values[4][LeadChannel_FP1], welch[0].band_values[2][LeadChannel_FP1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchReportedFrequenciesDoNotExceed50Hz)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 51.0, 1000000.0, 100.0, 500000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
ASSERT_GT(welch[0].frequency_count, 0U);
|
||||||
|
|
||||||
|
EXPECT_LE(welch[0].frequencies[welch[0].frequency_count - 1], 50.0);
|
||||||
|
EXPECT_EQ(FindFrequencyIndex(welch[0], 51.0), -1);
|
||||||
|
EXPECT_EQ(FindFrequencyIndex(welch[0], 100.0), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchDetects49And50HzPeakWhenMixedWith10Hz)
|
||||||
|
{
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
|
||||||
|
for (double line_noise_hz : {49.0, 50.0}) {
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, line_noise_hz, 1000000.0, 10.0, 100000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
|
||||||
|
const int frequency_10hz_index = FindFrequencyIndex(welch[0], 10.0);
|
||||||
|
const int line_noise_index = FindFrequencyIndex(welch[0], line_noise_hz);
|
||||||
|
const int peak_index = FindPeakPsdIndex(welch[0], LeadChannel_FP1);
|
||||||
|
|
||||||
|
ASSERT_GE(frequency_10hz_index, 0) << "line noise hz=" << line_noise_hz;
|
||||||
|
ASSERT_GE(line_noise_index, 0) << "line noise hz=" << line_noise_hz;
|
||||||
|
ASSERT_GE(peak_index, 0) << "line noise hz=" << line_noise_hz;
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(welch[0].frequencies[static_cast<std::size_t>(peak_index)], line_noise_hz)
|
||||||
|
<< "line noise hz=" << line_noise_hz;
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(line_noise_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)])
|
||||||
|
<< "line noise hz=" << line_noise_hz;
|
||||||
|
EXPECT_GT(welch[0].band_values[4][LeadChannel_FP1], welch[0].band_values[2][LeadChannel_FP1])
|
||||||
|
<< "line noise hz=" << line_noise_hz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchDoesNotReport51HzButGammaBandRisesWhenMixedWith10Hz)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 51.0, 1000000.0, 10.0, 100000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
|
||||||
|
const int frequency_10hz_index = FindFrequencyIndex(welch[0], 10.0);
|
||||||
|
const int frequency_50hz_index = FindFrequencyIndex(welch[0], 50.0);
|
||||||
|
|
||||||
|
ASSERT_GE(frequency_10hz_index, 0);
|
||||||
|
ASSERT_GE(frequency_50hz_index, 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(FindFrequencyIndex(welch[0], 51.0), -1);
|
||||||
|
EXPECT_GT(welch[0].band_values[4][LeadChannel_FP1], welch[0].band_values[2][LeadChannel_FP1]);
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_50hz_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchDoesNotReport100HzOrPolluteLowFrequencyBands)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 100.0, 1000000.0, 10.0, 100000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
|
||||||
|
const int frequency_10hz_index = FindFrequencyIndex(welch[0], 10.0);
|
||||||
|
const int frequency_50hz_index = FindFrequencyIndex(welch[0], 50.0);
|
||||||
|
|
||||||
|
ASSERT_GE(frequency_10hz_index, 0);
|
||||||
|
ASSERT_GE(frequency_50hz_index, 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(FindFrequencyIndex(welch[0], 100.0), -1);
|
||||||
|
EXPECT_LE(welch[0].frequencies[welch[0].frequency_count - 1], 50.0);
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)], 0.0);
|
||||||
|
EXPECT_LT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_50hz_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)]);
|
||||||
|
EXPECT_GT(welch[0].band_values[2][LeadChannel_FP1], welch[0].band_values[0][LeadChannel_FP1]);
|
||||||
|
EXPECT_GT(welch[0].band_values[2][LeadChannel_FP1], welch[0].band_values[1][LeadChannel_FP1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchResetClearsHalfWindowAfterDisableEnable)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetSampleRate(parser.get(), 10);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> full_data = BuildAlgorithmDataForSingleChannel(10, 2.0, 1000000.0);
|
||||||
|
const std::size_t half_row_count =
|
||||||
|
static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME) * XYPARSER_FRAME_DATA_COLUMN_COUNT;
|
||||||
|
const std::vector<double> first_half(full_data.begin(), full_data.begin() + static_cast<std::ptrdiff_t>(half_row_count));
|
||||||
|
const std::vector<double> second_half(full_data.begin() + static_cast<std::ptrdiff_t>(half_row_count), full_data.end());
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(first_half.data()),
|
||||||
|
first_half.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
EXPECT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 0);
|
||||||
|
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 0);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(first_half.data()),
|
||||||
|
first_half.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
EXPECT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(second_half.data()),
|
||||||
|
second_half.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
EXPECT_EQ(welch[0].ok, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchSeparatesDifferentChannelPeaks)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForTwoChannels(
|
||||||
|
kSampleRate, 10.0, 1000000.0, 20.0, 1000000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
ASSERT_EQ(welch[0].ok, 1);
|
||||||
|
|
||||||
|
const int fp1_peak_index = FindPeakPsdIndex(welch[0], LeadChannel_FP1);
|
||||||
|
const int fp2_peak_index = FindPeakPsdIndex(welch[0], LeadChannel_FP2);
|
||||||
|
const int frequency_10hz_index = FindFrequencyIndex(welch[0], 10.0);
|
||||||
|
const int frequency_20hz_index = FindFrequencyIndex(welch[0], 20.0);
|
||||||
|
|
||||||
|
ASSERT_GE(fp1_peak_index, 0);
|
||||||
|
ASSERT_GE(fp2_peak_index, 0);
|
||||||
|
ASSERT_GE(frequency_10hz_index, 0);
|
||||||
|
ASSERT_GE(frequency_20hz_index, 0);
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(welch[0].frequencies[static_cast<std::size_t>(fp1_peak_index)], 10.0);
|
||||||
|
EXPECT_DOUBLE_EQ(welch[0].frequencies[static_cast<std::size_t>(fp2_peak_index)], 20.0);
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_10hz_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(frequency_20hz_index)]);
|
||||||
|
EXPECT_GT(welch[0].psd_values[LeadChannel_FP2][static_cast<std::size_t>(frequency_20hz_index)],
|
||||||
|
welch[0].psd_values[LeadChannel_FP2][static_cast<std::size_t>(frequency_10hz_index)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, WelchPsdIncreasesWithSignalAmplitude)
|
||||||
|
{
|
||||||
|
ParserGuard low_amplitude_parser(XYParser_CreateParser(64));
|
||||||
|
ParserGuard high_amplitude_parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(low_amplitude_parser.get(), nullptr);
|
||||||
|
ASSERT_NE(high_amplitude_parser.get(), nullptr);
|
||||||
|
|
||||||
|
constexpr int kSampleRate = 200;
|
||||||
|
XYParser_SetSampleRate(low_amplitude_parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(low_amplitude_parser.get(), 1);
|
||||||
|
XYParser_SetSampleRate(high_amplitude_parser.get(), kSampleRate);
|
||||||
|
XYParser_SetWelchDetection(high_amplitude_parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> low_amplitude_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 10.0, 100000.0);
|
||||||
|
const std::vector<double> high_amplitude_data = BuildAlgorithmDataForSingleChannel(
|
||||||
|
kSampleRate, 10.0, 1000000.0);
|
||||||
|
|
||||||
|
std::array<XYParserWelchSummary, 1> low_welch{};
|
||||||
|
std::array<XYParserWelchSummary, 1> high_welch{};
|
||||||
|
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
low_amplitude_parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(low_amplitude_data.data()),
|
||||||
|
low_amplitude_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
high_amplitude_parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(high_amplitude_data.data()),
|
||||||
|
high_amplitude_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(low_amplitude_parser.get(), low_welch.data(), static_cast<int>(low_welch.size())), 1);
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(high_amplitude_parser.get(), high_welch.data(), static_cast<int>(high_welch.size())), 1);
|
||||||
|
|
||||||
|
const int low_frequency_10hz_index = FindFrequencyIndex(low_welch[0], 10.0);
|
||||||
|
const int high_frequency_10hz_index = FindFrequencyIndex(high_welch[0], 10.0);
|
||||||
|
ASSERT_GE(low_frequency_10hz_index, 0);
|
||||||
|
ASSERT_GE(high_frequency_10hz_index, 0);
|
||||||
|
|
||||||
|
EXPECT_GT(high_welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(high_frequency_10hz_index)],
|
||||||
|
low_welch[0].psd_values[LeadChannel_FP1][static_cast<std::size_t>(low_frequency_10hz_index)]);
|
||||||
|
EXPECT_GT(high_welch[0].band_values[2][LeadChannel_FP1],
|
||||||
|
low_welch[0].band_values[2][LeadChannel_FP1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, ReadWelchConsumesQueuedResults)
|
||||||
|
{
|
||||||
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetSampleRate(parser.get(), 10);
|
||||||
|
XYParser_SetWelchDetection(parser.get(), 1);
|
||||||
|
|
||||||
|
const std::vector<double> algorithm_data = BuildAlgorithmDataForSingleChannel(10, 2.0, 1000000.0);
|
||||||
|
std::array<XYParserWelchSummary, 1> welch{};
|
||||||
|
|
||||||
|
EXPECT_EQ(XYParser_FeedAlgorithmData(
|
||||||
|
parser.get(),
|
||||||
|
reinterpret_cast<const std::uint8_t*>(algorithm_data.data()),
|
||||||
|
algorithm_data.size() * sizeof(double),
|
||||||
|
nullptr,
|
||||||
|
0),
|
||||||
|
0);
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 1);
|
||||||
|
EXPECT_EQ(welch[0].ok, 1);
|
||||||
|
EXPECT_EQ(XYParser_ReadWelch(parser.get(), welch.data(), static_cast<int>(welch.size())), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XYParserApiTests, Convert8ChTo64ChAlgorithmDataMatchesDirect64Ch)
|
||||||
|
{
|
||||||
|
ParserGuard parser8(XYParser_CreateParser(8));
|
||||||
|
ParserGuard parser64(XYParser_CreateParser(64));
|
||||||
|
ASSERT_NE(parser8.get(), nullptr);
|
||||||
|
ASSERT_NE(parser64.get(), nullptr);
|
||||||
|
|
||||||
|
XYParser_SetBypassChecksum(parser8.get(), 1);
|
||||||
|
XYParser_SetBypassChecksum(parser64.get(), 1);
|
||||||
|
|
||||||
|
std::array<XYParserLeadChannelNumber, 8> leads8{};
|
||||||
|
std::array<XYParserLeadChannelNumber, 64> leads64{};
|
||||||
|
ASSERT_EQ(XYParser_GetLeadMap(8, leads8.data(), static_cast<int>(leads8.size())), 8);
|
||||||
|
ASSERT_EQ(XYParser_GetLeadMap(64, leads64.data(), static_cast<int>(leads64.size())), 64);
|
||||||
|
|
||||||
|
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples8{};
|
||||||
|
std::array<std::array<int, XYPARSER_MAX_CHANNELS>, XYPARSER_SAMPLES_PER_FRAME> raw_samples64{};
|
||||||
|
for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
|
||||||
|
for (std::size_t channel_index = 0; channel_index < leads8.size(); ++channel_index) {
|
||||||
|
const int raw_value = static_cast<int>((sample_index + 1) * 1000 + static_cast<int>(channel_index) * 100);
|
||||||
|
raw_samples8[sample_index][channel_index] = raw_value;
|
||||||
|
|
||||||
|
for (std::size_t raw64_index = 0; raw64_index < leads64.size(); ++raw64_index) {
|
||||||
|
if (leads64[raw64_index] == leads8[channel_index]) {
|
||||||
|
raw_samples64[sample_index][raw64_index] = raw_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::uint8_t> frame8 = BuildFrameWithRawSamples(8, 1U, raw_samples8);
|
||||||
|
const std::vector<std::uint8_t> frame64 = BuildFrameWithRawSamples(64, 1U, raw_samples64);
|
||||||
|
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries8{};
|
||||||
|
std::array<XYParserFrameSummary, 1> summaries64{};
|
||||||
|
std::array<XYParserFrameSummary, 1> converted64{};
|
||||||
|
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser8.get(), frame8.data(), frame8.size(), summaries8.data(), static_cast<int>(summaries8.size())), 1);
|
||||||
|
ASSERT_EQ(XYParser_Feed(parser64.get(), frame64.data(), frame64.size(), summaries64.data(), static_cast<int>(summaries64.size())), 1);
|
||||||
|
ASSERT_EQ(XYParser_Convert8ChFramesTo64Ch(summaries8.data(), 1, converted64.data(), 1), 1);
|
||||||
|
|
||||||
|
std::vector<double> converted_algorithm_data(XYPARSER_FRAME_ALGORITHM_VALUE_COUNT, 0.0);
|
||||||
|
std::vector<double> direct_algorithm_data(XYPARSER_FRAME_ALGORITHM_VALUE_COUNT, 0.0);
|
||||||
|
ASSERT_EQ(XYParser_ConvertSampleFramesToAlgorithmData(&converted64[0], converted_algorithm_data.data()), 1);
|
||||||
|
ASSERT_EQ(XYParser_ConvertSampleFramesToAlgorithmData(&summaries64[0], direct_algorithm_data.data()), 1);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < converted_algorithm_data.size(); ++i) {
|
||||||
|
EXPECT_DOUBLE_EQ(converted_algorithm_data[i], direct_algorithm_data[i]) << "index=" << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(XYParserApiTests, WelchFrameFeedDoesNotProduceResults)
|
TEST(XYParserApiTests, WelchFrameFeedDoesNotProduceResults)
|
||||||
{
|
{
|
||||||
ParserGuard parser(XYParser_CreateParser(64));
|
ParserGuard parser(XYParser_CreateParser(64));
|
||||||
|
|||||||
Reference in New Issue
Block a user