-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fe192b8
commit cc3dfe0
Showing
5 changed files
with
552 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// Copyright (c) 2024 Team Dissolve and contributors | ||
|
||
#include "math/peaks.h" | ||
#include "math/mathFunc.h" | ||
#include <algorithm> | ||
|
||
/* | ||
* Peaks | ||
*/ | ||
Peaks::Peaks(const std::vector<double> &values, const std::vector<double> &domain) : values_(values), domain_(domain) | ||
{ | ||
maxValue_ = *std::max_element(values_.begin(), values_.end()); | ||
resetConstraints(); | ||
} | ||
|
||
Peaks::Peaks(const Data1D &source) : values_(source.values()), domain_(source.xAxis()) | ||
{ | ||
maxValue_ = *std::max_element(values_.begin(), values_.end()); | ||
resetConstraints(); | ||
} | ||
|
||
// Set vertical threshold for peaks | ||
void Peaks::setThreshold(double threshold) { threshold_ = threshold; } | ||
|
||
// Set horizontal isolation radius for peaks | ||
void Peaks::setIsolation(double radius) { isolation_ = radius; } | ||
|
||
// Reset the threshold and isolation constraints | ||
void Peaks::resetConstraints() { threshold_ = -10e9, isolation_ = 0; } | ||
|
||
// Check if neighbouring points correspond to a local maximum | ||
bool Peaks::isLocalMaximum(const std::array<double, 3> &points) | ||
{ | ||
return (points[1] - points[0] > 0) && (points[2] - points[1] <= 0); | ||
} | ||
|
||
// Check if neighbouring points correspond to a local minimum | ||
bool Peaks::isLocalMinimum(const std::array<double, 3> &points) | ||
{ | ||
std::array<double, 3> invertedPoints = {-1.0 * points[0], -1.0 * points[1], -1.0 * points[2]}; | ||
return isLocalMaximum(invertedPoints); | ||
} | ||
|
||
// Check if neighbouring points correspond to an inflection point | ||
bool Peaks::isInflectionPoint(const std::array<double, 3> &points) | ||
{ | ||
return (((points[1] - points[0] > 0) || ((points[1] - points[0] < 0))) && (points[2] - points[1] == 0)) || | ||
(((points[2] - points[1] > 0) || ((points[2] - points[1] < 0))) && (points[1] - points[0] == 0)); | ||
} | ||
|
||
// Sort peaks in place by peak value, from highest to lowest | ||
void Peaks::sortPeaks(std::vector<Peaks::Peak1D> &peaks) | ||
{ | ||
std::sort(peaks.begin(), peaks.end(), [](const auto &a, const auto &b) { return a.peak > b.peak; }); | ||
} | ||
|
||
// Sort peaks in place by index, from first to last | ||
template <typename T> void Peaks::sortIndices(std::vector<T> &items) | ||
{ | ||
std::sort(items.begin(), items.end(), [](const auto &a, const auto &b) { return a.index < b.index; }); | ||
} | ||
|
||
// Sort prominences in place by prominence value, from highest to lowest | ||
void Peaks::sortProminences(std::vector<Peaks::Prominence1D> &prominences) | ||
{ | ||
std::sort(prominences.begin(), prominences.end(), [](const auto &a, const auto &b) { return a.prominence > b.prominence; }); | ||
} | ||
|
||
// Get top n peaks | ||
std::vector<Peaks::Peak1D> Peaks::top(std::size_t n, std::vector<Peaks::Peak1D> peaks) | ||
{ | ||
sortPeaks(peaks); | ||
|
||
std::vector<Peaks::Peak1D> isolatedPeaks; | ||
isolatedPeaks.reserve(peaks.size()); | ||
|
||
for (const auto &peak : peaks) | ||
{ | ||
bool withinRadius = std::any_of(isolatedPeaks.begin(), isolatedPeaks.end(), | ||
[this, &peak](const auto &p) { | ||
return (p.index != peak.index) && ((p.valueAt > peak.valueAt - isolation_) && | ||
(p.valueAt < peak.valueAt + isolation_)); | ||
}); | ||
|
||
if (!withinRadius) | ||
isolatedPeaks.push_back(peak); | ||
|
||
// Break if we exceed specified number peaks | ||
if (isolatedPeaks.size() >= n) | ||
break; | ||
} | ||
return isolatedPeaks; | ||
} | ||
|
||
// Find the peaks (local maxima) of data | ||
std::vector<Peaks::Peak1D> Peaks::find(bool heightOrder) | ||
{ | ||
std::vector<Peaks::Peak1D> peaks; | ||
peaks.reserve(values_.size()); | ||
|
||
for (int i = 1; i < values_.size() - 1; i++) | ||
{ | ||
// Check if the gradients either side show this is a local maximum | ||
std::array<double, 3> neighbours = {values_[i - 1], values_[i], values_[i + 1]}; | ||
|
||
if (values_[i] > threshold_ && isLocalMaximum(neighbours)) | ||
{ | ||
peaks.emplace_back(values_[i], domain_[i], i); | ||
} | ||
} | ||
|
||
if (isolation_ > 0) | ||
{ | ||
auto dx = abs(peaks.back().valueAt - peaks.front().valueAt); | ||
auto isolatedPeaks = top(std::round(dx / isolation_), peaks); | ||
if (!heightOrder) | ||
sortIndices<Peak1D>(isolatedPeaks); | ||
return isolatedPeaks; | ||
} | ||
return peaks; | ||
} | ||
|
||
/* | ||
* Calculate the prominence of peaks. | ||
* Prominence is defined by the height of the peak relative to a reference height. | ||
* This reference is determined by the heighest minimum of two intervals (bound by either the end of the data | ||
* or a higher data point), either side of the peak itself. | ||
*/ | ||
std::vector<Peaks::Prominence1D> Peaks::prominences(bool heightOrder) | ||
{ | ||
auto peaks = find(heightOrder); | ||
return prominences(peaks, heightOrder); | ||
} | ||
|
||
std::vector<Peaks::Prominence1D> Peaks::prominences(const std::vector<Peaks::Peak1D> &peaks, bool heightOrder) | ||
{ | ||
std::vector<Peaks::Prominence1D> prominences; | ||
prominences.reserve(peaks.size()); | ||
|
||
for (const auto &peak : peaks) | ||
{ | ||
auto iLeft = peak.index - 1, iRight = peak.index + 1; | ||
|
||
// Get interval to left of peak and find its minimum | ||
for (; iLeft > 0; iLeft--) | ||
if (values_[iLeft] >= peak.peak) | ||
break; | ||
|
||
auto heightRefLeft = *std::min_element(values_.begin() + iLeft, values_.begin() + peak.index); | ||
|
||
// Get interval to right of peak and find its minimum | ||
for (; iRight < values_.size(); iRight++) | ||
if (values_[iRight] >= peak.peak) | ||
break; | ||
|
||
auto heightRefRight = *std::min_element(values_.begin() + peak.index, values_.begin() + iRight); | ||
|
||
// Calculate reference height for prominence from the heighest of the two minima | ||
auto referenceHeight = std::max(heightRefLeft, heightRefRight); | ||
|
||
auto prominence = peak.peak - referenceHeight; | ||
prominences.emplace_back(&peak, prominence); | ||
} | ||
|
||
if (heightOrder) | ||
sortProminences(prominences); | ||
|
||
return prominences; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// Copyright (c) 2024 Team Dissolve and contributors | ||
|
||
#pragma once | ||
|
||
#include "math/data1D.h" | ||
#include "templates/vector3.h" | ||
#include <array> | ||
#include <variant> | ||
|
||
// Forward declarations | ||
|
||
/* | ||
* Peaks | ||
*/ | ||
class Peaks | ||
{ | ||
public: | ||
Peaks(const std::vector<double> &values, const std::vector<double> &domain); | ||
Peaks(const Data1D &source); | ||
~Peaks() = default; | ||
|
||
private: | ||
// Maximum y-axis value | ||
double maxValue_; | ||
// Values for which we are characterising peaks | ||
std::vector<double> values_; | ||
// Domain for which we are characterising peaks | ||
std::vector<double> domain_; | ||
// Characterise only peaks occuring above a given vertical threshold | ||
double threshold_; | ||
// Characterise only peaks occuring above a given horizontal isolation | ||
double isolation_; | ||
// Check if neighbouring points correspond to a local maximum | ||
bool isLocalMaximum(const std::array<double, 3> &points); | ||
// Check if neighbouring points correspond to a local minimum | ||
bool isLocalMinimum(const std::array<double, 3> &points); | ||
// Check if neighbouring points correspond to an inflection point | ||
bool isInflectionPoint(const std::array<double, 3> &points); | ||
|
||
public: | ||
// Container for a peak occuring in 1D data | ||
struct Peak1D | ||
{ | ||
double peak; | ||
double valueAt; | ||
int index; | ||
Peak1D(double peak, double valueAt, int index) : peak(peak), valueAt(valueAt), index(index) {} | ||
}; | ||
|
||
// Container for a prominence occuring in 1D data | ||
struct Prominence1D : public Peak1D | ||
{ | ||
double prominence; | ||
Prominence1D(const Peak1D *parent, double prominence) | ||
: Peak1D(parent->peak, parent->valueAt, parent->index), prominence(prominence) | ||
{ | ||
} | ||
}; | ||
|
||
public: | ||
// Reset the threshold and isolation constraints | ||
void resetConstraints(); | ||
// Sort peaks in place by peak value, from highest to lowest | ||
void sortPeaks(std::vector<Peak1D> &peaks); | ||
// Sort peaks in place by index, from first to last | ||
template <typename T> void sortIndices(std::vector<T> &items); | ||
// Sort prominences in place by prominence value, from highest to lowest | ||
void sortProminences(std::vector<Prominence1D> &prominences); | ||
// Sort peaks in place by index, from first to last | ||
void setThreshold(double range); | ||
// Set horizontal threshold for peaks | ||
void setIsolation(double range); | ||
// Get top n peaks | ||
std::vector<Peak1D> top(std::size_t n, std::vector<Peak1D> peaks); | ||
// Find the peaks (local maxima) of data | ||
std::vector<Peak1D> find(bool heightOrder = false); | ||
/* | ||
* Calculate the prominence of peaks. | ||
* Prominence is defined by the height of the peak relative to a reference height. | ||
* This reference is determined by the heighest minimum of two intervals (bound by either the end of the data | ||
* or a higher data point), either side of the peak itself. | ||
*/ | ||
std::vector<Prominence1D> prominences(bool heightOrder = false); | ||
std::vector<Prominence1D> prominences(const std::vector<Peak1D> &peaks, bool heightOrder = false); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.