Compare commits

..

15 Commits

Author SHA1 Message Date
nou f9f005e7ea Another test 2025-07-16 23:17:22 +02:00
nou 5ba6b4863c Fix metainfo linter 2025-07-16 09:07:08 +02:00
nou 6c7e078340 Testing filetree crash on ARM 2025-07-16 07:44:19 +02:00
nou b58559a18a Update modify FITS header script 2025-07-13 10:43:15 +02:00
nou 2ac14a6c04 Fix thumbnailer compilation 2025-07-13 10:42:59 +02:00
nou b84256625c Add stellarsolver6 as name 2025-06-12 16:48:09 +02:00
nou 202a2b11b7 Add marked files in batch processing 2025-06-09 19:09:50 +02:00
nou 32f192ed7e Add draw grid button 2025-05-31 00:19:15 +02:00
nou a0422683bd Move source files to src directory 2025-05-30 16:49:33 +02:00
nou ce67b35bfa Mention OpenNGC 2025-05-29 23:44:55 +02:00
nou f016500f12 Include ngc db 2025-05-29 17:39:13 +02:00
nou 6069ebbbac Refractor drawing grid 2025-05-27 16:26:03 +02:00
nou e587d84e05 Remove that empty action 2025-05-26 17:05:19 +02:00
nou c01f2e328a Add sky grid painting 2025-05-26 15:50:37 +02:00
nou 8b498bbe73 Prefer writing keyword as integer 2025-05-24 23:13:14 +02:00
72 changed files with 684 additions and 72 deletions
+32 -32
View File
@@ -23,39 +23,39 @@ find_library(FITS_LIB cfitsio REQUIRED)
find_library(RAW_LIB NAMES raw_r REQUIRED)
find_library(WCS_LIB wcs wcslib REQUIRED)
find_library(LCMS2_LIB lcms2 REQUIRED)
find_library(STELLARSOLVER_LIB stellarsolver)
find_library(STELLARSOLVER_LIB NAMES stellarsolver stellarsolver6)
add_subdirectory(libXISF)
set(TENMON_SRC
about.cpp about.h
batchprocessing.cpp batchprocessing.h batchprocessing.ui
chartgraph.h chartgraph.cpp
database.cpp database.h
databaseview.cpp databaseview.h
delete.cpp
filesystemwidget.cpp filesystemwidget.h
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
statusbar.cpp statusbar.h
stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h
tfloat16.h
src/about.cpp src/about.h
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
src/chartgraph.h src/chartgraph.cpp
src/database.cpp src/database.h
src/databaseview.cpp src/databaseview.h
src/delete.cpp
src/filesystemwidget.cpp src/filesystemwidget.h
src/histogram.cpp src/histogram.h
src/httpdownloader.h src/httpdownloader.cpp
src/imageinfo.cpp src/imageinfo.h
src/imageinfodata.cpp src/imageinfodata.h
src/imageringlist.cpp src/imageringlist.h
src/imagescrollarea.cpp src/imagescrollarea.h
src/imagewidget.h src/imagewidget.cpp
src/loadimage.h src/loadimage.cpp
src/loadrunable.cpp src/loadrunable.h
src/main.cpp
src/mainwindow.cpp src/mainwindow.h
src/markedfiles.cpp src/markedfiles.h
src/mtfparam.h
src/rawimage.cpp src/rawimage.h
src/rawimage_sse.cpp
src/scriptengine.cpp src/scriptengine.h
src/settingsdialog.cpp src/settingsdialog.h
src/statusbar.cpp src/statusbar.h
src/stfslider.cpp src/stfslider.h
src/stretchtoolbar.cpp src/stretchtoolbar.h
src/tfloat16.h
thumbnailer/genthumbnail.cpp thumbnailer/genthumbnail.h
)
@@ -96,9 +96,9 @@ if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
endif(MXE)
target_compile_definitions(tenmon PRIVATE "PLATESOLVER")
target_sources(tenmon PRIVATE
solver.cpp solver.h
platesolving.cpp platesolving.h platesolving.ui
platesolvingsettings.cpp platesolvingsettings.h platesolvingsettings.ui
src/solver.cpp src/solver.h
src/platesolving.cpp src/platesolving.h src/platesolving.ui
src/platesolvingsettings.cpp src/platesolvingsettings.h src/platesolvingsettings.ui
)
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
+2
View File
@@ -29,3 +29,5 @@ Then to build run standard cmake sequence
For working plate solving you must have compiled and installed StellarSolver https://github.com/rlancaste/stellarsolver
It is important that you compile StellarSolver with Qt6. By default it use Qt5 but when linked with Qt6 program it will
crash.
Using OpenNGC database https://github.com/mattiaverga/OpenNGC under CC-BY-SA-4.0 https://creativecommons.org/licenses/by-sa/4.0/
+141
View File
@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.699999 12.699999"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="grid.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="9.5144352"
inkscape:cx="39.361243"
inkscape:cy="25.067174"
inkscape:window-width="2510"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="skeletal"
id="path-effect636"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect632"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect628"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="bspline"
id="path-effect624"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="spiro"
id="path-effect620"
is_visible="true"
lpeversion="1" />
</defs>
<g
inkscape:label="Vrstva 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
d="M -5,-5 13,13"
id="path616"
sodipodi:nodetypes="cc" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
id="path643"
cx="-4.9824347"
cy="-4.9865055"
r="12.973718" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1665"
cx="-4.9600825"
cy="-4.9741392"
r="17.086035" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1667"
cx="-5.0079365"
cy="-5.0034046"
r="21.147657" />
<path
style="fill:none;stroke:#000000;stroke-width:0.467;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
d="M 14.371451,3.5622727 -4.9904999,-5.0054782 4.2432806,13.903978"
id="path1734" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1736"
cx="-5.155458"
cy="-5.1256938"
r="9.6808758" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
View File
Binary file not shown.
+5 -3
View File
@@ -19,14 +19,16 @@
<file>../translations/tenmon_pt_BR.qm</file>
<file alias="help">../about/help_en</file>
<file>colormap.png</file>
<file>ngc.db</file>
<file>grid.svg</file>
</qresource>
<qresource prefix="/" lang="en">
<qresource lang="en" prefix="/">
<file alias="help">../about/help_en</file>
</qresource>
<qresource prefix="/" lang="sk">
<qresource lang="sk" prefix="/">
<file alias="help">../about/help_sk</file>
</qresource>
<qresource prefix="/" lang="fr">
<qresource lang="fr" prefix="/">
<file alias="help">../about/help_fr</file>
</qresource>
</RCC>
+35 -2
View File
@@ -12,10 +12,12 @@ if(files.length == 0)
throw "";
}
let action = core.getItem(["UPDATE", "ADD", "REMOVE"], "Do you want update, add or remove record?");
let action = core.getItem(["UPDATE", "UPDATE_ADD", "ADD", "REMOVE"], "Do you want update, add or remove record?");
let modify = new FITSRecordModify();
let proceed = false;
if(action == "UPDATE")
{
let keywords = files[0].fitsKeywords().filter(checkFITS);
@@ -25,28 +27,59 @@ if(action == "UPDATE")
value = core.getString("Enter new value", value);
else
value = core.getFloat("Enter new value", value);
if(keyword && value)
{
proceed = true;
modify.updateKeyword(keyword, value);
}
}
else if(action == "UPDATE_ADD")
{
let keyword = core.getString("Enter keyword to update");
let value = core.getString("Enter new value");
if(keyword && value)
{
proceed = true;
keyword = keyword.toUpperCase();
modify.updateKeyword(keyword, value);
}
}
else if(action == "ADD")
{
let keyword = core.getString("Enter keyword to add");
let value = core.getString("Enter new value");
if(keyword && value)
{
proceed = true;
keyword = keyword.toUpperCase();
modify.addKeyword(keyword, value);
}
}
else if(action == "REMOVE")
{
let keywords = files[0].fitsKeywords().filter(checkFITS);
let keyword = core.getItem(keywords, "Select keyword to remove");
if(keyword)
{
proceed = true;
modify.removeKeyword(keyword);
}
}
for(file of files)
if(proceed)
{
for(file of files)
{
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
{
core.log("Modifing " + file.fileName());
file.modifyFITSRecords(modify);
}
}
}
else
{
core.log("Canceled");
}
+1 -1
View File
@@ -118,7 +118,7 @@
</release>
<release version="20240816" date="2024-08-16">
<description>
Fix saving image
<p>Fix saving image</p>
</description>
</release>
<release version="20240616" date="2024-06-16">
View File
View File
@@ -100,6 +100,7 @@ BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(
connect(_ui->addFilesButton, &QPushButton::released, this, &BatchProcessing::addFiles);
connect(_ui->addDirButton, &QPushButton::released, this, &BatchProcessing::addDir);
connect(_ui->addMarkedButton, &QPushButton::released, this, &BatchProcessing::addMarked);
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
connect(_ui->startButton, &QPushButton::released, this, &BatchProcessing::runScript);
@@ -164,6 +165,17 @@ void BatchProcessing::addDir()
}
}
void BatchProcessing::addMarked()
{
QStringList files = _database->getMarkedFiles();
for(const QString &file : files)
{
QFileInfo info(file);
if(info.exists() && info.isReadable())
_ui->pathsList->addItem(file);
};
}
void BatchProcessing::removePath()
{
for(auto &item : _ui->pathsList->selectedItems())
@@ -28,6 +28,7 @@ protected:
public slots:
void addFiles();
void addDir();
void addMarked();
void removePath();
void removeAllPaths();
void browse();
@@ -52,6 +52,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addMarkedButton">
<property name="text">
<string>Add marked</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="text">
View File
+55 -2
View File
@@ -16,10 +16,29 @@ bool Database::init(const QLatin1String &connectionName)
QDir dir(path);
database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
ngc = QSqlDatabase::addDatabase("QSQLITE", connectionName + "ngc");
if(!dir.mkpath("."))
return false;
if(ngc.isValid())
{
QString ngcDb = dir.absoluteFilePath("ngc.db");
if(!QFile::exists(ngcDb))
QFile::copy(":/ngc.db", ngcDb);
ngc.setDatabaseName(ngcDb);
if(ngc.open())
{
m_getNgc = QSqlQuery(ngc);
m_getNgc.prepare("SELECT *,IIF(V_Mag IS NULL, B_Mag, V_Mag) AS mag FROM ngc WHERE RA_deg BETWEEN ? AND ? AND DEC_deg BETWEEN ? AND ?");
}
else
{
qDebug() << "Could not open NGC database";
}
}
if(database.isValid())
{
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
@@ -182,7 +201,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
QStringList scannedDirs;
int count = countFiles(dir, scannedDirs);
progress->setMaximum(count);
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
scannedDirs.clear();
@@ -200,7 +218,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
void Database::reindex(QProgressDialog *progress)
{
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next();
@@ -239,6 +256,42 @@ QStringList Database::getFitsKeywords()
return keywords;
}
QVector<SkyObject> Database::getObjects(double minRa, double maxRa, double minDec, double maxDec)
{
QVector<SkyObject> objects;
if(!ngc.isOpen())return objects;
m_getNgc.bindValue(0, minRa);
m_getNgc.bindValue(1, maxRa);
m_getNgc.bindValue(2, minDec);
m_getNgc.bindValue(3, maxDec);
if(m_getNgc.exec())
{
while(m_getNgc.next())
{
QString name;
QString m = m_getNgc.value("M").toString();
QString ic = m_getNgc.value("IC").toString();
if(!m.isEmpty())name = "M" + m + " ";
if(!ic.isEmpty())name += "IC" + ic + " ";
name += m_getNgc.value("Name").toString();
objects.append({
name,
m_getNgc.value("Common names").toString(),
{m_getNgc.value("RA_deg").toDouble(), m_getNgc.value("DEC_deg").toDouble()},
m_getNgc.value("MajAx").toDouble(),
m_getNgc.value("MinAx").toDouble(),
m_getNgc.value("PosAng").toDouble(),
m_getNgc.value("mag").isNull() ? NAN : m_getNgc.value("mag").toDouble(),
{0, 0},
});
}
}
return objects;
}
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
{
if(scannedDirs.contains(dir.canonicalPath()))return true;
+5
View File
@@ -6,11 +6,13 @@
#include <QSqlQuery>
#include <QDir>
#include <QProgressDialog>
#include "imageinfodata.h"
class Database : public QObject
{
Q_OBJECT
QSqlDatabase database;
QSqlDatabase ngc;
QSqlQuery m_markQuery;
QSqlQuery m_unmarkQuery;
QSqlQuery m_isMarkedQuery;
@@ -22,6 +24,8 @@ class Database : public QObject
QSqlQuery m_headerKeywords;
QSqlQuery m_deleteFile;
QSqlQuery m_getNgc;
int m_progress;
public:
explicit Database(QObject *parent = 0);
@@ -37,6 +41,7 @@ public:
void indexDir(const QDir &dir, QProgressDialog *progress);
void reindex(QProgressDialog *progress);
QStringList getFitsKeywords();
QVector<SkyObject> getObjects(double minRa, double maxRa, double minDec, double maxDec);
protected:
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
bool indexFile(const QFileInfo &file);
View File
View File
View File
View File
View File
+170 -3
View File
@@ -4,6 +4,7 @@
#include <QRegularExpression>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "database.h"
#include "libxisf.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
@@ -153,9 +154,9 @@ bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
return false;
}
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
bool WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
if(wcs == nullptr)return false;
minRa = 1000;
maxRa = -1000;
@@ -208,6 +209,7 @@ void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, dou
if(s.contains(scp))
minDec = -90;
}
return true;
}
double hav(double x)
@@ -266,6 +268,16 @@ QString SkyPoint::toString() const
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');
}
QString SkyPoint::RAString() const
{
return toHMS(ra / 15);
}
QString SkyPoint::DECString() const
{
return toDMS(dec);
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
@@ -308,11 +320,21 @@ QString SkyPoint::toHMS(double decHour)
QString SkyPoint::toDMS(double deg)
{
int sign = deg < 0.0 ? -1 : 1;
deg *= sign;
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'));
return QString("%1˚ %2' %3\"").arg((int)d * sign, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
SkyPoint SkyPoint::operator+(const SkyPoint &p)
{
SkyPoint ret;
ret.ra = ra + p.ra;
ret.dec = dec + p.dec;
return ret;
}
SkyPointScale ImageInfoData::getCenterRaDec() const
@@ -403,3 +425,148 @@ SkyPointScale ImageInfoData::getCenterRaDec() const
}
return ret;
}
SkyPoint greatCircle(SkyPoint &p, double dist, double azm)
{
dist = dist * M_PI / 180;
azm = azm * M_PI / 180;
double dec0 = p.DEC() * M_PI / 180;
double ra0 = p.RA() * M_PI / 180;
double dec1 = std::asin(std::sin(dec0) * std::cos(dist) + std::cos(dec0) * std::sin(dist) * std::cos(azm));
double ra1 = ra0 + std::atan2(std::sin(azm) * std::sin(dist) * std::cos(dec0), std::cos(dist) - std::sin(dec0) * std::sin(dec1));
return SkyPoint(ra1 * 180 / M_PI, dec1 * 180 / M_PI);
}
SkyGrid WCSDataT::prepareGrid(uint32_t w, uint32_t h, Database *database)
{
SkyGrid skyGrid;
if(!wcs)return skyGrid;
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
QPointF a,b;
worldToPixel(SkyPoint(crVal1, crVal2), a);
worldToPixel(SkyPoint(crVal1 + 0.01, crVal2), b);
skyGrid.rot_ang = std::atan2(b.y() - a.y(), b.x() - a.x()) / M_PI * -180.0;
if(database)
{
skyGrid.objects = database->getObjects(minRa, maxRa, minDec, maxDec);
for(auto &object : skyGrid.objects)
{
QPointF p;
if(worldToPixel(object.skyPoint, p))
object.pixel = p;
QPointF majax;
worldToPixel(greatCircle(object.skyPoint, (object.min_ax + object.maj_ax) / 120.0, object.pos_ang), majax);
majax -= p;
object.maj_ax = std::sqrt(QPointF::dotProduct(majax, majax));
}
}
double raStep = 15;
double decStep = 15;
double raRange = maxRa - minRa;
double decRange = maxDec - minDec;
const QVector<double> raSteps = {15, 5, 2.5, 1.25, 0.25, 20/240.0, 10/240.0, 5/240.0, 1/240.0};
const QVector<double> decSteps = {20, 10, 5, 2, 1, 20/60.0, 10/60.0, 5/60.0, 2/60.0, 1/60.0, 20/3600.0, 10/3600.0, 5/3600.0, 2/3600.0, 1/3600.0};
for(double ra : raSteps)
{
if(ra * 5 <= raRange)
{
raStep = ra;
break;
}
}
for(double dec : decSteps)
{
if(dec * 5 <= decRange)
{
decStep = dec;
break;
}
}
minRa -= std::fmod(minRa, raStep);
minDec -= std::fmod(minDec, decStep);
if(minRa < 0)minRa -= raStep;
if(minDec < 0)minDec -= decStep;
QRectF clip(0, 0, w, h);
const double step = 0.2;
maxRa += raStep;
maxDec += decStep;
for(double ra = minRa; ra <= maxRa; ra += raStep)
{
QPointF p;
worldToPixel(SkyPoint(ra, minDec), p);
skyGrid.grid.moveTo(p);
for(double dec = minDec + decStep * step; dec <= maxDec; dec += decStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
for(double dec = minDec; dec <= maxDec; dec += decStep)
{
QPointF p;
worldToPixel(SkyPoint(minRa, dec), p);
skyGrid.grid.moveTo(p);
for(double ra = minRa + raStep * step; ra <= maxRa; ra += raStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
SkyPoint sp1, sp2,orig;
pixelToWorld(QPointF(-1, -1), orig);
sp1 = orig;
for(uint32_t x = 0; x < w; x++)
{
QPointF p(x, 0);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
sp1 = orig;
for(uint32_t y = 0; y < h; y++)
{
QPointF p(0, y);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
skyGrid.empty = false;
return skyGrid;
}
void SkyGrid::clear()
{
empty = true;
grid.clear();
text.clear();
objects.clear();
}
+30 -1
View File
@@ -5,12 +5,15 @@
#include <QPointF>
#include <QVector>
#include <QVariant>
#include <QPainterPath>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace LibXISF { struct FITSKeyword; struct Property; }
class Database;
struct FITSRecord
{
QByteArray key;
@@ -36,10 +39,13 @@ public:
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
QString RAString() const;
QString DECString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
SkyPoint operator+(const SkyPoint &p);
};
struct SkyPointScale
@@ -51,6 +57,28 @@ struct SkyPointScale
double scaleHigh = 10000.0;
};
struct SkyObject
{
QString name;
QString name2;
SkyPoint skyPoint;
double maj_ax;
double min_ax;
double pos_ang;
double mag;
QPointF pixel;
};
struct SkyGrid
{
bool empty = true;
QPainterPath grid;
QVector<QPair<QPointF, QString>> text;
QVector<SkyObject> objects;
double rot_ang = 0;
void clear();
};
class WCSDataT
{
int nwcs = 0;
@@ -65,9 +93,10 @@ public:
~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 calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
SkyGrid prepareGrid(uint32_t w, uint32_t h, Database *database);
};
struct ImageInfoData
@@ -142,6 +142,11 @@ void ImageScrollArea::falseColor(bool enable)
m_imageWidget->falseColor(enable);
}
void ImageScrollArea::drawGrid(bool enable)
{
m_imageWidget->drawGrid(enable);
}
QImage ImageScrollArea::renderToImage()
{
return m_imageWidget->renderToImage();
@@ -31,6 +31,7 @@ public slots:
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
void drawGrid(bool enable);
QImage renderToImage();
protected slots:
void scrollEvent();
+52 -2
View File
@@ -180,6 +180,10 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{
m_wcs = wcs;
m_grid.clear();
if(m_drawGrid && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
}
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
@@ -496,6 +500,18 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
painter.drawImage(0, 0, img);
}
void ImageWidgetGL::drawGrid(bool enable)
{
if(m_grid.empty && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
if(enable != m_drawGrid)
{
m_drawGrid = enable;
update();
}
}
void ImageWidgetGL::paintGL()
{
float dx = m_dx;
@@ -613,9 +629,43 @@ void ImageWidgetGL::paintGL()
m_program->setUniformValue("colormapIdx", m_colormapIdx);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
}
}
if(m_drawGrid && !m_grid.empty)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setPen(QPen(Qt::yellow, 1.0 / m_scale));
QTransform tran;
tran.translate(-std::floor(dx), -std::floor(dy));
tran.scale(m_scale, m_scale);
painter.setTransform(tran);
painter.setClipRect(0, 0, m_imgWidth, m_imgHeight);
painter.drawPath(m_grid.grid);
painter.setPen(Qt::yellow);
QFont font({"Arial", "serif-sans"});
font.setPointSizeF(12 / m_scale);
painter.setFont(font);
for(auto &text : m_grid.text)
painter.drawText(QRectF(text.first, QSizeF(4000, 4000)), text.second);
painter.setPen(QPen(Qt::green, 1.0 / m_scale));
QFontMetricsF fontMetric = QFontMetricsF(font);
for(auto &object : m_grid.objects)
{
QRectF rect = fontMetric.boundingRect(object.name);
rect.moveCenter(object.pixel);
painter.setTransform(tran);
painter.drawText(rect, Qt::TextDontClip, object.name);
painter.translate(object.pixel);
painter.rotate(object.pos_ang);
painter.drawEllipse(QPointF(0, 0), object.maj_ax, object.maj_ax);
}
}
}
}
}
void ImageWidgetGL::resizeGL(int w, int h)
+5
View File
@@ -8,6 +8,7 @@
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLFunctions>
#include <QPainterPath>
#include "database.h"
#include "rawimage.h"
#include "imageinfodata.h"
@@ -37,6 +38,7 @@ public:
virtual QImage renderToImage() = 0;
virtual void thumbnailLoaded(const Image *image) = 0;
virtual void showThumbnail(bool enable) = 0;
virtual void drawGrid(bool enable) = 0;
static QImage loadColormap();
};
@@ -70,6 +72,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSDataT> m_wcs;
SkyGrid m_grid;
int m_width, m_height;
int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0;
@@ -87,6 +90,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
bool m_selecting = false;
bool m_sizesDirty = false;
bool m_srgb = false;
bool m_drawGrid = false;
int m_thumbnailCount = 0;
int m_maxTextureSize = 0;
int m_maxArrayLayers = 0;
@@ -116,6 +120,7 @@ public:
QImage renderToImage() override;
void thumbnailLoaded(const Image *image) override;
void showThumbnail(bool enable) override;
void drawGrid(bool enable) override;
protected:
void paintGL() override;
void resizeGL(int w, int h) override;
View File
View File
+4 -3
View File
@@ -165,11 +165,12 @@ void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoD
double vald = record.value.toDouble(&isdouble);
int valb = record.value.toString() == "T";
long long vall = record.value.toLongLong(&isint);
if(isint)isint = vall == vald;
QByteArray str = record.value.toString().toLatin1();
if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isint)
if(isint)
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isbool)
fits_write_key(fw, TLOGICAL, record.key.data(), &valb, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(record.key == "COMMENT")
View File
+18 -1
View File
@@ -4,10 +4,27 @@
#include <QTranslator>
#include <QCommandLineParser>
#include <stdlib.h>
#include "thumbnailer/genthumbnail.h"
#include "../thumbnailer/genthumbnail.h"
#include <QTreeView>
#include <QFileSystemModel>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFileSystemModel model;
QTreeView treeView;
QTimer::singleShot(5000, [&model,&treeView](){
treeView.setModel(&model);
model.setRootPath("/home");
});
treeView.resize(800, 600);
treeView.show();
return app.exec();
#ifdef __linux__
setenv("LC_NUMERIC", "C", 1);
#endif
+4 -1
View File
@@ -92,6 +92,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel);
connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor);
connect(m_stretchPanel, &StretchToolbar::drawGrid, m_image, &ImageScrollArea::drawGrid);
m_ringList = new ImageRingList(m_database, nameFilter, this);
m_filesystem = new FilesystemWidget(m_ringList, this);
@@ -263,7 +264,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
slideshowAction->setCheckable(true);
viewMenu->addSeparator();
viewMenu->addActions(m_stretchPanel->actions());
auto actionList = m_stretchPanel->actions();
actionList.removeFirst();
viewMenu->addActions(actionList);
menuBar()->addMenu(viewMenu);
QMenu *selectMenu = new QMenu(tr("Select"), this);
View File
View File
View File
View File
View File
+70 -1
View File
@@ -92,6 +92,55 @@ bool ScriptEngine::isMarked(const File *file)
return ret;
}
QJSValue ScriptEngine::getObjects(double ra, double dec, double distance)
{
QVector<SkyObject> objects;
QMetaObject::invokeMethod(_database, [this, ra, dec, distance](){
return _database->getObjects(ra - distance, ra + distance, dec - distance, dec + distance); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("mag", object.mag);
ret.setProperty(i++, jsObj);
}
return ret;
}
QJSValue ScriptEngine::getObjects(const QJSValue &bounds)
{
QVector<SkyObject> objects;
double minRa = bounds.property("minRA").toNumber();
double maxRa = bounds.property("maxRA").toNumber();
double minDec = bounds.property("minDEC").toNumber();
double maxDec = bounds.property("maxDEC").toNumber();
QMetaObject::invokeMethod(_database, [this, minRa, maxRa, minDec, maxDec](){
return _database->getObjects(minRa, maxRa, minDec, maxDec); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("mag", object.mag);
ret.setProperty(i++, jsObj);
}
return ret;
}
void ScriptEngine::setMaxThread(int maxthread)
{
int newval = std::max(std::min(QThread::idealThreadCount(), maxthread), 1);
@@ -410,12 +459,14 @@ void File::loadFitsKeywords()
{
readXISFHeader(_path, info);
}
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit")
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit" || suffix().toLower() == "fz")
{
readFITSHeader(_path, info);
}
else return;
_wcs = info.wcs;
for(auto &record : info.fitsHeader)
{
_fitsKeywords.append(record.key);
@@ -788,6 +839,24 @@ QJSValue File::stats()
return _stats;
}
QJSValue File::calculatedBounds()
{
QJSValue ret = _engine->newObject();
loadFitsKeywords();
if(_wcs)
{
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
_wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
ret.setProperty("minRA", minRa);
ret.setProperty("maxRA", maxRa);
ret.setProperty("minDEC", minDec);
ret.setProperty("maxDEC", maxDec);
ret.setProperty("crVal1", crVal1);
ret.setProperty("crVal2", crVal2);
}
return ret;
}
#ifdef PLATESOLVER
QJSValue File::solve(bool updateHeader)
{
+4
View File
@@ -41,6 +41,8 @@ public:
Q_INVOKABLE void mark(File *file);
Q_INVOKABLE void unmark(File *file);
Q_INVOKABLE bool isMarked(const File *file);
Q_INVOKABLE QJSValue getObjects(double ra, double dec, double distance);
Q_INVOKABLE QJSValue getObjects(const QJSValue &bounds);
Q_INVOKABLE void setMaxThread(int maxthread);
Q_INVOKABLE void sync();
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
@@ -96,6 +98,7 @@ class File : public QObject
bool _fitsKeywordsLoaded = false;
QStringList _fitsKeywords;
QMultiHash<QString, FITSRecord> _fitsRecords;
std::shared_ptr<WCSDataT> _wcs;
void loadFitsKeywords();
bool mkpath(const QString &path) const;
QJSValue _stats;
@@ -124,6 +127,7 @@ public:
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE QJSValue stats();
Q_INVOKABLE QJSValue calculatedBounds();
#ifdef PLATESOLVER
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
Q_INVOKABLE QJSValue extractStars(bool hfr);
View File
View File
View File
View File
View File
View File
@@ -89,6 +89,11 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
m_autoStretchOnLoad->setCheckable(true);
QAction *showGridButton = addAction(QIcon(":/grid.svg"), tr("Draw equatorial grid"));
showGridButton->setCheckable(true);
connect(showGridButton, &QAction::toggled, this, &StretchToolbar::drawGrid);
QSettings settings;
m_autoStretchOnLoad->setChecked(settings.value("stretchtoolbar/autostretch", false).toBool());
}
@@ -33,6 +33,7 @@ signals:
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
void drawGrid(bool enable);
};
#endif // STRETCHTOOLBAR_H
View File
+5 -5
View File
@@ -6,9 +6,9 @@ if(BUILD_THUMBNAILER)
Dll.cpp
loadimage.cpp
TenmonThumbnailProvider.cpp
../rawimage.h
../rawimage.cpp
../rawimage_sse.cpp)
../src/rawimage.h
../src/rawimage.cpp
../src/rawimage_sse.cpp)
set_target_properties(tenmonthumbnailer PROPERTIES PREFIX "")
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
@@ -19,8 +19,8 @@ if(BUILD_THUMBNAILER)
qt_add_executable(tenmonthumbnailer
main.cpp
loadimage.cpp
../rawimage.cpp
../rawimage_sse.cpp)
../src/rawimage.cpp
../src/rawimage_sse.cpp)
target_link_libraries(tenmonthumbnailer PRIVATE ${FITS_LIB} XISF)
+1 -1
View File
@@ -2,7 +2,7 @@
#include <thumbcache.h> // For IThumbnailProvider.
#include <new>
#include "libxisf.h"
#include "../rawimage.h"
#include "../src/rawimage.h"
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
+2 -3
View File
@@ -1,7 +1,6 @@
#include "genthumbnail.h"
#include "../rawimage.h"
#include "../loadimage.h"
#include "../src/rawimage.h"
#include "../src/loadimage.h"
int generateThumbnail(const QString &input, const QString &output, uint32_t size)
{
+1 -1
View File
@@ -1,5 +1,5 @@
#include "libxisf.h"
#include "../rawimage.h"
#include "../src/rawimage.h"
#ifdef WIN32
#include <windows.h>
#endif
+1 -1
View File
@@ -1,7 +1,7 @@
#include <vector>
#include <string>
#include <iostream>
#include "../rawimage.h"
#include "../src/rawimage.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"