Skip to content
Snippets Groups Projects
Commit f3aa5160 authored by mingf2's avatar mingf2
Browse files

move reconstruction task to worker thread

parent fca74e76
No related branches found
No related tags found
No related merge requests found
Showing
with 307 additions and 2349 deletions
......@@ -26,19 +26,13 @@ public:
explicit MainWindow(QWidget *parent = nullptr);
virtual void changeEvent(QEvent *e);
~MainWindow();
// reconstruct images
void run();
void closeEvent (QCloseEvent *event);
bool aborted=false;
public slots:
private:
Ui::MainWindow *ui;
Setup* config;
SPSC* coneQueue;
TH2D* hist;
void createHist();
ULong64_t counts=0;
RecoImage* image;
TText* countsText;
void createCountsLabel();
// gridlines
......@@ -47,32 +41,21 @@ private:
void aitoff2xy(const double& l, const double& b, double &Al, double &Ab);
void createGridlines();
void redraw();
// void createSourceMarker();
// worker thread responsible for image reconstruction
Worker* workerThread;
bool threadExecutionFinished=false;
bool finished=false;
bool stop=true;
bool aborted=false;
// bool closed=false;
// void customClose();
// update image
void updateImage(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last,
const bool& normalized=true);
bool saveCanvas(const QString& fileName);
QString curFile;
protected:
private slots:
void handleOpen();
void handleSave();
void handleSaveAs();
bool handleSave();
bool handleSaveAs();
void handleClose();
void handleStart();
void handleStop();
void handleClear();
void handleScreenshot();
void handleAbout();
void notifyThreadFinished();
......
This license applies to all the code in this repository except that written by third
parties, namely the files in benchmarks/ext, which have their own licenses, and Jeff
Preshing's semaphore implementation (used in the blocking queues) which has a zlib
license (embedded in atomicops.h).
Simplified BSD License:
Copyright (c) 2013-2021, Cameron Desrochers
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# A single-producer, single-consumer lock-free queue for C++
This mini-repository has my very own implementation of a lock-free queue (that I designed from scratch) for C++.
It only supports a two-thread use case (one consuming, and one producing). The threads can't switch roles, though
you could use this queue completely from a single thread if you wish (but that would sort of defeat the purpose!).
Note: If you need a general-purpose multi-producer, multi-consumer lock free queue, I have [one of those too][mpmc].
This repository also includes a [circular-buffer SPSC queue][circular] which supports blocking on enqueue as well as dequeue.
## Features
- [Blazing fast][benchmarks]
- Compatible with C++11 (supports moving objects instead of making copies)
- Fully generic (templated container of any type) -- just like `std::queue`, you never need to allocate memory for elements yourself
(which saves you the hassle of writing a lock-free memory manager to hold the elements you're queueing)
- Allocates memory up front, in contiguous blocks
- Provides a `try_enqueue` method which is guaranteed never to allocate memory (the queue starts with an initial capacity)
- Also provides an `enqueue` method which can dynamically grow the size of the queue as needed
- Also provides `try_emplace`/`emplace` convenience methods
- Has a blocking version with `wait_dequeue`
- Completely "wait-free" (no compare-and-swap loop). Enqueue and dequeue are always O(1) (not counting memory allocation)
- On x86, the memory barriers compile down to no-ops, meaning enqueue and dequeue are just a simple series of loads and stores (and branches)
## Use
Simply drop the readerwriterqueue.h (or readerwritercircularbuffer.h) and atomicops.h files into your source code and include them :-)
A modern compiler is required (MSVC2010+, GCC 4.7+, ICC 13+, or any C++11 compliant compiler should work).
Note: If you're using GCC, you really do need GCC 4.7 or above -- [4.6 has a bug][gcc46bug] that prevents the atomic fence primitives
from working correctly.
Example:
```cpp
using namespace moodycamel;
ReaderWriterQueue<int> q(100); // Reserve space for at least 100 elements up front
q.enqueue(17); // Will allocate memory if the queue is full
bool succeeded = q.try_enqueue(18); // Will only succeed if the queue has an empty slot (never allocates)
assert(succeeded);
int number;
succeeded = q.try_dequeue(number); // Returns false if the queue was empty
assert(succeeded && number == 17);
// You can also peek at the front item of the queue (consumer only)
int* front = q.peek();
assert(*front == 18);
succeeded = q.try_dequeue(number);
assert(succeeded && number == 18);
front = q.peek();
assert(front == nullptr); // Returns nullptr if the queue was empty
```
The blocking version has the exact same API, with the addition of `wait_dequeue` and
`wait_dequeue_timed` methods:
```cpp
BlockingReaderWriterQueue<int> q;
std::thread reader([&]() {
int item;
#if 1
for (int i = 0; i != 100; ++i) {
// Fully-blocking:
q.wait_dequeue(item);
}
#else
for (int i = 0; i != 100; ) {
// Blocking with timeout
if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5)))
++i;
}
#endif
});
std::thread writer([&]() {
for (int i = 0; i != 100; ++i) {
q.enqueue(i);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
writer.join();
reader.join();
assert(q.size_approx() == 0);
```
Note that `wait_dequeue` will block indefinitely while the queue is empty; this
means care must be taken to only call `wait_dequeue` if you're sure another element
will come along eventually, or if the queue has a static lifetime. This is because
destroying the queue while a thread is waiting on it will invoke undefined behaviour.
The blocking circular buffer has a fixed number of slots, but is otherwise quite similar to
use:
```cpp
BlockingReaderWriterCircularBuffer<int> q(1024); // pass initial capacity
q.try_enqueue(1);
int number;
q.try_dequeue(number);
assert(number == 1);
q.wait_enqueue(123);
q.wait_dequeue(number);
assert(number == 123);
q.wait_dequeue_timed(number, std::chrono::milliseconds(10));
```
## CMake installation
As an alternative to including the source files in your project directly,
you can use CMake to install the library in your system's include directory:
```
mkdir build
cd build
cmake ..
make install
```
Then, you can include it from your source code:
```
#include <readerwriterqueue/readerwriterqueue.h>
```
## Disclaimers
The queue should only be used on platforms where aligned integer and pointer access is atomic; fortunately, that
includes all modern processors (e.g. x86/x86-64, ARM, and PowerPC). *Not* for use with a DEC Alpha processor (which has very weak memory ordering) :-)
Note that it's only been tested on x86(-64); if someone has access to other processors I'd love to run some tests on
anything that's not x86-based.
## More info
See the [LICENSE.md][license] file for the license (simplified BSD).
My [blog post][blog] introduces the context that led to this code, and may be of interest if you're curious
about lock-free programming.
[blog]: http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++
[license]: LICENSE.md
[benchmarks]: http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++#benchmarks
[gcc46bug]: http://stackoverflow.com/questions/16429669/stdatomic-thread-fence-has-undefined-reference
[mpmc]: https://github.com/cameron314/concurrentqueue
[circular]: readerwritercircularbuffer.h
This diff is collapsed.
// ©2020 Cameron Desrochers.
// Distributed under the simplified BSD license (see the license file that
// should have come with this header).
// Provides a C++11 implementation of a single-producer, single-consumer wait-free concurrent
// circular buffer (fixed-size queue).
#pragma once
#include <utility>
#include <chrono>
#include <memory>
#include <cstdlib>
#include <cstdint>
#include <cassert>
// Note that this implementation is fully modern C++11 (not compatible with old MSVC versions)
// but we still include atomicops.h for its LightweightSemaphore implementation.
#include "atomicops.h"
#ifndef MOODYCAMEL_CACHE_LINE_SIZE
#define MOODYCAMEL_CACHE_LINE_SIZE 64
#endif
namespace moodycamel {
template<typename T>
class BlockingReaderWriterCircularBuffer
{
public:
typedef T value_type;
public:
explicit BlockingReaderWriterCircularBuffer(std::size_t capacity)
: maxcap(capacity), mask(), rawData(), data(),
slots(new spsc_sema::LightweightSemaphore(static_cast<spsc_sema::LightweightSemaphore::ssize_t>(capacity))),
items(new spsc_sema::LightweightSemaphore(0)),
nextSlot(0), nextItem(0)
{
// Round capacity up to power of two to compute modulo mask.
// Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
--capacity;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
for (std::size_t i = 1; i < sizeof(std::size_t); i <<= 1)
capacity |= capacity >> (i << 3);
mask = capacity++;
rawData = static_cast<char*>(std::malloc(capacity * sizeof(T) + std::alignment_of<T>::value - 1));
data = align_for<T>(rawData);
}
BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer&& other)
: maxcap(0), mask(0), rawData(nullptr), data(nullptr),
slots(new spsc_sema::LightweightSemaphore(0)),
items(new spsc_sema::LightweightSemaphore(0)),
nextSlot(), nextItem()
{
swap(other);
}
BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer const&) = delete;
// Note: The queue should not be accessed concurrently while it's
// being deleted. It's up to the user to synchronize this.
~BlockingReaderWriterCircularBuffer()
{
for (std::size_t i = 0, n = items->availableApprox(); i != n; ++i)
reinterpret_cast<T*>(data)[(nextItem + i) & mask].~T();
std::free(rawData);
}
BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer&& other) noexcept
{
swap(other);
return *this;
}
BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer const&) = delete;
// Swaps the contents of this buffer with the contents of another.
// Not thread-safe.
void swap(BlockingReaderWriterCircularBuffer& other) noexcept
{
std::swap(maxcap, other.maxcap);
std::swap(mask, other.mask);
std::swap(rawData, other.rawData);
std::swap(data, other.data);
std::swap(slots, other.slots);
std::swap(items, other.items);
std::swap(nextSlot, other.nextSlot);
std::swap(nextItem, other.nextItem);
}
// Enqueues a single item (by copying it).
// Fails if not enough room to enqueue.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool try_enqueue(T const& item)
{
if (!slots->tryWait())
return false;
inner_enqueue(item);
return true;
}
// Enqueues a single item (by moving it, if possible).
// Fails if not enough room to enqueue.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool try_enqueue(T&& item)
{
if (!slots->tryWait())
return false;
inner_enqueue(std::move(item));
return true;
}
// Blocks the current thread until there's enough space to enqueue the given item,
// then enqueues it (via copy).
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
void wait_enqueue(T const& item)
{
while (!slots->wait());
inner_enqueue(item);
}
// Blocks the current thread until there's enough space to enqueue the given item,
// then enqueues it (via move, if possible).
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
void wait_enqueue(T&& item)
{
while (!slots->wait());
inner_enqueue(std::move(item));
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via copy) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool wait_enqueue_timed(T const& item, std::int64_t timeout_usecs)
{
if (!slots->wait(timeout_usecs))
return false;
inner_enqueue(item);
return true;
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via move, if possible) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool wait_enqueue_timed(T&& item, std::int64_t timeout_usecs)
{
if (!slots->wait(timeout_usecs))
return false;
inner_enqueue(std::move(item));
return true;
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via copy) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
template<typename Rep, typename Period>
inline bool wait_enqueue_timed(T const& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_enqueue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via move, if possible) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
template<typename Rep, typename Period>
inline bool wait_enqueue_timed(T&& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_enqueue_timed(std::move(item), std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue a single item.
// Returns false if the buffer is empty.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U>
bool try_dequeue(U& item)
{
if (!items->tryWait())
return false;
inner_dequeue(item);
return true;
}
// Blocks the current thread until there's something to dequeue, then dequeues it.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U>
void wait_dequeue(U& item)
{
while (!items->wait());
inner_dequeue(item);
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U>
bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
{
if (!items->wait(timeout_usecs))
return false;
inner_dequeue(item);
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Returns a (possibly outdated) snapshot of the total number of elements currently in the buffer.
// Thread-safe.
inline std::size_t size_approx() const
{
return items->availableApprox();
}
// Returns the maximum number of elements that this circular buffer can hold at once.
// Thread-safe.
inline std::size_t max_capacity() const
{
return maxcap;
}
private:
template<typename U>
void inner_enqueue(U&& item)
{
std::size_t i = nextSlot++;
new (reinterpret_cast<T*>(data) + (i & mask)) T(std::forward<U>(item));
items->signal();
}
template<typename U>
void inner_dequeue(U& item)
{
std::size_t i = nextItem++;
T& element = reinterpret_cast<T*>(data)[i & mask];
item = std::move(element);
element.~T();
slots->signal();
}
template<typename U>
static inline char* align_for(char* ptr)
{
const std::size_t alignment = std::alignment_of<U>::value;
return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
}
private:
std::size_t maxcap; // actual (non-power-of-two) capacity
std::size_t mask; // circular buffer capacity mask (for cheap modulo)
char* rawData; // raw circular buffer memory
char* data; // circular buffer memory aligned to element alignment
std::unique_ptr<spsc_sema::LightweightSemaphore> slots; // number of slots currently free
std::unique_ptr<spsc_sema::LightweightSemaphore> items; // number of elements currently enqueued
char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(char*) * 2 - sizeof(std::size_t) * 2 - sizeof(std::unique_ptr<spsc_sema::LightweightSemaphore>) * 2];
std::size_t nextSlot; // index of next free slot to enqueue into
char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(std::size_t)];
std::size_t nextItem; // index of next element to dequeue from
};
}
This diff is collapsed.
......@@ -2,39 +2,59 @@
#define RECONSTRUCTION_H
#include <QMutex>
#include <QString>
#include "TH2D.h"
#include "setup.h"
/**
* @brief Project cones onto the sphereical surface and update the image.
* Implementation based on
* https://www.nature.com/articles/s41598-020-58857-z
*
* @param config Input. Global settings.
* @param histo Input and output. Image to be updated.
* @param counts Input and output. Number of cones (events).
* @param first Input. First iterator to the vector of cones.
* @param last Input. Last iterator to the vector of cones.
* @return 0
*/
int addCones(const Setup* config, TH2D* histo, ULong64_t& counts,
std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last);
class RecoImage
{
public:
RecoImage(const Setup* config_);
~RecoImage();
QMutex mMutex;
ULong64_t counts=0;
/**
* @brief Project cones onto the sphereical surface and update the image.
* Imeplemention based on
* https://www.overleaf.com/read/hjdvrcrjcvpx
*
* @param config Input. Global settings.
* @param histo Input and output. Image to be updated.
* @param counts Input and output. Number of cones (events).
* @param first Input. First iterator of the vector of cones.
* @param last Input. Last iterator of the vector of cones.
* @return 0
*/
int addConesNormalized(const Setup* config, TH2D* histo, ULong64_t& counts,
std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last);
void clear();
bool saveImage(const QString& fileName);
void updateImage(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last,
const bool normailzed=true);
private:
const Setup* config;
TH2D* hist;
void createHist();
bool hist2txt(const QString& fileName);
/**
* @brief Project cones onto the sphereical surface and update the image.
* Implementation based on
* https://www.nature.com/articles/s41598-020-58857-z
*
* @param config Input. Global settings.
* @param histo Input and output. Image to be updated.
* @param counts Input and output. Number of cones (events).
* @param first Input. First iterator to the vector of cones.
* @param last Input. Last iterator to the vector of cones.
* @return 0
*/
int addCones(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last);
/**
* @brief Project cones onto the sphereical surface and update the image.
* Imeplemention based on
* https://www.overleaf.com/read/hjdvrcrjcvpx
*
* @param config Input. Global settings.
* @param histo Input and output. Image to be updated.
* @param counts Input and output. Number of cones (events).
* @param first Input. First iterator of the vector of cones.
* @param last Input. Last iterator of the vector of cones.
* @return 0
*/
int addConesNormalized(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last);
};
#endif // RECONSTRUCTION_H
......@@ -7,10 +7,6 @@
#include <stdexcept>
#include <QString>
#include <QStringRef>
#include "readerwriterqueue/atomicops.h"
#include "readerwriterqueue/readerwriterqueue.h"
class Vector3D
{
......@@ -115,7 +111,7 @@ public:
// queue capacity
ulong capacity=1024;
// max number of cones to process
ulong maxN=2000;
ulong maxN=1000;
};
class Cone
......@@ -160,6 +156,4 @@ public:
}
};
typedef moodycamel::BlockingReaderWriterQueue<Cone> SPSC;
#endif // SETUP_H
......@@ -3,26 +3,31 @@
#include <QObject>
#include <QThread>
#include "setup.h"
#include "reconstruction.h"
class Worker : public QThread
{
Q_OBJECT
const Setup* config;
SPSC* coneQueue;
RecoImage* image;
void run() override;
bool stop=false;
ulong localCounts=0;
bool stopped=true;
bool exitted=false;
public:
// explicit Worker(QObject *parent = nullptr);
Worker(QObject *parent, const Setup* config_, SPSC* coneQueue_) :
Worker(QObject *parent, const Setup* config_, RecoImage* image_) :
QThread(parent),
config(config_),
coneQueue(coneQueue_)
image(image_)
{}
//signals:
public slots:
void handleStart();
void handleStop();
void handleClear();
void stopExecution();
};
......
......@@ -68,6 +68,7 @@
<addaction name="actionStart"/>
<addaction name="actionStop"/>
<addaction name="actionClear"/>
<addaction name="actionScreenshot"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionOpen">
......@@ -89,6 +90,10 @@
</property>
</action>
<action name="actionSave_As">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/icons/saveas.png</normaloff>:/icons/icons/saveas.png</iconset>
</property>
<property name="text">
<string>Save As</string>
</property>
......@@ -138,6 +143,15 @@
<string>About</string>
</property>
</action>
<action name="actionScreenshot">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/icons/screenshot.svg</normaloff>:/icons/icons/screenshot.svg</iconset>
</property>
<property name="text">
<string>Screenshot</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
......
......@@ -6,7 +6,7 @@
```bash
mkdir -p build
cd build
qmake ../imagerQt.pro
qmake CONFIG+=realease ../imagerQt.pro
make
```
- Run:
......
......@@ -16,11 +16,10 @@
#include <QTimer>
#include <QDebug>
#include <QWindow>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
#include "reconstruction.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
......@@ -30,27 +29,33 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::handleClose);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::handleSave);
connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::handleSaveAs);
connect(ui->actionStart, &QAction::triggered, this, &MainWindow::handleStart);
connect(ui->actionStop, &QAction::triggered, this, &MainWindow::handleStop);
connect(ui->actionClear, &QAction::triggered, this, &MainWindow::handleClear);
connect(ui->actionScreenshot, &QAction::triggered, this, &MainWindow::handleScreenshot);
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::handleAbout);
config = new Setup(Vector3D(SOURCE_X, SOURCE_Y, SOURCE_Z));
coneQueue = new SPSC(config->capacity);
image = new RecoImage(config);
createGridlines();
createCountsLabel();
workerThread = new Worker(this, config, coneQueue);
// update image on worker thread
workerThread = new Worker(this, config, image);
connect(workerThread, &Worker::finished, workerThread, &QObject::deleteLater);
connect(workerThread, &Worker::finished, this, &MainWindow::notifyThreadFinished);
connect(ui->actionStart, &QAction::triggered, workerThread, &Worker::handleStart);
connect(ui->actionStop, &QAction::triggered, workerThread, &Worker::handleStop);
connect(ui->actionClear, &QAction::triggered, workerThread, &Worker::handleClear);
connect(this, &MainWindow::threadStopped, workerThread, &Worker::stopExecution);
workerThread->start();
createHist();
createGridlines();
createCountsLabel();
// update every 0.1 second
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, QOverload<>::of(&MainWindow::redraw));
timer->start(100);
setWindowTitle(tr("Back projection"));
ui->canvas->Canvas()->Modified();
ui->canvas->Canvas()->Update();
ui->statusBar->showMessage("Ready.");
}
void MainWindow::changeEvent(QEvent *e)
......@@ -78,22 +83,21 @@ void MainWindow::closeEvent (QCloseEvent *event)
if (resBtn != QMessageBox::Yes) {
event->ignore();
} else {
finished=true;
if (workerThread) {
// qDebug() << "Exiting thread..";
aborted=true;
// send stop signal to worker thread
aborted=true;
emit threadStopped();
}
// event->accept();
event->accept();
}
}
MainWindow::~MainWindow()
{
delete ui;
if (coneQueue)
delete coneQueue;
if (image)
delete image;
if (config)
delete config;
for (int i = 0; i < longitudes.size(); ++i) {
......@@ -108,74 +112,6 @@ MainWindow::~MainWindow()
}
}
void MainWindow::run()
{
std::vector<Cone> cones(config->chuckSize);
bool emptyQueue = false;
while (!finished)
{
if (!stop)
{
int i(0);
while (i < config->chuckSize)
{
emptyQueue = !(coneQueue->wait_dequeue_timed(cones[i],
std::chrono::milliseconds(10)));
if (emptyQueue) break;
i++;
}
// update image
updateImage(cones.cbegin(), cones.cbegin() + i, true);
if (emptyQueue && threadExecutionFinished)
{
// stop updating after this final run
finished = true;
// qDebug() << "Processing finished.";
QMessageBox messageBox;
messageBox.setText("Image reconstruction finished.");
messageBox.setWindowTitle("Finished");
messageBox.exec();
break;
}
// gSystem->Sleep(100);
}
QApplication::processEvents();
}
}
void MainWindow::updateImage(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last,
const bool& normalized)
{
if (first == last)
return;
// update image
if (normalized)
addConesNormalized(config, hist, counts, first, last);
else
addCones(config, hist, counts, first, last);
// redraw
redraw();
}
void MainWindow::createHist()
{
hist = new TH2D("ROI", " ; Azimuth (degree); Elevation (degree)",
config->phiBins, -180, 180,
config->thetaBins, -90, 90);
// init image
for (int i = 0; i < config->phiBins; i++)
{
for (int j = 0; j < config->thetaBins; j++)
{
hist->SetBinContent(i+1, j+1, 0);
}
}
gStyle->SetOptStat(0);
hist->GetZaxis()->SetLabelSize(0.02);
hist->Draw("z aitoff");
}
void MainWindow::createGridlines()
{
......@@ -234,7 +170,7 @@ void MainWindow::createGridlines()
void MainWindow::createCountsLabel()
{
std::string strtmp = "Total counts: " + std::to_string(counts);
std::string strtmp = "Total counts: " + std::to_string(image->counts);
countsText = new TText(0.7, 0.92, strtmp.c_str());
countsText->SetTextSizePixels(5);
countsText->SetNDC(kTRUE);
......@@ -261,10 +197,12 @@ void MainWindow::aitoff2xy(const double& l, const double& b, double &Al, double
void MainWindow::redraw()
{
std::string strtmp = "Total counts: " + std::to_string(counts);
image->mMutex.lock();
std::string strtmp = "Total counts: " + std::to_string(image->counts);
countsText->SetText(0.7, 0.92, strtmp.c_str());
ui->canvas->Canvas()->Modified();
ui->canvas->Canvas()->Update();
image->mMutex.unlock();
}
void MainWindow::handleOpen()
......@@ -272,53 +210,78 @@ void MainWindow::handleOpen()
qDebug() << "Open file clicked";
}
void MainWindow::handleSave()
bool MainWindow::handleSave()
{
qDebug() << "Save clicked";
// qDebug() << "Save clicked";
if (curFile.isEmpty()) {
return handleSaveAs();
} else {
return saveCanvas(curFile);
}
}
void MainWindow::handleSaveAs()
bool MainWindow::handleSaveAs()
{
qDebug() << "Save as clicked";
QString fileName = QFileDialog::getSaveFileName(this,
tr("Save Image"), "",
tr("PNG file (*.png);;ROOT file (*.root);;C file (*.C);;text file (*.txt)"));
if (fileName.isEmpty())
return false;
else {
if (saveCanvas(fileName))
curFile = fileName;
}
}
bool MainWindow::saveCanvas(const QString& fileName)
{
QFileInfo fileInfo(fileName);
QString ext=fileInfo.completeSuffix();
bool saved=false;
if (ext == "root" || ext == "png" || ext == "C") {
// qDebug() << "Save as " << ext << " file.";
image->mMutex.lock();
ui->canvas->Canvas()->SaveAs(fileName.toLocal8Bit().constData());
image->mMutex.unlock();
saved = true;
}
else if (ext == "txt") {
saved = image->saveImage(fileName);
}
if (saved)
{
ui->statusBar->showMessage(QString("Image saved to: %1").arg(fileName));
}
return saved;
}
void MainWindow::handleClose()
{
// qDebug() << "Close clicked";
this->close();
}
void MainWindow::handleStart()
{
stop=false;
// qDebug() << "Start clicked";
}
void MainWindow::handleStop()
void MainWindow::handleScreenshot()
{
stop=true;
// qDebug() << "Stop clicked";
QDateTime dateTime = QDateTime::currentDateTime();
QString currentDateTime = dateTime.toString("yyyy-MM-dd-HH-mm-ss");
QString fileName = QString("Screenshot-%1.png").arg(currentDateTime);
saveCanvas(fileName);
}
void MainWindow::handleClear()
{
counts = 0;
hist->Reset();
redraw();
// qDebug() << "Clear clicked";
}
void MainWindow::handleAbout()
{
QMessageBox messageBox;
messageBox.setText("This application is created using Qt 5.13.2. Source code is available at Gitlab.");
messageBox.setWindowTitle("About");
messageBox.exec();
QMessageBox::about(this, tr("About Application"),
tr("This application is created using Qt 5.13.2. Source code is available at Gitlab."));
// qDebug() << "About clicked";
}
void MainWindow::notifyThreadFinished()
{
// pop up a message box indicating processing is finished.
threadExecutionFinished = true;
if (!aborted)
{
QMessageBox messageBox;
messageBox.setText("Image reconstruction finished.");
messageBox.setWindowTitle("Finished");
messageBox.exec();
}
}
......@@ -18,13 +18,6 @@ int main(int argc, char *argv[])
w.resize(w.sizeHint());
w.show();
w.resize(700,700);
w.run();
if (!w.aborted) {
return a.exec();
}
else {
gSystem->Sleep(500);
return 0;
}
return a.exec();
}
#include "TStyle.h"
#include "TAxis.h"
#include <fstream>
#include <QFileInfo>
#include <QDebug>
#include "reconstruction.h"
int addCones(const Setup* config, TH2D* histo, ULong64_t& counts,
std::vector<Cone>::const_iterator first,
RecoImage::RecoImage(const Setup* config_):
config(config_),
mMutex()
{
counts=0;
createHist();
}
RecoImage::~RecoImage()
{
delete hist;
}
void RecoImage::createHist()
{
hist = new TH2D("ROI", " ; Azimuth (degree); Elevation (degree)",
config->phiBins, -180, 180,
config->thetaBins, -90, 90);
// init image
for (int i = 0; i < config->phiBins; i++)
{
for (int j = 0; j < config->thetaBins; j++)
{
hist->SetBinContent(i+1, j+1, 0);
}
}
gStyle->SetOptStat(0);
hist->GetZaxis()->SetLabelSize(0.02);
hist->Draw("z aitoff");
}
void RecoImage::clear()
{
mMutex.lock();
counts=0;
hist->Reset();
mMutex.unlock();
}
bool RecoImage::saveImage(const QString& fileName)
{
// save image
QFileInfo fileInfo(fileName);
QString ext=fileInfo.completeSuffix();
if(ext == "txt"){
// qDebug() << "Save as text file";
mMutex.lock();
bool saved=hist2txt(fileName);
mMutex.unlock();
return saved;
}
else {
return false;
}
}
bool RecoImage::hist2txt(const QString& fileName)
{
std::ofstream outf(fileName.toLocal8Bit().constData());
if (!outf.good())
{
return false;
}
outf << " Phi Theta Content\n";
for (int i = 1; i <= hist->GetNbinsX(); i++)
{
for (int j = 1; j <= hist->GetNbinsY(); j++)
{
outf << std::fixed << std::setprecision(2)
<< std::setw(8) << ((TAxis*)hist->GetXaxis())->GetBinCenter(i)
<< std::setw(8) << ((TAxis*)hist->GetYaxis())->GetBinCenter(j)
<< std::fixed << std::setprecision(8)
<< std::setw(13) << hist->GetBinContent(i,j) << '\n';
}
}
// qDebug() << "Text file saved.";
}
void RecoImage::updateImage(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last,
const bool normalized)
{
if (first==last)
return;
else if (normalized) {
mMutex.lock();
addConesNormalized(first, last);
mMutex.unlock();
}
else {
mMutex.lock();
addCones(first, last);
mMutex.unlock();
}
}
int RecoImage::addCones(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last)
{
// project cones onto the spherical surface
......@@ -30,8 +132,8 @@ int addCones(const Setup* config, TH2D* histo, ULong64_t& counts,
sgmb2 = std::pow((ray/(ray*ray) + k->axis/(k->axis*k->axis)-
(ray+k->axis)/(ray*k->axis))*config->sgmpos * beta, 2);
sgmb2+= std::pow((ray/(ray*k->axis)-k->axis/(k->axis*k->axis))*config->sgmpos * beta, 2);
histo->SetBinContent(j+1, i+1,
histo->GetBinContent(j+1, i+1) +
hist->SetBinContent(j+1, i+1,
hist->GetBinContent(j+1, i+1) +
std::exp(-std::pow((std::pow(beta, config->order)-std::pow(alpha, config->order)), 2)/
(2*std::pow(config->order, 2)*
(std::pow(alpha,2*config->order-2)*sgma2 +std::pow(beta, 2*config->order-2)*sgmb2))));
......@@ -39,12 +141,10 @@ int addCones(const Setup* config, TH2D* histo, ULong64_t& counts,
}
}
counts += (last - first);
return 0;
}
int addConesNormalized(const Setup* config, TH2D* histo, ULong64_t& counts,
std::vector<Cone>::const_iterator first,
int RecoImage::addConesNormalized(std::vector<Cone>::const_iterator first,
std::vector<Cone>::const_iterator last)
{
// project cones onto the spherical surface
......@@ -92,7 +192,7 @@ int addConesNormalized(const Setup* config, TH2D* histo, ULong64_t& counts,
{
for (int j = 0; j < config->phiBins; j++)
{
histo->SetBinContent(j+1, i+1, (histo->GetBinContent(j+1, i+1)*(counts - 1) + probDist[i][j] / summ) / counts);
hist->SetBinContent(j+1, i+1, (hist->GetBinContent(j+1, i+1)*(counts - 1) + probDist[i][j] / summ) / counts);
}
}
}
......
......@@ -19,31 +19,46 @@ void Worker::run()
QTextStream in(&conefile);
// skip header (first line)
in.readLineInto(&line);
ulong counts(0);
while(!stop) {
//some work to get data (read board or file)
int i(0);
while (!stop && i < config->chuckSize && in.readLineInto(&line))
{
coneQueue->enqueue(Cone(line));
i++;
}
counts+=i;
if (stop || line.isNull() || counts >= config->maxN)
{
break;
}
while (!stop && coneQueue->size_approx() >= config->capacity)
std::vector<Cone> cones(config->chuckSize, Cone());
while(!exitted)
{
if (!stopped)
{
gSystem->Sleep(50);
//some work to get data (read board or file)
int i(0);
while (!exitted && i < config->chuckSize && in.readLineInto(&line))
{
cones[i]=Cone(line);
i++;
}
localCounts+=i;
// update image
image->updateImage(cones.cbegin(), cones.cbegin()+i, true);
if (exitted || line.isNull() || localCounts >= config->maxN)
break;
}
}
conefile.close();
// qDebug() << "Worker thread exited.";
qDebug() << "Worker thread exited.";
}
void Worker::handleStart()
{
stopped=false;
}
void Worker::handleStop()
{
stopped=true;
}
void Worker::stopExecution()
{
stop = true;
exitted = true;
}
void Worker::handleClear()
{
localCounts = 0;
image->clear();
}
icons/saveas.png

18.2 KiB

<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-e</title><circle cx="256" cy="272" r="64"/><path d="M456,144H373c-3,0-6.72-1.94-9.62-5l-27.28-42.8C325,80,320,80,302,80H210c-18,0-24,0-34.07,16.21L148.62,139c-2.22,2.42-5.34,5-8.62,5V128a8,8,0,0,0-8-8H92a8,8,0,0,0-8,8v16H56a24,24,0,0,0-24,24V408a24,24,0,0,0,24,24H456a24,24,0,0,0,24-24V168A24,24,0,0,0,456,144ZM260.51,367.9a96,96,0,1,1,91.39-91.39A96.11,96.11,0,0,1,260.51,367.9Z"/></svg>
\ No newline at end of file
......@@ -38,9 +38,6 @@ SOURCES += \
HEADERS += \
$$PWD/Headers/MainWindow.h \
$$PWD/Headers/qrootcanvas.h \
$$PWD/Headers/readerwriterqueue/atomicops.h \
$$PWD/Headers/readerwriterqueue/readerwritercircularbuffer.h \
$$PWD/Headers/readerwriterqueue/readerwriterqueue.h \
$$PWD/Headers/reconstruction.h \
$$PWD/Headers/setup.h \
$$PWD/Headers/worker.h
......
......@@ -7,6 +7,8 @@
<file>icons/open.svg</file>
<file>icons/about.svg</file>
<file>icons/save.svg</file>
<file>icons/saveas.png</file>
<file>icons/screenshot.svg</file>
</qresource>
<qresource prefix="/Data">
<file>Data/cones_all_channels.txt</file>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment