Skip to content

Commit 23163f5

Browse files
committed
update
1 parent c460b2c commit 23163f5

File tree

10 files changed

+595
-17
lines changed

10 files changed

+595
-17
lines changed

FlowCyPy/cpp/digital_processing/peak_locator/interface.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,20 +402,25 @@ PYBIND11_MODULE(peak_locator, module) {
402402
R"pbdoc(
403403
Global peak detector for 1D signals.
404404
405-
This locator identifies the maximum value over the full signal.
405+
This locator identifies a single event over the full signal, then
406+
measures its amplitude according to configurable polarity, height,
407+
and baseline conventions.
406408
407409
Width and area semantics are controlled by the supplied support
408410
object.
409411
)pbdoc"
410412
)
411413
.def(
412-
py::init<int, int, bool, bool, bool, std::shared_ptr<BaseSupport>, bool>(),
414+
py::init<int, int, bool, bool, bool, std::shared_ptr<BaseSupport>, const std::string&, const std::string&, const std::string&, bool>(),
413415
py::arg("max_number_of_peaks") = 1,
414416
py::arg("padding_value") = -1,
415417
py::arg("compute_width") = false,
416418
py::arg("compute_area") = false,
417419
py::arg("allow_negative_area") = true,
418420
py::arg("support") = std::make_shared<FullWindowSupport>(),
421+
py::arg("polarity") = "positive",
422+
py::arg("height_mode") = "raw",
423+
py::arg("baseline_mode") = "zero",
419424
py::arg("debug_mode") = false,
420425
R"pbdoc(
421426
Construct a global peak locator.
@@ -441,8 +446,40 @@ PYBIND11_MODULE(peak_locator, module) {
441446
support : BaseSupport, default=FullWindowSupport()
442447
Support object controlling how width and area are defined.
443448
449+
polarity : {"positive", "negative", "absolute"}, default="positive"
450+
Rule used to choose the sample representing the event.
451+
452+
height_mode : {"raw", "peak_to_baseline", "peak_to_peak"}, default="raw"
453+
Rule used to convert the selected event into a reported
454+
height.
455+
456+
baseline_mode : {"zero", "segment_mean", "edge_mean"}, default="zero"
457+
Baseline convention used by height modes that depend on a
458+
reference level.
459+
444460
debug_mode : bool, default=False
445461
Whether to print debug information during peak detection.
446462
)pbdoc"
463+
)
464+
.def_readonly(
465+
"polarity",
466+
&GlobalPeakLocator::polarity,
467+
R"pbdoc(
468+
Measurement polarity used to select the event sample.
469+
)pbdoc"
470+
)
471+
.def_readonly(
472+
"height_mode",
473+
&GlobalPeakLocator::height_mode,
474+
R"pbdoc(
475+
Height measurement mode used for the reported event amplitude.
476+
)pbdoc"
477+
)
478+
.def_readonly(
479+
"baseline_mode",
480+
&GlobalPeakLocator::baseline_mode,
481+
R"pbdoc(
482+
Baseline convention used by baseline-aware measurement modes.
483+
)pbdoc"
447484
);
448485
}

FlowCyPy/cpp/digital_processing/peak_locator/peak_locator.cpp

Lines changed: 183 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@
55
#include <cstdio>
66
#include <stdexcept>
77

8+
namespace {
9+
10+
bool is_supported_global_polarity(const std::string& polarity) {
11+
return polarity == "positive" ||
12+
polarity == "negative" ||
13+
polarity == "absolute";
14+
}
15+
16+
bool is_supported_global_height_mode(const std::string& height_mode) {
17+
return height_mode == "raw" ||
18+
height_mode == "peak_to_baseline" ||
19+
height_mode == "peak_to_peak";
20+
}
21+
22+
bool is_supported_global_baseline_mode(const std::string& baseline_mode) {
23+
return baseline_mode == "zero" ||
24+
baseline_mode == "segment_mean" ||
25+
baseline_mode == "edge_mean";
26+
}
27+
28+
} // namespace
29+
830

931
// -------------------- FullWindowSupport --------------------
1032
/**
@@ -1191,6 +1213,9 @@ GlobalPeakLocator::GlobalPeakLocator(
11911213
bool compute_area,
11921214
bool allow_negative_area,
11931215
std::shared_ptr<BaseSupport> support,
1216+
const std::string& polarity,
1217+
const std::string& height_mode,
1218+
const std::string& baseline_mode,
11941219
bool debug_mode
11951220
)
11961221
: BasePeakLocator(
@@ -1201,22 +1226,153 @@ GlobalPeakLocator::GlobalPeakLocator(
12011226
max_number_of_peaks,
12021227
std::move(support),
12031228
debug_mode
1204-
)
1229+
),
1230+
polarity(polarity),
1231+
height_mode(height_mode),
1232+
baseline_mode(baseline_mode)
12051233
{
1234+
this->validate_measurement_modes();
1235+
12061236
if (this->debug_mode) {
12071237
std::printf(
1208-
"[GlobalPeakLocator] Initialized | support=%s | compute_width=%d | compute_area=%d | allow_negative_area=%d | padding_value=%d | max_number_of_peaks=%d\n",
1238+
"[GlobalPeakLocator] Initialized | support=%s | compute_width=%d | compute_area=%d | allow_negative_area=%d | padding_value=%d | max_number_of_peaks=%d | polarity=%s | height_mode=%s | baseline_mode=%s\n",
12091239
this->support->get_name().c_str(),
12101240
static_cast<int>(this->compute_width),
12111241
static_cast<int>(this->compute_area),
12121242
static_cast<int>(this->allow_negative_area),
12131243
this->padding_value,
1214-
this->max_number_of_peaks
1244+
this->max_number_of_peaks,
1245+
this->polarity.c_str(),
1246+
this->height_mode.c_str(),
1247+
this->baseline_mode.c_str()
1248+
);
1249+
}
1250+
}
1251+
1252+
1253+
void GlobalPeakLocator::validate_measurement_modes() const {
1254+
if (!is_supported_global_polarity(this->polarity)) {
1255+
throw std::runtime_error(
1256+
"GlobalPeakLocator polarity must be one of: 'positive', 'negative', 'absolute'."
1257+
);
1258+
}
1259+
1260+
if (!is_supported_global_height_mode(this->height_mode)) {
1261+
throw std::runtime_error(
1262+
"GlobalPeakLocator height_mode must be one of: 'raw', 'peak_to_baseline', 'peak_to_peak'."
1263+
);
1264+
}
1265+
1266+
if (!is_supported_global_baseline_mode(this->baseline_mode)) {
1267+
throw std::runtime_error(
1268+
"GlobalPeakLocator baseline_mode must be one of: 'zero', 'segment_mean', 'edge_mean'."
12151269
);
12161270
}
12171271
}
12181272

12191273

1274+
double GlobalPeakLocator::compute_baseline(
1275+
const std::vector<double>& signal,
1276+
size_t start,
1277+
size_t end,
1278+
size_t left_boundary,
1279+
size_t right_boundary
1280+
) const {
1281+
if (this->baseline_mode == "zero") {
1282+
return 0.0;
1283+
}
1284+
1285+
if (this->baseline_mode == "segment_mean") {
1286+
double sum = 0.0;
1287+
1288+
for (size_t index = start; index < end; ++index) {
1289+
sum += signal[index];
1290+
}
1291+
1292+
return sum / static_cast<double>(end - start);
1293+
}
1294+
1295+
return 0.5 * (signal[left_boundary] + signal[right_boundary]);
1296+
}
1297+
1298+
1299+
size_t GlobalPeakLocator::find_measurement_peak_index(
1300+
const std::vector<double>& signal,
1301+
size_t start,
1302+
size_t end,
1303+
double baseline
1304+
) const {
1305+
if (this->polarity == "positive") {
1306+
return this->find_local_peak(signal.data(), start, end);
1307+
}
1308+
1309+
if (this->polarity == "negative") {
1310+
size_t best_index = start;
1311+
double minimum_value = signal[start];
1312+
1313+
for (size_t index = start + 1; index < end; ++index) {
1314+
if (signal[index] < minimum_value) {
1315+
minimum_value = signal[index];
1316+
best_index = index;
1317+
}
1318+
}
1319+
1320+
return best_index;
1321+
}
1322+
1323+
size_t best_index = start;
1324+
double maximum_excursion = std::abs(signal[start] - baseline);
1325+
1326+
for (size_t index = start + 1; index < end; ++index) {
1327+
const double excursion = std::abs(signal[index] - baseline);
1328+
1329+
if (excursion > maximum_excursion) {
1330+
maximum_excursion = excursion;
1331+
best_index = index;
1332+
}
1333+
}
1334+
1335+
return best_index;
1336+
}
1337+
1338+
1339+
double GlobalPeakLocator::compute_peak_height(
1340+
const std::vector<double>& signal,
1341+
size_t left_boundary,
1342+
size_t right_boundary,
1343+
size_t peak_index,
1344+
double baseline
1345+
) const {
1346+
const double peak_value = signal[peak_index];
1347+
1348+
if (this->height_mode == "raw") {
1349+
return peak_value;
1350+
}
1351+
1352+
if (this->height_mode == "peak_to_baseline") {
1353+
if (this->polarity == "positive") {
1354+
return peak_value - baseline;
1355+
}
1356+
1357+
if (this->polarity == "negative") {
1358+
return baseline - peak_value;
1359+
}
1360+
1361+
return std::abs(peak_value - baseline);
1362+
}
1363+
1364+
double minimum_value = signal[left_boundary];
1365+
double maximum_value = signal[left_boundary];
1366+
1367+
for (size_t index = left_boundary + 1; index <= right_boundary; ++index) {
1368+
minimum_value = std::min(minimum_value, signal[index]);
1369+
maximum_value = std::max(maximum_value, signal[index]);
1370+
}
1371+
1372+
return maximum_value - minimum_value;
1373+
}
1374+
1375+
12201376
/**
12211377
* @brief Detect a global peak using the same signal for value and support.
12221378
*
@@ -1281,13 +1437,28 @@ std::vector<PeakData> GlobalPeakLocator::locate_peaks_with_support(
12811437
right_boundary
12821438
);
12831439

1284-
const size_t value_peak_index = this->find_local_peak(
1285-
value_signal.data(),
1440+
const double baseline = this->compute_baseline(
1441+
value_signal,
1442+
0,
1443+
signal_size,
1444+
left_boundary,
1445+
right_boundary
1446+
);
1447+
1448+
const size_t value_peak_index = this->find_measurement_peak_index(
1449+
value_signal,
12861450
left_boundary,
1287-
right_boundary + 1
1451+
right_boundary + 1,
1452+
baseline
12881453
);
12891454

1290-
const double peak_value = value_signal[value_peak_index];
1455+
const double peak_value = this->compute_peak_height(
1456+
value_signal,
1457+
left_boundary,
1458+
right_boundary,
1459+
value_peak_index,
1460+
baseline
1461+
);
12911462

12921463
double width = static_cast<double>(this->padding_value);
12931464
double area = static_cast<double>(this->padding_value);
@@ -1322,13 +1493,18 @@ std::vector<PeakData> GlobalPeakLocator::locate_peaks_with_support(
13221493
"support=%s | signal_size=%zu | "
13231494
"support_peak_index=%zu | value_peak_index=%zu | "
13241495
"left_boundary=%zu | right_boundary=%zu | "
1496+
"baseline=%g | polarity=%s | height_mode=%s | baseline_mode=%s | "
13251497
"peak_value=%g | width=%g | area=%g\n",
13261498
this->support->get_name().c_str(),
13271499
signal_size,
13281500
support_peak_index,
13291501
value_peak_index,
13301502
left_boundary,
13311503
right_boundary,
1504+
baseline,
1505+
this->polarity.c_str(),
1506+
this->height_mode.c_str(),
1507+
this->baseline_mode.c_str(),
13321508
peak_value,
13331509
width,
13341510
area

FlowCyPy/cpp/digital_processing/peak_locator/peak_locator.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,13 +632,20 @@ class SlidingWindowPeakLocator : public BasePeakLocator {
632632
*/
633633
class GlobalPeakLocator : public BasePeakLocator {
634634
public:
635+
std::string polarity;
636+
std::string height_mode;
637+
std::string baseline_mode;
638+
635639
GlobalPeakLocator(
636640
int max_number_of_peaks,
637641
int padding_value,
638642
bool compute_width,
639643
bool compute_area,
640644
bool allow_negative_area,
641645
std::shared_ptr<BaseSupport> support,
646+
const std::string& polarity,
647+
const std::string& height_mode,
648+
const std::string& baseline_mode,
642649
bool debug_mode
643650
);
644651

@@ -650,4 +657,29 @@ class GlobalPeakLocator : public BasePeakLocator {
650657
const std::vector<double>& value_signal,
651658
const std::vector<double>& support_signal
652659
) const override;
660+
661+
void validate_measurement_modes() const;
662+
663+
double compute_baseline(
664+
const std::vector<double>& signal,
665+
size_t start,
666+
size_t end,
667+
size_t left_boundary,
668+
size_t right_boundary
669+
) const;
670+
671+
size_t find_measurement_peak_index(
672+
const std::vector<double>& signal,
673+
size_t start,
674+
size_t end,
675+
double baseline
676+
) const;
677+
678+
double compute_peak_height(
679+
const std::vector<double>& signal,
680+
size_t left_boundary,
681+
size_t right_boundary,
682+
size_t peak_index,
683+
double baseline
684+
) const;
653685
};

0 commit comments

Comments
 (0)