libpappsomspp
Library for mass spectrometry
Loading...
Searching...
No Matches
massspectraceplotwidget.cpp
Go to the documentation of this file.
1/* This code comes right from the msXpertSuite software project.
2 *
3 * msXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * END software license
23 */
24
25
26/////////////////////// StdLib includes
27#include <vector>
28
29
30/////////////////////// Qt includes
31#include <QVector>
32
33
34/////////////////////// Local includes
35
36// For the proton mass
37#include "../../core/types.h"
38
41
42
44 qRegisterMetaType<pappso::MassSpecTracePlotContext>("pappso::MassSpecTracePlotContext");
45
47 qRegisterMetaType<pappso::MassSpecTracePlotContext *>("pappso::MassSpecTracePlotContext *");
48
49
50namespace pappso
51{
52
54{
56 m_context.m_dataKind = Enums::DataKind::mz;
57
58 // qDebug() << "Data kind:" << static_cast<int>(m_context.m_dataKind);
59}
60
62 const QString &x_axis_label,
63 const QString &y_axis_label)
64 : BaseTracePlotWidget(parent, x_axis_label, y_axis_label)
65{
66 // Set the base context to be of kind Enums::DataKind::mz;
67
69 m_context.m_dataKind = Enums::DataKind::mz;
70
71 // qDebug() << "Data kind:" << static_cast<int>(m_context.m_dataKind);
72}
73
74
78
79
80//! Set the \c m_pressedKeyCode to the key code in \p event.
81void
83{
84 // qDebug() << "ENTER";
86
87 // Before working on the various data belonging to the base context, we need
88 // to get it from the base class and refresh our local context with it.
90
91 // qDebug() << "Going to emit keyPressEventSignal(m_context);";
92
93 emit keyPressEventSignal(event, m_context);
94}
95
96
97//! Handle specific key codes and trigger respective actions.
98void
100{
101 // qDebug() << "ENTER";
103
104 // Before working on the various data belonging to the base context, we need
105 // to get it from the base class and refresh our local context with it.
107
108 emit keyReleaseEventSignal(event, m_context);
109}
110
111
112//! Handle mouse movements, in particular record all the last visited points.
113/*!
114
115 This function is reponsible for storing at each time the last visited point
116 in the graph. Here, point is intended as any x/y coordinate in the plot
117 widget viewport, not a graph point.
118
119 The stored values are then the basis for a large set of calculations
120 throughout all the plot widget.
121
122 \param pointer to QMouseEvent from which to retrieve the coordinates of the
123 visited viewport points.
124 */
125void
127{
128 // Before working on the various data belonging to the base context, we need
129 // to get it from the base class and refresh our local context with it.
131
133}
134
135
136void
138{
139 // Before working on the various data belonging to the base context, we need
140 // to get it from the base class and refresh our local context with it.
142
144
145 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
146}
147
148
149void
151{
153
154 // Before working on the various data belonging to the base context, we need
155 // to get it from the base class and refresh our local context with it.
157
158 if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
159 {
160 if(!m_context.m_isMeasuringDistance)
161 return;
162
163 // qDebug() << "Mouse Buttons at Mouse press:" << m_context.m_mouseButtonsAtMousePress;
164
165 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
166
167 deconvolute();
169 }
170}
171
172
173//! Record the clicks of the mouse.
174void
176{
177 // qDebug() << "Entering";
178
179 // Before working on the various data belonging to the base context, we need
180 // to get it from the base class and refresh our local context with it.
182
184
185 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
186
187 emit mousePressEventSignal(event, m_context);
188
189 // qDebug() << "Exiting after having emitted mousePressEventSignal with context:"
190 // << m_context.toString();
191}
192
193
194//! React to the release of the mouse buttons.
195void
197{
198 // qDebug() << "Entering";
199
200 // Before working on the various data belonging to the base context, we need
201 // to get it from the base class and refresh our local context with it.
203
205
206 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
207
208 if(!m_context.m_isMouseDragging)
209 {
210 // Since we were not dragging, by essence we were not measuring
211 // distances and _a fortiori_ we were not deconvoluting. So
212 // reset deconvolution data in the context.
213
214 m_context.resetDeconvolutionData();
215 }
216
218
219 // qDebug() << "Exiting after having emitted mouseReleaseEventSignal with context:"
220 // << m_context.toString();
221}
222
223
226{
227 // BasePlotWidget has a member m_context of type BasePlotContext.
228 // Here we also have a m_context *distinct* member that is of type
229 // MassSpecTracePlotContext.
230
231 // While MassSpecTracePlotContext is derived from BasePlotContext, the two
232 // m_context members are distinct and there are lots of housekeeping data that
233 // are managed by the parent BasePlotWidget class in its m_context member
234 // *independently* of what we have in the ::BasePlotContext part of our
235 // m_context member that is of type MassSpecTracePlotContext. We thus need to
236 // resynchronize the data from BasePlotWidget::m_context to our m_context.
237
238 // qDebug().noquote() << "The base plot context:" <<
239 // BasePlotWidget::m_context.toString();
240
241
242 // qDebug() << "Right before refreshing the context, the current m_lastZ value:"
243 // << m_context.m_lastZ;
244
246
247
248 // qDebug() << "Right after refreshing the context, the current m_lastZ value:"
249 // << m_context.m_lastZ;
250
251 // qDebug().noquote() << "After refreshing the base context, base context:"
252 // << m_context.toString();
253
254 return m_context;
255}
256
257
258void
260{
261 m_chargeMinimalFractionalPart = charge_fractional_part;
262}
263
264
265double
270
271
272void
277
278
279int
284
285
286//! Deconvolute the mass peaks into charge and molecular mass.
287bool
289{
290
291 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
292
293 // There are two situations: when the user is deconvoluting on the
294 // basis of the distance between two consecutive peaks of a same
295 // isotopic cluster or when the user deconvolutes on the basis of two
296 // different charged-stated peaks that belong to the same envelope.
297
298 // We can tell the difference because in the first case the xDelta
299 // should be less than 1. In the other case, of course the difference
300 // is much greater than 1.
301
302 // In order to do the deconvolutions, we need to know what is the tolerance
303 // on the fractional part of the deconvoluted charge value. This value is set
304 // in the parent window's double spin box.
305
306 if(fabs(m_context.m_xDelta) >= 0 && fabs(m_context.m_xDelta) <= 1.1)
307 {
308 // qDebug() << "m_xDelta:" << m_context.m_xDelta
309 //<< "trying isotope-based deconvolution.";
310
312 }
313
314 // If not deconvoluting on the basis of the isotopic cluster, then:
315
317}
318
319
320//! Deconvolute the mass peaks into charge and molecular mass.
321/*!
322
323 This is one of two methods to deconvolute mass data into a charge value and
324 a Mr value. The method implemented in this function is based on the charge
325 state envelope offered by the mass spectrum (most often for polymers of a
326 reasonable size).
327
328 \param span value representing the number of peaks of the charge state
329 envelope that are spanned by the user selection. Defaults to 1, that is, the
330 span encompasses two \e consecutive mass peaks of a given charge state
331 envelope.
332
333 The z charge that is computed is for the first peak that was clicked
334 in the trace plot, and thus the Mr value that is returned is calculated
335 from the m/z value under the peak that was clicked first.
336
337 Set m_lastMz, m_lastZ and m_lastMass.
338
339 \return true if the deconvolution could be performed, false otherwise.
340 */
341bool
343{
344 // We assume that we are dealing with two successive (if span is 1) mass
345 // peaks belonging to a given charge state family.
346
347 // We call span the number of intervals in a given charge state envelope
348 // that separate the initial peak (lowerMz) from the last peak (upperMz).
349 // That parameter defaults to 1, that is the two peaks are immediately
350 // consecutive, that is, there is only one interval.
351
352 // qDebug() << "The span is:" << span;
353
354 // The m_context ranges are inherently sorted. This is not something that
355 // is favorable to us because we need to know what is the direction of the
356 // drag. That should be configurable, but we can establish that by
357 // default, the z value will be calculated for the mass peak at which
358 // the mouse drag movement started.
359
360 // These two values reflect a m/z range:
361 // startMz and curMz are always sorted (startMz < curMz)
362 double startMz = m_context.m_xRegionRangeStart;
363 double curMz = m_context.m_xRegionRangeStop;
364
365 // qDebug() << "startMz:" << startMz << "curMz:" << curMz;
366
367 if(startMz == curMz)
368 {
369 m_context.m_lastZ = std::numeric_limits<quint16>::max();
370 m_context.m_lastMz = qQNaN();
371 m_context.m_lastTicIntensity = qQNaN();
372 m_context.m_lastMr = qQNaN();
373
374 // qDebug() << "Start and Cur are the same, no deconvolution possible.";
375 return false;
376 }
377
378 // After much discussing, the agreement we reached with users is that the
379 // m/z value that is reported has to be that of the clicked peak, that is
380 // the start point of the mouse cursor drag operation. The z charge value
381 // has to be related to that m/z value.
382
383 double chargeTemp = ((startMz * span) - span) / (curMz - startMz);
384 // This is the charge of the least charged peak, that is, by necessity
385 // the charge of the peak on the right (curMz, because the values
386 // are sorted).
387
388 // Make a judicious roundup.
389
390 double chargeIntPart;
391 double chargeFracPart = modf(chargeTemp, &chargeIntPart);
392
393 // When calculating the charge of the ion, very rarely does it provide a
394 // perfect integer value. Most often (if deconvolution is for bona fide
395 // peaks belonging to the same charge state envelope) that value is with
396 // either a large fractional part or a very small fractional part. What we
397 // test here, it that fractional part. If it is greater than
398 // m_chargeMinimalFractionalPart, then we simply round up to the next integer
399 // value (that is, chargeIntPart = 27 and chargeFracPart 0.995, then we
400 // set charge to 28). If it is lesser or equal to (1 -
401 // m_chargeMinimalFractionalPart /* that is >= 0.01 */, then we let
402 // chargeIntPart unmodified (that is, chargeIntPart = 29 and
403 // chargeFracPart 0.01, then we set charge to 29). If chargeFracPart is in
404 // between (1 - m_chargeMinimalFractionalPart) and
405 // m_chargeMinimalFractionalPart, then we consider that the peaks do not
406 // belong to the same charge state envelope.
407
408 // qDebug() << "Charge:" << chargeIntPart << "Charge fractional part: " << chargeFracPart;
409
410
411 if(chargeFracPart >= (1 - m_chargeMinimalFractionalPart /* that is >= 0.01 */) &&
412 chargeFracPart <= m_chargeMinimalFractionalPart /* that is <= 0.99 */)
413 {
414 m_context.m_lastZ = std::numeric_limits<quint16>::max();
415 m_context.m_lastMz = qQNaN();
416 m_context.m_lastTicIntensity = qQNaN();
417 m_context.m_lastMr = qQNaN();
418
419 // qDebug() << "Not a charge state family peak,"
420 // << "returning from deconvoluteChargeState";
421
422 return false;
423 }
424
425 if(chargeFracPart > m_chargeMinimalFractionalPart)
426 m_context.m_lastZ = chargeIntPart + 1;
427 else
428 m_context.m_lastZ = chargeIntPart;
429
430 // qDebug() << "Initially computed m_lastZ as " << m_context.m_lastZ;
431
432 // We know the that formula above computed the charge of the least charged
433 // ion, that corresponds to the greater m/z value of the range: curMz.
434
435 // Now, if the user did click on that least-charged peak, nothing is
436 // to be modified. But, if the user did click on the most-charged
437 // peak, that is on startMz, the we need to fix the values:
438
439 if(static_cast<int>(m_context.m_dragDirections) & static_cast<int>(DragDirections::RIGHT_TO_LEFT))
440 {
441 // qDebug() << "Dragging from right to left, that is from higher m/z value.";
442 m_context.m_lastMz = curMz;
443 }
444 else if(static_cast<int>(m_context.m_dragDirections) &
445 static_cast<int>(DragDirections::LEFT_TO_RIGHT))
446 {
447 // qDebug() << "Dragging from left to right, we need to report"
448 // << "the mass for the left peak, that is for z+span and lower m/z value.";
449 m_context.m_lastZ += span;
450 m_context.m_lastMz = startMz;
451 }
452
453 // qDebug() << "Final m_lastZ is " << m_context.m_lastZ << "m/z: m_context.m_lastMz";
454
455 m_context.m_lastMr = (m_context.m_lastMz * m_context.m_lastZ) - (m_context.m_lastZ * MPROTON);
456
457 // qDebug() << "startMz:" << QString("%1").arg(startMz, 0, 'f', 6)
458 // << "curMz:" << QString("%1").arg(curMz, 0, 'f', 6)
459 // << "m_lastMz (make computation for this startMz):"
460 // << QString("%1").arg(m_context.m_lastMz, 0, 'f', 6)
461 // << "m_lastZ:" << QString("%1").arg(m_context.m_lastZ)
462 // << "m_lastMass:" << QString("%1").arg(m_context.m_lastMr, 0, 'f', 6);
463
464 // The m_context was refreshed with the base class context in the calling
465 // chain.
467
468 return true;
469}
470
471
472//! Deconvolute the mass peaks into charge and molecular mass.
473/*!
474
475 This is one of two methods to deconvolute mass data into a charge value and
476 a Mr value. The method implemented in this function is based on the distance
477 that separates two immediately consecutive peaks of an isotopic cluster.
478 This method can be used as long as the instrument produced data with a
479 resolution sufficient to separate reasonably well the different peaks of an
480 isotopic cluster.
481
482 Set m_lastMz, m_lastZ and m_lastMass.
483
484 \return true if the deconvolution could be performed, false otherwise.
485 */
486bool
488{
489 // The m_context ranges are inherently sorted. This is not something that
490 // is favorable to us because we need to know what is the direction of the
491 // drag. That should be configurable, but we can establish that by
492 // default, the z value will be calculated for the mass peak at which
493 // the mouse drag movement started.
494
495 double startMz = m_context.m_xRegionRangeStart;
496 double curMz = m_context.m_xRegionRangeStop;
497
498 // qDebug() << "startMz:" << startMz << "curMz:" << curMz;
499
500 if(startMz == curMz)
501 {
502 m_context.m_lastZ = std::numeric_limits<quint16>::max();
503 m_context.m_lastMz = qQNaN();
504 m_context.m_lastTicIntensity = qQNaN();
505 m_context.m_lastMr = qQNaN();
506
507 return false;
508 }
509
510 // After much discussion with people who perform mass data analysis for
511 // heavy isotope fully labelled analytes, it was decided that the Mr
512 // value thas is returned is for the peak from which the drag operation
513 // was performed. This is an analogous way of computing Mr as that used
514 // in the charge state envelope method.
515
516
517 double chargeTemp = 1 / fabs(m_context.m_xDelta);
518
519 // qDebug() << qSetRealNumberPrecision(6) << "curMz - startMz = " << fabs(curMz - startMz)
520 // << "while m_context.m_xDelta = " << m_context.m_xDelta;
521
522 // Sanity check
523 if(fabs(m_context.m_xDelta) != fabs(curMz - startMz))
524 qFatal() << "Programming error.";
525
526 // Make a judicious roundup.
527 double chargeIntPart;
528 double chargeFracPart = modf(chargeTemp, &chargeIntPart);
529
530 // qDebug() << "m_xDelta:" << m_context.m_xDelta
531 // << "chargeTemp:" << QString("%1").arg(chargeTemp, 0, 'f', 6)
532 // << "chargeIntPart:" << chargeIntPart
533 // << "chargeFracPart:" << QString("%1").arg(chargeFracPart, 0, 'f', 6)
534 // << "m_chargeMinimalFractionalPart:" << m_chargeMinimalFractionalPart;
535
536 if(chargeFracPart >= (1 - m_chargeMinimalFractionalPart /* that is >= 0.01 */) &&
537 chargeFracPart <= m_chargeMinimalFractionalPart /* that is <= 0.99 */)
538 {
539 m_context.m_lastZ = std::numeric_limits<quint16>::max();
540 m_context.m_lastMz = qQNaN();
541 m_context.m_lastTicIntensity = qQNaN();
542 m_context.m_lastMr = qQNaN();
543
544 // qDebug() << "Not in a isotopic cluster peak:"
545 // << "returning from deconvoluteIsotopicCluster";
546
547 return false;
548 }
549
550 if(chargeFracPart > m_chargeMinimalFractionalPart)
551 {
552 m_context.m_lastZ = chargeIntPart + 1;
553
554 // qDebug() << "chargeFracPart > m_chargeMinimalFractionalPart -> m_lastZ
555 // = "
556 //<< m_context.m_lastZ;
557 }
558 else
559 {
560 m_context.m_lastZ = chargeIntPart;
561
562 // qDebug()
563 //<< "chargeFracPart <= m_chargeMinimalFractionalPart -> m_lastZ = "
564 //<< m_context.m_lastZ;
565 }
566
567 // qDebug() << "Settled on m_lastZ: " << m_context.m_lastZ;
568
569 // Now that we have the charge in the form of an int, we can compute the
570 // Mr of the isotopic cluster peak that was under the cursor when the
571 // mouse drag started.
572
573 // If we know the drag direction, we would automatically know what is the
574 // peak that was under the cursor when the mouse drag mouvement was initiated.
575 // This is because the curMz is inherently larger than startMz because
576 // m_context.m_xRegionRangeStart and m_context.m_xRegionRangeEnd are inherently
577 // sorted (start < end).
578
579 if(static_cast<int>(m_context.m_dragDirections) & static_cast<int>(DragDirections::RIGHT_TO_LEFT))
580 {
581 // qDebug() << "Dragging from right to left, that is from higher m/z value.";
582 m_context.m_lastMz = curMz;
583 }
584 else if(static_cast<int>(m_context.m_dragDirections) &
585 static_cast<int>(DragDirections::LEFT_TO_RIGHT))
586 {
587 // qDebug() << "Dragging from left to right, we need to report"
588 // << "the mass for the left peak.";
589 m_context.m_lastMz = startMz;
590 }
591
592 m_context.m_lastMr = (m_context.m_lastMz * m_context.m_lastZ) - (m_context.m_lastZ * MPROTON);
593
594 // qDebug() << "m_lastMz :" << QString("%1").arg(m_context.m_lastMz, 0, 'f', 6)
595 // << "m_lastZ:" << QString("%1").arg(m_context.m_lastZ)
596 // << "m_lastMr:" << QString("%1").arg(m_context.m_lastMr, 0, 'f', 6);
597
598 // The m_context was refreshed with the base class context in the calling
599 // chain.
601
602 return true;
603}
604
605
606bool
608{
609
610 // m_xRangeLower and m_xRangeUpper and m_xDelta (in fabs() form) have been set
611 // during mouve movement handling. Note that the range values *are
612 // sorted*.
613
614 if(!m_context.m_xDelta || m_context.m_xDelta > 1)
615 {
616 m_context.m_lastResolvingPower = std::numeric_limits<double>::min();
617
618 return false;
619 }
620
621 // Resolving power is m/z / Delta(m/z), for singly-charged species.
622
623 // qDebug() << "Calculating the resolving power with these data:"
624 //<< "m_context.m_xRegionRangeStart: " << m_context.m_xRegionRangeStart
625 //<< "m_context.m_xRegionRangeEnd: " << m_context.m_xRegionRangeEnd
626 //<< "m_context.m_xDelta / 2: " << m_context.m_xDelta / 2;
627
628 m_context.m_lastResolvingPower =
629 (std::min<double>(m_context.m_xRegionRangeStart, m_context.m_xRegionRangeStop) +
630 (m_context.m_xDelta / 2)) /
631 m_context.m_xDelta;
632
633 // The m_context was refreshed with the base class context in the calling
634 // chain.
636
637 return true;
638}
639
640
641} // namespace pappso
virtual void keyPressEvent(QKeyEvent *event)
KEYBOARD-related EVENTS.
virtual void mouseMoveHandlerDraggingCursor(QMouseEvent *event)
virtual void mouseMoveHandlerNotDraggingCursor(QMouseEvent *event)
virtual void mousePressHandler(QMouseEvent *event)
KEYBOARD-related EVENTS.
virtual void mouseReleaseHandler(QMouseEvent *event)
virtual void mouseMoveHandler(QMouseEvent *event)
KEYBOARD-related EVENTS.
virtual void keyReleaseEvent(QKeyEvent *event)
Handle specific key codes and trigger respective actions.
BasePlotContext m_context
virtual void mouseMoveHandler(QMouseEvent *event) override
Handle mouse movements, in particular record all the last visited points.
virtual void keyReleaseEvent(QKeyEvent *event) override
Handle specific key codes and trigger respective actions.
virtual void mouseMoveHandlerNotDraggingCursor(QMouseEvent *event) override
void keyReleaseEventSignal(QKeyEvent *event, const pappso::MassSpecTracePlotContext &context)
const MassSpecTracePlotContext & refreshBaseContext() const
void resolvingPowerComputationSignal(const pappso::MassSpecTracePlotContext &context)
void mouseReleaseEventSignal(QMouseEvent *event, const pappso::MassSpecTracePlotContext &context)
bool deconvoluteChargedState(int span=1)
Deconvolute the mass peaks into charge and molecular mass.
void massDeconvolutionSignal(const pappso::MassSpecTracePlotContext &context)
void mousePressEventSignal(QMouseEvent *event, const pappso::MassSpecTracePlotContext &context)
bool deconvoluteIsotopicCluster()
Deconvolute the mass peaks into charge and molecular mass.
bool deconvolute()
Deconvolute the mass peaks into charge and molecular mass.
virtual void mousePressHandler(QMouseEvent *event) override
Record the clicks of the mouse.
void setChargeMinimalFractionalPart(double charge_fractional_part)
virtual void keyPressEvent(QKeyEvent *event) override
Set the m_pressedKeyCode to the key code in event.
void keyPressEventSignal(QKeyEvent *event, const pappso::MassSpecTracePlotContext &context)
virtual void mouseMoveHandlerDraggingCursor(QMouseEvent *event) override
virtual void mouseReleaseHandler(QMouseEvent *event) override
React to the release of the mouse buttons.
int massSpecTracePlotContextMetaTypeId
int massSpecTracePlotContextPtrMetaTypeId
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition aa.cpp:39
const pappso_double MPROTON(1.007276466879)