Commit e01cfca5 authored by Jasem Mutlaq's avatar Jasem Mutlaq

Add adaptive sampling as a possible solution to load large images in memory constrained systems

parent 7619cc39
Pipeline #48362 canceled with stages
in 10 minutes and 29 seconds
...@@ -1737,4 +1737,39 @@ bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMes ...@@ -1737,4 +1737,39 @@ bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMes
#endif #endif
} }
double getAvailableRAM()
{
#if defined(Q_OS_OSX)
int mib [] = { CTL_HW, HW_MEMSIZE };
size_t length;
length = sizeof(int64_t);
int64_t RAMcheck;
if(sysctl(mib, 2, &RAMcheck, &length, NULL, 0))
return false; // On Error
//Until I can figure out how to get free RAM on Mac
return RAMcheck;
#elif defined(Q_OS_LINUX)
QProcess p;
p.start("awk", QStringList() << "/MemAvailable/ { print $2 }" << "/proc/meminfo");
p.waitForFinished();
QString memory = p.readAllStandardOutput();
p.close();
//kB to bytes
return (memory.toLong() * 1024.0);
#else
MEMORYSTATUSEX memory_status;
ZeroMemory(&memory_status, sizeof(MEMORYSTATUSEX));
memory_status.dwLength = sizeof(MEMORYSTATUSEX);
if (GlobalMemoryStatusEx(&memory_status))
{
return memory_status.ullAvailPhys;
}
else
{
return 0;
}
#endif
return 0;
}
} }
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "dms.h" #include "dms.h"
#include <QPointF> #include <QPointF>
#include <QProcess>
#include <QSharedPointer> #include <QSharedPointer>
#include "config-kstars.h" #include "config-kstars.h"
...@@ -301,4 +302,12 @@ QByteArray getJPLQueryString(const QByteArray &kind, const QByteArray &dataField ...@@ -301,4 +302,12 @@ QByteArray getJPLQueryString(const QByteArray &kind, const QByteArray &dataField
* @return True if conversion is successful, false otherwise. * @return True if conversion is successful, false otherwise.
*/ */
bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMessage); bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMessage);
/**
* @brief getAvailableRAM Try to get available and total RAM on system
* @return availableRAM Available (free and unclaimed) system RAM in bytes. 0 if failed to determine RAM
* @return True if RAM was successfully queried, false otherwise.
*/
double getAvailableRAM();
} }
...@@ -23,33 +23,33 @@ class QString; ...@@ -23,33 +23,33 @@ class QString;
class FITSLabel : public QLabel class FITSLabel : public QLabel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit FITSLabel(FITSView *img, QWidget *parent = nullptr); explicit FITSLabel(FITSView *img, QWidget *parent = nullptr);
virtual ~FITSLabel() override = default; virtual ~FITSLabel() override = default;
void setSize(double w, double h); void setSize(double w, double h);
void centerTelescope(double raJ2000, double decJ2000); void centerTelescope(double raJ2000, double decJ2000);
bool getMouseButtonDown(); bool getMouseButtonDown();
protected: protected:
virtual void mouseMoveEvent(QMouseEvent *e) override; virtual void mouseMoveEvent(QMouseEvent *e) override;
virtual void mousePressEvent(QMouseEvent *e) override; virtual void mousePressEvent(QMouseEvent *e) override;
virtual void mouseReleaseEvent(QMouseEvent *e) override; virtual void mouseReleaseEvent(QMouseEvent *e) override;
virtual void mouseDoubleClickEvent(QMouseEvent *e) override; virtual void mouseDoubleClickEvent(QMouseEvent *e) override;
private: private:
bool mouseButtonDown { false }; bool mouseButtonDown { false };
QPoint lastMousePoint; QPoint lastMousePoint;
FITSView *view { nullptr }; FITSView *view { nullptr };
dms ra; dms ra;
dms dec; dms dec;
double width { 0 }; double width { 0 };
double height { 0 }; double height { 0 };
double size { 0 }; double size { 0 };
signals: signals:
void newStatus(const QString &msg, FITSBar id); void newStatus(const QString &msg, FITSBar id);
void pointSelected(int x, int y); void pointSelected(int x, int y);
void markerSelected(int x, int y); void markerSelected(int x, int y);
}; };
...@@ -276,25 +276,25 @@ QHBoxLayout* FITSTab::setupStretchBar() ...@@ -276,25 +276,25 @@ QHBoxLayout* FITSTab::setupStretchBar()
{ {
StretchParams params = view->getStretchParams(); StretchParams params = view->getStretchParams();
params.grey_red.shadows = this->maxShadows * value / 10000.0f; params.grey_red.shadows = this->maxShadows * value / 10000.0f;
view->setSampling(Options::stretchPreviewSampling()); view->setPreviewSampling(Options::stretchPreviewSampling());
view->setStretchParams(params); view->setStretchParams(params);
view->setSampling(1); view->setPreviewSampling(0);
}); });
connect(midtonesSlider.get(), &QSlider::sliderMoved, [ = ](int value) connect(midtonesSlider.get(), &QSlider::sliderMoved, [ = ](int value)
{ {
StretchParams params = view->getStretchParams(); StretchParams params = view->getStretchParams();
params.grey_red.midtones = this->maxMidtones * value / 10000.0f; params.grey_red.midtones = this->maxMidtones * value / 10000.0f;
view->setSampling(Options::stretchPreviewSampling()); view->setPreviewSampling(Options::stretchPreviewSampling());
view->setStretchParams(params); view->setStretchParams(params);
view->setSampling(1); view->setPreviewSampling(0);
}); });
connect(highlightsSlider.get(), &QSlider::sliderMoved, [ = ](int value) connect(highlightsSlider.get(), &QSlider::sliderMoved, [ = ](int value)
{ {
StretchParams params = view->getStretchParams(); StretchParams params = view->getStretchParams();
params.grey_red.highlights = this->maxHighlights * value / 10000.0f; params.grey_red.highlights = this->maxHighlights * value / 10000.0f;
view->setSampling(Options::stretchPreviewSampling()); view->setPreviewSampling(Options::stretchPreviewSampling());
view->setStretchParams(params); view->setStretchParams(params);
view->setSampling(1); view->setPreviewSampling(0);
}); });
// Make a final full-res display when the slider is released. // Make a final full-res display when the slider is released.
......
...@@ -106,7 +106,7 @@ void FITSView::doStretch(QImage *outputImage) ...@@ -106,7 +106,7 @@ void FITSView::doStretch(QImage *outputImage)
tempParams = stretchParams; tempParams = stretchParams;
stretch.setParams(tempParams); stretch.setParams(tempParams);
stretch.run(imageData->getImageBuffer(), outputImage, sampling); stretch.run(imageData->getImageBuffer(), outputImage, m_PreviewSampling);
} }
// Store stretch parameters, and turn on stretching if it isn't already on. // Store stretch parameters, and turn on stretching if it isn't already on.
...@@ -377,6 +377,25 @@ bool FITSView::processData() ...@@ -377,6 +377,25 @@ bool FITSView::processData()
imageData->applyFilter(filter); imageData->applyFilter(filter);
double availableRAM = 0;
if (Options::adaptiveSampling() && (availableRAM = KSUtils::getAvailableRAM()) > 0)
{
// Possible color maximum image size
double max_size = image_width * image_height * 4;
// Ratio of image size to available RAM size
double ratio = max_size / availableRAM;
// Increase adaptive sampling with more limited RAM
if (ratio < 0.1)
m_AdaptiveSampling = 1;
else if (ratio < 0.2)
m_AdaptiveSampling = 2;
else
m_AdaptiveSampling = 4;
m_PreviewSampling *= m_AdaptiveSampling;
}
// Rescale to fits window on first load // Rescale to fits window on first load
if (firstLoad) if (firstLoad)
{ {
...@@ -737,14 +756,14 @@ void FITSView::updateFrameLargeImage() ...@@ -737,14 +756,14 @@ void FITSView::updateFrameLargeImage()
font.setPixelSize(scaleSize(FONT_SIZE)); font.setPixelSize(scaleSize(FONT_SIZE));
painter.setFont(font); painter.setFont(font);
if (sampling == 1) if (m_PreviewSampling == 1)
{ {
drawOverlay(&painter, 1.0); drawOverlay(&painter, 1.0);
drawStarFilter(&painter, 1.0); drawStarFilter(&painter, 1.0);
} }
image_frame->setPixmap(displayPixmap); image_frame->setPixmap(displayPixmap);
image_frame->resize(((sampling * currentZoom) / 100.0) * image_frame->pixmap()->size()); image_frame->resize(((m_PreviewSampling * currentZoom) / 100.0) * image_frame->pixmap()->size());
} }
void FITSView::updateFrameSmallImage() void FITSView::updateFrameSmallImage()
...@@ -755,13 +774,12 @@ void FITSView::updateFrameSmallImage() ...@@ -755,13 +774,12 @@ void FITSView::updateFrameSmallImage()
QPainter painter(&displayPixmap); QPainter painter(&displayPixmap);
if (sampling == 1) if (m_PreviewSampling == 1)
{ {
drawOverlay(&painter, currentZoom / ZOOM_DEFAULT); drawOverlay(&painter, currentZoom / ZOOM_DEFAULT);
drawStarFilter(&painter, currentZoom / ZOOM_DEFAULT); drawStarFilter(&painter, currentZoom / ZOOM_DEFAULT);
} }
image_frame->setPixmap(displayPixmap); image_frame->setPixmap(displayPixmap);
image_frame->resize(currentWidth, currentHeight); image_frame->resize(currentWidth, currentHeight);
} }
...@@ -1856,8 +1874,8 @@ void FITSView::initDisplayImage() ...@@ -1856,8 +1874,8 @@ void FITSView::initDisplayImage()
{ {
// Account for leftover when sampling. Thus a 5-wide image sampled by 2 // Account for leftover when sampling. Thus a 5-wide image sampled by 2
// would result in a width of 3 (samples 0, 2 and 4). // would result in a width of 3 (samples 0, 2 and 4).
int w = (imageData->width() + sampling - 1) / sampling; int w = (imageData->width() + m_PreviewSampling - 1) / m_PreviewSampling;
int h = (imageData->height() + sampling - 1) / sampling; int h = (imageData->height() + m_PreviewSampling - 1) / m_PreviewSampling;
if (imageData->channels() == 1) if (imageData->channels() == 1)
{ {
......
...@@ -231,9 +231,13 @@ class FITSView : public QScrollArea ...@@ -231,9 +231,13 @@ class FITSView : public QScrollArea
void setAutoStretchParams(); void setAutoStretchParams();
// When sampling is > 1, we will display the image at a lower resolution. // When sampling is > 1, we will display the image at a lower resolution.
void setSampling(int value) // When sampling = 0, reset to the adaptive sampling value
void setPreviewSampling(uint8_t value)
{ {
if (value > 0) sampling = value; if (value == 0)
m_PreviewSampling = m_AdaptiveSampling;
else
m_PreviewSampling = value * m_AdaptiveSampling;
} }
public slots: public slots:
...@@ -356,7 +360,9 @@ class FITSView : public QScrollArea ...@@ -356,7 +360,9 @@ class FITSView : public QScrollArea
StretchParams stretchParams; StretchParams stretchParams;
// Resolution for display. Sampling=2 means display every other sample. // Resolution for display. Sampling=2 means display every other sample.
int sampling { 1 }; uint8_t m_PreviewSampling { 1 };
// Adaptive sampling is based on available RAM
uint8_t m_AdaptiveSampling {1};
struct struct
{ {
......
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>452</width> <width>443</width>
<height>314</height> <height>312</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing"> <property name="spacing">
<number>3</number> <number>3</number>
</property> </property>
...@@ -28,98 +28,168 @@ ...@@ -28,98 +28,168 @@
</property> </property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<item> <item>
<widget class="QGroupBox" name="FITSViewerGroup"> <layout class="QVBoxLayout" name="verticalLayout_3">
<property name="enabled"> <property name="spacing">
<bool>true</bool> <number>3</number>
</property>
<property name="title">
<string>Look &amp;&amp; Feel</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <item>
<item> <widget class="QGroupBox" name="FITSViewerGroup">
<widget class="QCheckBox" name="kcfg_useFITSViewer"> <property name="enabled">
<property name="toolTip"> <bool>true</bool>
<string>Automatically display received images in the FITS Viewer</string> </property>
</property> <property name="title">
<property name="text"> <string>Look &amp;&amp; Feel</string>
<string>Use FITS Viewer</string> </property>
</property> <layout class="QVBoxLayout" name="verticalLayout_2">
</widget> <item>
</item> <widget class="QCheckBox" name="kcfg_useFITSViewer">
<item> <property name="toolTip">
<widget class="QCheckBox" name="kcfg_singlePreviewFITS"> <string>Automatically display received images in the FITS Viewer</string>
<property name="toolTip"> </property>
<string>Display all captured FITS images in a single tab instead of multiple tabs per image.</string> <property name="text">
</property> <string>Use FITS Viewer</string>
<property name="statusTip"> </property>
<string/> </widget>
</property> </item>
<property name="whatsThis"> <item>
<string/> <widget class="QCheckBox" name="kcfg_singlePreviewFITS">
</property> <property name="toolTip">
<property name="text"> <string>Display all captured FITS images in a single tab instead of multiple tabs per image.</string>
<string>Single Preview Tab</string> </property>
</property> <property name="statusTip">
<property name="checked"> <string/>
<bool>true</bool> </property>
</property> <property name="whatsThis">
</widget> <string/>
</item> </property>
<item> <property name="text">
<widget class="QCheckBox" name="kcfg_singleWindowCapturedFITS"> <string>Single Preview Tab</string>
<property name="toolTip"> </property>
<string>Display captured FITS images from all cameras in a single FITS Viewer window instead of a dedicated window to each camera.</string> <property name="checked">
</property> <bool>true</bool>
<property name="whatsThis"> </property>
<string/> </widget>
</property> </item>
<property name="text"> <item>
<string>Single Window Capture</string> <widget class="QCheckBox" name="kcfg_singleWindowCapturedFITS">
</property> <property name="toolTip">
<property name="checked"> <string>Display captured FITS images from all cameras in a single FITS Viewer window instead of a dedicated window to each camera.</string>
<bool>false</bool> </property>
</property> <property name="whatsThis">
</widget> <string/>
</item> </property>
<item> <property name="text">
<widget class="QCheckBox" name="kcfg_singleWindowOpenedFITS"> <string>Single Window Capture</string>
<property name="toolTip"> </property>
<string>Display opened FITS images in a single FITS Viewer window instead of a dedicated window to each file.</string> <property name="checked">
</property> <bool>false</bool>
<property name="whatsThis"> </property>
<string/> </widget>
</property> </item>
<property name="text"> <item>
<string>Single Window Open</string> <widget class="QCheckBox" name="kcfg_singleWindowOpenedFITS">
</property> <property name="toolTip">
<property name="checked"> <string>Display opened FITS images in a single FITS Viewer window instead of a dedicated window to each file.</string>
<bool>true</bool> </property>
</property> <property name="whatsThis">
</widget> <string/>
</item> </property>
<item> <property name="text">
<widget class="QCheckBox" name="kcfg_focusFITSOnNewImage"> <string>Single Window Open</string>
<property name="toolTip"> </property>
<string>Bring the FITSViewer window to the foreground when receiving a new image</string> <property name="checked">
</property> <bool>true</bool>
<property name="text"> </property>
<string>Focus on receiving an image</string> </widget>
</property> </item>
</widget> <item>
</item> <widget class="QCheckBox" name="kcfg_focusFITSOnNewImage">
<item> <property name="toolTip">
<widget class="QCheckBox" name="kcfg_independentWindowFITS"> <string>Bring the FITSViewer window to the foreground when receiving a new image</string>
<property name="toolTip"> </property>
<string>Make FITS Viewer window independent from KStars</string> <property name="text">
</property> <string>Focus on receiving an image</string>
<property name="text"> </property>
<string>Independent Window</string> </widget>
</property> </item>
</widget> <item>
</item> <widget class="QCheckBox" name="kcfg_independentWindowFITS">
</layout> <property name="toolTip">
</widget> <string>Make FITS Viewer window independent from KStars</string>
</property>
<property name="text">
<string>Independent Window</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Down Sampling</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="kcfg_AdaptiveSampling">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Automatically down sample images based on available resources.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Adaptive Sampling</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="stretchPreviewSamplingLabel">
<property name="toolTip">
<string>Set the coarseness of the preview shown when sliding the fitsviewer's stretch parameter sliders. 1 is full resolution, but can be slow, 4 would be coarse resolution and fast.</string>
</property>
<property name="text">
<string>Stretch Preview</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="kcfg_StretchPreviewSampling">
<property name="toolTip">
<string>Set the coarseness of the preview shown when sliding the fitsviewer's stretch parameter sliders. 1 is full resolution, but can be slow, 4 would be coarse resolution and fast.</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>5</number>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
...@@ -137,36 +207,6 @@ ...@@ -137,36 +207,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayoutStretch">
<item>
<widget class="QLabel" name="stretchPreviewSamplingLabel">
<property name="toolTip">
<string>Set the coarseness of the preview shown when sliding the fitsviewer's stretch parameter sliders. 1 is full resolution, but can be slow, 4 would be coarse resolution and fast.</string>
</property>
<property name="text">
<string>Stretch Preview</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="kcfg_StretchPreviewSampling">
<property name="toolTip">