GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/MassDataCborMassSpectrumHandler.cpp
Date: 2025-11-20 01:41:33
Lines:
0/176
0.0%
Functions:
0/17
0.0%
Branches:
0/180
0.0%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2020 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the MsXpertSuite project.
10 *
11 * The MsXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 /////////////////////// Std lib includes
35
36
37 /////////////////////// Qt includes
38 #include <QDebug>
39 #include <QFileInfo>
40
41
42 /////////////////////// IsoSpec
43
44
45 /////////////////////// Local includes
46 #include "MsXpS/libXpertMassCore/MassDataCborMassSpectrumHandler.hpp"
47
48 namespace MsXpS
49 {
50
51 namespace libXpertMassCore
52 {
53
54
55 /*!
56 \class MsXpS::libXpertMassCore::MassDataCborMassSpectrumHandler
57 \inmodule libXpertMassCore
58 \ingroup XpertMassCoreUtilities
59 \inheaderfile MassDataCborMassSpectrumHandler.hpp
60
61 \brief The MassDataCborMassSpectrumHandler class provides features to handle
62 mass spectrum data using the CBOR (Concise Binary Object Representation)
63 container streaming classes.
64
65 The data transported using CBOR is first qualified using the
66 \l{Enums::MassDataType::MASS_SPECTRUM} value. The data are packed following that
67 mass data type specification.
68
69 The format for this kind of CBOR mass spectrum data is the following:
70
71 The whole file is actually a map that contains the key/value
72 pairs below.
73
74 All the text string values below are described in this way in the CBOR Qt
75 implementation specification: "Major type 3: a text string, specifically a
76 string of Unicode characters that is encoded as UTF-8 (that is, not a
77 QByteArray, but a QString)".
78
79 The containers, map, strings and byte arrays dot not need the reader's next()
80 call.
81
82 The simple value, like integers, do need the reader's next() call.
83
84 In this format, the whole data set in the CBOR container is a map with the
85 following key/value pairs:
86
87 \e{start_map}
88
89 \list
90
91 \li "DATA_TYPE" / value (quint64) : this quint64
92 (QCborStreamReader::UnsignedInteger) value is only got as the very first data bit
93 in the file and represents the Enums::MassDataType::MASS_SPECTRUM value
94 describing the kind of data encoded in the CBOR container.
95
96 \li "TITLE" / value (text string): the title of the mass spectrum that should be
97 used to identify it later in a mass data visualization/exploration program like
98 MineXpert.
99
100 \li "TRACE_COLOR" / value (QByteArray)
101
102 \li "X_LABEL" / value (text string): the label of the x abscissae axis.
103
104 \li "X_DATA" / value (base64 QByteArray): the array of Base64-encoded m/z values
105 (x axis)
106
107 \li "Y_LABEL" / value (text string): the label of the y ordinates axis.
108
109 \li "Y_DATA" / value (base64 QByteArray): the array of Base64-encoded intensity
110 values (y axis)
111 \endlist
112
113 \e{end_map}
114
115 All the data above fully qualify a mass spectrum in order to display it as a
116 trace (that is, a graph) in a mass spectrometric data visualization/exploration
117 program like MineXpert, from \l{http://www.msxpertsuite.org/}.
118 */
119
120
121 /*!
122 \brief Constructs a MassDataCborMassSpectrumHandler instance setting parent to
123 \a parent_p.
124 */
125 MassDataCborMassSpectrumHandler::MassDataCborMassSpectrumHandler(
126 QObject *parent_p)
127 : MassDataCborBaseHandler(parent_p)
128 {
129 qDebug() << "Current thread:" << QThread::currentThread()
130 << "is main thread:" << QThread::isMainThread();
131 }
132
133 /*!
134 \brief Constructs a MassDataCborMassSpectrumHandler instance initializing the
135 member trace to \a trace and setting parent to \a parent_p.
136
137 \note The \a trace is considered the mass spectrum data to be streamed to a file
138 as a CBOR container.
139 */
140 MassDataCborMassSpectrumHandler::MassDataCborMassSpectrumHandler(
141 QObject *parent_p, const pappso::Trace &trace)
142 : MassDataCborBaseHandler(parent_p), m_trace(trace)
143 {
144 }
145
146 /*!
147 \brief Destructs the MassDataCborMassSpectrumHandler instance.
148 */
149 MassDataCborMassSpectrumHandler::~MassDataCborMassSpectrumHandler()
150 {
151 }
152
153 /*!
154 \brief Decodes the data stream from \a reader_sp.
155
156 The CBOR container consists in a succession of map key/value pairs.
157 The very first key/value pair is
158 "DATA_TYPE"/Enums::MassDataType::MASS_SPECTRUM. The following key/value pairs
159 contain data like the title of the mass spectrum, the color for tracing the
160 mass spectrum, the x (m/z) data and the y (intensity) data. See the class
161 documentation for details.
162
163 Data decoded from the \a reader_sp stream are set to member data for later
164 consumption.
165
166 Returns true if the decoding was successful, false otherwise.
167
168 \sa writeFile()
169 */
170 bool
171 MassDataCborMassSpectrumHandler::decodeStream(QCborStreamReaderSPtr &reader_sp)
172 {
173 // See QDoc class description above
174
175 while(reader_sp->lastError() == QCborError::NoError && reader_sp->hasNext())
176 {
177 QCborStreamReader::Type type = reader_sp->type();
178 // qDebug() << "Type is:" << type;
179
180 if(type == QCborStreamReader::UnsignedInteger)
181 {
182 // In this format, the QCborStreamReader::UnsignedInteger datum is
183 // only got as the very first data bit in the file. This bit of
184 // information is a quint64 representing
185 // Enums::MassDataType::MASS_SPECTRUM.
186
187 // Now check that the read value actually corresponds to the expected
188 // mass data type for which this object is working!
189
190 // The m_currentKey value was set in a previous loop iteration below
191 // where the type of data read from the stream was the string key
192 // "DATA_TYPE" and we now, for this loop iteratation get the value
193 // for that key.
194 if(m_currentKey == "DATA_TYPE")
195 {
196 // quint64 data_type = 0;
197 // data_type = reader_sp->toUnsignedInteger();
198 // qDebug() << "The mass data type:" << data_type;
199
200 if(static_cast<Enums::MassDataType>(
201 reader_sp->toUnsignedInteger()) !=
202 Enums::MassDataType::MASS_SPECTRUM)
203 {
204 qDebug()
205 << "The expected DATA_TYPE::MASS_SPECTRUM was not found.";
206 return false;
207 }
208
209 m_massDataType = Enums::MassDataType::MASS_SPECTRUM;
210
211 m_currentKey = QString();
212 }
213
214 // qDebug() << "At this point, check if it has next:"
215 //<< reader_sp->hasNext();
216
217 // We had what we wanted, go to next.
218 reader_sp->next();
219 }
220 // End of
221 // if(type == QCborStreamReader::UnsignedInteger)
222 else if(type == QCborStreamReader::String)
223 {
224 // Read a CBOR string, concatenating all
225 // the chunks into a single string.
226 QString str;
227 auto chunk = reader_sp->readString();
228 while(chunk.status == QCborStreamReader::Ok)
229 {
230 str += chunk.data;
231 chunk = reader_sp->readString();
232 }
233
234 if(chunk.status == QCborStreamReader::Error)
235 {
236 // handle error condition
237 qDebug() << "There was an error reading string chunk.";
238 str.clear();
239 }
240
241 // qDebug() << "The string that was read:" << str;
242
243 // If the current key is empty, this string value is a new key or tag.
244 // Otherwise, it's a value.
245
246 if(m_currentKey.isEmpty())
247 {
248 // qDebug() << "Setting m_currentKey:" << str;
249 m_currentKey = str;
250 }
251 else
252 {
253 // Depending on what we had already, we will set the proper member
254 // datum.
255 if(m_currentKey == "TITLE")
256 {
257 // qDebug() << "Setting m_title:" << str;
258 m_title = str;
259 }
260
261 if(m_currentKey == "X_LABEL")
262 {
263 // qDebug() << "Setting m_xLabel:" << str;
264
265 m_xLabel = str;
266 }
267
268 if(m_currentKey == "Y_LABEL")
269 {
270 // qDebug() << "Setting m_yLabel:" << str;
271 m_yLabel = str;
272 }
273
274 // At this point we can reset m_currentKey.
275 m_currentKey = QString();
276 }
277 }
278
279 // The ByteArray data for X_DATA and Y_DATA.
280 // They are preceded by the keys "X_DATA" and "Y_DATA" respectively.
281
282 else if(type == QCborStreamReader::ByteArray)
283 {
284 // Read a byte array. That could be either the X_DATA or the Y_DATA.
285
286 QByteArray array;
287
288 auto chunk = reader_sp->readByteArray();
289
290 while(chunk.status == QCborStreamReader::Ok)
291 {
292 array.append(chunk.data);
293 chunk = reader_sp->readByteArray();
294 }
295
296 if(m_currentKey == "X_DATA")
297 {
298 m_xBase64Data = array;
299 }
300 else if(m_currentKey == "Y_DATA")
301 {
302 m_yBase64Data = array;
303 }
304 else if(m_currentKey == "TRACE_COLOR")
305 {
306 m_colorByteArray = array;
307 }
308 else
309 {
310 qDebug() << "Error in the data read from file.";
311 return false;
312 }
313
314 m_currentKey = QString();
315 }
316 else if(type == QCborStreamReader::Map)
317 {
318
319 reader_sp->enterContainer();
320
321 // Read elements until end of map is reached
322 bool res = decodeStream(reader_sp);
323 if(res)
324 reader_sp->leaveContainer();
325 else
326 return false;
327 }
328 else
329 {
330 // Ignore all other types, go to the next element
331 reader_sp->next();
332 }
333 }
334
335 if(reader_sp->lastError() != QCborError::NoError)
336 qDebug() << "There was an error: " << reader_sp->lastError()
337 << "Returning false.";
338
339 // Return true if there were no errors
340 // qDebug() << "Returning: " << !reader_sp->lastError();
341
342 return !reader_sp->lastError();
343 }
344
345 /*!
346 \brief Writes member data to file \a output_file_name.
347
348 \note The member data are packed in a CBOR container and streamed to the file
349 as a set of key/value pairs that form the CBOR map being streamed to the file.
350
351 Returns true if the operation was successful, false, otherwise.
352 */
353 bool
354 MassDataCborMassSpectrumHandler::writeFile(const QString &output_file_name)
355 {
356 QString local_file_name = output_file_name;
357
358 if(local_file_name.isEmpty())
359 local_file_name = m_outputFileName;
360
361 QFile file(local_file_name);
362
363 bool res = file.open(QIODevice::WriteOnly);
364
365 if(!res)
366 {
367 qDebug() << "Failed to open the file for write.";
368 return false;
369 }
370
371 msp_writer = std::make_shared<QCborStreamWriter>(&file);
372
373 // qDebug() << "Now writing data to file:" << local_file_name;
374
375 // Start of array containing all mapped items
376 msp_writer->startMap(7);
377
378 // First the Enums::MassDataType: this is the very *first* data bit in the
379 // data stream.
380 msp_writer->append("DATA_TYPE");
381 msp_writer->append(static_cast<quint64>(Enums::MassDataType::MASS_SPECTRUM));
382
383 msp_writer->append("TITLE");
384 msp_writer->append(m_title);
385
386 msp_writer->append("TRACE_COLOR");
387 msp_writer->append(m_colorByteArray);
388
389 msp_writer->append("X_LABEL");
390 msp_writer->append("m/z");
391
392 msp_writer->append("X_DATA");
393 msp_writer->append(m_trace.xAsBase64Encoded());
394
395 msp_writer->append("Y_LABEL");
396 msp_writer->append("intensity");
397
398 msp_writer->append("Y_DATA");
399 msp_writer->append(m_trace.yAsBase64Encoded());
400
401 msp_writer->endMap();
402 // Close the map now.
403
404 file.close();
405
406 return res;
407 }
408
409 /*!
410 \brief Writes member data to byte array \a byte_array.
411
412 \note The member data are packed in a CBOR container and streamed to the byte
413 array as a set of key/value pairs that form the CBOR map being streamed to the
414 byte array.
415 */
416 void
417 MassDataCborMassSpectrumHandler::writeByteArray(QByteArray &byte_array)
418 {
419 msp_writer = std::make_shared<QCborStreamWriter>(&byte_array);
420
421 // qDebug() << "Now writing data byte array.";
422
423 // Start of array containing all mapped items
424 msp_writer->startMap(7);
425
426 // First the Enums::MassDataType: this is the very *first* data bit in the
427 // data stream.
428 msp_writer->append("DATA_TYPE");
429 msp_writer->append(static_cast<quint64>(Enums::MassDataType::MASS_SPECTRUM));
430
431 msp_writer->append("TITLE");
432 msp_writer->append(m_title);
433
434 msp_writer->append("TRACE_COLOR");
435 msp_writer->append(m_colorByteArray);
436
437 msp_writer->append("X_LABEL");
438 msp_writer->append("m/z");
439
440 msp_writer->append("X_DATA");
441 msp_writer->append(m_trace.xAsBase64Encoded());
442
443 msp_writer->append("Y_LABEL");
444 msp_writer->append("intensity");
445
446 msp_writer->append("Y_DATA");
447 msp_writer->append(m_trace.yAsBase64Encoded());
448
449 msp_writer->endMap();
450 // Close the map now.
451 }
452
453 /*!
454 \brief Reads data from the byte array \a byte_array.
455
456 The data in \a byte_array are packed in a CBOR container. These data have
457 typically travelled over the network and the receiving end needs to unpack the
458 data in that \a byte_array. This is what this function does.
459
460 The data unpacked from \a byte_array are stored in this object member variables
461 for later consumption by the user of this function.
462
463 A CBOR stream reader is allocated and set to decode \a byte_array. The actual
464 decoding occurs in \l{decodeStream()}.
465
466 Returns true if the operations was successful, false otherwise.
467 */
468 bool
469 MassDataCborMassSpectrumHandler::readByteArray(const QByteArray &byte_array)
470 {
471 msp_reader = std::make_shared<QCborStreamReader>(byte_array);
472
473 bool res = decodeStream(msp_reader);
474
475 // qDebug() << "After finishing the read, m_title is:" << m_title
476 //<< "and mass data type is:" << static_cast<quint64>(m_massDataType);
477
478 // At this point we need to decode the arrays and with the data initialize the
479 // pappso::Trace.
480
481 QByteArray x_array;
482 QByteArray y_array;
483
484 QByteArray::FromBase64Result decoding_result = QByteArray::fromBase64Encoding(
485 m_xBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);
486
487 if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
488 x_array = decoding_result.decoded;
489 else
490 {
491 qDebug() << "Failed to decode the " << m_xLabel << "data";
492 return false;
493 }
494
495 decoding_result = QByteArray::fromBase64Encoding(
496 m_yBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);
497
498 if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
499 y_array = decoding_result.decoded;
500 else
501 {
502 qDebug() << "Failed to decode the " << m_yLabel << "data";
503 return false;
504 }
505
506 m_trace.clear();
507
508 m_trace.initialize(QString(x_array), QString(y_array));
509
510 return res;
511 }
512
513 /*!
514 \brief Reads the data in file \a input_file_name by decoding the data stream
515 read from it.
516 This function first decodes the data in the CBOR container stored in the
517 file. Then it decodes the Base64-encoded x and y axis data into the member
518 pappso::Trace object.
519
520 Returns true if the operation was successful, false, otherwise.
521
522 \sa decodeStream(), readByteArray()
523 */
524 bool
525 MassDataCborMassSpectrumHandler::readFile(const QString &input_file_name)
526 {
527 // The format for this kind of CBOR pappso::Trace data is the following:
528
529 // First the quint64 that represents Enums::MassDataType. In our specific
530 // case, that must be Enums::MassDataType::MASS_SPECTRUM.
531
532 // Then there is the title of data, which is according to the specification:
533 //
534 // Major type 3: a text string, specifically a string of Unicode characters
535 // that is encoded as UTF-8 (that is, not a QByteArray, but a QString).
536
537 // Then there is a MAP with the following key/value pairs:
538
539 // start_map
540
541 // "X_LABEL" / value (text string)
542 // "Y_LABEL" / value (text string)
543 //
544 // "X_DATA" / value (base64 ByteArray)
545 // "Y_DATA" / value (base64 ByteArray)
546
547 // end_map
548
549
550 QString local_file_name = input_file_name;
551
552 if(local_file_name.isEmpty())
553 local_file_name = m_inputFileName;
554
555 QFileInfo file_info(local_file_name);
556
557 if(!file_info.exists())
558 {
559 qDebug() << "File not found.";
560 return false;
561 }
562
563 QFile file(local_file_name);
564
565 bool res = file.open(QIODevice::ReadOnly);
566
567 if(!res)
568 {
569 qDebug() << "Failed to open the file for read.";
570 return false;
571 }
572
573 // qDebug() << "Now starting the CBOR data read.";
574
575 msp_reader = std::make_shared<QCborStreamReader>(&file);
576
577 res = decodeStream(msp_reader);
578
579 file.close();
580
581 // qDebug() << "After finishing the read, m_title is:" << m_title
582 //<< "and mass data type is:" << static_cast<quint64>(m_massDataType);
583
584 // At this point we need to decode the arrays and with the data initialize the
585 // pappso::Trace.
586
587 QByteArray x_array;
588 QByteArray y_array;
589
590 QByteArray::FromBase64Result decoding_result = QByteArray::fromBase64Encoding(
591 m_xBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);
592
593 if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
594 x_array = decoding_result.decoded;
595 else
596 {
597 qDebug() << "Failed to decode the " << m_xLabel << "data";
598 return false;
599 }
600
601 decoding_result = QByteArray::fromBase64Encoding(
602 m_yBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);
603
604 if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok)
605 y_array = decoding_result.decoded;
606 else
607 {
608 qDebug() << "Failed to decode the " << m_yLabel << "data";
609 return false;
610 }
611
612 m_trace.clear();
613
614 m_trace.initialize(QString(x_array), QString(y_array));
615
616 return res;
617 }
618
619 /*!
620 \brief Sets the abscissae \a label for the mass spectrum data.
621 */
622 void
623 MassDataCborMassSpectrumHandler::setXLabel(const QString &label)
624 {
625 m_xLabel = label;
626 }
627
628 /*!
629 \brief Returns the abscissae label for the mass spectrum data.
630 */
631 QString
632 MassDataCborMassSpectrumHandler::getXLabel() const
633 {
634 return m_xLabel;
635 }
636
637 /*!
638 \brief Sets the ordinates \a label for the mass spectrum data.
639 */
640 void
641 MassDataCborMassSpectrumHandler::setYLabel(const QString &label)
642 {
643 m_yLabel = label;
644 }
645
646 /*!
647 \brief Returns the ordinates label for the mass spectrum data.
648 */
649 QString
650 MassDataCborMassSpectrumHandler::getYLabel() const
651 {
652 return m_yLabel;
653 }
654
655 /*!
656 \brief Sets the mass spectrum data to \a trace.
657 */
658 void
659 MassDataCborMassSpectrumHandler::setTrace(const pappso::Trace &trace)
660 {
661 m_trace = trace;
662 }
663
664 /*!
665 \brief Returns the mass spectrum data.
666 */
667 pappso::Trace
668 MassDataCborMassSpectrumHandler::getTrace() const
669 {
670 return m_trace;
671 };
672
673 /*!
674 \brief Clears the mass spectrum data.
675 */
676 void
677 MassDataCborMassSpectrumHandler::clearTrace()
678 {
679 m_trace.clear();
680 }
681
682 /*!
683 \brief Sets the color with which the mass spectrum data must be displayed. The
684 color i encoded as the \a color_byte_array byte array.
685 */
686 void
687 MassDataCborMassSpectrumHandler::setTraceColor(
688 const QByteArray &color_byte_array)
689 {
690 m_colorByteArray = color_byte_array;
691 }
692
693 /*!
694 \brief Returns the color with which the mass spectrum data must be displayed.
695 */
696 QByteArray
697 MassDataCborMassSpectrumHandler::getTraceColor() const
698 {
699 return m_colorByteArray;
700 }
701
702
703 } // namespace libXpertMassCore
704
705
706 } // namespace MsXpS
707