diff options
| author | Nikita Kostovsky <nikita@kostovsky.me> | 2025-02-21 07:27:00 +0100 |
|---|---|---|
| committer | Nikita Kostovsky <nikita@kostovsky.me> | 2025-02-21 07:27:00 +0100 |
| commit | d12498504c279a0a85bbfb024f7903e34dbe07db (patch) | |
| tree | 0df9f3f8bf27470ac211a57bb8e44be0aa2f6138 /src | |
| parent | 27637ab117d8738236f6ab155300ff6e79e4843b (diff) | |
broken img calc; change dir struct
Diffstat (limited to 'src')
| -rw-r--r-- | src/LibCamera.cpp | 267 | ||||
| -rw-r--r-- | src/LibCamera.h | 91 | ||||
| -rw-r--r-- | src/camera/icamera.h | 4 | ||||
| -rw-r--r-- | src/camera/innomakerov9281.cpp | 180 | ||||
| -rw-r--r-- | src/camera/innomakerov9281.h | 11 | ||||
| -rw-r--r-- | src/camera/ov9281.cpp | 12 | ||||
| -rw-r--r-- | src/constants.h | 2 | ||||
| -rw-r--r-- | src/fuck_intel.h | 12 | ||||
| -rw-r--r-- | src/genetic_algos.cpp | 1 | ||||
| -rw-r--r-- | src/genetic_algos.h | 174 | ||||
| -rw-r--r-- | src/httpservice.cpp | 1 | ||||
| -rw-r--r-- | src/httpservice.h | 203 | ||||
| -rw-r--r-- | src/image.cpp | 74 | ||||
| -rw-r--r-- | src/image.h | 18 | ||||
| -rw-r--r-- | src/imagealgos.cpp | 465 | ||||
| -rw-r--r-- | src/imagealgos.h | 18 | ||||
| -rw-r--r-- | src/macro.h | 17 | ||||
| -rw-r--r-- | src/main.cpp | 798 | ||||
| -rw-r--r-- | src/printerclient.cpp | 86 | ||||
| -rw-r--r-- | src/printerclient.h | 30 | ||||
| -rw-r--r-- | src/profile.cpp | 155 | ||||
| -rw-r--r-- | src/profile.h | 29 | ||||
| -rw-r--r-- | src/rotaryencoder.cpp | 125 | ||||
| -rw-r--r-- | src/rotaryencoder.h | 20 |
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 = ℑ +} + +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 }; +}; |
