Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FloatImageViewer: support multi-resolution #39

Merged
merged 9 commits into from
Sep 6, 2023
20 changes: 16 additions & 4 deletions src/qtAliceVision/FloatImageViewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ FloatImageViewer::FloatImageViewer(QQuickItem* parent)
connect(&_surface, &Surface::subdivisionsChanged, this, &FloatImageViewer::update);
connect(&_surface, &Surface::verticesChanged, this, &FloatImageViewer::update);

connect(&_sequenceCache, &imgserve::SequenceCache::requestHandled, this, &FloatImageViewer::reload);
connect(&_singleImageLoader, &imgserve::SingleImageLoader::requestHandled, this, &FloatImageViewer::reload);
connect(this, &FloatImageViewer::sequenceChanged, this, &FloatImageViewer::reload);
connect(&_sequenceCache, &imgserve::SequenceCache::requestHandled, this, &FloatImageViewer::reload);
connect(&_sequenceCache, &imgserve::SequenceCache::contentChanged, this, &FloatImageViewer::reload);
connect(this, &FloatImageViewer::useSequenceChanged, this, &FloatImageViewer::reload);
}

Expand All @@ -66,10 +66,15 @@ void FloatImageViewer::setLoading(bool loading)
void FloatImageViewer::setSequence(const QVariantList& paths)
{
_sequenceCache.setSequence(paths);

Q_EMIT sequenceChanged();
}

void FloatImageViewer::setTargetSize(int size)
{
_sequenceCache.setTargetSize(size);
Q_EMIT targetSizeChanged();
}

QVariantList FloatImageViewer::getCachedFrames() const
{
return _sequenceCache.getCachedFrames();
Expand Down Expand Up @@ -128,6 +133,12 @@ void FloatImageViewer::reload()
Q_EMIT cachedFramesChanged();
}

void FloatImageViewer::playback(bool active)
{
// Turn off interactive prefetching when playback is ON
_sequenceCache.setInteractivePrefetching(!active);
}

QVector4D FloatImageViewer::pixelValueAt(int x, int y)
{
if (!_image)
Expand Down Expand Up @@ -354,7 +365,8 @@ void FloatImageViewer::updatePaintSurface(QSGGeometryNode* root, QSGSimpleMateri
quint16* indices = root->geometry()->indexDataAsUShort();

// Update surface
_surface.update(vertices, indices, _textureSize, _downscaleLevel);
const QSize surfaceSize = _surface.isPanoramaViewerEnabled() ? _textureSize : _sourceSize;
_surface.update(vertices, indices, surfaceSize, _downscaleLevel);

root->geometry()->markIndexDataDirty();
root->geometry()->markVertexDataDirty();
Expand Down
16 changes: 10 additions & 6 deletions src/qtAliceVision/FloatImageViewer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <memory>
#include <string>
#include <algorithm>

namespace qtAliceVision
{
Expand Down Expand Up @@ -58,10 +59,13 @@ class FloatImageViewer : public QQuickItem

Q_PROPERTY(QVariantList sequence WRITE setSequence NOTIFY sequenceChanged)

Q_PROPERTY(int targetSize WRITE setTargetSize NOTIFY targetSizeChanged)

Q_PROPERTY(QVariantList cachedFrames READ getCachedFrames NOTIFY cachedFramesChanged)

Q_PROPERTY(bool useSequence MEMBER _useSequence NOTIFY useSequenceChanged)


public:
explicit FloatImageViewer(QQuickItem* parent = nullptr);
~FloatImageViewer() override;
Expand All @@ -80,12 +84,8 @@ class FloatImageViewer : public QQuickItem
if (level == _downscaleLevel)
return;

// Level [0;3]
if (level < 0 && level > 6)
level = 4;
_downscaleLevel = level;
reload();
// Q_EMIT downscaleLevelChanged();
_downscaleLevel = std::max(0, level);
Q_EMIT downscaleLevelChanged();
}

enum class EChannelMode : quint8
Expand Down Expand Up @@ -121,16 +121,20 @@ class FloatImageViewer : public QQuickItem
Q_SIGNAL void sfmRequiredChanged();
Q_SIGNAL void fisheyeCircleParametersChanged();
Q_SIGNAL void sequenceChanged();
Q_SIGNAL void targetSizeChanged();
Q_SIGNAL void cachedFramesChanged();
Q_SIGNAL void useSequenceChanged();

// Q_INVOKABLE
Q_INVOKABLE QVector4D pixelValueAt(int x, int y);
Q_INVOKABLE void playback(bool active);

Surface* getSurfacePtr() { return &_surface; }

void setSequence(const QVariantList& paths);

void setTargetSize(int size);

QVariantList getCachedFrames() const;

private:
Expand Down
3 changes: 2 additions & 1 deletion src/qtAliceVision/ImageServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ImageServer {
} // namespace imgserve
} // namespace qtAliceVision

// Make ResponseData struct known to QMetaType
// Make RequestData and ResponseData struct known to QMetaType
// for usage in signals and slots
Q_DECLARE_METATYPE(qtAliceVision::imgserve::RequestData)
Q_DECLARE_METATYPE(qtAliceVision::imgserve::ResponseData)
139 changes: 112 additions & 27 deletions src/qtAliceVision/SequenceCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@
#include <chrono>
#include <stdexcept>
#include <iostream>
#include <atomic>


namespace qtAliceVision {
namespace imgserve {

// Flag for aborting the prefetching worker thread from the main thread
std::atomic_bool abortPrefetching = false;

SequenceCache::SequenceCache(QObject* parent) :
QObject(parent)
{
Expand All @@ -37,10 +41,20 @@ SequenceCache::SequenceCache(QObject* parent) :
// Initialize internal state
_regionSafe = std::make_pair(-1, -1);
_loading = false;
_interactivePrefetching = true;
_targetSize = 1000;
}

SequenceCache::~SequenceCache()
{
// Check if a worker thread is currently active
if (_loading)
{
// Worker thread will return on next iteration
abortPrefetching = true;
QThreadPool::globalInstance()->waitForDone();
}

// Free memory occupied by image cache
if (_cache) delete _cache;
}
Expand All @@ -50,9 +64,9 @@ void SequenceCache::setSequence(const QVariantList& paths)
// Clear internal state
_sequence.clear();
_regionSafe = std::make_pair(-1, -1);
_loading = false;

// Fill sequence vector
int frameCounter = 0;
for (const auto& var : paths)
{
try
Expand All @@ -74,8 +88,18 @@ void SequenceCache::setSequence(const QVariantList& paths)
data.metadata[QString::fromStdString(item.name().string())] = QString::fromStdString(item.get_string());
}

// Compute downscale
const int maxDim = std::max(width, height);
const int level = static_cast<int>(std::floor(
std::log2(static_cast<double>(maxDim) / static_cast<double>(_targetSize))));
data.downscale = 1 << std::max(level, 0);

// Set frame number
data.frame = frameCounter;

// Add to sequence
_sequence.push_back(data);
++frameCounter;
}
catch (const std::runtime_error& e)
{
Expand All @@ -84,10 +108,42 @@ void SequenceCache::setSequence(const QVariantList& paths)
}
}

// Assign frame numbers
for (size_t i = 0; i < _sequence.size(); ++i)
// Notify listeners that sequence content has changed
Q_EMIT contentChanged();
}

void SequenceCache::setInteractivePrefetching(bool interactive)
{
_interactivePrefetching = interactive;
}

void SequenceCache::setTargetSize(int size)
{
// Update target size
_targetSize = size;

// Update downscale for each frame
bool refresh = false;
for (auto& data : _sequence)
{
// Compute downscale
const int maxDim = std::max(data.dim.width(), data.dim.height());
const int level = static_cast<int>(std::floor(
std::log2(static_cast<double>(maxDim) / static_cast<double>(_targetSize))));
const int downscale = 1 << std::max(level, 0);

refresh = refresh || (data.downscale != downscale);

data.downscale = downscale;
}

if (refresh)
{
_sequence[i].frame = static_cast<int>(i);
// Clear internal state
_regionSafe = std::make_pair(-1, -1);

// Notify listeners that sequence content has changed
Q_EMIT contentChanged();
}
}

Expand All @@ -105,7 +161,7 @@ QVariantList SequenceCache::getCachedFrames() const
const int frame = static_cast<int>(i);

// Check if current frame is in cache
if (_cache->contains<aliceVision::image::RGBAfColor>(_sequence[i].path, 1))
if (_cache->contains<aliceVision::image::RGBAfColor>(_sequence[i].path, _sequence[i].downscale))
{
// Either grow currently open region or create a new region
if (regionOpen)
Expand Down Expand Up @@ -152,9 +208,33 @@ ResponseData SequenceCache::request(const RequestData& reqData)
return response;
}

// Retrieve frame data
const std::size_t idx = static_cast<std::size_t>(frame);
const FrameData& data = _sequence[idx];

// Retrieve image from cache
const bool cachedOnly = true;
const bool lazyCleaning = false;
response.img = _cache->get<aliceVision::image::RGBAfColor>(data.path, data.downscale, cachedOnly, lazyCleaning);

// Retrieve metadata
response.dim = data.dim;
response.metadata = data.metadata;

// Requested image is not in cache
// and there is already a prefetching thread running
if (!response.img && _loading && _interactivePrefetching)
{
// Abort prefetching to avoid waiting until current worker thread is done
abortPrefetching = true;
}

// Request falls outside of safe region
if ((frame < _regionSafe.first || frame > _regionSafe.second) && !_loading)
{
// Make sur abort flag is off before launching a new prefetching thread
abortPrefetching = false;

// Update internal state
_loading = true;

Expand All @@ -171,19 +251,6 @@ ResponseData SequenceCache::request(const RequestData& reqData)
QThreadPool::globalInstance()->start(ioRunnable);
}

// Retrieve frame data
const std::size_t idx = static_cast<std::size_t>(frame);
const FrameData& data = _sequence[idx];

// Retrieve image from cache
const bool cachedOnly = true;
const bool lazyCleaning = false;
response.img = _cache->get<aliceVision::image::RGBAfColor>(data.path, 1, cachedOnly, lazyCleaning);

// Retrieve metadata
response.dim = data.dim;
response.metadata = data.metadata;

return response;
}

Expand All @@ -205,7 +272,7 @@ void SequenceCache::onPrefetchingDone(int reqFrame)
const std::size_t idx = static_cast<std::size_t>(frame);

// Grow region on the left as much as possible
if (_cache->contains<aliceVision::image::RGBAfColor>(_sequence[idx].path, 1))
if (_cache->contains<aliceVision::image::RGBAfColor>(_sequence[idx].path, _sequence[idx].downscale))
{
regionCached.first = frame;
}
Expand All @@ -219,7 +286,7 @@ void SequenceCache::onPrefetchingDone(int reqFrame)
const std::size_t idx = static_cast<std::size_t>(frame);

// Grow region on the right as much as possible
if (_cache->contains<aliceVision::image::RGBAfColor>(_sequence[idx].path, 1))
if (_cache->contains<aliceVision::image::RGBAfColor>(_sequence[idx].path, _sequence[idx].downscale))
{
regionCached.second = frame;
}
Expand All @@ -230,11 +297,18 @@ void SequenceCache::onPrefetchingDone(int reqFrame)
}

// Update safe region
// Here we define safe region to cover 80% of cached region
// The remaining 20% serves to anticipate prefetching
const int extentCached = (regionCached.second - regionCached.first) / 2;
const int extentSafe = static_cast<int>(static_cast<double>(extentCached) * 0.8);
_regionSafe = buildRegion(reqFrame, extentSafe);
if (regionCached == std::make_pair(-1, -1))
{
_regionSafe = std::make_pair(-1, -1);
}
else
{
// Here we define safe region to cover 80% of cached region
// The remaining 20% serves to anticipate prefetching
const int extentCached = (regionCached.second - regionCached.first) / 2;
const int extentSafe = static_cast<int>(static_cast<double>(extentCached) * 0.8);
_regionSafe = buildRegion(reqFrame, extentSafe);
}

// Notify clients that a request has been handled
Q_EMIT requestHandled();
Expand Down Expand Up @@ -307,8 +381,19 @@ void PrefetchingIORunnable::run()
// Load images from disk to cache
for (const auto& data : _toLoad)
{
// Check if main thread wants to abort prefetching
if (abortPrefetching)
{
abortPrefetching = false;
Q_EMIT done(_reqFrame);
return;
}

// Check if image size does not exceed limit
uint64_t memSize = static_cast<uint64_t>(data.dim.width()) * static_cast<uint64_t>(data.dim.height()) * 16;
uint64_t memSize =
static_cast<uint64_t>(data.dim.width() / data.downscale)
* static_cast<uint64_t>(data.dim.height() / data.downscale)
* 16;
if (filled + memSize > _toFill)
{
break;
Expand All @@ -319,7 +404,7 @@ void PrefetchingIORunnable::run()
{
const bool cachedOnly = false;
const bool lazyCleaning = false;
_cache->get<aliceVision::image::RGBAfColor>(data.path, 1, cachedOnly, lazyCleaning);
_cache->get<aliceVision::image::RGBAfColor>(data.path, data.downscale, cachedOnly, lazyCleaning);
filled += memSize;
}
catch (const std::runtime_error& e)
Expand Down
Loading