Initial stellarsolver implementation

This commit is contained in:
2024-09-17 23:05:27 +02:00
parent 30960033c5
commit da79197376
11 changed files with 182 additions and 35 deletions
+9
View File
@@ -25,6 +25,7 @@ find_library(FITS_LIB cfitsio REQUIRED)
find_library(RAW_LIB NAMES raw_r REQUIRED) find_library(RAW_LIB NAMES raw_r REQUIRED)
find_library(WCS_LIB wcs wcslib REQUIRED) find_library(WCS_LIB wcs wcslib REQUIRED)
find_library(LCMS2_LIB lcms2 REQUIRED) find_library(LCMS2_LIB lcms2 REQUIRED)
find_library(STELLARSOLVER_LIB stellarsolver)
add_subdirectory(libXISF) add_subdirectory(libXISF)
@@ -92,6 +93,14 @@ if(LIBRAW_STATIC)
target_link_libraries(tenmon PRIVATE jasper) target_link_libraries(tenmon PRIVATE jasper)
endif() endif()
find_path(STELLARSOLVER_INCLUDE stellarsolver.h PATH_SUFFIXES libstellarsolver)
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB})
target_compile_definitions(tenmon PRIVATE "PLATESOLVER")
target_sources(tenmon PRIVATE solver.cpp solver.h)
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
option(FLATPAK "Flatpak build" OFF) option(FLATPAK "Flatpak build" OFF)
if(FLATPAK) if(FLATPAK)
target_compile_definitions(tenmon PRIVATE FLATPAK) target_compile_definitions(tenmon PRIVATE FLATPAK)
+7 -7
View File
@@ -98,14 +98,14 @@ void ImageInfo::setInfo(const ImageInfoData &info)
expandAll(); expandAll();
} }
void WCSData::freeWCS() void WCSDataT::freeWCS()
{ {
wcsvfree(&nwcs, &wcs); wcsvfree(&nwcs, &wcs);
nwcs = 0; nwcs = 0;
wcs = nullptr; wcs = nullptr;
} }
WCSData::WCSData(int width, int height, char *header, int nrec) : WCSDataT::WCSDataT(int width, int height, char *header, int nrec) :
width(width), width(width),
height(height) height(height)
{ {
@@ -121,7 +121,7 @@ WCSData::WCSData(int width, int height, char *header, int nrec) :
freeWCS(); freeWCS();
} }
WCSData::WCSData(int width, int height, const QVector<FITSRecord> &header) : WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
width(width), width(width),
height(height) height(height)
{ {
@@ -156,13 +156,13 @@ WCSData::WCSData(int width, int height, const QVector<FITSRecord> &header) :
freeWCS(); freeWCS();
} }
WCSData::~WCSData() WCSDataT::~WCSDataT()
{ {
if(wcs) if(wcs)
freeWCS(); freeWCS();
} }
bool WCSData::pixelToWorld(const QPointF &pixel, SkyPoint &point) const bool WCSDataT::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
{ {
if(!valid())return false; if(!valid())return false;
@@ -181,7 +181,7 @@ bool WCSData::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
return false; return false;
} }
bool WCSData::worldToPixel(const SkyPoint &point, QPointF &pixel) const bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
{ {
if(!valid())return false; if(!valid())return false;
@@ -200,7 +200,7 @@ bool WCSData::worldToPixel(const SkyPoint &point, QPointF &pixel) const
return false; return false;
} }
void WCSData::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{ {
if(wcs == nullptr)return; if(wcs == nullptr)return;
+6 -6
View File
@@ -35,7 +35,7 @@ public:
QString toString() const; QString toString() const;
}; };
class WCSData class WCSDataT
{ {
int nwcs = 0; int nwcs = 0;
struct wcsprm *wcs = nullptr; struct wcsprm *wcs = nullptr;
@@ -43,10 +43,10 @@ class WCSData
int height; int height;
void freeWCS(); void freeWCS();
public: public:
WCSData(int width, int height, char *header, int nrec); WCSDataT(int width, int height, char *header, int nrec);
WCSData(int width, int height, const QVector<FITSRecord> &header); WCSDataT(int width, int height, const QVector<FITSRecord> &header);
WCSData(const WCSData &) = delete; WCSDataT(const WCSDataT &) = delete;
~WCSData(); ~WCSDataT();
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const; bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const; bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const; void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
@@ -57,7 +57,7 @@ struct ImageInfoData
{ {
QVector<FITSRecord> fitsHeader; QVector<FITSRecord> fitsHeader;
QVector<QPair<QString, QString>> info; QVector<QPair<QString, QString>> info;
std::shared_ptr<WCSData> wcs; std::shared_ptr<WCSDataT> wcs;
}; };
Q_DECLARE_METATYPE(ImageInfoData); Q_DECLARE_METATYPE(ImageInfoData);
+1 -1
View File
@@ -168,7 +168,7 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
else setOffset(m_dx, m_dy); else setOffset(m_dx, m_dy);
} }
void ImageWidgetGL::setWCS(std::shared_ptr<WCSData> wcs) void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{ {
m_wcs = wcs; m_wcs = wcs;
} }
+3 -3
View File
@@ -20,7 +20,7 @@ public:
virtual ~ImageWidget(){} virtual ~ImageWidget(){}
virtual void setImage(std::shared_ptr<RawImage> image, int index) = 0; virtual void setImage(std::shared_ptr<RawImage> image, int index) = 0;
virtual void setWCS(std::shared_ptr<WCSData> wcs) = 0; virtual void setWCS(std::shared_ptr<WCSDataT> wcs) = 0;
virtual void zoom(int zoom, const QPointF &mousePos = QPointF()) = 0; virtual void zoom(int zoom, const QPointF &mousePos = QPointF()) = 0;
virtual void bestFit() = 0; virtual void bestFit() = 0;
@@ -65,7 +65,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
std::unique_ptr<QOpenGLTexture> m_lut; std::unique_ptr<QOpenGLTexture> m_lut;
GLuint m_debayerTex = 0; GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage; std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs; std::shared_ptr<WCSDataT> m_wcs;
int m_width, m_height; int m_width, m_height;
int m_imgWidth = -1, m_imgHeight = -1; int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0; int m_currentImg = 0;
@@ -95,7 +95,7 @@ public:
explicit ImageWidgetGL(Database *database, QWidget *parent = nullptr); explicit ImageWidgetGL(Database *database, QWidget *parent = nullptr);
~ImageWidgetGL() override; ~ImageWidgetGL() override;
void setImage(std::shared_ptr<RawImage> image, int index) override; void setImage(std::shared_ptr<RawImage> image, int index) override;
void setWCS(std::shared_ptr<WCSData> wcs) override; void setWCS(std::shared_ptr<WCSDataT> wcs) override;
void zoom(int zoom, const QPointF &mousePos = QPointF()) override; void zoom(int zoom, const QPointF &mousePos = QPointF()) override;
void bestFit() override; void bestFit() override;
void allocateThumbnails(const QStringList &paths) override; void allocateThumbnails(const QStringList &paths) override;
+19 -10
View File
@@ -176,14 +176,14 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status); fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
if(status == 0) if(status == 0)
{ {
info.wcs = std::make_shared<WCSData>(naxes[0], naxes[1], header, nrec); info.wcs = std::make_shared<WCSDataT>(naxes[0], naxes[1], header, nrec);
if(!info.wcs->valid())info.wcs.reset(); if(!info.wcs->valid())info.wcs.reset();
} }
fits_free_memory(header, &status); fits_free_memory(header, &status);
return status; return status;
} }
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image) bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{ {
fitsfile *file; fitsfile *file;
int status = 0; int status = 0;
@@ -261,7 +261,7 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
s[i] -= INT16_MIN; s[i] -= INT16_MIN;
} }
if(img.channels() == 1) if(img.channels() == 1 || planar)
image = std::make_shared<RawImage>(std::move(img)); image = std::make_shared<RawImage>(std::move(img));
else else
image = RawImage::fromPlanar(img); image = RawImage::fromPlanar(img);
@@ -294,7 +294,7 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
return true; return true;
} }
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image) bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{ {
try try
{ {
@@ -314,7 +314,7 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
info.fitsHeader.append(prop); info.fitsHeader.append(prop);
} }
info.wcs = std::make_shared<WCSData>(xisfImage.width(), xisfImage.height(), info.fitsHeader); info.wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), info.fitsHeader);
info.info.append({QObject::tr("Width"), QString::number(xisfImage.width())}); info.info.append({QObject::tr("Width"), QString::number(xisfImage.width())});
info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())}); info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())});
if(!info.wcs->valid())info.wcs.reset(); if(!info.wcs->valid())info.wcs.reset();
@@ -341,7 +341,16 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
} }
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4) else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{ {
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type); if(planar)
{
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize());
}
else
{
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
}
image->setICCProfile(tmpImage.iccProfile()); image->setICCProfile(tmpImage.iccProfile());
return true; return true;
} }
@@ -471,7 +480,7 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
{ {
info.fitsHeader.append(prop); info.fitsHeader.append(prop);
} }
info.wcs = std::make_shared<WCSData>(image.width(), image.height(), info.fitsHeader); info.wcs = std::make_shared<WCSDataT>(image.width(), image.height(), info.fitsHeader);
if(!info.wcs->valid())info.wcs.reset(); if(!info.wcs->valid())info.wcs.reset();
} }
catch (LibXISF::Error &err) catch (LibXISF::Error &err)
@@ -482,7 +491,7 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
return true; return true;
} }
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage) bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
{ {
bool ret = false; bool ret = false;
QElapsedTimer timer; QElapsedTimer timer;
@@ -494,12 +503,12 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
} }
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive)) else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive))
{ {
ret = loadFITS(path, info, rawImage); ret = loadFITS(path, info, rawImage, planar);
qDebug() << "LoadFITS" << timer.elapsed(); qDebug() << "LoadFITS" << timer.elapsed();
} }
else if(path.endsWith(".XISF", Qt::CaseInsensitive)) else if(path.endsWith(".XISF", Qt::CaseInsensitive))
{ {
ret = loadXISF(path, info, rawImage); ret = loadXISF(path, info, rawImage, planar);
qDebug() << "LoadXISF" << timer.elapsed(); qDebug() << "LoadXISF" << timer.elapsed();
} }
else else
+1 -1
View File
@@ -10,7 +10,7 @@ class RawImage;
bool readFITSHeader(const QString &path, ImageInfoData &info); bool readFITSHeader(const QString &path, ImageInfoData &info);
bool readXISFHeader(const QString &path, ImageInfoData &info); bool readXISFHeader(const QString &path, ImageInfoData &info);
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage); bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar = false);
class Image; class Image;
+6 -6
View File
@@ -359,7 +359,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
fits_get_num_hdus(file, &num, &status); fits_get_num_hdus(file, &num, &status);
if(status) if(status)
{ {
_engine->newMessage("Failed to open FITS file", true); if(_engine)_engine->newMessage("Failed to open FITS file", true);
return false; return false;
} }
int imgtype; int imgtype;
@@ -412,7 +412,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
break; break;
} }
default: default:
_engine->newMessage("Unknown type for KEY " + record.key, true); if(_engine)_engine->newMessage("Unknown type for KEY " + record.key, true);
return false; return false;
break; break;
} }
@@ -420,7 +420,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
{ {
char error[100]; char error[100];
fits_get_errstatus(status, error); fits_get_errstatus(status, error);
_engine->newMessage(QString("Error when updating KEY %1 %2").arg(record.key).arg(error), true); if(_engine)_engine->newMessage(QString("Error when updating KEY %1 %2").arg(record.key).arg(error), true);
return false; return false;
} }
} }
@@ -456,7 +456,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
break; break;
} }
default: default:
_engine->newMessage("Unknown type for KEY " + record.key, true); if(_engine)_engine->newMessage("Unknown type for KEY " + record.key, true);
return false; return false;
break; break;
} }
@@ -464,7 +464,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
{ {
char error[100]; char error[100];
fits_get_errstatus(status, error); fits_get_errstatus(status, error);
_engine->newMessage(QString("Error when adding KEY {} {}").arg(record.key).arg(error), true); if(_engine)_engine->newMessage(QString("Error when adding KEY {} {}").arg(record.key).arg(error), true);
return false; return false;
} }
} }
@@ -496,7 +496,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
} }
catch(LibXISF::Error &err) catch(LibXISF::Error &err)
{ {
_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true); if(_engine)_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true);
return false; return false;
} }
} }
+1 -1
View File
@@ -76,7 +76,7 @@ class FITSRecordModify;
class File : public QObject class File : public QObject
{ {
Q_OBJECT Q_OBJECT
ScriptEngine *_engine; ScriptEngine *_engine = nullptr;
QString _path; QString _path;
QString _root; QString _root;
QFileInfo _info; QFileInfo _info;
+107
View File
@@ -0,0 +1,107 @@
#include "solver.h"
#include <fitsio.h>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsutil.h>
#include "rawimage.h"
#include "loadrunable.h"
#include "scriptengine.h"
Solver::Solver(QObject *parent) : QObject(parent)
{
_solver = std::make_unique<StellarSolver>();
QStringList indexFolder = StellarSolver::getDefaultIndexFolderPaths();
_solver->setProperty("ProcessType", SSolver::SOLVE);
_solver->setIndexFolderPaths(indexFolder);
_solver->setParameterProfile(SSolver::Parameters::ALL_STARS);
}
Solver::~Solver()
{
}
bool Solver::solveImage(const QString &path)
{
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
if(loadImage(path, info, rawImage, true))
{
_path = path;
switch(rawImage->type())
{
case RawImage::UINT8:
_stats.dataType = TBYTE;
break;
case RawImage::UINT16:
_stats.dataType = TUSHORT;
break;
case RawImage::UINT32:
_stats.dataType = TUINT;
break;
case RawImage::FLOAT32:
_stats.dataType = TFLOAT;
break;
case RawImage::FLOAT64:
_stats.dataType = TDOUBLE;
break;
default:
_error = tr("Unsupported image data type");
return false;
break;
}
_stats.bytesPerPixel = rawImage->typeSize(rawImage->type());
_stats.channels = rawImage->channels();
_stats.width = rawImage->width();
_stats.height = rawImage->height();
_stats.samples_per_channel = _stats.width * _stats.height;
_solver->loadNewImageBuffer(_stats, (const uint8_t*)rawImage->data());
return _solver->solve();
}
return false;
}
FITSImage::Solution Solver::getSolution() const
{
return _solver->getSolution();
}
QString Solver::errorMessage() const
{
return _error;
}
void Solver::updateHeader()
{
FITSImage::Solution solution = getSolution();
qDebug() << "RA" << solution.ra << "DEC" << solution.dec << "Orient" << solution.orientation << "field wxh" << solution.fieldWidth << solution.fieldHeight << solution.pixscale;
qDebug() << "error" << solution.raError << solution.decError;
double rotationDeg = 360.0 - solution.orientation;
if(rotationDeg > 360)rotationDeg -= 360;
double rotationRad = rotationDeg / 180.0 * M_PI;
double cdeltx = (solution.parity == FITSImage::NEGATIVE ? solution.pixscale : -solution.pixscale) / 3600.0;
double cdelty = solution.pixscale / 3600.0;
Script::File file(_path, nullptr);
Script::FITSRecordModify modify;
modify.updateKeyword("CRPIX1", _stats.width / 2.0, QByteArray("x pixel coordinate of the reference point"));
modify.updateKeyword("CRPIX2", _stats.height / 2.0, QByteArray("y pixel coordinate of the reference point"));
modify.updateKeyword("CDELT1", cdeltx, QByteArray("X pixel size (deg)"));
modify.updateKeyword("CDELT2", cdelty, QByteArray("Y pixel size (deg)"));
modify.updateKeyword("CRVAL1", solution.ra, QByteArray("RA of reference pixel (deg)"));
modify.updateKeyword("CRVAL2", solution.dec, QByteArray("DEC of reference pixel (deg)"));
modify.updateKeyword("CD1_1", std::cos(rotationRad) * cdeltx, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CD1_2",-std::sin(rotationRad) * cdelty, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CD2_1", std::sin(rotationRad) * cdeltx, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CD2_2", std::cos(rotationRad) * cdelty, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CROTA1", rotationDeg, QByteArray("Image twist X axis (deg)"));
modify.updateKeyword("CROTA2", rotationDeg, QByteArray("Image twist Y axis (deg)"));
modify.updateKeyword("CTYPE1", "RA---TAN", QByteArray("first parameter RA, projection TANgential"));
modify.updateKeyword("CTYPE2", "DEC--TAN", QByteArray("first parameter DEC, projection TANgential"));
modify.updateKeyword("RADESYS", "ICRS", QByteArray("International Celestial Reference System"));
modify.updateKeyword("EQUINOX", 2000, QByteArray("Equinox of coordinates"));
file.modifyFITSRecords(&modify);
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef SOLVER_H
#define SOLVER_H
#include <stellarsolver.h>
class Solver : public QObject
{
Q_OBJECT
std::unique_ptr<StellarSolver> _solver;
FITSImage::Statistic _stats;
QString _path;
QString _error;
public:
explicit Solver(QObject *parent = nullptr);
~Solver();
bool solveImage(const QString &path);
FITSImage::Solution getSolution() const;
QString errorMessage() const;
void updateHeader();
};
#endif // SOLVER_H