1 /*
2     SPDX-FileCopyrightText: 2004 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 
6     Some code fragments were adapted from Peter Kirchgessner's FITS plugin
7     SPDX-FileCopyrightText: Peter Kirchgessner <http://members.aol.com/pkirchg>
8 */
9 
10 #pragma once
11 
12 #include "config-kstars.h"
13 
14 // From StellarSolver
15 #ifdef HAVE_STELLARSOLVER
16 #include <structuredefinitions.h>
17 #else
18 #include "structuredefinitions.h"
19 #endif
20 
21 #include "kstarsdatetime.h"
22 #include "bayer.h"
23 #include "skybackground.h"
24 #include "fitscommon.h"
25 #include "fitsstardetector.h"
26 
27 #ifdef WIN32
28 // This header must be included before fitsio.h to avoid compiler errors with Visual Studio
29 #include <windows.h>
30 #endif
31 
32 #include <fitsio.h>
33 
34 #include <QFuture>
35 #include <QObject>
36 #include <QRect>
37 #include <QVariant>
38 #include <QTemporaryFile>
39 
40 #ifndef KSTARS_LITE
41 #include <kxmlguiwindow.h>
42 #ifdef HAVE_WCSLIB
43 #include <wcs.h>
44 #endif
45 #endif
46 
47 #include "fitsskyobject.h"
48 
49 class QProgressDialog;
50 
51 class SkyPoint;
52 class FITSHistogramData;
53 class Edge;
54 
55 class FITSData : public QObject
56 {
57         Q_OBJECT
58 
59         // Name of FITS file
60         Q_PROPERTY(QString filename READ filename)
61         // Size of file in bytes
62         Q_PROPERTY(qint64 size READ size)
63         // Width in pixels
64         Q_PROPERTY(quint16 width READ width)
65         // Height in pixels
66         Q_PROPERTY(quint16 height READ height)
67         // FITS MODE --> Normal, Focus, Guide..etc
68         Q_PROPERTY(FITSMode mode MEMBER m_Mode)
69         // 1 channel (grayscale) or 3 channels (RGB)
70         Q_PROPERTY(quint8 channels READ channels)
71         // Bits per pixel
72         Q_PROPERTY(quint8 bpp READ bpp)
73         // Does FITS have WSC header?
74         Q_PROPERTY(bool hasWCS READ hasWCS)
75         // Does FITS have bayer data?
76         Q_PROPERTY(bool hasDebayer READ hasDebayer)
77 
78     public:
79         explicit FITSData(FITSMode fitsMode = FITS_NORMAL);
80         explicit FITSData(const QSharedPointer<FITSData> &other);
81         ~FITSData();
82 
83         /** Structure to hold FITS Header records */
84         typedef struct
85         {
86             QString key;      /** FITS Header Key */
87             QVariant value;   /** FITS Header Value */
88             QString comment;  /** FITS Header Comment, if any */
89         } Record;
90 
91         typedef enum
92         {
93             Idle,
94             Busy,
95             Success,
96             Failure
97         } WCSState;
98 
99         ////////////////////////////////////////////////////////////////////////////////////////
100         ////////////////////////////////////////////////////////////////////////////////////////
101         /// Read and Write file/buffer Functions.
102         ////////////////////////////////////////////////////////////////////////////////////////
103         ////////////////////////////////////////////////////////////////////////////////////////
104         /**
105          * @brief loadFITS Loading FITS file asynchronously.
106          * @param inFilename Path to FITS file (or compressed fits.gz)
107          * @param silent If set, error messages are ignored. If set to false, the error message will get displayed in a popup.
108          * @return A QFuture that can be watched until the async operation is complete.
109          */
110         QFuture<bool> loadFromFile(const QString &inFilename, bool silent = true);
111 
112         /**
113          * @brief loadFITSFromMemory Loading FITS from memory buffer.
114          * @param buffer The memory buffer containing the fits data.
115          * @param extension file extension (e.g. "jpg", "fits", "cr2"...etc)
116          * @param inFilename Set filename metadata, does not load from file.
117          * @param silent If set, error messages are ignored. If set to false, the error message will get displayed in a popup.
118          * @return bool indicating success or failure.
119          */
120         bool loadFromBuffer(const QByteArray &buffer, const QString &extension, const QString &inFilename = QString(),
121                             bool silent = true);
122 
123         /**
124          * @brief parseSolution Parse the WCS solution information from the header into the given struct.
125          * @param solution Solution structure to fill out.
126          * @return True if parsing successful, false otherwise.
127          */
128         bool parseSolution(FITSImage::Solution &solution) const;
129 
130         /* Save FITS or JPG/PNG*/
131         bool saveImage(const QString &newFilename);
132 
133         // Access functions
134         void clearImageBuffers();
135         void setImageBuffer(uint8_t *buffer);
136         uint8_t const *getImageBuffer() const;
137         uint8_t *getWritableImageBuffer();
138 
139         ////////////////////////////////////////////////////////////////////////////////////////
140         ////////////////////////////////////////////////////////////////////////////////////////
141         /// Statistics Functions.
142         ////////////////////////////////////////////////////////////////////////////////////////
143         ////////////////////////////////////////////////////////////////////////////////////////
144         // Calculate stats
145         void calculateStats(bool refresh = false);
146         void saveStatistics(FITSImage::Statistic &other);
147         void restoreStatistics(FITSImage::Statistic &other);
getStatistics()148         FITSImage::Statistic const &getStatistics() const
149         {
150             return m_Statistics;
151         };
152 
width()153         uint16_t width() const
154         {
155             return m_Statistics.width;
156         }
height()157         uint16_t height() const
158         {
159             return m_Statistics.height;
160         }
size()161         int64_t size() const
162         {
163             return m_Statistics.size;
164         }
channels()165         int channels() const
166         {
167             return m_Statistics.channels;
168         }
samplesPerChannel()169         uint32_t samplesPerChannel() const
170         {
171             return m_Statistics.samples_per_channel;
172         }
dataType()173         uint32_t dataType() const
174         {
175             return m_Statistics.dataType;
176         }
177         double getMin(uint8_t channel = 0) const
178         {
179             return m_Statistics.min[channel];
180         }
181         double getMax(uint8_t channel = 0) const
182         {
183             return m_Statistics.max[channel];
184         }
185         void setMinMax(double newMin, double newMax, uint8_t channel = 0);
186         void getMinMax(double *min, double *max, uint8_t channel = 0) const
187         {
188             *min = m_Statistics.min[channel];
189             *max = m_Statistics.max[channel];
190         }
191         void setStdDev(double value, uint8_t channel = 0)
192         {
193             m_Statistics.stddev[channel] = value;
194         }
195         double getStdDev(uint8_t channel = 0) const
196         {
197             return m_Statistics.stddev[channel];
198         }
199         double getAverageStdDev() const;
200         void setMean(double value, uint8_t channel = 0)
201         {
202             m_Statistics.mean[channel] = value;
203         }
204         double getMean(uint8_t channel = 0) const
205         {
206             return m_Statistics.mean[channel];
207         }
208         // for single channel, just return the mean for channel zero
209         // for color, return the average
210         double getAverageMean() const;
211         void setMedian(double val, uint8_t channel = 0)
212         {
213             m_Statistics.median[channel] = val;
214         }
215         // for single channel, just return the median for channel zero
216         // for color, return the average
217         double getAverageMedian() const;
218         double getMedian(uint8_t channel = 0) const
219         {
220             return m_Statistics.median[channel];
221         }
222 
getBytesPerPixel()223         int getBytesPerPixel() const
224         {
225             return m_Statistics.bytesPerPixel;
226         }
setSNR(double val)227         void setSNR(double val)
228         {
229             m_Statistics.SNR = val;
230         }
getSNR()231         double getSNR() const
232         {
233             return m_Statistics.SNR;
234         }
bpp()235         uint32_t bpp() const
236         {
237             switch(m_Statistics.dataType)
238             {
239                 case TBYTE:
240                     return 8;
241                     break;
242                 case TSHORT:
243                 case TUSHORT:
244                     return 16;
245                     break;
246                 case TLONG:
247                 case TULONG:
248                 case TFLOAT:
249                     return 32;
250                     break;
251                 case TLONGLONG:
252                 case TDOUBLE:
253                     return 64;
254                     break;
255                 default:
256                     return 8;
257             }
258         }
259         double getADU() const;
260 
261         ////////////////////////////////////////////////////////////////////////////////////////
262         ////////////////////////////////////////////////////////////////////////////////////////
263         /// FITS Header Functions.
264         ////////////////////////////////////////////////////////////////////////////////////////
265         ////////////////////////////////////////////////////////////////////////////////////////
266         // FITS Record
267         bool getRecordValue(const QString &key, QVariant &value) const;
getRecords()268         const QList<Record> &getRecords() const
269         {
270             return m_HeaderRecords;
271         }
272 
273         ////////////////////////////////////////////////////////////////////////////////////////
274         ////////////////////////////////////////////////////////////////////////////////////////
275         /// Star Search & HFR Functions.
276         ////////////////////////////////////////////////////////////////////////////////////////
277         ////////////////////////////////////////////////////////////////////////////////////////
278         // Star Detection - Native KStars implementation
setStarAlgorithm(StarAlgorithm algorithm)279         void setStarAlgorithm(StarAlgorithm algorithm)
280         {
281             starAlgorithm = algorithm;
282         }
getDetectedStars()283         int getDetectedStars() const
284         {
285             return starCenters.count();
286         }
areStarsSearched()287         bool areStarsSearched() const
288         {
289             return starsSearched;
290         }
appendStar(Edge * newCenter)291         void appendStar(Edge *newCenter)
292         {
293             starCenters.append(newCenter);
294         }
getStarCenters()295         const QList<Edge *> &getStarCenters() const
296         {
297             return starCenters;
298         }
299         QList<Edge *> getStarCentersInSubFrame(QRect subFrame) const;
300 
setStarCenters(const QList<Edge * > & centers)301         void setStarCenters(const QList<Edge*> &centers)
302         {
303             starCenters = centers;
304         }
305         QFuture<bool> findStars(StarAlgorithm algorithm = ALGORITHM_CENTROID, const QRect &trackingBox = QRect());
306 
setSkyBackground(const SkyBackground & bg)307         void setSkyBackground(const SkyBackground &bg)
308         {
309             m_SkyBackground = bg;
310         }
getSkyBackground()311         const SkyBackground &getSkyBackground() const
312         {
313             return m_SkyBackground;
314         }
getSourceExtractorSettings()315         const QVariantMap &getSourceExtractorSettings() const
316         {
317             return m_SourceExtractorSettings;
318         }
setSourceExtractorSettings(const QVariantMap & settings)319         void setSourceExtractorSettings(const QVariantMap &settings)
320         {
321             m_SourceExtractorSettings = settings;
322         }
323         // Use SEP (Sextractor Library) to find stars
324         template <typename T>
325         void getFloatBuffer(float *buffer, int x, int y, int w, int h) const;
326         //int findSEPStars(QList<Edge*> &, const QRect &boundary = QRect()) const;
327 
328         // Apply ring filter to searched stars
329         int filterStars(const float innerRadius, const float outerRadius);
330 
331         // Half Flux Radius
getSelectedHFRStar()332         Edge *getSelectedHFRStar() const
333         {
334             return m_SelectedHFRStar;
335         }
336 
337         // Calculates the median star eccentricity.
338         double getEccentricity();
339 
340         double getHFR(HFRType type = HFR_AVERAGE);
341         double getHFR(int x, int y);
342 
343         ////////////////////////////////////////////////////////////////////////////////////////
344         ////////////////////////////////////////////////////////////////////////////////////////
345         /// Date & Time (WCS) Functions.
346         ////////////////////////////////////////////////////////////////////////////////////////
347         ////////////////////////////////////////////////////////////////////////////////////////
348 
getDateTime()349         const KStarsDateTime &getDateTime() const
350         {
351             return m_DateTime;
352         }
353 
354         // Set the time, for testing (doesn't set header field)
setDateTime(const KStarsDateTime & t)355         void setDateTime(const KStarsDateTime &t)
356         {
357             m_DateTime = t;
358         }
359 
360         ////////////////////////////////////////////////////////////////////////////////////////
361         ////////////////////////////////////////////////////////////////////////////////////////
362         /// World Coordinate System (WCS) Functions.
363         ////////////////////////////////////////////////////////////////////////////////////////
364         ////////////////////////////////////////////////////////////////////////////////////////
365         // Check if a particular point exists within the image
366         bool contains(const QPointF &point) const;
367         // Check if image has valid WCS header information and set HasWCS accordingly. Call in loadFITS()
368         bool checkForWCS();
369         // Does image have valid WCS?
hasWCS()370         bool hasWCS()
371         {
372             return HasWCS;
373         }
374         // The WCS can be loaded without pre-computing each pixel's position. This can make certain
375         // operations slow. FullWCS() is true if the pixel positions are pre-calculated.
fullWCS()376         bool fullWCS()
377         {
378             return FullWCS;
379         }
380         // Load WCS data
381         bool loadWCS(bool extras = true);
382         // Get WCS State
getWCSState()383         WCSState getWCSState() const
384         {
385             return m_WCSState;
386         }
387 
388         /**
389              * @brief wcsToPixel Given J2000 (RA0,DE0) coordinates. Find in the image the corresponding pixel coordinates.
390              * @param wcsCoord Coordinates of target
391              * @param wcsPixelPoint Return XY FITS coordinates
392              * @param wcsImagePoint Return XY Image coordinates
393              * @return True if conversion is successful, false otherwise.
394              */
395         bool wcsToPixel(const SkyPoint &wcsCoord, QPointF &wcsPixelPoint, QPointF &wcsImagePoint);
396 
397         /**
398              * @brief pixelToWCS Convert Pixel coordinates to J2000 world coordinates
399              * @param wcsPixelPoint Pixel coordinates in XY Image space.
400              * @param wcsCoord Store back WCS world coordinate in wcsCoord
401              * @return True if successful, false otherwise.
402              */
403         bool pixelToWCS(const QPointF &wcsPixelPoint, SkyPoint &wcsCoord);
404 
405         /**
406              * @brief injectWCS Add WCS keywords to file
407              * @param orientation Solver orientation, degrees E of N.
408              * @param ra J2000 Right Ascension
409              * @param dec J2000 Declination
410              * @param pixscale Pixel scale in arcsecs per pixel
411              * @param eastToTheRight if true, then when the image is rotated so that north is up, then east would be to the right on the image.
412              * @return  True if file is successfully updated with WCS info.
413              */
414         bool injectWCS(double orientation, double ra, double dec, double pixscale, bool eastToTheRight);
415 
416         ////////////////////////////////////////////////////////////////////////////////////////
417         ////////////////////////////////////////////////////////////////////////////////////////
418         /// Debayering Functions
419         ////////////////////////////////////////////////////////////////////////////////////////
420         ////////////////////////////////////////////////////////////////////////////////////////
421 
422         // Debayer
hasDebayer()423         bool hasDebayer()
424         {
425             return HasDebayer;
426         }
427 
428         /**
429          * @brief debayer the 1-channel data to 3-channel RGB using the default debayer pattern detected in the FITS header.
430          * @param reload If true, it will read the image again from disk before performing debayering. This is necessary to attempt
431          * subsequent debayering processes on an already debayered image.
432          */
433         bool debayer(bool reload = false);
434         bool debayer_8bit();
435         bool debayer_16bit();
436         void getBayerParams(BayerParams *param);
437         void setBayerParams(BayerParams *param);
438 
439         ////////////////////////////////////////////////////////////////////////////////////////
440         ////////////////////////////////////////////////////////////////////////////////////////
441         /// Public Histogram Functions
442         ////////////////////////////////////////////////////////////////////////////////////////
443         ////////////////////////////////////////////////////////////////////////////////////////
444 
resetHistogram()445         void resetHistogram()
446         {
447             m_HistogramConstructed = false;
448         }
449         double getHistogramBinWidth(int channel = 0)
450         {
451             return m_HistogramBinWidth[channel];
452         }
453 
454         const QVector<uint32_t> &getCumulativeFrequency(uint8_t channel = 0) const
455         {
456             return m_CumulativeFrequency[channel];
457         }
458         const QVector<double> &getHistogramIntensity(uint8_t channel = 0) const
459         {
460             return m_HistogramIntensity[channel];
461         }
462         const QVector<double> &getHistogramFrequency(uint8_t channel = 0) const
463         {
464             return m_HistogramFrequency[channel];
465         }
466 
467         /**
468          * @brief getJMIndex Overall contrast of the image used in find centeroid algorithm. i.e. is the image diffuse?
469          * @return Value of JMIndex
470          */
getJMIndex()471         double getJMIndex() const
472         {
473             return m_JMIndex;
474         }
475 
isHistogramConstructed()476         bool isHistogramConstructed()
477         {
478             return m_HistogramConstructed;
479         }
480         void constructHistogram();
481 
482         ////////////////////////////////////////////////////////////////////////////////////////
483         ////////////////////////////////////////////////////////////////////////////////////////
484         /// Filters and Rotations Functions.
485         ////////////////////////////////////////////////////////////////////////////////////////
486         ////////////////////////////////////////////////////////////////////////////////////////
487         // Filter
488         void applyFilter(FITSScale type, uint8_t *image = nullptr, QVector<double> *targetMin = nullptr,
489                          QVector<double> *targetMax = nullptr);
490 
491         // Rotation counter. We keep count to rotate WCS keywords on save
492         int getRotCounter() const;
493         void setRotCounter(int value);
494 
495         // Filename
filename()496         const QString &filename() const
497         {
498             return m_Filename;
499         }
compressedFilename()500         const QString &compressedFilename() const
501         {
502             return m_compressedFilename;
503         }
isCompressed()504         bool isCompressed() const
505         {
506             return m_isCompressed;
507         }
508 
509         // Horizontal flip counter. We keep count to rotate WCS keywords on save
510         int getFlipHCounter() const;
511         void setFlipHCounter(int value);
512 
513         // Horizontal flip counter. We keep count to rotate WCS keywords on save
514         int getFlipVCounter() const;
515         void setFlipVCounter(int value);
516 
517         ////////////////////////////////////////////////////////////////////////////////////////
518         ////////////////////////////////////////////////////////////////////////////////////////
519         /// Object Search Functions.
520         ////////////////////////////////////////////////////////////////////////////////////////
521         ////////////////////////////////////////////////////////////////////////////////////////
522 #ifndef KSTARS_LITE
523 #ifdef HAVE_WCSLIB
524         bool searchObjects();
525         bool findObjectsInImage(SkyPoint startPoint, SkyPoint endPoint);
526         bool findWCSBounds(double &minRA, double &maxRA, double &minDec, double &maxDec);
527 #endif
528 #endif
getSkyObjects()529         const QList<FITSSkyObject *> &getSkyObjects() const
530         {
531             return m_SkyObjects;
532         }
533 
534         ////////////////////////////////////////////////////////////////////////////////////////
535         ////////////////////////////////////////////////////////////////////////////////////////
536         /// Image Conversion Functions.
537         ////////////////////////////////////////////////////////////////////////////////////////
538         ////////////////////////////////////////////////////////////////////////////////////////
539         // Create autostretch image from FITS File
540         static QImage FITSToImage(const QString &filename);
541 
542         /**
543          * @brief ImageToFITS Convert an image file with supported format to a FITS file.
544          * @param filename full path to filename without extension
545          * @param format file extension. Supported formats are whatever supported by Qt (e.g. PNG, JPG,..etc)
546          * @param output Output temporary file path. The created file is generated by the function and store in output.
547          * @return True if conversion is successful, false otherwise.
548          */
549         static bool ImageToFITS(const QString &filename, const QString &format, QString &output);
550 
551         QString getLastError() const;
552 
553     signals:
554         void converted(QImage);
555 
556         /**
557          * @brief histogramReady Sends signal when histogram construction is complete.
558          */
559         void histogramReady();
560 
561         /**
562          * @brief dataChanged Send signal when undelying raw data buffer data changed.
563          */
564         void dataChanged();
565 
566     private:
567         void loadCommon(const QString &inFilename);
568         /**
569          * @brief privateLoad Load an image (FITS, RAW, or images supported by Qt like jpeg, png).
570          * @param Buffer pointer to image data. If buffer is emtpy, read from disk (m_Filename).
571          * @param silent If true, suppress any messages.
572          * @return true if successfully loaded, false otherwise.
573          */
574         bool privateLoad(const QByteArray &buffer, const QString &extension, bool silent);
575 
576         // Load Qt-supported images.
577         bool loadCanonicalImage(const QByteArray &buffer, const QString &extension, bool silent);
578         // Load FITS images.
579         bool loadFITSImage(const QByteArray &buffer, const QString &extension, bool silent);
580         // Load RAW images.
581         bool loadRAWImage(const QByteArray &buffer, const QString &extension, bool silent);
582 
583         void rotWCSFITS(int angle, int mirror);
584         void calculateMinMax(bool refresh = false);
585         void calculateMedian(bool refresh = false);
586         bool checkDebayer();
587         void readWCSKeys();
588 
589         // Record last FITS error
590         void recordLastError(int errorCode);
591         void logOOMError(uint32_t requiredMemory = 0);
592 
593         // FITS Record
594         bool parseHeader();
595         //int getFITSRecord(QString &recordList, int &nkeys);
596 
597         // Templated functions
598         template <typename T>
599         bool debayer();
600 
601         template <typename T>
602         bool rotFITS(int rotate, int mirror);
603 
604         // Apply Filter
605         template <typename T>
606         void applyFilter(FITSScale type, uint8_t *targetImage, QVector<double> * min = nullptr, QVector<double> * max = nullptr);
607 
608         template <typename T>
609         void calculateMinMax();
610         template <typename T>
611         void calculateMedian();
612 
613         template <typename T>
614         QPair<T, T> getParitionMinMax(uint32_t start, uint32_t stride);
615 
616         /* Calculate the Gaussian blur matrix and apply it to the image using the convolution filter */
617         QVector<double> createGaussianKernel(int size, double sigma);
618         template <typename T>
619         void convolutionFilter(const QVector<double> &kernel, int kernelSize);
620         template <typename T>
621         void gaussianBlur(int kernelSize, double sigma);
622 
623         /* Calculate running average & standard deviation using Welford’s method for computing variance */
624         template <typename T>
625         void runningAverageStdDev();
626         template <typename T>
627         QPair<double, double> getSquaredSumAndMean(uint32_t start, uint32_t stride);
628 
629         template <typename T>
630         void convertToQImage(double dataMin, double dataMax, double scale, double zero, QImage &image);
631 
632         ////////////////////////////////////////////////////////////////////////////////////////
633         ////////////////////////////////////////////////////////////////////////////////////////
634         /// Private Histogram Functions.
635         ////////////////////////////////////////////////////////////////////////////////////////
636         ////////////////////////////////////////////////////////////////////////////////////////
637         template <typename T>  void constructHistogramInternal();
638 
639         /// Pointer to CFITSIO FITS file struct
640         fitsfile *fptr { nullptr };
641         /// Generic data image buffer
642         uint8_t *m_ImageBuffer { nullptr };
643         /// Above buffer size in bytes
644         uint32_t m_ImageBufferSize { 0 };
645         /// Is this a temporary file or one loaded from disk?
646         bool m_isTemporary { false };
647         /// is this file compress (.fits.fz)?
648         bool m_isCompressed { false };
649         /// Did we search for stars yet?
650         bool starsSearched { false };
651         ///Star Selection Algorithm
652         StarAlgorithm starAlgorithm { ALGORITHM_GRADIENT };
653         /// Do we have WCS keywords in this FITS data?
654         bool HasWCS { false };        /// Do we have WCS keywords in this FITS data?
655         /// we can initialize wcs without computing all the image positions.
656         bool FullWCS { false };
657         /// Is the image debayarable?
658         bool HasDebayer { false };
659 
660         /// Our very own file name
661         QString m_Filename, m_compressedFilename;
662         /// FITS Mode (Normal, WCS, Guide, Focus..etc)
663         FITSMode m_Mode;
664         // FITS Observed UTC date time
665         KStarsDateTime m_DateTime;
666 
667         /// How many times the image was rotated? Useful for WCS keywords rotation on save.
668         int rotCounter { 0 };
669         /// How many times the image was flipped horizontally?
670         int flipHCounter { 0 };
671         /// How many times the image was flipped vertically?
672         int flipVCounter { 0 };
673 
674         /// WCS Struct
675         struct wcsprm *m_WCSHandle
676         {
677             nullptr
678         };
679         /// Number of coordinate representations found.
680         int m_nwcs {0};
681         WCSState m_WCSState { Idle };
682         /// All the stars we detected, if any.
683         QList<Edge *> starCenters;
684         QList<Edge *> localStarCenters;
685         /// The biggest fattest star in the image.
686         Edge *m_SelectedHFRStar { nullptr };
687 
688         /// Bayer parameters
689         BayerParams debayerParams;
690         QTemporaryFile m_TemporaryDataFile;
691 
692         /// Data type of fits pixel in the image. Used when saving FITS again.
693         /// There is bit depth and also data type. They're not the same.
694         /// 16bit can be either SHORT_IMG or USHORT_IMG, so m_FITSBITPIX specifies which is
695         int m_FITSBITPIX {USHORT_IMG};
696         FITSImage::Statistic m_Statistics;
697 
698         // A list of header records
699         QList<Record> m_HeaderRecords;
700 
701         // Sky Background
702         SkyBackground m_SkyBackground;
703 
704         // Detector Settings
705         QVariantMap m_SourceExtractorSettings;
706 
707         QFuture<bool> m_StarFindFuture;
708 
709         QList<FITSSkyObject *> m_SkyObjects;
710         bool m_ObjectsSearched {false};
711 
712         QString m_LastError;
713 
714         ////////////////////////////////////////////////////////////////////////////////////////
715         ////////////////////////////////////////////////////////////////////////////////////////
716         /// Histogram Variables
717         ////////////////////////////////////////////////////////////////////////////////////////
718         ////////////////////////////////////////////////////////////////////////////////////////
719         QVector<QVector<uint32_t>> m_CumulativeFrequency;
720         QVector<QVector<double>> m_HistogramIntensity;
721         QVector<QVector<double>> m_HistogramFrequency;
722         QVector<double> m_HistogramBinWidth;
723         uint16_t m_HistogramBinCount { 0 };
724         double m_JMIndex { 1 };
725         bool m_HistogramConstructed { false };
726 
727         // Cached values for hfr and eccentricity computations
728         double cacheHFR { -1 };
729         HFRType cacheHFRType { HFR_AVERAGE };
730         double cacheEccentricity { -1 };
731 };
732