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{
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
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
94}
95
96
97//! Handle specific key codes and trigger respective actions.
98void
100{
101 // Before working on the various data belonging to the base context, we need
102 // to get it from the base class and refresh our local context with it.
104
106}
107
108
109//! Handle mouse movements, in particular record all the last visited points.
110/*!
111
112 This function is reponsible for storing at each time the last visited point
113 in the graph. Here, point is intended as any x/y coordinate in the plot
114 widget viewport, not a graph point.
115
116 The stored values are then the basis for a large set of calculations
117 throughout all the plot widget.
118
119 \param pointer to QMouseEvent from which to retrieve the coordinates of the
120 visited viewport points.
121 */
122void
124{
125 // Before working on the various data belonging to the base context, we need
126 // to get it from the base class and refresh our local context with it.
128
130}
131
132
133void
135{
136 // Before working on the various data belonging to the base context, we need
137 // to get it from the base class and refresh our local context with it.
139
141
142 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
143}
144
145
146void
148{
150
151 // Before working on the various data belonging to the base context, we need
152 // to get it from the base class and refresh our local context with it.
154
155 if(m_context.m_mouseButtonsAtMousePress & Qt::LeftButton)
156 {
158 return;
159
160 // qDebug() << "Mouse Buttons at Mouse press:" << m_context.m_mouseButtonsAtMousePress;
161
162 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
163
164 deconvolute();
166 }
167}
168
169
170//! Record the clicks of the mouse.
171void
172MassSpecTracePlotWidget::mousePressHandler([[maybe_unused]] QMouseEvent *event)
173{
174 // qDebug() << "Entering";
175
176 // Before working on the various data belonging to the base context, we need
177 // to get it from the base class and refresh our local context with it.
179
181
182 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
183
185
186 // qDebug() << "Exiting after having emitted mousePressEventSignal with context:"
187 // << m_context.toString();
188}
189
190
191//! React to the release of the mouse buttons.
192void
194{
195 // qDebug() << "Entering";
196
197 // Before working on the various data belonging to the base context, we need
198 // to get it from the base class and refresh our local context with it.
200
202
203 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
204
206 {
207 // Since we were not dragging, by essence we were not measuring
208 // distances and _a fortiori_ we were not deconvoluting. So
209 // reset deconvolution data in the context.
210
212 }
213
215
216 // qDebug() << "Exiting after having emitted mouseReleaseEventSignal with context:"
217 // << m_context.toString();
218}
219
220
223{
224 // BasePlotWidget has a member m_context of type BasePlotContext.
225 // Here we also have a m_context *distinct* member that is of type
226 // MassSpecTracePlotContext.
227
228 // While MassSpecTracePlotContext is derived from BasePlotContext, the two
229 // m_context members are distinct and there are lots of housekeeping data that
230 // are managed by the parent BasePlotWidget class in its m_context member
231 // *independently* of what we have in the ::BasePlotContext part of our
232 // m_context member that is of type MassSpecTracePlotContext. We thus need to
233 // resynchronize the data from BasePlotWidget::m_context to our m_context.
234
235 // qDebug().noquote() << "The base plot context:" <<
236 // BasePlotWidget::m_context.toString();
237
238
239 // qDebug() << "Right before refreshing the context, the current m_lastZ value:"
240 // << m_context.m_lastZ;
241
243
244
245 // qDebug() << "Right after refreshing the context, the current m_lastZ value:"
246 // << m_context.m_lastZ;
247
248 // qDebug().noquote() << "After refreshing the base context, base context:"
249 // << m_context.toString();
250
251 return m_context;
252}
253
254
255void
257{
258 m_chargeMinimalFractionalPart = charge_fractional_part;
259}
260
261
262double
267
268
269void
274
275
276int
281
282
283//! Deconvolute the mass peaks into charge and molecular mass.
284bool
286{
287
288 // qDebug() << "The current m_lastZ value:" << m_context.m_lastZ;
289
290 // There are two situations: when the user is deconvoluting on the
291 // basis of the distance between two consecutive peaks of a same
292 // isotopic cluster or when the user deconvolutes on the basis of two
293 // different charged-stated peaks that belong to the same envelope.
294
295 // We can tell the difference because in the first case the xDelta
296 // should be less than 1. In the other case, of course the difference
297 // is much greater than 1.
298
299 // In order to do the deconvolutions, we need to know what is the tolerance
300 // on the fractional part of the deconvoluted charge value. This value is set
301 // in the parent window's double spin box.
302
303 if(fabs(m_context.m_xDelta) >= 0 && fabs(m_context.m_xDelta) <= 1.1)
304 {
305 // qDebug() << "m_xDelta:" << m_context.m_xDelta
306 //<< "trying isotope-based deconvolution.";
307
309 }
310
311 // If not deconvoluting on the basis of the isotopic cluster, then:
312
314}
315
316
317//! Deconvolute the mass peaks into charge and molecular mass.
318/*!
319
320 This is one of two methods to deconvolute mass data into a charge value and
321 a Mr value. The method implemented in this function is based on the charge
322 state envelope offered by the mass spectrum (most often for polymers of a
323 reasonable size).
324
325 \param span value representing the number of peaks of the charge state
326 envelope that are spanned by the user selection. Defaults to 1, that is, the
327 span encompasses two \e consecutive mass peaks of a given charge state
328 envelope.
329
330 Set m_lastMz, m_lastZ and m_lastMass.
331
332 \return true if the deconvolution could be performed, false otherwise.
333 */
334bool
336{
337 // We assume that we are dealing with two successive (if span is 1) mass
338 // peaks belonging to a given charge state family.
339
340 // We call span the number of intervals in a given charge state envelope
341 // that separate the initial peak (lowerMz) from the last peak (upperMz).
342 // That parameter defaults to 1, that is the two peaks are immediately
343 // consecutive, that is, there is only one interval.
344
345 qDebug() << "The span is:" << span;
346
347 // The m_context ranges are inherently sorted. This is not something that
348 // is favorable to us because we need to know what is the direction of the
349 // drag. That should be configurable, but we can establish that by
350 // default, the z value will be calculated for the mass peak at which
351 // the mouse drag movement started.
352
353 // These two values reflect a m/z range:
354 // startMz and curMz are always sorted (startMz < curMz)
355 double startMz = m_context.m_xRegionRangeStart;
356 double curMz = m_context.m_xRegionRangeEnd;
357
358 qDebug() << "startMz:" << startMz << "curMz:" << curMz;
359
360 if(startMz == curMz)
361 {
362 m_context.m_lastZ = std::numeric_limits<quint16>::max();
363 m_context.m_lastMz = qQNaN();
365 m_context.m_lastMr = qQNaN();
366
367 qDebug() << "Start and Cur are the same, no deconvolution possible.";
368 return false;
369 }
370
371 // After much discussing, the agreement we reached with users is that the
372 // m/z value that is reported has to be that of the clicked peak, that is
373 // the start point of the mouse cursor drag operation. The z charge value
374 // has to be related to that m/z value.
375
376 double chargeTemp = ((startMz * span) - span) / (curMz - startMz);
377 // This is the charge of the least charged peak, that is, by necessity
378 // the charge of the peak on the right (curMz, because the values
379 // are sorted).
380
381 // Make a judicious roundup.
382
383 double chargeIntPart;
384 double chargeFracPart = modf(chargeTemp, &chargeIntPart);
385
386 // When calculating the charge of the ion, very rarely does it provide a
387 // perfect integer value. Most often (if deconvolution is for bona fide
388 // peaks belonging to the same charge state envelope) that value is with
389 // either a large fractional part or a very small fractional part. What we
390 // test here, it that fractional part. If it is greater than
391 // m_chargeMinimalFractionalPart, then we simply round up to the next integer
392 // value (that is, chargeIntPart = 27 and chargeFracPart 0.995, then we
393 // set charge to 28). If it is lesser or equal to (1 -
394 // m_chargeMinimalFractionalPart /* that is >= 0.01 */, then we let
395 // chargeIntPart unmodified (that is, chargeIntPart = 29 and
396 // chargeFracPart 0.01, then we set charge to 29). If chargeFracPart is in
397 // between (1 - m_chargeMinimalFractionalPart) and
398 // m_chargeMinimalFractionalPart, then we consider that the peaks do not
399 // belong to the same charge state envelope.
400
401 // qDebug() << "Charge:" << chargeIntPart << "Charge fractional part: " << chargeFracPart;
402
403
404 if(chargeFracPart >= (1 - m_chargeMinimalFractionalPart /* that is >= 0.01 */) &&
405 chargeFracPart <= m_chargeMinimalFractionalPart /* that is <= 0.99 */)
406 {
407 m_context.m_lastZ = std::numeric_limits<quint16>::max();
408 m_context.m_lastMz = qQNaN();
410 m_context.m_lastMr = qQNaN();
411
412 // qDebug() << "Not a charge state family peak,"
413 // << "returning from deconvoluteChargeState";
414
415 return false;
416 }
417
418 if(chargeFracPart > m_chargeMinimalFractionalPart)
419 m_context.m_lastZ = chargeIntPart + 1;
420 else
421 m_context.m_lastZ = chargeIntPart;
422
423 qDebug() << "Initially computed m_lastZ as " << m_context.m_lastZ;
424
425 // We know the that formula above computed the charge of the least charged
426 // ion, that corresponds to the greater m/z value of the range: curMz.
427
428 // Now, if the user did click on that least-charged peak, nothing is
429 // to be modified. But, if the user did click on the most-charged
430 // peak, that is on startMz, the we need to fix the values:
431
432 if(static_cast<int>(m_context.m_dragDirections) & static_cast<int>(DragDirections::RIGHT_TO_LEFT))
433 {
434 qDebug() << "Dragging from right to left, that is from higher m/z value.";
435 m_context.m_lastMz = curMz;
436 }
437 else if(static_cast<int>(m_context.m_dragDirections) &
438 static_cast<int>(DragDirections::LEFT_TO_RIGHT))
439 {
440 qDebug() << "Dragging from left to right, we need to report"
441 << "the mass for the left peak, that is for z+span and lower m/z value.";
442 m_context.m_lastZ += span;
443 m_context.m_lastMz = startMz;
444 }
445
446 qDebug() << "Final m_lastZ is " << m_context.m_lastZ << "m/z: m_context.m_lastMz";
447
449
450 qDebug() << "startMz:" << QString("%1").arg(startMz, 0, 'f', 6)
451 << "curMz:" << QString("%1").arg(curMz, 0, 'f', 6)
452 << "m_lastMz (make computation for this startMz):"
453 << QString("%1").arg(m_context.m_lastMz, 0, 'f', 6)
454 << "m_lastZ:" << QString("%1").arg(m_context.m_lastZ)
455 << "m_lastMass:" << QString("%1").arg(m_context.m_lastMr, 0, 'f', 6);
456
457 // The m_context was refreshed with the base class context in the calling
458 // chain.
460
461 return true;
462}
463
464
465//! Deconvolute the mass peaks into charge and molecular mass.
466/*!
467
468 This is one of two methods to deconvolute mass data into a charge value and
469 a Mr value. The method implemented in this function is based on the distance
470 that separates two immediately consecutive peaks of an isotopic cluster.
471 This method can be used as long as the instrument produced data with a
472 resolution sufficient to separate reasonably well the different peaks of an
473 isotopic cluster.
474
475 Set m_lastMz, m_lastZ and m_lastMass.
476
477 \return true if the deconvolution could be performed, false otherwise.
478 */
479bool
481{
482
483 double startMz = m_context.m_xRegionRangeStart;
484 double curMz = m_context.m_xRegionRangeEnd;
485
486 qDebug() << "startMz:" << startMz << "curMz:" << curMz;
487
488 if(startMz == curMz)
489 {
490 m_context.m_lastZ = std::numeric_limits<quint16>::max();
491 m_context.m_lastMz = qQNaN();
493 m_context.m_lastMr = qQNaN();
494
495 return false;
496 }
497
498 double chargeTemp = 1 / fabs(m_context.m_xDelta);
499
500 qDebug() << qSetRealNumberPrecision(6) << "curMz - startMz = " << fabs(curMz - startMz)
501 << "while m_context.m_xDelta = " << m_context.m_xDelta;
502
503 // Sanity check
504 if(fabs(m_context.m_xDelta) != fabs(curMz - startMz))
505 qFatal() << "Programming error.";
506
507 // Make a judicious roundup.
508 double chargeIntPart;
509 double chargeFracPart = modf(chargeTemp, &chargeIntPart);
510
511 // qDebug() << "m_xDelta:" << m_context.m_xDelta
512 // << "chargeTemp:" << QString("%1").arg(chargeTemp, 0, 'f', 6)
513 // << "chargeIntPart:" << chargeIntPart
514 // << "chargeFracPart:" << QString("%1").arg(chargeFracPart, 0, 'f', 6)
515 // << "m_chargeMinimalFractionalPart:" << m_chargeMinimalFractionalPart;
516
517 if(chargeFracPart >= (1 - m_chargeMinimalFractionalPart /* that is >= 0.01 */) &&
518 chargeFracPart <= m_chargeMinimalFractionalPart /* that is <= 0.99 */)
519 {
520 m_context.m_lastZ = std::numeric_limits<quint16>::max();
521 m_context.m_lastMz = qQNaN();
523 m_context.m_lastMr = qQNaN();
524
525 // qDebug() << "Not in a isotopic cluster peak:"
526 // << "returning from deconvoluteIsotopicCluster";
527
528 return false;
529 }
530
531 if(chargeFracPart > m_chargeMinimalFractionalPart)
532 {
533 m_context.m_lastZ = chargeIntPart + 1;
534
535 // qDebug() << "chargeFracPart > m_chargeMinimalFractionalPart -> m_lastZ
536 // = "
537 //<< m_context.m_lastZ;
538 }
539 else
540 {
541 m_context.m_lastZ = chargeIntPart;
542
543 // qDebug()
544 //<< "chargeFracPart <= m_chargeMinimalFractionalPart -> m_lastZ = "
545 //<< m_context.m_lastZ;
546 }
547
548 // qDebug() << "Settled on m_lastZ: " << m_context.m_lastZ;
549
550 // Now that we have the charge in the form of an int, we can compute the
551 // Mr of the lightest isotopic cluster peak (the one that has the lowest x
552 // value). That value is stored in m_xRangeLower.
553
554 // We need to sort the xRegionRange before being certain that lower is indeed
555 // the left value of the drag span.
556
558
560
561 // qDebug() << "m_lastMz :" << QString("%1").arg(m_context.m_lastMz, 0, 'f', 6)
562 // << "m_lastZ:" << QString("%1").arg(m_context.m_lastZ)
563 // << "m_lastMr:" << QString("%1").arg(m_context.m_lastMr, 0, 'f', 6);
564
565 // The m_context was refreshed with the base class context in the calling
566 // chain.
568
569 return true;
570}
571
572
573bool
575{
576
577 // m_xRangeLower and m_xRangeUpper and m_xDelta (in fabs() form) have been set
578 // during mouve movement handling. Note that the range values *are
579 // sorted*.
580
582 {
583 m_context.m_lastResolvingPower = std::numeric_limits<double>::min();
584
585 return false;
586 }
587
588 // Resolving power is m/z / Delta(m/z), for singly-charged species.
589
590 // qDebug() << "Calculating the resolving power with these data:"
591 //<< "m_context.m_xRegionRangeStart: " << m_context.m_xRegionRangeStart
592 //<< "m_context.m_xRegionRangeEnd: " << m_context.m_xRegionRangeEnd
593 //<< "m_context.m_xDelta / 2: " << m_context.m_xDelta / 2;
594
597 (m_context.m_xDelta / 2)) /
599
600 // The m_context was refreshed with the base class context in the calling
601 // chain.
603
604 return true;
605}
606
607
608} // namespace pappso
Qt::MouseButtons m_mouseButtonsAtMousePress
Enums::DataKind m_dataKind
DragDirections m_dragDirections
virtual void mouseMoveHandlerDraggingCursor()
virtual void keyPressEvent(QKeyEvent *event)
KEYBOARD-related EVENTS.
virtual void mouseMoveHandlerNotDraggingCursor()
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
Q_INVOKABLE void initialize(const BasePlotContext &other)
virtual void mouseMoveHandler(QMouseEvent *event) override
Handle mouse movements, in particular record all the last visited points.
void resolvingPowerComputationSignal(const MassSpecTracePlotContext &context)
virtual void keyReleaseEvent(QKeyEvent *event) override
Handle specific key codes and trigger respective actions.
const MassSpecTracePlotContext & refreshBaseContext() const
bool deconvoluteChargedState(int span=1)
Deconvolute the mass peaks into charge and molecular mass.
void mousePressEventSignal(const 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 mouseMoveHandlerDraggingCursor() override
void keyPressEventSignal(const MassSpecTracePlotContext &context)
void massDeconvolutionSignal(const MassSpecTracePlotContext &context)
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 mouseReleaseEventSignal(const MassSpecTracePlotContext &context)
virtual void mouseMoveHandlerNotDraggingCursor() 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)