[education/kstars] /: Major refactoring for FITSViewer and how FITS and other data is handled:

Jasem Mutlaq null at kde.org
Sun Oct 25 15:19:47 GMT 2020


Git commit e12899511688a0dd4c0b40c84862a7abe711df6b by Jasem Mutlaq.
Committed on 25/10/2020 at 15:19.
Pushed by mutlaqja into branch 'master'.

Major refactoring for FITSViewer and how FITS and other data is handled:
1. Eliminate any unncessary "temporary" files. If we have a buffer, use it without creating any files on disk unless strictly required.
2. Use a shared pointer for data so that FITSView instances are implicitly sharing the same pointer. This finally allowed for Summary View to load the same view as FITS Viewer without incurring any memory penalty.
3. Add metadata to the FITSImage object to pass information that were passed before using AUX members of the blob which was not safe.
4. Add support to load RAW camera files directly in the FITS Viewer along with loading JPEG/PNGs that were added by Robert earliar.

CCMAIL:kstars-devel at kde.org

M  +7    -7    Tests/fitsviewer/testfitsdata.cpp
M  +0    -2    kstars/auxiliary/ksutils.cpp
M  +0    -1    kstars/auxiliary/ksutils.h
M  +55   -53   kstars/ekos/align/align.cpp
M  +7    -4    kstars/ekos/align/align.h
M  +1    -1    kstars/ekos/align/alignview.cpp
M  +2    -2    kstars/ekos/analyze/analyze.cpp
M  +2    -2    kstars/ekos/auxiliary/darklibrary.cpp
M  +68   -51   kstars/ekos/capture/capture.cpp
M  +9    -7    kstars/ekos/capture/capture.h
M  +3    -2    kstars/ekos/ekoslive/cloud.cpp
M  +1    -1    kstars/ekos/ekoslive/media.cpp
M  +1    -1    kstars/ekos/ekoslive/message.cpp
M  +1    -1    kstars/ekos/ekoslive/message.h
M  +34   -32   kstars/ekos/focus/focus.cpp
M  +4    -1    kstars/ekos/focus/focus.h
M  +4    -3    kstars/ekos/guide/externalguide/phd2.cpp
M  +25   -17   kstars/ekos/guide/guide.cpp
M  +2    -1    kstars/ekos/guide/guide.h
M  +24   -17   kstars/ekos/manager.cpp
M  +1    -1    kstars/ekos/manager.h
M  +1    -1    kstars/ekos/opsekos.cpp
M  +155  -32   kstars/fitsviewer/fitsdata.cpp
M  +14   -12   kstars/fitsviewer/fitsdata.h
M  +7    -7    kstars/fitsviewer/fitstab.cpp
M  +2    -5    kstars/fitsviewer/fitstab.h
M  +27   -26   kstars/fitsviewer/fitsview.cpp
M  +5    -5    kstars/fitsviewer/fitsview.h
M  +85   -76   kstars/fitsviewer/fitsviewer.cpp
M  +9    -9    kstars/fitsviewer/fitsviewer.h
M  +95   -72   kstars/indi/indiccd.cpp
M  +6    -5    kstars/indi/indiccd.h
M  +42   -22   kstars/indi/indistd.cpp
M  +14   -2    kstars/indi/indistd.h
M  +5    -5    kstars/kstarsactions.cpp
M  +1    -1    kstars/kstarsdbus.cpp

https://invent.kde.org/education/kstars/commit/e12899511688a0dd4c0b40c84862a7abe711df6b

diff --git a/Tests/fitsviewer/testfitsdata.cpp b/Tests/fitsviewer/testfitsdata.cpp
index 2700611b7..4aba35069 100644
--- a/Tests/fitsviewer/testfitsdata.cpp
+++ b/Tests/fitsviewer/testfitsdata.cpp
@@ -79,7 +79,7 @@ void TestFitsData::testComputeHFR()
     std::unique_ptr<FITSData> d(new FITSData(MODE));
     QVERIFY(d != nullptr);
 
-    QFuture<bool> worker = d->loadFITS(NAME);
+    QFuture<bool> worker = d->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 60000);
     QVERIFY(worker.result());
 
@@ -124,7 +124,7 @@ void TestFitsData::testBahtinovFocusHFR()
     std::unique_ptr<FITSData> d(new FITSData(MODE));
     QVERIFY(d != nullptr);
 
-    QFuture<bool> worker = d->loadFITS(NAME);
+    QFuture<bool> worker = d->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
     QVERIFY(worker.result());
 
@@ -226,7 +226,7 @@ void TestFitsData::testLoadFits()
     std::unique_ptr<FITSData> fd(new FITSData(MODE));
     QVERIFY(fd != nullptr);
 
-    QFuture<bool> worker = fd->loadFITS(NAME);
+    QFuture<bool> worker = fd->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
     QVERIFY(worker.result());
 
@@ -310,7 +310,7 @@ void TestFitsData::testCentroidAlgorithmBenchmark()
     std::unique_ptr<FITSData> d(new FITSData());
     QVERIFY(d != nullptr);
 
-    QFuture<bool> worker = d->loadFITS(NAME);
+    QFuture<bool> worker = d->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
     QVERIFY(worker.result());
 
@@ -340,7 +340,7 @@ void TestFitsData::testGradientAlgorithmBenchmark()
     std::unique_ptr<FITSData> d(new FITSData());
     QVERIFY(d != nullptr);
 
-    QFuture<bool> worker = d->loadFITS(NAME);
+    QFuture<bool> worker = d->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
     QVERIFY(worker.result());
 
@@ -372,7 +372,7 @@ void TestFitsData::testThresholdAlgorithmBenchmark()
     std::unique_ptr<FITSData> d(new FITSData());
     QVERIFY(d != nullptr);
 
-    QFuture<bool> worker = d->loadFITS(NAME);
+    QFuture<bool> worker = d->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
     QVERIFY(worker.result());
 
@@ -402,7 +402,7 @@ void TestFitsData::testSEPAlgorithmBenchmark()
     std::unique_ptr<FITSData> d(new FITSData());
     QVERIFY(d != nullptr);
 
-    QFuture<bool> worker = d->loadFITS(NAME);
+    QFuture<bool> worker = d->loadFromFile(NAME);
     QTRY_VERIFY_WITH_TIMEOUT(worker.isFinished(), 10000);
     QVERIFY(worker.result());
 
diff --git a/kstars/auxiliary/ksutils.cpp b/kstars/auxiliary/ksutils.cpp
index 9b2b35796..be02a9055 100644
--- a/kstars/auxiliary/ksutils.cpp
+++ b/kstars/auxiliary/ksutils.cpp
@@ -1714,9 +1714,7 @@ bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMes
             return false;
         }
     }
-
     return true;
-
 #endif
 }
 
diff --git a/kstars/auxiliary/ksutils.h b/kstars/auxiliary/ksutils.h
index ec48fabf3..9885ba2e6 100644
--- a/kstars/auxiliary/ksutils.h
+++ b/kstars/auxiliary/ksutils.h
@@ -293,5 +293,4 @@ QByteArray getJPLQueryString(const QByteArray &kind, const QByteArray &dataField
  * @return True if conversion is successful, false otherwise.
  */
 bool RAWToJPEG(const QString &rawImage, const QString &output, QString &errorMessage);
-
 }
diff --git a/kstars/ekos/align/align.cpp b/kstars/ekos/align/align.cpp
index 955a59d69..07c9e953c 100644
--- a/kstars/ekos/align/align.cpp
+++ b/kstars/ekos/align/align.cpp
@@ -2936,7 +2936,7 @@ bool Align::captureAndSolve()
 
     alignView->setBaseSize(alignWidget->size());
 
-    connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Align::newFITS);
+    connect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Align::processData);
     connect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Align::checkCCDExposureProgress);
 
     // In case of remote solver, check if we need to update active CCD
@@ -3087,17 +3087,20 @@ bool Align::captureAndSolve()
     return true;
 }
 
-void Align::newFITS(IBLOB *bp)
+void Align::processData(const QSharedPointer<FITSData> &data)
 {
-    // Ignore guide head if there is any.
-    if (!strcmp(bp->name, "CCD2"))
+    if (data->property("chip").toInt() == ISD::CCDChip::GUIDE_CCD)
         return;
 
-    disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Align::newFITS);
+    disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Align::processData);
     disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Align::checkCCDExposureProgress);
 
-    blobType     = *(static_cast<ISD::CCD::BlobType *>(bp->aux1));
-    blobFileName = QString(static_cast<char *>(bp->aux2));
+    if (data)
+        m_ImageData = data;
+    else
+        m_ImageData.reset();
+    //    blobType     = *(static_cast<ISD::CCD::BlobType *>(bp->aux1));
+    //    blobFileName = QString(static_cast<char *>(bp->aux2));
 
     // If it's Refresh, we're done
     if (pahStage == PAH_REFRESH)
@@ -3111,42 +3114,42 @@ void Align::newFITS(IBLOB *bp)
         if (solverBackendGroup->checkedId() != SOLVER_REMOTE)
         {
         **/
-    if (blobType == ISD::CCD::BLOB_FITS)
-    {
-        ISD::CCDChip *targetChip =
-            currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
-
-        if (alignDarkFrameCheck->isChecked())
-        {
-            int x, y, w, h, binx = 1, biny = 1;
-            targetChip->getFrame(&x, &y, &w, &h);
-            targetChip->getBinning(&binx, &biny);
+    //    if (blobType == ISD::CCD::BLOB_FITS)
+    //    {
+    ISD::CCDChip *targetChip =
+        currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
 
-            uint16_t offsetX = x / binx;
-            uint16_t offsetY = y / biny;
-            FITSData *darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value());
+    if (alignDarkFrameCheck->isChecked())
+    {
+        int x, y, w, h, binx = 1, biny = 1;
+        targetChip->getFrame(&x, &y, &w, &h);
+        targetChip->getBinning(&binx, &biny);
 
-            connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, [&](bool completed)
-            {
-                DarkLibrary::Instance()->disconnect(this);
-                alignDarkFrameCheck->setChecked(completed);
-                if (completed)
-                    setCaptureComplete();
-                else
-                    abort();
-            });
-            connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Align::appendLogText);
+        uint16_t offsetX = x / binx;
+        uint16_t offsetY = y / biny;
+        FITSData *darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, exposureIN->value());
 
-            if (darkData)
-                DarkLibrary::Instance()->subtract(darkData, alignView, FITS_NONE, offsetX, offsetY);
+        connect(DarkLibrary::Instance(), &DarkLibrary::darkFrameCompleted, this, [&](bool completed)
+        {
+            DarkLibrary::Instance()->disconnect(this);
+            alignDarkFrameCheck->setChecked(completed);
+            if (completed)
+                setCaptureComplete();
             else
-            {
-                DarkLibrary::Instance()->captureAndSubtract(targetChip, alignView, exposureIN->value(), offsetX, offsetY);
-            }
+                abort();
+        });
+        connect(DarkLibrary::Instance(), &DarkLibrary::newLog, this, &Ekos::Align::appendLogText);
 
-            return;
+        if (darkData)
+            DarkLibrary::Instance()->subtract(darkData, alignView, FITS_NONE, offsetX, offsetY);
+        else
+        {
+            DarkLibrary::Instance()->captureAndSubtract(targetChip, alignView, exposureIN->value(), offsetX, offsetY);
         }
+
+        return;
     }
+    //}
 
     setCaptureComplete();
     //}
@@ -3166,6 +3169,7 @@ void Align::setCaptureComplete()
 
     emit newImage(alignView);
 
+#if 0
     if (Options::solverType() == SSolver::SOLVER_ONLINEASTROMETRY &&
             Options::astrometryUseJPEG())
     {
@@ -3179,10 +3183,11 @@ void Align::setCaptureComplete()
                 blobFileName = jpegFile;
         }
     }
+#endif
 
     solverFOV->setImage(alignView->getDisplayImage());
 
-    m_FileToSolve = blobFileName;
+    //m_FileToSolve = blobFileName;
     startSolving();
 }
 
@@ -3223,7 +3228,8 @@ void Align::startSolving()
     const SSolver::SolverType type = static_cast<SSolver::SolverType>(m_StellarSolver->property("SolverType").toInt());
     if(type == SSolver::SOLVER_LOCALASTROMETRY || type == SSolver::SOLVER_ASTAP)
     {
-        m_StellarSolver->setProperty("FileToProcess", m_FileToSolve);
+        // TODO send FITSData buffer or convert to jpeg then set the filename
+        //m_StellarSolver->setProperty("FileToProcess", m_FileToSolve);
 
         if(Options::sextractorIsInternal())
             m_StellarSolver->setProperty("SextractorBinaryPath", QString("%1/%2").arg(QCoreApplication::applicationDirPath())
@@ -3250,7 +3256,8 @@ void Align::startSolving()
 
     if(type == SSolver::SOLVER_ONLINEASTROMETRY )
     {
-        m_StellarSolver->setProperty("FileToProcess", m_FileToSolve);
+        // TODO send FITSData buffer or convert to jpeg then set the filename
+        //m_StellarSolver->setProperty("FileToProcess", m_FileToSolve);
         m_StellarSolver->setProperty("AstrometryAPIKey", Options::astrometryAPIKey());
         m_StellarSolver->setProperty("AstrometryAPIURL", Options::astrometryAPIURL());
     }
@@ -3797,7 +3804,7 @@ void Align::abort()
     m_SlewErrorCounter = 0;
     m_AlignTimer.stop();
 
-    disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Align::newFITS);
+    disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Align::processData);
     disconnect(currentCCD, &ISD::CCD::newExposureValue, this, &Ekos::Align::checkCCDExposureProgress);
 
     if (rememberUploadMode != currentCCD->getUploadMode())
@@ -4870,8 +4877,8 @@ bool Align::loadAndSlew(QString fileURL)
     stopB->setEnabled(true);
     pi->startAnimation();
 
-    alignView->loadFITS(fileURL, false);
-    m_FileToSolve = fileURL;
+    alignView->loadFile(fileURL, false);
+    //m_FileToSolve = fileURL;
     connect(alignView, &FITSView::loaded, this, &Align::startSolving);
 
     return true;
@@ -5381,12 +5388,9 @@ void Align::setCaptureStatus(CaptureState newState)
 
 void Align::showFITSViewer()
 {
-    FITSData *data = alignView->getImageData();
-
-    if (data)
+    static int lastFVTabID = -1;
+    if (m_ImageData)
     {
-        QUrl url = QUrl::fromLocalFile(data->filename());
-
         if (fv.isNull())
         {
             if (Options::singleWindowCapturedFITS())
@@ -5397,13 +5401,11 @@ void Align::showFITSViewer()
                 KStars::Instance()->addFITSViewer(fv);
             }
 
-            fv->addFITS(url);
-            FITSView *currentView = fv->getCurrentView();
-            if (currentView)
-                currentView->getImageData()->setAutoRemoveTemporaryFITS(false);
+
+            fv->loadData(m_ImageData, QUrl(), &lastFVTabID);
         }
-        else
-            fv->updateFITS(url, 0);
+        else if (fv->updateData(m_ImageData, QUrl(), lastFVTabID, &lastFVTabID) == false)
+            fv->loadData(m_ImageData, QUrl(), &lastFVTabID);
 
         fv->show();
     }
diff --git a/kstars/ekos/align/align.h b/kstars/ekos/align/align.h
index 341fabef0..c5ac452a1 100644
--- a/kstars/ekos/align/align.h
+++ b/kstars/ekos/align/align.h
@@ -182,7 +182,7 @@ class Align : public QWidget, public Ui::Align
 
         std::unique_ptr<StellarSolver> m_StellarSolver;
         QList<SSolver::Parameters> optionsList;
-        QString m_FileToSolve;
+        //QString m_FileToSolve;
 
 
         /** DBUS interface function.
@@ -398,7 +398,7 @@ class Align : public QWidget, public Ui::Align
              * @brief Process new FITS received from CCD.
              * @param bp pointer to blob property
              */
-        void newFITS(IBLOB *bp);
+        void processData(const QSharedPointer<FITSData> &data);
 
         /** \addtogroup AlignDBusInterface
              *  @{
@@ -898,8 +898,8 @@ class Align : public QWidget, public Ui::Align
         QTimer m_AlignTimer;
 
         // BLOB Type
-        ISD::CCD::BlobType blobType;
-        QString blobFileName;
+        //        ISD::CCD::BlobType blobType;
+        //        QString blobFileName;
 
         // Align Frame
         AlignView *alignView { nullptr };
@@ -984,6 +984,9 @@ class Align : public QWidget, public Ui::Align
         // Filter Manager
         QSharedPointer<FilterManager> filterManager;
 
+        // Data
+        QSharedPointer<FITSData> m_ImageData;
+
         // Active Profile
         ProfileInfo *m_ActiveProfile { nullptr };
 
diff --git a/kstars/ekos/align/alignview.cpp b/kstars/ekos/align/alignview.cpp
index b37863cf0..60dddb798 100644
--- a/kstars/ekos/align/alignview.cpp
+++ b/kstars/ekos/align/alignview.cpp
@@ -54,7 +54,7 @@ bool AlignView::injectWCS(double orientation, double ra, double dec, double pixs
     if (wcsWatcher.isRunning() == false)
     {
         // Load WCS async
-        QFuture<bool> future = QtConcurrent::run(imageData, &FITSData::loadWCS);
+        QFuture<bool> future = QtConcurrent::run(imageData.get(), &FITSData::loadWCS);
         wcsWatcher.setFuture(future);
     }
 
diff --git a/kstars/ekos/analyze/analyze.cpp b/kstars/ekos/analyze/analyze.cpp
index 4c5ed28df..916db735f 100644
--- a/kstars/ekos/analyze/analyze.cpp
+++ b/kstars/ekos/analyze/analyze.cpp
@@ -1759,13 +1759,13 @@ void Analyze::displayFITS(const QString &filename)
             KStars::Instance()->addFITSViewer(fitsViewer);
         }
 
-        fitsViewer->addFITS(url);
+        fitsViewer->loadFile(url);
         FITSView *currentView = fitsViewer->getCurrentView();
         if (currentView)
             currentView->getImageData()->setAutoRemoveTemporaryFITS(false);
     }
     else
-        fitsViewer->updateFITS(url, 0);
+        fitsViewer->updateFile(url, 0);
 
     fitsViewer->show();
 }
diff --git a/kstars/ekos/auxiliary/darklibrary.cpp b/kstars/ekos/auxiliary/darklibrary.cpp
index 73865684e..35d5d1a66 100644
--- a/kstars/ekos/auxiliary/darklibrary.cpp
+++ b/kstars/ekos/auxiliary/darklibrary.cpp
@@ -119,7 +119,7 @@ bool DarkLibrary::loadDarkFile(const QString &filename)
 {
     FITSData *darkData = new FITSData();
 
-    bool rc = darkData->loadFITS(filename);
+    bool rc = darkData->loadFromFile(filename);
 
     if (rc)
         darkFiles[filename] = darkData;
@@ -501,7 +501,7 @@ void DarkLibrary::newFITS(IBLOB * bp)
     FITSData *calibrationData = new FITSData();
 
     // Deep copy of the data
-    if (calibrationData->loadFITS(calibrationView->getImageData()->filename()))
+    if (calibrationData->loadFromFile(calibrationView->getImageData()->filename()))
     {
         saveDarkFile(calibrationData);
         subtract(calibrationData, subtractParams.targetImage, subtractParams.targetChip->getCaptureFilter(),
diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp
index cd8b04b20..8cece3e54 100644
--- a/kstars/ekos/capture/capture.cpp
+++ b/kstars/ekos/capture/capture.cpp
@@ -770,9 +770,9 @@ void Capture::stop(CaptureState targetState)
 
     if (meridianFlipStage == MF_NONE || meridianFlipStage >= MF_COMPLETED)
         secondsLabel->clear();
-    disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS);
+    disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Capture::processData);
     disconnect(currentCCD, &ISD::CCD::newExposureValue, this,  &Ekos::Capture::setExposureProgress);
-    disconnect(currentCCD, &ISD::CCD::previewFITSGenerated, this, &Ekos::Capture::setGeneratedPreviewFITS);
+    //    disconnect(currentCCD, &ISD::CCD::previewFITSGenerated, this, &Ekos::Capture::setGeneratedPreviewFITS);
     disconnect(currentCCD, &ISD::CCD::ready, this, &Ekos::Capture::ready);
 
     currentCCD->setFITSDir(QString());
@@ -809,20 +809,20 @@ void Capture::stop(CaptureState targetState)
     setMeridianFlipStage(MF_READY);
 }
 
-void Capture::sendNewImage(const QString &filename, ISD::CCDChip * myChip)
-{
-    if (activeJob && (myChip == nullptr || myChip == targetChip))
-    {
-        activeJob->setProperty("filename", filename);
-        emit newImage(activeJob);
-        // We only emit this for client/both images since remote images already send this automatically
-        if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL && activeJob->isPreview() == false)
-        {
-            emit newSequenceImage(filename, m_GeneratedPreviewFITS);
-            m_GeneratedPreviewFITS.clear();
-        }
-    }
-}
+//void Capture::sendNewImage(const QString &filename, ISD::CCDChip * myChip)
+//{
+//    if (activeJob && (myChip == nullptr || myChip == targetChip))
+//    {
+//        activeJob->setProperty("filename", filename);
+//        emit newImage(activeJob);
+//        // We only emit this for client/both images since remote images already send this automatically
+//        if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL && activeJob->isPreview() == false)
+//        {
+//            emit newSequenceImage(filename, m_GeneratedPreviewFITS);
+//            m_GeneratedPreviewFITS.clear();
+//        }
+//    }
+//}
 
 bool Capture::setCamera(const QString &device)
 {
@@ -1570,26 +1570,41 @@ void Capture::checkNextExposure()
 }
 
 
-void Capture::newFITS(IBLOB * bp)
+void Capture::processData(const QSharedPointer<FITSData> &data)
 {
     ISD::CCDChip * tChip = nullptr;
 
+    QString blobInfo;
+    if (data)
+    {
+        m_ImageData = data;
+        blobInfo = QString("{Device: %1 Property: %2 Element: %3 Chip: %4}").arg(data->property("device").toString())
+                   .arg(data->property("blobVector").toString())
+                   .arg(data->property("blobElement").toString())
+                   .arg(data->property("chip").toInt());
+    }
+    else
+        m_ImageData.reset();
+
     // If there is no active job, ignore
     if (activeJob == nullptr)
     {
-        qCWarning(KSTARS_EKOS_CAPTURE) << "Ignoring received FITS" << bp->name << "as active job is null.";
+        if (data)
+            qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo << "Ignoring received FITS as active job is null.";
         return;
     }
 
     if (meridianFlipStage >= MF_ALIGNING)
     {
-        qCWarning(KSTARS_EKOS_CAPTURE) << "Ignoring Received FITS" << bp->name << "as meridian flip stage is" << meridianFlipStage;
+        if (data)
+            qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo << "Ignoring Received FITS as meridian flip stage is" << meridianFlipStage;
         return;
     }
 
+    // If image is client or both, let's process it.
     if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL)
     {
-        if (bp == nullptr)
+        if (data.isNull())
         {
             appendLogText(i18n("Failed to save file to %1", activeJob->getSignature()));
             abort();
@@ -1598,37 +1613,33 @@ void Capture::newFITS(IBLOB * bp)
 
         if (m_State == CAPTURE_IDLE || m_State == CAPTURE_ABORTED)
         {
-            qCWarning(KSTARS_EKOS_CAPTURE) << "Ignoring Received FITS" << bp->name << "as current capture state is not active" <<
-                                           m_State;
+            qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo << "Ignoring Received FITS as current capture state is not active" << m_State;
             return;
         }
 
-        if (!strcmp(bp->name, "CCD2"))
-            tChip = currentCCD->getChip(ISD::CCDChip::GUIDE_CCD);
-        else
-            tChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
-
+        //if (!strcmp(data->name, "CCD2"))
+        tChip = currentCCD->getChip(static_cast<ISD::CCDChip::ChipType>(data->property("chip").toInt()));
         if (tChip != targetChip)
         {
             if (guideState == GUIDE_IDLE)
-                qCWarning(KSTARS_EKOS_CAPTURE) << "Ignoring Received FITS" << bp->name << "as it does not correspond to the target chip" <<
-                                               targetChip->getType();
+                qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo << "Ignoring Received FITS as it does not correspond to the target chip"
+                                               << targetChip->getType();
             return;
         }
 
         if (targetChip->getCaptureMode() == FITS_FOCUS || targetChip->getCaptureMode() == FITS_GUIDE)
         {
-            qCWarning(KSTARS_EKOS_CAPTURE) << "Ignoring Received FITS" << bp->name << "as it has the wrong capture mode" <<
+            qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo << "Ignoring Received FITS as it has the wrong capture mode" <<
                                            targetChip->getCaptureMode();
             return;
         }
 
         // If the FITS is not for our device, simply ignore
         //if (QString(bp->bvp->device)  != currentCCD->getDeviceName() || (startB->isEnabled() && previewB->isEnabled()))
-        if (QString(bp->bvp->device) != currentCCD->getDeviceName())
+        if (data->property("device").toString() != currentCCD->getDeviceName())
         {
-            qCWarning(KSTARS_EKOS_CAPTURE) << "Ignoring Received FITS" << bp->name << "as the blob device name" << bp->bvp->device
-                                           << "does not equal active camera" << currentCCD->getDeviceName();
+            qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo << "Ignoring Received FITS as the blob device name does not equal active camera"
+                                           << currentCCD->getDeviceName();
             return;
         }
 
@@ -1641,7 +1652,7 @@ void Capture::newFITS(IBLOB * bp)
         // currentCCD->isLooping driver side looping (without any delays, next capture starts after driver reads data)
         if (m_isLooping == false && currentCCD->isLooping() == false)
         {
-            disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS);
+            disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Capture::processData);
 
             if (useGuideHead == false && darkSubCheck->isChecked() && activeJob->isPreview())
             {
@@ -1671,8 +1682,8 @@ void Capture::newFITS(IBLOB * bp)
         }
     }
 
-    blobChip    = bp ? static_cast<ISD::CCDChip *>(bp->aux0) : nullptr;
-    blobFilename = bp ? static_cast<const char *>(bp->aux2) : QString();
+    //    blobChip    = bp ? static_cast<ISD::CCDChip *>(bp->aux0) : nullptr;
+    //    blobFilename = bp ? static_cast<const char *>(bp->aux2) : QString();
 
     setCaptureComplete();
 }
@@ -1701,8 +1712,9 @@ IPState Capture::setCaptureComplete()
     // In case we're framing, let's start
     if (m_isLooping)
     {
-        sendNewImage(blobFilename, blobChip);
+        //sendNewImage(blobFilename, blobChip);
         secondsLabel->setText(i18n("Framing..."));
+        emit newImage(activeJob, m_ImageData);
         activeJob->capture(darkSubCheck->isChecked() ? true : false);
         return IPS_OK;
     }
@@ -1736,11 +1748,13 @@ IPState Capture::setCaptureComplete()
     // If it was initially set as pure preview job and NOT as preview for calibration
     if (activeJob->isPreview() && calibrationStage != CAL_CALIBRATION)
     {
-        sendNewImage(blobFilename, blobChip);
+        //sendNewImage(blobFilename, blobChip);
+        emit newImage(activeJob, m_ImageData);
         jobs.removeOne(activeJob);
         // Reset upload mode if it was changed by preview
         currentCCD->setUploadMode(rememberUploadMode);
-        delete (activeJob);
+        //delete (activeJob);
+        activeJob->deleteLater();
         // Reset active job pointer
         activeJob = nullptr;
         abort();
@@ -1771,11 +1785,11 @@ IPState Capture::setCaptureComplete()
     ditherCounter--;
 
     // JM 2020-06-17: Emit newImage for LOCAL images (stored on remote host)
-    if (currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL)
-        emit newImage(activeJob);
+    //if (currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL)
+    emit newImage(activeJob, m_ImageData);
     // For Client/Both images, send file name.
-    else
-        sendNewImage(blobFilename, blobChip);
+    //else
+    //    sendNewImage(blobFilename, blobChip);
 
 
     /* If we were assigned a captured frame map, also increase the relevant counter for prepareJob */
@@ -1799,6 +1813,9 @@ IPState Capture::setCaptureComplete()
 
     FITSView * currentImage = targetChip->getImageView(FITS_NORMAL);
     double hfr = currentImage ? currentImage->getImageData()->getHFR(HFR_AVERAGE) : 0;
+    QString blobFilename;
+    if (m_ImageData)
+        blobFilename = m_ImageData->property("filename").toString();
     emit captureComplete(blobFilename, activeJob->getExposure(), activeJob->getFilterName(), hfr);
 
     m_State = CAPTURE_IMAGE_RECEIVED;
@@ -2195,8 +2212,8 @@ void Capture::captureImage()
             currentCCD->setExposureLoopCount(static_cast<uint>(remaining));
     }
 
-    connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Capture::newFITS, Qt::UniqueConnection);
-    connect(currentCCD, &ISD::CCD::previewFITSGenerated, this, &Ekos::Capture::setGeneratedPreviewFITS, Qt::UniqueConnection);
+    connect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Capture::processData, Qt::UniqueConnection);
+    //connect(currentCCD, &ISD::CCD::previewFITSGenerated, this, &Ekos::Capture::setGeneratedPreviewFITS, Qt::UniqueConnection);
 
     if (activeJob->getFrameType() == FRAME_FLAT)
     {
@@ -2448,7 +2465,7 @@ void Capture::setExposureProgress(ISD::CCDChip * tChip, double value, IPState st
         {
             if (activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY)
             {
-                newFITS(nullptr);
+                processData(nullptr);
                 return;
             }
         }
@@ -3139,7 +3156,7 @@ void Capture::prepareJob(SequenceJob * job)
 void Capture::prepareActiveJob()
 {
     // Just notification of active job stating up
-    emit newImage(activeJob);
+    emit newImage(activeJob, m_ImageData);
 
     //connect(job, SIGNAL(checkFocus()), this, &Ekos::Capture::startPostFilterAutoFocus()));
 
@@ -6945,10 +6962,10 @@ void Capture::processCaptureTimeout()
     else restartExposure();
 }
 
-void Capture::setGeneratedPreviewFITS(const QString &previewFITS)
-{
-    m_GeneratedPreviewFITS = previewFITS;
-}
+//void Capture::setGeneratedPreviewFITS(const QString &previewFITS)
+//{
+//    m_GeneratedPreviewFITS = previewFITS;
+//}
 
 void Capture::createDSLRDialog()
 {
diff --git a/kstars/ekos/capture/capture.h b/kstars/ekos/capture/capture.h
index df44d342f..1f36981f9 100644
--- a/kstars/ekos/capture/capture.h
+++ b/kstars/ekos/capture/capture.h
@@ -547,7 +547,7 @@ class Capture : public QWidget, public Ui::Capture
              * @brief newFITS process new FITS data received from camera. Update status of active job and overall sequence.
              * @param bp pointer to blob containing FITS data
              */
-        void newFITS(IBLOB *bp);
+        void processData(const QSharedPointer<FITSData> &data);
 
         /**
              * @brief checkCCD Refreshes the CCD information in the capture module.
@@ -660,7 +660,7 @@ class Capture : public QWidget, public Ui::Capture
         void setMountStatus(ISD::Telescope::Status newState);
 
         void setGuideChip(ISD::CCDChip *chip);
-        void setGeneratedPreviewFITS(const QString &previewFITS);
+        //void setGeneratedPreviewFITS(const QString &previewFITS);
 
         // Clear Camera Configuration
         void clearCameraConfiguration();
@@ -743,7 +743,7 @@ class Capture : public QWidget, public Ui::Capture
         IPState checkDarkFramePendingTasks();
 
         // Send image info
-        void sendNewImage(const QString &filename, ISD::CCDChip *myChip);
+        //void sendNewImage(const QString &filename, ISD::CCDChip *myChip);
 
         // Capture
         IPState setCaptureComplete();
@@ -780,7 +780,7 @@ class Capture : public QWidget, public Ui::Capture
         void resetFocus();
         void suspendGuiding();
         void resumeGuiding();
-        void newImage(Ekos::SequenceJob *job);
+        void newImage(Ekos::SequenceJob *job, const QSharedPointer<FITSData> &data);
         void newExposureProgress(Ekos::SequenceJob *job);
         void newDownloadProgress(double);
         void sequenceChanged(const QJsonArray &sequence);
@@ -922,8 +922,8 @@ class Capture : public QWidget, public Ui::Capture
         ISD::CCDChip *targetChip { nullptr };
         ISD::CCDChip *guideChip { nullptr };
         ISD::CCDChip *blobChip { nullptr };
-        QString blobFilename;
-        QString m_GeneratedPreviewFITS;
+        //QString blobFilename;
+        //QString m_GeneratedPreviewFITS;
 
         // They're generic GDInterface because they could be either ISD::CCD or ISD::Filter
         QList<ISD::GDInterface *> Filters;
@@ -938,7 +938,9 @@ class Capture : public QWidget, public Ui::Capture
         ISD::LightBox *currentLightBox { nullptr };
         ISD::Dome *currentDome { nullptr };
 
-        QPointer<QDBusInterface> mountInterface { nullptr };
+        QPointer<QDBusInterface> mountInterface;
+
+        QSharedPointer<FITSData> m_ImageData;
 
         QStringList m_LogText;
         QUrl m_SequenceURL;
diff --git a/kstars/ekos/ekoslive/cloud.cpp b/kstars/ekos/ekoslive/cloud.cpp
index 4ec889a61..e794f367c 100644
--- a/kstars/ekos/ekoslive/cloud.cpp
+++ b/kstars/ekos/ekoslive/cloud.cpp
@@ -31,7 +31,8 @@ Cloud::Cloud(Ekos::Manager * manager): m_Manager(manager)
 {
     connect(&m_WebSocket, &QWebSocket::connected, this, &Cloud::onConnected);
     connect(&m_WebSocket, &QWebSocket::disconnected, this, &Cloud::onDisconnected);
-    connect(&m_WebSocket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this, &Cloud::onError);
+    connect(&m_WebSocket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this,
+            &Cloud::onError);
 
     connect(&watcher, &QFutureWatcher<bool>::finished, this, &Cloud::sendImage, Qt::UniqueConnection);
 
@@ -140,7 +141,7 @@ void Cloud::sendPreviewImage(const QString &filename, const QString &uuid)
     m_UUID = uuid;
     imageData.reset(new FITSData());
     imageData->setAutoRemoveTemporaryFITS(false);
-    QFuture<bool> result = imageData->loadFITS(filename);
+    QFuture<bool> result = imageData->loadFromFile(filename);
     watcher.setFuture(result);
 }
 
diff --git a/kstars/ekos/ekoslive/media.cpp b/kstars/ekos/ekoslive/media.cpp
index efcc7bb2a..a8704643d 100644
--- a/kstars/ekos/ekoslive/media.cpp
+++ b/kstars/ekos/ekoslive/media.cpp
@@ -173,7 +173,7 @@ void Media::sendPreviewImage(const QString &filename, const QString &uuid)
 
     previewImage.reset(new FITSView());
     connect(previewImage.get(), &FITSView::loaded, this, &Media::sendImage);
-    previewImage->loadFITS(filename);
+    previewImage->loadFile(filename);
 }
 
 void Media::sendPreviewImage(FITSView * view, const QString &uuid)
diff --git a/kstars/ekos/ekoslive/message.cpp b/kstars/ekos/ekoslive/message.cpp
index 3594f08bc..09c053769 100644
--- a/kstars/ekos/ekoslive/message.cpp
+++ b/kstars/ekos/ekoslive/message.cpp
@@ -235,7 +235,7 @@ void Message::sendCameras()
     {
         ISD::CCD *oneCCD = dynamic_cast<ISD::CCD*>(gd);
         connect(oneCCD, &ISD::CCD::newTemperatureValue, this, &Message::sendTemperature, Qt::UniqueConnection);
-        connect(oneCCD, &ISD::CCD::previewJPEGGenerated, this, &Message::previewJPEGGenerated, Qt::UniqueConnection);
+        //connect(oneCCD, &ISD::CCD::previewJPEGGenerated, this, &Message::previewJPEGGenerated, Qt::UniqueConnection);
         ISD::CCDChip *primaryChip = oneCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
 
         double temperature = Ekos::INVALID_VALUE;
diff --git a/kstars/ekos/ekoslive/message.h b/kstars/ekos/ekoslive/message.h
index 359a946ed..a0b72dc0a 100644
--- a/kstars/ekos/ekoslive/message.h
+++ b/kstars/ekos/ekoslive/message.h
@@ -65,7 +65,7 @@ class Message : public QObject
         void expired();
         void optionsChanged(QMap<int, bool> options);
         // This is forward signal from INDI::CCD
-        void previewJPEGGenerated(const QString &previewJPEG, QJsonObject metadata);
+        //void previewJPEGGenerated(const QString &previewJPEG, QJsonObject metadata);
 
         void resetPolarView();
 
diff --git a/kstars/ekos/focus/focus.cpp b/kstars/ekos/focus/focus.cpp
index 46c5e54e9..10c3591f4 100644
--- a/kstars/ekos/focus/focus.cpp
+++ b/kstars/ekos/focus/focus.cpp
@@ -918,7 +918,7 @@ void Focus::stop(bool aborted)
     HFRFrames.clear();
     //maxHFR=1;
 
-    disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Focus::newFITS);
+    disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Focus::processData);
     disconnect(currentCCD, &ISD::CCD::captureFailed, this, &Ekos::Focus::processCaptureFailure);
 
     if (rememberUploadMode != currentCCD->getUploadMode())
@@ -1051,7 +1051,7 @@ void Focus::capture(double settleTime)
     if (gainIN->isEnabled())
         currentCCD->setGain(gainIN->value());
 
-    connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Focus::newFITS);
+    connect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Focus::processData);
     connect(currentCCD, &ISD::CCD::captureFailed, this, &Ekos::Focus::processCaptureFailure);
 
     targetChip->setFrameType(FRAME_LIGHT);
@@ -1155,23 +1155,29 @@ bool Focus::changeFocus(int amount)
     return true;
 }
 
-void Focus::newFITS(IBLOB *bp)
+void Focus::processData(const QSharedPointer<FITSData> &data)
 {
-    if (bp == nullptr)
-    {
-        capture();
-        return;
-    }
+    //    if (data == nullptr)
+    //    {
+    //        capture();
+    //        return;
+    //    }
+
 
     // Ignore guide head if there is any.
-    if (!strcmp(bp->name, "CCD2"))
+    if (data->property("chip").toInt() == ISD::CCDChip::GUIDE_CCD)
         return;
 
+    if (data)
+        m_ImageData = data;
+    else
+        m_ImageData.reset();
+
     captureTimeout.stop();
     captureTimeoutCounter = 0;
 
     ISD::CCDChip *targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
-    disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Focus::newFITS);
+    disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Focus::processData);
     disconnect(currentCCD, &ISD::CCD::captureFailed, this, &Ekos::Focus::processCaptureFailure);
 
     if (darkFrameCheck->isChecked())
@@ -1218,7 +1224,7 @@ void Focus::calculateHFR()
         return;
     }
 
-    FITSData *image_data = focusView->getImageData();
+    //FITSData *image_data = focusView->getImageData();
 
     if (Options::focusUseFullField())
     {
@@ -1227,7 +1233,7 @@ void Focus::calculateHFR()
         focusView->filterStars();
 
         // Get the average HFR of the whole frame
-        setCurrentHFR(image_data->getHFR(HFR_AVERAGE));
+        setCurrentHFR(m_ImageData->getHFR(HFR_AVERAGE));
     }
     else
     {
@@ -1238,10 +1244,10 @@ void Focus::calculateHFR()
         double hfr = -1;
 
         if (starCenter.isNull() == false)
-            hfr = image_data->getHFR(starCenter.x(), starCenter.y());
+            hfr = m_ImageData->getHFR(starCenter.x(), starCenter.y());
         // If not found, then get the MAX or MEDIAN depending on the selected algorithm.
         if (hfr < 0)
-            hfr = image_data->getHFR(focusDetection == ALGORITHM_SEP ? HFR_HIGH : HFR_MAX);
+            hfr = m_ImageData->getHFR(focusDetection == ALGORITHM_SEP ? HFR_HIGH : HFR_MAX);
 
         setCurrentHFR(hfr);
     }
@@ -1362,11 +1368,11 @@ void Focus::setCurrentHFR(double value)
     currentHFR = value;
 
     // Get handle to the image data
-    FITSData *image_data = focusView->getImageData();
+    //FITSData *image_data = focusView->getImageData();
 
     // Let's now report the current HFR
     qCDebug(KSTARS_EKOS_FOCUS) << "Focus newFITS #" << HFRFrames.count() + 1 << ": Current HFR " << currentHFR << " Num stars "
-                               << (starSelected ? 1 : image_data->getDetectedStars());
+                               << (starSelected ? 1 : m_ImageData->getDetectedStars());
 
     // Take the new HFR into account, eventually continue to stack samples
     if (appendHFR(currentHFR))
@@ -1385,7 +1391,7 @@ void Focus::setCurrentHFR(double value)
     // Format the HFR value into a string
     QString HFRText = QString("%1").arg(currentHFR, 0, 'f', 2);
     HFROut->setText(HFRText);
-    starsOut->setText(QString("%1").arg(image_data->getDetectedStars()));
+    starsOut->setText(QString("%1").arg(m_ImageData->getDetectedStars()));
 
     // Display message in case _last_ HFR was negative
     if (lastHFR == -1)
@@ -1413,7 +1419,7 @@ void Focus::setCurrentHFR(double value)
         // The starCenter _must_ already be defined, otherwise, we proceed until
         // the latter half of the function searches for a star and define it.
         if (starCenter.isNull() == false && (inAutoFocus || minimumRequiredHFR >= 0) &&
-                (selectedHFRStarHFR = image_data->getSelectedHFRStar()) != nullptr)
+                (selectedHFRStarHFR = m_ImageData->getSelectedHFRStar()) != nullptr)
         {
             // Now we have star selected in the frame
             starSelected = true;
@@ -1518,7 +1524,7 @@ void Focus::setCaptureComplete()
     captureInProgress = false;
 
     // Get handle to the image data
-    FITSData *image_data = focusView->getImageData();
+    //FITSData *image_data = focusView->getImageData();
 
     // Emit the whole image
     emit newImage(focusView);
@@ -1531,7 +1537,7 @@ void Focus::setCaptureComplete()
     // THEN let's find stars in the image and get current HFR
     if (inFocusLoop == false || (inFocusLoop && (focusView->isTrackingBoxEnabled() || Options::focusUseFullField())))
     {
-        if (image_data->areStarsSearched() == false)
+        if (m_ImageData->areStarsSearched() == false)
         {
             analyzeSources();
         }
@@ -1543,7 +1549,7 @@ void Focus::setCaptureComplete()
 void Focus::setHFRComplete()
 {
     // Get handle to the image data
-    FITSData *image_data = focusView->getImageData();
+    //FITSData *image_data = focusView->getImageData();
 
     // If we are just framing, let's capture again
     if (inFocusLoop)
@@ -1586,7 +1592,7 @@ void Focus::setHFRComplete()
         if (useAutoStar->isChecked())
         {
             // Do we have a valid star detected?
-            Edge *selectedHFRStar = image_data->getSelectedHFRStar();
+            Edge *selectedHFRStar = m_ImageData->getSelectedHFRStar();
 
             if (selectedHFRStar == nullptr)
             {
@@ -3254,11 +3260,9 @@ void Focus::syncTrackingBoxPosition()
 
 void Focus::showFITSViewer()
 {
-    FITSData *data = focusView->getImageData();
-    if (data)
+    static int lastFVTabID = -1;
+    if (m_ImageData)
     {
-        QUrl url = QUrl::fromLocalFile(data->filename());
-
         if (fv.isNull())
         {
             if (Options::singleWindowCapturedFITS())
@@ -3269,13 +3273,11 @@ void Focus::showFITSViewer()
                 KStars::Instance()->addFITSViewer(fv);
             }
 
-            fv->addFITS(url);
-            FITSView *currentView = fv->getCurrentView();
-            if (currentView)
-                currentView->getImageData()->setAutoRemoveTemporaryFITS(false);
+
+            fv->loadData(m_ImageData, QUrl(), &lastFVTabID);
         }
-        else
-            fv->updateFITS(url, 0);
+        else if (fv->updateData(m_ImageData, QUrl(), lastFVTabID, &lastFVTabID) == false)
+            fv->loadData(m_ImageData, QUrl(), &lastFVTabID);
 
         fv->show();
     }
diff --git a/kstars/ekos/focus/focus.h b/kstars/ekos/focus/focus.h
index 18e74bddb..a4ee56df4 100644
--- a/kstars/ekos/focus/focus.h
+++ b/kstars/ekos/focus/focus.h
@@ -312,7 +312,7 @@ class Focus : public QWidget, public Ui::Focus
              * @brief newFITS A new FITS blob is received by the CCD driver.
              * @param bp pointer to blob data
              */
-        void newFITS(IBLOB *bp);
+        void processData(const QSharedPointer<FITSData> &data);
 
         /**
              * @brief processFocusNumber Read focus number properties of interest as they arrive from the focuser driver and process them accordingly.
@@ -710,6 +710,9 @@ class Focus : public QWidget, public Ui::Focus
         // Filter Manager
         QSharedPointer<FilterManager> filterManager;
 
+        // Data
+        QSharedPointer<FITSData> m_ImageData;
+
         // Linear focuser.
         std::unique_ptr<FocusAlgorithmInterface> linearFocuser;
         int focuserAdditionalMovement { 0 };
diff --git a/kstars/ekos/guide/externalguide/phd2.cpp b/kstars/ekos/guide/externalguide/phd2.cpp
index 4426d6733..9a41df22d 100644
--- a/kstars/ekos/guide/externalguide/phd2.cpp
+++ b/kstars/ekos/guide/externalguide/phd2.cpp
@@ -1044,10 +1044,11 @@ void PHD2::processStarImage(const QJsonObject &jsonStarFrame)
 
     //This loads the FITS file in the Guide FITSView
     //Then it updates the Summary Screen
-    FITSData* fdata = new FITSData();
-    fdata->loadFITSFromMemory("guideframe.fits", fits_buffer, fits_buffer_size, true);
+    QSharedPointer<FITSData> fdata;
+    fdata.reset(new FITSData());
+    fdata->loadFromBuffer(fits_buffer, fits_buffer_size, "fits", true);
     free(fits_buffer);
-    guideFrame->loadFITSFromData(fdata);
+    guideFrame->loadData(fdata);
 
     guideFrame->updateFrame();
     guideFrame->setTrackingBox(QRect(0, 0, width, height));
diff --git a/kstars/ekos/guide/guide.cpp b/kstars/ekos/guide/guide.cpp
index ed76e639c..ca9ac7385 100644
--- a/kstars/ekos/guide/guide.cpp
+++ b/kstars/ekos/guide/guide.cpp
@@ -1223,7 +1223,7 @@ bool Guide::captureOneFrame()
 
     currentCCD->setTransformFormat(ISD::CCD::FORMAT_FITS);
 
-    connect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS, Qt::UniqueConnection);
+    connect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Guide::processData, Qt::UniqueConnection);
     qCDebug(KSTARS_EKOS_GUIDE) << "Capturing frame...";
 
     double finalExposure = seqExpose;
@@ -1401,22 +1401,34 @@ void Guide::reconnectDriver(const QString &camera, const QString &via)
     });
 }
 
-void Guide::newFITS(IBLOB *bp)
+void Guide::processData(const QSharedPointer<FITSData> &data)
 {
-    INDI_UNUSED(bp);
-
     ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
     if (targetChip->getCaptureMode() != FITS_GUIDE)
     {
-        qCWarning(KSTARS_EKOS_GUIDE) << "Ignoring Received FITS" << bp->name << "as it has the wrong capture mode" <<
-                                     targetChip->getCaptureMode();
+        if (data)
+        {
+            QString blobInfo = QString("{Device: %1 Property: %2 Element: %3 Chip: %4}").arg(data->property("device").toString())
+                               .arg(data->property("blobVector").toString())
+                               .arg(data->property("blobElement").toString())
+                               .arg(data->property("chip").toInt());
+
+            qCWarning(KSTARS_EKOS_GUIDE) << blobInfo << "Ignoring Received FITS as it has the wrong capture mode" <<
+                                         targetChip->getCaptureMode();
+        }
+
         return;
     }
 
+    if (data)
+        m_ImageData = data;
+    else
+        m_ImageData.reset();
+
     captureTimeout.stop();
     m_CaptureTimeoutCounter = 0;
 
-    disconnect(currentCCD, &ISD::CCD::BLOBUpdated, this, &Ekos::Guide::newFITS);
+    disconnect(currentCCD, &ISD::CCD::newImage, this, &Ekos::Guide::processData);
 
     qCDebug(KSTARS_EKOS_GUIDE) << "Received guide frame.";
 
@@ -3243,11 +3255,9 @@ void Guide::processGuideOptions()
 
 void Guide::showFITSViewer()
 {
-    FITSData *data = guideView->getImageData();
-    if (data)
+    static int lastFVTabID = -1;
+    if (m_ImageData)
     {
-        QUrl url = QUrl::fromLocalFile(data->filename());
-
         if (fv.isNull())
         {
             if (Options::singleWindowCapturedFITS())
@@ -3258,13 +3268,11 @@ void Guide::showFITSViewer()
                 KStars::Instance()->addFITSViewer(fv);
             }
 
-            fv->addFITS(url);
-            FITSView *currentView = fv->getCurrentView();
-            if (currentView)
-                currentView->getImageData()->setAutoRemoveTemporaryFITS(false);
+
+            fv->loadData(m_ImageData, QUrl(), &lastFVTabID);
         }
-        else
-            fv->updateFITS(url, 0);
+        else if (fv->updateData(m_ImageData, QUrl(), lastFVTabID, &lastFVTabID) == false)
+            fv->loadData(m_ImageData, QUrl(), &lastFVTabID);
 
         fv->show();
     }
diff --git a/kstars/ekos/guide/guide.h b/kstars/ekos/guide/guide.h
index a305d4ec4..788d5b83b 100644
--- a/kstars/ekos/guide/guide.h
+++ b/kstars/ekos/guide/guide.h
@@ -346,7 +346,7 @@ class Guide : public QWidget, public Ui::Guide
         /**
              * @brief newFITS is called by the INDI framework whenever there is a new BLOB arriving
              */
-        void newFITS(IBLOB *);
+        void processData(const QSharedPointer<FITSData> &data);
 
         /**
              * @brief setST4 Sets a new ST4 device from the combobox index
@@ -670,6 +670,7 @@ class Guide : public QWidget, public Ui::Guide
         QPointer<PHD2> phd2Guider;
         QPointer<LinGuider> linGuider;
         QPointer<FITSViewer> fv;
+        QSharedPointer<FITSData> m_ImageData;
 
         double primaryFL = -1, primaryAperture = -1, guideFL = -1, guideAperture = -1;
         ISD::Telescope::Status m_MountStatus { ISD::Telescope::MOUNT_IDLE };
diff --git a/kstars/ekos/manager.cpp b/kstars/ekos/manager.cpp
index f2f15234b..9fa6a9c53 100644
--- a/kstars/ekos/manager.cpp
+++ b/kstars/ekos/manager.cpp
@@ -165,8 +165,8 @@ Manager::Manager(QWidget * parent) : QDialog(parent)
             &EkosLive::Message::setBoundingRect);
     connect(ekosLiveClient.get()->message(), &EkosLive::Message::resetPolarView, ekosLiveClient.get()->media(),
             &EkosLive::Media::resetPolarView);
-    connect(ekosLiveClient.get()->message(), &EkosLive::Message::previewJPEGGenerated, ekosLiveClient.get()->media(),
-            &EkosLive::Media::sendPreviewJPEG);
+    //    connect(ekosLiveClient.get()->message(), &EkosLive::Message::previewJPEGGenerated, ekosLiveClient.get()->media(),
+    //            &EkosLive::Media::sendPreviewJPEG);
     connect(KSMessageBox::Instance(), &KSMessageBox::newMessage, ekosLiveClient.get()->message(),
             &EkosLive::Message::sendDialog);
 
@@ -2259,19 +2259,19 @@ void Manager::initCapture()
     connect(captureProcess.get(), &Ekos::Capture::newStatus, this, &Ekos::Manager::updateCaptureStatus);
     connect(captureProcess.get(), &Ekos::Capture::newImage, this, &Ekos::Manager::updateCaptureProgress);
     connect(captureProcess.get(), &Ekos::Capture::driverTimedout, this, &Ekos::Manager::restartDriver);
-    connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString & filename, const QString & previewFITS)
-    {
-        if (Options::useSummaryPreview() && QFile::exists(filename))
-        {
-            if (Options::autoImageToFITS())
-            {
-                if (previewFITS.isEmpty() == false)
-                    summaryPreview->loadFITS(previewFITS);
-            }
-            else
-                summaryPreview->loadFITS(filename);
-        }
-    });
+    //    connect(captureProcess.get(), &Ekos::Capture::newSequenceImage, [&](const QString & filename, const QString & previewFITS)
+    //    {
+    //        if (Options::useSummaryPreview() && QFile::exists(filename))
+    //        {
+    //            if (Options::autoImageToFITS())
+    //            {
+    //                if (previewFITS.isEmpty() == false)
+    //                    summaryPreview->loadFile(previewFITS);
+    //            }
+    //            else
+    //                summaryPreview->loadFile(filename);
+    //        }
+    //    });
     connect(captureProcess.get(), &Ekos::Capture::newDownloadProgress, this, &Ekos::Manager::updateDownloadProgress);
     connect(captureProcess.get(), &Ekos::Capture::newExposureProgress, this, &Ekos::Manager::updateExposureProgress);
     captureGroup->setEnabled(true);
@@ -3028,7 +3028,7 @@ void Manager::updateCaptureStatus(Ekos::CaptureState status)
     ekosLiveClient.get()->message()->updateCaptureStatus(cStatus);
 }
 
-void Manager::updateCaptureProgress(Ekos::SequenceJob * job)
+void Manager::updateCaptureProgress(Ekos::SequenceJob * job, const QSharedPointer<FITSData> &data)
 {
     // Image is set to nullptr only on initial capture start up
     int completed = job->getCompleted();
@@ -3058,14 +3058,21 @@ void Manager::updateCaptureProgress(Ekos::SequenceJob * job)
     ekosLiveClient.get()->message()->updateCaptureStatus(status);
 
     const QString filename = job->property("filename").toString();
-    if (!filename.isEmpty() && job->getStatus() == SequenceJob::JOB_BUSY)
+    //if (!filename.isEmpty() && job->getStatus() == SequenceJob::JOB_BUSY)
+    if (job->getStatus() == SequenceJob::JOB_BUSY)
     {
         QString uuid = QUuid::createUuid().toString();
         uuid = uuid.remove(QRegularExpression("[-{}]"));
+
+
+        // FIXME
+        // Need to set data only, no need to send filename
         ekosLiveClient.get()->media()->sendPreviewImage(filename, uuid);
         if (job->isPreview() == false)
             ekosLiveClient.get()->cloud()->sendPreviewImage(filename, uuid);
 
+        if (Options::useSummaryPreview())
+            summaryPreview->loadData(data);
     }
 }
 
diff --git a/kstars/ekos/manager.h b/kstars/ekos/manager.h
index 43ea52974..b1f6ab087 100644
--- a/kstars/ekos/manager.h
+++ b/kstars/ekos/manager.h
@@ -414,7 +414,7 @@ class Manager : public QDialog, public Ui::Manager
 
         // Capture Summary
         void updateCaptureStatus(CaptureState status);
-        void updateCaptureProgress(SequenceJob *job);
+        void updateCaptureProgress(SequenceJob *job, const QSharedPointer<FITSData> &data);
         void updateDownloadProgress(double timeLeft);
         void updateExposureProgress(SequenceJob *job);
         void updateCaptureCountDown();
diff --git a/kstars/ekos/opsekos.cpp b/kstars/ekos/opsekos.cpp
index 848cbb63b..4a8613ab7 100644
--- a/kstars/ekos/opsekos.cpp
+++ b/kstars/ekos/opsekos.cpp
@@ -178,7 +178,7 @@ void OpsEkos::loadDarkFITS(QModelIndex index)
     if (filename.isEmpty() == false)
     {
         QUrl url = QUrl::fromLocalFile(filename);
-        KStars::Instance()->genericFITSViewer()->addFITS(url);
+        KStars::Instance()->genericFITSViewer()->loadFile(url);
         KStars::Instance()->genericFITSViewer()->show();
     }
 }
diff --git a/kstars/fitsviewer/fitsdata.cpp b/kstars/fitsviewer/fitsdata.cpp
index 40e1fc761..e7301d18e 100644
--- a/kstars/fitsviewer/fitsdata.cpp
+++ b/kstars/fitsviewer/fitsdata.cpp
@@ -48,6 +48,10 @@
 #include "fitshistogram.h"
 #endif
 
+#if !defined(KSTARS_LITE) && defined(HAVE_LIBRAW)
+#include <libraw/libraw.h>
+#endif
+
 #include <cfloat>
 #include <cmath>
 
@@ -60,7 +64,7 @@
 #define ZOOM_HIGH_INCR 50
 
 const QString FITSData::m_TemporaryPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
-
+const QStringList RAWFormats = { "cr2", "cr3", "crw", "nef", "raf", "dng", "arw" };
 
 
 FITSData::FITSData(FITSMode fitsMode): m_Mode(fitsMode)
@@ -143,20 +147,20 @@ void FITSData::loadCommon(const QString &inFilename)
     m_Filename = inFilename;
 }
 
-bool FITSData::loadFITSFromMemory(const QString &inFilename, void *fits_buffer,
-                                  size_t fits_buffer_size, bool silent)
+bool FITSData::loadFromBuffer(void *buffer, size_t size, const QString &extension, bool silent)
 {
-    loadCommon(inFilename);
-    qCDebug(KSTARS_FITS) << "Reading FITS file buffer (" << KFormat().formatByteSize(fits_buffer_size) << ")";
-    return privateLoadFITS(fits_buffer, fits_buffer_size, silent);
+    loadCommon(QString());
+    qCDebug(KSTARS_FITS) << "Reading file buffer (" << KFormat().formatByteSize(size) << ")";
+    return privateLoad(buffer, size, extension, silent);
 }
 
-QFuture<bool> FITSData::loadFITS(const QString &inFilename, bool silent)
+QFuture<bool> FITSData::loadFromFile(const QString &inFilename, bool silent)
 {
     loadCommon(inFilename);
-    qCInfo(KSTARS_FITS) << "Loading FITS file " << m_Filename;
-    QFuture<bool> result = QtConcurrent::run(
-                               this, &FITSData::privateLoadFITS, nullptr, 0, silent);
+    QFileInfo info(m_Filename);
+    QString extension = info.completeSuffix().toLower();
+    qCInfo(KSTARS_FITS) << "Loading file " << m_Filename;
+    QFuture<bool> result = QtConcurrent::run(this, &FITSData::privateLoad, nullptr, 0, extension, silent);
 
     return result;
 }
@@ -178,15 +182,27 @@ bool fitsOpenError(int status, const QString &message, bool silent)
 }
 }
 
-bool FITSData::privateLoadFITS(void *fits_buffer, size_t fits_buffer_size, bool silent)
+bool FITSData::privateLoad(void *buffer, size_t size, const QString &extension, bool silent)
+{
+    m_isTemporary = m_Filename.startsWith(m_TemporaryPath);
+
+    if (extension.contains("fit"))
+        return loadFITSImage(buffer, size, extension, silent);
+    if (QImageReader::supportedImageFormats().contains(extension.toLatin1()))
+        return loadCanonicalImage(buffer, size, extension, silent);
+    else if (RAWFormats.contains(extension))
+        return loadRAWImage(buffer, size, extension, silent);
+
+    return false;
+}
+
+bool FITSData::loadFITSImage(void *buffer, size_t size, const QString &extension, bool silent)
 {
     int status = 0, anynull = 0;
     long naxes[3];
     QString errMessage;
 
-    m_isTemporary = m_Filename.startsWith(m_TemporaryPath);
-
-    if (fits_buffer == nullptr && m_Filename.endsWith(".fz"))
+    if (buffer == nullptr && extension.contains(".fz"))
     {
         // Store so we don't lose.
         m_compressedFilename = m_Filename;
@@ -213,14 +229,12 @@ bool FITSData::privateLoadFITS(void *fits_buffer, size_t fits_buffer_size, bool
         m_isCompressed = true;
     }
 
-    if (fits_buffer == nullptr)
+    if (buffer == nullptr)
     {
         // Use open diskfile as it does not use extended file names which has problems opening
         // files with [ ] or ( ) in their names.
         if (fits_open_diskfile(&fptr, m_Filename.toLatin1(), READONLY, &status))
         {
-            if(privateLoadImage())
-                return true;
             return fitsOpenError(status, i18n("Error opening fits file %1", m_Filename), silent);
         }
         else
@@ -229,13 +243,13 @@ bool FITSData::privateLoadFITS(void *fits_buffer, size_t fits_buffer_size, bool
     else
     {
         // Read the FITS file from a memory buffer.
-        void *temp_buffer = fits_buffer;
-        size_t temp_size = fits_buffer_size;
+        void *temp_buffer = buffer;
+        size_t temp_size = size;
         if (fits_open_memfile(&fptr, m_Filename.toLatin1().data(), READONLY,
                               &temp_buffer, &temp_size, 0, nullptr, &status))
             return fitsOpenError(status, i18n("Error reading fits buffer."), silent);
         else
-            stats.size = fits_buffer_size;
+            stats.size = size;
     }
 
     if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
@@ -367,20 +381,21 @@ bool FITSData::privateLoadFITS(void *fits_buffer, size_t fits_buffer_size, bool
     return true;
 }
 
-bool FITSData::privateLoadImage()
+bool FITSData::loadCanonicalImage(void *buffer, size_t size, const QString &extension, bool silent)
 {
-    QImageReader fileReader(m_Filename.toLatin1());
-
-    if (QImageReader::supportedImageFormats().contains(fileReader.format()) == false)
-    {
-        qCCritical(KSTARS_FITS) << "Failed to convert" << m_Filename << "to FITS since format, " << fileReader.format() <<
-                                ", is not supported in Qt";
-        return false;
-    }
-
+    // TODO need to add error popups as well later on
+    Q_UNUSED(silent);
     QString errMessage;
     QImage imageFromFile;
-    if(!imageFromFile.load(m_Filename.toLatin1()))
+    if (buffer != nullptr)
+    {
+        if(!imageFromFile.loadFromData(reinterpret_cast<uint8_t*>(buffer), size, extension.toLatin1().constData()))
+        {
+            qCCritical(KSTARS_FITS) << "Failed to open image.";
+            return false;
+        }
+    }
+    else if(!imageFromFile.load(m_Filename.toLatin1()))
     {
         qCCritical(KSTARS_FITS) << "Failed to open image.";
         return false;
@@ -470,6 +485,114 @@ bool FITSData::privateLoadImage()
     return true;
 }
 
+bool FITSData::loadRAWImage(void *buffer, size_t size, const QString &extension, bool silent)
+{
+    // TODO need to add error popups as well later on
+    Q_UNUSED(silent);
+
+#if !defined(KSTARS_LITE) && !defined(HAVE_LIBRAW)
+    lastError = i18n("Unable to find dcraw and cjpeg. Please install the required tools to convert CR2/NEF to JPEG.");
+    return false;
+#else
+
+    int ret = 0;
+    // Creation of image processing object
+    LibRaw RawProcessor;
+
+    // Let us open the file/buffer
+    if (buffer == nullptr)
+    {
+        // Open file
+        if ((ret = RawProcessor.open_file(m_Filename.toLatin1().constData())) != LIBRAW_SUCCESS)
+        {
+            lastError = i18n("Cannot open file %1: %2", m_Filename, libraw_strerror(ret));
+            RawProcessor.recycle();
+            return false;
+        }
+    }
+    // Open Buffer
+    else if ((ret = RawProcessor.open_buffer(buffer, size)) != LIBRAW_SUCCESS)
+    {
+        lastError = i18n("Cannot open buffer: %1", libraw_strerror(ret));
+        RawProcessor.recycle();
+        return false;
+    }
+
+    // Let us unpack the thumbnail
+    if ((ret = RawProcessor.unpack()) != LIBRAW_SUCCESS)
+    {
+        lastError = i18n("Cannot unpack_thumb: %1", libraw_strerror(ret));
+        RawProcessor.recycle();
+        return false;
+    }
+
+    if ((ret = RawProcessor.dcraw_process()) != LIBRAW_SUCCESS)
+    {
+        lastError = i18n("Cannot dcraw_process: %1", libraw_strerror(ret));
+        RawProcessor.recycle();
+        return false;
+    }
+
+    libraw_processed_image_t *image = RawProcessor.dcraw_make_mem_image(&ret);
+    if (ret != LIBRAW_SUCCESS)
+    {
+        lastError = i18n("Cannot load to memory: %1", libraw_strerror(ret));
+        RawProcessor.recycle();
+        return false;
+    }
+
+    RawProcessor.recycle();
+
+    stats.bytesPerPixel = image->bits / 8;
+    // We only support two types now
+    if (stats.bytesPerPixel == 1)
+        stats.dataType = TBYTE;
+    else
+        stats.dataType = TUSHORT;
+    stats.width = image->width;
+    stats.height = image->height;
+    m_Channels = image->colors;
+    stats.samples_per_channel = stats.width * stats.height;
+    clearImageBuffers();
+    m_ImageBufferSize = stats.samples_per_channel * m_Channels * stats.bytesPerPixel;
+    m_ImageBuffer = new uint8_t[m_ImageBufferSize];
+    if (m_ImageBuffer == nullptr)
+    {
+        qCCritical(KSTARS_FITS) << QString("FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ").arg(
+                                    m_ImageBufferSize);
+        libraw_dcraw_clear_mem(image);
+        clearImageBuffers();
+        return false;
+    }
+
+    auto destination_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer);
+    auto source_buffer = reinterpret_cast<uint8_t *>(image->data);
+
+    // For mono, we memcpy directly
+    if (image->colors == 1)
+    {
+        memcpy(destination_buffer, source_buffer, m_ImageBufferSize);
+    }
+    else
+    {
+        // Data in RGB24, with bytes in the order of R,G,B. We copy them copy them into 3 layers for FITS
+        uint8_t * rBuff = destination_buffer;
+        uint8_t * gBuff = destination_buffer + (stats.width * stats.height);
+        uint8_t * bBuff = destination_buffer + (stats.width * stats.height * 2);
+
+        int imax = stats.samples_per_channel * 3 - 3;
+        for (int i = 0; i <= imax; i += 3)
+        {
+            *rBuff++ = source_buffer[i + 0];
+            *gBuff++ = source_buffer[i + 1];
+            *bBuff++ = source_buffer[i + 2];
+        }
+    }
+    libraw_dcraw_clear_mem(image);
+    return true;
+#endif
+}
+
 bool FITSData::saveImage(const QString &newFilename)
 {
     if (newFilename == m_Filename)
@@ -3191,7 +3314,7 @@ QImage FITSData::FITSToImage(const QString &filename)
 
     FITSData data;
 
-    QFuture<bool> future = data.loadFITS(filename);
+    QFuture<bool> future = data.loadFromFile(filename);
 
     // Wait synchronously
     future.waitForFinished();
diff --git a/kstars/fitsviewer/fitsdata.h b/kstars/fitsviewer/fitsdata.h
index 7130f41a5..a79bf0400 100644
--- a/kstars/fitsviewer/fitsdata.h
+++ b/kstars/fitsviewer/fitsdata.h
@@ -103,17 +103,17 @@ class FITSData : public QObject
          * @param silent If set, error messages are ignored. If set to false, the error message will get displayed in a popup.
          * @return A QFuture that can be watched until the async operation is complete.
          */
-        QFuture<bool> loadFITS(const QString &inFilename, bool silent = true);
+        QFuture<bool> loadFromFile(const QString &inFilename, bool silent = true);
 
         /**
          * @brief loadFITSFromMemory Loading FITS from memory buffer.
-         * @param inFilename Potential future path to FITS file (or compressed fits.gz), stored in a fitsdata class variable
          * @param fits_buffer The memory buffer containing the fits data.
          * @param fits_buffer_size The size in bytes of the buffer.
+         * @param extension file extension (e.g. "jpg", "fits", "cr2"...etc)
          * @param silent If set, error messages are ignored. If set to false, the error message will get displayed in a popup.
          * @return bool indicating success or failure.
          */
-        bool loadFITSFromMemory(const QString &inFilename, void *fits_buffer, size_t fits_buffer_size, bool silent);
+        bool loadFromBuffer(void *buffer, size_t size, const QString &extension, bool silent);
 
         /**
          * @brief parseSolution Parse the WCS solution information from the header into the given struct.
@@ -443,19 +443,21 @@ class FITSData : public QObject
     private:
         void loadCommon(const QString &inFilename);
         /**
-         * @brief privateLoadFITS Load a FITS Image buffer onto memory.
-         * @param fits_buffer pointer to FITS buffer
-         * @param fits_buffer_size size of FITS buffer
+         * @brief privateLoad Load an image (FITS, RAW, or images supported by Qt like jpeg, png).
+         * @param Buffer pointer to image data. If buffer is nullptr, read from disk (m_Filename).
+         * @param size size of image data in bytes.
          * @param silent If true, suppress any messages.
          * @return true if successfully loaded, false otherwise.
          */
-        bool privateLoadFITS(void *fits_buffer, size_t fits_buffer_size, bool silent);
+        bool privateLoad(void *buffer, size_t size, const QString &extension, bool silent);
+
+        // Load Qt-supported images.
+        bool loadCanonicalImage(void *buffer, size_t size, const QString &extension, bool silent);
+        // Load FITS images.
+        bool loadFITSImage(void *buffer, size_t size, const QString &extension, bool silent);
+        // Load RAW images.
+        bool loadRAWImage(void *buffer, size_t size, const QString &extension, bool silent);
 
-        /**
-         * @brief privateLoadImage Load any image format supported by Qt
-         * @return  true if successfully loaded, false otherwise.
-         */
-        bool privateLoadImage();
         void rotWCSFITS(int angle, int mirror);
         int calculateMinMax(bool refresh = false);
         bool checkDebayer();
diff --git a/kstars/fitsviewer/fitstab.cpp b/kstars/fitsviewer/fitstab.cpp
index 0255d53a5..bb851a702 100644
--- a/kstars/fitsviewer/fitstab.cpp
+++ b/kstars/fitsviewer/fitstab.cpp
@@ -98,7 +98,7 @@ void FITSTab::setPreviewText(const QString &value)
 
 void FITSTab::selectRecentFITS(int i)
 {
-    loadFITS(QUrl::fromLocalFile(recentImages->item(i)->text()));
+    loadFile(QUrl::fromLocalFile(recentImages->item(i)->text()));
 }
 
 void FITSTab::clearRecentFITS()
@@ -423,7 +423,7 @@ bool FITSTab::setupView(FITSMode mode, FITSScale filter)
     return false;
 }
 
-void FITSTab::loadFITS(const QUrl &imageURL, FITSMode mode, FITSScale filter, bool silent)
+void FITSTab::loadFile(const QUrl &imageURL, FITSMode mode, FITSScale filter, bool silent)
 {
     if (setupView(mode, filter))
     {
@@ -440,7 +440,7 @@ void FITSTab::loadFITS(const QUrl &imageURL, FITSMode mode, FITSScale filter, bo
 
     view->setFilter(filter);
 
-    view->loadFITS(imageURL.toLocalFile(), silent);
+    view->loadFile(imageURL.toLocalFile(), silent);
 }
 
 bool FITSTab::shouldComputeHFR() const
@@ -498,16 +498,16 @@ void FITSTab::processData()
     view->updateFrame();
 }
 
-bool FITSTab::loadFITSFromData(FITSData* data, const QUrl &imageURL, FITSMode mode, FITSScale filter)
+bool FITSTab::loadData(const QSharedPointer<FITSData> &data, FITSMode mode, FITSScale filter)
 {
     setupView(mode, filter);
 
-    currentURL = imageURL;
+    // Empty URL
+    currentURL = QUrl();
 
     view->setFilter(filter);
 
-    //if (!view->loadFITSFromData(data, imageURL.toLocalFile()))
-    if (!view->loadFITSFromData(data))
+    if (!view->loadData(data))
     {
         // On Failure to load
         // connect(view.get(), &FITSView::failed, this, &FITSTab::failed);
diff --git a/kstars/fitsviewer/fitstab.h b/kstars/fitsviewer/fitstab.h
index 594ca9470..f6ba9edc0 100644
--- a/kstars/fitsviewer/fitstab.h
+++ b/kstars/fitsviewer/fitstab.h
@@ -67,11 +67,8 @@ class FITSTab : public QWidget
 
         void clearRecentFITS();
         void selectRecentFITS(int i);
-        void loadFITS(const QUrl &imageURL, FITSMode mode = FITS_NORMAL,
-                      FITSScale filter = FITS_NONE, bool silent = true);
-
-        bool loadFITSFromData(FITSData *data, const QUrl &imageURL, FITSMode mode = FITS_NORMAL,
-                              FITSScale filter = FITS_NONE);
+        void loadFile(const QUrl &imageURL, FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE, bool silent = true);
+        bool loadData(const QSharedPointer<FITSData> &data, FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE);
 
         bool saveImage(const QString &filename);
 
diff --git a/kstars/fitsviewer/fitsview.cpp b/kstars/fitsviewer/fitsview.cpp
index 66b0d8437..cd40f09b6 100644
--- a/kstars/fitsviewer/fitsview.cpp
+++ b/kstars/fitsviewer/fitsview.cpp
@@ -80,13 +80,13 @@ void ComputeGBStretchParams(const StretchParams &newParams, StretchParams* param
 // We call stretch even if we're not stretching, as the stretch code still
 // converts the image to the uint8 output image which will be displayed.
 // In that case, it will use an identity stretch.
-void FITSView::doStretch(FITSData *data, QImage *outputImage)
+void FITSView::doStretch(QImage *outputImage)
 {
-    if (outputImage->isNull())
+    if (outputImage->isNull() || imageData.isNull())
         return;
-    Stretch stretch(static_cast<int>(data->width()),
-                    static_cast<int>(data->height()),
-                    data->channels(), data->getStatistics().dataType);
+    Stretch stretch(static_cast<int>(imageData->width()),
+                    static_cast<int>(imageData->height()),
+                    imageData->channels(), imageData->getStatistics().dataType);
 
     StretchParams tempParams;
     if (!stretchImage)
@@ -94,7 +94,7 @@ void FITSView::doStretch(FITSData *data, QImage *outputImage)
     else if (autoStretch)
     {
         // Compute new auto-stretch params.
-        stretchParams = stretch.computeParams(data->getImageBuffer());
+        stretchParams = stretch.computeParams(imageData->getImageBuffer());
         tempParams = stretchParams;
     }
     else
@@ -102,7 +102,7 @@ void FITSView::doStretch(FITSData *data, QImage *outputImage)
         tempParams = stretchParams;
 
     stretch.setParams(tempParams);
-    stretch.run(data->getImageBuffer(), outputImage, sampling);
+    stretch.run(imageData->getImageBuffer(), outputImage, sampling);
 }
 
 // Store stretch parameters, and turn on stretching if it isn't already on.
@@ -220,7 +220,6 @@ FITSView::~FITSView()
 {
     fitsWatcher.waitForFinished();
     wcsWatcher.waitForFinished();
-    delete (imageData);
 }
 
 /**
@@ -272,7 +271,7 @@ void FITSView::setCursorMode(CursorMode mode)
     {
         if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning())
         {
-            QFuture<bool> future = QtConcurrent::run(imageData, &FITSData::loadWCS);
+            QFuture<bool> future = QtConcurrent::run(imageData.get(), &FITSData::loadWCS);
             wcsWatcher.setFuture(future);
         }
     }
@@ -291,7 +290,7 @@ void FITSView::resizeEvent(QResizeEvent * event)
 }
 
 
-void FITSView::loadFITS(const QString &inFilename, bool silent)
+void FITSView::loadFile(const QString &inFilename, bool silent)
 {
     if (floatingToolBar != nullptr)
     {
@@ -312,23 +311,23 @@ void FITSView::loadFITS(const QString &inFilename, bool silent)
     // In case loadWCS is still running for previous image data, let's wait until it's over
     wcsWatcher.waitForFinished();
 
-    delete imageData;
-    imageData = nullptr;
+    //    delete imageData;
+    //    imageData = nullptr;
 
     filterStack.clear();
     filterStack.push(FITS_NONE);
     if (filter != FITS_NONE)
         filterStack.push(filter);
 
-    imageData = new FITSData(mode);
+    imageData.reset(new FITSData(mode));
 
     if (setBayerParams)
         imageData->setBayerParams(&param);
 
-    fitsWatcher.setFuture(imageData->loadFITS(inFilename, silent));
+    fitsWatcher.setFuture(imageData->loadFromFile(inFilename, silent));
 }
 
-bool FITSView::loadFITSFromData(FITSData *data)
+bool FITSView::loadData(const QSharedPointer<FITSData> &data)
 {
     if (floatingToolBar != nullptr)
     {
@@ -338,11 +337,11 @@ bool FITSView::loadFITSFromData(FITSData *data)
     // In case loadWCS is still running for previous image data, let's wait until it's over
     wcsWatcher.waitForFinished();
 
-    if (imageData != nullptr)
-    {
-        delete imageData;
-        imageData = nullptr;
-    }
+    //    if (imageData != nullptr)
+    //    {
+    //        delete imageData;
+    //        imageData = nullptr;
+    //    }
 
     filterStack.clear();
     filterStack.push(FITS_NONE);
@@ -358,7 +357,8 @@ bool FITSView::loadFITSFromData(FITSData *data)
 bool FITSView::processData()
 {
     // Set current width and height
-    if (!imageData) return false;
+    if (!imageData)
+        return false;
     currentWidth = imageData->width();
     currentHeight = imageData->height();
 
@@ -400,7 +400,7 @@ bool FITSView::processData()
     // Load WCS data now if selected and image contains valid WCS header
     if (imageData->hasWCS() && Options::autoWCS() && (mode == FITS_NORMAL || mode == FITS_ALIGN) && !wcsWatcher.isRunning())
     {
-        QFuture<bool> future = QtConcurrent::run(imageData, &FITSData::loadWCS);
+        QFuture<bool> future = QtConcurrent::run(imageData.get(), &FITSData::loadWCS);
         wcsWatcher.setFuture(future);
     }
     else
@@ -533,7 +533,8 @@ bool FITSView::rescale(FITSZoom type)
     //    if (rawImage.isNull())
     //        return false;
 
-    if (!imageData) return false;
+    if (!imageData)
+        return false;
     int image_width  = imageData->width();
     int image_height = imageData->height();
     currentWidth  = image_width;
@@ -590,7 +591,7 @@ bool FITSView::rescale(FITSZoom type)
 
     initDisplayImage();
     image_frame->setScaledContents(true);
-    doStretch(imageData, &rawImage);
+    doStretch(&rawImage);
     setWidget(image_frame.get());
 
     // This is needed by fitstab, even if the zoom doesn't change, to change the stretch UI.
@@ -1442,7 +1443,7 @@ void FITSView::toggleEQGrid()
 
     if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning())
     {
-        QFuture<bool> future = QtConcurrent::run(imageData, &FITSData::loadWCS);
+        QFuture<bool> future = QtConcurrent::run(imageData.get(), &FITSData::loadWCS);
         wcsWatcher.setFuture(future);
         return;
     }
@@ -1457,7 +1458,7 @@ void FITSView::toggleObjects()
 
     if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning())
     {
-        QFuture<bool> future = QtConcurrent::run(imageData, &FITSData::loadWCS);
+        QFuture<bool> future = QtConcurrent::run(imageData.get(), &FITSData::loadWCS);
         wcsWatcher.setFuture(future);
         return;
     }
diff --git a/kstars/fitsviewer/fitsview.h b/kstars/fitsviewer/fitsview.h
index 7060bb65b..224c61ceb 100644
--- a/kstars/fitsviewer/fitsview.h
+++ b/kstars/fitsviewer/fitsview.h
@@ -62,13 +62,13 @@ class FITSView : public QScrollArea
          * @note If image is successfully, loaded() signal is emitted, otherwise failed() signal is emitted.
          * Obtain error by calling lastError()
          */
-        void loadFITS(const QString &inFilename, bool silent = true);
+        void loadFile(const QString &inFilename, bool silent = true);
 
         /**
          * @brief loadFITSFromData Takes ownership of the FITSData instance passed in and displays it in a FITSView frame
          * @param data pointer to FITSData objects
          */
-        bool loadFITSFromData(FITSData *data);
+        bool loadData(const QSharedPointer<FITSData> &data);
 
         // Save FITS
         bool saveImage(const QString &newFilename);
@@ -78,7 +78,7 @@ class FITSView : public QScrollArea
         // Access functions
         FITSData *getImageData() const
         {
-            return imageData;
+            return imageData.get();
         }
         double getCurrentZoom() const
         {
@@ -295,7 +295,7 @@ class FITSView : public QScrollArea
         /// Cross hair
         QPointF markerCrosshair;
         /// Pointer to FITSData object
-        FITSData *imageData { nullptr };
+        QSharedPointer<FITSData> imageData;
         /// Current zoom level
         double currentZoom { 0 };
         // The maximum percent zoom. The value is recalculated in the constructor
@@ -304,7 +304,7 @@ class FITSView : public QScrollArea
 
     private:
         bool processData();
-        void doStretch(FITSData *data, QImage *outputImage);
+        void doStretch(QImage *outputImage);
         double scaleSize(double size);
         bool isLargeImage();
         void updateFrameLargeImage();
diff --git a/kstars/fitsviewer/fitsviewer.cpp b/kstars/fitsviewer/fitsviewer.cpp
index a0f974e45..9b06b47f8 100644
--- a/kstars/fitsviewer/fitsviewer.cpp
+++ b/kstars/fitsviewer/fitsviewer.cpp
@@ -48,9 +48,9 @@
 
 QStringList FITSViewer::filterTypes =
     QStringList() << I18N_NOOP("Auto Stretch") << I18N_NOOP("High Contrast") << I18N_NOOP("Equalize")
-                  << I18N_NOOP("High Pass") << I18N_NOOP("Median") << I18N_NOOP("Gaussian blur")
-                  << I18N_NOOP("Rotate Right") << I18N_NOOP("Rotate Left") << I18N_NOOP("Flip Horizontal")
-                  << I18N_NOOP("Flip Vertical");
+    << I18N_NOOP("High Pass") << I18N_NOOP("Median") << I18N_NOOP("Gaussian blur")
+    << I18N_NOOP("Rotate Right") << I18N_NOOP("Rotate Left") << I18N_NOOP("Flip Horizontal")
+    << I18N_NOOP("Flip Vertical");
 
 FITSViewer::FITSViewer(QWidget *parent) : KXmlGuiWindow(parent)
 {
@@ -79,8 +79,8 @@ FITSViewer::FITSViewer(QWidget *parent) : KXmlGuiWindow(parent)
     connect(fitsTabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabFocusUpdated(int)));
     connect(fitsTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
 
-//These two connections will enable or disable the scope button if a scope is available or not.
-//Of course this is also dependent on the presence of WCS data in the image.
+    //These two connections will enable or disable the scope button if a scope is available or not.
+    //Of course this is also dependent on the presence of WCS data in the image.
 
 #ifdef HAVE_INDI
     connect(INDIListener::Instance(), SIGNAL(newTelescope(ISD::GDInterface*)), this, SLOT(updateWCSFunctions()));
@@ -217,13 +217,13 @@ FITSViewer::FITSViewer(QWidget *parent) : KXmlGuiWindow(parent)
     action->setText(i18n("Zoom To Fit"));
     connect(action, SIGNAL(triggered(bool)), SLOT(ZoomToFit()));
 
-    #ifdef HAVE_DATAVISUALIZATION
+#ifdef HAVE_DATAVISUALIZATION
     action = actionCollection()->addAction("toggle_3D_graph");
     action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg")));
     action->setText(i18n("View 3D Graph"));
     action->setCheckable(true);
     connect(action, SIGNAL(triggered(bool)), SLOT(toggle3DGraph()));
-    #endif
+#endif
 
     action = actionCollection()->addAction("mark_stars");
     action->setText(i18n("Mark Stars"));
@@ -231,12 +231,12 @@ FITSViewer::FITSViewer(QWidget *parent) : KXmlGuiWindow(parent)
 
     int filterCounter = 1;
 
-    for (auto& filter : FITSViewer::filterTypes)
+    for (auto &filter : FITSViewer::filterTypes)
     {
         action = actionCollection()->addAction(QString("filter%1").arg(filterCounter));
         action->setText(i18n(filter.toUtf8().constData()));
         connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);});
-        filterCounter++; 
+        filterCounter++;
     }
 
     /* Create GUI */
@@ -263,17 +263,17 @@ void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state)
 
 FITSViewer::~FITSViewer()
 {
-//    if (KStars::Instance())
-//    {
-//        for (QPointer<FITSViewer> fv : KStars::Instance()->getFITSViewersList())
-//        {
-//            if (fv.data() == this)
-//            {
-//                KStars::Instance()->getFITSViewersList().removeOne(this);
-//                break;
-//            }
-//        }
-//    }
+    //    if (KStars::Instance())
+    //    {
+    //        for (QPointer<FITSViewer> fv : KStars::Instance()->getFITSViewersList())
+    //        {
+    //            if (fv.data() == this)
+    //            {
+    //                KStars::Instance()->getFITSViewersList().removeOne(this);
+    //                break;
+    //            }
+    //        }
+    //    }
 
     fitsTabWidget->disconnect();
 
@@ -330,9 +330,10 @@ bool FITSViewer::addFITSCommon(FITSTab *tab, const QUrl &imageName,
 {
     int tabIndex = fitsTabWidget->indexOf(tab);
     if (tabIndex != -1)
-      return false;
+        return false;
 
-    lastURL = QUrl(imageName.url(QUrl::RemoveFilename));
+    if (!imageName.isValid())
+        lastURL = QUrl(imageName.url(QUrl::RemoveFilename));
 
     QApplication::restoreOverrideCursor();
     tab->setPreviewText(previewText);
@@ -344,29 +345,29 @@ bool FITSViewer::addFITSCommon(FITSTab *tab, const QUrl &imageName,
     // Connect tab view signals
     connect(tab->getView(), &FITSView::actionUpdated, this, &FITSViewer::updateAction);
     connect(tab->getView(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions);
-    connect(tab->getView(),&FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
+    connect(tab->getView(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
 
     switch (mode)
     {
-    case FITS_NORMAL:
-	fitsTabWidget->addTab(tab, previewText.isEmpty() ? imageName.fileName() : previewText);
-	break;
+        case FITS_NORMAL:
+            fitsTabWidget->addTab(tab, previewText.isEmpty() ? imageName.fileName() : previewText);
+            break;
 
-    case FITS_CALIBRATE:
-	fitsTabWidget->addTab(tab, i18n("Calibrate"));
-	break;
+        case FITS_CALIBRATE:
+            fitsTabWidget->addTab(tab, i18n("Calibrate"));
+            break;
 
-    case FITS_FOCUS:
-	fitsTabWidget->addTab(tab, i18n("Focus"));
-	break;
+        case FITS_FOCUS:
+            fitsTabWidget->addTab(tab, i18n("Focus"));
+            break;
 
-    case FITS_GUIDE:
-	fitsTabWidget->addTab(tab, i18n("Guide"));
-	break;
+        case FITS_GUIDE:
+            fitsTabWidget->addTab(tab, i18n("Guide"));
+            break;
 
-    case FITS_ALIGN:
-	fitsTabWidget->addTab(tab, i18n("Align"));
-	break;
+        case FITS_ALIGN:
+            fitsTabWidget->addTab(tab, i18n("Align"));
+            break;
     }
 
     saveFileAction->setEnabled(true);
@@ -402,14 +403,15 @@ bool FITSViewer::addFITSCommon(FITSTab *tab, const QUrl &imageName,
     return true;
 }
 
-void FITSViewer::addFITS(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText, bool silent)
+void FITSViewer::loadFile(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText, bool silent)
 {
     led.setColor(Qt::yellow);
     QApplication::setOverrideCursor(Qt::WaitCursor);
 
     FITSTab *tab = new FITSTab(this);
 
-    connect(tab, &FITSTab::failed, [&]() {
+    connect(tab, &FITSTab::failed, [&]()
+    {
         QApplication::restoreOverrideCursor();
         led.setColor(Qt::red);
         if (fitsTabs.size() == 0)
@@ -421,23 +423,25 @@ void FITSViewer::addFITS(const QUrl &imageName, FITSMode mode, FITSScale filter,
         emit failed();
     });
 
-    connect(tab, &FITSTab::loaded, [=]() {
-          if (addFITSCommon(tab, imageName, mode, previewText))
-          emit loaded(fitsID++);
+    connect(tab, &FITSTab::loaded, [ = ]()
+    {
+        if (addFITSCommon(tab, imageName, mode, previewText))
+            emit loaded(fitsID++);
     });
 
-    tab->loadFITS(imageName, mode, filter, silent);
+    tab->loadFile(imageName, mode, filter, silent);
 }
 
-bool FITSViewer::addFITSFromData(FITSData *data, const QUrl &imageName, int *tab_uid,
-				 FITSMode mode, FITSScale filter, const QString &previewText)
+bool FITSViewer::loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid, FITSMode mode,
+                          FITSScale filter, const QString &previewText)
 {
     led.setColor(Qt::yellow);
     QApplication::setOverrideCursor(Qt::WaitCursor);
 
     FITSTab *tab = new FITSTab(this);
 
-    if (!tab->loadFITSFromData(data, imageName, mode, filter)) {
+    if (!tab->loadData(data, mode, filter))
+    {
         QApplication::restoreOverrideCursor();
         led.setColor(Qt::red);
         if (fitsTabs.size() == 0)
@@ -446,12 +450,12 @@ bool FITSViewer::addFITSFromData(FITSData *data, const QUrl &imageName, int *tab
             close();
         }
         emit failed();
-	return false;
+        return false;
     }
 
     if (!addFITSCommon(tab, imageName, mode, previewText))
-      return false;
-    
+        return false;
+
     *tab_uid = fitsID++;
     return true;
 }
@@ -477,7 +481,7 @@ bool FITSViewer::removeFITS(int fitsUID)
     return false;
 }
 
-void FITSViewer::updateFITS(const QUrl &imageName, int fitsUID, FITSScale filter, bool silent)
+void FITSViewer::updateFile(const QUrl &imageName, int fitsUID, FITSScale filter, bool silent)
 {
     FITSTab *tab = fitsMap.value(fitsUID);
 
@@ -493,15 +497,16 @@ void FITSViewer::updateFITS(const QUrl &imageName, int fitsUID, FITSScale filter
 
     // On tab load success
     auto conn = std::make_shared<QMetaObject::Connection>();
-    *conn = connect(tab, &FITSTab::loaded, this, [=]() {
+    *conn = connect(tab, &FITSTab::loaded, this, [ = ]()
+    {
         if (updateFITSCommon(tab, imageName))
         {
             QObject::disconnect(*conn);
             emit loaded(tab->getUID());
         }
-        });
+    });
 
-    tab->loadFITS(imageName, tab->getView()->getMode(), filter, silent);
+    tab->loadFile(imageName, tab->getView()->getMode(), filter, silent);
 }
 
 bool FITSViewer::updateFITSCommon(FITSTab *tab, const QUrl &imageName)
@@ -509,29 +514,29 @@ bool FITSViewer::updateFITSCommon(FITSTab *tab, const QUrl &imageName)
     // On tab load success
     int tabIndex = fitsTabWidget->indexOf(tab);
     if (tabIndex == -1)
-      return false;
+        return false;
 
     if (tab->getView()->getMode() == FITS_NORMAL)
     {
-      if ((imageName.path().startsWith(QLatin1String("/tmp")) ||
-	   imageName.path().contains("/Temp")) &&
-	  Options::singlePreviewFITS())
-	fitsTabWidget->setTabText(tabIndex,
-				  tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText());
-      else
-	fitsTabWidget->setTabText(tabIndex, imageName.fileName());
+        if ((imageName.path().startsWith(QLatin1String("/tmp")) ||
+                imageName.path().contains("/Temp")) &&
+                Options::singlePreviewFITS())
+            fitsTabWidget->setTabText(tabIndex,
+                                      tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText());
+        else
+            fitsTabWidget->setTabText(tabIndex, imageName.fileName());
     }
 
     tab->getUndoStack()->clear();
 
     if (tab->isVisible())
-      led.setColor(Qt::green);
+        led.setColor(Qt::green);
 
     return true;
 }
 
-bool FITSViewer::updateFITSFromData(FITSData *data, const QUrl &imageName, int fitsUID,
-				    int *tab_uid, FITSScale filter)
+bool FITSViewer::updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid,
+                            FITSScale filter)
 {
     FITSTab *tab = fitsMap.value(fitsUID);
 
@@ -541,12 +546,12 @@ bool FITSViewer::updateFITSFromData(FITSData *data, const QUrl &imageName, int f
     if (tab->isVisible())
         led.setColor(Qt::yellow);
 
-    if (!tab->loadFITSFromData(data, imageName, tab->getView()->getMode(), filter))
+    if (!tab->loadData(data, tab->getView()->getMode(), filter))
         return false;
 
     if (!updateFITSCommon(tab, imageName))
         return false;
-    
+
     *tab_uid = tab->getUID();
     return true;
 }
@@ -587,7 +592,7 @@ void FITSViewer::tabFocusUpdated(int currentIndex)
         actionCollection()->action("fits_debayer")->setEnabled(false);
 
     updateStatusBar("", FITS_WCS);
-    connect(view,&FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
+    connect(view, &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
     updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), getCurrentView()->isStarProfileShown());
     updateButtonStatus("view_crosshair", i18n("Cross Hairs"), getCurrentView()->isCrosshairShown());
     updateButtonStatus("view_eq_grid", i18n("Equatorial Gridines"), getCurrentView()->isEQGridShown());
@@ -605,17 +610,20 @@ void FITSViewer::starProfileButtonOff()
 
 void FITSViewer::openFile()
 {
-    QUrl fileURL =
-        QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Image"), lastURL, "Image (*.fits *.fits.fz *.fit *.fts *.jpg *.jpeg *.png *.gif *.bmp)");
+    QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Image"), lastURL,
+                   "FITS (*.fits *.fits.fz *.fit *.fts);;"
+                   "Images (*.jpg *.jpeg *.png *.gif *.bmp);;"
+                   "RAW (*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw)");
+
     if (fileURL.isEmpty())
         return;
 
-    lastURL       = QUrl(fileURL.url(QUrl::RemoveFilename));
+    lastURL = QUrl(fileURL.url(QUrl::RemoveFilename));
     QString fpath = fileURL.toLocalFile();
     QString cpath;
 
     // Make sure we don't have it open already, if yes, switch to it
-    foreach (FITSTab *tab, fitsTabs)
+    for (auto tab : fitsTabs)
     {
         cpath = tab->getCurrentURL()->path();
         if (fpath == cpath)
@@ -625,7 +633,7 @@ void FITSViewer::openFile()
         }
     }
 
-    addFITS(fileURL, FITS_NORMAL, FITS_NONE, QString(), false);
+    loadFile(fileURL, FITS_NORMAL, FITS_NONE, QString(), false);
 }
 
 void FITSViewer::saveFile()
@@ -639,8 +647,9 @@ void FITSViewer::saveFileAs()
         return;
 
     if (fitsTabs[fitsTabWidget->currentIndex()]->saveFileAs() &&
-        fitsTabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL)
-        fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), fitsTabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName());
+            fitsTabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL)
+        fitsTabWidget->setTabText(fitsTabWidget->currentIndex(),
+                                  fitsTabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName());
 }
 
 void FITSViewer::copyFITS()
@@ -826,7 +835,7 @@ void FITSViewer::closeTab(int index)
  when one of them gets pushed and also when tabs are switched.
  */
 
-void FITSViewer::updateButtonStatus(const QString& action, const QString& item, bool showing)
+void FITSViewer::updateButtonStatus(const QString &action, const QString &item, bool showing)
 {
     QAction *a = actionCollection()->action(action);
     if (a == nullptr)
diff --git a/kstars/fitsviewer/fitsviewer.h b/kstars/fitsviewer/fitsviewer.h
index e1b672cd8..6a5d4f5e0 100644
--- a/kstars/fitsviewer/fitsviewer.h
+++ b/kstars/fitsviewer/fitsviewer.h
@@ -63,16 +63,16 @@ class FITSViewer : public KXmlGuiWindow
         explicit FITSViewer(QWidget *parent);
         ~FITSViewer();
 
-        void addFITS(const QUrl &imageName, FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE,
-                     const QString &previewText = QString(), bool silent = true);
+        void loadFile(const QUrl &imageName, FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE,
+                      const QString &previewText = QString(), bool silent = true);
 
-        bool addFITSFromData(FITSData *data, const QUrl &imageName, int *tab_uid,
-			     FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE,
-			     const QString &previewText = QString());
+        bool loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid,
+                      FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE,
+                      const QString &previewText = QString());
 
-        void updateFITS(const QUrl &imageName, int fitsUID, FITSScale filter = FITS_NONE, bool silent = true);
-        bool updateFITSFromData(FITSData *data, const QUrl &imageName, int fitsUID,
-				int *tab_uid, FITSScale filter = FITS_NONE);
+        void updateFile(const QUrl &imageName, int fitsUID, FITSScale filter = FITS_NONE, bool silent = true);
+        bool updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid,
+                        FITSScale filter = FITS_NONE);
         bool removeFITS(int fitsUID);
 
         bool isStarsMarked()
@@ -140,7 +140,7 @@ class FITSViewer : public KXmlGuiWindow
         bool addFITSCommon(FITSTab *tab, const QUrl &imageName,
                            FITSMode mode, const QString &previewText);
         bool updateFITSCommon(FITSTab *tab, const QUrl &imageName);
-  
+
         QTabWidget *fitsTabWidget { nullptr };
         QUndoGroup *undoGroup { nullptr };
         FITSDebayer *debayerDialog { nullptr };
diff --git a/kstars/indi/indiccd.cpp b/kstars/indi/indiccd.cpp
index 55638319d..58fa9bdca 100644
--- a/kstars/indi/indiccd.cpp
+++ b/kstars/indi/indiccd.cpp
@@ -1582,6 +1582,8 @@ void CCD::processBLOB(IBLOB *bp)
     // 1. file is preview or batch mode is not enabled
     // 2. file type is not FITS_NORMAL (focus, guide..etc)
     QString filename;
+#if 0
+
     if (targetChip->isBatchMode() == false || targetChip->getCaptureMode() != FITS_NORMAL)
     {
         if (!writeTempImageFile(format, static_cast<char *>(bp->blob), bp->size, &filename))
@@ -1593,9 +1595,12 @@ void CCD::processBLOB(IBLOB *bp)
             addFITSKeywords(filename, filter);
 
     }
-    // Create file name for others
-    else
+#endif
+    // Create file name for sequences.
+    if (targetChip->isBatchMode())
     {
+        // If either generating file name or writing the image file fails
+        // then return
         if (!generateFilename(format, targetChip->isBatchMode(), &filename) ||
                 !writeImageFile(filename, bp, BType == BLOB_FITS))
         {
@@ -1603,12 +1608,14 @@ void CCD::processBLOB(IBLOB *bp)
             return;
         }
     }
+    else
+        filename = "image" + format;
 
     // store file name
-    strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME);
-    bp->aux0 = targetChip;
-    bp->aux1 = &BType;
-    bp->aux2 = BLOBFilename;
+    //    strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME);
+    //    bp->aux0 = targetChip;
+    //    bp->aux1 = &BType;
+    //    bp->aux2 = BLOBFilename;
 
     if (targetChip->getCaptureMode() == FITS_NORMAL && targetChip->isBatchMode() == true)
     {
@@ -1624,6 +1631,7 @@ void CCD::processBLOB(IBLOB *bp)
     }
 
     // Check if we need to process RAW or regular image. Anything but FITS.
+#if 0
     if (BType == BLOB_IMAGE || BType == BLOB_RAW)
     {
         bool useFITSViewer = Options::autoImageToFITS() &&
@@ -1674,7 +1682,7 @@ void CCD::processBLOB(IBLOB *bp)
 
             FITSData *blob_fits_data = new FITSData(targetChip->getCaptureMode());
 
-            QFuture<bool> fitsloader = blob_fits_data->loadFITS(filename, false);
+            QFuture<bool> fitsloader = blob_fits_data->loadFromFile(filename, false);
             fitsloader.waitForFinished();
             if (!fitsloader.result())
             {
@@ -1698,38 +1706,45 @@ void CCD::processBLOB(IBLOB *bp)
             emit previewJPEGGenerated(filename, m_ImageViewerWindow->metadata());
         }
     }
+#endif
 
     // Load FITS if either:
     // #1 FITS Viewer is set to enabled.
     // #2 This is a preview, so we MUST open FITS Viewer even if disabled.
-    if (BType == BLOB_FITS)
+    //    if (BType == BLOB_FITS)
+    //    {
+    // Don't display image if the following conditions are met:
+    // 1. Mode is NORMAL or CALIBRATE; and
+    // 2. FITS Viewer is disabled; and
+    // 3. Batch mode is enabled.
+    // 4. Summary view is false.
+    if ((targetChip->getCaptureMode() == FITS_NORMAL || targetChip->getCaptureMode() == FITS_CALIBRATE) &&
+            Options::useFITSViewer() == false &&
+            Options::useSummaryPreview() == false &&
+            targetChip->isBatchMode())
     {
-        // Don't display if (NORMAL or CALIBRATE) and ((not using fitsviewer) and (no in batch mode))
-        if ((targetChip->getCaptureMode() == FITS_NORMAL || targetChip->getCaptureMode() == FITS_CALIBRATE) &&
-                (!Options::useFITSViewer() && targetChip->isBatchMode()))
-        {
-            emit BLOBUpdated(bp);
-            return;
-        }
-        FITSData *blob_fits_data = new FITSData(targetChip->getCaptureMode());
-
-        if (!blob_fits_data->loadFITSFromMemory(filename, bp->blob, bp->size, false))
-        {
-            // If reading the blob fails, we treat it the same as exposure failure
-            // and recapture again if possible
-            delete (blob_fits_data);
-            qCCritical(KSTARS_INDI) << "failed reading FITS memory buffer";
-            emit newExposureValue(targetChip, 0, IPS_ALERT);
-            return;
-        }
+        emit BLOBUpdated(bp);
+        emit newImage(nullptr);
+        return;
+    }
 
-        displayFits(targetChip, filename, bp, blob_fits_data);
+    QSharedPointer<FITSData> blob_data;
+    blob_data.reset(new FITSData(targetChip->getCaptureMode()));
+    if (!blob_data->loadFromBuffer(bp->blob, bp->size, shortFormat, false))
+    {
+        // If reading the blob fails, we treat it the same as exposure failure
+        // and recapture again if possible
+        qCCritical(KSTARS_INDI) << "failed reading FITS memory buffer";
+        emit newExposureValue(targetChip, 0, IPS_ALERT);
+        return;
     }
-    else
-        emit BLOBUpdated(bp);
+
+    handleImage(targetChip, filename, bp, blob_data);
+    //    else
+    //        emit BLOBUpdated(bp);
 }
 
-void CCD::displayFits(CCDChip *targetChip, const QString &filename, IBLOB *bp, FITSData *blob_fits_data)
+void CCD::handleImage(CCDChip *targetChip, const QString &filename, IBLOB *bp, QSharedPointer<FITSData> data)
 {
     FITSMode captureMode = targetChip->getCaptureMode();
 
@@ -1738,72 +1753,79 @@ void CCD::displayFits(CCDChip *targetChip, const QString &filename, IBLOB *bp, F
     // this should be fixed in the future and should only use FITSData
     if (Options::useFITSViewer() || targetChip->isBatchMode() == false)
     {
-        if (m_FITSViewerWindows.isNull() &&
-                (captureMode == FITS_NORMAL || captureMode == FITS_CALIBRATE))
+        if (m_FITSViewerWindows.isNull() && (captureMode == FITS_NORMAL || captureMode == FITS_CALIBRATE))
             setupFITSViewerWindows();
     }
 
+    // Add metadata
+    data->setProperty("device", getDeviceName());
+    data->setProperty("blobVector", bp->bvp->name);
+    data->setProperty("blobElement", bp->name);
+    data->setProperty("chip", targetChip->getType());
+    data->setProperty("filename", filename);
+
     switch (captureMode)
     {
         case FITS_NORMAL:
         case FITS_CALIBRATE:
         {
-            bool success;
-            int tabIndex;
-            int *tabID = (captureMode == FITS_NORMAL) ? &normalTabID : &calibrationTabID;
-            QUrl fileURL = QUrl::fromLocalFile(filename);
-            FITSScale captureFilter = targetChip->getCaptureFilter();
-            if (*tabID == -1 || Options::singlePreviewFITS() == false)
+            if (Options::useFITSViewer())
             {
-                // If image is preview and we should display all captured images in a
-                // single tab called "Preview", then set the title to "Preview",
-                // Otherwise, the title will be the captured image name
-                QString previewTitle;
-                if (targetChip->isBatchMode() == false && Options::singlePreviewFITS())
+                bool success = false;
+                int tabIndex = -1;
+                int *tabID = (captureMode == FITS_NORMAL) ? &normalTabID : &calibrationTabID;
+                QUrl fileURL = QUrl::fromLocalFile(filename);
+                FITSScale captureFilter = targetChip->getCaptureFilter();
+                if (*tabID == -1 || Options::singlePreviewFITS() == false)
                 {
-                    // If we are displaying all images from all cameras in a single FITS
-                    // Viewer window, then we prefix the camera name to the "Preview" string
-                    if (Options::singleWindowCapturedFITS())
-                        previewTitle = i18n("%1 Preview", getDeviceName());
-                    else
-                        // Otherwise, just use "Preview"
-                        previewTitle = i18n("Preview");
-                }
+                    // If image is preview and we should display all captured images in a
+                    // single tab called "Preview", then set the title to "Preview",
+                    // Otherwise, the title will be the captured image name
+                    QString previewTitle;
+                    if (targetChip->isBatchMode() == false && Options::singlePreviewFITS())
+                    {
+                        // If we are displaying all images from all cameras in a single FITS
+                        // Viewer window, then we prefix the camera name to the "Preview" string
+                        if (Options::singleWindowCapturedFITS())
+                            previewTitle = i18n("%1 Preview", getDeviceName());
+                        else
+                            // Otherwise, just use "Preview"
+                            previewTitle = i18n("Preview");
+                    }
 
-                success = m_FITSViewerWindows->addFITSFromData(
-                              blob_fits_data, fileURL, &tabIndex, captureMode, captureFilter,
-                              previewTitle);
-            }
-            else
-                success = m_FITSViewerWindows->updateFITSFromData(
-                              blob_fits_data, fileURL, *tabID, &tabIndex, captureFilter);
+                    success = m_FITSViewerWindows->loadData(data, fileURL, &tabIndex, captureMode, captureFilter, previewTitle);
+                }
+                else
+                    success = m_FITSViewerWindows->updateData(data, fileURL, *tabID, &tabIndex, captureFilter);
 
-            if (!success)
-            {
-                // If opening file fails, we treat it the same as exposure failure
-                // and recapture again if possible
-                qCCritical(KSTARS_INDI) << "error adding/updating FITS";
-                emit newExposureValue(targetChip, 0, IPS_ALERT);
-                return;
+                if (!success)
+                {
+                    // If opening file fails, we treat it the same as exposure failure
+                    // and recapture again if possible
+                    qCCritical(KSTARS_INDI) << "error adding/updating FITS";
+                    emit newExposureValue(targetChip, 0, IPS_ALERT);
+                    return;
+                }
+                *tabID = tabIndex;
+                targetChip->setImageView(m_FITSViewerWindows->getView(tabIndex), captureMode);
+                if (Options::focusFITSOnNewImage())
+                    m_FITSViewerWindows->raise();
             }
-            *tabID = tabIndex;
-            targetChip->setImageView(m_FITSViewerWindows->getView(tabIndex), captureMode);
-            if (Options::focusFITSOnNewImage())
-                m_FITSViewerWindows->raise();
 
             emit BLOBUpdated(bp);
+            emit newImage(data);
         }
         break;
 
         case FITS_FOCUS:
         case FITS_GUIDE:
         case FITS_ALIGN:
-            loadImageInView(bp, targetChip, blob_fits_data);
+            loadImageInView(bp, targetChip, data);
             break;
     }
 }
 
-void CCD::loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip, FITSData *data)
+void CCD::loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip, const QSharedPointer<FITSData> &data)
 {
     FITSMode mode = targetChip->getCaptureMode();
     FITSView *view = targetChip->getImageView(mode);
@@ -1813,7 +1835,7 @@ void CCD::loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip, FITSData *data)
     {
         view->setFilter(targetChip->getCaptureFilter());
         //if (!view->loadFITSFromData(data, filename))
-        if (!view->loadFITSFromData(data))
+        if (!view->loadData(data))
         {
             emit newExposureValue(targetChip, 0, IPS_ALERT);
             return;
@@ -1828,6 +1850,7 @@ void CCD::loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip, FITSData *data)
             m_FITSViewerWindows->show();
 
         emit BLOBUpdated(bp);
+        emit newImage(data);
     }
 }
 
diff --git a/kstars/indi/indiccd.h b/kstars/indi/indiccd.h
index 71c6fd051..739f388be 100644
--- a/kstars/indi/indiccd.h
+++ b/kstars/indi/indiccd.h
@@ -357,20 +357,21 @@ class CCD : public DeviceDecorator
         void newFPS(double instantFPS, double averageFPS);
         void newVideoFrame(std::shared_ptr<QImage> frame);
         void coolerToggled(bool enabled);
-        void previewFITSGenerated(const QString &previewFITS);
-        void previewJPEGGenerated(const QString &previewJPEG, QJsonObject metadata);
+        //void previewFITSGenerated(const QString &previewFITS);
+        //void previewJPEGGenerated(const QString &previewJPEG, QJsonObject metadata);
         void ready();
         void captureFailed();
+        void newImage(const QSharedPointer<FITSData> &data);
 
     private:
         void processStream(IBLOB *bp);
-        void loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip, FITSData *data);
+        void loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip, const QSharedPointer<FITSData> &data);
         bool generateFilename(const QString &format, bool batch_mode, QString *filename);
         // Saves an image to disk on a separate thread.
         bool writeImageFile(const QString &filename, IBLOB *bp, bool is_fits);
         // Creates or finds the FITSViewer.
         void setupFITSViewerWindows();
-        void displayFits(CCDChip *targetChip, const QString &filename, IBLOB *bp, FITSData *blob_fits_data);
+        void handleImage(CCDChip *targetChip, const QString &filename, IBLOB *bp, QSharedPointer<FITSData> data);
 
         QString filter;
         bool ISOMode { true };
@@ -392,7 +393,7 @@ class CCD : public DeviceDecorator
         int guideTabID { -1 };
         int alignTabID { -1 };
 
-        char BLOBFilename[MAXINDIFILENAME + 1];
+        //char BLOBFilename[MAXINDIFILENAME + 1];
         IBLOB *primaryCCDBLOB { nullptr };
 
         std::unique_ptr<QTimer> readyTimer;
diff --git a/kstars/indi/indistd.cpp b/kstars/indi/indistd.cpp
index 27552c0e3..a3cd687c3 100644
--- a/kstars/indi/indistd.cpp
+++ b/kstars/indi/indistd.cpp
@@ -64,6 +64,12 @@ GenericDevice::GenericDevice(DeviceInfo &idv, ClientManager *cm)
     });
 }
 
+GenericDevice::~GenericDevice()
+{
+    for (auto &metadata : streamFileMetadata)
+        metadata.file->close();
+}
+
 void GenericDevice::registerDBusType()
 {
 #ifndef KSTARS_LITE
@@ -393,7 +399,6 @@ void GenericDevice::processBLOB(IBLOB *bp)
     if (bp->bvp->p == IP_WO)
         return;
 
-    QFile *data_file = nullptr;
     INDIDataTypes dataType;
 
     if (!strcmp(bp->format, ".ascii"))
@@ -413,40 +418,55 @@ void GenericDevice::processBLOB(IBLOB *bp)
 
     filename += QString("%1_").arg(bp->label) + ts + QString(bp->format).trimmed();
 
-    strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME);
-    bp->aux2 = BLOBFilename;
+    //    strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME);
+    //    bp->aux2 = BLOBFilename;
 
+    // Text Streaming
     if (dataType == DATA_ASCII)
     {
-        if (bp->aux0 == nullptr)
+        // First time, create a data file to hold the stream.
+
+        auto it = std::find_if(streamFileMetadata.begin(), streamFileMetadata.end(), [bp](const StreamFileMetadata & data)
         {
-            bp->aux0               = new int();
-            QFile *ascii_data_file = new QFile();
-            ascii_data_file->setFileName(filename);
-            if (!ascii_data_file->open(QIODevice::WriteOnly))
-            {
-                qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << ascii_data_file->fileName() << endl;
-                return;
-            }
+            return (bp->bvp->device == data.device && bp->bvp->name == data.property && bp->name == data.element);
+        });
+
+        QFile *streamDatafile = nullptr;
 
-            bp->aux1 = ascii_data_file;
+        // New stream data file
+        if (it == streamFileMetadata.end())
+        {
+            StreamFileMetadata metadata;
+            metadata.device = bp->bvp->device;
+            metadata.property = bp->bvp->name;
+            metadata.element = bp->name;
+
+            // Create new file instance
+            // Since it's a child of this class, don't worry about deallocating it
+            streamDatafile = new QFile(this);
+            metadata.file = streamDatafile;
+
+            streamFileMetadata.append(metadata);
         }
+        else
+            streamDatafile = (*it).file;
 
-        data_file = (QFile *)bp->aux1;
+        // Try to get
 
-        QDataStream out(data_file);
-        for (nr = 0; nr < (int)bp->size; nr += n)
+        QDataStream out(streamDatafile);
+        for (nr = 0; nr < bp->size; nr += n)
             n = out.writeRawData(static_cast<char *>(bp->blob) + nr, bp->size - nr);
 
         out.writeRawData((const char *)"\n", 1);
-        data_file->flush();
+        streamDatafile->flush();
+
     }
     else
     {
         QFile fits_temp_file(filename);
         if (!fits_temp_file.open(QIODevice::WriteOnly))
         {
-            qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << fits_temp_file.fileName() << endl;
+            qCCritical(KSTARS_INDI) << "GenericDevice Error: Unable to open " << fits_temp_file.fileName();
             return;
         }
 
@@ -759,7 +779,7 @@ bool GenericDevice::setProperty(QObject *setPropCommand)
 {
     GDSetCommand *indiCommand = static_cast<GDSetCommand *>(setPropCommand);
 
-    //qDebug() << "We are trying to set value for property " << indiCommand->indiProperty << " and element" << indiCommand->indiElement << " and value " << indiCommand->elementValue << endl;
+    //qDebug() << "We are trying to set value for property " << indiCommand->indiProperty << " and element" << indiCommand->indiElement << " and value " << indiCommand->elementValue;
 
     INDI::Property *pp = baseDevice->getProperty(indiCommand->indiProperty.toLatin1().constData());
 
@@ -785,7 +805,7 @@ bool GenericDevice::setProperty(QObject *setPropCommand)
 
             sp->s = indiCommand->elementValue.toInt() == 0 ? ISS_OFF : ISS_ON;
 
-            //qDebug() << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off") << endl;
+            //qDebug() << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
             clientManager->sendNewSwitch(svp);
 
             return true;
@@ -810,7 +830,7 @@ bool GenericDevice::setProperty(QObject *setPropCommand)
 
             np->value = value;
 
-            //qDebug() << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off") << endl;
+            //qDebug() << "Sending switch " << sp->name << " with status " << ((sp->s == ISS_ON) ? "On" : "Off");
             clientManager->sendNewNumber(nvp);
         }
         break;
@@ -1279,7 +1299,7 @@ bool ST4::doPulse(GuideDirection dir, int msecs)
 
     clientManager->sendNewNumber(npulse);
 
-    //qDebug() << "Sending pulse for " << npulse->name << " in direction " << dirPulse->name << " for " << msecs << " ms " << endl;
+    //qDebug() << "Sending pulse for " << npulse->name << " in direction " << dirPulse->name << " for " << msecs << " ms ";
 
     return true;
 }
diff --git a/kstars/indi/indistd.h b/kstars/indi/indistd.h
index f105d3e4f..c7f593fdd 100644
--- a/kstars/indi/indistd.h
+++ b/kstars/indi/indistd.h
@@ -29,6 +29,7 @@ class ClientManager;
 class DriverInfo;
 class DeviceInfo;
 class QTimer;
+class QFile;
 
 // INDI Standard Device Namespace
 namespace ISD
@@ -143,7 +144,7 @@ class GenericDevice : public GDInterface
 
     public:
         explicit GenericDevice(DeviceInfo &idv, ClientManager *cm);
-        virtual ~GenericDevice() override = default;
+        virtual ~GenericDevice();
 
         virtual void registerProperty(INDI::Property *prop) override;
         virtual void removeProperty(const QString &name) override;
@@ -213,6 +214,16 @@ class GenericDevice : public GDInterface
         void updateLocation();
 
     private:
+
+        class StreamFileMetadata
+        {
+            public:
+                QString device;
+                QString property;
+                QString element;
+                QFile *file { nullptr};
+        };
+
         static void registerDBusType();
         bool connected { false };
         QString m_Name;
@@ -221,7 +232,8 @@ class GenericDevice : public GDInterface
         INDI::BaseDevice *baseDevice { nullptr };
         ClientManager *clientManager { nullptr };
         QTimer *watchDogTimer { nullptr };
-        char BLOBFilename[MAXINDIFILENAME + 1];
+        QList<StreamFileMetadata> streamFileMetadata;
+        //char BLOBFilename[MAXINDIFILENAME + 1];
 };
 
 /**
diff --git a/kstars/kstarsactions.cpp b/kstars/kstarsactions.cpp
index 64aec5b96..0b38fcd4e 100644
--- a/kstars/kstarsactions.cpp
+++ b/kstars/kstarsactions.cpp
@@ -1189,10 +1189,10 @@ void KStars::slotOpenFITS()
 #ifdef HAVE_CFITSIO
 
     static QUrl path = QUrl::fromLocalFile(QDir::homePath());
-    QUrl fileURL =
-        QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Image"), path,
-                                    "Image (*.fits *.fits.fz *.fit *.fts *.jpg *.jpeg *.png *.gif *.bmp)");
-
+    QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Image"), path,
+                   "FITS (*.fits *.fits.fz *.fit *.fts);;"
+                   "Images (*.jpg *.jpeg *.png *.gif *.bmp);;"
+                   "RAW (*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw)");
     if (fileURL.isEmpty())
         return;
 
@@ -1207,7 +1207,7 @@ void KStars::slotOpenFITS()
         fv->show();
     });
 
-    fv->addFITS(fileURL, FITS_NORMAL, FITS_NONE, QString(), false);
+    fv->loadFile(fileURL, FITS_NORMAL, FITS_NONE, QString(), false);
 #endif
 }
 
diff --git a/kstars/kstarsdbus.cpp b/kstars/kstarsdbus.cpp
index e2141e3e8..beb82c206 100644
--- a/kstars/kstarsdbus.cpp
+++ b/kstars/kstarsdbus.cpp
@@ -1055,6 +1055,6 @@ void KStars::openFITS(const QUrl &imageURL)
         QObject::disconnect(*m_Failed);
     });
 
-    fv->addFITS(imageURL);
+    fv->loadFile(imageURL);
 #endif
 }



More information about the Kstars-devel mailing list