summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/calibration.cpp439
-rw-r--r--src/calibration.h31
-rw-r--r--src/constants.h23
-rw-r--r--src/dumps.cpp141
-rw-r--r--src/dumps.h6
-rw-r--r--src/pixels.cpp95
-rw-r--r--src/pixels.h32
-rw-r--r--src/typedefs.h33
8 files changed, 800 insertions, 0 deletions
diff --git a/src/calibration.cpp b/src/calibration.cpp
new file mode 100644
index 0000000..73bd786
--- /dev/null
+++ b/src/calibration.cpp
@@ -0,0 +1,439 @@
+#include "calibration.h"
+
+#include <execution>
+#include <iostream>
+
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QImage>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include "imagealgos.h"
+
+bool openCalibrationTable(const QString& filename, CalibrationTablePtr& table)
+{
+ QFile f(filename);
+
+ if (!f.open(QFile::ReadOnly)) {
+ qWarning() << Q_FUNC_INFO << "cannot open" << filename
+ << "for reading:" << f.errorString();
+
+ return false;
+ }
+
+ table.reset(new CalibrationTable{{0}});
+ auto bytes = f.read((char*) table.data(), sizeof(CalibrationTable));
+
+ if (bytes != sizeof(CalibrationTable)) {
+ qWarning() << "cannot read calibration table from" << filename << bytes;
+
+ if (f.error()) {
+ qWarning() << f.errorString() << f.error() << (void*) table.data();
+ } else {
+ qWarning() << "file size:" << f.size() << "; got:" << bytes;
+ }
+
+ return false;
+ }
+
+ // for (const auto& col : *table) {
+ // qDebug() << "calibration column mid:" << col[640];
+ // }
+
+ return true;
+}
+
+bool dump(const CalibrationTablePtr& table, const QString& filename)
+{
+ qDebug() << Q_FUNC_INFO << "size is" << sizeof(CalibrationTable);
+
+ QFile f(filename);
+
+ if (!f.open(QFile::WriteOnly)) {
+ qWarning() << Q_FUNC_INFO << "cannot open" << filename
+ << "for writing:" << f.errorString();
+
+ return false;
+ }
+
+ const auto written = f.write((const char*) table.data(),
+ sizeof(CalibrationTable));
+
+ if (written != sizeof(CalibrationTable) || !f.flush()) {
+ qWarning() << Q_FUNC_INFO << "cannot write" << filename << ":"
+ << f.errorString();
+ return false;
+ }
+
+ return true;
+}
+
+void interpolate(CalibrationTablePtr& table)
+{
+ std::for_each(std::execution::par,
+ table->begin(),
+ table->end(),
+ [](auto& column) { interpolate(column); });
+
+ for (size_t i = 9471; i < 9472; i++) {
+ std::cout << "row #" << i << ": ";
+
+ for (size_t j = 0; j < 1280; ++j) {
+ const auto& p = table->at(j).at(i);
+ std::cout << p << ' ';
+ }
+
+ std::cout << std::endl;
+ }
+
+ // for (size_t i = 0; i < discretesInRage; i++) {
+ // std::cout << "row #" << i << ": ";
+
+ // for (size_t j = 640 - 5; j < 640 + 5; ++j) {
+ // const auto& p = table->at(j).at(i);
+ // std::cout << p << ' ';
+ // }
+
+ // std::cout << std::endl;
+ // }
+
+ // for (const auto& p : (*table)[table->size() / 2]) {
+ // qDebug() << "mid column pixel" << p;
+ // }
+}
+
+void interpolate(CalibrationColumn& column)
+{
+ size_t start{0};
+ auto& c = column;
+
+#define FIND_IF(index, condition) \
+ while (bool(c[index]) != condition && index < calibrationTableHeight) \
+ ++index; \
+\
+ if (index == calibrationTableHeight) \
+ return
+
+ FIND_IF(start, true);
+
+ while (true) {
+ size_t left = start + 1;
+ FIND_IF(left, false);
+ --left;
+
+ size_t right = left + 1;
+ FIND_IF(right, true);
+
+ auto delta = (c[right] - c[left]) / (right - left);
+
+ for (auto i = left + 1; i < right; ++i) {
+ c[i] = c[i - 1] + delta;
+
+ if (c[i] > 190.) {
+ qWarning() << "interpolate: got invalid value mm" << c[i];
+
+ qWarning() << "left/i/right" << left << i << right;
+ qWarning() << "delta" << delta;
+ qWarning() << "c[left/i/right]" << c[left] << c[i] << c[right];
+
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ start = right;
+ }
+}
+
+QImage calibrationTableToImage(const CalibrationTablePtr& calibrationTable)
+{
+ QImage result(QSize(calibrationTable->size(),
+ calibrationTable->at(0).size()),
+ QImage::Format::Format_Indexed8);
+
+ // QImage image(QSize(imageWidth, imageHeight), QImage::Format_Indexed8);
+
+ QColor color(Qt::green);
+ auto r = color.redF();
+ auto g = color.greenF();
+ auto b = color.blueF();
+
+ for (int c = 0; c < 256; c++) {
+ QRgb col = qRgb(int(c * r), int(c * g), int(c * b));
+ result.setColor(c, col);
+ }
+
+ int notNull = 0;
+
+ for (size_t colIdx = 0; colIdx < calibrationTable->size(); ++colIdx) {
+ const auto& column = calibrationTable->at(colIdx);
+
+ for (size_t rowIdx = 0; rowIdx < column.size(); ++rowIdx) {
+ bool hasValue = !qFuzzyIsNull(column.at(rowIdx));
+
+ notNull += int(hasValue);
+
+ result.setPixel(colIdx, rowIdx, hasValue ? 255 : 0);
+ // if (column.at(rowIdx) >= 190.) {
+ // qWarning() << "invalid mm value" << column.at(rowIdx);
+ // exit(EXIT_FAILURE);
+ // }
+ // result.setPixel(colIdx, rowIdx, (column.at(rowIdx) / 190.) * 255);
+ }
+ }
+
+ qDebug() << "not null count" << notNull << "of"
+ << sizeof(CalibrationTable)
+ / sizeof(calibrationTable->at(0).at(0));
+
+ return result;
+}
+
+CalibrationTablePtr calibrateZ(const QList<Pixels>& rawProfiles,
+ const uint32_t& stepsPerMm)
+{
+ CalibrationTablePtr result{new CalibrationTable{{0}}};
+
+ for (const auto& rawProfile : rawProfiles) {
+ // std::cout << "calibration: pos is "
+ // << rawProfile.counters.encoderPosition << std::endl;
+ const float positionMm{float(rawProfile.counters.encoderPosition)
+ / float(stepsPerMm)};
+
+ if (positionMm >= 200) {
+ qWarning() << "got invalid calibration distance:" << positionMm;
+ qWarning() << "encoder position:"
+ << rawProfile.counters.encoderPosition;
+ exit(EXIT_FAILURE);
+ }
+
+ const auto& pixels = rawProfile.pixels;
+ // printPixels(pixels);
+ // static size_t counter { 0 };
+ // qDebug() << "calibrated" << counter++;
+ const float pos = rawProfile.counters.encoderPosition;
+ const float divider = stepsPerMm;
+ // std::cout << pos << " " << divider << " " << pos / divider <<
+ // std::endl; std::cout << std::setw(5) <<
+ // rawProfile.counters.encoderPosition
+ // << std::setw(5) << requested_params.stepsPerMm
+ // << std::setw(8) << positionMm
+ // << std::setw(8) <<
+ // float(rawProfile.counters.encoderPosition) /
+ // float(requested_params.stepsPerMm)
+ // << ": ";
+
+ for (size_t columnIdx = 0; columnIdx < pixels.size(); ++columnIdx) {
+ const auto& pixelValue = pixels.at(columnIdx);
+
+ // float[0, img_height] -> uint16_t[0, calibrationColumnHeight]
+ const uint16_t discretePixelValue{
+ uint16_t(pixelValue * discretesInRage / img_height)};
+
+ Q_ASSERT_X(discretePixelValue > 0
+ && discretePixelValue < calibrationColumnHeight,
+ Q_FUNC_INFO,
+ ("got ivalid discrete value "
+ + QString::number(discretePixelValue)
+ + ". pixelValues is " + QString::number(pixelValue)
+ + ". calc result is "
+ + QString::number(pixelValue * discretesInRage
+ / img_height))
+ .toStdString()
+ .c_str());
+
+ // auto& calibrationColumn = result[columnIdx];
+ auto& calibrationColumn = (*result)[columnIdx];
+
+ calibrationColumn[discretePixelValue] = positionMm;
+
+ // if (columnIdx >= ((img_width - 10) / 2) &&
+ // columnIdx < img_width - ((img_width - 10) / 2))
+ // {
+ // std::cout << discretePixelValue << ";";
+ // }
+ }
+
+ // std::cout << std::endl << std::flush;
+ }
+
+ return result;
+ // TODO: try something interesting
+ // return {};
+}
+
+CalibrationTablePtr calibrateX(const QList<Pixels>& rawProfiles)
+{
+ CalibrationTablePtr result{new CalibrationTable{{0}}};
+
+ static size_t tmp_counter = 0;
+
+ for (const auto& rawProfile : rawProfiles) {
+ const auto& pixels = rawProfile.pixels;
+ auto lines = pixelsToLines(rawProfile);
+
+ tmp_counter++;
+
+ Q_ASSERT_X(lines.count() > 2, Q_FUNC_INFO, "no lines");
+
+ if (tmp_counter == 9471) {
+ for (const auto& l : lines) {
+ qDebug() << "line:" << l;
+ }
+ }
+
+ QList<double> xAnchors(lines.size() + 1);
+
+ std::transform(std::execution::par_unseq,
+ lines.constBegin(),
+ lines.constEnd(),
+ xAnchors.begin(),
+ [](const auto& l) { return l.x1(); });
+
+ xAnchors.last() = lines.last().x2();
+
+ constexpr double triangleHeightMm{5.};
+ constexpr double triangleBaseMm{8.};
+
+ auto nearestAnchorToX0 = std::min_element(std::execution::par_unseq,
+ xAnchors.constBegin(),
+ xAnchors.constEnd(),
+ [](const auto& a,
+ const auto& b) {
+ return std::abs(a)
+ < std::abs(b);
+ });
+
+ int nearestAnchorToX0Idx = nearestAnchorToX0 - xAnchors.constBegin();
+
+ QList<double> xAnchorsMm(xAnchors.count());
+
+ for (int i = 0; i < xAnchors.size(); ++i) {
+ xAnchorsMm[i] = (i - nearestAnchorToX0Idx) * triangleBaseMm / 2.;
+ }
+
+ auto xAnchorIt = xAnchors.constBegin() + 1;
+ auto xAnchorMmIt = xAnchorsMm.constBegin() + 1;
+
+ for (size_t columnIdx = 0; columnIdx < pixels.size(); ++columnIdx) {
+ // skip points with to the left from left line and to the right from
+ // right line
+ const auto columnX = int(columnIdx) - int(img_width / 2);
+ if (columnX < xAnchors.first() || columnX > xAnchors.last()) {
+ continue;
+ }
+
+ if (columnX > *xAnchorIt) {
+ ++xAnchorIt;
+ ++xAnchorMmIt;
+ }
+ const auto xLeft = *(xAnchorIt - 1);
+ const auto xRight = *(xAnchorIt);
+
+ if (columnX < xLeft || columnX > xRight) {
+ if (rawProfile.counters.encoderPosition >= 0) {
+ qWarning()
+ << "x anchor not found" << xLeft << columnX << xRight;
+ continue;
+ }
+ }
+
+ const auto& pixelValue = pixels.at(columnIdx);
+ const uint16_t discretePixelValue{
+ uint16_t(pixelValue * discretesInRage / img_height)};
+
+ Q_ASSERT_X(discretePixelValue > 0
+ && discretePixelValue < calibrationColumnHeight,
+ Q_FUNC_INFO,
+ ("got ivalid discrete value "
+ + QString::number(discretePixelValue)
+ + ". pixelValues is " + QString::number(pixelValue)
+ + ". calc result is "
+ + QString::number(pixelValue * discretesInRage
+ / img_height))
+ .toStdString()
+ .c_str());
+
+ const auto xLineLen = xRight - xLeft;
+ const auto xLeftMm = *(xAnchorMmIt - 1);
+ const auto xRelative = float(columnX - xLeft) / xLineLen;
+ const auto xMmValue = xLeftMm + xRelative * (triangleBaseMm / 2.);
+
+ auto& calibrationColumn = (*result)[columnIdx];
+
+ if (tmp_counter == 9471) {
+ qDebug() << "calibration value" << columnIdx
+ << discretePixelValue << xMmValue;
+ }
+ calibrationColumn[discretePixelValue] = xMmValue;
+ }
+ }
+
+ return result;
+}
+
+void dumpCalibrationPixels(std::vector<Pixels>&& calibrationPixels)
+{
+ std::vector<Pixels> rawProfiles;
+
+ std::swap(rawProfiles, calibrationPixels);
+
+ const QString dumpSubdir{
+ QDateTime::currentDateTime().toString("yyyy.MM.dd_hh.mm.ss")};
+ const QDir dumpPath{dumpsRoot + "/" + dumpSubdir};
+
+ if (!dumpPath.mkdir(dumpPath.path())) {
+ qWarning() << "cannot create dir: " << dumpPath.path();
+
+ return;
+ }
+
+ for (const auto& rawProfile : rawProfiles) {
+ const auto filename = QLatin1String("raw_profile_meas_%1_enc_%2")
+ .arg(QString::number(
+ rawProfile.counters.measurementCounter))
+ .arg(rawProfile.counters.encoderPosition);
+ const auto filepath = dumpPath.path() + "/" + filename;
+
+ QFile f{filepath};
+
+ if (!f.open(QFile::WriteOnly)) {
+ qWarning() << "cannot open dump dump file" << f.fileName();
+ qWarning() << "error is:" << f.errorString();
+
+ return;
+ }
+
+ QJsonObject jsonCounters{
+ {"timestampUs", qint64(rawProfile.counters.timestampUs)},
+ {"measurementCounter",
+ qint64(rawProfile.counters.measurementCounter)},
+ {"encoderPosition", qint64(rawProfile.counters.encoderPosition)},
+ };
+
+ QJsonObject json;
+
+ json["counters"] = jsonCounters;
+
+ QJsonArray jsonPixels;
+
+ for (const auto& pixel : rawProfile.pixels) {
+ jsonPixels << pixel;
+ }
+
+ json["pixels"] = jsonPixels;
+
+ if (!f.write(QJsonDocument(json).toJson())) {
+ qWarning() << "cannot write file" << f.fileName();
+ qWarning() << "error is" << f.errorString();
+
+ return;
+ }
+
+ qDebug() << "file written: " << f.fileName();
+ }
+
+ qDebug() << "dump finished";
+}
diff --git a/src/calibration.h b/src/calibration.h
new file mode 100644
index 0000000..ddb66a6
--- /dev/null
+++ b/src/calibration.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <array>
+
+#include "fuck_intel.h"
+
+#include <QSharedPointer>
+
+#include "constants.h"
+#include "pixels.h"
+
+using CalibrationColumn = std::array<float, calibrationTableHeight>;
+// map [0; discretesInRage] (discretes) to [0; zRangeMm] (mm)
+using CalibrationTable = std::array<CalibrationColumn, img_width>;
+using CalibrationTablePtr = QSharedPointer<CalibrationTable>;
+
+constexpr auto calibrationColumnHeight = std::tuple_size<CalibrationColumn>();
+
+bool openCalibrationTable(const QString &filename, CalibrationTablePtr &table);
+
+void dumpCalibrationPixels(std::vector<Pixels> &&calibrationPixels);
+bool dump(const CalibrationTablePtr &table, const QString &filename);
+
+CalibrationTablePtr calibrateX(const QList<Pixels> &rawProfiles);
+CalibrationTablePtr calibrateZ(const QList<Pixels> &rawProfiles,
+ const uint32_t &stepsPerMm);
+
+QImage calibrationTableToImage(const CalibrationTablePtr &calibrationTable);
+
+void interpolate(CalibrationTablePtr &table);
+void interpolate(CalibrationColumn &column);
diff --git a/src/constants.h b/src/constants.h
new file mode 100644
index 0000000..1c451ab
--- /dev/null
+++ b/src/constants.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+#include <QString>
+
+constexpr size_t img_width = 1280;
+constexpr size_t img_height = 800;
+constexpr uint32_t patternSize = 16;
+
+constexpr float hardcodedZRangeMm{175.f};
+constexpr size_t calibrationTableHeight{0x4000}; // 16384
+
+namespace {
+constexpr uint16_t discretesInRage{16384};
+}
+
+// http json keys
+const QString exposureTimeKey = "exposureTime";
+const QString laserLevelKey = "laserLevel";
+
+const QString dumpsRoot{QStringLiteral("/home/user/dumps")};
diff --git a/src/dumps.cpp b/src/dumps.cpp
new file mode 100644
index 0000000..43a839c
--- /dev/null
+++ b/src/dumps.cpp
@@ -0,0 +1,141 @@
+#include "dumps.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QElapsedTimer>
+#include <QFile>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include <iostream>
+
+QList<Pixels> openDump(
+ const QString &dumpPath)
+{
+ QString dirToRead{dumpPath};
+ qDebug() << "trying to open dump path:" << dirToRead;
+
+ if (dirToRead.isEmpty()) {
+ qDebug() << "dumpPath not specified. looking into" << dumpsRoot;
+
+ QDir dumpsRootDir{dumpsRoot};
+
+ const auto filter = QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable;
+ // there is no battery in my rpi5 for now
+ const auto sort = QDir::Name;
+ const auto entries = dumpsRootDir.entryList(filter, sort);
+
+ if (entries.isEmpty()) {
+ qWarning() << "dumps root" << dumpsRoot << "contains no dumps. "
+ << "specify existing dump path";
+
+ return {};
+ }
+
+ dirToRead = dumpsRoot + "/" + entries.last();
+ }
+
+ QDir dumpDir{dirToRead};
+
+ // const QStringList nameFilters{"*.bin"};
+ const QStringList nameFilters{};
+ const auto filter = QDir::Files;
+ const auto sort = QDir::Name;
+
+ auto filenames = dumpDir.entryList(nameFilters, filter, sort);
+
+ if (filenames.isEmpty()) {
+ qDebug() << "no filenames found in" << dumpDir.path();
+ return {};
+ }
+
+ qDebug() << "create results array" << filenames.size();
+ auto resultOptionals = QScopedPointer(
+ new QList<std::optional<Pixels>>(filenames.size()));
+
+ QElapsedTimer t;
+ t.start();
+ qDebug() << "open real files";
+ std::cout << "here" << std::endl;
+
+ std::transform(
+ std::execution::par_unseq,
+ filenames.begin(),
+ filenames.end(),
+ resultOptionals->begin(),
+ [dirToRead](const auto &filename) {
+ // std::cout << '.';
+ // auto rawProfile = openRawProfile(dirToRead + "/" + filename);
+ auto rawProfile = Pixels::load(dirToRead + "/" + filename);
+
+ return rawProfile;
+ });
+
+ filenames.clear();
+ filenames.squeeze();
+
+ qDebug() << Q_FUNC_INFO << "open raw profiles: elapsed (ms)" << t.elapsed();
+ // std::cout << std::endl;
+
+ std::remove_if(std::execution::par,
+ resultOptionals->begin(),
+ resultOptionals->end(),
+ [](auto &a) { return !a.has_value(); });
+
+ QList<Pixels> result(resultOptionals->size());
+
+ std::transform(std::execution::par,
+ std::make_move_iterator(resultOptionals->begin()),
+ std::make_move_iterator(resultOptionals->end()),
+ result.begin(),
+ [](auto &p) { return p.value(); });
+
+ qDebug() << Q_FUNC_INFO << "elapsed (ms)" << t.elapsed();
+
+ return result;
+}
+
+std::optional<Pixels> openRawProfile(
+ const QString &filePath)
+{
+ QFile f{filePath};
+
+ if (!f.open(QFile::ReadOnly)) {
+ qWarning() << "cannot open file for reading:" << f.fileName();
+ qWarning() << "error string:" << f.errorString();
+
+ return {};
+ }
+
+ // TODO: rewrite to remove manual serialization/deserialization
+ const auto json = QJsonDocument::fromJson(f.readAll()).object();
+ const auto jsonCounters = json["counters"].toObject();
+
+ Pixels result;
+ result.counters.timestampUs = jsonCounters["timestampUs"].toInteger();
+ result.counters.measurementCounter = jsonCounters["measurementCounter"]
+ .toInteger();
+ result.counters.encoderPosition = jsonCounters["encoderPosition"].toInteger();
+
+ const auto jsonPixels = json["pixels"].toArray();
+ // TODO: check json pixels count
+#ifdef FIRST_COLUMN_ONLY
+ result.pixels[0] = jsonPixels.at(0).toDouble();
+#else
+ std::transform(
+ // std::execution::par_unseq,
+ jsonPixels.constBegin(),
+ jsonPixels.constEnd(),
+ result.pixels.begin(),
+ [](const auto &jsonPixel) { return jsonPixel.toDouble(); });
+
+// for (size_t i = 0; i < jsonPixels.count() && i < result.pixels.size();
+// ++i)
+// {
+// result.pixels[i] = jsonPixels[i].toDouble();
+// }
+#endif
+
+ return result;
+}
diff --git a/src/dumps.h b/src/dumps.h
new file mode 100644
index 0000000..8aa8dc9
--- /dev/null
+++ b/src/dumps.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "pixels.h"
+
+QList<Pixels> openDump(const QString &dumpPath = "");
+std::optional<Pixels> openRawProfile(const QString &filePath);
diff --git a/src/pixels.cpp b/src/pixels.cpp
new file mode 100644
index 0000000..b314e6a
--- /dev/null
+++ b/src/pixels.cpp
@@ -0,0 +1,95 @@
+#include "pixels.h"
+
+#include <execution>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+
+Pixels& Pixels::operator+=(const Pixels& other)
+{
+ std::transform(
+ std::execution::par,
+ pixels.begin(),
+ pixels.end(),
+ other.pixels.begin(),
+ pixels.begin(),
+ // [](auto& toAdd) { return dst += src; });
+ std::plus<>()
+ );
+
+ return *this;
+}
+
+Pixels& Pixels::operator/=(const float divider)
+{
+ std::for_each(
+ std::execution::par_unseq,
+ pixels.begin(),
+ pixels.end(),
+ [divider](auto& pixel) { pixel /= divider; }
+ );
+
+ return *this;
+}
+
+std::optional<Pixels> Pixels::load(const QString& filename)
+{
+ const std::filesystem::path filepath{filename.toStdString()};
+
+ if (!std::filesystem::exists(filepath))
+ {
+ std::cerr << "no such file: " << filepath << std::endl;
+
+ return {};
+ }
+
+ std::ifstream ifs(filepath, std::ios::in | std::ios::binary);
+
+ if (!ifs)
+ return {};
+
+ Pixels result;
+ ifs.read(reinterpret_cast<char*>(&result), sizeof(Pixels));
+ ifs.close();
+
+ if (!ifs)
+ {
+ std::cerr << "cannot read " << filepath << std::endl;
+
+ return {};
+ }
+
+ return result;
+}
+
+bool Pixels::save(const QString& filename)
+{
+ const std::filesystem::path filepath{filename.toStdString()};
+ const auto parent_path = filepath.parent_path();
+
+ if (!std::filesystem::exists(parent_path) &&
+ !std::filesystem::create_directories(parent_path))
+ {
+ std::cerr << "cannot create parent directory for file " << filepath
+ << std::endl;
+
+ return false;
+ }
+
+ std::ofstream ofs(filepath, std::ios::out | std::ios::binary);
+
+ if (!ofs)
+ return false;
+
+ ofs.write(reinterpret_cast<const char*>(this), sizeof(Pixels));
+ ofs.close();
+
+ if (!ofs)
+ {
+ std::cerr << "cannot write " << filepath << std::endl;
+
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/pixels.h b/src/pixels.h
new file mode 100644
index 0000000..6095220
--- /dev/null
+++ b/src/pixels.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <array>
+
+#include "fuck_intel.h"
+
+#include <QString>
+
+#include "typedefs.h"
+
+struct Pixels
+{
+ Counters counters{};
+ std::array<float, img_width> pixels{0.f};
+
+ Pixels& operator+=(const Pixels& other);
+ Pixels& operator/=(const float divider);
+
+ // TODO: tests for everything everywhere
+ /*!
+ * \brief load - load binary pixels from file
+ * \param filename - file path
+ * \return Pixels on success, empty std::optional otherwise
+ */
+ [[nodiscard]] static std::optional<Pixels> load(const QString& filename);
+ /*!
+ * \brief save - save binary profile to file
+ * \param filename - file path
+ * \return - true on success, false otherwise
+ */
+ [[nodiscard]] bool save(const QString& filename);
+};
diff --git a/src/typedefs.h b/src/typedefs.h
new file mode 100644
index 0000000..2af2921
--- /dev/null
+++ b/src/typedefs.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <libcamera/pixel_format.h>
+
+#include "constants.h"
+
+struct Counters
+{
+ uint32_t timestampUs{0};
+ uint32_t measurementCounter{0};
+ int32_t encoderPosition{0};
+};
+
+struct Image
+{
+ int width;
+ int height;
+ uint16_t data[img_height][img_width];
+ uint16_t rotated_cw[img_width][img_height];
+ size_t dataSize;
+ unsigned int stride;
+ libcamera::PixelFormat pixelFormat;
+ Counters counters{};
+};
+
+struct requested_params_t
+{
+ int32_t exposureTime = {1000};
+ int32_t laserLevel = {3000};
+ uint32_t stepsPerMm{200};
+};
+
+enum ScanningModeFlags : uint8_t { None = 0, Calibration };