Kstars

media.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 Media Channel
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "media.h"
10#include "commands.h"
11#include "skymapcomposite.h"
12#include "fitsviewer/fitsview.h"
13#include "fitsviewer/fitsdata.h"
14#include "indi/indilistener.h"
15#include "hips/hipsfinder.h"
16#include "kstarsdata.h"
17#include "ekos/auxiliary/darklibrary.h"
18#include "ekos/guide/guide.h"
19#include "ekos/align/align.h"
20#include "ekos/capture/capture.h"
21#include "ekos/capture/cameraprocess.h"
22#include "ekos/focus/focusmodule.h"
23#include "kspaths.h"
24#include "Options.h"
25
26#include "ekos_debug.h"
27#include "kstars.h"
28#include "version.h"
29
30#include <QtConcurrent>
31#include <KFormat>
32#include <QImageWriter>
33
34namespace EkosLive
35{
36
37///////////////////////////////////////////////////////////////////////////////////////////
38///
39///////////////////////////////////////////////////////////////////////////////////////////
40Media::Media(Ekos::Manager * manager, QVector<QSharedPointer<NodeManager>> &nodeManagers):
41 m_Manager(manager), m_NodeManagers(nodeManagers)
42{
43 for (auto &nodeManager : m_NodeManagers)
44 {
45 connect(nodeManager->media(), &Node::connected, this, &Media::onConnected);
46 connect(nodeManager->media(), &Node::disconnected, this, &Media::onDisconnected);
47 connect(nodeManager->media(), &Node::onTextReceived, this, &Media::onTextReceived);
48 connect(nodeManager->media(), &Node::onBinaryReceived, this, &Media::onBinaryReceived);
49 }
50
51 connect(this, &Media::newImage, this, [this](const QByteArray & image)
52 {
53 uploadImage(image);
54 });
55}
56
57///////////////////////////////////////////////////////////////////////////////////////////
58///
59///////////////////////////////////////////////////////////////////////////////////////////
60bool Media::isConnected() const
61{
62 return std::any_of(m_NodeManagers.begin(), m_NodeManagers.end(), [](auto & nodeManager)
63 {
64 return nodeManager->media()->isConnected();
65 });
66}
67
68///////////////////////////////////////////////////////////////////////////////////////////
69///
70///////////////////////////////////////////////////////////////////////////////////////////
71void Media::onConnected()
72{
73 auto node = qobject_cast<Node*>(sender());
74 if (!node)
75 return;
76
77 qCInfo(KSTARS_EKOS) << "Connected to Media Websocket server at" << node->url().toDisplayString();
78
79 emit connected();
80}
81
82///////////////////////////////////////////////////////////////////////////////////////////
83///
84///////////////////////////////////////////////////////////////////////////////////////////
85void Media::onDisconnected()
86{
87 auto node = qobject_cast<Node*>(sender());
88 if (!node)
89 return;
90
91 qCInfo(KSTARS_EKOS) << "Disconnected from Message Websocket server at" << node->url().toDisplayString();
92
93 if (isConnected() == false)
94 {
95 m_sendBlobs = true;
96
97 for (const QString &oneFile : temporaryFiles)
98 QFile::remove(oneFile);
99 temporaryFiles.clear();
100
101 emit disconnected();
102 }
103}
104
105///////////////////////////////////////////////////////////////////////////////////////////
106///
107///////////////////////////////////////////////////////////////////////////////////////////
108void Media::onTextReceived(const QString &message)
109{
110 qCInfo(KSTARS_EKOS) << "Media Text Websocket Message" << message;
112 auto serverMessage = QJsonDocument::fromJson(message.toLatin1(), &error);
113 if (error.error != QJsonParseError::NoError)
114 {
115 qCWarning(KSTARS_EKOS) << "Ekos Live Parsing Error" << error.errorString();
116 return;
117 }
118
119 const QJsonObject msgObj = serverMessage.object();
120 const QString command = msgObj["type"].toString();
121 const QJsonObject payload = msgObj["payload"].toObject();
122
123 if (command == commands[ALIGN_SET_FILE_EXTENSION])
124 extension = payload["ext"].toString();
125 else if (command == commands[SET_BLOBS])
126 m_sendBlobs = msgObj["payload"].toBool();
127 // Get a list of object based on criteria
128 else if (command == commands[ASTRO_GET_OBJECTS_IMAGE])
129 {
130 int level = payload["level"].toInt(5);
131 double zoom = payload["zoom"].toInt(20000);
132 bool exact = payload["exact"].toBool(false);
133
134 // Object Names
135 QVariantList objectNames = payload["names"].toArray().toVariantList();
136
137 for (auto &oneName : objectNames)
138 {
139 const QString name = oneName.toString();
140 SkyObject *oneObject = KStarsData::Instance()->skyComposite()->findByName(name, exact);
141 if (oneObject)
142 {
143 QImage centerImage(HIPS_TILE_WIDTH, HIPS_TILE_HEIGHT, QImage::Format_ARGB32_Premultiplied);
144 double fov_w = 0, fov_h = 0;
145
146 if (oneObject->type() == SkyObject::MOON || oneObject->type() == SkyObject::PLANET)
147 {
148 QProcess xplanetProcess;
149 const QString output = KSPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + "xplanet.jpg";
150 xplanetProcess.start(Options::xplanetPath(), QStringList()
151 << "--num_times" << "1"
152 << "--geometry" << QString("%1x%2").arg(HIPS_TILE_WIDTH).arg(HIPS_TILE_HEIGHT)
153 << "--body" << name.toLower()
154 << "--output" << output);
155 xplanetProcess.waitForFinished(5000);
156 centerImage.load(output);
157 }
158 else
159 HIPSFinder::Instance()->render(oneObject, level, zoom, &centerImage, fov_w, fov_h);
160
161 if (!centerImage.isNull())
162 {
163 // Use seed from name, level, and zoom so that it is unique
164 // even if regenerated again.
165 auto seed = QString("%1%2%3").arg(QString::number(level), QString::number(zoom), name);
166 QString uuid = QString("hips_") + QCryptographicHash::hash(seed.toLatin1(), QCryptographicHash::Md5).toHex();
167 // Send everything as strings
168 QJsonObject metadata =
169 {
170 {"uuid", uuid},
171 {"name", exact ? name : oneObject->name()},
172 {"zoom", zoom},
173 {"resolution", QString("%1x%2").arg(HIPS_TILE_WIDTH).arg(HIPS_TILE_HEIGHT)},
174 {"bin", "1x1"},
175 {"fov_w", QString::number(fov_w)},
176 {"fov_h", QString::number(fov_h)},
177 {"ext", "jpg"}
178 };
179
180 QByteArray jpegData;
181 QBuffer buffer(&jpegData);
182 buffer.open(QIODevice::WriteOnly);
183
184 // First METADATA_PACKET bytes of the binary data is always allocated
185 // to the metadata, the rest to the image data.
187 meta = meta.leftJustified(METADATA_PACKET, 0);
188 buffer.write(meta);
189 centerImage.save(&buffer, "jpg", 90);
190 buffer.close();
191
192 emit newImage(jpegData);
193 }
194 }
195 }
196 }
197 else if (command == commands[ASTRO_GET_SKYPOINT_IMAGE])
198 {
199 int level = payload["level"].toInt(5);
200 double zoom = payload["zoom"].toInt(20000);
201 double ra = payload["ra"].toDouble(0);
202 double de = payload["de"].toDouble(0);
203 double width = payload["width"].toDouble(512);
204 double height = payload["height"].toDouble(512);
205
206 QImage centerImage(width, height, QImage::Format_ARGB32_Premultiplied);
207 SkyPoint coords(ra, de);
208 SkyPoint J2000Coord(coords.ra(), coords.dec());
209 J2000Coord.catalogueCoord(KStars::Instance()->data()->ut().djd());
210 coords.setRA0(J2000Coord.ra());
211 coords.setDec0(J2000Coord.dec());
212 coords.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
213
214 volatile auto jnowRAString = coords.ra().toHMSString();
215 volatile auto jnowDEString = coords.dec().toDMSString();
216 volatile auto j2000RAString = coords.ra0().toHMSString();
217 volatile auto j2000DEString = coords.dec0().toDMSString();
218
219
220 double fov_w = 0, fov_h = 0;
221 HIPSFinder::Instance()->render(&coords, level, zoom, &centerImage, fov_w, fov_h);
222
223 if (!centerImage.isNull())
224 {
225 // Use seed from name, level, and zoom so that it is unique
226 // even if regenerated again.
227 // Send everything as strings
228 QJsonObject metadata =
229 {
230 {"uuid", "skypoint_hips"},
231 {"name", "skypoint_hips"},
232 {"zoom", zoom},
233 {"resolution", QString("%1x%2").arg(width).arg(height)},
234 {"bin", "1x1"},
235 {"fov_w", QString::number(fov_w)},
236 {"fov_h", QString::number(fov_h)},
237 {"ext", "jpg"}
238 };
239
240 QByteArray jpegData;
241 QBuffer buffer(&jpegData);
242 buffer.open(QIODevice::WriteOnly);
243
244 // First METADATA_PACKET bytes of the binary data is always allocated
245 // to the metadata, the rest to the image data.
247 meta = meta.leftJustified(METADATA_PACKET, 0);
248 buffer.write(meta);
249 centerImage.save(&buffer, "jpg", 95);
250 buffer.close();
251
252 emit newImage(jpegData);
253 }
254 }
255}
256
257///////////////////////////////////////////////////////////////////////////////////////////
258///
259///////////////////////////////////////////////////////////////////////////////////////////
260void Media::onBinaryReceived(const QByteArray &message)
261{
262 // Sometimes this is triggered even though it's a text message
263 Ekos::Align * align = m_Manager->alignModule();
264 if (align)
265 {
266 QString metadataString = message.left(METADATA_PACKET);
267 QJsonDocument metadataDocument = QJsonDocument::fromJson(metadataString.toLatin1());
268 QJsonObject metadataJSON = metadataDocument.object();
269 QString extension = metadataJSON.value("ext").toString();
270 align->loadAndSlew(message.mid(METADATA_PACKET), extension);
271 }
272}
273
274///////////////////////////////////////////////////////////////////////////////////////////
275///
276///////////////////////////////////////////////////////////////////////////////////////////
277void Media::sendDarkLibraryData(const QSharedPointer<FITSData> &data)
278{
279 sendData(data, "+D");
280};
281
282///////////////////////////////////////////////////////////////////////////////////////////
283///
284///////////////////////////////////////////////////////////////////////////////////////////
285void Media::sendData(const QSharedPointer<FITSData> &data, const QString &uuid)
286{
287 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false || isConnected() == false)
288 return;
289
290 StretchParams params;
291 QImage image;
292 stretch(data, image, params);
293 upload(data, image, params, uuid);
294}
295
296///////////////////////////////////////////////////////////////////////////////////////////
297///
298///////////////////////////////////////////////////////////////////////////////////////////
299void Media::sendFile(const QString &filename, const QString &uuid)
300{
301 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false || isConnected() == false)
302 return;
303
304 QSharedPointer<FITSData> data(new FITSData());
305 data->loadFromFile(filename);
306#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
307 QtConcurrent::run(&Media::dispatch, this, data, uuid);
308#else
309 QtConcurrent::run(this, &Media::dispatch, data, uuid);
310#endif
311}
312
313///////////////////////////////////////////////////////////////////////////////////////////
314///
315///////////////////////////////////////////////////////////////////////////////////////////
316void Media::sendView(const QSharedPointer<FITSView> &view, const QString &uuid)
317{
318 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false || isConnected() == false)
319 return;
320
321 upload(view, uuid);
322}
323
324///////////////////////////////////////////////////////////////////////////////////////////
325///
326///////////////////////////////////////////////////////////////////////////////////////////
327void Media::dispatch(const QSharedPointer<FITSData> &data, const QString &uuid)
328{
329 QSharedPointer<FITSView> previewImage(new FITSView());
330 previewImage->loadData(data);
331 upload(previewImage, uuid);
332}
333
334///////////////////////////////////////////////////////////////////////////////////////////
335///
336///////////////////////////////////////////////////////////////////////////////////////////
337void Media::stretch(const QSharedPointer<FITSData> &data, QImage &image, StretchParams &params) const
338{
339 double min = 0, max = 0;
340 data->getMinMax(&min, &max);
341 auto width = data->width();
342 auto height = data->height();
343 auto channels = data->channels();
344 auto dataType = data->dataType();
345
346 if (min == max)
347 {
348 image.fill(Qt::white);
349 }
350
351 if (channels == 1)
352 {
353 image = QImage(width, height, QImage::Format_Indexed8);
354
355 image.setColorCount(256);
356 for (int i = 0; i < 256; i++)
357 image.setColor(i, qRgb(i, i, i));
358 }
359 else
360 {
361 image = QImage(width, height, QImage::Format_RGB32);
362 }
363
364 Stretch stretch(width, height, channels, dataType);
365
366 // Compute new auto-stretch params.
367 params = stretch.computeParams(data->getImageBuffer());
368 stretch.setParams(params);
369 stretch.run(data->getImageBuffer(), &image, 1);
370}
371
372///////////////////////////////////////////////////////////////////////////////////////////
373///
374///////////////////////////////////////////////////////////////////////////////////////////
375void Media::upload(const QSharedPointer<FITSView> &view, const QString &uuid)
376{
377 const QString ext = "jpg";
378 QByteArray jpegData;
379 QBuffer buffer(&jpegData);
380 buffer.open(QIODevice::WriteOnly);
381
382 const QSharedPointer<FITSData> imageData = view->imageData();
383 QString resolution = QString("%1x%2").arg(imageData->width()).arg(imageData->height());
384 QString sizeBytes = KFormat().formatByteSize(imageData->size());
385 QVariant xbin(1), ybin(1), exposure(0), focal_length(0), gain(0), pixel_size(0), aperture(0);
386 imageData->getRecordValue("XBINNING", xbin);
387 imageData->getRecordValue("YBINNING", ybin);
388 imageData->getRecordValue("EXPTIME", exposure);
389 imageData->getRecordValue("GAIN", gain);
390 imageData->getRecordValue("PIXSIZE1", pixel_size);
391 imageData->getRecordValue("FOCALLEN", focal_length);
392 imageData->getRecordValue("APTDIA", aperture);
393
394 auto stretchParameters = view->getStretchParams();
395
396 // Account for binning
397 const double binned_pixel = pixel_size.toDouble() * xbin.toInt();
398
399 // Send everything as strings
400 QJsonObject metadata =
401 {
402 {"resolution", resolution},
403 {"size", sizeBytes},
404 {"channels", imageData->channels()},
405 {"mean", imageData->getAverageMean()},
406 {"median", imageData->getAverageMedian()},
407 {"stddev", imageData->getAverageStdDev()},
408 {"min", imageData->getMin()},
409 {"max", imageData->getMax()},
410 {"bin", QString("%1x%2").arg(xbin.toString(), ybin.toString())},
411 {"bpp", QString::number(imageData->bpp())},
412 {"uuid", uuid},
413 {"exposure", exposure.toString()},
414 {"focal_length", focal_length.toString()},
415 {"aperture", aperture.toString()},
416 {"gain", gain.toString()},
417 {"pixel_size", QString::number(binned_pixel, 'f', 4)},
418 {"shadows", stretchParameters.grey_red.shadows},
419 {"midtones", stretchParameters.grey_red.midtones},
420 {"highlights", stretchParameters.grey_red.highlights},
421 {"hasWCS", imageData->hasWCS()},
422 {"hfr", imageData->getHFR()},
423 {"view", view->objectName()},
424 {"ext", ext}
425 };
426
427 // First METADATA_PACKET bytes of the binary data is always allocated
428 // to the metadata
429 // the rest to the image data.
431 meta = meta.leftJustified(METADATA_PACKET, 0);
432 buffer.write(meta);
433
434 auto fastImage = (!Options::ekosLiveHighBandwidth() || uuid[0] == '+');
435 auto scaleWidth = fastImage ? HB_IMAGE_WIDTH / 2 : HB_IMAGE_WIDTH;
436
437 // For low bandwidth images
438 // Except for dark frames +D
439 QPixmap scaledImage = view->getDisplayPixmap().width() > scaleWidth ?
440 view->getDisplayPixmap().scaledToWidth(scaleWidth, fastImage ? Qt::FastTransformation : Qt::SmoothTransformation) :
441 view->getDisplayPixmap();
442 scaledImage.save(&buffer, ext.toLatin1().constData(), HB_IMAGE_QUALITY);
443
444 buffer.close();
445
446 emit newImage(jpegData);
447}
448
449///////////////////////////////////////////////////////////////////////////////////////////
450///
451///////////////////////////////////////////////////////////////////////////////////////////
452void Media::upload(const QSharedPointer<FITSData> &data, const QImage &image, const StretchParams &params,
453 const QString &uuid)
454{
455 const QString ext = "jpg";
456 QByteArray jpegData;
457 QBuffer buffer(&jpegData);
458 buffer.open(QIODevice::WriteOnly);
459
460 QString resolution = QString("%1x%2").arg(data->width()).arg(data->height());
461 QString sizeBytes = KFormat().formatByteSize(data->size());
462 QVariant xbin(1), ybin(1), exposure(0), focal_length(0), gain(0), pixel_size(0), aperture(0);
463 data->getRecordValue("XBINNING", xbin);
464 data->getRecordValue("YBINNING", ybin);
465 data->getRecordValue("EXPTIME", exposure);
466 data->getRecordValue("GAIN", gain);
467 data->getRecordValue("PIXSIZE1", pixel_size);
468 data->getRecordValue("FOCALLEN", focal_length);
469 data->getRecordValue("APTDIA", aperture);
470
471 // Account for binning
472 const double binned_pixel = pixel_size.toDouble() * xbin.toInt();
473
474 // Send everything as strings
475 QJsonObject metadata =
476 {
477 {"resolution", resolution},
478 {"size", sizeBytes},
479 {"channels", data->channels()},
480 {"mean", data->getAverageMean()},
481 {"median", data->getAverageMedian()},
482 {"stddev", data->getAverageStdDev()},
483 {"min", data->getMin()},
484 {"max", data->getMax()},
485 {"bin", QString("%1x%2").arg(xbin.toString(), ybin.toString())},
486 {"bpp", QString::number(data->bpp())},
487 {"uuid", uuid},
488 {"exposure", exposure.toString()},
489 {"focal_length", focal_length.toString()},
490 {"aperture", aperture.toString()},
491 {"gain", gain.toString()},
492 {"pixel_size", QString::number(binned_pixel, 'f', 4)},
493 {"shadows", params.grey_red.shadows},
494 {"midtones", params.grey_red.midtones},
495 {"highlights", params.grey_red.highlights},
496 {"hasWCS", data->hasWCS()},
497 {"hfr", data->getHFR()},
498 {"ext", ext}
499 };
500
501 // First METADATA_PACKET bytes of the binary data is always allocated
502 // to the metadata
503 // the rest to the image data.
505 meta = meta.leftJustified(METADATA_PACKET, 0);
506 buffer.write(meta);
507
508 auto fastImage = (!Options::ekosLiveHighBandwidth() || uuid[0] == '+');
509 auto scaleWidth = fastImage ? HB_IMAGE_WIDTH / 2 : HB_IMAGE_WIDTH;
510
511 // For low bandwidth images
512 // Except for dark frames +D
513 QImage scaledImage = image.width() > scaleWidth ?
514 image.scaledToWidth(scaleWidth, fastImage ? Qt::FastTransformation : Qt::SmoothTransformation) :
515 image;
516 scaledImage.save(&buffer, ext.toLatin1().constData(), HB_IMAGE_QUALITY);
517
518 buffer.close();
519
520 emit newImage(jpegData);
521}
522
523///////////////////////////////////////////////////////////////////////////////////////////
524///
525///////////////////////////////////////////////////////////////////////////////////////////
526void Media::sendUpdatedFrame(const QSharedPointer<FITSView> &view)
527{
528 if (isConnected() == false)
529 return;
530
531 QString ext = "jpg";
532 QByteArray jpegData;
533 QBuffer buffer(&jpegData);
534 buffer.open(QIODevice::WriteOnly);
535
536 const QSharedPointer<FITSData> imageData = view->imageData();
537
538 if (!imageData)
539 return;
540
541 const int32_t width = imageData->width();
542 const int32_t height = imageData->height();
543 QString resolution = QString("%1x%2").arg(width).arg(height);
544 QString sizeBytes = KFormat().formatByteSize(imageData->size());
545 QVariant xbin(1), ybin(1), exposure(0), focal_length(0), gain(0), pixel_size(0), aperture(0);
546 imageData->getRecordValue("XBINNING", xbin);
547 imageData->getRecordValue("YBINNING", ybin);
548 imageData->getRecordValue("EXPTIME", exposure);
549 imageData->getRecordValue("GAIN", gain);
550 imageData->getRecordValue("PIXSIZE1", pixel_size);
551 imageData->getRecordValue("FOCALLEN", focal_length);
552 imageData->getRecordValue("APTDIA", aperture);
553
554 // Account for binning
555 const double binned_pixel = pixel_size.toDouble() * xbin.toInt();
556 // Send everything as strings
557 QJsonObject metadata =
558 {
559 {"resolution", resolution},
560 {"size", sizeBytes},
561 {"channels", imageData->channels()},
562 {"mean", imageData->getAverageMean()},
563 {"median", imageData->getAverageMedian()},
564 {"stddev", imageData->getAverageStdDev()},
565 {"bin", QString("%1x%2").arg(xbin.toString()).arg(ybin.toString())},
566 {"bpp", QString::number(imageData->bpp())},
567 {"uuid", "+A"},
568 {"exposure", exposure.toString()},
569 {"focal_length", focal_length.toString()},
570 {"aperture", aperture.toString()},
571 {"gain", gain.toString()},
572 {"pixel_size", QString::number(binned_pixel, 'f', 4)},
573 {"ext", ext}
574 };
575
576 // First METADATA_PACKET bytes of the binary data is always allocated
577 // to the metadata
578 // the rest to the image data.
580 meta = meta.leftJustified(METADATA_PACKET, 0);
581 buffer.write(meta);
582
583 // For low bandwidth images
584 QPixmap scaledImage;
585 // Align images
586 if (correctionVector.isNull() == false)
587 {
588 scaledImage = view->getDisplayPixmap();
589 const double currentZoom = view->getCurrentZoom();
590 const double normalizedZoom = currentZoom / 100;
591 // If zoom level is not 100%, then scale.
592 if (fabs(normalizedZoom - 1) > 0.001)
593 scaledImage = view->getDisplayPixmap().scaledToWidth(view->zoomedWidth());
594 else
595 scaledImage = view->getDisplayPixmap();
596 // as we factor in the zoom level, we adjust center and length accordingly
597 QPointF center = 0.5 * correctionVector.p1() * normalizedZoom + 0.5 * correctionVector.p2() * normalizedZoom;
598 uint32_t length = qMax(correctionVector.length() / normalizedZoom, 100 / normalizedZoom);
599
600 QRect boundingRectable;
601 boundingRectable.setSize(QSize(length * 2, length * 2));
602 QPoint topLeft = (center - QPointF(length, length)).toPoint();
603 boundingRectable.moveTo(topLeft);
604 boundingRectable = boundingRectable.intersected(scaledImage.rect());
605
606 emit newBoundingRect(boundingRectable, scaledImage.size(), currentZoom);
607
608 scaledImage = scaledImage.copy(boundingRectable);
609 }
610 else
611 {
612 scaledImage = view->getDisplayPixmap().width() > HB_IMAGE_WIDTH / 2 ?
613 view->getDisplayPixmap().scaledToWidth(HB_IMAGE_WIDTH / 2, Qt::FastTransformation) :
614 view->getDisplayPixmap();
615 emit newBoundingRect(QRect(), QSize(), 100);
616 }
617
618 scaledImage.save(&buffer, ext.toLatin1().constData(), HB_IMAGE_QUALITY);
619 buffer.close();
620 emit newImage(jpegData);
621}
622
623///////////////////////////////////////////////////////////////////////////////////////////
624///
625///////////////////////////////////////////////////////////////////////////////////////////
626void Media::sendVideoFrame(const QSharedPointer<QImage> &frame)
627{
628 if (isConnected() == false ||
629 Options::ekosLiveImageTransfer() == false ||
630 m_sendBlobs == false ||
631 !frame)
632 return;
633
634 int32_t width = Options::ekosLiveHighBandwidth() ? HB_VIDEO_WIDTH : HB_VIDEO_WIDTH / 2;
635 QByteArray image;
636 QBuffer buffer(&image);
637 buffer.open(QIODevice::WriteOnly);
638
639 QImage videoImage = (frame->width() > width) ? frame->scaledToWidth(width) : *frame;
640
641 QString resolution = QString("%1x%2").arg(videoImage.width()).arg(videoImage.height());
642
643 // First METADATA_PACKET bytes of the binary data is always allocated
644 // to the metadata
645 // the rest to the image data.
646 QJsonObject metadata =
647 {
648 {"resolution", resolution},
649 {"ext", "jpg"}
650 };
652 meta = meta.leftJustified(METADATA_PACKET, 0);
653 buffer.write(meta);
654
655 QImageWriter writer;
656 writer.setDevice(&buffer);
657 writer.setFormat("JPG");
658 writer.setCompression(6);
659 writer.write(videoImage);
660 buffer.close();
661
662 for (auto &nodeManager : m_NodeManagers)
663 {
664 nodeManager->media()->sendBinaryMessage(image);
665 }
666}
667
668///////////////////////////////////////////////////////////////////////////////////////////
669///
670///////////////////////////////////////////////////////////////////////////////////////////
671void Media::registerCameras()
672{
673 static const QRegularExpression re("[-{}]");
674
675 for(auto &oneDevice : INDIListener::devices())
676 {
677 auto camera = oneDevice->getCamera();
678 if (camera)
679 {
680 camera->disconnect(this);
681 connect(camera, &ISD::Camera::newVideoFrame, this, &Media::sendVideoFrame);
682 }
683 }
684
685 auto captureModule = m_Manager->captureModule();
686 if (!captureModule)
687 return;
688
689 auto process = captureModule->process().get();
690 process->disconnect(this);
691 connect(process, &Ekos::CameraProcess::newView, this, [this](const QSharedPointer<FITSView> &view)
692 {
693 sendView(view, view->imageData()->objectName());
694 });
695}
696
697void Media::resetPolarView()
698{
699 this->correctionVector = QLineF();
700 m_Manager->alignModule()->zoomAlignView();
701}
702
703void Media::uploadImage(const QByteArray &image)
704{
705 for (auto &nodeManager : m_NodeManagers)
706 {
707 nodeManager->media()->sendBinaryMessage(image);
708 }
709}
710
711void Media::processNewBLOB(IBLOB * bp)
712{
713 Q_UNUSED(bp)
714}
715
716void Media::sendModuleFrame(const QSharedPointer<FITSView> &view)
717{
718 if (Options::ekosLiveImageTransfer() == false || m_sendBlobs == false)
719 return;
720
721 if (qobject_cast<Ekos::Align*>(sender()) == m_Manager->alignModule())
722 sendView(view, "+A");
723 else if (qobject_cast<Ekos::Focus*>(sender()) == m_Manager->focusModule()->mainFocuser())
724 sendView(view, "+F");
725 else if (qobject_cast<Ekos::Guide*>(sender()) == m_Manager->guideModule())
726 sendView(view, "+G");
727 else if (qobject_cast<Ekos::DarkLibrary*>(sender()) == Ekos::DarkLibrary::Instance())
728 sendView(view, "+D");
729}
730}
Align class handles plate-solving and polar alignment measurement and correction using astrometry....
Definition align.h:77
bool loadAndSlew(const QByteArray &image, const QString &extension)
DBUS interface function.
Definition align.cpp:3085
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
static KStars * Instance()
Definition kstars.h:122
Provides all necessary information about an object in the sky: its coordinates, name(s),...
Definition skyobject.h:50
virtual QString name(void) const
Definition skyobject.h:154
int type(void) const
Definition skyobject.h:212
The sky coordinates of a point in the sky.
Definition skypoint.h:45
Generic record interfaces and implementations.
Definition cloud.cpp:22
GeoCoordinates geo(const QVariant &location)
QString name(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QStringView level(QStringView ifopt)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
const char * constData() const const
QByteArray left(qsizetype len) const const
QByteArray leftJustified(qsizetype width, char fill, bool truncate) const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray toHex(char separator) const const
QByteArray hash(QByteArrayView data, Algorithm method)
QChar separator()
bool remove()
Format_ARGB32_Premultiplied
void fill(Qt::GlobalColor color)
int height() const const
bool save(QIODevice *device, const char *format, int quality) const const
QImage scaledToWidth(int width, Qt::TransformationMode mode) const const
void setColor(int index, QRgb colorValue)
void setColorCount(int colorCount)
int width() const const
void setCompression(int compression)
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
bool write(const QImage &image)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QByteArray toJson(JsonFormat format) const const
QJsonValue value(QLatin1StringView key) const const
QString toString() const const
QPixmap copy(const QRect &rectangle) const const
QRect rect() const const
bool save(QIODevice *device, const char *format, int quality) const const
QSize size() const const
void start(OpenMode mode)
bool waitForFinished(int msecs)
QRect intersected(const QRect &rectangle) const const
void moveTo(const QPoint &position)
void setSize(const QSize &size)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
QString toLower() const const
FastTransformation
QTextStream & center(QTextStream &stream)
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 28 2025 11:57:24 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.