Compare commits
18 Commits
20241116
...
5249b277ec
| Author | SHA1 | Date | |
|---|---|---|---|
| 5249b277ec | |||
| e4b9fefa5a | |||
| d069ce3302 | |||
| 58c182adc0 | |||
| c36068aaf4 | |||
| fcb3aec81f | |||
| 7510dac82b | |||
| 55439be04c | |||
| 0ff2001797 | |||
| fc36024eee | |||
| 3cda53f26c | |||
| 58d18cc28a | |||
| 2b96da60de | |||
| 236f66ed2f | |||
| a86c100e69 | |||
| 45ee9b7258 | |||
| be1e65251d | |||
| 9b7837e9fb |
+8
-4
@@ -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,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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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++)
|
||||||
|
|||||||
@@ -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
@@ -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, °) * 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
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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, °) * 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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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 ¤tFile, 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 ¤tFile)
|
void ImageRingList::setFiles(const QStringList files, const QString ¤tFile)
|
||||||
{
|
{
|
||||||
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
@@ -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
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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 ¶ms, QSemaphore *semaphore) :
|
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams ¶ms, 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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 ¶ms, bool async);
|
bool convert(File *file, QString &outpath, const QString &format, const QVariantMap ¶ms, 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
@@ -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 ¶meters)
|
|||||||
_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);
|
||||||
|
|||||||
@@ -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 ¶meters);
|
void setParameters(const SSolver::Parameters ¶meters);
|
||||||
|
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();
|
||||||
|
|||||||
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
bool OpenGLES = false;
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user