Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da1843e48c | |||
| e0d473c8c8 | |||
| 92f9920f24 | |||
| f68a9c4d7c | |||
| 027a38cb42 | |||
| 47d5a9fc96 | |||
| 061bb3892e |
+13
-1
@@ -31,6 +31,7 @@ set(TENMON_SRC
|
||||
loadrunable.cpp
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
markedfiles.cpp
|
||||
rawimage.cpp
|
||||
starfit.cpp
|
||||
stfslider.cpp
|
||||
@@ -66,5 +67,16 @@ endif()
|
||||
|
||||
install(TARGETS tenmon)
|
||||
if(UNIX)
|
||||
install(SCRIPT install.cmake)
|
||||
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
||||
if(XDG-DESKTOP-MENU_EXECUTABLE)
|
||||
install(SCRIPT install.cmake)
|
||||
else()
|
||||
if(DEFINED ENV{FLATPAK_DEST})
|
||||
install(FILES org.nou.tenmon.desktop DESTINATION "$ENV{FLATPAK_DEST}/share/applications")
|
||||
install(FILES org.nou.tenmon.png DESTINATION "$ENV{FLATPAK_DEST}/share/icons/hicolor/32x32/apps")
|
||||
else()
|
||||
install(FILES org.nou.tenmon.desktop DESTINATION "/usr/share/applications")
|
||||
install(FILES org.nou.tenmon.png DESTINATION "/usr/share/icons/hicolor/32x32/apps")
|
||||
endif()
|
||||
endif()
|
||||
endif(UNIX)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
||||
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
|
||||
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor org.nou.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 32 icon.png org.nou.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 32 org.nou.tenmon.png org.nou.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
+147
@@ -492,3 +492,150 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ConvertRunable::ConvertRunable(const QString &in, const QString &out) :
|
||||
m_infile(in),
|
||||
m_outfile(out)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeXISFImage(pcl::XISFWriter &writer, RawImage *rawimg)
|
||||
{
|
||||
const cv::Mat &cvmat = rawimg->mat();
|
||||
T pclimg(rawimg->width(), rawimg->height(), cvmat.channels() == 1 ? pcl::ColorSpace::Gray : pcl::ColorSpace::RGB);
|
||||
if(cvmat.channels() == 1)
|
||||
{
|
||||
memcpy(pclimg.PixelData(0), rawimg->data(), rawimg->size()*sizeof(typename T::sample));
|
||||
}
|
||||
if(cvmat.channels() == 3)
|
||||
{
|
||||
std::vector<cv::Mat> channels;
|
||||
cv::split(cvmat, channels);
|
||||
memcpy(pclimg.PixelData(0), channels[0].data, rawimg->size()*sizeof(typename T::sample));
|
||||
memcpy(pclimg.PixelData(1), channels[1].data, rawimg->size()*sizeof(typename T::sample));
|
||||
memcpy(pclimg.PixelData(2), channels[2].data, rawimg->size()*sizeof(typename T::sample));
|
||||
}
|
||||
writer.WriteImage(pclimg);
|
||||
}
|
||||
|
||||
void writeFITSImage(fitsfile *fw, RawImage *rawimage, ImageInfoData &imageinfo)
|
||||
{
|
||||
static QStringList skipKeys = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "BZERO", "BSCALE", "EXTEND"};
|
||||
|
||||
int status = 0;
|
||||
long firstpix[3] = {1,1,1};
|
||||
|
||||
int channels = rawimage->mat().channels();
|
||||
int naxis = channels == 1 ? 2 : 3;
|
||||
long naxes[3] = {(int)rawimage->width(), (int)rawimage->height(), rawimage->mat().channels()};
|
||||
|
||||
std::vector<cv::Mat> mat;
|
||||
if(channels == 1)
|
||||
mat.push_back(rawimage->mat());
|
||||
else
|
||||
cv::split(rawimage->mat(), mat);
|
||||
|
||||
switch(CV_MAT_DEPTH(rawimage->dataType()))
|
||||
{
|
||||
case CV_8U:
|
||||
fits_create_img(fw, BYTE_IMG, naxis, naxes, &status);
|
||||
for(int i=0; i<channels; i++)
|
||||
{
|
||||
firstpix[2] = i+1;
|
||||
fits_write_pix(fw, TBYTE, firstpix, rawimage->size(), mat[i].data, &status);
|
||||
}
|
||||
break;
|
||||
case CV_16U:
|
||||
fits_create_img(fw, USHORT_IMG, naxis, naxes, &status);
|
||||
for(int i=0; i<channels; i++)
|
||||
{
|
||||
firstpix[2] = i+1;
|
||||
fits_write_pix(fw, TUSHORT, firstpix, rawimage->size(), mat[i].data, &status);
|
||||
}
|
||||
break;
|
||||
case CV_32F:
|
||||
fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status);
|
||||
for(int i=0; i<channels; i++)
|
||||
{
|
||||
firstpix[2] = i+1;
|
||||
fits_write_pix(fw, TFLOAT, firstpix, rawimage->size(), mat[i].data, &status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
for(const FITSRecord &record : imageinfo.fitsHeader)
|
||||
{
|
||||
if(skipKeys.contains(record.key))continue;
|
||||
|
||||
bool isdouble;
|
||||
bool isint;
|
||||
bool isbool = record.value.toString() == "T" || record.value.toString() == "F";
|
||||
double vald = record.value.toDouble(&isdouble);
|
||||
int valb = record.value.toString() == "T";
|
||||
long long vall = record.value.toLongLong(&isint);
|
||||
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)
|
||||
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, 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")
|
||||
fits_write_comment(fw, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
else if(record.key == "HISTORY")
|
||||
fits_write_history(fw, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
else
|
||||
fits_write_key(fw, TSTRING, record.key.data(), str.isEmpty() ? nullptr : str.data(), record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertRunable::run()
|
||||
{
|
||||
ImageInfoData imageinfo;
|
||||
RawImage *rawimage = nullptr;
|
||||
if(m_infile.endsWith(".FITS", Qt::CaseInsensitive) || m_infile.endsWith(".FIT", Qt::CaseInsensitive))
|
||||
loadFITS(m_infile, imageinfo, &rawimage);
|
||||
if(m_infile.endsWith(".XISF", Qt::CaseInsensitive))
|
||||
loadXISF(m_infile, imageinfo, &rawimage);
|
||||
|
||||
if(rawimage)
|
||||
{
|
||||
if(m_outfile.endsWith(".XISF", Qt::CaseInsensitive))
|
||||
{
|
||||
pcl::XISFOptions options;
|
||||
pcl::FITSKeywordArray fitskeywords;
|
||||
for(auto &record : imageinfo.fitsHeader)
|
||||
{
|
||||
pcl::FITSHeaderKeyword key(pcl::IsoString(record.key.data()), pcl::IsoString(record.value.toString().toLatin1().data()), pcl::IsoString(record.comment.data()));
|
||||
fitskeywords.Append(key);
|
||||
}
|
||||
pcl::XISFWriter xisf;
|
||||
xisf.Create(m_outfile.utf16(), 1);
|
||||
xisf.WriteFITSKeywords(fitskeywords);
|
||||
switch(CV_MAT_DEPTH(rawimage->dataType()))
|
||||
{
|
||||
case CV_8U:
|
||||
writeXISFImage<pcl::UInt8Image>(xisf, rawimage);
|
||||
break;
|
||||
case CV_16U:
|
||||
writeXISFImage<pcl::UInt16Image>(xisf, rawimage);
|
||||
break;
|
||||
case CV_32F:
|
||||
writeXISFImage<pcl::Image>(xisf, rawimage);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(m_outfile.endsWith(".FITS", Qt::CaseInsensitive) || m_outfile.endsWith(".FIT", Qt::CaseInsensitive))
|
||||
{
|
||||
int status = 0;
|
||||
fitsfile *fw;
|
||||
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
|
||||
fits_create_diskfile(&fw, m_outfile.toLocal8Bit().data(), &status);
|
||||
writeFITSImage(fw, rawimage, imageinfo);
|
||||
fits_close_file(fw, &status);
|
||||
}
|
||||
delete rawimage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,4 +21,13 @@ public:
|
||||
void run();
|
||||
};
|
||||
|
||||
class ConvertRunable : public QRunnable
|
||||
{
|
||||
QString m_infile;
|
||||
QString m_outfile;
|
||||
public:
|
||||
ConvertRunable(const QString &in, const QString &out);
|
||||
void run();
|
||||
};
|
||||
|
||||
#endif // LOADRUNABLE_H
|
||||
|
||||
@@ -14,7 +14,7 @@ int main(int argc, char *argv[])
|
||||
QApplication a(argc, argv);
|
||||
a.setOrganizationName("nou");
|
||||
a.setApplicationName("Tenmon");
|
||||
a.setWindowIcon(QIcon(":/icon.png"));
|
||||
a.setWindowIcon(QIcon(":/org.nou.tenmon.png"));
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
+38
-4
@@ -14,6 +14,9 @@
|
||||
#include <unistd.h>
|
||||
#include <QSettings>
|
||||
#include <QCoreApplication>
|
||||
#include <QThreadPool>
|
||||
#include "loadrunable.h"
|
||||
#include "markedfiles.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/ioctl.h>
|
||||
@@ -78,6 +81,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
connect(m_ringList, SIGNAL(infoLoaded(ImageInfoData)), m_info, SLOT(setInfo(const ImageInfoData&)));
|
||||
connect(m_ringList, SIGNAL(currentImageChanged(int)), m_filesystem, SLOT(selectFile(int)));
|
||||
connect(m_ringList, &ImageRingList::thumbnailLoaded, m_imageGL->imageWidget(), &ImageWidget::thumbnailLoaded);
|
||||
connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded);
|
||||
connect(m_imageGL->imageWidget(), &ImageWidget::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
||||
|
||||
QMenu *fileMenu = new QMenu(tr("File"), this);
|
||||
@@ -112,6 +116,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
selectMenu->addSeparator();
|
||||
selectMenu->addAction(tr("Mark and next"), this, SLOT(markAndNext()), Qt::Key_M);
|
||||
selectMenu->addAction(tr("Unmark and next"), this, SLOT(unmarkAndNext()), Qt::Key_X);
|
||||
selectMenu->addAction(tr("Show marked"), this, &MainWindow::showMarkFilesDialog);
|
||||
menuBar()->addMenu(selectMenu);
|
||||
|
||||
QMenu *analyzeMenu = new QMenu(tr("Analyze"), this);
|
||||
@@ -298,15 +303,38 @@ void MainWindow::indexDir()
|
||||
|
||||
void MainWindow::saveAs()
|
||||
{
|
||||
QString file = QFileDialog::getSaveFileName(this, tr("Save as"), _lastDir, tr("Images (*.jpg *.png *.JPG *.PNG)"));
|
||||
QString selectedFilter;
|
||||
QString file = QFileDialog::getSaveFileName(this, tr("Save as"), _lastDir, tr("JPEG (*.jpg *.JPG);; PNG (*.png *.PNG);;FITS (*.fits *.FITS);;XISF (*.xisf *.XISF)"), &selectedFilter);
|
||||
if(!file.isEmpty())
|
||||
{
|
||||
QImage img = m_imageGL->imageWidget()->renderToImage();
|
||||
if(!img.isNull())
|
||||
img.save(file);
|
||||
QFileInfo info(file);
|
||||
if(info.suffix().isEmpty())
|
||||
{
|
||||
if(selectedFilter.contains("jpg"))file += ".jpg";
|
||||
if(selectedFilter.contains("png"))file += ".png";
|
||||
if(selectedFilter.contains("fits"))file += ".fits";
|
||||
if(selectedFilter.contains("xisf"))file += ".xisf";
|
||||
}
|
||||
|
||||
if(file.endsWith(".fits") || file.endsWith(".xisf"))
|
||||
{
|
||||
convert(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
QImage img = m_imageGL->imageWidget()->renderToImage();
|
||||
if(!img.isNull())
|
||||
img.save(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::convert(const QString &outfile)
|
||||
{
|
||||
QString file = m_ringList->currentImage()->name();
|
||||
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile));
|
||||
}
|
||||
|
||||
void MainWindow::markImage()
|
||||
{
|
||||
ImagePtr ptr = m_ringList->currentImage();
|
||||
@@ -421,6 +449,12 @@ void MainWindow::starFinder(bool findStars)
|
||||
m_ringList->setFindStars(findStars);
|
||||
}
|
||||
|
||||
void MainWindow::showMarkFilesDialog()
|
||||
{
|
||||
MarkedFiles markedFiles;
|
||||
markedFiles.exec();
|
||||
}
|
||||
|
||||
void MainWindow::updateWindowTitle()
|
||||
{
|
||||
ImagePtr ptr = m_ringList->currentImage();
|
||||
|
||||
@@ -45,6 +45,7 @@ protected slots:
|
||||
void loadFile(int row);
|
||||
void indexDir();
|
||||
void saveAs();
|
||||
void convert(const QString &outfile);
|
||||
void markImage();
|
||||
void unmarkImage();
|
||||
void markAndNext();
|
||||
@@ -55,6 +56,7 @@ protected slots:
|
||||
void imageStats(bool imageStats);
|
||||
void peakFinder(bool findPeaks);
|
||||
void starFinder(bool findStars);
|
||||
void showMarkFilesDialog();
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "markedfiles.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QTableView>
|
||||
#include <QSqlTableModel>
|
||||
#include <QPushButton>
|
||||
#include <QHeaderView>
|
||||
#include <QSqlQuery>
|
||||
|
||||
MarkedFiles::MarkedFiles(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Marked files"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
m_tableView = new QTableView(this);
|
||||
m_tableView->verticalHeader()->setDefaultSectionSize(1);
|
||||
|
||||
QSqlDatabase db = QSqlDatabase::database();
|
||||
m_model = new QSqlTableModel(this, db);
|
||||
|
||||
m_model->setTable("files");
|
||||
m_model->removeColumn(0);
|
||||
m_model->setHeaderData(0, Qt::Horizontal, tr("Filename"));
|
||||
m_model->select();
|
||||
|
||||
m_tableView->setModel(m_model);
|
||||
m_tableView->resizeColumnsToContents();
|
||||
m_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
QHBoxLayout *hlayout = new QHBoxLayout;
|
||||
QPushButton *clearSelectedButton = new QPushButton(tr("Clear selected"), this);
|
||||
QPushButton *clearAllButton = new QPushButton(tr("Clear all"), this);
|
||||
|
||||
connect(clearSelectedButton, &QPushButton::pressed, this, &MarkedFiles::clearSelected);
|
||||
connect(clearAllButton, &QPushButton::pressed, this, &MarkedFiles::clearAll);
|
||||
|
||||
layout->addWidget(m_tableView);
|
||||
layout->addLayout(hlayout);
|
||||
hlayout->addWidget(clearSelectedButton);
|
||||
hlayout->addWidget(clearAllButton);
|
||||
|
||||
resize(800, 600);
|
||||
}
|
||||
|
||||
void MarkedFiles::clearSelected()
|
||||
{
|
||||
|
||||
QSqlDatabase db = QSqlDatabase::database();
|
||||
QSqlQuery query("DELETE FROM files where file = ?", db);
|
||||
QModelIndexList rows = m_tableView->selectionModel()->selectedRows();
|
||||
QStringList files;
|
||||
for(const QModelIndex &row : rows)
|
||||
{
|
||||
files.append(row.data().toString());
|
||||
}
|
||||
query.bindValue(0, files);
|
||||
query.execBatch();
|
||||
m_model->select();
|
||||
}
|
||||
|
||||
void MarkedFiles::clearAll()
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::database();
|
||||
db.exec("DELETE FROM files");
|
||||
m_model->select();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef MARKEDFILES_H
|
||||
#define MARKEDFILES_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTableView>
|
||||
#include <QSqlTableModel>
|
||||
|
||||
class MarkedFiles : public QDialog
|
||||
{
|
||||
QTableView *m_tableView;
|
||||
QSqlTableModel *m_model;
|
||||
public:
|
||||
MarkedFiles(QWidget *parent = nullptr);
|
||||
protected slots:
|
||||
void clearSelected();
|
||||
void clearAll();
|
||||
};
|
||||
|
||||
#endif // MARKEDFILES_H
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 454 B |
@@ -5,5 +5,5 @@ Icon=org.nou.tenmon
|
||||
Comment=FITS Image viewer
|
||||
Name=Tenmon
|
||||
Categories=Graphics;2DGraphics;RasterGraphics;Viewer;
|
||||
MimeType=image/fits;
|
||||
MimeType=image/fits;image/x-xisf;
|
||||
Terminal=false
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -233,6 +233,11 @@ RawImage::ImgType RawImage::type() const
|
||||
return CV2Type(m_img.type());
|
||||
}
|
||||
|
||||
int RawImage::dataType() const
|
||||
{
|
||||
return m_img.type();
|
||||
}
|
||||
|
||||
uint32_t RawImage::norm() const
|
||||
{
|
||||
switch(m_img.type())
|
||||
@@ -288,3 +293,8 @@ float RawImage::thumbAspect() const
|
||||
{
|
||||
return m_thumbAspect;
|
||||
}
|
||||
|
||||
const cv::Mat& RawImage::mat() const
|
||||
{
|
||||
return m_img;
|
||||
}
|
||||
|
||||
@@ -73,11 +73,13 @@ public:
|
||||
uint32_t height() const;
|
||||
uint32_t size() const;
|
||||
ImgType type() const;
|
||||
int dataType() const;
|
||||
uint32_t norm() const;
|
||||
void* data();
|
||||
const void* data() const;
|
||||
void convertToThumbnail();
|
||||
float thumbAspect() const;
|
||||
const cv::Mat& mat() const;
|
||||
};
|
||||
|
||||
#endif // RAWIMAGE_H
|
||||
|
||||
+2
-1
@@ -6,9 +6,10 @@
|
||||
<file>thumb.vert</file>
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
<file>icon.png</file>
|
||||
<file>invert.png</file>
|
||||
<file>nuke.png</file>
|
||||
<file>bayer.png</file>
|
||||
<file>org.nou.tenmon.png</file>
|
||||
<file>nuke_a.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -37,6 +37,9 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
|
||||
QAction *superPixelButton = addAction(QIcon(":/bayer.png"), tr("Superpixel CFA draw 2x2 pixel as one"));
|
||||
superPixelButton->setCheckable(true);
|
||||
connect(superPixelButton, SIGNAL(toggled(bool)), this, SIGNAL(superPixel(bool)));
|
||||
|
||||
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Auto stretch"));
|
||||
m_autoStretchOnLoad->setCheckable(true);
|
||||
}
|
||||
|
||||
void StretchToolbar::stretchImage(Image *img)
|
||||
@@ -63,3 +66,10 @@ void StretchToolbar::resetMTF()
|
||||
emit paramChanged(0, 0.5, 1);
|
||||
}
|
||||
|
||||
void StretchToolbar::imageLoaded(Image *img)
|
||||
{
|
||||
if(m_autoStretchOnLoad->isChecked())
|
||||
stretchImage(img);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ class StretchToolbar : public QToolBar
|
||||
{
|
||||
Q_OBJECT
|
||||
STFSlider *m_stfSlider;
|
||||
QAction *m_autoStretchOnLoad;
|
||||
public:
|
||||
explicit StretchToolbar(QWidget *parent = nullptr);
|
||||
public slots:
|
||||
void stretchImage(Image *img);
|
||||
void resetMTF();
|
||||
void imageLoaded(Image *img);
|
||||
signals:
|
||||
void paramChanged(float low, float mid, float high);
|
||||
void autoStretch();
|
||||
|
||||
Reference in New Issue
Block a user