From 92f9920f246ba341597fc0f38cfd1e3b18c6639b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Poizl?= Date: Tue, 19 Apr 2022 16:57:54 +0200 Subject: [PATCH] Add saving to FITS and XISF --- loadrunable.cpp | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ loadrunable.h | 9 +++ mainwindow.cpp | 33 +++++++++-- mainwindow.h | 1 + rawimage.cpp | 10 ++++ rawimage.h | 2 + 6 files changed, 198 insertions(+), 4 deletions(-) diff --git a/loadrunable.cpp b/loadrunable.cpp index 3b5878a..d1ea1fb 100644 --- a/loadrunable.cpp +++ b/loadrunable.cpp @@ -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 +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 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 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; isize(), mat[i].data, &status); + } + break; + case CV_16U: + fits_create_img(fw, USHORT_IMG, naxis, naxes, &status); + for(int i=0; isize(), mat[i].data, &status); + } + break; + case CV_32F: + fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status); + for(int i=0; isize(), 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(xisf, rawimage); + break; + case CV_16U: + writeXISFImage(xisf, rawimage); + break; + case CV_32F: + writeXISFImage(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; + } +} diff --git a/loadrunable.h b/loadrunable.h index 1aab8bf..d2fb63b 100644 --- a/loadrunable.h +++ b/loadrunable.h @@ -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 diff --git a/mainwindow.cpp b/mainwindow.cpp index 24d09e9..6d24e66 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include "loadrunable.h" #ifdef __linux__ #include @@ -298,15 +300,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(); diff --git a/mainwindow.h b/mainwindow.h index 8f5f0aa..db88daf 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -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(); diff --git a/rawimage.cpp b/rawimage.cpp index 1ccc903..8557fb4 100644 --- a/rawimage.cpp +++ b/rawimage.cpp @@ -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; +} diff --git a/rawimage.h b/rawimage.h index 5b5b5f0..9d1f3bb 100644 --- a/rawimage.h +++ b/rawimage.h @@ -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