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) endif(SANITIZE_ADDRESS_LEAK)
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED) 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(EXIF_LIB exif REQUIRED)
find_library(FITS_LIB cfitsio REQUIRED) find_library(FITS_LIB cfitsio REQUIRED)
find_library(RAW_LIB NAMES raw_r REQUIRED) find_library(RAW_LIB NAMES raw_r REQUIRED)
@@ -39,18 +37,20 @@ set(TENMON_SRC
histogram.cpp histogram.h histogram.cpp histogram.h
httpdownloader.h httpdownloader.cpp httpdownloader.h httpdownloader.cpp
imageinfo.cpp imageinfo.h imageinfo.cpp imageinfo.h
imageinfodata.cpp imageinfodata.h
imageringlist.cpp imageringlist.h imageringlist.cpp imageringlist.h
imagescrollarea.cpp imagescrollarea.h imagescrollarea.cpp imagescrollarea.h
imagewidget.h imagewidget.cpp imagewidget.h imagewidget.cpp
loadimage.h loadimage.cpp
loadrunable.cpp loadrunable.h loadrunable.cpp loadrunable.h
main.cpp main.cpp
mainwindow.cpp mainwindow.h mainwindow.cpp mainwindow.h
markedfiles.cpp markedfiles.h markedfiles.cpp markedfiles.h
mtfparam.h
rawimage.cpp rawimage.h rawimage.cpp rawimage.h
rawimage_sse.cpp rawimage_sse.cpp
scriptengine.cpp scriptengine.h scriptengine.cpp scriptengine.h
settingsdialog.cpp settingsdialog.h settingsdialog.cpp settingsdialog.h
starfit.cpp starfit.h
statusbar.cpp statusbar.h statusbar.cpp statusbar.h
stfslider.cpp stfslider.h stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h stretchtoolbar.cpp stretchtoolbar.h
@@ -85,6 +85,8 @@ find_path(STELLARSOLVER_INCLUDE stellarsolver.h PATH_SUFFIXES libstellarsolver)
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB) if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE}) target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
if(MXE) 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) target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB} ${GSL_LIB} ${GSLCBLAS_LIB} boost_regex-mt-x64)
else(MXE) else(MXE)
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB}) 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}") message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
endif(STELLARSOLVER_INCLUDE AND 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) if(APPLE)
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation") target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
elseif(UNIX) elseif(UNIX)
@@ -138,3 +140,5 @@ else()
execute_process(COMMAND ${CMAKE_COMMAND} -Dlocal_dir=${CMAKE_CURRENT_SOURCE_DIR} -Doutput_dir=${CMAKE_CURRENT_BINARY_DIR} execute_process(COMMAND ${CMAKE_COMMAND} -Dlocal_dir=${CMAKE_CURRENT_SOURCE_DIR} -Doutput_dir=${CMAKE_CURRENT_BINARY_DIR}
-P "${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake") -P "${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake")
endif() 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 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 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 MacOS X
-3
View File
@@ -33,9 +33,6 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
setWindowTitle(tr("Help")); setWindowTitle(tr("Help"));
resize(800, 600); resize(800, 600);
QLocale locale;
QString l = QLocale::languageToString(locale.language());
QVBoxLayout *layout = new QVBoxLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
QTextEdit *helpText = new QTextEdit(this); QTextEdit *helpText = new QTextEdit(this);
helpText->setReadOnly(true); helpText->setReadOnly(true);
+31 -30
View File
@@ -4,7 +4,7 @@
#include <QSqlError> #include <QSqlError>
#include <QDebug> #include <QDebug>
#include <QDateTime> #include <QDateTime>
#include "loadrunable.h" #include "loadimage.h"
Database::Database(QObject *parent) : QObject(parent) Database::Database(QObject *parent) : QObject(parent)
{ {
@@ -15,32 +15,33 @@ bool Database::init(const QLatin1String &connectionName)
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path); QDir dir(path);
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName); QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
if(!dir.mkpath(".")) if(!dir.mkpath("."))
return false; return false;
if(m_database.isValid()) if(database.isValid())
{ {
m_database.setDatabaseName(dir.absoluteFilePath("database2.db")); database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(m_database.open()) if(database.open())
{ {
m_database.exec("PRAGMA foreign_keys = ON"); QSqlQuery query(database);
query.exec("PRAGMA foreign_keys = ON");
int version = checkVersion(); int version = checkVersion();
if(version == 0) if(version == 0)
{ {
m_database.exec("PRAGMA user_version = 1"); query.exec("PRAGMA user_version = 1");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)"); query.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("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)"); " 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)"); "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)"); query.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)"); query.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)"); query.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)"); query.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)"); query.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 maxDec_idx ON fits_files(maxDec)");
} }
else if(version > 1) else if(version > 1)
{ {
@@ -48,28 +49,28 @@ bool Database::init(const QLatin1String &connectionName)
return false; return false;
} }
QSqlError error = m_database.lastError(); QSqlError error = database.lastError();
if(error.type() == QSqlError::NoError) if(error.type() == QSqlError::NoError)
{ {
m_markQuery = QSqlQuery(m_database); m_markQuery = QSqlQuery(database);
m_markQuery.prepare("INSERT INTO files (file) VALUES (?)"); 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_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_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_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_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_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_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_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=?"); m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
return true; return true;
} }
@@ -130,7 +131,7 @@ QStringList Database::getMarkedFiles()
void Database::clearMarkedFiles() void Database::clearMarkedFiles()
{ {
QSqlDatabase::database().exec("DELETE FROM files"); QSqlQuery query("DELETE FROM files");
} }
bool Database::checkError(QSqlQuery &query) bool Database::checkError(QSqlQuery &query)
@@ -148,7 +149,7 @@ bool Database::checkError(QSqlQuery &query)
int Database::checkVersion() int Database::checkVersion()
{ {
QSqlDatabase db = QSqlDatabase::database(); QSqlDatabase db = QSqlDatabase::database();
QSqlQuery query = db.exec("PRAGMA user_version"); QSqlQuery query("PRAGMA user_version");
if(query.next()) if(query.next())
return query.value(0).toInt(); return query.value(0).toInt();
return -1; return -1;
@@ -194,10 +195,10 @@ void Database::reindex(QProgressDialog *progress)
QVariantList deleteids; QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database(); QSqlDatabase database = QSqlDatabase::database();
database.transaction(); database.transaction();
QSqlQuery size = database.exec("SELECT COUNT(*) FROM fits_files"); QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next(); size.next();
progress->setMaximum(size.value(0).toInt()); 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; int i = 0;
while(files.next()) while(files.next())
{ {
+1 -1
View File
@@ -375,7 +375,7 @@ bool DataBaseView::exportCSV(const QString &path)
if(!csv.open(QIODevice::WriteOnly | QIODevice::Text)) if(!csv.open(QIODevice::WriteOnly | QIODevice::Text))
return false; return false;
QSqlQuery sql = m_model->query(); QSqlQuery sql(m_model->query().lastQuery());
int colCount = m_model->columnCount(); int colCount = m_model->columnCount();
QStringList header; QStringList header;
for(int i=0; i<colCount; i++) for(int i=0; i<colCount; i++)
+1
View File
@@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <QPainter> #include <QPainter>
#include <QDebug> #include <QDebug>
#include <QStyleOption>
Histogram::Histogram(QWidget *parent) : QWidget(parent) Histogram::Histogram(QWidget *parent) : QWidget(parent)
{ {
-410
View File
@@ -1,63 +1,6 @@
#include "imageinfo.h" #include "imageinfo.h"
#include <QSettings> #include <QSettings>
#include <QTime>
#include <QHeaderView> #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) ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
{ {
@@ -97,356 +40,3 @@ void ImageInfo::setInfo(const ImageInfoData &info)
} }
expandAll(); 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 #define IMAGEINFO_H
#include <QTreeWidget> #include <QTreeWidget>
#include <wcslib/wcs.h> #include "imageinfodata.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;
class ImageInfo : public QTreeWidget 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
+27 -20
View File
@@ -4,6 +4,7 @@
#include <QDir> #include <QDir>
#include <QSettings> #include <QSettings>
#include <QTimer> #include <QTimer>
#include <QRegularExpression>
#include "loadrunable.h" #include "loadrunable.h"
#include "rawimage.h" #include "rawimage.h"
#include "database.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) if(!m_rawImage && !m_loading)
{ {
m_loading = true; m_loading = true;
m_released = false; 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) if(!m_loading && m_rawImage)
emit pixmapLoaded(this); emit pixmapLoaded(this);
@@ -110,7 +111,10 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
{ {
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString))); connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*."); m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
m_loadPool = new QThreadPool(this);
m_loadPool->setThreadPriority(QThread::LowPriority);
m_thumbPool = new QThreadPool(this); m_thumbPool = new QThreadPool(this);
m_thumbPool->setThreadPriority(QThread::LowPriority);
m_slideShowTimer = new QTimer(this); m_slideShowTimer = new QTimer(this);
connect(m_slideShowTimer, &QTimer::timeout, this, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment)); 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() ImageRingList::~ImageRingList()
{ {
QThreadPool::globalInstance()->clear(); m_loadPool->clear();
m_thumbPool->clear(); m_thumbPool->clear();
QThreadPool::globalInstance()->waitForDone(); m_loadPool->waitForDone();
m_thumbPool->waitForDone(); m_thumbPool->waitForDone();
} }
@@ -173,11 +177,14 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile, bool
void ImageRingList::setFile(const QString &file) void ImageRingList::setFile(const QString &file)
{ {
QFileInfo info(file); if(!file.isEmpty())
if(info.isDir()) {
setDir(file, QString(), true); QFileInfo info(file);
else if(info.isDir())
setDir(info.absolutePath(), file); setDir(file, QString(), true);
else
setDir(info.absolutePath(), file);
}
} }
ImagePtr ImageRingList::currentImage() ImagePtr ImageRingList::currentImage()
@@ -199,9 +206,9 @@ void ImageRingList::increment()
(*m_firstImage)->release(); (*m_firstImage)->release();
m_firstImage = increment(m_firstImage); m_firstImage = increment(m_firstImage);
m_currImage = increment(m_currImage); m_currImage = increment(m_currImage);
(*m_currImage)->load(); (*m_currImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage); m_lastImage = increment(m_lastImage);
(*m_lastImage)->load(); (*m_lastImage)->load(m_loadPool);
} }
} }
@@ -212,9 +219,9 @@ void ImageRingList::decrement()
(*m_lastImage)->release(); (*m_lastImage)->release();
m_firstImage = decrement(m_firstImage); m_firstImage = decrement(m_firstImage);
m_currImage = decrement(m_currImage); m_currImage = decrement(m_currImage);
(*m_currImage)->load(); (*m_currImage)->load(m_loadPool);
m_lastImage = decrement(m_lastImage); 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()) if(m_images.empty())
return; return;
(*m_currImage)->load(); (*m_currImage)->load(m_loadPool);
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2; m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
if(m_liveMode) if(m_liveMode)
@@ -277,9 +284,9 @@ void ImageRingList::loadFile(int row)
for(int i=0; i<m_width; i++) for(int i=0; i<m_width; i++)
{ {
m_firstImage = decrement(m_firstImage); m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load(); (*m_firstImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage); m_lastImage = increment(m_lastImage);
(*m_lastImage)->load(); (*m_lastImage)->load(m_loadPool);
} }
if(m_lastImage != m_firstImage) if(m_lastImage != m_firstImage)
{ {
@@ -403,9 +410,9 @@ void ImageRingList::setPreload(int width)
for(int i = newWidth - m_width; i>0; i--) for(int i = newWidth - m_width; i>0; i--)
{ {
m_firstImage = decrement(m_firstImage); m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load(); (*m_firstImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage); m_lastImage = increment(m_lastImage);
(*m_lastImage)->load(); (*m_lastImage)->load(m_loadPool);
} }
} }
if(newWidth < m_width) if(newWidth < m_width)
@@ -460,9 +467,9 @@ void ImageRingList::toggleSlideshow(bool start)
void ImageRingList::setFiles(const QStringList files, const QString &currentFile) void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
{ {
QThreadPool::globalInstance()->clear(); m_loadPool->clear();
m_thumbPool->clear(); m_thumbPool->clear();
QThreadPool::globalInstance()->waitForDone(); m_loadPool->waitForDone();
m_thumbPool->waitForDone(); m_thumbPool->waitForDone();
beginResetModel(); beginResetModel();
m_images.clear(); m_images.clear();
+4 -2
View File
@@ -7,8 +7,9 @@
#include <QPixmap> #include <QPixmap>
#include <QDir> #include <QDir>
#include <memory> #include <memory>
#include "imageinfo.h" #include "imageinfodata.h"
#include "rawimage.h" #include "rawimage.h"
#include <QAbstractItemModel>
class ImageRingList; class ImageRingList;
class QThreadPool; class QThreadPool;
@@ -27,7 +28,7 @@ class Image : public QObject
ImageRingList *m_ringList; ImageRingList *m_ringList;
public: public:
explicit Image(const QString name, int number, ImageRingList *ringList); explicit Image(const QString name, int number, ImageRingList *ringList);
void load(); void load(QThreadPool *pool);
void loadThumbnail(QThreadPool *pool); void loadThumbnail(QThreadPool *pool);
void release(); void release();
QString name() const; QString name() const;
@@ -62,6 +63,7 @@ class ImageRingList : public QAbstractItemModel
QDir::SortFlag m_sort = QDir::Name; QDir::SortFlag m_sort = QDir::Name;
bool m_reversed = false; bool m_reversed = false;
AnalyzeLevel m_analyzeLevel; AnalyzeLevel m_analyzeLevel;
QThreadPool *m_loadPool;
QThreadPool *m_thumbPool; QThreadPool *m_thumbPool;
Database *m_database; Database *m_database;
QStringList m_nameFilter; QStringList m_nameFilter;
+208 -55
View File
@@ -10,6 +10,7 @@
#include <QDragEnterEvent> #include <QDragEnterEvent>
#include <QPainter> #include <QPainter>
#include "imageringlist.h" #include "imageringlist.h"
#include <QFloat16>
int FILTERING = 1; int FILTERING = 1;
bool OpenGLES = false; bool OpenGLES = false;
@@ -110,16 +111,15 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
m_error.clear(); m_error.clear();
makeCurrent(); makeCurrent();
m_rawImage = image; 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_imgWidth = image->width();
m_imgHeight = image->height(); 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; if(!m_image)return;
@@ -133,21 +133,26 @@ 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()); m_lut->setData(0, 0, 0, LUT_SIZE, LUT_SIZE, LUT_SIZE, 0, QOpenGLTexture::RGBA, QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image->getLUT().data());
} }
QElapsedTimer timer; if(!tooBig)
timer.start(); {
m_image->destroy(); while(f->glGetError() != GL_NO_ERROR);
m_image->setAutoMipMapGenerationEnabled(false); QElapsedTimer timer;
m_image->setFormat(rawImageType.textureFormat); timer.start();
m_image->setSize(image->width(), image->height()); m_image->destroy();
m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }()); m_image->setAutoMipMapGenerationEnabled(false);
m_image->allocateStorage(); m_image->setFormat(rawImageType.textureFormat);
m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear); m_image->setSize(image->width(), image->height());
m_image->setWrapMode(QOpenGLTexture::ClampToEdge); m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }());
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data()); m_image->allocateStorage();
m_image->bind(); m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4); m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
f->glGenerateMipmap(GL_TEXTURE_2D); m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data());
qDebug() << "setImage" << timer.elapsed(); m_image->bind();
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[0] = 1.0f;
m_unit_scale[1] = 0.0f; m_unit_scale[1] = 0.0f;
@@ -185,10 +190,10 @@ void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
if(!mousePos.isNull()) if(!mousePos.isNull())
focus = mousePos; focus = mousePos;
if(width() > m_image->width() * m_scale) if(width() > m_imgWidth * m_scale)
m_dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f; m_dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
if(height() > m_image->height() * m_scale) if(height() > m_imgHeight * m_scale)
m_dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f; m_dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
float newScale = std::sqrt(std::pow(2.0f, (float)m_scaleStop)); float newScale = std::sqrt(std::pow(2.0f, (float)m_scaleStop));
float r = newScale / m_scale; float r = newScale / m_scale;
@@ -232,10 +237,10 @@ QVector2D ImageWidgetGL::getImagePixelCoord(const QVector2D &pos)
{ {
float dx = m_dx; float dx = m_dx;
float dy = m_dy; float dy = m_dy;
if(m_width > m_image->width()*m_scale) if(m_width > m_imgWidth * m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f; dx = -width()*0.5f + m_imgWidth*m_scale * 0.5f;
if(m_height > m_image->height()*m_scale) if(m_height > m_imgHeight * m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f; dy = -height()*0.5f + m_imgHeight*m_scale * 0.5f;
QVector2D offset(dx, dy); QVector2D offset(dx, dy);
return (pos + offset) / m_scale; return (pos + offset) / m_scale;
@@ -341,17 +346,158 @@ void ImageWidgetGL::showThumbnail(bool enable)
setOffset(m_dx, m_dy); 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() void ImageWidgetGL::paintGL()
{ {
float dx = m_dx; float dx = m_dx;
float dy = m_dy; float dy = m_dy;
if(m_width > m_image->width() * m_scale) if(m_width > m_imgWidth * m_scale)
dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f; dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
if(m_height > m_image->height() * m_scale) if(m_height > m_imgHeight * m_scale)
dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f; dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
QBrush highlight = style()->standardPalette().highlight(); QBrush highlight = style()->standardPalette().highlight();
f->glClear(GL_COLOR_BUFFER_BIT); f->glClear(GL_COLOR_BUFFER_BIT);
f->glBlendFunc(GL_ONE, GL_ZERO);
if(m_showThumbnails) if(m_showThumbnails)
{ {
m_vaoThumb->bind(); m_vaoThumb->bind();
@@ -425,31 +571,38 @@ void ImageWidgetGL::paintGL()
} }
else else
{ {
m_vao->bind(); if(m_swPaint)
debayer();
if(m_superpixel && m_debayerTex)
{ {
f->glActiveTexture(GL_TEXTURE0); swPaint(m_rawImage, dx, dy, m_scale, m_mtfParams, this);
f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
} }
else else
m_image->bind(0); {
m_vao->bind();
debayer();
m_program->bind(); if(m_superpixel && m_debayerTex)
m_program->setUniformValue("viewport", (float)width(), (float)height()); {
m_program->setUniformValue("offset", std::floor(dx), std::floor(dy)); f->glActiveTexture(GL_TEXTURE0);
m_program->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3); f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
m_program->setUniformValue("unit_scale", m_unit_scale[0], m_unit_scale[1]); }
m_program->setUniformValue("zoom", 1.0f/m_scale); else
m_program->setUniformValue("bw", m_bwImg && !m_superpixel); m_image->bind(0);
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert); m_program->bind();
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1); m_program->setUniformValue("viewport", (float)width(), (float)height());
m_program->setUniformValue("lut_table", 2); m_program->setUniformValue("offset", std::floor(dx), std::floor(dy));
m_program->setUniformValue("srgb", m_srgb); m_program->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); m_program->setUniformValue("unit_scale", m_unit_scale[0], m_unit_scale[1]);
m_vao->release(); m_program->setUniformValue("zoom", 1.0f/m_scale);
m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert);
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
m_program->setUniformValue("lut_table", 2);
m_program->setUniformValue("srgb", m_srgb);
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; Database *m_database = nullptr;
QPointF m_lastPos; QPointF m_lastPos;
QString m_error; QString m_error;
bool m_swPaint = false;
public: public:
explicit ImageWidgetGL(Database *database, QWidget *parent = nullptr); explicit ImageWidgetGL(Database *database, QWidget *parent = nullptr);
~ImageWidgetGL() override; ~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 "loadrunable.h"
#include "imageringlist.h" #include "imageringlist.h"
#include <libraw/libraw.h>
#include "imageinfo.h"
#include <QFileInfo> #include <QFileInfo>
#include <QPainter> #include <QPainter>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QDebug> #include <QDebug>
#include <iostream>
#include <algorithm> #include <algorithm>
#include <libexif/exif-data.h>
#include <fitsio2.h> #include <fitsio2.h>
#include <libxisf.h>
#include "rawimage.h" #include "rawimage.h"
#include "starfit.h" #include "loadimage.h"
#include <lcms2.h> #include <lcms2.h>
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) : LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
m_file(file), m_file(makeMaxPath(file)),
m_receiver(receiver), m_receiver(receiver),
m_analyzeLevel(level), m_analyzeLevel(level),
m_thumbnail(thumbnail) 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() void LoadRunable::run()
{ {
try 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) : ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams &params, QSemaphore *semaphore) :
m_infile(in), m_infile(makeMaxPath(in)),
m_outfile(out), m_outfile(makeMaxPath(out)),
m_format(format), m_format(format),
m_params(params), m_params(params),
m_semaphore(semaphore) m_semaphore(semaphore)
@@ -623,6 +191,17 @@ void ConvertRunable::run()
QFileInfo info(m_outfile); QFileInfo info(m_outfile);
info.dir().mkpath("."); 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(rawimage)
{ {
if(m_format == "xisf") if(m_format == "xisf")
@@ -755,4 +334,33 @@ ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map)
if(map.contains("compressionType")) if(map.contains("compressionType"))
compressionType = map["compressionType"].toString(); 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 <QRunnable>
#include <QString> #include <QString>
#include <QSemaphore> #include <QSemaphore>
#include "imageinfo.h" #include <QSize>
#include "imageinfodata.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);
class Image; class Image;
@@ -25,7 +20,6 @@ public:
void run() override; void run() override;
}; };
class ConvertRunable : public QRunnable class ConvertRunable : public QRunnable
{ {
public: public:
@@ -33,6 +27,10 @@ public:
{ {
int compressionLevel = -1; int compressionLevel = -1;
QString compressionType; QString compressionType;
int binning = 0;
bool average = true;
QSize resize;
Qt::AspectRatioMode aspect = Qt::KeepAspectRatio;
ConvertParams(){} ConvertParams(){}
ConvertParams(const QVariantMap &map); 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 In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut); 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("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(); viewMenu->addSeparator();
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask")); QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
QActionGroup *bayerActionGroup = new QActionGroup(this); QActionGroup *bayerActionGroup = new QActionGroup(this);
+1 -1
View File
@@ -60,6 +60,6 @@ void MarkedFiles::clearSelected()
void MarkedFiles::clearAll() void MarkedFiles::clearAll()
{ {
QSqlDatabase db = QSqlDatabase::database(); QSqlDatabase db = QSqlDatabase::database();
db.exec("DELETE FROM files"); QSqlQuery("DELETE FROM files", db);
m_model->select(); 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 "rawimage.h"
#include <QDebug>
#include <cstring> #include <cstring>
#include <lcms2.h>
#ifndef NO_QT
#include <QDebug>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QFloat16> #include <QFloat16>
#include <QColorSpace> #include <QColorSpace>
#include <lcms2.h>
using F16 = qfloat16; using F16 = qfloat16;
#else
#include <algorithm>
using F16 = _Float16;
#endif
int THUMB_SIZE = 128; int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138; 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_channels = ch;
m_ch = ch == 3 ? 4 : ch; m_ch = ch == 3 ? 4 : ch;
m_origType = m_type = type; 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() RawImage::RawImage()
@@ -60,7 +64,7 @@ RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type)
RawImage::RawImage(const RawImage &d) RawImage::RawImage(const RawImage &d)
{ {
allocate(d.m_width, d.m_height, d.m_channels, d.m_type); 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; m_stats = d.m_stats;
} }
@@ -78,6 +82,7 @@ RawImage::RawImage(RawImage &&d)
m_thumbAspect = d.m_thumbAspect; m_thumbAspect = d.m_thumbAspect;
} }
#ifndef NO_QT
RawImage::RawImage(const QImage &img) RawImage::RawImage(const QImage &img)
{ {
qDebug() << img; qDebug() << img;
@@ -118,6 +123,24 @@ RawImage::RawImage(const QImage &img)
for(int i=0; i<img.height(); i++) for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*2); 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 else
{ {
QImage tmp = img.convertToFormat(QImage::Format_RGBA8888); QImage tmp = img.convertToFormat(QImage::Format_RGBA8888);
@@ -127,6 +150,7 @@ RawImage::RawImage(const QImage &img)
} }
m_stats.m_stats = false; m_stats.m_stats = false;
} }
#endif
const RawImage::Stats& RawImage::imageStats() const const RawImage::Stats& RawImage::imageStats() const
{ {
@@ -294,9 +318,9 @@ uint32_t RawImage::channels() const
return m_channels; 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 RawImage::DataType RawImage::type() const
@@ -336,12 +360,12 @@ const void *RawImage::data() const
void *RawImage::data(uint32_t row, uint32_t col) 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 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 const void *RawImage::origData() const
@@ -356,12 +380,12 @@ const void *RawImage::origData(uint32_t row, uint32_t col) const
{ {
if(m_original) if(m_original)
{ {
col = col * m_origWidth / m_width; col = (uint64_t)col * m_origWidth / m_width;
row = row * m_origHeight / m_height; row = (uint64_t)row * m_origHeight / m_height;
return m_original.get() + (m_origWidth * row * m_ch + col * m_ch) * typeSize(m_origType); return m_original.get() + ((size_t)m_origWidth * row * m_ch + (size_t)col * m_ch) * typeSize(m_origType);
} }
else 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 bool RawImage::planar() const
@@ -381,12 +405,12 @@ void RawImage::convertToThumbnail()
auto loop = [&](F16 *out, auto *in, float scale) 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; int64_t idx = (i*THUMB_SIZE + o)*4;
int idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch; int64_t idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
if(m_channels == 1) if(m_channels == 1)
{ {
@@ -421,7 +445,9 @@ void RawImage::convertToThumbnail()
loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f); loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
break; break;
default: default:
#ifndef NO_QT
qWarning() << "FLOAT64 should not happend"; qWarning() << "FLOAT64 should not happend";
#endif
return; return;
} }
@@ -442,21 +468,21 @@ void RawImage::convertToGLFormat()
} }
template<typename T, typename U> 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>)) 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]; dst[i] = src[i];
} }
if constexpr(std::is_integral_v<T> && std::is_integral_v<U>) if constexpr(std::is_integral_v<T> && std::is_integral_v<U>)
{ {
if constexpr(sizeof(T) > sizeof(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); dst[i] = src[i] >> ((sizeof(T) - sizeof(U)) * 8);
else 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); 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(); U max = std::numeric_limits<U>::max();
T scale = (T)(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; dst[i] = src[i] * scale;
} }
if constexpr(std::is_integral_v<T> && (std::is_floating_point_v<U> || std::is_same_v<U, F16>)) 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()); 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; dst[i] = (U)src[i] * scale;
} }
} }
template<typename T> 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) switch(dstType)
{ {
@@ -514,7 +540,7 @@ void RawImage::convertToType(DataType type)
allocate(m_width, m_height, m_channels, type); allocate(m_width, m_height, m_channels, type);
m_origType = origType; m_origType = origType;
uint32_t s = size() * m_ch; size_t s = size() * m_ch;
switch(m_origType) switch(m_origType)
{ {
case UINT8: 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 sx = (float)w / oldw;
float sy = (float)h / oldh; 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}; U p[4] = {0.0f};
uint32_t xx = x * oldw / w;//calculate source rect uint64_t xx = x * oldw / w;//calculate source rect
uint32_t yy = y * oldh / h; uint64_t yy = y * oldh / h;
uint32_t xe = std::min((x + 1) * oldw / w, oldw - 1); uint64_t xe = std::min((x + 1) * oldw / w, (uint64_t)oldw - 1);
uint32_t ye = std::min((y + 1) * oldh / h, oldh - 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 for(uint32_t o = yy; o <= ye; o++)//iterate over source Y
{ {
float cy = o * sy - 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 std::pair<float, float> RawImage::unitScale() const
{ {
float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4); 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; return m_width > 0 && m_height > 0;
} }
#ifndef NO_QT
void RawImage::setICCProfile(const QByteArray &icc) void RawImage::setICCProfile(const QByteArray &icc)
{ {
if(icc.size()) if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end()); m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
} }
#endif
void RawImage::setICCProfile(const LibXISF::ByteArray &icc) void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
{ {
@@ -955,12 +1041,12 @@ void RawImage::convertTosRGB()
} }
else else
{ {
qDebug() << "Failed to create color transform"; //qDebug() << "Failed to create color transform";
} }
} }
else else
{ {
qDebug() << "Failed to open icc profile"; //qDebug() << "Failed to open icc profile";
} }
cmsCloseProfile(inProfile); cmsCloseProfile(inProfile);
@@ -1004,13 +1090,13 @@ void RawImage::generateLUT()
} }
else else
{ {
qDebug() << "Failed to create color transform"; //qDebug() << "Failed to create color transform";
m_lut.clear(); m_lut.clear();
} }
} }
else else
{ {
qDebug() << "Failed to open icc profile"; //qDebug() << "Failed to open icc profile";
m_lut.clear(); m_lut.clear();
} }
+8 -1
View File
@@ -7,7 +7,9 @@
#include <stdint.h> #include <stdint.h>
#include <math.h> #include <math.h>
#include <memory.h> #include <memory.h>
#ifndef NO_QT
#include <QImage> #include <QImage>
#endif
extern int THUMB_SIZE; extern int THUMB_SIZE;
extern int THUMB_SIZE_BORDER; extern int THUMB_SIZE_BORDER;
@@ -83,13 +85,15 @@ public:
RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type); RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
RawImage(const RawImage &d); RawImage(const RawImage &d);
RawImage(RawImage &&d); RawImage(RawImage &&d);
#ifndef NO_QT
RawImage(const QImage &img); RawImage(const QImage &img);
#endif
const RawImage::Stats& imageStats() const; const RawImage::Stats& imageStats() const;
void calcStats(); void calcStats();
uint32_t width() const; uint32_t width() const;
uint32_t height() const; uint32_t height() const;
uint32_t channels() const; uint32_t channels() const;
uint32_t size() const; uint64_t size() const;
DataType type() const; DataType type() const;
uint32_t norm() const; uint32_t norm() const;
uint32_t widthBytes() const; uint32_t widthBytes() const;
@@ -107,6 +111,7 @@ public:
float thumbAspect() const; float thumbAspect() const;
bool pixel(int x, int y, double &r, double &g, double &b) const; bool pixel(int x, int y, double &r, double &g, double &b) const;
void resize(uint32_t w, uint32_t h); void resize(uint32_t w, uint32_t h);
void resizeInt(int downsample, bool avg);
std::pair<float, float> unitScale() const; std::pair<float, float> unitScale() const;
void flip(); void flip();
@@ -116,7 +121,9 @@ public:
static size_t typeSize(DataType type); static size_t typeSize(DataType type);
std::vector<RawImage> split() const; std::vector<RawImage> split() const;
bool valid() const; bool valid() const;
#ifndef NO_QT
void setICCProfile(const QByteArray &icc); void setICCProfile(const QByteArray &icc);
#endif
void setICCProfile(const LibXISF::ByteArray &icc); void setICCProfile(const LibXISF::ByteArray &icc);
void convertTosRGB(); void convertTosRGB();
void generateLUT(); void generateLUT();
+2 -2
View File
@@ -1,7 +1,7 @@
#include "rawimage.h"
#ifdef __SSE2__ #ifdef __SSE2__
#include <x86intrin.h> #include <x86intrin.h>
#include <cstdint>
#include <limits>
template<typename T, int ch> template<typename T, int ch>
void fromPlanarSSE(const void *in, void *out, size_t count) void fromPlanarSSE(const void *in, void *out, size_t count)
+6 -4
View File
@@ -16,14 +16,16 @@
<file>grbg.png</file> <file>grbg.png</file>
<file>gbrg.png</file> <file>gbrg.png</file>
<file>space.nouspiro.tenmon.png</file> <file>space.nouspiro.tenmon.png</file>
</qresource> <file>../translations/tenmon_pt_BR.qm</file>
<qresource lang="en" prefix="/">
<file alias="help">../about/help_en</file> <file alias="help">../about/help_en</file>
</qresource> </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> <file alias="help">../about/help_sk</file>
</qresource> </qresource>
<qresource lang="fr" prefix="/"> <qresource prefix="/" lang="fr">
<file alias="help">../about/help_fr</file> <file alias="help">../about/help_fr</file>
</qresource> </qresource>
</RCC> </RCC>
+64 -8
View File
@@ -3,12 +3,13 @@
#include <QFileInfo> #include <QFileInfo>
#include <QDebug> #include <QDebug>
#include <QInputDialog> #include <QInputDialog>
#include <QJsonValue>
#include "loadrunable.h" #include "loadrunable.h"
#include "rawimage.h" #include "rawimage.h"
#include "loadrunable.h" #include "loadimage.h"
#include "batchprocessing.h" #include "batchprocessing.h"
#include <fitsio2.h> #include <fitsio2.h>
#include "libXISF/libxisf.h" #include "libxisf.h"
#ifdef PLATESOLVER #ifdef PLATESOLVER
#include "solver.h" #include "solver.h"
#endif // PLATESOLVER #endif // PLATESOLVER
@@ -28,6 +29,7 @@ ScriptEngine::ScriptEngine(BatchProcessing *parent)
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject); _jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
_database->init(QLatin1String("scriptengine")); _database->init(QLatin1String("scriptengine"));
_semaphore.release(_pool->maxThreadCount()); _semaphore.release(_pool->maxThreadCount());
_pool->setThreadPriority(QThread::LowPriority);
#ifdef PLATESOLVER #ifdef PLATESOLVER
_solver = new Solver(this); _solver = new Solver(this);
@@ -162,6 +164,58 @@ bool ScriptEngine::convert(File *file, QString &outpath, const QString &format,
} }
#ifdef PLATESOLVER #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) void ScriptEngine::setStartingSolution(const QJSValue &solution)
{ {
if(solution.isObject()) if(solution.isObject())
@@ -465,7 +519,8 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
{ {
fitsfile *file; fitsfile *file;
int status = 0; 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; int num = 0;
fits_get_num_hdus(file, &num, &status); fits_get_num_hdus(file, &num, &status);
if(status) if(status)
@@ -588,9 +643,10 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
try try
{ {
LibXISF::XISFModify modifyXISF; LibXISF::XISFModify modifyXISF;
modifyXISF.open(_path.toLocal8Bit().data()); QString in = makeMaxPath(absoluteFilePath());
QFileInfo in(_path); QString out = in + "~";
QFileInfo out(_path + "~"); modifyXISF.open(in.toLocal8Bit().data());
qDebug() << "modify" << in << out;
for(auto &remove : modify->_remove) for(auto &remove : modify->_remove)
modifyXISF.removeFITSKeyword(0, remove.toStdString()); modifyXISF.removeFITSKeyword(0, remove.toStdString());
@@ -601,9 +657,9 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
for(auto &record : modify->_add) for(auto &record : modify->_add)
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()}); 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(); modifyXISF.close();
std::filesystem::rename(out.filesystemAbsoluteFilePath(), in.filesystemAbsoluteFilePath()); std::filesystem::rename(out.toLocal8Bit().toStdString(), in.toLocal8Bit().toStdString());
return true; return true;
} }
catch(std::filesystem::filesystem_error &err) 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; 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); bool convert(File *file, QString &outpath, const QString &format, const QVariantMap &params, bool async);
#ifdef PLATESOLVER #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()); Q_INVOKABLE void setStartingSolution(const QJSValue &solution = QJSValue());
QJSValue solveImage(File *file, bool updateHeader); QJSValue solveImage(File *file, bool updateHeader);
QJSValue extractStars(File *file, bool hfr); QJSValue extractStars(File *file, bool hfr);
+6 -1
View File
@@ -7,7 +7,7 @@
#include <wcslib/wcshdr.h> #include <wcslib/wcshdr.h>
#include <wcslib/wcsutil.h> #include <wcslib/wcsutil.h>
#include "rawimage.h" #include "rawimage.h"
#include "loadrunable.h" #include "loadimage.h"
#include "scriptengine.h" #include "scriptengine.h"
Solver::Solver(QObject *parent) : QObject(parent) Solver::Solver(QObject *parent) : QObject(parent)
@@ -205,6 +205,11 @@ void Solver::setParameters(const Parameters &parameters)
_solver->setParameters(profile); _solver->setParameters(profile);
} }
Parameters Solver::getProfile() const
{
return _solver->getCurrentParameters();
}
void Solver::setSearchScale(double fovLow, double fowHigh, SSolver::ScaleUnits units) void Solver::setSearchScale(double fovLow, double fowHigh, SSolver::ScaleUnits units)
{ {
_solver->setSearchScale(fovLow, fowHigh, units); _solver->setSearchScale(fovLow, fowHigh, units);
+1
View File
@@ -34,6 +34,7 @@ public:
bool updateHeader(QString &error); bool updateHeader(QString &error);
void setParameters(SSolver::Parameters::ParametersProfile profile); void setParameters(SSolver::Parameters::ParametersProfile profile);
void setParameters(const SSolver::Parameters &parameters); void setParameters(const SSolver::Parameters &parameters);
SSolver::Parameters getProfile() const;
void setSearchScale(double fovLow, double fowHigh, ScaleUnits units); void setSearchScale(double fovLow, double fowHigh, ScaleUnits units);
void setSearchPosition(double ra, double dec); void setSearchPosition(double ra, double dec);
void clearStartingPositionAndScale(); void clearStartingPositionAndScale();
+9
View File
@@ -57,6 +57,15 @@
</screenshots> </screenshots>
<content_rating type="oars-1.1"/> <content_rating type="oars-1.1"/>
<releases> <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"> <release version="20241116" date="2024-11-16">
<description> <description>
<ul> <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 <QDebug>
#include <QToolButton> #include <QToolButton>
#include <QSettings> #include <QSettings>
#include <QStyle>
#include "imageringlist.h" #include "imageringlist.h"
const float BLACK_POINT_SIGMA = -2.8f; const float BLACK_POINT_SIGMA = -2.8f;
+1 -7
View File
@@ -4,16 +4,10 @@
#include <QToolBar> #include <QToolBar>
#include <QStackedWidget> #include <QStackedWidget>
#include "stfslider.h" #include "stfslider.h"
#include "mtfparam.h"
class Image; 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 class StretchToolbar : public QToolBar
{ {
Q_OBJECT 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