Compare commits

..

5 Commits

Author SHA1 Message Date
nou f016500f12 Include ngc db 2025-05-29 17:39:13 +02:00
nou 6069ebbbac Refractor drawing grid 2025-05-27 16:26:03 +02:00
nou e587d84e05 Remove that empty action 2025-05-26 17:05:19 +02:00
nou c01f2e328a Add sky grid painting 2025-05-26 15:50:37 +02:00
nou 8b498bbe73 Prefer writing keyword as integer 2025-05-24 23:13:14 +02:00
12 changed files with 347 additions and 11 deletions
+55 -2
View File
@@ -16,10 +16,29 @@ bool Database::init(const QLatin1String &connectionName)
QDir dir(path);
database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
ngc = QSqlDatabase::addDatabase("QSQLITE", connectionName + "ngc");
if(!dir.mkpath("."))
return false;
if(ngc.isValid())
{
QString ngcDb = dir.absoluteFilePath("ngc.db");
if(!QFile::exists(ngcDb))
QFile::copy(":/ngc.db", ngcDb);
ngc.setDatabaseName(ngcDb);
if(ngc.open())
{
m_getNgc = QSqlQuery(ngc);
m_getNgc.prepare("SELECT *,IIF(V_Mag IS NULL, B_Mag, V_Mag) AS mag FROM ngc WHERE RA_deg BETWEEN ? AND ? AND DEC_deg BETWEEN ? AND ?");
}
else
{
qDebug() << "Could not open NGC database";
}
}
if(database.isValid())
{
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
@@ -182,7 +201,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
QStringList scannedDirs;
int count = countFiles(dir, scannedDirs);
progress->setMaximum(count);
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
scannedDirs.clear();
@@ -200,7 +218,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
void Database::reindex(QProgressDialog *progress)
{
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next();
@@ -239,6 +256,42 @@ QStringList Database::getFitsKeywords()
return keywords;
}
QVector<SkyObject> Database::getObjects(double minRa, double maxRa, double minDec, double maxDec)
{
QVector<SkyObject> objects;
if(!ngc.isOpen())return objects;
m_getNgc.bindValue(0, minRa);
m_getNgc.bindValue(1, maxRa);
m_getNgc.bindValue(2, minDec);
m_getNgc.bindValue(3, maxDec);
if(m_getNgc.exec())
{
while(m_getNgc.next())
{
QString name;
QString m = m_getNgc.value("M").toString();
QString ic = m_getNgc.value("IC").toString();
if(!m.isEmpty())name = "M" + m + " ";
if(!ic.isEmpty())name += "IC" + ic + " ";
name += m_getNgc.value("Name").toString();
objects.append({
name,
m_getNgc.value("Common names").toString(),
{m_getNgc.value("RA_deg").toDouble(), m_getNgc.value("DEC_deg").toDouble()},
m_getNgc.value("MajAx").toDouble(),
m_getNgc.value("MinAx").toDouble(),
m_getNgc.value("PosAng").toDouble(),
m_getNgc.value("mag").toDouble(),
{0, 0},
});
}
}
return objects;
}
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
{
if(scannedDirs.contains(dir.canonicalPath()))return true;
+5
View File
@@ -6,11 +6,13 @@
#include <QSqlQuery>
#include <QDir>
#include <QProgressDialog>
#include "imageinfodata.h"
class Database : public QObject
{
Q_OBJECT
QSqlDatabase database;
QSqlDatabase ngc;
QSqlQuery m_markQuery;
QSqlQuery m_unmarkQuery;
QSqlQuery m_isMarkedQuery;
@@ -22,6 +24,8 @@ class Database : public QObject
QSqlQuery m_headerKeywords;
QSqlQuery m_deleteFile;
QSqlQuery m_getNgc;
int m_progress;
public:
explicit Database(QObject *parent = 0);
@@ -37,6 +41,7 @@ public:
void indexDir(const QDir &dir, QProgressDialog *progress);
void reindex(QProgressDialog *progress);
QStringList getFitsKeywords();
QVector<SkyObject> getObjects(double minRa, double maxRa, double minDec, double maxDec);
protected:
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
bool indexFile(const QFileInfo &file);
+170 -3
View File
@@ -4,6 +4,7 @@
#include <QRegularExpression>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "database.h"
#include "libxisf.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
@@ -153,9 +154,9 @@ bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
return false;
}
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
bool WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
if(wcs == nullptr)return false;
minRa = 1000;
maxRa = -1000;
@@ -208,6 +209,7 @@ void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, dou
if(s.contains(scp))
minDec = -90;
}
return true;
}
double hav(double x)
@@ -266,6 +268,16 @@ QString SkyPoint::toString() const
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
}
QString SkyPoint::RAString() const
{
return toHMS(ra / 15);
}
QString SkyPoint::DECString() const
{
return toDMS(dec);
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
@@ -308,11 +320,21 @@ QString SkyPoint::toHMS(double decHour)
QString SkyPoint::toDMS(double deg)
{
int sign = deg < 0.0 ? -1 : 1;
deg *= sign;
double d,m,s,md;
md = std::modf(deg, &d) * 60.0;
s = std::modf(md, &m) * 60.0;
return QString("%1˚ %2' %3\"").arg((int)d, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
return QString("%1˚ %2' %3\"").arg((int)d * sign, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
SkyPoint SkyPoint::operator+(const SkyPoint &p)
{
SkyPoint ret;
ret.ra = ra + p.ra;
ret.dec = dec + p.dec;
return ret;
}
SkyPointScale ImageInfoData::getCenterRaDec() const
@@ -403,3 +425,148 @@ SkyPointScale ImageInfoData::getCenterRaDec() const
}
return ret;
}
SkyPoint greatCircle(SkyPoint &p, double dist, double azm)
{
dist = dist * M_PI / 180;
azm = azm * M_PI / 180;
double dec0 = p.DEC() * M_PI / 180;
double ra0 = p.RA() * M_PI / 180;
double dec1 = std::asin(std::sin(dec0) * std::cos(dist) + std::cos(dec0) * std::sin(dist) * std::cos(azm));
double ra1 = ra0 + std::atan2(std::sin(azm) * std::sin(dist) * std::cos(dec0), std::cos(dist) - std::sin(dec0) * std::sin(dec1));
return SkyPoint(ra1 * 180 / M_PI, dec1 * 180 / M_PI);
}
SkyGrid WCSDataT::prepareGrid(uint32_t w, uint32_t h, Database *database)
{
SkyGrid skyGrid;
if(!wcs)return skyGrid;
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
QPointF a,b;
worldToPixel(SkyPoint(crVal1, crVal2), a);
worldToPixel(SkyPoint(crVal1 + 0.01, crVal2), b);
skyGrid.rot_ang = std::atan2(b.y() - a.y(), b.x() - a.x()) / M_PI * -180.0;
if(database)
{
skyGrid.objects = database->getObjects(minRa, maxRa, minDec, maxDec);
for(auto &object : skyGrid.objects)
{
QPointF p;
if(worldToPixel(object.skyPoint, p))
object.pixel = p;
QPointF majax;
worldToPixel(greatCircle(object.skyPoint, (object.min_ax + object.maj_ax) / 120.0, object.pos_ang), majax);
majax -= p;
object.maj_ax = std::sqrt(QPointF::dotProduct(majax, majax));
}
}
double raStep = 15;
double decStep = 15;
double raRange = maxRa - minRa;
double decRange = maxDec - minDec;
const QVector<double> raSteps = {15, 5, 2.5, 1.25, 0.25, 20/240.0, 10/240.0, 5/240.0, 1/240.0};
const QVector<double> decSteps = {20, 10, 5, 2, 1, 20/60.0, 10/60.0, 5/60.0, 2/60.0, 1/60.0, 20/3600.0, 10/3600.0, 5/3600.0, 2/3600.0, 1/3600.0};
for(double ra : raSteps)
{
if(ra * 5 <= raRange)
{
raStep = ra;
break;
}
}
for(double dec : decSteps)
{
if(dec * 5 <= decRange)
{
decStep = dec;
break;
}
}
minRa -= std::fmod(minRa, raStep);
minDec -= std::fmod(minDec, decStep);
if(minRa < 0)minRa -= raStep;
if(minDec < 0)minDec -= decStep;
QRectF clip(0, 0, w, h);
const double step = 0.2;
maxRa += raStep;
maxDec += decStep;
for(double ra = minRa; ra <= maxRa; ra += raStep)
{
QPointF p;
worldToPixel(SkyPoint(ra, minDec), p);
skyGrid.grid.moveTo(p);
for(double dec = minDec + decStep * step; dec <= maxDec; dec += decStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
for(double dec = minDec; dec <= maxDec; dec += decStep)
{
QPointF p;
worldToPixel(SkyPoint(minRa, dec), p);
skyGrid.grid.moveTo(p);
for(double ra = minRa + raStep * step; ra <= maxRa; ra += raStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
SkyPoint sp1, sp2,orig;
pixelToWorld(QPointF(-1, -1), orig);
sp1 = orig;
for(uint32_t x = 0; x < w; x++)
{
QPointF p(x, 0);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
sp1 = orig;
for(uint32_t y = 0; y < h; y++)
{
QPointF p(0, y);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
skyGrid.empty = false;
return skyGrid;
}
void SkyGrid::clear()
{
empty = true;
grid.clear();
text.clear();
objects.clear();
}
+30 -1
View File
@@ -5,12 +5,15 @@
#include <QPointF>
#include <QVector>
#include <QVariant>
#include <QPainterPath>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace LibXISF { struct FITSKeyword; struct Property; }
class Database;
struct FITSRecord
{
QByteArray key;
@@ -36,10 +39,13 @@ public:
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
QString RAString() const;
QString DECString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
SkyPoint operator+(const SkyPoint &p);
};
struct SkyPointScale
@@ -51,6 +57,28 @@ struct SkyPointScale
double scaleHigh = 10000.0;
};
struct SkyObject
{
QString name;
QString name2;
SkyPoint skyPoint;
double maj_ax;
double min_ax;
double pos_ang;
double vmag;
QPointF pixel;
};
struct SkyGrid
{
bool empty = true;
QPainterPath grid;
QVector<QPair<QPointF, QString>> text;
QVector<SkyObject> objects;
double rot_ang = 0;
void clear();
};
class WCSDataT
{
int nwcs = 0;
@@ -65,9 +93,10 @@ public:
~WCSDataT();
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
SkyGrid prepareGrid(uint32_t w, uint32_t h, Database *database);
};
struct ImageInfoData
+51 -1
View File
@@ -180,6 +180,10 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{
m_wcs = wcs;
m_grid.clear();
if(m_drawGrid && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
}
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
@@ -496,6 +500,18 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
painter.drawImage(0, 0, img);
}
void ImageWidgetGL::drawGrid(bool enable)
{
if(m_grid.empty && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
if(enable != m_drawGrid)
{
m_drawGrid = enable;
update();
}
}
void ImageWidgetGL::paintGL()
{
float dx = m_dx;
@@ -613,9 +629,43 @@ void ImageWidgetGL::paintGL()
m_program->setUniformValue("colormapIdx", m_colormapIdx);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
if(m_drawGrid && !m_grid.empty)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setPen(QPen(Qt::yellow, 1.0 / m_scale));
QTransform tran;
tran.translate(-std::floor(dx), -std::floor(dy));
tran.scale(m_scale, m_scale);
painter.setTransform(tran);
painter.setClipRect(0, 0, m_imgWidth, m_imgHeight);
painter.drawPath(m_grid.grid);
painter.setPen(Qt::yellow);
QFont font({"Arial", "serif-sans"});
font.setPointSizeF(12 / m_scale);
painter.setFont(font);
for(auto &text : m_grid.text)
painter.drawText(QRectF(text.first, QSizeF(4000, 4000)), text.second);
painter.setPen(QPen(Qt::green, 1.0 / m_scale));
QFontMetricsF fontMetric = QFontMetricsF(font);
for(auto &object : m_grid.objects)
{
QRectF rect = fontMetric.boundingRect(object.name);
rect.moveCenter(object.pixel);
painter.setTransform(tran);
painter.drawText(rect, Qt::TextDontClip, object.name);
painter.translate(object.pixel);
painter.rotate(object.pos_ang);
painter.drawEllipse(QPointF(0, 0), object.maj_ax, object.maj_ax);
}
}
}
}
}
void ImageWidgetGL::resizeGL(int w, int h)
+5
View File
@@ -8,6 +8,7 @@
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLFunctions>
#include <QPainterPath>
#include "database.h"
#include "rawimage.h"
#include "imageinfodata.h"
@@ -37,6 +38,7 @@ public:
virtual QImage renderToImage() = 0;
virtual void thumbnailLoaded(const Image *image) = 0;
virtual void showThumbnail(bool enable) = 0;
virtual void drawGrid(bool enable) = 0;
static QImage loadColormap();
};
@@ -70,6 +72,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSDataT> m_wcs;
SkyGrid m_grid;
int m_width, m_height;
int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0;
@@ -87,6 +90,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
bool m_selecting = false;
bool m_sizesDirty = false;
bool m_srgb = false;
bool m_drawGrid = false;
int m_thumbnailCount = 0;
int m_maxTextureSize = 0;
int m_maxArrayLayers = 0;
@@ -116,6 +120,7 @@ public:
QImage renderToImage() override;
void thumbnailLoaded(const Image *image) override;
void showThumbnail(bool enable) override;
void drawGrid(bool enable) override;
protected:
void paintGL() override;
void resizeGL(int w, int h) override;
+4 -3
View File
@@ -165,11 +165,12 @@ void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoD
double vald = record.value.toDouble(&isdouble);
int valb = record.value.toString() == "T";
long long vall = record.value.toLongLong(&isint);
if(isint)isint = vall == vald;
QByteArray str = record.value.toString().toLatin1();
if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isint)
if(isint)
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isbool)
fits_write_key(fw, TLOGICAL, record.key.data(), &valb, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(record.key == "COMMENT")
+3 -1
View File
@@ -263,7 +263,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
slideshowAction->setCheckable(true);
viewMenu->addSeparator();
viewMenu->addActions(m_stretchPanel->actions());
auto actionList = m_stretchPanel->actions();
actionList.removeFirst();
viewMenu->addActions(actionList);
menuBar()->addMenu(viewMenu);
QMenu *selectMenu = new QMenu(tr("Select"), this);
BIN
View File
Binary file not shown.
+1
View File
@@ -19,6 +19,7 @@
<file>../translations/tenmon_pt_BR.qm</file>
<file alias="help">../about/help_en</file>
<file>colormap.png</file>
<file>ngc.db</file>
</qresource>
<qresource prefix="/" lang="en">
<file alias="help">../about/help_en</file>
+22
View File
@@ -92,6 +92,28 @@ bool ScriptEngine::isMarked(const File *file)
return ret;
}
QJSValue ScriptEngine::getObjects(double ra, double dec, double distance)
{
QVector<SkyObject> objects;
QMetaObject::invokeMethod(_database, [this, ra, dec, distance](){
return _database->getObjects(ra - distance, ra + distance, dec - distance, dec + distance); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("vmag", object.vmag);
ret.setProperty(i++, jsObj);
}
return ret;
}
void ScriptEngine::setMaxThread(int maxthread)
{
int newval = std::max(std::min(QThread::idealThreadCount(), maxthread), 1);
+1
View File
@@ -41,6 +41,7 @@ public:
Q_INVOKABLE void mark(File *file);
Q_INVOKABLE void unmark(File *file);
Q_INVOKABLE bool isMarked(const File *file);
Q_INVOKABLE QJSValue getObjects(double ra, double dec, double distance);
Q_INVOKABLE void setMaxThread(int maxthread);
Q_INVOKABLE void sync();
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;