Compare commits

..

18 Commits

Author SHA1 Message Date
nou 5249b277ec Add integer resample 2025-02-25 17:41:29 +01:00
nou e4b9fefa5a Move MTFParam 2025-02-25 17:35:55 +01:00
nou d069ce3302 Add shortcut to 100% zoom 2025-02-23 09:45:29 +01:00
nou 58c182adc0 Add thumbnailer 2025-02-16 23:36:25 +01:00
nou c36068aaf4 Fix mxe build 2025-02-16 16:19:39 +01:00
nou fcb3aec81f Remove startfit 2025-02-16 15:22:38 +01:00
nou 7510dac82b Reorginize code 2025-02-16 15:19:20 +01:00
nou 55439be04c Add Portuguese translation thanks to John Peter Sá 2025-02-07 15:10:19 +01:00
nou 0ff2001797 Handle MAX_PATH every where 2025-01-26 16:00:00 +01:00
nou fc36024eee Remove deprecated QSqlQuery usage 2025-01-19 15:49:31 +01:00
nou 3cda53f26c Do not attempt open not existing file 2025-01-19 15:11:32 +01:00
nou 58d18cc28a Remove unused function 2025-01-19 15:11:14 +01:00
nou 2b96da60de Do not use global thread pool 2025-01-12 10:59:12 +01:00
nou 236f66ed2f Add solver profile to script engine 2024-12-27 23:20:51 +01:00
nou a86c100e69 Fix issue with Qt 6.8 2024-12-22 12:19:34 +01:00
nou 45ee9b7258 Fix half pixel offset and add filtering in sw rendering
Signed-off-by: Dušan Poizl <nou.spiro@gmail.com>
2024-12-05 16:02:46 +01:00
nou be1e65251d Support really big images 50000px 2024-11-30 22:03:58 +01:00
nou 9b7837e9fb SW rendering when image is too big for texture 2024-11-27 20:21:57 +01:00
39 changed files with 2959 additions and 1452 deletions
+8 -4
View File
@@ -18,8 +18,6 @@ if(SANITIZE_ADDRESS_LEAK)
endif(SANITIZE_ADDRESS_LEAK)
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
find_library(GSL_LIB gsl REQUIRED)
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
find_library(EXIF_LIB exif REQUIRED)
find_library(FITS_LIB cfitsio REQUIRED)
find_library(RAW_LIB NAMES raw_r REQUIRED)
@@ -39,18 +37,20 @@ set(TENMON_SRC
histogram.cpp histogram.h
httpdownloader.h httpdownloader.cpp
imageinfo.cpp imageinfo.h
imageinfodata.cpp imageinfodata.h
imageringlist.cpp imageringlist.h
imagescrollarea.cpp imagescrollarea.h
imagewidget.h imagewidget.cpp
loadimage.h loadimage.cpp
loadrunable.cpp loadrunable.h
main.cpp
mainwindow.cpp mainwindow.h
markedfiles.cpp markedfiles.h
mtfparam.h
rawimage.cpp rawimage.h
rawimage_sse.cpp
scriptengine.cpp scriptengine.h
settingsdialog.cpp settingsdialog.h
starfit.cpp starfit.h
statusbar.cpp statusbar.h
stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h
@@ -85,6 +85,8 @@ find_path(STELLARSOLVER_INCLUDE stellarsolver.h PATH_SUFFIXES libstellarsolver)
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
if(MXE)
find_library(GSL_LIB gsl REQUIRED)
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB} ${GSL_LIB} ${GSLCBLAS_LIB} boost_regex-mt-x64)
else(MXE)
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB})
@@ -98,7 +100,7 @@ if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
if(APPLE)
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
elseif(UNIX)
@@ -138,3 +140,5 @@ else()
execute_process(COMMAND ${CMAKE_COMMAND} -Dlocal_dir=${CMAKE_CURRENT_SOURCE_DIR} -Doutput_dir=${CMAKE_CURRENT_BINARY_DIR}
-P "${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake")
endif()
add_subdirectory(thumbnailer)
+2 -2
View File
@@ -2,11 +2,11 @@ FITS/XISF image viewer with multithreaded image loading
To get all dependencies install these packages
sudo apt install qt6-base-dev qt6-declarative-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
sudo apt install qt6-base-dev qt6-declarative-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
on OpenSUSE
sudo zypper install gsl-devel libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
sudo zypper install libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
MacOS X
-3
View File
@@ -33,9 +33,6 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
setWindowTitle(tr("Help"));
resize(800, 600);
QLocale locale;
QString l = QLocale::languageToString(locale.language());
QVBoxLayout *layout = new QVBoxLayout(this);
QTextEdit *helpText = new QTextEdit(this);
helpText->setReadOnly(true);
+31 -30
View File
@@ -4,7 +4,7 @@
#include <QSqlError>
#include <QDebug>
#include <QDateTime>
#include "loadrunable.h"
#include "loadimage.h"
Database::Database(QObject *parent) : QObject(parent)
{
@@ -15,32 +15,33 @@ bool Database::init(const QLatin1String &connectionName)
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
if(!dir.mkpath("."))
return false;
if(m_database.isValid())
if(database.isValid())
{
m_database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(m_database.open())
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(database.open())
{
m_database.exec("PRAGMA foreign_keys = ON");
QSqlQuery query(database);
query.exec("PRAGMA foreign_keys = ON");
int version = checkVersion();
if(version == 0)
{
m_database.exec("PRAGMA user_version = 1");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME,"
query.exec("PRAGMA user_version = 1");
query.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
query.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME,"
" minRa REAL, maxRa REAL, minDec REAL, maxDec REAL, crVal1 REAL, crVal2 REAL)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
query.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
"key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)");
m_database.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
m_database.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
m_database.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
query.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
query.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
query.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)");
query.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
query.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
query.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
}
else if(version > 1)
{
@@ -48,28 +49,28 @@ bool Database::init(const QLatin1String &connectionName)
return false;
}
QSqlError error = m_database.lastError();
QSqlError error = database.lastError();
if(error.type() == QSqlError::NoError)
{
m_markQuery = QSqlQuery(m_database);
m_markQuery = QSqlQuery(database);
m_markQuery.prepare("INSERT INTO files (file) VALUES (?)");
m_unmarkQuery = QSqlQuery(m_database);
m_unmarkQuery = QSqlQuery(database);
m_unmarkQuery.prepare("DELETE FROM files WHERE file = (?)");
m_isMarkedQuery = QSqlQuery(m_database);
m_isMarkedQuery = QSqlQuery(database);
m_isMarkedQuery.prepare("SELECT * FROM files WHERE file = (:name)");
m_insertFile = QSqlQuery(m_database);
m_insertFile = QSqlQuery(database);
m_insertFile.prepare("INSERT INTO fits_files (file, mtime) VALUES (?, ?)");
m_insertFileWcs = QSqlQuery(m_database);
m_insertFileWcs = QSqlQuery(database);
m_insertFileWcs.prepare("INSERT INTO fits_files (file, mtime, minRa, maxRa, minDec, maxDec, crVal1, crVal2) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
m_insertFitsHeader = QSqlQuery(m_database);
m_insertFitsHeader = QSqlQuery(database);
m_insertFitsHeader.prepare("INSERT INTO fits_headers (id_file, key, value, comment) VALUES (?, ?, ?, ?)");
m_checkFile = QSqlQuery(m_database);
m_checkFile = QSqlQuery(database);
m_checkFile.prepare("SELECT id,mtime FROM fits_files WHERE file=?");
m_headerKeywords = QSqlQuery(m_database);
m_headerKeywords = QSqlQuery(database);
m_headerKeywords.prepare("SELECT DISTINCT key FROM fits_headers ORDER BY key");
m_deleteFile = QSqlQuery(m_database);
m_deleteFile = QSqlQuery(database);
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
return true;
}
@@ -130,7 +131,7 @@ QStringList Database::getMarkedFiles()
void Database::clearMarkedFiles()
{
QSqlDatabase::database().exec("DELETE FROM files");
QSqlQuery query("DELETE FROM files");
}
bool Database::checkError(QSqlQuery &query)
@@ -148,7 +149,7 @@ bool Database::checkError(QSqlQuery &query)
int Database::checkVersion()
{
QSqlDatabase db = QSqlDatabase::database();
QSqlQuery query = db.exec("PRAGMA user_version");
QSqlQuery query("PRAGMA user_version");
if(query.next())
return query.value(0).toInt();
return -1;
@@ -194,10 +195,10 @@ void Database::reindex(QProgressDialog *progress)
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size = database.exec("SELECT COUNT(*) FROM fits_files");
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next();
progress->setMaximum(size.value(0).toInt());
QSqlQuery files = database.exec("SELECT id,file,mtime FROM fits_files");
QSqlQuery files("SELECT id,file,mtime FROM fits_files", database);
int i = 0;
while(files.next())
{
+1 -1
View File
@@ -375,7 +375,7 @@ bool DataBaseView::exportCSV(const QString &path)
if(!csv.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
QSqlQuery sql = m_model->query();
QSqlQuery sql(m_model->query().lastQuery());
int colCount = m_model->columnCount();
QStringList header;
for(int i=0; i<colCount; i++)
+1
View File
@@ -3,6 +3,7 @@
#include <algorithm>
#include <QPainter>
#include <QDebug>
#include <QStyleOption>
Histogram::Histogram(QWidget *parent) : QWidget(parent)
{
-410
View File
@@ -1,63 +1,6 @@
#include "imageinfo.h"
#include <QSettings>
#include <QTime>
#include <QHeaderView>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include <libxisf.h>
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
bool FITSRecord::editable() const
{
return noEditableKey.count(key);
}
FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment) :
key(key), value(value), comment(comment)
{
}
FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
{
key = record.name.c_str();
comment = record.comment.c_str();
QString string = record.value.c_str();
if(string.startsWith('\'') && string.endsWith('\''))
{
string.chop(1);
string.remove(0, 1);
}
bool isint;
bool isdouble;
double vald = string.toDouble(&isdouble);
long long vall = string.toLongLong(&isint);
if(isint)
value = vall;
else if(isdouble)
value = vald;
else if(string == "T" || string == "F")
value = string == "T";
else
value = string;
}
FITSRecord::FITSRecord(const LibXISF::Property &property)
{
key = property.id.c_str();
value = QString::fromStdString(property.value.toString());
comment = property.comment.c_str();
xisf = true;
}
QByteArray FITSRecord::valueToByteArray() const
{
if(value.type() == QVariant::Bool)
return value.toBool() ? "T" : "F";
else
return value.toString().toLatin1();
}
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
{
@@ -97,356 +40,3 @@ void ImageInfo::setInfo(const ImageInfoData &info)
}
expandAll();
}
void WCSDataT::freeWCS()
{
wcsvfree(&nwcs, &wcs);
nwcs = 0;
wcs = nullptr;
}
WCSDataT::WCSDataT(int width, int height, char *header, int nrec) :
width(width),
height(height)
{
int nreject = 0;
int status = wcspih(header, nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
width(width),
height(height)
{
int status = 0;
QByteArray str;
int nrec = 1;
for(const FITSRecord &record : header)
{
if(record.key.startsWith("PV"))continue;
QByteArray rec;
rec.append(record.key.leftJustified(8, ' '));
rec.append("= ");
rec.append(record.value.toString().toLatin1());
rec.append(" / ");
rec.append(record.comment);
str.append(rec.leftJustified(80, ' ', true));
nrec++;
}
str.append(QByteArray("END").leftJustified(80));
int nreject = 0;
status = wcspih(str.data(), nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSDataT::~WCSDataT()
{
if(wcs)
freeWCS();
}
bool WCSDataT::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
{
if(!valid())return false;
double pixcrd[2] = {pixel.x(), pixel.y()};
double imgcrd[8] = {0};
double phi = 0;
double theta = 0;
double world[8] = {0};
int stat[NWCSFIX] = {0};
int status = wcsp2s(wcs, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat);
if(status == 0)
{
point = SkyPoint(world[0], world[1]);
return true;
}
return false;
}
bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
{
if(!valid())return false;
double world[2] = {point.RA(), point.DEC()};
double phi = 0;
double theta = 0;
double imgcrd[8] = {0};
double pixcrd[8] = {0};
int stat[NWCSFIX] = {0};
int status = wcss2p(wcs, 1, 2, world, &phi, &theta, imgcrd, pixcrd, stat);
if(status == 0)
{
pixel = QPointF(pixcrd[0], pixcrd[1]);
return true;
}
return false;
}
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
minRa = 1000;
maxRa = -1000;
minDec = 1000;
maxDec = -1000;
if(wcs->crval)
{
crVal1 = wcs->crval[0];
crVal2 = wcs->crval[1];
}
else
{
crVal1 = crVal2 = NAN;
}
auto update = [&](const QPointF &pixel)
{
SkyPoint point;
pixelToWorld(pixel, point);
minRa = std::min(minRa, point.RA());
maxRa = std::max(maxRa, point.RA());
minDec = std::min(minDec, point.DEC());
maxDec = std::max(maxDec, point.DEC());
};
for(int x=0; x<width; x++)
{
update(QPointF(x, 0));
update(QPointF(x, height - 1));
}
for(int y=0; y<height; y++)
{
update(QPointF(0, y));
update(QPointF(width - 1, y));
}
QPointF ncp;
QPointF scp;
QRectF s(0, 0, width - 1, height - 1);
if(worldToPixel(SkyPoint(0, 90), ncp))
{
if(s.contains(ncp))
maxDec = 90;
}
if(worldToPixel(SkyPoint(0, -90), scp))
{
if(s.contains(scp))
minDec = -90;
}
}
double hav(double x)
{
return (1.0 - std::cos(x)) * 0.5;
}
double haverSine(const SkyPoint &a, SkyPoint &b)
{
const double ToRAD = M_PI / 180.0;
double d = hav((a.DEC() - b.DEC()) * ToRAD) + std::cos(a.DEC() * ToRAD) * std::cos(b.DEC() * ToRAD) * hav((a.RA() - b.RA()) * ToRAD);
return std::acos(1.0 - 2.0 * d) * (180.0 / M_PI);
}
SkyPointScale WCSDataT::getRaDecScale() const
{
SkyPointScale ret;
pixelToWorld(QPointF(width/2.0, height/2.0), ret.point);
SkyPoint pointX;
SkyPoint pointY;
pixelToWorld(QPointF(width/2.0+1, height/2.0), pointX);
pixelToWorld(QPointF(width/2.0, height/2.0+1), pointY);
double scaleX = haverSine(ret.point, pointX) * 3600.0;
double scaleY = haverSine(ret.point, pointY) * 3600.0;
ret.scaleLow = std::min(scaleX, scaleY);
ret.scaleHigh = std::max(scaleX, scaleY);
ret.scaleValid = true;
return ret;
}
SkyPoint::SkyPoint() : ra(NAN), dec(NAN)
{
}
SkyPoint::SkyPoint(double ra, double dec) : ra(ra), dec(dec)
{
}
void SkyPoint::set(double ra, double dec)
{
this->ra = ra;
this->dec = dec;
}
QString SkyPoint::toString() const
{
if(std::isnan(ra) || std::isnan(dec))
return QString();
QTime t(0, 0);
t = t.addSecs(ra * 240);
double deg, min, sec;
min = std::abs(std::modf(dec, &deg) * 60);
sec = std::modf(min, &min) * 60;
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');
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
if(std::isnan(deg))return deg;
return deg * 15.0;
}
double SkyPoint::fromDMS(const QString &dms)
{
double deg = 0.0;
QString str = dms.trimmed();
str.remove(QRegularExpression("[hdms°'\"]"));
str.replace(':', ' ');
str.replace(QRegularExpression("\\s+"), " ");
QStringList fields = str.split(' ');
double sign = 1.0;
bool ok = false;
if(fields.size() >= 1)
deg = fields.at(0).toDouble(&ok);
if(!ok)return NAN;
if(deg < 0.0)
sign = -1.0;
if(fields.size() >= 2)
deg += sign * fields.at(1).toDouble() / 60.0;
if(fields.size() >= 3)
deg += sign * fields.at(2).toDouble() / 3600.0;
return deg;
}
QString SkyPoint::toHMS(double decHour)
{
double h,m,s,md;
md = std::modf(decHour, &h) * 60.0;
s = std::modf(md, &m) * 60.0;
return QString("%1h %2m %3s").arg((int)h, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
QString SkyPoint::toDMS(double deg)
{
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'));
}
SkyPointScale ImageInfoData::getCenterRaDec() const
{
SkyPointScale ret;
if(wcs && wcs->valid())
{
ret = wcs->getRaDecScale();
}
else
{
double ra,dec,focalLen,scale,pixSizeX,pixSizeY;
int binX = 1;
int binY = 1;
ra = dec = focalLen = scale = pixSizeX = pixSizeY = NAN;
bool ok;
for(const FITSRecord &header : fitsHeader)
{
if(header.key == "OBJCTRA")
{
double tmp = SkyPoint::fromHMS(header.value.toString());
if(!std::isnan(tmp))ra = tmp;
}
else if(header.key == "RA" && std::isnan(ra))
{
double tmp = header.value.toDouble(&ok);
if(ok)ra = tmp;
}
else if(header.key == "OBJCTDEC")
{
double tmp = SkyPoint::fromDMS(header.value.toString());
if(!std::isnan(tmp))dec = tmp;
}
else if(header.key == "DEC" && std::isnan(dec))
{
double tmp = SkyPoint::fromDMS(header.value.toString());
if(!std::isnan(tmp))dec = tmp;
}
else if(header.key == "SCALE")
{
double tmp = header.value.toDouble(&ok);
if(ok)scale = tmp;
}
else if(header.key == "FOCALLEN")
{
double tmp = header.value.toDouble(&ok);
if(ok)focalLen = tmp;
}
else if(header.key == "PIXSIZE1" || header.key == "XPIXSZ")
{
pixSizeX = header.value.toDouble();
}
else if(header.key == "PIXSIZE2" || header.key == "YPIXSZ")
{
pixSizeY = header.value.toDouble();
}
else if(header.key == "XBINNING")
{
int tmp = header.value.toInt(&ok);
if(ok)binX = tmp;
}
else if(header.key == "YBINNING")
{
int tmp = header.value.toInt(&ok);
if(ok)binY = tmp;
}
}
ret.point.set(ra, dec);
if(!std::isnan(scale))
{
ret.scaleLow = ret.scaleHigh = scale;
ret.scaleValid = true;
}
else if(!(std::isnan(focalLen) || std::isnan(pixSizeX) || std::isnan(pixSizeY)))
{
const double r = 206.2648097656; // (180 * 3600) / (1000 * pi) magic number to convert pixel size to focal length ratio to arcsec.
ret.scaleLow = std::min(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r);
ret.scaleHigh = std::max(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r);
ret.scaleValid = true;
}
}
if(ret.scaleValid)
{
ret.scaleLow *= 0.8;
ret.scaleHigh *= 1.2;
}
return ret;
}
+1 -83
View File
@@ -2,89 +2,7 @@
#define IMAGEINFO_H
#include <QTreeWidget>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace LibXISF { struct FITSKeyword; struct Property; }
struct FITSRecord
{
QByteArray key;
QVariant value;
QByteArray comment;
bool xisf = false;
bool editable() const;
FITSRecord(){}
FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment);
FITSRecord(const LibXISF::FITSKeyword &record);
FITSRecord(const LibXISF::Property &property);
QByteArray valueToByteArray() const;
};
class SkyPoint
{
double ra = NAN;
double dec = NAN;
public:
SkyPoint();
SkyPoint(double ra, double dec);
void set(double ra, double dec);
double RA() const { return ra; }
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
};
struct SkyPointScale
{
SkyPoint point;
//arcsec per pixel
bool scaleValid = false;
double scaleLow = 0.0;
double scaleHigh = 10000.0;
};
class WCSDataT
{
int nwcs = 0;
struct wcsprm *wcs = nullptr;
int width;
int height;
void freeWCS();
public:
WCSDataT(int width, int height, char *header, int nrec);
WCSDataT(int width, int height, const QVector<FITSRecord> &header);
WCSDataT(const WCSDataT &) = delete;
~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 valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
};
struct ImageInfoData
{
QVector<FITSRecord> fitsHeader;
QVector<QPair<QString, QString>> info;
std::shared_ptr<WCSDataT> wcs;
SkyPointScale getCenterRaDec() const;
};
Q_DECLARE_METATYPE(ImageInfoData);
typedef enum
{
None,
Statistics,
Peaks,
Stars,
}AnalyzeLevel;
#include "imageinfodata.h"
class ImageInfo : public QTreeWidget
{
+405
View File
@@ -0,0 +1,405 @@
#include "imageinfodata.h"
#include <QTime>
#include <QRectF>
#include <QRegularExpression>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "libxisf.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
bool FITSRecord::editable() const
{
return noEditableKey.count(key);
}
FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment) :
key(key), value(value), comment(comment)
{
}
FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
{
key = record.name.c_str();
comment = record.comment.c_str();
QString string = record.value.c_str();
if(string.startsWith('\'') && string.endsWith('\''))
{
string.chop(1);
string.remove(0, 1);
}
bool isint;
bool isdouble;
double vald = string.toDouble(&isdouble);
long long vall = string.toLongLong(&isint);
if(isint)
value = vall;
else if(isdouble)
value = vald;
else if(string == "T" || string == "F")
value = string == "T";
else
value = string;
}
FITSRecord::FITSRecord(const LibXISF::Property &property)
{
key = property.id.c_str();
value = QString::fromStdString(property.value.toString());
comment = property.comment.c_str();
xisf = true;
}
void WCSDataT::freeWCS()
{
wcsvfree(&nwcs, &wcs);
nwcs = 0;
wcs = nullptr;
}
WCSDataT::WCSDataT(int width, int height, char *header, int nrec) :
width(width),
height(height)
{
int nreject = 0;
int status = wcspih(header, nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
width(width),
height(height)
{
int status = 0;
QByteArray str;
int nrec = 1;
for(const FITSRecord &record : header)
{
if(record.key.startsWith("PV"))continue;
QByteArray rec;
rec.append(record.key.leftJustified(8, ' '));
rec.append("= ");
rec.append(record.value.toString().toLatin1());
rec.append(" / ");
rec.append(record.comment);
str.append(rec.leftJustified(80, ' ', true));
nrec++;
}
str.append(QByteArray("END").leftJustified(80));
int nreject = 0;
status = wcspih(str.data(), nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSDataT::~WCSDataT()
{
if(wcs)
freeWCS();
}
bool WCSDataT::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
{
if(!valid())return false;
double pixcrd[2] = {pixel.x(), pixel.y()};
double imgcrd[8] = {0};
double phi = 0;
double theta = 0;
double world[8] = {0};
int stat[NWCSFIX] = {0};
int status = wcsp2s(wcs, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat);
if(status == 0)
{
point = SkyPoint(world[0], world[1]);
return true;
}
return false;
}
bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
{
if(!valid())return false;
double world[2] = {point.RA(), point.DEC()};
double phi = 0;
double theta = 0;
double imgcrd[8] = {0};
double pixcrd[8] = {0};
int stat[NWCSFIX] = {0};
int status = wcss2p(wcs, 1, 2, world, &phi, &theta, imgcrd, pixcrd, stat);
if(status == 0)
{
pixel = QPointF(pixcrd[0], pixcrd[1]);
return true;
}
return false;
}
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
minRa = 1000;
maxRa = -1000;
minDec = 1000;
maxDec = -1000;
if(wcs->crval)
{
crVal1 = wcs->crval[0];
crVal2 = wcs->crval[1];
}
else
{
crVal1 = crVal2 = NAN;
}
auto update = [&](const QPointF &pixel)
{
SkyPoint point;
pixelToWorld(pixel, point);
minRa = std::min(minRa, point.RA());
maxRa = std::max(maxRa, point.RA());
minDec = std::min(minDec, point.DEC());
maxDec = std::max(maxDec, point.DEC());
};
for(int x=0; x<width; x++)
{
update(QPointF(x, 0));
update(QPointF(x, height - 1));
}
for(int y=0; y<height; y++)
{
update(QPointF(0, y));
update(QPointF(width - 1, y));
}
QPointF ncp;
QPointF scp;
QRectF s(0, 0, width - 1, height - 1);
if(worldToPixel(SkyPoint(0, 90), ncp))
{
if(s.contains(ncp))
maxDec = 90;
}
if(worldToPixel(SkyPoint(0, -90), scp))
{
if(s.contains(scp))
minDec = -90;
}
}
double hav(double x)
{
return (1.0 - std::cos(x)) * 0.5;
}
double haverSine(const SkyPoint &a, SkyPoint &b)
{
const double ToRAD = M_PI / 180.0;
double d = hav((a.DEC() - b.DEC()) * ToRAD) + std::cos(a.DEC() * ToRAD) * std::cos(b.DEC() * ToRAD) * hav((a.RA() - b.RA()) * ToRAD);
return std::acos(1.0 - 2.0 * d) * (180.0 / M_PI);
}
SkyPointScale WCSDataT::getRaDecScale() const
{
SkyPointScale ret;
pixelToWorld(QPointF(width/2.0, height/2.0), ret.point);
SkyPoint pointX;
SkyPoint pointY;
pixelToWorld(QPointF(width/2.0+1, height/2.0), pointX);
pixelToWorld(QPointF(width/2.0, height/2.0+1), pointY);
double scaleX = haverSine(ret.point, pointX) * 3600.0;
double scaleY = haverSine(ret.point, pointY) * 3600.0;
ret.scaleLow = std::min(scaleX, scaleY);
ret.scaleHigh = std::max(scaleX, scaleY);
ret.scaleValid = true;
return ret;
}
SkyPoint::SkyPoint() : ra(NAN), dec(NAN)
{
}
SkyPoint::SkyPoint(double ra, double dec) : ra(ra), dec(dec)
{
}
void SkyPoint::set(double ra, double dec)
{
this->ra = ra;
this->dec = dec;
}
QString SkyPoint::toString() const
{
if(std::isnan(ra) || std::isnan(dec))
return QString();
QTime t(0, 0);
t = t.addSecs(ra * 240);
double deg, min, sec;
min = std::abs(std::modf(dec, &deg) * 60);
sec = std::modf(min, &min) * 60;
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');
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
if(std::isnan(deg))return deg;
return deg * 15.0;
}
double SkyPoint::fromDMS(const QString &dms)
{
double deg = 0.0;
QString str = dms.trimmed();
str.remove(QRegularExpression("[hdms°'\"]"));
str.replace(':', ' ');
str.replace(QRegularExpression("\\s+"), " ");
QStringList fields = str.split(' ');
double sign = 1.0;
bool ok = false;
if(fields.size() >= 1)
deg = fields.at(0).toDouble(&ok);
if(!ok)return NAN;
if(deg < 0.0)
sign = -1.0;
if(fields.size() >= 2)
deg += sign * fields.at(1).toDouble() / 60.0;
if(fields.size() >= 3)
deg += sign * fields.at(2).toDouble() / 3600.0;
return deg;
}
QString SkyPoint::toHMS(double decHour)
{
double h,m,s,md;
md = std::modf(decHour, &h) * 60.0;
s = std::modf(md, &m) * 60.0;
return QString("%1h %2m %3s").arg((int)h, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
QString SkyPoint::toDMS(double deg)
{
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'));
}
SkyPointScale ImageInfoData::getCenterRaDec() const
{
SkyPointScale ret;
if(wcs && wcs->valid())
{
ret = wcs->getRaDecScale();
}
else
{
double ra,dec,focalLen,scale,pixSizeX,pixSizeY;
int binX = 1;
int binY = 1;
ra = dec = focalLen = scale = pixSizeX = pixSizeY = NAN;
bool ok;
for(const FITSRecord &header : fitsHeader)
{
if(header.key == "OBJCTRA")
{
double tmp = SkyPoint::fromHMS(header.value.toString());
if(!std::isnan(tmp))ra = tmp;
}
else if(header.key == "RA" && std::isnan(ra))
{
double tmp = header.value.toDouble(&ok);
if(ok)ra = tmp;
}
else if(header.key == "OBJCTDEC")
{
double tmp = SkyPoint::fromDMS(header.value.toString());
if(!std::isnan(tmp))dec = tmp;
}
else if(header.key == "DEC" && std::isnan(dec))
{
double tmp = SkyPoint::fromDMS(header.value.toString());
if(!std::isnan(tmp))dec = tmp;
}
else if(header.key == "SCALE")
{
double tmp = header.value.toDouble(&ok);
if(ok)scale = tmp;
}
else if(header.key == "FOCALLEN")
{
double tmp = header.value.toDouble(&ok);
if(ok)focalLen = tmp;
}
else if(header.key == "PIXSIZE1" || header.key == "XPIXSZ")
{
pixSizeX = header.value.toDouble();
}
else if(header.key == "PIXSIZE2" || header.key == "YPIXSZ")
{
pixSizeY = header.value.toDouble();
}
else if(header.key == "XBINNING")
{
int tmp = header.value.toInt(&ok);
if(ok)binX = tmp;
}
else if(header.key == "YBINNING")
{
int tmp = header.value.toInt(&ok);
if(ok)binY = tmp;
}
}
ret.point.set(ra, dec);
if(!std::isnan(scale))
{
ret.scaleLow = ret.scaleHigh = scale;
ret.scaleValid = true;
}
else if(!(std::isnan(focalLen) || std::isnan(pixSizeX) || std::isnan(pixSizeY)))
{
const double r = 206.2648097656; // (180 * 3600) / (1000 * pi) magic number to convert pixel size to focal length ratio to arcsec.
ret.scaleLow = std::min(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r);
ret.scaleHigh = std::max(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r);
ret.scaleValid = true;
}
}
if(ret.scaleValid)
{
ret.scaleLow *= 0.8;
ret.scaleHigh *= 1.2;
}
return ret;
}
+91
View File
@@ -0,0 +1,91 @@
#ifndef IMAGEINFODATA_H
#define IMAGEINFODATA_H
#include <QString>
#include <QPointF>
#include <QVector>
#include <QVariant>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace LibXISF { struct FITSKeyword; struct Property; }
struct FITSRecord
{
QByteArray key;
QVariant value;
QByteArray comment;
bool xisf = false;
bool editable() const;
FITSRecord(){}
FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment);
FITSRecord(const LibXISF::FITSKeyword &record);
FITSRecord(const LibXISF::Property &property);
};
class SkyPoint
{
double ra = NAN;
double dec = NAN;
public:
SkyPoint();
SkyPoint(double ra, double dec);
void set(double ra, double dec);
double RA() const { return ra; }
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
};
struct SkyPointScale
{
SkyPoint point;
//arcsec per pixel
bool scaleValid = false;
double scaleLow = 0.0;
double scaleHigh = 10000.0;
};
class WCSDataT
{
int nwcs = 0;
struct wcsprm *wcs = nullptr;
int width;
int height;
void freeWCS();
public:
WCSDataT(int width, int height, char *header, int nrec);
WCSDataT(int width, int height, const QVector<FITSRecord> &header);
WCSDataT(const WCSDataT &) = delete;
~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 valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
};
struct ImageInfoData
{
QVector<FITSRecord> fitsHeader;
QVector<QPair<QString, QString>> info;
std::shared_ptr<WCSDataT> wcs;
SkyPointScale getCenterRaDec() const;
};
typedef enum
{
None,
Statistics,
Peaks,
Stars,
}AnalyzeLevel;
Q_DECLARE_METATYPE(ImageInfoData);
#endif // IMAGEINFODATA_H
+22 -15
View File
@@ -4,6 +4,7 @@
#include <QDir>
#include <QSettings>
#include <QTimer>
#include <QRegularExpression>
#include "loadrunable.h"
#include "rawimage.h"
#include "database.h"
@@ -22,13 +23,13 @@ Image::Image(const QString name, int number, ImageRingList *ringList) :
{
}
void Image::load()
void Image::load(QThreadPool *pool)
{
if(!m_rawImage && !m_loading)
{
m_loading = true;
m_released = false;
QThreadPool::globalInstance()->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
pool->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
}
if(!m_loading && m_rawImage)
emit pixmapLoaded(this);
@@ -110,7 +111,10 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
{
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
m_loadPool = new QThreadPool(this);
m_loadPool->setThreadPriority(QThread::LowPriority);
m_thumbPool = new QThreadPool(this);
m_thumbPool->setThreadPriority(QThread::LowPriority);
m_slideShowTimer = new QTimer(this);
connect(m_slideShowTimer, &QTimer::timeout, this, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
@@ -123,10 +127,10 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
ImageRingList::~ImageRingList()
{
QThreadPool::globalInstance()->clear();
m_loadPool->clear();
m_thumbPool->clear();
QThreadPool::globalInstance()->waitForDone();
m_loadPool->waitForDone();
m_thumbPool->waitForDone();
}
@@ -173,11 +177,14 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile, bool
void ImageRingList::setFile(const QString &file)
{
if(!file.isEmpty())
{
QFileInfo info(file);
if(info.isDir())
setDir(file, QString(), true);
else
setDir(info.absolutePath(), file);
}
}
ImagePtr ImageRingList::currentImage()
@@ -199,9 +206,9 @@ void ImageRingList::increment()
(*m_firstImage)->release();
m_firstImage = increment(m_firstImage);
m_currImage = increment(m_currImage);
(*m_currImage)->load();
(*m_currImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
(*m_lastImage)->load(m_loadPool);
}
}
@@ -212,9 +219,9 @@ void ImageRingList::decrement()
(*m_lastImage)->release();
m_firstImage = decrement(m_firstImage);
m_currImage = decrement(m_currImage);
(*m_currImage)->load();
(*m_currImage)->load(m_loadPool);
m_lastImage = decrement(m_lastImage);
(*m_firstImage)->load();
(*m_firstImage)->load(m_loadPool);
}
}
@@ -268,7 +275,7 @@ void ImageRingList::loadFile(int row)
if(m_images.empty())
return;
(*m_currImage)->load();
(*m_currImage)->load(m_loadPool);
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
if(m_liveMode)
@@ -277,9 +284,9 @@ void ImageRingList::loadFile(int row)
for(int i=0; i<m_width; i++)
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load();
(*m_firstImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
(*m_lastImage)->load(m_loadPool);
}
if(m_lastImage != m_firstImage)
{
@@ -403,9 +410,9 @@ void ImageRingList::setPreload(int width)
for(int i = newWidth - m_width; i>0; i--)
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load();
(*m_firstImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
(*m_lastImage)->load(m_loadPool);
}
}
if(newWidth < m_width)
@@ -460,9 +467,9 @@ void ImageRingList::toggleSlideshow(bool start)
void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
{
QThreadPool::globalInstance()->clear();
m_loadPool->clear();
m_thumbPool->clear();
QThreadPool::globalInstance()->waitForDone();
m_loadPool->waitForDone();
m_thumbPool->waitForDone();
beginResetModel();
m_images.clear();
+4 -2
View File
@@ -7,8 +7,9 @@
#include <QPixmap>
#include <QDir>
#include <memory>
#include "imageinfo.h"
#include "imageinfodata.h"
#include "rawimage.h"
#include <QAbstractItemModel>
class ImageRingList;
class QThreadPool;
@@ -27,7 +28,7 @@ class Image : public QObject
ImageRingList *m_ringList;
public:
explicit Image(const QString name, int number, ImageRingList *ringList);
void load();
void load(QThreadPool *pool);
void loadThumbnail(QThreadPool *pool);
void release();
QString name() const;
@@ -62,6 +63,7 @@ class ImageRingList : public QAbstractItemModel
QDir::SortFlag m_sort = QDir::Name;
bool m_reversed = false;
AnalyzeLevel m_analyzeLevel;
QThreadPool *m_loadPool;
QThreadPool *m_thumbPool;
Database *m_database;
QStringList m_nameFilter;
+172 -19
View File
@@ -10,6 +10,7 @@
#include <QDragEnterEvent>
#include <QPainter>
#include "imageringlist.h"
#include <QFloat16>
int FILTERING = 1;
bool OpenGLES = false;
@@ -110,16 +111,15 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
m_error.clear();
makeCurrent();
m_rawImage = image;
if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
{
uint32_t newW = std::min(image->width() * m_maxTextureSize / image->width(), image->width() * m_maxTextureSize / image->height());
uint32_t newH = std::min(image->height() * m_maxTextureSize / image->width(), image->height() * m_maxTextureSize / image->height());
m_rawImage->resize(newW, newH);
}
m_imgWidth = image->width();
m_imgHeight = image->height();
bool tooBig = false;
if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
{
tooBig = true;
m_swPaint = true;
}
if(!m_image)return;
@@ -133,6 +133,9 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
m_lut->setData(0, 0, 0, LUT_SIZE, LUT_SIZE, LUT_SIZE, 0, QOpenGLTexture::RGBA, QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image->getLUT().data());
}
if(!tooBig)
{
while(f->glGetError() != GL_NO_ERROR);
QElapsedTimer timer;
timer.start();
m_image->destroy();
@@ -148,6 +151,8 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
f->glGenerateMipmap(GL_TEXTURE_2D);
qDebug() << "setImage" << timer.elapsed();
m_swPaint = f->glGetError() != GL_NO_ERROR;
}
m_unit_scale[0] = 1.0f;
m_unit_scale[1] = 0.0f;
@@ -185,10 +190,10 @@ void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
if(!mousePos.isNull())
focus = mousePos;
if(width() > m_image->width() * m_scale)
m_dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f;
if(height() > m_image->height() * m_scale)
m_dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
if(width() > m_imgWidth * m_scale)
m_dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
if(height() > m_imgHeight * m_scale)
m_dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
float newScale = std::sqrt(std::pow(2.0f, (float)m_scaleStop));
float r = newScale / m_scale;
@@ -232,10 +237,10 @@ QVector2D ImageWidgetGL::getImagePixelCoord(const QVector2D &pos)
{
float dx = m_dx;
float dy = m_dy;
if(m_width > m_image->width()*m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f;
if(m_height > m_image->height()*m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f;
if(m_width > m_imgWidth * m_scale)
dx = -width()*0.5f + m_imgWidth*m_scale * 0.5f;
if(m_height > m_imgHeight * m_scale)
dy = -height()*0.5f + m_imgHeight*m_scale * 0.5f;
QVector2D offset(dx, dy);
return (pos + offset) / m_scale;
@@ -341,17 +346,158 @@ void ImageWidgetGL::showThumbnail(bool enable)
setOffset(m_dx, m_dy);
}
void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scale, const MTFParam &mtfParams, QWidget *widget)
{
QPainter painter(widget);
int width = widget->width();
int height = widget->height();
QImage img(width, height, QImage::Format_RGB32);
img.fill(Qt::darkGray);
int64_t ox = dx;
int64_t oy = dy;
auto mtf = [&mtfParams](int i, float x)
{
x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
x = std::clamp(x, 0.0f, 1.0f);
return ((mtfParams.midPoint[i] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[i] - 1.0f) * x - mtfParams.midPoint[i]);
};
int imgWidth = rawImage->width();
int imgHeight = rawImage->height();
auto convert = [&](auto *src)
{
float s = 1.0f;
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)
s = (float)std::numeric_limits<std::remove_reference_t<decltype(*src)>>::max();
float iscale = 1.0f / scale;
float r[4];
float g[4];
float b[4];
for(int64_t y = std::max((int64_t)0, -oy); y < height; y++)
{
uint32_t *pixels = (uint32_t*)(img.scanLine(y));
float iptr;
float fy = std::modf((y + oy) * iscale, &iptr);
int64_t py = iptr;
int64_t w = py * rawImage->widthBytes();
int64_t w2 = w;
if(py+1 < imgHeight)w2 += rawImage->widthBytes();
if(py >= imgHeight)break;
for(int64_t x = std::max((int64_t)0, -ox); x < width; x++)
{
float fx = std::modf((x + ox) * iscale, &iptr);
int px = iptr;
int px2 = px + 1 < imgWidth ? px + 1 : px;
if(px >= imgWidth)break;
if(rawImage->channels() > 1)
{
r[0] = src[w + px * 4 + 0];
g[0] = src[w + px * 4 + 1];
b[0] = src[w + px * 4 + 2];
if(FILTERING)
{
r[1] = src[w + px2 * 4 + 0];
g[1] = src[w + px2 * 4 + 1];
b[1] = src[w + px2 * 4 + 2];
r[2] = src[w2 + px * 4 + 0];
g[2] = src[w2 + px * 4 + 1];
b[2] = src[w2 + px * 4 + 2];
r[3] = src[w2 + px2 * 4 + 0];
g[3] = src[w2 + px2 * 4 + 1];
b[3] = src[w2 + px2 * 4 + 2];
}
}
else
{
r[0] = src[w + px];
if(FILTERING)
{
r[2] = src[w2 + px];
r[1] = src[w + px2];
r[3] = src[w2 + px2];
}
}
uint32_t rgb = 0xff000000;
if(FILTERING)
{
if(rawImage->channels() > 1)
{
rgb |= (uint8_t)(mtf(0, ((r[3] * fx + r[2] * (1.0f - fx)) * fy + (r[1] * fx + r[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f) << 16;
rgb |= (uint8_t)(mtf(1, ((g[3] * fx + g[2] * (1.0f - fx)) * fy + (g[1] * fx + g[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f) << 8;
rgb |= (uint8_t)(mtf(1, ((b[3] * fx + b[2] * (1.0f - fx)) * fy + (b[1] * fx + b[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f);
}
else
{
uint32_t v = (uint8_t)(mtf(0, ((r[3] * fx + r[2] * (1.0f - fx)) * fy + (r[1] * fx + r[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f);
rgb = 0xff000000 | (v << 16) | (v << 8) | v;
}
}
else
{
if(rawImage->channels() > 1)
{
rgb |= (uint8_t)(mtf(0, r[0] / s) * 255.0f) << 16;
rgb |= (uint8_t)(mtf(1, g[0] / s) * 255.0f) << 8;
rgb |= (uint8_t)(mtf(1, b[0] / s) * 255.0f);
}
else
{
uint32_t v = (uint8_t)(mtf(0, r[0] / s) * 255.0f);
rgb = 0xff000000 | (v << 16) | (v << 8) | v;
}
}
pixels[x] = rgb;
}
}
};
if(rawImage)
{
switch(rawImage->type())
{
case RawImage::UINT8:
convert(static_cast<uint8_t*>(rawImage->data()));
break;
case RawImage::UINT16:
convert(static_cast<uint16_t*>(rawImage->data()));
break;
case RawImage::UINT32:
convert(static_cast<uint32_t*>(rawImage->data()));
break;
case RawImage::FLOAT16:
convert(static_cast<qfloat16*>(rawImage->data()));
break;
case RawImage::FLOAT32:
convert(static_cast<float*>(rawImage->data()));
break;
case RawImage::FLOAT64:
convert(static_cast<double*>(rawImage->data()));
break;
}
}
painter.drawImage(0, 0, img);
}
void ImageWidgetGL::paintGL()
{
float dx = m_dx;
float dy = m_dy;
if(m_width > m_image->width() * m_scale)
dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f;
if(m_height > m_image->height() * m_scale)
dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
if(m_width > m_imgWidth * m_scale)
dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
if(m_height > m_imgHeight * m_scale)
dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
QBrush highlight = style()->standardPalette().highlight();
f->glClear(GL_COLOR_BUFFER_BIT);
f->glBlendFunc(GL_ONE, GL_ZERO);
if(m_showThumbnails)
{
m_vaoThumb->bind();
@@ -424,6 +570,12 @@ void ImageWidgetGL::paintGL()
painter.drawText(0, 0, width(), height(), Qt::AlignCenter | Qt::AlignHCenter, m_error);
}
else
{
if(m_swPaint)
{
swPaint(m_rawImage, dx, dy, m_scale, m_mtfParams, this);
}
else
{
m_vao->bind();
debayer();
@@ -451,6 +603,7 @@ void ImageWidgetGL::paintGL()
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
}
}
}
+1
View File
@@ -91,6 +91,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
Database *m_database = nullptr;
QPointF m_lastPos;
QString m_error;
bool m_swPaint = false;
public:
explicit ImageWidgetGL(Database *database, QWidget *parent = nullptr);
~ImageWidgetGL() override;
+409
View File
@@ -0,0 +1,409 @@
#include "loadimage.h"
#include <QElapsedTimer>
#include <QDebug>
#include <QFileInfo>
#include <QDir>
#include <libraw/libraw.h>
#include <fitsio2.h>
#include "libxisf.h"
#include <libexif/exif-data.h>
#include "rawimage.h"
QString makeMaxPath(QString path)
{
#ifdef Q_OS_WIN64
if(!path.startsWith("\\\\?\\"))
{
QFileInfo info(path);
path = info.absoluteFilePath();
path = QDir::toNativeSeparators(path);
path.prepend("\\\\?\\");
qDebug() << path;
}
#endif
return path;
}
int loadFITSHeader(fitsfile *file, ImageInfoData &info)
{
int imgtype;
int naxis;
long naxes[3] = {0};
int nexist;
int status = 0;
char key[FLEN_KEYWORD];
char val[FLEN_VALUE];
char comm[FLEN_COMMENT];
char strval[FLEN_VALUE];
QVariant var;
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
fits_get_hdrspace(file, &nexist, nullptr, &status);
for(int i=1; i<=nexist; i++)
{
fits_read_keyn(file, i, key, val, comm, &status);
fits_read_key(file, TSTRING, key, strval, nullptr, &status);
if(status == 0 || status == VALUE_UNDEFINED)
{
QString string(strval);
bool isint;
bool isdouble;
double vald = string.toDouble(&isdouble);
long long vall = string.toLongLong(&isint);
if(isint)
var = vall;
else if(isdouble)
var = vald;
else if(status == VALUE_UNDEFINED)
var = QVariant();
else if(string == "T" || string == "F")
var = string == "T";
else
var = string;
status = 0;
info.fitsHeader.append(FITSRecord(key, var, comm));
}
else
{
return status;
}
}
char *header = nullptr;
int nrec = 0;
const char *exclist[] = {"PV1_1", "PV1_2"};
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
if(status == 0)
{
info.wcs = std::make_shared<WCSDataT>(naxes[0], naxes[1], header, nrec);
if(!info.wcs->valid())info.wcs.reset();
}
fits_free_memory(header, &status);
return status;
}
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{
fitsfile *file;
int status = 0;
int type = -1;
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
int num = 0;
fits_get_num_hdus(file, &num, &status);
int imgtype;
int naxis;
long naxes[3] = {0};
for(int i=1; i <= num; i++)
{
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
fits_get_hdu_type(file, &type, &status);
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
fits_get_img_equivtype(file, &imgtype, &status);
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
{
RawImage::DataType type;
int fitstype;
long fpixel[3] = {1,1,1};
switch(imgtype)
{
case BYTE_IMG:
type = RawImage::UINT8;
fitstype = TBYTE;
break;
case SHORT_IMG:
type = RawImage::UINT16;
fitstype = TSHORT;
break;
case USHORT_IMG:
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG:
type = RawImage::FLOAT32;
fitstype = TFLOAT;
break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default:
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
goto noload;
break;
}
size_t size = naxes[0]*naxes[1];
size_t w = naxes[0];
size_t h = naxes[1];
info.info.append({QObject::tr("Width"), QString::number(naxes[0])});
info.info.append({QObject::tr("Height"), QString::number(naxes[1])});
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
uint8_t *data = static_cast<uint8_t*>(img.data());
for (int i=1; i==1 || i<=naxes[2]; i++)
{
fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
}
if(fitstype == TSHORT)
{
uint16_t *s = static_cast<uint16_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
if(img.channels() == 1 || planar)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
break;
}
}
noload:
if(file)
loadFITSHeader(file, info);
if(image)
{
for(auto fits : info.fitsHeader)
{
if(fits.key == "ROWORDER" && fits.value == "BOTTOM-UP")
image->flip();
}
}
fits_close_file(file, &status);
if(status)
{
char err[100];
fits_get_errstatus(status, err);
info.info.append({QObject::tr("Error"), QString(err)});
qDebug() << "Failed to load FITS file" << err;
}
return true;
}
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(path.toLocal8Bit().data());
const LibXISF::Image &xisfImage = xisf.getImage(0);
auto fitskeywords = xisfImage.fitsKeywords();
for(auto fits : fitskeywords)
{
info.fitsHeader.append(fits);
}
auto imageproperties = xisfImage.imageProperties();
for(auto prop : imageproperties)
{
info.fitsHeader.append(prop);
}
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("Height"), QString::number(xisfImage.height())});
if(!info.wcs->valid())info.wcs.reset();
RawImage::DataType type;
switch(xisfImage.sampleFormat())
{
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
default: break;
}
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
image->setICCProfile(tmpImage.iccProfile());
return true;
}
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
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());
return true;
}
return false;
}
catch (LibXISF::Error &err)
{
info.info.append(QPair<QString, QString>("Error", err.what()));
qDebug() << "Failed to load XISF" << err.what();
return false;
}
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
return false;
}
bool readFITSHeader(const QString &path, ImageInfoData &info)
{
fitsfile *fr;
int status = 0;
QString path2 = makeMaxPath(path);
fits_open_diskfile(&fr, path2.toLocal8Bit().data(), READONLY, &status);
if(fr && status == 0)
{
status = loadFITSHeader(fr, info);
fits_close_file(fr, &status);
}
return status == 0;
}
bool readXISFHeader(const QString &path, ImageInfoData &info)
{
QString path2 = makeMaxPath(path);
try
{
LibXISF::XISFReader xisf;
xisf.open(path2.toLocal8Bit().data());
const LibXISF::Image &image = xisf.getImage(0, false);
auto fitskeywords = image.fitsKeywords();
for(auto fits : fitskeywords)
{
info.fitsHeader.append(fits);
}
auto imageproperties = image.imageProperties();
for(auto prop : imageproperties)
{
info.fitsHeader.append(prop);
}
info.wcs = std::make_shared<WCSDataT>(image.width(), image.height(), info.fitsHeader);
if(!info.wcs->valid())info.wcs.reset();
}
catch (LibXISF::Error &err)
{
qDebug() << err.what();
return false;
}
return true;
}
void loadExifEntry(ImageInfoData &info, ExifContent *content, ExifTag tag)
{
char val[1024];
ExifEntry *entry = exif_content_get_entry(content, tag);
if(entry)
{
exif_entry_get_value(entry, val, sizeof(val));
info.info.append({exif_tag_get_title(tag), val});
}
}
bool loadRAW(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
{
std::unique_ptr<LibRaw> raw = std::make_unique<LibRaw>();
raw->open_file(path.toLocal8Bit().data());
raw->imgdata.params.half_size = true;
raw->imgdata.params.use_camera_wb = true;
raw->imgdata.params.user_flip = 0;
if(raw->unpack())
return false;
libraw_rawdata_t rawdata = raw->imgdata.rawdata;
size_t size = rawdata.sizes.width*rawdata.sizes.height;
std::vector<uint16_t> out;
out.resize(size);
size_t d = 0;
uint h=rawdata.sizes.top_margin+rawdata.sizes.height;
uint w=rawdata.sizes.left_margin+rawdata.sizes.width;
size_t pitch = rawdata.sizes.raw_pitch/sizeof(uint16_t);
for(size_t i=rawdata.sizes.top_margin;i<h;i++)
{
for(size_t o=rawdata.sizes.left_margin;o<w;o++)
{
uint16_t p = rawdata.raw_image[i*pitch+o];
out[d++] = p;
}
}
image = std::make_shared<RawImage>(rawdata.sizes.width, rawdata.sizes.height, 1, RawImage::UINT16);
memcpy(image->data(), &out[0], sizeof(uint16_t)*d);
QString shutterSpeed = QString::number(raw->imgdata.other.shutter);
if(raw->imgdata.other.shutter < 1)
{
shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter);
}
info.info.append({QObject::tr("Width"), QString::number(raw->imgdata.sizes.width)});
info.info.append({QObject::tr("Height"), QString::number(raw->imgdata.sizes.height)});
info.info.append({QObject::tr("ISO"), QString::number(raw->imgdata.other.iso_speed)});
info.info.append({QObject::tr("Shutter speed"), shutterSpeed});
#if LIBRAW_MINOR_VERSION>=19
// info.append(StringPair(QObject::tr("Camera temperature"), QString::number(raw.imgdata.other.CameraTemperature)));
#endif
return true;
}
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
{
bool ret = false;
QElapsedTimer timer;
timer.start();
if(path.endsWith(".CR2", Qt::CaseInsensitive) || path.endsWith(".CR3", Qt::CaseInsensitive) || path.endsWith(".NEF", Qt::CaseInsensitive) || path.endsWith(".DNG", Qt::CaseInsensitive))
{
ret = loadRAW(path, info, rawImage);
qDebug() << "LoadRAW" << timer.elapsed();
}
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive) || path.endsWith(".FZ", Qt::CaseInsensitive) || path.endsWith(".FTS", Qt::CaseInsensitive))
{
ret = loadFITS(path, info, rawImage, planar);
qDebug() << "LoadFITS" << timer.elapsed();
}
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
{
ret = loadXISF(path, info, rawImage, planar);
qDebug() << "LoadXISF" << timer.elapsed();
}
else
{
QImage img(path);
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
info.info.append({QObject::tr("Width"), QString::number(img.width())});
info.info.append({QObject::tr("Height"), QString::number(img.height())});
if(exif)
{
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS);
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
exif_data_free(exif);
}
rawImage = std::make_shared<RawImage>(img);
qDebug() << "LoadQImage" << timer.elapsed();
ret = !img.isNull();
}
return ret;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef LOADIMAGE_H
#define LOADIMAGE_H
#include <QString>
#include "imageinfodata.h"
class RawImage;
QString makeMaxPath(QString path);
bool readFITSHeader(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 planar = false);
#endif // LOADIMAGE_H
+44 -436
View File
@@ -1,371 +1,23 @@
#include "loadrunable.h"
#include "imageringlist.h"
#include <libraw/libraw.h>
#include "imageinfo.h"
#include <QFileInfo>
#include <QPainter>
#include <QElapsedTimer>
#include <QDebug>
#include <iostream>
#include <algorithm>
#include <libexif/exif-data.h>
#include <fitsio2.h>
#include <libxisf.h>
#include "rawimage.h"
#include "starfit.h"
#include "loadimage.h"
#include <lcms2.h>
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
m_file(file),
m_file(makeMaxPath(file)),
m_receiver(receiver),
m_analyzeLevel(level),
m_thumbnail(thumbnail)
{
}
void loadExifEntry(ImageInfoData &info, ExifContent *content, ExifTag tag)
{
char val[1024];
ExifEntry *entry = exif_content_get_entry(content, tag);
if(entry)
{
exif_entry_get_value(entry, val, sizeof(val));
info.info.append({exif_tag_get_title(tag), val});
}
}
void drawPeaks(QImage &img, const std::vector<Peak> &peaks)
{
QPixmap pix = QPixmap::fromImage(img);
QPainter painter(&pix);
painter.setPen(Qt::red);
for(auto peak : peaks)
{
painter.drawEllipse(QPoint(peak.x(), peak.y()), 5, 5);
}
img = pix.toImage();
}
void drawStars(QImage &img, const std::vector<Star> &stars)
{
QPixmap pix = QPixmap::fromImage(img);
QPainter painter(&pix);
painter.setPen(Qt::red);
for(auto star : stars)
{
painter.drawEllipse(QPointF(star.m_x, star.m_y), star.hw20X(), star.hw20Y());
}
img = pix.toImage();
}
void printStarModel(int radius, const std::vector<double> &data, const Star &star)
{
QString d = "d=[";
QString m = "m=[";
for(int y=0; y<radius; y++)
{
for(int x=0; x<radius; x++)
{
d += QString::number(data[y*radius+x]) + ",";
m += QString::number(gauss_model(star.m_am, star.m_x, star.m_y, star.m_sx, star.m_sy, x, y)) + ",";
}
d += ";";
m += ";";
}
d += "];";
m += "];";
//std::cout << star.m_am << " " << star.m_sx << star.m_sy << std::endl;
std::cout << d.toStdString() << std::endl;
std::cout << m.toStdString() << std::endl << std::endl;
}
bool loadRAW(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
{
std::unique_ptr<LibRaw> raw = std::make_unique<LibRaw>();
raw->open_file(path.toLocal8Bit().data());
raw->imgdata.params.half_size = true;
raw->imgdata.params.use_camera_wb = true;
raw->imgdata.params.user_flip = 0;
if(raw->unpack())
return false;
libraw_rawdata_t rawdata = raw->imgdata.rawdata;
size_t size = rawdata.sizes.width*rawdata.sizes.height;
std::vector<uint16_t> out;
out.resize(size);
size_t d = 0;
uint h=rawdata.sizes.top_margin+rawdata.sizes.height;
uint w=rawdata.sizes.left_margin+rawdata.sizes.width;
size_t pitch = rawdata.sizes.raw_pitch/sizeof(uint16_t);
for(size_t i=rawdata.sizes.top_margin;i<h;i++)
{
for(size_t o=rawdata.sizes.left_margin;o<w;o++)
{
uint16_t p = rawdata.raw_image[i*pitch+o];
out[d++] = p;
}
}
image = std::make_shared<RawImage>(rawdata.sizes.width, rawdata.sizes.height, 1, RawImage::UINT16);
memcpy(image->data(), &out[0], sizeof(uint16_t)*d);
QString shutterSpeed = QString::number(raw->imgdata.other.shutter);
if(raw->imgdata.other.shutter < 1)
{
shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter);
}
info.info.append({QObject::tr("Width"), QString::number(raw->imgdata.sizes.width)});
info.info.append({QObject::tr("Height"), QString::number(raw->imgdata.sizes.height)});
info.info.append({QObject::tr("ISO"), QString::number(raw->imgdata.other.iso_speed)});
info.info.append({QObject::tr("Shutter speed"), shutterSpeed});
#if LIBRAW_MINOR_VERSION>=19
// info.append(StringPair(QObject::tr("Camera temperature"), QString::number(raw.imgdata.other.CameraTemperature)));
#endif
return true;
}
int loadFITSHeader(fitsfile *file, ImageInfoData &info)
{
int imgtype;
int naxis;
long naxes[3] = {0};
int nexist;
int status = 0;
char key[FLEN_KEYWORD];
char val[FLEN_VALUE];
char comm[FLEN_COMMENT];
char strval[FLEN_VALUE];
QVariant var;
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
fits_get_hdrspace(file, &nexist, nullptr, &status);
for(int i=1; i<=nexist; i++)
{
fits_read_keyn(file, i, key, val, comm, &status);
fits_read_key(file, TSTRING, key, strval, nullptr, &status);
if(status == 0 || status == VALUE_UNDEFINED)
{
QString string(strval);
bool isint;
bool isdouble;
double vald = string.toDouble(&isdouble);
long long vall = string.toLongLong(&isint);
if(isint)
var = vall;
else if(isdouble)
var = vald;
else if(status == VALUE_UNDEFINED)
var = QVariant();
else if(string == "T" || string == "F")
var = string == "T";
else
var = string;
status = 0;
info.fitsHeader.append(FITSRecord(key, var, comm));
}
else
{
return status;
}
}
char *header = nullptr;
int nrec = 0;
const char *exclist[] = {"PV1_1", "PV1_2"};
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
if(status == 0)
{
info.wcs = std::make_shared<WCSDataT>(naxes[0], naxes[1], header, nrec);
if(!info.wcs->valid())info.wcs.reset();
}
fits_free_memory(header, &status);
return status;
}
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{
fitsfile *file;
int status = 0;
int type = -1;
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
int num = 0;
fits_get_num_hdus(file, &num, &status);
int imgtype;
int naxis;
long naxes[3] = {0};
for(int i=1; i <= num; i++)
{
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
fits_get_hdu_type(file, &type, &status);
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
fits_get_img_equivtype(file, &imgtype, &status);
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
{
RawImage::DataType type;
int fitstype;
long fpixel[3] = {1,1,1};
switch(imgtype)
{
case BYTE_IMG:
type = RawImage::UINT8;
fitstype = TBYTE;
break;
case SHORT_IMG:
type = RawImage::UINT16;
fitstype = TSHORT;
break;
case USHORT_IMG:
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG:
type = RawImage::FLOAT32;
fitstype = TFLOAT;
break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default:
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
goto noload;
break;
}
size_t size = naxes[0]*naxes[1];
size_t w = naxes[0];
size_t h = naxes[1];
info.info.append({QObject::tr("Width"), QString::number(naxes[0])});
info.info.append({QObject::tr("Height"), QString::number(naxes[1])});
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
uint8_t *data = static_cast<uint8_t*>(img.data());
for (int i=1; i==1 || i<=naxes[2]; i++)
{
fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
}
if(fitstype == TSHORT)
{
uint16_t *s = static_cast<uint16_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
if(img.channels() == 1 || planar)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
break;
}
}
noload:
if(file)
loadFITSHeader(file, info);
if(image)
{
for(auto fits : info.fitsHeader)
{
if(fits.key == "ROWORDER" && fits.value == "BOTTOM-UP")
image->flip();
}
}
fits_close_file(file, &status);
if(status)
{
char err[100];
fits_get_errstatus(status, err);
info.info.append({QObject::tr("Error"), QString(err)});
qDebug() << "Failed to load FITS file" << err;
}
return true;
}
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(path.toLocal8Bit().data());
const LibXISF::Image &xisfImage = xisf.getImage(0);
auto fitskeywords = xisfImage.fitsKeywords();
for(auto fits : fitskeywords)
{
info.fitsHeader.append(fits);
}
auto imageproperties = xisfImage.imageProperties();
for(auto prop : imageproperties)
{
info.fitsHeader.append(prop);
}
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("Height"), QString::number(xisfImage.height())});
if(!info.wcs->valid())info.wcs.reset();
RawImage::DataType type;
switch(xisfImage.sampleFormat())
{
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
default: break;
}
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
image->setICCProfile(tmpImage.iccProfile());
return true;
}
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
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());
return true;
}
return false;
}
catch (LibXISF::Error &err)
{
info.info.append(QPair<QString, QString>("Error", err.what()));
qDebug() << "Failed to load XISF" << err.what();
return false;
}
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
return false;
}
void LoadRunable::run()
{
try
@@ -447,93 +99,9 @@ void LoadRunable::run()
}
}
bool readFITSHeader(const QString &path, ImageInfoData &info)
{
fitsfile *fr;
int status = 0;
fits_open_diskfile(&fr, path.toLocal8Bit().data(), READONLY, &status);
if(fr && status == 0)
{
status = loadFITSHeader(fr, info);
fits_close_file(fr, &status);
}
return status == 0;
}
bool readXISFHeader(const QString &path, ImageInfoData &info)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(path.toLocal8Bit().data());
const LibXISF::Image &image = xisf.getImage(0, false);
auto fitskeywords = image.fitsKeywords();
for(auto fits : fitskeywords)
{
info.fitsHeader.append(fits);
}
auto imageproperties = image.imageProperties();
for(auto prop : imageproperties)
{
info.fitsHeader.append(prop);
}
info.wcs = std::make_shared<WCSDataT>(image.width(), image.height(), info.fitsHeader);
if(!info.wcs->valid())info.wcs.reset();
}
catch (LibXISF::Error &err)
{
qDebug() << err.what();
return false;
}
return true;
}
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
{
bool ret = false;
QElapsedTimer timer;
timer.start();
if(path.endsWith(".CR2", Qt::CaseInsensitive) || path.endsWith(".CR3", Qt::CaseInsensitive) || path.endsWith(".NEF", Qt::CaseInsensitive) || path.endsWith(".DNG", Qt::CaseInsensitive))
{
ret = loadRAW(path, info, rawImage);
qDebug() << "LoadRAW" << timer.elapsed();
}
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive) || path.endsWith(".FZ", Qt::CaseInsensitive) || path.endsWith(".FTS", Qt::CaseInsensitive))
{
ret = loadFITS(path, info, rawImage, planar);
qDebug() << "LoadFITS" << timer.elapsed();
}
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
{
ret = loadXISF(path, info, rawImage, planar);
qDebug() << "LoadXISF" << timer.elapsed();
}
else
{
QImage img(path);
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
info.info.append({QObject::tr("Width"), QString::number(img.width())});
info.info.append({QObject::tr("Height"), QString::number(img.height())});
if(exif)
{
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS);
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
exif_data_free(exif);
}
rawImage = std::make_shared<RawImage>(img);
qDebug() << "LoadQImage" << timer.elapsed();
ret = !img.isNull();
}
return ret;
}
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams &params, QSemaphore *semaphore) :
m_infile(in),
m_outfile(out),
m_infile(makeMaxPath(in)),
m_outfile(makeMaxPath(out)),
m_format(format),
m_params(params),
m_semaphore(semaphore)
@@ -623,6 +191,17 @@ void ConvertRunable::run()
QFileInfo info(m_outfile);
info.dir().mkpath(".");
if(m_params.binning > 1)
{
rawimage->resizeInt(m_params.binning, m_params.average);
}
else if(m_params.resize.isValid() && !m_params.resize.isEmpty())
{
QSize imgSize(rawimage->width(), rawimage->height());
imgSize = imgSize.scaled(m_params.resize, m_params.aspect);
rawimage->resize(imgSize.width(), imgSize.height());
}
if(rawimage)
{
if(m_format == "xisf")
@@ -755,4 +334,33 @@ ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map)
if(map.contains("compressionType"))
compressionType = map["compressionType"].toString();
if(map.contains("binning"))
binning = map["binning"].toInt();
if(map.contains("average"))
average = map["average"].toBool();
if(map.contains("resize"))
{
QVariantMap size = map["resize"].toMap();
if(size.contains("width") && size.contains("height"))
{
int w = size["width"].toInt();
int h = size["height"].toInt();
resize = QSize(w, h);
}
}
if(map.contains("aspect"))
{
QString aspectStr = map["aspect"].toString();
if(aspectStr == "keep")
aspect = Qt::KeepAspectRatio;
else if(aspectStr == "expand")
aspect = Qt::KeepAspectRatioByExpanding;
else if(aspectStr == "ignore")
aspect = Qt::IgnoreAspectRatio;
}
}
+6 -8
View File
@@ -4,13 +4,8 @@
#include <QRunnable>
#include <QString>
#include <QSemaphore>
#include "imageinfo.h"
class RawImage;
bool readFITSHeader(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 planar = false);
#include <QSize>
#include "imageinfodata.h"
class Image;
@@ -25,7 +20,6 @@ public:
void run() override;
};
class ConvertRunable : public QRunnable
{
public:
@@ -33,6 +27,10 @@ public:
{
int compressionLevel = -1;
QString compressionType;
int binning = 0;
bool average = true;
QSize resize;
Qt::AspectRatioMode aspect = Qt::KeepAspectRatio;
ConvertParams(){}
ConvertParams(const QVariantMap &map);
};
+1 -1
View File
@@ -186,7 +186,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut);
viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit);
viewMenu->addAction(tr("100%"), m_image, &ImageScrollArea::oneToOne);
viewMenu->addAction(tr("100%"), QKeySequence("Ctrl+0"), m_image, &ImageScrollArea::oneToOne);
viewMenu->addSeparator();
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
QActionGroup *bayerActionGroup = new QActionGroup(this);
+1 -1
View File
@@ -60,6 +60,6 @@ void MarkedFiles::clearSelected()
void MarkedFiles::clearAll()
{
QSqlDatabase db = QSqlDatabase::database();
db.exec("DELETE FROM files");
QSqlQuery("DELETE FROM files", db);
m_model->select();
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef MTFPARAM_H
#define MTFPARAM_H
struct MTFParam
{
float blackPoint[3] = {0.0f, 0.0f, 0.0f};
float midPoint[3] = {0.5f, 0.5f, 0.5f};
float whitePoint[3] = {1.0f, 1.0f, 1.0f};
};
#endif // MTFPARAM_H
+121 -35
View File
@@ -1,12 +1,16 @@
#include "rawimage.h"
#include <QDebug>
#include <cstring>
#include <lcms2.h>
#ifndef NO_QT
#include <QDebug>
#include <QElapsedTimer>
#include <QFloat16>
#include <QColorSpace>
#include <lcms2.h>
using F16 = qfloat16;
#else
#include <algorithm>
using F16 = _Float16;
#endif
int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138;
@@ -45,7 +49,7 @@ void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
m_channels = ch;
m_ch = ch == 3 ? 4 : ch;
m_origType = m_type = type;
m_pixels = std::make_unique<PixelType[]>(m_width * m_height * m_ch * typeSize(type));
m_pixels = std::make_unique<PixelType[]>((size_t)m_width * m_height * m_ch * typeSize(type));
}
RawImage::RawImage()
@@ -60,7 +64,7 @@ RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type)
RawImage::RawImage(const RawImage &d)
{
allocate(d.m_width, d.m_height, d.m_channels, d.m_type);
std::memcpy(m_pixels.get(), d.m_pixels.get(), m_width * m_height * m_ch * typeSize(m_type));
std::memcpy(m_pixels.get(), d.m_pixels.get(), (size_t)m_width * m_height * m_ch * typeSize(m_type));
m_stats = d.m_stats;
}
@@ -78,6 +82,7 @@ RawImage::RawImage(RawImage &&d)
m_thumbAspect = d.m_thumbAspect;
}
#ifndef NO_QT
RawImage::RawImage(const QImage &img)
{
qDebug() << img;
@@ -118,6 +123,24 @@ RawImage::RawImage(const QImage &img)
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*2);
}
else if(img.format() == QImage::Format_RGB32 || img.format() == QImage::Format_ARGB32)
{
allocate(img.width(), img.height(), 4, UINT8);
for(int i=0; i<img.height(); i++)
{
uint32_t *src = (uint32_t*)img.scanLine(i);
uint32_t *dst = (uint32_t*)data(i);
for(int o=0; o<img.width(); o++)
{
uint32_t p = src[o];
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
dst[o] = (p & 0xff000000) | (p >> 16 & 0xff) | (p & 0xff00) | (p << 16 & 0xff0000);
#else
dst[o] = (p >> 24) | (p << 8 & 0xffffff00);
#endif
}
}
}
else
{
QImage tmp = img.convertToFormat(QImage::Format_RGBA8888);
@@ -127,6 +150,7 @@ RawImage::RawImage(const QImage &img)
}
m_stats.m_stats = false;
}
#endif
const RawImage::Stats& RawImage::imageStats() const
{
@@ -294,9 +318,9 @@ uint32_t RawImage::channels() const
return m_channels;
}
uint32_t RawImage::size() const
uint64_t RawImage::size() const
{
return width()*height();
return (uint64_t)width()*height();
}
RawImage::DataType RawImage::type() const
@@ -336,12 +360,12 @@ const void *RawImage::data() const
void *RawImage::data(uint32_t row, uint32_t col)
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}
const void *RawImage::data(uint32_t row, uint32_t col) const
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}
const void *RawImage::origData() const
@@ -356,12 +380,12 @@ const void *RawImage::origData(uint32_t row, uint32_t col) const
{
if(m_original)
{
col = col * m_origWidth / m_width;
row = row * m_origHeight / m_height;
return m_original.get() + (m_origWidth * row * m_ch + col * m_ch) * typeSize(m_origType);
col = (uint64_t)col * m_origWidth / m_width;
row = (uint64_t)row * m_origHeight / m_height;
return m_original.get() + ((size_t)m_origWidth * row * m_ch + (size_t)col * m_ch) * typeSize(m_origType);
}
else
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}
bool RawImage::planar() const
@@ -381,12 +405,12 @@ void RawImage::convertToThumbnail()
auto loop = [&](F16 *out, auto *in, float scale)
{
for(int i=0; i<THUMB_SIZE; i++)
for(int64_t i=0; i<THUMB_SIZE; i++)
{
for(int o=0; o<THUMB_SIZE; o++)
for(int64_t o=0; o<THUMB_SIZE; o++)
{
int idx = (i*THUMB_SIZE + o)*4;
int idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
int64_t idx = (i*THUMB_SIZE + o)*4;
int64_t idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
if(m_channels == 1)
{
@@ -421,7 +445,9 @@ void RawImage::convertToThumbnail()
loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
break;
default:
#ifndef NO_QT
qWarning() << "FLOAT64 should not happend";
#endif
return;
}
@@ -442,21 +468,21 @@ void RawImage::convertToGLFormat()
}
template<typename T, typename U>
void convertType2(uint32_t size, const T *src, U *dst)
void convertType2(size_t size, const T *src, U *dst)
{
if constexpr((std::is_floating_point_v<T> || std::is_same_v<T, F16>) && (std::is_floating_point_v<U> || std::is_same_v<T, F16>))
{
for(uint32_t i = 0; i < size; i++)
for(size_t i = 0; i < size; i++)
dst[i] = src[i];
}
if constexpr(std::is_integral_v<T> && std::is_integral_v<U>)
{
if constexpr(sizeof(T) > sizeof(U))
for(uint32_t i = 0; i < size; i++)
for(size_t i = 0; i < size; i++)
dst[i] = src[i] >> ((sizeof(T) - sizeof(U)) * 8);
else
for(uint32_t i = 0; i < size; i++)
for(size_t i = 0; i < size; i++)
dst[i] = static_cast<U>(src[i]) << ((sizeof(U) - sizeof(T)) * 8);
}
@@ -464,20 +490,20 @@ void convertType2(uint32_t size, const T *src, U *dst)
{
U max = std::numeric_limits<U>::max();
T scale = (T)(max);
for(uint32_t i = 0; i < size; i++)
for(size_t i = 0; i < size; i++)
dst[i] = src[i] * scale;
}
if constexpr(std::is_integral_v<T> && (std::is_floating_point_v<U> || std::is_same_v<U, F16>))
{
U scale = (U)(1.0 / (double)std::numeric_limits<T>::max());
for(uint32_t i = 0; i < size; i++)
for(size_t i = 0; i < size; i++)
dst[i] = (U)src[i] * scale;
}
}
template<typename T>
void convertType(uint32_t size, RawImage::DataType dstType, const T *src, void *dst)
void convertType(size_t size, RawImage::DataType dstType, const T *src, void *dst)
{
switch(dstType)
{
@@ -514,7 +540,7 @@ void RawImage::convertToType(DataType type)
allocate(m_width, m_height, m_channels, type);
m_origType = origType;
uint32_t s = size() * m_ch;
size_t s = size() * m_ch;
switch(m_origType)
{
case UINT8:
@@ -656,15 +682,15 @@ void boxResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t ol
float sx = (float)w / oldw;
float sy = (float)h / oldh;
for(uint32_t y = 0; y < h; y++)//iterate over destination Y
for(uint64_t y = 0; y < h; y++)//iterate over destination Y
{
for(uint32_t x = 0; x < w; x++)//iterate over destination X
for(uint64_t x = 0; x < w; x++)//iterate over destination X
{
U p[4] = {0.0f};
uint32_t xx = x * oldw / w;//calculate source rect
uint32_t yy = y * oldh / h;
uint32_t xe = std::min((x + 1) * oldw / w, oldw - 1);
uint32_t ye = std::min((y + 1) * oldh / h, oldh - 1);
uint64_t xx = x * oldw / w;//calculate source rect
uint64_t yy = y * oldh / h;
uint64_t xe = std::min((x + 1) * oldw / w, (uint64_t)oldw - 1);
uint64_t ye = std::min((y + 1) * oldh / h, (uint64_t)oldh - 1);
for(uint32_t o = yy; o <= ye; o++)//iterate over source Y
{
float cy = o * sy - y;
@@ -728,6 +754,64 @@ void RawImage::resize(uint32_t w, uint32_t h)
}
}
template<typename T, typename U>
void integerResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t down, bool avg, const uint8_t *in_, uint8_t *out_)
{
const T *in = reinterpret_cast<const T*>(in_);
T *out = reinterpret_cast<T*>(out_);
uint32_t down2 = down * down;
U m = std::numeric_limits<T>::max();
if constexpr(std::is_floating_point_v<T>)m = down2;
for(uint64_t i = 0; i < h; i++)
{
for(uint64_t o = 0; o < w; o++)
{
for(uint64_t p = 0; p < ch; p++)
{
U pix = 0;
for(uint32_t y = 0; y < down; y++)
for(uint32_t x = 0; x < down; x++)
pix += in[((i * down) + y) * oldw * ch + ((o * down) + x) * ch + p];
if (avg)
out[(i * w + o) * ch + p] = pix / down2;
else
out[(i * w + o) * ch + p] = std::min(pix, m);
}
}
}
}
void RawImage::resizeInt(int downsample, bool avg)
{
uint32_t oldw = m_width;
std::unique_ptr<PixelType[]> old_pixels = std::move(m_pixels);
allocate(m_width / downsample, m_height / downsample, m_channels, m_type);
switch(m_type)
{
case RawImage::UINT8:
integerResample<uint8_t, uint32_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
break;
case RawImage::UINT16:
integerResample<uint16_t, uint32_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
break;
case RawImage::UINT32:
integerResample<uint32_t, uint64_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
break;
case RawImage::FLOAT32:
integerResample<float, double>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
break;
case RawImage::FLOAT64:
integerResample<double, double>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
break;
default:
break;
}
}
std::pair<float, float> RawImage::unitScale() const
{
float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4);
@@ -904,11 +988,13 @@ bool RawImage::valid() const
return m_width > 0 && m_height > 0;
}
#ifndef NO_QT
void RawImage::setICCProfile(const QByteArray &icc)
{
if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
}
#endif
void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
{
@@ -955,12 +1041,12 @@ void RawImage::convertTosRGB()
}
else
{
qDebug() << "Failed to create color transform";
//qDebug() << "Failed to create color transform";
}
}
else
{
qDebug() << "Failed to open icc profile";
//qDebug() << "Failed to open icc profile";
}
cmsCloseProfile(inProfile);
@@ -1004,13 +1090,13 @@ void RawImage::generateLUT()
}
else
{
qDebug() << "Failed to create color transform";
//qDebug() << "Failed to create color transform";
m_lut.clear();
}
}
else
{
qDebug() << "Failed to open icc profile";
//qDebug() << "Failed to open icc profile";
m_lut.clear();
}
+8 -1
View File
@@ -7,7 +7,9 @@
#include <stdint.h>
#include <math.h>
#include <memory.h>
#ifndef NO_QT
#include <QImage>
#endif
extern int THUMB_SIZE;
extern int THUMB_SIZE_BORDER;
@@ -83,13 +85,15 @@ public:
RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
RawImage(const RawImage &d);
RawImage(RawImage &&d);
#ifndef NO_QT
RawImage(const QImage &img);
#endif
const RawImage::Stats& imageStats() const;
void calcStats();
uint32_t width() const;
uint32_t height() const;
uint32_t channels() const;
uint32_t size() const;
uint64_t size() const;
DataType type() const;
uint32_t norm() const;
uint32_t widthBytes() const;
@@ -107,6 +111,7 @@ public:
float thumbAspect() const;
bool pixel(int x, int y, double &r, double &g, double &b) const;
void resize(uint32_t w, uint32_t h);
void resizeInt(int downsample, bool avg);
std::pair<float, float> unitScale() const;
void flip();
@@ -116,7 +121,9 @@ public:
static size_t typeSize(DataType type);
std::vector<RawImage> split() const;
bool valid() const;
#ifndef NO_QT
void setICCProfile(const QByteArray &icc);
#endif
void setICCProfile(const LibXISF::ByteArray &icc);
void convertTosRGB();
void generateLUT();
+2 -2
View File
@@ -1,7 +1,7 @@
#include "rawimage.h"
#ifdef __SSE2__
#include <x86intrin.h>
#include <cstdint>
#include <limits>
template<typename T, int ch>
void fromPlanarSSE(const void *in, void *out, size_t count)
+6 -4
View File
@@ -16,14 +16,16 @@
<file>grbg.png</file>
<file>gbrg.png</file>
<file>space.nouspiro.tenmon.png</file>
</qresource>
<qresource lang="en" prefix="/">
<file>../translations/tenmon_pt_BR.qm</file>
<file alias="help">../about/help_en</file>
</qresource>
<qresource lang="sk" prefix="/">
<qresource prefix="/" lang="en">
<file alias="help">../about/help_en</file>
</qresource>
<qresource prefix="/" lang="sk">
<file alias="help">../about/help_sk</file>
</qresource>
<qresource lang="fr" prefix="/">
<qresource prefix="/" lang="fr">
<file alias="help">../about/help_fr</file>
</qresource>
</RCC>
+64 -8
View File
@@ -3,12 +3,13 @@
#include <QFileInfo>
#include <QDebug>
#include <QInputDialog>
#include <QJsonValue>
#include "loadrunable.h"
#include "rawimage.h"
#include "loadrunable.h"
#include "loadimage.h"
#include "batchprocessing.h"
#include <fitsio2.h>
#include "libXISF/libxisf.h"
#include "libxisf.h"
#ifdef PLATESOLVER
#include "solver.h"
#endif // PLATESOLVER
@@ -28,6 +29,7 @@ ScriptEngine::ScriptEngine(BatchProcessing *parent)
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
_database->init(QLatin1String("scriptengine"));
_semaphore.release(_pool->maxThreadCount());
_pool->setThreadPriority(QThread::LowPriority);
#ifdef PLATESOLVER
_solver = new Solver(this);
@@ -162,6 +164,58 @@ bool ScriptEngine::convert(File *file, QString &outpath, const QString &format,
}
#ifdef PLATESOLVER
void ScriptEngine::setSolverProfile(int index)
{
if(_solver && index >= SSolver::Parameters::DEFAULT && index < SSolver::Parameters::BIG_STARS)
{
_solver->setParameters((SSolver::Parameters::ParametersProfile)index);
}
}
void ScriptEngine::setSolverProfile(const QVariantMap &profile)
{
if(_solver)
{
SSolver::Parameters params = SSolver::Parameters::convertFromMap(profile);
_solver->setParameters(params);
}
}
QJSValue ScriptEngine::getSolverProfile() const
{
if(_solver)
{
QMap<QString, QVariant> params = SSolver::Parameters::convertToMap(_solver->getProfile());
QJSValue ret = _jsEngine->newObject();
for(auto i = params.begin(); i != params.end(); i++)
{
switch(i.value().metaType().id())
{
case QMetaType::Int:
ret.setProperty(i.key(), i.value().toInt());
break;
case QMetaType::Double:
ret.setProperty(i.key(), i.value().toDouble());
break;
case QMetaType::Bool:
ret.setProperty(i.key(), i.value().toBool());
break;
case QMetaType::QString:
ret.setProperty(i.key(), i.value().toString());
break;
default:
qWarning() << "unhandled metatype" << i.key() << i.value();
break;
}
}
return ret;
}
else
{
return QJSValue();
}
}
void ScriptEngine::setStartingSolution(const QJSValue &solution)
{
if(solution.isObject())
@@ -465,7 +519,8 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
{
fitsfile *file;
int status = 0;
fits_open_diskfile(&file, _path.toLocal8Bit().data(), READWRITE, &status);
QString path = makeMaxPath(_path);
fits_open_diskfile(&file, path.toLocal8Bit().data(), READWRITE, &status);
int num = 0;
fits_get_num_hdus(file, &num, &status);
if(status)
@@ -588,9 +643,10 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
try
{
LibXISF::XISFModify modifyXISF;
modifyXISF.open(_path.toLocal8Bit().data());
QFileInfo in(_path);
QFileInfo out(_path + "~");
QString in = makeMaxPath(absoluteFilePath());
QString out = in + "~";
modifyXISF.open(in.toLocal8Bit().data());
qDebug() << "modify" << in << out;
for(auto &remove : modify->_remove)
modifyXISF.removeFITSKeyword(0, remove.toStdString());
@@ -601,9 +657,9 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
for(auto &record : modify->_add)
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
modifyXISF.save(out.absoluteFilePath().toLocal8Bit().toStdString());
modifyXISF.save(out.toLocal8Bit().toStdString());
modifyXISF.close();
std::filesystem::rename(out.filesystemAbsoluteFilePath(), in.filesystemAbsoluteFilePath());
std::filesystem::rename(out.toLocal8Bit().toStdString(), in.toLocal8Bit().toStdString());
return true;
}
catch(std::filesystem::filesystem_error &err)
+3
View File
@@ -49,6 +49,9 @@ public:
Q_INVOKABLE QJSValue getItem(const QStringList &items, const QString &label = "", int current = 0) const;
bool convert(File *file, QString &outpath, const QString &format, const QVariantMap &params, bool async);
#ifdef PLATESOLVER
Q_INVOKABLE void setSolverProfile(int index);
Q_INVOKABLE void setSolverProfile(const QVariantMap &profile);
Q_INVOKABLE QJSValue getSolverProfile() const;
Q_INVOKABLE void setStartingSolution(const QJSValue &solution = QJSValue());
QJSValue solveImage(File *file, bool updateHeader);
QJSValue extractStars(File *file, bool hfr);
+6 -1
View File
@@ -7,7 +7,7 @@
#include <wcslib/wcshdr.h>
#include <wcslib/wcsutil.h>
#include "rawimage.h"
#include "loadrunable.h"
#include "loadimage.h"
#include "scriptengine.h"
Solver::Solver(QObject *parent) : QObject(parent)
@@ -205,6 +205,11 @@ void Solver::setParameters(const Parameters &parameters)
_solver->setParameters(profile);
}
Parameters Solver::getProfile() const
{
return _solver->getCurrentParameters();
}
void Solver::setSearchScale(double fovLow, double fowHigh, SSolver::ScaleUnits units)
{
_solver->setSearchScale(fovLow, fowHigh, units);
+1
View File
@@ -34,6 +34,7 @@ public:
bool updateHeader(QString &error);
void setParameters(SSolver::Parameters::ParametersProfile profile);
void setParameters(const SSolver::Parameters &parameters);
SSolver::Parameters getProfile() const;
void setSearchScale(double fovLow, double fowHigh, ScaleUnits units);
void setSearchPosition(double ra, double dec);
void clearStartingPositionAndScale();
+9
View File
@@ -57,6 +57,15 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="20250126" date="2025-01-26">
<description>
<ul>
<li>Support for really big images +50000px</li>
<li>Fix handling of MAX_PATH on Windows</li>
<li>Add setting solver profile in scripts</li>
</ul>
</description>
</release>
<release version="20241116" date="2024-11-16">
<description>
<ul>
-299
View File
@@ -1,299 +0,0 @@
#include "starfit.h"
#include <gsl/gsl_blas.h>
#include <QDebug>
#include <iostream>
const int PARAM_AM = 0;
const int PARAM_X0 = 1;
const int PARAM_Y0 = 2;
const int PARAM_SX = 3;
const int PARAM_SY = 4;
const int PARAM_TH = 5;
const int MAX_ITER = 20;
const double TOL = 1.0e-3;
struct StarData
{
size_t size;
std::vector<double> val;
};
// a * exp(-0.5*((x-x0)/sx)^2 + ((y-y0)/sy)^2)
double gauss_model(double a, double x0, double y0, double sx, double sy, double x, double y)
{
double _x = (x-x0)/sx;
double _y = (y-y0)/sy;
return a*exp(-0.5*(_x*_x + _y*_y));
}
int func_f(const gsl_vector *X, void *params, gsl_vector *f)
{
StarData *d = static_cast<StarData*>(params);
double am = gsl_vector_get(X, PARAM_AM);
double x0 = gsl_vector_get(X, PARAM_X0);
double y0 = gsl_vector_get(X, PARAM_Y0);
double sx = gsl_vector_get(X, PARAM_SX);
double sy = gsl_vector_get(X, PARAM_SY);
int i = 0;
for(size_t y=0;y<d->size;y++)
{
for(size_t x=0;x<d->size;x++)
{
double v = gauss_model(am, x0, y0, sx, sy, x, y);
gsl_vector_set(f, i, d->val[i] - v);
i++;
}
}
return GSL_SUCCESS;
}
int func_df(const gsl_vector *X, void *params, gsl_matrix *J)
{
StarData *d = static_cast<StarData*>(params);
double am = gsl_vector_get(X, PARAM_AM);
double x0 = gsl_vector_get(X, PARAM_X0);
double y0 = gsl_vector_get(X, PARAM_Y0);
double sx = gsl_vector_get(X, PARAM_SX);
double sy = gsl_vector_get(X, PARAM_SY);
int i = 0;
for(size_t y=0;y<d->size;y++)
{
for(size_t x=0;x<d->size;x++)
{
double tx = x-x0;
double ty = y-y0;
double e = gauss_model(am, x0, y0, sx, sy, x, y);
gsl_matrix_set(J, i, PARAM_AM, -e/am);
gsl_matrix_set(J, i, PARAM_X0, -e*(tx/(sx*sx)));
gsl_matrix_set(J, i, PARAM_Y0, -e*(ty/(sy*sy)));
gsl_matrix_set(J, i, PARAM_SX, -e*(tx*tx/(sx*sx*sx)));
gsl_matrix_set(J, i, PARAM_SY, -e*(ty*ty/(sy*sy*sy)));
i++;
}
}
return GSL_SUCCESS;
}
int func_f_an(const gsl_vector *X, void *params, gsl_vector *f)
{
StarData *d = static_cast<StarData*>(params);
double am = gsl_vector_get(X, PARAM_AM);
double x0 = gsl_vector_get(X, PARAM_X0);
double y0 = gsl_vector_get(X, PARAM_Y0);
double sx = gsl_vector_get(X, PARAM_SX);
double sy = gsl_vector_get(X, PARAM_SY);
double th = gsl_vector_get(X, PARAM_TH);
int i = 0;
double a = sin(th);
double b = cos(th);
for(size_t y=0;y<d->size;y++)
{
for(size_t x=0;x<d->size;x++)
{
double v = gauss_model(am, x0, y0, sx, sy, x*b-y*a, x*a+y*b);
gsl_vector_set(f, i, d->val[i] - v);
i++;
}
}
return GSL_SUCCESS;
}
int func_df_af(const gsl_vector *X, void *params, gsl_matrix *J)
{
StarData *d = static_cast<StarData*>(params);
double am = gsl_vector_get(X, PARAM_AM);
double x0 = gsl_vector_get(X, PARAM_X0);
double y0 = gsl_vector_get(X, PARAM_Y0);
double sx = gsl_vector_get(X, PARAM_SX);
double sy = gsl_vector_get(X, PARAM_SY);
int i = 0;
for(size_t y=0;y<d->size;y++)
{
for(size_t x=0;x<d->size;x++)
{
double tx = x-x0;
double ty = y-y0;
double e = gauss_model(am, x0, y0, sx, sy, x, y);
gsl_matrix_set(J, i, PARAM_AM, -e/am);
gsl_matrix_set(J, i, PARAM_X0, -e*(tx/(sx*sx)));
gsl_matrix_set(J, i, PARAM_Y0, -e*(ty/(sy*sy)));
gsl_matrix_set(J, i, PARAM_SX, -e*(tx*tx/(sx*sx*sx)));
gsl_matrix_set(J, i, PARAM_SY, -e*(ty*ty/(sy*sy*sy)));
i++;
}
}
return GSL_SUCCESS;
}
//int func_fvv(const gsl_vector *x, const gsl_vector * v, void *params, gsl_vector *fvv)
//{
// return GSL_SUCCESS;
//}
void callback(const size_t iter, void *, const gsl_multifit_nlinear_workspace *w)
{
double rcond;
gsl_vector *x = gsl_multifit_nlinear_position(w);
gsl_multifit_nlinear_rcond(&rcond, w);
QString r = "Iter: " + QString::number(iter)
+ " Am: " + QString::number(gsl_vector_get(x, PARAM_AM))
+ " X0: " + QString::number(gsl_vector_get(x, PARAM_X0))
+ " Y0: " + QString::number(gsl_vector_get(x, PARAM_Y0))
+ " SX: " + QString::number(gsl_vector_get(x, PARAM_SX))
+ " SY: " + QString::number(gsl_vector_get(x, PARAM_SY))
+ " J(X) :" + QString::number(1.0/rcond)
+ " av: " + QString::number(gsl_multifit_nlinear_avratio(w));
std::cout << r.toStdString() << std::endl;
}
void callback_an(const size_t iter, void *, const gsl_multifit_nlinear_workspace *w)
{
double rcond;
gsl_vector *x = gsl_multifit_nlinear_position(w);
gsl_multifit_nlinear_rcond(&rcond, w);
qDebug() << "Iter:" << iter << "Am:" << gsl_vector_get(x, PARAM_AM)
<< "X0:" << gsl_vector_get(x, PARAM_X0)
<< "Y0:" << gsl_vector_get(x, PARAM_Y0)
<< "SX:" << gsl_vector_get(x, PARAM_SX)
<< "SY:" << gsl_vector_get(x, PARAM_SY)
<< "TH:" << gsl_vector_get(x, PARAM_TH)
<< "J(X):" << 1.0/rcond
<< "av:" << gsl_multifit_nlinear_avratio(w);
}
Star::Star()
{
m_am = m_x = m_y = m_sx = m_sy = NAN;
}
bool Star::valid() const
{
return !isnan(m_am);
}
//half width at half maximum = sqrt(2*ln(2))
double Star::hwhmX() const
{
return 1.177410023*m_sx;
}
double Star::hwhmY() const
{
return 1.177410023*m_sy;
}
// half width at 1/20 maximum
double Star::hw20X() const
{
return 2.447746831*m_sx;
}
double Star::hw20Y() const
{
return 2.447746831*m_sy;
}
// full width at half maximum
double Star::fwhmX() const
{
return 2.354820045*m_sx;
}
double Star::fwhmY() const
{
return 2.354820045*m_sy;
}
bool Star::operator<(const Star &d) const
{
return m_am < d.m_am;
}
StarFit::StarFit(int size)
{
m_size = size;
m_fdf_params = gsl_multifit_nlinear_default_parameters();
m_fdf_params.trs = gsl_multifit_nlinear_trs_lmaccel;
m_fdf.f = func_f;
m_fdf.df = func_df;
m_fdf.fvv = nullptr;
m_fdf.n = size*size;
m_fdf.p = 5;//number of model parameters amplitude, x, y, fwhm_x, fwhm_y
m_fdf_an.f = func_f_an;
m_fdf_an.df = nullptr;
m_fdf_an.fvv = nullptr;
m_fdf_an.n = size*size;
m_fdf_an.p = 6;//number of model parameters amplitude, x, y, sigma_x, sigma_y, angle
gsl_set_error_handler_off();
}
StarFit::~StarFit()
{
}
Star StarFit::fitStar(const std::vector<double> &data, bool angle)
{
gsl_multifit_nlinear_fdf *fdf = angle ? &m_fdf_an : &m_fdf;
Star star;
StarData d;
d.val = data;
d.size = m_size;
d.val = data;
fdf->params = &d;
int info;
double min = *std::min_element(data.begin(), data.end());
double max = *std::max_element(data.begin(), data.end()) - min;
for(double &v : d.val)
{
v -= min;
}
gsl_vector *start = gsl_vector_alloc(fdf->p);
gsl_vector_set(start, PARAM_AM, max);
gsl_vector_set(start, PARAM_X0, m_size/2);
gsl_vector_set(start, PARAM_Y0, m_size/2);
gsl_vector_set(start, PARAM_SX, 1.0);
gsl_vector_set(start, PARAM_SY, 1.0);
if(angle)
gsl_vector_set(start, PARAM_TH, 0.0);
gsl_multifit_nlinear_workspace *workspace = gsl_multifit_nlinear_alloc(gsl_multifit_nlinear_trust, &m_fdf_params, fdf->n, fdf->p);
int ret = gsl_multifit_nlinear_init(start, fdf, workspace);
if(ret)return star;
ret = gsl_multifit_nlinear_driver(MAX_ITER, TOL, TOL, TOL, nullptr, nullptr, &info, workspace);
if(ret==0)
{
gsl_vector *y = gsl_multifit_nlinear_position(workspace);
star.m_am = gsl_vector_get(y, PARAM_AM);
star.m_x = gsl_vector_get(y, PARAM_X0);
star.m_y = gsl_vector_get(y, PARAM_Y0);
star.m_sx = gsl_vector_get(y, PARAM_SX);
star.m_sy = gsl_vector_get(y, PARAM_SY);
if(angle)
star.m_theta = gsl_vector_get(y, PARAM_TH);
//qDebug() << "finished" << star.m_am << star.m_sx << star.m_sy;
}
gsl_vector_free(start);
gsl_multifit_nlinear_free(workspace);
return star;
}
-39
View File
@@ -1,39 +0,0 @@
#ifndef STARFIT_H
#define STARFIT_H
#include "rawimage.h"
#include <gsl/gsl_multifit_nlinear.h>
double gauss_model(double a, double x0, double y0, double sx, double sy, double x, double y);
struct Star
{
double m_am;
double m_x,m_y;
double m_sx,m_sy;
double m_theta;
Star();
bool valid() const;
double hwhmX() const;
double hwhmY() const;
double hw20X() const;
double hw20Y() const;
double fwhmX() const;
double fwhmY() const;
bool operator<(const Star &d) const;
};
class StarFit
{
int m_size;
gsl_multifit_nlinear_fdf m_fdf;
gsl_multifit_nlinear_fdf m_fdf_an;
gsl_multifit_nlinear_parameters m_fdf_params;
gsl_vector *m_vector;
public:
StarFit(int size);
~StarFit();
Star fitStar(const std::vector<double> &data, bool angle);
};
#endif // STARFIT_H
+1
View File
@@ -3,6 +3,7 @@
#include <QDebug>
#include <QToolButton>
#include <QSettings>
#include <QStyle>
#include "imageringlist.h"
const float BLACK_POINT_SIGMA = -2.8f;
+1 -7
View File
@@ -4,16 +4,10 @@
#include <QToolBar>
#include <QStackedWidget>
#include "stfslider.h"
#include "mtfparam.h"
class Image;
struct MTFParam
{
float blackPoint[3] = {0.0f, 0.0f, 0.0f};
float midPoint[3] = {0.5f, 0.5f, 0.5f};
float whitePoint[3] = {1.0f, 1.0f, 1.0f};
};
class StretchToolbar : public QToolBar
{
Q_OBJECT
+26
View File
@@ -0,0 +1,26 @@
option(BUILD_THUMBNAILER "Build generator of thumbnails" OFF)
if(BUILD_THUMBNAILER)
if(WIN32)
add_library(tenmonthumbnailer SHARED
winmain.cpp
../rawimage.cpp
../rawimage_sse.cpp)
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
target_link_libraries(tenmonthumbnailer PRIVATE ${LCMS2_LIB} XISF)
else(WIN32)
qt_add_executable(tenmonthumbnailer
main.cpp
../rawimage.cpp
../rawimage_sse.cpp
../loadimage.cpp
../imageinfodata.cpp)
target_link_libraries(tenmonthumbnailer PRIVATE Qt6::Core Qt6::Gui ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
endif(WIN32)
endif(BUILD_THUMBNAILER)
+48
View File
@@ -0,0 +1,48 @@
#include <QCoreApplication>
#include <QCommandLineParser>
#include "../rawimage.h"
#include "../loadimage.h"
bool OpenGLES = false;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QCommandLineParser parser;
parser.addOption({{"s", "size"}, "Size of the thumbnail in pixels (default: 128)", "size", "128"});
parser.addPositionalArgument("input", "Input image file");
parser.addPositionalArgument("output", "Output image file");
parser.addHelpOption();
parser.process(a);
QStringList args = parser.positionalArguments();
if(args.size() < 2)
return 1;
QString input = args[0];
QString output = args[1];
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
if(!loadImage(input, info, rawImage))
return 1;
if(!rawImage)
return 2;
rawImage->convertToType(RawImage::UINT8);
QImage img((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), QImage::Format_RGBA8888);
bool ok = false;
int size = parser.value("s").toInt(&ok);
if(!ok)size = 128;
img = img.scaled(size, size, Qt::KeepAspectRatio);
img.save(output, "png");
//rawImage->convertTosRGB();
return 0;
}
+1
View File
@@ -0,0 +1 @@
bool OpenGLES = false;
Binary file not shown.
File diff suppressed because it is too large Load Diff