200 lines
5.7 KiB
C++
200 lines
5.7 KiB
C++
#include <nanobind/nanobind.h>
|
|
#include <nanobind/ndarray.h>
|
|
#include <nanobind/stl/string.h>
|
|
#include <nanobind/eigen/dense.h>
|
|
|
|
#include <autoopt/btls.hpp>
|
|
#include <autoopt/ellipse.hpp>
|
|
#include <autoopt/interface.hpp>
|
|
#include <autoopt/optimization_problem.hpp>
|
|
#include <autoopt/util.hpp>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace nb = nanobind;
|
|
|
|
struct input_data {
|
|
size_t n_points;
|
|
double* data;
|
|
|
|
input_data(size_t n) : n_points(n) {
|
|
if (n_points == 0) {
|
|
data = nullptr;
|
|
} else {
|
|
data = new double[n_points * 2];
|
|
}
|
|
}
|
|
|
|
input_data(const input_data&) = delete;
|
|
input_data& operator=(const input_data&) = delete;
|
|
|
|
input_data(input_data&& other) noexcept
|
|
: n_points(other.n_points), data(other.data) {
|
|
other.data = nullptr;
|
|
other.n_points = 0;
|
|
}
|
|
|
|
input_data& operator=(input_data&& other) noexcept {
|
|
if (this != &other) {
|
|
delete[] data;
|
|
n_points = other.n_points;
|
|
data = other.data;
|
|
other.data = nullptr;
|
|
other.n_points = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
~input_data() { delete[] data; }
|
|
|
|
void set_point(size_t i, double x, double y) {
|
|
data[i * 2] = x;
|
|
data[i * 2 + 1] = y;
|
|
}
|
|
};
|
|
|
|
input_data read_data(const std::filesystem::path& filename) {
|
|
// check if file exists
|
|
if (!std::filesystem::exists(filename)) {
|
|
throw std::runtime_error("File does not exist: " + filename.string());
|
|
}
|
|
|
|
std::fstream file(filename, std::ios::in);
|
|
|
|
std::unordered_map<double, size_t> index_map;
|
|
std::vector<double> x_values;
|
|
std::vector<std::vector<double>> data_points;
|
|
|
|
std::string line;
|
|
|
|
size_t index = 0;
|
|
|
|
double x_avg = 0.0;
|
|
|
|
while (std::getline(file, line)) {
|
|
if (line.empty() || line[0] == '#') {
|
|
std::cout << "Skipped: " << line << std::endl;
|
|
continue;
|
|
}
|
|
std::istringstream iss(line);
|
|
double x, y;
|
|
iss >> x;
|
|
for (size_t i = 1; i < 5; ++i) {
|
|
iss >> y; // skip unused columns
|
|
}
|
|
|
|
y = autoopt::arcsec2rad(-y);
|
|
|
|
if (index_map.find(x) == index_map.end()) {
|
|
index_map[x] = index++;
|
|
x_values.push_back(x);
|
|
data_points.emplace_back();
|
|
data_points.back().push_back(y);
|
|
x_avg += x;
|
|
continue;
|
|
}
|
|
data_points[index_map[x]].push_back(y);
|
|
}
|
|
x_avg /= x_values.size();
|
|
|
|
input_data result(index);
|
|
for (size_t i = 0; i < x_values.size(); ++i) {
|
|
double x = x_values[i];
|
|
double y_avg = 0.0;
|
|
for (double y : data_points[i]) {
|
|
y_avg += y;
|
|
}
|
|
y_avg /= data_points[i].size();
|
|
result.set_point(i, x - x_avg, y_avg);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct ellipse_params {
|
|
double left_arm;
|
|
double right_arm;
|
|
double theta;
|
|
};
|
|
|
|
NB_MODULE(slopefit, m) {
|
|
m.def("import_dat_file", [](std::string filename) {
|
|
input_data data = read_data(filename);
|
|
std::cout << "First data point: (" << data.data[0] << ", " << data.data[1]
|
|
<< ")" << std::endl;
|
|
nb::ndarray<double, nb::numpy> array(data.data, {data.n_points, 2});
|
|
return nb::cast(array);
|
|
});
|
|
|
|
nb::class_<ellipse_params>(m, "EllipseParams")
|
|
.def(nb::init<double, double, double>())
|
|
.def_rw("left_arm", &ellipse_params::left_arm)
|
|
.def_rw("right_arm", &ellipse_params::right_arm)
|
|
.def_rw("theta", &ellipse_params::theta)
|
|
.def("__repr__", [](const ellipse_params& params) {
|
|
std::ostringstream oss;
|
|
oss << "EllipseParams(left_arm=" << params.left_arm
|
|
<< ", right_arm=" << params.right_arm << ", theta=" << params.theta
|
|
<< ")";
|
|
return oss.str();
|
|
}).def("__call__", [](const ellipse_params& params, nb::ndarray<double, nb::ndim<1>> xs) {
|
|
auto e = autoopt::ellipse(params.left_arm, params.right_arm,
|
|
autoopt::deg2rad(params.theta));
|
|
autoopt::quadric<double> q = e.to_quadric();
|
|
Eigen::VectorXd ys(xs.shape(0));
|
|
for (size_t i = 0; i < xs.shape(0); ++i) {
|
|
ys(i) = q.slope_at(xs(i));
|
|
}
|
|
return ys;
|
|
})
|
|
;
|
|
|
|
m.def(
|
|
"fit_ellipse",
|
|
[](nb::ndarray<double, nb::ndim<2>> data, ellipse_params initial_params,
|
|
ellipse_params delta) -> ellipse_params {
|
|
std::cout << "Fitting ellipse to data with " << data.shape(0)
|
|
<< " points." << std::endl;
|
|
|
|
if (data.shape(1) != 2) {
|
|
throw std::runtime_error("Data array must have shape (n_points, 2)");
|
|
}
|
|
|
|
std::vector<std::pair<double, double>> data_vec;
|
|
for (size_t i = 0; i < data.shape(0); ++i) {
|
|
data_vec.emplace_back(data(i, 0), data(i, 1));
|
|
}
|
|
|
|
std::cout << "Initial parameters: "
|
|
<< "left_arm=" << initial_params.left_arm
|
|
<< ", right_arm=" << initial_params.right_arm
|
|
<< ", theta=" << initial_params.theta << std::endl;
|
|
|
|
double midpoint_y = data_vec[data_vec.size() / 2].second;
|
|
|
|
Eigen::VectorX<double> init_params(4);
|
|
init_params(0) = initial_params.left_arm;
|
|
init_params(1) = initial_params.right_arm;
|
|
init_params(2) = autoopt::deg2rad(initial_params.theta);
|
|
init_params(3) = midpoint_y;
|
|
|
|
Eigen::VectorX<double> deltas(4);
|
|
deltas(0) = delta.left_arm;
|
|
deltas(1) = delta.right_arm;
|
|
deltas(2) = autoopt::deg2rad(delta.theta);
|
|
deltas(3) = autoopt::deg2rad(0.1);
|
|
|
|
std::cout << "calculating fit..." << std::endl;
|
|
Eigen::VectorX<double> fitted_params =
|
|
autoopt::fit_ellipse(data_vec, init_params, deltas);
|
|
ellipse_params result;
|
|
result.left_arm = fitted_params(0);
|
|
result.right_arm = fitted_params(1);
|
|
result.theta = autoopt::rad2deg(fitted_params(2));
|
|
return result;
|
|
});
|
|
} |