ref #39 smoothing is refactored

KDE smoothing algorithmisch mal geschrieben, jetzt noch testen
This commit is contained in:
toni
2017-12-06 17:37:14 +01:00
parent 95a5c8f34f
commit 74981c6a45
14 changed files with 1303 additions and 127 deletions

127
math/boxkde/BoxGaus.h Normal file
View File

@@ -0,0 +1,127 @@
#pragma once
#include <cmath>
#include <vector>
#include "BoxSizes.h"
template <class T>
struct BoxGaus
{
void boxfilter(std::vector<T>& input, size_t w, size_t h, unsigned filterSize)
{
assertMsg((filterSize % 2) == 1, "filterSize must be odd");
unsigned radius = filterSize / 2;
std::vector<T> buffer(input.size());
boxBlur(input, buffer, w, h, radius);
boxBlur(buffer, input, w, h, radius);
}
void approxGaus(Image2D<T>& input, T sigmaX, T sigmaY, unsigned nFilt)
{
approxGaus(input.data(), input.width, input.height, sigmaX, sigmaY, nFilt);
}
void approxGaus(std::vector<T>& input, size_t w, size_t h, T sigmaX, T sigmaY, unsigned nFilt)
{
BoxSizes<T> bsX(sigmaX, nFilt);
BoxSizes<T> bsY(sigmaY, nFilt);
std::vector<T> buffer(input.size());
assertMsg((2 * bsX.wl + 1 < w) && (2 * bsX.wl + 1 < h), "Box-Filter size in X direction is too big");
assertMsg((2 * bsX.wu + 1 < w) && (2 * bsX.wu + 1 < h), "Box-Filter size in X direction is too big");
assertMsg((2 * bsY.wl + 1 < w) && (2 * bsY.wl + 1 < h), "Box-Filter size in Y direction is too big");
assertMsg((2 * bsY.wu + 1 < w) && (2 * bsY.wu + 1 < h), "Box-Filter size in Y direction is too big");
// if equal, we can save some cond's inside the loop
if (bsX.m == bsY.m)
{
const size_t m = bsX.m;
for (size_t i = 0; i < m; i++)
{
boxBlur(input, buffer, w, h, bsY.wl);
boxBlur(buffer, input, w, h, bsX.wl);
}
for (size_t i = 0; i < nFilt - m; i++)
{
boxBlur(input, buffer, w, h, bsY.wu);
boxBlur(buffer, input, w, h, bsX.wu);
}
}
else
{
for (size_t i = 0; i < nFilt; i++)
{
boxBlur(input, buffer, w, h, (i < bsY.m ? bsY.wl : bsY.wu) );
boxBlur(buffer, input, w, h, (i < bsX.m ? bsX.wl : bsX.wu));
}
}
}
private:
void boxBlur(const std::vector<T> &src, std::vector<T> &dst, size_t w, size_t h, size_t r)
{
T iarr = (T)1.0 / (r + r + 1);
for (size_t i = 0; i < w; i++)
{
// Init indexes
size_t ti = i;
size_t li = ti;
size_t ri = ti + r*w;
// Init values
T fv = src[ti]; // first values
T lv = src[ti + w*(h - 1)]; // last values
T val = fv * (r + 1); // overhang over image border
for (size_t j = 0; j < r; j++)
{
val += src[ti + j*w]; // col sum
}
// <20>berhangbereich links vom Bild
for (size_t j = 0; j <= r; j++)
{
val += src[ri] - fv;
dst[j + i*w] = val * iarr;
ri += w;
ti += w;
}
// Bildbereich
for (size_t j = r + 1; j < h - r; j++)
{
val += src[ri] - src[li];
dst[j + i*w] = val * iarr;
li += w;
ri += w;
ti += w;
}
// <20>berhangbereich rechts vom Bild
for (size_t j = h - r; j < h; j++)
{
val += lv - src[li];
dst[j + i*w] = val * iarr;
li += w;
ti += w;
}
}
}
};

85
math/boxkde/BoxSizes.h Normal file
View File

@@ -0,0 +1,85 @@
#pragma once
#include "DataStructures.h"
template<typename T>
struct BoxSizes
{
static_assert(std::is_floating_point<T>::value, "This class only works with floats.");
T sigma, sigmaActual;
T wIdeal, mIdeal;
unsigned n, m, wl, wu;
BoxSizes(T sigma, unsigned n)
: sigma(sigma), n(n)
{
assertMsg(sigma >= 0.8, "Sigma values below about 0.8 cannot be represented");
wIdeal = sqrt(12 * sigma*sigma / n + 1); // Ideal averaging filter width
// wl is first odd valued integer less than wIdeal
wl = (unsigned)floor(wIdeal);
if (wl % 2 == 0)
wl = wl - 1;
// wu is the next odd value > wl
wu = wl + 2;
// Compute m.Refer to the tech note for derivation of this formula
mIdeal = (12 * sigma*sigma - n*wl*wl - 4 * n*wl - 3 * n) / (-4 * wl - 4);
m = (unsigned)round(mIdeal);
assertMsg(!(m > n || m < 0), "calculation of m has failed");
// Compute actual sigma that will be achieved
sigmaActual = sqrt((m*wl*wl + (n - m)*wu*wu - n) / (T)12.0);
}
};
template<typename T>
struct ExBoxSizes
{
static_assert(std::is_floating_point<T>::value, "This class only works with floats.");
T sigma, sigma_actual;
unsigned n, r;
T alpha, c, c1, c2;
T r_f;
// Special case for h == 1
ExBoxSizes(T sigma, unsigned n)
: sigma(sigma), n(n)
{
T var = sigma*sigma;
r_f = 0.5*sqrt((12 * var) / n + 1) - T(0.5);
r = (unsigned)std::floor(r_f);
alpha = (2 * r + 1) * ( (r*r + r - 3*var/n) / (6 * (var/n - (r+1)*(r+1))) );
c1 = alpha / (2*alpha + 2*r + 1);
c2 = (1 - alpha) / (2 * alpha + 2 * r + 1);
c = c1 + c2;
}
ExBoxSizes(T sigma, unsigned d, T h)
: sigma(sigma), n(d)
{
T v = sigma*sigma;
r_f = sqrt((12 * v) / n + 1)/(2*h) - T(0.5); // (7)
r = (unsigned)std::floor(std::max(T(0), r_f));
alpha = (2 * r + 1) * (((r*r + r) - (v*3*h)/(d*h*h*h)) / (6 * ( v/(d*h*h) - (r+1)*(r+1)) )); // (8) (14)
c1 = alpha / (h*(2 * r + 2 * alpha + 1)); // (8) (13)
c2 = (1 - alpha) / (h*(2 * r + 2 * alpha + 1)); // (8) (13)
c = c1 + c2;
T lambda = h*(2*r + 1 + 2*alpha); // (8)
T variance_actual = (d*h*h*h) / (3 * lambda) * (2*r*r*r + 3*r*r + r + 6*alpha*(r+1)*(r+1)); // (14)
sigma_actual = sqrt(variance_actual);
}
};

View File

@@ -0,0 +1,115 @@
#pragma once
#include <cassert>
#include <limits>
#include <memory>
#include <sstream>
#include <vector>
template <class T>
struct BoundingBox
{
static_assert(std::is_arithmetic<T>::value, "This class only works with floats or integers.");
T MinX, MaxX, MinY, MaxY;
BoundingBox(T MinX = std::numeric_limits<T>::max(),
T MaxX = std::numeric_limits<T>::lowest(),
T MinY = std::numeric_limits<T>::max(),
T MaxY = std::numeric_limits<T>::lowest())
: MinX(MinX), MaxX(MaxX), MinY(MinY), MaxY(MaxY)
{ }
T width () const { return MaxX - MinX; }
T heigth() const { return MaxY - MinY; }
T area () const { return width()*heigth(); }
bool isInside(T x, T y) const { return (x >= MinX && x <= MaxX) && (y >= MinY && y <= MaxY); }
// Expands the size of the BB if the given values are extreme
void expand(T x, T y)
{
if (x < MinX) MinX = x;
if (x > MaxX) MaxX = x;
if (y < MinY) MinY = y;
if (y > MaxY) MaxY = y;
}
// Enlarges the BB in both direction along an axis.
void inflate(T szX, T szY)
{
MinX -= szX;
MinY -= szY;
MaxX += szX;
MaxY += szY;
}
};
template <class T>
struct Point2D
{
static_assert(std::is_arithmetic<T>::value, "This class only works with floats and integers.");
T X, Y;
Point2D(T x = 0, T y = 0)
: X(x), Y(y)
{ }
};
template <class T>
struct Size2D
{
static_assert(std::is_arithmetic<T>::value, "This class only works with floats and integers.");
T sX, sY;
Size2D(T all)
: sX(all), sY(all)
{ }
Size2D(T x = 0, T y = 0)
: sX(x), sY(y)
{ }
};
#ifdef NDEBUG
#define assertCond(_EXPR_) (false)
#define assertThrow(_arg_) ((void)0)
#define assert_throw(...) ((void)0)
#define assertMsg(...) ((void)0)
#else
// Evaluates the expression. Ifdef NDEBUG returns always false
#define assertCond(_EXPR_) (!(_EXPR_))
// Throws a excpetion with the argument as message by prepending the current file name and line number
#define assertThrow(_std_string_) assert_throw( (_std_string_), __FILE__, __LINE__)
inline void assert_throw(const std::string& message, const char* file, int line)
{
std::stringstream ss;
ss << file << ":" << line << ": " << message;
throw std::invalid_argument(ss.str());
}
#define assertMsg(_EXPR_, _MSG_) if (!(_EXPR_)) assertThrow(_MSG_)
#endif
// ostream overloads
template<typename T>
std::ostream& operator<<(std::ostream& os, const Point2D<T>& pt)
{
return os << "(" << pt.X << "; " << pt.Y << ")";
}
template<typename T>
std::ostream& operator<<(std::ostream& os, const BoundingBox<T>& bb)
{
return os << "(X: " << bb.MinX << " - " << bb.MaxX << ";"
<< " Y: " << bb.MinY << " - " << bb.MaxY << ")";
}

34
math/boxkde/GausLib.h Normal file
View File

@@ -0,0 +1,34 @@
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the GAUSLIB_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// GAUSLIB_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
typedef struct c_bbox {
double minX;
double maxX;
double minY;
double maxY;
double minZ;
double maxZ;
} c_bbox;
#define ALGO_BOX 0
#define ALGO_EXBOX 1
#define ALGO_BOX_SIMD 2
#define ALGO_BOX_CL 3
void gausKde2D_simple(float* X, int sizeX,
float* weights,
float hx, float hy,
int nBinsX, int nBinsY,
c_bbox* boundingBox,
int algorithm,
double* out_maxValue,
double* out_maxPosX, double* out_maxPosY,
double* out_runTimeInNS,
float* out_density);

211
math/boxkde/Grid2D.h Normal file
View File

@@ -0,0 +1,211 @@
#pragma once
#include <sstream>
#include "DataStructures.h"
#include "Image2D.h"
template<class T>
struct Grid2D
{
static_assert(std::is_floating_point<T>::value, "Grid2D only supports float values");
BoundingBox<T> bb;
size_t numBinsX, numBinsY;
T binSizeX, binSizeY;
private:
Image2D<T> data;
public:
Grid2D(){ } //TODO: fast hack
Grid2D(BoundingBox<T> bb, size_t numBins)
: Grid2D(bb, numBins, numBins)
{
}
Grid2D(BoundingBox<T> bb, size_t numBinsX, size_t numBinsY)
: bb(bb),
numBinsX(numBinsX),
numBinsY(numBinsY),
binSizeX((bb.width()) / (numBinsX-1)),
binSizeY((bb.heigth()) / (numBinsY-1)),
data(numBinsX, numBinsY)
{
}
Image2D<T>& image() { return data; }
const Image2D<T>& image() const { return data; }
T& operator() (size_t x, size_t y) { return data(x, y); }
const T& operator() (size_t x, size_t y) const { return data(x, y); }
void clear() { data.clear(); }
void fill(const std::vector<Point2D<T>>& samples)
{
assertMsg(!samples.empty(), "Samples must be non-empty");
data.clear();
const T weight = T(1.0) / samples.size();
for (const auto& pt : samples)
{
add(pt.X, pt.Y, weight);
}
}
void fill(const std::vector<Point2D<T>>& samples, const std::vector<T>& weights)
{
assertMsg(!samples.empty(), "Samples must be non-empty");
assertMsg(weights.size() == samples.size(), "Weights must have the same size as samples");
data.clear();
for (size_t i = 0; i < samples.size(); i++)
{
add(samples[i].X, samples[i].Y, weights[i]);
}
}
Point2D<size_t> add(T x, T y, T w)
{
if (assertCond(bb.isInside(x,y)))
{
std::stringstream ss;
ss << "Point " << Point2D<T>(x, y) << " is out of bounds. " << bb;
assertThrow(ss.str());
}
return add_simple_bin(x, y, w);
//return add_linear_bin(x, y, w);
}
void add_linear(T x, T y, T w)
{
if (assertCond(bb.isInside(x, y)))
{
std::stringstream ss;
ss << "Point " << Point2D<T>(x, y) << " is out of bounds. " << bb;
assertThrow(ss.str());
}
add_linear_bin(x, y, w);
}
T fetch(T x, T y) const
{
size_t bin_x = (size_t)((x - bb.MinX) / binSizeX);
size_t bin_y = (size_t)((y - bb.MinY) / binSizeY);
//if (bin_x == data.width) bin_x--;
//if (bin_y == data.height) bin_y--;
return data(bin_x, bin_y);
}
// Returns the summation of all bin values
T sum() const
{
return data.sum();
}
// Takes a point in input space and converts it to grid space coordinates
Point2D<size_t> asGridSpace(T x, T y) const
{
size_t bin_x = (size_t)((x - bb.MinX) / binSizeX);
size_t bin_y = (size_t)((y - bb.MinY) / binSizeY);
if (bin_x == data.width) bin_x--;
if (bin_y == data.height) bin_y--;
return Point2D<size_t>(bin_x, bin_y);
}
// Takes a Size2D in input space and converts it to grid space coordiantes
Size2D<size_t> asGridSpace(Size2D<T> sz) const
{
return Size2D<size_t>(sz.sX / binSizeX, sz.sY / binSizeY);
}
// Takes a point in grid space and converts it to input space coordinates
Point2D<T> asInputSpace(Point2D<size_t> pt) const
{
return asInputSpace(pt.X, pt.Y);
}
// Takes a point in grid space and converts it to input space coordinates
Point2D<T> asInputSpace(size_t x, size_t y) const
{
return Point2D<T>(x * binSizeX + bb.MinX + T(0.5)*binSizeX,
y * binSizeY + bb.MinY + T(0.5)*binSizeY);
}
T maximum(Point2D<T>& pt) const
{
Point2D<size_t> gridPt;
T maxValue = image().maximum(gridPt);
pt = asInputSpace(gridPt);
return maxValue;
}
private:
Point2D<size_t> add_simple_bin(T x, T y, T w)
{
size_t bin_x = (size_t)((x - bb.MinX) / binSizeX);
size_t bin_y = (size_t)((y - bb.MinY) / binSizeY);
if (bin_x == data.width) bin_x--;
if (bin_y == data.height) bin_y--;
data(bin_x, bin_y) += w;
return Point2D<size_t>(bin_x, bin_y);
}
void add_linear_bin(T x, T y, T w)
{
T xpos1 = (x - bb.MinX) / binSizeX;
T xpos2 = (y - bb.MinY) / binSizeY;
size_t ix1 = (size_t)floor(xpos1);
size_t ix2 = (size_t)floor(xpos2);
T fx1 = xpos1 - ix1;
T fx2 = xpos2 - ix2;
const size_t ixmin1 = 0;
const size_t ixmax1 = numBinsX - 2;
const size_t ixmin2 = 0;
const size_t ixmax2 = numBinsY - 2;
if ( ixmin1 <= ix1 && ixmin2 <= ix2 && ix2 <= ixmax2)
{
data(ix1, ix2) += w*(1 - fx1)*(1 - fx2);
data(ix1+1, ix2) += w*fx1*(1 - fx2);
data(ix1, ix2+1) += w*(1 - fx1)*fx2;
data(ix1 + 1, ix2 + 1) += w*fx1*fx2;
}
else if (ix1 == ixmax1 + 1 && ixmin2 <= ix2 && ix2 <= ixmax2)
{
// rechts
data(ix1, ix2) += w*(1 - fx1)*(1 - fx2);
data(ix1, ix2+1) += w*(1 - fx1)*fx2;
}
else if (ixmin1 <= ix1 && ix1 <= ixmax1 && ix2 == ixmax2 + 1)
{
// unten
data(ix1, ix2) += w*(1 - fx1)*(1 - fx2);
data(ix1+1, ix2) += w*fx1*(1 - fx2);
}
else if (ix1 == ixmax1 + 1 && ix2 == ixmax2 + 1)
{
// rechts-unten
data(ix1, ix2) += w*(1 - fx1)*(1 - fx2);
}
}
};
template struct Grid2D<float>;
template struct Grid2D<double>;

188
math/boxkde/Image2D.h Normal file
View File

@@ -0,0 +1,188 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <numeric>
#include <vector>
#include "DataStructures.h"
enum struct LineDirection { X, Y };
template <typename TValue>
struct ImageView2D
{
static_assert(std::is_arithmetic<TValue>::value, "Image only supports integers or floats");
// forward declaration
//enum struct LineDirection;
template<LineDirection D> struct LineView;
template<LineDirection D> struct ConstLineView;
size_t width, height, size;
protected:
TValue* values; // contains image data row-wise
public:
ImageView2D()
: width(0), height(0), size(0), values(nullptr)
{ }
ImageView2D(size_t width, size_t height)
: width(width), height(height), size(width*height), values(nullptr)
{ }
ImageView2D(size_t width, size_t height, TValue* data)
: width(width), height(height), size(width*height), values(data)
{ }
inline TValue& operator() (size_t x, size_t y) { return values[indexFromCoord(x, y)]; }
inline const TValue& operator() (size_t x, size_t y) const { return values[indexFromCoord(x, y)]; }
TValue* val_begin() { return values; }
TValue* val_end () { return values + size; }
const TValue* val_begin() const { return values; }
const TValue* val_end () const { return values + size; }
inline size_t indexFromCoord(size_t x, size_t y) const
{
assertMsg(x < width && y < height, "(x,y) out of bounds");
return y * width + x;
}
Point2D<size_t> coordFromIndex(size_t index) const
{
assertMsg(index < size, "Index out of bounds");
return Point2D<size_t>(index % width, index / width);
}
Point2D<size_t> maximum() const
{
size_t maxValueIndex = std::distance(val_begin(), std::max_element(val_begin(), val_end()));
return coordFromIndex(maxValueIndex);
}
TValue maximum(Point2D<size_t>& pt) const
{
size_t maxValueIndex = std::distance(val_begin(), std::max_element(val_begin(), val_end()));
pt = coordFromIndex(maxValueIndex);
return values[maxValueIndex];
}
void clear()
{
fill(TValue(0));
}
void fill(TValue value)
{
std::fill(val_begin(), val_end(), value);
}
void assign(const ImageView2D<TValue>& other)
{
assertMsg(size == other.size, "Other must be of the same size as this");
std::copy(other.val_begin(), other.val_end(), val_begin());
}
TValue sum() const
{
return std::accumulate(val_begin(), val_end(), TValue(0));
}
// Returns a transposed view of this image without copying any data.
ImageView2D<TValue> transpose() const
{
return ImageView2D<TValue>(height, width, values);
}
// Returns the value at (x,y). Throws if out of bounds.
const TValue& at(size_t x, size_t y) const
{
return values[indexFromCoord(x, y)];
}
// Returns the value at (x,y) but falls back to default if out of bounds.
const TValue& get(size_t x, size_t y, TValue dflt = 0) const
{
if (x >= width || y >= height)
return dflt;
else
return values[indexFromCoord(x, y)];
}
std::vector<Point2D<size_t>> findLocalMaxima(int radiusX = 1, int radiusY = 1) const
{
std::vector<Point2D<size_t>> result;
findLocalMaxima(result, radiusX, radiusY);
return result;
}
void findLocalMaxima(std::vector<Point2D<size_t>>& result, int radiusX = 1, int radiusY = 1) const
{
assertMsg(radiusX > 0, "Search radius must be greater than 0.");
for (size_t y = 0; y < height; y++)
{
for (size_t x = 0; x < width; x++)
{
TValue val = at(x, y);
bool lclMax = true;
for (int ry = -radiusY; ry <= radiusY; ry++)
{
for (int rx = -radiusX; rx <= radiusX; rx++)
{
TValue other = get(x + rx, y + ry);
if (!(rx == 0 && ry == 0) && val <= other)
{
lclMax = false;
}
}
}
if (lclMax)
{
result.emplace_back(x, y);
}
}
}
}
};
template <class TValue>
struct Image2D : public ImageView2D<TValue>
{
static_assert(std::is_arithmetic<TValue>::value, "Image only supports integers or floats");
std::vector<TValue> values_vec;
Image2D()
{
}
Image2D(size_t width, size_t height)
: ImageView2D<TValue>(width, height), values_vec(width * height)
{
this->values = values_vec.data();
}
Image2D(size_t width, size_t height, std::vector<TValue>& data)
: ImageView2D<TValue>(width, height), values_vec(data)
{
assertMsg(data.size() == width*height, "Sizes must be the same");
this->values = values_vec.data();
}
std::vector<TValue>& data() { return values_vec; }
const std::vector<TValue>& data() const { return values_vec; }
};
template struct ImageView2D<float>;
template struct ImageView2D<double>;
template struct Image2D<float>;
template struct Image2D<double>;

105
math/boxkde/benchmark.h Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
#include <chrono>
#include <string>
#include <iostream>
template <typename F>
static void benchmark(std::string name, size_t count, F&& lambda)
{
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < count; i++)
{
lambda();
}
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start);
long long durationAVG = duration.count() / count;
std::cout << "Function " << name << " took avg. " << durationAVG << "us (" << durationAVG / 1000.0f << "ms) over " << count << " runs." << std::endl;
}
struct BenchResult
{
const long long min, max;
const double mean, median;
BenchResult()
: min(0), max(0), mean(0), median(0)
{}
BenchResult(long long min, long long max, double mean, double median)
: min(min), max(max), mean(mean), median(median)
{}
};
template <typename F>
static BenchResult benchmarkEx(std::string name, size_t count, F&& lambda)
{
std::vector<long long> durations(count);
for (size_t i = 0; i < count; i++)
{
auto start = std::chrono::high_resolution_clock::now();
lambda();
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start);
durations[i] = duration.count();
}
long long min = *std::min_element(durations.begin(), durations.end());
long long max = *std::max_element(durations.begin(), durations.end());
double average = std::accumulate(durations.begin(), durations.end(), 0.0) / durations.size();
double median;
std::sort(durations.begin(), durations.end());
if (durations.size() % 2 == 0)
median = (double) (durations[durations.size() / 2 - 1] + durations[durations.size() / 2]) / 2;
else
median = (double) durations[durations.size() / 2];
std::cout << "Function " << name << " took avg. " << average << "ns (" << average / 1000.0f << "us) over " << count << " runs." << std::endl;
return BenchResult(min, max, average, median);
}
struct StopWatch
{
typedef std::chrono::high_resolution_clock clock;
typedef clock::time_point time_point;
time_point startTime; // Point in time when start() was called
time_point lapTime; // Point in time when operator() was called
public:
StopWatch()
{
reset();
}
void reset()
{
startTime = clock::now();
lapTime = startTime;
}
std::chrono::microseconds stop()
{
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(clock::now() - startTime);
std::cout << "Total time: " << duration.count() << "us (" << duration.count() / 1000.0f << "ms)" << std::endl;
return duration;
}
void operator()(const std::string& str = "")
{
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(clock::now() - lapTime);
std::cout << str << (str.empty() ? "" : " ") << "took " << duration.count() << "us (" << duration.count() / 1000.0f << "ms)" << std::endl;
lapTime = clock::now();
}
};