Compare commits
9 Commits
20250126
...
d545c6ca0f
| Author | SHA1 | Date | |
|---|---|---|---|
| d545c6ca0f | |||
| 5249b277ec | |||
| e4b9fefa5a | |||
| d069ce3302 | |||
| 58c182adc0 | |||
| c36068aaf4 | |||
| fcb3aec81f | |||
| 7510dac82b | |||
| 55439be04c |
+8
-4
@@ -18,8 +18,6 @@ if(SANITIZE_ADDRESS_LEAK)
|
||||
endif(SANITIZE_ADDRESS_LEAK)
|
||||
|
||||
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
|
||||
find_library(GSL_LIB gsl REQUIRED)
|
||||
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
|
||||
find_library(EXIF_LIB exif REQUIRED)
|
||||
find_library(FITS_LIB cfitsio REQUIRED)
|
||||
find_library(RAW_LIB NAMES raw_r REQUIRED)
|
||||
@@ -39,18 +37,20 @@ set(TENMON_SRC
|
||||
histogram.cpp histogram.h
|
||||
httpdownloader.h httpdownloader.cpp
|
||||
imageinfo.cpp imageinfo.h
|
||||
imageinfodata.cpp imageinfodata.h
|
||||
imageringlist.cpp imageringlist.h
|
||||
imagescrollarea.cpp imagescrollarea.h
|
||||
imagewidget.h imagewidget.cpp
|
||||
loadimage.h loadimage.cpp
|
||||
loadrunable.cpp loadrunable.h
|
||||
main.cpp
|
||||
mainwindow.cpp mainwindow.h
|
||||
markedfiles.cpp markedfiles.h
|
||||
mtfparam.h
|
||||
rawimage.cpp rawimage.h
|
||||
rawimage_sse.cpp
|
||||
scriptengine.cpp scriptengine.h
|
||||
settingsdialog.cpp settingsdialog.h
|
||||
starfit.cpp starfit.h
|
||||
statusbar.cpp statusbar.h
|
||||
stfslider.cpp stfslider.h
|
||||
stretchtoolbar.cpp stretchtoolbar.h
|
||||
@@ -85,6 +85,8 @@ find_path(STELLARSOLVER_INCLUDE stellarsolver.h PATH_SUFFIXES libstellarsolver)
|
||||
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
|
||||
if(MXE)
|
||||
find_library(GSL_LIB gsl REQUIRED)
|
||||
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
|
||||
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB} ${GSL_LIB} ${GSLCBLAS_LIB} boost_regex-mt-x64)
|
||||
else(MXE)
|
||||
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB})
|
||||
@@ -98,7 +100,7 @@ if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
|
||||
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||
|
||||
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
||||
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
||||
if(APPLE)
|
||||
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
||||
elseif(UNIX)
|
||||
@@ -138,3 +140,5 @@ else()
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -Dlocal_dir=${CMAKE_CURRENT_SOURCE_DIR} -Doutput_dir=${CMAKE_CURRENT_BINARY_DIR}
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake")
|
||||
endif()
|
||||
|
||||
add_subdirectory(thumbnailer)
|
||||
|
||||
@@ -2,11 +2,11 @@ FITS/XISF image viewer with multithreaded image loading
|
||||
|
||||
To get all dependencies install these packages
|
||||
|
||||
sudo apt install qt6-base-dev qt6-declarative-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
|
||||
sudo apt install qt6-base-dev qt6-declarative-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
|
||||
|
||||
on OpenSUSE
|
||||
|
||||
sudo zypper install gsl-devel libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
|
||||
sudo zypper install libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
|
||||
|
||||
MacOS X
|
||||
|
||||
|
||||
@@ -33,9 +33,6 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
|
||||
setWindowTitle(tr("Help"));
|
||||
resize(800, 600);
|
||||
|
||||
QLocale locale;
|
||||
QString l = QLocale::languageToString(locale.language());
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
QTextEdit *helpText = new QTextEdit(this);
|
||||
helpText->setReadOnly(true);
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
#include <QSqlError>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include "loadrunable.h"
|
||||
#include "loadimage.h"
|
||||
|
||||
Database::Database(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
#include <QStyleOption>
|
||||
|
||||
Histogram::Histogram(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
|
||||
-402
@@ -1,55 +1,6 @@
|
||||
#include "imageinfo.h"
|
||||
#include <QSettings>
|
||||
#include <QTime>
|
||||
#include <QHeaderView>
|
||||
#include <wcslib/wcshdr.h>
|
||||
#include <wcslib/wcsfix.h>
|
||||
#include <libxisf.h>
|
||||
|
||||
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
|
||||
|
||||
bool FITSRecord::editable() const
|
||||
{
|
||||
return noEditableKey.count(key);
|
||||
}
|
||||
|
||||
FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment) :
|
||||
key(key), value(value), comment(comment)
|
||||
{
|
||||
}
|
||||
|
||||
FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
|
||||
{
|
||||
key = record.name.c_str();
|
||||
comment = record.comment.c_str();
|
||||
|
||||
QString string = record.value.c_str();
|
||||
if(string.startsWith('\'') && string.endsWith('\''))
|
||||
{
|
||||
string.chop(1);
|
||||
string.remove(0, 1);
|
||||
}
|
||||
bool isint;
|
||||
bool isdouble;
|
||||
double vald = string.toDouble(&isdouble);
|
||||
long long vall = string.toLongLong(&isint);
|
||||
if(isint)
|
||||
value = vall;
|
||||
else if(isdouble)
|
||||
value = vald;
|
||||
else if(string == "T" || string == "F")
|
||||
value = string == "T";
|
||||
else
|
||||
value = string;
|
||||
}
|
||||
|
||||
FITSRecord::FITSRecord(const LibXISF::Property &property)
|
||||
{
|
||||
key = property.id.c_str();
|
||||
value = QString::fromStdString(property.value.toString());
|
||||
comment = property.comment.c_str();
|
||||
xisf = true;
|
||||
}
|
||||
|
||||
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
|
||||
{
|
||||
@@ -89,356 +40,3 @@ void ImageInfo::setInfo(const ImageInfoData &info)
|
||||
}
|
||||
expandAll();
|
||||
}
|
||||
|
||||
void WCSDataT::freeWCS()
|
||||
{
|
||||
wcsvfree(&nwcs, &wcs);
|
||||
nwcs = 0;
|
||||
wcs = nullptr;
|
||||
}
|
||||
|
||||
WCSDataT::WCSDataT(int width, int height, char *header, int nrec) :
|
||||
width(width),
|
||||
height(height)
|
||||
{
|
||||
int nreject = 0;
|
||||
int status = wcspih(header, nrec, 1, 0, &nreject, &nwcs, &wcs);
|
||||
if(status != 0)
|
||||
{
|
||||
freeWCS();
|
||||
return;
|
||||
}
|
||||
status = cdfix(wcs);
|
||||
if(status > 0 || wcs->crpix[0] == 0)
|
||||
freeWCS();
|
||||
}
|
||||
|
||||
WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
|
||||
width(width),
|
||||
height(height)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
QByteArray str;
|
||||
int nrec = 1;
|
||||
for(const FITSRecord &record : header)
|
||||
{
|
||||
if(record.key.startsWith("PV"))continue;
|
||||
|
||||
QByteArray rec;
|
||||
rec.append(record.key.leftJustified(8, ' '));
|
||||
rec.append("= ");
|
||||
rec.append(record.value.toString().toLatin1());
|
||||
rec.append(" / ");
|
||||
rec.append(record.comment);
|
||||
str.append(rec.leftJustified(80, ' ', true));
|
||||
nrec++;
|
||||
}
|
||||
str.append(QByteArray("END").leftJustified(80));
|
||||
|
||||
int nreject = 0;
|
||||
status = wcspih(str.data(), nrec, 1, 0, &nreject, &nwcs, &wcs);
|
||||
if(status != 0)
|
||||
{
|
||||
freeWCS();
|
||||
return;
|
||||
}
|
||||
status = cdfix(wcs);
|
||||
if(status > 0 || wcs->crpix[0] == 0)
|
||||
freeWCS();
|
||||
}
|
||||
|
||||
WCSDataT::~WCSDataT()
|
||||
{
|
||||
if(wcs)
|
||||
freeWCS();
|
||||
}
|
||||
|
||||
bool WCSDataT::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
|
||||
{
|
||||
if(!valid())return false;
|
||||
|
||||
double pixcrd[2] = {pixel.x(), pixel.y()};
|
||||
double imgcrd[8] = {0};
|
||||
double phi = 0;
|
||||
double theta = 0;
|
||||
double world[8] = {0};
|
||||
int stat[NWCSFIX] = {0};
|
||||
int status = wcsp2s(wcs, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat);
|
||||
if(status == 0)
|
||||
{
|
||||
point = SkyPoint(world[0], world[1]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
|
||||
{
|
||||
if(!valid())return false;
|
||||
|
||||
double world[2] = {point.RA(), point.DEC()};
|
||||
double phi = 0;
|
||||
double theta = 0;
|
||||
double imgcrd[8] = {0};
|
||||
double pixcrd[8] = {0};
|
||||
int stat[NWCSFIX] = {0};
|
||||
int status = wcss2p(wcs, 1, 2, world, &phi, &theta, imgcrd, pixcrd, stat);
|
||||
if(status == 0)
|
||||
{
|
||||
pixel = QPointF(pixcrd[0], pixcrd[1]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
|
||||
{
|
||||
if(wcs == nullptr)return;
|
||||
|
||||
minRa = 1000;
|
||||
maxRa = -1000;
|
||||
minDec = 1000;
|
||||
maxDec = -1000;
|
||||
|
||||
if(wcs->crval)
|
||||
{
|
||||
crVal1 = wcs->crval[0];
|
||||
crVal2 = wcs->crval[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
crVal1 = crVal2 = NAN;
|
||||
}
|
||||
|
||||
auto update = [&](const QPointF &pixel)
|
||||
{
|
||||
SkyPoint point;
|
||||
pixelToWorld(pixel, point);
|
||||
minRa = std::min(minRa, point.RA());
|
||||
maxRa = std::max(maxRa, point.RA());
|
||||
minDec = std::min(minDec, point.DEC());
|
||||
maxDec = std::max(maxDec, point.DEC());
|
||||
};
|
||||
|
||||
for(int x=0; x<width; x++)
|
||||
{
|
||||
update(QPointF(x, 0));
|
||||
update(QPointF(x, height - 1));
|
||||
}
|
||||
|
||||
for(int y=0; y<height; y++)
|
||||
{
|
||||
update(QPointF(0, y));
|
||||
update(QPointF(width - 1, y));
|
||||
}
|
||||
|
||||
QPointF ncp;
|
||||
QPointF scp;
|
||||
QRectF s(0, 0, width - 1, height - 1);
|
||||
if(worldToPixel(SkyPoint(0, 90), ncp))
|
||||
{
|
||||
if(s.contains(ncp))
|
||||
maxDec = 90;
|
||||
}
|
||||
|
||||
if(worldToPixel(SkyPoint(0, -90), scp))
|
||||
{
|
||||
if(s.contains(scp))
|
||||
minDec = -90;
|
||||
}
|
||||
}
|
||||
|
||||
double hav(double x)
|
||||
{
|
||||
return (1.0 - std::cos(x)) * 0.5;
|
||||
}
|
||||
|
||||
double haverSine(const SkyPoint &a, SkyPoint &b)
|
||||
{
|
||||
const double ToRAD = M_PI / 180.0;
|
||||
double d = hav((a.DEC() - b.DEC()) * ToRAD) + std::cos(a.DEC() * ToRAD) * std::cos(b.DEC() * ToRAD) * hav((a.RA() - b.RA()) * ToRAD);
|
||||
return std::acos(1.0 - 2.0 * d) * (180.0 / M_PI);
|
||||
}
|
||||
|
||||
SkyPointScale WCSDataT::getRaDecScale() const
|
||||
{
|
||||
SkyPointScale ret;
|
||||
pixelToWorld(QPointF(width/2.0, height/2.0), ret.point);
|
||||
SkyPoint pointX;
|
||||
SkyPoint pointY;
|
||||
pixelToWorld(QPointF(width/2.0+1, height/2.0), pointX);
|
||||
pixelToWorld(QPointF(width/2.0, height/2.0+1), pointY);
|
||||
double scaleX = haverSine(ret.point, pointX) * 3600.0;
|
||||
double scaleY = haverSine(ret.point, pointY) * 3600.0;
|
||||
ret.scaleLow = std::min(scaleX, scaleY);
|
||||
ret.scaleHigh = std::max(scaleX, scaleY);
|
||||
ret.scaleValid = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SkyPoint::SkyPoint() : ra(NAN), dec(NAN)
|
||||
{
|
||||
}
|
||||
|
||||
SkyPoint::SkyPoint(double ra, double dec) : ra(ra), dec(dec)
|
||||
{
|
||||
}
|
||||
|
||||
void SkyPoint::set(double ra, double dec)
|
||||
{
|
||||
this->ra = ra;
|
||||
this->dec = dec;
|
||||
}
|
||||
|
||||
QString SkyPoint::toString() const
|
||||
{
|
||||
if(std::isnan(ra) || std::isnan(dec))
|
||||
return QString();
|
||||
|
||||
QTime t(0, 0);
|
||||
t = t.addSecs(ra * 240);
|
||||
|
||||
double deg, min, sec;
|
||||
min = std::abs(std::modf(dec, °) * 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
|
||||
|
||||
#include <QTreeWidget>
|
||||
#include <wcslib/wcs.h>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
namespace LibXISF { struct FITSKeyword; struct Property; }
|
||||
|
||||
struct FITSRecord
|
||||
{
|
||||
QByteArray key;
|
||||
QVariant value;
|
||||
QByteArray comment;
|
||||
bool xisf = false;
|
||||
bool editable() const;
|
||||
FITSRecord(){}
|
||||
FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment);
|
||||
FITSRecord(const LibXISF::FITSKeyword &record);
|
||||
FITSRecord(const LibXISF::Property &property);
|
||||
};
|
||||
|
||||
class SkyPoint
|
||||
{
|
||||
double ra = NAN;
|
||||
double dec = NAN;
|
||||
public:
|
||||
SkyPoint();
|
||||
SkyPoint(double ra, double dec);
|
||||
void set(double ra, double dec);
|
||||
double RA() const { return ra; }
|
||||
double RAHour() const { return ra / 15.0; }
|
||||
double DEC() const { return dec; }
|
||||
QString toString() const;
|
||||
static double fromHMS(const QString &hms);
|
||||
static double fromDMS(const QString &dms);
|
||||
static QString toHMS(double decHour);
|
||||
static QString toDMS(double deg);
|
||||
};
|
||||
|
||||
struct SkyPointScale
|
||||
{
|
||||
SkyPoint point;
|
||||
//arcsec per pixel
|
||||
bool scaleValid = false;
|
||||
double scaleLow = 0.0;
|
||||
double scaleHigh = 10000.0;
|
||||
};
|
||||
|
||||
class WCSDataT
|
||||
{
|
||||
int nwcs = 0;
|
||||
struct wcsprm *wcs = nullptr;
|
||||
int width;
|
||||
int height;
|
||||
void freeWCS();
|
||||
public:
|
||||
WCSDataT(int width, int height, char *header, int nrec);
|
||||
WCSDataT(int width, int height, const QVector<FITSRecord> &header);
|
||||
WCSDataT(const WCSDataT &) = delete;
|
||||
~WCSDataT();
|
||||
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
|
||||
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
|
||||
void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
|
||||
bool valid() const { return wcs; };
|
||||
SkyPointScale getRaDecScale() const;
|
||||
};
|
||||
|
||||
struct ImageInfoData
|
||||
{
|
||||
QVector<FITSRecord> fitsHeader;
|
||||
QVector<QPair<QString, QString>> info;
|
||||
std::shared_ptr<WCSDataT> wcs;
|
||||
SkyPointScale getCenterRaDec() const;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ImageInfoData);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
None,
|
||||
Statistics,
|
||||
Peaks,
|
||||
Stars,
|
||||
}AnalyzeLevel;
|
||||
#include "imageinfodata.h"
|
||||
|
||||
class ImageInfo : public QTreeWidget
|
||||
{
|
||||
|
||||
@@ -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 <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QRegularExpression>
|
||||
#include "loadrunable.h"
|
||||
#include "rawimage.h"
|
||||
#include "database.h"
|
||||
|
||||
+2
-1
@@ -7,8 +7,9 @@
|
||||
#include <QPixmap>
|
||||
#include <QDir>
|
||||
#include <memory>
|
||||
#include "imageinfo.h"
|
||||
#include "imageinfodata.h"
|
||||
#include "rawimage.h"
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
class ImageRingList;
|
||||
class QThreadPool;
|
||||
|
||||
+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)
|
||||
{
|
||||
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]);
|
||||
};
|
||||
|
||||
|
||||
+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 "imageringlist.h"
|
||||
#include <libraw/libraw.h>
|
||||
#include "imageinfo.h"
|
||||
#include <QFileInfo>
|
||||
#include <QPainter>
|
||||
#include <QElapsedTimer>
|
||||
#include <QDebug>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <libexif/exif-data.h>
|
||||
#include <fitsio2.h>
|
||||
#include <libxisf.h>
|
||||
#include "rawimage.h"
|
||||
#include "starfit.h"
|
||||
#include "loadimage.h"
|
||||
#include <lcms2.h>
|
||||
|
||||
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) :
|
||||
m_file(makeMaxPath(file)),
|
||||
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()
|
||||
{
|
||||
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) :
|
||||
m_infile(makeMaxPath(in)),
|
||||
m_outfile(makeMaxPath(out)),
|
||||
@@ -640,6 +191,17 @@ void ConvertRunable::run()
|
||||
QFileInfo info(m_outfile);
|
||||
info.dir().mkpath(".");
|
||||
|
||||
if(m_params.binning > 1)
|
||||
{
|
||||
rawimage->resizeInt(m_params.binning, m_params.average);
|
||||
}
|
||||
else if(m_params.resize.isValid() && !m_params.resize.isEmpty())
|
||||
{
|
||||
QSize imgSize(rawimage->width(), rawimage->height());
|
||||
imgSize = imgSize.scaled(m_params.resize, m_params.aspect);
|
||||
rawimage->resize(imgSize.width(), imgSize.height());
|
||||
}
|
||||
|
||||
if(rawimage)
|
||||
{
|
||||
if(m_format == "xisf")
|
||||
@@ -772,4 +334,33 @@ ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map)
|
||||
|
||||
if(map.contains("compressionType"))
|
||||
compressionType = map["compressionType"].toString();
|
||||
|
||||
if(map.contains("binning"))
|
||||
binning = map["binning"].toInt();
|
||||
|
||||
if(map.contains("average"))
|
||||
average = map["average"].toBool();
|
||||
|
||||
if(map.contains("resize"))
|
||||
{
|
||||
QVariantMap size = map["resize"].toMap();
|
||||
if(size.contains("width") && size.contains("height"))
|
||||
{
|
||||
int w = size["width"].toInt();
|
||||
int h = size["height"].toInt();
|
||||
resize = QSize(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
if(map.contains("aspect"))
|
||||
{
|
||||
QString aspectStr = map["aspect"].toString();
|
||||
if(aspectStr == "keep")
|
||||
aspect = Qt::KeepAspectRatio;
|
||||
else if(aspectStr == "expand")
|
||||
aspect = Qt::KeepAspectRatioByExpanding;
|
||||
else if(aspectStr == "ignore")
|
||||
aspect = Qt::IgnoreAspectRatio;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-9
@@ -4,14 +4,8 @@
|
||||
#include <QRunnable>
|
||||
#include <QString>
|
||||
#include <QSemaphore>
|
||||
#include "imageinfo.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);
|
||||
#include <QSize>
|
||||
#include "imageinfodata.h"
|
||||
|
||||
class Image;
|
||||
|
||||
@@ -26,7 +20,6 @@ public:
|
||||
void run() override;
|
||||
};
|
||||
|
||||
|
||||
class ConvertRunable : public QRunnable
|
||||
{
|
||||
public:
|
||||
@@ -34,6 +27,10 @@ public:
|
||||
{
|
||||
int compressionLevel = -1;
|
||||
QString compressionType;
|
||||
int binning = 0;
|
||||
bool average = true;
|
||||
QSize resize;
|
||||
Qt::AspectRatioMode aspect = Qt::KeepAspectRatio;
|
||||
ConvertParams(){}
|
||||
ConvertParams(const QVariantMap &map);
|
||||
};
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
|
||||
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut);
|
||||
viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit);
|
||||
viewMenu->addAction(tr("100%"), m_image, &ImageScrollArea::oneToOne);
|
||||
viewMenu->addAction(tr("100%"), QKeySequence("Ctrl+0"), m_image, &ImageScrollArea::oneToOne);
|
||||
viewMenu->addSeparator();
|
||||
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
|
||||
QActionGroup *bayerActionGroup = new QActionGroup(this);
|
||||
|
||||
+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 <QDebug>
|
||||
#include <cstring>
|
||||
#include <lcms2.h>
|
||||
#ifndef NO_QT
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFloat16>
|
||||
#include <QColorSpace>
|
||||
#include <lcms2.h>
|
||||
|
||||
using F16 = qfloat16;
|
||||
#else
|
||||
#include <algorithm>
|
||||
using F16 = _Float16;
|
||||
#endif
|
||||
|
||||
int THUMB_SIZE = 128;
|
||||
int THUMB_SIZE_BORDER = 138;
|
||||
@@ -78,6 +82,7 @@ RawImage::RawImage(RawImage &&d)
|
||||
m_thumbAspect = d.m_thumbAspect;
|
||||
}
|
||||
|
||||
#ifndef NO_QT
|
||||
RawImage::RawImage(const QImage &img)
|
||||
{
|
||||
qDebug() << img;
|
||||
@@ -145,6 +150,7 @@ RawImage::RawImage(const QImage &img)
|
||||
}
|
||||
m_stats.m_stats = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
const RawImage::Stats& RawImage::imageStats() const
|
||||
{
|
||||
@@ -439,7 +445,9 @@ void RawImage::convertToThumbnail()
|
||||
loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
|
||||
break;
|
||||
default:
|
||||
#ifndef NO_QT
|
||||
qWarning() << "FLOAT64 should not happend";
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#ifndef NO_QT
|
||||
void RawImage::setICCProfile(const QByteArray &icc)
|
||||
{
|
||||
if(icc.size())
|
||||
m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
|
||||
}
|
||||
#endif
|
||||
|
||||
void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
|
||||
{
|
||||
@@ -973,12 +1041,12 @@ void RawImage::convertTosRGB()
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to create color transform";
|
||||
//qDebug() << "Failed to create color transform";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to open icc profile";
|
||||
//qDebug() << "Failed to open icc profile";
|
||||
}
|
||||
|
||||
cmsCloseProfile(inProfile);
|
||||
@@ -1022,13 +1090,13 @@ void RawImage::generateLUT()
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to create color transform";
|
||||
//qDebug() << "Failed to create color transform";
|
||||
m_lut.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to open icc profile";
|
||||
//qDebug() << "Failed to open icc profile";
|
||||
m_lut.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <memory.h>
|
||||
#ifndef NO_QT
|
||||
#include <QImage>
|
||||
#endif
|
||||
|
||||
extern int THUMB_SIZE;
|
||||
extern int THUMB_SIZE_BORDER;
|
||||
@@ -83,7 +85,9 @@ public:
|
||||
RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
|
||||
RawImage(const RawImage &d);
|
||||
RawImage(RawImage &&d);
|
||||
#ifndef NO_QT
|
||||
RawImage(const QImage &img);
|
||||
#endif
|
||||
const RawImage::Stats& imageStats() const;
|
||||
void calcStats();
|
||||
uint32_t width() const;
|
||||
@@ -107,6 +111,7 @@ public:
|
||||
float thumbAspect() const;
|
||||
bool pixel(int x, int y, double &r, double &g, double &b) const;
|
||||
void resize(uint32_t w, uint32_t h);
|
||||
void resizeInt(int downsample, bool avg);
|
||||
std::pair<float, float> unitScale() const;
|
||||
void flip();
|
||||
|
||||
@@ -116,7 +121,9 @@ public:
|
||||
static size_t typeSize(DataType type);
|
||||
std::vector<RawImage> split() const;
|
||||
bool valid() const;
|
||||
#ifndef NO_QT
|
||||
void setICCProfile(const QByteArray &icc);
|
||||
#endif
|
||||
void setICCProfile(const LibXISF::ByteArray &icc);
|
||||
void convertTosRGB();
|
||||
void generateLUT();
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#include "rawimage.h"
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <x86intrin.h>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
template<typename T, int ch>
|
||||
void fromPlanarSSE(const void *in, void *out, size_t count)
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
<file>grbg.png</file>
|
||||
<file>gbrg.png</file>
|
||||
<file>space.nouspiro.tenmon.png</file>
|
||||
</qresource>
|
||||
<qresource lang="en" prefix="/">
|
||||
<file>../translations/tenmon_pt_BR.qm</file>
|
||||
<file alias="help">../about/help_en</file>
|
||||
</qresource>
|
||||
<qresource lang="sk" prefix="/">
|
||||
<qresource prefix="/" lang="en">
|
||||
<file alias="help">../about/help_en</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="sk">
|
||||
<file alias="help">../about/help_sk</file>
|
||||
</qresource>
|
||||
<qresource lang="fr" prefix="/">
|
||||
<qresource prefix="/" lang="fr">
|
||||
<file alias="help">../about/help_fr</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
+2
-2
@@ -6,10 +6,10 @@
|
||||
#include <QJsonValue>
|
||||
#include "loadrunable.h"
|
||||
#include "rawimage.h"
|
||||
#include "loadrunable.h"
|
||||
#include "loadimage.h"
|
||||
#include "batchprocessing.h"
|
||||
#include <fitsio2.h>
|
||||
#include "libXISF/libxisf.h"
|
||||
#include "libxisf.h"
|
||||
#ifdef PLATESOLVER
|
||||
#include "solver.h"
|
||||
#endif // PLATESOLVER
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
#include <wcslib/wcshdr.h>
|
||||
#include <wcslib/wcsutil.h>
|
||||
#include "rawimage.h"
|
||||
#include "loadrunable.h"
|
||||
#include "loadimage.h"
|
||||
#include "scriptengine.h"
|
||||
|
||||
Solver::Solver(QObject *parent) : QObject(parent)
|
||||
|
||||
-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 <QToolButton>
|
||||
#include <QSettings>
|
||||
#include <QStyle>
|
||||
#include "imageringlist.h"
|
||||
|
||||
const float BLACK_POINT_SIGMA = -2.8f;
|
||||
|
||||
+1
-7
@@ -4,16 +4,10 @@
|
||||
#include <QToolBar>
|
||||
#include <QStackedWidget>
|
||||
#include "stfslider.h"
|
||||
#include "mtfparam.h"
|
||||
|
||||
class Image;
|
||||
|
||||
struct MTFParam
|
||||
{
|
||||
float blackPoint[3] = {0.0f, 0.0f, 0.0f};
|
||||
float midPoint[3] = {0.5f, 0.5f, 0.5f};
|
||||
float whitePoint[3] = {1.0f, 1.0f, 1.0f};
|
||||
};
|
||||
|
||||
class StretchToolbar : public QToolBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -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