From 8c89b6c37d962c3e619fa363bdb3a7df86964f1d Mon Sep 17 00:00:00 2001 From: Dmitry Avtonomov Date: Mon, 3 Dec 2018 02:02:21 -0800 Subject: [PATCH] Release v1.0 --- .gitignore | 842 ++++++++ build.gradle | 83 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 56172 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 ++ gradlew.bat | 84 + libs/balloontip-1.2.4.1-src.zip | Bin 0 -> 44200 bytes libs/balloontip-1.2.4.1.jar | Bin 0 -> 61925 bytes libs/org-netbeans-swing-outline-8.2-src.zip | Bin 0 -> 154030 bytes libs/org-netbeans-swing-outline-8.2.jar | Bin 0 -> 288703 bytes logo/delta_logo.psd | Bin 0 -> 279659 bytes logo/delta_logo_128.png | Bin 0 -> 5978 bytes logo/delta_logo_16.png | Bin 0 -> 1497 bytes logo/delta_logo_256.png | Bin 0 -> 13325 bytes logo/delta_logo_32.png | Bin 0 -> 2161 bytes logo/delta_logo_48.png | Bin 0 -> 2751 bytes logo/delta_logo_64.png | Bin 0 -> 3396 bytes logo/delta_logo_96.png | Bin 0 -> 4680 bytes logo/delta_logo_full_size.png | Bin 0 -> 13325 bytes logo/delta_logo_splash.png | Bin 0 -> 31653 bytes logo/delta_logo_splash.psd | Bin 0 -> 364588 bytes logo/delta_logo_up-to-256.ico | Bin 0 -> 150669 bytes pom.xml | 356 ++++ settings.gradle | 7 + .../com/dmtavt/deltamass/Bundle.properties | 1 + .../com/dmtavt/deltamass/DeltaMassGui.java | 88 + .../com/dmtavt/deltamass/DeltaMassInfo.java | 100 + .../com/dmtavt/deltamass/DeltaMassMain.java | 121 ++ .../dmtavt/deltamass/DeltaMassMainGui.java | 9 + .../dmtavt/deltamass/args/DecoyTreatment.java | 7 + .../com/dmtavt/deltamass/args/Kernel.java | 6 + .../dmtavt/deltamass/args/MassCorrection.java | 10 + .../converters/DecimalFormatConverter.java | 23 + .../converters/DecimalFormatValidator.java | 21 + .../converters/DecimalFormatWithTostring.java | 23 + .../ExistingReadablePathValidator.java | 26 + .../args/converters/PathConverter.java | 16 + .../args/converters/RegexConverter.java | 24 + .../args/converters/RegexValidator.java | 19 + .../converters/StringToPathValidator.java | 21 + .../dmtavt/deltamass/data/PepSearchFile.java | 38 + .../deltamass/data/PepSearchResult.java | 222 ++ .../java/com/dmtavt/deltamass/data/Spm.java | 49 + .../dmtavt/deltamass/kde/GaussFasterKF.java | 76 + .../com/dmtavt/deltamass/kde/KdeKludge.java | 592 ++++++ .../com/dmtavt/deltamass/kde/KdeUtils.java | 37 + .../deltamass/kde/KernelFuncPrime2.java | 20 + .../logging/LogbackJTextPaneAppender.java | 185 ++ .../dmtavt/deltamass/logic/CacheLocator.java | 33 + .../dmtavt/deltamass/logic/CommandClean.java | 40 + .../dmtavt/deltamass/logic/CommandGui.java | 15 + .../dmtavt/deltamass/logic/CommandPeaks.java | 25 + .../dmtavt/deltamass/logic/CommandPlot.java | 29 + .../dmtavt/deltamass/logic/LogicClean.java | 161 ++ .../com/dmtavt/deltamass/logic/LogicGui.java | 125 ++ .../deltamass/logic/LogicInputFiles.java | 441 ++++ .../com/dmtavt/deltamass/logic/LogicKde.java | 219 ++ .../dmtavt/deltamass/logic/LogicPeaks.java | 411 ++++ .../com/dmtavt/deltamass/logic/LogicPlot.java | 362 ++++ .../deltamass/logic/MassCorrectionMs1.java | 471 +++++ .../dmtavt/deltamass/logic/MassCorrector.java | 9 + .../deltamass/logic/MassCorrectorNone.java | 16 + .../logic/MassCorrectorZeroPeak.java | 127 ++ .../deltamass/logic/UserOptsInputFiles.java | 88 + .../dmtavt/deltamass/logic/UserOptsKde.java | 28 + .../dmtavt/deltamass/logic/UserOptsPeaks.java | 58 + .../dmtavt/deltamass/logic/UserOptsPlot.java | 26 + .../dmtavt/deltamass/messages/MsgCleanup.java | 14 + .../deltamass/messages/MsgFlushGuiCache.java | 9 + .../deltamass/messages/MsgModsInRange.java | 27 + .../deltamass/messages/MsgPlotClicked.java | 22 + .../deltamass/messages/MsgPsmsRequest.java | 13 + .../deltamass/messages/MsgPsmsResponse.java | 12 + .../messages/MsgRequestHistogram.java | 12 + .../dmtavt/deltamass/messages/MsgRunPlot.java | 13 + .../deltamass/messages/MsgSelectionClear.java | 5 + .../deltamass/messages/MsgSelectionMade.java | 12 + .../dmtavt/deltamass/messages/MsgStop.java | 9 + .../deltamass/messages/MsgSwitchView.java | 12 + .../dmtavt/deltamass/messages/MsgTest.java | 9 + .../messages/MsgVersionUpdateInfo.java | 48 + .../dmtavt/deltamass/parsers/ILcmsParser.java | 10 + .../deltamass/parsers/IParserFactory.java | 8 + .../deltamass/parsers/IPepidParser.java | 10 + .../deltamass/parsers/LcmsParserRegistry.java | 29 + .../dmtavt/deltamass/parsers/MzmlParser.java | 44 + .../dmtavt/deltamass/parsers/MzxmlParser.java | 44 + .../deltamass/parsers/ParsingException.java | 24 + .../parsers/PepidParserRegistry.java | 30 + .../deltamass/parsers/PepxmlParser.java | 286 +++ .../deltamass/predicates/ComparisonType.java | 85 + .../predicates/DoubleSpmPredicate.java | 37 + .../deltamass/predicates/ISpmPredicate.java | 10 + .../deltamass/predicates/IntSpmPredicate.java | 32 + .../predicates/SpmPredicateFactory.java | 57 + .../deltamass/ui/ChartPanelPatched.java | 273 +++ .../deltamass/ui/DeltaMassOptionsForm.java | 769 +++++++ .../deltamass/ui/DeltaMassUiElements.java | 10 + .../dmtavt/deltamass/ui/DeltamassDisplay.java | 737 +++++++ .../com/dmtavt/deltamass/ui/FormEntry.java | 93 + .../dmtavt/deltamass/ui/FrameSerializer.java | 48 + .../com/dmtavt/deltamass/ui/PlotFactory.java | 596 ++++++ .../com/dmtavt/deltamass/ui/SimpleETable.java | 106 + .../dmtavt/deltamass/ui/SimpleTableModel.java | 74 + .../dmtavt/deltamass/utils/ArrayUtils.java | 88 + .../com/dmtavt/deltamass/utils/GridUtils.java | 55 + .../com/dmtavt/deltamass/utils/NumUtils.java | 9 + .../com/dmtavt/deltamass/utils/PeakUtils.java | 297 +++ .../com/dmtavt/deltamass/utils/TextUtils.java | 51 + .../dmtavt/deltamass/ui/delta_logo_128.png | Bin 0 -> 5978 bytes .../com/dmtavt/deltamass/ui/delta_logo_16.png | Bin 0 -> 1497 bytes .../dmtavt/deltamass/ui/delta_logo_256.png | Bin 0 -> 13325 bytes .../com/dmtavt/deltamass/ui/delta_logo_32.png | Bin 0 -> 2161 bytes .../com/dmtavt/deltamass/ui/delta_logo_48.png | Bin 0 -> 2751 bytes .../com/dmtavt/deltamass/ui/delta_logo_64.png | Bin 0 -> 3396 bytes .../com/dmtavt/deltamass/ui/delta_logo_96.png | Bin 0 -> 4680 bytes .../dmtavt/deltamass/ui/delta_logo_splash.png | Bin 0 -> 31653 bytes src/main/resources/icons/delta_logo_128.png | Bin 0 -> 5978 bytes src/main/resources/icons/delta_logo_16.png | Bin 0 -> 1497 bytes src/main/resources/icons/delta_logo_256.png | Bin 0 -> 13325 bytes src/main/resources/icons/delta_logo_32.png | Bin 0 -> 2161 bytes src/main/resources/icons/delta_logo_48.png | Bin 0 -> 2751 bytes src/main/resources/icons/delta_logo_64.png | Bin 0 -> 3396 bytes src/main/resources/icons/delta_logo_96.png | Bin 0 -> 4680 bytes .../resources/icons/delta_logo_splash.png | Bin 0 -> 31653 bytes src/main/resources/logback.xml | 13 + .../stat/distribution/ComparableHolder.java | 38 + .../smile/stat/distribution/DataProvider.java | 32 + .../distribution/GaussMixtureWeighted.java | 356 ++++ .../distribution/MixtureComponentPeak.java | 38 + .../smile/stat/distribution/NormalDist.java | 98 + .../stat/distribution/WeightProvider.java | 31 + .../java/umich/opensearch/kde/BinReader.java | 132 ++ .../java/umich/opensearch/kde/KDE.java | 33 + .../java/umich/opensearch/kde/KDEBin.java | 432 ++++ .../java/umich/opensearch/kde/KDEMain.java | 903 +++++++++ .../umich/opensearch/kde/KdeBinParams.java | 55 + .../opensearch/kde/OpenSearchParams.java | 704 +++++++ .../umich/opensearch/kde/PeaksWriter.java | 34 + .../java/umich/opensearch/kde/Version.java | 29 + .../opensearch/kde/api/IWeightedData.java | 27 + .../opensearch/kde/api/IWeightedValues.java | 26 + .../java/umich/opensearch/kde/api/Pair.java | 43 + .../opensearch/kde/api/PepXmlContent.java | 557 +++++ .../opensearch/kde/api/SearchHitDiff.java | 144 ++ .../opensearch/kde/api/SearchHitDiffs.java | 244 +++ .../umich/opensearch/kde/gui/KDEGuiProps.java | 75 + .../opensearch/kde/gui/KDEOptionsGui.form | 920 +++++++++ .../opensearch/kde/gui/KDEOptionsGui.java | 1789 +++++++++++++++++ .../umich/opensearch/kde/gui/batman_350.png | Bin 0 -> 18396 bytes .../kde/gui/batman_logo_square_84.png | Bin 0 -> 5881 bytes .../umich/opensearch/kde/gui/batmass_16.png | Bin 0 -> 669 bytes .../umich/opensearch/kde/gui/batmass_32.png | Bin 0 -> 1346 bytes .../umich/opensearch/kde/gui/batmass_48.png | Bin 0 -> 2112 bytes .../opensearch/kde/gui/delta_logo_128.png | Bin 0 -> 5978 bytes .../opensearch/kde/gui/delta_logo_16.png | Bin 0 -> 1497 bytes .../opensearch/kde/gui/delta_logo_256.png | Bin 0 -> 13325 bytes .../opensearch/kde/gui/delta_logo_32.png | Bin 0 -> 2161 bytes .../opensearch/kde/gui/delta_logo_48.png | Bin 0 -> 2751 bytes .../opensearch/kde/gui/delta_logo_64.png | Bin 0 -> 3396 bytes .../opensearch/kde/gui/delta_logo_96.png | Bin 0 -> 4680 bytes .../opensearch/kde/gui/delta_logo_splash.png | Bin 0 -> 31653 bytes .../kde/impl/WeightedDataDelimitedFile.java | 70 + .../kde/impl/WeightedDataPepXmlList.java | 133 ++ .../kde/jfree/ChartPanelPathched.java | 277 +++ .../opensearch/kde/jfree/InfoWindow.java | 160 ++ .../opensearch/kde/jfree/JFreeChartPlot.java | 600 ++++++ .../opensearch/kde/jfree/TextConsole.java | 46 + .../umich/opensearch/kde/jsat/KDEKludge.java | 580 ++++++ .../umich/opensearch/kde/jsat/KDEUtils.java | 43 + .../opensearch/kde/logging/LogHelper.java | 69 + .../kde/logging/LogbackJTextPaneAppender.java | 197 ++ .../opensearch/kde/logging/logging.properties | 52 + .../kde/params/CSVFormatValidator.java | 21 + .../kde/params/DecimalFormatParameter.java | 37 + .../opensearch/kde/params/DecoyTreatment.java | 26 + .../kde/params/DoubleParameter.java | 8 + .../kde/params/ExistingPathValidator.java | 38 + .../opensearch/kde/params/FileChecker.java | 28 + .../opensearch/kde/params/IWeightFetcher.java | 25 + .../opensearch/kde/params/KDEKernelType.java | 23 + .../opensearch/kde/params/MassCorrection.java | 175 ++ .../opensearch/kde/params/NamedBandwidth.java | 83 + .../kde/params/PathCreatorParameter.java | 16 + .../kde/params/PathListParameter.java | 24 + .../opensearch/kde/params/PathParameter.java | 32 + .../opensearch/kde/params/PepXmlScore.java | 19 + .../ScorePredicateFactoryParameter.java | 56 + .../params/WeightFetcherSearchHitDiff.java | 36 + .../params/WriteableDirectoryParameter.java | 47 + .../kde/params/denoise/Denoiser.java | 42 + .../kde/params/denoise/Denoising.java | 21 + .../params/denoise/FusedLassoDenoiser.java | 131 ++ .../kde/params/denoise/NoOpDenoiser.java | 46 + .../kde/params/denoise/NumberedParams.java | 22 + .../params/denoise/NumberedParamsDefault.java | 27 + .../denoise/TotalVariationDenoiser.java | 146 ++ .../kde/params/kernels/GaussFasterKF.java | 76 + .../kde/params/kernels/KernelFuncPrime2.java | 21 + .../kde/params/predicates/ComparisonType.java | 41 + .../params/predicates/IsDecoyPredicate.java | 19 + .../params/predicates/IsForwardPredicate.java | 19 + .../predicates/MassDiffRangePredicate.java | 56 + .../kde/params/predicates/NamedPredicate.java | 15 + .../kde/params/predicates/ScorePredicate.java | 127 ++ .../kde/params/predicates/Symbols.java | 15 + .../opensearch/kde/pepxml/DecoyDetector.java | 11 + .../kde/pepxml/DecoyDetectorAcceptAll.java | 19 + .../kde/pepxml/DecoyDetectorByProtName.java | 33 + .../kde/pepxml/MsmsRunSummaryUtils.java | 241 +++ .../opensearch/kde/pepxml/SearchHitCount.java | 23 + .../opensearch/kde/util/ArrayFilter.java | 109 + .../opensearch/kde/util/BracketUtils.java | 233 +++ .../kde/util/DecimalFormatWithToString.java | 26 + .../kde/util/DelimitedFileUtil.java | 113 ++ .../kde/util/DistributionResult.java | 107 + .../opensearch/kde/util/IndexBracket.java | 59 + .../umich/opensearch/kde/util/MapUtils.java | 62 + .../umich/opensearch/kde/util/Maximum.java | 21 + .../java/umich/opensearch/kde/util/Peak.java | 39 + .../umich/opensearch/kde/util/PeakApprox.java | 58 + .../umich/opensearch/kde/util/PeakOutput.java | 158 ++ .../opensearch/kde/util/PeakProminence.java | 119 ++ .../umich/opensearch/kde/util/PeakUtils.java | 933 +++++++++ .../umich/opensearch/kde/util/Predicate.java | 25 + .../kde/util/SimpleConcurrentList.java | 113 ++ src/oldMain/resources/logback.xml | 13 + src/oldMain/resources/simplelogger.properties | 44 + .../umich/opensearch/kde/VersionTest.java | 55 + .../dmtavt/deltamass/ptm/PtmFactoryTest.java | 24 + .../deltamass/utils/ArrayUtilsTest.java | 180 ++ .../dmtavt/deltamass/utils/GridUtilsTest.java | 96 + 232 files changed, 23508 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 libs/balloontip-1.2.4.1-src.zip create mode 100644 libs/balloontip-1.2.4.1.jar create mode 100644 libs/org-netbeans-swing-outline-8.2-src.zip create mode 100644 libs/org-netbeans-swing-outline-8.2.jar create mode 100644 logo/delta_logo.psd create mode 100644 logo/delta_logo_128.png create mode 100644 logo/delta_logo_16.png create mode 100644 logo/delta_logo_256.png create mode 100644 logo/delta_logo_32.png create mode 100644 logo/delta_logo_48.png create mode 100644 logo/delta_logo_64.png create mode 100644 logo/delta_logo_96.png create mode 100644 logo/delta_logo_full_size.png create mode 100644 logo/delta_logo_splash.png create mode 100644 logo/delta_logo_splash.psd create mode 100644 logo/delta_logo_up-to-256.ico create mode 100644 pom.xml create mode 100644 settings.gradle create mode 100644 src/main/java/com/dmtavt/deltamass/Bundle.properties create mode 100644 src/main/java/com/dmtavt/deltamass/DeltaMassGui.java create mode 100644 src/main/java/com/dmtavt/deltamass/DeltaMassInfo.java create mode 100644 src/main/java/com/dmtavt/deltamass/DeltaMassMain.java create mode 100644 src/main/java/com/dmtavt/deltamass/DeltaMassMainGui.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/DecoyTreatment.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/Kernel.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/MassCorrection.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatConverter.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatValidator.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatWithTostring.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/ExistingReadablePathValidator.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/PathConverter.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/RegexConverter.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/RegexValidator.java create mode 100644 src/main/java/com/dmtavt/deltamass/args/converters/StringToPathValidator.java create mode 100644 src/main/java/com/dmtavt/deltamass/data/PepSearchFile.java create mode 100644 src/main/java/com/dmtavt/deltamass/data/PepSearchResult.java create mode 100644 src/main/java/com/dmtavt/deltamass/data/Spm.java create mode 100644 src/main/java/com/dmtavt/deltamass/kde/GaussFasterKF.java create mode 100644 src/main/java/com/dmtavt/deltamass/kde/KdeKludge.java create mode 100644 src/main/java/com/dmtavt/deltamass/kde/KdeUtils.java create mode 100644 src/main/java/com/dmtavt/deltamass/kde/KernelFuncPrime2.java create mode 100644 src/main/java/com/dmtavt/deltamass/logging/LogbackJTextPaneAppender.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/CacheLocator.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/CommandClean.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/CommandGui.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/CommandPeaks.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/CommandPlot.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/LogicClean.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/LogicGui.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/LogicInputFiles.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/LogicKde.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/LogicPeaks.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/LogicPlot.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/MassCorrectionMs1.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/MassCorrector.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/MassCorrectorNone.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/MassCorrectorZeroPeak.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/UserOptsInputFiles.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/UserOptsKde.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/UserOptsPeaks.java create mode 100644 src/main/java/com/dmtavt/deltamass/logic/UserOptsPlot.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgCleanup.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgFlushGuiCache.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgModsInRange.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgPlotClicked.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgPsmsRequest.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgPsmsResponse.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgRequestHistogram.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgRunPlot.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgSelectionClear.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgSelectionMade.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgStop.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgSwitchView.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgTest.java create mode 100644 src/main/java/com/dmtavt/deltamass/messages/MsgVersionUpdateInfo.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/ILcmsParser.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/IParserFactory.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/IPepidParser.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/LcmsParserRegistry.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/MzmlParser.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/MzxmlParser.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/ParsingException.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/PepidParserRegistry.java create mode 100644 src/main/java/com/dmtavt/deltamass/parsers/PepxmlParser.java create mode 100644 src/main/java/com/dmtavt/deltamass/predicates/ComparisonType.java create mode 100644 src/main/java/com/dmtavt/deltamass/predicates/DoubleSpmPredicate.java create mode 100644 src/main/java/com/dmtavt/deltamass/predicates/ISpmPredicate.java create mode 100644 src/main/java/com/dmtavt/deltamass/predicates/IntSpmPredicate.java create mode 100644 src/main/java/com/dmtavt/deltamass/predicates/SpmPredicateFactory.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/ChartPanelPatched.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/DeltaMassOptionsForm.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/DeltaMassUiElements.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/DeltamassDisplay.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/FormEntry.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/FrameSerializer.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/PlotFactory.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/SimpleETable.java create mode 100644 src/main/java/com/dmtavt/deltamass/ui/SimpleTableModel.java create mode 100644 src/main/java/com/dmtavt/deltamass/utils/ArrayUtils.java create mode 100644 src/main/java/com/dmtavt/deltamass/utils/GridUtils.java create mode 100644 src/main/java/com/dmtavt/deltamass/utils/NumUtils.java create mode 100644 src/main/java/com/dmtavt/deltamass/utils/PeakUtils.java create mode 100644 src/main/java/com/dmtavt/deltamass/utils/TextUtils.java create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_128.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_16.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_256.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_32.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_48.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_64.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_96.png create mode 100644 src/main/resources/com/dmtavt/deltamass/ui/delta_logo_splash.png create mode 100644 src/main/resources/icons/delta_logo_128.png create mode 100644 src/main/resources/icons/delta_logo_16.png create mode 100644 src/main/resources/icons/delta_logo_256.png create mode 100644 src/main/resources/icons/delta_logo_32.png create mode 100644 src/main/resources/icons/delta_logo_48.png create mode 100644 src/main/resources/icons/delta_logo_64.png create mode 100644 src/main/resources/icons/delta_logo_96.png create mode 100644 src/main/resources/icons/delta_logo_splash.png create mode 100644 src/main/resources/logback.xml create mode 100644 src/oldMain/java/smile/stat/distribution/ComparableHolder.java create mode 100644 src/oldMain/java/smile/stat/distribution/DataProvider.java create mode 100644 src/oldMain/java/smile/stat/distribution/GaussMixtureWeighted.java create mode 100644 src/oldMain/java/smile/stat/distribution/MixtureComponentPeak.java create mode 100644 src/oldMain/java/smile/stat/distribution/NormalDist.java create mode 100644 src/oldMain/java/smile/stat/distribution/WeightProvider.java create mode 100644 src/oldMain/java/umich/opensearch/kde/BinReader.java create mode 100644 src/oldMain/java/umich/opensearch/kde/KDE.java create mode 100644 src/oldMain/java/umich/opensearch/kde/KDEBin.java create mode 100644 src/oldMain/java/umich/opensearch/kde/KDEMain.java create mode 100644 src/oldMain/java/umich/opensearch/kde/KdeBinParams.java create mode 100644 src/oldMain/java/umich/opensearch/kde/OpenSearchParams.java create mode 100644 src/oldMain/java/umich/opensearch/kde/PeaksWriter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/Version.java create mode 100644 src/oldMain/java/umich/opensearch/kde/api/IWeightedData.java create mode 100644 src/oldMain/java/umich/opensearch/kde/api/IWeightedValues.java create mode 100644 src/oldMain/java/umich/opensearch/kde/api/Pair.java create mode 100644 src/oldMain/java/umich/opensearch/kde/api/PepXmlContent.java create mode 100644 src/oldMain/java/umich/opensearch/kde/api/SearchHitDiff.java create mode 100644 src/oldMain/java/umich/opensearch/kde/api/SearchHitDiffs.java create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/KDEGuiProps.java create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.form create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.java create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/batman_350.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/batman_logo_square_84.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/batmass_16.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/batmass_32.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/batmass_48.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_128.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_16.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_256.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_32.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_48.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_64.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_96.png create mode 100644 src/oldMain/java/umich/opensearch/kde/gui/delta_logo_splash.png create mode 100644 src/oldMain/java/umich/opensearch/kde/impl/WeightedDataDelimitedFile.java create mode 100644 src/oldMain/java/umich/opensearch/kde/impl/WeightedDataPepXmlList.java create mode 100644 src/oldMain/java/umich/opensearch/kde/jfree/ChartPanelPathched.java create mode 100644 src/oldMain/java/umich/opensearch/kde/jfree/InfoWindow.java create mode 100644 src/oldMain/java/umich/opensearch/kde/jfree/JFreeChartPlot.java create mode 100644 src/oldMain/java/umich/opensearch/kde/jfree/TextConsole.java create mode 100644 src/oldMain/java/umich/opensearch/kde/jsat/KDEKludge.java create mode 100644 src/oldMain/java/umich/opensearch/kde/jsat/KDEUtils.java create mode 100644 src/oldMain/java/umich/opensearch/kde/logging/LogHelper.java create mode 100644 src/oldMain/java/umich/opensearch/kde/logging/LogbackJTextPaneAppender.java create mode 100644 src/oldMain/java/umich/opensearch/kde/logging/logging.properties create mode 100644 src/oldMain/java/umich/opensearch/kde/params/CSVFormatValidator.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/DecimalFormatParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/DecoyTreatment.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/DoubleParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/ExistingPathValidator.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/FileChecker.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/IWeightFetcher.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/KDEKernelType.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/MassCorrection.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/NamedBandwidth.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/PathCreatorParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/PathListParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/PathParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/PepXmlScore.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/ScorePredicateFactoryParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/WeightFetcherSearchHitDiff.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/WriteableDirectoryParameter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/Denoiser.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/Denoising.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/FusedLassoDenoiser.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/NoOpDenoiser.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParams.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParamsDefault.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/denoise/TotalVariationDenoiser.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/kernels/GaussFasterKF.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/kernels/KernelFuncPrime2.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/ComparisonType.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/IsDecoyPredicate.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/IsForwardPredicate.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/MassDiffRangePredicate.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/NamedPredicate.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/ScorePredicate.java create mode 100644 src/oldMain/java/umich/opensearch/kde/params/predicates/Symbols.java create mode 100644 src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetector.java create mode 100644 src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorAcceptAll.java create mode 100644 src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorByProtName.java create mode 100644 src/oldMain/java/umich/opensearch/kde/pepxml/MsmsRunSummaryUtils.java create mode 100644 src/oldMain/java/umich/opensearch/kde/pepxml/SearchHitCount.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/ArrayFilter.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/BracketUtils.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/DecimalFormatWithToString.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/DelimitedFileUtil.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/DistributionResult.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/IndexBracket.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/MapUtils.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/Maximum.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/Peak.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/PeakApprox.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/PeakOutput.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/PeakProminence.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/PeakUtils.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/Predicate.java create mode 100644 src/oldMain/java/umich/opensearch/kde/util/SimpleConcurrentList.java create mode 100644 src/oldMain/resources/logback.xml create mode 100644 src/oldMain/resources/simplelogger.properties create mode 100644 src/oldTest/java/umich/opensearch/kde/VersionTest.java create mode 100644 src/test/java/com/dmtavt/deltamass/ptm/PtmFactoryTest.java create mode 100644 src/test/java/com/dmtavt/deltamass/utils/ArrayUtilsTest.java create mode 100644 src/test/java/com/dmtavt/deltamass/utils/GridUtilsTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ff2239 --- /dev/null +++ b/.gitignore @@ -0,0 +1,842 @@ +# Created by https://www.gitignore.io/api/vim,java,maven,macos,linux,emacs,csharp,gradle,eclipse,windows,netbeans,intellij,notepadpp,visualstudio,visualstudiocode + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +### Emacs ### +# -*- mode: gitignore; -*- +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +nbdist/ +.nb-gradle/ + +### NotepadPP ### +# Notepad++ backups # + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Build results + +# Visual Studio 2015/2017 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files + +# MSTest test Results + +# NUNIT + +# Build Results of an ATL Project + +# Benchmark Results + +# .NET Core + +# StyleCop + +# Files built by Visual Studio + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# Visual Studio Trace Files + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# JustCode is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# AxoCover is a Code Coverage Tool + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted + +# NuGet Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# JetBrains Rider + +# CodeRush + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio + +# Telerik's JustMock configuration file + +# BizTalk build output + +# OpenCover UI analysis results + +# Azure Stream Analytics local run output + +# MSBuild Binary and Structured Log + +# NVidia Nsight GPU debugger configuration file + +# MFractors (Xamarin productivity tool) working folder + +# Local History for Visual Studio + + +# End of https://www.gitignore.io/api/vim,java,maven,macos,linux,emacs,csharp,gradle,eclipse,windows,netbeans,intellij,notepadpp,visualstudio,visualstudiocode + +## My additions +!libs/* +*.kde-cache +*.iml \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..72f2e63 --- /dev/null +++ b/build.gradle @@ -0,0 +1,83 @@ +plugins { + id 'java' + id 'application' + id 'maven' + id 'idea' + id 'com.github.johnrengelman.shadow' version '4.0.3' + id 'edu.sc.seis.launch4j' version '2.4.4' + id "com.github.ben-manes.versions" version '0.20.0' +} + +group = 'com.dmtavt' +version = '1.0' + +description = """DeltaMass - detection and visualization of common mass shifts in proteomic searches.""" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + + +mainClassName = 'com.dmtavt.deltamass.DeltaMassMain' +jar { + manifest { + attributes 'Main-Class': "${mainClassName}" + } +} + +shadowJar { + classifier = null + minimize() +} + +launch4j { + mainClassName = 'com.dmtavt.deltamass.DeltaMassMainGui' + icon = "${projectDir}/logo/delta_logo_up-to-256.ico" + outputDir = 'libs' + dontWrapJar = false + downloadUrl = 'https://adoptopenjdk.net/' +} + +repositories { + mavenLocal() + mavenCentral() +} +dependencies { + // our projects + implementation("com.github.chhh:dmtavt-utils:0.4.4") + implementation("com.github.chhh:msftbx:1.8.6") + implementation("com.github.chhh:ptm-factory:1.0") + implementation("edu.umich.andykong:mscalibrator:1.1") + + // basic + implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' + implementation group: 'com.beust', name: 'jcommander', version: '1.72' + + // commons + implementation group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1' + compile 'commons-io:commons-io:2.6' + + // other + //implementation group: 'org.jfree', name: 'jfreechart', version: '1.0.19' + compile 'org.greenrobot:eventbus:3.1.1' + compile 'com.miglayout:miglayout-swing:5.2' + compile 'org.jfree:jfreechart:1.5.0' + + implementation group: 'com.edwardraff', name: 'JSAT', version: '0.0.9' + implementation(group: 'com.github.haifengl', name: 'smile-core', version: '1.5.2') { + exclude(module: 'smile-data') + exclude(module: 'smile-graph') + } + + // downloaded / precompiled jars + compile fileTree(dir: 'libs', include: '*.jar') + + // test + testImplementation(group: 'org.apache.maven', name: 'maven-model', version: '3.6.0') + testImplementation group: 'junit', name: 'junit', version: '4.12' + testCompile("org.assertj:assertj-core:3.11.1") +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..28861d273a5d270fd8f65dd74570c17c9c507736 GIT binary patch literal 56172 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

ZM+x{iQDVZRnLYbdXrQjU&=u%hsN4|smH&B~F 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 b+=;!jU|P+J`>$CfFsoU|bwOU-ceCsYH7qU$ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..115e6ac --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs/balloontip-1.2.4.1-src.zip b/libs/balloontip-1.2.4.1-src.zip new file mode 100644 index 0000000000000000000000000000000000000000..83b4bf36d04631a05a75fe42f5946eceef8b8dce GIT binary patch literal 44200 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�^ADJq#ExN%=m!&RM6^aA`q4?_B_ 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�m3fJa;3mO}9VqT>_ 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

*0njvyd4W@GvH575CJKq-7(WXYZn0BebJD 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

!lm%-4o&+Sokzv9tn8<+&9K-C`OZhVrf!ReeQye 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

JKlC_JJh7a> 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 literal 0 HcmV?d00001 diff --git a/libs/org-netbeans-swing-outline-8.2.jar b/libs/org-netbeans-swing-outline-8.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..836fc29c4814e69d9b2f31c93395126950df8486 GIT binary patch literal 288703 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 G(EkJBKC-L; literal 0 HcmV?d00001 diff --git a/logo/delta_logo.psd b/logo/delta_logo.psd new file mode 100644 index 0000000000000000000000000000000000000000..49b3559c630642c5407aa2e77af5469311662c78 GIT binary patch literal 279659 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 literal 0 HcmV?d00001 diff --git a/logo/delta_logo_32.png b/logo/delta_logo_32.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6f3df3c60f7cb04f2c3bd9bfe52896cbca3d0f GIT binary patch literal 2161 zcmbVOc~}#78jgr9AfOaPuIdmER3vvMM}sjpR1!`F3o1DVl1ef$2_zh@2w@RH@Bl4~ zwq7fBDOW`i4^XKnaus+`uA++qmDZxj0-|6iBJTd-(?52enfV>_zVCPZzVCS^BS0ju zv2w9Op-?uyKHMN=1Wb>`C&*iykT8Y}&N^P0E*Or|>BSld#gf4i2=G;kqoE*3EQ{Y& z4>3@v`B93HFkP6?k1mCk7_kY1F(}mt8--#p4QjD;C!_-;P_#njjUKsLg9a2bZ*(YG zh!d(gkX+%Dpn-xDL?P0Iol=?%&D;Pm40J?53F*XuK^ddc(hc6|ce-?BZraA8fp-wy zPH*(AQ(?jYfCFnFfQ%t}NpW~Q0D>4ii421H^#B2fCtz{N4SL~8bTUXMP=NOr8nLF4 zMbU$}{P(tyl{Z?h)2ZoLtX{9j=!qCu6OF~wXf%@sf#8K8ytMHuo!H=|(yo|O;6hrd zMxoXzU=?6e6ieV(oi`fsbT$N~S}6P=tkS+u6p}KmL9E8&F*vMJX^QI|TB{3!{_Dm^ z(b|xBHG~a^T;#`nM%Xa zNGuA4zz50HIj*lts}rlF(41WbVt1BH<^Ls@&e1?(9jpm~;h4D!2#~`%SSyFs04Ep( z)`TilGFY!&YdW6UXt|I^5eLcm8dwRu%P(E=5&IyIh9~1lBobvdZW^CL;^EkA4jJUJ zDQq74Jy-Vs4(xs79W}iQXw6qL0aRNJ)hr0q0CSDa#~iV>EjPUs9gkB{N@~-=1>hnAZc3#lZOK31js{VLz#G=Ewm0`KlCw5&KCxXy4E_rK?~+3$kA$ zaL|lp4U6)#&eUAWHAmao5IuWr>b6OPONv5-9M7b(5c594*`TWVN!5<)?l2O!mGqA# zF7f@r3Y>@U&wFZcY2SEk5R=Q`KD(F|THoY&-|F8BryVNUk=x}sk#qJ{xmiy=#;p5fU`|bv(KV3vQXZA&pEv*MWMSbjN50^x#M1AJK4Hr)T+edp z5vTsecRN-a7wuYhC?i_|958SnP9(rPK8^nNN|ocIn_WwXzAh5Do&C{eY4YScfXCG|ut@b?xEeg8bXQ?y+s@neN`?1#u5O$8)My7~h5z{$BbjNsKXT z7o2a=FRM6ueWJ9%D2$&eJGyNotvhvj3X56y>y{#$Y!KwMpC<+YjmEQ1)eogMb)$hp zAxp2W?VX-UTWm;q94Y#>y_nseo|`wdVx(@ll{|)i#?ui_HvW|7_e*Q3KgYl3UPuL} zpGl~F+*x(nvoxYzJ@_~KT|-IcwrWYWbHul9lBv#4r>MfF;-#$IaEWR=D(D)uNpaf8?s%Q$hdui#>o{V@5FuE*e*NV&f`fpYFXF}8Pd^)&gY%Lp;t z_Dlk$|6ci<-m%X!EPp?L#rBlUk|9XS0OhB`iD@^F^Bv;1{pV0#pq-?-K5i2fDM7Q9 zdT#zUjMk9}ChqpjN(OhQcM3(M!|hC~ET{6B{h6Wm<$ScN_V&`GwrV28o<*5*NgDY>?fTKV8L!~N0eykMu7^We?-b@8|KOb>if z#A$)u*2>IDRC$g~Omkeoa3$tt!}_$aNVE0hyHC1$#8)?c*LZY5IOWW~^ZH>;_FKD1 YRLQ{~!`_@qE-?KB`0_;D5_V+DpRE>g82|tP literal 0 HcmV?d00001 diff --git a/logo/delta_logo_48.png b/logo/delta_logo_48.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4aab4e996fe8ee067382903f6014a960ef0c49 GIT binary patch literal 2751 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 AO#lD@ literal 0 HcmV?d00001 diff --git a/logo/delta_logo_96.png b/logo/delta_logo_96.png new file mode 100644 index 0000000000000000000000000000000000000000..74a6bf5513692095e6bf33ff6a4fe8ebd63736d2 GIT binary patch literal 4680 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 literal 0 HcmV?d00001 diff --git a/logo/delta_logo_full_size.png b/logo/delta_logo_full_size.png new file mode 100644 index 0000000000000000000000000000000000000000..32d510f983d7e00deadfce42cc4c3f621d5c05ab GIT binary patch literal 13325 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 JPDHLkV1m4)3gQ3& literal 0 HcmV?d00001 diff --git a/logo/delta_logo_splash.psd b/logo/delta_logo_splash.psd new file mode 100644 index 0000000000000000000000000000000000000000..903eeeb6b896ef95da049c55000b088cfaa5396e GIT binary patch literal 364588 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

N&Wrm0$xw~aNPj&qu^lX>vT1XmlSQOdz?iv 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⊀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 literal 0 HcmV?d00001 diff --git a/logo/delta_logo_up-to-256.ico b/logo/delta_logo_up-to-256.ico new file mode 100644 index 0000000000000000000000000000000000000000..9b50b7af9ec024914b7787de54e3a871507db652 GIT binary patch literal 150669 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"Z4j0R+AguoTK*VEkO#Rw6#ce2bJhcfJt+8( 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 + + + + 4.0.0 + + com.github.chhh + okde + 1.6-SNAPSHOT + jar + + Open Search KDE + + + + chhh + Dmitry Avtonomov + https://github.com/chhh/ + + author + maintainer + + + + + + 1.8 + 1.8 + UTF-8 + UTF-8 + umich.opensearch.kde.KDEMain + + + + + disable-java8-doclint + + [1.8,) + + + -Xdoclint:none + + + + + + + + src/main/resources + + + src/main/java + + **/*.java + + + **/*.properties + **/*.png + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + ${maven.compiler.target} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + + package + + shade + + + + + + ${mainClass} + + + false + + + org.jfree:jfreechart + + ** + org/jfree/resources/* + + + + + + + + + + + + + + + + ch.qos.logback + logback-classic + 1.2.3 + + + + + com.gitgub.chhh + jcobyla + 1.0 + + + com.github.chhh + utils + 0.4.4 + + + com.github.chhh + msftbx + 1.8.2 + + + guava + com.google.guava + + + + + + com.github.chhh + ptm-parser + 1.0 + + + + + com.github.davidmoten + rtree + 0.8.4 + + + com.beust + jcommander + 1.72 + + + org.apache.commons + commons-math3 + 3.6.1 + + + net.sourceforge.parallelcolt + parallelcolt + 0.10.1 + + + * + * + + + + + org.apache.commons + commons-pool2 + 2.4.2 + + + com.google.guava + guava + 24.1-jre + + + * + * + + + + + com.edwardraff + JSAT + 0.0.9 + + + com.googlecode.combinatoricslib + combinatoricslib + 2.1 + + + org.apache.commons + commons-csv + 1.5 + + + org.apache.commons + commons-lang3 + 3.7 + + + org.jfree + jfreechart + 1.0.19 + + + com.github.haifengl + smile-core + 1.5.1 + + + smile-data + com.github.haifengl + + + smile-graph + com.github.haifengl + + + + + net.sourceforge.owlapi + owlapi-distribution + 5.1.3 + + + trove4j + net.sf.trove4j + + + xz + org.tukaani + + + commons-io + commons-io + + + jackson-databind + com.fasterxml.jackson.core + + + guava + com.google.guava + + + owlapi-compatibility + net.sourceforge.owlapi + + + jsonld-java + com.github.jsonld-java + + + guice-multibindings + com.google.inject.extensions + + + guice-assistedinject + com.google.inject.extensions + + + guice + com.google.inject + + + jcl-over-slf4j + org.slf4j + + + jackson-core + com.fasterxml.jackson.core + + + jackson-annotations + com.fasterxml.jackson.core + + + slf4j-api + org.slf4j + + + jsr305 + com.google.code.findbugs + + + commons-rdf-api + org.apache.commons + + + org.eclipse.rdf4j + * + + + + + + + org.apache.maven + maven-model + 3.5.2 + test + + + commons-lang3 + org.apache.commons + + + + + junit + junit + 4.12 + test + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9169c2d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,7 @@ +rootProject.name = 'deltamass' + +//include('dmtavt-utils') +includeBuild('../dmtavt-utils') +includeBuild('../lib-msftbx/lib-msftbx-io/') +includeBuild('../../com/mscalibrator/') +includeBuild('../ptm-factory/') diff --git a/src/main/java/com/dmtavt/deltamass/Bundle.properties b/src/main/java/com/dmtavt/deltamass/Bundle.properties new file mode 100644 index 0000000..266e39b --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/Bundle.properties @@ -0,0 +1 @@ +deltamass.version.current=1.0 diff --git a/src/main/java/com/dmtavt/deltamass/DeltaMassGui.java b/src/main/java/com/dmtavt/deltamass/DeltaMassGui.java new file mode 100644 index 0000000..f819f90 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/DeltaMassGui.java @@ -0,0 +1,88 @@ +package com.dmtavt.deltamass; + +import com.dmtavt.deltamass.logging.LogbackJTextPaneAppender; +import com.dmtavt.deltamass.ui.DeltaMassUiElements; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.swing.TextConsole; +import java.awt.Color; +import java.awt.HeadlessException; +import java.awt.Paint; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.UIManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeltaMassGui extends JFrame { + private static final Logger log = LoggerFactory.getLogger(DeltaMassGui.class); + private LogbackJTextPaneAppender appender; + public static final List ICON_NAMES = Arrays.asList( + "delta_logo_16.png", "delta_logo_32.png", "delta_logo_48.png", + "delta_logo_64.png", "delta_logo_96.png", "delta_logo_128.png", "delta_logo_256.png"); + private ExecutorService exec; + private TextConsole console; + private JScrollPane scrollConsole; + + public static final Paint[] COLORS_BETTER = new Paint[] { + new Color(106,61,154), + new Color(255,127,0), + new Color(227,26,28), + new Color(51,160,44), + new Color(31,120,180), + new Color(202,178,214), + new Color(253,191,111), + new Color(251,154,153), + new Color(178,223,138), + new Color(166,206,227), + }; + + public DeltaMassGui() throws HeadlessException { + super(); + initComponents(); + initBehaviors(); + initLaf(); + } + + private void initComponents() { + + } + + private void initBehaviors() { + setTitle(DeltaMassInfo.getNameVersion()); + exec = Executors.newCachedThreadPool(); + SwingUtils.setFrameIcons(this, ICON_NAMES, DeltaMassUiElements.class); + + console = new TextConsole(); + scrollConsole.setViewportView(console); + + appender = new LogbackJTextPaneAppender(); + appender.start(); + log.debug("Started LogbackJTextPaneAppender logger"); + appender.setTextPane(console); + + + } + + private void initLaf() { + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + log.warn("Neither System nor Nimbus look-and-feel found."); + } catch (Exception e1) { + log.warn("Could not initialize Look-And-Feel (LAF)."); + } + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/DeltaMassInfo.java b/src/main/java/com/dmtavt/deltamass/DeltaMassInfo.java new file mode 100644 index 0000000..a99ee42 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/DeltaMassInfo.java @@ -0,0 +1,100 @@ +package com.dmtavt.deltamass; + +import com.dmtavt.deltamass.messages.MsgVersionUpdateInfo; +import com.github.chhh.utils.PathUtils; +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.VersionComparator; +import java.io.StringReader; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Properties; +import org.apache.commons.io.IOUtils; +import org.greenrobot.eventbus.EventBus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class DeltaMassInfo { + private static final Logger log = LoggerFactory.getLogger(DeltaMassInfo.class); + private static final EventBus bus = EventBus.getDefault(); + + public static final String Name = "DeltaMass"; + public static final String Ver = "1.0"; + + private static final String PROP_VER = "deltamass.version.current"; + private static final String PROP_DOWNLOAD_URL = "deltamass.download.url"; + private static final String DEFAULT_DOWNLOAD_URL = "https://github.com/chhh/deltamass/releases"; + private static final String BUNDLE_PATH = "com/dmtavt/deltamass/Bundle"; + private static final String BUNDLE_URL = + "https://raw.githubusercontent.com/chhh/deltamass/master/src/main/java/" + BUNDLE_PATH + + ".properties"; + private static final URI BUNDLE_URI = URI.create(BUNDLE_URL); + + private DeltaMassInfo() { + } + + public static Path getCacheDir() { + final Path tempDir = PathUtils.getTempDir(); + return tempDir.resolve(".deltamass"); + } + + public static String getNameVersion() { + return Name + " (v" + Ver + ")"; + } + + /** + * Tries to fetch new version info from remote. Runs in a separate thread, posts + * a sticky message to the default {@link EventBus} about a new version in case + * of success. + */ + public static void checkForNewVersions() { + Thread t = new Thread(() -> { + try { + + log.debug("Checking for new versions"); + final Charset utf8 = Charset.forName("UTF-8"); + final Properties props = new Properties(); + + // read from remote + final URL url = BUNDLE_URI.toURL(); + log.debug("Fetching update info, trying to get file: {}", url.toString()); + final String remoteFileContent = IOUtils.toString(url, utf8); + if (StringUtils.isBlank(remoteFileContent)) { + log.debug("Remote version file fetched, but was empty"); + return; + } + log.debug("Remote version file fetched"); + props.load(new StringReader(remoteFileContent)); + + // this is used to test functionality without pushing changes to github +// props.put(PROP_VER, "5.7"); + + // add new versions notification + + final VersionComparator vc = new VersionComparator(); + final String remoteVer = props.getProperty(PROP_VER); + log.debug("Remote version: {}, local verison: {}", remoteVer, Ver); + if (remoteVer != null) { + final int compare = vc.compare(Ver, remoteVer); + log.debug("Version comparison result: {}" , compare); + if (compare < 0) { + log.debug("Posting notification about a newer version updateg"); + String downloadUrl = props.getProperty(PROP_DOWNLOAD_URL, DEFAULT_DOWNLOAD_URL); + bus.postSticky(new MsgVersionUpdateInfo(Name, Ver, remoteVer, downloadUrl)); + } + } else { + log.debug("Could not parse remote version"); + return; + } + + + } catch (Exception e) { + // it doesn't matter, it's fine if we can't fetch the file from github + log.info("Could not get version update info from GitHub"); + } + }); + t.start(); + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/DeltaMassMain.java b/src/main/java/com/dmtavt/deltamass/DeltaMassMain.java new file mode 100644 index 0000000..bd7dff1 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/DeltaMassMain.java @@ -0,0 +1,121 @@ +package com.dmtavt.deltamass; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.MissingCommandException; +import com.beust.jcommander.ParameterException; +import com.dmtavt.deltamass.logic.CommandClean; +import com.dmtavt.deltamass.logic.CommandGui; +import com.dmtavt.deltamass.logic.CommandPeaks; +import com.dmtavt.deltamass.logic.CommandPlot; +import com.dmtavt.deltamass.logic.LogicClean; +import com.dmtavt.deltamass.logic.LogicGui; +import com.dmtavt.deltamass.logic.LogicPeaks; +import com.dmtavt.deltamass.logic.LogicPlot; +import com.dmtavt.deltamass.utils.TextUtils; +import java.util.Arrays; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeltaMassMain { + + private static final Logger log = LoggerFactory.getLogger(DeltaMassMain.class); + + public static void main(String[] args) { + System.out.println(DeltaMassInfo.getNameVersion()); + System.out.println(); + + parseArgs(args); + } + + private static void parseArgs(String[] args) { + + CommandGui cmdGui = new CommandGui(); + CommandPeaks cmdPeaks = new CommandPeaks(); + CommandPlot cmdPlot = new CommandPlot(); + CommandClean cmdCleanup = new CommandClean(); + + JCommander jc = JCommander.newBuilder() + .acceptUnknownOptions(false) + .allowAbbreviatedOptions(false) + .programName("java -jar " + DeltaMassInfo.Name + "_" + DeltaMassInfo.Ver + ".jar") + .addCommand(cmdGui) + .addCommand(cmdPeaks) + .addCommand(cmdPlot) + .addCommand(cmdCleanup) + .build(); + + if (args.length == 0) { + System.out.println("No command found."); + } + + if (args.length == 0 || "-h".equals(args[0]) || "--help".equals(args[0])) { + String msg = "Available commands:\n" + getWrappedCommandsDescription(jc).toString(); + System.out.println(msg); + return; + } + + try { + jc.parse(args); + } catch (MissingCommandException e) { + String msg = "Error parsing command line parameters, unknown command '" + + e.getUnknownCommand() + "'.\n\n" + + "Available commands:\n" + getWrappedCommandsDescription(jc).toString(); + System.err.println(msg); + return; + } catch (ParameterException e) { + System.err.printf("Error parsing parameters for command '%s'.\n%s", + e.getJCommander().getParsedCommand(), e.getMessage()); + return; + } + + // some known command has been parsed + if (Arrays.stream(args).anyMatch(s -> "-h".equals(s) || "--help".equals(s))) { + // show help for command + String cmd = "Command: " + jc.getParsedCommand(); + System.out.println(cmd); + System.out.println(TextUtils.repeat("-", cmd.length())); + jc.usage(jc.getParsedCommand()); + return; + } + + // do something with the parsed arguments + System.out.println("Selected command: " + jc.getParsedCommand()); + switch (jc.getParsedCommand()) { + case CommandGui.CMD: + new LogicGui(cmdGui).run(); + break; + case CommandPeaks.CMD: + new LogicPeaks(cmdPeaks).run(); + break; + case CommandPlot.CMD: + new LogicPlot(cmdPlot).run(); + break; + case CommandClean.CMD: + new LogicClean(cmdCleanup).run(); + break; + default: + throw new IllegalStateException("Unknown command parsed: " + jc.getParsedCommand()); + } + } + + private static StringBuilder getWrappedCommandsDescription(JCommander jc) { + final int indent = 4; + final int maxCmd = jc.getCommands().keySet().stream().mapToInt(String::length).max().orElse(0); + StringBuilder sb = new StringBuilder(); + + for (Map.Entry e : jc.getCommands().entrySet()) { + String cmd = e.getKey(); + String desc = "\n" + TextUtils.repeat(" ", indent) + cmd + + TextUtils.repeat(" ", maxCmd - cmd.length()) + + " - " + jc.getCommandDescription(cmd) + "\n"; + StringBuilder wrapped = new StringBuilder(); + TextUtils.wrap(wrapped, jc.getColumnSize(), 8, desc); + sb.append(wrapped.toString()); + } + sb.append("\nTry '").append(DeltaMassInfo.Name).append(" -h' for more info.\n"); + return sb; + } + + +} diff --git a/src/main/java/com/dmtavt/deltamass/DeltaMassMainGui.java b/src/main/java/com/dmtavt/deltamass/DeltaMassMainGui.java new file mode 100644 index 0000000..02c7d8c --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/DeltaMassMainGui.java @@ -0,0 +1,9 @@ +package com.dmtavt.deltamass; + +public class DeltaMassMainGui { + + public static void main(String[] args) { + DeltaMassMain.main(new String[] {"gui"}); + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/args/DecoyTreatment.java b/src/main/java/com/dmtavt/deltamass/args/DecoyTreatment.java new file mode 100644 index 0000000..74ea615 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/DecoyTreatment.java @@ -0,0 +1,7 @@ +package com.dmtavt.deltamass.args; + +public enum DecoyTreatment { + FORWARDS_ONLY, + DECOYS_ONLY, + USE_BOTH +} diff --git a/src/main/java/com/dmtavt/deltamass/args/Kernel.java b/src/main/java/com/dmtavt/deltamass/args/Kernel.java new file mode 100644 index 0000000..f07a674 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/Kernel.java @@ -0,0 +1,6 @@ +package com.dmtavt.deltamass.args; + +public final class Kernel { + + public enum Type {GAUSS, EPANECHNIKOV} +} diff --git a/src/main/java/com/dmtavt/deltamass/args/MassCorrection.java b/src/main/java/com/dmtavt/deltamass/args/MassCorrection.java new file mode 100644 index 0000000..153c51c --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/MassCorrection.java @@ -0,0 +1,10 @@ +package com.dmtavt.deltamass.args; + +import java.io.Serializable; + +public enum MassCorrection implements Serializable { + NONE, + ZERO_PEAK, + //MS1_TRACE, + PEP_ID +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatConverter.java b/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatConverter.java new file mode 100644 index 0000000..0f7de59 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatConverter.java @@ -0,0 +1,23 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; +import java.text.DecimalFormat; + +public class DecimalFormatConverter implements IStringConverter { + + @Override + public DecimalFormat convert(String value) { + + DecimalFormat format; + try { + format = new DecimalFormat(value); + } catch (Exception e) { + throw new ParameterException(String.format( + "Invalid format '%s'. Check https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html " + + + "for correct formats.", value)); + } + return format; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatValidator.java b/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatValidator.java new file mode 100644 index 0000000..1d9bda1 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatValidator.java @@ -0,0 +1,21 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; +import java.text.DecimalFormat; + +public class DecimalFormatValidator implements IParameterValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + DecimalFormat format = new DecimalFormat(); + try { + format.applyPattern(value); + } catch (IllegalArgumentException e) { + throw new ParameterException(String.format( + "Parameter '%s' had incorrect format '%s'. " + + "Check https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html " + + "for correct formats.", name, value)); + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatWithTostring.java b/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatWithTostring.java new file mode 100644 index 0000000..d86f43e --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/DecimalFormatWithTostring.java @@ -0,0 +1,23 @@ +package com.dmtavt.deltamass.args.converters; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; + +public class DecimalFormatWithTostring extends DecimalFormat { + + public DecimalFormatWithTostring() { + } + + public DecimalFormatWithTostring(String pattern) { + super(pattern); + } + + public DecimalFormatWithTostring(String pattern, DecimalFormatSymbols symbols) { + super(pattern, symbols); + } + + @Override + public String toString() { + return this.toPattern(); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/ExistingReadablePathValidator.java b/src/main/java/com/dmtavt/deltamass/args/converters/ExistingReadablePathValidator.java new file mode 100644 index 0000000..74d3373 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/ExistingReadablePathValidator.java @@ -0,0 +1,26 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.ParameterException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public class ExistingReadablePathValidator implements IValueValidator> { + + @Override + public void validate(String name, List value) throws ParameterException { + + for (Path p : value) { + p = p.toAbsolutePath(); + if (Files.notExists(p)) { + throw new ParameterException( + String.format("Parameter: '%s'. Path not exists: '%s'", name, p.toString())); + } + if (!Files.isReadable(p)) { + throw new ParameterException( + String.format("Parameter: '%s'. Path not readable: '%s'", name, p.toString())); + } + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/PathConverter.java b/src/main/java/com/dmtavt/deltamass/args/converters/PathConverter.java new file mode 100644 index 0000000..5ed7228 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/PathConverter.java @@ -0,0 +1,16 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IStringConverter; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Convert string to {@link Path}. + */ +public class PathConverter implements IStringConverter { + + @Override + public Path convert(String value) { + return Paths.get(value).toAbsolutePath(); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/RegexConverter.java b/src/main/java/com/dmtavt/deltamass/args/converters/RegexConverter.java new file mode 100644 index 0000000..926b1ea --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/RegexConverter.java @@ -0,0 +1,24 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Creates case-insensitive regular expression. + */ +public class RegexConverter implements IStringConverter { + + @Override + public Pattern convert(String value) { + Pattern pattern; + try { + pattern = Pattern.compile(value, Pattern.CASE_INSENSITIVE); + } catch (PatternSyntaxException e) { + throw new ParameterException( + String.format("Invalid regular expression: '%s'", e.getPattern()), e); + } + return pattern; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/RegexValidator.java b/src/main/java/com/dmtavt/deltamass/args/converters/RegexValidator.java new file mode 100644 index 0000000..91a1e4b --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/RegexValidator.java @@ -0,0 +1,19 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class RegexValidator implements IParameterValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + try { + Pattern.compile(value); + } catch (PatternSyntaxException e) { + throw new ParameterException( + String.format("Invalid regular expression: '%s'", e.getPattern()), e); + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/args/converters/StringToPathValidator.java b/src/main/java/com/dmtavt/deltamass/args/converters/StringToPathValidator.java new file mode 100644 index 0000000..88ec93d --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/args/converters/StringToPathValidator.java @@ -0,0 +1,21 @@ +package com.dmtavt.deltamass.args.converters; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class StringToPathValidator implements IParameterValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + Path path; + try { + path = Paths.get(value); + } catch (InvalidPathException e) { + throw new ParameterException( + String.format("Parameter: '%s'. Can't parse path: '%s'", name, value)); + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/data/PepSearchFile.java b/src/main/java/com/dmtavt/deltamass/data/PepSearchFile.java new file mode 100644 index 0000000..7b16831 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/data/PepSearchFile.java @@ -0,0 +1,38 @@ +package com.dmtavt.deltamass.data; + +import com.dmtavt.deltamass.args.DecoyTreatment; +import com.dmtavt.deltamass.predicates.SpmPredicateFactory; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * The contents of a proteomics search file. + */ +public class PepSearchFile implements Serializable { + + private static final long serialVersionUID = 8233549653301313773L; + public final String sourceFile; + public final List pepSearchResults; + + public PepSearchFile(String sourceFile, + List pepSearchResults) { + this.sourceFile = sourceFile; + this.pepSearchResults = pepSearchResults; + } + + public static PepSearchFile filter(PepSearchFile psf, + Double dmLo, Double dmHi, List excludes, DecoyTreatment decoyTreatment, + List spfs) { + + ArrayList results = new ArrayList<>(psf.pepSearchResults.size()); + for (PepSearchResult psr : psf.pepSearchResults) { + PepSearchResult filtered = PepSearchResult + .filter(psr, dmLo, dmHi, excludes, decoyTreatment, spfs); + results.add(filtered); + } + + return new PepSearchFile(psf.sourceFile, results); + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/data/PepSearchResult.java b/src/main/java/com/dmtavt/deltamass/data/PepSearchResult.java new file mode 100644 index 0000000..be997cd --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/data/PepSearchResult.java @@ -0,0 +1,222 @@ +package com.dmtavt.deltamass.data; + +import static com.dmtavt.deltamass.utils.NumUtils.isGoodDouble; + +import com.dmtavt.deltamass.args.DecoyTreatment; +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.predicates.ISpmPredicate; +import com.dmtavt.deltamass.predicates.SpmPredicateFactory; +import com.dmtavt.deltamass.predicates.SpmPredicateFactory.ScoreNameNotExists; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PepSearchResult implements Serializable { + + private static final long serialVersionUID = 8810968807763917692L; + private static final Logger log = LoggerFactory.getLogger(PepSearchResult.class); + /** + * Raw file name, the original LCMS file from which the search results file was derived. E.g. + * `thermo-01.RAW` + */ + public final String rawFileName; + /** + * Search results file name. E.g. `interact-123.pepxml`. + */ + public final String sourceFileName; + /** + * Directory where the search results file was located. + */ + public final String sourceFileDir; + + public final ArrayList spms; + private SortOrder sortOrder; + /** + * Individual SPMs reference proteins by index in this list. + */ + public final ArrayList proteinAccessions; + public final HashMap scoreMapping; + public MassCorrection massCorrection; + + public PepSearchResult(String rawFileName, String sourceFileName, String sourceFileDir, + ArrayList spms, ArrayList proteinAccessions, + HashMap scoreMapping, + MassCorrection massCorrection) { + this.rawFileName = rawFileName; + this.sourceFileName = sourceFileName; + this.sourceFileDir = sourceFileDir; + this.spms = spms; + this.proteinAccessions = proteinAccessions; + this.scoreMapping = scoreMapping; + this.massCorrection = massCorrection; + this.sortOrder = SortOrder.NONE; + } + + public enum SortOrder { + NONE, DELTA_MASS_ASCENDING + } + + public SortOrder getSortOrder() { + return sortOrder; + } + + public void sort(SortOrder order) { + switch (order) { + case NONE: + sortOrder = SortOrder.NONE; + log.info("Sorted {} by {}", sourceFileName, sortOrder); + break; + + case DELTA_MASS_ASCENDING: + spms.sort((o1, o2) -> Double.compare(o1.mDiff, o2.mDiff)); + sortOrder = SortOrder.DELTA_MASS_ASCENDING; + log.info("Sorted {} by {}", sourceFileName, sortOrder); + break; + + default: + throw new UnsupportedOperationException("Unknown switch option"); + } + } + + /** + * Filter search results. The original SPMs will be modified, instead of fully copied. However a + * new instance of {@link PepSearchResult} object is returned. + * + * @param dmLo Low delta mass cutoff. + * @param dmHi High delta mass cutoff. + * @param excludes Excluded delta mass ranges. Size must be a multiple of 2. + * @param decoyTreatment What to do with decoys. + * @param spfs Other predicates for filtering SPMs. + * @return A new instance of {@link PepSearchResult} object with SPMs and corresponding Protein + * Accessions lists filterd. + */ + public static PepSearchResult filter(PepSearchResult psr, + Double dmLo, Double dmHi, List excludes, DecoyTreatment decoyTreatment, + List spfs) { + if (excludes.size() % 2 != 0) { + throw new IllegalArgumentException("Excludes' list size must be a multiple of 2."); + } + List predicates = new ArrayList<>(); + + // global delta mass range + if (isGoodDouble(dmLo)) { + predicates.add(spm -> spm.mDiffCorrected >= dmLo); + } + if (isGoodDouble(dmHi)) { + predicates.add(spm -> spm.mDiffCorrected <= dmHi); + } + + // delta mass exclusions + for (int i = 0; i < excludes.size(); i += 2) { + Double lo = excludes.get(i); + Double hi = excludes.get(i + 1); + if (isGoodDouble(lo) && isGoodDouble(hi)) { + predicates.add(spm -> spm.mDiffCorrected <= lo || spm.mDiffCorrected >= hi); + } else { + throw new IllegalStateException("Some values in excludes list were not finite doubles."); + } + } + + // When DecoyTreatment is USE_BOTH no predicates are added + if (DecoyTreatment.FORWARDS_ONLY.equals(decoyTreatment)) { + predicates.add(spm -> !spm.isDecoy); + } else if (DecoyTreatment.DECOYS_ONLY.equals(decoyTreatment)) { + predicates.add(spm -> spm.isDecoy); + } + + if (spfs != null) { + for (SpmPredicateFactory spf : spfs) { + try { + ISpmPredicate predicate = spf.create(psr.scoreMapping); + predicates.add(predicate); + } catch (ScoreNameNotExists snne) { + throw new IllegalStateException(snne); + } + } + } + + if (predicates.isEmpty()) { + return psr; + } + + // compose all predicates into one + Predicate composite = predicates.get(0); + if (predicates.size() > 1) { + for (int i = 1; i < predicates.size(); i++) { + composite = composite.and(predicates.get(i)); + } + } + + // perform actual filtering + ArrayList spmsFiltered = psr.spms.stream().filter(composite) + .collect(Collectors.toCollection(ArrayList::new)); + spmsFiltered.trimToSize(); + + // weed out prot ids that are no longer in use after filtering spm list + ArrayList protAccessionsFiltered = new ArrayList<>(); + int[] protIdRemap = new int[psr.proteinAccessions.size()]; + Arrays.fill(protIdRemap, -1); + int ptr = -1; + for (Spm spm : spmsFiltered) { + + // main prot id + if (protIdRemap[spm.protId] < 0) { + protIdRemap[spm.protId] = ++ptr; + protAccessionsFiltered.add(psr.proteinAccessions.get(spm.protId)); + } + spm.protId = protIdRemap[spm.protId]; + + // alt prot ids + int[] protIdAlt = spm.protIdAlt; + for (int i = 0; i < protIdAlt.length; i++) { + int altProdIdx = protIdAlt[i]; + if (protIdRemap[altProdIdx] < 0) { + protIdRemap[altProdIdx] = ++ptr; + protAccessionsFiltered.add(psr.proteinAccessions.get(altProdIdx)); + } + protIdAlt[i] = protIdRemap[altProdIdx]; + } + } + protAccessionsFiltered.trimToSize(); + + return new PepSearchResult(psr.rawFileName, psr.sourceFileName, + psr.sourceFileDir, + spmsFiltered, protAccessionsFiltered, psr.scoreMapping, psr.massCorrection); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PepSearchResult that = (PepSearchResult) o; + + if (rawFileName != null ? !rawFileName.equals(that.rawFileName) : that.rawFileName != null) { + return false; + } + if (sourceFileName != null ? !sourceFileName.equals(that.sourceFileName) + : that.sourceFileName != null) { + return false; + } + return sourceFileDir != null ? sourceFileDir.equals(that.sourceFileDir) + : that.sourceFileDir == null; + } + + @Override + public int hashCode() { + int result = rawFileName != null ? rawFileName.hashCode() : 0; + result = 31 * result + (sourceFileName != null ? sourceFileName.hashCode() : 0); + result = 31 * result + (sourceFileDir != null ? sourceFileDir.hashCode() : 0); + return result; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/data/Spm.java b/src/main/java/com/dmtavt/deltamass/data/Spm.java new file mode 100644 index 0000000..de2053f --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/data/Spm.java @@ -0,0 +1,49 @@ +package com.dmtavt.deltamass.data; + +import java.io.Serializable; + +/** + * Spectrum-to-peptide match. Comparisons are based on mass diff only! + */ +public class Spm implements Serializable, Comparable { + + private static final long serialVersionUID = 2974262482081903368L; + + public String spectrumId = ""; + public String seq; + public String seqModStateId; + public String mods; + public int protId; + public int[] protIdAlt; + public int charge; + public double mObsNeutral; + public double mzObs; + public double mCalcNeutral; + public double mDiff; + public double mDiffCorrected; + public double rtSec = Double.NaN; + public double[] scores; + public boolean isDecoy; + + public Spm(int numScores) { + scores = new double[numScores]; + } + + @Override + public String toString() { + return "{" + + "Mcalc=" + String.format("%.5f", mCalcNeutral) + + ", Mobs=" + String.format("%.5f", mObsNeutral) + + ", dM=" + String.format("%.5f", mDiff) + + ", Z=" + charge + + ", Seq='" + seq + '\'' + + '}'; + + } + + @Override + public int compareTo(Spm o) { + return Double.compare(mDiff, o.mDiff); + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/kde/GaussFasterKF.java b/src/main/java/com/dmtavt/deltamass/kde/GaussFasterKF.java new file mode 100644 index 0000000..c6bf5e9 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/kde/GaussFasterKF.java @@ -0,0 +1,76 @@ +package com.dmtavt.deltamass.kde; + +import jsat.distributions.Normal; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; + +/** + * @author Dmitry Avtonomov + */ +public class GaussFasterKF implements KernelFuncPrime2 { + + private GaussFasterKF() {} + + private static class SingletonHolder { + public static final GaussFasterKF INSTANCE = new GaussFasterKF(); + } + + /** + * Returns the singleton instance of this class + * @return the instance of this class + */ + public static GaussFasterKF getInstance() + { + return SingletonHolder.INSTANCE; + } + + @Override + public double k(double u) { + return 1/FastMath.sqrt(MathUtils.TWO_PI)*FastMath.exp(-FastMath.pow(u,2)/2); + } + + @Override + public double intK(double u) + { + return Normal.cdf(u, 0, 1); + } + + @Override + public double k2() + { + return 1; + } + + @Override + public double cutOff() { + /* + * This is not techincaly correct, as this value of k(u) is still 7.998827757006813E-38 + * However, this is very close to zero, and is so small that k(u)+x = x, for most values of x. + * Unless this probability si going to be near zero, values past this point will have + * no effect on the result + */ + return 13; + } + + @Override + public double kPrime(double u) { + return -u*k(u); + } + + @Override + public double kPrime2(double u) { + return (FastMath.pow(u, 2)-1)*k(u); + } + + @Override + public boolean isKPrime2Exact() { + return true; + } + + @Override + public String toString() + { + return "Gaussian Kernel using Apache Commons Math3 library"; + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/kde/KdeKludge.java b/src/main/java/com/dmtavt/deltamass/kde/KdeKludge.java new file mode 100644 index 0000000..677de5c --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/kde/KdeKludge.java @@ -0,0 +1,592 @@ +package com.dmtavt.deltamass.kde; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import jsat.distributions.ContinuousDistribution; +import jsat.distributions.empirical.kernelfunc.EpanechnikovKF; +import jsat.distributions.empirical.kernelfunc.GaussKF; +import jsat.distributions.empirical.kernelfunc.KernelFunction; +import jsat.distributions.empirical.kernelfunc.UniformKF; +import jsat.linear.Vec; +import jsat.math.Function; +import jsat.math.OnLineStatistics; +import jsat.utils.ProbailityMatch; + +/** + * Kernel Density Estimator, KDE, uses the data set itself to approximate the underlying probability + * distribution using {@link KernelFunction Kernel Functions}. + * + * @author Edward Raff + * @author Dmitry Avtonomov + */ +public class KdeKludge extends ContinuousDistribution +{ + /* + * README + * Implementation note: + * The values are stored in sorted order, which allows for fast evaluations. + * Instead of doing the full loop on each function call, O(n) time, + * we know the bounds on the values that will effect results, so we + * can do 2 binary searches and then a loop. Though this is still + * technically, O(n), its more accurately described as O(n * epsilon * log(n)) , where n * epsilon << n + */ + + + private static final long serialVersionUID = 7708020456632603947L; + /** + * The various values + */ + protected double[] X; + /** + * Weights corresponding to each value. If all the same, weights should have a length of 0 + */ + protected double[] weights; + /** + * For unweighted data, this is equal to X.length + */ + protected double sumOFWeights; + /** + * The bandwidth + */ + protected double h; + protected double Xmean, Xvar, Xskew; + + protected KernelFunction k; + + + public static double BandwithGuassEstimate(Vec X) + { + if(X.length() == 1 ) + return 1; + else if(X.standardDeviation() == 0) + return 1.06 * Math.pow(X.length(), -1.0/5.0); + return 1.06 * X.standardDeviation() * Math.pow(X.length(), -1.0/5.0); + } + + /** + * Automatically selects a good Kernel function for the data set that balances Execution time and accuracy + * @param dataPoints + * @return a kernel that will work well for the given distribution + */ + public static KernelFunction autoKernel(Vec dataPoints ) + { + if(dataPoints.length() < 30) + return GaussKF.getInstance(); + else if(dataPoints.length() < 1000) + return EpanechnikovKF.getInstance(); + else//For very large data sets, Uniform is FAST and just as accurate + return UniformKF.getInstance(); + } + + public KdeKludge(Vec dataPoints) + { + this(dataPoints, autoKernel(dataPoints)); + } + + public KdeKludge(Vec dataPoints, KernelFunction k) + { + this(dataPoints, k, BandwithGuassEstimate(dataPoints)); + } + + public KdeKludge(Vec dataPoints, KernelFunction k, double[] weights) + { + this(dataPoints, k, BandwithGuassEstimate(dataPoints), weights); + } + + public KdeKludge(Vec dataPoints, KernelFunction k, double h) + { + setUpX(dataPoints); + this.k = k; + this.h = h; + } + + public KdeKludge(Vec dataPoints, KernelFunction k, double h, double[] weights) + { + setUpX(dataPoints, weights); + this.k = k; + this.h = h; + } + + /** + * Copy constructor + */ + private KdeKludge(double[] X, double h, double Xmean, double Xvar, double Xskew, KernelFunction k, double sumOfWeights, double[] weights) + { + this.X = Arrays.copyOf(X, X.length); + this.h = h; + this.Xmean = Xmean; + this.Xvar = Xvar; + this.Xskew = Xskew; + this.k = k; + this.sumOFWeights = sumOfWeights; + this.weights = Arrays.copyOf(weights, weights.length); + } + + public KernelFunction getKernelFunction() { + return k; + } + + public void setKernelFunction(KernelFunction k) { + this.k = k; + } + + private void setUpX(Vec S) + { + Xmean = S.mean(); + Xvar = S.variance(); + Xskew = S.skewness(); + X = S.arrayCopy(); + Arrays.sort(X); + sumOFWeights = X.length; + weights = new double[0]; + } + + private void setUpX(Vec S, double[] weights) + { + if(S.length() != weights.length) + throw new RuntimeException("Weights and variables do not have the same length"); + + OnLineStatistics stats = new OnLineStatistics(); + + X = new double[S.length()]; + this.weights = Arrays.copyOf(weights, S.length()); + + //Probability is the X value, match is the weights - so that they can be sorted together. + List> sorter = new ArrayList<>(S.length()); + for(int i = 0; i < S.length(); i++) + sorter.add(new ProbailityMatch<>(S.get(i), weights[i])); + Collections.sort(sorter); + for(int i = 0; i < sorter.size(); i++) + { + this.X[i] = sorter.get(i).getProbability(); + this.weights[i] = sorter.get(i).getMatch(); + stats.add(this.X[i], this.weights[i]); + } + //Now do some helpful preprocessing on weights. We will make index i store the sum for [0, i]. + //Each individual weight can still be retrieved in O(1) by accessing a 2nd index and a subtraction + //Methods that need the sum can now access it in O(1) time from the weights array instead of doing an O(n) summations + for(int i = 1; i < this.weights.length; i++) + this.weights[i] += this.weights[i-1]; + sumOFWeights = this.weights[this.weights.length-1]; + this.Xmean = stats.getMean(); + this.Xvar = stats.getVarance(); + this.Xskew = stats.getSkewness(); + } + + /** + * By the point you can call this, the array should have already been sorted. + * @return + */ + public double[] getX() { + return X; + } + + private double getWeight(int i) + { + if(weights.length == 0) + return 1.0; + else if(i == 0) + return weights[i]; + else + return weights[i] - weights[i-1]; + } + + + @Override + public double pdf(double x) + { + return pdf(x, -1, h); + } + + /** + * Calculate PDF using the provided value of bandwidth. + * @param x the point to calculate at + * @param h custom bandwidth value + * @return + */ + public double pdf(double x, double h) { + return pdf(x, -1, h); + } + + /** + * Computes the Leave One Out PDF of the estimator + * @param x the value to get the pdf of + * @param j the sorted index of the value to leave. If a negative value is given, the PDF with all values is returned + * @param h which bandwidth to use + * @return the pdf with the given index left out + */ + private double pdf(double x, int j, double h) + { + /* + * n + * ===== /x - x \ + * 1 \ | i| + * f(x) = --- > K|------| + * n h / \ h / + * ===== + * i = 1 + * + */ + + + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x-h*k.cutOff()); + int to = Arrays.binarySearch(X, x+h*k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? -from-1 : from; + to = to < 0 ? -to-1 : to; + + //Univariate opt, if uniform weights, the sum is just the number of elements divide by half + if(weights.length == 0 && k instanceof UniformKF) + return (to-from)*0.5/ (sumOFWeights*h); + + double sum = 0; + for(int i = Math.max(0, from); i < Math.min(X.length, to+1); i++) + if(i != j) + sum += k.k( (x-X[i])/h )*getWeight(i); + + return sum / (sumOFWeights * h); + } + + public double pdfPrime2(double x) { + return pdfPrime2(x, -1, h); + } + public double pdfPrime2(double x, double h) { + return pdfPrime2(x, -1, h); + } + + protected double pdfPrime2(double x, int j, double h) + { + /* + * n + * ===== /x - x \ + * 1 \ | i| + * f(x) = --- > K|------| + * n h / \ h / + * ===== + * i = 1 + * + */ + + if (!(k instanceof KernelFuncPrime2)) { + throw new IllegalStateException("You can only call 2nd derivative of PDF if you've set a kernel function that " + + "is an instance of KernelFuncPrime2"); + } + KernelFuncPrime2 kernelFuncPrime2 = (KernelFuncPrime2)k; + + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x-h*k.cutOff()); + int to = Arrays.binarySearch(X, x+h*k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? ~from : from; + to = to < 0 ? ~to : to; + + //Univariate opt, if uniform weights, the sum is just the number of elements divide by half + if(weights.length == 0 && k instanceof UniformKF) + return (to-from)*0.5/ (sumOFWeights*h); + + double sum = 0; + for(int i = Math.max(0, from); i < Math.min(X.length, to+1); i++) + if(i != j) + sum += kernelFuncPrime2.kPrime2( (x-X[i])/h )*getWeight(i); + + return sum / (sumOFWeights * h); + } + + public double pdfPrime(double x) { + return pdfPrime(x, -1, h); + } + public double pdfPrime(double x, double h) { + return pdfPrime(x, -1, h); + } + + protected double pdfPrime(double x, int j, double h) + { + /* + * n + * ===== /x - x \ + * 1 \ | i| + * f(x) = --- > K|------| + * n h / \ h / + * ===== + * i = 1 + * + */ + + + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x-h*k.cutOff()); + int to = Arrays.binarySearch(X, x+h*k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? -from-1 : from; + to = to < 0 ? -to-1 : to; + + //Univariate opt, if uniform weights, the sum is just the number of elements divide by half + if(weights.length == 0 && k instanceof UniformKF) + return (to-from)*0.5/ (sumOFWeights*h); + + double sum = 0; + for(int i = Math.max(0, from); i < Math.min(X.length, to+1); i++) + if(i != j) + sum += k.kPrime( (x-X[i])/h )*getWeight(i); + + return sum / (sumOFWeights * h); + } + + @Override + public double cdf(double x) + { + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x-h*k.cutOff()); + int to = Arrays.binarySearch(X, x+h*k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? -from-1 : from; + to = to < 0 ? -to-1 : to; + + double sum = 0; + + for(int i = Math.max(0, from); i < Math.min(X.length, to+1); i++) + sum += k.intK( (x-X[i]) /h )*getWeight(i); + + /* + * Slightly different, all things below the from value for the cdf would be + * adding 1 to the value, as the value of x would be the integration over + * the entire range, which by definition, is equal to 1. + */ + //We perform the addition after the summation to reduce the difference size + if(weights.length == 0)//No weights + sum += Math.max(0, from); + else + sum += weights[from]; + + + return sum / (X.length); + } + + @SuppressWarnings("unused") + private final Function cdfFunc = new Function() { + + /** + * + */ + private static final long serialVersionUID = -4100975560125048798L; + + public double f(double... x) + { + return cdf(x[0]); + } + + public double f(Vec x) + { + return f(x.get(0)); + } + }; + + @Override + public double invCdf(double p) + { + int index; + double kd0; + + if(weights.length == 0) + { + double r = p*X.length; + index = (int)r; + double pd0 = r - index, pd1 = 1-pd0; + kd0 = k.intK(pd1); + } + else//CDF can be found from the weights summings + { + double XEstimate = p*sumOFWeights; + index = Arrays.binarySearch(weights, XEstimate); + index = index < 0 ? -index-1 : index; + if(X[index] != 0)//TODO fix this bit + kd0 = 1.0;//-Math.abs((XEstimate-X[index])/X[index]); + else + kd0 = 1.0; + } + + + if(index == X.length-1)//at the tail end + return X[index]*kd0; + double x = X[index]*kd0 + X[index+1]*(1-kd0); + + return x; + } + + @Override + public double min() + { + return X[0]-h; + } + + @Override + public double max() + { + return X[X.length-1]+h; + } + + @Override + public String getDistributionName() + { + return "Kernel Density Estimate"; + } + + @Override + public String[] getVariables() + { + return new String[] { "h" } ; + } + + @Override + public double[] getCurrentVariableValues() + { + return new double[] { h }; + } + + /** + * Sets the bandwidth used for smoothing. Higher values make the pdf smoother, but can + * obscure features. Too small a bandwidth will causes spikes at only the data points. + * @param val new bandwidth + */ + public void setBandwith(double val) + { + if(val <= 0 || Double.isInfinite(val)) + throw new ArithmeticException("Bandwith parameter h must be greater than zero, not " + 0); + this.h = val; + } + + /** + * + * @return the bandwidth parameter + */ + public double getBandwith() + { + return h; + } + + @Override + public void setVariable(String var, double value) + { + if(var.equals("h")) + setBandwith(value); + } + + @Override + public KdeKludge clone() + { + return new KdeKludge(X, h, Xmean, Xvar, Xskew, k, sumOFWeights, weights); + } + + @Override + public void setUsingData(Vec data) + { + setUpX(data); + this.h = BandwithGuassEstimate(data); + } + + @Override + public double mean() + { + return Xmean; + } + + + @Override + public double mode() + { + double maxP = 0, pTmp; + double maxV = Double.NaN; + for(int i = 0; i < X.length; i++) + if( (pTmp = pdf(X[i]) ) > maxP) + { + maxP = pTmp; + maxV = X[i]; + } + + return maxV; + } + + @Override + public double variance() + { + return Xvar + h*h*k.k2(); + } + + @Override + public double skewness() + { + //TODO cant find anything about what this should really be... + return Xskew; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(X); + long temp; + temp = Double.doubleToLongBits(Xmean); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(Xskew); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(Xvar); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(h); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + ((k == null) ? 0 : k.hashCode()); + temp = Double.doubleToLongBits(sumOFWeights); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Arrays.hashCode(weights); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof KdeKludge)) { + return false; + } + KdeKludge other = (KdeKludge) obj; + if (Double.doubleToLongBits(Xmean) != Double + .doubleToLongBits(other.Xmean)) { + return false; + } + if (Double.doubleToLongBits(Xskew) != Double + .doubleToLongBits(other.Xskew)) { + return false; + } + if (Double.doubleToLongBits(Xvar) != Double + .doubleToLongBits(other.Xvar)) { + return false; + } + + if (Double.doubleToLongBits(h) != Double.doubleToLongBits(other.h)) { + return false; + } + if (Double.doubleToLongBits(sumOFWeights) != Double + .doubleToLongBits(other.sumOFWeights)) { + return false; + } + if (k == null) { + if (other.k != null) { + return false; + } + } else if (k.getClass()!=other.k.getClass()) { + return false; + } + if (!Arrays.equals(X, other.X)) { + return false; + } + return Arrays.equals(weights, other.weights); + } + + +} diff --git a/src/main/java/com/dmtavt/deltamass/kde/KdeUtils.java b/src/main/java/com/dmtavt/deltamass/kde/KdeUtils.java new file mode 100644 index 0000000..78e3026 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/kde/KdeUtils.java @@ -0,0 +1,37 @@ +package com.dmtavt.deltamass.kde; + +import com.github.chhh.utils.MathUtils; +import java.util.ArrayList; +import jsat.distributions.empirical.KernelDensityEstimator; +import jsat.linear.DenseVector; + +/** + * @author Dmitry Avtonomov + */ +public class KdeUtils { + private KdeUtils() {} + + + /** + * Estimate bandwidth for KDE using Gaussian kernel method. + * @param massDiffs an array of all the mass diffs that were there + * @param nearestPeak near which mass will we be searching for a single good peak to estimate bandwidth + * @param maxDistance the max distance from the {@code nearestPeak} to accept a point for the estimate + * (this will be used as +/- distance) + * @return estimated bandwidth + */ + public static double estimateBandwidth(DenseVector massDiffs, double nearestPeak, double maxDistance) { + if (massDiffs.length() < 3) { + return 0.5; // slightly better than returning 1 by default + } + ArrayList accepted = new ArrayList<>(massDiffs.length() / 2); + for (int i = 0; i < massDiffs.length(); i++) { + double diff = massDiffs.get(i); + if (MathUtils.isWithinAbs(diff, nearestPeak, maxDistance)) { + accepted.add(diff); + } + } + DenseVector acceptedVec = new DenseVector(accepted); + return KernelDensityEstimator.BandwithGuassEstimate(acceptedVec); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/kde/KernelFuncPrime2.java b/src/main/java/com/dmtavt/deltamass/kde/KernelFuncPrime2.java new file mode 100644 index 0000000..328e502 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/kde/KernelFuncPrime2.java @@ -0,0 +1,20 @@ +package com.dmtavt.deltamass.kde; + +import jsat.distributions.empirical.kernelfunc.KernelFunction; + +/** + * @author Dmitry Avtonomov + */ +public interface KernelFuncPrime2 extends KernelFunction { + /** + * Second derivative at point U. + * @param u point for derivative calcualtion + * @return + */ + double kPrime2(double u); + + /** + * True if the value for second derivative is calculated analytically. + */ + boolean isKPrime2Exact(); +} diff --git a/src/main/java/com/dmtavt/deltamass/logging/LogbackJTextPaneAppender.java b/src/main/java/com/dmtavt/deltamass/logging/LogbackJTextPaneAppender.java new file mode 100644 index 0000000..071c09f --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logging/LogbackJTextPaneAppender.java @@ -0,0 +1,185 @@ +package com.dmtavt.deltamass.logging; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.awt.Color; +import javax.swing.JTextPane; +import javax.swing.SwingUtilities; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @see ch.qos.logback.core.AppenderBase + */ +public class LogbackJTextPaneAppender extends AppenderBase { + private static final Logger log = LoggerFactory.getLogger(LogbackJTextPaneAppender.class); + + private PatternLayout patternLayout; + private JTextPane textPane; + + private static SimpleAttributeSet ERROR_ATT, WARN_ATT, INFO_ATT, DEBUG_ATT, TRACE_ATT, OTHER_ATT; + + static { + ERROR_ATT = new SimpleAttributeSet(); + ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.TRUE); + ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE); + ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 0, 0)); + + WARN_ATT = new SimpleAttributeSet(); + WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE); + WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 76, 0)); + + INFO_ATT = new SimpleAttributeSet(); + INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE); + INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(0, 0, 153)); + + DEBUG_ATT = new SimpleAttributeSet(); + DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE); + DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(64, 64, 64)); + + TRACE_ATT = new SimpleAttributeSet(); + TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE); + TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 0, 76)); + + OTHER_ATT = new SimpleAttributeSet(); + OTHER_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + OTHER_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE); + OTHER_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(0, 0, 0)); + } + + public void setTextPane(JTextPane textPane) { + this.textPane = textPane; + } + + public void setPatternLayout(PatternLayout patternLayout) { + this.patternLayout = patternLayout; + } + + @Override + public void start() { + if (patternLayout == null) + patternLayout = new PatternLayout(); + + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + setContext(lc); + //patternLayout.setContext(getContext()); + patternLayout.setContext(lc); + //patternLayout.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"); + patternLayout.setPattern("%d{HH:mm:ss} %-5level: %msg%n"); + patternLayout.start(); + + lc.getLogger("ROOT").addAppender(this); + + super.start(); + } + + @Override + protected void append(ILoggingEvent event) { + + String formattedMsg = patternLayout.doLayout(event); + + SwingUtilities.invokeLater(() -> { + try { + int limit = 1000; + int lineNum = 200; + if (textPane.getDocument().getDefaultRootElement().getElementCount() > limit) { + int end = getLineEndOffset(textPane, lineNum); + replaceRange(textPane, null, 0, end); + } + + if (event.getLevel() == Level.ERROR) + textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, ERROR_ATT); + else if (event.getLevel() == Level.WARN) + textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, WARN_ATT); + else if (event.getLevel() == Level.INFO) + textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, INFO_ATT); + else if (event.getLevel() == Level.DEBUG) + textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, DEBUG_ATT); + else if (event.getLevel() == Level.TRACE) + textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, TRACE_ATT); + else + textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, OTHER_ATT); + + } catch (BadLocationException e) { + } + + textPane.setCaretPosition(textPane.getDocument().getLength()); + }); + } + + /** + * Num lines in text pane. + */ + private int getLineCount(JTextPane textPane) { + return textPane.getDocument().getDefaultRootElement().getElementCount(); + } + + /** + * @param textPane de onde quero o offset + * @param line the line >= 0 + * @return the offset >= 0 + * @throws BadLocationException Thrown if the line is + * less than zero or greater or equal to the number of + * lines contained in the document (as reported by + * getLineCount) + */ + private int getLineEndOffset(JTextPane textPane, int line) throws BadLocationException { + int lineCount = getLineCount(textPane); + if (line < 0) { + throw new BadLocationException("Negative line", -1); + } else if (line >= lineCount) { + throw new BadLocationException("No such line", textPane.getDocument().getLength()+1); + } else { + Element map = textPane.getDocument().getDefaultRootElement(); + Element lineElem = map.getElement(line); + int endOffset = lineElem.getEndOffset(); + // hide the implicit break at the end of the document + return ((line == lineCount - 1) ? (endOffset - 1) : endOffset); + } + } + + /** + * Replaces text from the indicated start to end position with the + * new text specified. Does nothing if the model is null. Simply + * does a delete if the new string is null or empty.
+ * + * @param textPane de onde quero substituir o texto + * @param str the text to use as the replacement + * @param start the start position >= 0 + * @param end the end position >= start + * @exception IllegalArgumentException if part of the range is an invalid position in the model + */ + private void replaceRange(JTextPane textPane, String str, int start, int end) throws IllegalArgumentException { + if (end < start) { + throw new IllegalArgumentException("end before start"); + } + Document doc = textPane.getDocument(); + if (doc != null) { + try { + if (doc instanceof AbstractDocument) { + ((AbstractDocument)doc).replace(start, end - start, str, null); + } + else { + doc.remove(start, end - start); + doc.insertString(start, str, null); + } + } catch (BadLocationException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + } +} + diff --git a/src/main/java/com/dmtavt/deltamass/logic/CacheLocator.java b/src/main/java/com/dmtavt/deltamass/logic/CacheLocator.java new file mode 100644 index 0000000..7f1b829 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/CacheLocator.java @@ -0,0 +1,33 @@ +package com.dmtavt.deltamass.logic; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class CacheLocator { + public static final String CACHE_EXT_PEP = ".deltamass-pep-cache"; + public static final String CACHE_EXT_CAL = ".deltamass-cal-cache"; + + public static Path locateForPep(Path path) { + return Paths.get(path.toAbsolutePath().normalize().toString() + CACHE_EXT_PEP); + } + + /** + * Locate cached file for a particular pep id file and LCMS file. + * @param path Path to pep id file. + * @param rawFileId Identifier of the raw file or experiment within the pep id file. Can be + * raw file name or just ordinal number of a search result within the a multi-result + * pep id file. + * @return The path to where the cache file should be. No checks for existence are made. + */ + public static Path locateForCal(Path path, String rawFileId) { + return Paths.get(path.toAbsolutePath().normalize().toString() + "_" + rawFileId + CACHE_EXT_CAL); + } + + public static boolean isPepCacheFile(Path path) { + return path.getFileName().toString().toLowerCase().endsWith(CACHE_EXT_PEP); + } + + public static boolean isCalCacheFile(Path path) { + return path.getFileName().toString().toLowerCase().endsWith(CACHE_EXT_CAL); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/CommandClean.java b/src/main/java/com/dmtavt/deltamass/logic/CommandClean.java new file mode 100644 index 0000000..842d225 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/CommandClean.java @@ -0,0 +1,40 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.dmtavt.deltamass.args.converters.ExistingReadablePathValidator; +import com.dmtavt.deltamass.args.converters.PathConverter; +import com.dmtavt.deltamass.args.converters.StringToPathValidator; +import java.nio.file.Path; +import java.util.List; + +@Parameters(commandDescription = "Clean up cached files.", + commandNames = {CommandClean.CMD}) +public class CommandClean { + + public static final String CMD = "clean"; + + @Parameter(names = {"-h", "--help"}, hidden = true, help = true, description = "Display help message.") + boolean help = false; + + @Parameter(names = {"-i", "--in"}, required = true, + description = "Relative or absolute paths to directories or separate files. Separate entries with commas.", + validateWith = StringToPathValidator.class, converter = PathConverter.class, + validateValueWith = ExistingReadablePathValidator.class, + order = 10) + public List inputFiles; + + @Parameter(names = {"--dry-run"}, description = "Print files to be deleted without taking action.", + order = 20) + public boolean dryRun = false; + + @Parameter(names = {"--no-pep"}, description = "Do not clean up cached peptide identification files.", + order = 30) + public boolean noPep = false; + + @Parameter(names = {"--no-lcms"}, description = "Do not clean up LC-MS mass calibration files.", + order = 40) + public boolean noLcms = false; + + +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/CommandGui.java b/src/main/java/com/dmtavt/deltamass/logic/CommandGui.java new file mode 100644 index 0000000..9074717 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/CommandGui.java @@ -0,0 +1,15 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +@Parameters(commandDescription = "Runs the GUI where you can specify all the options.", + commandNames = {CommandGui.CMD}) +public class CommandGui { + + public static final String CMD = "gui"; + + @Parameter(names = {"-h", + "--help"}, hidden = true, help = true, description = "Display help message.") + boolean help = false; +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/CommandPeaks.java b/src/main/java/com/dmtavt/deltamass/logic/CommandPeaks.java new file mode 100644 index 0000000..d6c5b57 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/CommandPeaks.java @@ -0,0 +1,25 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; + +@Parameters(commandDescription = "Do kernel density estimation (KDE) and detect peaks, " + + "write peaks to a file.", commandNames = {CommandPeaks.CMD}) +public class CommandPeaks { + + public static final String CMD = "peaks"; + + @Parameter(names = {"-h", "--help"}, + hidden = true, help = true, description = "Display help message.") + boolean help = false; + + @ParametersDelegate + public UserOptsInputFiles optsInputFiles = new UserOptsInputFiles(); + + @ParametersDelegate + public UserOptsKde optsKde = new UserOptsKde(); + + @ParametersDelegate + public UserOptsPeaks optsPeaks = new UserOptsPeaks(); +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/CommandPlot.java b/src/main/java/com/dmtavt/deltamass/logic/CommandPlot.java new file mode 100644 index 0000000..2a4c9f8 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/CommandPlot.java @@ -0,0 +1,29 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; + +@Parameters(commandDescription = "Do kernel density estimation (KDE) and plot it, " + + "optionally detect peaks as well.", commandNames = {CommandPlot.CMD}) +public class CommandPlot { + + public static final String CMD = "plot"; + + @Parameter(names = {"-h", "--help"}, hidden = true, + help = true, description = "Display help message.") + boolean help = false; + + @ParametersDelegate + public UserOptsInputFiles optsInputFiles = new UserOptsInputFiles(); + + @ParametersDelegate + public UserOptsKde optsKde = new UserOptsKde(); + + @ParametersDelegate + public UserOptsPlot optsPlot = new UserOptsPlot(); + + @ParametersDelegate + public UserOptsPeaks optsPeaks = new UserOptsPeaks(); + +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/LogicClean.java b/src/main/java/com/dmtavt/deltamass/logic/LogicClean.java new file mode 100644 index 0000000..c428d78 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/LogicClean.java @@ -0,0 +1,161 @@ +package com.dmtavt.deltamass.logic; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogicClean { + + private static final Logger log = LoggerFactory.getLogger(LogicClean.class); + final CommandClean cmd; + + public LogicClean(CommandClean cmd) { + this.cmd = cmd; + } + + public void run() { + log.info("Running cleanup"); + if (cmd.dryRun) log.info("Dry-run, won't take any action."); + int stepsPerformed = 0; + + if (!cmd.noPep) { + stepsPerformed++; + log.info("Cleaning up peptide identification files."); + DeletionStats stats = deleteMatchingFilesInSubtrees(cmd.inputFiles, + CacheLocator::isPepCacheFile, cmd.dryRun); + log.info("Pep ID cleanup summary: scheduled: {}, deleted: {}, skipped: {}, error: {}", + stats.scheduled, stats.deleted, stats.skipped.size(), stats.error.size()); + } + + if (!cmd.noLcms) { + stepsPerformed++; + log.info("Cleaning up LC-MS calibration files."); + DeletionStats stats = deleteMatchingFilesInSubtrees(cmd.inputFiles, + CacheLocator::isCalCacheFile, cmd.dryRun); + log.info("Calibration cleanup summary: scheduled: {}, deleted: {}, skipped: {}, error: {}", + stats.scheduled, stats.deleted, stats.skipped.size(), stats.error.size()); + } + + if (stepsPerformed == 0) log.info("You made it clear that nothing should be cleaned up. Doing nothing."); + } + + public List getScheduledForDeletion() { + List delete = new ArrayList<>(); + if (!cmd.noPep) { + delete.addAll(getScheduledForDeletion(cmd.inputFiles, CacheLocator::isPepCacheFile)); + } + if (!cmd.noLcms) { + delete.addAll(getScheduledForDeletion(cmd.inputFiles, CacheLocator::isCalCacheFile)); + } + return delete; + } + + private List getScheduledForDeletion(Iterable paths, Predicate condition) { + final ConcurrentLinkedQueue delete = new ConcurrentLinkedQueue<>(); + + for (Path path : paths) { + if (Files.isDirectory(path)) { + try { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + if (!Files.isReadable(dir)) return FileVisitResult.SKIP_SUBTREE; + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (!Files.isWritable(file)) { + log.warn("File not writeable, skipping: {}", file); + return FileVisitResult.CONTINUE; + } + if (condition.test(file)) { + delete.add(file); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.warn("Error collecting files for deletion in subtree: {}" + path, e); + } + } else { + if (condition.test(path)) { + delete.add(path); + } + } + } + return new ArrayList<>(delete); + } + + private class DeletionStats { + int scheduled = 0; + int deleted = 0; + ConcurrentLinkedQueue skipped = new ConcurrentLinkedQueue<>(); + ConcurrentLinkedQueue error = new ConcurrentLinkedQueue<>(); + } + + private DeletionStats deleteMatchingFilesInSubtrees(Iterable paths, Predicate condition, boolean dryRun) { + final ConcurrentLinkedQueue delete = new ConcurrentLinkedQueue<>(); + final DeletionStats stats = new DeletionStats(); + + for (Path path : paths) { + if (Files.isDirectory(path)) { + try { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + if (!Files.isReadable(dir)) return FileVisitResult.SKIP_SUBTREE; + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (!Files.isWritable(file)) { + log.warn("File not writeable, skipping: {}", file); + stats.skipped.add(file); + return FileVisitResult.CONTINUE; + } + if (condition.test(file)) delete.add(file); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.error("Error collecting files for deletion in subtree: {}" + path, e); + } + } else { + if (CacheLocator.isPepCacheFile(path)) + delete.add(path); + } + } + + if (!delete.isEmpty()) { + log.info("Files scheduled for deletion:\n {}", + delete.stream().map(Path::toString).collect(Collectors.joining("\n "))); + } else { + log.info("Found no files to delete."); + } + stats.scheduled = delete.size(); + + if (!dryRun) delete.forEach(path -> { + try { + Files.deleteIfExists(path); + stats.deleted++; + } catch (IOException e) { + log.error("Could not delete file: " + path, e); + stats.error.add(path); + } + }); + + return stats; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/LogicGui.java b/src/main/java/com/dmtavt/deltamass/logic/LogicGui.java new file mode 100644 index 0000000..1fe5f9d --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/LogicGui.java @@ -0,0 +1,125 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.DeltaMassInfo; +import com.dmtavt.deltamass.messages.MsgFlushGuiCache; +import com.dmtavt.deltamass.messages.MsgTest; +import com.dmtavt.deltamass.messages.MsgVersionUpdateInfo; +import com.dmtavt.deltamass.ui.FrameSerializer; +import com.dmtavt.deltamass.ui.DeltaMassOptionsForm; +import com.dmtavt.deltamass.ui.DeltaMassUiElements; +import com.github.chhh.utils.SwingUtils; +import java.awt.Component; +import java.awt.Window; +import java.awt.event.WindowEvent; +import java.lang.ref.WeakReference; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogicGui { + + private static final Logger log = LoggerFactory.getLogger(LogicGui.class); + private final EventBus bus = EventBus.getDefault(); + private WeakReference frameRef = new WeakReference<>(null); + private WeakReference scrollRef = new WeakReference<>(null); + + CommandGui cmd; + + + public LogicGui(CommandGui cmd) { + this.cmd = cmd; + bus.register(this); + } + + public void run() { + // try restore old cached window + final JFrame frame; + frame = new JFrame(DeltaMassInfo.getNameVersion()); + frameRef = new WeakReference<>(frame); + SwingUtilities.invokeLater(() -> SwingUtils.setUncaughtExceptionHandlerMessageDialog(frame)); + setLaf(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setMainFrameContent(frame); + frame.setJMenuBar(createMenuBar()); + frame.pack(); + frame.setVisible(true); + frame.addWindowListener(new FrameSerializer(frame)); + SwingUtils.centerFrame(frame); + SwingUtils.setFrameIcons(frame, DeltaMassUiElements.ICON_NAMES, DeltaMassUiElements.class); + } + + private void setMainFrameContent(JFrame frame) { + JPanel p = new DeltaMassOptionsForm(); + final JScrollPane oldScroll = scrollRef.get(); + if (oldScroll != null) { + frame.remove(oldScroll); + } + final JScrollPane newScroll = new JScrollPane(); + scrollRef = new WeakReference<>(newScroll); + newScroll.setViewportView(p); + frame.add(newScroll); + frame.revalidate(); + } + + @Subscribe + public void onMsgTest(MsgTest m) { + log.debug("MsgTest received: " + m.message); + } + + private JMenuBar createMenuBar() { + final JMenuBar bar = new JMenuBar(); + final JMenu menuFile = new JMenu("File"); + final JMenuItem close = new JMenuItem("Exit"); + close.addActionListener(e -> { + Component parent = SwingUtils.findParentFrameForDialog(bar); + if (parent instanceof JFrame) { + parent.dispatchEvent(new WindowEvent((Window) parent, WindowEvent.WINDOW_CLOSING)); + } + }); + menuFile.add(close); + + final JMenu menuAbout = new JMenu("Help"); + final JMenuItem itemFlushGuiCache = new JMenuItem("Restore default parameters"); + itemFlushGuiCache.addActionListener(e -> { + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(bar, "Restore defaults?")) { + bus.post(new MsgFlushGuiCache(itemFlushGuiCache)); + final JFrame frame = frameRef.get(); + setMainFrameContent(frame); + } + }); + menuAbout.add(itemFlushGuiCache); + + bar.add(menuFile); + bar.add(menuAbout); + + return bar; + } + + private void setLaf() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException e1) { + log.info("Could not load native LAF.", e1); + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException e2) { + log.warn("Could not set Look-And-Feel."); + } + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/LogicInputFiles.java b/src/main/java/com/dmtavt/deltamass/logic/LogicInputFiles.java new file mode 100644 index 0000000..c454f78 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/LogicInputFiles.java @@ -0,0 +1,441 @@ +package com.dmtavt.deltamass.logic; + +import static com.dmtavt.deltamass.utils.NumUtils.isGoodDouble; + +import com.dmtavt.deltamass.args.DecoyTreatment; +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.data.PepSearchFile; +import com.dmtavt.deltamass.data.PepSearchResult; +import com.dmtavt.deltamass.data.PepSearchResult.SortOrder; +import com.dmtavt.deltamass.parsers.IParserFactory; +import com.dmtavt.deltamass.parsers.IPepidParser; +import com.dmtavt.deltamass.parsers.PepidParserRegistry; +import com.dmtavt.deltamass.predicates.SpmPredicateFactory; +import com.dmtavt.deltamass.utils.NumUtils; +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.exceptions.ParsingException; +import com.github.chhh.utils.files.FileListing; +import com.github.chhh.utils.ser.DataStoreUtils; +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogicInputFiles { + private static final Logger log = LoggerFactory.getLogger(LogicInputFiles.class); + final UserOptsInputFiles opts; + + private FoundFiles foundFiles; + + public LogicInputFiles(UserOptsInputFiles opts) { + this.opts = opts; + } + + private void checkInterrupt() { + if (Thread.interrupted()) { + throw new RuntimeException("Interrupted"); + } + } + + public List run() { + List inputFiles; + final boolean isRecursive = !opts.noRecurse; + if (opts.fileRegex != null && !StringUtils.isNullOrWhitespace(opts.fileRegex.pattern())) { + inputFiles = inputFiles(opts.inputFiles, opts.fileRegex, isRecursive); + log.info("Total {} paths given or match file regular expression", inputFiles.size()); + } else { + inputFiles = collectSupportedFiles(opts.inputFiles, isRecursive); + log.info("Total {} found files in given paths without a regular expression to match against", inputFiles.size()); + } + + checkInterrupt(); + List supportedFiles = supportedFiles(inputFiles); + log.info("Total {} files supported by installed parsers", supportedFiles.size()); + + if (supportedFiles.isEmpty()) { + log.info("No files to read."); + return Collections.emptyList(); + } + + final FileReadingOpts fileReadingOpts = new FileReadingOpts(opts.decoyRegex); + final MassCorrectionOpts massCorrOpts = new MassCorrectionOpts( + opts.massCorrection, opts.additionalSearchPaths, opts.scoreNameForPsmSorting, opts.noCache); + + ConcurrentHashMap pepSearchFiles = new ConcurrentHashMap<>(); + Queue couldNotReadFiles = new ConcurrentLinkedQueue<>(); + + + foundFiles = new FoundFiles(inputFiles, supportedFiles, cachedFiles(supportedFiles)); + final List toLoad = foundFiles.supported.stream().sorted().collect(Collectors.toList()); + AtomicLong totalSpmsBeforeFiltering = new AtomicLong(0); + AtomicLong totalSpmsAfterFiltering = new AtomicLong(0); + + log.info("Loading {} files\n {}", toLoad.size(), + toLoad.stream().map(path -> { + if (!opts.noCache && foundFiles.cached.containsKey(path)) + return "[cached] " + path.toAbsolutePath().normalize().toString(); + return "[parse ] " + path.toAbsolutePath().normalize().toString(); + }).collect(Collectors.joining("\n "))); + + List descriptions = filterOptsDescription(opts); + if (!descriptions.isEmpty()) { + log.info("Filtering PSMs (rules follow):\n {}", String.join("\n ", descriptions)); + } + + // iterate over found files + for (Path raw : toLoad) { + checkInterrupt(); + PepSearchFile pepSearchFile = null; + + Path cached = opts.noCache ? null : foundFiles.cached.get(raw); + if (cached != null) { // trying to read cached + log.info("Cached file: {}", cached); + try { + pepSearchFile = readCached(cached); + } catch (IOException e1) { + log.error(String.format("Error loading cached file: %s,\n" + + " trying original instead: %s", cached, raw)); + } + } + + if (pepSearchFile == null) { // no cache, parsing file + log.info("Parsing file: {}", raw); + try { + pepSearchFile = readRaw(raw, fileReadingOpts); + } catch (IOException e) { + log.error("Could not read file: {}", raw); + couldNotReadFiles.add(raw); + } + if (!opts.noCache && pepSearchFile != null) { + try { + Path cachePepPath = CacheLocator.locateForPep(raw); + log.info("Writing cache: {}", cachePepPath); + writeCached(cachePepPath, pepSearchFile); + } catch (IOException e) { + log.warn("Could not write cache file for: " + raw, e); + } + } + } + + + if (pepSearchFile == null) + continue; + + + // update Mass Correction for the loaded file + boolean updated; + try { + updated = updateMassCorrection(pepSearchFile, massCorrOpts); + } catch (IOException e) { + log.error("Could not update mass correction for: {}", pepSearchFile.sourceFile); + log.warn("Try specifying Additional search paths for LCMS files. 'Search paths' text field in GUI, " + + "or '--search-path' command-line option."); + throw new IllegalStateException("Mass correction could not be updated.", e); + } + if (!opts.noCache && updated) { + final Path cachePepPath = CacheLocator.locateForPep(raw); + try { + log.info("Mass correction changed, updating cache: {}", cachePepPath); + writeCached(cachePepPath, pepSearchFile); + } catch (IOException e) { + log.warn("Could not update cache file for: " + raw, e); + } + } + + + // filter the file + int spmsBeforeFiltering = pepSearchFile.pepSearchResults.stream() + .mapToInt(pepSearchResult -> pepSearchResult.spms.size()).sum(); + totalSpmsBeforeFiltering.addAndGet(spmsBeforeFiltering); + + pepSearchFile = PepSearchFile.filter(pepSearchFile, + opts.mLo, opts.mHi, opts.excludeMzRanges, opts.decoyTreatment, opts.predicates); + + int spmsAfterFiltering = pepSearchFile.pepSearchResults.stream() + .mapToInt(pepSearchResult -> pepSearchResult.spms.size()).sum(); + totalSpmsAfterFiltering.addAndGet(spmsAfterFiltering); + + + // done reading/processing input file + pepSearchFiles.put(raw, pepSearchFile); + } + + + if (!couldNotReadFiles.isEmpty()) + throw new IllegalStateException("Not all input files could be read."); + + + log.info("Loaded {} files total, containing: {} datasets, {} PSMs total, " + + "{} PSMs left after filtering.", + pepSearchFiles.size(), pepSearchFiles.values().stream() + .mapToInt(psf -> psf.pepSearchResults.size()).sum(), + totalSpmsBeforeFiltering.get(), totalSpmsAfterFiltering.get()); + + + return new ArrayList<>(pepSearchFiles.values()); + } + + private List filterOptsDescription(UserOptsInputFiles opts) { + List desciptors = new ArrayList<>(); + + // global delta mass range + final boolean isLoGood = NumUtils.isGoodDouble(opts.mLo); + final boolean isHiGood = NumUtils.isGoodDouble(opts.mHi); + if (isLoGood || isHiGood) { + StringBuilder sb = new StringBuilder(); + sb.append("Corrected delta mass: [") + .append(isLoGood ? String.format("%.1f", opts.mLo) : "inf").append("; ") + .append(isLoGood ? String.format("%.1f", opts.mHi) : "inf"); + desciptors.add(sb.toString()); + } + + // delta mass exclusions + for (int i = 0; i < opts.excludeMzRanges.size(); i += 2) { + Double lo = opts.excludeMzRanges.get(i); + Double hi = opts.excludeMzRanges.get(i + 1); + if (isGoodDouble(lo) && isGoodDouble(hi)) { + desciptors.add(String.format("Excluding delta mass range: [%.1f; %.1f]", lo, hi)); + } else { + throw new IllegalStateException("Some values in excludes list were not finite doubles."); + } + } + + // When DecoyTreatment is USE_BOTH no predicates are added + if (DecoyTreatment.FORWARDS_ONLY.equals(opts.decoyTreatment)) { + desciptors.add("Leaving only: Non-decoy PSMs"); + } else if (DecoyTreatment.DECOYS_ONLY.equals(opts.decoyTreatment)) { + desciptors.add("Leaving only: Decoy PSMs"); + } + + if (opts.predicates != null) { + opts.predicates.stream().map(SpmPredicateFactory::toString).forEach(desciptors::add); + } + + return desciptors; + } + + /** + * + * @param inputPaths Can be directories. + * @param regex To be matched against the whole path. + * @return List of file paths that match. + */ + private List inputFiles(List inputPaths, final Pattern regex, boolean recursive) { + List matched = new ArrayList<>(); + + if (regex != null) { + // match by regex + for (Path path : inputPaths) { + if (!Files.isDirectory(path)) { + matched.add(path); + } else { + matched.addAll( + new FileListing(path, regex).setRecursive(recursive).setIncludeDirectories(false) + .findFiles()); + } + } + } else { + // all supported + + } + return matched; + } + + private List collectSupportedFiles(Iterable paths, boolean recursive) { + final ConcurrentLinkedQueue supported = new ConcurrentLinkedQueue<>(); + + for (Path path : paths) { + if (Thread.interrupted()) { + throw new RuntimeException("Interrupted"); + } + if (Files.isDirectory(path)) { + try { + final int depth = recursive ? Integer.MAX_VALUE : 1; + final EnumSet fileVisitOptions = EnumSet.noneOf(FileVisitOption.class); + final SimpleFileVisitor visitor = new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + if (!Files.isReadable(dir)) + return FileVisitResult.SKIP_SUBTREE; + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + IParserFactory parser = PepidParserRegistry.find(file); + if (parser != null) { + supported.add(file); + } + return FileVisitResult.CONTINUE; + } + }; + Files.walkFileTree(path, fileVisitOptions, depth, visitor); + } catch (IOException e) { + log.warn("Error collecting files for deletion in subtree: {}" + path, e); + } + } else { + if (CacheLocator.isPepCacheFile(path)) + supported.add(path); + } + } + return new ArrayList<>(supported); + } + + /** + * Find files that are supported by installed parsers. + * @param files List of files to check against installed parsers. + * @return A list that should not be modified. + */ + private List supportedFiles(List files) { + return files.stream() + .filter(path -> PepidParserRegistry.find(path) != null) + .collect(Collectors.toList()); + } + + /** + * Find the cache files. + * @param files List of input files to check. + * @return Mapping from input paths to corresponding cache paths. + */ + private Map cachedFiles(List files) { + Map cached = new HashMap<>(); + for (Path inputFile : files) { + Path cachedPath = CacheLocator.locateForPep(inputFile); + if (Files.exists(cachedPath)) { + cached.put(inputFile, cachedPath); + } + } + return cached; + } + + private PepSearchFile readRaw(Path path, FileReadingOpts opts) throws IOException { + if (Thread.interrupted()) { + throw new RuntimeException("Interrupted"); + } + IParserFactory factory = PepidParserRegistry.find(path); + if (factory == null) throw new IllegalStateException("Couldn't find parser factory."); + PepSearchFile psf = factory.create(path).parse(opts.decoyRegex); + // when reading data we always need it in sorted order by mass diff + psf.pepSearchResults.forEach(psr -> psr.sort(SortOrder.DELTA_MASS_ASCENDING)); + return psf; + } + + private PepSearchFile readCached(Path path) throws IOException { + if (Thread.interrupted()) { + throw new RuntimeException("Interrupted"); + } + try { + return DataStoreUtils.deserialize(path, PepSearchFile.class); + } catch (ParsingException e) { + throw new IOException(e); + } + } + + private void writeCached(Path path, PepSearchFile pepSearchFile) throws IOException { + try { + DataStoreUtils.serialize(path, pepSearchFile); + } catch (ParsingException e) { + throw new IOException(e); + } + } + + /** + * + * @param psf + * @param opts + * @return True if input {@code PepSearchFile} was modified, thus might need to be + * serialized again. + * @throws IOException When can't read original raw files in case of PEP_ID mass correction. + */ + private boolean updateMassCorrection(PepSearchFile psf, MassCorrectionOpts opts) throws IOException { + boolean updated = false; + + for (PepSearchResult pepSearchResult : psf.pepSearchResults) { + if (Thread.interrupted()) { + throw new RuntimeException("Interrupted"); + } + if (pepSearchResult.massCorrection.equals(opts.massCorrection)) + continue; + MassCorrector mc; + switch (opts.massCorrection) { + case NONE: + mc = new MassCorrectorNone(); + break; + + case ZERO_PEAK: + mc = new MassCorrectorZeroPeak(); + break; + + + case PEP_ID: + mc = new MassCorrectionMs1(opts.scoreNameForPsmSorting, opts.additionalSearchPaths, opts.noCache); + break; + +// case MS1_TRACE: +// throw new NotImplementedException("TODO: Just trace MS1 without pep ids"); // TODO: Not implemented + + default: + throw new IllegalStateException("Unknown enum element for MassCorrection"); + } + mc.apply(pepSearchResult); + updated = true; + } + + return updated; + } + + public static class FoundFiles { + final public List input; + final public List supported; + final public Map cached; + + public FoundFiles(List input, + List supported, + Map cached) { + this.input = input; + this.supported = supported; + this.cached = cached; + } + } + + private static class MassCorrectionOpts { + final MassCorrection massCorrection; + final List additionalSearchPaths; + final String scoreNameForPsmSorting; + final boolean noCache; + + private MassCorrectionOpts(MassCorrection massCorrection, + List additionalSearchPaths, String scoreNameForPsmSorting, boolean noCache) { + this.massCorrection = massCorrection; + this.additionalSearchPaths = additionalSearchPaths; + this.scoreNameForPsmSorting = scoreNameForPsmSorting; + this.noCache = noCache; + } + } + + private static class FileReadingOpts { + final Pattern decoyRegex; + + public FileReadingOpts(Pattern decoyRegex) { + this.decoyRegex = decoyRegex; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/LogicKde.java b/src/main/java/com/dmtavt/deltamass/logic/LogicKde.java new file mode 100644 index 0000000..aaf73a8 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/LogicKde.java @@ -0,0 +1,219 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.data.PepSearchFile; +import com.dmtavt.deltamass.data.PepSearchResult; +import com.dmtavt.deltamass.data.PepSearchResult.SortOrder; +import com.dmtavt.deltamass.data.Spm; +import com.dmtavt.deltamass.kde.GaussFasterKF; +import com.dmtavt.deltamass.kde.KdeKludge; +import com.dmtavt.deltamass.utils.GridUtils; +import com.github.chhh.utils.search.BinarySearch; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import jsat.distributions.empirical.KernelDensityEstimator; +import jsat.linear.DenseVector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogicKde { + + private static final Logger log = LoggerFactory.getLogger(LogicKde.class); + private static final DecimalFormat df2 = new DecimalFormat("0.00"); + private static final DecimalFormat df3 = new DecimalFormat("0.000)"); + + private final UserOptsKde opts; + private final List pepSearchFiles; + + LogicKde(UserOptsKde opts, List pepSearchFiles) { + this.opts = opts; + this.pepSearchFiles = pepSearchFiles; + } + + public List run() { + + // make sure input data is sorted + log.info("Collecting and sorting input data for KDE."); + pepSearchFiles.stream() + .flatMap(psf -> psf.pepSearchResults.stream()) + .parallel().forEach(psr -> psr.sort(SortOrder.DELTA_MASS_ASCENDING)); + + // Collect SPMs from all files into one structure, + // wrap SPMs with meta info and sort by mass diff + List spmInfos = pepSearchFiles.stream() + .flatMap(f -> f.pepSearchResults.stream()) + .flatMap(r -> r.spms.stream() + .map(spm -> new SpmInfo(spm, r))) + .sorted(Comparator.comparingDouble(si -> si.spm.mDiffCorrected)) + .collect(Collectors.toList()); + if (spmInfos.isEmpty()) { + log.error("No PSMs given for KDE. Maybe you filtered them all out?"); + throw new IllegalStateException("No PSMs given for KDE."); + } + + List data = segment(spmInfos, opts.minData); + log.info("Prepared data for {} nominal mass data segments", data.size()); + + ProcessingConfig conf = new ProcessingConfig(opts.step, opts.bandwidth); + List processed = process(data, conf); + + return processed; + } + + /** + * @param minData If a segment contains less than + */ + List segment(List spmInfos, int minData) { + // find delta mass bounds + final double dmLo = spmInfos.get(0).spm.mDiffCorrected; + final double dmHi = spmInfos.get(spmInfos.size() - 1).spm.mDiffCorrected; + final double segSpan = 1.0; + final double xLoBound = Math.round(dmLo) - segSpan / 2.0; + final double xHiBound = Math.round(dmHi) + segSpan / 2.0; + + List segments = new ArrayList<>(); + for (int i = (int) xLoBound; i <= (int) xHiBound; i++) { + final double lo = i - segSpan / 2.0; + final double hi = i + segSpan / 2.0; + int searchLo = BinarySearch.search(spmInfos, si -> Double.compare(si.spm.mDiffCorrected, lo)); + int insertionLo = searchLo < 0 ? ~searchLo : searchLo; + int searchHi = BinarySearch.search(spmInfos, si -> Double.compare(si.spm.mDiffCorrected, hi)); + int insertionHi = searchHi < 0 ? ~searchHi : searchHi; + List sub = spmInfos.subList(insertionLo, insertionHi); + if (sub.isEmpty()) { + log.info("Skipping data range [{}; {}] - empty", df2.format(lo), df2.format(hi)); + } else if (sub.size() < minData) { + log.info("Skipping data range [{}; {}] - less than {} data points", df2.format(lo), + df2.format(hi), minData); + } else { + segments.add(new DataSegment(lo, hi, sub)); + } + } + return segments; + } + + private List process(List data, ProcessingConfig conf) { + List processed = new ArrayList<>(); + for (DataSegment ds : data) { + ProcessingSegment ps = new ProcessingSegment(ds); + ps.process(conf); + processed.add(ps); + } + return processed; + } + + public static class SpmInfo { + + public final Spm spm; + public final PepSearchResult psr; + + public SpmInfo(Spm spm, PepSearchResult psr) { + this.spm = spm; + this.psr = psr; + } + } + + public static class DataSegment { + + public final double xLoBracket; + public final double xHiBracket; + + public final double xLoData; + public final double xHiData; + public final double xMeanData; + public final List spmInfos; + + public DataSegment(double xLoBracket, double xHiBracket, List spmsInfos) { + this.xLoBracket = xLoBracket; + this.xHiBracket = xHiBracket; + for (int i = 0, sz = spmsInfos.size() - 1; i < sz; i++) { + if (spmsInfos.get(i + 1).spm.mDiffCorrected < spmsInfos.get(i).spm.mDiffCorrected) + throw new IllegalArgumentException("Input list must be sorted ascending by SPM mDiffCorrected"); + } + if (spmsInfos.isEmpty()) + throw new IllegalArgumentException("Input list can't be empty"); + + this.xLoData = spmsInfos.get(0).spm.mDiffCorrected; + this.xHiData = spmsInfos.get(spmsInfos.size()-1).spm.mDiffCorrected; + this.xMeanData = spmsInfos.stream().mapToDouble(si -> si.spm.mDiffCorrected).average().orElseThrow(IllegalArgumentException::new); + this.spmInfos = spmsInfos; + } + } + + public static class ProcessingSegment { + + public final DataSegment data; + private boolean isProcessed; + + public double[] x; + public double xStep; + public double bandwidth = Double.NaN; + public double[] kde; + public double[] der2; + public double kdeAuc; + + public ProcessingSegment(DataSegment data) { + this.data = data; + isProcessed = false; + } + + public boolean isProcessed() { return isProcessed; } + + void process(ProcessingConfig config) { + if (Thread.interrupted()) { + throw new RuntimeException("Interrupted"); + } + isProcessed = false; + + // kde data + double[] kdeDataArr = new double[data.spmInfos.size()]; + for (int i = 0, sz = data.spmInfos.size(); i < sz; i++) + kdeDataArr[i] = data.spmInfos.get(i).spm.mDiffCorrected; + DenseVector kdeDataVec = new DenseVector(kdeDataArr); + + // bandwidth + final double h = config.bandwidth > 0 && Double.isFinite(config.bandwidth) + ? config.bandwidth + : KernelDensityEstimator.BandwithGuassEstimate(kdeDataVec); + bandwidth = h; + + // grid + xStep = config.xStep; + final double drawDistance = h * 2.0; + final double gridLoBound = Math.max(data.xLoData - drawDistance, data.xLoBracket); + final double gridHiBound = Math.min(data.xHiData + drawDistance, data.xHiBracket); + x = GridUtils.grid(data.xMeanData, gridLoBound, gridHiBound, xStep, xStep/2); + + // kde estimate + KdeKludge estimator = new KdeKludge(kdeDataVec, GaussFasterKF.getInstance(), h); + kde = new double[x.length]; + der2 = new double[x.length]; + kdeAuc = 0; + for (int i = 0; i < x.length; i++) { + final double pdf = estimator.pdf(x[i]); + kde[i] = pdf; + kdeAuc += pdf; + der2[i] = estimator.pdfPrime2(x[i]); + } + kdeAuc *= xStep; + log.debug("Total KDE PDF area for range [{}; {}] is {}", + df2.format(data.xLoBracket), df2.format(data.xHiBracket), df2.format(kdeAuc)); + isProcessed = true; + } + } + + + + protected static class ProcessingConfig { + + public final double xStep; + public final double bandwidth; + + private ProcessingConfig(double xStep, double bandwidth) { + this.xStep = xStep; + this.bandwidth = bandwidth; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/LogicPeaks.java b/src/main/java/com/dmtavt/deltamass/logic/LogicPeaks.java new file mode 100644 index 0000000..05fbe05 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/LogicPeaks.java @@ -0,0 +1,411 @@ +package com.dmtavt.deltamass.logic; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; + +import com.beust.jcommander.ParameterException; +import com.dmtavt.deltamass.data.PepSearchFile; +import com.dmtavt.deltamass.logic.LogicKde.ProcessingSegment; +import com.dmtavt.deltamass.utils.PeakUtils; +import com.dmtavt.deltamass.utils.PeakUtils.Peak; +import com.dmtavt.deltamass.utils.PeakUtils.PeakDetectionConfig; +import com.github.chhh.utils.MathUtils; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.math3.analysis.interpolation.LinearInterpolator; +import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.ptm.PtmFactory; +import umich.ptm.exceptions.ModParsingException; +import umich.ptm.mod.Mod; +import umich.ptm.mod.Mods; + +public class LogicPeaks { + + private static final Logger log = LoggerFactory.getLogger(LogicPeaks.class); + public static final String OUTPUT_FILE_NAME = "deltamass-peaks.csv"; + final CommandPeaks cmd; + + public LogicPeaks(CommandPeaks cmd) { + this.cmd = cmd; + } + + public void run() throws ParameterException { + validateInputs(); + + LogicInputFiles inputFiles = new LogicInputFiles(cmd.optsInputFiles); + List searchFiles = inputFiles.run(); + LogicKde logicKde = new LogicKde(cmd.optsKde, searchFiles); + List processingSegments = logicKde.run(); + + // detect peaks in segments + final PeakDetectionConfig conf = new PeakDetectionConfig(cmd.optsPeaks.minPeakPct, + cmd.optsPeaks.minPsmsPerGmm); + final List detecteds = new ArrayList<>(); + log.debug("Running peak detection"); + for (ProcessingSegment segment : processingSegments) { + detecteds.add(LogicPeaks.detectPeaks(segment, conf)); + } + + // write peaks + final Path outPath = cmd.optsPeaks.out; + if (outPath == null) { + // if no path, just print to screen + writePeaks(System.out, detecteds, cmd.optsPeaks.digitsMz, cmd.optsPeaks.digitsAb); + } else { + try { + // we asked for user permission to overwrite the file before running + final Path outputFilePath = LogicPeaks.getOutputFilePath(outPath); + LogicPeaks.writePeaks(outputFilePath, detecteds, cmd.optsPeaks.digitsMz, cmd.optsPeaks.digitsAb); + } catch (IOException e) { + log.error("Error writing output file: " + outPath.toString(), e); + } + } + } + + private void validateInputs() { + final Path outPath = cmd.optsPeaks.out; + if (outPath != null) { + // check if output file exists + final Path outFilePath = getOutputFilePath(outPath); + if (!cmd.optsPeaks.overwrite && Files.exists(outFilePath) + && Files.isRegularFile(outFilePath)) { + String msg = "Output file exists:\n" + + " " + outFilePath.toString() + "\n" + + "Overwrite option (\"-w\" or \"--force\") not specified. Not proceeding."; + throw new ParameterException(msg); + } + } + } + + public static String getDefaultOutputFileName() { + return OUTPUT_FILE_NAME; + } + + /** + * If the given path not exists or is a regular file, just returns the same path. + * Otherwise (i.e. if given an existing directory) returns a file with default name in that + * directory. + * + * User should be asked about overwriting if the path returned by this method exists. + */ + public static Path getOutputFilePath(Path userSpecifiedPath) { + if (!Files.exists(userSpecifiedPath)) { + return userSpecifiedPath; + } + // provided path exists, but is a directory + if (Files.isDirectory(userSpecifiedPath)) { + return userSpecifiedPath.resolve(getDefaultOutputFileName()); + } + // existing regular file, should ask about overwriting + return userSpecifiedPath; + } + + /** + * Will overwrite any existing file, it's up to the user to figure if the file exists. + * Will also create all the missing directories along the way to the file. + * + * @param path Should point to the file to write to. + */ + public static void writePeaks(Path path, List detecteds, + Integer decimalDigitsMz, Integer decimalDigitsAb) throws IOException { + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + try (OutputStream os = Files.newOutputStream(path, CREATE, TRUNCATE_EXISTING)) { + PrintStream ps = new PrintStream(os, true, "UTF-8"); + log.info("Writing peaks to file: " + path.toString()); + writePeaks(ps, detecteds, decimalDigitsMz, decimalDigitsAb); + } + } + + /** + * @param decimalDigitsMz The format for printing masses. Can be null, default will be used. + * @param decimalDigitsAb The format for printing intensities. Can be null, default will be used. + */ + public static void writePeaks(PrintStream ps, List detecteds, + Integer decimalDigitsMz, Integer decimalDigitsAb) { + ArrayList toPrint = detecteds.stream() + .flatMap(detected -> detected.gmms.stream().map(GmmForPrinting::new)) + .sorted((o1, o2) -> Double.compare(o2.score, o1.score)) + .collect(Collectors.toCollection(ArrayList::new)); + + final PeaksWriter pw = PeaksWriter.getDefault(decimalDigitsMz, decimalDigitsAb); + final String delimiterCol = ", "; + final String delimiterRow = "\n"; + String rows = Stream + .concat(Stream.of(pw.colsNames), toPrint.stream().map(pw.mapper)) + .map(cols -> String.join(delimiterCol, cols)) + .collect(Collectors.joining(delimiterRow)); + + ps.println(rows); + } + + private static class ModEntry { + final double m; + final double mError; + final double likelihood; + final Mod mod; + + public ModEntry(double m, double mError, double likelihood, Mod mod) { + this.m = m; + this.mError = mError; + this.likelihood = likelihood; + this.mod = mod; + } + } + + public static class PeaksWriter { + + public final List colsNames; + public final Function> mapper; + + private final static String valSep = "::"; + private final static String entrySep = "; "; + + public PeaksWriter(List colsNames, Function> mapper) { + this.colsNames = colsNames; + this.mapper = mapper; + } + + private static final String fmt(String format, Object param) { + return String.format(format, param); + } + + public static PeaksWriter getDefault(Integer decimalDigitsMz, Integer decimalDigitsAb) { + + decimalDigitsMz = decimalDigitsMz != null ? decimalDigitsMz : 4; + decimalDigitsAb = decimalDigitsAb != null ? decimalDigitsAb : 1; + + // PTM annotations + final Mods mods; + try { + mods = PtmFactory.getMods(EnumSet.of(PtmFactory.SOURCE.GEN, PtmFactory.SOURCE.UNIMOD)); + } catch (ModParsingException e) { + throw new IllegalStateException("Could not create a list of annotation PTMs"); + } + + final String fMz = "%." + decimalDigitsMz + "f"; + final String fAb = "%." + decimalDigitsAb + "f"; + final String f1 = "%.1f"; + final String f2 = "%.2f"; + final String f3 = "%.3f"; + final String f4 = "%.4f"; + final DecimalFormat df1 = new DecimalFormat("0.0E0"); + + final Function annotationProvider = gfp -> { + final double m = gfp.gmmc.mu; + final double sd = gfp.gmmc.sigma; + final double sdDistance = 2.0; // annotations max 2 SD away from peaks in KDE + + return mods.findByMass(m - sdDistance * sd, m + sdDistance * sd).stream() + .map(mod -> { + final Double massMono = mod.getComposition().getMassMono(); + if (massMono == null) { + log.warn( + "Monoisotopic mass of elemental composition was null while mapping possible mods for a peak"); + return null; + } + final double mError = Math.abs(massMono - m); + final double p = MathUtils.normalPdf(mError,0, sd); + final double pMax = MathUtils.normalPdf(0, 0, sd); + final double pNorm = p / pMax; + final double likelihood = Double.isFinite(pNorm) ? pNorm : 0; + return new ModEntry(massMono, mError, likelihood, mod); + }) + .filter(Objects::nonNull) + .sorted((o1, o2) -> Double.compare(o2.likelihood, o1.likelihood)) + .map(me -> me.mod.getRef() + valSep + + "m=" + fmt(f4, me.m) + "Da" + valSep + + "mErr=" + df1.format(me.mError) + "Da" + valSep + + "q=" + fmt(f2, me.likelihood) + valSep + + me.mod.getDescShort()) + .collect(Collectors.joining(entrySep, "\"", "\"")); + }; + + final List names = Arrays + .asList("dm", "fwhm", "stddev", "support", "intensity", "quality", "score", "annotations"); + + final Function> mapper = gfp -> { + List cols = new ArrayList<>(names.size()); + cols.add(fmt(fMz, gfp.gmmc.mu)); + cols.add(fmt(fMz, MathUtils.fwhmFromSigma(gfp.gmmc.sigma))); + cols.add(fmt(fMz, gfp.gmmc.sigma)); + cols.add(fmt(f1, gfp.gmmc.psmSupportApprox)); + cols.add(fmt(fAb, gfp.gmmc.kdeValAtPeakOrigin)); + cols.add(fmt(f2, gfp.quality)); + cols.add(fmt(f2, gfp.score)); + cols.add(annotationProvider.apply(gfp)); + return cols; + }; + + return new PeaksWriter(names, mapper); + } + } + + public static class GmmForPrinting { + public final GmmComponent gmmc; + public final double quality; + public final double score; + + public GmmForPrinting(GmmComponent gmmc) { + this.gmmc = gmmc; + quality = gmmc.peakOrigin.intensity; + score = quality * gmmc.psmSupportApprox; + } + } + + /** + * Detect peaks in a processed data segment. Processed means KDE was calculated for it. + * @throws IllegalStateException if {@link ProcessingSegment#isProcessed()} is false. + */ + public static Detected detectPeaks(ProcessingSegment segment, PeakDetectionConfig conf) { + if (!segment.isProcessed()) { + throw new IllegalStateException("Unprocessed segment passed to detectPeaks()"); + } + + // invert sign of 2nd derivative + double[] der2Inv = Arrays.copyOf(segment.der2, segment.der2.length); + for (int i = 0; i < der2Inv.length; i++) { + der2Inv[i] *= -1; + } + + final Detected detected = new Detected(segment); + // local minima in the 2nd derivative + List der2Peaks = PeakUtils + .peakLocations(der2Inv, 0, der2Inv.length, peakApprox -> true); + // remove flanking plateaus + der2Peaks.removeIf(p -> p.valTop == 0.0); + + // fit peaks with parabolas + for (PeakUtils.PeakApprox peakApprox : der2Peaks) { + Peak peak = PeakUtils.fitPeakByParabola(peakApprox, segment.x, der2Inv, true); + detected.peaks.add(peak); + } + + // fit GMM + List gmms = gmm(detected, conf); + detected.gmms = gmms; + + return detected; + } + + private static List gmm(Detected detected, PeakDetectionConfig conf) { + final int len = detected.peaks.size(); + final double[] weights = new double[len]; + final double[] mus = new double[len]; + final double[] sigmas = new double[len]; + double sumWeights = 0; + for (int i = 0; i < len; i++) { + Peak p = detected.peaks.get(i); + weights[i] = p.intensity; + sumWeights += weights[i]; + mus[i] = p.location; + switch (p.widthType) { + case AT_BASE: + sigmas[i] = p.width / 6.0; + break; + case FWHM: + sigmas[i] = p.width / 2.0; + break; + default: + throw new AssertionError("Unknown enum value"); + } + } + for (int i = 0; i < len; i++) { + weights[i] /= sumWeights; + } + + List gmms = new ArrayList<>(); + for (int i = 0; i < len; i++) { + Peak peak = detected.peaks.get(i); + ProcessingSegment seg = detected.processed; + final int psmsInSeg = seg.data.spmInfos.size(); + final double psmsInKdeAuc = psmsInSeg * seg.kdeAuc; + + // KDE value at peak's location interpolated from the calculated KDE grid + LinearInterpolator interpolator = new LinearInterpolator(); + PolynomialSplineFunction iKde = interpolator.interpolate(seg.x, seg.kde); + final double kdeValAtPeakOrigin = iKde.value(peak.location) * psmsInKdeAuc; + + final double psmSupport = psmsInKdeAuc * weights[i]; + GmmComponent gmmc = new GmmComponent(peak, kdeValAtPeakOrigin, weights[i], mus[i], sigmas[i], psmSupport); + if (gmmc.psmSupportApprox < conf.minPsmsPerGmm) { + continue; + } + gmms.add(gmmc); + } + + return gmms; + + // TODO: optimization search here, not urgent + +// Normal.pdf() +// BacktrackingArmijoLineSearch lineSearch = new BacktrackingArmijoLineSearch(); +// +// LBFGS lbfgs = new LBFGS(5, 100, lineSearch); +// Vec solution = new DenseVector(sz * 2); +// +// Function f = new Function() { +// @Override +// public double f(double... x) { +// return 0; +// } +// +// @Override +// public double f(Vec x) { +// return 0; +// } +// }; +// +// +// lbfgs.optimize(1e-12, solution, new DenseVector(Arrays.asList(0.0)), func, funcp, null); +// double mCorrection = solution.get(0); + } + + public static class Detected { + public final ProcessingSegment processed; + public List peaks; + public List gmms; + + + public Detected(ProcessingSegment processed) { + this.processed = processed; + peaks = new ArrayList<>(); + gmms = new ArrayList<>(); + } + } + + public static class GmmComponent { + final Peak peakOrigin; + final double kdeValAtPeakOrigin; + final double weight; + final double mu; + final double sigma; + final double psmSupportApprox; + + public GmmComponent(Peak associatedPeak, double kdeValAtPeakOrigin, double weight, + double mu, double sigma, double psmSupportApprox) { + this.peakOrigin = associatedPeak; + this.kdeValAtPeakOrigin = kdeValAtPeakOrigin; + this.weight = weight; + this.mu = mu; + this.sigma = sigma; + this.psmSupportApprox = psmSupportApprox; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/LogicPlot.java b/src/main/java/com/dmtavt/deltamass/logic/LogicPlot.java new file mode 100644 index 0000000..7ab9e5b --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/LogicPlot.java @@ -0,0 +1,362 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.DeltaMassInfo; +import com.dmtavt.deltamass.data.PepSearchFile; +import com.dmtavt.deltamass.logic.LogicKde.ProcessingSegment; +import com.dmtavt.deltamass.logic.LogicPeaks.Detected; +import com.dmtavt.deltamass.logic.LogicPeaks.GmmComponent; +import com.dmtavt.deltamass.ui.PlotFactory; +import com.dmtavt.deltamass.ui.PlotFactory.CloseOption; +import com.dmtavt.deltamass.utils.GridUtils; +import com.dmtavt.deltamass.utils.PeakUtils.PeakDetectionConfig; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.color.ColorHelper; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Paint; +import java.awt.Stroke; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import jsat.distributions.Normal; +import org.apache.commons.math3.analysis.interpolation.LinearInterpolator; +import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.renderer.xy.StandardXYBarPainter; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.chart.renderer.xy.YIntervalRenderer; +import org.jfree.data.statistics.SimpleHistogramBin; +import org.jfree.data.statistics.SimpleHistogramDataset; +import org.jfree.data.xy.DefaultXYDataset; +import org.jfree.data.xy.YIntervalDataItem; +import org.jfree.data.xy.YIntervalSeries; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogicPlot { + + private static final Logger log = LoggerFactory.getLogger(LogicPlot.class); + final CommandPlot cmd; + + public static final Paint[] COLORS_BETTER = new Paint[] { + new Color(106,61,154), + new Color(255,127,0), + new Color(227,26,28), + new Color(51,160,44), + new Color(31,120,180), + new Color(202,178,214), + new Color(253,191,111), + new Color(251,154,153), + new Color(178,223,138), + new Color(166,206,227), + }; + + public static final Paint[] COLORS_GMMS = new Paint[] { +// new Color(0,60,0), + new Color(42, 49, 179), + new Color(210, 0, 12), + new Color(25, 152, 34), + }; + + public LogicPlot(CommandPlot cmd) { + this.cmd = cmd; + } + + public void run() { + if (!SwingUtils.isGraphicalEnvironmentAvailable()) { + log.error("No graphical environment, can't plot."); + return; + } + + LogicInputFiles inputFiles = new LogicInputFiles(cmd.optsInputFiles); + + final List searchFiles; + try { + searchFiles = inputFiles.run(); + } catch (Exception e) { + log.error("Error collecting input data, stopping"); + return; + } + + LogicKde logicKde = new LogicKde(cmd.optsKde, searchFiles); + List kde = logicKde.run(); + plot(kde); + } + + private void plot(List segments) { + log.debug("Start plot(segments)"); + double xLo = Double.POSITIVE_INFINITY; + double xHi = Double.NEGATIVE_INFINITY; + for (ProcessingSegment seg : segments) { + if (seg.x[0] < xLo) + xLo = seg.x[0]; + if (seg.x[seg.x.length - 1] > xHi) + xHi = seg.x[seg.x.length - 1]; + } + final double plottingStep = cmd.optsPlot.step; + double[] grid = GridUtils.grid(xLo, xHi, plottingStep); + + // interpolate values from KDE results onto the plotting grid + double[] valsKde = new double[grid.length]; + double[] valsDer2 = new double[grid.length]; + LinearInterpolator interpolator = new LinearInterpolator(); +// SplineInterpolator interpolator = new SplineInterpolator(); + + for (ProcessingSegment seg : segments) { + // map data range of segment to the corresponding span in grid + final double segXLo = seg.x[0]; + final double segXHi = seg.x[seg.x.length - 1]; + int iXLo = Arrays.binarySearch(grid, segXLo); + iXLo = iXLo >= 0 ? iXLo : ~iXLo; + int iXHi = Arrays.binarySearch(grid, segXHi); + iXHi = iXHi >= 0 ? iXHi : ~iXHi; + + PolynomialSplineFunction iKde = interpolator.interpolate(seg.x, seg.kde); + PolynomialSplineFunction iDer = interpolator.interpolate(seg.x, seg.der2); +; + for (int i = iXLo, cap = iXHi; i < cap; i++) { + final double gridVal = grid[i]; + valsKde[i] = iKde.value(gridVal) * seg.data.spmInfos.size(); + valsDer2[i] = iDer.value(gridVal); + } + } + + // Dataset KDE + log.debug("Dataset KDE"); + final DefaultXYDataset datasetKde = new DefaultXYDataset(); + final String seriesKdeKey = "KDE"; + datasetKde.addSeries(seriesKdeKey, new double[][]{grid, valsKde}); + + // Dataset histogram + List datasetHistograms = new ArrayList<>(); + final boolean doHistogram = false; + if (doHistogram) { + log.debug("Dataset Histogram"); + //List histDx = Arrays.asList(0.01, 0.005, 0.001); +// List histDx = Arrays.asList(0.005, 0.001, 0.0002); + List histDx = Arrays.asList(0.002, 0.001, 0.0005); + histDx.sort((o1, o2) -> Double.compare(o2,o1)); + + for (Double dx : histDx) { + double histLo = Double.POSITIVE_INFINITY; + double histHi = Double.NEGATIVE_INFINITY; + for (ProcessingSegment seg : segments) { + histLo = Math.min(histLo, seg.data.xLoBracket); + histHi = Math.max(histHi, seg.data.xHiBracket); + } + final double offset = 0; + double ptrLo = histLo + offset; + final String key = String.format("Histogram-dx=%.4f", dx); + final SimpleHistogramDataset hist = new SimpleHistogramDataset(key); + // create bins + while (ptrLo < histHi) { + final double loBound = ptrLo; + final double hiBound = ptrLo + dx; + SimpleHistogramBin bin = new SimpleHistogramBin(loBound, hiBound, true, false); + ptrLo = hiBound; + // fill bin + long pointsInBin = segments.stream().flatMap(seg -> seg.data.spmInfos.stream()) + .filter(si -> si.spm.mDiffCorrected >= loBound && si.spm.mDiffCorrected < hiBound) + .count(); + bin.setItemCount((int)pointsInBin); + log.trace("Assigned {} points to bin [{}, {}]", pointsInBin, loBound, hiBound); + hist.addBin(bin); + } + int totalDataPoints = segments.stream().mapToInt(seg -> seg.data.spmInfos.size()).sum(); + // old bin filling +// segments.stream().flatMap(seg -> seg.data.spmInfos.stream()).map(si -> si.spm.mDiffCorrected) +// .forEach(mDiffCorr -> hist.addObservation(mDiffCorr, false)); + + + log.debug("Create dataset histogram key: {}, total num data points: {}, step: {}, offset: {}", key, totalDataPoints, dx, offset); + datasetHistograms.add(hist); + } + } + + // detect peaks + List detecteds = new ArrayList<>(); + if (!cmd.optsPlot.noPeaks) { + final PeakDetectionConfig conf = new PeakDetectionConfig(cmd.optsPeaks.minPeakPct, + cmd.optsPeaks.minPsmsPerGmm); + log.debug("Running peak detection"); + for (ProcessingSegment segment : segments) { + detecteds.add(LogicPeaks.detectPeaks(segment, conf)); + } + } + + // if output file given, save peaks + final Path outPath = cmd.optsPeaks.out; + if (outPath != null) { + try { + // we asked for user permission to overwrite the file before running kde + final Path outputFilePath = LogicPeaks.getOutputFilePath(outPath); + LogicPeaks.writePeaks(outputFilePath, detecteds, cmd.optsPeaks.digitsMz, cmd.optsPeaks.digitsAb); + } catch (IOException e) { + log.error("Error writing output file: " + outPath.toString(), e); + } + } else { + // use for debugging only + //LogicPeaks.writePeaks(System.err, detecteds, cmd.optsPeaks.digitsMz, cmd.optsPeaks.digitsAb); + } + + // DatasetPeaks + log.debug("Dataset Peaks"); + final YIntervalSeriesCollection datasetPeaks = new YIntervalSeriesCollection(); + if (!detecteds.isEmpty()) { + final String seriesPeakLocsKey = "Peak locations"; + final YIntervalSeries peakSeries = new YIntervalSeries(seriesPeakLocsKey); + detecteds.stream().flatMap(dp -> dp.gmms.stream().map(gmm -> gmm.peakOrigin)).forEachOrdered( + peak -> peakSeries.add(new YIntervalDataItem(peak.location, peak.intensity, 0, peak.intensity), false)); + if (peakSeries.getItemCount() > 0) { + datasetPeaks.addSeries(peakSeries); + } + } + + // Dataset GMMs + final boolean doPlotGmms = false; + final String datasetGmmsKey = "dataset-GMMs"; + DefaultXYDataset datasetGmms = null; + if (doPlotGmms) { + log.debug("Dataset GMMs"); + int maxGmmCompCount = detecteds.stream().mapToInt(d -> d.gmms.size()).max().orElse(0); + for (Detected detected : detecteds) { +// detected.gmms.sort((gmm1, gmm2) -> Double.compare(gmm2.weight, gmm1.weight)); + detected.gmms.sort((gmm1, gmm2) -> Double.compare(gmm1.mu, gmm2.mu)); + } + for (int i = 0; i < maxGmmCompCount; i++) { + final String seriesGmmKey = String.format("GMM-%02d", i + 1); + final double[] gmmSeriesVals = new double[grid.length]; + int pointsAdded = 0; + + for (Detected detected : detecteds) { + if (i > detected.gmms.size() - 1) { + continue; // this segment does not have enough GMM components + } + GmmComponent gmc = detected.gmms.get(i); + final double drawDistance = gmc.sigma * 2.0; + double boundLo = gmc.mu - drawDistance; + double boundHi = gmc.mu + drawDistance; + int gridIdxLo = Arrays.binarySearch(grid, boundLo); + if (gridIdxLo < 0) { + gridIdxLo = ~gridIdxLo; + } + int gridIdxHi = Arrays.binarySearch(grid, boundHi); + if (gridIdxHi < 0) { + gridIdxHi = ~gridIdxHi; + } + for (int j = gridIdxLo; j < gridIdxHi; j++) { + final double pdf = Normal.pdf(grid[j], gmc.mu, gmc.sigma); // TODO: consider using my MathUtils + final double val = pdf * gmc.weight * gmc.psmSupportApprox; + gmmSeriesVals[j] = val; + pointsAdded++; + } + } + + datasetGmms = new DefaultXYDataset(); + if (pointsAdded > 0) { + datasetGmms.addSeries(seriesGmmKey, new double[][]{grid, gmmSeriesVals}); + } + } + } + + PlotFactory pf = new PlotFactory(); + pf.setData(segments); + + final String datasetKdeKey = "dataset-KDE"; + pf.addDataset(datasetKdeKey, datasetKde, null); + + final String datasetPeaksKey = "dataset-Peaks"; + final BasicStroke datasetPeaksStroke = new BasicStroke( + 2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{3.0f, 3.0f}, 0.0f); + YIntervalRenderer datasetPeaksRenderer = new YIntervalRenderer() { + @Override + public Paint getSeriesPaint(int series) { + return COLORS_BETTER[series % COLORS_BETTER.length]; + } + + @Override + public Stroke getSeriesStroke(int series) { + return datasetPeaksStroke; + } + }; + String yAxisPeakQualKey = "peak-quality"; + ValueAxis yAxisPeakQual = new NumberAxis("Peak quality"); + pf.addSecondaryYAxis(yAxisPeakQualKey, yAxisPeakQual); + pf.addDataset(datasetPeaksKey, datasetPeaks, datasetPeaksRenderer, yAxisPeakQualKey); + + if (doHistogram) { + // histogram goes to the secondary axis + log.debug("Adding histogram datasets"); + final String datasetHistKeyBase = "dataset-Histogram"; + final List palette = ColorHelper + .getDistinctColors(datasetHistograms.size(), 0.5f, 0.75f); + final List histColors = new ArrayList<>(); + for (Color c : palette) { + final int histAlpha = 100; + Color cTransparent = new Color(c.getRed(), c.getGreen(), c.getBlue(), histAlpha); + histColors.add(cTransparent); + } + + String yAxisHistKey = "histogram"; + ValueAxis yAxisHist = new NumberAxis("Count"); + pf.addSecondaryYAxis(yAxisHistKey, yAxisHist); + for (int i = 0; i < datasetHistograms.size(); i++) { + SimpleHistogramDataset datasetHistogram = datasetHistograms.get(i); + final int histogramIndex = i; + XYBarRenderer r = new XYBarRenderer() { + @Override + public Paint getSeriesPaint(int series) { + int colorIndex = histogramIndex % histColors.size(); + return histColors.get(colorIndex); + } + }; + StandardXYBarPainter bp = new StandardXYBarPainter(); + r.setBarPainter(bp); + r.setShadowVisible(false); + r.setGradientPaintTransformer(null); + r.setDrawBarOutline(true); + final String key = String.format("%s-%d", datasetHistKeyBase, i); + pf.addDataset(key, datasetHistogram, r, yAxisHistKey); + } + } + + if (datasetGmms != null) { + log.debug("Adding GMM datasets"); + final BasicStroke strokeDashed = new BasicStroke( + 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{3.0f, 3.0f}, 0.0f); + XYLineAndShapeRenderer datasetGmmsRenderer = new XYLineAndShapeRenderer(true, false) { + @Override + public Stroke getItemStroke(int row, int column) { + return strokeDashed; + } + + @Override + public Stroke getSeriesStroke(int series) { + return strokeDashed; + } + + @Override + public Paint getSeriesPaint(int series) { + return COLORS_GMMS[series % COLORS_GMMS.length]; + } + }; + datasetGmmsRenderer.setDefaultStroke(strokeDashed); + datasetGmmsRenderer.setDrawSeriesLineAsPath(true); + pf.addDataset(datasetGmmsKey, datasetGmms, datasetGmmsRenderer, null); + } + + final CloseOption onClose = cmd.optsPlot.exitOnClose + ? CloseOption.EXIT + : CloseOption.DISPOSE; + + // the main call that shows the window with KDE plot and PSM table + pf.display(onClose, DeltaMassInfo.getNameVersion()); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/MassCorrectionMs1.java b/src/main/java/com/dmtavt/deltamass/logic/MassCorrectionMs1.java new file mode 100644 index 0000000..046e382 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/MassCorrectionMs1.java @@ -0,0 +1,471 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.data.PepSearchResult; +import com.dmtavt.deltamass.data.Spm; +import com.dmtavt.deltamass.parsers.ILcmsParser; +import com.dmtavt.deltamass.parsers.IParserFactory; +import com.dmtavt.deltamass.parsers.LcmsParserRegistry; +import com.github.chhh.utils.exceptions.ParsingException; +import com.github.chhh.utils.ser.DataStoreUtils; +import edu.umich.andykong.msutils.mscalibrator.QuantPair; +import edu.umich.andykong.msutils.mscalibrator.SpectralIndex; +import edu.umich.andykong.msutils.mscalibrator.Spectrum; +import edu.umich.andykong.msutils.mscalibrator.SpectrumResult; +import java.io.IOException; +import java.io.Serializable; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.ms.datatypes.LCMSData; +import umich.ms.datatypes.LCMSDataSubset; +import umich.ms.datatypes.index.Index; +import umich.ms.datatypes.index.IndexElement; +import umich.ms.datatypes.scan.IScan; +import umich.ms.datatypes.scancollection.IScanCollection; +import umich.ms.datatypes.scancollection.ScanIndex; +import umich.ms.datatypes.spectrum.ISpectrum; +import umich.ms.fileio.exceptions.FileParsingException; +import umich.ms.util.IntervalST; +import umich.ptm.chem.Table; + +public class MassCorrectionMs1 implements MassCorrector { + private static final Logger log = LoggerFactory.getLogger(MassCorrectionMs1.class); + private static final double MASS_H = 1.0078250321; + private static final double MASS_C_DIFF = 1.0033548378; + + /** This score name is used for FDR calculation (to sort the PSM entries). */ + private final String scoreName; + /** Where LCMS files will be searched. */ + private final List additionalSearchPaths; + private final boolean noCache; + + /** + * + * @param scoreName For FDR estimation. + * @param additionalSearchPaths Where LCMS files will be searched. + */ + public MassCorrectionMs1(String scoreName, List additionalSearchPaths, boolean noCache) { + this.scoreName = scoreName; + this.additionalSearchPaths = additionalSearchPaths != null ? additionalSearchPaths : + Collections.emptyList(); + this.noCache = noCache; + } + + private String substrToLastDot(String text) { + final int idx = text.lastIndexOf('.'); + return idx < 0 ? text : text.substring(0, idx); + } + + /** + * @return NULL if the given scan was empty, i.e. had no data values. + * @throws FileParsingException + */ + private static Spectrum convert(IScan scan, Index idx, String baseName) throws FileParsingException { + IndexElement idxElem = idx.getByNum(scan.getNum()); + int scanNumRaw = idxElem.getRawNumber(); + ISpectrum spectrum = scan.fetchSpectrum(); + if (spectrum == null || spectrum.getMZs().length == 0) { + return null; + } + + Spectrum ns = new Spectrum(spectrum.getMZs().length); + try { + ns.charge = (byte) scan.getPrecursor().getCharge().intValue(); + } catch (Exception e) { + ns.charge = 0; + } + try { + ns.precursorMass = scan.getPrecursor().getMzTarget(); + } catch (Exception e) { + ns.precursorMass = 0; + } + ns.scanNum = scanNumRaw; + ns.msLevel = (byte) scan.getMsLevel().intValue(); + ns.retentionTime = scan.getRt(); + ns.scanName = baseName + "." + scanNumRaw + "." + scanNumRaw + "." + ns.charge; + + for (int i = 0; i < ns.peakMZ.length; i++) { + ns.peakMZ[i] = (float) spectrum.getMZs()[i]; + ns.peakInt[i] = (float) spectrum.getIntensities()[i]; + } + + return ns; + } + + private static SpectralIndex buildSpectralIndex(LCMSData data, String baseName) throws FileParsingException { + final SpectralIndex si = new SpectralIndex(); + IScanCollection scans = data.getScans(); + + IntervalST> ms1ranges = scans.getMapMsLevel2rangeGroups().get(1); + if (ms1ranges != null && ms1ranges.size() != 0 && ms1ranges.size() < 100) { + throw new IllegalStateException("Likely a DIA run. Only standard DDA runs supported."); + } + ScanIndex index = scans.getMapMsLevel2index().get(1); + if (index == null || index.getNum2scan() == null) + throw new IllegalStateException("Index was not built"); + TreeMap num2scan = index.getNum2scan(); + + Index sourceIndex = data.getSource().fetchIndex(); + if (sourceIndex == null) + throw new IllegalStateException("Could not fetch index"); + + for (Entry kv : num2scan.entrySet()) { + IScan scan = kv.getValue(); + Spectrum spec = convert(scan, sourceIndex, baseName); + si.add(spec); + } + + return si; + } + + @Override + public void apply(PepSearchResult pepSearchResult) throws IOException { + if (!pepSearchResult.scoreMapping.containsKey(scoreName)) + throw new UnsupportedOperationException(String.format("Peptide search result '%s' didn't " + + "contain score with name '%s', which is needed for FDR estimation.", + pepSearchResult.sourceFileName, scoreName)); + final int scoreIndex = pepSearchResult.scoreMapping.get(scoreName); + + CalibrationData calibrationData = null; + if (noCache) { + calibrationData = createCalibration(pepSearchResult, scoreIndex); + } else { + Path cached = CacheLocator + .locateForCal(Paths.get(pepSearchResult.sourceFileDir, pepSearchResult.sourceFileName), + pepSearchResult.rawFileName); + if (Files.exists(cached)) { + try { + log.info("Reading cached calibration: {}", cached); + calibrationData = DataStoreUtils.deserialize(cached, CalibrationData.class); + } catch (ParsingException e) { + log.warn("Could not load cached cal file: {}", cached); + } + } + if (calibrationData == null) { + calibrationData = createCalibration(pepSearchResult, scoreIndex); + try { + DataStoreUtils.serialize(cached, calibrationData); + } catch (ParsingException e1) { + log.warn("Could not write calibration cache file: {}", cached); + } + } + } + + applyCalibration(pepSearchResult.spms, calibrationData); + pepSearchResult.massCorrection = MassCorrection.PEP_ID; + } + + private CalibrationData createCalibration(PepSearchResult pepSearchResult, int scoreIndex) + throws IOException { + log.info("Building calibration for: {}/{}", pepSearchResult.sourceFileName, pepSearchResult.rawFileName); + + // search for LCMS file + final Path pepidFilePath = Paths.get(pepSearchResult.sourceFileDir); + final String origLcmsFn = pepSearchResult.rawFileName.toLowerCase(); + final String origLcmsFnNoExt = substrToLastDot(origLcmsFn); + + final ArrayList searchPaths = new ArrayList<>(); + searchPaths.add(pepidFilePath); + searchPaths.addAll(additionalSearchPaths); + + Path lcmsPath = null; + for (Path searchPath : searchPaths) { + Optional match = Files.walk(searchPath) + .filter(path -> { + final String fn = path.getFileName().toString().toLowerCase(); + final String fnNoExt = substrToLastDot(fn); + if (!fnNoExt.equals(origLcmsFnNoExt)) { + return false; + } + return LcmsParserRegistry.find(path) != null; + }).findFirst(); + if (match.isPresent()) { + lcmsPath= match.get(); + break; + } + } + if (lcmsPath == null) { + String msg = String.format("Could not find LCMS file ('%s') for pep id file: %s", + pepSearchResult.rawFileName, pepSearchResult.sourceFileName); + log.error(msg); + throw new IOException(msg); + } + IParserFactory factory = LcmsParserRegistry.find(lcmsPath); + if (factory == null) + throw new IllegalStateException("Factory should not be null, we have checked for that already."); + ILcmsParser lcmsParser = factory.create(lcmsPath); + + // FDR for search results + SpecAssembly sa = new SpecAssembly(scoreIndex); + sa.addPepSearchResult(pepSearchResult); + sa.buildFdrByScore(); + + // read LCMS + log.info("Reading LCMS data: {}", lcmsPath); + LCMSData lcmsData = lcmsParser.parse(LCMSDataSubset.MS1_WITH_SPECTRA); + log.info("Building spectral index: {}", lcmsPath); + final int threads = Math.max(1, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)); + final String baseName = substrToLastDot(pepSearchResult.rawFileName); + SpectralIndex si; + try { + si = buildSpectralIndex(lcmsData, baseName); + si.build(); + } catch (Exception e) { + throw new IOException(e); + } + + // create calibration + return calibrate(sa, si); + } + + private static class SpecAssembly { + Map spmMap; + List spms; + final int scoreIndex; + boolean[] isDecoy; + double[] fdr; + int[] ppprob; + int[] bestProb; + + public SpecAssembly(int scoreIndex) { + this.scoreIndex = scoreIndex; + this.spmMap = new HashMap<>(); + } + + public void addPepSearchResult(PepSearchResult psr) { + for (Spm spm : psr.spms) { + Spm old = spmMap.putIfAbsent(spm.spectrumId, spm); + if (old != null) + log.warn("While adding SearchResult encountered the same spectrumId multiple times."); + } + } + + private static double[] calculateFdr(int[] probs, boolean[] isDecoy) { + int[][] counts = new int[10001][4]; + double[] fdrs = new double[10001]; + for (int i = 0; i < probs.length; i++) { + counts[probs[i]][isDecoy[i] ? 1 : 0]++; + } + counts[10000][2] = counts[10000][0]; + counts[10000][3] = counts[10000][1]; + for (int i = 9999; i >= 0; i--) { + counts[i][2] = counts[i + 1][2] + counts[i][0]; + counts[i][3] = counts[i + 1][3] + counts[i][1]; + } + double bestFDR = 1; + for (int i = 0; i <= 10000; i++) { + double cFDR = ((double) counts[i][3]) / counts[i][2]; + if (cFDR < bestFDR) { + bestFDR = cFDR; + } + fdrs[i] = bestFDR; + } + return fdrs; + } + + void buildFdrByScore() { + spms = new ArrayList<>(spmMap.values()); + spms.sort(Comparator.comparingDouble(o -> o.scores[scoreIndex])); + int nDecoy = 0, nFwd = 0; + + for (Spm spm : spms) { + if (spm.isDecoy) nDecoy++; + else nFwd++; + } + + fdr = new double[spms.size()]; + double bestFdr = 1; + for (int i = spms.size() - 1; i >= 0; i--) { + double curFdr = nDecoy / (double)nFwd; + if (curFdr < bestFdr) { + bestFdr = curFdr; + } + fdr[i] = bestFdr; + if (spms.get(i).isDecoy) nDecoy--; + else nFwd--; + } + } + } + + public CalibrationData calibrate(SpecAssembly sa, SpectralIndex sis) { + //perform MS1 traces + ArrayList ms1traces = new ArrayList<>(); + double[] adj_delta = new double[sa.spms.size()]; + HashMap adj_delta_map = new HashMap<>(); + double max_RT = 0, max_MZ = 0; + + for (int i = 0; i < sa.spms.size(); i++) { + Spm spm = sa.spms.get(i); + double calc_mass = spm.mCalcNeutral; + double calc_mz = (calc_mass + spm.charge * MASS_H) / spm.charge; + double ms1tol = 20; + double rt = spm.rtSec / 60; + QuantPair[] qp = new QuantPair[3]; + +// for (int ccharge = 1; ccharge <= 5; ccharge++) { +// for (int dc = -5; dc <= 5; dc++) { +// QuantPair cqp = sis.quantXIC(calc_mz + (dc * MASS_C_DIFF/ ccharge), rt, ms1tol); +// } +// } + + for (int j = 0; j < 3; j++) { + qp[j] = sis.quantXIC(calc_mz + (j * MASS_C_DIFF / spm.charge), rt, ms1tol); + } + if (qp[0].val > 0) { + int npts = 1; + adj_delta[i] += qp[0].delta; + for (int j = 1; j < 3; j++) { + if (qp[j].val > 0 && Math.abs(qp[j].delta - qp[0].delta) < 5) { + npts++; + adj_delta[i] += qp[j].delta; + } else { + break; + } + } + adj_delta[i] /= npts; + adj_delta_map.put(spm.spectrumId, adj_delta[i]); + } + ms1traces.add(qp); + if (calc_mz > max_MZ) { + max_MZ = calc_mz; + } + if (rt > max_RT) { + max_RT = rt; + } + } + double div_RT = 5; + double div_MZ = 200; + double[][] corr = new double[(int) (Math.round(max_RT / div_RT)) + 2][ + (int) (Math.round(max_MZ / div_MZ)) + 2]; + double[][] cnts = new double[(int) (Math.round(max_RT / div_RT)) + 2][ + (int) (Math.round(max_MZ / div_MZ)) + 2]; + //build calibration profile + for (int i = 0; i < sa.spms.size(); i++) { + Spm spm = sa.spms.get(i); + double calc_mass = spm.mCalcNeutral; + double calc_mz = (calc_mass + spm.charge * MASS_H) / spm.charge; + double rt = spm.rtSec / 60; + QuantPair[] qp = ms1traces.get(i); + + if (sa.fdr[i] <= 0.01) { + if (Math.abs(spm.mDiff) < 0.1 && qp[0].val > 0) { + double diff_ppm = 1000000 * ((spm.mDiff / spm.charge) / calc_mz); + double adj_ppm = diff_ppm + adj_delta[i]; + if (Math.abs(adj_ppm) < 10) { + int rti = (int) (rt / div_RT); + int mzi = (int) (calc_mz / div_MZ); + double dr = (rt - rti * div_RT) / div_RT; + double dm = (calc_mz - mzi * div_MZ) / div_MZ; + double[] dx = {1 - dr, dr}; + double[] dy = {1 - dm, dm}; + for (int x = 0; x < 2; x++) { + for (int y = 0; y < 2; y++) { + corr[rti + x][mzi + y] += dx[x] * dy[y] * adj_ppm; + cnts[rti + x][mzi + y] += dx[x] * dy[y]; + } + } + } + } + } + } + for (int i = 0; i < corr.length; i++) { + for (int j = 0; j < corr[i].length; j++) { + if (cnts[i][j] > 0) { + corr[i][j] /= cnts[i][j]; + } + } + } + + return new CalibrationData(div_RT, div_MZ, corr, adj_delta_map); + } + + private void applyCalibration(List spms, CalibrationData calData) { + final double div_RT = calData.div_RT; + final double div_MZ = calData.div_MZ; + final double[][] corr = calData.corr; + final HashMap adj_delta_map = calData.adj_delta_map; + + int countFound = 0; + int countNotFound = 0; + + //apply profile + double sum = 0; + for (int i = 0; i < spms.size(); i++) { + Spm spm = spms.get(i); + double calc_mass = spm.mCalcNeutral; + double calc_mz = (calc_mass + spm.charge * Table.Proton.monoMass) / spm.charge; + double rt = spm.rtSec / 60; + int rti = (int) (rt / div_RT); + int mzi = (int) (calc_mz / div_MZ); + double corrf = 0; + double dr = (rt - rti * div_RT) / div_RT; + double dm = (calc_mz - mzi * div_MZ) / div_MZ; + double[] dx = {1 - dr, dr}; + double[] dy = {1 - dm, dm}; + for (int x = 0; x < 2; x++) { + for (int y = 0; y < 2; y++) { + corrf += dx[x] * dy[y] * corr[rti + x][mzi + y]; + } + } + + double ppm_delta = -corrf; + Double adj_delta = adj_delta_map.get(spm.spectrumId); + if (adj_delta != null) { + //throw new IllegalStateException("Could not find adj_delta for psm " + spm.spectrumId); + countFound++; + ppm_delta += adj_delta; + } else { + countNotFound++; + //ppm_delta += 0.0; + continue; + } + + //double ppm_massdiff = 1000000.0*((sr.massdiff / sr.charge)/calc_mz); + //System.out.printf("%.4f %.4f %.4f %.4f %.4f %.4f\n",rt,calc_mz, ppm_massdiff, ppm_massdiff + adj_delta[i], ppm_massdiff + ppm_delta, corrf); + double mass_corr = (ppm_delta * calc_mz * spm.charge) / 1e6; + sum += mass_corr; + //out.printf("%s %.6f %.6f\n", sr.specName, sr.massdiff, sr.massdiff + mass_corr); + spm.mDiffCorrected = spm.mDiff + mass_corr; + + } + if (countNotFound > 0) + log.info("Applied mass correction not to all peptide entries: {}/{}, avg mass_cor = {}", countFound, countNotFound + countFound, sum / countFound); + } + + public static class CalibrationData implements Serializable { + private static final long serialVersionUID = -7497899011001186998L; + + public final HashMap adj_delta_map; + public final double div_RT; + public final double div_MZ; + public final double[][] corr; + + /** + * + * @param div_rt + * @param div_mz + * @param corr + * @param adj_delta_map Key = spectrum id, Value = ppm correction to be applied to observed m/z. + * This is the value obtained by tracing MS1 peak. + */ + public CalibrationData(double div_rt, double div_mz, double[][] corr, + HashMap adj_delta_map) { + this.adj_delta_map = adj_delta_map; + div_RT = div_rt; + div_MZ = div_mz; + this.corr = corr; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/MassCorrector.java b/src/main/java/com/dmtavt/deltamass/logic/MassCorrector.java new file mode 100644 index 0000000..99ba048 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/MassCorrector.java @@ -0,0 +1,9 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.data.PepSearchResult; +import java.io.IOException; + +@FunctionalInterface +public interface MassCorrector { + void apply(PepSearchResult pepSearchResult) throws IOException; +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/MassCorrectorNone.java b/src/main/java/com/dmtavt/deltamass/logic/MassCorrectorNone.java new file mode 100644 index 0000000..eecb35b --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/MassCorrectorNone.java @@ -0,0 +1,16 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.data.PepSearchResult; + +/** + * Resets corrected mass diff to its original raw value. + */ +public class MassCorrectorNone implements MassCorrector { + + @Override + public void apply(PepSearchResult pepSearchResult) { + pepSearchResult.spms.parallelStream().forEach(spm -> spm.mDiffCorrected = spm.mDiff); + pepSearchResult.massCorrection = MassCorrection.NONE; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/MassCorrectorZeroPeak.java b/src/main/java/com/dmtavt/deltamass/logic/MassCorrectorZeroPeak.java new file mode 100644 index 0000000..be04b05 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/MassCorrectorZeroPeak.java @@ -0,0 +1,127 @@ +package com.dmtavt.deltamass.logic; + +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.data.PepSearchResult; +import com.dmtavt.deltamass.data.PepSearchResult.SortOrder; +import com.dmtavt.deltamass.data.Spm; +import com.dmtavt.deltamass.kde.KdeKludge; +import com.github.chhh.utils.search.BinarySearch; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import jsat.distributions.empirical.KernelDensityEstimator; +import jsat.distributions.empirical.kernelfunc.EpanechnikovKF; +import jsat.distributions.empirical.kernelfunc.KernelFunction; +import jsat.linear.DenseVector; +import jsat.linear.Vec; +import jsat.math.Function; +import jsat.math.FunctionVec; +import jsat.math.optimization.BacktrackingArmijoLineSearch; +import jsat.math.optimization.LBFGS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MassCorrectorZeroPeak implements MassCorrector { + private static final Logger log = LoggerFactory.getLogger(MassCorrectorZeroPeak.class); + + private List sublistInMassDiffRange(List spms, final double mDiffLo, final double mDiffHi) { + int loSearch = BinarySearch.search(spms, o -> Double.compare(o.mDiff, mDiffLo)); + int hiSearch = BinarySearch.search(spms, o -> Double.compare(o.mDiff, mDiffHi)); + if (loSearch < 0) loSearch = ~loSearch; + if (hiSearch < 0) hiSearch = ~hiSearch; + return spms.subList(loSearch, hiSearch); + } + + + @Override + public void apply(PepSearchResult pepSearchResult) { + final double WINDOW = 0.5; + final double mLo = -1 * WINDOW / 2.0; + final double mHi = WINDOW / 2.0; + // the step to find the initial location near which we'll be looking for a solution + final double stepInitSearch = 0.01; + // final KernelFunction kernelFunction = UniformKF.getInstance(); + // final KernelFunction kernelFunction = GaussKF.getInstance(); + final KernelFunction kernelFunction = EpanechnikovKF.getInstance(); + final int numBins = (int)Math.ceil((WINDOW * 2) / stepInitSearch); + if (numBins < 3) + throw new IllegalStateException("Calculated number of bins for zero-peak mass correction was " + + "smaller than 3, this should not happen"); + + if (pepSearchResult.getSortOrder() != SortOrder.DELTA_MASS_ASCENDING) + pepSearchResult.sort(SortOrder.DELTA_MASS_ASCENDING); + + final ArrayList spms = pepSearchResult.spms; + List selectedSpms = sublistInMassDiffRange(spms, mLo, mHi); + final int limit = 10; + if (selectedSpms.size() < limit) { + log.warn(String.format("Less than %d hits found for zero-peak mass correction in mass range " + + "[%.2f; %.2f], not performing correction", limit, mLo, mHi)); + pepSearchResult.massCorrection = MassCorrection.ZERO_PEAK; + return; + } + + + // find the possible location for maximum + double[] massDiffsArr = new double[selectedSpms.size()]; + for (int i = 0, size = selectedSpms.size(); i < size; i++) + massDiffsArr[i] = selectedSpms.get(i).mDiff; + DenseVector massDiffsVec = new DenseVector(massDiffsArr); + final double bandwidthEstimate = KernelDensityEstimator.BandwithGuassEstimate(massDiffsVec); + log.debug("Estimated bandwidth for zero-peak correction for '{}', " + + "obtained a value of {} using gaussian formula", pepSearchResult.rawFileName, bandwidthEstimate); + final double bandwidthUsed = bandwidthEstimate * 2.0; // relax a little + final KdeKludge kde = new KdeKludge(massDiffsVec, kernelFunction, bandwidthUsed); + + + final double eps = 1e-8d; + final double maxMassToleranceWanted = 1e-6d; + final Function func = new Function() { + @Override public double f(double... x) { + return -1 * kde.pdf(x[0]); + } + @Override public double f(Vec x) { + return -1 * kde.pdf(x.get(0)); + } + }; + final FunctionVec funcp = new FunctionVec() { + @Override public Vec f(double... x) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override public Vec f(Vec x) { + DenseVector result = new DenseVector(x.length()); + f(x, result); + return result; + } + + @Override public Vec f(Vec x, Vec s) { + return f(x, s, null); + } + + @Override public Vec f(Vec x, Vec s, ExecutorService ex) { + Vec xLo = new DenseVector(x); + final double delta = maxMassToleranceWanted / 2d; + xLo = xLo.add(-1d * delta); + Vec xHi = new DenseVector(x); + xHi = xHi.add(delta); + double v = (func.f(xHi) - func.f(xLo)) / (delta * 2d); + return new DenseVector(Collections.singletonList(v)); + } + }; + + BacktrackingArmijoLineSearch lineSearch = new BacktrackingArmijoLineSearch(); + LBFGS lbfgs = new LBFGS(5, 100, lineSearch); + Vec lbfgsSolution = new DenseVector(1); + lbfgs.optimize(1e-12, lbfgsSolution, new DenseVector(Arrays.asList(0.0)), func, funcp, null); + double mCorrection = lbfgsSolution.get(0); + + log.debug(String.format("ZeroPeak correction: computed for '%s', zero-peak shift of '%.5f' " + + "using LBFGS", pepSearchResult.rawFileName, mCorrection)); + spms.parallelStream().forEach(spm -> spm.mDiffCorrected = spm.mDiff - mCorrection); + + pepSearchResult.massCorrection = MassCorrection.ZERO_PEAK; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/UserOptsInputFiles.java b/src/main/java/com/dmtavt/deltamass/logic/UserOptsInputFiles.java new file mode 100644 index 0000000..5ff6317 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/UserOptsInputFiles.java @@ -0,0 +1,88 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.converters.CommaParameterSplitter; +import com.dmtavt.deltamass.args.DecoyTreatment; +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.args.converters.ExistingReadablePathValidator; +import com.dmtavt.deltamass.args.converters.PathConverter; +import com.dmtavt.deltamass.args.converters.RegexConverter; +import com.dmtavt.deltamass.args.converters.RegexValidator; +import com.dmtavt.deltamass.args.converters.StringToPathValidator; +import com.dmtavt.deltamass.predicates.SpmPredicateFactory; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +public class UserOptsInputFiles { + + private static final int ORDER = 100; + private static final int EXTRA = ORDER * 1000; + + @Parameter(names = {"-i", "--in"}, required = true, + description = "Relative or absolute paths to directories or separate files. " + + "Separate entries with commas.", + validateWith = StringToPathValidator.class, converter = PathConverter.class, + validateValueWith = ExistingReadablePathValidator.class, splitter = CommaParameterSplitter.class, + order = ORDER + 10) + public List inputFiles; + + @Parameter(names = {"--no-recurse"}, description = "Do not recurse into sub-directories.", + order = ORDER + 11) + public boolean noRecurse = false; + + @Parameter(names = {"--file-regex"}, + description = "Regular expression that input files will be matched against. If not set, " + + "will try to accept all supported files.", + validateWith = RegexValidator.class, converter = RegexConverter.class, + order = ORDER + 20) + public Pattern fileRegex = null; //Pattern.compile(".*\\.pep\\.?xml$"); + + @Parameter(names = {"--decoy-regex"}, + description = "Regex to identify decoys. Matched against protein ID string.", + validateWith = RegexValidator.class, converter = RegexConverter.class, order = ORDER + 30) + public Pattern decoyRegex = Pattern.compile("^rev_.*"); + + @Parameter(names = {"--decoys"}, description = "What to do with decoys.", order = ORDER + 31) + public DecoyTreatment decoyTreatment = DecoyTreatment.FORWARDS_ONLY; + + @Parameter(names = {"-f", "--filter"}, description = + "Predicates for filtering incoming PSMs. E.g. " + + "\"expect<=0.001\" or \"peptideprophet>0.95\" for pepxml or \"Mascot:score>1.8\" for mzid.", order = + ORDER + 40) + public List predicates; + + @Parameter(names = {"--mLo"}, description = "Low mass cutoff.", order = ORDER + 58) + public Double mLo = null; + @Parameter(names = {"--mHi"}, description = "High mass cutoff.", order = ORDER + 59) + public Double mHi = null; + + @Parameter(names = {"-x", "--exclude"}, arity = 2, description = + "Exclude ranges of mass deltas from analysis. " + + "To specify multiple ranges: \"-x -0.5 0.5 -x 2.5 3.5\"", order = ORDER + 60) + public List excludeMzRanges = Arrays.asList(-0.5, 0.5); + + @Parameter(names = {"-c", "--calibration"}, description = + "Correct observed peptide masses. For PEP_ID the " + + "original LCMS files are required.", order = ORDER + 70) + public MassCorrection massCorrection = MassCorrection.ZERO_PEAK; + + @Parameter(names = {"-s", "--search-path"}, description = "Additional search paths for raw LCMS " + + "files. Used only with '--calibration PEP_ID'. Separate entries with commas.", + validateWith = StringToPathValidator.class, converter = PathConverter.class, + validateValueWith = ExistingReadablePathValidator.class, splitter = CommaParameterSplitter.class, + order = ORDER + 91) + public List additionalSearchPaths = Collections.emptyList(); + + @Parameter(names = {"--score"}, description = "When '--calibration PEP_ID' is used, this score " + + "is used for FDR estimation, to select good calibration peptides.") + public String scoreNameForPsmSorting = "expect"; + + @Parameter(names = {"--nc", "--no-cache"}, description = + "Do not cache the parsed content of input files. The default setting greatly speeds up " + + "consecutive analysis of the same input files.", order = + EXTRA + 80) + public boolean noCache = false; +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/UserOptsKde.java b/src/main/java/com/dmtavt/deltamass/logic/UserOptsKde.java new file mode 100644 index 0000000..502e696 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/UserOptsKde.java @@ -0,0 +1,28 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.dmtavt.deltamass.args.DecoyTreatment; +import com.dmtavt.deltamass.args.Kernel; + +public class UserOptsKde { + + private static final int ORDER = 300; + private static final int EXTRA = ORDER * 1000; + + @Parameter(names = {"-b", "--bandwidth"}, description = + "KDE bandwidth. If less or equal to zero - automatic " + + "determination at each nominal mass.", order = ORDER + 10) + public double bandwidth = 0.0; + + @Parameter(names = {"-k", "--kernel"}, description = "Kernel type. GAUSS should be " + + "good enough for most cases. Use EPANECHNIKOV if you have huge datasets and KDE " + + "takes too long.", hidden = true, order = EXTRA + 20) + public Kernel.Type kernel = Kernel.Type.GAUSS; + + @Parameter(names = {"--dx", "--step"}, description = "Granularity of X axis.") + public double step = 0.01; + + @Parameter(names = {"--min-data"}, description = "Min number of data points (PSMs) to " + + "consider a nominal mass region for KDE.", order = ORDER + 40) + public int minData = 10; +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/UserOptsPeaks.java b/src/main/java/com/dmtavt/deltamass/logic/UserOptsPeaks.java new file mode 100644 index 0000000..2bffbbb --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/UserOptsPeaks.java @@ -0,0 +1,58 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; +import com.dmtavt.deltamass.args.converters.DecimalFormatConverter; +import com.dmtavt.deltamass.args.converters.DecimalFormatValidator; +import com.dmtavt.deltamass.args.converters.DecimalFormatWithTostring; +import com.dmtavt.deltamass.args.converters.PathConverter; +import com.dmtavt.deltamass.args.converters.StringToPathValidator; +import java.nio.file.Path; + +public class UserOptsPeaks { + + static final int ORDER = 400; + private static final int EXTRA = ORDER * 1000; + + @Parameter(names = {"-o", "--out"}, required = false, + description = "If not present prints to STDERR. " + + "Path to write the output to. If given PATH NOT EXISTS or IS AN EXISINTG " + + "FILE: writes to that specific file. If given PATH IS AN EXISTING DIRECTORY: creates " + + "a new file with default name in it.", + validateWith = StringToPathValidator.class, converter = PathConverter.class, + order = ORDER) + public Path out; + + @Parameter(names = {"-w", "--force"}, description = "Overwrite existing output file?") + public boolean overwrite = false; + + @Parameter(names = {"--min-psms"}, description = "Minimum number of PSMs supporting a peak in " + + "GMM. Otherwise a peak is not reported.", + order = ORDER + 10) + public int minPsmsPerGmm = 10; + @Parameter(names = {"--min-pct"}, description = "For each nominal delta mass region only report " + + "peaks that are at least this % of the top peak.", + order = ORDER + 20) + public double minPeakPct = 0.025; + + @Parameter(names = {"--digits-mz"}, description = "Number of decimal digits for mass values " + + "in output.", hidden = true, + + order = EXTRA + 10) + public int digitsMz = 5; + @Parameter(names = {"--digits-ab"}, description = "Number of decimal digits for intensity values " + + "in output.", hidden = true, + order = EXTRA + 20) + public int digitsAb = 1; + +// @Parameter(names = {"--out-fmt-mz"}, description = "Format to be used in the output for m/z " +// + "values. See https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html " +// + "for details.", +// validateWith = DecimalFormatValidator.class, converter = DecimalFormatConverter.class, +// order = EXTRA + 10) +// public DecimalFormatWithTostring outFormatMz = new DecimalFormatWithTostring("0.#####"); +// @Parameter(names = {"--out-fmt-ab"}, description = "Format for the intensity/abundance in the " +// + "output file. See also '--out-fmt-mz'.", +// validateWith = DecimalFormatValidator.class, converter = DecimalFormatConverter.class, +// order = EXTRA + 20) +// public DecimalFormatWithTostring outFormatAb = new DecimalFormatWithTostring("0.#E0"); +} diff --git a/src/main/java/com/dmtavt/deltamass/logic/UserOptsPlot.java b/src/main/java/com/dmtavt/deltamass/logic/UserOptsPlot.java new file mode 100644 index 0000000..0a61ea0 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/logic/UserOptsPlot.java @@ -0,0 +1,26 @@ +package com.dmtavt.deltamass.logic; + +import com.beust.jcommander.Parameter; + +public class UserOptsPlot { + + private static final int ORDER = 500; + private static final int EXTRA = ORDER * 1000; + + @Parameter(names = "--mStep", description = "Mass step used for plotting the KDE.", + order = ORDER + 10) + public double step = 0.01; + + @Parameter(names = {"--no-peaks"}, description = "No peak detection.", + order = ORDER + 20) + public boolean noPeaks = false; + + @Parameter(names = {"--gmm"}, description = "Plot the final Gaussian Mixture Model. Can be slow.", + order = ORDER + 30) + public boolean isGmm = false; + + @Parameter(names = {"--exit-on-close"}, hidden = true, + description = "Stop the program when the plot window is close? Only useful for NOT closing " + + "the GUI window.") + public boolean exitOnClose = true; +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgCleanup.java b/src/main/java/com/dmtavt/deltamass/messages/MsgCleanup.java new file mode 100644 index 0000000..4966b28 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgCleanup.java @@ -0,0 +1,14 @@ +package com.dmtavt.deltamass.messages; + +import java.nio.file.Path; +import java.util.List; + +public class MsgCleanup { + public final Object sender; + public final List paths; + + public MsgCleanup(Object sender, List paths) { + this.sender = sender; + this.paths = paths; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgFlushGuiCache.java b/src/main/java/com/dmtavt/deltamass/messages/MsgFlushGuiCache.java new file mode 100644 index 0000000..43c9bd3 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgFlushGuiCache.java @@ -0,0 +1,9 @@ +package com.dmtavt.deltamass.messages; + +public class MsgFlushGuiCache { + final Object sender; + + public MsgFlushGuiCache(Object sender) { + this.sender = sender; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgModsInRange.java b/src/main/java/com/dmtavt/deltamass/messages/MsgModsInRange.java new file mode 100644 index 0000000..b344d9d --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgModsInRange.java @@ -0,0 +1,27 @@ +package com.dmtavt.deltamass.messages; + +import java.util.List; +import umich.ptm.mod.Mod; + +public class MsgModsInRange { + + public final double dmLo; + public final double dmHi; + public final List mods; + + public MsgModsInRange(double dmLo, double dmHi, List mods) { + this.dmLo = dmLo; + this.dmHi = dmHi; + this.mods = mods; + } + + @Override + public String toString() { + String size = mods == null ? "0" : Integer.toString(mods.size()); + return "MsgModsInRange{" + + "dmLo=" + dmLo + + ", dmHi=" + dmHi + + ", modsCount=" + size + + '}'; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgPlotClicked.java b/src/main/java/com/dmtavt/deltamass/messages/MsgPlotClicked.java new file mode 100644 index 0000000..1f6fa3e --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgPlotClicked.java @@ -0,0 +1,22 @@ +package com.dmtavt.deltamass.messages; + +import java.text.DecimalFormat; + +public class MsgPlotClicked { + public final double x; + public final double y; + final private static DecimalFormat df = new DecimalFormat("0.00"); + + public MsgPlotClicked(double x, double y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "MsgPlotClicked{" + + "x=" + df.format(x) + + ", y=" + df.format(y) + + '}'; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgPsmsRequest.java b/src/main/java/com/dmtavt/deltamass/messages/MsgPsmsRequest.java new file mode 100644 index 0000000..6b3b4ec --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgPsmsRequest.java @@ -0,0 +1,13 @@ +package com.dmtavt.deltamass.messages; + +public class MsgPsmsRequest { + public final double dmLo; + public final double dmHi; + public final int softLimit; + + public MsgPsmsRequest(double dmLo, double dmHi, int softLimit) { + this.dmLo = dmLo; + this.dmHi = dmHi; + this.softLimit = softLimit; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgPsmsResponse.java b/src/main/java/com/dmtavt/deltamass/messages/MsgPsmsResponse.java new file mode 100644 index 0000000..ceb1f19 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgPsmsResponse.java @@ -0,0 +1,12 @@ +package com.dmtavt.deltamass.messages; + +import com.dmtavt.deltamass.logic.LogicKde.SpmInfo; +import java.util.List; + +public class MsgPsmsResponse { + public final List spmInfos; + + public MsgPsmsResponse(List spmInfos) { + this.spmInfos = spmInfos; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgRequestHistogram.java b/src/main/java/com/dmtavt/deltamass/messages/MsgRequestHistogram.java new file mode 100644 index 0000000..ba0e97e --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgRequestHistogram.java @@ -0,0 +1,12 @@ +package com.dmtavt.deltamass.messages; + +public class MsgRequestHistogram { + + public final double mLo; + public final double mHi; + + public MsgRequestHistogram(double mLo, double mHi) { + this.mLo = mLo; + this.mHi = mHi; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgRunPlot.java b/src/main/java/com/dmtavt/deltamass/messages/MsgRunPlot.java new file mode 100644 index 0000000..a540adc --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgRunPlot.java @@ -0,0 +1,13 @@ +package com.dmtavt.deltamass.messages; + +import com.dmtavt.deltamass.logic.CommandPlot; + +public class MsgRunPlot { + public final Object sender; + public final CommandPlot cmd; + + public MsgRunPlot(Object sender, CommandPlot cmd) { + this.sender = sender; + this.cmd = cmd; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgSelectionClear.java b/src/main/java/com/dmtavt/deltamass/messages/MsgSelectionClear.java new file mode 100644 index 0000000..fb729eb --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgSelectionClear.java @@ -0,0 +1,5 @@ +package com.dmtavt.deltamass.messages; + +public class MsgSelectionClear { + +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgSelectionMade.java b/src/main/java/com/dmtavt/deltamass/messages/MsgSelectionMade.java new file mode 100644 index 0000000..c3e0338 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgSelectionMade.java @@ -0,0 +1,12 @@ +package com.dmtavt.deltamass.messages; + +public class MsgSelectionMade { + + private final double lo; + private final double hi; + + public MsgSelectionMade(double lo, double hi) { + this.lo = lo; + this.hi = hi; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgStop.java b/src/main/java/com/dmtavt/deltamass/messages/MsgStop.java new file mode 100644 index 0000000..2f796b4 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgStop.java @@ -0,0 +1,9 @@ +package com.dmtavt.deltamass.messages; + +public class MsgStop { + final Object sender; + + public MsgStop(Object sender) { + this.sender = sender; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgSwitchView.java b/src/main/java/com/dmtavt/deltamass/messages/MsgSwitchView.java new file mode 100644 index 0000000..257215b --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgSwitchView.java @@ -0,0 +1,12 @@ +package com.dmtavt.deltamass.messages; + +import com.dmtavt.deltamass.ui.DeltamassDisplay; +import com.dmtavt.deltamass.ui.DeltamassDisplay.VIEW; + +public class MsgSwitchView { + public final DeltamassDisplay.VIEW view; + + public MsgSwitchView(VIEW view) { + this.view = view; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgTest.java b/src/main/java/com/dmtavt/deltamass/messages/MsgTest.java new file mode 100644 index 0000000..fc19d61 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgTest.java @@ -0,0 +1,9 @@ +package com.dmtavt.deltamass.messages; + +public class MsgTest { + public final String message; + + public MsgTest(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/messages/MsgVersionUpdateInfo.java b/src/main/java/com/dmtavt/deltamass/messages/MsgVersionUpdateInfo.java new file mode 100644 index 0000000..02e80d3 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/messages/MsgVersionUpdateInfo.java @@ -0,0 +1,48 @@ +package com.dmtavt.deltamass.messages; + +public class MsgVersionUpdateInfo { + public final String programName; + public final String currentVersion; + public final String newVersion; + public final String downloadUrl; + + public MsgVersionUpdateInfo(String programName, String currentVersion, String newVersion, + String downloadUrl) { + this.programName = programName; + this.currentVersion = currentVersion; + this.newVersion = newVersion; + this.downloadUrl = downloadUrl; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MsgVersionUpdateInfo that = (MsgVersionUpdateInfo) o; + + if (!programName.equals(that.programName)) { + return false; + } + if (!currentVersion.equals(that.currentVersion)) { + return false; + } + if (!newVersion.equals(that.newVersion)) { + return false; + } + return downloadUrl != null ? downloadUrl.equals(that.downloadUrl) : that.downloadUrl == null; + } + + @Override + public int hashCode() { + int result = programName.hashCode(); + result = 31 * result + currentVersion.hashCode(); + result = 31 * result + newVersion.hashCode(); + result = 31 * result + (downloadUrl != null ? downloadUrl.hashCode() : 0); + return result; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/ILcmsParser.java b/src/main/java/com/dmtavt/deltamass/parsers/ILcmsParser.java new file mode 100644 index 0000000..e720aac --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/ILcmsParser.java @@ -0,0 +1,10 @@ +package com.dmtavt.deltamass.parsers; + +import java.io.IOException; +import umich.ms.datatypes.LCMSData; +import umich.ms.datatypes.LCMSDataSubset; + +@FunctionalInterface +public interface ILcmsParser { + LCMSData parse(LCMSDataSubset subset) throws IOException; +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/IParserFactory.java b/src/main/java/com/dmtavt/deltamass/parsers/IParserFactory.java new file mode 100644 index 0000000..6698bdd --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/IParserFactory.java @@ -0,0 +1,8 @@ +package com.dmtavt.deltamass.parsers; + +import java.nio.file.Path; + +public interface IParserFactory { + boolean supports(String fileNameLowerCase, Path path); + T create(Path path); +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/IPepidParser.java b/src/main/java/com/dmtavt/deltamass/parsers/IPepidParser.java new file mode 100644 index 0000000..4389993 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/IPepidParser.java @@ -0,0 +1,10 @@ +package com.dmtavt.deltamass.parsers; + +import com.dmtavt.deltamass.data.PepSearchFile; +import java.io.IOException; +import java.util.regex.Pattern; + +@FunctionalInterface +public interface IPepidParser { + PepSearchFile parse(final Pattern decoyRegex) throws IOException; +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/LcmsParserRegistry.java b/src/main/java/com/dmtavt/deltamass/parsers/LcmsParserRegistry.java new file mode 100644 index 0000000..e57bb3c --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/LcmsParserRegistry.java @@ -0,0 +1,29 @@ +package com.dmtavt.deltamass.parsers; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class LcmsParserRegistry { + private LcmsParserRegistry() {} + + public static final List> factories = Collections + .unmodifiableList(Arrays.asList( + new MzmlParser.Factory(), + new MzxmlParser.Factory() + )); + + /** + * Find a factory in the registry that supports a file. + * @param path Path to the file. + * @return Null if no supporting factories found. + */ + public static IParserFactory find(Path path) { + String fnLow = path.getFileName().toString().toLowerCase(); + for (IParserFactory factory : factories) { + if (factory.supports(fnLow, path)) return factory; + } + return null; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/MzmlParser.java b/src/main/java/com/dmtavt/deltamass/parsers/MzmlParser.java new file mode 100644 index 0000000..31cc707 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/MzmlParser.java @@ -0,0 +1,44 @@ +package com.dmtavt.deltamass.parsers; + +import java.io.IOException; +import java.nio.file.Path; +import umich.ms.datatypes.LCMSData; +import umich.ms.datatypes.LCMSDataSubset; +import umich.ms.fileio.exceptions.FileParsingException; +import umich.ms.fileio.filetypes.mzml.MZMLFile; + +public class MzmlParser implements ILcmsParser { + MZMLFile lcms; + + + public MzmlParser(Path path) { + lcms = new MZMLFile(path.toAbsolutePath().normalize().toString()); + } + + @Override + public LCMSData parse(LCMSDataSubset subset) throws IOException { + try { + LCMSData data = new LCMSData(lcms); + data.load(subset); + return data; + } catch (FileParsingException e) { + throw new IOException(e); + } + } + + public static class Factory implements IParserFactory { + + @Override + public boolean supports(String fileNameLowerCase, Path path) { + return fileNameLowerCase.endsWith(".mzml"); + } + + @Override + public MzmlParser create(Path path) { + MzmlParser parser = new MzmlParser(path); + parser.lcms.setExcludeEmptyScans(true); + parser.lcms.setNumThreadsForParsing(Math.min(4, Runtime.getRuntime().availableProcessors())); + return parser; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/MzxmlParser.java b/src/main/java/com/dmtavt/deltamass/parsers/MzxmlParser.java new file mode 100644 index 0000000..d7668c7 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/MzxmlParser.java @@ -0,0 +1,44 @@ +package com.dmtavt.deltamass.parsers; + +import java.io.IOException; +import java.nio.file.Path; +import umich.ms.datatypes.LCMSData; +import umich.ms.datatypes.LCMSDataSubset; +import umich.ms.fileio.exceptions.FileParsingException; +import umich.ms.fileio.filetypes.mzxml.MZXMLFile; + +public class MzxmlParser implements ILcmsParser { + MZXMLFile lcms; + + + public MzxmlParser(Path path) { + lcms = new MZXMLFile(path.toAbsolutePath().normalize().toString()); + } + + @Override + public LCMSData parse(LCMSDataSubset subset) throws IOException { + try { + LCMSData data = new LCMSData(lcms); + data.load(subset); + return data; + } catch (FileParsingException e) { + throw new IOException(e); + } + } + + public static class Factory implements IParserFactory { + + @Override + public boolean supports(String fileNameLowerCase, Path path) { + return fileNameLowerCase.endsWith(".mzxml"); + } + + @Override + public MzxmlParser create(Path path) { + MzxmlParser parser = new MzxmlParser(path); + parser.lcms.setExcludeEmptyScans(true); + parser.lcms.setNumThreadsForParsing(Math.min(6, Runtime.getRuntime().availableProcessors())); + return parser; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/ParsingException.java b/src/main/java/com/dmtavt/deltamass/parsers/ParsingException.java new file mode 100644 index 0000000..41ce675 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/ParsingException.java @@ -0,0 +1,24 @@ +package com.dmtavt.deltamass.parsers; + +public class ParsingException extends Exception { + + public ParsingException() { + } + + public ParsingException(String message) { + super(message); + } + + public ParsingException(String message, Throwable cause) { + super(message, cause); + } + + public ParsingException(Throwable cause) { + super(cause); + } + + public ParsingException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/PepidParserRegistry.java b/src/main/java/com/dmtavt/deltamass/parsers/PepidParserRegistry.java new file mode 100644 index 0000000..ac0968a --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/PepidParserRegistry.java @@ -0,0 +1,30 @@ +package com.dmtavt.deltamass.parsers; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class PepidParserRegistry { + + public static final List> factories = Collections + .unmodifiableList(Arrays.asList( + new PepxmlParser.Factory() + )); + + /** + * Find a factory in the registry that supports a file. + * @param path Path to the file. + * @return Null if no supporting factories found. + */ + public static IParserFactory find(Path path) { + String fnLow = path.getFileName().toString().toLowerCase(); + for (IParserFactory factory : factories) { + if (factory.supports(fnLow, path)) return factory; + } + return null; + } + + private PepidParserRegistry() { + } +} diff --git a/src/main/java/com/dmtavt/deltamass/parsers/PepxmlParser.java b/src/main/java/com/dmtavt/deltamass/parsers/PepxmlParser.java new file mode 100644 index 0000000..b1b1bed --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/parsers/PepxmlParser.java @@ -0,0 +1,286 @@ +package com.dmtavt.deltamass.parsers; + +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.data.PepSearchFile; +import com.dmtavt.deltamass.data.PepSearchResult; +import com.dmtavt.deltamass.data.Spm; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.ms.fileio.exceptions.FileParsingException; +import umich.ms.fileio.filetypes.pepxml.PepXmlParser; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.AltProteinDataType; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.AnalysisResult; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.InterprophetResult; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.ModAminoacidMass; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.ModificationInfo; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.MsmsRunSummary; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.NameValueType; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.PeptideprophetResult; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SearchHit; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SearchResult; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SearchSummary; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SpectrumQuery; +import umich.ptm.chem.Table; + +public class PepxmlParser implements IPepidParser { + + private static final Logger log = LoggerFactory.getLogger(PepxmlParser.class); + private final Path path; + private boolean hasNoSearchResults = false; + private boolean hasMultipleSearchResults = false; + private boolean hasNoSearchHits = false; + private boolean hasMultipleSearchHits = false; + private static final String PEPTIDE_PROPHET = "peptideprophet"; + private static final String I_PROPHET = "interprophet"; + private final double PROTON_MASS = Table.Proton.monoMass; + + public PepxmlParser(Path path) { + this.path = path; + } + + @Override + public PepSearchFile parse(final Pattern decoyRegex) throws IOException { + List results = new ArrayList<>(); + + // decoy detector + Predicate decoyDetector = searchHit -> decoyRegex.matcher(searchHit.getProtein()).find(); + + try (BufferedInputStream bis = new BufferedInputStream( + Files.newInputStream(path, StandardOpenOption.READ))) { + Iterator it = PepXmlParser.parse(bis); + while (it.hasNext()) { + MsmsRunSummary mmrs = it.next(); + if (mmrs.getSearchSummary().size() != 1) { + throw new IOException( + "Count of SearchSummary elements in Pepxml -> MsMsRunSummary not equal " + + "to one. Not supported."); + } + + SearchSummary ss = mmrs.getSearchSummary().get(0); + Path originalRawFile; + try { + originalRawFile = Paths.get(mmrs.getBaseName() + mmrs.getRawData()); + } catch (Exception e) { + throw new IOException("Could not parse MsMsRunSummary -> baseName + rawData."); + } + + final String rawFileName = originalRawFile.getFileName().toString(); + final String sourceFileName = path.getFileName().toString(); + final String sourceFileDir = path.getParent().toString(); + final ArrayList spms = new ArrayList<>(mmrs.getSpectrumQuery().size()); + final ArrayList proteinAccessions = new ArrayList<>(); + + final HashMap scoreMapping = mapScoreNames(mmrs.getSpectrumQuery()); + // less than zero means peptide prophet data is not available + int pepProphIdx = scoreMapping.getOrDefault(PEPTIDE_PROPHET, -1); + // less than zero means iProphet data is not available + int iProphIdx = scoreMapping.getOrDefault(I_PROPHET, -1); + + + final HashMap proteinMapping = new HashMap<>(); // protein Accession to Id + + PepSearchResult pepSearchResult = new PepSearchResult( + rawFileName, sourceFileName, sourceFileDir, + spms, proteinAccessions, scoreMapping, MassCorrection.NONE); + + for (SpectrumQuery sq : mmrs.getSpectrumQuery()) { + final String spectrumId = sq.getSpectrum(); + final double precMassZ0 = sq.getPrecursorNeutralMass(); + final int charge = sq.getAssumedCharge(); + + if (sq.getSearchResult().size() == 0 && !hasNoSearchResults) { + hasNoSearchResults = true; + log.warn("No SearchResult for some SearchQueries in: {}", path.toString()); + continue; + } + if (sq.getSearchResult().size() != 1 && !hasMultipleSearchResults) { + hasMultipleSearchResults = true; + log.warn("Multiple SearchResults per SearchQuery encountered. Only the first taken. " + + "File: {}", path.toString()); + } + SearchResult sr = sq.getSearchResult().get(0); + if (sr.getSearchHit().size() == 0 && !hasNoSearchHits) { + hasNoSearchHits = true; + log.warn("No SearchHits in a SearchResult of a SpectrumQuery. File: {}", path.toString()); + } + if (sr.getSearchHit().size() != 1 && !hasMultipleSearchHits) { + hasMultipleSearchHits = true; + log.warn("Multiple SearchHits per SearchResult encountered. Only the first taken. " + + "File: {}", path.toString()); + } + SearchHit sh = sr.getSearchHit().get(0); + + + // construct an SPM + Spm spm = new Spm(scoreMapping.size()); + + final String protAccession = sh.getProtein(); + final int protIndex = proteinMapping.computeIfAbsent(protAccession, accession -> { + proteinAccessions.add(accession); + return proteinAccessions.size() - 1; + }); + spm.protId = protIndex; + + List altProts = sh.getAlternativeProtein(); + if (altProts == null || altProts.isEmpty()) { + spm.protIdAlt = new int[0]; + } else { + spm.protIdAlt = new int[altProts.size()]; + for (int i = 0; i < altProts.size(); i++) { + final String altProtAccession = altProts.get(i).getProtein(); + final int altProtIndex = proteinMapping.computeIfAbsent(altProtAccession, accession -> { + proteinAccessions.add(altProtAccession); + return proteinAccessions.size() - 1; + }); + spm.protIdAlt[i] = altProtIndex; + } + } + + spm.mDiff = sh.getMassdiff(); + spm.mDiffCorrected = sh.getMassdiff(); + spm.mCalcNeutral = sh.getCalcNeutralPepMass(); + spm.mObsNeutral = precMassZ0; + spm.charge = charge; + if (charge != 0) { + // TODO: is this value really not present in pepxml and need to be computed? + spm.mzObs = (precMassZ0 + charge * PROTON_MASS) / charge; + int a = 1; + } + spm.spectrumId = spectrumId; + spm.seq = sh.getPeptide(); + spm.rtSec = sq.getRetentionTimeSec() == null ? Double.NaN : sq.getRetentionTimeSec(); + + // decoy status + spm.isDecoy = decoyDetector.test(sh); + + // mods string + ModificationInfo mi = sh.getModificationInfo(); + if (mi == null) { + spm.seqModStateId = spm.seq; + } else { + StringBuilder sb = new StringBuilder(mi.getModifiedPeptide()); + StringBuilder sbMods = new StringBuilder(); + if (mi.getModNtermMass() != null) { + sb.append(String.format("_%.2f@0", mi.getModNtermMass())); + sbMods.append(String.format("%.2f@0(Nt)", mi.getModNtermMass())); + } + for (ModAminoacidMass mam : mi.getModAminoacidMass()) { + sb.append(String.format("_%.2f@%d", mam.getMass(), mam.getPosition())); + if (sbMods.length() > 0) { + sbMods.append(", "); + } + sbMods.append(String.format("%.2f@%d(%s)", mam.getMass(), mam.getPosition(), sh.getPeptide().charAt(mam.getPosition()-1))); + } + + if (mi.getModCtermMass() != null) { + sb.append(String.format("_%.2f@%d", mi.getModCtermMass(), sh.getPeptide().length() + 1)); + if (sbMods.length() > 0) { + sbMods.append(", "); + } + sbMods.append(String.format("%.2f@%d(Ct)", mi.getModCtermMass(), sh.getPeptide().length() + 1)); + } + spm.seqModStateId = sb.toString(); + spm.mods = sbMods.toString(); + } + + // scores + List searchScores = sh.getSearchScore(); + for (int i = 0; i < searchScores.size(); i++) { + try { + spm.scores[i] = Double.parseDouble(searchScores.get(i).getValueStr()); + } catch (NumberFormatException e) { + spm.scores[i] = Double.NaN; + throw new IllegalStateException(String.format("Could not parse score #%d for " + + "spectrum query `%s`", i, sq.getSpectrum())); + } + } + if (pepProphIdx > -1) { + Object analysis = sh.getAnalysisResult().stream() + .flatMap(analysisResult -> analysisResult.getAny().stream()) + .filter(o -> o instanceof PeptideprophetResult).findAny() + .orElseThrow(() -> new IllegalStateException(String.format( + "PeptideProphet score could not be found in file `%s`, SpectrumQuery `%s`", + path, sq.getSpectrum()))); + spm.scores[pepProphIdx] = ((PeptideprophetResult) analysis).getProbability(); + } + if (iProphIdx> -1) { + Object analysis = sh.getAnalysisResult().stream() + .flatMap(analysisResult -> analysisResult.getAny().stream()) + .filter(o -> o instanceof InterprophetResult).findAny() + .orElseThrow(() -> new IllegalStateException(String.format( + "iProphet score could not be found in file `%s`, SpectrumQuery `%s`", path, + sq.getSpectrum()))); + spm.scores[iProphIdx] = ((InterprophetResult) analysis).getProbability(); + } + spms.add(spm); + } + results.add(pepSearchResult); + } + } catch (FileParsingException e) { + throw new IOException(e); + } + + return new PepSearchFile(path.toAbsolutePath().normalize().toString(), results); + } + + private HashMap mapScoreNames(List queries) throws IOException { + + // find first SearchHit entry + SearchHit sh = queries.stream() + .flatMap(spectrumQuery -> spectrumQuery.getSearchResult().stream()) + .flatMap(searchResult -> searchResult.getSearchHit().stream()) + .findFirst().orElseThrow(() -> new IOException("No SearchHit elements found")); + + HashMap scoreMapping = new HashMap<>(10); + + // get regular scores + List searchScore = sh.getSearchScore(); + int scoreIndex = 0; + for (; scoreIndex < searchScore.size(); scoreIndex++) { + NameValueType score = searchScore.get(scoreIndex); + String scoreName = score.getName(); + scoreMapping.put(scoreName, scoreIndex); + } + + // try get peptide prophet score + List analysisResults = sh.getAnalysisResult(); + for (AnalysisResult analysisResult : analysisResults) { + String analysis = analysisResult.getAnalysis(); + switch (analysis) { + case PEPTIDE_PROPHET: + scoreMapping.put(PEPTIDE_PROPHET, scoreIndex++); + break; + case I_PROPHET: + scoreMapping.put(I_PROPHET, scoreIndex++); + break; + } + } + + return scoreMapping; + } + + public static class Factory implements IParserFactory { + + @Override + public boolean supports(String fileNameLowerCase, Path path) { + return fileNameLowerCase.endsWith(".pep.xml") || fileNameLowerCase.endsWith(".pepxml"); + } + + @Override + public IPepidParser create(Path path) { + return new PepxmlParser(path); + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/predicates/ComparisonType.java b/src/main/java/com/dmtavt/deltamass/predicates/ComparisonType.java new file mode 100644 index 0000000..ada3e80 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/predicates/ComparisonType.java @@ -0,0 +1,85 @@ +package com.dmtavt.deltamass.predicates; + +import java.util.function.Function; + +public enum ComparisonType { + LESS("<"), + LESS_OR_EQUAL("<="), + EQUAL("=="), + GREATER_OR_EQUAL(">="), + GREATER(">"); + + public final String symbol; + + ComparisonType(String symbol) { + this.symbol = symbol; + } + + public static ComparisonType fromSymbol(String s) { + switch (s) { + case "<": + return LESS; + case "<=": + return LESS_OR_EQUAL; + case "==": + return EQUAL; + case ">=": + return GREATER_OR_EQUAL; + case ">": + return GREATER; + default: + throw new IllegalArgumentException("Unknown comparison symbol: " + s); + } + } + + public static Function create(ComparisonType cmp, final double compareTo) { + switch (cmp) { + case LESS: + return val -> Double.compare(val, compareTo) < 0; + case LESS_OR_EQUAL: + return val -> Double.compare(val, compareTo) <= 0; + case EQUAL: + return val -> Double.compare(val, compareTo) == 0; + case GREATER_OR_EQUAL: + return val -> Double.compare(val, compareTo) >= 0; + case GREATER: + return val -> Double.compare(val, compareTo) > 0; + default: + throw new IllegalStateException("Illegal comparison type for Doubles: " + cmp.symbol); + } + } + + public static Function create(ComparisonType cmp, final int compareTo) { + switch (cmp) { + case LESS: + return val -> val < compareTo; + case LESS_OR_EQUAL: + return val -> val <= compareTo; + case EQUAL: + return val -> val == compareTo; + case GREATER_OR_EQUAL: + return val -> val >= compareTo; + case GREATER: + return val -> val > compareTo; + default: + throw new IllegalStateException("Illegal comparison type for Integers: " + cmp.symbol); + } + } + + public static Function create(ComparisonType cmp, final String compareTo) { + if (compareTo == null) { + throw new IllegalArgumentException("No nulls"); + } + switch (cmp) { + case EQUAL: + return val -> compareTo.equals(val); + default: + throw new IllegalStateException("Illegal comparison type for Strings: " + cmp.symbol); + } + } + + @Override + public String toString() { + return symbol; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/predicates/DoubleSpmPredicate.java b/src/main/java/com/dmtavt/deltamass/predicates/DoubleSpmPredicate.java new file mode 100644 index 0000000..b796788 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/predicates/DoubleSpmPredicate.java @@ -0,0 +1,37 @@ +package com.dmtavt.deltamass.predicates; + +import com.dmtavt.deltamass.data.Spm; +import java.text.DecimalFormat; +import java.util.function.Function; + +public class DoubleSpmPredicate implements ISpmPredicate { + + private final String property; + private final ComparisonType cmp; + private final double compareTo; + private final Function fetcher; + private final Function comparisonDelegate; + private static DecimalFormat format = new DecimalFormat("0.###E3"); + + public DoubleSpmPredicate(String property, ComparisonType cmp, String comparisonStr, Function fetcher) { + this(property, cmp, Double.parseDouble(comparisonStr), fetcher); + } + + public DoubleSpmPredicate(String property, ComparisonType cmp, double compareTo, Function fetcher) { + this.property = property; + this.cmp = cmp; + this.compareTo = compareTo; + this.fetcher = fetcher; + this.comparisonDelegate = ComparisonType.create(cmp, compareTo); + } + + @Override + public boolean test(Spm spm) { + return comparisonDelegate.apply(fetcher.apply(spm)); + } + + @Override + public String toString() { + return "Spm Predicate: " + property + " " + cmp + " " + format.format(compareTo); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/predicates/ISpmPredicate.java b/src/main/java/com/dmtavt/deltamass/predicates/ISpmPredicate.java new file mode 100644 index 0000000..6a2419e --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/predicates/ISpmPredicate.java @@ -0,0 +1,10 @@ +package com.dmtavt.deltamass.predicates; + +import com.dmtavt.deltamass.data.Spm; +import java.io.Serializable; +import java.util.function.Predicate; + +@FunctionalInterface +public interface ISpmPredicate extends Predicate, Serializable { + +} diff --git a/src/main/java/com/dmtavt/deltamass/predicates/IntSpmPredicate.java b/src/main/java/com/dmtavt/deltamass/predicates/IntSpmPredicate.java new file mode 100644 index 0000000..6f5390a --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/predicates/IntSpmPredicate.java @@ -0,0 +1,32 @@ +package com.dmtavt.deltamass.predicates; + +import com.dmtavt.deltamass.data.Spm; +import java.util.function.Function; + +public class IntSpmPredicate implements ISpmPredicate { + + private final String property; + private final ComparisonType cmp; + private final int compareTo; + private final Function fetcher; + private final Function comparisonDelegate; + + public IntSpmPredicate(String property, ComparisonType cmp, String comparisonStr, + Function fetcher) { + this(property, cmp, Integer.parseInt(comparisonStr), fetcher); + } + + public IntSpmPredicate(String property, ComparisonType cmp, int compareTo, Function fetcher) { + this.property = property; + this.cmp = cmp; + this.compareTo = compareTo; + this.fetcher = fetcher; + this.comparisonDelegate = ComparisonType.create(cmp, compareTo); + } + + @Override + public boolean test(Spm spm) { + return comparisonDelegate.apply(fetcher.apply(spm)); + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/predicates/SpmPredicateFactory.java b/src/main/java/com/dmtavt/deltamass/predicates/SpmPredicateFactory.java new file mode 100644 index 0000000..203a1a6 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/predicates/SpmPredicateFactory.java @@ -0,0 +1,57 @@ +package com.dmtavt.deltamass.predicates; + +import java.util.Map; + +public class SpmPredicateFactory { + + private String property; + private ComparisonType cmp; + private String value; + + public SpmPredicateFactory(String property, ComparisonType cmp, String value) { + this.property = property.toLowerCase(); + this.cmp = cmp; + this.value = value; + } + + @Override + public String toString() { + return property + " " + cmp + " " + value; + } + + public ISpmPredicate create(Map scoreMapping) throws ScoreNameNotExists { + switch (property) { + case "mobs": + return new DoubleSpmPredicate(property, cmp, value, spm -> spm.mObsNeutral); + case "mcalc": + return new DoubleSpmPredicate(property, cmp, value, spm -> spm.mCalcNeutral); + case "mdiff": + return new DoubleSpmPredicate(property, cmp, value, spm -> spm.mDiff); + case "mdiffcorr": + return new DoubleSpmPredicate(property, cmp, value, spm -> spm.mDiffCorrected); + case "mzobs": + return new DoubleSpmPredicate(property, cmp, value, spm -> spm.mzObs); + case "z": + return new IntSpmPredicate(property, cmp, value, spm -> spm.charge); + default: + // other property names are considered "score names" + Integer ordinal = scoreMapping.get(property); + if (ordinal == null) { + throw new ScoreNameNotExists("Sore mapping didn't contain name: '" + property + "'", + property); + } + final int index = ordinal; + return new DoubleSpmPredicate(property, cmp, value, spm -> spm.scores[index]); + } + } + + public class ScoreNameNotExists extends Exception { + + private final String name; + + public ScoreNameNotExists(String message, String name) { + super(message); + this.name = name; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/ChartPanelPatched.java b/src/main/java/com/dmtavt/deltamass/ui/ChartPanelPatched.java new file mode 100644 index 0000000..af5b6af --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/ChartPanelPatched.java @@ -0,0 +1,273 @@ +package com.dmtavt.deltamass.ui; + +import java.awt.event.ActionEvent; +import java.awt.geom.Rectangle2D; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.XYPlot; +import org.jfree.data.Range; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChartPanelPatched extends ChartPanel { + + private static final Logger log = LoggerFactory.getLogger(ChartPanelPatched.class); + + private JCheckBoxMenuItem checkboxZoomXAxisOnly; + private JCheckBoxMenuItem checkboxScaleYAxisAuto; + private Action zoomXonlyAction; + private Action scaleYautoAction; + private volatile boolean zoomXonly; + private volatile boolean scaleYauto; + + private final ConcurrentHashMap mapPlotLastState = new ConcurrentHashMap<>(3); + + public ChartPanelPatched(JFreeChart chart) { + super(chart); + init(); + } + + @Override + public void setChart(JFreeChart chart) { + super.setChart(chart); + init(); + } + + public ChartPanelPatched(JFreeChart chart, boolean useBuffer) { + super(chart, useBuffer); + init(); + } + + public ChartPanelPatched(JFreeChart chart, boolean properties, boolean save, boolean print, + boolean zoom, boolean tooltips) { + super(chart, properties, save, print, zoom, tooltips); + init(); + } + + public ChartPanelPatched(JFreeChart chart, int width, int height, int minimumDrawWidth, + int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, + boolean properties, boolean save, boolean print, boolean zoom, boolean tooltips) { + super(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, + maximumDrawHeight, useBuffer, properties, save, print, zoom, tooltips); + init(); + } + + public ChartPanelPatched(JFreeChart chart, int width, int height, int minimumDrawWidth, + int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, + boolean properties, boolean copy, boolean save, boolean print, boolean zoom, + boolean tooltips) { + super(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, + maximumDrawHeight, useBuffer, properties, copy, save, print, zoom, tooltips); + init(); + } + + private void init() { + setHorizontalAxisTrace(true); + setVerticalAxisTrace(true); + setMouseWheelEnabled(true); + setRangeZoomable(!zoomXonly); + + // ASMS-2017 mods + setHorizontalAxisTrace(false); + setVerticalAxisTrace(false); + + if (getChart().getPlot() instanceof XYPlot) { + XYPlot plot = (XYPlot) getChart().getPlot(); + plot.setDomainPannable(true); + setRangeAxesAutoscale(getChart(), true); + } + + getChart().getPlot().addChangeListener(event -> { + log.trace("PlotChangeEvent: {} on some {}", event.getType(), + event.getPlot().getClass().getSimpleName()); + //setRangeAxesAutoscale(getChart(), true); + final Plot plt = getChart().getPlot(); + if (plt instanceof XYPlot) { + XYPlot plot = (XYPlot) plt; +// plot.getRangeAxis().setAutoRange(false); +// plot.getRangeAxis().setAutoRange(true); + if (mapPlotLastState == null) { + // Don't trust IDEA's hint that this condition is always true. + // If you don't believe me, then comment the next line out. + return; + } + Range rangeOld = mapPlotLastState.get(plot); + Range rangeCur = plot.getDomainAxis().getRange(); + if (rangeOld == null) { + // no messages yet from this plot, add it to the map and + log.trace("Old range not found, new range created {}", rangeCur); + mapPlotLastState.put(plot, rangeCur); + runYAxisRescaling(); + } else { + if (!rangeOld.equals(rangeCur)) { + log.trace("Range in map updated, old range {}, new range {}", rangeOld, rangeCur); + mapPlotLastState.put(plot, rangeCur); + runYAxisRescaling(); + } else { + log.trace("Range in map was equal to the new one, nothing to update"); + } + } + } + +// if (ChartPanelPatched.this.scaleYauto) { +// ChartPanelPatched.this.runYAxisRescaling(); +// } + }); + } + + private static Field getField(Class clazz, String fieldName) { + Class tmpClass = clazz; + do { + try { + Field f = tmpClass.getDeclaredField(fieldName); + return f; + } catch (NoSuchFieldException e) { + tmpClass = tmpClass.getSuperclass(); + } + } while (tmpClass != null); + + throw new RuntimeException("Field '" + fieldName + "' not found on class " + clazz); + } + + private List getAllFields(Class clazz) { + + List fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())); + + Class superClazz = clazz.getSuperclass(); + if (superClazz != null) { + fields.addAll(getAllFields(superClazz)); + } + + return fields; + } + + @Override + protected JPopupMenu createPopupMenu(boolean properties, boolean copy, boolean save, + boolean print, boolean zoom) { + JPopupMenu menu = super.createPopupMenu(properties, copy, save, print, zoom); + this.scaleYauto = true; + this.zoomXonly = true; + + checkboxZoomXAxisOnly = new JCheckBoxMenuItem(); + checkboxZoomXAxisOnly.setSelected(zoomXonly); + ChartPanelPatched.this.setRangeZoomable(!zoomXonly); + zoomXonlyAction = new AbstractAction("Zoom X-axis only") { + @Override + public void actionPerformed(ActionEvent e) { + Runnable runnable = () -> { + boolean wasRangeZoomable = ChartPanelPatched.this.isRangeZoomable(true); + ChartPanelPatched.this.setRangeZoomable(!wasRangeZoomable); + zoomXonly = !ChartPanelPatched.this.isRangeZoomable(true); + checkboxZoomXAxisOnly.setSelected(zoomXonly); + }; + SwingUtilities.invokeLater(runnable); + } + }; + checkboxZoomXAxisOnly.setAction(zoomXonlyAction); + menu.add(checkboxZoomXAxisOnly, 0); + + checkboxScaleYAxisAuto = new JCheckBoxMenuItem(); + checkboxScaleYAxisAuto.setSelected(scaleYauto); + setRangeAxesAutoscale(getChart(), scaleYauto); + scaleYautoAction = new AbstractAction("Auto zoom Y-axis") { + @Override + public void actionPerformed(ActionEvent e) { + Runnable runnable = () -> { + scaleYauto = checkboxScaleYAxisAuto.isSelected(); + setRangeAxesAutoscale(getChart(), scaleYauto); + }; + SwingUtilities.invokeLater(runnable); + } + }; + checkboxScaleYAxisAuto.setAction(scaleYautoAction); + menu.add(checkboxScaleYAxisAuto, 0); + + return menu; + } + + public static void setRangeAxesAutoscale(JFreeChart chart, boolean rangeAxisAutoscale) { + Plot plot = chart.getPlot(); + if (plot instanceof XYPlot) { + XYPlot xyPlot = (XYPlot) plot; + int rangeAxisCount = xyPlot.getRangeAxisCount(); + for (int i = 0; i < rangeAxisCount; i++) { + ValueAxis rangeAxis = xyPlot.getRangeAxis(i); + rangeAxis.setAutoRange(rangeAxisAutoscale); + } + } + } + + @Override + public void restoreAutoBounds() { + super.restoreAutoBounds(); +// XYPlot plot = getChart().getXYPlot(); +// if (plot.getRangeAxisCount() > 1) { +// PlotFactory.synchronizeAxes(plot); +// } + } + + @Override + public void zoomInDomain(double x, double y) { + super.zoomInDomain(x, y); + } + + @Override + public void zoomOutDomain(double x, double y) { + super.zoomOutDomain(x, y); + } + + @Override + public void zoomInBoth(double x, double y) { + super.zoomInBoth(x, y); + } + + @Override + public void zoomOutBoth(double x, double y) { + super.zoomOutBoth(x, y); + } + + @Override + public void zoomOutRange(double x, double y) { + super.zoomOutRange(x, y); + } + + @Override + public void zoomInRange(double x, double y) { + super.zoomInRange(x, y); + } + + @Override + public void zoom(Rectangle2D selection) { + super.zoom(selection); + } + + private void runYAxisRescaling() { + if (scaleYauto) { + log.trace("Running automatic rescaling of Y axis"); + restoreAutoRangeBounds(); + } + } + + @Override + public boolean isRangeZoomable() { + return zoomXonly && scaleYauto; + } + + public boolean isRangeZoomable(boolean callSuper) { + return callSuper ? super.isRangeZoomable() : this.isRangeZoomable(); + } + + +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/DeltaMassOptionsForm.java b/src/main/java/com/dmtavt/deltamass/ui/DeltaMassOptionsForm.java new file mode 100644 index 0000000..98fb9ba --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/DeltaMassOptionsForm.java @@ -0,0 +1,769 @@ +package com.dmtavt.deltamass.ui; + +import com.dmtavt.deltamass.DeltaMassInfo; +import com.dmtavt.deltamass.args.DecoyTreatment; +import com.dmtavt.deltamass.args.MassCorrection; +import com.dmtavt.deltamass.logging.LogbackJTextPaneAppender; +import com.dmtavt.deltamass.logic.CommandClean; +import com.dmtavt.deltamass.logic.CommandPlot; +import com.dmtavt.deltamass.logic.LogicClean; +import com.dmtavt.deltamass.logic.LogicPeaks; +import com.dmtavt.deltamass.logic.LogicPlot; +import com.dmtavt.deltamass.logic.UserOptsInputFiles; +import com.dmtavt.deltamass.logic.UserOptsKde; +import com.dmtavt.deltamass.logic.UserOptsPeaks; +import com.dmtavt.deltamass.logic.UserOptsPlot; +import com.dmtavt.deltamass.messages.MsgCleanup; +import com.dmtavt.deltamass.messages.MsgFlushGuiCache; +import com.dmtavt.deltamass.messages.MsgRunPlot; +import com.dmtavt.deltamass.messages.MsgStop; +import com.dmtavt.deltamass.messages.MsgVersionUpdateInfo; +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.ser.SwingCachePropsStore; +import com.github.chhh.utils.swing.DocumentFilters; +import com.github.chhh.utils.swing.FileNameEndingFilter; +import com.github.chhh.utils.swing.TextConsole; +import com.github.chhh.utils.swing.UiCheck; +import com.github.chhh.utils.swing.UiCombo; +import com.github.chhh.utils.swing.UiSpinnerDouble; +import com.github.chhh.utils.swing.UiSpinnerInt; +import com.github.chhh.utils.swing.UiText; +import java.awt.Color; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.regex.Pattern; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.TitledBorder; +import javax.swing.table.DefaultTableModel; +import net.java.balloontip.BalloonTip; +import net.java.balloontip.styles.BalloonTipStyle; +import net.java.balloontip.styles.RoundedBalloonStyle; +import net.miginfocom.layout.CC; +import net.miginfocom.layout.LC; +import net.miginfocom.swing.MigLayout; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeltaMassOptionsForm extends JPanel { + + private static final Logger log = LoggerFactory.getLogger(DeltaMassOptionsForm.class); + private static final String DELTAMASS_FORM_CACHE = "deltamass-form.cache"; + private LogbackJTextPaneAppender appender = new LogbackJTextPaneAppender(); + private EventBus bus = EventBus.getDefault(); + private final SwingCachePropsStore cache; + private BalloonTipStyle style = new RoundedBalloonStyle(5, 5, Color.WHITE, Color.BLACK); + private BalloonRegistry balloonRegistry = new BalloonRegistry(); + + private UiText uiTextInputPaths = new UiText(); + private JButton uiBtnCleanup = new JButton("Cleanup"); + private UiText uiTextReFiles = new UiText(); + private UiText uiTextReDecoys = new UiText("rev_"); + //private JTextField text = new JTextField(); + private UiCheck uiCheckNoCache = new UiCheck(); + private UiCombo uiComboDecoysUse = new UiCombo(); + private UiCombo uiComboMassCorrection = new UiCombo(); + private UiText uiTextAdditionalSearchPaths = new UiText(); + private static DecimalFormat df1 = new DecimalFormat("0.0"); + private static DecimalFormat df2 = new DecimalFormat("0.00"); + private static DecimalFormat df3 = new DecimalFormat("0.000"); + private static DecimalFormat df4 = new DecimalFormat("0.0000"); + private UiCheck uiCheckRangeLimitDo = new UiCheck(null, null, false); + private UiSpinnerDouble uiSpinnerRangeLimitLo = new UiSpinnerDouble(+5.5, -50000.0, 50000.0, 5.0, df1); + private UiSpinnerDouble uiSpinnerRangeLimitHi = new UiSpinnerDouble(+85.5, -50000.0, 50000.0, 5.0, df1); + private UiText uiTextRangeExclusions = new UiText("-0.5 0.5,"); + private UiSpinnerDouble uiSpinnerKdeBandwidth = new UiSpinnerDouble(0.0, 0.0, 0.1, 0.001, 3, df3); + private UiSpinnerDouble uiSpinnerKdeDx = new UiSpinnerDouble(+0.002, 0.0001, 1.0, 0.0001, 4, df4); + private UiSpinnerInt uiSpinnerKdeMinData = new UiSpinnerInt(10, 0, Integer.MAX_VALUE, 10); + private UiCheck uiCheckPeaksDo = new UiCheck(null, null, true); + private UiSpinnerInt uiSpinnerMinPsm = new UiSpinnerInt(10, 0, Integer.MAX_VALUE, 10); + private UiSpinnerDouble uiSpinnerMinPct = new UiSpinnerDouble(0.025, 0, 1, 0.005, 3, df3); + private UiSpinnerDouble uiSpinnerPlotStep = new UiSpinnerDouble(0.004, 0.0001, 0.5, 0.0001, 4, df4); + private UiText uiTextOutputFile = new UiText(); + private JButton uiBtnRun = new JButton("Run"); + private JButton uiBtnStop = new JButton("Stop"); + private JButton uiBtnConsoleClear = new JButton("Clear Console"); + + private ExecutorService exec = Executors.newSingleThreadExecutor(); + private ConcurrentLinkedQueue> tasks = new ConcurrentLinkedQueue<>(); + private ConcurrentLinkedQueue threads = new ConcurrentLinkedQueue<>(); + + + private JPanel pInputs = new JPanel(); + private JPanel pKde = new JPanel(); + private JPanel pOut = new JPanel(); + private JPanel pButtons = new JPanel(); + private JScrollPane scrollConsole = new JScrollPane(); + private TextConsole console = new TextConsole(); + + private static final String pathsSeparator = "; "; + + public DeltaMassOptionsForm() { + super(); + cache = new SwingCachePropsStore(DELTAMASS_FORM_CACHE, DeltaMassInfo::getCacheDir, DeltaMassInfo::getNameVersion); + init(); + } + + private void init() { + this.setLayout(new MigLayout(new LC().flowY().fillX())); + LC lc = new LC(); +// LC lc = new LC().debug(); + + pInputs.setLayout(new MigLayout(lc)); + pInputs.setBorder(new TitledBorder("Inputs files")); + + CC ccLbl = new CC().alignX("right").gapBefore("5px"); + CC ccComp = new CC().width("50:70:100px"); + CC ccCompWrap = new CC().width("30:50:70px").wrap(); + + // adding components to the inputs panel + JFileChooser fci = new JFileChooser(); + fci.setAcceptAllFileFilterUsed(true); + fci.setMultiSelectionEnabled(true); + fci.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + fci.setApproveButtonText("Select"); + fci.addChoosableFileFilter(new FileNameEndingFilter("PepXml", "pepxml", "pep.xml")); +// fc.addChoosableFileFilter(new FileNameEndingFilter("MzIdentMl", "mzid", "mzidentml", "mzidml")); + final String textInputPathsText = uiTextInputPaths.getText().trim(); + if (!StringUtils.isNullOrWhitespace(textInputPathsText)) { + try { + String[] split = textInputPathsText.split("\\s*;\\s*"); + Path p = Paths.get(split[0]); + if (Files.exists(p)) { + if (Files.isDirectory(p)) { + fci.setCurrentDirectory(p.toFile()); + } else { + fci.setCurrentDirectory(p.getParent().toFile()); + } + } + } catch (Exception ignored) { + } + } + uiBtnCleanup.addActionListener(e -> { + List> errors = new ArrayList<>(); + ValidationResult> inputPaths = pre(getInputPaths(), errors); + if (inputPaths.isError) { + log.warn("Could not initiate cleanup"); + return; + } + bus.post(new MsgCleanup(uiBtnCleanup, inputPaths.result)); + }); + + FormEntry feInputs = new FormEntry("field.input.paths", "Input paths", uiTextInputPaths); + pInputs.add(feInputs + .browseButton("Browse", fci, "Select peptide ID files, directories or both", + pathsSeparator), + new CC().skip(1).split(2).spanX()); + pInputs.add(uiBtnCleanup, new CC().wrap()); + pInputs.add(feInputs.label(), ccLbl); + pInputs.add(feInputs.comp, new CC().width("200:300:1000px").spanX().growX().wrap()); + + + FormEntry feReFiles = new FormEntry("field.input.re-files", "File regex", uiTextReFiles); + pInputs.add(feReFiles.label(), ccLbl); + pInputs.add(feReFiles.comp, ccComp); + FormEntry feReDecoys = new FormEntry("field.input.re-decoys", "Decoys regex", uiTextReDecoys); + pInputs.add(feReDecoys.label(), ccLbl); + pInputs.add(feReDecoys.comp, ccCompWrap); + + uiComboDecoysUse.setModel(new DefaultComboBoxModel<>(Arrays.stream(DecoyTreatment.values()) + .map(DecoyTreatment::name).toArray(String[]::new))); + uiComboDecoysUse.setSelectedItem(DecoyTreatment.FORWARDS_ONLY.toString()); + FormEntry feDecoysUse = new FormEntry("field.input.decoys-use", "Use", uiComboDecoysUse); + pInputs.add(feDecoysUse.label(), ccLbl); + pInputs.add(feDecoysUse.comp, new CC().width("100:150px")); + FormEntry feNoCache = new FormEntry("field.input.no-cache", "No Cache", uiCheckNoCache); + pInputs.add(feNoCache.label(), ccLbl); + pInputs.add(feNoCache.comp, ccCompWrap); + + uiComboMassCorrection.setModel(new DefaultComboBoxModel<>(Arrays.stream(MassCorrection.values()) + .map(MassCorrection::name).toArray(String[]::new))); + uiComboMassCorrection.setSelectedItem(MassCorrection.ZERO_PEAK.toString()); + FormEntry feCalibration = new FormEntry("field.input.calibration", "Calibration", + uiComboMassCorrection); + pInputs.add(feCalibration.label(), ccLbl); + pInputs.add(feCalibration.comp, new CC().width("100:150px").wrap()); + FormEntry feSearchPaths = new FormEntry("field.input.additional-search-paths", + "Search paths", + uiTextAdditionalSearchPaths, "These paths will be searched for LCMS files
" + + "when PEP_ID calibration requested."); + pInputs.add(feSearchPaths.label(), ccLbl); + pInputs.add(feSearchPaths.comp, new CC().width("200:300:1000px").span(3).growX()); + JFileChooser fca = new JFileChooser(); + fca.setAcceptAllFileFilterUsed(true); + fca.setMultiSelectionEnabled(true); + fca.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + fca.setApproveButtonText("Select"); + final String additionalSearchPathsText = uiTextAdditionalSearchPaths.getText().trim(); + if (!StringUtils.isNullOrWhitespace(additionalSearchPathsText)) { + try { + String[] split = textInputPathsText.split("\\s*;\\s*"); + Path p = Paths.get(split[0]); + if (Files.exists(p)) { + if (Files.isDirectory(p)) { + fca.setCurrentDirectory(p.toFile()); + } else { + fca.setCurrentDirectory(p.getParent().toFile()); + } + } + } catch (Exception ignored) { + } + } + pInputs.add(feSearchPaths.browseButton("Browse", fca, + "Additional LCMS files search locations", pathsSeparator), new CC().wrap()); + + CC ccGrowX = new CC().growX(); + this.add(pInputs, ccGrowX); + + pKde.setLayout(new MigLayout(lc)); + pKde.setBorder(new TitledBorder("KDE Options")); + + FormEntry feRangeLimitDo = new FormEntry("field.kde.range-limit.do", "Limit dM range", + uiCheckRangeLimitDo); + pKde.add(feRangeLimitDo.label(), new CC().split(2).alignX("right")); + pKde.add(feRangeLimitDo.comp, new CC()); + FormEntry feRangeLimitLo = new FormEntry("field.kde.range-limit.lo", "dM Lo", + uiSpinnerRangeLimitLo); + pKde.add(feRangeLimitLo.label(), new CC().alignX("right")); + final String w1 = "50:70:100px"; + pKde.add(feRangeLimitLo.comp, new CC().width(w1)); + FormEntry feRangeLimitHi = new FormEntry("field.kde.range-limit.hi", "dM Hi", uiSpinnerRangeLimitHi); + pKde.add(feRangeLimitHi.comp, new CC().width(w1)); + pKde.add(feRangeLimitHi.label(), new CC().spanX().alignX("left").wrap()); + + final String uiTextRangeExclusionsOldText = uiTextRangeExclusions.getText(); + uiTextRangeExclusions.setDocument(DocumentFilters.getDigitCommaDotSpaceMinusFitler()); + uiTextRangeExclusions.setText(uiTextRangeExclusionsOldText); + FormEntry feRangeExclusions = new FormEntry("field.kde.range-excludes", "Exclude dM ranges", + uiTextRangeExclusions); + pKde.add(feRangeExclusions.label(), ccLbl); + pKde.add(feRangeExclusions.comp, new CC().growX().spanX().wrap()); + + FormEntry feKdeBandwith = new FormEntry("field.kde.bandwidth", "Bandwidth", uiSpinnerKdeBandwidth, + "KDE bandwidth. Zero = automatic determination. The smaller the number the less smoothing is applied to data."); + pKde.add(feKdeBandwith.label(), new CC().split(2)); + pKde.add(feKdeBandwith.comp, new CC()); + + FormEntry feKdeStep = new FormEntry("field.kde.dx", "KDE Step", uiSpinnerKdeDx); +// pKde.add(feKdeStep.label(), new CC().alignX("right").gapBefore("5px").span(2)); +// pKde.add(feKdeStep.comp, ccComp); + pKde.add(feKdeStep.label(), new CC().alignX("right")); + pKde.add(feKdeStep.comp, new CC().width(w1)); + FormEntry feKdeMinData = new FormEntry("field.kde.min-data", "Min Data", uiSpinnerKdeMinData); + pKde.add(feKdeMinData.label(), ccLbl); + pKde.add(feKdeMinData.comp, new CC().width(w1).wrap()); + + FormEntry fePeaksDo = new FormEntry("field.peaks.do", "Detect peaks", uiCheckPeaksDo); + pKde.add(fePeaksDo.label(), new CC().alignX("right").gapBefore("5px").split(2)); + pKde.add(fePeaksDo.comp, new CC()); + FormEntry fePeaksMinPsms = new FormEntry("field.peaks.min-psms", "Min PSMs", uiSpinnerMinPsm); + pKde.add(fePeaksMinPsms.label(), new CC().alignX("right")); + pKde.add(fePeaksMinPsms.comp, new CC().width(w1)); + FormEntry fePeaksMinPct = new FormEntry("field.peaks.min-pct", "Min %", + uiSpinnerMinPct); + pKde.add(fePeaksMinPct.label(), ccLbl); + pKde.add(fePeaksMinPct.comp, new CC().width(w1).wrap()); + + FormEntry fePlotStep = new FormEntry("field.plot.step", "Plotting step", + uiSpinnerPlotStep); + pKde.add(fePlotStep.label(), new CC().skip(1).alignX("right")); + pKde.add(fePlotStep.comp, new CC().width(w1).wrap()); + + this.add(pKde, ccGrowX); + + pOut.setLayout(new MigLayout(lc)); + pOut.setBorder(new TitledBorder("Output")); + FormEntry feOutFile = new FormEntry("field.output.path", "Output file", uiTextOutputFile); + pOut.add(feOutFile.label(), ccLbl); + pOut.add(feOutFile.comp, new CC().width("200:300:1000px")); + JFileChooser fco = new JFileChooser(); + fco.setMultiSelectionEnabled(false); + fco.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + final String outPath = uiTextOutputFile.getText().trim(); + if (!StringUtils.isNullOrWhitespace(outPath)) { + try { + fco.setCurrentDirectory(Paths.get(outPath).toFile()); + } catch (Exception ignored) {} + } + pOut.add(feOutFile.browseButton("Browse", fco, "Select output directory or file", pathsSeparator)); + + this.add(pOut, ccGrowX); + + pButtons.setLayout(new MigLayout(lc)); + pButtons.add(uiBtnRun, new CC()); + pButtons.add(uiBtnStop, new CC()); + pButtons.add(uiBtnConsoleClear, new CC().wrap()); + scrollConsole.setViewportView(console); + this.add(pButtons, new CC()); + + this.add(scrollConsole, new CC().width("100:200:800px").height("100:250:1000px").dockSouth().growY()); + + appender.start(); + log.debug("Started LogbackJTextPaneAppender logger"); + appender.setTextPane(console); + uiBtnRun.addActionListener(e -> run()); + uiBtnConsoleClear.addActionListener(e -> console.setText("")); + uiBtnStop.addActionListener(e -> { + log.info("Stop signal sent"); + bus.post(new MsgStop(uiBtnStop)); + }); + + bus.register(this); + // Try to fetch new version info from remote. Runs in a separate thread. + DeltaMassInfo.checkForNewVersions(); + + cache.load(this); + } + + @Subscribe + public void onStop(MsgStop m) { + synchronized (this) { + + exec.shutdownNow(); + } + } + + public void run() { + synchronized (this) { + MsgStop msgStop = bus.removeStickyEvent(MsgStop.class); + if (msgStop != null) { + log.debug("Removed old sticky stop message"); + } + log.info("Validating inputs"); + balloonRegistry.clear(); + cache.store(this); + ValidationReport report = validateInputs(); + if (!report.errors.isEmpty()) { + log.warn("Input validation failed"); + return; + } + final LogicPlot logic = new LogicPlot(report.cmd); + + // ask user about overwriting existing output file + if (report.cmd.optsPeaks.out != null) { + log.debug("Checking if the output file is ok"); + final Path out = LogicPeaks.getOutputFilePath(report.cmd.optsPeaks.out); + if (Files.exists(out) && !Files.isDirectory(out)) { + // if we got here, it's an existing regular file + // ask user about overwriting + int choice = JOptionPane.showConfirmDialog(this, new JLabel( + "Output file exists:
" + out.toString() + "
Overwrite?")); + if (JOptionPane.YES_OPTION != choice) { + log.info("User chose not to overwrite existing file, stopping."); + return; + } + } + } + + if (!tasks.stream().allMatch(Future::isDone)) { + onStop(null); + if (!exec.isTerminated()) { + JOptionPane + .showMessageDialog(this, "Previous task is still running, try again a little later"); + return; + } else { + tasks.clear(); + } + } + + threads.forEach(Thread::interrupt); + threads.clear(); + Thread thread = new Thread(logic::run); + threads.add(thread); + thread.start(); + +// exec = Executors.newSingleThreadExecutor(); +// Future future = exec.submit(logic::run); +// tasks.add(future); + } + } + + @Subscribe + public void onVersionUpdateInfo(MsgVersionUpdateInfo m) { + + final StringBuilder sb = new StringBuilder(); + sb.append(String.format(Locale.ROOT, + "Your %s version is [%s]
\n" + + "There is a newer version of %s available [%s].
\n" + + "Please
click here to download a newer one.
" + + "Full link:
" + + "%s", + m.programName, m.currentVersion, m.programName, m.newVersion, m.downloadUrl, m.downloadUrl, + m.downloadUrl)); + JEditorPane clickableHtml = SwingUtils.createClickableHtml(sb.toString()); + clickableHtml.setBackground(new JLabel().getBackground()); + JOptionPane.showMessageDialog(this, clickableHtml, + "New version available", JOptionPane.INFORMATION_MESSAGE); + } + + private ValidationReport validateInputs() { + List> errors = new ArrayList<>(); + + CommandPlot cmd = new CommandPlot(); + UserOptsInputFiles optsInputs = new UserOptsInputFiles(); + cmd.optsInputFiles = optsInputs; + + optsInputs.noCache = uiCheckNoCache.isSelected(); + + ValidationResult> exclusionRanges = pre(getExclusionRanges(), errors); + if (!exclusionRanges.isError) { + optsInputs.excludeMzRanges = exclusionRanges.result; + } + + ValidationResult regexFiles = pre(getRegexFiles(), errors); + if (!regexFiles.isError && regexFiles.result != null) { + optsInputs.fileRegex = regexFiles.result; + } + + ValidationResult regexDecoys = pre(getRegexDecoys(), errors); + if (!regexDecoys.isError && regexDecoys.result != null) { + optsInputs.decoyRegex = regexDecoys.result; + } + + ValidationResult decoyTreatment = pre(getDecoyTreatment(), errors); + if (!decoyTreatment.isError) { + optsInputs.decoyTreatment = decoyTreatment.result; + } + + ValidationResult> inputPaths = pre(getInputPaths(), errors); + if (!inputPaths.isError) { + optsInputs.inputFiles = inputPaths.result; + } + + ValidationResult massCutoffLo = pre(getMassCutoffLo(), errors); + if (!massCutoffLo.isError) { + optsInputs.mLo = massCutoffLo.result; + } + + ValidationResult massCutoffHi = pre(getMassCutoffHi(), errors); + if (!massCutoffHi.isError) { + optsInputs.mHi = massCutoffHi.result; + } + + ValidationResult> searchPaths = pre(getSearchPaths(), errors); + if (!searchPaths.isError) { + optsInputs.additionalSearchPaths = searchPaths.result; + } + + ValidationResult massCorrection = pre(getMassCorrection(), errors); + if (!massCorrection.isError) { + optsInputs.massCorrection = massCorrection.result; + } + + UserOptsKde optsKde = new UserOptsKde(); + cmd.optsKde = optsKde; + + optsKde.bandwidth = (Double) uiSpinnerKdeBandwidth.getValue(); + optsKde.step = (Double) uiSpinnerKdeDx.getValue(); + optsKde.minData = (Integer) uiSpinnerKdeMinData.getValue(); + + UserOptsPeaks optsPeaks = new UserOptsPeaks(); + cmd.optsPeaks = optsPeaks; + + optsPeaks.minPsmsPerGmm = (Integer) uiSpinnerMinPsm.getValue(); + optsPeaks.minPeakPct = (Double) uiSpinnerMinPct.getValue(); + ValidationResult outputPath = pre(getOutputPath(), errors); + if (!outputPath.isError) { + optsPeaks.out = outputPath.result; + } + + UserOptsPlot optsPlot = new UserOptsPlot(); + cmd.optsPlot = optsPlot; + optsPlot.noPeaks = !uiCheckPeaksDo.isSelected(); + optsPlot.step = (Double) uiSpinnerPlotStep.getValue(); + optsPlot.exitOnClose = false; + + return new ValidationReport(errors, cmd); + } + + private class ValidationReport { + final List> errors; + final CommandPlot cmd; + + private ValidationReport( + List> errors, CommandPlot cmd) { + this.errors = errors; + this.cmd = cmd; + } + } + + private ValidationResult pre(ValidationResult result, List> errors) { + if (result.isError) { + errors.add(result); + balloonRegistry.createTip(result.component, result.errorMessage); + } + return result; + } + + private ValidationResult getMassCutoffLo() { + boolean isLimit = uiCheckRangeLimitDo.isSelected(); + if (!isLimit) { + return new ValidationResult<>(null); + } + double lo = (Double)uiSpinnerRangeLimitLo.getValue(); + double hi = (Double)uiSpinnerRangeLimitHi.getValue(); + if (hi < lo) { + return new ValidationResult<>("Low must be < high", uiSpinnerRangeLimitLo); + } + return new ValidationResult<>(lo); + } + + private ValidationResult getMassCutoffHi() { + boolean isLimit = uiCheckRangeLimitDo.isSelected(); + if (!isLimit) { + return new ValidationResult<>(null); + } + double lo = (Double)uiSpinnerRangeLimitLo.getValue(); + double hi = (Double)uiSpinnerRangeLimitHi.getValue(); + if (hi < lo) { + return new ValidationResult<>("High must be > low", uiSpinnerRangeLimitHi); + } + return new ValidationResult<>(hi); + } + + private ValidationResult> getInputPaths() { + final UiText c = uiTextInputPaths; + String text = c.getNonGhostText(); + if (StringUtils.isNullOrWhitespace(text)) { + return new ValidationResult<>("Can't be left empty", c); + } + + String[] paths = text.split(pathsSeparator); + List res = new ArrayList<>(); + for (String path : paths) { + try { + Path p = Paths.get(path); + if (!p.toFile().exists()) { + return new ValidationResult<>("Not all given paths exist", c); + } + res.add(p); + } catch (Exception e) { + return new ValidationResult<>("Not all given paths are valid", c); + } + } + return new ValidationResult<>(res); + } + + private ValidationResult> getSearchPaths() { + final UiText c = uiTextAdditionalSearchPaths; + String text = uiTextAdditionalSearchPaths.getText(); + if (StringUtils.isNullOrWhitespace(text) || text.equals(c.getGhostText())) { + return new ValidationResult<>(Collections.emptyList()); + } + + String[] paths = text.split(pathsSeparator); + List res = new ArrayList<>(); + for (String path : paths) { + try { + Path p = Paths.get(path); + if (!p.toFile().exists()) { + return new ValidationResult<>("Not all given paths exist", c); + } + res.add(p); + } catch (Exception e) { + return new ValidationResult<>("Not all given paths are valid", c); + } + } + return new ValidationResult<>(res); + } + + private ValidationResult getDecoyTreatment() { + try { + return new ValidationResult<>(DecoyTreatment.valueOf(uiComboDecoysUse.asString())); + } catch (Exception e) { + return new ValidationResult<>(e.getMessage(), uiComboDecoysUse); + } + } + + private ValidationResult getMassCorrection() { + try { + return new ValidationResult<>(MassCorrection.valueOf(uiComboMassCorrection.asString())); + } catch (Exception e) { + return new ValidationResult<>(e.getMessage(), uiComboMassCorrection); + } + } + + private ValidationResult getOutputPath() { + final UiText c = uiTextOutputFile; + final String t = c.getNonGhostText(); + if (!StringUtils.isNullOrWhitespace(t)) { + try { + return new ValidationResult<>(Paths.get(t)); + } catch (Exception e) { + return new ValidationResult<>("Not a valid path", c); + } + } + return new ValidationResult<>(null); + } + + /** + * @return Null when input text field is empty or whitespace. + */ + private ValidationResult getRegexFiles() { + final String text = uiTextReFiles.getText(); + try { + if (StringUtils.isNullOrWhitespace(text)) { + return new ValidationResult<>(null); + } else { + return new ValidationResult<>(Pattern.compile(text)); + } + } catch (Exception e) { + return new ValidationResult<>(e.getMessage(), uiTextReFiles); + } + } + + /** + * @return Null when input text field is empty or whitespace. + */ + private ValidationResult getRegexDecoys() { + final String text = uiTextReDecoys.getText(); + try { + return StringUtils.isNullOrWhitespace(text) ? new ValidationResult<>(null) : new ValidationResult<>(Pattern.compile(text)); + } catch (Exception e) { + return new ValidationResult<>(e.getMessage(), uiTextReDecoys); + } + } + + private ValidationResult> getExclusionRanges() { + final String text = uiTextRangeExclusions.getText().trim(); + String[] pairs = text.split("\\s*,\\s*"); + List res = new ArrayList<>(); + for (String pair : pairs) { + if (StringUtils.isNullOrWhitespace(pair)) { + continue; + } + String[] numbers = pair.trim().split("\\s+"); + if (numbers.length != 2) { + return new ValidationResult<>("Not 2 numbers between commas", uiTextRangeExclusions); + } + try { + double v1 = Double.parseDouble(numbers[0]); + double v2 = Double.parseDouble(numbers[1]); + if (v2 <= v1) { + return new ValidationResult<>("2nd number in range must be greater than 1st", uiTextRangeExclusions); + } + res.add(v1); + res.add(v2); + } catch (Exception e) { + return new ValidationResult<>("Could not parse number", uiTextRangeExclusions); + } + } + return new ValidationResult<>(res); + } + + @Subscribe + public void onFlushGuiCache(MsgFlushGuiCache m) { + log.debug("Flushing cache"); + cache.flush(); + } + + @Subscribe + public void onCleanup(MsgCleanup m) { + CommandClean cmd = new CommandClean(); + cmd.inputFiles = m.paths; + LogicClean logicClean = new LogicClean(cmd); + List toDelete = logicClean.getScheduledForDeletion(); + + String[] columns = {"File to delete"}; + String[][] data = new String[toDelete.size()][1]; + for (int i = 0; i < toDelete.size(); i++) { + data[i][0] = toDelete.get(i).toString(); + } + DefaultTableModel model = new DefaultTableModel(data, columns); + JTable table = new JTable(model); + int confirmation = JOptionPane.showConfirmDialog(this, SwingUtils.wrapInScrollForDialog(table), + "Delete these files?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); + if (JOptionPane.YES_OPTION == confirmation) { + for (Path path : toDelete) { + try { + Files.deleteIfExists(path); + } catch (Exception e) { + log.warn("Could not delete file: " + path.toString(), e); + } + } + } + } + + @Subscribe + public void onRunPlot(MsgRunPlot m) { + run(); + } + + private static class ValidationResult { + final T result; + final boolean isError; + final String errorMessage; + final JComponent component; + + public ValidationResult(T result) { + this.result = result; + isError = false; + errorMessage = null; + component = null; + } + + public ValidationResult(String errorMessage, JComponent component) { + this.result = null; + this.isError = true; + this.errorMessage = errorMessage; + this.component = component; + } + + public ValidationResult(T result, boolean isError, String errorMessage, JComponent component) { + this.result = result; + this.isError = isError; + this.errorMessage = errorMessage; + this.component = component; + } + } + + private class BalloonRegistry { + private Map tips = new HashMap<>(); + private final Object lock = new Object(); + + public void clear() { + synchronized (lock) { + for (BalloonTip tip : tips.values()) { + tip.closeBalloon(); + } + tips = new HashMap<>(); + } + } + + public void createTip(JComponent attachTo, String text) { + synchronized (lock) { + BalloonTip oldTip = tips.get(attachTo); + if (oldTip != null) { + oldTip.closeBalloon(); + } + BalloonTip tip = new BalloonTip(attachTo, text, DeltaMassOptionsForm.this.style, true); + tips.put(attachTo, tip); + tip.setVisible(true); + } + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/DeltaMassUiElements.java b/src/main/java/com/dmtavt/deltamass/ui/DeltaMassUiElements.java new file mode 100644 index 0000000..a88a3a7 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/DeltaMassUiElements.java @@ -0,0 +1,10 @@ +package com.dmtavt.deltamass.ui; + +import java.util.Arrays; +import java.util.List; + +public class DeltaMassUiElements { + public static final List ICON_NAMES = Arrays.asList( + "delta_logo_16.png", "delta_logo_32.png", "delta_logo_48.png", + "delta_logo_64.png", "delta_logo_96.png", "delta_logo_128.png", "delta_logo_256.png"); +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/DeltamassDisplay.java b/src/main/java/com/dmtavt/deltamass/ui/DeltamassDisplay.java new file mode 100644 index 0000000..490993a --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/DeltamassDisplay.java @@ -0,0 +1,737 @@ +package com.dmtavt.deltamass.ui; + +import com.dmtavt.deltamass.DeltaMassInfo; +import com.dmtavt.deltamass.logic.LogicKde.ProcessingSegment; +import com.dmtavt.deltamass.logic.LogicKde.SpmInfo; +import com.dmtavt.deltamass.messages.MsgSelectionClear; +import com.dmtavt.deltamass.messages.MsgModsInRange; +import com.dmtavt.deltamass.messages.MsgPlotClicked; +import com.dmtavt.deltamass.messages.MsgPsmsRequest; +import com.dmtavt.deltamass.messages.MsgPsmsResponse; +import com.dmtavt.deltamass.messages.MsgSelectionMade; +import com.dmtavt.deltamass.messages.MsgSwitchView; +import com.dmtavt.deltamass.messages.MsgRequestHistogram; +import com.dmtavt.deltamass.ui.PlotFactory.CloseOption; +import com.dmtavt.deltamass.ui.SimpleTableModel.Col; +import com.dmtavt.deltamass.ui.SimpleTableModel.ColDouble; +import com.dmtavt.deltamass.ui.SimpleTableModel.ColInt; +import com.dmtavt.deltamass.ui.SimpleTableModel.ColString; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.color.ColorHelper; +import com.github.chhh.utils.ser.SwingCachePropsStore; +import com.github.chhh.utils.swing.TextConsole; +import com.github.chhh.utils.swing.UiSpinnerInt; +import java.awt.BasicStroke; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Paint; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.border.TitledBorder; +import javax.swing.table.DefaultTableModel; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.IntervalMarker; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.StandardXYBarPainter; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.data.general.DatasetGroup; +import org.jfree.data.statistics.SimpleHistogramBin; +import org.jfree.data.statistics.SimpleHistogramDataset; +import org.jfree.data.xy.XYDataset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.ptm.mod.Mod; +import umich.ptm.mod.ModSpecificity; + +public class DeltamassDisplay extends JFrame { + + private static final Logger log = LoggerFactory.getLogger(DeltamassDisplay.class); + + private static final VIEW DEFAULT_VIEW = VIEW.DETAILED; + private static final String DELTAMASS_DISPLAY_CACHE = "deltamass-display.cache"; + + private final EventBus bus; + private final SwingCachePropsStore cache; + private final List data; + + // chart + private final ChartPanel chart; + private final Markers markers; + // main menu + private ButtonGroup group; + private JRadioButtonMenuItem rbViewChart; + private JRadioButtonMenuItem rbViewDetailed; + private JSplitPane splitMain; + // detailed view + private JSplitPane splitDetailed; + private JPanel detailedLeftPanel; + private JPanel detailedRightPanel; + private TextConsole console; + private SimpleETable eTable; + private JTextField statusBar; + private UiSpinnerInt spinnerPsmLimit; + private JLabel labelTableCount; + private volatile VIEW view = VIEW.INIT; + private JButton btnHistogram; + + public DeltamassDisplay(ChartPanel chart, String frameTitle, CloseOption onClose, + EventBus bus, List data) { + super(); + checkNotNull(chart, "Chart"); + checkNotNull(frameTitle, "Frame title"); + + setDefaultCloseOperation(onClose.windowConstant); + this.setTitle(frameTitle); + this.bus = bus; + this.data = data; + this.cache = new SwingCachePropsStore(DELTAMASS_DISPLAY_CACHE, DeltaMassInfo::getCacheDir, DeltaMassInfo::getNameVersion); + this.chart = chart; + this.markers = new Markers(); + this.markers.reset(); + + init(); + bus.register(this); + } + + private void checkNotNull(Object o, String name) { + if (o == null) { + throw new NullPointerException(name + " can't be null"); + } + } + + private void init() { + SwingUtils.setFrameIcons(this, DeltaMassUiElements.ICON_NAMES, DeltaMassUiElements.class); + this.setJMenuBar(createMenuBar()); + setView(DEFAULT_VIEW); + chart.setDoubleBuffered(true); + + cache.load(this); + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + log.debug("Closing Display"); + cache.store(DeltamassDisplay.this); + } + + @Override + public void windowClosed(WindowEvent e) { + log.debug("Closed Display"); + } + }); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onSwitchView(MsgSwitchView m) { + setView(m.view); + } + + @Subscribe + public void onRequestHistogram(MsgRequestHistogram m) { + // Dataset histogram + List datasetHistograms = new ArrayList<>(); + final boolean doHistogram = false; + log.debug("Dataset Histogram"); + List histDx = Arrays.asList(0.002, 0.001, 0.0005); + histDx.sort((o1, o2) -> Double.compare(o2,o1)); + final List segments = this.data; + for (Double dx : histDx) { +// double histLo = Double.POSITIVE_INFINITY; +// double histHi = Double.NEGATIVE_INFINITY; +// for (ProcessingSegment seg : segments) { +// histLo = Math.min(histLo, seg.data.xLoBracket); +// histHi = Math.max(histHi, seg.data.xHiBracket); +// } + double histLo = m.mLo; + double histHi = m.mHi; + + final double offset = 0; + double ptrLo = histLo + offset; + final String key = String.format("Histogram-dx=%.4f", dx); + final SimpleHistogramDataset hist = new SimpleHistogramDataset(key); + // create bins + while (ptrLo < histHi) { + final double loBound = ptrLo; + final double hiBound = ptrLo + dx; + SimpleHistogramBin bin = new SimpleHistogramBin(loBound, hiBound, true, false); + ptrLo = hiBound; + // fill bin + long pointsInBin = segments.stream().flatMap(seg -> seg.data.spmInfos.stream()) + .filter(si -> si.spm.mDiffCorrected >= loBound && si.spm.mDiffCorrected < hiBound) + .count(); + bin.setItemCount((int)pointsInBin); + log.trace("Assigned {} points to bin [{}, {}]", pointsInBin, loBound, hiBound); + hist.addBin(bin); + } + int totalDataPoints = segments.stream().mapToInt(seg -> seg.data.spmInfos.size()).sum(); + log.debug("Create dataset histogram key: {}, total num data points: {}, step: {}, offset: {}", key, totalDataPoints, dx, offset); + datasetHistograms.add(hist); + } + + + final String datasetHistKeyBase = "dataset-Histogram"; + final List palette = ColorHelper + .getDistinctColors(datasetHistograms.size(), 0.5f, 0.75f); + final List histColors = new ArrayList<>(); + for (Color c : palette) { + final int histAlpha = 100; + Color cTransparent = new Color(c.getRed(), c.getGreen(), c.getBlue(), histAlpha); + histColors.add(cTransparent); + } + + final String yAxisHistKey = "histogram"; + final String yAxisHistLabel = "Count (histogram)"; + ValueAxis yAxisHist = new NumberAxis(yAxisHistLabel); + + // adding histogram datasets + XYPlot plt = getPlot(chart); + for (int i = 0; i < datasetHistograms.size(); i++) { + SimpleHistogramDataset datasetHistogram = datasetHistograms.get(i); + datasetHistogram.setGroup(new DatasetGroup(yAxisHistKey)); + final int histogramIndex = i; + XYBarRenderer r = new XYBarRenderer() { + @Override + public Paint getSeriesPaint(int series) { + int colorIndex = histogramIndex % histColors.size(); + return histColors.get(colorIndex); + } + }; + StandardXYBarPainter bp = new StandardXYBarPainter(); + r.setBarPainter(bp); + r.setShadowVisible(false); + r.setGradientPaintTransformer(null); + r.setDrawBarOutline(true); + final String key = String.format("%s-%d", datasetHistKeyBase, i); + + // add dataset to plot + final int datasetIdx = plt.getDatasetCount(); + plt.setRenderer(datasetIdx, r); + + int existingYAxisIdx = -1; + for (int j = 0; j < plt.getRangeAxisCount(); j++) { + if (yAxisHistLabel.equals(plt.getRangeAxis(j).getLabel())) { + existingYAxisIdx = j; + break; + } + } + ValueAxis yAxis; + if (existingYAxisIdx == -1) { + // axis not exists + yAxis = new NumberAxis(yAxisHistLabel); + existingYAxisIdx = plt.getRangeAxisCount(); + plt.setRangeAxis(existingYAxisIdx, yAxis); + } else { + yAxis = plt.getRangeAxis(existingYAxisIdx); + } + plt.mapDatasetToRangeAxis(datasetIdx, existingYAxisIdx); + plt.setDataset(datasetIdx, datasetHistogram); + } + + } + + @Subscribe + public void onModsInRange(MsgModsInRange m) { + log.debug("{} received {} message: {}", + DeltamassDisplay.class.getSimpleName(), MsgModsInRange.class.getSimpleName(), m.toString()); + console.setText(""); + List lines = new ArrayList<>(); + + lines.add(String.format("Mass shift range: [%.4f, %.4f], spans [%.4f] Da", + m.dmLo, m.dmHi, Math.abs(m.dmHi - m.dmLo))); + lines.add(""); + + if (m.mods == null || m.mods.isEmpty()) { + lines.add("No known modifications"); + + } else { + int maxRefLen = m.mods.stream().mapToInt(mod -> mod.getRef().toString().length()) + .max().orElse(10); + StringBuilder fmt = new StringBuilder(); + fmt.append("%+4.4f [%-").append(maxRefLen).append("s] %s\nDesc: %s\nSites: %s"); + + for (Mod mod : m.mods) { + String sites = mod.getSpecificities().stream().map(ModSpecificity::toString) + .collect(Collectors.joining(", ")); + Double massMono = mod.getComposition().getMassMono(); + lines.add(String + .format(fmt.toString(), massMono, mod.getRef(), mod.getDescShort(), mod.getDescLong(), + sites)); + } + } + console.setText(String.join("\n----------\n", lines)); + + // add the mods as markers to the view + if (m.mods != null) { + List listOfUniqueMasses = m.mods.stream().mapToDouble(mod -> mod.getComposition().getMassMono()) + .filter(Double::isFinite).distinct().boxed().collect(Collectors.toList()); + for (Double modMass : listOfUniqueMasses) { + markers.addModMark(modMass); + } + } + } + + @Subscribe + public void onResponsePsms(MsgPsmsResponse m) { + log.debug("{} received {} PSMs in {} message", DeltamassDisplay.class.getSimpleName(), + m.spmInfos.size(), MsgPsmsResponse.class.getSimpleName()); + + SimpleTableModel tableModel = new SimpleTableModel<>(m.spmInfos, createTableModelColumns(m.spmInfos)); + log.debug("Setting new model for table with {} rows, {} cols", tableModel.data.size(), tableModel.cols.size()); + eTable.setModel(tableModel); + labelTableCount.setText(String.format("%d PSMs in selection", tableModel.data.size())); + } + + private List> createTableModelColumns(List spms) { + List> cols = new ArrayList<>(); + cols.add(new ColString<>(si -> si.spm.seq, "Seq")); + cols.add(new ColString<>(si -> si.spm.mods, "Mods")); + cols.add(new ColString<>(si -> si.psr.proteinAccessions.get(si.spm.protId), "Prot")); + cols.add(new ColDouble<>(si -> si.spm.mDiffCorrected, "dM (Corr)")); + cols.add(new ColDouble<>(si -> si.spm.mDiff, "dM (raw)")); + cols.add(new ColInt<>(si -> si.spm.charge, "z")); + cols.add(new ColDouble<>(si -> si.spm.mzObs, "m/z (Obs)")); + cols.add(new ColDouble<>(si -> si.spm.mObsNeutral, "M (Obs, z=0)")); + cols.add(new ColDouble<>(si -> si.spm.mCalcNeutral, "M (Calc, z=0)")); + cols.add(new ColDouble<>(si -> si.spm.rtSec, "RT (sec")); + cols.add(new ColString<>(si -> si.psr.rawFileName, "File")); + + if (spms != null && !spms.isEmpty()) { + List scoreNames = spms.stream() + .map(si -> si.psr).distinct() + .flatMap(psr -> psr.scoreMapping.keySet().stream()).distinct() + .collect(Collectors.toList()); + for (String scoreName : scoreNames) { + cols.add(new ColDouble<>(si -> { + Integer scoreIdx = si.psr.scoreMapping.get(scoreName); + if (scoreIdx == null) { + return Double.NaN; + } + return si.spm.scores[scoreIdx]; + }, scoreName)); + } + } + +// cols.add(new ColDouble<>(si -> si., "")); + return cols; + } + + private void detailedViewTeardown() { + statusBar = null; + if (console != null) { + console.setText(""); + } + console = null; + + if (eTable != null) { + eTable.clearSelection(); + eTable.setModel(new DefaultTableModel()); + } + eTable = null; + spinnerPsmLimit = null; + labelTableCount = null; + + detailedLeftPanel = null; + detailedRightPanel = null; + splitDetailed = null; + + splitMain = null; + } + + private void detailedViewSetup() { + + splitMain = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + splitDetailed = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + Dimension minSize100x100 = new Dimension(100, 100); + chart.setMinimumSize(minSize100x100); + + // left part of detailed view + splitDetailed.add(createDetailedLeftPanel()); + + // right part of detailed view + splitDetailed.add(createDetailedRightPanel()); + + splitDetailed.setMinimumSize(minSize100x100); + splitMain.add(chart); + splitMain.add(splitDetailed); + + splitDetailed.revalidate(); + splitDetailed.resetToPreferredSizes(); + splitMain.revalidate(); + splitMain.resetToPreferredSizes(); + } + + private JPanel createDetailedLeftPanel() { + + // top status bar text-field + JPanel topPanel = new JPanel(new GridBagLayout()); + statusBar = new JTextField(); + statusBar.setEditable(false); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.weightx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + topPanel.add(statusBar, gbc); + + // text console + JScrollPane scrollConsole = new JScrollPane(); + Dimension minSize50x10 = new Dimension(50, 10); + scrollConsole.setMinimumSize(minSize50x10); + console = new TextConsole(); + console.setMinimumSize(minSize50x10); + scrollConsole.setViewportView(console); + + detailedLeftPanel = new JPanel(new BorderLayout()); + detailedLeftPanel.setMinimumSize(minSize50x10); + detailedLeftPanel.add(topPanel, BorderLayout.NORTH); + detailedLeftPanel.add(scrollConsole, BorderLayout.CENTER); + + return detailedLeftPanel; + } + + private JPanel createDetailedRightPanel() { + Dimension minSize = new Dimension(100, 100); + + // table/PSM controls + JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); + controls.setBorder(new TitledBorder("PSM list controls")); + controls.add(new JBtnBuilder().text("Clear Selection").tip("Clear highlighted range") + .onClick(e -> bus.post(new MsgSelectionClear())).create()); + + btnHistogram = new JBtnBuilder().text("Histogram").tip("Show histogram for current view") + .enabled(false) + .onClick(e -> { + //ValueAxis xAxis = getPlot(chart).getDomainAxis(); + //double lb = xAxis.getLowerBound(); + //double ub = xAxis.getUpperBound(); + + double[] sel = markers.getCurrentSelection(); + if (sel == null) { + log.error("Selection was null at histogram request"); + return; + } + if (sel.length != 2) { + log.error("Selection array length !=2 at histogram creation"); + return; + } + + final double maxHistSpanWarning = 3.0; + final double distance = Math.abs(sel[0] - sel[1]); + if (distance > maxHistSpanWarning) { + JOptionPane.showConfirmDialog(this, + String.format("Histogram span is larger than %.1f Da.
" + + "It might take a while to plot.
" + + "Do you want to proceed?", + maxHistSpanWarning)); + } + + bus.post(new MsgRequestHistogram(sel[0], sel[1])); + }).create(); + + controls.add(btnHistogram); + spinnerPsmLimit = new UiSpinnerInt(10000, 0, Integer.MAX_VALUE, 5000); + spinnerPsmLimit.setPreferredSize(new Dimension(150, 20)); + FormEntry feLimit = new FormEntry("view.psm-limit", "Limit", spinnerPsmLimit); + controls.add(Box.createHorizontalStrut(3)); + controls.add(feLimit.label()); + controls.add(feLimit.comp); + + labelTableCount = new JLabel(); + controls.add(labelTableCount); + + // PSM table + JScrollPane scroll = new JScrollPane(); + scroll.setMinimumSize(minSize); + scroll.setMinimumSize(minSize); + eTable = new SimpleETable(); + eTable.setMinimumSize(minSize); + scroll.setViewportView(eTable); + + // whole panel + detailedRightPanel = new JPanel(new BorderLayout()); + detailedRightPanel.setMinimumSize(minSize); + detailedRightPanel.add(controls, BorderLayout.NORTH); + detailedRightPanel.add(scroll, BorderLayout.CENTER); + + return detailedRightPanel; + } + + private synchronized void setView(VIEW v) { + log.debug("setView() called with view: " + v.toString()); + if (this.view == v) { + log.debug("Same as old view, not switching: " + v.toString()); + return; + } + + view = v; + getContentPane().removeAll(); + detailedViewTeardown(); + group.clearSelection(); + + switch (v) { + case INIT: + getContentPane().setLayout(new BorderLayout()); + getContentPane() + .add(new JLabel("Initializing", SwingConstants.CENTER), BorderLayout.CENTER); + break; + case CHART: + group.setSelected(rbViewChart.getModel(), true); + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(chart, BorderLayout.CENTER); + break; + case DETAILED: + group.setSelected(rbViewDetailed.getModel(), true); + getContentPane().setLayout(new BorderLayout()); + detailedViewSetup(); + getContentPane().add(splitMain, BorderLayout.CENTER); + + break; + default: + throw new AssertionError("Unknown enum constant"); + } + + this.pack(); + this.revalidate(); + if (splitDetailed != null) { + splitDetailed.setDividerLocation(0.3); + } + if (splitMain != null) { + splitMain.setDividerLocation(0.5); + } + } + + private JMenuBar createMenuBar() { + final JMenuBar bar = new JMenuBar(); + JMenu menuFile = new JMenu("File"); + JMenu menuView = new JMenu("View"); + JMenu menuActions = new JMenu("Actions"); + + JMenuItem close = new JMenuItem("Close Window"); + close.addActionListener( + e -> this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING))); + menuFile.add(close); + + menuView.addSeparator(); + group = new ButtonGroup(); + rbViewChart = new JRadioButtonMenuItem("Only plot"); + rbViewChart.addActionListener(e -> bus.post(new MsgSwitchView(VIEW.CHART))); + rbViewChart.setSelected(false); + //rbMenuItem.setMnemonic(KeyEvent.VK_R); + group.add(rbViewChart); + menuView.add(rbViewChart); + + rbViewDetailed = new JRadioButtonMenuItem("Detailed"); + rbViewDetailed.addActionListener(e -> bus.post(new MsgSwitchView(VIEW.DETAILED))); + //rbMenuItem.setMnemonic(KeyEvent.VK_O); + rbViewDetailed.setSelected(false); + + group.clearSelection(); + + group.add(rbViewDetailed); + menuView.add(rbViewDetailed); + menuView.addSeparator(); + + JMenuItem itemClearMarkers = new JMenuItem("Clear markers"); + itemClearMarkers.addActionListener(e -> bus.post(new MsgSelectionClear())); + menuActions.add(itemClearMarkers); + + bar.add(menuFile); + bar.add(menuView); + bar.add(menuActions); + + return bar; + } + + @Subscribe + public void onPlotClicked(MsgPlotClicked m) { + log.debug("onPlotClicked(MsgPlotClicked m) fired, message: " + m.toString()); + markers.addUserMark(m.x); + if (statusBar != null) { + statusBar.setText(String.format("Click at dm: %.5f", m.x)); + } + } + + @Subscribe + public void onSelectionMade(MsgSelectionMade m) { + btnHistogram.setEnabled(true); + } + + @Subscribe + public void onClearMarkers(MsgSelectionClear m) { + markers.reset(); + eTable.setModel(new SimpleTableModel<>(Collections.emptyList(), createTableModelColumns(Collections.emptyList()))); + labelTableCount.setText("No selection"); + console.setText(""); + + // clear histogram + btnHistogram.setEnabled(false); + XYPlot plt = getPlot(chart); + final int datasetCount = plt.getDatasetCount(); + for (int i = 0; i < datasetCount; i++) { + XYDataset dataset = plt.getDataset(i); + if (dataset == null) { + continue; + } + DatasetGroup group = dataset.getGroup(); + if (group == null) { + continue; + } + if (group.getID() != null && group.getID().toLowerCase().startsWith("histogram")) { + plt.setDataset(i, null); + } + } + } + + private XYPlot getPlot(ChartPanel chartPanel) { + if (chartPanel == null) { + return null; + } + final JFreeChart jfc = chartPanel.getChart(); + if (jfc == null) { + return null; + } + return jfc.getXYPlot(); + } + + public enum VIEW {INIT, CHART, DETAILED} + + private class Markers { + + final private List userMarks = new ArrayList<>(); + final private List areaMarks = new ArrayList<>(); + final private List modMarks = new ArrayList<>(); + final private Color USER_MARKER_COLOR = Color.MAGENTA; + final private BasicStroke USER_MARKER_STROKE = new BasicStroke( + 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{10.0f, 10.0f}, 0.0f); + + + void addModMark(double xVal) { + BasicStroke stroke = new BasicStroke( + 1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{5.0f, 5.0f}, 0.0f); + Color color = new Color(42, 103, 109); + ValueMarker vm = new ValueMarker(xVal, color, stroke); + modMarks.add(vm); + getPlot(chart).addDomainMarker(vm); + } + + void addUserMark(double xVal) { + if (areaMarks.size() >= 1) { + log.info("Clear previous selection before adding new ones."); + return; + } + if (userMarks.size() == 0) { + // adding first user mark + final ValueMarker m = new ValueMarker(xVal, USER_MARKER_COLOR, USER_MARKER_STROKE); + userMarks.add(m); + getPlot(chart).addDomainMarker(m); + + } else { + // create an area mark + final ValueMarker vm = new ValueMarker(xVal, USER_MARKER_COLOR, USER_MARKER_STROKE); + userMarks.add(vm); + userMarks.sort(Comparator.comparingDouble(ValueMarker::getValue)); + final double lo = userMarks.get(0).getValue(); + final double hi = userMarks.get(1).getValue(); + XYPlot plot = getPlot(chart); + userMarks.forEach(plot::removeDomainMarker); + addRangeMark(lo, hi); + bus.post(new MsgSelectionMade(lo , hi)); + + log.info( + String.format("Requesting PSM info for dM range [%.4f, %.4f]", lo, hi)); + bus.post(new MsgPsmsRequest(lo, hi, spinnerPsmLimit.getActualValue())); + + } + } + + public double[] getCurrentSelection() { + if (areaMarks.isEmpty()) { + return null; + } + double[] sel = new double[2]; + sel[0] = areaMarks.get(0).getStartValue(); + sel[1] = areaMarks.get(0).getEndValue(); + return sel; + } + + void addRangeMark(double xValLo, double xValHi) { + Color color = new Color(175, 123, 43, 44); + IntervalMarker im = new IntervalMarker(xValLo, xValHi, color); + areaMarks.add(im); + getPlot(chart).addDomainMarker(im); + } + + void reset() { + XYPlot plot = getPlot(chart); + plot.clearDomainMarkers(); + + userMarks.clear(); + areaMarks.clear(); + modMarks.clear(); + } + } + + private class JBtnBuilder { + + private JButton b = new JButton(); + + public JBtnBuilder text(String text) { + b.setText(text); + return this; + } + + public JBtnBuilder onClick(ActionListener actionListener) { + b.addActionListener(actionListener); + return this; + } + + public JBtnBuilder tip(String text) { + b.setToolTipText(text); + return this; + } + + public JBtnBuilder enabled(boolean enabled) { + b.setEnabled(enabled); + return this; + } + + + + public JButton create() { + return b; + } + } +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/FormEntry.java b/src/main/java/com/dmtavt/deltamass/ui/FormEntry.java new file mode 100644 index 0000000..712dd73 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/FormEntry.java @@ -0,0 +1,93 @@ +package com.dmtavt.deltamass.ui; + +import com.github.chhh.utils.swing.GhostedTextComponent; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.swing.GhostText; +import java.io.File; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.Collectors; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JTextField; +import com.github.chhh.utils.StringUtils; + +public class FormEntry { + public final JComponent comp; + public final String propName; + public final String labelText; + public final String tooltip; + + public FormEntry(String propName, String labelText, JComponent comp) { + this(propName, labelText, comp, null); + } + + public FormEntry(String propName, String labelText, JComponent comp, String tooltip) { + this.comp = comp; + this.propName = propName; + this.labelText = labelText; + this.tooltip = tooltip; + init(); + } + + private void init() { + comp.setName(propName); + comp.setToolTipText(tooltip); + } + + public JLabel label() { + JLabel l = new JLabel(labelText); + l.setLabelFor(comp); + l.setToolTipText(tooltip); + return l; + } + + public JButton browseButton(String buttonText, final JFileChooser fc, String ghostText, String concat) { + if (!(comp instanceof JTextField)) { + throw new IllegalStateException( + "Can only call browseButton() method for FormEntries which are JTextField"); + } + if (fc.isMultiSelectionEnabled() && concat == null) { + throw new IllegalArgumentException("Concatenation string must be non-null when FileChooser supports multi-selection."); + } + + final JTextField tf = (JTextField)comp; + if (!StringUtils.isNullOrWhitespace(ghostText)) { + if (tf instanceof GhostedTextComponent) { + ((GhostedTextComponent) tf).setGhostText(ghostText); + } + GhostText.register(tf, ghostText, GhostText.LIGHT_GREY); + } + + final JButton btn = new JButton(buttonText); + btn.setToolTipText(tooltip); + btn.addActionListener(e -> { + String s = tf.getText().trim(); + if (!StringUtils.isNullOrWhitespace(s) && !ghostText.equals(s)) { + try { + SwingUtils.setFileChooserPath(fc, Paths.get(s).toString()); + } catch (Exception ignore) {} + } + if (JFileChooser.APPROVE_OPTION == fc.showOpenDialog(SwingUtils.findParentFrameForDialog(comp))) { + String newText = "Could not get selected file paths"; + + File[] fs = fc.getSelectedFiles(); + if (fs.length != 0) { + newText = Arrays.stream(fs) + .map(File::getAbsolutePath).collect(Collectors.joining(concat)); + } else { + File f = fc.getSelectedFile(); + if (f != null && !StringUtils.isBlank(f.toString())) { + newText = f.toString(); + } + } + + + tf.setText(newText); + } + }); + return btn; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/FrameSerializer.java b/src/main/java/com/dmtavt/deltamass/ui/FrameSerializer.java new file mode 100644 index 0000000..933ddfa --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/FrameSerializer.java @@ -0,0 +1,48 @@ +package com.dmtavt.deltamass.ui; + +import com.dmtavt.deltamass.DeltaMassInfo; +import com.dmtavt.deltamass.logic.LogicGui; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import javax.swing.JFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FrameSerializer extends WindowAdapter { + private static final Logger log = LoggerFactory.getLogger(FrameSerializer.class); + private final JFrame frame; + + public FrameSerializer(JFrame frame) { + this.frame = frame; + } + +// @Override +// public void windowClosing(WindowEvent e) { +// super.windowClosing(e); +// +// // try save the window state +// final Path cacheDir = DeltaMassInfo.getCacheDir(); +// +// if (!Files.exists(cacheDir)) { +// try { +// Files.createDirectories(cacheDir); +// } catch (IOException e1) { +// log.error("Could not create directory structure for caching.", e1); +// return; +// } +// } +// +// final Path cachePath = cacheDir.resolve(LogicGui.getSerializedFilename()); +// try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(cachePath, +// StandardOpenOption.CREATE))) { +// oos.writeObject(frame.getContentPane()); +// } catch (IOException e1) { +// log.error("Could not serialize form."); +// } +// } +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/PlotFactory.java b/src/main/java/com/dmtavt/deltamass/ui/PlotFactory.java new file mode 100644 index 0000000..09ff66f --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/PlotFactory.java @@ -0,0 +1,596 @@ +package com.dmtavt.deltamass.ui; + +import com.dmtavt.deltamass.logic.LogicKde.ProcessingSegment; +import com.dmtavt.deltamass.logic.LogicKde.SpmInfo; +import com.dmtavt.deltamass.messages.MsgModsInRange; +import com.dmtavt.deltamass.messages.MsgPlotClicked; +import com.dmtavt.deltamass.messages.MsgPsmsRequest; +import com.dmtavt.deltamass.messages.MsgPsmsResponse; +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.search.BinarySearch; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Stroke; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartMouseEvent; +import org.jfree.chart.ChartMouseListener; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.DatasetRenderingOrder; +import org.jfree.chart.plot.Marker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.chart.title.TextTitle; +import org.jfree.data.xy.XYDataset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.ptm.PtmFactory; +import umich.ptm.PtmFactory.SOURCE; +import umich.ptm.exceptions.ModParsingException; +import umich.ptm.mod.Mod; +import umich.ptm.mod.Mods; + +public class PlotFactory { + + private static final Logger log = LoggerFactory.getLogger(PlotFactory.class); + private EventBus bus; + private JFrame frame = null; + private JFreeChart chart = null; + private ChartPanel chartPanel = null; + private Map datasetMap = new LinkedHashMap<>(); + private Map mapYAxes = new LinkedHashMap<>(); + private String mainDatasetName = null; + private List permanentMarkers = new ArrayList<>(); + private List data; + private static Mods mods; + + static { + try { + mods = PtmFactory.getMods(EnumSet.of(SOURCE.UNIMOD, SOURCE.GEN)); + } catch (ModParsingException e) { + throw new RuntimeException("Could not parse/generate PTMs"); + } + } + + public PlotFactory() { + init(); + bus.register(this); + } + + private void init() { + bus = new EventBus(); + } + + private static void customizeChart(JFreeChart chart) { + setTooltip(chart); + setPointRenderer(chart); + + chart.removeLegend(); + chart.setAntiAlias(true); + chart.setBackgroundPaint(Color.WHITE); + chart.setTitle((TextTitle) null); + } + + public void addSecondaryYAxis(String key, ValueAxis axis) { + mapYAxes.put(key, axis); + } + + private static void customizePlot(XYPlot plot) { + + //final String fontName = SwingUtils.checkFontAvailable("Fira Code", "Calibri", "Roboto", "Arial"); + final String fontName = SwingUtils.checkFontAvailable("Calibri", "Roboto", "Arial"); + Font f = new Font(fontName, Font.PLAIN, 20); + Font fAxisLabel = new Font(fontName, Font.PLAIN, 20); + Font fAxisNumbers = new Font(fontName, Font.PLAIN, 18); + + final int numDomainAxes = plot.getDomainAxisCount(); + for (int i = 0; i < numDomainAxes; i++) { + final ValueAxis xAxis = plot.getDomainAxis(i); + xAxis.setLabelFont(fAxisLabel); + xAxis.setTickLabelFont(fAxisNumbers); + plot.setDomainGridlinePaint(Color.LIGHT_GRAY); + } + + final int numRangeAxes = plot.getRangeAxisCount(); + for (int i = 0; i < numRangeAxes; i++) { + final ValueAxis yAxis = plot.getRangeAxis(i); + yAxis.setLabelFont(fAxisLabel); + yAxis.setTickLabelFont(fAxisNumbers); + plot.setRangeGridlinePaint(Color.LIGHT_GRAY); + } + + plot.setBackgroundPaint(Color.WHITE); + + plot.setDomainCrosshairLockedOnData(false); + plot.setRangeCrosshairLockedOnData(false); + + Stroke stroke = new BasicStroke( + 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 0.0f, new float[]{2.0f, 2.0f}, 0.0f); + plot.setDomainCrosshairStroke(stroke); + plot.setRangeCrosshairStroke(stroke); + plot.setDomainCrosshairVisible(false); + plot.setRangeCrosshairVisible(false); + } + + private static void synchronizeAxes(XYPlot plot) { + int rangeAxisCount = plot.getRangeAxisCount(); + log.debug("Plot has {} range axes to synchronize", rangeAxisCount); + List axes = new ArrayList<>(); + for (int i = 0; i < rangeAxisCount; i++) { + axes.add(plot.getRangeAxis(i)); + } + double[] abs_lb = new double[axes.size()]; + double[] ub = new double[axes.size()]; + double[] upper = new double[axes.size()]; + for (int i = 0; i < axes.size(); i++) { + ValueAxis ax = axes.get(i); + abs_lb[i] = Math.abs(ax.getLowerBound()); + ub[i] = ax.getUpperBound(); + upper[i] = (abs_lb[i] > ub[i]) ? abs_lb[i] : ub[i]; + ax.setUpperBound(upper[i]); + } + if (axes.stream().allMatch(ax -> ax.getLowerBound() >= 0)) { + axes.forEach(ax -> ax.setLowerBound(0)); + } else { + for (int i = 0; i < axes.size(); i++) { + ValueAxis ax = axes.get(i); + ax.setLowerBound(-1 * upper[i]); + } + } + + } + + private static void synchronizeAxes(XYPlot plot, boolean useOldImplFor2AxesOnly) { + if (!useOldImplFor2AxesOnly) { + synchronizeAxes(plot); + return; + } + //T Hackman from here + // To synchronize on zero and allow the ranges to scale independently + // lets find and set the first upper axis range + + int rangeAxisCount = plot.getRangeAxisCount(); + log.debug("Plot has {} range axes to synchronize", rangeAxisCount); + + if (rangeAxisCount != 2) { + throw new IllegalArgumentException( + "Provided plot had the number of range axes not equal to 2"); + } + + ValueAxis axis1 = plot.getRangeAxis(0); + ValueAxis axis2 = plot.getRangeAxis(1); + + double abs_axis1lb = Math.abs(axis1.getLowerBound()); + double abs_axis2lb = Math.abs(axis2.getLowerBound()); + double axis1ub = axis1.getUpperBound(); + double axis2ub = axis2.getUpperBound(); + double axis1upper = (abs_axis1lb > axis1ub) ? abs_axis1lb : axis1ub; + double axis2upper = (abs_axis2lb > axis2ub) ? abs_axis2lb : axis2ub; + + axis1.setUpperBound(axis1upper); + axis2.setUpperBound(axis2upper); + + // now set the lower range if no negative values set to zero + if ((axis1.getLowerBound() >= 0) && (axis2.getLowerBound() >= 0)) { + axis1.setLowerBound(0); + axis2.setLowerBound(0); + } else { + axis1.setLowerBound(-axis1upper); + axis2.setLowerBound(-axis2upper); + } + + //T Hackman To here + } + + public static void setAutoRangeXAxes(XYPlot plot, boolean autorange) { + int rangeAxisCount = plot.getRangeAxisCount(); + for (int i = 0; i < rangeAxisCount; i++) { + ValueAxis rangeAxis = plot.getRangeAxis(); + rangeAxis.setAutoRange(autorange); + } + } + + private static void setTooltip(JFreeChart chart) { + XYPlot plot = chart.getXYPlot(); + XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer(); + + String TOOL_TIP_FORMAT = "{0}: {1} \u21E2 {2}"; + String xFmtStr = "\u0394m/z: 0.000000 ; \u0394m/z: -0.000000"; + String yFmtStr = "#.#####"; + DecimalFormat xFmt = new DecimalFormat(xFmtStr); + DecimalFormat yFmt = new DecimalFormat(yFmtStr); + + StandardXYToolTipGenerator tipGen = new StandardXYToolTipGenerator(TOOL_TIP_FORMAT, xFmt, yFmt); + renderer.setDefaultToolTipGenerator(tipGen); + } + + private static void setPointRenderer(JFreeChart chart) { + XYPlot plot = chart.getXYPlot(); + XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer(); +// Ellipse2D.Double pointMarker = new Ellipse2D.Double(-3, -3, 6, 6); +// renderer.setBaseShape(pointMarker); +// renderer.setBaseShapesFilled(false); +// renderer.setBaseShapesVisible(true); + } + + /** + * Show the chart in a separate frame. + */ + public void display(CloseOption onClose, String frameTitle) { + log.debug("Plot factory display()"); + SwingUtils.setPlatformLafOrNimbus(); + String chartTitle = "Mass shifts"; + String xAxisLabel = "Mass shift (Da)"; + String yAxisLabel = "PSM Density"; + RenderingData mainRd = datasetMap.get(mainDatasetName); + chart = ChartFactory.createXYLineChart(chartTitle, xAxisLabel, yAxisLabel, mainRd.dataset); + customizeChart(chart); + XYPlot plt = chart.getXYPlot(); + + // ASMS-2017 mods + customizePlot(plt); + + List primaryYAxisDatasets = new LinkedList<>(); + List secondaryYAxesDatasets = new ArrayList<>(); + int mainDatasetIdx = -1; + for (Map.Entry e : datasetMap.entrySet()) { + RenderingData rd = e.getValue(); + final String yAxisKey = rd.secondaryYAxisKey; + final boolean isSecondaryAxis = yAxisKey != null; + if (isSecondaryAxis) { + if (rd.name.equals(mainDatasetName)) { + throw new IllegalStateException( + "Primary dataset can not be displayed on the secondary Y axis"); + } + if (!mapYAxes.containsKey(yAxisKey)) { + throw new IllegalStateException(String.format( + "%s Y axis map does not contain yAxisKey [%s]. Add a new Y axis first.", + PlotFactory.class.getSimpleName(), yAxisKey)); + } + secondaryYAxesDatasets.add(rd); + } else { + if (rd.name.equals(mainDatasetName)) { + mainDatasetIdx = primaryYAxisDatasets.size(); + } + primaryYAxisDatasets.add(rd); + } + } + + log.debug("Plotting primary dataset"); + RenderingData mainDataset = primaryYAxisDatasets.remove(mainDatasetIdx); + plt.setDataset(0, mainDataset.dataset); + int primaryYAxisIdx = 0; + plt.mapDatasetToRangeAxis(0, primaryYAxisIdx); + int cnt = 1; + for (int i = 0; i < primaryYAxisDatasets.size(); i++) { + if (i == mainDatasetIdx) { + continue; + } + RenderingData rd = primaryYAxisDatasets.get(i); + plt.setDataset(cnt, rd.dataset); + plt.setRenderer(cnt, rd.renderer); + plt.mapDatasetToRangeAxis(cnt, primaryYAxisIdx); + cnt++; + } + + log.debug("Mapping secondary Y Axes"); + final int secondaryAxisIdxStart = 1; + int secondaryAxisIdxPtr = -1; + final Map secondaryAxisIdxMap = new HashMap<>(); + for (String key : mapYAxes.keySet()) { + secondaryAxisIdxPtr++; + secondaryAxisIdxMap.put(key, secondaryAxisIdxStart + secondaryAxisIdxPtr); + } + + log.debug("Checking secondary datasets"); + if (!secondaryYAxesDatasets.isEmpty()) { + log.debug("Plotting secondary datasets"); + //final NumberAxis yAxis2 = new NumberAxis(secondaryAxisTitle); + //plt.setRangeAxis(secondaryAxisIdx, yAxis2); + + for (RenderingData rd : secondaryYAxesDatasets) { + plt.setDataset(cnt, rd.dataset); + plt.setRenderer(cnt, rd.renderer); + ValueAxis axis = mapYAxes.get(rd.secondaryYAxisKey); + Integer axisIdx = secondaryAxisIdxMap.get(rd.secondaryYAxisKey); + if (plt.getRangeAxis(axisIdx) == null) { + // if the axis has not yet been added, then add it. + plt.setRangeAxis(axisIdx, axis); + } + plt.mapDatasetToRangeAxis(cnt, axisIdx); + cnt++; + } + log.debug("Synchronizing axes"); + synchronizeAxes(plt); + } + log.debug("Chart preparations done"); + + // first plot secondary data, then primary on top + plt.setDatasetRenderingOrder(DatasetRenderingOrder.REVERSE); + + final ChartPanel chartPanel = new ChartPanelPatched(chart); + chartPanel.setMinimumSize(new Dimension(100, 100)); + chartPanel.setPreferredSize(new Dimension(800, 600)); + installMouseListener(chartPanel); + + this.chartPanel = chartPanel; + final String title = StringUtils.isBlank(frameTitle) ? "???" : frameTitle; + frame = new DeltamassDisplay(this.chartPanel, title, onClose, bus, data); + frame.pack(); + SwingUtils.centerFrame(frame); + frame.setLocationByPlatform(true); + frame.setVisible(true); + } + + private void installMouseListener(final ChartPanel chartPanel) { + ChartMouseListener chartMouseListener = new ChartMouseListener() { + @Override + public void chartMouseClicked(final ChartMouseEvent e) { + + switch (e.getTrigger().getButton()) { + case MouseEvent.BUTTON1: + final XYPlot xyPlot = e.getChart().getXYPlot(); + // These are not set properly when chart crosshairs are not enabled + final double xVal = xyPlot.getDomainCrosshairValue(); + final double yVal = xyPlot.getRangeCrosshairValue(); + + //Point2D p = chartPanel.translateScreenToJava2D(e.getTrigger().getPoint()); + Point2D p = e.getTrigger().getPoint(); + Rectangle2D plotArea = chartPanel.getScreenDataArea(); + XYPlot plot = (XYPlot) chart.getPlot(); // your plot + final double chartX = plot.getDomainAxis().java2DToValue(p.getX(), plotArea, plot.getDomainAxisEdge()); + final double chartY = plot.getRangeAxis().java2DToValue(p.getY(), plotArea, plot.getRangeAxisEdge()); + + SwingUtilities.invokeLater(() -> { + MsgPlotClicked m = new MsgPlotClicked(chartX, chartY); + log.debug("Sending message: " + m.toString()); + bus.post(m); + }); + } + } + + @Override + public void chartMouseMoved(ChartMouseEvent event) { + // do nothing + } + }; + + chartPanel.addChartMouseListener(chartMouseListener); + } + + private void clearMarkers() { + if (chart == null) { + throw new IllegalStateException( + "can't addUserMark anything to the chart unless it has been displayed"); + } + XYPlot xyPlot = chart.getXYPlot(); + if (xyPlot == null) { + return; + } + xyPlot.clearDomainMarkers(); + } + + private void addMarker(Marker marker) { + if (chart == null) { + throw new IllegalStateException( + "can't addUserMark anything to the chart unless it has been displayed"); + } + XYPlot xyPlot = chart.getXYPlot(); + if (xyPlot == null) { + return; + } + xyPlot.addDomainMarker(marker); + } + + /** + * Add a dataset to the plot. If it's the first dataset being added, it will become the main + * dataset. + * + * @param renderer Can be null, in which case a simple line chart will be used. + * @param secondaryYAxisKey Use non-null value if you want this dataset to use the secondary Y + * axis. The secondary axis must first be added using {@link #addSecondaryYAxis(String, + * ValueAxis)}. + */ + public void addDataset(String name, XYDataset dataset, XYItemRenderer renderer, + String secondaryYAxisKey) { + RenderingData renderingData = new RenderingData(name, dataset, renderer, secondaryYAxisKey); + if (datasetMap.isEmpty()) { + mainDatasetName = name; + } + datasetMap.put(name, renderingData); + } + + /** + * Add dataset to the plot, that will use some default renderer. + * + * @param secondaryYAxisKey Use non-null if you want this dataset to use the secondary Y axis. + * The secondary axis must first be added using {@link #addSecondaryYAxis(String, ValueAxis)}. + */ + public void addDataset(String name, XYDataset dataset, String secondaryYAxisKey) { + XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false); +// renderer.setBaseStroke(new BasicStroke( +// 2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, +// 1.0f, new float[] {6.0f, 6.0f}, 0.0f)); + addDataset(name, dataset, renderer, secondaryYAxisKey); + } + + public void setMainDataset(String name) { + if (datasetMap.containsKey(name)) { + throw new IllegalArgumentException("The dataset mapping did not contain the requested name."); + } + mainDatasetName = name; + } + + public void addPermanentMarker(String datasetKey, String seriesKey, + Collection markers) { + permanentMarkers.addAll(markers); + } + + public synchronized XYDataset getDataset(String key) { + RenderingData rd = datasetMap.get(key); + return rd == null ? null : rd.dataset; + } + + public void setData(List segments) { + this.data = segments; + } + + @Subscribe + public void onRequestPsms(MsgPsmsRequest m) { + + List modsInRange = mods.findByMass(m.dmLo, m.dmHi); + bus.post(new MsgModsInRange(m.dmLo, m.dmHi, modsInRange)); + + if (this.data == null || this.data.isEmpty()) { + return; + } + List> foundSublists = new ArrayList<>(); + for (ProcessingSegment segment : data) { + final double segDataLo = segment.data.xLoData; + final double segDataHi = segment.data.xHiData; + if (!((segDataHi >= m.dmLo) && (segDataLo <= m.dmHi))) { + continue; // skip segments that don't overlap with target search + } + List spmInfos = segment.data.spmInfos; + int searchLo = BinarySearch.search(spmInfos, si -> Double.compare(si.spm.mDiffCorrected, m.dmLo)); + if (searchLo < 0) { + searchLo = ~searchLo; + } + int searchHi = BinarySearch.search(spmInfos, si -> Double.compare(si.spm.mDiffCorrected, m.dmHi)); + if (searchHi < 0) { + searchHi = ~searchHi; + } else { + searchHi += 1; // we will use this as upper bound for .sublist(), the index needs to be exclusive + } + if (searchHi - searchLo > 0) { + foundSublists.add(spmInfos.subList(searchLo, searchHi)); + } + } + + final int total = foundSublists.stream().mapToInt(List::size).sum(); + if (total == 0) { + log.info(String.format("No PSMs in dM range: [%.4f, %.4f]", m.dmLo, m.dmHi)); + bus.post(new MsgPsmsResponse(Collections.emptyList())); + return; + } + + // DEBUG: START +// List collect = data.stream().flatMap(s -> s.data.spmInfos.stream()) +// .filter(si -> si.spm.mDiffCorrected > m.dmLo && si.spm.mDiffCorrected < m.dmHi) +// .collect(Collectors.toList()); +// log.error("Streaming size: {}, manual size: {}, {}", collect.size(), total, total == collect.size() ? "Equal" : "Not equal"); + // DEBUG: END + + + log.debug("Total {} PSMs in selected area (responding to {} in {})", total, + MsgPsmsRequest.class.getSimpleName(), PlotFactory.class.getSimpleName()); + final int max = m.softLimit; + List toPost; + if (total <= max) { + toPost = copySpmInfosToOneList(foundSublists); + } else { + NumberFormat fmt = NumberFormat.getNumberInstance(Locale.US); + int choice = JOptionPane.showConfirmDialog(chartPanel, + String.format("Selection contains %s psms (more than %s limit), truncate?
" + + "

  • Yes - Truncate to %s
  • " + + "
  • No - proceed with all %s
  • " + + "
  • Cancel - to just cancel
", + fmt.format(total), fmt.format(max), fmt.format(max), fmt.format(total)), + "Large output", + JOptionPane.YES_NO_CANCEL_OPTION); + + switch (choice) { + case JOptionPane.YES_OPTION: + ArrayList foundSpmsCut = new ArrayList<>(max); + for (List foundSublist : foundSublists) { + final int spaceLeft = max - foundSpmsCut.size(); + if (spaceLeft <= 0) { + break; + } + if (spaceLeft >= foundSublist.size()) { + foundSpmsCut.addAll(foundSublist); + } else { + foundSpmsCut.addAll(foundSublist.subList(0, spaceLeft)); + } + } + toPost = foundSpmsCut; + break; + case JOptionPane.NO_OPTION: + toPost = copySpmInfosToOneList(foundSublists); + break; + case JOptionPane.CANCEL_OPTION: + toPost = null; + break; + default: + throw new AssertionError("Unexpected response from JOptinoPane selection"); + } + } + if (toPost != null) { + bus.post(new MsgPsmsResponse(toPost)); + } + } + + private ArrayList copySpmInfosToOneList(List> lists) { + final int total = lists.stream().mapToInt(List::size).sum(); + ArrayList foundSpms = new ArrayList<>(total); + for (List foundSublist : lists) { + foundSpms.addAll(foundSublist); + } + return foundSpms; + } + + public enum CloseOption { + DISPOSE(JFrame.DISPOSE_ON_CLOSE), EXIT(JFrame.EXIT_ON_CLOSE); + + public final int windowConstant; + + CloseOption(int windowConstant) { + this.windowConstant = windowConstant; + } + } + + public static class RenderingData { + + public final String name; + public final XYDataset dataset; + public final XYItemRenderer renderer; + public final String secondaryYAxisKey; + + + public RenderingData(String name, XYDataset dataset, XYItemRenderer renderer, + String secondaryYAxisKey) { + this.name = name; + this.dataset = dataset; + this.renderer = renderer; + this.secondaryYAxisKey = secondaryYAxisKey; + } + } + +} diff --git a/src/main/java/com/dmtavt/deltamass/ui/SimpleETable.java b/src/main/java/com/dmtavt/deltamass/ui/SimpleETable.java new file mode 100644 index 0000000..5fc3f31 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/SimpleETable.java @@ -0,0 +1,106 @@ +package com.dmtavt.deltamass.ui; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import javax.swing.JComponent; +import javax.swing.ListSelectionModel; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableModel; +import org.netbeans.swing.etable.ETable; + +public class SimpleETable extends ETable { + + private static final long serialVersionUID = 1L; + + protected ArrayList> listenersNonEmptyData = new ArrayList<>(); + protected ArrayList> listenersNonEmptySelection = new ArrayList<>(); + + public SimpleETable() { + super(); + init(); + } + + public SimpleETable(TableModel dm) { + super(dm); + init(); + } + + + public void fireInitialization() { + TableModel model = getModel(); + if (model instanceof AbstractTableModel) { + AbstractTableModel atm = (AbstractTableModel) model; + atm.fireTableDataChanged(); + } + getSelectionModel().setSelectionInterval(0, 0); + getSelectionModel().clearSelection(); + } + + @Override + public void setModel(TableModel dataModel) { + TableModel old = getModel(); + super.setModel(dataModel); + if (dataModel != null && !dataModel.equals(old)) { + initModelListeners(); + } + } + + @Override + public void setSelectionModel(ListSelectionModel newModel) { + ListSelectionModel old = getSelectionModel(); + super.setSelectionModel(newModel); + if (newModel != null && !newModel.equals(old)) { + initSelectionListeners(); + } + } + + private void initModelListeners() { + getModel().addTableModelListener(e -> { + boolean notEmpty = getModel().getRowCount() > 0; + Iterator> it = listenersNonEmptyData.iterator(); + while (it.hasNext()) { + WeakReference ref = it.next(); + JComponent comp = ref.get(); + if (comp == null) { + it.remove(); + continue; + } + ref.get().setEnabled(notEmpty); + } + }); + } + + private void initSelectionListeners() { + getSelectionModel().addListSelectionListener(e -> { + int[] sel = getSelectedRows(); + boolean notEmpty = sel.length > 0; + Iterator> it = listenersNonEmptySelection.iterator(); + while (it.hasNext()) { + WeakReference ref = it.next(); + JComponent comp = ref.get(); + if (comp == null) { + it.remove(); + continue; + } + comp.setEnabled(notEmpty); + } + }); + } + + private void init() { + setFullyNonEditable(true); + putClientProperty("terminateEditOnFocusLost", true); + initModelListeners(); + initSelectionListeners(); + } + + public void addComponentsEnabledOnNonEmptyData(JComponent component) { + listenersNonEmptyData.add(new WeakReference<>(component)); + } + + public void addComponentsEnabledOnNonEmptySelection(JComponent component) { + listenersNonEmptySelection.add(new WeakReference<>(component)); + } +} + diff --git a/src/main/java/com/dmtavt/deltamass/ui/SimpleTableModel.java b/src/main/java/com/dmtavt/deltamass/ui/SimpleTableModel.java new file mode 100644 index 0000000..62358ec --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/ui/SimpleTableModel.java @@ -0,0 +1,74 @@ +package com.dmtavt.deltamass.ui; + +import java.util.List; +import java.util.function.Function; +import javax.swing.table.AbstractTableModel; + +public class SimpleTableModel extends AbstractTableModel { + List data; + List> cols; + + public static class Col { + final Class clazz; + final Function valueFetcher; + final String colName; + + public Col(Class clazz, Function valueFetcher, String colName) { + this.clazz = clazz; + this.valueFetcher = valueFetcher; + this.colName = colName; + } + } + + public static class ColDouble extends Col { + public ColDouble(Function valueFetcher, String colName) { + super(Double.class, valueFetcher, colName); + } + } + public static class ColInt extends Col { + public ColInt(Function valueFetcher, String colName) { + super(Integer.class, valueFetcher, colName); + } + } + public static class ColBool extends Col { + public ColBool(Function valueFetcher, String colName) { + super(Boolean.class, valueFetcher, colName); + } + } + public static class ColString extends Col { + public ColString(Function valueFetcher, String colName) { + super(String.class, valueFetcher, colName); + } + } + + public SimpleTableModel(List data, + List> cols) { + this.data = data; + this.cols = cols; + } + + @Override + public Class getColumnClass(int columnIndex) { + return cols.get(columnIndex).clazz; + } + + @Override + public int getRowCount() { + return data.size(); + } + + @Override + public int getColumnCount() { + return cols.size(); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return cols.get(columnIndex).valueFetcher.apply(data.get(rowIndex)); + } + + @Override + public String getColumnName(int column) { + return cols.get(column).colName; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/utils/ArrayUtils.java b/src/main/java/com/dmtavt/deltamass/utils/ArrayUtils.java new file mode 100644 index 0000000..f266ca4 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/utils/ArrayUtils.java @@ -0,0 +1,88 @@ +package com.dmtavt.deltamass.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.DoublePredicate; +import java.util.stream.Collectors; + +public class ArrayUtils { + private ArrayUtils() {} + + public static void main(String[] args) { + + } + + /** + * Removes zero valued entries from arrays in a given list, based on values in the first + * array. + * @param arrays Values from the first array in this list are used. + * @param leaveFlanking Whether to leave flanking points (that don't match the criteria) around + * each accepted value. Useful when you want to keep zero entries around non-zero values for + * plotting. + * @return A new list with new filtered data arrays. + */ + public static List removeZeros(List arrays, boolean leaveFlanking) { + return remove(arrays, value -> value != 0, leaveFlanking); + } + + /** + * Removes values from arrays in a given list that don't match a predicate. Values from the first + * array are used to compute the set of indicies to be left, the same entries are left for the + * other arrays in the list. + * @param arrays Values from the first array in this list are used. + * @param predicate The criterion for leaving the entries in. + * @param leaveFlanking Whether to leave flanking points (that don't match the criteria) around + * each accepted value. Useful when you want to keep zero entries around non-zero values for + * plotting. + * @return A new list with new filtered data arrays. An empty list if input list is null or empty. + */ + public static List remove(List arrays, DoublePredicate predicate, boolean leaveFlanking) { + if (arrays == null || arrays.isEmpty()) + return Collections.emptyList(); + Set lengths = arrays.stream().map(arr -> arr.length) + .collect(Collectors.toSet()); + if (lengths.size() != 1) + throw new IllegalArgumentException("All arrays must be of the same length"); + + final double[] base = arrays.get(0); + int[] accepted = new int[base.length]; + int ptr = -1; + boolean inside = false; + // first element + if (predicate.test(base[0])) { + accepted[++ptr] = 0; + inside = true; + } + for (int i = 1, cap = base.length; i < cap; i++) { + if (predicate.test(base[i])) { + if (leaveFlanking && !inside) { + // transition to non-zero region + inside = true; + accepted[++ptr] = i - 1; + } + accepted[++ptr] = i; + } else { + if (leaveFlanking && inside) { + // transition to zero region + inside = false; + accepted[++ptr] = i; + } + } + } + + final int acceptedSize = ptr + 1; + + List result = new ArrayList<>(arrays.size()); + for (double[] arr : arrays) { + double[] filtered = new double[acceptedSize]; + for (int i = 0; i < acceptedSize; i++) { + filtered[i] = arr[accepted[i]]; + } + result.add(filtered); + } + + return result; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/utils/GridUtils.java b/src/main/java/com/dmtavt/deltamass/utils/GridUtils.java new file mode 100644 index 0000000..1f8fb6e --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/utils/GridUtils.java @@ -0,0 +1,55 @@ +package com.dmtavt.deltamass.utils; + +public class GridUtils { + private GridUtils() {} + + /** + * Starts at precisely {@code lo} and up to the final segment that includes {@code hi}. + * @param lo Low point to start from. + * @param hi High point to go to. + * @param step Step size. + * @return A new double[]. + */ + public static double[] grid(double lo, double hi, double step) { + if (hi < lo) + throw new IllegalArgumentException("hi < lo"); + if (step <= 0) + throw new IllegalStateException("step <= 0"); + double span = hi-lo; + final long n = 1 + (long)Math.ceil(span / step); + if (n >= Integer.MAX_VALUE) + throw new IllegalStateException("Resulting number of grid elements over Integer.MAX_VALUE"); + final double[] x = new double[(int)n]; + for (int i = 0; i < n; i++) { + x[i] = lo + i * step; + } + return x; + } + + /** + * Create a data grid that has one element matching {@code pivot} exactly. Goes from down from + * pivot to include {@code lo - margin} and up to include {@code hi + margin}. + * @param pivot Stationary point that is guaranteed to be in the output grid. + * @param lo Lowest value to be included. + * @param hi Highest value to be included. + * @param step Step size. + * @param margin Extra margin down from {@code lo} and up from {@code hi}. + * @return A new double[]. + */ + public static double[] grid(double pivot, double lo, double hi, double step, double margin) { + if (hi < pivot || pivot < lo) + throw new IllegalArgumentException("hi < pivot || pivot < lo"); + if (step <= 0) + throw new IllegalStateException("step <= 0"); + final long nLo = (long)Math.ceil((pivot - lo + margin) / step); + final long nHi = (long)Math.ceil((hi - pivot + margin) / step); + final long n = 1 + nLo + nHi; + if (n >= Integer.MAX_VALUE) + throw new IllegalStateException("Resulting number of grid elements over Integer.MAX_VALUE"); + final double[] x = new double[(int)n]; + for (int i = 0; i < n; i++) { + x[i] = pivot + (i - nLo) * step; + } + return x; + } +} diff --git a/src/main/java/com/dmtavt/deltamass/utils/NumUtils.java b/src/main/java/com/dmtavt/deltamass/utils/NumUtils.java new file mode 100644 index 0000000..fde455a --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/utils/NumUtils.java @@ -0,0 +1,9 @@ +package com.dmtavt.deltamass.utils; + +public class NumUtils { + private NumUtils() {} + + public static boolean isGoodDouble(Double v) { + return v != null && !Double.isNaN(v) && Double.isFinite(v); + } +} diff --git a/src/main/java/com/dmtavt/deltamass/utils/PeakUtils.java b/src/main/java/com/dmtavt/deltamass/utils/PeakUtils.java new file mode 100644 index 0000000..25ca279 --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/utils/PeakUtils.java @@ -0,0 +1,297 @@ +package com.dmtavt.deltamass.utils; + +import com.dmtavt.deltamass.utils.PeakUtils.Peak.WIDTH; +import com.dmtavt.deltamass.utils.PeakUtils.PeakApprox.State; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; + +public class PeakUtils { + + private PeakUtils() { + } + + /** + * Finds locations of maxima. Can detect plateaus. + * + * @param data Array with data values to detect peaks in. + * @param from Inclusive. + * @param to Exclusive. + * @param filter Filters out peaks before they are added to the resulting list. + */ + public static List peakLocations(double[] data, int from, int to, + Predicate filter) { + if (from < 0 || from > data.length - 1) { + throw new IllegalArgumentException("'from' must be within data[] index range"); + } + if (to < from) { + throw new IllegalArgumentException("'to' must be >= 'from'"); + } + if (data.length < 3) { + return Collections.emptyList(); + } + + List peaks = new ArrayList<>(Math.min(data.length / 10, 64)); + double diff; + PeakApprox p = null; + State s0 = PeakApprox.State.FLAT, s1; + + int lastFlatStart = -1; + for (int i = from; i < to - 1; + i++) // -1 because to is exclusive and we're comparing to i+1 data point at each step + { + int ip1 = i + 1; + diff = data[ip1] - data[i]; + if (diff > 0) { + s1 = PeakApprox.State.UP; + } else if (diff < 0) { + s1 = PeakApprox.State.DOWN; + } else { + s1 = PeakApprox.State.FLAT; + } + if (s1 == PeakApprox.State.FLAT && s0 != PeakApprox.State.FLAT) { + lastFlatStart = i; + } + + switch (s1) { + case UP: + if (p == null) { + p = peakStart(i); + } else if (p.idxHi != p.idxTopHi) { + peakFinish(data, filter, peaks, p, lastFlatStart); + p = peakStart(i); + } + p.idxTopLo = ip1; + p.idxTopHi = ip1; + p.idxHi = ip1; + break; + + case FLAT: + if (p == null) { + break; + } + if (p.idxTopHi == p.idxHi) { + p.idxTopHi = ip1; + } + p.idxHi = ip1; + break; + + case DOWN: + if (p == null) { + break; + } + p.idxHi = ip1; + break; + } + + s0 = s1; + } + if (p != null) { + peakFinish(data, filter, peaks, p, lastFlatStart); + } + + return peaks; + } + + /** + * Starts a new peak and initializes its {@code idxLo} and {@code valLo} fields. + * + * @param i Current pointer in the data array. + * @return A new peak object. + */ + private static PeakApprox peakStart(int i) { + PeakApprox p = new PeakApprox(); + p.idxLo = i; + + return p; + } + + /** + * Add the peak to a list if it passes certain criteria. + * + * @param data The data array. + * @param filter Options for peak detection. + * @param toAddTo The list to add the peak to if it passes quality checks. + * @param p The peak to be finalized. + * @param lastFlatStart The index where the last flat area started. If it's between {@code + * idxTopHi} and {@code idxHi}, it will be used instead of {@code idxHi} + * @return True, if the peak passed criteria in {@code opts} and was added to {@code toAddTo}. + */ + private static boolean peakFinish(double[] data, Predicate filter, + List toAddTo, PeakApprox p, int lastFlatStart) { + if (lastFlatStart > p.idxTopHi && lastFlatStart < p.idxHi) { + p.idxHi = lastFlatStart; + } + p.valLo = data[p.idxLo]; + + p.valTop = data[p.idxTopLo]; + p.valHi = data[p.idxHi]; + p.numNonZeroPts = p.idxHi - p.idxLo + 1; + if (data[p.idxLo] == 0) { + p.numNonZeroPts--; + } + if (data[p.idxHi] == 0) { + p.numNonZeroPts--; + } + if (!filter.test(p)) { + return false; + } + toAddTo.add(p); + return true; + } + + public static Peak fitPeakByParabola(PeakApprox peakApprox, double[] xVals, double[] yVals, + boolean useYRangeAsHeight) { + + double vertexX; + if (peakApprox.idxTopHi == peakApprox.idxTopLo) { + int idx = peakApprox.idxTopLo; + if (idx > 0 && idx < xVals.length - 1) { + double[] x = new double[3]; + double[] y = new double[3]; + for (int i = -1; i <= 1; i++) { + x[i + 1] = xVals[idx + i]; + y[i + 1] = yVals[idx + i]; + } + double[] parabola = fitParabola(x, y); + vertexX = parabolaVertexX(parabola[2], parabola[1]); + } else { + vertexX = xVals[peakApprox.idxTopLo]; + } + + } else { + int idx = peakApprox.idxTopLo; + if (idx > 0) { + double[] x = new double[3]; + double[] y = new double[3]; + x[0] = xVals[idx - 1]; + y[0] = yVals[idx - 1]; + x[1] = xVals[idx]; + y[1] = yVals[idx]; + x[2] = xVals[peakApprox.idxTopHi]; + y[2] = yVals[peakApprox.idxTopHi]; + + double[] parabola = fitParabola(x, y); + vertexX = parabolaVertexX(parabola[2], parabola[1]); + } else { + vertexX = xVals[peakApprox.idxTopLo]; + } + + } + + double height; + if (useYRangeAsHeight) { + height = peakApprox.valTop - Math.max(peakApprox.valLo, peakApprox.valHi); + } else { + height = peakApprox.valTop; + } + + double width = xVals[peakApprox.idxHi] - xVals[peakApprox.idxLo]; + + Peak p = new Peak(vertexX, height, width, WIDTH.AT_BASE); + return p; + } + + /** + * 3-point fit of a parabola. + * + * @param x the x coordinates. + * @param y the y coordinates. + * @return double[]: [0] - c, [1] - b, [2] - a + */ + public static double[] fitParabola(double[] x, double[] y) { + if (x.length != 3) { + throw new IllegalArgumentException("Length of input data arrays must be 3."); + } + if (x.length != y.length) { + throw new IllegalArgumentException("Arrays x and y must be of the same length: 3."); + } + double a = (y[1] * (x[2] - x[0]) - y[0] * (x[2] - x[1]) - y[2] * (x[1] - x[0])) / ( + Math.pow(x[0], 2) * (x[1] - x[2]) - Math.pow(x[2], 2) * (x[1] - x[0]) + - Math.pow(x[1], 2) * (x[0] - x[2])); + double b = (y[1] - y[0] + a * (Math.pow(x[0], 2) - Math.pow(x[1], 2))) / (x[1] - x[0]); + double c = -1 * a * Math.pow(x[0], 2) - b * x[0] + y[0]; + double[] parabola = new double[3]; + parabola[0] = c; + parabola[1] = b; + parabola[2] = a; + return parabola; + } + + public static double parabolaVertexX(double a, double b) { + return -1 * b / (2.0 * a); + } + + /** + * Approximate peak location in a 1D array of values. Stores the start and finish locations in the + * array as well as the corresponding values at the start, finish and at the top. + * + * @author Dmitry Avtonomov + */ + public static class PeakApprox { + + public int idxLo = -1; + public int idxHi = -1; + public int idxTopLo = -1; + public int idxTopHi = -1; + public double valLo = Double.NEGATIVE_INFINITY; + public double valTop = Double.NEGATIVE_INFINITY; + public double valHi = Double.NEGATIVE_INFINITY; + + public int numNonZeroPts = 0; + public int idxLoNonZero = -1; + public int idxHiNonZero = -1; + + public double mzInterpolated; + + public void flipValSign() { + valLo = -1 * valLo; + valTop = -1 * valTop; + valHi = -1 * valHi; + } + + public double amplitudeLo() { + return Math.min(Math.abs(valTop - valLo), Math.abs(valTop - valHi)); + } + + public double amplitudeHi() { + return Math.max(Math.abs(valTop - valLo), Math.abs(valTop - valHi)); + } + + public double amplitudeMax() { + return Math.max(amplitudeLo(), amplitudeHi()); + } + + enum State {UP, DOWN, FLAT} + } + + public static class PeakDetectionConfig { + + public final double minPeakPct; + public final double minPsmsPerGmm; + + public PeakDetectionConfig(double minPeakPct, double minPsmsPerGmm) { + this.minPeakPct = minPeakPct; + this.minPsmsPerGmm = minPsmsPerGmm; + } + } + + public static class Peak { + + public final double location; + public final double intensity; + public final double width; + public final WIDTH widthType; + + public Peak(double location, double intensity, double width, + WIDTH widthType) { + this.location = location; + this.intensity = intensity; + this.width = width; + this.widthType = widthType; + } + + public enum WIDTH {AT_BASE, FWHM} + } +} diff --git a/src/main/java/com/dmtavt/deltamass/utils/TextUtils.java b/src/main/java/com/dmtavt/deltamass/utils/TextUtils.java new file mode 100644 index 0000000..b4355dd --- /dev/null +++ b/src/main/java/com/dmtavt/deltamass/utils/TextUtils.java @@ -0,0 +1,51 @@ +package com.dmtavt.deltamass.utils; + +public final class TextUtils { + + private TextUtils() { + } + + /** + * Wrap text to fixed column size. + * + * @param out Output will be placed here. + * @param colSize Length to wrap to. + * @param indent Starting indentation of text. + * @param text Text to wrap. + */ + public static void wrap(StringBuilder out, int colSize, int indent, String text) { + String[] words = text.split(" "); + int current = 0; + int i = 0; + while (i < words.length) { + String word = words[i]; + if (word.length() > colSize || current + 1 + word.length() <= colSize) { + out.append(word); + current += word.length(); + if (i != words.length - 1) { + out.append(" "); + current++; + } + } else { + out.append("\n").append(repeat(" ", indent)).append(word).append(" "); + current = indent + 1 + word.length(); + } + i++; + } + } + + /** + * Create a string of N spaces. + * + * @param count number of space characters. + * @return A new string with spaces. + */ + public static String repeat(String str, int count) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) { + sb.append(str); + } + + return sb.toString(); + } +} diff --git a/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_128.png b/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_128.png new file mode 100644 index 0000000000000000000000000000000000000000..4028b9c9502b0e16f51b0429f966d889d2cf7717 GIT binary patch literal 5978 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_32.png b/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_32.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6f3df3c60f7cb04f2c3bd9bfe52896cbca3d0f GIT binary patch literal 2161 zcmbVOc~}#78jgr9AfOaPuIdmER3vvMM}sjpR1!`F3o1DVl1ef$2_zh@2w@RH@Bl4~ zwq7fBDOW`i4^XKnaus+`uA++qmDZxj0-|6iBJTd-(?52enfV>_zVCPZzVCS^BS0ju zv2w9Op-?uyKHMN=1Wb>`C&*iykT8Y}&N^P0E*Or|>BSld#gf4i2=G;kqoE*3EQ{Y& z4>3@v`B93HFkP6?k1mCk7_kY1F(}mt8--#p4QjD;C!_-;P_#njjUKsLg9a2bZ*(YG zh!d(gkX+%Dpn-xDL?P0Iol=?%&D;Pm40J?53F*XuK^ddc(hc6|ce-?BZraA8fp-wy zPH*(AQ(?jYfCFnFfQ%t}NpW~Q0D>4ii421H^#B2fCtz{N4SL~8bTUXMP=NOr8nLF4 zMbU$}{P(tyl{Z?h)2ZoLtX{9j=!qCu6OF~wXf%@sf#8K8ytMHuo!H=|(yo|O;6hrd zMxoXzU=?6e6ieV(oi`fsbT$N~S}6P=tkS+u6p}KmL9E8&F*vMJX^QI|TB{3!{_Dm^ z(b|xBHG~a^T;#`nM%Xa zNGuA4zz50HIj*lts}rlF(41WbVt1BH<^Ls@&e1?(9jpm~;h4D!2#~`%SSyFs04Ep( z)`TilGFY!&YdW6UXt|I^5eLcm8dwRu%P(E=5&IyIh9~1lBobvdZW^CL;^EkA4jJUJ zDQq74Jy-Vs4(xs79W}iQXw6qL0aRNJ)hr0q0CSDa#~iV>EjPUs9gkB{N@~-=1>hnAZc3#lZOK31js{VLz#G=Ewm0`KlCw5&KCxXy4E_rK?~+3$kA$ zaL|lp4U6)#&eUAWHAmao5IuWr>b6OPONv5-9M7b(5c594*`TWVN!5<)?l2O!mGqA# zF7f@r3Y>@U&wFZcY2SEk5R=Q`KD(F|THoY&-|F8BryVNUk=x}sk#qJ{xmiy=#;p5fU`|bv(KV3vQXZA&pEv*MWMSbjN50^x#M1AJK4Hr)T+edp z5vTsecRN-a7wuYhC?i_|958SnP9(rPK8^nNN|ocIn_WwXzAh5Do&C{eY4YScfXCG|ut@b?xEeg8bXQ?y+s@neN`?1#u5O$8)My7~h5z{$BbjNsKXT z7o2a=FRM6ueWJ9%D2$&eJGyNotvhvj3X56y>y{#$Y!KwMpC<+YjmEQ1)eogMb)$hp zAxp2W?VX-UTWm;q94Y#>y_nseo|`wdVx(@ll{|)i#?ui_HvW|7_e*Q3KgYl3UPuL} zpGl~F+*x(nvoxYzJ@_~KT|-IcwrWYWbHul9lBv#4r>MfF;-#$IaEWR=D(D)uNpaf8?s%Q$hdui#>o{V@5FuE*e*NV&f`fpYFXF}8Pd^)&gY%Lp;t z_Dlk$|6ci<-m%X!EPp?L#rBlUk|9XS0OhB`iD@^F^Bv;1{pV0#pq-?-K5i2fDM7Q9 zdT#zUjMk9}ChqpjN(OhQcM3(M!|hC~ET{6B{h6Wm<$ScN_V&`GwrV28o<*5*NgDY>?fTKV8L!~N0eykMu7^We?-b@8|KOb>if z#A$)u*2>IDRC$g~Omkeoa3$tt!}_$aNVE0hyHC1$#8)?c*LZY5IOWW~^ZH>;_FKD1 YRLQ{~!`_@qE-?KB`0_;D5_V+DpRE>g82|tP literal 0 HcmV?d00001 diff --git a/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_48.png b/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_48.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4aab4e996fe8ee067382903f6014a960ef0c49 GIT binary patch literal 2751 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 AO#lD@ literal 0 HcmV?d00001 diff --git a/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_96.png b/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_96.png new file mode 100644 index 0000000000000000000000000000000000000000..74a6bf5513692095e6bf33ff6a4fe8ebd63736d2 GIT binary patch literal 4680 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_splash.png b/src/main/resources/com/dmtavt/deltamass/ui/delta_logo_splash.png new file mode 100644 index 0000000000000000000000000000000000000000..b175a2b67ffd3c320dc72c4e951e8b9facba012a GIT binary patch literal 31653 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 JPDHLkV1m4)3gQ3& literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/delta_logo_128.png b/src/main/resources/icons/delta_logo_128.png new file mode 100644 index 0000000000000000000000000000000000000000..4028b9c9502b0e16f51b0429f966d889d2cf7717 GIT binary patch literal 5978 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/delta_logo_32.png b/src/main/resources/icons/delta_logo_32.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6f3df3c60f7cb04f2c3bd9bfe52896cbca3d0f GIT binary patch literal 2161 zcmbVOc~}#78jgr9AfOaPuIdmER3vvMM}sjpR1!`F3o1DVl1ef$2_zh@2w@RH@Bl4~ zwq7fBDOW`i4^XKnaus+`uA++qmDZxj0-|6iBJTd-(?52enfV>_zVCPZzVCS^BS0ju zv2w9Op-?uyKHMN=1Wb>`C&*iykT8Y}&N^P0E*Or|>BSld#gf4i2=G;kqoE*3EQ{Y& z4>3@v`B93HFkP6?k1mCk7_kY1F(}mt8--#p4QjD;C!_-;P_#njjUKsLg9a2bZ*(YG zh!d(gkX+%Dpn-xDL?P0Iol=?%&D;Pm40J?53F*XuK^ddc(hc6|ce-?BZraA8fp-wy zPH*(AQ(?jYfCFnFfQ%t}NpW~Q0D>4ii421H^#B2fCtz{N4SL~8bTUXMP=NOr8nLF4 zMbU$}{P(tyl{Z?h)2ZoLtX{9j=!qCu6OF~wXf%@sf#8K8ytMHuo!H=|(yo|O;6hrd zMxoXzU=?6e6ieV(oi`fsbT$N~S}6P=tkS+u6p}KmL9E8&F*vMJX^QI|TB{3!{_Dm^ z(b|xBHG~a^T;#`nM%Xa zNGuA4zz50HIj*lts}rlF(41WbVt1BH<^Ls@&e1?(9jpm~;h4D!2#~`%SSyFs04Ep( z)`TilGFY!&YdW6UXt|I^5eLcm8dwRu%P(E=5&IyIh9~1lBobvdZW^CL;^EkA4jJUJ zDQq74Jy-Vs4(xs79W}iQXw6qL0aRNJ)hr0q0CSDa#~iV>EjPUs9gkB{N@~-=1>hnAZc3#lZOK31js{VLz#G=Ewm0`KlCw5&KCxXy4E_rK?~+3$kA$ zaL|lp4U6)#&eUAWHAmao5IuWr>b6OPONv5-9M7b(5c594*`TWVN!5<)?l2O!mGqA# zF7f@r3Y>@U&wFZcY2SEk5R=Q`KD(F|THoY&-|F8BryVNUk=x}sk#qJ{xmiy=#;p5fU`|bv(KV3vQXZA&pEv*MWMSbjN50^x#M1AJK4Hr)T+edp z5vTsecRN-a7wuYhC?i_|958SnP9(rPK8^nNN|ocIn_WwXzAh5Do&C{eY4YScfXCG|ut@b?xEeg8bXQ?y+s@neN`?1#u5O$8)My7~h5z{$BbjNsKXT z7o2a=FRM6ueWJ9%D2$&eJGyNotvhvj3X56y>y{#$Y!KwMpC<+YjmEQ1)eogMb)$hp zAxp2W?VX-UTWm;q94Y#>y_nseo|`wdVx(@ll{|)i#?ui_HvW|7_e*Q3KgYl3UPuL} zpGl~F+*x(nvoxYzJ@_~KT|-IcwrWYWbHul9lBv#4r>MfF;-#$IaEWR=D(D)uNpaf8?s%Q$hdui#>o{V@5FuE*e*NV&f`fpYFXF}8Pd^)&gY%Lp;t z_Dlk$|6ci<-m%X!EPp?L#rBlUk|9XS0OhB`iD@^F^Bv;1{pV0#pq-?-K5i2fDM7Q9 zdT#zUjMk9}ChqpjN(OhQcM3(M!|hC~ET{6B{h6Wm<$ScN_V&`GwrV28o<*5*NgDY>?fTKV8L!~N0eykMu7^We?-b@8|KOb>if z#A$)u*2>IDRC$g~Omkeoa3$tt!}_$aNVE0hyHC1$#8)?c*LZY5IOWW~^ZH>;_FKD1 YRLQ{~!`_@qE-?KB`0_;D5_V+DpRE>g82|tP literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/delta_logo_48.png b/src/main/resources/icons/delta_logo_48.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4aab4e996fe8ee067382903f6014a960ef0c49 GIT binary patch literal 2751 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 AO#lD@ literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/delta_logo_96.png b/src/main/resources/icons/delta_logo_96.png new file mode 100644 index 0000000000000000000000000000000000000000..74a6bf5513692095e6bf33ff6a4fe8ebd63736d2 GIT binary patch literal 4680 zcmbVQc|26@+doVUiO^J(n2_va9ZY6yV|^l9LM6it2D30DTQreuSrf@l$gV8elYL1G zp~#jsged#+o1W+CdEY;t_xHzp?sLw)obUJA?&~_A^Esi$M!M{WPaOsTfL&h?ZA$NL ze_brh^!t}bp33x&m#S?^H6uAtJu&V$Km$v%!$I{47)P8b4ukb}>%^%70LTGvZb`K? zyoj(T5u`D{Fw$NGGMxzf}6zSp>Vjgtb!sOE-ML@laZB!$jy57IJD*u&>&~nFNs3doD z63O*<1{gb$s3eLLi44^;gF{8H;E7n0Cq?Yn_Wb278t0C`iNor+lL*j1N(p&=dBLO0rb`hbO0T~pm)M~R@bSIkAN4ptHBtnVC~k; z(rcxKP29fc508R!EP!+t4p%*a9wbGzcEftK-B)sdKprr*Rq01=>ZLG7Lu4{K^U^zO zxnoh{(tY3wl4EG5%v!M)Lv;s5{aY`$``FXAZTP9=p5p(Zdq zMH#lQ#--f$O!0$vu`MXyL80Opp7K}QeQK=QUu~<}>|xxy*laQ`0UB41Io@n8kThAc zV}D%w-5{J7XzvIv2{&183IBQq>vY zpgAh&UP{~J7j5$qS+&&Lc+ZomIYZnJAseAU`5w4;zcJOR``SFLGEq?URpld z8AF!(Q8uM}x$QR7X8eTcoG{2XvpC=eRybZjSvBm6eH^HCUn~9P1`fE0_Ni4&C@|h( zO}>CnjNAA5W1N*<2X}d1Ajgee^4nGLG={PEn0pznNjY4?agGf ziOjP+2IvW*=s-PqJ>mFRz+xuUX6^pfp~0~3F7$4A)MIYot-bd(8VTIuz)TZEVgJb6 zDj(k$%RL&UarXS-PLr9HpDiXUx6FX_i1ER+z^3!Z*OFU)9+C+v#J|_+nSc7E?pCLE zg-=At`ADefsSAK`$V+l{7kjtyUg@e&d?4uscdwkSffhCZbx!g*+kie_1Bb1_ZG1U! zzi~Am7{gGjeC)nSd5^pp9(=f;qrDNHjJk0kqG*Vfw!D)yzSMS@7BVlJ-gBVR>dd1` zV%xuZ+&Z&vm(yZa*;RPAKZ1+4Wy#Mq?h7XDZ{1%=T3Jvd&5m_y?WZbj!D_v{a~tI) zTTI9|>=_%+TR~wK zJto2}Lw{6MXMofx#SnHGpf z-7?Q$HOsbXtl|%9D$$RN)iF9d=~Iw55yNkJWAaBINcR;RI}^zFAiu;agVXVcz^v?Kk42gPYy5 zG2Y+rywh<*4CHemGWh`uOI||bKp--s!&qcXlwMGgsgs|Ov1_vBAhUELfxv2@G0W7y zioy(`&q!gj(YeN!J~cmsc%vfkaz$-^8cG*%4b02ZX&gU`dL3EPe!i(KeQ&Jy35xJE zZMh@nsFtfgd@%&o?7Go%qo(f1L?c5QIW?)5|H93qD7$M{pDv*=-8$cD0B9VSPN^|} z3r>p3urqhPVK>jLt$*|E8*Q;y&CO>J)WVO@Y9Rz)T;kV@E=QQGU6->;;eT+^23

9&vKd8aEdcOfqFk>vscZ(^U-WVm#OA9JJZ;-QPSPv zKuea5lU32{wC_Tt@T9gVhAV?J9uDyy#Ba>@^>vA=-?blKQFlGo9_)DvfT&NBLf#nn z33e5%n_n8+AN3H3Tb&0Hi=`|jQAhcasiK^jUZ-?lfjq;Gqb=I`7#jMW*4~!o`gDB3 zykAbRo6xfq7M_|3aNJp!@mzm!SrQrpXPbE)a6-9>(PtwNc`{3Q_;_4=LrjUp^}fn# zHWHd7`_i`_1dR>l3%7`3UI;FNMdO;eX9jmTG+V5)4PvrPRPY80p&KqcGuY{y(avV3 znSsk3e8s&Yui9mrRvVgYd^EAng2Gd?2KPjn@9}vy!BmqZ`P(j-r1p{}9p}xM>Vt6U z(LFbQK4lO>ek_vcD^y@T2r!6wYoJD*o)? zl{VTV{ub6(=jU~)PEt_m64t1|MCZC8!wTn-SU;vKWn$otnYl-fIhSHxA^htFR+~!2 zU(&+6!PU#)le1QZ@Y=wJ;Fgb5SZ#y-)-D30h{eeAY=1Fwv)a=UNeNGM%yKdfo2@&Y zXkj+b26T10O|N`bPf`om<@4o8h&!eK$7K!U71&nThk!#_&{+jhUk;t_;XDyo8 zLpSlVl^b6w5_@aalqDXCsjnS)B2BPT_7p#0rJUBxXl$`Boqy-Ww>*B|(PHh0#hve> zqVD0BKknOSL9@(G*e9urc|3fO0@et=1i8#82^xNlsdzsHp`pZ56G3S$g?Zxv#*o{qgq`k-g?oeP4ER>5+0m?FXcDCQ0l!(x7R z#0NiOAm4*{7K9)31V&JIx?s~IClF=sKMU3N2VsO=1~<1&RIU@^Ag4} z4@)_QS1w<&crPYodz^49dx>&->RJ!RX=(THf?_1j;cb(%z%1b_wfOb{FJN3s$OFc# zsWq-P_ZQtX`qywcMZx|ygP)IpaHGUG1j?!c&(PSnI}d&cP8~25DOlvRIvuktvu2hqUzApA`uTu!grctIB)VHRlIs7t<|L5m9t|t=9=x5U(tH`gq@4MdcG= zXSytwP>ffA%x1EB%TCrO1&W#1rx)u0-nF}Hr^CvV8f|M&T+DB;cR8thIqJ!&X9Yf) zX1l=~OVcn90kQen2?qa^n&+0=J_4wCZHiKE!PfehYZVNQ_0USrXJCSV*Q{qHhtd&m zr*xtG_GA4I?ep8wYrD+sPX}6NhVwz%&K{a6+vYP)0`8x9PU-%!tnus(arHwzVPx?tPrFg`vj;IJWn0nu;HEddI)2SxG2#jp1yKA3CIt{eooe`jKk-O5!=fDes-;58v67 zzTdMiS!=SNj>yjqxS1RlCYyhX?Y;QmW2p-oyvJNv73=I_@W72BqqRVm_Y^)=2;;EY zP8&}ZsLx%zJDKVyD}JxC+-96=XwjHew-YX@FMTMc6+EQ*IKu&)Q=a{doDeQMS0egh zMxE$ZlR16@r|qgf0Uln-Vm+L_Jw5)#HTeT%OX+gh{U*q}_~}RWBLz2?wFdfndY99) z*_6y(Z$$=N{iJ+goc8KtE)LSm<@8ps!uL}_J6kvZ+#YYPFG4&c%5Tuh)$oyAzv%7f zOtU%*E7|80L1w1qCvVmEkcW7m^%nLvE`9;)ACA2@_a@}c3~VGen-5DgYdJG|LCAsS z9?P7HUP=&RWhq0S;g~4+;fwd=oUOzHrbZOyb+EP@8TIqDs8T7nBivPl7f4X)BFjD3w%&d}?CWuzsIw(@o5{x{KEGZQ{3!{tLfTW=G) zUvKm&LB1}qGiwt=mnf4?`49y{4q0w$fhw&;MOe2<%mCxWb@ob?FRV(SXUEuH25c^d zCU>eXy>8-7mGtw;< z{#Yfay-k6HF`wVH&Z#{o39kj-p}W*1E6iVVD4Zj?Rry=bEGwPFK7W1wn>LWHAN;~> zSK->lmuXo$<9g!{u-aao&tFO{RHy=TdVqg%ih&y81Pqp^7LC-%IybHIj zV9k~Th|tOiDiU!`wIcS^_E6art*T4zMaJ7j26ZDy;?+7HA|w5qMQ0s1ikZ1P;-s>V~=wi2$}2XVY>HFEw6AE2$MFzA)LmY2hLe-DvBVS zqV2TA#Uw)3O+PsVe0?_q6SorVE@>-o89H6bObc)r(5SdEVd?`bdnj!76R-ABrL#3v zsPd?|0DH2Hx^Gcx!t@oP%*EQ)%nTO6zOZP9o9Ye|x2*gXMPePF3jx>eB-Lz8Si_6Z zKcnfuZFZg*T$X?>OX+oe^TNjv#vD=puTs_p9`U%ZG9%!eY(xA3Z5K$KkD5B@y~eE0 otS-U}@B)cgfZp=K+b;nSpwsq{oZL%U{Pk}~U)u;>tZ5tYAFsPp7XSbN literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/delta_logo_splash.png b/src/main/resources/icons/delta_logo_splash.png new file mode 100644 index 0000000000000000000000000000000000000000..b175a2b67ffd3c320dc72c4e951e8b9facba012a GIT binary patch literal 31653 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 JPDHLkV1m4)3gQ3& literal 0 HcmV?d00001 diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..4973127 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/oldMain/java/smile/stat/distribution/ComparableHolder.java b/src/oldMain/java/smile/stat/distribution/ComparableHolder.java new file mode 100644 index 0000000..c24b38e --- /dev/null +++ b/src/oldMain/java/smile/stat/distribution/ComparableHolder.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smile.stat.distribution; + +/** + * @author Dmitry Avtonomov + */ +public class ComparableHolder, V> implements + Comparable> { + + public final K key; + public final V val; + + + public ComparableHolder(K key, V val) { + this.key = key; + this.val = val; + } + + @Override + public int compareTo(ComparableHolder o) { + return key.compareTo(o.key); + } +} diff --git a/src/oldMain/java/smile/stat/distribution/DataProvider.java b/src/oldMain/java/smile/stat/distribution/DataProvider.java new file mode 100644 index 0000000..e60e8ef --- /dev/null +++ b/src/oldMain/java/smile/stat/distribution/DataProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smile.stat.distribution; + +/** + * @author Dmitry Avtonomov + */ +public interface DataProvider { + + double get(int i); + + /** + * Size of the data in this provider. + * + * @return The total number of elements. + */ + int size(); +} diff --git a/src/oldMain/java/smile/stat/distribution/GaussMixtureWeighted.java b/src/oldMain/java/smile/stat/distribution/GaussMixtureWeighted.java new file mode 100644 index 0000000..0b8a1de --- /dev/null +++ b/src/oldMain/java/smile/stat/distribution/GaussMixtureWeighted.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smile.stat.distribution; + +import java.util.ArrayList; +import java.util.List; +import umich.opensearch.kde.util.Peak; + +/** + * @author Dmitry Avtonomov + */ +public class GaussMixtureWeighted extends ExponentialFamilyMixture { + + private static final double LOG2 = java.lang.Math.log(2); + WeightProvider w = null; + int wSum = 0; + double gamma; + + /** + * Constructor. The Gaussian mixture model will be learned from the given data with the EM + * algorithm. The number of components will be selected by BIC. + * + * @param data the training data. + * @param gamma regularization parameter for EM. Should be in range [0.0-0.2]. + * @param maxIterations Max number of EM iterations. Set to Integer.MAX_VALUE if unsure. + */ + @SuppressWarnings("unchecked") + public GaussMixtureWeighted(final double[] data, WeightProvider w, double gamma, + int maxIterations) { + int wSum = 0; + if (w != null) { + for (int i = 0; i < data.length; i++) { + wSum += w.get(i); + } + this.wSum = wSum; + } else { + this.wSum = data.length; + } + + if (wSum < 20) { + throw new IllegalArgumentException("Too few samples."); + } + + this.w = w == null ? new WeightProvider() { + @Override + public int get(int i) { + return 1; + } + } : w; + this.gamma = gamma; + + ArrayList mixture = new ArrayList<>(); + Component c = new Component(); + c.priori = 1.0; + DataProvider dp = new DataProvider() { + @Override + public double get(int i) { + return data[i]; + } + + @Override + public int size() { + return data.length; + } + }; + NormalDist.MuSigma muSigma = NormalDist.estimateGaussianParams(dp, w); + c.distribution = new NormalDist(muSigma.mu, muSigma.sigma); + mixture.add(c); + + int freedom = 0; + for (Component aMixture : mixture) { + freedom += aMixture.distribution.npara(); + } + + double bic = 0.0; + for (int i = 0; i < data.length; i++) { + double xi = data[i]; + double wi = w == null ? 1 : w.get(i); + if (wi <= 0) { + continue; + } + double p = c.distribution.p(xi); + if (p > 0) { + bic += Math.log(p) * wi; + } + } + bic -= 0.5 * freedom * Math.log(wSum); + + double b = Double.NEGATIVE_INFINITY; + + while (bic > b) { + b = bic; + components = (ArrayList) mixture.clone(); + + split(mixture); + bic = EM(mixture, data, gamma, maxIterations); + + freedom = 0; + for (int i = 0; i < mixture.size(); i++) { + freedom += mixture.get(i).distribution.npara(); + } + bic -= 0.5 * freedom * Math.log(wSum); + } + } + + + /** + * Optimize an exising GMM using EM algorithm. + * + * @param initMix The initial GMM to be optimized. + * @param doSingleEMround If true, will only run EM once, thus no new components will be added to + * GMM, but only the existing ones' means and sigmas updated. + * @param gamma Regularization parameter. Should generally be zero, unless runtimes are too slow + * for you. + * @param maxIterations Max number of iterations of EM till convergence. Reasonable number is + * 100-1000. + */ + @SuppressWarnings("unchecked") + public GaussMixtureWeighted(ArrayList initMix, boolean doSingleEMround, + double[] data, WeightProvider w, double gamma, int maxIterations) { + int wSum = 0; + if (w != null) { + for (int i = 0; i < data.length; i++) { + wSum += w.get(i); + } + this.wSum = wSum; + } else { + this.wSum = data.length; + } + + if (wSum < 20) { + throw new IllegalArgumentException("Too few samples."); + } + + this.w = w == null ? new WeightProvider() { + @Override + public int get(int i) { + return 1; + } + } : w; + this.gamma = gamma; + + ArrayList mixture = (ArrayList) initMix.clone(); + components = mixture; + + double sumCompWeights = 0; + for (Component component : mixture) { + sumCompWeights += component.priori; + } + for (Component component : mixture) { + component.priori /= sumCompWeights; + } + int freedom = 0; + for (Component aMixture : mixture) { + freedom += aMixture.distribution.npara(); + } + + double bic = EM(mixture, data, gamma, maxIterations); + double b = Double.NEGATIVE_INFINITY; + + if (!doSingleEMround) { + while (bic > b) { + b = bic; + components = (ArrayList) mixture.clone(); + + split(mixture); + bic = EM(mixture, data, gamma, maxIterations); + + freedom = 0; + for (int i = 0; i < mixture.size(); i++) { + freedom += mixture.get(i).distribution.npara(); + } + bic -= 0.5 * freedom * Math.log(wSum); + } + } + } + + private static double log2(double x) { + return java.lang.Math.log(x) / LOG2; + } + + /** + * Split the most heterogeneous cluster along its main direction (eigenvector). + */ + private void split(List mixture) { + // Find most dispersive cluster (biggest sigma) + Component componentToSplit = null; + + double maxSigma = 0.0; + for (Component c : mixture) { + if (c.distribution.sd() > maxSigma) { + maxSigma = c.distribution.sd(); + componentToSplit = c; + } + } + + // Splits the component + if (componentToSplit == null) { + return; + } + double delta = componentToSplit.distribution.sd(); + double mu = componentToSplit.distribution.mean(); + Peak p = null; + if (componentToSplit instanceof MixtureComponentPeak) { + MixtureComponentPeak mc = (MixtureComponentPeak) componentToSplit; + p = mc.peak; + } + + MixtureComponentPeak c = new MixtureComponentPeak(p); + c.priori = componentToSplit.priori / 2; + c.distribution = new NormalDist(mu + delta / 2, delta); + mixture.add(c); + + c = new MixtureComponentPeak(p); + c.priori = componentToSplit.priori / 2; + c.distribution = new NormalDist(mu - delta / 2, delta); + mixture.add(c); + + mixture.remove(componentToSplit); + } + + @Override + double EM(List components, double[] x, double gamma, int maxIter) { + if (wSum < components.size() / 2) { + throw new IllegalArgumentException("Too many components"); + } + + if (gamma < 0.0 || gamma > 0.2) { + throw new IllegalArgumentException("Invalid regularization factor gamma."); + } + + int n = x.length; + int m = components.size(); + + double[][] posteriori = new double[m][n]; + + // Log Likelihood + double L = 0.0; + for (int i = 0; i < x.length; i++) { + double xi = x[i]; + double wi = w == null ? 1 : w.get(i); + double p = 0.0; + for (Component c : components) { + p += c.priori * c.distribution.p(xi); + } + if (p > 0) { + L += Math.log(p) * wi; + } + } + + // EM loop until convergence + int iter = 0; + for (; iter < maxIter; iter++) { + + // Expectation step + for (int i = 0; i < m; i++) { + Component c = components.get(i); + + for (int j = 0; j < n; j++) { + posteriori[i][j] = c.priori * c.distribution.p(x[j]); + } + } + + // Normalize posteriori probability. + for (int j = 0; j < n; j++) { + double p = 0.0; + + for (int i = 0; i < m; i++) { + p += posteriori[i][j]; + } + + for (int i = 0; i < m; i++) { + posteriori[i][j] /= p; + } + + // Adjust posterior probabilites based on Regularized EM algorithm. + if (gamma > 0) { + for (int i = 0; i < m; i++) { + posteriori[i][j] *= (1 + gamma * log2(posteriori[i][j])); + if (Double.isNaN(posteriori[i][j]) || posteriori[i][j] < 0.0) { + posteriori[i][j] = 0.0; + } + } + } + } + + // Maximization step + ArrayList newConfig = new ArrayList<>(); + for (int i = 0; i < m; i++) { + final Component c = components.get(i); + MixtureComponentPeak mc = null; + if (c instanceof MixtureComponentPeak) { + mc = (MixtureComponentPeak) c; + } + Distribution distribution = c.distribution; + if (w == null) { + final Component m1 = ((ExponentialFamily) distribution).M(x, posteriori[i]); + newConfig.add(mc == null ? m1 : MixtureComponentPeak.from(m1, mc.peak)); + } else if (distribution instanceof NormalDist) { + NormalDist normalDist = (NormalDist) distribution; + final Component m1 = normalDist.M(x, w, posteriori[i]); + newConfig.add(mc == null ? m1 : MixtureComponentPeak.from(m1, mc.peak)); + } else { + throw new IllegalStateException( + "Only NormalDist supported when weight are provided to GaussMixtureWeighted"); + } + } + + double sumAlpha = 0.0; + for (int i = 0; i < m; i++) { + sumAlpha += newConfig.get(i).priori; + } + + for (int i = 0; i < m; i++) { + newConfig.get(i).priori /= sumAlpha; + } + + double newL = 0.0; + for (int i = 0; i < x.length; i++) { + double xi = x[i]; + double wi = w == null ? 1 : w.get(i); + double p = 0.0; + for (Component c : newConfig) { + p += c.priori * c.distribution.p(xi); + } + if (p > 0) { + newL += Math.log(p) * wi; + } + } + + if (newL > L) { + L = newL; + components.clear(); + components.addAll(newConfig); + } else { + break; + } + } + + return L; + } +} diff --git a/src/oldMain/java/smile/stat/distribution/MixtureComponentPeak.java b/src/oldMain/java/smile/stat/distribution/MixtureComponentPeak.java new file mode 100644 index 0000000..66c2bb0 --- /dev/null +++ b/src/oldMain/java/smile/stat/distribution/MixtureComponentPeak.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smile.stat.distribution; + +import umich.opensearch.kde.util.Peak; + +/** + * @author Dmitry Avtonomov + */ +public class MixtureComponentPeak extends Mixture.Component { + + public final Peak peak; + + public MixtureComponentPeak(Peak peak) { + this.peak = peak; + } + + public static MixtureComponentPeak from(Mixture.Component mc, Peak peak) { + final MixtureComponentPeak out = new MixtureComponentPeak(peak); + out.distribution = mc.distribution; + out.priori = mc.priori; + return out; + } +} diff --git a/src/oldMain/java/smile/stat/distribution/NormalDist.java b/src/oldMain/java/smile/stat/distribution/NormalDist.java new file mode 100644 index 0000000..b75b3f5 --- /dev/null +++ b/src/oldMain/java/smile/stat/distribution/NormalDist.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smile.stat.distribution; + +//import smile.math.Math; + +/** + * @author Dmitry Avtonomov + */ +public class NormalDist extends GaussianDistribution { + + + public NormalDist(double mu, double sigma) { + super(mu, sigma); + } + + public NormalDist(double[] data) { + super(data); + } + + public static MuSigma estimateGaussianParams(DataProvider x, WeightProvider w) { + double mu = 0; + double wSum = 0; + for (int i = 0; i < x.size(); i++) { + mu += x.get(i) * w.get(i); + wSum += w.get(i); + } + mu = mu / wSum; + + double sigmaSq = 0; + for (int i = 0; i < x.size(); i++) { + double d = x.get(i) - mu; + sigmaSq += d * d * w.get(i); + } + sigmaSq = sigmaSq / wSum; + double sigma = Math.sqrt(sigmaSq); + + return new MuSigma(mu, sigma); + } + + public Mixture.Component M(double[] x, WeightProvider w, double[] posteriori) { + double alpha = 0.0; + double mean = 0.0; + double sd = 0.0; + + for (int i = 0; i < x.length; i++) { + double wi = w == null ? 1 : w.get(i); + alpha += posteriori[i] * wi; + mean += x[i] * posteriori[i] * wi; + } + + mean /= alpha; + + for (int i = 0; i < x.length; i++) { + double wi = w == null ? 1 : w.get(i); + double d = x[i] - mean; + sd += (d * d * posteriori[i]) * wi; + } + + sd = Math.sqrt(sd / alpha); + + Mixture.Component c = new Mixture.Component(); + c.priori = alpha; + c.distribution = new NormalDist(mean, sd); + + return c; + } + + @Override + public String toString() { + return String.format("NormalDist(%.4f, %.4f)", mean(), sd()); + } + + public static class MuSigma { + + public final double mu; + public final double sigma; + + private MuSigma(double mu, double sigma) { + this.mu = mu; + this.sigma = sigma; + } + } +} diff --git a/src/oldMain/java/smile/stat/distribution/WeightProvider.java b/src/oldMain/java/smile/stat/distribution/WeightProvider.java new file mode 100644 index 0000000..2384300 --- /dev/null +++ b/src/oldMain/java/smile/stat/distribution/WeightProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package smile.stat.distribution; + +/** + * @author Dmitry Avtonomov + */ +public interface WeightProvider { + + /** + * Get weight for a particular indexed entry. + * + * @param i Index of the entry. + * @return The weight of the entry. + */ + int get(int i); +} diff --git a/src/oldMain/java/umich/opensearch/kde/BinReader.java b/src/oldMain/java/umich/opensearch/kde/BinReader.java new file mode 100644 index 0000000..ba3f76a --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/BinReader.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.regex.Pattern; + +/** + * @author Dmitry Avtonomov + */ +public class BinReader { + + public static double[][] readData(Path path, boolean isBinary, OpenSearchParams params) { + boolean dataHasWeights = params.getColNumWeight() != null; + if (!dataHasWeights) { + if (isBinary) { + int numDataPoints = (int) (path.toFile().length() / 8); + double[] result = new double[numDataPoints]; + try (DataInputStream dis = new DataInputStream( + new BufferedInputStream(new FileInputStream(path.toFile())))) { + double val; + int i = 0; + while (true) { + val = dis.readDouble(); + result[i] = val; + i++; + } + } catch (EOFException e) { + // simply reached the end of file, all fine + } catch (IOException e) { + e.printStackTrace(); + } + return new double[][]{result}; + } else { + final int colNumData = params.getColNumData(); + ArrayList listVals = new ArrayList<>((int) 1e6); + try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) { + double val; + String line; + String[] split; + Pattern delimiterRegEx = Pattern.compile(params.getDelimiterRegEx()); + if (params.isHeaderRow()) { + reader.readLine(); + } + while ((line = reader.readLine()) != null) { + split = delimiterRegEx.split(line); + val = Double.parseDouble(split[colNumData]); + listVals.add(val); + } + } catch (IOException e) { + e.printStackTrace(); + } + double[] result = new double[listVals.size()]; + for (int i = 0; i < listVals.size(); i++) { + result[i] = listVals.get(i); + } + return new double[][]{result}; + } + } else { + if (isBinary) { + int numDataPoints = (int) ((path.toFile().length() / 8) / 2); + double[] result = new double[numDataPoints]; + double[] weights = new double[numDataPoints]; + try (DataInputStream dis = new DataInputStream( + new BufferedInputStream(new FileInputStream(path.toFile())))) { + double val; + int i = 0; + while (true) { + val = dis.readDouble(); + result[i] = val; + val = dis.readDouble(); + weights[i] = val; + i++; + } + } catch (EOFException e) { + // simply reached the end of file, all fine + } catch (IOException e) { + e.printStackTrace(); + } + return new double[][]{result}; + } else { + ArrayList listVals = new ArrayList<>((int) 1e6); + ArrayList listWeights = new ArrayList<>((int) 1e6); + final int colNumData = params.colNumData; + final int colNumWeight = params.colNumWeight; + try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) { + double val; + String line; + String[] split; + Pattern delimiterRegEx = Pattern.compile(params.getDelimiterRegEx()); + if (params.isHeaderRow()) { + reader.readLine(); + } + while ((line = reader.readLine()) != null) { + split = delimiterRegEx.split(line); + val = Double.parseDouble(split[colNumData]); + listVals.add(val); + val = Double.parseDouble(split[colNumWeight]); + listWeights.add(val); + } + } catch (IOException e) { + e.printStackTrace(); + } + if (listVals.size() != listWeights.size()) { + throw new IllegalStateException("Resulting lists must be of the same size"); + } + double[] vals = new double[listVals.size()]; + double[] weights = new double[listWeights.size()]; + for (int i = 0; i < listWeights.size(); i++) { + vals[i] = listVals.get(i); + weights[i] = listWeights.get(i); + } + return new double[][]{vals, weights}; + } + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/KDE.java b/src/oldMain/java/umich/opensearch/kde/KDE.java new file mode 100644 index 0000000..a4db2e8 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/KDE.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde; + +import umich.opensearch.kde.api.IWeightedValues; + +/** + * Use this for every unit region for which KDE is estimated separately. + */ +public class KDE { + + private final IWeightedValues data; + + public KDE(IWeightedValues data) { + this.data = data; + } + + +} diff --git a/src/oldMain/java/umich/opensearch/kde/KDEBin.java b/src/oldMain/java/umich/opensearch/kde/KDEBin.java new file mode 100644 index 0000000..10be8bf --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/KDEBin.java @@ -0,0 +1,432 @@ +package umich.opensearch.kde; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Dmitry Avtonomov + */ +public class KDEBin { + + public static final String PEAKS_DATASET_NAME = "Detected Peaks"; + private static final Logger log = LoggerFactory.getLogger(KDEBin.class); + +// public static void main(String[] args) { +//// if (args.length != 1) +//// System.err.println("The input must be exactly one existing file path"); +//// Path path = Paths.get(args[0]).toAbsolutePath(); +//// if (Files.notExists(path)) +//// System.err.printf("File does not exist: %s", path); +// +// LogHelper.configureJavaUtilLogging(); +// KDEBin kdeBin = new KDEBin(); +// KdeBinParams params = KDEBin.parseParams(args, System.err); +// if (params == null) +// return; +// +// kdeBin.runKde(params, System.out); +// } +// +// public static KdeBinParams parseParams(String[] args, Appendable out) throws ParameterException { +// log.debug("Program {} started", KdeBinParams.APP_NAME); +// KdeBinParams params = new KdeBinParams(); +// JCommander jcom = new JCommander(params); +// try { +// jcom.setProgramName(KdeBinParams.APP_NAME); +// if (args.length == 0) { +// jcom.usage(); +// return null; +// } +// jcom.parse(args); +// if (params.isHelp()) { +// jcom.usage(); +// return null; +// } +// +// params.validate(); +// +// } catch (ParameterException pe) { +// if (!params.isRunGui()) { +// LogUtils.println(out, pe.getMessage()); +// return null; +// } +// } +// return params; +// } +// private static boolean isBinary(Path path) { +// return path.toString().endsWith(".bin"); +// } +// +// private double[][] readData(Path path, boolean isBinary, KdeBinParams params) { +// boolean dataHasWeights = params.hasWeights || params.colNumWeight != null; +// if (!dataHasWeights) { +// if (isBinary) { +// int numDataPoints = (int) (path.toFile().length() / 8); +// double[] result = new double[numDataPoints]; +// try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(path.toFile())))) { +// double val; +// int i = 0; +// while (true) { +// val = dis.readDouble(); +// result[i] = val; +// i++; +// } +// } catch (EOFException e) { +// // simply reached the end of file, all fine +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return new double[][]{result}; +// } else { +// final int colNumData = params.colNumData; +// ArrayList listVals = new ArrayList<>((int) 1e6); +// try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) { +// double val; +// String line; +// String[] split; +// Pattern delimiterRegEx = Pattern.compile(params.getDelimiterRegEx()); +// if (params.hasHeaderRow()) { +// line = reader.readLine(); +// } +// while ((line = reader.readLine()) != null) { +// split = delimiterRegEx.split(line); +// val = Double.parseDouble(split[colNumData]); +// listVals.add(val); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } +// double[] result = new double[listVals.size()]; +// for (int i = 0; i < listVals.size(); i++) +// result[i] = listVals.get(i); +// return new double[][]{result}; +// } +// } else { +// if (isBinary) { +// int numDataPoints = (int) ((path.toFile().length() / 8) / 2); +// double[] result = new double[numDataPoints]; +// double[] weights = new double[numDataPoints]; +// try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(path.toFile())))) { +// double val; +// int i = 0; +// while (true) { +// val = dis.readDouble(); +// result[i] = val; +// val = dis.readDouble(); +// weights[i] = val; +// i++; +// } +// } catch (EOFException e) { +// // simply reached the end of file, all fine +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return new double[][]{result}; +// } else { +// ArrayList listVals = new ArrayList<>((int) 1e6); +// ArrayList listWeights = new ArrayList<>((int) 1e6); +// final int colNumData = params.colNumData; +// final int colNumWeight = params.colNumWeight; +// try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) { +// double val; +// String line; +// String[] split; +// Pattern delimiterRegEx = Pattern.compile(params.getDelimiterRegEx()); +// if (params.hasHeaderRow()) { +// line = reader.readLine(); +// } +// while ((line = reader.readLine()) != null) { +// split = delimiterRegEx.split(line); +// val = Double.parseDouble(split[colNumData]); +// listVals.add(val); +// val = Double.parseDouble(split[colNumWeight]); +// listWeights.add(val); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } +// if (listVals.size() != listWeights.size()) +// throw new IllegalStateException("Resulting lists must be of the same size"); +// double[] vals = new double[listVals.size()]; +// double[] weights = new double[listWeights.size()]; +// for (int i = 0; i < listWeights.size(); i++) { +// vals[i] = listVals.get(i); +// weights[i] = listWeights.get(i); +// } +// return new double[][]{vals, weights}; +// } +// } +// } +// +// +// +// private void runKde(KdeBinParams params, Appendable out) { +// String msg; +// LogUtils.println(out, "Running Kernel Density Estimation"); +// log.debug("Running Kernel Density Estimation"); +// +// List matchingPaths = KDEMain.findMatchingPaths(params); +// msg = String.format("Found %d files matching input (single files and '%s' pattern) in '%s'", +// matchingPaths.size(), params.getInFileRegex(), StringUtils.join(params.getInFilePath(), ", ")); +// log.info(msg); +// LogUtils.println(out, msg); +// if (matchingPaths.size() == 0) { +// msg = "No files found - nothing to do, exiting."; +// log.info(msg); +// LogUtils.println(out, msg); +// return; +// } +// +// +// msg = "Preparing data for KDE"; +// log.info(msg); +// LogUtils.println(out, msg); +// +// List inFilePaths = params.getInFilePath(); +// ArrayList inputData = new ArrayList<>(inFilePaths.size()); +// for (Path inFilePath : inFilePaths) { +// double[][] data = readData(inFilePath, isBinary(inFilePath), params); +// inputData.add(data); +// } +// +// double[] massDiffs; +// double[] weights = null; +// if (inputData.size() == 1) { +// massDiffs = inputData.get(0)[0]; +// if (params.hasWeights()) +// weights = inputData.get(0)[1]; +// } else { +// int totalSize = 0; +// for (double[][] a : inputData) +// totalSize += a[0].length; +// massDiffs = new double[totalSize]; +// if (params.hasWeights()) +// weights = new double[totalSize]; +// int curStartingPos = 0; +// for (double[][] a : inputData) { +// System.arraycopy(a[0], 0, massDiffs, curStartingPos, a[0].length); +// if (params.hasWeights()) +// System.arraycopy(a[1], 0, weights, curStartingPos, a[1].length); +// curStartingPos += a.length; +// } +// } +// +// int totalEntries = massDiffs.length; +// if (totalEntries < 10) { +// msg = "There were less than 10 entries in the parsed data set. Won't run KDE on such small data."; +// log.info(msg); +// LogUtils.println(out, msg); +// } +// +// DenseVector massDiffVec = new DenseVector(massDiffs); +// KDEKludge kde; +// msg = "Setting up KDE, might take a while.."; +// log.info(msg); +// LogUtils.println(out, msg); +// if (weights == null) +// kde = new KDEKludge(massDiffVec, params.getKernelType().kernel); +// else +// kde = new KDEKludge(massDiffVec, params.getKernelType().kernel, weights); +// +// double kdeMin = kde.min(); +// double kdeMax = kde.max(); +// msg = String.format("KDE's PDF support is: [%.2f; %.2f], number of points %d.", kdeMin, kdeMax, massDiffVec.length()); +// log.info(msg); +// LogUtils.println(out, msg); +// +// +// // figuring out bandwidths +// List namedBandwidths = params.getNamedBandwidths(); +// if (namedBandwidths.isEmpty()) { +// log.error("Bandwidths list was empty!"); +// } +// +// +// // setting up axes +// double lo = params.getMzLo() == null ? kdeMin : params.getMzLo(); +// double hi = params.getMzHi() == null ? kdeMax : params.getMzHi(); +// double step = params.getMzStep(); +// int numPts = (int) ((hi - lo) / step) + 1; +// if (numPts < 2) { +// throw new IllegalStateException("For some reason x axis only contained 2 points, proably incorrect parameters"); +// } +// double[] xAxis = new double[numPts]; +// xAxis[0] = lo; +// for (int i = 1; i < xAxis.length; i++) { +// xAxis[i] = xAxis[i - 1] + step; +// } +// String datasetId = "KDE series"; +// JFreeChartPlot plot = new JFreeChartPlot("KDE Open Search"); +// try { +// Mods mods = PtmFactory.getMods(); +// plot.setMods(mods); +// } catch (ModParsingException e) { +// log.error("Could not parse modifications from different sources", e); +// LogUtils.println(out, "Error parsing modification data"); +// System.exit(1); +// } +// +// YIntervalSeriesCollection datasetPeaks = new YIntervalSeriesCollection(); +// +// if (params.isDynamicBandwidthEstimation()) { +// doKdeForDynBandwidth(out, params, namedBandwidths, +// massDiffVec, kde, lo, hi, step, +// xAxis, datasetId, plot, numPts, totalEntries, datasetPeaks); +// } +// for (NamedBandwidth bandwidth : namedBandwidths) { +// doKdeForOtherBandwiths(out, params, bandwidth, +// massDiffVec, kde, lo, hi, step, +// xAxis, datasetId, plot, numPts, totalEntries, datasetPeaks); +// } +// +// +// // plotting first derivative +// if (false) { +// double h = 0.001; +// String seriesIdDer = "der2@0.001h"; +// +// msg = String.format("Calcing KDE for dataset: %s\n", seriesIdDer); +// log.info(msg); +// LogUtils.println(out, msg); +// +// kde.setBandwith(h); +// kde.setKernelFunction(GaussFasterKF.getInstance()); +// double cur = lo, val; +// int curIdx = 0; +// double integralPdf = 0.0d; +// double[] yAxis = new double[numPts]; +// while (cur <= hi) { +// val = kde.pdfPrime2(cur); +// yAxis[curIdx] = val; +// integralPdf += val * step; +// curIdx++; +// cur += step; +// } +// msg = String.format("\tTotal area under KDEder : %.4f\n", integralPdf); +// log.info(msg); +// LogUtils.println(out, msg); +// ArrayFilter zeroFilterDer = new ArrayFilter(xAxis, yAxis); +// zeroFilterDer = zeroFilterDer.filterZeroesLeaveFlanking(); +// plot.addSeries(datasetId, seriesIdDer, zeroFilterDer.getMass(), zeroFilterDer.getInts()); +// } +// +// +// msg = "Generating plot"; +// log.info(msg); +// LogUtils.println(out, msg); +// +// if (params.isPlotKde()) { +// if (datasetPeaks.getSeriesCount() > 0) // if we were adding detected peaks, then plot them +// plot.addDataset(datasetPeaks, new YIntervalRenderer()); +// plot.display(); +// plot.addMouseListener(Collections.emptyList()); +// } +// +// log.debug("Plotting KDE finished"); +// } +// +// private void doKdeForOtherBandwiths(Appendable out, KdeBinParams params, NamedBandwidth bandwidth, DenseVector massDiffVec, KDEKludge kde, double lo, double hi, double step, double[] xAxis, String datasetId, JFreeChartPlot plot, int numPts, int totalEntries, YIntervalSeriesCollection datasetPeaks) { +// String msg; +// double h; +// String seriesId; +// +// if (bandwidth.isDynamic() && bandwidth.getTargetMz() != null) { +// // dynamic estimation at prescribed m/z +// h = KDEUtils.estimateBandwidth(massDiffVec, params.getAutoBandwithTarget(), params.getAutoBandwidthWindow()); +// seriesId = String.format("h=%.4f (est. @ %.2f+/-%.2f)", h, params.getAutoBandwithTarget(), params.getAutoBandwidthWindow()); +// } else { +// h = bandwidth.getBandwidth(); +// seriesId = String.format("h=%.4f", h); +// } +// +// msg = String.format("Calcing KDE for dataset: %s\n", seriesId); +// log.info(msg); +// LogUtils.println(out, msg); +// +// kde.setBandwith(h); +// double cur = lo, val; +// int curIdx = 0; +// double integralPdf = 0.0d; +// double[] yAxis = new double[numPts]; +// while (cur <= hi && curIdx < yAxis.length) { +// val = kde.pdf(cur); +// yAxis[curIdx] = val; +// integralPdf += val * step; +// curIdx++; +// cur += step; +// } +// addSeries(out, xAxis, yAxis, plot, params, datasetId, seriesId, integralPdf, totalEntries); +// if (params.isDetectPeaks()) { +// KDEMain.detectPeaks(out, params, xAxis, datasetPeaks, seriesId, bandwidth, yAxis); +// } +// } +// +// private void doKdeForDynBandwidth(Appendable out, KdeBinParams params, List namedBandwidths, DenseVector massDiffVec, KDEKludge kde, double lo, double hi, double step, double[] xAxis, String datasetId, JFreeChartPlot plot, int numPts, int totalEntries, YIntervalSeriesCollection datasetPeaks) { +// String msg; +// NamedBandwidth dynamicBandwidth = null; +// +// // ugly, but works. removing the dynamic bandwith estimation entry from the list of bandwidths. +// for (int i = 0; i < namedBandwidths.size(); i++) { +// dynamicBandwidth = namedBandwidths.get(i); +// if (dynamicBandwidth.isBandwidthAuto()) { +// namedBandwidths.remove(i); +// break; +// } +// } +// long nomMassPrev = Long.MIN_VALUE; +// long nomMassCur = nomMassPrev; +// String seriesId = dynamicBandwidth.getName(); +// msg = String.format("Calcing KDE for series '%s'", seriesId); +// log.info(msg); +// LogUtils.println(out, msg); +// +// double cur = lo, val; +// int curIdx = 0; +// double integralPdf = 0; +// double[] yAxis = new double[numPts]; +// while (cur <= hi && curIdx < yAxis.length) { +// nomMassCur = Math.round(cur); +// if (nomMassCur != nomMassPrev) { +// // recalc bandwidth +// double bandwidthAtThisNomMass = KDEUtils.estimateBandwidth(massDiffVec, (double) nomMassCur, params.getAutoBandwidthWindow()); +// if (bandwidthAtThisNomMass == 0 || Double.isInfinite(bandwidthAtThisNomMass) || Double.isNaN(bandwidthAtThisNomMass)) { +// bandwidthAtThisNomMass = 0.5; +// } +// kde.setBandwith(bandwidthAtThisNomMass); +// nomMassPrev = nomMassCur; +// } +// val = kde.pdf(cur); +// yAxis[curIdx] = val; +// integralPdf += val * step; +// curIdx++; +// cur += step; +// } +// addSeries(out, xAxis, yAxis, plot, params, datasetId, seriesId, integralPdf, totalEntries); +// if (params.isDetectPeaks()) { +// KDEMain.detectPeaks(out, params, xAxis, datasetPeaks, seriesId, dynamicBandwidth, yAxis); +// } +// } +// +// private void addSeries(Appendable out, double[] xAxis, double[] yAxis, +// JFreeChartPlot plot, KdeBinParams params, String datasetId, String seriesId, +// double integralPdf, int totalEntries) { +// String msg; +// msg = String.format("\tTotal area under KDE: %.4f\n", integralPdf); +// log.info(msg); +// LogUtils.println(out, msg); +// +//// Denoiser denoiser = params.getDenoising().getInstance(); +//// NumberedParams denoiserProperties = denoiser.getDefaultConfig(); +//// denoiserProperties.put("massAxis", xAxis); +//// +//// denoiserProperties.put(TotalVariationDenoiser.Config.PROP_LAMBDA, String.valueOf(params.getDenoisingParams())); +//// denoiserProperties.put(TotalVariationDenoiser.Config.PROP_DO_PLOT, String.valueOf(params.isDenoisingPlot())); +//// double[] denoised = new double[yAxis.length]; +//// denoiser.denoise(yAxis, denoised, denoiserProperties); +// +// ArrayFilter zeroFilter = new ArrayFilter(xAxis, yAxis); +// zeroFilter = zeroFilter.filterZeroesLeaveFlanking(); +// +// plot.addSeries(datasetId, seriesId, zeroFilter.getMass(), zeroFilter.getInts()); +// } +} diff --git a/src/oldMain/java/umich/opensearch/kde/KDEMain.java b/src/oldMain/java/umich/opensearch/kde/KDEMain.java new file mode 100644 index 0000000..8c76184 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/KDEMain.java @@ -0,0 +1,903 @@ +package umich.opensearch.kde; + +import com.github.chhh.utils.LogUtils; +import com.github.chhh.utils.MathUtils; +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.files.FileListing; +import com.google.common.base.Charsets; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.util.List; +import java.util.stream.Collectors; +import jsat.distributions.empirical.KernelDensityEstimator; +import jsat.distributions.empirical.kernelfunc.KernelFunction; +import jsat.linear.DenseVector; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.chart.renderer.xy.YIntervalRenderer; +import org.jfree.data.xy.DefaultXYDataset; +import org.jfree.data.xy.YIntervalSeries; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import smile.stat.distribution.Mixture; +import umich.opensearch.kde.api.IWeightedData; +import umich.opensearch.kde.api.PepXmlContent; +import umich.opensearch.kde.api.SearchHitDiffs; +import umich.opensearch.kde.gui.KDEGuiProps; +import umich.opensearch.kde.gui.KDEOptionsGui; +import umich.opensearch.kde.impl.WeightedDataDelimitedFile; +import umich.opensearch.kde.impl.WeightedDataPepXmlList; +import umich.opensearch.kde.jfree.JFreeChartPlot; +import umich.opensearch.kde.jsat.KDEKludge; +import umich.opensearch.kde.logging.LogHelper; +import umich.opensearch.kde.params.NamedBandwidth; +import umich.opensearch.kde.params.kernels.GaussFasterKF; +import umich.opensearch.kde.util.*; +import umich.ptm.PtmFactory; +import umich.ptm.exceptions.ModParsingException; +import umich.ptm.mod.Mod; +import umich.ptm.mod.Mods; + +/** + * \* @author Dmitry Avtonomov + */ +public class KDEMain { + + public static final Paint[] COLORS_NICE = new Paint[]{ + Color.decode("#a6cee3"), + Color.decode("#1f78b4"), + Color.decode("#b2df8a"), + Color.decode("#33a02c"), + Color.decode("#fb9a99"), + Color.decode("#e31a1c"), + Color.decode("#fdbf6f"), + Color.decode("#ff7f00"), + Color.decode("#cab2d6"), + Color.decode("#6a3d9a"), + Color.decode("#ffff99"), + Color.decode("#b15928"), + }; + public static final Paint[] COLORS_BETTER = new Paint[]{ + new Color(106, 61, 154), + new Color(255, 127, 0), + new Color(227, 26, 28), + new Color(51, 160, 44), + new Color(31, 120, 180), + new Color(202, 178, 214), + new Color(253, 191, 111), + new Color(251, 154, 153), + new Color(178, 223, 138), + new Color(166, 206, 227), + }; + private static final Logger log = LoggerFactory.getLogger(KDEMain.class); + private static final boolean DEBUG = false; + public static Paint[] COLORS_UGLY = new Paint[]{Color.red, Color.blue, Color.green, Color.orange, + Color.magenta, Color.PINK}; + private JFreeChartPlot.ChartCloseOption chartCloseOption = JFreeChartPlot.ChartCloseOption.EXIT; + + /** + * Original main entry point. Changed to {@link #run(umich.opensearch.kde.OpenSearchParams)} now + * as the same code is reused by the GUI version. + */ + public static void main(String[] args) { + LogHelper.configureJavaUtilLogging(); + + KDEMain kdeMain = new KDEMain(); + + boolean isFlush = Arrays.stream(args).anyMatch("--flush"::equals); + if (isFlush) { + KDEGuiProps.deleteTemp(); + } + + // Need to check for gui flag separately as "-i" is required in the params. + // Check will fail during parameter parsing if only `java -jar ... --gui" is executed. + boolean isGui = Arrays.stream(args).anyMatch("--gui"::equals); + boolean isInputSpecified = Arrays.stream(args) + .anyMatch(s -> "-i".equals(s) || "--in".equals(s)); + + OpenSearchParams params; + if (isGui && !isInputSpecified) { + params = new OpenSearchParams(); + params.setRunGui(true); + } else { + params = OpenSearchParams.parseParams(args, System.err); + } + + if (params == null) { + return; + } + kdeMain.run(params); + } + + public static void runKde(OpenSearchParams params, List pepxmls) { + + } + + /** + * Run the KDE with plotting and peak output. + * + * @param params Options. + * @param kdeMain This is really only used to set up the action of the window closing operation. + * In full GUI mode, this allows the main window not to close, when you close KDE plot. If null, + * closing the KDE plot will always shut down the application. + * @param pepXmlList Can be null, in which case the info viewer won't be set up. + */ + public static void runKde(OpenSearchParams params, KDEMain kdeMain, + List pepXmlList, IWeightedData diffsWeighted) { + log.info("Running Kernel Density Estimation"); + + double[] massDiffs = diffsWeighted.getData(); + double[] weights = diffsWeighted.getWeights(); + log.info(String.format("Sorting input data.. (%d points)", massDiffs.length)); + Arrays.sort(massDiffs); + log.info("Done"); + + // setting up axes + double lo = params.getMzLo() == null ? massDiffs[0] : params.getMzLo(); + double hi = params.getMzHi() == null ? massDiffs[massDiffs.length - 1] : params.getMzHi(); + double step = params.getMzStep(); + double[] xAxis = createAxis(lo, hi, step); + final String yAxisLblDer = "Der(2)"; + final String yAxisLblBfgs = "BFGS"; + + log.info("Setting up KDE, might take a while.."); + List kdeResults = new ArrayList<>(); + List brXaxis = BracketUtils.findIntegerBrackets(0.5, xAxis); + List bandwidths = params.getNamedBandwidths(); + for (NamedBandwidth bandwidth : bandwidths) { + + log.info(String.format("Bandwidth: %s", bandwidth.getName())); + + double[] yAxis = new double[xAxis.length]; + double[] yAxisLowRemoved = new double[xAxis.length]; + double[] yAxisDer = new double[xAxis.length]; + double h = Double.NaN; + + KdeResult kdeResult = new KdeResult(); + kdeResult.step = step; + kdeResult.kdeXAxis = xAxis; + kdeResult.kdeYAxis = yAxis; + + // deprecated + if (bandwidth.isBandwidthAutoAtFixedMz()) { + // dynamic bandwidth estimation at a single predefined m/z + double baseMz = params.getAutoBandwithTarget(); + double baseWnd = params.getAutoBandwidthWindow(); + double loVal = baseMz - baseWnd; + double hiVal = baseMz + baseWnd; + IndexBracket bracket = BracketUtils.findBracket(loVal, hiVal, massDiffs); + if (bracket.size < 5) { + log.info(String.format( + "Requested dynamic bandwidth estimation at m/z [%.2f], " + + "but less than 5 data points were found there.", baseMz)); + return; + } + DenseVector baseVec = new DenseVector(massDiffs, bracket.lo, bracket.hi); + h = KernelDensityEstimator.BandwithGuassEstimate(baseVec); + } else if (!Double.isNaN(bandwidth.getBandwidth())) { + // fixed bandwidth + h = bandwidth.getBandwidth(); + } + + for (IndexBracket brX : brXaxis) { + log.info(String.format("Processing interval [%.2f to %.2f]", brX.loVal, brX.hiVal)); + IndexBracket brD = BracketUtils.findBracket(brX.loVal, brX.hiVal, massDiffs); + if (brD.size == 0) { + continue; + } + DenseVector vecLocal = new DenseVector(massDiffs, brD.lo, brD.hi); + + // in case bandwidth needs to be estimated at each nominal mass separately + if (bandwidth.isBandwidthAuto()) { + double baseMz = Math.ceil(brX.loVal); + double baseWnd = bandwidth.getWindow(); + double loVal = baseMz - baseWnd; + double hiVal = baseMz + baseWnd; + IndexBracket bracket = BracketUtils.findBracket(loVal, hiVal, massDiffs); + if (bracket.size == 0) { + continue; + //throw new IllegalStateException("Should not happen, we already made vec from brD just fine"); + } + DenseVector baseVec = new DenseVector(massDiffs, bracket.lo, bracket.hi); + h = KernelDensityEstimator.BandwithGuassEstimate(baseVec); + } + + KernelFunction kernel = params.getKernelType().kernel; + KDEKludge kde = weights == null ? new KDEKludge(vecLocal, kernel, h) + : new KDEKludge(vecLocal, kernel, h, weights); + + final int minPtsKdeTest = params.getKdeMinPts(); + DenseVector testVec = new DenseVector(new double[]{0d, 0.49, 0.5, 0.51}); + int vecLocalLen = vecLocal.length(); + KDEKludge kdeTestLow = new KDEKludge(testVec, kernel, h, + new double[]{minPtsKdeTest, vecLocalLen / 4d, vecLocalLen / 2d, vecLocalLen / 4d}); + final double kdeMinAbundance = kdeTestLow.pdf(0); + + // calculating KDE profile + double pdfArea = 0; + for (int i = brX.lo; i < brX.hi; i++) { + double x = xAxis[i]; + double pdf = kde.pdf(x); + yAxis[i] = pdf; + yAxisLowRemoved[i] = yAxis[i] < kdeMinAbundance ? 0 : yAxis[i]; + yAxisDer[i] = yAxis[i] < kdeMinAbundance ? 0 : kde.pdfPrime2(x); + double area = pdf * step; + pdfArea += area; + } + + // normalize the KDE plot scale to the number of PSMs + double density = vecLocal.length() / pdfArea; + for (int i = brX.lo; i < brX.hi; i++) { + yAxis[i] *= density; // KDE is now scaled to the number of points in the bracket + yAxisLowRemoved[i] *= density; + } + + if (params.isDetectPeaks()) { + PeakUtils.NoiseResult noiseEstimate = PeakUtils + .estimateNoiseDistribution(params, brX, xAxis, yAxisLowRemoved, vecLocal); + + PeakUtils.BracketResult bracketResult = + PeakUtils.findPeaksGMM4(params, brX, xAxis, yAxisLowRemoved, yAxisDer, vecLocal); + kdeResult.bracketResults.put(brX, bracketResult); + } + + log.info("Done"); + } + + // finalize peak detection, including filtering remaining peaks by intensity / PSM support + prepareResults(kdeResult, params); + + kdeResult.namedBandwidth = bandwidth; + kdeResult.kdeXAxis = xAxis; + kdeResult.kdeYAxis = yAxis; + kdeResult.yAxes.put(yAxisLblDer, yAxisDer); + + kdeResults.add(kdeResult); + } + + // write out detected peaks + if (params.isDetectPeaks() && params.outFilePath != null) { + for (KdeResult kdeResult : kdeResults) { + writePeaks(params, kdeResult); + } + } + + if (params.isPlotKde()) { + + JFreeChartPlot plot = new JFreeChartPlot("KDE Open Search"); + DefaultXYDataset datasetKde = new DefaultXYDataset(); + DefaultXYDataset datasetGmms = new DefaultXYDataset(); + String datasetIdKde = "KDE"; + String datasetIdGmm = "GMM"; + YIntervalSeriesCollection datasetPeaks = new YIntervalSeriesCollection(); + DefaultXYDataset datasetDer = new DefaultXYDataset(); + + for (KdeResult kdeResult : kdeResults) { + + // plot KDE itself + String seriesIdBase = kdeResult.namedBandwidth.getName(); + double[] kdeXAxis = kdeResult.kdeXAxis; + double[] kdeYAxis = kdeResult.kdeYAxis; + ArrayFilter zeroFilter = new ArrayFilter(kdeXAxis, kdeYAxis); + zeroFilter = zeroFilter.filterZeroesLeaveFlanking(); + datasetKde.addSeries("KDE:" + seriesIdBase, + new double[][]{zeroFilter.getMass(), zeroFilter.getInts()}); + + if (params.isPlotDer2() && kdeResult.yAxes.get(yAxisLblDer) != null) { + double[] yAxis = kdeResult.yAxes.get(yAxisLblDer); + ArrayFilter zf = new ArrayFilter(kdeXAxis, yAxis); + zf.filterZeroesLeaveFlanking(); + String id = "KDE:" + seriesIdBase + "(" + yAxisLblDer + ")"; + datasetDer.addSeries(id, new double[][]{zf.getMass(), zf.getInts()}); + } + + // plot GMMs + if (params.plotGmm && !kdeResult.bracketResults.isEmpty()) { + int maxComps = -1; + for (PeakUtils.BracketResult r : kdeResult.bracketResults.values()) { + maxComps = Math.max(maxComps, r.peaksOutput.size()); + + // sort the output peaks by intensity in descending order + r.peaksOutput + .sort((p1, p2) -> Double.compare(p2.gmmComponentArea, p1.gmmComponentArea)); + } + + if (maxComps > 0) { + double[] gmmXAxis = kdeResult.kdeXAxis; + + for (int i = 0; i < maxComps; i++) { + String gmmSeriesId = String.format("GMM(%d):%s", i, seriesIdBase); + double[] gmmYAxis = new double[gmmXAxis.length]; + + for (Map.Entry e : kdeResult.bracketResults + .entrySet()) { + PeakUtils.BracketResult r = e.getValue(); + if (r.peaksOutput.isEmpty()) { + continue; + } + + if (i >= r.peaksOutput.size()) { + continue; + } + final PeakOutput p = r.peaksOutput.get(i); + + final double sd3 = p.sd * 3; + final double mu = p.mean; + double factor = p.kdeTotalArea / p.gmmCombinedArea; + for (int j = r.bracket.lo; j < r.bracket.hi; j++) { + double x = gmmXAxis[j]; + if (Math.abs(mu - x) < sd3) { + gmmYAxis[j] += p.distribution.p(x) * p.priori * factor; + } + } + + } + + ArrayFilter zf = new ArrayFilter(gmmXAxis, gmmYAxis); + zf = zf.filterZeroesLeaveFlanking(); + datasetGmms.addSeries(gmmSeriesId, new double[][]{zf.getMass(), zf.getInts()}); + + + } + } + } + + // plot peaks as vertical lines + final List peakOutputs = gatherPeaks(kdeResult, params); + if (!peakOutputs.isEmpty()) { + YIntervalSeries peakSeries = new YIntervalSeries( + "Peak locations (" + kdeResult.namedBandwidth.getName() + ")"); + for (PeakOutput p : peakOutputs) { + peakSeries.add(p.der2Loc, 0, 0, p.gmmComponentScaledHeight); + } + datasetPeaks.addSeries(peakSeries); + } + } + + // adding dataset to the plot + if (datasetKde.getSeriesCount() > 0) { + XYLineAndShapeRenderer render = new XYLineAndShapeRenderer(true, false) { + @Override + public Paint getSeriesPaint(int series) { + return COLORS_BETTER[series % COLORS_BETTER.length]; + } + }; + + plot.addDataset(datasetIdKde, datasetKde, render, false); + } + + // configuring plotting + if (datasetGmms.getSeriesCount() > 0) { + final BasicStroke dashedStroke = new BasicStroke( + 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{3.0f, 3.0f}, 0.0f); + XYLineAndShapeRenderer render = new XYLineAndShapeRenderer(true, false) { + @Override + public Stroke getItemStroke(int row, int column) { + return dashedStroke; + } + + @Override + public Paint getSeriesPaint(int series) { + return COLORS_BETTER[series % COLORS_BETTER.length]; + } + }; + render.setDrawSeriesLineAsPath(true); + plot.addDataset(datasetIdGmm, datasetGmms, render, false); + } + if (datasetPeaks.getSeriesCount() > 0) { + YIntervalRenderer renderer = new YIntervalRenderer() { + @Override + public Paint getSeriesPaint(int series) { + return COLORS_BETTER[series % COLORS_BETTER.length]; + } + }; + plot.addDataset("Local Maxima", datasetPeaks, renderer, false); + } + if (datasetDer.getSeriesCount() > 0) { + XYLineAndShapeRenderer r = new XYLineAndShapeRenderer(true, false); + r.setBaseStroke(new BasicStroke( + 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{3.0f, 3.0f}, 0.0f)); + r.setDrawSeriesLineAsPath(true); + plot.addDataset("Derivatives", datasetDer, r, true); + } + + if (kdeMain != null) { + plot.display(kdeMain.chartCloseOption.windowConstant, params.getInFilePath().toString()); + } else { + plot.display(WindowConstants.EXIT_ON_CLOSE, params.getInFilePath().toString()); + } + + if (pepXmlList != null) { + try { + Mods mods = PtmFactory + .getMods(EnumSet.of(PtmFactory.SOURCE.GEN, PtmFactory.SOURCE.UNIMOD)); + plot.setMods(mods); + } catch (ModParsingException e) { + log.error("Could not parse modifications from modification sources", e); + System.exit(1); + } + plot.addMouseListener(pepXmlList); + } + + } + + } + + private static void writePeaks(OpenSearchParams params, KdeResult kdeResult) { + //CSVFormat csvFormat = CSVFormat.newFormat(',').withHeader("dm", "sd", "weight") + // .withQuoteMode(QuoteMode.NON_NUMERIC).withRecordSeparator("\r\n").withSkipHeaderRecord(false); + + final List peakOutputs = gatherPeaks(kdeResult, params); + // sort the output peaks by delta mass in ascending order + peakOutputs.sort(Comparator.comparingDouble(p2 -> p2.der2Loc)); + + // PTM annotations + Mods mods; + try { + mods = PtmFactory.getMods(EnumSet.of(PtmFactory.SOURCE.GEN, PtmFactory.SOURCE.UNIMOD)); + } catch (ModParsingException e) { + throw new IllegalStateException("Could not create a list of annotation PTMs"); + } + + final double sdDistance = 2.0; // annotations max 2 SD away from peaks in KDE + final double maxAbsDistance = 0.001; + final DecimalFormat df4 = new DecimalFormat("0.0000"); + final DecimalFormat df1 = new DecimalFormat("0.0"); + final String valSep = "::"; + final String entrySep = "; "; + + final List list = new ArrayList<>(); + for (PeakUtils.BracketResult r : kdeResult.bracketResults.values()) { + for (PeakOutput p : r.peaksOutput) { + String annotation = mods + .findByMass(p.der2Loc - sdDistance * p.sd, p.der2Loc + sdDistance * p.sd).stream() + .map(mod -> { + final Double massMono = mod.getComposition().getMassMono(); + if (massMono == null) { + log.error( + "Monoisotopic mass of elemental composition was null while mapping possible mods for a peak"); + } + final double dm = massMono - p.der2Loc; + final double likelihood = p.distribution.likelihood(new double[]{massMono}); + return new ModEntry(massMono, dm, likelihood, mod); + }) + .sorted((o1, o2) -> Double.compare(o2.likelihood, o1.likelihood)) + .map(me -> { + StringBuilder desc = new StringBuilder(); + desc.append(me.mod.getRef()).append(valSep) + .append("M=").append(df4.format(me.m)).append("Da").append(valSep) + .append("dM=").append(df4.format(me.dm)).append("Da").append(valSep) + .append("q=").append(df4.format(me.likelihood)).append(valSep) + .append(me.mod.getDescShort()); + return desc.toString(); + }) + .collect(Collectors.joining(entrySep, "\"", "\"")); + + double dm = p.der2Loc; + double sd = p.sd; + double support = p.gmmSupport; + double intensity = p.gmmComponentScaledHeight; + double quality = p.der2Value; + double score = quality * intensity; + list.add(new OutputEntry(dm, sd, support, intensity, quality, score, annotation)); + } + } + log.info("Created output list of {} peaks", list.size()); + // sort by score + list.sort((o1, o2) -> Double.compare(o2.score, o1.score)); + log.info("Sorted by descending score"); + + // write the list to the file + Path outputPath = kdeResult.namedBandwidth.createOutputPath(params.outFilePath); + log.info("Writing peaks to file: {}", outputPath); + try (OutputStreamWriter osw = new OutputStreamWriter( + new FileOutputStream(outputPath.toFile(), false), Charsets.UTF_8)) { + osw.write("dm, fwhm, support, intensity, quality, score, annotations\n"); + for (OutputEntry oe : list) { + String format = String.format("%.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %s\n", + oe.dm, oe.fwhm, oe.support, oe.intensity, oe.quality, oe.score, oe.annotations); + osw.write(format); + } + osw.flush(); + } catch (FileNotFoundException e) { + log.error("Error while writing output, file not found"); + } catch (IOException e) { + log.error("Error while writing output", e); + } + log.info("Done writing peaks"); + } + + private static void prepareResults(KdeResult kdeResult, OpenSearchParams params) { + if (kdeResult.bracketResults.isEmpty()) { + return; + } + + double[] kdeXAxis = kdeResult.kdeXAxis; + double[] kdeYAxis = kdeResult.kdeYAxis; + double step = kdeResult.step; + + // GMMs + for (Map.Entry entry : kdeResult.bracketResults + .entrySet()) { + PeakUtils.BracketResult r = entry.getValue(); + if (r.gmm == null || r.gmm.getComponents().isEmpty()) { + continue; + } + + // area under the KDE curve + // at this point is should be equal to the number of PSM entries in the bracket + double kdeArea = 0; + for (int j = r.bracket.lo; j < r.bracket.hi; j++) { + kdeArea += step * kdeYAxis[j]; + } + + // area of all GMM components + double gmmCombinedArea = 0; + for (Mixture.Component mc : r.gmm.getComponents()) { + for (int j = r.bracket.lo; j < r.bracket.hi; j++) { + gmmCombinedArea += step * mc.distribution.p(kdeXAxis[j]) * mc.priori; + } + } + + // find the highest GMM component (before mapping peaks to GMM components) + double topGmmCompIntensity = 0; + for (Mixture.Component mc : r.gmm.getComponents()) { + double gmmPeakHeight = mc.distribution.p(mc.distribution.mean()) * mc.priori; + if (gmmPeakHeight > topGmmCompIntensity) { + topGmmCompIntensity = gmmPeakHeight; + } + } + + // map peaks from 2nd derivative to GMM components + final TreeMap gmmMap = new TreeMap<>(); + for (Mixture.Component mc : r.gmm.getComponents()) { + gmmMap.put(mc.distribution.mean(), mc); + } + TreeMap peakMapping = new TreeMap<>(); + final HashMap> gmmToPeakList = new HashMap<>(); + //final TreeMap, List> gmmToPeakList2 = new TreeMap<>(); + for (Peak p : r.peaks) { + final Mixture.Component mc = MapUtils.findClosest(gmmMap, p.x); + if (mc == null) { + continue; + } + double distance = Math.abs(mc.distribution.mean() - p.x); + // if a peak is more than 1 SD away from the mean of a GMM component - disregard it + if (distance > mc.distribution.sd()) { + continue; + } + peakMapping.put(p, mc); + List list = gmmToPeakList.computeIfAbsent(mc, k -> new ArrayList<>()); + list.add(p); + + //List list2 = gmmToPeakList2.computeIfAbsent(new ComparableHolder<>(mc.distribution.mean(), mc), k -> new ArrayList<>()); + //list2.add(p); + } + + // iterate over mixture components mapped to peaks + for (Map.Entry> e : gmmToPeakList.entrySet()) { + final Mixture.Component mc = e.getKey(); + + // check if GMM component's height is enough relative to the top peak at this nominal delta mass + double gmmPeak = mc.distribution.p(mc.distribution.mean()) * mc.priori; + if (gmmPeak < topGmmCompIntensity * params.cutoff) { + //LogHelper.logMsg(String.format("Removing peak, not high enough (%.2f) relative to top (%.2f)", gmmPeak, gmmTop), out, log, true); + continue; + } + + // volume of the current GMM component (should be roughly 1.0) + double cArea = 0; + for (int j = r.bracket.lo; j < r.bracket.hi; j++) { + cArea += step * mc.distribution.p(kdeXAxis[j]) * mc.priori; + } + + // check if the GMM component is supported by enough PSMs + final double factor = kdeArea / gmmCombinedArea; + double gmmSupport = cArea * factor; + if (gmmSupport < params.gmmMinSupport) { + //LogHelper.logMsg(String.format("Removing peak, not enough support (%.2f), min support needed(%d)", gmmSupport, params.gmmMinSupport), out, log, true); + continue; + } + + final double gmmPeakHeight = mc.distribution.p(mc.distribution.mean()) * mc.priori * factor; + + // take the highest peak + final List peaks = e.getValue(); + if (peaks.isEmpty()) // unlikely + { + continue; + } + if (peaks.size() > 1) { + peaks.sort((p1, p2) -> Double.compare(p2.peak.amplitudeLo(), p1.peak.amplitudeLo())); + } + Peak p = peaks.get(0); + + // all tests passed, this peak and GMM component can be reported + final PeakOutput.Builder b = new PeakOutput.Builder(); + b.setMean(mc.distribution.mean()) + .setSd(mc.distribution.sd()) + .setPriori(mc.priori) + .setDistribution(mc.distribution) + .setKdeTotalArea(kdeArea) + .setKdeTotalEntries(r.numEntries) + .setGmmComponentArea(cArea) + .setGmmCombinedArea(gmmCombinedArea) + .setGmmComponentScaledHeight(gmmPeakHeight) + .setGmmSupport(gmmSupport) + .setDer2Loc(p.x) + .setDer2Amplitude(p.peak.amplitudeLo()) + .setDer2Value(p.peak.valTop); + + // add the peak to the final list + final PeakOutput outPeak = b.createPeakOutput(); + if (outPeak.der2Value + < 0) { // der2 values are inverted, hence the check for < 0 instead of > 0 + continue; + } + r.peaksOutput.add(outPeak); + } + r.peaksOutput.sort(Comparator.comparingDouble(o -> o.der2Loc)); + } + } + + private static List gatherPeaks(KdeResult kdeResult, OpenSearchParams params) { + ArrayList res = new ArrayList<>(); + + for (PeakUtils.BracketResult br : kdeResult.bracketResults.values()) { + res.addAll(br.peaksOutput); + } + + return res; + } + + private static void plotFirstDerivative(Appendable out, double lo, double hi, double step, + double[] xAxis, KDEKludge kde, String datasetId, JFreeChartPlot plot) { + String msg; + double h = 0.001; + String seriesIdDer = "der2@0.001h"; + + msg = String.format("Calcing KDE for dataset: %s\n", seriesIdDer); + log.info(msg); + LogUtils.println(out, msg); + + kde.setBandwith(h); + kde.setKernelFunction(GaussFasterKF.getInstance()); + double cur = lo, val; + int curIdx = 0; + double integralPdf = 0.0d; + double[] yAxis = new double[xAxis.length]; + while (cur <= hi) { + val = kde.pdfPrime2(cur); + yAxis[curIdx] = val; + integralPdf += val * step; + curIdx++; + cur += step; + } + msg = String.format("\tTotal area under KDEder : %.4f\n", integralPdf); + log.info(msg); + LogUtils.println(out, msg); + ArrayFilter zeroFilterDer = new ArrayFilter(xAxis, yAxis); + zeroFilterDer = zeroFilterDer.filterZeroesLeaveFlanking(); + DefaultXYDataset dataset = new DefaultXYDataset(); + dataset + .addSeries(seriesIdDer, new double[][]{zeroFilterDer.getMass(), zeroFilterDer.getInts()}); + plot.addDataset("Derivative", dataset, new XYLineAndShapeRenderer(true, false), true); + } + + private static double[] createAxis(double lo, double hi, double step) { + int numPts = (int) ((hi - lo) / step) + 1; + if (numPts < 2) { + throw new IllegalStateException( + "For some reason x axis only contained 2 points, probably incorrect parameters"); + } + double[] xAxis = new double[numPts]; + xAxis[0] = lo; + for (int i = 1; i < xAxis.length; i++) { + xAxis[i] = xAxis[i - 1] + step; + } + return xAxis; + } + + private static void deleteCache(OpenSearchParams params) { + log.info("Delete cache option was specified, will delete cache now"); + PepXmlContent.deleteCache(params); + return; + } + + public static List findMatchingPaths(OpenSearchParams params) { + List matchingPaths = new ArrayList<>(); + List inFilePath = params.getInFilePath(); + for (Path path : inFilePath) { + if (!Files.isDirectory(path)) { + matchingPaths.add( + path); // if a particular file was specified on the cmd line, just add it even if it doesn't match + continue; + } + FileListing fileListing = new FileListing(path, params.getInFileRegex()); + fileListing.setFollowLinks(params.isFollowSymlinks()); + fileListing.setIncludeDirectories(false); + fileListing.setIncludeFiles(true); + List files = fileListing.findFiles(); + matchingPaths.addAll(files); + + } + return matchingPaths; + } + + public JFreeChartPlot.ChartCloseOption getChartCloseOption() { + return chartCloseOption; + } + + public void setChartCloseOption(JFreeChartPlot.ChartCloseOption chartCloseOption) { + this.chartCloseOption = chartCloseOption; + } + + /** + * New main entry point. Consider using {@link OpenSearchParams#parseParams(String[], Appendable) + * } to actually parse the parameters if you have them in command line form. + */ + public void run(OpenSearchParams params) { + log.debug("Running with parameters:\n{}", params.toString()); + + if (params.isRunGui()) { + if (!SwingUtils.isGraphicalEnvironmentAvailable()) { + log.error("Graphcal environment is needed to run the GUI."); + return; + } + KDEOptionsGui.initAndRun(params); + return; + } else { + final SplashScreen splash = SplashScreen.getSplashScreen(); + if (splash != null) { + splash.close(); + } + } + + if (Thread.interrupted()) { + return; + } + + if (params.isDeleteCache()) { + deleteCache(params); + return; + } + + // check bandwidths + List bandwidths = params.getNamedBandwidths(); + if (bandwidths == null || bandwidths.isEmpty()) { + log.error("Bandwidths list can't be empty."); + return; + } + + // get the data in + IWeightedData diffsWeighted; + List pepXmlList = null; + List matchingPaths = findMatchingPaths(params); + if (!params.isBinaryInput()) { + // get the data in: for regular pep xml input + String foundFilesStr = String + .format("Found %d files matching input (single files and '%s' pattern) in '%s'", + matchingPaths.size(), params.getInFileRegex(), + StringUtils.join(params.getInFilePath(), ", ")); + log.info(foundFilesStr); + + if (matchingPaths.size() == 0) { + log.warn("No files found - nothing to do, exiting."); + return; + } + pepXmlList = PepXmlContent.getPepXmlContents(params, matchingPaths); + log.info(String.format("Loaded %d search results files", pepXmlList.size())); + int searchHitCount = 0; + for (PepXmlContent pepXmlContent : pepXmlList) { + for (SearchHitDiffs searchHitDiffs : pepXmlContent.getHitsList()) { + searchHitCount += searchHitDiffs.getHits().size(); + } + } + log.info(String.format("It contained %d hits before filtering", searchHitCount)); + + diffsWeighted = WeightedDataPepXmlList.create(pepXmlList, params); + int totalEntries = 0; + for (PepXmlContent pepXmlContent : pepXmlList) { + List hitsList = pepXmlContent.getHitsList(); + for (SearchHitDiffs searchHitDiffs : hitsList) { + log.info(String.format("Sorting entries of '%s'", searchHitDiffs.getName())); + searchHitDiffs.sortByDiffMass(); + totalEntries += searchHitDiffs.getHits().size(); + } + } + + if (totalEntries < 50) { + log.warn( + "There were less than 50 entries in the parsed/filtered data set. Won't run KDE on such small data."); + return; + } + + } else { + // get the data in: for delimited text file with numbers + ArrayList inputData = new ArrayList<>(matchingPaths.size()); + for (Path inFilePath : matchingPaths) { + double[][] data = BinReader.readData(inFilePath, false, params); + inputData.add(data); + } + diffsWeighted = WeightedDataDelimitedFile.create(inputData); + } + if (Thread.interrupted()) { + return; + } + + // do the plotting and peak finding + if (params.isPlotKde()) { + if (!SwingUtils + .isGraphicalEnvironmentAvailable()) { // this can only happen in console mode, so no worries + log.error("Graphical environment is needed to run the GUI, won't plot KDE"); + return; + } + } + if (params.isPlotKde() || params.isDetectPeaks()) { + // there is no reason to do KDE if we're not plotting or detecting peaks + runKde(params, this, pepXmlList, diffsWeighted); + } + + + } + + private static class OutputEntry { + + final double dm; + final double sd; + final double fwhm; + final double support; + final double intensity; + final double quality; + final double score; + final String annotations; + + public OutputEntry(double dm, double sd, double support, double intensity, double quality, + double score, String annotations) { + this.dm = dm; + this.sd = sd; + this.fwhm = MathUtils.fwhmFromSigma(sd); + this.support = support; + this.intensity = intensity; + this.quality = quality; + this.score = score; + this.annotations = annotations; + } + } + + private static class ModEntry { + + final double m; + final double dm; + final double likelihood; + final Mod mod; + + public ModEntry(double m, double dm, double likelihood, Mod mod) { + this.m = m; + this.dm = dm; + this.likelihood = likelihood; + this.mod = mod; + } + } + + private static class KdeResult { + + NamedBandwidth namedBandwidth = null; + double[] kdeXAxis = null; + double step; + double[] kdeYAxis = null; + HashMap yAxes = new HashMap<>(); + TreeMap bracketResults = new TreeMap<>(); + + public KdeResult() { + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/KdeBinParams.java b/src/oldMain/java/umich/opensearch/kde/KdeBinParams.java new file mode 100644 index 0000000..fcf2495 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/KdeBinParams.java @@ -0,0 +1,55 @@ +package umich.opensearch.kde; + +import com.beust.jcommander.Parameter; + +/** + * @author Dmitry Avtonomov + */ +public class KdeBinParams extends OpenSearchParams { + + public static String APP_NAME = "KDE Binary Version"; + @Parameter(names = {"--colData"}, + description = "If parsing a delimited file, this column will be used as the data input.") + public Integer colNumData = 0; + @Parameter(names = {"--colWeight"}, + description = "If parsing a delimited file, this column can optionally be used as weights.") + public Integer colNumWeight = null; + @Parameter(names = {"--hasWeights"}, + description = "Whether the file has weights for data points. This parameter is only needed for binary files.") + protected boolean hasWeights = false; + @Parameter(names = {"--delimiterRegEx"}, + description = "Each line of text input will be split according to this regular expression.") + protected String delimiterRegEx = "\\s"; + @Parameter(names = {"--hasHeader"}, + description = "When parsing a text (delimited) file, if set to true will skip the first row.") + protected boolean hasHeaderRow = false; + + public boolean hasWeights() { + return hasWeights; + } + + public String getDelimiterRegEx() { + return delimiterRegEx; + } + + public void setDelimiterRegEx(String delimiterRegEx) { + this.delimiterRegEx = delimiterRegEx; + } + + public boolean hasHeaderRow() { + return hasHeaderRow; + } + + public void setHasHeaderRow(boolean hasHeaderRow) { + this.hasHeaderRow = hasHeaderRow; + } + + public boolean isHasWeights() { + return hasWeights; + } + + public void setHasWeights(boolean hasWeights) { + this.hasWeights = hasWeights; + } + +} diff --git a/src/oldMain/java/umich/opensearch/kde/OpenSearchParams.java b/src/oldMain/java/umich/opensearch/kde/OpenSearchParams.java new file mode 100644 index 0000000..2ef3b12 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/OpenSearchParams.java @@ -0,0 +1,704 @@ +package umich.opensearch.kde; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import com.beust.jcommander.converters.CommaParameterSplitter; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.params.*; +import umich.opensearch.kde.params.denoise.Denoising; +import umich.opensearch.kde.params.predicates.MassDiffRangePredicate; +import umich.opensearch.kde.params.predicates.ScorePredicate; +import umich.opensearch.kde.pepxml.DecoyDetector; +import umich.opensearch.kde.pepxml.DecoyDetectorAcceptAll; +import umich.opensearch.kde.pepxml.DecoyDetectorByProtName; +import umich.opensearch.kde.util.DecimalFormatWithToString; + +/** + * @author Dmitry Avtonomov + */ +public class OpenSearchParams implements Serializable { + + public static final String APP_NAME = "Open Search KDE"; + public static final Charset UTF8 = Charset.forName("UTF-8"); + private static final Logger log = LoggerFactory.getLogger(OpenSearchParams.class); + private static final long serialVersionUID = -5354151314716159125L; + @Parameter(names = {"-i", "--in"}, description = + "Relative or absolute path to input file. Can be directories or separate file paths." + + " Separate entries with commas.", + required = true, validateWith = PathListParameter.class, converter = PathParameter.class, splitter = CommaParameterSplitter.class) + protected List inFilePath; + @Parameter(names = {"-ip", + "--in_pattern"}, description = "Regular expression that files will be matched against.") + protected String inFileRegex = ".*\\.pep\\.xml"; + @Parameter(names = {"--symlinks"}, arity = 1, hidden = true, + description = "Try to follow symlinks.") + protected boolean followSymlinks = true; + @Parameter(names = {"--help", "-help"}, description = "Display help message", help = true) + protected Boolean help; + @Parameter(names = {"-d", "--decoy_prefix"}, description = + "Decoy protein name prefix. You can explicitly provide an empty string" + + " as a parameter, in which case decoys won't be left out from the analysis.") + protected List decoyProtPrefix = Arrays.asList("rev_"); + @Parameter(names = {"--filter"}, description = + "Path to a comma or tab delimited text file containing identifiers " + + "of peptides to be kept in the analysis. See '-fc' option, by default the 1st column of the file is used.", + validateValueWith = ExistingPathValidator.class, converter = PathParameter.class) + protected Path filterFile; + @Parameter(names = {"-fc", "--filterCol"}, description = + "The column number in the tab/comma delimited file given " + + "in '--filter' option.") + protected int filterCol = 0; + @Parameter(names = {"-u", + "--use"}, description = "Only consider forwards, decoys or both in the analysis.") + protected DecoyTreatment decoysTreatment = DecoyTreatment.FORWARDS_ONLY; + @Parameter(names = {"-c", "--cache"}, arity = 1, + description = "Whether to use caching - write parsed info from pepxml files to separate files (to turn off use '-c false').") + protected boolean useCache = true; + @Parameter(names = {"-x", "--delete_cache"}, description = + "Will delete all cached files, you still need to provide proper path in '--in'." + + " Won't do any KDE stuff, just the deletions.") + protected boolean deleteCache = false; + @Parameter(names = {"--flush"}, description = "Clear stored parameters from previous runs.") + protected boolean flush = false; + @Parameter(names = { + "--cache_ext"}, description = "Extension for the cache file, which is used to avoid re-parsing pep.xml files") + protected String cacheExt = ".kde-cache"; + @Parameter(names = {"-b"}, description = + "Set bandwidths to be used for generating KDEs manually. " + + "You can specify multiple comma separated values (e.g. -b 0.1,0.001). See '-bd' switch.") + protected List bandwidths = new ArrayList<>(); + @Parameter(names = {"-ba", "--b_auto"}, arity = 1, + description = "Will automatically estimate the bandwidth for each nominal mass.") + protected boolean dynamicBandwidthEstimation = true; + @Parameter(names = {"-bm", "--b_mz"}, hidden = true, + description = "The m/z around which samples will be collected to evaluate the bandwidth. Will only work if non-null.") + protected double autoBandwithTarget = Double.NaN; + @Parameter(names = {"-bw", "--b_mz_wnd"}, hidden = true, + description = "The m/z window used for -bm and -bd.") + protected double autoBandwidthWindow = 0.15; + @Parameter(names = {"-k", + "--kernel"}, description = "What kernel will be used for KDE. Better not to change from default.") + protected KDEKernelType kernelType = KDEKernelType.GAUSS_FAST; + @Parameter(names = {"-s", + "--score"}, description = "Parameter cutoffs, e.g. \"expect<=0.001\" or \"peptideprophet>0.95\"", + validateWith = ScorePredicateFactoryParameter.class, converter = ScorePredicateFactoryParameter.class) + protected List scorePredicateFactories; + @Parameter(names = {"-ml", "--mz_lo"}, description = + "The lower m/z bound for which to do the KDE, can be left null, in which case" + + " the whole range of mass differences from pep.xml files will be used. That filter is applied after reading pepxml or cache files," + + + " so it's safe to reuse cache files with this option set to a different value.") + protected Double mzLo = null; + @Parameter(names = {"-mh", "--mz_hi"}, description = "See '--mz_lo' description") + protected Double mzHi = null; + @Parameter(names = {"-ms", + "--mz_step"}, description = "The step used for m/z axis plotting of KDE.") + protected double mzStep = 0.001; + @Parameter(names = {"-mc", "--mass_correction"}, description = + "Mass shift correction. ZERO_PEAK will use the shift of the" + + " highest peak near delta mass of 0.0 to correct all other mass differences.") + protected MassCorrection massCorrection = MassCorrection.NONE; + @Parameter(names = {"--mc_paths"}, description = + "Directories where to search for mass correction files. " + + "Separate entries with commas.", + validateWith = PathListParameter.class, converter = PathParameter.class, splitter = CommaParameterSplitter.class) + protected List massCorFilePaths = new ArrayList<>(); + @Parameter(names = {"-f", "--force"}, description = + "Forces overwriting files. If not set and some path needs to be created," + + " but already exists - the program will exit.") + protected boolean force = false; + @Parameter(names = {"-o", "--out"}, + description = "If KDE peak detection is enabled (-p switch), the output will be written to this file.", + validateWith = WriteableDirectoryParameter.class, + converter = WriteableDirectoryParameter.class, + validateValueWith = WriteableDirectoryParameter.class) + protected Path outFilePath; + @Parameter(names = {"-g", + "--gui"}, description = "Run GUI version, where you can select all the parameters.") + protected boolean runGui = false; + @Parameter(names = {"--plot"}, arity = 1, + description = "Should the KDE be plotted in GUI? (Use '--plot false' to turn off)") + protected boolean plotKde = true; + @Parameter(names = {"--plotder"}, hidden = true, + description = "Plot second derivative of KDE?") + protected boolean plotDer2 = false; + @Parameter(names = {"--plotgmm"}, + description = "Plot the final GMM model? Very slow when zoomed in") + protected boolean plotGmm = true; + @Parameter(names = {"-p", "--peaks"}, arity = 1, + description = "Should peak detection be run? (use '-p false' to turn off)") + protected boolean detectPeaks = true; + @Parameter(names = {"--clear"}, arity = 2, description = + "Remove data points that are within delta mass ranges. " + + "You can specify multiple ranges in pairs, e.g. \"--clear -0.5 0.5 2.5 3.5\"") + protected List clearRange; + @Parameter(names = {"-w", "--weights"}, + description = "Name of a score (from pepxml) to be used as weighting factor for KDE. If not specified all weights are 1.") + protected String weightsScoreName; + @Parameter(names = {"--minsupport"}, + description = "The minimum number of data points supporting a peak for it to be reported.") + protected int gmmMinSupport = 20; + @Parameter(names = {"--cutoff"}, + description = "For each nominal delta mass region only report peaks that are at least this % of the top peak.") + protected double cutoff = 0.025; + @Parameter(names = {"-omf", "--out_mz_fmt"}, + validateWith = DecimalFormatParameter.class, + converter = DecimalFormatParameter.class, + description = "Format to be used in the output for m/z values. See " + + "https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html for details.") + protected DecimalFormatWithToString outMzFmt = new DecimalFormatWithToString("0.#####"); + @Parameter(names = {"-oaf", "--out_ab_fmt"}, + validateWith = DecimalFormatParameter.class, + converter = DecimalFormatParameter.class, + description = "Format for the intensity/abundance in the output file, if peaks are detected. See '-omf'.") + protected DecimalFormatWithToString outIntensityFmt = new DecimalFormatWithToString("0.#E0"); + @Parameter(names = { + "--threads"}, description = "The max number of threads to be used. Zero means use all cores.") + protected int threads = 0; + @Parameter(names = {"--denoise"}, hidden = true, + description = + "Apply some denoising before peak detection. TOTAL_VARIATION takes 1 parameter, " + + "FUSED_LASSO takes 2 parameters (use --denoiseParams to set them).") + protected Denoising denoising = Denoising.FUSED_LASSO; + @Parameter(names = {"--denoisingParams"}, hidden = true, + description = "Parameters for the denoising algorithm (see --denoise).") + protected List denoisingParams = new ArrayList<>(); + @Parameter(names = {"--denoisingPlot"}, hidden = true, + description = "Apply some denoising before peak detection.") + protected boolean denoisingPlot = false; + @Parameter(names = {"--bin"}, hidden = true, + description = + "If your input is in the form of a tab delimited file with numbers (first column - value, optional" + + + " second column - weights), set this flag.") + protected boolean isBinaryInput = false; + @Parameter(names = {"--binDelimiter"}, hidden = true, + description = "Works with --bin only. Each line of text input will be split according to this regular expression.") + protected String delimiterRegEx = "\\s"; + @Parameter(names = {"--binHeader"}, hidden = true, + description = "Works with --bin only. When parsing a text (delimited) file, if set to true will skip the first row.") + protected boolean headerRow = false; + @Parameter(names = {"--binColData"}, hidden = true, + description = "Works with --bin only. If parsing a delimited file, this column will be used as the data input.") + protected Integer colNumData = 0; + @Parameter(names = {"--binColWeight"}, hidden = true, + description = "Works with --bin only. If parsing a delimited file, this column can optionally be used as weights.") + protected Integer colNumWeight = null; + @Parameter(names = {"--gmmNum"}, hidden = true, + description = "How many entries to distribute according to KDE distribution for GMM modeling.") + protected int gmmNumEntries = 10000; + @Parameter(names = {"--gmmMin"}, hidden = true, + description = "Minimum number of entries to be accepted for GMM modeling.") + protected int gmmMinEntries = 1; + +// @Parameter(names = {"-orf", "--out_rt_fmt"}, description = "printf() style format to be used in the output for RT values.") +// protected String outRtFmt = "%.2f"; +// +// @Parameter(names = {"-oif", "--out_int_fmt"}, description = "printf() style format to be used in the output for intensity values.") +// protected String outIntFmt = "%.1f"; + @Parameter(names = {"--kdeminpts"}, hidden = true, + description = + "KDE cutoff threshold. This number of points will be placed at the same exact spot and KDE " + + + "response evaluated. Any KDE values below that value are discarded.") + protected int kdeMinPts = 10; + + public static OpenSearchParams parseParams(String[] args, Appendable out) + throws ParameterException { + log.debug("Program {} started", APP_NAME); + OpenSearchParams params = new OpenSearchParams(); + JCommander jcom = new JCommander(params); + try { + jcom.setProgramName(APP_NAME); + if (args.length == 0) { + jcom.usage(); + return null; + } + jcom.parse(args); + if (params.isHelp()) { + jcom.usage(); + return null; + } + + params.validate(); + + } catch (ParameterException pe) { + log.error(pe.getMessage()); + return null; + } + return params; + } + + public boolean isHelp() { + return help != null && help; + } + + public int getThreads() { + return threads; + } + + public void setThreads(Integer threads) { + this.threads = threads; + } + + /** + * The predicate for filtering search hits based on their mass difference. + * + * @return null if there are no bounds for mass diffs + */ + public MassDiffRangePredicate getPredicateMassRange() { + if (mzLo == null && mzHi == null && clearRange == null) { + return null; + } + double mLo = mzLo == null ? Double.NEGATIVE_INFINITY : mzLo; + double mHi = mzHi == null ? Double.POSITIVE_INFINITY : mzHi; + double[][] clearRanges = null; + if (clearRange != null) { + int numExclusions = clearRange.size() / 2; + clearRanges = new double[numExclusions][2]; + for (int i = 0; i < numExclusions; i++) { + int lo = i * 2; + int hi = lo + 1; + clearRanges[i][0] = clearRange.get(lo); + clearRanges[i][1] = clearRange.get(hi); + } + } + return new MassDiffRangePredicate(mLo, mHi, clearRanges); + } + + public List getMassCorFilePaths() { + return massCorFilePaths; + } + + public int getKdeMinPts() { + return kdeMinPts; + } + + public int getGmmMinSupport() { + return gmmMinSupport; + } + + public int getGmmNumEntries() { + return gmmNumEntries; + } + + public void setGmmNumEntries(int gmmNumEntries) { + this.gmmNumEntries = gmmNumEntries; + } + + public int getGmmMinEntries() { + return gmmMinEntries; + } + + public void setGmmMinEntries(int gmmMinEntries) { + this.gmmMinEntries = gmmMinEntries; + } + + public List getInFilePath() { + return inFilePath; + } + + public void setInFilePath(List inFilePath) { + this.inFilePath = inFilePath; + } + + public String getInFileRegex() { + return inFileRegex; + } + + public void setInFileRegex(String inFileRegex) { + this.inFileRegex = inFileRegex; + } + + public boolean isFollowSymlinks() { + return followSymlinks; + } + + public void setFollowSymlinks(boolean followSymlinks) { + this.followSymlinks = followSymlinks; + } + + public boolean isBinaryInput() { + return isBinaryInput; + } + + public String getDelimiterRegEx() { + return delimiterRegEx; + } + + public boolean isHeaderRow() { + return headerRow; + } + + public Integer getColNumData() { + return colNumData; + } + + public Integer getColNumWeight() { + return colNumWeight; + } + + public LinkOption[] getFollowSymlinkOptions() { + return followSymlinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; + } + + public Boolean getHelp() { + return help; + } + + public void setHelp(Boolean help) { + this.help = help; + } + + public List getDecoyProtPrefix() { + return decoyProtPrefix; + } + + public void setDecoyProtPrefix(List decoyProtPrefix) { + this.decoyProtPrefix = decoyProtPrefix; + } + + public DecoyDetector getDecoyDetector() { + List decoyProtPrefixes = decoyProtPrefix; + DecoyDetector decoyDetector; + if (decoyProtPrefixes == null || decoyProtPrefixes.isEmpty()) { + decoyDetector = new DecoyDetectorAcceptAll(); + } else { + decoyDetector = new DecoyDetectorByProtName(decoyProtPrefixes); + } + return decoyDetector; + } + + public DecimalFormat getOutIntensityFmt() { + return outIntensityFmt; + } + + public void setOutIntensityFmt(DecimalFormatWithToString outIntensityFmt) { + this.outIntensityFmt = outIntensityFmt; + } + + public boolean isDenoisingPlot() { + return denoisingPlot; + } + + public void setDenoisingPlot(boolean denoisingPlot) { + this.denoisingPlot = denoisingPlot; + } + + public Path getFilterFile() { + return filterFile; + } + + public int getFilterCol() { + return filterCol; + } + + public List getScorePredicateFactories() { + return scorePredicateFactories; + } + + public void setScorePredicateFactories(List scorePredicateFactories) { + this.scorePredicateFactories = scorePredicateFactories; + } + + public boolean isUseCache() { + return useCache; + } + + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + public boolean isDeleteCache() { + return deleteCache; + } + + public void setDeleteCache(boolean deleteCache) { + this.deleteCache = deleteCache; + } + + public String getCacheExt() { + return cacheExt; + } + + public void setCacheExt(String cacheExt) { + this.cacheExt = cacheExt; + } + + public List getBandwidths() { + return bandwidths; + } + + public void setBandwidths(List bandwidths) { + this.bandwidths = bandwidths; + } + + public boolean isDynamicBandwidthEstimation() { + return dynamicBandwidthEstimation; + } + + public void setDynamicBandwidthEstimation(boolean dynamicBandwidthEstimation) { + this.dynamicBandwidthEstimation = dynamicBandwidthEstimation; + } + + public List getNamedBandwidths() { + List result = new ArrayList<>(); + if (dynamicBandwidthEstimation) { + result.add(new NamedBandwidth("Auto at each nominal mass", Double.NaN, Double.NaN, + autoBandwidthWindow)); + } + if (!Double.isNaN(autoBandwithTarget)) { + String name = String.format("Auto@%.2f[%.2f]", autoBandwithTarget, autoBandwidthWindow); + NamedBandwidth bandwidth = new NamedBandwidth(name, Double.NaN, autoBandwithTarget, + autoBandwidthWindow); + result.add(bandwidth); + } + for (Double b : bandwidths) { + DecimalFormat fmt = new DecimalFormat("0.##E0"); + String name = fmt.format(b); + result.add(new NamedBandwidth(name, b)); + } + return result; + } + + public List getDenoisingParams() { + return denoisingParams; + } + + public void setDenoisingParams(List denoisingParams) { + this.denoisingParams = denoisingParams; + } + + public Double getAutoBandwithTarget() { + return autoBandwithTarget; + } + + public void setAutoBandwithTarget(Double autoBandwithTarget) { + this.autoBandwithTarget = autoBandwithTarget; + } + + public Double getAutoBandwidthWindow() { + return autoBandwidthWindow; + } + + public void setAutoBandwidthWindow(Double autoBandwidthWindow) { + this.autoBandwidthWindow = autoBandwidthWindow; + } + + public DecoyTreatment getDecoysTreatment() { + return decoysTreatment; + } + + public void setDecoysTreatment(DecoyTreatment decoysTreatment) { + this.decoysTreatment = decoysTreatment; + } + + public KDEKernelType getKernelType() { + return kernelType; + } + + public void setKernelType(KDEKernelType kernelType) { + this.kernelType = kernelType; + } + + public String getWeightsScoreName() { + return weightsScoreName; + } + + public void setWeightsScoreName(String weightsScoreName) { + this.weightsScoreName = weightsScoreName; + } + + public boolean isUseWeights() { + return weightsScoreName != null && !weightsScoreName.isEmpty(); + } + + public WeightFetcherSearchHitDiff.Factory getWeightFetcherFactory() { + if (!isUseWeights()) { + return null; + } + return new WeightFetcherSearchHitDiff.Factory(weightsScoreName); + } + + public Double getMzLo() { + return mzLo; + } + + public void setMzLo(Double mzLo) { + this.mzLo = mzLo; + } + + public Double getMzHi() { + return mzHi; + } + + public void setMzHi(Double mzHi) { + this.mzHi = mzHi; + } + + public double getMzStep() { + return mzStep; + } + + public void setMzStep(double mzStep) { + this.mzStep = mzStep; + } + + public DecimalFormat getOutMzFmt() { + return outMzFmt; + } + + public void setOutMzFmt(DecimalFormatWithToString outMzFmt) { + this.outMzFmt = outMzFmt; + } + + public boolean isForce() { + return force; + } + + public void setForce(boolean force) { + this.force = force; + } + + public Path getOutFilePath() { + return outFilePath; + } + + public void setOutFilePath(Path outFilePath) { + this.outFilePath = outFilePath; + } + + public boolean isPlotKde() { + return plotKde; + } + + public void setPlotKde(boolean plotKde) { + this.plotKde = plotKde; + } + + public boolean isPlotDer2() { + return plotDer2; + } + + public void setPlotDer2(boolean plotDer2) { + this.plotDer2 = plotDer2; + } + + public boolean isDetectPeaks() { + return detectPeaks; + } + + public void setDetectPeaks(boolean detectPeaks) { + this.detectPeaks = detectPeaks; + } + + public MassCorrection getMassCorrection() { + return massCorrection; + } + + public void setMassCorrection(MassCorrection massCorrection) { + this.massCorrection = massCorrection; + } + + public Denoising getDenoising() { + return denoising; + } + + public void setDenoising(Denoising denoising) { + this.denoising = denoising; + } + + /** + * Only run this method after the params have been initialised with actual program input. + */ + public void validate() throws ParameterException { + if (deleteCache) // this should be allowed no matter what + { + return; + } + + if (cacheExt == null || cacheExt.isEmpty()) { + throw new ParameterException( + "Cache file extension ('--cache_ext' option) can not be left empty."); + } + + if (bandwidths.isEmpty() && !dynamicBandwidthEstimation) { + throw new ParameterException( + "You must provide at least one bandwidth using '-b' option or use dynamic " + + "bandwidth estimation ('-ba' option) or provide a specific m/z to be used " + + "for bandwith estimation ('-bm' option). You can also combine those."); + } + + if (dynamicBandwidthEstimation && Double.isNaN(autoBandwidthWindow)) { + throw new ParameterException("When using '-hd' option you must provide '-hw' as well."); + } + + if (!getNamedBandwidths().isEmpty() && detectPeaks && outFilePath != null) { + // in case of peak detection we check if the file paths are available + if (Files.exists(outFilePath)) { + if (!Files.isDirectory(outFilePath)) { + throw new ParameterException("The provided output path exists, but it" + + "s not a directory. This is not allowed."); + } + } else { + // Output path doesn't exist. Try creating it + try { + Files.createDirectories(outFilePath); + } catch (IOException ex) { + throw new ParameterException("Could not create directory structure for the output.", ex); + } + } + if (!Files.isWritable(outFilePath)) { + throw new ParameterException(String + .format("Output path (%s) does not appear to be writeable. Exiting.", outFilePath)); + } + + if (force) { + return; + } + for (NamedBandwidth nb : getNamedBandwidths()) { + Path nbOutputPath = nb.createOutputPath(outFilePath); + if (Files.exists(nbOutputPath)) { + throw new ParameterException( + String.format("You've requested to store the output of peak detection, \n" + + "and did not specify --force option. \n" + + "One of the output files to be generated already exists, \n" + + "won't overwrite: \n" + + "(%s).", nbOutputPath)); + } + } + } + } + + public void setPlotGmm(boolean plotGmm) { + this.plotGmm = plotGmm; + } + + public void setClearRange(List range) { + if (range.size() != 2) { + throw new IllegalArgumentException("Clear List Range size must be excatly 2."); + } + this.clearRange = new ArrayList<>(range.size()); + this.clearRange.addAll(range); + } + + public boolean isRunGui() { + return runGui; + } + + public void setRunGui(boolean runGui) { + this.runGui = runGui; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/PeaksWriter.java b/src/oldMain/java/umich/opensearch/kde/PeaksWriter.java new file mode 100644 index 0000000..71a2a72 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/PeaksWriter.java @@ -0,0 +1,34 @@ +package umich.opensearch.kde; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.util.List; +import umich.opensearch.kde.util.Peak; + +/** + * @author Dmitry Avtonomov + */ +public class PeaksWriter { + + public void write(Path out, OpenSearchParams params, List peaks) throws IOException { + if (!out.toFile().createNewFile()) { + throw new IllegalStateException("Could not create a new output file: " + out.toString()); + } + try (BufferedWriter bw = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(out.toFile(), false), + Charset.forName("UTF-8")))) { + DecimalFormat formatMz = params.getOutMzFmt(); + DecimalFormat formatInt = params.getOutIntensityFmt(); + for (Peak peak : peaks) { + bw.write(formatMz.format(peak.x)); + bw.write("\t"); + bw.write(formatInt.format(peak.y)); + bw.newLine(); + } + + bw.flush(); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/Version.java b/src/oldMain/java/umich/opensearch/kde/Version.java new file mode 100644 index 0000000..fdaecf9 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/Version.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde; + +/** + * @author Dmitry Avtonomov + */ +public class Version { + + public static final String version = "1.5"; + + public static String getVersion() { + return version; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/api/IWeightedData.java b/src/oldMain/java/umich/opensearch/kde/api/IWeightedData.java new file mode 100644 index 0000000..075bc6f --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/api/IWeightedData.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.api; + +/** + * @author Dmitry Avtonomov + */ +public interface IWeightedData { + + double[] getData(); + + double[] getWeights(); +} diff --git a/src/oldMain/java/umich/opensearch/kde/api/IWeightedValues.java b/src/oldMain/java/umich/opensearch/kde/api/IWeightedValues.java new file mode 100644 index 0000000..8469b60 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/api/IWeightedValues.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.api; + +public interface IWeightedValues { + + int size(); + + double val(int i); + + double weight(int i); +} diff --git a/src/oldMain/java/umich/opensearch/kde/api/Pair.java b/src/oldMain/java/umich/opensearch/kde/api/Pair.java new file mode 100644 index 0000000..0e5e158 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/api/Pair.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.api; + +import java.io.Serializable; + +/** + * Pairs can be compared based on their first value. + */ +public class Pair> implements Comparable>, Serializable { + + private static final long serialVersionUID = 200541829177602541L; + public final T v1; + public final T v2; + + public Pair(T v1, T v2) { + this.v1 = v1; + this.v2 = v2; + } + + @Override + public int compareTo(Pair o) { + int result = v1.compareTo(o.v1); + if (result == 0) { + result = v2.compareTo(o.v2); + } + return result; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/api/PepXmlContent.java b/src/oldMain/java/umich/opensearch/kde/api/PepXmlContent.java new file mode 100644 index 0000000..d1c6851 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/api/PepXmlContent.java @@ -0,0 +1,557 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.api; + +import static java.lang.System.out; + +import com.github.chhh.utils.FileUtils; +import com.github.chhh.utils.LogUtils; +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.exceptions.ParsingException; +import com.github.chhh.utils.files.FileSizeUnit; +import com.github.chhh.utils.ser.DataStoreUtils; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.ms.fileio.exceptions.FileParsingException; +import umich.ms.fileio.filetypes.pepxml.PepXmlParser; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.MsmsPipelineAnalysis; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.MsmsRunSummary; +import umich.opensearch.kde.KDEMain; +import umich.opensearch.kde.OpenSearchParams; +import umich.opensearch.kde.params.MassCorrection; +import umich.opensearch.kde.params.predicates.IsDecoyPredicate; +import umich.opensearch.kde.params.predicates.IsForwardPredicate; +import umich.opensearch.kde.params.predicates.NamedPredicate; +import umich.opensearch.kde.params.predicates.ScorePredicate; +import umich.opensearch.kde.pepxml.DecoyDetector; +import umich.opensearch.kde.pepxml.MsmsRunSummaryUtils; +import umich.opensearch.kde.util.SimpleConcurrentList; + +/** + * A storage for the contents of a single .pep.xml file. + * + * @author Dmitry Avtonomov + */ +public class PepXmlContent implements Serializable { + + private static final long serialVersionUID = 2912576418487478090L; + + private static final Logger log = LoggerFactory.getLogger(PepXmlContent.class); + private static final boolean DEBUG = false; + private static final String TAG_RUN_SUMMARY = "msms_run_summary"; + private List hitsList; + private URI uri; + + public PepXmlContent(URI uri) { + if (uri == null) { + throw new IllegalArgumentException("URI must be non null"); + } + this.hitsList = new ArrayList<>(); + this.uri = uri; + } + + public PepXmlContent(List hitsList) { + this.hitsList = hitsList; + } + + public static List getPepXmlContents(final OpenSearchParams params, + List matchingPaths) { + final ArrayList pepXmlList = new SimpleConcurrentList<>(); + + long timeLo = System.nanoTime(); + + int numCores = Runtime.getRuntime().availableProcessors(); + int numThreadsToUse = numCores; + if (params.getThreads() > 0) { + numThreadsToUse = Math.min(numCores, params.getThreads()); + } + ExecutorService exec = Executors.newFixedThreadPool(numThreadsToUse); + + final int memPerThreadMb = 32; + final int bufSize = memPerThreadMb * 1024 * 1024; + + for (final Path path : matchingPaths) { + exec.execute(() -> parseSingleFile(params, pepXmlList, path, bufSize)); + } + + exec.shutdown(); + try { + exec.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); + } catch (InterruptedException e) { + log.error("Something awful happened, could not stop Executor Service"); + System.exit(1); + } + + long timeHi = System.nanoTime(); + + return pepXmlList; + } + + /** + * Can be called asynchhronously, so please synchronize access to 'toAddTo' list. + * + * @param toAddTo Synchronize access to this list. That's where all the results are stored. + * @param pepXmlPath Path to a pep.xml file to be parsed. + * @param bufSize Buffer size used for reading pep.xml. Use 8192 if unsure. + */ + public static void parseSingleFile(OpenSearchParams params, + final ArrayList toAddTo, Path pepXmlPath, int bufSize) { + if (Thread.interrupted()) { + return; + } + try { + // if caching is on and we find a cached file - just read it + PepXmlContent pepXmlContent = null; + boolean doesNeedSerialization = false; + MassCorrection massCorrection = params.getMassCorrection(); + if (params.isUseCache()) { + Path pathToCached = checkCacheExists(pepXmlPath, params); + if (pathToCached != null) { + // deserialize + try { + log.info( + String.format("Cached entry '%s' was found for file '%s', trying to deserialize.", + pathToCached.toString(), pepXmlPath.toString())); + pepXmlContent = parseCached(pathToCached); + log.debug("Done deserializing '{}'", pathToCached); + + // check that serialized data had the right mass correction + boolean isRightMassCorrection = true; + if (massCorrection != MassCorrection.NONE) { + for (SearchHitDiffs searchHitDiffs : pepXmlContent.hitsList) { + if (searchHitDiffs.getMassCorrection() != massCorrection) { + isRightMassCorrection = false; + break; + } + } + } + + if (isRightMassCorrection) { + doesNeedSerialization = false; // all fine, no need to rewrite the data to disk + } else { + // fix the correction in the cache + log.info(String.format("Cached file '%s' had wrong mass correction, fixing", + pathToCached.toString())); + switch (massCorrection) { + case NONE: // unreachable + break; + case ZERO_PEAK: + for (SearchHitDiffs searchHitDiffs : pepXmlContent.hitsList) { + MassCorrection.correctMasses(searchHitDiffs, massCorrection, null); + } + break; + case CAL_FILES: + HashMap massCal = parseMassCorrectionFile(pepXmlPath.toUri(), + params); + for (SearchHitDiffs searchHitDiffs : pepXmlContent.hitsList) { + MassCorrection.correctMasses(searchHitDiffs, massCorrection, massCal); + } + break; + } + doesNeedSerialization = true; // setting this to false will trigger re-saving the data to disk + } + + } catch (Exception e) { + // we couldn't deserialize, that's not the end of the world, we can still try re-parse + log.error(String.format( + "Could not deserialize cached file '%s', falling back to reading the original.", + pathToCached)); + } + } + } + + // parse from file and save (apply mass correction before removing entries) + if (pepXmlContent == null) { + doesNeedSerialization = true; + log.info(String.format("Unmarshalling '%s' file '%s' (%.2fMB)", + MsmsPipelineAnalysis.class.getSimpleName(), + pepXmlPath.toString(), FileUtils.fileSize(pepXmlPath, FileSizeUnit.MB))); + + try (BufferedInputStream bis = new BufferedInputStream( + new FileInputStream(pepXmlPath.toFile()), bufSize)) { + pepXmlContent = parse(pepXmlPath.toUri(), bis, params); + if (pepXmlContent == null) { + throw new IOException("Parsing returned null"); + } + + // we might need to do mass correction + if (massCorrection != MassCorrection.NONE) { + log.info(String + .format("Performing mass correction for file '%s'", pepXmlContent.uri.toString())); + switch (massCorrection) { + case NONE: // unreachable + break; + case ZERO_PEAK: + for (SearchHitDiffs searchHitDiffs : pepXmlContent.hitsList) { + MassCorrection.correctMasses(searchHitDiffs, massCorrection, null); + } + break; + case CAL_FILES: + HashMap massCal = parseMassCorrectionFile(pepXmlPath.toUri(), + params); + for (SearchHitDiffs searchHitDiffs : pepXmlContent.hitsList) { + MassCorrection.correctMasses(searchHitDiffs, massCorrection, massCal); + } + break; + } + log.debug("Done running mass correction for '{}'", pepXmlContent.uri.toString()); + } + } catch (IOException e) { + log.error(String + .format("Error unmarshalling file '%s'.\n%s", pepXmlPath.toString(), e.getMessage()), + e); + } + + + } + + if (pepXmlContent == null) { + // we couldn't parse or deserialize the file, just move on to the next one + String msg = String + .format("Could not parse or deserialize '%s', skipping", pepXmlPath.toString()); + log.error(msg); + LogUtils.println(out, msg); + return; + } + synchronized (PepXmlContent.class) { + toAddTo.add(pepXmlContent); + } + + log.debug("Done unmarshalling '{}' from file '{}'", + MsmsPipelineAnalysis.class.getSimpleName(), pepXmlPath.toString()); + + if (params.isUseCache() && doesNeedSerialization) { + // delete a previously saved file + // this is only hit if we had problems deserializing, and proceeded to just reparsing the file + Path pathToCachedFile = checkCacheExists(pepXmlPath, params); + if (pathToCachedFile != null) { + log.info("Deleting old cache file (" + pathToCachedFile.toString() + ")"); + try { + Files.delete(pathToCachedFile); + } catch (IOException e) { + String msg = String + .format("Could not delete cached file '%s', reason: %s", pepXmlPath.toString(), + e.getMessage()); + log.error(msg, e); + LogUtils.println(out, msg); + } + } + // and store the new cached version + Path cachedPath = createCachedPath(pepXmlPath, params); + log.info("Writing new cache file (" + cachedPath.toString() + ")"); + DataStoreUtils.serialize(cachedPath, pepXmlContent); + } + + // remove entries that we are not interested in + pepXmlContent.filter(params); + + } catch (ParsingException e) { + log.error("Could not parse file '%s'", pepXmlPath.toString(), e); + } + } + + public static void deleteCache(OpenSearchParams params) { + List matchingPaths = KDEMain.findMatchingPaths(params); + log.info(String.format("Found %d files matching input (single files and '%s' pattern) in '%s'", + matchingPaths.size(), params.getInFileRegex(), + StringUtils.join(params.getInFilePath(), ", "))); + for (Path p : matchingPaths) { + Path cachedPath = checkCacheExists(p, params); + if (cachedPath != null) { + try { + log.info( + String.format("Found cached file '%s', will delete now.", cachedPath.toString())); + Files.delete(cachedPath); + } catch (IOException e) { + log.error("Could not delete cached file", e); + } + } + } + } + + /** + * Checks if there is a cached file for the original pep.xml. + * + * @param pathOrig path to the original file to be checked for cache existence + * @return null if a cached file does not exist + */ + public static Path checkCacheExists(Path pathOrig, OpenSearchParams params) { + Path pathToCached = createCachedPath(pathOrig, params); + if (Files.exists(pathToCached, params.getFollowSymlinkOptions())) { + return pathToCached; + } + return null; + } + + public static PepXmlContent parseCached(Path pathToCached) throws ParsingException { + PepXmlContent pepXmlContent = DataStoreUtils.deserialize(pathToCached, PepXmlContent.class); + pepXmlContent.sortByMassDiff(); + return pepXmlContent; + } + + public static Path createCachedPath(Path pathOrig, OpenSearchParams params) { + return Paths.get(pathOrig.toString() + params.getCacheExt()); + } + + /** + * Parse the diffs from a stream. + * + * @param uri URI of the source of the input stream, e.g. a file. + * @param is the stream to parse XML from. If you want buffering, wrap it into a buffered stream + * yourself. + * @return null if thread was interrupted or + */ + public static PepXmlContent parse(URI uri, InputStream is, OpenSearchParams params) + throws ParsingException { + if (Thread.interrupted()) { + return null; + } + PepXmlContent pepXmlContent = new PepXmlContent(uri); + + try (BufferedInputStream bis = new BufferedInputStream(is)) { + // we'll manually iterate over msmsRunSummaries - won't need so much memory at once for processing large files. + Iterator pepIt = PepXmlParser.parse(bis); + while (pepIt.hasNext()) { + long timeLo = System.nanoTime(); + MsmsRunSummary runSummary = pepIt.next(); + long timeHi = System.nanoTime(); + if (DEBUG) { + System.err.printf("Unmarshalling took %.4fms (%.2fs)\n", (timeHi - timeLo) / 1e6, + (timeHi - timeLo) / 1e9); + } + + if (runSummary.getSpectrumQuery().isEmpty()) { + log.warn(String.format( + "Parsed msms_run_summary was empty for file '%s', summary base_name '%s'", + uri.toString(), runSummary.getBaseName())); + } + DecoyDetector decoyDetector = params.getDecoyDetector(); + SearchHitDiffs searchHitDiffs = MsmsRunSummaryUtils + .convert(runSummary, decoyDetector, null); + pepXmlContent.getHitsList().add(searchHitDiffs); + } + return pepXmlContent; + + } catch (IOException | FileParsingException e) { + throw new ParsingException(e); + } + } + + /** + * Parse mass calibration from .masscor files. + * + * @param uri The URI of the pepxml file. + * @return Mapping from spectral identifiers to masses. + */ + public static HashMap parseMassCorrectionFile(URI uri, OpenSearchParams params) + throws ParsingException { + HashMap massCal = null; + if (params.getMassCorrection() == MassCorrection.CAL_FILES) { + Path pathPepXml = Paths.get(uri); + + List possiblePaths = new ArrayList<>(); + + Path pathCalFile = Paths.get(pathPepXml.toString() + ".masscorr"); + + if (!params.getMassCorFilePaths().isEmpty()) { + Path fileName = pathCalFile.getFileName(); + for (Path p : params.getMassCorFilePaths()) { + if (!Files.isDirectory(p)) { + continue; + } + Path possiblePath = p.resolve(fileName); + if (Files.exists(possiblePath)) { + possiblePaths.add(possiblePath); + } + } + } else { + if (Files.exists(pathCalFile)) { + possiblePaths.add(pathCalFile); + } + } + + if (possiblePaths.isEmpty()) { + log.info("Mass correction file for pepxml not found (" + uri + ")"); + return null; + } + + log.info(String.format("Found mass correction file: %s", pathCalFile)); + File fileCal = possiblePaths.get(0).toFile(); // take the first one + massCal = new HashMap<>(10000); + try (BufferedReader br = new BufferedReader( + new InputStreamReader(new FileInputStream(fileCal), Charset.forName("UTF-8")))) { + String line; + String[] split; + Pattern splitRegex = Pattern.compile("\\s+"); + double massDiffCorrected; + String spectrumQueryString; + while ((line = br.readLine()) != null) { + split = splitRegex.split(line); + spectrumQueryString = split[0]; + massDiffCorrected = Double.parseDouble(split[2]); + massCal.put(spectrumQueryString, massDiffCorrected); + } + } catch (IOException e) { + throw new ParsingException( + "Found matching calibration file, but something went wrong while reading it", e); + } + + } + return massCal; + } + + private static boolean advanceReaderToNextRunSummary(XMLStreamReader xsr) + throws XMLStreamException { + long timeLo = System.nanoTime(); + do { + if (xsr.next() == XMLStreamConstants.END_DOCUMENT) { + return false; + } + } while (!(xsr.isStartElement() && xsr.getLocalName().equals(TAG_RUN_SUMMARY))); + + long timeHi = System.nanoTime(); + if (DEBUG) { + System.err.printf("Advancing reader took: %.4fms\n", (timeHi - timeLo) / 1e6d); + } + + return true; + } + + public URI getUri() { + return uri; + } + + public List getHitsList() { + return hitsList; + } + + public int size() { + int size = 0; + for (SearchHitDiffs searchHitDiffs : hitsList) { + size += searchHitDiffs.getHits().size(); + } + return size; + } + + public boolean isEmpty() { + return size() == 0; + } + + public void sortByMassDiff() { + for (SearchHitDiffs searchHitDiffs : hitsList) { + if (!searchHitDiffs.isSorted()) { + searchHitDiffs.sortByDiffMass(); + } + } + } + + /** + * Remove everything that doesn't meet the criteria from params. + */ + public int filter(OpenSearchParams params) { + NamedPredicate predicate; + int totalRemoved = 0; + boolean didFiltering = false; + for (SearchHitDiffs searchHitDiffs : hitsList) { + log.debug("Filtering started."); + List> predicates = new ArrayList<>(10); + switch (params.getDecoysTreatment()) { + case FORWARDS_ONLY: + log.debug("Creating IsForwardPredicate"); + predicate = new IsForwardPredicate(); // leave forwards + predicates.add(predicate); + break; + case DECOYS_ONLY: + log.debug("Creating IsDecoyPredicate"); + predicate = new IsDecoyPredicate(); // leave decoys + predicates.add(predicate); + break; + case USE_BOTH: + // not removing neither decoys nor forwards, don't need to add any predicates + break; + default: + throw new IllegalStateException( + "Unknown enum value encountered for params.getDecoysTreatment()"); + } + predicate = params.getPredicateMassRange(); + if (predicate != null) { + log.debug("Creating MassRange predicate"); + predicates.add(predicate); + } + List factories = params.getScorePredicateFactories(); + if (factories != null && !factories.isEmpty()) { + log.debug("Found {} score factories, creating predicates for them", factories.size()); + for (ScorePredicate.Factory factory : factories) { + log.debug("Creating predicate '{}'", factory.toString()); + try { + predicate = factory.create(searchHitDiffs.getMapScoreName2Index()); + predicates.add(predicate); + } catch (ParsingException e) { + String msg = "Could not configure score predicate, probably the file didn't have that score in the first place, skipping that filter."; + log.error(msg, e); + LogUtils.println(out, msg + "\n" + e.getMessage()); + throw new RuntimeException(e); + } + } + } + log.debug("Done composing predicates for filtering, total {} predicates.", predicates.size()); + if (!predicates.isEmpty()) { + didFiltering = true; + log.info("Filtering MsMsRun '{}' from file '{}'", searchHitDiffs.getName(), uri.toString()); + for (NamedPredicate p : predicates) { + log.info("\tLeaving sequences matching: {}", p.getDescription()); + } + Predicate criteria = + predicates.size() == 1 ? predicates.get(0) : Predicates.and(predicates); + int filtered = searchHitDiffs.filter(criteria); + totalRemoved += filtered; + + log.info("\tRemoved {} entries, {} entries remaining", filtered, + searchHitDiffs.getHits().size()); + } + log.debug("Filtering done."); + } + + if (didFiltering) { + int remainingEntries = 0; + for (SearchHitDiffs searchHitDiffs : hitsList) { + remainingEntries += searchHitDiffs.getHits().size(); + } + log.info("Overall removed {} entries, {} entries remaining", totalRemoved, remainingEntries); + } + + return totalRemoved; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/api/SearchHitDiff.java b/src/oldMain/java/umich/opensearch/kde/api/SearchHitDiff.java new file mode 100644 index 0000000..8962814 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/api/SearchHitDiff.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.api; + +import java.io.Serializable; + +/** + * @author Dmitry Avtonomov + */ +public class SearchHitDiff implements Serializable, Comparable { + + private static final long serialVersionUID = -7250766622941392404L; + + String spectrumId = ""; + double massDiff; + double massDiffCal = Double.NaN; + String seq; + int charge; + double observedNeutralMass; + double calcedNeutralMass; + double rtSec = Double.NaN; + int scanNum = Integer.MIN_VALUE; + double[] scores; + boolean isDecoy; + + public SearchHitDiff(int numScores) { + scores = new double[numScores]; + } + + public String getSpectrumId() { + return spectrumId; + } + + public void setSpectrumId(String spectrumId) { + this.spectrumId = spectrumId; + } + + public double getMassDiffCal() { + return massDiffCal; + } + + public void setMassDiffCal(double massDiffCal) { + this.massDiffCal = massDiffCal; + } + + public double getRtSec() { + return rtSec; + } + + public void setRtSec(double rtSec) { + this.rtSec = rtSec; + } + + public int getScanNum() { + return scanNum; + } + + public void setScanNum(int scanNum) { + this.scanNum = scanNum; + } + + public double[] getScores() { + return scores; + } + + public boolean isDecoy() { + return isDecoy; + } + + public void setDecoy(boolean isDecoy) { + this.isDecoy = isDecoy; + } + + public double getMassDiff() { + return massDiff; + } + + public void setMassDiff(double massDiff) { + this.massDiff = massDiff; + } + + public String getSeq() { + return seq; + } + + public void setSeq(String seq) { + this.seq = seq; + } + + public int getCharge() { + return charge; + } + + public void setCharge(int charge) { + this.charge = charge; + } + + public double getObservedNeutralMass() { + return observedNeutralMass; + } + + public void setObservedNeutralMass(double observedNeutralMass) { + this.observedNeutralMass = observedNeutralMass; + } + + public double getCalcedNeutralMass() { + return calcedNeutralMass; + } + + public void setCalcedNeutralMass(double calcedNeutralMass) { + this.calcedNeutralMass = calcedNeutralMass; + } + + @Override + public String toString() { + return "{" + + "M=" + calcedNeutralMass + + ", Mobs=" + observedNeutralMass + + ", z=" + charge + + ", seq='" + seq + '\'' + + ", dM=" + String.format("%.5f", massDiff) + + '}'; + + } + + @Override + public int compareTo(SearchHitDiff o) { + return Double.compare(getMassDiff(), o.getMassDiff()); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/api/SearchHitDiffs.java b/src/oldMain/java/umich/opensearch/kde/api/SearchHitDiffs.java new file mode 100644 index 0000000..bde9876 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/api/SearchHitDiffs.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.api; + +import com.github.chhh.utils.exceptions.ParsingException; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import java.io.IOException; +import java.io.Serializable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.params.MassCorrection; +import umich.opensearch.kde.params.predicates.ScorePredicate; + +/** + * @author Dmitry Avtonomov + */ +public class SearchHitDiffs implements Serializable { + + public static final Logger log = LoggerFactory.getLogger(SearchHitDiffs.class); + private static final long serialVersionUID = 47769708261008276L; + private List hits; + private MassCorrection massCorrection; + private volatile boolean isSorted = false; + private Map mapScoreName2Index = new HashMap<>(10); + private String name = ""; + + public SearchHitDiffs(List hits, Map scoreMap, + MassCorrection massCorrection) { + this.hits = hits; + this.mapScoreName2Index = scoreMap; + this.massCorrection = massCorrection; + } + + public static double[] getMassDiffsArray(List shds) { + double[] diffs = new double[shds.size()]; + for (int i = 0; i < diffs.length; i++) { + diffs[i] = shds.get(i).getMassDiff(); + } + return diffs; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MassCorrection getMassCorrection() { + return massCorrection; + } + + public void setMassCorrection(MassCorrection massCorrection) { + this.massCorrection = massCorrection; + } + + public Map getMapScoreName2Index() { + return mapScoreName2Index; + } + + public boolean isSorted() { + return isSorted; + } + + public List getHits() { + return hits; + } + + public void sortByDiffMass() { + hits.sort(Comparator.comparingDouble(SearchHitDiff::getMassDiffCal)); + isSorted = true; + } + + public double[] getMassDiffsArray() { + double[] diffs = new double[hits.size()]; + for (int i = 0; i < hits.size(); i++) { + diffs[i] = hits.get(i).getMassDiff(); + } + return diffs; + } + + /** + * @param weightIdx the index of the score from pep.xml file to be used as a weight + */ + public double[] getWeightsArray(int weightIdx) { + double[] weights = new double[hits.size()]; + for (int i = 0; i < hits.size(); i++) { + weights[i] = hits.get(i).getScores()[weightIdx]; + } + return weights; + } + + public void printPepsByMass(double mzLo, double mzHi, int maxCountToPrint) { + printPepsByMass(System.out, mzLo, mzHi, maxCountToPrint); + } + + public void printPepsByMass(Appendable out, double mzLo, double mzHi, int maxCountToPrint) { + try { + if (!isSorted) { + throw new IllegalStateException( + "You need to call .sortByDiffMass() before calling this method."); + } + SearchHitDiff shd = new SearchHitDiff(1); + + out.append(String.format("Peptides in mass diff range [%.4f; %.4f]:\n", mzLo, mzHi)); + + shd.setMassDiff(mzLo); + + int idxLo = findPepIndex(shd); + if (idxLo < 0) { + idxLo = -idxLo - 1; + } + + shd.setMassDiff(mzHi); + int idxHi = findPepIndex(shd); + if (idxHi < 0) { + idxHi = -idxHi - 1; + } + List found = hits.subList(idxLo, idxHi); + HashMap map = new HashMap<>(); + for (SearchHitDiff searchHitDiff : found) { + map.merge(searchHitDiff.getSeq(), 1, (a, b) -> a + b); + } + int countPrinted = 0; + for (Map.Entry entry : map.entrySet()) { + out.append(String.format("\t%s : %d hits\n", entry.getKey(), entry.getValue())); + if (++countPrinted >= maxCountToPrint) { + break; + } + } + if (map.size() > maxCountToPrint) { + out.append("\tPeptide list too long, not printing all peptides\n"); + } + out.append(String + .format("\tTotal %d distinct sequences, %d overall hits\n", map.size(), found.size())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private int findPepIndex(SearchHitDiff shd) { + return Collections + .binarySearch(hits, shd, Comparator.comparingDouble(SearchHitDiff::getMassDiff)); + } + + public List findDiffsByMassDiffRange(double mLo, double mHi) { + Comparator comparator = Comparator.comparingDouble(SearchHitDiff::getMassDiff); + SearchHitDiff lo = new SearchHitDiff(0); + lo.setMassDiff(mLo); + int idxLo = Collections.binarySearch(hits, lo, comparator); + SearchHitDiff hi = new SearchHitDiff(0); + hi.setMassDiff(mHi); + int idxHi = Collections.binarySearch(hits, hi, comparator); + + if (idxLo < 0) { + idxLo = -idxLo - 1; + } + + if (idxHi < 0) { + idxHi = -idxHi - 1; + } + + return hits.subList(idxLo, idxHi); + } + + + /** + * Remove entries that don't fit criteria. + * + * @return number of entries removed + */ + public int filter(Predicate criteria) { + return filter(criteria, new ScorePredicate.Factory[]{}); + } + + /** + * Remove entries that don't fit criteria. + * + * @param criteria will be AND'ed with whatever predicates are produced by {@code + * scorePredicateFactories} + * @param scorePredicateFactories this has to be passed in separately, because ScorePredicates + * need to be configured before being used, and that factory configures the predicate. + * @return number of entries removed + */ + public int filter(Predicate criteria, + ScorePredicate.Factory... scorePredicateFactories) { + // generate criteria + if (scorePredicateFactories != null && scorePredicateFactories.length > 0) { + List> predicates = new ArrayList<>(); + predicates.add(criteria); + for (ScorePredicate.Factory factory : scorePredicateFactories) { + log.debug("Creating predicate '{}'", factory.toString()); + try { + ScorePredicate predicate = factory.create(mapScoreName2Index); + predicates.add(predicate); + } catch (ParsingException e) { + String msg = "Could not configure score predicate, probably the file didn't have that score in the first place, skipping that filter."; + log.error(msg, e); + throw new RuntimeException(e); + //LogUtils.println(out, msg + "\n" + e.getMessage()); + } + } + criteria = Predicates.and(predicates); + } + + // find the array size + int toBeLeft = 0, startingSize = hits.size(); + List hitsList = getHits(); + for (SearchHitDiff hit : hitsList) { + if (criteria.apply(hit)) { + toBeLeft++; + } + } + if (toBeLeft == startingSize) { + return 0; // all good, nothing to do + } + + SearchHitDiff[] filtered = new SearchHitDiff[toBeLeft]; + int i = 0; + for (SearchHitDiff hit : hitsList) { + if (criteria.apply(hit)) { + filtered[i++] = hit; + } + } + this.hits = Arrays.asList(filtered); + return startingSize - filtered.length; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/gui/KDEGuiProps.java b/src/oldMain/java/umich/opensearch/kde/gui/KDEGuiProps.java new file mode 100644 index 0000000..72359de --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/gui/KDEGuiProps.java @@ -0,0 +1,75 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package umich.opensearch.kde.gui; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author dmitriya + */ +public class KDEGuiProps extends Properties { + + public static final String PROP_FILE_IN = "path.file.in"; + public static final String PROP_FILE_FILTER = "path.file.filter"; + public static final String PROP_FILE_OUT = "path.file.out"; + public static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); + public static final String TEMP_FILE_NAME = "opensearchkde.cache"; + private static final long serialVersionUID = -6019756883432498605L; + private static final Logger log = LoggerFactory.getLogger(KDEGuiProps.class); + + private static Path getTempPath() { + return Paths.get(TEMP_DIR, TEMP_FILE_NAME); + } + + public static void deleteTemp() { + try { + Files.deleteIfExists(getTempPath()); + } catch (IOException e) { + log.error("Error while trying to delete stored cache file", e); + } + } + + public static KDEGuiProps loadFromTemp() { + Path path = getTempPath(); + if (!Files.exists(path)) { + return null; + } + try { + KDEGuiProps props = new KDEGuiProps(); + props.load(new FileInputStream(path.toFile())); + return props; + + } catch (IOException ex) { + log.warn("Could not load properties from temporary directory: {}", ex.getMessage()); + } + + return null; + } + + public static void save(String propName, String propValue) { + KDEGuiProps props = KDEGuiProps.loadFromTemp(); + props = props == null ? new KDEGuiProps() : props; + props.put(propName, propValue); + props.save(); + } + + public void save() { + Path path = getTempPath(); + try (FileOutputStream fos = new FileOutputStream(path.toFile())) { + store(fos, "Open Search KDE runtime properties"); + } catch (IOException ex) { + log.warn("Could not load properties from temporary directory: {}", ex.getMessage()); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.form b/src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.form new file mode 100644 index 0000000..dd5e71a --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.form @@ -0,0 +1,920 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.java b/src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.java new file mode 100644 index 0000000..be1f35e --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/gui/KDEOptionsGui.java @@ -0,0 +1,1789 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package umich.opensearch.kde.gui; + +import com.beust.jcommander.ParameterException; +import com.github.chhh.utils.LogUtils; +import com.github.chhh.utils.SwingUtils; +import com.github.chhh.utils.swing.TextConsole; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.SpinnerModel; +import javax.swing.SpinnerNumberModel; +import javax.swing.UIManager; +import javax.swing.filechooser.FileFilter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.KDEMain; +import umich.opensearch.kde.OpenSearchParams; +import umich.opensearch.kde.Version; +import umich.opensearch.kde.jfree.JFreeChartPlot; +import umich.opensearch.kde.logging.LogbackJTextPaneAppender; +import umich.opensearch.kde.params.DecoyTreatment; +import umich.opensearch.kde.params.KDEKernelType; +import umich.opensearch.kde.params.MassCorrection; +import umich.opensearch.kde.params.PepXmlScore; +import umich.opensearch.kde.params.ScorePredicateFactoryParameter; +import umich.opensearch.kde.params.denoise.Denoising; +import umich.opensearch.kde.params.predicates.ComparisonType; +import umich.opensearch.kde.params.predicates.ScorePredicate; + +/** + * @author dmitriya + */ +public class KDEOptionsGui extends javax.swing.JFrame { + + public static final String FRAME_NAME = "OpenSearch KDE"; + public static final List ICON_NAMES = Arrays.asList( + "delta_logo_16.png", "delta_logo_32.png", "delta_logo_48.png", + "delta_logo_64.png", "delta_logo_96.png", "delta_logo_128.png", "delta_logo_256.png"); + public static final String PROP_PATHIN = "pathIn"; + public static final String PROP_PATHOUT = "pathOut"; + private static final Logger log = LoggerFactory.getLogger(KDEOptionsGui.class); + private static final String SPLIT = ", "; + private static final String STR_PARAM_ERROR = "Parameter error"; + private static final String TEMP_FILE_NAME = "opensearchkde.cache"; + private static double DEFAULT_BANDWIDTH_FOR_GUI = 0.001; + private transient final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport( + this); + protected ExecutorService exec; + private LogbackJTextPaneAppender appender; + private TextConsole console; + private OpenSearchParams paramsDefault = new OpenSearchParams(); + private String pathIn; + private String pathOut; + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox boxClearDmRange; + private javax.swing.JCheckBox boxDetectPeaks; + private javax.swing.JCheckBox boxDoPlot; + private javax.swing.JCheckBox boxForce; + private javax.swing.JCheckBox boxMassDependent; + private javax.swing.JCheckBox boxUseCache; + private javax.swing.JButton btnBrowseFilterFile; + private javax.swing.JButton btnConsoleClear; + private javax.swing.JButton btnDeleteCache; + private javax.swing.JButton btnFileIn; + private javax.swing.JButton btnFileOut; + private javax.swing.JButton btnFilterAdd; + private javax.swing.JButton btnFilterRemove; + private javax.swing.JButton btnGmmReset; + private javax.swing.JButton btnGo; + private javax.swing.JComboBox comboDecoyTreatment; + private javax.swing.JComboBox comboFilterOperation; + private javax.swing.JComboBox comboFilterScoreName; + private javax.swing.JComboBox comboKernelType; + private javax.swing.JComboBox comboMassCorrection; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; + private javax.swing.JLabel jLabel11; + private javax.swing.JLabel jLabel12; + private javax.swing.JLabel jLabel13; + private javax.swing.JLabel jLabel14; + private javax.swing.JLabel jLabel15; + private javax.swing.JLabel jLabel16; + private javax.swing.JLabel jLabel17; + private javax.swing.JLabel jLabel18; + private javax.swing.JLabel jLabel19; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel20; + private javax.swing.JLabel jLabel21; + private javax.swing.JLabel jLabel22; + private javax.swing.JLabel jLabel23; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JLabel jLabel9; + private javax.swing.JPanel panelCache; + private javax.swing.JPanel panelFilters; + private javax.swing.JPanel panelKDE; + private javax.swing.JPanel panelOutput; + private javax.swing.JPanel panelPeakDetection; + private javax.swing.JScrollPane scrollPaneConsole; + private javax.swing.JSpinner spinnerClearDmHi; + private javax.swing.JSpinner spinnerClearDmLo; + private javax.swing.JSpinner spinnerMassWindowForBandwidthEstimate; + private javax.swing.JTextField textFilterFile; + private javax.swing.JTextField txtBandwidths; + private javax.swing.JTextField txtCacheExt; + private javax.swing.JTextField txtDecoyPrefix; + private javax.swing.JTextField txtFileIn; + private javax.swing.JTextField txtFileOut; + private javax.swing.JTextField txtFileRegex; + private javax.swing.JTextField txtFilter; + private javax.swing.JTextField txtFilterValue; + private javax.swing.JTextField txtGmmMinPts; + private javax.swing.JTextField txtGmmNumPts; + private javax.swing.JTextField txtMassForBandwidthEstimate; + private javax.swing.JTextField txtMassHi; + private javax.swing.JTextField txtMassLo; + private javax.swing.JTextField txtMassStep; + private javax.swing.JTextField txtWeightScoreName; + /** + * Creates new form KDEOptionsGui + */ + public KDEOptionsGui() { + initComponents(); + initMore(); + } + + /** + * @param params can be null, otherwise these params will be used as defaults in GUI. + */ + public static void initAndRun(final OpenSearchParams params) { + /* Set the Nimbus look and feel */ + // + /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. + * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html + */ + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) { + java.util.logging.Logger.getLogger(KDEOptionsGui.class.getName()) + .log(java.util.logging.Level.SEVERE, null, ex); + } + // + + /* Create and display the form */ + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + KDEOptionsGui kdeOptionsGui = new KDEOptionsGui(); + kdeOptionsGui.pack(); + SwingUtils.centerFrame(kdeOptionsGui); + kdeOptionsGui.setVisible(true); + if (params != null) { + kdeOptionsGui.setParamsDefault(params); + } + // set up logging to print to the JTextPane console + + } + }); + } + + private static List splitTrim(String input, String sep) { + String[] split = input.split(sep); + List strings = new ArrayList<>(split.length); + for (int i = 0; i < split.length; i++) { + split[i] = split[i].trim(); + if (!split[i].isEmpty()) { + strings.add(split[i]); + } + } + return strings; + } + + private void initMore() { + umich.opensearch.kde.logging.LogHelper.configureJavaUtilLogging(); + + setTitle(FRAME_NAME + " v" + Version.version); + + exec = Executors.newCachedThreadPool(); + SwingUtils.setFrameIcons(this, ICON_NAMES, KDEOptionsGui.class); + console = new TextConsole(); + scrollPaneConsole.setViewportView(console); + + appender = new LogbackJTextPaneAppender(); + appender.start(); + log.debug("Started LogbackJTextPaneAppender logger"); + appender.setTextPane(console); + } + + public OpenSearchParams getParamsDefault() { + return paramsDefault; + } + + public void setParamsDefault(OpenSearchParams params) { + paramsDefault = params; + } + + private SpinnerModel createSpinnerModel() { + Double window = getParamsDefault().getAutoBandwidthWindow(); + double w = window != null ? window : 0.5; + SpinnerNumberModel model = new SpinnerNumberModel(w, 0.0, 1.0, 0.05); + return model; + } + + private SpinnerModel createSpinnerModelMassStep() { + double step = getParamsDefault().getMzStep(); + SpinnerNumberModel model = new SpinnerNumberModel(step, 0.000001, 1.0, 0.0001); + return model; + } + + private String createTextMassStep() { + return String.format("%s", getParamsDefault().getMzStep()); + } + + private String createTextGmmNumPts() { + return String.format("%d", getParamsDefault().getGmmNumEntries()); + } + + private String createTextGmmMinPts() { + return String.format("%d", getParamsDefault().getGmmMinEntries()); + } + + private String createTextBandwidths() { + List bandwidths = getParamsDefault().getBandwidths(); + if (bandwidths.isEmpty() && !getParamsDefault().isDynamicBandwidthEstimation()) { + bandwidths.add(DEFAULT_BANDWIDTH_FOR_GUI); + } + + StringBuilder sb = new StringBuilder(); + if (!bandwidths.isEmpty()) { + DecimalFormat format = new DecimalFormat("0.##################"); + for (int i = 0; i < bandwidths.size(); i++) { + sb.append(format.format(bandwidths.get(i))); + if (i < bandwidths.size() - 1) { + sb.append(','); + } + } + } + return sb.toString(); + } + + private SpinnerModel createFilterSpinnerModel() { + SpinnerNumberModel model = new SpinnerNumberModel(0.95, null, null, 0.01); + return model; + } + + private ComboBoxModel createComboKernelType() { + DefaultComboBoxModel model = new DefaultComboBoxModel<>(KDEKernelType.values()); + model.setSelectedItem(getParamsDefault().getKernelType()); + return model; + } + + private ComboBoxModel createComboDecoyTreatment() { + DefaultComboBoxModel model = new DefaultComboBoxModel<>( + DecoyTreatment.values()); + model.setSelectedItem(getParamsDefault().getDecoysTreatment()); + return model; + } + + private ComboBoxModel createComboDenoisingType() { + DefaultComboBoxModel model = new DefaultComboBoxModel<>(Denoising.values()); + model.setSelectedItem(getParamsDefault().getDenoising()); + return model; + } + + private ComboBoxModel createComboFilterScoreName() { + DefaultComboBoxModel model = new DefaultComboBoxModel<>(PepXmlScore.values()); + model.setSelectedItem(PepXmlScore.peptideprophet); + return model; + } + + private ComboBoxModel createComboMassCorrection() { + DefaultComboBoxModel model = new DefaultComboBoxModel<>( + MassCorrection.values()); + model.setSelectedItem(getParamsDefault().getMassCorrection()); + return model; + } + + private ComboBoxModel createComboComparisonType() { + DefaultComboBoxModel model = new DefaultComboBoxModel<>( + ComparisonType.values()); + model.setSelectedItem(ComparisonType.GREATER_OR_EQUAL); + return model; + } + + /** + * Get the value of pathIn + * + * @return the value of pathIn + */ + public String getPathIn() { + return pathIn; + } + + /** + * Set the value of pathIn + * + * @param pathIn new value of pathIn + */ + public void setPathIn(String pathIn) { + String oldPathIn = this.pathIn; + this.pathIn = pathIn; + propertyChangeSupport.firePropertyChange(PROP_PATHIN, oldPathIn, pathIn); + } + + private String getPreviousPathsIn() { + KDEGuiProps props = KDEGuiProps.loadFromTemp(); + return props == null ? "" : props.getProperty(KDEGuiProps.PROP_FILE_IN); + } + + private String getPreviousPathsOut() { + KDEGuiProps props = KDEGuiProps.loadFromTemp(); + return props == null ? "" : props.getProperty(KDEGuiProps.PROP_FILE_OUT); + } + + /** + * Add PropertyChangeListener. + */ + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + /** + * Creates parameter object for OKDE based on the state of the GUI. + */ + private OpenSearchParams createOpenSearchParams() { + + OpenSearchParams params = new OpenSearchParams(); + + String filesInStr = txtFileIn.getText().trim(); + if (StringUtils.isBlank(filesInStr)) { + JOptionPane.showMessageDialog(this, "Select some files or a directory", STR_PARAM_ERROR, + JOptionPane.INFORMATION_MESSAGE); + return null; + } + + List inFilePath = new ArrayList<>(); + List filePaths = splitTrim(filesInStr, ","); + for (int i = 0; i < filePaths.size(); i++) { + String filePath = filePaths.get(i); + Path p = Paths.get(filePath).toAbsolutePath(); + if (!Files.exists(p)) { + JOptionPane.showMessageDialog(this, String.format( + "Not an existing path: %s", p.toString()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + inFilePath.add(p); + } + params.setInFilePath(inFilePath); + + params.setDecoysTreatment((DecoyTreatment) comboDecoyTreatment.getModel().getSelectedItem()); + + params.setDeleteCache(false); + params.setUseCache(boxUseCache.isSelected()); + params.setForce(boxForce.isSelected()); + if (params.isDeleteCache()) { + return params; + } + if (params.isUseCache()) { + String cacheExtText = txtCacheExt.getText().trim(); + if (cacheExtText.isEmpty()) { + JOptionPane.showMessageDialog(this, String.format( + "Can't leave 'Cache file ext' field empty when 'Use cache' specified"), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + params.setCacheExt(cacheExtText); + } + + String regex = txtFileRegex.getText(); + if (regex.isEmpty()) { + JOptionPane.showMessageDialog(this, String.format( + "Can't leave File regex empty"), STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } else { + params.setInFileRegex(regex); + } + + //params.setFollowSymlinks(boxFollowSymlinks.isSelected()); + params.setPlotKde(boxDoPlot.isSelected()); + List decoyPrefixes = splitTrim(txtDecoyPrefix.getText(), ","); + params.setDecoyProtPrefix(decoyPrefixes); + + if (boxClearDmRange.isSelected()) { + double dmLo = (Double) spinnerClearDmLo.getValue(); + double dmHi = (Double) spinnerClearDmHi.getValue(); + if (dmHi <= dmLo) { + JOptionPane.showMessageDialog(this, String.format( + "Can't leave File regex empty"), STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + } + params.setClearRange(Arrays.asList(dmLo, dmHi)); + } + + params.setDynamicBandwidthEstimation(boxMassDependent.isSelected()); + String massText = txtMassForBandwidthEstimate.getText(); + if (!massText.isEmpty()) { + try { + params.setAutoBandwithTarget(Double.parseDouble(massText)); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, String.format( + "Could not parse numeric value from 'Mass(h)' field.\n%s", e.getMessage()), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + } + + params.setAutoBandwidthWindow((Double) spinnerMassWindowForBandwidthEstimate.getValue()); + params.setKernelType((KDEKernelType) comboKernelType.getModel().getSelectedItem()); + + String bandwidthsText = txtBandwidths.getText().trim(); + if (!bandwidthsText.isEmpty()) { + List bandwidths = splitTrim(bandwidthsText, ","); + List bs = new ArrayList<>(bandwidths.size()); + for (String bandwidth : bandwidths) { + try { + bs.add(Double.parseDouble(bandwidth)); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, String.format( + "Could not parse numeric value of provided bandwidth.\n%s", e.getMessage()), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + } + params.setBandwidths(bs); + } else { + params.setBandwidths(Collections.emptyList()); + } + + String massLoStr = txtMassLo.getText().trim(); + if (!massLoStr.isEmpty()) { + try { + params.setMzLo(Double.parseDouble(massLoStr)); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, String.format( + "Incorrect number format for low mass range.\n%s", e.getMessage()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + } + + String massHiStr = txtMassHi.getText().trim(); + if (!massHiStr.isEmpty()) { + try { + params.setMzHi(Double.parseDouble(massHiStr)); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, String.format( + "Incorrect number format for high mass range.\n%s", e.getMessage()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + } + + String massStepStr = txtMassStep.getText().trim(); + if (!massStepStr.isEmpty()) { + try { + params.setMzStep(Double.parseDouble(massStepStr)); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, String.format( + "Incorrect number format for mass step.\n%s", e.getMessage()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + } + + params.setMassCorrection((MassCorrection) comboMassCorrection.getSelectedItem()); + + String weight = txtWeightScoreName.getText().trim(); + if (!weight.isEmpty()) { + params.setWeightsScoreName(weight); + } + + String filterText = txtFilter.getText().trim(); + if (!filterText.isEmpty()) { + List scorePredicates = new ArrayList<>(); + ScorePredicateFactoryParameter validator = new ScorePredicateFactoryParameter(); + List filters = splitTrim(filterText, ","); + for (String filter : filters) { + try { + validator.validate("Score filter", filter); + ScorePredicate.Factory factory = validator.convert(filter); + scorePredicates.add(factory); + } catch (ParameterException e) { + JOptionPane.showMessageDialog(this, String.format( + "Filter string incorrect.\n%s", e.getMessage()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + } + params.setScorePredicateFactories(scorePredicates); + } + + params.setDetectPeaks(boxDetectPeaks.isSelected()); + + if (params.isDetectPeaks()) { + if (!txtGmmNumPts.getText().isEmpty()) { + try { + int gmmNumPts = Integer.parseInt(txtGmmNumPts.getText()); + if (gmmNumPts < 100) { + JOptionPane.showMessageDialog(this, + String.format("Peak detection: The allowed minimum for Num pts is 100.\n"), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + params.setGmmNumEntries(gmmNumPts); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, + String.format("Peak detection: Could not parse Num pts as an integer.\n"), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + } else { + JOptionPane.showMessageDialog(this, String.format("Peak detection: Num pts is empty.\n"), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + + if (!txtGmmMinPts.getText().isEmpty()) { + try { + int gmmMinPts = Integer.parseInt(txtGmmMinPts.getText()); + params.setGmmMinEntries(gmmMinPts); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, + String.format("Peak detection: Could not parse Min pts as an integer.\n"), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + } else { + JOptionPane.showMessageDialog(this, String.format("Peak detection: Min pts is empty.\n"), + STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + } + + String outFileText = txtFileOut.getText().trim(); + if (!outFileText.isEmpty()) { + + try { + Path outFilePath = Paths.get(outFileText).toAbsolutePath(); + + if (!params.isForce() && Files.exists(outFilePath) && Files.isRegularFile(outFilePath)) { + JOptionPane.showMessageDialog(this, String.format( + "Cowardly refusing to overwrite existing output file."), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + params.setOutFilePath(outFilePath); + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, "Bad output file path", STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return null; + } + } + + int countBandwidths = 0; + if (params.isDynamicBandwidthEstimation()) { + countBandwidths++; + } + if (params.getAutoBandwithTarget() != null) { + countBandwidths++; + } + if (params.getBandwidths() != null && params.getBandwidths().size() > 0) { + countBandwidths += params.getBandwidths().size(); + } + if (countBandwidths == 0) { + String msg = String.format( + "Must provide at least one bandwidth.\nSet it to 'dynamic' or set a mass to " + + "be used for automatic estimation or provide your own values.\n" + + "Check out 'Mass dependent', 'Mass(h)' and 'List of bandwidths' parameters in 'KDE' section"); + JOptionPane.showMessageDialog(this, msg, STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + + try { + params.validate(); + } catch (ParameterException e) { + String msg = String + .format("Hold on, something is not right with the parameters.\n%s", e.getMessage()); + JOptionPane.showMessageDialog(this, msg, STR_PARAM_ERROR, JOptionPane.WARNING_MESSAGE); + return null; + } + + return params; + } + + protected void run() { + if (!SwingUtils.isGraphicalEnvironmentAvailable()) { + System.err.println("Graphcal environment is needed to run the GUI."); + } + + System.gc(); // just in case + + final OpenSearchParams params = createOpenSearchParams(); + if (params == null) { + return; + } else { + try { + params.validate(); + } catch (ParameterException e) { + JOptionPane.showMessageDialog(this, String.format( + "Not all is well with parameters.\n%s", e.getMessage()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return; + } + } + final KDEMain kdeMain = new KDEMain(); + + kdeMain.setChartCloseOption(JFreeChartPlot.ChartCloseOption.DISPOSE); + + Runnable runnable = () -> kdeMain.run(params); + REHandler reHandler = new REHandler(runnable, System.err, console); + exec.execute(reHandler); + + } + + /** + * Remove PropertyChangeListener. + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + /** + * Get the value of pathOut + * + * @return the value of pathOut + */ + public String getPathOut() { + return pathOut; + } + + /** + * Set the value of pathOut + * + * @param pathOut new value of pathOut + */ + public void setPathOut(String pathOut) { + String oldPathOut = this.pathOut; + this.pathOut = pathOut; + propertyChangeSupport.firePropertyChange(PROP_PATHOUT, oldPathOut, pathOut); + } + + /** + * This method is called from within the constructor to initialize the form. WARNING: Do NOT + * modify this code. The content of this method is always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + txtFileIn = new javax.swing.JTextField(); + btnFileIn = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + txtFileRegex = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + txtDecoyPrefix = new javax.swing.JTextField(); + panelCache = new javax.swing.JPanel(); + boxUseCache = new javax.swing.JCheckBox(); + jLabel3 = new javax.swing.JLabel(); + txtCacheExt = new javax.swing.JTextField(); + boxForce = new javax.swing.JCheckBox(); + panelKDE = new javax.swing.JPanel(); + boxMassDependent = new javax.swing.JCheckBox(); + jLabel4 = new javax.swing.JLabel(); + txtMassForBandwidthEstimate = new javax.swing.JTextField(); + jLabel5 = new javax.swing.JLabel(); + spinnerMassWindowForBandwidthEstimate = new javax.swing.JSpinner(); + jLabel6 = new javax.swing.JLabel(); + comboKernelType = new javax.swing.JComboBox<>(); + jLabel7 = new javax.swing.JLabel(); + txtBandwidths = new javax.swing.JTextField(); + jLabel8 = new javax.swing.JLabel(); + txtWeightScoreName = new javax.swing.JTextField(); + jLabel12 = new javax.swing.JLabel(); + txtMassLo = new javax.swing.JTextField(); + jLabel13 = new javax.swing.JLabel(); + txtMassHi = new javax.swing.JTextField(); + jLabel14 = new javax.swing.JLabel(); + jLabel15 = new javax.swing.JLabel(); + txtMassStep = new javax.swing.JTextField(); + jLabel16 = new javax.swing.JLabel(); + comboMassCorrection = new javax.swing.JComboBox<>(); + boxClearDmRange = new javax.swing.JCheckBox(); + jLabel19 = new javax.swing.JLabel(); + spinnerClearDmLo = new javax.swing.JSpinner(); + jLabel20 = new javax.swing.JLabel(); + spinnerClearDmHi = new javax.swing.JSpinner(); + jLabel21 = new javax.swing.JLabel(); + boxDoPlot = new javax.swing.JCheckBox(); + panelOutput = new javax.swing.JPanel(); + jLabel9 = new javax.swing.JLabel(); + txtFileOut = new javax.swing.JTextField(); + btnFileOut = new javax.swing.JButton(); + jLabel10 = new javax.swing.JLabel(); + btnGo = new javax.swing.JButton(); + panelFilters = new javax.swing.JPanel(); + jLabel11 = new javax.swing.JLabel(); + txtFilter = new javax.swing.JTextField(); + comboFilterScoreName = new javax.swing.JComboBox<>(); + comboFilterOperation = new javax.swing.JComboBox<>(); + btnFilterAdd = new javax.swing.JButton(); + btnFilterRemove = new javax.swing.JButton(); + txtFilterValue = new javax.swing.JTextField(); + btnConsoleClear = new javax.swing.JButton(); + panelPeakDetection = new javax.swing.JPanel(); + boxDetectPeaks = new javax.swing.JCheckBox(); + btnGmmReset = new javax.swing.JButton(); + jLabel17 = new javax.swing.JLabel(); + txtGmmNumPts = new javax.swing.JTextField(); + jLabel18 = new javax.swing.JLabel(); + txtGmmMinPts = new javax.swing.JTextField(); + scrollPaneConsole = new javax.swing.JScrollPane(); + jLabel22 = new javax.swing.JLabel(); + comboDecoyTreatment = new javax.swing.JComboBox<>(); + btnDeleteCache = new javax.swing.JButton(); + textFilterFile = new javax.swing.JTextField(); + jLabel23 = new javax.swing.JLabel(); + btnBrowseFilterFile = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("Open Search Mass Diff KDE"); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowOpened(java.awt.event.WindowEvent evt) { + formWindowOpened(evt); + } + }); + + txtFileIn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtFileInActionPerformed(evt); + } + }); + + btnFileIn.setText("Browse"); + btnFileIn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFileInActionPerformed(evt); + } + }); + + jLabel1.setText("File regex"); + jLabel1.setToolTipText(txtFileRegex.getToolTipText()); + + txtFileRegex.setText(getParamsDefault().getInFileRegex()); + txtFileRegex.setToolTipText("A regular expression, not just a glob"); + txtFileRegex.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtFileRegexActionPerformed(evt); + } + }); + + jLabel2.setText("Decoy prefix"); + jLabel2.setToolTipText(txtDecoyPrefix.getToolTipText()); + + txtDecoyPrefix.setText(StringUtils.join(getParamsDefault().getDecoyProtPrefix(), ",")); + txtDecoyPrefix.setToolTipText("Separate multiple entries with commas"); + + panelCache.setBorder(javax.swing.BorderFactory.createTitledBorder("Files & Cache")); + + boxUseCache.setSelected(getParamsDefault().isUseCache()); + boxUseCache.setText("Use cache"); + boxUseCache.setToolTipText( + "When selected, will store information parsed from pep.xml in separate files"); + boxUseCache.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + boxUseCacheActionPerformed(evt); + } + }); + + jLabel3.setText("Cache file ext"); + jLabel3.setToolTipText(txtCacheExt.getToolTipText()); + + txtCacheExt.setText(getParamsDefault().getCacheExt()); + txtCacheExt.setToolTipText("This extension will be used for cache files"); + + boxForce.setText("Force Overwrite"); + boxForce.setToolTipText( + "If some file needs to be created but already exists,
\nusing this option will force overwrite it.
\nOtherwise the program will cowardly refuse to delete the existing file."); + boxForce.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + boxForceActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelCacheLayout = new javax.swing.GroupLayout(panelCache); + panelCache.setLayout(panelCacheLayout); + panelCacheLayout.setHorizontalGroup( + panelCacheLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelCacheLayout.createSequentialGroup() + .addContainerGap() + .addComponent(boxForce) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(boxUseCache) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtCacheExt, javax.swing.GroupLayout.PREFERRED_SIZE, 80, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + panelCacheLayout.setVerticalGroup( + panelCacheLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup( + panelCacheLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(boxUseCache) + .addComponent(jLabel3) + .addComponent(txtCacheExt, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(boxForce)) + ); + + panelKDE.setBorder(javax.swing.BorderFactory.createTitledBorder("KDE")); + + boxMassDependent.setSelected(getParamsDefault().isDynamicBandwidthEstimation()); + boxMassDependent.setText("Auto Bandwidth"); + boxMassDependent.setToolTipText( + "Dynamic estimation of bandwidth at each nominal mass.
\nWill only use points that are within the window range of the mass itself."); + + jLabel4.setText("Mass (h)"); + + txtMassForBandwidthEstimate.setToolTipText( + "If non-empty value provided, bandwidth will be
\nautomatically estimated using points that lie
\nwithin \"Window(h)\" from this mass"); + + jLabel5.setText("Window (h)"); + jLabel5.setToolTipText(spinnerMassWindowForBandwidthEstimate.getToolTipText()); + + spinnerMassWindowForBandwidthEstimate.setModel(createSpinnerModel()); + spinnerMassWindowForBandwidthEstimate.setToolTipText( + "This window will be used if dynamic estimate of bandwidth
\nwas requested by providing a non-empty value in \"Mass (h)\" box.
\nIt will also be used by \"Mass dependent option\"."); + + jLabel6.setText("Kernel type"); + jLabel6.setToolTipText(comboKernelType.getToolTipText()); + + comboKernelType.setModel(createComboKernelType()); + comboKernelType.setToolTipText( + "Kernel function that will be used for KDE estimation.
\nDifferences are in speed and accuracy.
\nGaussian will be 100x slower than Epanechnikov, for example,
\nbut will give smoother estimates."); + + jLabel7.setText("List of bandwidths"); + + txtBandwidths.setText(createTextBandwidths()); + txtBandwidths.setToolTipText( + "You can manually provide a comma separated
\nlist of bandwidths.
\nE.g.: 0.01,0.001"); + + jLabel8.setText("Score as weight"); + jLabel8.setToolTipText(txtWeightScoreName.getToolTipText()); + + txtWeightScoreName.setToolTipText( + "One name of a corr, that must be present in all pepxml
\nfiles that will be used as weighting factor in KDE analysis.
\nIt only makes sense to use \"the larger - the better\" scores
\nso don't use \"expect\" here."); + + jLabel12.setText("Mass Lo"); + jLabel12.setToolTipText(txtMassLo.getToolTipText()); + + txtMassLo.setToolTipText( + "Low mass range end to use for KDE.
\nLeave blank to use all mass differences.
\nUse this option when you want to zoom in on a peak."); + + jLabel13.setText("-"); + + txtMassHi.setToolTipText( + "High mass range end to use for KDE.
\nLeave blank to use all mass differences.
\nUse this option when you want to zoom in on a peak."); + + jLabel14.setText("Mass Hi"); + jLabel14.setToolTipText(txtMassHi.getToolTipText()); + + jLabel15.setText("Mass Step"); + + txtMassStep.setText(createTextMassStep()); + txtMassStep.setToolTipText("Mass step of the X axis for plotting"); + + jLabel16.setText("Mass correction"); + jLabel16.setToolTipText(comboMassCorrection.getToolTipText()); + + comboMassCorrection.setModel(createComboMassCorrection()); + comboMassCorrection.setToolTipText( + "Right after loading data from files mass errors can be estimated and corrected"); + + boxClearDmRange.setSelected(true); + boxClearDmRange.setText("Clear range"); + boxClearDmRange.setToolTipText("Remove masses in the range
left of this checkbox"); + + jLabel19.setText("dM Lo"); + + spinnerClearDmLo.setModel(new javax.swing.SpinnerNumberModel(-0.5d, null, null, 0.5d)); + spinnerClearDmLo.setToolTipText("Low end of removal mass range"); + + jLabel20.setText("-"); + + spinnerClearDmHi.setModel(new javax.swing.SpinnerNumberModel(0.5d, null, null, 0.5d)); + spinnerClearDmHi.setToolTipText("Low end of removal mass range"); + + jLabel21.setText("dM Hi"); + + javax.swing.GroupLayout panelKDELayout = new javax.swing.GroupLayout(panelKDE); + panelKDE.setLayout(panelKDELayout); + panelKDELayout.setHorizontalGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelKDELayout.createSequentialGroup() + .addContainerGap() + .addGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelKDELayout.createSequentialGroup() + .addComponent(jLabel12) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtMassLo, javax.swing.GroupLayout.PREFERRED_SIZE, 80, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel13) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtMassHi, javax.swing.GroupLayout.PREFERRED_SIZE, 81, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel14) + .addGap(18, 18, 18) + .addComponent(jLabel15) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtMassStep, javax.swing.GroupLayout.DEFAULT_SIZE, 104, + Short.MAX_VALUE)) + .addGroup(panelKDELayout.createSequentialGroup() + .addComponent(jLabel16) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboMassCorrection, + javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(135, 135, 135) + .addComponent(jLabel8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtWeightScoreName)) + .addGroup(panelKDELayout.createSequentialGroup() + .addGroup(panelKDELayout + .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelKDELayout.createSequentialGroup() + .addComponent(boxMassDependent) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel4) + .addGap(2, 2, 2) + .addComponent(txtMassForBandwidthEstimate, + javax.swing.GroupLayout.PREFERRED_SIZE, 58, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel5) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerMassWindowForBandwidthEstimate, + javax.swing.GroupLayout.PREFERRED_SIZE, 70, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(panelKDELayout.createSequentialGroup() + .addComponent(boxClearDmRange) + .addGap(18, 18, 18) + .addComponent(jLabel19) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerClearDmLo, + javax.swing.GroupLayout.PREFERRED_SIZE, 70, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel20) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerClearDmHi, + javax.swing.GroupLayout.PREFERRED_SIZE, 70, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel21))) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(panelKDELayout.createSequentialGroup() + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboKernelType, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jLabel7) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtBandwidths))) + .addContainerGap()) + ); + panelKDELayout.setVerticalGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelKDELayout.createSequentialGroup() + .addContainerGap() + .addGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(boxClearDmRange) + .addComponent(jLabel19) + .addComponent(spinnerClearDmLo, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel20) + .addComponent(spinnerClearDmHi, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel21)) + .addGap(14, 14, 14) + .addGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(boxMassDependent) + .addComponent(jLabel4) + .addComponent(txtMassForBandwidthEstimate, + javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel5) + .addComponent(spinnerMassWindowForBandwidthEstimate, + javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel6) + .addComponent(comboKernelType, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel7) + .addComponent(txtBandwidths, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(txtMassLo, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel13) + .addComponent(txtMassHi, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel14) + .addComponent(jLabel15) + .addComponent(txtMassStep, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup( + panelKDELayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel16) + .addComponent(txtWeightScoreName, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(comboMassCorrection, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + boxDoPlot.setSelected(getParamsDefault().isPlotKde()); + boxDoPlot.setText("Plot"); + boxDoPlot.setToolTipText("Should the interactive plot be displayed?"); + + panelOutput.setBorder(javax.swing.BorderFactory.createTitledBorder("Output")); + panelOutput.setMinimumSize(new java.awt.Dimension(100, 100)); + + jLabel9.setText("Output dir"); + jLabel9.setToolTipText(txtFileOut.getToolTipText()); + + txtFileOut.setToolTipText("Directory where files with detected peaks will be written to"); + txtFileOut.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtFileOutActionPerformed(evt); + } + }); + + btnFileOut.setText("Browse"); + btnFileOut.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFileOutActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelOutputLayout = new javax.swing.GroupLayout(panelOutput); + panelOutput.setLayout(panelOutputLayout); + panelOutputLayout.setHorizontalGroup( + panelOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelOutputLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel9) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtFileOut) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnFileOut)) + ); + panelOutputLayout.setVerticalGroup( + panelOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelOutputLayout.createSequentialGroup() + .addGroup(panelOutputLayout + .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel9) + .addComponent(txtFileOut, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnFileOut)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jLabel10.setText("Input path"); + + btnGo.setText("Go"); + btnGo.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnGoActionPerformed(evt); + } + }); + + panelFilters.setBorder(javax.swing.BorderFactory.createTitledBorder("Filters")); + + jLabel11.setText("Filter line"); + + comboFilterScoreName.setModel(createComboFilterScoreName()); + comboFilterScoreName + .setToolTipText("Score name.
\nYou can also manually edit the string below"); + comboFilterScoreName.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + comboFilterScoreNameActionPerformed(evt); + } + }); + + comboFilterOperation.setModel(createComboComparisonType()); + comboFilterOperation.setToolTipText("Filter operation to be applied"); + + btnFilterAdd.setText("Add"); + btnFilterAdd.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFilterAddActionPerformed(evt); + } + }); + + btnFilterRemove.setText("Clear"); + btnFilterRemove.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFilterRemoveActionPerformed(evt); + } + }); + + txtFilterValue.setText("0.95"); + + javax.swing.GroupLayout panelFiltersLayout = new javax.swing.GroupLayout(panelFilters); + panelFilters.setLayout(panelFiltersLayout); + panelFiltersLayout.setHorizontalGroup( + panelFiltersLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelFiltersLayout.createSequentialGroup() + .addContainerGap() + .addGroup(panelFiltersLayout + .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelFiltersLayout.createSequentialGroup() + .addComponent(comboFilterScoreName, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboFilterOperation, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtFilterValue, javax.swing.GroupLayout.PREFERRED_SIZE, 70, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnFilterAdd) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnFilterRemove) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(panelFiltersLayout.createSequentialGroup() + .addComponent(jLabel11) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtFilter))) + .addContainerGap()) + ); + panelFiltersLayout.setVerticalGroup( + panelFiltersLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelFiltersLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelFiltersLayout + .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(comboFilterScoreName, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(comboFilterOperation, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtFilterValue, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnFilterAdd) + .addComponent(btnFilterRemove)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(panelFiltersLayout + .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtFilter, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel11))) + ); + + btnConsoleClear.setText("Clear console"); + btnConsoleClear.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnConsoleClearActionPerformed(evt); + } + }); + + panelPeakDetection.setBorder(javax.swing.BorderFactory.createTitledBorder("Peak Detection")); + + boxDetectPeaks.setSelected(getParamsDefault().isDetectPeaks()); + boxDetectPeaks.setText("Detect peaks"); + boxDetectPeaks.setToolTipText("Try to detect peaks in KDE results"); + + btnGmmReset.setText("Defaults"); + btnGmmReset.setToolTipText("Reset parameters to defaults"); + btnGmmReset.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnGmmResetActionPerformed(evt); + } + }); + + jLabel17.setText("Num pts"); + + txtGmmNumPts.setText(createTextGmmNumPts()); + + jLabel18.setText("Min pts"); + + txtGmmMinPts.setText(createTextGmmMinPts()); + + javax.swing.GroupLayout panelPeakDetectionLayout = new javax.swing.GroupLayout( + panelPeakDetection); + panelPeakDetection.setLayout(panelPeakDetectionLayout); + panelPeakDetectionLayout.setHorizontalGroup( + panelPeakDetectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPeakDetectionLayout.createSequentialGroup() + .addContainerGap() + .addComponent(boxDetectPeaks) + .addGap(18, 18, 18) + .addComponent(btnGmmReset) + .addGap(18, 18, 18) + .addComponent(jLabel17) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtGmmNumPts, javax.swing.GroupLayout.PREFERRED_SIZE, 53, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel18) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtGmmMinPts, javax.swing.GroupLayout.PREFERRED_SIZE, 58, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + panelPeakDetectionLayout.setVerticalGroup( + panelPeakDetectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelPeakDetectionLayout + .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(boxDetectPeaks) + .addComponent(btnGmmReset) + .addComponent(jLabel17) + .addComponent(txtGmmNumPts, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel18) + .addComponent(txtGmmMinPts, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + + jLabel22.setText("Decoys treatment"); + + comboDecoyTreatment.setModel(createComboDecoyTreatment()); + + btnDeleteCache.setText("Clear Cache"); + btnDeleteCache.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnDeleteCacheActionPerformed(evt); + } + }); + + textFilterFile.addFocusListener(new java.awt.event.FocusAdapter() { + public void focusLost(java.awt.event.FocusEvent evt) { + textFilterFileFocusLost(evt); + } + }); + + jLabel23.setText("Filter File"); + + btnBrowseFilterFile.setText("Browse"); + btnBrowseFilterFile.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnBrowseFilterFileActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panelCache, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(scrollPaneConsole, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel2) + .addComponent(jLabel10, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel23, javax.swing.GroupLayout.Alignment.TRAILING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup( + javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(txtFileRegex, + javax.swing.GroupLayout.DEFAULT_SIZE, 120, + Short.MAX_VALUE) + .addComponent(txtDecoyPrefix)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup( + javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(boxDoPlot) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel22) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(comboDecoyTreatment, + javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, + layout.createSequentialGroup() + .addComponent(textFilterFile) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnBrowseFilterFile)) + .addGroup(layout.createSequentialGroup() + .addComponent(txtFileIn) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnFileIn)))) + .addComponent(panelKDE, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelFilters, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelPeakDetection, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelOutput, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(btnGo, javax.swing.GroupLayout.PREFERRED_SIZE, 57, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnConsoleClear) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnDeleteCache))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtFileIn, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnFileIn) + .addComponent(jLabel10)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(txtFileRegex, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(boxDoPlot)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(txtDecoyPrefix, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel22) + .addComponent(comboDecoyTreatment, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(textFilterFile, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel23) + .addComponent(btnBrowseFilterFile)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelCache, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelKDE, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelFilters, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelPeakDetection, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelOutput, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnGo) + .addComponent(btnConsoleClear) + .addComponent(btnDeleteCache)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(scrollPaneConsole, javax.swing.GroupLayout.DEFAULT_SIZE, 174, + Short.MAX_VALUE) + .addContainerGap()) + ); + + pack(); + }//
//GEN-END:initComponents + + private void txtFileRegexActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtFileRegexActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtFileRegexActionPerformed + + private void boxForceActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_boxForceActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_boxForceActionPerformed + + private void btnFileInActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFileInActionPerformed + if (btnFileIn == evt.getSource()) { + String approveText = "Select"; + JFileChooser fc = new JFileChooser(); + fc.setAcceptAllFileFilterUsed(true); + fc.addChoosableFileFilter(new FileFilter() { + @Override + public boolean accept(File f) { + String name = f.getName().toLowerCase(); + return name.endsWith(".pep.xml") || name.endsWith(".pepxml"); + } + + @Override + public String getDescription() { + return "PepXML files"; + } + }); + fc.setApproveButtonText(approveText); + fc.setDialogTitle("Choose input directory or files"); + fc.setMultiSelectionEnabled(true); + fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + + List filePaths = splitTrim(txtFileIn.getText().trim(), ","); + for (int i = 0; i < filePaths.size(); i++) { + String filePath = filePaths.get(i); + Path p = Paths.get(filePath).toAbsolutePath(); + if (Files.exists(p)) { + fc.setSelectedFile(p.toFile()); + } + } + + int filechooserResult = fc.showDialog(this, approveText); + if (JFileChooser.APPROVE_OPTION == filechooserResult) { + File[] files = fc.getSelectedFiles(); + String filesStr = StringUtils.join(Arrays.asList(files), ", "); + //This is where a real application would open the file. + log.debug("User selected files: {}", filesStr); + setPathIn(filesStr); + if (files.length > 0) { + KDEGuiProps.save(KDEGuiProps.PROP_FILE_IN, filesStr); + txtFileIn.setText(filesStr); + if (files.length == 1 && + Files.exists(files[0].toPath()) && + Files.isDirectory(files[0].toPath())) { + int choice = JOptionPane.showConfirmDialog(this, + "Input is a directory.\n" + + "Do you want to set the output to the same location?"); + if (JOptionPane.OK_OPTION == choice) { + txtFileOut.setText(files[0].toString()); + } + } + } + + } else { + log.debug("Choose input files command cancelled by user."); + } + } + }//GEN-LAST:event_btnFileInActionPerformed + + private void btnGoActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGoActionPerformed + run(); + }//GEN-LAST:event_btnGoActionPerformed + + private void comboFilterScoreNameActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_comboFilterScoreNameActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_comboFilterScoreNameActionPerformed + + private void btnFilterRemoveActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFilterRemoveActionPerformed + txtFilter.setText(""); + }//GEN-LAST:event_btnFilterRemoveActionPerformed + + private void btnFilterAddActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFilterAddActionPerformed + String text = txtFilterValue.getText(); + if (text.isEmpty()) { + JOptionPane.showMessageDialog(this, "Filter value can't be empty", STR_PARAM_ERROR, + JOptionPane.INFORMATION_MESSAGE); + return; + } + try { + double value = Double.parseDouble(text); + } catch (NumberFormatException nfe) { + JOptionPane.showMessageDialog(this, + String.format("Not a number in filter value.\n%s", nfe.getMessage()), STR_PARAM_ERROR, + JOptionPane.ERROR_MESSAGE); + return; + } + + String filterLine = txtFilter.getText(); + StringBuilder sb = new StringBuilder(filterLine.trim()); + if (filterLine.length() > 0) { + sb.append(","); + } + sb.append(comboFilterScoreName.getSelectedItem().toString()); + sb.append(comboFilterOperation.getSelectedItem().toString()); + sb.append(text); + txtFilter.setText(sb.toString()); + }//GEN-LAST:event_btnFilterAddActionPerformed + + private void txtFileInActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtFileInActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtFileInActionPerformed + + private void btnConsoleClearActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnConsoleClearActionPerformed + console.setText(""); + }//GEN-LAST:event_btnConsoleClearActionPerformed + + private void btnFileOutActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFileOutActionPerformed + if (btnFileOut == evt.getSource()) { + String approveText = "Select"; + JFileChooser fc = new JFileChooser(); + fc.setAcceptAllFileFilterUsed(true); + fc.setApproveButtonText(approveText); + fc.setDialogTitle("Choose output path"); + fc.setMultiSelectionEnabled(false); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + List filePaths = splitTrim(txtFileOut.getText().trim(), ","); + if (!filePaths.isEmpty()) { + for (int i = 0; i < filePaths.size(); i++) { + String filePath = filePaths.get(i); + Path p = Paths.get(filePath).toAbsolutePath(); + if (Files.exists(p)) { + fc.setSelectedFile(p.toFile()); + break; + } + } + } else { + // try to set it to the path of input file + filePaths = splitTrim(txtFileIn.getText().trim(), ","); + if (!filePaths.isEmpty()) { + for (int i = 0; i < filePaths.size(); i++) { + String filePath = filePaths.get(i); + Path p = Paths.get(filePath).toAbsolutePath(); + if (Files.exists(p)) { + fc.setSelectedFile(p.toFile()); + break; + } + } + } + } + + int retVal = fc.showDialog(this, approveText); + if (retVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + if (file == null) { + return; + } + String pathStr = file.getAbsolutePath(); + txtFileOut.setText(pathStr); + //This is where a real application would open the file. + log.debug("User selected output dir: {}", pathStr); + setPathOut(pathStr); + KDEGuiProps props = KDEGuiProps.loadFromTemp(); + props = props == null ? new KDEGuiProps() : props; + props.put(KDEGuiProps.PROP_FILE_OUT, pathStr); + props.save(); + } else { + log.debug("Choose output path command cancelled by user."); + } + } + }//GEN-LAST:event_btnFileOutActionPerformed + + private void txtFileOutActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtFileOutActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtFileOutActionPerformed + + private void btnGmmResetActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGmmResetActionPerformed + txtGmmNumPts.setText(createTextGmmNumPts()); + txtGmmMinPts.setText(createTextGmmMinPts()); + }//GEN-LAST:event_btnGmmResetActionPerformed + + private void boxUseCacheActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_boxUseCacheActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_boxUseCacheActionPerformed + + private void btnDeleteCacheActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDeleteCacheActionPerformed + + String text = txtFileIn.getText(); + if (StringUtils.isBlank(text)) { + JOptionPane.showConfirmDialog(this, "Invalid input path", "Warning", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); + return; + } + + StringBuilder sb = new StringBuilder(); + sb.append("Are you sure you want to delete cached input files?\n"); + sb.append("They will need to be re-parsed at next run."); + + int choice = JOptionPane + .showConfirmDialog(this, sb.toString(), "Clear cache", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (JOptionPane.OK_OPTION == choice) { + final OpenSearchParams params = new OpenSearchParams(); + + List inFilePath = new ArrayList<>(); + List filePaths = splitTrim(text, ","); + for (int i = 0; i < filePaths.size(); i++) { + String filePath = filePaths.get(i); + Path p = Paths.get(filePath).toAbsolutePath(); + if (!Files.exists(p)) { + JOptionPane.showMessageDialog(this, String.format( + "Not an existing path: %s", p.toString()), STR_PARAM_ERROR, + JOptionPane.WARNING_MESSAGE); + return; + } + inFilePath.add(p); + } + params.setInFilePath(inFilePath); + + params.setDeleteCache(true); + final KDEMain kdeMain = new KDEMain(); + + kdeMain.setChartCloseOption(JFreeChartPlot.ChartCloseOption.DISPOSE); + + Runnable runnable = () -> kdeMain.run(params); + REHandler reHandler = new REHandler(runnable, System.err, console); + exec.execute(reHandler); + } + }//GEN-LAST:event_btnDeleteCacheActionPerformed + + private void textFilterFileFocusLost( + java.awt.event.FocusEvent evt) {//GEN-FIRST:event_textFilterFileFocusLost + saveTextFilterFile(null); + }//GEN-LAST:event_textFilterFileFocusLost + + private void btnBrowseFilterFileActionPerformed( + java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseFilterFileActionPerformed + String approveText = "Select"; + JFileChooser fc = new JFileChooser(); + fc.setAcceptAllFileFilterUsed(true); + fc.setApproveButtonText(approveText); + fc.setDialogTitle("Choose filter file"); + fc.setMultiSelectionEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + + String text = textFilterFile.getText(); + try { + Path p = Paths.get(text); + if (Files.exists(p)) { + fc.setSelectedFile(p.toFile()); + } else { + KDEGuiProps props = KDEGuiProps.loadFromTemp(); + String filterFile = props.getProperty(KDEGuiProps.PROP_FILE_FILTER); + if (filterFile != null) { + p = Paths.get(filterFile); + if (Files.exists(p)) { + fc.setSelectedFile(p.toFile()); + } + } + } + } catch (Exception e) { + // novermind + } + + int result = fc.showDialog(this, approveText); + if (JFileChooser.APPROVE_OPTION == result) { + String file = fc.getSelectedFile().getAbsolutePath(); + saveTextFilterFile(file); + } else { + log.debug("Choose input files command cancelled by user."); + } + }//GEN-LAST:event_btnBrowseFilterFileActionPerformed + + private void formWindowOpened(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowOpened + if (StringUtils.isBlank(txtFileIn.getText())) { + String previousPathsIn = getPreviousPathsIn(); + txtFileIn.setText(previousPathsIn); + } + }//GEN-LAST:event_formWindowOpened + + /** + * @param toSave if null, value from the text field will be taken. + */ + private void saveTextFilterFile(String toSave) { + String t = toSave != null ? toSave : textFilterFile.getText(); + if (StringUtils.isBlank(t)) { + return; + } + try { + Path p = Paths.get(t); + if (!Files.exists(p)) { + return; + } + String f = p.toAbsolutePath().toString(); + KDEGuiProps.save(KDEGuiProps.PROP_FILE_FILTER, f); + textFilterFile.setText(f); + } catch (Exception e) { + // nevermind + } + } + + public static class REHandler implements Runnable { + + Runnable delegate; + Appendable[] outs; + + public REHandler(Runnable delegate, Appendable... out) { + this.delegate = delegate; + this.outs = out; + } + + public void run() { + try { + delegate.run(); + } catch (Exception e) { + log.error("Something bad happened in a worker thread", e); + String msg = String + .format("Something bad happened in a worker thread:\n%s", e.getMessage()); + for (Appendable out : outs) { + LogUtils.println(out, msg); + } + } + } + } + // End of variables declaration//GEN-END:variables + + +} diff --git a/src/oldMain/java/umich/opensearch/kde/gui/batman_350.png b/src/oldMain/java/umich/opensearch/kde/gui/batman_350.png new file mode 100644 index 0000000000000000000000000000000000000000..417d3e4a744d19aa95f7c4596d7a154e040ef253 GIT binary patch literal 18396 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 M1XY7n%9@4z4?;_Mv;Y7A literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/gui/batmass_16.png b/src/oldMain/java/umich/opensearch/kde/gui/batmass_16.png new file mode 100644 index 0000000000000000000000000000000000000000..38857c0e5e5f8cc5248188c9d1da0335e27353e4 GIT binary patch literal 669 zcmV;O0%HA%P)NA*rJL3kTNp?Yi;B&`!@7}$dv|{@uV>Ov8e~lyb ziFSd&#pMSYJNtI*zW89xliOFAp5D7HB>#B#zDTJLhB!?yy{-rl~-(Ae~srnU9Iy1Kd@Ky{)Z^-wGbq8Ybu->%SndHJ>9fr^~S z2LJu^li_9D1_rKhT?T|DGIOuv~K*o62Petdkxpr)<* zF3&2=4HSs~)fdJx@L4ILcS2}8NOb54%YDR%P$5#BSkR#)3JLDPmA|3yqbD~;p5SJ zV6l&9?=hUec!`CHZB0N13qJ<~6Eicz?;qb7Kx`2B^Yc3cuYo+o0HCH1ckeMgpLLXh z-$ofM#`pO5pTwBRgN(qCTm|HZB0Hb$H{%`YIgj7p^5ZpSmF8y)spXP-o1Utb0c5c111*yQbnKR z%v=7Y-tP|)%nQ>a^q4bOT>6VQDN~vonHVSp009O7UzO#$Smn{{00000NkvXXu0mjf DI^IGs literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/gui/batmass_32.png b/src/oldMain/java/umich/opensearch/kde/gui/batmass_32.png new file mode 100644 index 0000000000000000000000000000000000000000..1104304ab68a1985bf27ed3f7126abd6a662192a GIT binary patch literal 1346 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 literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_32.png b/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_32.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6f3df3c60f7cb04f2c3bd9bfe52896cbca3d0f GIT binary patch literal 2161 zcmbVOc~}#78jgr9AfOaPuIdmER3vvMM}sjpR1!`F3o1DVl1ef$2_zh@2w@RH@Bl4~ zwq7fBDOW`i4^XKnaus+`uA++qmDZxj0-|6iBJTd-(?52enfV>_zVCPZzVCS^BS0ju zv2w9Op-?uyKHMN=1Wb>`C&*iykT8Y}&N^P0E*Or|>BSld#gf4i2=G;kqoE*3EQ{Y& z4>3@v`B93HFkP6?k1mCk7_kY1F(}mt8--#p4QjD;C!_-;P_#njjUKsLg9a2bZ*(YG zh!d(gkX+%Dpn-xDL?P0Iol=?%&D;Pm40J?53F*XuK^ddc(hc6|ce-?BZraA8fp-wy zPH*(AQ(?jYfCFnFfQ%t}NpW~Q0D>4ii421H^#B2fCtz{N4SL~8bTUXMP=NOr8nLF4 zMbU$}{P(tyl{Z?h)2ZoLtX{9j=!qCu6OF~wXf%@sf#8K8ytMHuo!H=|(yo|O;6hrd zMxoXzU=?6e6ieV(oi`fsbT$N~S}6P=tkS+u6p}KmL9E8&F*vMJX^QI|TB{3!{_Dm^ z(b|xBHG~a^T;#`nM%Xa zNGuA4zz50HIj*lts}rlF(41WbVt1BH<^Ls@&e1?(9jpm~;h4D!2#~`%SSyFs04Ep( z)`TilGFY!&YdW6UXt|I^5eLcm8dwRu%P(E=5&IyIh9~1lBobvdZW^CL;^EkA4jJUJ zDQq74Jy-Vs4(xs79W}iQXw6qL0aRNJ)hr0q0CSDa#~iV>EjPUs9gkB{N@~-=1>hnAZc3#lZOK31js{VLz#G=Ewm0`KlCw5&KCxXy4E_rK?~+3$kA$ zaL|lp4U6)#&eUAWHAmao5IuWr>b6OPONv5-9M7b(5c594*`TWVN!5<)?l2O!mGqA# zF7f@r3Y>@U&wFZcY2SEk5R=Q`KD(F|THoY&-|F8BryVNUk=x}sk#qJ{xmiy=#;p5fU`|bv(KV3vQXZA&pEv*MWMSbjN50^x#M1AJK4Hr)T+edp z5vTsecRN-a7wuYhC?i_|958SnP9(rPK8^nNN|ocIn_WwXzAh5Do&C{eY4YScfXCG|ut@b?xEeg8bXQ?y+s@neN`?1#u5O$8)My7~h5z{$BbjNsKXT z7o2a=FRM6ueWJ9%D2$&eJGyNotvhvj3X56y>y{#$Y!KwMpC<+YjmEQ1)eogMb)$hp zAxp2W?VX-UTWm;q94Y#>y_nseo|`wdVx(@ll{|)i#?ui_HvW|7_e*Q3KgYl3UPuL} zpGl~F+*x(nvoxYzJ@_~KT|-IcwrWYWbHul9lBv#4r>MfF;-#$IaEWR=D(D)uNpaf8?s%Q$hdui#>o{V@5FuE*e*NV&f`fpYFXF}8Pd^)&gY%Lp;t z_Dlk$|6ci<-m%X!EPp?L#rBlUk|9XS0OhB`iD@^F^Bv;1{pV0#pq-?-K5i2fDM7Q9 zdT#zUjMk9}ChqpjN(OhQcM3(M!|hC~ET{6B{h6Wm<$ScN_V&`GwrV28o<*5*NgDY>?fTKV8L!~N0eykMu7^We?-b@8|KOb>if z#A$)u*2>IDRC$g~Omkeoa3$tt!}_$aNVE0hyHC1$#8)?c*LZY5IOWW~^ZH>;_FKD1 YRLQ{~!`_@qE-?KB`0_;D5_V+DpRE>g82|tP literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_48.png b/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_48.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4aab4e996fe8ee067382903f6014a960ef0c49 GIT binary patch literal 2751 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 AO#lD@ literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_96.png b/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_96.png new file mode 100644 index 0000000000000000000000000000000000000000..74a6bf5513692095e6bf33ff6a4fe8ebd63736d2 GIT binary patch literal 4680 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 literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_splash.png b/src/oldMain/java/umich/opensearch/kde/gui/delta_logo_splash.png new file mode 100644 index 0000000000000000000000000000000000000000..b175a2b67ffd3c320dc72c4e951e8b9facba012a GIT binary patch literal 31653 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 JPDHLkV1m4)3gQ3& literal 0 HcmV?d00001 diff --git a/src/oldMain/java/umich/opensearch/kde/impl/WeightedDataDelimitedFile.java b/src/oldMain/java/umich/opensearch/kde/impl/WeightedDataDelimitedFile.java new file mode 100644 index 0000000..5ca0cd8 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/impl/WeightedDataDelimitedFile.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.impl; + +import java.util.ArrayList; +import umich.opensearch.kde.api.IWeightedData; + +/** + * @author Dmitry Avtonomov + */ +public class WeightedDataDelimitedFile implements IWeightedData { + + private double[] diffs; + private double[] weights = null; + private WeightedDataDelimitedFile() { + } + + public static WeightedDataDelimitedFile create(ArrayList inputData) { + if (inputData.isEmpty()) { + throw new IllegalArgumentException("input data can't be empty"); + } + boolean hasWeights = inputData.get(0).length > 1; + int size = 0; + for (double[][] doubles : inputData) { + size += doubles[0].length; + } + WeightedDataDelimitedFile result = new WeightedDataDelimitedFile(); + result.diffs = new double[size]; + if (hasWeights) { + result.weights = new double[size]; + } + int idx = 0; + for (double[][] doubles : inputData) { + double[] diffs = doubles[0]; + double[] weights = hasWeights ? doubles[1] : null; + for (int i = 0; i < diffs.length; i++) { + result.diffs[idx] = diffs[i]; + if (result.weights != null) { + result.weights[idx] = weights[i]; + } + idx++; + } + } + return result; + } + + @Override + public double[] getData() { + return new double[0]; + } + + @Override + public double[] getWeights() { + return new double[0]; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/impl/WeightedDataPepXmlList.java b/src/oldMain/java/umich/opensearch/kde/impl/WeightedDataPepXmlList.java new file mode 100644 index 0000000..ca27a75 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/impl/WeightedDataPepXmlList.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.impl; + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.OpenSearchParams; +import umich.opensearch.kde.api.IWeightedData; +import umich.opensearch.kde.api.PepXmlContent; +import umich.opensearch.kde.api.SearchHitDiff; +import umich.opensearch.kde.api.SearchHitDiffs; +import umich.opensearch.kde.params.MassCorrection; +import umich.opensearch.kde.params.WeightFetcherSearchHitDiff; + +/** + * @author Dmitry Avtonomov + */ +public class WeightedDataPepXmlList implements IWeightedData { + + private static final Logger log = LoggerFactory.getLogger(WeightedDataPepXmlList.class); + private double[] massDiffs; + private double[] weights; + + private WeightedDataPepXmlList() { + } + + public static WeightedDataPepXmlList create(List pepXmlList, + OpenSearchParams params) { + int totalEntries = countHits(pepXmlList); + WeightedDataPepXmlList result = new WeightedDataPepXmlList(); + result.massDiffs = new double[totalEntries]; + result.weights = null; + + if (params.getFilterFile() != null) { + log.info("Loading filter file"); + // TODO: load the file into hashmap + + } + + log.info("Getting data for KDE"); + MassCorrection massCorrection = params.getMassCorrection(); + if (params.isUseWeights()) { + log.debug("Fetching mass diffs and weights started"); + log.info(String + .format("Fetching weights, using score '%s' as weight", params.getWeightsScoreName())); + result.weights = new double[totalEntries]; + WeightFetcherSearchHitDiff.Factory weightFetcherFactory = params.getWeightFetcherFactory(); + + int idx = 0; + for (PepXmlContent pepXmlContent : pepXmlList) { + List hitsList = pepXmlContent.getHitsList(); + for (SearchHitDiffs searchHitDiffs : hitsList) { + + WeightFetcherSearchHitDiff w = weightFetcherFactory + .create(searchHitDiffs.getMapScoreName2Index()); + List hits = searchHitDiffs.getHits(); + for (SearchHitDiff hit : hits) { + switch (massCorrection) { + case NONE: + result.massDiffs[idx] = hit.getMassDiff(); + break; + default: + result.massDiffs[idx] = hit.getMassDiffCal(); + break; + } + result.weights[idx] = w.fetch(hit); + idx++; + } + } + } + log.debug("Fetching mass diffs and weights done"); + } else { + log.debug("Fetching mass diffs without weights started"); + int idx = 0; + for (PepXmlContent pepXmlContent : pepXmlList) { + List hitsList = pepXmlContent.getHitsList(); + for (SearchHitDiffs searchHitDiffs : hitsList) { + List hits = searchHitDiffs.getHits(); + for (SearchHitDiff hit : hits) { + switch (massCorrection) { + case NONE: + result.massDiffs[idx] = hit.getMassDiff(); + break; + default: + result.massDiffs[idx] = hit.getMassDiffCal(); + break; + } + idx++; + } + } + } + log.debug("Fetching mass diffs without weights done"); + } + return result; + } + + private static int countHits(List list) { + int count = 0; + for (PepXmlContent pepXmlContent : list) { + List hitsList = pepXmlContent.getHitsList(); + for (SearchHitDiffs searchHitDiffs : hitsList) { + List hits = searchHitDiffs.getHits(); + count += hits.size(); + } + } + return count; + } + + @Override + public double[] getData() { + return massDiffs; + } + + @Override + public double[] getWeights() { + return weights; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/jfree/ChartPanelPathched.java b/src/oldMain/java/umich/opensearch/kde/jfree/ChartPanelPathched.java new file mode 100644 index 0000000..d6a7658 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/jfree/ChartPanelPathched.java @@ -0,0 +1,277 @@ +package umich.opensearch.kde.jfree; + +import java.awt.event.ActionEvent; +import java.awt.geom.Rectangle2D; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.event.PlotChangeEvent; +import org.jfree.chart.event.PlotChangeListener; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.XYPlot; +import org.jfree.data.Range; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Dmitry Avtonomov + */ +public class ChartPanelPathched extends ChartPanel { + + private static final Logger log = LoggerFactory.getLogger(ChartPanelPathched.class); + private final ConcurrentHashMap mapPlotLastState = new ConcurrentHashMap<>(3); + private JCheckBoxMenuItem checkboxZoomXAxisOnly; + private JCheckBoxMenuItem checkboxScaleYAxisAuto; + private Action zoomXonlyAction; + private Action scaleYautoAction; + private volatile boolean zoomXonly; + private volatile boolean scaleYauto; + + public ChartPanelPathched(JFreeChart chart) { + super(chart); + init(); + } + + public ChartPanelPathched(JFreeChart chart, boolean useBuffer) { + super(chart, useBuffer); + init(); + } + + public ChartPanelPathched(JFreeChart chart, boolean properties, boolean save, boolean print, + boolean zoom, boolean tooltips) { + super(chart, properties, save, print, zoom, tooltips); + init(); + } + + public ChartPanelPathched(JFreeChart chart, int width, int height, int minimumDrawWidth, + int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, + boolean properties, boolean save, boolean print, boolean zoom, boolean tooltips) { + super(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, + maximumDrawHeight, useBuffer, properties, save, print, zoom, tooltips); + init(); + } + + public ChartPanelPathched(JFreeChart chart, int width, int height, int minimumDrawWidth, + int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, + boolean properties, boolean copy, boolean save, boolean print, boolean zoom, + boolean tooltips) { + super(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, + maximumDrawHeight, useBuffer, properties, copy, save, print, zoom, tooltips); + init(); + } + + private static Field getField(Class clazz, String fieldName) { + Class tmpClass = clazz; + do { + try { + Field f = tmpClass.getDeclaredField(fieldName); + return f; + } catch (NoSuchFieldException e) { + tmpClass = tmpClass.getSuperclass(); + } + } while (tmpClass != null); + + throw new RuntimeException("Field '" + fieldName + "' not found on class " + clazz); + } + + public static void setRangeAxesAutoscale(JFreeChart chart, boolean rangeAxisAutoscale) { + Plot plot = chart.getPlot(); + if (plot instanceof XYPlot) { + XYPlot xyPlot = (XYPlot) plot; + int rangeAxisCount = xyPlot.getRangeAxisCount(); + for (int i = 0; i < rangeAxisCount; i++) { + ValueAxis rangeAxis = xyPlot.getRangeAxis(i); + rangeAxis.setAutoRange(rangeAxisAutoscale); + } + } + } + + @Override + public void setChart(JFreeChart chart) { + super.setChart(chart); + init(); + } + + private void init() { + setHorizontalAxisTrace(true); + setVerticalAxisTrace(true); + setMouseWheelEnabled(true); + setRangeZoomable(!zoomXonly); + + // ASMS-2017 mods + setHorizontalAxisTrace(false); + setVerticalAxisTrace(false); + + if (getChart().getPlot() instanceof XYPlot) { + XYPlot plot = (XYPlot) getChart().getPlot(); + plot.setDomainPannable(true); + setRangeAxesAutoscale(getChart(), true); + } + + getChart().getPlot().addChangeListener(new PlotChangeListener() { + @Override + public void plotChanged(PlotChangeEvent event) { + log.trace("PlotChangeEvent: {} on some {}", event.getType(), + event.getPlot().getClass().getSimpleName()); + //setRangeAxesAutoscale(getChart(), true); + final Plot plt = getChart().getPlot(); + if (plt != null && plt instanceof XYPlot) { + XYPlot plot = (XYPlot) plt; +// plot.getRangeAxis().setAutoRange(false); +// plot.getRangeAxis().setAutoRange(true); + + if (mapPlotLastState == null) { + return; + } + Range rangeOld = mapPlotLastState.get(plot); + Range rangeCur = plot.getDomainAxis().getRange(); + if (rangeOld == null) { + // no messages yet from this plot, add it to the map and + log.trace("Old range not found, new range created {}", rangeCur); + mapPlotLastState.put(plot, rangeCur); + runYAxisRescaling(); + } else { + if (!rangeOld.equals(rangeCur)) { + log.trace("Range in map updated, old range {}, new range {}", rangeOld, rangeCur); + mapPlotLastState.put(plot, rangeCur); + runYAxisRescaling(); + } else { + log.trace("Range in map was equal to the new one, nothing to update"); + } + } + } + +// if (ChartPanelPathched.this.scaleYauto) { +// ChartPanelPathched.this.runYAxisRescaling(); +// } + } + }); + } + + private List getAllFields(Class clazz) { + + List fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())); + + Class superClazz = clazz.getSuperclass(); + if (superClazz != null) { + fields.addAll(getAllFields(superClazz)); + } + + return fields; + } + + @Override + protected JPopupMenu createPopupMenu(boolean properties, boolean copy, boolean save, + boolean print, boolean zoom) { + JPopupMenu menu = super.createPopupMenu(properties, copy, save, print, zoom); + this.scaleYauto = true; + this.zoomXonly = true; + + checkboxZoomXAxisOnly = new JCheckBoxMenuItem(); + checkboxZoomXAxisOnly.setSelected(zoomXonly); + ChartPanelPathched.this.setRangeZoomable(!zoomXonly); + zoomXonlyAction = new AbstractAction("Zoom X-axis only") { + @Override + public void actionPerformed(ActionEvent e) { + Runnable runnable = new Runnable() { + @Override + public void run() { + boolean wasRangeZoomable = ChartPanelPathched.this.isRangeZoomable(true); + ChartPanelPathched.this.setRangeZoomable(!wasRangeZoomable); + zoomXonly = !ChartPanelPathched.this.isRangeZoomable(true); + checkboxZoomXAxisOnly.setSelected(zoomXonly); + } + }; + SwingUtilities.invokeLater(runnable); + } + }; + checkboxZoomXAxisOnly.setAction(zoomXonlyAction); + menu.add(checkboxZoomXAxisOnly, 0); + + checkboxScaleYAxisAuto = new JCheckBoxMenuItem(); + checkboxScaleYAxisAuto.setSelected(scaleYauto); + setRangeAxesAutoscale(getChart(), scaleYauto); + scaleYautoAction = new AbstractAction("Auto zoom Y-axis") { + @Override + public void actionPerformed(ActionEvent e) { + Runnable runnable = new Runnable() { + @Override + public void run() { + scaleYauto = checkboxScaleYAxisAuto.isSelected(); + setRangeAxesAutoscale(getChart(), scaleYauto); + } + }; + SwingUtilities.invokeLater(runnable); + } + }; + checkboxScaleYAxisAuto.setAction(scaleYautoAction); + menu.add(checkboxScaleYAxisAuto, 0); + + return menu; + } + + @Override + public void restoreAutoBounds() { + super.restoreAutoBounds(); +// XYPlot plot = getChart().getXYPlot(); +// if (plot.getRangeAxisCount() > 1) { +// JFreeChartPlot.synchronizeAxes(plot); +// } + } + + @Override + public void zoomInDomain(double x, double y) { + super.zoomInDomain(x, y); + } + + @Override + public void zoomOutDomain(double x, double y) { + super.zoomOutDomain(x, y); + } + + @Override + public void zoomInBoth(double x, double y) { + super.zoomInBoth(x, y); + } + + @Override + public void zoomOutBoth(double x, double y) { + super.zoomOutBoth(x, y); + } + + @Override + public void zoomOutRange(double x, double y) { + super.zoomOutRange(x, y); + } + + @Override + public void zoomInRange(double x, double y) { + super.zoomInRange(x, y); + } + + @Override + public void zoom(Rectangle2D selection) { + super.zoom(selection); + } + + private void runYAxisRescaling() { + if (scaleYauto) { + log.trace("Running automatic rescaling of Y axis"); + restoreAutoRangeBounds(); + } + } + + @Override + public boolean isRangeZoomable() { + return zoomXonly && scaleYauto; + } + + public boolean isRangeZoomable(boolean callSuper) { + return callSuper ? super.isRangeZoomable() : this.isRangeZoomable(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/jfree/InfoWindow.java b/src/oldMain/java/umich/opensearch/kde/jfree/InfoWindow.java new file mode 100644 index 0000000..bf5d53c --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/jfree/InfoWindow.java @@ -0,0 +1,160 @@ +package umich.opensearch.kde.jfree; + +import java.text.NumberFormat; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.DefaultCaret; + +/** + * @author Dmitry Avtonomov + */ +public class InfoWindow extends JFrame { + + private static final int WINDOW_SIZE_PRECISION = 4; + public JPanel panelMain; + public JPanel panelControls; + public JFormattedTextField txtWndSize; + public JFormattedTextField txtEntriesToShow; + public TextConsole textConsole; + public double listenerWindowSize = 0.01; + public int entriesToShow = 200; + JFrame parent = null; + + public InfoWindow(String title, JFrame parent) throws HeadlessException { + super(title); + this.parent = parent; + if (parent != null) { + this.setIconImages(parent.getIconImages()); + } + init(); + } + + public TextConsole getTextConsole() { + return textConsole; + } + + @Override + public JFrame getParent() { + return parent; + } + + public double getListenerWindowSize() { + return listenerWindowSize; + } + + public void setListenerWindowSize(double listenerWindowSize) { + this.listenerWindowSize = listenerWindowSize; + } + + public int getEntriesToShow() { + return entriesToShow; + } + + public void setEntriesToShow(int entriesToShow) { + this.entriesToShow = entriesToShow; + } + + private void init() { + setPreferredSize(new Dimension(640, 720)); + + panelMain = new JPanel(); + panelMain.setLayout(new BorderLayout()); + setContentPane(panelMain); + + FlowLayout flowLayout = new FlowLayout(); + flowLayout.setAlignment(FlowLayout.LEFT); + flowLayout.setAlignOnBaseline(true); + + panelControls = new JPanel(flowLayout); + panelMain.add(panelControls, BorderLayout.NORTH); + + panelControls.add(new JLabel("Window Size")); + NumberFormat fmt = NumberFormat.getInstance(); + fmt.setMaximumFractionDigits(WINDOW_SIZE_PRECISION); + txtWndSize = new JFormattedTextField(fmt); + txtWndSize.setValue(getListenerWindowSize()); + txtWndSize.setPreferredSize(new Dimension(50, 25)); + txtWndSize.getDocument().addDocumentListener(new TxtWndSizeDocListener()); + panelControls.add(txtWndSize); + + panelControls.add(new JSeparator()); + panelControls.add(new JLabel("Entries to show")); + NumberFormat fmt2 = NumberFormat.getInstance(); + fmt2.setMaximumFractionDigits(0); + txtEntriesToShow = new JFormattedTextField(fmt2); + txtEntriesToShow.setValue(getEntriesToShow()); + txtEntriesToShow.setPreferredSize(new Dimension(50, 25)); + txtEntriesToShow.getDocument().addDocumentListener(new TxtEntriesDocListener()); + panelControls.add(txtEntriesToShow); + + textConsole = new TextConsole(); + JScrollPane scroll = new JScrollPane(textConsole); + scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); +// textConsole.setLineWrap(false); + Font font = textConsole.getFont().deriveFont(12f); + textConsole.setFont(font); + textConsole.setVisible(true); + DefaultCaret caret = (DefaultCaret) textConsole.getCaret(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + + panelMain.add(scroll, BorderLayout.CENTER); + if (parent.isVisible()) { + setLocation(parent.getX() + parent.getWidth(), parent.getY()); + } + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + pack(); + } + + private class TxtWndSizeDocListener implements DocumentListener { + + @Override + public void insertUpdate(DocumentEvent e) { + try { + double val = Double.parseDouble(txtWndSize.getText()); + setListenerWindowSize(val); + } catch (NumberFormatException nfe) { + } + } + + @Override + public void removeUpdate(DocumentEvent e) { + try { + double val = Double.parseDouble(txtWndSize.getText()); + setListenerWindowSize(val); + } catch (NumberFormatException nfe) { + } + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + } + + private class TxtEntriesDocListener implements DocumentListener { + + @Override + public void insertUpdate(DocumentEvent e) { + try { + int val = Integer.parseInt(txtEntriesToShow.getText()); + setEntriesToShow(val); + } catch (NumberFormatException nfe) { + } + } + + @Override + public void removeUpdate(DocumentEvent e) { + try { + int val = Integer.parseInt(txtEntriesToShow.getText()); + setEntriesToShow(val); + } catch (NumberFormatException nfe) { + } + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + } + +} diff --git a/src/oldMain/java/umich/opensearch/kde/jfree/JFreeChartPlot.java b/src/oldMain/java/umich/opensearch/kde/jfree/JFreeChartPlot.java new file mode 100644 index 0000000..9fb2f79 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/jfree/JFreeChartPlot.java @@ -0,0 +1,600 @@ +package umich.opensearch.kde.jfree; + +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.SwingUtils; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.DatasetRenderingOrder; +import org.jfree.chart.plot.Marker; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.chart.title.TextTitle; +import org.jfree.data.xy.XYDataset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.api.PepXmlContent; +import umich.opensearch.kde.api.SearchHitDiff; +import umich.opensearch.kde.api.SearchHitDiffs; +import umich.opensearch.kde.gui.KDEOptionsGui; +import umich.ptm.mod.Mod; +import umich.ptm.mod.ModSpecificity; +import umich.ptm.mod.Mods; + +/** + * @author Dmitry Avtonomov + */ +public class JFreeChartPlot { + + private static final Logger log = LoggerFactory.getLogger(JFreeChartPlot.class); + public double LISTENER_WINDOW_SIZE = 0.01 / 2d; + public int MAX_PEPS_TO_PRINT = 200; + JFrame frame = null; + JFreeChart chart = null; + ChartPanel panel = null; + Map datasetMap = new HashMap<>(); + String mainDatasetName = null; + String title = ""; + InfoWindow infoWnd = null; + Mods mods; + List permanentMarkers = new ArrayList<>(); + + public JFreeChartPlot() { + init(); + } + + public JFreeChartPlot(String title) { + this.title = title; + init(); + } + + public static void synchronizeAxes(XYPlot plot) { + //T Hackman from here + // To synchronize on zero and allow the ranges to scale independently + // lets find and set the first upper axis range + + int rangeAxisCount = plot.getRangeAxisCount(); + if (rangeAxisCount != 2) { + throw new IllegalArgumentException( + "Provided plot had the number of range axes not equal to 2"); + } + ValueAxis axis1 = plot.getRangeAxis(0); + ValueAxis axis2 = plot.getRangeAxis(1); + + double abs_axis1lb = Math.abs(axis1.getLowerBound()); + double abs_axis2lb = Math.abs(axis2.getLowerBound()); + double axis1ub = axis1.getUpperBound(); + double axis2ub = axis2.getUpperBound(); + double axis1upper = (abs_axis1lb > axis1ub) ? abs_axis1lb : axis1ub; + double axis2upper = (abs_axis2lb > axis2ub) ? abs_axis2lb : axis2ub; + + axis1.setUpperBound(axis1upper); + axis2.setUpperBound(axis2upper); + + // now set the lower range if no negative values set to zero + if ((axis1.getLowerBound() >= 0) && (axis2.getLowerBound() >= 0)) { + axis1.setLowerBound(0); + axis2.setLowerBound(0); + } else { + axis1.setLowerBound(-axis1upper); + axis2.setLowerBound(-axis2upper); + } + + //T Hackman To here + } + + public static void setAutoRangeXAxes(XYPlot plot, boolean autorange) { + int rangeAxisCount = plot.getRangeAxisCount(); + for (int i = 0; i < rangeAxisCount; i++) { + ValueAxis rangeAxis = plot.getRangeAxis(); + rangeAxis.setAutoRange(autorange); + } + } + + private void init() { + + } + + public void setMods(Mods mods) { + this.mods = mods; + } + + public void customizeChart(JFreeChart chart) { + setTooltip(chart); + setPointRenderer(chart); + + // ASMS-2017 mods + chart.removeLegend(); + chart.setAntiAlias(true); + chart.setBackgroundPaint(Color.WHITE); + chart.setTitle((TextTitle) null); + } + + public void customizePlot(XYPlot plot) { + + Font f = new Font("Calibri", Font.PLAIN, 24); + + ValueAxis xAxis = plot.getDomainAxis(); + xAxis.setLabelFont(f); + xAxis.setTickLabelFont(f); + plot.setDomainGridlinePaint(Color.LIGHT_GRAY); + + ValueAxis yAxis = plot.getRangeAxis(); + yAxis.setLabelFont(f); + yAxis.setTickLabelFont(f); + plot.setRangeGridlinePaint(Color.LIGHT_GRAY); + + plot.setBackgroundPaint(Color.WHITE); + + plot.setDomainCrosshairLockedOnData(false); + plot.setRangeCrosshairLockedOnData(false); + +// Stroke stroke = new BasicStroke(1.0f, +// BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] {2.0f, 2.0f}, 0.0f); +// plot.setDomainCrosshairStroke(stroke); +// plot.setRangeCrosshairStroke(stroke); +// plot.setDomainCrosshairVisible(false); +// plot.setRangeCrosshairVisible(false); + } + + /** + * Show the chart. + * + * @param whatToDoOnClose use WindowConstants.EXIT_ON_CLOSE if you want to shut down the whole + * program when the main chart window is closed. or WindowConstants.DISPOSE_ON_CLOSE. + */ + public void display(int whatToDoOnClose, String frameTitle) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) { + e.printStackTrace(); + System.exit(1); + } + + String title = "KDE on open search mass diffs"; + String xAxisLabel = "Mass Delta"; + String yAxisLabel = "PSM Density"; + RenderingData mainRd = datasetMap.get(mainDatasetName); + chart = ChartFactory.createXYLineChart(title, xAxisLabel, yAxisLabel, mainRd.dataset); + customizeChart(chart); + XYPlot plt = chart.getXYPlot(); + + // ASMS-2017 mods + customizePlot(plt); + + List primaryYAxisDatasets = new LinkedList<>(); + List secondaryYAxisDatasets = new ArrayList<>(); + int primaryDatasetIdx = -1; + for (Map.Entry e : datasetMap.entrySet()) { + RenderingData rd = e.getValue(); + if (rd.secondaryYAxis) { + if (rd.name.equals(mainDatasetName)) { + throw new IllegalStateException( + "Primary dataset can not be displayed on the secondary Y axis"); + } + secondaryYAxisDatasets.add(rd); + } else { + if (rd.name.equals(mainDatasetName)) { + primaryDatasetIdx = primaryYAxisDatasets.size(); + } + primaryYAxisDatasets.add(rd); + } + } + + RenderingData mainDataset = primaryYAxisDatasets.remove(primaryDatasetIdx); + plt.setDataset(0, mainDataset.dataset); + int primaryYAxisIdx = 0; + plt.mapDatasetToRangeAxis(0, primaryYAxisIdx); + int cnt = 1; + for (int i = 0; i < primaryYAxisDatasets.size(); i++) { + if (i == primaryDatasetIdx) { + continue; + } + RenderingData rd = primaryYAxisDatasets.get(i); + plt.setDataset(cnt, rd.dataset); + plt.setRenderer(cnt, rd.renderer); + plt.mapDatasetToRangeAxis(cnt, primaryYAxisIdx); + cnt++; + } + + if (!secondaryYAxisDatasets.isEmpty()) { + + final NumberAxis yAxis2 = new NumberAxis("Secondary Y Axis"); + int secondaryAxisIdx = 1; + plt.setRangeAxis(secondaryAxisIdx, yAxis2); + + //XYItemRenderer secondaryRenderer = plt.getRenderer(0); + //XYLineAndShapeRenderer secondaryRenderer = new XYLineAndShapeRenderer(true, false); + //secondaryRenderer.setBaseStroke(new BasicStroke( + // 2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + // 1.0f, new float[] {6.0f, 6.0f}, 0.0f)); + + for (int i = 0; i < secondaryYAxisDatasets.size(); i++) { + RenderingData rd = secondaryYAxisDatasets.get(i); + plt.setDataset(cnt, rd.dataset); + plt.setRenderer(cnt, rd.renderer); + plt.mapDatasetToRangeAxis(cnt, secondaryAxisIdx); + } + synchronizeAxes(plt); + } + + // first plot secondary data + plt.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD); + + final ChartPanel chartPanel = new ChartPanelPathched(chart); + Dimension dims = new Dimension(800, 600); + chartPanel.setPreferredSize(dims); + + panel = chartPanel; + frame = new JFrame(); + if (!StringUtils.isBlank(frameTitle)) { + frame.setTitle(frameTitle); + } + + frame.setContentPane(chartPanel); + frame.setDefaultCloseOperation(whatToDoOnClose); + frame.pack(); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + if (infoWnd != null) { + infoWnd.dispatchEvent(new WindowEvent(infoWnd, WindowEvent.WINDOW_CLOSING)); + } + } + + }); + SwingUtils.centerFrame(frame); + SwingUtils.setFrameIcons(frame, KDEOptionsGui.ICON_NAMES, KDEOptionsGui.class); + frame.setLocationByPlatform(true); + frame.setVisible(true); + } + + public void addMouseListener(final java.util.List pepXmls) { + if (panel != null) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + displayInfoWindow(); + } + }); + + ChartMouseListener chartMouseListener = new ChartMouseListener() { + @Override + public void chartMouseClicked(final ChartMouseEvent e) { + + switch (e.getTrigger().getButton()) { + case MouseEvent.BUTTON1: + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + XYPlot xyPlot = JFreeChartPlot.this.chart.getXYPlot(); + double xVal = xyPlot.getDomainCrosshairValue(); + double yVal = xyPlot.getRangeCrosshairValue(); + + String clickCoords = String.format("Click at: [%.5f; %.5f]\n", xVal, yVal); + double mzLo, mzHi; + + if (infoWnd != null && infoWnd.isVisible()) { + double wndSize = infoWnd.getListenerWindowSize(); + int maxPepToPrint = infoWnd.getEntriesToShow(); + mzLo = xVal - wndSize; + mzHi = xVal + wndSize; + TextConsole out = infoWnd.getTextConsole(); + out.setText(clickCoords); + printModsByMass(out, mzLo, mzHi); + printPepsByMass(out, mzLo, pepXmls, mzHi, maxPepToPrint); + } else { + mzLo = xVal - LISTENER_WINDOW_SIZE; + mzHi = xVal + LISTENER_WINDOW_SIZE; + System.err.printf(clickCoords); + printModsByMass(System.out, mzLo, mzHi); + printPepsByMass(System.out, mzLo, pepXmls, mzHi, MAX_PEPS_TO_PRINT); + } + + clearMarkers(); + Color markerColor = Color.MAGENTA; + BasicStroke markerStroke = new BasicStroke( + 1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1.0f, new float[]{10.0f, 10.0f}, 0.0f); + Marker mzLoMarker = new ValueMarker(mzLo, markerColor, markerStroke); + Marker mzHiMarker = new ValueMarker(mzHi, markerColor, markerStroke); + addMarker(mzLoMarker); + addMarker(mzHiMarker); + } + }); + default: + return; + } + } + + @Override + public void chartMouseMoved(ChartMouseEvent arg0) { + } + }; + panel.addChartMouseListener(chartMouseListener); + } else { + System.err.println("You tried to attach a mouse listener before calling .display() method." + + " ChartPanel was not yet created, so there was nothing to attach a listener to."); + } + } + + private void clearMarkers() { + if (chart == null) { + throw new IllegalStateException( + "can't add anything to the chart unless it has been displayed"); + } + XYPlot xyPlot = chart.getXYPlot(); + if (xyPlot == null) { + return; + } + xyPlot.clearDomainMarkers(); + } + + private void addMarker(Marker marker) { + if (chart == null) { + throw new IllegalStateException( + "can't add anything to the chart unless it has been displayed"); + } + XYPlot xyPlot = chart.getXYPlot(); + if (xyPlot == null) { + return; + } + xyPlot.addDomainMarker(marker); + } + + private void printPepsByMass(Appendable out, double mzLo, List pepXmls, + double mzHi, int maxPepsToPrint) { + if (out == null || pepXmls == null) { + return; + } + StringBuilder sb = new StringBuilder(); + int totalFound = 0; + HashMap map = new HashMap<>(500); + + for (PepXmlContent pepXmlContent : pepXmls) { + List hitsList = pepXmlContent.getHitsList(); + + // TODO: ACHTUNG: remove this loop + for (int i = 0; i < hitsList.size(); i++) { + SearchHitDiffs searchHitDiffs = hitsList.get(i); + List hits = searchHitDiffs.getHits(); + for (int j = 0; j < hits.size() - 1; j++) { + if (hits.get(j).getMassDiffCal() > hits.get(j + 1).getMassDiffCal()) { + throw new IllegalStateException("Entries are not sorted!"); + } + } + } + + for (SearchHitDiffs searchHitDiffs : hitsList) { + if (!searchHitDiffs.isSorted()) { + throw new IllegalStateException("Must sort entries first"); + } + List hits = searchHitDiffs.getHits(); + Comparator comparator = new Comparator() { + @Override + public int compare(SearchHitDiff o1, SearchHitDiff o2) { + return Double.compare(o1.getMassDiff(), o2.getMassDiff()); + } + }; + SearchHitDiff lo = new SearchHitDiff(0); + lo.setMassDiff(mzLo); + int idxLo = Collections.binarySearch(hits, lo, comparator); + SearchHitDiff hi = new SearchHitDiff(0); + hi.setMassDiff(mzHi); + int idxHi = Collections.binarySearch(hits, hi, comparator); + + if (idxLo < 0) { + idxLo = -idxLo - 1; + } + + if (idxHi < 0) { + idxHi = -idxHi - 1; + } + + List sublist = hits.subList(idxLo, idxHi); + totalFound += sublist.size(); + for (SearchHitDiff searchHitDiff : sublist) { + AtomicInteger count = map.get(searchHitDiff.getSeq()); + if (count == null) { + map.put(searchHitDiff.getSeq(), new AtomicInteger(1)); + } else { + count.incrementAndGet(); + } + } + } + } + sb.append(String + .format("\n\nTotal %d distinct sequences, %d overall hits\n", map.size(), totalFound)); + int countPrinted = 0; + for (Map.Entry entry : map.entrySet()) { + sb.append(String.format("\t%s : %d hits\n", entry.getKey(), entry.getValue().get())); + if (++countPrinted >= maxPepsToPrint) { + sb.append("\tPeptide list too long, not printing all peptides\n"); + break; + } + } + + try { + out.append(sb.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private void printModsByMass(Appendable out, double mzLo, double mzHi) { + if (mods == null) { + log.debug("Won't print mod info as mods were not set for this JFReeChart"); + } + if (out == null || mods == null) { + return; + } + List byMass = mods.findByMass(mzLo, mzHi); + StringBuilder sb = new StringBuilder(); + + sb.append(String.format("\n\nMods in mass range [%.4f; %.4f]:\n", mzLo, mzHi)); + if (byMass.isEmpty()) { + sb.append("\tNone\n"); + } else { + List modEntries = new ArrayList<>(byMass.size()); + + Collections.sort(byMass, new Comparator() { + @Override + public int compare(Mod o1, Mod o2) { + int srcCmp = o1.getRef().getSrc().compareTo(o2.getRef().getSrc()); + if (srcCmp != 0) { + return srcCmp; + } + return o1.getComposition().getMassMono().compareTo(o2.getComposition().getMassMono()); + } + }); + + for (Mod mod : byMass) { + + sb.append("\t"); + sb.append(mod.getRef()) + .append(String.format(" (m=%.5f)", mod.getComposition().getMassMono())); + List specificities = mod.getSpecificities(); + if (specificities.size() > 0) { + sb.append(specificities.toString()); + } + String desc = mod.getDescShort().length() > 80 ? mod.getDescShort().substring(0, 80) + "..." + : mod.getDescShort(); + sb.append(" ").append(desc); + sb.append("\n"); + } + } + + try { + out.append(sb.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void displayInfoWindow() { + infoWnd = new InfoWindow("KDE Info Display", frame); + infoWnd.setEntriesToShow(this.MAX_PEPS_TO_PRINT); + infoWnd.setListenerWindowSize(this.LISTENER_WINDOW_SIZE); + infoWnd.setVisible(true); + } + + protected void setTooltip(JFreeChart chart) { + XYPlot plot = chart.getXYPlot(); + XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer(); + + String TOOL_TIP_FORMAT = "{0}: {1} \u21E2 {2}"; + String xFmtStr = "\u0394m/z: 0.000000 ; \u0394m/z: -0.000000"; + String yFmtStr = "#.#####"; + DecimalFormat xFmt = new DecimalFormat(xFmtStr); + DecimalFormat yFmt = new DecimalFormat(yFmtStr); + + StandardXYToolTipGenerator tipGen = new StandardXYToolTipGenerator(TOOL_TIP_FORMAT, xFmt, yFmt); + renderer.setBaseToolTipGenerator(tipGen); + //renderer.setLegendItemToolTipGenerator(new StandardXYSeriesLabelGenerator("Legend {0}")); + } + + protected void setPointRenderer(JFreeChart chart) { + XYPlot plot = chart.getXYPlot(); + XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer(); +// Ellipse2D.Double pointMarker = new Ellipse2D.Double(-3, -3, 6, 6); +// renderer.setBaseShape(pointMarker); +// renderer.setBaseShapesFilled(false); +// renderer.setBaseShapesVisible(true); + } + + /** + * Add a dataset to the plot. If it's the first dataset being added, it will become the main + * dataset. + * + * @param renderer Can be null, in which case a simple line chart will be used. + * @param secondaryYAxis Set to true if you want this dataset to use the secondary Y axis. + */ + public void addDataset(String name, XYDataset dataset, XYItemRenderer renderer, + boolean secondaryYAxis) { + RenderingData renderingData = new RenderingData(name, dataset, renderer, secondaryYAxis); + if (datasetMap.isEmpty()) { + mainDatasetName = name; + } + datasetMap.put(name, renderingData); + } + + /** + * Add dataset to the plot, that will use some default renderer. + * + * @param secondaryYAxis Set to true if you want this dataset to use the secondary Y axis. + */ + public void addDataset(String name, XYDataset dataset, boolean secondaryYAxis) { + XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false); +// renderer.setBaseStroke(new BasicStroke( +// 2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, +// 1.0f, new float[] {6.0f, 6.0f}, 0.0f)); + addDataset(name, dataset, renderer, secondaryYAxis); + } + + public void setMainDataset(String name) { + if (datasetMap.containsKey(name)) { + throw new IllegalArgumentException("The dataset mapping did not contain the requested name."); + } + mainDatasetName = name; + } + + public void addPermanentMarker(String datasetKey, String seriesKey, + Collection markers) { + permanentMarkers.addAll(markers); + } + + public synchronized XYDataset getDataset(String key) { + RenderingData rd = datasetMap.get(key); + return rd == null ? null : rd.dataset; + } + + public enum ChartCloseOption { + DISPOSE(WindowConstants.DISPOSE_ON_CLOSE), EXIT(WindowConstants.EXIT_ON_CLOSE); + + public final int windowConstant; + + ChartCloseOption(int windowConstant) { + this.windowConstant = windowConstant; + } + } + + public static class RenderingData { + + public final String name; + public final XYDataset dataset; + public final XYItemRenderer renderer; + public final boolean secondaryYAxis; + + + public RenderingData(String name, XYDataset dataset, XYItemRenderer renderer, + boolean secondaryYAxis) { + this.name = name; + this.dataset = dataset; + this.renderer = renderer; + this.secondaryYAxis = secondaryYAxis; + } + } + + private class ModEntry { + + final String source; + final int id; + final Mod mod; + + private ModEntry(String source, int id, Mod mod) { + this.source = source; + this.id = id; + this.mod = mod; + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/jfree/TextConsole.java b/src/oldMain/java/umich/opensearch/kde/jfree/TextConsole.java new file mode 100644 index 0000000..3ac9a5e --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/jfree/TextConsole.java @@ -0,0 +1,46 @@ +package umich.opensearch.kde.jfree; + +import javax.swing.text.BadLocationException; +import javax.swing.text.StyledDocument; + +/** + * @author Dmitry Avtonomov + */ +public class TextConsole extends JTextPane implements Appendable { + + @Override + public Appendable append(CharSequence csq) { + //append(csq.toString()); + StyledDocument doc = getStyledDocument(); + try { + doc.insertString(doc.getLength(), csq.toString(), null); + } catch (BadLocationException e) { + e.printStackTrace(); + } + return this; + } + + @Override + public Appendable append(CharSequence csq, int start, int end) { + //append(csq.subSequence(start, end).toString()); + StyledDocument doc = getStyledDocument(); + try { + doc.insertString(doc.getLength(), csq.subSequence(start, end).toString(), null); + } catch (BadLocationException e) { + e.printStackTrace(); + } + return this; + } + + @Override + public Appendable append(char c) { + //append(Character.toString(c)); + StyledDocument doc = getStyledDocument(); + try { + doc.insertString(doc.getLength(), String.valueOf(c), null); + } catch (BadLocationException e) { + e.printStackTrace(); + } + return this; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/jsat/KDEKludge.java b/src/oldMain/java/umich/opensearch/kde/jsat/KDEKludge.java new file mode 100644 index 0000000..4adeb17 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/jsat/KDEKludge.java @@ -0,0 +1,580 @@ +package umich.opensearch.kde.jsat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import jsat.distributions.ContinuousDistribution; +import jsat.distributions.empirical.kernelfunc.EpanechnikovKF; +import jsat.distributions.empirical.kernelfunc.GaussKF; +import jsat.distributions.empirical.kernelfunc.KernelFunction; +import jsat.distributions.empirical.kernelfunc.UniformKF; +import jsat.linear.Vec; +import jsat.math.Function; +import jsat.math.OnLineStatistics; +import jsat.utils.ProbailityMatch; +import umich.opensearch.kde.params.kernels.KernelFuncPrime2; + +/** + * Kernel Density Estimator, KDE, uses the data set itself to approximate the underlying probability + * distribution using {@link KernelFunction Kernel Functions}. + * + * @author Edward Raff + * @author Dmitry Avtonomov + */ +public class KDEKludge extends ContinuousDistribution { + /* + * README + * Implementation note: + * The values are stored in sorted order, which allows for fast evaluations. + * Instead of doing the full loop on each function call, O(n) time, + * we know the bounds on the values that will effect results, so we + * can do 2 binary searches and then a loop. Though this is still + * technically, O(n), its more accurately described as O(n * epsilon * log(n)) , where n * epsilon << n + */ + + + private static final long serialVersionUID = 7708020456632603947L; + /** + * The various values + */ + protected double[] X; + /** + * Weights corresponding to each value. If all the same, weights should have a length of 0 + */ + protected double[] weights; + /** + * For unweighted data, this is equal to X.length + */ + protected double sumOFWeights; + /** + * The bandwidth + */ + protected double h; + protected double Xmean, Xvar, Xskew; + + protected KernelFunction k; + @SuppressWarnings("unused") + private final Function cdfFunc = new Function() { + + /** + * + */ + private static final long serialVersionUID = -4100975560125048798L; + + public double f(double... x) { + return cdf(x[0]); + } + + public double f(Vec x) { + return f(x.get(0)); + } + }; + + public KDEKludge(Vec dataPoints) { + this(dataPoints, autoKernel(dataPoints)); + } + + public KDEKludge(Vec dataPoints, KernelFunction k) { + this(dataPoints, k, BandwithGuassEstimate(dataPoints)); + } + + public KDEKludge(Vec dataPoints, KernelFunction k, double[] weights) { + this(dataPoints, k, BandwithGuassEstimate(dataPoints), weights); + } + + public KDEKludge(Vec dataPoints, KernelFunction k, double h) { + setUpX(dataPoints); + this.k = k; + this.h = h; + } + + public KDEKludge(Vec dataPoints, KernelFunction k, double h, double[] weights) { + setUpX(dataPoints, weights); + this.k = k; + this.h = h; + } + + /** + * Copy constructor + */ + private KDEKludge(double[] X, double h, double Xmean, double Xvar, double Xskew, KernelFunction k, + double sumOfWeights, double[] weights) { + this.X = Arrays.copyOf(X, X.length); + this.h = h; + this.Xmean = Xmean; + this.Xvar = Xvar; + this.Xskew = Xskew; + this.k = k; + this.sumOFWeights = sumOfWeights; + this.weights = Arrays.copyOf(weights, weights.length); + } + + public static double BandwithGuassEstimate(Vec X) { + if (X.length() == 1) { + return 1; + } else if (X.standardDeviation() == 0) { + return 1.06 * Math.pow(X.length(), -1.0 / 5.0); + } + return 1.06 * X.standardDeviation() * Math.pow(X.length(), -1.0 / 5.0); + } + + /** + * Automatically selects a good Kernel function for the data set that balances Execution time and + * accuracy + * + * @return a kernel that will work well for the given distribution + */ + public static KernelFunction autoKernel(Vec dataPoints) { + if (dataPoints.length() < 30) { + return GaussKF.getInstance(); + } else if (dataPoints.length() < 1000) { + return EpanechnikovKF.getInstance(); + } else//For very large data sets, Uniform is FAST and just as accurate + { + return UniformKF.getInstance(); + } + } + + public KernelFunction getKernelFunction() { + return k; + } + + public void setKernelFunction(KernelFunction k) { + this.k = k; + } + + private void setUpX(Vec S) { + Xmean = S.mean(); + Xvar = S.variance(); + Xskew = S.skewness(); + X = S.arrayCopy(); + Arrays.sort(X); + sumOFWeights = X.length; + weights = new double[0]; + } + + private void setUpX(Vec S, double[] weights) { + if (S.length() != weights.length) { + throw new RuntimeException("Weights and variables do not have the same length"); + } + + OnLineStatistics stats = new OnLineStatistics(); + + X = new double[S.length()]; + this.weights = Arrays.copyOf(weights, S.length()); + + //Probability is the X value, match is the weights - so that they can be sorted together. + List> sorter = new ArrayList<>(S.length()); + for (int i = 0; i < S.length(); i++) { + sorter.add(new ProbailityMatch<>(S.get(i), weights[i])); + } + Collections.sort(sorter); + for (int i = 0; i < sorter.size(); i++) { + this.X[i] = sorter.get(i).getProbability(); + this.weights[i] = sorter.get(i).getMatch(); + stats.add(this.X[i], this.weights[i]); + } + //Now do some helpful preprocessing on weights. We will make index i store the sum for [0, i]. + //Each individual weight can still be retrieved in O(1) by accessing a 2nd index and a subtraction + //Methods that need the sum can now access it in O(1) time from the weights array instead of doing an O(n) summations + for (int i = 1; i < this.weights.length; i++) { + this.weights[i] += this.weights[i - 1]; + } + sumOFWeights = this.weights[this.weights.length - 1]; + this.Xmean = stats.getMean(); + this.Xvar = stats.getVarance(); + this.Xskew = stats.getSkewness(); + } + + /** + * By the point you can call this, the array should have already been sorted. + */ + public double[] getX() { + return X; + } + + private double getWeight(int i) { + if (weights.length == 0) { + return 1.0; + } else if (i == 0) { + return weights[i]; + } else { + return weights[i] - weights[i - 1]; + } + } + + @Override + public double pdf(double x) { + return pdf(x, -1, h); + } + + /** + * Calculate PDF using the provided value of bandwidth. + * + * @param x the point to calculate at + * @param h custom bandwidth value + */ + public double pdf(double x, double h) { + return pdf(x, -1, h); + } + + /** + * Computes the Leave One Out PDF of the estimator + * + * @param x the value to get the pdf of + * @param j the sorted index of the value to leave. If a negative value is given, the PDF with all + * values is returned + * @param h which bandwidth to use + * @return the pdf with the given index left out + */ + private double pdf(double x, int j, double h) { + /* + * n + * ===== /x - x \ + * 1 \ | i| + * f(x) = --- > K|------| + * n h / \ h / + * ===== + * i = 1 + * + */ + + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x - h * k.cutOff()); + int to = Arrays.binarySearch(X, x + h * k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? -from - 1 : from; + to = to < 0 ? -to - 1 : to; + + //Univariate opt, if uniform weights, the sum is just the number of elements divide by half + if (weights.length == 0 && k instanceof UniformKF) { + return (to - from) * 0.5 / (sumOFWeights * h); + } + + double sum = 0; + for (int i = Math.max(0, from); i < Math.min(X.length, to + 1); i++) { + if (i != j) { + sum += k.k((x - X[i]) / h) * getWeight(i); + } + } + + return sum / (sumOFWeights * h); + } + + public double pdfPrime2(double x) { + return pdfPrime2(x, -1, h); + } + + public double pdfPrime2(double x, double h) { + return pdfPrime2(x, -1, h); + } + + protected double pdfPrime2(double x, int j, double h) { + /* + * n + * ===== /x - x \ + * 1 \ | i| + * f(x) = --- > K|------| + * n h / \ h / + * ===== + * i = 1 + * + */ + + if (!(k instanceof KernelFuncPrime2)) { + throw new IllegalStateException( + "You can only call 2nd derivative of PDF if you've set a kernel function that " + + "is an instance of KernelFuncPrime2"); + } + KernelFuncPrime2 kernelFuncPrime2 = (KernelFuncPrime2) k; + + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x - h * k.cutOff()); + int to = Arrays.binarySearch(X, x + h * k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? ~from : from; + to = to < 0 ? ~to : to; + + //Univariate opt, if uniform weights, the sum is just the number of elements divide by half + if (weights.length == 0 && k instanceof UniformKF) { + return (to - from) * 0.5 / (sumOFWeights * h); + } + + double sum = 0; + for (int i = Math.max(0, from); i < Math.min(X.length, to + 1); i++) { + if (i != j) { + sum += kernelFuncPrime2.kPrime2((x - X[i]) / h) * getWeight(i); + } + } + + return sum / (sumOFWeights * h); + } + + public double pdfPrime(double x) { + return pdfPrime(x, -1, h); + } + + public double pdfPrime(double x, double h) { + return pdfPrime(x, -1, h); + } + + protected double pdfPrime(double x, int j, double h) { + /* + * n + * ===== /x - x \ + * 1 \ | i| + * f(x) = --- > K|------| + * n h / \ h / + * ===== + * i = 1 + * + */ + + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x - h * k.cutOff()); + int to = Arrays.binarySearch(X, x + h * k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? -from - 1 : from; + to = to < 0 ? -to - 1 : to; + + //Univariate opt, if uniform weights, the sum is just the number of elements divide by half + if (weights.length == 0 && k instanceof UniformKF) { + return (to - from) * 0.5 / (sumOFWeights * h); + } + + double sum = 0; + for (int i = Math.max(0, from); i < Math.min(X.length, to + 1); i++) { + if (i != j) { + sum += k.kPrime((x - X[i]) / h) * getWeight(i); + } + } + + return sum / (sumOFWeights * h); + } + + @Override + public double cdf(double x) { + //Only values within a certain range will have an effect on the result, so we will skip to that range! + int from = Arrays.binarySearch(X, x - h * k.cutOff()); + int to = Arrays.binarySearch(X, x + h * k.cutOff()); + //Mostly likely the exact value of x is not in the list, so it returns the inseration points + from = from < 0 ? -from - 1 : from; + to = to < 0 ? -to - 1 : to; + + double sum = 0; + + for (int i = Math.max(0, from); i < Math.min(X.length, to + 1); i++) { + sum += k.intK((x - X[i]) / h) * getWeight(i); + } + + /* + * Slightly different, all things below the from value for the cdf would be + * adding 1 to the value, as the value of x would be the integration over + * the entire range, which by definition, is equal to 1. + */ + //We perform the addition after the summation to reduce the difference size + if (weights.length == 0)//No weights + { + sum += Math.max(0, from); + } else { + sum += weights[from]; + } + + return sum / (X.length); + } + + @Override + public double invCdf(double p) { + int index; + double kd0; + + if (weights.length == 0) { + double r = p * X.length; + index = (int) r; + double pd0 = r - index, pd1 = 1 - pd0; + kd0 = k.intK(pd1); + } else//CDF can be found from the weights summings + { + double XEstimate = p * sumOFWeights; + index = Arrays.binarySearch(weights, XEstimate); + index = index < 0 ? -index - 1 : index; + if (X[index] != 0)//TODO fix this bit + { + kd0 = 1.0;//-Math.abs((XEstimate-X[index])/X[index]); + } else { + kd0 = 1.0; + } + } + + if (index == X.length - 1)//at the tail end + { + return X[index] * kd0; + } + double x = X[index] * kd0 + X[index + 1] * (1 - kd0); + + return x; + } + + @Override + public double min() { + return X[0] - h; + } + + @Override + public double max() { + return X[X.length - 1] + h; + } + + @Override + public String getDistributionName() { + return "Kernel Density Estimate"; + } + + @Override + public String[] getVariables() { + return new String[]{"h"}; + } + + @Override + public double[] getCurrentVariableValues() { + return new double[]{h}; + } + + /** + * @return the bandwidth parameter + */ + public double getBandwith() { + return h; + } + + /** + * Sets the bandwidth used for smoothing. Higher values make the pdf smoother, but can obscure + * features. Too small a bandwidth will causes spikes at only the data points. + * + * @param val new bandwidth + */ + public void setBandwith(double val) { + if (val <= 0 || Double.isInfinite(val)) { + throw new ArithmeticException("Bandwith parameter h must be greater than zero, not " + 0); + } + this.h = val; + } + + @Override + public void setVariable(String var, double value) { + if (var.equals("h")) { + setBandwith(value); + } + } + + @Override + public KDEKludge clone() { + return new KDEKludge(X, h, Xmean, Xvar, Xskew, k, sumOFWeights, weights); + } + + @Override + public void setUsingData(Vec data) { + setUpX(data); + this.h = BandwithGuassEstimate(data); + } + + @Override + public double mean() { + return Xmean; + } + + + @Override + public double mode() { + double maxP = 0, pTmp; + double maxV = Double.NaN; + for (int i = 0; i < X.length; i++) { + if ((pTmp = pdf(X[i])) > maxP) { + maxP = pTmp; + maxV = X[i]; + } + } + + return maxV; + } + + @Override + public double variance() { + return Xvar + h * h * k.k2(); + } + + @Override + public double skewness() { + //TODO cant find anything about what this should really be... + return Xskew; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(X); + long temp; + temp = Double.doubleToLongBits(Xmean); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(Xskew); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(Xvar); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(h); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + ((k == null) ? 0 : k.hashCode()); + temp = Double.doubleToLongBits(sumOFWeights); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Arrays.hashCode(weights); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof KDEKludge)) { + return false; + } + KDEKludge other = (KDEKludge) obj; + if (Double.doubleToLongBits(Xmean) != Double + .doubleToLongBits(other.Xmean)) { + return false; + } + if (Double.doubleToLongBits(Xskew) != Double + .doubleToLongBits(other.Xskew)) { + return false; + } + if (Double.doubleToLongBits(Xvar) != Double + .doubleToLongBits(other.Xvar)) { + return false; + } + + if (Double.doubleToLongBits(h) != Double.doubleToLongBits(other.h)) { + return false; + } + if (Double.doubleToLongBits(sumOFWeights) != Double + .doubleToLongBits(other.sumOFWeights)) { + return false; + } + if (k == null) { + if (other.k != null) { + return false; + } + } else if (k.getClass() != other.k.getClass()) { + return false; + } + if (!Arrays.equals(X, other.X)) { + return false; + } + return Arrays.equals(weights, other.weights); + } + + +} diff --git a/src/oldMain/java/umich/opensearch/kde/jsat/KDEUtils.java b/src/oldMain/java/umich/opensearch/kde/jsat/KDEUtils.java new file mode 100644 index 0000000..e5ab3f1 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/jsat/KDEUtils.java @@ -0,0 +1,43 @@ +package umich.opensearch.kde.jsat; + +import com.github.chhh.utils.MathUtils; +import java.util.ArrayList; +import jsat.distributions.empirical.KernelDensityEstimator; +import jsat.linear.DenseVector; + +/** + * @author Dmitry Avtonomov + */ +public class KDEUtils { + + private KDEUtils() { + } + + + /** + * Estimate bandwidth for KDE using Gaussian kernel method. + * + * @param massDiffs an array of all the mass diffs that were there + * @param nearestPeak near which mass will we be searching for a single good peak to estimate + * bandwidth + * @param maxDistance the max distance from the {@code nearestPeak} to accept a point for the + * estimate (this will be used as +/- distance) + * @return estimated bandwidth + */ + public static double estimateBandwidth(DenseVector massDiffs, double nearestPeak, + double maxDistance) { + if (massDiffs.length() < 3) { + return 0.5; // slightly better than returning 1 by default + } + ArrayList accepted = new ArrayList<>(massDiffs.length() / 2); + for (int i = 0; i < massDiffs.length(); i++) { + double diff = massDiffs.get(i); + if (MathUtils.isWithinAbs(diff, nearestPeak, maxDistance)) { + accepted.add(diff); + } + } + DenseVector acceptedVec = new DenseVector(accepted); + double estimatedBandwidth = KernelDensityEstimator.BandwithGuassEstimate(acceptedVec); + return estimatedBandwidth; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/logging/LogHelper.java b/src/oldMain/java/umich/opensearch/kde/logging/LogHelper.java new file mode 100644 index 0000000..0c85986 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/logging/LogHelper.java @@ -0,0 +1,69 @@ +package umich.opensearch.kde.logging; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.FileAppender; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.LogManager; +import org.slf4j.LoggerFactory; + +/** + * @author Dmitry Avtonomov + */ +public class LogHelper { + + private LogHelper() { + } + + /** + * Configures JUL (java.util.logging) using the logging.properties file located in this package. + * Only use this method for testing purposes, clients should configure logging themselves - that + * is you need to provide a logging bridge for SLF4J compatible to your logging infrastructure, or + * use SLF4J no-op logger. + * + * @deprecated Switched to slf4j-simple. The config file 'simplelogger.properties' is in thr + * 'resources' folder. + */ + @Deprecated + public static void configureJavaUtilLogging() { + + try (InputStream is = LogHelper.class.getResourceAsStream("logging.properties")) { + LogManager logMan = LogManager.getLogManager(); + logMan.readConfiguration(is); + } catch (final IOException e) { + java.util.logging.Logger.getAnonymousLogger().severe( + "Could not load development logging.properties file using " + + "LogHelper.class.getResourceAsStream(\"/logging.properties\")"); + java.util.logging.Logger.getAnonymousLogger().severe(e.getMessage()); + } + + // print internal state +// LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); +// StatusPrinter.print(lc); + } + + private static Logger createLoggerFor(String string, String file) { + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + PatternLayoutEncoder ple = new PatternLayoutEncoder(); + + ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n"); + ple.setContext(lc); + ple.start(); + FileAppender fileAppender = new FileAppender(); + fileAppender.setFile(file); + fileAppender.setEncoder(ple); + fileAppender.setContext(lc); + fileAppender.start(); + + Logger logger = (Logger) LoggerFactory.getLogger(string); + logger.addAppender(fileAppender); + logger.setLevel(Level.DEBUG); + logger.setAdditive(false); /* set to true if root should log too */ + + return logger; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/logging/LogbackJTextPaneAppender.java b/src/oldMain/java/umich/opensearch/kde/logging/LogbackJTextPaneAppender.java new file mode 100644 index 0000000..237eaf3 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/logging/LogbackJTextPaneAppender.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2018 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.logging; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import javax.swing.text.BadLocationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @see ch.qos.logback.core.AppenderBase + */ +public class LogbackJTextPaneAppender extends AppenderBase { + + private static final Logger log = LoggerFactory.getLogger(LogbackJTextPaneAppender.class); + private static SimpleAttributeSet ERROR_ATT, WARN_ATT, INFO_ATT, DEBUG_ATT, TRACE_ATT, OTHER_ATT; + + static { + ERROR_ATT = new SimpleAttributeSet(); + ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.TRUE); + ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE); + ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 0, 0)); + + WARN_ATT = new SimpleAttributeSet(); + WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE); + WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 76, 0)); + + INFO_ATT = new SimpleAttributeSet(); + INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE); + INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(0, 0, 153)); + + DEBUG_ATT = new SimpleAttributeSet(); + DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE); + DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(64, 64, 64)); + + TRACE_ATT = new SimpleAttributeSet(); + TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE); + TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 0, 76)); + + OTHER_ATT = new SimpleAttributeSet(); + OTHER_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE); + OTHER_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE); + OTHER_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(0, 0, 0)); + } + + private PatternLayout patternLayout; + private JTextPane textPane; + + public void setTextPane(JTextPane textPane) { + this.textPane = textPane; + } + + public void setPatternLayout(PatternLayout patternLayout) { + this.patternLayout = patternLayout; + } + + @Override + public void start() { + if (patternLayout == null) { + patternLayout = new PatternLayout(); + } + + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + setContext(lc); + //patternLayout.setContext(getContext()); + patternLayout.setContext(lc); + //patternLayout.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"); + patternLayout.setPattern("%d{HH:mm:ss} %-5level: %msg%n"); + patternLayout.start(); + + lc.getLogger("ROOT").addAppender(this); + + super.start(); + } + + @Override + protected void append(ILoggingEvent event) { + + String formattedMsg = patternLayout.doLayout(event); + + SwingUtilities.invokeLater(() -> { + try { + int limit = 1000; + int lineNum = 200; + if (textPane.getDocument().getDefaultRootElement().getElementCount() > limit) { + int end = getLineEndOffset(textPane, lineNum); + replaceRange(textPane, null, 0, end); + } + + if (event.getLevel() == Level.ERROR) { + textPane.getDocument() + .insertString(textPane.getDocument().getLength(), formattedMsg, ERROR_ATT); + } else if (event.getLevel() == Level.WARN) { + textPane.getDocument() + .insertString(textPane.getDocument().getLength(), formattedMsg, WARN_ATT); + } else if (event.getLevel() == Level.INFO) { + textPane.getDocument() + .insertString(textPane.getDocument().getLength(), formattedMsg, INFO_ATT); + } else if (event.getLevel() == Level.DEBUG) { + textPane.getDocument() + .insertString(textPane.getDocument().getLength(), formattedMsg, DEBUG_ATT); + } else if (event.getLevel() == Level.TRACE) { + textPane.getDocument() + .insertString(textPane.getDocument().getLength(), formattedMsg, TRACE_ATT); + } else { + textPane.getDocument() + .insertString(textPane.getDocument().getLength(), formattedMsg, OTHER_ATT); + } + + } catch (BadLocationException e) { + } + + textPane.setCaretPosition(textPane.getDocument().getLength()); + }); + } + + /** + * Num lines in text pane. + */ + private int getLineCount(JTextPane textPane) { + return textPane.getDocument().getDefaultRootElement().getElementCount(); + } + + /** + * @param textPane de onde quero o offset + * @param line the line >= 0 + * @return the offset >= 0 + * @throws BadLocationException Thrown if the line is less than zero or greater or equal to the + * number of lines contained in the document (as reported by getLineCount) + */ + private int getLineEndOffset(JTextPane textPane, int line) throws BadLocationException { + int lineCount = getLineCount(textPane); + if (line < 0) { + throw new BadLocationException("Negative line", -1); + } else if (line >= lineCount) { + throw new BadLocationException("No such line", textPane.getDocument().getLength() + 1); + } else { + Element map = textPane.getDocument().getDefaultRootElement(); + Element lineElem = map.getElement(line); + int endOffset = lineElem.getEndOffset(); + // hide the implicit break at the end of the document + return ((line == lineCount - 1) ? (endOffset - 1) : endOffset); + } + } + + /** + * Replaces text from the indicated start to end position with the new text specified. Does + * nothing if the model is null. Simply does a delete if the new string is null or empty.
+ * + * @param textPane de onde quero substituir o texto + * @param str the text to use as the replacement + * @param start the start position >= 0 + * @param end the end position >= start + * @throws IllegalArgumentException if part of the range is an invalid position in the model + */ + private void replaceRange(JTextPane textPane, String str, int start, int end) + throws IllegalArgumentException { + if (end < start) { + throw new IllegalArgumentException("end before start"); + } + Document doc = textPane.getDocument(); + if (doc != null) { + try { + if (doc instanceof AbstractDocument) { + ((AbstractDocument) doc).replace(start, end - start, str, null); + } else { + doc.remove(start, end - start); + doc.insertString(start, str, null); + } + } catch (BadLocationException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/logging/logging.properties b/src/oldMain/java/umich/opensearch/kde/logging/logging.properties new file mode 100644 index 0000000..b2c9d97 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/logging/logging.properties @@ -0,0 +1,52 @@ +############################################################ +# Default Logging Configuration File +# +# You can use a different file by specifying a filename +# with the java.util.logging.config.file system property. +# For example java -Djava.util.logging.config.file=myfile +############################################################ +############################################################ +# Global properties +############################################################ +# "handlers" specifies a comma separated list of log Handler +# classes. These handlers will be installed during VM startup. +# Note that these classes must be on the system classpath. +# By default we only configure a ConsoleHandler, which will only +# show messages at the INFO and above levels. +handlers=java.util.logging.ConsoleHandler +# To also add the FileHandler, use the following line instead. +#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler +# Default global logging level. +# This specifies which kinds of events are logged across +# all loggers. For any given facility this global level +# can be overriden by a facility specific level +# Note that the ConsoleHandler also has a separate level +# setting to limit messages printed to the console. +.level=INFO +############################################################ +# Handler specific properties. +# Describes specific configuration info for Handlers. +############################################################ +# default file output is in user's home directory. +#java.util.logging.FileHandler.pattern = %h/java%u.log +#java.util.logging.FileHandler.limit = 50000 +#java.util.logging.FileHandler.count = 1 +#java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter +# Limit the message that are printed on the console to INFO and above. +java.util.logging.ConsoleHandler.level=FINEST +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +# Example to customize the SimpleFormatter output format +# to print one-line log message like this: +# : [] +# +# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n +############################################################ +# Facility specific properties. +# Provides extra control for each logger. +############################################################ +# For example, set the com.xyz.foo logger to only log SEVERE +# messages: +umich.opensearch.level=WARNING +com.github.chhh.utils.level=WARNING +umich.opensearch.kde.jfree.level=WARNING +umich.opensearch.kde.gui.level=WARNING diff --git a/src/oldMain/java/umich/opensearch/kde/params/CSVFormatValidator.java b/src/oldMain/java/umich/opensearch/kde/params/CSVFormatValidator.java new file mode 100644 index 0000000..a3ebc40 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/CSVFormatValidator.java @@ -0,0 +1,21 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.ParameterException; +import java.util.Arrays; +import java.util.List; + +/** + * @author Dmitry Avtonomov + */ +public class CSVFormatValidator implements IValueValidator { + + @Override + public void validate(String s, String val) throws ParameterException { + List csvFormats = Arrays.asList("csv", "tsv", "excel"); + if (!csvFormats.contains(val)) { + throw new ParameterException(String.format("Supported formats for \"%s\" optiion are: %s", s, + Arrays.toString(csvFormats.toArray()))); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/DecimalFormatParameter.java b/src/oldMain/java/umich/opensearch/kde/params/DecimalFormatParameter.java new file mode 100644 index 0000000..65295cb --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/DecimalFormatParameter.java @@ -0,0 +1,37 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.ParameterException; +import java.text.DecimalFormat; + +/** + * @author Dmitry Avtonomov + */ +public class DecimalFormatParameter implements IParameterValidator, IStringConverter, + IValueValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + DecimalFormat format = new DecimalFormat(); + try { + format.applyPattern(value); + } catch (IllegalArgumentException e) { + throw new ParameterException(String.format( + "Parameter '%s' had incorrect format '%s'. " + + "Check https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html " + + "for correct formats.", name, value)); + } + } + + @Override + public DecimalFormat convert(String value) { + return new DecimalFormat(value); + } + + @Override + public void validate(String name, DecimalFormat value) throws ParameterException { + + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/DecoyTreatment.java b/src/oldMain/java/umich/opensearch/kde/params/DecoyTreatment.java new file mode 100644 index 0000000..2b55594 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/DecoyTreatment.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.params; + +/** + * @author Dmitry Avtonomov + */ +public enum DecoyTreatment { + FORWARDS_ONLY, + DECOYS_ONLY, + USE_BOTH +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/DoubleParameter.java b/src/oldMain/java/umich/opensearch/kde/params/DoubleParameter.java new file mode 100644 index 0000000..eadedc3 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/DoubleParameter.java @@ -0,0 +1,8 @@ +package umich.opensearch.kde.params; + +/** + * @author Dmitry Avtonomov + */ +public class DoubleParameter { + +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/ExistingPathValidator.java b/src/oldMain/java/umich/opensearch/kde/params/ExistingPathValidator.java new file mode 100644 index 0000000..d90c190 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/ExistingPathValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.params; + +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.ParameterException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Checks that a path corresponds to existing file. + * + * @author Dmitry Avtonomov + */ +public class ExistingPathValidator implements IValueValidator { + + @Override + public void validate(String name, Path value) throws ParameterException { + if (!Files.exists(value)) { + throw new ParameterException(String.format("Error with '%s'. Path '%s' doesn't exist.", name, + value.toAbsolutePath().toString())); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/FileChecker.java b/src/oldMain/java/umich/opensearch/kde/params/FileChecker.java new file mode 100644 index 0000000..936f2fc --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/FileChecker.java @@ -0,0 +1,28 @@ +package umich.opensearch.kde.params; + +import java.nio.file.Path; + +/** + * @author Dmitry Avtonomov + */ +public class FileChecker { + + String[] exts; + + public FileChecker(String... exts) { + this.exts = exts; + for (int i = 0; i < this.exts.length; i++) { + this.exts[i] = this.exts[i].toLowerCase(); + } + } + + public boolean accepts(Path path) { + String pathStr = path.toString().toLowerCase(); + for (String ext : exts) { + if (pathStr.endsWith(ext)) { + return true; + } + } + return false; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/IWeightFetcher.java b/src/oldMain/java/umich/opensearch/kde/params/IWeightFetcher.java new file mode 100644 index 0000000..2d12aa9 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/IWeightFetcher.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.params; + +/** + * @author Dmitry Avtonomov + */ +public interface IWeightFetcher { + + double fetch(T t); +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/KDEKernelType.java b/src/oldMain/java/umich/opensearch/kde/params/KDEKernelType.java new file mode 100644 index 0000000..7b083c8 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/KDEKernelType.java @@ -0,0 +1,23 @@ +package umich.opensearch.kde.params; + +import jsat.distributions.empirical.kernelfunc.EpanechnikovKF; +import jsat.distributions.empirical.kernelfunc.GaussKF; +import jsat.distributions.empirical.kernelfunc.KernelFunction; +import jsat.distributions.empirical.kernelfunc.UniformKF; +import umich.opensearch.kde.params.kernels.GaussFasterKF; + +/** + * @author Dmitry Avtonomov + */ +public enum KDEKernelType { + GAUSS(GaussKF.getInstance()), + GAUSS_FAST(GaussFasterKF.getInstance()), + EPANECHNIKOV(EpanechnikovKF.getInstance()), + UNIFORM(UniformKF.getInstance()); + + public final KernelFunction kernel; + + KDEKernelType(KernelFunction kernel) { + this.kernel = kernel; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/MassCorrection.java b/src/oldMain/java/umich/opensearch/kde/params/MassCorrection.java new file mode 100644 index 0000000..64c2aeb --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/MassCorrection.java @@ -0,0 +1,175 @@ +package umich.opensearch.kde.params; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ExecutorService; +import jsat.distributions.empirical.kernelfunc.EpanechnikovKF; +import jsat.distributions.empirical.kernelfunc.KernelFunction; +import jsat.linear.DenseVector; +import jsat.linear.Vec; +import jsat.math.Function; +import jsat.math.FunctionVec; +import jsat.math.optimization.BacktrackingArmijoLineSearch; +import jsat.math.optimization.LBFGS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.api.SearchHitDiff; +import umich.opensearch.kde.api.SearchHitDiffs; +import umich.opensearch.kde.jsat.KDEKludge; +import umich.opensearch.kde.jsat.KDEUtils; + +/** + * @author Dmitry Avtonomov + */ +public enum MassCorrection { + NONE, + ZERO_PEAK, + CAL_FILES; + + private static final Logger log = LoggerFactory.getLogger(MassCorrection.class); + + + /** + * Some correction converters might incur calling sorting by mass diff. + * + * @param shds The diffs to be corrected (in place). The mass correction type will be set as + * well. + * @param type Mass correction type. + * @param massCal Can be null. Needed only for {@link MassCorrection#CAL_FILES}. + */ + public static void correctMasses(SearchHitDiffs shds, MassCorrection type, + HashMap massCal) { + log.info("Running mass correction for '{}' using correction type '{}'", shds.getName(), + type.toString()); + switch (type) { + case ZERO_PEAK: + final double ZERO_PEAK_WINDOW = 0.5; + final double mLo = -1 * ZERO_PEAK_WINDOW; + final double mHi = ZERO_PEAK_WINDOW; + final double stepInitSearch = 0.01; // we'll use this step to find the initial location near which we'll be looking for a solution +// final KernelFunction kernelFunction = UniformKF.getInstance(); + final KernelFunction kernelFunction = EpanechnikovKF.getInstance(); +// final KernelFunction kernelFunction = GaussKF.getInstance(); + final int numBins = (int) Math.ceil((ZERO_PEAK_WINDOW * 2) / stepInitSearch); + if (numBins < 3) { + String msg = "Calculated number of bins for zero-peak mass correction was smaller than 3, this should not happen"; + log.error(msg); + throw new IllegalStateException(msg); + } + if (!shds.isSorted()) { + shds.sortByDiffMass(); + } + List diffsByMassDiffRange = shds.findDiffsByMassDiffRange(mLo, mHi); + if (diffsByMassDiffRange.size() < 10) { + log.warn( + "Less than 10 hits found for zero-peak mass correction in mass range [-0.5; 0.5], not doing any correction"); + return; + } + + // find the possible location for maximum + double[] massDiffsArray = SearchHitDiffs.getMassDiffsArray(diffsByMassDiffRange); + DenseVector massDiffsVec = new DenseVector(massDiffsArray); + double bandwidth = KDEUtils.estimateBandwidth(massDiffsVec, 0.0, ZERO_PEAK_WINDOW); + log.debug( + "Estimated bandwidth for zero-peak correction for '{}', obtained a value of {} using gaussian formula", + shds.getName(), bandwidth); + bandwidth = bandwidth * 2; // relax a little + final KDEKludge kde = new KDEKludge(massDiffsVec, kernelFunction, bandwidth); + + final double eps = 1e-8d; + final double maxMassToleranceWanted = 1e-6d; + final Function func = new Function() { + @Override + public double f(double... x) { + return -1 * kde.pdf(x[0]); + } + + @Override + public double f(Vec x) { + return -1 * kde.pdf(x.get(0)); + } + }; + final FunctionVec funcp = new FunctionVec() { + @Override + public Vec f(double... x) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public Vec f(Vec x) { + DenseVector result = new DenseVector(x.length()); + f(x, result); + return result; + } + + @Override + public Vec f(Vec x, Vec s) { + return f(x, s, null); + } + + @Override + public Vec f(Vec x, Vec s, ExecutorService ex) { + Vec xLo = new DenseVector(x); + final double delta = maxMassToleranceWanted / 2d; + xLo = xLo.add(-1d * delta); + Vec xHi = new DenseVector(x); + xHi = xHi.add(delta); + double v = (func.f(xHi) - func.f(xLo)) / (delta * 2d); + return new DenseVector(Arrays.asList(v)); + } + }; + + BacktrackingArmijoLineSearch lineSearch = new BacktrackingArmijoLineSearch(); + LBFGS lbfgs = new LBFGS(5, 100, lineSearch); + Vec lbfgsSolution = new DenseVector(1); + lbfgs + .optimize(1e-12, lbfgsSolution, new DenseVector(Arrays.asList(0.0)), func, funcp, null); + double mCorrection = lbfgsSolution.get(0); + + log.debug(String + .format("ZeroPeak correction: computed for '%s', zero-peak shift of '%.5f' using LBFGS", + shds.getName(), mCorrection)); + + List hits = shds.getHits(); + double newDiffVal; + for (SearchHitDiff hit : hits) { + newDiffVal = hit.getMassDiff() - mCorrection; + hit.setMassDiffCal(newDiffVal); + } + shds.setMassCorrection(MassCorrection.ZERO_PEAK); + + break; + + case CAL_FILES: + if (massCal == null) { + throw new IllegalArgumentException( + "When calling mass correction for CAL_FILES, the mass correction mapping must be provided. " + + + "See PepXmlContent HashMap parseMassCorrectionFile()"); + } + for (SearchHitDiff shd : shds.getHits()) { + String id = shd.getSpectrumId(); + Double calibrated = massCal.get(id); + if (calibrated == null) { + shd.setMassDiffCal(shd.getMassDiff()); + log.warn("Could not find calibrated mass for spectrum query (" + id + + "), setting diff to uncalibrated mass."); + } else { + shd.setMassDiffCal(calibrated); + } + } + shds.setMassCorrection(MassCorrection.CAL_FILES); + + break; + + case NONE: + break; + + default: + throw new IllegalArgumentException( + "MassCorrection " + type.toString() + " is not supported in this method."); + } + } + +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/NamedBandwidth.java b/src/oldMain/java/umich/opensearch/kde/params/NamedBandwidth.java new file mode 100644 index 0000000..44a1162 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/NamedBandwidth.java @@ -0,0 +1,83 @@ +package umich.opensearch.kde.params; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DecimalFormat; + +/** + * @author Dmitry Avtonomov + */ +public class NamedBandwidth { + + public final String name; + public final double bandwidth; + public final double targetMz; + public final double window; + + /** + * Bandwidth with label + * + * @param name to be used on the plot + * @param bandwidth use NaN or Infinity to indicate that you want dynamic bandwidth estimation + */ + public NamedBandwidth(String name, double bandwidth, double targetMz, double window) { + this.name = name; + this.bandwidth = bandwidth; + this.window = window; + this.targetMz = targetMz; + } + + public NamedBandwidth(String name, double bandwidth) { + this.name = name; + this.bandwidth = bandwidth; + this.window = Double.NaN; + this.targetMz = Double.NaN; + } + + public String getName() { + return name; + } + + public double getBandwidth() { + return bandwidth; + } + + public double getTargetMz() { + return targetMz; + } + + public double getWindow() { + return window; + } + + public boolean isBandwidthAutoAtFixedMz() { + return !Double.isNaN(window) && !Double.isNaN(targetMz); + } + + public boolean isBandwidthAuto() { + return !Double.isNaN(window) && Double.isNaN(targetMz); + } + + public String getFilenameAddon() { + StringBuilder sb = new StringBuilder(); + DecimalFormat f = new DecimalFormat("0.###########"); + if (isBandwidthAuto()) { + sb.append("-h-auto"); + } else if (isBandwidthAutoAtFixedMz()) { + sb.append("-h-auto-at-").append(f.format(targetMz)).append("-").append(f.format(window)); + } else { + sb.append("-h-").append(f.format(bandwidth)); + } + return sb.toString(); + } + + /** + * Propose a name for the output file of KDE peak picking results. + * + * @param in should be a directory path, at laest the file will be created relative to this path + * @return full file path of the output file + */ + public Path createOutputPath(Path in) { + return Paths.get(in.toAbsolutePath().toString(), "KDE" + getFilenameAddon() + ".tsv"); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/PathCreatorParameter.java b/src/oldMain/java/umich/opensearch/kde/params/PathCreatorParameter.java new file mode 100644 index 0000000..5f82919 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/PathCreatorParameter.java @@ -0,0 +1,16 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IStringConverter; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Use for paths that are to be created. + */ +public class PathCreatorParameter implements IStringConverter { + + @Override + public Path convert(String value) { + return Paths.get(value).toAbsolutePath(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/PathListParameter.java b/src/oldMain/java/umich/opensearch/kde/params/PathListParameter.java new file mode 100644 index 0000000..d02febe --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/PathListParameter.java @@ -0,0 +1,24 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; +import com.beust.jcommander.converters.CommaParameterSplitter; +import java.util.List; + +/** + * @author Dmitry Avtonomov + */ +public class PathListParameter implements IParameterValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + + CommaParameterSplitter splitter = new CommaParameterSplitter(); + List splittedValues = splitter.split(value); + + PathParameter pathParameter = new PathParameter(); + for (String val : splittedValues) { + pathParameter.validate(name, val); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/PathParameter.java b/src/oldMain/java/umich/opensearch/kde/params/PathParameter.java new file mode 100644 index 0000000..70a3cdc --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/PathParameter.java @@ -0,0 +1,32 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Use for existing paths only. + */ +public class PathParameter implements IParameterValidator, IStringConverter { + + @Override + public void validate(String name, String value) throws ParameterException { + Path path = Paths.get(value).toAbsolutePath(); + if (Files.notExists(path)) { + throw new ParameterException(String.format( + "The specified file doesn't exist: '%s' absolute path: '%s'", value, path.toString())); + } + if (!Files.isReadable(path)) { + throw new ParameterException(String.format( + "The specified file is not readable: '%s' absolute path: '%s'", value, path.toString())); + } + } + + @Override + public Path convert(String value) { + return Paths.get(value).toAbsolutePath(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/PepXmlScore.java b/src/oldMain/java/umich/opensearch/kde/params/PepXmlScore.java new file mode 100644 index 0000000..5718928 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/PepXmlScore.java @@ -0,0 +1,19 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package umich.opensearch.kde.params; + +/** + * @author Dmitry Avtonomov + */ +public enum PepXmlScore { + peptideprophet, + interprophet, + hyperscore, + expect, + bscore, + yscore, + xcorr +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/ScorePredicateFactoryParameter.java b/src/oldMain/java/umich/opensearch/kde/params/ScorePredicateFactoryParameter.java new file mode 100644 index 0000000..b3bc353 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/ScorePredicateFactoryParameter.java @@ -0,0 +1,56 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; +import umich.opensearch.kde.params.predicates.ComparisonType; +import umich.opensearch.kde.params.predicates.ScorePredicate; + + +/** + * @author Dmitry Avtonomov + */ +public class ScorePredicateFactoryParameter implements IParameterValidator, + IStringConverter { + + @Override + public void validate(String name, String value) throws ParameterException { + try { + ScorePredicate.Factory predicate = convert(value); + if (predicate == null) { + throw new ParameterException(String.format( + "Invalid score predicate parameter given (%s). Don't use spaces and use '==' for equality.", + value)); + } + } catch (ParameterException e) { + throw new ParameterException(String.format( + "Invalid score predicate parameter given (%s). Don't use spaces and use '==' for equality.", + value), e); + } + } + + @Override + public ScorePredicate.Factory convert(String value) { + + ComparisonType[] comparisonTypes = ComparisonType.values(); + for (ComparisonType comparisonType : comparisonTypes) { + String[] split = value.split(comparisonType.symbol); + if (split.length == 2) { + String scoreName = split[0]; + if (scoreName.length() == 0) { + throw new ParameterException("Score name must be non-zero length"); + } + String scoreValStr = split[1]; + if (scoreValStr.length() == 0) { + throw new ParameterException("Score value must be non-zero length"); + } + try { + double scoreVal = Double.parseDouble(scoreValStr); + return new ScorePredicate.Factory(comparisonType, scoreName, scoreVal); + } catch (NumberFormatException e) { + } + } + } + return null; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/WeightFetcherSearchHitDiff.java b/src/oldMain/java/umich/opensearch/kde/params/WeightFetcherSearchHitDiff.java new file mode 100644 index 0000000..a5c51a3 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/WeightFetcherSearchHitDiff.java @@ -0,0 +1,36 @@ +package umich.opensearch.kde.params; + +import java.util.Map; +import umich.opensearch.kde.api.SearchHitDiff; + +/** + * @author Dmitry Avtonomov + */ +public class WeightFetcherSearchHitDiff implements IWeightFetcher { + + private final int index; + + private WeightFetcherSearchHitDiff(int index) { + this.index = index; + } + + @Override + public double fetch(SearchHitDiff searchHitDiff) { + return searchHitDiff.getScores()[index]; + } + + public static class Factory { + + String weightScoreName; + + public Factory(String weightScoreName) { + this.weightScoreName = weightScoreName; + } + + public WeightFetcherSearchHitDiff create(Map map) { + Integer index = map.get(weightScoreName); + + return new WeightFetcherSearchHitDiff(index); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/WriteableDirectoryParameter.java b/src/oldMain/java/umich/opensearch/kde/params/WriteableDirectoryParameter.java new file mode 100644 index 0000000..c78c640 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/WriteableDirectoryParameter.java @@ -0,0 +1,47 @@ +package umich.opensearch.kde.params; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.IValueValidator; +import com.beust.jcommander.ParameterException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Dmitry Avtonomov + */ +public class WriteableDirectoryParameter implements IParameterValidator, IStringConverter, + IValueValidator { + + @Override + public void validate(String name, String value) throws ParameterException { + try { + Path path = Paths.get(value); + } catch (InvalidPathException e) { + throw new ParameterException(e); + } + } + + @Override + public Path convert(String value) { + return Paths.get(value).toAbsolutePath(); + } + + @Override + public void validate(String name, Path path) throws ParameterException { + if (Files.notExists(path)) { + throw new ParameterException(String.format( + "The specified directory doesn't exist (%s).", path)); + } + if (!Files.isDirectory(path)) { + throw new ParameterException(String.format( + "Provided path must be an existing directory, not a file (%s).", path)); + } + if (!Files.isWritable(path)) { + throw new ParameterException(String.format( + "The specified directory is not writable (%s)", path)); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/Denoiser.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/Denoiser.java new file mode 100644 index 0000000..17e63e9 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/Denoiser.java @@ -0,0 +1,42 @@ +package umich.opensearch.kde.params.denoise; + +import java.util.List; + +/** + * @param parameter type for the denoising algo. + */ +public interface Denoiser { + + /** + * Apply denoising to a discrete signal. If target equals to origin then the denoising can be done + * in-place, for some implementations you might be required to provide different arrays. + * + * @param origin the data to be denoised + * @param target can be the same as origin, if the implementation supports in-place denoising + * @param params this can, for example, be the result of {@link #getDefaultConfig() } + */ + void denoise(double[] origin, double[] target, NumberedParams params); + + /** + * Default properties to use with this denoiser. + */ + T getDefaultConfig(); + + /** + * Initialize this denoiser's config with the provided parameters. + * + * @param params to find how many params are accepted check the {@link #getNumberConfigParams() } + * method. + */ + T configure(List params); + + int getNumberConfigParams(); + + /** + * Provides description for input parameters. + * + * @param i the ordinal number of the configuration parameter. + * @return A short description of what the parameter does + */ + String getParamDescription(int i); +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/Denoising.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/Denoising.java new file mode 100644 index 0000000..15a1f13 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/Denoising.java @@ -0,0 +1,21 @@ +package umich.opensearch.kde.params.denoise; + +/** + * @author Dmitry Avtonomov + */ +public enum Denoising { + NONE(NoOpDenoiser.getInstance()), + TOTAL_VARIATION(TotalVariationDenoiser.getInstance()), + FUSED_LASSO(FusedLassoDenoiser.getInstance()); + + + private Denoiser denoiser; + + Denoising(Denoiser denoiser) { + this.denoiser = denoiser; + } + + public Denoiser getInstance() { + return denoiser; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/FusedLassoDenoiser.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/FusedLassoDenoiser.java new file mode 100644 index 0000000..1d8649e --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/FusedLassoDenoiser.java @@ -0,0 +1,131 @@ +package umich.opensearch.kde.params.denoise; + +import com.github.chhh.utils.dsp.denoise.DenoiseUtils; +import java.util.List; +import org.jfree.data.xy.DefaultXYDataset; +import org.jfree.data.xy.XYDataset; +import umich.opensearch.kde.jfree.JFreeChartPlot; + +/** + * @author Dmitry Avtonomov + */ +public class FusedLassoDenoiser implements Denoiser { + + private FusedLassoDenoiser() { + } + + public static FusedLassoDenoiser getInstance() { + return Singleton.instance; + } + + @Override + public void denoise(double[] origin, double[] target, NumberedParams params) { + if (!(params instanceof Config)) { + throw new IllegalArgumentException("Params must be of FusedLassoDenoiser.Config class"); + } + if (origin.length != target.length) { + throw new IllegalArgumentException("Array sizes must be equal"); + } + FusedLassoDenoiser.Config conf = (FusedLassoDenoiser.Config) params; + + final double LAMBDA = conf.getLambda(); + final double MU = conf.getMu(); + DenoiseUtils.fused_lasso(origin, target, origin.length, LAMBDA, MU); + + if (conf.isPlotDenoising()) { + JFreeChartPlot plot = new JFreeChartPlot("Fused Lasso denoising"); + String datasetKey = "TotalVar"; + String seriesOrigKey = "Original"; + String seriesDenoisedKey = String.format("Denoised (lambda: %.3f, mu:%.3f)", LAMBDA, MU); + double[] xAxis = (double[]) params.get("massAxis"); + XYDataset dataset = plot.getDataset(datasetKey); + if (dataset == null) { + dataset = new DefaultXYDataset(); + plot.addDataset(datasetKey, dataset, false); + } + if (dataset instanceof DefaultXYDataset) { + DefaultXYDataset xyDataset = (DefaultXYDataset) dataset; + xyDataset.addSeries(seriesOrigKey, new double[][]{xAxis, origin}); + xyDataset.addSeries(seriesDenoisedKey, new double[][]{xAxis, target}); + } + plot.display(JFreeChartPlot.ChartCloseOption.DISPOSE.windowConstant, "Denoise results"); + } + } + + + @Override + public FusedLassoDenoiser.Config getDefaultConfig() { + return new Config(); + } + + @Override + public Config configure(List params) { + if (params.size() != 2) { + throw new IllegalArgumentException( + "FusedLasso denoiser takes exactly 2 parameters for its configuration."); + } + Config config = new Config(); + config.setProperty(Config.PROP_LAMBDA, String.valueOf(params.get(0))); + config.setProperty(Config.PROP_MU, String.valueOf(params.get(1))); + return config; + } + + @Override + public int getNumberConfigParams() { + return 2; + } + + @Override + public String getParamDescription(int i) { + switch (i) { + case 0: + return "Lambda parameter (t1), the amount of penalty for fitted function variation.\n" + + "Should be from 0 (in which case nothing is done to the signal) to any number.\n" + + "See https://en.wikipedia.org/wiki/Lasso_%28statistics%29#Fused_lasso"; + case 1: + return "Mu parameter (t2), the amount of allowed variation in coefficients [0-..]." + + "See https://en.wikipedia.org/wiki/Lasso_%28statistics%29#Fused_lasso"; + } + throw new IllegalArgumentException("Only 0 is the allowed argument."); + } + + private static class Singleton { + + private static final FusedLassoDenoiser instance = new FusedLassoDenoiser(); + } + + public static class Config extends NumberedParams { + + public static String PROP_LAMBDA = "PROP_LAMBDA"; + public static String PROP_MU = "PROP_MU"; + public static String PROP_DO_PLOT = "PROP_DO_PLOT"; + + public double getLambda() { + return Double.parseDouble(getProperty(Config.PROP_LAMBDA, "0.05")); + } + + public double getMu() { + return Double.parseDouble(getProperty(PROP_MU, "0.01")); + } + + public boolean isPlotDenoising() { + return Boolean.parseBoolean(getProperty(PROP_DO_PLOT, "false")); + } + + @Override + public int getNumberOfParameters() { + return 2; + } + + @Override + public double getParameter(int i) { + switch (i) { + case 0: + return getLambda(); + case 1: + return getMu(); + } + throw new IllegalArgumentException("Parameter number can be 0 or 1 for FusedLasso Denoiser"); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/NoOpDenoiser.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/NoOpDenoiser.java new file mode 100644 index 0000000..2e6954c --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/NoOpDenoiser.java @@ -0,0 +1,46 @@ +package umich.opensearch.kde.params.denoise; + +import java.util.List; + +/** + * @author Dmitry Avtonomov + */ +public class NoOpDenoiser implements Denoiser { + + private NoOpDenoiser() { + } + + public static NoOpDenoiser getInstance() { + return Singleton.instance; + } + + @Override + public void denoise(double[] origin, double[] target, NumberedParams params) { + + } + + @Override + public NumberedParams getDefaultConfig() { + return new NumberedParamsDefault(); + } + + @Override + public NumberedParams configure(List params) { + return getDefaultConfig(); + } + + @Override + public int getNumberConfigParams() { + return 0; + } + + @Override + public String getParamDescription(int i) { + return ""; + } + + private static class Singleton { + + private static NoOpDenoiser instance = new NoOpDenoiser(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParams.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParams.java new file mode 100644 index 0000000..b4acf77 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParams.java @@ -0,0 +1,22 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package umich.opensearch.kde.params.denoise; + +import java.util.Properties; + +/** + * @author Dmitry Avtonomov + */ +public abstract class NumberedParams extends Properties { + + public abstract int getNumberOfParameters(); + + /** + * @param i 0-based parameter number + * @return a valid value of parameter + */ + public abstract double getParameter(int i); +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParamsDefault.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParamsDefault.java new file mode 100644 index 0000000..5526ed8 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/NumberedParamsDefault.java @@ -0,0 +1,27 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package umich.opensearch.kde.params.denoise; + +/** + * @author Dmitry Avtonomov + */ +public class NumberedParamsDefault extends NumberedParams { + + public NumberedParamsDefault() { + } + + @Override + public int getNumberOfParameters() { + return 0; + } + + @Override + public double getParameter(int i) { + throw new IllegalStateException( + "You can't get parameters when getNumberOfParameters() returns 0."); + } + +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/denoise/TotalVariationDenoiser.java b/src/oldMain/java/umich/opensearch/kde/params/denoise/TotalVariationDenoiser.java new file mode 100644 index 0000000..84076ff --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/denoise/TotalVariationDenoiser.java @@ -0,0 +1,146 @@ +package umich.opensearch.kde.params.denoise; + +import com.github.chhh.utils.dsp.denoise.DenoiseUtils; +import java.util.List; +import org.jfree.data.xy.DefaultXYDataset; +import org.jfree.data.xy.XYDataset; +import umich.opensearch.kde.jfree.JFreeChartPlot; + +/** + * @author Dmitry Avtonomov + */ +public class TotalVariationDenoiser implements Denoiser { + + private TotalVariationDenoiser() { + } + + public static TotalVariationDenoiser getInstance() { + return Singleton.instance; + } + + @Override + public void denoise(double[] origin, double[] target, NumberedParams params) { + if (!(params instanceof Config)) { + throw new IllegalArgumentException("Params must be of TotalVariationDenoiser.Config class"); + } + if (origin.length != target.length) { + throw new IllegalArgumentException("Array sizes must be equal"); + } + TotalVariationDenoiser.Config conf = (TotalVariationDenoiser.Config) params; + + long time1 = System.nanoTime(); + final double LAMBDA = conf.getLambda(); + DenoiseUtils.TV1D_denoise(origin, target, origin.length, LAMBDA); + long time2 = System.nanoTime(); + //System.out.printf("computation time: %.5fs\n",(double)(time2 - time1) / 1e9); + + if (conf.isPlotDenoising()) { + JFreeChartPlot plot = new JFreeChartPlot("Total variation denoising"); + String datasetKey = "TotalVar"; + String seriesOrigKey = "Original"; + String seriesDenoisedKey = String.format("Denoised, lambda=%.3f", LAMBDA); + double[] xAxis = (double[]) params.get("massAxis"); + + XYDataset dataset = plot.getDataset(datasetKey); + if (dataset == null) { + dataset = new DefaultXYDataset(); + plot.addDataset(datasetKey, dataset, false); + } + if (dataset instanceof DefaultXYDataset) { + DefaultXYDataset xyDataset = (DefaultXYDataset) dataset; + xyDataset.addSeries(seriesOrigKey, new double[][]{xAxis, origin}); + xyDataset.addSeries(seriesDenoisedKey, new double[][]{xAxis, target}); + } + plot.display(JFreeChartPlot.ChartCloseOption.DISPOSE.windowConstant, + "Total Variation Denoise"); + } + } + + + //sort the array, and return the median + private int median(int[] a) { + int temp; + int asize = a.length; + //sort the array in increasing order + for (int i = 0; i < asize; i++) { + for (int j = i + 1; j < asize; j++) { + if (a[i] > a[j]) { + temp = a[i]; + a[i] = a[j]; + a[j] = temp; + } + } + } + //if it's odd + if (asize % 2 == 1) { + return a[asize / 2]; + } else { + return ((a[asize / 2] + a[asize / 2 - 1]) / 2); + } + } + + @Override + public TotalVariationDenoiser.Config getDefaultConfig() { + return new Config(); + } + + @Override + public Config configure(List params) { + if (params.size() != 1) { + throw new IllegalArgumentException( + "TotalVariation denoiser takes exactly 1 parameter for its configuration."); + } + Config config = new Config(); + config.setProperty(Config.PROP_LAMBDA, String.valueOf(params.get(0))); + return config; + } + + @Override + public int getNumberConfigParams() { + return 1; + } + + @Override + public String getParamDescription(int i) { + if (i != 0) { + throw new IllegalArgumentException("Only 0 is the allowed argument."); + } + return "Lambda parameter, the amount of penalty for fitted function variation. " + + "Should be from 0 (in which case nothing is done to the signal) to any number.\n" + + "See https://en.wikipedia.org/wiki/Total_variation_denoising"; + } + + private static class Singleton { + + private static final TotalVariationDenoiser instance = new TotalVariationDenoiser(); + } + + public static class Config extends NumberedParams { + + public static String PROP_LAMBDA = "PROP_LAMBDA"; + public static String PROP_DO_PLOT = "PROP_DO_PLOT"; + + public double getLambda() { + return Double.parseDouble(getProperty(Config.PROP_LAMBDA, "0.5")); + } + + public boolean isPlotDenoising() { + return Boolean.parseBoolean(getProperty(PROP_DO_PLOT, "false")); + } + + @Override + public int getNumberOfParameters() { + return 1; + } + + @Override + public double getParameter(int i) { + switch (i) { + case 0: + return getLambda(); + } + throw new IllegalArgumentException( + "Only 0 is accepted as parameter number for TotalVariation Denoiser."); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/kernels/GaussFasterKF.java b/src/oldMain/java/umich/opensearch/kde/params/kernels/GaussFasterKF.java new file mode 100644 index 0000000..b4bc21f --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/kernels/GaussFasterKF.java @@ -0,0 +1,76 @@ +package umich.opensearch.kde.params.kernels; + +import jsat.distributions.Normal; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; + +/** + * @author Dmitry Avtonomov + */ +public class GaussFasterKF implements KernelFuncPrime2 { + + private GaussFasterKF() { + } + + /** + * Returns the singleton instance of this class + * + * @return the instance of this class + */ + public static GaussFasterKF getInstance() { + return SingletonHolder.INSTANCE; + } + + @Override + public double k(double u) { + return 1 / FastMath.sqrt(MathUtils.TWO_PI) * FastMath.exp(-FastMath.pow(u, 2) / 2); + } + + @Override + public double intK(double u) { + return Normal.cdf(u, 0, 1); + } + + @Override + public double k2() { + return 1; + } + + @Override + public double cutOff() { + /* + * This is not techincaly correct, as this value of k(u) is still 7.998827757006813E-38 + * However, this is very close to zero, and is so small that k(u)+x = x, for most values of x. + * Unless this probability si going to be near zero, values past this point will have + * no effect on the result + */ + return 13; + } + + @Override + public double kPrime(double u) { + return -u * k(u); + } + + @Override + public double kPrime2(double u) { + return (FastMath.pow(u, 2) - 1) * k(u); + } + + @Override + public boolean isKPrime2Exact() { + return true; + } + + @Override + public String toString() { + return "Gaussian Kernel using Apache Commons Math3 library"; + } + + private static class SingletonHolder { + + public static final GaussFasterKF INSTANCE = new GaussFasterKF(); + } + +} + diff --git a/src/oldMain/java/umich/opensearch/kde/params/kernels/KernelFuncPrime2.java b/src/oldMain/java/umich/opensearch/kde/params/kernels/KernelFuncPrime2.java new file mode 100644 index 0000000..72ecc7d --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/kernels/KernelFuncPrime2.java @@ -0,0 +1,21 @@ +package umich.opensearch.kde.params.kernels; + +import jsat.distributions.empirical.kernelfunc.KernelFunction; + +/** + * @author Dmitry Avtonomov + */ +public interface KernelFuncPrime2 extends KernelFunction { + + /** + * Second derivative at point U. + * + * @param u point for derivative calcualtion + */ + double kPrime2(double u); + + /** + * True if the value for second derivative is calculated analytically. + */ + boolean isKPrime2Exact(); +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/ComparisonType.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/ComparisonType.java new file mode 100644 index 0000000..2515733 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/ComparisonType.java @@ -0,0 +1,41 @@ +package umich.opensearch.kde.params.predicates; + +/** + * @author Dmitry Avtonomov + */ +public enum ComparisonType { + LESS(Symbols.LESS), + LESS_OR_EQUAL(Symbols.LESS_OR_EQUAL), + EQUAL(Symbols.EQUAL), + GREATER_OR_EQUAL(Symbols.GREATER_OR_EQUAL), + GREATER(Symbols.GREATER); + + public final String symbol; + + ComparisonType(String symbol) { + this.symbol = symbol; + } + + public static ComparisonType fromString(String s) { + switch (s) { + case Symbols.LESS: + return LESS; + case Symbols.LESS_OR_EQUAL: + return LESS_OR_EQUAL; + case Symbols.EQUAL: + return EQUAL; + case Symbols.GREATER_OR_EQUAL: + return GREATER_OR_EQUAL; + case Symbols.GREATER: + return GREATER; + default: + throw new IllegalArgumentException( + String.format("Unknown string given for parsing as a comparison type: '%s'", s)); + } + } + + @Override + public String toString() { + return symbol; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/IsDecoyPredicate.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/IsDecoyPredicate.java new file mode 100644 index 0000000..d58fe54 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/IsDecoyPredicate.java @@ -0,0 +1,19 @@ +package umich.opensearch.kde.params.predicates; + +import umich.opensearch.kde.api.SearchHitDiff; + +/** + * @author Dmitry Avtonomov + */ +public class IsDecoyPredicate implements NamedPredicate { + + @Override + public boolean apply(SearchHitDiff searchHitDiff) { + return searchHitDiff.isDecoy(); + } + + @Override + public String getDescription() { + return "Decoy sequences"; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/IsForwardPredicate.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/IsForwardPredicate.java new file mode 100644 index 0000000..ccbd865 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/IsForwardPredicate.java @@ -0,0 +1,19 @@ +package umich.opensearch.kde.params.predicates; + +import umich.opensearch.kde.api.SearchHitDiff; + +/** + * @author Dmitry Avtonomov + */ +public class IsForwardPredicate implements NamedPredicate { + + @Override + public String getDescription() { + return "Non-decoy sequences"; + } + + @Override + public boolean apply(SearchHitDiff searchHitDiff) { + return !searchHitDiff.isDecoy(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/MassDiffRangePredicate.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/MassDiffRangePredicate.java new file mode 100644 index 0000000..7c8ef7b --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/MassDiffRangePredicate.java @@ -0,0 +1,56 @@ +package umich.opensearch.kde.params.predicates; + +import com.github.chhh.utils.MathUtils; +import umich.opensearch.kde.api.SearchHitDiff; + +/** + * @author Dmitry Avtonomov + */ +public class MassDiffRangePredicate implements NamedPredicate { + + final double mLo; + final double mHi; + final double[][] exclude; + + public MassDiffRangePredicate(double mLo, double mHi, double[][] exclusionRnages) { + if (mLo > mHi) { + throw new IllegalArgumentException("mLo must be lower or equal to mHi"); + } + this.mLo = mLo; + this.mHi = mHi; + this.exclude = exclusionRnages; + } + + @Override + public boolean apply(SearchHitDiff searchHitDiff) { + if (!MathUtils.isBetweenOrEqual(searchHitDiff.getMassDiff(), mLo, mHi)) { + return false; + } + if (exclude != null) { + for (int i = 0; i < exclude.length; i++) { + if (searchHitDiff.getMassDiff() >= exclude[i][0] + && searchHitDiff.getMassDiff() <= exclude[i][1]) { + return false; + } + } + } + return true; + } + + @Override + public String getDescription() { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("Mass difference in range [%.4f; %.4f]", mLo, mHi)); + if (exclude != null && exclude.length > 0) { + sb.append(", but not in "); + for (int i = 0; i < exclude.length; i++) { + sb.append(String.format("[%.4f; %.4f]", exclude[i][0], exclude[i][1])); + if (i != exclude.length - 1) { + sb.append(", "); + } + } + } + + return sb.toString(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/NamedPredicate.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/NamedPredicate.java new file mode 100644 index 0000000..9fb530e --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/NamedPredicate.java @@ -0,0 +1,15 @@ +package umich.opensearch.kde.params.predicates; + +import com.google.common.base.Predicate; +import java.io.Serializable; + +/** + * @author Dmitry Avtonomov + */ +public interface NamedPredicate extends Predicate, Serializable { + + /** + * Provide a short description of how this predicate operates. + */ + String getDescription(); +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/ScorePredicate.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/ScorePredicate.java new file mode 100644 index 0000000..6696c84 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/ScorePredicate.java @@ -0,0 +1,127 @@ +package umich.opensearch.kde.params.predicates; + +import com.github.chhh.utils.StringUtils; +import com.github.chhh.utils.exceptions.ParsingException; +import com.google.common.base.Predicate; +import java.util.Map; +import umich.opensearch.kde.api.SearchHitDiff; + +/** + * @author Dmitry Avtonomov + */ +public class ScorePredicate implements NamedPredicate { + + final ComparisonType type; + final String name; + final double value; + final private Predicate delegate; + final private Integer index; + + /** + * Use {@link Factory} instead. The reason is that the predicate needs to be configured, as it + * doesn't know the right score index beforehand. + * + * @param type comparison type + * @param name name of the score + * @param value value of the score + */ + protected ScorePredicate(ComparisonType type, String name, double value, + Predicate delegate, Integer index) { + this.type = type; + this.name = name; + this.value = value; + this.delegate = delegate; + this.index = index; + } + + @Override + public boolean apply(SearchHitDiff searchHitDiff) { + return delegate.apply(searchHitDiff.getScores()[index]); + } + + @Override + public String getDescription() { + return String.format("Score '%s' (ordinal #%d) %s %.4f", name, index, type.symbol, value); + } + + public static class Factory { + + final ComparisonType type; + final String name; + final double value; + private Predicate delegate; + + public Factory(ComparisonType type, String name, double value) { + this.type = type; + this.name = name; + this.value = value; + switch (type) { + case LESS: + delegate = new Predicate() { + @Override + public boolean apply(Double v) { + return v < Factory.this.value; + } + }; + break; + case LESS_OR_EQUAL: + delegate = new Predicate() { + @Override + public boolean apply(Double v) { + return v <= Factory.this.value; + } + }; + break; + case EQUAL: + delegate = new Predicate() { + @Override + public boolean apply(Double v) { + return v == Factory.this.value; + } + }; + break; + case GREATER_OR_EQUAL: + delegate = new Predicate() { + @Override + public boolean apply(Double v) { + return v >= Factory.this.value; + } + }; + break; + case GREATER: + delegate = new Predicate() { + @Override + public boolean apply(Double v) { + return v > Factory.this.value; + } + }; + break; + } + + + } + + @Override + public String toString() { + return String.format("ScorePredicate Factory: {%s %s %s}", name, type.symbol, value); + } + + /** + * Creates the predicate. Use this instead of {@link ScorePredicate} constructor, which is + * protected. + * + * @param map mapping from score names to score indexes. SearchHitDiffs must have such a + * mapping. + */ + public ScorePredicate create(Map map) throws ParsingException { + Integer idx = map.get(name); + if (idx == null) { + throw new ParsingException(String.format( + "Mapping from score names to score indexes did not contain the name we searched for: '%s'. " + + + "Known mappings are: '%s'", name, StringUtils.join(map.keySet(), ", "))); + } + return new ScorePredicate(type, name, value, delegate, idx); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/params/predicates/Symbols.java b/src/oldMain/java/umich/opensearch/kde/params/predicates/Symbols.java new file mode 100644 index 0000000..686f70e --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/params/predicates/Symbols.java @@ -0,0 +1,15 @@ +package umich.opensearch.kde.params.predicates; + +/** + * @author Dmitry Avtonomov + */ +public class Symbols { + + public static final String LESS = "<"; + public static final String LESS_OR_EQUAL = "<="; + public static final String EQUAL = "=="; + public static final String GREATER_OR_EQUAL = ">="; + public static final String GREATER = ">"; + private Symbols() { + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetector.java b/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetector.java new file mode 100644 index 0000000..3bed8f7 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetector.java @@ -0,0 +1,11 @@ +package umich.opensearch.kde.pepxml; + +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SearchHit; +import umich.opensearch.kde.params.predicates.NamedPredicate; + +/** + * @author Dmitry Avtonomov + */ +public interface DecoyDetector extends NamedPredicate { + +} diff --git a/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorAcceptAll.java b/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorAcceptAll.java new file mode 100644 index 0000000..9057897 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorAcceptAll.java @@ -0,0 +1,19 @@ +package umich.opensearch.kde.pepxml; + +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SearchHit; + +/** + * @author Dmitry Avtonomov + */ +public class DecoyDetectorAcceptAll implements DecoyDetector { + + @Override + public String getDescription() { + return "All peptides accepted as forwards"; + } + + @Override + public boolean apply(SearchHit searchHit) { + return false; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorByProtName.java b/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorByProtName.java new file mode 100644 index 0000000..cbacb21 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/pepxml/DecoyDetectorByProtName.java @@ -0,0 +1,33 @@ +package umich.opensearch.kde.pepxml; + +import java.util.List; +import umich.ms.fileio.filetypes.pepxml.jaxb.standard.SearchHit; + +/** + * @author Dmitry Avtonomov + */ +public class DecoyDetectorByProtName implements DecoyDetector { + + private List protNamePrefixes; + private String desc; + + public DecoyDetectorByProtName(List protNamePrefixes) { + this.protNamePrefixes = protNamePrefixes; + this.desc = String.format("Protein name starts with %s", this.protNamePrefixes); + } + + @Override + public String getDescription() { + return desc; + } + + @Override + public boolean apply(SearchHit searchHit) { + for (String protNamePrefix : protNamePrefixes) { + if (searchHit.getProtein().startsWith(protNamePrefix)) { + return true; + } + } + return false; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/pepxml/MsmsRunSummaryUtils.java b/src/oldMain/java/umich/opensearch/kde/pepxml/MsmsRunSummaryUtils.java new file mode 100644 index 0000000..44113ac --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/pepxml/MsmsRunSummaryUtils.java @@ -0,0 +1,241 @@ +package umich.opensearch.kde.pepxml; + +import com.github.chhh.utils.MathUtils; +import com.google.common.primitives.Doubles; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import umich.opensearch.kde.api.SearchHitDiff; +import umich.opensearch.kde.api.SearchHitDiffs; +import umich.opensearch.kde.params.MassCorrection; +import umich.ptm.chem.Table; + +/** + * @author Dmitry Avtonomov + */ +public class MsmsRunSummaryUtils { + + private static final Logger log = LoggerFactory.getLogger(MsmsRunSummaryUtils.class); + private static final String PEP_PROPH = "peptideprophet"; + private static final String I_PROPH = "interprophet"; + + private MsmsRunSummaryUtils() { + } + + public static SearchHitDiffs convert(MsmsRunSummary summary, DecoyDetector detector, + HashMap massCal) { + SearchHitCount count = countSearchHits(summary, detector); + ArrayList result = new ArrayList<>(count.getTotal()); + + double precursorNeutralMass, massDiff, calcNeutralPepMass, massDiffCalced; + int assumedCharge; + double observedMzInferred; + final double PROTON_MASS = Table.Proton.monoMass; + Double rt; + Long startScan; + int[] chargeStatesToCheck = {1, 2, 3, 4, 5, 6, 7}; + int hitsWithPossibleWrongCharge = 0; + double mzToCheck; + final double PPM_PLUS_MINUS = 20; + int pepProphIdx = -1; // less than zero means it's not there + int iProphIdx = -1; // less than zero means it's not there + + int hitsTotal = 0; + int hitsModified = 0; + int hitsDecoy = 0; + + List searchResults; + List spectrumQueries; + List searchHits; + SearchHit searchHitFirst = null; + + log.info(String.format("Processing msms_run_summary '%s'", summary.getBaseName())); + + Map mapScore2Index = new HashMap<>(10); + + spectrumQueries = summary.getSpectrumQuery(); + for (SpectrumQuery spectrumQuery : spectrumQueries) { + + precursorNeutralMass = spectrumQuery.getPrecursorNeutralMass(); + rt = spectrumQuery.getRetentionTimeSec(); + startScan = spectrumQuery.getStartScan(); + assumedCharge = spectrumQuery.getAssumedCharge(); + observedMzInferred = (precursorNeutralMass + assumedCharge * PROTON_MASS) / assumedCharge; + + searchResults = spectrumQuery.getSearchResult(); + for (SearchResult searchResult : searchResults) { + + searchHits = searchResult.getSearchHit(); + int searchHitNumInQuery = 0; + for (SearchHit searchHit : searchHits) { + if (searchHitFirst == null) { + // this is the first hit, we'll use it to determine the size of score arrays, + // including peptide prophet and iprophet analysis + searchHitFirst = searchHit; + + // get regular scores + List searchScore = searchHitFirst.getSearchScore(); + int scoreIndex = 0; + for (; scoreIndex < searchScore.size(); scoreIndex++) { + NameValueType score = searchScore.get(scoreIndex); + String scoreName = score.getName(); + mapScore2Index.put(scoreName, scoreIndex); + } + + // try get peptide prophet score + List analysisResults = searchHit.getAnalysisResult(); + for (AnalysisResult analysisResult : analysisResults) { + String analysis = analysisResult.getAnalysis(); + switch (analysis) { + case PEP_PROPH: + pepProphIdx = scoreIndex; + mapScore2Index.put(PEP_PROPH, scoreIndex); + scoreIndex++; + break; + case I_PROPH: + iProphIdx = scoreIndex; + mapScore2Index.put(I_PROPH, scoreIndex); + scoreIndex++; + break; + } + } + } + searchHitNumInQuery++; + hitsTotal++; + + if (searchHit.getModificationInfo() != null) { + hitsModified++; + } + + massDiff = searchHit.getMassdiff(); + calcNeutralPepMass = searchHit.getCalcNeutralPepMass(); + //massDiffCalced = precursorNeutralMass - calcNeutralPepMass; + massDiffCalced = massDiff; + + SearchHitDiff shd = new SearchHitDiff(mapScore2Index.size()); + shd.setMassDiff(massDiffCalced); + shd.setCalcedNeutralMass(calcNeutralPepMass); + shd.setObservedNeutralMass(precursorNeutralMass); + shd.setCharge(assumedCharge); + shd.setSpectrumId(spectrumQuery.getSpectrum()); + if (massCal != null) { + Double calibrated = massCal.get(spectrumQuery.getSpectrum()); + if (calibrated == null) { + log.warn("Could not find calibrated mass for spectrum query (" + spectrumQuery + .getSpectrum() + ")"); + } else { + shd.setMassDiffCal(calibrated); + } + } else { + shd.setMassDiffCal(massDiffCalced); + } + shd.setSeq(searchHit.getPeptide()); + if (startScan != null) { + shd.setScanNum(startScan.intValue()); + startScan = null; + } + if (rt != null) { + shd.setRtSec(rt); + rt = null; + } + + List searchScores = searchHit.getSearchScore(); + double[] scores = shd.getScores(); + for (int i = 0; i < searchScores.size(); i++) { + try { + scores[i] = Doubles.tryParse(searchScores.get(i).getValueStr()); + } catch (NumberFormatException e) { + scores[i] = Double.NaN; + log.warn("Could not parse score #{} for spectrum query '{}' search hit '{}'", i, + spectrumQuery.getSpectrum(), searchHitNumInQuery); + } + } + if (pepProphIdx > -1 || iProphIdx > -1) { + List analysisResults = searchHit.getAnalysisResult(); + for (AnalysisResult analysisResult : analysisResults) { + final String analysis = analysisResult.getAnalysis(); + final List any = analysisResult.getAny(); + for (Object o : any) { + if (o instanceof PeptideprophetResult) { + scores[pepProphIdx] = ((PeptideprophetResult) o).getProbability(); + } else if (o instanceof InterprophetResult) { + scores[iProphIdx] = ((InterprophetResult) o).getProbability(); + } else { + log.error("Unknown AnalysisResult type encountered: '{}'", analysis); + } + } + } + } + + if (detector.apply(searchHit)) { + shd.setDecoy(true); + hitsDecoy++; + } else { + shd.setDecoy(false); + } + if (massDiff > 2) { + // then check if this diff can be attributed to misassigned charge + for (int z : chargeStatesToCheck) { + mzToCheck = (calcNeutralPepMass + z * PROTON_MASS) / z; + if (MathUtils.isWithinPpm(observedMzInferred, mzToCheck, PPM_PLUS_MINUS)) { + if (z != assumedCharge) { + hitsWithPossibleWrongCharge++; + break; + } + } + } + } + result.add(shd); + } + } + } + + MassCorrection mc = massCal == null ? MassCorrection.NONE : MassCorrection.CAL_FILES; + SearchHitDiffs searchHitDiffs = new SearchHitDiffs(result, mapScore2Index, mc); + + searchHitDiffs.sortByDiffMass(); + searchHitDiffs.setName(summary.getBaseName()); + long timeSortStart = System.nanoTime(); + log.debug("Started sorting MsMsRunSummary by diff mass", + (System.nanoTime() - timeSortStart) / 1e6d); + searchHitDiffs.sortByDiffMass(); + log.debug(String.format("Sorting took %.3fms", (System.nanoTime() - timeSortStart) / 1e6d)); + log.info(String.format("There were a total of %d search hits in the run summary.\n" + + "\tModified: %d\n" + + "\tMight be incorrect charge assignments: %d\n" + + "\tDecoy hits (%s): %d\n" + + "\tFinal list contains: %d hits", + hitsTotal, hitsModified, hitsWithPossibleWrongCharge, + detector.getDescription(), hitsDecoy, result.size())); + return searchHitDiffs; + } + + private static SearchHitCount countSearchHits(MsmsRunSummary msmsRunSummary, + DecoyDetector detector) { + int hitsFwd = 0; + int hitsRev = 0; + List searchResults; + List spectrumQueries; + List searchHits; + + spectrumQueries = msmsRunSummary.getSpectrumQuery(); + for (SpectrumQuery spectrumQuery : spectrumQueries) { + searchResults = spectrumQuery.getSearchResult(); + for (SearchResult searchResult : searchResults) { + searchHits = searchResult.getSearchHit(); + for (SearchHit searchHit : searchHits) { + if (detector.apply(searchHit)) { + hitsRev++; + } else { + hitsFwd++; + } + } + } + } + + return new SearchHitCount(hitsFwd, hitsRev); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/pepxml/SearchHitCount.java b/src/oldMain/java/umich/opensearch/kde/pepxml/SearchHitCount.java new file mode 100644 index 0000000..03b5cd6 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/pepxml/SearchHitCount.java @@ -0,0 +1,23 @@ +package umich.opensearch.kde.pepxml; + +/** + * @author Dmitry Avtonomov + */ +public class SearchHitCount { + + public final int forwards; + public final int decoys; + + public SearchHitCount(int forwards, int decoys) { + this.forwards = forwards; + this.decoys = decoys; + } + + public int getTotal() { + if ((long) forwards + (long) decoys > Integer.MAX_VALUE) { + throw new RuntimeException( + "Total number of forward and decoy hits was larger than Integer.MAX_VALUE"); + } + return forwards + decoys; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/ArrayFilter.java b/src/oldMain/java/umich/opensearch/kde/util/ArrayFilter.java new file mode 100644 index 0000000..f6805af --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/ArrayFilter.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package umich.opensearch.kde.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Dmitry Avtonomov + */ +public class ArrayFilter { + + private static final Logger log = LoggerFactory.getLogger(ArrayFilter.class); + + double[] mass; + double[] ints; + + public ArrayFilter(double[] mass, double[] ints) { + if (mass.length != ints.length) { + throw new IllegalArgumentException("Must be equal lengths"); + } + this.mass = mass; + this.ints = ints; + } + + public double[] getMass() { + return mass; + } + + public double[] getInts() { + return ints; + } + + /** + * Filter based on zeroes from the second array provided to the constructor. + * + * @return New arrays, the original are left intact. + */ + public ArrayFilter filterZeroesLeaveFlanking() { + if (ints.length < 5) { + return this; // don't bother + } + double prevInt = ints[0], curInt = ints[1], nextInt; + int lengthOriginal = ints.length; + + int[] acceptedIndexes = new int[mass.length]; + int acceptedCount = 0; + acceptedIndexes[0] = 0; // always accept first + acceptedCount++; + + int i = 1; + do { + nextInt = ints[i + 1]; + if ((prevInt == 0 && nextInt == 0) && curInt == 0) { + // these are being cut out + } else { + acceptedIndexes[acceptedCount] = i; + acceptedCount++; + } + prevInt = curInt; + curInt = nextInt; + i++; + } while (i < ints.length - 1); + acceptedIndexes[acceptedCount] = ints.length - 1; // always accept last + acceptedCount++; + + double[] massFiltered = new double[acceptedCount]; + double[] intsFiltered = new double[acceptedCount]; + int acceptedIndex; + for (int j = 0; j < acceptedCount; j++) { + acceptedIndex = acceptedIndexes[j]; + massFiltered[j] = mass[acceptedIndex]; + intsFiltered[j] = ints[acceptedIndex]; + } + log.debug("Filtering two double arrays helped remove {} entries. " + + "Old size {} new size {}.", lengthOriginal - intsFiltered.length, lengthOriginal, + intsFiltered.length); + return new ArrayFilter(massFiltered, intsFiltered); + } + +// public static void main(String[] args) { +// double[] mass = new double[]{1,2,3,4,5,6}; +// double[] ints = new double[]{1,2,0,0,0,6}; +// ArrayFilter filter = new ArrayFilter(mass, ints); +// ArrayFilter filtered = filter.filterZeroesLeaveFlanking(); +// +// System.out.printf("Original size: %d\n", filter.getInts().length); +// System.out.printf("Filtered size: %d\n", filtered.getInts().length); +// } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/BracketUtils.java b/src/oldMain/java/umich/opensearch/kde/util/BracketUtils.java new file mode 100644 index 0000000..a39038b --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/BracketUtils.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import jsat.linear.DenseVector; + +/** + * @author Dmitry Avtonomov + */ +public class BracketUtils { + + /** + * Finds index brackets for values which are within maxDist of integer values. + * + * @param maxDist max distance from the integer value + * @param data array to search in + * @return lo index is inclusive, hi index is exclusive. + */ + public static List findIntegerBrackets(double maxDist, double[] data) { + List brackets = new ArrayList<>(); + if (data.length == 0) { + return brackets; + } + int loInt = (int) (data[0] + 0.5); + int hiInt = (int) (data[data.length - 1] + 0.5); + for (int i = loInt; i <= hiInt; i++) { + double loTarget = i - maxDist; + double hiTarget = i + maxDist; + IndexBracket bracket = findBracket(loTarget, hiTarget, data); + if (bracket.size != 0) { + brackets.add(bracket); + } + } + + return brackets; + } + + public static IndexBracket findBracket(double loTarget, double hiTarget, double[] data) { + if (hiTarget < loTarget) { + throw new IllegalArgumentException("HI target must be greater or equal to LO target."); + } + int loVecIdx = Arrays.binarySearch(data, loTarget); + if (loVecIdx < 0) { + loVecIdx = ~loVecIdx; + } else { + // if we found the exact value, we still need to check to the left, there might be repeating values + double origValue = data[loVecIdx]; + for (int i = loVecIdx - 1; i >= 0; i--) { + if (data[i] == origValue) { + loVecIdx = i; + } else { + break; + } + } + } + int hiVecIdx = Arrays.binarySearch(data, hiTarget); + if (hiVecIdx < 0) { + hiVecIdx = ~hiVecIdx; + } else { + double origValue = data[hiVecIdx]; + for (int i = hiVecIdx + 1; i < data.length; i++) { + if (data[i] == origValue) { + hiVecIdx = i; + } else { + break; + } + } + } + if (hiVecIdx < loVecIdx) { + throw new IllegalStateException( + "Input data array was likely not sorted, which resulted in incorrect binary search."); + } + if (hiVecIdx == loVecIdx) { + return new IndexBracket(loVecIdx, hiVecIdx, Double.NaN, Double.NaN); + } + return new IndexBracket(loVecIdx, hiVecIdx, data[loVecIdx], data[hiVecIdx - 1]); + } + + public static IndexBracket findBracket(double loTarget, double hiTarget, DenseVector data) { + if (hiTarget < loTarget) { + throw new IllegalArgumentException("HI target must be greater or equal to LO target."); + } + int loVecIdx = binarySearch(data, 0, data.length(), loTarget); + if (loVecIdx < 0) { + loVecIdx = ~loVecIdx; + } else { + // if we found the exact value, we still need to check to the left, there might be repeating values + double origValue = data.get(loVecIdx); + for (int i = loVecIdx - 1; i >= 0; i--) { + if (data.get(i) == origValue) { + loVecIdx = i; + } else { + break; + } + } + } + int hiVecIdx = binarySearch(data, 0, data.length(), hiTarget); + if (hiVecIdx < 0) { + hiVecIdx = ~hiVecIdx; + } else { + double origValue = data.get(hiVecIdx); + for (int i = hiVecIdx + 1; i < data.length(); i++) { + if (data.get(i) == origValue) { + hiVecIdx = i; + } else { + break; + } + } + } + if (hiVecIdx < loVecIdx) { + throw new IllegalStateException( + "Input data array was likely not sorted, which resulted in incorrect binary search."); + } + if (hiVecIdx == loVecIdx) { + return new IndexBracket(loVecIdx, hiVecIdx, Double.NaN, Double.NaN); + } + return new IndexBracket(loVecIdx, hiVecIdx, data.get(loVecIdx), data.get(hiVecIdx - 1)); + } + + public static double[] createArraySliceCopy(IndexBracket b, double[] data) { + return Arrays.copyOfRange(data, b.lo, b.hi); + } + + /** + * Linear interpolation based on a precomputed array of data. + * + * @param xAxis The data X array. + * @param yAxis The data Y array. + * @param x The point to calculate interpolation at. If outside of data range + * IllegalArgumentException is thrown. + * @return Linearly interpolated value. + */ + public static double interpolateLinear(final double[] xAxis, final double[] yAxis, + final double x) { + if (x < xAxis[0] || x > xAxis[xAxis.length - 1]) { + throw new IllegalArgumentException("x must be within xAxis range."); + } + int pos = Arrays.binarySearch(xAxis, x); + if (pos >= 0) { + return yAxis[pos]; + } + + pos = ~pos; + if (pos == 0) { + return yAxis[0]; + } + if (pos == xAxis.length - 1) { + return yAxis[xAxis.length - 1]; + } + + // value at pos is higher than the one searched for + double span = xAxis[pos] - xAxis[pos - 1]; + double w0 = (xAxis[pos] - x) / span; + double w1 = (x - xAxis[pos - 1]) / span; + double interp = w0 * yAxis[pos - 1] + w1 * yAxis[pos]; + return interp; + } + + public static int binarySearch(DenseVector vec, int from, int to, double target) { + int low = from; + int high = to - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + double midVal = vec.get(mid); + + if (midVal < target) { + low = mid + 1; + } else if (midVal > target) { + high = mid - 1; + } else { + long midBits = Double.doubleToLongBits(midVal); + long keyBits = Double.doubleToLongBits(target); + if (midBits == keyBits) { + return mid; + } else if (midBits < keyBits) { + low = mid + 1; + } else { + high = mid - 1; + } + } + } + return -(low + 1); + } + + /** + * Y axis is searched for gaps of zeros and corresponding X axis ranges are reported (for non-zero + * regions). + * + * @param from Inclusive. + * @param to Exclusive. + */ + public static List findNonZeroBrackets(double[] xAxis, double[] yAxis, int from, + int to) { + ArrayList res = new ArrayList<>(); + int lo = -1; + for (int i = 0; i < xAxis.length; i++) { + double y = yAxis[i]; + if (lo < 0) { + if (y > 0) { + lo = i; + } + } else if (y == 0) { + IndexBracket b = new IndexBracket(lo, i, xAxis[lo], xAxis[i - 1]); + res.add(b); + lo = -1; + } + } + if (lo >= 0) { + int hi = xAxis.length; + IndexBracket b = new IndexBracket(lo, hi, xAxis[lo], xAxis[hi - 1]); + res.add(b); + } + return res; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/DecimalFormatWithToString.java b/src/oldMain/java/umich/opensearch/kde/util/DecimalFormatWithToString.java new file mode 100644 index 0000000..587bf4e --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/DecimalFormatWithToString.java @@ -0,0 +1,26 @@ +package umich.opensearch.kde.util; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; + +/** + * @author Dmitry Avtonomov + */ +public class DecimalFormatWithToString extends DecimalFormat { + + public DecimalFormatWithToString() { + } + + public DecimalFormatWithToString(String pattern) { + super(pattern); + } + + public DecimalFormatWithToString(String pattern, DecimalFormatSymbols symbols) { + super(pattern, symbols); + } + + @Override + public String toString() { + return this.toPattern(); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/DelimitedFileUtil.java b/src/oldMain/java/umich/opensearch/kde/util/DelimitedFileUtil.java new file mode 100644 index 0000000..868ee9a --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/DelimitedFileUtil.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utilities for working with delimited files. + * + * @author Dmitry Avtonomov + */ +public class DelimitedFileUtil { + + private DelimitedFileUtil() { + } + + /** + * Tries to guess a single character delimiter from several lines of input.
Only guesses + * between comma, tab, semicolon and space (multiple spaces not supported!). + * + * @param lines E.g. lines of a delimited text file. + * @return Null in case a delimiter could not be guessed. + */ + public Character guessDelimiter(List lines) { + char[] delimiters = {' ', '\t', ',', ';'}; + int[][] counts = new int[lines.size()][delimiters.length]; // [line][delimiter] + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + for (int j = 0; j < line.length(); j++) { + for (int k = 0; k < delimiters.length; k++) { + if (line.charAt(j) == delimiters[k]) { + counts[i][k]++; + } + } + } + } + + Map map = new HashMap<>(); + for (int i = 0; i < delimiters.length; i++) { + char delimiter = delimiters[i]; + boolean isSameCounts = true; + int cntPrev = counts[0][i]; + for (int j = 0; j < lines.size(); j++) { + if (counts[j][i] != cntPrev) { + isSameCounts = false; + break; + } + cntPrev = counts[j][i]; + } + if (isSameCounts) { + map.put(delimiter, cntPrev); + } + } + + Character delimiter = null; + Integer count = -1; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() > count) { + count = entry.getValue(); + delimiter = entry.getKey(); + } + } + + return count <= 0 ? null : delimiter; + } + + /** + * Reads N lines from a stream. + * + * @param is The stream to read from. Will be wrapped into a BufferedReader using UTF-8 charset. + * @param n The number of lines to read. + * @param nonEmpty If true, empty lines will be skipped. Lines containing whitespace only will NOT + * be skipped as whitespace characters can be delimiters. + * @return List of read lines. The list might contain fewer lines than requested if there were + * fewer lines in the input. + */ + public List readLines(InputStream is, int n, boolean nonEmpty) throws IOException { + List lines = new ArrayList<>(n); + try (BufferedReader br = new BufferedReader( + new InputStreamReader(is, Charset.forName("UTF-8")))) { + for (int i = 0; i < n; i++) { + String line = br.readLine(); + if (nonEmpty && line.isEmpty()) { + continue; + } + lines.add(line); + } + } + return lines; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/DistributionResult.java b/src/oldMain/java/umich/opensearch/kde/util/DistributionResult.java new file mode 100644 index 0000000..c115386 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/DistributionResult.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +import java.util.ArrayList; +import umich.opensearch.kde.api.Pair; + +/** + * @author Dmitry Avtonomov + */ +public final class DistributionResult { + + int numWorkers; + double valLo; + double valHi; + double stepSize; + int maxStepNum; + /** + * This is the value, that is calculated based on the stepSize and valLo and valHi. valLo and + * valHi must be covered by interval [startVal; endVal] + */ + double startVal; + /** + * This is the value, that is calculated based on the stepSize and valLo and valHi. valLo and + * valHi must be covered by interval [startVal; endVal] + */ + double endVal; + ArrayList> workerIntervals; + + public int getMaxStepNum() { + return maxStepNum; + } + + public void setMaxStepNum(int maxStepNum) { + this.maxStepNum = maxStepNum; + } + + public int getNumWorkers() { + return numWorkers; + } + + public void setNumWorkers(int numWorkers) { + this.numWorkers = numWorkers; + } + + public double getValLo() { + return valLo; + } + + public void setValLo(double valLo) { + this.valLo = valLo; + } + + public double getValHi() { + return valHi; + } + + public void setValHi(double valHi) { + this.valHi = valHi; + } + + public double getStepSize() { + return stepSize; + } + + public void setStepSize(double stepSize) { + this.stepSize = stepSize; + } + + public double getStartVal() { + return startVal; + } + + public void setStartVal(double startVal) { + this.startVal = startVal; + } + + public double getEndVal() { + return endVal; + } + + public void setEndVal(double endVal) { + this.endVal = endVal; + } + + public ArrayList> getWorkerIntervals() { + return workerIntervals; + } + + public void setWorkerIntervals(ArrayList> workerIntervals) { + this.workerIntervals = workerIntervals; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/IndexBracket.java b/src/oldMain/java/umich/opensearch/kde/util/IndexBracket.java new file mode 100644 index 0000000..714e01a --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/IndexBracket.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +/** + * @author Dmitry Avtonomov + */ +public class IndexBracket implements Comparable { + + /** + * Inclusive. + */ + public final int lo; + /** + * Exclusive. + */ + public final int hi; + public final double loVal; + public final double hiVal; + public final int size; + + + public IndexBracket(int lo, int hi, double loVal, double hiVal) { + this.lo = lo; + this.hi = hi; + this.loVal = loVal; + this.hiVal = hiVal; + this.size = hi - lo; + } + + @Override + public String toString() { + return "IndexBracket{" + + "lo=" + lo + + ", hi=" + hi + + ", loVal=" + loVal + + ", hiVal=" + hiVal + + '}'; + } + + @Override + public int compareTo(IndexBracket o) { + return Integer.compare(lo, o.lo); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/MapUtils.java b/src/oldMain/java/umich/opensearch/kde/util/MapUtils.java new file mode 100644 index 0000000..a5088e1 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/MapUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +import java.util.Map; +import java.util.NavigableMap; + +/** + * @author Dmitry Avtonomov + */ +public class MapUtils { + + private MapUtils() { + } + + /** + * Find the closest entry in a map. + * + * @param map The map to search in. + * @param target The target value for lookup. + * @param Type of mapping in the map. + * @return Null if nothing found or the closest value. If two map entries are at the same + * distance, the lower one is returned. + */ + public static T findClosest(NavigableMap map, double target) { + if (map.isEmpty()) { + return null; + } + final Map.Entry ceil = map.ceilingEntry(target); + final Map.Entry floor = map.floorEntry(target); + if (ceil != null) { + if (floor == null) { + return ceil.getValue(); + } + // both non null, find closest + if (Math.abs(floor.getKey() - target) <= Math.abs(ceil.getKey() - target)) { + return floor.getValue(); + } + return ceil.getValue(); + + } else { // ceil == null + if (floor != null) { + return floor.getValue(); + } + throw new IllegalStateException("ceil == null && floor == null, should not happen"); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/Maximum.java b/src/oldMain/java/umich/opensearch/kde/util/Maximum.java new file mode 100644 index 0000000..cc2c828 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/Maximum.java @@ -0,0 +1,21 @@ +package umich.opensearch.kde.util; + +/** + * @author Dmitry Avtonomov + */ +public class Maximum { + + public int idxLo; + public int idxHi; + public double xLo; + public double xHi; + public double val; + + public Maximum(int idxdxLo, int idxHi, double xLo, double xHi, double val) { + this.idxLo = idxdxLo; + this.idxHi = idxHi; + this.xLo = xLo; + this.xHi = xHi; + this.val = val; + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/Peak.java b/src/oldMain/java/umich/opensearch/kde/util/Peak.java new file mode 100644 index 0000000..80a91b2 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/Peak.java @@ -0,0 +1,39 @@ +package umich.opensearch.kde.util; + +/** + * A peak at location x of height y. Optionally with a width and the parent {@link PeakApprox}. + * + * @author Dmitry Avtonomov + */ +public class Peak implements Comparable { + + public final double x; + public final double y; + public final double width; + public final WIDTH widthType; + public final PeakApprox peak; + + + public Peak(double x, double y) { + this.x = x; + this.y = y; + this.width = Double.NaN; + this.widthType = WIDTH.NONE; + this.peak = null; + } + + public Peak(PeakApprox peak, double x, double y, double width, WIDTH type) { + this.peak = peak; + this.x = x; + this.y = y; + this.width = width; + this.widthType = type; + } + + @Override + public int compareTo(Peak o) { + return Double.compare(x, o.x); + } + + public enum WIDTH {FULL, FWHM, SD, NONE} +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/PeakApprox.java b/src/oldMain/java/umich/opensearch/kde/util/PeakApprox.java new file mode 100644 index 0000000..6c19474 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/PeakApprox.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +/** + * Approximate peak location in a 1D array of values. Stores the start and finish locations in the + * array as well as the corresponding values at the start, finish and at the top. + * + * @author Dmitry Avtonomov + */ +public class PeakApprox { + + public int idxLo = -1; + public int idxHi = -1; + public int idxTopLo = -1; + public int idxTopHi = -1; + public double valLo = Double.NEGATIVE_INFINITY; + public double valTop = Double.NEGATIVE_INFINITY; + public double valHi = Double.NEGATIVE_INFINITY; + + public int numNonZeroPts = 0; + public int idxLoNonZero = -1; + public int idxHiNonZero = -1; + + public double mzInterpolated; + + public void flipValSign() { + valLo = -1 * valLo; + valTop = -1 * valTop; + valHi = -1 * valHi; + } + + public double amplitudeLo() { + return Math.min(Math.abs(valTop - valLo), Math.abs(valTop - valHi)); + } + + public double amplitudeHi() { + return Math.max(Math.abs(valTop - valLo), Math.abs(valTop - valHi)); + } + + public double amplitudeMax() { + return Math.max(amplitudeLo(), amplitudeHi()); + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/PeakOutput.java b/src/oldMain/java/umich/opensearch/kde/util/PeakOutput.java new file mode 100644 index 0000000..a3274a8 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/PeakOutput.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +import smile.stat.distribution.Distribution; + +/** + * @author Dmitry Avtonomov + */ +public class PeakOutput implements Comparable { + + public final double mean; + public final double sd; + public final double priori; + public final Distribution distribution; + + public final double kdeTotalArea; + public final double kdeTotalEntries; + public final double gmmComponentArea; + public final double gmmCombinedArea; + public final double gmmComponentScaledHeight; + public final double gmmSupport; + + public final double der2Loc; + public final double der2Amplitude; + public final double der2Value; + + protected PeakOutput(double mean, double sd, double priori, Distribution distribution, + double kdeTotalArea, double kdeTotalEntries, + double gmmComponentArea, double gmmCombinedArea, double gmmComponentScaledHeight, + double gmmSupport, + double der2Loc, double der2Amplitude, double der2Value) { + this.mean = mean; + this.sd = sd; + this.priori = priori; + this.distribution = distribution; + this.kdeTotalArea = kdeTotalArea; + this.kdeTotalEntries = kdeTotalEntries; + this.gmmComponentArea = gmmComponentArea; + this.gmmCombinedArea = gmmCombinedArea; + this.gmmComponentScaledHeight = gmmComponentScaledHeight; + this.gmmSupport = gmmSupport; + this.der2Loc = der2Loc; + this.der2Amplitude = der2Amplitude; + this.der2Value = der2Value; + } + + @Override + public int compareTo(PeakOutput o) { + return Double.compare(this.der2Loc, o.der2Loc); + } + + public static class Builder { + + private double mean = Double.NaN; + private double sd = Double.NaN; + private double priori = Double.NaN; + private Distribution distribution = null; + + private double kdeTotalArea = Double.NaN; + private double kdeTotalEntries = Double.NaN; + + private double gmmComponentArea = Double.NaN; + private double gmmCombinedArea = Double.NaN; + private double gmmComponentScaledHeight = Double.NaN; + private double gmmSupport = Double.NaN; + + private double der2Loc = Double.NaN; + private double der2Amplitude = Double.NaN; + private double der2Value = Double.NaN; + + public Builder setMean(double mean) { + this.mean = mean; + return this; + } + + public Builder setSd(double sd) { + this.sd = sd; + return this; + } + + public Builder setPriori(double priori) { + this.priori = priori; + return this; + } + + public Builder setDistribution(Distribution distribution) { + this.distribution = distribution; + return this; + } + + public Builder setGmmSupport(double gmmSupport) { + this.gmmSupport = gmmSupport; + return this; + } + + public Builder setKdeTotalArea(double kdeTotalArea) { + this.kdeTotalArea = kdeTotalArea; + return this; + } + + public Builder setKdeTotalEntries(double kdeTotalEntries) { + this.kdeTotalEntries = kdeTotalEntries; + return this; + } + + public Builder setGmmComponentArea(double gmmCompArea) { + this.gmmComponentArea = gmmCompArea; + return this; + } + + public Builder setGmmCombinedArea(double gmmPeakHeight) { + this.gmmCombinedArea = gmmPeakHeight; + return this; + } + + public Builder setGmmComponentScaledHeight(double gmmComponentScaledHeight) { + this.gmmComponentScaledHeight = gmmComponentScaledHeight; + return this; + } + + public Builder setDer2Loc(double der2Loc) { + this.der2Loc = der2Loc; + return this; + } + + public Builder setDer2Amplitude(double der2Amplitude) { + this.der2Amplitude = der2Amplitude; + return this; + } + + public Builder setDer2Value(double der2Value) { + this.der2Value = der2Value; + return this; + } + + public PeakOutput createPeakOutput() { + return new PeakOutput(mean, sd, priori, distribution, + kdeTotalArea, kdeTotalEntries, + gmmComponentArea, gmmCombinedArea, gmmComponentScaledHeight, gmmSupport, + der2Loc, der2Amplitude, der2Value); + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/PeakProminence.java b/src/oldMain/java/umich/opensearch/kde/util/PeakProminence.java new file mode 100644 index 0000000..b7f50eb --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/PeakProminence.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2016 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +public class PeakProminence { + + private PeakProminence() { + } + + public static double pp(double[] a) { + int[] mp = maxp(a, 1); + return pp(a, mp[0]); + } + + public static double pp(double[] a, int mpos) { + double[] x = new double[a.length]; + for (int i = 0; i < a.length; ++i) { + x[i] = (double) i; + } + + double[] lp = fit(x, a, mpos); + + // compute the prominence, i.e., the distance between the regression + // line and the actual value + return Math.abs(a[mpos] - lp[0] + mpos * lp[1]); + } + + /** + * Estimate the parameters of a straight line, ignoring the indicated value + * + * @return offset, slope + */ + public static double[] fit(double[] x, double[] y, int skip) { + double s = 0.0, sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0, del; + + s = x.length; + for (int i = 0; i < x.length; i++) { + if (i == skip) { + continue; + } + sx += x[i]; + sy += y[i]; + sxx += x[i] * x[i]; + sxy += x[i] * y[i]; + } + + del = s * sxx - sx * sx; + + double[] pout = new double[2]; + pout[0] = (sxx * sy - sx * sxy) / del; + pout[1] = (s * sxy - sx * sy) / del; + + return pout; + } + + /** + * Find and return the indices of the maximum values in a descending order regarding the max + * values + */ + public static int[] maxp(double[] values, int n) { + int[] p = new int[n]; + double[] v = new double[n]; + max(values, p, v); + return p; + } + + /** + * Find and locate the maxima and write them to the pos and val array. The output is sorted + * (descending). + */ + public static void max(double[] in, int[] pos, double[] val) { + int u = 0; + int n = pos.length; + + for (int i = 0; i < in.length; ++i) { + double v = in[i]; + + // locate the insert position + int p = 0; + while (p < u && v < val[p]) { + p++; + } + + // v is smaller than all other values + if (p == n) { + continue; + } + + // shift the old values and indices + for (int j = u - 2; j >= p; --j) { + pos[j + 1] = pos[j]; + val[j + 1] = val[j]; + } + + // insert + pos[p] = i; + val[p] = v; + + // keep track how many slots are used already + if (u < n) { + u++; + } + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/PeakUtils.java b/src/oldMain/java/umich/opensearch/kde/util/PeakUtils.java new file mode 100644 index 0000000..b53e0b3 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/PeakUtils.java @@ -0,0 +1,933 @@ +package umich.opensearch.kde.util; + +import com.github.chhh.utils.LogUtils; +import hep.aida.tdouble.bin.MightyStaticDoubleBin1D; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import jsat.linear.DenseVector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import smile.math.DoubleArrayList; +import smile.math.IntArrayList; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; +import umich.opensearch.kde.OpenSearchParams; +import umich.opensearch.kde.api.IWeightedValues; +import umich.opensearch.kde.params.denoise.Denoiser; +import umich.opensearch.kde.params.denoise.Denoising; +import umich.opensearch.kde.params.denoise.NumberedParams; +import umich.opensearch.kde.params.denoise.TotalVariationDenoiser; + +/** + * @author Dmitry Avtonomov + */ +public class PeakUtils { + + private static final Logger log = LoggerFactory.getLogger(PeakUtils.class); + + private PeakUtils() { + } + + + public static List findMaxima(double[] dataX, double[] dataY) { + ArrayList maxima = new ArrayList<>(64); + double valPre = Double.POSITIVE_INFINITY; + double valCur; + int possibleMaxPlateuStartIdx = -1; + int possibleMaxPlateuEndIdx = -1; + for (int i = 0; i < dataY.length; i++) { + valCur = dataY[i]; + if (valCur > valPre) { // climbing up + possibleMaxPlateuStartIdx = i; + possibleMaxPlateuEndIdx = i; + } else if (valCur == valPre) { // on a high plateau + possibleMaxPlateuEndIdx = i; + } else if (valCur < valPre) { // found a peak/high plateau + if (possibleMaxPlateuStartIdx > 0 && possibleMaxPlateuEndIdx > 0) { + maxima.add(new Maximum( + possibleMaxPlateuStartIdx, possibleMaxPlateuEndIdx, + dataX[possibleMaxPlateuStartIdx], dataX[possibleMaxPlateuEndIdx], + dataY[possibleMaxPlateuStartIdx])); + } + possibleMaxPlateuEndIdx = -1; + possibleMaxPlateuStartIdx = -1; + } + valPre = valCur; + } + return maxima; + } + + public static NoiseResult estimateNoiseDistribution(OpenSearchParams params, + final IndexBracket brX, + final double[] xAxis, final double[] yAxis, DenseVector vecLocal) { + IndexBracket brVec = BracketUtils.findBracket(brX.loVal, brX.hiVal, vecLocal); + + // non-zero areas of psm density function (think non-zero areas of KDE plot) + List locations = BracketUtils.findNonZeroBrackets(xAxis, yAxis, brX.lo, brX.hi); + + int entriesUnderKde = locations.stream() + .map(kdeBracket -> BracketUtils.findBracket(kdeBracket.loVal, kdeBracket.hiVal, vecLocal)) + .mapToInt(vecLocalBracket -> vecLocalBracket == null ? 0 : vecLocalBracket.size) + .sum(); + + // TODO: ACHTUNG: Continue optimization path here + final double[] xVals = BracketUtils.createArraySliceCopy(brX, xAxis); + final double[] yVals = BracketUtils.createArraySliceCopy(brX, yAxis); + + IWeightedValues data = new IWeightedValues() { + @Override + public int size() { + return brX.size; + } + + @Override + public double val(int i) { + return xAxis[brX.lo + i]; + } + + @Override + public double weight(int i) { + return yAxis[brX.lo + i]; + } + }; + + final double yValsOrigSum = Arrays.stream(yVals).sum(); + for (int i = 0; i < yVals.length; i++) { + yVals[i] /= yValsOrigSum; + } + final double xStep = xVals[1] - xVals[0]; + + InitNoiseMixEstimate init = new InitNoiseMixEstimate(brX, xVals, yVals).invoke(); + + List mix = new ArrayList<>(); + mix.add(new CompNorm(init.d1w, init.d1mu, init.d1sd)); + mix.add(new CompNorm(init.d2w, init.d2mu, init.d2sd)); + + double[][] expect = createExpectArray(mix, data); + for (int i = 0; i < 5; i++) { + int a = 1; + E(mix, data, expect); +// normalizeExpectArray(expect); + M(mix, data, expect); + normalizeMixWeights(mix); + int b = 1; + } + + return null; + } + + private static double[][] createExpectArray(List mix, IWeightedValues data) { + return new double[mix.size()][data.size()]; + } + + private static void normalizeExpectArray(double[][] expect) { + int size = expect[0].length; + for (int i = 0; i < size; i++) { + double sum = 0; + for (double[] e : expect) { + sum += e[i]; + } + for (double[] e : expect) { + e[i] /= sum; + } + } + } + + private static void normalizeMixWeights(List mix) { + double sum = 0; + for (MixCompUni comp : mix) { + sum += comp.w(); + } + for (MixCompUni comp : mix) { + comp.updateWeight(comp.w() / sum); + } + } + + private static void E(List mix, IWeightedValues data, final double[][] expect) { + if (expect.length < mix.size()) { + throw new IllegalArgumentException( + "first dim of expect array was smaller than the number of mixture components"); + } + for (int i = 0; i < expect.length; i++) { + if (expect[i].length < data.size()) { + throw new IllegalArgumentException( + String.format("expect array dimension [%d] was smaller than data size", i)); + } + } + for (int iMix = 0; iMix < mix.size(); iMix++) { + MixCompUni comp = mix.get(iMix); + for (int iData = 0; iData < data.size(); iData++) { + double v = comp.w() * comp.p(data.val(iData)); + if (!Double.isFinite(v) || Double.isNaN(v)) { + int a = 1; + } + expect[iMix][iData] = v; + } + } + } + + private static void M(List mix, IWeightedValues data, final double[][] expect) { + if (expect.length < mix.size()) { + throw new IllegalArgumentException( + "first dim of expect array was smaller than the number of mixture components"); + } + for (int i = 0; i < expect.length; i++) { + if (expect[i].length < data.size()) { + throw new IllegalArgumentException( + String.format("expect array dimension [%d] was smaller than data size", i)); + } + } + for (int i = 0; i < mix.size(); i++) { + mix.get(i).M(data, expect[i]); + } + } + + public static BracketResult findPeaksGMM4(OpenSearchParams params, IndexBracket brX, + double[] xAxis, double[] yAxis, double[] yDer2, DenseVector vecLocal) { + List peaks = new ArrayList<>(); + BracketResult result = new BracketResult(); + result.peaks = peaks; + result.bracket = brX; + + if (vecLocal.length() < params.getGmmMinSupport()) { + log.info(String.format("Fewer than %d data points, not searching for peaks.", + params.getGmmMinSupport())); + return result; + } + + // find the number of entries in the non-zero portion of yAxis + List nonZeroBrackets = BracketUtils + .findNonZeroBrackets(xAxis, yAxis, brX.lo, brX.hi); + int cntNonZeroDensityEntries = 0; + for (IndexBracket nonZeroBracket : nonZeroBrackets) { + IndexBracket bracket = BracketUtils + .findBracket(nonZeroBracket.loVal, nonZeroBracket.hiVal, vecLocal); + cntNonZeroDensityEntries += bracket.size; + } + result.numEntries = cntNonZeroDensityEntries; + if (cntNonZeroDensityEntries < params.getGmmMinSupport()) { + DecimalFormat df2 = new DecimalFormat("0.00"); + log.info( + "Fewer than {} data points in non-zero density regions of [{}-{}], not searching for peaks.", + params.getGmmMinSupport(), df2.format(brX.loVal), df2.format(brX.hiVal)); + return result; + } + + // parameters for detecting local minima in second derivative + PeakDetectionOptions optsPeaksDer = new PeakDetectionOptions(); + optsPeaksDer.peakAcceptor = p -> p.numNonZeroPts > 2; + optsPeaksDer.zeroTolerance = 1e-8; + + // copy the 2nd derivative and invert the sign, so that we could use maxima finding routine + double[] yDer2InvSign = BracketUtils.createArraySliceCopy(brX, yDer2); + for (int i = 0; i < yDer2InvSign.length; i++) { + yDer2InvSign[i] *= -1; + } + + // local minima in the 2nd derivative + List peaksDer = detectPeakLocations(yDer2InvSign, optsPeaksDer); + // remove flanking plateaus and invert the sign back + peaksDer.removeIf(p -> p.valTop == 0.0); + + // fit them with parabolas + List peaksDerFitted = new ArrayList<>(peaksDer.size()); + for (PeakApprox p : peaksDer) { + Peak parabolicPeak = createParabolicPeak(brX.lo, true, xAxis, yDer2, p); + peaksDerFitted.add(parabolicPeak); + } + result.peaks.clear(); + result.peaks.addAll(peaksDerFitted); + + int gmmNumEntries = params.getGmmNumEntries(); + int gmmMinEntries = params.getGmmMinEntries(); + final WeightedFakePoints fakePoints = createFakeGmmPointsWeighted(brX, xAxis, yAxis, + gmmNumEntries, gmmMinEntries); + if (fakePoints.weightSum < 20) { + return result; + } + //WeightProvider w = i -> fakePoints.weights[i]; + WeightProvider w = i -> fakePoints.weights[i]; + + final double gamma = 0.0; + final int maxIters = 5; + + // if we have some assumptions about peak locations, try fitting GMM + if (!result.peaks.isEmpty()) { + ArrayList initMix = new ArrayList<>(); + for (Peak p : result.peaks) { + double mu = p.x; + MixtureComponentPeak c = new MixtureComponentPeak(p); + double sd; + switch (p.widthType) { + case SD: + sd = p.width; + break; + case FWHM: + sd = p.width / 2.3548; + break; + case FULL: + sd = p.width / 4; + break; + default: + throw new UnsupportedOperationException("Standard deviation calc for peak with type " + + p.widthType.toString() + " is not supported."); + } + + c.priori = p.y; + c.distribution = new NormalDist(mu, sd); + initMix.add(c); + } + + // make sure that priors add up to 1 + double sum = 0; + for (Mixture.Component c : initMix) { + sum += c.priori; + } + for (Mixture.Component c : initMix) { + c.priori /= sum; + } + + // TODO: maybe MixtureComponentPeak should contain all the peak info, not only the initial MU + + GaussMixtureWeighted gmw = new GaussMixtureWeighted(initMix, true, fakePoints.values, w, + gamma, maxIters); + + // TODO: continue here, now the initial component's MUs are stored in the reesulting components after GMM fitting + + GaussianMixture gm = new GaussianMixture(gmw.getComponents()); + + // sanity check GMM results + GaussianMixture gmChecked = sanityCheckGmm(params, brX, xAxis, vecLocal, gm); + result.gmm = gmChecked; + } + + return result; + } + + private static Peak createParabolicPeak(double[] xAxis, double[] yAxis, PeakApprox peakApprox) { + return createParabolicPeak(0, false, xAxis, yAxis, peakApprox); + } + + private static Peak createParabolicPeak(int idxShift, boolean peakYrangeAsHeight, double[] xAxis, + double[] yAxis, PeakApprox peakApprox) { + + double vertexX; + if (peakApprox.idxTopHi == peakApprox.idxTopLo) { + int idx = peakApprox.idxTopLo; + if (idx > 0 && idx < xAxis.length - 1) { + double[] x = new double[3]; + double[] y = new double[3]; + for (int i = -1; i <= 1; i++) { + x[i + 1] = xAxis[idxShift + idx + i]; + y[i + 1] = yAxis[idxShift + idx + i]; + } + double[] parabola = fitParabola(x, y); + vertexX = parabolaVertexX(parabola[2], parabola[1]); + } else { + vertexX = xAxis[idxShift + peakApprox.idxTopLo]; + } + + } else { + int idx = peakApprox.idxTopLo; + if (idx > 0) { + double[] x = new double[3]; + double[] y = new double[3]; + x[0] = xAxis[idxShift + idx - 1]; + y[0] = yAxis[idxShift + idx - 1]; + x[1] = xAxis[idxShift + idx]; + y[1] = yAxis[idxShift + idx]; + x[2] = xAxis[idxShift + peakApprox.idxTopHi]; + y[2] = yAxis[idxShift + peakApprox.idxTopHi]; + + double[] parabola = fitParabola(x, y); + vertexX = parabolaVertexX(parabola[2], parabola[1]); + } else { + vertexX = xAxis[idxShift + peakApprox.idxTopLo]; + } + + } + + double height; + if (!peakYrangeAsHeight) { + height = peakApprox.valTop; + } else { + height = peakApprox.valTop - Math.max(peakApprox.valLo, peakApprox.valHi); + } + + double width = xAxis[idxShift + peakApprox.idxHi] - xAxis[idxShift + peakApprox.idxLo]; + + Peak p = new Peak(peakApprox, vertexX, height, width, Peak.WIDTH.FULL); + return p; + } + + /** + * Checks that GMM does not have too narrow components or components with too weak evidence + * support. + * + * @param gm Will be modified in-place. + */ + private static GaussianMixture sanityCheckGmm(OpenSearchParams params, IndexBracket brX, + double[] xAxis, DenseVector vecLocal, GaussianMixture gm) { + List toRemove = new ArrayList<>(); + int entriesInBracket = vecLocal.length(); + double step = xAxis[1] - xAxis[0]; + for (Mixture.Component c1 : gm.getComponents()) { + Mixture.Component componentToRemove = null; + if (c1.distribution.sd() * 6 < step) { + componentToRemove = c1; + //LogHelper.logMsg(String.format( + // "[%.2f-%.2f] Encountered GMM component with very small SD. Mu=%.2f, Sigma=%.2f, removing.", + // brX.loVal, brX.hiVal, c1.distribution.mean(), c1.distribution.sd()), out, logger, true); + } + int minCnt = params.getGmmMinSupport(); + if (c1.priori * entriesInBracket < minCnt) { + componentToRemove = c1; + //LogHelper.logMsg(String.format( + // "[%.2f-%.2f] Encountered GMM component that is supported by less than %d entries. Mu=%.2f, Sigma=%.2f, removing.", + // brX.loVal, brX.hiVal, minCnt, c1.distribution.mean(), c1.distribution.sd()), out, logger, true); + } else { + // a more stringent check, validate that in 4*sd there were at least N data points + double loTarget = c1.distribution.mean() - c1.distribution.sd() * 2; + double hiTarget = c1.distribution.mean() + c1.distribution.sd() * 2; + IndexBracket b = BracketUtils.findBracket(loTarget, hiTarget, vecLocal); + if (b.size < minCnt) { + componentToRemove = c1; + //LogHelper.logMsg(String.format( + // "[%.2f-%.2f] Encountered GMM component that is supported by less than %d entries. Mu=%.2f, Sigma=%.2f, removing.", + // brX.loVal, brX.hiVal, minCnt, c1.distribution.mean(), c1.distribution.sd()), out, logger, true); + } + } + if (componentToRemove != null && !toRemove.contains(componentToRemove)) { + toRemove.add(componentToRemove); + } + } + for (Mixture.Component c : toRemove) { + gm.getComponents().remove(c); + } + + if (gm.getComponents().isEmpty()) { + return null; + } + + // renormalize + double sum = 0; + for (Mixture.Component c : gm.getComponents()) { + sum += c.priori; + } + for (Mixture.Component c : gm.getComponents()) { + c.priori = c.priori / sum; + } + + return gm; + } + + /** + * 3-point fit of a parabola. + * + * @param x the x coordinates. + * @param y the y coordinates. + * @return double[]: [0] - c, [1] - b, [2] - a + */ + public static double[] fitParabola(double[] x, double[] y) { + if (x.length != 3) { + throw new IllegalArgumentException("Length of input data arrays must be 3."); + } + if (x.length != y.length) { + throw new IllegalArgumentException("Arrays x and y must be of the same length: 3."); + } + double a = (y[1] * (x[2] - x[0]) - y[0] * (x[2] - x[1]) - y[2] * (x[1] - x[0])) / ( + Math.pow(x[0], 2) * (x[1] - x[2]) - Math.pow(x[2], 2) * (x[1] - x[0]) + - Math.pow(x[1], 2) * (x[0] - x[2])); + double b = (y[1] - y[0] + a * (Math.pow(x[0], 2) - Math.pow(x[1], 2))) / (x[1] - x[0]); + double c = -1 * a * Math.pow(x[0], 2) - b * x[0] + y[0]; + double[] parabola = new double[3]; + parabola[0] = c; + parabola[1] = b; + parabola[2] = a; + return parabola; + } + + public static double parabolaVertexX(double a, double b) { + return -1 * b / (2.0 * a); + } + + private static WeightedFakePoints createFakeGmmPointsWeighted(IndexBracket brX, double[] xAxis, + double[] yAxis, int totalPoints, int minCnt) { + final int minPts = 20; + if (totalPoints < minPts) { + throw new IllegalArgumentException( + String.format("The minimum is %d points to be spread out across", minPts)); + } + double step = xAxis[1] - xAxis[0]; + double area = 0; + for (int i = brX.lo; i < brX.hi; i++) { + area += yAxis[i] * step; + } + double density = totalPoints / area; + + double accumArea = 0; + double sumX = 0; + double sumY = 0; + DoubleArrayList points = new DoubleArrayList(totalPoints); + IntArrayList weights = new IntArrayList(totalPoints); + + int weightSum = 0; + for (int i = brX.lo; i < brX.hi; i++) { + accumArea += yAxis[i] * step; + sumX += xAxis[i] * yAxis[i]; + sumY += yAxis[i]; + double accumCount = accumArea * density; + if (accumCount > 1) { + double val = sumX / sumY; + int cnt = (int) Math.floor(accumCount); + if (cnt >= 1) { + points.add(val); + weights.add(cnt); + weightSum += cnt; + } + accumArea = 0; + sumX = 0; + sumY = 0; + } + } + return new WeightedFakePoints(points.toArray(), weights.toArray(), weightSum); + } + + public static List findPeaks(Appendable out, OpenSearchParams params, + double[] xAxis, double[] yAxis) { + double[] denoised; + if (params.getDenoising() == Denoising.NONE) { + denoised = Arrays.copyOf(yAxis, yAxis.length); + } else { + denoised = new double[yAxis.length]; + Denoiser denoiser = params.getDenoising().getInstance(); + NumberedParams denoiserProperties = denoiser.getDefaultConfig(); + if (!params.getDenoisingParams().isEmpty()) { + denoiserProperties = denoiser.configure(params.getDenoisingParams()); + } + denoiserProperties.put("massAxis", xAxis); + denoiserProperties.put(TotalVariationDenoiser.Config.PROP_DO_PLOT, + String.valueOf(params.isDenoisingPlot())); + denoiser.denoise(yAxis, denoised, denoiserProperties); + } + List maxima = findMaxima(xAxis, denoised); + + List peaks = new ArrayList<>(maxima.size()); + if (maxima.size() > 0) { + + MightyStaticDoubleBin1D binWi = new MightyStaticDoubleBin1D(true, true, 4); + MightyStaticDoubleBin1D binHi = new MightyStaticDoubleBin1D(true, true, 4); + + for (Maximum maximum : maxima) { + double width = Math.abs(maximum.xHi - maximum.xLo); + binWi.add(width); + binHi.add(maximum.val); + } + + double stddevWi = 0; + double stddevHi = 0; + double meanWi = binWi.mean(); + double meanHi = binHi.mean(); + + for (Maximum maximum : maxima) { + stddevWi += Math.pow(Math.abs((maximum.xHi - maximum.xLo)) - meanWi, 2d); + stddevHi += Math.pow(maximum.val - meanHi, 2d); + } + stddevWi = Math.sqrt(stddevWi); + stddevHi = Math.sqrt(stddevHi); + + DecimalFormat fmt = new DecimalFormat("0.###E0"); + + if (out != null) { + String msg = String.format("Number of detected peaks: %d", maxima.size()); + LogUtils.println(out, msg); + msg = String.format( + "Peak width stats: min=%s, max=%.4f, avg=%.4f, stddev=%.4f, harmonic=%s, geometric=%s", + fmt.format(binWi.min()), binWi.max(), binWi.mean(), stddevWi, + fmt.format(binWi.harmonicMean()), fmt.format(binWi.geometricMean())); + LogUtils.println(out, msg); + msg = String.format( + "Peak height stats: min=%s, max=%.4f, avg=%.4f, stddev=%.4f, harmonic=%s, geometric=%s", + fmt.format(binHi.min()), binHi.max(), binHi.mean(), stddevHi, + fmt.format(binHi.harmonicMean()), fmt.format(binHi.geometricMean())); + LogUtils.println(out, msg); + LogUtils.println(out, ""); + } + + for (Maximum maximum : maxima) { + double minAcceptedPeakWidth = binWi.mean() + stddevWi; + double peakWidth = (maximum.xHi - maximum.xLo); + if (peakWidth <= minAcceptedPeakWidth) { + double avgPeakX = (maximum.xLo + maximum.xHi) / 2d; + double peakHeight; + int idxSum = maximum.idxLo + maximum.idxHi; + if (maximum.idxLo == maximum.idxHi) { + peakHeight = yAxis[maximum.idxLo]; + } else if (idxSum % 2 != 0) { + // the sum of indexes is odd + // we will take the average of the 2 central cells + int idxLo = (maximum.idxLo + maximum.idxHi) / 2; + peakHeight = (yAxis[idxLo] + yAxis[idxLo + 1]) / 2d; + } else { + // the sum of indexes is even, there is one single central element + int idxCenter = (maximum.idxLo + maximum.idxHi) / 2; + peakHeight = yAxis[idxCenter]; + } + peaks.add(new Peak(avgPeakX, peakHeight)); + } else { + //System.err.println("PEAK HAS BEEN REJECTED!"); + } + } + } + return peaks; + } + + private static List detectPeakLocations(double[] data, PeakDetectionOptions opts) { + return detectPeakLocations(data, 0, data.length, opts); + } + + /** + * Finds locations of maxima. Can detect plateaus. + * + * @param data Array with data values to detect peaks in. + * @param from Inclusive. + * @param to Exclusive. + * @param opts Basic peak detection options. + */ + private static List detectPeakLocations(double[] data, int from, int to, + PeakDetectionOptions opts) { + if (from < 0 || from > data.length - 1) { + throw new IllegalArgumentException("'from' must be within data[] index range"); + } + if (to < from) { + throw new IllegalArgumentException("'to' must be >= 'from'"); + } + if (data.length < 3) { + return Collections.emptyList(); + } + + List peaks = new ArrayList<>(Math.min(data.length / 10, 64)); + double diff; + PeakApprox p = null; + State s0 = State.FLAT, s1; + + int lastFlatStart = -1; + for (int i = from; i < to - 1; + i++) // -1 because to is exclusive and we're comparing to i+1 data point at each step + { + int ip1 = i + 1; + diff = data[ip1] - data[i]; + if (diff > 0) { + s1 = State.UP; + } else if (diff < 0) { + s1 = State.DOWN; + } else { + s1 = State.FLAT; + } + if (s1 == State.FLAT && s0 != State.FLAT) { + lastFlatStart = i; + } + + switch (s1) { + case UP: + if (p == null) { + p = peakStart(i); + } else if (p.idxHi != p.idxTopHi) { + peakFinish(data, opts, peaks, p, lastFlatStart); + p = peakStart(i); + } + p.idxTopLo = ip1; + p.idxTopHi = ip1; + p.idxHi = ip1; + break; + + case FLAT: + if (p == null) { + break; + } + if (p.idxTopHi == p.idxHi) { + p.idxTopHi = ip1; + } + p.idxHi = ip1; + break; + + case DOWN: + if (p == null) { + break; + } + p.idxHi = ip1; + break; + } + + s0 = s1; + } + if (p != null) { + peakFinish(data, opts, peaks, p, lastFlatStart); + } + + return peaks; + } + + /** + * Starts a new peak and initializes its {@code idxLo} and {@code valLo} fields. + * + * @param i Current pointer in the data array. + * @return A new peak object. + */ + private static PeakApprox peakStart(int i) { + PeakApprox p = new PeakApprox(); + p.idxLo = i; + + return p; + } + + /** + * Add the peak to a list if it passes certain criteria. + * + * @param data The data array. + * @param opts Options for peak detection. + * @param toAddTo The list to add the peak to if it passes quality checks. + * @param p The peak to be finalized. + * @param lastFlatStart The index where the last flat area started. If it's between {@code + * idxTopHi} and {@code idxHi}, it will be used instead of {@code idxHi} + * @return True, if the peak passed criteria in {@code logic} and was added to {@code toAddTo}. + */ + private static boolean peakFinish(double[] data, PeakDetectionOptions opts, + List toAddTo, PeakApprox p, int lastFlatStart) { + boolean wasAdded = false; + if (lastFlatStart > p.idxTopHi && lastFlatStart < p.idxHi) { + p.idxHi = lastFlatStart; + } + p.valLo = data[p.idxLo]; + + p.valTop = data[p.idxTopLo]; + p.valHi = data[p.idxHi]; + p.numNonZeroPts = p.idxHi - p.idxLo + 1; + if (data[p.idxLo] == 0) { + p.numNonZeroPts--; + } + if (data[p.idxHi] == 0) { + p.numNonZeroPts--; + } + if (opts.peakAcceptor.test(p)) { + toAddTo.add(p); + wasAdded = true; + } + return wasAdded; + } + + enum State {UP, DOWN, FLAT} + + private interface MixCompUni { + + double w(); + + double p(double x); + + void updateWeight(double w); + + void M(IWeightedValues data, double[] posterior); + } + + public static class NoiseResult { + + } + + private static class CompNorm implements MixCompUni { + + public GaussianDistribution d; + public double w; + + public CompNorm(double w, double mu, double sd) { + this.w = w; + this.d = new GaussianDistribution(mu, sd); + } + + @Override + public String toString() { + return String.format("Normal distribution (mu=%.4f, sd=%.4f), w=%.4f", d.mean(), d.sd(), w); + } + + @Override + public double w() { + return w; + } + + @Override + public double p(double x) { + return d.p(x); + } + + @Override + public void updateWeight(double w) { + this.w = w; + } + + @Override + public void M(IWeightedValues data, double[] posterior) { + if (posterior.length < data.size()) { + throw new IllegalArgumentException("posterior array was shorter than data length"); + } + + double posteriorSum = 0; + double wSum = 0; + double muSum = 0; + for (int i = 0; i < data.size(); i++) { + wSum += data.weight(i); + posteriorSum += posterior[i]; + muSum += data.val(i) * data.weight(i) * posterior[i]; + } + final double mu = muSum / wSum; + + // variance for weighted samples, follow: + // https://stats.stackexchange.com/questions/47325/bias-correction-in-weighted-variance + double varianceSum = 0; + double w2Sum = 0; + for (int i = 0; i < data.size(); i++) { + double d = data.val(i) - mu; + varianceSum += d * d * posterior[i]; + w2Sum += data.weight(i) * data.weight(i); + } + double varianceNonWeighted = varianceSum / data.size(); + final double variance = varianceNonWeighted * (wSum * wSum) / (wSum * wSum - w2Sum * w2Sum); + final double sd = Math.sqrt(variance); + + w = posteriorSum; + d = new GaussianDistribution(mu, sd); + } + } + + private static class CompUniform implements MixCompUni { + + public double w; + public double p; + + @Override + public double w() { + return w; + } + + @Override + public double p(double x) { + return p; + } + + @Override + public void updateWeight(double w) { + this.w = w; + } + + @Override + public void M(IWeightedValues data, double[] posterior) { + throw new NotImplementedException(); + } + } + + public static class BracketResult { + + public IndexBracket bracket; + public List peaks = new ArrayList<>(); + public List peaksOutput = new ArrayList<>(); + public GaussianMixture gmm; + public int numEntries; + } + + private static class WeightedFakePoints { + + final double[] values; + final int[] weights; + final int weightSum; + + private WeightedFakePoints(double[] values, int[] weights, int weightSum) { + this.values = values; + this.weights = weights; + this.weightSum = weightSum; + } + } + + private static class PeakDetectionOptions { + + Predicate peakAcceptor = peakApprox -> true; + double zeroTolerance = 1e-8; + } + + private static class InitNoiseMixEstimate { + + private IndexBracket brX; + private double[] xVals; + private double[] yVals; + private double d1mu; + private double d2mu; + private double d1sd; + private double d2sd; + private double d1w; + private double d2w; + + public InitNoiseMixEstimate(IndexBracket brX, double[] xVals, double... yVals) { + this.brX = brX; + this.xVals = xVals; + this.yVals = yVals; + } + + public double getD1mu() { + return d1mu; + } + + public double getD2mu() { + return d2mu; + } + + public double getD1sd() { + return d1sd; + } + + public double getD2sd() { + return d2sd; + } + + public double getD1w() { + return d1w; + } + + public double getD2w() { + return d2w; + } + + public InitNoiseMixEstimate invoke() { + // mu of the 'narrow' main distribution + PeakDetectionOptions optsPeaksDer = new PeakDetectionOptions(); + optsPeaksDer.peakAcceptor = p -> p.numNonZeroPts >= 3; + optsPeaksDer.zeroTolerance = 1e-8; + List yPeaks = detectPeakLocations(yVals, optsPeaksDer); + yPeaks.removeIf(p -> p.valTop == 0.0); + yPeaks.sort((p1, p2) -> Double.compare(p2.valTop, p1.valTop)); + List yPeaksFitted = new ArrayList<>(yPeaks.size()); + for (PeakApprox p : yPeaks) { + Peak parabolicPeak = createParabolicPeak(brX.lo, true, xVals, yVals, p); + yPeaksFitted.add(parabolicPeak); + } + if (yPeaksFitted.isEmpty()) { + log.warn("No peaks detected while selecting seed point for noise estimation."); + } + yPeaksFitted.sort((p1, p2) -> Double.compare(p2.y, p1.y)); + + // mu of the 'wide' noise distrubution + double ySum = 0; + double wSum = 0; + for (int i = 0; i < xVals.length; i++) { + ySum += yVals[i]; + wSum += yVals[i] * xVals[i]; + } + + d1mu = yPeaksFitted.get(0).x; + d2mu = wSum / ySum; + d1sd = 0.02 / 3; + d2sd = d1sd * 2; + d1w = 2d / 3d; + d2w = 1d / 3d; + return this; + } + } +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/Predicate.java b/src/oldMain/java/umich/opensearch/kde/util/Predicate.java new file mode 100644 index 0000000..d54cfea --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/Predicate.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2017 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde.util; + +/** + * @author Dmitry Avtonomov + */ +public interface Predicate { + + boolean test(T t); +} diff --git a/src/oldMain/java/umich/opensearch/kde/util/SimpleConcurrentList.java b/src/oldMain/java/umich/opensearch/kde/util/SimpleConcurrentList.java new file mode 100644 index 0000000..7b719d1 --- /dev/null +++ b/src/oldMain/java/umich/opensearch/kde/util/SimpleConcurrentList.java @@ -0,0 +1,113 @@ +package umich.opensearch.kde.util; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * A kludge with all modification methods being synchronized on the list itself. + * + * @author Dmitry Avtonomov + */ +public class SimpleConcurrentList extends ArrayList { + + public SimpleConcurrentList(int initialCapacity) { + super(initialCapacity); + } + + public SimpleConcurrentList() { + } + + public SimpleConcurrentList(Collection c) { + super(c); + } + + @Override + public synchronized boolean isEmpty() { + return super.isEmpty(); + } + + @Override + public synchronized int size() { + return super.size(); + } + + @Override + public synchronized void ensureCapacity(int minCapacity) { + super.ensureCapacity(minCapacity); + } + + @Override + public synchronized E get(int index) { + return super.get(index); + } + + @Override + public synchronized E set(int index, E element) { + return super.set(index, element); + } + + @Override + public synchronized boolean add(E e) { + return super.add(e); + } + + @Override + public synchronized void add(int index, E element) { + super.add(index, element); + } + + @Override + public synchronized E remove(int index) { + return super.remove(index); + } + + @Override + public synchronized boolean remove(Object o) { + return super.remove(o); + } + + @Override + public synchronized void clear() { + super.clear(); + } + + @Override + public synchronized boolean addAll(Collection c) { + return super.addAll(c); + } + + @Override + public synchronized boolean addAll(int index, Collection c) { + return super.addAll(index, c); + } + + @Override + protected void removeRange(int fromIndex, int toIndex) { + super.removeRange(fromIndex, toIndex); + } + + @Override + public synchronized boolean removeAll(Collection c) { + return super.removeAll(c); + } + + @Override + public synchronized boolean retainAll(Collection c) { + return super.retainAll(c); + } + + @Override + public synchronized boolean contains(Object o) { + return super.contains(o); + } + + @Override + public synchronized int indexOf(Object o) { + return super.indexOf(o); + } + + @Override + public synchronized int lastIndexOf(Object o) { + return super.lastIndexOf(o); + } +} diff --git a/src/oldMain/resources/logback.xml b/src/oldMain/resources/logback.xml new file mode 100644 index 0000000..8a67d21 --- /dev/null +++ b/src/oldMain/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/oldMain/resources/simplelogger.properties b/src/oldMain/resources/simplelogger.properties new file mode 100644 index 0000000..c644c81 --- /dev/null +++ b/src/oldMain/resources/simplelogger.properties @@ -0,0 +1,44 @@ +# +# Copyright (c) 2017 Dmitry Avtonomov +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=warn +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= +org.slf4j.simpleLogger.log.umich.opensearch.kde.KDEMain=warn +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss +#org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss:SSS +# Set to true if you want to output the current thread name. +# Defaults to true. +org.slf4j.simpleLogger.showThreadName=true +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=true +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true diff --git a/src/oldTest/java/umich/opensearch/kde/VersionTest.java b/src/oldTest/java/umich/opensearch/kde/VersionTest.java new file mode 100644 index 0000000..0474be7 --- /dev/null +++ b/src/oldTest/java/umich/opensearch/kde/VersionTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Dmitry Avtonomov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package umich.opensearch.kde; + +import static org.junit.Assert.*; + +import java.io.FileReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.apache.maven.model.Model; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.junit.Assert; +import org.junit.Test; + +public class VersionTest { + + @Test + public void VerifyVersionStringUpdatedBeforeRelease() throws Exception { + + System.out.println("Verifying version information"); + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model pom = null; + if (Files.exists(Paths.get("pom.xml"))) { + pom = reader.read(new FileReader("pom.xml")); + } + if (pom == null) { + System.err.println("Could not load pom.xml as Model"); + return; + } + System.out.printf("%nInfo in pom.xml:%n"); + System.out.printf("\tGroup Id: %s%n", pom.getGroupId()); + System.out.printf("\tArtifact Id: %s%n", pom.getArtifactId()); + System.out.printf("\tVersion: %s%n", pom.getVersion()); + System.out.printf("%nInfo in %s:%n", Version.class.getName()); + System.out.printf("\tVersion: %s%n", Version.getVersion()); + + Assert.assertEquals(String.format("Version info in pom.xml does not match version info " + + "returned by %s.getVersion()", Version.class.getCanonicalName()), pom.getVersion(), + Version.getVersion()); + } +} diff --git a/src/test/java/com/dmtavt/deltamass/ptm/PtmFactoryTest.java b/src/test/java/com/dmtavt/deltamass/ptm/PtmFactoryTest.java new file mode 100644 index 0000000..b3064da --- /dev/null +++ b/src/test/java/com/dmtavt/deltamass/ptm/PtmFactoryTest.java @@ -0,0 +1,24 @@ +package com.dmtavt.deltamass.ptm; + +import java.util.EnumSet; +import org.junit.Assert; +import org.junit.Test; +import umich.ptm.PtmFactory; +import umich.ptm.PtmFactory.SOURCE; +import umich.ptm.exceptions.ModParsingException; +import umich.ptm.mod.Mods; + +public class PtmFactoryTest { + + @Test + public void ModFactoryTest() throws Exception { + try { + Mods modsGen = PtmFactory.getMods(EnumSet.of(SOURCE.GEN)); + Assert.assertTrue(modsGen.size() > 0); + Mods modsUni = PtmFactory.getMods(EnumSet.of(SOURCE.UNIMOD)); + Assert.assertTrue(modsUni.size() > 0); + } catch (ModParsingException e) { + throw new Exception(e); + } + } +} diff --git a/src/test/java/com/dmtavt/deltamass/utils/ArrayUtilsTest.java b/src/test/java/com/dmtavt/deltamass/utils/ArrayUtilsTest.java new file mode 100644 index 0000000..8b8bf24 --- /dev/null +++ b/src/test/java/com/dmtavt/deltamass/utils/ArrayUtilsTest.java @@ -0,0 +1,180 @@ +package com.dmtavt.deltamass.utils; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; + +public class ArrayUtilsTest { + private List in; + double[] a0, a1; + + @Before + public void setup() { + setup(5); + } + + private void setup(int arraySize) { + in = new ArrayList<>(); + a0 = new double[arraySize]; + a1 = new double[arraySize]; + in.add(a0); + in.add(a1); + } + + @Test + public void differentArraySizes() { + in = new ArrayList<>(); + in.add(new double[1]); + in.add(new double[2]); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ArrayUtils.removeZeros(in, true)); + } + + @Test + public void onlyZerosIn() { + setup(100); + List doubles = ArrayUtils.removeZeros(in, true); + for (double[] arr : doubles) { + assertThat(arr.length).isEqualTo(0); + } + } + + @Test + public void singleNonZeroMidNoFlanking() { + a0[2] = 1; + a1[2] = 77; + List out = ArrayUtils.removeZeros(in, false); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(1); + } + assertThat(out.get(0)[0]).isEqualTo(1); + assertThat(out.get(1)[0]).isEqualTo(77); + } + + @Test + public void singleNonZeroLeftNoFlanking() { + a0[0] = 1; + a1[0] = 77; + List out = ArrayUtils.removeZeros(in, false); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(1); + } + assertThat(out.get(0)[0]).isEqualTo(1); + assertThat(out.get(1)[0]).isEqualTo(77); + } + + @Test + public void singleNonZeroRightNoFlanking() { + a0[4] = 1; + a1[4] = 77; + List out = ArrayUtils.removeZeros(in, false); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(1); + } + assertThat(out.get(0)[0]).isEqualTo(1); + assertThat(out.get(1)[0]).isEqualTo(77); + } + + @Test + public void singleNonZeroMidFlanking() { + a0[2] = 1; + a1[2] = 77; + List out = ArrayUtils.removeZeros(in, true); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(3); + } + assertThat(out.get(0)[0]).isEqualTo(0); + assertThat(out.get(0)[1]).isEqualTo(1); + assertThat(out.get(0)[2]).isEqualTo(0); + assertThat(out.get(1)[0]).isEqualTo(0); + assertThat(out.get(1)[1]).isEqualTo(77); + assertThat(out.get(1)[2]).isEqualTo(0); + } + + @Test + public void singleNonZeroLeftFlanking() { + a0[0] = 1; + a1[0] = 77; + List out = ArrayUtils.removeZeros(in, true); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(2); + } + assertThat(out.get(0)[0]).isEqualTo(1); + assertThat(out.get(0)[1]).isEqualTo(0); + assertThat(out.get(1)[0]).isEqualTo(77); + assertThat(out.get(1)[1]).isEqualTo(0); + } + + @Test + public void singleNonZeroRightFlanking() { + a0[4] = 1; + a1[4] = 77; + List out = ArrayUtils.removeZeros(in, true); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(2); + } + assertThat(out.get(0)[0]).isEqualTo(0); + assertThat(out.get(0)[1]).isEqualTo(1); + assertThat(out.get(1)[0]).isEqualTo(0); + assertThat(out.get(1)[1]).isEqualTo(77); + } + + @Test + public void twoNonZeroMidFlanking() { + a0[2] = 1; + a0[3] = 2; + a1[2] = 77; + a1[3] = 78; + List out = ArrayUtils.removeZeros(in, true); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(4); + } + assertThat(out.get(0)[0]).isEqualTo(0); + assertThat(out.get(0)[1]).isEqualTo(1); + assertThat(out.get(0)[2]).isEqualTo(2); + assertThat(out.get(0)[3]).isEqualTo(0); + assertThat(out.get(1)[0]).isEqualTo(0); + assertThat(out.get(1)[1]).isEqualTo(77); + assertThat(out.get(1)[2]).isEqualTo(78); + assertThat(out.get(1)[3]).isEqualTo(0); + } + + @Test + public void twoNonZeroLeftFlanking() { + a0[0] = 1; + a0[1] = 2; + a1[0] = 77; + a1[1] = 78; + List out = ArrayUtils.removeZeros(in, true); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(3); + } + assertThat(out.get(0)[0]).isEqualTo(1); + assertThat(out.get(0)[1]).isEqualTo(2); + assertThat(out.get(0)[2]).isEqualTo(0); + assertThat(out.get(1)[0]).isEqualTo(77); + assertThat(out.get(1)[1]).isEqualTo(78); + assertThat(out.get(1)[2]).isEqualTo(0); + } + + @Test + public void twoNonZeroRightFlanking() { + a0[3] = 1; + a0[4] = 2; + a1[3] = 77; + a1[4] = 78; + List out = ArrayUtils.removeZeros(in, true); + for (double[] arr : out) { + assertThat(arr.length).isEqualTo(3); + } + assertThat(out.get(0)[0]).isEqualTo(0); + assertThat(out.get(0)[1]).isEqualTo(1); + assertThat(out.get(0)[2]).isEqualTo(2); + assertThat(out.get(1)[0]).isEqualTo(0); + assertThat(out.get(1)[1]).isEqualTo(77); + assertThat(out.get(1)[2]).isEqualTo(78); + } +} diff --git a/src/test/java/com/dmtavt/deltamass/utils/GridUtilsTest.java b/src/test/java/com/dmtavt/deltamass/utils/GridUtilsTest.java new file mode 100644 index 0000000..e9afae8 --- /dev/null +++ b/src/test/java/com/dmtavt/deltamass/utils/GridUtilsTest.java @@ -0,0 +1,96 @@ +package com.dmtavt.deltamass.utils; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import org.junit.Test; + +public class GridUtilsTest { + + @Test + public void gridTest1() { + double lo = -1.0; + double hi = 1.0; + double step = 0.1; + double eps = 1e-8; + + double[] grid = GridUtils.grid(lo, hi, step); + assertEquals(21, grid.length); + assertEquals(lo, grid[0], eps); + assertEquals(hi, grid[grid.length-1], eps); + } + + @Test + public void gridTest2() { + double lo = -1.05; + double hi = 1.05; + double step = 0.1; + double eps = 1e-8; + + double[] grid = GridUtils.grid(lo, hi, step); + assertEquals(22, grid.length); + assertEquals(lo, grid[0], eps); + assertEquals(hi, grid[grid.length-1], eps); + } + + @Test + public void gridTest3() { + double lo = -1.1; + double hi = 1.05; + double step = 0.1; + double eps = 1e-8; + + double[] grid = GridUtils.grid(lo, hi, step); + assertEquals(23, grid.length); + assertEquals(lo, grid[0], eps); + assertTrue(grid[grid.length-1] > hi); + assertTrue(Math.abs(grid[grid.length-1] - hi) < step); + } + + @Test + public void gridWithPivotTest1() { + double lo = -1.0; + double hi = 1.0; + double pivot = 0.00; + double step = 0.2; + double margin = 0.0; + double eps = 1e-8; + + double[] grid = GridUtils.grid(pivot, lo, hi, step, margin); + assertEquals(11, grid.length); + assertEquals(lo, grid[0], eps); + assertEquals(hi, grid[grid.length - 1], eps); + } + + @Test + public void gridWithPivotTest2() { + double lo = -1.0; + double hi = 1.0; + double pivot = 0.05; + double step = 0.2; + double margin = 0.0; + double eps = 1e-8; + + double[] grid = GridUtils.grid(pivot, lo, hi, step, margin); + assertEquals(12, grid.length); + assertTrue(Arrays.stream(grid).filter(v -> Math.abs(v - pivot) <= eps).findAny().isPresent()); + assertTrue(grid[0] <= lo); + assertTrue(grid[grid.length-1] >= hi); + } + + @Test + public void gridWithPivotTest3() { + double lo = -1.0; + double hi = 1.0; + double pivot = 0.05; + double step = 0.2; + double margin = 0.2; + double eps = 1e-8; + + double[] grid = GridUtils.grid(pivot, lo, hi, step, margin); + assertEquals(14, grid.length); + assertTrue(Arrays.stream(grid).filter(v -> Math.abs(v - pivot) <= eps).findAny().isPresent()); + assertTrue(grid[0] <= lo); + assertTrue(grid[grid.length-1] >= hi); + } +}

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 nzJ8p~Usj1vR1>>faQ{4kkk4-Om4-Y{ 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