1 /* massXpert - the true massist's program. 2 -------------------------------------- 3 Copyright(C) 2006,2007 Filippo Rusconi 4 5 http://www.filomace.org/massXpert 6 7 This file is part of the massXpert project. 8 9 The massxpert project is the successor to the "GNU polyxmass" 10 project that is an official GNU project package(see 11 www.gnu.org). The massXpert project is not endorsed by the GNU 12 project, although it is released ---in its entirety--- under the 13 GNU General Public License. A huge part of the code in massXpert 14 is actually a C++ rewrite of code in GNU polyxmass. As such 15 massXpert was started at the Centre National de la Recherche 16 Scientifique(FRANCE), that granted me the formal authorization to 17 publish it under this Free Software License. 18 19 This software is free software; you can redistribute it and/or 20 modify it under the terms of the GNU General Public 21 License version 3, as published by the Free Software Foundation. 22 23 24 This software is distributed in the hope that it will be useful, 25 but WITHOUT ANY WARRANTY; without even the implied warranty of 26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 General Public License for more details. 28 29 You should have received a copy of the GNU General Public License 30 along with this software; if not, write to the 31 32 Free Software Foundation, Inc., 33 34 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 35 */ 36 37 38 /////////////////////// Qt includes 39 #include<QtGui> 40 #include<QMessageBox> 41 #include<QFileDialog> 42 43 /////////////////////// Std includes 44 #include <math.h> 45 #include <algorithm> 46 #include <limits> // for std::numeric_limits 47 48 using namespace std; 49 50 /////////////////////// Local includes 51 #include "spectrumCalculationDlg.hpp" 52 #include "isotopicPatternCalculator.hpp" 53 #include "application.hpp" 54 55 56 namespace massXpert 57 { 58 59 enum 60 { 61 TAB_WIDGET_INPUT_DATA, 62 TAB_WIDGET_LOG, 63 TAB_WIDGET_RESULTS, 64 }; 65 66 SpectrumCalculationDlg(QWidget * parent,const QList<Atom * > & atomList,SpectrumCalculationMode mode)67 SpectrumCalculationDlg::SpectrumCalculationDlg 68 (QWidget *parent, 69 const QList<Atom *> &atomList, 70 SpectrumCalculationMode mode) 71 : QDialog(parent), 72 m_mode(mode), 73 m_atomList(atomList), 74 m_polChemDef(static_cast<CalculatorWnd *>(parent)->polChemDef()) 75 { 76 Q_ASSERT(parent); 77 78 setWindowTitle("massXpert: Spectrum Calculator"); 79 80 m_aborted = false; 81 82 // When the window is created the formula line edit is empty, so 83 // at least that data has an error. The ionization data is most 84 // probably not erroneous because it comes from the polymer 85 // chemistry definition or from the caller. 86 m_validationErrors = MXP_VALIDATION_FORMULA_ERRORS; 87 88 m_filePath = ""; 89 90 m_ui.setupUi(this); 91 92 // Set the oligomer list pointer to 0, so that we know if it was 93 // set or not later. 94 mp_oligomerList = 0; 95 96 setupDialog(); 97 98 QSettings settings 99 (static_cast<Application *>(qApp)->configSettingsFilePath(), 100 QSettings::IniFormat); 101 102 settings.beginGroup("isotopic_pattern_calculation_dlg"); 103 104 restoreGeometry(settings.value("geometry").toByteArray()); 105 106 m_ui.splitter->restoreState(settings.value("splitter").toByteArray()); 107 108 settings.endGroup(); 109 110 connect(m_ui.chargeSpinBox, 111 SIGNAL(valueChanged(int)), 112 this, 113 SLOT(chargeChanged(int))); 114 115 connect(m_ui.formulaLineEdit, 116 SIGNAL(textEdited(const QString &)), 117 this, 118 SLOT(formulaEdited(const QString &))); 119 120 connect(m_ui.pointNumberSpinBox, 121 SIGNAL(valueChanged(int)), 122 this, 123 SLOT(pointsChanged(int))); 124 125 connect(m_ui.resolutionSpinBox, 126 SIGNAL(valueChanged(int)), 127 this, 128 SLOT(resolutionChanged(int))); 129 130 connect(m_ui.fwhmLineEdit, 131 SIGNAL(textEdited(const QString &)), 132 this, 133 SLOT(fwhmEdited(const QString &))); 134 135 connect(m_ui.monoRadioButton, 136 SIGNAL(toggled(bool)), 137 this, 138 SLOT(massTypeRadioButtonToggled(bool))); 139 140 connect(m_ui.avgRadioButton, 141 SIGNAL(toggled(bool)), 142 this, 143 SLOT(massTypeRadioButtonToggled(bool))); 144 145 connect(m_ui.avgRadioButton, 146 SIGNAL(toggled(bool)), 147 this, 148 SLOT(massTypeRadioButtonToggled(bool))); 149 150 connect(m_ui.isotopicClusterCheckBox, 151 SIGNAL(toggled(bool)), 152 this, 153 SLOT(isotopicClusterCheckBoxToggled(bool))); 154 155 connect(m_ui.executePushButton, 156 SIGNAL(clicked()), 157 this, 158 SLOT(execute())); 159 160 connect(m_ui.abortPushButton, 161 SIGNAL(clicked()), 162 this, 163 SLOT(abort())); 164 165 connect(m_ui.outputFilePushButton, 166 SIGNAL(clicked()), 167 this, 168 SLOT(outputFile())); 169 } 170 171 172 void closeEvent(QCloseEvent * event)173 SpectrumCalculationDlg::closeEvent(QCloseEvent *event) 174 { 175 if (event) 176 printf("%s", ""); 177 178 QSettings settings 179 (static_cast<Application *>(qApp)->configSettingsFilePath(), 180 QSettings::IniFormat); 181 182 settings.beginGroup("isotopic_pattern_calculation_dlg"); 183 184 settings.setValue("geometry", saveGeometry()); 185 186 settings.setValue("splitter", m_ui.splitter->saveState()); 187 188 settings.endGroup(); 189 } 190 191 ~SpectrumCalculationDlg()192 SpectrumCalculationDlg::~SpectrumCalculationDlg() 193 { 194 while(m_patternPointList.size()) 195 delete m_patternPointList.takeFirst(); 196 } 197 198 199 void setOligomerList(OligomerList * list)200 SpectrumCalculationDlg::setOligomerList(OligomerList *list) 201 { 202 if(!list) 203 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 204 205 mp_oligomerList = list; 206 } 207 208 209 void setupDialog()210 SpectrumCalculationDlg::setupDialog() 211 { 212 // Always start the dialog with the first page of the tab widget, 213 // the input data page. 214 m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_INPUT_DATA); 215 216 // By default we want a gaussian-type shape. 217 m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN); 218 219 // Throughout of *this* dialog window, the normalization factor 220 // for the peak shapes (Gaussian, specifically) is going to be 1. 221 222 // Charge stuff. 223 m_ui.chargeSpinBox->setRange(1, 10000000); 224 m_ui.chargeSpinBox->setValue(1); 225 m_charge = m_ui.chargeSpinBox->value(); 226 227 // Isotopic peak probability stuff. 228 m_ui.minimumProbabilityLineEdit->setText("0.0000001"); 229 m_minimumProbability = 0.0000001; 230 231 // Max number of peaks in the isotopic cluster. 232 m_ui.maximumPeaksSpinBox->setRange(0, 1000); 233 m_ui.maximumPeaksSpinBox->setValue(100); 234 m_maximumPeaks = 100; 235 236 // Resolution and FWHM stuff 237 m_ui.resolutionSpinBox->setRange(0, 1000000); 238 m_ui.resolutionSpinBox->setValue(0); 239 m_resolution = 0; 240 241 // Get the number of points used to craft the curve. 242 m_config.setPointNumber(m_ui.pointNumberSpinBox->value()); 243 244 // Since both values above are not correct, set the corresponding 245 // bits to the validation error int. 246 m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS; 247 m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS; 248 249 // Now, depending on the kind of operation required for the 250 // dialog, adapt some behaviour. 251 252 253 if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 254 { 255 // In isotopic cluster calculation mode, the m/z calculated 256 // starting from the formula and the charge is of course mono! 257 // Thus, we set it checked and disable the enclosing group box. 258 259 m_ui.monoRadioButton->setChecked(true); 260 m_ui.massTypeGroupBox->setDisabled(true); 261 262 // Of course, the computeIsotopicClusterCheckBox should be 263 // checked and disabled. 264 m_ui.isotopicClusterCheckBox->setChecked(true); 265 m_ui.isotopicClusterCheckBox->setDisabled(true); 266 267 // And all the related widgets should be active; 268 269 m_ui.pointNumberSpinBox->setDisabled(false); 270 m_ui.minimumProbabilityLineEdit->setDisabled(false); 271 m_ui.maximumPeaksSpinBox->setDisabled(false); 272 273 } 274 else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM) 275 { 276 // In spectrum mode, the formula, z and m/z are not active. 277 m_ui.mzGroupBox->setDisabled(true); 278 279 // By default, we work on mono masses. 280 m_withMonoMass= true; 281 m_ui.monoRadioButton->setChecked(m_withMonoMass); 282 283 // By default, we do not want isotopic cluster calculations. 284 m_withCluster = false; 285 m_ui.isotopicClusterCheckBox->setChecked(m_withCluster); 286 287 // Not willing to compute isotopic clusters, thus all the 288 // related widgets should be inactive. 289 m_ui.pointNumberSpinBox->setDisabled(true); 290 m_ui.minimumProbabilityLineEdit->setDisabled(true); 291 m_ui.maximumPeaksSpinBox->setDisabled(true); 292 } 293 } 294 295 296 bool fetchValidateInputData()297 SpectrumCalculationDlg::fetchValidateInputData() 298 { 299 if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 300 return fetchValidateInputDataIsotopicClusterMode(); 301 else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM) 302 return fetchValidateInputDataSpectrumMode(); 303 else 304 return false; 305 } 306 307 308 bool fetchValidateInputDataIsotopicClusterMode()309 SpectrumCalculationDlg::fetchValidateInputDataIsotopicClusterMode() 310 { 311 Application *application = static_cast<Application *>(qApp); 312 313 // The ionization stuff, the formula for which the isotopic 314 // pattern is to be calculated, the mz ratio (in m_mono) all have 315 // been checked previously by automated procedures (see the 316 // "changed" slots). We have to make sure that either the 317 // resolution or the FWHM values are OK. All the other bits must 318 // be clear. Only either the RESOLUTION or the FWHM bit might be 319 // set. 320 321 int testBitset = 0; 322 testBitset |= MXP_VALIDATION_RESOLUTION_ERRORS; 323 testBitset |= MXP_VALIDATION_FWHM_ERRORS; 324 325 // debugPutStdErrBitset(__FILE__, __LINE__, 326 // testBitset, "testBitset"); 327 328 if(m_validationErrors == testBitset) 329 return false; 330 331 if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) == 332 MXP_VALIDATION_FORMULA_ERRORS) 333 return false; 334 335 // Now that we know that the formula was validated, we can ask 336 // that all the isotopes be deep-copied into the formula. 337 338 // The list of atomCount objects has to be made with fully detailed 339 // isotopes... 340 if (!m_formula.deepAtomCopy(m_atomList)) 341 { 342 QMessageBox::warning(0, 343 tr("massXpert: Spectrum Calculator"), 344 tr("Failed to deep-copy atom list."), 345 QMessageBox::Ok); 346 347 return false; 348 } 349 350 // We still have to fetch a number of parameters. 351 352 // Get to know if we want gaussian or lorentzian shapes. 353 if(m_ui.gaussianRadioButton->isChecked()) 354 { 355 m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN); 356 } 357 else 358 { 359 m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_LORENTZIAN); 360 } 361 362 // Shall we use localization for all the numerical output? 363 bool isUsingLocale = m_ui.localeCheckBox->checkState() == Qt::Checked ? 364 true : false; 365 366 int totAtoms = m_formula.totalAtoms(); 367 m_ui.progressBar->setRange(0, totAtoms); 368 m_ui.progressBar->setValue(0); 369 370 int totIsotopes = m_formula.totalIsotopes(m_atomList); 371 372 QString textEditText; 373 374 if (isUsingLocale) 375 textEditText += tr("INPUT\n=====\n\n" 376 "Formula: %1\n" 377 "Charge: %2\n" 378 "Mono Mass: %3 \t Avg mass: %4\n" 379 "Total number of atoms: %5\t" 380 "Total number of isotopes: %6\n\n") 381 .arg(m_formula.text()) 382 .arg(m_charge) 383 .arg(application->locale(). 384 toString(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES)) 385 .arg(application->locale(). 386 toString(m_avg, 'f', MXP_OLIGOMER_DEC_PLACES)) 387 .arg(totAtoms) 388 .arg(totIsotopes); 389 else 390 textEditText += tr("INPUT\n=====\n\n" 391 "Formula: %1\n" 392 "Charge: %2\n" 393 "Mono Mass: %3 \t Avg mass: %4\n" 394 "Total number of atoms: %5\t" 395 "Total number of isotopes: %6\n\n") 396 .arg(m_formula.text()) 397 .arg(m_charge) 398 .arg(QString().setNum(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES)) 399 .arg(QString().setNum(m_avg, 'f', MXP_OLIGOMER_DEC_PLACES)) 400 .arg(totAtoms) 401 .arg(totIsotopes); 402 403 // Put that description in the text edit widget. 404 m_ui.resultPlainTextEdit->appendPlainText(textEditText); 405 406 // qDebug() << __FILE__ << __LINE__ 407 // << textEditText; 408 409 // At this point we have to sort out wether the user wants to use 410 // the resolution or the FWHM for the calculations. This is 411 // automatically known by looking at which value is 0 (see the 412 // corresponding changed() slots). 413 414 // Get the points value. 415 m_config.setPointNumber(m_ui.pointNumberSpinBox->value()); 416 417 // How about setting the increment, that is the size of the gap 418 // between two points ? If it is not set (its value is 0), then 419 // increment is fwhm() / m_pointNumber; 420 421 // First however, see if the user wants to use the FWHM value or 422 // the resolution. 423 424 if(m_config.fwhm()) 425 { 426 // FWHM is not zero, which means the user has set a value for 427 // it. Keep it. 428 } 429 else 430 { 431 // We have to compute the FWHM starting from the mz ratio and 432 // the resolution. 433 434 m_config.setFwhm(m_mono / m_resolution); 435 } 436 437 // Set the maximum number of peaks in the isotopic curve. 438 m_maximumPeaks = m_ui.maximumPeaksSpinBox->value(); 439 440 // Set the minimum probability each isotopic peak must have to be 441 // retained in the final curve. 442 443 QString minProbString = m_ui.minimumProbabilityLineEdit->text(); 444 bool ok = false; 445 446 double minProb = minProbString.toDouble(&ok); 447 448 if(!minProb && !ok) 449 { 450 QMessageBox::warning(0, 451 tr("massXpert: Spectrum Calculator"), 452 tr("Please, fix the minimum probability."), 453 QMessageBox::Ok); 454 455 return false; 456 } 457 458 m_minimumProbability = minProb; 459 460 return true; 461 } 462 463 464 bool fetchValidateInputDataSpectrumMode()465 SpectrumCalculationDlg::fetchValidateInputDataSpectrumMode() 466 { 467 // The ionization stuff, the formula for which the isotopic 468 // pattern is to be calculated, the mz ratio (in m_mono) all have 469 // been checked previously by automated procedures (see the 470 // "changed" slots). We have to make sure that either the 471 // resolution or the FWHM values are OK. All the other bits must 472 // be clear. Only either the RESOLUTION or the FWHM bit might be 473 // set. 474 475 int testBitset = 0; 476 testBitset |= MXP_VALIDATION_RESOLUTION_ERRORS; 477 testBitset |= MXP_VALIDATION_FWHM_ERRORS; 478 479 // debugPutStdErrBitset(__FILE__, __LINE__, 480 // testBitset, "testBitset"); 481 482 if(m_validationErrors == testBitset) 483 return false; 484 485 // We do not care of the formula in our spectrum calculation 486 // situation. Each oligomer from a cleavage contains a chemical 487 // composition formula that we'll use each time. 488 489 // We still have to fetch a number of parameters. 490 491 // Are we working on the mono or the avg masses of the oligomers? 492 493 m_withMonoMass = m_ui.monoRadioButton->isChecked(); 494 495 // Get to know if we want gaussian or lorentzian shapes. 496 if(m_ui.gaussianRadioButton->isChecked()) 497 { 498 m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_GAUSSIAN); 499 } 500 else 501 { 502 m_config.setPeakShapeType(MXP_PEAK_SHAPE_TYPE_LORENTZIAN); 503 } 504 505 // At this point we have to sort out wether the user wants to use 506 // the resolution or the FWHM for the calculations. This is 507 // automatically known by looking at which value is 0 (see the 508 // corresponding changed() slots). 509 510 // Get the points value. 511 m_config.setPointNumber(m_ui.pointNumberSpinBox->value()); 512 513 // How about setting the increment, that is the size of the gap 514 // between two points ? If it is not set (its value is 0), then 515 // increment is fwhm() / m_pointNumber; 516 517 // First however, see if the user wants to use the FWHM value or 518 // the resolution. 519 520 if(m_config.fwhm()) 521 { 522 // FWHM is not zero, which means the user has set a value for 523 // it. Keep it. 524 } 525 else 526 { 527 // We cannot compute fwhm starting from the resolution because 528 // we cannot know what is the m/z of the oligomers beforehand. 529 530 // All we can check is that resolution is non-zero. 531 532 if(m_resolution <= 0) 533 { 534 QMessageBox::warning(0, 535 tr("massXpert: Spectrum Calculator"), 536 tr("Please, fix the resolution or the FWHM."), 537 QMessageBox::Ok); 538 539 return false; 540 } 541 } 542 543 // Set the maximum number of peaks in the isotopic curve. 544 m_maximumPeaks = m_ui.maximumPeaksSpinBox->value(); 545 546 // Set the minimum probability each isotopic peak must have to be 547 // retained in the final curve. 548 549 QString minProbString = m_ui.minimumProbabilityLineEdit->text(); 550 bool ok = false; 551 552 double minProb = minProbString.toDouble(&ok); 553 554 if(!minProb && !ok) 555 { 556 QMessageBox::warning(0, 557 tr("massXpert: Spectrum Calculator"), 558 tr("Please, fix the minimum probability."), 559 QMessageBox::Ok); 560 561 return false; 562 } 563 564 m_minimumProbability = minProb; 565 566 return true; 567 } 568 569 570 // Returns false if formula is bad or aborts if calculation fails.. 571 bool fetchFormulaMass(double * mono,double * avg)572 SpectrumCalculationDlg::fetchFormulaMass(double *mono, double *avg) 573 { 574 // Show what's the value of the errors. 575 // debugPutStdErrBitset(__FILE__, __LINE__, 576 // m_validationErrors, 577 // "m_validationErrors @ fetchFormulaMass"); 578 579 // Check if there are currently errors set. 580 581 if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) == 582 MXP_VALIDATION_FORMULA_ERRORS) 583 return false; 584 585 double monoMass = 0; 586 double avgMass = 0; 587 588 // It is impossible that we have an error here, since the formula 589 // was previously validated (MXP_VALIDATION_FORMULA_ERRORS above). 590 if (!m_formula.accountMasses(m_atomList, &monoMass, &avgMass, 1)) 591 qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__); 592 593 if(mono) 594 *mono = monoMass; 595 596 if(avg) 597 *avg = avgMass; 598 599 // qDebug() << __FILE__ << __LINE__ 600 // << "Formula mono/avg masses:" 601 // << monoMass << "/" << avgMass; 602 603 // Show what's the value of the errors. 604 // debugPutStdErrBitset(__FILE__, __LINE__, 605 // m_validationErrors, 606 // "m_validationErrors @ fetchFormulaMass"); 607 608 return true; 609 } 610 611 // Returns false if the formula is not validated and aborts if an 612 // error occurs while the formula was validated. 613 bool fetchMzRatio(double * mono,double * avg)614 SpectrumCalculationDlg::fetchMzRatio(double *mono, double *avg) 615 { 616 // Check if there are currently errors set either in the formula 617 // or in the ionization rule. 618 619 // Show what's the value of the errors. 620 // debugPutStdErrBitset(__FILE__, __LINE__, 621 // m_validationErrors, 622 // "m_validationErrors @ ENTER fetchMzRatio"); 623 624 if((m_validationErrors & MXP_VALIDATION_FORMULA_ERRORS) == 625 MXP_VALIDATION_FORMULA_ERRORS) 626 { 627 return false; 628 } 629 630 // The formula data have already been automatically fetched and 631 // set to m_formula when the data in the widgets changed using the 632 // correponding slot. 633 634 // We can compute the m/z ratio. 635 636 double monoMass = 0; 637 double avgMass = 0; 638 639 fetchFormulaMass(&monoMass, &avgMass); 640 641 // qDebug() << __FILE__ << __LINE__ 642 // << "monoMass:" << monoMass 643 // << "avgMass:" << avgMass; 644 645 // Note that the only acceptable value returned here is mono > 0, 646 // because the formula was validated and thus mass calculation 647 // should not fail. Indeed, if there is a failure in the called 648 // function, qFatal is triggered. No need, thus, to check the 649 // status of the call. It cannot fail. 650 651 // Compute the m/z ratio. 652 monoMass = monoMass / m_charge; 653 avgMass = avgMass / m_charge; 654 655 // At this point the ionizable has the right mzRatio. 656 657 if(mono) 658 *mono = monoMass; 659 660 if(avg) 661 *avg = avgMass; 662 663 // Show what's the value of the errors. 664 // debugPutStdErrBitset(__FILE__, __LINE__, 665 // m_validationErrors, 666 // "m_validationErrors @ EXIT fetchMzRatio"); 667 668 return true; 669 } 670 671 void massTypeRadioButtonToggled(bool checked)672 SpectrumCalculationDlg::massTypeRadioButtonToggled(bool checked) 673 { 674 Q_UNUSED(checked); 675 676 m_withMonoMass = m_ui.monoRadioButton->isChecked(); 677 678 // If the mass type is average, then, of course, no isotopic 679 // cluster calculation can be required. 680 681 if(!m_withMonoMass) 682 m_ui.isotopicClusterCheckBox->setChecked(false); 683 } 684 685 void isotopicClusterCheckBoxToggled(bool checked)686 SpectrumCalculationDlg::isotopicClusterCheckBoxToggled(bool checked) 687 { 688 m_withCluster = checked; 689 690 if(checked) 691 { 692 // If the isotopic cluster is to be computed for each oligomer, 693 // then that means necessarily that the mass is mono! 694 695 m_withMonoMass = true; 696 m_ui.monoRadioButton->setChecked(true); 697 698 m_ui.pointNumberSpinBox->setDisabled(false); 699 m_ui.minimumProbabilityLineEdit->setDisabled(false); 700 m_ui.maximumPeaksSpinBox->setDisabled(false); 701 } 702 else 703 { 704 // Not willing to compute isotopic clusters, thus all the 705 // related widgets should be inactive. 706 707 m_ui.pointNumberSpinBox->setDisabled(true); 708 m_ui.minimumProbabilityLineEdit->setDisabled(true); 709 m_ui.maximumPeaksSpinBox->setDisabled(true); 710 } 711 712 } 713 714 715 void formulaEdited(const QString & text)716 SpectrumCalculationDlg::formulaEdited(const QString &text) 717 { 718 // We only handle the automatic recalculations on the basis of 719 // this change if we are in a isotopic cluster calculation task. 720 if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 721 return; 722 723 // Show what's the value of the errors. 724 // debugPutStdErrBitset(__FILE__, __LINE__, 725 // m_validationErrors, 726 // "m_validationErrors @ ENTER formulaChanged"); 727 728 // qDebug() << __FILE__ << __LINE__ 729 // << "formulaChanged has text:" << text; 730 731 // New text contains a new formula. Check it. 732 733 if(text.isEmpty()) 734 { 735 // Set the error bit. 736 m_validationErrors |= MXP_VALIDATION_FORMULA_ERRORS; 737 738 m_ui.monoMzRatioLineEdit->setText("0.000"); 739 m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error")); 740 741 return; 742 } 743 744 Formula formula(text); 745 746 // Do not bother storing all the atoms in the formula, this is 747 // only a check (false param below). m_atomList is a reference to 748 // the atom list of the polymer chemistry definition currently 749 // used in the caller calculator window. 750 751 if (!formula.validate(m_atomList, false, true)) 752 { 753 // Set the error bit. 754 m_validationErrors |= MXP_VALIDATION_FORMULA_ERRORS; 755 756 m_ui.monoMzRatioLineEdit->setText("0.000"); 757 m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error")); 758 759 return; 760 } 761 762 // qDebug() << __FILE__ << __LINE__ 763 // << "Formula:" << formula.formula() << "validated." ; 764 765 // At this point we know we could actually validate the formula, 766 // so do that work with the member formula: 767 768 // We want to validate the formula and in the mean time construct 769 // the list of all the AtomCount objects(first true), and since 770 // the formula is reused we also ensure that that list is reset 771 // (second true). m_atomList is a reference to the atom list of 772 // the polymer chemistry definition currently used in the caller 773 // calculator window. 774 775 m_formula.setFormula(text); 776 m_formula.validate(m_atomList, true, true); 777 778 // Clear the bit as there is no error here. 779 m_validationErrors &= ~MXP_VALIDATION_FORMULA_ERRORS; 780 781 m_ui.inputDataFeedbackLineEdit->setText(tr("Formula fine")); 782 783 // qDebug() << __FILE__ << __LINE__ 784 // << "Going to call updateMzRatio."; 785 786 updateMzRatio(); 787 updateIncrement(); 788 789 // Show what's the value of the errors. 790 // debugPutStdErrBitset(__FILE__, __LINE__, 791 // m_validationErrors, 792 // "m_validationErrors @ EXIT formulaChanged"); 793 794 return; 795 } 796 797 798 void chargeChanged(int value)799 SpectrumCalculationDlg::chargeChanged(int value) 800 { 801 // We only handle the automatic recalculations on the basis of 802 // this change if we are in a isotopic cluster calculation task. 803 if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 804 return; 805 806 //The charge has changed, we should compute the mzRatio. 807 m_charge = m_ui.chargeSpinBox->value(); 808 809 updateMzRatio(); 810 updateIncrement(); 811 } 812 813 814 void pointsChanged(int value)815 SpectrumCalculationDlg::pointsChanged(int value) 816 { 817 // We only handle the automatic recalculations on the basis of 818 // this change if we are in a isotopic cluster calculation task. 819 if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 820 return; 821 822 //The points have changed, we should compute the increment. 823 m_config.setPointNumber(m_ui.pointNumberSpinBox->value()); 824 825 updateIncrement(); 826 } 827 828 829 void resolutionChanged(int value)830 SpectrumCalculationDlg::resolutionChanged(int value) 831 { 832 // If the value changed, that means that the user wants the 833 // resolution to be taken into account for calculation of the peak 834 // width, and not the FWHM. Only if resolution is set to 0, FWHM 835 // will be the value taken into account. 836 837 // Show what's the value of the errors. 838 839 // debugPutStdErrBitset(__FILE__, __LINE__, 840 // m_validationErrors, 841 // "m_validationErrors @ ENTER resolutionChanged"); 842 843 if(value <= 0) 844 { 845 // Tell the user to set a valid FWHM value, then. 846 m_ui.inputDataFeedbackLineEdit->setText 847 (tr("Set a valid FWHM value")); 848 849 m_ui.resolutionSpinBox->setValue(0); 850 851 // Set the error bit so that we can let the increment 852 // calculation know that we can not perform that computation 853 // using FWHM. 854 855 m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS; 856 857 return; 858 } 859 860 m_resolution = value; 861 862 m_ui.inputDataFeedbackLineEdit->setText 863 (tr("Will use the resolution")); 864 865 // Clear the bit as there is no error here. 866 m_validationErrors &= ~MXP_VALIDATION_RESOLUTION_ERRORS; 867 868 // We only handle the automatic recalculations on the basis of 869 // this change if we are in a isotopic cluster calculation task 870 // because only in that case do we know before starting the 871 // calculation what will be the mz. 872 if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 873 return; 874 875 // We can try to compute the fwhm value corresponding to the 876 // current mono m/z value and the resolution. 877 878 double mono; 879 double avg; 880 881 if(fetchFormulaMass(&mono, &avg)) 882 { 883 Application *application = static_cast<Application *>(qApp); 884 885 QString value = application->locale(). 886 toString(mono / m_resolution, 'f', MXP_OLIGOMER_DEC_PLACES); 887 888 // Note that the setText call below will not trigger automatic 889 // fwhm setting because the lineEdit widget is connected to a 890 // textEdited signal, not a textChanged signal. 891 892 m_ui.fwhmLineEdit->setText(value); 893 } 894 895 // Now, because we want the resolution to be the governing 896 // parameter when validation will occur, we do this: 897 898 m_config.setFwhm(0); 899 m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS; 900 901 // Show what's the value of the errors. 902 903 // debugPutStdErrBitset(__FILE__, __LINE__, 904 // m_validationErrors, 905 // "m_validationErrors @ EXIT resolutionChanged"); 906 907 updateIncrement(); 908 909 return; 910 } 911 912 913 void fwhmEdited(const QString & text)914 SpectrumCalculationDlg::fwhmEdited(const QString &text) 915 { 916 // Show what's the value of the errors. 917 // debugPutStdErrBitset(__FILE__, __LINE__, 918 // m_validationErrors, 919 // "m_validationErrors @ ENTER fwhmEdited"); 920 921 // If the text was edited in the line edit, that means that the 922 // user wants the FWHM to be taken into account for calculation of 923 // the peak width, and not the resolution. Only if FWHM is set to 924 // 0, resolution will be the value taken into account. 925 926 QString txt = m_ui.fwhmLineEdit->text(); 927 928 bool ok = false; 929 930 double fwhmValue = txt.toDouble(&ok); 931 932 if(!fwhmValue && !ok) 933 { 934 // Set the error bit. 935 m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS; 936 937 m_ui.inputDataFeedbackLineEdit->setText 938 (tr("Fix the FWHM value, please")); 939 940 return; 941 } 942 943 // But the value might be faithfully 0, if the user is telling us 944 // that she wants to take the resolution into account and not the 945 // FWHM. 946 947 if(!fwhmValue) 948 { 949 m_ui.inputDataFeedbackLineEdit->setText 950 (tr("Set a valid resolution value")); 951 952 // Set the error bit to let the increment calculation know 953 // that it cannot base the calculation on the FWHM value. 954 955 m_validationErrors |= MXP_VALIDATION_FWHM_ERRORS; 956 957 return; 958 } 959 960 // At this point we know that fwhmValue contains a proper value. 961 962 m_ui.inputDataFeedbackLineEdit->setText 963 (tr("Will use the FWHM")); 964 965 m_validationErrors &= ~MXP_VALIDATION_FWHM_ERRORS; 966 967 m_config.setFwhm(fwhmValue); 968 969 // Set the resolution spinbox to 0 and set the error bit 970 // associated with it to let the increment calculation know that 971 // it cannot be based on that value. 972 973 m_resolution = 0; 974 m_ui.resolutionSpinBox->setValue(0); 975 m_validationErrors |= MXP_VALIDATION_RESOLUTION_ERRORS; 976 977 // Show what's the value of the errors. 978 // debugPutStdErrBitset(__FILE__, __LINE__, 979 // m_validationErrors, 980 // "m_validationErrors @ EXIT fwhmEdited"); 981 982 // We only handle the automatic recalculation of the increment on 983 // the basis of this change if we are in a isotopic cluster 984 // calculation task because only in this case do we know the mono 985 // mz value. 986 if(m_mode != MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 987 return; 988 989 updateIncrement(); 990 991 return; 992 } 993 994 995 bool updateMzRatio()996 SpectrumCalculationDlg::updateMzRatio() 997 { 998 // Show what's the value of the errors. 999 // debugPutStdErrBitset(__FILE__, __LINE__, 1000 // m_validationErrors, 1001 // "m_validationErrors @ ENTER updateMzRatio"); 1002 1003 // Set the mz ratio to our member data variable. 1004 bool res = fetchMzRatio(&m_mono, &m_avg); 1005 1006 // If res is falsed, that means that the formula is not validated 1007 // at present. Silently return. 1008 if(!res) 1009 { 1010 QMessageBox::warning(0, 1011 tr("massXpert - Spectrum Calculator"), 1012 tr("Failed to fetch the mz ratio."), 1013 QMessageBox::Ok); 1014 1015 return false; 1016 } 1017 1018 Application *application = static_cast<Application *>(qApp); 1019 1020 QString mzRatio = application->locale(). 1021 toString(m_mono, 'f', MXP_OLIGOMER_DEC_PLACES); 1022 1023 m_ui.monoMzRatioLineEdit->setText(mzRatio); 1024 1025 // Show what's the value of the errors. 1026 // debugPutStdErrBitset(__FILE__, __LINE__, 1027 // m_validationErrors, 1028 // "m_validationErrors @ EXIT updateMzRatio"); 1029 1030 return true; 1031 } 1032 1033 1034 bool updateIncrement()1035 SpectrumCalculationDlg::updateIncrement() 1036 { 1037 // We can only calculate the increment if we have the number of 1038 // points to construct the curve and either the resolution and the 1039 // mzRatio or the FWHM. 1040 1041 // If we know the FWHM, then, fine, we can compute the increment 1042 // right away. 1043 1044 double increment = 0; 1045 1046 if((m_validationErrors & MXP_VALIDATION_FWHM_ERRORS) != 1047 MXP_VALIDATION_FWHM_ERRORS) 1048 { 1049 increment = 1050 (MXP_FWHM_PEAK_SPAN_FACTOR * m_config.fwhm()) / 1051 m_config.pointNumber(); 1052 } 1053 else if((m_validationErrors & MXP_VALIDATION_RESOLUTION_ERRORS) != 1054 MXP_VALIDATION_RESOLUTION_ERRORS) 1055 { 1056 // If we know the resolution and we can get the mzRatio, we'll 1057 // be able to first compute the FWHM as (mzRatio / FWHM). From 1058 // there, we'll be able to compute the increment. 1059 1060 double mono = 0; 1061 double avg = 0; 1062 1063 // We compute the increment by using the mass of the formula, 1064 // not its mzRatio. 1065 if(fetchFormulaMass(&mono, &avg)) 1066 { 1067 double fwhm = mono / m_resolution; 1068 1069 increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) / 1070 m_config.pointNumber(); 1071 1072 // Because we'll need more points when the charge 1073 // increases... Formally, we have to check that charge is 1074 // not 0, but we should not be required to do it because 1075 // the charge can never be 0 (gui-limited values for the 1076 // charge spin box). 1077 if(m_charge) 1078 increment = increment / m_charge; 1079 } 1080 } 1081 1082 if(increment) 1083 { 1084 m_config.setIncrement(increment); 1085 1086 QString text; 1087 text.setNum(m_config.increment()); 1088 1089 m_ui.incrementLabel->setText(text); 1090 1091 return true; 1092 } 1093 else 1094 m_ui.incrementLabel->setText("0"); 1095 1096 return false; 1097 } 1098 1099 1100 void execute()1101 SpectrumCalculationDlg::execute() 1102 { 1103 // We might be called to perform two different things: 1104 // 1105 // 1. Calculate a single istotopic cluster pattern. 1106 // 1107 // 2. Calculate a spectrum for a whole set of oligomers. 1108 1109 if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_CLUSTER) 1110 return executeCluster(); 1111 else if(m_mode == MXP_SPECTRUM_CALCULATION_MODE_SPECTRUM) 1112 return executeSpectrum(); 1113 } 1114 1115 1116 void executeCluster()1117 SpectrumCalculationDlg::executeCluster() 1118 { 1119 Application *application = static_cast<Application *>(qApp); 1120 1121 // Clear the textEdit widget so that we can start out-putting text 1122 // into it, note that fetchValidateInputData() puts text in it. 1123 m_ui.resultPlainTextEdit->clear(); 1124 1125 // Clear the results string for the creation of the calculator 1126 // below to receive an empty string. 1127 m_resultsString.clear(); 1128 1129 // We are asked to launch the calculation. But we ought to make 1130 // sure that all the required data are set fine. The call below 1131 // will fill-in the m_config data. 1132 1133 if(!fetchValidateInputData()) 1134 { 1135 QMessageBox::warning(0, 1136 tr("massXpert - Spectrum Calculator"), 1137 tr("Please fix the input data first."), 1138 QMessageBox::Ok); 1139 return; 1140 } 1141 1142 // Before starting the calculations, swith to the log tab so that 1143 // the user can monitor what's going on. 1144 1145 m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_LOG); 1146 1147 // Send to both the log and result text edit widget the data we 1148 // are going to use for the calculation. 1149 1150 QString *text = new QString(); 1151 1152 bool isUsingLocale = m_ui.localeCheckBox->checkState() == Qt::Checked ? 1153 true : false; 1154 1155 if (isUsingLocale) 1156 text->append(tr("FWHM: %1 \t Max. peaks %2 " 1157 "\t Min. probability: %3\n\n") 1158 .arg(application->locale().toString(m_config.fwhm(), 1159 'f', 4)) 1160 .arg(application->locale().toString(m_maximumPeaks)) 1161 .arg(application->locale().toString(m_minimumProbability, 1162 'f', 15))); 1163 else 1164 text->append(tr("FWHM: %1 \t Max. peaks %2 " 1165 "\t Min. probability: %3\n\n") 1166 .arg(QString().setNum(m_config.fwhm(), 1167 'f', 4)) 1168 .arg(QString().setNum(m_maximumPeaks)) 1169 .arg(QString().setNum(m_minimumProbability, 1170 'f', 15))); 1171 1172 m_ui.logPlainTextEdit->appendPlainText(*text); 1173 m_ui.resultPlainTextEdit->appendPlainText(*text); 1174 1175 // Free the string, we will reuse it. 1176 delete text; 1177 1178 // We will allocate a IsotopicPatternCalculator instance and let 1179 // it do the work. 1180 1181 IsotopicPatternCalculator *calculator = 1182 new IsotopicPatternCalculator(m_formula, m_charge, 1183 m_maximumPeaks, m_minimumProbability, 1184 m_atomList, m_config); 1185 1186 connect(calculator, 1187 SIGNAL(isotopicCalculationProgressValueChanged(int)), 1188 this, 1189 SLOT(spectrumCalculationProgressValueChanged(int))); 1190 1191 connect(calculator, 1192 SIGNAL(isotopicCalculationMessageChanged(QString)), 1193 this, 1194 SLOT(spectrumCalculationMessageChanged(QString))); 1195 1196 connect(this, 1197 SIGNAL(spectrumCalculationAborted()), 1198 calculator, 1199 SLOT(spectrumCalculationAborted())); 1200 1201 1202 // Perform the calculation... 1203 calculator->sumPeakShapes(); 1204 1205 // Get the list of peaks as "1251.14 0.025" pairs. 1206 text = calculator->peakCentroidListAsString(); 1207 1208 // Now prepend a header: 1209 1210 text->prepend("\nList of peak centroids:\n"); 1211 1212 // Display the data to the user. 1213 m_ui.resultPlainTextEdit->appendPlainText(*text); 1214 1215 // We want to get the calculation configuration. 1216 1217 // Let's get the detailed calculation configuration 1218 // string, so that we can append it to the textEdit 1219 // widget. 1220 text->clear(); 1221 text->append(m_config.config()); 1222 m_ui.resultPlainTextEdit->appendPlainText(*text); 1223 1224 // Finally delete the text. 1225 delete text; 1226 1227 // Finally, if the user had asked for the data to be sent to a 1228 // file... or to a string... Get a reference to the list of 1229 // QPointF instances in the calculator and send them either to the 1230 // file or to the text edit widget. 1231 1232 const QList<QPointF *> &spectrumPoints = calculator->pointList(); 1233 QString dataString; 1234 1235 if(!m_filePath.isEmpty()) 1236 { 1237 // Send all the data (that is, the points located in 1238 // QList<QPointF *>m_patternPointList) to the output file. 1239 1240 QFile file(m_filePath); 1241 1242 if(!file.open(QFile::WriteOnly | QFile::Truncate)) 1243 { 1244 QMessageBox::warning(this, 1245 tr("massXpert - Spectrum Calculator"), 1246 tr("Failed to export the isotopic pattern " 1247 "data to file."), 1248 QMessageBox::Ok); 1249 return; 1250 } 1251 1252 QTextStream stream(&file); 1253 1254 for(int iter = 0, size = spectrumPoints.size(); 1255 iter < size ; ++iter) 1256 { 1257 QPointF *point = spectrumPoints.at(iter); 1258 1259 dataString = QString().setNum(point->x(), 'f', 10); 1260 dataString += " "; 1261 dataString += QString().setNum(point->y(), 'f', 10); 1262 dataString += "\n"; 1263 stream << dataString; 1264 // qDebug() << __FILE__ << __LINE__ << dataString; 1265 } 1266 1267 stream.flush(); 1268 file.close(); 1269 1270 // At this moment, tell the user in the log that the data have 1271 // been written to the given file. 1272 1273 QString message; 1274 1275 message += tr("\n\nData have been written to file %1\n\n") 1276 .arg(m_filePath); 1277 1278 message += tr("\n\nAlso check the results tab\n\n"); 1279 1280 m_ui.logPlainTextEdit->appendPlainText(message); 1281 1282 return; 1283 } 1284 else 1285 { 1286 for(int iter = 0, size = spectrumPoints.size(); 1287 iter < size ; ++iter) 1288 { 1289 QPointF *point = spectrumPoints.at(iter); 1290 1291 dataString += QString().setNum(point->x(), 'f', 10); 1292 dataString += " "; 1293 dataString += QString().setNum(point->y(), 'f', 10); 1294 dataString += "\n"; 1295 } 1296 1297 m_ui.resultPlainTextEdit->appendPlainText(dataString); 1298 1299 // At this moment, tell the user in the log that the data have 1300 // been written to result tab. 1301 1302 QString message; 1303 1304 message += tr("\n\nData have been written to the results tab\n\n"); 1305 1306 m_ui.logPlainTextEdit->appendPlainText(message); 1307 1308 } 1309 1310 delete calculator; 1311 } 1312 1313 1314 void executeSpectrum()1315 SpectrumCalculationDlg::executeSpectrum() 1316 { 1317 // Clear the textEdit widget so that we can start out-putting text 1318 // into it, note that fetchValidateInputData() puts text in it. 1319 m_ui.resultPlainTextEdit->clear(); 1320 m_ui.logPlainTextEdit->clear(); 1321 1322 // Verify that there are oligomers in the list. 1323 1324 if(!mp_oligomerList) 1325 return; 1326 1327 if(mp_oligomerList->isEmpty()) 1328 return; 1329 1330 if(!fetchValidateInputData()) 1331 { 1332 QMessageBox::warning(0, 1333 tr("massXpert - Spectrum Calculator"), 1334 tr("Please fix the input data first."), 1335 QMessageBox::Ok); 1336 return; 1337 } 1338 1339 // List of lists of QPointF, that is list of spectra. 1340 QList<QList<QPointF *> *> spectrumList; 1341 1342 // Each time a new spectrum is calculated, store the corresponding 1343 // increment we'll need later to computed the summed final 1344 // spectrum. 1345 QHash<QList<QPointF *> *, double> incrementHash; 1346 1347 // Before starting the calculations, swith to the log tab so that 1348 // the user can monitor what's going on. 1349 1350 m_ui.tabWidget->setCurrentIndex(TAB_WIDGET_LOG); 1351 1352 if(m_withCluster) 1353 executeSpectrumWithCluster(&spectrumList, &incrementHash); 1354 else 1355 executeSpectrumWithoutCluster(&spectrumList, &incrementHash); 1356 1357 1358 // At this point, with one method or the other, we should have as 1359 // many allocated QList<QPointF *> * instances (that is, spectrum 1360 // instances) as there are oligomers. Make that sanity check. 1361 1362 if(spectrumList.isEmpty()) 1363 return; 1364 1365 if(spectrumList.size() < mp_oligomerList->size()) 1366 qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__); 1367 if(spectrumList.size() < incrementHash.size()) 1368 qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__); 1369 1370 // qDebug() << __FILE__ << __LINE__ 1371 // << "Done generating the unitary spectra for each oligomer."; 1372 1373 // Before computing the sum of all spectra to yield the last 1374 // spectrum, we have to perform the ordering of the spectra so 1375 // that the m/z regions covered by each are in an ascending order. 1376 1377 // We want to sort the spectra in ascending order. There are two 1378 // concepts: 1379 1380 // 1. one spectrum is greater than the other if its first point is 1381 // greater than the other's. 1382 1383 // 2. one spectrum is greater than the other, even if their first 1384 // points are equal, if its last point is greater then the 1385 // other's. That means two spectra starting at the same point are 1386 // different if they end at different points and the longest 1387 // spectrum sorts after the shortest. 1388 1389 // Only sort if there are at least two spectra ! 1390 1391 m_ui.logPlainTextEdit-> 1392 appendPlainText(tr("Sorting all oligomer-related sub-spectra...")); 1393 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1394 qApp->processEvents(); 1395 1396 if(spectrumList.size() >= 2) 1397 { 1398 bool sorted = false; 1399 1400 while(!sorted) 1401 { 1402 bool changed = false; 1403 1404 for(int iter = 0; iter < spectrumList.size() - 1; ++iter) 1405 { 1406 QList<QPointF *> *thisSpectrum = spectrumList.at(iter); 1407 QList<QPointF *> *nextSpectrum = spectrumList.at(iter + 1); 1408 1409 QPointF *thisFirstPoint = thisSpectrum->first(); 1410 QPointF *thisLastPoint = thisSpectrum->last(); 1411 1412 QPointF *nextFirstPoint = nextSpectrum->first(); 1413 QPointF *nextLastPoint = nextSpectrum->last(); 1414 1415 if(thisFirstPoint->x() > nextFirstPoint->x()) 1416 { 1417 // Reverse the spectra in the list, as this spectrum 1418 // is farther away in the mz axis than is the next 1419 // spectrum. 1420 1421 int thisIdx = iter; 1422 int nextIdx = iter + 1; 1423 1424 spectrumList.removeAt(thisIdx); 1425 spectrumList.insert(nextIdx, thisSpectrum); 1426 1427 changed = true; 1428 1429 continue; 1430 } 1431 1432 if(thisFirstPoint->x() == nextFirstPoint->x() && 1433 thisLastPoint->x() > nextLastPoint->x()) 1434 { 1435 // Reverse the spectra in the list, as this spectrum 1436 // is longer than is the next spectrum. 1437 1438 int thisIdx = iter; 1439 int nextIdx = iter + 1; 1440 1441 spectrumList.removeAt(thisIdx); 1442 spectrumList.insert(nextIdx, thisSpectrum); 1443 1444 changed = true; 1445 1446 continue; 1447 } 1448 } 1449 // End of 1450 // for(int iter = 0; iter < spectrumList.size() + 1; ++iter) 1451 // That is done making one iteration in the spectrum list. 1452 1453 // If changed is true, then that means that we still have at 1454 // least one round to do to finalize the sorting. 1455 1456 // If changed is false, then the spectrum list is sorted ! 1457 1458 sorted = !changed; 1459 } 1460 // End of 1461 // while(!sorted) 1462 // 1463 // That is, finished sorting of the spectrum list. 1464 } 1465 // End of 1466 // if(spectrumList.size() >= 2) 1467 1468 m_ui.logPlainTextEdit->textCursor().insertText(" Done.\n"); 1469 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1470 qApp->processEvents(); 1471 1472 // for(int iter = 0; iter < spectrumList.size(); ++iter) 1473 // { 1474 // QList<QPointF *> *spectrum = spectrumList.at(iter); 1475 1476 // QPointF *firstPoint = spectrum->first(); 1477 // QPointF *lastPoint = spectrum->last(); 1478 1479 // qDebug() << __FILE__ << __LINE__ 1480 // << "List of sorted spectra:\n"; 1481 1482 // qDebug() 1483 // << "Spectrum index:" << iter 1484 // << "spectrum: " << spectrum 1485 // << "first point:" << firstPoint->x() 1486 // << "last point:" << lastPoint->x(); 1487 // } 1488 1489 m_ui.logPlainTextEdit-> 1490 appendPlainText(tr("Computing sets of overlapping/non-overlapping " 1491 "sub-spectra...")); 1492 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1493 qApp->processEvents(); 1494 1495 // Compute sets of overlapping spectra or of a single spectra 1496 // non-overlapping spectrum, without loosing the sorting made 1497 // before. One set of spectra is thus a list of lists of QPointF 1498 // instances. 1499 // To make things clear: 1500 // 1501 // A spectrum: QList<QPointF *> 1502 // A set of spectra: QList<QList<QPointF *> *> 1503 // A list of set of spectra: Qlist<QList<QList<QPointF *> *> *> 1504 QList<QList<QList<QPointF *> *> *>spectrumSetList; 1505 1506 // Seed the iterations in the spectrum list at 0 1507 1508 // During one iteration, all the members of the spectrum list are 1509 // tested to check if there is an overlap between these iterated 1510 // members and the seeded spectrum. Each time an overlap is found, 1511 // the new range border points are stored for the next test. 1512 1513 // qDebug() << __FILE__ << __LINE__ 1514 // << "Before making spectral sets" 1515 // << "spectrumList size:" << spectrumList.size(); 1516 1517 // for(int iter = 0 ; iter < spectrumList.size() - 1; ++iter) 1518 while(!spectrumList.isEmpty()) 1519 { 1520 QList<QPointF *> *thisSpectrum = spectrumList.first(); 1521 1522 // Allocate a new set of spectra that will hold all the spectra 1523 // that are overlapping with thisSpectrum. 1524 1525 QList<QList<QPointF *> *> *newSpectrumSet = 1526 new QList<QList<QPointF *> *>; 1527 1528 // Transer right away thisSpectrum to the new set of overlapping 1529 // spectra (if it remains alone because no spectrum overlaps 1530 // with it, then fine. 1531 1532 spectrumList.removeOne(thisSpectrum); 1533 newSpectrumSet->append(thisSpectrum); 1534 spectrumSetList.append(newSpectrumSet); 1535 1536 // If there are overlaps, we'll have to document them using new 1537 // 'borders' of the current spectrum set. We seed these border's 1538 // points with thisSpectrum's first and last points. 1539 1540 // double newRangeFirstPointX = thisSpectrum->first()->x(); 1541 double newRangeLastPointX = thisSpectrum->last()->x(); 1542 1543 // qDebug() << __FILE__ << __LINE__ 1544 // << "\nNew set seeded with thisSpectrum:" << thisSpectrum 1545 // << "\n\t with last point x:" << thisSpectrum->last()->x() 1546 // << "\n\t and newRangeLastPointX:" << newRangeLastPointX; 1547 1548 // Now iterate in each remaining spectrum and check the overlap 1549 // with thisSpectrum. Remark that we can start at jter = 0 1550 // because we already removed the seeding spectrum for the list. 1551 1552 for(int jter = 0 ; jter < spectrumList.size(); ++jter) 1553 { 1554 QList<QPointF *> *nextSpectrum = spectrumList.at(jter); 1555 1556 double nextFirstPointX = nextSpectrum->first()->x(); 1557 // double nextLastPointX = nextSpectrum->last()->x(); 1558 1559 // Check if there is an overlap of thisSpectrum with 1560 // nextSpectrum. Because we have sorted the spectra in a 1561 // previous step, thisSpectrum is necessarily <= 1562 // nextSpectrum. So, make the tests accordingly. 1563 1564 if(nextFirstPointX < newRangeLastPointX) 1565 { 1566 // There is an overlap, as next spectrum has its first 1567 // point located inside the first spectrum. 1568 1569 // That means that we have to move nextSpectrum to the 1570 // set of overlapping spectra. 1571 1572 spectrumList.removeOne(nextSpectrum); 1573 newSpectrumSet->append(nextSpectrum); 1574 1575 // Update the new right border of the range. 1576 1577 newRangeLastPointX = nextSpectrum->last()->x(); 1578 1579 // We removed a spectrum from the list, thus decrement 1580 // jter so as to start a new loop at what was the next 1581 // spectrum in the list prior to the removal. 1582 --jter; 1583 } 1584 else 1585 { 1586 // There is no overlap between the last processed 1587 // spectrum, thus we break, which will "close" this 1588 // current set and start a new one. 1589 1590 break; 1591 } 1592 } 1593 // End of 1594 // for(int jter = 0 ; jter < spectrumList.size(); ++jter) 1595 } 1596 // End of 1597 // for(int iter = 0 ; iter < spectrumList.size() + 1; ++iter) 1598 1599 // If there was a last item in the spectrumList, it is necessarily a 1600 // non-overlapping item, thus allocate a last spectrumSet and 1601 // append the item to it. 1602 1603 if(spectrumList.size()) 1604 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); 1605 1606 m_ui.logPlainTextEdit->textCursor().insertText(" Done.\n\n"); 1607 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1608 qApp->processEvents(); 1609 1610 // At this point we have a list of spectrum sets which each might 1611 // contain one or more spectra. When more than one spectra are 1612 // contained in a spectrum set, then that means that they overlap 1613 // with each other. 1614 1615 m_ui.logPlainTextEdit-> 1616 textCursor().insertText(tr("Creating final spectrum... ")); 1617 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1618 qApp->processEvents(); 1619 1620 // Iterate in each spectrum set and see how to create the final 1621 // spectrum. 1622 1623 QList<QPointF *> finalSpectrum; 1624 1625 double pointCount = 0; 1626 1627 for(int iter = 0; iter < spectrumSetList.size(); ++iter) 1628 { 1629 QList<QList<QPointF *> *> *set = spectrumSetList.at(iter); 1630 1631 // Only iterate in the spectrum set if it contains overlapping 1632 // spectra, that is more than one single spectrum. Otherwise 1633 // just dump all the points of the non-overlapping spectrum into 1634 // the final spectrum. 1635 1636 if(set->size() == 1) 1637 { 1638 // Only one non-overlapping spectrum in the set, dump all 1639 // its points into the final spectrum. 1640 1641 while(!set->first()->isEmpty()) 1642 { 1643 finalSpectrum.append(set->first()->takeFirst()); 1644 1645 ++pointCount; 1646 if(pointCount >= 1000) 1647 { 1648 m_ui.logPlainTextEdit->textCursor().insertText(". "); 1649 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1650 qApp->processEvents(); 1651 1652 pointCount = 0; 1653 1654 qApp->processEvents(); 1655 } 1656 } 1657 1658 continue; 1659 } 1660 1661 // There are more than one spectrum in the set, that is these 1662 // spectra are overlapping. We have to ensure we sum all the 1663 // spectra one with the other. minMz is the first point of the 1664 // first spectrum of the set, while maxMz is the last point of 1665 // the last spectrum of the set. 1666 1667 double minMz = set->first()->first()->x(); 1668 double maxMz = set->last()->last()->x(); 1669 1670 double curMz = minMz; 1671 1672 while(curMz <= maxMz) 1673 { 1674 // qDebug() << __FILE__ << __LINE__ 1675 // << "Handling mz value:" << curMz; 1676 1677 // curMz now has a mz value for which we have to sum all the 1678 // intensities in each pointList. 1679 1680 double summedIntensity = 0; 1681 1682 // Iterate in each spectrum of the set of spectra and seek 1683 // the intensity at curMz. when found, add that intensity to 1684 // summedIntensity. 1685 1686 double increment = 0; 1687 1688 for(int jter = 0, jSize = set->size(); jter < jSize; ++jter) 1689 { 1690 QList<QPointF *> *curSpectrum = set->at(jter); 1691 1692 increment = incrementHash.value(curSpectrum); 1693 1694 // We are iterating in the list of points of a 1695 // spectrum. What is the intensity of the point at 1696 // x=curMz ? We provide a tolerance for x corresponding 1697 // to (increment / 2). 1698 1699 for(int kter = 0, 1700 kSize = curSpectrum->size(); kter < kSize; ++kter) 1701 { 1702 QPointF *point = curSpectrum->at(kter); 1703 1704 double mz = point->x(); 1705 1706 if(mz <= (curMz + increment / 2) && 1707 mz >= (curMz - increment / 2)) 1708 { 1709 summedIntensity += point->y(); 1710 1711 break; 1712 } 1713 // Else, go on to next point. If not point is 1714 // found at all, then, ok, we do not increment the 1715 // summedIntensity variable. 1716 } 1717 // At this point we have finished iterating in a given 1718 // spectrum (that is a point list). 1719 } 1720 // We have finished iterating in all the spectra of the set 1721 // for the current mz. Make a point (curMz,summedIntensity). 1722 1723 QPointF *newPoint = new QPointF(curMz, summedIntensity); 1724 1725 finalSpectrum.append(newPoint); 1726 1727 ++pointCount; 1728 if(pointCount >= 1000) 1729 { 1730 m_ui.logPlainTextEdit->textCursor().insertText(". "); 1731 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1732 qApp->processEvents(); 1733 1734 pointCount = 0; 1735 1736 if(m_aborted) 1737 break; 1738 } 1739 1740 // qDebug() << __FILE__ << __LINE__ 1741 // << "Added new point:" 1742 // << "x:" << newPoint->x() << "," << "y:" << newPoint->y() 1743 // << "\n"; 1744 1745 curMz += increment; 1746 } 1747 // End of while(curMz <= maxMz). That is, we have finished 1748 // creating (x,y) pairs for the whole x axis, that is the mz 1749 // ratio axis. 1750 } 1751 // End of 1752 // for(int iter = 0; iter < spectrumSetList.size(); ++iter) 1753 // 1754 // We have finished iterating in the set of spectra, that is we have 1755 // finished going through all the spectra, that is, we have finished 1756 // doing the final spectrum. 1757 1758 // qDebug() << __FILE__ << __LINE__ 1759 // << "Ended the creation of the final spectrum."; 1760 1761 m_ui.logPlainTextEdit-> 1762 textCursor().insertText("\n\nDone creating the spectrum.\n\n"); 1763 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1764 qApp->processEvents(); 1765 1766 // Finally, if the user had asked for the data to be sent to a 1767 // file... or to a string... Send the spectrum data either to the 1768 // file or to the text edit widget. 1769 1770 QString dataString; 1771 1772 if(!m_filePath.isEmpty()) 1773 { 1774 // Send all the data (that is, the points located in 1775 // QList<QPointF *>m_patternPointList) to the output file. 1776 1777 QFile file(m_filePath); 1778 1779 if(!file.open(QFile::WriteOnly | QFile::Truncate)) 1780 { 1781 QMessageBox::warning(this, 1782 tr("massXpert - Spectrum Calculator"), 1783 tr("Failed to export the isotopic pattern " 1784 "data to file."), 1785 QMessageBox::Ok); 1786 return; 1787 } 1788 1789 QTextStream stream(&file); 1790 1791 m_ui.logPlainTextEdit-> 1792 textCursor().insertText("Writing data to file... "); 1793 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1794 qApp->processEvents(); 1795 1796 for(int iter = 0, size = finalSpectrum.size(); 1797 iter < size ; ++iter) 1798 { 1799 QPointF *point = finalSpectrum.at(iter); 1800 1801 dataString += QString().setNum(point->x(), 'f', 10); 1802 dataString += " "; 1803 dataString += QString().setNum(point->y(), 'f', 10); 1804 dataString += "\n"; 1805 1806 // Stream the data by packets of 4096 or less characters. 1807 if(dataString.size() > 4096) 1808 { 1809 stream << dataString; 1810 dataString.clear(); 1811 1812 m_ui.logPlainTextEdit->textCursor().insertText(". "); 1813 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1814 qApp->processEvents(); 1815 1816 if(m_aborted) 1817 break; 1818 } 1819 // qDebug() << __FILE__ << __LINE__ << dataString; 1820 } 1821 1822 // One last time: 1823 stream << dataString; 1824 dataString.clear(); 1825 1826 // And finally: 1827 stream.flush(); 1828 file.close(); 1829 1830 m_ui.logPlainTextEdit-> 1831 textCursor().insertText("\n\nDone writing " 1832 "data to file.\n\n"); 1833 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1834 qApp->processEvents(); 1835 1836 return; 1837 } 1838 else 1839 { 1840 m_ui.logPlainTextEdit-> 1841 textCursor().insertText("Writing data to results tab... "); 1842 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1843 qApp->processEvents(); 1844 1845 for(int iter = 0, size = finalSpectrum.size(); 1846 iter < size ; ++iter) 1847 { 1848 QPointF *point = finalSpectrum.at(iter); 1849 1850 dataString += QString().setNum(point->x(), 'f', 10); 1851 dataString += " "; 1852 dataString += QString().setNum(point->y(), 'f', 10); 1853 dataString += "\n"; 1854 1855 // Append the data by packets of 4096 or less characters. 1856 if(dataString.size() > 4096) 1857 { 1858 m_ui.resultPlainTextEdit->appendPlainText(dataString); 1859 dataString.clear(); 1860 1861 m_ui.logPlainTextEdit->textCursor().insertText(". "); 1862 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1863 qApp->processEvents(); 1864 1865 if(m_aborted) 1866 break; 1867 } 1868 1869 } 1870 1871 // One last time: 1872 m_ui.resultPlainTextEdit->appendPlainText(dataString); 1873 dataString.clear(); 1874 1875 m_ui.logPlainTextEdit-> 1876 textCursor().insertText("\n\nDone writing " 1877 "data to results tab.\n\n"); 1878 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1879 qApp->processEvents(); 1880 } 1881 1882 1883 // Finally, we have a bunch of data to free. 1884 1885 QPointF *point = 0; 1886 1887 // Free all the spectrum sets along with all the QPointF instances 1888 // contained in their spectra. 1889 while(!spectrumSetList.isEmpty()) 1890 { 1891 QList<QList<QPointF *> *> *spectrumList = spectrumSetList.takeFirst(); 1892 1893 for(int iter = 0, size = spectrumList->size(); iter < size; ++iter) 1894 { 1895 QList<QPointF *> *curSpectrum = spectrumList->at(iter); 1896 1897 foreach(point, *curSpectrum) 1898 delete point; 1899 1900 curSpectrum->clear(); 1901 1902 delete curSpectrum; 1903 } 1904 1905 delete spectrumList; 1906 } 1907 1908 // qDebug() << __FILE__ << __LINE__ 1909 // << "Done freeing up material."; 1910 1911 foreach(point, finalSpectrum) 1912 { 1913 // qDebug() << point->x() << " " << point->y(); 1914 1915 delete point; 1916 } 1917 1918 finalSpectrum.clear(); 1919 } 1920 1921 1922 void executeSpectrumWithCluster(QList<QList<QPointF * > * > * spectrumList,QHash<QList<QPointF * > *,double> * incrementHash)1923 SpectrumCalculationDlg::executeSpectrumWithCluster 1924 (QList<QList<QPointF *> *> *spectrumList, 1925 QHash<QList<QPointF *> *, double> *incrementHash) 1926 { 1927 if(!spectrumList) 1928 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 1929 1930 if(!incrementHash) 1931 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 1932 1933 m_ui.logPlainTextEdit-> 1934 textCursor().insertText(tr("Simulating a spectrum " 1935 "with calculation of an isotopic " 1936 "cluster for each oligomer.\n\n")); 1937 1938 m_ui.logPlainTextEdit-> 1939 textCursor().insertText(tr("There are %1 oligomers\n" 1940 "Calculating sub-spectrum " 1941 "for each\n\n") 1942 .arg(mp_oligomerList->size())); 1943 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1944 qApp->processEvents(); 1945 1946 for(int iter = 0, 1947 size = mp_oligomerList->size(); iter < size; ++iter) 1948 { 1949 Oligomer *oligomer = mp_oligomerList->at(iter); 1950 1951 // 1. Does the user want a isotopic cluster calculation for 1952 // each oligomer in the list? If so we have to manage a list 1953 // of IsotopicPatternCalculator instances. 1954 1955 // Since we have to perform an isotopic cluster 1956 // calculation, retrieve the elemental composition. 1957 StringProp *prop = 1958 static_cast<StringProp *>(oligomer-> 1959 prop("ELEMENTAL_COMPOSITION")); 1960 1961 if(!prop) 1962 { 1963 // qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 1964 1965 QMessageBox::warning(this, 1966 tr("massXpert - Spectrum Calculator"), 1967 tr("Elemental composition not found for " 1968 "current oligomer.\n Uncheck the Isotopic " 1969 "cluster checkbox for this calculation."), 1970 QMessageBox::Ok); 1971 1972 return; 1973 } 1974 1975 QString *formulaString = static_cast<QString *>(prop->data()); 1976 1977 Formula formula(*formulaString); 1978 1979 m_ui.logPlainTextEdit-> 1980 textCursor().insertText(tr("Computing isotopic cluster" 1981 " for oligomer %1\n" 1982 "\tformula: %2.\n") 1983 .arg(iter + 1) 1984 .arg(*formulaString)); 1985 1986 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 1987 qApp->processEvents(); 1988 1989 // We want to validate the formula and in the mean time 1990 // construct the list of all the AtomCount objects(first 1991 // param true), and since the formula is never reused we 1992 // do not need to reset (second param false). m_atomList 1993 // is a reference to the atom list of the polymer 1994 // chemistry definition currently used in the caller 1995 // calculator window. 1996 1997 m_ui.logPlainTextEdit-> 1998 textCursor().insertText(tr(" Validating formula... ")); 1999 qApp->processEvents(); 2000 2001 if(!formula.validate(m_atomList, true, false)) 2002 { 2003 m_ui.inputDataFeedbackLineEdit->setText(tr("Formula error")); 2004 2005 m_ui.logPlainTextEdit->textCursor().insertText(tr(" Failure.\n\n")); 2006 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2007 qApp->processEvents(); 2008 2009 return; 2010 } 2011 2012 m_ui.logPlainTextEdit->textCursor().insertText(tr("Success.\n")); 2013 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2014 qApp->processEvents(); 2015 2016 double mono = oligomer->mono(); 2017 double charge = oligomer->charge(); 2018 2019 // Because we have the mono m/z, we can compute the fwhm: 2020 double fwhm = (mono / m_resolution); 2021 m_config.setFwhm(fwhm); 2022 2023 double increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) / 2024 m_config.pointNumber(); 2025 2026 // But we have to take into account the charge: 2027 if(charge) 2028 increment = increment / charge; 2029 2030 m_config.setIncrement(increment); 2031 2032 m_ui.logPlainTextEdit-> 2033 textCursor().insertText(tr("\tmono m/z: %1\n" 2034 "\tcharge: %2\n" 2035 "\tfwhm: %3\n" 2036 "\tincrement: %4\n\n") 2037 .arg(mono) 2038 .arg(charge) 2039 .arg(fwhm) 2040 .arg(increment)); 2041 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2042 qApp->processEvents(); 2043 2044 // qDebug() << __FILE__ << __LINE__ 2045 // << "index:" << iter << "\n" 2046 // << "formula: " << formula.formula() << "\n" 2047 // << "charge:" << charge << "\n" 2048 // << "mono m/z:" << mono << "\n" 2049 // << "avg m/z:" << oligomer->avg() << "\n" 2050 // << "resolution:" << m_resolution << "\n" 2051 // << "fwhm:" << fwhm << "\n" 2052 // << "Whole config:" << m_config.config(); 2053 2054 IsotopicPatternCalculator *calculator = 2055 new IsotopicPatternCalculator(formula, charge, 2056 m_maximumPeaks, 2057 m_minimumProbability, 2058 m_atomList, m_config); 2059 2060 // Perform the calculation... 2061 const QList<QPointF *> &pointList = calculator->sumPeakShapes(); 2062 2063 if(pointList.isEmpty()) 2064 qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__); 2065 2066 QList<QPointF *> *newPointList = new QList<QPointF *>; 2067 2068 calculator->transferPoints(newPointList); 2069 2070 spectrumList->append(newPointList); 2071 2072 m_ui.logPlainTextEdit-> 2073 textCursor().insertText(tr("\t\tDone computing the cluster\n\n")); 2074 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2075 qApp->processEvents(); 2076 2077 // Store along with the point list, the increment that was 2078 // used for that calculation. We'll need it later when 2079 // computing the whole spectrum. 2080 2081 incrementHash->insert(newPointList, m_config.increment()); 2082 2083 // At this point we have the points, we can delete the 2084 // calculator. 2085 delete calculator; 2086 2087 if(m_aborted) 2088 break; 2089 } 2090 // End of 2091 // for(int iter = 0, 2092 // size = mp_oligomerList->size(); iter < size; ++iter) 2093 2094 m_ui.logPlainTextEdit-> 2095 textCursor().insertText(tr("Done computing *all* the clusters\n\n")); 2096 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2097 qApp->processEvents(); 2098 } 2099 2100 2101 void executeSpectrumWithoutCluster(QList<QList<QPointF * > * > * spectrumList,QHash<QList<QPointF * > *,double> * incrementHash)2102 SpectrumCalculationDlg::executeSpectrumWithoutCluster 2103 (QList<QList<QPointF *> *> *spectrumList, 2104 QHash<QList<QPointF *> *, double> *incrementHash) 2105 { 2106 if(!spectrumList) 2107 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 2108 2109 if(!incrementHash) 2110 qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__); 2111 2112 m_ui.logPlainTextEdit-> 2113 textCursor().insertText(tr("Simulating a spectrum " 2114 "without calculation of isotopic " 2115 "clusters for each " 2116 "oligomer.\n\n")); 2117 2118 m_ui.logPlainTextEdit->appendPlainText(tr("There are %1 oligomers\n" 2119 "Calculating sub-spectrum " 2120 "for each\n\n") 2121 .arg(mp_oligomerList->size())); 2122 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2123 qApp->processEvents(); 2124 2125 for(int iter = 0, 2126 size = mp_oligomerList->size(); iter < size; ++iter) 2127 { 2128 Oligomer *oligomer = mp_oligomerList->at(iter); 2129 2130 // Not asking for an isotopic cluster to be computed for 2131 // each oligomer. The user want the mono or avg mass of 2132 // the oligomer to be considered the centroid of a peak 2133 // 2134 double mz = 0; 2135 2136 if(m_withMonoMass) 2137 mz = oligomer->mono(); 2138 else 2139 mz = oligomer->avg(); 2140 2141 double charge = oligomer->charge(); 2142 2143 PeakCentroid centroid(mz, 100, 1); 2144 2145 // Because we have the m/z, we can compute the fwhm: 2146 double fwhm = (mz / m_resolution); 2147 m_config.setFwhm(fwhm); 2148 2149 double increment = (MXP_FWHM_PEAK_SPAN_FACTOR * fwhm) / 2150 m_config.pointNumber(); 2151 2152 // But we have to take into account the charge: 2153 if(charge) 2154 increment = increment / charge; 2155 2156 m_config.setIncrement(increment); 2157 2158 m_ui.logPlainTextEdit-> 2159 textCursor().insertText(tr("Computing peak shape for oligomer %1\n") 2160 .arg(iter + 1)); 2161 2162 if(m_withMonoMass) 2163 m_ui.logPlainTextEdit-> 2164 textCursor().insertText(tr("\tmono m/z: %1\n") 2165 .arg(mz)); 2166 else 2167 m_ui.logPlainTextEdit-> 2168 textCursor().insertText(tr("\tavg m/z: %1\n") 2169 .arg(mz)); 2170 2171 m_ui.logPlainTextEdit-> 2172 textCursor().insertText(tr("\tcharge: %2\n" 2173 "\tfwhm: %3\n" 2174 "\tincrement: %4\n\n") 2175 .arg(charge) 2176 .arg(fwhm) 2177 .arg(increment)); 2178 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2179 qApp->processEvents(); 2180 2181 // qDebug() << __FILE__ << __LINE__ 2182 // << "index:" << iter << "\n" 2183 // << "charge:" << charge << "\n" 2184 // << "m/z:" << mz << "\n" 2185 // << "avg m/z:" << oligomer->avg() << "\n" 2186 // << "resolution:" << m_resolution << "\n" 2187 // << "fwhm:" << fwhm << "\n" 2188 // << "Whole config:" << m_config.config(); 2189 2190 PeakShape *shape = new PeakShape(centroid, m_config); 2191 2192 shape->calculatePeakShape(); 2193 2194 const QList<QPointF *> &pointList = shape->pointList(); 2195 2196 if(pointList.isEmpty()) 2197 qFatal("Fatal error at %s@%d.Aborting.", __FILE__, __LINE__); 2198 2199 QList<QPointF *> *newPointList = new QList<QPointF *>; 2200 2201 shape->transferPoints(newPointList); 2202 2203 spectrumList->append(newPointList); 2204 2205 m_ui.logPlainTextEdit-> 2206 textCursor().insertText(tr("\t\tDone computing the peak shape\n\n")); 2207 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2208 qApp->processEvents(); 2209 2210 // Store along with the point list, the increment that was 2211 // used for that calculation. We'll need it later when 2212 // computing the whole spectrum. 2213 2214 incrementHash->insert(newPointList, m_config.increment()); 2215 2216 // At this point we have the points, we can delete the 2217 // peack shape. 2218 delete shape; 2219 2220 if(m_aborted) 2221 break; 2222 } 2223 // End of 2224 // for(int iter = 0, 2225 // size = mp_oligomerList->size(); iter < size; ++iter) 2226 2227 m_ui.logPlainTextEdit-> 2228 textCursor().insertText(tr("Done computing *all* the peak shapes\n\n")); 2229 // m_ui.logPlainTextEdit->moveCursor(QTextCursor::Down); 2230 qApp->processEvents(); 2231 } 2232 2233 2234 void spectrumCalculationProgressValueChanged(int value)2235 SpectrumCalculationDlg::spectrumCalculationProgressValueChanged 2236 (int value) 2237 { 2238 m_ui.progressBar->setValue(value); 2239 // qDebug() << __FILE__ << __LINE__ 2240 // << "spectrumCalculationProgressValueChanged:" << value; 2241 qApp->processEvents(); 2242 } 2243 2244 2245 void spectrumCalculationMessageChanged(QString text)2246 SpectrumCalculationDlg::spectrumCalculationMessageChanged 2247 (QString text) 2248 { 2249 m_ui.logPlainTextEdit->appendPlainText(text); 2250 // qDebug() << __FILE__ << __LINE__ 2251 // << "spectrumCalculationMessageChanged:" << text; 2252 qApp->processEvents(); 2253 } 2254 2255 2256 void abort()2257 SpectrumCalculationDlg::abort() 2258 { 2259 m_aborted = true; 2260 emit(spectrumCalculationAborted()); 2261 } 2262 2263 2264 void outputFile()2265 SpectrumCalculationDlg::outputFile() 2266 { 2267 QString name; 2268 2269 m_filePath = 2270 QFileDialog::getSaveFileName(this, tr("Export Raw Text File"), 2271 QDir::homePath(), 2272 tr("Any file type(*)")); 2273 } 2274 2275 void debugPutStdErrBitset(QString file,int line,int value,const QString & text)2276 SpectrumCalculationDlg::debugPutStdErrBitset(QString file, int line, 2277 int value, 2278 const QString &text) 2279 { 2280 qDebug() << file << line << "\n" 2281 << GlobBinaryRepresentation(MXP_VALIDATION_ERRORS_NONE) 2282 << ": MXP_VALIDATION_ERRORS_NONE" << "\n" 2283 << GlobBinaryRepresentation(MXP_VALIDATION_FORMULA_ERRORS) 2284 << ": MXP_VALIDATION_FORMULA_ERRORS" << "\n" 2285 << GlobBinaryRepresentation(MXP_VALIDATION_RESOLUTION_ERRORS) 2286 << ": MXP_VALIDATION_RESOLUTION_ERRORS" << "\n" 2287 << GlobBinaryRepresentation(MXP_VALIDATION_FWHM_ERRORS) 2288 << ": MXP_VALIDATION_FWHM_ERRORS" << "\n" 2289 << "\n" 2290 << GlobBinaryRepresentation(value) 2291 << ":" << text << ":" << value 2292 << "\n"; 2293 } 2294 2295 } // namespace massXpert 2296 2297 2298 2299 #if 0 2300 /*************************************************************************** 2301 * file : ipc.c * 2302 * copyright :(C) 2001-2005 by Dirk Nolting * 2303 * email : nolting@uni-duesseldorf.de * 2304 ***************************************************************************/ 2305 2306 /*************************************************************************** 2307 * * 2308 * This program is free software; you can redistribute it and/or modify * 2309 * it under the terms of the GNU General Public License as published by * 2310 * the Free Software Foundation; either version~2 of the License, or * 2311 * (at your option) any later version. * 2312 * * 2313 ***************************************************************************/ 2314 2315 #include "global.h" 2316 #include "ipc.h" 2317 #include "pars.h" 2318 #include "element.h" 2319 #include "gp_out.h" 2320 #include <time.h> 2321 #include <signal.h> 2322 #include <math.h> 2323 2324 //#define SUMMARIZE_LEVEL 0.00000001 2325 //#define SUMMARIZE_LEVEL 0.0001 2326 //#define SUMMARIZE_LEVEL 0.001 2327 #define SUMMARIZE_LEVEL 0.01 2328 2329 compound *verbindung=NULL; 2330 isotope *m_peakList; 2331 int fast_calc=0; 2332 2333 void free_list(isotope *target) 2334 { 2335 while(target->next) 2336 { 2337 target=target->next; 2338 free(target->previous); 2339 } 2340 free(target); 2341 } 2342 2343 void cut_peaks(isotope *spectrum) 2344 { 2345 int dummy=1; 2346 2347 while((spectrum->next) &&(dummy<fast_calc) ) 2348 { 2349 ++dummy; 2350 spectrum=spectrum->next; 2351 } 2352 2353 if(spectrum->next) 2354 { 2355 free_list(spectrum->next); 2356 spectrum->next=NULL; 2357 } 2358 } 2359 2360 void summarize_peaks() 2361 { isotope *dummy,*d2; 2362 2363 for(dummy=m_peakList;dummy;dummy=dummy->next) 2364 /* Differenz wegen Rundungsfehlern */ 2365 while( dummy->next &&(dummy->next->mass - dummy->mass < SUMMARIZE_LEVEL) ) 2366 { 2367 d2=dummy->next; 2368 dummy->next=d2->next; 2369 if(dummy->next) 2370 dummy->next->previous=dummy; 2371 dummy->p+=d2->p; 2372 free(d2); 2373 } 2374 } 2375 2376 isotope *add_peak(isotope *base,isotope *peak) 2377 { 2378 static isotope *IsotopeIterator; 2379 2380 if(!(base->mass)) 2381 { 2382 peak->next=NULL; 2383 peak->previous=NULL; 2384 IsotopeIterator = peak; 2385 return peak; 2386 } 2387 2388 if( peak->mass >= IsotopeIterator->mass ) 2389 while((IsotopeIterator->next) &&(IsotopeIterator->mass < peak->mass)) 2390 IsotopeIterator=IsotopeIterator->next; 2391 else 2392 { 2393 while((IsotopeIterator->previous) &&(IsotopeIterator->mass > peak->mass) ) 2394 IsotopeIterator=IsotopeIterator->previous; 2395 IsotopeIterator=IsotopeIterator->next; 2396 } 2397 2398 if((IsotopeIterator->mass) >=(peak->mass) ) 2399 { 2400 peak->next=IsotopeIterator; 2401 peak->previous=IsotopeIterator->previous; 2402 peak->previous->next=peak; 2403 IsotopeIterator->previous=peak; 2404 return base; 2405 } 2406 else 2407 { 2408 IsotopeIterator->next=peak; 2409 peak->next=NULL; 2410 peak->previous=IsotopeIterator; 2411 return base; 2412 } 2413 return 0; 2414 } 2415 2416 int calculate_peaks(){ 2417 compound *c; 2418 isotope *newPeakList,*p,*i,*np1; 2419 int amount; 2420 2421 if(!(m_peakList=malloc(sizeof(isotope)))) 2422 return 0; 2423 m_peakList->mass=0; 2424 m_peakList->p=1; 2425 m_peakList->previous=NULL; 2426 m_peakList->next=NULL; 2427 2428 for (c = verbindung; c; c = c->next) 2429 { 2430 for(amount = 0; amount < c->amount; ++amount) 2431 { 2432 if (!(newPeakList=malloc(sizeof(isotope)))) 2433 return 0; 2434 2435 newPeakList->mass = 0; 2436 2437 for (p = m_peakList; p; p = p->next) 2438 { 2439 for(i = c->isotopes; i; i = i->next) 2440 { 2441 //printf("working on isotope of mass %f\n", i->mass); 2442 2443 if (!(np1 = malloc(sizeof(isotope)))) 2444 return 0; 2445 2446 np1->mass=p->mass + i->mass; 2447 np1->p=p->p * i->p; 2448 2449 if(!(newPeakList = add_peak(newPeakList,np1))) 2450 return 0; 2451 } 2452 } 2453 2454 free_list(m_peakList); 2455 m_peakList = newPeakList; 2456 summarize_peaks(); 2457 2458 if (fast_calc) 2459 cut_peaks(m_peakList); 2460 } 2461 } 2462 2463 return 1; 2464 } 2465 2466 2467 void print_result(int digits,int charge){ 2468 isotope *d; 2469 double maxp=0,relint=0,sump=0; 2470 int permutationen=0; 2471 2472 printf("\n"); 2473 2474 for(d=m_peakList;d;d=d->next) 2475 { 2476 ++permutationen; 2477 sump+=d->p; 2478 d->mass=d->mass / charge; 2479 d->mass=(rint( d->mass * pow(10,digits) ) / pow(10,digits) ); 2480 } 2481 2482 summarize_peaks(); 2483 for(d=m_peakList;d;d=d->next) 2484 if(d->p > maxp) 2485 maxp=d->p; 2486 2487 for(d=m_peakList;d;d=d->next) 2488 { 2489 if(( relint=(d->p/maxp)*100) > MIN_INT ) 2490 printf("M= %f, p= %e, rel. Int.= %f%%\n", 2491 d->mass,d->p,relint); 2492 } 2493 if(!(fast_calc)) 2494 printf("\nNumber of permutations: %i\n",permutationen); 2495 else 2496 { 2497 sump=(rint(sump*10000)/100); 2498 printf("\nCovered Intensity: %2.2f%% \n",sump); 2499 } 2500 } 2501 2502 int main(int argc,char **argv){ 2503 long seconds; 2504 int d=1,zeig_summenformel=0,calc_peaks=1,gnuplot=0,use_digits=USE_DIGITS,charge=1; 2505 char *gnuplotfile=NULL; 2506 2507 if(!argv[d]) 2508 { 2509 usage(); 2510 return 1; 2511 } 2512 2513 #ifdef HAVE_SIGNAL 2514 signal(SIGHUP,SIG_IGN); 2515 #endif 2516 2517 if(!init_elements()){ 2518 printf("Error in init_elements\n"); 2519 return 1; 2520 } 2521 while(argv[d]) 2522 { 2523 if(!strcmp(argv[d],"-f")) 2524 { 2525 ++d; 2526 if(!argv[d]) 2527 { 2528 printf("Missing argument for -f\n"); 2529 return 1; 2530 } 2531 fast_calc=strtol(argv[d],NULL,10); 2532 } 2533 2534 else if(!strcmp(argv[d],"-z")) 2535 { 2536 ++d; 2537 if(!argv[d]) 2538 { 2539 printf("Missing argument for -z\n"); 2540 return 1; 2541 } 2542 charge=strtol(argv[d],NULL,10); 2543 } 2544 2545 else if(!strcmp(argv[d],"-d")) 2546 { 2547 ++d; 2548 if(!argv[d]) 2549 { 2550 printf("Missing argument for -d\n"); 2551 return 1; 2552 } 2553 use_digits=strtol(argv[d],NULL,10); 2554 } 2555 2556 else if(!strcmp(argv[d],"-c")){ 2557 ++d; 2558 if(!argv[d]) 2559 { 2560 printf("Missing argument for -c\n"); 2561 return 1; 2562 } 2563 if(!pars_chem_form(argv[d])){ 2564 printf("Parser error.\n"); 2565 return 1; 2566 } 2567 } 2568 2569 else if(!strcmp(argv[d],"-g")){ 2570 ++d; 2571 if(!argv[d]){ 2572 printf("Missing argument for -g\n"); 2573 return 1; 2574 } 2575 gnuplot=1; 2576 if(!(gnuplotfile=strdup(argv[d]))){ 2577 printf("Not enough memory\n"); 2578 return 1; 2579 } 2580 } 2581 2582 2583 else if(!strcmp(argv[d],"-p")){ 2584 ++d; 2585 if(!argv[d]){ 2586 printf("Missing argument for -p\n"); 2587 return 1; 2588 } 2589 if(!(pars_peptid(argv[d]))){ 2590 printf("Error in peptid parser.\n"); 2591 return 1; 2592 } 2593 } 2594 2595 else if(!strcmp(argv[d],"-a")){ 2596 ++d; 2597 if(!argv[d]){ 2598 printf("Missing argument for -a\n"); 2599 return 1; 2600 } 2601 if(!pars_amino_acid(argv[d])){ 2602 printf("Error in pars_amino_acid.\n"); 2603 return 1; 2604 } 2605 } 2606 2607 else if(!strcmp(argv[d],"-s")) 2608 zeig_summenformel=1; 2609 2610 else if(!strcmp(argv[d],"-h")) 2611 { 2612 usage(); 2613 return 1; 2614 } 2615 2616 else if(!strcmp(argv[d],"-x")) 2617 calc_peaks=0; 2618 2619 else 2620 { 2621 printf("Unknown flag: %s\n",argv[d]); 2622 usage(); 2623 return 1; 2624 } 2625 ++d; 2626 } 2627 2628 if(zeig_summenformel) 2629 { 2630 if(!print_sum()) 2631 { 2632 printf("error while showing chemical formula.\n"); 2633 return 1; 2634 } 2635 } 2636 #ifdef HAVE_TIME 2637 seconds=time(0); 2638 #endif 2639 2640 if(calc_peaks) 2641 if(!calculate_peaks()){ 2642 printf("Error in calculate_peaks\n"); 2643 return 1; 2644 } 2645 2646 print_result(use_digits,charge); 2647 2648 #ifdef HAVE_TIME 2649 seconds=time(0)-seconds; 2650 printf("Computing time: %li seconds.\n",seconds); 2651 #endif 2652 2653 if(gnuplot) 2654 if(!(make_gnuplot_output(gnuplotfile))){ 2655 printf("Error while generating gnuplot file\n"); 2656 return 1; 2657 } 2658 2659 return 0; 2660 } 2661 2662 2663 void usage() 2664 { 2665 printf("\nThis is IPC v %s\nCopyright Dirk Nolting 2001-2005\n\n",VERSION); 2666 printf("\nSynopsis:\n ipc -d <int> -z <int> -f <int> <-a <amino acid> -c <chemical formula> -p <File> -g <name> -s -x -h\n\n"); 2667 2668 printf("-c <chemical formula> calculates the isotopic pattern of the \n"); 2669 printf(" given chemical formula. Additive with -p\n"); 2670 2671 printf("-p <File> reads peptide sequenz(one letter notation) from the \n"); 2672 printf(" given file and calculates the isotopic pattern. Additive with -c\n"); 2673 2674 printf("-a <amino acids> calculate isotopic pattern from given amino acids\n"); 2675 printf(" in one letter notation\n"); 2676 2677 printf("-s gives the chemical formula. Usefull for -a or -p\n"); 2678 printf("-g creates gnuplot output and stores it in the file <name> and <name>.gnu\n"); 2679 2680 printf("-x no calculation of the isotopic pattern. Usefull for -s\n"); 2681 printf("-f fast calc, calculates only first <int> peaks\n"); 2682 printf("-d <int> digits significant\n"); 2683 printf("-z assume <int> charges on ion \n"); 2684 printf("-h show this text\n\n"); 2685 } 2686 #endif 2687