summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNikita Kostovsky <nikita@kostovsky.me>2025-02-21 07:27:00 +0100
committerNikita Kostovsky <nikita@kostovsky.me>2025-02-21 07:27:00 +0100
commitd12498504c279a0a85bbfb024f7903e34dbe07db (patch)
tree0df9f3f8bf27470ac211a57bb8e44be0aa2f6138 /src
parent27637ab117d8738236f6ab155300ff6e79e4843b (diff)
broken img calc; change dir struct
Diffstat (limited to 'src')
-rw-r--r--src/LibCamera.cpp267
-rw-r--r--src/LibCamera.h91
-rw-r--r--src/camera/icamera.h4
-rw-r--r--src/camera/innomakerov9281.cpp180
-rw-r--r--src/camera/innomakerov9281.h11
-rw-r--r--src/camera/ov9281.cpp12
-rw-r--r--src/constants.h2
-rw-r--r--src/fuck_intel.h12
-rw-r--r--src/genetic_algos.cpp1
-rw-r--r--src/genetic_algos.h174
-rw-r--r--src/httpservice.cpp1
-rw-r--r--src/httpservice.h203
-rw-r--r--src/image.cpp74
-rw-r--r--src/image.h18
-rw-r--r--src/imagealgos.cpp465
-rw-r--r--src/imagealgos.h18
-rw-r--r--src/macro.h17
-rw-r--r--src/main.cpp798
-rw-r--r--src/printerclient.cpp86
-rw-r--r--src/printerclient.h30
-rw-r--r--src/profile.cpp155
-rw-r--r--src/profile.h29
-rw-r--r--src/rotaryencoder.cpp125
-rw-r--r--src/rotaryencoder.h20
24 files changed, 2716 insertions, 77 deletions
diff --git a/src/LibCamera.cpp b/src/LibCamera.cpp
new file mode 100644
index 0000000..1e68228
--- /dev/null
+++ b/src/LibCamera.cpp
@@ -0,0 +1,267 @@
+// #include "LibCamera.h"
+
+// using namespace std::placeholders;
+
+// int LibCamera::initCamera() {
+// int ret;
+// cm = std::make_unique<CameraManager>();
+// ret = cm->start();
+// if (ret){
+// std::cout << "Failed to start camera manager: "
+// << ret << std::endl;
+// return ret;
+// }
+// cameraId = cm->cameras()[0]->id();
+// camera_ = cm->get(cameraId);
+// if (!camera_) {
+// std::cerr << "Camera " << cameraId << " not found" << std::endl;
+// return 1;
+// }
+
+// if (camera_->acquire()) {
+// std::cerr << "Failed to acquire camera " << cameraId
+// << std::endl;
+// return 1;
+// }
+// camera_acquired_ = true;
+// return 0;
+// }
+
+// char * LibCamera::getCameraId(){
+// return cameraId.data();
+// }
+
+// void LibCamera::configureStill(int width, int height, PixelFormat format, int buffercount, int rotation) {
+// printf("Configuring still capture...\n");
+// config_ = camera_->generateConfiguration({ StreamRole::StillCapture });
+// if (width && height) {
+// libcamera::Size size(width, height);
+// config_->at(0).size = size;
+// }
+// config_->at(0).pixelFormat = format;
+// if (buffercount)
+// config_->at(0).bufferCount = buffercount;
+// Transform transform = Transform::Identity;
+// bool ok;
+// Transform rot = transformFromRotation(rotation, &ok);
+// if (!ok)
+// throw std::runtime_error("illegal rotation value, Please use 0 or 180");
+// transform = rot * transform;
+// if (!!(transform & Transform::Transpose))
+// throw std::runtime_error("transforms requiring transpose not supported");
+// // FIXME: update
+// // config_->transform = transform;
+
+// CameraConfiguration::Status validation = config_->validate();
+// if (validation == CameraConfiguration::Invalid)
+// throw std::runtime_error("failed to valid stream configurations");
+// else if (validation == CameraConfiguration::Adjusted)
+// std::cout << "Stream configuration adjusted" << std::endl;
+
+// printf("Still capture setup complete\n");
+// }
+
+// int LibCamera::startCamera() {
+// int ret;
+// ret = camera_->configure(config_.get());
+// if (ret < 0) {
+// std::cout << "Failed to configure camera" << std::endl;
+// return ret;
+// }
+
+// camera_->requestCompleted.connect(this, &LibCamera::requestComplete);
+
+// allocator_ = std::make_unique<FrameBufferAllocator>(camera_);
+
+// return startCapture();
+// }
+
+// int LibCamera::startCapture() {
+// int ret;
+// unsigned int nbuffers = UINT_MAX;
+// for (StreamConfiguration &cfg : *config_) {
+// ret = allocator_->allocate(cfg.stream());
+// if (ret < 0) {
+// std::cerr << "Can't allocate buffers" << std::endl;
+// return -ENOMEM;
+// }
+
+// unsigned int allocated = allocator_->buffers(cfg.stream()).size();
+// nbuffers = std::min(nbuffers, allocated);
+// }
+
+// for (unsigned int i = 0; i < nbuffers; i++) {
+// std::unique_ptr<Request> request = camera_->createRequest();
+// if (!request) {
+// std::cerr << "Can't create request" << std::endl;
+// return -ENOMEM;
+// }
+
+// for (StreamConfiguration &cfg : *config_) {
+// Stream *stream = cfg.stream();
+// const std::vector<std::unique_ptr<FrameBuffer>> &buffers =
+// allocator_->buffers(stream);
+// const std::unique_ptr<FrameBuffer> &buffer = buffers[i];
+
+// ret = request->addBuffer(stream, buffer.get());
+// if (ret < 0) {
+// std::cerr << "Can't set buffer for request"
+// << std::endl;
+// return ret;
+// }
+// for (const FrameBuffer::Plane &plane : buffer->planes()) {
+// void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
+// plane.fd.get(), 0);
+// mappedBuffers_[plane.fd.get()] =
+// std::make_pair(memory, plane.length);
+// }
+// }
+
+// requests_.push_back(std::move(request));
+// }
+
+// ret = camera_->start(&this->controls_);
+// // ret = camera_->start();
+// if (ret) {
+// std::cout << "Failed to start capture" << std::endl;
+// return ret;
+// }
+// controls_.clear();
+// camera_started_ = true;
+// for (std::unique_ptr<Request> &request : requests_) {
+// ret = queueRequest(request.get());
+// if (ret < 0) {
+// std::cerr << "Can't queue request" << std::endl;
+// camera_->stop();
+// return ret;
+// }
+// }
+// viewfinder_stream_ = config_->at(0).stream();
+// return 0;
+// }
+
+// void LibCamera::StreamDimensions(Stream const *stream, uint32_t *w, uint32_t *h, uint32_t *stride) const
+// {
+// StreamConfiguration const &cfg = stream->configuration();
+// if (w)
+// *w = cfg.size.width;
+// if (h)
+// *h = cfg.size.height;
+// if (stride)
+// *stride = cfg.stride;
+// }
+
+// Stream *LibCamera::VideoStream(uint32_t *w, uint32_t *h, uint32_t *stride) const
+// {
+// StreamDimensions(viewfinder_stream_, w, h, stride);
+// return viewfinder_stream_;
+// }
+
+// int LibCamera::queueRequest(Request *request) {
+// std::lock_guard<std::mutex> stop_lock(camera_stop_mutex_);
+// if (!camera_started_)
+// return -1;
+// {
+// std::lock_guard<std::mutex> lock(control_mutex_);
+// request->controls() = std::move(controls_);
+// }
+// return camera_->queueRequest(request);
+// }
+
+// void LibCamera::requestComplete(Request *request) {
+// if (request->status() == Request::RequestCancelled)
+// return;
+// processRequest(request);
+// }
+
+// void LibCamera::processRequest(Request *request) {
+// requestQueue.push(request);
+// }
+
+// void LibCamera::returnFrameBuffer(LibcameraOutData frameData) {
+// uint64_t request = frameData.request;
+// Request * req = (Request *)request;
+// req->reuse(Request::ReuseBuffers);
+// queueRequest(req);
+// }
+
+// bool LibCamera::readFrame(LibcameraOutData *frameData){
+// std::lock_guard<std::mutex> lock(free_requests_mutex_);
+// // int w, h, stride;
+// if (!requestQueue.empty()){
+// Request *request = this->requestQueue.front();
+
+// const Request::BufferMap &buffers = request->buffers();
+// for (auto it = buffers.begin(); it != buffers.end(); ++it) {
+// FrameBuffer *buffer = it->second;
+// for (unsigned int i = 0; i < buffer->planes().size(); ++i) {
+// const FrameBuffer::Plane &plane = buffer->planes()[i];
+// const FrameMetadata::Plane &meta = buffer->metadata().planes()[i];
+
+// void *data = mappedBuffers_[plane.fd.get()].first;
+// int length = std::min(meta.bytesused, plane.length);
+
+// frameData->size = length;
+// frameData->imageData = (uint8_t *)data;
+// }
+// }
+// this->requestQueue.pop();
+// frameData->request = (uint64_t)request;
+// return true;
+// } else {
+// Request *request = nullptr;
+// frameData->request = (uint64_t)request;
+// return false;
+// }
+// }
+
+// void LibCamera::set(ControlList controls){
+// std::lock_guard<std::mutex> lock(control_mutex_);
+// this->controls_ = std::move(controls);
+// }
+
+// int LibCamera::resetCamera(int width, int height, PixelFormat format, int buffercount, int rotation) {
+// stopCamera();
+// configureStill(width, height, format, buffercount, rotation);
+// return startCamera();
+// }
+
+// void LibCamera::stopCamera() {
+// if (camera_){
+// {
+// std::lock_guard<std::mutex> lock(camera_stop_mutex_);
+// if (camera_started_){
+// if (camera_->stop())
+// throw std::runtime_error("failed to stop camera");
+// camera_started_ = false;
+// }
+// }
+// camera_->requestCompleted.disconnect(this, &LibCamera::requestComplete);
+// }
+// while (!requestQueue.empty())
+// requestQueue.pop();
+
+// for (auto &iter : mappedBuffers_)
+// {
+// std::pair<void *, unsigned int> pair_ = iter.second;
+// munmap(std::get<0>(pair_), std::get<1>(pair_));
+// }
+
+// mappedBuffers_.clear();
+
+// requests_.clear();
+
+// allocator_.reset();
+
+// controls_.clear();
+// }
+
+// void LibCamera::closeCamera(){
+// if (camera_acquired_)
+// camera_->release();
+// camera_acquired_ = false;
+
+// camera_.reset();
+
+// cm.reset();
+// }
diff --git a/src/LibCamera.h b/src/LibCamera.h
new file mode 100644
index 0000000..89cf835
--- /dev/null
+++ b/src/LibCamera.h
@@ -0,0 +1,91 @@
+#pragma once
+
+// #include <atomic>
+// #include <iomanip>
+// #include <iostream>
+// #include <signal.h>
+// #include <limits.h>
+// #include <memory>
+// #include <stdint.h>
+// #include <string>
+// #include <vector>
+// #include <unordered_map>
+// #include <queue>
+// #include <sstream>
+// #include <sys/mman.h>
+// #include <unistd.h>
+// #include <time.h>
+// #include <mutex>
+
+#ifdef signals
+#error ("don't include this file after any qt files")
+#endif
+
+#include <libcamera/controls.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/property_ids.h>
+#include <libcamera/libcamera.h>
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/framebuffer_allocator.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.h>
+#include <libcamera/formats.h>
+#include <libcamera/transform.h>
+// using namespace libcamera;
+
+// typedef struct {
+// uint8_t *imageData;
+// uint32_t size;
+// uint64_t request;
+// } LibcameraOutData;
+
+// class LibCamera {
+// public:
+// LibCamera(){};
+// ~LibCamera(){};
+
+// int initCamera();
+// void configureStill(int width, int height, PixelFormat format, int buffercount, int rotation);
+// int startCamera();
+// int resetCamera(int width, int height, PixelFormat format, int buffercount, int rotation);
+// bool readFrame(LibcameraOutData *frameData);
+// void returnFrameBuffer(LibcameraOutData frameData);
+
+// void set(ControlList controls);
+// void stopCamera();
+// void closeCamera();
+
+// Stream *VideoStream(uint32_t *w, uint32_t *h, uint32_t *stride) const;
+// char * getCameraId();
+
+// private:
+// int startCapture();
+// int queueRequest(Request *request);
+// void requestComplete(Request *request);
+// void processRequest(Request *request);
+
+// void StreamDimensions(Stream const *stream, uint32_t *w, uint32_t *h, uint32_t *stride) const;
+
+// unsigned int cameraIndex_;
+// uint64_t last_;
+// std::unique_ptr<CameraManager> cm;
+// std::shared_ptr<Camera> camera_;
+// bool camera_acquired_ = false;
+// bool camera_started_ = false;
+// std::unique_ptr<CameraConfiguration> config_;
+// std::unique_ptr<FrameBufferAllocator> allocator_;
+// std::vector<std::unique_ptr<Request>> requests_;
+// // std::map<std::string, Stream *> stream_;
+// std::map<int, std::pair<void *, unsigned int>> mappedBuffers_;
+
+// std::queue<Request *> requestQueue;
+
+// ControlList controls_;
+// std::mutex control_mutex_;
+// std::mutex camera_stop_mutex_;
+// std::mutex free_requests_mutex_;
+
+// Stream *viewfinder_stream_ = nullptr;
+// std::string cameraId;
+// };
diff --git a/src/camera/icamera.h b/src/camera/icamera.h
index 3fccc4b..11919bb 100644
--- a/src/camera/icamera.h
+++ b/src/camera/icamera.h
@@ -35,12 +35,14 @@ public:
virtual bool setExposureTimeUs(int value) = 0;
virtual bool setGain(int value) = 0;
virtual bool setLaserLevel(int value) = 0;
+ virtual bool setSomething(int value) = 0;
public:
libcamera::Signal<std::shared_ptr<Pixels>> newPixels;
libcamera::Signal<std::shared_ptr<Image>> newImage;
std::function<void(std::shared_ptr<Pixels>)> newPixelsCallback;
- std::function<void(std::shared_ptr<Image>)> newImageCallback;
+ // std::function<void(std::shared_ptr<Image>)> newImageCallback;
+ std::function<void(Image &)> newImageCallback;
public:
virtual bool startStream() = 0;
diff --git a/src/camera/innomakerov9281.cpp b/src/camera/innomakerov9281.cpp
index b7c5cd4..79ae0b4 100644
--- a/src/camera/innomakerov9281.cpp
+++ b/src/camera/innomakerov9281.cpp
@@ -10,6 +10,9 @@
#include <unistd.h>
#include <iostream>
+#include <span>
+
+#include <QElapsedTimer>
#include "constants.h"
// #include "rotaryencoder.h"
@@ -23,10 +26,13 @@
#define DBG(fmt, args...) LOGD("%s:%d, " fmt, __FUNCTION__, __LINE__, ##args);
+extern uint64_t dq_elapsed_ns;
+extern uint64_t get_elapsed_ns;
extern uint64_t sum_elapsed_ns;
extern uint64_t corr_elapsed_ns;
extern uint64_t max_elapsed_ns;
extern uint64_t value_elapsed_ns;
+extern uint64_t rot_elapsed_ns;
// constexpr char videoDevice[] = "/dev/video0";
@@ -37,6 +43,9 @@ InnoMakerOV9281::~InnoMakerOV9281()
m_streamThread.request_stop();
m_streamThread.join();
+ m_someThread.request_stop();
+ m_someThread.join();
+
int ret{-1};
for (int i = 0; i < BUFFER_COUNT; ++i)
@@ -74,43 +83,84 @@ std::vector<std::shared_ptr<ICamera> > InnoMakerOV9281::search()
if (!cam->init())
return {};
- if (!cam->setExposureTimeUs(3000))
+ if (!cam->setExposureTimeUs(68000))
+ return {};
+
+ if (!cam->setLaserLevel(1))
+ return {};
+
+ if (!cam->setGain(2))
return {};
- if (!cam->setGain(3000))
+ if (!cam->setSomething(0)) {
return {};
+ }
+
+ // m_someThread = std::jthread{[=](std::stop_token stopToken) {
+ // std::cout << "InnoMakerOV9281: start stream" << std::endl;
+ // sleep(5);
+
+ // static int i = 0;
+ // while (!stopToken.stop_requested()) {
+ // cam->setSomething(i);
+ // i -= 1;
+ // }
+ // }};
return {cam};
}
bool InnoMakerOV9281::startStream()
{
+ int buffer_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ auto ret = ioctl(m_cam_fd, VIDIOC_STREAMON, &buffer_type);
+
+ if (ret != 0) {
+ DBG("ioctl(VIDIOC_STREAMON) failed %d(%s)", errno, strerror(errno));
+ return false;
+ }
+
m_streamThread = std::jthread{[&](std::stop_token stopToken) {
std::cout << "InnoMakerOV9281: start stream" << std::endl;
+ // Image image;
+ std::shared_ptr<Image> imageCopy = std::make_shared<Image>();
while (!stopToken.stop_requested())
{
- std::shared_ptr<Image> image = std::make_shared<Image>();
- getImage(*image);
-// FIXME: backup emit value
-#ifdef emit
-#undef emit
- // std::cout << "emit new image" << std::endl << std::flush;
- // newImage.emit(image);
- if (newImageCallback)
- {
+ size_t imageIndex{};
+
+ // if (!getImage(image))
+ if (!getImage(imageIndex))
+ break;
+
+ auto image = m_images[imageIndex];
+
+ // // FIXME: backup emit value
+ // #ifdef emit
+ // #undef emit
+ // // std::cout << "emit new image" << std::endl << std::flush;
+ // // newImage.emit(image);
+ if (newImageCallback) {
+ // memcpy(imageCopy.get(), &image, sizeof(Image));
+ // newImageCallback(imageCopy);
newImageCallback(image);
}
- // newPixels.emit(pixels);
- image->rotate();
- if (newPixelsCallback)
- {
- auto pixels = image->pixels();
+ // continue;
+ // // // newPixels.emit(pixels);
+ // auto &img = *image;
+ // image->rotate();
+ // img.rotate();
+ image.rotate();
+ // image.rotate();
+ if (newPixelsCallback) {
+ // auto pixels = image->pixels();
+ auto pixels = image.pixels();
newPixelsCallback(pixels);
}
+ continue;
}
-#define emit
-#endif
+ // #define emit
+ // #endif
std::cout << "InnoMakerOV9281: stream interruption requested"
<< std::endl;
}};
@@ -170,6 +220,13 @@ bool InnoMakerOV9281::setLaserLevel(int value)
return setCamParam(V4L2_CID_FLASH_TIMEOUT, value);
}
+bool InnoMakerOV9281::setSomething(int value)
+{
+ std::cout << __func__ << ": " << value << std::endl << std::flush;
+
+ return setCamParam(V4L2_CID_FLASH_INTENSITY, value);
+}
+
bool InnoMakerOV9281::setCamParam(unsigned int v4l2controlId, int value)
{
v4l2_control ctl{v4l2controlId, value};
@@ -182,6 +239,7 @@ bool InnoMakerOV9281::setCamParam(unsigned int v4l2controlId, int value)
"cannot set cam param: id - %d, error - '%s'\n",
v4l2controlId,
strerror(errno));
+ fflush(stderr);
return false;
}
@@ -195,6 +253,8 @@ bool InnoMakerOV9281::setCamParam(unsigned int v4l2controlId, int value)
v4l2controlId,
strerror(errno));
+ fflush(stderr);
+
return false;
}
@@ -277,11 +337,8 @@ bool InnoMakerOV9281::initCam()
request.memory = V4L2_MEMORY_MMAP;
ret = ioctl(m_cam_fd, VIDIOC_REQBUFS, &request);
- if (ret < 0)
- {
- fprintf(stderr,
- "cannot set cam request buffers: ioctl error - '%s'\n",
- strerror(errno));
+ if (ret < 0) {
+ fprintf(stderr, "cannot set cam request buffers: ioctl error - '%s'\n", strerror(errno));
return false;
}
@@ -311,14 +368,9 @@ bool InnoMakerOV9281::initCam()
DBG("buffer.length: %d", buffer.length);
DBG("buffer.m.offset: %d", buffer.m.offset);
- video_buffer_ptr[i] = (uint8_t *) mmap(NULL,
- buffer.length,
- PROT_READ | PROT_WRITE,
- MAP_SHARED,
- m_cam_fd,
- buffer.m.offset);
- if (video_buffer_ptr[i] == MAP_FAILED)
- {
+ video_buffer_ptr[i] = (uint8_t *)
+ mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, m_cam_fd, buffer.m.offset);
+ if (video_buffer_ptr[i] == MAP_FAILED) {
DBG("mmap() failed %d(%s)", errno, strerror(errno));
return false;
}
@@ -334,20 +386,23 @@ bool InnoMakerOV9281::initCam()
}
}
- int buffer_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = ioctl(m_cam_fd, VIDIOC_STREAMON, &buffer_type);
- if (ret != 0)
- {
- DBG("ioctl(VIDIOC_STREAMON) failed %d(%s)", errno, strerror(errno));
- return false;
- }
+ // int buffer_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ // ret = ioctl(m_cam_fd, VIDIOC_STREAMON, &buffer_type);
+ // if (ret != 0)
+ // {
+ // DBG("ioctl(VIDIOC_STREAMON) failed %d(%s)", errno, strerror(errno));
+ // return false;
+ // }
DBG("cam init done.");
return true;
}
-bool InnoMakerOV9281::getImage(Image &image)
+// Image &InnoMakerOV9281::getImage()
+// bool InnoMakerOV9281::getImage(Image &image)
+bool InnoMakerOV9281::getImage(size_t &imageIndex)
+// TODO: get Image from video_buffer_ptr
{
static struct timeval curr, prev;
static uint16_t counter = 0;
@@ -359,17 +414,25 @@ bool InnoMakerOV9281::getImage(Image &image)
if (elapsedTime > 1000.)
{
- fprintf(stderr, "fps: %d, sec: %d\n", counter, curr.tv_sec);
fprintf(stderr,
- "sum: %d,\tcorr: %d,\tval: %d\n",
+ "fps: %d\tsec: %d\tdq: %d\tget: %d\trot: %d\tsum: %d,\tcorr: %d,\tval: %d\n",
+ counter,
+ curr.tv_sec % 1000,
+ dq_elapsed_ns / 1000 / counter,
+ get_elapsed_ns / 1000 / counter,
+ rot_elapsed_ns / 1000 / counter,
sum_elapsed_ns / 1000 / counter,
corr_elapsed_ns / 1000 / counter,
// max_elapsed_ns / 1000 / counter,
value_elapsed_ns / 1000 / counter);
+
+ dq_elapsed_ns = 0;
+ get_elapsed_ns = 0;
sum_elapsed_ns = 0;
corr_elapsed_ns = 0;
max_elapsed_ns = 0;
value_elapsed_ns = 0;
+ rot_elapsed_ns = 0;
counter = 0;
prev = curr;
@@ -383,10 +446,14 @@ bool InnoMakerOV9281::getImage(Image &image)
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = BUFFER_COUNT;
- ret = ioctl(m_cam_fd, VIDIOC_DQBUF, &buffer);
-
- if (ret != 0)
{
+ QElapsedTimer t;
+ t.start();
+ ret = ioctl(m_cam_fd, VIDIOC_DQBUF, &buffer);
+ dq_elapsed_ns += t.nsecsElapsed();
+ }
+
+ if (ret != 0) {
DBG("ioctl(VIDIOC_DQBUF) failed %d(%s)", errno, strerror(errno));
return false;
}
@@ -397,19 +464,34 @@ bool InnoMakerOV9281::getImage(Image &image)
return false;
}
+ // auto &image = video_buffer_ptr[buffer.index];
+ imageIndex = buffer.index;
+ auto &image = m_images[buffer.index];
image.height = img_height;
image.width = img_width;
// TODO: fill
// image.counters.encoderPosition = RotaryEncoder::instance()->position();
image.counters.measurementCounter = buffer.sequence;
- image.counters.timestampUs = buffer.timestamp.tv_sec * 1000 * 1000 +
- buffer.timestamp.tv_usec;
- memcpy(image.data, video_buffer_ptr[buffer.index], img_size);
+ image.counters.timestampUs = buffer.timestamp.tv_sec * 1000 * 1000 + buffer.timestamp.tv_usec;
+
+ {
+ QElapsedTimer t;
+ t.start();
+ memcpy(&image.data, video_buffer_ptr[buffer.index], img_size);
+ // std::cout << (void *) video_buffer_ptr[buffer.index] << std::endl;
+ get_elapsed_ns += t.nsecsElapsed();
+ }
+
+ static bool done{false};
+
+ if (!done) {
+ // memcpy(image.data.data(), video_buffer_ptr[buffer.index], img_size);
+ done = true;
+ }
ret = ioctl(m_cam_fd, VIDIOC_QBUF, &buffer);
- if (ret != 0)
- {
+ if (ret != 0) {
DBG("ioctl(VIDIOC_QBUF) failed %d(%s)", errno, strerror(errno));
return false;
}
diff --git a/src/camera/innomakerov9281.h b/src/camera/innomakerov9281.h
index ccfe1ac..7f2d206 100644
--- a/src/camera/innomakerov9281.h
+++ b/src/camera/innomakerov9281.h
@@ -33,7 +33,11 @@ public:
bool setLaserLevel(int value) override;
- bool getImage(Image &image);
+ bool setSomething(int value) override;
+
+ // bool getImage(Image &image);
+ bool getImage(size_t &image);
+ // Image &getImage();
public:
libcamera::Signal<std::shared_ptr<Pixels>> newPixels;
@@ -48,7 +52,12 @@ private:
private:
int m_cam_fd{-1};
static constexpr uint8_t BUFFER_COUNT{3};
+ // #ifdef USER_PTR
+ Image m_images[BUFFER_COUNT];
+ // #else
uint8_t *video_buffer_ptr[BUFFER_COUNT];
+ // #endif
// buffer_t m_buf;
std::jthread m_streamThread;
+ static inline std::jthread m_someThread;
};
diff --git a/src/camera/ov9281.cpp b/src/camera/ov9281.cpp
index 1a324e4..5b6936d 100644
--- a/src/camera/ov9281.cpp
+++ b/src/camera/ov9281.cpp
@@ -259,14 +259,14 @@ std::vector<std::shared_ptr<OV9281>> OV9281::search(
{
auto id = camera->id();
auto c = manager->get(id);
- auto ov9281 = std::shared_ptr<OV9281>(new OV9281(c));
+ // auto ov9281 = std::shared_ptr<OV9281>(new OV9281(c));
- if (!ov9281->init())
- {
- continue;
- }
+ // if (!ov9281->init())
+ // {
+ // continue;
+ // }
- result.push_back(ov9281);
+ // result.push_back(ov9281);
}
return result;
diff --git a/src/constants.h b/src/constants.h
index 4f37fe5..53e6063 100644
--- a/src/constants.h
+++ b/src/constants.h
@@ -10,7 +10,7 @@ constexpr size_t img_width = 1280;
constexpr size_t img_height = 800;
constexpr size_t img_size = img_width * img_height;
-constexpr uint32_t patternSize = 16;
+constexpr uint32_t patternSize = 10;
constexpr float hardcodedZRangeMm{175.f};
constexpr size_t calibrationTableHeight{0x4000}; // 16384
diff --git a/src/fuck_intel.h b/src/fuck_intel.h
new file mode 100644
index 0000000..6d9f9d5
--- /dev/null
+++ b/src/fuck_intel.h
@@ -0,0 +1,12 @@
+#pragma once
+
+// include this file before including any qt files if you have emit conflict
+// with tbb
+
+#ifdef emit
+#undef emit
+#include <execution>
+#define emit
+#else
+#include <execution>
+#endif
diff --git a/src/genetic_algos.cpp b/src/genetic_algos.cpp
new file mode 100644
index 0000000..1962768
--- /dev/null
+++ b/src/genetic_algos.cpp
@@ -0,0 +1 @@
+#include "genetic_algos.h"
diff --git a/src/genetic_algos.h b/src/genetic_algos.h
new file mode 100644
index 0000000..8dea732
--- /dev/null
+++ b/src/genetic_algos.h
@@ -0,0 +1,174 @@
+#pragma once
+
+// #define _USE_MATH_DEFINES
+#include <algorithm>
+#include <cmath>
+#include <numbers>
+#include <random>
+// TODO: <execution>
+
+/*
+
+a, sigma
+
+*/
+
+// #define POP_SIZE 30
+// #define NUM_IT 25
+// #define COL_SIZE 16
+
+// using Column = double;
+
+static std::random_device random_device;
+static std::mt19937 gen(random_device());
+static std::uniform_real_distribution<> dis01(0., 1.);
+static std::uniform_real_distribution<> disDelta2(-2., 2.);
+
+template<typename T, size_t COL_SIZE = 16>
+struct Item {
+ double a, sigma; // E, sigma = sqrt(D)
+ T* column;
+
+ double W;
+
+ Item() {}
+
+ Item(double a_, double s, T * column_) : a(a_), sigma(s), column(column_) {
+ update();
+ }
+
+ double gauss(double t) {
+ return std::exp(std::pow((t - a) / sigma,
+ 2) /
+ 2) /
+ std::sqrt(2 * std::numbers::pi) /
+ sigma;
+ }
+
+ double F() { // objective function
+ double s = 0;
+ int x0 = std::floor(.5 + a - 3 * sigma);
+ double x1 = std::round(a + 3 * sigma);
+
+ for (int j = x0; j <= x1; j++)
+ s += std::pow(gauss(j) - column[j], 2);
+
+ return s;
+ }
+
+ void update() {
+ W = F();
+ }
+
+ // action
+
+ Item move() {
+ double a1 = a + disDelta2(gen);
+ double sigma1 = sigma * (1 + disDelta2(gen)); // a: ~ +- 2 pixel
+
+ return Item(a1, sigma1, column);
+ }
+
+ // a = q * a1 + (1 - q) * a2, sigma = q * s1 + (1 - q) * s2
+ Item crossover(const Item& other) {
+ double q = dis01(gen);
+ double a_ = q * a + (1 - q) * other.a;
+ double sigma_ = q * sigma + (1 - q) * other.sigma;
+
+ return Item(a_, sigma_, column);
+ }
+};
+
+template <typename T = uint16_t,
+ size_t POP_SIZE = 30,
+ size_t COL_SIZE = 16,
+ size_t NUM_IT = 25,
+ double maxW = .01>
+struct Algo {
+ T * column;
+ using I = Item<T, COL_SIZE>;
+ std::vector<I> population;
+ std::uniform_real_distribution<double> disA { 0., double(1.) };
+
+ double targetW;
+
+ Algo(T * column_): column(column_) {
+ init();
+ }
+
+ I getNewItem() {
+ double a = rand() % (COL_SIZE * 1000) / 1000.;
+ double sigma = rand() % (COL_SIZE * 100) / 1000.;
+
+ return I(a, sigma, column);
+ }
+
+ void init() {
+ for (size_t i = 0; i < POP_SIZE; i++) {
+ population.push_back(getNewItem());
+ }
+ }
+
+ bool stopCondition() {
+ // return population[0].W <= targetW;
+ return population[0].W <= maxW;
+ }
+
+ I run() {
+ for (int it = 0; it < NUM_IT; it++) {
+ work();
+
+ // if (stopCondition())
+ // break;
+ }
+
+ return population[0];
+ }
+
+ void work() {
+ move();
+ crossover();
+ addNew();
+
+ sort();
+
+ remove();
+ }
+
+ void sort() {
+ // sort vector ASC
+ std::sort(population.begin(), population.end(),
+ [](const auto & a, const auto & b)
+ {
+ return a.W < b.W;
+ });
+ }
+
+ void remove() {
+ population.erase(population.begin() + POP_SIZE, population.end());
+ }
+
+ void move() {
+ for (I& item : population) {
+ population.push_back(item.move());
+ }
+ }
+
+ void addNew() {
+ for (int i = 0; i < 5; i++) {
+ population.push_back(getNewItem());
+ }
+ }
+
+ void crossover() {
+ for (int i1 = 0; i1 < 4; i1++) {
+
+ for (int i2 = i1 + 1; i2 < 4; i2++) {
+ I& x1 = population[i1];
+ I& x2 = population[i2];
+ // a = q * a1 + (1 - q) * a2, sigma = q * s1 + (1 - q) * s2
+ population.push_back(x1.crossover(x2));
+ }
+ }
+ }
+};
diff --git a/src/httpservice.cpp b/src/httpservice.cpp
new file mode 100644
index 0000000..7fa72fb
--- /dev/null
+++ b/src/httpservice.cpp
@@ -0,0 +1 @@
+#include "httpservice.h"
diff --git a/src/httpservice.h b/src/httpservice.h
new file mode 100644
index 0000000..a6c3f76
--- /dev/null
+++ b/src/httpservice.h
@@ -0,0 +1,203 @@
+#pragma once
+
+#include <pistache/description.h>
+#include <pistache/endpoint.h>
+#include <pistache/http.h>
+
+#include <pistache/serializer/rapidjson.h>
+
+#include "imagealgos.h"
+
+using namespace Pistache;
+
+extern uint8_t pgm_image[64 + img_width * img_height * sizeof(uint8_t)];
+extern size_t pgm_image_size;
+extern std::mutex pgm_image_mtx;
+
+class HttpService
+{
+public:
+ HttpService(Address addr)
+ : httpEndpoint(std::make_shared<Http::Endpoint>(addr))
+ , desc("Banking API", "0.1")
+ { }
+
+ void init(size_t thr = 2)
+ {
+ auto opts = Http::Endpoint::options()
+ .threads(static_cast<int>(thr));
+ httpEndpoint->init(opts);
+ createDescription();
+ }
+
+ void start()
+ {
+ router.initFromDescription(desc);
+
+ Rest::Swagger swagger(desc);
+ swagger
+ .uiPath("/doc")
+ .uiDirectory("/home/user/swagger-ui/dist")
+ .apiPath("/banker-api.json")
+ .serializer(&Rest::Serializer::rapidJson)
+ .install(router);
+
+ httpEndpoint->setHandler(router.handler());
+ httpEndpoint->serve();
+ }
+
+private:
+ void createDescription()
+ {
+ desc
+ .info()
+ .license("Apache", "http://www.apache.org/licenses/LICENSE-2.0");
+
+ auto backendErrorResponse = desc.response(Http::Code::Internal_Server_Error, "An error occured with the backend");
+
+ desc
+ .schemes(Rest::Scheme::Http)
+ .basePath("/v1")
+ .produces(MIME(Application, Json))
+ .consumes(MIME(Application, Json));
+
+ // desc
+ // .route(desc.get("/ready"))
+ // .bind(&Generic::handleReady)
+ // .response(Http::Code::Ok, "Response to the /ready call")
+ // .hide();
+
+ auto versionPath = desc.path("/v1");
+
+ auto sensorPath = versionPath.path("/sensor");
+
+ sensorPath
+ .route(desc.get("/image"))
+ .bind(&HttpService::image, this)
+ .produces(MIME(Image, Png))
+ .response(Http::Code::Ok, "Image from sensor");
+
+ // tmp
+ sensorPath
+ .route(desc.get("/image2"))
+ .bind(&HttpService::image, this)
+ .produces(MIME(Image, Png))
+ .response(Http::Code::Ok, "Image from sensor");
+
+ sensorPath
+ .route(desc.get("/params"), "Retrive sensor parameters")
+ .bind(&HttpService::get_sensorParams, this)
+ .produces(MIME(Application, Plain))
+ .response(Http::Code::Ok, "Parameter value")
+ .response(backendErrorResponse);;
+
+ sensorPath
+ .route(desc.get("/:param"), "Retrive sensor parameter")
+ .bind(&HttpService::get_sensorParam, this)
+ .produces(MIME(Application, Json))
+ .parameter<Rest::Type::String>("param", "The name of the parameter to retrieve")
+ .response(Http::Code::Ok, "Parameter value")
+ .response(backendErrorResponse);;
+
+ sensorPath
+ .route(desc.post("/:param"), "Set sensor parameter")
+ .bind(&HttpService::set_sensorParam, this)
+ .produces(MIME(Application, Plain))
+ .consumes(MIME(Application, Plain))
+ .response(Http::Code::Ok, "Setting parameter result");
+
+
+ auto accountsPath = versionPath.path("/accounts");
+
+ accountsPath
+ .route(desc.get("/all"))
+ .bind(&HttpService::retrieveAllAccounts, this)
+ .produces(MIME(Application, Json), MIME(Application, Xml))
+ .response(Http::Code::Ok, "The list of all account");
+
+ accountsPath
+ .route(desc.get("/:name"), "Retrieve an account")
+ .bind(&HttpService::retrieveAccount, this)
+ .produces(MIME(Application, Json))
+ .parameter<Rest::Type::String>("name", "The name of the account to retrieve")
+ .response(Http::Code::Ok, "The requested account")
+ .response(backendErrorResponse);
+
+ accountsPath
+ .route(desc.post("/:name"), "Create an account")
+ .bind(&HttpService::createAccount, this)
+ .produces(MIME(Application, Json))
+ .consumes(MIME(Application, Json))
+ .parameter<Rest::Type::String>("name", "The name of the account to create")
+ .response(Http::Code::Ok, "The initial state of the account")
+ .response(backendErrorResponse);
+ auto accountPath = accountsPath.path("/:name");
+ accountPath.parameter<Rest::Type::String>("name", "The name of the account to operate on");
+
+ accountPath
+ .route(desc.post("/budget"), "Add budget to the account")
+ .bind(&HttpService::creditAccount, this)
+ .produces(MIME(Application, Json))
+ .response(Http::Code::Ok, "Budget has been added to the account")
+ .response(backendErrorResponse);
+ }
+
+ void retrieveAllAccounts(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok,
+ "No Account",
+ { Http::Mime::Type::Text, Http::Mime::Subtype::Plain} );
+ }
+
+ void retrieveAccount(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok, "The bank is closed, come back later");
+ }
+
+ void createAccount(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok, "The bank is closed, come back later");
+ }
+
+ void creditAccount(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok, "The bank is closed, come back later");
+ }
+
+ void image(const Rest::Request&, Http::ResponseWriter response)
+ {
+ // FIXME: image should be valid
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // char data[pgm_image_size];
+ // memcpy(data, pgm_image, pgm_image_size);
+ std::cout << "send bytes: " << pgm_image_size << std::endl;
+
+ auto res = response.send(Http::Code::Ok,
+ (const char*)pgm_image, pgm_image_size,
+ Http::Mime::MediaType { "image/pgm" });
+ // { Http::Mime::Type::Image, Http::Mime::Subtype::Png });
+
+ res.then([](ssize_t bytes)
+ { std::cout << bytes << " bytes have been sent\n"; },
+ Async::NoExcept);
+ }
+
+ void get_sensorParam(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok, std::to_string(123));
+ }
+
+ void get_sensorParams(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok, std::to_string(123));
+ }
+
+ void set_sensorParam(const Rest::Request&, Http::ResponseWriter response)
+ {
+ response.send(Http::Code::Ok, std::to_string(123));
+ }
+
+ std::shared_ptr<Http::Endpoint> httpEndpoint;
+ Rest::Description desc;
+ Rest::Router router;
+};
diff --git a/src/image.cpp b/src/image.cpp
index e4d51c1..befcb2b 100644
--- a/src/image.cpp
+++ b/src/image.cpp
@@ -9,12 +9,17 @@
#include "macro.h"
#include "pixels.h"
+uint64_t dq_elapsed_ns = 0;
+uint64_t get_elapsed_ns = 0;
uint64_t sum_elapsed_ns = 0;
uint64_t corr_elapsed_ns = 0;
uint64_t max_elapsed_ns = 0;
uint64_t value_elapsed_ns = 0;
+uint64_t rot_elapsed_ns = 0;
-float process_column(const uint8_t (&column)[])
+// float process_column(const uint8_t (&column)[])
+// float process_column(const Image::row_t &column)
+float process_column(const Image::column_t &column)
{
start_timer(process_column);
QElapsedTimer t;
@@ -23,39 +28,45 @@ float process_column(const uint8_t (&column)[])
float result = std::numeric_limits<float>::quiet_NaN();
constexpr uint32_t signalThreshold = 900; // = SKO * sqrt(patternSize)
- static constexpr uint32_t patternOffset = patternSize -
- ((patternSize % 2 == 1) ? 1 : 0);
- const uint32_t correlationSize = img_height - patternSize +
- ((patternSize % 2 == 1) ? 1 : 0);
+ static constexpr uint32_t patternOffset = patternSize - ((patternSize % 2 == 1) ? 1 : 0);
+ const uint32_t correlationSize = img_height - patternSize + ((patternSize % 2 == 1) ? 1 : 0);
+ // constexpr uint32_t correlationSize = img_height - patternSize;
uint32_t correlation[img_height];
uint32_t integralSum[img_height];
- uint32_t maxSum = signalThreshold * 50;
+ uint32_t maxTripleSum = signalThreshold * 50;
uint32_t x1 = 0;
int32_t y1 = 0;
int32_t y2 = 0;
memset(correlation, 0, img_height * sizeof(correlation[0]));
+ // memset(correlation, 0, patternSize / 2);
integralSum[0] = 0;
for (uint32_t i = 1; i < img_height; ++i) {
integralSum[i] = column[i] + integralSum[i - 1];
}
+
sum_elapsed_ns += t.nsecsElapsed();
t.restart();
+ // pixel * <sum of neighbours>
for (uint32_t i = 0; i < correlationSize; ++i)
correlation[i + patternSize / 2] = column[i + patternSize / 2]
* (integralSum[i + patternOffset] - integralSum[i]);
+ // * (integralSum[i + patternSize] - integralSum[i]);
corr_elapsed_ns += t.nsecsElapsed();
t.restart();
for (uint32_t i = 3; i < img_height - 2; ++i) {
+ // p - pixel, n - neighbour
+ // P - pixel used in sum, N - neighbour used in sum
+ // [N P N]
const auto sum = correlation[i - 1] + correlation[i] +
correlation[i + 1];
- if (sum > maxSum)
- {
+ if (sum > maxTripleSum) {
+ // [N N n p] - [P N]
const int32_t rioux0 = int32_t(correlation[i - 2 - 1] +
correlation[i - 1 - 1]) -
int32_t(correlation[i + 1 - 1] +
@@ -63,6 +74,7 @@ float process_column(const uint8_t (&column)[])
if (rioux0 < 0)
{
+ // [N N p] - [p N N]
const int32_t rioux1 = int32_t(correlation[i - 2] +
correlation[i - 1]) -
int32_t(correlation[i + 1] +
@@ -73,7 +85,7 @@ float process_column(const uint8_t (&column)[])
x1 = i - 1;
y1 = rioux0;
y2 = rioux1;
- maxSum = sum;
+ maxTripleSum = sum;
}
}
}
@@ -88,23 +100,40 @@ float process_column(const uint8_t (&column)[])
return result;
}
+// uint8_t &Image::dataAt(size_t row, size_t col)
+// {
+// const auto index = img_width * row + col;
+// return *(data + index);
+// }
+
void Image::rotate()
{
- start_timer(rotate);
+ QElapsedTimer t;
+ t.start();
+ // start_timer(rotate);
using namespace std;
+ // Image::data_t &d = *data;
#pragma omp parallel
#pragma omp for
- for (size_t i = 0; i < img_height; ++i)
- {
- for (size_t j = 0; j < img_width; ++j)
- {
- rotated_cw[j][i] = data[img_height - i][j];
+ for (size_t i = 0; i < img_height; ++i) {
+ for (size_t j = 0; j < img_width; ++j) {
+ // for (size_t j = 0; j < img_width; j += 4) {
+ rotated_cw[j][i] = data[img_height - i - 1][j];
+ // rotated_cw[j][i] = d[img_height - i - 1][j];
+
+ // rotated_cw[j + 0][i] = data[img_height - i - 1][j + 0];
+ // rotated_cw[j + 1][i] = data[img_height - i - 1][j + 1];
+ // rotated_cw[j + 2][i] = data[img_height - i - 1][j + 2];
+ // rotated_cw[j + 3][i] = data[img_height - i - 1][j + 3];
+
+ // rotated_cw[j][i] = dataAt(img_height - i - 1, j);
}
}
- stop_timer(rotate);
+ // stop_timer(rotate);
+ rot_elapsed_ns += t.nsecsElapsed();
}
std::shared_ptr<Pixels> Image::pixels() const
@@ -113,6 +142,11 @@ std::shared_ptr<Pixels> Image::pixels() const
result->counters = counters;
start_timer(process_columns);
+ // std::transform(std::execution::par_unseq,
+ // rotated_cw.cbegin(),
+ // rotated_cw.cend(),
+ // result->pixels.begin(),
+ // [](const auto &column) -> float { return process_column(column); });
#pragma omp chunk
#pragma omp parallel for
@@ -139,15 +173,21 @@ void Image::copyFromData(const void *src, size_t size)
switch (pixelFormat) {
case libcamera::formats::R8: {
// std::cout << "R8" << std::endl;
- memcpy(data, src, size);
+ // memcpy(data, src, size);
+ // memcpy(data->data(), src, size);
+ memcpy(&data, src, size);
+ // data = (uint8_t *) src;
break;
}
case libcamera::formats::R16: {
// std::cout << "R16" << std::endl;
+ // data_t &d = *data;
#pragma omp parallel
#pragma omp for
for (size_t i = 0; i < img_size; i++) {
data[i / img_width][i % img_width] = (((uint16_t *) src)[i] & 0xff00) >> 8;
+ // d[i / img_width][i % img_width] = (((uint16_t *) src)[i] & 0xff00) >> 8;
+ // dataAt(i / img_width, i % img_width) = (((uint16_t *) src)[i] & 0xff00) >> 8;
}
break;
}
diff --git a/src/image.h b/src/image.h
index 934aa40..b4e8a58 100644
--- a/src/image.h
+++ b/src/image.h
@@ -3,6 +3,8 @@
#include "constants.h"
#include "typedefs.h"
+#define USER_PTR
+
class Pixels;
// TODO: template
@@ -10,8 +12,20 @@ struct Image
{
int width{0};
int height{0};
- uint8_t data[img_height][img_width] = {{0}};
- uint8_t rotated_cw[img_width][img_height] = {{0}};
+ // uint8_t data[img_height][img_width] = {{0}};
+
+ using row_t = std::array<uint8_t, img_width>;
+ using data_t = std::array<row_t, img_height>;
+ using rotated_row_t = std::array<uint8_t, img_height>;
+ using column_t = rotated_row_t;
+ using rotated_data_t = std::array<column_t, img_width>;
+ // data_t d;
+ data_t data;
+ // data_t *data;
+ // uint8_t *data = {nullptr};
+ // uint8_t &dataAt(size_t row, size_t col);
+ // uint8_t rotated_cw[img_width][img_height] = {{0}};
+ rotated_data_t rotated_cw;
// size_t dataSize{0};
// unsigned int stride{0};
libcamera::PixelFormat pixelFormat{0};
diff --git a/src/imagealgos.cpp b/src/imagealgos.cpp
new file mode 100644
index 0000000..2257244
--- /dev/null
+++ b/src/imagealgos.cpp
@@ -0,0 +1,465 @@
+#include "imagealgos.h"
+
+#include <QDebug>
+
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+
+#include <algorithm>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <utility>
+
+#include "macro.h"
+
+uint8_t pgm_image[64 + img_width * img_height * sizeof(uint8_t)];
+size_t pgm_image_size = 0;
+std::mutex pgm_image_mtx;
+
+template<typename T>
+T median3(const T &a, const T &b, const T &c)
+{
+ using namespace std;
+ return max(min(a, b), min(max(a, b), c));
+}
+
+// size_t pgm_save(std::shared_ptr<Image> img, FILE *outfile, bool really_save)
+size_t pgm_save(Image *img, FILE *outfile, bool really_save)
+{
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+
+ size_t n{0};
+
+ n += sprintf((char*)pgm_image, "P5\n%d %d\n%d\n",
+ img->width, img->height, 0xFF);
+
+ memcpy(pgm_image + n, img->data.data(), sizeof(img->data));
+ // memcpy(pgm_image + n, img->data->data(), sizeof(img->data));
+ n += sizeof(img->data);
+ pgm_image_size = n;
+
+ if (really_save)
+ {
+ if (outfile) {
+ fwrite(pgm_image, 1, pgm_image_size, outfile);
+ fflush(outfile);
+
+ } else {
+ std::cerr << __func__ << ": output filename is nullptr, cannot save"
+ << std::endl;
+ }
+ }
+
+ // std::cout << "written pgm image" << std::endl;
+ return n;
+}
+
+// void unpack_10bit(uint8_t const *src, Image const &image, uint16_t *dest)
+// {
+// unsigned int w_align = image.width & ~3;
+// for (unsigned int y = 0; y < image.height; y++, src += image.stride)
+// {
+// uint8_t const *ptr = src;
+// unsigned int x;
+// for (x = 0; x < w_align; x += 4, ptr += 5)
+// {
+// *dest++ = (ptr[0] << 2) | ((ptr[4] >> 0) & 3);
+// *dest++ = (ptr[1] << 2) | ((ptr[4] >> 2) & 3);
+// *dest++ = (ptr[2] << 2) | ((ptr[4] >> 4) & 3);
+// *dest++ = (ptr[3] << 2) | ((ptr[4] >> 6) & 3);
+// }
+// for (; x < image.width; x++)
+// *dest++ = (ptr[x & 3] << 2) | ((ptr[4] >> ((x & 3) << 1)) & 3);
+// }
+// }
+
+// void unpack_16bit(uint8_t const *src, Image const &image, uint16_t *dest)
+// {
+// start_timer(unpack_16bit);
+// /* Assume the pixels in memory are already in native byte order */
+// unsigned int w = image.width;
+
+// for (unsigned int y = 0; y < image.height; y++)
+// {
+// memcpy(dest, src, 2 * w);
+// dest += w;
+// src += image.stride;
+// }
+// stop_timer(unpack_16bit);
+// }
+
+template<class T, size_t N>
+constexpr size_t mysize(T (&)[N]) { return N; }
+
+// void smooth_column(uint8_t (&column)[])
+// {
+// for (size_t i = 1; i < img_height - 1; ++i) {
+// column[i] = median3(column[i - 1], column[i], column[i + 1]);
+// }
+// }
+
+// float process_column(uint8_t (&column)[])
+// {
+// std::cout << "aaaaaaaaaaa\n";
+// start_timer(process_column);
+
+// float result = std::numeric_limits<float>::quiet_NaN();
+
+// constexpr uint32_t signalThreshold = 900; // = SKO * sqrt(patternSize)
+// static constexpr uint32_t patternOffset = patternSize - ((patternSize % 2 == 1) ? 1 : 0);
+// const uint32_t correlationSize = img_height - patternSize + ((patternSize % 2 == 1) ? 1 : 0);
+// uint32_t correlation[img_height];
+// uint32_t integralSum[img_height];
+// uint32_t maxSum = signalThreshold * 50;
+// uint32_t x1 = 0;
+// int32_t y1 = 0;
+// int32_t y2 = 0;
+
+// memset(correlation, 0, img_height * sizeof(correlation[0]));
+// integralSum[0] = 0;
+
+// for (uint32_t i = 1; i < img_height; ++i) {
+// // if (column[i + 0] < 100) { column[i + 0] = 0; } integralSum[i + 0] = column[i + 0] / 256 + integralSum[i + 0 - 1];
+// // if (column[i + 1] < 100) { column[i + 1] = 0; } integralSum[i + 1] = column[i + 1] / 256 + integralSum[i + 1 - 1];
+// // if (column[i + 2] < 100) { column[i + 2] = 0; } integralSum[i + 2] = column[i + 2] / 256 + integralSum[i + 2 - 1];
+// // if (column[i + 3] < 100) { column[i + 3] = 0; } integralSum[i + 3] = column[i + 3] / 256 + integralSum[i + 3 - 1];
+// // if (column[i + 4] < 100) { column[i + 4] = 0; } integralSum[i + 4] = column[i + 4] / 256 + integralSum[i + 4 - 1];
+// // if (column[i + 5] < 100) { column[i + 5] = 0; } integralSum[i + 5] = column[i + 5] / 256 + integralSum[i + 5 - 1];
+// // if (column[i + 6] < 100) { column[i + 6] = 0; } integralSum[i + 6] = column[i + 6] / 256 + integralSum[i + 6 - 1];
+// // if (column[i + 7] < 100) { column[i + 7] = 0; } integralSum[i + 7] = column[i + 7] / 256 + integralSum[i + 7 - 1];
+// // if (column[i + 8] < 100) { column[i + 8] = 0; } integralSum[i + 8] = column[i + 8] / 256 + integralSum[i + 8 - 1];
+// // if (column[i + 9] < 100) { column[i + 9] = 0; } integralSum[i + 9] = column[i + 9] / 256 + integralSum[i + 9 - 1];
+// // if (column[i + 10] < 100) { column[i + 10] = 0; } integralSum[i + 10] = column[i + 10] / 256 + integralSum[i + 10 - 1];
+// // if (column[i + 11] < 100) { column[i + 11] = 0; } integralSum[i + 11] = column[i + 11] / 256 + integralSum[i + 11 - 1];
+// // if (column[i + 12] < 100) { column[i + 12] = 0; } integralSum[i + 12] = column[i + 12] / 256 + integralSum[i + 12 - 1];
+// // if (column[i + 13] < 100) { column[i + 13] = 0; } integralSum[i + 13] = column[i + 13] / 256 + integralSum[i + 13 - 1];
+// // if (column[i + 14] < 100) { column[i + 14] = 0; } integralSum[i + 14] = column[i + 14] / 256 + integralSum[i + 14 - 1];
+// // if (column[i + 15] < 100) { column[i + 15] = 0; } integralSum[i + 15] = column[i + 15] / 256 + integralSum[i + 15 - 1];
+
+// if (column[i] < 100) {
+// column[i] = 0;
+// }
+// integralSum[i] = column[i] + integralSum[i - 1];
+// }
+
+// // maxSum = 0 ;
+// // size_t maxIdx { 0 };
+
+// // for (size_t i = 0; i < img_height - patternSize; ++i) {
+// // const auto sum = integralSum[i + patternSize] - integralSum[i];
+// // if (sum > maxSum) {
+// // maxSum = sum;
+// // maxIdx = i;
+// // }
+// // }
+
+// // Algo genetic(column + maxIdx);
+// // // std::cout << "maxIdx " << maxIdx << std::endl;
+
+// // // return maxIdx + genetic.run().a;
+// // return 500;
+// // return img_height - maxIdx - genetic.run().a;
+
+// for (uint32_t i = 0; i < correlationSize; ++i)
+// correlation[i + patternSize / 2] = column[i + patternSize / 2] / 256
+// * (integralSum[i + patternOffset] - integralSum[i]);
+
+// for (uint32_t i = 3; i < img_height - 2; ++i) {
+// const auto sum = correlation[i - 1] + correlation[i] + correlation[i + 1];
+
+// if (sum > maxSum) {
+// const int32_t rioux0 = int32_t(correlation[i - 2 - 1] + correlation[i - 1 - 1])
+// - int32_t(correlation[i + 1 - 1] + correlation[i + 2 - 1]);
+
+// if (rioux0 < 0) {
+// const int32_t rioux1 = int32_t(correlation[i - 2] + correlation[i - 1])
+// - int32_t(correlation[i + 1] + correlation[i + 2]);
+
+// if (rioux1 >= 0) {
+// x1 = i - 1;
+// y1 = rioux0;
+// y2 = rioux1;
+// maxSum = sum;
+// }
+// }
+// }
+// }
+
+// result = (y2 != y1) ? (float(x1) - (float(y1) / (y2 - y1)))
+// : std::numeric_limits<float>::quiet_NaN();
+
+// static bool result_done = false;
+// if (!result_done) {
+// std::cout << "result " << result << std::endl;
+// result_done = true;
+// }
+// // std::cout << "result is '" << result << "'\n";
+
+// // stop_timer(process_column);
+
+// return result;
+
+// // center of mass
+// #if 0
+// auto max_el = std::max_element(std::begin(accumulated_sum),
+// std::end(accumulated_sum) - window_size);
+
+// size_t max_sum_idx = max_el - std::begin(accumulated_sum) + window_size;
+
+// double sum_w = 0;
+// double prod_wx = 0;
+// double wmc = 0;
+
+// for(int i = max_sum_idx - window_size; i < max_sum_idx; ++i)
+// {
+// prod_wx += column[i] * i;
+// sum_w += column[i];
+// }
+
+// wmc = float(prod_wx) / float(sum_w);
+
+// result = img_height - wmc;
+
+// return result;
+// #endif
+// }
+
+// Pixels process_columns(Image &image)
+// {
+// Pixels result;
+// result.counters = image.counters;
+
+// // std::cout << "here\n";
+// start_timer(process_columns);
+
+// for (size_t i = 0; i < image.width; i++)
+// {
+// // smooth_column(image.rotated_cw[i]);
+// result.pixels[i] = process_column(image.rotated_cw[i]);
+// // Algo genetic(image.rotated_cw[i]);
+
+// // image.pixels[i] = genetic.run().a;
+
+// // if (i == 0) {
+// // std::cout << "pixel: " << image.pixels[i] << std::endl;
+// // }
+// }
+
+// stop_timer(process_columns);
+
+// return result;
+// }
+
+QList<QLineF> pixelsToLines(const Pixels &rawProfile)
+{
+ const auto& pixels = rawProfile.pixels;
+ QList<QPointF> points(pixels.size());
+
+ for (int i = 0; i < pixels.size(); ++i)
+ {
+ points[i] = { qreal(i) - img_width / 2, pixels.at(i) };
+ }
+
+ // qDebug() << "mid points" << points.mid(points.count() - 3, 6);
+
+ return pointsToLines(std::move(points));
+}
+
+QList<QLineF> pointsToLines(const QList<QPointF> &points)
+{
+ constexpr double maxDistanceFromLine { 3 };
+ constexpr double minLineLength { 10 };
+
+ QList<int> lineAnchors{0, int(points.count() - 1)};
+
+ auto vecLength = [](const QPointF& vector) {
+ return std::hypot(vector.x(), vector.y());
+ };
+
+ auto distanceToLine = [vecLength](const QPointF& point, const QLineF& line) {
+ // const auto d1 = point - line.p1();
+ // const auto d2 = line.p2() - line.p1();
+
+ // const auto norm = vecLength(d2);
+ // qDebug() << "norm" << norm;
+
+ // if (norm <= std::numeric_limits<double>::epsilon())
+ // {
+ // qDebug() << "AAAAAAAAAAAAAAAAAA";
+ // return vecLength(d1);
+ // }
+
+ // const auto result = std::fabs(d1.x() * d2.y() - d1.y() * d2.x()) / norm;
+
+ // if (!qFuzzyIsNull(result))
+ // {
+ // qDebug() << "NOT NULL" << result << point << line;
+ // }
+
+ // return result;
+ // transform to loocal coordinates system (0,0) - (lx, ly)
+ QPointF p1 = line.p1();
+ QPointF p2 = line.p2();
+ const auto& p = point;
+ qreal x = p.x() - p1.x();
+ qreal y = p.y() - p1.y();
+ qreal x2 = p2.x() - p1.x();
+ qreal y2 = p2.y() - p1.y();
+
+ // if line is a point (nodes are the same) =>
+ // just return distance between point and one line node
+ qreal norm = sqrt(x2*x2 + y2*y2);
+ if (norm <= std::numeric_limits<int>::epsilon())
+ return sqrt(x*x + y*y);
+
+ // distance
+ // qDebug() << "dist" << fabs(x*y2 - y*x2) / norm << point << line;
+ return fabs(x * y2 - y * x2) / norm;
+ };
+
+ // for (const auto& p : points)
+ // {
+ // qDebug() << "\t" << p;
+ // }
+
+ for (int i = 0; i < lineAnchors.count() - 1; ++i)
+ {
+ const auto &lineFirst = i;
+ const auto &lineLast = i + 1;
+ const auto& leftPointIdx = lineAnchors.at(lineFirst);
+ const auto& rightPointIdx = lineAnchors.at(lineLast);
+
+ if (rightPointIdx - leftPointIdx < 2)
+ {
+ continue;
+ }
+
+ const QLineF line { points.at(leftPointIdx), points.at(rightPointIdx) };
+
+ // std::cout << "max between " << leftPointIdx + 1 << " and "
+ // << rightPointIdx - 1 << std::endl;
+
+ const auto farthest = std::max_element(
+ // std::execution::par_unseq,
+ points.cbegin() + leftPointIdx + 1,
+ points.cbegin() + rightPointIdx - 1,
+ [line, distanceToLine](const QPointF& a, const QPointF& b) {
+ return distanceToLine(a, line) < distanceToLine(b, line);
+ }
+ );
+
+ // std::cout << "done max" << std::endl;
+ // auto tmp = *farthest;
+ // std::cout << "done farthest" << std::endl;
+
+ // qDebug() << "farthest point" << distanceToLine(*farthest, line);
+ // qDebug() << "farthest dist" << distanceToLine(*farthest, line);
+ // qDebug() << "that's from line" << line;
+
+ if (distanceToLine(*farthest, line) > maxDistanceFromLine)
+ {
+ // std::cout << "try insert at " << i + 1 << ". lineAnchors.size is
+ // "
+ // << lineAnchors.size()
+ // << ". inserting this: " << farthest - points.cbegin()
+ // << std::endl;
+ lineAnchors.insert(i + 1, farthest - points.cbegin());
+ // std::cout << "done insert" << std::endl;
+ --i;
+ // qDebug() << "I'm here" << i;
+ continue;
+ }
+ }
+
+ struct LineAB
+ {
+ double a { std::numeric_limits<double>::quiet_NaN() };
+ double b { std::numeric_limits<double>::quiet_NaN() };
+
+ LineAB(const QList<QPointF>& points) {
+ if (points.isEmpty())
+ {
+ return;
+ }
+
+ double sumX { 0 };
+ double sumY { 0 };
+ double sumXY { 0 };
+ double sumXX { 0 };
+
+ // FIXME: validate
+ // for (const auto& point : points)
+ const int delta = points.count() * 0.15;
+ for (int i = delta; i < points.count() - delta; ++i)
+ {
+ const auto& point = points.at(i);
+ sumX += point.x();
+ sumY += point.y();
+ sumXY += point.x() * point.y();
+ sumXX += point.x() * point.x();
+ }
+
+ // sumX /= points.count();
+ // sumY /= points.count();
+ // sumXY /= points.count();
+ // sumXX /= points.count();
+
+ const int n = points.count() - delta * 2;
+ Q_ASSERT_X(n > 0, Q_FUNC_INFO, "n <= 0");
+
+ a = (n * sumXY - sumX * sumY) /
+ (n * sumXX - sumX * sumX);
+ b = (sumY - a * sumX) / n;
+ }
+ };
+
+ // auto pointsToLineAB =
+
+ // qDebug() << "line anchors count is" << lineAnchors.count();
+
+ constexpr bool useLsd = true;
+
+ QList<QLineF> result { lineAnchors.length() - 1 };
+
+ for (int i = 0; i < lineAnchors.count() - 1; ++i)
+ {
+ const auto& leftPointIdx = lineAnchors.at(i);
+ const auto& rightPointIdx = lineAnchors.at(i + 1);
+
+ QPointF leftPoint = points.at(leftPointIdx);
+ QPointF rightPoint = points.at(rightPointIdx);
+
+ LineAB lineAB(points.mid(leftPointIdx, rightPointIdx - leftPointIdx));
+
+ leftPoint.setY(lineAB.a * leftPoint.x() + lineAB.b);
+ rightPoint.setY(lineAB.a * rightPoint.x() + lineAB.b);
+
+ if (useLsd)
+ result[i] = QLineF{ std::move(leftPoint), std::move(rightPoint) };
+ else
+ result[i] = QLineF{ points.at(lineAnchors.at(i)), points.at(lineAnchors.at(i + 1)) };
+ }
+
+ if (useLsd)
+ {
+ for (int i = 0; i < result.count() - 1; ++i)
+ {
+ auto& lineA = result[i];
+ auto& lineB = result[i + 1];
+
+ QPointF intersectionPoint {};
+
+ if (lineA.intersects(lineB, &intersectionPoint) != QLineF::NoIntersection)
+ {
+ lineA.setP2(intersectionPoint);
+ lineB.setP1(intersectionPoint);
+ }
+ }
+ }
+
+ // qDebug() << result;
+
+ return result;
+}
diff --git a/src/imagealgos.h b/src/imagealgos.h
new file mode 100644
index 0000000..4fd9d53
--- /dev/null
+++ b/src/imagealgos.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <QLineF>
+#include <QList>
+
+#include "image.h"
+#include "pixels.h"
+
+// size_t pgm_save(std::shared_ptr<Image> img,
+size_t pgm_save(Image *img, FILE *outfile = nullptr, bool really_save = false);
+
+// void unpack_10bit(uint8_t const *src, Image const &image, uint16_t *dest);
+// void unpack_16bit(uint8_t const *src, Image const &image, uint16_t *dest);
+
+// Pixels process_columns(Image & image);
+
+QList<QLineF> pixelsToLines(const Pixels& rawProfile);
+QList<QLineF> pointsToLines(const QList<QPointF>& points);
diff --git a/src/macro.h b/src/macro.h
new file mode 100644
index 0000000..9bcc8e0
--- /dev/null
+++ b/src/macro.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <chrono>
+#include <iostream>
+
+#define start_timer(name) \
+ std::chrono::steady_clock::time_point begin_##name = std::chrono::steady_clock::now();
+
+#define stop_timer(name) \
+ std::chrono::steady_clock::time_point end_##name = std::chrono::steady_clock::now(); \
+ // std::cout << #name << " (us): " \
+ // << std::chrono::duration_cast<std::chrono::microseconds>(end_##name - begin_##name) \
+ // << std::endl;
+
+#define INIT_FIELD(name) m_##name(name)
+
+#define INNO_MAKER
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..d986f91
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,798 @@
+#include <chrono>
+#include <errno.h>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string.h>
+#include <sys/mman.h>
+#include <thread>
+
+#include "LibCamera.h"
+#include "calibration.h"
+#include "camera/innomakerov9281.h"
+#include "camera/ov9281.h"
+#include "dumps.h"
+#include "fuck_intel.h"
+#include "genetic_algos.h"
+#include "httpservice.h"
+#include "imagealgos.h"
+#include "laser.h"
+#include "macro.h"
+#include "pigpio.h"
+#include "printerclient.h"
+#include "profile.h"
+#include "rotaryencoder.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QHttpServer>
+#include <QImage>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QSerialPort>
+#include <QTextStream>
+#include <QTimer>
+#include <QtConcurrent/QtConcurrent>
+
+#define try_apply_config() \
+ if (!applyConfig(config)) \
+ { \
+ camera->release(); \
+ cm->stop(); \
+\
+ return EXIT_FAILURE; \
+ }
+
+ScanningModeFlags scanningModeFlags{ScanningModeFlags::None};
+
+QElapsedTimer calibrationTimer;
+
+extern volatile int32_t positionSteps;
+
+requested_params_t requested_params;
+
+namespace {
+// std::shared_ptr<Image> img;
+Image *img = nullptr;
+Pixels pixels;
+std::vector<Pixels> calibrationPixels;
+QMutex calibrationPixelsMutex;
+} // namespace
+
+using namespace std::chrono_literals;
+
+// static std::shared_ptr<libcamera::Camera> camera;
+// std::unique_ptr<libcamera::CameraConfiguration> config;
+// static std::map<int, std::pair<void*, unsigned int>> mappedBuffers_;
+// std::vector<std::unique_ptr<libcamera::Request>> requests;
+libcamera::ControlList lastControls;
+
+namespace {
+CalibrationTablePtr calibrationTableZ;
+CalibrationTablePtr calibrationTableX;
+} // namespace
+
+// static bool applyConfig(
+// const std::unique_ptr<libcamera::CameraConfiguration>& config
+// );
+// static void onRequestCompleted(libcamera::Request* completed_request);
+// static void printControls();
+// static QList<Pixels> filter(const QList<Pixels>& rawProfiles);
+
+auto printPixels = [](const auto& pixels) {
+ for (size_t i = (img_width - 10) / 2;
+ i < img_width - ((img_width - 10) / 2);
+ ++i)
+ {
+ std::cout << pixels[i] << " ";
+ }
+ std::cout << std::endl;
+};
+
+// void onNewImage(std::shared_ptr<Image> image)
+void onNewImage(Image &image)
+{
+ // std::cout << __func__ << std::endl << std::flush;
+
+ // if (!image)
+ // {
+ // qDebug() << __func__ << "no image";
+ // return;
+ // }
+
+ ::img = &image;
+}
+
+void onNewPixels(std::shared_ptr<Pixels> pixels)
+{
+ // std::cout << __func__ << std::endl << std::flush;
+
+ if (!pixels)
+ {
+ qDebug() << __func__ << "got null pixels";
+ }
+
+ if (!*pixels)
+ {
+ // qDebug() << __func__ << "got empty pixels";
+ }
+
+ for (size_t i = 640 - 5; i < 640 + 5; i++)
+ {
+ // std::cout << pixels->pixels[i] << " ";
+ }
+
+ // std::cout << std::endl
+
+ ::pixels = *pixels;
+}
+
+bool initLaser();
+
+int main(int argc, char* argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ QList<QFuture<void>> initializers;
+
+#ifdef INNO_MAKER
+ if (false)
+ {
+ std::cout << std::boolalpha;
+ InnoMakerOV9281 innoMakerCam;
+ qDebug() << "init:" << innoMakerCam.init();
+ qDebug() << "set exposure:" << innoMakerCam.setExposureTimeUs(3000000);
+ qDebug() << "set gain:" << innoMakerCam.setGain(2);
+
+ innoMakerCam.startStream();
+ QThread::sleep(3);
+ qDebug() << "should be stopped";
+ // Image buf;
+
+ // for (size_t i = 0; i < 1000; ++i) {
+ // if (!innoMakerCam.getImage(buf)) {
+ // break;
+ // }
+
+ // buf.rotate();
+ // auto pixels = buf.pixels();
+ // }
+ }
+ // qDebug() << "ok";
+ // exit(EXIT_SUCCESS);
+#endif
+
+ // if (false)
+ qDebug() << "size of raw profile" << sizeof(Pixels);
+ if (true)
+ {
+ // open binary calibration table
+ if (true)
+ {
+ initializers << QtConcurrent::run([&]() {
+ if (!openCalibrationTable(
+ // "/home/user/dumps/binz.calibration_table",
+ "/tmp/binz.calibration_table",
+ ::calibrationTableZ)) {
+ exit(EXIT_FAILURE);
+ }
+ });
+
+ initializers << QtConcurrent::run([&]() {
+ if (!openCalibrationTable(
+ // "/home/user/dumps/binx.calibration_table",
+ "/tmp/binx.calibration_table",
+ ::calibrationTableX)) {
+ exit(EXIT_FAILURE);
+ }
+ });
+ }
+
+ if (false)
+ {
+ // z
+ // if (!openCalibrationTable(
+ // "/home/user/dumps/binz.calibration_table",
+ // ::calibrationTableZ
+ // ))
+ // {
+ // exit(EXIT_FAILURE);
+ // }
+
+ // if (!calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/imageZ.png"))
+ // {
+ // qDebug() << "cannot save imageZ.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ // interpolate(::calibrationTableZ);
+ // exit(EXIT_SUCCESS);
+
+ // calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/imageZ_interpolated.png");
+
+ auto rawProfiles = openDump("/home/user/dumps/binx");
+ qDebug() << "raw x-profiles count is" << rawProfiles.size();
+ // qDebug() << "height" << calibrationColumnHeight;
+
+ auto filteredRawProfiles = filter(std::move(rawProfiles));
+ qDebug() << "filtered x-profiles count is"
+ << filteredRawProfiles.count();
+
+ ::calibrationTableX = calibrateX(std::move(filteredRawProfiles));
+
+ // for (size_t i = 9471; i < 9472; i++) {
+ // std::cout << "row #" << i << ": ";
+
+ // for (size_t j = 0; j < 1280; ++j) {
+ // const auto& p = ::calibrationTableX->at(j).at(i);
+ // std::cout << p << ' ';
+ // }
+
+ // std::cout << std::endl;
+ // }
+
+ // x
+ // qDebug() << "open x table";
+ // if (!openCalibrationTable("/home/user/dumps/binx.calibration_table",
+ // ::calibrationTableX)) {
+ // exit(EXIT_FAILURE);
+ // }
+
+ // if (!calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/imageX.png")) {
+ // qDebug() << "cannot save imageX.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ // for (size_t i = 9471; i < 9472; i++) {
+ // std::cout << "row #" << i << ": ";
+
+ // for (size_t j = 0; j < 1280; ++j) {
+ // const auto& p = ::calibrationTableX->at(j).at(i);
+ // std::cout << p << ' ';
+ // }
+
+ // std::cout << std::endl;
+ // }
+
+ // exit(EXIT_SUCCESS);
+ interpolate(::calibrationTableX);
+
+ // calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/imageX_interpolated.png");
+ }
+
+ // load binary calibration dumps and calibrate
+ if (false)
+ {
+ if (true)
+ {
+ auto rawProfiles = openDump("/home/user/dumps/binz");
+ // auto rawProfiles = openDump("/home/user/dumps/z");
+ qDebug() << "raw z-profiles count is" << rawProfiles.size();
+ // qDebug() << "height" << calibrationColumnHeight;
+
+ auto filteredRawProfiles = filter(std::move(rawProfiles));
+ qDebug() << "filtered z-profiles count is"
+ << filteredRawProfiles.count();
+
+ ::calibrationTableZ = calibrateZ(std::move(filteredRawProfiles),
+ requested_params.stepsPerMm);
+
+ // bool ok = calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/z/imageZ.png");
+
+ // if (!ok)
+ // {
+ // qDebug() << "cannot save imageZ.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ interpolate(::calibrationTableZ);
+
+ if (!dump(::calibrationTableZ,
+ "/home/user/dumps/binz.calibration_table"))
+ {
+ qApp->exit(EXIT_FAILURE);
+ }
+ // calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/z/imageZ_interpolated.png");
+ // exit(EXIT_SUCCESS);
+ }
+
+ qDebug()
+ << "--------------------------------------------------------";
+
+ if (true)
+ {
+ auto rawProfiles = openDump("/home/user/dumps/binx");
+ qDebug() << "raw x-profiles count is" << rawProfiles.size();
+ // qDebug() << "height" << calibrationColumnHeight;
+
+ auto filteredRawProfiles = filter(std::move(rawProfiles));
+ qDebug() << "filtered x-profiles count is"
+ << filteredRawProfiles.count();
+
+ ::calibrationTableX = calibrateX(std::move(filteredRawProfiles));
+
+ // bool ok = calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/z/imageX.png");
+
+ // if (!ok)
+ // {
+ // qDebug() << "cannot save imageX.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ interpolate(::calibrationTableX);
+
+ if (!dump(::calibrationTableX,
+ "/home/user/dumps/binx.calibration_table"))
+ {
+ qApp->exit(EXIT_FAILURE);
+ }
+
+ // calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/z/imageX_interpolated.png");
+ }
+ }
+ }
+
+ // exit(EXIT_SUCCESS);
+
+ // if (!initLaser()) {
+ // return EXIT_FAILURE;
+ // }
+
+ // PrinterClient printerClient;
+
+ QElapsedTimer t;
+ t.start();
+
+ qDebug() << "msecs before encoder:" << t.elapsed();
+
+ // RotaryEncoder encoder;
+
+ qDebug() << "msecs before camera:" << t.elapsed();
+ // FIXME: don't use one var for everything
+ int ret;
+#ifndef INNO_MAKER
+ std::unique_ptr<libcamera::CameraManager> cm =
+ std::make_unique<libcamera::CameraManager>();
+ cm->start();
+#endif
+ // const auto cameras = cm->cameras();
+ // const auto cameras = OV9281::search(cm);
+ const auto cameras = InnoMakerOV9281::search();
+ // const auto cameras =
+
+ if (cameras.empty())
+ {
+ std::cerr << "No cameras were identified on the system." << std::endl;
+#ifndef INNO_MAKER
+ cm->stop();
+#endif
+
+ return EXIT_FAILURE;
+ }
+
+ auto camera = cameras.at(0);
+
+#ifndef INNO_MAKER
+ camera->printControls();
+#endif
+
+ std::cout << "connect everything" << std::endl;
+ // camera->newPixels.connect(&onNewPixels);
+ // camera->newImage.connect(&onNewImage);
+ camera->newImageCallback = &onNewImage;
+ camera->newPixelsCallback = &onNewPixels;
+
+ for (auto& i : initializers)
+ i.waitForFinished();
+ std::cout << "loaded calibration tables" << std::endl;
+
+ if (!camera->startStream())
+ {
+#ifndef INNO_MAKER
+ cm->stop();
+#endif
+
+ return EXIT_FAILURE;
+ }
+
+ QHttpServer qHttpServer;
+ qHttpServer.route("/v1/sensor/image", [&]() {
+ // std::cout << "http: image" << std::endl << std::flush;
+ // FILE *f = fopen("/tmp/img.pgm", "w");
+ // static bool save = false;
+ pgm_save(::img);
+ // save = false;
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // qDebug() << "mutex locked";
+ // qDebug() << "image saved to array";
+ return QByteArray((const char*) pgm_image, pgm_image_size);
+ });
+ qHttpServer.route("/v1/sensor/image2", [&]() {
+ // std::cout << "http: image2" << std::endl;
+ pgm_save(::img);
+
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // qDebug() << "image2";
+ return QByteArray((const char*) pgm_image, pgm_image_size);
+ });
+ // qHttpServer.route("/v1/sensor/exposureTimeUs", [&]() {
+ // // std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // return "123";
+ // });
+ qHttpServer.route("/v1/pixels", [&]() {
+ // std::cout << "http: pixels" << std::endl;
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+
+ QJsonArray pixels;
+
+ for (size_t i = 0; i < img_width; ++i)
+ {
+ // pixels << img_height - img.pixels[i];
+ pixels << ::pixels.pixels[i];
+ }
+
+ QJsonObject json;
+ json["pixels"] = pixels;
+ // json["encoderPosition"] = qint64{encoder.position()};
+ // FIXME: get prom pixels struct
+ json["measurementCounter"] = qint64{img->counters.measurementCounter};
+ json["timestampUs"] = qint64(img->counters.timestampUs);
+
+ const auto lines = pixelsToLines(::pixels);
+
+ // qDebug() << "lines count is " << lines.count();
+
+ QJsonArray jsonLines;
+
+ for (const auto& l : lines)
+ {
+ jsonLines << QJsonArray{QJsonArray{l.p1().x(), l.p1().y()},
+ QJsonArray{l.p2().x(), l.p2().y()}};
+ }
+
+ json["lines"] = jsonLines;
+
+ return QHttpServerResponse(QJsonDocument(json).toJson());
+ });
+
+ qHttpServer.route("/v1/profile", [&]() -> QHttpServerResponse {
+ // std::cout << "http: profile" << std::endl;
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+
+ if (!::calibrationTableZ || !::calibrationTableX)
+ return QHttpServerResponse::StatusCode::ServiceUnavailable;
+
+ const Profile profile(::pixels,
+ ::calibrationTableZ,
+ ::calibrationTableX);
+
+ const QJsonObject json{{"profile", QJsonObject(profile)}};
+
+ return QHttpServerResponse(QJsonDocument(json).toJson());
+ });
+
+ qHttpServer
+ .route("/v1/commands/resetEncoder",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: resetEncoder" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
+
+ qDebug() << "reset encoder";
+
+ positionSteps = 0;
+
+ return QHttpServerResponse::StatusCode::Ok;
+ });
+
+ qHttpServer
+ .route("/v1/commands/startCalibration",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: startCalibration" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
+
+ qDebug() << "start calibration";
+
+ // TODO: use flags
+ scanningModeFlags = ScanningModeFlags::Calibration;
+ calibrationTimer.start();
+
+ return QHttpServerResponse::StatusCode::Ok;
+ });
+
+ qHttpServer
+ .route("/v1/commands/gCode",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: gCode" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
+
+ const auto command = request.body();
+
+ qDebug() << "send gCode:" << command;
+
+ // printerClient.sendCommand(command);
+
+ return QHttpServerResponse::StatusCode::Ok;
+ });
+
+ // qHttpServer
+ // .route("/v1/commands/startCalibration",
+ // [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ // std::cout << "http: startCalibration" << std::endl;
+ // if (request.method() != QHttpServerRequest::Method::Post)
+ // {
+ // return QHttpServerResponse::StatusCode::NotFound;
+ // }
+
+ // const auto command = request.body();
+
+ // qDebug() << "send gCode:" << command;
+
+ // // printerClient.sendCommand(command);
+
+ // return QHttpServerResponse::StatusCode::Ok;
+ // });
+
+ qHttpServer.route(
+ "/v1/sensor/params",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ // std::cout << "http: params" << std::endl;
+ switch (request.method())
+ {
+ case QHttpServerRequest::Method::Get: {
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ QJsonObject json;
+
+ // const libcamera::ControlIdMap& ctrlIdMap =
+ // camera->controls().idmap();
+
+ // qDebug() << "readParams:" << lastControls.size();
+ // qDebug() << request.method();
+
+ // for (const auto& [id, value] : lastControls)
+ // {
+ // const libcamera::ControlId* controlId = ctrlIdMap.at(id);
+ // auto name = QString::fromStdString(controlId->name());
+ // const auto valueStr =
+ // QString::fromStdString(value.toString());
+ // qDebug()
+ // << "\t param:" << controlId->id() << name << valueStr;
+
+ // name[0] = name[0].toLower();
+ // json[name] = valueStr;
+ // }
+
+ // json[laserLevelKey] = requested_params.laserLevel;
+
+ // qDebug() << "response body:" << json;
+
+ // QHttpServerResponse
+ return QHttpServerResponse(QJsonDocument(json).toJson());
+ }
+
+ case QHttpServerRequest::Method::Post: {
+ // qDebug() << "request body:" << request.body();
+
+ auto json = QJsonDocument::fromJson(request.body()).object();
+
+ if (json.contains(exposureTimeKey))
+ {
+ const int32_t value{json[exposureTimeKey].toInt()};
+
+ if (value == 0)
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+
+ // qDebug() << "set new exposure time:" << value;
+
+ // requested_params.exposureTime = value;
+ if (!camera->setExposureTimeUs(value))
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+ }
+
+ if (json.contains(gainKey))
+ {
+ const int32_t value{json[gainKey].toInt()};
+
+ if (value == 0)
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+
+ // qDebug() << "set gain:" << value;
+
+ // requested_params.exposureTime = value;
+ if (!camera->setGain(value))
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+ }
+
+ if (json.contains(laserLevelKey))
+ {
+ const int32_t value{json[laserLevelKey].toInt()};
+
+ // if (value == 0)
+ // {
+ // return QHttpServerResponse::StatusCode::NotFound;
+ // }
+
+ // qDebug() << "set new laserLevel:" << value;
+ if (!camera->setLaserLevel(value))
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+
+ requested_params.laserLevel = value;
+
+ // const QString laserLevelFile{
+ // "/sys/class/pwm/pwmchip2/pwm1/duty_cycle"};
+ // QFile f{laserLevelFile};
+
+ // if (!f.open(QFile::ReadWrite))
+ // {
+ // qDebug() << "cannot open laser level file:"
+ // << f.errorString();
+ // qDebug() << "file path is" << f.fileName();
+ // return QHttpServerResponse::StatusCode::InternalServerError;
+ // }
+
+ // QTextStream s{&f};
+
+ // s << value;
+
+ // s >> requested_params.laserLevel;
+
+ // qDebug() << "done with laser level";
+ }
+
+ return QHttpServerResponse(request.body());
+ }
+ default: {
+ return QHttpServerResponse(
+ QByteArray("unsupported http method"));
+ }
+ }
+ });
+
+ qDebug() << "listen: " << qHttpServer.listen(QHostAddress::Any, 8081);
+
+ QFuture<void> future = QtConcurrent::run([]() {
+ Port port(8080);
+ Address addr(Ipv4::any(), port);
+
+ HttpService httpService(addr);
+
+ size_t threads_count = 1;
+ httpService.init(threads_count);
+ httpService.start();
+ });
+
+ ////////////////////////////////////////////////////////////////////////////
+ std::clog << std::flush;
+ std::cerr << std::flush;
+ std::cout << "ok for now" << std::endl << std::flush;
+
+ // camera->stop();
+ // camera->release();
+ // cm->stop();
+
+ auto result = app.exec();
+
+ future.cancel();
+ future.waitForFinished();
+
+ // for (auto& [fd, mem] : mappedBuffers_)
+ // {
+ // munmap(mem.first, mem.second);
+ // }
+
+ // FIXME: crash somewhere here. proper libcamera finishing needed
+ // requests.clear();
+ // mappedBuffers_.clear();
+
+ // camera->stop();
+ // config.reset();
+ // allocator->free(stream);
+ // allocator.reset();
+ // camera->release();
+ // camera.reset();
+#ifndef INNO_MAKER
+ cm->stop();
+#endif
+
+ return result;
+}
+
+bool initLaser()
+{
+ const QLatin1String pwmChip{"pwmchip2"};
+ const uint16_t pwmChannel{1};
+ const QLatin1String pwmSystemRoot{"/sys/class/pwm"};
+ const QString pwmChipRoot{pwmSystemRoot + "/" + pwmChip};
+
+ const QString pwmExportFile{pwmChipRoot + "/export"};
+
+ QFile f{pwmExportFile};
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ QTextStream s{&f};
+ s << pwmChannel;
+
+ const QString pwm{QLatin1String("pwm%1").arg(QString::number(pwmChannel))};
+ const QString pwmRoot{pwmChipRoot + "/" + pwm};
+
+ const QString periodFilename{pwmRoot + "/period"};
+ f.close();
+ f.setFileName(periodFilename);
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ const unsigned periodHz{50'000};
+
+ s << periodHz;
+
+ const QString dutyCycleFilename{pwmRoot + "/duty_cycle"};
+ f.close();
+ f.setFileName(dutyCycleFilename);
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ const unsigned dutyCycle{3'000};
+
+ s << dutyCycle;
+
+ const QString enableFilename{pwmRoot + "/enable"};
+ f.close();
+ f.setFileName(enableFilename);
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ const int enable{1};
+
+ s << enable;
+
+ return true;
+}
diff --git a/src/printerclient.cpp b/src/printerclient.cpp
new file mode 100644
index 0000000..4fed38f
--- /dev/null
+++ b/src/printerclient.cpp
@@ -0,0 +1,86 @@
+#include "printerclient.h"
+
+#include <exception>
+
+#include <QDebug>
+#include <QFile>
+#include <QSerialPort>
+#include <QSerialPortInfo>
+
+QString getFirstTtyUSB()
+{
+ auto ports = QSerialPortInfo::availablePorts();
+
+ std::remove_if(ports.begin(), ports.end(), [](const auto& port) {
+ return !port.portName().contains("ttyUSB");
+ });
+
+ return ports.isEmpty() ? "" : ports.first().portName();
+}
+
+PrinterClient::PrinterClient(QObject* parent)
+ : QObject{parent} // , m_serialPort{new QSerialPort{"/dev/ttyUSB0", this}}
+ , m_serialPort{new QSerialPort{getFirstTtyUSB(), this}}
+{
+ if (!m_serialPort->setBaudRate(QSerialPort::Baud115200)) {
+ throw std::runtime_error(
+ "serial port: cannot set baud rate: " +
+ m_serialPort->errorString().toStdString());
+
+ return;
+ }
+
+ if (!m_serialPort->open(QFile::ReadWrite)) {
+ throw std::runtime_error(
+ "cannot open serial port: " +
+ m_serialPort->errorString().toStdString());
+
+ return;
+ }
+
+ qDebug() << "serial port baud rate:" << m_serialPort->baudRate();
+
+ qDebug() << "serial port data bits:" << m_serialPort->dataBits();
+ qDebug() << "serial port parity:" << m_serialPort->parity();
+ qDebug() << "serial port stop bits:" << m_serialPort->stopBits();
+
+ QObject::connect(m_serialPort, &QSerialPort::readyRead,
+ this, &PrinterClient::onReadyRead);
+
+ QObject::connect(m_serialPort, &QSerialPort::errorOccurred,
+ this, &PrinterClient::onErrorOccured);
+
+ // m_serialPort->write(QByteArray { "G91\n" });
+ // m_serialPort->flush();
+ // m_serialPort->write(QByteArray { "G1 Z10\n" });
+ // m_serialPort->flush();
+ sendCommand("G91");
+ // sendCommand("G1 Z10");
+ // sendCommand("G1 Z-10");
+
+ onErrorOccured(QSerialPort::SerialPortError::PermissionError);
+}
+
+void PrinterClient::sendCommand(const QString command)
+{
+ const auto written = m_serialPort->write(command.toUtf8() + "\n");
+
+ qDebug() << QString("serialPort: send '%1': (written %2 bytes)")
+ .arg(command).arg(written);
+
+ m_serialPort->flush();
+}
+
+void PrinterClient::onReadyRead()
+{
+ const auto data = m_serialPort->readAll();
+ qDebug() << "serialPort: " << data;
+
+ // emit newData(data);
+}
+
+void PrinterClient::onErrorOccured(QSerialPort::SerialPortError error)
+{
+ qWarning() << "serial port error:" << m_serialPort->errorString()
+ << "-" << error;
+}
diff --git a/src/printerclient.h b/src/printerclient.h
new file mode 100644
index 0000000..d1266dd
--- /dev/null
+++ b/src/printerclient.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <QObject>
+
+#include <QSerialPort>
+
+class QSerialPort;
+
+class PrinterClient : public QObject
+{
+ // Q_OBJECT
+
+public:
+ explicit PrinterClient(QObject *parent = nullptr);
+ // ~PrinterClient() override = default;
+ // ~PrinterClient
+
+ // signals:
+ // void newData(const QString output);
+
+public:
+ void sendCommand(const QString command);
+
+private:
+ void onReadyRead();
+ void onErrorOccured(QSerialPort::SerialPortError error);
+
+private:
+ QSerialPort* m_serialPort { nullptr };
+};
diff --git a/src/profile.cpp b/src/profile.cpp
new file mode 100644
index 0000000..2c7b9a9
--- /dev/null
+++ b/src/profile.cpp
@@ -0,0 +1,155 @@
+#include "profile.h"
+
+#include <iostream>
+
+#include <QDebug>
+#include <QJsonArray>
+
+Profile::Profile(
+ const Pixels& pixels,
+ const CalibrationTablePtr calibrationTableZ,
+ const CalibrationTablePtr calibrationTableX)
+ : m_counters(pixels.counters)
+{
+ if (!calibrationTableZ || !calibrationTableX)
+ {
+ std::cerr << __func__ << ": got invalid calibration tables"
+ << std::endl;
+
+ return;
+ }
+
+ static bool done{false};
+
+ if (!done) {
+ for (size_t i = 9471; i < 9472; i++) {
+ std::cout << __func__ << ": row #" << i << ": ";
+
+ for (size_t j = 0; j < 1280; ++j) {
+ const auto& x = calibrationTableX->at(j).at(i);
+ const auto& z = calibrationTableZ->at(j).at(i);
+ std::cout << "Profile: table: " << x << ' ' << z << std::endl;
+ }
+
+ std::cout << std::endl;
+ }
+ }
+
+ for (size_t i = 0; i < img_width; ++i) {
+ try {
+ const auto& pixel = pixels.pixels[i];
+ const auto pixelDiscrete = pixel * discretesInRage / img_height;
+ if (Q_UNLIKELY(pixel >= sizeof(CalibrationColumn) / sizeof(float))) {
+ qWarning() << "got invalid calibration pixel at" << i << ":"
+ << pixel;
+ m_counters = {};
+ m_pointsMm = {QPointF{std::nan(""), std::nan("")}};
+ return;
+ }
+
+ if (Q_UNLIKELY(pixel == sizeof(CalibrationColumn) - 1)) {
+ qDebug() << "Profile: got edge value";
+ const auto& z = calibrationTableZ->at(i).at(pixelDiscrete);
+
+ if (qFuzzyIsNull(z) || std::isnan(z)) {
+ m_pointsMm.at(i) = {std::nan(""), std::nan("")};
+ continue;
+ }
+
+ const auto& x = calibrationTableX->at(i).at(pixelDiscrete);
+ m_pointsMm.at(i) = {x, z};
+
+ continue;
+ }
+
+ const auto& leftMmZ = calibrationTableZ->at(i).at(
+ size_t(pixelDiscrete));
+ const auto& rightMmZ = calibrationTableZ->at(i).at(
+ size_t(pixelDiscrete) + 1);
+
+ const auto& leftMmX = calibrationTableX->at(i).at(
+ size_t(pixelDiscrete));
+ const auto& rightMmX = calibrationTableX->at(i).at(
+ size_t(pixelDiscrete) + 1);
+
+ const auto& fract = pixelDiscrete - long(pixelDiscrete);
+
+ const auto z = (leftMmZ * (1 - fract) + rightMmZ * fract);
+
+ // TODO: use only NaN (or zero?) everywhere
+ // NOTE: QJsonValue converts NaN to zero
+ if (qFuzzyIsNull(z) || std::isnan(z)) {
+ // qDebug() << "got nan z for discrete" << pixelDiscrete << leftMmZ
+ // << rightMmZ;
+ m_pointsMm.at(i) = {std::nan(""), std::nan("")};
+ continue;
+ }
+
+ const auto x = (leftMmX * (1 - fract) + rightMmX * fract);
+
+ m_pointsMm.at(i) = {x, z};
+
+ if (!done) {
+ qDebug() << "got these values\t" << pixels.pixels[i]
+ << pixelDiscrete << m_pointsMm[i] << leftMmZ
+ << rightMmZ << leftMmX << rightMmX;
+ }
+ } catch (const std::out_of_range& ex) {
+ qWarning() << "out of range exception:" << ex.what();
+ qWarning() << "i:" << i; // << "pixels[i]" << pixels.pixels[i];
+ m_counters = {};
+ m_pointsMm = {QPointF{std::nan(""), std::nan("")}};
+
+ return;
+ } catch (const std::exception& ex) {
+ qWarning() << "exception:" << ex.what();
+ qWarning() << "i:" << i << "pixels[i]" << pixels.pixels[i];
+ m_counters = {};
+ m_pointsMm = {QPointF{std::nan(""), std::nan("")}};
+
+ return;
+ } catch (...) {
+ qWarning() << "unknown exception";
+ m_counters = {};
+ m_pointsMm = {QPointF{std::nan(""), std::nan("")}};
+ return;
+ }
+ }
+
+ done = true;
+
+ // static bool printOnce = [&]() -> bool {
+ // for (size_t i = 0; i < m_pointsMm.size(); ++i) {
+ // qDebug() << '\t' << pixels.pixels[i] << m_pointsMm[i];
+ // }
+
+ // return true;
+ // }();
+}
+
+const Counters& Profile::counters() const
+{
+ return m_counters;
+}
+
+const Profile::PointsMm& Profile::pointsMm() const
+{
+ return m_pointsMm;
+}
+
+Profile::operator const QJsonObject() const
+{
+ QJsonObject counters{
+ {"timestampUs", qint64(m_counters.timestampUs)},
+ {"measurementCounter", qint64(m_counters.measurementCounter)},
+ {"encoderPosition", qint64(m_counters.encoderPosition)},
+ };
+
+ QJsonArray jsonPoints;
+
+ for (const auto& p : m_pointsMm) {
+ jsonPoints << QJsonArray{p.x(), p.y()};
+ }
+
+ return {{"counters", counters}, {"pointsMm", jsonPoints}};
+}
diff --git a/src/profile.h b/src/profile.h
new file mode 100644
index 0000000..0e2f839
--- /dev/null
+++ b/src/profile.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <QJsonObject>
+#include <QPointF>
+
+#include "calibration.h"
+#include "pixels.h"
+
+class Profile
+{
+public:
+ using PointsMm = std::array<QPointF, img_width>;
+
+public:
+ // TODO: make private/protected
+ explicit Profile(const Pixels& pixels,
+ const CalibrationTablePtr calibrationTableZ,
+ const CalibrationTablePtr calibrationTableX);
+
+public:
+ const Counters& counters() const;
+ const PointsMm& pointsMm() const;
+
+ operator const QJsonObject() const;
+
+private:
+ Counters m_counters{};
+ PointsMm m_pointsMm{QPointF{std::nan(""), std::nan("")}};
+};
diff --git a/src/rotaryencoder.cpp b/src/rotaryencoder.cpp
new file mode 100644
index 0000000..62ca33d
--- /dev/null
+++ b/src/rotaryencoder.cpp
@@ -0,0 +1,125 @@
+#include "rotaryencoder.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <vector>
+
+#include <QDebug>
+#include <QElapsedTimer>
+
+#include <wiringPi.h>
+
+// got from https://gist.github.com/ast/a19813fce9d34c7240091db11b8190dd
+// https://gist.github.com/ast
+// Inspired by Paul Stoffregen's excellent Arduino library Encoder:
+// https://github.com/PaulStoffregen/Encoder
+
+constexpr int gpioA = 17;
+constexpr int gpioB = 27;
+// constexpr int gpioA = 0;
+// constexpr int gpioB = 2;
+// constexpr int gpioA = 11;
+// constexpr int gpioB = 13;
+
+const std::vector<int> encoderTable = {
+ 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
+};
+
+volatile int32_t positionSteps;
+volatile uint8_t state;
+
+void pin_isr(void) {
+ uint8_t p1val = digitalRead(gpioA);
+ uint8_t p2val = digitalRead(gpioB);
+ uint8_t s = state & 3;
+
+ if (p1val) s |= 4;
+ if (p2val) s |= 8;
+
+ state = (s >> 2);
+
+ switch (s) {
+ case 1: case 7: case 8: case 14:
+ positionSteps = positionSteps + 1;
+ return;
+ case 2: case 4: case 11: case 13:
+ positionSteps = positionSteps - 1;
+ return;
+ case 3: case 12:
+ positionSteps = positionSteps + 2;
+ return;
+ case 6: case 9:
+ positionSteps = positionSteps - 2;
+ return;
+ }
+}
+
+RotaryEncoder::RotaryEncoder()
+{
+ if (!m_self) {
+ m_self = this;
+ } else {
+ qWarning() << "normally there should be only one instance of RotaryEncoder";
+ }
+
+ QElapsedTimer t;
+ t.start();
+
+ if (wiringPiSetupGpio()) {
+ perror("wiringPiSetupGpio");
+ exit(EXIT_FAILURE);
+ }
+
+ qDebug() << "msecs to setup wiringPi:" << t.elapsed();
+
+ if ( wiringPiISR (gpioA, INT_EDGE_BOTH, &pin_isr) < 0 ) {
+ perror("wiringPiISR");
+ exit(EXIT_FAILURE);
+ }
+
+ qDebug() << "msecs to register interruption A:" << t.elapsed();
+
+ if ( wiringPiISR (gpioB, INT_EDGE_BOTH, &pin_isr) < 0 ) {
+ perror("wiringPiISR");
+ exit(EXIT_FAILURE);
+ }
+
+ qDebug() << "msecs to register interruption B:" << t.elapsed();
+
+ // pinMode (gpioA, INPUT) ;
+ // pinMode (gpioB, INPUT) ;
+ // pullUpDnControl(gpioA, PUD_UP);
+ // pullUpDnControl(gpioB, PUD_UP);
+
+ qDebug() << "encoder is ok";
+
+ // // Show position every second
+ // while ( 1 ) {
+ // constexpr double stepsPerMm = 200;
+ // const double positionMm = ::positionSteps / stepsPerMm;
+ // qDebug() << ::positionSteps
+ // << '-'
+ // << QString::number(positionMm, 'f', 3)
+ // << "(mm)";
+ // // printf( "%ld\n", ::position);
+ // usleep( 100 * 1000 ); // wait 1 second
+ // }
+}
+
+RotaryEncoder::~RotaryEncoder()
+{
+ if (m_self == this) {
+ m_self = nullptr;
+ }
+}
+
+RotaryEncoder *RotaryEncoder::instance()
+{
+ return m_self;
+}
+
+int32_t RotaryEncoder::position() const
+{
+ return ::positionSteps;
+}
diff --git a/src/rotaryencoder.h b/src/rotaryencoder.h
new file mode 100644
index 0000000..c0c39d8
--- /dev/null
+++ b/src/rotaryencoder.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <cstdint>
+
+// TODO: delete singleton functionality
+class RotaryEncoder final
+{
+public:
+ RotaryEncoder();
+ ~RotaryEncoder();
+
+public:
+ static RotaryEncoder* instance();
+
+public:
+ int32_t position() const;
+
+private:
+ static inline RotaryEncoder* m_self { nullptr };
+};