11#include "fitsbahtinovdetector.h"
12#include "fitsthresholddetector.h"
13#include "fitsgradientdetector.h"
14#include "fitscentroiddetector.h"
15#include "fitssepdetector.h"
19#include "kstarsdata.h"
23#include "skymapcomposite.h"
24#include "auxiliary/ksnotification.h"
25#include "auxiliary/robuststatistics.h"
28#include <QApplication>
30#include <QtConcurrent>
31#include <QImageReader>
33#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
38#if !defined(KSTARS_LITE) && defined(HAVE_LIBRAW)
39#include <libraw/libraw.h>
49#include <fits_debug.h>
51#define ZOOM_DEFAULT 100.0
54#define ZOOM_LOW_INCR 10
55#define ZOOM_HIGH_INCR 50
63const QStringList RAWFormats = {
"cr2",
"cr3",
"crw",
"nef",
"raf",
"dng",
"arw",
"orf" };
65bool FITSData::readableFilename(
const QString &filename)
68 QString extension = info.completeSuffix().toLower();
75FITSData::FITSData(FITSMode fitsMode): m_Mode(fitsMode)
79 qRegisterMetaType<FITSMode>(
"FITSMode");
81 debayerParams.method = DC1394_BAYER_METHOD_NEAREST;
82 debayerParams.filter = DC1394_COLOR_FILTER_RGGB;
83 debayerParams.offsetX = debayerParams.offsetY = 0;
86 m_CumulativeFrequency.resize(3);
87 m_HistogramBinWidth.resize(3);
88 m_HistogramFrequency.resize(3);
89 m_HistogramIntensity.resize(3);
100 qRegisterMetaType<FITSMode>(
"FITSMode");
102 debayerParams.method = DC1394_BAYER_METHOD_NEAREST;
103 debayerParams.filter = DC1394_COLOR_FILTER_RGGB;
104 debayerParams.offsetX = debayerParams.offsetY = 0;
108 this->m_Mode = other->m_Mode;
109 this->m_Statistics.channels = other->m_Statistics.channels;
110 memcpy(&m_Statistics, &(other->m_Statistics),
sizeof(m_Statistics));
111 m_ImageBuffer =
new uint8_t[m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel];
112 memcpy(m_ImageBuffer, other->m_ImageBuffer,
113 m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel);
131 if (m_WCSHandle !=
nullptr)
133 wcsvfree(&m_nwcs, &m_WCSHandle);
134 m_WCSHandle =
nullptr;
139 if (starCenters.
count() > 0)
140 qDeleteAll(starCenters);
143 if (m_SkyObjects.
count() > 0)
144 qDeleteAll(m_SkyObjects);
145 m_SkyObjects.
clear();
149 fits_flush_file(fptr, &status);
150 fits_close_file(fptr, &status);
152 m_PackBuffer =
nullptr;
157void FITSData::loadCommon(
const QString &inFilename)
160 qDeleteAll(starCenters);
165 fits_flush_file(fptr, &status);
166 fits_close_file(fptr, &status);
168 m_PackBuffer =
nullptr;
172 m_Filename = inFilename;
175bool FITSData::loadFromBuffer(
const QByteArray &buffer)
179 return privateLoad(buffer);
184 loadCommon(inFilename);
186 m_Extension = info.completeSuffix().toLower();
187 qCDebug(KSTARS_FITS) <<
"Loading file " << m_Filename;
188#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
198QString fitsErrorToString(
int status)
200 char error_status[512] = {0};
201 fits_report_error(stderr, status);
202 fits_get_errstatus(status, error_status);
203 QString message = error_status;
208bool FITSData::privateLoad(
const QByteArray &buffer)
212 cacheEccentricity = -1;
215 return loadFITSImage(buffer);
217 return loadXISFImage(buffer);
219 return loadCanonicalImage(buffer);
220 else if (RAWFormats.
contains(m_Extension))
221 return loadRAWImage(buffer);
226bool FITSData::loadFITSImage(
const QByteArray &buffer,
const bool isCompressed)
228 int status = 0, anynull = 0;
231 m_HistogramConstructed =
false;
233 if (m_Extension.
contains(
".fz") || isCompressed)
242 m_compressedFilename = m_Filename;
247 rc = fp_unpack_file_to_fits(m_Filename.
toLocal8Bit().
data(), &fptr, fpvar) == 0;
250 m_Filename = uncompressedFile;
255 size_t m_PackBufferSize = 100000;
257 m_PackBuffer = (uint8_t *)malloc(m_PackBufferSize);
258 rc = fp_unpack_data_to_data(buffer.
data(), buffer.
size(), &m_PackBuffer, &m_PackBufferSize, fpvar) == 0;
262 void *data =
reinterpret_cast<void *
>(m_PackBuffer);
263 if (fits_open_memfile(&fptr, m_Filename.
toLocal8Bit().
data(), READONLY, &data, &m_PackBufferSize, 0,
266 m_LastError =
i18n(
"Error reading fits buffer: %1.", fitsErrorToString(status));
270 m_Statistics.size = m_PackBufferSize;
278 m_PackBuffer =
nullptr;
279 m_LastError =
i18n(
"Failed to unpack compressed fits");
280 qCCritical(KSTARS_FITS) << m_LastError;
284 m_isTemporary =
true;
285 m_isCompressed =
true;
286 m_Statistics.size = fptr->Fptr->logfilesize;
293 if (fits_open_diskfile(&fptr, m_Filename.
toLocal8Bit(), READONLY, &status))
295 m_LastError =
i18n(
"Error opening fits file %1 : %2", m_Filename, fitsErrorToString(status));
298 m_Statistics.size =
QFile(m_Filename).
size();
303 void *temp_buffer =
const_cast<void *
>(
reinterpret_cast<const void *
>(buffer.
data()));
304 size_t temp_size = buffer.
size();
305 if (fits_open_memfile(&fptr, m_Filename.
toLocal8Bit().
data(), READONLY,
306 &temp_buffer, &temp_size, 0,
nullptr, &status))
308 m_LastError =
i18n(
"Error reading fits buffer: %1", fitsErrorToString(status));
312 m_Statistics.size = temp_size;
315 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
319 m_PackBuffer =
nullptr;
320 m_LastError =
i18n(
"Could not locate image HDU: %1", fitsErrorToString(status));
323 if (fits_get_img_param(fptr, 3, &m_FITSBITPIX, &(m_Statistics.ndim), naxes, &status))
326 m_PackBuffer =
nullptr;
327 m_LastError =
i18n(
"FITS file open error (fits_get_img_param): %1", fitsErrorToString(status));
332 if ((fits_is_compressed_image(fptr, &status) || m_Statistics.ndim <= 0) && !isCompressed)
334 loadCommon(m_Filename);
335 qCDebug(KSTARS_FITS) <<
"Image is compressed. Reloading...";
336 return loadFITSImage(buffer,
true);
339 if (m_Statistics.ndim < 2)
341 m_LastError =
i18n(
"1D FITS images are not supported in KStars.");
342 qCCritical(KSTARS_FITS) << m_LastError;
344 m_PackBuffer =
nullptr;
348 switch (m_FITSBITPIX)
351 m_Statistics.dataType = TBYTE;
352 m_Statistics.bytesPerPixel =
sizeof(uint8_t);
356 m_FITSBITPIX = USHORT_IMG;
357 m_Statistics.dataType = TUSHORT;
358 m_Statistics.bytesPerPixel =
sizeof(int16_t);
361 m_Statistics.dataType = TUSHORT;
362 m_Statistics.bytesPerPixel =
sizeof(uint16_t);
366 m_FITSBITPIX = ULONG_IMG;
367 m_Statistics.dataType = TULONG;
368 m_Statistics.bytesPerPixel =
sizeof(int32_t);
371 m_Statistics.dataType = TULONG;
372 m_Statistics.bytesPerPixel =
sizeof(uint32_t);
375 m_Statistics.dataType = TFLOAT;
376 m_Statistics.bytesPerPixel =
sizeof(float);
379 m_Statistics.dataType = TLONGLONG;
380 m_Statistics.bytesPerPixel =
sizeof(int64_t);
383 m_Statistics.dataType = TDOUBLE;
384 m_Statistics.bytesPerPixel =
sizeof(double);
387 m_LastError =
i18n(
"Bit depth %1 is not supported.", m_FITSBITPIX);
388 qCCritical(KSTARS_FITS) << m_LastError;
392 if (m_Statistics.ndim < 3)
395 if (naxes[0] == 0 || naxes[1] == 0)
397 m_LastError =
i18n(
"Image has invalid dimensions %1x%2", naxes[0], naxes[1]);
398 qCCritical(KSTARS_FITS) << m_LastError;
400 m_PackBuffer =
nullptr;
404 m_Statistics.width = naxes[0];
405 m_Statistics.height = naxes[1];
406 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height;
407 roiCenter.
setX(m_Statistics.width / 2);
408 roiCenter.
setY(m_Statistics.height / 2);
409 if(m_Statistics.width % 2)
410 roiCenter.
setX(roiCenter.
x() + 1);
411 if(m_Statistics.height % 2)
412 roiCenter.
setY(roiCenter.
y() + 1);
416 m_Statistics.channels = naxes[2];
420 if ( (m_Mode != FITS_NORMAL && m_Mode != FITS_CALIBRATE) || !Options::auto3DCube())
421 m_Statistics.channels = 1;
423 m_ImageBufferSize = m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel;
424 m_ImageBuffer =
new uint8_t[m_ImageBufferSize];
425 if (m_ImageBuffer ==
nullptr)
427 qCWarning(KSTARS_FITS) <<
"FITSData: Not enough memory for image_buffer channel. Requested: "
428 << m_ImageBufferSize <<
" bytes.";
431 m_PackBuffer =
nullptr;
438 long nelements = m_Statistics.samples_per_channel * m_Statistics.channels;
440 if (fits_read_img(fptr, m_Statistics.dataType, 1, nelements,
nullptr, m_ImageBuffer, &anynull, &status))
442 m_LastError =
i18n(
"Error reading image: %1", fitsErrorToString(status));
450 if (getRecordValue(
"DATE-OBS", value) && value.
isValid())
458 if (naxes[2] == 1 && m_Statistics.channels == 1 && Options::autoDebayer() && checkDebayer())
461 if (m_isTemporary && m_TemporaryDataFile.
open())
463 m_TemporaryDataFile.
write(buffer);
464 m_TemporaryDataFile.
close();
465 m_Filename = m_TemporaryDataFile.
fileName();
469 calculateStats(
false,
false);
472 calculateStats(
false,
false);
474 if (m_Mode == FITS_NORMAL || m_Mode == FITS_ALIGN)
477 starsSearched =
false;
482bool FITSData::loadXISFImage(
const QByteArray &buffer)
484 m_HistogramConstructed =
false;
490 LibXISF::XISFReader xisfReader;
497 LibXISF::ByteArray byteArray(buffer.
constData(), buffer.
size());
498 xisfReader.open(byteArray);
501 if (xisfReader.imagesCount() == 0)
503 m_LastError =
i18n(
"File contain no images");
507 const LibXISF::Image &image = xisfReader.getImage(0);
509 switch (image.sampleFormat())
511 case LibXISF::Image::UInt8:
512 m_Statistics.dataType = TBYTE;
513 m_Statistics.bytesPerPixel =
sizeof(LibXISF::UInt8);
514 m_FITSBITPIX = TBYTE;
516 case LibXISF::Image::UInt16:
517 m_Statistics.dataType = TUSHORT;
518 m_Statistics.bytesPerPixel =
sizeof(LibXISF::UInt16);
519 m_FITSBITPIX = TUSHORT;
521 case LibXISF::Image::UInt32:
522 m_Statistics.dataType = TULONG;
523 m_Statistics.bytesPerPixel =
sizeof(LibXISF::UInt32);
524 m_FITSBITPIX = TULONG;
526 case LibXISF::Image::Float32:
527 m_Statistics.dataType = TFLOAT;
528 m_Statistics.bytesPerPixel =
sizeof(LibXISF::Float32);
529 m_FITSBITPIX = TFLOAT;
532 m_LastError =
i18n(
"Sample format %1 is not supported.", LibXISF::Image::sampleFormatString(image.sampleFormat()).c_str());
533 qCCritical(KSTARS_FITS) << m_LastError;
537 m_Statistics.width = image.width();
538 m_Statistics.height = image.height();
539 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height;
540 m_Statistics.channels = image.channelCount();
541 m_Statistics.size = buffer.
size();
542 roiCenter.
setX(m_Statistics.width / 2);
543 roiCenter.
setY(m_Statistics.height / 2);
544 if(m_Statistics.width % 2)
545 roiCenter.
setX(roiCenter.
x() + 1);
546 if(m_Statistics.height % 2)
547 roiCenter.
setY(roiCenter.
y() + 1);
549 m_HeaderRecords.clear();
550 auto &fitsKeywords = image.fitsKeywords();
551 for(
auto &fitsKeyword : fitsKeywords)
555 if (getRecordValue(
"DATE-OBS", value) && value.
isValid())
561 m_ImageBufferSize = image.imageDataSize();
562 m_ImageBuffer =
new uint8_t[m_ImageBufferSize];
563 std::memcpy(m_ImageBuffer, image.imageData(), m_ImageBufferSize);
565 calculateStats(
false,
false);
568 catch (LibXISF::Error &error)
570 m_LastError =
i18n(
"XISF file open error: ") +
error.what();
581bool FITSData::saveXISFImage(
const QString &newFilename)
586 LibXISF::XISFWriter xisfWriter;
587 LibXISF::Image image;
588 image.setGeometry(m_Statistics.width, m_Statistics.height, m_Statistics.channels);
589 if (m_Statistics.channels > 1)
590 image.setColorSpace(LibXISF::Image::RGB);
592 switch (m_FITSBITPIX)
595 image.setSampleFormat(LibXISF::Image::UInt8);
598 image.setSampleFormat(LibXISF::Image::UInt16);
601 image.setSampleFormat(LibXISF::Image::UInt32);
604 image.setSampleFormat(LibXISF::Image::Float32);
607 m_LastError =
i18n(
"Bit depth %1 is not supported.", m_FITSBITPIX);
608 qCCritical(KSTARS_FITS) << m_LastError;
612 std::memcpy(image.imageData(), m_ImageBuffer, m_ImageBufferSize);
613 for (
auto &fitsKeyword : m_HeaderRecords)
614 image.addFITSKeyword({fitsKeyword.key.toUtf8().data(), fitsKeyword.value.toString().toUtf8().data(), fitsKeyword.comment.toUtf8().data()});
616 xisfWriter.writeImage(image);
618 m_Filename = newFilename;
620 catch (LibXISF::Error &err)
622 m_LastError =
i18n(
"Error saving XISF image") + err.what();
627 Q_UNUSED(newFilename)
632bool FITSData::loadCanonicalImage(
const QByteArray &buffer)
639 qCCritical(KSTARS_FITS) <<
"Failed to open image.";
643 m_Statistics.size = buffer.
size();
649 qCCritical(KSTARS_FITS) <<
"Failed to open image.";
661 m_FITSBITPIX = BYTE_IMG;
662 switch (m_FITSBITPIX)
665 m_Statistics.dataType = TBYTE;
666 m_Statistics.bytesPerPixel =
sizeof(uint8_t);
670 m_Statistics.dataType = TUSHORT;
671 m_Statistics.bytesPerPixel =
sizeof(int16_t);
674 m_Statistics.dataType = TUSHORT;
675 m_Statistics.bytesPerPixel =
sizeof(uint16_t);
679 m_Statistics.dataType = TULONG;
680 m_Statistics.bytesPerPixel =
sizeof(int32_t);
683 m_Statistics.dataType = TULONG;
684 m_Statistics.bytesPerPixel =
sizeof(uint32_t);
687 m_Statistics.dataType = TFLOAT;
688 m_Statistics.bytesPerPixel =
sizeof(float);
691 m_Statistics.dataType = TLONGLONG;
692 m_Statistics.bytesPerPixel =
sizeof(int64_t);
695 m_Statistics.dataType = TDOUBLE;
696 m_Statistics.bytesPerPixel =
sizeof(double);
699 m_LastError =
QString(
"Bit depth %1 is not supported.").
arg(m_FITSBITPIX);
700 qCCritical(KSTARS_FITS) << m_LastError;
704 m_Statistics.width =
static_cast<uint16_t
>(imageFromFile.
width());
705 m_Statistics.height =
static_cast<uint16_t
>(imageFromFile.
height());
707 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height;
709 m_ImageBufferSize = m_Statistics.samples_per_channel * m_Statistics.channels *
static_cast<uint16_t
>
710 (m_Statistics.bytesPerPixel);
711 m_ImageBuffer =
new uint8_t[m_ImageBufferSize];
712 if (m_ImageBuffer ==
nullptr)
714 m_LastError =
i18n(
"FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ", m_ImageBufferSize);
715 qCCritical(KSTARS_FITS) << m_LastError;
720 if (m_Statistics.channels == 1)
722 memcpy(m_ImageBuffer, imageFromFile.
bits(), m_ImageBufferSize);
727 auto debayered_buffer =
reinterpret_cast<uint8_t *
>(m_ImageBuffer);
728 auto * original_bayered_buffer =
reinterpret_cast<uint8_t *
>(imageFromFile.
bits());
731 uint8_t * rBuff = debayered_buffer;
732 uint8_t * gBuff = debayered_buffer + (m_Statistics.width * m_Statistics.height);
733 uint8_t * bBuff = debayered_buffer + (m_Statistics.width * m_Statistics.height * 2);
735 int imax = m_Statistics.samples_per_channel * 4 - 4;
736 for (
int i = 0; i <= imax; i += 4)
738 *rBuff++ = original_bayered_buffer[i + 2];
739 *gBuff++ = original_bayered_buffer[i + 1];
740 *bBuff++ = original_bayered_buffer[i + 0];
744 calculateStats(
false,
false);
748bool FITSData::loadRAWImage(
const QByteArray &buffer)
750#if !defined(KSTARS_LITE) && !defined(HAVE_LIBRAW)
751 m_LastError =
i18n(
"Unable to find dcraw and cjpeg. Please install the required tools to convert CR2/NEF to JPEG.");
765 m_LastError =
i18n(
"Cannot open file %1: %2", m_Filename, libraw_strerror(ret));
766 RawProcessor.recycle();
775 if ((ret = RawProcessor.open_buffer(
const_cast<void *
>(
reinterpret_cast<const void *
>(buffer.
data())),
776 buffer.
size())) != LIBRAW_SUCCESS)
778 m_LastError =
i18n(
"Cannot open buffer: %1", libraw_strerror(ret));
779 RawProcessor.recycle();
783 m_Statistics.size = buffer.
size();
787 if ((ret = RawProcessor.unpack()) != LIBRAW_SUCCESS)
789 m_LastError =
i18n(
"Cannot unpack_thumb: %1", libraw_strerror(ret));
790 RawProcessor.recycle();
794 if ((ret = RawProcessor.dcraw_process()) != LIBRAW_SUCCESS)
796 m_LastError =
i18n(
"Cannot dcraw_process: %1", libraw_strerror(ret));
797 RawProcessor.recycle();
801 libraw_processed_image_t *image = RawProcessor.dcraw_make_mem_image(&ret);
802 if (ret != LIBRAW_SUCCESS)
804 m_LastError =
i18n(
"Cannot load to memory: %1", libraw_strerror(ret));
805 RawProcessor.recycle();
809 RawProcessor.recycle();
811 m_Statistics.bytesPerPixel = image->bits / 8;
813 if (m_Statistics.bytesPerPixel == 1)
814 m_Statistics.dataType = TBYTE;
816 m_Statistics.dataType = TUSHORT;
817 m_Statistics.width = image->width;
818 m_Statistics.height = image->height;
819 m_Statistics.channels = image->colors;
820 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height;
822 m_ImageBufferSize = m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel;
823 m_ImageBuffer =
new uint8_t[m_ImageBufferSize];
824 if (m_ImageBuffer ==
nullptr)
826 m_LastError =
i18n(
"FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ", m_ImageBufferSize);
827 qCCritical(KSTARS_FITS) << m_LastError;
828 libraw_dcraw_clear_mem(image);
833 auto destination_buffer =
reinterpret_cast<uint8_t *
>(m_ImageBuffer);
834 auto source_buffer =
reinterpret_cast<uint8_t *
>(image->data);
837 if (image->colors == 1)
839 memcpy(destination_buffer, source_buffer, m_ImageBufferSize);
844 uint8_t * rBuff = destination_buffer;
845 uint8_t * gBuff = destination_buffer + (m_Statistics.width * m_Statistics.height);
846 uint8_t * bBuff = destination_buffer + (m_Statistics.width * m_Statistics.height * 2);
848 int imax = m_Statistics.samples_per_channel * 3 - 3;
849 for (
int i = 0; i <= imax; i += 3)
851 *rBuff++ = source_buffer[i + 0];
852 *gBuff++ = source_buffer[i + 1];
853 *bBuff++ = source_buffer[i + 2];
856 libraw_dcraw_clear_mem(image);
858 calculateStats(
false,
false);
863bool FITSData::saveImage(
const QString &newFilename)
865 if (newFilename == m_Filename)
870 if (ext ==
"jpg" || ext ==
"png")
873 QImage fitsImage = FITSToImage(newFilename);
874 getMinMax(&min, &max);
880 else if (channels() == 1)
885 for (
int i = 0; i < 256; i++)
886 fitsImage.
setColor(i, qRgb(i, i, i));
893 double dataMin = m_Statistics.mean[0] - m_Statistics.stddev[0];
894 double dataMax = m_Statistics.mean[0] + m_Statistics.stddev[0] * 3;
896 double bscale = 255. / (dataMax - dataMin);
897 double bzero = (-dataMin) * (255. / (dataMax - dataMin));
900 switch (m_Statistics.dataType)
903 convertToQImage<uint8_t>(dataMin, dataMax, bscale, bzero, fitsImage);
907 convertToQImage<int16_t>(dataMin, dataMax, bscale, bzero, fitsImage);
911 convertToQImage<uint16_t>(dataMin, dataMax, bscale, bzero, fitsImage);
915 convertToQImage<int32_t>(dataMin, dataMax, bscale, bzero, fitsImage);
919 convertToQImage<uint32_t>(dataMin, dataMax, bscale, bzero, fitsImage);
923 convertToQImage<float>(dataMin, dataMax, bscale, bzero, fitsImage);
927 convertToQImage<int64_t>(dataMin, dataMax, bscale, bzero, fitsImage);
931 convertToQImage<double>(dataMin, dataMax, bscale, bzero, fitsImage);
940 m_Filename = newFilename;
941 qCInfo(KSTARS_FITS) <<
"Saved image file:" << m_Filename;
947 m_LastError =
i18n(
"Saving compressed files is not supported.");
959 fits_close_file(fptr, &status);
962 rotCounter = flipHCounter = flipVCounter = 0;
963 return saveXISFImage(newFilename);
1002 nelements = m_Statistics.samples_per_channel * m_Statistics.channels;
1005 if (fptr && fits_close_file(fptr, &status))
1007 m_LastError =
i18n(
"Failed to close file: %1", fitsErrorToString(status));
1012 if (fits_create_file(&new_fptr,
QString(
"!%1").arg(newFilename).toLocal8Bit(), &status))
1014 m_LastError =
i18n(
"Failed to create file: %1", fitsErrorToString(status));
1023 long naxis = m_Statistics.channels == 1 ? 2 : 3;
1024 long naxes[3] = {m_Statistics.width, m_Statistics.height, naxis};
1027 if (fits_create_img(fptr, m_FITSBITPIX, naxis, naxes, &status))
1029 m_LastError =
i18n(
"Failed to create image: %1", fitsErrorToString(status));
1036 if (fits_update_key(fptr, TDOUBLE,
"DATAMIN", &(m_Statistics.min),
"Minimum value", &status))
1038 m_LastError =
i18n(
"Failed to update key: %1", fitsErrorToString(status));
1043 if (fits_update_key(fptr, TDOUBLE,
"DATAMAX", &(m_Statistics.max),
"Maximum value", &status))
1045 m_LastError =
i18n(
"Failed to update key: %1", fitsErrorToString(status));
1050 fits_write_key(fptr, TDOUBLE,
"MIN1", &m_Statistics.min[0],
"Min Channel 1", &status);
1053 fits_write_key(fptr, TDOUBLE,
"MIN2", &m_Statistics.min[1],
"Min Channel 2", &status);
1054 fits_write_key(fptr, TDOUBLE,
"MIN3", &m_Statistics.min[2],
"Min Channel 3", &status);
1058 fits_write_key(fptr, TDOUBLE,
"MAX1", &m_Statistics.max[0],
"Max Channel 1", &status);
1061 fits_write_key(fptr, TDOUBLE,
"MAX2", &m_Statistics.max[1],
"Max Channel 2", &status);
1062 fits_write_key(fptr, TDOUBLE,
"MAX3", &m_Statistics.max[2],
"Max Channel 3", &status);
1066 if (m_Statistics.mean[0] > 0)
1068 fits_write_key(fptr, TDOUBLE,
"MEAN1", &m_Statistics.mean[0],
"Mean Channel 1", &status);
1071 fits_write_key(fptr, TDOUBLE,
"MEAN2", &m_Statistics.mean[1],
"Mean Channel 2", &status);
1072 fits_write_key(fptr, TDOUBLE,
"MEAN3", &m_Statistics.mean[2],
"Mean Channel 3", &status);
1077 if (m_Statistics.median[0] > 0)
1079 fits_write_key(fptr, TDOUBLE,
"MEDIAN1", &m_Statistics.median[0],
"Median Channel 1", &status);
1082 fits_write_key(fptr, TDOUBLE,
"MEDIAN2", &m_Statistics.median[1],
"Median Channel 2", &status);
1083 fits_write_key(fptr, TDOUBLE,
"MEDIAN3", &m_Statistics.median[2],
"Median Channel 3", &status);
1088 if (m_Statistics.stddev[0] > 0)
1090 fits_write_key(fptr, TDOUBLE,
"STDDEV1", &m_Statistics.stddev[0],
"Standard Deviation Channel 1", &status);
1093 fits_write_key(fptr, TDOUBLE,
"STDDEV2", &m_Statistics.stddev[1],
"Standard Deviation Channel 2", &status);
1094 fits_write_key(fptr, TDOUBLE,
"STDDEV3", &m_Statistics.stddev[2],
"Standard Deviation Channel 3", &status);
1099 for (
int i = 10; i < m_HeaderRecords.count(); i++)
1101 QString key = m_HeaderRecords[i].key;
1105 switch (value.
type())
1110 fits_write_key(fptr, TINT, key.
toLatin1().
constData(), &number, comment, &status);
1117 fits_write_key(fptr, TDOUBLE, key.
toLatin1().
constData(), &number, comment, &status);
1124 char valueBuffer[256] = {0};
1126 fits_write_key(fptr, TSTRING, key.
toLatin1().
constData(), valueBuffer, comment, &status);
1132 if (fits_write_date(fptr, &status))
1134 m_LastError =
i18n(
"Failed to update date: %1", fitsErrorToString(status));
1141 if (fits_write_history(fptr, history.
toLatin1(), &status))
1143 m_LastError =
i18n(
"Failed to update history: %1", fitsErrorToString(status));
1147 int rot = 0, mirror = 0;
1150 rot = (90 * rotCounter) % 360;
1154 if (flipHCounter % 2 != 0 || flipVCounter % 2 != 0)
1157 if ((rot != 0) || (mirror != 0))
1158 rotWCSFITS(rot, mirror);
1160 rotCounter = flipHCounter = flipVCounter = 0;
1163 if (fits_write_img(fptr, m_Statistics.dataType, 1, nelements, m_ImageBuffer, &status))
1165 m_LastError =
i18n(
"Failed to write image: %1", fitsErrorToString(status));
1169 m_Filename = newFilename;
1171 fits_flush_file(fptr, &status);
1173 qCInfo(KSTARS_FITS) <<
"Saved FITS file:" << m_Filename;
1178void FITSData::clearImageBuffers()
1180 delete[] m_ImageBuffer;
1181 m_ImageBuffer =
nullptr;
1182 if(m_ImageRoiBuffer !=
nullptr )
1184 delete[] m_ImageRoiBuffer;
1185 m_ImageRoiBuffer =
nullptr;
1191void FITSData::makeRoiBuffer(
QRect roi)
1196 uint32_t channelSize = roi.
height() * roi.
width();
1197 if(channelSize > m_Statistics.samples_per_channel || channelSize == 1)
1201 if(m_ImageRoiBuffer !=
nullptr )
1203 delete[] m_ImageRoiBuffer;
1204 m_ImageRoiBuffer =
nullptr;
1206 int xoffset = roi.
topLeft().
x() - 1;
1207 int yoffset = roi.
topLeft().
y() - 1;
1208 uint32_t bpp = m_Statistics.bytesPerPixel;
1209 m_ImageRoiBuffer =
new uint8_t[roi.
height()*roi.
width()*m_Statistics.channels * m_Statistics.bytesPerPixel]();
1210 for(
int n = 0 ; n < m_Statistics.channels ; n++)
1212 for(
int i = 0; i < roi.
height(); i++)
1214 size_t i1 = n * channelSize * bpp + i * roi.
width() * bpp;
1215 size_t i2 = n * m_Statistics.samples_per_channel * bpp + (yoffset + i) * width() * bpp + xoffset * bpp;
1216 memcpy(&m_ImageRoiBuffer[i1],
1222 memcpy(&m_ROIStatistics, &m_Statistics,
sizeof(FITSImage::Statistic));
1223 m_ROIStatistics.samples_per_channel = roi.
height() * roi.
width();
1224 m_ROIStatistics.width = roi.
width();
1225 m_ROIStatistics.height = roi.
height();
1226 calculateStats(
false,
true);
1228void FITSData::calculateStats(
bool refresh,
bool roi)
1233 calculateMinMax(refresh);
1234 calculateMedian(refresh);
1237 if (refresh ==
false && fptr)
1241 if (fits_read_key_dbl(fptr,
"MEAN1", &m_Statistics.mean[0],
nullptr, &status) == 0)
1244 fits_read_key_dbl(fptr,
"MEAN2", & m_Statistics.mean[1],
nullptr, &status);
1245 fits_read_key_dbl(fptr,
"MEAN3", &m_Statistics.mean[2],
nullptr, &status);
1248 if (fits_read_key_dbl(fptr,
"STDDEV1", &m_Statistics.stddev[0],
nullptr, &status) == 0)
1251 fits_read_key_dbl(fptr,
"STDDEV2", &m_Statistics.stddev[1],
nullptr, &status);
1252 fits_read_key_dbl(fptr,
"STDDEV3", &m_Statistics.stddev[2],
nullptr, &status);
1260 switch (m_Statistics.dataType)
1263 calculateStdDev<uint8_t>();
1267 calculateStdDev<int16_t>();
1271 calculateStdDev<uint16_t>();
1275 calculateStdDev<int32_t>();
1279 calculateStdDev<uint32_t>();
1283 calculateStdDev<float>();
1287 calculateStdDev<int64_t>();
1291 calculateStdDev<double>();
1299 m_Statistics.SNR = m_Statistics.mean[0] / m_Statistics.stddev[0];
1303 calculateMinMax(refresh, roi);
1304 calculateMedian(refresh, roi);
1306 switch (m_ROIStatistics.dataType)
1309 calculateStdDev<uint8_t>(roi);
1313 calculateStdDev<int16_t>(roi);
1317 calculateStdDev<uint16_t>(roi);
1321 calculateStdDev<int32_t>(roi);
1325 calculateStdDev<uint32_t>(roi);
1329 calculateStdDev<float>(roi);
1333 calculateStdDev<int64_t>(roi);
1337 calculateStdDev<double>(roi);
1346void FITSData::calculateMinMax(
bool refresh,
bool roi)
1356 if (fptr !=
nullptr && !refresh)
1361 if (fits_read_key_dbl(fptr,
"DATAMIN", &(m_Statistics.min[0]),
nullptr, &status) == 0)
1363 else if (fits_read_key_dbl(fptr,
"MIN1", &(m_Statistics.min[0]),
nullptr, &status) == 0)
1367 fits_read_key_dbl(fptr,
"MIN2", &m_Statistics.min[1],
nullptr, &status);
1368 fits_read_key_dbl(fptr,
"MIN3", &m_Statistics.min[2],
nullptr, &status);
1372 if (fits_read_key_dbl(fptr,
"DATAMAX", &(m_Statistics.max[0]),
nullptr, &status) == 0)
1374 else if (fits_read_key_dbl(fptr,
"MAX1", &(m_Statistics.max[0]),
nullptr, &status) == 0)
1378 fits_read_key_dbl(fptr,
"MAX2", &m_Statistics.max[1],
nullptr, &status);
1379 fits_read_key_dbl(fptr,
"MAX3", &m_Statistics.max[2],
nullptr, &status);
1382 if (nfound == 2 && !(m_Statistics.min[0] == 0 && m_Statistics.max[0] == 0))
1386 m_Statistics.min[0] = 1.0E30;
1387 m_Statistics.max[0] = -1.0E30;
1389 m_Statistics.min[1] = 1.0E30;
1390 m_Statistics.max[1] = -1.0E30;
1392 m_Statistics.min[2] = 1.0E30;
1393 m_Statistics.max[2] = -1.0E30;
1395 switch (m_Statistics.dataType)
1398 calculateMinMax<uint8_t>();
1402 calculateMinMax<int16_t>();
1406 calculateMinMax<uint16_t>();
1410 calculateMinMax<int32_t>();
1414 calculateMinMax<uint32_t>();
1418 calculateMinMax<float>();
1422 calculateMinMax<int64_t>();
1426 calculateMinMax<double>();
1435 m_ROIStatistics.min[0] = 1.0E30;
1436 m_ROIStatistics.max[0] = -1.0E30;
1438 m_ROIStatistics.min[1] = 1.0E30;
1439 m_ROIStatistics.max[1] = -1.0E30;
1441 m_ROIStatistics.min[2] = 1.0E30;
1442 m_ROIStatistics.max[2] = -1.0E30;
1444 switch (m_Statistics.dataType)
1447 calculateMinMax<uint8_t>(roi);
1451 calculateMinMax<int16_t>(roi);
1455 calculateMinMax<uint16_t>(roi);
1459 calculateMinMax<int32_t>(roi);
1463 calculateMinMax<uint32_t>(roi);
1467 calculateMinMax<float>(roi);
1471 calculateMinMax<int64_t>(roi);
1475 calculateMinMax<double>(roi);
1485void FITSData::calculateMedian(
bool refresh,
bool roi)
1495 if (fptr !=
nullptr && !refresh)
1498 if (fits_read_key_dbl(fptr,
"MEDIAN1", &m_Statistics.median[0],
nullptr, &status) == 0)
1502 fits_read_key_dbl(fptr,
"MEDIAN2", &m_Statistics.median[1],
nullptr, &status);
1503 fits_read_key_dbl(fptr,
"MEDIAN3", &m_Statistics.median[2],
nullptr, &status);
1509 m_Statistics.median[RED_CHANNEL] = 0;
1510 m_Statistics.median[GREEN_CHANNEL] = 0;
1511 m_Statistics.median[BLUE_CHANNEL] = 0;
1513 switch (m_Statistics.dataType)
1516 calculateMedian<uint8_t>();
1520 calculateMedian<int16_t>();
1524 calculateMedian<uint16_t>();
1528 calculateMedian<int32_t>();
1532 calculateMedian<uint32_t>();
1536 calculateMedian<float>();
1540 calculateMedian<int64_t>();
1544 calculateMedian<double>();
1553 m_ROIStatistics.median[RED_CHANNEL] = 0;
1554 m_ROIStatistics.median[GREEN_CHANNEL] = 0;
1555 m_ROIStatistics.median[BLUE_CHANNEL] = 0;
1557 switch (m_ROIStatistics.dataType)
1560 calculateMedian<uint8_t>(roi);
1564 calculateMedian<int16_t>(roi);
1568 calculateMedian<uint16_t>(roi);
1572 calculateMedian<int32_t>(roi);
1576 calculateMedian<uint32_t>(roi);
1580 calculateMedian<float>(roi);
1584 calculateMedian<int64_t>(roi);
1588 calculateMedian<double>(roi);
1598template <
typename T>
1599void FITSData::calculateMedian(
bool roi)
1601 auto * buffer =
reinterpret_cast<T *
>(roi ? m_ImageRoiBuffer : m_ImageBuffer);
1602 const uint32_t maxMedianSize = 500000;
1603 uint32_t medianSize = roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel;
1604 uint8_t downsample = 1;
1605 if (medianSize > maxMedianSize)
1607 downsample = (
static_cast<double>(medianSize) / maxMedianSize) + 0.999;
1608 medianSize /= downsample;
1614 std::vector<int32_t> samples;
1615 samples.reserve(medianSize);
1617 for (uint8_t n = 0; n < m_Statistics.channels; n++)
1619 auto *oneChannel = buffer + n * (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel);
1620 for (uint32_t upto = 0; upto < (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel);
1623 auto median = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_MEDIAN, samples);
1624 roi ? m_ROIStatistics.median[n] = median : m_Statistics.median[n] = median;
1628template <
typename T>
1629QPair<T, T> FITSData::getParitionMinMax(uint32_t start, uint32_t stride,
bool roi)
1631 auto * buffer =
reinterpret_cast<T *
>(roi ? m_ImageRoiBuffer : m_ImageBuffer);
1632 T min = std::numeric_limits<T>::max();
1633 T max = std::numeric_limits<T>::min();
1635 uint32_t
end = start + stride;
1637 for (uint32_t i = start; i <
end; i++)
1639 min = qMin(buffer[i], min);
1640 max = qMax(buffer[i], max);
1647 return qMakePair(min, max);
1650template <
typename T>
1651void FITSData::calculateMinMax(
bool roi)
1653 T min = std::numeric_limits<T>::max();
1654 T max = std::numeric_limits<T>::min();
1658 const uint8_t nThreads = 16;
1660 for (
int n = 0; n < m_Statistics.channels; n++)
1662 uint32_t cStart = n * (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel);
1665 uint32_t tStride = (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) / nThreads;
1668 uint32_t fStride = tStride + ((roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) -
1669 (tStride * nThreads));
1672 uint32_t tStart = cStart;
1677 for (
int i = 0; i < nThreads; i++)
1680#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1681 futures.
append(
QtConcurrent::run(&FITSData::getParitionMinMax<T>,
this, tStart, (i == (nThreads - 1)) ? fStride : tStride,
1684 futures.
append(
QtConcurrent::run(
this, &FITSData::getParitionMinMax<T>, tStart, (i == (nThreads - 1)) ? fStride : tStride,
1691 for (
int i = 0; i < nThreads; i++)
1693 QPair<T, T> result = futures[i].result();
1694 min = qMin(result.first, min);
1695 max = qMax(result.second, max);
1700 m_Statistics.min[n] = min;
1701 m_Statistics.max[n] = max;
1705 m_ROIStatistics.min[n] = min;
1706 m_ROIStatistics.max[n] = max;
1719 SumData(
double s,
double sq,
int n) : sum(s), squaredSum(sq), numSamples(n) {}
1720 SumData() : sum(0), squaredSum(0), numSamples(0) {}
1723template <
typename T>
1724SumData getSumAndSquaredSum(uint32_t start, uint32_t stride, uint8_t *buff)
1726 auto * buffer =
reinterpret_cast<T *
>(buff);
1727 const uint32_t
end = start + stride;
1729 double squaredSum = 0;
1730 for (uint32_t i = start; i <
end; i++)
1732 double sample = buffer[i];
1734 squaredSum += sample * sample;
1736 const double numSamples =
end - start;
1737 return SumData(sum, squaredSum, numSamples);
1740template <
typename T>
1741void FITSData::calculateStdDev(
bool roi )
1744 const uint8_t nThreads = 16;
1746 for (
int n = 0; n < m_Statistics.channels; n++)
1748 uint32_t cStart = n * (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel);
1751 uint32_t tStride = (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) / nThreads;
1754 uint32_t fStride = tStride + ((roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) -
1755 (tStride * nThreads));
1758 uint32_t tStart = cStart;
1763 for (
int i = 0; i < nThreads; i++)
1766 uint8_t *buff = roi ? m_ImageRoiBuffer : m_ImageBuffer;
1768 (i == (nThreads - 1)) ? fStride : tStride, buff));
1773 double sum = 0, squared_sum = 0;
1774 double numSamples = 0;
1775 for (
int i = 0; i < nThreads; i++)
1777 const SumData result = futures[i].result();
1779 squared_sum += result.squaredSum;
1780 numSamples += result.numSamples;
1783 if (numSamples <= 0)
continue;
1784 const double mean = sum / numSamples;
1785 const double variance = squared_sum / numSamples - mean * mean;
1788 m_Statistics.mean[n] = mean;
1789 m_Statistics.stddev[n] = sqrt(variance);
1793 m_ROIStatistics.mean[n] = mean;
1794 m_ROIStatistics.stddev[n] = sqrt(variance);
1802 kernel.fill(0.0, size * size);
1804 double kernelSum = 0.0;
1805 int fOff = (size - 1) / 2;
1806 double normal = 1.0 / (2.0 * M_PI * sigma * sigma);
1807 for (
int y = -fOff; y <= fOff; y++)
1809 for (
int x = -fOff; x <= fOff; x++)
1811 double distance = ((y * y) + (x * x)) / (2.0 * sigma * sigma);
1812 int index = (y + fOff) * size + (x + fOff);
1813 kernel[index] = normal * qExp(-distance);
1814 kernelSum += kernel.at(index);
1817 for (
int y = 0; y < size; y++)
1819 for (
int x = 0; x < size; x++)
1821 int index = y * size + x;
1822 kernel[index] = kernel.at(index) * 1.0 / kernelSum;
1829template <
typename T>
1830void FITSData::convolutionFilter(
const QVector<double> &kernel,
int kernelSize)
1832 T * imagePtr =
reinterpret_cast<T *
>(m_ImageBuffer);
1838 int fOff = (kernelSize - 1) / 2;
1842 for (
int offsetY = 0; offsetY < m_Statistics.height; offsetY++)
1844 for (
int offsetX = 0; offsetX < m_Statistics.width; offsetX++)
1849 int byteOffset = offsetY * m_Statistics.width + offsetX;
1852 for (
int filterY = -fOff; filterY <= fOff; filterY++)
1854 for (
int filterX = -fOff; filterX <= fOff; filterX++)
1856 if ((offsetY + filterY) >= 0 && (offsetY + filterY) < m_Statistics.height
1857 && ((offsetX + filterX) >= 0 && (offsetX + filterX) < m_Statistics.width ))
1860 int calcOffset = byteOffset + filterX + filterY * m_Statistics.width;
1861 int index = (filterY + fOff) * kernelSize + (filterX + fOff);
1862 double kernelValue = kernel.
at(index);
1863 gt += (imagePtr[calcOffset]) * kernelValue;
1869 imagePtr[byteOffset] = gt;
1874template <
typename T>
1875void FITSData::gaussianBlur(
int kernelSize,
double sigma)
1878 if (kernelSize % 2 == 0)
1881 qCInfo(KSTARS_FITS) <<
"Warning, size must be an odd number, correcting size to " << kernelSize;
1889 QVector<double> gaussianKernel = createGaussianKernel(kernelSize, sigma);
1890 convolutionFilter<T>(gaussianKernel, kernelSize);
1893void FITSData::setMinMax(
double newMin,
double newMax, uint8_t channel)
1895 m_Statistics.min[channel] = newMin;
1896 m_Statistics.max[channel] = newMax;
1899bool FITSData::parseHeader()
1901 char * header =
nullptr;
1902 int status = 0, nkeys = 0;
1904 if (fits_hdr2str(fptr, 0,
nullptr, 0, &header, &nkeys, &status))
1906 fits_report_error(stderr, status);
1911 m_HeaderRecords.clear();
1914 for (
int i = 0; i < nkeys; i++)
1924 oneRecord.comment =
properties[0].mid(8).simplified();
1929 oneRecord.value =
properties[1].simplified();
1931 oneRecord.comment =
properties[2].simplified();
1938 oneRecord.value.toInt(&ok);
1944 oneRecord.value.toDouble(&ok);
1950 m_HeaderRecords.
append(oneRecord);
1958bool FITSData::getRecordValue(
const QString &key,
QVariant &value)
const
1960 auto result = std::find_if(m_HeaderRecords.begin(), m_HeaderRecords.end(), [&key](
const Record & oneRecord)
1962 return (oneRecord.key == key && oneRecord.value.isValid());
1965 if (result != m_HeaderRecords.end())
1967 value = (*result).
value;
1973bool FITSData::parseSolution(FITSImage::Solution &solution)
const
1976 bool raOK =
false, deOK =
false, coordOK =
false, scaleOK =
false;
1980 solution.fieldWidth = solution.fieldHeight = solution.pixscale = solution.ra = solution.dec = -1;
1983 if (getRecordValue(
"OBJCTRA", value))
1986 solution.ra = angleValue.
Degrees();
1989 else if (getRecordValue(
"RA", value))
1991 solution.ra = value.
toDouble(&raOK);
1995 if (getRecordValue(
"OBJCTDEC", value))
1998 solution.dec = angleValue.
Degrees();
2001 else if (getRecordValue(
"DEC", value))
2003 solution.dec = value.
toDouble(&deOK);
2006 coordOK = raOK && deOK;
2010 if (getRecordValue(
"SCALE", value))
2015 double focal_length = -1;
2016 if (getRecordValue(
"FOCALLEN", value))
2021 double pixsize1 = -1, pixsize2 = -1;
2023 if (getRecordValue(
"PIXSIZE1", value))
2028 if (getRecordValue(
"PIXSIZE2", value))
2033 int binx = 1, biny = 1;
2035 if (getRecordValue(
"XBINNING", value))
2040 if (getRecordValue(
"YBINNING", value))
2045 if (pixsize1 > 0 && pixsize2 > 0)
2051 solution.pixscale = scale;
2053 solution.fieldWidth = (m_Statistics.width * scale) / 60.0;
2055 solution.fieldHeight = (m_Statistics.height * scale * (pixsize2 / pixsize1)) / 60.0;
2057 else if (focal_length > 0)
2060 solution.fieldWidth = ((206264.8062470963552 * m_Statistics.width * (pixsize1 / 1000.0)) / (focal_length * binx)) / 60.0;
2062 solution.fieldHeight = ((206264.8062470963552 * m_Statistics.height * (pixsize2 / 1000.0)) / (focal_length * biny)) / 60.0;
2064 solution.pixscale = (206264.8062470963552 * (pixsize1 / 1000.0)) / (focal_length * binx);
2070 return (coordOK || scaleOK);
2078 starAlgorithm = algorithm;
2079 qDeleteAll(starCenters);
2080 starCenters.
clear();
2081 starsSearched =
true;
2087 m_StarDetector.
reset(
new FITSSEPDetector(
this));
2088 m_StarDetector->setSettings(m_SourceExtractorSettings);
2089 if (m_Mode == FITS_NORMAL && trackingBox.
isNull())
2091 if (Options::quickHFR())
2094 const int w = getStatistics().width;
2095 const int h = getStatistics().height;
2096 QRect middle(
static_cast<int>(w * 0.25),
static_cast<int>(h * 0.25), w / 2, h / 2);
2097 m_StarFindFuture = m_StarDetector->findSources(middle);
2098 return m_StarFindFuture;
2101 m_StarFindFuture = m_StarDetector->findSources(trackingBox);
2102 return m_StarFindFuture;
2105 case ALGORITHM_GRADIENT:
2108 m_StarDetector.
reset(
new FITSGradientDetector(
this));
2109 m_StarDetector->setSettings(m_SourceExtractorSettings);
2110 m_StarFindFuture = m_StarDetector->findSources(trackingBox);
2111 return m_StarFindFuture;
2114 case ALGORITHM_CENTROID:
2117 m_StarDetector.
reset(
new FITSCentroidDetector(
this));
2118 m_StarDetector->setSettings(m_SourceExtractorSettings);
2120 if (!isHistogramConstructed())
2121 constructHistogram();
2122 m_StarDetector->configure(
"JMINDEX", m_JMIndex);
2123 m_StarFindFuture = m_StarDetector->findSources(trackingBox);
2124 return m_StarFindFuture;
2128 m_StarDetector.
reset(
new FITSCentroidDetector(
this));
2129 m_StarFindFuture = starDetector->findSources(trackingBox);
2130 return m_StarFindFuture;
2134 case ALGORITHM_THRESHOLD:
2136 m_StarDetector.
reset(
new FITSThresholdDetector(
this));
2137 m_StarDetector->setSettings(m_SourceExtractorSettings);
2138 m_StarDetector->configure(
"THRESHOLD_PERCENTAGE", Options::focusThreshold());
2139 m_StarFindFuture = m_StarDetector->findSources(trackingBox);
2140 return m_StarFindFuture;
2143 case ALGORITHM_BAHTINOV:
2145 m_StarDetector.
reset(
new FITSBahtinovDetector(
this));
2146 m_StarDetector->setSettings(m_SourceExtractorSettings);
2147 m_StarDetector->configure(
"NUMBER_OF_AVERAGE_ROWS", Options::focusMultiRowAverage());
2148 m_StarFindFuture = m_StarDetector->findSources(trackingBox);
2149 return m_StarFindFuture;
2156 if (mask.
isNull() ==
false)
2158 starCenters.
erase(std::remove_if(starCenters.
begin(), starCenters.
end(),
2161 return (mask->isVisible(edge->x, edge->y) == false);
2162 }), starCenters.
end());
2165 return starCenters.
count();
2169double FITSData::getHFR(HFRType type)
2171 if (starCenters.
empty())
2174 if (cacheHFR >= 0 && cacheHFRType == type)
2177 m_SelectedHFRStar.invalidate();
2179 if (type == HFR_MAX)
2184 for (
int i = 0; i < starCenters.
count(); i++)
2186 if (starCenters[i]->HFR > maxVal)
2189 maxVal = starCenters[i]->HFR;
2193 m_SelectedHFRStar = *starCenters[maxIndex];
2194 cacheHFR = starCenters[maxIndex]->HFR;
2195 cacheHFRType =
type;
2198 else if (type == HFR_HIGH)
2201 int minX = width() / 10;
2202 int minY = height() / 10;
2203 int maxX = width() - minX;
2204 int maxY = height() - minY;
2205 starCenters.
erase(std::remove_if(starCenters.
begin(), starCenters.
end(), [minX, minY, maxX, maxY](Edge * oneStar)
2207 return (oneStar->x < minX || oneStar->x > maxX || oneStar->y < minY || oneStar->y > maxY);
2208 }), starCenters.
end());
2210 if (starCenters.
empty())
2213 m_SelectedHFRStar = *starCenters[
static_cast<int>(starCenters.
size() * 0.05)];
2214 cacheHFR = m_SelectedHFRStar.HFR;
2215 cacheHFRType =
type;
2218 else if (type == HFR_MEDIAN)
2220 std::nth_element(starCenters.
begin(), starCenters.
begin() + starCenters.
size() / 2, starCenters.
end());
2221 m_SelectedHFRStar = *starCenters[starCenters.
size() / 2];
2223 cacheHFR = m_SelectedHFRStar.HFR;
2224 cacheHFRType =
type;
2232 const int saturationValue = m_Statistics.dataType == TBYTE ? 250 : 50000;
2233 int numSaturated = 0;
2234 for (
auto center : starCenters)
2235 if (
center->val > saturationValue)
2237 const bool removeSaturatedStars = starCenters.size() - numSaturated > 20;
2238 if (removeSaturatedStars && numSaturated > 0)
2239 qCDebug(KSTARS_FITS) <<
"Removing " << numSaturated <<
" stars from HFR calculation";
2241 std::vector<double> HFRs;
2243 for (
auto center : starCenters)
2245 if (removeSaturatedStars &&
center->val > saturationValue)
continue;
2247 if (type == HFR_AVERAGE)
2248 HFRs.push_back(
center->HFR);
2254 if (m_SkyBackground.mean <= 0.0 ||
center->val < m_SkyBackground.mean)
2256 HFRs.push_back(
center->HFR);
2257 qCDebug(KSTARS_FITS) <<
"HFR Adj, sky background " << m_SkyBackground.mean <<
" star peak " <<
center->val <<
2262 const double a_div_b =
center->val / m_SkyBackground.mean;
2263 const double factor = erf(sqrt(log(a_div_b))) / (1 - (1 / a_div_b));
2264 HFRs.push_back(
center->HFR * factor);
2265 qCDebug(KSTARS_FITS) <<
"HFR Adj, brightness adjusted from " <<
center->HFR <<
" to " <<
center->HFR * factor;
2270 auto m = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_SIGMACLIPPING,
2274 cacheHFRType = HFR_AVERAGE;
2278double FITSData::getHFR(
int x,
int y,
double scale)
2280 if (starCenters.empty())
2283 for (
int i = 0; i < starCenters.count(); i++)
2285 const int maxDist = std::max(2,
static_cast<int>(0.5 + 2 * starCenters[i]->width / scale));
2286 const int dx = std::fabs(starCenters[i]->x - x);
2287 const int dy = std::fabs(starCenters[i]->y - y);
2288 if (dx <= maxDist && dy <= maxDist)
2290 m_SelectedHFRStar = *starCenters[i];
2291 return starCenters[i]->HFR;
2298double FITSData::getEccentricity()
2300 if (starCenters.empty())
2302 if (cacheEccentricity >= 0)
2303 return cacheEccentricity;
2304 std::vector<float> eccs;
2305 for (
const auto &s : starCenters)
2306 eccs.push_back(s->ellipticity);
2307 int middle = eccs.size() / 2;
2308 std::nth_element(eccs.begin(), eccs.begin() + middle, eccs.end());
2309 float medianEllipticity = eccs[middle];
2315 const float eccentricity = sqrt(medianEllipticity * (2 - medianEllipticity));
2316 cacheEccentricity = eccentricity;
2317 return eccentricity;
2322 if (type == FITS_NONE)
2335 case FITS_AUTO_STRETCH:
2337 for (
int i = 0; i < 3; i++)
2339 dataMin[i] = m_Statistics.mean[i] - m_Statistics.stddev[i];
2340 dataMax[i] = m_Statistics.mean[i] + m_Statistics.stddev[i] * 3;
2345 case FITS_HIGH_CONTRAST:
2347 for (
int i = 0; i < 3; i++)
2349 dataMin[i] = m_Statistics.mean[i] + m_Statistics.stddev[i];
2350 dataMax[i] = m_Statistics.mean[i] + m_Statistics.stddev[i] * 3;
2355 case FITS_HIGH_PASS:
2357 for (
int i = 0; i < 3; i++)
2359 dataMin[i] = m_Statistics.mean[i];
2368 switch (m_Statistics.dataType)
2372 for (
int i = 0; i < 3; i++)
2374 dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i];
2375 dataMax[i] = dataMax[i] > UINT8_MAX ? UINT8_MAX : dataMax[i];
2377 applyFilter<uint8_t>(type, image, &dataMin, &dataMax);
2383 for (
int i = 0; i < 3; i++)
2385 dataMin[i] = dataMin[i] < INT16_MIN ? INT16_MIN : dataMin[i];
2386 dataMax[i] = dataMax[i] > INT16_MAX ? INT16_MAX : dataMax[i];
2388 applyFilter<uint16_t>(type, image, &dataMin, &dataMax);
2395 for (
int i = 0; i < 3; i++)
2397 dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i];
2398 dataMax[i] = dataMax[i] > UINT16_MAX ? UINT16_MAX : dataMax[i];
2400 applyFilter<uint16_t>(type, image, &dataMin, &dataMax);
2406 for (
int i = 0; i < 3; i++)
2408 dataMin[i] = dataMin[i] < INT_MIN ? INT_MIN : dataMin[i];
2409 dataMax[i] = dataMax[i] > INT_MAX ? INT_MAX : dataMax[i];
2411 applyFilter<uint16_t>(type, image, &dataMin, &dataMax);
2417 for (
int i = 0; i < 3; i++)
2419 dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i];
2420 dataMax[i] = dataMax[i] > UINT_MAX ? UINT_MAX : dataMax[i];
2422 applyFilter<uint16_t>(type, image, &dataMin, &dataMax);
2428 for (
int i = 0; i < 3; i++)
2430 dataMin[i] = dataMin[i] < FLT_MIN ? FLT_MIN : dataMin[i];
2431 dataMax[i] = dataMax[i] > FLT_MAX ? FLT_MAX : dataMax[i];
2433 applyFilter<float>(type, image, &dataMin, &dataMax);
2439 for (
int i = 0; i < 3; i++)
2441 dataMin[i] = dataMin[i] < LLONG_MIN ? LLONG_MIN : dataMin[i];
2442 dataMax[i] = dataMax[i] > LLONG_MAX ? LLONG_MAX : dataMax[i];
2445 applyFilter<long>(type, image, &dataMin, &dataMax);
2451 for (
int i = 0; i < 3; i++)
2453 dataMin[i] = dataMin[i] < DBL_MIN ? DBL_MIN : dataMin[i];
2454 dataMax[i] = dataMax[i] > DBL_MAX ? DBL_MAX : dataMax[i];
2456 applyFilter<double>(type, image, &dataMin, &dataMax);
2473template <
typename T>
2476 bool calcStats =
false;
2477 T * image =
nullptr;
2480 image =
reinterpret_cast<T *
>(targetImage);
2483 image =
reinterpret_cast<T *
>(m_ImageBuffer);
2488 for (
int i = 0; i < 3; i++)
2490 min[i] = (*targetMin)[i] < std::numeric_limits<T>::min() ? std::numeric_limits<T>::min() : (*targetMin)[i];
2491 max[i] = (*targetMax)[i] > std::numeric_limits<T>::max() ? std::numeric_limits<T>::max() : (*targetMax)[i];
2496 const uint8_t nThreads = 16;
2498 uint32_t width = m_Statistics.width;
2499 uint32_t height = m_Statistics.height;
2507 case FITS_AUTO_STRETCH:
2508 case FITS_HIGH_CONTRAST:
2511 case FITS_HIGH_PASS:
2517 if (type == FITS_LOG)
2519 for (
int i = 0; i < 3; i++)
2520 coeff[i] = max[i] / std::log(1 + max[i]);
2522 else if (type == FITS_SQRT)
2524 for (
int i = 0; i < 3; i++)
2525 coeff[i] = max[i] / sqrt(max[i]);
2528 for (
int n = 0; n < m_Statistics.channels; n++)
2530 if (type == FITS_HIGH_PASS)
2531 min[n] = m_Statistics.mean[n];
2533 uint32_t cStart = n * m_Statistics.samples_per_channel;
2536 uint32_t tStride = m_Statistics.samples_per_channel / nThreads;
2539 uint32_t fStride = tStride + (m_Statistics.samples_per_channel - (tStride * nThreads));
2541 T * runningBuffer = image + cStart;
2543 if (type == FITS_LOG)
2545 for (
int i = 0; i < nThreads; i++)
2548 futures.
append(
QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max,
2551 a = qBound(min[n],
static_cast<T
>(round(coeff[n] * std::log(1 + qBound(min[n], a, max[n])))), max[n]);
2554 runningBuffer += tStride;
2557 else if (type == FITS_SQRT)
2559 for (
int i = 0; i < nThreads; i++)
2562 futures.
append(
QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max,
2565 a = qBound(min[n],
static_cast<T
>(round(coeff[n] * a)), max[n]);
2569 runningBuffer += tStride;
2573 for (
int i = 0; i < nThreads; i++)
2576 futures.
append(
QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max,
2579 a = qBound(min[n], a, max[n]);
2581 runningBuffer += tStride;
2586 for (
int i = 0; i < nThreads * m_Statistics.channels; i++)
2587 futures[i].waitForFinished();
2591 for (
int i = 0; i < 3; i++)
2593 m_Statistics.min[i] = min[i];
2594 m_Statistics.max[i] = max[i];
2596 calculateStdDev<T>();
2604 if (!isHistogramConstructed())
2605 constructHistogram();
2609 double coeff = 255.0 / (height * width);
2613 for (
int i = 0; i < m_Statistics.channels; i++)
2615 uint32_t offset = i * m_Statistics.samples_per_channel;
2616 for (uint32_t j = 0; j < height; j++)
2618 row = offset + j * width;
2619 for (uint32_t k = 0; k < width; k++)
2622 bufferVal = (image[index] - min[i]) / m_HistogramBinWidth[i];
2624 if (bufferVal >= m_CumulativeFrequency[i].size())
2625 bufferVal = m_CumulativeFrequency[i].size() - 1;
2627 image[index] = qBound(min[i],
static_cast<T
>(round(coeff * m_CumulativeFrequency[i][bufferVal])), max[i]);
2634 calculateStats(
true,
false);
2640 uint8_t BBP = m_Statistics.bytesPerPixel;
2641 auto * extension =
new T[(width + 2) * (height + 2)];
2646 for (uint32_t ch = 0; ch < m_Statistics.channels; ch++)
2648 uint32_t offset = ch * m_Statistics.samples_per_channel;
2649 uint32_t N = width, M = height;
2651 for (uint32_t i = 0; i < M; ++i)
2653 memcpy(extension + (N + 2) * (i + 1) + 1, image + (N * i) + offset, N * BBP);
2654 extension[(N + 2) * (i + 1)] = image[N * i + offset];
2655 extension[(N + 2) * (i + 2) - 1] = image[N * (i + 1) - 1 + offset];
2658 memcpy(extension, extension + N + 2, (N + 2) * BBP);
2660 memcpy(extension + (N + 2) * (M + 1), extension + (N + 2) * M, (N + 2) * BBP);
2667 for (uint32_t m = 1; m < M - 1; ++m)
2668 for (uint32_t n = 1; n < N - 1; ++n)
2674 memset(&window[0], 0, 9 *
sizeof(
float));
2675 for (uint32_t j = m - 1; j < m + 2; ++j)
2676 for (uint32_t i = n - 1; i < n + 2; ++i)
2677 window[k++] = extension[j * N + i];
2679 for (uint32_t j = 0; j < 5; ++j)
2683 for (uint32_t l = j + 1; l < 9; ++l)
2684 if (window[l] < window[mine])
2687 const float temp =
window[j];
2692 image[(m - 1) * (N - 2) + n - 1 + offset] =
window[4];
2700 calculateStdDev<T>();
2705 gaussianBlur<T>(Options::focusGaussianKernelSize(), Options::focusGaussianSigma());
2707 calculateStats(
true,
false);
2710 case FITS_ROTATE_CW:
2715 case FITS_ROTATE_CCW:
2720 case FITS_MOUNT_FLIP_H:
2725 case FITS_MOUNT_FLIP_V:
2738 for (
int i = 0; i < starCenters.count(); i++)
2740 int x =
static_cast<int>(starCenters[i]->x);
2741 int y =
static_cast<int>(starCenters[i]->y);
2744 starCentersInSubFrame.
append(starCenters[i]);
2747 return starCentersInSubFrame;
2750bool FITSData::loadWCS()
2752#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
2754 if (m_WCSState == Success)
2756 qCWarning(KSTARS_FITS) <<
"WCS data already loaded";
2760 if (m_WCSHandle !=
nullptr)
2762 wcsvfree(&m_nwcs, &m_WCSHandle);
2764 m_WCSHandle =
nullptr;
2767 qCDebug(KSTARS_FITS) <<
"Started WCS Data Processing...";
2771 int nkeyrec = 0, nreject = 0;
2774 char *header =
nullptr;
2775 if (fits_hdr2str(fptr, 1,
nullptr, 0, &header, &nkeyrec, &status))
2778 fits_get_errstatus(status, errmsg);
2779 m_LastError = errmsg;
2780 m_WCSState = Failure;
2784 fits_free_memory(header, &status);
2789 for(
auto &fitsKeyword : m_HeaderRecords)
2792 rec.
append(fitsKeyword.key.leftJustified(8,
' ').toLatin1());
2794 rec.
append(fitsKeyword.value.toByteArray());
2796 rec.
append(fitsKeyword.comment.toLatin1());
2803 if ((status = wcspih(header_str.
data(), nkeyrec, WCSHDR_all, 0, &nreject, &m_nwcs, &m_WCSHandle)) != 0)
2805 wcsvfree(&m_nwcs, &m_WCSHandle);
2806 m_WCSHandle =
nullptr;
2808 m_LastError =
QString(
"wcspih ERROR %1: %2.").
arg(status).
arg(wcshdr_errmsg[status]);
2809 m_WCSState = Failure;
2813 if (m_WCSHandle ==
nullptr)
2815 m_LastError =
i18n(
"No world coordinate systems found.");
2816 m_WCSState = Failure;
2821 if (m_WCSHandle->crpix[0] == 0)
2823 wcsvfree(&m_nwcs, &m_WCSHandle);
2824 m_WCSHandle =
nullptr;
2826 m_LastError =
i18n(
"No world coordinate systems found.");
2827 m_WCSState = Failure;
2832 if ((status = wcsset(m_WCSHandle)) != 0)
2834 wcsvfree(&m_nwcs, &m_WCSHandle);
2835 m_WCSHandle =
nullptr;
2837 m_LastError =
QString(
"wcsset error %1: %2.").
arg(status).
arg(wcs_errmsg[status]);
2838 m_WCSState = Failure;
2842 m_ObjectsSearched =
false;
2843 m_WCSState = Success;
2846 qCDebug(KSTARS_FITS) <<
"Finished WCS Data processing...";
2856#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
2858 double imgcrd[NWCSFIX], phi, pixcrd[NWCSFIX], theta, worldcrd[NWCSFIX];
2860 if (m_WCSHandle ==
nullptr)
2862 m_LastError =
i18n(
"No world coordinate systems found.");
2869 if ((status = wcss2p(m_WCSHandle, 1, 2, worldcrd, &phi, &theta, imgcrd, pixcrd, stat)) != 0)
2871 m_LastError =
QString(
"wcss2p error %1: %2.").
arg(status).
arg(wcs_errmsg[status]);
2875 wcsImagePoint.
setX(imgcrd[0]);
2876 wcsImagePoint.
setY(imgcrd[1]);
2878 wcsPixelPoint.
setX(pixcrd[0]);
2879 wcsPixelPoint.
setY(pixcrd[1]);
2884 Q_UNUSED(wcsPixelPoint);
2885 Q_UNUSED(wcsImagePoint);
2890bool FITSData::pixelToWCS(
const QPointF &wcsPixelPoint,
SkyPoint &wcsCoord)
2892#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
2894 double imgcrd[NWCSFIX], phi, pixcrd[NWCSFIX], theta, world[NWCSFIX];
2896 if (m_WCSHandle ==
nullptr)
2898 m_LastError =
i18n(
"No world coordinate systems found.");
2902 pixcrd[0] = wcsPixelPoint.
x();
2903 pixcrd[1] = wcsPixelPoint.
y();
2905 if ((status = wcsp2s(m_WCSHandle, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat)) != 0)
2907 m_LastError =
QString(
"wcsp2s error %1: %2.").
arg(status).
arg(wcs_errmsg[status]);
2912 wcsCoord.
setRA0(world[0] / 15.0);
2918 Q_UNUSED(wcsPixelPoint);
2924#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
2925bool FITSData::searchObjects()
2927 if (m_ObjectsSearched)
2930 m_ObjectsSearched =
true;
2935 pixelToWCS(
QPointF(0, 0), startPoint);
2936 pixelToWCS(
QPointF(width() - 1, height() - 1), endPoint);
2938 return findObjectsInImage(startPoint, endPoint);
2941bool FITSData::findWCSBounds(
double &minRA,
double &maxRA,
double &minDec,
double &maxDec)
2943 if (m_WCSHandle ==
nullptr)
2945 m_LastError =
i18n(
"No world coordinate systems found.");
2954 auto updateMinMax = [&](
int x,
int y)
2957 double imgcrd[2], phi, pixcrd[2], theta, world[2];
2962 if (wcsp2s(m_WCSHandle, 1, 2, &pixcrd[0], &imgcrd[0], &phi, &theta, &world[0], &stat[0]))
2965 minRA = std::min(minRA, world[0]);
2966 maxRA = std::max(maxRA, world[0]);
2967 minDec = std::min(minDec, world[1]);
2968 maxDec = std::max(maxDec, world[1]);
2972 for (
int y = 0; y < height(); y++)
2975 updateMinMax(width() - 1, y);
2978 for (
int x = 1; x < width() - 1; x++)
2981 updateMinMax(x, height() - 1);
2988 if (wcsToPixel(NCP, pPoint, imagePoint))
2990 if (pPoint.
x() > 0 && pPoint.
x() < width() && pPoint.
y() > 0 && pPoint.
y() < height())
2995 if (wcsToPixel(SCP, pPoint, imagePoint))
2997 if (pPoint.
x() > 0 && pPoint.
x() < width() && pPoint.
y() > 0 && pPoint.
y() < height())
3006#if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB)
3009 if (KStarsData::Instance() ==
nullptr)
3017 if (getRecordValue(
"DATE-OBS", date))
3020 tsString = tsString.remove(
'\'').trimmed();
3032 num =
new KSNumbers(KStarsData::Instance()->ut().djd());
3037 m_SkyObjects.
clear();
3042 int type = oneObject->type();
3043 return (type == SkyObject::STAR || type == SkyObject::PLANET || type == SkyObject::ASTEROID ||
3044 type == SkyObject::COMET || type == SkyObject::SUPERNOVA || type == SkyObject::MOON ||
3045 type == SkyObject::SATELLITE);
3048 double world[2], phi, theta, imgcrd[2], pixcrd[2];
3050 for (
auto &
object : list)
3052 world[0] =
object->ra0().Degrees();
3053 world[1] =
object->dec0().Degrees();
3055 if (wcss2p(m_WCSHandle, 1, 2, &world[0], &phi, &theta, &imgcrd[0], &pixcrd[0], &stat[0]) == 0)
3060 if (x > 0 && y > 0 && x < w && y < h)
3061 m_SkyObjects.
append(
new FITSSkyObject(
object, x, y));
3070int FITSData::getFlipVCounter()
const
3072 return flipVCounter;
3075void FITSData::setFlipVCounter(
int value)
3077 flipVCounter = value;
3080int FITSData::getFlipHCounter()
const
3082 return flipHCounter;
3085void FITSData::setFlipHCounter(
int value)
3087 flipHCounter = value;
3090int FITSData::getRotCounter()
const
3095void FITSData::setRotCounter(
int value)
3105template <
typename T>
3106bool FITSData::rotFITS(
int rotate,
int mirror)
3110 uint8_t * rotimage =
nullptr;
3115 else if (rotate == 2)
3117 else if (rotate == 3)
3119 else if (rotate < 0)
3120 rotate = rotate + 360;
3122 nx = m_Statistics.width;
3123 ny = m_Statistics.height;
3125 int BBP = m_Statistics.bytesPerPixel;
3128 rotimage =
new uint8_t[m_Statistics.samples_per_channel * m_Statistics.channels * BBP];
3130 if (rotimage ==
nullptr)
3132 qWarning() <<
"Unable to allocate memory for rotated image buffer!";
3136 auto * rotBuffer =
reinterpret_cast<T *
>(rotimage);
3137 auto * buffer =
reinterpret_cast<T *
>(m_ImageBuffer);
3140 if (rotate < 45 && rotate > -45)
3144 for (
int i = 0; i < m_Statistics.channels; i++)
3146 offset = m_Statistics.samples_per_channel * i;
3147 for (x1 = 0; x1 < nx; x1++)
3150 for (y1 = 0; y1 < ny; y1++)
3151 rotBuffer[(y1 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3155 else if (mirror == 2)
3157 for (
int i = 0; i < m_Statistics.channels; i++)
3159 offset = m_Statistics.samples_per_channel * i;
3160 for (y1 = 0; y1 < ny; y1++)
3163 for (x1 = 0; x1 < nx; x1++)
3164 rotBuffer[(y2 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset];
3170 for (
int i = 0; i < m_Statistics.channels; i++)
3172 offset = m_Statistics.samples_per_channel * i;
3173 for (y1 = 0; y1 < ny; y1++)
3175 for (x1 = 0; x1 < nx; x1++)
3176 rotBuffer[(y1 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset];
3183 else if (rotate >= 45 && rotate < 135)
3187 for (
int i = 0; i < m_Statistics.channels; i++)
3189 offset = m_Statistics.samples_per_channel * i;
3190 for (y1 = 0; y1 < ny; y1++)
3193 for (x1 = 0; x1 < nx; x1++)
3196 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3201 else if (mirror == 2)
3203 for (
int i = 0; i < m_Statistics.channels; i++)
3205 offset = m_Statistics.samples_per_channel * i;
3206 for (y1 = 0; y1 < ny; y1++)
3208 for (x1 = 0; x1 < nx; x1++)
3209 rotBuffer[(x1 * ny) + y1 + offset] = buffer[(y1 * nx) + x1 + offset];
3215 for (
int i = 0; i < m_Statistics.channels; i++)
3217 offset = m_Statistics.samples_per_channel * i;
3218 for (y1 = 0; y1 < ny; y1++)
3221 for (x1 = 0; x1 < nx; x1++)
3224 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3230 m_Statistics.width = ny;
3231 m_Statistics.height = nx;
3235 else if (rotate >= 135 && rotate < 225)
3239 for (
int i = 0; i < m_Statistics.channels; i++)
3241 offset = m_Statistics.samples_per_channel * i;
3242 for (y1 = 0; y1 < ny; y1++)
3245 for (x1 = 0; x1 < nx; x1++)
3246 rotBuffer[(y2 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset];
3250 else if (mirror == 2)
3252 for (
int i = 0; i < m_Statistics.channels; i++)
3254 offset = m_Statistics.samples_per_channel * i;
3255 for (x1 = 0; x1 < nx; x1++)
3258 for (y1 = 0; y1 < ny; y1++)
3259 rotBuffer[(y1 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3265 for (
int i = 0; i < m_Statistics.channels; i++)
3267 offset = m_Statistics.samples_per_channel * i;
3268 for (y1 = 0; y1 < ny; y1++)
3271 for (x1 = 0; x1 < nx; x1++)
3274 rotBuffer[(y2 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3282 else if (rotate >= 225 && rotate < 315)
3286 for (
int i = 0; i < m_Statistics.channels; i++)
3288 offset = m_Statistics.samples_per_channel * i;
3289 for (y1 = 0; y1 < ny; y1++)
3291 for (x1 = 0; x1 < nx; x1++)
3292 rotBuffer[(x1 * ny) + y1 + offset] = buffer[(y1 * nx) + x1 + offset];
3296 else if (mirror == 2)
3298 for (
int i = 0; i < m_Statistics.channels; i++)
3300 offset = m_Statistics.samples_per_channel * i;
3301 for (y1 = 0; y1 < ny; y1++)
3304 for (x1 = 0; x1 < nx; x1++)
3307 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3314 for (
int i = 0; i < m_Statistics.channels; i++)
3316 offset = m_Statistics.samples_per_channel * i;
3317 for (y1 = 0; y1 < ny; y1++)
3320 for (x1 = 0; x1 < nx; x1++)
3323 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3329 m_Statistics.width = ny;
3330 m_Statistics.height = nx;
3334 else if (rotate >= 315 && mirror)
3336 for (
int i = 0; i < m_Statistics.channels; i++)
3338 offset = m_Statistics.samples_per_channel * i;
3339 for (y1 = 0; y1 < ny; y1++)
3341 for (x1 = 0; x1 < nx; x1++)
3345 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset];
3351 delete[] m_ImageBuffer;
3352 m_ImageBuffer = rotimage;
3357void FITSData::rotWCSFITS(
int angle,
int mirror)
3361 double ctemp1, ctemp2, ctemp3, ctemp4, naxis1, naxis2;
3362 int WCS_DECIMALS = 6;
3364 naxis1 = m_Statistics.width;
3365 naxis2 = m_Statistics.height;
3367 if (fits_read_key_dbl(fptr,
"CD1_1", &ctemp1, comment, &status))
3376 if (!fits_read_key_dbl(fptr,
"CROTA1", &ctemp1, comment, &status))
3377 fits_update_key_dbl(fptr,
"CROTA1", ctemp1 + 90.0, WCS_DECIMALS, comment, &status);
3379 if (!fits_read_key_dbl(fptr,
"CROTA2", &ctemp1, comment, &status))
3380 fits_update_key_dbl(fptr,
"CROTA2", ctemp1 + 90.0, WCS_DECIMALS, comment, &status);
3388 if (!fits_read_key_dbl(fptr,
"CROTA1", &ctemp1, comment, &status))
3389 fits_update_key_dbl(fptr,
"CROTA1", -ctemp1, WCS_DECIMALS, comment, &status);
3391 if (!fits_read_key_dbl(fptr,
"CROTA2", &ctemp1, comment, &status))
3392 fits_update_key_dbl(fptr,
"CROTA2", -ctemp1, WCS_DECIMALS, comment, &status);
3396 if (!fits_read_key_dbl(fptr,
"LTM1_1", &ctemp1, comment, &status))
3397 fits_update_key_dbl(fptr,
"LTM1_1", -ctemp1, WCS_DECIMALS, comment, &status);
3401 if (!fits_read_key_dbl(fptr,
"CD1_1", &ctemp1, comment, &status))
3402 fits_update_key_dbl(fptr,
"CD1_1", -ctemp1, WCS_DECIMALS, comment, &status);
3404 if (!fits_read_key_dbl(fptr,
"CD1_2", &ctemp1, comment, &status))
3405 fits_update_key_dbl(fptr,
"CD1_2", -ctemp1, WCS_DECIMALS, comment, &status);
3407 if (!fits_read_key_dbl(fptr,
"CD2_1", &ctemp1, comment, &status))
3408 fits_update_key_dbl(fptr,
"CD2_1", -ctemp1, WCS_DECIMALS, comment, &status);
3414 if (!fits_read_key_dbl(fptr,
"LTM1_1", &ctemp1, comment, &status))
3418 if (!fits_read_key_dbl(fptr,
"LTM2_2", &ctemp2, comment, &status))
3419 if (ctemp1 == ctemp2)
3424 if (!fits_read_key_dbl(fptr,
"LTV1", <v1, comment, &status))
3425 fits_delete_key(fptr,
"LTV1", &status);
3426 if (!fits_read_key_dbl(fptr,
"LTV2", <v2, comment, &status))
3427 fits_delete_key(fptr,
"LTV2", &status);
3431 if (!fits_read_key_dbl(fptr,
"CRPIX1", &ctemp3, comment, &status))
3432 fits_update_key_dbl(fptr,
"CRPIX1", (ctemp3 - ltv1) / ctemp1, WCS_DECIMALS, comment, &status);
3434 if (!fits_read_key_dbl(fptr,
"CRPIX2", &ctemp3, comment, &status))
3435 fits_update_key_dbl(fptr,
"CRPIX2", (ctemp3 - ltv2) / ctemp1, WCS_DECIMALS, comment, &status);
3439 if (!fits_read_key_dbl(fptr,
"CD1_1", &ctemp3, comment, &status))
3440 fits_update_key_dbl(fptr,
"CD1_1", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status);
3442 if (!fits_read_key_dbl(fptr,
"CD1_2", &ctemp3, comment, &status))
3443 fits_update_key_dbl(fptr,
"CD1_2", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status);
3445 if (!fits_read_key_dbl(fptr,
"CD2_1", &ctemp3, comment, &status))
3446 fits_update_key_dbl(fptr,
"CD2_1", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status);
3448 if (!fits_read_key_dbl(fptr,
"CD2_2", &ctemp3, comment, &status))
3449 fits_update_key_dbl(fptr,
"CD2_2", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status);
3453 fits_delete_key(fptr,
"LTM1_1", &status);
3454 fits_delete_key(fptr,
"LTM1_2", &status);
3462 if (!fits_read_key_dbl(fptr,
"CRPIX1", &ctemp1, comment, &status) &&
3463 !fits_read_key_dbl(fptr,
"CRPIX2", &ctemp2, comment, &status))
3468 fits_update_key_dbl(fptr,
"CRPIX1", naxis1 - ctemp1, WCS_DECIMALS, comment, &status);
3469 else if (angle == 90)
3471 fits_update_key_dbl(fptr,
"CRPIX1", naxis2 - ctemp2, WCS_DECIMALS, comment, &status);
3472 fits_update_key_dbl(fptr,
"CRPIX2", naxis1 - ctemp1, WCS_DECIMALS, comment, &status);
3474 else if (angle == 180)
3476 fits_update_key_dbl(fptr,
"CRPIX1", ctemp1, WCS_DECIMALS, comment, &status);
3477 fits_update_key_dbl(fptr,
"CRPIX2", naxis2 - ctemp2, WCS_DECIMALS, comment, &status);
3479 else if (angle == 270)
3481 fits_update_key_dbl(fptr,
"CRPIX1", ctemp2, WCS_DECIMALS, comment, &status);
3482 fits_update_key_dbl(fptr,
"CRPIX2", ctemp1, WCS_DECIMALS, comment, &status);
3489 fits_update_key_dbl(fptr,
"CRPIX1", naxis2 - ctemp2, WCS_DECIMALS, comment, &status);
3490 fits_update_key_dbl(fptr,
"CRPIX2", ctemp1, WCS_DECIMALS, comment, &status);
3492 else if (angle == 180)
3494 fits_update_key_dbl(fptr,
"CRPIX1", naxis1 - ctemp1, WCS_DECIMALS, comment, &status);
3495 fits_update_key_dbl(fptr,
"CRPIX2", naxis2 - ctemp2, WCS_DECIMALS, comment, &status);
3497 else if (angle == 270)
3499 fits_update_key_dbl(fptr,
"CRPIX1", ctemp2, WCS_DECIMALS, comment, &status);
3500 fits_update_key_dbl(fptr,
"CRPIX2", naxis1 - ctemp1, WCS_DECIMALS, comment, &status);
3508 if (!fits_read_key_dbl(fptr,
"CDELT1", &ctemp1, comment, &status) &&
3509 !fits_read_key_dbl(fptr,
"CDELT2", &ctemp2, comment, &status))
3514 fits_update_key_dbl(fptr,
"CDELT1", -ctemp1, WCS_DECIMALS, comment, &status);
3515 else if (angle == 90)
3517 fits_update_key_dbl(fptr,
"CDELT1", -ctemp2, WCS_DECIMALS, comment, &status);
3518 fits_update_key_dbl(fptr,
"CDELT2", -ctemp1, WCS_DECIMALS, comment, &status);
3520 else if (angle == 180)
3522 fits_update_key_dbl(fptr,
"CDELT1", ctemp1, WCS_DECIMALS, comment, &status);
3523 fits_update_key_dbl(fptr,
"CDELT2", -ctemp2, WCS_DECIMALS, comment, &status);
3525 else if (angle == 270)
3527 fits_update_key_dbl(fptr,
"CDELT1", ctemp2, WCS_DECIMALS, comment, &status);
3528 fits_update_key_dbl(fptr,
"CDELT2", ctemp1, WCS_DECIMALS, comment, &status);
3535 fits_update_key_dbl(fptr,
"CDELT1", -ctemp2, WCS_DECIMALS, comment, &status);
3536 fits_update_key_dbl(fptr,
"CDELT2", ctemp1, WCS_DECIMALS, comment, &status);
3538 else if (angle == 180)
3540 fits_update_key_dbl(fptr,
"CDELT1", -ctemp1, WCS_DECIMALS, comment, &status);
3541 fits_update_key_dbl(fptr,
"CDELT2", -ctemp2, WCS_DECIMALS, comment, &status);
3543 else if (angle == 270)
3545 fits_update_key_dbl(fptr,
"CDELT1", ctemp2, WCS_DECIMALS, comment, &status);
3546 fits_update_key_dbl(fptr,
"CDELT2", -ctemp1, WCS_DECIMALS, comment, &status);
3557 if (!fits_read_key_dbl(fptr,
"CD1_1", &ctemp1, comment, &status))
3559 fits_read_key_dbl(fptr,
"CD1_2", &ctemp2, comment, &status);
3560 fits_read_key_dbl(fptr,
"CD2_1", &ctemp3, comment, &status);
3561 fits_read_key_dbl(fptr,
"CD2_2", &ctemp4, comment, &status);
3567 fits_update_key_dbl(fptr,
"CD1_2", -ctemp2, WCS_DECIMALS, comment, &status);
3568 fits_update_key_dbl(fptr,
"CD2_1", -ctemp3, WCS_DECIMALS, comment, &status);
3570 else if (angle == 90)
3572 fits_update_key_dbl(fptr,
"CD1_1", -ctemp4, WCS_DECIMALS, comment, &status);
3573 fits_update_key_dbl(fptr,
"CD1_2", -ctemp3, WCS_DECIMALS, comment, &status);
3574 fits_update_key_dbl(fptr,
"CD2_1", -ctemp2, WCS_DECIMALS, comment, &status);
3575 fits_update_key_dbl(fptr,
"CD2_2", -ctemp1, WCS_DECIMALS, comment, &status);
3577 else if (angle == 180)
3579 fits_update_key_dbl(fptr,
"CD1_1", ctemp1, WCS_DECIMALS, comment, &status);
3580 fits_update_key_dbl(fptr,
"CD1_2", ctemp2, WCS_DECIMALS, comment, &status);
3581 fits_update_key_dbl(fptr,
"CD2_1", -ctemp3, WCS_DECIMALS, comment, &status);
3582 fits_update_key_dbl(fptr,
"CD2_2", -ctemp4, WCS_DECIMALS, comment, &status);
3584 else if (angle == 270)
3586 fits_update_key_dbl(fptr,
"CD1_1", ctemp4, WCS_DECIMALS, comment, &status);
3587 fits_update_key_dbl(fptr,
"CD1_2", ctemp3, WCS_DECIMALS, comment, &status);
3588 fits_update_key_dbl(fptr,
"CD2_1", ctemp3, WCS_DECIMALS, comment, &status);
3589 fits_update_key_dbl(fptr,
"CD2_2", ctemp1, WCS_DECIMALS, comment, &status);
3596 fits_update_key_dbl(fptr,
"CD1_1", -ctemp4, WCS_DECIMALS, comment, &status);
3597 fits_update_key_dbl(fptr,
"CD1_2", -ctemp3, WCS_DECIMALS, comment, &status);
3598 fits_update_key_dbl(fptr,
"CD2_1", ctemp1, WCS_DECIMALS, comment, &status);
3599 fits_update_key_dbl(fptr,
"CD2_2", ctemp1, WCS_DECIMALS, comment, &status);
3601 else if (angle == 180)
3603 fits_update_key_dbl(fptr,
"CD1_1", -ctemp1, WCS_DECIMALS, comment, &status);
3604 fits_update_key_dbl(fptr,
"CD1_2", -ctemp2, WCS_DECIMALS, comment, &status);
3605 fits_update_key_dbl(fptr,
"CD2_1", -ctemp3, WCS_DECIMALS, comment, &status);
3606 fits_update_key_dbl(fptr,
"CD2_2", -ctemp4, WCS_DECIMALS, comment, &status);
3608 else if (angle == 270)
3610 fits_update_key_dbl(fptr,
"CD1_1", ctemp4, WCS_DECIMALS, comment, &status);
3611 fits_update_key_dbl(fptr,
"CD1_2", ctemp3, WCS_DECIMALS, comment, &status);
3612 fits_update_key_dbl(fptr,
"CD2_1", -ctemp2, WCS_DECIMALS, comment, &status);
3613 fits_update_key_dbl(fptr,
"CD2_2", -ctemp1, WCS_DECIMALS, comment, &status);
3621 if (!fits_read_key_dbl(fptr,
"CO1_1", &ctemp1, comment, &status))
3626 for (i = 1; i < 13; i++)
3628 sprintf(keyword,
"CO1_%d", i);
3629 fits_delete_key(fptr, keyword, &status);
3631 for (i = 1; i < 13; i++)
3633 sprintf(keyword,
"CO2_%d", i);
3634 fits_delete_key(fptr, keyword, &status);
3640uint8_t * FITSData::getWritableImageBuffer()
3642 return m_ImageBuffer;
3645uint8_t
const * FITSData::getImageBuffer()
const
3647 return m_ImageBuffer;
3650void FITSData::setImageBuffer(uint8_t * buffer)
3652 delete[] m_ImageBuffer;
3653 m_ImageBuffer = buffer;
3656bool FITSData::checkDebayer()
3659 char bayerPattern[64], roworder[64];
3662 if (fits_read_keyword(fptr,
"BAYERPAT", bayerPattern,
nullptr, &status))
3665 if (m_Statistics.dataType != TUSHORT && m_Statistics.dataType != TBYTE)
3667 m_LastError =
i18n(
"Only 8 and 16 bits bayered images supported.");
3670 QString pattern(bayerPattern);
3671 pattern = pattern.remove(
'\'').trimmed();
3674 order = order.remove(
'\'').trimmed();
3676 if (order ==
"BOTTOM-UP" && !(m_Statistics.height % 2))
3678 if (pattern ==
"RGGB")
3680 else if (pattern ==
"GBRG")
3682 else if (pattern ==
"GRBG")
3684 else if (pattern ==
"BGGR")
3689 if (pattern ==
"RGGB")
3690 debayerParams.filter = DC1394_COLOR_FILTER_RGGB;
3691 else if (pattern ==
"GBRG")
3692 debayerParams.filter = DC1394_COLOR_FILTER_GBRG;
3693 else if (pattern ==
"GRBG")
3694 debayerParams.filter = DC1394_COLOR_FILTER_GRBG;
3695 else if (pattern ==
"BGGR")
3696 debayerParams.filter = DC1394_COLOR_FILTER_BGGR;
3700 m_LastError =
i18n(
"Unsupported bayer pattern %1.", pattern);
3704 fits_read_key(fptr, TINT,
"XBAYROFF", &debayerParams.offsetX,
nullptr, &status);
3705 fits_read_key(fptr, TINT,
"YBAYROFF", &debayerParams.offsetY,
nullptr, &status);
3707 if (debayerParams.offsetX == 1)
3712 switch (debayerParams.filter)
3714 case DC1394_COLOR_FILTER_RGGB:
3715 debayerParams.filter = DC1394_COLOR_FILTER_GRBG;
3717 case DC1394_COLOR_FILTER_GBRG:
3718 debayerParams.filter = DC1394_COLOR_FILTER_BGGR;
3720 case DC1394_COLOR_FILTER_GRBG:
3721 debayerParams.filter = DC1394_COLOR_FILTER_RGGB;
3723 case DC1394_COLOR_FILTER_BGGR:
3724 debayerParams.filter = DC1394_COLOR_FILTER_GBRG;
3727 debayerParams.offsetX = 0;
3729 if (debayerParams.offsetX != 0 || debayerParams.offsetY > 1 || debayerParams.offsetY < 0)
3731 m_LastError =
i18n(
"Unsupported bayer offsets %1 %2.", debayerParams.offsetX, debayerParams.offsetY);
3740void FITSData::getBayerParams(BayerParams * param)
3742 param->method = debayerParams.method;
3743 param->filter = debayerParams.filter;
3744 param->offsetX = debayerParams.offsetX;
3745 param->offsetY = debayerParams.offsetY;
3748void FITSData::setBayerParams(BayerParams * param)
3750 debayerParams.method = param->method;
3751 debayerParams.filter = param->filter;
3752 debayerParams.offsetX = param->offsetX;
3753 debayerParams.offsetY = param->offsetY;
3756bool FITSData::debayer(
bool reload)
3760 int anynull = 0,
status = 0;
3762 if (fits_read_img(fptr, m_Statistics.dataType, 1, m_Statistics.samples_per_channel,
nullptr, m_ImageBuffer,
3772 switch (m_Statistics.dataType)
3775 return debayer_8bit();
3778 return debayer_16bit();
3785bool FITSData::debayer_8bit()
3787 dc1394error_t error_code;
3789 uint32_t rgb_size = m_Statistics.samples_per_channel * 3 * m_Statistics.bytesPerPixel;
3790 uint8_t * destinationBuffer =
nullptr;
3794 destinationBuffer =
new uint8_t[rgb_size];
3796 catch (
const std::bad_alloc &e)
3798 logOOMError(rgb_size);
3799 m_LastError =
i18n(
"Unable to allocate memory for temporary bayer buffer: %1", e.what());
3803 auto * bayer_source_buffer =
reinterpret_cast<uint8_t *
>(m_ImageBuffer);
3804 auto * bayer_destination_buffer =
reinterpret_cast<uint8_t *
>(destinationBuffer);
3806 if (bayer_destination_buffer ==
nullptr)
3808 logOOMError(rgb_size);
3809 m_LastError =
i18n(
"Unable to allocate memory for temporary bayer buffer.");
3813 int ds1394_height = m_Statistics.height;
3814 auto dc1394_source = bayer_source_buffer;
3816 if (debayerParams.offsetY == 1)
3818 dc1394_source += m_Statistics.width;
3823 error_code = dc1394_bayer_decoding_8bit(dc1394_source, bayer_destination_buffer, m_Statistics.width, ds1394_height,
3824 debayerParams.filter,
3825 debayerParams.method);
3827 if (error_code != DC1394_SUCCESS)
3829 m_LastError =
i18n(
"Debayer failed (%1)", error_code);
3830 m_Statistics.channels = 1;
3831 delete[] destinationBuffer;
3835 if (m_ImageBufferSize != rgb_size)
3837 delete[] m_ImageBuffer;
3840 m_ImageBuffer =
new uint8_t[rgb_size];
3842 catch (
const std::bad_alloc &e)
3844 delete[] destinationBuffer;
3845 logOOMError(rgb_size);
3846 m_LastError =
i18n(
"Unable to allocate memory for temporary bayer buffer: %1", e.what());
3850 m_ImageBufferSize = rgb_size;
3853 auto bayered_buffer =
reinterpret_cast<uint8_t *
>(m_ImageBuffer);
3857 uint8_t * rBuff = bayered_buffer;
3858 uint8_t * gBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height);
3859 uint8_t * bBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height * 2);
3861 int imax = m_Statistics.samples_per_channel * 3 - 3;
3862 for (
int i = 0; i <= imax; i += 3)
3864 *rBuff++ = bayer_destination_buffer[i];
3865 *gBuff++ = bayer_destination_buffer[i + 1];
3866 *bBuff++ = bayer_destination_buffer[i + 2];
3872 m_Statistics.channels = (m_Mode == FITS_NORMAL || m_Mode == FITS_CALIBRATE) ? 3 : 1;
3873 m_Statistics.dataType = TBYTE;
3874 delete[] destinationBuffer;
3878bool FITSData::debayer_16bit()
3880 dc1394error_t error_code;
3882 uint32_t rgb_size = m_Statistics.samples_per_channel * 3 * m_Statistics.bytesPerPixel;
3883 uint8_t *destinationBuffer =
nullptr;
3886 destinationBuffer =
new uint8_t[rgb_size];
3888 catch (
const std::bad_alloc &e)
3890 logOOMError(rgb_size);
3891 m_LastError =
i18n(
"Unable to allocate memory for temporary bayer buffer: %1", e.what());
3895 auto * bayer_source_buffer =
reinterpret_cast<uint16_t *
>(m_ImageBuffer);
3896 auto * bayer_destination_buffer =
reinterpret_cast<uint16_t *
>(destinationBuffer);
3898 if (bayer_destination_buffer ==
nullptr)
3900 logOOMError(rgb_size);
3901 m_LastError =
i18n(
"Unable to allocate memory for temporary bayer buffer.");
3905 int ds1394_height = m_Statistics.height;
3906 auto dc1394_source = bayer_source_buffer;
3908 if (debayerParams.offsetY == 1)
3910 dc1394_source += m_Statistics.width;
3915 error_code = dc1394_bayer_decoding_16bit(dc1394_source, bayer_destination_buffer, m_Statistics.width, ds1394_height,
3916 debayerParams.filter,
3917 debayerParams.method, 16);
3919 if (error_code != DC1394_SUCCESS)
3921 m_LastError =
i18n(
"Debayer failed (%1)");
3922 m_Statistics.channels = 1;
3923 delete[] destinationBuffer;
3927 if (m_ImageBufferSize != rgb_size)
3929 delete[] m_ImageBuffer;
3932 m_ImageBuffer =
new uint8_t[rgb_size];
3934 catch (
const std::bad_alloc &e)
3936 logOOMError(rgb_size);
3937 delete[] destinationBuffer;
3938 m_LastError =
i18n(
"Unable to allocate memory for temporary bayer buffer: %1", e.what());
3942 m_ImageBufferSize = rgb_size;
3945 auto bayered_buffer =
reinterpret_cast<uint16_t *
>(m_ImageBuffer);
3949 uint16_t * rBuff = bayered_buffer;
3950 uint16_t * gBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height);
3951 uint16_t * bBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height * 2);
3953 int imax = m_Statistics.samples_per_channel * 3 - 3;
3954 for (
int i = 0; i <= imax; i += 3)
3956 *rBuff++ = bayer_destination_buffer[i];
3957 *gBuff++ = bayer_destination_buffer[i + 1];
3958 *bBuff++ = bayer_destination_buffer[i + 2];
3961 m_Statistics.channels = (m_Mode == FITS_NORMAL || m_Mode == FITS_CALIBRATE) ? 3 : 1;
3962 m_Statistics.dataType = TUSHORT;
3963 delete[] destinationBuffer;
3967void FITSData::logOOMError(uint32_t requiredMemory)
3969 qCCritical(KSTARS_FITS) <<
"Debayed memory allocation failure. Required Memory:" <<
KFormat().
formatByteSize(requiredMemory)
3970 <<
"Available system memory:" << KSUtils::getAvailableRAM();
3973double FITSData::getADU()
const
3976 for (
int i = 0; i < m_Statistics.channels; i++)
3977 adu += m_Statistics.mean[i];
3979 return (adu /
static_cast<double>(m_Statistics.channels));
3982QString FITSData::getLastError()
const
3987template <
typename T>
3988void FITSData::convertToQImage(
double dataMin,
double dataMax,
double scale,
double zero,
QImage &image)
3990 const auto * buffer =
reinterpret_cast<const T *
>(getImageBuffer());
3991 const T limit = std::numeric_limits<T>::max();
3992 T bMin = dataMin < 0 ? 0 : dataMin;
3993 T bMax = dataMax > limit ? limit : dataMax;
3994 uint16_t w = width();
3995 uint16_t h = height();
3996 uint32_t size = w * h;
3999 if (channels() == 1)
4002 for (
int j = 0; j < h; j++)
4004 unsigned char * scanLine = image.
scanLine(j);
4006 for (
int i = 0; i < w; i++)
4008 val = qBound(bMin, buffer[j * w + i], bMax);
4009 val = val * scale + zero;
4010 scanLine[i] = qBound<unsigned char>(0,
static_cast<uint8_t
>(val), 255);
4016 double rval = 0, gval = 0, bval = 0;
4019 for (
int j = 0; j < h; j++)
4021 auto * scanLine =
reinterpret_cast<QRgb *
>((image.
scanLine(j)));
4023 for (
int i = 0; i < w; i++)
4025 rval = qBound(bMin, buffer[j * w + i], bMax);
4026 gval = qBound(bMin, buffer[j * w + i + size], bMax);
4027 bval = qBound(bMin, buffer[j * w + i + size * 2], bMax);
4029 value = qRgb(rval * scale + zero, gval * scale + zero, bval * scale + zero);
4031 scanLine[i] = value;
4048 if (future.
result() ==
false)
4051 data.getMinMax(&min, &max);
4059 if (data.channels() == 1)
4064 for (
int i = 0; i < 256; i++)
4065 fitsImage.
setColor(i, qRgb(i, i, i));
4072 double dataMin = data.m_Statistics.mean[0] - data.m_Statistics.stddev[0];
4073 double dataMax = data.m_Statistics.mean[0] + data.m_Statistics.stddev[0] * 3;
4075 double bscale = 255. / (dataMax - dataMin);
4076 double bzero = (-dataMin) * (255. / (dataMax - dataMin));
4079 switch (data.m_Statistics.dataType)
4082 data.convertToQImage<uint8_t>(dataMin, dataMax, bscale, bzero, fitsImage);
4086 data.convertToQImage<int16_t>(dataMin, dataMax, bscale, bzero, fitsImage);
4090 data.convertToQImage<uint16_t>(dataMin, dataMax, bscale, bzero, fitsImage);
4094 data.convertToQImage<int32_t>(dataMin, dataMax, bscale, bzero, fitsImage);
4098 data.convertToQImage<uint32_t>(dataMin, dataMax, bscale, bzero, fitsImage);
4102 data.convertToQImage<
float>(dataMin, dataMax, bscale, bzero, fitsImage);
4106 data.convertToQImage<int64_t>(dataMin, dataMax, bscale, bzero, fitsImage);
4110 data.convertToQImage<
double>(dataMin, dataMax, bscale, bzero, fitsImage);
4124 qCCritical(KSTARS_FITS) <<
"Failed to convert" << filename <<
"to FITS since format" << format <<
"is not supported in Qt";
4132 qCCritical(KSTARS_FITS) <<
"Failed to open image" << filename;
4136 output =
QDir(getTemporaryPath()).
filePath(filename +
".fits");
4139 fitsfile *fptr =
nullptr;
4141 long fpixel = 1, naxis = input.
allGray() ? 2 : 3, nelements, exposure;
4142 long naxes[3] = { input.
width(), input.
height(), naxis == 3 ? 3 : 1 };
4143 char error_status[512] = {0};
4145 if (fits_create_file(&fptr,
QString(
'!' + output).toLocal8Bit().data(), &status))
4147 fits_get_errstatus(status, error_status);
4148 qCCritical(KSTARS_FITS) <<
"Failed to create FITS file. Error:" << error_status;
4152 if (fits_create_img(fptr, BYTE_IMG, naxis, naxes, &status))
4154 qCWarning(KSTARS_FITS) <<
"fits_create_img failed:" << error_status;
4156 fits_flush_file(fptr, &status);
4157 fits_close_file(fptr, &status);
4162 fits_update_key(fptr, TLONG,
"EXPOSURE", &exposure,
"Total Exposure Time", &status);
4167 nelements = naxes[0] * naxes[1];
4168 if (fits_write_img(fptr, TBYTE, fpixel, nelements, input.
bits(), &status))
4170 fits_get_errstatus(status, error_status);
4171 qCWarning(KSTARS_FITS) <<
"fits_write_img GRAY failed:" << error_status;
4173 fits_flush_file(fptr, &status);
4174 fits_close_file(fptr, &status);
4181 nelements = naxes[0] * naxes[1] * 3;
4183 uint8_t *srcBuffer = input.
bits();
4185 uint32_t srcBytes = naxes[0] * naxes[1] * 4 - 4;
4187 uint8_t *rgbBuffer =
new uint8_t[nelements];
4188 if (rgbBuffer ==
nullptr)
4190 qCWarning(KSTARS_FITS) <<
"Not enough memory for RGB buffer";
4191 fits_flush_file(fptr, &status);
4192 fits_close_file(fptr, &status);
4196 uint8_t *subR = rgbBuffer;
4197 uint8_t *subG = rgbBuffer + naxes[0] * naxes[1];
4198 uint8_t *subB = rgbBuffer + naxes[0] * naxes[1] * 2;
4199 for (uint32_t i = 0; i < srcBytes; i += 4)
4201 *subB++ = srcBuffer[i];
4202 *subG++ = srcBuffer[i + 1];
4203 *subR++ = srcBuffer[i + 2];
4206 if (fits_write_img(fptr, TBYTE, fpixel, nelements, rgbBuffer, &status))
4208 fits_get_errstatus(status, error_status);
4209 qCWarning(KSTARS_FITS) <<
"fits_write_img RGB failed:" << error_status;
4211 fits_flush_file(fptr, &status);
4212 fits_close_file(fptr, &status);
4213 delete [] rgbBuffer;
4217 delete [] rgbBuffer;
4220 if (fits_flush_file(fptr, &status))
4222 fits_get_errstatus(status, error_status);
4223 qCWarning(KSTARS_FITS) <<
"fits_flush_file failed:" << error_status;
4225 fits_close_file(fptr, &status);
4229 if (fits_close_file(fptr, &status))
4231 fits_get_errstatus(status, error_status);
4232 qCWarning(KSTARS_FITS) <<
"fits_close_file failed:" << error_status;
4239void FITSData::injectWCS(
double orientation,
double ra,
double dec,
double pixscale,
bool eastToTheRight)
4243 if (fptr ==
nullptr)
4246 fits_update_key(fptr, TDOUBLE,
"OBJCTRA", &ra,
"Object RA", &status);
4247 fits_update_key(fptr, TDOUBLE,
"OBJCTDEC", &dec,
"Object DEC", &status);
4251 fits_update_key(fptr, TINT,
"EQUINOX", &epoch,
"Equinox", &status);
4253 fits_update_key(fptr, TDOUBLE,
"CRVAL1", &ra,
"CRVAL1", &status);
4254 fits_update_key(fptr, TDOUBLE,
"CRVAL2", &dec,
"CRVAL1", &status);
4256 char radecsys[8] =
"FK5";
4257 char ctype1[16] =
"RA---TAN";
4258 char ctype2[16] =
"DEC--TAN";
4260 fits_update_key(fptr, TSTRING,
"RADECSYS", radecsys,
"RADECSYS", &status);
4261 fits_update_key(fptr, TSTRING,
"CTYPE1", ctype1,
"CTYPE1", &status);
4262 fits_update_key(fptr, TSTRING,
"CTYPE2", ctype2,
"CTYPE2", &status);
4264 double crpix1 = width() / 2.0;
4265 double crpix2 = height() / 2.0;
4267 fits_update_key(fptr, TDOUBLE,
"CRPIX1", &crpix1,
"CRPIX1", &status);
4268 fits_update_key(fptr, TDOUBLE,
"CRPIX2", &crpix2,
"CRPIX2", &status);
4271 double secpix1 = eastToTheRight ? pixscale : -pixscale;
4272 double secpix2 = pixscale;
4274 fits_update_key(fptr, TDOUBLE,
"SECPIX1", &secpix1,
"SECPIX1", &status);
4275 fits_update_key(fptr, TDOUBLE,
"SECPIX2", &secpix2,
"SECPIX2", &status);
4277 double degpix1 = secpix1 / 3600.0;
4278 double degpix2 = secpix2 / 3600.0;
4280 fits_update_key(fptr, TDOUBLE,
"CDELT1", °pix1,
"CDELT1", &status);
4281 fits_update_key(fptr, TDOUBLE,
"CDELT2", °pix2,
"CDELT2", &status);
4284 double rotation = 360 - orientation;
4288 fits_update_key(fptr, TDOUBLE,
"CROTA1", &rotation,
"CROTA1", &status);
4289 fits_update_key(fptr, TDOUBLE,
"CROTA2", &rotation,
"CROTA2", &status);
4294bool FITSData::contains(
const QPointF &point)
const
4296 return (point.
x() >= 0 && point.
y() >= 0 && point.
x() <= m_Statistics.width && point.
y() <= m_Statistics.height);
4299void FITSData::saveStatistics(FITSImage::Statistic &other)
4301 other = m_Statistics;
4304void FITSData::restoreStatistics(FITSImage::Statistic &other)
4306 m_Statistics = other;
4311void FITSData::constructHistogram()
4313 switch (m_Statistics.dataType)
4316 constructHistogramInternal<uint8_t>();
4320 constructHistogramInternal<int16_t>();
4324 constructHistogramInternal<uint16_t>();
4328 constructHistogramInternal<int32_t>();
4332 constructHistogramInternal<uint32_t>();
4336 constructHistogramInternal<float>();
4340 constructHistogramInternal<int64_t>();
4344 constructHistogramInternal<double>();
4352template <
typename T> int32_t FITSData::histogramBinInternal(T value,
int channel)
const
4354 return qMax(
static_cast<T
>(0), qMin(
static_cast<T
>(m_HistogramBinCount),
4355 static_cast<T
>(rint((value - m_Statistics.min[channel]) / m_HistogramBinWidth[channel]))));
4358template <
typename T> int32_t FITSData::histogramBinInternal(
int x,
int y,
int channel)
const
4360 if (!m_ImageBuffer || !isHistogramConstructed())
4362 uint32_t samples = m_Statistics.width * m_Statistics.height;
4363 uint32_t offset = channel * samples;
4364 auto *
const buffer =
reinterpret_cast<T
const *
>(m_ImageBuffer);
4365 int index = y * m_Statistics.width + x;
4366 const T &sample = buffer[index + offset];
4367 return histogramBinInternal(sample, channel);
4370int32_t FITSData::histogramBin(
int x,
int y,
int channel)
const
4372 switch (m_Statistics.dataType)
4375 return histogramBinInternal<uint8_t>(x, y, channel);
4379 return histogramBinInternal<int16_t>(x, y, channel);
4383 return histogramBinInternal<uint16_t>(x, y, channel);
4387 return histogramBinInternal<int32_t>(x, y, channel);
4391 return histogramBinInternal<uint32_t>(x, y, channel);
4395 return histogramBinInternal<float>(x, y, channel);
4399 return histogramBinInternal<int64_t>(x, y, channel);
4403 return histogramBinInternal<double>(x, y, channel);
4412template <
typename T>
void FITSData::constructHistogramInternal()
4414 auto *
const buffer =
reinterpret_cast<T
const *
>(m_ImageBuffer);
4415 uint32_t samples = m_Statistics.width * m_Statistics.height;
4416 const uint32_t sampleBy = samples > 500000 ? samples / 500000 : 1;
4418 m_HistogramBinCount = qMax(0., qMin(m_Statistics.max[0] - m_Statistics.min[0], 256.0));
4419 if (m_HistogramBinCount <= 0)
4420 m_HistogramBinCount = 256;
4422 for (
int n = 0; n < m_Statistics.channels; n++)
4424 m_HistogramIntensity[n].fill(0, m_HistogramBinCount + 1);
4425 m_HistogramFrequency[n].fill(0, m_HistogramBinCount + 1);
4426 m_CumulativeFrequency[n].fill(0, m_HistogramBinCount + 1);
4428 const double minBinSize = (m_Statistics.max[n] > 1.1) ? 1.0 : .0001;
4429 m_HistogramBinWidth[n] = qMax(minBinSize, (m_Statistics.max[n] - m_Statistics.min[n]) / m_HistogramBinCount);
4434 for (
int n = 0; n < m_Statistics.channels; n++)
4438 for (
int i = 0; i < m_HistogramBinCount; i++)
4439 m_HistogramIntensity[n][i] = m_Statistics.min[n] + (m_HistogramBinWidth[n] * i);
4443 for (
int n = 0; n < m_Statistics.channels; n++)
4447 uint32_t offset = n * samples;
4449 for (uint32_t i = 0; i < samples; i += sampleBy)
4451 int32_t
id = histogramBinInternal<T>(buffer[i + offset], n);
4452 m_HistogramFrequency[n][id] += sampleBy;
4462 for (
int n = 0; n < m_Statistics.channels; n++)
4466 uint32_t accumulator = 0;
4467 for (
int i = 0; i < m_HistogramBinCount; i++)
4469 accumulator += m_HistogramFrequency[n][i];
4470 m_CumulativeFrequency[n].replace(i, accumulator);
4481 if (m_CumulativeFrequency[RED_CHANNEL][m_HistogramBinCount / 4] > 0)
4482 m_JMIndex = m_CumulativeFrequency[RED_CHANNEL][m_HistogramBinCount / 8] /
static_cast<double>
4483 (m_CumulativeFrequency[RED_CHANNEL][m_HistogramBinCount /
4488 qCDebug(KSTARS_FITS) <<
"FITHistogram: JMIndex " << m_JMIndex;
4490 m_HistogramConstructed =
true;
4491 emit histogramReady();
4494void FITSData::recordLastError(
int errorCode)
4496 char fitsErrorMessage[512] = {0};
4497 fits_get_errstatus(errorCode, fitsErrorMessage);
4498 m_LastError = fitsErrorMessage;
4501double FITSData::getAverageMean(
bool roi)
const
4503 const FITSImage::Statistic* ptr = (roi ? &m_ROIStatistics : &m_Statistics);
4504 if (ptr->channels == 1)
4505 return ptr->mean[0];
4507 return (ptr->mean[0] + ptr->mean[1] + ptr->mean[2]) / 3.0;
4510double FITSData::getAverageMedian(
bool roi)
const
4512 const FITSImage::Statistic* ptr = (roi ? &m_ROIStatistics : &m_Statistics);
4513 if (ptr->channels == 1)
4514 return ptr->median[0];
4516 return (ptr->median[0] + ptr->median[1] + ptr->median[2]) / 3.0;
4519double FITSData::getAverageStdDev(
bool roi)
const
4521 const FITSImage::Statistic* ptr = (roi ? &m_ROIStatistics : &m_Statistics);
4522 if (ptr->channels == 1)
4523 return ptr->stddev[0];
4525 return (ptr->stddev[0] + ptr->stddev[1] + ptr->stddev[2]) / 3.0;
There are several time-dependent values used in position calculations, that are not specific to an ob...
SkyMapComposite * skyComposite()
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
QList< SkyObject * > findObjectsInArea(const SkyPoint &p1, const SkyPoint &p2)
Provides all necessary information about an object in the sky: its coordinates, name(s),...
The sky coordinates of a point in the sky.
const CachingDms & ra0() const
virtual void updateCoordsNow(const KSNumbers *num)
updateCoordsNow Shortcut for updateCoords( const KSNumbers *num, false, nullptr, nullptr,...
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
const CachingDms & dec0() const
void setDec0(dms d)
Sets Dec0, the catalog Declination.
An angle, stored as degrees, but expressible in many ways.
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
const double & Degrees() const
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
KIOCORE_EXPORT QString number(KIO::filesize_t size)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & end()
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
QByteArray & append(QByteArrayView data)
const_iterator constBegin() const const
const char * constData() const const
bool isEmpty() const const
QByteArray leftJustified(qsizetype width, char fill, bool truncate) const const
void push_back(QByteArrayView str)
qsizetype size() const const
QDateTime currentDateTime()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
QString filePath(const QString &fileName) const const
QString path() const const
virtual qint64 size() const const override
virtual void close() override
qint64 size() const const
QString suffix() const const
bool isRunning() const const
bool allGray() const const
void fill(Qt::GlobalColor color)
bool load(QIODevice *device, const char *format)
bool loadFromData(QByteArrayView data, const char *format)
bool save(QIODevice *device, const char *format, int quality) const const
void setColor(int index, QRgb colorValue)
void setColorCount(int colorCount)
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool contains(const AT &value) const const
qsizetype count() const const
iterator erase(const_iterator begin, const_iterator end)
qsizetype size() const const
void setObjectName(QAnyStringView name)
bool contains(const QPoint &point, bool proper) const const
bool isNull() const const
bool isValid() const const
QPoint topLeft() const const
bool isNull() const const
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromStdString(const std::string &str)
QString mid(qsizetype position, qsizetype n) const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QByteArray toLocal8Bit() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QTextStream & center(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< T > run(Function function,...)
virtual QString fileName() const const override
void setFileTemplate(const QString &name)
QString toString(StringFormat mode) const const
bool isValid() const const
QDateTime toDateTime() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const