From 75d6c25eda27658611e3d6fc0764e7c52762e388 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Tue, 26 Dec 2023 16:29:47 -0700 Subject: [PATCH 01/35] Build fixes for new Python versions. Fixed isotope mode bug --- GUniDec.spec | 57 +- PublicScripts/MutantCycleAnalysis/__init__.py | 0 build.bat | 2 +- readme.md | 6 + unidec/GUniDec.py | 4 + unidec/UniDecCD.py | 19 +- unidec/UniDecHT.py | 118 + .../GroEL_CDMS_1_conf.dat | 6 +- .../GroEL_CDMS_1_decon.txt | 9244 +++++++++-------- unidec/bin/UniDec.exe | Bin 3640320 -> 3640832 bytes unidec/modules/CDEng.py | 130 +- unidec/modules/HTEng.py | 503 + unidec/modules/PlotBase.py | 8 + unidec/modules/gui_elements/CDWindow.py | 6 +- unidec/modules/gui_elements/CD_controls.py | 43 +- unidec/modules/plot1d.py | 18 +- unidec/modules/thermo_reader/RawFileReader.py | 2 +- unidec/modules/unidec_enginebase.py | 8 + unidec/modules/unidec_presbase.py | 6 +- unidec/modules/unidecstructure.py | 2 + unidec/src/UniDec.h | 18 +- unidec/tools.py | 16 +- 22 files changed, 5555 insertions(+), 4661 deletions(-) create mode 100644 PublicScripts/MutantCycleAnalysis/__init__.py create mode 100644 unidec/UniDecHT.py create mode 100644 unidec/modules/HTEng.py diff --git a/GUniDec.spec b/GUniDec.spec index bf2e8e60..d5cc5be7 100644 --- a/GUniDec.spec +++ b/GUniDec.spec @@ -10,18 +10,43 @@ from multiprocessing import freeze_support from os import listdir from PyInstaller import compat import matplotlib -import hashing +import sys +import hashlib + + +def hashfile(path): + # BUF_SIZE is totally arbitrary, change for your app! + BUF_SIZE = 65536 # lets read stuff in 64kb chunks! + + md5 = hashlib.md5() + sha256 = hashlib.sha256() + + with open(path, 'rb') as f: + while True: + data = f.read(BUF_SIZE) + if not data: + break + md5.update(data) + sha256.update(data) + + md5hash = md5.hexdigest() + sha256hash = sha256.hexdigest() + + print("MD5: {0}".format(md5hash)) + print("SHA256: {0}".format(sha256hash)) + + return md5hash, sha256hash freeze_support() -def dir_files(path, rel): +def dir_files(path, rel, type='DATA'): ret = [] for p, d, f in os.walk(path): relpath = p.replace(path, '')[1:] for fname in f: ret.append((os.path.join(rel, relpath, fname), - os.path.join(p, fname), 'DATA')) + os.path.join(p, fname), type)) return ret @@ -66,24 +91,27 @@ if system == "Windows": a.datas += [('CDCReader.exe', 'unidec\\bin\\CDCReader.exe', 'DATA')] a.datas += [('h5repack.exe', 'unidec\\bin\\h5repack.exe', 'DATA')] a.datas += [('unimod.sqlite', 'unidec\\bin\\unimod.sqlite', 'DATA')] + #a.datas += [('numpy/DLLs', 'unidec\\bin\\mkl_def.2.dll', 'BINARY')] + #a.datas += [('numpy/DLLs', 'unidec\\bin\\mkl_avx2.2.dll', 'BINARY')] + #a.datas += [('numpy/DLLs', 'unidec\\bin\\mkl_intel_thread.2.dll', 'BINARY')] a.datas += [('pymzml\\version.txt', compat.base_prefix + '\\Lib\\site-packages\\pymzml\\version.txt', 'DATA')] a.datas += [('massql\\msql.ebnf', compat.base_prefix + '\\Lib\\site-packages\\massql\\msql.ebnf', 'DATA')] # Copy over all the DLLs from the bin folder for file in os.listdir('unidec\\bin'): if fnmatch.fnmatch(file, '*.dll'): - add = [(file, 'unidec\\bin\\' + file, 'DATA')] + add = [(file, 'unidec\\bin\\' + file, 'BINARY')] a.datas += add # print add - a.datas += [('RawFileReaderLicense.doc', 'unidec\\modules\\thermo_reader\\RawFileReaderLicense.doc', 'DATA')] - a.datas += [('ThermoFisher.CommonCore.Data.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.Data.dll', 'DATA')] - a.datas += [('ThermoFisher.CommonCore.RawFileReader.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.RawFileReader.dll', 'DATA')] - a.datas += [('ThermoFisher.CommonCore.MassPrecisionEstimator.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.MassPrecisionEstimator.dll', 'DATA')] - a.datas += [('ThermoFisher.CommonCore.BackgroundSubtraction.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.BackgroundSubtraction.dll', 'DATA')] + a.datas += [('RawFileReaderLicense.doc', 'unidec\\modules\\thermo_reader\\RawFileReaderLicense.doc', 'BINARY')] + a.datas += [('ThermoFisher.CommonCore.Data.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.Data.dll', 'BINARY')] + a.datas += [('ThermoFisher.CommonCore.RawFileReader.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.RawFileReader.dll', 'BINARY')] + a.datas += [('ThermoFisher.CommonCore.MassPrecisionEstimator.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.MassPrecisionEstimator.dll', 'BINARY')] + a.datas += [('ThermoFisher.CommonCore.BackgroundSubtraction.dll', 'unidec\\modules\\thermo_reader\\ThermoFisher.CommonCore.BackgroundSubtraction.dll', 'BINARY')] a.datas += [('Waters_MassLynxSDK_EULA.txt', 'unidec\\bin\\Waters_MassLynxSDK_EULA.txt', 'DATA')] elif system == "Linux": - a.datas += [('unideclinux', 'unidec/bin/unideclinux', 'DATA')] + a.datas += [('unideclinux', 'unidec/bin/unideclinux', 'BINARY')] a.datas += [('cacert.pem', os.path.join('unidec\\bin', 'cacert.pem'), 'DATA')] a.datas += [('logo.ico', 'unidec\\bin\\logo.ico', 'DATA')] @@ -102,9 +130,10 @@ a.datas.extend(dir_files("unidec\\bin\\Example Data", 'Example Data')) a.datas.extend(dir_files(compat.base_prefix + '\\Lib\\site-packages\\matchms\\data', "matchms\\data")) mkldir = compat.base_prefix + "/Lib/site-packages/numpy/DLLs" -a.datas.extend(dir_files(mkldir, '')) -a.datas.extend( - [(mkldir + "/" + mkl, '', 'DATA') for mkl in listdir(mkldir) if mkl.startswith('mkl_') or mkl.startswith('libio')]) +newdir = "numpy/DLLs" +a.datas.extend(dir_files(mkldir, newdir, type="BINARY")) +#a.datas.extend( +# [(mkldir + "/" + mkl, newdir, 'BINARY') for mkl in listdir(mkldir) if mkl.startswith('mkl_') or mkl.startswith('libio')]) rdkitlibs = compat.base_prefix + "/Lib/site-packages/rdkit.libs" a.datas.extend(dir_files(rdkitlibs, '')) @@ -149,7 +178,7 @@ for root, dirs, files in os.walk(outputdir): zipf.close() print("Zipped to", zipdirectory, "from", outputdir) -hashing.hashfile(zipdirectory) +hashfile(zipdirectory) tend = time.perf_counter() print("Build Time: %.2gm" % ((tend - tstart) / 60.0)) diff --git a/PublicScripts/MutantCycleAnalysis/__init__.py b/PublicScripts/MutantCycleAnalysis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build.bat b/build.bat index d70c695b..4a4ef73e 100644 --- a/build.bat +++ b/build.bat @@ -10,5 +10,5 @@ rem update docs with .\unidec_doc\make.bat html rem paste docs into UniDecDocumentation and push to git echo "Building" -C:\Python310\Scripts\pyinstaller.exe GUniDec.spec --noconfirm +C:\Python311\Scripts\pyinstaller.exe GUniDec.spec --noconfirm rem call "C:\Python\UniDec3\dist\UniDec_Windows\GUI_UniDec.exe" \ No newline at end of file diff --git a/readme.md b/readme.md index 1a812309..0d2ba9ed 100644 --- a/readme.md +++ b/readme.md @@ -195,6 +195,12 @@ Of course, using the pre-compiled version means you don't need to know Python at ## Change Log +v.6.0.5 + +Minor fixes to update compatibility for Python 3.11 and 3.12. + +Fixed common crash bug when isotope mode is on. Added warnings for if isotope mode is on. + v.6.0.4 Added "Notes" option to UPP to add notes to the bottom of the report. Added peak and data normalization options to UPP spreadsheet inputs. See help file. diff --git a/unidec/GUniDec.py b/unidec/GUniDec.py index 138ccab3..6f223f4d 100644 --- a/unidec/GUniDec.py +++ b/unidec/GUniDec.py @@ -410,6 +410,10 @@ def on_unidec_button(self, e=None): self.view.SetStatusText("Error %.0g" % out, number=5) print("Error ", out) self.warn("Error %.0g" % out) + if self.eng.check_isomode(): + self.view.SetStatusText("Warning: Isotope Mode On!", number=4) + else: + self.view.SetStatusText("", number=4) pass def after_unidec_run(self): diff --git a/unidec/UniDecCD.py b/unidec/UniDecCD.py index 41edddff..84cfa91d 100644 --- a/unidec/UniDecCD.py +++ b/unidec/UniDecCD.py @@ -19,8 +19,8 @@ class UniDecCDApp(UniDecApp): """ - Main UniDec GUI Application. - Presenter contains UniDec engine at self.eng and main GUI window at self.view + Main UniDecCD GUI Application. + Presenter contains UniDecCD engine at self.eng and main GUI window at self.view """ def __init__(self, *args, **kwargs): @@ -46,9 +46,7 @@ def init(self, *args, **kwargs): pub.subscribe(self.on_get_mzlimits, 'mzlimits') pub.subscribe(self.on_smash, 'smash') - ''' - pub.subscribe(self.on_integrate, 'integrate') - pub.subscribe(self.on_left_click, 'left_click')''' + self.eng.config.recentfile = self.eng.config.recentfileCD self.recent_files = self.read_recent() self.cleanup_recent_file(self.recent_files) @@ -96,6 +94,17 @@ def on_open(self, e=None): dlg.Destroy() def on_open_file(self, filename, directory, path=None, refresh=False): + """ + Opens a file. Run self.eng.open_file. + :param filename: File name + :param directory: Directory containing file + :param path: Full path to file + :param refresh: Refresh the data ranges from the file. Default False. + :return: None + """ + self.on_open_cdms_file(filename, directory, path=path, refresh=refresh) + + def on_open_cdms_file(self, filename, directory, path=None, refresh=False): """ Opens a file. Run self.eng.open_file. :param filename: File name diff --git a/unidec/UniDecHT.py b/unidec/UniDecHT.py new file mode 100644 index 00000000..a707a77d --- /dev/null +++ b/unidec/UniDecHT.py @@ -0,0 +1,118 @@ +from UniDecCD import UniDecCDApp +import multiprocessing +import modules.HTEng as HTEng +from unidec.modules.gui_elements import CDWindow +from pubsub import pub +import wx +import unidec.tools as ud +import numpy as np + +class UniDecHTCDApp(UniDecCDApp): + """ + Main UniDec GUI Application. + Presenter contains UniDec engine at self.eng and main GUI window at self.view + """ + + def init(self, *args, **kwargs): + """ + Initialize Engine and View. Load defaults. + :param args: + :param kwargs: + :return: + """ + self.eng = HTEng.UniDecCDHT() + self.cycol = ud.create_color_cycle("bgcmy") + self.view = CDWindow.CDMainwindow(self, "UniDecHT for HT-CD-MS Data", + self.eng.config, htmode=True) + self.comparedata = None + + pub.subscribe(self.on_select_mzz_region, 'mzlimits') + pub.subscribe(self.on_smash, 'smash') + + self.eng.config.recentfile = self.eng.config.recentfileCD + self.recent_files = self.read_recent() + self.cleanup_recent_file(self.recent_files) + self.view.menu.update_recent() + + self.on_load_default(0) + + if "path" in kwargs: + newdir, fname = os.path.split(kwargs["path"]) + self.on_open_file(fname, newdir) + # self.on_dataprep_button(0) + # self.on_auto(0) + + if self.infile is not None: + newdir, fname = os.path.split(self.infile) + self.on_open_file(fname, newdir) + # self.on_dataprep_button(0) + # self.on_auto(0) + + if True: # and platform.node() == 'DESKTOP-08TGCJO': + print("Opening Test File") + path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" + "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") + + self.on_open_file(None, None, path=path) + + def on_open_file(self, filename, directory, path=None, refresh=False): + """ + Opens a file. Run self.eng.open_file. + :param filename: File name + :param directory: Directory containing file + :param path: Full path to file + :param refresh: Refresh the data ranges from the file. Default False. + :return: None + """ + self.on_open_cdms_file(filename, directory, path=path, refresh=refresh) + self.eng.process_data_scans() + self.make_tic_plot() + + def on_select_mzz_region(self): + try: + if not wx.GetKeyState(wx.WXK_CONTROL): + xlimits = self.view.plot1.subplot1.get_xlim() + ylimits = self.view.plot1.subplot1.get_ylim() + print("New limits:", xlimits, ylimits) + color = next(self.cycol) + self.run_eic_ht(xlimits, ylimits, color=color) + + self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], + edgecolor=color, facecolor=color) + + except Exception as e: + print(e) + + def make_tic_plot(self): + data = self.eng.get_tic() + self.view.plot3.plotrefreshtop(data[:, 0], data[:, 1], config=self.eng.config, zoomout=True) + + def on_run_tic_ht(self, e=None): + self.run_tic_ht() + + def run_tic_ht(self): + self.make_tic_plot() + data = self.eng.tic_ht(normalize=False) + self.view.plot3.plotadd(data[:, 0], data[:, 1], colval="red", nopaint=False) + + def run_eic_ht(self, mzrange, zrange, color='b'): + label = "m/z: " + str(round(mzrange[0])) + "-" + str(round(mzrange[1])) + \ + " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) + + htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=False) + self.view.plot3.plotadd(htdata[:, 0], htdata[:, 1], colval=color, nopaint=False) + self.view.plot3.addtext(label, np.amax(self.eng.fulltime) * 0.8, np.amax(htdata[:, 1]), color=color, + vlines=False, hlines=False) + pass + + def on_run_all_ht(self, e=None): + self.run_all_ht() + + def run_all_ht(self): + self.eng.run_ht() + + +if __name__ == "__main__": + multiprocessing.freeze_support() + app = UniDecHTCDApp() + app.start() diff --git a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat index d964f73a..93f38f51 100644 --- a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat +++ b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat @@ -1,4 +1,4 @@ -version 6.0.3 +version 6.0.5 imflag 0 cdmsflag 1 input C:\Python\UniDec3\unidec\bin\Example Data\CDMS\GroEL_CDMS_1_unidecfiles\GroEL_CDMS_1_input.dat @@ -21,8 +21,8 @@ msig 0.0 molig 0.0 massbins 10.0 mtabsig 0.0 -minmz 10017.040865384615 -maxmz 12598.242788461539 +minmz 9992.689903846154 +maxmz 12573.891826923078 subbuff 0.0 subtype 0 smooth 0.0 diff --git a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_decon.txt b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_decon.txt index 189c2298..67eb8a8c 100644 --- a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_decon.txt +++ b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_decon.txt @@ -1,7 +1,3 @@ -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -16,50 +12,17 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000859 +0.000131 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 0.000000 -0.000000 -0.000000 @@ -69,24 +32,11 @@ -0.000000 -0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -95,11 +45,7 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 0.000000 -0.000000 -0.000000 @@ -113,24 +59,15 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -144,14 +81,13 @@ 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -164,12 +100,13 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 -0.000000 @@ -181,13 +118,9 @@ -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -206,39 +139,18 @@ -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -252,8 +164,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 0.000000 0.000000 -0.000000 @@ -264,11 +174,18 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -284,8 +201,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 0.000000 -0.000000 -0.000000 @@ -295,12 +210,9 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -320,10 +232,13 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -341,22 +256,28 @@ -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 @@ -373,30 +294,15 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -426,7 +332,14 @@ 0.000000 0.000000 0.000000 -0.001035 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -435,6 +348,10 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -452,14 +369,15 @@ -0.000000 -0.000000 -0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -470,20 +388,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -501,13 +405,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -520,20 +417,20 @@ -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -552,17 +449,26 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -571,15 +477,23 @@ 0.000000 0.000000 0.000000 -0.000295 0.000000 0.000000 0.000000 -0.000740 -0.000000 -0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -597,7 +511,19 @@ -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -610,8 +536,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -622,6 +546,14 @@ -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -643,31 +575,38 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -677,7 +616,6 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -687,24 +625,13 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 +0.000992 +0.000000 0.000000 0.000000 0.000000 @@ -728,11 +655,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 @@ -740,10 +662,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -755,24 +673,6 @@ -0.000000 -0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -780,15 +680,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 @@ -806,16 +697,8 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -833,13 +716,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -848,16 +724,9 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -870,7 +739,6 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -885,9 +753,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -900,17 +765,12 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 @@ -925,8 +785,8 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 -0.001035 0.000000 0.000000 0.000000 @@ -943,6 +803,17 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -951,13 +822,15 @@ 0.000000 0.000000 0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000805 +0.000181 +0.000004 +0.000002 0.000000 0.000000 0.000000 @@ -969,18 +842,28 @@ 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -988,6 +871,9 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -999,16 +885,29 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.001035 -0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1017,7 +916,6 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 -0.000000 -0.000000 @@ -1033,17 +931,6 @@ 0.000000 0.000000 0.000000 -0.000073 -0.000389 -0.000435 -0.000072 -0.000012 -0.000047 -0.000007 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -1054,11 +941,8 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -1068,9 +952,11 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -1083,31 +969,6 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -1121,6 +982,14 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -1138,15 +1007,36 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1164,16 +1054,9 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -1181,6 +1064,12 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -1198,16 +1087,6 @@ 0.000000 0.000000 0.000000 -0.000004 -0.000568 -0.000127 -0.000198 -0.000122 -0.000016 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -1226,6 +1105,7 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1233,6 +1113,12 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1242,22 +1128,13 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 +0.000991 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -1271,33 +1148,18 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 @@ -1305,15 +1167,16 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1324,8 +1187,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -1346,28 +1207,14 @@ 0.000000 0.000000 0.000000 +0.000992 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -1375,8 +1222,8 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -1390,12 +1237,13 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000037 +0.000443 +0.000371 +0.000073 +0.000013 +0.000048 +0.000007 0.000000 0.000000 0.000000 @@ -1410,9 +1258,13 @@ -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -1422,10 +1274,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -1439,6 +1287,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1451,32 +1300,22 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 0.000000 @@ -1484,20 +1323,25 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.001029 -0.000005 -0.000001 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1519,47 +1363,12 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000002 -0.000022 -0.000174 -0.000230 -0.000170 -0.000361 -0.000076 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1577,12 +1386,10 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -1595,12 +1402,12 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000004 +0.000529 +0.000124 +0.000198 +0.000121 +0.000016 0.000000 0.000000 0.000000 @@ -1614,7 +1421,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -1626,12 +1432,12 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -1654,6 +1460,10 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -1662,22 +1472,17 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -1686,13 +1491,29 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1700,8 +1521,30 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1724,17 +1567,13 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -1748,22 +1587,15 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 -0.000002 -0.000004 -0.000029 -0.000014 -0.000043 -0.000048 -0.000045 -0.002406 -0.000252 -0.000220 -0.000041 -0.000002 0.000000 0.000000 0.000000 @@ -1772,18 +1604,41 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -1793,10 +1648,7 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -1809,12 +1661,9 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 -0.000852 -0.000034 -0.000078 -0.000062 -0.000008 0.000000 0.000000 0.000000 @@ -1824,18 +1673,12 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -1848,80 +1691,29 @@ 0.000000 0.000000 0.000000 -0.000088 -0.000493 -0.000359 -0.000019 -0.000003 -0.000477 -0.000401 -0.000081 -0.000088 -0.000026 -0.000006 -0.000004 -0.000007 -0.008101 -0.000070 -0.000081 -0.000043 -0.000003 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 +0.000003 +0.000968 +0.000021 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000001 -0.000004 -0.000007 -0.000003 -0.000001 -0.000002 -0.001012 -0.000003 -0.000001 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 @@ -1940,19 +1732,13 @@ -0.000000 -0.000000 -0.000000 --0.000000 +0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -1964,25 +1750,24 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000021 +0.000161 +0.000240 +0.000151 +0.000347 +0.000069 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -1990,16 +1775,17 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -2021,35 +1807,24 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 -0.000000 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -2063,19 +1838,9 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2083,22 +1848,15 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 0.000000 @@ -2116,12 +1874,8 @@ 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -2131,16 +1885,6 @@ -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -2149,74 +1893,10 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000005 -0.000063 -0.000020 -0.000071 -0.000164 -0.000402 -0.000303 -0.000000 -0.000000 -0.000000 -0.000005 -0.000236 -0.000147 -0.000312 -0.000000 -0.000343 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000002 -0.000015 -0.000021 -0.000031 -0.000767 -0.000199 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -2229,8 +1909,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2249,40 +1927,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000001 -0.000009 -0.000077 -0.000247 -0.001034 -0.000394 -0.000128 -0.000168 -0.000012 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -2295,12 +1939,6 @@ -0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2318,6 +1956,18 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000004 +0.000034 +0.000020 +0.000069 +0.000075 +0.000064 +0.002121 +0.000297 +0.000240 +0.000046 +0.000002 0.000000 0.000000 0.000000 @@ -2329,6 +1979,8 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -2354,14 +2006,6 @@ 0.000000 0.000000 0.000000 -0.001898 -0.000117 -0.000030 -0.000020 -0.000003 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -2370,12 +2014,11 @@ 0.000000 0.000000 0.000000 -0.000003 -0.000270 -0.000664 -0.000097 -0.000002 -0.000000 +0.000793 +0.000037 +0.000086 +0.000066 +0.000008 0.000000 0.000000 0.000000 @@ -2384,10 +2027,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2399,33 +2038,6 @@ 0.000000 0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -2440,6 +2052,24 @@ 0.000000 0.000000 0.000000 +0.000024 +0.000625 +0.000249 +0.000019 +0.000004 +0.000472 +0.000346 +0.000084 +0.000099 +0.000028 +0.000007 +0.000004 +0.000007 +0.006754 +0.000067 +0.000085 +0.000045 +0.000004 0.000000 0.000000 0.000000 @@ -2451,15 +2081,28 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2467,26 +2110,30 @@ 0.000000 0.000000 0.000001 -0.000017 -0.000013 -0.000019 +0.000002 +0.000006 0.000008 -0.000005 -0.005081 -0.000017 +0.000004 +0.000002 +0.000003 +0.001944 0.000009 0.000004 -0.000000 -0.000000 +0.000001 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -2496,7 +2143,21 @@ -0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2506,27 +2167,7 @@ 0.000000 0.000000 0.000000 -0.000417 0.000000 -0.000288 -0.000138 -0.000172 -0.000014 -0.000005 -0.000001 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000001 -0.000000 -0.000001 -0.000001 -0.006195 -0.000007 -0.000004 -0.000001 0.000000 0.000000 0.000000 @@ -2534,9 +2175,35 @@ 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -2544,6 +2211,9 @@ -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2555,38 +2225,87 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000001 -0.000006 -0.000057 -0.000039 -0.000168 -0.000131 -0.000022 -0.001205 -0.001683 -0.000513 -0.000807 -0.000154 -0.000021 -0.000006 -0.000006 -0.090298 -0.000042 -0.000037 -0.000021 -0.000002 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2600,6 +2319,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2608,32 +2328,51 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000007 -0.000023 -0.000022 -0.000048 -0.000012 -0.000001 -0.000001 0.000000 -0.009190 -0.000005 -0.000002 -0.000002 -0.000001 0.000000 0.000000 0.000000 +0.000005 +0.000056 +0.000019 +0.000070 +0.000157 +0.000394 +0.000284 +0.000000 +0.000000 0.000000 +0.000003 +0.000005 0.000000 0.000000 0.000000 @@ -2648,6 +2387,17 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -2655,28 +2405,31 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000016 0.000000 -0.000402 -0.000491 -0.000124 0.000002 +0.000014 +0.000024 +0.000028 +0.000750 +0.000173 +0.000000 +0.000000 0.000000 -0.000001 -0.000005 0.000004 -0.000012 -0.000005 -0.000001 +0.000214 +0.000144 +0.000298 +0.000000 +0.000331 0.000000 0.000000 -0.002041 0.000000 0.000000 0.000000 @@ -2691,7 +2444,10 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2701,11 +2457,19 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000011 +0.000073 +0.000224 +0.000940 +0.000455 +0.000107 +0.000163 +0.000010 0.000000 0.000000 0.000000 @@ -2734,37 +2498,24 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2774,8 +2525,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2785,6 +2534,8 @@ 0.000000 0.000000 -0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -2792,25 +2543,10 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 -0.000000 -0.000000 @@ -2822,26 +2558,34 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000977 +0.000010 +0.000002 0.000000 0.000000 0.000000 0.000000 --0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000002 +0.000289 +0.000605 +0.000093 +0.000002 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -2849,14 +2593,7 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -2868,22 +2605,21 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +0.000002 +0.000938 +0.000037 +0.000013 +0.000000 0.000000 0.000000 0.000000 -0.000001 0.000000 0.000000 0.000000 -0.000015 -0.001003 -0.000012 -0.000004 0.000000 0.000000 0.000000 @@ -2893,11 +2629,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 @@ -2911,6 +2642,8 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2937,16 +2670,26 @@ 0.000000 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 +0.000002 +0.000026 +0.000022 +0.000038 +0.000017 +0.000008 +0.004804 +0.000023 +0.000013 +0.000005 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -2954,8 +2697,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -2969,7 +2710,13 @@ 0.000000 0.000000 0.000000 +0.000249 0.000000 +0.000337 +0.000236 +0.000161 +0.000007 +0.000001 0.000000 0.000000 0.000000 @@ -2980,29 +2727,62 @@ 0.000000 0.000000 0.000000 +0.001978 +0.000002 +0.000002 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000002 +0.000009 +0.000060 +0.000026 +0.000146 +0.000101 +0.000093 +0.001209 +0.001342 +0.000507 +0.000899 +0.000154 +0.000027 +0.000008 +0.000007 +0.088487 +0.000049 +0.000046 +0.000025 +0.000002 0.000000 0.000000 0.000000 @@ -3012,8 +2792,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -3021,21 +2799,6 @@ 0.000000 0.000000 0.000000 -0.000004 -0.000024 -0.000148 -0.000190 -0.000936 -0.000468 -0.000483 -0.001720 -0.000640 -0.000004 -0.000003 -0.000181 -0.000220 -0.000150 -0.000003 0.000000 0.000000 0.000000 @@ -3043,6 +2806,34 @@ 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000014 +0.000037 +0.000047 +0.000126 +0.000029 +0.000004 +0.000001 +0.000001 +0.010630 +0.000008 +0.000005 +0.000003 +0.000001 0.000000 0.000000 0.000000 @@ -3051,22 +2842,18 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 0.000000 @@ -3077,18 +2864,23 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000046 +0.000000 +0.000065 +0.000804 +0.000075 +0.000002 +0.000000 0.000001 +0.000003 +0.000004 +0.000020 0.000005 -0.000035 -0.000024 0.000001 -0.000002 -0.000402 -0.000898 -0.001620 -0.000115 -0.000002 +0.000001 0.000000 +0.001948 0.000000 0.000000 0.000000 @@ -3099,8 +2891,8 @@ 0.000000 0.000000 -0.000000 --0.000000 --0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -3116,7 +2908,7 @@ -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -3141,17 +2933,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -3167,42 +2948,42 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000004 -0.000009 -0.000065 -0.000097 -0.000368 -0.000124 -0.000048 -0.000006 -0.000002 -0.028193 -0.000017 -0.000025 -0.000019 -0.000002 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -3211,40 +2992,47 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000001 -0.000001 -0.000002 -0.000150 -0.000124 -0.000368 -0.000168 -0.000202 -0.000018 -0.000002 0.000000 0.000000 -0.016544 -0.000002 -0.000005 -0.000004 -0.000001 -0.000001 0.000000 0.000000 0.000000 +-0.000000 +0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -3255,13 +3043,7 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -3270,49 +3052,13 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000833 -0.000113 -0.000023 -0.000031 -0.000008 -0.000011 -0.000003 -0.000009 -0.000013 -0.000969 -0.000463 -0.000945 -0.000147 -0.000014 -0.000002 -0.000001 -0.305775 -0.000013 -0.000027 -0.000034 -0.000012 -0.000015 -0.000001 -0.000001 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -3324,47 +3070,40 @@ 0.000000 0.000000 0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 -0.000017 -0.000003 -0.000036 -0.000057 -0.000018 -0.000186 -0.001135 -0.000283 -0.000333 -0.000033 -0.000002 0.000000 0.000000 -0.067230 -0.000002 -0.000003 -0.000004 -0.000001 0.000001 0.000000 0.000000 0.000000 +0.000023 +0.000951 +0.000013 +0.000004 +0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -3373,23 +3112,16 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000001 -0.000067 -0.000028 -0.000437 -0.000407 -0.000009 -0.000041 -0.000037 -0.000006 -0.000004 -0.000001 0.000000 0.000000 0.000000 -0.004138 0.000000 0.000000 0.000000 @@ -3402,10 +3134,12 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 -0.000000 -0.000000 @@ -3418,29 +3152,24 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000002 -0.000005 -0.000214 -0.000060 -0.000685 -0.000497 -0.000044 -0.000277 -0.000234 -0.000020 -0.000015 -0.000011 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000005 0.000000 0.000000 0.000000 @@ -3453,12 +3182,7 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -3467,40 +3191,71 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000004 +0.000031 +0.000139 +0.000170 +0.000831 +0.000607 +0.000378 +0.001722 +0.000509 +0.000004 +0.000003 +0.000158 +0.000261 +0.000137 +0.000003 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -3514,6 +3269,7 @@ -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -3525,26 +3281,30 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000004 +0.000033 +0.000020 +0.000001 +0.000002 +0.000324 +0.001032 +0.001443 +0.000112 +0.000002 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 -0.000002 -0.000068 -0.000560 -0.000203 -0.000131 -0.000047 -0.000024 -0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000006 -0.001027 -0.000001 0.000000 0.000000 0.000000 @@ -3552,15 +3312,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -3568,25 +3319,16 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000002 0.000000 -0.000142 -0.000451 -0.001096 -0.000173 -0.000129 -0.000068 -0.000010 0.000000 0.000000 0.000000 @@ -3597,11 +3339,14 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -3622,28 +3367,34 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000003 0.000000 -0.000026 -0.000079 -0.000300 -0.000282 -0.000216 -0.000114 -0.000014 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000003 +0.000008 +0.000062 +0.000130 +0.000551 +0.000171 +0.000079 +0.000011 +0.000003 +0.022698 +0.000020 +0.000031 +0.000026 +0.000003 +0.000001 0.000000 0.000000 0.000000 @@ -3651,24 +3402,8 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -3681,6 +3416,8 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -3692,8 +3429,23 @@ 0.000000 0.000000 0.000000 +0.000008 +0.000016 +0.000102 +0.000096 +0.000311 +0.000185 +0.000244 +0.000018 +0.000003 0.000000 0.000000 +0.017838 +0.000003 +0.000006 +0.000006 +0.000001 +0.000001 0.000000 0.000000 0.000000 @@ -3704,9 +3456,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -3716,11 +3465,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -3738,25 +3482,26 @@ 0.000000 0.000000 0.000000 -0.000008 -0.000018 -0.000367 -0.000642 -0.000360 0.000006 +0.000011 +0.000756 +0.000457 +0.001039 +0.000133 +0.000019 0.000002 -0.000616 -0.000034 +0.000002 +0.271103 +0.000015 +0.000032 +0.000043 +0.000016 0.000018 +0.000001 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -3767,40 +3512,42 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000028 -0.000103 -0.001544 -0.000735 -0.000534 -0.000362 -0.000110 -0.000003 -0.000002 -0.002392 -0.000152 -0.000237 0.000006 +0.000607 +0.000039 +0.000358 +0.000003 +0.000020 +0.000022 +0.000036 +0.000185 +0.001024 +0.000359 +0.000551 +0.000052 +0.000004 +0.000001 0.000000 +0.086933 +0.000004 +0.000008 +0.000010 +0.000002 +0.000003 0.000000 0.000000 0.000000 @@ -3808,12 +3555,22 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -3822,46 +3579,21 @@ 0.000000 0.000000 0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000396 -0.000413 -0.000177 -0.000038 -0.000008 -0.000003 -0.000001 0.000000 -0.000004 -0.000011 -0.000258 -0.000203 -0.000203 -0.000128 -0.000021 0.000000 0.000000 -0.003093 -0.000031 -0.000155 -0.000028 -0.000003 +0.000001 0.000001 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.003962 0.000000 0.000000 0.000000 @@ -3875,57 +3607,21 @@ 0.000000 0.000000 -0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000001 -0.000005 -0.000010 -0.000017 -0.000053 -0.000099 -0.000165 -0.000354 -0.000095 -0.000282 -0.000096 -0.000110 -0.000041 -0.000009 -0.000001 -0.000000 -0.037903 -0.000015 -0.000041 -0.000025 -0.000004 -0.000002 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -3933,44 +3629,30 @@ 0.000000 0.000000 0.000000 +0.000008 +0.000017 +0.000381 +0.000053 +0.000979 +0.000631 +0.000154 +0.000396 +0.000218 +0.000033 +0.000087 +0.000015 +0.000001 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000003 -0.000012 -0.000021 -0.000180 -0.000130 -0.000163 -0.000018 -0.000003 -0.000000 -0.000000 -0.071893 -0.000003 -0.000009 -0.000009 -0.000002 -0.000003 -0.000000 +0.000994 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -3982,42 +3664,20 @@ 0.000000 0.000000 0.000000 -0.000001 0.000000 0.000000 0.000000 -0.000055 -0.000145 -0.000263 -0.000235 -0.000107 -0.001348 -0.000642 -0.002557 -0.000922 -0.000785 -0.000068 -0.000007 -0.000001 0.000000 -0.449134 -0.000003 -0.000017 -0.000032 -0.000022 -0.000061 -0.000024 -0.000074 -0.000131 -0.000223 -0.000336 -0.000212 -0.000060 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4033,39 +3693,12 @@ 0.000000 0.000000 0.000000 -0.001001 0.000000 0.000000 0.000000 -0.000023 -0.000008 -0.000002 -0.000002 -0.000003 -0.000120 -0.000138 -0.001152 -0.000533 -0.000460 -0.000050 -0.000003 0.000000 0.000000 -0.507232 -0.000002 -0.000009 -0.000035 -0.000066 -0.000419 -0.000209 -0.000489 -0.000515 -0.000582 -0.000718 -0.000461 -0.000156 0.000000 -0.000002 0.000000 0.000000 0.000000 @@ -4086,34 +3719,43 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +0.000004 +0.000322 +0.000396 +0.000150 +0.000031 +0.000060 +0.000028 +0.000001 0.000000 0.000000 0.000000 +0.000001 0.000000 -0.000002 -0.000125 -0.000076 -0.000145 -0.000043 +0.000012 +0.000977 0.000001 0.000000 0.000000 -0.035812 -0.000001 0.000000 -0.000001 -0.000002 -0.000008 -0.000003 -0.000003 -0.000002 -0.000001 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4129,6 +3771,9 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 @@ -4137,7 +3782,14 @@ 0.000000 0.000000 0.000000 +0.000001 0.000000 +0.000007 +0.000645 +0.000296 +0.000031 +0.000010 +0.000002 0.000000 0.000000 0.000000 @@ -4146,19 +3798,6 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000003 -0.000032 -0.000017 -0.000001 -0.000000 -0.000000 -0.005103 -0.000013 -0.000000 -0.000001 -0.000001 -0.000001 0.000000 0.000000 0.000000 @@ -4167,11 +3806,25 @@ 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -4180,40 +3833,21 @@ 0.000000 0.000000 0.000000 +0.000023 0.000000 +0.000051 +0.000263 +0.000362 +0.000379 +0.000439 +0.000387 +0.000075 +0.000004 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000027 -0.000025 -0.000965 -0.000019 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000001 -0.000602 -0.002492 -0.000002 -0.000002 -0.000003 -0.000003 -0.000001 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -4221,16 +3855,37 @@ 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4250,7 +3905,28 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4259,21 +3935,30 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000008 +0.000028 +0.000302 +0.000757 +0.000241 +0.000006 +0.000002 +0.000575 +0.000044 +0.000017 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -4285,30 +3970,59 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000144 -0.000024 -0.000475 -0.000375 -0.000013 -0.000002 0.000000 0.000000 0.000000 0.000000 +0.000024 +0.000089 +0.001297 +0.001071 +0.000356 +0.000335 +0.000102 +0.000003 +0.000003 +0.002218 +0.000219 +0.000225 +0.000007 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4320,19 +4034,31 @@ 0.000000 0.000000 0.000000 +0.000356 +0.000398 +0.000184 +0.000042 +0.000009 +0.000003 +0.000001 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000004 +0.000010 +0.000227 +0.000312 +0.000143 +0.000112 +0.000018 0.000000 0.000000 +0.001935 +0.000033 +0.000146 +0.000030 +0.000003 +0.000001 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -4343,13 +4069,6 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000143 -0.000028 -0.000499 -0.000349 -0.000013 -0.000001 0.000000 0.000000 0.000000 @@ -4362,11 +4081,33 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000005 +0.000010 +0.000017 +0.000048 +0.000118 +0.000208 +0.000306 +0.000078 +0.000230 +0.000147 +0.000108 +0.000036 +0.000009 +0.000001 0.000000 +0.027376 +0.000013 +0.000026 +0.000013 +0.000002 +0.000001 0.000000 0.000000 0.000000 @@ -4384,6 +4125,10 @@ 0.000000 0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -4398,21 +4143,22 @@ 0.000000 0.000000 0.000000 +0.000003 +0.000009 +0.000017 +0.000158 +0.000194 +0.000216 +0.000020 +0.000004 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000001 -0.000000 -0.000002 -0.000306 -0.000186 -0.001494 -0.000038 -0.000042 -0.000002 +0.071720 +0.000004 +0.000011 +0.000013 +0.000004 +0.000004 0.000000 0.000000 0.000000 @@ -4420,6 +4166,8 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4432,67 +4180,56 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000009 -0.000041 -0.000112 -0.000445 -0.000269 -0.000009 -0.000006 -0.000058 -0.000002 -0.000002 -0.001676 -0.000237 -0.001231 -0.000037 -0.000006 0.000000 0.000000 +0.000001 +0.000006 +0.000021 +0.000104 +0.000085 +0.000677 +0.000377 +0.001680 +0.001015 +0.000873 +0.000057 +0.000007 +0.000001 0.000000 +0.345915 +0.000003 +0.000018 +0.000038 +0.000027 +0.000061 +0.000011 +0.000008 +0.000003 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -4500,70 +4237,84 @@ 0.000000 0.000000 0.000000 +0.000975 0.000000 0.000000 +0.000000 +0.000021 +0.000018 +0.000021 +0.000473 +0.000234 +0.000441 +0.000234 +0.001554 +0.000927 +0.000740 +0.000053 0.000004 -0.000009 -0.000014 -0.000015 -0.000015 -0.000001 0.000001 -0.002446 -0.000136 -0.000424 -0.000035 -0.000005 0.000000 +0.569701 +0.000003 +0.000015 +0.000058 +0.000100 +0.000555 +0.000220 +0.000468 +0.000572 +0.000749 +0.001010 +0.000658 +0.000218 0.000000 +0.000003 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 -0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000002 +0.000099 +0.000081 +0.000142 +0.000032 +0.000001 +0.000000 +0.000000 +0.041263 +0.000001 +0.000001 +0.000002 0.000003 0.000010 -0.000341 -0.000337 -0.000256 -0.000071 -0.000016 -0.000001 +0.000002 +0.000002 0.000001 -0.004064 -0.000032 -0.000039 -0.000005 0.000000 0.000000 0.000000 @@ -4571,20 +4322,14 @@ 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -4602,38 +4347,35 @@ 0.000000 0.000000 0.000000 -0.000009 -0.000031 -0.000713 -0.000391 -0.000230 -0.000055 -0.000006 0.000000 0.000000 -0.013908 -0.000024 -0.000059 -0.000065 -0.000017 -0.000015 +0.000000 +0.000001 0.000002 +0.000019 +0.000010 0.000001 0.000000 0.000000 +0.003922 +0.000008 0.000000 +0.000001 +0.000001 +0.000001 0.000000 0.000000 0.000000 0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 -0.000000 +0.000000 -0.000000 -0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -4648,51 +4390,54 @@ 0.000000 0.000000 0.000000 +0.000100 +0.000071 +0.000235 +0.000585 +0.000001 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000001 -0.000063 -0.000113 -0.001848 -0.000698 -0.000490 -0.000081 -0.000006 +0.000012 +0.000007 +0.000001 0.000000 +0.000002 +0.002797 +0.001138 +0.000001 +0.000002 +0.000002 +0.000002 0.000000 -0.088318 -0.000026 -0.000067 -0.000122 -0.000062 -0.000121 -0.000035 -0.000042 -0.000019 -0.000003 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -4704,30 +4449,9 @@ 0.000000 0.000000 0.000000 -0.000009 -0.000018 -0.000316 -0.000154 -0.000077 -0.000009 -0.000001 0.000000 0.000000 -0.094598 -0.000003 -0.000009 -0.000030 -0.000046 -0.000242 -0.000153 -0.000419 -0.000368 -0.000436 -0.000739 -0.000508 -0.000182 0.000000 -0.000006 0.000000 0.000000 0.000000 @@ -4736,54 +4460,19 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000278 -0.000723 -0.000835 -0.000551 -0.000396 -0.000477 -0.000430 -0.000296 -0.000954 -0.000331 -0.003728 -0.000996 -0.000374 -0.000027 -0.000001 0.000000 0.000000 -0.774288 -0.000003 -0.000011 -0.000046 -0.000091 -0.000358 -0.000137 -0.000198 -0.000113 -0.000172 -0.000352 -0.000312 -0.000186 0.000000 -0.000109 0.000000 0.000000 -0.000940 -0.000953 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -4798,41 +4487,25 @@ 0.000000 0.000000 0.000000 -0.000571 -0.000908 -0.000658 -0.000295 -0.000174 -0.000206 -0.000239 -0.000199 -0.000686 -0.000463 -0.002415 -0.000833 -0.000297 -0.000022 -0.000001 0.000000 0.000000 -0.609804 -0.000001 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000008 0.000002 -0.000016 -0.000074 -0.000438 -0.000261 -0.000367 -0.000040 -0.000108 -0.000104 -0.000222 -0.000372 +0.000220 +0.000015 +0.000414 +0.000314 +0.000014 +0.000004 0.000000 -0.000182 0.000000 0.000000 -0.000001 0.000000 0.000000 0.000000 @@ -4851,31 +4524,32 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000004 -0.000018 -0.000073 -0.000113 -0.000100 -0.000310 -0.000132 -0.000236 -0.000054 -0.000024 -0.000005 0.000000 0.000000 0.000000 -0.044371 -0.000001 0.000000 -0.000002 -0.000008 -0.000040 -0.000023 -0.000018 -0.000002 -0.000002 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -4886,6 +4560,33 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 0.000000 @@ -4894,12 +4595,58 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000000 +0.000001 +0.000104 +0.000460 +0.000284 +0.000135 +0.000005 +0.000003 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000001 +0.000245 +0.000194 +0.001458 +0.000039 +0.000044 +0.000002 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -4913,26 +4660,65 @@ 0.000000 0.000000 0.000001 +0.000000 0.000001 +0.000890 +0.000182 +0.000871 +0.000032 +0.000006 +0.000000 0.000000 0.000000 0.000000 -0.002059 -0.000009 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000002 +0.000019 +0.000306 +0.000463 +0.000115 +0.000072 +0.000023 +0.000002 +0.000001 +0.003194 +0.000230 +0.000482 +0.000041 +0.000006 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -4945,43 +4731,53 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000002 -0.000005 -0.000095 -0.000115 -0.000007 -0.000002 -0.000005 -0.000771 -0.000033 -0.000000 -0.000000 -0.000000 -0.000000 +0.000012 +0.000266 +0.000520 +0.000133 +0.000055 +0.000013 +0.000001 +0.000001 +0.002917 +0.000028 +0.000017 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 --0.000000 0.000000 -0.000000 -0.000000 @@ -4994,45 +4790,48 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 0.000000 0.000000 0.000008 -0.001251 -0.001663 -0.000036 +0.000026 +0.000575 +0.000652 +0.000133 +0.000045 +0.000006 +0.000000 +0.000000 +0.014180 +0.000039 +0.000070 0.000080 -0.000031 0.000024 -0.000008 +0.000023 0.000002 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -5042,7 +4841,14 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5050,34 +4856,50 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000052 +0.000095 +0.001468 +0.001182 +0.000219 +0.000041 +0.000003 +0.000000 +0.000000 +0.069801 +0.000026 +0.000058 +0.000122 +0.000075 +0.000153 +0.000032 +0.000022 +0.000017 +0.000003 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000035 -0.000306 -0.000313 -0.000052 -0.000312 -0.000013 -0.000003 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5086,9 +4908,30 @@ 0.000000 0.000000 0.000000 +0.000007 +0.000016 +0.000271 +0.000306 +0.000076 +0.000009 +0.000001 0.000000 0.000000 +0.098342 +0.000004 +0.000011 +0.000041 +0.000065 +0.000321 +0.000158 +0.000348 +0.000334 +0.000418 +0.000713 +0.000495 +0.000180 0.000000 +0.000008 0.000000 0.000000 0.000000 @@ -5097,6 +4940,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5107,11 +4951,42 @@ 0.000000 0.000000 0.000000 +0.000092 +0.000329 +0.000505 +0.000411 +0.000319 +0.000361 +0.000351 +0.000225 +0.000501 +0.000168 +0.001853 +0.001103 +0.000343 +0.000020 +0.000001 0.000000 0.000000 +0.637996 +0.000003 +0.000013 +0.000059 +0.000111 +0.000406 +0.000121 +0.000145 +0.000098 +0.000165 +0.000343 +0.000303 +0.000173 0.000000 +0.000087 0.000000 0.000000 +0.000913 +0.000924 0.000000 0.000000 0.000000 @@ -5127,11 +5002,41 @@ 0.000000 0.000000 0.000000 +0.000644 +0.001172 +0.000966 +0.000465 +0.000269 +0.000309 +0.000462 +0.000439 +0.000912 +0.000550 +0.002776 +0.001679 +0.000529 +0.000024 +0.000001 0.000000 0.000000 +0.683517 +0.000001 +0.000004 +0.000029 +0.000112 +0.000604 +0.000251 +0.000311 +0.000034 +0.000085 +0.000103 +0.000227 +0.000366 0.000000 +0.000179 0.000000 0.000000 +0.000001 0.000000 0.000000 0.000000 @@ -5150,32 +5055,51 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000003 +0.000011 +0.000047 +0.000149 +0.000148 +0.000265 +0.000112 +0.000195 +0.000066 +0.000025 +0.000003 0.000000 0.000000 0.000000 +0.053416 +0.000001 0.000000 +0.000002 +0.000009 +0.000043 +0.000018 +0.000013 +0.000001 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000393 -0.000158 -0.000458 -0.000019 -0.000006 0.000000 0.000000 0.000000 @@ -5185,18 +5109,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5205,20 +5117,12 @@ 0.000000 0.000000 0.000000 -0.000055 -0.000057 -0.000897 -0.000030 -0.000003 -0.000556 -0.000010 -0.000188 -0.000265 -0.000008 0.000001 0.000000 0.000000 0.000000 +0.001977 +0.000004 0.000000 0.000000 0.000000 @@ -5233,20 +5137,11 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5254,8 +5149,15 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5263,6 +5165,15 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000004 +0.000073 +0.000066 +0.000007 +0.000001 +0.000005 +0.000816 +0.000018 0.000000 0.000000 0.000000 @@ -5273,13 +5184,11 @@ 0.000000 0.000000 0.000000 -0.000940 -0.000037 -0.000054 -0.000003 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5288,10 +5197,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 @@ -5299,8 +5204,7 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 -0.000000 -0.000000 @@ -5308,6 +5212,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5317,6 +5222,15 @@ 0.000000 0.000000 0.000000 +0.000006 +0.001960 +0.000832 +0.000019 +0.000084 +0.000031 +0.000030 +0.000010 +0.000002 0.000000 0.000000 0.000000 @@ -5324,14 +5238,6 @@ 0.000000 0.000000 0.000000 -0.002004 -0.000020 -0.000035 -0.000008 -0.000002 -0.000001 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -5341,6 +5247,10 @@ 0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5351,6 +5261,8 @@ 0.000000 0.000000 0.000000 +0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -5360,38 +5272,18 @@ 0.000000 0.000000 0.000000 -0.000004 -0.000033 -0.000051 -0.000235 -0.000187 -0.000244 -0.000144 -0.000041 -0.000122 -0.000070 -0.000030 -0.000004 0.000001 -0.000000 -0.000000 -0.015045 -0.000019 -0.000053 -0.000087 -0.000067 -0.000087 -0.000021 +0.000031 +0.000367 +0.000231 +0.000043 +0.000303 0.000012 -0.000001 -0.000000 +0.000003 0.000000 0.000000 0.000000 --0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5404,8 +5296,17 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5415,29 +5316,8 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000019 -0.000054 -0.000109 -0.002395 -0.001699 -0.000447 -0.000026 -0.000002 0.000000 0.000000 -0.070141 -0.000022 -0.000078 -0.000195 -0.000174 -0.000431 -0.000201 -0.000436 -0.000120 -0.000027 -0.000011 -0.000002 0.000000 0.000000 0.000000 @@ -5446,12 +5326,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5463,29 +5337,12 @@ 0.000000 0.000000 0.000000 -0.000065 -0.000097 -0.000103 -0.000048 -0.000068 -0.000242 -0.000127 -0.001242 -0.000416 -0.000062 -0.000004 0.000000 0.000000 0.000000 -0.179832 -0.000007 -0.000039 -0.000164 -0.000167 -0.000384 -0.000087 -0.000038 -0.000003 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5498,9 +5355,6 @@ 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5510,90 +5364,70 @@ 0.000000 0.000000 0.000000 -0.001998 0.000000 0.000000 0.000000 -0.000057 -0.000013 -0.000003 -0.000002 -0.000014 -0.000136 -0.000227 -0.003332 -0.001091 -0.000114 -0.000006 -0.000001 0.000000 0.000000 -0.594055 -0.000003 -0.000021 -0.000120 -0.000185 -0.000597 -0.000240 -0.000128 -0.000012 -0.000019 -0.000020 -0.000120 -0.000310 0.000000 -0.000576 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000312 +0.000182 +0.000469 +0.000020 +0.000007 +0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000097 +0.000084 +0.000485 +0.000437 +0.000006 +0.000490 +0.000006 +0.000142 +0.000227 +0.000007 +0.000002 0.000000 0.000000 0.000000 0.000000 -0.000003 -0.000004 -0.000003 -0.000005 -0.000022 -0.000246 -0.000183 -0.007266 -0.001698 -0.000101 -0.000005 0.000000 0.000000 0.000000 -0.949939 -0.000002 -0.000007 -0.000063 -0.000216 -0.001138 -0.001558 -0.001816 -0.000574 -0.002020 -0.000406 -0.001172 -0.000200 0.000000 -0.000105 0.000000 0.000000 0.000000 @@ -5606,6 +5440,10 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 -0.000000 @@ -5613,6 +5451,11 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 -0.000000 0.000000 0.000000 @@ -5621,30 +5464,46 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000003 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000889 0.000045 -0.000029 -0.000010 +0.000053 0.000003 0.000000 0.000000 0.000000 -0.134376 -0.000004 -0.000001 -0.000007 -0.000014 -0.000033 -0.000014 -0.000007 -0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -5658,7 +5517,6 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5670,30 +5528,36 @@ 0.000000 0.000000 0.000000 +0.001906 +0.000027 +0.000037 +0.000008 +0.000003 +0.000001 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000001 +-0.000000 0.000000 0.000000 -0.000001 -0.018439 -0.000044 -0.000007 -0.000027 -0.000052 -0.000043 -0.000013 -0.000002 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5701,48 +5565,85 @@ 0.000000 0.000000 0.000000 +0.000003 +0.000006 +0.000299 +0.000519 +0.000140 +0.000007 +0.000005 +0.000037 +0.000099 +0.000015 +0.000003 +0.000001 0.000000 0.000000 +0.012391 +0.000021 +0.000049 +0.000079 +0.000072 +0.000101 +0.000023 +0.000011 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000005 +0.000021 +0.000063 +0.001158 +0.001813 +0.000120 +0.000008 +0.000001 0.000000 0.000000 +0.046821 +0.000018 +0.000059 +0.000155 +0.000164 +0.000446 +0.000197 +0.000354 +0.000112 +0.000027 +0.000011 +0.000002 0.000000 0.000000 -0.000004 -0.000009 -0.000022 -0.000051 -0.000004 -0.000001 -0.000004 -0.005727 -0.000893 -0.000096 -0.000379 -0.000254 -0.000372 -0.000270 -0.000141 -0.000041 -0.000011 -0.000001 0.000000 0.000000 0.000000 @@ -5754,6 +5655,10 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 +0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5761,9 +5666,36 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000053 +0.000075 +0.000113 +0.000069 +0.000117 +0.000235 +0.000144 +0.001445 +0.001383 +0.000070 +0.000005 +0.000001 +0.000000 +0.000000 +0.186476 +0.000008 +0.000046 +0.000200 +0.000236 +0.000549 +0.000095 +0.000035 +0.000003 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 0.000000 @@ -5782,36 +5714,44 @@ 0.000000 0.000000 0.000000 +0.001926 0.000000 -0.000167 -0.000197 -0.000063 -0.000393 -0.000113 -0.000077 -0.000021 -0.000004 0.000000 0.000000 +0.000044 +0.000010 +0.000002 +0.000001 +0.000008 +0.000057 +0.000117 +0.001905 +0.001942 +0.000094 +0.000005 0.000000 0.000000 0.000000 +0.440803 +0.000003 +0.000019 +0.000109 +0.000179 +0.000615 +0.000189 +0.000106 +0.000012 +0.000018 +0.000017 +0.000108 +0.000295 0.000000 +0.000563 0.000000 --0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -5829,18 +5769,35 @@ 0.000000 0.000000 0.000000 +0.000004 0.000005 -0.000016 -0.000006 -0.000007 -0.000163 -0.003628 -0.001184 -0.000076 -0.000085 0.000004 +0.000006 +0.000029 +0.000267 +0.000202 +0.006036 +0.003811 +0.000137 +0.000005 +0.000000 0.000000 0.000000 +1.000000 +0.000002 +0.000011 +0.000106 +0.000334 +0.001951 +0.001433 +0.001726 +0.000510 +0.001606 +0.000433 +0.001325 +0.000200 +0.000000 +0.000107 0.000000 0.000000 0.000000 @@ -5854,43 +5811,37 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000003 +0.000033 +0.000043 +0.000011 +0.000002 0.000000 0.000000 0.000000 -0.000019 -0.000325 -0.000655 -0.000239 -0.000754 -0.000070 -0.000008 +0.168328 +0.000003 +0.000001 +0.000011 +0.000022 +0.000065 +0.000023 +0.000009 0.000001 0.000000 0.000000 @@ -5907,43 +5858,20 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000001 -0.000003 -0.000002 -0.000007 -0.000966 -0.000536 -0.001245 -0.000385 -0.000708 -0.000176 -0.000086 -0.000023 -0.000001 0.000000 0.000000 0.000000 @@ -5952,8 +5880,20 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000000 0.000000 0.000000 +0.000001 +0.020385 +0.000027 +0.000006 +0.000043 +0.000106 +0.000165 +0.000069 +0.000015 +0.000002 0.000000 0.000000 0.000000 @@ -5964,17 +5904,21 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -5986,15 +5930,25 @@ 0.000000 0.000000 0.000003 -0.001869 -0.000962 -0.002290 -0.000623 -0.000931 -0.000294 -0.000199 -0.000069 -0.000004 +0.000010 +0.000018 +0.000027 +0.000003 +0.000000 +0.000005 +0.006634 +0.000680 +0.000070 +0.000399 +0.000222 +0.000421 +0.000273 +0.000117 +0.000031 +0.000009 +0.000001 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -6005,7 +5959,11 @@ -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 @@ -6016,14 +5974,8 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -6034,26 +5986,24 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000907 +0.000315 +0.000072 +0.000464 +0.000106 +0.000092 +0.000022 +0.000004 +0.000000 0.000000 0.000000 0.000000 -0.001492 -0.000352 -0.001102 -0.000440 -0.000394 -0.000145 -0.000173 -0.000040 -0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -6078,29 +6028,21 @@ 0.000000 0.000000 0.000000 -0.000022 -0.000294 -0.000092 -0.000286 -0.000219 -0.000042 -0.000004 0.000000 0.000000 -0.000001 -0.003591 -0.000144 -0.000263 -0.000156 -0.000170 -0.000230 -0.000333 -0.000339 -0.000022 -0.000001 0.000000 0.000000 0.000000 +0.000003 +0.000009 +0.000005 +0.000003 +0.000140 +0.003916 +0.000752 +0.000053 +0.000073 +0.000003 0.000000 0.000000 0.000000 @@ -6108,18 +6050,8 @@ 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -6132,6 +6064,10 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -6139,18 +6075,11 @@ 0.000000 0.000000 0.000000 -0.009893 -0.000089 -0.000240 -0.000398 -0.000387 -0.000310 -0.000055 -0.000012 -0.000001 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -6159,6 +6088,14 @@ 0.000000 0.000000 0.000000 +0.000016 +0.000378 +0.000593 +0.000211 +0.000713 +0.000063 +0.000008 +0.000001 0.000000 0.000000 0.000000 @@ -6171,33 +6108,46 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000002 -0.000001 0.000000 0.000000 0.000000 0.000000 -0.012411 -0.000018 -0.000110 -0.000445 +0.000001 +0.000004 +0.000004 +0.000005 +0.000808 0.000658 -0.000730 -0.000097 -0.000015 +0.001175 +0.000355 +0.000687 +0.000165 +0.000087 +0.000018 +0.000001 0.000000 0.000000 0.000000 @@ -6207,49 +6157,29 @@ 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000047 -0.000023 -0.000012 -0.000763 -0.000134 -0.000044 -0.000014 -0.000006 -0.000292 -0.000430 -0.000064 -0.000003 0.000000 0.000000 0.000000 -0.116379 -0.000028 -0.000244 -0.001383 -0.002240 -0.002736 -0.000345 -0.000047 -0.000001 0.000000 0.000000 0.000000 @@ -6258,6 +6188,17 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000002 +0.001571 +0.001103 +0.002210 +0.000577 +0.000927 +0.000284 +0.000205 +0.000058 +0.000002 0.000000 0.000000 0.000000 @@ -6270,36 +6211,26 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000001 -0.000012 -0.000008 -0.000016 -0.000761 -0.000409 -0.000343 -0.000289 -0.000116 -0.001249 -0.000644 -0.000033 -0.000001 0.000000 0.000000 0.000000 -0.117562 -0.000003 -0.000028 -0.000158 -0.000223 -0.000231 -0.000034 -0.000008 0.000000 0.000000 0.000000 @@ -6310,59 +6241,36 @@ 0.000000 0.000000 0.000000 +0.001320 +0.000400 +0.001069 +0.000409 +0.000411 +0.000148 +0.000175 +0.000032 +0.000001 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 -0.000291 -0.000044 -0.000149 -0.000271 -0.000158 -0.000581 -0.000555 -0.000715 -0.001103 -0.000538 -0.006211 -0.003692 -0.000190 -0.000005 0.000000 0.000000 0.000000 -1.000000 -0.000004 -0.000025 -0.000301 -0.000764 -0.001591 -0.001134 -0.001484 -0.000644 -0.000997 -0.000343 -0.001234 -0.000195 -0.000231 -0.000159 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -6374,41 +6282,30 @@ 0.000000 0.000000 0.000000 +0.000019 +0.000230 +0.000077 +0.000434 +0.000134 +0.000030 +0.000005 0.000000 0.000000 0.000000 -0.000023 -0.000028 -0.000122 -0.000428 +0.003336 +0.000179 +0.000285 +0.000151 +0.000180 0.000247 -0.000164 -0.000064 -0.000040 -0.000090 -0.000289 -0.003190 -0.001502 -0.000051 +0.000341 +0.000276 +0.000021 0.000001 0.000000 0.000000 0.000000 -0.647080 -0.000009 -0.000009 -0.000076 -0.000168 -0.000241 -0.000276 -0.000285 -0.000206 -0.000220 -0.000070 -0.000142 -0.000079 -0.000045 -0.000002 +0.000000 0.000000 0.000000 0.000000 @@ -6420,13 +6317,16 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 0.000000 @@ -6439,33 +6339,29 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000001 0.000000 0.000000 0.000000 0.000000 -0.075132 -0.000016 -0.000012 -0.000072 -0.000118 -0.000269 -0.000406 -0.000365 -0.000141 -0.000049 -0.000005 -0.000003 +0.008575 +0.000088 +0.000208 +0.000307 +0.000349 +0.000321 +0.000056 +0.000010 0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -6475,13 +6371,14 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -6491,30 +6388,28 @@ 0.000000 0.000000 0.000000 +0.000002 0.000000 0.000000 0.000000 0.000000 0.000000 -0.008431 -0.000062 -0.000050 -0.000209 -0.000178 -0.000214 -0.000129 -0.000037 -0.000005 -0.000001 +0.010066 +0.000017 +0.000102 +0.000363 +0.000567 +0.000689 +0.000082 +0.000011 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -6527,45 +6422,90 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000079 +0.000182 +0.000014 +0.000482 +0.000185 +0.000045 +0.000003 +0.000001 +0.000023 +0.000105 +0.000007 +0.000001 0.000000 0.000000 0.000000 +0.094234 +0.000022 +0.000212 +0.001113 +0.001837 +0.002336 +0.000225 +0.000026 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000003 -0.000009 -0.000001 0.000000 -0.000003 -0.005625 -0.000422 -0.000356 -0.001553 -0.000623 -0.000498 -0.000211 -0.000009 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 +0.000001 +0.000009 +0.000005 +0.000006 +0.000702 +0.000670 +0.000321 +0.000146 +0.000100 +0.000846 +0.001195 +0.000022 +0.000001 0.000000 0.000000 0.000000 +0.123085 +0.000004 +0.000041 +0.000254 +0.000484 +0.000806 +0.000146 +0.000049 +0.000002 +0.000001 0.000000 0.000000 0.000000 @@ -6577,13 +6517,9 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -6594,18 +6530,38 @@ 0.000000 0.000000 0.000000 +0.000514 +0.000041 +0.000062 +0.000095 +0.000061 +0.000631 +0.001011 +0.000734 +0.000738 +0.000483 +0.003526 +0.006034 +0.000142 +0.000004 0.000000 0.000000 0.000000 -0.000016 -0.004417 -0.001354 -0.000799 -0.002229 -0.000363 -0.000116 -0.000020 -0.000001 +0.826521 +0.000003 +0.000030 +0.000350 +0.000947 +0.002569 +0.001471 +0.001393 +0.000494 +0.000743 +0.000309 +0.001183 +0.000160 +0.000229 +0.000225 0.000000 0.000000 0.000000 @@ -6619,45 +6575,46 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +0.000044 +0.000032 +0.000056 +0.000342 +0.000195 +0.000246 +0.000133 +0.000050 +0.000076 +0.000280 +0.001989 +0.003434 +0.000079 +0.000002 0.000000 0.000000 0.000000 +0.738926 +0.000008 +0.000012 +0.000129 +0.000313 +0.000661 +0.000509 +0.000339 +0.000210 +0.000225 +0.000074 +0.000133 +0.000052 +0.000028 +0.000003 0.000000 0.000000 -0.000001 -0.000024 -0.001111 -0.000461 -0.000244 -0.000685 -0.000280 -0.000197 -0.000087 -0.000013 -0.000001 0.000000 0.000000 0.000000 @@ -6667,49 +6624,43 @@ 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000002 +0.000001 0.000000 0.000000 -0.000065 -0.000669 -0.000896 -0.000880 -0.003021 -0.001060 -0.000519 -0.000130 -0.000007 0.000000 0.000000 +0.090794 +0.000011 +0.000008 +0.000068 +0.000099 +0.000335 +0.000421 +0.000310 +0.000111 +0.000041 +0.000004 +0.000002 0.000000 0.000000 0.000000 @@ -6720,12 +6671,14 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -6733,10 +6686,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -6749,29 +6698,38 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000258 -0.000553 -0.000939 -0.000554 -0.001173 -0.000449 -0.000180 -0.000033 -0.000001 -0.000000 0.000000 0.000000 +0.008157 +0.000036 +0.000031 +0.000185 +0.000128 +0.000235 +0.000117 +0.000030 +0.000004 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -6781,35 +6739,34 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000003 +0.000005 +0.000001 +0.000000 +0.000002 +0.004850 +0.000278 +0.000242 +0.001350 +0.000447 +0.000539 +0.000203 +0.000009 +0.000001 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000121 -0.000226 -0.000762 -0.000581 -0.001328 -0.000959 -0.000767 -0.000418 -0.000012 0.000000 0.000000 0.000000 @@ -6820,25 +6777,15 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 @@ -6851,18 +6798,18 @@ 0.000000 0.000000 0.000000 -0.000002 -0.001843 -0.000573 -0.001033 -0.000564 -0.000687 -0.000367 -0.000089 -0.000015 0.000000 0.000000 0.000000 +0.000010 +0.004424 +0.001078 +0.000706 +0.002226 +0.000322 +0.000138 +0.000019 +0.000000 0.000000 0.000000 0.000000 @@ -6870,6 +6817,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 -0.000000 @@ -6889,8 +6837,13 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -6899,22 +6852,23 @@ 0.000000 0.000000 0.000000 +0.000010 +0.000910 +0.000403 +0.000233 +0.000384 +0.000036 +0.000006 0.000000 0.000000 0.000000 0.000000 -0.002296 -0.000238 -0.000629 -0.000738 -0.001167 -0.000952 -0.000166 -0.000024 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -6928,14 +6882,11 @@ -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 -0.000000 0.000000 0.000000 @@ -6945,23 +6896,24 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000014 -0.000230 -0.000370 -0.000034 -0.000002 0.000000 0.000000 0.000000 -0.006583 -0.000118 -0.000304 -0.000706 -0.001067 -0.000786 -0.000121 -0.000014 +0.000000 +0.000000 +0.000000 +0.000001 +0.000123 +0.001277 +0.000973 +0.000785 +0.002807 +0.001106 +0.000714 +0.000142 +0.000004 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -6970,6 +6922,10 @@ 0.000000 0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -6982,7 +6938,7 @@ 0.000000 0.000000 0.000000 --0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -6998,27 +6954,19 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000004 +0.000171 +0.000685 +0.000959 +0.000611 +0.001587 +0.000627 +0.000278 +0.000038 0.000001 0.000000 0.000000 0.000000 0.000000 -0.013849 -0.000033 -0.000195 -0.000786 -0.001457 -0.001131 -0.000129 -0.000009 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 @@ -7030,11 +6978,13 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 -0.000000 0.000000 0.000000 @@ -7044,26 +6994,28 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000007 -0.000098 -0.000328 -0.000067 -0.000495 -0.000188 -0.000015 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000092 +0.000249 +0.000717 +0.000519 +0.001322 +0.000943 +0.000786 +0.000320 +0.000011 +0.000000 0.000000 -0.089848 -0.000044 -0.000380 -0.001286 -0.001709 -0.000677 -0.000067 -0.000006 0.000000 0.000000 0.000000 @@ -7077,10 +7029,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -7090,36 +7038,49 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000008 -0.000015 -0.000003 -0.000000 0.000000 0.000000 0.000000 -0.097454 -0.000006 -0.000096 -0.000292 -0.000772 -0.000855 -0.000519 -0.000288 -0.000070 -0.000012 -0.000001 0.000001 +0.001652 +0.000632 +0.001049 +0.000498 +0.000664 +0.000362 +0.000088 +0.000012 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7132,6 +7093,8 @@ -0.000000 -0.000000 -0.000000 +0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -7143,42 +7106,36 @@ 0.000000 0.000000 0.000000 -0.000291 -0.000261 -0.000141 -0.002580 -0.000423 -0.000122 -0.000176 -0.000090 -0.002016 -0.001892 -0.000095 -0.000002 0.000000 +0.002042 +0.000254 +0.000710 +0.000662 +0.001131 +0.000969 +0.000162 +0.000019 0.000000 0.000000 -0.703629 -0.000008 -0.000097 -0.000477 -0.001392 -0.001983 -0.002496 -0.001959 -0.000649 -0.000504 -0.000112 -0.000385 -0.000615 -0.000706 -0.000207 0.000000 -0.000113 -0.000042 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7186,47 +7143,33 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000011 +0.000447 +0.000140 +0.000020 +0.000003 0.000000 0.000000 0.000000 -0.000053 -0.000070 -0.000059 -0.000828 -0.000307 -0.000187 -0.000184 -0.000116 -0.001619 -0.002329 -0.000049 -0.000001 +0.003694 +0.000071 +0.000224 +0.000480 +0.000855 +0.000865 +0.000118 +0.000011 0.000000 0.000000 0.000000 -0.677403 -0.000009 -0.000038 -0.000573 -0.001150 -0.001834 -0.002402 -0.001965 -0.000697 -0.000416 -0.000052 -0.000047 -0.000059 -0.000130 -0.000229 0.000000 -0.000845 -0.000829 0.000000 0.000000 0.000000 @@ -7234,6 +7177,12 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7248,48 +7197,77 @@ 0.000000 0.000000 0.000000 -0.000001 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000003 0.000002 -0.000006 -0.000015 -0.000021 -0.000455 -0.000826 -0.000013 0.000000 0.000000 0.000000 0.000000 -0.083847 +0.000000 +0.014583 +0.000029 +0.000171 +0.000592 +0.001140 +0.001191 +0.000127 0.000008 -0.000019 -0.000300 -0.000379 -0.000464 -0.000407 -0.000153 -0.000021 -0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +0.000000 +-0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000002 +0.000008 +0.000096 +0.000246 +0.000051 +0.000667 +0.000082 +0.000004 0.000000 0.000000 0.000000 0.000000 +0.071939 +0.000034 +0.000377 +0.001237 +0.001626 +0.000878 +0.000082 +0.000006 0.000000 0.000000 0.000000 @@ -7297,12 +7275,27 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7311,27 +7304,31 @@ 0.000000 0.000000 0.000000 -0.006694 -0.000033 -0.000066 -0.000370 -0.000369 -0.000383 -0.000299 -0.000061 0.000005 +0.000011 +0.000002 0.000000 0.000000 0.000000 0.000000 +0.103493 +0.000006 +0.000112 +0.000360 +0.000876 +0.001334 +0.000593 +0.000219 +0.000059 +0.000010 +0.000001 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -7342,37 +7339,57 @@ -0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000063 +0.000088 +0.000043 +0.000760 +0.000250 +0.000552 +0.000220 +0.000050 +0.000763 +0.001134 +0.000037 +0.000001 0.000000 0.000000 0.000000 +0.553662 +0.000006 +0.000099 +0.000436 +0.001109 +0.002317 +0.002069 +0.001243 +0.000532 +0.000342 +0.000058 +0.000115 +0.000098 +0.000070 +0.000021 0.000000 +0.000003 +0.000001 0.000000 0.000000 0.000000 0.000000 -0.000001 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000002 -0.003803 -0.000230 -0.000370 -0.001227 -0.000525 -0.000426 -0.000493 -0.000144 -0.000017 -0.000003 -0.000001 0.000000 0.000000 0.000000 @@ -7381,21 +7398,44 @@ 0.000000 0.000000 0.000000 +0.000660 +0.000921 +0.000164 +0.001054 +0.000443 +0.000327 +0.000223 +0.000154 +0.000930 +0.003886 +0.000072 +0.000002 0.000000 0.000000 0.000000 +0.747322 +0.000008 +0.000047 +0.000721 +0.001177 +0.002932 +0.002692 +0.001567 +0.000894 +0.000736 +0.000178 +0.000226 +0.000274 +0.000398 +0.000568 0.000000 --0.000000 --0.000000 --0.000000 +0.000979 +0.000839 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -7412,18 +7452,31 @@ 0.000000 0.000000 0.000000 -0.000003 -0.000739 -0.000169 -0.000286 -0.001049 -0.000495 -0.000256 -0.000097 -0.000010 +0.000001 +0.000004 +0.000011 +0.000021 +0.000035 +0.000246 +0.001914 +0.000042 +0.000002 0.000000 0.000000 0.000000 +0.101987 +0.000006 +0.000017 +0.000345 +0.000326 +0.000795 +0.000766 +0.000399 +0.000148 +0.000013 +0.000003 +0.000001 +0.000000 0.000000 0.000000 0.000000 @@ -7435,26 +7488,43 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +0.009361 +0.000025 +0.000051 +0.000454 +0.000260 +0.000429 +0.000261 +0.000054 +0.000008 +0.000001 0.000000 0.000000 0.000000 @@ -7463,27 +7533,15 @@ 0.000000 0.000000 0.000000 -0.000020 -0.001336 -0.000738 -0.000912 -0.002355 -0.000659 -0.000163 -0.000025 -0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -7491,6 +7549,41 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000003 +0.000001 +0.000001 +0.000000 +0.000000 +0.000002 +0.002996 +0.000142 +0.000266 +0.001104 +0.000387 +0.000497 +0.000422 +0.000094 +0.000029 +0.000005 +0.000001 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -7498,14 +7591,19 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -7514,18 +7612,20 @@ 0.000000 0.000000 0.000000 -0.000012 -0.000255 -0.000338 -0.000496 -0.001361 -0.000508 -0.000117 -0.000018 0.000000 0.000000 0.000000 0.000000 +0.000003 +0.001338 +0.000227 +0.000339 +0.001174 +0.000473 +0.000332 +0.000074 +0.000004 +0.000001 0.000000 0.000000 0.000000 @@ -7533,27 +7633,32 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7562,19 +7667,14 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000004 -0.000470 -0.001563 -0.001803 -0.001630 -0.003675 -0.001579 -0.000499 -0.000157 -0.000003 -0.000000 +0.000012 +0.001159 +0.000456 +0.000516 +0.001330 +0.000365 +0.000115 +0.000012 0.000000 0.000000 0.000000 @@ -7588,6 +7688,11 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -7600,7 +7705,6 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 0.000000 @@ -7614,16 +7718,14 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000004 -0.000740 -0.001085 -0.001323 -0.001051 -0.001842 -0.000944 -0.000216 -0.000039 +0.000012 +0.000495 +0.000549 +0.000754 +0.002132 +0.000787 +0.000207 +0.000022 0.000000 0.000000 0.000000 @@ -7636,13 +7738,19 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7650,9 +7758,6 @@ 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -7663,19 +7768,19 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000246 +0.001840 +0.001688 +0.001442 +0.003561 +0.001494 +0.000515 +0.000116 +0.000002 0.000000 0.000000 0.000000 -0.000005 -0.000620 -0.000202 -0.000154 -0.000081 -0.000266 -0.000473 -0.000204 -0.000063 -0.000001 0.000000 0.000000 0.000000 @@ -7687,19 +7792,10 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -7711,22 +7807,27 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000436 -0.000148 -0.000234 -0.000402 -0.000857 -0.000840 -0.000165 -0.000021 0.000000 +0.000329 +0.001187 +0.001280 +0.000841 +0.001466 +0.000661 +0.000160 +0.000024 0.000000 0.000000 0.000000 @@ -7737,20 +7838,9 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -7759,8 +7849,18 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7768,17 +7868,18 @@ 0.000000 0.000000 0.000000 -0.000001 -0.001595 -0.000118 -0.000276 -0.000559 -0.000905 -0.000557 -0.000115 -0.000015 0.000000 0.000000 +0.000001 +0.000637 +0.000188 +0.000264 +0.000233 +0.000642 +0.000723 +0.000233 +0.000052 +0.000001 0.000000 0.000000 0.000000 @@ -7794,12 +7895,14 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 @@ -7819,18 +7922,14 @@ 0.000000 0.000000 0.000000 -0.000000 -0.003391 -0.000069 -0.000376 -0.001364 -0.002133 -0.000768 -0.000163 -0.000016 -0.000000 -0.000000 -0.000000 +0.000501 +0.000095 +0.000248 +0.000330 +0.000754 +0.000872 +0.000160 +0.000015 0.000000 0.000000 0.000000 @@ -7847,21 +7946,21 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -7871,14 +7970,17 @@ 0.000000 0.000000 0.000000 -0.008335 -0.000019 -0.000157 -0.000334 -0.000368 -0.000078 -0.000020 -0.000003 +0.000000 +0.000000 +0.000001 +0.001566 +0.000106 +0.000311 +0.000450 +0.000730 +0.000667 +0.000125 +0.000011 0.000000 0.000000 0.000000 @@ -7886,8 +7988,10 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7895,6 +7999,12 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7903,6 +8013,7 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7913,32 +8024,29 @@ 0.000000 0.000000 0.000000 -0.000024 -0.000086 -0.000540 -0.000387 -0.000009 +0.002690 +0.000043 +0.000262 +0.000786 +0.001207 +0.000804 +0.000146 +0.000010 0.000000 0.000000 0.000000 0.000000 -0.061457 -0.000047 -0.000563 -0.001711 -0.002933 -0.001244 -0.000984 -0.000359 -0.000033 -0.000003 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7948,6 +8056,8 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -7955,34 +8065,23 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000002 -0.000008 -0.000001 0.000000 0.000000 0.000000 0.000000 -0.060012 -0.000005 -0.000091 -0.000359 -0.000969 -0.000743 -0.001063 -0.000746 -0.000135 -0.000030 +0.006390 +0.000016 +0.000208 +0.000483 +0.000544 +0.000237 +0.000050 0.000004 0.000000 0.000000 @@ -7990,11 +8089,10 @@ 0.000000 0.000000 0.000000 +0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -8008,43 +8106,36 @@ 0.000000 0.000000 0.000000 +-0.000000 +0.000000 0.000000 0.000000 -0.000397 -0.000564 -0.000644 -0.000543 -0.000662 -0.000326 -0.000069 -0.000365 -0.000441 -0.000119 -0.000002 0.000000 0.000000 0.000000 -0.693806 -0.000025 -0.000242 -0.001833 -0.003470 -0.003019 -0.003782 -0.002401 -0.000622 -0.000367 -0.000214 -0.000081 -0.000090 -0.000061 0.000000 0.000000 0.000000 0.000000 +0.000004 +0.000040 +0.000893 +0.000058 +0.000003 +0.000000 0.000000 0.000000 0.000000 +0.056783 +0.000039 +0.000614 +0.001750 +0.002367 +0.001732 +0.000896 +0.000211 +0.000044 +0.000004 0.000000 0.000000 0.000000 @@ -8053,14 +8144,25 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8068,41 +8170,78 @@ 0.000000 0.000000 0.000000 -0.000001 +0.000004 +0.000002 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.043353 +0.000004 +0.000097 +0.000378 +0.000800 +0.001092 +0.001083 +0.000523 +0.000211 +0.000039 0.000005 0.000001 0.000000 0.000000 0.000000 0.000000 -0.221393 -0.000017 -0.000122 -0.001024 -0.001649 -0.001452 -0.001448 -0.000542 -0.000040 -0.000003 -0.000001 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000181 +0.000316 +0.000424 +0.000367 +0.000498 +0.000220 +0.000052 +0.000555 +0.000462 +0.000065 +0.000002 0.000000 0.000000 --0.000000 --0.000000 0.000000 +0.627673 +0.000021 +0.000279 +0.002045 +0.002892 +0.004151 +0.003653 +0.001767 +0.000985 +0.000426 +0.000264 +0.000062 +0.000055 +0.000034 0.000000 0.000000 0.000000 @@ -8111,49 +8250,53 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000012 -0.000101 -0.000002 0.000000 0.000000 0.000000 0.000000 -0.018310 -0.000015 -0.000066 -0.000501 -0.000646 -0.000524 -0.000422 -0.000093 -0.000007 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000250 +0.000265 +0.000219 +0.000113 +0.000125 +0.000019 +0.000001 +0.000003 +0.000021 +0.000003 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 +0.261980 +0.000017 +0.000137 +0.001254 +0.001367 +0.001944 +0.001379 +0.000476 +0.000106 +0.000007 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8161,37 +8304,41 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000013 -0.000001 0.000000 0.000000 0.000000 0.000000 -0.007600 -0.000091 -0.000235 -0.000798 -0.000458 -0.000350 -0.000461 -0.000239 -0.000067 -0.000012 -0.000010 -0.000007 -0.000004 +0.000001 +0.000096 +0.000003 +0.000000 +0.000000 0.000000 0.000000 +0.020005 +0.000009 +0.000044 +0.000297 +0.000181 +0.000143 +0.000037 +0.000003 +0.000000 0.000000 0.000000 0.000000 @@ -8204,16 +8351,21 @@ 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8227,18 +8379,21 @@ 0.000000 0.000000 0.000000 -0.000001 -0.002109 -0.000183 -0.000482 -0.001524 -0.000947 -0.000688 -0.000251 -0.000023 -0.000001 0.000000 0.000000 +0.006914 +0.000062 +0.000195 +0.000977 +0.000694 +0.001061 +0.000602 +0.000177 +0.000178 +0.000026 +0.000011 +0.000006 +0.000004 0.000000 0.000000 0.000000 @@ -8254,13 +8409,11 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -8273,23 +8426,32 @@ 0.000000 0.000000 0.000000 +0.000027 +0.000009 +0.000003 +0.000001 +0.000000 +0.000001 +0.002459 +0.000150 +0.000470 +0.001723 +0.000913 +0.000967 +0.000201 +0.000013 +0.000003 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000257 -0.000118 -0.000379 -0.001374 -0.000883 -0.000625 -0.000466 -0.000036 -0.000001 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -8304,8 +8466,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -8313,11 +8473,8 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -8325,18 +8482,23 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000946 +0.000188 +0.000434 +0.001444 +0.000796 +0.000778 +0.000343 +0.000024 +0.000002 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 -0.000004 -0.000004 -0.000006 -0.000138 -0.002500 -0.000747 -0.000716 -0.001366 -0.000582 -0.000122 -0.000025 0.000000 0.000000 0.000000 @@ -8357,18 +8519,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -8380,21 +8530,17 @@ 0.000000 0.000000 0.000001 -0.000088 -0.001017 -0.000654 -0.001034 -0.002591 -0.001763 -0.000630 -0.000472 -0.000030 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000005 +0.000005 +0.000001 +0.000057 +0.002667 +0.000632 +0.000628 +0.001280 +0.000517 +0.000135 +0.000019 0.000000 0.000000 0.000000 @@ -8403,6 +8549,12 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000000 @@ -8410,9 +8562,14 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -8427,19 +8584,18 @@ 0.000000 0.000000 0.000000 +0.000035 +0.001058 +0.000612 +0.000910 +0.002575 +0.001637 +0.000690 +0.000373 +0.000041 0.000000 0.000000 0.000000 -0.000001 -0.000213 -0.001486 -0.000976 -0.001171 -0.002204 -0.001564 -0.000495 -0.000164 -0.000005 0.000000 0.000000 0.000000 @@ -8451,15 +8607,17 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -8477,20 +8635,15 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000021 -0.000181 -0.000205 -0.000445 -0.001106 -0.001409 -0.000574 -0.000195 -0.000005 +0.000078 +0.001543 +0.000976 +0.001103 +0.002147 +0.001436 +0.000514 +0.000128 +0.000007 0.000000 0.000000 0.000000 @@ -8503,10 +8656,8 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8515,7 +8666,15 @@ -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -8527,21 +8686,15 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000002 -0.000051 -0.000539 -0.000353 -0.000077 -0.000148 -0.000581 -0.000897 +0.000008 +0.000153 +0.000215 +0.000369 +0.000839 +0.000911 0.000376 -0.000079 -0.000001 +0.000098 +0.000005 0.000000 0.000000 0.000000 @@ -8549,22 +8702,13 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 -0.000000 @@ -8575,49 +8719,37 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000003 -0.000316 -0.000170 -0.000326 -0.001036 -0.001630 -0.000528 -0.000115 -0.000016 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000004 +0.000021 +0.000110 +0.000509 +0.000906 +0.000382 +0.000049 +0.000001 +0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -8628,23 +8760,16 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000001 -0.000467 -0.000091 -0.000247 -0.000617 -0.000930 -0.000346 -0.000206 -0.000190 -0.000011 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8652,8 +8777,6 @@ 0.000000 -0.000000 -0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -8664,12 +8787,17 @@ 0.000000 0.000000 0.000000 +0.000003 +0.001225 +0.000204 +0.000559 +0.001005 +0.001496 +0.001098 +0.000322 +0.000034 +0.000002 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -8679,6 +8807,12 @@ 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8686,25 +8820,34 @@ 0.000000 0.000000 0.000000 -0.000756 -0.000036 -0.000169 -0.000648 -0.001856 -0.001014 -0.001247 -0.001363 -0.000146 -0.000010 -0.000001 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000524 +0.000078 +0.000314 +0.000536 +0.000670 +0.000518 +0.000228 +0.000091 +0.000014 0.000000 0.000000 0.000000 @@ -8712,6 +8855,14 @@ 0.000000 0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -8729,6 +8880,9 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8736,28 +8890,34 @@ 0.000000 0.000000 0.000000 -0.000000 -0.004343 -0.000027 -0.000217 -0.000272 -0.000325 -0.000236 -0.000787 -0.001568 -0.000411 -0.000071 -0.000018 -0.000003 -0.000001 +0.000662 +0.000034 +0.000208 +0.000641 +0.001328 +0.001455 +0.001046 +0.000464 +0.000108 +0.000004 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8769,8 +8929,6 @@ 0.000000 -0.000000 -0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -8783,23 +8941,21 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000000 +0.003137 +0.000018 +0.000195 +0.000280 +0.000236 +0.000323 +0.000730 +0.000967 +0.000893 +0.000134 +0.000025 +0.000003 0.000001 0.000000 0.000000 -0.026125 -0.000047 -0.000540 -0.001219 -0.002288 -0.001178 -0.002330 -0.001328 -0.000120 -0.000009 -0.000001 -0.000000 0.000000 0.000000 0.000000 @@ -8812,14 +8968,10 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 @@ -8829,26 +8981,37 @@ -0.000000 -0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000001 +0.000001 +0.000000 +0.000000 +0.021264 +0.000037 +0.000542 +0.001219 +0.001535 +0.001903 +0.002153 +0.000819 +0.000252 +0.000016 +0.000001 +0.000000 0.000000 0.000000 0.000000 -0.023337 -0.000008 -0.000129 -0.000366 -0.000802 -0.000521 -0.001184 -0.000520 -0.000038 -0.000003 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8857,8 +9020,17 @@ 0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8871,6 +9043,17 @@ 0.000000 0.000000 0.000000 +0.024876 +0.000007 +0.000152 +0.000443 +0.000655 +0.000851 +0.001197 +0.000423 +0.000138 +0.000009 +0.000001 0.000000 0.000000 0.000000 @@ -8878,35 +9061,23 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000005 -0.000022 -0.000082 -0.000664 -0.000073 -0.000004 -0.000001 -0.000000 -0.000001 -0.312602 -0.000081 -0.001121 -0.004519 -0.005270 -0.001837 -0.002019 -0.000760 -0.000059 -0.000008 -0.000026 -0.000152 -0.000564 -0.000000 0.000000 -0.000294 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -8914,8 +9085,31 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000030 +0.000686 +0.000141 +0.000034 +0.000004 +0.000001 +0.000001 +0.000001 +0.279158 +0.000065 +0.001195 +0.004690 +0.004097 +0.002698 +0.001881 +0.000561 +0.000212 +0.000020 +0.000024 +0.000089 +0.000522 0.000000 0.000000 +0.000349 0.000000 0.000000 0.000000 @@ -8923,33 +9117,19 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 -0.000003 -0.000011 -0.000003 0.000000 0.000000 0.000000 0.000000 -0.107504 -0.000034 -0.000379 -0.001992 -0.002214 -0.000948 -0.000620 -0.000135 -0.000005 0.000000 0.000000 0.000000 @@ -8958,9 +9138,23 @@ 0.000000 0.000000 0.000000 +0.000008 +0.000019 +0.000006 +0.000001 0.000000 0.000000 0.000000 +0.119827 +0.000032 +0.000462 +0.002435 +0.002000 +0.001492 +0.000520 +0.000092 +0.000016 +0.000001 0.000000 0.000000 0.000000 @@ -8974,15 +9168,18 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -8992,14 +9189,6 @@ 0.000000 0.000000 0.000000 -0.009172 -0.000021 -0.000121 -0.000478 -0.000359 -0.000146 -0.000048 -0.000005 0.000000 0.000000 0.000000 @@ -9007,11 +9196,21 @@ 0.000000 0.000000 0.000000 +0.013465 +0.000019 +0.000149 +0.000623 +0.000347 +0.000227 +0.000039 +0.000003 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -9021,6 +9220,9 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -9037,23 +9239,23 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000000 -0.000001 0.000000 0.000000 0.000001 -0.004347 -0.000090 -0.000254 -0.000738 -0.000590 -0.000731 -0.000445 -0.000046 +0.000001 +0.000001 0.000000 0.000000 0.000000 +0.004222 +0.000057 +0.000220 +0.000690 +0.000461 +0.000947 +0.000314 +0.000027 +0.000001 0.000000 0.000000 0.000000 @@ -9066,9 +9268,11 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -9088,48 +9292,21 @@ 0.000000 0.000000 0.000000 -0.000008 -0.000002 -0.000002 -0.000001 -0.000001 +0.000007 +0.000006 0.000004 -0.002703 -0.000146 -0.000351 -0.000673 -0.000208 -0.000036 -0.000005 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000001 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000001 +0.002626 +0.000113 +0.000332 +0.000656 +0.000169 +0.000047 +0.000003 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -9137,22 +9314,6 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000086 -0.000786 -0.000063 -0.000022 -0.000003 -0.000001 -0.000020 -0.001136 -0.000210 -0.000488 -0.001317 -0.000771 -0.000204 -0.000066 -0.000001 0.000000 0.000000 0.000000 @@ -9174,18 +9335,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -9194,15 +9343,19 @@ 0.000000 0.000000 0.000000 +0.000684 +0.000185 +0.000049 +0.000004 0.000000 -0.000002 -0.000126 -0.000124 -0.000479 -0.001546 -0.001407 -0.000341 -0.000114 +0.000005 +0.001174 +0.000160 +0.000460 +0.001296 +0.000652 +0.000235 +0.000051 0.000002 0.000000 0.000000 @@ -9216,10 +9369,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -9229,11 +9378,12 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -9242,32 +9392,7 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000004 -0.000004 -0.000006 -0.000099 -0.001161 -0.000208 -0.000349 -0.000841 -0.001800 -0.001154 -0.001463 -0.000156 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -9275,6 +9400,14 @@ 0.000000 0.000000 0.000000 +0.000084 +0.000072 +0.000313 +0.001140 +0.000977 +0.000310 +0.000075 +0.000003 0.000000 0.000000 0.000000 @@ -9287,25 +9420,50 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000004 +0.000008 +0.000010 +0.000001 +0.000031 +0.001243 +0.000239 +0.000450 +0.001180 +0.001951 +0.001387 +0.001169 +0.000261 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000017 -0.000391 -0.000236 -0.000591 -0.000920 -0.001230 -0.000492 -0.000253 -0.000008 0.000000 0.000000 0.000000 @@ -9325,18 +9483,15 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -9346,32 +9501,28 @@ 0.000000 0.000000 0.000000 +0.000005 +0.000306 +0.000188 +0.000388 +0.000638 +0.000835 +0.000446 +0.000156 +0.000012 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 -0.000001 -0.000145 -0.000975 -0.000335 -0.000561 -0.000704 -0.000796 -0.000429 -0.000190 -0.000004 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -9382,8 +9533,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -9399,19 +9548,20 @@ 0.000000 0.000000 0.000000 -0.000005 -0.000027 -0.000073 -0.000368 -0.000816 -0.000422 -0.000207 -0.000148 -0.000005 0.000000 0.000000 0.000000 0.000000 +0.000025 +0.000486 +0.000300 +0.000563 +0.000845 +0.001015 +0.000589 +0.000135 +0.000007 +0.000000 0.000000 0.000000 0.000000 @@ -9420,12 +9570,10 @@ 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -9438,6 +9586,8 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -9449,76 +9599,38 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000042 -0.000036 -0.000065 -0.000285 -0.000719 -0.000366 -0.000841 -0.001583 -0.000187 -0.000014 -0.000001 -0.000000 0.000000 0.000000 0.000000 0.000000 +0.000090 +0.000180 +0.000405 +0.000658 +0.000746 +0.000552 +0.000256 +0.000079 +0.000008 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000002 -0.000077 -0.001333 -0.000330 -0.000565 -0.001401 -0.002954 -0.000856 -0.001567 -0.002115 -0.000174 -0.000009 -0.000002 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -9530,8 +9642,6 @@ 0.000000 0.000000 0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -9540,30 +9650,25 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000003 +0.000024 +0.000133 +0.000397 +0.000789 +0.000417 +0.000194 +0.000024 +0.000001 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000012 -0.000092 -0.000106 -0.000629 -0.001091 -0.000127 -0.000009 -0.000002 -0.000001 0.000000 0.000000 0.000000 @@ -9580,6 +9685,8 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -9590,12 +9697,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -9603,19 +9704,19 @@ 0.000000 0.000000 0.000001 -0.001913 -0.000098 -0.000525 -0.000342 -0.000386 -0.000235 -0.001742 -0.002515 -0.000420 -0.000044 -0.000030 -0.000014 -0.000015 +0.000009 +0.001567 +0.000290 +0.000880 +0.001324 +0.001791 +0.001543 +0.001792 +0.001101 +0.000565 +0.000040 +0.000004 +0.000000 0.000000 0.000000 0.000000 @@ -9627,9 +9728,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -9644,33 +9742,41 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 -0.000121 -0.000229 -0.000111 -0.000071 -0.000005 -0.000001 -0.003822 -0.000060 -0.000608 -0.000611 -0.000668 -0.000323 -0.001050 -0.000551 -0.000043 0.000003 0.000002 -0.000001 +0.000023 +0.000105 +0.000311 +0.000705 +0.001456 +0.000894 +0.000437 +0.000027 +0.000003 +0.000000 0.000000 0.000000 0.000000 0.000000 +0.000000 +-0.000000 -0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -9682,14 +9788,14 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -9699,25 +9805,21 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000009 -0.000010 -0.000013 -0.000003 +0.000000 0.000001 -0.009651 -0.000061 -0.000719 -0.000858 -0.000803 -0.000329 -0.001067 -0.000780 -0.000099 -0.000025 -0.000026 -0.000027 +0.001557 +0.000090 +0.000559 +0.000468 +0.000343 +0.000515 +0.001648 +0.001427 +0.001191 +0.000092 +0.000021 0.000008 +0.000012 0.000000 0.000000 0.000000 @@ -9730,18 +9832,23 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -9751,34 +9858,22 @@ 0.000000 0.000000 0.000000 +0.002364 +0.000032 +0.000450 +0.000615 +0.000470 +0.000599 +0.000925 +0.000333 +0.000151 +0.000008 0.000001 -0.000001 -0.000002 -0.000001 -0.000001 -0.035791 -0.000095 -0.001475 -0.002390 -0.002148 -0.000609 -0.000524 -0.000405 -0.000024 -0.000003 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -9787,41 +9882,46 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000101 -0.000045 -0.000015 -0.000006 -0.000002 -0.000002 -0.101038 -0.000229 -0.002873 -0.005591 -0.004998 -0.001426 -0.000583 -0.000044 -0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000037 +0.000144 +0.000170 +0.000041 +0.000006 +0.000001 +0.009528 +0.000048 +0.000736 +0.001041 +0.000583 +0.000403 +0.000485 +0.000290 +0.000294 +0.000049 +0.000015 +0.000006 +0.000004 0.000000 0.000000 0.000000 @@ -9838,6 +9938,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -9854,25 +9955,30 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000005 +0.000003 +0.000001 +0.000001 +0.024931 +0.000057 +0.001182 +0.002045 +0.001451 +0.000982 +0.000509 +0.000370 +0.000177 +0.000013 +0.000001 0.000000 0.000000 0.000000 0.000000 -0.018305 -0.000051 -0.000363 -0.000567 -0.000316 -0.000051 -0.000009 -0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -9885,21 +9991,36 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000002 +0.000003 +0.000006 +0.000004 +0.000003 +0.000003 +0.099195 +0.000189 +0.003214 +0.006247 +0.004278 +0.002326 +0.000497 +0.000034 +0.000004 +0.000000 0.000000 0.000000 0.000000 @@ -9909,25 +10030,46 @@ 0.000000 0.000000 0.000000 -0.002558 -0.000034 -0.000132 -0.000244 -0.000110 -0.000023 -0.000004 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 +0.000006 +0.000076 +0.000050 +0.000023 +0.000011 +0.000003 +0.000001 +0.000001 +0.023347 +0.000056 +0.000636 +0.000997 +0.000434 +0.000127 +0.000011 +0.000000 0.000000 0.000000 0.000000 @@ -9939,15 +10081,25 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -9956,17 +10108,18 @@ 0.000000 0.000000 0.000001 +0.000001 0.000000 0.000000 -0.000001 -0.000003 -0.003882 -0.000219 -0.000721 -0.001467 -0.000731 -0.000165 -0.000055 +0.000000 +0.000000 +0.003467 +0.000024 +0.000133 +0.000228 +0.000081 +0.000029 +0.000002 0.000000 0.000000 0.000000 @@ -9976,13 +10129,48 @@ 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000000 0.000000 0.000000 +0.000001 +0.003908 +0.000143 +0.000699 +0.001418 +0.000552 +0.000180 +0.000037 0.000000 0.000000 0.000000 @@ -9990,6 +10178,14 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 @@ -10002,6 +10198,15 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10010,20 +10215,18 @@ 0.000000 0.000000 0.000000 -0.000002 -0.000800 -0.000081 -0.000258 -0.000476 -0.000279 -0.000102 -0.000068 -0.000002 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000796 +0.000059 +0.000251 +0.000487 +0.000218 +0.000117 +0.000052 +0.000004 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 -0.000000 @@ -10032,18 +10235,16 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -10055,15 +10256,18 @@ 0.000000 0.000000 0.000000 -0.000006 -0.001454 -0.000316 -0.000108 -0.000012 -0.000005 -0.000011 -0.000150 +0.000000 +0.000000 +0.000000 0.000005 +0.001134 +0.000545 +0.000151 +0.000010 +0.000001 +0.000001 +0.000130 +0.000004 0.000001 0.000000 0.000000 @@ -10071,9 +10275,15 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 -0.000000 -0.000000 @@ -10081,13 +10291,12 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -10098,6 +10307,7 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10106,21 +10316,27 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000245 +0.000114 +0.000295 +0.000472 +0.000609 +0.000495 +0.000511 +0.000233 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000006 -0.000248 -0.000119 -0.000310 -0.000451 -0.000708 -0.000408 -0.000723 -0.000131 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10132,9 +10348,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -10145,16 +10358,8 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -10163,15 +10368,15 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000009 +0.000008 0.000013 -0.000097 -0.000210 -0.000604 -0.000452 -0.000649 -0.000037 +0.000094 +0.000229 +0.000547 +0.000620 +0.000407 +0.000065 +0.000000 0.000000 0.000000 0.000000 @@ -10179,6 +10384,17 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10186,6 +10402,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10198,6 +10415,18 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000003 +0.000001 +0.000013 +0.001139 +0.000293 +0.000631 +0.000414 +0.000259 +0.000164 +0.000049 +0.000006 0.000000 0.000000 0.000000 @@ -10207,22 +10436,6 @@ 0.000000 0.000000 0.000000 -0.000036 -0.000692 -0.000060 -0.000059 -0.000069 -0.000024 -0.000011 -0.000062 -0.001326 -0.000229 -0.000640 -0.000426 -0.000276 -0.000118 -0.000106 -0.000004 0.000000 0.000000 0.000000 @@ -10234,14 +10447,7 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -10251,14 +10457,29 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.001025 +0.000981 0.000010 0.000000 +0.000003 0.000000 0.000000 0.000000 +0.000262 +0.000364 +0.000236 +0.000071 +0.000003 +0.000010 +0.000039 +0.000003 +0.000002 +0.000007 +0.000030 +0.000145 +0.000397 +0.000390 +0.000019 +0.000001 0.000000 0.000000 0.000000 @@ -10267,14 +10488,10 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000001 -0.000008 -0.000023 -0.000083 -0.000761 -0.000153 -0.000004 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10284,28 +10501,40 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 +0.000005 +0.000002 +0.000004 +0.000002 +0.000042 +0.000112 +0.000312 +0.000297 +0.000168 +0.000038 +0.000008 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10313,76 +10542,52 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000004 -0.000123 -0.000119 -0.000103 -0.000373 -0.000697 -0.000104 -0.000132 -0.000401 -0.000015 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 +0.000021 +0.000005 +0.000064 +0.000280 +0.000676 +0.000997 +0.001729 +0.001425 +0.000738 +0.000067 +0.000028 +0.000040 +0.000278 0.000000 -0.000001 -0.000020 -0.000007 -0.000029 -0.000308 -0.001079 -0.000248 -0.000595 -0.002687 -0.000202 -0.000017 -0.000023 -0.000076 -0.000343 0.000000 +0.000593 0.000000 -0.000575 0.000000 0.000000 0.000000 @@ -10395,44 +10600,39 @@ 0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000004 -0.000084 -0.000541 -0.000095 -0.000078 -0.000164 -0.000307 -0.000110 -0.000729 -0.000919 -0.000066 -0.000006 -0.000001 0.000000 0.000000 0.000000 0.000000 +0.000002 +0.000021 +0.000564 +0.000080 +0.000124 +0.000158 +0.000189 +0.000306 +0.000804 +0.000458 +0.000247 +0.000020 +0.000001 0.000000 0.000000 0.000000 @@ -10442,12 +10642,8 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -10467,21 +10663,6 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000049 -0.000668 -0.000154 -0.000244 -0.000531 -0.000761 -0.000267 -0.001802 -0.000825 -0.000150 -0.000154 -0.000236 -0.000289 -0.000077 0.000000 0.000000 0.000000 @@ -10490,6 +10671,26 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000012 +0.000623 +0.000125 +0.000347 +0.000498 +0.000477 +0.000628 +0.001469 +0.000459 +0.000785 +0.000348 +0.000127 +0.000029 +0.000020 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -10507,31 +10708,34 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000001 -0.000000 0.000007 -0.000056 -0.000252 -0.000274 -0.002697 -0.000755 -0.000068 -0.000019 -0.000009 -0.000003 +0.000064 +0.000184 +0.000610 +0.002208 +0.000410 +0.000410 +0.000066 +0.000006 +0.000000 0.000000 0.000000 0.000000 @@ -10540,8 +10744,8 @@ 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10553,9 +10757,10 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -10567,27 +10772,31 @@ 0.000000 0.000000 0.000000 -0.000003 -0.000009 -0.000001 -0.000001 -0.001775 -0.000122 -0.001109 -0.000573 -0.000277 -0.000103 -0.000159 -0.000008 0.000000 0.000000 0.000000 +0.000423 +0.000030 +0.000383 +0.000590 +0.000219 +0.000203 +0.000128 +0.000005 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10606,9 +10815,6 @@ -0.000000 -0.000000 0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -10616,18 +10822,16 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000005 -0.000015 -0.000021 -0.000004 -0.000002 -0.002521 -0.000053 -0.000672 -0.000536 -0.000279 0.000026 +0.000016 +0.000007 +0.000003 +0.003957 +0.000055 +0.000700 +0.000828 +0.000289 +0.000062 0.000005 0.000000 0.000000 @@ -10637,17 +10841,14 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10657,8 +10858,12 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -10670,16 +10875,15 @@ 0.000000 0.000000 0.000001 -0.000002 0.000001 0.000001 -0.005199 -0.000088 -0.000963 -0.000650 -0.000292 -0.000041 -0.000007 +0.003247 +0.000049 +0.000776 +0.000593 +0.000215 +0.000069 +0.000006 0.000000 0.000000 0.000000 @@ -10691,53 +10895,54 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000003 -0.000013 -0.000009 -0.000013 +0.000001 +0.000010 +0.000033 +0.000022 +0.000014 0.000008 -0.000006 -0.018616 +0.019039 +0.000195 +0.003399 +0.002904 +0.001722 +0.001159 0.000249 -0.003259 -0.003222 -0.002458 -0.000780 -0.000346 0.000002 -0.000018 +0.000014 0.000000 0.000000 0.000000 -0.000531 -0.000482 +0.000514 +0.000461 +0.000000 0.000000 0.000000 0.000000 @@ -10753,17 +10958,70 @@ -0.000000 -0.000000 -0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 +0.000000 +0.000000 +0.000002 +0.000004 +0.000004 +0.000003 +0.000002 +0.007380 +0.000107 +0.001683 +0.001105 +0.000426 +0.000172 +0.000019 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -10771,17 +11029,16 @@ 0.000000 0.000000 0.000001 +0.000001 +0.004374 +0.000129 +0.000883 +0.000452 +0.000092 +0.000014 0.000002 -0.000003 -0.000002 -0.000003 -0.008101 -0.000138 -0.001417 -0.001056 -0.000525 -0.000113 -0.000025 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -10803,10 +11060,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -10816,28 +11069,31 @@ -0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +0.000001 +0.000001 +0.000001 0.000003 -0.003972 -0.000163 -0.000591 -0.000341 -0.000092 -0.000010 -0.000002 +0.006029 +0.000291 +0.000995 +0.000569 +0.000195 +0.000157 +0.000263 +0.000417 +0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -10848,9 +11104,13 @@ 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -10871,20 +11131,20 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000813 +0.000054 +0.000107 +0.000074 +0.000110 +0.000133 +0.000362 +0.000330 +0.000000 0.000000 0.000000 0.000000 0.000000 -0.000002 -0.000017 -0.006208 -0.000416 -0.000946 -0.000582 -0.000330 -0.000156 -0.000423 -0.000234 0.000000 0.000000 0.000000 @@ -10900,11 +11160,6 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -10918,24 +11173,24 @@ 0.000000 0.000000 0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000009 -0.000813 -0.000079 -0.000113 -0.000067 -0.000130 -0.000131 -0.000560 -0.000167 +0.000003 +0.000006 +0.000033 +0.000008 +0.000003 +0.000004 +0.000906 +0.000101 +0.000193 +0.000208 +0.000229 +0.000152 +0.000111 +0.000025 0.000000 0.000000 0.000000 @@ -10943,6 +11198,11 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -10950,48 +11210,43 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000003 -0.000005 +0.000026 +0.000110 +0.001239 +0.000481 +0.000116 +0.000012 +0.000004 +0.000009 +0.001903 +0.000502 +0.000391 +0.000128 0.000030 -0.000010 -0.000020 -0.000087 -0.000861 -0.000102 -0.000184 -0.000180 -0.000287 -0.000118 -0.000170 -0.000013 -0.000000 +0.000006 +0.000001 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11002,6 +11257,11 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11011,32 +11271,31 @@ 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000002 +0.000017 +0.000436 +0.000332 +0.000131 +0.000018 +0.000004 +0.000011 +0.000825 +0.000119 +0.000130 +0.000084 +0.000166 +0.000595 +0.000734 +0.000355 +0.000008 0.000000 0.000000 -0.000026 -0.000137 -0.001913 -0.000615 -0.000203 -0.000031 -0.000043 -0.000214 -0.002025 -0.000502 -0.000360 -0.000103 -0.000032 -0.000004 -0.000001 0.000000 0.000000 0.000000 @@ -11053,21 +11312,20 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 0.000000 @@ -11075,45 +11333,32 @@ 0.000000 0.000000 0.000000 -0.000007 -0.000010 -0.000011 -0.000005 -0.000011 -0.000067 -0.000774 -0.000089 -0.000102 -0.000069 -0.000180 -0.000301 -0.001247 -0.000231 -0.000001 -0.000000 -0.000000 0.000000 0.000000 +0.000002 +0.000075 +0.000032 +0.000599 +0.000180 +0.000055 +0.000038 +0.000007 +0.000001 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -11122,25 +11367,9 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000009 -0.000096 -0.000034 -0.000623 -0.000189 -0.000035 -0.000024 -0.000022 -0.000001 -0.000000 0.000000 0.000000 0.000000 @@ -11148,7 +11377,10 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11156,6 +11388,15 @@ 0.000000 0.000000 0.000000 +0.000004 +0.000007 +0.000020 +0.000058 +0.000393 +0.000340 +0.000158 +0.000011 +0.000001 0.000000 0.000000 0.000000 @@ -11167,10 +11408,20 @@ -0.000000 -0.000000 -0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -11184,21 +11435,29 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000007 -0.000031 -0.000018 -0.000055 -0.000873 -0.000047 -0.000001 0.000000 0.000000 0.000000 +0.000001 +0.000033 +0.000190 +0.000492 +0.000638 +0.000745 +0.001036 +0.000694 +0.000128 +0.000009 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11213,19 +11472,17 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11234,17 +11491,16 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000015 +0.000067 +0.000284 +0.000238 +0.000305 +0.000073 +0.000007 +0.000000 0.000000 -0.000003 -0.000144 -0.001147 -0.000115 -0.000179 -0.003327 -0.000232 -0.000021 -0.000004 -0.000001 0.000000 0.000000 0.000000 @@ -11258,22 +11514,14 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -11281,7 +11529,6 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11294,23 +11541,23 @@ 0.000000 0.000000 0.000000 +0.000001 +0.000029 +0.000137 +0.000400 +0.001309 +0.000058 +0.000045 +0.000004 +0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -11323,8 +11570,8 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -11335,18 +11582,24 @@ 0.000000 0.000000 0.000000 -0.000004 -0.000004 -0.000012 -0.000201 -0.000820 -0.000238 -0.002552 -0.000264 -0.000023 -0.000010 -0.000008 -0.000005 +0.000000 +0.000000 +0.000000 +0.000020 +0.000134 +0.000065 +0.000110 +0.000189 +0.000425 +0.000054 +0.000112 +0.000506 +0.000972 +0.001459 +0.002515 +0.000125 +0.000237 +0.000017 0.000001 0.000000 0.000000 @@ -11354,6 +11607,8 @@ 0.000000 0.000000 0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -11369,8 +11624,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11382,19 +11635,14 @@ 0.000000 0.000000 0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 -0.000062 -0.000137 -0.000371 -0.000415 -0.000055 -0.000049 -0.000390 -0.001278 -0.000392 -0.001936 -0.000089 -0.000003 0.000000 0.000000 0.000000 @@ -11409,27 +11657,37 @@ 0.000000 0.000000 0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000973 +0.000019 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11450,11 +11708,6 @@ 0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -11467,18 +11720,19 @@ 0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 0.000000 0.000000 -0.001016 -0.000019 -0.000000 0.000000 0.000000 0.000000 @@ -11487,6 +11741,15 @@ 0.000000 0.000000 0.000000 +0.000003 +0.000005 +0.000319 +0.000062 +0.000448 +0.000665 +0.000286 +0.000186 +0.000009 0.000000 0.000000 0.000000 @@ -11494,6 +11757,34 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11502,11 +11793,20 @@ 0.000000 0.000000 0.000000 --0.000000 0.000000 +0.000042 +0.000015 +0.000509 +0.000255 +0.000350 +0.000632 +0.000165 +0.000003 +0.000012 0.000000 0.000000 0.000000 +0.000001 0.000000 0.000000 0.000000 @@ -11514,10 +11814,11 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11527,9 +11828,11 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11538,14 +11841,26 @@ 0.000000 0.000000 0.000001 -0.000001 -0.000263 -0.000094 -0.001196 -0.000271 -0.000174 -0.000064 -0.000007 +0.000039 +0.000026 +0.000019 +0.000011 +0.001847 +0.000129 +0.002343 +0.001007 +0.000395 +0.000124 +0.000040 +0.000004 +0.000068 +0.000000 +0.000000 +0.000000 +0.000520 +0.000368 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -11554,16 +11869,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -11578,52 +11883,51 @@ -0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000030 -0.000015 -0.000429 -0.000424 -0.000515 -0.000400 -0.000240 -0.000003 -0.000013 0.000000 0.000000 0.000000 +0.000000 +0.000000 +0.000000 +0.000002 +0.001891 +0.000192 +0.003005 +0.001279 +0.000476 +0.000070 +0.000024 0.000001 0.000000 0.000000 0.000000 0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -11635,26 +11939,23 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000010 -0.000030 -0.000034 -0.000013 -0.000010 -0.002193 -0.000223 -0.002940 -0.001759 -0.000902 -0.000141 -0.000064 -0.000004 -0.000089 0.000000 0.000000 0.000000 -0.000528 -0.000374 +0.000004 +0.000013 +0.000014 +0.000012 +0.000014 +0.004714 +0.000171 +0.000757 +0.000199 +0.000040 +0.000006 +0.000003 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -11666,13 +11967,6 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 @@ -11680,8 +11974,15 @@ -0.000000 -0.000000 0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -11691,22 +11992,32 @@ 0.000000 0.000000 0.000000 +0.000000 0.000001 -0.001789 -0.000202 -0.001755 -0.000914 -0.000446 -0.000042 -0.000024 -0.000001 +0.000003 +0.000003 +0.000002 +0.000005 +0.002142 +0.000114 +0.000401 +0.000209 +0.000108 +0.000135 +0.000452 +0.000391 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11714,53 +12025,38 @@ 0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000004 -0.000006 -0.000010 -0.000007 -0.000015 -0.005112 -0.000236 -0.000587 -0.000169 -0.000052 -0.000006 -0.000005 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000002 +0.000002 +0.000005 +0.003311 +0.000334 +0.000752 +0.000348 +0.000132 +0.000044 +0.000023 +0.000003 0.000000 0.000000 0.000000 @@ -11779,6 +12075,7 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11789,23 +12086,6 @@ 0.000000 0.000000 0.000000 -0.000001 -0.000002 -0.000003 -0.000002 -0.000013 -0.003943 -0.000263 -0.000520 -0.000244 -0.000187 -0.000141 -0.000710 -0.000180 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -11814,14 +12094,20 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.002539 +0.000764 +0.001503 +0.000836 +0.000470 +0.000419 +0.000308 +0.000102 0.000000 0.000000 0.000000 @@ -11829,32 +12115,52 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000001 -0.000001 -0.000001 -0.000001 -0.000016 -0.001825 -0.000267 -0.000603 -0.000355 -0.000387 -0.000175 -0.000450 -0.000059 0.000000 0.000000 +0.000002 +0.001788 +0.000791 +0.001109 +0.000460 +0.000284 +0.000287 +0.000122 +0.000113 +0.000001 +0.000000 0.000000 0.000000 0.000000 @@ -11873,45 +12179,42 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000002 -0.000058 -0.003000 -0.001100 -0.001769 -0.000688 -0.000433 -0.000101 -0.000091 -0.000004 0.000000 +0.000002 +0.000717 +0.000401 +0.000660 +0.000274 +0.000186 +0.000412 +0.000222 +0.000093 +0.000007 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -11922,8 +12225,11 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -11932,6 +12238,13 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -11945,18 +12258,6 @@ 0.000000 0.000000 0.000000 -0.000006 -0.000166 -0.001596 -0.000615 -0.000718 -0.000315 -0.000468 -0.000234 -0.000835 -0.000221 -0.000001 -0.000000 0.000000 0.000000 0.000000 @@ -11968,6 +12269,24 @@ -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 +-0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -11984,27 +12303,25 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 +0.000001 +0.000006 +0.000054 +0.000213 +0.000452 +0.000220 +0.000042 +0.000003 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000005 -0.000125 -0.001115 -0.000350 -0.000365 -0.000081 -0.000023 -0.000003 -0.000003 0.000000 0.000000 0.000000 @@ -12015,6 +12332,8 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12024,11 +12343,6 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -12040,7 +12354,9 @@ 0.000000 0.000000 0.000000 --0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -12085,32 +12401,25 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000002 +0.000087 +0.000335 +0.000331 +0.000163 +0.000073 +0.000001 +0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000002 -0.000008 -0.000911 -0.000107 -0.000005 -0.000001 -0.000001 0.000000 0.000000 0.000000 @@ -12126,23 +12435,12 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -12160,13 +12458,20 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000042 +0.000140 +0.000290 +0.000503 +0.000008 +0.000006 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -12178,11 +12483,6 @@ 0.000000 0.000000 0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -12195,6 +12495,9 @@ -0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12203,12 +12506,17 @@ 0.000000 0.000000 0.000000 -0.000004 -0.000318 -0.001660 -0.000022 -0.000060 -0.000005 +0.000000 +0.000001 +0.000001 +0.000008 +0.000125 +0.000510 +0.001241 +0.002862 +0.000061 +0.000138 +0.000010 0.000000 0.000000 0.000000 @@ -12222,6 +12530,8 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -12234,10 +12544,12 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -12248,22 +12560,22 @@ 0.000000 0.000000 0.000000 +0.000002 +0.000044 +0.000277 +0.000831 +0.001807 +0.000006 +0.000006 +0.000001 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000058 -0.000555 -0.000150 -0.001265 -0.000041 -0.000001 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12272,20 +12584,23 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +-0.000000 +-0.000000 -0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 +0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -12296,23 +12611,8 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 -0.000001 -0.000001 -0.000003 -0.000109 -0.000736 -0.000288 -0.002904 -0.000093 -0.000004 -0.000000 0.000000 0.000000 0.000000 @@ -12320,6 +12620,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 -0.000000 @@ -12330,6 +12631,11 @@ -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -12348,6 +12654,7 @@ 0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12355,13 +12662,13 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000001 -0.000051 -0.000494 -0.000224 -0.001295 0.000004 +0.000077 +0.000247 +0.000587 +0.000074 +0.000001 +0.000001 0.000000 0.000000 0.000000 @@ -12383,9 +12690,7 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 +0.000000 0.000000 0.000000 0.000000 @@ -12395,35 +12700,15 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12447,30 +12732,7 @@ -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000011 -0.000076 -0.000355 -0.000448 -0.000142 -0.000001 -0.000001 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -12484,26 +12746,15 @@ 0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 -0.000000 -0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12528,45 +12779,9 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 -0.000007 -0.000010 -0.000375 -0.000400 -0.000212 -0.000025 -0.000006 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -12586,18 +12801,9 @@ -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 --0.000000 -0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -12607,25 +12813,28 @@ 0.000000 0.000000 0.000000 +0.000040 +0.000024 +0.000869 +0.000628 +0.000305 +0.000073 +0.000033 +0.000011 0.000000 0.000000 -0.000013 -0.000012 -0.000314 -0.000344 -0.000267 -0.000047 -0.000035 -0.000004 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12638,6 +12847,9 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12646,28 +12858,22 @@ -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000264 +0.000070 +0.001156 +0.000582 +0.000345 +0.000178 +0.000289 +0.000090 0.000000 0.000000 -0.000355 -0.000113 -0.001013 -0.000518 -0.000462 -0.000171 -0.000429 -0.000043 0.000000 0.000000 0.000000 @@ -12694,14 +12900,12 @@ 0.000000 0.000000 0.000000 +0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 0.000000 0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12711,14 +12915,14 @@ 0.000000 0.000001 0.000004 -0.002364 -0.000460 -0.002073 -0.001049 -0.001072 -0.000424 -0.000783 -0.000050 +0.001985 +0.000320 +0.002713 +0.001170 +0.000684 +0.000416 +0.000516 +0.000124 0.000000 0.000000 0.000000 @@ -12734,13 +12938,16 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 -0.000000 -0.000000 -0.000000 @@ -12751,9 +12958,6 @@ -0.000000 -0.000000 0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -12761,15 +12965,15 @@ 0.000000 0.000000 0.000000 -0.000005 -0.003069 -0.000685 -0.001924 -0.001263 -0.001243 -0.000440 -0.000653 -0.000032 +0.000003 +0.002286 +0.000445 +0.001969 +0.001342 +0.000850 +0.000543 +0.000444 +0.000050 0.000000 0.000000 0.000000 @@ -12789,20 +12993,20 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 @@ -12812,21 +13016,17 @@ 0.000000 0.000000 0.000001 -0.000009 -0.002936 -0.000782 -0.001123 -0.000245 +0.000007 +0.003405 +0.000731 +0.001342 +0.000378 0.000071 -0.000006 +0.000012 0.000002 0.000000 0.000000 0.000000 --0.000000 --0.000000 -0.000000 -0.000000 0.000000 0.000000 0.000000 @@ -12839,17 +13039,8 @@ -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 @@ -12857,26 +13048,9 @@ -0.000000 0.000000 0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000003 -0.000750 -0.000859 -0.002946 -0.001079 -0.000501 -0.000041 -0.000031 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 +-0.000000 +-0.000000 +-0.000000 -0.000000 -0.000000 -0.000000 @@ -12894,19 +13068,14 @@ 0.000000 0.000000 0.000000 +0.000563 +0.000434 +0.001729 +0.000887 +0.000281 +0.000067 +0.000004 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -12914,15 +13083,6 @@ 0.000000 0.000000 0.000000 -0.000031 -0.000686 -0.000738 -0.001948 -0.000447 -0.000210 -0.000019 -0.000061 -0.000001 0.000000 0.000000 0.000000 @@ -12931,8 +13091,11 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 +0.000000 +0.000000 +0.000000 +0.000000 +0.000000 0.000000 0.000000 -0.000000 @@ -12941,40 +13104,34 @@ -0.000000 -0.000000 -0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 +0.000114 +0.000203 +0.001014 +0.000452 +0.000156 +0.000041 +0.000002 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000748 -0.000000 -0.000266 -0.000020 -0.000001 -0.000000 -0.000013 -0.000308 -0.001052 -0.000342 -0.000387 -0.000112 -0.000038 -0.000014 -0.000799 -0.000040 -0.000000 0.000000 0.000000 0.000000 @@ -12996,35 +13153,12 @@ -0.000000 -0.000000 -0.000000 --0.000000 --0.000000 --0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 -0.000000 --0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.000001 -0.000001 -0.000000 0.000000 -0.000027 -0.000697 -0.001772 -0.000409 -0.000294 -0.000078 -0.000012 -0.000013 -0.000807 -0.000029 0.000000 0.000000 0.000000 @@ -13040,28 +13174,9 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -13074,22 +13189,13 @@ 0.000000 0.000000 0.000000 +-0.000000 +-0.000000 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 0.000000 0.000000 @@ -13100,8 +13206,4 @@ 0.000000 0.000000 0.000000 --0.000000 --0.000000 --0.000000 --0.000000 0.000000 diff --git a/unidec/bin/UniDec.exe b/unidec/bin/UniDec.exe index 711cfd4c9b2c7cfc20ccc49593045461e06b3763..cc657653696f8791c69a913214b046d2f87e4f7f 100644 GIT binary patch delta 342060 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

^$c*G7R{)Ns$z*SQi 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 delta 341395 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 0: + self.farray = self.farray[self.farray[:, 2] > self.config.CDScanStart] + except: + pass + try: + if int(self.config.CDScanEnd) > 0: + self.farray = self.farray[self.farray[:, 2] < self.config.CDScanEnd] + except: + pass + + # Filter m/z print("Filtering m/z range:", self.config.minmz, self.config.maxmz, "Start Length:", len(self.farray)) self.filter_mz(mzrange=[self.config.minmz, self.config.maxmz]) + + # Filter Centroids print("Filtering centroids:", self.config.CDres, "Start Length:", len(self.farray)) self.filter_centroid_all(self.config.CDres) + + # Filter Charge States print("Filtering Charge range:", self.config.startz, self.config.endz, "Start Length:", len(self.farray)) self.filter_z(zrange=[self.config.startz, self.config.endz + 1]) + # Convert intensity to charge print("Converting From Intensity to Charge. Slope:", self.config.CDslope, "Start Length:", len(self.farray)) self.convert(slope=self.config.CDslope) + # Create Histogram print("Creating Histogram Bins:", self.config.mzbins, self.config.CDzbins, "Start Length:", len(self.farray)) self.histogram(mzbins=self.config.mzbins, zbins=self.config.CDzbins) if len(self.harray) > 0: - self.hist_data_prep() + self.harray = self.hist_data_prep() + + self.data.data2 = np.transpose([self.mz, np.sum(self.harray, axis=0)]) + np.savetxt(self.config.infname, self.data.data2) + print("Transforming m/z to mass:", self.config.massbins, "Start Length:", len(self.farray)) if transform: self.transform() + np.savetxt(self.config.massdatfile, self.data.massdat) self.unprocessed = deepcopy(self.data.massdat) else: print("ERROR: Empty histogram array on process") @@ -567,39 +601,49 @@ def histogram(self, mzbins=1, zbins=1, x=None, y=None, mzrange=None, zrange=None self.data.data3 = np.transpose([np.ravel(self.X, order="F"), np.ravel(self.Y, order="F"), np.ravel(self.harray, order="F")]) - def hist_data_prep(self): + def hist_data_prep(self, harray=None): + if harray is None: + harray = self.harray if self.config.smooth > 0 or self.config.smoothdt > 0: print("Histogram Smoothing:", self.config.smooth, self.config.smoothdt) - self.harray = IM_functions.smooth_2d(self.harray, self.config.smoothdt, self.config.smooth) + harray = IM_functions.smooth_2d(harray, self.config.smoothdt, self.config.smooth) if self.config.intthresh > 0: print("Histogram Intensity Threshold:", self.config.intthresh) - self.hist_int_threshold(self.config.intthresh) + harray = self.hist_int_threshold(harray, self.config.intthresh) if self.config.reductionpercent > 0: print("Histogram Data Reduction:", self.config.reductionpercent) - self.hist_datareduction(self.config.reductionpercent) + harray = self.hist_datareduction(harray, self.config.reductionpercent) if self.config.subbuff > 0 or self.config.subbufdt > 0: print("Histogram Background Subtraction:", self.config.subbuff, self.config.subbufdt) - self.harray = IM_functions.subtract_complex_2d(self.harray.transpose(), self.config).transpose() + harray = IM_functions.subtract_complex_2d(harray.transpose(), self.config).transpose() - self.harray = self.hist_filter_smash() + harray = self.hist_filter_smash(harray) - self.data.data2 = np.transpose([self.mz, np.sum(self.harray, axis=0)]) - np.savetxt(self.config.infname, self.data.data2) - pass + return harray + + def hist_int_threshold(self, harray, int_threshold): + """ + Filter the harray to include only intensities above the int_threshold. - def hist_int_threshold(self, int_threshold): - boo1 = self.harray > int_threshold - self.harray *= boo1 + :param harray: 2D numpy array to be filtered + :param int_threshold: Intensity threshold + :return: harray, the filtered 2D numpy array + """ + boo1 = harray > int_threshold + harray *= boo1 + return harray - def hist_datareduction(self, red_per): - sdat = np.sort(np.ravel(self.harray)) + + def hist_datareduction(self, harray, red_per): + sdat = np.sort(np.ravel(harray)) index = round(len(sdat) * red_per / 100.) cutoff = sdat[index] - self.hist_int_threshold(cutoff) + harray = self.hist_int_threshold(harray, cutoff) + return harray - def hist_mass_filter(self, massrange=None): + def hist_mass_filter(self, harray, massrange=None): # Get values from config if not supplied if massrange is None: massrange = [self.config.masslb, self.config.massub] @@ -609,10 +653,10 @@ def hist_mass_filter(self, massrange=None): boo2 = self.mass > massrange[1] boo3 = np.logical_or(boo1, boo2) # Set values outside range to 0 - self.harray[boo3] = 0 - return self.harray + harray[boo3] = 0 + return harray - def hist_filter_smash(self, smashrange=None): + def hist_filter_smash(self, harray, smashrange=None): """ Smashes the range region to 0. Used to eliminate unwanted peaks. :return: None @@ -630,7 +674,7 @@ def hist_filter_smash(self, smashrange=None): boo3 = np.logical_and(boo1, boo2) print(boo3.shape) # Set values outside range to 0 - self.harray[boo3] = 0 + harray[boo3] = 0 if not ud.isempty(self.config.smashlist) and self.config.smashflag == 1: print("Smashing: ", self.config.smashlist) @@ -648,8 +692,8 @@ def hist_filter_smash(self, smashrange=None): boo3 = np.logical_and(boo3, boo4) # Set values outside range to 0 - self.harray[boo3] = 0 - return self.harray + harray[boo3] = 0 + return harray def hist_nativeZ_filter(self, nativeZrange=None): # Get values from config if not supplied @@ -669,15 +713,19 @@ def hist_nativeZ_filter(self, nativeZrange=None): # Set values outside range to 0 self.harray[boo3] = 0 - def transform(self): + def transform(self, harray=None, dataobj=None): + if harray is None: + harray = self.harray + if dataobj is None: + dataobj = self.data # Test for if array is empty and error if so - if len(self.harray) == 0: + if len(harray) == 0: print("ERROR: Empty histogram array on transform") return 0 # filter out zeros - self.harray = np.array(self.harray) - boo1 = self.harray > 0 + harray = np.array(harray) + boo1 = harray > 0 mass = self.mass[boo1] # Test for if array is empty and error if so @@ -692,30 +740,31 @@ def transform(self): massaxis = np.arange(minval, maxval, self.config.massbins) # Create the mass grid - self.data.massgrid = [] + dataobj.massgrid = [] for i in range(len(self.ztab)): - d = self.harray[i] + d = harray[i] boo1 = d > 0 newdata = np.transpose([self.mass[i][boo1], d[boo1]]) if len(newdata) < 2: - self.data.massgrid.append(massaxis * 0) + dataobj.massgrid.append(massaxis * 0) else: if self.config.poolflag == 1: massdata = ud.linterpolate(newdata, massaxis) else: massdata = ud.lintegrate(newdata, massaxis) - self.data.massgrid.append(massdata[:, 1]) - self.data.massgrid = np.transpose(self.data.massgrid) + dataobj.massgrid.append(massdata[:, 1]) + dataobj.massgrid = np.transpose(dataobj.massgrid) # Create the linearized mass data by integrating everything into the new linear axis - self.data.massdat = np.transpose([massaxis, np.sum(self.data.massgrid, axis=1)]) - np.savetxt(self.config.massdatfile, self.data.massdat) + dataobj.massdat = np.transpose([massaxis, np.sum(dataobj.massgrid, axis=1)]) + # Ravel the massgrid to make the format match unidec - self.data.massgrid = np.ravel(self.data.massgrid) + dataobj.massgrid = np.ravel(dataobj.massgrid) # Create the data2 and mzgrid objects from the histogram array for compatiblity with unidec functions - self.data.data2 = np.transpose([self.mz, np.sum(self.harray, axis=0)]) - self.data.zdat = np.transpose([self.ztab, np.sum(self.harray, axis=1)]) - self.data.mzgrid = np.transpose( - [np.ravel(self.X.transpose()), np.ravel(self.Y.transpose()), np.ravel(self.harray.transpose())]) + dataobj.data2 = np.transpose([self.mz, np.sum(harray, axis=0)]) + dataobj.zdat = np.transpose([self.ztab, np.sum(harray, axis=1)]) + dataobj.mzgrid = np.transpose( + [np.ravel(self.X.transpose()), np.ravel(self.Y.transpose()), np.ravel(harray.transpose())]) + return dataobj def transform_mzmass(self): # Test for if array is empty and error if so @@ -983,7 +1032,7 @@ def run_deconvolution(self): # Process data but don't transform self.process_data(transform=False) # Filter histogram to remove masses that are not allowed - self.hist_mass_filter() + self.harray = self.hist_mass_filter(self.harray) # Filter histogram to remove charge states that aren't allowed based on the native charge state filter self.hist_nativeZ_filter() @@ -1000,6 +1049,7 @@ def run_deconvolution(self): print("Deconvolution Time:", time.perf_counter() - starttime) # Transform m/z to mass self.transform() + np.savetxt(self.config.massdatfile, self.data.massdat) def decon_external_call(self): self.export_config() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py new file mode 100644 index 00000000..5b107fdb --- /dev/null +++ b/unidec/modules/HTEng.py @@ -0,0 +1,503 @@ +import scipy.ndimage +from modules.fitting import * + +from modules.CDEng import * +from modules.ChromEng import * +import unidec.tools as ud +from modules.unidecstructure import DataContainer + +import matplotlib.pyplot as plt + +HTseqDict = {'2': '101', '3': '1110100', '4': '000100110101111', '5': '0000100101100111110001101110101', + '6': '000001000011000101001111010001110010010110111011001101010111111', + '7': '0000001000001100001010001111001000101100111010100111110100001110001001001101101011011110110001101001011101110011001010101111111', + '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111'} + + +class HTEng: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.scans = [] + self.fullscans = [] + self.fulltime = [] + self.fulltic = [] + self.htkernel = [] + self.fftk = [] + self.htoutput = [] + self.padindex = 0 + self.shiftindex = 0 + self.kernelroll = 0 + self.cycleindex = 0 + + self.htseq = HTseqDict['3'] + + self.analysistime = 38 # total analysis time in min, should automatically grab this + self.timespacing = 0.165576 # Scan time, should automatically grab this + self.cycletime = 2.0 # Total cycle time, should automatically grab this + self.timepad = 0 # Length of time pad, should automatically grab this + + self.timeshift = 7 # How far back in time to shift + + self.kernelsmooth = 5 # Kernel smoothing width + print("HT Engine") + + def parse_file_name(self, path): + if "cyc" in path: + # Find location of cyc and take next character + cycindex = path.find("cyc") + cyc = int(path[cycindex + 3]) + self.cycletime = float(cyc) + + if "zp" in path: + # Find location of zp and take next character + zpindex = path.find("zp") + zp = float(path[zpindex + 2]) + self.timepad = zp * self.cycletime + + if "bit" in path: + # Find location of bit and take next character + bitindex = path.find("bit") + bit = int(path[bitindex + 3]) + try: + self.htseq = HTseqDict[str(bit)] + except KeyError as e: + print("ERROR: Bit not found in dictionary. Using 3 bit sequence.") + print(e) + self.htseq = '1110100' + + print("Cycle Time:", self.cycletime, "Time Pad:", self.timepad, "HT Sequence:", self.htseq) + + def setup_ht(self, cycleindex=None): + """ + Sets up the HT kernel for deconvolution. This is a binary sequence that is convolved with the data to + deconvolve the data. The sequence is defined by the htseq variable. The timepad and timeshift variables + shift the sequence in time. The kernelsmooth variable smooths the kernel to reduce ringing. + :param cycleindex: The length of a cycle in number of scans. + If not specified, default is the full number of scans divided by the number of cycles. + :return: None + """ + # Finds the number of scans that are padded + if self.timepad > 0: + self.padindex = self.set_timepad_index(self.timepad) + + if self.timeshift >= 0: + # Index for the first scan above the timepad + padindex2 = self.set_timepad_index(self.timeshift) + # Describes the number of scans to shift the data back by + self.shiftindex = self.padindex - padindex2 + + # Check for negative shift index and fix if necessary + if self.shiftindex < 0: + self.shiftindex = 0 + print("Shift index is less than pad index. Check timepad and timeshift values. " + "Likely need to decrease timeshift. Setting to 0.") + + # Convert sequence to array + seqarray = np.array([int(s) for s in self.htseq]) + seqarray = seqarray * 2 - 1 + shortkernel = seqarray + + # Roll short kernel so that the first 1 is in index 0 + self.kernelroll = -np.argmax(shortkernel) + shortkernel = np.roll(shortkernel, self.kernelroll) + + scaledkernel = np.arange(len(shortkernel)) / (len(shortkernel)) + kernelscans = scaledkernel * (np.amax(self.scans) - self.padindex) + self.cycleindex = np.round(kernelscans[1]) + + # Correct the cycle time to be not a division of the total sequence length but a fixed number of scans + if cycleindex is not None: + diff = np.amax(self.scans) - self.padindex - cycleindex * len(shortkernel) + self.padindex += diff + 1 + kernelscans = np.arange(len(shortkernel)) * cycleindex + print("Correction Diff:", diff) + + # Create the HT kernel + self.htkernel = np.zeros_like(self.fullscans[self.padindex:]).astype(float) + print("HT Kernel Length:", len(self.htkernel), self.padindex, cycleindex) + + index = 0 + for i, s in enumerate(self.fullscans): + # check if this is the first scan above the next scaledkernel + if s >= kernelscans[index]: + self.htkernel[i] = shortkernel[index] + index += 1 + if index >= len(shortkernel): + break + + # Smooth the kernel if desired + if self.kernelsmooth > 0: + self.gausskernel = np.zeros_like(self.htkernel) + self.gausskernel += ndis(self.fullscans[self.padindex:], self.padindex, self.kernelsmooth) + self.gausskernel += ndis(self.fullscans[self.padindex:], np.amax(self.fullscans[self.padindex:]) + 1, + self.kernelsmooth) + self.gausskernel /= np.amax(self.gausskernel) + self.fftg = np.fft.fft(self.gausskernel) + self.htkernel = np.fft.ifft(np.fft.fft(self.htkernel) * self.fftg).real + + # Make fft of kernel for later use + self.fftk = np.fft.fft(self.htkernel).conj() + + def htdecon(self, data, *args, **kwargs): + # Whether to correct the kernel to optimize the cycle time + if "correct" in kwargs: + if kwargs["correct"]: + print("Correcting") + self.get_cycle_time(data) + self.setup_ht(cycleindex=self.cycleindex) + # Whether to smooth the data before deconvolution + if "gsmooth" in kwargs: + data = scipy.ndimage.gaussian_filter1d(data, kwargs["gsmooth"]) + if "sgsmooth" in kwargs: + data = scipy.signal.savgol_filter(data, kwargs["sgsmooth"], 2) + + # Set the range of indexes used in the deconvolution + # Starts at the pad but shift will move it back + self.indexrange = [self.padindex - self.shiftindex, len(data) - self.shiftindex] + # print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex, "Len:", + # len(data)) + # Do the convolution + output = np.fft.ifft( + np.fft.fft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk).real + + # Shift the output back to the original time + if self.padindex > 0: + # add zeros back on the front + rollindex = int(-self.shiftindex + self.cycleindex * self.kernelroll) + output = np.roll(np.concatenate((np.zeros(self.padindex), output)), rollindex) + + output = np.real(output) + if "normalize" in kwargs: + if kwargs["normalize"]: + output /= np.amax(output) + # Return demultiplexed data + return output + + def set_timepad_index(self, timepad): + # find first index above timepad in self.fulltimes + padindex = np.argmax(self.fulltime >= timepad) + return padindex + + def get_first_peak(self, data, threshold=0.5): + """ + Get the index of the first peak in the data above a threshold. + :param data: Data array 1D + :param threshold: Threshold as ratio of highest peak + :return: Index of first data point above threshold + """ + # Find the first peak above threshold + maxindex = np.argmax(data) + maxval = np.amax(data) + if maxval > 0: + # Find the first peak above threshold + peakindex = np.argmax(data[maxindex:] < threshold * maxval) + maxindex + else: + peakindex = 0 + return peakindex + + def get_cycle_time(self, data, cycleindexguess=None, widthguess=110): + # autocorrelation of the data + ac = np.correlate(data, data, mode="same") + # Get peak after largest peak + maxindex = np.argmax(ac) + ac = ac[maxindex:] + if cycleindexguess is not None: + maxindex = cycleindexguess + else: + maxindex = len(ac) - 2 * widthguess - 1 + # Get first peak + self.cycleindex = np.argmax(ac[widthguess:widthguess + maxindex]) + widthguess + self.timespacing = np.amax(self.fulltime) / len(self.fulltime) + self.cycletime = self.cycleindex * self.timespacing + print(self.cycleindex, self.cycletime) + return ac + + +class UniChromHT(HTEng, ChromEngine): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + print("HT Chromatogram Engine") + + def open_file(self, path): + self.open_chrom(path) + times = self.get_minmax_times() + self.analysistime = np.amax(times[1]) + self.fullscans -= np.amin(self.fullscans) + self.scans = np.array(self.fullscans) + self.parse_file_name(path) + print("Loaded File:", path) + + def eic_ht(self, massrange): + eic = self.chromdat.get_eic(mass_range=np.array(massrange)) + print(eic.shape) + self.fulltic = eic[:, 1] + self.fulltime = eic[:, 0] + self.setup_ht() + self.htoutput = self.htdecon(self.fulltic) + return self.htoutput + + def tic_ht(self, correct=False, *args, **kwargs): + self.fulltic = self.ticdat[:, 1] + self.fulltime = self.ticdat[:, 0] + self.setup_ht() + self.htoutput = self.htdecon(self.fulltic, correct=correct, *args, **kwargs) + return self.htoutput + + +class UniDecCDHT(HTEng, UniDecCD): + def __init__(self, *args, **kwargs): + super(UniDecCDHT, self).__init__(*args, **kwargs) + print("HT-CD-MS Engine") + self.hstack = None + self.fullhstack = None + self.topfarray = None + self.topzarray = None + self.topharray = None + self.mzaxis = None + self.zaxis = None + self.X = None + self.Y = None + self.mass = None + self.fullstack_ht = None + self.mz = None + self.ztab = None + + def open_file(self, path, refresh=False): + self.open_cdms_file(path, refresh=refresh) + self.parse_file_name(path) + + def process_data_scans(self, transform=True): + """ + Main function for processing CDMS data, includes filtering, converting intensity to charge, histogramming, + processing the histograms, and transforming (if specified). Transforming is sometimes unnecessary, which is why + it can be turned off for speed. + + :param transform: Sets whether to transform the data from m/z to mass. Default True. + :return: None + """ + starttime = time.perf_counter() + self.process_data() + + self.scans = np.unique(self.farray[:, 2]) + self.fullscans = np.arange(1, np.amax(self.scans) + 1) + self.fulltime = self.fullscans * self.analysistime / np.amax(self.fullscans) + + self.topfarray = deepcopy(self.farray) + self.topzarray = deepcopy(self.zarray) + + # Prepare histogram + self.prep_hist(mzbins=self.config.mzbins, zbins=self.config.CDzbins) + + # Create a stack with one histogram for each scan + self.hstack = np.zeros((len(self.scans), self.topharray.shape[0], self.topharray.shape[1])) + + print("Creating Histograms for Each Scan") + # Loop through scans and create histograms + for i, s in enumerate(self.scans): + # Pull out subset of data for this scan + b1 = self.topfarray[:, 2] == s + + # Create histogram + harray = self.histogramLC(x=self.topfarray[b1, 0], y=self.topzarray[b1]) + + # Transform histogram m/z to mass + # NEED TO WORK ON THIS SECTION + if len(harray) > 0 and np.amax(harray) > 0 and False: + print("TRANSFORMING") + harray = self.hist_data_prep(harray) + dataobj = DataContainer() + if transform: + dataobj = self.transform(harray=harray, dataobj=dataobj) + + # Add histogram to stack + self.topharray += harray + self.hstack[i] = harray + + # Normalize the histogram + if self.config.datanorm == 1: + maxval = np.amax(self.topharray) + if maxval > 0: + self.topharray /= maxval + + # Reshape the data into a 3 column array + self.data.data3 = np.transpose([np.ravel(self.X, order="F"), np.ravel(self.Y, order="F"), + np.ravel(self.topharray, order="F")]) + + print("Process Time:", time.perf_counter() - starttime) + + def prep_hist(self, mzbins=1, zbins=1, mzrange=None, zrange=None): + # Set up parameters + if mzbins < 0.001: + print("Error, mzbins too small. Changing to 1", mzbins) + mzbins = 1 + self.config.mzbins = 1 + self.config.mzbins = mzbins + self.config.CDzbins = zbins + + x = self.farray[:, 0] + y = self.zarray + # Set Up Ranges + if mzrange is None: + mzrange = [np.floor(np.amin(x)), np.amax(x)] + if zrange is None: + zrange = [np.floor(np.amin(y)), np.amax(y)] + + # Create Axes + mzaxis = np.arange(mzrange[0] - mzbins / 2., mzrange[1] + mzbins / 2, mzbins) + # Weird fix to make this axis even is necessary for CuPy fft for some reason... + if len(mzaxis) % 2 == 1: + mzaxis = np.arange(mzrange[0] - mzbins / 2., mzrange[1] + 3 * mzbins / 2, mzbins) + zaxis = np.arange(zrange[0] - zbins / 2., zrange[1] + zbins / 2, zbins) + self.mzaxis = mzaxis + self.zaxis = zaxis + + # Create an empty histogram with the right dimensions + self.topharray, self.mz, self.ztab = np.histogram2d([], [], [self.mzaxis, self.zaxis]) + self.topharray = np.transpose(self.topharray) + + # Calculate the charge and m/z arrays for histogram + self.mz = self.mz[1:] - self.config.mzbins / 2. + self.ztab = self.ztab[1:] - self.config.CDzbins / 2. + self.data.ztab = self.ztab + # Create matrices of m/z and z + self.X, self.Y = np.meshgrid(self.mz, self.ztab, indexing='xy') + # Calculate the mass of every point on histogram + self.mass = (self.X - self.config.adductmass) * self.Y + + def histogramLC(self, x=None, y=None): + # X is m/z + if x is None: + x = self.farray[:, 0] + # Y is charge + if y is None: + y = self.zarray + # Look for empty data and return zero array if so + if len(x) <= 1 or len(y) <= 1: + harray = np.zeros_like(self.topharray) + return harray + # Create histogram + harray, mz, ztab = np.histogram2d(x, y, [self.mzaxis, self.zaxis]) + # Transpose and return + harray = np.transpose(harray) + return harray + + def get_eic(self, farray, *args, **kwargs): + # Count of number of time each scans appears in farray + scans, counts = np.unique(farray[:, 2], return_counts=True) + + fulleic = np.zeros_like(self.fullscans) + for i, s in enumerate(scans): + fulleic[int(s) - 1] = counts[i] + + # Normalize + if "normalize" in kwargs: + if kwargs["normalize"]: + fulleic /= np.amax(fulleic) + else: + fulleic /= np.amax(fulleic) + return np.transpose([self.fulltime, fulleic]) + + def get_tic(self, farray=None, *args, **kwargs): + if farray is None: + farray = self.farray + fulltic = self.get_eic(farray, *args, **kwargs) + self.fulltic = fulltic[:, 1] + return fulltic + + def tic_ht(self, *args, **kwargs): + self.get_tic(*args, **kwargs) + self.setup_ht() + self.htoutput = self.htdecon(self.fulltic, *args, **kwargs) + return np.transpose([self.fulltime, self.htoutput]) + + def eic_ht(self, mzrange, zrange, *args, **kwargs): + # Filter farray + b1 = self.farray[:, 0] >= mzrange[0] + b2 = self.farray[:, 0] <= mzrange[1] + b3 = self.zarray >= zrange[0] + b4 = self.zarray <= zrange[1] + b = np.logical_and(b1, b2) + b = np.logical_and(b, b3) + b = np.logical_and(b, b4) + farray2 = self.farray[b] + + # Create EIC + eic = self.get_eic(farray2) + self.setup_ht() + self.htoutput = self.htdecon(eic[:, 1], *args, **kwargs) + return np.transpose([self.fulltime, self.htoutput]), eic + + def run_ht(self): + # create full hstack to fill any holes in the data for scans with no ions + self.fullhstack = np.zeros((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) + self.fullhstack_ht = np.zeros((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) + for i, s in enumerate(self.scans): + self.fullhstack[int(s) - 1] = self.hstack[i] + + # Run the HT on each track in the stack + # TEST THIS! + self.setup_ht() + for i, x in enumerate(self.mz): + for j, y in enumerate(self.ztab): + trace = self.fullhstack[:, j, i] + htoutput = self.htdecon(trace) + self.fullhstack_ht[:, j, i] = htoutput + + +if __name__ == '__main__': + + eng = UniDecCDHT() + + dir = "C:\Data\HT-CD-MS" + # dir = "Z:\\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-10-26" + dir = "Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM" + os.chdir(dir) + path = "C:\\Data\\HT-CD-MS\\20230906 JDS BSA SEC f22 10x dilute STORI high flow 1_20230906171314_2023-09-07-01-43-26.dmt" + path = "C:\\Data\\HT-CD-MS\\20230906 JDS BSA SEC f22 10x dilute STORI high flow 1_20230906171314.raw" + path = "2023103 JDS BSA inj5s cyc2m bit3 zp3 rep2.raw" + path = "2023103 JDS BSA inj5s cyc2m bit3 zp5 rep1.raw" + path = "20231026 JDS BSA cyc2s inj5s bit3 zp1 rep1.raw" + # path = '20231102_ADH_BSA SEC 15k cyc2m inj3s bit3 zp3 no1.raw' + path = '20231202 JDS 0o1uMBgal 0o4uMgroEL shortCol 300ul_m 6_1spl bit5 zp7 inj4s cyc1m AICoff IIT100.RAW' + path = "Z:\\Group Share\\Skippy\Projects\HT\\2023-10-13 BSA ADH\\20231013 BSA STORI inj2s cyc2m 5bit_2023-10-13-05-06-03.dmt" + path = "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt" + # path = "20231202 JDS 0o1uMBgal 0o4uMgroEL shortCol 300ul_m 6_1spl bit3 zp4 inj5s cyc1m AICoff IIT200.RAW" + + eng.open_file(path) + eng.process_data_scans() + # eng.eic_ht([8500, 10500]) + eng.tic_ht() + # np.savetxt("tic.txt", np.transpose([eng.fulltime, eng.fulltic])) + ac = eng.get_cycle_time(eng.fulltic) + + # import matplotlib.pyplot as plt + # plt.plot(ac) + # plt.show() + # exit() + plt.plot(eng.fulltime, eng.fulltic / np.amax(eng.fulltic)) + plt.plot(eng.fulltime[eng.padindex:], np.roll(eng.htkernel, 0)) + plt.plot(eng.fulltime, eng.htoutput / np.amax(eng.htoutput) - 1) + plt.show() + exit() + + eng.run_ht() + print(np.shape(eng.hstack)) + plt.figure() + + plt.subplot(121) + for i, x in enumerate(eng.mz): + for j, y in enumerate(eng.ztab): + plt.plot(eng.fullscans, eng.fullhstack_ht[:, j, i]) + + plt.subplot(122) + plt.imshow(np.sum(eng.fullhstack[:150], axis=0), aspect="auto", origin="lower", + extent=[eng.mz[0], eng.mz[-1], eng.ztab[0], eng.ztab[-1]]) + + plt.show() + + from unidec.modules import PlotAnimations as PA + import wx + + app = wx.App(False) + PA.AnimationWindow(None, eng.fullhstack[50:], mode="2D") + app.MainLoop() diff --git a/unidec/modules/PlotBase.py b/unidec/modules/PlotBase.py index cd48093a..9b280ac5 100644 --- a/unidec/modules/PlotBase.py +++ b/unidec/modules/PlotBase.py @@ -9,6 +9,7 @@ # noinspection PyUnresolvedReferences import numpy as np from unidec.modules.isolated_packages.ZoomCommon import * +from matplotlib.patches import Rectangle rcParams['ps.useafm'] = True rcParams['ps.fonttype'] = 42 @@ -247,6 +248,13 @@ def plotadddot(self, x, y, colval, markval, label="", linewidth=None): self.subplot1.plot(np.array(x) / self.kdnorm, y, color=colval, marker=markval, linestyle='None', clip_on=True, markeredgecolor="k", label=label, linewidth=linewidth) + def add_rect(self, xstart, ystart, xwidth, ywidth, alpha=0.5, facecolor="k", edgecolor='k', nopaint=False): + self.subplot1.add_patch( + Rectangle((xstart, ystart), xwidth, ywidth, alpha=alpha, facecolor=facecolor, edgecolor=edgecolor, + fill=True)) + if not nopaint: + self.repaint() + def addtext(self, txt, x, y, vlines=True, hlines=False, color="k", ymin=0, ymax=None, verticalalignment="top", xmin=0, xmax=None, nopaint=False, **kwargs): """ diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index 19da5b09..ab002e37 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -19,7 +19,7 @@ class CDMainwindow(MainwindowBase): Main UniDec GUI Window. """ - def __init__(self, parent, title, config, iconfile=None, tabbed=None): + def __init__(self, parent, title, config, iconfile=None, tabbed=None, htmode=False): """ initialize window and feed in links to presenter and config. @@ -30,6 +30,7 @@ def __init__(self, parent, title, config, iconfile=None, tabbed=None): """ MainwindowBase.__init__(self, parent, title, config, iconfile, tabbed) + self.htmode=htmode if tabbed is None: # If tabbed isn't specified, use the display size to decide what is best print("Display Size ", self.displaysize) @@ -177,6 +178,9 @@ def setup_main_panel(self): plotwindow.Bind(wx.EVT_SET_FOCUS, self.onFocus) self.plotpanel = plotwindow + #if self.htmode: + # self.Bind(self.plot1.EVT_MZLIMITS, self.extract, self.plot1) + self.plots = [self.plot1, self.plot2, self.plot5, self.plot4, self.plot3, self.plot6] self.plotnames = ["Figure1", "Figure2", "Figure5", "Figure4", "Figure3", "Figure6"] diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 84942915..f603d144 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -8,7 +8,7 @@ class main_controls(wx.Panel): # scrolled.ScrolledPanel): # noinspection PyMissingConstructor - def __init__(self, parent, config, pres, panel, iconfile): + def __init__(self, parent, config, pres, panel, iconfile, htmode=False): super(wx.Panel, self).__init__(panel) # super(scrolled.ScrolledPanel, self).__init__(parent=panel, style=wx.ALL | wx.EXPAND) # self.SetAutoLayout(1) @@ -82,6 +82,7 @@ def __init__(self, parent, config, pres, panel, iconfile): style2b = fpb.CaptionBarStyle() style3 = fpb.CaptionBarStyle() style3b = fpb.CaptionBarStyle() + styleht = fpb.CaptionBarStyle() bright = 150 bright2 = 200 @@ -103,6 +104,8 @@ def __init__(self, parent, config, pres, panel, iconfile): style3.SetSecondColour(wx.Colour(255, bright3, bright3)) style3b.SetSecondColour(wx.Colour(255, bright4, bright4)) + styleht.SetSecondColour(wx.Colour(0, bright4, 0)) + self.imflag = 0 # Panel to set the data prep controls @@ -179,6 +182,17 @@ def __init__(self, parent, config, pres, panel, iconfile): gbox1b = wx.GridBagSizer(wx.VERTICAL) i = 0 + self.ctlscanstart = wx.TextCtrl(panel1b, value="", size=(60, -1)) + self.ctlscanend = wx.TextCtrl(panel1b, value="", size=(60, -1)) + scanrange = wx.BoxSizer(wx.HORIZONTAL) + scanrange.Add(wx.StaticText(panel1b, label="Scan Range: "), 0, wx.ALIGN_CENTER_VERTICAL) + scanrange.Add(self.ctlscanstart) + scanrange.Add(wx.StaticText(panel1b, label=" to "), 0, wx.ALIGN_CENTER_VERTICAL) + scanrange.Add(self.ctlscanend) + gbox1b.Add(scanrange, (i, 0), span=(1, 2)) + i += 1 + + self.ctlzbins = wx.TextCtrl(panel1b, value="", size=size1) gbox1b.Add(self.ctlzbins, (i, 1)) gbox1b.Add(wx.StaticText(panel1b, label="Charge Bin Size: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) @@ -246,6 +260,26 @@ def __init__(self, parent, config, pres, panel, iconfile): gbox1b.Fit(panel1b) self.foldpanels.AddFoldPanelWindow(foldpanel1b, panel1b, fpb.FPB_ALIGN_WIDTH) + if htmode: + foldpanelht = self.foldpanels.AddFoldPanel(caption="HT Parameters", collapsed=False, cbstyle=styleht) + panelht = wx.Panel(foldpanelht, -1) + sizercontrolht1 = wx.GridBagSizer(wx.VERTICAL) + i=0 + + self.runticht = wx.Button(panelht, -1, "Run TIC Hadamard Transform") + self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_tic_ht, self.runticht) + sizercontrolht1.Add(self.runticht, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + self.runallht = wx.Button(panelht, -1, "Run All Hadamard Transform") + self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_all_ht, self.runallht) + sizercontrolht1.Add(self.runallht, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + panelht.SetSizer(sizercontrolht1) + sizercontrolht1.Fit(panelht) + self.foldpanels.AddFoldPanelWindow(foldpanelht, panelht, fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(foldpanelht, wx.StaticText(foldpanelht, -1, " "), fpb.FPB_ALIGN_WIDTH) # Panel for unidec Parameters foldpanel2 = self.foldpanels.AddFoldPanel(caption="UniDec Parameters", collapsed=False, cbstyle=style2) @@ -623,6 +657,9 @@ def import_config_to_gui(self): self.ctlmtabsig.SetValue(str(self.config.mtabsig)) self.ctlmanualassign.SetValue(self.config.manualfileflag) ''' + self.ctlscanstart.SetValue(str(self.config.CDScanStart)) + self.ctlscanend.SetValue(str(self.config.CDScanEnd)) + self.ctlzbins.SetValue(str(self.config.CDzbins)) self.ctlcentroid.SetValue(str(self.config.CDres)) self.ctlbinsize.SetValue(str(self.config.mzbins)) @@ -699,6 +736,8 @@ def export_gui_to_config(self, e=None): self.config.minmz = ud.string_to_value(self.ctlminmz.GetValue()) self.config.maxmz = ud.string_to_value(self.ctlmaxmz.GetValue()) self.config.CDzbins = ud.string_to_value(self.ctlzbins.GetValue()) + self.config.CDScanStart = ud.string_to_value(self.ctlscanstart.GetValue()) + self.config.CDScanEnd = ud.string_to_value(self.ctlscanend.GetValue()) self.config.CDres = ud.string_to_value(self.ctlcentroid.GetValue()) self.config.CDslope = ud.string_to_value(self.ctlslope.GetValue()) self.config.subtype = self.subtypectl.GetSelection() @@ -810,6 +849,8 @@ def setup_tool_tips(self): self.ctlstartz.SetToolTip(wx.ToolTip("Minimum allowed charge state in deconvolution.")) self.ctlendz.SetToolTip(wx.ToolTip("Maximum allowed charge state in deconvolution.")) self.ctlzbins.SetToolTip(wx.ToolTip("Charge Bin Size for Histogram.")) + self.ctlscanstart.SetToolTip(wx.ToolTip("Minimum scan number to include in deconvolution.")) + self.ctlscanend.SetToolTip(wx.ToolTip("Maximum scan number to include in deconvolution.")) self.ctlcentroid.SetToolTip(wx.ToolTip("Width for Centroid Filtering in units of m/z.")) self.ctlbinsize.SetToolTip(wx.ToolTip( "Controls Linearization.\nConstant bin size (Th) for Linear m/z" diff --git a/unidec/modules/plot1d.py b/unidec/modules/plot1d.py index 41b6a165..7df6ff91 100644 --- a/unidec/modules/plot1d.py +++ b/unidec/modules/plot1d.py @@ -6,7 +6,7 @@ import matplotlib.colorbar as colorbar import matplotlib as mpl import matplotlib.cm as cm -from matplotlib.patches import Rectangle + from unidec.modules.PlotBase import PlotBase @@ -24,7 +24,8 @@ def __init__(self, *args, **kwargs): self.colors = [] def plotrefreshtop(self, xvals, yvals, title="", xlabel="", ylabel="", label="", config=None, color="black", - marker=None, zoom="box", nopaint=False, test_kda=False, integerticks=False, **kwargs): + marker=None, zoom="box", nopaint=False, test_kda=False, integerticks=False, zoomout=False, + **kwargs): """ Create a new 1D plot. :param xvals: x values @@ -40,6 +41,7 @@ def plotrefreshtop(self, xvals, yvals, title="", xlabel="", ylabel="", label="", :param nopaint: Boolean, whether to repaint or not :param test_kda: Boolean, whether to attempt to plot kDa rather than Da :param integerticks: Boolean, whether to use inter tickmarks only + :param zoomout: Boolean, whether to zoom out :param kwargs: Keywords :return: None """ @@ -96,7 +98,10 @@ def plotrefreshtop(self, xvals, yvals, title="", xlabel="", ylabel="", label="", self.subplot1.set_clip_on(True) if not nopaint: - self.repaint(setupzoom=True) + if zoomout: + self.repaint(setupzoom=True, resetzoom=True) + else: + self.repaint(setupzoom=True) self.flag = True self.mlist = [] self.x1, self.x2 = None, None @@ -218,12 +223,7 @@ def filledplot(self, x, y, color="black"): self.subplot1.fill_between(np.array(x) / self.kdnorm, y, y2=0, facecolor=color, alpha=0.75) self.data = np.transpose([x, y]) - def add_rect(self, xstart, ystart, xwidth, ywidth, alpha=0.5, facecolor="k", edgecolor='k', nopaint=False): - self.subplot1.add_patch( - Rectangle((xstart, ystart), xwidth, ywidth, alpha=alpha, facecolor=facecolor, edgecolor=edgecolor, - fill=True)) - if not nopaint: - self.repaint() + def histogram(self, xarray, labels=None, xlab="", ylab="", title=""): """ diff --git a/unidec/modules/thermo_reader/RawFileReader.py b/unidec/modules/thermo_reader/RawFileReader.py index 720b188f..b726ed68 100644 --- a/unidec/modules/thermo_reader/RawFileReader.py +++ b/unidec/modules/thermo_reader/RawFileReader.py @@ -388,7 +388,7 @@ def Get_EIC(self, massrange=None, scanrange=None, trace_number=0): scanrange = [self.FirstSpectrumNumber, self.LastSpectrumNumber] # Define the settings for getting the Base Peak chromatogram #TraceType.BasePeak # Define the settings for getting the TIC chromatogram #TraceType.TIC - MR = Range.Create(massrange[0], massrange[1]) + MR = Range.Create(float(massrange[0]), float(massrange[1])) settings = ChromatogramTraceSettings(TraceType.MassRange) settings.MassRanges = [MR] #print(MR, dir(MR), MR.High, MR.Low) diff --git a/unidec/modules/unidec_enginebase.py b/unidec/modules/unidec_enginebase.py index 4ba8dd04..183ca260 100644 --- a/unidec/modules/unidec_enginebase.py +++ b/unidec/modules/unidec_enginebase.py @@ -183,8 +183,16 @@ def check_badness(self): badness, warning = self.config.check_badness() if warning != "": print(warning) + self.check_isomode() return badness + def check_isomode(self): + if self.config.isotopemode != 0: + print("\nIsotope mode is on!!! If you are seeing errors, try turning this off.\n") + return True + else: + return False + def auto_polarity(self, path=None, importer=None): if path is None: path = self.config.filename diff --git a/unidec/modules/unidec_presbase.py b/unidec/modules/unidec_presbase.py index 2863d162..d04c6ac6 100644 --- a/unidec/modules/unidec_presbase.py +++ b/unidec/modules/unidec_presbase.py @@ -116,6 +116,7 @@ def check_badness(self): badness, warning = self.eng.config.check_badness() if warning != "": self.warn(warning) + self.eng.check_isomode() return badness def warn(self, message, caption='Warning!'): @@ -374,14 +375,13 @@ def on_color_peaks(self, e=None, plot2=None, plot4=None, mass=None, peakpanel=No except: linewidth = 1.75 - # TODO: Take away black background on colored lines for p in self.eng.pks.peaks: if p.mass in peaksel: if not ud.isempty(p.intervalFWHM): # limits = p.integralrange - limits = 2 * (np.array(p.intervalFWHM)-p.mass) + p.mass - #print(p.intervalFWHM, limits, p.mass) + limits = 2 * (np.array(p.intervalFWHM) - p.mass) + p.mass + # print(p.intervalFWHM, limits, p.mass) color = p.color self.plot_integral(limits, color=color, filled=filled, plot=plot2, repaint=False) diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index a843ad6d..c033d904 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -260,6 +260,8 @@ def __init__(self): self.CDslope = 0.2074 self.CDzbins = 1 self.CDres = 0 + self.CDScanStart = -1 + self.CDScanEnd = -1 self.doubledec = False self.kernel = "" diff --git a/unidec/src/UniDec.h b/unidec/src/UniDec.h index 52803d86..e22e267e 100644 --- a/unidec/src/UniDec.h +++ b/unidec/src/UniDec.h @@ -567,7 +567,7 @@ void PrintHelp() printf("\t\t\t\t\t\"tcal3\"=Calibration paramter 3 (P3)\n"); printf("\t\t\t\t5=SLIM T-Wave 3rd Order Polynomial Calibration\n"); printf("\t\t\t\t\t\"tcal4\"=Calibration parmater 4 (P4)\n"); - printf("\nEnjoy! Please report bugs to Michael Marty (mtmarty@email.arizona.edu) commit date 11/1/22\n"); + printf("\nEnjoy! Please report bugs to Michael Marty (mtmarty@arizona.edu) commit date 12/26/23\n"); //printf("\nsize of: %d",sizeof(char)); /* @@ -2680,21 +2680,22 @@ void make_isotopes(float* isoparams, int* isotopepos, float* isotopeval, float* } float massdiff = 1.0026; - //float minmid = isotopemid(minmass, isoparams); - //float minsig = isotopesig(minmass, isoparams); + float minmid = isotopemid(minmass, isoparams); + float minsig = isotopesig(minmass, isoparams); float maxmid = isotopemid(maxmass, isoparams); float maxsig = isotopesig(maxmass, isoparams); - //int isostart = (int)(minmid - 4 * minsig); - int isostart = 0; + int isostart = (int)(minmid - 4 * minsig); + //int isostart = 0; int isoend = (int)(maxmid + 4 * maxsig); - //if (isostart<0){ isostart = 0; } + if (isostart<0){ isostart = 0; } if (isoend < 4) { isoend = 4; } int isolength = isoend - isostart; float* isorange = NULL; int* isoindex = NULL; isorange = (float*) calloc(isolength, sizeof(float)); isoindex = (int*) calloc(isolength, sizeof(int)); + if (isorange&&isoindex){ for (i = 0; i < isolength; i++) { @@ -2719,7 +2720,8 @@ void make_isotopes(float* isoparams, int* isotopepos, float* isotopeval, float* } } } - #pragma omp parallel for private (i,j,k), schedule(auto) + +#pragma omp parallel for private (i,j,k), schedule(auto) for (i = 0; i < lengthmz; i++) { for (j = 0; j < numz; j++) @@ -2838,7 +2840,9 @@ void setup_and_make_isotopes(Config* config, Input* inp) { printf("Isotope Mode: %d\n", config->isotopemode); config->isolength = setup_isotopes(inp->isoparams, inp->isotopepos, inp->isotopeval, inp->mtab, inp->nztab, inp->barr, inp->dataMZ, config->lengthmz, config->numz); + int l = config->isolength * config->lengthmz * config->numz; + inp->isotopepos = (int*)calloc(l , sizeof(int)); inp->isotopeval = (float*) calloc(l, sizeof(float)); diff --git a/unidec/tools.py b/unidec/tools.py index 918e48fc..ebc75c48 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -20,6 +20,8 @@ import matplotlib.cm as cm import matplotlib.colors as colors from unidec.modules.fitting import * +from itertools import cycle + try: from unidec.modules.mzMLimporter import mzMLimporter @@ -134,6 +136,11 @@ def get_luminance(color, type=2): return larray[type] +def create_color_cycle(seq='bgrcmk'): + cycol = cycle(seq) + return cycol + + def match_files(directory, string, exclude=None): files = [] for file in os.listdir(directory): @@ -1912,9 +1919,9 @@ def unidec_call(config, silent=False, conv=False, **kwargs): call.append("-conv") if silent: - out = subprocess.call(call, stdout=subprocess.PIPE) + out = subprocess.call(call, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) else: - out = subprocess.call(call) + out = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) return out @@ -3177,17 +3184,16 @@ def subtract_and_divide(pks, basemass, refguess, outputall=False): if __name__ == "__main__": testdir = "C:\\Data\\AgilentData" testfile = "LYZ-F319-2-11-22-P1-A1.D" - #testfile = "2019_05_15_bsa_ccs_02.d" + # testfile = "2019_05_15_bsa_ccs_02.d" path = os.path.join(testdir, testfile) data = load_mz_file(path) - exit() testfile = "C:\Python\\UniDec3\\TestSpectra\\test_imms.raw" # data = waters_convert(testfile) # print(np.amax(data)) - data = waters_convert2(testfile)#, time_range=[0,1]) + data = waters_convert2(testfile) # , time_range=[0,1]) # print(np.amax(data)) print(data) exit() From 4dabb8f391c8ede7a8c2556b388bcecf4853b8d4 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Wed, 27 Dec 2023 17:21:03 -0700 Subject: [PATCH 02/35] Major development work on HT-CD-MS window --- unidec/UniDecCD.py | 5 +- unidec/UniDecHT.py | 180 ++++++++++++-- .../GroEL_CDMS_1_conf.dat | 3 + unidec/metaunidec/mudstruct.py | 2 +- unidec/modules/AutocorrWindow.py | 11 +- unidec/modules/HTEng.py | 116 +++++---- unidec/modules/gui_elements/CDWindow.py | 47 +++- unidec/modules/gui_elements/CD_controls.py | 87 ++++++- unidec/modules/gui_elements/HTCD_ListCtrls.py | 231 ++++++++++++++++++ unidec/modules/isolated_packages/ZoomSpan.py | 2 +- unidec/modules/plot1d.py | 2 +- unidec/modules/unidecstructure.py | 93 ++++++- unidec/tools.py | 9 +- 13 files changed, 691 insertions(+), 97 deletions(-) create mode 100644 unidec/modules/gui_elements/HTCD_ListCtrls.py diff --git a/unidec/UniDecCD.py b/unidec/UniDecCD.py index 84cfa91d..e7d6a5b2 100644 --- a/unidec/UniDecCD.py +++ b/unidec/UniDecCD.py @@ -156,6 +156,9 @@ def on_stori(self, e=None, dirname=None): self.on_open_file(None, None, path=newfile) def on_dataprep_button(self, e=None): + self.dataprep() + + def dataprep(self): self.view.clear_all_plots() self.export_config(self.eng.config.confname) self.eng.process_data() @@ -238,7 +241,7 @@ def on_pick_peaks(self, e=None): self.view.SetStatusText("Detecting Peaks", number=5) tstart = time.perf_counter() self.export_config(self.eng.config.confname) - self.eng.pick_peaks() + self.eng.pick_peaks(calc_dscore=False) self.view.SetStatusText("Plotting Peaks", number=5) if self.eng.config.batchflag == 0: self.view.peakpanel.add_data(self.eng.pks) diff --git a/unidec/UniDecHT.py b/unidec/UniDecHT.py index a707a77d..985d122f 100644 --- a/unidec/UniDecHT.py +++ b/unidec/UniDecHT.py @@ -6,6 +6,10 @@ import wx import unidec.tools as ud import numpy as np +from unidec.modules import AutocorrWindow +from unidec.modules.unidecstructure import ChromatogramContainer +import os + class UniDecHTCDApp(UniDecCDApp): """ @@ -21,7 +25,10 @@ def init(self, *args, **kwargs): :return: """ self.eng = HTEng.UniDecCDHT() + self.cc = ChromatogramContainer() + self.showht = False self.cycol = ud.create_color_cycle("bgcmy") + self.view = CDWindow.CDMainwindow(self, "UniDecHT for HT-CD-MS Data", self.eng.config, htmode=True) self.comparedata = None @@ -64,53 +71,172 @@ def on_open_file(self, filename, directory, path=None, refresh=False): :param refresh: Refresh the data ranges from the file. Default False. :return: None """ + self.cc.clear() self.on_open_cdms_file(filename, directory, path=path, refresh=refresh) - self.eng.process_data_scans() + self.load_chroms(self.eng.config.cdchrom) + + def on_dataprep_button(self, e=None): + """ + Run data preparation. Run self.eng.process_data_scans. + :param e: Unused event + :return: None + """ + self.cc.clear() + self.dataprep() self.make_tic_plot() def on_select_mzz_region(self): - try: - if not wx.GetKeyState(wx.WXK_CONTROL): - xlimits = self.view.plot1.subplot1.get_xlim() - ylimits = self.view.plot1.subplot1.get_ylim() - print("New limits:", xlimits, ylimits) - color = next(self.cycol) + self.export_config(self.eng.config.confname) + if not wx.GetKeyState(wx.WXK_CONTROL): + xlimits = self.view.plot1.subplot1.get_xlim() + ylimits = self.view.plot1.subplot1.get_ylim() + print("New limits:", xlimits, ylimits) + self.view.plot1.reset_zoom() + color = next(self.cycol) + if self.showht: self.run_eic_ht(xlimits, ylimits, color=color) + else: + self.add_eic(xlimits, ylimits, color=color) - self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], - edgecolor=color, facecolor=color) + def make_tic_plot(self): + data = self.eng.get_tic(normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(data, color="black", label="TIC") - except Exception as e: - print(e) + self.showht = False + self.plot_chromatograms(save=False) - def make_tic_plot(self): - data = self.eng.get_tic() - self.view.plot3.plotrefreshtop(data[:, 0], data[:, 1], config=self.eng.config, zoomout=True) + def add_eic(self, mzrange, zrange, color='b'): + eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) + + self.plot_chromatograms() def on_run_tic_ht(self, e=None): + self.export_config(self.eng.config.confname) self.run_tic_ht() - def run_tic_ht(self): - self.make_tic_plot() - data = self.eng.tic_ht(normalize=False) - self.view.plot3.plotadd(data[:, 0], data[:, 1], colval="red", nopaint=False) - - def run_eic_ht(self, mzrange, zrange, color='b'): - label = "m/z: " + str(round(mzrange[0])) + "-" + str(round(mzrange[1])) + \ - " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) + def on_run_eic_ht(self, e=None): + self.export_config(self.eng.config.confname) + for c in self.cc.chromatograms: + if "TIC" not in c.label: + if "HT" not in c.label: + self.run_eic_ht(c.mzrange, c.zrange, color=c.color, add_eic=False, plot=False) + self.plot_chromatograms() - htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=False) - self.view.plot3.plotadd(htdata[:, 0], htdata[:, 1], colval=color, nopaint=False) - self.view.plot3.addtext(label, np.amax(self.eng.fulltime) * 0.8, np.amax(htdata[:, 1]), color=color, - vlines=False, hlines=False) - pass + def run_tic_ht(self): + self.cc.clear() + data = self.eng.get_tic(normalize=self.eng.config.datanorm) + htdata = self.eng.tic_ht(normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(data, color="black", label="TIC") + self.cc.add_chromatogram(htdata, color="red", label="TIC HT", ht=True) + + self.showht = True + self.plot_chromatograms(save=False) + + def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): + htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) + if add_eic: + self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) + self.cc.add_chromatogram(htdata, color=color, zrange=zrange, mzrange=mzrange, ht=True) + if plot: + self.plot_chromatograms() + + def plot_chromatograms(self, e=None, save=True): + self.makeplot1() + self.view.plottic.clear_plot() + self.view.chrompanel.list.populate(self.cc) + for c in self.cc.chromatograms: + if c.ignore: + continue + + if self.eng.config.HTxaxis == "Scans": + xdat = self.eng.fullscans + xlab = "Scan Number" + else: + xdat = c.chromdat[:, 0] + xlab = "Time" + + if not self.view.plottic.flag: + self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, + zoomout=True, label=c.label, xlabel=xlab, + color=c.color, nopaint=True) + else: + self.view.plottic.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) + + xlimits = c.mzrange + ylimits = c.zrange + if xlimits[0] != -1 and ylimits[0] != -1 and xlimits[1] != -1 and ylimits[1] != -1: + self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], + edgecolor=c.color, facecolor=c.color, nopaint=True) + + self.view.plottic.add_legend() + self.view.plottic.repaint() + self.view.plot1.repaint() + if save: + self.save_chroms() + + def save_chroms(self): + chromarray = self.cc.to_array() + np.savetxt(self.eng.config.cdchrom, chromarray, delimiter="\t", fmt="%s") + + def load_chroms(self, fname=None, array=None): + if fname is not None: + if not os.path.isfile(fname): + print("Chrom file not found:", fname) + return + array = np.loadtxt(fname, delimiter="\t", dtype=str) + + if ud.isempty(array): + return + if len(array.shape) == 1: + array = np.reshape(array, (1, len(array))) # Make sure it is 2D + print(array) + for a in array: + print(a) + label = a[0] + color = a[1] + index = a[2] + ht = a[8] + mzrange = [float(a[3]), float(a[4])] + zrange = [float(a[5]), float(a[6])] + print(mzrange, zrange) + if label == "TIC" or "HT" in label: + continue + chromdat = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) + + self.cc.add_chromatogram(chromdat, color=color, label=label, ht=ht, zrange=zrange, mzrange=mzrange) + + self.plot_chromatograms() + + def on_ignore_repopulate(self, e=None): + self.cc = self.view.chrompanel.list.get_data() + self.plot_chromatograms() def on_run_all_ht(self, e=None): self.run_all_ht() def run_all_ht(self): + self.eng.process_data_scans() self.eng.run_ht() + def on_auto_set_ct(self, e=None): + self.eng.get_cycle_time() + self.eng.config.HTcycleindex = self.eng.cycleindex + self.import_config() + + def on_autocorr2(self, index): + """ + Manual Test - Passed + :param index: + :return: + """ + data = self.cc.chromatograms[index].chromdat + data[:, 0] = np.arange(0, len(data)) + dlg = AutocorrWindow.AutocorrWindow(self.view) + dlg.initalize_dialog(self.eng.config, data, window=10) + dlg.ShowModal() + if __name__ == "__main__": multiprocessing.freeze_support() diff --git a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat index 93f38f51..46227561 100644 --- a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat +++ b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat @@ -61,6 +61,9 @@ kernel CDslope 0.2074 CDzbins 1.0 CDres 1.0 +CDScanStart -1.0 +CDScanEnd -1.0 +HTksmooth 0 csig 4.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/metaunidec/mudstruct.py b/unidec/metaunidec/mudstruct.py index 85608681..2dea4ced 100644 --- a/unidec/metaunidec/mudstruct.py +++ b/unidec/metaunidec/mudstruct.py @@ -70,7 +70,6 @@ def import_hdf5(self, file=None, speedy=False): pass hdf.close() - def export_hdf5(self, file=None, vars_only=False, delete=False): if file is None: file = self.filename @@ -364,6 +363,7 @@ def get_colors(self): colors.append(s.color) return np.array(colors) + class Spectrum: def __init__(self, topname, index, eng): # self.fitdat = np.array([]) diff --git a/unidec/modules/AutocorrWindow.py b/unidec/modules/AutocorrWindow.py index b5bd2b49..080005e6 100644 --- a/unidec/modules/AutocorrWindow.py +++ b/unidec/modules/AutocorrWindow.py @@ -96,8 +96,9 @@ def __init__(self, *args, **kwargs): self.massdat = [] self.plot1 = None self.listpanel = None + self.window=None - def initalize_dialog(self, config, massdat): + def initalize_dialog(self, config, massdat, window=None): """ Initilizes dialog and the layout. Calls run_autocorr. :param config: UniDecConfig object @@ -106,6 +107,7 @@ def initalize_dialog(self, config, massdat): """ self.config = config self.massdat = massdat + self.window=window panel = wx.Panel(self) vbox = wx.BoxSizer(wx.VERTICAL) @@ -141,8 +143,11 @@ def run_autocorr(self, e): :param e: Unused event :return: None """ - corr, peaks = ud.autocorr(self.massdat, self.config) - self.plot1.plotrefreshtop(corr[:, 0], corr[:, 1], "Autocorrelation", "Mass Difference", "", "", self.config) + if self.window is not None: + corr, peaks = ud.autocorr(self.massdat, config=None, window=self.window) + else: + corr, peaks = ud.autocorr(self.massdat, self.config) + self.plot1.plotrefreshtop(corr[:, 0], corr[:, 1], "Autocorrelation", "Difference", "", "", self.config) pks2 = peakstructure.Peaks() pks2.add_peaks(peaks, self.config.massbins) pks2.default_params() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 5b107fdb..77e55862 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -8,15 +8,13 @@ import matplotlib.pyplot as plt -HTseqDict = {'2': '101', '3': '1110100', '4': '000100110101111', '5': '0000100101100111110001101110101', - '6': '000001000011000101001111010001110010010110111011001101010111111', - '7': '0000001000001100001010001111001000101100111010100111110100001110001001001101101011011110110001101001011101110011001010101111111', - '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111'} - class HTEng: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.config.htmode = True + + # Empty Arrays self.scans = [] self.fullscans = [] self.fulltime = [] @@ -24,21 +22,26 @@ def __init__(self, *args, **kwargs): self.htkernel = [] self.fftk = [] self.htoutput = [] + + # Index Values self.padindex = 0 self.shiftindex = 0 self.kernelroll = 0 self.cycleindex = 0 + self.config.HTcycleindex = -1 - self.htseq = HTseqDict['3'] + # Important Parameters that Should be Automatically Set by File Name + self.config.htbit = 3 # HT bit, should automatically grab this + self.config.htseq = ud.HTseqDict[str(self.config.htbit)] # HT Sequence, should automatically set this - self.analysistime = 38 # total analysis time in min, should automatically grab this - self.timespacing = 0.165576 # Scan time, should automatically grab this - self.cycletime = 2.0 # Total cycle time, should automatically grab this - self.timepad = 0 # Length of time pad, should automatically grab this + self.config.HTcycletime = 2.0 # Total cycle time, should automatically grab this + self.config.HTtimepad = 0 # Length of time pad, should automatically grab this - self.timeshift = 7 # How far back in time to shift + # Important Parameters that the User may have to Set + self.config.HTanalysistime = 38 # total analysis time in min, should automatically grab this but not for CDMS + self.config.HTtimeshift = 7 # How far back in time to shift + self.config.HTksmooth = 0 # Kernel Smooth - self.kernelsmooth = 5 # Kernel smoothing width print("HT Engine") def parse_file_name(self, path): @@ -46,43 +49,45 @@ def parse_file_name(self, path): # Find location of cyc and take next character cycindex = path.find("cyc") cyc = int(path[cycindex + 3]) - self.cycletime = float(cyc) + self.config.HTcycletime = float(cyc) if "zp" in path: # Find location of zp and take next character zpindex = path.find("zp") zp = float(path[zpindex + 2]) - self.timepad = zp * self.cycletime + self.config.HTtimepad = zp * self.config.HTcycletime if "bit" in path: # Find location of bit and take next character bitindex = path.find("bit") - bit = int(path[bitindex + 3]) + self.config.htbit = int(path[bitindex + 3]) try: - self.htseq = HTseqDict[str(bit)] + self.config.htseq = ud.HTseqDict[str(self.config.htbit)] except KeyError as e: print("ERROR: Bit not found in dictionary. Using 3 bit sequence.") print(e) - self.htseq = '1110100' + self.config.htseq = '1110100' + self.config.htbit = 3 - print("Cycle Time:", self.cycletime, "Time Pad:", self.timepad, "HT Sequence:", self.htseq) + print("Cycle Time:", self.config.HTcycletime, "Time Pad:", self.config.HTtimepad, + "HT Sequence:", self.config.htseq) def setup_ht(self, cycleindex=None): """ Sets up the HT kernel for deconvolution. This is a binary sequence that is convolved with the data to deconvolve the data. The sequence is defined by the htseq variable. The timepad and timeshift variables - shift the sequence in time. The kernelsmooth variable smooths the kernel to reduce ringing. + shift the sequence in time. The config.HTksmooth variable smooths the kernel to reduce ringing. :param cycleindex: The length of a cycle in number of scans. If not specified, default is the full number of scans divided by the number of cycles. :return: None """ # Finds the number of scans that are padded - if self.timepad > 0: - self.padindex = self.set_timepad_index(self.timepad) + if self.config.HTtimepad > 0: + self.padindex = self.set_timepad_index(self.config.HTtimepad) - if self.timeshift >= 0: + if self.config.HTtimeshift >= 0: # Index for the first scan above the timepad - padindex2 = self.set_timepad_index(self.timeshift) + padindex2 = self.set_timepad_index(self.config.HTtimeshift) # Describes the number of scans to shift the data back by self.shiftindex = self.padindex - padindex2 @@ -93,7 +98,7 @@ def setup_ht(self, cycleindex=None): "Likely need to decrease timeshift. Setting to 0.") # Convert sequence to array - seqarray = np.array([int(s) for s in self.htseq]) + seqarray = np.array([int(s) for s in self.config.htseq]) seqarray = seqarray * 2 - 1 shortkernel = seqarray @@ -103,18 +108,24 @@ def setup_ht(self, cycleindex=None): scaledkernel = np.arange(len(shortkernel)) / (len(shortkernel)) kernelscans = scaledkernel * (np.amax(self.scans) - self.padindex) - self.cycleindex = np.round(kernelscans[1]) + + if cycleindex is None: + if self.config.HTcycleindex <= 0: + self.cycleindex = np.round(kernelscans[1]) + else: + cycleindex = int(self.config.HTcycleindex) # Correct the cycle time to be not a division of the total sequence length but a fixed number of scans if cycleindex is not None: diff = np.amax(self.scans) - self.padindex - cycleindex * len(shortkernel) - self.padindex += diff + 1 + self.padindex += int(diff) + 1 kernelscans = np.arange(len(shortkernel)) * cycleindex + self.cycleindex = int(cycleindex) print("Correction Diff:", diff) # Create the HT kernel - self.htkernel = np.zeros_like(self.fullscans[self.padindex:]).astype(float) - print("HT Kernel Length:", len(self.htkernel), self.padindex, cycleindex) + self.htkernel = np.zeros_like(self.fullscans[int(self.padindex):]).astype(float) + print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex) index = 0 for i, s in enumerate(self.fullscans): @@ -126,14 +137,14 @@ def setup_ht(self, cycleindex=None): break # Smooth the kernel if desired - if self.kernelsmooth > 0: + if self.config.HTksmooth > 0: self.gausskernel = np.zeros_like(self.htkernel) - self.gausskernel += ndis(self.fullscans[self.padindex:], self.padindex, self.kernelsmooth) + self.gausskernel += ndis(self.fullscans[self.padindex:], self.padindex, self.config.HTksmooth) self.gausskernel += ndis(self.fullscans[self.padindex:], np.amax(self.fullscans[self.padindex:]) + 1, - self.kernelsmooth) - self.gausskernel /= np.amax(self.gausskernel) + self.config.HTksmooth) + self.gausskernel /= np.sum(self.gausskernel) self.fftg = np.fft.fft(self.gausskernel) - self.htkernel = np.fft.ifft(np.fft.fft(self.htkernel) * self.fftg).real + self.htkernel = np.fft.ifft(np.fft.fft(self.htkernel) * self.fftg) # Make fft of kernel for later use self.fftk = np.fft.fft(self.htkernel).conj() @@ -148,8 +159,10 @@ def htdecon(self, data, *args, **kwargs): # Whether to smooth the data before deconvolution if "gsmooth" in kwargs: data = scipy.ndimage.gaussian_filter1d(data, kwargs["gsmooth"]) + print("Gaussian Smoothing") if "sgsmooth" in kwargs: data = scipy.signal.savgol_filter(data, kwargs["sgsmooth"], 2) + print("Savitzky-Golay Smoothing") # Set the range of indexes used in the deconvolution # Starts at the pad but shift will move it back @@ -158,7 +171,7 @@ def htdecon(self, data, *args, **kwargs): # len(data)) # Do the convolution output = np.fft.ifft( - np.fft.fft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk).real + np.fft.fft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk) # Shift the output back to the original time if self.padindex > 0: @@ -195,7 +208,9 @@ def get_first_peak(self, data, threshold=0.5): peakindex = 0 return peakindex - def get_cycle_time(self, data, cycleindexguess=None, widthguess=110): + def get_cycle_time(self, data=None, cycleindexguess=None, widthguess=110): + if data is None: + data = self.fulltic # autocorrelation of the data ac = np.correlate(data, data, mode="same") # Get peak after largest peak @@ -207,9 +222,9 @@ def get_cycle_time(self, data, cycleindexguess=None, widthguess=110): maxindex = len(ac) - 2 * widthguess - 1 # Get first peak self.cycleindex = np.argmax(ac[widthguess:widthguess + maxindex]) + widthguess - self.timespacing = np.amax(self.fulltime) / len(self.fulltime) - self.cycletime = self.cycleindex * self.timespacing - print(self.cycleindex, self.cycletime) + timespacing = np.amax(self.fulltime) / len(self.fulltime) + self.config.HTcycletime = self.cycleindex * timespacing + print("Cycle Index:", self.cycleindex, "Cycle Time:", self.config.HTcycletime) return ac @@ -221,7 +236,7 @@ def __init__(self, *args, **kwargs): def open_file(self, path): self.open_chrom(path) times = self.get_minmax_times() - self.analysistime = np.amax(times[1]) + self.config.HTanalysistime = np.amax(times[1]) self.fullscans -= np.amin(self.fullscans) self.scans = np.array(self.fullscans) self.parse_file_name(path) @@ -266,6 +281,11 @@ def open_file(self, path, refresh=False): self.open_cdms_file(path, refresh=refresh) self.parse_file_name(path) + def prep_time_domain(self): + self.scans = np.unique(self.farray[:, 2]) + self.fullscans = np.arange(1, np.amax(self.scans) + 1) + self.fulltime = self.fullscans * self.config.HTanalysistime / np.amax(self.fullscans) + def process_data_scans(self, transform=True): """ Main function for processing CDMS data, includes filtering, converting intensity to charge, histogramming, @@ -278,9 +298,7 @@ def process_data_scans(self, transform=True): starttime = time.perf_counter() self.process_data() - self.scans = np.unique(self.farray[:, 2]) - self.fullscans = np.arange(1, np.amax(self.scans) + 1) - self.fulltime = self.fullscans * self.analysistime / np.amax(self.fullscans) + self.prep_time_domain() self.topfarray = deepcopy(self.farray) self.topzarray = deepcopy(self.zarray) @@ -381,14 +399,13 @@ def histogramLC(self, x=None, y=None): harray = np.transpose(harray) return harray - def get_eic(self, farray, *args, **kwargs): + def create_chrom(self, farray, *args, **kwargs): # Count of number of time each scans appears in farray scans, counts = np.unique(farray[:, 2], return_counts=True) fulleic = np.zeros_like(self.fullscans) for i, s in enumerate(scans): fulleic[int(s) - 1] = counts[i] - # Normalize if "normalize" in kwargs: if kwargs["normalize"]: @@ -398,9 +415,10 @@ def get_eic(self, farray, *args, **kwargs): return np.transpose([self.fulltime, fulleic]) def get_tic(self, farray=None, *args, **kwargs): + self.prep_time_domain() if farray is None: farray = self.farray - fulltic = self.get_eic(farray, *args, **kwargs) + fulltic = self.create_chrom(farray, *args, **kwargs) self.fulltic = fulltic[:, 1] return fulltic @@ -410,7 +428,7 @@ def tic_ht(self, *args, **kwargs): self.htoutput = self.htdecon(self.fulltic, *args, **kwargs) return np.transpose([self.fulltime, self.htoutput]) - def eic_ht(self, mzrange, zrange, *args, **kwargs): + def get_eic(self, mzrange, zrange, *args, **kwargs): # Filter farray b1 = self.farray[:, 0] >= mzrange[0] b2 = self.farray[:, 0] <= mzrange[1] @@ -422,7 +440,11 @@ def eic_ht(self, mzrange, zrange, *args, **kwargs): farray2 = self.farray[b] # Create EIC - eic = self.get_eic(farray2) + eic = self.create_chrom(farray2, *args, **kwargs) + return eic + + def eic_ht(self, mzrange, zrange, *args, **kwargs): + eic = self.get_eic(mzrange, zrange, *args, **kwargs) self.setup_ht() self.htoutput = self.htdecon(eic[:, 1], *args, **kwargs) return np.transpose([self.fulltime, self.htoutput]), eic diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index ab002e37..ee88cee1 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -9,6 +9,7 @@ from unidec.modules import miscwindows from unidec.modules.gui_elements import peaklistsort from unidec.modules.gui_elements.mainwindow_base import MainwindowBase +from unidec.modules.gui_elements.HTCD_ListCtrls import ListCtrlPanel __author__ = 'Michael.Marty' @@ -30,7 +31,7 @@ def __init__(self, parent, title, config, iconfile=None, tabbed=None, htmode=Fal """ MainwindowBase.__init__(self, parent, title, config, iconfile, tabbed) - self.htmode=htmode + self.htmode = htmode if tabbed is None: # If tabbed isn't specified, use the display size to decide what is best print("Display Size ", self.displaysize) @@ -140,6 +141,12 @@ def setup_main_panel(self): miscwindows.setup_tab_box(tab5, self.plot5) miscwindows.setup_tab_box(tab6, self.plot6) + if self.htmode: + self.plottic = PlottingWindow.Plot1d(tab1, figsize=figsize) + tabtic = wx.Panel(plotwindow) + miscwindows.setup_tab_box(tabtic, self.plottic) + plotwindow.AddPage(tabtic, "Chromatogram") + plotwindow.AddPage(tab1, "CD-MS Data") plotwindow.AddPage(tab3, "Charge Distribution") plotwindow.AddPage(tab2, "Mass Distribution") @@ -160,12 +167,18 @@ def setup_main_panel(self): self.plot5 = PlottingWindow.Plot2d(plotwindow, figsize=figsize) self.plot6 = PlottingWindow.Plot1d(plotwindow, figsize=figsize) - sizerplot.Add(self.plot1, (0, 0), span=(1, 1), flag=wx.EXPAND) - sizerplot.Add(self.plot2, (0, 1), span=(1, 1), flag=wx.EXPAND) - sizerplot.Add(self.plot3, (1, 0), span=(1, 1), flag=wx.EXPAND) - sizerplot.Add(self.plot4, (1, 1), span=(1, 1), flag=wx.EXPAND) - sizerplot.Add(self.plot5, (2, 0), span=(1, 1), flag=wx.EXPAND) - sizerplot.Add(self.plot6, (2, 1), span=(1, 1), flag=wx.EXPAND) + i = 0 + if self.htmode: + self.plottic = PlottingWindow.Plot1d(plotwindow, figsize=figsize) + sizerplot.Add(self.plottic, (0, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + sizerplot.Add(self.plot1, (i, 0), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot2, (i, 1), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot3, (i + 1, 0), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot4, (i + 1, 1), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot5, (i + 2, 0), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot6, (i + 2, 1), span=(1, 1), flag=wx.EXPAND) # plotwindow.SetScrollbars(1, 1,1,1) if self.system == "Linux": @@ -178,12 +191,17 @@ def setup_main_panel(self): plotwindow.Bind(wx.EVT_SET_FOCUS, self.onFocus) self.plotpanel = plotwindow - #if self.htmode: + # if self.htmode: # self.Bind(self.plot1.EVT_MZLIMITS, self.extract, self.plot1) self.plots = [self.plot1, self.plot2, self.plot5, self.plot4, self.plot3, self.plot6] self.plotnames = ["Figure1", "Figure2", "Figure5", "Figure4", "Figure3", "Figure6"] + if self.htmode: + self.plots = [self.plottic] + self.plots + self.plotnames = ["Chromatogram"] + self.plotnames + self.plottic._axes = [0.07, 0.11, 0.9, 0.8] + # ........................... # # Sizer for Peaks @@ -191,9 +209,17 @@ def setup_main_panel(self): # ........................... sizerpeaks = wx.BoxSizer(wx.VERTICAL) - self.peakpanel = peaklistsort.PeakListCtrlPanel(panelp) + if self.htmode: + self.peakpanel = peaklistsort.PeakListCtrlPanel(panelp, size=(300, 500)) + else: + self.peakpanel = peaklistsort.PeakListCtrlPanel(panelp) self.bind_peakpanel() sizerpeaks.Add(self.peakpanel, 0, wx.EXPAND) + + if self.htmode: + self.chrompanel = ListCtrlPanel(panelp, self.pres, size=(300, 300)) + sizerpeaks.Add(self.chrompanel, 0, wx.EXPAND) + panelp.SetSizer(sizerpeaks) sizerpeaks.Fit(self) @@ -203,7 +229,8 @@ def setup_main_panel(self): # # ............................. sizercontrols = wx.BoxSizer(wx.VERTICAL) - self.controls = CD_controls.main_controls(self, self.config, self.pres, panel, self.icon_path) + self.controls = CD_controls.main_controls(self, self.config, self.pres, panel, self.icon_path, + htmode=self.htmode) sizercontrols.Add(self.controls, 1, wx.EXPAND) panel.SetSizer(sizercontrols) sizercontrols.Fit(self) diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index f603d144..04e8ed39 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -13,7 +13,7 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): # super(scrolled.ScrolledPanel, self).__init__(parent=panel, style=wx.ALL | wx.EXPAND) # self.SetAutoLayout(1) # self.SetupScrolling(scroll_x=False) - + self.htmode = htmode self.parent = parent self.config = config self.pres = pres @@ -192,7 +192,6 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): gbox1b.Add(scanrange, (i, 0), span=(1, 2)) i += 1 - self.ctlzbins = wx.TextCtrl(panel1b, value="", size=size1) gbox1b.Add(self.ctlzbins, (i, 1)) gbox1b.Add(wx.StaticText(panel1b, label="Charge Bin Size: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) @@ -264,18 +263,82 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): foldpanelht = self.foldpanels.AddFoldPanel(caption="HT Parameters", collapsed=False, cbstyle=styleht) panelht = wx.Panel(foldpanelht, -1) sizercontrolht1 = wx.GridBagSizer(wx.VERTICAL) - i=0 + i = 0 + # Button for Run TIC HT self.runticht = wx.Button(panelht, -1, "Run TIC Hadamard Transform") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_tic_ht, self.runticht) sizercontrolht1.Add(self.runticht, (i, 0), span=(1, 2), flag=wx.EXPAND) i += 1 + # Button for Run EIC HT + self.runeicht = wx.Button(panelht, -1, "Run EIC Hadamard Transform") + self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_eic_ht, self.runeicht) + sizercontrolht1.Add(self.runeicht, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + self.runallht = wx.Button(panelht, -1, "Run All Hadamard Transform") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_all_ht, self.runallht) sizercontrolht1.Add(self.runallht, (i, 0), span=(1, 2), flag=wx.EXPAND) i += 1 + # Text Control for Kernel Smoothing + self.ctlkernelsmooth = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlkernelsmooth, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Kernel Smoothing: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Text Control for HTtimeshift + self.ctlhttimeshift = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlhttimeshift, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Time Shift: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Text Control for Analysis Time + self.ctlanalysistime = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlanalysistime, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Analysis Time: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Drop down menu for X-axis + self.ctlxaxis = wx.Choice(panelht, -1, choices=["Time", "Scans"]) + self.ctlxaxis.SetSelection(0) + sizercontrolht1.Add(self.ctlxaxis, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="X-Axis: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Drop down for HTseq + self.ctlhtseq = wx.Choice(panelht, -1, choices=list(ud.HTseqDict.keys())) + self.ctlhtseq.SetSelection(3) + sizercontrolht1.Add(self.ctlhtseq, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="HT Sequence Bit: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Text control for timepad + self.ctltimepad = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctltimepad, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Time Padding: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Text control for cycle index + self.ctlcycleindex = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlcycleindex, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Cycle Length: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Button for Auto Set Cycle Time + self.autosetct = wx.Button(panelht, -1, "Auto Set Cycle Index") + self.parent.Bind(wx.EVT_BUTTON, self.pres.on_auto_set_ct, self.autosetct) + sizercontrolht1.Add(self.autosetct, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + panelht.SetSizer(sizercontrolht1) sizercontrolht1.Fit(panelht) self.foldpanels.AddFoldPanelWindow(foldpanelht, panelht, fpb.FPB_ALIGN_WIDTH) @@ -681,6 +744,15 @@ def import_config_to_gui(self): self.ctlpublicationmode.SetValue(self.config.publicationmode) self.ctlrawflag.SetSelection(self.config.rawflag) + if self.htmode: + self.ctlkernelsmooth.SetValue(str(self.config.HTksmooth)) + self.ctlhtseq.SetStringSelection(str(self.config.htbit)) + self.ctlhttimeshift.SetValue(str(self.config.HTtimeshift)) + self.ctltimepad.SetValue(str(self.config.HTtimepad)) + self.ctlanalysistime.SetValue(str(self.config.HTanalysistime)) + self.ctlcycleindex.SetValue(str(self.config.HTcycleindex)) + self.ctlxaxis.SetStringSelection(self.config.HTxaxis) + if self.config.adductmass < 0: self.ctlnegmode.SetValue(1) else: @@ -779,6 +851,15 @@ def export_gui_to_config(self, e=None): self.config.separation = ud.string_to_value(self.ctlsep.GetValue()) self.config.adductmass = ud.string_to_value(self.ctladductmass.GetValue()) + if self.htmode: + self.config.HTksmooth = ud.string_to_value(self.ctlkernelsmooth.GetValue()) + self.config.htbit = int(self.ctlhtseq.GetStringSelection()) + self.config.HTxaxis = self.ctlxaxis.GetStringSelection() + self.config.HTtimeshift = ud.string_to_value(self.ctlhttimeshift.GetValue()) + self.config.HTtimepad = ud.string_to_value(self.ctltimepad.GetValue()) + self.config.HTanalysistime = ud.string_to_value(self.ctlanalysistime.GetValue()) + self.config.HTcycleindex = ud.string_to_value(self.ctlcycleindex.GetValue()) + self.config.numit = ud.string_to_int(self.ctlnumit.GetValue()) self.config.integratelb = ud.string_to_value(self.ctlintlb.GetValue()) diff --git a/unidec/modules/gui_elements/HTCD_ListCtrls.py b/unidec/modules/gui_elements/HTCD_ListCtrls.py new file mode 100644 index 00000000..1cf6c3fc --- /dev/null +++ b/unidec/modules/gui_elements/HTCD_ListCtrls.py @@ -0,0 +1,231 @@ +import wx +import wx.lib.mixins.listctrl as listmix +import numpy as np +import matplotlib as mpl + +import unidec.tools as ud +from copy import deepcopy + +luminance_cutoff = 135 +white_text = wx.Colour(250, 250, 250) +black_text = wx.Colour(0, 0, 0) + + +class YValueListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.TextEditMixin): + def __init__(self, parent, id_value, config=None, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, id_value, pos, size, style) + listmix.ListCtrlAutoWidthMixin.__init__(self) + listmix.TextEditMixin.__init__(self) + self.config = config + self.InsertColumn(0, "") + self.InsertColumn(1, "Label") + self.InsertColumn(2, "Sum") + # self.InsertColumn(3, "Name") + self.SetColumnWidth(0, width=30) + self.SetColumnWidth(1, width=175) + self.SetColumnWidth(2, width=50) + # self.SetColumnWidth(3, width=75) + self.freeze_reorder = False + self.parent = parent + + def populate(self, cc): + self.DeleteAllItems() + cc.len = len(cc.chromatograms) + self.data = cc + for i in range(0, cc.len): + c = cc.chromatograms[i] + if c.ignore: + continue + index = self.InsertItem(cc.len, str(c.index)) + try: + self.SetItem(index, 1, str(c.label)) + except: + self.SetItem(index, 1, "") + + try: + self.SetItem(index, 2, str(c.sum)) + except: + self.SetItem(index, 2, str(-1)) + ''' + try: + self.SetItem(index, 3, s.name) + except: + self.SetItem(index, 3, "")''' + self.SetItemData(index, i) + color = c.color + if color is not None: + try: + color = mpl.colors.to_rgba(color) + except: + color = np.fromstring(color[1:-1], sep=",", dtype=float) + c.color = color + + color = wx.Colour(int(round(color[0] * 255)), int(round(color[1] * 255)), + int(round(color[2] * 255)), alpha=255) + self.SetItemBackgroundColour(index, col=color) + + luminance = ud.get_luminance(color, type=2) + # print(wx.Colour(colout), luminance) + if luminance < luminance_cutoff: + self.SetItemTextColour(index, col=white_text) + else: + self.SetItemTextColour(index, col=black_text) + + def clear_list(self): + self.DeleteAllItems() + + def repopulate(self, reset=True): + if reset: + for c in self.data.chromatograms: + c.ignore = False + self.populate(self.data) + + def get_data(self): + return self.data + + +class ListCtrlPanel(wx.Panel): + def __init__(self, parent, pres, size=(200, 400)): + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS) + id_value = wx.ID_ANY + self.selection = [] + self.pres = pres + self.config = self.pres.eng.config + sizer = wx.BoxSizer(wx.VERTICAL) + + self.list = YValueListCtrl(self, id_value, config=self.config, size=size, style=wx.LC_REPORT | wx.BORDER_NONE) + + sizer.Add(self.list, 1, wx.EXPAND) + self.SetSizer(sizer) + self.SetAutoLayout(True) + self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.on_right_click, self.list) + + self.popupID1 = wx.NewIdRef() + self.popupID4 = wx.NewIdRef() + self.popupID5 = wx.NewIdRef() + self.popupID6 = wx.NewIdRef() + self.popupID7 = wx.NewIdRef() + self.popupID10 = wx.NewIdRef() + self.popupID11 = wx.NewIdRef() + + self.Bind(wx.EVT_MENU, self.on_popup_one, id=self.popupID1) + self.Bind(wx.EVT_MENU, self.on_popup_four, id=self.popupID4) + self.Bind(wx.EVT_MENU, self.on_popup_five, id=self.popupID5) + self.Bind(wx.EVT_MENU, self.on_popup_six, id=self.popupID6) + self.Bind(wx.EVT_MENU, self.on_popup_seven, id=self.popupID7) + self.Bind(wx.EVT_MENU, self.on_popup_ten, id=self.popupID10) + self.Bind(wx.EVT_MENU, self.on_popup_eleven, id=self.popupID11) + + def on_right_click(self, event): + if hasattr(self, "popupID1"): + menu = wx.Menu() + menu.Append(self.popupID4, "Ignore") + menu.Append(self.popupID5, "Isolate") + menu.Append(self.popupID6, "Repopulate") + menu.AppendSeparator() + + menu.Append(self.popupID10, "Autocorrelation") + + menu.AppendSeparator() + menu.Append(self.popupID7, "Change Color") + + menu.AppendSeparator() + menu.Append(self.popupID1, "Delete") + menu.Append(self.popupID11, "Delete All") + + self.PopupMenu(menu) + menu.Destroy() + + def get_selected(self): + item = self.list.GetFirstSelected() + num = self.list.GetSelectedItemCount() + self.selection = [] + self.selection.append(item) + for i in range(1, num): + item = self.list.GetNextSelected(item) + self.selection.append(item) + print("Selection:", self.selection) + return self.selection + + def on_popup_one(self, event): + # Delete + self.selection = self.get_selected() + for i in range(0, len(self.selection)): + self.list.data.chromatograms = np.delete(self.list.data.chromatograms, self.selection[i]) + self.list.repopulate() + self.pres.on_ignore_repopulate() + + def on_popup_eleven(self, event): + # Delete All + self.list.data.chromatograms = [] + self.list.repopulate() + self.pres.on_ignore_repopulate() + + def on_popup_four(self, event): + self.selection = self.get_selected() + for i in range(0, len(self.selection)): + self.list.data.chromatograms[self.selection[i]].ignore = True + + self.list.repopulate(reset=False) + self.pres.on_ignore_repopulate() + + if self.list.GetItemCount() < 1: + print("Ignored everything; repopulating...") + self.on_popup_six() + + def on_popup_five(self, event): + self.selection = self.get_selected() + for c in self.list.data.chromatograms: + c.ignore = True + for i in range(0, len(self.selection)): + self.list.data.chromatograms[self.selection[i]].ignore = False + + self.list.repopulate(reset=False) + self.pres.on_ignore_repopulate() + + if self.list.GetItemCount() < 1: + print("Isolated nothing; repopulating...") + self.on_popup_six() + + def on_popup_six(self, event=None): + self.list.repopulate() + self.pres.on_ignore_repopulate() + + def on_popup_seven(self, event=None): + """ + Spawns a dialog for the first selected item to select the color. + Redraws the list control with the new colors and then triggers an EVT_DELETE_SELECTION_2. + :param event: Unused Event + :return: None + """ + # Change Color + item = self.list.GetFirstSelected() + col = self.list.GetItemBackgroundColour(item) + print("Color In:", col) + col = wx.Colour(int(col[0]), int(col[1]), int(col[2]), alpha=int(col.alpha)) + col2 = wx.ColourData() + col2.SetColour(col) + colout = col2 + dlg = wx.ColourDialog(None, data=col2) + if dlg.ShowModal() == wx.ID_OK: + colout = dlg.GetColourData() + colout = deepcopy(colout.GetColour()) + print("Color Out", colout) + dlg.Destroy() + self.list.SetItemBackgroundColour(item, col=colout) + colout = colout.Get(includeAlpha=True) + luminance = ud.get_luminance(wx.Colour(colout), type=2) + colout = ([colout[0] / 255., colout[1] / 255., colout[2] / 255., colout[3] / 255.]) + + if luminance < luminance_cutoff: + self.list.SetItemTextColour(item, col=white_text) + else: + self.list.SetItemTextColour(item, col=black_text) + + self.list.data.chromatograms[item].color = colout + + self.pres.on_ignore_repopulate() + + def on_popup_ten(self, event=None): + item = self.list.GetFirstSelected() + self.pres.on_autocorr2(item) diff --git a/unidec/modules/isolated_packages/ZoomSpan.py b/unidec/modules/isolated_packages/ZoomSpan.py index 152d23e0..0fb9a093 100644 --- a/unidec/modules/isolated_packages/ZoomSpan.py +++ b/unidec/modules/isolated_packages/ZoomSpan.py @@ -1,6 +1,6 @@ from matplotlib.transforms import blended_transform_factory from matplotlib.patches import Rectangle -# from pubsub import setupkwargs + from unidec.modules.isolated_packages.ZoomCommon import * diff --git a/unidec/modules/plot1d.py b/unidec/modules/plot1d.py index 7df6ff91..7fff870b 100644 --- a/unidec/modules/plot1d.py +++ b/unidec/modules/plot1d.py @@ -67,7 +67,7 @@ def plotrefreshtop(self, xvals, yvals, title="", xlabel="", ylabel="", label="", if config.publicationmode != 0: pubflag = 1 - if config.datanorm == 0: + if config.datanorm == 0 and not config.htmode: self._axes = [0.2, 0.11, 0.75, 0.8] self.subplot1 = self.figure.add_axes(self._axes) diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index c033d904..e8b0c3d1 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -76,6 +76,7 @@ def __init__(self): self.massgridfile = '' self.massdatfile = '' self.cdrawextracts = '' + self.cdchrom = '' self.mzgridfile = '' self.cdcreaderpath = '' self.UniDecPath = '' @@ -263,6 +264,18 @@ def __init__(self): self.CDScanStart = -1 self.CDScanEnd = -1 + # Hadamard Transform Parameters + self.htmode = False + self.htseq = "" + self.htbit = 0 + self.HTksmooth = 0 + self.HTanalysistime = 38.0 + self.HTcycletime = 2.0 + self.HTtimepad = 0.0 + self.HTtimeshift = 7.0 + self.HTcycleindex = -1 + self.HTxaxis = "Time" + self.doubledec = False self.kernel = "" @@ -421,6 +434,9 @@ def default_decon_params(self): self.CDslope = 0.2074 self.CDzbins = 1 self.CDres = 0 + self.CDScanStart = -1 + self.CDScanEnd = -1 + self.HTksmooth = 0 self.doubledec = False self.kernel = "" @@ -537,6 +553,10 @@ def config_export(self, name): f.write("CDslope " + str(self.CDslope) + "\n") f.write("CDzbins " + str(self.CDzbins) + "\n") f.write("CDres " + str(self.CDres) + "\n") + f.write("CDScanStart " + str(self.CDScanStart) + "\n") + f.write("CDScanEnd " + str(self.CDScanEnd) + "\n") + f.write("HTksmooth " + str(self.HTksmooth) + "\n") + f.write("csig " + str(self.csig) + "\n") f.write("smoothdt " + str(self.smoothdt) + "\n") f.write("subbufdt " + str(self.subbufdt) + "\n") @@ -651,7 +671,12 @@ def config_import(self, name): self.CDzbins = ud.string_to_value(line.split()[1]) if line.startswith("CDres"): self.CDres = ud.string_to_value(line.split()[1]) - + if line.startswith("CDScanStart"): + self.CDScanStart = ud.string_to_int(line.split()[1]) + if line.startswith("CDScanEnd"): + self.CDScanEnd = ud.string_to_int(line.split()[1]) + if line.startswith("HTksmooth"): + self.HTksmooth = ud.string_to_value(line.split()[1]) if line.startswith("zzsig"): self.zzsig = ud.string_to_value(line.split()[1]) if line.startswith("psig"): @@ -1087,6 +1112,7 @@ def default_file_names(self): self.deconfile = self.outfname + "_decon.txt" self.mzgridfile = self.outfname + "_grid.bin" self.cdrawextracts = self.outfname + "_rawdata.npz" + self.cdchrom = self.outfname + "_chroms.txt" if self.filetype == 0: self.hdf_file = self.outfname + ".hdf5" @@ -1505,6 +1531,71 @@ def read_hdf5(self, file_name): hdf.close() +class Chromatogram: + def __init__(self): + self.chromdat = np.array([]) + self.label = "" + self.color = "#000000" + self.index = 0 + self.mzrange = [-1, -1] + self.zrange = [-1, -1] + self.sum = -1 + self.ignore = 0 + self.ht = False + + def to_row(self): + out = [self.label, str(self.color), str(self.index), str(self.mzrange[0]), str(self.mzrange[1]), + str(self.zrange[0]), str(self.zrange[1]), str(self.sum), str(self.ht)] + return out + + +class ChromatogramContainer: + def __init__(self): + self.chromatograms = [] + + def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrange=None, zrange=None, ht=False): + chrom = Chromatogram() + chrom.chromdat = data + chrom.sum = np.sum(data[:, 1]) + + if label is None: + label = "" + if mzrange is not None: + label += "m/z: " + str(round(mzrange[0])) + "-" + str(round(mzrange[1])) + if zrange is not None: + label += " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) + if ht: + label += " HT" + + chrom.label = label + + chrom.color = color + + if index is not None: + chrom.index = index + else: + chrom.index = len(self.chromatograms) + + if mzrange is not None: + chrom.mzrange = mzrange + + if zrange is not None: + chrom.zrange = zrange + + self.ht = ht + + self.chromatograms.append(chrom) + + def clear(self): + self.chromatograms = [] + + def to_array(self): + arr = [] + for chrom in self.chromatograms: + arr.append(chrom.to_row()) + return np.array(arr).astype(str) + + if __name__ == '__main__': fname = "test.hdf5" data = DataContainer() diff --git a/unidec/tools.py b/unidec/tools.py index ebc75c48..f11c4547 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -55,6 +55,11 @@ known_extensions = [".raw", ".d", ".mzml", ".gz", ".mzml.gz", ".mzxml", ".mzML", ".mzML.gz", ".mzXML", ".txt", ".csv", ".dat", ".npz"] +HTseqDict = {'2': '101', '3': '1110100', '4': '000100110101111', '5': '0000100101100111110001101110101', + '6': '000001000011000101001111010001110010010110111011001101010111111', + '7': '0000001000001100001010001111001000101100111010100111110100001110001001001101101011011110110001101001011101110011001010101111111', + '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111'} + def get_importer(path): extension = os.path.splitext(path)[1] @@ -2541,7 +2546,7 @@ def continuous_wavelet_transform(a, widths, wavelet_type="Ricker"): return signal.cwt(a, wavelet, widths) -def autocorr(datatop, config=None): +def autocorr(datatop, config=None, window=None): """ Note: ASSUMES LINEARIZED DATA :param datatop: 1D data @@ -2566,7 +2571,7 @@ def autocorr(datatop, config=None): corrx = corrx - corrx[maxpos] autocorr = np.transpose([corrx, corry]) boo1 = autocorr[:, 0] > xdiff - cpeaks = peakdetect(autocorr[boo1], config) + cpeaks = peakdetect(autocorr[boo1], config, window=window) return autocorr, cpeaks else: From b00f919d6f965c53a922479a363bc885bfa36a41 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 29 Dec 2023 17:10:03 -0700 Subject: [PATCH 03/35] V. 7.0.0b. Working draft of UniChromCD. Fixed deep bug on integration transform. --- readme.md | 8 + unidec/Launcher.py | 18 +- unidec/UniChromCD.py | 509 ++++++++++++++++++ unidec/UniDecCD.py | 19 +- unidec/UniDecHT.py | 244 --------- .../GroEL_CDMS_1_conf.dat | 13 +- unidec/modules/CDEng.py | 77 +-- unidec/modules/HTEng.py | 302 +++++++++-- unidec/modules/PlottingWindow.py | 7 +- unidec/modules/gui_elements/CDMenu.py | 22 +- unidec/modules/gui_elements/CDWindow.py | 42 +- unidec/modules/gui_elements/CD_controls.py | 84 ++- unidec/modules/gui_elements/HTCD_ListCtrls.py | 19 +- unidec/modules/plot2d.py | 1 - unidec/modules/unidec_enginebase.py | 5 +- unidec/modules/unidecstructure.py | 39 +- unidec/tools.py | 38 +- 17 files changed, 1081 insertions(+), 366 deletions(-) create mode 100644 unidec/UniChromCD.py delete mode 100644 unidec/UniDecHT.py diff --git a/readme.md b/readme.md index 0d2ba9ed..820d7149 100644 --- a/readme.md +++ b/readme.md @@ -195,8 +195,16 @@ Of course, using the pre-compiled version means you don't need to know Python at ## Change Log +v.7.0.0 + +Added new module for time domain CD-MS data analysis, UniChromCD. This includes some cool new plotting and analysis features. Most importantly, it also includes our new method for Hadamard Transform analysis of CD-MS spectra. You can use the tools for regular LC-CD-MS or go crazy with HT-LC-CD-MS. More info coming soon on this front...:) + +Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. + v.6.0.5 +Merged into v.7 + Minor fixes to update compatibility for Python 3.11 and 3.12. Fixed common crash bug when isotope mode is on. Added warnings for if isotope mode is on. diff --git a/unidec/Launcher.py b/unidec/Launcher.py index d7e88b05..ac84c0e5 100644 --- a/unidec/Launcher.py +++ b/unidec/Launcher.py @@ -20,6 +20,7 @@ from unidec.UniChrom import ChromApp from unidec.UPP import UPPApp from unidec.modules import unidecstructure +from unidec.UniChromCD import UniChromCDApp import wx.py as py import os import sys @@ -93,7 +94,7 @@ def __init__(self, parent): sizer = wx.GridBagSizer(wx.HORIZONTAL) panel = wx.Panel(self) button1 = wx.Button(panel, -1, "UniDec\n\nDeconvolve MS and IM-MS") - button2 = wx.Button(panel, -1, "Data Collector\n\nVisualize multiple spectra\nExtract Trends\nFit Kd's") + button2 = wx.Button(panel, -1, "Data Collector\n\nVisualize multiple spectra\nExtract Trends and Kd's") button3 = wx.Button(panel, -1, "Import Wizard\n\nBatch convert Waters Raw to Txt") button4 = wx.Button(panel, -1, "MetaUniDec\n\nBatch process and visualize MS spectra") button5 = wx.Button(panel, -1, "UniDec API Shell\n\nScript UniDec with console") @@ -101,7 +102,9 @@ def __init__(self, parent): button7 = wx.Button(panel, -1, "UltraMeta Data Collector\n\nVisualize Multiple HDF5 Data Sets\nFit Trends") button8 = wx.Button(panel, -1, "UniChrom\n\nDeconvolution of Chromatograms\nUniDec for LC/MS Data") button9 = wx.Button(panel, -1, "UniDecCD\n\nDeconvolution of Charge Detection MS\nUniDec for CD-MS Data") - button10 = wx.Button(panel, -1, "UPP\n\nUniDec Processing Pipeline\nBatch Processing Workflow") + button10 = wx.Button(panel, -1, "UniDec Processing Pipeline\n\nBatch Processing Workflow") + button11 = wx.Button(panel, -1, + "UniChromCD\n\nDeconvolution of CD-MS Chromatograms") html = wx.html.HtmlWindow(panel, -1, size=(390, 310)) pathtofile = os.path.dirname(os.path.abspath(__file__)) self.imagepath = self.eng.config.toplogofile @@ -122,10 +125,12 @@ def __init__(self, parent): sizer.Add(button6, (2, 1), flag=wx.EXPAND) sizer.Add(button7, (1, 1), flag=wx.EXPAND) sizer.Add(button9, (3, 0), flag=wx.EXPAND) - sizer.Add(button5, (4, 1), span=(1, 1), flag=wx.EXPAND) + sizer.Add(button10, (4, 0), span=(1, 1), flag=wx.EXPAND) + sizer.Add(button11, (4, 1), span=(1, 1), flag=wx.EXPAND) sizer.Add(button8, (3, 1), flag=wx.EXPAND) - sizer.Add(html, (0, 2), span=(5, 2)) + sizer.Add(button5, (5, 0), span=(1, 2), flag=wx.EXPAND) + sizer.Add(html, (0, 2), span=(6, 2)) self.Bind(wx.EVT_BUTTON, self.button1, button1) self.Bind(wx.EVT_BUTTON, self.button2, button2) @@ -137,6 +142,7 @@ def __init__(self, parent): self.Bind(wx.EVT_BUTTON, self.button8, button8) self.Bind(wx.EVT_BUTTON, self.button9, button9) self.Bind(wx.EVT_BUTTON, self.button10, button10) + self.Bind(wx.EVT_BUTTON, self.button11, button11) panel.SetSizer(sizer) sizer.Fit(self) @@ -203,6 +209,10 @@ def button10(self, e=None): print("Launching UPP") app = UPPApp() + def button11(self, e=None): + print("Launching UniChromCD") + app = UniChromCDApp() + app.start() class Shell(object): def __init__(self, *args, **kwargs): diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py new file mode 100644 index 00000000..2a36b546 --- /dev/null +++ b/unidec/UniChromCD.py @@ -0,0 +1,509 @@ +from UniDecCD import UniDecCDApp +import multiprocessing +import modules.HTEng as HTEng +from unidec.modules.gui_elements import CDWindow +from pubsub import pub +import wx +import unidec.tools as ud +import numpy as np +from unidec.modules import AutocorrWindow +from unidec.modules.unidecstructure import ChromatogramContainer +import os, time + + +class UniChromCDApp(UniDecCDApp): + """ + Main UniDec GUI Application. + Presenter contains UniDec engine at self.eng and main GUI window at self.view + """ + + def init(self, *args, **kwargs): + """ + Initialize Engine and View. Load defaults. + :param args: + :param kwargs: + :return: + """ + self.eng = HTEng.UniDecCDHT() + self.cc = ChromatogramContainer() + self.showht = False + self.cycol = ud.create_color_cycle("bgcmy") + + self.view = CDWindow.CDMainwindow(self, "UniChrom for CD-MS Data", + self.eng.config, htmode=True) + self.comparedata = None + + pub.subscribe(self.on_select_mzz_region, 'mzlimits') + pub.subscribe(self.on_smash, 'smash') + + self.eng.config.recentfile = self.eng.config.recentfileCD + self.recent_files = self.read_recent() + self.cleanup_recent_file(self.recent_files) + self.view.menu.update_recent() + + self.on_load_default(0) + + if "path" in kwargs: + newdir, fname = os.path.split(kwargs["path"]) + self.on_open_file(fname, newdir) + # self.on_dataprep_button(0) + # self.on_auto(0) + + if self.infile is not None: + newdir, fname = os.path.split(self.infile) + self.on_open_file(fname, newdir) + # self.on_dataprep_button(0) + # self.on_auto(0) + + if False: # and platform.node() == 'DESKTOP-08TGCJO': + print("Opening Test File") + path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" + "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") + + self.on_open_file(None, None, path=path) + # self.eng.process_data_scans() + # self.make_cube_plot() + # self.make_mass_time_2dplot() + # self.run_all_ht() + # self.run_all_mass_transform() + # self.make_mass_cube_plot() + + def on_open_file(self, filename, directory, path=None, refresh=False): + """ + Opens a file. Run self.eng.open_file. + :param filename: File name + :param directory: Directory containing file + :param path: Full path to file + :param refresh: Refresh the data ranges from the file. Default False. + :return: None + """ + self.cc.clear() + self.on_open_cdms_file(filename, directory, path=path, refresh=refresh) + self.load_chroms(self.eng.config.cdchrom) + + def on_dataprep_button(self, e=None): + """ + Run data preparation. Makes the TIC and clears the chromatogram list. + :param e: Unused event + :return: None + """ + self.cc.clear() + self.dataprep() + self.make_tic_plot() + + def on_pick_peaks(self, e=None): + self.export_config(self.eng.config.confname) + self.pick_peaks() + if self.eng.fullmstack is not None: + for p in self.eng.pks.peaks: + mass = p.mass + try: + ub = float(self.eng.config.integrateub) + except: + ub = float(self.eng.config.peakwindow) + try: + lb = float(self.eng.config.integratelb) + except: + lb = -float(self.eng.config.peakwindow) + + massrange = [mass + lb, mass + ub] + + chromdat = self.eng.get_mass_eic(massrange) + label = "Mass EIC: " + str(mass) + self.cc.add_chromatogram(chromdat, color=p.color, label=label, ht=False) + if self.eng.fullmstack_ht is not None: + htdata = self.eng.get_mass_eic(massrange, ht=True) + self.cc.add_chromatogram(htdata, color=p.color, label=label +" HT", ht=True) + self.plot_chromatograms() + + def on_select_mzz_region(self): + self.export_config(self.eng.config.confname) + if not wx.GetKeyState(wx.WXK_CONTROL): + xlimits = self.view.plot1.subplot1.get_xlim() + ylimits = self.view.plot1.subplot1.get_ylim() + print("New limits:", xlimits, ylimits) + self.view.plot1.reset_zoom() + color = next(self.cycol) + if self.showht: + self.run_eic_ht(xlimits, ylimits, color=color) + else: + self.add_eic(xlimits, ylimits, color=color) + + def make_tic_plot(self): + data = self.eng.get_tic(normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(data, color="black", label="TIC") + + self.showht = False + self.plot_chromatograms(save=False) + + def add_eic(self, mzrange, zrange, color='b'): + eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) + + self.plot_chromatograms() + + def on_run_tic_ht(self, e=None): + self.export_config(self.eng.config.confname) + self.run_tic_ht() + + def on_run_eic_ht(self, e=None): + self.export_config(self.eng.config.confname) + for c in self.cc.chromatograms: + if "TIC" not in c.label: + if "HT" not in c.label: + self.run_eic_ht(c.mzrange, c.zrange, color=c.color, add_eic=False, plot=False) + self.plot_chromatograms() + + def run_tic_ht(self): + self.cc.clear() + data = self.eng.get_tic(normalize=self.eng.config.datanorm) + htdata = self.eng.tic_ht(normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(data, color="black", label="TIC") + self.cc.add_chromatogram(htdata, color="red", label="TIC HT", ht=True) + + self.showht = True + self.plot_chromatograms(save=False) + + def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): + htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) + if add_eic: + self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) + self.cc.add_chromatogram(htdata, color=color, zrange=zrange, mzrange=mzrange, ht=True) + if plot: + self.plot_chromatograms() + + def plot_chromatograms(self, e=None, save=True): + self.makeplot1() + self.view.plottic.clear_plot() + self.view.chrompanel.list.populate(self.cc) + for c in self.cc.chromatograms: + if c.ignore: + continue + + if self.eng.config.HTxaxis == "Scans": + xdat = self.eng.fullscans + xlab = "Scan Number" + else: + xdat = c.chromdat[:, 0] + xlab = "Time" + + if not self.view.plottic.flag: + self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, + zoomout=True, label=c.label, xlabel=xlab, + color=c.color, nopaint=True) + else: + self.view.plottic.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) + + xlimits = c.mzrange + ylimits = c.zrange + if xlimits[0] != -1 and ylimits[0] != -1 and xlimits[1] != -1 and ylimits[1] != -1: + self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], + edgecolor=c.color, facecolor=c.color, nopaint=True) + + self.view.plottic.add_legend() + self.view.plottic.repaint() + self.view.plot1.repaint() + if save: + self.save_chroms() + + def save_chroms(self): + chromarray = self.cc.to_array() + np.savetxt(self.eng.config.cdchrom, chromarray, delimiter="\t", fmt="%s") + + def load_chroms(self, fname=None, array=None): + if fname is not None: + if not os.path.isfile(fname): + print("Chrom file not found:", fname) + return + else: + print("Loading Chroms:", fname) + array = np.loadtxt(fname, delimiter="\t", dtype=str) + + if ud.isempty(array): + return + if len(array.shape) == 1: + array = np.reshape(array, (1, len(array))) # Make sure it is 2D + + for a in array: + label = a[0] + color = a[1] + index = a[2] + ht = a[8] + mzrange = [float(a[3]), float(a[4])] + zrange = [float(a[5]), float(a[6])] + + if "TIC" in label or "HT" in label or "Mass EIC" in label: + continue + chromdat = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) + + self.cc.add_chromatogram(chromdat, color=color, label=label, ht=ht, zrange=zrange, mzrange=mzrange) + + self.plot_chromatograms() + + def on_load_chroms(self, e=None): + # Load File dialog + dlg = wx.FileDialog(self.view, "Choose a file") + if dlg.ShowModal() == wx.ID_OK: + fname = dlg.GetPath() + self.load_chroms(fname=fname) + dlg.Destroy() + + def on_ignore_repopulate(self, e=None): + self.cc = self.view.chrompanel.list.get_data() + self.plot_chromatograms() + + def on_select_time_range(self, e=None): + if not self.showht or self.eng.fullhstack_ht is None: + return + if not wx.GetKeyState(wx.WXK_CONTROL): + self.plot_chromatograms() + xlimits = self.view.plottic.subplot1.get_xlim() + print("New limits:", xlimits) + self.view.plottic.reset_zoom() + ylimits = self.view.plottic.subplot1.get_ylim() + # Plot Red box on plottic + self.view.plottic.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], + edgecolor="red", facecolor="red", nopaint=False) + self.select_ht_range(range=xlimits) + + def on_run_all_ht(self, e=None): + self.run_all_ht() + + def run_all_ht(self): + self.export_config(self.eng.config.confname) + self.eng.process_data_scans() + ticdat = self.eng.run_all_ht() + + self.cc.add_chromatogram(ticdat, color="gold", label="TIC*_HT") + + #tic2 = np.sum(self.eng.fullhstack, axis=(1, 2)) + #ticdat2 = np.transpose(np.vstack((self.eng.fulltime, tic2))) + #self.cc.add_chromatogram(ticdat2, color="red", label="TIC*") + + self.showht = True + self.plot_chromatograms() + + def run_all_mass_transform(self, e=None): + self.eng.transform_stacks() + self.cc.add_chromatogram(self.eng.mass_tic, color="grey", label="Mass TIC") + if self.eng.fullmstack_ht is not None: + self.cc.add_chromatogram(self.eng.mass_tic_ht, color="goldenrod", label="Mass TIC HT") + self.plot_chromatograms() + def select_ht_range(self, range=None): + self.eng.select_ht_range(range=range) + self.makeplot1() + self.makeplot2() + self.makeplot3() + self.makeplot4() + + def make_charge_time_2dplot(self, e=None): + self.export_config(self.eng.config.confname) + self.make_2d_plot(face=1) + + def make_mz_time_2dplot(self, e=None): + self.export_config(self.eng.config.confname) + self.make_2d_plot(face=2) + + def make_mass_time_2dplot(self, e=None): + self.export_config(self.eng.config.confname) + self.make_2d_plot(face=3) + + def make_2d_plot(self, e=None, face=1): + starttime = time.perf_counter() + print("Starting 2D Plots...", ) + if self.eng.fullhstack is None: + print("Need to create fullhstack") + self.eng.process_data_scans() + + if self.eng.fullmstack is None and face == 3: + print("Need to create fullmstack") + self.run_all_mass_transform() + + if self.eng.config.HTxaxis == "Scans": + xdat = self.eng.fullscans + xlab = "Scan Number" + else: + xdat = self.eng.fulltime + xlab = "Time" + + if face == 1: + grid = np.sum(self.eng.fullhstack, axis=2) + y = self.eng.zaxis[1:] + ylab = "Charge" + discrete = True + elif face == 2: + grid = np.sum(self.eng.fullhstack, axis=1) + y = self.eng.mzaxis[1:] + ylab = "m/z (Th)" + discrete = True + elif face == 3: + grid = self.eng.fullmstack + print(np.shape(grid)) + y = self.eng.massaxis + ylab = "Mass (Da)" + discrete = True + else: + return + + self.view.plot7.contourplot(xvals=xdat, yvals=y, zgrid=grid, + xlab=xlab, ylab=ylab, config=self.eng.config, discrete=discrete) + + if self.eng.fullhstack_ht is None: + return + else: + fullhstack_ht = np.clip(self.eng.fullhstack_ht, 0, np.amax(self.eng.fullhstack_ht)) + if face == 1: + grid = np.sum(fullhstack_ht, axis=2) + elif face == 2: + grid = np.sum(fullhstack_ht, axis=1) + elif face == 3: + if self.eng.fullmstack_ht is None: + return + else: + grid = np.clip(self.eng.fullmstack_ht, 0, np.amax(self.eng.fullmstack_ht)) + else: + return + self.view.plot8.contourplot(xvals=xdat, yvals=y, zgrid=grid, + xlab=xlab, ylab=ylab, config=self.eng.config, discrete=discrete) + + print("Finished 2D Plot", (time.perf_counter() - starttime), " s") + + def make_mass_cube_plot(self, e=None): + self.make_cube_plot(e, mass=True) + + def make_cube_plot(self, e=None, mass=False): + self.export_config(self.eng.config.confname) + + if mass and self.eng.fullmstack is None: + self.run_all_mass_transform() + + if self.eng.config.HTxaxis == "Scans": + xdat = self.eng.fullscans + xlab = "Scan Number" + else: + xdat = self.eng.fulltime + xlab = "Time" + + try: + print(np.shape(self.eng.fullhstack)) + if self.eng.fullhstack is None: + self.eng.process_data_scans() + starttime = time.perf_counter() + face1 = np.sum(self.eng.fullhstack, axis=2).transpose() + if mass: + face3 = np.reshape(self.eng.data.massgrid, (len(self.eng.massaxis), len(self.eng.ztab))) + ydat = self.eng.massaxis + ylab = "Mass (Da)" + face2 = self.eng.fullmstack.transpose() + else: + # mz by z + ydat = self.eng.mzaxis[1:] + ylab = "m/z (Th)" + face3 = np.sum(self.eng.fullhstack, axis=0).transpose() + face2 = np.sum(self.eng.fullhstack, axis=1).transpose() + self.view.plot9.cubeplot(ydat, self.eng.zaxis[1:], xdat, + face3, face2, face1, + xlab=ylab, ylab="Charge", zlab=xlab, + cmap=self.eng.config.cmap) + endtime = time.perf_counter() + print("Finished Raw Cube in: ", (endtime - starttime), " s") + except Exception as ex: + print("Failed Raw cube", ex) + return + pass + + try: + if self.eng.fullhstack_ht is None: + return + if mass and self.eng.fullmstack_ht is None: + return + fullhstack_ht = np.clip(self.eng.fullhstack_ht, 0, np.amax(self.eng.fullhstack_ht)) + starttime = time.perf_counter() + face1 = np.sum(fullhstack_ht, axis=2).transpose() + + if mass: + fullmstack_ht = np.clip(self.eng.fullmstack_ht, 0, np.amax(self.eng.fullmstack_ht)) + face3 = np.reshape(self.eng.data.massgrid, (len(self.eng.massaxis), len(self.eng.ztab))) + face2 = fullmstack_ht.transpose() + else: + face3 = np.sum(self.eng.fullhstack_ht, axis=0).transpose() + face2 = np.sum(self.eng.fullhstack_ht, axis=1).transpose() + + self.view.plot10.cubeplot(ydat, self.eng.zaxis[1:], xdat, + face3, face2, face1, + xlab=ylab, ylab="Charge", zlab=xlab, + cmap=self.eng.config.cmap) + endtime = time.perf_counter() + print("Finished HT Cube in: ", (endtime - starttime), " s") + except Exception as ex: + print("Failed HT cube", ex) + pass + + def on_auto_set_ct(self, e=None): + self.eng.get_cycle_time() + self.eng.config.HTcycleindex = self.eng.cycleindex + self.import_config() + + def on_plot_kernel(self, e=None): + self.export_config(self.eng.config.confname) + if not self.showht: + self.eng.setup_ht() + data = self.eng.htkernel + + if self.eng.config.HTxaxis == "Scans": + xdat = self.eng.fullscans + xlab = "Scan Number" + else: + xdat = self.eng.fulltime + xlab = "Time" + + label = "HT Kernel" + color = "orange" + self.indexrange = [self.eng.padindex - self.eng.shiftindex, len(xdat) - self.eng.shiftindex] + xdat = xdat[self.indexrange[0]:self.indexrange[1]] + data *= np.amax(self.eng.fulltic) / np.amax(data) + if not self.view.plottic.flag: + self.view.plottic.plotrefreshtop(xdat, data, config=self.eng.config, + zoomout=True, label=label, xlabel=xlab, + color=color, nopaint=False) + else: + self.view.plottic.plotadd(xdat, data, colval=color, nopaint=False, + newlabel=label) + self.view.plottic.add_legend() + + def on_autocorr2(self, index): + """ + Manual Test - Passed + :param index: + :return: + """ + data = self.cc.chromatograms[index].chromdat + data[:, 0] = np.arange(0, len(data)) + dlg = AutocorrWindow.AutocorrWindow(self.view) + dlg.initalize_dialog(self.eng.config, data, window=10) + dlg.ShowModal() + + def on_export_arrays(self, e=None): + self.export_config(self.eng.config.confname) + self.export_arrays() + + def export_arrays(self): + # Export Kernel Array + np.savetxt(self.eng.config.outfname + "_htkernel.txt", self.eng.htkernel) + # Export Chromatograms + for c in self.cc.chromatograms: + if c.ignore: + continue + newlabel = c.label.replace("/", "") + newlabel = newlabel.replace(" ", "_") + newlabel = newlabel.replace(":", "_") + newlabel = os.path.join(os.path.split(self.eng.config.outfname)[0], newlabel) + np.savetxt(newlabel + "_chrom.txt", c.chromdat) + print("Saved Files") + + +if __name__ == "__main__": + multiprocessing.freeze_support() + app = UniChromCDApp() + app.start() diff --git a/unidec/UniDecCD.py b/unidec/UniDecCD.py index e7d6a5b2..ab6dc7c5 100644 --- a/unidec/UniDecCD.py +++ b/unidec/UniDecCD.py @@ -32,6 +32,8 @@ def __init__(self, *args, **kwargs): """ UniDecPres.__init__(self, *args, **kwargs) self.init(*args, **kwargs) + self.showht = False + self.comparedata = None def init(self, *args, **kwargs): """ @@ -42,7 +44,6 @@ def init(self, *args, **kwargs): """ self.eng = CDEng.UniDecCD() self.view = CDWindow.CDMainwindow(self, "UCD: UniDec for Charge Detection-Mass Spectrometry", self.eng.config) - self.comparedata = None pub.subscribe(self.on_get_mzlimits, 'mzlimits') pub.subscribe(self.on_smash, 'smash') @@ -223,7 +224,10 @@ def on_unidec_button(self, e=None): self.view.SetStatusText("Deconvolving", number=5) # self.view.clear_all_plots() self.export_config(self.eng.config.confname) - self.eng.run_deconvolution() + if self.showht: + self.eng.run_deconvolution(process_data=False) + else: + self.eng.run_deconvolution() self.makeplot1() self.makeplot2() self.makeplot3() @@ -232,11 +236,14 @@ def on_unidec_button(self, e=None): pass def on_pick_peaks(self, e=None): + self.pick_peaks() + + def pick_peaks(self, e=None): + """ + Pick peaks and perform initial plots on them. + :param e: unused space for event + :return: None """ - Pick peaks and perform initial plots on them. - :param e: unused space for event - :return: None - """ print("Peak Picking") self.view.SetStatusText("Detecting Peaks", number=5) tstart = time.perf_counter() diff --git a/unidec/UniDecHT.py b/unidec/UniDecHT.py deleted file mode 100644 index 985d122f..00000000 --- a/unidec/UniDecHT.py +++ /dev/null @@ -1,244 +0,0 @@ -from UniDecCD import UniDecCDApp -import multiprocessing -import modules.HTEng as HTEng -from unidec.modules.gui_elements import CDWindow -from pubsub import pub -import wx -import unidec.tools as ud -import numpy as np -from unidec.modules import AutocorrWindow -from unidec.modules.unidecstructure import ChromatogramContainer -import os - - -class UniDecHTCDApp(UniDecCDApp): - """ - Main UniDec GUI Application. - Presenter contains UniDec engine at self.eng and main GUI window at self.view - """ - - def init(self, *args, **kwargs): - """ - Initialize Engine and View. Load defaults. - :param args: - :param kwargs: - :return: - """ - self.eng = HTEng.UniDecCDHT() - self.cc = ChromatogramContainer() - self.showht = False - self.cycol = ud.create_color_cycle("bgcmy") - - self.view = CDWindow.CDMainwindow(self, "UniDecHT for HT-CD-MS Data", - self.eng.config, htmode=True) - self.comparedata = None - - pub.subscribe(self.on_select_mzz_region, 'mzlimits') - pub.subscribe(self.on_smash, 'smash') - - self.eng.config.recentfile = self.eng.config.recentfileCD - self.recent_files = self.read_recent() - self.cleanup_recent_file(self.recent_files) - self.view.menu.update_recent() - - self.on_load_default(0) - - if "path" in kwargs: - newdir, fname = os.path.split(kwargs["path"]) - self.on_open_file(fname, newdir) - # self.on_dataprep_button(0) - # self.on_auto(0) - - if self.infile is not None: - newdir, fname = os.path.split(self.infile) - self.on_open_file(fname, newdir) - # self.on_dataprep_button(0) - # self.on_auto(0) - - if True: # and platform.node() == 'DESKTOP-08TGCJO': - print("Opening Test File") - path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" - "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") - - self.on_open_file(None, None, path=path) - - def on_open_file(self, filename, directory, path=None, refresh=False): - """ - Opens a file. Run self.eng.open_file. - :param filename: File name - :param directory: Directory containing file - :param path: Full path to file - :param refresh: Refresh the data ranges from the file. Default False. - :return: None - """ - self.cc.clear() - self.on_open_cdms_file(filename, directory, path=path, refresh=refresh) - self.load_chroms(self.eng.config.cdchrom) - - def on_dataprep_button(self, e=None): - """ - Run data preparation. Run self.eng.process_data_scans. - :param e: Unused event - :return: None - """ - self.cc.clear() - self.dataprep() - self.make_tic_plot() - - def on_select_mzz_region(self): - self.export_config(self.eng.config.confname) - if not wx.GetKeyState(wx.WXK_CONTROL): - xlimits = self.view.plot1.subplot1.get_xlim() - ylimits = self.view.plot1.subplot1.get_ylim() - print("New limits:", xlimits, ylimits) - self.view.plot1.reset_zoom() - color = next(self.cycol) - if self.showht: - self.run_eic_ht(xlimits, ylimits, color=color) - else: - self.add_eic(xlimits, ylimits, color=color) - - def make_tic_plot(self): - data = self.eng.get_tic(normalize=self.eng.config.datanorm) - self.cc.add_chromatogram(data, color="black", label="TIC") - - self.showht = False - self.plot_chromatograms(save=False) - - def add_eic(self, mzrange, zrange, color='b'): - eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) - self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) - - self.plot_chromatograms() - - def on_run_tic_ht(self, e=None): - self.export_config(self.eng.config.confname) - self.run_tic_ht() - - def on_run_eic_ht(self, e=None): - self.export_config(self.eng.config.confname) - for c in self.cc.chromatograms: - if "TIC" not in c.label: - if "HT" not in c.label: - self.run_eic_ht(c.mzrange, c.zrange, color=c.color, add_eic=False, plot=False) - self.plot_chromatograms() - - def run_tic_ht(self): - self.cc.clear() - data = self.eng.get_tic(normalize=self.eng.config.datanorm) - htdata = self.eng.tic_ht(normalize=self.eng.config.datanorm) - self.cc.add_chromatogram(data, color="black", label="TIC") - self.cc.add_chromatogram(htdata, color="red", label="TIC HT", ht=True) - - self.showht = True - self.plot_chromatograms(save=False) - - def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): - htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) - if add_eic: - self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) - self.cc.add_chromatogram(htdata, color=color, zrange=zrange, mzrange=mzrange, ht=True) - if plot: - self.plot_chromatograms() - - def plot_chromatograms(self, e=None, save=True): - self.makeplot1() - self.view.plottic.clear_plot() - self.view.chrompanel.list.populate(self.cc) - for c in self.cc.chromatograms: - if c.ignore: - continue - - if self.eng.config.HTxaxis == "Scans": - xdat = self.eng.fullscans - xlab = "Scan Number" - else: - xdat = c.chromdat[:, 0] - xlab = "Time" - - if not self.view.plottic.flag: - self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, - zoomout=True, label=c.label, xlabel=xlab, - color=c.color, nopaint=True) - else: - self.view.plottic.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, - newlabel=c.label) - - xlimits = c.mzrange - ylimits = c.zrange - if xlimits[0] != -1 and ylimits[0] != -1 and xlimits[1] != -1 and ylimits[1] != -1: - self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], - edgecolor=c.color, facecolor=c.color, nopaint=True) - - self.view.plottic.add_legend() - self.view.plottic.repaint() - self.view.plot1.repaint() - if save: - self.save_chroms() - - def save_chroms(self): - chromarray = self.cc.to_array() - np.savetxt(self.eng.config.cdchrom, chromarray, delimiter="\t", fmt="%s") - - def load_chroms(self, fname=None, array=None): - if fname is not None: - if not os.path.isfile(fname): - print("Chrom file not found:", fname) - return - array = np.loadtxt(fname, delimiter="\t", dtype=str) - - if ud.isempty(array): - return - if len(array.shape) == 1: - array = np.reshape(array, (1, len(array))) # Make sure it is 2D - print(array) - for a in array: - print(a) - label = a[0] - color = a[1] - index = a[2] - ht = a[8] - mzrange = [float(a[3]), float(a[4])] - zrange = [float(a[5]), float(a[6])] - print(mzrange, zrange) - if label == "TIC" or "HT" in label: - continue - chromdat = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) - - self.cc.add_chromatogram(chromdat, color=color, label=label, ht=ht, zrange=zrange, mzrange=mzrange) - - self.plot_chromatograms() - - def on_ignore_repopulate(self, e=None): - self.cc = self.view.chrompanel.list.get_data() - self.plot_chromatograms() - - def on_run_all_ht(self, e=None): - self.run_all_ht() - - def run_all_ht(self): - self.eng.process_data_scans() - self.eng.run_ht() - - def on_auto_set_ct(self, e=None): - self.eng.get_cycle_time() - self.eng.config.HTcycleindex = self.eng.cycleindex - self.import_config() - - def on_autocorr2(self, index): - """ - Manual Test - Passed - :param index: - :return: - """ - data = self.cc.chromatograms[index].chromdat - data[:, 0] = np.arange(0, len(data)) - dlg = AutocorrWindow.AutocorrWindow(self.view) - dlg.initalize_dialog(self.eng.config, data, window=10) - dlg.ShowModal() - - -if __name__ == "__main__": - multiprocessing.freeze_support() - app = UniDecHTCDApp() - app.start() diff --git a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat index 46227561..a1eb5998 100644 --- a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat +++ b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat @@ -61,9 +61,16 @@ kernel CDslope 0.2074 CDzbins 1.0 CDres 1.0 -CDScanStart -1.0 -CDScanEnd -1.0 -HTksmooth 0 +CDScanStart +CDScanEnd +HTksmooth 0.0 +CDScanCompresse 0 +HTtimepad 0.0 +HTanalysistime 38.0 +HTxaxis Time +HTcycleindex -1 +HTtimepad 0.0 +htbit 0 csig 4.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/modules/CDEng.py b/unidec/modules/CDEng.py index 9f7d4321..e0fee0c5 100644 --- a/unidec/modules/CDEng.py +++ b/unidec/modules/CDEng.py @@ -121,11 +121,12 @@ def __init__(self): self.config.mzbins = 1 self.config.rawflag = 1 self.config.poolflag = 1 - self.exemode=True + self.exemode = True + self.massaxis = None pass def exe_mode(self, exemode=True): - self.exemode=exemode + self.exemode = exemode def open_file(self, path, refresh=False): """ @@ -431,6 +432,10 @@ def process_data(self, transform=True): except: pass + # Compress Scans + if self.config.CDScanCompress > 1: + self.farray[:, 2] = np.floor(self.farray[:, 2] / self.config.CDScanCompress) + # Filter m/z print("Filtering m/z range:", self.config.minmz, self.config.maxmz, "Start Length:", len(self.farray)) self.filter_mz(mzrange=[self.config.minmz, self.config.maxmz]) @@ -453,20 +458,23 @@ def process_data(self, transform=True): if len(self.harray) > 0: self.harray = self.hist_data_prep() + self.harray_process(transform=transform) - self.data.data2 = np.transpose([self.mz, np.sum(self.harray, axis=0)]) - np.savetxt(self.config.infname, self.data.data2) - - print("Transforming m/z to mass:", self.config.massbins, "Start Length:", len(self.farray)) - if transform: - self.transform() - np.savetxt(self.config.massdatfile, self.data.massdat) - self.unprocessed = deepcopy(self.data.massdat) else: print("ERROR: Empty histogram array on process") return 0 print("Process Time:", time.perf_counter() - starttime) + def harray_process(self, transform=True): + self.data.data2 = np.transpose([self.mz, np.sum(self.harray, axis=0)]) + np.savetxt(self.config.infname, self.data.data2) + + print("Transforming m/z to mass:", self.config.massbins, "Start Length:", len(self.farray)) + if transform: + self.transform() + np.savetxt(self.config.massdatfile, self.data.massdat) + self.unprocessed = deepcopy(self.data.massdat) + def filter_int(self, int_range): """ Filter the self.farray to include only intensities within the int_range. @@ -635,7 +643,6 @@ def hist_int_threshold(self, harray, int_threshold): harray *= boo1 return harray - def hist_datareduction(self, harray, red_per): sdat = np.sort(np.ravel(harray)) index = round(len(sdat) * red_per / 100.) @@ -713,15 +720,9 @@ def hist_nativeZ_filter(self, nativeZrange=None): # Set values outside range to 0 self.harray[boo3] = 0 - def transform(self, harray=None, dataobj=None): + def create_mass_axis(self, harray=None): if harray is None: harray = self.harray - if dataobj is None: - dataobj = self.data - # Test for if array is empty and error if so - if len(harray) == 0: - print("ERROR: Empty histogram array on transform") - return 0 # filter out zeros harray = np.array(harray) @@ -732,27 +733,41 @@ def transform(self, harray=None, dataobj=None): if ud.isempty(mass): print("ERROR: Empty histogram array on transform") return 0 - # Find the max and min and create the new linear mass axis minval = np.amax([np.amin(mass) - self.config.massbins * 3, self.config.masslb]) maxval = np.amin([np.amax(mass) + self.config.massbins * 3, self.config.massub]) minval = round(minval / self.config.massbins) * self.config.massbins # To prevent weird decimals massaxis = np.arange(minval, maxval, self.config.massbins) + return massaxis + + def transform(self, harray=None, dataobj=None): + if harray is None: + harray = self.harray + if dataobj is None: + dataobj = self.data + # Test for if array is empty and error if so + if len(harray) == 0: + print("ERROR: Empty histogram array on transform") + return 0 + + massaxis = self.create_mass_axis(harray) # Create the mass grid dataobj.massgrid = [] for i in range(len(self.ztab)): d = harray[i] - boo1 = d > 0 - newdata = np.transpose([self.mass[i][boo1], d[boo1]]) - if len(newdata) < 2: - dataobj.massgrid.append(massaxis * 0) + if self.config.poolflag == 1: + newdata = np.transpose([self.mass[i], d]) + massdata = ud.linterpolate(newdata, massaxis) else: - if self.config.poolflag == 1: - massdata = ud.linterpolate(newdata, massaxis) + boo1 = d > 0 + newdata = np.transpose([self.mass[i][boo1], d[boo1]]) + if len(newdata) < 2: + massdata = np.transpose([massaxis, massaxis * 0]) else: massdata = ud.lintegrate(newdata, massaxis) - dataobj.massgrid.append(massdata[:, 1]) + + dataobj.massgrid.append(massdata[:, 1]) dataobj.massgrid = np.transpose(dataobj.massgrid) # Create the linearized mass data by integrating everything into the new linear axis dataobj.massdat = np.transpose([massaxis, np.sum(dataobj.massgrid, axis=1)]) @@ -764,6 +779,7 @@ def transform(self, harray=None, dataobj=None): dataobj.zdat = np.transpose([self.ztab, np.sum(harray, axis=1)]) dataobj.mzgrid = np.transpose( [np.ravel(self.X.transpose()), np.ravel(self.Y.transpose()), np.ravel(harray.transpose())]) + self.massaxis = massaxis return dataobj def transform_mzmass(self): @@ -1023,14 +1039,15 @@ def decon_core(self): return self.harray - def run_deconvolution(self): + def run_deconvolution(self, process_data=True): """ Function for running the full deconvolution sequence, including setup, pre-, and post-processing. :return: None """ - # Process data but don't transform - self.process_data(transform=False) + if process_data: + # Process data but don't transform + self.process_data(transform=False) # Filter histogram to remove masses that are not allowed self.harray = self.hist_mass_filter(self.harray) # Filter histogram to remove charge states that aren't allowed based on the native charge state filter @@ -1056,7 +1073,7 @@ def decon_external_call(self): # Check for this if self.config.CDzbins != 1 and self.config.zzsig != 0: print("ERROR: Charge smoothing is only define for when charges are binned to unit charge") - self.harray=[[]] + self.harray = [[]] return # Output input data self.harray = np.array(self.harray) diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 77e55862..dfda276d 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -1,3 +1,5 @@ +import time + import scipy.ndimage from modules.fitting import * @@ -7,7 +9,11 @@ from modules.unidecstructure import DataContainer import matplotlib.pyplot as plt +import scipy.fft as fft + +# import fast_histogram +# import pyfftw.interfaces.numpy_fft as fft class HTEng: def __init__(self, *args, **kwargs): @@ -123,9 +129,12 @@ def setup_ht(self, cycleindex=None): self.cycleindex = int(cycleindex) print("Correction Diff:", diff) + self.rollindex = int(-self.shiftindex + self.cycleindex * self.kernelroll) + # Create the HT kernel self.htkernel = np.zeros_like(self.fullscans[int(self.padindex):]).astype(float) - print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex) + print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex, + "Shift Index:", self.shiftindex, "Roll Index:", self.rollindex) index = 0 for i, s in enumerate(self.fullscans): @@ -143,19 +152,18 @@ def setup_ht(self, cycleindex=None): self.gausskernel += ndis(self.fullscans[self.padindex:], np.amax(self.fullscans[self.padindex:]) + 1, self.config.HTksmooth) self.gausskernel /= np.sum(self.gausskernel) - self.fftg = np.fft.fft(self.gausskernel) - self.htkernel = np.fft.ifft(np.fft.fft(self.htkernel) * self.fftg) + self.fftg = fft.rfft(self.gausskernel) + self.htkernel = fft.irfft(fft.rfft(self.htkernel) * self.fftg).real # Make fft of kernel for later use - self.fftk = np.fft.fft(self.htkernel).conj() + self.fftk = fft.rfft(self.htkernel).conj() + + def correct_cycle_time(self): + print("Correcting") + self.get_cycle_time(data) + self.setup_ht(cycleindex=self.cycleindex) def htdecon(self, data, *args, **kwargs): - # Whether to correct the kernel to optimize the cycle time - if "correct" in kwargs: - if kwargs["correct"]: - print("Correcting") - self.get_cycle_time(data) - self.setup_ht(cycleindex=self.cycleindex) # Whether to smooth the data before deconvolution if "gsmooth" in kwargs: data = scipy.ndimage.gaussian_filter1d(data, kwargs["gsmooth"]) @@ -167,18 +175,59 @@ def htdecon(self, data, *args, **kwargs): # Set the range of indexes used in the deconvolution # Starts at the pad but shift will move it back self.indexrange = [self.padindex - self.shiftindex, len(data) - self.shiftindex] - # print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex, "Len:", - # len(data)) + # print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex) + # Do the convolution - output = np.fft.ifft( - np.fft.fft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk) + output = fft.irfft(fft.rfft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk).real # Shift the output back to the original time if self.padindex > 0: - # add zeros back on the front - rollindex = int(-self.shiftindex + self.cycleindex * self.kernelroll) - output = np.roll(np.concatenate((np.zeros(self.padindex), output)), rollindex) + # add zeros back on the front and roll to the correct index + output = np.roll(np.concatenate((np.zeros(self.padindex), output)), self.rollindex) + + if "normalize" in kwargs: + if kwargs["normalize"]: + output /= np.amax(output) + # Return demultiplexed data + return output + + def htdecon_speedy(self, data): + # Do the convolution, and only the convolution... :) + return fft.irfft(fft.rfft(data) * self.fftk).real + + def decon_3d_fft(self, array, *args, **kwargs): + """ + Developed this to see if it would speed things up. It turns out not to. About half as slow. Leaving in for + legacy reasons and because it's super cool code. + :param array: + :param args: + :param kwargs: + :return: + """ + starttime = time.perf_counter() + # Slice data to appropriate range + dims = np.shape(array) + self.indexrange = [self.padindex - self.shiftindex, dims[0] - self.shiftindex] + data = array[self.indexrange[0]:self.indexrange[1]] + dims2 = np.shape(data) + print(dims2) + + # HT Kernel 3D FFT + # Create 3D array of copies of 1D kernel + kernel3d = np.broadcast_to(self.htkernel[:, np.newaxis, np.newaxis], dims2) + + print("3D Kernel", np.shape(kernel3d), time.perf_counter() - starttime) + # FFT of kernel3d + fftk3d = fft.rfftn(kernel3d).conj() + print("3D FFT of Kernel", np.shape(fftk3d), time.perf_counter() - starttime) + + data_fft = fft.rfftn(data) + print("3D FFT of Data", np.shape(data_fft), time.perf_counter() - starttime) + + # Deconvolve + output = fft.irfftn(data_fft * fftk3d) + print("Decon", np.shape(output), time.perf_counter() - starttime) output = np.real(output) if "normalize" in kwargs: if kwargs["normalize"]: @@ -263,6 +312,8 @@ class UniDecCDHT(HTEng, UniDecCD): def __init__(self, *args, **kwargs): super(UniDecCDHT, self).__init__(*args, **kwargs) print("HT-CD-MS Engine") + self.config.poolflag = 0 + self.hstack = None self.fullhstack = None self.topfarray = None @@ -273,7 +324,11 @@ def __init__(self, *args, **kwargs): self.X = None self.Y = None self.mass = None - self.fullstack_ht = None + self.fullhstack_ht = None + self.fullmstack = None + self.fullmstack_ht = None + self.mass_tic = None + self.mass_tic_ht = None self.mz = None self.ztab = None @@ -281,6 +336,15 @@ def open_file(self, path, refresh=False): self.open_cdms_file(path, refresh=refresh) self.parse_file_name(path) + def clear_arrays(self, massonly=False): + if not massonly: + self.fullhstack = None + self.fullhstack_ht = None + self.fullmstack = None + self.fullmstack_ht = None + self.mass_tic = None + self.mass_tic_ht = None + def prep_time_domain(self): self.scans = np.unique(self.farray[:, 2]) self.fullscans = np.arange(1, np.amax(self.scans) + 1) @@ -296,8 +360,8 @@ def process_data_scans(self, transform=True): :return: None """ starttime = time.perf_counter() - self.process_data() - + # self.process_data() + self.clear_arrays() self.prep_time_domain() self.topfarray = deepcopy(self.farray) @@ -309,7 +373,7 @@ def process_data_scans(self, transform=True): # Create a stack with one histogram for each scan self.hstack = np.zeros((len(self.scans), self.topharray.shape[0], self.topharray.shape[1])) - print("Creating Histograms for Each Scan") + print("Creating Histograms for Each Scan", time.perf_counter() - starttime) # Loop through scans and create histograms for i, s in enumerate(self.scans): # Pull out subset of data for this scan @@ -317,31 +381,29 @@ def process_data_scans(self, transform=True): # Create histogram harray = self.histogramLC(x=self.topfarray[b1, 0], y=self.topzarray[b1]) - - # Transform histogram m/z to mass - # NEED TO WORK ON THIS SECTION - if len(harray) > 0 and np.amax(harray) > 0 and False: - print("TRANSFORMING") - harray = self.hist_data_prep(harray) - dataobj = DataContainer() - if transform: - dataobj = self.transform(harray=harray, dataobj=dataobj) + harray = self.hist_data_prep(harray) # Add histogram to stack - self.topharray += harray self.hstack[i] = harray + self.topharray = np.sum(self.hstack, axis=0) + # Normalize the histogram if self.config.datanorm == 1: maxval = np.amax(self.topharray) if maxval > 0: self.topharray /= maxval - + print("T:", time.perf_counter() - starttime) # Reshape the data into a 3 column array - self.data.data3 = np.transpose([np.ravel(self.X, order="F"), np.ravel(self.Y, order="F"), - np.ravel(self.topharray, order="F")]) + # self.data.data3 = np.transpose([np.ravel(self.X, order="F"), np.ravel(self.Y, order="F"), + # np.ravel(self.topharray, order="F")]) - print("Process Time:", time.perf_counter() - starttime) + # create full hstack to fill any holes in the data for scans with no ions + self.fullhstack = np.zeros((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) + for i, s in enumerate(self.scans): + self.fullhstack[int(s) - 1] = self.hstack[i] + + print("Process Time HT:", time.perf_counter() - starttime) def prep_hist(self, mzbins=1, zbins=1, mzrange=None, zrange=None): # Set up parameters @@ -395,6 +457,9 @@ def histogramLC(self, x=None, y=None): return harray # Create histogram harray, mz, ztab = np.histogram2d(x, y, [self.mzaxis, self.zaxis]) + # harray = fast_histogram.histogram2d(x, y, [len(self.mzaxis)-1, len(self.zaxis)-1], + # [(np.amin(self.mzaxis), np.amax(self.mzaxis)), + # (np.amin(self.zaxis), np.amax(self.zaxis))]) # Transpose and return harray = np.transpose(harray) return harray @@ -449,22 +514,169 @@ def eic_ht(self, mzrange, zrange, *args, **kwargs): self.htoutput = self.htdecon(eic[:, 1], *args, **kwargs) return np.transpose([self.fulltime, self.htoutput]), eic - def run_ht(self): - # create full hstack to fill any holes in the data for scans with no ions - self.fullhstack = np.zeros((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) - self.fullhstack_ht = np.zeros((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) - for i, s in enumerate(self.scans): - self.fullhstack[int(s) - 1] = self.hstack[i] + def run_all_ht(self): + starttime = time.perf_counter() + if self.fullhstack is None: + self.process_data_scans() + self.clear_arrays(massonly=True) + # Setup HT + self.setup_ht() + ''' + # self.fullhstack_ht = self.decon_3d(self.fullhstack) + starttime = time.perf_counter() + # Prep the ranges + self.indexrange = [self.padindex - self.shiftindex, len(self.fullhstack) - self.shiftindex] + substack = self.fullhstack[self.indexrange[0]:self.indexrange[1]] + substack_ht = np.empty_like(substack) + sumgrid = np.sum(substack, axis=0) # Run the HT on each track in the stack - # TEST THIS! - self.setup_ht() for i, x in enumerate(self.mz): for j, y in enumerate(self.ztab): + trace = substack[:, j, i] + tracesum = sumgrid[j, i] + if tracesum <= self.config.intthresh or tracesum <= 2: + htoutput = np.zeros_like(trace) + else: + htoutput = self.htdecon_speedy(trace) + substack_ht[:, j, i] = htoutput + + # Shift the output back to the original time + if self.padindex > 0: + # add zeros back on the front and roll to the correct index + zeroarray = np.zeros((self.padindex, substack_ht.shape[1], substack_ht.shape[2])) + self.fullhstack_ht = np.roll(np.concatenate((zeroarray, substack_ht)), self.rollindex, axis=0) + else: + self.fullhstack_ht = substack_ht + + print("Full HT Demultiplexing Done:", time.perf_counter() - starttime) + starttime = time.perf_counter()''' + # Run the HT on each track in the stack + self.fullhstack_ht = np.empty((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) + + for i in range(len(self.mz)): + for j in range(len(self.ztab)): trace = self.fullhstack[:, j, i] - htoutput = self.htdecon(trace) + tracesum = self.topharray[j, i] + if tracesum <= self.config.intthresh: + htoutput = np.zeros_like(trace) + else: + htoutput = self.htdecon(trace) self.fullhstack_ht[:, j, i] = htoutput + # Clip all values below 1e-6 to zero + # self.fullhstack_ht[np.abs(self.fullhstack_ht) < 1e-6] = 0 + + tic = np.sum(self.fullhstack_ht, axis=(1, 2)) + ticdat = np.transpose(np.vstack((self.fulltime, tic))) + if self.config.datanorm == 1: + norm = np.amax(ticdat[:, 1]) + ticdat[:, 1] /= norm + self.fullhstack_ht /= norm + + print("Full HT Demultiplexing Done:", time.perf_counter() - starttime) + return ticdat + + def select_ht_range(self, range=None): + if range is None: + range = [np.amin(self.fulltime), np.amax(self.fulltime)] + b1 = self.fulltime >= range[0] + b2 = self.fulltime <= range[1] + b = np.logical_and(b1, b2) + substack_ht = self.fullhstack_ht[b] + self.harray = np.sum(substack_ht, axis=0) + self.harray = np.clip(self.harray, 0, np.amax(self.harray)) + self.harray_process() + + def transform_array(self, array): + mlen = len(self.massaxis) + outarray = np.zeros((len(array), mlen)) + + for j in range(len(self.ztab)): + indexes = np.array([ud.nearest(self.massaxis, m) for m in self.mass[j]]) + uindexes = np.unique(indexes) + + subarray = array[:, j] + # Sum together everything with the same index + subarray = np.transpose([np.sum(subarray[:, indexes == u], axis=1) for u in uindexes]) + # Add to the output array + outarray[:, uindexes] += subarray + + return outarray + + def transform_stacks(self): + if self.massaxis is None: + self.process_data(transform=True) + if self.fullhstack is None: + self.process_data_scans(transform=True) + + if self.config.poolflag == 1: + print("Transforming Stacks by Interpolation") + else: + print("Transforming Stacks by Integration") + starttime = time.perf_counter() + mlen = len(self.massaxis) + + self.fullmstack = self.transform_array(self.fullhstack) + + self.mass_tic = np.transpose([self.fulltime, np.sum(self.fullmstack, axis=1)]) + + if self.config.datanorm == 1: + norm = np.amax(self.mass_tic[:, 1]) + self.mass_tic[:, 1] /= norm + self.fullmstack /= norm + + print("Full Mass 1 Transform Done:", time.perf_counter() - starttime) + ''' + self.fullmstack = np.zeros((len(self.fullscans), mlen)) + for i in range(len(self.fullscans)): + for j in range(len(self.ztab)): + d = self.fullhstack[i, j] + + if self.config.poolflag == 1: + newdata = np.transpose([self.mass[j], d]) + output = ud.linterpolate(newdata, self.massaxis)[:, 1] + else: + boo1 = d != 0 + newdata = np.transpose([self.mass[j][boo1], d[boo1]]) + if len(newdata) == 0: + output = self.massaxis * 0 + else: + output = ud.lintegrate(newdata, self.massaxis, fastmode=True)[:, 1] + + self.fullmstack[i, :] += output + + self.mass_tic = np.transpose([self.fulltime, np.sum(self.fullmstack, axis=1)]) + print("Full Mass 1 Transform Done:", time.perf_counter() - starttime)''' + + if self.fullhstack_ht is None: + return + + self.fullmstack_ht = self.transform_array(self.fullhstack_ht) + + self.mass_tic_ht = np.transpose([self.fulltime, np.sum(self.fullmstack_ht, axis=1)]) + + if self.config.datanorm == 1: + norm = np.amax(self.mass_tic_ht[:, 1]) + self.mass_tic_ht[:, 1] /= norm + self.fullmstack_ht /= norm + print("Full Mass 2 Transform Done:", time.perf_counter() - starttime) + + def get_mass_eic(self, massrange, ht=False): + # Filter fullmstack + b1 = self.massaxis >= massrange[0] + b2 = self.massaxis <= massrange[1] + b = np.logical_and(b1, b2) + + if ht: + array = self.fullmstack_ht + else: + array = self.fullmstack + + substack = array[:, b] + mass_eic = np.sum(substack, axis=1) + return np.transpose([self.fulltime, mass_eic]) + if __name__ == '__main__': @@ -502,7 +714,7 @@ def run_ht(self): plt.show() exit() - eng.run_ht() + eng.run_all_ht() print(np.shape(eng.hstack)) plt.figure() diff --git a/unidec/modules/PlottingWindow.py b/unidec/modules/PlottingWindow.py index 1ed42204..93c9db38 100644 --- a/unidec/modules/PlottingWindow.py +++ b/unidec/modules/PlottingWindow.py @@ -155,6 +155,7 @@ def on_release(self, event): except: print("Could not switch on labels") if event.button == 2 or (event.button == 1 and wx.GetKeyState(wx.WXK_DOWN)): + # Middle Clicks if wx.GetKeyState(wx.WXK_CONTROL): dlg = DoubleInputDialog(self) dlg.initialize_interface("Matplotlib RC Parameters", "RC Param Name:", 'lines.markersize', @@ -214,6 +215,8 @@ def on_key(self, evt): if evt.key == "ctrl+u": self.on_write_dialog(evt) + + def on_save_fig_dialog(self, evt): """ Open a save figure dialog for specified plot. @@ -395,7 +398,6 @@ def on_left_click(self, x, y): def on_right_click(self, event=None): if self.int == 1: - # print "rightclick" pub.sendMessage('integrate') elif self.smash == 1: if event.dblclick: @@ -407,6 +409,9 @@ def on_right_click(self, event=None): elif self.smash == 2: event = ScanSelectedEvent(MZLimitsEventType, self.GetId()) self.GetEventHandler().ProcessEvent(event) + else: + event = ScanSelectedEvent(ScanSelectedEventType, self.GetId()) + self.GetEventHandler().ProcessEvent(event) class Plot1d(PlottingWindowBase, Plot1dBase): """ diff --git a/unidec/modules/gui_elements/CDMenu.py b/unidec/modules/gui_elements/CDMenu.py index 46fd06af..daee8c6d 100644 --- a/unidec/modules/gui_elements/CDMenu.py +++ b/unidec/modules/gui_elements/CDMenu.py @@ -6,12 +6,13 @@ class CDMenu(wx.Menu): # noinspection PyMissingConstructor - def __init__(self, parent, config, pres, tabbed): + def __init__(self, parent, config, pres, tabbed, htmode=False): super(wx.Menu, self).__init__() self.pres = pres self.config = config self.parent = parent self.tabbed = tabbed + self.htmode = htmode self.filemenu = wx.Menu() self.toolsmenu = wx.Menu() @@ -28,6 +29,14 @@ def __init__(self, parent, config, pres, tabbed): self.menuLoad = self.filemenu.Append(wx.ID_ANY, "Load External Config File", "Load in a configuration file") + if self.htmode: + # Load chrom file + self.filemenu.AppendSeparator() + self.menuLoadChrom = self.filemenu.Append(wx.ID_ANY, "Load Chromatogram File", + "Load in a chromatogram file") + self.parent.Bind(wx.EVT_MENU, self.pres.on_load_chroms, self.menuLoadChrom) + self.filemenu.AppendSeparator() + ''' self.menuLoadEverything = self.filemenu.Append(wx.ID_ANY, "Load Prior State for Current File\tCtrl+L", "Load past deconvolution results from the current opened file") @@ -122,6 +131,17 @@ def __init__(self, parent, config, pres, tabbed): self.menucal = self.toolsmenu.Append(wx.ID_ANY, "Calibration Tool") self.parent.Bind(wx.EVT_MENU, self.pres.on_calibrate, self.menucal) + if self.htmode: + self.toolsmenu.AppendSeparator() + self.menuexportHT = self.toolsmenu.Append(wx.ID_ANY, "Export Chromatograms") + self.parent.Bind(wx.EVT_MENU, self.pres.on_export_arrays, self.menuexportHT) + + self.menuplotkernel = self.toolsmenu.Append(wx.ID_ANY, "Plot Kernel") + self.parent.Bind(wx.EVT_MENU, self.pres.on_plot_kernel, self.menuplotkernel) + + self.menuautocycle = self.toolsmenu.Append(wx.ID_ANY, "Print Optimal Cycle Index") + self.parent.Bind(wx.EVT_MENU, self.pres.on_auto_set_ct, self.menuautocycle) + self.toolsmenu.AppendSeparator() self.menustori = self.toolsmenu.Append(wx.ID_ANY, "Convert STORI Folder of CSVs") self.parent.Bind(wx.EVT_MENU, self.pres.on_stori, self.menustori) diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index ee88cee1..1145ad75 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -5,7 +5,7 @@ from unidec.modules.gui_elements import CD_controls from unidec.modules.gui_elements import CDMenu -from unidec.modules import PlottingWindow +from unidec.modules import PlottingWindow, plot3d from unidec.modules import miscwindows from unidec.modules.gui_elements import peaklistsort from unidec.modules.gui_elements.mainwindow_base import MainwindowBase @@ -55,7 +55,7 @@ def __init__(self, parent, title, config, iconfile=None, tabbed=None, htmode=Fal self.twave = self.config.twaveflag > 0 - self.menu = CDMenu.CDMenu(self, self.config, self.pres, self.tabbed) + self.menu = CDMenu.CDMenu(self, self.config, self.pres, self.tabbed, htmode=self.htmode) self.SetMenuBar(self.menu.menuBar) self.setup_main_panel() @@ -134,6 +134,8 @@ def setup_main_panel(self): self.plot5 = PlottingWindow.Plot2d(tab5, figsize=figsize) self.plot6 = PlottingWindow.Plot1d(tab6, figsize=figsize) + + miscwindows.setup_tab_box(tab1, self.plot1) miscwindows.setup_tab_box(tab2, self.plot2) miscwindows.setup_tab_box(tab3, self.plot3) @@ -142,8 +144,8 @@ def setup_main_panel(self): miscwindows.setup_tab_box(tab6, self.plot6) if self.htmode: - self.plottic = PlottingWindow.Plot1d(tab1, figsize=figsize) tabtic = wx.Panel(plotwindow) + self.plottic = PlottingWindow.Plot1d(tabtic, figsize=figsize) miscwindows.setup_tab_box(tabtic, self.plottic) plotwindow.AddPage(tabtic, "Chromatogram") @@ -153,6 +155,25 @@ def setup_main_panel(self): plotwindow.AddPage(tab4, "m/z Distribution and Peaks") plotwindow.AddPage(tab5, "Mass vs. Charge") plotwindow.AddPage(tab6, "Bar Chart") + + if self.htmode: + tab7 = wx.Panel(plotwindow) + tab8 = wx.Panel(plotwindow) + tab9 = wx.Panel(plotwindow) + tab10 = wx.Panel(plotwindow) + self.plot7 = PlottingWindow.Plot2d(tab7, figsize=figsize) + self.plot8 = PlottingWindow.Plot2d(tab8, figsize=figsize) + self.plot9 = plot3d.CubePlot(tab9, figsize=figsize) + self.plot10 = plot3d.CubePlot(tab10, figsize=figsize) + miscwindows.setup_tab_box(tab7, self.plot7) + miscwindows.setup_tab_box(tab8, self.plot8) + miscwindows.setup_tab_box(tab9, self.plot9) + miscwindows.setup_tab_box(tab10, self.plot10) + plotwindow.AddPage(tab7, "2D Plot Raw") + plotwindow.AddPage(tab8, "2D Plot HT") + plotwindow.AddPage(tab9, "3D Plot Raw") + plotwindow.AddPage(tab10, "3D Plot HT") + # Scrolled panel view of plots else: # TODO: Line up plots on left hand side so that they share an m/z axis @@ -180,6 +201,16 @@ def setup_main_panel(self): sizerplot.Add(self.plot5, (i + 2, 0), span=(1, 1), flag=wx.EXPAND) sizerplot.Add(self.plot6, (i + 2, 1), span=(1, 1), flag=wx.EXPAND) + if self.htmode: + self.plot7 = PlottingWindow.Plot2d(plotwindow, figsize=figsize) + self.plot8 = PlottingWindow.Plot2d(plotwindow, figsize=figsize) + self.plot9 = plot3d.CubePlot(plotwindow, figsize=figsize) + self.plot10 = plot3d.CubePlot(plotwindow, figsize=figsize) + sizerplot.Add(self.plot7, (i + 3, 0), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot8, (i + 3, 1), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot9, (i + 4, 0), span=(1, 1), flag=wx.EXPAND) + sizerplot.Add(self.plot10, (i + 4, 1), span=(1, 1), flag=wx.EXPAND) + # plotwindow.SetScrollbars(1, 1,1,1) if self.system == "Linux": plotwindow.SetSizer(sizerplot) @@ -198,9 +229,10 @@ def setup_main_panel(self): self.plotnames = ["Figure1", "Figure2", "Figure5", "Figure4", "Figure3", "Figure6"] if self.htmode: - self.plots = [self.plottic] + self.plots - self.plotnames = ["Chromatogram"] + self.plotnames + self.plots = [self.plottic] + self.plots + [self.plot7, self.plot8, self.plot9, self.plot10] + self.plotnames = ["Chromatogram"] + self.plotnames + ["Figure7", "Figure8", "Figure9", "Figure10"] self.plottic._axes = [0.07, 0.11, 0.9, 0.8] + self.Bind(self.plottic.EVT_SCANS_SELECTED, self.pres.on_select_time_range, self.plottic) # ........................... # diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 04e8ed39..22df3e17 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -160,6 +160,14 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 + if htmode: + # Control for scan compression + self.ctlscancompress = wx.TextCtrl(panel1, value="", size=size1) + sizercontrol1.Add(self.ctlscancompress, (i, 1), span=(1, 2)) + sizercontrol1.Add(wx.StaticText(panel1, label="Scan Compression: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + self.ctldatanorm = wx.CheckBox(panel1, label="Normalize Data") self.ctldatanorm.SetValue(True) sizercontrol1.Add(self.ctldatanorm, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) @@ -269,17 +277,29 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): self.runticht = wx.Button(panelht, -1, "Run TIC Hadamard Transform") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_tic_ht, self.runticht) sizercontrolht1.Add(self.runticht, (i, 0), span=(1, 2), flag=wx.EXPAND) + self.runticht.SetToolTip(wx.ToolTip("HT Demultiplexing of TIC")) i += 1 # Button for Run EIC HT self.runeicht = wx.Button(panelht, -1, "Run EIC Hadamard Transform") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_eic_ht, self.runeicht) sizercontrolht1.Add(self.runeicht, (i, 0), span=(1, 2), flag=wx.EXPAND) + self.runeicht.SetToolTip(wx.ToolTip("HT Demultiplexing of each EIC selected. " + "To select, zoom on a region of the 2D plot and right click.")) i += 1 self.runallht = wx.Button(panelht, -1, "Run All Hadamard Transform") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_all_ht, self.runallht) sizercontrolht1.Add(self.runallht, (i, 0), span=(1, 2), flag=wx.EXPAND) + self.runallht.SetToolTip(wx.ToolTip("Run HT on all data points. Used to create crazy cubes.")) + i += 1 + + # Button to Mass Transformation + self.masstransform = wx.Button(panelht, -1, "Run All Mass Transform") + self.parent.Bind(wx.EVT_BUTTON, self.pres.run_all_mass_transform, self.masstransform) + sizercontrolht1.Add(self.masstransform, (i, 0), span=(1, 2), flag=wx.EXPAND) + self.masstransform.SetToolTip(wx.ToolTip("Run Mass Transformation on all data points. " + "Click button above first to do HT.")) i += 1 # Text Control for Kernel Smoothing @@ -333,12 +353,41 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 - # Button for Auto Set Cycle Time - self.autosetct = wx.Button(panelht, -1, "Auto Set Cycle Index") - self.parent.Bind(wx.EVT_BUTTON, self.pres.on_auto_set_ct, self.autosetct) - sizercontrolht1.Add(self.autosetct, (i, 0), span=(1, 2), flag=wx.EXPAND) + # Button to make 2d time vs. charge plot + self.maketvsc = wx.Button(panelht, -1, "Make 2D Time vs. Charge Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_charge_time_2dplot, self.maketvsc) + sizercontrolht1.Add(self.maketvsc, (i, 0), span=(1, 2), flag=wx.EXPAND) i += 1 + # Button to make 2d time vs. mz plot + self.maketvsm = wx.Button(panelht, -1, "Make 2D Time vs. m/z Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mz_time_2dplot, self.maketvsm) + sizercontrolht1.Add(self.maketvsm, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + # Button to make 2d time vs. mass plot + self.makevtm = wx.Button(panelht, -1, "Make 2D Time vs. Mass Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_time_2dplot, self.makevtm) + sizercontrolht1.Add(self.makevtm, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + # Button for Make m/z Cube plots + self.makemzcube = wx.Button(panelht, -1, "Make m/z Cube Plots") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_cube_plot, self.makemzcube) + sizercontrolht1.Add(self.makemzcube, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + # Button for make mass cube plot + self.makemasscube = wx.Button(panelht, -1, "Make Mass Cube Plots") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_cube_plot, self.makemasscube) + sizercontrolht1.Add(self.makemasscube, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + # self.autosetct = wx.Button(panelht, -1, "Auto Set Cycle Index") + # self.parent.Bind(wx.EVT_BUTTON, self.pres.on_auto_set_ct, self.autosetct) + # sizercontrolht1.Add(self.autosetct, (i, 0), span=(1, 2), flag=wx.EXPAND) + # i += 1 + panelht.SetSizer(sizercontrolht1) sizercontrolht1.Fit(panelht) self.foldpanels.AddFoldPanelWindow(foldpanelht, panelht, fpb.FPB_ALIGN_WIDTH) @@ -690,6 +739,7 @@ def import_config_to_gui(self): self.update_flag = False if self.config.batchflag == 0: self.ctlmassbins.SetValue(str(self.config.massbins)) + self.ctlstartz.SetValue(str(self.config.startz)) self.ctlendz.SetValue(str(self.config.endz)) self.ctlslope.SetValue(str(self.config.CDslope)) @@ -745,6 +795,7 @@ def import_config_to_gui(self): self.ctlrawflag.SetSelection(self.config.rawflag) if self.htmode: + self.ctlscancompress.SetValue(str(self.config.CDScanCompress)) self.ctlkernelsmooth.SetValue(str(self.config.HTksmooth)) self.ctlhtseq.SetStringSelection(str(self.config.htbit)) self.ctlhttimeshift.SetValue(str(self.config.HTtimeshift)) @@ -836,6 +887,7 @@ def export_gui_to_config(self, e=None): self.config.datanorm = int(self.ctldatanorm.GetValue()) self.config.intthresh = ud.string_to_value(self.ctlintthresh.GetValue()) self.config.massbins = ud.string_to_value(self.ctlmassbins.GetValue()) + self.config.endz = ud.string_to_int(self.ctlendz.GetValue()) self.config.startz = ud.string_to_int(self.ctlstartz.GetValue()) @@ -852,6 +904,7 @@ def export_gui_to_config(self, e=None): self.config.adductmass = ud.string_to_value(self.ctladductmass.GetValue()) if self.htmode: + self.config.CDScanCompress = ud.string_to_value(self.ctlscancompress.GetValue()) self.config.HTksmooth = ud.string_to_value(self.ctlkernelsmooth.GetValue()) self.config.htbit = int(self.ctlhtseq.GetStringSelection()) self.config.HTxaxis = self.ctlxaxis.GetStringSelection() @@ -1022,6 +1075,29 @@ def setup_tool_tips(self): self.ctlzsmoothcheck.SetToolTip( wx.ToolTip("Select whether to assume a smooth charge state distribution")) + + if self.htmode: + self.ctlscancompress.SetToolTip(wx.ToolTip( + "Average each n scans together. This reduces the number of scans by a factor of n.")) + + self.ctlkernelsmooth.SetToolTip(wx.ToolTip( + "Smooth the data with a Gaussian kernel of this many data points.")) + self.ctlhtseq.SetToolTip(wx.ToolTip( + "The Hadamard bit depth. Put bit{n} in file name to set automatically.")) + self.ctlhttimeshift.SetToolTip(wx.ToolTip( + "Shift the HT start time by this amount to account for a delay at the start of the run. " + "Units of retention time.")) + + self.ctltimepad.SetToolTip(wx.ToolTip( + "Pad the time axis by this amount to account for a delay at the end of the run. " + "Units of retention time. Set cyc{n} and zp{m} in file name to set automatically as n and m.")) + self.ctlanalysistime.SetToolTip(wx.ToolTip( + "Total analysis time. Must be set manually for DMT files.")) + self.ctlxaxis.SetToolTip(wx.ToolTip("Select the x-axis unit for plotting.")) + self.ctlcycleindex.SetToolTip(wx.ToolTip( + "The number of scans per cycle. Determined automatically if -1. " + "See tools menu or autocorrelation right click on TIC to get hints on setting this differently.")) + pass # ....................................................... diff --git a/unidec/modules/gui_elements/HTCD_ListCtrls.py b/unidec/modules/gui_elements/HTCD_ListCtrls.py index 1cf6c3fc..3dd4c1e7 100644 --- a/unidec/modules/gui_elements/HTCD_ListCtrls.py +++ b/unidec/modules/gui_elements/HTCD_ListCtrls.py @@ -56,8 +56,21 @@ def populate(self, cc): if color is not None: try: color = mpl.colors.to_rgba(color) - except: - color = np.fromstring(color[1:-1], sep=",", dtype=float) + except Exception as e: + if type(color) is str or type(color) is np.str_: + color = str(color) + if "," in color: + color = np.fromstring(color[1:-1], sep=",", dtype=float) + elif " " in color: + color = np.fromstring(color[1:-1], sep=" ", dtype=float) + else: + print("Color not be recognized:", color) + color = [0, 0, 0, 1] + else: + print("Color not recognized:", color) + print(e) + print(type(color)) + color = [0, 0, 0, 1] c.color = color color = wx.Colour(int(round(color[0] * 255)), int(round(color[1] * 255)), @@ -150,7 +163,7 @@ def get_selected(self): def on_popup_one(self, event): # Delete self.selection = self.get_selected() - for i in range(0, len(self.selection)): + for i in range(0, len(self.selection))[::-1]: self.list.data.chromatograms = np.delete(self.list.data.chromatograms, self.selection[i]) self.list.repopulate() self.pres.on_ignore_repopulate() diff --git a/unidec/modules/plot2d.py b/unidec/modules/plot2d.py index bd795cfd..47811ff6 100644 --- a/unidec/modules/plot2d.py +++ b/unidec/modules/plot2d.py @@ -184,7 +184,6 @@ def contourplot(self, dat=None, config=None, xvals=None, yvals=None, zgrid=None, cax = self.subplot1.imshow(np.transpose(newgrid), origin="lower", cmap=self.cmap, extent=extent, aspect='auto', norm=norm, interpolation='nearest') datalims = [extent[0], extent[2], extent[1], extent[3]] - print(newgrid.shape) # Set X and Y axis labels self.subplot1.set_xlabel(self.xlabel) self.subplot1.set_ylabel(self.ylabel) diff --git a/unidec/modules/unidec_enginebase.py b/unidec/modules/unidec_enginebase.py index 183ca260..2c646a15 100644 --- a/unidec/modules/unidec_enginebase.py +++ b/unidec/modules/unidec_enginebase.py @@ -6,7 +6,7 @@ import webbrowser from unidec.modules.html_writer import * -version = "6.0.5" +version = "7.0.0b" def copy_config(config): @@ -519,7 +519,8 @@ def makeplot5(self, plot=None, xdata=None, ydata=None, zdata=None, config=None): if self.config.batchflag == 0: tstart = time.perf_counter() plot.contourplot(xvals=xdata[:, 0], yvals=ydata, zgrid=zdata, config=config, title="Mass vs. Charge", - test_kda=True) + test_kda=True, repaint=False) + plot.repaint(resetzoom=True, setupzoom=True) print("Plot 5: %.2gs" % (time.perf_counter() - tstart)) return plot diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index e8b0c3d1..fbd57429 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -275,6 +275,7 @@ def __init__(self): self.HTtimeshift = 7.0 self.HTcycleindex = -1 self.HTxaxis = "Time" + self.CDScanCompress = 3 self.doubledec = False self.kernel = "" @@ -437,6 +438,10 @@ def default_decon_params(self): self.CDScanStart = -1 self.CDScanEnd = -1 self.HTksmooth = 0 + self.CDScanCompress = 0 + self.HTcycleindex = -1 + self.HTxaxis = "Time" + self.doubledec = False self.kernel = "" @@ -556,6 +561,14 @@ def config_export(self, name): f.write("CDScanStart " + str(self.CDScanStart) + "\n") f.write("CDScanEnd " + str(self.CDScanEnd) + "\n") f.write("HTksmooth " + str(self.HTksmooth) + "\n") + f.write("CDScanCompresse " + str(self.CDScanCompress) + "\n") + f.write("HTtimepad " + str(self.HTtimepad) + "\n") + f.write("HTanalysistime " + str(self.HTanalysistime) + "\n") + f.write("HTxaxis " + str(self.HTxaxis) + "\n") + f.write("HTcycleindex " + str(self.HTcycleindex) + "\n") + f.write("HTtimepad " + str(self.HTtimepad) + "\n") + f.write("htbit " + str(self.htbit) + "\n") + f.write("csig " + str(self.csig) + "\n") f.write("smoothdt " + str(self.smoothdt) + "\n") @@ -677,6 +690,19 @@ def config_import(self, name): self.CDScanEnd = ud.string_to_int(line.split()[1]) if line.startswith("HTksmooth"): self.HTksmooth = ud.string_to_value(line.split()[1]) + + if line.startswith("HTtimepad"): + self.HTtimepad = ud.string_to_value(line.split()[1]) + if line.startswith("HTanalysistime"): + self.HTanalysistime = ud.string_to_value(line.split()[1]) + if line.startswith("HTxaxis"): + self.HTxaxis = line.split()[1] + if line.startswith("HTcycleindex"): + self.HTcycleindex = ud.string_to_value(line.split()[1]) + if line.startswith("htbit"): + self.htbit = ud.string_to_value(line.split()[1]) + if line.startswith("CDScanCompress"): + self.CDScanCompress = ud.string_to_value(line.split()[1]) if line.startswith("zzsig"): self.zzsig = ud.string_to_value(line.split()[1]) if line.startswith("psig"): @@ -1584,7 +1610,18 @@ def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrang self.ht = ht - self.chromatograms.append(chrom) + # If label already exists, replace it + if label in [x.label for x in self.chromatograms]: + for i, x in enumerate(self.chromatograms): + if x.label == label: + self.chromatograms[i] = chrom + break + else: + # Add it if new + if type(self.chromatograms) is np.ndarray: + self.chromatograms = np.concatenate((self.chromatograms, [chrom])) + else: + self.chromatograms.append(chrom) def clear(self): self.chromatograms = [] diff --git a/unidec/tools.py b/unidec/tools.py index f11c4547..6a01916a 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -1573,7 +1573,7 @@ def linear_interpolation(x1, x2, x): return float(x - x1) / float(x2 - x1) -def lintegrate(datatop, intx): +def lintegrate(datatop, intx, fastmode=False): """ Linearize x-axis by integration. @@ -1582,27 +1582,33 @@ def lintegrate(datatop, intx): The total sum of the intensity values should be constant. :param datatop: Data array :param intx: New x-axis for data + :param fastmode: If True, uses a faster but less accurate method of integration that just picks the nearest point. :return: Integration of intensity from original data onto the new x-axis. Same shape as the old data but new length. """ length = len(datatop) + l2 = len(intx) inty = np.zeros_like(intx) for i in range(0, length): - if intx[0] < datatop[i, 0] < intx[len(intx) - 1]: - index = nearest(intx, datatop[i, 0]) - # inty[index]+=datatop[i,1] - if intx[index] == datatop[i, 0]: - inty[index] += datatop[i, 1] - if intx[index] < datatop[i, 0] and index < length - 1: - index2 = index + 1 - interpos = linear_interpolation(intx[index], intx[index2], datatop[i, 0]) - inty[index] += (1 - interpos) * datatop[i, 1] - inty[index2] += interpos * datatop[i, 1] - if intx[index] > datatop[i, 0] and index > 0: - index2 = index - 1 - interpos = linear_interpolation(intx[index], intx[index2], datatop[i, 0]) - inty[index] += (1 - interpos) * datatop[i, 1] - inty[index2] += interpos * datatop[i, 1] + x = datatop[i, 0] + y = datatop[i, 1] + if intx[0] < x < intx[len(intx) - 1]: + index = nearest(intx, x) + if fastmode: + inty[index] += y + else: + if intx[index] == x: + inty[index] += y + elif intx[index] < x and index < l2 - 1: + index2 = index + 1 + interpos = linear_interpolation(intx[index], intx[index2], x) + inty[index] += (1 - interpos) * y + inty[index2] += interpos * y + elif intx[index] > x and index > 0: + index2 = index - 1 + interpos = linear_interpolation(intx[index], intx[index2], x) + inty[index] += (1 - interpos) * y + inty[index2] += interpos * y newdat = np.column_stack((intx, inty)) return newdat From 2dcf1dff4ca5cbc5656882f0e0d60768cd3d1bd0 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Tue, 2 Jan 2024 16:45:25 -0700 Subject: [PATCH 04/35] Fixed bugs with selection of chromatograms in UCCD --- unidec/modules/gui_elements/HTCD_ListCtrls.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/unidec/modules/gui_elements/HTCD_ListCtrls.py b/unidec/modules/gui_elements/HTCD_ListCtrls.py index 3dd4c1e7..0cfd85d0 100644 --- a/unidec/modules/gui_elements/HTCD_ListCtrls.py +++ b/unidec/modules/gui_elements/HTCD_ListCtrls.py @@ -96,6 +96,17 @@ def repopulate(self, reset=True): def get_data(self): return self.data + def translate_selection(self, sindex): + # Converts index of selection into actual index after accounting for ignored chromatograms + outindex = 0 + for i, c in enumerate(self.data.chromatograms): + if c.ignore: + continue + if outindex == sindex: + return i + outindex += 1 + return None + class ListCtrlPanel(wx.Panel): def __init__(self, parent, pres, size=(200, 400)): @@ -157,7 +168,9 @@ def get_selected(self): for i in range(1, num): item = self.list.GetNextSelected(item) self.selection.append(item) - print("Selection:", self.selection) + + self.selection = [self.list.translate_selection(s) for s in self.selection] + return self.selection def on_popup_one(self, event): @@ -177,7 +190,9 @@ def on_popup_eleven(self, event): def on_popup_four(self, event): self.selection = self.get_selected() for i in range(0, len(self.selection)): - self.list.data.chromatograms[self.selection[i]].ignore = True + #index = self.list.translate_selection(self.selection[i]) + index = self.selection[i] + self.list.data.chromatograms[index].ignore = True self.list.repopulate(reset=False) self.pres.on_ignore_repopulate() @@ -191,7 +206,9 @@ def on_popup_five(self, event): for c in self.list.data.chromatograms: c.ignore = True for i in range(0, len(self.selection)): - self.list.data.chromatograms[self.selection[i]].ignore = False + #index = self.list.translate_selection(self.selection[i]) + index = self.selection[i] + self.list.data.chromatograms[index].ignore = False self.list.repopulate(reset=False) self.pres.on_ignore_repopulate() From 6dc7747a4e4f2e77264a0033fad3be5f0cfacb4e Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 4 Jan 2024 16:04:50 -0700 Subject: [PATCH 05/35] Fixed bug with auto peak width. Fixed weird execution bug on servers (maybe?). Better docs for UCCD. --- unidec/MetaUniDec.py | 18 +- unidec/UniChromCD.py | 165 +++++++++++++- .../Example Data/BSA_unidecfiles/BSA_conf.dat | 12 +- unidec/engine.py | 2 +- unidec/metaunidec/mudeng.py | 2 +- unidec/modules/HTEng.py | 213 +++++++++++++++--- unidec/modules/biopolymertools.py | 20 +- unidec/modules/isolated_packages/texmaker.py | 4 +- .../isolated_packages/texmaker_nmsgsb.py | 4 +- .../tims_import_wizard/get_data_wrapper.py | 2 +- unidec/tools.py | 9 +- 11 files changed, 396 insertions(+), 55 deletions(-) diff --git a/unidec/MetaUniDec.py b/unidec/MetaUniDec.py index 2efee13c..d1268dbd 100644 --- a/unidec/MetaUniDec.py +++ b/unidec/MetaUniDec.py @@ -707,10 +707,10 @@ def on_delete_spectrum(self, indexes=None): self.eng.data.import_vars() self.view.clear_plots() self.view.ypanel.list.populate(self.eng.data) - #try: + # try: # self.eng.pick_peaks() - #3except: - # pass + # 3except: + # pass self.on_replot(plotsums=False) def on_export_params(self, e=None): @@ -725,7 +725,9 @@ def repack_hdf5(self, e=None): if self.eng.config.hdf_file != 'default.hdf5': new_path = self.eng.config.hdf_file.replace(".hdf5", "temp.hdf5") if 0 == subprocess.call( - "\"" + self.eng.config.h5repackfile + "\" \"" + self.eng.config.hdf_file + "\" \"" + new_path + "\"") and os.path.isfile( + "\"" + self.eng.config.h5repackfile + "\" \"" + + self.eng.config.hdf_file + "\" \"" + new_path + "\"", + stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) and os.path.isfile( new_path): os.remove(self.eng.config.hdf_file) os.rename(new_path, self.eng.config.hdf_file) @@ -740,8 +742,9 @@ def recursive_repack_hdf5(self, e=None): print("Repacking: ", name) new_path = name.replace(".hdf5", "temp.hdf5") if 0 == subprocess.call( - "\"" + self.eng.config.h5repackfile + "\" \"" + name + "\" \"" + new_path + "\"") and os.path.isfile( - new_path): + "\"" + self.eng.config.h5repackfile + "\" \"" + name + "\" \"" + new_path + "\"" + , stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) and os.path.isfile( + new_path): os.remove(name) os.rename(new_path, name) print("Done Repacking") @@ -1588,7 +1591,7 @@ def on_imzml_to_hdf5(self, e=None): def make_image_plot(self, e=None): peak_index = e.id print("Making Image for peak #", peak_index) - #imdat = self.eng.generate_image(peak_index) + # imdat = self.eng.generate_image(peak_index) self.view.plot8.clear_plot() zdat = self.eng.data.exgrid[peak_index] self.view.plot8._axes = [0.12, 0.12, 0.75, 0.8] @@ -1603,6 +1606,7 @@ def on_imaging_viewer(self, e=None): dlg = image_plotter.ImagingWindow(self.view) dlg.init(self.eng.data, self.eng.config) + # Critical # TODO: Thorough testing # TODO: Better tuning and control of autobaseline diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 2a36b546..410aee1d 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -92,6 +92,11 @@ def on_dataprep_button(self, e=None): self.make_tic_plot() def on_pick_peaks(self, e=None): + """ + Pick peaks. Runs the peak picking algorithm. Adds Mass EIC beyond conventional UCD. + :param e: event (unused) + :return: None + """ self.export_config(self.eng.config.confname) self.pick_peaks() if self.eng.fullmstack is not None: @@ -113,10 +118,14 @@ def on_pick_peaks(self, e=None): self.cc.add_chromatogram(chromdat, color=p.color, label=label, ht=False) if self.eng.fullmstack_ht is not None: htdata = self.eng.get_mass_eic(massrange, ht=True) - self.cc.add_chromatogram(htdata, color=p.color, label=label +" HT", ht=True) + self.cc.add_chromatogram(htdata, color=p.color, label=label + " HT", ht=True) self.plot_chromatograms() def on_select_mzz_region(self): + """ + Trigged by right click of m/z vs z 2D plot. Triggers EIC creation and HT if already setup. + :return: None + """ self.export_config(self.eng.config.confname) if not wx.GetKeyState(wx.WXK_CONTROL): xlimits = self.view.plot1.subplot1.get_xlim() @@ -130,6 +139,10 @@ def on_select_mzz_region(self): self.add_eic(xlimits, ylimits, color=color) def make_tic_plot(self): + """ + Make the TIC Plot + :return: None + """ data = self.eng.get_tic(normalize=self.eng.config.datanorm) self.cc.add_chromatogram(data, color="black", label="TIC") @@ -137,16 +150,33 @@ def make_tic_plot(self): self.plot_chromatograms(save=False) def add_eic(self, mzrange, zrange, color='b'): + """ + Add an EIC to the list of chromatograms. Plot the chromatograms. + :param mzrange: M/z range + :param zrange: charge range + :param color: Color of the plot. Default blue. + :return: None + """ eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) self.plot_chromatograms() def on_run_tic_ht(self, e=None): + """ + Button to run TIC HT. Runs run_tic_ht. + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) self.run_tic_ht() def on_run_eic_ht(self, e=None): + """ + Button to run EIC HT. Runs run_eic_ht on each EIC that is extracted. + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) for c in self.cc.chromatograms: if "TIC" not in c.label: @@ -155,6 +185,10 @@ def on_run_eic_ht(self, e=None): self.plot_chromatograms() def run_tic_ht(self): + """ + Runs HT on the TIC. Clears the chromatogram list. Runs HT on TIC. Adds both to list. Plots chromatograms. + :return: None + """ self.cc.clear() data = self.eng.get_tic(normalize=self.eng.config.datanorm) htdata = self.eng.tic_ht(normalize=self.eng.config.datanorm) @@ -165,6 +199,16 @@ def run_tic_ht(self): self.plot_chromatograms(save=False) def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): + """ + Function to generate and HT an EIC. Adds both to list. Plots chromatograms. + :param mzrange: M/z range + :param zrange: Charge range + :param color: Color of plot. Default blue. + :param add_eic: Add the EIC to the list. Default True. + :param plot: Plot the chromatograms. Default True. + :return: None + """ + htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) if add_eic: self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) @@ -173,6 +217,12 @@ def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): self.plot_chromatograms() def plot_chromatograms(self, e=None, save=True): + """ + Plot all chromatograms in the list. Add a box to the 2D plot if the chromatogram is an EIC. + :param e: Unused event + :param save: Whether to save the output to a text file. Default True + :return: None + """ self.makeplot1() self.view.plottic.clear_plot() self.view.chrompanel.list.populate(self.cc) @@ -208,10 +258,20 @@ def plot_chromatograms(self, e=None, save=True): self.save_chroms() def save_chroms(self): + """ + Save the chromatograms to a text file. + :return: None + """ chromarray = self.cc.to_array() np.savetxt(self.eng.config.cdchrom, chromarray, delimiter="\t", fmt="%s") def load_chroms(self, fname=None, array=None): + """ + Load chromatograms from a text file. Add them to the list. Plot the chromatograms. + :param fname: Input file name + :param array: Input array. Not currently used, but could be used to load from a numpy array rather than file. + :return: None + """ if fname is not None: if not os.path.isfile(fname): print("Chrom file not found:", fname) @@ -219,6 +279,9 @@ def load_chroms(self, fname=None, array=None): else: print("Loading Chroms:", fname) array = np.loadtxt(fname, delimiter="\t", dtype=str) + elif array is None: + print("No file name or array supplied to load_chroms") + return if ud.isempty(array): return @@ -242,6 +305,11 @@ def load_chroms(self, fname=None, array=None): self.plot_chromatograms() def on_load_chroms(self, e=None): + """ + Load chromatograms from a file. Triggered by a button. + :param e: Unused event + :return: None + """ # Load File dialog dlg = wx.FileDialog(self.view, "Choose a file") if dlg.ShowModal() == wx.ID_OK: @@ -250,10 +318,21 @@ def on_load_chroms(self, e=None): dlg.Destroy() def on_ignore_repopulate(self, e=None): + """ + Event triggered by updates to the chromatogram list. Repopulate the list based on adjusted ignore flags. + :param e: Unused event + :return: None + """ self.cc = self.view.chrompanel.list.get_data() self.plot_chromatograms() def on_select_time_range(self, e=None): + """ + Event triggered by a click on the TIC plot. Select a time range and create a 2D m/z vs z sum from + that time range post-HT. + :param e: Unused event + :return: None + """ if not self.showht or self.eng.fullhstack_ht is None: return if not wx.GetKeyState(wx.WXK_CONTROL): @@ -268,29 +347,50 @@ def on_select_time_range(self, e=None): self.select_ht_range(range=xlimits) def on_run_all_ht(self, e=None): + """ + Button to trigger running HT on all data. Runs run_all_ht. + :param e: Unused event. + :return: None + """ self.run_all_ht() def run_all_ht(self): + """ + Run HT on all data. Clears the chromatogram list. Runs HT on each data point. Plots chromatograms after + adding extracted TIC*_HT. + :return: None + """ self.export_config(self.eng.config.confname) self.eng.process_data_scans() ticdat = self.eng.run_all_ht() self.cc.add_chromatogram(ticdat, color="gold", label="TIC*_HT") - #tic2 = np.sum(self.eng.fullhstack, axis=(1, 2)) - #ticdat2 = np.transpose(np.vstack((self.eng.fulltime, tic2))) - #self.cc.add_chromatogram(ticdat2, color="red", label="TIC*") + # tic2 = np.sum(self.eng.fullhstack, axis=(1, 2)) + # ticdat2 = np.transpose(np.vstack((self.eng.fulltime, tic2))) + # self.cc.add_chromatogram(ticdat2, color="red", label="TIC*") self.showht = True self.plot_chromatograms() def run_all_mass_transform(self, e=None): + """ + Button to trigger running mass transform on all data. Runs transform_stacks. Creates mass TICs. + :param e: Unused event. + :return: None + """ self.eng.transform_stacks() self.cc.add_chromatogram(self.eng.mass_tic, color="grey", label="Mass TIC") if self.eng.fullmstack_ht is not None: self.cc.add_chromatogram(self.eng.mass_tic_ht, color="goldenrod", label="Mass TIC HT") self.plot_chromatograms() + def select_ht_range(self, range=None): + """ + Select a time range and create a 2D m/z vs z sum from that time range post-HT. + :param range: Time range to select. Default None, which should be all times + :return: None + """ self.eng.select_ht_range(range=range) self.makeplot1() self.makeplot2() @@ -298,18 +398,39 @@ def select_ht_range(self, range=None): self.makeplot4() def make_charge_time_2dplot(self, e=None): + """ + Creates a 2D plot of charge vs time + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) self.make_2d_plot(face=1) def make_mz_time_2dplot(self, e=None): + """ + Creates a 2D plot of m/z vs time + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) self.make_2d_plot(face=2) def make_mass_time_2dplot(self, e=None): + """ + Creates a 2D plot of mass vs time + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) self.make_2d_plot(face=3) def make_2d_plot(self, e=None, face=1): + """ + Creates 2D plots of charge, m/z, or mass vs time. General 2D plot function called by others + :param e: Unused event + :param face: Which face to plot against time. 1=charge, 2=m/z, 3=mass + :return: None + """ starttime = time.perf_counter() print("Starting 2D Plots...", ) if self.eng.fullhstack is None: @@ -370,9 +491,20 @@ def make_2d_plot(self, e=None, face=1): print("Finished 2D Plot", (time.perf_counter() - starttime), " s") def make_mass_cube_plot(self, e=None): + """ + Creates a cube plot of mass vs charge vs time + :param e: Unused event + :return: None + """ self.make_cube_plot(e, mass=True) def make_cube_plot(self, e=None, mass=False): + """ + Creates a cube plot of mass or m/z vs charge vs time. Trys both regular and HT if possible. + :param e: Unused event + :param mass: Flag to plot mass vs charge vs time. Default False, which plots m/z vs charge vs time. + :return: None + """ self.export_config(self.eng.config.confname) if mass and self.eng.fullmstack is None: @@ -441,11 +573,21 @@ def make_cube_plot(self, e=None, mass=False): pass def on_auto_set_ct(self, e=None): + """ + Automatically set the cycle index based on the autocorrelation. + :param e: Unused event + :return: None + """ self.eng.get_cycle_time() self.eng.config.HTcycleindex = self.eng.cycleindex self.import_config() def on_plot_kernel(self, e=None): + """ + Plot the HT kernel + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) if not self.showht: self.eng.setup_ht() @@ -474,9 +616,9 @@ def on_plot_kernel(self, e=None): def on_autocorr2(self, index): """ - Manual Test - Passed - :param index: - :return: + Launch an autocorrelation window for a given chromatogram. + :param index: Index of chromatogram in the list + :return: None """ data = self.cc.chromatograms[index].chromdat data[:, 0] = np.arange(0, len(data)) @@ -485,10 +627,19 @@ def on_autocorr2(self, index): dlg.ShowModal() def on_export_arrays(self, e=None): + """ + Export the chromatograms and HT kernel to text files. + :param e: Unused event + :return: None + """ self.export_config(self.eng.config.confname) self.export_arrays() def export_arrays(self): + """ + Export the chromatograms and HT kernel to text files. + :return: None + """ # Export Kernel Array np.savetxt(self.eng.config.outfname + "_htkernel.txt", self.eng.htkernel) # Export Chromatograms diff --git a/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat b/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat index 08894ea9..22582e55 100644 --- a/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat +++ b/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat @@ -1,4 +1,4 @@ -version 6.0.5 +version 7.0.0b imflag 0 cdmsflag 0 input C:\Python\UniDec3\unidec\bin\Example Data\BSA_unidecfiles\BSA_input.dat @@ -61,6 +61,16 @@ kernel C:\Python\UniDec3\unidec\bin\Example Data\BSA_unidecfiles\BSA_mass.txt CDslope 12.5 CDzbins 1.0 CDres 1.0 +CDScanStart -1 +CDScanEnd -1 +HTksmooth 0.0 +CDScanCompresse 0.0 +HTtimepad 0.0 +HTanalysistime 38.0 +HTxaxis Time +HTcycleindex -1.0 +HTtimepad 0.0 +htbit 0.0 csig 0.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/engine.py b/unidec/engine.py index b502093c..bca379f5 100644 --- a/unidec/engine.py +++ b/unidec/engine.py @@ -268,7 +268,7 @@ def raw_process(self, dirname, inflag=False, binsize=1): newfilepath[:-10] + "_msraw.txt", '-i', newfilepath, '--ms_bin', binsize, "--ms_smooth_window", "0", "--ms_number_smooth", "0", "--im_bin", binsize, "--sparse", "1"] - result = subprocess.call(call) + result = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) self.config.filename = newfilename if result == 0 and os.path.isfile(newfilepath): print("Converted IM data from raw to txt") diff --git a/unidec/metaunidec/mudeng.py b/unidec/metaunidec/mudeng.py index dd0d01ae..7a1caf73 100644 --- a/unidec/metaunidec/mudeng.py +++ b/unidec/metaunidec/mudeng.py @@ -32,7 +32,7 @@ def metaunidec_call(config, *args, **kwargs): call.append("-" + str(key)) call.append(kwargs[key]) tstart = time.perf_counter() - out = subprocess.call(call) + out = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) tend = time.perf_counter() # print(call, out) print("Execution Time:", (tend - tstart)) diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index dfda276d..97e7ae9f 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -17,6 +17,13 @@ class HTEng: def __init__(self, *args, **kwargs): + """ + Initialize the HTEng class. This class is used for handling Hadamard Transform (HT) related operations. + :param args: Arguments (currently unused) + :param kwargs: Keyword Arguments (currently unused) + :return: None + """ + super().__init__(*args, **kwargs) self.config.htmode = True @@ -28,12 +35,14 @@ def __init__(self, *args, **kwargs): self.htkernel = [] self.fftk = [] self.htoutput = [] + self.indexrange = [0, 0] # Index Values self.padindex = 0 self.shiftindex = 0 self.kernelroll = 0 self.cycleindex = 0 + self.rollindex = 0 self.config.HTcycleindex = -1 # Important Parameters that Should be Automatically Set by File Name @@ -51,6 +60,11 @@ def __init__(self, *args, **kwargs): print("HT Engine") def parse_file_name(self, path): + """ + Parse the file name to extract relevant parameters for HT processing. + :param path: File path + :return: None + """ if "cyc" in path: # Find location of cyc and take next character cycindex = path.find("cyc") @@ -147,23 +161,35 @@ def setup_ht(self, cycleindex=None): # Smooth the kernel if desired if self.config.HTksmooth > 0: - self.gausskernel = np.zeros_like(self.htkernel) - self.gausskernel += ndis(self.fullscans[self.padindex:], self.padindex, self.config.HTksmooth) - self.gausskernel += ndis(self.fullscans[self.padindex:], np.amax(self.fullscans[self.padindex:]) + 1, - self.config.HTksmooth) - self.gausskernel /= np.sum(self.gausskernel) - self.fftg = fft.rfft(self.gausskernel) - self.htkernel = fft.irfft(fft.rfft(self.htkernel) * self.fftg).real + gausskernel = np.zeros_like(self.htkernel) + gausskernel += ndis(self.fullscans[self.padindex:], self.padindex, self.config.HTksmooth) + gausskernel += ndis(self.fullscans[self.padindex:], np.amax(self.fullscans[self.padindex:]) + 1, + self.config.HTksmooth) + gausskernel /= np.sum(gausskernel) + fftg = fft.rfft(gausskernel) + self.htkernel = fft.irfft(fft.rfft(self.htkernel) * fftg).real # Make fft of kernel for later use self.fftk = fft.rfft(self.htkernel).conj() def correct_cycle_time(self): + """ + Correct the cycle time to be not a division of the total sequence length but a fixed number of scans + :return: None + """ print("Correcting") self.get_cycle_time(data) self.setup_ht(cycleindex=self.cycleindex) - def htdecon(self, data, *args, **kwargs): + def htdecon(self, data, **kwargs): + """ + Deconvolve the data using the HT kernel. Need to call setup_ht first. + :param data: 1D array of data to be deconvolved. Should be same dimension as self.htkernel. + :param kwargs: Keyword arguments. Currently supports "normalize" which normalizes the output to the maximum + value. Also supports "gsmooth" which smooths the data with a Gaussian filter before deconvolution. + Also supports "sgsmooth" which smooths the data with a Savitzky-Golay filter before deconvolution. + :return: Demultiplexed data. Same length as input. + """ # Whether to smooth the data before deconvolution if "gsmooth" in kwargs: data = scipy.ndimage.gaussian_filter1d(data, kwargs["gsmooth"]) @@ -192,17 +218,23 @@ def htdecon(self, data, *args, **kwargs): return output def htdecon_speedy(self, data): + """ + Deconvolve the data using the HT kernel. Need to call setup_ht first. Currently unused. + :param data: 1D data array. Should be same dimension as self.htkernel. + :return: Demultiplexed data. Same length as input. + """ # Do the convolution, and only the convolution... :) return fft.irfft(fft.rfft(data) * self.fftk).real - def decon_3d_fft(self, array, *args, **kwargs): + def decon_3d_fft(self, array, **kwargs): """ Developed this to see if it would speed things up. It turns out not to. About half as slow. Leaving in for legacy reasons and because it's super cool code. - :param array: - :param args: - :param kwargs: - :return: + :param array: 3D array of data to be deconvolved. + Should be same length as self.htkernel with the other dimensions set by the harray size. + :param kwargs: Keyword arguments. Currently supports "normalize" which normalizes the output to the maximum + value. + :return: Demultiplexed data array. Same length as input array. """ starttime = time.perf_counter() # Slice data to appropriate range @@ -236,6 +268,11 @@ def decon_3d_fft(self, array, *args, **kwargs): return output def set_timepad_index(self, timepad): + """ + Find the index of the first scan above a timepad + :param timepad: Time value + :return: Index of first scan above timepad + """ # find first index above timepad in self.fulltimes padindex = np.argmax(self.fulltime >= timepad) return padindex @@ -258,6 +295,15 @@ def get_first_peak(self, data, threshold=0.5): return peakindex def get_cycle_time(self, data=None, cycleindexguess=None, widthguess=110): + """ + Get the cycle time from the data. This is the time between the first peak and the next peak. + Uses an autocorrelation and then peak picking on the autocorrelation. + May need to adjust the peak picking parameters to get it to work right. + :param data: Input data + :param cycleindexguess: Guess for the cycle index + :param widthguess: Guess for peak width in number of scans + :return: autocorrelation of the data + """ if data is None: data = self.fulltic # autocorrelation of the data @@ -279,10 +325,21 @@ def get_cycle_time(self, data=None, cycleindexguess=None, widthguess=110): class UniChromHT(HTEng, ChromEngine): def __init__(self, *args, **kwargs): + """ + Initialize the UniChromHT class. This class is used for handling Hadamard Transform (HT) related operations + on chromatograms. + :param args: Arguments + :param kwargs: Keyword Arguments + """ super().__init__(*args, **kwargs) print("HT Chromatogram Engine") def open_file(self, path): + """ + Open file and set up the time domain. + :param path: File path + :return: None + """ self.open_chrom(path) times = self.get_minmax_times() self.config.HTanalysistime = np.amax(times[1]) @@ -292,6 +349,11 @@ def open_file(self, path): print("Loaded File:", path) def eic_ht(self, massrange): + """ + Get the EIC and run HT on it. + :param massrange: Mass range for EIC selection + :return: Demultiplexed data output + """ eic = self.chromdat.get_eic(mass_range=np.array(massrange)) print(eic.shape) self.fulltic = eic[:, 1] @@ -300,16 +362,29 @@ def eic_ht(self, massrange): self.htoutput = self.htdecon(self.fulltic) return self.htoutput - def tic_ht(self, correct=False, *args, **kwargs): + def tic_ht(self, correct=False, **kwargs): + """ + Get the TIC and run HT on it. + :param correct: Whether to correct the data for the first peak + :param kwargs: Deconvolution keyword arguments + :return: Demultiplexed data output + """ self.fulltic = self.ticdat[:, 1] self.fulltime = self.ticdat[:, 0] self.setup_ht() - self.htoutput = self.htdecon(self.fulltic, correct=correct, *args, **kwargs) + self.htoutput = self.htdecon(self.fulltic, correct=correct, **kwargs) return self.htoutput class UniDecCDHT(HTEng, UniDecCD): def __init__(self, *args, **kwargs): + """ + Initialize the UniDecCDHT class. This class is used for handling Hadamard Transform (HT) related operations + on CDMS data. + :param args: Arguments + :param kwargs: Keyword Arguments + :return: None + """ super(UniDecCDHT, self).__init__(*args, **kwargs) print("HT-CD-MS Engine") self.config.poolflag = 0 @@ -333,10 +408,21 @@ def __init__(self, *args, **kwargs): self.ztab = None def open_file(self, path, refresh=False): + """ + Open CDMS file and set up the time domain. + :param path: Path to file + :param refresh: Whether to refresh the data. Default False. + :return: None + """ self.open_cdms_file(path, refresh=refresh) self.parse_file_name(path) def clear_arrays(self, massonly=False): + """ + Clear arrays to reset. + :param massonly: Whether to only reset mass arrays. Default False. + :return: None + """ if not massonly: self.fullhstack = None self.fullhstack_ht = None @@ -346,6 +432,11 @@ def clear_arrays(self, massonly=False): self.mass_tic_ht = None def prep_time_domain(self): + """ + Prepare the time domain for CDMS data. Creates scans, fullscans, fulltime arrays. + Need to set self.config.HTanalysistime before calling this function. + :return: None + """ self.scans = np.unique(self.farray[:, 2]) self.fullscans = np.arange(1, np.amax(self.scans) + 1) self.fulltime = self.fullscans * self.config.HTanalysistime / np.amax(self.fullscans) @@ -406,6 +497,14 @@ def process_data_scans(self, transform=True): print("Process Time HT:", time.perf_counter() - starttime) def prep_hist(self, mzbins=1, zbins=1, mzrange=None, zrange=None): + """ + Prepare the histogram for process_data_scans CDMS data. + :param mzbins: Bin size for m/z + :param zbins: Bin size for charge + :param mzrange: m/z range + :param zrange: charge range + :return: None + """ # Set up parameters if mzbins < 0.001: print("Error, mzbins too small. Changing to 1", mzbins) @@ -445,6 +544,12 @@ def prep_hist(self, mzbins=1, zbins=1, mzrange=None, zrange=None): self.mass = (self.X - self.config.adductmass) * self.Y def histogramLC(self, x=None, y=None): + """ + Histogram function used for LC-CD-MS data. + :param x: x-axis (m/z) + :param y: y-axis (charge) + :return: Histogram array + """ # X is m/z if x is None: x = self.farray[:, 0] @@ -464,7 +569,13 @@ def histogramLC(self, x=None, y=None): harray = np.transpose(harray) return harray - def create_chrom(self, farray, *args, **kwargs): + def create_chrom(self, farray, **kwargs): + """ + Create a chromatogram from the farray. + :param farray: Data array of features + :param kwargs: Keyword arguments. Currently supports "normalize" which normalizes the output to the maximum. + :return: TIC/EIC in 2D array (time, intensity) + """ # Count of number of time each scans appears in farray scans, counts = np.unique(farray[:, 2], return_counts=True) @@ -479,21 +590,39 @@ def create_chrom(self, farray, *args, **kwargs): fulleic /= np.amax(fulleic) return np.transpose([self.fulltime, fulleic]) - def get_tic(self, farray=None, *args, **kwargs): + def get_tic(self, farray=None, **kwargs): + """ + Get the TIC from the farray. + :param farray: Optional input feature array + :param kwargs: Keywords to be passed down to create_chrom + :return: 2D array of TIC (time, intensity) + """ self.prep_time_domain() if farray is None: farray = self.farray - fulltic = self.create_chrom(farray, *args, **kwargs) + fulltic = self.create_chrom(farray, **kwargs) self.fulltic = fulltic[:, 1] return fulltic - def tic_ht(self, *args, **kwargs): - self.get_tic(*args, **kwargs) + def tic_ht(self, **kwargs): + """ + Get the TIC and run HT on it. + :param kwargs: Keyword arguments. Passed down to create_chrom and htdecon. + :return: Demultiplexed data output. 2D array (time, intensity) + """ + self.get_tic(**kwargs) self.setup_ht() - self.htoutput = self.htdecon(self.fulltic, *args, **kwargs) + self.htoutput = self.htdecon(self.fulltic, **kwargs) return np.transpose([self.fulltime, self.htoutput]) - def get_eic(self, mzrange, zrange, *args, **kwargs): + def get_eic(self, mzrange, zrange, **kwargs): + """ + Get the EIC from the farray. + :param mzrange: m/z range + :param zrange: charge range + :param kwargs: Keywords to be passed down to create_chrom + :return: 2D array of EIC (time, intensity) + """ # Filter farray b1 = self.farray[:, 0] >= mzrange[0] b2 = self.farray[:, 0] <= mzrange[1] @@ -505,16 +634,27 @@ def get_eic(self, mzrange, zrange, *args, **kwargs): farray2 = self.farray[b] # Create EIC - eic = self.create_chrom(farray2, *args, **kwargs) + eic = self.create_chrom(farray2, **kwargs) return eic - def eic_ht(self, mzrange, zrange, *args, **kwargs): - eic = self.get_eic(mzrange, zrange, *args, **kwargs) + def eic_ht(self, mzrange, zrange, **kwargs): + """ + Get the EIC and run HT on it. + :param mzrange: m/z range + :param zrange: charge range + :param kwargs: Keyword arguments. Passed down to create_chrom and htdecon. + :return: Demultiplexed data output. 2D array (time, intensity) + """ + eic = self.get_eic(mzrange, zrange,**kwargs) self.setup_ht() - self.htoutput = self.htdecon(eic[:, 1], *args, **kwargs) + self.htoutput = self.htdecon(eic[:, 1], **kwargs) return np.transpose([self.fulltime, self.htoutput]), eic def run_all_ht(self): + """ + Run HT on all data in full 3D array. Will call process_data_scans if necessary. + :return: TIC based on demultiplexed data. 2D array (time, intensity) + """ starttime = time.perf_counter() if self.fullhstack is None: self.process_data_scans() @@ -578,6 +718,11 @@ def run_all_ht(self): return ticdat def select_ht_range(self, range=None): + """ + Select a range of time from HT stack and processes it as a histogram array + :param range: Time range + :return: 2D histogram array + """ if range is None: range = [np.amin(self.fulltime), np.amax(self.fulltime)] b1 = self.fulltime >= range[0] @@ -587,8 +732,14 @@ def select_ht_range(self, range=None): self.harray = np.sum(substack_ht, axis=0) self.harray = np.clip(self.harray, 0, np.amax(self.harray)) self.harray_process() + return self.harray def transform_array(self, array): + """ + Transforms a histogram stack from m/z to mass + :param array: Histogram stack. Shape is time vs. charge vs. m/z. + :return: Transformed array + """ mlen = len(self.massaxis) outarray = np.zeros((len(array), mlen)) @@ -605,6 +756,10 @@ def transform_array(self, array): return outarray def transform_stacks(self): + """ + Transform the histogram stacks from m/z to mass. Calls transform_array function on each stack. + :return: None + """ if self.massaxis is None: self.process_data(transform=True) if self.fullhstack is None: @@ -663,6 +818,12 @@ def transform_stacks(self): print("Full Mass 2 Transform Done:", time.perf_counter() - starttime) def get_mass_eic(self, massrange, ht=False): + """ + Get the EIC for a mass range after transforming the data to mass. Can be either HT or not. + :param massrange: Mass range + :param ht: Boolean whether to use HT or not + :return: 2D array of EIC (time, intensity) + """ # Filter fullmstack b1 = self.massaxis >= massrange[0] b2 = self.massaxis <= massrange[1] diff --git a/unidec/modules/biopolymertools.py b/unidec/modules/biopolymertools.py index 285c6b58..548a00fa 100644 --- a/unidec/modules/biopolymertools.py +++ b/unidec/modules/biopolymertools.py @@ -8,6 +8,11 @@ 'M': 131.1926, 'N': 114.1038, 'P': 97.1167, 'Q': 128.1307, 'R': 156.1875, 'S': 87.0782, 'T': 101.1051, 'V': 99.1326, 'W': 186.2132, 'Y': 163.1760} +aa_masses_monoisotopic = {'A': 71.03711, 'C': 103.00919, 'D': 115.02694, 'E': 129.04259, 'F': 147.06841, + 'G': 57.02146, 'H': 137.05891, 'I': 113.08406, 'K': 128.09496, 'L': 113.08406, + 'M': 131.04049, 'N': 114.04293, 'P': 97.05276, 'Q': 128.05858, 'R': 156.10111, + 'S': 87.03203, 'T': 101.04768, 'V': 99.06841, 'W': 186.07931, 'Y': 163.06333} + rna_masses = {'A': 329.2, 'U': 306.2, 'C': 305.2, 'G': 345.2, 'T': 306.2} dna_masses = {'A': 313.2, 'T': 304.2, 'C': 289.2, 'G': 329.2, 'U': 304.2, } @@ -22,6 +27,8 @@ mass_O = 15.9994 mass_HPO4 = 95.9793 mass_H = 1.00794 +mass_proton = 1.00727647 + def get_aa_mass(letter): if letter == " " or letter == "\t" or letter == "\n": @@ -144,8 +151,11 @@ def read_fasta(path): if __name__ == "__main__": - print(mass_HPO4 + mass_HPO4 - mass_O - mass_O + mass_H + mass_H) - os.chdir("..\\..\\Scripts\\old\\Jessica") - file = "ecoli.fasta" - genes = read_fasta(file) - print(genes) \ No newline at end of file + #print(mass_HPO4 + mass_HPO4 - mass_O - mass_O + mass_H + mass_H) + #os.chdir("..\\..\\Scripts\\old\\Jessica") + #file = "ecoli.fasta" + #genes = read_fasta(file) + #print(genes) + oligo = "aacauucaACgcugucggugAgu" + mass = calc_rna_mass(oligo) + print(mass) diff --git a/unidec/modules/isolated_packages/texmaker.py b/unidec/modules/isolated_packages/texmaker.py index d7c464ef..9dd539f8 100644 --- a/unidec/modules/isolated_packages/texmaker.py +++ b/unidec/modules/isolated_packages/texmaker.py @@ -151,9 +151,9 @@ def PDFTexReport(fname): if os.path.isfile(path): call = [path, str(fname),"-halt-on-error"] print(call) - subprocess.call(call) + subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) else: call = ["pdflatex", str(fname), "-halt-on-error"] print(call) - subprocess.call(call) + subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) os.chdir(oldpath) diff --git a/unidec/modules/isolated_packages/texmaker_nmsgsb.py b/unidec/modules/isolated_packages/texmaker_nmsgsb.py index 31dc73f7..99d3f947 100644 --- a/unidec/modules/isolated_packages/texmaker_nmsgsb.py +++ b/unidec/modules/isolated_packages/texmaker_nmsgsb.py @@ -170,9 +170,9 @@ def PDFTexReport(fname): if os.path.isfile(path): call = [path, str(fname), "-halt-on-error"] # print(call) - subprocess.call(call) + subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) else: call = ["pdflatex", str(fname), "-halt-on-error"] # print(call) - subprocess.call(call) + subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) os.chdir(oldpath) diff --git a/unidec/modules/tims_import_wizard/get_data_wrapper.py b/unidec/modules/tims_import_wizard/get_data_wrapper.py index 3543e64d..9f226690 100644 --- a/unidec/modules/tims_import_wizard/get_data_wrapper.py +++ b/unidec/modules/tims_import_wizard/get_data_wrapper.py @@ -51,7 +51,7 @@ def new_get_data(start, end, im_bin_size, raw_file, pusher, function_no, scan_st except ValueError: pass - p = subprocess.call(call_params, shell=False) # , stdout=devnull, shell=False) + p = subprocess.call(call_params, shell=False, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) # , stdout=devnull, shell=False) if p != 0: print("CONVERSION ERROR! Call Parameters:", call_params) diff --git a/unidec/tools.py b/unidec/tools.py index 6a01916a..8b75fc65 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -22,7 +22,6 @@ from unidec.modules.fitting import * from itertools import cycle - try: from unidec.modules.mzMLimporter import mzMLimporter except: @@ -1947,11 +1946,16 @@ def peakdetect(data, config=None, window=10, threshold=0, ppm=None, norm=True): :param data: Mass data array (N x 2) (mass intensity) :param config: UniDecConfig object + :param window: Tolerance window of the x values + :param threshold: Threshold of the y values + :param ppm: Tolerance window in ppm + :param norm: Whether to normalize the data before peak detection :return: Array of peaks positions and intensities (P x 2) (mass intensity) """ if config is not None: window = config.peakwindow / config.massbins threshold = config.peakthresh + peaks = [] length = len(data) if norm: @@ -2552,11 +2556,12 @@ def continuous_wavelet_transform(a, widths, wavelet_type="Ricker"): return signal.cwt(a, wavelet, widths) -def autocorr(datatop, config=None, window=None): +def autocorr(datatop, config=None, window=10): """ Note: ASSUMES LINEARIZED DATA :param datatop: 1D data :param config: Config file (optional file) + :param window: Window for peak detection, default 10 data points :return: Autocorr spectrum, peaks in autocorrelation. """ corry = signal.fftconvolve(datatop[:, 1], datatop[:, 1][::-1], mode='same') From 4d9985cccd495ab2c425670d7c53a465121874ab Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 4 Jan 2024 16:05:32 -0700 Subject: [PATCH 06/35] Fixed bug with auto peak width. Fixed weird execution bug on servers (maybe?). Better docs for UCCD. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 820d7149..1b1a5e07 100644 --- a/readme.md +++ b/readme.md @@ -199,7 +199,7 @@ v.7.0.0 Added new module for time domain CD-MS data analysis, UniChromCD. This includes some cool new plotting and analysis features. Most importantly, it also includes our new method for Hadamard Transform analysis of CD-MS spectra. You can use the tools for regular LC-CD-MS or go crazy with HT-LC-CD-MS. More info coming soon on this front...:) -Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. +Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. Fixed execution bug on servers. v.6.0.5 From c250c296c7e838365c1517c1efd20e7f65f62158 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 5 Jan 2024 09:38:07 -0700 Subject: [PATCH 07/35] Testing new call process --- unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat | 2 +- unidec/engine.py | 5 +++-- unidec/tools.py | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat b/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat index 22582e55..47ef333e 100644 --- a/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat +++ b/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat @@ -10,7 +10,7 @@ startz 10 zzsig 1.0 psig 1.0 beta 50.0 -mzsig 1.0 +mzsig 1.49881 psfun 0 zpsfn 0 discreteplot 0 diff --git a/unidec/engine.py b/unidec/engine.py index bca379f5..6256d39f 100644 --- a/unidec/engine.py +++ b/unidec/engine.py @@ -1535,11 +1535,12 @@ def makeplot1(self, plot=None, intthresh=False, imfit=True, config=None): # eng.run_unidec(silent=False) eng.open_file(filename, path, load_results=True) - eng.match() + # eng.match() + eng.run_unidec() # plot = eng.makeplot2() # os.chdir(path) # plot.save_figure("test.png") - eng.gen_html_report() + # eng.gen_html_report() # eng.pick_peaks() # eng.pick_peaks() diff --git a/unidec/tools.py b/unidec/tools.py index 8b75fc65..0a279a3d 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -1928,10 +1928,12 @@ def unidec_call(config, silent=False, conv=False, **kwargs): if conv: call.append("-conv") - if silent: - out = subprocess.call(call, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) - else: - out = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + result = subprocess.run(call, shell=True, capture_output=True, text=True) + out = result.returncode + if not silent: + print(result.stdout) + #else: + # out = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) return out From 84e3d4ffe2596fd47b5ab703a3622ac580fef557 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 5 Jan 2024 11:08:17 -0700 Subject: [PATCH 08/35] Final? Fix of call issues --- unidec/MetaUniDec.py | 16 ++++------- unidec/engine.py | 7 ++--- unidec/metaunidec/mudeng.py | 3 +- unidec/modules/isolated_packages/texmaker.py | 6 ++-- .../isolated_packages/texmaker_nmsgsb.py | 15 ++-------- .../tims_import_wizard/get_data_wrapper.py | 5 ++-- unidec/tools.py | 28 +++++++++++++------ 7 files changed, 39 insertions(+), 41 deletions(-) diff --git a/unidec/MetaUniDec.py b/unidec/MetaUniDec.py index d1268dbd..020e8a89 100644 --- a/unidec/MetaUniDec.py +++ b/unidec/MetaUniDec.py @@ -1,4 +1,3 @@ -import subprocess import atexit import wx.html import numpy as np @@ -724,11 +723,9 @@ def on_export_params(self, e=None): def repack_hdf5(self, e=None): if self.eng.config.hdf_file != 'default.hdf5': new_path = self.eng.config.hdf_file.replace(".hdf5", "temp.hdf5") - if 0 == subprocess.call( - "\"" + self.eng.config.h5repackfile + "\" \"" + - self.eng.config.hdf_file + "\" \"" + new_path + "\"", - stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) and os.path.isfile( - new_path): + call = "\"" + self.eng.config.h5repackfile + "\" \"" + self.eng.config.hdf_file + "\" \"" + new_path + "\"" + out = ud.exe_call(call) + if 0 == out and os.path.isfile(new_path): os.remove(self.eng.config.hdf_file) os.rename(new_path, self.eng.config.hdf_file) @@ -741,10 +738,9 @@ def recursive_repack_hdf5(self, e=None): name = os.path.join(root, name) print("Repacking: ", name) new_path = name.replace(".hdf5", "temp.hdf5") - if 0 == subprocess.call( - "\"" + self.eng.config.h5repackfile + "\" \"" + name + "\" \"" + new_path + "\"" - , stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) and os.path.isfile( - new_path): + call = "\"" + self.eng.config.h5repackfile + "\" \"" + name + "\" \"" + new_path + "\"" + out = ud.exe_call(call) + if 0 == out and os.path.isfile(new_path): os.remove(name) os.rename(new_path, name) print("Done Repacking") diff --git a/unidec/engine.py b/unidec/engine.py index 6256d39f..29cde4e5 100644 --- a/unidec/engine.py +++ b/unidec/engine.py @@ -5,7 +5,6 @@ import sys import getopt from copy import deepcopy -import subprocess import zipfile import fnmatch import numpy as np @@ -255,8 +254,7 @@ def raw_process(self, dirname, inflag=False, binsize=1): if self.config.imflag == 0: # print("Testing: ", newfilepath) ud.waters_convert2(self.config.dirname, config=self.config, outfile=newfilepath) - # result = subprocess.call( - # [self.config.rawreaderpath, "-i", self.config.dirname, "-o", newfilepath]) + self.config.filename = newfilename if os.path.isfile(newfilepath): print("Converted data from raw to txt") @@ -268,7 +266,8 @@ def raw_process(self, dirname, inflag=False, binsize=1): newfilepath[:-10] + "_msraw.txt", '-i', newfilepath, '--ms_bin', binsize, "--ms_smooth_window", "0", "--ms_number_smooth", "0", "--im_bin", binsize, "--sparse", "1"] - result = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + result = ud.exe_call(call) + self.config.filename = newfilename if result == 0 and os.path.isfile(newfilepath): print("Converted IM data from raw to txt") diff --git a/unidec/metaunidec/mudeng.py b/unidec/metaunidec/mudeng.py index 7a1caf73..20975c04 100644 --- a/unidec/metaunidec/mudeng.py +++ b/unidec/metaunidec/mudeng.py @@ -1,5 +1,4 @@ import os -import subprocess import numpy as np import unidec.tools as ud @@ -32,7 +31,7 @@ def metaunidec_call(config, *args, **kwargs): call.append("-" + str(key)) call.append(kwargs[key]) tstart = time.perf_counter() - out = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + out = ud.exe_call(call) tend = time.perf_counter() # print(call, out) print("Execution Time:", (tend - tstart)) diff --git a/unidec/modules/isolated_packages/texmaker.py b/unidec/modules/isolated_packages/texmaker.py index 9dd539f8..5f08f09b 100644 --- a/unidec/modules/isolated_packages/texmaker.py +++ b/unidec/modules/isolated_packages/texmaker.py @@ -4,11 +4,11 @@ @author: Michael.Marty """ -import subprocess import time import numpy as np import os from copy import deepcopy +import unidec.tools as ud # import os @@ -151,9 +151,9 @@ def PDFTexReport(fname): if os.path.isfile(path): call = [path, str(fname),"-halt-on-error"] print(call) - subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + ud.exe_call(call) else: call = ["pdflatex", str(fname), "-halt-on-error"] print(call) - subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + ud.exe_call(call) os.chdir(oldpath) diff --git a/unidec/modules/isolated_packages/texmaker_nmsgsb.py b/unidec/modules/isolated_packages/texmaker_nmsgsb.py index 99d3f947..53ccec13 100644 --- a/unidec/modules/isolated_packages/texmaker_nmsgsb.py +++ b/unidec/modules/isolated_packages/texmaker_nmsgsb.py @@ -4,11 +4,11 @@ @author: Michael.Marty """ -import subprocess import time import numpy as np import os from copy import deepcopy +import unidec.tools as ud def MakeTexReport(fname, config, path, peaks, labels, names, color, figureflags, output, rawsamplename, match, @@ -146,15 +146,6 @@ def MakeTexReport(fname, config, path, peaks, labels, names, color, figureflags, f.close() -''' -def PDFTexReportOld(fname): - path="C:\\Program Files (x86)\\MiKTeX 2.9\\miktex\\bin\\pdflatex.exe" - if os.path.isfile(path): - subprocess.call([path,fname,"-halt-on-error"]) - else: - subprocess.call(["pdflatex", fname, "-halt-on-error"])''' - - def PDFTexReport(fname): """ This new call adds in a change directory, which previously had been part of the UniDec code. @@ -170,9 +161,9 @@ def PDFTexReport(fname): if os.path.isfile(path): call = [path, str(fname), "-halt-on-error"] # print(call) - subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + ud.exe_call(call) else: call = ["pdflatex", str(fname), "-halt-on-error"] # print(call) - subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + ud.exe_call(call) os.chdir(oldpath) diff --git a/unidec/modules/tims_import_wizard/get_data_wrapper.py b/unidec/modules/tims_import_wizard/get_data_wrapper.py index 9f226690..87a311e4 100644 --- a/unidec/modules/tims_import_wizard/get_data_wrapper.py +++ b/unidec/modules/tims_import_wizard/get_data_wrapper.py @@ -1,4 +1,4 @@ -import os, subprocess, sys +import os, sys try: from unidec.modules.waters_importer.WatersImporter import WatersDataImporter as WDI @@ -51,7 +51,8 @@ def new_get_data(start, end, im_bin_size, raw_file, pusher, function_no, scan_st except ValueError: pass - p = subprocess.call(call_params, shell=False, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) # , stdout=devnull, shell=False) + p = ud.exe_call(call_params) + # p = subprocess.call(call_params, shell=False, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) # , stdout=devnull, shell=False) if p != 0: print("CONVERSION ERROR! Call Parameters:", call_params) diff --git a/unidec/tools.py b/unidec/tools.py index 0a279a3d..7ee84092 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -1902,6 +1902,24 @@ def dataprep(datatop, config, peaks=True, intthresh=True, silent=False): # ............................................................................ +def exe_call(call, silent=False): + """ + Run the binary specified in the call. + If silent is False (default), the output from exepath will be printed to the standard out. + If silent is True, the output is suppressed. + + :param call: Call arguments to be passed ot the shell + :param silent: Whether to print the output of exepath to the standard out + :return: Standard error of exepath execution + """ + print("System Call:", *call) + result = subprocess.run(call, shell=False, capture_output=True, text=True) + out = result.returncode + if not silent: + print(result.stdout) + return out + + def unidec_call(config, silent=False, conv=False, **kwargs): """ Run the unidec binary specified by exepath with the configuration file specified by configfile. @@ -1912,8 +1930,7 @@ def unidec_call(config, silent=False, conv=False, **kwargs): unidec.exe conf.dat - :param exepath: Path to unidec or UniDecIM binary - :param configfile: Path to configuration file + :param config: Config object :param silent: Whether to print the output of exepath to the standard out :param conv: Whether to call the convolution function only rather than the standard deconvolution :param kwargs: @@ -1928,12 +1945,7 @@ def unidec_call(config, silent=False, conv=False, **kwargs): if conv: call.append("-conv") - result = subprocess.run(call, shell=True, capture_output=True, text=True) - out = result.returncode - if not silent: - print(result.stdout) - #else: - # out = subprocess.call(call, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) + out = exe_call(call, silent=silent) return out From e0707e695c02dfe388d224307dbda715e3435af9 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 5 Jan 2024 14:48:34 -0700 Subject: [PATCH 09/35] Doc string updates --- .../GroEL HCD UniDec_conf.dat | 14 ++++++++++-- unidec/engine.py | 22 +++++++++---------- unidec/modules/HTEng.py | 9 ++++---- unidec/modules/IM_functions.py | 1 - unidec/modules/unidecstructure.py | 10 ++++----- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_conf.dat b/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_conf.dat index baec618a..381a3c36 100644 --- a/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_conf.dat +++ b/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_conf.dat @@ -1,4 +1,4 @@ -version 6.0.3 +version 7.0.0b imflag 0 cdmsflag 0 input C:\Python\UniDec3\unidec\bin\Example Data\GroEL HCD UniDec_unidecfiles\GroEL HCD UniDec_input.dat @@ -12,7 +12,7 @@ psig 0.0 beta 0.0 mzsig 0.0 psfun 0 -zpsfun 0 +zpsfn 0 discreteplot 0 smashflag 0 massub 1000000.0 @@ -62,6 +62,16 @@ kernel CDslope 0.2074 CDzbins 1.0 CDres 0.0 +CDScanStart -1 +CDScanEnd -1 +HTksmooth 0.0 +CDScanCompresse 0.0 +HTtimepad 0.0 +HTanalysistime 38.0 +HTxaxis Time +HTcycleindex -1.0 +HTtimepad 0.0 +htbit 0.0 csig 0.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/engine.py b/unidec/engine.py index 29cde4e5..c970327e 100644 --- a/unidec/engine.py +++ b/unidec/engine.py @@ -89,10 +89,11 @@ def open_file(self, file_name, file_directory=None, time_range=None, refresh=Fal If it finds a _conf.dat file in _unidecfiles, it will import the old configuration. Otherwise, it keeps the existing configuration but resets file names. - If silent=True is passed in **kwargs, prints are suppressed. - - :param: file_name: Name of file to open. May be in x y or x y z text format or in mzML format. May be tab or space delimited - :param file_directory: Directory in which filename is located. Default is current directory. If None, will get from file_name. + If silent=True is passed in kwargs, prints are suppressed. + :param: file_name: Name of file to open. + May be in x y or x y z text format or in mzML format. May be tab or space delimited + :param file_directory: Directory in which filename is located. + Default is current directory. If None, will get from file_name. :param time_range: Array or tuple of (start, end) times to load from data file. Default is to load all. :param refresh: If True, will refresh the data file even if it already exists. :param load_results: If True, will load the results from the _unidecfiles directory. @@ -206,14 +207,13 @@ def open_file(self, file_name, file_directory=None, time_range=None, refresh=Fal def raw_process(self, dirname, inflag=False, binsize=1): """ Processes Water's Raw files into .txt using external calls to: - self.config.cdcreaderpath for IM-MS + self.config.cdcreaderpath for IM-MS MS is processed by modules.waters_importer.Importer Default files are created with the header of the .raw file plus: - _rawdata.txt for MS - _imraw.txt for IM-MS - + _rawdata.txt for MS + _imraw.txt for IM-MS :param dirname: .raw directory name :param inflag: If True, it will put the output .txt file inside the existing .raw directory. If False, it will put the file in the same directory that contains the .raw directory @@ -987,9 +987,9 @@ def load_state(self, load_path): def normalize_peaks(self): """ Noamlize everything in the peaks accoring to the type set in self.config.peaknorm - 0 = No normalization - 1 = Normalize the max value to 1 - 2 = Normalize the sum to 1 + 0 = No normalization + 1 = Normalize the max value to 1 + 2 = Normalize the sum to 1 :return: None """ integrals = np.array([p.integral for p in self.pks.peaks]) diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 97e7ae9f..93abdacb 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -98,7 +98,7 @@ def setup_ht(self, cycleindex=None): deconvolve the data. The sequence is defined by the htseq variable. The timepad and timeshift variables shift the sequence in time. The config.HTksmooth variable smooths the kernel to reduce ringing. :param cycleindex: The length of a cycle in number of scans. - If not specified, default is the full number of scans divided by the number of cycles. + If not specified, default is the full number of scans divided by the number of cycles. :return: None """ # Finds the number of scans that are padded @@ -186,8 +186,8 @@ def htdecon(self, data, **kwargs): Deconvolve the data using the HT kernel. Need to call setup_ht first. :param data: 1D array of data to be deconvolved. Should be same dimension as self.htkernel. :param kwargs: Keyword arguments. Currently supports "normalize" which normalizes the output to the maximum - value. Also supports "gsmooth" which smooths the data with a Gaussian filter before deconvolution. - Also supports "sgsmooth" which smooths the data with a Savitzky-Golay filter before deconvolution. + value. Also supports "gsmooth" which smooths the data with a Gaussian filter before deconvolution. + Also supports "sgsmooth" which smooths the data with a Savitzky-Golay filter before deconvolution. :return: Demultiplexed data. Same length as input. """ # Whether to smooth the data before deconvolution @@ -226,6 +226,7 @@ def htdecon_speedy(self, data): # Do the convolution, and only the convolution... :) return fft.irfft(fft.rfft(data) * self.fftk).real + ''' def decon_3d_fft(self, array, **kwargs): """ Developed this to see if it would speed things up. It turns out not to. About half as slow. Leaving in for @@ -265,7 +266,7 @@ def decon_3d_fft(self, array, **kwargs): if kwargs["normalize"]: output /= np.amax(output) # Return demultiplexed data - return output + return output''' def set_timepad_index(self, timepad): """ diff --git a/unidec/modules/IM_functions.py b/unidec/modules/IM_functions.py index 65b0e280..aed7cf19 100644 --- a/unidec/modules/IM_functions.py +++ b/unidec/modules/IM_functions.py @@ -134,7 +134,6 @@ def detectoreff_2d(igrid, xgrid, va): def process_data_2d(xgrid, ygrid, igrid, config): """ Process IM-MS raw data. - 1. Chop data to defined limits 2. Linearize 3. Smooth diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index fbd57429..ed51602a 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -1120,7 +1120,7 @@ def print_config(self): def default_file_names(self): """ Sets the default file names. For things comming into and out of the program. In theory these can be modified, - but it might be risky. + but it might be risky. :return: None """ self.infname = self.outfname + "_input.dat" @@ -1145,10 +1145,10 @@ def default_file_names(self): def check_badness(self): """ Test for a few things that will crash the program: - Min is greater than Max for m/z, charge, mass, native charge, ccs, native ccs, dt - Bad IM-MS calibration values. - Peak width is zero - m/z resolution is really small. + Min is greater than Max for m/z, charge, mass, native charge, ccs, native ccs, dt + Bad IM-MS calibration values. + Peak width is zero + m/z resolution is really small. :return: None """ badest = 0 From 993ad980d8b63838e7bf0b8727afcf3f591bc433 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Mon, 29 Jan 2024 13:49:20 -0700 Subject: [PATCH 10/35] Adding new PlotControlsPanel.py and testing --- unidec/DataCollector.py | 44 ++++---- unidec/UniChromCD.py | 2 +- unidec/modules/PlotBase.py | 10 ++ .../modules/gui_elements/PlotControlsPanel.py | 104 ++++++++++++++++++ unidec/modules/gui_elements/ud_controls.py | 7 +- unidec/modules/hdf5_tools.py | 6 + unidec/modules/unidecstructure.py | 4 + 7 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 unidec/modules/gui_elements/PlotControlsPanel.py diff --git a/unidec/DataCollector.py b/unidec/DataCollector.py index 64553e57..51ea2366 100644 --- a/unidec/DataCollector.py +++ b/unidec/DataCollector.py @@ -28,7 +28,8 @@ luminance_cutoff = 135 white_text = wx.Colour(250, 250, 250) -black_text = wx.Colour(0,0,0) +black_text = wx.Colour(0, 0, 0) + # TODO: Rewrite this code to clean it up... @@ -173,7 +174,7 @@ def __init__(self, parent, list_type="X", size=(200, 400)): id_value = wx.NewIdRef() self.selection = [] self.list_type = list_type - self.parent=parent + self.parent = parent sizer = wx.BoxSizer(wx.VERTICAL) if list_type == "X": self.list = XValueListCtrl(self, id_value, size=size, style=wx.LC_REPORT | wx.BORDER_NONE) @@ -240,7 +241,8 @@ def on_popup_four(self, event): self.list.SetItem(i, 3, val) -datachoices = {0: "Raw Data", 1: "Processed Data", 2: "Zero Charge Mass Spectrum", 3: "CCS (Experimental)", 4: "DoubleDec (Experimental)"} +datachoices = {0: "Raw Data", 1: "Processed Data", 2: "Zero Charge Mass Spectrum", 3: "CCS (Experimental)", + 4: "DoubleDec (Experimental)"} extractchoices = {0: "Height", 1: "Local Max", 2: "Area", 3: "Center of Mass", 4: "Local Max Position", 5: "Center of Mass 50%", 6: "Center of Mass 10%", 11: "Estimated Area"} extractmethods = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 11} @@ -318,7 +320,7 @@ def __init__(self, parent, title, config=None, pks=None, *args, **kwargs): "Normalizes mass deconvolutions to MS1 scan for variable 1 +/- variable 2") self.Bind(wx.EVT_MENU, self.on_msms_norm, self.menumsmsnorm) self.menudd = self.experimentalmenu.Append(wx.ID_ANY, "Batch DoubleDec", - "Run DoubleDec on All Files") + "Run DoubleDec on All Files") self.Bind(wx.EVT_MENU, self.on_doubledec, self.menudd) self.menuautocorr = self.experimentalmenu.Append(wx.ID_ANY, "View Autocorrelation of Sum", "Shows autocorelation plot of sum") @@ -331,7 +333,8 @@ def __init__(self, parent, title, config=None, pks=None, *args, **kwargs): self.Bind(wx.EVT_MENU, self.shade_plots, self.menuplotx) self.experimentalmenu.AppendSeparator() - self.menugfit = self.experimentalmenu.Append(wx.ID_ANY, "Fit Extracted Data to Gaussians", "Fit Extracted Data to Gaussians") + self.menugfit = self.experimentalmenu.Append(wx.ID_ANY, "Fit Extracted Data to Gaussians", + "Fit Extracted Data to Gaussians") self.Bind(wx.EVT_MENU, self.on_fitting, self.menugfit) self.toolsmenu.AppendSeparator() @@ -342,7 +345,7 @@ def __init__(self, parent, title, config=None, pks=None, *args, **kwargs): idval = 500 + i self.scalemenu.Append(idval, self.config.cmaps[i], "", wx.ITEM_RADIO) self.Bind(wx.EVT_MENU, self.menu_fcmaps, id=idval) - if i%20==19: + if i % 20 == 19: self.scalemenu.Break() self.toolsmenu.AppendSubMenu(self.scalemenu, 'Files Color Map') @@ -352,7 +355,7 @@ def __init__(self, parent, title, config=None, pks=None, *args, **kwargs): idval = 1500 + i self.scalemenu2.Append(idval, self.config.cmaps[i], "", wx.ITEM_RADIO) self.Bind(wx.EVT_MENU, self.menu_xcmaps, id=idval) - if i%20==19: + if i % 20 == 19: self.scalemenu2.Break() self.toolsmenu.AppendSubMenu(self.scalemenu2, 'X Value Color Map') @@ -528,7 +531,7 @@ def __init__(self, parent, title, config=None, pks=None, *args, **kwargs): self.offsets = [] self.check_cmaps() self.hdf5_file = "" - self.kernelfile="" + self.kernelfile = "" self.filetype = 0 self.update_set(0) self.Centre() @@ -548,7 +551,7 @@ def __init__(self, parent, title, config=None, pks=None, *args, **kwargs): # self.load(os.path.join(self.directory,"AmtB_04_test.json")) # self.directory = "C:\\Data\\AmtB_DMPC" # self.load(os.path.join(self.directory, "AmtB_07.json")) - if True: + if False: self.directory = "C:\\Data\\Others\\Miranda" self.directory = "Z:\\Group Share\\Marius Kostelic\\Baker Lab AAVs\\Test for gaussian fitting\\" self.load(os.path.join(self.directory, "collection1.json")) @@ -834,7 +837,6 @@ def update_set(self, e): self.ctlmin.SetValue(str(self.range[0])) self.ctlmax.SetValue(str(self.range[1])) - def on_run(self, e=None, vals=None): tstart = time.perf_counter() self.update_get(e) @@ -1030,15 +1032,15 @@ def shade_plots(self, e): val = 0 for xval in xs: val += float(xval) - self.make_shade_plot(val, window, color) + self.make_shade_plot(val, window, color) self.plot1.repaint() def make_shade_plot(self, val, window, color): y0 = np.amin(self.data[0][:, 1]) ywidth = np.amax(self.data[0][:, 1]) - y0 if self.plot1.kdnorm == 1000: - val = val/self.plot1.kdnorm - window = window/self.plot1.kdnorm + val = val / self.plot1.kdnorm + window = window / self.plot1.kdnorm print(val, window) self.plot1.subplot1.add_patch( @@ -1389,7 +1391,7 @@ def on_doubledec(self, e): DoubleDec.batch_dd(self.paths, self.kernelfile) def on_fitting(self, e=None): - starttime= time.perf_counter() + starttime = time.perf_counter() self.update_get(None) print("Starting Fitting") self.extract = [] @@ -1398,20 +1400,20 @@ def on_fitting(self, e=None): fcolor = np.array(l[4:7]) data = self.data[k] print(len(data)) - masses = [float(s) for s in np.array(self.xvals)[:,0]] + masses = [float(s) for s in np.array(self.xvals)[:, 0]] print(masses) print(self.window) fits, fitdat = MD_Fitter(data, mds=masses, maxshift=self.window, widthguess=self.window, ) - fitdat /= np.amax(data[:,1])/np.amax(fitdat) + fitdat /= np.amax(data[:, 1]) / np.amax(fitdat) self.plot1.plotadd(data[:, 0], fitdat, fcolor, nopaint=False, linestyle="--") if not self.plot4.flag: - self.plot4.plotrefreshtop(data[:, 0], data[:, 1]-k, "Extracted Data", "", "", "", None, + self.plot4.plotrefreshtop(data[:, 0], data[:, 1] - k, "Extracted Data", "", "", "", None, nopaint=True, color=fcolor, test_kda=True) else: - self.plot4.plotadd(data[:, 0], data[:,1]-k, fcolor) - self.plot4.plotadd(data[:, 0], fitdat-k, fcolor, linestyle="--") - xext = fits[:,2] + self.plot4.plotadd(data[:, 0], data[:, 1] - k, fcolor) + self.plot4.plotadd(data[:, 0], fitdat - k, fcolor, linestyle="--") + xext = fits[:, 2] self.extract.append(xext) self.plot4.repaint() @@ -1429,7 +1431,7 @@ def on_fitting(self, e=None): np.savetxt(os.path.join(self.directory, "extracts_fitted.txt"), self.extract) endtime = time.perf_counter() - print("Fitting Time: ", endtime-starttime) + print("Fitting Time: ", endtime - starttime) def on_ylabel(self, e): dlg = miscwindows.SingleInputDialog(self) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 410aee1d..b2e74833 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -55,7 +55,7 @@ def init(self, *args, **kwargs): # self.on_dataprep_button(0) # self.on_auto(0) - if False: # and platform.node() == 'DESKTOP-08TGCJO': + if True: # and platform.node() == 'DESKTOP-08TGCJO': print("Opening Test File") path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") diff --git a/unidec/modules/PlotBase.py b/unidec/modules/PlotBase.py index 9b280ac5..f43e38f8 100644 --- a/unidec/modules/PlotBase.py +++ b/unidec/modules/PlotBase.py @@ -4,6 +4,7 @@ from matplotlib.ticker import MaxNLocator from matplotlib import rcParams import matplotlib.cm as cm +import matplotlib as mpl import io from matplotlib.backends.backend_svg import FigureCanvasSVG # noinspection PyUnresolvedReferences @@ -410,3 +411,12 @@ def write_data(self, path): print("Saving Data to", path) print("Data Dimensions:", self.data.shape) np.savetxt(path, self.data) + + def update_style(self, stylefile=None): + if stylefile is None: + stylefile = self.config.mplstylefile + try: + mpl.style.use(stylefile) + except Exception as e: + print(e) + print("Failed to load style file:", stylefile) diff --git a/unidec/modules/gui_elements/PlotControlsPanel.py b/unidec/modules/gui_elements/PlotControlsPanel.py new file mode 100644 index 00000000..763ed460 --- /dev/null +++ b/unidec/modules/gui_elements/PlotControlsPanel.py @@ -0,0 +1,104 @@ +import matplotlib as mpl +import wx +from unidec.modules.PlottingWindow import Plot1d +from unidec.modules.unidecstructure import UniDecConfig + +mpl.rcParams['ps.useafm'] = True +mpl.rcParams['ps.fonttype'] = 42 +mpl.rcParams['pdf.fonttype'] = 42 + +plot_params = {"font.size": 12, "xtick.labelsize": 12, "ytick.labelsize": 12, "lines.linewidth": 1.5, + "lines.linestyle": "-", "font.family": "Arial", "axes.linewidth": 1.5, "axes.labelpad": 4.0, + "axes.xmargin": 0.05, "axes.ymargin": 0.05, + } + + +# Export plot_params to .mplstyle file +def export_mplstyle(params, filename="unidec.mplstyle"): + with open(filename, "w") as f: + for p in params: + f.write(p + ": " + str(params[p]) + "\n") + + +# Plot Controls Panel +class PlotControlsPanel(wx.Panel): + def __init__(self, parent, pres, config=None, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + if config is None: + self.config = UniDecConfig() + else: + self.config = config + # self.SetBackgroundColour("white") + self.parent = parent + self.pres = pres + self.sizer = wx.GridBagSizer(wx.VERTICAL) + self.SetSizer(self.sizer) + + # For p in plot_params, create a text box input + for i, p in enumerate(plot_params): + label = wx.StaticText(self, label=p) + txt_ctrl = wx.TextCtrl(self, value=str(plot_params[p]), name=p) + + # Insert into sizer + self.sizer.Add(label, pos=(i, 0), flag=wx.ALL, border=5) + self.sizer.Add(txt_ctrl, pos=(i, 1), flag=wx.ALL, border=5) + + # Add in apply button + self.apply_button = wx.Button(self, label="Apply") + self.sizer.Add(self.apply_button, pos=(len(plot_params), 0), flag=wx.ALL, border=5) + self.apply_button.Bind(wx.EVT_BUTTON, self.on_apply) + + # Finish + self.sizer.Layout() + self.Fit() + + def get_values_from_gui(self, event=None): + for child in self.GetChildren(): + if isinstance(child, wx.TextCtrl): + plot_params[child.GetName()] = child.GetValue() + + def set_values_to_gui(self, event=None): + for child in self.GetChildren(): + if isinstance(child, wx.TextCtrl): + child.SetValue(str(plot_params[child.GetName()])) + + def on_apply(self, event=None): + self.get_values_from_gui() + export_mplstyle(plot_params, filename=self.config.mplstylefile) + self.pres.on_update_plot_params(stylefile=self.config.mplstylefile) + + +class PlotControlWindow(wx.Frame): + ''' + Test window for PlotControlsPanel + ''' + + def __init__(self, parent, *args, **kwargs): + wx.Frame.__init__(self, parent, *args, **kwargs) + + self.config = UniDecConfig() + sizer = wx.BoxSizer(wx.HORIZONTAL) + plotpanel = wx.Panel(self) + self.plot = Plot1d(plotpanel) + sizer.Add(self.plot, 1, wx.EXPAND) + + self.panel = PlotControlsPanel(plotpanel, self) + sizer.Add(self.panel, 0, wx.EXPAND) + + plotpanel.SetSizer(sizer) + sizer.Fit(self) + self.makeplot() + self.Show() + + def makeplot(self): + self.plot.plotrefreshtop([1, 2, 3], [1, 4, 9]) + def on_update_plot_params(self, event=None, stylefile=None): + self.plot.update_style(stylefile) + self.makeplot() + + + +if __name__ == '__main__': + app = wx.App(False) + frame = PlotControlWindow(None, -1, "Plot Controls") + app.MainLoop() diff --git a/unidec/modules/gui_elements/ud_controls.py b/unidec/modules/gui_elements/ud_controls.py index dafc8a5a..3c946e00 100644 --- a/unidec/modules/gui_elements/ud_controls.py +++ b/unidec/modules/gui_elements/ud_controls.py @@ -5,7 +5,7 @@ import numpy as np import time import wx.lib.scrolledpanel as scrolled - +from unidec.modules.gui_elements.PlotControlsPanel import PlotControlsPanel class main_controls(wx.Panel): # scrolled.ScrolledPanel): # noinspection PyMissingConstructor @@ -683,6 +683,11 @@ def __init__(self, parent, config, pres, panel, iconfile): self.foldpanels.AddFoldPanelWindow(foldpanel3b, panel3b, fpb.FPB_ALIGN_WIDTH) self.foldpanels.AddFoldPanelWindow(foldpanel3b, wx.StaticText(foldpanel3b, -1, " "), fpb.FPB_ALIGN_WIDTH) + # Create plotcontrol panel + #foldpanel4 = self.foldpanels.AddFoldPanel(caption="Plot Controls", collapsed=False, cbstyle=style3b) + #pcpanel = PlotControlsPanel(foldpanel4, self.config) + #self.foldpanels.AddFoldPanelWindow(foldpanel4, pcpanel, fpb.FPB_ALIGN_WIDTH) + bright = 250 foldpanel1.SetBackgroundColour(wx.Colour(bright, bright, 255)) foldpanel1b.SetBackgroundColour(wx.Colour(bright, bright, 255)) diff --git a/unidec/modules/hdf5_tools.py b/unidec/modules/hdf5_tools.py index 2b1712e4..5025b018 100644 --- a/unidec/modules/hdf5_tools.py +++ b/unidec/modules/hdf5_tools.py @@ -36,6 +36,12 @@ def get_data(f, group, dataset): return data +def get_mass_data(f): + x = get_data(f, "ms_dataset", "mass_axis") + y = get_data(f, "ms_dataset", "mass_sum") + return np.transpose([x, y]) + + def get_metadata(f, key): hdf = h5py.File(f) g = hdf.require_group("ms_dataset") diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index ed51602a..4b00e139 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -83,6 +83,8 @@ def __init__(self): self.UniDecDir = '' self.UniDecName = '' self.defaultUnidecDir = '' + self.mplstyledir = '' + self.mplstylefile = 'UniDec.mplstyle' self.opencommand = '' self.defaultUnidecName = '' self.iconfile = '' @@ -1403,6 +1405,8 @@ def giveup(): self.exampledatadirUC = os.path.join(self.UniDecDir, "Example Data", "UniChrom") self.toplogofile = os.path.join(self.UniDecDir, "UniDecLogoMR.png") self.iconfile = os.path.join(self.UniDecDir, "logo.ico") + self.mplstyledir = os.path.join(self.UniDecDir, "PlotStyles") + self.mplstylefile = os.path.join(self.mplstyledir, "UniDec.mplstyle") print("\nUniDec Path:", self.UniDecPath) From 64d76624f4ed2eab26c468cbce3409ae91d1d6d1 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Tue, 13 Feb 2024 17:39:52 -0700 Subject: [PATCH 11/35] Adding FT-IM-CD-MS options into UniChromCD --- unidec/UniChromCD.py | 110 +++-- unidec/metaunidec/ultrameta.py | 2 +- unidec/modules/HTEng.py | 378 +++++++++++++----- unidec/modules/gui_elements/CDWindow.py | 21 +- unidec/modules/gui_elements/CD_controls.py | 187 ++++++--- .../modules/thermo_reader/ThermoImporter.py | 2 +- unidec/modules/unidecstructure.py | 23 +- 7 files changed, 512 insertions(+), 211 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index b2e74833..29a5c984 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -60,7 +60,9 @@ def init(self, *args, **kwargs): path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") - self.on_open_file(None, None, path=path) + pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" + + self.on_open_file(None, None, path=pathft) # self.eng.process_data_scans() # self.make_cube_plot() # self.make_mass_time_2dplot() @@ -118,7 +120,8 @@ def on_pick_peaks(self, e=None): self.cc.add_chromatogram(chromdat, color=p.color, label=label, ht=False) if self.eng.fullmstack_ht is not None: htdata = self.eng.get_mass_eic(massrange, ht=True) - self.cc.add_chromatogram(htdata, color=p.color, label=label + " HT", ht=True) + self.cc.add_chromatogram(htdata, color=p.color, label=label + " " + self.eng.config.demultiplexmode, + ht=True) self.plot_chromatograms() def on_select_mzz_region(self): @@ -180,7 +183,7 @@ def on_run_eic_ht(self, e=None): self.export_config(self.eng.config.confname) for c in self.cc.chromatograms: if "TIC" not in c.label: - if "HT" not in c.label: + if not c.ht: self.run_eic_ht(c.mzrange, c.zrange, color=c.color, add_eic=False, plot=False) self.plot_chromatograms() @@ -190,10 +193,11 @@ def run_tic_ht(self): :return: None """ self.cc.clear() - data = self.eng.get_tic(normalize=self.eng.config.datanorm) + self.eng.get_tic(normalize=self.eng.config.datanorm) htdata = self.eng.tic_ht(normalize=self.eng.config.datanorm) - self.cc.add_chromatogram(data, color="black", label="TIC") - self.cc.add_chromatogram(htdata, color="red", label="TIC HT", ht=True) + ticdata = np.transpose(np.vstack((self.eng.fulltime, self.eng.fulltic))) + self.cc.add_chromatogram(ticdata, color="black", label="TIC") + self.cc.add_chromatogram(htdata, color="red", label="TIC " + self.eng.config.demultiplexmode, ht=True) self.showht = True self.plot_chromatograms(save=False) @@ -212,7 +216,8 @@ def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) if add_eic: self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) - self.cc.add_chromatogram(htdata, color=color, zrange=zrange, mzrange=mzrange, ht=True) + self.cc.add_chromatogram(htdata, color=color, zrange=zrange, mzrange=mzrange, ht=True, + mode=self.eng.config.demultiplexmode) if plot: self.plot_chromatograms() @@ -224,35 +229,46 @@ def plot_chromatograms(self, e=None, save=True): :return: None """ self.makeplot1() + self.view.SetStatusText("# Ions: " + str(len(self.eng.farray)), number=2) self.view.plottic.clear_plot() + self.view.plotdecontic.clear_plot() self.view.chrompanel.list.populate(self.cc) for c in self.cc.chromatograms: if c.ignore: continue if self.eng.config.HTxaxis == "Scans": - xdat = self.eng.fullscans + xdat = np.arange(0, len(c.chromdat)) + # xdat = self.eng.fullscans xlab = "Scan Number" else: xdat = c.chromdat[:, 0] xlab = "Time" - if not self.view.plottic.flag: - self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, - zoomout=True, label=c.label, xlabel=xlab, - color=c.color, nopaint=True) + if c.ht: + plot = self.view.plotdecontic else: - self.view.plottic.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, - newlabel=c.label) + plot = self.view.plottic + + if not plot.flag: + plot.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, + zoomout=True, label=c.label, xlabel=xlab, + color=c.color, nopaint=True) + else: + plot.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) xlimits = c.mzrange ylimits = c.zrange if xlimits[0] != -1 and ylimits[0] != -1 and xlimits[1] != -1 and ylimits[1] != -1: self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], edgecolor=c.color, facecolor=c.color, nopaint=True) - - self.view.plottic.add_legend() - self.view.plottic.repaint() + if self.view.plotdecontic.flag: + self.view.plotdecontic.add_legend() + self.view.plotdecontic.repaint() + if self.view.plottic.flag: + self.view.plottic.add_legend() + self.view.plottic.repaint() self.view.plot1.repaint() if save: self.save_chroms() @@ -296,11 +312,13 @@ def load_chroms(self, fname=None, array=None): mzrange = [float(a[3]), float(a[4])] zrange = [float(a[5]), float(a[6])] - if "TIC" in label or "HT" in label or "Mass EIC" in label: + if "TIC" in label or self.eng.config.demultiplexmode in label or "Mass EIC" in label: continue chromdat = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) - self.cc.add_chromatogram(chromdat, color=color, label=label, ht=ht, zrange=zrange, mzrange=mzrange) + ht = ht.lower() in ['true', '1', 't', 'y', 'yes', 'yeah'] + + self.cc.add_chromatogram(chromdat, color=color, label=label, ht=bool(ht), zrange=zrange, mzrange=mzrange) self.plot_chromatograms() @@ -333,8 +351,6 @@ def on_select_time_range(self, e=None): :param e: Unused event :return: None """ - if not self.showht or self.eng.fullhstack_ht is None: - return if not wx.GetKeyState(wx.WXK_CONTROL): self.plot_chromatograms() xlimits = self.view.plottic.subplot1.get_xlim() @@ -344,6 +360,29 @@ def on_select_time_range(self, e=None): # Plot Red box on plottic self.view.plottic.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], edgecolor="red", facecolor="red", nopaint=False) + self.select_ht_range(range=xlimits, raw=True) + + def on_select_time_range_decon(self, e=None): + """ + Event triggered by a click on the TIC plot. Select a time range and create a 2D m/z vs z sum from + that time range post-HT. + :param e: Unused event + :return: None + """ + if not self.showht or self.eng.fullhstack_ht is None: + print("Need to process full data first") + print("Running Decon on all data") + self.run_all_ht() + return + if not wx.GetKeyState(wx.WXK_CONTROL): + self.plot_chromatograms() + xlimits = self.view.plotdecontic.subplot1.get_xlim() + print("New limits:", xlimits) + self.view.plotdecontic.reset_zoom() + ylimits = self.view.plotdecontic.subplot1.get_ylim() + # Plot Red box on plottic + self.view.plotdecontic.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], + edgecolor="red", facecolor="red", nopaint=False) self.select_ht_range(range=xlimits) def on_run_all_ht(self, e=None): @@ -364,7 +403,7 @@ def run_all_ht(self): self.eng.process_data_scans() ticdat = self.eng.run_all_ht() - self.cc.add_chromatogram(ticdat, color="gold", label="TIC*_HT") + self.cc.add_chromatogram(ticdat, color="gold", label="TIC*_" + self.eng.config.demultiplexmode, ht=True) # tic2 = np.sum(self.eng.fullhstack, axis=(1, 2)) # ticdat2 = np.transpose(np.vstack((self.eng.fulltime, tic2))) @@ -382,21 +421,28 @@ def run_all_mass_transform(self, e=None): self.eng.transform_stacks() self.cc.add_chromatogram(self.eng.mass_tic, color="grey", label="Mass TIC") if self.eng.fullmstack_ht is not None: - self.cc.add_chromatogram(self.eng.mass_tic_ht, color="goldenrod", label="Mass TIC HT") + self.cc.add_chromatogram(self.eng.mass_tic_ht, color="goldenrod", + label="Mass TIC " + self.eng.config.demultiplexmode, ht=True) self.plot_chromatograms() - def select_ht_range(self, range=None): + def select_ht_range(self, range=None, raw=False): """ Select a time range and create a 2D m/z vs z sum from that time range post-HT. :param range: Time range to select. Default None, which should be all times :return: None """ - self.eng.select_ht_range(range=range) + if raw: + self.eng.select_raw_range(range=range) + self.view.SetStatusText("Raw # Ions: " + str(np.sum(self.eng.harray)), number=2) + else: + self.eng.select_ht_range(range=range) + self.view.SetStatusText(self.eng.config.demultiplexmode + " # Ions: " + str(np.sum(self.eng.harray)), number=2) self.makeplot1() self.makeplot2() self.makeplot3() self.makeplot4() + def make_charge_time_2dplot(self, e=None): """ Creates a 2D plot of charge vs time @@ -443,9 +489,11 @@ def make_2d_plot(self, e=None, face=1): if self.eng.config.HTxaxis == "Scans": xdat = self.eng.fullscans + xdat2 = self.eng.deconscans xlab = "Scan Number" else: xdat = self.eng.fulltime + xdat2 = self.eng.decontime xlab = "Time" if face == 1: @@ -485,7 +533,7 @@ def make_2d_plot(self, e=None, face=1): grid = np.clip(self.eng.fullmstack_ht, 0, np.amax(self.eng.fullmstack_ht)) else: return - self.view.plot8.contourplot(xvals=xdat, yvals=y, zgrid=grid, + self.view.plot8.contourplot(xvals=xdat2, yvals=y, zgrid=grid, xlab=xlab, ylab=ylab, config=self.eng.config, discrete=discrete) print("Finished 2D Plot", (time.perf_counter() - starttime), " s") @@ -512,9 +560,11 @@ def make_cube_plot(self, e=None, mass=False): if self.eng.config.HTxaxis == "Scans": xdat = self.eng.fullscans + xdat2 = self.eng.deconscans xlab = "Scan Number" else: xdat = self.eng.fulltime + xdat2 = self.eng.decontime xlab = "Time" try: @@ -561,15 +611,15 @@ def make_cube_plot(self, e=None, mass=False): else: face3 = np.sum(self.eng.fullhstack_ht, axis=0).transpose() face2 = np.sum(self.eng.fullhstack_ht, axis=1).transpose() - - self.view.plot10.cubeplot(ydat, self.eng.zaxis[1:], xdat, + print(np.shape(face1), np.shape(face2), np.shape(face3)) + self.view.plot10.cubeplot(ydat, self.eng.zaxis[1:], xdat2, face3, face2, face1, xlab=ylab, ylab="Charge", zlab=xlab, cmap=self.eng.config.cmap) endtime = time.perf_counter() print("Finished HT Cube in: ", (endtime - starttime), " s") except Exception as ex: - print("Failed HT cube", ex) + print("Failed HT cube:", ex) pass def on_auto_set_ct(self, e=None): @@ -590,7 +640,7 @@ def on_plot_kernel(self, e=None): """ self.export_config(self.eng.config.confname) if not self.showht: - self.eng.setup_ht() + self.eng.setup_demultiplex() data = self.eng.htkernel if self.eng.config.HTxaxis == "Scans": diff --git a/unidec/metaunidec/ultrameta.py b/unidec/metaunidec/ultrameta.py index 13dc9c03..b0165a61 100644 --- a/unidec/metaunidec/ultrameta.py +++ b/unidec/metaunidec/ultrameta.py @@ -248,7 +248,7 @@ def __init__(self, parent, title, config=None, *args, **kwargs): # testfile = "collection2.json" # testdir = "C:\\Data\\Triplicate Data" testdir = "C:\Data\Guozhi" - # testdir = "Z:\Group Share\James Rohrbough\Peptide nanodiscs\D1T0 Mellitin\DMPC" + # testdir = "Z:\Group Share\JamesKeener Rohrbough\Peptide nanodiscs\D1T0 Mellitin\DMPC" testfile = "collection1.json" self.load(os.path.join(testdir, testfile)) # self.on_plot_all() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 93abdacb..b1204936 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -1,16 +1,16 @@ import time import scipy.ndimage -from modules.fitting import * +from unidec.modules.fitting import * -from modules.CDEng import * -from modules.ChromEng import * +from unidec.modules.CDEng import * +from unidec.modules.ChromEng import * import unidec.tools as ud -from modules.unidecstructure import DataContainer +# from modules.unidecstructure import DataContainer import matplotlib.pyplot as plt import scipy.fft as fft - +import math # import fast_histogram # import pyfftw.interfaces.numpy_fft as fft @@ -36,6 +36,8 @@ def __init__(self, *args, **kwargs): self.fftk = [] self.htoutput = [] self.indexrange = [0, 0] + self.decontime = [] + self.deconscans = [] # Index Values self.padindex = 0 @@ -89,6 +91,28 @@ def parse_file_name(self, path): self.config.htseq = '1110100' self.config.htbit = 3 + if "frange" in path: + # Split the file name by _ + parts = path.split("_") + # Find the part that contains frange + frangeindex = [i for i, s in enumerate(parts) if "frange" in s] + if len(frangeindex) > 0: + frangeindex = frangeindex[0] + # get the next two fields + ftstart = parts[frangeindex+1] + ftend = parts[frangeindex+2] + + # Convert to float and set to field in config + try: + self.config.FTstart = float(ftstart) + except Exception as e: + print("Error setting FTstart:", e, ftstart) + try: + self.config.FTend = float(ftend) + except Exception as e: + print("Error setting FTend:", e, ftend) + print("FT Start:", self.config.FTstart, "FT End:", self.config.FTend) + print("Cycle Time:", self.config.HTcycletime, "Time Pad:", self.config.HTtimepad, "HT Sequence:", self.config.htseq) @@ -171,6 +195,8 @@ def setup_ht(self, cycleindex=None): # Make fft of kernel for later use self.fftk = fft.rfft(self.htkernel).conj() + self.decontime = self.fulltime + self.deconscans = self.fullscans[:len(self.decontime)] def correct_cycle_time(self): """ @@ -179,7 +205,40 @@ def correct_cycle_time(self): """ print("Correcting") self.get_cycle_time(data) - self.setup_ht(cycleindex=self.cycleindex) + self.setup_demultiplex(cycleindex=self.cycleindex) + + def run_demultiplex(self, data, mode=None, **kwargs): + """ + Overarching function to demultiplex data. Calls each demultiplexing type based on config parameter + :param data: 1D array of data to be deconvolved. Should be same dimension as self.htkernel if HT mode. + :param mode: Type of demultiplexing. Default is HT. + :param kwargs: Additional keyword arguments. + :return: Demultiplexed data. Same length as input. + """ + if mode is None: + mode = self.config.demultiplexmode + + if mode == "HT": + return self.htdecon(data, **kwargs) + elif mode == "FT": + return self.ftdecon(data, aFT=False, **kwargs) + elif mode == "aFT": + return self.ftdecon(data, aFT=True, **kwargs) + + def setup_demultiplex(self, mode=None, **kwargs): + """ + Overarching function to set up demultiplexing. Calls each demultiplexing type based on config parameter + :param mode: Type of demultiplexing. Default is HT. + :param kwargs: Additional keyword arguments. + :return: None + """ + if mode is None: + mode = self.config.demultiplexmode + + if mode == "HT": + self.setup_ht(**kwargs) + elif mode == "FT" or mode == "aFT": + self.setup_ft(**kwargs) def htdecon(self, data, **kwargs): """ @@ -215,58 +274,98 @@ def htdecon(self, data, **kwargs): if kwargs["normalize"]: output /= np.amax(output) # Return demultiplexed data - return output + return output, data - def htdecon_speedy(self, data): + def setup_ft(self, FTstart=None, FTend=None, nzp=None): """ - Deconvolve the data using the HT kernel. Need to call setup_ht first. Currently unused. - :param data: 1D data array. Should be same dimension as self.htkernel. - :return: Demultiplexed data. Same length as input. + Set up the Fourier Transform Deconvolution. Sets self.decontime with drift time axis. + :param FTstart: Starting frequence (hz) + :param FTend: Ending frequence (hz) + :return: None """ - # Do the convolution, and only the convolution... :) - return fft.irfft(fft.rfft(data) * self.fftk).real + if FTstart is None: + FTstart = self.config.FTstart + else: + self.config.FTstart = FTstart + if FTend is None: + FTend = self.config.FTend + else: + self.config.FTend = FTend - ''' - def decon_3d_fft(self, array, **kwargs): + x = self.fulltime + + if nzp is None: + nzp = self.config.HTtimepad + + x_len = int(len(x)) + sweep_time = x[-1] + + if nzp > 0: + pad_len = int(2 ** math.ceil(math.log2(int(len(x)))) * nzp) + time_step = (x[1] - x[0]) + x = np.linspace(x[0], x[-1] + time_step * (pad_len-x_len), pad_len) + #self.fulltime = x + #self.fullscans = np.arange(len(self.fulltime)) + + freq = np.fft.rfftfreq(len(x), d=(x[1] - x[0]) * 60) + + # Create time axis + sweepRate = (FTend - FTstart) / (sweep_time * 60) + self.decontime = freq / sweepRate + self.deconscans = self.fullscans[:len(self.decontime)] + + def ftdecon(self, data, flattenTIC=True, apodize=True, aFT=False, normalize=False, nzp=None): """ - Developed this to see if it would speed things up. It turns out not to. About half as slow. Leaving in for - legacy reasons and because it's super cool code. - :param array: 3D array of data to be deconvolved. - Should be same length as self.htkernel with the other dimensions set by the harray size. - :param kwargs: Keyword arguments. Currently supports "normalize" which normalizes the output to the maximum - value. - :return: Demultiplexed data array. Same length as input array. + Perform Fourier Transform Deconvolution + :param data: 1D data array of y-data only + :param flattenTIC: Whether to flatten the TIC before demultiplexing to remove low frequency components + :param apodize: Whether to apodize the data with a Hanning window + :param aFT: Whether to use Absorption FT mode + :return: 1D array of demultiplexed data. Same length as input. """ - starttime = time.perf_counter() - # Slice data to appropriate range - dims = np.shape(array) - self.indexrange = [self.padindex - self.shiftindex, dims[0] - self.shiftindex] - data = array[self.indexrange[0]:self.indexrange[1]] - dims2 = np.shape(data) - print(dims2) - - # HT Kernel 3D FFT - # Create 3D array of copies of 1D kernel - kernel3d = np.broadcast_to(self.htkernel[:, np.newaxis, np.newaxis], dims2) - - print("3D Kernel", np.shape(kernel3d), time.perf_counter() - starttime) - - # FFT of kernel3d - fftk3d = fft.rfftn(kernel3d).conj() - print("3D FFT of Kernel", np.shape(fftk3d), time.perf_counter() - starttime) - - data_fft = fft.rfftn(data) - print("3D FFT of Data", np.shape(data_fft), time.perf_counter() - starttime) - - # Deconvolve - output = fft.irfftn(data_fft * fftk3d) - print("Decon", np.shape(output), time.perf_counter() - starttime) - output = np.real(output) - if "normalize" in kwargs: - if kwargs["normalize"]: - output /= np.amax(output) - # Return demultiplexed data - return output''' + if np.amax(data) == 0: + return data[:len(self.decontime)], data + + y = data + + if self.config.HTksmooth > 0: + y = scipy.signal.savgol_filter(y, int(np.round(float(self.config.HTksmooth))), 3) + + if flattenTIC: + # fit trendline to y and subtract to eliminate low frequency components + ytrnd = scipy.signal.savgol_filter(y, 15, 3) + y = y - ytrnd + + if apodize: + # create hanning window + hanning = np.hanning(len(y) * 2) + y = y * hanning[len(y):] + + if nzp is None: + nzp = self.config.HTtimepad + + original_len = len(y) + if nzp > 0 and apodize: + pad_len = int(2 ** math.ceil(math.log2(int(len(y)))) * nzp) + z = np.zeros(pad_len) + z[:len(y)] = y + y = z + + # Fourier Transform + Y = np.fft.rfft(y) + + if aFT: + maxindex = np.argmax(np.abs(Y[5:])) + 5 + phase = np.angle(Y[maxindex]) + Y = Y * np.exp(-1j * phase) + Y = np.real(Y) + else: + Y = np.abs(Y) + + if normalize: + Y /= np.amax(Y) + + return Y, y[:original_len] def set_timepad_index(self, timepad): """ @@ -323,6 +422,58 @@ def get_cycle_time(self, data=None, cycleindexguess=None, widthguess=110): print("Cycle Index:", self.cycleindex, "Cycle Time:", self.config.HTcycletime) return ac + ''' + + def htdecon_speedy(self, data): + """ + Deconvolve the data using the HT kernel. Need to call setup_ht first. Currently unused. + :param data: 1D data array. Should be same dimension as self.htkernel. + :return: Demultiplexed data. Same length as input. + """ + # Do the convolution, and only the convolution... :) + return fft.irfft(fft.rfft(data) * self.fftk).real, data + + def decon_3d_fft(self, array, **kwargs): + """ + Developed this to see if it would speed things up. It turns out not to. About half as slow. Leaving in for + legacy reasons and because it's super cool code. + :param array: 3D array of data to be deconvolved. + Should be same length as self.htkernel with the other dimensions set by the harray size. + :param kwargs: Keyword arguments. Currently supports "normalize" which normalizes the output to the maximum + value. + :return: Demultiplexed data array. Same length as input array. + """ + starttime = time.perf_counter() + # Slice data to appropriate range + dims = np.shape(array) + self.indexrange = [self.padindex - self.shiftindex, dims[0] - self.shiftindex] + data = array[self.indexrange[0]:self.indexrange[1]] + dims2 = np.shape(data) + print(dims2) + + # HT Kernel 3D FFT + # Create 3D array of copies of 1D kernel + kernel3d = np.broadcast_to(self.htkernel[:, np.newaxis, np.newaxis], dims2) + + print("3D Kernel", np.shape(kernel3d), time.perf_counter() - starttime) + + # FFT of kernel3d + fftk3d = fft.rfftn(kernel3d).conj() + print("3D FFT of Kernel", np.shape(fftk3d), time.perf_counter() - starttime) + + data_fft = fft.rfftn(data) + print("3D FFT of Data", np.shape(data_fft), time.perf_counter() - starttime) + + # Deconvolve + output = fft.irfftn(data_fft * fftk3d) + print("Decon", np.shape(output), time.perf_counter() - starttime) + output = np.real(output) + if "normalize" in kwargs: + if kwargs["normalize"]: + output /= np.amax(output) + # Return demultiplexed data + return output''' + class UniChromHT(HTEng, ChromEngine): def __init__(self, *args, **kwargs): @@ -349,18 +500,26 @@ def open_file(self, path): self.parse_file_name(path) print("Loaded File:", path) + def get_eic(self, massrange): + """ + Get the EIC from the chromatogram. + :param massrange: Mass range for EIC selection [low, high] + :return: + """ + return self.chromdat.get_eic(mass_range=np.array(massrange)) + def eic_ht(self, massrange): """ Get the EIC and run HT on it. - :param massrange: Mass range for EIC selection + :param massrange: Mass range for EIC selection [low, high] :return: Demultiplexed data output """ - eic = self.chromdat.get_eic(mass_range=np.array(massrange)) + eic = self.get_eic(massrange) print(eic.shape) self.fulltic = eic[:, 1] self.fulltime = eic[:, 0] - self.setup_ht() - self.htoutput = self.htdecon(self.fulltic) + self.setup_demultiplex() + self.htoutput = self.run_demultiplex(self.fulltic)[0] return self.htoutput def tic_ht(self, correct=False, **kwargs): @@ -372,8 +531,8 @@ def tic_ht(self, correct=False, **kwargs): """ self.fulltic = self.ticdat[:, 1] self.fulltime = self.ticdat[:, 0] - self.setup_ht() - self.htoutput = self.htdecon(self.fulltic, correct=correct, **kwargs) + self.setup_demultiplex() + self.htoutput = self.run_demultiplex(self.fulltic, correct=correct, **kwargs)[0] return self.htoutput @@ -441,6 +600,8 @@ def prep_time_domain(self): self.scans = np.unique(self.farray[:, 2]) self.fullscans = np.arange(1, np.amax(self.scans) + 1) self.fulltime = self.fullscans * self.config.HTanalysistime / np.amax(self.fullscans) + self.decontime = self.fulltime + self.deconscans = self.fullscans def process_data_scans(self, transform=True): """ @@ -485,7 +646,6 @@ def process_data_scans(self, transform=True): maxval = np.amax(self.topharray) if maxval > 0: self.topharray /= maxval - print("T:", time.perf_counter() - starttime) # Reshape the data into a 3 column array # self.data.data3 = np.transpose([np.ravel(self.X, order="F"), np.ravel(self.Y, order="F"), # np.ravel(self.topharray, order="F")]) @@ -612,9 +772,10 @@ def tic_ht(self, **kwargs): :return: Demultiplexed data output. 2D array (time, intensity) """ self.get_tic(**kwargs) - self.setup_ht() - self.htoutput = self.htdecon(self.fulltic, **kwargs) - return np.transpose([self.fulltime, self.htoutput]) + self.setup_demultiplex() + self.htoutput, self.fulltic = self.run_demultiplex(self.fulltic, **kwargs) + + return np.transpose([self.decontime, self.htoutput]) def get_eic(self, mzrange, zrange, **kwargs): """ @@ -646,10 +807,10 @@ def eic_ht(self, mzrange, zrange, **kwargs): :param kwargs: Keyword arguments. Passed down to create_chrom and htdecon. :return: Demultiplexed data output. 2D array (time, intensity) """ - eic = self.get_eic(mzrange, zrange,**kwargs) - self.setup_ht() - self.htoutput = self.htdecon(eic[:, 1], **kwargs) - return np.transpose([self.fulltime, self.htoutput]), eic + eic = self.get_eic(mzrange, zrange, **kwargs) + self.setup_demultiplex() + self.htoutput, eic[:, 1] = self.run_demultiplex(eic[:, 1], **kwargs) + return np.transpose([self.decontime, self.htoutput]), eic def run_all_ht(self): """ @@ -661,7 +822,7 @@ def run_all_ht(self): self.process_data_scans() self.clear_arrays(massonly=True) # Setup HT - self.setup_ht() + self.setup_demultiplex() ''' # self.fullhstack_ht = self.decon_3d(self.fullhstack) @@ -693,27 +854,27 @@ def run_all_ht(self): print("Full HT Demultiplexing Done:", time.perf_counter() - starttime) starttime = time.perf_counter()''' # Run the HT on each track in the stack - self.fullhstack_ht = np.empty((len(self.fullscans), self.topharray.shape[0], self.topharray.shape[1])) + self.fullhstack_ht = np.empty((len(self.decontime), self.topharray.shape[0], self.topharray.shape[1])) for i in range(len(self.mz)): for j in range(len(self.ztab)): trace = self.fullhstack[:, j, i] tracesum = self.topharray[j, i] if tracesum <= self.config.intthresh: - htoutput = np.zeros_like(trace) + htoutput = np.zeros_like(self.decontime) else: - htoutput = self.htdecon(trace) + htoutput, trace = self.run_demultiplex(trace) self.fullhstack_ht[:, j, i] = htoutput # Clip all values below 1e-6 to zero # self.fullhstack_ht[np.abs(self.fullhstack_ht) < 1e-6] = 0 tic = np.sum(self.fullhstack_ht, axis=(1, 2)) - ticdat = np.transpose(np.vstack((self.fulltime, tic))) + ticdat = np.transpose(np.vstack((self.decontime, tic))) if self.config.datanorm == 1: norm = np.amax(ticdat[:, 1]) ticdat[:, 1] /= norm - self.fullhstack_ht /= norm + #self.fullhstack_ht /= norm print("Full HT Demultiplexing Done:", time.perf_counter() - starttime) return ticdat @@ -724,13 +885,32 @@ def select_ht_range(self, range=None): :param range: Time range :return: 2D histogram array """ + if range is None: + range = [np.amin(self.decontime), np.amax(self.decontime)] + b1 = self.decontime >= range[0] + b2 = self.decontime <= range[1] + b = np.logical_and(b1, b2) + substack_ht = self.fullhstack_ht[b] + self.harray = np.sum(substack_ht, axis=0) + self.harray = np.clip(self.harray, 0, np.amax(self.harray)) + self.harray_process() + return self.harray + + def select_raw_range(self, range=None): + """ + Select a range of time from raw data and processes it as a histogram array + :param range: Time range + :return: 2D histogram array + """ + if self.fullhstack is None: + self.process_data_scans() if range is None: range = [np.amin(self.fulltime), np.amax(self.fulltime)] b1 = self.fulltime >= range[0] b2 = self.fulltime <= range[1] b = np.logical_and(b1, b2) - substack_ht = self.fullhstack_ht[b] - self.harray = np.sum(substack_ht, axis=0) + substack = self.fullhstack[b] + self.harray = np.sum(substack, axis=0) self.harray = np.clip(self.harray, 0, np.amax(self.harray)) self.harray_process() return self.harray @@ -783,39 +963,18 @@ def transform_stacks(self): self.fullmstack /= norm print("Full Mass 1 Transform Done:", time.perf_counter() - starttime) - ''' - self.fullmstack = np.zeros((len(self.fullscans), mlen)) - for i in range(len(self.fullscans)): - for j in range(len(self.ztab)): - d = self.fullhstack[i, j] - - if self.config.poolflag == 1: - newdata = np.transpose([self.mass[j], d]) - output = ud.linterpolate(newdata, self.massaxis)[:, 1] - else: - boo1 = d != 0 - newdata = np.transpose([self.mass[j][boo1], d[boo1]]) - if len(newdata) == 0: - output = self.massaxis * 0 - else: - output = ud.lintegrate(newdata, self.massaxis, fastmode=True)[:, 1] - - self.fullmstack[i, :] += output - - self.mass_tic = np.transpose([self.fulltime, np.sum(self.fullmstack, axis=1)]) - print("Full Mass 1 Transform Done:", time.perf_counter() - starttime)''' if self.fullhstack_ht is None: return self.fullmstack_ht = self.transform_array(self.fullhstack_ht) - self.mass_tic_ht = np.transpose([self.fulltime, np.sum(self.fullmstack_ht, axis=1)]) + self.mass_tic_ht = np.transpose([self.decontime, np.sum(self.fullmstack_ht, axis=1)]) if self.config.datanorm == 1: norm = np.amax(self.mass_tic_ht[:, 1]) self.mass_tic_ht[:, 1] /= norm - self.fullmstack_ht /= norm + #self.fullmstack_ht /= norm print("Full Mass 2 Transform Done:", time.perf_counter() - starttime) def get_mass_eic(self, massrange, ht=False): @@ -832,21 +991,24 @@ def get_mass_eic(self, massrange, ht=False): if ht: array = self.fullmstack_ht + xvals = self.decontime else: array = self.fullmstack + xvals = self.fulltime substack = array[:, b] mass_eic = np.sum(substack, axis=1) - return np.transpose([self.fulltime, mass_eic]) + return np.transpose([xvals, mass_eic]) if __name__ == '__main__': eng = UniDecCDHT() + # eng = UniChromHT() dir = "C:\Data\HT-CD-MS" - # dir = "Z:\\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-10-26" - dir = "Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM" + dir = "Z:\\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-10-26" + # dir = "Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM" os.chdir(dir) path = "C:\\Data\\HT-CD-MS\\20230906 JDS BSA SEC f22 10x dilute STORI high flow 1_20230906171314_2023-09-07-01-43-26.dmt" path = "C:\\Data\\HT-CD-MS\\20230906 JDS BSA SEC f22 10x dilute STORI high flow 1_20230906171314.raw" @@ -854,17 +1016,19 @@ def get_mass_eic(self, massrange, ht=False): path = "2023103 JDS BSA inj5s cyc2m bit3 zp5 rep1.raw" path = "20231026 JDS BSA cyc2s inj5s bit3 zp1 rep1.raw" # path = '20231102_ADH_BSA SEC 15k cyc2m inj3s bit3 zp3 no1.raw' - path = '20231202 JDS 0o1uMBgal 0o4uMgroEL shortCol 300ul_m 6_1spl bit5 zp7 inj4s cyc1m AICoff IIT100.RAW' - path = "Z:\\Group Share\\Skippy\Projects\HT\\2023-10-13 BSA ADH\\20231013 BSA STORI inj2s cyc2m 5bit_2023-10-13-05-06-03.dmt" - path = "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt" + # path = '20231202 JDS 0o1uMBgal 0o4uMgroEL shortCol 300ul_m 6_1spl bit5 zp7 inj4s cyc1m AICoff IIT100.RAW' + # path = "Z:\\Group Share\\Skippy\Projects\HT\\2023-10-13 BSA ADH\\20231013 BSA STORI inj2s cyc2m 5bit_2023-10-13-05-06-03.dmt" + # path = "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt" # path = "20231202 JDS 0o1uMBgal 0o4uMgroEL shortCol 300ul_m 6_1spl bit3 zp4 inj5s cyc1m AICoff IIT200.RAW" + path = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\GDH\\01302024_GDH_stepsize3_repeat15_5to500_2024-02-06-04-44-16.dmt" eng.open_file(path) - eng.process_data_scans() - # eng.eic_ht([8500, 10500]) + # eng.process_data_scans() + # xicdata = eng.get_eic([8500, 10500]) + eng.eic_ht([8500, 10500], [1, 100]) eng.tic_ht() # np.savetxt("tic.txt", np.transpose([eng.fulltime, eng.fulltic])) - ac = eng.get_cycle_time(eng.fulltic) + # ac = eng.get_cycle_time(eng.fulltic) # import matplotlib.pyplot as plt # plt.plot(ac) @@ -872,7 +1036,7 @@ def get_mass_eic(self, massrange, ht=False): # exit() plt.plot(eng.fulltime, eng.fulltic / np.amax(eng.fulltic)) plt.plot(eng.fulltime[eng.padindex:], np.roll(eng.htkernel, 0)) - plt.plot(eng.fulltime, eng.htoutput / np.amax(eng.htoutput) - 1) + plt.plot(eng.fulltime[1:], eng.htoutput / np.amax(eng.htoutput) - 1) plt.show() exit() diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index 1145ad75..32f3d3e9 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -134,8 +134,6 @@ def setup_main_panel(self): self.plot5 = PlottingWindow.Plot2d(tab5, figsize=figsize) self.plot6 = PlottingWindow.Plot1d(tab6, figsize=figsize) - - miscwindows.setup_tab_box(tab1, self.plot1) miscwindows.setup_tab_box(tab2, self.plot2) miscwindows.setup_tab_box(tab3, self.plot3) @@ -149,6 +147,11 @@ def setup_main_panel(self): miscwindows.setup_tab_box(tabtic, self.plottic) plotwindow.AddPage(tabtic, "Chromatogram") + decontictab = wx.Panel(plotwindow) + self.plotdecontic = PlottingWindow.Plot1d(decontictab, figsize=figsize) + miscwindows.setup_tab_box(decontictab, self.plotdecontic) + plotwindow.AddPage(decontictab, "Deconvolved Chromatogram") + plotwindow.AddPage(tab1, "CD-MS Data") plotwindow.AddPage(tab3, "Charge Distribution") plotwindow.AddPage(tab2, "Mass Distribution") @@ -191,7 +194,10 @@ def setup_main_panel(self): i = 0 if self.htmode: self.plottic = PlottingWindow.Plot1d(plotwindow, figsize=figsize) - sizerplot.Add(self.plottic, (0, 0), span=(1, 2), flag=wx.EXPAND) + sizerplot.Add(self.plottic, (0, 0), span=(1, 1), flag=wx.EXPAND) + + self.plotdecontic = PlottingWindow.Plot1d(plotwindow, figsize=figsize) + sizerplot.Add(self.plotdecontic, (0, 1), span=(1, 1), flag=wx.EXPAND) i += 1 sizerplot.Add(self.plot1, (i, 0), span=(1, 1), flag=wx.EXPAND) @@ -229,10 +235,13 @@ def setup_main_panel(self): self.plotnames = ["Figure1", "Figure2", "Figure5", "Figure4", "Figure3", "Figure6"] if self.htmode: - self.plots = [self.plottic] + self.plots + [self.plot7, self.plot8, self.plot9, self.plot10] - self.plotnames = ["Chromatogram"] + self.plotnames + ["Figure7", "Figure8", "Figure9", "Figure10"] - self.plottic._axes = [0.07, 0.11, 0.9, 0.8] + self.plots = [self.plottic, self.plotdecontic] + self.plots + [self.plot7, self.plot8, self.plot9, + self.plot10] + self.plotnames = ["Chromatogram", "Decon Chromatogram"] + self.plotnames + ["Figure7", "Figure8", "Figure9", + "Figure10"] + # self.plottic._axes = [0.07, 0.11, 0.9, 0.8] self.Bind(self.plottic.EVT_SCANS_SELECTED, self.pres.on_select_time_range, self.plottic) + self.Bind(self.plotdecontic.EVT_SCANS_SELECTED, self.pres.on_select_time_range_decon, self.plotdecontic) # ........................... # diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 22df3e17..6f766307 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -268,66 +268,118 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): self.foldpanels.AddFoldPanelWindow(foldpanel1b, panel1b, fpb.FPB_ALIGN_WIDTH) if htmode: - foldpanelht = self.foldpanels.AddFoldPanel(caption="HT Parameters", collapsed=False, cbstyle=styleht) - panelht = wx.Panel(foldpanelht, -1) + self.foldpaneldm = self.foldpanels.AddFoldPanel(caption="Demultiplexing Controls", collapsed=False, + cbstyle=styleht) + paneldm = wx.Panel(self.foldpaneldm, -1) sizercontrolht1 = wx.GridBagSizer(wx.VERTICAL) i = 0 # Button for Run TIC HT - self.runticht = wx.Button(panelht, -1, "Run TIC Hadamard Transform") + self.runticht = wx.Button(paneldm, -1, "Run TIC Demultiplex") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_tic_ht, self.runticht) sizercontrolht1.Add(self.runticht, (i, 0), span=(1, 2), flag=wx.EXPAND) - self.runticht.SetToolTip(wx.ToolTip("HT Demultiplexing of TIC")) + self.runticht.SetToolTip(wx.ToolTip("Demultiplexing of TIC")) i += 1 # Button for Run EIC HT - self.runeicht = wx.Button(panelht, -1, "Run EIC Hadamard Transform") + self.runeicht = wx.Button(paneldm, -1, "Run EIC Demultiplex") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_eic_ht, self.runeicht) sizercontrolht1.Add(self.runeicht, (i, 0), span=(1, 2), flag=wx.EXPAND) - self.runeicht.SetToolTip(wx.ToolTip("HT Demultiplexing of each EIC selected. " + self.runeicht.SetToolTip(wx.ToolTip("Demultiplexing of each EIC selected. " "To select, zoom on a region of the 2D plot and right click.")) i += 1 - self.runallht = wx.Button(panelht, -1, "Run All Hadamard Transform") + self.runallht = wx.Button(paneldm, -1, "Run All Demultiplex") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_run_all_ht, self.runallht) sizercontrolht1.Add(self.runallht, (i, 0), span=(1, 2), flag=wx.EXPAND) - self.runallht.SetToolTip(wx.ToolTip("Run HT on all data points. Used to create crazy cubes.")) + self.runallht.SetToolTip(wx.ToolTip("Run demultiplex on all data points. Used to create crazy cubes.")) i += 1 # Button to Mass Transformation - self.masstransform = wx.Button(panelht, -1, "Run All Mass Transform") + self.masstransform = wx.Button(paneldm, -1, "Run All Mass Transform") self.parent.Bind(wx.EVT_BUTTON, self.pres.run_all_mass_transform, self.masstransform) sizercontrolht1.Add(self.masstransform, (i, 0), span=(1, 2), flag=wx.EXPAND) self.masstransform.SetToolTip(wx.ToolTip("Run Mass Transformation on all data points. " "Click button above first to do HT.")) i += 1 - # Text Control for Kernel Smoothing - self.ctlkernelsmooth = wx.TextCtrl(panelht, value="", size=size1) - sizercontrolht1.Add(self.ctlkernelsmooth, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelht, label="Kernel Smoothing: "), (i, 0), + # Control for decmultiplex choice + self.ctlmultiplexmode = wx.Choice(paneldm, -1, choices=self.config.demultiplexchoices) + self.ctlmultiplexmode.SetSelection(0) + sizercontrolht1.Add(self.ctlmultiplexmode, (i, 1), span=(1, 2)) + sizercontrolht1.Add(wx.StaticText(paneldm, label="Demultiplex Mode: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + self.ctlmultiplexmode.SetToolTip(wx.ToolTip("Choose the demultiplexing mode.")) + self.ctlmultiplexmode.Bind(wx.EVT_CHOICE, self.update_demultiplex_mode) i += 1 - # Text Control for HTtimeshift - self.ctlhttimeshift = wx.TextCtrl(panelht, value="", size=size1) - sizercontrolht1.Add(self.ctlhttimeshift, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelht, label="Time Shift: "), (i, 0), + # Text Control for Kernel Smoothing + self.ctlkernelsmooth = wx.TextCtrl(paneldm, value="", size=size1) + sizercontrolht1.Add(self.ctlkernelsmooth, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(paneldm, label="Smoothing: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 - # Text Control for Analysis Time - self.ctlanalysistime = wx.TextCtrl(panelht, value="", size=size1) - sizercontrolht1.Add(self.ctlanalysistime, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelht, label="Analysis Time: "), (i, 0), + # Text control for timepad + self.ctltimepad = wx.TextCtrl(paneldm, value="", size=size1) + sizercontrolht1.Add(self.ctltimepad, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(paneldm, label="Time Padding: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 # Drop down menu for X-axis - self.ctlxaxis = wx.Choice(panelht, -1, choices=["Time", "Scans"]) + self.ctlxaxis = wx.Choice(paneldm, -1, choices=["Time", "Scans"]) self.ctlxaxis.SetSelection(0) sizercontrolht1.Add(self.ctlxaxis, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelht, label="X-Axis: "), (i, 0), + sizercontrolht1.Add(wx.StaticText(paneldm, label="X-Axis: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Button to make 2d time vs. charge plot + self.maketvsc = wx.Button(paneldm, -1, "Time-Z Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_charge_time_2dplot, self.maketvsc) + sizercontrolht1.Add(self.maketvsc, (i, 0), span=(1, 1), flag=wx.EXPAND) + + # Button to make 2d time vs. mz plot + self.maketvsm = wx.Button(paneldm, -1, "Time-m/z Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mz_time_2dplot, self.maketvsm) + sizercontrolht1.Add(self.maketvsm, (i, 1), span=(1, 1), flag=wx.EXPAND) + i += 1 + + # Button to make 2d time vs. mass plot + self.makevtm = wx.Button(paneldm, -1, "Make 2D Time vs. Mass Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_time_2dplot, self.makevtm) + sizercontrolht1.Add(self.makevtm, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + # Button for Make m/z Cube plots + self.makemzcube = wx.Button(paneldm, -1, "m/z Cube") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_cube_plot, self.makemzcube) + sizercontrolht1.Add(self.makemzcube, (i, 0), span=(1, 1), flag=wx.EXPAND) + + # Button for make mass cube plot + self.makemasscube = wx.Button(paneldm, -1, "Mass Cube") + self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_cube_plot, self.makemasscube) + sizercontrolht1.Add(self.makemasscube, (i, 1), span=(1, 1), flag=wx.EXPAND) + i += 1 + + paneldm.SetSizer(sizercontrolht1) + sizercontrolht1.Fit(paneldm) + + self.foldpanels.AddFoldPanelWindow(self.foldpaneldm, paneldm, fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(self.foldpaneldm, wx.StaticText(self.foldpaneldm, -1, " "), + fpb.FPB_ALIGN_WIDTH) + + self.foldpanelht = self.foldpanels.AddFoldPanel(caption="HT Parameters", collapsed=False, + cbstyle=styleht) + panelht = wx.Panel(self.foldpanelht, -1) + sizercontrolht1 = wx.GridBagSizer(wx.VERTICAL) + i = 0 + + # Text Control for Analysis Time + self.ctlanalysistime = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlanalysistime, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Analysis Time: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 @@ -339,10 +391,10 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 - # Text control for timepad - self.ctltimepad = wx.TextCtrl(panelht, value="", size=size1) - sizercontrolht1.Add(self.ctltimepad, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelht, label="Time Padding: "), (i, 0), + # Text Control for HTtimeshift + self.ctlhttimeshift = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlhttimeshift, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Time Shift: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 @@ -353,45 +405,38 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 - # Button to make 2d time vs. charge plot - self.maketvsc = wx.Button(panelht, -1, "Make 2D Time vs. Charge Plot") - self.parent.Bind(wx.EVT_BUTTON, self.pres.make_charge_time_2dplot, self.maketvsc) - sizercontrolht1.Add(self.maketvsc, (i, 0), span=(1, 2), flag=wx.EXPAND) - i += 1 + panelht.SetSizer(sizercontrolht1) + sizercontrolht1.Fit(panelht) - # Button to make 2d time vs. mz plot - self.maketvsm = wx.Button(panelht, -1, "Make 2D Time vs. m/z Plot") - self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mz_time_2dplot, self.maketvsm) - sizercontrolht1.Add(self.maketvsm, (i, 0), span=(1, 2), flag=wx.EXPAND) - i += 1 + self.foldpanels.AddFoldPanelWindow(self.foldpanelht, panelht, fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(self.foldpanelht, wx.StaticText(self.foldpanelht, -1, " "), + fpb.FPB_ALIGN_WIDTH) - # Button to make 2d time vs. mass plot - self.makevtm = wx.Button(panelht, -1, "Make 2D Time vs. Mass Plot") - self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_time_2dplot, self.makevtm) - sizercontrolht1.Add(self.makevtm, (i, 0), span=(1, 2), flag=wx.EXPAND) - i += 1 + self.foldpanelft = self.foldpanels.AddFoldPanel(caption="FT Parameters", collapsed=True, + cbstyle=styleht) + panelft = wx.Panel(self.foldpanelft, -1) + sizercontrolht1 = wx.GridBagSizer(wx.VERTICAL) + i = 0 - # Button for Make m/z Cube plots - self.makemzcube = wx.Button(panelht, -1, "Make m/z Cube Plots") - self.parent.Bind(wx.EVT_BUTTON, self.pres.make_cube_plot, self.makemzcube) - sizercontrolht1.Add(self.makemzcube, (i, 0), span=(1, 2), flag=wx.EXPAND) + # Text Control for FTstart and FTend + self.ctlftstart = wx.TextCtrl(panelft, value="", size=size1) + sizercontrolht1.Add(self.ctlftstart, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelft, label="FT Start: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) i += 1 - # Button for make mass cube plot - self.makemasscube = wx.Button(panelht, -1, "Make Mass Cube Plots") - self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_cube_plot, self.makemasscube) - sizercontrolht1.Add(self.makemasscube, (i, 0), span=(1, 2), flag=wx.EXPAND) + self.ctlftend = wx.TextCtrl(panelft, value="", size=size1) + sizercontrolht1.Add(self.ctlftend, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelft, label="FT End: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) i += 1 - # self.autosetct = wx.Button(panelht, -1, "Auto Set Cycle Index") - # self.parent.Bind(wx.EVT_BUTTON, self.pres.on_auto_set_ct, self.autosetct) - # sizercontrolht1.Add(self.autosetct, (i, 0), span=(1, 2), flag=wx.EXPAND) - # i += 1 + panelft.SetSizer(sizercontrolht1) + sizercontrolht1.Fit(panelft) - panelht.SetSizer(sizercontrolht1) - sizercontrolht1.Fit(panelht) - self.foldpanels.AddFoldPanelWindow(foldpanelht, panelht, fpb.FPB_ALIGN_WIDTH) - self.foldpanels.AddFoldPanelWindow(foldpanelht, wx.StaticText(foldpanelht, -1, " "), fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(self.foldpanelft, panelft, fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(self.foldpanelft, wx.StaticText(self.foldpanelft, -1, " "), + fpb.FPB_ALIGN_WIDTH) # Panel for unidec Parameters foldpanel2 = self.foldpanels.AddFoldPanel(caption="UniDec Parameters", collapsed=False, cbstyle=style2) @@ -803,6 +848,9 @@ def import_config_to_gui(self): self.ctlanalysistime.SetValue(str(self.config.HTanalysistime)) self.ctlcycleindex.SetValue(str(self.config.HTcycleindex)) self.ctlxaxis.SetStringSelection(self.config.HTxaxis) + self.ctlmultiplexmode.SetStringSelection(self.config.demultiplexmode) + self.ctlftstart.SetValue(str(self.config.FTstart)) + self.ctlftend.SetValue(str(self.config.FTend)) if self.config.adductmass < 0: self.ctlnegmode.SetValue(1) @@ -849,8 +897,11 @@ def import_config_to_gui(self): except Exception as e: print("Error updating quick controls", e) # print("5: %.2gs" % (time.perf_counter() - tstart)) + self.Thaw() + self.update_demultiplex_mode() + def export_gui_to_config(self, e=None): """ Exports parameters from the GUI to the config object. @@ -912,6 +963,9 @@ def export_gui_to_config(self, e=None): self.config.HTtimepad = ud.string_to_value(self.ctltimepad.GetValue()) self.config.HTanalysistime = ud.string_to_value(self.ctlanalysistime.GetValue()) self.config.HTcycleindex = ud.string_to_value(self.ctlcycleindex.GetValue()) + self.config.demultiplexmode = self.ctlmultiplexmode.GetStringSelection() + self.config.FTstart = ud.string_to_value(self.ctlftstart.GetValue()) + self.config.FTend = ud.string_to_value(self.ctlftend.GetValue()) self.config.numit = ud.string_to_int(self.ctlnumit.GetValue()) @@ -1211,7 +1265,7 @@ def on_expand_yellow(self, e=None): num = self.foldpanels.GetCount() for i in range(0, num): fp = self.foldpanels.GetFoldPanel(i) - if i in [2, 3, 4]: + if i in [5, 6, 7]: self.foldpanels.Expand(fp) else: self.foldpanels.Collapse(fp) @@ -1221,7 +1275,7 @@ def on_expand_red(self, e=None): num = self.foldpanels.GetCount() for i in range(0, num): fp = self.foldpanels.GetFoldPanel(i) - if i in [5, 6]: + if i in [8, 9]: self.foldpanels.Expand(fp) else: self.foldpanels.Collapse(fp) @@ -1231,7 +1285,7 @@ def on_expand_main(self, e=None): num = self.foldpanels.GetCount() for i in range(0, num): fp = self.foldpanels.GetFoldPanel(i) - if i in [0, 2, 3, 5]: + if i in [0, 5, 6, 8]: self.foldpanels.Expand(fp) else: self.foldpanels.Collapse(fp) @@ -1250,6 +1304,17 @@ def bind_changes(self, e=None): ''' + def update_demultiplex_mode(self, e=None): + demultiplexmode = self.ctlmultiplexmode.GetStringSelection() + if "HT" in demultiplexmode: + self.foldpanels.Collapse(self.foldpanelft) + self.foldpanels.Expand(self.foldpanelht) + elif "FT" in demultiplexmode: + self.foldpanels.Collapse(self.foldpanelht) + self.foldpanels.Expand(self.foldpanelft) + else: + print("Unknown demultiplex mode:", demultiplexmode) + def update_quick_controls(self, e=None): if self.update_flag: diff --git a/unidec/modules/thermo_reader/ThermoImporter.py b/unidec/modules/thermo_reader/ThermoImporter.py index 09d6ed91..978ce554 100644 --- a/unidec/modules/thermo_reader/ThermoImporter.py +++ b/unidec/modules/thermo_reader/ThermoImporter.py @@ -210,7 +210,7 @@ def get_polarity(self, scan=1): if __name__ == "__main__": test = u"C:\Python\\UniDec3\TestSpectra\\test.RAW" #test = "Z:\\Group Share\\Levi\\MS DATA\\vt_ESI data\\DMPG LL37 ramps\\18to1\\20210707_LB_DMPG3_LL37_18to1_RAMP_16_37_3.RAW" - #test = "Z:\Group Share\Group\Archive\James Keener\AqpZ mix lipid ND\\20190226_JEK_AQPZ_E3T0_PGPC_GC_NEG.RAW" + #test = "Z:\Group Share\Group\Archive\JamesKeener Keener\AqpZ mix lipid ND\\20190226_JEK_AQPZ_E3T0_PGPC_GC_NEG.RAW" tstart = time.perf_counter() d = ThermoDataImporter(test) diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 4b00e139..d93324dc 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -278,6 +278,10 @@ def __init__(self): self.HTcycleindex = -1 self.HTxaxis = "Time" self.CDScanCompress = 3 + self.demultiplexmode = "HT" + self.demultiplexchoices = ["HT", "FT", "aFT"] + self.FTstart = 5 + self.FTend = 1000 self.doubledec = False self.kernel = "" @@ -444,7 +448,6 @@ def default_decon_params(self): self.HTcycleindex = -1 self.HTxaxis = "Time" - self.doubledec = False self.kernel = "" @@ -570,7 +573,9 @@ def config_export(self, name): f.write("HTcycleindex " + str(self.HTcycleindex) + "\n") f.write("HTtimepad " + str(self.HTtimepad) + "\n") f.write("htbit " + str(self.htbit) + "\n") - + f.write("FTstart " + str(self.FTstart) + "\n") + f.write("FTend " + str(self.FTend) + "\n") + f.write("demultiplexmode " + str(self.demultiplexmode) + "\n") f.write("csig " + str(self.csig) + "\n") f.write("smoothdt " + str(self.smoothdt) + "\n") @@ -705,6 +710,13 @@ def config_import(self, name): self.htbit = ud.string_to_value(line.split()[1]) if line.startswith("CDScanCompress"): self.CDScanCompress = ud.string_to_value(line.split()[1]) + if line.startswith("demultiplexmode"): + self.demultiplexmode = line.split()[1] + if line.startswith("FTstart"): + self.FTstart = ud.string_to_value(line.split()[1]) + if line.startswith("FTend"): + self.FTend = ud.string_to_value(line.split()[1]) + if line.startswith("zzsig"): self.zzsig = ud.string_to_value(line.split()[1]) if line.startswith("psig"): @@ -1583,7 +1595,8 @@ class ChromatogramContainer: def __init__(self): self.chromatograms = [] - def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrange=None, zrange=None, ht=False): + def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrange=None, zrange=None, ht=False, + mode="DM"): chrom = Chromatogram() chrom.chromdat = data chrom.sum = np.sum(data[:, 1]) @@ -1595,7 +1608,7 @@ def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrang if zrange is not None: label += " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) if ht: - label += " HT" + label += " " + mode chrom.label = label @@ -1612,7 +1625,7 @@ def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrang if zrange is not None: chrom.zrange = zrange - self.ht = ht + chrom.ht = ht # If label already exists, replace it if label in [x.label for x in self.chromatograms]: From 44b25673387fe6a48cba3169dbe35bcfdb3fb4fb Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Mon, 19 Feb 2024 11:12:26 -0700 Subject: [PATCH 12/35] Improvements to FT-IM-CD-MS --- unidec/UniChromCD.py | 250 ++++++++++++------ unidec/modules/HTEng.py | 287 ++++++++++++++++----- unidec/modules/IM_functions.py | 30 ++- unidec/modules/PlotBase.py | 6 + unidec/modules/gui_elements/CDWindow.py | 9 +- unidec/modules/gui_elements/CD_controls.py | 114 +++++++- unidec/modules/unidecstructure.py | 34 ++- 7 files changed, 574 insertions(+), 156 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 29a5c984..9f549ae3 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -27,13 +27,14 @@ def init(self, *args, **kwargs): self.eng = HTEng.UniDecCDHT() self.cc = ChromatogramContainer() self.showht = False + self.showccs = False self.cycol = ud.create_color_cycle("bgcmy") self.view = CDWindow.CDMainwindow(self, "UniChrom for CD-MS Data", self.eng.config, htmode=True) self.comparedata = None - pub.subscribe(self.on_select_mzz_region, 'mzlimits') + # pub.subscribe(self.on_select_mzz_region, 'mzlimits') pub.subscribe(self.on_smash, 'smash') self.eng.config.recentfile = self.eng.config.recentfileCD @@ -69,6 +70,7 @@ def init(self, *args, **kwargs): # self.run_all_ht() # self.run_all_mass_transform() # self.make_mass_cube_plot() + self.on_run_ccs() def on_open_file(self, filename, directory, path=None, refresh=False): """ @@ -101,7 +103,7 @@ def on_pick_peaks(self, e=None): """ self.export_config(self.eng.config.confname) self.pick_peaks() - if self.eng.fullmstack is not None: + if self.eng.mstack is not None: for p in self.eng.pks.peaks: mass = p.mass try: @@ -114,19 +116,51 @@ def on_pick_peaks(self, e=None): lb = -float(self.eng.config.peakwindow) massrange = [mass + lb, mass + ub] + self.add_mass_eic(massrange, color=p.color, plot=False, label="Mass EIC: " + str(mass)) - chromdat = self.eng.get_mass_eic(massrange) - label = "Mass EIC: " + str(mass) - self.cc.add_chromatogram(chromdat, color=p.color, label=label, ht=False) - if self.eng.fullmstack_ht is not None: - htdata = self.eng.get_mass_eic(massrange, ht=True) - self.cc.add_chromatogram(htdata, color=p.color, label=label + " " + self.eng.config.demultiplexmode, - ht=True) self.plot_chromatograms() - def on_select_mzz_region(self): + def add_mass_eic(self, massrange, zrange=None, color=None, label=None, plot=True, ccs=False): + if self.eng.mstack is None: + self.run_all_mass_transform() + if zrange is None: + zrange = [self.eng.config.startz, self.eng.config.endz] + + # Round z appropriately + zrange = np.round(zrange) + zrange = np.array(zrange, dtype=int) + # Round mass range to nearest massbins + massrange = np.array(massrange) + massrange = np.round(massrange / self.eng.config.massbins) * self.eng.config.massbins + + # Get chromatogram for raw + chromdat = self.eng.get_mass_eic(massrange, zrange) + if label is None: + label = "Mass: " + str(massrange[0]) + "-" + str(massrange[1]) + " z: " + str(zrange[0]) + "-" + str( + zrange[1]) + + # Get chromatogram for HT + if self.eng.mstack_ht is not None: + htdata = self.eng.get_mass_eic(massrange, zrange, ht=True) + else: + htdata = None + + # Get chromatogram for HT + if self.eng.ccsstack_ht is not None: + ccsdata = self.eng.get_ccs_eic(massrange, zrange) + else: + ccsdata = None + + self.cc.add_chromatogram(chromdat, decondat=htdata, ccsdat=ccsdata, color=color, label=label, + massrange=massrange, zrange=zrange, mode=self.eng.config.demultiplexmode) + + if plot: + self.plot_chromatograms() + + def on_select_mzz_region(self, e=None): """ Trigged by right click of m/z vs z 2D plot. Triggers EIC creation and HT if already setup. + :param e: Unused event :return: None """ self.export_config(self.eng.config.confname) @@ -136,11 +170,25 @@ def on_select_mzz_region(self): print("New limits:", xlimits, ylimits) self.view.plot1.reset_zoom() color = next(self.cycol) - if self.showht: + if self.showht or self.showccs: self.run_eic_ht(xlimits, ylimits, color=color) else: self.add_eic(xlimits, ylimits, color=color) + def on_select_massz_range(self, e=None): + self.export_config(self.eng.config.confname) + if not wx.GetKeyState(wx.WXK_CONTROL): + xlimits, ylimits = self.view.plot5.get_limits() + xlimits = np.array(xlimits) * self.view.plot5.kdnorm + print("TEST", xlimits) + self.view.plot5.reset_zoom() + color = next(self.cycol) + + if self.eng.ccsstack_ht is not None: + self.add_mass_eic(xlimits, ylimits, color=color, plot=True, ccs=True) + else: + self.add_mass_eic(xlimits, ylimits, color=color, plot=True) + def make_tic_plot(self): """ Make the TIC Plot @@ -148,11 +196,9 @@ def make_tic_plot(self): """ data = self.eng.get_tic(normalize=self.eng.config.datanorm) self.cc.add_chromatogram(data, color="black", label="TIC") - - self.showht = False self.plot_chromatograms(save=False) - def add_eic(self, mzrange, zrange, color='b'): + def add_eic(self, mzrange, zrange, color='b', plot=True): """ Add an EIC to the list of chromatograms. Plot the chromatograms. :param mzrange: M/z range @@ -162,7 +208,15 @@ def add_eic(self, mzrange, zrange, color='b'): """ eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) + if plot: + self.plot_chromatograms() + def on_run_eic_ccs(self, e=None): + self.export_config(self.eng.config.confname) + for c in self.cc.chromatograms: + if "TIC" not in c.label: + eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) + c.ccsdat = eicdata self.plot_chromatograms() def on_run_tic_ht(self, e=None): @@ -181,10 +235,12 @@ def on_run_eic_ht(self, e=None): :return: None """ self.export_config(self.eng.config.confname) + self.showccs=False for c in self.cc.chromatograms: if "TIC" not in c.label: - if not c.ht: - self.run_eic_ht(c.mzrange, c.zrange, color=c.color, add_eic=False, plot=False) + htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, normalize=self.eng.config.datanorm) + c.chromdat = eicdata + c.decondat = htdata self.plot_chromatograms() def run_tic_ht(self): @@ -192,34 +248,37 @@ def run_tic_ht(self): Runs HT on the TIC. Clears the chromatogram list. Runs HT on TIC. Adds both to list. Plots chromatograms. :return: None """ - self.cc.clear() + self.eng.get_tic(normalize=self.eng.config.datanorm) htdata = self.eng.tic_ht(normalize=self.eng.config.datanorm) ticdata = np.transpose(np.vstack((self.eng.fulltime, self.eng.fulltic))) - self.cc.add_chromatogram(ticdata, color="black", label="TIC") - self.cc.add_chromatogram(htdata, color="red", label="TIC " + self.eng.config.demultiplexmode, ht=True) + self.cc.add_chromatogram(ticdata, decondat=htdata, color="black", label="TIC", + mode=self.eng.config.demultiplexmode) self.showht = True + self.showccs = False self.plot_chromatograms(save=False) - def run_eic_ht(self, mzrange, zrange, color='b', add_eic=True, plot=True): + def run_eic_ht(self, mzrange, zrange, color='b'): """ Function to generate and HT an EIC. Adds both to list. Plots chromatograms. :param mzrange: M/z range :param zrange: Charge range :param color: Color of plot. Default blue. - :param add_eic: Add the EIC to the list. Default True. - :param plot: Plot the chromatograms. Default True. :return: None """ htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) - if add_eic: - self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) - self.cc.add_chromatogram(htdata, color=color, zrange=zrange, mzrange=mzrange, ht=True, + if self.eng.ccsstack_ht is not None: + ccsdata = self.eng.get_ccs_eic(mzrange=mzrange, zrange=zrange, normalize=self.eng.config.datanorm) + print("Got CCS Data") + else: + ccsdata = None + print("No CCS Data") + self.cc.add_chromatogram(eicdata, decondat=htdata, ccsdat=ccsdata, color=color, zrange=zrange, mzrange=mzrange, mode=self.eng.config.demultiplexmode) - if plot: - self.plot_chromatograms() + self.showht = True + self.plot_chromatograms() def plot_chromatograms(self, e=None, save=True): """ @@ -233,42 +292,71 @@ def plot_chromatograms(self, e=None, save=True): self.view.plottic.clear_plot() self.view.plotdecontic.clear_plot() self.view.chrompanel.list.populate(self.cc) + for c in self.cc.chromatograms: if c.ignore: continue - if self.eng.config.HTxaxis == "Scans": - xdat = np.arange(0, len(c.chromdat)) - # xdat = self.eng.fullscans - xlab = "Scan Number" - else: - xdat = c.chromdat[:, 0] - xlab = "Time" - - if c.ht: - plot = self.view.plotdecontic - else: - plot = self.view.plottic - - if not plot.flag: - plot.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, - zoomout=True, label=c.label, xlabel=xlab, - color=c.color, nopaint=True) - else: - plot.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, - newlabel=c.label) - xlimits = c.mzrange ylimits = c.zrange if xlimits[0] != -1 and ylimits[0] != -1 and xlimits[1] != -1 and ylimits[1] != -1: self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], edgecolor=c.color, facecolor=c.color, nopaint=True) + + if not ud.isempty(c.chromdat): + if self.eng.config.HTxaxis == "Scans": + xdat = np.arange(0, len(c.chromdat)) + xlab = "Scan Number" + else: + xdat = c.chromdat[:, 0] + xlab = "Time" + + if not self.view.plottic.flag: + self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, + zoomout=True, label=c.label, xlabel=xlab, + color=c.color, nopaint=True) + else: + self.view.plottic.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) + + if c.decondat is not None and not self.showccs: + if self.eng.config.HTxaxis == "Scans": + xdat = np.arange(0, len(c.decondat)) + xlab = "Scan Number" + else: + xdat = c.decondat[:, 0] + xlab = "Time" + + if not self.view.plotdecontic.flag: + self.view.plotdecontic.plotrefreshtop(xdat, c.decondat[:, 1], config=self.eng.config, + zoomout=True, label=c.label, xlabel=xlab, + color=c.color, nopaint=True) + else: + self.view.plotdecontic.plotadd(xdat, c.decondat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) + + if c.ccsdat is not None and self.showccs: + if self.eng.config.HTxaxis == "Scans": + xdat = np.arange(0, len(c.ccsdat)) + xlab = "Scan Number" + else: + xdat = c.ccsdat[:, 0] + xlab = "CCS (\u212B\u00B2)" + + if not self.view.plotdecontic.flag: + self.view.plotdecontic.plotrefreshtop(xdat, c.ccsdat[:, 1], config=self.eng.config, + zoomout=True, label=c.label, xlabel=xlab, + color=c.color, nopaint=True) + else: + self.view.plotdecontic.plotadd(xdat, c.ccsdat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) + if self.view.plotdecontic.flag: self.view.plotdecontic.add_legend() - self.view.plotdecontic.repaint() + self.view.plotdecontic.repaint(resetzoom=True) if self.view.plottic.flag: self.view.plottic.add_legend() - self.view.plottic.repaint() + self.view.plottic.repaint(resetzoom=True) self.view.plot1.repaint() if save: self.save_chroms() @@ -318,7 +406,7 @@ def load_chroms(self, fname=None, array=None): ht = ht.lower() in ['true', '1', 't', 'y', 'yes', 'yeah'] - self.cc.add_chromatogram(chromdat, color=color, label=label, ht=bool(ht), zrange=zrange, mzrange=mzrange) + self.cc.add_chromatogram(chromdat, color=color, label=label, zrange=zrange, mzrange=mzrange) self.plot_chromatograms() @@ -401,15 +489,14 @@ def run_all_ht(self): """ self.export_config(self.eng.config.confname) self.eng.process_data_scans() - ticdat = self.eng.run_all_ht() - - self.cc.add_chromatogram(ticdat, color="gold", label="TIC*_" + self.eng.config.demultiplexmode, ht=True) + decondat, procdat = self.eng.run_all_ht() + procdat = np.transpose(np.vstack((self.eng.fulltime, procdat))) - # tic2 = np.sum(self.eng.fullhstack, axis=(1, 2)) - # ticdat2 = np.transpose(np.vstack((self.eng.fulltime, tic2))) - # self.cc.add_chromatogram(ticdat2, color="red", label="TIC*") + self.cc.add_chromatogram(procdat, decondat=decondat, color="gold", + label="DM_TIC", mode=self.eng.config.demultiplexmode) self.showht = True + self.showccs = False self.plot_chromatograms() def run_all_mass_transform(self, e=None): @@ -419,10 +506,27 @@ def run_all_mass_transform(self, e=None): :return: None """ self.eng.transform_stacks() - self.cc.add_chromatogram(self.eng.mass_tic, color="grey", label="Mass TIC") - if self.eng.fullmstack_ht is not None: - self.cc.add_chromatogram(self.eng.mass_tic_ht, color="goldenrod", - label="Mass TIC " + self.eng.config.demultiplexmode, ht=True) + + if self.eng.mstack_ht is not None: + decondat = self.eng.mass_tic_ht + else: + decondat = None + self.cc.add_chromatogram(self.eng.mass_tic, decondat=decondat, color="goldenrod", label="Mass_TIC", + mode=self.eng.config.demultiplexmode, massmode=True) + self.plot_chromatograms() + + def on_run_ccs(self, e=None): + """ + Run CCS calculation. Runs run_ccs. + :param e: Unused event + :return: None + """ + self.showccs=True + self.export_config(self.eng.config.confname) + ccs_tic = self.eng.ccs_transform_stacks() + for c in self.cc.chromatograms: + if "TIC" in c.label: + c.ccsdat = ccs_tic self.plot_chromatograms() def select_ht_range(self, range=None, raw=False): @@ -436,13 +540,13 @@ def select_ht_range(self, range=None, raw=False): self.view.SetStatusText("Raw # Ions: " + str(np.sum(self.eng.harray)), number=2) else: self.eng.select_ht_range(range=range) - self.view.SetStatusText(self.eng.config.demultiplexmode + " # Ions: " + str(np.sum(self.eng.harray)), number=2) + self.view.SetStatusText(self.eng.config.demultiplexmode + " # Ions: " + str(np.sum(self.eng.harray)), + number=2) self.makeplot1() self.makeplot2() self.makeplot3() self.makeplot4() - def make_charge_time_2dplot(self, e=None): """ Creates a 2D plot of charge vs time @@ -483,8 +587,8 @@ def make_2d_plot(self, e=None, face=1): print("Need to create fullhstack") self.eng.process_data_scans() - if self.eng.fullmstack is None and face == 3: - print("Need to create fullmstack") + if self.eng.mstack is None and face == 3: + print("Need to create mstack") self.run_all_mass_transform() if self.eng.config.HTxaxis == "Scans": @@ -507,7 +611,7 @@ def make_2d_plot(self, e=None, face=1): ylab = "m/z (Th)" discrete = True elif face == 3: - grid = self.eng.fullmstack + grid = self.eng.mstack print(np.shape(grid)) y = self.eng.massaxis ylab = "Mass (Da)" @@ -527,10 +631,10 @@ def make_2d_plot(self, e=None, face=1): elif face == 2: grid = np.sum(fullhstack_ht, axis=1) elif face == 3: - if self.eng.fullmstack_ht is None: + if self.eng.mstack_ht is None: return else: - grid = np.clip(self.eng.fullmstack_ht, 0, np.amax(self.eng.fullmstack_ht)) + grid = np.clip(self.eng.mstack_ht, 0, np.amax(self.eng.mstack_ht)) else: return self.view.plot8.contourplot(xvals=xdat2, yvals=y, zgrid=grid, @@ -555,7 +659,7 @@ def make_cube_plot(self, e=None, mass=False): """ self.export_config(self.eng.config.confname) - if mass and self.eng.fullmstack is None: + if mass and self.eng.mstack is None: self.run_all_mass_transform() if self.eng.config.HTxaxis == "Scans": @@ -577,7 +681,7 @@ def make_cube_plot(self, e=None, mass=False): face3 = np.reshape(self.eng.data.massgrid, (len(self.eng.massaxis), len(self.eng.ztab))) ydat = self.eng.massaxis ylab = "Mass (Da)" - face2 = self.eng.fullmstack.transpose() + face2 = self.eng.mstack.transpose() else: # mz by z ydat = self.eng.mzaxis[1:] @@ -598,16 +702,16 @@ def make_cube_plot(self, e=None, mass=False): try: if self.eng.fullhstack_ht is None: return - if mass and self.eng.fullmstack_ht is None: + if mass and self.eng.mstack_ht is None: return fullhstack_ht = np.clip(self.eng.fullhstack_ht, 0, np.amax(self.eng.fullhstack_ht)) starttime = time.perf_counter() face1 = np.sum(fullhstack_ht, axis=2).transpose() if mass: - fullmstack_ht = np.clip(self.eng.fullmstack_ht, 0, np.amax(self.eng.fullmstack_ht)) + mstack_ht = np.clip(self.eng.mstack_ht, 0, np.amax(self.eng.mstack_ht)) face3 = np.reshape(self.eng.data.massgrid, (len(self.eng.massaxis), len(self.eng.ztab))) - face2 = fullmstack_ht.transpose() + face2 = mstack_ht.transpose() else: face3 = np.sum(self.eng.fullhstack_ht, axis=0).transpose() face2 = np.sum(self.eng.fullhstack_ht, axis=1).transpose() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index b1204936..4fbb9452 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -1,10 +1,12 @@ import time +import numpy as np import scipy.ndimage from unidec.modules.fitting import * from unidec.modules.CDEng import * from unidec.modules.ChromEng import * +from unidec.modules.IM_functions import calc_linear_ccs import unidec.tools as ud # from modules.unidecstructure import DataContainer @@ -12,6 +14,9 @@ import scipy.fft as fft import math +import warnings +warnings.filterwarnings("error") + # import fast_histogram # import pyfftw.interfaces.numpy_fft as fft @@ -38,6 +43,7 @@ def __init__(self, *args, **kwargs): self.indexrange = [0, 0] self.decontime = [] self.deconscans = [] + self.dtype = float # Index Values self.padindex = 0 @@ -99,8 +105,8 @@ def parse_file_name(self, path): if len(frangeindex) > 0: frangeindex = frangeindex[0] # get the next two fields - ftstart = parts[frangeindex+1] - ftend = parts[frangeindex+2] + ftstart = parts[frangeindex + 1] + ftend = parts[frangeindex + 2] # Convert to float and set to field in config try: @@ -236,8 +242,10 @@ def setup_demultiplex(self, mode=None, **kwargs): mode = self.config.demultiplexmode if mode == "HT": + self.dtype = float self.setup_ht(**kwargs) elif mode == "FT" or mode == "aFT": + self.dtype= complex self.setup_ft(**kwargs) def htdecon(self, data, **kwargs): @@ -303,40 +311,50 @@ def setup_ft(self, FTstart=None, FTend=None, nzp=None): if nzp > 0: pad_len = int(2 ** math.ceil(math.log2(int(len(x)))) * nzp) time_step = (x[1] - x[0]) - x = np.linspace(x[0], x[-1] + time_step * (pad_len-x_len), pad_len) - #self.fulltime = x - #self.fullscans = np.arange(len(self.fulltime)) + x = np.linspace(x[0], x[-1] + time_step * (pad_len - x_len), pad_len) + # self.fulltime = x + # self.fullscans = np.arange(len(self.fulltime)) freq = np.fft.rfftfreq(len(x), d=(x[1] - x[0]) * 60) # Create time axis sweepRate = (FTend - FTstart) / (sweep_time * 60) - self.decontime = freq / sweepRate + self.decontime = freq / sweepRate * 1000. self.deconscans = self.fullscans[:len(self.decontime)] - def ftdecon(self, data, flattenTIC=True, apodize=True, aFT=False, normalize=False, nzp=None): + def ftdecon(self, data, flatten=None, apodize=None, aFT=False, normalize=False, nzp=None, keepcomplex=False): """ Perform Fourier Transform Deconvolution :param data: 1D data array of y-data only - :param flattenTIC: Whether to flatten the TIC before demultiplexing to remove low frequency components + :param flatten: Whether to flatten the TIC before demultiplexing to remove low frequency components :param apodize: Whether to apodize the data with a Hanning window :param aFT: Whether to use Absorption FT mode + :param normalize: Whether to normalize the output to the maximum value + :param nzp: Zero padding factor :return: 1D array of demultiplexed data. Same length as input. """ if np.amax(data) == 0: - return data[:len(self.decontime)], data + if keepcomplex: + return np.zeros(len(self.decontime), dtype=self.dtype), data + else: + return data[:len(self.decontime)], data + + if flatten is not None: + self.config.FTflatten = flatten + if apodize is not None: + self.config.FTapodize = apodize y = data if self.config.HTksmooth > 0: y = scipy.signal.savgol_filter(y, int(np.round(float(self.config.HTksmooth))), 3) - if flattenTIC: + if self.config.FTflatten: # fit trendline to y and subtract to eliminate low frequency components ytrnd = scipy.signal.savgol_filter(y, 15, 3) y = y - ytrnd - if apodize: + if self.config.FTapodize == 1: # create hanning window hanning = np.hanning(len(y) * 2) y = y * hanning[len(y):] @@ -345,7 +363,7 @@ def ftdecon(self, data, flattenTIC=True, apodize=True, aFT=False, normalize=Fals nzp = self.config.HTtimepad original_len = len(y) - if nzp > 0 and apodize: + if nzp > 0 and self.config.FTapodize: pad_len = int(2 ** math.ceil(math.log2(int(len(y)))) * nzp) z = np.zeros(pad_len) z[:len(y)] = y @@ -358,9 +376,11 @@ def ftdecon(self, data, flattenTIC=True, apodize=True, aFT=False, normalize=Fals maxindex = np.argmax(np.abs(Y[5:])) + 5 phase = np.angle(Y[maxindex]) Y = Y * np.exp(-1j * phase) - Y = np.real(Y) + if not keepcomplex: + Y = np.real(Y) else: - Y = np.abs(Y) + if not keepcomplex: + Y = np.abs(Y) if normalize: Y /= np.amax(Y) @@ -556,14 +576,18 @@ def __init__(self, *args, **kwargs): self.topharray = None self.mzaxis = None self.zaxis = None + self.ccsaxis = None self.X = None self.Y = None self.mass = None self.fullhstack_ht = None + self.mstack = None + self.mstack_ht = None self.fullmstack = None self.fullmstack_ht = None self.mass_tic = None self.mass_tic_ht = None + self.ccsstack_ht = None self.mz = None self.ztab = None @@ -586,10 +610,13 @@ def clear_arrays(self, massonly=False): if not massonly: self.fullhstack = None self.fullhstack_ht = None + self.mstack = None + self.mstack_ht = None self.fullmstack = None self.fullmstack_ht = None self.mass_tic = None self.mass_tic_ht = None + self.ccsstack_ht = None def prep_time_domain(self): """ @@ -774,7 +801,10 @@ def tic_ht(self, **kwargs): self.get_tic(**kwargs) self.setup_demultiplex() self.htoutput, self.fulltic = self.run_demultiplex(self.fulltic, **kwargs) - + if self.config.datanorm: + self.htoutput /= np.amax(self.htoutput) + self.fulltic /= np.amax(self.fulltic) + print(np.shape(self.decontime), np.shape(self.htoutput)) return np.transpose([self.decontime, self.htoutput]) def get_eic(self, mzrange, zrange, **kwargs): @@ -821,41 +851,15 @@ def run_all_ht(self): if self.fullhstack is None: self.process_data_scans() self.clear_arrays(massonly=True) + # Setup HT self.setup_demultiplex() - ''' - # self.fullhstack_ht = self.decon_3d(self.fullhstack) - starttime = time.perf_counter() - # Prep the ranges - self.indexrange = [self.padindex - self.shiftindex, len(self.fullhstack) - self.shiftindex] - substack = self.fullhstack[self.indexrange[0]:self.indexrange[1]] - substack_ht = np.empty_like(substack) - sumgrid = np.sum(substack, axis=0) - # Run the HT on each track in the stack - for i, x in enumerate(self.mz): - for j, y in enumerate(self.ztab): - trace = substack[:, j, i] - tracesum = sumgrid[j, i] - if tracesum <= self.config.intthresh or tracesum <= 2: - htoutput = np.zeros_like(trace) - else: - htoutput = self.htdecon_speedy(trace) - substack_ht[:, j, i] = htoutput - - # Shift the output back to the original time - if self.padindex > 0: - # add zeros back on the front and roll to the correct index - zeroarray = np.zeros((self.padindex, substack_ht.shape[1], substack_ht.shape[2])) - self.fullhstack_ht = np.roll(np.concatenate((zeroarray, substack_ht)), self.rollindex, axis=0) - else: - self.fullhstack_ht = substack_ht - - print("Full HT Demultiplexing Done:", time.perf_counter() - starttime) - starttime = time.perf_counter()''' # Run the HT on each track in the stack - self.fullhstack_ht = np.empty((len(self.decontime), self.topharray.shape[0], self.topharray.shape[1])) + self.fullhstack_ht = np.empty((len(self.decontime), self.topharray.shape[0], self.topharray.shape[1]) + , dtype=self.dtype) + processed_tic = np.zeros_like(self.fulltime) for i in range(len(self.mz)): for j in range(len(self.ztab)): trace = self.fullhstack[:, j, i] @@ -863,21 +867,22 @@ def run_all_ht(self): if tracesum <= self.config.intthresh: htoutput = np.zeros_like(self.decontime) else: - htoutput, trace = self.run_demultiplex(trace) + htoutput, trace = self.run_demultiplex(trace, keepcomplex=True) + processed_tic += trace self.fullhstack_ht[:, j, i] = htoutput # Clip all values below 1e-6 to zero # self.fullhstack_ht[np.abs(self.fullhstack_ht) < 1e-6] = 0 tic = np.sum(self.fullhstack_ht, axis=(1, 2)) + tic = np.abs(tic) ticdat = np.transpose(np.vstack((self.decontime, tic))) if self.config.datanorm == 1: - norm = np.amax(ticdat[:, 1]) - ticdat[:, 1] /= norm - #self.fullhstack_ht /= norm + ticdat[:, 1] /= np.amax(ticdat[:, 1]) + processed_tic /= np.amax(processed_tic) print("Full HT Demultiplexing Done:", time.perf_counter() - starttime) - return ticdat + return ticdat, processed_tic def select_ht_range(self, range=None): """ @@ -890,7 +895,7 @@ def select_ht_range(self, range=None): b1 = self.decontime >= range[0] b2 = self.decontime <= range[1] b = np.logical_and(b1, b2) - substack_ht = self.fullhstack_ht[b] + substack_ht = np.abs(self.fullhstack_ht[b]) self.harray = np.sum(substack_ht, axis=0) self.harray = np.clip(self.harray, 0, np.amax(self.harray)) self.harray_process() @@ -915,14 +920,15 @@ def select_raw_range(self, range=None): self.harray_process() return self.harray - def transform_array(self, array): + def transform_array(self, array, dtype=float): """ Transforms a histogram stack from m/z to mass :param array: Histogram stack. Shape is time vs. charge vs. m/z. :return: Transformed array """ mlen = len(self.massaxis) - outarray = np.zeros((len(array), mlen)) + zlen = len(self.ztab) + outarray = np.zeros((len(array), mlen, zlen), dtype=dtype) for j in range(len(self.ztab)): indexes = np.array([ud.nearest(self.massaxis, m) for m in self.mass[j]]) @@ -932,9 +938,9 @@ def transform_array(self, array): # Sum together everything with the same index subarray = np.transpose([np.sum(subarray[:, indexes == u], axis=1) for u in uindexes]) # Add to the output array - outarray[:, uindexes] += subarray + outarray[:, uindexes, j] += subarray - return outarray + return np.sum(outarray, axis=2), outarray def transform_stacks(self): """ @@ -946,6 +952,8 @@ def transform_stacks(self): if self.fullhstack is None: self.process_data_scans(transform=True) + self.ccsstack_ht = None + if self.config.poolflag == 1: print("Transforming Stacks by Interpolation") else: @@ -953,42 +961,51 @@ def transform_stacks(self): starttime = time.perf_counter() mlen = len(self.massaxis) - self.fullmstack = self.transform_array(self.fullhstack) + self.mstack, self.fullmstack = self.transform_array(self.fullhstack) - self.mass_tic = np.transpose([self.fulltime, np.sum(self.fullmstack, axis=1)]) + self.mass_tic = np.transpose([self.fulltime, np.sum(self.mstack, axis=1)]) if self.config.datanorm == 1: norm = np.amax(self.mass_tic[:, 1]) self.mass_tic[:, 1] /= norm - self.fullmstack /= norm + self.mstack /= norm print("Full Mass 1 Transform Done:", time.perf_counter() - starttime) if self.fullhstack_ht is None: return - self.fullmstack_ht = self.transform_array(self.fullhstack_ht) + self.mstack_ht, self.fullmstack_ht = self.transform_array(self.fullhstack_ht, dtype=self.dtype) - self.mass_tic_ht = np.transpose([self.decontime, np.sum(self.fullmstack_ht, axis=1)]) + self.mass_tic_ht = np.transpose([self.decontime, np.abs(np.sum(self.mstack_ht, axis=1))]) if self.config.datanorm == 1: norm = np.amax(self.mass_tic_ht[:, 1]) self.mass_tic_ht[:, 1] /= norm - #self.fullmstack_ht /= norm + # self.mstack_ht /= norm print("Full Mass 2 Transform Done:", time.perf_counter() - starttime) - def get_mass_eic(self, massrange, ht=False): + def get_mass_eic(self, massrange, zrange=None, ht=False): """ Get the EIC for a mass range after transforming the data to mass. Can be either HT or not. :param massrange: Mass range + :param zrange: Charge range. Default None sets to full range. :param ht: Boolean whether to use HT or not :return: 2D array of EIC (time, intensity) """ - # Filter fullmstack + if zrange is None: + zrange = [np.amin(self.ztab), np.amax(self.ztab)] + + # Filter mstack b1 = self.massaxis >= massrange[0] b2 = self.massaxis <= massrange[1] b = np.logical_and(b1, b2) + # Filter ztab + b3 = self.ztab >= zrange[0] + b4 = self.ztab <= zrange[1] + b5 = np.logical_and(b4, b3) + if ht: array = self.fullmstack_ht xvals = self.decontime @@ -996,10 +1013,131 @@ def get_mass_eic(self, massrange, ht=False): array = self.fullmstack xvals = self.fulltime - substack = array[:, b] - mass_eic = np.sum(substack, axis=1) + substack = array[:, b, :][:, :, b5] + mass_eic = np.sum(substack, axis=(1, 2)) return np.transpose([xvals, mass_eic]) + def get_ccs_eic(self, massrange=None, zrange=None, mzrange=None, normalize=False): + """ + Get the EIC for a mass range after transforming the data to CCS. + :param massrange: Mass range + :param zrange: Charge range. Default None sets to full range. + :param mzrange: m/z range. Default None sets to full range. + :param normalize: Whether to normalize the output to the maximum. + :return: 2D array of EIC (time, intensity) + """ + array = self.ccsstack_ht + print("Ranges:", massrange, zrange, mzrange) + if zrange is not None: + # Filter ztab + b3 = self.ztab >= zrange[0] + b4 = self.ztab <= zrange[1] + b5 = np.logical_and(b4, b3) + # Project b5 into array of same shape + b5 = np.array([[b5 for i in range(len(self.massaxis))] for j in range(len(self.ccsaxis))]) + else: + b5 = np.ones_like(array, dtype=bool) + + if mzrange is not None: + dt3d, mass3d, ztab3d = np.meshgrid(self.ccsaxis, self.massaxis, self.ztab, indexing='ij') + mz3d = (mass3d + ztab3d * self.config.adductmass) / ztab3d + b6 = mz3d >= mzrange[0] + b7 = mz3d <= mzrange[1] + b8 = np.logical_and(b7, b6) + else: + b8 = np.ones_like(array, dtype=bool) + + if massrange is not None: + # Filter mstack + b1 = self.massaxis >= massrange[0] + b2 = self.massaxis <= massrange[1] + b = np.logical_and(b1, b2) + b = np.transpose([[b for i in range(len(self.ccsaxis))] for j in range(len(self.ztab))], axes=[1, 2, 0]) + else: + b = np.ones_like(array, dtype=bool) + + # Merge B8, B5 and B into one large 3D array + b8 = b8 * b5 * b + + substack = array * b8 + + ccs_eic = np.abs(np.sum(substack, axis=(1, 2))) + + if self.config.FTsmooth > 0: + ccs_eic = scipy.signal.savgol_filter(ccs_eic, int(self.config.FTsmooth), 3) + + if normalize: + ccs_eic /= np.amax(ccs_eic) + + return np.transpose([self.ccsaxis, ccs_eic]) + + def transform_dt_ccs_array(self, array, keepcomplex=True): + """ + Transforms a histogram stack from drift time to CCS + :param array: Histogram Mass stack. Shape is time vs. mass vs. charge. + :return: Transformed array + """ + # Create dt, mass, and z arrays in 3D + dt3d, mass3d, ztab3d = np.meshgrid(self.decontime, self.massaxis, self.ztab, indexing='ij') + # Parallel calc all CCS values into new 3d array + ccs3d = calc_linear_ccs(mass3d, ztab3d, dt3d, self.config) + # Create new CCS axis + minccs = np.amin(ccs3d) + maxccs = np.amax(ccs3d) + if self.config.ccsbins == -1: + binsize = (maxccs-minccs)/len(self.decontime) + else: + binsize = self.config.ccsbins + ccsaxis = np.arange(minccs, maxccs, binsize) + # Create bins shifted by half a bin + ccsbins = np.arange(minccs - binsize / 2., maxccs + binsize / 2., binsize) + self.ccsaxis = ccsaxis + + mlen = len(self.massaxis) + zlen = len(self.ztab) + outarray = np.zeros((len(ccsaxis), mlen, zlen), dtype=self.dtype) + if not keepcomplex: + array = np.abs(array) + # Loop through array and paste back onto the new CCS axis + for i in range(mlen): + for j in range(zlen): + #indexes = np.array([ud.nearest(self.ccsaxis, c) for c in ccs3d[:, i, j]]) + #outlist = np.zeros_like(self.ccsaxis, dtype=self.dtype) + #outlist[indexes] += array[:, i, j] + + outlist = np.histogram(ccs3d[:, i, j], bins=ccsbins, weights=array[:, i, j])[0] + outarray[:, i, j] = outlist + # interpolate the existing arrray onto the new ccs axis + #outarray[:, i, j] = np.interp(self.ccsaxis, ccs3d[:, i, j], array[:, i, j]) + + + + return outarray + + def ccs_transform_stacks(self): + """ + Transform the histogram stacks from drift time to CCS. + :return: None + """ + if self.fullmstack_ht is None: + self.run_all_ht() + self.transform_stacks() + starttime = time.perf_counter() + self.ccsstack_ht = self.transform_dt_ccs_array(self.fullmstack_ht) + print("Full CCS Transform Done:", time.perf_counter() - starttime) + + ccs_tic = np.sum(self.ccsstack_ht, axis=(1, 2)) + ccs_tic = np.abs(ccs_tic) + b1 = ccs_tic > 0 + ccs_tic = ccs_tic[b1] + self.ccsaxis = self.ccsaxis[b1] + self.ccsstack_ht = self.ccsstack_ht[b1] + if self.config.FTsmooth > 0: + ccs_tic = scipy.signal.savgol_filter(ccs_tic, int(self.config.FTsmooth), 3) + + ccs_tic = np.transpose(np.vstack((self.ccsaxis, ccs_tic))) + return ccs_tic + if __name__ == '__main__': @@ -1021,19 +1159,30 @@ def get_mass_eic(self, massrange, ht=False): # path = "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt" # path = "20231202 JDS 0o1uMBgal 0o4uMgroEL shortCol 300ul_m 6_1spl bit3 zp4 inj5s cyc1m AICoff IIT200.RAW" path = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\GDH\\01302024_GDH_stepsize3_repeat15_5to500_2024-02-06-04-44-16.dmt" + pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" + eng.open_file(pathft) + eng.process_data() + eng.process_data_scans() + eng.run_all_ht() + eng.transform_stacks() + eng.config.ccsbins = 1 + eng.ccs_transform_stacks() - eng.open_file(path) + ccs_tic = np.sum(eng.ccsstack_ht, axis=(1, 2)) + plt.plot(eng.ccsaxis, ccs_tic) + plt.show() + # eng.get_mass_eic([8500, 10500], [1, 100]) # eng.process_data_scans() # xicdata = eng.get_eic([8500, 10500]) - eng.eic_ht([8500, 10500], [1, 100]) - eng.tic_ht() + # eng.eic_ht([8500, 10500], [1, 100]) + # eng.tic_ht() # np.savetxt("tic.txt", np.transpose([eng.fulltime, eng.fulltic])) # ac = eng.get_cycle_time(eng.fulltic) # import matplotlib.pyplot as plt # plt.plot(ac) # plt.show() - # exit() + exit() plt.plot(eng.fulltime, eng.fulltic / np.amax(eng.fulltic)) plt.plot(eng.fulltime[eng.padindex:], np.roll(eng.htkernel, 0)) plt.plot(eng.fulltime[1:], eng.htoutput / np.amax(eng.htoutput) - 1) diff --git a/unidec/modules/IM_functions.py b/unidec/modules/IM_functions.py index aed7cf19..cd22e97b 100644 --- a/unidec/modules/IM_functions.py +++ b/unidec/modules/IM_functions.py @@ -248,7 +248,7 @@ def calc_linear_dt(mass, z, ccs, config): tempo = 273.15 temp = config.temp + tempo ccsconst = (np.sqrt(18.0 * np.pi) / 16.0) * (e / np.sqrt(kb * temp)) / n * ( - config.volt / pow(config.driftlength, 2.0)) * (po / config.pressure) * (temp / tempo) * 1E20 + config.volt / pow(config.driftlength, 2.0)) * (po / config.pressure) * (temp / tempo) * 1E20 ac = 1.6605389E-27 rmass = ac * (mass * config.gasmass) / (mass + config.gasmass) td = ccs / (ccsconst * z * np.sqrt(1 / rmass)) @@ -256,6 +256,34 @@ def calc_linear_dt(mass, z, ccs, config): return dt +def calc_linear_ccs(mass, z, dt, config): + if config.ccsconstant == 0: + calc_linear_ccsconst(config) + ccsconst = config.ccsconstant + hmass = config.gasmass + to = config.to + ac = 1.6605389E-27 + factor = 0.001 + rmass = ac * (mass * hmass) / (mass + hmass) + td = (dt - to) * factor + ccs = z * td * (1 / rmass) ** 0.5 * ccsconst + return ccs + + +def calc_linear_ccsconst(config): + e = 1.60217657E-19 + kb = 1.3806488E-23 + n = 2.6867774E25 + po = 760 + tempo = 273.15 + temp = config.temp + pressure = config.pressure + driftlength = config.driftlength + ccsconst = (np.sqrt(18.0 * np.pi) / 16.0) * (e / np.sqrt(kb * temp)) / n * ( + config.volt / pow(driftlength, 2.0)) * (po / pressure) * (temp / tempo) * 1E20 + config.ccsconstant = ccsconst + + def calc_native_ccs(mass, gasmass): """ Predict native CCS value for a particular mass in a given mass diff --git a/unidec/modules/PlotBase.py b/unidec/modules/PlotBase.py index f43e38f8..4b0076c4 100644 --- a/unidec/modules/PlotBase.py +++ b/unidec/modules/PlotBase.py @@ -406,6 +406,12 @@ def set_tickcolor(self): else: self.tickcolor = u"white" + def get_limits(self): + xlimits = self.subplot1.get_xlim() + ylimits = self.subplot1.get_ylim() + print("New limits:", xlimits, ylimits) + return np.array(xlimits), np.array(ylimits) + def write_data(self, path): if self.data is not None: print("Saving Data to", path) diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index 32f3d3e9..abb90b64 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -131,7 +131,7 @@ def setup_main_panel(self): self.plot2 = PlottingWindow.Plot1d(tab2, integrate=1, figsize=figsize) self.plot3 = PlottingWindow.Plot1d(tab3, figsize=figsize) self.plot4 = PlottingWindow.Plot1d(tab4, figsize=figsize) - self.plot5 = PlottingWindow.Plot2d(tab5, figsize=figsize) + self.plot5 = PlottingWindow.Plot2d(tab5, smash=2, figsize=figsize) self.plot6 = PlottingWindow.Plot1d(tab6, figsize=figsize) miscwindows.setup_tab_box(tab1, self.plot1) @@ -188,7 +188,7 @@ def setup_main_panel(self): self.plot2 = PlottingWindow.Plot1d(plotwindow, integrate=1, figsize=figsize) self.plot3 = PlottingWindow.Plot1d(plotwindow, figsize=figsize) self.plot4 = PlottingWindow.Plot1d(plotwindow, figsize=figsize) - self.plot5 = PlottingWindow.Plot2d(plotwindow, figsize=figsize) + self.plot5 = PlottingWindow.Plot2d(plotwindow, smash=2, figsize=figsize) self.plot6 = PlottingWindow.Plot1d(plotwindow, figsize=figsize) i = 0 @@ -228,8 +228,9 @@ def setup_main_panel(self): plotwindow.Bind(wx.EVT_SET_FOCUS, self.onFocus) self.plotpanel = plotwindow - # if self.htmode: - # self.Bind(self.plot1.EVT_MZLIMITS, self.extract, self.plot1) + if self.htmode: + self.Bind(self.plot5.EVT_MZLIMITS, self.pres.on_select_massz_range, self.plot5) + self.Bind(self.plot1.EVT_MZLIMITS, self.pres.on_select_mzz_region, self.plot1) self.plots = [self.plot1, self.plot2, self.plot5, self.plot4, self.plot3, self.plot6] self.plotnames = ["Figure1", "Figure2", "Figure5", "Figure4", "Figure3", "Figure6"] diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 6f766307..451ab6c9 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -347,9 +347,13 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): i += 1 # Button to make 2d time vs. mass plot - self.makevtm = wx.Button(paneldm, -1, "Make 2D Time vs. Mass Plot") + self.makevtm = wx.Button(paneldm, -1, "Time-Mass Plot") self.parent.Bind(wx.EVT_BUTTON, self.pres.make_mass_time_2dplot, self.makevtm) - sizercontrolht1.Add(self.makevtm, (i, 0), span=(1, 2), flag=wx.EXPAND) + sizercontrolht1.Add(self.makevtm, (i, 0), span=(1, 1), flag=wx.EXPAND) + + self.plot5button2 = wx.Button(paneldm, -1, "Mass-Charge Plot") + self.parent.Bind(wx.EVT_BUTTON, self.pres.makeplot5, self.plot5button2) + sizercontrolht1.Add(self.plot5button2, (i, 1), span=(1, 1), flag=wx.EXPAND) i += 1 # Button for Make m/z Cube plots @@ -421,13 +425,31 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): # Text Control for FTstart and FTend self.ctlftstart = wx.TextCtrl(panelft, value="", size=size1) sizercontrolht1.Add(self.ctlftstart, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelft, label="FT Start: "), (i, 0), + sizercontrolht1.Add(wx.StaticText(panelft, label="FT Start (Hz): "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 self.ctlftend = wx.TextCtrl(panelft, value="", size=size1) sizercontrolht1.Add(self.ctlftend, (i, 1), span=(1, 1)) - sizercontrolht1.Add(wx.StaticText(panelft, label="FT End: "), (i, 0), + sizercontrolht1.Add(wx.StaticText(panelft, label="FT End (Hz): "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Create Two checkboxes for FTflatten and FTapodize + self.ctlftflatten = wx.CheckBox(panelft, label="Flatten") + sizercontrolht1.Add(self.ctlftflatten, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + self.ctlftflatten.SetValue(self.config.FTflatten) + self.ctlftflatten.SetToolTip(wx.ToolTip("Flatten the FT data")) + self.ctlftapodize = wx.CheckBox(panelft, label="Apodize") + sizercontrolht1.Add(self.ctlftapodize, (i, 1), flag=wx.ALIGN_CENTER_VERTICAL) + self.ctlftapodize.SetValue(self.config.FTapodize) + self.ctlftapodize.SetToolTip(wx.ToolTip("Apodize the FT data")) + i += 1 + + # Add text input for post smoothing + self.ctlftsmooth = wx.TextCtrl(panelft, value="", size=size1) + sizercontrolht1.Add(self.ctlftsmooth, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelft, label="Post Smoothing: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 @@ -438,6 +460,68 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): self.foldpanels.AddFoldPanelWindow(self.foldpanelft, wx.StaticText(self.foldpanelft, -1, " "), fpb.FPB_ALIGN_WIDTH) + foldpanel1c = self.foldpanels.AddFoldPanel(caption="Ion Mobility Parameters", collapsed=False, + cbstyle=style1c) + panel1c = wx.Panel(foldpanel1c, -1) + gbox1c = wx.GridBagSizer(wx.VERTICAL) + i = 0 + + # Create Run CCS Calc button + self.runccsbutton = wx.Button(panel1c, -1, "Run All DT to CCS") + self.Bind(wx.EVT_BUTTON, self.pres.on_run_ccs, self.runccsbutton) + gbox1c.Add(self.runccsbutton, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + # Button to run EIC CCS + self.runeicccs = wx.Button(panel1c, -1, "Run EIC CCS") + self.Bind(wx.EVT_BUTTON, self.pres.on_run_eic_ccs, self.runeicccs) + gbox1c.Add(self.runeicccs, (i, 0), span=(1, 2), flag=wx.EXPAND) + i += 1 + + self.ctlvolt = wx.TextCtrl(panel1c, value="", size=size1) + gbox1c.Add(self.ctlvolt, (i, 1), span=(1, 1)) + gbox1c.Add(wx.StaticText(panel1c, label="Voltage (V): "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + self.ctlpressure = wx.TextCtrl(panel1c, value='', size=size1) + gbox1c.Add(self.ctlpressure, (i, 1), span=(1, 1)) + gbox1c.Add(wx.StaticText(panel1c, label="Pressure (Torr): "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + self.ctltemp = wx.TextCtrl(panel1c, value='', size=size1) + gbox1c.Add(self.ctltemp, (i, 1), span=(1, 1)) + gbox1c.Add(wx.StaticText(panel1c, label="Temperature (\u00B0C): "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + self.ctlgasmass = wx.TextCtrl(panel1c, value='', size=size1) + gbox1c.Add(self.ctlgasmass, (i, 1), span=(1, 1)) + gbox1c.Add(wx.StaticText(panel1c, label="Gas Mass (Da): "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + self.ctlto = wx.TextCtrl(panel1c, value='', size=size1) + gbox1c.Add(self.ctlto, (i, 1), span=(1, 1)) + gbox1c.Add(wx.StaticText(panel1c, label="Dead Time (t\u2080 in ms): "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + self.ctldriftlength = wx.TextCtrl(panel1c, value='', size=size1) + gbox1c.Add(self.ctldriftlength, (i, 1), span=(1, 1)) + gbox1c.Add(wx.StaticText(panel1c, label="Drift Cell Length (m)"), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + self.ctlccsbins = wx.TextCtrl(panel1c, value="", size=size1) + gbox1c.Add(self.ctlccsbins, (i, 1), span=(1, 2)) + gbox1c.Add(wx.StaticText(panel1c, label="Sample CCS Every (\u212B\u00B2): "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + + panel1c.SetSizer(gbox1c) + gbox1c.Fit(panel1c) + + self.foldpanels.AddFoldPanelWindow(foldpanel1c, panel1c, fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(foldpanel1c, wx.StaticText(foldpanel1c, -1, " "), fpb.FPB_ALIGN_WIDTH) + # Panel for unidec Parameters foldpanel2 = self.foldpanels.AddFoldPanel(caption="UniDec Parameters", collapsed=False, cbstyle=style2) panel2 = wx.Panel(foldpanel2, -1) @@ -851,6 +935,17 @@ def import_config_to_gui(self): self.ctlmultiplexmode.SetStringSelection(self.config.demultiplexmode) self.ctlftstart.SetValue(str(self.config.FTstart)) self.ctlftend.SetValue(str(self.config.FTend)) + self.ctlftflatten.SetValue(self.config.FTflatten) + self.ctlftapodize.SetValue(self.config.FTapodize) + self.ctlftsmooth.SetValue(str(self.config.FTsmooth)) + + self.ctlvolt.SetValue(str(self.config.volt)) + self.ctltemp.SetValue(str(self.config.temp)) + self.ctlpressure.SetValue(str(self.config.pressure)) + self.ctlgasmass.SetValue(str(self.config.gasmass)) + self.ctlto.SetValue(str(self.config.to)) + self.ctldriftlength.SetValue(str(self.config.driftlength)) + self.ctlccsbins.SetValue(str(self.config.ccsbins)) if self.config.adductmass < 0: self.ctlnegmode.SetValue(1) @@ -966,6 +1061,17 @@ def export_gui_to_config(self, e=None): self.config.demultiplexmode = self.ctlmultiplexmode.GetStringSelection() self.config.FTstart = ud.string_to_value(self.ctlftstart.GetValue()) self.config.FTend = ud.string_to_value(self.ctlftend.GetValue()) + self.config.FTflatten = self.ctlftflatten.GetValue() + self.config.FTapodize = self.ctlftapodize.GetValue() + self.config.FTsmooth = ud.string_to_value(self.ctlftsmooth.GetValue()) + + self.config.volt = ud.string_to_value(self.ctlvolt.GetValue()) + self.config.temp = ud.string_to_value(self.ctltemp.GetValue()) + self.config.pressure = ud.string_to_value(self.ctlpressure.GetValue()) + self.config.gasmass = ud.string_to_value(self.ctlgasmass.GetValue()) + self.config.to = ud.string_to_value(self.ctlto.GetValue()) + self.config.driftlength = ud.string_to_value(self.ctldriftlength.GetValue()) + self.config.ccsbins = ud.string_to_value(self.ctlccsbins.GetValue()) self.config.numit = ud.string_to_int(self.ctlnumit.GetValue()) diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index d93324dc..c6e83aeb 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -157,6 +157,7 @@ def __init__(self): self.edc = 1.57 self.gasmass = 4.002602 self.twaveflag = 0 + self.ccsconstant = 0 # Misc self.batchflag = 0 @@ -282,6 +283,9 @@ def __init__(self): self.demultiplexchoices = ["HT", "FT", "aFT"] self.FTstart = 5 self.FTend = 1000 + self.FTflatten = True + self.FTapodize = 1 + self.FTsmooth = 0 self.doubledec = False self.kernel = "" @@ -575,7 +579,10 @@ def config_export(self, name): f.write("htbit " + str(self.htbit) + "\n") f.write("FTstart " + str(self.FTstart) + "\n") f.write("FTend " + str(self.FTend) + "\n") + f.write("FTflatten " + str(int(self.FTflatten)) + "\n") + f.write("FTapodize " + str(int(self.FTapodize)) + "\n") f.write("demultiplexmode " + str(self.demultiplexmode) + "\n") + f.write("FTsmooth " + str(self.FTsmooth) + "\n") f.write("csig " + str(self.csig) + "\n") f.write("smoothdt " + str(self.smoothdt) + "\n") @@ -716,6 +723,12 @@ def config_import(self, name): self.FTstart = ud.string_to_value(line.split()[1]) if line.startswith("FTend"): self.FTend = ud.string_to_value(line.split()[1]) + if line.startswith("FTflatten"): + self.FTflatten = ud.string_to_int(line.split()[1]) + if line.startswith("FTapodize"): + self.FTapodize = ud.string_to_int(line.split()[1]) + if line.startswith("FTsmooth"): + self.FTsmooth = ud.string_to_value(line.split()[1]) if line.startswith("zzsig"): self.zzsig = ud.string_to_value(line.split()[1]) @@ -1576,6 +1589,8 @@ def read_hdf5(self, file_name): class Chromatogram: def __init__(self): self.chromdat = np.array([]) + self.decondat = np.array([]) + self.ccsdat = np.array([]) self.label = "" self.color = "#000000" self.index = 0 @@ -1584,6 +1599,8 @@ def __init__(self): self.sum = -1 self.ignore = 0 self.ht = False + self.massrange = [-1, -1] + self.massmode = False def to_row(self): out = [self.label, str(self.color), str(self.index), str(self.mzrange[0]), str(self.mzrange[1]), @@ -1595,10 +1612,12 @@ class ChromatogramContainer: def __init__(self): self.chromatograms = [] - def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrange=None, zrange=None, ht=False, - mode="DM"): + def add_chromatogram(self, data, decondat=None, ccsdat=None, label=None, color="#000000", index=None, mzrange=None, + zrange=None, massrange=None, massmode=False, mode="DM"): chrom = Chromatogram() chrom.chromdat = data + chrom.decondat = decondat + chrom.ccsdat = ccsdat chrom.sum = np.sum(data[:, 1]) if label is None: @@ -1607,8 +1626,6 @@ def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrang label += "m/z: " + str(round(mzrange[0])) + "-" + str(round(mzrange[1])) if zrange is not None: label += " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) - if ht: - label += " " + mode chrom.label = label @@ -1621,11 +1638,16 @@ def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrang if mzrange is not None: chrom.mzrange = mzrange + chrom.massmode = False if zrange is not None: chrom.zrange = zrange - chrom.ht = ht + if massrange is not None: + chrom.massrange = massrange + chrom.massmode = True + + chrom.massmode = massmode # If label already exists, replace it if label in [x.label for x in self.chromatograms]: @@ -1640,6 +1662,8 @@ def add_chromatogram(self, data, label=None, color="#000000", index=None, mzrang else: self.chromatograms.append(chrom) + return chrom + def clear(self): self.chromatograms = [] From 6365b7e9d3abde0ca3d26043b34653d5f9e9b4d6 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 22 Feb 2024 12:28:33 -0700 Subject: [PATCH 13/35] Bug fixes and CCS EIC change to weighted average mode. Added Show Legends check box --- unidec/UniChromCD.py | 40 +++++++++++----- unidec/modules/HTEng.py | 56 ++++++++++++++++++---- unidec/modules/gui_elements/CD_controls.py | 12 ++++- unidec/modules/unidecstructure.py | 1 + 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 9f549ae3..55049321 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -213,9 +213,16 @@ def add_eic(self, mzrange, zrange, color='b', plot=True): def on_run_eic_ccs(self, e=None): self.export_config(self.eng.config.confname) + self.showccs = True for c in self.cc.chromatograms: if "TIC" not in c.label: - eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) + if c.decondat is None: + htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, normalize=self.eng.config.datanorm) + c.decondat = htdata + c.chromdat = eicdata + # eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) + eicdata = self.eng.convert_trace_to_ccs(c.decondat, mzrange=c.mzrange, zrange=c.zrange, + normalize=self.eng.config.datanorm) c.ccsdat = eicdata self.plot_chromatograms() @@ -235,7 +242,7 @@ def on_run_eic_ht(self, e=None): :return: None """ self.export_config(self.eng.config.confname) - self.showccs=False + self.showccs = False for c in self.cc.chromatograms: if "TIC" not in c.label: htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, normalize=self.eng.config.datanorm) @@ -269,12 +276,20 @@ def run_eic_ht(self, mzrange, zrange, color='b'): """ htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) - if self.eng.ccsstack_ht is not None: - ccsdata = self.eng.get_ccs_eic(mzrange=mzrange, zrange=zrange, normalize=self.eng.config.datanorm) - print("Got CCS Data") - else: + # if self.eng.ccsstack_ht is not None: + # ccsdata = self.eng.get_ccs_eic(mzrange=mzrange, zrange=zrange, normalize=self.eng.config.datanorm) + # print("Got CCS Data") + # else: + # ccsdata = None + # print("No CCS Data") + + try: + ccsdata = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, + normalize=self.eng.config.datanorm) + except: + print("Failed to convert to CCS") ccsdata = None - print("No CCS Data") + self.cc.add_chromatogram(eicdata, decondat=htdata, ccsdat=ccsdata, color=color, zrange=zrange, mzrange=mzrange, mode=self.eng.config.demultiplexmode) self.showht = True @@ -352,10 +367,12 @@ def plot_chromatograms(self, e=None, save=True): newlabel=c.label) if self.view.plotdecontic.flag: - self.view.plotdecontic.add_legend() + if self.eng.config.showlegends: + self.view.plotdecontic.add_legend() self.view.plotdecontic.repaint(resetzoom=True) if self.view.plottic.flag: - self.view.plottic.add_legend() + if self.eng.config.showlegends: + self.view.plottic.add_legend() self.view.plottic.repaint(resetzoom=True) self.view.plot1.repaint() if save: @@ -521,7 +538,7 @@ def on_run_ccs(self, e=None): :param e: Unused event :return: None """ - self.showccs=True + self.showccs = True self.export_config(self.eng.config.confname) ccs_tic = self.eng.ccs_transform_stacks() for c in self.cc.chromatograms: @@ -766,7 +783,8 @@ def on_plot_kernel(self, e=None): else: self.view.plottic.plotadd(xdat, data, colval=color, nopaint=False, newlabel=label) - self.view.plottic.add_legend() + if self.eng.config.showlegends: + self.view.plottic.add_legend() def on_autocorr2(self, index): """ diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 4fbb9452..a7ab5c2b 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -15,8 +15,10 @@ import math import warnings + warnings.filterwarnings("error") + # import fast_histogram # import pyfftw.interfaces.numpy_fft as fft @@ -245,7 +247,7 @@ def setup_demultiplex(self, mode=None, **kwargs): self.dtype = float self.setup_ht(**kwargs) elif mode == "FT" or mode == "aFT": - self.dtype= complex + self.dtype = complex self.setup_ft(**kwargs) def htdecon(self, data, **kwargs): @@ -1085,7 +1087,7 @@ def transform_dt_ccs_array(self, array, keepcomplex=True): minccs = np.amin(ccs3d) maxccs = np.amax(ccs3d) if self.config.ccsbins == -1: - binsize = (maxccs-minccs)/len(self.decontime) + binsize = (maxccs - minccs) / len(self.decontime) else: binsize = self.config.ccsbins ccsaxis = np.arange(minccs, maxccs, binsize) @@ -1101,16 +1103,14 @@ def transform_dt_ccs_array(self, array, keepcomplex=True): # Loop through array and paste back onto the new CCS axis for i in range(mlen): for j in range(zlen): - #indexes = np.array([ud.nearest(self.ccsaxis, c) for c in ccs3d[:, i, j]]) - #outlist = np.zeros_like(self.ccsaxis, dtype=self.dtype) - #outlist[indexes] += array[:, i, j] + # indexes = np.array([ud.nearest(self.ccsaxis, c) for c in ccs3d[:, i, j]]) + # outlist = np.zeros_like(self.ccsaxis, dtype=self.dtype) + # outlist[indexes] += array[:, i, j] outlist = np.histogram(ccs3d[:, i, j], bins=ccsbins, weights=array[:, i, j])[0] outarray[:, i, j] = outlist # interpolate the existing arrray onto the new ccs axis - #outarray[:, i, j] = np.interp(self.ccsaxis, ccs3d[:, i, j], array[:, i, j]) - - + # outarray[:, i, j] = np.interp(self.ccsaxis, ccs3d[:, i, j], array[:, i, j]) return outarray @@ -1133,11 +1133,51 @@ def ccs_transform_stacks(self): self.ccsaxis = self.ccsaxis[b1] self.ccsstack_ht = self.ccsstack_ht[b1] if self.config.FTsmooth > 0: + if self.config.FTsmooth > len(ccs_tic): + self.config.FTsmooth = 10 + print("Smoothing", self.config.FTsmooth) ccs_tic = scipy.signal.savgol_filter(ccs_tic, int(self.config.FTsmooth), 3) ccs_tic = np.transpose(np.vstack((self.ccsaxis, ccs_tic))) return ccs_tic + def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): + """ + Convert a trace to CCS. + :param trace: 1D array of intensity + :param mzrange: m/z range + :param zrange: charge range + :param normalize: Whether to normalize the output to the maximum. + :return: 2D array of CCS (time, intensity) + """ + trace = deepcopy(trace) + b1 = self.X >= mzrange[0] + b2 = self.X <= mzrange[1] + b3 = self.Y >= zrange[0] + b4 = self.Y <= zrange[1] + b = np.logical_and(b1, b2) + b = np.logical_and(b, b3) + b = np.logical_and(b, b4) + intarray = self.topharray[b] + mzarray = self.X[b] + zarray = self.Y[b] + + avgmz = np.sum(intarray * mzarray) / np.sum(intarray) + avgz = np.sum(intarray * zarray) / np.sum(intarray) + # Calculate weighted average of m/z and charge + + ccs_axis = calc_linear_ccs(avgmz, avgz, trace[:, 0], self.config) + + if normalize: + trace[:, 1] /= np.amax(trace[:, 1]) + + if self.config.FTsmooth > 0: + if self.config.FTsmooth > len(ccs_axis): + self.config.FTsmooth = 10 + trace[:, 1] = scipy.signal.savgol_filter(trace[:, 1], int(self.config.FTsmooth), 3) + + return np.transpose([ccs_axis, trace[:, 1]]) + if __name__ == '__main__': diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 451ab6c9..ce799b43 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -335,6 +335,12 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 + # Check box to show legends + self.ctllegends = wx.CheckBox(paneldm, label="Show Legends") + sizercontrolht1.Add(self.ctllegends, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + self.ctllegends.SetValue(True) + i += 1 + # Button to make 2d time vs. charge plot self.maketvsc = wx.Button(paneldm, -1, "Time-Z Plot") self.parent.Bind(wx.EVT_BUTTON, self.pres.make_charge_time_2dplot, self.maketvsc) @@ -447,7 +453,7 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): i += 1 # Add text input for post smoothing - self.ctlftsmooth = wx.TextCtrl(panelft, value="", size=size1) + self.ctlftsmooth = wx.TextCtrl(panelft, value=str(self.config.FTsmooth), size=size1) sizercontrolht1.Add(self.ctlftsmooth, (i, 1), span=(1, 1)) sizercontrolht1.Add(wx.StaticText(panelft, label="Post Smoothing: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) @@ -995,7 +1001,8 @@ def import_config_to_gui(self): self.Thaw() - self.update_demultiplex_mode() + if self.htmode: + self.update_demultiplex_mode() def export_gui_to_config(self, e=None): """ @@ -1064,6 +1071,7 @@ def export_gui_to_config(self, e=None): self.config.FTflatten = self.ctlftflatten.GetValue() self.config.FTapodize = self.ctlftapodize.GetValue() self.config.FTsmooth = ud.string_to_value(self.ctlftsmooth.GetValue()) + self.config.showlegends = self.ctllegends.GetValue() self.config.volt = ud.string_to_value(self.ctlvolt.GetValue()) self.config.temp = ud.string_to_value(self.ctltemp.GetValue()) diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index c6e83aeb..dca0e493 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -286,6 +286,7 @@ def __init__(self): self.FTflatten = True self.FTapodize = 1 self.FTsmooth = 0 + self.showlegends = True self.doubledec = False self.kernel = "" From 77af00ce34ff15f2f8448655cfd632445a5e5fb0 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 22 Feb 2024 12:45:57 -0700 Subject: [PATCH 14/35] Bug fixes. Wired in the separation parameter and fixed the expand functions --- unidec/UniChromCD.py | 19 +++++++++++-------- unidec/modules/HTEng.py | 9 ++++----- unidec/modules/IM_functions.py | 2 +- unidec/modules/gui_elements/CD_controls.py | 6 +++--- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 55049321..6d48df3c 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -307,10 +307,11 @@ def plot_chromatograms(self, e=None, save=True): self.view.plottic.clear_plot() self.view.plotdecontic.clear_plot() self.view.chrompanel.list.populate(self.cc) - - for c in self.cc.chromatograms: + number = 0 + for i, c in enumerate(self.cc.chromatograms): if c.ignore: continue + sep = number * self.eng.config.separation xlimits = c.mzrange ylimits = c.zrange @@ -327,11 +328,11 @@ def plot_chromatograms(self, e=None, save=True): xlab = "Time" if not self.view.plottic.flag: - self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1], config=self.eng.config, + self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1]-sep, config=self.eng.config, zoomout=True, label=c.label, xlabel=xlab, color=c.color, nopaint=True) else: - self.view.plottic.plotadd(xdat, c.chromdat[:, 1], colval=c.color, nopaint=True, + self.view.plottic.plotadd(xdat, c.chromdat[:, 1]-sep, colval=c.color, nopaint=True, newlabel=c.label) if c.decondat is not None and not self.showccs: @@ -343,11 +344,11 @@ def plot_chromatograms(self, e=None, save=True): xlab = "Time" if not self.view.plotdecontic.flag: - self.view.plotdecontic.plotrefreshtop(xdat, c.decondat[:, 1], config=self.eng.config, + self.view.plotdecontic.plotrefreshtop(xdat, c.decondat[:, 1]-sep, config=self.eng.config, zoomout=True, label=c.label, xlabel=xlab, color=c.color, nopaint=True) else: - self.view.plotdecontic.plotadd(xdat, c.decondat[:, 1], colval=c.color, nopaint=True, + self.view.plotdecontic.plotadd(xdat, c.decondat[:, 1]-sep, colval=c.color, nopaint=True, newlabel=c.label) if c.ccsdat is not None and self.showccs: @@ -359,13 +360,15 @@ def plot_chromatograms(self, e=None, save=True): xlab = "CCS (\u212B\u00B2)" if not self.view.plotdecontic.flag: - self.view.plotdecontic.plotrefreshtop(xdat, c.ccsdat[:, 1], config=self.eng.config, + self.view.plotdecontic.plotrefreshtop(xdat, c.ccsdat[:, 1]-sep, config=self.eng.config, zoomout=True, label=c.label, xlabel=xlab, color=c.color, nopaint=True) else: - self.view.plotdecontic.plotadd(xdat, c.ccsdat[:, 1], colval=c.color, nopaint=True, + self.view.plotdecontic.plotadd(xdat, c.ccsdat[:, 1]-sep, colval=c.color, nopaint=True, newlabel=c.label) + number += 1 + if self.view.plotdecontic.flag: if self.eng.config.showlegends: self.view.plotdecontic.add_legend() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index a7ab5c2b..1692a0d3 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -14,9 +14,8 @@ import scipy.fft as fft import math -import warnings - -warnings.filterwarnings("error") +#import warnings +#warnings.filterwarnings("error") # import fast_histogram @@ -1134,7 +1133,7 @@ def ccs_transform_stacks(self): self.ccsstack_ht = self.ccsstack_ht[b1] if self.config.FTsmooth > 0: if self.config.FTsmooth > len(ccs_tic): - self.config.FTsmooth = 10 + self.config.FTsmooth = 10. print("Smoothing", self.config.FTsmooth) ccs_tic = scipy.signal.savgol_filter(ccs_tic, int(self.config.FTsmooth), 3) @@ -1173,7 +1172,7 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): if self.config.FTsmooth > 0: if self.config.FTsmooth > len(ccs_axis): - self.config.FTsmooth = 10 + self.config.FTsmooth = 10. trace[:, 1] = scipy.signal.savgol_filter(trace[:, 1], int(self.config.FTsmooth), 3) return np.transpose([ccs_axis, trace[:, 1]]) diff --git a/unidec/modules/IM_functions.py b/unidec/modules/IM_functions.py index cd22e97b..aaa704d0 100644 --- a/unidec/modules/IM_functions.py +++ b/unidec/modules/IM_functions.py @@ -1,7 +1,7 @@ import math import numpy as np -import scipy.ndimage.filters as filt +import scipy.ndimage as filt # import time from unidec import tools as ud diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index ce799b43..58179981 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -1379,7 +1379,7 @@ def on_expand_yellow(self, e=None): num = self.foldpanels.GetCount() for i in range(0, num): fp = self.foldpanels.GetFoldPanel(i) - if i in [5, 6, 7]: + if i in [6, 7, 8]: self.foldpanels.Expand(fp) else: self.foldpanels.Collapse(fp) @@ -1389,7 +1389,7 @@ def on_expand_red(self, e=None): num = self.foldpanels.GetCount() for i in range(0, num): fp = self.foldpanels.GetFoldPanel(i) - if i in [8, 9]: + if i in [9, 10]: self.foldpanels.Expand(fp) else: self.foldpanels.Collapse(fp) @@ -1399,7 +1399,7 @@ def on_expand_main(self, e=None): num = self.foldpanels.GetCount() for i in range(0, num): fp = self.foldpanels.GetFoldPanel(i) - if i in [0, 5, 6, 8]: + if i in [0, 6, 7, 9]: self.foldpanels.Expand(fp) else: self.foldpanels.Collapse(fp) From ce26a992c231b6a7d0e0d1593d8f0bd72a6be52f Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 22 Feb 2024 13:02:13 -0700 Subject: [PATCH 15/35] Improvements to EIC CCS labelling and calculations --- unidec/UniChromCD.py | 11 +++++++++-- unidec/modules/HTEng.py | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 6d48df3c..392fc590 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -221,9 +221,10 @@ def on_run_eic_ccs(self, e=None): c.decondat = htdata c.chromdat = eicdata # eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) - eicdata = self.eng.convert_trace_to_ccs(c.decondat, mzrange=c.mzrange, zrange=c.zrange, + eicdata, avgz, avgmz = self.eng.convert_trace_to_ccs(c.decondat, mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) c.ccsdat = eicdata + c.label = "Avg. m/z: " + str(round(avgmz)) + " z: " + str(round(avgz)) self.plot_chromatograms() def on_run_tic_ht(self, e=None): @@ -284,15 +285,21 @@ def run_eic_ht(self, mzrange, zrange, color='b'): # print("No CCS Data") try: - ccsdata = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, + ccsdata, avgz, avgmz = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) except: print("Failed to convert to CCS") ccsdata = None + avgz = None + avgmz = None self.cc.add_chromatogram(eicdata, decondat=htdata, ccsdat=ccsdata, color=color, zrange=zrange, mzrange=mzrange, mode=self.eng.config.demultiplexmode) self.showht = True + + if avgz is not None: + c = self.cc.chromatograms[-1] + c.label = "Avg. m/z: " + str(round(avgmz)) + " z: " + str(round(avgz)) self.plot_chromatograms() def plot_chromatograms(self, e=None, save=True): diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 1692a0d3..959aeccf 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -1163,9 +1163,11 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): avgmz = np.sum(intarray * mzarray) / np.sum(intarray) avgz = np.sum(intarray * zarray) / np.sum(intarray) + avgz = np.round(avgz) + avgmass = (avgmz - self.config.adductmass) * avgz # Calculate weighted average of m/z and charge - ccs_axis = calc_linear_ccs(avgmz, avgz, trace[:, 0], self.config) + ccs_axis = calc_linear_ccs(avgmass, avgz, trace[:, 0], self.config) if normalize: trace[:, 1] /= np.amax(trace[:, 1]) @@ -1175,7 +1177,7 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): self.config.FTsmooth = 10. trace[:, 1] = scipy.signal.savgol_filter(trace[:, 1], int(self.config.FTsmooth), 3) - return np.transpose([ccs_axis, trace[:, 1]]) + return np.transpose([ccs_axis, trace[:, 1]]), avgz, avgmz if __name__ == '__main__': From 289bbc07929347b7c7e91cb6b1a12f31290201a4 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 22 Feb 2024 13:29:05 -0700 Subject: [PATCH 16/35] Updated color selection --- unidec/UniChromCD.py | 7 +++---- unidec/tools.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 392fc590..7ae84ade 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -28,7 +28,6 @@ def init(self, *args, **kwargs): self.cc = ChromatogramContainer() self.showht = False self.showccs = False - self.cycol = ud.create_color_cycle("bgcmy") self.view = CDWindow.CDMainwindow(self, "UniChrom for CD-MS Data", self.eng.config, htmode=True) @@ -169,7 +168,7 @@ def on_select_mzz_region(self, e=None): ylimits = self.view.plot1.subplot1.get_ylim() print("New limits:", xlimits, ylimits) self.view.plot1.reset_zoom() - color = next(self.cycol) + color = ud.get_color_from_index(len(self.cc.chromatograms)) if self.showht or self.showccs: self.run_eic_ht(xlimits, ylimits, color=color) else: @@ -180,9 +179,9 @@ def on_select_massz_range(self, e=None): if not wx.GetKeyState(wx.WXK_CONTROL): xlimits, ylimits = self.view.plot5.get_limits() xlimits = np.array(xlimits) * self.view.plot5.kdnorm - print("TEST", xlimits) + print("New Mass Limits", xlimits) self.view.plot5.reset_zoom() - color = next(self.cycol) + color = get_color_from_index(len(self.cc.chromatograms)) if self.eng.ccsstack_ht is not None: self.add_mass_eic(xlimits, ylimits, color=color, plot=True, ccs=True) diff --git a/unidec/tools.py b/unidec/tools.py index 7ee84092..6a425f76 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -145,6 +145,19 @@ def create_color_cycle(seq='bgrcmk'): return cycol +def get_color_from_index(index, seq="custom1"): + if seq == "tab": + carray = colors.TABLEAU_COLORS.keys() + elif seq == "custom1": + carray = ["purple", "blue", "dodgerblue", "cyan", "green", "lime", "gold", "orange", "coral", "red", "magenta"] + else: + carray = cm.get_cmap(seq).colors + color = list(carray)[index % len(carray)] + return color + + + + def match_files(directory, string, exclude=None): files = [] for file in os.listdir(directory): From 8e5acf25beee332007a102c8e7f6edad62ff351f Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 22 Feb 2024 13:36:38 -0700 Subject: [PATCH 17/35] Improvements to replot and controls --- unidec/UniChromCD.py | 14 ++++++++++++++ unidec/modules/gui_elements/CD_controls.py | 10 ++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 7ae84ade..65396582 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -84,6 +84,20 @@ def on_open_file(self, filename, directory, path=None, refresh=False): self.on_open_cdms_file(filename, directory, path=path, refresh=refresh) self.load_chroms(self.eng.config.cdchrom) + def on_replot(self, e=None): + self.export_config() + self.makeplot1() + self.makeplot2() + self.makeplot3() + self.makeplot4() + self.makeplot6() + self.on_replot_chrom() + pass + + def on_replot_chrom(self, e=None): + self.export_config() + self.plot_chromatograms() + def on_dataprep_button(self, e=None): """ Run data preparation. Makes the TIC and clears the chromatogram list. diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 58179981..98e9c1fb 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -333,12 +333,18 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): sizercontrolht1.Add(self.ctlxaxis, (i, 1), span=(1, 1)) sizercontrolht1.Add(wx.StaticText(paneldm, label="X-Axis: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + # Bind to on replot + self.ctlxaxis.Bind(wx.EVT_CHOICE, self.pres.on_replot_chrom) i += 1 # Check box to show legends - self.ctllegends = wx.CheckBox(paneldm, label="Show Legends") - sizercontrolht1.Add(self.ctllegends, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + self.ctllegends = wx.CheckBox(paneldm, label="") + sizercontrolht1.Add(self.ctllegends, (i, 1), flag=wx.ALIGN_CENTER_VERTICAL) + sizercontrolht1.Add(wx.StaticText(paneldm, label="Show Legends: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) self.ctllegends.SetValue(True) + # Bind to on replot + self.ctllegends.Bind(wx.EVT_CHECKBOX, self.pres.on_replot_chrom) i += 1 # Button to make 2d time vs. charge plot From b10a96f02b5411898637e8c6f1e35049a075dbe3 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 22 Feb 2024 17:00:31 -0700 Subject: [PATCH 18/35] Fixed some IM CCS calc, but some issues remain --- unidec/modules/HTEng.py | 5 +++-- unidec/modules/IM_functions.py | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 959aeccf..c8ec6146 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -6,7 +6,7 @@ from unidec.modules.CDEng import * from unidec.modules.ChromEng import * -from unidec.modules.IM_functions import calc_linear_ccs +from unidec.modules.IM_functions import calc_linear_ccs, calc_linear_ccsconst import unidec.tools as ud # from modules.unidecstructure import DataContainer @@ -1081,6 +1081,7 @@ def transform_dt_ccs_array(self, array, keepcomplex=True): # Create dt, mass, and z arrays in 3D dt3d, mass3d, ztab3d = np.meshgrid(self.decontime, self.massaxis, self.ztab, indexing='ij') # Parallel calc all CCS values into new 3d array + calc_linear_ccsconst(self.config) ccs3d = calc_linear_ccs(mass3d, ztab3d, dt3d, self.config) # Create new CCS axis minccs = np.amin(ccs3d) @@ -1166,7 +1167,7 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): avgz = np.round(avgz) avgmass = (avgmz - self.config.adductmass) * avgz # Calculate weighted average of m/z and charge - + calc_linear_ccsconst(self.config) ccs_axis = calc_linear_ccs(avgmass, avgz, trace[:, 0], self.config) if normalize: diff --git a/unidec/modules/IM_functions.py b/unidec/modules/IM_functions.py index aaa704d0..a0f76bea 100644 --- a/unidec/modules/IM_functions.py +++ b/unidec/modules/IM_functions.py @@ -266,7 +266,7 @@ def calc_linear_ccs(mass, z, dt, config): factor = 0.001 rmass = ac * (mass * hmass) / (mass + hmass) td = (dt - to) * factor - ccs = z * td * (1 / rmass) ** 0.5 * ccsconst + ccs = z * td * ((1 / rmass) ** 0.5) * ccsconst return ccs @@ -274,9 +274,9 @@ def calc_linear_ccsconst(config): e = 1.60217657E-19 kb = 1.3806488E-23 n = 2.6867774E25 - po = 760 + po = 760.0 tempo = 273.15 - temp = config.temp + temp = config.temp + tempo pressure = config.pressure driftlength = config.driftlength ccsconst = (np.sqrt(18.0 * np.pi) / 16.0) * (e / np.sqrt(kb * temp)) / n * ( From 627fd7efc588cc2c963b9fc32981fa30b736596f Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 23 Feb 2024 11:53:05 -0700 Subject: [PATCH 19/35] Added InvInjTime to CD-MS --- unidec/UniChromCD.py | 2 +- unidec/modules/CDEng.py | 49 ++++++++++++++++++---- unidec/modules/HTEng.py | 19 +++++++-- unidec/modules/gui_elements/CD_controls.py | 17 ++++++-- unidec/modules/i2ms_importer.py | 7 ++++ unidec/modules/unidecstructure.py | 1 + 6 files changed, 79 insertions(+), 16 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 65396582..ea38bd79 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -69,7 +69,7 @@ def init(self, *args, **kwargs): # self.run_all_ht() # self.run_all_mass_transform() # self.make_mass_cube_plot() - self.on_run_ccs() + # self.on_run_ccs() def on_open_file(self, filename, directory, path=None, refresh=False): """ diff --git a/unidec/modules/CDEng.py b/unidec/modules/CDEng.py index e0fee0c5..d07a6c97 100644 --- a/unidec/modules/CDEng.py +++ b/unidec/modules/CDEng.py @@ -123,6 +123,7 @@ def __init__(self): self.config.poolflag = 1 self.exemode = True self.massaxis = None + self.invinjtime = None pass def exe_mode(self, exemode=True): @@ -175,6 +176,7 @@ def open_cdms_file(self, path, refresh=False): # Get the extension extension = os.path.splitext(self.path)[1] + self.invinjtime = None if extension.lower() == ".raw": # Import Thermo Raw file using ThermoDataImporter @@ -189,6 +191,7 @@ def open_cdms_file(self, path, refresh=False): self.res = self.TDI.msrun.resolution # Set flag for correcting injection times later self.thermodata = True + self.invinjtime = 1./self.it elif extension.lower() == ".i2ms" or extension.lower() == ".dmt": # Import Thermo Raw file using ThermoDataImporter @@ -201,6 +204,8 @@ def open_cdms_file(self, path, refresh=False): scans = self.I2MSI.scans # Set flag for correcting injection times later self.thermodata = False + # Set the inverse injection time array for DMT data + self.invinjtime = self.I2MSI.invinjtime elif extension.lower() == ".mzml" or extension.lower() == ".gz": # Import mzML data, scans, and injection time @@ -209,6 +214,7 @@ def open_cdms_file(self, path, refresh=False): data = self.MLI.grab_data(threshold=0) self.scans = self.MLI.scans self.it = self.MLI.get_inj_time_array() + self.invinjtime = 1./self.it self.thermodata = True elif extension.lower() == ".txt": @@ -222,9 +228,13 @@ def open_cdms_file(self, path, refresh=False): # Look for scans in column 3 try: scans = data[:, 2] - except: + except Exception: # If not found, assume all are scan 1 scans = np.ones_like(mz) + try: + self.invinjtime = data[:, 3] + except Exception: + self.invinjtime = None except: # More complex text file from Jarrold Lab data = np.genfromtxt(path, dtype=np.str, comments=None) @@ -235,6 +245,7 @@ def open_cdms_file(self, path, refresh=False): mz = data[1:, mzcol].astype(float) intensity = data[1:, intcol].astype(float) scans = np.arange(len(mz)) + self.invinjtime = None # Don't do post-processing for thermo data self.thermodata = False @@ -247,9 +258,13 @@ def open_cdms_file(self, path, refresh=False): # Look for scans in column 3 try: scans = data[:, 2] - except: + except Exception: # If not found, assume all are scan 1 scans = np.ones_like(mz) + try: + self.invinjtime = data[:, 3] + except Exception: + self.invinjtime = None # Don't do post-processing for thermo data self.thermodata = False @@ -258,7 +273,7 @@ def open_cdms_file(self, path, refresh=False): data = np.fromfile(self.path) try: data = data.reshape((int(len(data) / 3), 3)) - except: + except Exception: data = data.reshape((int(len(data) / 2)), 2) # Assume m/z is in column 1 and intensity column 2 mz = data[:, 0] @@ -269,6 +284,10 @@ def open_cdms_file(self, path, refresh=False): except: # If not found, assume all are scan 1 scans = np.ones_like(mz) + try: + self.invinjtime = data[:, 3] + except Exception: + self.invinjtime = None # Don't do post-processing for thermo data self.thermodata = False @@ -281,15 +300,20 @@ def open_cdms_file(self, path, refresh=False): # Look for scans in column 3 try: scans = data[:, 2] - except: + except Exception: # If not found, assume all are scan 1 scans = np.ones_like(mz) + try: + self.invinjtime = data[:, 3] + except Exception: + self.invinjtime = None # Don't do post-processing for thermo data self.thermodata = False else: print("Unrecognized file type:", self.path) return 0 + print("File Read. Length: ", len(data)) # Post processing if data is from raw or mzML # Ignored if text file input @@ -298,12 +322,15 @@ def open_cdms_file(self, path, refresh=False): mz = np.concatenate([d[:, 0] for d in data]) try: intensity = np.concatenate([d[:, 1] * self.it[i] / 1000. for i, d in enumerate(data)]) - except: - print(self.it) + except Exception as e: + print(e, self.it) intensity = np.concatenate([d[:, 1] for i, d in enumerate(data)]) + if self.invinjtime is None: + self.invinjtime = np.ones_like(scans) + # Create data array - self.darray = np.transpose([mz, intensity, scans]) + self.darray = np.transpose([mz, intensity, scans, self.invinjtime]) # Filter out only the data with positive intensities boo1 = self.darray[:, 1] > 0 self.darray = self.darray[boo1] @@ -590,7 +617,13 @@ def histogram(self, mzbins=1, zbins=1, x=None, y=None, mzrange=None, zrange=None mzaxis = np.arange(mzrange[0] - mzbins / 2., mzrange[1] + 3 * mzbins / 2, mzbins) zaxis = np.arange(zrange[0] - zbins / 2., zrange[1] + zbins / 2, zbins) - self.harray, self.mz, self.ztab = np.histogram2d(x, y, [mzaxis, zaxis]) + if self.config.CDiitflag and self.invinjtime is not None: + weights = self.farray[:, 3] + print("Using weighted hist", np.mean(weights), np.amin(weights), np.amax(weights)) + else: + weights = None + + self.harray, self.mz, self.ztab = np.histogram2d(x, y, [mzaxis, zaxis], weights=weights) self.mz = self.mz[1:] - mzbins / 2. self.ztab = self.ztab[1:] - zbins / 2. diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index c8ec6146..ccfb48c8 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -661,7 +661,7 @@ def process_data_scans(self, transform=True): b1 = self.topfarray[:, 2] == s # Create histogram - harray = self.histogramLC(x=self.topfarray[b1, 0], y=self.topzarray[b1]) + harray = self.histogramLC(x=self.topfarray[b1, 0], y=self.topzarray[b1], w=self.topfarray[b1, 3]) harray = self.hist_data_prep(harray) # Add histogram to stack @@ -732,7 +732,7 @@ def prep_hist(self, mzbins=1, zbins=1, mzrange=None, zrange=None): # Calculate the mass of every point on histogram self.mass = (self.X - self.config.adductmass) * self.Y - def histogramLC(self, x=None, y=None): + def histogramLC(self, x=None, y=None, w=None): """ Histogram function used for LC-CD-MS data. :param x: x-axis (m/z) @@ -749,8 +749,19 @@ def histogramLC(self, x=None, y=None): if len(x) <= 1 or len(y) <= 1: harray = np.zeros_like(self.topharray) return harray + + if self.config.CDiitflag: + if w is None: + weights = self.farray[:, 3] + if len(weights) != len(x): + weights = None + else: + weights = w + else: + weights = None + # Create histogram - harray, mz, ztab = np.histogram2d(x, y, [self.mzaxis, self.zaxis]) + harray, mz, ztab = np.histogram2d(x, y, [self.mzaxis, self.zaxis], weights=weights) # harray = fast_histogram.histogram2d(x, y, [len(self.mzaxis)-1, len(self.zaxis)-1], # [(np.amin(self.mzaxis), np.amax(self.mzaxis)), # (np.amin(self.zaxis), np.amax(self.zaxis))]) @@ -1150,6 +1161,8 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): :param normalize: Whether to normalize the output to the maximum. :return: 2D array of CCS (time, intensity) """ + if self.topharray is None: + self.process_data_scans() trace = deepcopy(trace) b1 = self.X >= mzrange[0] b2 = self.X <= mzrange[1] diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 98e9c1fb..7b454baa 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -261,6 +261,12 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): self.ctlsmashflag.SetToolTip(wx.ToolTip("Remove Noise Peaks. See Tools>Select Noise Peaks")) i += 1 + # Check box for inv inj time + self.ctlinjtime = wx.CheckBox(panel1b, label="DMT Inverse Injection Time") + gbox1b.Add(self.ctlinjtime, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) + self.ctlinjtime.SetToolTip(wx.ToolTip("Use inverse injection time for histogram.")) + i += 1 + gbox1b.Add(wx.StaticText(panel1b, label=""), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) panel1b.SetSizer(gbox1b) @@ -472,9 +478,9 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): self.foldpanels.AddFoldPanelWindow(self.foldpanelft, wx.StaticText(self.foldpanelft, -1, " "), fpb.FPB_ALIGN_WIDTH) - foldpanel1c = self.foldpanels.AddFoldPanel(caption="Ion Mobility Parameters", collapsed=False, + self.foldpanelim = self.foldpanels.AddFoldPanel(caption="Ion Mobility Parameters", collapsed=False, cbstyle=style1c) - panel1c = wx.Panel(foldpanel1c, -1) + panel1c = wx.Panel(self.foldpanelim, -1) gbox1c = wx.GridBagSizer(wx.VERTICAL) i = 0 @@ -531,8 +537,8 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): panel1c.SetSizer(gbox1c) gbox1c.Fit(panel1c) - self.foldpanels.AddFoldPanelWindow(foldpanel1c, panel1c, fpb.FPB_ALIGN_WIDTH) - self.foldpanels.AddFoldPanelWindow(foldpanel1c, wx.StaticText(foldpanel1c, -1, " "), fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(self.foldpanelim, panel1c, fpb.FPB_ALIGN_WIDTH) + self.foldpanels.AddFoldPanelWindow(self.foldpanelim, wx.StaticText(self.foldpanelim, -1, " "), fpb.FPB_ALIGN_WIDTH) # Panel for unidec Parameters foldpanel2 = self.foldpanels.AddFoldPanel(caption="UniDec Parameters", collapsed=False, cbstyle=style2) @@ -932,6 +938,7 @@ def import_config_to_gui(self): self.ctldiscrete.SetValue(self.config.discreteplot) self.ctlsmashflag.SetValue(self.config.smashflag) + self.ctlinjtime.SetValue(self.config.CDiitflag) self.ctlpublicationmode.SetValue(self.config.publicationmode) self.ctlrawflag.SetSelection(self.config.rawflag) @@ -1094,6 +1101,7 @@ def export_gui_to_config(self, e=None): self.config.reductionpercent = ud.string_to_value(self.ctldatareductionpercent.GetValue()) self.config.smashflag = int(self.ctlsmashflag.GetValue()) + self.config.CDiitflag = int(self.ctlinjtime.GetValue()) self.config.discreteplot = int(self.ctldiscrete.GetValue()) self.config.publicationmode = int(self.ctlpublicationmode.GetValue()) self.config.rawflag = self.ctlrawflag.GetSelection() @@ -1429,6 +1437,7 @@ def update_demultiplex_mode(self, e=None): if "HT" in demultiplexmode: self.foldpanels.Collapse(self.foldpanelft) self.foldpanels.Expand(self.foldpanelht) + self.foldpanels.Collapse(self.foldpanelim) elif "FT" in demultiplexmode: self.foldpanels.Collapse(self.foldpanelht) self.foldpanels.Expand(self.foldpanelft) diff --git a/unidec/modules/i2ms_importer.py b/unidec/modules/i2ms_importer.py index 941cd25c..f62246b3 100644 --- a/unidec/modules/i2ms_importer.py +++ b/unidec/modules/i2ms_importer.py @@ -16,6 +16,13 @@ def __init__(self, file): self.scankey = np.where(self.keys[:, 1] == "ScanNumber")[0][0] self.slopekey = np.where(self.keys[:, 1] == "Slope")[0][0] self.scans = self.data[:,self.scankey] + self.iitkey = np.where(self.keys[:, 1] == "InverseInjectionTimeSeconds")[0][0] + self.invinjtime = self.data[:, self.iitkey] + ''' + try: + self.invinjtime = np.where(self.keys[:, 1] == "InverseInjectionTimeSeconds")[0][0] + except: + self.invinjtime = None''' def grab_data(self): slopes = self.data[:, self.slopekey] diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index dca0e493..577663ba 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -266,6 +266,7 @@ def __init__(self): self.CDres = 0 self.CDScanStart = -1 self.CDScanEnd = -1 + self.CDiitflag = False # Hadamard Transform Parameters self.htmode = False From 81d250d4dc7dc15405381030e660364af84c2f97 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 7 Mar 2024 17:16:11 -0700 Subject: [PATCH 20/35] Fixed glitches and bugs --- unidec/UniChromCD.py | 4 ++-- unidec/modules/HTEng.py | 26 +++++++++++++++++--------- unidec/modules/unidecstructure.py | 11 +++++++---- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index ea38bd79..8e4597d2 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -59,10 +59,10 @@ def init(self, *args, **kwargs): print("Opening Test File") path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") - + path2 = "Z:\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-03-06 Bgal 5bit triplicate\\20240306 Bgal inj5 iit1 bit5 zp10.dmt" pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" - self.on_open_file(None, None, path=pathft) + self.on_open_file(None, None, path=path2) # self.eng.process_data_scans() # self.make_cube_plot() # self.make_mass_time_2dplot() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index ccfb48c8..37249c7c 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -74,22 +74,26 @@ def parse_file_name(self, path): :param path: File path :return: None """ + path = os.path.split(path)[1] if "cyc" in path: # Find location of cyc and take next character cycindex = path.find("cyc") cyc = int(path[cycindex + 3]) self.config.HTcycletime = float(cyc) - if "zp" in path: + #if "zp" in path: # Find location of zp and take next character - zpindex = path.find("zp") - zp = float(path[zpindex + 2]) - self.config.HTtimepad = zp * self.config.HTcycletime + # zpindex = path.find("zp") + # zp = float(path[zpindex + 2]) + # self.config.HTtimepad = zp * self.config.HTcycletime if "bit" in path: # Find location of bit and take next character bitindex = path.find("bit") - self.config.htbit = int(path[bitindex + 3]) + try: + self.config.htbit = int(path[bitindex + 3]) + except Exception as e: + print("Error setting HT bit:", e, bitindex, path[bitindex:bitindex+4]) try: self.config.htseq = ud.HTseqDict[str(self.config.htbit)] except KeyError as e: @@ -269,16 +273,19 @@ def htdecon(self, data, **kwargs): # Set the range of indexes used in the deconvolution # Starts at the pad but shift will move it back self.indexrange = [self.padindex - self.shiftindex, len(data) - self.shiftindex] - # print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex) + #print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex, "Data Length:", len(data)) # Do the convolution output = fft.irfft(fft.rfft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk).real - + # If output is odd, add a 0 to the end + if len(output) < len(data[self.indexrange[0]:self.indexrange[1]]): + output = np.append(output, 0) + #print(len(output), len(data[self.indexrange[0]:self.indexrange[1]])) # Shift the output back to the original time if self.padindex > 0: # add zeros back on the front and roll to the correct index output = np.roll(np.concatenate((np.zeros(self.padindex), output)), self.rollindex) - + #print(np.shape(data), np.shape(output)) if "normalize" in kwargs: if kwargs["normalize"]: output /= np.amax(output) @@ -812,11 +819,12 @@ def tic_ht(self, **kwargs): """ self.get_tic(**kwargs) self.setup_demultiplex() + #print(np.shape(self.decontime), np.shape(self.fulltic)) self.htoutput, self.fulltic = self.run_demultiplex(self.fulltic, **kwargs) if self.config.datanorm: self.htoutput /= np.amax(self.htoutput) self.fulltic /= np.amax(self.fulltic) - print(np.shape(self.decontime), np.shape(self.htoutput)) + #print(np.shape(self.decontime), np.shape(self.htoutput)) return np.transpose([self.decontime, self.htoutput]) def get_eic(self, mzrange, zrange, **kwargs): diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 577663ba..7fb14a4a 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -572,12 +572,13 @@ def config_export(self, name): f.write("CDScanStart " + str(self.CDScanStart) + "\n") f.write("CDScanEnd " + str(self.CDScanEnd) + "\n") f.write("HTksmooth " + str(self.HTksmooth) + "\n") - f.write("CDScanCompresse " + str(self.CDScanCompress) + "\n") + f.write("CDScanCompress " + str(self.CDScanCompress) + "\n") f.write("HTtimepad " + str(self.HTtimepad) + "\n") f.write("HTanalysistime " + str(self.HTanalysistime) + "\n") f.write("HTxaxis " + str(self.HTxaxis) + "\n") f.write("HTcycleindex " + str(self.HTcycleindex) + "\n") - f.write("HTtimepad " + str(self.HTtimepad) + "\n") + f.write("HTcylctime " + str(self.HTcycletime) + "\n") + f.write("HTtimeshift " + str(self.HTtimeshift) + "\n") f.write("htbit " + str(self.htbit) + "\n") f.write("FTstart " + str(self.FTstart) + "\n") f.write("FTend " + str(self.FTend) + "\n") @@ -706,7 +707,8 @@ def config_import(self, name): self.CDScanEnd = ud.string_to_int(line.split()[1]) if line.startswith("HTksmooth"): self.HTksmooth = ud.string_to_value(line.split()[1]) - + if line.startswith("HTtimeshift"): + self.HTtimeshift = ud.string_to_value(line.split()[1]) if line.startswith("HTtimepad"): self.HTtimepad = ud.string_to_value(line.split()[1]) if line.startswith("HTanalysistime"): @@ -715,6 +717,8 @@ def config_import(self, name): self.HTxaxis = line.split()[1] if line.startswith("HTcycleindex"): self.HTcycleindex = ud.string_to_value(line.split()[1]) + if line.startswith("HTcylctime"): + self.HTcycletime = ud.string_to_value(line.split()[1]) if line.startswith("htbit"): self.htbit = ud.string_to_value(line.split()[1]) if line.startswith("CDScanCompress"): @@ -731,7 +735,6 @@ def config_import(self, name): self.FTapodize = ud.string_to_int(line.split()[1]) if line.startswith("FTsmooth"): self.FTsmooth = ud.string_to_value(line.split()[1]) - if line.startswith("zzsig"): self.zzsig = ud.string_to_value(line.split()[1]) if line.startswith("psig"): From 791e8b62ced87c31be9954637b93497042ccf505 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 15 Mar 2024 13:12:37 -0700 Subject: [PATCH 21/35] Fixes to UCCD for plot kernel and bit selection on HT --- unidec/UniChromCD.py | 2 ++ unidec/modules/HTEng.py | 1 + unidec/modules/gui_elements/CD_controls.py | 1 + unidec/tools.py | 3 ++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 8e4597d2..9d6dfec3 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -799,6 +799,8 @@ def on_plot_kernel(self, e=None): self.indexrange = [self.eng.padindex - self.eng.shiftindex, len(xdat) - self.eng.shiftindex] xdat = xdat[self.indexrange[0]:self.indexrange[1]] data *= np.amax(self.eng.fulltic) / np.amax(data) + if len(xdat) > len(data): + xdat = xdat[:len(data)] if not self.view.plottic.flag: self.view.plottic.plotrefreshtop(xdat, data, config=self.eng.config, zoomout=True, label=label, xlabel=xlab, diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 37249c7c..88c5ca49 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -136,6 +136,7 @@ def setup_ht(self, cycleindex=None): If not specified, default is the full number of scans divided by the number of cycles. :return: None """ + self.config.htseq = ud.HTseqDict[str(self.config.htbit)] # Finds the number of scans that are padded if self.config.HTtimepad > 0: self.padindex = self.set_timepad_index(self.config.HTtimepad) diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 7b454baa..24e7102e 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -1073,6 +1073,7 @@ def export_gui_to_config(self, e=None): self.config.CDScanCompress = ud.string_to_value(self.ctlscancompress.GetValue()) self.config.HTksmooth = ud.string_to_value(self.ctlkernelsmooth.GetValue()) self.config.htbit = int(self.ctlhtseq.GetStringSelection()) + self.config.htseq = ud.HTseqDict[str(self.config.htbit)] self.config.HTxaxis = self.ctlxaxis.GetStringSelection() self.config.HTtimeshift = ud.string_to_value(self.ctlhttimeshift.GetValue()) self.config.HTtimepad = ud.string_to_value(self.ctltimepad.GetValue()) diff --git a/unidec/tools.py b/unidec/tools.py index 6a425f76..fd33d29e 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -57,7 +57,8 @@ HTseqDict = {'2': '101', '3': '1110100', '4': '000100110101111', '5': '0000100101100111110001101110101', '6': '000001000011000101001111010001110010010110111011001101010111111', '7': '0000001000001100001010001111001000101100111010100111110100001110001001001101101011011110110001101001011101110011001010101111111', - '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111'} + '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111', + '-2': '010', '-3': '0001011', '-4': '111011001010000', '-5': '1111011010011000001110010001010'} def get_importer(path): From bc4ec26276f249a687ab4307bdd3965196ad1c9f Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 15 Mar 2024 13:24:42 -0700 Subject: [PATCH 22/35] Gave - sign overrides for mz and charge range on UCD (and UCCD) --- unidec/modules/CDEng.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/unidec/modules/CDEng.py b/unidec/modules/CDEng.py index d07a6c97..e93f3657 100644 --- a/unidec/modules/CDEng.py +++ b/unidec/modules/CDEng.py @@ -465,7 +465,7 @@ def process_data(self, transform=True): # Filter m/z print("Filtering m/z range:", self.config.minmz, self.config.maxmz, "Start Length:", len(self.farray)) - self.filter_mz(mzrange=[self.config.minmz, self.config.maxmz]) + self.filter_mz(mzrange=np.abs([self.config.minmz, self.config.maxmz])) # Filter Centroids print("Filtering centroids:", self.config.CDres, "Start Length:", len(self.farray)) @@ -473,7 +473,7 @@ def process_data(self, transform=True): # Filter Charge States print("Filtering Charge range:", self.config.startz, self.config.endz, "Start Length:", len(self.farray)) - self.filter_z(zrange=[self.config.startz, self.config.endz + 1]) + self.filter_z(zrange=np.abs([self.config.startz, self.config.endz + 1])) # Convert intensity to charge print("Converting From Intensity to Charge. Slope:", self.config.CDslope, "Start Length:", len(self.farray)) @@ -607,9 +607,17 @@ def histogram(self, mzbins=1, zbins=1, x=None, y=None, mzrange=None, zrange=None return 0 if mzrange is None: - mzrange = [np.floor(np.amin(x)), np.amax(x)] + mzrange = np.array([self.config.minmz, self.config.maxmz]) + if np.any(mzrange < 0): + mzrange = np.abs(mzrange) + else: + mzrange = [np.floor(np.amin(x)), np.amax(x)] if zrange is None: - zrange = [np.floor(np.amin(y)), np.amax(y)] + zrange = np.array([self.config.startz, self.config.endz + 1]) + if np.any(zrange < 0): + zrange = np.abs(zrange) + else: + zrange = [np.floor(np.amin(y)), np.ceil(np.amax(y))+1] mzaxis = np.arange(mzrange[0] - mzbins / 2., mzrange[1] + mzbins / 2, mzbins) # Weird fix to make this axis even is necessary for CuPy fft for some reason... From 29dc4a6dda6dcf9bdb38d47b6c8382a4069e8952 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Fri, 15 Mar 2024 15:44:43 -0700 Subject: [PATCH 23/35] Fixes to saving and inverse bit file parsing --- unidec/UniChromCD.py | 4 ++++ unidec/modules/HTEng.py | 33 ++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 9d6dfec3..4cae49a7 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -848,6 +848,10 @@ def export_arrays(self): newlabel = newlabel.replace(":", "_") newlabel = os.path.join(os.path.split(self.eng.config.outfname)[0], newlabel) np.savetxt(newlabel + "_chrom.txt", c.chromdat) + if c.decondat is not None: + np.savetxt(newlabel + "_decon.txt", c.decondat) + if c.ccsdat is not None: + np.savetxt(newlabel + "_ccs.txt", c.ccsdat) print("Saved Files") diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 88c5ca49..79f7781a 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -14,8 +14,9 @@ import scipy.fft as fft import math -#import warnings -#warnings.filterwarnings("error") + +# import warnings +# warnings.filterwarnings("error") # import fast_histogram @@ -81,8 +82,8 @@ def parse_file_name(self, path): cyc = int(path[cycindex + 3]) self.config.HTcycletime = float(cyc) - #if "zp" in path: - # Find location of zp and take next character + # if "zp" in path: + # Find location of zp and take next character # zpindex = path.find("zp") # zp = float(path[zpindex + 2]) # self.config.HTtimepad = zp * self.config.HTcycletime @@ -91,9 +92,19 @@ def parse_file_name(self, path): # Find location of bit and take next character bitindex = path.find("bit") try: - self.config.htbit = int(path[bitindex + 3]) + firstbit = path[bitindex + 3] except Exception as e: - print("Error setting HT bit:", e, bitindex, path[bitindex:bitindex+4]) + print("Error setting HT bit:", e, bitindex, path[bitindex:bitindex + 4]) + firstbit = "" + if firstbit.isdigit(): + self.config.htbit = int(firstbit) + elif firstbit == "-": + self.config.htbit = -1 * int(path[bitindex + 4]) + else: + try: + self.config.htbit = int(path[bitindex + 3]) + except Exception as e: + print("Error setting HT bit:", e, bitindex, path[bitindex:bitindex + 4]) try: self.config.htseq = ud.HTseqDict[str(self.config.htbit)] except KeyError as e: @@ -274,19 +285,19 @@ def htdecon(self, data, **kwargs): # Set the range of indexes used in the deconvolution # Starts at the pad but shift will move it back self.indexrange = [self.padindex - self.shiftindex, len(data) - self.shiftindex] - #print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex, "Data Length:", len(data)) + # print("Index Range:", self.indexrange, "Pad Index:", self.padindex, "Shift Index:", self.shiftindex, "Data Length:", len(data)) # Do the convolution output = fft.irfft(fft.rfft(data[self.indexrange[0]:self.indexrange[1]]) * self.fftk).real # If output is odd, add a 0 to the end if len(output) < len(data[self.indexrange[0]:self.indexrange[1]]): output = np.append(output, 0) - #print(len(output), len(data[self.indexrange[0]:self.indexrange[1]])) + # print(len(output), len(data[self.indexrange[0]:self.indexrange[1]])) # Shift the output back to the original time if self.padindex > 0: # add zeros back on the front and roll to the correct index output = np.roll(np.concatenate((np.zeros(self.padindex), output)), self.rollindex) - #print(np.shape(data), np.shape(output)) + # print(np.shape(data), np.shape(output)) if "normalize" in kwargs: if kwargs["normalize"]: output /= np.amax(output) @@ -820,12 +831,12 @@ def tic_ht(self, **kwargs): """ self.get_tic(**kwargs) self.setup_demultiplex() - #print(np.shape(self.decontime), np.shape(self.fulltic)) + # print(np.shape(self.decontime), np.shape(self.fulltic)) self.htoutput, self.fulltic = self.run_demultiplex(self.fulltic, **kwargs) if self.config.datanorm: self.htoutput /= np.amax(self.htoutput) self.fulltic /= np.amax(self.fulltic) - #print(np.shape(self.decontime), np.shape(self.htoutput)) + # print(np.shape(self.decontime), np.shape(self.htoutput)) return np.transpose([self.decontime, self.htoutput]) def get_eic(self, mzrange, zrange, **kwargs): From 00803cd88e631fb01b1da3d1db0c57191e59ab52 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Mon, 18 Mar 2024 12:02:53 -0700 Subject: [PATCH 24/35] Added gitignore to top directory --- .gitignore | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..abedf6aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,271 @@ +*.pyc +*.map +*.md5 +*.tex +*.hdf5 +unidec_doc +.idea +*.spec +*.sdf +*.vcxproj.filters +*.vcxproj.user +*.VC.db +*.o +*.obj +*.suo +conf.dat +unidec_src/UniDec/.vs +unidec_src/UniDec/x64 +UD_peak_width.h +Licensing +Scripts +.svn +dist +build +old +TestSpectra +default_conf.dat +unidec_src/cfunction/x64 +unidec_src/cfunction/libmypfunc32.dll +Scripts/MTM/unidec_tests.py +pyinstaller_64.bat +pyinstaller_64_dist.bat +/Scripts/MTM/fft_test.py +Scripts/test_with_spectrum.py +todo.txt +unidec/bin/Other_builds/ +unidec_src/Old +test_GUI.py +Scripts/MTM/test_UniDec.py +unidec/metaunidec/test_MUD.py +unidec/bin/CDCReaderOld.exe +Scripts/MTM/Old/fft_test.py +unidec_src/UniDec/compilewin.bat +unidec_src/UniDec/UniDec.pdb +unidec_src/UniDec/compilewinpgcc.bat +/unidec/modules/GridDecon2.py +/unidec/bin/multiplierz/ +unidec/modules/1D_Mass_Defects.txt +unidec/modules/2D_Mass_Defects.txt +unidec/modules/Mass_Defect_Extracts.txt +unidec/modules/Mass_Defect_Grid.txt +unidec/modules/Total_1D_Mass_Defects.txt +unidec/modules/Total_2D_Mass_Defects.txt +unidec/modules/Mass_Defect_Extracts_xvals.txt +unidec/modules/0_1D_Mass_Defects.txt +unidec/modules/0_2D_Mass_Defects.txt +unidec/modules/1_1D_Mass_Defects.txt +unidec/modules/1_2D_Mass_Defects.txt +unidec_src/UniDec/UniDec.VC.VC.opendb +unidec_src/UniDec/UD_sg.h +unidec_src/UniDec/UniDec_Mainh-rev485.h +unidec_src/UniDec/UniDech-rev511.h +unidec/bin/old_waters/ +unidec/modules/Mass_Defect_Extracts_Total.txt +UniDecPastedSpectra/ +unidec/iFAMS/test_unidecfiles/ +/unidec/modules/waters_importer/old/ +/unidec/modules/waters_importer/MassLynxLockMassProcessor.py +/unidec/modules/waters_importer/MassLynxRaw.lib +unidec_src/UniDec/UniDec.log +unidec_src/UniDec/oldVS/ +/unidec/bin/Example Data/ADH_unidecfiles/ADH_fitdat.bin +/unidec/bin/Example Data/ADH_unidecfiles/ADH_grid.bin +/unidec/bin/Example Data/ADH_unidecfiles/ADH_match.dat +/unidec/bin/Example Data/ADH_unidecfiles/ADH_mass.txt +/unidec/bin/Example Data/ADH_unidecfiles/ADH_input.dat +/unidec/bin/Example Data/ADH_unidecfiles/ADH_massgrid.bin +/unidec/bin/Example Data/ADH_unidecfiles/ADH_error.txt +/unidec/bin/Example Data/ADH_unidecfiles/ADH_rawdata.txt +/unidec/bin/Example Data/ADH_unidecfiles/ADH_peaks.dat +/unidec/bin/Example Data/ADH_unidecfiles/default.hdf5 +/unidec/bin/Example Data/BSA_unidecfiles/BSA_input.dat +/unidec/bin/Example Data/BSA_unidecfiles/BSA_match.dat +/unidec/bin/Example Data/BSA_unidecfiles/BSA_error.txt +/unidec/bin/Example Data/BSA_unidecfiles/BSA_grid.bin +/unidec/bin/Example Data/BSA_unidecfiles/BSA_massgrid.bin +/unidec/bin/Example Data/BSA_unidecfiles/BSA_rawdata.txt +/unidec/bin/Example Data/BSA_unidecfiles/BSA_fitdat.bin +/unidec/bin/Example Data/BSA_unidecfiles/BSA_mass.txt +/unidec/bin/Example Data/BSA_unidecfiles/BSA_peaks.dat +/unidec/bin/Example Data/UniDec_Figures_and_Files/ +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_fitdat.bin +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_rawdata.txt +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_grid.bin +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_massgrid.bin +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_error.txt +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_match.dat +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_peaks.dat +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_input.dat +/unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_mass.txt +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_mass.txt +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_rawdata.txt +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_peaks.dat +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_fitdat.bin +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_input.dat +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_massgrid.bin +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_grid.bin +/unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_error.txt +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_grid.bin +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_rawdata.txt +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_error.txt +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_mass.txt +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_massgrid.bin +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_input.dat +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_fitdat.bin +/unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_peaks.dat +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_Total_1D_Mass_Defects.txt +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_Total_2D_Mass_Defects.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_chargedata.dat +unidec/bin/Example Data/BSA_unidecfiles/BSA_chargedata_areas.dat +unidec/bin/Example Data/BSA_unidecfiles/BSA_peakparam.dat + /__pycache__/ + __pycache__/ + __pycache__ +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_peak_areas.dat +unidec/bin/mkl_intel_thread.dll +unidec/bin/Example Data/ADH_unidecfiles/ADH_peak_areas.dat +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_1D_Mass_Defects.txt +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_2D_Mass_Defects.txt +Scripts/MTM/Old/greparam.py +unidec/bin/Presets/Marius/ +MANIFEST.in +desktop.ini +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure1.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure2.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure3.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure4.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure5.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure6.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_manualfile.dat +unidec/bin/Example Data/BSA_unidecfiles/BSA_report.aux +unidec/bin/Example Data/BSA_unidecfiles/BSA_report.log +unidec/bin/Example Data/BSA_unidecfiles/BSA_report.pdf +unidec/bin/recent.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_chargepeaks.txt +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/Extract_grid_2D_Extract.txt +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/Extract_total_2D_Extract.txt +ExtractFull2D_Da.txt +extracts.txt +sums.txt +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/POPC_Nanodiscs_chargepeaks.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_peak_areas.dat +ofile.dat +BSA_report.aux +BSA_report.log +BSA_report.out +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure1.eps +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure2.eps +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure3.eps +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure4.eps +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure5.eps +unidec/bin/Example Data/BSA_unidecfiles/BSA_Figure6.eps +unidec/bin/Example Data/BSA_unidecfiles/BSA_report.out +unidec/bin/Example Data/BSA_unidecfiles/BSA_composite_spectrum.dat + +unidec/modules/thermo_reader/RawFileReader +unidec/modules/thermo_reader/RawFileReaderExample +unidec/bin/Example Data/POPC_Nanodiscs_unidecfiles/Peaks_Mass_Defects.txt +*.diagsession +unidec_src/UniDec/UniDec.common.settings +unidec/bin/recentCD.txt +Scripts/MTM/Old/ZoomBoxMargot.py +*.feather +unidec/bin/Other_builds.zip +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_input.dat +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_mass.txt +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_peaks.dat +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_rawdata.npz +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_error.txt +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_fitdat.bin +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_grid.bin +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_input.dat +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_mass.txt +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_massgrid.bin +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_peak_areas.dat +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_peaks.dat +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_rawdata.txt +unidec/bin/Presets/CDMS/Heck_CDMS_AAVs_120K_0148_slope +unidec/bin/Presets/CDMS/Heck_CDMS_AAVs_120k_0156 +unidec/bin/Presets/CDMS/Heck_CDMS_AAVs_240K_02193_slope +unidec/bin/Presets/CDMS/Heck_CDMS_AAVs_240K_021_slope +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_fitdat.bin +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_Figure1.pdf +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_Figure2.pdf +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_Figure3.pdf +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_Figure4.pdf +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_Figure5.pdf +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_Figure6.pdf +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_report.aux +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_report.log +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_report.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_Figure1.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_Figure2.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_Figure3.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_Figure4.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_Figure5.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_Figure6.pdf +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_report.aux +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_report.log +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_report.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_Figure1.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_Figure2.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_Figure3.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_Figure4.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_Figure5.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_Figure6.pdf +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_report.aux +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_report.log +unidec/bin/Example Data/GroEL UniDec_unidecfiles/GroEL UniDec_report.pdf +unidec/bin/Example Data/BSA_unidecfiles/BSA_ofile_crazy.dat +unidec/bin/Example Data/BSA_unidecfiles/BSA_report.html +unidec_src/UniDec/test_match.bat +unidec/LipiDec/LipyDec.py +unidec/LipiDec/SkyQuant.py +docker_commands.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_conv.bin +.pypirc +UniDec.egg-info +distpypi +publish_test.cmd +unidec/bin/IUPAC-atomic-masses.csv +unidec/modules/hramools.py +unidec/src/x64/ +unidec/src/.vs/ +pyproject.toml +unidec/iFAMS/pyqtgraph/ +unidec/iFAMS/iFAMS_V5_GUI.py +unidec/iFAMS/GuiTestFun.py +unidec/bin/TestSpectra/test_1_unidecfiles/ +unidec/bin/Example Data/UniChrom/UniDec_Figures_and_Files/ +.dockerignore +unidec/src/test_match.bat +unidec/bin/Example Data/BSA_unidecfiles/test.html +unidec/bin/Example Data/BSA_unidecfiles/BSA_baseline.bin +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_report.html +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_match.dat +PublicScripts/DIA-PTCR/IL22/glycan_compare_hist_and_spectrum.py +unidec/bin/Example Data/ADH_unidecfiles/ADH_chargepeaks.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_extracted_heights.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_extracted_intensities0.txt +unidec/bin/Example Data/BSA_unidecfiles/BSA_mzpeakdata.dat +unidec/bin/Example Data/BSA_unidecfiles/BSA_total_extracted_heights.txt +unidec/bin/Example Data/BSA_unidecfiles/Extract_grid_2D_Extract.txt +unidec/bin/Example Data/BSA_unidecfiles/Extract_total_2D_Extract.txt +unidec/bin/Example Data/UniChrom/SEC_Native_Herceptin_deconvolved.mzML +unidec/bin/Example Data/BSA_unidecfiles/BSA_smashfile.dat +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_smashfile.dat +unidec/bin/Example Data/ADHclean_unidecfiles/ADHclean_conv.bin +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_match.dat +unidec/bin/Example Data/GroEL HCD UniDec_unidecfiles/GroEL HCD UniDec_ofile.dat +unidec/bin/Presets/CDMS/LDL STORI dat.dat +unidec/src/UniDecLib/ +unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_extracted_intensities0.txt +unidec/bin/WiffReaderCOM.dll +unidec/bin/WiffReaderCOM.pdb +unidec/bin/WiffReaderCOM.tlb +hashing.py +PublicScripts/MutantCycleAnalysis/Mutant Cycle Analysis/ +unidec/modules/gui_elements/UniDec.mplstyle + + From 2c3239475ca4ae646c331906dff407a9aea0f022 Mon Sep 17 00:00:00 2001 From: Cesar Quihuis-Romero Date: Mon, 18 Mar 2024 12:13:00 -0700 Subject: [PATCH 25/35] added sliders, working on action event handlers. --- .../modules/gui_elements/PlotControlsPanel.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/unidec/modules/gui_elements/PlotControlsPanel.py b/unidec/modules/gui_elements/PlotControlsPanel.py index 763ed460..73c3c823 100644 --- a/unidec/modules/gui_elements/PlotControlsPanel.py +++ b/unidec/modules/gui_elements/PlotControlsPanel.py @@ -12,7 +12,6 @@ "axes.xmargin": 0.05, "axes.ymargin": 0.05, } - # Export plot_params to .mplstyle file def export_mplstyle(params, filename="unidec.mplstyle"): with open(filename, "w") as f: @@ -39,9 +38,28 @@ def __init__(self, parent, pres, config=None, *args, **kwargs): label = wx.StaticText(self, label=p) txt_ctrl = wx.TextCtrl(self, value=str(plot_params[p]), name=p) + # Insert into sizer self.sizer.Add(label, pos=(i, 0), flag=wx.ALL, border=5) self.sizer.Add(txt_ctrl, pos=(i, 1), flag=wx.ALL, border=5) + # Create and Insert a slider into sizer (ommiting a slider for lineStyle) + if (i != 4 and i != 5): + slider = wx.Slider(self, value=0, minValue=0, maxValue=100, name=p) + self.sizer.Add(slider, pos=(i,2),flag=wx.ALL, border=5) + + # Adding a drop down menu with 4 options: Large, Medium, Small, and Custom + size_label = wx.StaticText(self, label="Size: ") + size_choices = ["Large", "Medium", "Small", "Custom"] + self.size_choice = wx.Choice(self, choices=size_choices) + self.sizer.Add(size_label, pos=(len(plot_params)+1 , 0), + flag=wx.ALL,border=5) + self.sizer.Add(self.size_choice, pos=(len(plot_params)+2,0), + flag=wx.ALL, border=5) + + # Adding a radio button that "locks" settings + self.lock_settings_radio = wx.RadioButton(self, label="Lock Settings") + self.sizer.Add(self.lock_settings_radio, pos=(len(plot_params) + 1, 1), + flag=wx.ALL, border=5) # Add in apply button self.apply_button = wx.Button(self, label="Apply") From eb9f31a40a91bc43729d54b608aee6562a8e734c Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Wed, 20 Mar 2024 16:36:21 -0700 Subject: [PATCH 26/35] Fixed bugs with UCCD when there are blank scans that are not included at the end of the run in the DMT file --- unidec/UniChromCD.py | 3 ++- unidec/modules/HTEng.py | 18 +++++++++++++++--- unidec/modules/gui_elements/CD_controls.py | 11 +++++++++++ unidec/modules/unidecstructure.py | 4 ++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 4cae49a7..77045f96 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -61,8 +61,9 @@ def init(self, *args, **kwargs): "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") path2 = "Z:\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-03-06 Bgal 5bit triplicate\\20240306 Bgal inj5 iit1 bit5 zp10.dmt" pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" + path3 = "Z:\\Group Share\\ONO\\CDMS\\5 bit HT data\\20240313 JDS Bgal 0o1g_l inj1o5 quad9k bit-5 zp10 i2.dmt" - self.on_open_file(None, None, path=path2) + self.on_open_file(None, None, path=path3) # self.eng.process_data_scans() # self.make_cube_plot() # self.make_mass_time_2dplot() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 79f7781a..d31d0f11 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -174,7 +174,7 @@ def setup_ht(self, cycleindex=None): shortkernel = np.roll(shortkernel, self.kernelroll) scaledkernel = np.arange(len(shortkernel)) / (len(shortkernel)) - kernelscans = scaledkernel * (np.amax(self.scans) - self.padindex) + kernelscans = scaledkernel * (np.amax(self.fullscans) - self.padindex) if cycleindex is None: if self.config.HTcycleindex <= 0: @@ -184,7 +184,7 @@ def setup_ht(self, cycleindex=None): # Correct the cycle time to be not a division of the total sequence length but a fixed number of scans if cycleindex is not None: - diff = np.amax(self.scans) - self.padindex - cycleindex * len(shortkernel) + diff = np.amax(self.fullscans) - self.padindex - cycleindex * len(shortkernel) self.padindex += int(diff) + 1 kernelscans = np.arange(len(shortkernel)) * cycleindex self.cycleindex = int(cycleindex) @@ -645,7 +645,19 @@ def prep_time_domain(self): :return: None """ self.scans = np.unique(self.farray[:, 2]) - self.fullscans = np.arange(1, np.amax(self.scans) + 1) + if self.config.HTmaxscans < 0: + maxscans = np.amax(self.scans) + print("Max Scans: ", maxscans) + else: + maxscans = self.config.HTmaxscans + if self.config.CDScanCompress > 1: + maxscans = int(maxscans / self.config.CDScanCompress) + + if maxscans < np.amax(self.scans): + print("Error with setting Max Scans, less than total scan number:", maxscans, np.amax(self.scans)) + maxscans = np.amax(self.scans) + + self.fullscans = np.arange(1, maxscans + 1) self.fulltime = self.fullscans * self.config.HTanalysistime / np.amax(self.fullscans) self.decontime = self.fulltime self.deconscans = self.fullscans diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 24e7102e..df715230 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -405,6 +405,13 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 + # Text control for max scans + self.ctlmaxscans = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlmaxscans, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="Max Scans: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + # Drop down for HTseq self.ctlhtseq = wx.Choice(panelht, -1, choices=list(ud.HTseqDict.keys())) self.ctlhtseq.SetSelection(3) @@ -949,6 +956,7 @@ def import_config_to_gui(self): self.ctlhttimeshift.SetValue(str(self.config.HTtimeshift)) self.ctltimepad.SetValue(str(self.config.HTtimepad)) self.ctlanalysistime.SetValue(str(self.config.HTanalysistime)) + self.ctlmaxscans.SetValue(str(self.config.HTmaxscans)) self.ctlcycleindex.SetValue(str(self.config.HTcycleindex)) self.ctlxaxis.SetStringSelection(self.config.HTxaxis) self.ctlmultiplexmode.SetStringSelection(self.config.demultiplexmode) @@ -1078,6 +1086,7 @@ def export_gui_to_config(self, e=None): self.config.HTtimeshift = ud.string_to_value(self.ctlhttimeshift.GetValue()) self.config.HTtimepad = ud.string_to_value(self.ctltimepad.GetValue()) self.config.HTanalysistime = ud.string_to_value(self.ctlanalysistime.GetValue()) + self.config.HTmaxscans = ud.string_to_value(self.ctlmaxscans.GetValue()) self.config.HTcycleindex = ud.string_to_value(self.ctlcycleindex.GetValue()) self.config.demultiplexmode = self.ctlmultiplexmode.GetStringSelection() self.config.FTstart = ud.string_to_value(self.ctlftstart.GetValue()) @@ -1276,6 +1285,8 @@ def setup_tool_tips(self): "Units of retention time. Set cyc{n} and zp{m} in file name to set automatically as n and m.")) self.ctlanalysistime.SetToolTip(wx.ToolTip( "Total analysis time. Must be set manually for DMT files.")) + self.ctlmaxscans.SetToolTip(wx.ToolTip( + "Maximum number of scans to use in the analysis. Set to -1 to automatically determine from data")) self.ctlxaxis.SetToolTip(wx.ToolTip("Select the x-axis unit for plotting.")) self.ctlcycleindex.SetToolTip(wx.ToolTip( "The number of scans per cycle. Determined automatically if -1. " diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 7fb14a4a..75e43a95 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -280,6 +280,7 @@ def __init__(self): self.HTcycleindex = -1 self.HTxaxis = "Time" self.CDScanCompress = 3 + self.HTmaxscans = -1 self.demultiplexmode = "HT" self.demultiplexchoices = ["HT", "FT", "aFT"] self.FTstart = 5 @@ -573,6 +574,7 @@ def config_export(self, name): f.write("CDScanEnd " + str(self.CDScanEnd) + "\n") f.write("HTksmooth " + str(self.HTksmooth) + "\n") f.write("CDScanCompress " + str(self.CDScanCompress) + "\n") + f.write("HTmaxscans " + str(self.HTmaxscans) + "\n") f.write("HTtimepad " + str(self.HTtimepad) + "\n") f.write("HTanalysistime " + str(self.HTanalysistime) + "\n") f.write("HTxaxis " + str(self.HTxaxis) + "\n") @@ -723,6 +725,8 @@ def config_import(self, name): self.htbit = ud.string_to_value(line.split()[1]) if line.startswith("CDScanCompress"): self.CDScanCompress = ud.string_to_value(line.split()[1]) + if line.startswith("HTmaxscans"): + self.HTmaxscans = ud.string_to_value(line.split()[1]) if line.startswith("demultiplexmode"): self.demultiplexmode = line.split()[1] if line.startswith("FTstart"): From 8bb26655adbf3f9ea9ec9633e19b184e78fc4769 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Wed, 20 Mar 2024 16:52:18 -0700 Subject: [PATCH 27/35] Fixed bug with tab mode switching in UCCD --- unidec/UniDecCD.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unidec/UniDecCD.py b/unidec/UniDecCD.py index ab6dc7c5..bf852f1a 100644 --- a/unidec/UniDecCD.py +++ b/unidec/UniDecCD.py @@ -405,6 +405,7 @@ def on_exe_mode(self, exemode=True): self.eng.exe_mode(exemode) def remake_mainwindow(self, tabbed=None): + htmode = self.view.htmode iconfile = self.view.icon_path # evt=EventManager() # print evt.GetStats() @@ -412,7 +413,7 @@ def remake_mainwindow(self, tabbed=None): self.view.on_exit() self.view = [] self.view = CDWindow.CDMainwindow(self, "UCD: UniDec for Charge Detection-Mass Spectrometry", self.eng.config, - iconfile=iconfile, tabbed=tabbed) + iconfile=iconfile, tabbed=tabbed, htmode=htmode) self.view.Show() self.view.import_config_to_gui() From 8e41b4edd5a77088a8a4bff8f49610c73af8b42e Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 4 Apr 2024 17:26:55 -0700 Subject: [PATCH 28/35] Blocked mousewheel events on ComboBoxes and Choices to prevent accidental changes when scrolling. Mass, Charge, and Mz extraction on EICs for UCCD. --- unidec/UniChromCD.py | 73 ++++++++++++---- .../GroEL_CDMS_1_conf.dat | 22 +++-- .../metaunidec/gui_elements/ud_cont_meta.py | 8 ++ unidec/modules/CDEng.py | 42 +++++++--- unidec/modules/HTEng.py | 84 ++++++++++++++++++- unidec/modules/gui_elements/CD_controls.py | 6 ++ unidec/modules/gui_elements/ud_controls.py | 10 ++- unidec/modules/unidecstructure.py | 1 + 8 files changed, 207 insertions(+), 39 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 77045f96..7426c317 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -8,7 +8,7 @@ import numpy as np from unidec.modules import AutocorrWindow from unidec.modules.unidecstructure import ChromatogramContainer -import os, time +import os, time, platform class UniChromCDApp(UniDecCDApp): @@ -55,15 +55,18 @@ def init(self, *args, **kwargs): # self.on_dataprep_button(0) # self.on_auto(0) - if True: # and platform.node() == 'DESKTOP-08TGCJO': + if True and platform.node() == 'MTM-VOSTRO': print("Opening Test File") path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") path2 = "Z:\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-03-06 Bgal 5bit triplicate\\20240306 Bgal inj5 iit1 bit5 zp10.dmt" pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" - path3 = "Z:\\Group Share\\ONO\\CDMS\\5 bit HT data\\20240313 JDS Bgal 0o1g_l inj1o5 quad9k bit-5 zp10 i2.dmt" - - self.on_open_file(None, None, path=path3) + # path3 = "Z:\\Group Share\\ONO\\CDMS\\5 bit HT data\\20240313 JDS Bgal 0o1g_l inj1o5 quad9k bit-5 zp10 i2.dmt" + path4 = "Z:\\Group Share\\Skippy\\Projects\\HT\\2024-04-01\\20240401 ribos s6Col bit3 inj5s 1_2024-04-01-04-19-29.dmt" + # try: + self.on_open_file(None, None, path=path2) + # except: + # pass # self.eng.process_data_scans() # self.make_cube_plot() # self.make_mass_time_2dplot() @@ -196,7 +199,7 @@ def on_select_massz_range(self, e=None): xlimits = np.array(xlimits) * self.view.plot5.kdnorm print("New Mass Limits", xlimits) self.view.plot5.reset_zoom() - color = get_color_from_index(len(self.cc.chromatograms)) + color = ud.get_color_from_index(len(self.cc.chromatograms)) if self.eng.ccsstack_ht is not None: self.add_mass_eic(xlimits, ylimits, color=color, plot=True, ccs=True) @@ -236,7 +239,7 @@ def on_run_eic_ccs(self, e=None): c.chromdat = eicdata # eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) eicdata, avgz, avgmz = self.eng.convert_trace_to_ccs(c.decondat, mzrange=c.mzrange, zrange=c.zrange, - normalize=self.eng.config.datanorm) + normalize=self.eng.config.datanorm) c.ccsdat = eicdata c.label = "Avg. m/z: " + str(round(avgmz)) + " z: " + str(round(avgz)) self.plot_chromatograms() @@ -300,7 +303,7 @@ def run_eic_ht(self, mzrange, zrange, color='b'): try: ccsdata, avgz, avgmz = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, - normalize=self.eng.config.datanorm) + normalize=self.eng.config.datanorm) except: print("Failed to convert to CCS") ccsdata = None @@ -349,11 +352,11 @@ def plot_chromatograms(self, e=None, save=True): xlab = "Time" if not self.view.plottic.flag: - self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1]-sep, config=self.eng.config, + self.view.plottic.plotrefreshtop(xdat, c.chromdat[:, 1] - sep, config=self.eng.config, zoomout=True, label=c.label, xlabel=xlab, color=c.color, nopaint=True) else: - self.view.plottic.plotadd(xdat, c.chromdat[:, 1]-sep, colval=c.color, nopaint=True, + self.view.plottic.plotadd(xdat, c.chromdat[:, 1] - sep, colval=c.color, nopaint=True, newlabel=c.label) if c.decondat is not None and not self.showccs: @@ -365,11 +368,11 @@ def plot_chromatograms(self, e=None, save=True): xlab = "Time" if not self.view.plotdecontic.flag: - self.view.plotdecontic.plotrefreshtop(xdat, c.decondat[:, 1]-sep, config=self.eng.config, + self.view.plotdecontic.plotrefreshtop(xdat, c.decondat[:, 1] - sep, config=self.eng.config, zoomout=True, label=c.label, xlabel=xlab, color=c.color, nopaint=True) else: - self.view.plotdecontic.plotadd(xdat, c.decondat[:, 1]-sep, colval=c.color, nopaint=True, + self.view.plotdecontic.plotadd(xdat, c.decondat[:, 1] - sep, colval=c.color, nopaint=True, newlabel=c.label) if c.ccsdat is not None and self.showccs: @@ -381,11 +384,11 @@ def plot_chromatograms(self, e=None, save=True): xlab = "CCS (\u212B\u00B2)" if not self.view.plotdecontic.flag: - self.view.plotdecontic.plotrefreshtop(xdat, c.ccsdat[:, 1]-sep, config=self.eng.config, + self.view.plotdecontic.plotrefreshtop(xdat, c.ccsdat[:, 1] - sep, config=self.eng.config, zoomout=True, label=c.label, xlabel=xlab, color=c.color, nopaint=True) else: - self.view.plotdecontic.plotadd(xdat, c.ccsdat[:, 1]-sep, colval=c.color, nopaint=True, + self.view.plotdecontic.plotadd(xdat, c.ccsdat[:, 1] - sep, colval=c.color, nopaint=True, newlabel=c.label) number += 1 @@ -402,6 +405,8 @@ def plot_chromatograms(self, e=None, save=True): if save: self.save_chroms() + self.plot_mass_for_eic() + def save_chroms(self): """ Save the chromatograms to a text file. @@ -464,6 +469,42 @@ def on_load_chroms(self, e=None): self.load_chroms(fname=fname) dlg.Destroy() + def plot_mass_for_eic(self): + """ + Plot the mass for the EICs. + :return: None + """ + for c in self.cc.chromatograms: + if "TIC" not in c.label: + mzrange = c.mzrange + zrange = c.zrange + newd = self.eng.extract_subdata(mzrange, zrange) + c.dataobj = newd + massdat = newd.massdat + if not self.view.plot2.flag: + self.view.plot2.plotrefreshtop(massdat[:, 0], massdat[:, 1], config=self.eng.config, zoomout=True, + label=c.label, xlabel="Mass (Da)", color=c.color, nopaint=True) + else: + self.view.plot2.plotadd(massdat[:, 0], massdat[:, 1], colval=c.color, nopaint=True, + newlabel=c.label) + + zdat = newd.zdat + if not self.view.plot3.flag: + self.view.plot3.plotrefreshtop(zdat[:, 0], zdat[:, 1], config=self.eng.config, zoomout=True, + label=c.label, xlabel="Charge", color=c.color, nopaint=True) + else: + self.view.plot3.plotadd(zdat[:, 0], zdat[:, 1], colval=c.color, nopaint=True, newlabel=c.label) + + data2 = newd.data2 + if not self.view.plot4.flag: + self.view.plot4.plotrefreshtop(data2[:, 0], data2[:, 1], config=self.eng.config, zoomout=True, + label=c.label, xlabel="Time", color=c.color, nopaint=True) + else: + self.view.plot4.plotadd(data2[:, 0], data2[:, 1], colval=c.color, nopaint=True, newlabel=c.label) + self.view.plot2.repaint() + self.view.plot3.repaint() + self.view.plot4.repaint() + def on_ignore_repopulate(self, e=None): """ Event triggered by updates to the chromatogram list. Repopulate the list based on adjusted ignore flags. @@ -481,11 +522,13 @@ def on_select_time_range(self, e=None): :return: None """ if not wx.GetKeyState(wx.WXK_CONTROL): - self.plot_chromatograms() xlimits = self.view.plottic.subplot1.get_xlim() print("New limits:", xlimits) self.view.plottic.reset_zoom() ylimits = self.view.plottic.subplot1.get_ylim() + ylimits = np.array(ylimits) + ylimits[0] = 0 + self.plot_chromatograms() # Plot Red box on plottic self.view.plottic.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], edgecolor="red", facecolor="red", nopaint=False) diff --git a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat index a1eb5998..358076cb 100644 --- a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat +++ b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat @@ -1,4 +1,4 @@ -version 6.0.5 +version 7.0.0b imflag 0 cdmsflag 1 input C:\Python\UniDec3\unidec\bin\Example Data\CDMS\GroEL_CDMS_1_unidecfiles\GroEL_CDMS_1_input.dat @@ -61,16 +61,24 @@ kernel CDslope 0.2074 CDzbins 1.0 CDres 1.0 -CDScanStart -CDScanEnd +CDScanStart -1.0 +CDScanEnd -1.0 HTksmooth 0.0 -CDScanCompresse 0 +CDScanCompress 0.0 +HTmaxscans -1 HTtimepad 0.0 HTanalysistime 38.0 HTxaxis Time -HTcycleindex -1 -HTtimepad 0.0 -htbit 0 +HTcycleindex -1.0 +HTcylctime 2.0 +HTtimeshift 7.0 +htbit 0.0 +FTstart 5 +FTend 1000 +FTflatten 1 +FTapodize 1 +demultiplexmode HT +FTsmooth 0 csig 4.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/metaunidec/gui_elements/ud_cont_meta.py b/unidec/metaunidec/gui_elements/ud_cont_meta.py index c67c6239..8ac444dc 100644 --- a/unidec/metaunidec/gui_elements/ud_cont_meta.py +++ b/unidec/metaunidec/gui_elements/ud_cont_meta.py @@ -297,6 +297,7 @@ def __init__(self, parent, config, pres, panel, iconfile): # self.ctlisotopemode = wx.CheckBox(panel2b, label="Isotope Mode") self.ctlisotopemode = wx.Choice(panel2b, -1, size=(100, -1), choices=self.config.isotopechoices) + self.ctlisotopemode.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) gbox2b.Add(self.ctlisotopemode, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) self.ctlmanualassign = wx.CheckBox(panel2b, label="Manual Mode") @@ -361,6 +362,7 @@ def __init__(self, parent, config, pres, panel, iconfile): self.ctlextract = wx.ComboBox(panel3, value="Height", choices=list(self.parent.extractchoices.values()), style=wx.CB_READONLY) + self.ctlextract.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.plotbutton = wx.Button(panel3, -1, "Peak Detection/Extraction") self.plotbutton2 = wx.Button(panel3, -1, "Plot 2D Grids") @@ -413,6 +415,9 @@ def __init__(self, parent, config, pres, panel, iconfile): self.ctl2dcm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) self.ctlpeakcm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) self.ctlspeccm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) + self.ctl2dcm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) + self.ctlpeakcm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) + self.ctlspeccm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.parent.Bind(wx.EVT_COMBOBOX, self.on_spectra_color_change, self.ctlspeccm) # for mp in self.config.cmaps2: @@ -1050,6 +1055,9 @@ def bind_changes(self, e=None): self.parent.Bind(wx.EVT_TEXT, self.update_quick_controls, self.ctlbeta) self.parent.Bind(wx.EVT_TEXT, self.update_quick_controls, self.ctlpsig) + def on_mousewheel(self, e): + pass + def update_quick_controls(self, e=None): if self.update_flag: # Z Box diff --git a/unidec/modules/CDEng.py b/unidec/modules/CDEng.py index e93f3657..f6442403 100644 --- a/unidec/modules/CDEng.py +++ b/unidec/modules/CDEng.py @@ -617,7 +617,7 @@ def histogram(self, mzbins=1, zbins=1, x=None, y=None, mzrange=None, zrange=None if np.any(zrange < 0): zrange = np.abs(zrange) else: - zrange = [np.floor(np.amin(y)), np.ceil(np.amax(y))+1] + zrange = np.array([np.floor(np.amin(y)), np.ceil(np.amax(y))]) mzaxis = np.arange(mzrange[0] - mzbins / 2., mzrange[1] + mzbins / 2, mzbins) # Weird fix to make this axis even is necessary for CuPy fft for some reason... @@ -761,14 +761,16 @@ def hist_nativeZ_filter(self, nativeZrange=None): # Set values outside range to 0 self.harray[boo3] = 0 - def create_mass_axis(self, harray=None): + def create_mass_axis(self, harray=None, mass=None): if harray is None: harray = self.harray + if mass is None: + mass = self.mass # filter out zeros harray = np.array(harray) boo1 = harray > 0 - mass = self.mass[boo1] + mass = mass[boo1] # Test for if array is empty and error if so if ud.isempty(mass): @@ -781,28 +783,37 @@ def create_mass_axis(self, harray=None): massaxis = np.arange(minval, maxval, self.config.massbins) return massaxis - def transform(self, harray=None, dataobj=None): + def transform(self, harray=None, dataobj=None, ztab=None, mass=None, mz=None): if harray is None: harray = self.harray if dataobj is None: dataobj = self.data + flag = True + else: + flag = False + if ztab is None: + ztab = self.ztab + if mass is None: + mass = self.mass + if mz is None: + mz = self.mz # Test for if array is empty and error if so if len(harray) == 0: print("ERROR: Empty histogram array on transform") return 0 - massaxis = self.create_mass_axis(harray) + massaxis = self.create_mass_axis(harray, mass=mass) # Create the mass grid dataobj.massgrid = [] - for i in range(len(self.ztab)): + for i in range(len(ztab)): d = harray[i] if self.config.poolflag == 1: - newdata = np.transpose([self.mass[i], d]) + newdata = np.atleast_2d(np.transpose([mass[i], d])) massdata = ud.linterpolate(newdata, massaxis) else: boo1 = d > 0 - newdata = np.transpose([self.mass[i][boo1], d[boo1]]) + newdata = np.transpose([mass[i][boo1], d[boo1]]) if len(newdata) < 2: massdata = np.transpose([massaxis, massaxis * 0]) else: @@ -816,11 +827,18 @@ def transform(self, harray=None, dataobj=None): # Ravel the massgrid to make the format match unidec dataobj.massgrid = np.ravel(dataobj.massgrid) # Create the data2 and mzgrid objects from the histogram array for compatiblity with unidec functions - dataobj.data2 = np.transpose([self.mz, np.sum(harray, axis=0)]) - dataobj.zdat = np.transpose([self.ztab, np.sum(harray, axis=1)]) + dataobj.data2 = np.transpose([mz, np.sum(harray, axis=0)]) + dataobj.zdat = np.transpose([ztab, np.sum(harray, axis=1)]) + if self.X.shape != harray.shape: + X, Y = np.meshgrid(mz, ztab, indexing='xy') + else: + X = self.X + Y = self.Y + dataobj.mzgrid = np.transpose( - [np.ravel(self.X.transpose()), np.ravel(self.Y.transpose()), np.ravel(harray.transpose())]) - self.massaxis = massaxis + [np.ravel(X.transpose()), np.ravel(Y.transpose()), np.ravel(harray.transpose())]) + if flag: + self.massaxis = massaxis return dataobj def transform_mzmass(self): diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index d31d0f11..4b07cdfe 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -8,7 +8,7 @@ from unidec.modules.ChromEng import * from unidec.modules.IM_functions import calc_linear_ccs, calc_linear_ccsconst import unidec.tools as ud -# from modules.unidecstructure import DataContainer +from unidec.modules.unidecstructure import UniDecConfig import matplotlib.pyplot as plt import scipy.fft as fft @@ -32,6 +32,8 @@ def __init__(self, *args, **kwargs): """ super().__init__(*args, **kwargs) + if not hasattr(self, "config"): + self.config = UniDecConfig() self.config.htmode = True # Empty Arrays @@ -138,7 +140,7 @@ def parse_file_name(self, path): print("Cycle Time:", self.config.HTcycletime, "Time Pad:", self.config.HTtimepad, "HT Sequence:", self.config.htseq) - def setup_ht(self, cycleindex=None): + def setup_ht(self, cycleindex=None, seq=None): """ Sets up the HT kernel for deconvolution. This is a binary sequence that is convolved with the data to deconvolve the data. The sequence is defined by the htseq variable. The timepad and timeshift variables @@ -147,7 +149,10 @@ def setup_ht(self, cycleindex=None): If not specified, default is the full number of scans divided by the number of cycles. :return: None """ - self.config.htseq = ud.HTseqDict[str(self.config.htbit)] + if seq is None: + self.config.htseq = ud.HTseqDict[str(int(self.config.htbit))] + else: + self.config.htseq = seq # Finds the number of scans that are padded if self.config.HTtimepad > 0: self.padindex = self.set_timepad_index(self.config.HTtimepad) @@ -166,15 +171,22 @@ def setup_ht(self, cycleindex=None): # Convert sequence to array seqarray = np.array([int(s) for s in self.config.htseq]) + seqarray = seqarray * 2 - 1 + # For any less than -1, set equal to 0 + seqarray[seqarray < -1] = 0 + shortkernel = seqarray + print(shortkernel) # Roll short kernel so that the first 1 is in index 0 self.kernelroll = -np.argmax(shortkernel) shortkernel = np.roll(shortkernel, self.kernelroll) scaledkernel = np.arange(len(shortkernel)) / (len(shortkernel)) + # print("Scaled Kernel:", scaledkernel) kernelscans = scaledkernel * (np.amax(self.fullscans) - self.padindex) + # print("Kernel Scans:", kernelscans) if cycleindex is None: if self.config.HTcycleindex <= 0: @@ -190,6 +202,7 @@ def setup_ht(self, cycleindex=None): self.cycleindex = int(cycleindex) print("Correction Diff:", diff) + print("Cycle Index:", self.cycleindex) self.rollindex = int(-self.shiftindex + self.cycleindex * self.kernelroll) # Create the HT kernel @@ -304,6 +317,39 @@ def htdecon(self, data, **kwargs): # Return demultiplexed data return output, data + def masked_demultiplex_ht(self, data, win, n, mode="con", **kwargs): + self.config.htseq = ud.HTseqDict[str(int(self.config.htbit))] + htseq = np.array([int(s) for s in self.config.htseq]) + outdata = [] + for i in range(n): + seq = np.array(htseq).astype(int) + # Alter HT seq + if mode == "con": + starti = np.random.randint(len(seq)) + endi = starti + win + if endi > len(seq): + seq[starti:] = -1 + seq[:endi % len(seq)] = -1 + else: + seq[starti:endi] = -1 + elif mode == "rand": + seq[np.random.randint(len(seq), size=win)] = -1 + elif mode == "alt": + starti = np.random.randint(len(seq)) + endi = starti + win * 2 + if endi > len(seq): + seq[starti::2] = -1 + seq[:endi % len(seq):2] = -1 + else: + seq[starti:endi:2] = -1 + # Set window to -1 + self.setup_ht(seq=seq) + out, _ = self.htdecon(data, **kwargs) + outdata.append(out) + # plt.plot(outdata[-1]) + avg = np.mean(outdata, axis=0) + return outdata, avg + def setup_ft(self, FTstart=None, FTend=None, nzp=None): """ Set up the Fourier Transform Deconvolution. Sets self.decontime with drift time axis. @@ -618,6 +664,7 @@ def open_file(self, path, refresh=False): :param refresh: Whether to refresh the data. Default False. :return: None """ + self.clear_arrays() self.open_cdms_file(path, refresh=refresh) self.parse_file_name(path) @@ -816,7 +863,7 @@ def create_chrom(self, farray, **kwargs): # Normalize if "normalize" in kwargs: if kwargs["normalize"]: - fulleic /= np.amax(fulleic) + fulleic = fulleic / np.amax(fulleic) else: fulleic /= np.amax(fulleic) return np.transpose([self.fulltime, fulleic]) @@ -873,6 +920,35 @@ def get_eic(self, mzrange, zrange, **kwargs): eic = self.create_chrom(farray2, **kwargs) return eic + def extract_subdata(self, mzrange, zrange): + b1 = self.mz >= mzrange[0] + b2 = self.mz <= mzrange[1] + b3 = self.ztab >= zrange[0] + b4 = self.ztab <= zrange[1] + b = np.logical_and(b1, b2) + b2 = np.logical_and(b3, b4) + + b3 = np.outer(b2, b) + newd = deepcopy(self.data) + '''newh = np.atleast_2d(self.harray[:, b][b2]) + + + ztab = np.atleast_1d(self.ztab[b2]) + mz = np.atleast_1d(self.mz[b]) + mass = np.atleast_2d(self.mass[:, b][b2]) + + if len(ztab) != newh.shape[0]: + newh = newh.transpose() + mass = mass.transpose()''' + + newh2 = self.harray * b3 + + if np.sum(newh2) != 0: + newd = self.transform(newh2, newd) + # newd = self.transform(newh, newd, ztab=ztab, mz=mz, mass=mass) + + return newd + def eic_ht(self, mzrange, zrange, **kwargs): """ Get the EIC and run HT on it. diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index df715230..4b3056b8 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -748,8 +748,10 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): i = 0 self.ctl2dcm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) + self.ctl2dcm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.ctlpeakcm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) + self.ctlpeakcm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.ctl2dcm.AppendItems(self.config.cmaps2) self.ctlpeakcm.AppendItems(self.config.cmaps) @@ -1444,6 +1446,10 @@ def bind_changes(self, e=None): ''' + def on_mousewheel(self, e): + #print("Wee") + pass + def update_demultiplex_mode(self, e=None): demultiplexmode = self.ctlmultiplexmode.GetStringSelection() if "HT" in demultiplexmode: diff --git a/unidec/modules/gui_elements/ud_controls.py b/unidec/modules/gui_elements/ud_controls.py index 3c946e00..404ed797 100644 --- a/unidec/modules/gui_elements/ud_controls.py +++ b/unidec/modules/gui_elements/ud_controls.py @@ -181,6 +181,7 @@ def __init__(self, parent, config, pres, panel, iconfile): i += 1 self.subtypectl = wx.Choice(panel1b, -1, choices=self.backgroundchoices) + self.subtypectl.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.ctlbuff = wx.TextCtrl(panel1b, value="", size=size1) self.subtypectl.SetSelection(2) gbox1b.Add(self.subtypectl, (i, 0)) @@ -229,6 +230,7 @@ def __init__(self, parent, config, pres, panel, iconfile): choices=["Linear m/z (Constant " + '\N{GREEK CAPITAL LETTER DELTA}' + "m/z)", "Linear resolution (Constant (m/z)/(" + '\N{GREEK CAPITAL LETTER DELTA}' + "m/z))", "Nonlinear", "Linear Interpolated", "Linear Resolution Interpolated"]) + self.ctlbintype.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) gbox1b.Add(self.ctlbintype, (i, 0), span=(1, 2)) i += 1 else: @@ -323,6 +325,7 @@ def __init__(self, parent, config, pres, panel, iconfile): i += 1 self.ctltwavecaltype = wx.Choice(panel1c, -1, choices=list(self.config.twavedict.values())) + self.ctltwavecaltype.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) gbox1c.Add(self.ctltwavecaltype, (i, 1), span=(1, 1)) gbox1c.Add(wx.StaticText(panel1c, label="Calibration Type: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) i += 1 @@ -516,6 +519,7 @@ def __init__(self, parent, config, pres, panel, iconfile): if self.config.imflag == 0: # self.ctlisotopemode = wx.CheckBox(panel2b, label="Isotope Mode") self.ctlisotopemode = wx.Choice(panel2b, -1, size=(100, -1), choices=self.config.isotopechoices) + self.ctlisotopemode.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) gbox2b.Add(self.ctlisotopemode, (i, 1), flag=wx.ALIGN_CENTER_VERTICAL) self.ctlorbimode = wx.CheckBox(panel2b, label="Charge Scaling") @@ -607,10 +611,11 @@ def __init__(self, parent, config, pres, panel, iconfile): i = 0 - self.ctl2dcm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) + self.ctl2dcm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.ctlpeakcm = wx.ComboBox(panel3b, wx.ID_ANY, style=wx.CB_READONLY) + self.ctlpeakcm.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) # for mp in self.config.cmaps2: # self.ctl2dcm.Append(mp) @@ -1380,6 +1385,9 @@ def bind_changes(self, e=None): self.parent.Bind(wx.EVT_TEXT, self.update_quick_controls, self.ctlbeta) self.parent.Bind(wx.EVT_TEXT, self.update_quick_controls, self.ctlpsig) + def on_mousewheel(self, e): + pass + def update_quick_controls(self, e=None): if self.update_flag: # Background Subtraction diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 75e43a95..207dedbc 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -1610,6 +1610,7 @@ def __init__(self): self.ht = False self.massrange = [-1, -1] self.massmode = False + self.dataobj = None def to_row(self): out = [self.label, str(self.color), str(self.index), str(self.mzrange[0]), str(self.mzrange[1]), From 06089b0ee9bf39f973bb691c4ef1f24d482ec9f3 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Wed, 10 Apr 2024 16:48:15 -0700 Subject: [PATCH 29/35] Added masked HT mode to UCCD. Fixed some UPP bugs. --- readme.md | 4 ++++ unidec/UniChromCD.py | 4 ++-- unidec/batch.py | 6 +++++ .../GroEL_CDMS_1_conf.dat | 12 +++++----- unidec/engine.py | 12 ++++++++-- unidec/modules/HTEng.py | 24 ++++++++++++++++--- unidec/modules/gui_elements/CD_controls.py | 18 ++++++++++++++ unidec/modules/matchtools.py | 13 ++++++++++ unidec/modules/unidec_enginebase.py | 8 +++---- unidec/modules/unidecstructure.py | 10 +++++++- unidec/tools.py | 6 ++++- 11 files changed, 98 insertions(+), 19 deletions(-) diff --git a/readme.md b/readme.md index 1b1a5e07..52ff491e 100644 --- a/readme.md +++ b/readme.md @@ -199,6 +199,10 @@ v.7.0.0 Added new module for time domain CD-MS data analysis, UniChromCD. This includes some cool new plotting and analysis features. Most importantly, it also includes our new method for Hadamard Transform analysis of CD-MS spectra. You can use the tools for regular LC-CD-MS or go crazy with HT-LC-CD-MS. More info coming soon on this front...:) +UPP added NumPeaks output on all to help with catching bad files in the results. Also, fixed bugs to allow files with bad data to proceed relatively unscathed. It should be more error tolerant now for large runs. + +Blocked mouse wheel events on drop down selections. This should fix a lot of accidental switches to parameters. + Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. Fixed execution bug on servers. v.6.0.5 diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 7426c317..0718832a 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -1,6 +1,6 @@ -from UniDecCD import UniDecCDApp +from unidec.UniDecCD import UniDecCDApp import multiprocessing -import modules.HTEng as HTEng +import unidec.modules.HTEng as HTEng from unidec.modules.gui_elements import CDWindow from pubsub import pub import wx diff --git a/unidec/batch.py b/unidec/batch.py index bb6211f1..195dc5f7 100644 --- a/unidec/batch.py +++ b/unidec/batch.py @@ -463,6 +463,7 @@ def run_df(self, df=None, decon=True, use_converted=True, interactive=False, wri # Set up the lists for the results htmlfiles = [] + npeaks = [] # Check if the "Correct" column is in the DataFrame to start correct pair mode self.correct_pair_mode = check_for_word_in_keys(self.rundf, "Correct") @@ -537,6 +538,8 @@ def run_df(self, df=None, decon=True, use_converted=True, interactive=False, wri print("Error in integrating", err) self.integrate = False + npeaks.append(len(self.eng.pks.peaks)) + results_string = None # The First Recipe, correct pair mode @@ -592,6 +595,9 @@ def run_df(self, df=None, decon=True, use_converted=True, interactive=False, wri print("File not found:", path) htmlfiles.append("") + # Write the number of peaks IDed + self.rundf["NumPeaks"] = npeaks + # Set the HTML file names in the DataFrame self.rundf["Reports"] = htmlfiles diff --git a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat index 358076cb..cad2bb39 100644 --- a/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat +++ b/unidec/bin/Example Data/CDMS/GroEL_CDMS_1_unidecfiles/GroEL_CDMS_1_conf.dat @@ -61,11 +61,11 @@ kernel CDslope 0.2074 CDzbins 1.0 CDres 1.0 -CDScanStart -1.0 -CDScanEnd -1.0 +CDScanStart +CDScanEnd HTksmooth 0.0 CDScanCompress 0.0 -HTmaxscans -1 +HTmaxscans -1.0 HTtimepad 0.0 HTanalysistime 38.0 HTxaxis Time @@ -73,12 +73,12 @@ HTcycleindex -1.0 HTcylctime 2.0 HTtimeshift 7.0 htbit 0.0 -FTstart 5 -FTend 1000 +FTstart 5.0 +FTend 1000.0 FTflatten 1 FTapodize 1 demultiplexmode HT -FTsmooth 0 +FTsmooth 0.0 csig 4.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/engine.py b/unidec/engine.py index c970327e..a28173c3 100644 --- a/unidec/engine.py +++ b/unidec/engine.py @@ -784,8 +784,12 @@ def export_params(self, e=None, silent=False): peakparams = [] for i in range(0, len(peaks)): - avg = np.average(self.data.ztab, weights=mztab[i, :, 1]) - std = np.sqrt(np.average((np.array(self.data.ztab) - avg) ** 2, weights=mztab[i, :, 1])) + try: + avg = np.average(self.data.ztab, weights=mztab[i, :, 1]) + std = np.sqrt(np.average((np.array(self.data.ztab) - avg) ** 2, weights=mztab[i, :, 1])) + except: + avg = -1 + std = -1 p = self.pks.peaks[i] if e == "PostFit": peakparams.append( @@ -992,6 +996,10 @@ def normalize_peaks(self): 2 = Normalize the sum to 1 :return: None """ + if len(self.pks.peaks) == 0: + print("No Peaks Detected") + return + integrals = np.array([p.integral for p in self.pks.peaks]) heights = np.array([p.height for p in self.pks.peaks]) # corrints = np.array([p.corrint for p in self.pks.peaks]) diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 4b07cdfe..93f823de 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -256,6 +256,8 @@ def run_demultiplex(self, data, mode=None, **kwargs): if mode == "HT": return self.htdecon(data, **kwargs) + elif mode == "mHT": + return self.masked_demultiplex_ht(data, **kwargs) elif mode == "FT": return self.ftdecon(data, aFT=False, **kwargs) elif mode == "aFT": @@ -317,9 +319,20 @@ def htdecon(self, data, **kwargs): # Return demultiplexed data return output, data - def masked_demultiplex_ht(self, data, win, n, mode="con", **kwargs): + def masked_demultiplex_ht(self, data, win=None, n=None, demult=None, mode="rand", **kwargs): + + if win is None: + win = self.config.HTwin + if n is None: + n = self.config.HTmaskn + win = int(win) + n = int(n) + self.config.htseq = ud.HTseqDict[str(int(self.config.htbit))] htseq = np.array([int(s) for s in self.config.htseq]) + if demult is None: + self.setup_ht(seq=htseq) + demult, fulltic = self.htdecon(data, **kwargs) outdata = [] for i in range(n): seq = np.array(htseq).astype(int) @@ -347,8 +360,13 @@ def masked_demultiplex_ht(self, data, win, n, mode="con", **kwargs): out, _ = self.htdecon(data, **kwargs) outdata.append(out) # plt.plot(outdata[-1]) - avg = np.mean(outdata, axis=0) - return outdata, avg + outdata = np.array(outdata) + + # Calculate number of sign changes per data point + negcount = np.sum(outdata < 0, axis=0) + 1 + # Divide the demultiplexed data by the negcount plus 1 + wavg = ud.safedivide(demult, negcount.astype(float)) + return wavg, fulltic def setup_ft(self, FTstart=None, FTend=None, nzp=None): """ diff --git a/unidec/modules/gui_elements/CD_controls.py b/unidec/modules/gui_elements/CD_controls.py index 4b3056b8..7788043c 100644 --- a/unidec/modules/gui_elements/CD_controls.py +++ b/unidec/modules/gui_elements/CD_controls.py @@ -434,6 +434,20 @@ def __init__(self, parent, config, pres, panel, iconfile, htmode=False): flag=wx.ALIGN_CENTER_VERTICAL) i += 1 + # Text control for HTmaskn + self.ctlhtmaskn = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlhtmaskn, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="mHT Mask N: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + + # Text control for HTwin + self.ctlhtwin = wx.TextCtrl(panelht, value="", size=size1) + sizercontrolht1.Add(self.ctlhtwin, (i, 1), span=(1, 1)) + sizercontrolht1.Add(wx.StaticText(panelht, label="mHT Window: "), (i, 0), + flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + panelht.SetSizer(sizercontrolht1) sizercontrolht1.Fit(panelht) @@ -960,6 +974,8 @@ def import_config_to_gui(self): self.ctlanalysistime.SetValue(str(self.config.HTanalysistime)) self.ctlmaxscans.SetValue(str(self.config.HTmaxscans)) self.ctlcycleindex.SetValue(str(self.config.HTcycleindex)) + self.ctlhtmaskn.SetValue(str(self.config.HTmaskn)) + self.ctlhtwin.SetValue(str(self.config.HTwin)) self.ctlxaxis.SetStringSelection(self.config.HTxaxis) self.ctlmultiplexmode.SetStringSelection(self.config.demultiplexmode) self.ctlftstart.SetValue(str(self.config.FTstart)) @@ -1090,6 +1106,8 @@ def export_gui_to_config(self, e=None): self.config.HTanalysistime = ud.string_to_value(self.ctlanalysistime.GetValue()) self.config.HTmaxscans = ud.string_to_value(self.ctlmaxscans.GetValue()) self.config.HTcycleindex = ud.string_to_value(self.ctlcycleindex.GetValue()) + self.config.HTmaskn = ud.string_to_value(self.ctlhtmaskn.GetValue()) + self.config.HTwin = ud.string_to_value(self.ctlhtwin.GetValue()) self.config.demultiplexmode = self.ctlmultiplexmode.GetStringSelection() self.config.FTstart = ud.string_to_value(self.ctlftstart.GetValue()) self.config.FTend = ud.string_to_value(self.ctlftend.GetValue()) diff --git a/unidec/modules/matchtools.py b/unidec/modules/matchtools.py index 43033aae..9057f9fb 100644 --- a/unidec/modules/matchtools.py +++ b/unidec/modules/matchtools.py @@ -529,6 +529,10 @@ def UPP_check_peaks(row, pks, tol, vmoddf=None, fmoddf=None, favor="Closest", in incorrectint = 0 unmatchedint = 0 ignoreheight = 0 + ncorr = 0 + nincorr = 0 + nunmatched = 0 + nignore = 0 totalint = np.sum(peakheights) matches = [] matchstring = "" @@ -605,17 +609,20 @@ def UPP_check_peaks(row, pks, tol, vmoddf=None, fmoddf=None, favor="Closest", in # print("Correct", peakmass, label) correctint += peakheights[i] p.color = [0, 1, 0] # Green + ncorr += 1 elif "Ignore" in rowkey: # print("Ignore", peakmass, label) p.color = [0, 0, 1] # Blue totalint -= peakheights[i] # Remove this from the total intensity] ignoreheight += peakheights[i] + nignore += 1 elif "Incorrect" in rowkey: # print("Incorrect", peakmass, label) incorrectint += peakheights[i] p.color = [1, 0, 0] # Red + nincorr += 1 else: print("Error: Something went wrong with row name parsing", rowkey) @@ -633,6 +640,7 @@ def UPP_check_peaks(row, pks, tol, vmoddf=None, fmoddf=None, favor="Closest", in p.color = [1, 1, 0] # Yellow p.match = 0 p.matcherror = 0 + nunmatched += 1 p.label = label for i, label in enumerate(plabels): @@ -671,6 +679,11 @@ def UPP_check_peaks(row, pks, tol, vmoddf=None, fmoddf=None, favor="Closest", in row = calc_bispecific_correct(row) + row["NumCorr"] = ncorr + row["NumIncorr"] = nincorr + row["NumUnmatched"] = nunmatched + row["NumIgnored"] = nignore + return row diff --git a/unidec/modules/unidec_enginebase.py b/unidec/modules/unidec_enginebase.py index 2c646a15..c92b1a23 100644 --- a/unidec/modules/unidec_enginebase.py +++ b/unidec/modules/unidec_enginebase.py @@ -541,7 +541,7 @@ def makeplot2(self, plot=None, data=None, pks=None, config=None, silent=False): pks = self.pks if config is None: config = self.config - if config.batchflag == 0 and data.shape[1] == 2 and len(data) >= 2: + if config.batchflag == 0 and len(data.shape) > 1 and len(data) >= 2: tstart = time.perf_counter() plot.plotrefreshtop(data[:, 0], data[:, 1], "Zero-charge Mass Spectrum", "Mass (Da)", @@ -555,7 +555,7 @@ def makeplot2(self, plot=None, data=None, pks=None, config=None, silent=False): tend = time.perf_counter() if not silent: print("Plot 2: %.2gs" % (tend - tstart)) - if data.shape[1] != 2 or len(data) < 2: + if len(data.shape) < 2 or len(data) < 2: print("Data Too Small. Adjust parameters.", data) return plot @@ -565,7 +565,7 @@ def makeplot4(self, plot=None, data=None, pks=None, config=None, silent=False): Will plot dots at peak positions. If possible, will plot full isolated spectra. :param plot: Plot object. Default is None, which will set plot to creating a new plot - :param data: 2D data with mass in the first column and intensity in the second. Default is self.data.massdat + :param data: 2D data with m/z in the first column and intensity in the second. Default is self.data.data2 :param pks: Peaks object. Default is self.pks :param config: Config objet. Default is self.config :return plot: The Plot object @@ -640,7 +640,7 @@ def gen_html_report(self, event=None, outfile=None, plots=None, interactive=Fals peaks_df = self.pks.to_df() colors = [p.color for p in self.pks.peaks] - if del_columns is not None: + if del_columns is not None and len(peaks_df) != 0: peaks_df = peaks_df.drop(del_columns, axis=1) if len(peaks_df) > 0: diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 207dedbc..ee7bb235 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -282,12 +282,14 @@ def __init__(self): self.CDScanCompress = 3 self.HTmaxscans = -1 self.demultiplexmode = "HT" - self.demultiplexchoices = ["HT", "FT", "aFT"] + self.demultiplexchoices = ["HT", "mHT", "FT", "aFT"] self.FTstart = 5 self.FTend = 1000 self.FTflatten = True self.FTapodize = 1 self.FTsmooth = 0 + self.HTmaskn = 1000 + self.HTwin = 5 self.showlegends = True self.doubledec = False @@ -588,6 +590,8 @@ def config_export(self, name): f.write("FTapodize " + str(int(self.FTapodize)) + "\n") f.write("demultiplexmode " + str(self.demultiplexmode) + "\n") f.write("FTsmooth " + str(self.FTsmooth) + "\n") + f.write("HTmaskn " + str(self.HTmaskn) + "\n") + f.write("HTwin " + str(self.HTwin) + "\n") f.write("csig " + str(self.csig) + "\n") f.write("smoothdt " + str(self.smoothdt) + "\n") @@ -739,6 +743,10 @@ def config_import(self, name): self.FTapodize = ud.string_to_int(line.split()[1]) if line.startswith("FTsmooth"): self.FTsmooth = ud.string_to_value(line.split()[1]) + if line.startswith("HTmaskn"): + self.HTmaskn = ud.string_to_value(line.split()[1]) + if line.startswith("HTwin"): + self.HTwin = ud.string_to_value(line.split()[1]) if line.startswith("zzsig"): self.zzsig = ud.string_to_value(line.split()[1]) if line.startswith("psig"): diff --git a/unidec/tools.py b/unidec/tools.py index fd33d29e..9e27dfb3 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -1727,7 +1727,7 @@ def normalize(datatop): try: maxval = np.amax(datatop[:, 1]) datatop[:, 1] = datatop[:, 1] / maxval - except: + except Exception as e: pass return datatop @@ -1986,6 +1986,10 @@ def peakdetect(data, config=None, window=10, threshold=0, ppm=None, norm=True): peaks = [] length = len(data) + shape = np.shape(data) + if length == 0 or shape[1] != 2: + return np.array(peaks) + if norm: maxval = np.amax(data[:, 1]) else: From 365e95a6bb70623396e5792717b397f9ea8f8595 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Wed, 17 Apr 2024 16:11:06 -0700 Subject: [PATCH 30/35] Bug fixes on UCCD for CCS calc --- readme.md | 2 ++ unidec/UniChromCD.py | 5 +++-- .../Example Data/BSA_unidecfiles/BSA_conf.dat | 21 ++++++++++++++----- unidec/modules/HTEng.py | 20 +++++++++++------- unidec/modules/IM_functions.py | 2 ++ unidec/modules/gui_elements/ud_controls.py | 7 +++++++ unidec/modules/unidecstructure.py | 7 +++++++ unidec/tools.py | 1 + 8 files changed, 50 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 52ff491e..cda0858c 100644 --- a/readme.md +++ b/readme.md @@ -201,6 +201,8 @@ Added new module for time domain CD-MS data analysis, UniChromCD. This includes UPP added NumPeaks output on all to help with catching bad files in the results. Also, fixed bugs to allow files with bad data to proceed relatively unscathed. It should be more error tolerant now for large runs. +Added check box on main UniDec panel for whether to normalize the peak threshold. When on, it will behave as previously where the peak thershold is a ratio of the max intensity. When off, it will be a fixed intensity. Try switching off "Publication Mode" to view the plot with intensity axes. Note, this interacts with the Peak Normalization parameter directly above it. Max will normalize everything to 100. Total can get weird. None is the safest bet. + Blocked mouse wheel events on drop down selections. This should fix a lot of accidental switches to parameters. Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. Fixed execution bug on servers. diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 0718832a..35946ae9 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -61,10 +61,11 @@ def init(self, *args, **kwargs): "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") path2 = "Z:\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-03-06 Bgal 5bit triplicate\\20240306 Bgal inj5 iit1 bit5 zp10.dmt" pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" + pathft2 = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\CCS troubleshooting\\03082024_GDH_4kV_stepsize3_repeate15_5to500_1_2024-03-14-09-04-57.dmt" # path3 = "Z:\\Group Share\\ONO\\CDMS\\5 bit HT data\\20240313 JDS Bgal 0o1g_l inj1o5 quad9k bit-5 zp10 i2.dmt" path4 = "Z:\\Group Share\\Skippy\\Projects\\HT\\2024-04-01\\20240401 ribos s6Col bit3 inj5s 1_2024-04-01-04-19-29.dmt" # try: - self.on_open_file(None, None, path=path2) + self.on_open_file(None, None, path=pathft) # except: # pass # self.eng.process_data_scans() @@ -73,7 +74,7 @@ def init(self, *args, **kwargs): # self.run_all_ht() # self.run_all_mass_transform() # self.make_mass_cube_plot() - # self.on_run_ccs() + #self.on_run_ccs() def on_open_file(self, filename, directory, path=None, refresh=False): """ diff --git a/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat b/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat index 47ef333e..c86d159e 100644 --- a/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat +++ b/unidec/bin/Example Data/BSA_unidecfiles/BSA_conf.dat @@ -28,7 +28,8 @@ subtype 2 smooth 0.0 mzbins 0.0 peakwindow 20.0 -peakthresh 0.01 +peakthresh 0.1 +normthresh1 peakplotthresh 0.1 plotsep 0.025 intthresh 0.0 @@ -46,9 +47,9 @@ linflag 2 cmap nipy_spectral peakcmap rainbow spectracmap rainbow -publicationmode 1 +publicationmode 0 isotopemode 0 -peaknorm 2 +peaknorm 1 datanorm 1 baselineflag 1.0 orbimode 0 @@ -64,13 +65,23 @@ CDres 1.0 CDScanStart -1 CDScanEnd -1 HTksmooth 0.0 -CDScanCompresse 0.0 +CDScanCompress 0.0 +HTmaxscans -1.0 HTtimepad 0.0 HTanalysistime 38.0 HTxaxis Time HTcycleindex -1.0 -HTtimepad 0.0 +HTcylctime 2.0 +HTtimeshift 7.0 htbit 0.0 +FTstart 5.0 +FTend 1000.0 +FTflatten 1 +FTapodize 1 +demultiplexmode HT +FTsmooth 0.0 +HTmaskn 1000.0 +HTwin 5.0 csig 0.0 smoothdt 0.0 subbufdt 0.0 diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 93f823de..eb92bd86 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -69,7 +69,7 @@ def __init__(self, *args, **kwargs): self.config.HTtimeshift = 7 # How far back in time to shift self.config.HTksmooth = 0 # Kernel Smooth - print("HT Engine") + #print("HT Engine") def parse_file_name(self, path): """ @@ -177,7 +177,7 @@ def setup_ht(self, cycleindex=None, seq=None): seqarray[seqarray < -1] = 0 shortkernel = seqarray - print(shortkernel) + #print(shortkernel) # Roll short kernel so that the first 1 is in index 0 self.kernelroll = -np.argmax(shortkernel) @@ -202,13 +202,13 @@ def setup_ht(self, cycleindex=None, seq=None): self.cycleindex = int(cycleindex) print("Correction Diff:", diff) - print("Cycle Index:", self.cycleindex) + #print("Cycle Index:", self.cycleindex) self.rollindex = int(-self.shiftindex + self.cycleindex * self.kernelroll) # Create the HT kernel self.htkernel = np.zeros_like(self.fullscans[int(self.padindex):]).astype(float) - print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex, - "Shift Index:", self.shiftindex, "Roll Index:", self.rollindex) + #print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex, + # "Shift Index:", self.shiftindex, "Roll Index:", self.rollindex) index = 0 for i, s in enumerate(self.fullscans): @@ -361,6 +361,7 @@ def masked_demultiplex_ht(self, data, win=None, n=None, demult=None, mode="rand" outdata.append(out) # plt.plot(outdata[-1]) outdata = np.array(outdata) + #print('Shape of outdata: ', np.shape(outdata)) # Calculate number of sign changes per data point negcount = np.sum(outdata < 0, axis=0) + 1 @@ -1227,6 +1228,7 @@ def transform_dt_ccs_array(self, array, keepcomplex=True): binsize = (maxccs - minccs) / len(self.decontime) else: binsize = self.config.ccsbins + ccsaxis = np.arange(minccs, maxccs, binsize) # Create bins shifted by half a bin ccsbins = np.arange(minccs - binsize / 2., maxccs + binsize / 2., binsize) @@ -1271,9 +1273,11 @@ def ccs_transform_stacks(self): self.ccsstack_ht = self.ccsstack_ht[b1] if self.config.FTsmooth > 0: if self.config.FTsmooth > len(ccs_tic): - self.config.FTsmooth = 10. - print("Smoothing", self.config.FTsmooth) - ccs_tic = scipy.signal.savgol_filter(ccs_tic, int(self.config.FTsmooth), 3) + self.config.FTsmooth = len(ccs_tic) + if len(ccs_tic) > 4: + + print("Smoothing", self.config.FTsmooth) + ccs_tic = scipy.signal.savgol_filter(ccs_tic, int(self.config.FTsmooth), 3) ccs_tic = np.transpose(np.vstack((self.ccsaxis, ccs_tic))) return ccs_tic diff --git a/unidec/modules/IM_functions.py b/unidec/modules/IM_functions.py index a0f76bea..c9ccfc1d 100644 --- a/unidec/modules/IM_functions.py +++ b/unidec/modules/IM_functions.py @@ -266,6 +266,8 @@ def calc_linear_ccs(mass, z, dt, config): factor = 0.001 rmass = ac * (mass * hmass) / (mass + hmass) td = (dt - to) * factor + if np.any(rmass <= 0): + return mass * 0 ccs = z * td * ((1 / rmass) ** 0.5) * ccsconst return ccs diff --git a/unidec/modules/gui_elements/ud_controls.py b/unidec/modules/gui_elements/ud_controls.py index 404ed797..cf9db30b 100644 --- a/unidec/modules/gui_elements/ud_controls.py +++ b/unidec/modules/gui_elements/ud_controls.py @@ -582,6 +582,7 @@ def __init__(self, parent, config, pres, panel, iconfile): self.ctlwindow = wx.TextCtrl(panel3, value="", size=size1) self.ctlthresh = wx.TextCtrl(panel3, value="", size=size1) + self.plotbutton = wx.Button(panel3, -1, "Peak Detection") self.plotbutton2 = wx.Button(panel3, -1, "Plot Peaks") self.parent.Bind(wx.EVT_BUTTON, self.pres.on_plot_peaks, self.plotbutton2) @@ -647,6 +648,10 @@ def __init__(self, parent, config, pres, panel, iconfile): gbox3b.Add(self.ctlnorm, (i, 0), span=(1, 2), flag=wx.EXPAND) i += 1 + self.ctlnormpeakthresh = wx.CheckBox(panel3b, label="Normalize Peak Threshold") + gbox3b.Add(self.ctlnormpeakthresh, (i, 0), span=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL) + i += 1 + self.ctlthresh2 = wx.TextCtrl(panel3b, value="", size=size1) gbox3b.Add(wx.StaticText(panel3b, label="Marker Threshold: "), (i, 0), flag=wx.ALIGN_CENTER_VERTICAL) gbox3b.Add(self.ctlthresh2, (i, 1), flag=wx.ALIGN_CENTER_VERTICAL) @@ -780,6 +785,7 @@ def import_config_to_gui(self): self.ctlbinsize.SetValue(str(self.config.mzbins)) self.ctlwindow.SetValue(str(self.config.peakwindow)) self.ctlthresh.SetValue(str(self.config.peakthresh)) + self.ctlnormpeakthresh.SetValue(int(self.config.normthresh)) self.ctlthresh2.SetValue(str(self.config.peakplotthresh)) self.ctlsep.SetValue(str(self.config.separation)) self.ctlintthresh.SetValue(str(self.config.intthresh)) @@ -926,6 +932,7 @@ def export_gui_to_config(self, e=None): self.config.mfileflag = int(self.ctlmasslistflag.GetValue()) self.config.peakwindow = ud.string_to_value(self.ctlwindow.GetValue()) self.config.peakthresh = ud.string_to_value(self.ctlthresh.GetValue()) + self.config.normthresh = int(self.ctlnormpeakthresh.GetValue()) self.config.peakplotthresh = ud.string_to_value(self.ctlthresh2.GetValue()) self.config.separation = ud.string_to_value(self.ctlsep.GetValue()) self.config.adductmass = ud.string_to_value(self.ctladductmass.GetValue()) diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index ee7bb235..3b58810b 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -234,6 +234,7 @@ def __init__(self): # Peak Selection and plotting self.peakwindow = 500 self.peakthresh = 0.1 + self.normthresh = 1 self.peakplotthresh = 0.1 self.separation = 0.025 self.peaknorm = 1 @@ -420,6 +421,7 @@ def default_decon_params(self): # Peak Selection and plotting self.peakwindow = 500 self.peakthresh = 0.1 + self.normthresh = 1 self.peakplotthresh = 0.1 self.separation = 0.025 self.peaknorm = 1 @@ -533,6 +535,7 @@ def config_export(self, name): f.write("mzbins " + str(self.mzbins) + "\n") f.write("peakwindow " + str(self.peakwindow) + "\n") f.write("peakthresh " + str(self.peakthresh) + "\n") + f.write("normthresh" + str(int(self.normthresh)) + "\n") f.write("peakplotthresh " + str(self.peakplotthresh) + "\n") f.write("plotsep " + str(self.separation) + "\n") f.write("intthresh " + str(self.intthresh) + "\n") @@ -796,6 +799,8 @@ def config_import(self, name): self.peakwindow = ud.string_to_value(line.split()[1]) if line.startswith("peakthresh"): self.peakthresh = ud.string_to_value(line.split()[1]) + if line.startswith("normthresh"): + self.normthresh = ud.string_to_int(line.split()[1]) if line.startswith("peakplotthresh"): self.peakplotthresh = ud.string_to_value(line.split()[1]) if line.startswith("plotsep"): @@ -983,6 +988,7 @@ def get_config_dict(self): "msig": self.msig, "molig": self.molig, "massbins": self.massbins, "mtabsig": self.mtabsig, "minmz": self.minmz, "maxmz": self.maxmz, "subbuff": self.subbuff, "smooth": self.smooth, "mzbins": self.mzbins, "peakwindow": self.peakwindow, "peakthresh": self.peakthresh, + "normthresh": self.normthresh, "peakplotthresh": self.peakplotthresh, "plotsep": self.separation, "intthresh": self.intthresh, "reductionpercent": self.reductionpercent, "aggressive": self.aggressiveflag, "rawflag": self.rawflag, "adductmass": self.adductmass, @@ -1072,6 +1078,7 @@ def read_hdf5(self, file_name=None): self.mzbins = read_attr(self.mzbins, "mzbins", config_group) self.peakwindow = read_attr(self.peakwindow, "peakwindow", config_group) self.peakthresh = read_attr(self.peakthresh, "peakthresh", config_group) + self.normthresh = read_attr(self.normthresh, "normthresh", config_group) self.peakplotthresh = read_attr(self.peakplotthresh, "peakplotthresh", config_group) self.separation = read_attr(self.separation, "separation", config_group) self.intthresh = read_attr(self.intthresh, "intthresh", config_group) diff --git a/unidec/tools.py b/unidec/tools.py index 9e27dfb3..8c34be14 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -1983,6 +1983,7 @@ def peakdetect(data, config=None, window=10, threshold=0, ppm=None, norm=True): if config is not None: window = config.peakwindow / config.massbins threshold = config.peakthresh + norm = config.normthresh peaks = [] length = len(data) From b4b52c72f06824f99505f89e03c921ee5a94f347 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Mon, 22 Apr 2024 16:54:31 -0700 Subject: [PATCH 31/35] Fixed bugs with UCCD mass selection and abs --- unidec/UniChromCD.py | 4 ++-- unidec/modules/HTEng.py | 2 +- unidec/tools.py | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 35946ae9..7c539145 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -65,7 +65,7 @@ def init(self, *args, **kwargs): # path3 = "Z:\\Group Share\\ONO\\CDMS\\5 bit HT data\\20240313 JDS Bgal 0o1g_l inj1o5 quad9k bit-5 zp10 i2.dmt" path4 = "Z:\\Group Share\\Skippy\\Projects\\HT\\2024-04-01\\20240401 ribos s6Col bit3 inj5s 1_2024-04-01-04-19-29.dmt" # try: - self.on_open_file(None, None, path=pathft) + self.on_open_file(None, None, path=path) # except: # pass # self.eng.process_data_scans() @@ -548,11 +548,11 @@ def on_select_time_range_decon(self, e=None): self.run_all_ht() return if not wx.GetKeyState(wx.WXK_CONTROL): - self.plot_chromatograms() xlimits = self.view.plotdecontic.subplot1.get_xlim() print("New limits:", xlimits) self.view.plotdecontic.reset_zoom() ylimits = self.view.plotdecontic.subplot1.get_ylim() + self.plot_chromatograms() # Plot Red box on plottic self.view.plotdecontic.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], edgecolor="red", facecolor="red", nopaint=False) diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index eb92bd86..13ee4751 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -1014,7 +1014,7 @@ def run_all_ht(self): # self.fullhstack_ht[np.abs(self.fullhstack_ht) < 1e-6] = 0 tic = np.sum(self.fullhstack_ht, axis=(1, 2)) - tic = np.abs(tic) + tic = np.real(tic) ticdat = np.transpose(np.vstack((self.decontime, tic))) if self.config.datanorm == 1: ticdat[:, 1] /= np.amax(ticdat[:, 1]) diff --git a/unidec/tools.py b/unidec/tools.py index 8c34be14..29257001 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -58,7 +58,11 @@ '6': '000001000011000101001111010001110010010110111011001101010111111', '7': '0000001000001100001010001111001000101100111010100111110100001110001001001101101011011110110001101001011101110011001010101111111', '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111', - '-2': '010', '-3': '0001011', '-4': '111011001010000', '-5': '1111011010011000001110010001010'} + '-2': '010', '-3': '0001011', '-4': '111011001010000', '-5': '1111011010011000001110010001010', + '-105': '1010111011000111110011010010000' + + + } def get_importer(path): From e4413ea2b4df9445a50a7621b25d5fc07fc6e42e Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 25 Apr 2024 10:29:05 -0700 Subject: [PATCH 32/35] Swoop selection on UCCD --- unidec/UniChromCD.py | 66 +++++++---- unidec/modules/HTEng.py | 111 ++++++++++++++---- unidec/modules/PlotBase.py | 27 ++++- unidec/modules/PlottingWindow.py | 14 +++ unidec/modules/gui_elements/CDWindow.py | 1 + .../modules/gui_elements/PlotControlsPanel.py | 6 +- unidec/modules/isolated_packages/ZoomBox.py | 31 +++++ unidec/modules/unidecstructure.py | 14 ++- unidec/tools.py | 26 ++++ 9 files changed, 246 insertions(+), 50 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 7c539145..2c91455b 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -74,7 +74,7 @@ def init(self, *args, **kwargs): # self.run_all_ht() # self.run_all_mass_transform() # self.make_mass_cube_plot() - #self.on_run_ccs() + # self.on_run_ccs() def on_open_file(self, filename, directory, path=None, refresh=False): """ @@ -216,16 +216,22 @@ def make_tic_plot(self): self.cc.add_chromatogram(data, color="black", label="TIC") self.plot_chromatograms(save=False) - def add_eic(self, mzrange, zrange, color='b', plot=True): + def add_eic(self, mzrange, zrange, color='b', plot=True, sarray=None, label=None): """ Add an EIC to the list of chromatograms. Plot the chromatograms. :param mzrange: M/z range :param zrange: charge range :param color: Color of the plot. Default blue. + :param plot: Whether to plot the chromatograms. Default True. + :param sarray: Swoop array. Default None. + :param label: Label for the chromatogram. Default None. :return: None """ - eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) - self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange) + if sarray is not None and sarray[0] != -1 and sarray[1] != -1: + eicdata = self.eng.get_swoop_eic(sarray, normalize=self.eng.config.datanorm) + else: + eicdata = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) + self.cc.add_chromatogram(eicdata, color=color, zrange=zrange, mzrange=mzrange, sarray=sarray, label=label) if plot: self.plot_chromatograms() @@ -235,7 +241,8 @@ def on_run_eic_ccs(self, e=None): for c in self.cc.chromatograms: if "TIC" not in c.label: if c.decondat is None: - htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, normalize=self.eng.config.datanorm) + htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, + normalize=self.eng.config.datanorm, sarray=c.sarray) c.decondat = htdata c.chromdat = eicdata # eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) @@ -264,7 +271,8 @@ def on_run_eic_ht(self, e=None): self.showccs = False for c in self.cc.chromatograms: if "TIC" not in c.label: - htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, normalize=self.eng.config.datanorm) + htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, + normalize=self.eng.config.datanorm, sarray=c.sarray) c.chromdat = eicdata c.decondat = htdata self.plot_chromatograms() @@ -285,22 +293,17 @@ def run_tic_ht(self): self.showccs = False self.plot_chromatograms(save=False) - def run_eic_ht(self, mzrange, zrange, color='b'): + def run_eic_ht(self, mzrange, zrange, color='b', sarray=None): """ Function to generate and HT an EIC. Adds both to list. Plots chromatograms. :param mzrange: M/z range :param zrange: Charge range :param color: Color of plot. Default blue. + :param sarray: Swoop array. Default None. :return: None """ - htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm) - # if self.eng.ccsstack_ht is not None: - # ccsdata = self.eng.get_ccs_eic(mzrange=mzrange, zrange=zrange, normalize=self.eng.config.datanorm) - # print("Got CCS Data") - # else: - # ccsdata = None - # print("No CCS Data") + htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm, sarray=sarray) try: ccsdata, avgz, avgmz = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, @@ -312,7 +315,7 @@ def run_eic_ht(self, mzrange, zrange, color='b'): avgmz = None self.cc.add_chromatogram(eicdata, decondat=htdata, ccsdat=ccsdata, color=color, zrange=zrange, mzrange=mzrange, - mode=self.eng.config.demultiplexmode) + mode=self.eng.config.demultiplexmode, sarray=sarray) self.showht = True if avgz is not None: @@ -343,6 +346,10 @@ def plot_chromatograms(self, e=None, save=True): if xlimits[0] != -1 and ylimits[0] != -1 and xlimits[1] != -1 and ylimits[1] != -1: self.view.plot1.add_rect(xlimits[0], ylimits[0], xlimits[1] - xlimits[0], ylimits[1] - ylimits[0], edgecolor=c.color, facecolor=c.color, nopaint=True) + if c.sarray is not None: + if c.sarray[0] != -1 and c.sarray[1] != -1: + self.view.plot1.draw_mz_curve(c.sarray, color=c.color, repaint=False, + adduct_mass=self.eng.config.adductmass) if not ud.isempty(c.chromdat): if self.eng.config.HTxaxis == "Scans": @@ -447,14 +454,13 @@ def load_chroms(self, fname=None, array=None): mzrange = [float(a[3]), float(a[4])] zrange = [float(a[5]), float(a[6])] + sarray = [float(a[9]), float(a[10]), float(a[11]), float(a[12])] + print(sarray) + if "TIC" in label or self.eng.config.demultiplexmode in label or "Mass EIC" in label: continue - chromdat = self.eng.get_eic(mzrange, zrange, normalize=self.eng.config.datanorm) - - ht = ht.lower() in ['true', '1', 't', 'y', 'yes', 'yeah'] - - self.cc.add_chromatogram(chromdat, color=color, label=label, zrange=zrange, mzrange=mzrange) - + self.add_eic(mzrange, zrange, color=color, label=label, sarray=sarray, plot=False) + #ht = ht.lower() in ['true', '1', 't', 'y', 'yes', 'yeah'] self.plot_chromatograms() def on_load_chroms(self, e=None): @@ -477,9 +483,10 @@ def plot_mass_for_eic(self): """ for c in self.cc.chromatograms: if "TIC" not in c.label: - mzrange = c.mzrange - zrange = c.zrange - newd = self.eng.extract_subdata(mzrange, zrange) + if c.sarray is not None and c.sarray[0] != -1: + newd = self.eng.extract_swoop_subdata(c.sarray) + else: + newd = self.eng.extract_subdata(c.mzrange, c.zrange) c.dataobj = newd massdat = newd.massdat if not self.view.plot2.flag: @@ -899,6 +906,17 @@ def export_arrays(self): np.savetxt(newlabel + "_ccs.txt", c.ccsdat) print("Saved Files") + def on_select_swoop(self, e=None): + print("SWOOP there it is!") + self.eng.sarray = e.sarray + self.export_config(self.eng.config.confname) + if not wx.GetKeyState(wx.WXK_CONTROL): + color = ud.get_color_from_index(len(self.cc.chromatograms)) + if self.showht or self.showccs: + self.run_eic_ht(None, None, color=color, sarray=self.eng.sarray) + else: + self.add_eic(None, None, color=color, sarray=self.eng.sarray) + if __name__ == "__main__": multiprocessing.freeze_support() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 13ee4751..35f11f43 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -69,7 +69,7 @@ def __init__(self, *args, **kwargs): self.config.HTtimeshift = 7 # How far back in time to shift self.config.HTksmooth = 0 # Kernel Smooth - #print("HT Engine") + # print("HT Engine") def parse_file_name(self, path): """ @@ -177,7 +177,7 @@ def setup_ht(self, cycleindex=None, seq=None): seqarray[seqarray < -1] = 0 shortkernel = seqarray - #print(shortkernel) + # print(shortkernel) # Roll short kernel so that the first 1 is in index 0 self.kernelroll = -np.argmax(shortkernel) @@ -202,12 +202,12 @@ def setup_ht(self, cycleindex=None, seq=None): self.cycleindex = int(cycleindex) print("Correction Diff:", diff) - #print("Cycle Index:", self.cycleindex) + # print("Cycle Index:", self.cycleindex) self.rollindex = int(-self.shiftindex + self.cycleindex * self.kernelroll) # Create the HT kernel self.htkernel = np.zeros_like(self.fullscans[int(self.padindex):]).astype(float) - #print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex, + # print("HT Kernel Length:", len(self.htkernel), "Pad Index:", self.padindex, "Cycle Index:", self.cycleindex, # "Shift Index:", self.shiftindex, "Roll Index:", self.rollindex) index = 0 @@ -361,7 +361,7 @@ def masked_demultiplex_ht(self, data, win=None, n=None, demult=None, mode="rand" outdata.append(out) # plt.plot(outdata[-1]) outdata = np.array(outdata) - #print('Shape of outdata: ', np.shape(outdata)) + # print('Shape of outdata: ', np.shape(outdata)) # Calculate number of sign changes per data point negcount = np.sum(outdata < 0, axis=0) + 1 @@ -675,6 +675,7 @@ def __init__(self, *args, **kwargs): self.ccsstack_ht = None self.mz = None self.ztab = None + self.sarray = None # Params for swoop selection def open_file(self, path, refresh=False): """ @@ -917,6 +918,77 @@ def tic_ht(self, **kwargs): # print(np.shape(self.decontime), np.shape(self.htoutput)) return np.transpose([self.decontime, self.htoutput]) + def extract_swoop_subdata(self, sarray): + """ + Extract a subdata object based on the Swoop selection. + :param sarray: Swoop array, m/z mid, z mid, z spread (vertical), z width (horizontal) + :return: Data Object + """ + # Calculate the Swoop m/z range, zrange, upper charge, and lower charge bounds + mz, z, zup, zdown = ud.calc_swoop(sarray, adduct_mass=self.config.adductmass) + # Create Boolean array + bsum = np.zeros(self.harray.shape) + # Loop over all charge states + for i, zval in enumerate(z): + # For each charge state, filter z values within the bounds + b1 = self.ztab >= zdown[i] + b2 = self.ztab <= zup[i] + bz = b1 & b2 + + # Filter m/z values within the bounds of that charge state + mzmin, mzmax = ud.get_swoop_mz_minmax(mz, i) + b1 = self.mz >= mzmin + b2 = self.mz <= mzmax + bmz = b1 & b2 + + # Take everything that is within the charge and m/z range for that charge state + # Add rather than multiple because it's OR for each charge state + bsum += np.outer(bz, bmz) + + # Create new data object + newd = deepcopy(self.data) + # Filter Harray with the boolean array + newh2 = self.harray * bsum + # Transform and populate the new data object + if np.sum(newh2) != 0: + newd = self.transform(newh2, newd) + # Return data object + return newd + + def get_swoop_eic(self, sarray, **kwargs): + """ + Extract an EIC based on the Swoop selection. + :param sarray: Swoop array, m/z mid, z mid, z spread (vertical), z width (horizontal) + :param kwargs: Keywords to be passed down to create_chrom + :return: Data Object + """ + # Calculate the Swoop m/z range, zrange, upper charge, and lower charge bounds + mz, z, zup, zdown = ud.calc_swoop(sarray, adduct_mass=self.config.adductmass) + # Create Boolean array + bsum = np.zeros(len(self.farray)) + # Loop over all charge states + for i, zval in enumerate(z): + # For each charge state, filter z values within the bounds + b1 = self.zarray >= zdown[i] + b2 = self.zarray <= zup[i] + bz = b1 & b2 + + # Filter m/z values within the bounds of that charge state + mzmin, mzmax = ud.get_swoop_mz_minmax(mz, i) + b1 = self.farray[:, 0] >= mzmin + b2 = self.farray[:, 0] <= mzmax + bmz = b1 & b2 + + # Take everything that is within the charge and m/z range for that charge state + # Add rather than multiple because it's OR for each charge state + bsum += bmz * bz + # Filter farray + farray2 = self.farray[bsum.astype(bool)] + + # Create EIC + eic = self.create_chrom(farray2, **kwargs) + return eic + def get_eic(self, mzrange, zrange, **kwargs): """ Get the EIC from the farray. @@ -940,6 +1012,12 @@ def get_eic(self, mzrange, zrange, **kwargs): return eic def extract_subdata(self, mzrange, zrange): + """ + Extract a subdata object based on m/z and charge range. + :param mzrange: m/z range, [low, high] + :param zrange: z range, [low, high] + :return: Data Object + """ b1 = self.mz >= mzrange[0] b2 = self.mz <= mzrange[1] b3 = self.ztab >= zrange[0] @@ -949,16 +1027,6 @@ def extract_subdata(self, mzrange, zrange): b3 = np.outer(b2, b) newd = deepcopy(self.data) - '''newh = np.atleast_2d(self.harray[:, b][b2]) - - - ztab = np.atleast_1d(self.ztab[b2]) - mz = np.atleast_1d(self.mz[b]) - mass = np.atleast_2d(self.mass[:, b][b2]) - - if len(ztab) != newh.shape[0]: - newh = newh.transpose() - mass = mass.transpose()''' newh2 = self.harray * b3 @@ -968,15 +1036,19 @@ def extract_subdata(self, mzrange, zrange): return newd - def eic_ht(self, mzrange, zrange, **kwargs): + def eic_ht(self, mzrange, zrange, sarray=None, **kwargs): """ Get the EIC and run HT on it. :param mzrange: m/z range :param zrange: charge range + :param sarray: Swoop array, m/z mid, z mid, z spread (vertical), z width (horizontal), default None :param kwargs: Keyword arguments. Passed down to create_chrom and htdecon. :return: Demultiplexed data output. 2D array (time, intensity) """ - eic = self.get_eic(mzrange, zrange, **kwargs) + if sarray is not None and sarray[0] != -1: + eic = self.get_swoop_eic(sarray, **kwargs) + else: + eic = self.get_eic(mzrange, zrange, **kwargs) self.setup_demultiplex() self.htoutput, eic[:, 1] = self.run_demultiplex(eic[:, 1], **kwargs) return np.transpose([self.decontime, self.htoutput]), eic @@ -1034,7 +1106,7 @@ def select_ht_range(self, range=None): b1 = self.decontime >= range[0] b2 = self.decontime <= range[1] b = np.logical_and(b1, b2) - substack_ht = np.abs(self.fullhstack_ht[b]) + substack_ht = np.real(self.fullhstack_ht[b]) self.harray = np.sum(substack_ht, axis=0) self.harray = np.clip(self.harray, 0, np.amax(self.harray)) self.harray_process() @@ -1116,7 +1188,7 @@ def transform_stacks(self): self.mstack_ht, self.fullmstack_ht = self.transform_array(self.fullhstack_ht, dtype=self.dtype) - self.mass_tic_ht = np.transpose([self.decontime, np.abs(np.sum(self.mstack_ht, axis=1))]) + self.mass_tic_ht = np.transpose([self.decontime, np.real(np.sum(self.mstack_ht, axis=1))]) if self.config.datanorm == 1: norm = np.amax(self.mass_tic_ht[:, 1]) @@ -1275,7 +1347,6 @@ def ccs_transform_stacks(self): if self.config.FTsmooth > len(ccs_tic): self.config.FTsmooth = len(ccs_tic) if len(ccs_tic) > 4: - print("Smoothing", self.config.FTsmooth) ccs_tic = scipy.signal.savgol_filter(ccs_tic, int(self.config.FTsmooth), 3) diff --git a/unidec/modules/PlotBase.py b/unidec/modules/PlotBase.py index 4b0076c4..beddd5f5 100644 --- a/unidec/modules/PlotBase.py +++ b/unidec/modules/PlotBase.py @@ -96,7 +96,7 @@ def repaint(self, setupzoom=False, resetzoom=False): self.setup_zoom() try: self.zoomout() - except: + except Exception: pass def zoomout(self): @@ -319,7 +319,7 @@ def textremove(self): self.text[i].remove() try: self.lines[i].remove() - except: + except Exception: print(self.text[i]) self.text = [] self.lines = [] @@ -418,6 +418,29 @@ def write_data(self, path): print("Data Dimensions:", self.data.shape) np.savetxt(path, self.data) + def draw_mz_curve(self, sarray, color="y", alpha=0.4, adduct_mass=1, repaint=False): + mz_mid, z_mid, z_spread, z_width = sarray + + z_mid = np.round(z_mid) + z_width = np.round(z_width) + mass = mz_mid * z_mid - adduct_mass * z_mid + minz = z_mid - z_width + maxz = z_mid + z_width + z = np.arange(minz, maxz + 1) + mz = (mass + adduct_mass * z) / z + + zup = z + z_spread + zdown = z - z_spread + + zup = np.round(zup) + zdown = np.round(zdown) + + polyobj = self.subplot1.fill_between(mz, zdown, zup, color=color, alpha=alpha) + if repaint: + self.repaint() + + return polyobj, sarray + def update_style(self, stylefile=None): if stylefile is None: stylefile = self.config.mplstylefile diff --git a/unidec/modules/PlottingWindow.py b/unidec/modules/PlottingWindow.py index 93c9db38..f68dcfe4 100644 --- a/unidec/modules/PlottingWindow.py +++ b/unidec/modules/PlottingWindow.py @@ -46,6 +46,15 @@ def __init__(self, evttype, id, minval=None, maxval=None): self.minval = minval self.maxval = maxval +# Create swoop drag custom event +SwoopDragEventType = wx.NewEventType() +EVT_SWOOP_DRAG = wx.PyEventBinder(SwoopDragEventType, 1) + +class SwoopDragEvent(wx.PyCommandEvent): + def __init__(self, evttype, id, sarray=None): + wx.PyCommandEvent.__init__(self, evttype, id) + self.sarray = sarray + class PlottingWindowBase(PlotBase, wx.Panel): """ @@ -71,6 +80,7 @@ def __init__(self, *args, **kwargs): self.displaysize = wx.GetDisplaySize() self.EVT_SCANS_SELECTED = EVT_SCANS_SELECTED self.EVT_MZLIMITS = EVT_MZLIMITS + self.EVT_SWOOP_DRAG = EVT_SWOOP_DRAG if "integrate" in kwargs: self.int = kwargs["integrate"] @@ -413,6 +423,10 @@ def on_right_click(self, event=None): event = ScanSelectedEvent(ScanSelectedEventType, self.GetId()) self.GetEventHandler().ProcessEvent(event) + def on_swoop_drag(self, sarray): + event = SwoopDragEvent(SwoopDragEventType, self.GetId(), sarray=sarray) + self.GetEventHandler().ProcessEvent(event) + class Plot1d(PlottingWindowBase, Plot1dBase): """ Class for 1D plots. diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index abb90b64..90a29c3d 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -231,6 +231,7 @@ def setup_main_panel(self): if self.htmode: self.Bind(self.plot5.EVT_MZLIMITS, self.pres.on_select_massz_range, self.plot5) self.Bind(self.plot1.EVT_MZLIMITS, self.pres.on_select_mzz_region, self.plot1) + self.Bind(self.plot1.EVT_SWOOP_DRAG, self.pres.on_select_swoop, self.plot1) self.plots = [self.plot1, self.plot2, self.plot5, self.plot4, self.plot3, self.plot6] self.plotnames = ["Figure1", "Figure2", "Figure5", "Figure4", "Figure3", "Figure6"] diff --git a/unidec/modules/gui_elements/PlotControlsPanel.py b/unidec/modules/gui_elements/PlotControlsPanel.py index 73c3c823..c6ae5b81 100644 --- a/unidec/modules/gui_elements/PlotControlsPanel.py +++ b/unidec/modules/gui_elements/PlotControlsPanel.py @@ -2,6 +2,7 @@ import wx from unidec.modules.PlottingWindow import Plot1d from unidec.modules.unidecstructure import UniDecConfig +import numpy as np mpl.rcParams['ps.useafm'] = True mpl.rcParams['ps.fonttype'] = 42 @@ -18,7 +19,6 @@ def export_mplstyle(params, filename="unidec.mplstyle"): for p in params: f.write(p + ": " + str(params[p]) + "\n") - # Plot Controls Panel class PlotControlsPanel(wx.Panel): def __init__(self, parent, pres, config=None, *args, **kwargs): @@ -109,7 +109,9 @@ def __init__(self, parent, *args, **kwargs): self.Show() def makeplot(self): - self.plot.plotrefreshtop([1, 2, 3], [1, 4, 9]) + self.plot.plotrefreshtop([1, 2, 3, 14000], [1, 4, 9, 15]) + #self.plot.draw_mz_curve( 10000, 10, 1, 2) + def on_update_plot_params(self, event=None, stylefile=None): self.plot.update_style(stylefile) self.makeplot() diff --git a/unidec/modules/isolated_packages/ZoomBox.py b/unidec/modules/isolated_packages/ZoomBox.py index 571c4c50..56c5b699 100644 --- a/unidec/modules/isolated_packages/ZoomBox.py +++ b/unidec/modules/isolated_packages/ZoomBox.py @@ -97,6 +97,9 @@ def __init__(self, axes, onselect, parent=None, groups=None, drawtype='box', self.canvas = None self.visible = True self.cids = [] + self.mzz = None + self.mzcurve = None + self.sarray = None self.lflag = 0 @@ -258,6 +261,14 @@ def press(self, event): if self.comparemode is True: self.comparexvals.append(event.xdata) self.compareyvals.append(event.ydata) + + if wx.GetKeyState(wx.WXK_SHIFT): + # Get the current event x and y + x, y = event.xdata, event.ydata + self.mzz = [x, y] + # print("MZZ", self.mzz) + else: + self.mzz = None return False def release(self, event): @@ -265,6 +276,11 @@ def release(self, event): if self.eventpress is None or (self.ignore(event) and not self.buttonDown): return # Do compare mode stuff self.buttonDown = False + + if self.sarray is not None: + self.parent.on_swoop_drag(self.sarray) + return + if self.comparemode is True: if event.xdata is None: try: @@ -442,6 +458,21 @@ def onmove(self, event): if minx > maxx: minx, maxx = maxx, minx # get them in the right order if miny > maxy: miny, maxy = maxy, miny + if self.mzz is not None: + spany = maxy - miny + minz = self.mzz[0] * self.mzz[1] / maxx + maxz = self.mzz[0] * self.mzz[1] / minx + zwidth = maxz - minz + #print(self.mzz, spany, zwidth) + if self.mzcurve is not None: + self.mzcurve.remove() + sarray = [self.mzz[0], self.mzz[1], spany, zwidth] + self.mzcurve, self.sarray = self.parent.draw_mz_curve(sarray) + self.canvas.draw() + return + else: + self.sarray = None + # Changes from a yellow box to a colored line for axes in self.axes: y0, y1 = axes.get_ylim() diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 3b58810b..3c79361b 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -1620,16 +1620,19 @@ def __init__(self): self.index = 0 self.mzrange = [-1, -1] self.zrange = [-1, -1] + self.sarray = [-1, -1, -1, -1] self.sum = -1 self.ignore = 0 self.ht = False self.massrange = [-1, -1] self.massmode = False self.dataobj = None + self.swoopmode = False def to_row(self): out = [self.label, str(self.color), str(self.index), str(self.mzrange[0]), str(self.mzrange[1]), - str(self.zrange[0]), str(self.zrange[1]), str(self.sum), str(self.ht)] + str(self.zrange[0]), str(self.zrange[1]), str(self.sum), str(self.ht), str(self.sarray[0]) + , str(self.sarray[1]), str(self.sarray[2]), str(self.sarray[3])] return out @@ -1638,7 +1641,7 @@ def __init__(self): self.chromatograms = [] def add_chromatogram(self, data, decondat=None, ccsdat=None, label=None, color="#000000", index=None, mzrange=None, - zrange=None, massrange=None, massmode=False, mode="DM"): + zrange=None, massrange=None, massmode=False, mode="DM", sarray=None): chrom = Chromatogram() chrom.chromdat = data chrom.decondat = decondat @@ -1651,6 +1654,8 @@ def add_chromatogram(self, data, decondat=None, ccsdat=None, label=None, color=" label += "m/z: " + str(round(mzrange[0])) + "-" + str(round(mzrange[1])) if zrange is not None: label += " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) + if sarray is not None: + label += "Swoop m/z: " + str(sarray[0]) + " z: " + str(sarray[1]) chrom.label = label @@ -1672,6 +1677,11 @@ def add_chromatogram(self, data, decondat=None, ccsdat=None, label=None, color=" chrom.massrange = massrange chrom.massmode = True + if sarray is not None: + chrom.sarray = sarray + chrom.swoopmode = True + chrom.massmode = False + chrom.massmode = massmode # If label already exists, replace it diff --git a/unidec/tools.py b/unidec/tools.py index 29257001..89a3d36b 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -3233,6 +3233,32 @@ def subtract_and_divide(pks, basemass, refguess, outputall=False): else: return np.average(avgmass, weights=ints) +def calc_swoop(sarray, adduct_mass=1): + mz_mid, z_mid, z_spread, z_width = sarray + z_mid = np.round(z_mid) + z_width = np.round(z_width) + mass = mz_mid * z_mid - adduct_mass * z_mid + minz = z_mid - z_width + maxz = z_mid + z_width + z = np.arange(minz, maxz + 1) + mz = (mass + adduct_mass * z) / z + + zup = np.round(z + z_spread) + zdown = np.round(z - z_spread) + + return mz, z, zup, zdown + +def get_swoop_mz_minmax(mz, i): + if i == 0: + mzmax = mz[i] + else: + mzmax = (mz[i] + mz[i - 1]) / 2 + if i == len(mz) - 1: + mzmin = mz[i] + else: + mzmin = (mz[i] + mz[i + 1]) / 2 + return mzmin, mzmax + if __name__ == "__main__": testdir = "C:\\Data\\AgilentData" From 429480f9563ac0c1cef77091cd6cfd473e67c88c Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 25 Apr 2024 11:15:44 -0700 Subject: [PATCH 33/35] Finished Swoop Selection Integration into UCCD --- unidec/UniChromCD.py | 15 ++-- unidec/modules/HTEng.py | 79 +++++++++++---------- unidec/modules/PlottingWindow.py | 8 ++- unidec/modules/gui_elements/CDWindow.py | 9 ++- unidec/modules/isolated_packages/ZoomBox.py | 12 ++-- 5 files changed, 70 insertions(+), 53 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 2c91455b..1c55f53b 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -247,7 +247,7 @@ def on_run_eic_ccs(self, e=None): c.chromdat = eicdata # eicdata = self.eng.get_ccs_eic(mzrange=c.mzrange, zrange=c.zrange, normalize=self.eng.config.datanorm) eicdata, avgz, avgmz = self.eng.convert_trace_to_ccs(c.decondat, mzrange=c.mzrange, zrange=c.zrange, - normalize=self.eng.config.datanorm) + normalize=self.eng.config.datanorm, sarray=c.sarray) c.ccsdat = eicdata c.label = "Avg. m/z: " + str(round(avgmz)) + " z: " + str(round(avgz)) self.plot_chromatograms() @@ -305,10 +305,10 @@ def run_eic_ht(self, mzrange, zrange, color='b', sarray=None): htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm, sarray=sarray) - try: + if True: ccsdata, avgz, avgmz = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, - normalize=self.eng.config.datanorm) - except: + normalize=self.eng.config.datanorm, sarray=c.sarray) + else: print("Failed to convert to CCS") ccsdata = None avgz = None @@ -453,9 +453,10 @@ def load_chroms(self, fname=None, array=None): ht = a[8] mzrange = [float(a[3]), float(a[4])] zrange = [float(a[5]), float(a[6])] - - sarray = [float(a[9]), float(a[10]), float(a[11]), float(a[12])] - print(sarray) + try: + sarray = [float(a[9]), float(a[10]), float(a[11]), float(a[12])] + except Exception: + sarray = [-1, -1, -1, -1] if "TIC" in label or self.eng.config.demultiplexmode in label or "Mass EIC" in label: continue diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 35f11f43..517b93fb 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -1353,40 +1353,70 @@ def ccs_transform_stacks(self): ccs_tic = np.transpose(np.vstack((self.ccsaxis, ccs_tic))) return ccs_tic - def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): + def convert_trace_to_ccs(self, trace, mzrange, zrange, sarray=None, normalize=False): """ Convert a trace to CCS. :param trace: 1D array of intensity :param mzrange: m/z range :param zrange: charge range + :param sarray: Swoop array, m/z mid, z mid, z spread (vertical), z width (horizontal), default None :param normalize: Whether to normalize the output to the maximum. :return: 2D array of CCS (time, intensity) """ + # If needed, process the scans if self.topharray is None: self.process_data_scans() - trace = deepcopy(trace) - b1 = self.X >= mzrange[0] - b2 = self.X <= mzrange[1] - b3 = self.Y >= zrange[0] - b4 = self.Y <= zrange[1] - b = np.logical_and(b1, b2) - b = np.logical_and(b, b3) - b = np.logical_and(b, b4) + + # Swoop Array Filtering + if sarray is not None and sarray[0] != -1: + b = np.zeros_like(self.topharray) + # Calculate the Swoop m/z range, zrange, upper charge, and lower charge bounds + mz, z, zup, zdown = ud.calc_swoop(sarray, adduct_mass=self.config.adductmass) + # Loop over all charge states + for i, zval in enumerate(z): + # For each charge state, filter z values within the bounds + b1 = self.Y >= zdown[i] + b2 = self.Y <= zup[i] + bz = b1 & b2 + + # Filter m/z values within the bounds of that charge state + mzmin, mzmax = ud.get_swoop_mz_minmax(mz, i) + b1 = self.X >= mzmin + b2 = self.X <= mzmax + bmz = b1 & b2 + + # Take everything that is within the charge and m/z range for that charge state + # Add rather than multiple because it's OR for each charge state + b += bz * bmz + b = b.astype(bool) + else: + # Basic Rectangle Filtering + b1 = self.X >= mzrange[0] + b2 = self.X <= mzrange[1] + b3 = self.Y >= zrange[0] + b4 = self.Y <= zrange[1] + b = np.logical_and(b1, b2) + b = np.logical_and(b, b3) + b = np.logical_and(b, b4) + # Filter int, mz, z intarray = self.topharray[b] mzarray = self.X[b] zarray = self.Y[b] + # Calc avg mz, z, and mass avgmz = np.sum(intarray * mzarray) / np.sum(intarray) avgz = np.sum(intarray * zarray) / np.sum(intarray) avgz = np.round(avgz) avgmass = (avgmz - self.config.adductmass) * avgz - # Calculate weighted average of m/z and charge + + # Convert DT to CCS calc_linear_ccsconst(self.config) + trace = deepcopy(trace) ccs_axis = calc_linear_ccs(avgmass, avgz, trace[:, 0], self.config) + # Process Intensity Data if normalize: trace[:, 1] /= np.amax(trace[:, 1]) - if self.config.FTsmooth > 0: if self.config.FTsmooth > len(ccs_axis): self.config.FTsmooth = 10. @@ -1438,31 +1468,4 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, normalize=False): # import matplotlib.pyplot as plt # plt.plot(ac) # plt.show() - exit() - plt.plot(eng.fulltime, eng.fulltic / np.amax(eng.fulltic)) - plt.plot(eng.fulltime[eng.padindex:], np.roll(eng.htkernel, 0)) - plt.plot(eng.fulltime[1:], eng.htoutput / np.amax(eng.htoutput) - 1) - plt.show() - exit() - - eng.run_all_ht() - print(np.shape(eng.hstack)) - plt.figure() - - plt.subplot(121) - for i, x in enumerate(eng.mz): - for j, y in enumerate(eng.ztab): - plt.plot(eng.fullscans, eng.fullhstack_ht[:, j, i]) - - plt.subplot(122) - plt.imshow(np.sum(eng.fullhstack[:150], axis=0), aspect="auto", origin="lower", - extent=[eng.mz[0], eng.mz[-1], eng.ztab[0], eng.ztab[-1]]) - - plt.show() - - from unidec.modules import PlotAnimations as PA - import wx - app = wx.App(False) - PA.AnimationWindow(None, eng.fullhstack[50:], mode="2D") - app.MainLoop() diff --git a/unidec/modules/PlottingWindow.py b/unidec/modules/PlottingWindow.py index f68dcfe4..dfa7c6ac 100644 --- a/unidec/modules/PlottingWindow.py +++ b/unidec/modules/PlottingWindow.py @@ -99,6 +99,12 @@ def __init__(self, *args, **kwargs): else: self.parent = None + if "swoop" in kwargs: + self.swoop = kwargs["swoop"] + del kwargs["swoop"] + else: + self.swoop = False + wx.Window.__init__(self, *args) self.Bind(wx.EVT_SIZE, self.size_handler) # self.Bind(wx.EVT_IDLE, self.on_idle) @@ -381,7 +387,7 @@ def setup_zoom(self, plots, zoom, data_lims=None, pad=0, groups=None): onmove_callback=None, spancoords='data', rectprops=dict(alpha=0.2, facecolor='yellow'), - data_lims=data_lims, + data_lims=data_lims, swoop=self.swoop, integrate=self.int, smash=self.smash, pad=pad) if zoom == "fixed_span": self.zoom = NoZoomSpan( diff --git a/unidec/modules/gui_elements/CDWindow.py b/unidec/modules/gui_elements/CDWindow.py index 90a29c3d..1741794a 100644 --- a/unidec/modules/gui_elements/CDWindow.py +++ b/unidec/modules/gui_elements/CDWindow.py @@ -115,6 +115,11 @@ def setup_main_panel(self): # # ................................... + if self.htmode: + swoop = True + else: + swoop = False + # Tabbed view of plots if self.tabbed == 1: figsize = self.config.figsize @@ -127,7 +132,7 @@ def setup_main_panel(self): tab5 = wx.Panel(plotwindow) tab6 = wx.Panel(plotwindow) - self.plot1 = PlottingWindow.Plot2d(tab1, smash=1, figsize=figsize) + self.plot1 = PlottingWindow.Plot2d(tab1, smash=1, figsize=figsize, swoop=swoop) self.plot2 = PlottingWindow.Plot1d(tab2, integrate=1, figsize=figsize) self.plot3 = PlottingWindow.Plot1d(tab3, figsize=figsize) self.plot4 = PlottingWindow.Plot1d(tab4, figsize=figsize) @@ -184,7 +189,7 @@ def setup_main_panel(self): splitterwindow.SplitVertically(plotwindow, splitterwindow2, sashPosition=-550) sizerplot = wx.GridBagSizer() figsize = self.config.figsize - self.plot1 = PlottingWindow.Plot2d(plotwindow, smash=1, figsize=figsize) + self.plot1 = PlottingWindow.Plot2d(plotwindow, smash=1, figsize=figsize, swoop=swoop) self.plot2 = PlottingWindow.Plot1d(plotwindow, integrate=1, figsize=figsize) self.plot3 = PlottingWindow.Plot1d(plotwindow, figsize=figsize) self.plot4 = PlottingWindow.Plot1d(plotwindow, figsize=figsize) diff --git a/unidec/modules/isolated_packages/ZoomBox.py b/unidec/modules/isolated_packages/ZoomBox.py index 56c5b699..c11a0293 100644 --- a/unidec/modules/isolated_packages/ZoomBox.py +++ b/unidec/modules/isolated_packages/ZoomBox.py @@ -47,7 +47,7 @@ def __init__(self, axes, onselect, parent=None, groups=None, drawtype='box', onmove_callback=None, spancoords='data', button=None, - data_lims=None, + data_lims=None, swoop=False, integrate=0, smash=0, pad=0.0001): """ @@ -100,6 +100,7 @@ def __init__(self, axes, onselect, parent=None, groups=None, drawtype='box', self.mzz = None self.mzcurve = None self.sarray = None + self.swoop = swoop self.lflag = 0 @@ -273,11 +274,12 @@ def press(self, event): def release(self, event): """on button release event""" - if self.eventpress is None or (self.ignore(event) and not self.buttonDown): return + if self.eventpress is None or (self.ignore(event) and not self.buttonDown): + return # Do compare mode stuff self.buttonDown = False - if self.sarray is not None: + if self.sarray is not None and self.swoop: self.parent.on_swoop_drag(self.sarray) return @@ -286,7 +288,7 @@ def release(self, event): try: self.comparexvals.append(self.prev[0]) self.compareyvals.append(self.prev[1]) - except: + except Exception: self.comparexvals.append(event.xdata) self.compareyvals.append(event.ydata) else: @@ -458,7 +460,7 @@ def onmove(self, event): if minx > maxx: minx, maxx = maxx, minx # get them in the right order if miny > maxy: miny, maxy = maxy, miny - if self.mzz is not None: + if self.mzz is not None and self.swoop: spany = maxy - miny minz = self.mzz[0] * self.mzz[1] / maxx maxz = self.mzz[0] * self.mzz[1] / minx From 035fbb579388d2f39f1622bf317a9c01df8b2ddf Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Thu, 25 Apr 2024 11:35:19 -0700 Subject: [PATCH 34/35] UCCD bug fixes --- unidec/UniChromCD.py | 20 +++++++++++--------- unidec/modules/unidecstructure.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 1c55f53b..799b79f9 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -269,6 +269,7 @@ def on_run_eic_ht(self, e=None): """ self.export_config(self.eng.config.confname) self.showccs = False + self.showht = True for c in self.cc.chromatograms: if "TIC" not in c.label: htdata, eicdata = self.eng.eic_ht(c.mzrange, c.zrange, @@ -305,14 +306,15 @@ def run_eic_ht(self, mzrange, zrange, color='b', sarray=None): htdata, eicdata = self.eng.eic_ht(mzrange, zrange, normalize=self.eng.config.datanorm, sarray=sarray) - if True: - ccsdata, avgz, avgmz = self.eng.convert_trace_to_ccs(htdata, mzrange=c.mzrange, zrange=c.zrange, - normalize=self.eng.config.datanorm, sarray=c.sarray) - else: - print("Failed to convert to CCS") - ccsdata = None - avgz = None - avgmz = None + ccsdata = None + avgz = None + avgmz = None + if self.showccs: + try: + ccsdata, avgz, avgmz = self.eng.convert_trace_to_ccs(htdata, mzrange=mzrange, zrange=zrange, + normalize=self.eng.config.datanorm, sarray=sarray) + except Exception: + print("Failed to convert to CCS") self.cc.add_chromatogram(eicdata, decondat=htdata, ccsdat=ccsdata, color=color, zrange=zrange, mzrange=mzrange, mode=self.eng.config.demultiplexmode, sarray=sarray) @@ -908,7 +910,7 @@ def export_arrays(self): print("Saved Files") def on_select_swoop(self, e=None): - print("SWOOP there it is!") + print("SWOOP there it is!", self.showht) self.eng.sarray = e.sarray self.export_config(self.eng.config.confname) if not wx.GetKeyState(wx.WXK_CONTROL): diff --git a/unidec/modules/unidecstructure.py b/unidec/modules/unidecstructure.py index 3c79361b..da4dc3a9 100644 --- a/unidec/modules/unidecstructure.py +++ b/unidec/modules/unidecstructure.py @@ -1655,7 +1655,7 @@ def add_chromatogram(self, data, decondat=None, ccsdat=None, label=None, color=" if zrange is not None: label += " z: " + str(round(zrange[0])) + "-" + str(round(zrange[1])) if sarray is not None: - label += "Swoop m/z: " + str(sarray[0]) + " z: " + str(sarray[1]) + label += "Swoop m/z: " + str(round(sarray[0])) + " z: " + str(round(sarray[1])) chrom.label = label From 98c3bef9181b57b24d272332acac4cf3c9b3c923 Mon Sep 17 00:00:00 2001 From: michaelmarty Date: Tue, 14 May 2024 16:27:12 -0700 Subject: [PATCH 35/35] Bug fixes and compatibility enhancements --- readme.md | 8 +++-- setupdocker.py | 2 +- unidec/GUniDec.py | 13 ++++---- unidec/MetaUniDec.py | 10 +++--- unidec/UniChrom.py | 3 ++ unidec/UniChromCD.py | 2 +- unidec/engine.py | 5 ++- unidec/metaunidec/metafft.py | 2 +- unidec/metaunidec/mudeng.py | 21 +------------ unidec/metaunidec/mudhelp.py | 2 +- unidec/metaunidec/mudstruct.py | 8 ++--- unidec/metaunidec/ultrameta.py | 2 +- unidec/modules/CDCal.py | 2 +- unidec/modules/CDEng.py | 31 ------------------- unidec/modules/HTEng.py | 9 +++--- unidec/modules/IM_windows.py | 4 +-- unidec/modules/ManualSelectionWindow.py | 7 ++--- unidec/modules/biopolymertools.py | 13 ++++---- .../modules/gui_elements/mainwindow_base.py | 10 ++++-- unidec/modules/gui_elements/ud_menu.py | 6 ++-- unidec/modules/html_writer.py | 6 +++- unidec/modules/isolated_packages/ZoomBox.py | 29 ++++++++++++++++- unidec/modules/unidec_enginebase.py | 3 +- unidec/modules/unidec_presbase.py | 8 +++++ unidec/tools.py | 4 +-- 25 files changed, 108 insertions(+), 102 deletions(-) diff --git a/readme.md b/readme.md index cda0858c..e99d1347 100644 --- a/readme.md +++ b/readme.md @@ -201,11 +201,15 @@ Added new module for time domain CD-MS data analysis, UniChromCD. This includes UPP added NumPeaks output on all to help with catching bad files in the results. Also, fixed bugs to allow files with bad data to proceed relatively unscathed. It should be more error tolerant now for large runs. -Added check box on main UniDec panel for whether to normalize the peak threshold. When on, it will behave as previously where the peak thershold is a ratio of the max intensity. When off, it will be a fixed intensity. Try switching off "Publication Mode" to view the plot with intensity axes. Note, this interacts with the Peak Normalization parameter directly above it. Max will normalize everything to 100. Total can get weird. None is the safest bet. +Added Ctrl+0 as shortcut to open the most recent file. + +Added experimental feature for check box on main UniDec panel for whether to normalize the peak threshold. When on, it will behave as previously where the peak thershold is a ratio of the max intensity. When off, it will be a fixed intensity. Try switching off "Publication Mode" to view the plot with intensity axes. Note, this interacts with the Peak Normalization parameter directly above it. Max will normalize everything to 100. Total can get weird. None is the safest bet. Blocked mouse wheel events on drop down selections. This should fix a lot of accidental switches to parameters. -Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. Fixed execution bug on servers. +Removed or made optional some dependencies to streamline installation. Removed special report type. Updates for Python 3.12 and general library updates. + +Fixed deep bug with integration transforms. Improvements and refactoring to support UniChromCD. Fixed execution bug on servers. Fixed other bugs. v.6.0.5 diff --git a/setupdocker.py b/setupdocker.py index a4f0950d..ade00351 100644 --- a/setupdocker.py +++ b/setupdocker.py @@ -11,7 +11,7 @@ python_requires='>=3.7', install_requires=["numpy>=1.16", "scipy>=1.2", "matplotlib>=3.1", "lxml", 'pandas', - "pymzml", "natsort", "networkx", "h5py", "pyimzml", "pyteomics", "mpld3"], + "pymzml", "natsort", "networkx", "h5py", "pyimzml", "pyteomics", "lxml_html_clean"], packages=find_packages(exclude=["Scripts", "Scripts.*", "*.Scripts", "*.Scripts.*"]), include_package_data=True, diff --git a/unidec/GUniDec.py b/unidec/GUniDec.py index 6f223f4d..49042ee3 100644 --- a/unidec/GUniDec.py +++ b/unidec/GUniDec.py @@ -24,11 +24,11 @@ from unidec.modules.unidec_presbase import UniDecPres from unidec.iFAMS.wxiFAMS import iFAMS_Window -try: - import unidec.modules.thermo_reader.rawreader as rawreader -except Exception as e: - print("Error importing Thermo Raw Reader, try installing MSFileReader from Thermo and pymsfilereader") - print(e) +#try: +# import unidec.modules.thermo_reader.rawreader as rawreader +#except Exception as e: +# print("Error importing Thermo Raw Reader, try installing MSFileReader from Thermo and pymsfilereader") +# print(e) # import FileDialog # Needed for pyinstaller @@ -1306,6 +1306,7 @@ def on_pdf_report(self, e=None): print("PDF Figures written.") pass + ''' def on_nmsgsb_report(self, e=0): """ @@ -1375,7 +1376,7 @@ def on_nmsgsb_report(self, e=0): # print("Resetting Figure Sizes", self.eng.config.figsize) # self.on_replot() self.on_flip_tabbed(e=0) - pass + pass''' def on_fft_window(self, e): print("FFT window...") diff --git a/unidec/MetaUniDec.py b/unidec/MetaUniDec.py index 020e8a89..1c056bf0 100644 --- a/unidec/MetaUniDec.py +++ b/unidec/MetaUniDec.py @@ -101,7 +101,8 @@ def makeplot2_mud(self, e=None): colval=s.color, newlabel=s.name) self.view.plot2.repaint() try: - self.makeplot9() + if not self.chrommode: + self.makeplot9() except: pass @@ -723,6 +724,7 @@ def on_export_params(self, e=None): def repack_hdf5(self, e=None): if self.eng.config.hdf_file != 'default.hdf5': new_path = self.eng.config.hdf_file.replace(".hdf5", "temp.hdf5") + print("Repacking 2:", new_path, self.eng.config.hdf_file, self.eng.config.h5repackfile) call = "\"" + self.eng.config.h5repackfile + "\" \"" + self.eng.config.hdf_file + "\" \"" + new_path + "\"" out = ud.exe_call(call) if 0 == out and os.path.isfile(new_path): @@ -768,13 +770,13 @@ def on_redo(self, e=None): # print("Redo") def makeplot9(self, e=None): - print("Empty Function") + print("Empty Function 9") def makeplot6(self, e=None, show=None): - print("Empty Function") + print("Empty Function 6") def makeplot8(self): - print("Empty Function") + print("Empty Function 8") class UniDecApp(MetaUniDecBase): diff --git a/unidec/UniChrom.py b/unidec/UniChrom.py index 31e6bb0f..4e0dc6bd 100644 --- a/unidec/UniChrom.py +++ b/unidec/UniChrom.py @@ -46,6 +46,9 @@ def init(self, *args, **kwargs): # path = "D:\Data\ChromTest\SYJMX160819_04.hdf5" self.open_file(path) + if False: + self.open_most_recent() + def on_open(self, e=None): """ Open dialog for file opening diff --git a/unidec/UniChromCD.py b/unidec/UniChromCD.py index 799b79f9..7eff4fca 100644 --- a/unidec/UniChromCD.py +++ b/unidec/UniChromCD.py @@ -59,7 +59,7 @@ def init(self, *args, **kwargs): print("Opening Test File") path = ("Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\" "20231202 JDS Bgal groEL bit5 zp7 inj4s cyc1m_2023-12-07-03-46-56.dmt") - path2 = "Z:\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-03-06 Bgal 5bit triplicate\\20240306 Bgal inj5 iit1 bit5 zp10.dmt" + path2 = "Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\2023-03-06 Bgal 5bit triplicate\\20240306 Bgal inj5 iit1 bit5 zp10.dmt" pathft = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\01302024_GDH_stepsize3_repeat15_5to500.dmt" pathft2 = "Z:\\Group Share\\Skippy\\Projects\\FT IM CD MS\\CCS troubleshooting\\03082024_GDH_4kV_stepsize3_repeate15_5to500_1_2024-03-14-09-04-57.dmt" # path3 = "Z:\\Group Share\\ONO\\CDMS\\5 bit HT data\\20240313 JDS Bgal 0o1g_l inj1o5 quad9k bit-5 zp10 i2.dmt" diff --git a/unidec/engine.py b/unidec/engine.py index a28173c3..18359df8 100644 --- a/unidec/engine.py +++ b/unidec/engine.py @@ -508,7 +508,10 @@ def pick_peaks(self, calc_dscore=True): if len(peaks) > 0: self.setup_peaks(peaks) if calc_dscore: - self.dscore() + try: + self.dscore() + except: + pass else: print("No peaks detected", peaks, self.config.peakwindow, self.config.peakthresh) print("Mass Data:", self.data.massdat) diff --git a/unidec/metaunidec/metafft.py b/unidec/metaunidec/metafft.py index 488fb262..c5533231 100644 --- a/unidec/metaunidec/metafft.py +++ b/unidec/metaunidec/metafft.py @@ -309,7 +309,7 @@ def on_add_line(self, e): # Main App Execution if __name__ == "__main__": - datfile = "C:\\Data\\New\POPC_D1T0-2m_ISTRAP\\20170207_P1D_POPC_ND_D1T0-2m_ISTRAP_RAMP_0_275_25_1_200.0.txt" + datfile = "C:\\Data\\New\\POPC_D1T0-2m_ISTRAP\\20170207_P1D_POPC_ND_D1T0-2m_ISTRAP_RAMP_0_275_25_1_200.0.txt" data2 = np.loadtxt(datfile) data3 = deepcopy(data2) diff --git a/unidec/metaunidec/mudeng.py b/unidec/metaunidec/mudeng.py index 20975c04..45155503 100644 --- a/unidec/metaunidec/mudeng.py +++ b/unidec/metaunidec/mudeng.py @@ -494,24 +494,5 @@ def generate_image(self, peak_index=0): if __name__ == '__main__': eng = MetaUniDec() - ''' - testpath = "C:\Python\\unidec\\unidec_src\\unidec\\x64\Release\\test.hdf5" - eng.data.new_file(testpath) - data1 = [1, 2, 3] - data2 = [4, 5, 6] - data3 = [7, 8, 9] - eng.data.add_data(data1) - eng.data.add_data(data2) - eng.data.add_data(data3) - eng.data.remove_data([0, 2]) - exit() - - testdir = "C:\Python\\UniDec3\\unidec_src\\unidec\\x64\Release" - testfile = "JAW.hdf5" - testpath = os.path.join(testdir, testfile) - eng.open(testpath) - eng.run_unidec() - eng.pick_scanpeaks() - ''' - path = "C:\Data\HTS_Sharon\\20220404-5_sequence_Shortlist.csv" + path = "C:\\Data\\HTS_Sharon\\20220404-5_sequence_Shortlist.csv" eng.csv_reader(path) diff --git a/unidec/metaunidec/mudhelp.py b/unidec/metaunidec/mudhelp.py index fa0ce57a..a756f73a 100644 --- a/unidec/metaunidec/mudhelp.py +++ b/unidec/metaunidec/mudhelp.py @@ -92,7 +92,7 @@ def get_started(self): " File->Save Figure As will allow you to select the directory " "and file name header, along with the extension and image dimensions. File->Save Figure Presets will save " "your figures as .pdf/.png/.pdf thumbnails to the default location, which is the location of the original " - ".HDF5 file. Ex: C:/HDF5-location/UniDec_Figures_and_Files/Fig1.pdf<\p>" + ".HDF5 file. Ex: C:\\HDF5-location\\UniDec_Figures_and_Files\\Fig1.pdf<\p>" "

Importing Configs

" "

File->Load External Config will load the parameters from a previous run." " This can either be a _conf.dat file from UniDec of an HDF5 file from MetaUniDec. " diff --git a/unidec/metaunidec/mudstruct.py b/unidec/metaunidec/mudstruct.py index 2dea4ced..c678d882 100644 --- a/unidec/metaunidec/mudstruct.py +++ b/unidec/metaunidec/mudstruct.py @@ -64,10 +64,10 @@ def import_hdf5(self, file=None, speedy=False): self.data2 = self.spectra[0].data2 self.import_vars(hdfobj=hdf) - try: - self.import_grids() - except: - pass + #try: + # self.import_grids() + #except: + # pass hdf.close() def export_hdf5(self, file=None, vars_only=False, delete=False): diff --git a/unidec/metaunidec/ultrameta.py b/unidec/metaunidec/ultrameta.py index b0165a61..259fea5e 100644 --- a/unidec/metaunidec/ultrameta.py +++ b/unidec/metaunidec/ultrameta.py @@ -247,7 +247,7 @@ def __init__(self, parent, title, config=None, *args, **kwargs): # testdir = "Z:\\Group Share\\Deseree\\Ciara\\Test" # testfile = "collection2.json" # testdir = "C:\\Data\\Triplicate Data" - testdir = "C:\Data\Guozhi" + testdir = "C:\\Data\\Guozhi" # testdir = "Z:\Group Share\JamesKeener Rohrbough\Peptide nanodiscs\D1T0 Mellitin\DMPC" testfile = "collection1.json" self.load(os.path.join(testdir, testfile)) diff --git a/unidec/modules/CDCal.py b/unidec/modules/CDCal.py index 92933428..f5d49da8 100644 --- a/unidec/modules/CDCal.py +++ b/unidec/modules/CDCal.py @@ -336,7 +336,7 @@ def on_plot(self, e=None): # Main App Execution if __name__ == "__main__": #calpath = "Z:\Group Share\Marius Kostelic\CD-MS\Replicates\Calibration.csv" - calpath = "C:\Data\CDMS\AqpZ_STORI\AqpZ_STORI\caltest.csv" + calpath = "C:\\Data\\CDMS\\AqpZ_STORI\\AqpZ_STORI\\caltest.csv" app = wx.App(False) frame = CDCalDialog(None) from unidec.modules.unidecstructure import UniDecConfig diff --git a/unidec/modules/CDEng.py b/unidec/modules/CDEng.py index f6442403..20adfffb 100644 --- a/unidec/modules/CDEng.py +++ b/unidec/modules/CDEng.py @@ -1303,17 +1303,6 @@ def sim_dist(self): plt.show() # eng.run_deconvolution() - exit() - - path = "C:\Data\CDMS\\20210309_MK_ADH_pos_CDMS_512ms_5min_50ms_pressure01_unidecfiles\\20210309_MK_ADH_pos_CDMS_512ms_5min_50ms_pressure01_rawdata.txt" - data = np.loadtxt(path) - noise = np.median(data[:, 1]) - print(noise) - - plt.plot(data[:, 0], data[:, 1]) - plt.axhline(noise, color='r') - plt.hlines(noise, 4000, 14000, color='r') - plt.show() exit() eng = UniDecCD() @@ -1325,24 +1314,4 @@ def sim_dist(self): eng.harray = eng.mass # eng.filter_zdist() exit() - # path = "Z:\\Group Share\\Marius Kostelic\\CD-MS\\02242021\\02242021_MK_ADH_ACN_50_IST_10min_Aqu_CD-MS.RAW" - # mzrange = [5000, 10000] - # irange = [200, 800] - path = "Z:\\Group Share\\Marius Kostelic\\CD-MS\\02242021 Data Set\\02242021_MK_BSA__CD-MS_Aqu_ACN_10min.RAW" - mzrange = [3000, 7000] - irange = [100, 600] - path = "Z:\\Group Share\Marius Kostelic\\CD-MS\\03082021\\20210308_MK_GroEL_CDMS_50msinject_10min.RAW" - mzrange = [9000, 13000] - irange = [1000, 2000] - - eng = UniDecCD() - eng.open_file(path) - eng.filter_mz(mzrange=mzrange) - eng.filter_int(int_range=irange) - eng.convert() - eng.histogram() - eng.make_kernel() - eng.decon_core() - eng.transform() - eng.plot_hist() diff --git a/unidec/modules/HTEng.py b/unidec/modules/HTEng.py index 517b93fb..2259f486 100644 --- a/unidec/modules/HTEng.py +++ b/unidec/modules/HTEng.py @@ -320,7 +320,6 @@ def htdecon(self, data, **kwargs): return output, data def masked_demultiplex_ht(self, data, win=None, n=None, demult=None, mode="rand", **kwargs): - if win is None: win = self.config.HTwin if n is None: @@ -359,10 +358,10 @@ def masked_demultiplex_ht(self, data, win=None, n=None, demult=None, mode="rand" self.setup_ht(seq=seq) out, _ = self.htdecon(data, **kwargs) outdata.append(out) - # plt.plot(outdata[-1]) + #plt.plot(outdata[-1]) outdata = np.array(outdata) # print('Shape of outdata: ', np.shape(outdata)) - + #plt.show() # Calculate number of sign changes per data point negcount = np.sum(outdata < 0, axis=0) + 1 # Divide the demultiplexed data by the negcount plus 1 @@ -1430,8 +1429,8 @@ def convert_trace_to_ccs(self, trace, mzrange, zrange, sarray=None, normalize=Fa eng = UniDecCDHT() # eng = UniChromHT() - dir = "C:\Data\HT-CD-MS" - dir = "Z:\\Group Share\Skippy\Projects\HT\Example data for MTM\\2023-10-26" + dir = "C:\\Data\\HT-CD-MS" + dir = "Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM\\2023-10-26" # dir = "Z:\\Group Share\\Skippy\\Projects\\HT\\Example data for MTM" os.chdir(dir) path = "C:\\Data\\HT-CD-MS\\20230906 JDS BSA SEC f22 10x dilute STORI high flow 1_20230906171314_2023-09-07-01-43-26.dmt" diff --git a/unidec/modules/IM_windows.py b/unidec/modules/IM_windows.py index 52fccdaf..ca698dd8 100644 --- a/unidec/modules/IM_windows.py +++ b/unidec/modules/IM_windows.py @@ -576,12 +576,12 @@ def on_plot(self, e): zoutgrid = self.totalgrid # 2D plot self.plot1.contourplot(xvals=self.massdat[:, 0], yvals=self.ccsdat[:, 0], zgrid=zoutgrid, - config=self.config, ylab="CCS (${\AA}$$^2$)", title="Mass vs. CCS", test_kda=True) + config=self.config, ylab=r"CCS (${\AA}$$^2$)", title="Mass vs. CCS", test_kda=True) # 1D CCS projection zoutgrid = np.reshape(zoutgrid, (len(self.massdat), len(self.ccsdat))) ccsproj = np.sum(zoutgrid, axis=0) self.plot2.plotrefreshtop(self.ccsdat[:, 0], ccsproj / np.amax(ccsproj), "CCS Extract", - "CCS (${\AA}$$^2$)", "Normalized Intensity", "", self.config) + r"CCS (${\AA}$$^2$)", "Normalized Intensity", "", self.config) # colormap = cm.get_cmap(self.config.peakcmap, len(outs)) colormap = mpl.colormaps[self.config.peakcmap].resampled(len(outs)) xcolors = colormap(np.arange(len(outs))) diff --git a/unidec/modules/ManualSelectionWindow.py b/unidec/modules/ManualSelectionWindow.py index 3ebfd10c..772cd3d5 100644 --- a/unidec/modules/ManualSelectionWindow.py +++ b/unidec/modules/ManualSelectionWindow.py @@ -394,11 +394,8 @@ def on_close(self, e): manuallist = np.array(manuallist) manuallist = manuallist[np.any([manuallist != 0], axis=2)[0], :] - if manuallist != "": - if len(manuallist) > 0: - self.config.manuallist = manuallist - else: - self.config.manuallist = [] + if len(manuallist) > 0: + self.config.manuallist = manuallist else: self.config.manuallist = [] diff --git a/unidec/modules/biopolymertools.py b/unidec/modules/biopolymertools.py index 548a00fa..6e243b5c 100644 --- a/unidec/modules/biopolymertools.py +++ b/unidec/modules/biopolymertools.py @@ -132,8 +132,8 @@ def read_fasta(path): f = open(path, 'r') lines = f.readlines() - hre = re.compile('>(\S+)') - lre = re.compile('^(\S+)$') + hre = re.compile(r'>(\S+)') + lre = re.compile(r'^(\S+)$') gene = {} @@ -152,10 +152,11 @@ def read_fasta(path): if __name__ == "__main__": #print(mass_HPO4 + mass_HPO4 - mass_O - mass_O + mass_H + mass_H) - #os.chdir("..\\..\\Scripts\\old\\Jessica") - #file = "ecoli.fasta" - #genes = read_fasta(file) - #print(genes) + os.chdir("..\\..\\Scripts\\old\\Jessica") + file = "ecoli.fasta" + genes = read_fasta(file) + print(genes) + exit() oligo = "aacauucaACgcugucggugAgu" mass = calc_rna_mass(oligo) print(mass) diff --git a/unidec/modules/gui_elements/mainwindow_base.py b/unidec/modules/gui_elements/mainwindow_base.py index 74e88ea3..dca85972 100644 --- a/unidec/modules/gui_elements/mainwindow_base.py +++ b/unidec/modules/gui_elements/mainwindow_base.py @@ -59,6 +59,12 @@ def __init__(self, parent, title, config, iconfile=None, tabbed=None): self.displaysize = wx.GetDisplaySize() pub.subscribe(self.on_motion, 'newxy') + recentopenid = wx.NewIdRef() + self.Bind(wx.EVT_MENU, self.pres.open_most_recent, id=recentopenid) + + self.defaultshortcuts = [["0", self.pres.open_most_recent, recentopenid]] + + # Bind to resize event # self.Bind(wx.EVT_SIZE, self.onResize) @@ -94,7 +100,7 @@ def setup_shortcuts(self, keys): Setup shortcuts in GUI. Binds key combinations to functions in presenter (self.pres) :return: None """ - + keys += self.defaultshortcuts ids = [k[2].GetId() for k in keys] tab = [] for i, k in enumerate(keys): @@ -360,7 +366,7 @@ def on_about(self, e): """ dlg = wx.MessageDialog(self, "UniDec GUI version " + self.version + - "\nPlease contact mtmarty@email.arizona.edu with any questions, bugs, or features to add.\n" + "\nPlease contact mtmarty@arizona.edu with any questions, bugs, or features to add.\n" "The latest version may be found at https://github.com/michaelmarty/UniDec/releases.\n" "RawFileReader reading tool. Copyright © 2016 by Thermo Fisher Scientific, Inc. All rights reserved.\n" "If used in publication, please cite Marty et Al. Anal. Chem. 2015, DOI: 10.1021/acs.analchem.5b00140 ", diff --git a/unidec/modules/gui_elements/ud_menu.py b/unidec/modules/gui_elements/ud_menu.py index dabdf2ac..6b82de00 100644 --- a/unidec/modules/gui_elements/ud_menu.py +++ b/unidec/modules/gui_elements/ud_menu.py @@ -338,9 +338,9 @@ def __init__(self, parent, config, pres, tabbed): "Mark matched monomers and dimers automatically") self.parent.Bind(wx.EVT_MENU, self.pres.on_autoformat, self.autoformat) - self.menuNMS_Report = self.experimentalmenu.Append(wx.ID_ANY, "Generate NMSGSB PDF Report", - "Generate PDF Report in NMSGSB format") - self.parent.Bind(wx.EVT_MENU, self.pres.on_nmsgsb_report, self.menuNMS_Report) + #self.menuNMS_Report = self.experimentalmenu.Append(wx.ID_ANY, "Generate NMSGSB PDF Report", + # "Generate PDF Report in NMSGSB format") + #self.parent.Bind(wx.EVT_MENU, self.pres.on_nmsgsb_report, self.menuNMS_Report) self.experimentalmenu.AppendSeparator() self.menuifams = self.experimentalmenu.Append(wx.ID_ANY, "iFAMS") diff --git a/unidec/modules/html_writer.py b/unidec/modules/html_writer.py index d357050c..f953f916 100644 --- a/unidec/modules/html_writer.py +++ b/unidec/modules/html_writer.py @@ -1,4 +1,3 @@ -import mpld3 import pandas as pd import matplotlib.pyplot as plt import matplotlib.colors @@ -14,6 +13,11 @@ import webbrowser import os +try: + import mpld3 +except: + print("mpld3 not found") + luminance_cutoff = 135 diff --git a/unidec/modules/isolated_packages/ZoomBox.py b/unidec/modules/isolated_packages/ZoomBox.py index c11a0293..e823d215 100644 --- a/unidec/modules/isolated_packages/ZoomBox.py +++ b/unidec/modules/isolated_packages/ZoomBox.py @@ -98,6 +98,7 @@ def __init__(self, axes, onselect, parent=None, groups=None, drawtype='box', self.visible = True self.cids = [] self.mzz = None + self.mzz2 = None self.mzcurve = None self.sarray = None self.swoop = swoop @@ -267,9 +268,15 @@ def press(self, event): # Get the current event x and y x, y = event.xdata, event.ydata self.mzz = [x, y] - # print("MZZ", self.mzz) + self.mzz2 = None + elif wx.GetKeyState(wx.WXK_ESCAPE): + # Get the current event x and y + x, y = event.xdata, event.ydata + self.mzz2 = [x, y] + self.mzz = None else: self.mzz = None + self.mzz2 = None return False def release(self, event): @@ -472,6 +479,26 @@ def onmove(self, event): self.mzcurve, self.sarray = self.parent.draw_mz_curve(sarray) self.canvas.draw() return + elif self.mzz2 is not None and self.swoop: + spany = maxy - miny + roughmass = self.mzz2[0] * self.mzz2[1] + minz = roughmass / maxx + maxz = roughmass / minx + minz = np.ceil(minz) + maxz = np.floor(maxz) + zwidth = maxz - minz + #print(roughmass, spany, zwidth, minz, maxz) + # print(self.mzz, spany, zwidth) + if self.mzcurve is not None: + self.mzcurve.remove() + + zmid = (minz + maxz) / 2 + mzmid = roughmass/zmid + + sarray = [mzmid, zmid, spany, zwidth] + self.mzcurve, self.sarray = self.parent.draw_mz_curve(sarray) + self.canvas.draw() + return else: self.sarray = None diff --git a/unidec/modules/unidec_enginebase.py b/unidec/modules/unidec_enginebase.py index c92b1a23..9a1f78d1 100644 --- a/unidec/modules/unidec_enginebase.py +++ b/unidec/modules/unidec_enginebase.py @@ -692,7 +692,8 @@ def gen_html_report(self, event=None, outfile=None, plots=None, interactive=Fals for f in figure_list: try: self.html_str += fig_to_html_mpld3(f, outfile) - except Exception: + except Exception as e: + print("Unable to create interactive figures", e) pass # Write results string paragraph diff --git a/unidec/modules/unidec_presbase.py b/unidec/modules/unidec_presbase.py index d04c6ac6..9b41933a 100644 --- a/unidec/modules/unidec_presbase.py +++ b/unidec/modules/unidec_presbase.py @@ -829,6 +829,14 @@ def cleanup_recent_file(self, recent_files): f.write(l) f.truncate() + def open_most_recent(self, e=None): + recent_files = self.read_recent() + if not ud.isempty(recent_files): + directory, file = os.path.split(recent_files[0]) + self.on_open_file(file, directory, refresh=True) + else: + print("No Recent Files") + def auto_refresh_stop(self, e=None): self.timer.Stop() diff --git a/unidec/tools.py b/unidec/tools.py index 89a3d36b..5bd40d25 100644 --- a/unidec/tools.py +++ b/unidec/tools.py @@ -59,7 +59,7 @@ '7': '0000001000001100001010001111001000101100111010100111110100001110001001001101101011011110110001101001011101110011001010101111111', '8': '000000010111000111011110001011001101100001111001110000101011111111001011110100101000011011101101111101011101000001100101010100011010110001100000100101101101010011010011111101110011001111011001000010000001110010010011000100111010101101000100010100100011111', '-2': '010', '-3': '0001011', '-4': '111011001010000', '-5': '1111011010011000001110010001010', - '-105': '1010111011000111110011010010000' + '-103': '0010111', '-105': '1010111011000111110011010010000' } @@ -1930,7 +1930,7 @@ def exe_call(call, silent=False): :param silent: Whether to print the output of exepath to the standard out :return: Standard error of exepath execution """ - print("System Call:", *call) + print("System Call:", call) result = subprocess.run(call, shell=False, capture_output=True, text=True) out = result.returncode if not silent: