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

KDE's Doxygen guidelines are available online.