Compare commits
8 Commits
20250126
...
5249b277ec
| Author | SHA1 | Date | |
|---|---|---|---|
| 5249b277ec | |||
| e4b9fefa5a | |||
| d069ce3302 | |||
| 58c182adc0 | |||
| c36068aaf4 | |||
| fcb3aec81f | |||
| 7510dac82b | |||
| 55439be04c |
+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);
|
||||||
|
|||||||
+1
-1
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
-402
@@ -1,55 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
|
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
|
||||||
{
|
{
|
||||||
@@ -89,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
-82
@@ -2,88 +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);
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
||||||
@@ -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"
|
||||||
|
|||||||
+2
-1
@@ -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;
|
||||||
|
|||||||
+1
-1
@@ -359,7 +359,7 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
|
|||||||
auto mtf = [&mtfParams](int i, float x)
|
auto mtf = [&mtfParams](int i, float x)
|
||||||
{
|
{
|
||||||
x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
|
x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
|
||||||
x = std::min(std::max(x, 0.0f), 1.0f);
|
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]);
|
return ((mtfParams.midPoint[i] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[i] - 1.0f) * x - mtfParams.midPoint[i]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+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
|
||||||
+41
-450
@@ -1,35 +1,15 @@
|
|||||||
#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>
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
|
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
|
||||||
m_file(makeMaxPath(file)),
|
m_file(makeMaxPath(file)),
|
||||||
m_receiver(receiver),
|
m_receiver(receiver),
|
||||||
@@ -38,349 +18,6 @@ LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel leve
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -462,92 +99,6 @@ void LoadRunable::run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(makeMaxPath(in)),
|
m_infile(makeMaxPath(in)),
|
||||||
m_outfile(makeMaxPath(out)),
|
m_outfile(makeMaxPath(out)),
|
||||||
@@ -640,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")
|
||||||
@@ -772,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
-9
@@ -4,14 +4,8 @@
|
|||||||
#include <QRunnable>
|
#include <QRunnable>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QSemaphore>
|
#include <QSemaphore>
|
||||||
#include "imageinfo.h"
|
#include <QSize>
|
||||||
|
#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);
|
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
|
|
||||||
@@ -26,7 +20,6 @@ public:
|
|||||||
void run() override;
|
void run() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ConvertRunable : public QRunnable
|
class ConvertRunable : public QRunnable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -34,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);
|
||||||
|
|||||||
+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
|
||||||
+75
-7
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -145,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
|
||||||
{
|
{
|
||||||
@@ -439,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,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);
|
||||||
@@ -922,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)
|
||||||
{
|
{
|
||||||
@@ -973,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);
|
||||||
@@ -1022,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +85,9 @@ 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;
|
||||||
@@ -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>
|
||||||
|
|||||||
+2
-2
@@ -6,10 +6,10 @@
|
|||||||
#include <QJsonValue>
|
#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
|
||||||
|
|||||||
+1
-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)
|
||||||
|
|||||||
-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